diff --git a/.cspell.json b/.cspell.json index a16205fd9..0b309d6cb 100644 --- a/.cspell.json +++ b/.cspell.json @@ -6,39 +6,51 @@ "en_us", // code "go", - "node", - // package names - "npm" + "node" ], "words": [ "abool", + "addgroup", + "adduser", "anbraten", "antfu", "apimachinery", + "appleboy", + "Archlinux", "autoincr", + "automerge", "autoscaler", + "backporting", + "backports", "binutils", "bitbucketdatacenter", "Boguslawski", "bradrydzewski", "BUILDPLATFORM", "buildx", + "caddyfile", "ccmenu", "certmagic", "charmbracelet", + "cicd", "ciphertext", + "Cloudron", "Codeberg", "compatiblelicenses", "corepack", "cpuset", "creativecommons", "Curr", + "CERTDIR", "datacenter", "DATASOURCE", "Debugf", "desaturate", "devx", + "dind", + "Dockle", "doublestar", + "emojify", "envsubst", "errgroup", "estree", @@ -46,64 +58,90 @@ "excalidraw", "favicons", "Fediverse", + "Feishu", "Fogas", "forbidigo", "Forgejo", "fsnotify", + "Geeklab", "Georgiana", "gitea", + "gitmodules", "GOARCH", "GOBIN", "gocritic", "GODEBUG", + "godoc", + "Gogs", "golangci", "gomod", "gonic", "GOPATH", + "Gource", + "handlebargh", "HEALTHCHECK", "healthz", "Hetzner", + "HETZNERCLOUD", + "homelab", "HTMLURL", "HTTPFS", - "httpsig", + "httpsign", "HTTPURL", "httputil", "ianvs", "iconify", + "inetutils", + "Infima", "Infof", "Informatyka", "intlify", "Ionescu", + "Jetpack", "Kaniko", "Keyfunc", "kyvg", "LASTEXITCODE", "Laszlo", "laszlocph", + "letsencrypt", + "loadbalancer", "logfile", "loglevel", "LONGBLOB", "LONGTEXT", + "lonix1", "mapstructure", "markdownlint", + "mdbook", "memswap", "Metas", "mhmxs", "moby", "Msgf", + "mstruebing", "multiarch", "multierr", "netdns", "Netrc", + "Nextcloud", "nfpm", "nixos", + "nixpkgs", "nocolor", "nolint", "norunningpipelines", "nosniff", + "ntfy", "octocat", + "openapi", + "opensource", + "Pacman", + "picus", "Pinia", "pkce", + "pnpx", + "Polyform", "posix", "ppid", "Println", @@ -123,19 +161,26 @@ "repology", "reslimit", "Reviewdog", + "Rieter", "riscv", "rundll32", "Rydzewski", "seccomp", "secprofile", "securecookie", + "selfhosted", "sess", "shellescape", + "sigstore", + "Sonatype", "SSHURL", + "sslmode", "stepbuilder", "stretchr", + "structs", "sublicensable", "swaggo", + "syscalls", "TARGETARCH", "TARGETOS", "techknowlogick", @@ -143,38 +188,49 @@ "threadcreate", "tink", "tinycolor", + "tmole", "tmpfs", "tmpl", "tolerations", + "Traefik", "tseslint", "ttlcache", + "TUNEIT", + "Tunnelmole", "typecheck", "Typeflag", "unplugin", "Upsert", "urfave", + "usecase", "varchar", "varz", + "Vieter", + "virtualisation", + "visualisation", + "vite", "vueuse", "waivable", "Warnf", + "webhookd", "Weblate", "windi", "windicss", "woodpeckerci", "WORKDIR", "Wrapf", + "x-enum-varnames", "xlink", "xlog", "xorm", "xormigrate", "xyaml", "yamls", + "Yuno", "zerolog", "zerologger" ], "ignorePaths": [ - "**/node_modules/**/*", "*.excalidraw", "*.svg", "*_test.go", @@ -185,19 +241,26 @@ ".vscode/extensions.json", "CHANGELOG.md", "Makefile", - "flake.lock", "flake.nix", "go.mod", - "go.sum", - "pipeline/rpc/proto/woodpecker.pb.go", - "pnpm-lock.yaml", + "**/*.pb.go", "server/store/datastore/migration/**/*", "web/components.d.ts", "web/src/assets/locales/**/*", "**/fixtures/**", "**/testdata/**", + "docs/versioned_docs/", + "package.json", + // generated + "go.sum", + "flake.lock", + "pnpm-lock.yaml", + "**/node_modules/**/*", + "cmd/server/openapi/docs.go", + "renovate.json", // TODO: remove the following - "docs/" + "docs/**/*.js", + "docs/**/*.ts" ], // Exclude imports, because they are also strings. "ignoreRegExpList": [ @@ -210,7 +273,9 @@ // ignore nolint directive "//\\s*nolint:.*", // ignore docker image names - "\\s*docker\\.io/.*" + "\\s*docker\\.io/.*", + // ignore inline svg in css + "\\s*url\\(\"data:image/svg\\+xml.*" ], "enableFiletypes": ["dockercompose"] } diff --git a/.ecrc b/.ecrc index 21b8d92b8..53c5abd81 100644 --- a/.ecrc +++ b/.ecrc @@ -1,14 +1,15 @@ { "Exclude": [ ".git", - "go.mod", "go.sum", + "go.mod", + "go.sum", "vendor", "fixtures", "LICENSE", "node_modules", "server/store/datastore/migration/test-files/sqlite.db", "server/store/datastore/feed.go", - "cmd/server/docs/docs.go", + "cmd/server/openapi/docs.go", "_test.go", "Makefile" ] diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..46dd8d027 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,11 @@ + diff --git a/.github/renovate.json b/.github/renovate.json index 758ff2b22..6d0ed9fd1 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -1,12 +1,13 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": ["github>woodpecker-ci/renovate-config"], + "automergeType": "pr", "customManagers": [ { "customType": "regex", "fileMatch": ["shared/constant/constant.go"], "matchStrings": [ - "//\\s*renovate:\\s*datasource=(?.*?) depName=(?.*?)( versioning=(?.*?))?\\s+DefaultCloneImage = \"docker.io/woodpeckerci/plugin-git:(?.*)\"" + "//\\s*renovate:\\s*datasource=(?.*?) depName=(?.*?)( versioning=(?.*?))?\\s+DefaultClonePlugin = \"docker.io/woodpeckerci/plugin-git:(?.*)\"" ], "versioningTemplate": "{{#if versioning}}{{{versioning}}}{{else}}semver{{/if}}" } @@ -29,8 +30,8 @@ }, { "groupName": "golang-lang", - "matchPackagePatterns": ["^golang$", "xgo"], - "matchUpdateTypes": ["minor", "patch"] + "matchUpdateTypes": ["minor", "patch"], + "matchPackageNames": ["/^golang$/", "/xgo/"] }, { "groupName": "golang-packages", @@ -60,9 +61,10 @@ "matchFileNames": ["docs/**/package.json"] }, { + "description": "Extract version from xgo container tags", "matchDatasources": ["docker"], - "matchPackagePatterns": ["xgo"], - "versioning": "regex:^go-(?\\d+)?(\\.(?\\d+))?(\\.(?\\d+))$" + "versioning": "regex:^go-(?\\d+)\\.(?\\d+)\\.x$", + "matchPackageNames": ["/techknowlogick/xgo/"] } ] } diff --git a/.gitignore b/.gitignore index 4cbc9626d..08a30ad70 100644 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,7 @@ docs/venv ### Generated by CI ### docs/docs/40-cli.md +docs/openapi.json + +# Removed once v3.0.x is minimum version to be touched docs/swagger.json diff --git a/.gitpod.yml b/.gitpod.yml index b99e67542..118298258 100644 --- a/.gitpod.yml +++ b/.gitpod.yml @@ -112,9 +112,10 @@ vscode: - 'EditorConfig.EditorConfig' - 'dbaeumer.vscode-eslint' - 'esbenp.prettier-vscode' - - 'voorjaar.windicss-intellisense' + - 'bradlc.vscode-tailwindcss' - 'Vue.volar' - 'redhat.vscode-yaml' - 'davidanson.vscode-markdownlint' - 'streetsidesoftware.code-spell-checker' + - 'stivo.tailwind-fold' # cSpell:enable diff --git a/.lycheeignore b/.lycheeignore new file mode 100644 index 000000000..fea8b85ab --- /dev/null +++ b/.lycheeignore @@ -0,0 +1 @@ +https://stackoverflow.com/* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 305726233..32d9acf2f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,16 +5,16 @@ repos: - id: check-hooks-apply - id: check-useless-excludes - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v5.0.0 hooks: - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/golangci/golangci-lint - rev: v1.59.1 + rev: v1.63.4 hooks: - id: golangci-lint - repo: https://github.com/igorshubovych/markdownlint-cli - rev: v0.41.0 + rev: v0.43.0 hooks: - id: markdownlint exclude: '^(docs/versioned_docs/.*|CHANGELOG.md)$' @@ -24,11 +24,11 @@ repos: - id: checkmake exclude: '^docker/Dockerfile.make$' # actually a Dockerfile and not a makefile - repo: https://github.com/hadolint/hadolint - rev: v2.13.0-beta + rev: v2.13.1-beta hooks: - id: hadolint - - repo: https://github.com/pre-commit/mirrors-prettier - rev: v4.0.0-alpha.8 + - repo: https://github.com/rbubley/mirrors-prettier + rev: v3.4.2 hooks: - id: prettier - repo: https://github.com/adrienverge/yamllint.git diff --git a/.prettierignore b/.prettierignore index f1549c906..48f67fc13 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,10 +1,8 @@ build/ -docs/versioned_docs/ -docs/.docusaurus/ -docs/pnpm-lock.yaml dist/ CHANGELOG.md -# web/ must be directly formatted from there +# web/ and docs/ must be directly formatted from there # to prevent conflicts with different prettier version web/ +docs/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json index fba35f380..0f775c65f 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -5,10 +5,11 @@ "EditorConfig.EditorConfig", "dbaeumer.vscode-eslint", "esbenp.prettier-vscode", - "voorjaar.windicss-intellisense", + "bradlc.vscode-tailwindcss", "Vue.volar", "redhat.vscode-yaml", - "davidanson.vscode-markdownlint" + "davidanson.vscode-markdownlint", + "stivo.tailwind-fold" ], // List of extensions recommended by VS Code that should not be recommended for users of this workspace. "unwantedRecommendations": [] diff --git a/.woodpecker/binaries.yaml b/.woodpecker/binaries.yaml index b1f50d80f..e296a3396 100644 --- a/.woodpecker/binaries.yaml +++ b/.woodpecker/binaries.yaml @@ -1,12 +1,17 @@ when: - event: tag + - event: tag + - event: pull_request + branch: ${CI_REPO_DEFAULT_BRANCH} + path: + - Makefile + - .woodpecker/binaries.yaml variables: - - &golang_image 'docker.io/golang:1.22' - - &node_image 'docker.io/node:22-alpine' - - &xgo_image 'docker.io/techknowlogick/xgo:go-1.22.x' + - &golang_image 'docker.io/golang:1.23' + - &node_image 'docker.io/node:23-alpine' + - &xgo_image 'docker.io/techknowlogick/xgo:go-1.23.x' -# cspell:words bindata netgo TARGZ +# cspell:words bindata netgo steps: build-web: @@ -35,7 +40,7 @@ steps: environment: PLATFORMS: linux|arm64/v8;linux|amd64;windows|amd64 TAGS: bindata sqlite sqlite_unlock_notify netgo - TARGZ: '1' + ARCHIVE_IT: '1' build-tarball: depends_on: @@ -50,6 +55,8 @@ steps: - vendor image: *golang_image commands: + - apt update + - apt install -y zip - make release-agent build-cli: @@ -57,6 +64,8 @@ steps: - vendor image: *golang_image commands: + - apt update + - apt install -y zip - make release-cli build-deb-rpm: @@ -90,13 +99,16 @@ steps: release: depends_on: - checksums - image: woodpeckerci/plugin-release:0.1.0 + image: woodpeckerci/plugin-release:0.2.2 settings: api_key: from_secret: github_token files: - dist/*.tar.gz + - dist/*.zip - dist/*.deb - dist/*.rpm - dist/checksums.txt title: ${CI_COMMIT_TAG##v} + when: + event: tag diff --git a/.woodpecker/docker.yaml b/.woodpecker/docker.yaml index 6d023816c..4072c78fc 100644 --- a/.woodpecker/docker.yaml +++ b/.woodpecker/docker.yaml @@ -1,15 +1,15 @@ variables: - - &golang_image 'docker.io/golang:1.22' - - &node_image 'docker.io/node:22-alpine' - - &xgo_image 'docker.io/techknowlogick/xgo:go-1.22.x' - - &buildx_plugin 'docker.io/woodpeckerci/plugin-docker-buildx:4.0.0' + - &golang_image 'docker.io/golang:1.23' + - &node_image 'docker.io/node:23-alpine' + - &xgo_image 'docker.io/techknowlogick/xgo:go-1.23.x' + - &buildx_plugin 'docker.io/woodpeckerci/plugin-docker-buildx:5.1.0' - &platforms_release 'linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/386,linux/amd64,linux/ppc64le,linux/riscv64,linux/s390x,freebsd/arm64,freebsd/amd64,openbsd/arm64,openbsd/amd64' - &platforms_server 'linux/arm/v7,linux/arm64/v8,linux/amd64,linux/ppc64le,linux/riscv64' - &platforms_preview 'linux/amd64' - &platforms_alpine 'linux/arm/v6,linux/arm/v7,linux/arm64/v8,linux/amd64,linux/ppc64le' - &build_args 'CI_COMMIT_SHA=${CI_COMMIT_SHA},CI_COMMIT_BRANCH=${CI_COMMIT_BRANCH},CI_COMMIT_TAG=${CI_COMMIT_TAG}' - # cspell:words woodpeckerbot netgo TARGZ + # cspell:words woodpeckerbot netgo # vars used on push / tag events only - publish_logins: &publish_logins # Default DockerHub login @@ -41,9 +41,6 @@ variables: when: - event: [pull_request, tag] - - event: push - branch: - - renovate/* - event: push branch: ${CI_REPO_DEFAULT_BRANCH} path: *when_path @@ -61,7 +58,6 @@ steps: path: *when_path - branch: - ${CI_REPO_DEFAULT_BRANCH} - - renovate/* event: [push, tag] path: *when_path @@ -82,7 +78,6 @@ steps: path: *when_path - branch: - ${CI_REPO_DEFAULT_BRANCH} - - renovate/* event: [push, tag] path: *when_path @@ -104,9 +99,6 @@ steps: evaluate: 'CI_COMMIT_PULL_REQUEST_LABELS contains "build_pr_images"' - event: pull_request path: *when_path - - event: push - path: *when_path - branch: renovate/* cross-compile-server: depends_on: @@ -127,31 +119,19 @@ steps: event: [push, tag] path: *when_path - publish-server-preview: - depends_on: - - cross-compile-server-preview - image: *buildx_plugin - settings: - repo: woodpeckerci/woodpecker-server - dockerfile: docker/Dockerfile.server.multiarch - platforms: *platforms_preview - tag: pull_${CI_COMMIT_PULL_REQUEST} - logins: *publish_logins - when: &when-preview - evaluate: 'CI_COMMIT_PULL_REQUEST_LABELS contains "build_pr_images"' - event: pull_request - publish-server-alpine-preview: depends_on: - cross-compile-server-preview image: *buildx_plugin settings: repo: woodpeckerci/woodpecker-server - dockerfile: docker/Dockerfile.server.alpine.multiarch + dockerfile: docker/Dockerfile.server.alpine.multiarch.rootless platforms: *platforms_preview tag: pull_${CI_COMMIT_PULL_REQUEST}-alpine logins: *publish_logins - when: *when-preview + when: &when-preview + evaluate: 'CI_COMMIT_PULL_REQUEST_LABELS contains "build_pr_images"' + event: pull_request build-server-dryrun: depends_on: @@ -162,16 +142,13 @@ steps: settings: dry_run: true repo: woodpeckerci/woodpecker-server - dockerfile: docker/Dockerfile.server.multiarch + dockerfile: docker/Dockerfile.server.multiarch.rootless platforms: *platforms_preview tag: pull_${CI_COMMIT_PULL_REQUEST} when: &when-dryrun - evaluate: 'not (CI_COMMIT_PULL_REQUEST_LABELS contains "build_pr_images")' event: pull_request path: *when_path - - event: push - path: *when_path - branch: renovate/* publish-next-server: depends_on: @@ -179,7 +156,7 @@ steps: image: *buildx_plugin settings: repo: *publish_repos_server - dockerfile: docker/Dockerfile.server.multiarch + dockerfile: docker/Dockerfile.server.multiarch.rootless platforms: *platforms_server tag: [next, 'next-${CI_COMMIT_SHA:0:10}'] logins: *publish_logins @@ -194,7 +171,7 @@ steps: image: *buildx_plugin settings: repo: *publish_repos_server - dockerfile: docker/Dockerfile.server.alpine.multiarch + dockerfile: docker/Dockerfile.server.alpine.multiarch.rootless platforms: *platforms_alpine tag: [next-alpine, 'next-${CI_COMMIT_SHA:0:10}-alpine'] logins: *publish_logins @@ -206,10 +183,9 @@ steps: image: *buildx_plugin settings: repo: *publish_repos_server - dockerfile: docker/Dockerfile.server.multiarch + dockerfile: docker/Dockerfile.server.multiarch.rootless platforms: *platforms_server - # remove 'latest' on older version branches to avoid accidental downgrade - tag: [latest, '${CI_COMMIT_TAG}'] + tag: ['${CI_COMMIT_TAG%%.*}', '${CI_COMMIT_TAG%.*}-alpine', '${CI_COMMIT_TAG}'] logins: *publish_logins when: &when-release event: tag @@ -220,10 +196,9 @@ steps: image: *buildx_plugin settings: repo: *publish_repos_server - dockerfile: docker/Dockerfile.server.alpine.multiarch + dockerfile: docker/Dockerfile.server.alpine.multiarch.rootless platforms: *platforms_alpine - # remove 'latest-alpine' on older version branches to avoid accidental downgrade - tag: [latest-alpine, '${CI_COMMIT_TAG}-alpine'] + tag: ['${CI_COMMIT_TAG%%.*}-alpine', '${CI_COMMIT_TAG%.*}-alpine', '${CI_COMMIT_TAG}-alpine'] logins: *publish_logins when: *when-release @@ -231,15 +206,15 @@ steps: # A g e n t # ############# - publish-agent-preview: + publish-agent-preview-alpine: depends_on: - vendor image: *buildx_plugin settings: repo: woodpeckerci/woodpecker-agent - dockerfile: docker/Dockerfile.agent.multiarch + dockerfile: docker/Dockerfile.agent.alpine.multiarch platforms: *platforms_preview - tag: pull_${CI_COMMIT_PULL_REQUEST} + tag: pull_${CI_COMMIT_PULL_REQUEST}-alpine build_args: *build_args logins: *publish_logins when: *when-preview @@ -303,8 +278,7 @@ steps: repo: *publish_repos_agent dockerfile: docker/Dockerfile.agent.multiarch platforms: *platforms_release - # remove 'latest' on older version branches to avoid accidental downgrade - tag: [latest, '${CI_COMMIT_TAG}'] + tag: ['${CI_COMMIT_TAG%%.*}', '${CI_COMMIT_TAG%.*}', '${CI_COMMIT_TAG}'] logins: *publish_logins build_args: *build_args when: *when-release @@ -320,8 +294,7 @@ steps: repo: *publish_repos_agent dockerfile: docker/Dockerfile.agent.alpine.multiarch platforms: *platforms_alpine - # remove 'latest-alpine' on older version branches to avoid accidental downgrade - tag: [latest-alpine, '${CI_COMMIT_TAG}-alpine'] + tag: ['${CI_COMMIT_TAG%%.*}-alpine', '${CI_COMMIT_TAG%.*}-alpine', '${CI_COMMIT_TAG}-alpine'] logins: *publish_logins build_args: *build_args when: *when-release @@ -330,19 +303,6 @@ steps: # C L I # ######### - publish-cli-preview: - depends_on: - - vendor - image: *buildx_plugin - settings: - repo: woodpeckerci/woodpecker-cli - dockerfile: docker/Dockerfile.cli.multiarch - platforms: *platforms_preview - tag: pull_${CI_COMMIT_PULL_REQUEST} - build_args: *build_args - logins: *publish_logins - when: *when-preview - build-cli-dryrun: depends_on: - vendor @@ -350,7 +310,7 @@ steps: settings: dry_run: true repo: woodpeckerci/woodpecker-cli - dockerfile: docker/Dockerfile.cli.multiarch + dockerfile: docker/Dockerfile.cli.multiarch.rootless platforms: *platforms_preview tag: pull_${CI_COMMIT_PULL_REQUEST} build_args: *build_args @@ -365,7 +325,7 @@ steps: image: *buildx_plugin settings: repo: *publish_repos_cli - dockerfile: docker/Dockerfile.cli.multiarch + dockerfile: docker/Dockerfile.cli.multiarch.rootless platforms: *platforms_release tag: [next, 'next-${CI_COMMIT_SHA:0:10}'] logins: *publish_logins @@ -381,7 +341,7 @@ steps: image: *buildx_plugin settings: repo: *publish_repos_cli - dockerfile: docker/Dockerfile.cli.alpine.multiarch + dockerfile: docker/Dockerfile.cli.alpine.multiarch.rootless platforms: *platforms_alpine tag: [next-alpine, 'next-${CI_COMMIT_SHA:0:10}-alpine'] logins: *publish_logins @@ -397,10 +357,9 @@ steps: image: *buildx_plugin settings: repo: *publish_repos_cli - dockerfile: docker/Dockerfile.cli.multiarch + dockerfile: docker/Dockerfile.cli.multiarch.rootless platforms: *platforms_release - # remove 'latest' on older version branches to avoid accidental downgrade - tag: [latest, '${CI_COMMIT_TAG}'] + tag: ['${CI_COMMIT_TAG%%.*}', '${CI_COMMIT_TAG%.*}', '${CI_COMMIT_TAG}'] logins: *publish_logins build_args: *build_args when: *when-release @@ -414,10 +373,9 @@ steps: image: *buildx_plugin settings: repo: *publish_repos_cli - dockerfile: docker/Dockerfile.cli.alpine.multiarch + dockerfile: docker/Dockerfile.cli.alpine.multiarch.rootless platforms: *platforms_alpine - # remove 'latest-alpine' on older version branches to avoid accidental downgrade - tag: [latest-alpine, '${CI_COMMIT_TAG}-alpine'] + tag: ['${CI_COMMIT_TAG%%.*}-alpine', '${CI_COMMIT_TAG%.*}-alpine', '${CI_COMMIT_TAG}-alpine'] logins: *publish_logins build_args: *build_args when: *when-release diff --git a/.woodpecker/docs.yaml b/.woodpecker/docs.yaml index bc59052d6..583560aca 100644 --- a/.woodpecker/docs.yaml +++ b/.woodpecker/docs.yaml @@ -1,7 +1,7 @@ variables: - - &golang_image 'docker.io/golang:1.22' - - &node_image 'docker.io/node:21-alpine' - - &alpine_image 'docker.io/alpine:3.19' + - &golang_image 'docker.io/golang:1.23' + - &node_image 'docker.io/node:23-alpine' + - &alpine_image 'docker.io/alpine:3.21' - path: &when_path - 'docs/**' - '.woodpecker/docs.yaml' @@ -31,13 +31,22 @@ when: - <<: *docker_path branch: - ${CI_REPO_DEFAULT_BRANCH} - - renovate/* - event: pull_request_closed path: *when_path - event: manual evaluate: 'TASK == "docs"' steps: + prettier: + image: docker.io/woodpeckerci/plugin-prettier:next + settings: + version: 3.3.3 + plugins: + - 'prettier-plugin-tailwindcss' + - '@ianvs/prettier-plugin-sort-imports' + when: + - event: pull_request + build-cli: image: *golang_image commands: @@ -60,7 +69,7 @@ steps: - event: manual deploy-preview: - image: docker.io/woodpeckerci/plugin-surge-preview:1.3.0 + image: docker.io/woodpeckerci/plugin-surge-preview:1.3.3 settings: path: 'docs/build/' surge_token: @@ -69,13 +78,14 @@ steps: from_secret: GITHUB_TOKEN_SURGE failure: ignore when: - event: [pull_request, pull_request_closed] - path: *when_path + - event: [pull_request, pull_request_closed] + path: *when_path deploy-prepare: image: *alpine_image - secrets: - - BOT_PRIVATE_KEY + environment: + BOT_PRIVATE_KEY: + from_secret: BOT_PRIVATE_KEY commands: - apk add openssh-client git - mkdir -p $HOME/.ssh @@ -127,8 +137,9 @@ steps: deploy: image: *alpine_image - secrets: - - BOT_PRIVATE_KEY + environment: + BOT_PRIVATE_KEY: + from_secret: BOT_PRIVATE_KEY commands: - apk add openssh-client rsync git - mkdir -p $HOME/.ssh diff --git a/.woodpecker/links.yaml b/.woodpecker/links.yaml new file mode 100644 index 000000000..b97a2e9cd --- /dev/null +++ b/.woodpecker/links.yaml @@ -0,0 +1,31 @@ +when: + - event: cron + cron: links + +steps: + - name: links + image: docker.io/lycheeverse/lychee:0.15.1 + failure: ignore + depends_on: [] + commands: + - lychee pipeline/frontend/yaml/linter/schema/schema.json > links.md + - lychee --exclude localhost docs/docs/ >> links.md + - lychee --exclude localhost docs/src/pages/ >> links.md + - echo -e "\nLast checked:$(date)" >> links.md + + - name: Update issue + image: docker.io/alpine:3.21 + depends_on: links + environment: + GITHUB_TOKEN: + from_secret: github_token + commands: + - apk add -q --no-cache jq curl + - export ISSUE_NUMBER=4514 + - export DESCRIPTION=$(cat links.md) + - | + curl -X PATCH \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github.v3+json" \ + https://api.github.com/repos/${CI_REPO}/issues/$ISSUE_NUMBER \ + -d "$(jq -n --arg body "$DESCRIPTION" '{body: $body}')" diff --git a/.woodpecker/release-helper.yaml b/.woodpecker/release-helper.yaml index d733ec184..45a8a1f44 100644 --- a/.woodpecker/release-helper.yaml +++ b/.woodpecker/release-helper.yaml @@ -1,16 +1,15 @@ +when: + - event: push + branch: + - ${CI_REPO_DEFAULT_BRANCH} + - release/* + steps: - release-helper: - image: woodpeckerci/plugin-ready-release-go:1.1.2 - pull: true + - name: release-helper + image: docker.io/woodpeckerci/plugin-ready-release-go:3.1.1 settings: - release_branch: ${CI_REPO_DEFAULT_BRANCH} + release_branch: ${CI_COMMIT_BRANCH} forge_type: github git_email: woodpecker-bot@obermui.de github_token: from_secret: GITHUB_TOKEN - -when: - - event: push - branch: ${CI_REPO_DEFAULT_BRANCH} - - event: manual - evaluate: 'TASK == "release-helper"' diff --git a/.woodpecker/securityscan.yaml b/.woodpecker/securityscan.yaml index fb2e28971..143355148 100644 --- a/.woodpecker/securityscan.yaml +++ b/.woodpecker/securityscan.yaml @@ -1,24 +1,25 @@ when: - - event: [pull_request, cron] + - event: [pull_request] - event: push branch: - ${CI_REPO_DEFAULT_BRANCH} - - renovate/* variables: - - &trivy_plugin docker.io/woodpeckerci/plugin-trivy:1.1.0 + - &trivy_plugin docker.io/woodpeckerci/plugin-trivy:1.3.0 steps: backend: depends_on: [] image: *trivy_plugin settings: + server: server skip-dirs: web/,docs/ docs: depends_on: [] image: *trivy_plugin settings: + server: server skip-dirs: node_modules/,plugins/woodpecker-plugins/node_modules/ dir: docs/ @@ -26,5 +27,15 @@ steps: depends_on: [] image: *trivy_plugin settings: + server: server skip-dirs: node_modules/ dir: web/ + +services: + server: + image: *trivy_plugin + settings: + service: true + db-repository: docker.io/aquasec/trivy-db:2 + ports: + - 10000 diff --git a/.woodpecker/static.yaml b/.woodpecker/static.yaml index 17b9f0e9f..3918b864f 100644 --- a/.woodpecker/static.yaml +++ b/.woodpecker/static.yaml @@ -1,19 +1,15 @@ when: - event: pull_request - - event: push - branch: renovate/* steps: - name: lint-editorconfig - image: docker.io/mstruebing/editorconfig-checker:v3.0.3 + image: docker.io/woodpeckerci/plugin-editorconfig-checker:0.2.0 depends_on: [] when: - event: pull_request - - event: push - branch: renovate/* - name: spellcheck - image: docker.io/node:22-alpine + image: docker.io/node:23-alpine depends_on: [] commands: - corepack enable @@ -23,15 +19,11 @@ steps: - tree --gitignore -I 012_columns_rename_procs_to_steps.go -I versioned_docs -I '*opensource.svg'| pnpx cspell lint --no-progress stdin - name: prettier - image: docker.io/woodpeckerci/plugin-prettier:0.1.0 + image: docker.io/woodpeckerci/plugin-prettier:next + pull: true depends_on: [] settings: - version: 3.2.5 - - - name: links - image: docker.io/lycheeverse/lychee:0.15.1 - depends_on: [] - commands: - - lychee pipeline/frontend/yaml/linter/schema/schema.json - - lychee --exclude localhost docs/docs/ - - lychee --exclude localhost docs/src/pages/ + version: 3.3.3 + plugins: + - 'prettier-plugin-tailwindcss' + - '@ianvs/prettier-plugin-sort-imports' diff --git a/.woodpecker/test.yaml b/.woodpecker/test.yaml index cf0d443fa..724f7a71b 100644 --- a/.woodpecker/test.yaml +++ b/.woodpecker/test.yaml @@ -1,5 +1,5 @@ variables: - - &golang_image 'docker.io/golang:1.22' + - &golang_image 'docker.io/golang:1.23' - &when - path: &when_path # related config files - '.woodpecker/test.yaml' @@ -10,14 +10,9 @@ variables: # schema changes - 'pipeline/schema/**' event: pull_request - - event: push - branch: renovate/* - path: *when_path when: - event: pull_request - - event: push - branch: renovate/* - event: push branch: ${CI_REPO_DEFAULT_BRANCH} path: *when_path @@ -37,9 +32,11 @@ steps: - vendor image: *golang_image commands: - - go run go.woodpecker-ci.org/woodpecker/v2/cmd/cli lint + - go run go.woodpecker-ci.org/woodpecker/v3/cmd/cli lint environment: WOODPECKER_DISABLE_UPDATE_CHECK: true + WOODPECKER_LINT_STRICT: true + WOODPECKER_PLUGINS_PRIVILEGED: 'docker.io/woodpeckerci/plugin-docker-buildx' when: - event: pull_request path: @@ -61,14 +58,14 @@ steps: - make lint when: *when - check-swagger: + check-openapi: depends_on: - vendor image: *golang_image commands: - - 'make generate-swagger' + - 'make generate-openapi' - 'DIFF=$(git diff | head)' - - '[ -n "$DIFF" ] && { echo "swagger not up to date, exec `make generate-swagger` and commit"; exit 1; } || true' + - '[ -n "$DIFF" ] && { echo "openapi not up to date, exec `make generate-openapi` and commit"; exit 1; } || true' when: *when lint-license-header: @@ -128,7 +125,7 @@ steps: - test - sqlite pull: true - image: docker.io/woodpeckerci/plugin-codecov:2.1.2 + image: docker.io/woodpeckerci/plugin-codecov:2.1.6 settings: files: - agent-coverage.out @@ -144,7 +141,7 @@ steps: services: postgres: - image: docker.io/postgres:16 + image: docker.io/postgres:17 ports: ['5432'] environment: POSTGRES_USER: postgres @@ -152,7 +149,7 @@ services: when: *when mysql: - image: docker.io/mysql:8.2.0 + image: docker.io/mysql:9.1.0 ports: ['3306'] environment: MYSQL_DATABASE: test diff --git a/.woodpecker/web.yaml b/.woodpecker/web.yaml index b64f33f5c..6812de3ca 100644 --- a/.woodpecker/web.yaml +++ b/.woodpecker/web.yaml @@ -3,10 +3,9 @@ when: - event: push branch: - release/* - - renovate/* variables: - - &node_image 'docker.io/node:22-alpine' + - &node_image 'docker.io/node:23-alpine' - &when path: # related config files @@ -25,6 +24,18 @@ steps: - pnpm install --frozen-lockfile when: *when + prettier: + depends_on: + - install-dependencies + image: docker.io/woodpeckerci/plugin-prettier:next + pull: true + settings: + version: 3.3.3 + plugins: + - 'prettier-plugin-tailwindcss' + - '@ianvs/prettier-plugin-sort-imports' + when: *when + lint: depends_on: - install-dependencies @@ -58,6 +69,7 @@ steps: test: depends_on: - install-dependencies + - format-check # wait for it else test artifacts are falsely detected as wrong image: *node_image directory: web/ commands: diff --git a/.yamllint.yaml b/.yamllint.yaml index 37a80852c..24a86199e 100644 --- a/.yamllint.yaml +++ b/.yamllint.yaml @@ -6,6 +6,7 @@ ignore-from-file: - .gitignore - server/store/datastore/migration/test-files/.gitignore - web/.gitignore + - web/.yamlignore rules: line-length: disable diff --git a/CHANGELOG.md b/CHANGELOG.md index da9f14e50..63dd72544 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,490 @@ # Changelog +## [3.0.0](https://github.com/woodpecker-ci/woodpecker/releases/tag/v3.0.0) - 2024-12-13 + +### ❤️ Thanks to all contributors! ❤️ + +@6543, @Fishbowler, @M0Rf30, @anbraten, @cduchenoy, @fernandrone, @gnowland, @greenaar, @hg, @j04n-f, @jenrik, @johanneskastl, @jolheiser, @lafriks, @lukashass, @meln5674, @not-my-profile, @pat-s, @qwerty287, @smainz, @tori-27, @tsufeki, @xoxys, @xtexChooser, @zc-devs + +### 💥 Breaking changes + +- Drop native Let's Encrypt support [[#4541](https://github.com/woodpecker-ci/woodpecker/pull/4541)] +- Set new default approval mode based on repo visibility [[#4456](https://github.com/woodpecker-ci/woodpecker/pull/4456)] +- Do not set empty environment variables [[#4193](https://github.com/woodpecker-ci/woodpecker/pull/4193)] +- Unify cli commands and flags [[#4481](https://github.com/woodpecker-ci/woodpecker/pull/4481)] +- Move pipeline logs command [[#4480](https://github.com/woodpecker-ci/woodpecker/pull/4480)] +- Fix woodpecker-go repo model to match server [[#4479](https://github.com/woodpecker-ci/woodpecker/pull/4479)] +- Restructure cli commands [[#4467](https://github.com/woodpecker-ci/woodpecker/pull/4467)] +- Add pagination options to all supported endpoints in sdk [[#4463](https://github.com/woodpecker-ci/woodpecker/pull/4463)] +- Allow to set custom trusted clone plugins [[#4352](https://github.com/woodpecker-ci/woodpecker/pull/4352)] +- Add PipelineListsOptions to woodpecker-go [[#3652](https://github.com/woodpecker-ci/woodpecker/pull/3652)] +- Remove `secrets` in favor of `from_secret` [[#4363](https://github.com/woodpecker-ci/woodpecker/pull/4363)] +- Kubernetes | Docker: Add support for rootless images [[#4151](https://github.com/woodpecker-ci/woodpecker/pull/4151)] +- Split repo trusted setting [[#4025](https://github.com/woodpecker-ci/woodpecker/pull/4025)] +- Move docker resource limit settings from server to agent [[#3174](https://github.com/woodpecker-ci/woodpecker/pull/3174)] +- Set `/woodpecker` as default workdir for the **woodpecker-cli** container [[#4130](https://github.com/woodpecker-ci/woodpecker/pull/4130)] +- Require upgrade from 2.x [[#4112](https://github.com/woodpecker-ci/woodpecker/pull/4112)] +- Don't expose task data via api [[#4108](https://github.com/woodpecker-ci/woodpecker/pull/4108)] +- Remove some ci environment variables [[#3846](https://github.com/woodpecker-ci/woodpecker/pull/3846)] +- Remove all default privileged plugins [[#4053](https://github.com/woodpecker-ci/woodpecker/pull/4053)] +- Add option to filter secrets by plugins with specific tags [[#4069](https://github.com/woodpecker-ci/woodpecker/pull/4069)] +- Remove old pipeline options [[#4016](https://github.com/woodpecker-ci/woodpecker/pull/4016)] +- Remove various deprecations [[#4017](https://github.com/woodpecker-ci/woodpecker/pull/4017)] +- Drop repo name fallback for hooks [[#4013](https://github.com/woodpecker-ci/woodpecker/pull/4013)] +- Improve local backend detection [[#4006](https://github.com/woodpecker-ci/woodpecker/pull/4006)] +- Refactor JSON and SDK fields [[#3968](https://github.com/woodpecker-ci/woodpecker/pull/3968)] +- Migrate to maintained cron lib and remove seconds [[#3785](https://github.com/woodpecker-ci/woodpecker/pull/3785)] +- Switch to profile-based AppArmor configuration [[#4008](https://github.com/woodpecker-ci/woodpecker/pull/4008)] +- Remove Kubernetes default image pull secret name `regcred` [[#4005](https://github.com/woodpecker-ci/woodpecker/pull/4005)] +- Drop "WOODPECKER_WEBHOOK_HOST" env var and adjust docs [[#3969](https://github.com/woodpecker-ci/woodpecker/pull/3969)] +- Drop version in schema [[#3970](https://github.com/woodpecker-ci/woodpecker/pull/3970)] +- Update docker to v27 [[#3972](https://github.com/woodpecker-ci/woodpecker/pull/3972)] +- Require gitlab 12.4 [[#3966](https://github.com/woodpecker-ci/woodpecker/pull/3966)] +- Migrate to maintained httpsign library [[#3839](https://github.com/woodpecker-ci/woodpecker/pull/3839)] +- Remove `WOODPECKER_DEV_OAUTH_HOST` and `WOODPECKER_DEV_GITEA_OAUTH_URL` [[#3961](https://github.com/woodpecker-ci/woodpecker/pull/3961)] +- Remove deprecated pipeline keywords: `pipeline:`, `platform:`, `branches:` [[#3916](https://github.com/woodpecker-ci/woodpecker/pull/3916)] +- server: remove old unused routes [[#3845](https://github.com/woodpecker-ci/woodpecker/pull/3845)] +- CLI: remove step-id and add step-number as option to logs [[#3927](https://github.com/woodpecker-ci/woodpecker/pull/3927)] + +### 🔒 Security + +- Add server config to disable user registered agents [[#4206](https://github.com/woodpecker-ci/woodpecker/pull/4206)] +- chore: fix `http-proxy-middleware` CVE [[#4257](https://github.com/woodpecker-ci/woodpecker/pull/4257)] +- Allow altering trusted clone plugins and filter them via tag [[#4074](https://github.com/woodpecker-ci/woodpecker/pull/4074)] +- Update gitea sdk [[#4012](https://github.com/woodpecker-ci/woodpecker/pull/4012)] +- Update Forgejo SDK [[#3948](https://github.com/woodpecker-ci/woodpecker/pull/3948)] + +### ✨ Features + +- Add user as docker backend_option [[#4526](https://github.com/woodpecker-ci/woodpecker/pull/4526)] +- Implement org/user agents [[#3539](https://github.com/woodpecker-ci/woodpecker/pull/3539)] +- Replay pipeline using `cli exec` by downloading metadata [[#4103](https://github.com/woodpecker-ci/woodpecker/pull/4103)] +- Update clone plugin to support sha256 [[#4136](https://github.com/woodpecker-ci/woodpecker/pull/4136)] + +### 🐛 Bug Fixes + +- Fix BB ambiguous commit status key [[#4544](https://github.com/woodpecker-ci/woodpecker/pull/4544)] +- fix: addon JSON pointers [[#4508](https://github.com/woodpecker-ci/woodpecker/pull/4508)] +- Fix apparmorProfile being ignored when it's the only field [[#4507](https://github.com/woodpecker-ci/woodpecker/pull/4507)] +- Sanitize strings in table output [[#4466](https://github.com/woodpecker-ci/woodpecker/pull/4466)] +- Cleanup openapi generation [[#4331](https://github.com/woodpecker-ci/woodpecker/pull/4331)] +- Support github refresh tokens [[#3811](https://github.com/woodpecker-ci/woodpecker/pull/3811)] +- Fix not working overflow on repo list message [[#4420](https://github.com/woodpecker-ci/woodpecker/pull/4420)] +- Fix avatar column type [[#4340](https://github.com/woodpecker-ci/woodpecker/pull/4340)] +- fix `error="io: read/write on closed pipe"` on k8s backend [[#4281](https://github.com/woodpecker-ci/woodpecker/pull/4281)] +- Move update notifier dot into settings button [[#4334](https://github.com/woodpecker-ci/woodpecker/pull/4334)] +- gitea: add check if pull_request webhook is missing pull info [[#4305](https://github.com/woodpecker-ci/woodpecker/pull/4305)] +- Refresh token before loading branches [[#4284](https://github.com/woodpecker-ci/woodpecker/pull/4284)] +- Delete GitLab webhooks with partial URL match [[#4259](https://github.com/woodpecker-ci/woodpecker/pull/4259)] +- Increase `WOODPECKER_FORGE_TIMEOUT` to fix config fetching for GitLab [[#4262](https://github.com/woodpecker-ci/woodpecker/pull/4262)] +- Ensure cli exec has by default not the same prefix [[#4132](https://github.com/woodpecker-ci/woodpecker/pull/4132)] +- Fix repo add loading spinner [[#4135](https://github.com/woodpecker-ci/woodpecker/pull/4135)] +- Fix migration registries table [[#4111](https://github.com/woodpecker-ci/woodpecker/pull/4111)] +- Wait for tracer to be done before finishing workflow [[#4068](https://github.com/woodpecker-ci/woodpecker/pull/4068)] +- Fix schema with detached steps [[#4066](https://github.com/woodpecker-ci/woodpecker/pull/4066)] +- Fix schema with commands and entrypoint [[#4065](https://github.com/woodpecker-ci/woodpecker/pull/4065)] +- Read long log lines from file storage correctly [[#4048](https://github.com/woodpecker-ci/woodpecker/pull/4048)] +- Set refspec for gitlab MR [[#4021](https://github.com/woodpecker-ci/woodpecker/pull/4021)] +- Set `CI_PREV_COMMIT_{SOURCE,TARGET}_BRANCH` as mentioned in the documentation [[#4001](https://github.com/woodpecker-ci/woodpecker/pull/4001)] +- [Bitbucket Datacenter] Return empty list instead of null [[#4010](https://github.com/woodpecker-ci/woodpecker/pull/4010)] +- Fix BB PR pipeline ref [[#3985](https://github.com/woodpecker-ci/woodpecker/pull/3985)] +- Change Bitbucket PR hook to point the source branch, commit & ref [[#3965](https://github.com/woodpecker-ci/woodpecker/pull/3965)] +- Add updated, merged and declined events to bb webhook activation [[#3963](https://github.com/woodpecker-ci/woodpecker/pull/3963)] +- Fix login via navbar [[#3962](https://github.com/woodpecker-ci/woodpecker/pull/3962)] +- Truncate creation in list [[#3952](https://github.com/woodpecker-ci/woodpecker/pull/3952)] +- Fix panic if forge is unreachable [[#3944](https://github.com/woodpecker-ci/woodpecker/pull/3944)] + +### 📚 Documentation + +- Show client flags [[#4542](https://github.com/woodpecker-ci/woodpecker/pull/4542)] +- chore(deps): update react monorepo to v19 (major) [[#4523](https://github.com/woodpecker-ci/woodpecker/pull/4523)] +- chore(deps): update docs npm deps non-major [[#4519](https://github.com/woodpecker-ci/woodpecker/pull/4519)] +- chore(deps): lock file maintenance [[#4502](https://github.com/woodpecker-ci/woodpecker/pull/4502)] +- chore(deps): lock file maintenance [[#4501](https://github.com/woodpecker-ci/woodpecker/pull/4501)] +- chore(deps): update dependency isomorphic-dompurify to v2.18.0 [[#4493](https://github.com/woodpecker-ci/woodpecker/pull/4493)] +- fix(deps): update docs npm deps non-major [[#4484](https://github.com/woodpecker-ci/woodpecker/pull/4484)] +- Add migration notes for restructured cli commands [[#4476](https://github.com/woodpecker-ci/woodpecker/pull/4476)] +- Various fixes for `awesome.md` [[#4448](https://github.com/woodpecker-ci/woodpecker/pull/4448)] +- chore(deps): lock file maintenance [[#4453](https://github.com/woodpecker-ci/woodpecker/pull/4453)] +- chore(deps): update dependency isomorphic-dompurify to v2.17.0 [[#4449](https://github.com/woodpecker-ci/woodpecker/pull/4449)] +- fix(deps): update docs npm deps non-major [[#4441](https://github.com/woodpecker-ci/woodpecker/pull/4441)] +- chore(deps): update dependency @docusaurus/tsconfig to v3.6.2 [[#4433](https://github.com/woodpecker-ci/woodpecker/pull/4433)] +- chore(deps): lock file maintenance [[#4435](https://github.com/woodpecker-ci/woodpecker/pull/4435)] +- Bump minimum nodejs to v20 [[#4417](https://github.com/woodpecker-ci/woodpecker/pull/4417)] +- chore(deps): lock file maintenance [[#4402](https://github.com/woodpecker-ci/woodpecker/pull/4402)] +- Add microsoft teams plugin [[#4400](https://github.com/woodpecker-ci/woodpecker/pull/4400)] +- fix(deps): update docs npm deps non-major [[#4394](https://github.com/woodpecker-ci/woodpecker/pull/4394)] +- chore(deps): update dependency @types/node to v22 [[#4395](https://github.com/woodpecker-ci/woodpecker/pull/4395)] +- chore(deps): update dependency marked to v15 [[#4396](https://github.com/woodpecker-ci/woodpecker/pull/4396)] +- Podman is not (official) supported [[#4367](https://github.com/woodpecker-ci/woodpecker/pull/4367)] +- Add EditorConfig-Checker Plugin to docs [[#4371](https://github.com/woodpecker-ci/woodpecker/pull/4371)] +- Update netrc option description [[#4342](https://github.com/woodpecker-ci/woodpecker/pull/4342)] +- Fix deployment event note [[#4283](https://github.com/woodpecker-ci/woodpecker/pull/4283)] +- Improve migration notes [[#4291](https://github.com/woodpecker-ci/woodpecker/pull/4291)] +- Add instructions how to build images locally [[#4277](https://github.com/woodpecker-ci/woodpecker/pull/4277)] +- chore(deps): update docs npm deps non-major [[#4238](https://github.com/woodpecker-ci/woodpecker/pull/4238)] +- Correct spelling [[#4279](https://github.com/woodpecker-ci/woodpecker/pull/4279)] +- Add Telegram plugin [[#4229](https://github.com/woodpecker-ci/woodpecker/pull/4229)] +- Remove archived plugin [[#4227](https://github.com/woodpecker-ci/woodpecker/pull/4227)] +- Use "Woodpecker Authors" as copyright on website [[#4225](https://github.com/woodpecker-ci/woodpecker/pull/4225)] +- chore(deps): update dependency cookie to v1 [[#4224](https://github.com/woodpecker-ci/woodpecker/pull/4224)] +- fix(deps): update docs npm deps non-major [[#4221](https://github.com/woodpecker-ci/woodpecker/pull/4221)] +- Fix errant apostrophe in docker-compose documentation [[#4203](https://github.com/woodpecker-ci/woodpecker/pull/4203)] +- chore(deps): lock file maintenance [[#4186](https://github.com/woodpecker-ci/woodpecker/pull/4186)] +- chore(deps): update dependency concurrently to v9 [[#4176](https://github.com/woodpecker-ci/woodpecker/pull/4176)] +- chore(deps): update docs npm deps non-major [[#4164](https://github.com/woodpecker-ci/woodpecker/pull/4164)] +- Update image filter error message [[#4143](https://github.com/woodpecker-ci/woodpecker/pull/4143)] +- Docs: reference to built-in docker compose and remove deprecated version from compose examples [[#4123](https://github.com/woodpecker-ci/woodpecker/pull/4123)] +- directory key is allowed for services [[#4127](https://github.com/woodpecker-ci/woodpecker/pull/4127)] +- [docs] Removes dot prefix from pipeline configuration filenames [[#4105](https://github.com/woodpecker-ci/woodpecker/pull/4105)] +- Use kaniko plugin in docs as example [[#4072](https://github.com/woodpecker-ci/woodpecker/pull/4072)] +- Add some posts and videos [[#4070](https://github.com/woodpecker-ci/woodpecker/pull/4070)] +- Move event type descriptions from Terminology to Workflow Syntax [[#4062](https://github.com/woodpecker-ci/woodpecker/pull/4062)] +- Add community posts from discussions [[#4058](https://github.com/woodpecker-ci/woodpecker/pull/4058)] +- Add a pull request template with some basic guidelines [[#4055](https://github.com/woodpecker-ci/woodpecker/pull/4055)] +- Add examples of CI environment variable values [[#4009](https://github.com/woodpecker-ci/woodpecker/pull/4009)] +- Fix inline author warning [[#4040](https://github.com/woodpecker-ci/woodpecker/pull/4040)] +- Updated Secrets image filter docs [[#4028](https://github.com/woodpecker-ci/woodpecker/pull/4028)] +- Update dependency marked to v14 [[#4036](https://github.com/woodpecker-ci/woodpecker/pull/4036)] +- Update docs npm deps non-major [[#4033](https://github.com/woodpecker-ci/woodpecker/pull/4033)] +- Overhaul README [[#3995](https://github.com/woodpecker-ci/woodpecker/pull/3995)] +- fix(deps): update docs npm deps non-major [[#3990](https://github.com/woodpecker-ci/woodpecker/pull/3990)] +- Add spellchecking for docs [[#3787](https://github.com/woodpecker-ci/woodpecker/pull/3787)] + +### 📈 Enhancement + +- Use docusaurus faster [[#4528](https://github.com/woodpecker-ci/woodpecker/pull/4528)] +- Use pagination helper to list pipelines in cli [[#4478](https://github.com/woodpecker-ci/woodpecker/pull/4478)] +- Some UI improvements [[#4497](https://github.com/woodpecker-ci/woodpecker/pull/4497)] +- Add status filter to list pipeline API [[#4494](https://github.com/woodpecker-ci/woodpecker/pull/4494)] +- Use JS-native date/time formatting [[#4488](https://github.com/woodpecker-ci/woodpecker/pull/4488)] +- Add pipeline purge command to cli [[#4470](https://github.com/woodpecker-ci/woodpecker/pull/4470)] +- Add option to limit the resultset returned by paginate helper [[#4475](https://github.com/woodpecker-ci/woodpecker/pull/4475)] +- Add filter to list repository pipelines API [[#4416](https://github.com/woodpecker-ci/woodpecker/pull/4416)] +- Increase log level when failing to fetch YAML [[#4107](https://github.com/woodpecker-ci/woodpecker/pull/4107)] +- Trim space to all config flags that allow to read value from file [[#4468](https://github.com/woodpecker-ci/woodpecker/pull/4468)] +- Change default icon size to 20 [[#4458](https://github.com/woodpecker-ci/woodpecker/pull/4458)] +- Add visibility icon to repo list [[#4460](https://github.com/woodpecker-ci/woodpecker/pull/4460)] +- Unify pipeline status icons [[#4414](https://github.com/woodpecker-ci/woodpecker/pull/4414)] +- Improve project settings descriptions [[#4410](https://github.com/woodpecker-ci/woodpecker/pull/4410)] +- Add count badge to visualize counters in tab title [[#4419](https://github.com/woodpecker-ci/woodpecker/pull/4419)] +- Redesign repo list and include last pipeline [[#4386](https://github.com/woodpecker-ci/woodpecker/pull/4386)] +- Use KeyValueEditor for DeployPipelinePopup too [[#4412](https://github.com/woodpecker-ci/woodpecker/pull/4412)] +- Use separate routes instead of anchors [[#4285](https://github.com/woodpecker-ci/woodpecker/pull/4285)] +- Untangle settings / header slots [[#4403](https://github.com/woodpecker-ci/woodpecker/pull/4403)] +- Fix responsiveness of the settings template [[#4383](https://github.com/woodpecker-ci/woodpecker/pull/4383)] +- Use squared spinner for active pipelines [[#4379](https://github.com/woodpecker-ci/woodpecker/pull/4379)] +- Add server configuration option to add default set of labels for workflows that has no labels specified [[#4326](https://github.com/woodpecker-ci/woodpecker/pull/4326)] +- Add `cli lint` option to treat warnings as errors [[#4373](https://github.com/woodpecker-ci/woodpecker/pull/4373)] +- Improve error message for wrong secrets / environment config [[#4359](https://github.com/woodpecker-ci/woodpecker/pull/4359)] +- Improve linter messages in UI [[#4351](https://github.com/woodpecker-ci/woodpecker/pull/4351)] +- Pass settings to services [[#4338](https://github.com/woodpecker-ci/woodpecker/pull/4338)] +- Inline model types for migrations [[#4293](https://github.com/woodpecker-ci/woodpecker/pull/4293)] +- Add options to control the database connections (open,idle,timeout) [[#4212](https://github.com/woodpecker-ci/woodpecker/pull/4212)] +- Move Queue creation behind new func that evaluates queue type [[#4252](https://github.com/woodpecker-ci/woodpecker/pull/4252)] +- Add additional error message on swagger v2 to v3 convert [[#4254](https://github.com/woodpecker-ci/woodpecker/pull/4254)] +- Deprecate `secrets` [[#4235](https://github.com/woodpecker-ci/woodpecker/pull/4235)] +- Agent edit/detail view: change the help url based on the backend [[#4219](https://github.com/woodpecker-ci/woodpecker/pull/4219)] +- Use middleware to load org [[#4208](https://github.com/woodpecker-ci/woodpecker/pull/4208)] +- Assign workflows to agents with the best label matches [[#4201](https://github.com/woodpecker-ci/woodpecker/pull/4201)] +- Report custom labels set by agent admins back [[#4141](https://github.com/woodpecker-ci/woodpecker/pull/4141)] +- Highlight invalid entries in manual pipeline trigger [[#4153](https://github.com/woodpecker-ci/woodpecker/pull/4153)] +- Print agent labels in debug mode [[#4155](https://github.com/woodpecker-ci/woodpecker/pull/4155)] +- Implement registries for Kubernetes backend [[#4092](https://github.com/woodpecker-ci/woodpecker/pull/4092)] +- Correct cli exec flags and remove ineffective ones [[#4129](https://github.com/woodpecker-ci/woodpecker/pull/4129)] +- Set repo user to repairing user when old user is missing [[#4128](https://github.com/woodpecker-ci/woodpecker/pull/4128)] +- Restart tasks on dead agents sooner [[#4114](https://github.com/woodpecker-ci/woodpecker/pull/4114)] +- Adjust cli exec metadata structure to equal server metadata [[#4119](https://github.com/woodpecker-ci/woodpecker/pull/4119)] +- Allow to restart declined pipelines [[#4109](https://github.com/woodpecker-ci/woodpecker/pull/4109)] +- Add indices to repo table [[#4087](https://github.com/woodpecker-ci/woodpecker/pull/4087)] +- Add systemd unit files to the RPM/DEB packages [[#3986](https://github.com/woodpecker-ci/woodpecker/pull/3986)] +- Duplicate key `workflow_id` in the agent logs [[#4046](https://github.com/woodpecker-ci/woodpecker/pull/4046)] +- Improve error on config loading [[#4024](https://github.com/woodpecker-ci/woodpecker/pull/4024)] +- Show error if secret name is missing [[#4014](https://github.com/woodpecker-ci/woodpecker/pull/4014)] +- Show error returned from API [[#3980](https://github.com/woodpecker-ci/woodpecker/pull/3980)] +- Move manual popup to own page [[#3981](https://github.com/woodpecker-ci/woodpecker/pull/3981)] +- Fail on InvalidImageName [[#4007](https://github.com/woodpecker-ci/woodpecker/pull/4007)] +- Use Bitbucket PR title for pipeline message [[#3984](https://github.com/woodpecker-ci/woodpecker/pull/3984)] +- Show logs if step has error [[#3979](https://github.com/woodpecker-ci/woodpecker/pull/3979)] +- Refactor docker backend and add more test coverage [[#2700](https://github.com/woodpecker-ci/woodpecker/pull/2700)] +- Make cli plugin log purge recognize steps by name [[#3953](https://github.com/woodpecker-ci/woodpecker/pull/3953)] +- Pin page size [[#3946](https://github.com/woodpecker-ci/woodpecker/pull/3946)] +- Improve cron list [[#3947](https://github.com/woodpecker-ci/woodpecker/pull/3947)] +- Add PULLREQUEST_DRONE_PULL_REQUEST drone env [[#3939](https://github.com/woodpecker-ci/woodpecker/pull/3939)] +- Make agent gRPC errors distinguishable [[#3936](https://github.com/woodpecker-ci/woodpecker/pull/3936)] + +### 📦️ Dependency + +- fix(deps): update module google.golang.org/grpc to v1.69.0 [[#4563](https://github.com/woodpecker-ci/woodpecker/pull/4563)] +- fix(deps): update golang-packages [[#4553](https://github.com/woodpecker-ci/woodpecker/pull/4553)] +- Update kin-openapi [[#4560](https://github.com/woodpecker-ci/woodpecker/pull/4560)] +- fix(deps): update module golang.org/x/crypto to v0.31.0 [security] [[#4557](https://github.com/woodpecker-ci/woodpecker/pull/4557)] +- fix(deps): update golang-packages [[#4546](https://github.com/woodpecker-ci/woodpecker/pull/4546)] +- chore(deps): update docker.io/woodpeckerci/plugin-ready-release-go docker tag to v3.1.0 [[#4536](https://github.com/woodpecker-ci/woodpecker/pull/4536)] +- chore(deps): update docker.io/curlimages/curl docker tag to v8.11.0 [[#4530](https://github.com/woodpecker-ci/woodpecker/pull/4530)] +- fix(deps): update golang-packages [[#4496](https://github.com/woodpecker-ci/woodpecker/pull/4496)] +- chore(deps): update docker.io/woodpeckerci/plugin-docker-buildx docker tag to v5.1.0 [[#4524](https://github.com/woodpecker-ci/woodpecker/pull/4524)] +- chore(deps): update docker.io/woodpeckerci/plugin-prettier docker tag to v1 [[#4522](https://github.com/woodpecker-ci/woodpecker/pull/4522)] +- chore(deps): update docker.io/alpine docker tag to v3.21 [[#4520](https://github.com/woodpecker-ci/woodpecker/pull/4520)] +- chore(deps): update dependency vite to v6 [[#4485](https://github.com/woodpecker-ci/woodpecker/pull/4485)] +- chore(deps): update docker.io/woodpeckerci/plugin-ready-release-go docker tag to v3 [[#4506](https://github.com/woodpecker-ci/woodpecker/pull/4506)] +- chore(deps): update docker.io/woodpeckerci/plugin-surge-preview docker tag to v1.3.3 [[#4495](https://github.com/woodpecker-ci/woodpecker/pull/4495)] +- fix(deps): update golang-packages [[#4477](https://github.com/woodpecker-ci/woodpecker/pull/4477)] +- fix(deps): update dependency @vueuse/core to v12 [[#4486](https://github.com/woodpecker-ci/woodpecker/pull/4486)] +- fix(deps): update module github.com/google/go-github/v66 to v67 [[#4487](https://github.com/woodpecker-ci/woodpecker/pull/4487)] +- chore(deps): update woodpeckerci/plugin-release docker tag to v0.2.2 [[#4483](https://github.com/woodpecker-ci/woodpecker/pull/4483)] +- chore(deps): update pre-commit hook golangci/golangci-lint to v1.62.2 [[#4482](https://github.com/woodpecker-ci/woodpecker/pull/4482)] +- fix(deps): update golang-packages [[#4452](https://github.com/woodpecker-ci/woodpecker/pull/4452)] +- fix(deps): update golang-packages [[#4411](https://github.com/woodpecker-ci/woodpecker/pull/4411)] +- chore(deps): update pre-commit hook igorshubovych/markdownlint-cli to v0.43.0 [[#4443](https://github.com/woodpecker-ci/woodpecker/pull/4443)] +- chore(deps): update postgres docker tag to v17.2 [[#4442](https://github.com/woodpecker-ci/woodpecker/pull/4442)] +- chore(deps): update docker.io/woodpeckerci/plugin-trivy docker tag to v1.3.0 [[#4434](https://github.com/woodpecker-ci/woodpecker/pull/4434)] +- chore(deps): update web npm deps non-major [[#4432](https://github.com/woodpecker-ci/woodpecker/pull/4432)] +- fix(deps): update golang-packages [[#4401](https://github.com/woodpecker-ci/woodpecker/pull/4401)] +- chore(deps): update web npm deps non-major [[#4391](https://github.com/woodpecker-ci/woodpecker/pull/4391)] +- fix(deps): update dependency @intlify/unplugin-vue-i18n to v6 [[#4397](https://github.com/woodpecker-ci/woodpecker/pull/4397)] +- chore(deps): update pre-commit hook golangci/golangci-lint to v1.62.0 [[#4390](https://github.com/woodpecker-ci/woodpecker/pull/4390)] +- chore(deps): update postgres docker tag to v17.1 [[#4389](https://github.com/woodpecker-ci/woodpecker/pull/4389)] +- chore(deps): update docker.io/techknowlogick/xgo docker tag to go-1.23.x [[#4388](https://github.com/woodpecker-ci/woodpecker/pull/4388)] +- chore(config): migrate renovate config [[#4296](https://github.com/woodpecker-ci/woodpecker/pull/4296)] +- chore(deps): update docker.io/woodpeckerci/plugin-trivy docker tag to v1.2.0 [[#4289](https://github.com/woodpecker-ci/woodpecker/pull/4289)] +- chore(deps): update docker.io/techknowlogick/xgo docker tag to go-1.23.x [[#4282](https://github.com/woodpecker-ci/woodpecker/pull/4282)] +- fix(deps): update golang-packages [[#4251](https://github.com/woodpecker-ci/woodpecker/pull/4251)] +- fix(deps): update web npm deps non-major [[#4258](https://github.com/woodpecker-ci/woodpecker/pull/4258)] +- chore(deps): update web npm deps non-major [[#4250](https://github.com/woodpecker-ci/woodpecker/pull/4250)] +- chore(deps): update node.js to v23 [[#4239](https://github.com/woodpecker-ci/woodpecker/pull/4239)] +- chore(deps): update web npm deps non-major [[#4237](https://github.com/woodpecker-ci/woodpecker/pull/4237)] +- chore(deps): update docker.io/mysql docker tag to v9.1.0 [[#4236](https://github.com/woodpecker-ci/woodpecker/pull/4236)] +- fix(deps): update dependency simple-icons to v13.14.0 [[#4226](https://github.com/woodpecker-ci/woodpecker/pull/4226)] +- fix(deps): update web npm deps non-major [[#4223](https://github.com/woodpecker-ci/woodpecker/pull/4223)] +- fix(deps): update golang-packages [[#4215](https://github.com/woodpecker-ci/woodpecker/pull/4215)] +- fix(deps): update golang-packages [[#4210](https://github.com/woodpecker-ci/woodpecker/pull/4210)] +- fix(deps): update module github.com/google/go-github/v65 to v66 [[#4205](https://github.com/woodpecker-ci/woodpecker/pull/4205)] +- fix(deps): update dependency vue-i18n to v10.0.4 [[#4200](https://github.com/woodpecker-ci/woodpecker/pull/4200)] +- chore(deps): update pre-commit hook pre-commit/pre-commit-hooks to v5 [[#4192](https://github.com/woodpecker-ci/woodpecker/pull/4192)] +- fix(deps): update dependency simple-icons to v13.13.0 [[#4196](https://github.com/woodpecker-ci/woodpecker/pull/4196)] +- chore(deps): update web npm deps non-major [[#4174](https://github.com/woodpecker-ci/woodpecker/pull/4174)] +- chore(deps): update docker.io/postgres docker tag to v17 [[#4179](https://github.com/woodpecker-ci/woodpecker/pull/4179)] +- fix(deps): update dependency @intlify/unplugin-vue-i18n to v5 [[#4183](https://github.com/woodpecker-ci/woodpecker/pull/4183)] +- fix(deps): update dependency @vueuse/core to v11 [[#4184](https://github.com/woodpecker-ci/woodpecker/pull/4184)] +- chore(deps): update docker.io/woodpeckerci/plugin-codecov docker tag to v2.1.5 [[#4167](https://github.com/woodpecker-ci/woodpecker/pull/4167)] +- fix(deps): update module github.com/google/go-github/v64 to v65 [[#4185](https://github.com/woodpecker-ci/woodpecker/pull/4185)] +- chore(deps): update docker.io/mysql docker tag to v9 [[#4178](https://github.com/woodpecker-ci/woodpecker/pull/4178)] +- chore(deps): update docker.io/alpine docker tag to v3.20 [[#4169](https://github.com/woodpecker-ci/woodpecker/pull/4169)] +- fix(deps): update github.com/urfave/cli/v3 digest to 20ef97b [[#4166](https://github.com/woodpecker-ci/woodpecker/pull/4166)] +- chore(deps): update docker.io/woodpeckerci/plugin-surge-preview docker tag to v1.3.2 [[#4168](https://github.com/woodpecker-ci/woodpecker/pull/4168)] +- chore(deps): update woodpeckerci/plugin-release docker tag to v0.2.1 [[#4175](https://github.com/woodpecker-ci/woodpecker/pull/4175)] +- chore(deps): update woodpeckerci/plugin-ready-release-go docker tag to v2 [[#4182](https://github.com/woodpecker-ci/woodpecker/pull/4182)] +- fix(deps): update github.com/muesli/termenv digest to 82936c5 [[#4165](https://github.com/woodpecker-ci/woodpecker/pull/4165)] +- chore(deps): update postgres docker tag to v17 [[#4181](https://github.com/woodpecker-ci/woodpecker/pull/4181)] +- chore(deps): update pre-commit non-major [[#4173](https://github.com/woodpecker-ci/woodpecker/pull/4173)] +- chore(deps): update docker.io/golang docker tag to v1.23 [[#4170](https://github.com/woodpecker-ci/woodpecker/pull/4170)] +- chore(deps): update node.js to v22 [[#4180](https://github.com/woodpecker-ci/woodpecker/pull/4180)] +- fix(deps): update golang-packages [[#4161](https://github.com/woodpecker-ci/woodpecker/pull/4161)] +- chore(deps): update dependency @antfu/eslint-config to v3 [[#4095](https://github.com/woodpecker-ci/woodpecker/pull/4095)] +- chore(deps): update dependency jsdom to v25 [[#4094](https://github.com/woodpecker-ci/woodpecker/pull/4094)] +- chore(deps): update docker.io/golang docker tag to v1.23 [[#4081](https://github.com/woodpecker-ci/woodpecker/pull/4081)] +- chore(deps): update docker.io/woodpeckerci/plugin-prettier docker tag to v0.2.0 [[#4082](https://github.com/woodpecker-ci/woodpecker/pull/4082)] +- fix(deps): update module github.com/google/go-github/v63 to v64 [[#4073](https://github.com/woodpecker-ci/woodpecker/pull/4073)] +- fix(deps): update golang-packages [[#4059](https://github.com/woodpecker-ci/woodpecker/pull/4059)] +- Update github.com/urfave/cli/v3 digest to fc07a8c [[#4043](https://github.com/woodpecker-ci/woodpecker/pull/4043)] +- Update woodpeckerci/plugin-git Docker tag to v2.5.2 [[#4041](https://github.com/woodpecker-ci/woodpecker/pull/4041)] +- Update web npm deps non-major [[#4034](https://github.com/woodpecker-ci/woodpecker/pull/4034)] +- Update dependency simple-icons to v13 [[#4037](https://github.com/woodpecker-ci/woodpecker/pull/4037)] +- chore(deps): lock file maintenance [[#3991](https://github.com/woodpecker-ci/woodpecker/pull/3991)] +- fix(deps): update golang-packages [[#3958](https://github.com/woodpecker-ci/woodpecker/pull/3958)] + +### Misc + +- Move link checks into cron-curated issue dashboard [[#4515](https://github.com/woodpecker-ci/woodpecker/pull/4515)] +- Add settings title action [[#4499](https://github.com/woodpecker-ci/woodpecker/pull/4499)] +- Use same default sort for repo and org repo list [[#4461](https://github.com/woodpecker-ci/woodpecker/pull/4461)] +- Add dns config option to official feature set [[#4418](https://github.com/woodpecker-ci/woodpecker/pull/4418)] +- Remove `renovate` branch triggers [[#4437](https://github.com/woodpecker-ci/woodpecker/pull/4437)] +- Improve tab layout and add hover effect [[#4431](https://github.com/woodpecker-ci/woodpecker/pull/4431)] +- Dont run pipeline on push events to renovate branches [[#4406](https://github.com/woodpecker-ci/woodpecker/pull/4406)] +- Harden and correct fifo task queue tests [[#4377](https://github.com/woodpecker-ci/woodpecker/pull/4377)] +- Kubernetes documentation enhancements [[#4374](https://github.com/woodpecker-ci/woodpecker/pull/4374)] +- Use release-helper for release/* branches [[#4301](https://github.com/woodpecker-ci/woodpecker/pull/4301)] +- Fix wording for privileged plugins linter error [[#4280](https://github.com/woodpecker-ci/woodpecker/pull/4280)] +- Fix renovate support for `xgo` [[#4276](https://github.com/woodpecker-ci/woodpecker/pull/4276)] +- Improve nix development environment [[#4256](https://github.com/woodpecker-ci/woodpecker/pull/4256)] +- [pre-commit.ci] pre-commit autoupdate [[#4209](https://github.com/woodpecker-ci/woodpecker/pull/4209)] +- Add `.lycheeignore` [[#4154](https://github.com/woodpecker-ci/woodpecker/pull/4154)] +- Add eslint-plugin-promise back [[#4022](https://github.com/woodpecker-ci/woodpecker/pull/4022)] +- Improve wording [[#3951](https://github.com/woodpecker-ci/woodpecker/pull/3951)] +- Fix typos and optimize wording [[#3940](https://github.com/woodpecker-ci/woodpecker/pull/3940)] + +## [2.7.2](https://github.com/woodpecker-ci/woodpecker/releases/tag/v2.7.2) - 2024-11-03 + +### Important + +To secure your instance, set `WOODPECKER_PLUGINS_PRIVILEGED` to only allow specific versions of the `woodpeckerci/plugin-docker-buildx` plugin, use version 5.0.0 or above. This prevents older, potentially unstable versions from being privileged. + +For example, to allow only version 5.0.0, use: + +```bash +WOODPECKER_PLUGINS_PRIVILEGED=woodpeckerci/plugin-docker-buildx:5.0.0 +``` + +To allow multiple versions, you can separate them with commas: + +```bash +WOODPECKER_PLUGINS_PRIVILEGED=woodpeckerci/plugin-docker-buildx:5.0.0,woodpeckerci/plugin-docker-buildx:5.1.0 +``` + +This setup ensures only specified, stable plugin versions are given privileged access. + +Read more about it in [#4213](https://github.com/woodpecker-ci/woodpecker/pull/4213) + +### ❤️ Thanks to all contributors! ❤️ + +@6543, @anbraten, @j04n-f, @pat-s, @qwerty287 + +### 🔒 Security + +- Chore(deps): update dependency vite to v5.4.6 [security] ([#4163](https://github.com/woodpecker-ci/woodpecker/pull/4163)) [[#4187](https://github.com/woodpecker-ci/woodpecker/pull/4187)] + +### 🐛 Bug Fixes + +- Don't parse forge config files multiple times if no error occured ([#4272](https://github.com/woodpecker-ci/woodpecker/pull/4272)) [[#4273](https://github.com/woodpecker-ci/woodpecker/pull/4273)] +- Fix repo/owner parsing for gitlab ([#4255](https://github.com/woodpecker-ci/woodpecker/pull/4255)) [[#4261](https://github.com/woodpecker-ci/woodpecker/pull/4261)] +- Run queue.process() in background [[#4115](https://github.com/woodpecker-ci/woodpecker/pull/4115)] +- Only update agent.LastWork if not done recently ([#4031](https://github.com/woodpecker-ci/woodpecker/pull/4031)) [[#4100](https://github.com/woodpecker-ci/woodpecker/pull/4100)] + +### Misc + +- Backport JS dependency updates [[#4189](https://github.com/woodpecker-ci/woodpecker/pull/4189)] + +## [2.7.1](https://github.com/woodpecker-ci/woodpecker/releases/tag/v2.7.1) - 2024-09-07 + +### ❤️ Thanks to all contributors! ❤️ + +@6543, @anbraten, @j04n-f, @qwerty287 + +### 🔒 Security + +- Lint privileged plugin match and allow to be set empty [[#4084](https://github.com/woodpecker-ci/woodpecker/pull/4084)] +- Allow admins to specify privileged plugins by name **and tag** [[#4076](https://github.com/woodpecker-ci/woodpecker/pull/4076)] +- Warn if using secrets/env with plugin [[#4039](https://github.com/woodpecker-ci/woodpecker/pull/4039)] + +### 🐛 Bug Fixes + +- Set refspec for gitlab MR [[#4021](https://github.com/woodpecker-ci/woodpecker/pull/4021)] +- Change Bitbucket PR hook to point the source branch, commit & ref [[#3965](https://github.com/woodpecker-ci/woodpecker/pull/3965)] +- Add updated, merged and declined events to bb webhook activation [[#3963](https://github.com/woodpecker-ci/woodpecker/pull/3963)] +- Fix login via navbar [[#3962](https://github.com/woodpecker-ci/woodpecker/pull/3962)] +- Fix panic if forge is unreachable [[#3944](https://github.com/woodpecker-ci/woodpecker/pull/3944)] +- Fix org settings page [[#4093](https://github.com/woodpecker-ci/woodpecker/pull/4093)] + +### Misc + +- Bump github.com/docker/docker from v24.0.9 to v24.0.9+30 [[#4077](https://github.com/woodpecker-ci/woodpecker/pull/4077)] + +## [2.7.0](https://github.com/woodpecker-ci/woodpecker/releases/tag/v2.7.0) - 2024-07-18 + +### ❤️ Thanks to all contributors! ❤️ + +@6543, @anbraten, @dvjn, @hhamalai, @lafriks, @pat-s, @qwerty287, @smainz, @tongjicoder, @zc-devs + +### 🔒 Security + +- Add blocklist of environment variables who could alter execution of plugins [[#3934](https://github.com/woodpecker-ci/woodpecker/pull/3934)] +- Make sure plugins only mount the workspace base in a predefinde location [[#3933](https://github.com/woodpecker-ci/woodpecker/pull/3933)] +- Disallow to set arbitrary environments for plugins [[#3909](https://github.com/woodpecker-ci/woodpecker/pull/3909)] +- Use proper oauth state [[#3847](https://github.com/woodpecker-ci/woodpecker/pull/3847)] +- Enhance token checking [[#3842](https://github.com/woodpecker-ci/woodpecker/pull/3842)] +- Bump github.com/hashicorp/go-retryablehttp v0.7.5 -> v0.7.7 [[#3834](https://github.com/woodpecker-ci/woodpecker/pull/3834)] + +### ✨ Features + +- Gracefully shutdown server [[#3896](https://github.com/woodpecker-ci/woodpecker/pull/3896)] +- Gracefully shutdown agent [[#3895](https://github.com/woodpecker-ci/woodpecker/pull/3895)] +- Convert urls in logs to links [[#3904](https://github.com/woodpecker-ci/woodpecker/pull/3904)] +- Allow login using multiple forges [[#3822](https://github.com/woodpecker-ci/woodpecker/pull/3822)] +- Global and organization registries [[#1672](https://github.com/woodpecker-ci/woodpecker/pull/1672)] +- Cli get repo from git remote [[#3830](https://github.com/woodpecker-ci/woodpecker/pull/3830)] +- Add api for forges [[#3733](https://github.com/woodpecker-ci/woodpecker/pull/3733)] + +### 📈 Enhancement + +- Cli fix pipeline logs [[#3913](https://github.com/woodpecker-ci/woodpecker/pull/3913)] +- Migrate to github.com/urfave/cli/v3 [[#2951](https://github.com/woodpecker-ci/woodpecker/pull/2951)] +- Allow to change the working directory also for plugins and services [[#3914](https://github.com/woodpecker-ci/woodpecker/pull/3914)] +- Remove `unplugin-icons` [[#3809](https://github.com/woodpecker-ci/woodpecker/pull/3809)] +- Release windows binaries as zip file [[#3906](https://github.com/woodpecker-ci/woodpecker/pull/3906)] +- Convert to openapi 3.0 [[#3897](https://github.com/woodpecker-ci/woodpecker/pull/3897)] +- Enhance pipeline list [[#3898](https://github.com/woodpecker-ci/woodpecker/pull/3898)] +- Add user registries UI [[#3888](https://github.com/woodpecker-ci/woodpecker/pull/3888)] +- Sort users by login [[#3891](https://github.com/woodpecker-ci/woodpecker/pull/3891)] +- Exclude dummy backend in production [[#3877](https://github.com/woodpecker-ci/woodpecker/pull/3877)] +- Fix deploy task env [[#3878](https://github.com/woodpecker-ci/woodpecker/pull/3878)] +- Get default branch and show message in pipeline list [[#3867](https://github.com/woodpecker-ci/woodpecker/pull/3867)] +- Add timestamp for last work done by agent [[#3844](https://github.com/woodpecker-ci/woodpecker/pull/3844)] +- Adjust logger types [[#3859](https://github.com/woodpecker-ci/woodpecker/pull/3859)] +- Cleanup state reporting [[#3850](https://github.com/woodpecker-ci/woodpecker/pull/3850)] +- Unify DB tables/columns [[#3806](https://github.com/woodpecker-ci/woodpecker/pull/3806)] +- Let webhook pass on pipeline parsing error [[#3829](https://github.com/woodpecker-ci/woodpecker/pull/3829)] +- Exclude mocks from release build [[#3831](https://github.com/woodpecker-ci/woodpecker/pull/3831)] +- K8s secrets reference from step [[#3655](https://github.com/woodpecker-ci/woodpecker/pull/3655)] + +### 🐛 Bug Fixes + +- Handle empty repositories in gitea when listing PRs [[#3925](https://github.com/woodpecker-ci/woodpecker/pull/3925)] +- Update alpine package dep for docker images [[#3917](https://github.com/woodpecker-ci/woodpecker/pull/3917)] +- Don't report error if agent was terminated gracefully [[#3894](https://github.com/woodpecker-ci/woodpecker/pull/3894)] +- Let agents continuously report their health [[#3893](https://github.com/woodpecker-ci/woodpecker/pull/3893)] +- Ignore warnings for cli exec [[#3868](https://github.com/woodpecker-ci/woodpecker/pull/3868)] +- Correct favicon states [[#3832](https://github.com/woodpecker-ci/woodpecker/pull/3832)] +- Cleanup of the login flow and tests [[#3810](https://github.com/woodpecker-ci/woodpecker/pull/3810)] +- Fix newlines in logs [[#3808](https://github.com/woodpecker-ci/woodpecker/pull/3808)] +- Fix authentication error handling [[#3807](https://github.com/woodpecker-ci/woodpecker/pull/3807)] + +### 📚 Documentation + +- Streamline docs for new users [[#3803](https://github.com/woodpecker-ci/woodpecker/pull/3803)] +- Add mastodon verification [[#3843](https://github.com/woodpecker-ci/woodpecker/pull/3843)] +- chore(deps): update docs npm deps non-major [[#3837](https://github.com/woodpecker-ci/woodpecker/pull/3837)] +- fix(deps): update docs npm deps non-major [[#3824](https://github.com/woodpecker-ci/woodpecker/pull/3824)] +- Add openSUSE package [[#3800](https://github.com/woodpecker-ci/woodpecker/pull/3800)] +- chore(deps): update docs npm deps non-major [[#3798](https://github.com/woodpecker-ci/woodpecker/pull/3798)] +- Add "Docker Tags" Plugin [[#3796](https://github.com/woodpecker-ci/woodpecker/pull/3796)] +- chore(deps): update dependency marked to v13 [[#3792](https://github.com/woodpecker-ci/woodpecker/pull/3792)] +- chore: fix some comments [[#3788](https://github.com/woodpecker-ci/woodpecker/pull/3788)] + +### Misc + +- chore(deps): update web npm deps non-major [[#3930](https://github.com/woodpecker-ci/woodpecker/pull/3930)] +- chore(deps): update dependency vitest to v2 [[#3905](https://github.com/woodpecker-ci/woodpecker/pull/3905)] +- fix(deps): update module github.com/google/go-github/v62 to v63 [[#3910](https://github.com/woodpecker-ci/woodpecker/pull/3910)] +- chore(deps): update docker.io/woodpeckerci/plugin-docker-buildx docker tag to v4.1.0 [[#3908](https://github.com/woodpecker-ci/woodpecker/pull/3908)] +- Update plugin-git and add renovate trigger [[#3901](https://github.com/woodpecker-ci/woodpecker/pull/3901)] +- chore(deps): update docker.io/mstruebing/editorconfig-checker docker tag to v3.0.3 [[#3903](https://github.com/woodpecker-ci/woodpecker/pull/3903)] +- fix(deps): update golang-packages [[#3875](https://github.com/woodpecker-ci/woodpecker/pull/3875)] +- chore(deps): lock file maintenance [[#3876](https://github.com/woodpecker-ci/woodpecker/pull/3876)] +- [pre-commit.ci] pre-commit autoupdate [[#3862](https://github.com/woodpecker-ci/woodpecker/pull/3862)] +- Add dummy backend [[#3820](https://github.com/woodpecker-ci/woodpecker/pull/3820)] +- chore(deps): update dependency replace-in-file to v8 [[#3852](https://github.com/woodpecker-ci/woodpecker/pull/3852)] +- Update forgejo sdk [[#3840](https://github.com/woodpecker-ci/woodpecker/pull/3840)] +- chore(deps): lock file maintenance [[#3838](https://github.com/woodpecker-ci/woodpecker/pull/3838)] +- Allow to set dist dir using env var [[#3814](https://github.com/woodpecker-ci/woodpecker/pull/3814)] +- chore(deps): lock file maintenance [[#3805](https://github.com/woodpecker-ci/woodpecker/pull/3805)] +- chore(deps): update docker.io/lycheeverse/lychee docker tag to v0.15.1 [[#3797](https://github.com/woodpecker-ci/woodpecker/pull/3797)] + +## [2.6.1](https://github.com/woodpecker-ci/woodpecker/releases/tag/v2.6.1) - 2024-07-19 + +### 🔒 Security + +- Add blocklist of environment variables who could alter execution of plugins [[#3934](https://github.com/woodpecker-ci/woodpecker/pull/3934)] +- Make sure plugins only mount the workspace base in a predefinde location [[#3933](https://github.com/woodpecker-ci/woodpecker/pull/3933)] +- Disalow to set arbitrary environments for plugins [[#3909](https://github.com/woodpecker-ci/woodpecker/pull/3909)] +- Bump trivy plugin version and remove unused variable [[#3833](https://github.com/woodpecker-ci/woodpecker/pull/3833)] + +### 🐛 Bug Fixes + +- Let webhook pass on pipeline parsion error [[#3829](https://github.com/woodpecker-ci/woodpecker/pull/3829)] +- Fix newlines in logs [[#3808](https://github.com/woodpecker-ci/woodpecker/pull/3808)] + ## [2.6.0](https://github.com/woodpecker-ci/woodpecker/releases/tag/v2.6.0) - 2024-06-13 ### ❤️ Thanks to all contributors! ❤️ diff --git a/Makefile b/Makefile index 627e0381e..a9cf6bb42 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ else endif TAGS ?= -LDFLAGS := -X go.woodpecker-ci.org/woodpecker/v2/version.Version=${VERSION} +LDFLAGS := -X go.woodpecker-ci.org/woodpecker/v3/version.Version=${VERSION} STATIC_BUILD ?= true ifeq ($(STATIC_BUILD),true) LDFLAGS := -s -w -extldflags "-static" $(LDFLAGS) @@ -39,7 +39,8 @@ CGO_ENABLED ?= 1 # only used to compile server HAS_GO = $(shell hash go > /dev/null 2>&1 && echo "GO" || echo "NOGO" ) ifeq ($(HAS_GO),GO) - XGO_VERSION ?= go-1.20.x + # renovate: datasource=docker depName=docker.io/techknowlogick/xgo + XGO_VERSION ?= go-1.23.x CGO_CFLAGS ?= $(shell go env CGO_CFLAGS) endif CGO_CFLAGS ?= @@ -108,15 +109,15 @@ clean: ## Clean build artifacts clean-all: clean ## Clean all artifacts rm -rf ${DIST_DIR} web/dist docs/build docs/node_modules web/node_modules # delete generated - rm -rf docs/docs/40-cli.md docs/swagger.json + rm -rf docs/docs/40-cli.md docs/openapi.json .PHONY: generate -generate: install-tools generate-swagger ## Run all code generations +generate: install-tools generate-openapi ## Run all code generations CGO_ENABLED=0 go generate ./... -generate-swagger: install-tools ## Run swagger code generation - swag init -g server/api/ -g cmd/server/swagger.go --outputTypes go -output cmd/server/docs - CGO_ENABLED=0 go generate cmd/server/swagger.go +generate-openapi: install-tools ## Run openapi code generation and format it + go run github.com/swaggo/swag/cmd/swag fmt + CGO_ENABLED=0 go generate cmd/server/openapi.go generate-license-header: install-tools addlicense -c "Woodpecker Authors" -ignore "vendor/**" **/*.go @@ -133,9 +134,6 @@ install-tools: ## Install development tools hash gofumpt > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ go install mvdan.cc/gofumpt@latest; \ fi ; \ - hash swag > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ - go install github.com/swaggo/swag/cmd/swag@latest; \ - fi ; \ hash addlicense > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ go install github.com/google/addlicense@latest; \ fi ; \ @@ -163,20 +161,20 @@ lint-ui: ui-dependencies ## Lint UI code (cd web/; pnpm lint --quiet) test-agent: ## Test agent code - go test -race -cover -coverprofile agent-coverage.out -timeout 60s -tags 'test $(TAGS)' go.woodpecker-ci.org/woodpecker/v2/cmd/agent go.woodpecker-ci.org/woodpecker/v2/agent/... + go test -race -cover -coverprofile agent-coverage.out -timeout 60s -tags 'test $(TAGS)' go.woodpecker-ci.org/woodpecker/v3/cmd/agent go.woodpecker-ci.org/woodpecker/v3/agent/... test-server: ## Test server code - go test -race -cover -coverprofile server-coverage.out -timeout 60s -tags 'test $(TAGS)' go.woodpecker-ci.org/woodpecker/v2/cmd/server $(shell go list go.woodpecker-ci.org/woodpecker/v2/server/... | grep -v '/store') + go test -race -cover -coverprofile server-coverage.out -timeout 60s -tags 'test $(TAGS)' go.woodpecker-ci.org/woodpecker/v3/cmd/server $(shell go list go.woodpecker-ci.org/woodpecker/v3/server/... | grep -v '/store') test-cli: ## Test cli code - go test -race -cover -coverprofile cli-coverage.out -timeout 60s -tags 'test $(TAGS)' go.woodpecker-ci.org/woodpecker/v2/cmd/cli go.woodpecker-ci.org/woodpecker/v2/cli/... + go test -race -cover -coverprofile cli-coverage.out -timeout 60s -tags 'test $(TAGS)' go.woodpecker-ci.org/woodpecker/v3/cmd/cli go.woodpecker-ci.org/woodpecker/v3/cli/... test-server-datastore: ## Test server datastore - go test -timeout 300s -tags 'test $(TAGS)' -run TestMigrate go.woodpecker-ci.org/woodpecker/v2/server/store/... - go test -race -timeout 100s -tags 'test $(TAGS)' -skip TestMigrate go.woodpecker-ci.org/woodpecker/v2/server/store/... + go test -timeout 300s -tags 'test $(TAGS)' -run TestMigrate go.woodpecker-ci.org/woodpecker/v3/server/store/... + go test -race -timeout 100s -tags 'test $(TAGS)' -skip TestMigrate go.woodpecker-ci.org/woodpecker/v3/server/store/... test-server-datastore-coverage: ## Test server datastore with coverage report - go test -race -cover -coverprofile datastore-coverage.out -timeout 300s -tags 'test $(TAGS)' go.woodpecker-ci.org/woodpecker/v2/server/store/... + go test -race -cover -coverprofile datastore-coverage.out -timeout 300s -tags 'test $(TAGS)' go.woodpecker-ci.org/woodpecker/v3/server/store/... test-ui: ui-dependencies ## Test UI code (cd web/; pnpm run lint) @@ -195,14 +193,14 @@ test: test-agent test-server test-server-datastore test-cli test-lib ## Run all build-ui: ## Build UI (cd web/; pnpm install --frozen-lockfile; pnpm build) -build-server: build-ui generate-swagger ## Build server - CGO_ENABLED=${CGO_ENABLED} GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -tags '$(TAGS)' -ldflags '${LDFLAGS}' -o ${DIST_DIR}/woodpecker-server${BIN_SUFFIX} go.woodpecker-ci.org/woodpecker/v2/cmd/server +build-server: build-ui generate-openapi ## Build server + CGO_ENABLED=${CGO_ENABLED} GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -tags '$(TAGS)' -ldflags '${LDFLAGS}' -o ${DIST_DIR}/woodpecker-server${BIN_SUFFIX} go.woodpecker-ci.org/woodpecker/v3/cmd/server build-agent: ## Build agent - CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -tags '$(TAGS)' -ldflags '${LDFLAGS}' -o ${DIST_DIR}/woodpecker-agent${BIN_SUFFIX} go.woodpecker-ci.org/woodpecker/v2/cmd/agent + CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -tags '$(TAGS)' -ldflags '${LDFLAGS}' -o ${DIST_DIR}/woodpecker-agent${BIN_SUFFIX} go.woodpecker-ci.org/woodpecker/v3/cmd/agent build-cli: ## Build cli - CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -tags '$(TAGS)' -ldflags '${LDFLAGS}' -o ${DIST_DIR}/woodpecker-cli${BIN_SUFFIX} go.woodpecker-ci.org/woodpecker/v2/cmd/cli + CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -tags '$(TAGS)' -ldflags '${LDFLAGS}' -o ${DIST_DIR}/woodpecker-cli${BIN_SUFFIX} go.woodpecker-ci.org/woodpecker/v3/cmd/cli build-tarball: ## Build tar archive mkdir -p ${DIST_DIR} && tar chzvf ${DIST_DIR}/woodpecker-src.tar.gz \ @@ -235,54 +233,75 @@ release-server-xgo: check-xgo ## Create server binaries for release using xgo @echo "arch orgi:$(TARGETARCH)" @echo "arch (xgo):$(TARGETARCH_XGO)" @echo "arch (buildx):$(TARGETARCH_BUILDX)" - + # build via xgo CGO_CFLAGS="$(CGO_CFLAGS)" xgo -go $(XGO_VERSION) -dest ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH_BUILDX) -tags 'netgo osusergo grpcnotrace $(TAGS)' -ldflags '-linkmode external $(LDFLAGS)' -targets '$(TARGETOS)/$(TARGETARCH_XGO)' -out woodpecker-server -pkg cmd/server . - @if [ "$${XGO_IN_XGO:-0}" -eq "1" ]; then echo "inside xgo image"; \ + # move binary into subfolder depending on target os and arch + @if [ "$${XGO_IN_XGO:-0}" -eq "1" ]; then \ + echo "inside xgo image"; \ mkdir -p ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH_BUILDX); \ mv -vf /build/woodpecker-server* ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH_BUILDX)/woodpecker-server$(BIN_SUFFIX); \ - else echo "outside xgo image"; \ + else \ + echo "outside xgo image"; \ [ -f "${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH_BUILDX)/woodpecker-server$(BIN_SUFFIX)" ] && rm -v ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH_BUILDX)/woodpecker-server$(BIN_SUFFIX); \ mv -v ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH_XGO)/woodpecker-server* ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH_BUILDX)/woodpecker-server$(BIN_SUFFIX); \ fi - @[ "$${TARGZ:-0}" -eq "1" ] && tar -cvzf ${DIST_DIR}/woodpecker-server_$(TARGETOS)_$(TARGETARCH_BUILDX).tar.gz -C ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH_BUILDX) woodpecker-server$(BIN_SUFFIX) || echo "skip creating '${DIST_DIR}/woodpecker-server_$(TARGETOS)_$(TARGETARCH_BUILDX).tar.gz'" + # if enabled package it in an archive + @if [ "$${ARCHIVE_IT:-0}" -eq "1" ]; then \ + if [ "$(BIN_SUFFIX)" = ".exe" ]; then \ + rm -f ${DIST_DIR}/woodpecker-server_$(TARGETOS)_$(TARGETARCH_BUILDX).zip; \ + zip -j ${DIST_DIR}/woodpecker-server_$(TARGETOS)_$(TARGETARCH_BUILDX).zip ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH_BUILDX)/woodpecker-server.exe; \ + else \ + tar -cvzf ${DIST_DIR}/woodpecker-server_$(TARGETOS)_$(TARGETARCH_BUILDX).tar.gz -C ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH_BUILDX) woodpecker-server$(BIN_SUFFIX); \ + fi; \ + else \ + echo "skip creating '${DIST_DIR}/woodpecker-server_$(TARGETOS)_$(TARGETARCH_BUILDX).tar.gz'"; \ + fi release-server: ## Create server binaries for release # compile - GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) CGO_ENABLED=${CGO_ENABLED} go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH)/woodpecker-server$(BIN_SUFFIX) go.woodpecker-ci.org/woodpecker/v2/cmd/server + GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) CGO_ENABLED=${CGO_ENABLED} go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH)/woodpecker-server$(BIN_SUFFIX) go.woodpecker-ci.org/woodpecker/v3/cmd/server # tar binary files - tar -cvzf ${DIST_DIR}/woodpecker-server_$(TARGETOS)_$(TARGETARCH).tar.gz -C ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH) woodpecker-server$(BIN_SUFFIX) + if [ "$(BIN_SUFFIX)" == ".exe" ]; then \ + zip -j ${DIST_DIR}/woodpecker-server_$(TARGETOS)_$(TARGETARCH).zip ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH)/woodpecker-server.exe; \ + else \ + tar -cvzf ${DIST_DIR}/woodpecker-server_$(TARGETOS)_$(TARGETARCH).tar.gz -C ${DIST_DIR}/server/$(TARGETOS)_$(TARGETARCH) woodpecker-server$(BIN_SUFFIX); \ + fi release-agent: ## Create agent binaries for release # compile - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/linux_amd64/woodpecker-agent go.woodpecker-ci.org/woodpecker/v2/cmd/agent - GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/linux_arm64/woodpecker-agent go.woodpecker-ci.org/woodpecker/v2/cmd/agent - GOOS=linux GOARCH=arm CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/linux_arm/woodpecker-agent go.woodpecker-ci.org/woodpecker/v2/cmd/agent - GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/windows_amd64/woodpecker-agent.exe go.woodpecker-ci.org/woodpecker/v2/cmd/agent - GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/darwin_amd64/woodpecker-agent go.woodpecker-ci.org/woodpecker/v2/cmd/agent - GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/darwin_arm64/woodpecker-agent go.woodpecker-ci.org/woodpecker/v2/cmd/agent + GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/linux_amd64/woodpecker-agent go.woodpecker-ci.org/woodpecker/v3/cmd/agent + GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/linux_arm64/woodpecker-agent go.woodpecker-ci.org/woodpecker/v3/cmd/agent + GOOS=linux GOARCH=arm CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/linux_arm/woodpecker-agent go.woodpecker-ci.org/woodpecker/v3/cmd/agent + GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/windows_amd64/woodpecker-agent.exe go.woodpecker-ci.org/woodpecker/v3/cmd/agent + GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/darwin_amd64/woodpecker-agent go.woodpecker-ci.org/woodpecker/v3/cmd/agent + GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -tags 'grpcnotrace $(TAGS)' -o ${DIST_DIR}/agent/darwin_arm64/woodpecker-agent go.woodpecker-ci.org/woodpecker/v3/cmd/agent # tar binary files tar -cvzf ${DIST_DIR}/woodpecker-agent_linux_amd64.tar.gz -C ${DIST_DIR}/agent/linux_amd64 woodpecker-agent tar -cvzf ${DIST_DIR}/woodpecker-agent_linux_arm64.tar.gz -C ${DIST_DIR}/agent/linux_arm64 woodpecker-agent tar -cvzf ${DIST_DIR}/woodpecker-agent_linux_arm.tar.gz -C ${DIST_DIR}/agent/linux_arm woodpecker-agent - tar -cvzf ${DIST_DIR}/woodpecker-agent_windows_amd64.tar.gz -C ${DIST_DIR}/agent/windows_amd64 woodpecker-agent.exe tar -cvzf ${DIST_DIR}/woodpecker-agent_darwin_amd64.tar.gz -C ${DIST_DIR}/agent/darwin_amd64 woodpecker-agent tar -cvzf ${DIST_DIR}/woodpecker-agent_darwin_arm64.tar.gz -C ${DIST_DIR}/agent/darwin_arm64 woodpecker-agent + # zip binary files + rm -f ${DIST_DIR}/woodpecker-agent_windows_amd64.zip + zip -j ${DIST_DIR}/woodpecker-agent_windows_amd64.zip ${DIST_DIR}/agent/windows_amd64/woodpecker-agent.exe release-cli: ## Create cli binaries for release # compile - GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/linux_amd64/woodpecker-cli go.woodpecker-ci.org/woodpecker/v2/cmd/cli - GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/linux_arm64/woodpecker-cli go.woodpecker-ci.org/woodpecker/v2/cmd/cli - GOOS=linux GOARCH=arm CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/linux_arm/woodpecker-cli go.woodpecker-ci.org/woodpecker/v2/cmd/cli - GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/windows_amd64/woodpecker-cli.exe go.woodpecker-ci.org/woodpecker/v2/cmd/cli - GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/darwin_amd64/woodpecker-cli go.woodpecker-ci.org/woodpecker/v2/cmd/cli - GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/darwin_arm64/woodpecker-cli go.woodpecker-ci.org/woodpecker/v2/cmd/cli + GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/linux_amd64/woodpecker-cli go.woodpecker-ci.org/woodpecker/v3/cmd/cli + GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/linux_arm64/woodpecker-cli go.woodpecker-ci.org/woodpecker/v3/cmd/cli + GOOS=linux GOARCH=arm CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/linux_arm/woodpecker-cli go.woodpecker-ci.org/woodpecker/v3/cmd/cli + GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/windows_amd64/woodpecker-cli.exe go.woodpecker-ci.org/woodpecker/v3/cmd/cli + GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/darwin_amd64/woodpecker-cli go.woodpecker-ci.org/woodpecker/v3/cmd/cli + GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -ldflags '${LDFLAGS}' -o ${DIST_DIR}/cli/darwin_arm64/woodpecker-cli go.woodpecker-ci.org/woodpecker/v3/cmd/cli # tar binary files tar -cvzf ${DIST_DIR}/woodpecker-cli_linux_amd64.tar.gz -C ${DIST_DIR}/cli/linux_amd64 woodpecker-cli tar -cvzf ${DIST_DIR}/woodpecker-cli_linux_arm64.tar.gz -C ${DIST_DIR}/cli/linux_arm64 woodpecker-cli tar -cvzf ${DIST_DIR}/woodpecker-cli_linux_arm.tar.gz -C ${DIST_DIR}/cli/linux_arm woodpecker-cli - tar -cvzf ${DIST_DIR}/woodpecker-cli_windows_amd64.tar.gz -C ${DIST_DIR}/cli/windows_amd64 woodpecker-cli.exe tar -cvzf ${DIST_DIR}/woodpecker-cli_darwin_amd64.tar.gz -C ${DIST_DIR}/cli/darwin_amd64 woodpecker-cli tar -cvzf ${DIST_DIR}/woodpecker-cli_darwin_arm64.tar.gz -C ${DIST_DIR}/cli/darwin_arm64 woodpecker-cli + # zip binary files + rm -f ${DIST_DIR}/woodpecker-cli_windows_amd64.zip + zip -j ${DIST_DIR}/woodpecker-cli_windows_amd64.zip ${DIST_DIR}/cli/windows_amd64/woodpecker-cli.exe release-checksums: ## Create checksums for all release files # generate shas for tar files @@ -321,6 +340,6 @@ spellcheck: .PHONY: docs docs: ## Generate docs (currently only for the cli) CGO_ENABLED=0 go generate cmd/cli/app.go - CGO_ENABLED=0 go generate cmd/server/swagger.go + CGO_ENABLED=0 go generate cmd/server/openapi.go endif diff --git a/README.md b/README.md index dec158360..9806ceb33 100644 --- a/README.md +++ b/README.md @@ -19,11 +19,11 @@ Matrix space - - Go Report Card + + Go Report Card - - go reference + + go reference GitHub release @@ -43,55 +43,48 @@


-Woodpecker is a simple yet powerful CI/CD engine with great extensibility. +Woodpecker is a simple, yet powerful CI/CD engine with great extensibility. -![woodpecker](docs/docs/woodpecker.png) +![woodpecker](docs/woodpecker.png) -## 🫶 Support +## Installation & Resources -Please consider donating and become a backer. 🙏 [[Become a backer](https://opencollective.com/woodpecker-ci#category-CONTRIBUTE)] +Woodpecker can be installed in various ways (see the [Installation Instructions](https://woodpecker-ci.org/docs/administration/getting-started)) and runs with SQLite as database by default. +It requires around 100 MB of RAM (Server) and 30 MB (Agent) at runtime in idle mode. + +## Support + +You can support the project by becoming a backer on [Open Collective](https://opencollective.com/woodpecker-ci#category-CONTRIBUTE) or via [GitHub Sponsors](https://github.com/sponsors/woodpecker-ci).
Open Collective backers -## 📖 Documentation +## Documentation - +Our documentation can be found at . -## ✨ Contribute +## Translation -See [Contributing Guide](https://github.com/woodpecker-ci/.github/blob/main/CONTRIBUTING.md) +We have a self-hosted [Weblate](https://weblate.org/en/) instance at [translate.woodpecker-ci.org](https://translate.woodpecker-ci.org). -[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://woodpecker-ci.org/docs/next/development/getting-started#gitpod) +An overview of the current translation state is available at . -## 📣 Translate +## Public Woodpecker Instances -We use an own [Weblate](https://weblate.org/en/) instance at [translate.woodpecker-ci.org](https://translate.woodpecker-ci.org). +Woodpecker is used as the main CI/CD engine at [Codeberg](https://codeberg.org), an alternative Git hosting platform with a focus on privacy and free software development. - - Translation status - +## Plugins -## 👋 Who uses Woodpecker? +Woodpecker can be extended via plugins. +The [plugin overview website](https://woodpecker-ci.org/plugins) helps browsing available plugins. +It combines both plugins by the Woodpecker core team and community-maintained ones. -Woodpecker is used by [itself](https://ci.woodpecker-ci.org/woodpecker/woodpecker-ci/), multiple well-known companies, organizations like [Codeberg](https://codeberg.org), hobbyists and many others. +## Star History -Leave a [comment](https://github.com/woodpecker-ci/woodpecker/discussions/2149) if you're using it as well. - -Also consider using the topic `WoodpeckerCI` in your repository, so others can learn from your config and use the hashtag `#WoodpeckerCI` when talking about the project on social media! - -Here are some places where people mention Woodpecker: - -- [GitHub](https://github.com/topics/WoodpeckerCI) -- [Codeberg](https://codeberg.org/explore/repos?q=woodpeckerci&topic=1) -- [Twitter](https://twitter.com/search?q=%23WoodpeckerCI&src=typed_query) -- [Fediverse](https://mastodon.social/tags/WoodpeckerCI) - -## ✨ Stars over time - -[![Stargazers over time](https://starchart.cc/woodpecker-ci/woodpecker.svg)](https://starchart.cc/woodpecker-ci/woodpecker) +[![Star History Chart](https://api.star-history.com/svg?repos=woodpecker-ci/woodpecker&type=Date)](https://star-history.com/#woodpecker-ci/woodpecker&Date) ## License -Woodpecker is Apache 2.0 licensed with the source files in this repository having a header indicating which license they are under and what copyrights apply. +Woodpecker is Apache 2.0 licensed. +The source files have a header indicating which license they are under and what copyrights apply. -Files under the `docs/` folder are licensed under Creative Commons Attribution-ShareAlike 4.0 International Public License. +Everything in `docs/` is licensed under the Creative Commons Attribution-ShareAlike 4.0 International Public License. diff --git a/agent/logger.go b/agent/logger.go index 7847999a7..47413df90 100644 --- a/agent/logger.go +++ b/agent/logger.go @@ -20,16 +20,10 @@ import ( "github.com/rs/zerolog" - "go.woodpecker-ci.org/woodpecker/v2/pipeline" - backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/log" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc" -) - -const ( - // Store not more than 1mb in a log-line as 4mb is the limit of a grpc message - // and log-lines needs to be parsed by the browsers later on. - maxLogLineLength = 1024 * 1024 // 1mb + "go.woodpecker-ci.org/woodpecker/v3/pipeline" + backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/log" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc" ) func (r *Runner) createLogger(_logger zerolog.Logger, uploads *sync.WaitGroup, workflow *rpc.Workflow) pipeline.Logger { @@ -38,7 +32,6 @@ func (r *Runner) createLogger(_logger zerolog.Logger, uploads *sync.WaitGroup, w logger := _logger.With(). Str("image", step.Image). - Str("workflow_id", workflow.ID). Logger() uploads.Add(1) @@ -51,7 +44,7 @@ func (r *Runner) createLogger(_logger zerolog.Logger, uploads *sync.WaitGroup, w logger.Debug().Msg("log stream opened") logStream := log.NewLineWriter(r.client, step.UUID, secrets...) - if err := log.CopyLineByLine(logStream, rc, maxLogLineLength); err != nil { + if err := log.CopyLineByLine(logStream, rc, pipeline.MaxLogLineLength); err != nil { logger.Error().Err(err).Msg("copy limited logStream part") } diff --git a/agent/rpc/auth_client_grpc.go b/agent/rpc/auth_client_grpc.go index 9562f47ea..7abc7835d 100644 --- a/agent/rpc/auth_client_grpc.go +++ b/agent/rpc/auth_client_grpc.go @@ -20,9 +20,11 @@ import ( "google.golang.org/grpc" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc/proto" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc/proto" ) +const authClientTimeout = time.Second * 5 + type AuthClient struct { client proto.WoodpeckerAuthClient conn *grpc.ClientConn @@ -39,8 +41,8 @@ func NewAuthGrpcClient(conn *grpc.ClientConn, agentToken string, agentID int64) return client } -func (c *AuthClient) Auth() (string, int64, error) { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) //nolint:mnd +func (c *AuthClient) Auth(ctx context.Context) (string, int64, error) { + ctx, cancel := context.WithTimeout(ctx, authClientTimeout) defer cancel() req := &proto.AuthRequest{ diff --git a/agent/rpc/auth_interceptor.go b/agent/rpc/auth_interceptor.go index 1283aa970..17aa2a587 100644 --- a/agent/rpc/auth_interceptor.go +++ b/agent/rpc/auth_interceptor.go @@ -30,15 +30,12 @@ type AuthInterceptor struct { } // NewAuthInterceptor returns a new auth interceptor. -func NewAuthInterceptor( - authClient *AuthClient, - refreshDuration time.Duration, -) (*AuthInterceptor, error) { +func NewAuthInterceptor(ctx context.Context, authClient *AuthClient, refreshDuration time.Duration) (*AuthInterceptor, error) { interceptor := &AuthInterceptor{ authClient: authClient, } - err := interceptor.scheduleRefreshToken(refreshDuration) + err := interceptor.scheduleRefreshToken(ctx, refreshDuration) if err != nil { return nil, err } @@ -78,21 +75,26 @@ func (interceptor *AuthInterceptor) attachToken(ctx context.Context) context.Con return metadata.AppendToOutgoingContext(ctx, "token", interceptor.accessToken) } -func (interceptor *AuthInterceptor) scheduleRefreshToken(refreshDuration time.Duration) error { - err := interceptor.refreshToken() +func (interceptor *AuthInterceptor) scheduleRefreshToken(ctx context.Context, refreshInterval time.Duration) error { + err := interceptor.refreshToken(ctx) if err != nil { return err } go func() { - wait := refreshDuration + wait := refreshInterval + for { - time.Sleep(wait) - err := interceptor.refreshToken() - if err != nil { - wait = time.Second - } else { - wait = refreshDuration + select { + case <-ctx.Done(): + return + case <-time.After(wait): + err := interceptor.refreshToken(ctx) + if err != nil { + wait = time.Second + } else { + wait = refreshInterval + } } } }() @@ -100,8 +102,8 @@ func (interceptor *AuthInterceptor) scheduleRefreshToken(refreshDuration time.Du return nil } -func (interceptor *AuthInterceptor) refreshToken() error { - accessToken, _, err := interceptor.authClient.Auth() +func (interceptor *AuthInterceptor) refreshToken(ctx context.Context) error { + accessToken, _, err := interceptor.authClient.Auth(ctx) if err != nil { return err } diff --git a/agent/rpc/client_grpc.go b/agent/rpc/client_grpc.go index 5fb747ac0..29c552e2a 100644 --- a/agent/rpc/client_grpc.go +++ b/agent/rpc/client_grpc.go @@ -17,44 +17,57 @@ package rpc import ( "context" "encoding/json" - "fmt" "strings" "time" - "github.com/cenkalti/backoff/v4" + "github.com/cenkalti/backoff/v5" "github.com/rs/zerolog/log" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + grpcproto "google.golang.org/protobuf/proto" - backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc/proto" + backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc/proto" ) -// Set grpc version on compile time to compare against server version response. -const ClientGrpcVersion int32 = proto.Version +const ( + // Set grpc version on compile time to compare against server version response. + ClientGrpcVersion int32 = proto.Version + + // Maximum size of an outgoing log message. + // Picked to prevent it from going over GRPC size limit (4 MiB) with a large safety margin. + maxLogBatchSize int = 1 * 1024 * 1024 + + // Maximum amount of time between sending consecutive batched log messages. + // Controls the delay between the CI job generating a log record, and web users receiving it. + maxLogFlushPeriod time.Duration = time.Second +) type client struct { client proto.WoodpeckerClient conn *grpc.ClientConn + logs chan *proto.LogEntry } // NewGrpcClient returns a new grpc Client. -func NewGrpcClient(conn *grpc.ClientConn) rpc.Peer { +func NewGrpcClient(ctx context.Context, conn *grpc.ClientConn) rpc.Peer { client := new(client) client.client = proto.NewWoodpeckerClient(conn) client.conn = conn + client.logs = make(chan *proto.LogEntry, 10) // max memory use: 10 lines * 1 MiB + go client.processLogs(ctx) return client } func (c *client) Close() error { + close(c.logs) return c.conn.Close() } func (c *client) newBackOff() backoff.BackOff { b := backoff.NewExponentialBackOff() - b.MaxElapsedTime = 0 b.MaxInterval = 10 * time.Second //nolint:mnd b.InitialInterval = 10 * time.Millisecond //nolint:mnd return b @@ -73,13 +86,13 @@ func (c *client) Version(ctx context.Context) (*rpc.Version, error) { } // Next returns the next workflow in the queue. -func (c *client) Next(ctx context.Context, f rpc.Filter) (*rpc.Workflow, error) { +func (c *client) Next(ctx context.Context, filter rpc.Filter) (*rpc.Workflow, error) { var res *proto.NextResponse var err error retry := c.newBackOff() req := new(proto.NextRequest) req.Filter = new(proto.Filter) - req.Filter.Labels = f.Labels + req.Filter.Labels = filter.Labels for { res, err = c.client.Next(ctx, req) if err == nil { @@ -90,8 +103,10 @@ func (c *client) Next(ctx context.Context, f rpc.Filter) (*rpc.Workflow, error) case codes.Canceled: if ctx.Err() != nil { // expected as context was canceled + log.Debug().Err(err).Msgf("grpc error: next(): context canceled") return nil, nil } + log.Error().Err(err).Msgf("grpc error: next(): code: %v", status.Code(err)) return nil, err case codes.Aborted, @@ -105,10 +120,11 @@ func (c *client) Next(ctx context.Context, f rpc.Filter) (*rpc.Workflow, error) // https://github.com/woodpecker-ci/woodpecker/issues/717#issuecomment-1049365104 log.Trace().Err(err).Msg("grpc: to many keepalive pings without sending data") } else { - log.Error().Err(err).Msgf("grpc error: next(): code: %v", status.Code(err)) + log.Warn().Err(err).Msgf("grpc error: next(): code: %v", status.Code(err)) } default: - return nil, fmt.Errorf("grpc error: next(): code: %v: %w", status.Code(err), err) + log.Error().Err(err).Msgf("grpc error: next(): code: %v", status.Code(err)) + return nil, err } select { @@ -143,9 +159,15 @@ func (c *client) Wait(ctx context.Context, workflowID string) (err error) { break } - log.Error().Err(err).Msgf("grpc error: wait(): code: %v", status.Code(err)) - switch status.Code(err) { + case codes.Canceled: + if ctx.Err() != nil { + // expected as context was canceled + log.Debug().Err(err).Msgf("grpc error: wait(): context canceled") + return nil + } + log.Error().Err(err).Msgf("grpc error: wait(): code: %v", status.Code(err)) + return err case codes.Aborted, codes.DataLoss, @@ -153,7 +175,9 @@ func (c *client) Wait(ctx context.Context, workflowID string) (err error) { codes.Internal, codes.Unavailable: // non-fatal errors + log.Warn().Err(err).Msgf("grpc error: wait(): code: %v", status.Code(err)) default: + log.Error().Err(err).Msgf("grpc error: wait(): code: %v", status.Code(err)) return err } @@ -184,6 +208,14 @@ func (c *client) Init(ctx context.Context, workflowID string, state rpc.Workflow log.Error().Err(err).Msgf("grpc error: init(): code: %v", status.Code(err)) switch status.Code(err) { + case codes.Canceled: + if ctx.Err() != nil { + // expected as context was canceled + log.Debug().Err(err).Msgf("grpc error: init(): context canceled") + return nil + } + log.Error().Err(err).Msgf("grpc error: init(): code: %v", status.Code(err)) + return err case codes.Aborted, codes.DataLoss, @@ -191,7 +223,9 @@ func (c *client) Init(ctx context.Context, workflowID string, state rpc.Workflow codes.Internal, codes.Unavailable: // non-fatal errors + log.Warn().Err(err).Msgf("grpc error: init(): code: %v", status.Code(err)) default: + log.Error().Err(err).Msgf("grpc error: init(): code: %v", status.Code(err)) return err } @@ -222,6 +256,14 @@ func (c *client) Done(ctx context.Context, workflowID string, state rpc.Workflow log.Error().Err(err).Msgf("grpc error: done(): code: %v", status.Code(err)) switch status.Code(err) { + case codes.Canceled: + if ctx.Err() != nil { + // expected as context was canceled + log.Debug().Err(err).Msgf("grpc error: done(): context canceled") + return nil + } + log.Error().Err(err).Msgf("grpc error: done(): code: %v", status.Code(err)) + return err case codes.Aborted, codes.DataLoss, @@ -229,7 +271,9 @@ func (c *client) Done(ctx context.Context, workflowID string, state rpc.Workflow codes.Internal, codes.Unavailable: // non-fatal errors + log.Warn().Err(err).Msgf("grpc error: done(): code: %v", status.Code(err)) default: + log.Error().Err(err).Msgf("grpc error: done(): code: %v", status.Code(err)) return err } @@ -256,6 +300,14 @@ func (c *client) Extend(ctx context.Context, workflowID string) (err error) { log.Error().Err(err).Msgf("grpc error: extend(): code: %v", status.Code(err)) switch status.Code(err) { + case codes.Canceled: + if ctx.Err() != nil { + // expected as context was canceled + log.Debug().Err(err).Msgf("grpc error: extend(): context canceled") + return nil + } + log.Error().Err(err).Msgf("grpc error: extend(): code: %v", status.Code(err)) + return err case codes.Aborted, codes.DataLoss, @@ -263,7 +315,9 @@ func (c *client) Extend(ctx context.Context, workflowID string) (err error) { codes.Internal, codes.Unavailable: // non-fatal errors + log.Warn().Err(err).Msgf("grpc error: extend(): code: %v", status.Code(err)) default: + log.Error().Err(err).Msgf("grpc error: extend(): code: %v", status.Code(err)) return err } @@ -297,6 +351,14 @@ func (c *client) Update(ctx context.Context, workflowID string, state rpc.StepSt log.Error().Err(err).Msgf("grpc error: update(): code: %v", status.Code(err)) switch status.Code(err) { + case codes.Canceled: + if ctx.Err() != nil { + // expected as context was canceled + log.Debug().Err(err).Msgf("grpc error: update(): context canceled") + return nil + } + log.Error().Err(err).Msgf("grpc error: update(): code: %v", status.Code(err)) + return err case codes.Aborted, codes.DataLoss, @@ -304,7 +366,9 @@ func (c *client) Update(ctx context.Context, workflowID string, state rpc.StepSt codes.Internal, codes.Unavailable: // non-fatal errors + log.Warn().Err(err).Msgf("grpc error: update(): code: %v", status.Code(err)) default: + log.Error().Err(err).Msgf("grpc error: update(): code: %v", status.Code(err)) return err } @@ -317,25 +381,82 @@ func (c *client) Update(ctx context.Context, workflowID string, state rpc.StepSt return nil } -// Log writes the step log entry. -func (c *client) Log(ctx context.Context, logEntry *rpc.LogEntry) (err error) { - retry := c.newBackOff() - req := new(proto.LogRequest) - req.LogEntry = new(proto.LogEntry) - req.LogEntry.StepUuid = logEntry.StepUUID - req.LogEntry.Data = logEntry.Data - req.LogEntry.Line = int32(logEntry.Line) - req.LogEntry.Time = logEntry.Time - req.LogEntry.Type = int32(logEntry.Type) +// EnqueueLog queues the log entry to be written in a batch later. +func (c *client) EnqueueLog(logEntry *rpc.LogEntry) { + c.logs <- &proto.LogEntry{ + StepUuid: logEntry.StepUUID, + Data: logEntry.Data, + Line: int32(logEntry.Line), + Time: logEntry.Time, + Type: int32(logEntry.Type), + } +} + +func (c *client) processLogs(ctx context.Context) { + var entries []*proto.LogEntry + var bytes int + + send := func() { + if len(entries) == 0 { + return + } + + log.Debug(). + Int("entries", len(entries)). + Int("bytes", bytes). + Msg("log drain: sending queued logs") + + if err := c.sendLogs(ctx, entries); err != nil { + log.Error().Err(err).Msg("log drain: could not send logs to server") + } + + // even if send failed, we don't have infinite memory; retry has already been used + entries = entries[:0] + bytes = 0 + } + + // ctx.Done() is covered by the log channel being closed for { - _, err = c.client.Log(ctx, req) + select { + case entry, ok := <-c.logs: + if !ok { + log.Info().Msg("log drain: channel closed") + send() + return + } + + entries = append(entries, entry) + bytes += grpcproto.Size(entry) // cspell:words grpcproto + + if bytes >= maxLogBatchSize { + send() + } + + case <-time.After(maxLogFlushPeriod): + send() + } + } +} + +func (c *client) sendLogs(ctx context.Context, entries []*proto.LogEntry) error { + req := &proto.LogRequest{LogEntries: entries} + retry := c.newBackOff() + + for { + _, err := c.client.Log(ctx, req) if err == nil { break } - log.Error().Err(err).Msgf("grpc error: log(): code: %v", status.Code(err)) - switch status.Code(err) { + case codes.Canceled: + if ctx.Err() != nil { + // expected as context was canceled + log.Debug().Err(err).Msgf("grpc error: log(): context canceled") + return nil + } + log.Error().Err(err).Msgf("grpc error: log(): code: %v", status.Code(err)) + return err case codes.Aborted, codes.DataLoss, @@ -343,7 +464,9 @@ func (c *client) Log(ctx context.Context, logEntry *rpc.LogEntry) (err error) { codes.Internal, codes.Unavailable: // non-fatal errors + log.Warn().Err(err).Msgf("grpc error: log(): code: %v", status.Code(err)) default: + log.Error().Err(err).Msgf("grpc error: log(): code: %v", status.Code(err)) return err } @@ -356,12 +479,15 @@ func (c *client) Log(ctx context.Context, logEntry *rpc.LogEntry) (err error) { return nil } -func (c *client) RegisterAgent(ctx context.Context, platform, backend, version string, capacity int) (int64, error) { +func (c *client) RegisterAgent(ctx context.Context, info rpc.AgentInfo) (int64, error) { req := new(proto.RegisterAgentRequest) - req.Platform = platform - req.Backend = backend - req.Version = version - req.Capacity = int32(capacity) + req.Info = &proto.AgentInfo{ + Platform: info.Platform, + Backend: info.Backend, + Version: info.Version, + Capacity: int32(info.Capacity), + CustomLabels: info.CustomLabels, + } res, err := c.client.RegisterAgent(ctx, req) return res.GetAgentId(), err @@ -383,6 +509,14 @@ func (c *client) ReportHealth(ctx context.Context) (err error) { return nil } switch status.Code(err) { + case codes.Canceled: + if ctx.Err() != nil { + // expected as context was canceled + log.Debug().Err(err).Msgf("grpc error: report_health(): context canceled") + return nil + } + log.Error().Err(err).Msgf("grpc error: report_health(): code: %v", status.Code(err)) + return err case codes.Aborted, codes.DataLoss, @@ -390,7 +524,9 @@ func (c *client) ReportHealth(ctx context.Context) (err error) { codes.Internal, codes.Unavailable: // non-fatal errors + log.Warn().Err(err).Msgf("grpc error: report_health(): code: %v", status.Code(err)) default: + log.Error().Err(err).Msgf("grpc error: report_health(): code: %v", status.Code(err)) return err } diff --git a/agent/runner.go b/agent/runner.go index 6608bd025..f5640982e 100644 --- a/agent/runner.go +++ b/agent/runner.go @@ -25,10 +25,11 @@ import ( "github.com/rs/zerolog/log" "google.golang.org/grpc/metadata" - "go.woodpecker-ci.org/woodpecker/v2/pipeline" - backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc" - "go.woodpecker-ci.org/woodpecker/v2/shared/utils" + "go.woodpecker-ci.org/woodpecker/v3/pipeline" + backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc" + "go.woodpecker-ci.org/woodpecker/v3/shared/constant" + "go.woodpecker-ci.org/woodpecker/v3/shared/utils" ) type Runner struct { @@ -49,7 +50,7 @@ func NewRunner(workEngine rpc.Peer, f rpc.Filter, h string, state *State, backen } } -func (r *Runner) Run(runnerCtx context.Context) error { //nolint:contextcheck +func (r *Runner) Run(runnerCtx, shutdownCtx context.Context) error { //nolint:contextcheck log.Debug().Msg("request next execution") meta, _ := metadata.FromOutgoingContext(runnerCtx) @@ -118,7 +119,7 @@ func (r *Runner) Run(runnerCtx context.Context) error { //nolint:contextcheck logger.Debug().Msg("pipeline done") return - case <-time.After(time.Minute): + case <-time.After(constant.TaskTimeout / 3): logger.Debug().Msg("pipeline lease renewed") if err := r.client.Extend(workflowCtx, workflow.ID); err != nil { log.Error().Err(err).Msg("extending pipeline deadline failed") @@ -176,7 +177,11 @@ func (r *Runner) Run(runnerCtx context.Context) error { //nolint:contextcheck Str("error", state.Error). Msg("updating workflow status") - if err := r.client.Done(runnerCtx, workflow.ID, state); err != nil { + doneCtx := runnerCtx + if doneCtx.Err() != nil { + doneCtx = shutdownCtx + } + if err := r.client.Done(doneCtx, workflow.ID, state); err != nil { logger.Error().Err(err).Msg("updating workflow status failed") } else { logger.Debug().Msg("updating workflow status complete") diff --git a/agent/tracer.go b/agent/tracer.go index 7bc97c339..54625ca6d 100644 --- a/agent/tracer.go +++ b/agent/tracer.go @@ -23,8 +23,8 @@ import ( "github.com/rs/zerolog" - "go.woodpecker-ci.org/woodpecker/v2/pipeline" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc" + "go.woodpecker-ci.org/woodpecker/v3/pipeline" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc" ) func (r *Runner) createTracer(ctxMeta context.Context, uploads *sync.WaitGroup, logger zerolog.Logger, workflow *rpc.Workflow) pipeline.TraceFunc { @@ -74,21 +74,12 @@ func (r *Runner) createTracer(ctxMeta context.Context, uploads *sync.WaitGroup, // TODO: find better way to update this state and move it to pipeline to have the same env in cli-exec state.Pipeline.Step.Environment["CI_MACHINE"] = r.hostname - state.Pipeline.Step.Environment["CI_PIPELINE_STATUS"] = "success" state.Pipeline.Step.Environment["CI_PIPELINE_STARTED"] = strconv.FormatInt(state.Pipeline.Started, 10) - state.Pipeline.Step.Environment["CI_PIPELINE_FINISHED"] = strconv.FormatInt(time.Now().Unix(), 10) - state.Pipeline.Step.Environment["CI_STEP_STATUS"] = "success" state.Pipeline.Step.Environment["CI_STEP_STARTED"] = strconv.FormatInt(state.Pipeline.Started, 10) - state.Pipeline.Step.Environment["CI_STEP_FINISHED"] = strconv.FormatInt(time.Now().Unix(), 10) state.Pipeline.Step.Environment["CI_SYSTEM_PLATFORM"] = runtime.GOOS + "/" + runtime.GOARCH - if state.Pipeline.Error != nil { - state.Pipeline.Step.Environment["CI_PIPELINE_STATUS"] = "failure" - state.Pipeline.Step.Environment["CI_STEP_STATUS"] = "failure" - } - return nil } } diff --git a/cli/admin/admin.go b/cli/admin/admin.go index 39187c2c9..110c210d4 100644 --- a/cli/admin/admin.go +++ b/cli/admin/admin.go @@ -15,16 +15,22 @@ package admin import ( - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/admin/registry" + "go.woodpecker-ci.org/woodpecker/v3/cli/admin/loglevel" + "go.woodpecker-ci.org/woodpecker/v3/cli/admin/registry" + "go.woodpecker-ci.org/woodpecker/v3/cli/admin/secret" + "go.woodpecker-ci.org/woodpecker/v3/cli/admin/user" ) // Command exports the admin command set. var Command = &cli.Command{ Name: "admin", - Usage: "administer server settings", - Subcommands: []*cli.Command{ + Usage: "manage server settings", + Commands: []*cli.Command{ + loglevel.Command, registry.Command, + secret.Command, + user.Command, }, } diff --git a/cli/loglevel/loglevel.go b/cli/admin/loglevel/loglevel.go similarity index 77% rename from cli/loglevel/loglevel.go rename to cli/admin/loglevel/loglevel.go index 1908260e0..aac46af36 100644 --- a/cli/loglevel/loglevel.go +++ b/cli/admin/loglevel/loglevel.go @@ -15,24 +15,26 @@ package loglevel import ( + "context" + "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) // Command exports the log-level command used to change the servers log-level. var Command = &cli.Command{ Name: "log-level", ArgsUsage: "[level]", - Usage: "get the logging level of the server, or set it with [level]", + Usage: "retrieve log level from server, or set it with [level]", Action: logLevel, } -func logLevel(c *cli.Context) error { - client, err := internal.NewClient(c) +func logLevel(ctx context.Context, c *cli.Command) error { + client, err := internal.NewClient(ctx, c) if err != nil { return err } @@ -57,6 +59,6 @@ func logLevel(c *cli.Context) error { } } - log.Info().Msgf("logging level: %s", ll.Level) + log.Info().Msgf("log level: %s", ll.Level) return nil } diff --git a/cli/admin/registry/registry.go b/cli/admin/registry/registry.go index 4f091d0e3..7055c2873 100644 --- a/cli/admin/registry/registry.go +++ b/cli/admin/registry/registry.go @@ -15,18 +15,18 @@ package registry import ( - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // Command exports the registry command set. var Command = &cli.Command{ Name: "registry", Usage: "manage global registries", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ registryCreateCmd, registryDeleteCmd, - registryUpdateCmd, - registryInfoCmd, registryListCmd, + registryShowCmd, + registryUpdateCmd, }, } diff --git a/cli/admin/registry/registry_add.go b/cli/admin/registry/registry_add.go index 9f779ad8f..756bcc9dd 100644 --- a/cli/admin/registry/registry_add.go +++ b/cli/admin/registry/registry_add.go @@ -15,18 +15,19 @@ package registry import ( + "context" "os" "strings" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) var registryCreateCmd = &cli.Command{ Name: "add", - Usage: "adds a registry", + Usage: "add a registry", Action: registryCreate, Flags: []cli.Flag{ &cli.StringFlag{ @@ -45,14 +46,14 @@ var registryCreateCmd = &cli.Command{ }, } -func registryCreate(c *cli.Context) error { +func registryCreate(ctx context.Context, c *cli.Command) error { var ( hostname = c.String("hostname") username = c.String("username") password = c.String("password") ) - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } diff --git a/cli/admin/registry/registry_list.go b/cli/admin/registry/registry_list.go index f40f15ed8..cef4af5b4 100644 --- a/cli/admin/registry/registry_list.go +++ b/cli/admin/registry/registry_list.go @@ -15,13 +15,15 @@ package registry import ( + "context" "html/template" "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) var registryListCmd = &cli.Command{ @@ -33,15 +35,17 @@ var registryListCmd = &cli.Command{ }, } -func registryList(c *cli.Context) error { +func registryList(ctx context.Context, c *cli.Command) error { format := c.String("format") + "\n" - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } - list, err := client.GlobalRegistryList() + opt := woodpecker.RegistryListOptions{} + + list, err := client.GlobalRegistryList(opt) if err != nil { return err } diff --git a/cli/admin/registry/registry_rm.go b/cli/admin/registry/registry_rm.go index 752696aff..296135694 100644 --- a/cli/admin/registry/registry_rm.go +++ b/cli/admin/registry/registry_rm.go @@ -15,9 +15,11 @@ package registry import ( - "github.com/urfave/cli/v2" + "context" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "github.com/urfave/cli/v3" + + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" ) var registryDeleteCmd = &cli.Command{ @@ -33,10 +35,10 @@ var registryDeleteCmd = &cli.Command{ }, } -func registryDelete(c *cli.Context) error { +func registryDelete(ctx context.Context, c *cli.Command) error { hostname := c.String("hostname") - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } diff --git a/cli/admin/registry/registry_set.go b/cli/admin/registry/registry_set.go index 26daa140a..dca865e93 100644 --- a/cli/admin/registry/registry_set.go +++ b/cli/admin/registry/registry_set.go @@ -15,14 +15,15 @@ package registry import ( + "context" "os" "strings" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) var registryUpdateCmd = &cli.Command{ @@ -47,14 +48,14 @@ var registryUpdateCmd = &cli.Command{ }, } -func registryUpdate(c *cli.Context) error { +func registryUpdate(ctx context.Context, c *cli.Command) error { var ( hostname = c.String("hostname") username = c.String("username") password = c.String("password") ) - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } diff --git a/cli/admin/registry/registry_info.go b/cli/admin/registry/registry_show.go similarity index 76% rename from cli/admin/registry/registry_info.go rename to cli/admin/registry/registry_show.go index 770a7074e..bf6561ed7 100644 --- a/cli/admin/registry/registry_info.go +++ b/cli/admin/registry/registry_show.go @@ -15,19 +15,20 @@ package registry import ( + "context" "html/template" "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" ) -var registryInfoCmd = &cli.Command{ - Name: "info", - Usage: "display registry info", - Action: registryInfo, +var registryShowCmd = &cli.Command{ + Name: "show", + Usage: "show registry information", + Action: registryShow, Flags: []cli.Flag{ &cli.StringFlag{ Name: "hostname", @@ -38,13 +39,13 @@ var registryInfoCmd = &cli.Command{ }, } -func registryInfo(c *cli.Context) error { +func registryShow(ctx context.Context, c *cli.Command) error { var ( hostname = c.String("hostname") format = c.String("format") + "\n" ) - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } diff --git a/server/store/datastore/migration/005_repos_drop_repo_counter.go b/cli/admin/secret/secret.go similarity index 59% rename from server/store/datastore/migration/005_repos_drop_repo_counter.go rename to cli/admin/secret/secret.go index dc1785e0c..da805a98e 100644 --- a/server/store/datastore/migration/005_repos_drop_repo_counter.go +++ b/cli/admin/secret/secret.go @@ -1,10 +1,10 @@ -// Copyright 2021 Woodpecker Authors +// Copyright 2023 Woodpecker Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -12,16 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -package migration +package secret import ( - "src.techknowlogick.com/xormigrate" - "xorm.io/xorm" + "github.com/urfave/cli/v3" ) -var alterTableReposDropCounter = xormigrate.Migration{ - ID: "alter-table-drop-counter", - MigrateSession: func(sess *xorm.Session) error { - return dropTableColumns(sess, "repos", "repo_counter") +// Command exports the secret command. +var Command = &cli.Command{ + Name: "secret", + Usage: "manage global secrets", + Commands: []*cli.Command{ + secretCreateCmd, + secretDeleteCmd, + secretListCmd, + secretShowCmd, + secretUpdateCmd, }, } diff --git a/cli/admin/secret/secret_add.go b/cli/admin/secret/secret_add.go new file mode 100644 index 000000000..ad65c00db --- /dev/null +++ b/cli/admin/secret/secret_add.go @@ -0,0 +1,82 @@ +// Copyright 2023 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package secret + +import ( + "context" + "os" + "strings" + + "github.com/urfave/cli/v3" + + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" +) + +var secretCreateCmd = &cli.Command{ + Name: "add", + Usage: "add a secret", + ArgsUsage: "[repo-id|repo-full-name]", + Action: secretCreate, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "value", + Usage: "secret value", + }, + &cli.StringSliceFlag{ + Name: "event", + Usage: "secret limited to these events", + }, + &cli.StringSliceFlag{ + Name: "image", + Usage: "secret limited to these images", + }, + }, +} + +func secretCreate(ctx context.Context, c *cli.Command) error { + client, err := internal.NewClient(ctx, c) + if err != nil { + return err + } + + secret := &woodpecker.Secret{ + Name: strings.ToLower(c.String("name")), + Value: c.String("value"), + Images: c.StringSlice("image"), + Events: c.StringSlice("event"), + } + if len(secret.Events) == 0 { + secret.Events = defaultSecretEvents + } + if strings.HasPrefix(secret.Value, "@") { + path := strings.TrimPrefix(secret.Value, "@") + out, err := os.ReadFile(path) + if err != nil { + return err + } + secret.Value = string(out) + } + + _, err = client.GlobalSecretCreate(secret) + return err +} + +var defaultSecretEvents = []string{ + woodpecker.EventPush, + woodpecker.EventTag, + woodpecker.EventRelease, + woodpecker.EventDeploy, +} diff --git a/cli/secret/secret_list.go b/cli/admin/secret/secret_list.go similarity index 66% rename from cli/secret/secret_list.go rename to cli/admin/secret/secret_list.go index e698cb505..ee00567fc 100644 --- a/cli/secret/secret_list.go +++ b/cli/admin/secret/secret_list.go @@ -15,15 +15,16 @@ package secret import ( + "context" "html/template" "os" "strings" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) var secretListCmd = &cli.Command{ @@ -32,48 +33,25 @@ var secretListCmd = &cli.Command{ ArgsUsage: "[repo-id|repo-full-name]", Action: secretList, Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "global", - Usage: "global secret", - }, - common.OrgFlag, - common.RepoFlag, common.FormatFlag(tmplSecretList, true), }, } -func secretList(c *cli.Context) error { +func secretList(ctx context.Context, c *cli.Command) error { format := c.String("format") + "\n" - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } - global, orgID, repoID, err := parseTargetArgs(client, c) + opt := woodpecker.SecretListOptions{} + + list, err := client.GlobalSecretList(opt) if err != nil { return err } - var list []*woodpecker.Secret - switch { - case global: - list, err = client.GlobalSecretList() - if err != nil { - return err - } - case orgID != -1: - list, err = client.OrgSecretList(orgID) - if err != nil { - return err - } - default: - list, err = client.SecretList(repoID) - if err != nil { - return err - } - } - tmpl, err := template.New("_").Funcs(secretFuncMap).Parse(format) if err != nil { return err diff --git a/cli/admin/secret/secret_rm.go b/cli/admin/secret/secret_rm.go new file mode 100644 index 000000000..596672d3a --- /dev/null +++ b/cli/admin/secret/secret_rm.go @@ -0,0 +1,47 @@ +// Copyright 2023 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package secret + +import ( + "context" + + "github.com/urfave/cli/v3" + + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" +) + +var secretDeleteCmd = &cli.Command{ + Name: "rm", + Usage: "remove a secret", + ArgsUsage: "[repo-id|repo-full-name]", + Action: secretDelete, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "name", + Usage: "secret name", + }, + }, +} + +func secretDelete(ctx context.Context, c *cli.Command) error { + secretName := c.String("name") + + client, err := internal.NewClient(ctx, c) + if err != nil { + return err + } + + return client.GlobalSecretDelete(secretName) +} diff --git a/cli/admin/secret/secret_set.go b/cli/admin/secret/secret_set.go new file mode 100644 index 000000000..7e86d66e2 --- /dev/null +++ b/cli/admin/secret/secret_set.go @@ -0,0 +1,76 @@ +// Copyright 2023 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package secret + +import ( + "context" + "os" + "strings" + + "github.com/urfave/cli/v3" + + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" +) + +var secretUpdateCmd = &cli.Command{ + Name: "update", + Usage: "update a secret", + ArgsUsage: "[repo-id|repo-full-name]", + Action: secretUpdate, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "name", + Usage: "secret name", + }, + &cli.StringFlag{ + Name: "value", + Usage: "secret value", + }, + &cli.StringSliceFlag{ + Name: "event", + Usage: "secret limited to these events", + }, + &cli.StringSliceFlag{ + Name: "image", + Usage: "secret limited to these images", + }, + }, +} + +func secretUpdate(ctx context.Context, c *cli.Command) error { + client, err := internal.NewClient(ctx, c) + if err != nil { + return err + } + + secret := &woodpecker.Secret{ + Name: strings.ToLower(c.String("name")), + Value: c.String("value"), + Images: c.StringSlice("image"), + Events: c.StringSlice("event"), + } + if strings.HasPrefix(secret.Value, "@") { + path := strings.TrimPrefix(secret.Value, "@") + out, err := os.ReadFile(path) + if err != nil { + return err + } + secret.Value = string(out) + } + + _, err = client.GlobalSecretUpdate(secret) + return err +} diff --git a/cli/admin/secret/secret_show.go b/cli/admin/secret/secret_show.go new file mode 100644 index 000000000..b5b9855b9 --- /dev/null +++ b/cli/admin/secret/secret_show.go @@ -0,0 +1,68 @@ +// Copyright 2023 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package secret + +import ( + "context" + "fmt" + "html/template" + "os" + + "github.com/urfave/cli/v3" + + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" +) + +var secretShowCmd = &cli.Command{ + Name: "show", + Usage: "show secret information", + ArgsUsage: "[repo-id|repo-full-name]", + Action: secretShow, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "name", + Usage: "secret name", + }, + common.FormatFlag(tmplSecretList, true), + }, +} + +func secretShow(ctx context.Context, c *cli.Command) error { + var ( + secretName = c.String("name") + format = c.String("format") + "\n" + ) + + if secretName == "" { + return fmt.Errorf("secret name is missing") + } + + client, err := internal.NewClient(ctx, c) + if err != nil { + return err + } + + secret, err := client.GlobalSecret(secretName) + if err != nil { + return err + } + + tmpl, err := template.New("_").Funcs(secretFuncMap).Parse(format) + if err != nil { + return err + } + return tmpl.Execute(os.Stdout, secret) +} diff --git a/cli/user/user.go b/cli/admin/user/user.go similarity index 91% rename from cli/user/user.go rename to cli/admin/user/user.go index a2323b672..29951a635 100644 --- a/cli/user/user.go +++ b/cli/admin/user/user.go @@ -15,17 +15,17 @@ package user import ( - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // Command exports the user command set. var Command = &cli.Command{ Name: "user", Usage: "manage users", - Subcommands: []*cli.Command{ - userListCmd, - userInfoCmd, + Commands: []*cli.Command{ userAddCmd, + userListCmd, userRemoveCmd, + userShowCmd, }, } diff --git a/cli/user/user_add.go b/cli/admin/user/user_add.go similarity index 77% rename from cli/user/user_add.go rename to cli/admin/user/user_add.go index f3828b699..aecf11e9d 100644 --- a/cli/user/user_add.go +++ b/cli/admin/user/user_add.go @@ -15,25 +15,26 @@ package user import ( + "context" "fmt" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) var userAddCmd = &cli.Command{ Name: "add", - Usage: "adds a user", + Usage: "add a user", ArgsUsage: "", Action: userAdd, } -func userAdd(c *cli.Context) error { +func userAdd(ctx context.Context, c *cli.Command) error { login := c.Args().First() - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } diff --git a/cli/user/user_list.go b/cli/admin/user/user_list.go similarity index 76% rename from cli/user/user_list.go rename to cli/admin/user/user_list.go index 63863b95f..baa436861 100644 --- a/cli/user/user_list.go +++ b/cli/admin/user/user_list.go @@ -15,13 +15,15 @@ package user import ( + "context" "os" "text/template" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) var userListCmd = &cli.Command{ @@ -32,13 +34,15 @@ var userListCmd = &cli.Command{ Flags: []cli.Flag{common.FormatFlag(tmplUserList)}, } -func userList(c *cli.Context) error { - client, err := internal.NewClient(c) +func userList(ctx context.Context, c *cli.Command) error { + client, err := internal.NewClient(ctx, c) if err != nil { return err } - users, err := client.UserList() + opt := woodpecker.UserListOptions{} + + users, err := client.UserList(opt) if err != nil || len(users) == 0 { return err } diff --git a/cli/user/user_rm.go b/cli/admin/user/user_rm.go similarity index 83% rename from cli/user/user_rm.go rename to cli/admin/user/user_rm.go index 081b0cf23..f6f4a7cdc 100644 --- a/cli/user/user_rm.go +++ b/cli/admin/user/user_rm.go @@ -15,11 +15,12 @@ package user import ( + "context" "fmt" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" ) var userRemoveCmd = &cli.Command{ @@ -29,10 +30,10 @@ var userRemoveCmd = &cli.Command{ Action: userRemove, } -func userRemove(c *cli.Context) error { +func userRemove(ctx context.Context, c *cli.Command) error { login := c.Args().First() - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } diff --git a/cli/user/user_info.go b/cli/admin/user/user_show.go similarity index 77% rename from cli/user/user_info.go rename to cli/admin/user/user_show.go index 7747f5d18..9f45d5a13 100644 --- a/cli/user/user_info.go +++ b/cli/admin/user/user_show.go @@ -15,26 +15,27 @@ package user import ( + "context" "fmt" "os" "text/template" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" ) -var userInfoCmd = &cli.Command{ - Name: "info", - Usage: "show user details", +var userShowCmd = &cli.Command{ + Name: "show", + Usage: "show user information", ArgsUsage: "", - Action: userInfo, + Action: userShow, Flags: []cli.Flag{common.FormatFlag(tmplUserInfo)}, } -func userInfo(c *cli.Context) error { - client, err := internal.NewClient(c) +func userShow(ctx context.Context, c *cli.Command) error { + client, err := internal.NewClient(ctx, c) if err != nil { return err } diff --git a/cli/common/flags.go b/cli/common/flags.go index 6a7431249..5cada2671 100644 --- a/cli/common/flags.go +++ b/cli/common/flags.go @@ -15,52 +15,49 @@ package common import ( - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/shared/logger" + "go.woodpecker-ci.org/woodpecker/v3/shared/logger" ) var GlobalFlags = append([]cli.Flag{ &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_CONFIG"}, + Sources: cli.EnvVars("WOODPECKER_CONFIG"), Name: "config", Aliases: []string{"c"}, Usage: "path to config file", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_SERVER"}, + Sources: cli.EnvVars("WOODPECKER_SERVER"), Name: "server", Aliases: []string{"s"}, Usage: "server address", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_TOKEN"}, + Sources: cli.EnvVars("WOODPECKER_TOKEN"), Name: "token", Aliases: []string{"t"}, Usage: "server auth token", }, &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_DISABLE_UPDATE_CHECK"}, + Sources: cli.EnvVars("WOODPECKER_DISABLE_UPDATE_CHECK"), Name: "disable-update-check", Usage: "disable update check", }, &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_SKIP_VERIFY"}, + Sources: cli.EnvVars("WOODPECKER_SKIP_VERIFY"), Name: "skip-verify", Usage: "skip ssl verification", - Hidden: true, }, &cli.StringFlag{ - EnvVars: []string{"SOCKS_PROXY"}, + Sources: cli.EnvVars("SOCKS_PROXY"), Name: "socks-proxy", Usage: "socks proxy address", - Hidden: true, }, &cli.BoolFlag{ - EnvVars: []string{"SOCKS_PROXY_OFF"}, + Sources: cli.EnvVars("SOCKS_PROXY_OFF"), Name: "socks-proxy-off", Usage: "socks proxy ignored", - Hidden: true, }, }, logger.GlobalLoggerFlags...) diff --git a/cli/common/hooks.go b/cli/common/hooks.go index 952742cba..fb7a4b4c7 100644 --- a/cli/common/hooks.go +++ b/cli/common/hooks.go @@ -6,10 +6,10 @@ import ( "time" "github.com/rs/zerolog/log" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal/config" - "go.woodpecker-ci.org/woodpecker/v2/cli/update" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal/config" + "go.woodpecker-ci.org/woodpecker/v3/cli/update" ) var ( @@ -17,12 +17,12 @@ var ( cancelWaitForUpdate context.CancelCauseFunc ) -func Before(c *cli.Context) error { - if err := setupGlobalLogger(c); err != nil { - return err +func Before(ctx context.Context, c *cli.Command) (context.Context, error) { + if err := setupGlobalLogger(ctx, c); err != nil { + return ctx, err } - go func() { + go func(context.Context) { if c.Bool("disable-update-check") { return } @@ -35,31 +35,31 @@ func Before(c *cli.Context) error { waitForUpdateCheck, cancelWaitForUpdate = context.WithCancelCause(context.Background()) defer cancelWaitForUpdate(errors.New("update check finished")) - log.Debug().Msg("Checking for updates ...") + log.Debug().Msg("checking for updates ...") - newVersion, err := update.CheckForUpdate(waitForUpdateCheck, false) + newVersion, err := update.CheckForUpdate(waitForUpdateCheck, false) //nolint:contextcheck if err != nil { - log.Error().Err(err).Msgf("Failed to check for updates") + log.Error().Err(err).Msgf("failed to check for updates") return } if newVersion != nil { - log.Warn().Msgf("A new version of woodpecker-cli is available: %s. Update by running: %s update", newVersion.Version, c.App.Name) + log.Warn().Msgf("new version of woodpecker-cli is available: %s, update with: %s update", newVersion.Version, c.Root().Name) } else { - log.Debug().Msgf("No update required") + log.Debug().Msgf("no update required") } - }() + }(ctx) - return config.Load(c) + return ctx, config.Load(ctx, c) } -func After(_ *cli.Context) error { +func After(_ context.Context, _ *cli.Command) error { if waitForUpdateCheck != nil { select { case <-waitForUpdateCheck.Done(): // When the actual command already finished, we still wait 500ms for the update check to finish case <-time.After(time.Millisecond * 500): - log.Debug().Msg("Update check stopped due to timeout") + log.Debug().Msg("update check stopped due to timeout") cancelWaitForUpdate(errors.New("update check timeout")) } } diff --git a/cli/common/pipeline.go b/cli/common/pipeline.go index b479e5693..ba560ba1e 100644 --- a/cli/common/pipeline.go +++ b/cli/common/pipeline.go @@ -15,13 +15,14 @@ package common import ( + "context" "fmt" "os" "strings" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/shared/constant" + "go.woodpecker-ci.org/woodpecker/v3/shared/constant" ) func DetectPipelineConfig() (isDir bool, config string, _ error) { @@ -37,16 +38,16 @@ func DetectPipelineConfig() (isDir bool, config string, _ error) { return false, "", fmt.Errorf("could not detect pipeline config") } -func RunPipelineFunc(c *cli.Context, fileFunc, dirFunc func(*cli.Context, string) error) error { +func RunPipelineFunc(ctx context.Context, c *cli.Command, fileFunc, dirFunc func(context.Context, *cli.Command, string) error) error { if c.Args().Len() == 0 { isDir, path, err := DetectPipelineConfig() if err != nil { return err } if isDir { - return dirFunc(c, path) + return dirFunc(ctx, c, path) } - return fileFunc(c, path) + return fileFunc(ctx, c, path) } multiArgs := c.Args().Len() > 1 @@ -59,11 +60,11 @@ func RunPipelineFunc(c *cli.Context, fileFunc, dirFunc func(*cli.Context, string fmt.Println("#", fi.Name()) } if fi.IsDir() { - if err := dirFunc(c, arg); err != nil { + if err := dirFunc(ctx, c, arg); err != nil { return err } } else { - if err := fileFunc(c, arg); err != nil { + if err := fileFunc(ctx, c, arg); err != nil { return err } } diff --git a/cli/common/zerologger.go b/cli/common/zerologger.go index d3e275898..e27682023 100644 --- a/cli/common/zerologger.go +++ b/cli/common/zerologger.go @@ -15,11 +15,13 @@ package common import ( - "github.com/urfave/cli/v2" + "context" - "go.woodpecker-ci.org/woodpecker/v2/shared/logger" + "github.com/urfave/cli/v3" + + "go.woodpecker-ci.org/woodpecker/v3/shared/logger" ) -func setupGlobalLogger(c *cli.Context) error { - return logger.SetupGlobalLogger(c, false) +func setupGlobalLogger(ctx context.Context, c *cli.Command) error { + return logger.SetupGlobalLogger(ctx, c, false) } diff --git a/cli/exec/dummy.go b/cli/exec/dummy.go index edb544598..757bd9519 100644 --- a/cli/exec/dummy.go +++ b/cli/exec/dummy.go @@ -17,7 +17,7 @@ package exec -import "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/dummy" +import "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/dummy" func init() { //nolint:gochecknoinits backends = append(backends, dummy.New()) diff --git a/cli/exec/exec.go b/cli/exec/exec.go index 76e4da538..43bd57388 100644 --- a/cli/exec/exec.go +++ b/cli/exec/exec.go @@ -25,23 +25,26 @@ import ( "strings" "github.com/drone/envsubst" + "github.com/oklog/ulid/v2" "github.com/rs/zerolog/log" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/lint" - "go.woodpecker-ci.org/woodpecker/v2/pipeline" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/docker" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/kubernetes" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/local" - backend_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/compiler" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/linter" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/matrix" - pipelineLog "go.woodpecker-ci.org/woodpecker/v2/pipeline/log" - "go.woodpecker-ci.org/woodpecker/v2/shared/utils" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/lint" + "go.woodpecker-ci.org/woodpecker/v3/pipeline" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/docker" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/kubernetes" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/local" + backend_types "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/compiler" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/linter" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/matrix" + pipelineLog "go.woodpecker-ci.org/woodpecker/v3/pipeline/log" + "go.woodpecker-ci.org/woodpecker/v3/shared/constant" + "go.woodpecker-ci.org/woodpecker/v3/shared/utils" ) // Command exports the exec command. @@ -59,11 +62,11 @@ var backends = []backend_types.Backend{ local.New(), } -func run(c *cli.Context) error { - return common.RunPipelineFunc(c, execFile, execDir) +func run(ctx context.Context, c *cli.Command) error { + return common.RunPipelineFunc(ctx, c, execFile, execDir) } -func execDir(c *cli.Context, dir string) error { +func execDir(ctx context.Context, c *cli.Command, dir string) error { // TODO: respect pipeline dependency repoPath := c.String("repo-path") if repoPath != "" { @@ -74,6 +77,7 @@ func execDir(c *cli.Context, dir string) error { if runtime.GOOS == "windows" { repoPath = convertPathForWindows(repoPath) } + // TODO: respect depends_on and do parallel runs with output to multiple _windows_ e.g. tmux like return filepath.Walk(dir, func(path string, info os.FileInfo, e error) error { if e != nil { return e @@ -82,7 +86,7 @@ func execDir(c *cli.Context, dir string) error { // check if it is a regular file (not dir) if info.Mode().IsRegular() && (strings.HasSuffix(info.Name(), ".yaml") || strings.HasSuffix(info.Name(), ".yml")) { fmt.Println("#", info.Name()) - _ = runExec(c, path, repoPath) // TODO: should we drop errors or store them and report back? + _ = runExec(ctx, c, path, repoPath, false) // TODO: should we drop errors or store them and report back? fmt.Println("") return nil } @@ -91,7 +95,7 @@ func execDir(c *cli.Context, dir string) error { }) } -func execFile(c *cli.Context, file string) error { +func execFile(ctx context.Context, c *cli.Command, file string) error { repoPath := c.String("repo-path") if repoPath != "" { repoPath, _ = filepath.Abs(repoPath) @@ -101,10 +105,10 @@ func execFile(c *cli.Context, file string) error { if runtime.GOOS == "windows" { repoPath = convertPathForWindows(repoPath) } - return runExec(c, file, repoPath) + return runExec(ctx, c, file, repoPath, true) } -func runExec(c *cli.Context, file, repoPath string) error { +func runExec(ctx context.Context, c *cli.Command, file, repoPath string, singleExec bool) error { dat, err := os.ReadFile(file) if err != nil { return err @@ -119,7 +123,7 @@ func runExec(c *cli.Context, file, repoPath string) error { axes = append(axes, matrix.Axis{}) } for _, axis := range axes { - err := execWithAxis(c, file, repoPath, axis) + err := execWithAxis(ctx, c, file, repoPath, axis, singleExec) if err != nil { return err } @@ -127,8 +131,20 @@ func runExec(c *cli.Context, file, repoPath string) error { return nil } -func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error { - metadata := metadataFromContext(c, axis) +func execWithAxis(ctx context.Context, c *cli.Command, file, repoPath string, axis matrix.Axis, singleExec bool) error { + metadataWorkflow := &metadata.Workflow{} + if !singleExec { + // TODO: proper try to use the engine to generate the same metadata for workflows + // https://github.com/woodpecker-ci/woodpecker/pull/3967 + metadataWorkflow.Name = strings.TrimSuffix(strings.TrimSuffix(file, ".yaml"), ".yml") + } + metadata, err := metadataFromContext(ctx, c, axis, metadataWorkflow) + if err != nil { + return fmt.Errorf("could not create metadata: %w", err) + } else if metadata == nil { + return fmt.Errorf("metadata is nil") + } + environ := metadata.Environ() var secrets []compiler.Secret for key, val := range metadata.Workflow.Matrix { @@ -166,6 +182,9 @@ func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error return err } + // emulate server behavior https://github.com/woodpecker-ci/woodpecker/blob/eebaa10d104cbc3fa7ce4c0e344b0b7978405135/server/pipeline/stepbuilder/stepBuilder.go#L289-L295 + prefix := "wp_" + ulid.Make().String() + // configure volumes for local execution volumes := c.StringSlice("volumes") if c.Bool("local") { @@ -180,18 +199,28 @@ func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error workspacePath = c.String("workspace-path") } - volumes = append(volumes, c.String("prefix")+"_default:"+workspaceBase) + volumes = append(volumes, prefix+"_default:"+workspaceBase) volumes = append(volumes, repoPath+":"+path.Join(workspaceBase, workspacePath)) } + privilegedPlugins := c.StringSlice("plugins-privileged") + // lint the yaml file - err = linter.New(linter.WithTrusted(true)).Lint([]*linter.WorkflowConfig{{ + err = linter.New( + linter.WithTrusted(linter.TrustedConfiguration{ + Security: c.Bool("repo-trusted-security"), + Network: c.Bool("repo-trusted-network"), + Volumes: c.Bool("repo-trusted-volumes"), + }), + linter.PrivilegedPlugins(privilegedPlugins), + linter.WithTrustedClonePlugins(constant.TrustedClonePlugins), + ).Lint([]*linter.WorkflowConfig{{ File: path.Base(file), RawConfig: confStr, Workflow: conf, }}) if err != nil { - str, err := lint.FormatLintError(file, err) + str, err := lint.FormatLintError(file, err, false) fmt.Print(str) if err != nil { return err @@ -201,7 +230,7 @@ func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error // compiles the yaml file compiled, err := compiler.New( compiler.WithEscalated( - c.StringSlice("privileged")..., + privilegedPlugins..., ), compiler.WithVolumes(volumes...), compiler.WithWorkspace( @@ -211,9 +240,7 @@ func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error compiler.WithNetworks( c.StringSlice("network")..., ), - compiler.WithPrefix( - c.String("prefix"), - ), + compiler.WithPrefix(prefix), compiler.WithProxy(compiler.ProxyOptions{ NoProxy: c.String("backend-no-proxy"), HTTPProxy: c.String("backend-http-proxy"), @@ -227,7 +254,7 @@ func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error c.String("netrc-password"), c.String("netrc-machine"), ), - compiler.WithMetadata(metadata), + compiler.WithMetadata(*metadata), compiler.WithSecret(secrets...), compiler.WithEnviron(pipelineEnv), ).Compile(conf) @@ -235,7 +262,7 @@ func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error return err } - backendCtx := context.WithValue(c.Context, backend_types.CliContext, c) + backendCtx := context.WithValue(ctx, backend_types.CliCommand, c) backendEngine, err := backend.FindBackend(backendCtx, backends, c.String("backend-engine")) if err != nil { return err @@ -245,21 +272,21 @@ func execWithAxis(c *cli.Context, file, repoPath string, axis matrix.Axis) error return err } - ctx, cancel := context.WithTimeout(context.Background(), c.Duration("timeout")) + pipelineCtx, cancel := context.WithTimeout(context.Background(), c.Duration("timeout")) defer cancel() - ctx = utils.WithContextSigtermCallback(ctx, func() { - fmt.Println("ctrl+c received, terminating process") + pipelineCtx = utils.WithContextSigtermCallback(pipelineCtx, func() { + fmt.Printf("ctrl+c received, terminating current pipeline '%s'\n", confStr) }) return pipeline.New(compiled, - pipeline.WithContext(ctx), + pipeline.WithContext(pipelineCtx), //nolint:contextcheck pipeline.WithTracer(pipeline.DefaultTracer), pipeline.WithLogger(defaultLogger), pipeline.WithBackend(backendEngine), pipeline.WithDescription(map[string]string{ "CLI": "exec", }), - ).Run(c.Context) + ).Run(ctx) } // convertPathForWindows converts a path to use slash separators @@ -281,8 +308,7 @@ func convertPathForWindows(path string) string { return filepath.ToSlash(path) } -const maxLogLineLength = 1024 * 1024 // 1mb var defaultLogger = pipeline.Logger(func(step *backend_types.Step, rc io.ReadCloser) error { logWriter := NewLineWriter(step.Name, step.UUID) - return pipelineLog.CopyLineByLine(logWriter, rc, maxLogLineLength) + return pipelineLog.CopyLineByLine(logWriter, rc, pipeline.MaxLogLineLength) }) diff --git a/cli/exec/flags.go b/cli/exec/flags.go index 66178188f..10518d32b 100644 --- a/cli/exec/flags.go +++ b/cli/exec/flags.go @@ -17,53 +17,49 @@ package exec import ( "time" - "github.com/urfave/cli/v2" - - "go.woodpecker-ci.org/woodpecker/v2/shared/constant" + "github.com/urfave/cli/v3" ) var flags = []cli.Flag{ &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_LOCAL"}, + Sources: cli.EnvVars("WOODPECKER_LOCAL"), Name: "local", Usage: "run from local directory", Value: true, }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_REPO_PATH"}, + Sources: cli.EnvVars("WOODPECKER_REPO_PATH"), Name: "repo-path", Usage: "path to local repository", }, + &cli.StringFlag{ + Sources: cli.EnvVars("WOODPECKER_METADATA_FILE"), + Name: "metadata-file", + Usage: "path to pipeline metadata file (normally downloaded from UI). Parameters can be adjusted by applying additional cli flags", + }, &cli.DurationFlag{ - EnvVars: []string{"WOODPECKER_TIMEOUT"}, + Sources: cli.EnvVars("WOODPECKER_TIMEOUT"), Name: "timeout", Usage: "pipeline timeout", Value: time.Hour, }, &cli.StringSliceFlag{ - EnvVars: []string{"WOODPECKER_VOLUMES"}, + Sources: cli.EnvVars("WOODPECKER_VOLUMES"), Name: "volumes", Usage: "pipeline volumes", }, &cli.StringSliceFlag{ - EnvVars: []string{"WOODPECKER_NETWORKS"}, + Sources: cli.EnvVars("WOODPECKER_NETWORKS"), Name: "network", Usage: "external networks", }, - &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_PREFIX"}, - Name: "prefix", - Value: "woodpecker", - Usage: "prefix used for containers, volumes, networks, ... created by woodpecker", - Hidden: true, - }, &cli.StringSliceFlag{ - Name: "privileged", - Usage: "privileged plugins", - Value: cli.NewStringSlice(constant.PrivilegedPlugins...), + Sources: cli.EnvVars("WOODPECKER_PLUGINS_PRIVILEGED"), + Name: "plugins-privileged", + Usage: "Allow plugins to run in privileged mode, if environment variable is defined but empty there will be none", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_BACKEND"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND"), Name: "backend-engine", Usage: "backend engine to run pipelines on", Value: "auto-detect", @@ -73,17 +69,17 @@ var flags = []cli.Flag{ // backend options for pipeline compiler // &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_BACKEND_NO_PROXY", "NO_PROXY", "no_proxy"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND_NO_PROXY", "NO_PROXY", "no_proxy"), Usage: "if set, pass the environment variable down as \"NO_PROXY\" to steps", Name: "backend-no-proxy", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_BACKEND_HTTP_PROXY", "HTTP_PROXY", "http_proxy"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND_HTTP_PROXY", "HTTP_PROXY", "http_proxy"), Usage: "if set, pass the environment variable down as \"HTTP_PROXY\" to steps", Name: "backend-http-proxy", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_BACKEND_HTTPS_PROXY", "HTTPS_PROXY", "https_proxy"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND_HTTPS_PROXY", "HTTPS_PROXY", "https_proxy"), Usage: "if set, pass the environment variable down as \"HTTPS_PROXY\" to steps", Name: "backend-https-proxy", }, @@ -97,12 +93,12 @@ var flags = []cli.Flag{ // workspace default // &cli.StringFlag{ - EnvVars: []string{"CI_WORKSPACE_BASE"}, + Sources: cli.EnvVars("CI_WORKSPACE_BASE"), Name: "workspace-base", Value: "/woodpecker", }, &cli.StringFlag{ - EnvVars: []string{"CI_WORKSPACE_PATH"}, + Sources: cli.EnvVars("CI_WORKSPACE_PATH"), Name: "workspace-path", Value: "src", }, @@ -110,218 +106,298 @@ var flags = []cli.Flag{ // netrc parameters // &cli.StringFlag{ - EnvVars: []string{"CI_NETRC_USERNAME"}, + Sources: cli.EnvVars("CI_NETRC_USERNAME"), Name: "netrc-username", }, &cli.StringFlag{ - EnvVars: []string{"CI_NETRC_PASSWORD"}, + Sources: cli.EnvVars("CI_NETRC_PASSWORD"), Name: "netrc-password", }, &cli.StringFlag{ - EnvVars: []string{"CI_NETRC_MACHINE"}, + Sources: cli.EnvVars("CI_NETRC_MACHINE"), Name: "netrc-machine", }, // // metadata parameters // &cli.StringFlag{ - EnvVars: []string{"CI_SYSTEM_PLATFORM"}, + Sources: cli.EnvVars("CI_SYSTEM_PLATFORM"), Name: "system-platform", + Usage: "Set the metadata environment variable \"CI_SYSTEM_PLATFORM\".", }, &cli.StringFlag{ - EnvVars: []string{"CI_SYSTEM_NAME"}, + Sources: cli.EnvVars("CI_SYSTEM_HOST"), + Name: "system-host", + Usage: "Set the metadata environment variable \"CI_SYSTEM_HOST\".", + }, + &cli.StringFlag{ + Sources: cli.EnvVars("CI_SYSTEM_NAME"), Name: "system-name", + Usage: "Set the metadata environment variable \"CI_SYSTEM_NAME\".", Value: "woodpecker", }, &cli.StringFlag{ - EnvVars: []string{"CI_SYSTEM_URL"}, + Sources: cli.EnvVars("CI_SYSTEM_URL"), Name: "system-url", + Usage: "Set the metadata environment variable \"CI_SYSTEM_URL\".", Value: "https://github.com/woodpecker-ci/woodpecker", }, &cli.StringFlag{ - EnvVars: []string{"CI_REPO"}, + Sources: cli.EnvVars("CI_REPO"), Name: "repo", - Usage: "full repo name", + Usage: "Set the full name to derive metadata environment variables \"CI_REPO\", \"CI_REPO_NAME\" and \"CI_REPO_OWNER\".", }, &cli.StringFlag{ - EnvVars: []string{"CI_REPO_REMOTE_ID"}, + Sources: cli.EnvVars("CI_REPO_REMOTE_ID"), Name: "repo-remote-id", + Usage: "Set the metadata environment variable \"CI_REPO_REMOTE_ID\".", }, &cli.StringFlag{ - EnvVars: []string{"CI_REPO_URL"}, + Sources: cli.EnvVars("CI_REPO_URL"), Name: "repo-url", + Usage: "Set the metadata environment variable \"CI_REPO_URL\".", }, &cli.StringFlag{ - EnvVars: []string{"CI_REPO_CLONE_URL"}, + Sources: cli.EnvVars("CI_REPO_DEFAULT_BRANCH"), + Name: "repo-default-branch", + Usage: "Set the metadata environment variable \"CI_REPO_DEFAULT_BRANCH\".", + Value: "main", + }, + &cli.StringFlag{ + Sources: cli.EnvVars("CI_REPO_CLONE_URL"), Name: "repo-clone-url", + Usage: "Set the metadata environment variable \"CI_REPO_CLONE_URL\".", }, &cli.StringFlag{ - EnvVars: []string{"CI_REPO_CLONE_SSH_URL"}, + Sources: cli.EnvVars("CI_REPO_CLONE_SSH_URL"), Name: "repo-clone-ssh-url", + Usage: "Set the metadata environment variable \"CI_REPO_CLONE_SSH_URL\".", }, &cli.StringFlag{ - EnvVars: []string{"CI_REPO_PRIVATE"}, + Sources: cli.EnvVars("CI_REPO_PRIVATE"), Name: "repo-private", + Usage: "Set the metadata environment variable \"CI_REPO_PRIVATE\".", }, &cli.BoolFlag{ - EnvVars: []string{"CI_REPO_TRUSTED"}, - Name: "repo-trusted", + Sources: cli.EnvVars("CI_REPO_TRUSTED_NETWORK"), + Name: "repo-trusted-network", + Usage: "Set the metadata environment variable \"CI_REPO_TRUSTED_NETWORK\".", + }, + &cli.BoolFlag{ + Sources: cli.EnvVars("CI_REPO_TRUSTED_VOLUMES"), + Name: "repo-trusted-volumes", + Usage: "Set the metadata environment variable \"CI_REPO_TRUSTED_VOLUMES\".", + }, + &cli.BoolFlag{ + Sources: cli.EnvVars("CI_REPO_TRUSTED_SECURITY"), + Name: "repo-trusted-security", + Usage: "Set the metadata environment variable \"CI_REPO_TRUSTED_SECURITY\".", }, &cli.IntFlag{ - EnvVars: []string{"CI_PIPELINE_NUMBER"}, + Sources: cli.EnvVars("CI_PIPELINE_NUMBER"), Name: "pipeline-number", + Usage: "Set the metadata environment variable \"CI_PIPELINE_NUMBER\".", }, &cli.IntFlag{ - EnvVars: []string{"CI_PIPELINE_PARENT"}, + Sources: cli.EnvVars("CI_PIPELINE_PARENT"), Name: "pipeline-parent", + Usage: "Set the metadata environment variable \"CI_PIPELINE_PARENT\".", }, - &cli.Int64Flag{ - EnvVars: []string{"CI_PIPELINE_CREATED"}, + &cli.IntFlag{ + Sources: cli.EnvVars("CI_PIPELINE_CREATED"), Name: "pipeline-created", + Usage: "Set the metadata environment variable \"CI_PIPELINE_CREATED\".", }, - &cli.Int64Flag{ - EnvVars: []string{"CI_PIPELINE_STARTED"}, + &cli.IntFlag{ + Sources: cli.EnvVars("CI_PIPELINE_STARTED"), Name: "pipeline-started", - }, - &cli.Int64Flag{ - EnvVars: []string{"CI_PIPELINE_FINISHED"}, - Name: "pipeline-finished", + Usage: "Set the metadata environment variable \"CI_PIPELINE_STARTED\".", }, &cli.StringFlag{ - EnvVars: []string{"CI_PIPELINE_STATUS"}, - Name: "pipeline-status", - }, - &cli.StringFlag{ - EnvVars: []string{"CI_PIPELINE_EVENT"}, + Sources: cli.EnvVars("CI_PIPELINE_EVENT"), Name: "pipeline-event", + Usage: "Set the metadata environment variable \"CI_PIPELINE_EVENT\".", Value: "manual", }, &cli.StringFlag{ - EnvVars: []string{"CI_PIPELINE_URL"}, + Sources: cli.EnvVars("CI_PIPELINE_FORGE_URL"), Name: "pipeline-url", + Usage: "Set the metadata environment variable \"CI_PIPELINE_FORGE_URL\".", }, &cli.StringFlag{ - EnvVars: []string{"CI_PIPELINE_DEPLOY_TARGET", "CI_PIPELINE_TARGET"}, // TODO: remove CI_PIPELINE_TARGET in 3.x + Sources: cli.EnvVars("CI_PIPELINE_DEPLOY_TARGET"), Name: "pipeline-deploy-to", + Usage: "Set the metadata environment variable \"CI_PIPELINE_DEPLOY_TARGET\".", }, &cli.StringFlag{ - EnvVars: []string{"CI_PIPELINE_DEPLOY_TASK", "CI_PIPELINE_TASK"}, // TODO: remove CI_PIPELINE_TASK in 3.x + Sources: cli.EnvVars("CI_PIPELINE_DEPLOY_TASK"), Name: "pipeline-deploy-task", + Usage: "Set the metadata environment variable \"CI_PIPELINE_DEPLOY_TASK\".", }, &cli.StringFlag{ - EnvVars: []string{"CI_COMMIT_SHA"}, + Sources: cli.EnvVars("CI_PIPELINE_FILES"), + Usage: "Set the metadata environment variable \"CI_PIPELINE_FILES\", either json formatted list of strings, or comma separated string list.", + Name: "pipeline-changed-files", + }, + &cli.StringFlag{ + Sources: cli.EnvVars("CI_COMMIT_SHA"), Name: "commit-sha", + Usage: "Set the metadata environment variable \"CI_COMMIT_SHA\".", }, &cli.StringFlag{ - EnvVars: []string{"CI_COMMIT_REF"}, + Sources: cli.EnvVars("CI_COMMIT_REF"), Name: "commit-ref", + Usage: "Set the metadata environment variable \"CI_COMMIT_REF\".", }, &cli.StringFlag{ - EnvVars: []string{"CI_COMMIT_REFSPEC"}, + Sources: cli.EnvVars("CI_COMMIT_REFSPEC"), Name: "commit-refspec", + Usage: "Set the metadata environment variable \"CI_COMMIT_REFSPEC\".", }, &cli.StringFlag{ - EnvVars: []string{"CI_COMMIT_BRANCH"}, + Sources: cli.EnvVars("CI_COMMIT_BRANCH"), Name: "commit-branch", + Usage: "Set the metadata environment variable \"CI_COMMIT_BRANCH\".", + Value: "main", }, &cli.StringFlag{ - EnvVars: []string{"CI_COMMIT_MESSAGE"}, + Sources: cli.EnvVars("CI_COMMIT_MESSAGE"), Name: "commit-message", + Usage: "Set the metadata environment variable \"CI_COMMIT_MESSAGE\".", }, &cli.StringFlag{ - EnvVars: []string{"CI_COMMIT_AUTHOR_NAME"}, + Sources: cli.EnvVars("CI_COMMIT_AUTHOR"), Name: "commit-author-name", + Usage: "Set the metadata environment variable \"CI_COMMIT_AUTHOR\".", }, &cli.StringFlag{ - EnvVars: []string{"CI_COMMIT_AUTHOR_AVATAR"}, + Sources: cli.EnvVars("CI_COMMIT_AUTHOR_AVATAR"), Name: "commit-author-avatar", + Usage: "Set the metadata environment variable \"CI_COMMIT_AUTHOR_AVATAR\".", }, &cli.StringFlag{ - EnvVars: []string{"CI_COMMIT_AUTHOR_EMAIL"}, + Sources: cli.EnvVars("CI_COMMIT_AUTHOR_EMAIL"), Name: "commit-author-email", - }, - &cli.IntFlag{ - EnvVars: []string{"CI_PREV_PIPELINE_NUMBER"}, - Name: "prev-pipeline-number", - }, - &cli.Int64Flag{ - EnvVars: []string{"CI_PREV_PIPELINE_CREATED"}, - Name: "prev-pipeline-created", - }, - &cli.Int64Flag{ - EnvVars: []string{"CI_PREV_PIPELINE_STARTED"}, - Name: "prev-pipeline-started", - }, - &cli.Int64Flag{ - EnvVars: []string{"CI_PREV_PIPELINE_FINISHED"}, - Name: "prev-pipeline-finished", - }, - &cli.StringFlag{ - EnvVars: []string{"CI_PREV_PIPELINE_STATUS"}, - Name: "prev-pipeline-status", - }, - &cli.StringFlag{ - EnvVars: []string{"CI_PREV_PIPELINE_EVENT"}, - Name: "prev-pipeline-event", - }, - &cli.StringFlag{ - EnvVars: []string{"CI_PREV_PIPELINE_URL"}, - Name: "prev-pipeline-url", - }, - &cli.StringFlag{ - EnvVars: []string{"CI_PREV_COMMIT_SHA"}, - Name: "prev-commit-sha", - }, - &cli.StringFlag{ - EnvVars: []string{"CI_PREV_COMMIT_REF"}, - Name: "prev-commit-ref", - }, - &cli.StringFlag{ - EnvVars: []string{"CI_PREV_COMMIT_REFSPEC"}, - Name: "prev-commit-refspec", - }, - &cli.StringFlag{ - EnvVars: []string{"CI_PREV_COMMIT_BRANCH"}, - Name: "prev-commit-branch", - }, - &cli.StringFlag{ - EnvVars: []string{"CI_PREV_COMMIT_MESSAGE"}, - Name: "prev-commit-message", - }, - &cli.StringFlag{ - EnvVars: []string{"CI_PREV_COMMIT_AUTHOR_NAME"}, - Name: "prev-commit-author-name", - }, - &cli.StringFlag{ - EnvVars: []string{"CI_PREV_COMMIT_AUTHOR_AVATAR"}, - Name: "prev-commit-author-avatar", - }, - &cli.StringFlag{ - EnvVars: []string{"CI_PREV_COMMIT_AUTHOR_EMAIL"}, - Name: "prev-commit-author-email", - }, - &cli.IntFlag{ - EnvVars: []string{"CI_WORKFLOW_NAME"}, - Name: "workflow-name", - }, - &cli.IntFlag{ - EnvVars: []string{"CI_WORKFLOW_NUMBER"}, - Name: "workflow-number", - }, - &cli.IntFlag{ - EnvVars: []string{"CI_STEP_NAME"}, - Name: "step-name", + Usage: "Set the metadata environment variable \"CI_COMMIT_AUTHOR_EMAIL\".", }, &cli.StringSliceFlag{ - EnvVars: []string{"CI_ENV"}, + Sources: cli.EnvVars("CI_COMMIT_PULL_REQUEST_LABELS"), + Name: "commit-pull-labels", + Usage: "Set the metadata environment variable \"CI_COMMIT_PULL_REQUEST_LABELS\".", + }, + &cli.BoolFlag{ + Sources: cli.EnvVars("CI_COMMIT_PRERELEASE"), + Name: "commit-release-is-pre", + Usage: "Set the metadata environment variable \"CI_COMMIT_PRERELEASE\".", + }, + &cli.IntFlag{ + Sources: cli.EnvVars("CI_PREV_PIPELINE_NUMBER"), + Name: "prev-pipeline-number", + Usage: "Set the metadata environment variable \"CI_PREV_PIPELINE_NUMBER\".", + }, + &cli.IntFlag{ + Sources: cli.EnvVars("CI_PREV_PIPELINE_CREATED"), + Name: "prev-pipeline-created", + Usage: "Set the metadata environment variable \"CI_PREV_PIPELINE_CREATED\".", + }, + &cli.IntFlag{ + Sources: cli.EnvVars("CI_PREV_PIPELINE_STARTED"), + Name: "prev-pipeline-started", + Usage: "Set the metadata environment variable \"CI_PREV_PIPELINE_STARTED\".", + }, + &cli.IntFlag{ + Sources: cli.EnvVars("CI_PREV_PIPELINE_FINISHED"), + Name: "prev-pipeline-finished", + Usage: "Set the metadata environment variable \"CI_PREV_PIPELINE_FINISHED\".", + }, + &cli.StringFlag{ + Sources: cli.EnvVars("CI_PREV_PIPELINE_STATUS"), + Name: "prev-pipeline-status", + Usage: "Set the metadata environment variable \"CI_PREV_PIPELINE_STATUS\".", + }, + &cli.StringFlag{ + Sources: cli.EnvVars("CI_PREV_PIPELINE_EVENT"), + Name: "prev-pipeline-event", + Usage: "Set the metadata environment variable \"CI_PREV_PIPELINE_EVENT\".", + }, + &cli.StringFlag{ + Sources: cli.EnvVars("CI_PREV_PIPELINE_FORGE_URL"), + Name: "prev-pipeline-url", + Usage: "Set the metadata environment variable \"CI_PREV_PIPELINE_FORGE_URL\".", + }, + &cli.StringFlag{ + Sources: cli.EnvVars("CI_PREV_PIPELINE_DEPLOY_TARGET"), + Name: "prev-pipeline-deploy-to", + Usage: "Set the metadata environment variable \"CI_PREV_PIPELINE_DEPLOY_TARGET\".", + }, + &cli.StringFlag{ + Sources: cli.EnvVars("CI_PREV_PIPELINE_DEPLOY_TASK"), + Name: "prev-pipeline-deploy-task", + Usage: "Set the metadata environment variable \"CI_PREV_PIPELINE_DEPLOY_TASK\".", + }, + &cli.StringFlag{ + Sources: cli.EnvVars("CI_PREV_COMMIT_SHA"), + Name: "prev-commit-sha", + Usage: "Set the metadata environment variable \"CI_PREV_COMMIT_SHA\".", + }, + &cli.StringFlag{ + Sources: cli.EnvVars("CI_PREV_COMMIT_REF"), + Name: "prev-commit-ref", + Usage: "Set the metadata environment variable \"CI_PREV_COMMIT_REF\".", + }, + &cli.StringFlag{ + Sources: cli.EnvVars("CI_PREV_COMMIT_REFSPEC"), + Name: "prev-commit-refspec", + Usage: "Set the metadata environment variable \"CI_PREV_COMMIT_REFSPEC\".", + }, + &cli.StringFlag{ + Sources: cli.EnvVars("CI_PREV_COMMIT_BRANCH"), + Name: "prev-commit-branch", + Usage: "Set the metadata environment variable \"CI_PREV_COMMIT_BRANCH\".", + }, + &cli.StringFlag{ + Sources: cli.EnvVars("CI_PREV_COMMIT_MESSAGE"), + Name: "prev-commit-message", + Usage: "Set the metadata environment variable \"CI_PREV_COMMIT_MESSAGE\".", + }, + &cli.StringFlag{ + Sources: cli.EnvVars("CI_PREV_COMMIT_AUTHOR"), + Name: "prev-commit-author-name", + Usage: "Set the metadata environment variable \"CI_PREV_COMMIT_AUTHOR\".", + }, + &cli.StringFlag{ + Sources: cli.EnvVars("CI_PREV_COMMIT_AUTHOR_AVATAR"), + Name: "prev-commit-author-avatar", + Usage: "Set the metadata environment variable \"CI_PREV_COMMIT_AUTHOR_AVATAR\".", + }, + &cli.StringFlag{ + Sources: cli.EnvVars("CI_PREV_COMMIT_AUTHOR_EMAIL"), + Name: "prev-commit-author-email", + Usage: "Set the metadata environment variable \"CI_PREV_COMMIT_AUTHOR_EMAIL\".", + }, + &cli.StringFlag{ + Sources: cli.EnvVars("CI_WORKFLOW_NAME"), + Name: "workflow-name", + Usage: "Set the metadata environment variable \"CI_WORKFLOW_NAME\".", + }, + &cli.IntFlag{ + Sources: cli.EnvVars("CI_WORKFLOW_NUMBER"), + Name: "workflow-number", + Usage: "Set the metadata environment variable \"CI_WORKFLOW_NUMBER\".", + }, + &cli.StringSliceFlag{ + Sources: cli.EnvVars("CI_ENV"), Name: "env", + Usage: "Set the metadata environment variable \"CI_ENV\".", }, &cli.StringFlag{ - EnvVars: []string{"CI_FORGE_TYPE"}, + Sources: cli.EnvVars("CI_FORGE_TYPE"), Name: "forge-type", + Usage: "Set the metadata environment variable \"CI_FORGE_TYPE\".", }, &cli.StringFlag{ - EnvVars: []string{"CI_FORGE_URL"}, + Sources: cli.EnvVars("CI_FORGE_URL"), Name: "forge-url", + Usage: "Set the metadata environment variable \"CI_FORGE_URL\".", }, } diff --git a/cli/exec/metadata.go b/cli/exec/metadata.go index add382292..8664ec2c4 100644 --- a/cli/exec/metadata.go +++ b/cli/exec/metadata.go @@ -15,105 +15,147 @@ package exec import ( + "context" + "encoding/json" + "fmt" + "os" "runtime" "strings" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/matrix" - "go.woodpecker-ci.org/woodpecker/v2/version" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/matrix" + "go.woodpecker-ci.org/woodpecker/v3/version" ) // return the metadata from the cli context. -func metadataFromContext(c *cli.Context, axis matrix.Axis) metadata.Metadata { +func metadataFromContext(_ context.Context, c *cli.Command, axis matrix.Axis, w *metadata.Workflow) (*metadata.Metadata, error) { + m := &metadata.Metadata{} + + if c.IsSet("metadata-file") { + metadataFile, err := os.Open(c.String("metadata-file")) + if err != nil { + return nil, err + } + defer metadataFile.Close() + + if err := json.NewDecoder(metadataFile).Decode(m); err != nil { + return nil, err + } + } + platform := c.String("system-platform") if platform == "" { platform = runtime.GOOS + "/" + runtime.GOARCH } - fullRepoName := c.String("repo-name") - repoOwner := "" - repoName := "" - if idx := strings.LastIndex(fullRepoName, "/"); idx != -1 { - repoOwner = fullRepoName[:idx] - repoName = fullRepoName[idx+1:] + metadataFileAndOverrideOrDefault(c, "repo-name", func(fullRepoName string) { + if idx := strings.LastIndex(fullRepoName, "/"); idx != -1 { + m.Repo.Owner = fullRepoName[:idx] + m.Repo.Name = fullRepoName[idx+1:] + } + }, c.String) + + var err error + metadataFileAndOverrideOrDefault(c, "pipeline-changed-files", func(changedFilesRaw string) { + var changedFiles []string + if len(changedFilesRaw) != 0 && changedFilesRaw[0] == '[' { + if jsonErr := json.Unmarshal([]byte(changedFilesRaw), &changedFiles); jsonErr != nil { + err = fmt.Errorf("pipeline-changed-files detected json but could not parse it: %w", jsonErr) + } + } else { + for _, file := range strings.Split(changedFilesRaw, ",") { + changedFiles = append(changedFiles, strings.TrimSpace(file)) + } + } + m.Curr.Commit.ChangedFiles = changedFiles + }, c.String) + if err != nil { + return nil, err } - return metadata.Metadata{ - Repo: metadata.Repo{ - Name: repoName, - Owner: repoOwner, - RemoteID: c.String("repo-remote-id"), - ForgeURL: c.String("repo-url"), - CloneURL: c.String("repo-clone-url"), - CloneSSHURL: c.String("repo-clone-ssh-url"), - Private: c.Bool("repo-private"), - Trusted: c.Bool("repo-trusted"), - }, - Curr: metadata.Pipeline{ - Number: c.Int64("pipeline-number"), - Parent: c.Int64("pipeline-parent"), - Created: c.Int64("pipeline-created"), - Started: c.Int64("pipeline-started"), - Finished: c.Int64("pipeline-finished"), - Status: c.String("pipeline-status"), - Event: c.String("pipeline-event"), - ForgeURL: c.String("pipeline-url"), - DeployTo: c.String("pipeline-deploy-to"), - DeployTask: c.String("pipeline-deploy-task"), - Commit: metadata.Commit{ - Sha: c.String("commit-sha"), - Ref: c.String("commit-ref"), - Refspec: c.String("commit-refspec"), - Branch: c.String("commit-branch"), - Message: c.String("commit-message"), - Author: metadata.Author{ - Name: c.String("commit-author-name"), - Email: c.String("commit-author-email"), - Avatar: c.String("commit-author-avatar"), - }, - }, - }, - Prev: metadata.Pipeline{ - Number: c.Int64("prev-pipeline-number"), - Created: c.Int64("prev-pipeline-created"), - Started: c.Int64("prev-pipeline-started"), - Finished: c.Int64("prev-pipeline-finished"), - Status: c.String("prev-pipeline-status"), - Event: c.String("prev-pipeline-event"), - ForgeURL: c.String("prev-pipeline-url"), - Commit: metadata.Commit{ - Sha: c.String("prev-commit-sha"), - Ref: c.String("prev-commit-ref"), - Refspec: c.String("prev-commit-refspec"), - Branch: c.String("prev-commit-branch"), - Message: c.String("prev-commit-message"), - Author: metadata.Author{ - Name: c.String("prev-commit-author-name"), - Email: c.String("prev-commit-author-email"), - Avatar: c.String("prev-commit-author-avatar"), - }, - }, - }, - Workflow: metadata.Workflow{ - Name: c.String("workflow-name"), - Number: c.Int("workflow-number"), - Matrix: axis, - }, - Step: metadata.Step{ - Name: c.String("step-name"), - Number: c.Int("step-number"), - }, - Sys: metadata.System{ - Name: c.String("system-name"), - URL: c.String("system-url"), - Platform: platform, - Version: version.Version, - }, - Forge: metadata.Forge{ - Type: c.String("forge-type"), - URL: c.String("forge-url"), - }, + // Repo + metadataFileAndOverrideOrDefault(c, "repo-remote-id", func(s string) { m.Repo.RemoteID = s }, c.String) + metadataFileAndOverrideOrDefault(c, "repo-url", func(s string) { m.Repo.ForgeURL = s }, c.String) + metadataFileAndOverrideOrDefault(c, "repo-default-branch", func(s string) { m.Repo.Branch = s }, c.String) + metadataFileAndOverrideOrDefault(c, "repo-clone-url", func(s string) { m.Repo.CloneURL = s }, c.String) + metadataFileAndOverrideOrDefault(c, "repo-clone-ssh-url", func(s string) { m.Repo.CloneSSHURL = s }, c.String) + metadataFileAndOverrideOrDefault(c, "repo-private", func(b bool) { m.Repo.Private = b }, c.Bool) + metadataFileAndOverrideOrDefault(c, "repo-trusted-network", func(b bool) { m.Repo.Trusted.Network = b }, c.Bool) + metadataFileAndOverrideOrDefault(c, "repo-trusted-security", func(b bool) { m.Repo.Trusted.Security = b }, c.Bool) + metadataFileAndOverrideOrDefault(c, "repo-trusted-volumes", func(b bool) { m.Repo.Trusted.Volumes = b }, c.Bool) + + // Current Pipeline + metadataFileAndOverrideOrDefault(c, "pipeline-number", func(i int64) { m.Curr.Number = i }, c.Int) + metadataFileAndOverrideOrDefault(c, "pipeline-parent", func(i int64) { m.Curr.Parent = i }, c.Int) + metadataFileAndOverrideOrDefault(c, "pipeline-created", func(i int64) { m.Curr.Created = i }, c.Int) + metadataFileAndOverrideOrDefault(c, "pipeline-started", func(i int64) { m.Curr.Started = i }, c.Int) + metadataFileAndOverrideOrDefault(c, "pipeline-finished", func(i int64) { m.Curr.Finished = i }, c.Int) + metadataFileAndOverrideOrDefault(c, "pipeline-status", func(s string) { m.Curr.Status = s }, c.String) + metadataFileAndOverrideOrDefault(c, "pipeline-event", func(s string) { m.Curr.Event = s }, c.String) + metadataFileAndOverrideOrDefault(c, "pipeline-url", func(s string) { m.Curr.ForgeURL = s }, c.String) + metadataFileAndOverrideOrDefault(c, "pipeline-deploy-to", func(s string) { m.Curr.DeployTo = s }, c.String) + metadataFileAndOverrideOrDefault(c, "pipeline-deploy-task", func(s string) { m.Curr.DeployTask = s }, c.String) + + // Current Pipeline Commit + metadataFileAndOverrideOrDefault(c, "commit-sha", func(s string) { m.Curr.Commit.Sha = s }, c.String) + metadataFileAndOverrideOrDefault(c, "commit-ref", func(s string) { m.Curr.Commit.Ref = s }, c.String) + metadataFileAndOverrideOrDefault(c, "commit-refspec", func(s string) { m.Curr.Commit.Refspec = s }, c.String) + metadataFileAndOverrideOrDefault(c, "commit-branch", func(s string) { m.Curr.Commit.Branch = s }, c.String) + metadataFileAndOverrideOrDefault(c, "commit-message", func(s string) { m.Curr.Commit.Message = s }, c.String) + metadataFileAndOverrideOrDefault(c, "commit-author-name", func(s string) { m.Curr.Commit.Author.Name = s }, c.String) + metadataFileAndOverrideOrDefault(c, "commit-author-email", func(s string) { m.Curr.Commit.Author.Email = s }, c.String) + metadataFileAndOverrideOrDefault(c, "commit-author-avatar", func(s string) { m.Curr.Commit.Author.Avatar = s }, c.String) + + metadataFileAndOverrideOrDefault(c, "commit-pull-labels", func(sl []string) { m.Curr.Commit.PullRequestLabels = sl }, c.StringSlice) + metadataFileAndOverrideOrDefault(c, "commit-release-is-pre", func(b bool) { m.Curr.Commit.IsPrerelease = b }, c.Bool) + + // Previous Pipeline + metadataFileAndOverrideOrDefault(c, "prev-pipeline-number", func(i int64) { m.Prev.Number = i }, c.Int) + metadataFileAndOverrideOrDefault(c, "prev-pipeline-created", func(i int64) { m.Prev.Created = i }, c.Int) + metadataFileAndOverrideOrDefault(c, "prev-pipeline-started", func(i int64) { m.Prev.Started = i }, c.Int) + metadataFileAndOverrideOrDefault(c, "prev-pipeline-finished", func(i int64) { m.Prev.Finished = i }, c.Int) + metadataFileAndOverrideOrDefault(c, "prev-pipeline-status", func(s string) { m.Prev.Status = s }, c.String) + metadataFileAndOverrideOrDefault(c, "prev-pipeline-event", func(s string) { m.Prev.Event = s }, c.String) + metadataFileAndOverrideOrDefault(c, "prev-pipeline-url", func(s string) { m.Prev.ForgeURL = s }, c.String) + + // Previous Pipeline Commit + metadataFileAndOverrideOrDefault(c, "prev-commit-sha", func(s string) { m.Prev.Commit.Sha = s }, c.String) + metadataFileAndOverrideOrDefault(c, "prev-commit-ref", func(s string) { m.Prev.Commit.Ref = s }, c.String) + metadataFileAndOverrideOrDefault(c, "prev-commit-refspec", func(s string) { m.Prev.Commit.Refspec = s }, c.String) + metadataFileAndOverrideOrDefault(c, "prev-commit-branch", func(s string) { m.Prev.Commit.Branch = s }, c.String) + metadataFileAndOverrideOrDefault(c, "prev-commit-message", func(s string) { m.Prev.Commit.Message = s }, c.String) + metadataFileAndOverrideOrDefault(c, "prev-commit-author-name", func(s string) { m.Prev.Commit.Author.Name = s }, c.String) + metadataFileAndOverrideOrDefault(c, "prev-commit-author-email", func(s string) { m.Prev.Commit.Author.Email = s }, c.String) + metadataFileAndOverrideOrDefault(c, "prev-commit-author-avatar", func(s string) { m.Prev.Commit.Author.Avatar = s }, c.String) + + // Workflow + metadataFileAndOverrideOrDefault(c, "workflow-name", func(s string) { m.Workflow.Name = s }, c.String) + metadataFileAndOverrideOrDefault(c, "workflow-number", func(i int64) { m.Workflow.Number = int(i) }, c.Int) + m.Workflow.Matrix = axis + + // System + metadataFileAndOverrideOrDefault(c, "system-name", func(s string) { m.Sys.Name = s }, c.String) + metadataFileAndOverrideOrDefault(c, "system-url", func(s string) { m.Sys.URL = s }, c.String) + metadataFileAndOverrideOrDefault(c, "system-host", func(s string) { m.Sys.Host = s }, c.String) + m.Sys.Platform = platform + m.Sys.Version = version.Version + + // Forge + metadataFileAndOverrideOrDefault(c, "forge-type", func(s string) { m.Forge.Type = s }, c.String) + metadataFileAndOverrideOrDefault(c, "forge-url", func(s string) { m.Forge.URL = s }, c.String) + + if w != nil { + m.Workflow = *w + } + + return m, nil +} + +// metadataFileAndOverrideOrDefault will either use the flag default or if metadata file is set only overload if explicit set. +func metadataFileAndOverrideOrDefault[T any](c *cli.Command, flag string, setter func(T), getter func(string) T) { + if !c.IsSet("metadata-file") || c.IsSet(flag) { + setter(getter(flag)) } } diff --git a/cli/exec/metadata_test.go b/cli/exec/metadata_test.go new file mode 100644 index 000000000..231dc9cb2 --- /dev/null +++ b/cli/exec/metadata_test.go @@ -0,0 +1,142 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package exec + +import ( + "context" + "encoding/json" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/urfave/cli/v3" + + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/matrix" +) + +func TestMetadataFromContext(t *testing.T) { + sampleMetadata := &metadata.Metadata{ + Repo: metadata.Repo{Owner: "test-user", Name: "test-repo"}, + Curr: metadata.Pipeline{Number: 5}, + } + + runCommand := func(flags []cli.Flag, fn func(c *cli.Command)) { + c := &cli.Command{ + Flags: flags, + Action: func(_ context.Context, c *cli.Command) error { + fn(c) + return nil + }, + } + assert.NoError(t, c.Run(context.Background(), []string{"woodpecker-cli"})) + } + + t.Run("LoadFromFile", func(t *testing.T) { + tempFileName := createTempFile(t, sampleMetadata) + + flags := []cli.Flag{ + &cli.StringFlag{Name: "metadata-file"}, + } + + runCommand(flags, func(c *cli.Command) { + _ = c.Set("metadata-file", tempFileName) + + m, err := metadataFromContext(context.Background(), c, nil, nil) + require.NoError(t, err) + assert.Equal(t, "test-repo", m.Repo.Name) + assert.Equal(t, int64(5), m.Curr.Number) + }) + }) + + t.Run("OverrideFromFlags", func(t *testing.T) { + tempFileName := createTempFile(t, sampleMetadata) + + flags := []cli.Flag{ + &cli.StringFlag{Name: "metadata-file"}, + &cli.StringFlag{Name: "repo-name"}, + &cli.IntFlag{Name: "pipeline-number"}, + } + + runCommand(flags, func(c *cli.Command) { + _ = c.Set("metadata-file", tempFileName) + _ = c.Set("repo-name", "aUser/override-repo") + _ = c.Set("pipeline-number", "10") + + m, err := metadataFromContext(context.Background(), c, nil, nil) + require.NoError(t, err) + assert.Equal(t, "override-repo", m.Repo.Name) + assert.Equal(t, int64(10), m.Curr.Number) + }) + }) + + t.Run("InvalidFile", func(t *testing.T) { + tempFile, err := os.CreateTemp("", "invalid.json") + require.NoError(t, err) + t.Cleanup(func() { os.Remove(tempFile.Name()) }) + + _, err = tempFile.Write([]byte("invalid json")) + require.NoError(t, err) + + flags := []cli.Flag{ + &cli.StringFlag{Name: "metadata-file"}, + } + + runCommand(flags, func(c *cli.Command) { + _ = c.Set("metadata-file", tempFile.Name()) + + _, err = metadataFromContext(context.Background(), c, nil, nil) + assert.Error(t, err) + }) + }) + + t.Run("DefaultValues", func(t *testing.T) { + flags := []cli.Flag{ + &cli.StringFlag{Name: "repo-name", Value: "test/default-repo"}, + &cli.IntFlag{Name: "pipeline-number", Value: 1}, + } + + runCommand(flags, func(c *cli.Command) { + m, err := metadataFromContext(context.Background(), c, nil, nil) + require.NoError(t, err) + if assert.NotNil(t, m) { + assert.Equal(t, "test", m.Repo.Owner) + assert.Equal(t, "default-repo", m.Repo.Name) + assert.Equal(t, int64(1), m.Curr.Number) + } + }) + }) + + t.Run("MatrixAxis", func(t *testing.T) { + runCommand([]cli.Flag{}, func(c *cli.Command) { + axis := matrix.Axis{"go": "1.16", "os": "linux"} + m, err := metadataFromContext(context.Background(), c, axis, nil) + require.NoError(t, err) + assert.EqualValues(t, map[string]string{"go": "1.16", "os": "linux"}, m.Workflow.Matrix) + }) + }) +} + +func createTempFile(t *testing.T, content any) string { + t.Helper() + tempFile, err := os.CreateTemp("", "metadata.json") + require.NoError(t, err) + t.Cleanup(func() { os.Remove(tempFile.Name()) }) + + err = json.NewEncoder(tempFile).Encode(content) + require.NoError(t, err) + return tempFile.Name() +} diff --git a/cli/info/info.go b/cli/info/info.go index 5963f4425..06f81e1c4 100644 --- a/cli/info/info.go +++ b/cli/info/info.go @@ -15,13 +15,14 @@ package info import ( + "context" "os" "text/template" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" ) // Command exports the info command. @@ -33,8 +34,8 @@ var Command = &cli.Command{ Flags: []cli.Flag{common.FormatFlag(tmplInfo, true)}, } -func info(c *cli.Context) error { - client, err := internal.NewClient(c) +func info(ctx context.Context, c *cli.Command) error { + client, err := internal.NewClient(ctx, c) if err != nil { return err } diff --git a/cli/internal/config/config.go b/cli/internal/config/config.go index 29741212e..274572c67 100644 --- a/cli/internal/config/config.go +++ b/cli/internal/config/config.go @@ -1,6 +1,7 @@ package config import ( + "context" "encoding/json" "errors" "os" @@ -8,7 +9,7 @@ import ( "github.com/adrg/xdg" "github.com/rs/zerolog/log" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" "github.com/zalando/go-keyring" ) @@ -32,18 +33,18 @@ func (c *Config) MergeIfNotSet(c2 *Config) { var skipSetupForCommands = []string{"setup", "help", "h", "version", "update", "lint", "exec", ""} -func Load(c *cli.Context) error { +func Load(ctx context.Context, c *cli.Command) error { if firstArg := c.Args().First(); slices.Contains(skipSetupForCommands, firstArg) { return nil } - config, err := Get(c, c.String("config")) + config, err := Get(ctx, c, c.String("config")) if err != nil { return err } if config.ServerURL == "" || config.Token == "" { - log.Info().Msg("The woodpecker-cli is not yet set up. Please run `woodpecker-cli setup` or provide the required environment variables / flags.") + log.Info().Msg("woodpecker-cli is not set up, run `woodpecker-cli setup` or provide required environment variables/flags") return errors.New("woodpecker-cli is not configured") } @@ -62,7 +63,7 @@ func Load(c *cli.Context) error { return err } - log.Debug().Any("config", config).Msg("Loaded config") + log.Debug().Any("config", config).Msg("loaded config") return nil } @@ -80,11 +81,11 @@ func getConfigPath(configPath string) (string, error) { return configPath, nil } -func Get(ctx *cli.Context, _configPath string) (*Config, error) { - c := &Config{ - LogLevel: ctx.String("log-level"), - Token: ctx.String("token"), - ServerURL: ctx.String("server"), +func Get(_ context.Context, c *cli.Command, _configPath string) (*Config, error) { + conf := &Config{ + LogLevel: c.String("log-level"), + Token: c.String("token"), + ServerURL: c.String("server"), } configPath, err := getConfigPath(_configPath) @@ -92,16 +93,16 @@ func Get(ctx *cli.Context, _configPath string) (*Config, error) { return nil, err } - log.Debug().Str("configPath", configPath).Msg("Checking for config file") + log.Debug().Str("configPath", configPath).Msg("checking for config file") content, err := os.ReadFile(configPath) switch { case err != nil && !os.IsNotExist(err): - log.Debug().Err(err).Msg("Failed to read the config file") + log.Debug().Err(err).Msg("failed to read the config file") return nil, err case err != nil && os.IsNotExist(err): - log.Debug().Msg("The config file does not exist") + log.Debug().Msg("config file does not exist") default: configFromFile := &Config{} @@ -109,33 +110,33 @@ func Get(ctx *cli.Context, _configPath string) (*Config, error) { if err != nil { return nil, err } - c.MergeIfNotSet(configFromFile) - log.Debug().Msg("Loaded config from file") + conf.MergeIfNotSet(configFromFile) + log.Debug().Msg("loaded config from file") } // if server or token are explicitly set, use them - if ctx.IsSet("server") || ctx.IsSet("token") { - return c, nil + if c.IsSet("server") || c.IsSet("token") { + return conf, nil } // load token from keyring - service := ctx.App.Name - secret, err := keyring.Get(service, c.ServerURL) + service := c.Root().Name + secret, err := keyring.Get(service, conf.ServerURL) if errors.Is(err, keyring.ErrUnsupportedPlatform) { - log.Warn().Msg("Keyring is not supported on this platform") - return c, nil + log.Warn().Msg("keyring is not supported on this platform") + return conf, nil } if errors.Is(err, keyring.ErrNotFound) { - log.Warn().Msg("Token not found in keyring") - return c, nil + log.Warn().Msg("token not found in keyring") + return conf, nil } - c.Token = secret + conf.Token = secret - return c, nil + return conf, nil } -func Save(ctx *cli.Context, _configPath string, c *Config) error { - config, err := json.Marshal(c) +func Save(_ context.Context, c *cli.Command, _configPath string, conf *Config) error { + config, err := json.Marshal(conf) if err != nil { return err } @@ -146,8 +147,8 @@ func Save(ctx *cli.Context, _configPath string, c *Config) error { } // save token to keyring - service := ctx.App.Name - err = keyring.Set(service, c.ServerURL, c.Token) + service := c.Root().Name + err = keyring.Set(service, conf.ServerURL, conf.Token) if err != nil { return err } diff --git a/cli/internal/util.go b/cli/internal/util.go index 001752ed3..e1367dfd5 100644 --- a/cli/internal/util.go +++ b/cli/internal/util.go @@ -15,6 +15,7 @@ package internal import ( + "context" "crypto/tls" "crypto/x509" "fmt" @@ -25,15 +26,15 @@ import ( vsc_url "github.com/gitsight/go-vcsurl" "github.com/rs/zerolog/log" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" "golang.org/x/net/proxy" "golang.org/x/oauth2" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) // NewClient returns a new client from the CLI context. -func NewClient(c *cli.Context) (woodpecker.Client, error) { +func NewClient(ctx context.Context, c *cli.Command) (woodpecker.Client, error) { var ( skip = c.Bool("skip-verify") socks = c.String("socks-proxy") @@ -63,8 +64,7 @@ func NewClient(c *cli.Context) (woodpecker.Client, error) { } config := new(oauth2.Config) - client := config.Client( - c.Context, + client := config.Client(ctx, &oauth2.Token{ AccessToken: token, }, @@ -161,3 +161,40 @@ func ParseKeyPair(p []string) map[string]string { } return params } + +/* +ParseStep parses the step id form a string which may either be the step PID (step number) or a step name. +These rules apply: + +- Step PID take precedence over step name when searching for a match. +- First match is used, when there are multiple steps with the same name. + +Strictly speaking, this is not parsing, but a lookup. +*/ +func ParseStep(client woodpecker.Client, repoID, number int64, stepArg string) (stepID int64, err error) { + pipeline, err := client.Pipeline(repoID, number) + if err != nil { + return 0, err + } + + stepPID, err := strconv.ParseInt(stepArg, 10, 64) + if err == nil { + for _, wf := range pipeline.Workflows { + for _, step := range wf.Children { + if int64(step.PID) == stepPID { + return step.ID, nil + } + } + } + } + + for _, wf := range pipeline.Workflows { + for _, step := range wf.Children { + if step.Name == stepArg { + return step.ID, nil + } + } + } + + return 0, fmt.Errorf("no step with number or name '%s' found", stepArg) +} diff --git a/cli/lint/lint.go b/cli/lint/lint.go index f77d6cfe0..832a508bd 100644 --- a/cli/lint/lint.go +++ b/cli/lint/lint.go @@ -15,17 +15,19 @@ package lint import ( + "context" "fmt" "os" "path" "path/filepath" "strings" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/linter" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/linter" + "go.woodpecker-ci.org/woodpecker/v3/shared/constant" ) // Command exports the info command. @@ -34,13 +36,31 @@ var Command = &cli.Command{ Usage: "lint a pipeline configuration file", ArgsUsage: "[path/to/.woodpecker.yaml]", Action: lint, + Flags: []cli.Flag{ + &cli.StringSliceFlag{ + Sources: cli.EnvVars("WOODPECKER_PLUGINS_PRIVILEGED"), + Name: "plugins-privileged", + Usage: "allow plugins to run in privileged mode, if set empty, there is no", + }, + &cli.StringSliceFlag{ + Sources: cli.EnvVars("WOODPECKER_PLUGINS_TRUSTED_CLONE"), + Name: "plugins-trusted-clone", + Usage: "plugins that are trusted to handle Git credentials in cloning steps", + Value: constant.TrustedClonePlugins, + }, + &cli.BoolFlag{ + Sources: cli.EnvVars("WOODPECKER_LINT_STRICT"), + Name: "strict", + Usage: "treat warnings as errors", + }, + }, } -func lint(c *cli.Context) error { - return common.RunPipelineFunc(c, lintFile, lintDir) +func lint(ctx context.Context, c *cli.Command) error { + return common.RunPipelineFunc(ctx, c, lintFile, lintDir) } -func lintDir(c *cli.Context, dir string) error { +func lintDir(ctx context.Context, c *cli.Command, dir string) error { var errorStrings []string if err := filepath.Walk(dir, func(path string, info os.FileInfo, e error) error { if e != nil { @@ -50,7 +70,7 @@ func lintDir(c *cli.Context, dir string) error { // check if it is a regular file (not dir) if info.Mode().IsRegular() && (strings.HasSuffix(info.Name(), ".yaml") || strings.HasSuffix(info.Name(), ".yml")) { fmt.Println("#", info.Name()) - if err := lintFile(c, path); err != nil { + if err := lintFile(ctx, c, path); err != nil { errorStrings = append(errorStrings, err.Error()) } fmt.Println("") @@ -68,7 +88,7 @@ func lintDir(c *cli.Context, dir string) error { return nil } -func lintFile(_ *cli.Context, file string) error { +func lintFile(_ context.Context, c *cli.Command, file string) error { fi, err := os.Open(file) if err != nil { return err @@ -82,7 +102,7 @@ func lintFile(_ *cli.Context, file string) error { rawConfig := string(buf) - c, err := yaml.ParseString(rawConfig) + parsedConfig, err := yaml.ParseString(rawConfig) if err != nil { return err } @@ -90,13 +110,21 @@ func lintFile(_ *cli.Context, file string) error { config := &linter.WorkflowConfig{ File: path.Base(file), RawConfig: rawConfig, - Workflow: c, + Workflow: parsedConfig, } // TODO: lint multiple files at once to allow checks for sth like "depends_on" to work - err = linter.New(linter.WithTrusted(true)).Lint([]*linter.WorkflowConfig{config}) + err = linter.New( + linter.WithTrusted(linter.TrustedConfiguration{ + Network: true, + Volumes: true, + Security: true, + }), + linter.PrivilegedPlugins(c.StringSlice("plugins-privileged")), + linter.WithTrustedClonePlugins(c.StringSlice("plugins-trusted-clone")), + ).Lint([]*linter.WorkflowConfig{config}) if err != nil { - str, err := FormatLintError(config.File, err) + str, err := FormatLintError(config.File, err, c.Bool("strict")) if str != "" { fmt.Print(str) diff --git a/cli/lint/utils.go b/cli/lint/utils.go index 5a062ae3e..bfa29c6c0 100644 --- a/cli/lint/utils.go +++ b/cli/lint/utils.go @@ -7,10 +7,10 @@ import ( term_env "github.com/muesli/termenv" - pipeline_errors "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors" + pipeline_errors "go.woodpecker-ci.org/woodpecker/v3/pipeline/errors" ) -func FormatLintError(file string, err error) (string, error) { +func FormatLintError(file string, err error, strict bool) (string, error) { if err == nil { return "", nil } @@ -24,7 +24,7 @@ func FormatLintError(file string, err error) (string, error) { for _, err := range linterErrors { line := " " - if err.IsWarning { + if !strict && err.IsWarning { line = fmt.Sprintf("%s ⚠️ ", line) amountWarnings++ } else { diff --git a/cli/org/org.go b/cli/org/org.go index 862d5eba8..1c03978df 100644 --- a/cli/org/org.go +++ b/cli/org/org.go @@ -15,16 +15,18 @@ package org import ( - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/org/registry" + "go.woodpecker-ci.org/woodpecker/v3/cli/org/registry" + "go.woodpecker-ci.org/woodpecker/v3/cli/org/secret" ) // Command exports the org command set. var Command = &cli.Command{ Name: "org", Usage: "manage organizations", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ registry.Command, + secret.Command, }, } diff --git a/cli/org/registry/registry.go b/cli/org/registry/registry.go index 6d8904958..4788deb91 100644 --- a/cli/org/registry/registry.go +++ b/cli/org/registry/registry.go @@ -17,25 +17,25 @@ package registry import ( "strconv" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) // Command exports the registry command set. var Command = &cli.Command{ Name: "registry", Usage: "manage organization registries", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ registryCreateCmd, registryDeleteCmd, - registryUpdateCmd, - registryInfoCmd, registryListCmd, + registryShowCmd, + registryUpdateCmd, }, } -func parseTargetArgs(client woodpecker.Client, c *cli.Context) (orgID int64, err error) { +func parseTargetArgs(client woodpecker.Client, c *cli.Command) (orgID int64, err error) { orgIDOrName := c.String("organization") if orgIDOrName == "" { orgIDOrName = c.Args().First() diff --git a/cli/org/registry/registry_add.go b/cli/org/registry/registry_add.go index f3cdf5f5f..c15990b42 100644 --- a/cli/org/registry/registry_add.go +++ b/cli/org/registry/registry_add.go @@ -15,19 +15,20 @@ package registry import ( + "context" "os" "strings" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) var registryCreateCmd = &cli.Command{ Name: "add", - Usage: "adds a registry", + Usage: "add a registry", ArgsUsage: "[org-id|org-full-name]", Action: registryCreate, Flags: []cli.Flag{ @@ -48,14 +49,14 @@ var registryCreateCmd = &cli.Command{ }, } -func registryCreate(c *cli.Context) error { +func registryCreate(ctx context.Context, c *cli.Command) error { var ( hostname = c.String("hostname") username = c.String("username") password = c.String("password") ) - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } diff --git a/cli/org/registry/registry_list.go b/cli/org/registry/registry_list.go index 4fd1134ae..ca036a806 100644 --- a/cli/org/registry/registry_list.go +++ b/cli/org/registry/registry_list.go @@ -15,13 +15,15 @@ package registry import ( + "context" "html/template" "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) var registryListCmd = &cli.Command{ @@ -35,10 +37,10 @@ var registryListCmd = &cli.Command{ }, } -func registryList(c *cli.Context) error { +func registryList(ctx context.Context, c *cli.Command) error { format := c.String("format") + "\n" - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } @@ -48,7 +50,9 @@ func registryList(c *cli.Context) error { return err } - list, err := client.OrgRegistryList(orgID) + opt := woodpecker.RegistryListOptions{} + + list, err := client.OrgRegistryList(orgID, opt) if err != nil { return err } diff --git a/cli/org/registry/registry_rm.go b/cli/org/registry/registry_rm.go index e56322c95..151cf1b9d 100644 --- a/cli/org/registry/registry_rm.go +++ b/cli/org/registry/registry_rm.go @@ -15,10 +15,12 @@ package registry import ( - "github.com/urfave/cli/v2" + "context" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "github.com/urfave/cli/v3" + + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" ) var registryDeleteCmd = &cli.Command{ @@ -36,10 +38,10 @@ var registryDeleteCmd = &cli.Command{ }, } -func registryDelete(c *cli.Context) error { +func registryDelete(ctx context.Context, c *cli.Command) error { hostname := c.String("hostname") - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } diff --git a/cli/org/registry/registry_set.go b/cli/org/registry/registry_set.go index a79a0331b..08d8f829f 100644 --- a/cli/org/registry/registry_set.go +++ b/cli/org/registry/registry_set.go @@ -15,14 +15,15 @@ package registry import ( + "context" "os" "strings" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) var registryUpdateCmd = &cli.Command{ @@ -48,14 +49,14 @@ var registryUpdateCmd = &cli.Command{ }, } -func registryUpdate(c *cli.Context) error { +func registryUpdate(ctx context.Context, c *cli.Command) error { var ( hostname = c.String("hostname") username = c.String("username") password = c.String("password") ) - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } diff --git a/cli/org/registry/registry_info.go b/cli/org/registry/registry_show.go similarity index 77% rename from cli/org/registry/registry_info.go rename to cli/org/registry/registry_show.go index a28a4fdd6..ede2994e5 100644 --- a/cli/org/registry/registry_info.go +++ b/cli/org/registry/registry_show.go @@ -15,20 +15,21 @@ package registry import ( + "context" "html/template" "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" ) -var registryInfoCmd = &cli.Command{ - Name: "info", - Usage: "display registry info", +var registryShowCmd = &cli.Command{ + Name: "show", + Usage: "show registry information", ArgsUsage: "[org-id|org-full-name]", - Action: registryInfo, + Action: registryShow, Flags: []cli.Flag{ common.OrgFlag, &cli.StringFlag{ @@ -40,13 +41,13 @@ var registryInfoCmd = &cli.Command{ }, } -func registryInfo(c *cli.Context) error { +func registryShow(ctx context.Context, c *cli.Command) error { var ( hostname = c.String("hostname") format = c.String("format") + "\n" ) - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } diff --git a/cli/org/secret/secret.go b/cli/org/secret/secret.go new file mode 100644 index 000000000..af53be6ed --- /dev/null +++ b/cli/org/secret/secret.go @@ -0,0 +1,60 @@ +// Copyright 2023 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package secret + +import ( + "strconv" + + "github.com/urfave/cli/v3" + + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" +) + +// Command exports the secret command. +var Command = &cli.Command{ + Name: "secret", + Usage: "manage secrets", + Commands: []*cli.Command{ + secretCreateCmd, + secretDeleteCmd, + secretListCmd, + secretShowCmd, + secretUpdateCmd, + }, +} + +func parseTargetArgs(client woodpecker.Client, c *cli.Command) (orgID int64, err error) { + orgIDOrName := c.String("organization") + if orgIDOrName == "" { + orgIDOrName = c.Args().First() + } + + if orgIDOrName == "" { + if err := cli.ShowSubcommandHelp(c); err != nil { + return -1, err + } + } + + if orgID, err := strconv.ParseInt(orgIDOrName, 10, 64); err == nil { + return orgID, nil + } + + org, err := client.OrgLookup(orgIDOrName) + if err != nil { + return -1, err + } + + return org.ID, nil +} diff --git a/cli/secret/secret_add.go b/cli/org/secret/secret_add.go similarity index 73% rename from cli/secret/secret_add.go rename to cli/org/secret/secret_add.go index 16b75e0e0..a0a83ef80 100644 --- a/cli/secret/secret_add.go +++ b/cli/org/secret/secret_add.go @@ -15,28 +15,24 @@ package secret import ( + "context" "os" "strings" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) var secretCreateCmd = &cli.Command{ Name: "add", - Usage: "adds a secret", + Usage: "add a secret", ArgsUsage: "[repo-id|repo-full-name]", Action: secretCreate, Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "global", - Usage: "global secret", - }, common.OrgFlag, - common.RepoFlag, &cli.StringFlag{ Name: "name", Usage: "secret name", @@ -56,8 +52,8 @@ var secretCreateCmd = &cli.Command{ }, } -func secretCreate(c *cli.Context) error { - client, err := internal.NewClient(c) +func secretCreate(ctx context.Context, c *cli.Command) error { + client, err := internal.NewClient(ctx, c) if err != nil { return err } @@ -80,22 +76,12 @@ func secretCreate(c *cli.Context) error { secret.Value = string(out) } - global, orgID, repoID, err := parseTargetArgs(client, c) + orgID, err := parseTargetArgs(client, c) if err != nil { return err } - if global { - _, err = client.GlobalSecretCreate(secret) - return err - } - - if orgID != -1 { - _, err = client.OrgSecretCreate(orgID, secret) - return err - } - - _, err = client.SecretCreate(repoID, secret) + _, err = client.OrgSecretCreate(orgID, secret) return err } diff --git a/cli/org/secret/secret_list.go b/cli/org/secret/secret_list.go new file mode 100644 index 000000000..bba6079e9 --- /dev/null +++ b/cli/org/secret/secret_list.go @@ -0,0 +1,87 @@ +// Copyright 2023 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package secret + +import ( + "context" + "html/template" + "os" + "strings" + + "github.com/urfave/cli/v3" + + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" +) + +var secretListCmd = &cli.Command{ + Name: "ls", + Usage: "list secrets", + ArgsUsage: "[repo-id|repo-full-name]", + Action: secretList, + Flags: []cli.Flag{ + common.OrgFlag, + common.FormatFlag(tmplSecretList, true), + }, +} + +func secretList(ctx context.Context, c *cli.Command) error { + format := c.String("format") + "\n" + + client, err := internal.NewClient(ctx, c) + if err != nil { + return err + } + + orgID, err := parseTargetArgs(client, c) + if err != nil { + return err + } + + opt := woodpecker.SecretListOptions{} + + list, err := client.OrgSecretList(orgID, opt) + if err != nil { + return err + } + + tmpl, err := template.New("_").Funcs(secretFuncMap).Parse(format) + if err != nil { + return err + } + for _, secret := range list { + if err := tmpl.Execute(os.Stdout, secret); err != nil { + return err + } + } + return nil +} + +// Template for secret list items. +var tmplSecretList = "\x1b[33m{{ .Name }} \x1b[0m" + ` +Events: {{ list .Events }} +{{- if .Images }} +Images: {{ list .Images }} +{{- else }} +Images: +{{- end }} +` + +var secretFuncMap = template.FuncMap{ + "list": func(s []string) string { + return strings.Join(s, ", ") + }, +} diff --git a/cli/org/secret/secret_rm.go b/cli/org/secret/secret_rm.go new file mode 100644 index 000000000..37b2fe8fa --- /dev/null +++ b/cli/org/secret/secret_rm.go @@ -0,0 +1,54 @@ +// Copyright 2023 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package secret + +import ( + "context" + + "github.com/urfave/cli/v3" + + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" +) + +var secretDeleteCmd = &cli.Command{ + Name: "rm", + Usage: "remove a secret", + ArgsUsage: "[repo-id|repo-full-name]", + Action: secretDelete, + Flags: []cli.Flag{ + common.OrgFlag, + &cli.StringFlag{ + Name: "name", + Usage: "secret name", + }, + }, +} + +func secretDelete(ctx context.Context, c *cli.Command) error { + secretName := c.String("name") + + client, err := internal.NewClient(ctx, c) + if err != nil { + return err + } + + orgID, err := parseTargetArgs(client, c) + if err != nil { + return err + } + + return client.OrgSecretDelete(orgID, secretName) +} diff --git a/cli/org/secret/secret_set.go b/cli/org/secret/secret_set.go new file mode 100644 index 000000000..cb784c316 --- /dev/null +++ b/cli/org/secret/secret_set.go @@ -0,0 +1,83 @@ +// Copyright 2023 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package secret + +import ( + "context" + "os" + "strings" + + "github.com/urfave/cli/v3" + + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" +) + +var secretUpdateCmd = &cli.Command{ + Name: "update", + Usage: "update a secret", + ArgsUsage: "[repo-id|repo-full-name]", + Action: secretUpdate, + Flags: []cli.Flag{ + common.OrgFlag, + &cli.StringFlag{ + Name: "name", + Usage: "secret name", + }, + &cli.StringFlag{ + Name: "value", + Usage: "secret value", + }, + &cli.StringSliceFlag{ + Name: "event", + Usage: "limit secret to these event", + }, + &cli.StringSliceFlag{ + Name: "image", + Usage: "limit secret to these image", + }, + }, +} + +func secretUpdate(ctx context.Context, c *cli.Command) error { + client, err := internal.NewClient(ctx, c) + if err != nil { + return err + } + + secret := &woodpecker.Secret{ + Name: strings.ToLower(c.String("name")), + Value: c.String("value"), + Images: c.StringSlice("image"), + Events: c.StringSlice("event"), + } + if strings.HasPrefix(secret.Value, "@") { + path := strings.TrimPrefix(secret.Value, "@") + out, err := os.ReadFile(path) + if err != nil { + return err + } + secret.Value = string(out) + } + + orgID, err := parseTargetArgs(client, c) + if err != nil { + return err + } + + _, err = client.OrgSecretUpdate(orgID, secret) + return err +} diff --git a/cli/org/secret/secret_show.go b/cli/org/secret/secret_show.go new file mode 100644 index 000000000..86790c42b --- /dev/null +++ b/cli/org/secret/secret_show.go @@ -0,0 +1,74 @@ +// Copyright 2023 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package secret + +import ( + "context" + "fmt" + "html/template" + "os" + + "github.com/urfave/cli/v3" + + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" +) + +var secretShowCmd = &cli.Command{ + Name: "show", + Usage: "show secret information", + ArgsUsage: "[repo-id|repo-full-name]", + Action: secretShow, + Flags: []cli.Flag{ + common.OrgFlag, + &cli.StringFlag{ + Name: "name", + Usage: "secret name", + }, + common.FormatFlag(tmplSecretList, true), + }, +} + +func secretShow(ctx context.Context, c *cli.Command) error { + var ( + secretName = c.String("name") + format = c.String("format") + "\n" + ) + + if secretName == "" { + return fmt.Errorf("secret name is missing") + } + + client, err := internal.NewClient(ctx, c) + if err != nil { + return err + } + + orgID, err := parseTargetArgs(client, c) + if err != nil { + return err + } + + secret, err := client.OrgSecret(orgID, secretName) + if err != nil { + return err + } + + tmpl, err := template.New("_").Funcs(secretFuncMap).Parse(format) + if err != nil { + return err + } + return tmpl.Execute(os.Stdout, secret) +} diff --git a/cli/output/table.go b/cli/output/table.go index 4b7a24e00..9fff467df 100644 --- a/cli/output/table.go +++ b/cli/output/table.go @@ -9,7 +9,7 @@ import ( "text/tabwriter" "unicode" - "github.com/mitchellh/mapstructure" + "github.com/go-viper/mapstructure/v2" ) // NewTable creates a new Table. @@ -147,12 +147,12 @@ func (o *Table) Write(columns []string, obj any) error { colName := strings.ToLower(col) if alias, ok := o.fieldAlias[colName]; ok { if fn, ok := o.fieldMapping[alias]; ok { - out = append(out, fn(obj)) + out = append(out, sanitizeString(fn(obj))) continue } } if fn, ok := o.fieldMapping[colName]; ok { - out = append(out, fn(obj)) + out = append(out, sanitizeString(fn(obj))) continue } if value, ok := dataL[strings.ReplaceAll(colName, "_", "")]; ok { @@ -165,10 +165,10 @@ func (o *Table) Write(columns []string, obj any) error { continue } if s, ok := value.(string); ok { - out = append(out, NA(s)) + out = append(out, NA(sanitizeString(s))) continue } - out = append(out, fmt.Sprintf("%v", value)) + out = append(out, sanitizeString(value)) } } _, _ = fmt.Fprintln(o.w, strings.Join(out, "\t")) @@ -201,3 +201,9 @@ func fieldName(name string) string { } return string(out) } + +func sanitizeString(value any) string { + str := fmt.Sprintf("%v", value) + replacer := strings.NewReplacer("\n", " ", "\r", " ") + return strings.TrimSpace(replacer.Replace(str)) +} diff --git a/cli/pipeline/approve.go b/cli/pipeline/approve.go index 4e2185b27..299d7c10d 100644 --- a/cli/pipeline/approve.go +++ b/cli/pipeline/approve.go @@ -15,12 +15,13 @@ package pipeline import ( + "context" "fmt" "strconv" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" ) var pipelineApproveCmd = &cli.Command{ @@ -30,9 +31,9 @@ var pipelineApproveCmd = &cli.Command{ Action: pipelineApprove, } -func pipelineApprove(c *cli.Context) (err error) { +func pipelineApprove(ctx context.Context, c *cli.Command) (err error) { repoIDOrFullName := c.Args().First() - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } diff --git a/cli/pipeline/create.go b/cli/pipeline/create.go index 008a27f3e..b317d4b3c 100644 --- a/cli/pipeline/create.go +++ b/cli/pipeline/create.go @@ -15,13 +15,14 @@ package pipeline import ( + "context" "strings" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) var pipelineCreateCmd = &cli.Command{ @@ -42,9 +43,9 @@ var pipelineCreateCmd = &cli.Command{ }...), } -func pipelineCreate(c *cli.Context) error { +func pipelineCreate(ctx context.Context, c *cli.Command) error { repoIDOrFullName := c.Args().First() - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } @@ -73,5 +74,5 @@ func pipelineCreate(c *cli.Context) error { return err } - return pipelineOutput(c, []woodpecker.Pipeline{*pipeline}) + return pipelineOutput(c, []*woodpecker.Pipeline{pipeline}) } diff --git a/cli/pipeline/decline.go b/cli/pipeline/decline.go index b99b13452..330515314 100644 --- a/cli/pipeline/decline.go +++ b/cli/pipeline/decline.go @@ -15,12 +15,13 @@ package pipeline import ( + "context" "fmt" "strconv" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" ) var pipelineDeclineCmd = &cli.Command{ @@ -30,9 +31,9 @@ var pipelineDeclineCmd = &cli.Command{ Action: pipelineDecline, } -func pipelineDecline(c *cli.Context) (err error) { +func pipelineDecline(ctx context.Context, c *cli.Command) (err error) { repoIDOrFullName := c.Args().First() - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } diff --git a/cli/deploy/deploy.go b/cli/pipeline/deploy/deploy.go similarity index 81% rename from cli/deploy/deploy.go rename to cli/pipeline/deploy/deploy.go index d5786e110..110c675c5 100644 --- a/cli/deploy/deploy.go +++ b/cli/pipeline/deploy/deploy.go @@ -15,16 +15,17 @@ package deploy import ( + "context" "fmt" "html/template" "os" "strconv" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) // Command exports the deploy command. @@ -52,13 +53,13 @@ var Command = &cli.Command{ &cli.StringSliceFlag{ Name: "param", Aliases: []string{"p"}, - Usage: "custom parameters to be injected into the step environment. Format: KEY=value", + Usage: "custom parameters to inject into the step environment. Format: KEY=value", }, }, } -func deploy(c *cli.Context) error { - client, err := internal.NewClient(c) +func deploy(ctx context.Context, c *cli.Command) error { + client, err := internal.NewClient(ctx, c) if err != nil { return err } @@ -79,14 +80,14 @@ func deploy(c *cli.Context) error { return err } - branch = repo.DefaultBranch + branch = repo.Branch } pipelineArg := c.Args().Get(1) var number int64 if pipelineArg == "last" { // Fetch the pipeline number from the last pipeline - pipelines, err := client.PipelineList(repoID) + pipelines, err := client.PipelineList(repoID, woodpecker.PipelineListOptions{}) if err != nil { return err } @@ -120,9 +121,12 @@ func deploy(c *cli.Context) error { return fmt.Errorf("please specify the target environment (i.e. production)") } - params := internal.ParseKeyPair(c.StringSlice("param")) + opt := woodpecker.DeployOptions{ + DeployTo: env, + Params: internal.ParseKeyPair(c.StringSlice("param")), + } - deploy, err := client.Deploy(repoID, number, env, params) + deploy, err := client.Deploy(repoID, number, opt) if err != nil { return err } diff --git a/cli/pipeline/kill.go b/cli/pipeline/kill.go index 3c8fa3fb9..ac72899b7 100644 --- a/cli/pipeline/kill.go +++ b/cli/pipeline/kill.go @@ -15,12 +15,13 @@ package pipeline import ( + "context" "fmt" "strconv" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" ) var pipelineKillCmd = &cli.Command{ @@ -31,14 +32,14 @@ var pipelineKillCmd = &cli.Command{ Hidden: true, } -func pipelineKill(c *cli.Context) (err error) { +func pipelineKill(ctx context.Context, c *cli.Command) (err error) { number, err := strconv.ParseInt(c.Args().Get(1), 10, 64) if err != nil { return err } repoIDOrFullName := c.Args().First() - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } @@ -47,7 +48,7 @@ func pipelineKill(c *cli.Context) (err error) { return err } - err = client.PipelineKill(repoID, number) + err = client.PipelineDelete(repoID, number) if err != nil { return err } diff --git a/cli/pipeline/last.go b/cli/pipeline/last.go index e0af7d17f..3b3b4e315 100644 --- a/cli/pipeline/last.go +++ b/cli/pipeline/last.go @@ -15,16 +15,18 @@ package pipeline import ( - "github.com/urfave/cli/v2" + "context" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" + "github.com/urfave/cli/v3" + + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) var pipelineLastCmd = &cli.Command{ Name: "last", - Usage: "show latest pipeline details", + Usage: "show latest pipeline information", ArgsUsage: "", Action: pipelineLast, Flags: append(common.OutputFlags("table"), []cli.Flag{ @@ -36,9 +38,9 @@ var pipelineLastCmd = &cli.Command{ }...), } -func pipelineLast(c *cli.Context) error { +func pipelineLast(ctx context.Context, c *cli.Command) error { repoIDOrFullName := c.Args().First() - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } @@ -47,10 +49,14 @@ func pipelineLast(c *cli.Context) error { return err } - pipeline, err := client.PipelineLast(repoID, c.String("branch")) + opt := woodpecker.PipelineLastOptions{ + Branch: c.String("branch"), + } + + pipeline, err := client.PipelineLast(repoID, opt) if err != nil { return err } - return pipelineOutput(c, []woodpecker.Pipeline{*pipeline}) + return pipelineOutput(c, []*woodpecker.Pipeline{pipeline}) } diff --git a/cli/pipeline/list.go b/cli/pipeline/list.go index 6a3576323..312456a7a 100644 --- a/cli/pipeline/list.go +++ b/cli/pipeline/list.go @@ -15,88 +15,114 @@ package pipeline import ( - "github.com/urfave/cli/v2" + "context" + "time" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" + "github.com/urfave/cli/v3" + + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + shared_utils "go.woodpecker-ci.org/woodpecker/v3/shared/utils" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) //nolint:mnd -var pipelineListCmd = &cli.Command{ - Name: "ls", - Usage: "show pipeline history", - ArgsUsage: "", - Action: List, - Flags: append(common.OutputFlags("table"), []cli.Flag{ - &cli.StringFlag{ - Name: "branch", - Usage: "branch filter", - }, - &cli.StringFlag{ - Name: "event", - Usage: "event filter", - }, - &cli.StringFlag{ - Name: "status", - Usage: "status filter", - }, - &cli.IntFlag{ - Name: "limit", - Usage: "limit the list size", - Value: 25, - }, - }...), +func buildPipelineListCmd() *cli.Command { + return &cli.Command{ + Name: "ls", + Usage: "show pipeline history", + ArgsUsage: "", + Action: List, + Flags: append(common.OutputFlags("table"), []cli.Flag{ + &cli.StringFlag{ + Name: "branch", + Usage: "branch filter", + }, + &cli.StringFlag{ + Name: "event", + Usage: "event filter", + }, + &cli.StringFlag{ + Name: "status", + Usage: "status filter", + }, + &cli.IntFlag{ + Name: "limit", + Usage: "limit the list size", + Value: 25, + }, + &cli.TimestampFlag{ + Name: "before", + Usage: "only return pipelines before this date (RFC3339)", + Config: cli.TimestampConfig{ + Layouts: []string{ + time.RFC3339, + }, + }, + }, + &cli.TimestampFlag{ + Name: "after", + Usage: "only return pipelines after this date (RFC3339)", + Config: cli.TimestampConfig{ + Layouts: []string{ + time.RFC3339, + }, + }, + }, + }...), + } } -func List(c *cli.Context) error { - client, err := internal.NewClient(c) +func List(ctx context.Context, c *cli.Command) error { + client, err := internal.NewClient(ctx, c) if err != nil { return err } - resources, err := pipelineList(c, client) + pipelines, err := pipelineList(c, client) if err != nil { return err } - return pipelineOutput(c, resources) + return pipelineOutput(c, pipelines) } -func pipelineList(c *cli.Context, client woodpecker.Client) ([]woodpecker.Pipeline, error) { - resources := make([]woodpecker.Pipeline, 0) - +func pipelineList(c *cli.Command, client woodpecker.Client) ([]*woodpecker.Pipeline, error) { repoIDOrFullName := c.Args().First() repoID, err := internal.ParseRepo(client, repoIDOrFullName) if err != nil { - return resources, err + return nil, err } - pipelines, err := client.PipelineList(repoID) - if err != nil { - return resources, err + opt := woodpecker.PipelineListOptions{} + + if before := c.Timestamp("before"); !before.IsZero() { + opt.Before = before + } + if after := c.Timestamp("after"); !after.IsZero() { + opt.After = after } branch := c.String("branch") event := c.String("event") status := c.String("status") - limit := c.Int("limit") + limit := int(c.Int("limit")) - var count int - for _, pipeline := range pipelines { - if count >= limit { - break - } - if branch != "" && pipeline.Branch != branch { - continue - } - if event != "" && pipeline.Event != event { - continue - } - if status != "" && pipeline.Status != status { - continue - } - resources = append(resources, *pipeline) - count++ + pipelines, err := shared_utils.Paginate(func(page int) ([]*woodpecker.Pipeline, error) { + return client.PipelineList(repoID, + woodpecker.PipelineListOptions{ + ListOptions: woodpecker.ListOptions{ + Page: page, + }, + Before: opt.Before, + After: opt.After, + Branch: branch, + Events: []string{event}, + Status: status, + }, + ) + }, limit) + if err != nil { + return nil, err } - return resources, nil + return pipelines, nil } diff --git a/cli/pipeline/list_test.go b/cli/pipeline/list_test.go index 0f913bf71..969e06794 100644 --- a/cli/pipeline/list_test.go +++ b/cli/pipeline/list_test.go @@ -1,16 +1,17 @@ package pipeline import ( + "context" "errors" "io" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker/mocks" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker/mocks" ) func TestPipelineList(t *testing.T) { @@ -21,7 +22,7 @@ func TestPipelineList(t *testing.T) { pipelines []*woodpecker.Pipeline pipelineErr error args []string - expected []woodpecker.Pipeline + expected []*woodpecker.Pipeline wantErr error }{ { @@ -33,53 +34,12 @@ func TestPipelineList(t *testing.T) { {ID: 3, Branch: "main", Event: "push", Status: "failure"}, }, args: []string{"ls", "repo/name"}, - expected: []woodpecker.Pipeline{ + expected: []*woodpecker.Pipeline{ {ID: 1, Branch: "main", Event: "push", Status: "success"}, {ID: 2, Branch: "develop", Event: "pull_request", Status: "running"}, {ID: 3, Branch: "main", Event: "push", Status: "failure"}, }, }, - { - name: "filter by branch", - repoID: 1, - pipelines: []*woodpecker.Pipeline{ - {ID: 1, Branch: "main", Event: "push", Status: "success"}, - {ID: 2, Branch: "develop", Event: "pull_request", Status: "running"}, - {ID: 3, Branch: "main", Event: "push", Status: "failure"}, - }, - args: []string{"ls", "--branch", "main", "repo/name"}, - expected: []woodpecker.Pipeline{ - {ID: 1, Branch: "main", Event: "push", Status: "success"}, - {ID: 3, Branch: "main", Event: "push", Status: "failure"}, - }, - }, - { - name: "filter by event", - repoID: 1, - pipelines: []*woodpecker.Pipeline{ - {ID: 1, Branch: "main", Event: "push", Status: "success"}, - {ID: 2, Branch: "develop", Event: "pull_request", Status: "running"}, - {ID: 3, Branch: "main", Event: "push", Status: "failure"}, - }, - args: []string{"ls", "--event", "push", "repo/name"}, - expected: []woodpecker.Pipeline{ - {ID: 1, Branch: "main", Event: "push", Status: "success"}, - {ID: 3, Branch: "main", Event: "push", Status: "failure"}, - }, - }, - { - name: "filter by status", - repoID: 1, - pipelines: []*woodpecker.Pipeline{ - {ID: 1, Branch: "main", Event: "push", Status: "success"}, - {ID: 2, Branch: "develop", Event: "pull_request", Status: "running"}, - {ID: 3, Branch: "main", Event: "push", Status: "failure"}, - }, - args: []string{"ls", "--status", "success", "repo/name"}, - expected: []woodpecker.Pipeline{ - {ID: 1, Branch: "main", Event: "push", Status: "success"}, - }, - }, { name: "limit results", repoID: 1, @@ -89,7 +49,7 @@ func TestPipelineList(t *testing.T) { {ID: 3, Branch: "main", Event: "push", Status: "failure"}, }, args: []string{"ls", "--limit", "2", "repo/name"}, - expected: []woodpecker.Pipeline{ + expected: []*woodpecker.Pipeline{ {ID: 1, Branch: "main", Event: "push", Status: "success"}, {ID: 2, Branch: "develop", Event: "pull_request", Status: "running"}, }, @@ -106,14 +66,20 @@ func TestPipelineList(t *testing.T) { for _, tt := range testtases { t.Run(tt.name, func(t *testing.T) { mockClient := mocks.NewClient(t) - mockClient.On("PipelineList", mock.Anything).Return(tt.pipelines, tt.pipelineErr) + mockClient.On("PipelineList", mock.Anything, mock.Anything).Return(func(_ int64, opt woodpecker.PipelineListOptions) ([]*woodpecker.Pipeline, error) { + if tt.pipelineErr != nil { + return nil, tt.pipelineErr + } + if opt.Page == 1 { + return tt.pipelines, nil + } + return []*woodpecker.Pipeline{}, nil + }).Maybe() mockClient.On("RepoLookup", mock.Anything).Return(&woodpecker.Repo{ID: tt.repoID}, nil) - app := &cli.App{Writer: io.Discard} - c := cli.NewContext(app, nil, nil) - - command := pipelineListCmd - command.Action = func(c *cli.Context) error { + command := buildPipelineListCmd() + command.Writer = io.Discard + command.Action = func(_ context.Context, c *cli.Command) error { pipelines, err := pipelineList(c, mockClient) if tt.wantErr != nil { assert.EqualError(t, err, tt.wantErr.Error()) @@ -126,7 +92,7 @@ func TestPipelineList(t *testing.T) { return nil } - _ = command.Run(c, tt.args...) + _ = command.Run(context.Background(), tt.args) }) } } diff --git a/cli/log/log.go b/cli/pipeline/log/log.go similarity index 91% rename from cli/log/log.go rename to cli/pipeline/log/log.go index b9436c534..4227288dc 100644 --- a/cli/log/log.go +++ b/cli/pipeline/log/log.go @@ -15,14 +15,15 @@ package log import ( - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // Command exports the log command set. var Command = &cli.Command{ Name: "log", Usage: "manage logs", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ logPurgeCmd, + logShowCmd, }, } diff --git a/cli/log/log_purge.go b/cli/pipeline/log/log_purge.go similarity index 58% rename from cli/log/log_purge.go rename to cli/pipeline/log/log_purge.go index 8a62ac79d..80cd0d55f 100644 --- a/cli/log/log_purge.go +++ b/cli/pipeline/log/log_purge.go @@ -15,55 +15,64 @@ package log import ( + "context" "fmt" "strconv" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" ) var logPurgeCmd = &cli.Command{ Name: "purge", Usage: "purge a log", - ArgsUsage: " [step]", + ArgsUsage: " [step-number|step-name]", Action: logPurge, } -func logPurge(c *cli.Context) (err error) { - client, err := internal.NewClient(c) +func logPurge(ctx context.Context, c *cli.Command) (err error) { + client, err := internal.NewClient(ctx, c) if err != nil { return err } repoIDOrFullName := c.Args().First() + if len(repoIDOrFullName) == 0 { + return fmt.Errorf("missing required argument repo-id / repo-full-name") + } repoID, err := internal.ParseRepo(client, repoIDOrFullName) if err != nil { - return err + return fmt.Errorf("invalid repo '%s': %w", repoIDOrFullName, err) } - number, err := strconv.ParseInt(c.Args().Get(1), 10, 64) + + pipelineArg := c.Args().Get(1) + if len(pipelineArg) == 0 { + return fmt.Errorf("missing required argument pipeline") + } + number, err := strconv.ParseInt(pipelineArg, 10, 64) if err != nil { return err } stepArg := c.Args().Get(2) //nolint:mnd - // TODO: Add lookup by name: stepID, err := internal.ParseStep(client, repoID, stepIDOrName) var stepID int64 if len(stepArg) != 0 { - stepID, err = strconv.ParseInt(stepArg, 10, 64) + stepID, err = internal.ParseStep(client, repoID, number, stepArg) if err != nil { return err } } if stepID > 0 { + fmt.Printf("Purging logs for pipeline %s#%d step %d\n", repoIDOrFullName, number, stepID) err = client.StepLogsPurge(repoID, number, stepID) } else { + fmt.Printf("Purging logs for pipeline %s#%d\n", repoIDOrFullName, number) err = client.LogsPurge(repoID, number) } if err != nil { return err } - fmt.Printf("Purging logs for pipeline %s#%d\n", repoIDOrFullName, number) return nil } diff --git a/cli/pipeline/log/log_show.go b/cli/pipeline/log/log_show.go new file mode 100644 index 000000000..86931e31e --- /dev/null +++ b/cli/pipeline/log/log_show.go @@ -0,0 +1,112 @@ +// Copyright 2022 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package log + +import ( + "context" + "fmt" + "os" + "strconv" + "text/template" + + "github.com/urfave/cli/v3" + + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" +) + +var logShowCmd = &cli.Command{ + Name: "show", + Usage: "show pipeline logs", + ArgsUsage: " [step-number|step-name]", + Action: logShow, +} + +func logShow(ctx context.Context, c *cli.Command) error { + repoIDOrFullName := c.Args().First() + client, err := internal.NewClient(ctx, c) + if err != nil { + return err + } + if len(repoIDOrFullName) == 0 { + return fmt.Errorf("missing required argument repo-id / repo-full-name") + } + repoID, err := internal.ParseRepo(client, repoIDOrFullName) + if err != nil { + return fmt.Errorf("invalid repo '%s': %w ", repoIDOrFullName, err) + } + + pipelineArg := c.Args().Get(1) + if len(pipelineArg) == 0 { + return fmt.Errorf("missing required argument pipeline") + } + number, err := strconv.ParseInt(pipelineArg, 10, 64) + if err != nil { + return fmt.Errorf("invalid pipeline '%s': %w", pipelineArg, err) + } + + stepArg := c.Args().Get(2) //nolint:mnd + if len(stepArg) == 0 { + return pipelineLog(client, repoID, number) + } + + step, err := internal.ParseStep(client, repoID, number, stepArg) + if err != nil { + return fmt.Errorf("invalid step '%s': %w", stepArg, err) + } + return stepLog(client, repoID, number, step) +} + +func pipelineLog(client woodpecker.Client, repoID, number int64) error { + pipeline, err := client.Pipeline(repoID, number) + if err != nil { + return err + } + + tmpl, err := template.New("_").Parse(tmplPipelineLogs + "\n") + if err != nil { + return err + } + + for _, workflow := range pipeline.Workflows { + for _, step := range workflow.Children { + if err := tmpl.Execute(os.Stdout, map[string]any{"workflow": workflow, "step": step}); err != nil { + return err + } + err := stepLog(client, repoID, number, step.ID) + if err != nil { + return err + } + } + } + + return nil +} + +func stepLog(client woodpecker.Client, repoID, number, step int64) error { + logs, err := client.StepLogEntries(repoID, number, step) + if err != nil { + return err + } + + for _, log := range logs { + fmt.Println(string(log.Data)) + } + + return nil +} + +// template for pipeline ps information. +var tmplPipelineLogs = "\x1b[33m{{ .workflow.Name }} > {{ .step.Name }} (#{{ .step.PID }}):\x1b[0m" diff --git a/cli/pipeline/logs.go b/cli/pipeline/logs.go deleted file mode 100644 index 116d68a4d..000000000 --- a/cli/pipeline/logs.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2022 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package pipeline - -import ( - "fmt" - "strconv" - - "github.com/urfave/cli/v2" - - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" -) - -var pipelineLogsCmd = &cli.Command{ - Name: "logs", - Usage: "show pipeline logs", - ArgsUsage: " [pipeline] [stepID]", - Action: pipelineLogs, -} - -func pipelineLogs(c *cli.Context) error { - repoIDOrFullName := c.Args().First() - client, err := internal.NewClient(c) - if err != nil { - return err - } - repoID, err := internal.ParseRepo(client, repoIDOrFullName) - if err != nil { - return err - } - - numberArgIndex := 1 - number, err := strconv.ParseInt(c.Args().Get(numberArgIndex), 10, 64) - if err != nil { - return err - } - - stepArgIndex := 2 - step, err := strconv.ParseInt(c.Args().Get(stepArgIndex), 10, 64) - if err != nil { - return err - } - - logs, err := client.StepLogEntries(repoID, number, step) - if err != nil { - return err - } - - for _, log := range logs { - fmt.Print(string(log.Data)) - } - - return nil -} diff --git a/cli/pipeline/pipeline.go b/cli/pipeline/pipeline.go index 27044cac3..efcae8553 100644 --- a/cli/pipeline/pipeline.go +++ b/cli/pipeline/pipeline.go @@ -20,33 +20,37 @@ import ( "os" "text/template" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/output" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" + "go.woodpecker-ci.org/woodpecker/v3/cli/output" + "go.woodpecker-ci.org/woodpecker/v3/cli/pipeline/deploy" + "go.woodpecker-ci.org/woodpecker/v3/cli/pipeline/log" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) // Command exports the pipeline command set. var Command = &cli.Command{ Name: "pipeline", Usage: "manage pipelines", - Subcommands: []*cli.Command{ - pipelineListCmd, - pipelineLastCmd, - pipelineLogsCmd, - pipelineInfoCmd, - pipelineStopCmd, - pipelineStartCmd, + Commands: []*cli.Command{ pipelineApproveCmd, - pipelineDeclineCmd, - pipelineQueueCmd, - pipelineKillCmd, - pipelinePsCmd, pipelineCreateCmd, + pipelineDeclineCmd, + deploy.Command, + pipelineKillCmd, + pipelineLastCmd, + buildPipelineListCmd(), + log.Command, + pipelinePsCmd, + pipelinePurgeCmd, + pipelineQueueCmd, + pipelineShowCmd, + pipelineStartCmd, + pipelineStopCmd, }, } -func pipelineOutput(c *cli.Context, resources []woodpecker.Pipeline, fd ...io.Writer) error { +func pipelineOutput(c *cli.Command, pipelines []*woodpecker.Pipeline, fd ...io.Writer) error { outFmt, outOpt := output.ParseOutputOptions(c.String("output")) noHeader := c.Bool("output-no-headers") @@ -70,7 +74,7 @@ func pipelineOutput(c *cli.Context, resources []woodpecker.Pipeline, fd ...io.Wr if err != nil { return err } - if err := tmpl.Execute(out, resources); err != nil { + if err := tmpl.Execute(out, pipelines); err != nil { return err } case "table": @@ -85,7 +89,7 @@ func pipelineOutput(c *cli.Context, resources []woodpecker.Pipeline, fd ...io.Wr if !noHeader { table.WriteHeader(cols) } - for _, resource := range resources { + for _, resource := range pipelines { if err := table.Write(cols, resource); err != nil { return err } diff --git a/cli/pipeline/pipeline_test.go b/cli/pipeline/pipeline_test.go index 3ff2080ae..5238a19fe 100644 --- a/cli/pipeline/pipeline_test.go +++ b/cli/pipeline/pipeline_test.go @@ -2,14 +2,15 @@ package pipeline import ( "bytes" + "context" "io" "testing" "github.com/stretchr/testify/assert" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) func TestPipelineOutput(t *testing.T) { @@ -22,7 +23,7 @@ func TestPipelineOutput(t *testing.T) { { name: "table output with default columns", args: []string{}, - expected: "NUMBER STATUS EVENT BRANCH MESSAGE AUTHOR\n1 success push main message John Doe\n", + expected: "NUMBER STATUS EVENT BRANCH MESSAGE AUTHOR\n1 success push main message multiline John Doe\n", }, { name: "table output with custom columns", @@ -32,7 +33,7 @@ func TestPipelineOutput(t *testing.T) { { name: "table output with no header", args: []string{"output", "--output-no-headers"}, - expected: "1 success push main message John Doe\n", + expected: "1 success push main message multiline John Doe\n", }, { name: "go-template output", @@ -46,41 +47,40 @@ func TestPipelineOutput(t *testing.T) { }, } - pipelines := []woodpecker.Pipeline{ + pipelines := []*woodpecker.Pipeline{ { Number: 1, Status: "success", Event: "push", Branch: "main", - Message: "message", - Author: "John Doe", + Message: "message\nmultiline", + Author: "John Doe\n", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - app := &cli.App{Writer: io.Discard} - c := cli.NewContext(app, nil, nil) + command := &cli.Command{ + Writer: io.Discard, + Name: "output", + Flags: common.OutputFlags("table"), + Action: func(_ context.Context, c *cli.Command) error { + var buf bytes.Buffer + err := pipelineOutput(c, pipelines, &buf) - command := &cli.Command{} - command.Name = "output" - command.Flags = common.OutputFlags("table") - command.Action = func(c *cli.Context) error { - var buf bytes.Buffer - err := pipelineOutput(c, pipelines, &buf) + if tt.wantErr { + assert.Error(t, err) + return nil + } + + assert.NoError(t, err) + assert.Equal(t, tt.expected, buf.String()) - if tt.wantErr { - assert.Error(t, err) return nil - } - - assert.NoError(t, err) - assert.Equal(t, tt.expected, buf.String()) - - return nil + }, } - _ = command.Run(c, tt.args...) + _ = command.Run(context.Background(), tt.args) }) } } diff --git a/cli/pipeline/ps.go b/cli/pipeline/ps.go index bba6f4043..f2836d7c3 100644 --- a/cli/pipeline/ps.go +++ b/cli/pipeline/ps.go @@ -15,33 +15,36 @@ package pipeline import ( + "context" + "fmt" "os" "strconv" "text/template" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) var pipelinePsCmd = &cli.Command{ Name: "ps", Usage: "show pipeline steps", - ArgsUsage: " [pipeline]", + ArgsUsage: " ", Action: pipelinePs, Flags: []cli.Flag{common.FormatFlag(tmplPipelinePs)}, } -func pipelinePs(c *cli.Context) error { +func pipelinePs(ctx context.Context, c *cli.Command) error { repoIDOrFullName := c.Args().First() - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } repoID, err := internal.ParseRepo(client, repoIDOrFullName) if err != nil { - return err + return fmt.Errorf("invalid repo '%s': %w", repoIDOrFullName, err) } pipelineArg := c.Args().Get(1) @@ -49,7 +52,7 @@ func pipelinePs(c *cli.Context) error { if pipelineArg == "last" || len(pipelineArg) == 0 { // Fetch the pipeline number from the last pipeline - pipeline, err := client.PipelineLast(repoID, "") + pipeline, err := client.PipelineLast(repoID, woodpecker.PipelineLastOptions{}) if err != nil { return err } @@ -58,7 +61,7 @@ func pipelinePs(c *cli.Context) error { } else { number, err = strconv.ParseInt(pipelineArg, 10, 64) if err != nil { - return err + return fmt.Errorf("invalid pipeline '%s': %w", pipelineArg, err) } } @@ -72,9 +75,9 @@ func pipelinePs(c *cli.Context) error { return err } - for _, step := range pipeline.Workflows { - for _, child := range step.Children { - if err := tmpl.Execute(os.Stdout, child); err != nil { + for _, workflow := range pipeline.Workflows { + for _, step := range workflow.Children { + if err := tmpl.Execute(os.Stdout, map[string]any{"workflow": workflow, "step": step}); err != nil { return err } } @@ -83,8 +86,11 @@ func pipelinePs(c *cli.Context) error { return nil } -// Template for pipeline ps information. -var tmplPipelinePs = "\x1b[33mStep #{{ .PID }} \x1b[0m" + ` -Step: {{ .Name }} -State: {{ .State }} +// template for pipeline ps information. +var tmplPipelinePs = "\x1b[33m{{ .workflow.Name }} > {{ .step.Name }} (#{{ .step.PID }}):\x1b[0m" + ` +Step: {{ .step.Name }} +Started: {{ .step.Started }} +Stopped: {{ .step.Stopped }} +Type: {{ .step.Type }} +State: {{ .step.State }} ` diff --git a/cli/pipeline/purge.go b/cli/pipeline/purge.go new file mode 100644 index 000000000..0e22537f3 --- /dev/null +++ b/cli/pipeline/purge.go @@ -0,0 +1,164 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pipeline + +import ( + "context" + "errors" + "fmt" + "net/http" + "time" + + "github.com/rs/zerolog/log" + "github.com/urfave/cli/v3" + + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + shared_utils "go.woodpecker-ci.org/woodpecker/v3/shared/utils" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" +) + +//nolint:mnd +var pipelinePurgeCmd = &cli.Command{ + Name: "purge", + Usage: "purge pipelines", + ArgsUsage: "", + Action: Purge, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "older-than", + Usage: "remove pipelines older than the specified time limit", + Required: true, + }, + &cli.IntFlag{ + Name: "keep-min", + Usage: "minimum number of pipelines to keep", + Value: 10, + }, + &cli.BoolFlag{ + Name: "dry-run", + Usage: "disable non-read api calls", + Value: false, + }, + }, +} + +func Purge(ctx context.Context, c *cli.Command) error { + client, err := internal.NewClient(ctx, c) + if err != nil { + return err + } + return pipelinePurge(c, client) +} + +func pipelinePurge(c *cli.Command, client woodpecker.Client) (err error) { + repoIDOrFullName := c.Args().First() + if len(repoIDOrFullName) == 0 { + return fmt.Errorf("missing required argument repo-id / repo-full-name") + } + repoID, err := internal.ParseRepo(client, repoIDOrFullName) + if err != nil { + return fmt.Errorf("invalid repo '%s': %w", repoIDOrFullName, err) + } + + olderThan := c.String("older-than") + keepMin := c.Int("keep-min") + dryRun := c.Bool("dry-run") + + duration, err := time.ParseDuration(olderThan) + if err != nil { + return err + } + + var pipelinesKeep []*woodpecker.Pipeline + + if keepMin > 0 { + pipelinesKeep, err = fetchPipelinesToKeep(client, repoID, int(keepMin)) + if err != nil { + return err + } + } + + pipelines, err := fetchPipelines(client, repoID, duration) + if err != nil { + return err + } + + // Create a map of pipeline IDs to keep + keepMap := make(map[int64]struct{}) + for _, p := range pipelinesKeep { + keepMap[p.Number] = struct{}{} + } + + // Filter pipelines to only include those not in keepMap + var pipelinesToPurge []*woodpecker.Pipeline + for _, p := range pipelines { + if _, exists := keepMap[p.Number]; !exists { + pipelinesToPurge = append(pipelinesToPurge, p) + } + } + + msgPrefix := "" + if dryRun { + msgPrefix = "DRY-RUN: " + } + + for i, p := range pipelinesToPurge { + // cspell:words spurge + log.Debug().Msgf("%spurge %v/%v pipelines from repo '%v' (pipeline %v)", msgPrefix, i+1, len(pipelinesToPurge), repoIDOrFullName, p.Number) + if dryRun { + continue + } + + err := client.PipelineDelete(repoID, p.Number) + if err != nil { + var clientErr *woodpecker.ClientError + if errors.As(err, &clientErr) && clientErr.StatusCode == http.StatusUnprocessableEntity { + log.Error().Err(err).Msgf("failed to delete pipeline %d", p.Number) + continue + } + return err + } + } + + return nil +} + +func fetchPipelinesToKeep(client woodpecker.Client, repoID int64, keepMin int) ([]*woodpecker.Pipeline, error) { + if keepMin <= 0 { + return nil, nil + } + return shared_utils.Paginate(func(page int) ([]*woodpecker.Pipeline, error) { + return client.PipelineList(repoID, + woodpecker.PipelineListOptions{ + ListOptions: woodpecker.ListOptions{ + Page: page, + }, + }, + ) + }, keepMin) +} + +func fetchPipelines(client woodpecker.Client, repoID int64, duration time.Duration) ([]*woodpecker.Pipeline, error) { + return shared_utils.Paginate(func(page int) ([]*woodpecker.Pipeline, error) { + return client.PipelineList(repoID, + woodpecker.PipelineListOptions{ + ListOptions: woodpecker.ListOptions{ + Page: page, + }, + Before: time.Now().Add(-duration), + }, + ) + }, -1) +} diff --git a/cli/pipeline/purge_test.go b/cli/pipeline/purge_test.go new file mode 100644 index 000000000..b6e285492 --- /dev/null +++ b/cli/pipeline/purge_test.go @@ -0,0 +1,126 @@ +package pipeline + +import ( + "context" + "errors" + "io" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/urfave/cli/v3" + + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker/mocks" +) + +func TestPipelinePurge(t *testing.T) { + tests := []struct { + name string + repoID int64 + args []string + pipelinesKeep []*woodpecker.Pipeline + pipelines []*woodpecker.Pipeline + mockDeleteError error + wantDelete int + wantErr error + }{ + { + name: "success with no pipelines to purge", + repoID: 1, + args: []string{"purge", "--older-than", "1h", "repo/name"}, + pipelinesKeep: []*woodpecker.Pipeline{ + {Number: 1}, + }, + pipelines: []*woodpecker.Pipeline{}, + }, + { + name: "success with pipelines to purge", + repoID: 1, + args: []string{"purge", "--older-than", "1h", "repo/name"}, + pipelinesKeep: []*woodpecker.Pipeline{ + {Number: 1}, + }, + pipelines: []*woodpecker.Pipeline{ + {Number: 1}, + {Number: 2}, + {Number: 3}, + }, + wantDelete: 2, + }, + { + name: "error on invalid duration", + repoID: 1, + args: []string{"purge", "--older-than", "invalid", "repo/name"}, + wantErr: errors.New("time: invalid duration \"invalid\""), + }, + { + name: "continue on 422 error", + repoID: 1, + args: []string{"purge", "--older-than", "1h", "repo/name"}, + pipelinesKeep: []*woodpecker.Pipeline{ + {Number: 1}, + }, + pipelines: []*woodpecker.Pipeline{ + {Number: 1}, + {Number: 2}, + {Number: 3}, + }, + wantDelete: 2, + mockDeleteError: &woodpecker.ClientError{ + StatusCode: 422, + Message: "test error", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockClient := mocks.NewClient(t) + mockClient.On("RepoLookup", mock.Anything).Maybe().Return(&woodpecker.Repo{ID: tt.repoID}, nil) + + mockClient.On("PipelineList", mock.Anything, mock.Anything).Return(func(_ int64, opt woodpecker.PipelineListOptions) ([]*woodpecker.Pipeline, error) { + // Return keep pipelines for first call + if opt.Before.IsZero() { + if opt.Page == 1 { + return tt.pipelinesKeep, nil + } + return []*woodpecker.Pipeline{}, nil + } + + // Return pipelines to purge for calls with Before filter + if !opt.Before.IsZero() { + if opt.Page == 1 { + return tt.pipelines, nil + } + return []*woodpecker.Pipeline{}, nil + } + + return []*woodpecker.Pipeline{}, nil + }).Maybe() + + if tt.mockDeleteError != nil { + mockClient.On("PipelineDelete", tt.repoID, mock.Anything).Return(tt.mockDeleteError) + } else if tt.wantDelete > 0 { + mockClient.On("PipelineDelete", tt.repoID, mock.Anything).Return(nil).Times(tt.wantDelete) + } + + command := pipelinePurgeCmd + command.Writer = io.Discard + command.Action = func(_ context.Context, c *cli.Command) error { + err := pipelinePurge(c, mockClient) + + if tt.wantErr != nil { + assert.EqualError(t, err, tt.wantErr.Error()) + return nil + } + + assert.NoError(t, err) + + return nil + } + + _ = command.Run(context.Background(), tt.args) + }) + } +} diff --git a/cli/pipeline/queue.go b/cli/pipeline/queue.go index 1fbccd8a3..239179456 100644 --- a/cli/pipeline/queue.go +++ b/cli/pipeline/queue.go @@ -15,14 +15,15 @@ package pipeline import ( + "context" "fmt" "os" "text/template" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" ) var pipelineQueueCmd = &cli.Command{ @@ -33,8 +34,8 @@ var pipelineQueueCmd = &cli.Command{ Flags: []cli.Flag{common.FormatFlag(tmplPipelineQueue)}, } -func pipelineQueue(c *cli.Context) error { - client, err := internal.NewClient(c) +func pipelineQueue(ctx context.Context, c *cli.Command) error { + client, err := internal.NewClient(ctx, c) if err != nil { return err } diff --git a/cli/pipeline/info.go b/cli/pipeline/show.go similarity index 69% rename from cli/pipeline/info.go rename to cli/pipeline/show.go index 91cae4743..2b9a25d4a 100644 --- a/cli/pipeline/info.go +++ b/cli/pipeline/show.go @@ -15,26 +15,27 @@ package pipeline import ( + "context" "strconv" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) -var pipelineInfoCmd = &cli.Command{ - Name: "info", - Usage: "show pipeline details", +var pipelineShowCmd = &cli.Command{ + Name: "show", + Usage: "show pipeline information", ArgsUsage: " [pipeline]", - Action: pipelineInfo, + Action: pipelineShow, Flags: common.OutputFlags("table"), } -func pipelineInfo(c *cli.Context) error { +func pipelineShow(ctx context.Context, c *cli.Command) error { repoIDOrFullName := c.Args().First() - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } @@ -47,7 +48,7 @@ func pipelineInfo(c *cli.Context) error { var number int64 if pipelineArg == "last" || len(pipelineArg) == 0 { // Fetch the pipeline number from the last pipeline - pipeline, err := client.PipelineLast(repoID, "") + pipeline, err := client.PipelineLast(repoID, woodpecker.PipelineLastOptions{}) if err != nil { return err } @@ -64,5 +65,5 @@ func pipelineInfo(c *cli.Context) error { return err } - return pipelineOutput(c, []woodpecker.Pipeline{*pipeline}) + return pipelineOutput(c, []*woodpecker.Pipeline{pipeline}) } diff --git a/cli/pipeline/start.go b/cli/pipeline/start.go index c3d870982..694295fae 100644 --- a/cli/pipeline/start.go +++ b/cli/pipeline/start.go @@ -15,13 +15,15 @@ package pipeline import ( + "context" "errors" "fmt" "strconv" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) var pipelineStartCmd = &cli.Command{ @@ -33,14 +35,14 @@ var pipelineStartCmd = &cli.Command{ &cli.StringSliceFlag{ Name: "param", Aliases: []string{"p"}, - Usage: "custom parameters to be injected into the step environment. Format: KEY=value", + Usage: "custom parameters to inject into the step environment. Format: KEY=value", }, }, } -func pipelineStart(c *cli.Context) (err error) { +func pipelineStart(ctx context.Context, c *cli.Command) (err error) { repoIDOrFullName := c.Args().First() - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } @@ -53,7 +55,7 @@ func pipelineStart(c *cli.Context) (err error) { var number int64 if pipelineArg == "last" { // Fetch the pipeline number from the last pipeline - pipeline, err := client.PipelineLast(repoID, "") + pipeline, err := client.PipelineLast(repoID, woodpecker.PipelineLastOptions{}) if err != nil { return err } @@ -68,9 +70,11 @@ func pipelineStart(c *cli.Context) (err error) { } } - params := internal.ParseKeyPair(c.StringSlice("param")) + opt := woodpecker.PipelineStartOptions{ + Params: internal.ParseKeyPair(c.StringSlice("param")), + } - pipeline, err := client.PipelineStart(repoID, number, params) + pipeline, err := client.PipelineStart(repoID, number, opt) if err != nil { return err } diff --git a/cli/pipeline/stop.go b/cli/pipeline/stop.go index 4d949f9cb..af2fa7b4d 100644 --- a/cli/pipeline/stop.go +++ b/cli/pipeline/stop.go @@ -15,12 +15,13 @@ package pipeline import ( + "context" "fmt" "strconv" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" ) var pipelineStopCmd = &cli.Command{ @@ -30,9 +31,9 @@ var pipelineStopCmd = &cli.Command{ Action: pipelineStop, } -func pipelineStop(c *cli.Context) (err error) { +func pipelineStop(ctx context.Context, c *cli.Command) (err error) { repoIDOrFullName := c.Args().First() - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } diff --git a/cli/cron/cron.go b/cli/repo/cron/cron.go similarity index 91% rename from cli/cron/cron.go rename to cli/repo/cron/cron.go index 97b9ac43c..90edf8f9c 100644 --- a/cli/cron/cron.go +++ b/cli/repo/cron/cron.go @@ -15,18 +15,18 @@ package cron import ( - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // Command exports the cron command set. var Command = &cli.Command{ Name: "cron", Usage: "manage cron jobs", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ cronCreateCmd, cronDeleteCmd, - cronUpdateCmd, - cronInfoCmd, cronListCmd, + cronShowCmd, + cronUpdateCmd, }, } diff --git a/cli/cron/cron_add.go b/cli/repo/cron/cron_add.go similarity index 83% rename from cli/cron/cron_add.go rename to cli/repo/cron/cron_add.go index 2eb5eddd4..df86ef483 100644 --- a/cli/cron/cron_add.go +++ b/cli/repo/cron/cron_add.go @@ -15,14 +15,15 @@ package cron import ( + "context" "html/template" "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) var cronCreateCmd = &cli.Command{ @@ -50,9 +51,9 @@ var cronCreateCmd = &cli.Command{ }, } -func cronCreate(c *cli.Context) error { +func cronCreate(ctx context.Context, c *cli.Command) error { var ( - jobName = c.String("name") + cronName = c.String("name") branch = c.String("branch") schedule = c.String("schedule") repoIDOrFullName = c.String("repository") @@ -62,7 +63,7 @@ func cronCreate(c *cli.Context) error { repoIDOrFullName = c.Args().First() } - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } @@ -73,7 +74,7 @@ func cronCreate(c *cli.Context) error { } cron := &woodpecker.Cron{ - Name: jobName, + Name: cronName, Branch: branch, Schedule: schedule, } diff --git a/cli/cron/cron_list.go b/cli/repo/cron/cron_list.go similarity index 80% rename from cli/cron/cron_list.go rename to cli/repo/cron/cron_list.go index edd6c88d3..45b1d4fb4 100644 --- a/cli/cron/cron_list.go +++ b/cli/repo/cron/cron_list.go @@ -15,13 +15,15 @@ package cron import ( + "context" "html/template" "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) var cronListCmd = &cli.Command{ @@ -35,7 +37,7 @@ var cronListCmd = &cli.Command{ }, } -func cronList(c *cli.Context) error { +func cronList(ctx context.Context, c *cli.Command) error { var ( format = c.String("format") + "\n" repoIDOrFullName = c.String("repository") @@ -43,7 +45,7 @@ func cronList(c *cli.Context) error { if repoIDOrFullName == "" { repoIDOrFullName = c.Args().First() } - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } @@ -51,7 +53,8 @@ func cronList(c *cli.Context) error { if err != nil { return err } - list, err := client.CronList(repoID) + opt := woodpecker.CronListOptions{} + list, err := client.CronList(repoID, opt) if err != nil { return err } diff --git a/cli/cron/cron_rm.go b/cli/repo/cron/cron_rm.go similarity index 79% rename from cli/cron/cron_rm.go rename to cli/repo/cron/cron_rm.go index 0e6fbc4c0..89a6fcb7b 100644 --- a/cli/cron/cron_rm.go +++ b/cli/repo/cron/cron_rm.go @@ -15,12 +15,13 @@ package cron import ( + "context" "fmt" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" ) var cronDeleteCmd = &cli.Command{ @@ -38,15 +39,15 @@ var cronDeleteCmd = &cli.Command{ }, } -func cronDelete(c *cli.Context) error { +func cronDelete(ctx context.Context, c *cli.Command) error { var ( - jobID = c.Int64("id") + cronID = c.Int("id") repoIDOrFullName = c.String("repository") ) if repoIDOrFullName == "" { repoIDOrFullName = c.Args().First() } - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } @@ -54,7 +55,7 @@ func cronDelete(c *cli.Context) error { if err != nil { return err } - err = client.CronDelete(repoID, jobID) + err = client.CronDelete(repoID, cronID) if err != nil { return err } diff --git a/cli/cron/cron_info.go b/cli/repo/cron/cron_show.go similarity index 75% rename from cli/cron/cron_info.go rename to cli/repo/cron/cron_show.go index fd6f16bc1..c2bc9c36b 100644 --- a/cli/cron/cron_info.go +++ b/cli/repo/cron/cron_show.go @@ -15,20 +15,21 @@ package cron import ( + "context" "html/template" "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" ) -var cronInfoCmd = &cli.Command{ - Name: "info", - Usage: "display info about a cron job", +var cronShowCmd = &cli.Command{ + Name: "show", + Usage: "show cron job information", ArgsUsage: "[repo-id|repo-full-name]", - Action: cronInfo, + Action: cronShow, Flags: []cli.Flag{ common.RepoFlag, &cli.StringFlag{ @@ -40,16 +41,16 @@ var cronInfoCmd = &cli.Command{ }, } -func cronInfo(c *cli.Context) error { +func cronShow(ctx context.Context, c *cli.Command) error { var ( - jobID = c.Int64("id") + cronID = c.Int("id") repoIDOrFullName = c.String("repository") format = c.String("format") + "\n" ) if repoIDOrFullName == "" { repoIDOrFullName = c.Args().First() } - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } @@ -58,7 +59,7 @@ func cronInfo(c *cli.Context) error { return err } - cron, err := client.CronGet(repoID, jobID) + cron, err := client.CronGet(repoID, cronID) if err != nil { return err } diff --git a/cli/cron/cron_update.go b/cli/repo/cron/cron_update.go similarity index 84% rename from cli/cron/cron_update.go rename to cli/repo/cron/cron_update.go index 0c25f03f7..987505b84 100644 --- a/cli/cron/cron_update.go +++ b/cli/repo/cron/cron_update.go @@ -15,14 +15,15 @@ package cron import ( + "context" "html/template" "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) var cronUpdateCmd = &cli.Command{ @@ -53,10 +54,10 @@ var cronUpdateCmd = &cli.Command{ }, } -func cronUpdate(c *cli.Context) error { +func cronUpdate(ctx context.Context, c *cli.Command) error { var ( repoIDOrFullName = c.String("repository") - jobID = c.Int64("id") + cronID = c.Int("id") jobName = c.String("name") branch = c.String("branch") schedule = c.String("schedule") @@ -65,7 +66,7 @@ func cronUpdate(c *cli.Context) error { if repoIDOrFullName == "" { repoIDOrFullName = c.Args().First() } - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } @@ -74,7 +75,7 @@ func cronUpdate(c *cli.Context) error { return err } cron := &woodpecker.Cron{ - ID: jobID, + ID: cronID, Name: jobName, Branch: branch, Schedule: schedule, diff --git a/cli/repo/registry/registry.go b/cli/repo/registry/registry.go index 29c8d35fa..475d7564f 100644 --- a/cli/repo/registry/registry.go +++ b/cli/repo/registry/registry.go @@ -15,26 +15,26 @@ package registry import ( - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) // Command exports the registry command set. var Command = &cli.Command{ Name: "registry", Usage: "manage registries", - Subcommands: []*cli.Command{ + Commands: []*cli.Command{ registryCreateCmd, registryDeleteCmd, - registryUpdateCmd, - registryInfoCmd, registryListCmd, + registryShowCmd, + registryUpdateCmd, }, } -func parseTargetArgs(client woodpecker.Client, c *cli.Context) (repoID int64, err error) { +func parseTargetArgs(client woodpecker.Client, c *cli.Command) (repoID int64, err error) { repoIDOrFullName := c.String("repository") if repoIDOrFullName == "" { repoIDOrFullName = c.Args().First() diff --git a/cli/repo/registry/registry_add.go b/cli/repo/registry/registry_add.go index dae231048..87202244b 100644 --- a/cli/repo/registry/registry_add.go +++ b/cli/repo/registry/registry_add.go @@ -15,19 +15,20 @@ package registry import ( + "context" "os" "strings" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) var registryCreateCmd = &cli.Command{ Name: "add", - Usage: "adds a registry", + Usage: "add a registry", ArgsUsage: "[repo-id|repo-full-name]", Action: registryCreate, Flags: []cli.Flag{ @@ -48,14 +49,14 @@ var registryCreateCmd = &cli.Command{ }, } -func registryCreate(c *cli.Context) error { +func registryCreate(ctx context.Context, c *cli.Command) error { var ( hostname = c.String("hostname") username = c.String("username") password = c.String("password") ) - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } diff --git a/cli/repo/registry/registry_list.go b/cli/repo/registry/registry_list.go index cd0a89a6b..8d41457b3 100644 --- a/cli/repo/registry/registry_list.go +++ b/cli/repo/registry/registry_list.go @@ -15,13 +15,15 @@ package registry import ( + "context" "html/template" "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) var registryListCmd = &cli.Command{ @@ -35,10 +37,10 @@ var registryListCmd = &cli.Command{ }, } -func registryList(c *cli.Context) error { +func registryList(ctx context.Context, c *cli.Command) error { format := c.String("format") + "\n" - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } @@ -48,7 +50,9 @@ func registryList(c *cli.Context) error { return err } - list, err := client.RegistryList(repoID) + opt := woodpecker.RegistryListOptions{} + + list, err := client.RegistryList(repoID, opt) if err != nil { return err } diff --git a/cli/repo/registry/registry_rm.go b/cli/repo/registry/registry_rm.go index d1b058a17..94613e8d0 100644 --- a/cli/repo/registry/registry_rm.go +++ b/cli/repo/registry/registry_rm.go @@ -15,10 +15,12 @@ package registry import ( - "github.com/urfave/cli/v2" + "context" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "github.com/urfave/cli/v3" + + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" ) var registryDeleteCmd = &cli.Command{ @@ -36,10 +38,10 @@ var registryDeleteCmd = &cli.Command{ }, } -func registryDelete(c *cli.Context) error { +func registryDelete(ctx context.Context, c *cli.Command) error { hostname := c.String("hostname") - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } diff --git a/cli/repo/registry/registry_set.go b/cli/repo/registry/registry_set.go index 5b6497f49..131269f9c 100644 --- a/cli/repo/registry/registry_set.go +++ b/cli/repo/registry/registry_set.go @@ -15,14 +15,15 @@ package registry import ( + "context" "os" "strings" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) var registryUpdateCmd = &cli.Command{ @@ -48,14 +49,14 @@ var registryUpdateCmd = &cli.Command{ }, } -func registryUpdate(c *cli.Context) error { +func registryUpdate(ctx context.Context, c *cli.Command) error { var ( hostname = c.String("hostname") username = c.String("username") password = c.String("password") ) - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } diff --git a/cli/repo/registry/registry_info.go b/cli/repo/registry/registry_show.go similarity index 77% rename from cli/repo/registry/registry_info.go rename to cli/repo/registry/registry_show.go index 1f984c415..b3b7869a5 100644 --- a/cli/repo/registry/registry_info.go +++ b/cli/repo/registry/registry_show.go @@ -15,20 +15,21 @@ package registry import ( + "context" "html/template" "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" ) -var registryInfoCmd = &cli.Command{ - Name: "info", - Usage: "display registry info", +var registryShowCmd = &cli.Command{ + Name: "show", + Usage: "show registry information", ArgsUsage: "[repo-id|repo-full-name]", - Action: registryInfo, + Action: registryShow, Flags: []cli.Flag{ common.RepoFlag, &cli.StringFlag{ @@ -40,13 +41,13 @@ var registryInfoCmd = &cli.Command{ }, } -func registryInfo(c *cli.Context) error { +func registryShow(ctx context.Context, c *cli.Command) error { var ( hostname = c.String("hostname") format = c.String("format") + "\n" ) - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } diff --git a/cli/repo/repo.go b/cli/repo/repo.go index ea42c5616..f64f6a280 100644 --- a/cli/repo/repo.go +++ b/cli/repo/repo.go @@ -15,24 +15,28 @@ package repo import ( - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/repo/registry" + "go.woodpecker-ci.org/woodpecker/v3/cli/repo/cron" + "go.woodpecker-ci.org/woodpecker/v3/cli/repo/registry" + "go.woodpecker-ci.org/woodpecker/v3/cli/repo/secret" ) // Command exports the repository command. var Command = &cli.Command{ Name: "repo", Usage: "manage repositories", - Subcommands: []*cli.Command{ - repoListCmd, - repoInfoCmd, + Commands: []*cli.Command{ repoAddCmd, - repoUpdateCmd, + repoChownCmd, + cron.Command, + repoListCmd, + registry.Command, repoRemoveCmd, repoRepairCmd, - repoChownCmd, + secret.Command, + repoShowCmd, repoSyncCmd, - registry.Command, + repoUpdateCmd, }, } diff --git a/cli/repo/repo_add.go b/cli/repo/repo_add.go index 00094b40c..c9cd60a73 100644 --- a/cli/repo/repo_add.go +++ b/cli/repo/repo_add.go @@ -15,12 +15,14 @@ package repo import ( + "context" "fmt" "strconv" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) var repoAddCmd = &cli.Command{ @@ -30,19 +32,23 @@ var repoAddCmd = &cli.Command{ Action: repoAdd, } -func repoAdd(c *cli.Context) error { +func repoAdd(ctx context.Context, c *cli.Command) error { _forgeRemoteID := c.Args().First() forgeRemoteID, err := strconv.Atoi(_forgeRemoteID) if err != nil { return fmt.Errorf("invalid forge remote id: %s", _forgeRemoteID) } - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } - repo, err := client.RepoPost(int64(forgeRemoteID)) + opt := woodpecker.RepoPostOptions{ + ForgeRemoteID: int64(forgeRemoteID), + } + + repo, err := client.RepoPost(opt) if err != nil { return err } diff --git a/cli/repo/repo_chown.go b/cli/repo/repo_chown.go index 050a3975a..f294ab277 100644 --- a/cli/repo/repo_chown.go +++ b/cli/repo/repo_chown.go @@ -15,11 +15,12 @@ package repo import ( + "context" "fmt" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" ) var repoChownCmd = &cli.Command{ @@ -29,9 +30,9 @@ var repoChownCmd = &cli.Command{ Action: repoChown, } -func repoChown(c *cli.Context) error { +func repoChown(ctx context.Context, c *cli.Command) error { repoIDOrFullName := c.Args().First() - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } diff --git a/cli/repo/repo_list.go b/cli/repo/repo_list.go index 9aa569338..92e5b73d5 100644 --- a/cli/repo/repo_list.go +++ b/cli/repo/repo_list.go @@ -15,13 +15,15 @@ package repo import ( + "context" "os" "text/template" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) var repoListCmd = &cli.Command{ @@ -35,16 +37,24 @@ var repoListCmd = &cli.Command{ Name: "org", Usage: "filter by organization", }, + &cli.BoolFlag{ + Name: "all", + Usage: "query all repos, including inactive ones", + }, }, } -func repoList(c *cli.Context) error { - client, err := internal.NewClient(c) +func repoList(ctx context.Context, c *cli.Command) error { + client, err := internal.NewClient(ctx, c) if err != nil { return err } - repos, err := client.RepoList() + opt := woodpecker.RepoListOptions{ + All: c.Bool("all"), + } + + repos, err := client.RepoList(opt) if err != nil || len(repos) == 0 { return err } @@ -67,4 +77,4 @@ func repoList(c *cli.Context) error { } // Template for repository list items. -var tmplRepoList = "\x1b[33m{{ .FullName }}\x1b[0m (id: {{ .ID }}, forgeRemoteID: {{ .ForgeRemoteID }})" +var tmplRepoList = "\x1b[33m{{ .FullName }}\x1b[0m (id: {{ .ID }}, forgeRemoteID: {{ .ForgeRemoteID }}, isActive: {{ .IsActive }})" diff --git a/cli/repo/repo_repair.go b/cli/repo/repo_repair.go index 15ab3157f..850d97357 100644 --- a/cli/repo/repo_repair.go +++ b/cli/repo/repo_repair.go @@ -15,11 +15,12 @@ package repo import ( + "context" "fmt" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" ) var repoRepairCmd = &cli.Command{ @@ -29,9 +30,9 @@ var repoRepairCmd = &cli.Command{ Action: repoRepair, } -func repoRepair(c *cli.Context) error { +func repoRepair(ctx context.Context, c *cli.Command) error { repoIDOrFullName := c.Args().First() - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } diff --git a/cli/repo/repo_rm.go b/cli/repo/repo_rm.go index 5603cd336..0b56c70e7 100644 --- a/cli/repo/repo_rm.go +++ b/cli/repo/repo_rm.go @@ -15,11 +15,12 @@ package repo import ( + "context" "fmt" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" ) var repoRemoveCmd = &cli.Command{ @@ -29,9 +30,9 @@ var repoRemoveCmd = &cli.Command{ Action: repoRemove, } -func repoRemove(c *cli.Context) error { +func repoRemove(ctx context.Context, c *cli.Command) error { repoIDOrFullName := c.Args().First() - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } diff --git a/cli/repo/repo_info.go b/cli/repo/repo_show.go similarity index 78% rename from cli/repo/repo_info.go rename to cli/repo/repo_show.go index 72d4bf7a6..c9c9ce87c 100644 --- a/cli/repo/repo_info.go +++ b/cli/repo/repo_show.go @@ -15,26 +15,27 @@ package repo import ( + "context" "os" "text/template" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" ) -var repoInfoCmd = &cli.Command{ - Name: "info", - Usage: "show repository details", +var repoShowCmd = &cli.Command{ + Name: "show", + Usage: "show repository information", ArgsUsage: "", - Action: repoInfo, + Action: repoShow, Flags: []cli.Flag{common.FormatFlag(tmplRepoInfo)}, } -func repoInfo(c *cli.Context) error { +func repoShow(ctx context.Context, c *cli.Command) error { repoIDOrFullName := c.Args().First() - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } @@ -64,6 +65,7 @@ Visibility: {{ .Visibility }} Private: {{ .IsSCMPrivate }} Trusted: {{ .IsTrusted }} Gated: {{ .IsGated }} +Require approval for: {{ .RequireApproval }} Clone url: {{ .Clone }} Allow pull-requests: {{ .AllowPullRequests }} ` diff --git a/cli/repo/repo_sync.go b/cli/repo/repo_sync.go index 14f13401f..d7802c92e 100644 --- a/cli/repo/repo_sync.go +++ b/cli/repo/repo_sync.go @@ -15,13 +15,15 @@ package repo import ( + "context" "os" "text/template" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) var repoSyncCmd = &cli.Command{ @@ -33,13 +35,17 @@ var repoSyncCmd = &cli.Command{ } // TODO: remove this and add an option to the list cmd as we do not store the remote repo list anymore -func repoSync(c *cli.Context) error { - client, err := internal.NewClient(c) +func repoSync(ctx context.Context, c *cli.Command) error { + client, err := internal.NewClient(ctx, c) if err != nil { return err } - repos, err := client.RepoListOpts(true) + opt := woodpecker.RepoListOptions{ + All: true, + } + + repos, err := client.RepoList(opt) if err != nil || len(repos) == 0 { return err } diff --git a/cli/repo/repo_update.go b/cli/repo/repo_update.go index 51999bc76..dd28e3d28 100644 --- a/cli/repo/repo_update.go +++ b/cli/repo/repo_update.go @@ -15,13 +15,14 @@ package repo import ( + "context" "fmt" "time" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) var repoUpdateCmd = &cli.Command{ @@ -35,8 +36,12 @@ var repoUpdateCmd = &cli.Command{ Usage: "repository is trusted", }, &cli.BoolFlag{ - Name: "gated", - Usage: "repository is gated", + Name: "gated", // TODO: remove in next release + Hidden: true, + }, + &cli.StringFlag{ + Name: "require-approval", + Usage: "repository requires approval for", }, &cli.DurationFlag{ Name: "timeout", @@ -48,7 +53,7 @@ var repoUpdateCmd = &cli.Command{ }, &cli.StringFlag{ Name: "config", - Usage: "repository configuration path (e.g. .woodpecker.yml)", + Usage: "repository configuration path. Example: .woodpecker.yml", }, &cli.IntFlag{ Name: "pipeline-counter", @@ -56,14 +61,14 @@ var repoUpdateCmd = &cli.Command{ }, &cli.BoolFlag{ Name: "unsafe", - Usage: "validate updating the pipeline-counter is unsafe", + Usage: "allow unsafe operations", }, }, } -func repoUpdate(c *cli.Context) error { +func repoUpdate(ctx context.Context, c *cli.Command) error { repoIDOrFullName := c.Args().First() - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } @@ -77,8 +82,8 @@ func repoUpdate(c *cli.Context) error { config = c.String("config") timeout = c.Duration("timeout") trusted = c.Bool("trusted") - gated = c.Bool("gated") - pipelineCounter = c.Int("pipeline-counter") + requireApproval = c.String("require-approval") + pipelineCounter = int(c.Int("pipeline-counter")) unsafe = c.Bool("unsafe") ) @@ -86,8 +91,18 @@ func repoUpdate(c *cli.Context) error { if c.IsSet("trusted") { patch.IsTrusted = &trusted } + + // TODO: remove in next release if c.IsSet("gated") { - patch.IsGated = &gated + return fmt.Errorf("'gated' option has been set in version 2.8, use 'require-approval' in >= 3.0") + } + + if c.IsSet("require-approval") { + if mode := woodpecker.ApprovalMode(requireApproval); mode.Valid() { + patch.RequireApproval = &mode + } else { + return fmt.Errorf("update approval mode failed: '%s' is no valid mode", mode) + } } if c.IsSet("timeout") { v := int64(timeout / time.Minute) diff --git a/cli/repo/secret/secret.go b/cli/repo/secret/secret.go new file mode 100644 index 000000000..4bdbf674b --- /dev/null +++ b/cli/repo/secret/secret.go @@ -0,0 +1,44 @@ +// Copyright 2023 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package secret + +import ( + "github.com/urfave/cli/v3" + + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" +) + +// Command exports the secret command. +var Command = &cli.Command{ + Name: "secret", + Usage: "manage secrets", + Commands: []*cli.Command{ + secretCreateCmd, + secretDeleteCmd, + secretListCmd, + secretShowCmd, + secretUpdateCmd, + }, +} + +func parseTargetArgs(client woodpecker.Client, c *cli.Command) (repoID int64, err error) { + repoIDOrFullName := c.String("repository") + if repoIDOrFullName == "" { + repoIDOrFullName = c.Args().First() + } + + return internal.ParseRepo(client, repoIDOrFullName) +} diff --git a/cli/repo/secret/secret_add.go b/cli/repo/secret/secret_add.go new file mode 100644 index 000000000..fb6e21383 --- /dev/null +++ b/cli/repo/secret/secret_add.go @@ -0,0 +1,93 @@ +// Copyright 2023 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package secret + +import ( + "context" + "os" + "strings" + + "github.com/urfave/cli/v3" + + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" +) + +var secretCreateCmd = &cli.Command{ + Name: "add", + Usage: "add a secret", + ArgsUsage: "[repo-id|repo-full-name]", + Action: secretCreate, + Flags: []cli.Flag{ + common.RepoFlag, + &cli.StringFlag{ + Name: "name", + Usage: "secret name", + }, + &cli.StringFlag{ + Name: "value", + Usage: "secret value", + }, + &cli.StringSliceFlag{ + Name: "event", + Usage: "limit secret to these events", + }, + &cli.StringSliceFlag{ + Name: "image", + Usage: "limit secret to these images", + }, + }, +} + +func secretCreate(ctx context.Context, c *cli.Command) error { + client, err := internal.NewClient(ctx, c) + if err != nil { + return err + } + + secret := &woodpecker.Secret{ + Name: strings.ToLower(c.String("name")), + Value: c.String("value"), + Images: c.StringSlice("image"), + Events: c.StringSlice("event"), + } + if len(secret.Events) == 0 { + secret.Events = defaultSecretEvents + } + if strings.HasPrefix(secret.Value, "@") { + path := strings.TrimPrefix(secret.Value, "@") + out, err := os.ReadFile(path) + if err != nil { + return err + } + secret.Value = string(out) + } + + repoID, err := parseTargetArgs(client, c) + if err != nil { + return err + } + + _, err = client.SecretCreate(repoID, secret) + return err +} + +var defaultSecretEvents = []string{ + woodpecker.EventPush, + woodpecker.EventTag, + woodpecker.EventRelease, + woodpecker.EventDeploy, +} diff --git a/cli/repo/secret/secret_list.go b/cli/repo/secret/secret_list.go new file mode 100644 index 000000000..b6bf0145f --- /dev/null +++ b/cli/repo/secret/secret_list.go @@ -0,0 +1,87 @@ +// Copyright 2023 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package secret + +import ( + "context" + "html/template" + "os" + "strings" + + "github.com/urfave/cli/v3" + + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" +) + +var secretListCmd = &cli.Command{ + Name: "ls", + Usage: "list secrets", + ArgsUsage: "[repo-id|repo-full-name]", + Action: secretList, + Flags: []cli.Flag{ + common.RepoFlag, + common.FormatFlag(tmplSecretList, true), + }, +} + +func secretList(ctx context.Context, c *cli.Command) error { + format := c.String("format") + "\n" + + client, err := internal.NewClient(ctx, c) + if err != nil { + return err + } + + repoID, err := parseTargetArgs(client, c) + if err != nil { + return err + } + + opt := woodpecker.SecretListOptions{} + + list, err := client.SecretList(repoID, opt) + if err != nil { + return err + } + + tmpl, err := template.New("_").Funcs(secretFuncMap).Parse(format) + if err != nil { + return err + } + for _, secret := range list { + if err := tmpl.Execute(os.Stdout, secret); err != nil { + return err + } + } + return nil +} + +// Template for secret list items. +var tmplSecretList = "\x1b[33m{{ .Name }} \x1b[0m" + ` +Events: {{ list .Events }} +{{- if .Images }} +Images: {{ list .Images }} +{{- else }} +Images: +{{- end }} +` + +var secretFuncMap = template.FuncMap{ + "list": func(s []string) string { + return strings.Join(s, ", ") + }, +} diff --git a/cli/secret/secret_rm.go b/cli/repo/secret/secret_rm.go similarity index 68% rename from cli/secret/secret_rm.go rename to cli/repo/secret/secret_rm.go index 0dbc0c188..b2fb8c07b 100644 --- a/cli/secret/secret_rm.go +++ b/cli/repo/secret/secret_rm.go @@ -15,10 +15,12 @@ package secret import ( - "github.com/urfave/cli/v2" + "context" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" + "github.com/urfave/cli/v3" + + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" ) var secretDeleteCmd = &cli.Command{ @@ -27,11 +29,6 @@ var secretDeleteCmd = &cli.Command{ ArgsUsage: "[repo-id|repo-full-name]", Action: secretDelete, Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "global", - Usage: "global secret", - }, - common.OrgFlag, common.RepoFlag, &cli.StringFlag{ Name: "name", @@ -40,24 +37,18 @@ var secretDeleteCmd = &cli.Command{ }, } -func secretDelete(c *cli.Context) error { +func secretDelete(ctx context.Context, c *cli.Command) error { secretName := c.String("name") - client, err := internal.NewClient(c) + client, err := internal.NewClient(ctx, c) if err != nil { return err } - global, orgID, repoID, err := parseTargetArgs(client, c) + repoID, err := parseTargetArgs(client, c) if err != nil { return err } - if global { - return client.GlobalSecretDelete(secretName) - } - if orgID != -1 { - return client.OrgSecretDelete(orgID, secretName) - } return client.SecretDelete(repoID, secretName) } diff --git a/cli/secret/secret_set.go b/cli/repo/secret/secret_set.go similarity index 70% rename from cli/secret/secret_set.go rename to cli/repo/secret/secret_set.go index c1e0d1443..b28473527 100644 --- a/cli/secret/secret_set.go +++ b/cli/repo/secret/secret_set.go @@ -15,14 +15,15 @@ package secret import ( + "context" "os" "strings" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" + "go.woodpecker-ci.org/woodpecker/v3/woodpecker-go/woodpecker" ) var secretUpdateCmd = &cli.Command{ @@ -31,11 +32,6 @@ var secretUpdateCmd = &cli.Command{ ArgsUsage: "[repo-id|repo-full-name]", Action: secretUpdate, Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "global", - Usage: "global secret", - }, - common.OrgFlag, common.RepoFlag, &cli.StringFlag{ Name: "name", @@ -47,17 +43,17 @@ var secretUpdateCmd = &cli.Command{ }, &cli.StringSliceFlag{ Name: "event", - Usage: "secret limited to these events", + Usage: "limit secret to these events", }, &cli.StringSliceFlag{ Name: "image", - Usage: "secret limited to these images", + Usage: "limit secret to these images", }, }, } -func secretUpdate(c *cli.Context) error { - client, err := internal.NewClient(c) +func secretUpdate(ctx context.Context, c *cli.Command) error { + client, err := internal.NewClient(ctx, c) if err != nil { return err } @@ -77,19 +73,11 @@ func secretUpdate(c *cli.Context) error { secret.Value = string(out) } - global, orgID, repoID, err := parseTargetArgs(client, c) + repoID, err := parseTargetArgs(client, c) if err != nil { return err } - if global { - _, err = client.GlobalSecretUpdate(secret) - return err - } - if orgID != -1 { - _, err = client.OrgSecretUpdate(orgID, secret) - return err - } _, err = client.SecretUpdate(repoID, secret) return err } diff --git a/cli/secret/secret_info.go b/cli/repo/secret/secret_show.go similarity index 56% rename from cli/secret/secret_info.go rename to cli/repo/secret/secret_show.go index 02d1a5d5e..15515fdb8 100644 --- a/cli/secret/secret_info.go +++ b/cli/repo/secret/secret_show.go @@ -15,27 +15,23 @@ package secret import ( + "context" + "fmt" "html/template" "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal" ) -var secretInfoCmd = &cli.Command{ - Name: "info", - Usage: "display secret info", +var secretShowCmd = &cli.Command{ + Name: "show", + Usage: "show secret information", ArgsUsage: "[repo-id|repo-full-name]", - Action: secretInfo, + Action: secretShow, Flags: []cli.Flag{ - &cli.BoolFlag{ - Name: "global", - Usage: "global secret", - }, - common.OrgFlag, common.RepoFlag, &cli.StringFlag{ Name: "name", @@ -45,38 +41,29 @@ var secretInfoCmd = &cli.Command{ }, } -func secretInfo(c *cli.Context) error { +func secretShow(ctx context.Context, c *cli.Command) error { var ( secretName = c.String("name") format = c.String("format") + "\n" ) - client, err := internal.NewClient(c) + + if secretName == "" { + return fmt.Errorf("secret name is missing") + } + + client, err := internal.NewClient(ctx, c) if err != nil { return err } - global, orgID, repoID, err := parseTargetArgs(client, c) + repoID, err := parseTargetArgs(client, c) if err != nil { return err } - var secret *woodpecker.Secret - switch { - case global: - secret, err = client.GlobalSecret(secretName) - if err != nil { - return err - } - case orgID != -1: - secret, err = client.OrgSecret(orgID, secretName) - if err != nil { - return err - } - default: - secret, err = client.Secret(repoID, secretName) - if err != nil { - return err - } + secret, err := client.Secret(repoID, secretName) + if err != nil { + return err } tmpl, err := template.New("_").Funcs(secretFuncMap).Parse(format) diff --git a/cli/secret/secret.go b/cli/secret/secret.go deleted file mode 100644 index 0e78d8d0d..000000000 --- a/cli/secret/secret.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2023 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package secret - -import ( - "fmt" - "strconv" - "strings" - - "github.com/urfave/cli/v2" - - "go.woodpecker-ci.org/woodpecker/v2/cli/internal" - "go.woodpecker-ci.org/woodpecker/v2/woodpecker-go/woodpecker" -) - -// Command exports the secret command. -var Command = &cli.Command{ - Name: "secret", - Usage: "manage secrets", - Subcommands: []*cli.Command{ - secretCreateCmd, - secretDeleteCmd, - secretUpdateCmd, - secretInfoCmd, - secretListCmd, - }, -} - -func parseTargetArgs(client woodpecker.Client, c *cli.Context) (global bool, orgID, repoID int64, err error) { - if c.Bool("global") { - return true, -1, -1, nil - } - - repoIDOrFullName := c.String("repository") - if repoIDOrFullName == "" { - repoIDOrFullName = c.Args().First() - } - - orgIDOrName := c.String("organization") - if orgIDOrName == "" && repoIDOrFullName == "" { - if err := cli.ShowSubcommandHelp(c); err != nil { - return false, -1, -1, err - } - - return false, -1, -1, fmt.Errorf("missing arguments") - } - - if orgIDOrName != "" && repoIDOrFullName == "" { - if orgID, err := strconv.ParseInt(orgIDOrName, 10, 64); err == nil { - return false, orgID, -1, nil - } - - org, err := client.OrgLookup(orgIDOrName) - if err != nil { - return false, -1, -1, err - } - - return false, org.ID, -1, nil - } - - if orgIDOrName != "" && !strings.Contains(repoIDOrFullName, "/") { - repoIDOrFullName = orgIDOrName + "/" + repoIDOrFullName - } - - repoID, err = internal.ParseRepo(client, repoIDOrFullName) - if err != nil { - return false, -1, -1, err - } - - return false, -1, repoID, nil -} diff --git a/cli/setup/setup.go b/cli/setup/setup.go index 1d3126efe..8b00467cf 100644 --- a/cli/setup/setup.go +++ b/cli/setup/setup.go @@ -1,37 +1,37 @@ package setup import ( + "context" "errors" "strings" "github.com/rs/zerolog/log" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/internal/config" - "go.woodpecker-ci.org/woodpecker/v2/cli/setup/ui" + "go.woodpecker-ci.org/woodpecker/v3/cli/internal/config" + "go.woodpecker-ci.org/woodpecker/v3/cli/setup/ui" ) // Command exports the setup command. var Command = &cli.Command{ Name: "setup", Usage: "setup the woodpecker-cli for the first time", - Args: true, ArgsUsage: "[server]", Flags: []cli.Flag{ &cli.StringFlag{ Name: "server", - Usage: "The URL of the woodpecker server", + Usage: "URL of the woodpecker server", }, &cli.StringFlag{ Name: "token", - Usage: "The token to authenticate with the woodpecker server", + Usage: "token to authenticate with the woodpecker server", }, }, Action: setup, } -func setup(c *cli.Context) error { - _config, err := config.Get(c, c.String("config")) +func setup(ctx context.Context, c *cli.Command) error { + _config, err := config.Get(ctx, c, c.String("config")) if err != nil { return err } else if _config != nil { @@ -41,7 +41,7 @@ func setup(c *cli.Context) error { } if !setupAgain { - log.Info().Msg("Configuration skipped") + log.Info().Msg("configuration skipped") return nil } } @@ -68,7 +68,7 @@ func setup(c *cli.Context) error { token := c.String("token") if token == "" { - token, err = receiveTokenFromUI(c.Context, serverURL) + token, err = receiveTokenFromUI(ctx, serverURL) if err != nil { return err } @@ -78,7 +78,7 @@ func setup(c *cli.Context) error { } } - err = config.Save(c, c.String("config"), &config.Config{ + err = config.Save(ctx, c, c.String("config"), &config.Config{ ServerURL: serverURL, Token: token, LogLevel: "info", @@ -87,7 +87,7 @@ func setup(c *cli.Context) error { return err } - log.Info().Msg("The woodpecker-cli has been successfully setup") + log.Info().Msg("woodpecker-cli has been successfully setup") return nil } diff --git a/cli/setup/token_fetcher.go b/cli/setup/token_fetcher.go index 59be6b08d..05305e7ca 100644 --- a/cli/setup/token_fetcher.go +++ b/cli/setup/token_fetcher.go @@ -24,12 +24,12 @@ func receiveTokenFromUI(c context.Context, serverURL string) (string, error) { srv.Handler = setupRouter(tokenReceived) go func() { - log.Debug().Msgf("Listening for token response on :%d", port) + log.Debug().Msgf("listening for token response on :%d", port) _ = srv.ListenAndServe() }() defer func() { - log.Debug().Msg("Shutting down server") + log.Debug().Msg("shutting down server") _ = srv.Shutdown(c) }() @@ -90,7 +90,7 @@ func setupRouter(tokenReceived chan string) *gin.Engine { err := c.BindJSON(&data) if err != nil { - log.Debug().Err(err).Msg("Failed to bind JSON") + log.Debug().Err(err).Msg("failed to bind JSON") c.JSON(http.StatusBadRequest, gin.H{ "error": "invalid request", }) @@ -110,7 +110,7 @@ func setupRouter(tokenReceived chan string) *gin.Engine { func openBrowser(url string) error { var err error - log.Debug().Msgf("Opening browser with URL: %s", url) + log.Debug().Msgf("opening browser with URL: %s", url) switch runtime.GOOS { case "linux": diff --git a/cli/update/command.go b/cli/update/command.go index 786a5c62b..a4dad8530 100644 --- a/cli/update/command.go +++ b/cli/update/command.go @@ -1,12 +1,13 @@ package update import ( + "context" "fmt" "os" "path/filepath" "github.com/rs/zerolog/log" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // Command exports the update command. @@ -22,35 +23,35 @@ var Command = &cli.Command{ Action: update, } -func update(c *cli.Context) error { - log.Info().Msg("Checking for updates ...") +func update(ctx context.Context, c *cli.Command) error { + log.Info().Msg("checking for updates ...") - newVersion, err := CheckForUpdate(c.Context, c.Bool("force")) + newVersion, err := CheckForUpdate(ctx, c.Bool("force")) if err != nil { return err } if newVersion == nil { - fmt.Println("You are using the latest version of woodpecker-cli") + fmt.Println("you are using the latest version of woodpecker-cli") return nil } - log.Info().Msgf("New version %s is available! Updating ...", newVersion.Version) + log.Info().Msgf("new version %s is available! Updating ...", newVersion.Version) var tarFilePath string - tarFilePath, err = downloadNewVersion(c.Context, newVersion.AssetURL) + tarFilePath, err = downloadNewVersion(ctx, newVersion.AssetURL) if err != nil { return err } - log.Debug().Msgf("New version %s has been downloaded successfully! Installing ...", newVersion.Version) + log.Debug().Msgf("new version %s has been downloaded successfully! Installing ...", newVersion.Version) binFile, err := extractNewVersion(tarFilePath) if err != nil { return err } - log.Debug().Msgf("New version %s has been extracted to %s", newVersion.Version, binFile) + log.Debug().Msgf("new version %s has been extracted to %s", newVersion.Version, binFile) executablePathOrSymlink, err := os.Executable() if err != nil { diff --git a/cli/update/updater.go b/cli/update/updater.go index 9071e2605..2810989ee 100644 --- a/cli/update/updater.go +++ b/cli/update/updater.go @@ -14,7 +14,7 @@ import ( "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/version" + "go.woodpecker-ci.org/woodpecker/v3/version" ) func CheckForUpdate(ctx context.Context, force bool) (*NewVersion, error) { @@ -22,10 +22,10 @@ func CheckForUpdate(ctx context.Context, force bool) (*NewVersion, error) { } func checkForUpdate(ctx context.Context, versionURL string, force bool) (*NewVersion, error) { - log.Debug().Msgf("Current version: %s", version.String()) + log.Debug().Msgf("current version: %s", version.String()) if (version.String() == "dev" || strings.HasPrefix(version.String(), "next-")) && !force { - log.Debug().Msgf("Skipping update check for development & next versions") + log.Debug().Msgf("skipping update check for development/next versions") return nil, nil } @@ -61,11 +61,11 @@ func checkForUpdate(ctx context.Context, versionURL string, force bool) (*NewVer // using the latest release if installedVersion == upstreamVersion && !force { - log.Debug().Msgf("No new version available") + log.Debug().Msgf("no new version available") return nil, nil } - log.Debug().Msgf("New version available: %s", upstreamVersion) + log.Debug().Msgf("new version available: %s", upstreamVersion) assetURL := fmt.Sprintf(githubBinaryURL, upstreamVersion, runtime.GOOS, runtime.GOARCH) return &NewVersion{ @@ -75,7 +75,7 @@ func checkForUpdate(ctx context.Context, versionURL string, force bool) (*NewVer } func downloadNewVersion(ctx context.Context, downloadURL string) (string, error) { - log.Debug().Msgf("Downloading new version from %s ...", downloadURL) + log.Debug().Msgf("downloading new version from %s ...", downloadURL) req, err := http.NewRequestWithContext(ctx, http.MethodGet, downloadURL, nil) if err != nil { @@ -102,13 +102,13 @@ func downloadNewVersion(ctx context.Context, downloadURL string) (string, error) return "", err } - log.Debug().Msgf("New version downloaded to %s", file.Name()) + log.Debug().Msgf("new version downloaded to %s", file.Name()) return file.Name(), nil } func extractNewVersion(tarFilePath string) (string, error) { - log.Debug().Msgf("Extracting new version from %s ...", tarFilePath) + log.Debug().Msgf("extracting new version from %s ...", tarFilePath) tarFile, err := os.Open(tarFilePath) if err != nil { @@ -132,7 +132,7 @@ func extractNewVersion(tarFilePath string) (string, error) { return "", err } - log.Debug().Msgf("New version extracted to %s", tmpDir) + log.Debug().Msgf("new version extracted to %s", tmpDir) return path.Join(tmpDir, "woodpecker-cli"), nil } diff --git a/cli/update/updater_test.go b/cli/update/updater_test.go index a2855ac8e..9de9001af 100644 --- a/cli/update/updater_test.go +++ b/cli/update/updater_test.go @@ -8,7 +8,7 @@ import ( "os" "testing" - "go.woodpecker-ci.org/woodpecker/v2/version" + "go.woodpecker-ci.org/woodpecker/v3/version" ) func TestCheckForUpdate(t *testing.T) { diff --git a/cmd/agent/core/agent.go b/cmd/agent/core/agent.go index d4c37fc4b..a9f858300 100644 --- a/cmd/agent/core/agent.go +++ b/cmd/agent/core/agent.go @@ -20,15 +20,16 @@ import ( "crypto/tls" "errors" "fmt" + "maps" "net/http" "os" "strings" - "sync" + "sync/atomic" "time" "github.com/rs/zerolog/log" - "github.com/tevino/abool/v2" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" + "golang.org/x/sync/errgroup" "google.golang.org/grpc" "google.golang.org/grpc/codes" grpc_credentials "google.golang.org/grpc/credentials" @@ -37,36 +38,81 @@ import ( "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" - "go.woodpecker-ci.org/woodpecker/v2/agent" - agent_rpc "go.woodpecker-ci.org/woodpecker/v2/agent/rpc" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc" - "go.woodpecker-ci.org/woodpecker/v2/shared/logger" - "go.woodpecker-ci.org/woodpecker/v2/shared/utils" - "go.woodpecker-ci.org/woodpecker/v2/version" + "go.woodpecker-ci.org/woodpecker/v3/agent" + agent_rpc "go.woodpecker-ci.org/woodpecker/v3/agent/rpc" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc" + "go.woodpecker-ci.org/woodpecker/v3/shared/logger" + "go.woodpecker-ci.org/woodpecker/v3/shared/utils" + "go.woodpecker-ci.org/woodpecker/v3/version" ) -func run(c *cli.Context, backends []types.Backend) error { +const ( + reportHealthInterval = time.Second * 10 + authInterceptorRefreshInterval = time.Minute * 30 +) + +const ( + shutdownTimeout = time.Second * 5 +) + +var ( + stopAgentFunc context.CancelCauseFunc = func(error) {} + shutdownCancelFunc context.CancelFunc = func() {} + shutdownCtx = context.Background() +) + +func run(ctx context.Context, c *cli.Command, backends []types.Backend) error { + agentCtx, ctxCancel := context.WithCancelCause(ctx) + stopAgentFunc = func(err error) { + msg := "shutdown of whole agent" + if err != nil { + log.Error().Err(err).Msg(msg) + } else { + log.Info().Msg(msg) + } + stopAgentFunc = func(error) {} + shutdownCtx, shutdownCancelFunc = context.WithTimeout(shutdownCtx, shutdownTimeout) + ctxCancel(err) + } + defer stopAgentFunc(nil) + defer shutdownCancelFunc() + + serviceWaitingGroup := errgroup.Group{} + agentConfigPath := c.String("agent-config") hostname := c.String("hostname") if len(hostname) == 0 { hostname, _ = os.Hostname() } - counter.Polling = c.Int("max-workflows") + counter.Polling = int(c.Int("max-workflows")) counter.Running = 0 if c.Bool("healthcheck") { - go func() { - if err := http.ListenAndServe(c.String("healthcheck-addr"), nil); err != nil { - log.Error().Err(err).Msgf("cannot listen on address %s", c.String("healthcheck-addr")) - } - }() + serviceWaitingGroup.Go( + func() error { + server := &http.Server{Addr: c.String("healthcheck-addr")} + go func() { + <-agentCtx.Done() + log.Info().Msg("shutdown healthcheck server ...") + if err := server.Shutdown(shutdownCtx); err != nil { //nolint:contextcheck + log.Error().Err(err).Msg("shutdown healthcheck server failed") + } else { + log.Info().Msg("healthcheck server stopped") + } + }() + if err := server.ListenAndServe(); err != nil { + log.Error().Err(err).Msgf("cannot listen on address %s", c.String("healthcheck-addr")) + } + return nil + }) } var transport grpc.DialOption if c.Bool("grpc-secure") { + log.Trace().Msg("use ssl for grpc") transport = grpc.WithTransportCredentials(grpc_credentials.NewTLS(&tls.Config{InsecureSkipVerify: c.Bool("grpc-skip-insecure")})) } else { transport = grpc.WithTransportCredentials(insecure.NewCredentials()) @@ -81,17 +127,19 @@ func run(c *cli.Context, backends []types.Backend) error { }), ) if err != nil { - return err + return fmt.Errorf("could not create new gRPC 'channel' for authentication: %w", err) } defer authConn.Close() agentConfig := readAgentConfig(agentConfigPath) agentToken := c.String("grpc-token") + grpcClientCtx, grpcClientCtxCancel := context.WithCancelCause(context.Background()) + defer grpcClientCtxCancel(nil) authClient := agent_rpc.NewAuthGrpcClient(authConn, agentToken, agentConfig.AgentID) - authInterceptor, err := agent_rpc.NewAuthInterceptor(authClient, 30*time.Minute) //nolint:mnd + authInterceptor, err := agent_rpc.NewAuthInterceptor(grpcClientCtx, authClient, authInterceptorRefreshInterval) //nolint:contextcheck if err != nil { - return err + return fmt.Errorf("could not create new auth interceptor: %w", err) } conn, err := grpc.NewClient( @@ -105,35 +153,17 @@ func run(c *cli.Context, backends []types.Backend) error { grpc.WithStreamInterceptor(authInterceptor.Stream()), ) if err != nil { - return err + return fmt.Errorf("could not create new gRPC 'channel' for normal orchestration: %w", err) } defer conn.Close() - client := agent_rpc.NewGrpcClient(conn) + client := agent_rpc.NewGrpcClient(ctx, conn) + agentConfigPersisted := atomic.Bool{} - sigterm := abool.New() - ctx := metadata.NewOutgoingContext( - context.Background(), - metadata.Pairs("hostname", hostname), - ) - - agentConfigPersisted := abool.New() - ctx = utils.WithContextSigtermCallback(ctx, func() { - log.Info().Msg("termination signal is received, shutting down") - sigterm.Set() - - // Remove stateless agents from server - if agentConfigPersisted.IsNotSet() { - log.Debug().Msg("unregistering agent from server") - err := client.UnregisterAgent(ctx) - if err != nil { - log.Err(err).Msg("failed to unregister agent from server") - } - } - }) + grpcCtx := metadata.NewOutgoingContext(grpcClientCtx, metadata.Pairs("hostname", hostname)) // check if grpc server version is compatible with agent - grpcServerVersion, err := client.Version(ctx) + grpcServerVersion, err := client.Version(grpcCtx) //nolint:contextcheck if err != nil { log.Error().Err(err).Msg("could not get grpc server version") return err @@ -147,12 +177,8 @@ func run(c *cli.Context, backends []types.Backend) error { return err } - var wg sync.WaitGroup - parallel := c.Int("max-workflows") - wg.Add(parallel) - // new engine - backendCtx := context.WithValue(ctx, types.CliContext, c) + backendCtx := context.WithValue(agentCtx, types.CliCommand, c) backendName := c.String("backend-engine") backendEngine, err := backend.FindBackend(backendCtx, backends, backendName) if err != nil { @@ -172,27 +198,63 @@ func run(c *cli.Context, backends []types.Backend) error { } log.Debug().Msgf("loaded %s backend engine", backendEngine.Name()) - agentConfig.AgentID, err = client.RegisterAgent(ctx, engInfo.Platform, backendEngine.Name(), version.String(), parallel) + maxWorkflows := int(c.Int("max-workflows")) + + customLabels := make(map[string]string) + if err := stringSliceAddToMap(c.StringSlice("labels"), customLabels); err != nil { + return err + } + if len(customLabels) != 0 { + log.Debug().Msgf("custom labels detected: %#v", customLabels) + } + + agentConfig.AgentID, err = client.RegisterAgent(grpcCtx, rpc.AgentInfo{ //nolint:contextcheck + Version: version.String(), + Backend: backendEngine.Name(), + Platform: engInfo.Platform, + Capacity: maxWorkflows, + CustomLabels: customLabels, + }) if err != nil { return err } + serviceWaitingGroup.Go(func() error { + // we close grpc client context once unregister was handled + defer grpcClientCtxCancel(nil) + // we wait till agent context is done + <-agentCtx.Done() + // Remove stateless agents from server + if !agentConfigPersisted.Load() { + log.Debug().Msg("unregister agent from server ...") + // we want to run it explicit run when context got canceled so run it in background + err := client.UnregisterAgent(grpcClientCtx) + if err != nil { + log.Err(err).Msg("failed to unregister agent from server") + } else { + log.Info().Msg("agent unregistered from server") + } + } + return nil + }) + if agentConfigPath != "" { if err := writeAgentConfig(agentConfig, agentConfigPath); err == nil { - agentConfigPersisted.Set() + agentConfigPersisted.Store(true) } } + // set default labels ... labels := map[string]string{ "hostname": hostname, "platform": engInfo.Platform, "backend": backendEngine.Name(), "repo": "*", // allow all repos by default } + // ... and let it overwrite by custom ones + maps.Copy(labels, customLabels) - if err := stringSliceAddToMap(c.StringSlice("filter"), labels); err != nil { - return err - } + log.Debug().Any("labels", labels).Msgf("agent configured with labels") filter := rpc.Filter{ Labels: labels, @@ -200,66 +262,62 @@ func run(c *cli.Context, backends []types.Backend) error { log.Debug().Msgf("agent registered with ID %d", agentConfig.AgentID) - go func() { + serviceWaitingGroup.Go(func() error { for { - if sigterm.IsSet() { - log.Debug().Msg("terminating health reporting") - return - } - - err := client.ReportHealth(ctx) + err := client.ReportHealth(grpcCtx) if err != nil { log.Err(err).Msg("failed to report health") } - <-time.After(time.Second * 10) + select { + case <-agentCtx.Done(): + log.Debug().Msg("terminating health reporting") + return nil + case <-time.After(reportHealthInterval): + } } - }() + }) - for i := 0; i < parallel; i++ { + for i := 0; i < maxWorkflows; i++ { i := i - go func() { - defer wg.Done() - - r := agent.NewRunner(client, filter, hostname, counter, &backendEngine) + serviceWaitingGroup.Go(func() error { + runner := agent.NewRunner(client, filter, hostname, counter, &backendEngine) log.Debug().Msgf("created new runner %d", i) for { - if sigterm.IsSet() { - log.Debug().Msgf("terminating runner %d", i) - return + if agentCtx.Err() != nil { + return nil } log.Debug().Msg("polling new steps") - if err := r.Run(ctx); err != nil { - log.Error().Err(err).Msg("pipeline done with error") - return + if err := runner.Run(agentCtx, shutdownCtx); err != nil { + log.Error().Err(err).Msg("runner done with error") + return err } } - }() + }) } log.Info().Msgf( "starting Woodpecker agent with version '%s' and backend '%s' using platform '%s' running up to %d pipelines in parallel", - version.String(), backendEngine.Name(), engInfo.Platform, parallel) + version.String(), backendEngine.Name(), engInfo.Platform, maxWorkflows) - wg.Wait() - return nil + return serviceWaitingGroup.Wait() } -func runWithRetry(backendEngines []types.Backend) func(context *cli.Context) error { - return func(context *cli.Context) error { - if err := logger.SetupGlobalLogger(context, true); err != nil { +func runWithRetry(backendEngines []types.Backend) func(ctx context.Context, c *cli.Command) error { + return func(ctx context.Context, c *cli.Command) error { + if err := logger.SetupGlobalLogger(ctx, c, true); err != nil { return err } initHealth() - retryCount := context.Int("connect-retry-count") - retryDelay := context.Duration("connect-retry-delay") + retryCount := int(c.Int("connect-retry-count")) + retryDelay := c.Duration("connect-retry-delay") var err error for i := 0; i < retryCount; i++ { - if err = run(context, backendEngines); status.Code(err) == codes.Unavailable { + if err = run(ctx, c, backendEngines); status.Code(err) == codes.Unavailable { log.Warn().Err(err).Msg(fmt.Sprintf("cannot connect to server, retrying in %v", retryDelay)) time.Sleep(retryDelay) } else { diff --git a/cmd/agent/core/flags.go b/cmd/agent/core/flags.go index f0ab7cbcd..40dbefc50 100644 --- a/cmd/agent/core/flags.go +++ b/cmd/agent/core/flags.go @@ -19,93 +19,98 @@ import ( "os" "time" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) //nolint:mnd var flags = []cli.Flag{ &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_SERVER"}, + Sources: cli.EnvVars("WOODPECKER_SERVER"), Name: "server", Usage: "server address", Value: "localhost:9000", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_AGENT_SECRET"}, - Name: "grpc-token", - Usage: "server-agent shared token", - FilePath: os.Getenv("WOODPECKER_AGENT_SECRET_FILE"), + Name: "grpc-token", + Usage: "server-agent shared token", + Sources: cli.NewValueSourceChain( + cli.File(os.Getenv("WOODPECKER_AGENT_SECRET_FILE")), + cli.EnvVar("WOODPECKER_AGENT_SECRET")), + Config: cli.StringConfig{ + TrimSpace: true, + }, }, &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_GRPC_SECURE"}, + Sources: cli.EnvVars("WOODPECKER_GRPC_SECURE"), Name: "grpc-secure", Usage: "should the connection to WOODPECKER_SERVER be made using a secure transport", }, &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_GRPC_VERIFY"}, + Sources: cli.EnvVars("WOODPECKER_GRPC_VERIFY"), Name: "grpc-skip-insecure", Usage: "should the grpc server certificate be verified, only valid when WOODPECKER_GRPC_SECURE is true", Value: true, }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_HOSTNAME"}, + Sources: cli.EnvVars("WOODPECKER_HOSTNAME"), Name: "hostname", Usage: "agent hostname", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_AGENT_CONFIG_FILE"}, + Sources: cli.EnvVars("WOODPECKER_AGENT_CONFIG_FILE"), Name: "agent-config", Usage: "agent config file path, if set empty the agent will be stateless and unregister on termination", Value: "/etc/woodpecker/agent.conf", }, &cli.StringSliceFlag{ - EnvVars: []string{"WOODPECKER_FILTER_LABELS"}, - Name: "filter", + Sources: cli.EnvVars("WOODPECKER_AGENT_LABELS", "WOODPECKER_FILTER_LABELS"), // remove WOODPECKER_FILTER_LABELS in v4.x + Name: "labels", + Aliases: []string{"filter"}, // remove in v4.x Usage: "List of labels to filter tasks on. An agent must be assigned every tag listed in a task to be selected.", }, &cli.IntFlag{ - EnvVars: []string{"WOODPECKER_MAX_WORKFLOWS", "WOODPECKER_MAX_PROCS"}, // cspell:words PROCS + Sources: cli.EnvVars("WOODPECKER_MAX_WORKFLOWS", "WOODPECKER_MAX_PROCS"), // cspell:words PROCS Name: "max-workflows", Usage: "agent parallel workflows", Value: 1, }, &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_HEALTHCHECK"}, + Sources: cli.EnvVars("WOODPECKER_HEALTHCHECK"), Name: "healthcheck", Usage: "enable healthcheck endpoint", Value: true, }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_HEALTHCHECK_ADDR"}, + Sources: cli.EnvVars("WOODPECKER_HEALTHCHECK_ADDR"), Name: "healthcheck-addr", Usage: "healthcheck endpoint address", Value: ":3000", }, &cli.DurationFlag{ - EnvVars: []string{"WOODPECKER_KEEPALIVE_TIME"}, + Sources: cli.EnvVars("WOODPECKER_KEEPALIVE_TIME"), Name: "keepalive-time", Usage: "after a duration of this time of no activity, the agent pings the server to check if the transport is still alive", }, &cli.DurationFlag{ - EnvVars: []string{"WOODPECKER_KEEPALIVE_TIMEOUT"}, + Sources: cli.EnvVars("WOODPECKER_KEEPALIVE_TIMEOUT"), Name: "keepalive-timeout", Usage: "after pinging for a keepalive check, the agent waits for a duration of this time before closing the connection if no activity", Value: time.Second * 20, }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_BACKEND"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND"), Name: "backend-engine", Usage: "backend to run pipelines on", Value: "auto-detect", }, &cli.IntFlag{ - EnvVars: []string{"WOODPECKER_CONNECT_RETRY_COUNT"}, + Sources: cli.EnvVars("WOODPECKER_CONNECT_RETRY_COUNT"), Name: "connect-retry-count", Usage: "number of times to retry connecting to the server", Value: 5, }, &cli.DurationFlag{ - EnvVars: []string{"WOODPECKER_CONNECT_RETRY_DELAY"}, + Sources: cli.EnvVars("WOODPECKER_CONNECT_RETRY_DELAY"), Name: "connect-retry-delay", Usage: "duration to wait before retrying to connect to the server", Value: time.Second * 2, diff --git a/cmd/agent/core/health.go b/cmd/agent/core/health.go index 263bd3269..7997c570f 100644 --- a/cmd/agent/core/health.go +++ b/cmd/agent/core/health.go @@ -15,16 +15,17 @@ package core import ( + "context" "encoding/json" "fmt" "net/http" "strings" "github.com/rs/zerolog/log" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/agent" - "go.woodpecker-ci.org/woodpecker/v2/version" + "go.woodpecker-ci.org/woodpecker/v3/agent" + "go.woodpecker-ci.org/woodpecker/v3/version" ) // The file implements some basic healthcheck logic based on the @@ -81,13 +82,18 @@ var counter = &agent.State{ // handles pinging the endpoint and returns an error if the // agent is in an unhealthy state. -func pinger(c *cli.Context) error { +func pinger(ctx context.Context, c *cli.Command) error { healthcheckAddress := c.String("healthcheck-addr") if strings.HasPrefix(healthcheckAddress, ":") { // this seems sufficient according to https://pkg.go.dev/net#Dial healthcheckAddress = "localhost" + healthcheckAddress } - resp, err := http.Get("http://" + healthcheckAddress + "/healthz") + + req, err := http.NewRequestWithContext(ctx, http.MethodGet, "http://"+healthcheckAddress+"/healthz", nil) + if err != nil { + return err + } + resp, err := http.DefaultClient.Do(req) if err != nil { return err } diff --git a/cmd/agent/core/health_test.go b/cmd/agent/core/health_test.go index 326d37536..30bea8cc9 100644 --- a/cmd/agent/core/health_test.go +++ b/cmd/agent/core/health_test.go @@ -20,7 +20,7 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/agent" + "go.woodpecker-ci.org/woodpecker/v3/agent" ) func TestHealthy(t *testing.T) { diff --git a/cmd/agent/core/run.go b/cmd/agent/core/run.go index 9c64fec3d..f3175c5b4 100644 --- a/cmd/agent/core/run.go +++ b/cmd/agent/core/run.go @@ -15,21 +15,22 @@ package core import ( + "context" "os" // Load config from .env file. _ "github.com/joho/godotenv/autoload" "github.com/rs/zerolog/log" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" - "go.woodpecker-ci.org/woodpecker/v2/shared/logger" - "go.woodpecker-ci.org/woodpecker/v2/shared/utils" - "go.woodpecker-ci.org/woodpecker/v2/version" + backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" + "go.woodpecker-ci.org/woodpecker/v3/shared/logger" + "go.woodpecker-ci.org/woodpecker/v3/shared/utils" + "go.woodpecker-ci.org/woodpecker/v3/version" ) -func RunAgent(backends []backend.Backend) { - app := cli.NewApp() +func RunAgent(ctx context.Context, backends []backend.Backend) { + app := &cli.Command{} app.Name = "woodpecker-agent" app.Version = version.String() app.Usage = "woodpecker agent" @@ -47,7 +48,7 @@ func RunAgent(backends []backend.Backend) { } app.Flags = agentFlags - if err := app.Run(os.Args); err != nil { + if err := app.Run(ctx, os.Args); err != nil { log.Fatal().Err(err).Msg("error running agent") //nolint:forbidigo } } diff --git a/cmd/agent/dummy.go b/cmd/agent/dummy.go index 398b50f10..f2da7c105 100644 --- a/cmd/agent/dummy.go +++ b/cmd/agent/dummy.go @@ -17,7 +17,7 @@ package main -import "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/dummy" +import "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/dummy" func init() { //nolint:gochecknoinits backends = append(backends, dummy.New()) diff --git a/cmd/agent/main.go b/cmd/agent/main.go index cb18939f4..8b1854aec 100644 --- a/cmd/agent/main.go +++ b/cmd/agent/main.go @@ -15,11 +15,16 @@ package main import ( - "go.woodpecker-ci.org/woodpecker/v2/cmd/agent/core" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/docker" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/kubernetes" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/local" - backendTypes "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" + "context" + + "github.com/rs/zerolog/log" + + "go.woodpecker-ci.org/woodpecker/v3/cmd/agent/core" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/docker" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/kubernetes" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/local" + backendTypes "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" + "go.woodpecker-ci.org/woodpecker/v3/shared/utils" ) var backends = []backendTypes.Backend{ @@ -29,5 +34,8 @@ var backends = []backendTypes.Backend{ } func main() { - core.RunAgent(backends) + ctx := utils.WithContextSigtermCallback(context.Background(), func() { + log.Info().Msg("termination signal is received, shutting down agent") + }) + core.RunAgent(ctx, backends) } diff --git a/cmd/cli/app.go b/cmd/cli/app.go index 8b78b3ecd..301ec61f4 100644 --- a/cmd/cli/app.go +++ b/cmd/cli/app.go @@ -15,55 +15,40 @@ package main import ( - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/cli/admin" - "go.woodpecker-ci.org/woodpecker/v2/cli/common" - "go.woodpecker-ci.org/woodpecker/v2/cli/cron" - "go.woodpecker-ci.org/woodpecker/v2/cli/deploy" - "go.woodpecker-ci.org/woodpecker/v2/cli/exec" - "go.woodpecker-ci.org/woodpecker/v2/cli/info" - "go.woodpecker-ci.org/woodpecker/v2/cli/lint" - "go.woodpecker-ci.org/woodpecker/v2/cli/log" - "go.woodpecker-ci.org/woodpecker/v2/cli/loglevel" - "go.woodpecker-ci.org/woodpecker/v2/cli/org" - "go.woodpecker-ci.org/woodpecker/v2/cli/pipeline" - "go.woodpecker-ci.org/woodpecker/v2/cli/repo" - "go.woodpecker-ci.org/woodpecker/v2/cli/repo/registry" - "go.woodpecker-ci.org/woodpecker/v2/cli/secret" - "go.woodpecker-ci.org/woodpecker/v2/cli/setup" - "go.woodpecker-ci.org/woodpecker/v2/cli/update" - "go.woodpecker-ci.org/woodpecker/v2/cli/user" - "go.woodpecker-ci.org/woodpecker/v2/version" + "go.woodpecker-ci.org/woodpecker/v3/cli/admin" + "go.woodpecker-ci.org/woodpecker/v3/cli/common" + "go.woodpecker-ci.org/woodpecker/v3/cli/exec" + "go.woodpecker-ci.org/woodpecker/v3/cli/info" + "go.woodpecker-ci.org/woodpecker/v3/cli/lint" + "go.woodpecker-ci.org/woodpecker/v3/cli/org" + "go.woodpecker-ci.org/woodpecker/v3/cli/pipeline" + "go.woodpecker-ci.org/woodpecker/v3/cli/repo" + "go.woodpecker-ci.org/woodpecker/v3/cli/setup" + "go.woodpecker-ci.org/woodpecker/v3/cli/update" + "go.woodpecker-ci.org/woodpecker/v3/version" ) //go:generate go run docs.go app.go -func newApp() *cli.App { - app := cli.NewApp() +func newApp() *cli.Command { + app := &cli.Command{} app.Name = "woodpecker-cli" app.Description = "Woodpecker command line utility" app.Version = version.String() - app.EnableBashCompletion = true + app.Usage = "command line utility" app.Flags = common.GlobalFlags app.Before = common.Before app.After = common.After app.Suggest = true app.Commands = []*cli.Command{ admin.Command, - org.Command, - repo.Command, - pipeline.Command, - log.Command, - deploy.Command, exec.Command, info.Command, - // TODO: Remove in 3.x - registry.Command, - secret.Command, - user.Command, lint.Command, - loglevel.Command, - cron.Command, + org.Command, + pipeline.Command, + repo.Command, setup.Command, update.Command, } diff --git a/cmd/cli/docs.go b/cmd/cli/docs.go index 9ed83ef47..d8dae41de 100644 --- a/cmd/cli/docs.go +++ b/cmd/cli/docs.go @@ -19,11 +19,13 @@ package main import ( "os" + + docs "github.com/urfave/cli-docs/v3" ) func main() { app := newApp() - md, err := app.ToMarkdown() + md, err := docs.ToMarkdown(app) if err != nil { panic(err) } diff --git a/cmd/cli/main.go b/cmd/cli/main.go index d0b155868..d6670dadc 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -15,15 +15,22 @@ package main import ( + "context" "os" _ "github.com/joho/godotenv/autoload" "github.com/rs/zerolog/log" + + "go.woodpecker-ci.org/woodpecker/v3/shared/utils" ) func main() { + ctx := utils.WithContextSigtermCallback(context.Background(), func() { + log.Info().Msg("termination signal is received, terminate cli") + }) + app := newApp() - if err := app.Run(os.Args); err != nil { + if err := app.Run(ctx, os.Args); err != nil { log.Fatal().Err(err).Msg("error running cli") //nolint:forbidigo } } diff --git a/cmd/server/flags.go b/cmd/server/flags.go index a511a4b83..7e63e7f51 100644 --- a/cmd/server/flags.go +++ b/cmd/server/flags.go @@ -1,3 +1,4 @@ +// Copyright 2023 Woodpecker Authors // Copyright 2019 Laszlo Fogas // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,237 +19,280 @@ import ( "os" "time" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/shared/constant" - "go.woodpecker-ci.org/woodpecker/v2/shared/logger" + "go.woodpecker-ci.org/woodpecker/v3/shared/constant" + "go.woodpecker-ci.org/woodpecker/v3/shared/logger" ) var flags = append([]cli.Flag{ &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_LOG_XORM"}, - Name: "log-xorm", - Usage: "enable xorm logging", + Sources: cli.EnvVars("WOODPECKER_DATABASE_LOG", "WOODPECKER_LOG_XORM"), + Name: "db-log", + Aliases: []string{"log-xorm"}, // TODO: remove in v4.0.0 + Usage: "enable logging in database engine (currently xorm)", }, &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_LOG_XORM_SQL"}, - Name: "log-xorm-sql", - Usage: "enable xorm sql command logging", + Sources: cli.EnvVars("WOODPECKER_DATABASE_LOG_SQL", "WOODPECKER_LOG_XORM_SQL"), + Name: "db-log-sql", + Aliases: []string{"log-xorm-sql"}, // TODO: remove in v4.0.0 + Usage: "enable logging of sql commands", + }, + &cli.IntFlag{ + Sources: cli.EnvVars("WOODPECKER_DATABASE_MAX_CONNECTIONS"), + Name: "db-max-open-connections", + Usage: "max connections xorm is allowed create", + Value: 100, + }, + &cli.IntFlag{ + Sources: cli.EnvVars("WOODPECKER_DATABASE_IDLE_CONNECTIONS"), + Name: "db-max-idle-connections", + Usage: "amount of connections xorm will hold open", + Value: 2, + }, + &cli.DurationFlag{ + Sources: cli.EnvVars("WOODPECKER_DATABASE_CONNECTION_TIMEOUT"), + Name: "db-max-connection-timeout", + Usage: "time an active connection is allowed to stay open", + Value: 3 * time.Second, }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_HOST"}, + Sources: cli.EnvVars("WOODPECKER_HOST"), Name: "server-host", Usage: "server fully qualified url. Format: ://[/]", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_SERVER_ADDR"}, + Sources: cli.EnvVars("WOODPECKER_SERVER_ADDR"), Name: "server-addr", Usage: "server address", Value: ":8000", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_SERVER_ADDR_TLS"}, + Sources: cli.EnvVars("WOODPECKER_SERVER_ADDR_TLS"), Name: "server-addr-tls", Usage: "port https with tls (:443)", Value: ":443", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_SERVER_CERT"}, + Sources: cli.EnvVars("WOODPECKER_SERVER_CERT"), Name: "server-cert", Usage: "server ssl cert path", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_SERVER_KEY"}, + Sources: cli.EnvVars("WOODPECKER_SERVER_KEY"), Name: "server-key", Usage: "server ssl key path", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_CUSTOM_CSS_FILE"}, + Sources: cli.EnvVars("WOODPECKER_CUSTOM_CSS_FILE"), Name: "custom-css-file", Usage: "file path for the server to serve a custom .CSS file, used for customizing the UI", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_CUSTOM_JS_FILE"}, + Sources: cli.EnvVars("WOODPECKER_CUSTOM_JS_FILE"), Name: "custom-js-file", Usage: "file path for the server to serve a custom .JS file, used for customizing the UI", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_LETS_ENCRYPT_EMAIL"}, - Name: "lets-encrypt-email", - Usage: "let's encrypt email", - }, - &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_LETS_ENCRYPT"}, - Name: "lets-encrypt", - Usage: "enable let's encrypt", - }, - &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_GRPC_ADDR"}, + Sources: cli.EnvVars("WOODPECKER_GRPC_ADDR"), Name: "grpc-addr", Usage: "grpc address", Value: ":9000", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_GRPC_SECRET"}, - Name: "grpc-secret", - Usage: "grpc jwt secret", - Value: "secret", - FilePath: os.Getenv("WOODPECKER_GRPC_SECRET_FILE"), + Sources: cli.NewValueSourceChain( + cli.File(os.Getenv("WOODPECKER_GRPC_SECRET_FILE")), + cli.EnvVar("WOODPECKER_GRPC_SECRET")), + Name: "grpc-secret", + Usage: "grpc jwt secret", + Value: "secret", + Config: cli.StringConfig{ + TrimSpace: true, + }, }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_METRICS_SERVER_ADDR"}, + Sources: cli.EnvVars("WOODPECKER_METRICS_SERVER_ADDR"), Name: "metrics-server-addr", Usage: "metrics server address", Value: "", }, &cli.StringSliceFlag{ - EnvVars: []string{"WOODPECKER_ADMIN"}, + Sources: cli.EnvVars("WOODPECKER_ADMIN"), Name: "admin", Usage: "list of admin users", }, &cli.StringSliceFlag{ - EnvVars: []string{"WOODPECKER_ORGS"}, + Sources: cli.EnvVars("WOODPECKER_ORGS"), Name: "orgs", Usage: "list of approved organizations", }, &cli.StringSliceFlag{ - EnvVars: []string{"WOODPECKER_REPO_OWNERS"}, + Sources: cli.EnvVars("WOODPECKER_REPO_OWNERS"), Name: "repo-owners", Usage: "Repositories by those owners will be allowed to be used in woodpecker", }, &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_OPEN"}, + Sources: cli.EnvVars("WOODPECKER_OPEN"), Name: "open", Usage: "enable open user registration", }, &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_AUTHENTICATE_PUBLIC_REPOS"}, + Sources: cli.EnvVars("WOODPECKER_AUTHENTICATE_PUBLIC_REPOS"), Name: "authenticate-public-repos", Usage: "Always use authentication to clone repositories even if they are public. Needed if the SCM requires to always authenticate as used by many companies.", }, &cli.StringSliceFlag{ - EnvVars: []string{"WOODPECKER_DEFAULT_CANCEL_PREVIOUS_PIPELINE_EVENTS"}, + Sources: cli.EnvVars("WOODPECKER_DEFAULT_CANCEL_PREVIOUS_PIPELINE_EVENTS"), Name: "default-cancel-previous-pipeline-events", Usage: "List of event names that will be canceled when a new pipeline for the same context (tag, branch) is created.", - Value: cli.NewStringSlice("push", "pull_request"), + Value: []string{"push", "pull_request"}, }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_DEFAULT_CLONE_IMAGE"}, - Name: "default-clone-image", + Sources: cli.EnvVars("WOODPECKER_DEFAULT_CLONE_PLUGIN", "WOODPECKER_DEFAULT_CLONE_IMAGE"), + Name: "default-clone-plugin", + Aliases: []string{"default-clone-image"}, Usage: "The default docker image to be used when cloning the repo", - Value: constant.DefaultCloneImage, + Value: constant.DefaultClonePlugin, }, - &cli.Int64Flag{ - EnvVars: []string{"WOODPECKER_DEFAULT_PIPELINE_TIMEOUT"}, + &cli.IntFlag{ + Sources: cli.EnvVars("WOODPECKER_DEFAULT_PIPELINE_TIMEOUT"), Name: "default-pipeline-timeout", Usage: "The default time in minutes for a repo in minutes before a pipeline gets killed", Value: 60, }, - &cli.Int64Flag{ - EnvVars: []string{"WOODPECKER_MAX_PIPELINE_TIMEOUT"}, + &cli.IntFlag{ + Sources: cli.EnvVars("WOODPECKER_MAX_PIPELINE_TIMEOUT"), Name: "max-pipeline-timeout", Usage: "The maximum time in minutes you can set in the repo settings before a pipeline gets killed", Value: 120, }, + &cli.StringSliceFlag{ + Sources: cli.EnvVars("WOODPECKER_DEFAULT_WORKFLOW_LABELS"), + Name: "default-workflow-labels", + Usage: "The default label filter to set for workflows that has no label filter set. By default workflows will be allowed to run on any agent, if not specified in the workflow.", + }, &cli.DurationFlag{ - EnvVars: []string{"WOODPECKER_SESSION_EXPIRES"}, + Sources: cli.EnvVars("WOODPECKER_SESSION_EXPIRES"), Name: "session-expires", Usage: "session expiration time", Value: time.Hour * 72, }, &cli.StringSliceFlag{ - EnvVars: []string{"WOODPECKER_ESCALATE"}, - Name: "escalate", - Usage: "images to run in privileged mode", - Value: cli.NewStringSlice(constant.PrivilegedPlugins...), + Sources: cli.EnvVars("WOODPECKER_PLUGINS_PRIVILEGED"), + Name: "plugins-privileged", + Usage: "Allow plugins to run in privileged mode, if environment variable is defined but empty there will be none", }, &cli.StringSliceFlag{ - EnvVars: []string{"WOODPECKER_VOLUME"}, + Sources: cli.EnvVars("WOODPECKER_PLUGINS_TRUSTED_CLONE"), + Name: "plugins-trusted-clone", + Usage: "Plugins which are trusted to handle Git credentials in clone steps", + Value: constant.TrustedClonePlugins, + }, + &cli.StringSliceFlag{ + Sources: cli.EnvVars("WOODPECKER_VOLUME"), Name: "volume", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_DOCKER_CONFIG"}, + Sources: cli.EnvVars("WOODPECKER_DOCKER_CONFIG"), Name: "docker-config", }, &cli.StringSliceFlag{ - EnvVars: []string{"WOODPECKER_ENVIRONMENT"}, + Sources: cli.EnvVars("WOODPECKER_ENVIRONMENT"), Name: "environment", }, &cli.StringSliceFlag{ - EnvVars: []string{"WOODPECKER_NETWORK"}, + Sources: cli.EnvVars("WOODPECKER_NETWORK"), Name: "network", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_AGENT_SECRET"}, - Name: "agent-secret", - Usage: "server-agent shared password", - FilePath: os.Getenv("WOODPECKER_AGENT_SECRET_FILE"), + Sources: cli.NewValueSourceChain( + cli.File(os.Getenv("WOODPECKER_AGENT_SECRET_FILE")), + cli.EnvVar("WOODPECKER_AGENT_SECRET")), + Name: "agent-secret", + Usage: "server-agent shared password", + Config: cli.StringConfig{ + TrimSpace: true, + }, + }, + &cli.BoolFlag{ + Sources: cli.EnvVars("WOODPECKER_DISABLE_USER_AGENT_REGISTRATION"), + Name: "disable-user-agent-registration", + Usage: "Disable user registered agents", }, &cli.DurationFlag{ - EnvVars: []string{"WOODPECKER_KEEPALIVE_MIN_TIME"}, + Sources: cli.EnvVars("WOODPECKER_KEEPALIVE_MIN_TIME"), Name: "keepalive-min-time", Usage: "server-side enforcement policy on the minimum amount of time a client should wait before sending a keepalive ping.", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_CONFIG_SERVICE_ENDPOINT"}, + Sources: cli.EnvVars("WOODPECKER_CONFIG_SERVICE_ENDPOINT"), Name: "config-service-endpoint", Usage: "url used for calling configuration service endpoint", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_DATABASE_DRIVER"}, - Name: "driver", + Sources: cli.EnvVars("WOODPECKER_DATABASE_DRIVER"), + Name: "db-driver", + Aliases: []string{"driver"}, // TODO: remove in v4.0.0 Usage: "database driver", Value: "sqlite3", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_DATABASE_DATASOURCE"}, - Name: "datasource", - Usage: "database driver configuration string", - Value: datasourceDefaultValue(), - FilePath: os.Getenv("WOODPECKER_DATABASE_DATASOURCE_FILE"), + Sources: cli.NewValueSourceChain( + cli.File(os.Getenv("WOODPECKER_DATABASE_DATASOURCE_FILE")), + cli.EnvVar("WOODPECKER_DATABASE_DATASOURCE")), + Name: "db-datasource", + Aliases: []string{"datasource"}, // TODO: remove in v4.0.0 + Usage: "database driver configuration string", + Value: datasourceDefaultValue(), + Config: cli.StringConfig{ + TrimSpace: true, + }, }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_PROMETHEUS_AUTH_TOKEN"}, - Name: "prometheus-auth-token", - Usage: "token to secure prometheus metrics endpoint", - Value: "", - FilePath: os.Getenv("WOODPECKER_PROMETHEUS_AUTH_TOKEN_FILE"), + Sources: cli.NewValueSourceChain( + cli.File(os.Getenv("WOODPECKER_PROMETHEUS_AUTH_TOKEN_FILE")), + cli.EnvVar("WOODPECKER_PROMETHEUS_AUTH_TOKEN")), + Name: "prometheus-auth-token", + Usage: "token to secure prometheus metrics endpoint", + Config: cli.StringConfig{ + TrimSpace: true, + }, }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_STATUS_CONTEXT", "WOODPECKER_GITHUB_CONTEXT", "WOODPECKER_GITEA_CONTEXT"}, + Sources: cli.EnvVars("WOODPECKER_STATUS_CONTEXT", "WOODPECKER_GITHUB_CONTEXT", "WOODPECKER_GITEA_CONTEXT"), Name: "status-context", Usage: "status context prefix", Value: "ci/woodpecker", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_STATUS_CONTEXT_FORMAT"}, + Sources: cli.EnvVars("WOODPECKER_STATUS_CONTEXT_FORMAT"), Name: "status-context-format", Usage: "status context format", Value: "{{ .context }}/{{ .event }}/{{ .workflow }}{{if not (eq .axis_id 0)}}/{{.axis_id}}{{end}}", }, &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_MIGRATIONS_ALLOW_LONG"}, + Sources: cli.EnvVars("WOODPECKER_MIGRATIONS_ALLOW_LONG"), Name: "migrations-allow-long", Value: false, }, &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_ENABLE_SWAGGER"}, + Sources: cli.EnvVars("WOODPECKER_ENABLE_SWAGGER"), Name: "enable-swagger", Value: true, }, &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_DISABLE_VERSION_CHECK"}, + Sources: cli.EnvVars("WOODPECKER_DISABLE_VERSION_CHECK"), Usage: "Disable version check in admin web ui.", Name: "skip-version-check", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_LOG_STORE"}, + Sources: cli.EnvVars("WOODPECKER_LOG_STORE"), Name: "log-store", Usage: "log store to use ('database' or 'file')", Value: "database", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_LOG_STORE_FILE_PATH"}, + Sources: cli.EnvVars("WOODPECKER_LOG_STORE_FILE_PATH"), Name: "log-store-file-path", Usage: "directory used for file based log storage", }, @@ -256,17 +300,17 @@ var flags = append([]cli.Flag{ // backend options for pipeline compiler // &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_BACKEND_NO_PROXY", "NO_PROXY", "no_proxy"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND_NO_PROXY", "NO_PROXY", "no_proxy"), Usage: "if set, pass the environment variable down as \"NO_PROXY\" to steps", Name: "backend-no-proxy", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_BACKEND_HTTP_PROXY", "HTTP_PROXY", "http_proxy"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND_HTTP_PROXY", "HTTP_PROXY", "http_proxy"), Usage: "if set, pass the environment variable down as \"HTTP_PROXY\" to steps", Name: "backend-http-proxy", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_BACKEND_HTTPS_PROXY", "HTTPS_PROXY", "https_proxy"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND_HTTPS_PROXY", "HTTPS_PROXY", "https_proxy"), Usage: "if set, pass the environment variable down as \"HTTPS_PROXY\" to steps", Name: "backend-https-proxy", }, @@ -274,72 +318,85 @@ var flags = append([]cli.Flag{ // resource limit parameters // &cli.DurationFlag{ - EnvVars: []string{"WOODPECKER_FORGE_TIMEOUT"}, + Sources: cli.EnvVars("WOODPECKER_FORGE_TIMEOUT"), Name: "forge-timeout", Usage: "how many seconds before timeout when fetching the Woodpecker configuration from a Forge", - Value: time.Second * 3, + Value: time.Second * 5, }, &cli.UintFlag{ - EnvVars: []string{"WOODPECKER_FORGE_RETRY"}, + Sources: cli.EnvVars("WOODPECKER_FORGE_RETRY"), Name: "forge-retry", Usage: "How many retries of fetching the Woodpecker configuration from a forge are done before we fail", Value: 3, }, - &cli.Int64Flag{ - EnvVars: []string{"WOODPECKER_LIMIT_MEM_SWAP"}, - Name: "limit-mem-swap", - Usage: "maximum memory used for swap in bytes", - }, - &cli.Int64Flag{ - EnvVars: []string{"WOODPECKER_LIMIT_MEM"}, - Name: "limit-mem", - Usage: "maximum memory allowed in bytes", - }, - &cli.Int64Flag{ - EnvVars: []string{"WOODPECKER_LIMIT_SHM_SIZE"}, - Name: "limit-shm-size", - Usage: "docker compose /dev/shm allowed in bytes", - }, - &cli.Int64Flag{ - EnvVars: []string{"WOODPECKER_LIMIT_CPU_QUOTA"}, - Name: "limit-cpu-quota", - Usage: "impose a cpu quota", - }, - &cli.Int64Flag{ - EnvVars: []string{"WOODPECKER_LIMIT_CPU_SHARES"}, - Name: "limit-cpu-shares", - Usage: "change the cpu shares", - }, - &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_LIMIT_CPU_SET"}, - Name: "limit-cpu-set", - Usage: "set the cpus allowed to execute containers", - }, + // + // generic forge settings // &cli.StringFlag{ Name: "forge-url", Usage: "url of the forge", - EnvVars: []string{"WOODPECKER_FORGE_URL", "WOODPECKER_GITHUB_URL", "WOODPECKER_GITLAB_URL", "WOODPECKER_GITEA_URL", "WOODPECKER_FORGEJO_URL", "WOODPECKER_BITBUCKET_URL", "WOODPECKER_BITBUCKET_DC_URL"}, + Sources: cli.EnvVars("WOODPECKER_FORGE_URL", "WOODPECKER_GITHUB_URL", "WOODPECKER_GITLAB_URL", "WOODPECKER_GITEA_URL", "WOODPECKER_FORGEJO_URL", "WOODPECKER_BITBUCKET_URL", "WOODPECKER_BITBUCKET_DC_URL"), }, &cli.StringFlag{ - Name: "forge-oauth-client", - Usage: "oauth2 client id", - EnvVars: []string{"WOODPECKER_FORGE_CLIENT", "WOODPECKER_GITHUB_CLIENT", "WOODPECKER_GITLAB_CLIENT", "WOODPECKER_GITEA_CLIENT", "WOODPECKER_FORGEJO_CLIENT", "WOODPECKER_BITBUCKET_CLIENT", "WOODPECKER_BITBUCKET_DC_CLIENT_ID"}, - FilePath: getFirstNonEmptyEnvVar([]string{"WOODPECKER_FORGE_CLIENT_FILE", "WOODPECKER_GITHUB_CLIENT_FILE", "WOODPECKER_GITLAB_CLIENT_FILE", "WOODPECKER_GITEA_CLIENT_FILE", "WOODPECKER_FORGEJO_CLIENT_FILE", "WOODPECKER_BITBUCKET_CLIENT_FILE", "WOODPECKER_BITBUCKET_DC_CLIENT_ID_FILE"}), + Sources: cli.NewValueSourceChain( + cli.File(getFirstNonEmptyEnvVar( + "WOODPECKER_FORGE_CLIENT_FILE", + "WOODPECKER_GITHUB_CLIENT_FILE", + "WOODPECKER_GITLAB_CLIENT_FILE", + "WOODPECKER_GITEA_CLIENT_FILE", + "WOODPECKER_FORGEJO_CLIENT_FILE", + "WOODPECKER_BITBUCKET_CLIENT_FILE", + "WOODPECKER_BITBUCKET_DC_CLIENT_ID_FILE")), + cli.EnvVar("WOODPECKER_FORGE_CLIENT"), + cli.EnvVar("WOODPECKER_GITHUB_CLIENT"), + cli.EnvVar("WOODPECKER_GITLAB_CLIENT"), + cli.EnvVar("WOODPECKER_GITEA_CLIENT"), + cli.EnvVar("WOODPECKER_FORGEJO_CLIENT"), + cli.EnvVar("WOODPECKER_BITBUCKET_CLIENT"), + cli.EnvVar("WOODPECKER_BITBUCKET_DC_CLIENT_ID")), + Name: "forge-oauth-client", + Usage: "oauth2 client id", + Config: cli.StringConfig{ + TrimSpace: true, + }, }, &cli.StringFlag{ - Name: "forge-oauth-secret", - Usage: "oauth2 client secret", - EnvVars: []string{"WOODPECKER_FORGE_SECRET", "WOODPECKER_GITHUB_SECRET", "WOODPECKER_GITLAB_SECRET", "WOODPECKER_GITEA_SECRET", "WOODPECKER_FORGEJO_SECRET", "WOODPECKER_BITBUCKET_SECRET", "WOODPECKER_BITBUCKET_DC_CLIENT_SECRET"}, - FilePath: getFirstNonEmptyEnvVar([]string{"WOODPECKER_FORGE_SECRET_FILE", "WOODPECKER_GITHUB_SECRET_FILE", "WOODPECKER_GITLAB_SECRET_FILE", "WOODPECKER_GITEA_SECRET_FILE", "WOODPECKER_FORGEJO_SECRET_FILE", "WOODPECKER_BITBUCKET_SECRET_FILE", "WOODPECKER_BITBUCKET_DC_CLIENT_SECRET_FILE"}), + Sources: cli.NewValueSourceChain( + cli.File(getFirstNonEmptyEnvVar( + "WOODPECKER_FORGE_SECRET_FILE", + "WOODPECKER_GITHUB_SECRET_FILE", + "WOODPECKER_GITLAB_SECRET_FILE", + "WOODPECKER_GITEA_SECRET_FILE", + "WOODPECKER_FORGEJO_SECRET_FILE", + "WOODPECKER_BITBUCKET_SECRET_FILE", + "WOODPECKER_BITBUCKET_DC_CLIENT_SECRET_FILE", + )), + cli.EnvVar("WOODPECKER_FORGE_SECRET"), + cli.EnvVar("WOODPECKER_GITHUB_SECRET"), + cli.EnvVar("WOODPECKER_GITLAB_SECRET"), + cli.EnvVar("WOODPECKER_GITEA_SECRET"), + cli.EnvVar("WOODPECKER_FORGEJO_SECRET"), + cli.EnvVar("WOODPECKER_BITBUCKET_SECRET"), + cli.EnvVar("WOODPECKER_BITBUCKET_DC_CLIENT_SECRET")), + Name: "forge-oauth-secret", + Usage: "oauth2 client secret", + Config: cli.StringConfig{ + TrimSpace: true, + }, }, &cli.BoolFlag{ - Name: "forge-skip-verify", - Usage: "skip ssl verification", - EnvVars: []string{"WOODPECKER_FORGE_SKIP_VERIFY", "WOODPECKER_GITHUB_SKIP_VERIFY", "WOODPECKER_GITLAB_SKIP_VERIFY", "WOODPECKER_GITEA_SKIP_VERIFY", "WOODPECKER_FORGEJO_SKIP_VERIFY", "WOODPECKER_BITBUCKET_SKIP_VERIFY"}, + Name: "forge-skip-verify", + Usage: "skip ssl verification", + Sources: cli.EnvVars( + "WOODPECKER_FORGE_SKIP_VERIFY", + "WOODPECKER_GITHUB_SKIP_VERIFY", + "WOODPECKER_GITLAB_SKIP_VERIFY", + "WOODPECKER_GITEA_SKIP_VERIFY", + "WOODPECKER_FORGEJO_SKIP_VERIFY", + "WOODPECKER_BITBUCKET_SKIP_VERIFY"), }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_EXPERT_FORGE_OAUTH_HOST", "WOODPECKER_DEV_GITEA_OAUTH_URL"}, // TODO: remove WOODPECKER_DEV_GITEA_OAUTH_URL in next major release + Sources: cli.EnvVars("WOODPECKER_EXPERT_FORGE_OAUTH_HOST"), Name: "forge-oauth-host", Usage: "!!!for experts!!! fully qualified public forge url. Use it if your forge url WOODPECKER_FORGE_URL or WOODPECKER_GITEA_URL, ... isn't a public url. Format: ://[/]", }, @@ -347,7 +404,7 @@ var flags = append([]cli.Flag{ // Addon // &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_ADDON_FORGE"}, + Sources: cli.EnvVars("WOODPECKER_ADDON_FORGE"), Name: "addon-forge", Usage: "path to forge addon executable", }, @@ -355,18 +412,18 @@ var flags = append([]cli.Flag{ // GitHub // &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_GITHUB"}, + Sources: cli.EnvVars("WOODPECKER_GITHUB"), Name: "github", Usage: "github driver is enabled", }, &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_GITHUB_MERGE_REF"}, + Sources: cli.EnvVars("WOODPECKER_GITHUB_MERGE_REF"), Name: "github-merge-ref", Usage: "github pull requests use merge ref", Value: true, }, &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_GITHUB_PUBLIC_ONLY"}, + Sources: cli.EnvVars("WOODPECKER_GITHUB_PUBLIC_ONLY"), Name: "github-public-only", Usage: "github tokens should only get access to public repos", Value: false, @@ -375,7 +432,7 @@ var flags = append([]cli.Flag{ // Gitea // &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_GITEA"}, + Sources: cli.EnvVars("WOODPECKER_GITEA"), Name: "gitea", Usage: "gitea driver is enabled", }, @@ -383,7 +440,7 @@ var flags = append([]cli.Flag{ // Forgejo // &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_FORGEJO"}, + Sources: cli.EnvVars("WOODPECKER_FORGEJO"), Name: "forgejo", Usage: "forgejo driver is enabled", }, @@ -391,7 +448,7 @@ var flags = append([]cli.Flag{ // Bitbucket // &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_BITBUCKET"}, + Sources: cli.EnvVars("WOODPECKER_BITBUCKET"), Name: "bitbucket", Usage: "bitbucket driver is enabled", }, @@ -399,7 +456,7 @@ var flags = append([]cli.Flag{ // Gitlab // &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_GITLAB"}, + Sources: cli.EnvVars("WOODPECKER_GITLAB"), Name: "gitlab", Usage: "gitlab driver is enabled", }, @@ -407,27 +464,35 @@ var flags = append([]cli.Flag{ // Bitbucket DataCenter/Server (previously Stash) // &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_BITBUCKET_DC"}, + Sources: cli.EnvVars("WOODPECKER_BITBUCKET_DC"), Name: "bitbucket-dc", Usage: "Bitbucket DataCenter/Server driver is enabled", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_BITBUCKET_DC_GIT_USERNAME"}, - Name: "bitbucket-dc-git-username", - Usage: "Bitbucket DataCenter/Server service account username", - FilePath: os.Getenv("WOODPECKER_BITBUCKET_DC_GIT_USERNAME_FILE"), + Sources: cli.NewValueSourceChain( + cli.File(os.Getenv("WOODPECKER_BITBUCKET_DC_GIT_USERNAME_FILE")), + cli.EnvVar("WOODPECKER_BITBUCKET_DC_GIT_USERNAME")), + Name: "bitbucket-dc-git-username", + Usage: "Bitbucket DataCenter/Server service account username", + Config: cli.StringConfig{ + TrimSpace: true, + }, }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_BITBUCKET_DC_GIT_PASSWORD"}, - Name: "bitbucket-dc-git-password", - Usage: "Bitbucket DataCenter/Server service account password", - FilePath: os.Getenv("WOODPECKER_BITBUCKET_DC_GIT_PASSWORD_FILE"), + Sources: cli.NewValueSourceChain( + cli.File(os.Getenv("WOODPECKER_BITBUCKET_DC_GIT_PASSWORD_FILE")), + cli.EnvVar("WOODPECKER_BITBUCKET_DC_GIT_PASSWORD")), + Name: "bitbucket-dc-git-password", + Usage: "Bitbucket DataCenter/Server service account password", + Config: cli.StringConfig{ + TrimSpace: true, + }, }, // // development flags // &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_DEV_WWW_PROXY"}, + Sources: cli.EnvVars("WOODPECKER_DEV_WWW_PROXY"), Name: "www-proxy", Usage: "serve the website by using a proxy (used for development)", Hidden: true, @@ -436,34 +501,30 @@ var flags = append([]cli.Flag{ // expert flags // &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_EXPERT_WEBHOOK_HOST", "WOODPECKER_WEBHOOK_HOST"}, // TODO: remove WOODPECKER_WEBHOOK_HOST in next major release + Sources: cli.EnvVars("WOODPECKER_EXPERT_WEBHOOK_HOST"), Name: "server-webhook-host", Usage: "!!!for experts!!! fully qualified woodpecker server url called by forge's webhooks. Format: ://[/]", }, - // TODO: remove in next major release - &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_DEV_OAUTH_HOST"}, - Name: "server-dev-oauth-host-deprecated", - Usage: "DEPRECATED: use WOODPECKER_EXPERT_FORGE_OAUTH_HOST instead\nfully qualified url used for oauth redirects. Format: ://[/]", - Value: "", - Hidden: true, - }, // // secrets encryption in DB // &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_ENCRYPTION_KEY"}, - Name: "encryption-raw-key", - Usage: "Raw encryption key", - FilePath: os.Getenv("WOODPECKER_ENCRYPTION_KEY_FILE"), + Sources: cli.NewValueSourceChain( + cli.File(os.Getenv("WOODPECKER_ENCRYPTION_KEY_FILE")), + cli.EnvVar("WOODPECKER_ENCRYPTION_KEY")), + Name: "encryption-raw-key", + Usage: "Raw encryption key", + Config: cli.StringConfig{ + TrimSpace: true, + }, }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_ENCRYPTION_TINK_KEYSET_FILE"}, + Sources: cli.EnvVars("WOODPECKER_ENCRYPTION_TINK_KEYSET_FILE"), Name: "encryption-tink-keyset", Usage: "Google tink AEAD-compatible keyset file to encrypt secrets in DB", }, &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_ENCRYPTION_DISABLE"}, + Sources: cli.EnvVars("WOODPECKER_ENCRYPTION_DISABLE"), Name: "encryption-disable-flag", Usage: "Flag to decrypt all encrypted data and disable encryption on server", }, @@ -479,7 +540,7 @@ func datasourceDefaultValue() string { return "woodpecker.sqlite" } -func getFirstNonEmptyEnvVar(envVars []string) string { +func getFirstNonEmptyEnvVar(envVars ...string) string { for _, envVar := range envVars { val := os.Getenv(envVar) if val != "" { diff --git a/cmd/server/grpc_server.go b/cmd/server/grpc_server.go new file mode 100644 index 000000000..7e7cf2a1a --- /dev/null +++ b/cmd/server/grpc_server.go @@ -0,0 +1,88 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "fmt" + "net" + + "github.com/rs/zerolog/log" + "github.com/urfave/cli/v3" + "google.golang.org/grpc" + "google.golang.org/grpc/keepalive" + + "go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc/proto" + "go.woodpecker-ci.org/woodpecker/v3/server" + woodpeckerGrpcServer "go.woodpecker-ci.org/woodpecker/v3/server/grpc" + "go.woodpecker-ci.org/woodpecker/v3/server/store" +) + +func runGrpcServer(ctx context.Context, c *cli.Command, _store store.Store) error { + lis, err := net.Listen("tcp", c.String("grpc-addr")) + if err != nil { + return fmt.Errorf("failed to listen on grpc-addr: %w", err) + } + + jwtSecret := c.String("grpc-secret") + jwtManager := woodpeckerGrpcServer.NewJWTManager(jwtSecret) + + authorizer := woodpeckerGrpcServer.NewAuthorizer(jwtManager) + grpcServer := grpc.NewServer( + grpc.StreamInterceptor(authorizer.StreamInterceptor), + grpc.UnaryInterceptor(authorizer.UnaryInterceptor), + grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{ + MinTime: c.Duration("keepalive-min-time"), + }), + ) + + woodpeckerServer := woodpeckerGrpcServer.NewWoodpeckerServer( + server.Config.Services.Queue, + server.Config.Services.Logs, + server.Config.Services.Pubsub, + _store, + ) + proto.RegisterWoodpeckerServer(grpcServer, woodpeckerServer) + + woodpeckerAuthServer := woodpeckerGrpcServer.NewWoodpeckerAuthServer( + jwtManager, + server.Config.Server.AgentToken, + _store, + ) + proto.RegisterWoodpeckerAuthServer(grpcServer, woodpeckerAuthServer) + + grpcCtx, cancel := context.WithCancelCause(ctx) + defer cancel(nil) + + go func() { + <-grpcCtx.Done() + if grpcServer == nil { + return + } + log.Info().Msg("terminating grpc service gracefully") + grpcServer.GracefulStop() + log.Info().Msg("grpc service stopped") + }() + + if err := grpcServer.Serve(lis); err != nil { + // signal that we don't have to stop the server gracefully anymore + grpcServer = nil + + // wrap the error so we know where it did come from + return fmt.Errorf("grpc server failed: %w", err) + } + + return nil +} diff --git a/cmd/server/health.go b/cmd/server/health.go index eb780b9a9..590bb552a 100644 --- a/cmd/server/health.go +++ b/cmd/server/health.go @@ -16,20 +16,21 @@ package main import ( + "context" "fmt" "net/http" "strings" "time" "github.com/rs/zerolog/log" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) const pingTimeout = 1 * time.Second // handles pinging the endpoint and returns an error if the // server is in an unhealthy state. -func pinger(c *cli.Context) error { +func pinger(_ context.Context, c *cli.Command) error { scheme := "http" serverAddr := c.String("server-addr") if strings.HasPrefix(serverAddr, ":") { @@ -38,7 +39,7 @@ func pinger(c *cli.Context) error { } // if woodpecker do ssl on it's own - if c.String("server-cert") != "" || c.Bool("lets-encrypt") { + if c.String("server-cert") != "" { scheme = "https" } diff --git a/cmd/server/main.go b/cmd/server/main.go index da7f986b4..5ac49ff27 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -12,21 +12,30 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:build !generate +// +build !generate + package main import ( + "context" "os" _ "github.com/joho/godotenv/autoload" "github.com/rs/zerolog/log" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - _ "go.woodpecker-ci.org/woodpecker/v2/cmd/server/docs" - "go.woodpecker-ci.org/woodpecker/v2/version" + _ "go.woodpecker-ci.org/woodpecker/v3/cmd/server/openapi" + "go.woodpecker-ci.org/woodpecker/v3/shared/utils" + "go.woodpecker-ci.org/woodpecker/v3/version" ) func main() { - app := cli.NewApp() + ctx := utils.WithContextSigtermCallback(context.Background(), func() { + log.Info().Msg("termination signal is received, shutting down server") + }) + + app := cli.Command{} app.Name = "woodpecker-server" app.Version = version.String() app.Usage = "woodpecker server" @@ -40,9 +49,9 @@ func main() { } app.Flags = flags - setupSwaggerStaticConfig() + setupOpenAPIStaticConfig() - if err := app.Run(os.Args); err != nil { - log.Fatal().Err(err).Msgf("error running server") //nolint:forbidigo + if err := app.Run(ctx, os.Args); err != nil { + log.Error().Err(err).Msgf("error running server") } } diff --git a/cmd/server/metrics_server.go b/cmd/server/metrics_server.go new file mode 100644 index 000000000..078099b3b --- /dev/null +++ b/cmd/server/metrics_server.go @@ -0,0 +1,108 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "errors" + "time" + + "github.com/prometheus/client_golang/prometheus" + prometheus_auto "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/rs/zerolog/log" + + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/store" +) + +func startMetricsCollector(ctx context.Context, _store store.Store) { + pendingSteps := prometheus_auto.NewGauge(prometheus.GaugeOpts{ + Namespace: "woodpecker", + Name: "pending_steps", + Help: "Total number of pending pipeline steps.", + }) + waitingSteps := prometheus_auto.NewGauge(prometheus.GaugeOpts{ + Namespace: "woodpecker", + Name: "waiting_steps", + Help: "Total number of pipeline waiting on deps.", + }) + runningSteps := prometheus_auto.NewGauge(prometheus.GaugeOpts{ + Namespace: "woodpecker", + Name: "running_steps", + Help: "Total number of running pipeline steps.", + }) + workers := prometheus_auto.NewGauge(prometheus.GaugeOpts{ + Namespace: "woodpecker", + Name: "worker_count", + Help: "Total number of workers.", + }) + pipelines := prometheus_auto.NewGauge(prometheus.GaugeOpts{ + Namespace: "woodpecker", + Name: "pipeline_total_count", + Help: "Total number of pipelines.", + }) + users := prometheus_auto.NewGauge(prometheus.GaugeOpts{ + Namespace: "woodpecker", + Name: "user_count", + Help: "Total number of users.", + }) + repos := prometheus_auto.NewGauge(prometheus.GaugeOpts{ + Namespace: "woodpecker", + Name: "repo_count", + Help: "Total number of repos.", + }) + + go func() { + log.Info().Msg("queue metric collector started") + + for { + stats := server.Config.Services.Queue.Info(ctx) + pendingSteps.Set(float64(stats.Stats.Pending)) + waitingSteps.Set(float64(stats.Stats.WaitingOnDeps)) + runningSteps.Set(float64(stats.Stats.Running)) + workers.Set(float64(stats.Stats.Workers)) + + select { + case <-ctx.Done(): + log.Info().Msg("queue metric collector stopped") + return + case <-time.After(queueInfoRefreshInterval): + } + } + }() + go func() { + log.Info().Msg("store metric collector started") + + for { + repoCount, repoErr := _store.GetRepoCount() + userCount, userErr := _store.GetUserCount() + pipelineCount, pipelineErr := _store.GetPipelineCount() + pipelines.Set(float64(pipelineCount)) + users.Set(float64(userCount)) + repos.Set(float64(repoCount)) + + if err := errors.Join(repoErr, userErr, pipelineErr); err != nil { + log.Error().Err(err).Msg("could not update store information for metrics") + } + + select { + case <-ctx.Done(): + log.Info().Msg("store metric collector stopped") + return + case <-time.After(storeInfoRefreshInterval): + } + } + }() +} diff --git a/cmd/server/openapi.go b/cmd/server/openapi.go new file mode 100644 index 000000000..1d37fc1f1 --- /dev/null +++ b/cmd/server/openapi.go @@ -0,0 +1,38 @@ +// Copyright 2023 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "go.woodpecker-ci.org/woodpecker/v3/cmd/server/openapi" + "go.woodpecker-ci.org/woodpecker/v3/version" +) + +// Generate docs/openapi.json via: +//go:generate go run github.com/swaggo/swag/cmd/swag init -g cmd/server/openapi.go --outputTypes go -output openapi -d ../../ +//go:generate go run openapi_json_gen.go openapi.go +//go:generate go run github.com/getkin/kin-openapi/cmd/validate ../../docs/openapi.json + +// setupOpenAPIStaticConfig initializes static content (version) for the OpenAPI config. +// +// @title Woodpecker CI API +// @description Woodpecker is a simple, yet powerful CI/CD engine with great extensibility. +// @description To get a personal access token (PAT) for authentication, please log in your Woodpecker server, +// @description and go to you personal profile page, by clicking the user icon at the top right. +// @BasePath /api +// @contact.name Woodpecker CI +// @contact.url https://woodpecker-ci.org/ +func setupOpenAPIStaticConfig() { + openapi.SwaggerInfo.Version = version.String() +} diff --git a/cmd/server/docs/docs.go b/cmd/server/openapi/docs.go similarity index 90% rename from cmd/server/docs/docs.go rename to cmd/server/openapi/docs.go index 3113408cc..074fafbab 100644 --- a/cmd/server/docs/docs.go +++ b/cmd/server/openapi/docs.go @@ -1,5 +1,5 @@ -// Package docs Code generated by swaggo/swag. DO NOT EDIT -package docs +// Package openapi Code generated by swaggo/swag. DO NOT EDIT +package openapi import "github.com/swaggo/swag" @@ -10,7 +10,7 @@ const docTemplate = `{ "description": "{{escape .Description}}", "title": "{{.Title}}", "contact": { - "name": "Woodpecker CI Community", + "name": "Woodpecker CI", "url": "https://woodpecker-ci.org/" }, "version": "{{.Version}}" @@ -101,7 +101,7 @@ const docTemplate = `{ } } }, - "/agents/{agent}": { + "/agents/{agent_id}": { "get": { "produces": [ "application/json" @@ -122,7 +122,7 @@ const docTemplate = `{ { "type": "integer", "description": "the agent's id", - "name": "agent", + "name": "agent_id", "in": "path", "required": true } @@ -156,7 +156,7 @@ const docTemplate = `{ { "type": "integer", "description": "the agent's id", - "name": "agent", + "name": "agent_id", "in": "path", "required": true } @@ -187,7 +187,7 @@ const docTemplate = `{ { "type": "integer", "description": "the agent's id", - "name": "agent", + "name": "agent_id", "in": "path", "required": true }, @@ -211,7 +211,7 @@ const docTemplate = `{ } } }, - "/agents/{agent}/tasks": { + "/agents/{agent_id}/tasks": { "get": { "produces": [ "application/json" @@ -232,7 +232,7 @@ const docTemplate = `{ { "type": "integer", "description": "the agent's id", - "name": "agent", + "name": "agent_id", "in": "path", "required": true } @@ -1063,6 +1063,193 @@ const docTemplate = `{ } } }, + "/orgs/{org_id}/agents": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "Agents" + ], + "summary": "List agents for an organization", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cpersonal access token\u003e", + "description": "Insert your personal access token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "the organization's id", + "name": "org_id", + "in": "path", + "required": true + }, + { + "type": "integer", + "default": 1, + "description": "for response pagination, page offset number", + "name": "page", + "in": "query" + }, + { + "type": "integer", + "default": 50, + "description": "for response pagination, max items per page", + "name": "perPage", + "in": "query" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "#/definitions/Agent" + } + } + } + } + }, + "post": { + "description": "Creates a new agent with a random token, scoped to the specified organization", + "produces": [ + "application/json" + ], + "tags": [ + "Agents" + ], + "summary": "Create a new organization-scoped agent", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cpersonal access token\u003e", + "description": "Insert your personal access token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "the organization's id", + "name": "org_id", + "in": "path", + "required": true + }, + { + "description": "the agent's data (only 'name' and 'no_schedule' are read)", + "name": "agent", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Agent" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Agent" + } + } + } + } + }, + "/orgs/{org_id}/agents/{agent_id}": { + "delete": { + "produces": [ + "text/plain" + ], + "tags": [ + "Agents" + ], + "summary": "Delete an organization-scoped agent", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cpersonal access token\u003e", + "description": "Insert your personal access token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "the organization's id", + "name": "org_id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "the agent's id", + "name": "agent_id", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "description": "No Content" + } + } + }, + "patch": { + "produces": [ + "application/json" + ], + "tags": [ + "Agents" + ], + "summary": "Update an organization-scoped agent", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cpersonal access token\u003e", + "description": "Insert your personal access token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "the organization's id", + "name": "org_id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "the agent's id", + "name": "agent_id", + "in": "path", + "required": true + }, + { + "description": "the agent's updated data", + "name": "agent", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/Agent" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/Agent" + } + } + } + } + }, "/orgs/{org_id}/permissions": { "get": { "produces": [ @@ -2779,6 +2966,30 @@ const docTemplate = `{ "description": "only return pipelines after this RFC3339 date", "name": "after", "in": "query" + }, + { + "type": "string", + "description": "filter pipelines by branch", + "name": "branch", + "in": "query" + }, + { + "type": "string", + "description": "filter pipelines by webhook events (comma separated)", + "name": "event", + "in": "query" + }, + { + "type": "string", + "description": "filter pipelines by strings contained in ref", + "name": "ref", + "in": "query" + }, + { + "type": "string", + "description": "filter pipelines by status", + "name": "status", + "in": "query" } ], "responses": { @@ -3144,6 +3355,49 @@ const docTemplate = `{ } } }, + "/repos/{repo_id}/pipelines/{number}/metadata": { + "get": { + "produces": [ + "application/json" + ], + "tags": [ + "Pipelines" + ], + "summary": "Get metadata for a pipeline or a specific workflow, including previous pipeline info", + "parameters": [ + { + "type": "string", + "default": "Bearer \u003cpersonal access token\u003e", + "description": "Insert your personal access token", + "name": "Authorization", + "in": "header", + "required": true + }, + { + "type": "integer", + "description": "the repository id", + "name": "repo_id", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "the number of the pipeline", + "name": "number", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/metadata.Metadata" + } + } + } + } + }, "/repos/{repo_id}/pull_requests": { "get": { "produces": [ @@ -4324,22 +4578,15 @@ const docTemplate = `{ "200": { "description": "OK", "schema": { - "allOf": [ - { + "type": "object", + "properties": { + "source": { "type": "string" }, - { - "type": "object", - "properties": { - "source": { - "type": "string" - }, - "version": { - "type": "string" - } - } + "version": { + "type": "string" } - ] + } } } } @@ -4359,6 +4606,12 @@ const docTemplate = `{ "created": { "type": "integer" }, + "custom_labels": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, "id": { "type": "integer" }, @@ -4375,6 +4628,10 @@ const docTemplate = `{ "no_schedule": { "type": "boolean" }, + "org_id": { + "description": "OrgID is counted as unset if set to -1, this is done to ensure a new(Agent) still enforce the OrgID check by default", + "type": "integer" + }, "owner_id": { "type": "integer" }, @@ -4415,8 +4672,7 @@ const docTemplate = `{ "branch": { "type": "string" }, - "created_at": { - "description": "TODO change JSON field to \"created\" in 3.0", + "created": { "type": "integer" }, "creator_id": { @@ -4458,15 +4714,13 @@ const docTemplate = `{ "commit": { "type": "string" }, - "created_at": { - "description": "TODO change JSON field to \"created\" in 3.0", + "created": { "type": "integer" }, "event": { "type": "string" }, - "finished_at": { - "description": "TODO change JSON field to \"finished\" in 3.0", + "finished": { "type": "integer" }, "id": { @@ -4487,8 +4741,7 @@ const docTemplate = `{ "repo_id": { "type": "integer" }, - "started_at": { - "description": "TODO change JSON field to \"started\" in 3.0", + "started": { "type": "integer" }, "status": { @@ -4645,8 +4898,7 @@ const docTemplate = `{ "commit": { "type": "string" }, - "created_at": { - "description": "TODO change JSON field to \"created\" in 3.0", + "created": { "type": "integer" }, "deploy_task": { @@ -4664,13 +4916,15 @@ const docTemplate = `{ "event": { "$ref": "#/definitions/WebhookEvent" }, - "finished_at": { - "description": "TODO change JSON field to \"finished\" in 3.0", + "finished": { "type": "integer" }, "forge_url": { "type": "string" }, + "from_fork": { + "type": "boolean" + }, "id": { "type": "integer" }, @@ -4698,8 +4952,7 @@ const docTemplate = `{ "refspec": { "type": "string" }, - "reviewed_at": { - "description": "TODO change JSON field to \"reviewed\" in 3.0", + "reviewed": { "type": "integer" }, "reviewed_by": { @@ -4709,8 +4962,7 @@ const docTemplate = `{ "description": "uses reported user for webhooks and name of cron for cron pipelines", "type": "string" }, - "started_at": { - "description": "TODO change JSON field to \"started\" in 3.0", + "started": { "type": "integer" }, "status": { @@ -4722,8 +4974,7 @@ const docTemplate = `{ "title": { "type": "string" }, - "updated_at": { - "description": "TODO change JSON field to \"updated\" in 3.0", + "updated": { "type": "integer" }, "variables": { @@ -4837,17 +5088,17 @@ const docTemplate = `{ "full_name": { "type": "string" }, - "gated": { - "type": "boolean" - }, "id": { "type": "integer" }, "name": { "type": "string" }, - "netrc_only_trusted": { - "type": "boolean" + "netrc_trusted": { + "type": "array", + "items": { + "type": "string" + } }, "org_id": { "type": "integer" @@ -4861,14 +5112,14 @@ const docTemplate = `{ "private": { "type": "boolean" }, - "scm": { - "$ref": "#/definitions/SCMKind" + "require_approval": { + "$ref": "#/definitions/model.ApprovalMode" }, "timeout": { "type": "integer" }, "trusted": { - "type": "boolean" + "$ref": "#/definitions/model.TrustedConfiguration" }, "visibility": { "$ref": "#/definitions/RepoVisibility" @@ -4893,17 +5144,20 @@ const docTemplate = `{ "config_file": { "type": "string" }, - "gated": { - "type": "boolean" + "netrc_trusted": { + "type": "array", + "items": { + "type": "string" + } }, - "netrc_only_trusted": { - "type": "boolean" + "require_approval": { + "type": "string" }, "timeout": { "type": "integer" }, "trusted": { - "type": "boolean" + "$ref": "#/definitions/model.TrustedConfigurationPatch" }, "visibility": { "type": "string" @@ -4923,21 +5177,6 @@ const docTemplate = `{ "VisibilityInternal" ] }, - "SCMKind": { - "type": "string", - "enum": [ - "git", - "hg", - "fossil", - "perforce" - ], - "x-enum-varnames": [ - "RepoGit", - "RepoHg", - "RepoFossil", - "RepoPerforce" - ] - }, "Secret": { "type": "object", "properties": { @@ -5012,15 +5251,15 @@ const docTemplate = `{ "Step": { "type": "object", "properties": { - "end_time": { - "type": "integer" - }, "error": { "type": "string" }, "exit_code": { "type": "integer" }, + "finished": { + "type": "integer" + }, "id": { "type": "integer" }, @@ -5036,7 +5275,7 @@ const docTemplate = `{ "ppid": { "type": "integer" }, - "start_time": { + "started": { "type": "integer" }, "state": { @@ -5073,12 +5312,6 @@ const docTemplate = `{ "agent_id": { "type": "integer" }, - "data": { - "type": "array", - "items": { - "type": "integer" - } - }, "dep_status": { "type": "object", "additionalProperties": { @@ -5163,6 +5396,257 @@ const docTemplate = `{ "EventManual" ] }, + "metadata.Author": { + "type": "object", + "properties": { + "avatar": { + "type": "string" + }, + "email": { + "type": "string" + }, + "name": { + "type": "string" + } + } + }, + "metadata.Commit": { + "type": "object", + "properties": { + "author": { + "$ref": "#/definitions/metadata.Author" + }, + "branch": { + "type": "string" + }, + "changed_files": { + "type": "array", + "items": { + "type": "string" + } + }, + "is_prerelease": { + "type": "boolean" + }, + "labels": { + "type": "array", + "items": { + "type": "string" + } + }, + "message": { + "type": "string" + }, + "ref": { + "type": "string" + }, + "refspec": { + "type": "string" + }, + "sha": { + "type": "string" + } + } + }, + "metadata.Forge": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "url": { + "type": "string" + } + } + }, + "metadata.Metadata": { + "type": "object", + "properties": { + "curr": { + "$ref": "#/definitions/metadata.Pipeline" + }, + "forge": { + "$ref": "#/definitions/metadata.Forge" + }, + "id": { + "type": "string" + }, + "prev": { + "$ref": "#/definitions/metadata.Pipeline" + }, + "repo": { + "$ref": "#/definitions/metadata.Repo" + }, + "step": { + "$ref": "#/definitions/metadata.Step" + }, + "sys": { + "$ref": "#/definitions/metadata.System" + }, + "workflow": { + "$ref": "#/definitions/metadata.Workflow" + } + } + }, + "metadata.Pipeline": { + "type": "object", + "properties": { + "commit": { + "$ref": "#/definitions/metadata.Commit" + }, + "created": { + "type": "integer" + }, + "cron": { + "type": "string" + }, + "event": { + "type": "string" + }, + "finished": { + "type": "integer" + }, + "forge_url": { + "type": "string" + }, + "number": { + "type": "integer" + }, + "parent": { + "type": "integer" + }, + "started": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "target": { + "type": "string" + }, + "task": { + "type": "string" + } + } + }, + "metadata.Repo": { + "type": "object", + "properties": { + "clone_url": { + "type": "string" + }, + "clone_url_ssh": { + "type": "string" + }, + "default_branch": { + "type": "string" + }, + "forge_url": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "owner": { + "type": "string" + }, + "private": { + "type": "boolean" + }, + "remote_id": { + "type": "string" + }, + "trusted": { + "$ref": "#/definitions/metadata.TrustedConfiguration" + } + } + }, + "metadata.Step": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "number": { + "type": "integer" + } + } + }, + "metadata.System": { + "type": "object", + "properties": { + "arch": { + "type": "string" + }, + "host": { + "type": "string" + }, + "name": { + "type": "string" + }, + "url": { + "type": "string" + }, + "version": { + "type": "string" + } + } + }, + "metadata.TrustedConfiguration": { + "type": "object", + "properties": { + "network": { + "type": "boolean" + }, + "security": { + "type": "boolean" + }, + "volumes": { + "type": "boolean" + } + } + }, + "metadata.Workflow": { + "type": "object", + "properties": { + "matrix": { + "type": "object", + "additionalProperties": { + "type": "string" + } + }, + "name": { + "type": "string" + }, + "number": { + "type": "integer" + } + } + }, + "model.ApprovalMode": { + "type": "string", + "enum": [ + "none", + "forks", + "pull_requests", + "all_events" + ], + "x-enum-comments": { + "RequireApprovalAllEvents": "require approval for all external events", + "RequireApprovalForks": "require approval for PRs from forks (default)", + "RequireApprovalNone": "require approval for no events", + "RequireApprovalPullRequests": "require approval for all PRs" + }, + "x-enum-varnames": [ + "RequireApprovalNone", + "RequireApprovalForks", + "RequireApprovalPullRequests", + "RequireApprovalAllEvents" + ] + }, "model.ForgeType": { "type": "string", "enum": [ @@ -5184,6 +5668,34 @@ const docTemplate = `{ "ForgeTypeAddon" ] }, + "model.TrustedConfiguration": { + "type": "object", + "properties": { + "network": { + "type": "boolean" + }, + "security": { + "type": "boolean" + }, + "volumes": { + "type": "boolean" + } + } + }, + "model.TrustedConfigurationPatch": { + "type": "object", + "properties": { + "network": { + "type": "boolean" + }, + "security": { + "type": "boolean" + }, + "volumes": { + "type": "boolean" + } + } + }, "model.Workflow": { "type": "object", "properties": { @@ -5196,9 +5708,6 @@ const docTemplate = `{ "$ref": "#/definitions/Step" } }, - "end_time": { - "type": "integer" - }, "environ": { "type": "object", "additionalProperties": { @@ -5208,6 +5717,9 @@ const docTemplate = `{ "error": { "type": "string" }, + "finished": { + "type": "integer" + }, "id": { "type": "integer" }, @@ -5223,7 +5735,7 @@ const docTemplate = `{ "platform": { "type": "string" }, - "start_time": { + "started": { "type": "integer" }, "state": { @@ -5277,10 +5789,10 @@ const docTemplate = `{ var SwaggerInfo = &swag.Spec{ Version: "", Host: "", - BasePath: "", + BasePath: "/api", Schemes: []string{}, - Title: "", - Description: "", + Title: "Woodpecker CI API", + Description: "Woodpecker is a simple, yet powerful CI/CD engine with great extensibility.\nTo get a personal access token (PAT) for authentication, please log in your Woodpecker server,\nand go to you personal profile page, by clicking the user icon at the top right.", InfoInstanceName: "swagger", SwaggerTemplate: docTemplate, LeftDelim: "{{", diff --git a/cmd/server/woodpecker_docs_gen.go b/cmd/server/openapi_json_gen.go similarity index 67% rename from cmd/server/woodpecker_docs_gen.go rename to cmd/server/openapi_json_gen.go index 9807c92d0..73474b8e3 100644 --- a/cmd/server/woodpecker_docs_gen.go +++ b/cmd/server/openapi_json_gen.go @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -// ************************************************************************************************ -// This is a generator tool, to update the Markdown documentation for the woodpecker-ci.org website -// ************************************************************************************************ +// ********************************************************* +// This is a generator tool, to update the openapi.json file +// ********************************************************* //go:build generate // +build generate @@ -24,28 +24,30 @@ package main import ( "context" "encoding/json" + "fmt" "os" "path" "github.com/getkin/kin-openapi/openapi2" "github.com/getkin/kin-openapi/openapi2conv" - "go.woodpecker-ci.org/woodpecker/v2/cmd/server/docs" + + "go.woodpecker-ci.org/woodpecker/v3/cmd/server/openapi" ) func main() { - // set swagger infos - setupSwaggerStaticConfig() + // set openapi infos + setupOpenAPIStaticConfig() basePath := path.Join("..", "..") - filePath := path.Join(basePath, "docs", "swagger.json") + filePath := path.Join(basePath, "docs", "openapi.json") - // generate swagger file + // generate openapi file f, err := os.Create(filePath) if err != nil { panic(err) } defer f.Close() - doc := docs.SwaggerInfo.ReadDoc() + doc := openapi.SwaggerInfo.ReadDoc() doc, err = removeHost(doc) if err != nil { panic(err) @@ -55,8 +57,11 @@ func main() { panic(err) } + fmt.Println("generated openapi.json") + // convert to OpenApi3 if err := toOpenApi3(filePath, filePath); err != nil { + fmt.Printf("converting '%s' from openapi v2 to v3 failed\n", filePath) panic(err) } } @@ -77,18 +82,18 @@ func removeHost(jsonIn string) (string, error) { func toOpenApi3(input, output string) error { data2, err := os.ReadFile(input) if err != nil { - return err + return fmt.Errorf("read input: %w", err) } var doc2 openapi2.T err = json.Unmarshal(data2, &doc2) if err != nil { - return err + return fmt.Errorf("unmarshal input: %w", err) } doc3, err := openapi2conv.ToV3(&doc2) if err != nil { - return err + return fmt.Errorf("convert openapi v2 to v3: %w", err) } err = doc3.Validate(context.Background()) if err != nil { @@ -97,8 +102,12 @@ func toOpenApi3(input, output string) error { data, err := json.Marshal(doc3) if err != nil { - return err + return fmt.Errorf("Marshal converted: %w", err) } - return os.WriteFile(output, data, 0644) + if err = os.WriteFile(output, data, 0o644); err != nil { + return fmt.Errorf("write output: %w", err) + } + + return nil } diff --git a/cmd/server/openapi_test.go b/cmd/server/openapi_test.go new file mode 100644 index 000000000..448aa1fad --- /dev/null +++ b/cmd/server/openapi_test.go @@ -0,0 +1,14 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "go.woodpecker-ci.org/woodpecker/v3/cmd/server/openapi" +) + +func TestSetupOpenApiStaticConfig(t *testing.T) { + setupOpenAPIStaticConfig() + assert.Equal(t, "/api", openapi.SwaggerInfo.BasePath) +} diff --git a/cmd/server/server.go b/cmd/server/server.go index 525b01034..b6ab3c3b7 100644 --- a/cmd/server/server.go +++ b/cmd/server/server.go @@ -15,42 +15,59 @@ package main import ( + "context" "crypto/tls" "errors" "fmt" - "net" "net/http" "net/http/httputil" "net/url" "strings" "time" - "github.com/caddyserver/certmagic" "github.com/gin-gonic/gin" prometheus_http "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" "golang.org/x/sync/errgroup" - "google.golang.org/grpc" - "google.golang.org/grpc/keepalive" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc/proto" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/cron" - woodpeckerGrpcServer "go.woodpecker-ci.org/woodpecker/v2/server/grpc" - "go.woodpecker-ci.org/woodpecker/v2/server/router" - "go.woodpecker-ci.org/woodpecker/v2/server/router/middleware" - "go.woodpecker-ci.org/woodpecker/v2/server/web" - "go.woodpecker-ci.org/woodpecker/v2/shared/logger" - "go.woodpecker-ci.org/woodpecker/v2/version" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/cron" + "go.woodpecker-ci.org/woodpecker/v3/server/router" + "go.woodpecker-ci.org/woodpecker/v3/server/router/middleware" + "go.woodpecker-ci.org/woodpecker/v3/server/web" + "go.woodpecker-ci.org/woodpecker/v3/shared/logger" + "go.woodpecker-ci.org/woodpecker/v3/version" ) -func run(c *cli.Context) error { - if err := logger.SetupGlobalLogger(c, true); err != nil { +const ( + shutdownTimeout = time.Second * 5 +) + +var ( + stopServerFunc context.CancelCauseFunc = func(error) {} + shutdownCancelFunc context.CancelFunc = func() {} + shutdownCtx = context.Background() +) + +func run(ctx context.Context, c *cli.Command) error { + if err := logger.SetupGlobalLogger(ctx, c, true); err != nil { return err } + ctx, ctxCancel := context.WithCancelCause(ctx) + stopServerFunc = func(err error) { + if err != nil { + log.Error().Err(err).Msg("shutdown of whole server") + } + stopServerFunc = func(error) {} + shutdownCtx, shutdownCancelFunc = context.WithTimeout(shutdownCtx, shutdownTimeout) + ctxCancel(err) + } + defer stopServerFunc(nil) + defer shutdownCancelFunc() + // set gin mode based on log level if zerolog.GlobalLevel() > zerolog.DebugLevel { gin.SetMode(gin.ReleaseMode) @@ -74,7 +91,7 @@ func run(c *cli.Context) error { ) } - _store, err := setupStore(c) + _store, err := setupStore(ctx, c) if err != nil { return fmt.Errorf("can't setup store: %w", err) } @@ -84,56 +101,33 @@ func run(c *cli.Context) error { } }() - err = setupEvilGlobals(c, _store) + err = setupEvilGlobals(ctx, c, _store) if err != nil { return fmt.Errorf("can't setup globals: %w", err) } - var g errgroup.Group + // wait for all services until one do stops with an error + serviceWaitingGroup := errgroup.Group{} - setupMetrics(&g, _store) + log.Info().Msgf("starting Woodpecker server with version '%s'", version.String()) - g.Go(func() error { - return cron.Start(c.Context, _store) + serviceWaitingGroup.Go(func() error { + log.Info().Msg("starting cron service ...") + if err := cron.Run(ctx, _store); err != nil { + go stopServerFunc(err) + return err + } + log.Info().Msg("cron service stopped") + return nil }) // start the grpc server - g.Go(func() error { - lis, err := net.Listen("tcp", c.String("grpc-addr")) - if err != nil { - log.Fatal().Err(err).Msg("failed to listen on grpc-addr") //nolint:forbidigo - } - - jwtSecret := c.String("grpc-secret") - jwtManager := woodpeckerGrpcServer.NewJWTManager(jwtSecret) - - authorizer := woodpeckerGrpcServer.NewAuthorizer(jwtManager) - grpcServer := grpc.NewServer( - grpc.StreamInterceptor(authorizer.StreamInterceptor), - grpc.UnaryInterceptor(authorizer.UnaryInterceptor), - grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{ - MinTime: c.Duration("keepalive-min-time"), - }), - ) - - woodpeckerServer := woodpeckerGrpcServer.NewWoodpeckerServer( - server.Config.Services.Queue, - server.Config.Services.Logs, - server.Config.Services.Pubsub, - _store, - ) - proto.RegisterWoodpeckerServer(grpcServer, woodpeckerServer) - - woodpeckerAuthServer := woodpeckerGrpcServer.NewWoodpeckerAuthServer( - jwtManager, - server.Config.Server.AgentToken, - _store, - ) - proto.RegisterWoodpeckerAuthServer(grpcServer, woodpeckerAuthServer) - - err = grpcServer.Serve(lis) - if err != nil { - log.Fatal().Err(err).Msg("failed to serve grpc server") //nolint:forbidigo + serviceWaitingGroup.Go(func() error { + log.Info().Msg("starting grpc server ...") + if err := runGrpcServer(ctx, c, _store); err != nil { + // stop whole server as grpc is essential + go stopServerFunc(err) + return err } return nil }) @@ -170,23 +164,35 @@ func run(c *cli.Context) error { middleware.Store(_store), ) - switch { - case c.String("server-cert") != "": + if c.String("server-cert") != "" { // start the server with tls enabled - g.Go(func() error { - serve := &http.Server{ + serviceWaitingGroup.Go(func() error { + tlsServer := &http.Server{ Addr: server.Config.Server.PortTLS, Handler: handler, TLSConfig: &tls.Config{ NextProtos: []string{"h2", "http/1.1"}, }, } - err = serve.ListenAndServeTLS( + + go func() { + <-ctx.Done() + log.Info().Msg("shutdown tls server ...") + if err := tlsServer.Shutdown(shutdownCtx); err != nil { //nolint:contextcheck + log.Error().Err(err).Msg("shutdown tls server failed") + } else { + log.Info().Msg("tls server stopped") + } + }() + + log.Info().Msg("starting tls server ...") + err := tlsServer.ListenAndServeTLS( c.String("server-cert"), c.String("server-key"), ) if err != nil && !errors.Is(err, http.ErrServerClosed) { - log.Fatal().Err(err).Msg("failed to start server with tls") //nolint:forbidigo + log.Error().Err(err).Msg("TLS server failed") + stopServerFunc(fmt.Errorf("TLS server failed: %w", err)) } return err }) @@ -202,56 +208,85 @@ func run(c *cli.Context) error { http.Redirect(w, req, req.URL.String(), http.StatusMovedPermanently) } - g.Go(func() error { - err := http.ListenAndServe(server.Config.Server.Port, http.HandlerFunc(redirect)) - if err != nil && !errors.Is(err, http.ErrServerClosed) { - log.Fatal().Err(err).Msg("unable to start server to redirect from http to https") //nolint:forbidigo + serviceWaitingGroup.Go(func() error { + redirectServer := &http.Server{ + Addr: server.Config.Server.Port, + Handler: http.HandlerFunc(redirect), } - return err - }) - case c.Bool("lets-encrypt"): - // start the server with lets-encrypt - certmagic.DefaultACME.Email = c.String("lets-encrypt-email") - certmagic.DefaultACME.Agreed = true + go func() { + <-ctx.Done() + log.Info().Msg("shutdown redirect server ...") + if err := redirectServer.Shutdown(shutdownCtx); err != nil { //nolint:contextcheck + log.Error().Err(err).Msg("shutdown redirect server failed") + } else { + log.Info().Msg("redirect server stopped") + } + }() - address, err := url.Parse(strings.TrimSuffix(c.String("server-host"), "/")) - if err != nil { - return err - } - - g.Go(func() error { - if err := certmagic.HTTPS([]string{address.Host}, handler); err != nil { - log.Fatal().Err(err).Msg("certmagic does not work") //nolint:forbidigo + log.Info().Msg("starting redirect server ...") + if err := redirectServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + log.Error().Err(err).Msg("redirect server failed") + stopServerFunc(fmt.Errorf("redirect server failed: %w", err)) } return nil }) - default: + } else { // start the server without tls - g.Go(func() error { - err := http.ListenAndServe( - c.String("server-addr"), - handler, - ) - if err != nil && !errors.Is(err, http.ErrServerClosed) { - log.Fatal().Err(err).Msg("could not start server") //nolint:forbidigo + serviceWaitingGroup.Go(func() error { + httpServer := &http.Server{ + Addr: c.String("server-addr"), + Handler: handler, + } + + go func() { + <-ctx.Done() + log.Info().Msg("shutdown http server ...") + if err := httpServer.Shutdown(shutdownCtx); err != nil { //nolint:contextcheck + log.Error().Err(err).Msg("shutdown http server failed") + } else { + log.Info().Msg("http server stopped") + } + }() + + log.Info().Msg("starting http server ...") + if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + log.Error().Err(err).Msg("http server failed") + stopServerFunc(fmt.Errorf("http server failed: %w", err)) } return err }) } if metricsServerAddr := c.String("metrics-server-addr"); metricsServerAddr != "" { - g.Go(func() error { + startMetricsCollector(ctx, _store) + + serviceWaitingGroup.Go(func() error { metricsRouter := gin.New() metricsRouter.GET("/metrics", gin.WrapH(prometheus_http.Handler())) - err := http.ListenAndServe(metricsServerAddr, metricsRouter) - if err != nil && !errors.Is(err, http.ErrServerClosed) { - log.Fatal().Err(err).Msg("could not start metrics server") //nolint:forbidigo + + metricsServer := &http.Server{ + Addr: metricsServerAddr, + Handler: metricsRouter, + } + + go func() { + <-ctx.Done() + log.Info().Msg("shutdown metrics server ...") + if err := metricsServer.Shutdown(shutdownCtx); err != nil { //nolint:contextcheck + log.Error().Err(err).Msg("shutdown metrics server failed") + } else { + log.Info().Msg("metrics server stopped") + } + }() + + log.Info().Msg("starting metrics server ...") + if err := metricsServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { + log.Error().Err(err).Msg("metrics server failed") + stopServerFunc(fmt.Errorf("metrics server failed: %w", err)) } return err }) } - log.Info().Msgf("starting Woodpecker server with version '%s'", version.String()) - - return g.Wait() + return serviceWaitingGroup.Wait() } diff --git a/cmd/server/setup.go b/cmd/server/setup.go index 3c885dbaf..b36c83dbd 100644 --- a/cmd/server/setup.go +++ b/cmd/server/setup.go @@ -26,35 +26,39 @@ import ( "time" "github.com/gorilla/securecookie" - "github.com/prometheus/client_golang/prometheus" - prometheus_auto "github.com/prometheus/client_golang/prometheus/promauto" "github.com/rs/zerolog/log" - "github.com/urfave/cli/v2" - "golang.org/x/sync/errgroup" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/cache" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/setup" - "go.woodpecker-ci.org/woodpecker/v2/server/logging" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/pubsub" - "go.woodpecker-ci.org/woodpecker/v2/server/queue" - "go.woodpecker-ci.org/woodpecker/v2/server/services" - logService "go.woodpecker-ci.org/woodpecker/v2/server/services/log" - "go.woodpecker-ci.org/woodpecker/v2/server/services/log/file" - "go.woodpecker-ci.org/woodpecker/v2/server/services/permissions" - "go.woodpecker-ci.org/woodpecker/v2/server/store" - "go.woodpecker-ci.org/woodpecker/v2/server/store/datastore" - "go.woodpecker-ci.org/woodpecker/v2/server/store/types" - "go.woodpecker-ci.org/woodpecker/v2/shared/constant" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/cache" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/setup" + "go.woodpecker-ci.org/woodpecker/v3/server/logging" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/pubsub" + "go.woodpecker-ci.org/woodpecker/v3/server/queue" + "go.woodpecker-ci.org/woodpecker/v3/server/services" + logService "go.woodpecker-ci.org/woodpecker/v3/server/services/log" + "go.woodpecker-ci.org/woodpecker/v3/server/services/log/file" + "go.woodpecker-ci.org/woodpecker/v3/server/services/permissions" + "go.woodpecker-ci.org/woodpecker/v3/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/store/datastore" + "go.woodpecker-ci.org/woodpecker/v3/server/store/types" ) -func setupStore(c *cli.Context) (store.Store, error) { - datasource := c.String("datasource") - driver := c.String("driver") +const ( + queueInfoRefreshInterval = 500 * time.Millisecond + storeInfoRefreshInterval = 10 * time.Second +) + +func setupStore(ctx context.Context, c *cli.Command) (store.Store, error) { + datasource := c.String("db-datasource") + driver := c.String("db-driver") xorm := store.XORM{ - Log: c.Bool("log-xorm"), - ShowSQL: c.Bool("log-xorm-sql"), + Log: c.Bool("db-log"), + ShowSQL: c.Bool("db-log-sql"), + MaxOpenConns: int(c.Int("db-max-open-connections")), + MaxIdleConns: int(c.Int("db-max-idle-connections")), + ConnMaxLifetime: c.Duration("db-max-connection-timeout"), } if driver == "sqlite3" { @@ -80,13 +84,13 @@ func setupStore(c *cli.Context) (store.Store, error) { Config: datasource, XORM: xorm, } - log.Trace().Msgf("setup datastore: %#v", *opts) + log.Debug().Str("driver", driver).Any("xorm", xorm).Msg("setting up datastore") store, err := datastore.NewEngine(opts) if err != nil { return nil, fmt.Errorf("could not open datastore: %w", err) } - if err := store.Migrate(c.Bool("migrations-allow-long")); err != nil { + if err := store.Migrate(ctx, c.Bool("migrations-allow-long")); err != nil { return nil, fmt.Errorf("could not migrate datastore: %w", err) } @@ -102,75 +106,18 @@ func checkSqliteFileExist(path string) error { return err } -func setupQueue(c *cli.Context, s store.Store) queue.Queue { - return queue.WithTaskStore(queue.New(c.Context), s) +func setupQueue(ctx context.Context, s store.Store) (queue.Queue, error) { + return queue.New(ctx, queue.Config{ + Backend: queue.TypeMemory, + Store: s, + }) } -func setupMembershipService(_ *cli.Context, _store store.Store) cache.MembershipService { +func setupMembershipService(_ context.Context, _store store.Store) cache.MembershipService { return cache.NewMembershipService(_store) } -func setupMetrics(g *errgroup.Group, _store store.Store) { - pendingSteps := prometheus_auto.NewGauge(prometheus.GaugeOpts{ - Namespace: "woodpecker", - Name: "pending_steps", - Help: "Total number of pending pipeline steps.", - }) - waitingSteps := prometheus_auto.NewGauge(prometheus.GaugeOpts{ - Namespace: "woodpecker", - Name: "waiting_steps", - Help: "Total number of pipeline waiting on deps.", - }) - runningSteps := prometheus_auto.NewGauge(prometheus.GaugeOpts{ - Namespace: "woodpecker", - Name: "running_steps", - Help: "Total number of running pipeline steps.", - }) - workers := prometheus_auto.NewGauge(prometheus.GaugeOpts{ - Namespace: "woodpecker", - Name: "worker_count", - Help: "Total number of workers.", - }) - pipelines := prometheus_auto.NewGauge(prometheus.GaugeOpts{ - Namespace: "woodpecker", - Name: "pipeline_total_count", - Help: "Total number of pipelines.", - }) - users := prometheus_auto.NewGauge(prometheus.GaugeOpts{ - Namespace: "woodpecker", - Name: "user_count", - Help: "Total number of users.", - }) - repos := prometheus_auto.NewGauge(prometheus.GaugeOpts{ - Namespace: "woodpecker", - Name: "repo_count", - Help: "Total number of repos.", - }) - - g.Go(func() error { - for { - stats := server.Config.Services.Queue.Info(context.TODO()) - pendingSteps.Set(float64(stats.Stats.Pending)) - waitingSteps.Set(float64(stats.Stats.WaitingOnDeps)) - runningSteps.Set(float64(stats.Stats.Running)) - workers.Set(float64(stats.Stats.Workers)) - time.Sleep(500 * time.Millisecond) - } - }) - g.Go(func() error { - for { - repoCount, _ := _store.GetRepoCount() - userCount, _ := _store.GetUserCount() - pipelineCount, _ := _store.GetPipelineCount() - pipelines.Set(float64(pipelineCount)) - users.Set(float64(userCount)) - repos.Set(float64(repoCount)) - time.Sleep(10 * time.Second) - } - }) -} - -func setupLogStore(c *cli.Context, s store.Store) (logService.Service, error) { +func setupLogStore(c *cli.Command, s store.Store) (logService.Service, error) { switch c.String("log-store") { case "file": return file.NewLogStore(c.String("log-store-file-path")) @@ -202,29 +149,34 @@ func setupJWTSecret(_store store.Store) (string, error) { return jwtSecret, nil } -func setupEvilGlobals(c *cli.Context, s store.Store) error { +func setupEvilGlobals(ctx context.Context, c *cli.Command, s store.Store) (err error) { // services - server.Config.Services.Queue = setupQueue(c, s) server.Config.Services.Logs = logging.New() server.Config.Services.Pubsub = pubsub.New() - server.Config.Services.Membership = setupMembershipService(c, s) - serviceManager, err := services.NewManager(c, s, setup.Forge) + server.Config.Services.Membership = setupMembershipService(ctx, s) + server.Config.Services.Queue, err = setupQueue(ctx, s) + if err != nil { + return fmt.Errorf("could not setup queue: %w", err) + } + server.Config.Services.Manager, err = services.NewManager(c, s, setup.Forge) if err != nil { return fmt.Errorf("could not setup service manager: %w", err) } - server.Config.Services.Manager = serviceManager - server.Config.Services.LogStore, err = setupLogStore(c, s) if err != nil { return fmt.Errorf("could not setup log store: %w", err) } + // agents + server.Config.Agent.DisableUserRegisteredAgentRegistration = c.Bool("disable-user-agent-registration") + // authentication server.Config.Pipeline.AuthenticatePublicRepos = c.Bool("authenticate-public-repos") // Cloning - server.Config.Pipeline.DefaultCloneImage = c.String("default-clone-image") - constant.TrustedCloneImages = append(constant.TrustedCloneImages, server.Config.Pipeline.DefaultCloneImage) + server.Config.Pipeline.DefaultClonePlugin = c.String("default-clone-plugin") + server.Config.Pipeline.TrustedClonePlugins = c.StringSlice("plugins-trusted-clone") + server.Config.Pipeline.TrustedClonePlugins = append(server.Config.Pipeline.TrustedClonePlugins, server.Config.Pipeline.DefaultClonePlugin) // Execution _events := c.StringSlice("default-cancel-previous-pipeline-events") @@ -233,16 +185,19 @@ func setupEvilGlobals(c *cli.Context, s store.Store) error { events = append(events, model.WebhookEvent(v)) } server.Config.Pipeline.DefaultCancelPreviousPipelineEvents = events - server.Config.Pipeline.DefaultTimeout = c.Int64("default-pipeline-timeout") - server.Config.Pipeline.MaxTimeout = c.Int64("max-pipeline-timeout") + server.Config.Pipeline.DefaultTimeout = c.Int("default-pipeline-timeout") + server.Config.Pipeline.MaxTimeout = c.Int("max-pipeline-timeout") - // limits - server.Config.Pipeline.Limits.MemSwapLimit = c.Int64("limit-mem-swap") - server.Config.Pipeline.Limits.MemLimit = c.Int64("limit-mem") - server.Config.Pipeline.Limits.ShmSize = c.Int64("limit-shm-size") - server.Config.Pipeline.Limits.CPUQuota = c.Int64("limit-cpu-quota") - server.Config.Pipeline.Limits.CPUShares = c.Int64("limit-cpu-shares") - server.Config.Pipeline.Limits.CPUSet = c.String("limit-cpu-set") + _labels := c.StringSlice("default-workflow-labels") + labels := make(map[string]string, len(_labels)) + for _, v := range _labels { + name, value, ok := strings.Cut(v, "=") + if !ok { + return fmt.Errorf("invalid label filter: %s", v) + } + labels[name] = value + } + server.Config.Pipeline.DefaultWorkflowLabels = labels // backend options for pipeline compiler server.Config.Pipeline.Proxy.No = c.String("backend-no-proxy") @@ -264,11 +219,7 @@ func setupEvilGlobals(c *cli.Context, s store.Store) error { } else { server.Config.Server.WebhookHost = serverHost } - if c.IsSet("server-dev-oauth-host-deprecated") { - server.Config.Server.OAuthHost = c.String("server-dev-oauth-host-deprecated") - } else { - server.Config.Server.OAuthHost = serverHost - } + server.Config.Server.OAuthHost = serverHost server.Config.Server.Port = c.String("server-addr") server.Config.Server.PortTLS = c.String("server-addr-tls") server.Config.Server.StatusContext = c.String("status-context") @@ -284,9 +235,9 @@ func setupEvilGlobals(c *cli.Context, s store.Store) error { server.Config.Server.CustomJsFile = strings.TrimSpace(c.String("custom-js-file")) server.Config.Pipeline.Networks = c.StringSlice("network") server.Config.Pipeline.Volumes = c.StringSlice("volume") - server.Config.Pipeline.Privileged = c.StringSlice("escalate") server.Config.WebUI.EnableSwagger = c.Bool("enable-swagger") server.Config.WebUI.SkipVersionCheck = c.Bool("skip-version-check") + server.Config.Pipeline.PrivilegedPlugins = c.StringSlice("plugins-privileged") // prometheus server.Config.Prometheus.AuthToken = c.String("prometheus-auth-token") diff --git a/cmd/server/swagger.go b/cmd/server/swagger.go deleted file mode 100644 index a38ec599e..000000000 --- a/cmd/server/swagger.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2023 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "go.woodpecker-ci.org/woodpecker/v2/cmd/server/docs" - "go.woodpecker-ci.org/woodpecker/v2/version" -) - -// Generate docs/swagger.json via: -//go:generate go run woodpecker_docs_gen.go swagger.go -//go:generate go run github.com/getkin/kin-openapi/cmd/validate@latest ../../docs/swagger.json - -// setupSwaggerStaticConfig initializes static content only (contacts, title and description) -// for dynamic configuration of e.g. hostname, etc. see router.setupSwaggerConfigAndRoutes -// -// @contact.name Woodpecker CI Community -// @contact.url https://woodpecker-ci.org/ -func setupSwaggerStaticConfig() { - docs.SwaggerInfo.BasePath = "/api" - docs.SwaggerInfo.InfoInstanceName = "api" - docs.SwaggerInfo.Title = "Woodpecker CI API" - docs.SwaggerInfo.Version = version.String() - docs.SwaggerInfo.Description = "Woodpecker is a simple yet powerful CI/CD engine with great extensibility.\n" + - "To get a personal access token (PAT) for authentication, please log in your Woodpecker server,\n" + - "and go to you personal profile page, by clicking the user icon at the top right." -} diff --git a/cmd/server/swagger_test.go b/cmd/server/swagger_test.go deleted file mode 100644 index 5d678aed9..000000000 --- a/cmd/server/swagger_test.go +++ /dev/null @@ -1,14 +0,0 @@ -package main - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "go.woodpecker-ci.org/woodpecker/v2/cmd/server/docs" -) - -func TestSetupSwaggerStaticConfig(t *testing.T) { - setupSwaggerStaticConfig() - assert.Equal(t, "/api", docs.SwaggerInfo.BasePath) -} diff --git a/docker-compose.example.yaml b/docker-compose.example.yaml index 6ce862d79..f54121f7f 100644 --- a/docker-compose.example.yaml +++ b/docker-compose.example.yaml @@ -2,7 +2,7 @@ version: '3' services: woodpecker-server: - image: woodpeckerci/woodpecker-server:latest + image: woodpeckerci/woodpecker-server:v3 ports: - 8000:8000 networks: @@ -21,7 +21,7 @@ services: depends_on: woodpecker-server: condition: service_healthy - image: woodpeckerci/woodpecker-agent:latest + image: woodpeckerci/woodpecker-agent:v3 networks: - woodpecker volumes: diff --git a/docker-compose.gitpod.yaml b/docker-compose.gitpod.yaml index 8b111d791..ba55003f3 100644 --- a/docker-compose.gitpod.yaml +++ b/docker-compose.gitpod.yaml @@ -3,7 +3,7 @@ version: '3' services: gitea-database: - image: postgres:16.3-alpine + image: postgres:17.2-alpine environment: POSTGRES_USER: gitea POSTGRES_PASSWORD: 123456 diff --git a/docker/Dockerfile.agent.alpine.multiarch b/docker/Dockerfile.agent.alpine.multiarch index 5de3db18c..68a2c36ce 100644 --- a/docker/Dockerfile.agent.alpine.multiarch +++ b/docker/Dockerfile.agent.alpine.multiarch @@ -1,4 +1,4 @@ -FROM --platform=$BUILDPLATFORM docker.io/golang:1.22 AS build +FROM --platform=$BUILDPLATFORM docker.io/golang:1.23 AS build WORKDIR /src COPY . . @@ -7,15 +7,19 @@ RUN --mount=type=cache,target=/root/.cache/go-build \ --mount=type=cache,target=/go/pkg \ make build-agent -FROM docker.io/alpine:3.20 -# renovate: datasource=repology depName=alpine_3_18/ca-certificates versioning=loose -ENV CA_CERTIFICATES_VERSION="20240226-r0" -RUN apk add -U --no-cache ca-certificates=${CA_CERTIFICATES_VERSION} +FROM docker.io/alpine:3.21 + +RUN apk add -U --no-cache ca-certificates && \ + adduser -u 1000 -g 1000 woodpecker -D && \ + mkdir -p /etc/woodpecker && \ + chown -R woodpecker:woodpecker /etc/woodpecker + ENV GODEBUG=netdns=go +# Internal setting do NOT change! Signals that woodpecker is running inside a container +ENV WOODPECKER_IN_CONTAINER=true EXPOSE 3000 COPY --from=build /src/dist/woodpecker-agent /bin/ -RUN mkdir -p /etc/woodpecker HEALTHCHECK CMD ["/bin/woodpecker-agent", "ping"] ENTRYPOINT ["/bin/woodpecker-agent"] diff --git a/docker/Dockerfile.agent.multiarch b/docker/Dockerfile.agent.multiarch index 6349a3d50..91e208124 100644 --- a/docker/Dockerfile.agent.multiarch +++ b/docker/Dockerfile.agent.multiarch @@ -1,4 +1,9 @@ -FROM --platform=$BUILDPLATFORM docker.io/golang:1.22 AS build +FROM --platform=$BUILDPLATFORM docker.io/golang:1.23 AS build + +RUN groupadd -g 1000 woodpecker && \ + useradd -u 1000 -g 1000 woodpecker && \ + mkdir -p /etc/woodpecker && \ + chown -R woodpecker:woodpecker /etc/woodpecker WORKDIR /src COPY . . @@ -6,10 +11,11 @@ ARG TARGETOS TARGETARCH CI_COMMIT_SHA CI_COMMIT_TAG CI_COMMIT_BRANCH RUN --mount=type=cache,target=/root/.cache/go-build \ --mount=type=cache,target=/go/pkg \ make build-agent -RUN mkdir -p /etc/woodpecker FROM scratch ENV GODEBUG=netdns=go +# Internal setting do NOT change! Signals that woodpecker is running inside a container +ENV WOODPECKER_IN_CONTAINER=true EXPOSE 3000 # copy certs from build image @@ -17,6 +23,8 @@ COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certifica # copy agent binary COPY --from=build /src/dist/woodpecker-agent /bin/ COPY --from=build /etc/woodpecker /etc +COPY --from=build /etc/passwd /etc/passwd +COPY --from=build /etc/group /etc/group HEALTHCHECK CMD ["/bin/woodpecker-agent", "ping"] ENTRYPOINT ["/bin/woodpecker-agent"] diff --git a/docker/Dockerfile.cli.alpine.multiarch b/docker/Dockerfile.cli.alpine.multiarch.rootless similarity index 58% rename from docker/Dockerfile.cli.alpine.multiarch rename to docker/Dockerfile.cli.alpine.multiarch.rootless index e8348da87..c9e5a90a4 100644 --- a/docker/Dockerfile.cli.alpine.multiarch +++ b/docker/Dockerfile.cli.alpine.multiarch.rootless @@ -1,4 +1,4 @@ -FROM --platform=$BUILDPLATFORM docker.io/golang:1.22 AS build +FROM --platform=$BUILDPLATFORM docker.io/golang:1.23 AS build WORKDIR /src COPY . . @@ -7,14 +7,19 @@ RUN --mount=type=cache,target=/root/.cache/go-build \ --mount=type=cache,target=/go/pkg \ make build-cli -FROM docker.io/alpine:3.20 -# renovate: datasource=repology depName=alpine_3_18/ca-certificates versioning=loose -ENV CA_CERTIFICATES_VERSION="20240226-r0" -RUN apk add -U --no-cache ca-certificates=${CA_CERTIFICATES_VERSION} +FROM docker.io/alpine:3.21 + +WORKDIR /woodpecker + +RUN apk add -U --no-cache ca-certificates && \ + adduser -u 1000 -g 1000 -D woodpecker + ENV GODEBUG=netdns=go ENV WOODPECKER_DISABLE_UPDATE_CHECK=true COPY --from=build /src/dist/woodpecker-cli /bin/ +USER woodpecker + HEALTHCHECK CMD ["/bin/woodpecker-cli", "ping"] ENTRYPOINT ["/bin/woodpecker-cli"] diff --git a/docker/Dockerfile.cli.multiarch b/docker/Dockerfile.cli.multiarch.rootless similarity index 68% rename from docker/Dockerfile.cli.multiarch rename to docker/Dockerfile.cli.multiarch.rootless index 2afeb82e2..0a57a315c 100644 --- a/docker/Dockerfile.cli.multiarch +++ b/docker/Dockerfile.cli.multiarch.rootless @@ -1,4 +1,7 @@ -FROM --platform=$BUILDPLATFORM docker.io/golang:1.22 AS build +FROM --platform=$BUILDPLATFORM docker.io/golang:1.23 AS build + +RUN groupadd -g 1000 woodpecker && \ + useradd -u 1000 -g 1000 woodpecker WORKDIR /src COPY . . @@ -8,6 +11,8 @@ RUN --mount=type=cache,target=/root/.cache/go-build \ make build-cli FROM scratch +WORKDIR /woodpecker + ENV GODEBUG=netdns=go ENV WOODPECKER_DISABLE_UPDATE_CHECK=true @@ -15,6 +20,10 @@ ENV WOODPECKER_DISABLE_UPDATE_CHECK=true COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt # copy cli binary COPY --from=build /src/dist/woodpecker-cli /bin/ +COPY --from=build /etc/passwd /etc/passwd +COPY --from=build /etc/group /etc/group + +USER woodpecker HEALTHCHECK CMD ["/bin/woodpecker-cli", "ping"] ENTRYPOINT ["/bin/woodpecker-cli"] diff --git a/docker/Dockerfile.make b/docker/Dockerfile.make index d18cf733e..44718613a 100644 --- a/docker/Dockerfile.make +++ b/docker/Dockerfile.make @@ -1,19 +1,8 @@ -# docker build --rm -f docker/Dockerfile.make -t woodpecker/make:local . -FROM docker.io/golang:1.22-alpine3.19 as golang_image -FROM docker.io/node:22-alpine3.19 +# docker build --rm -f docker/Dockerfile.make -t woodpecker/make:local . +FROM docker.io/golang:1.23-alpine AS golang_image +FROM docker.io/node:23-alpine -# renovate: datasource=repology depName=alpine_3_19/make versioning=loose -ENV MAKE_VERSION="4.4.1-r2" -# renovate: datasource=repology depName=alpine_3_19/gcc versioning=loose -ENV GCC_VERSION="13.2.1_git20231014-r0" -# renovate: datasource=repology depName=alpine_3_19/binutils-gold versioning=loose -ENV BINUTILS_GOLD_VERSION="2.41-r0" -# renovate: datasource=repology depName=alpine_3_19/musl-dev versioning=loose -ENV MUSL_DEV_VERSION="1.2.4_git20230717-r4" -# renovate: datasource=repology depName=alpine_3_19/protoc versioning=loose -ENV PROTOC_VERSION="24.4-r0" - -RUN apk add --no-cache --update make=${MAKE_VERSION} gcc=${GCC_VERSION} binutils-gold=${BINUTILS_GOLD_VERSION} musl-dev=${MUSL_DEV_VERSION} protoc=${PROTOC_VERSION} && \ +RUN apk add --no-cache --update make gcc binutils-gold musl-dev protoc && \ corepack enable # Build packages. @@ -21,6 +10,7 @@ COPY --from=golang_image /usr/local/go /usr/local/go COPY Makefile / ENV PATH=$PATH:/usr/local/go/bin ENV COREPACK_ENABLE_DOWNLOAD_PROMPT=0 +ENV COREPACK_ENABLE_AUTO_PIN=0 # Cache tools RUN GOBIN=/usr/local/go/bin make install-tools && \ diff --git a/docker/Dockerfile.server.alpine.multiarch b/docker/Dockerfile.server.alpine.multiarch.rootless similarity index 65% rename from docker/Dockerfile.server.alpine.multiarch rename to docker/Dockerfile.server.alpine.multiarch.rootless index 002cbcb69..f5d50951d 100644 --- a/docker/Dockerfile.server.alpine.multiarch +++ b/docker/Dockerfile.server.alpine.multiarch.rootless @@ -1,10 +1,11 @@ -FROM docker.io/alpine:3.20 - -# renovate: datasource=repology depName=alpine_3_18/ca-certificates versioning=loose -ENV CA_CERTIFICATES_VERSION="20240226-r0" +FROM docker.io/alpine:3.21 ARG TARGETOS TARGETARCH -RUN apk add -U --no-cache ca-certificates=${CA_CERTIFICATES_VERSION} +RUN apk add -U --no-cache ca-certificates && \ + adduser -u 1000 -g 1000 woodpecker -D && \ + mkdir -p /var/lib/woodpecker && \ + chown -R woodpecker:woodpecker /var/lib/woodpecker + ENV GODEBUG=netdns=go # Internal setting do NOT change! Signals that woodpecker is running inside a container ENV WOODPECKER_IN_CONTAINER=true @@ -14,5 +15,7 @@ EXPOSE 8000 9000 80 443 COPY dist/server/${TARGETOS}_${TARGETARCH}/woodpecker-server /bin/ +USER woodpecker + HEALTHCHECK CMD ["/bin/woodpecker-server", "ping"] ENTRYPOINT ["/bin/woodpecker-server"] diff --git a/docker/Dockerfile.server.multiarch b/docker/Dockerfile.server.multiarch.rootless similarity index 53% rename from docker/Dockerfile.server.multiarch rename to docker/Dockerfile.server.multiarch.rootless index 3bf86a3be..7bf4b5cde 100644 --- a/docker/Dockerfile.server.multiarch +++ b/docker/Dockerfile.server.multiarch.rootless @@ -1,4 +1,9 @@ -FROM --platform=$BUILDPLATFORM docker.io/golang:1.22 AS certs +FROM --platform=$BUILDPLATFORM docker.io/golang:1.23 AS build + +RUN groupadd -g 1000 woodpecker && \ + useradd -u 1000 -g 1000 woodpecker && \ + mkdir -p /var/lib/woodpecker && \ + chown -R woodpecker:woodpecker /var/lib/woodpecker FROM scratch ARG TARGETOS TARGETARCH @@ -10,9 +15,14 @@ ENV XDG_DATA_HOME=/var/lib/woodpecker EXPOSE 8000 9000 80 443 # copy certs from certs image -COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt +COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt # copy server binary COPY dist/server/${TARGETOS}_${TARGETARCH}/woodpecker-server /bin/ +COPY --from=build /etc/passwd /etc/passwd +COPY --from=build /etc/group /etc/group +COPY --from=build /var/lib/woodpecker /var/lib/woodpecker + +USER woodpecker HEALTHCHECK CMD ["/bin/woodpecker-server", "ping"] ENTRYPOINT ["/bin/woodpecker-server"] diff --git a/docs/.prettierignore b/docs/.prettierignore new file mode 100644 index 000000000..2087370c5 --- /dev/null +++ b/docs/.prettierignore @@ -0,0 +1,7 @@ +pnpm-lock.yaml +dist +coverage/ +LICENSE +components.d.ts +src/assets/locales/*.json +!src/assets/locales/en.json diff --git a/docs/.prettierrc.js b/docs/.prettierrc.js new file mode 100644 index 000000000..1fb217ea6 --- /dev/null +++ b/docs/.prettierrc.js @@ -0,0 +1,16 @@ +import { readFile } from 'node:fs/promises'; + +// eslint-disable-next-line antfu/no-top-level-await +const config = JSON.parse(await readFile(new URL('../.prettierrc.json', import.meta.url))); + +export default { + ...config, + plugins: ['@ianvs/prettier-plugin-sort-imports'], + importOrder: [ + '', // Imports not matched by other special words or groups. + '', // Empty string will match any import not matched by other special words or groups. + '^(#|@|~|\\$)(/.*)$', + '', + '^[./]', + ], +}; diff --git a/docs/babel.config.js b/docs/babel.config.js deleted file mode 100644 index e00595dae..000000000 --- a/docs/babel.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - presets: [require.resolve('@docusaurus/core/lib/babel/preset')], -}; diff --git a/docs/blog/2023-11-09-release-v2.0.0/index.md b/docs/blog/2023-11-09-release-v2.0.0/index.md index 1fc3db91e..1aa7c94b2 100644 --- a/docs/blog/2023-11-09-release-v2.0.0/index.md +++ b/docs/blog/2023-11-09-release-v2.0.0/index.md @@ -53,7 +53,7 @@ Security is pretty important to us and we want to make sure that no one can stea ## Migration notes -There have been a few more breaking changes. [Read more about what you need to do when upgrading!](../docs/migrations#200) +There have been a few more breaking changes. [Read more about what you need to do when upgrading!](/migrations#200) ## New features diff --git a/docs/blog/2023-12-12-podman-image-builds/index.md b/docs/blog/2023-12-12-podman-image-builds/index.md new file mode 100644 index 000000000..e0501de4a --- /dev/null +++ b/docs/blog/2023-12-12-podman-image-builds/index.md @@ -0,0 +1,60 @@ +--- +title: '[Community] Podman-in-Podman image builds' +description: Build images in Podman with buildah +slug: podman-image-builds +authors: + - name: handlebargh + url: https://github.com/handlebargh + image_url: https://github.com/handlebargh.png +hide_table_of_contents: true +tags: [community, image, podman] +--- + + + +I run Woodpecker CI with podman backend instead of docker and just figured out how to build images with buildah. Since I couldn't find this anywhere documented, I thought I might as well just share it here. + + + +It's actually pretty straight forward. Here's what my repository structure looks like: + +```bash +. +├── roundcube +│   ├── Containerfile +│   ├── docker-entrypoint.sh +│   └── php.ini +└── .woodpecker + └── .build_roundcube.yml +``` + +As you can see I'm building a roundcube mail image. + +This is the `.woodpecker/.build_roundcube.yaml` + +```yaml +when: + event: [cron, manual] + cron: build_roundcube + +steps: + build-image: + image: quay.io/buildah/stable:latest + pull: true + privileged: true + commands: + - echo $REGISTRY_LOGIN_TOKEN | buildah login -u --password-stdin registry.gitlab.com + - cd roundcube + - buildah build --tag registry.gitlab.com///roundcube:latest . + - buildah push registry.gitlab.com///roundcube:latest + + secrets: [registry_login_token] +``` + +As you can see, I'm using this workflow over at gitlab.com. It should work with GitHub as well, with adjusting the registry login. + +You may have to adjust the `when:` to your needs. Furthermore, you must check the `trusted` checkbox in project settings. Therefore, be sure to run trusted code only in this setup. + +This seems to work fine so far. I wonder if anybody else made this work a different way. + +EDIT: Removed the additional step that would run buildah in a podman container. I didn't know it could be that easy to be honest. diff --git a/docs/blog/2023-12-13-debug-pipeline-steps/index.md b/docs/blog/2023-12-13-debug-pipeline-steps/index.md new file mode 100644 index 000000000..7682d50bc --- /dev/null +++ b/docs/blog/2023-12-13-debug-pipeline-steps/index.md @@ -0,0 +1,31 @@ +--- +title: '[Community] Debug pipeline steps' +description: Debug pipeline steps using sshx +slug: debug-pipeline-steps +authors: + - name: anbraten + url: https://github.com/anbraten + image_url: https://github.com/anbraten.png +hide_table_of_contents: true +tags: [community, debug] +--- + + + +Sometimes you want to debug a pipeline. +Therefore I recently discovered: + + + +A simple step like should allow you to debug: + +```yaml +steps: + - name: debug + image: alpine + commands: + - curl -sSf https://sshx.io/get | sh && sshx + # ^ + # └ This will open a remote terminal session and print the URL. It + # should take under a second. +``` diff --git a/docs/blog/2023-12-15-podman-sigstore/index.md b/docs/blog/2023-12-15-podman-sigstore/index.md new file mode 100644 index 000000000..e2600946e --- /dev/null +++ b/docs/blog/2023-12-15-podman-sigstore/index.md @@ -0,0 +1,140 @@ +--- +title: '[Community] Podman image build with sigstore' +description: Build images in Podman with sigstore signature checking and signing +slug: podman-image-build-sigstore +authors: + - name: handlebargh + url: https://github.com/handlebargh + image_url: https://github.com/handlebargh.png +hide_table_of_contents: false +tags: [community, image, podman, sigstore, signature] +--- + + + +This example shows how to build a container image with podman while verifying the base image and signing the resulting image. + + + +The image being pulled uses a keyless signature, while the image being built will be signed by a pre-generated private key. + +## Prerequisites + +### Generate signing keypair + +You can use cosing or skopeo to generate the keypair. + +Using skopeo: + +```bash +skopeo generate-sigstore-key --output-prefix myKey +``` + +This command will generate a `myKey.private` and a `myKey.pub` keyfile. + +Store the `myKey.private` as secret in Woodpecker. In the example below, the secret is called `sigstore_private_key` + +### Configure hosts pulling the resulting image + +See [here](https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/8/html/building_running_and_managing_containers/assembly_signing-container-images_building-running-and-managing-containers#proc_verifying-sigstore-image-signatures-using-a-public-key_assembly_signing-container-images) on how to configure the hosts pulling the built and signed image. + +## Repository structure + +Consider the `Makefile` having a `build` target that will be used in the following workflow. +This target yields a Go binary with the filename `app` that will be placed in the root directory. + +```bash +. +├── Containerfile +├── main.go +├── go.mod +├── go.sum +├── .woodpecker.yml +└── Makefile +``` + +### Containerfile + +The Containerfile refers to the base image that will be verified when pulled. + +```dockerfile +FROM gcr.io/distroless/static-debian12:nonroot +COPY app /app +CMD ["/app"] +``` + +### Woodpecker workflow + +```yaml +steps: + build: + image: docker.io/library/golang:1.21 + pull: true + commands: + - make build + + publish: + image: quay.io/podman/stable:latest + # Caution: This image is built daily. It might fill up your image store quickly. + pull: true + # Fill in the trusted checkbox in Woodpecker's settings as well + privileged: true + commands: + # Configure podman to use sigstore attachments for both, the registry you pull from and the registry you push to. + - | + printf "docker: + registry.gitlab.com: + use-sigstore-attachments: true + gcr.io: + use-sigstore-attachments: true" >> /etc/containers/registries.d/default.yaml + + # At pull, check the keyless sigstore signature of the distroless image. + # This is a very strict container policy. It allows pulling from gcr.io/distroless only. Every other registry will be rejected. + # See https://github.com/containers/image/blob/main/docs/containers-policy.json.5.md for more information. + + # fulcio CA crt obtained from https://github.com/sigstore/sigstore/blob/main/pkg/tuf/repository/targets/fulcio_v1.crt.pem + # rekor public key obtained from https://github.com/sigstore/sigstore/blob/main/pkg/tuf/repository/targets/rekor.pub + # crt/key data is base64 encoded. --> echo "$CERT" | base64 + - | + printf '{ + "default": [ + { + "type": "reject" + } + ], + "transports": { + "docker": { + "gcr.io/distroless": [ + { + "type": "sigstoreSigned", + "fulcio": { + "caData": "LS0tLS1CRUdJTiBDR...QVRFLS0tLS0K", + "oidcIssuer": "https://accounts.google.com", + "subjectEmail": "keyless@distroless.iam.gserviceaccount.com" + }, + "rekorPublicKeyData": "LS0tLS1CRUdJTiBQVUJ...lDIEtFWS0tLS0tCg==", + "signedIdentity": { "type": "matchRepository" } + } + ] + }, + "docker-daemon": { + "": [ + { + "type": "reject" + } + ] + } + } + }' > /etc/containers/policy.json + + # Use this key to sign the built image at push. + - echo "$SIGSTORE_PRIVATE_KEY" > key.private + # Login at the registry + - echo $REGISTRY_LOGIN_TOKEN | podman login -u --password-stdin registry.gitlab.com + # Build the container image + - podman build --tag registry.gitlab.com///:latest . + # Sign and push the image + - podman push --sign-by-sigstore-private-key ./key.private registry.gitlab.com///:latest + + secrets: [sigstore_private_key, registry_login_token] +``` diff --git a/docs/cookbook/2024-1-1-continuous-deployment/index.md b/docs/blog/2024-01-01-continuous-deployment/index.md similarity index 97% rename from docs/cookbook/2024-1-1-continuous-deployment/index.md rename to docs/blog/2024-01-01-continuous-deployment/index.md index c1bedcc37..f455186a6 100644 --- a/docs/cookbook/2024-1-1-continuous-deployment/index.md +++ b/docs/blog/2024-01-01-continuous-deployment/index.md @@ -1,5 +1,5 @@ --- -title: Continuous Deployment +title: '[Community] Continuous Deployment' description: Deploy your artifacts to an app server slug: continuous-deployment authors: @@ -7,16 +7,17 @@ authors: url: https://github.com/lonix1 image_url: https://github.com/lonix1.png hide_table_of_contents: false +tags: [community, cd, deployment] --- - - A typical CI pipeline contains steps such as: _clone_, _build_, _test_, _package_ and _push_. The final build product may be artifacts pushed to a git repository or a docker container pushed to a container registry. When these should be deployed on an app server, the pipeline should include a _deploy_ step, which represents the "CD" in CI/CD - the automatic deployment of a pipeline's final product. There are various ways to accomplish CD with Woodpecker, depending on your project's specific needs. + + ## Invoking deploy script via SSH The final step in your pipeline could SSH into the app server and run a deployment script. diff --git a/docs/blog/2024-05-27-release-v2.5.0/index.md b/docs/blog/2024-05-27-release-v2.5.0/index.md index 2adee0bc0..3de36b5ee 100644 --- a/docs/blog/2024-05-27-release-v2.5.0/index.md +++ b/docs/blog/2024-05-27-release-v2.5.0/index.md @@ -45,7 +45,7 @@ NUMBER STATUS EVENT BRANCH COMMIT AUTHO 42 success push ``` -In addition especially useful for programmtic usage there is a `go-template` output format which will output the data using the provided go template like this: +In addition especially useful for programmatic usage there is a `go-template` output format which will output the data using the provided go template like this: ```bash ######## @@ -87,4 +87,4 @@ be removed in the next major release: - Use `WOODPECKER_EXPERT_FORGE_OAUTH_HOST` instead of `WOODPECKER_DEV_GITEA_OAUTH_URL` or `WOODPECKER_DEV_OAUTH_HOST` - Deprecated `WOODPECKER_WEBHOOK_HOST` in favor of `WOODPECKER_EXPERT_WEBHOOK_HOST` -For a full list of deprecations that will be dropped in the `next` major release `3.0.0` (no eta yet), please check the [migrations](/docs/migrations#next) section. +For a full list of deprecations that will be dropped in the `next` major release `3.0.0` (no eta yet), please check the [migrations](/migrations#next) section. diff --git a/docs/cookbook/2023-12-23-hello-cookbook/index.md b/docs/cookbook/2023-12-23-hello-cookbook/index.md deleted file mode 100644 index d3055dfba..000000000 --- a/docs/cookbook/2023-12-23-hello-cookbook/index.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: Welcome to Woodpecker's cookbook blog -description: Here, we'll publish various guides and tutorials. -slug: hello-cookbook -authors: - - name: qwerty287 - title: Maintainer of Woodpecker - url: https://github.com/qwerty287 - image_url: https://github.com/qwerty287.png -hide_table_of_contents: false ---- - -Welcome to this cookbook blog. Here, we and any other interested user can publish guides and tutorials. If you got something in mind, just add your guide! diff --git a/docs/docs/10-intro.md b/docs/docs/10-intro.md deleted file mode 100644 index 309c6f1af..000000000 --- a/docs/docs/10-intro.md +++ /dev/null @@ -1,89 +0,0 @@ -# Welcome to Woodpecker - -Woodpecker is a simple yet powerful CI/CD engine with great extensibility. It focuses on executing pipelines inside [containers](https://opencontainers.org/). -If you are already using containers in your daily workflow, you'll for sure love Woodpecker. - -![woodpecker](woodpecker.png) - -## `.woodpecker.yaml` - -- Place your pipeline in a file named `.woodpecker.yaml` in your repository -- Pipeline steps can be named as you like -- Run any command in the commands section - -```yaml title=".woodpecker.yaml" -steps: - - name: build - image: debian - commands: - - echo "This is the build step" - - name: a-test-step - image: debian - commands: - - echo "Testing.." -``` - -### Steps are containers - -- Define any container image as context - - either use your own and install the needed tools in a custom image - - or search for available images that are already tailored for your needs in image registries like [Docker Hub](https://hub.docker.com/search?type=image) -- List the commands that should be executed in the container - -```diff - steps: - - name: build -- image: debian -+ image: mycompany/image-with-awscli - commands: - - aws help -``` - -### File changes are incremental - -- Woodpecker clones the source code in the beginning -- File changes are persisted throughout individual steps as the same volume is being mounted in all steps - -```yaml title=".woodpecker.yaml" -steps: - - name: build - image: debian - commands: - - touch myfile - - name: a-test-step - image: debian - commands: - - cat myfile -``` - -## Plugins are straightforward - -- If you copy the same shell script from project to project -- Pack it into a plugin instead -- And make the yaml declarative -- Plugins are Docker images with your script as an entrypoint - -```dockerfile title="Dockerfile" -FROM laszlocloud/kubectl -COPY deploy /usr/local/deploy -ENTRYPOINT ["/usr/local/deploy"] -``` - -```bash title="deploy" -kubectl apply -f $PLUGIN_TEMPLATE -``` - -```yaml title=".woodpecker.yaml" -steps: - - name: deploy-to-k8s - image: laszlocloud/my-k8s-plugin - settings: - template: config/k8s/service.yaml -``` - -See [plugin docs](./20-usage/51-plugins/51-overview.md). - -## Continue reading - -- [Create a Woodpecker pipeline for your repository](./20-usage/10-intro.md) -- [Setup your own Woodpecker instance](./30-administration/00-deployment/00-overview.md) diff --git a/docs/docs/10-intro/index.md b/docs/docs/10-intro/index.md new file mode 100644 index 000000000..7d9ced179 --- /dev/null +++ b/docs/docs/10-intro/index.md @@ -0,0 +1,26 @@ +# Welcome to Woodpecker + +Woodpecker is a CI/CD tool. It is designed to be lightweight, simple to use and fast. Before we dive into the details, let's have a look at some of the basics. + +## Have you ever heard of CI/CD or pipelines? + +Don't worry if you haven't. We'll guide you through the basics. CI/CD stands for Continuous Integration and Continuous Deployment. It's basically like a conveyor belt that moves your code from development to production doing all kinds of +checks, tests and routines along the way. A typical pipeline might include the following steps: + +1. Running tests +2. Building your application +3. Deploying your application + +[Have a deeper look into the idea of CI/CD](https://www.redhat.com/en/topics/devops/what-is-ci-cd) + +## Do you know containers? + +If you are already using containers in your daily workflow, you'll for sure love Woodpecker. If not yet, you'll be amazed how easy it is to get started with [containers](https://opencontainers.org/). + +## Already have access to a Woodpecker instance? + +Then you might want to jump directly into it and [start creating your first pipelines](../20-usage/10-intro.md). + +## Want to start from scratch and deploy your own Woodpecker instance? + +Woodpecker is [pretty lightweight](../30-administration/00-getting-started.md#hardware-requirements) and will even run on your Raspberry Pi. You can follow the [deployment guide](../30-administration/00-getting-started.md) to set up your own Woodpecker instance. diff --git a/docs/docs/20-usage/10-intro.md b/docs/docs/20-usage/10-intro.md index 875411ff9..1c4baec1f 100644 --- a/docs/docs/20-usage/10-intro.md +++ b/docs/docs/20-usage/10-intro.md @@ -1,73 +1,110 @@ -# Getting started +# Your first pipeline -## Repository Activation +Let's get started and create your first pipeline. -To activate your project navigate to your account settings. You will see a list of repositories which can be activated with a simple toggle. When you activate your repository, Woodpecker automatically adds webhooks to your forge (e.g. GitHub, Gitea, ...). +## 1. Repository Activation -Webhooks are used to trigger pipeline executions. When you push code to your repository, open a pull request, or create a tag, your forge will automatically send a webhook to Woodpecker which will in turn trigger the pipeline execution. +To activate your repository in Woodpecker navigate to the repository list and `New repository`. You will see a list of repositories from your forge (GitHub, Gitlab, ...) which can be activated with a simple click. -![repository list](repo-list.png) +![new repository list](repo-new.png) -## Required Permissions +To enable a repository in Woodpecker you must have `Admin` rights on that repository, so that Woodpecker can add something +that is called a webhook (Woodpecker needs it to know about actions like pushes, pull requests, tags, etc.). -The user who enables a repo in Woodpecker must have `Admin` rights on that repo, so that Woodpecker can add the webhook. +## 2. Define first workflow -:::note -Note that manually creating webhooks yourself is not possible. -This is because webhooks are signed using a per-repository secret key which is not exposed to end users. -::: +After enabling a repository Woodpecker will listen for changes in your repository. When a change is detected, Woodpecker will check for a pipeline configuration. So let's create a file at `.woodpecker/my-first-workflow.yaml` inside your repository: -## Configuration +```yaml title=".woodpecker/my-first-workflow.yaml" +when: + - event: push + branch: main -To configure your pipeline you must create a `.woodpecker.yaml` file in the root of your repository. The `.woodpecker.yaml` file is used to define your pipeline steps. - -:::note -We support most of YAML 1.2, but preserve some behavior from 1.1 for backward compatibility. -Read more at: [https://github.com/go-yaml/yaml](https://github.com/go-yaml/yaml/tree/v3) -::: - -Example pipeline configuration: - -```yaml steps: - name: build - image: golang + image: debian commands: - - go get - - go build - - go test - -services: - - name: postgres - image: postgres:9.4.5 - environment: - - POSTGRES_USER=myapp + - echo "This is the build step" + - echo "binary-data-123" > executable + - name: a-test-step + image: golang:1.16 + commands: + - echo "Testing ..." + - ./executable ``` -Example pipeline configuration with multiple, serial steps: +**So what did we do here?** + +1. We defined your first workflow file `my-first-workflow.yaml`. +2. This workflow will be executed when a push event happens on the `main` branch, + because we added a filter using the `when` section: + + ```diff + + when: + + - event: push + + branch: main + + ... + ``` + +3. We defined two steps: `build` and `a-test-step` + +The steps are executed in the order they are defined, so `build` will be executed first and then `a-test-step`. + +In the `build` step we use the `debian` image and build a "binary file" called `executable`. + +In the `a-test-step` we use the `golang:1.16` image and run the `executable` file to test it. + +You can use any image from registries like the [Docker Hub](https://hub.docker.com/search?type=image) you have access to: + +```diff + steps: + - name: build +- image: debian ++ image: my-company/image-with-aws_cli + commands: + - aws help +``` + +## 3. Push the file and trigger first pipeline + +If you push this file to your repository now, Woodpecker will already execute your first pipeline. + +You can check the pipeline execution in the Woodpecker UI by navigating to the `Pipelines` section of your repository. + +![pipeline view](./pipeline.png) + +As you probably noticed, there is another step in called `clone` which is executed before your steps. This step clones your repository into a folder called `workspace` which is available throughout all steps. + +This for example allows the first step to build your application using your source code and as the second step will receive +the same workspace it can use the previously built binary and test it. + +## 4. Use a plugin for reusable tasks + +Sometimes you have some tasks that you need to do in every project. For example, deploying to Kubernetes or sending a Slack message. Therefore you can use one of the [official and community plugins](/plugins) or simply [create your own](./51-plugins/20-creating-plugins.md). + +If you want to get a Slack notification after your pipeline has finished, you can add a Slack plugin to your pipeline: ```yaml steps: - - name: backend - image: golang - commands: - - go get - - go build - - go test - - - name: frontend - image: node:6 - commands: - - npm install - - npm test - - - name: notify + # ... + - name: notify me on Slack image: plugins/slack settings: channel: developers username: woodpecker + password: + from_secret: slack_token + when: + status: [success, failure] # This will execute the step on success and failure ``` -## Execution +To configure a plugin you can use the `settings` section. -To trigger your first pipeline execution you can push code to your repository, open a pull request, or push a tag. Any of these events triggers a webhook from your forge and execute your pipeline. +Sometime you need to provide secrets to the plugin. You can do this by using the `from_secret` key. The secret must be defined in the Woodpecker UI. You can find more information about secrets [here](./40-secrets.md). + +Similar to the `when` section at the top of the file which is for the complete workflow, you can use the `when` section for each step to define when a step should be executed. + +Learn more about [plugins](./51-plugins/51-overview.md). + +As you now have a basic understanding of how to create a pipeline, you can dive deeper into the [workflow syntax](./20-workflow-syntax.md) and [plugins](./51-plugins/51-overview.md). diff --git a/docs/docs/20-usage/15-terminology/index.md b/docs/docs/20-usage/15-terminology/index.md index 5e9d8e5de..0b5eafd8a 100644 --- a/docs/docs/20-usage/15-terminology/index.md +++ b/docs/docs/20-usage/15-terminology/index.md @@ -1,13 +1,5 @@ # Terminology -## Woodpecker architecture - -![Woodpecker architecture](architecture.svg) - -## Pipeline, workflow & step - -![Relation between pipelines, workflows and steps](pipeline-workflow-step.svg) - ## Glossary - **Woodpecker CI**: The project name around Woodpecker. @@ -15,33 +7,31 @@ - **Server**: The component of Woodpecker that handles webhooks from forges, orchestrates agents, and sends status back. It also serves the API and web UI for administration and configuration. - **Agent**: A component of Woodpecker that executes [pipelines][Pipeline] (specifically one or more [workflows][Workflow]) with a specific backend (e.g. [Docker][], Kubernetes, [local][Local]). It connects to the server via GRPC. - **CLI**: The Woodpecker command-line interface (CLI) is a terminal tool used to administer the server, to execute pipelines locally for debugging / testing purposes, and to perform tasks like linting pipelines. -- **Pipeline**: A sequence of [workflows][Workflow] that are executed on the code. [Pipelines][Pipeline] are triggered by events. -- **Workflow**: A sequence of steps and services that are executed as part of a [pipeline][Pipeline]. Workflows are represented by YAML files. Each [workflow][Workflow] has its own isolated [workspace][Workspace], and often additional resources like a shared network (docker). +- **[Pipeline][Pipeline]**: A sequence of [workflows][Workflow] that are executed on the code. Pipelines are triggered by events. +- **[Workflow][Workflow]**: A sequence of steps and services that are executed as part of a [pipeline][Pipeline]. Workflows are represented by YAML files. Each workflow has its own isolated [workspace][Workspace], and often additional resources like a shared network (docker). - **Steps**: Individual commands, actions or tasks within a [workflow][Workflow]. - **Code**: Refers to the files tracked by the version control system used by the [forge][Forge]. - **Repos**: Short for repositories, these are storage locations where code is stored. -- **Forge**: The hosting platform or service where the repositories are hosted. -- **Workspace**: A folder shared between all steps of a [workflow][Workflow] containing the repository and all the generated data from previous steps. -- **Event**: Triggers the execution of a [pipeline][Pipeline], such as a [forge][Forge] event like `push`, or `manual` triggered manually from the UI. +- **[Forge][Forge]**: The hosting platform or service where the repositories are hosted. +- **[Workspace][workspace]**: A folder shared between all steps of a [workflow][Workflow] containing the repository and all the generated data from previous steps. +- **[Event][Event]**: Triggers the execution of a [pipeline][Pipeline], such as a [forge][Forge] event like `push`, or `manual` triggered manually from the UI. - **Commit**: A defined state of the code, usually associated with a version control system like Git. -- **Matrix**: A configuration option that allows the execution of [workflows][Workflow] for each value in the [matrix][Matrix]. +- **[Matrix][Matrix]**: A configuration option that allows the execution of [workflows][Workflow] for each value in the matrix. - **Service**: A service is a step that is executed from the start of a [workflow][Workflow] until its end. It can be accessed by name via the network from other steps within the same [workflow][Workflow]. -- **Plugins**: [Plugins][Plugin] are extensions that provide pre-defined actions or commands for a step in a [workflow][Workflow]. They can be configured via settings. +- **[Plugins][Plugin]**: Plugins are extensions that provide pre-defined actions or commands for a step in a [workflow][Workflow]. They can be configured via settings. - **Container**: A lightweight and isolated environment where commands are executed. - **YAML File**: A file format used to define and configure [workflows][Workflow]. - **Dependency**: [Workflows][Workflow] can depend on each other, and if possible, they are executed in parallel. - **Status**: Status refers to the outcome of a step or [workflow][Workflow] after it has been executed, determined by the internal command exit code. At the end of a [workflow][Workflow], its status is sent to the [forge][Forge]. - **Service extension**: Some parts of Woodpecker internal services like secrets storage or config fetcher can be replaced through service extensions. -## Pipeline events +## Woodpecker architecture -- `push`: A push event is triggered when a commit is pushed to a branch. -- `pull_request`: A pull request event is triggered when a pull request is opened or a new commit is pushed to it. -- `pull_request_closed`: A pull request closed event is triggered when a pull request is closed or merged. -- `tag`: A tag event is triggered when a tag is pushed. -- `release`: A release event is triggered when a release, pre-release or draft is created. (You can apply further filters using [evaluate](../20-workflow-syntax.md#evaluate) with [environment variables](../50-environment.md#built-in-environment-variables).) -- `manual`: A manual event is triggered when a user manually triggers a pipeline. -- `cron`: A cron event is triggered when a cron job is executed. +![Woodpecker architecture](architecture.svg) + +## Pipeline, workflow & step + +![Relation between pipelines, workflows and steps](pipeline-workflow-step.svg) ## Conventions @@ -54,6 +44,7 @@ Sometimes there are multiple terms that can be used to describe something. This +[Event]: ../20-workflow-syntax.md#event [Pipeline]: ../20-workflow-syntax.md [Workflow]: ../25-workflows.md [Forge]: ../../30-administration/11-forges/11-overview.md diff --git a/docs/docs/20-usage/20-workflow-syntax.md b/docs/docs/20-usage/20-workflow-syntax.md index 7b966fc48..970ae3eff 100644 --- a/docs/docs/20-usage/20-workflow-syntax.md +++ b/docs/docs/20-usage/20-workflow-syntax.md @@ -6,6 +6,11 @@ The Workflow section defines a list of steps to build, test and deploy your code An exception to this rule are steps with a [`status: [failure]`](#status) condition, which ensures that they are executed in the case of a failed run. ::: +:::note +We support most of YAML 1.2, but preserve some behavior from 1.1 for backward compatibility. +Read more at: [https://github.com/go-yaml/yaml](https://github.com/go-yaml/yaml/tree/v3) +::: + Example steps: ```yaml @@ -99,7 +104,7 @@ When using the `local` backend, the `image` entry is used to specify the shell, - go test - name: publish -+ image: plugins/docker ++ image: woodpeckerci/plugin-kaniko repo: foo/bar services: @@ -174,12 +179,6 @@ Woodpecker provides the ability to pass environment variables to individual step For more details, check the [environment docs](./50-environment.md). -### `secrets` - -Woodpecker provides the ability to store named parameters external to the YAML configuration file, in a central secret store. These secrets can be passed to individual steps of the workflow at runtime. - -For more details, check the [secrets docs](./40-secrets.md). - ### `failure` Some of the steps may be allowed to fail without causing the whole workflow and therefore pipeline to report a failure (e.g., a step executing a linting check). To enable this, add `failure: ignore` to your step. If Woodpecker encounters an error while executing the step, it will report it as failed but still executes the next steps of the workflow, if any, without affecting the status of the workflow. @@ -196,7 +195,7 @@ Some of the steps may be allowed to fail without causing the whole workflow and ### `when` - Conditional Execution -Woodpecker supports defining a list of conditions for a step by using a `when` block. If at least one of the conditions in the `when` block evaluate to true the step is executed, otherwise it is skipped. A condition is evaluated to true if _all_ subconditions are true. +Woodpecker supports defining a list of conditions for a step by using a `when` block. If at least one of the conditions in the `when` block evaluate to true the step is executed, otherwise it is skipped. A condition is evaluated to true if _all_ sub-conditions are true. A condition can be a check like: ```diff @@ -215,7 +214,7 @@ A condition can be a check like: The `slack` step is executed if one of these conditions is met: 1. The pipeline is executed from a pull request in the repo `test/test` -2. The pipeline is executed from a push to `maiǹ` +2. The pipeline is executed from a push to `main` #### `repo` @@ -283,7 +282,16 @@ when: #### `event` -Available events: `push`, `pull_request`, `pull_request_closed`, `tag`, `release`, `deployment`, `cron`, `manual` +The available events are: + +- `push`: triggered when a commit is pushed to a branch. +- `pull_request`: triggered when a pull request is opened or a new commit is pushed to it. +- `pull_request_closed`: triggered when a pull request is closed or merged. +- `tag`: triggered when a tag is pushed. +- `release`: triggered when a release, pre-release or draft is created. (You can apply further filters using [evaluate](#evaluate) with [environment variables](./50-environment.md#built-in-environment-variables).) +- `deployment`: triggered when a deployment is created in the repository. (This event can be triggered from Woodpecker directly. GitHub also supports webhook triggers.) +- `cron`: triggered when a cron job is executed. +- `manual`: triggered when a user manually triggers a pipeline. Execute a step if the build event is a `tag`: @@ -470,7 +478,7 @@ Normally steps of a workflow are executed serially in the order in which they ar - go build - name: deploy - image: plugins/docker + image: woodpeckerci/plugin-kaniko settings: repo: foo/bar + depends_on: [build, test] # deploy will be executed after build and test finished @@ -518,7 +526,9 @@ For more details check the [services docs](./60-services.md). ## `workspace` -The workspace defines the shared volume and working directory shared by all workflow steps. The default workspace matches the pattern `/woodpecker/src/github.com/octocat/hello-world`, based on your repository URL. +The workspace defines the shared volume and working directory shared by all workflow steps. +The default workspace base is `/woodpecker` and the path is extended with the repository URL (`src/{url-without-schema}`). +So an example would be `/woodpecker/src/github.com/octocat/hello-world`. The workspace can be customized using the workspace block in the YAML file: @@ -535,6 +545,10 @@ The workspace can be customized using the workspace block in the YAML file: - go test ``` +:::note +Plugins will always have the workspace base at `/woodpecker` +::: + The base attribute defines a shared base volume available to all steps. This ensures your source code, dependencies and compiled binaries are persisted and shared between steps. ```diff @@ -590,7 +604,7 @@ For more details check the [matrix build docs](./30-matrix-workflows.md). You can set labels for your workflow to select an agent to execute the workflow on. An agent will pick up and run a workflow when **every** label assigned to it matches the agents labels. -To set additional agent labels, check the [agent configuration options](../30-administration/15-agent-config.md#woodpecker_filter_labels). Agents will have at least four default labels: `platform=agent-os/agent-arch`, `hostname=my-agent`, `backend=docker` (type of the agent backend) and `repo=*`. Agents can use a `*` as a wildcard for a label. For example `repo=*` will match every repo. +To set additional agent labels, check the [agent configuration options](../30-administration/15-agent-config.md#woodpecker_agent_labels). Agents will have at least four default labels: `platform=agent-os/agent-arch`, `hostname=my-agent`, `backend=docker` (type of the agent backend) and `repo=*`. Agents can use a `*` as a wildcard for a label. For example `repo=*` will match every repo. Workflow labels with an empty value will be ignored. By default, each workflow has at least the `repo=your-user/your-repo-name` label. If you have set the [platform attribute](#platform) for your workflow it will have a label like `platform=your-os/your-arch` as well. @@ -672,16 +686,6 @@ Example configuration to use a custom clone plugin: + image: octocat/custom-git-plugin ``` -Example configuration to clone Mercurial repository: - -```diff - clone: - - name: hg -+ image: plugins/hg -+ settings: -+ path: bitbucket.org/foo/bar -``` - ### Git Submodules To use the credentials that cloned the repository to clone it's submodules, update `.gitmodules` to use `https` instead of `git`: @@ -749,6 +753,25 @@ Woodpecker supports to define multiple workflows for a repository. Those workflo Workflows that should run even on failure should set the `runs_on` tag. See [here](./25-workflows.md#flow-control) for an example. +## Advanced network options for steps + +:::warning +Only allowed if 'Trusted Network' option is enabled in repo settings by an admin. +::: + +### `dns` + +If the backend engine understands to change the DNS server and lookup domain, +this options will be used to alter the default DNS config to a custom one for a specific step. + +```yaml +steps: + - name: build + image: plugin/abc + dns: 1.2.3.4 + dns_search: 'internal.company' +``` + ## Privileged mode Woodpecker gives the ability to configure privileged mode in the YAML. You can use this parameter to launch containers with escalated capabilities. @@ -766,8 +789,8 @@ Privileged mode is only available to trusted repositories and for security reaso commands: - docker --tls=false ps - - name: services - docker: + services: + - name: docker image: docker:dind commands: dockerd-entrypoint.sh --storage-driver=vfs --tls=false + privileged: true diff --git a/docs/docs/20-usage/25-workflows.md b/docs/docs/20-usage/25-workflows.md index 5adc39f85..ef09d485e 100644 --- a/docs/docs/20-usage/25-workflows.md +++ b/docs/docs/20-usage/25-workflows.md @@ -23,13 +23,13 @@ If you still need to pass artifacts between the workflows you need use some stor ```bash .woodpecker/ -├── .build.yaml -├── .deploy.yaml -├── .lint.yaml -└── .test.yaml +├── build.yaml +├── deploy.yaml +├── lint.yaml +└── test.yaml ``` -```yaml title=".woodpecker/.build.yaml" +```yaml title=".woodpecker/build.yaml" steps: - name: build image: debian:stable-slim @@ -38,7 +38,7 @@ steps: - sleep 5 ``` -```yaml title=".woodpecker/.deploy.yaml" +```yaml title=".woodpecker/deploy.yaml" steps: - name: deploy image: debian:stable-slim @@ -51,7 +51,7 @@ depends_on: - test ``` -```yaml title=".woodpecker/.test.yaml" +```yaml title=".woodpecker/test.yaml" steps: - name: test image: debian:stable-slim @@ -63,7 +63,7 @@ depends_on: - build ``` -```yaml title=".woodpecker/.lint.yaml" +```yaml title=".woodpecker/lint.yaml" steps: - name: lint image: debian:stable-slim diff --git a/docs/docs/20-usage/40-secrets.md b/docs/docs/20-usage/40-secrets.md index 1b55d9ce1..059bb52dd 100644 --- a/docs/docs/20-usage/40-secrets.md +++ b/docs/docs/20-usage/40-secrets.md @@ -1,132 +1,155 @@ # Secrets -Woodpecker provides the ability to store named parameters external to the YAML configuration file, in a central secret store. These secrets can be passed to individual steps of the pipeline at runtime. +Woodpecker provides the ability to store named variables in a central secret store. +These secrets can be passed securely to individual pipeline steps using the `from_secret` keyword. -Woodpecker provides three different levels to add secrets to your pipeline. The following list shows the priority of the different levels. If a secret is defined in multiple levels, will be used following this priorities: Repository secrets > Organization secrets > Global secrets. +Three different levels of secrets are available. +The following list shows the priority of these. +If a secret is defined in multiple levels, the following precedence applies: Repository secrets > Organization secrets > Global secrets. -1. **Repository secrets**: They are available to all pipelines of an repository. -2. **Organization secrets**: They are available to all pipelines of an organization. -3. **Global secrets**: Can be configured by an instance admin. - They are available to all pipelines of the **whole** Woodpecker instance and should therefore **only** be used for secrets that are allowed to be read by **all** users. +1. **Repository secrets**: Available to all pipelines of a repository. +1. **Organization secrets**: Available to all pipelines of an organization. +1. **Global secrets**: Can only be set by instance admins. + Global secret are available to all pipelines of the **entire** Woodpecker instance and should therefore be used with caution. + +:::tip +In addition to the native secret integration, external secret providers can be utilized by interacting with them directly within pipeline steps. +Access to these providers can be configured using Woodpecker secrets, enabling the retrieval of secrets from the respective external sources. +::: + +:::warning +Woodpecker can mask secrets from its native secret store, but it cannot apply the same protection to external secrets. As a result, these external secrets may be exposed in the pipeline logs. +::: ## Usage -### Use secrets in commands +You can set a setting or environment value from Woodpecker secrets using the `from_secret` syntax. -Secrets are exposed to your pipeline steps and plugins as uppercase environment variables and can therefore be referenced in the commands section of your pipeline, -once their usage is declared in the `secrets` section: +The example below passes a secret called `secret_token` which will be stored in an environment variable named `TOKEN_ENV`: ```diff steps: - - name: docker - image: docker + - name: 'step name' + image: registry/repo/image:tag commands: -+ - echo $docker_username -+ - echo $DOCKER_PASSWORD -+ secrets: [ docker_username, DOCKER_PASSWORD ] -``` - -The case of the environment variables is not changed, but secret matching is done case-insensitively. In the example above, `DOCKER_PASSWORD` would also match if the secret is called `docker_password`. - -### Use secrets in settings and environment - -You can set an setting or environment value from secrets using the `from_secret` syntax. - -In this example, the secret named `secret_token` would be passed to the setting named `token`,which will be available in the plugin as environment variable named `PLUGIN_TOKEN` (See [plugins](./51-plugins/20-creating-plugins.md#settings) for details), and to the environment variable `TOKEN_ENV`. - -```diff - steps: - - name: docker - image: my-plugin ++ - echo "The secret is $TOKEN_ENV" + environment: + TOKEN_ENV: + from_secret: secret_token +``` + +The same syntax can be used to pass secrets to (plugin) settings. +A secret named `secret_token` is assigned to the setting `TOKEN`, which will then be available in the plugin as environment variable `PLUGIN_TOKEN` (see [plugins](./51-plugins/20-creating-plugins.md#settings) for details). +`PLUGIN_TOKEN` is then internally consumed by the plugin itself and will be honored during execution. + +```diff + steps: + - name: 'step name' + image: registry/repo/image:tag + settings: -+ token: ++ TOKEN: + from_secret: secret_token ``` ### Note about parameter pre-processing -Please note parameter expressions are subject to pre-processing. When using secrets in parameter expressions they should be escaped. +Please note that parameter expressions undergo pre-processing, meaning they are evaluated before the pipeline starts. +If secrets are to be used in expressions, they must be properly escaped (using `$$`) to ensure correct handling. ```diff steps: - name: docker image: docker commands: -- - echo ${docker_username} -- - echo ${DOCKER_PASSWORD} -+ - echo $${docker_username} -+ - echo $${DOCKER_PASSWORD} - secrets: [ docker_username, DOCKER_PASSWORD ] +- - echo ${TOKEN_ENV} ++ - echo $${TOKEN_ENV} + environment: + TOKEN_ENV: + from_secret: secret_token ``` ### Use in Pull Requests events -Secrets are not exposed to pull requests by default. You can override this behavior by creating the secret and enabling the `pull_request` event type, either in UI or by CLI, see below. +By default, secrets are not exposed to pull requests. +However, you can change this behavior by creating the secret and enabling the `pull_request` event type. +This can be configured either through the UI or via the CLI, as demonstrated below. -:::note -Please be careful when exposing secrets to pull requests. If your repository is open source and accepts pull requests your secrets are not safe. A bad actor can submit a malicious pull request that exposes your secrets. +:::warning +Be cautious when exposing secrets to pull requests. +If your repository is public and initiates pull request runs without requiring approval, your secrets may be at risk. +Malicious actors could potentially exploit this to expose or transmit your secrets to an external location. ::: -## Image filter +## Plugins filter -To prevent abusing your secrets from malicious usage, you can limit a secret to a list of images. If enabled they are not available to any other plugin (steps without user-defined commands). If you or an attacker defines explicit commands, the secrets will not be available to the container to prevent leaking them. +To prevent abusing your secrets from malicious usage, you can limit a secret to a list of plugins. +If enabled they are not available to any other plugin (steps without user-defined commands). +Plugins have the advantage that they cannot run arbitrary commands, hence they cannot be used to expose secrets (in contrast to arbitrary steps). + +:::note +If you specify a tag, the filter will honor it. +However, if the same image appears multiple times in the list, the least privileged entry takes precedence. +For example, an image without a tag will permit all tags, even if another entry with a pinned tag is included. +::: + +![plugins filter](./secrets-plugins-filter.png) ## Adding Secrets -Secrets are added to the Woodpecker in the UI or with the CLI. +Secrets can be added through the UI or via the CLI. ### CLI Examples -Create the secret using default settings. The secret will be available to all images in your pipeline, and will be available to all push, tag, and deployment events (not pull request events). +Create the secret using default settings. +The secret will be available to all images in your pipeline, and will be available to all `push`, `tag`, and `deployment` events (not `pull_request` events). ```bash -woodpecker-cli secret add \ - -repository octocat/hello-world \ - -name aws_access_key_id \ - -value +woodpecker-cli repo secret add \ + --repository octocat/hello-world \ + --name aws_access_key_id \ + --value ``` -Create the secret and limit to a single image: +Create the secret and limit it to a single image: ```diff woodpecker-cli secret add \ - -repository octocat/hello-world \ -+ -image plugins/s3 \ - -name aws_access_key_id \ - -value + --repository octocat/hello-world \ ++ --image woodpeckerci/plugin-s3 \ + --name aws_access_key_id \ + --value ``` -Create the secrets and limit to a set of images: +Create the secrets and limit it to a set of images: ```diff - woodpecker-cli secret add \ - -repository octocat/hello-world \ -+ -image plugins/s3 \ -+ -image peloton/woodpecker-ecs \ - -name aws_access_key_id \ - -value + woodpecker-cli repo secret add \ + --repository octocat/hello-world \ ++ --image woodpeckerci/plugin-s3 \ ++ --image woodpeckerci/plugin-docker-buildx \ + --name aws_access_key_id \ + --value ``` -Create the secret and enable for multiple hook events: +Create the secret and enable it for multiple hook events: ```diff - woodpecker-cli secret add \ - -repository octocat/hello-world \ - -image plugins/s3 \ -+ -event pull_request \ -+ -event push \ -+ -event tag \ - -name aws_access_key_id \ - -value + woodpecker-cli repo secret add \ + --repository octocat/hello-world \ + --image woodpeckerci/plugin-s3 \ ++ --event pull_request \ ++ --event push \ ++ --event tag \ + --name aws_access_key_id \ + --value ``` -Loading secrets from file using curl `@` syntax. This is the recommended approach for loading secrets from file to preserve newlines: +Secrets can be loaded from a file using the `@` syntax. +This method is recommended for loading secrets from a file, as it ensures that newlines are preserved (this is for example important for SSH keys). +Here’s an example: ```diff - woodpecker-cli secret add \ + woodpecker-cli repo secret add \ -repository octocat/hello-world \ -name ssh_key \ + -value @/root/ssh/id_rsa diff --git a/docs/docs/20-usage/41-registries.md b/docs/docs/20-usage/41-registries.md index 8508da876..a8aab8b33 100644 --- a/docs/docs/20-usage/41-registries.md +++ b/docs/docs/20-usage/41-registries.md @@ -32,12 +32,8 @@ Example registry hostname matching logic: - Hostname `gcr.io` matches image `gcr.io/foo/bar` - Hostname `docker.io` matches `golang` - Hostname `docker.io` matches `library/golang` -- Hostname `docker.io` matches `bradyrydzewski/golang` -- Hostname `docker.io` matches `bradyrydzewski/golang:latest` - -:::note -The flow above doesn't work in Kubernetes. There is [workaround](../30-administration/22-backends/40-kubernetes.md#images-from-private-registries). -::: +- Hostname `docker.io` matches `bradrydzewski/golang` +- Hostname `docker.io` matches `bradrydzewski/golang:latest` ## Global registry support diff --git a/docs/docs/20-usage/45-cron.md b/docs/docs/20-usage/45-cron.md index 95ee8202e..2cb088122 100644 --- a/docs/docs/20-usage/45-cron.md +++ b/docs/docs/20-usage/45-cron.md @@ -23,12 +23,6 @@ To configure cron jobs you need at least push access to the repository. ![cron settings](./cron-settings.png) - The supported schedule syntax can be found at . If you need general understanding of the cron syntax is a good place to start and experiment. + The supported schedule syntax can be found at . If you need general understanding of the cron syntax is a good place to start and experiment. - Examples: `@every 5m`, `@daily`, `0 30 * * * *` ... - - :::info - Woodpeckers cron syntax starts with seconds instead of minutes as used by most linux cron schedulers. - - Example: "At minute 30 every hour" would be `0 30 * * * *` instead of `30 * * * *` - ::: + Examples: `@every 5m`, `@daily`, `30 * * * *` ... diff --git a/docs/docs/20-usage/50-environment.md b/docs/docs/20-usage/50-environment.md index 299bb8f53..746cb7634 100644 --- a/docs/docs/20-usage/50-environment.md +++ b/docs/docs/20-usage/50-environment.md @@ -48,97 +48,94 @@ Please note that the environment section is not able to expand environment varia This is the reference list of all environment variables available to your pipeline containers. These are injected into your pipeline step and plugins containers, at runtime. -| NAME | Description | -| -------------------------------- | ------------------------------------------------------------------------------------------------------------------ | -| `CI` | CI environment name (value: `woodpecker`) | -| | **Repository** | -| `CI_REPO` | repository full name `/` | -| `CI_REPO_OWNER` | repository owner | -| `CI_REPO_NAME` | repository name | -| `CI_REPO_REMOTE_ID` | repository remote ID, is the UID it has in the forge | -| `CI_REPO_SCM` | repository SCM (git) | -| `CI_REPO_URL` | repository web URL | -| `CI_REPO_CLONE_URL` | repository clone URL | -| `CI_REPO_CLONE_SSH_URL` | repository SSH clone URL | -| `CI_REPO_DEFAULT_BRANCH` | repository default branch (main) | -| `CI_REPO_PRIVATE` | repository is private | -| `CI_REPO_TRUSTED` | repository is trusted | -| | **Current Commit** | -| `CI_COMMIT_SHA` | commit SHA | -| `CI_COMMIT_REF` | commit ref | -| `CI_COMMIT_REFSPEC` | commit ref spec | -| `CI_COMMIT_BRANCH` | commit branch (equals target branch for pull requests) | -| `CI_COMMIT_SOURCE_BRANCH` | commit source branch (empty if event is not `pull_request` or `pull_request_closed`) | -| `CI_COMMIT_TARGET_BRANCH` | commit target branch (empty if event is not `pull_request` or `pull_request_closed`) | -| `CI_COMMIT_TAG` | commit tag name (empty if event is not `tag`) | -| `CI_COMMIT_PULL_REQUEST` | commit pull request number (empty if event is not `pull_request` or `pull_request_closed`) | -| `CI_COMMIT_PULL_REQUEST_LABELS` | labels assigned to pull request (empty if event is not `pull_request` or `pull_request_closed`) | -| `CI_COMMIT_MESSAGE` | commit message | -| `CI_COMMIT_AUTHOR` | commit author username | -| `CI_COMMIT_AUTHOR_EMAIL` | commit author email address | -| `CI_COMMIT_AUTHOR_AVATAR` | commit author avatar | -| `CI_COMMIT_PRERELEASE` | release is a pre-release (empty if event is not `release`) | -| | **Current pipeline** | -| `CI_PIPELINE_NUMBER` | pipeline number | -| `CI_PIPELINE_PARENT` | number of parent pipeline | -| `CI_PIPELINE_EVENT` | pipeline event (see [pipeline events](../20-usage/15-terminology/index.md#pipeline-events)) | -| `CI_PIPELINE_URL` | link to the web UI for the pipeline | -| `CI_PIPELINE_FORGE_URL` | link to the forge's web UI for the commit(s) or tag that triggered the pipeline | -| `CI_PIPELINE_DEPLOY_TARGET` | pipeline deploy target for `deployment` events (i.e. production) | -| `CI_PIPELINE_DEPLOY_TASK` | pipeline deploy task for `deployment` events (i.e. migration) | -| `CI_PIPELINE_STATUS` | pipeline status (success, failure) | -| `CI_PIPELINE_CREATED` | pipeline created UNIX timestamp | -| `CI_PIPELINE_STARTED` | pipeline started UNIX timestamp | -| `CI_PIPELINE_FINISHED` | pipeline finished UNIX timestamp | -| `CI_PIPELINE_FILES` | changed files (empty if event is not `push` or `pull_request`), it is undefined if more than 500 files are touched | -| | **Current workflow** | -| `CI_WORKFLOW_NAME` | workflow name | -| | **Current step** | -| `CI_STEP_NAME` | step name | -| `CI_STEP_NUMBER` | step number | -| `CI_STEP_STATUS` | step status (success, failure) | -| `CI_STEP_STARTED` | step started UNIX timestamp | -| `CI_STEP_FINISHED` | step finished UNIX timestamp | -| `CI_STEP_URL` | URL to step in UI | -| | **Previous commit** | -| `CI_PREV_COMMIT_SHA` | previous commit SHA | -| `CI_PREV_COMMIT_REF` | previous commit ref | -| `CI_PREV_COMMIT_REFSPEC` | previous commit ref spec | -| `CI_PREV_COMMIT_BRANCH` | previous commit branch | -| `CI_PREV_COMMIT_SOURCE_BRANCH` | previous commit source branch | -| `CI_PREV_COMMIT_TARGET_BRANCH` | previous commit target branch | -| `CI_PREV_COMMIT_URL` | previous commit link in forge | -| `CI_PREV_COMMIT_MESSAGE` | previous commit message | -| `CI_PREV_COMMIT_AUTHOR` | previous commit author username | -| `CI_PREV_COMMIT_AUTHOR_EMAIL` | previous commit author email address | -| `CI_PREV_COMMIT_AUTHOR_AVATAR` | previous commit author avatar | -| | **Previous pipeline** | -| `CI_PREV_PIPELINE_NUMBER` | previous pipeline number | -| `CI_PREV_PIPELINE_PARENT` | previous pipeline number of parent pipeline | -| `CI_PREV_PIPELINE_EVENT` | previous pipeline event (see [pipeline events](../20-usage/15-terminology/index.md#pipeline-events)) | -| `CI_PREV_PIPELINE_URL` | previous pipeline link in CI | -| `CI_PREV_PIPELINE_FORGE_URL` | previous pipeline link to event in forge | -| `CI_PREV_PIPELINE_DEPLOY_TARGET` | previous pipeline deploy target for `deployment` events (ie production) | -| `CI_PREV_PIPELINE_DEPLOY_TASK` | previous pipeline deploy task for `deployment` events (ie migration) | -| `CI_PREV_PIPELINE_STATUS` | previous pipeline status (success, failure) | -| `CI_PREV_PIPELINE_CREATED` | previous pipeline created UNIX timestamp | -| `CI_PREV_PIPELINE_STARTED` | previous pipeline started UNIX timestamp | -| `CI_PREV_PIPELINE_FINISHED` | previous pipeline finished UNIX timestamp | -| |   | -| `CI_WORKSPACE` | Path of the workspace where source code gets cloned to | -| | **System** | -| `CI_SYSTEM_NAME` | name of the CI system: `woodpecker` | -| `CI_SYSTEM_URL` | link to CI system | -| `CI_SYSTEM_HOST` | hostname of CI server | -| `CI_SYSTEM_VERSION` | version of the server | -| | **Forge** | -| `CI_FORGE_TYPE` | name of forge (gitea, github, ...) | -| `CI_FORGE_URL` | root URL of configured forge | -| | **Internal** - Please don't use! | -| `CI_SCRIPT` | Internal script path. Used to call pipeline step commands. | -| `CI_NETRC_USERNAME` | Credentials for private repos to be able to clone data. (Only available for specific images) | -| `CI_NETRC_PASSWORD` | Credentials for private repos to be able to clone data. (Only available for specific images) | -| `CI_NETRC_MACHINE` | Credentials for private repos to be able to clone data. (Only available for specific images) | +| NAME | Description | Example | +| -------------------------------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------ | +| `CI` | CI environment name | `woodpecker` | +| | **Repository** | | +| `CI_REPO` | repository full name `/` | `john-doe/my-repo` | +| `CI_REPO_OWNER` | repository owner | `john-doe` | +| `CI_REPO_NAME` | repository name | `my-repo` | +| `CI_REPO_REMOTE_ID` | repository remote ID, is the UID it has in the forge | `82` | +| `CI_REPO_URL` | repository web URL | `https://git.example.com/john-doe/my-repo` | +| `CI_REPO_CLONE_URL` | repository clone URL | `https://git.example.com/john-doe/my-repo.git` | +| `CI_REPO_CLONE_SSH_URL` | repository SSH clone URL | `git@git.example.com:john-doe/my-repo.git` | +| `CI_REPO_DEFAULT_BRANCH` | repository default branch | `main` | +| `CI_REPO_PRIVATE` | repository is private | `true` | +| `CI_REPO_TRUSTED_NETWORK` | repository has trusted network access | `false` | +| `CI_REPO_TRUSTED_VOLUMES` | repository has trusted volumes access | `false` | +| `CI_REPO_TRUSTED_SECURITY` | repository has trusted security access | `false` | +| | **Current Commit** | | +| `CI_COMMIT_SHA` | commit SHA | `eba09b46064473a1d345da7abf28b477468e8dbd` | +| `CI_COMMIT_REF` | commit ref | `refs/heads/main` | +| `CI_COMMIT_REFSPEC` | commit ref spec | `issue-branch:main` | +| `CI_COMMIT_BRANCH` | commit branch (equals target branch for pull requests) | `main` | +| `CI_COMMIT_SOURCE_BRANCH` | commit source branch (set only for `pull_request` and `pull_request_closed` events) | `issue-branch` | +| `CI_COMMIT_TARGET_BRANCH` | commit target branch (set only for `pull_request` and `pull_request_closed` events) | `main` | +| `CI_COMMIT_TAG` | commit tag name (empty if event is not `tag`) | `v1.10.3` | +| `CI_COMMIT_PULL_REQUEST` | commit pull request number (set only for `pull_request` and `pull_request_closed` events) | `1` | +| `CI_COMMIT_PULL_REQUEST_LABELS` | labels assigned to pull request (set only for `pull_request` and `pull_request_closed` events) | `server` | +| `CI_COMMIT_MESSAGE` | commit message | `Initial commit` | +| `CI_COMMIT_AUTHOR` | commit author username | `john-doe` | +| `CI_COMMIT_AUTHOR_EMAIL` | commit author email address | `john-doe@example.com` | +| `CI_COMMIT_AUTHOR_AVATAR` | commit author avatar | `https://git.example.com/avatars/5dcbcadbce6f87f8abef` | +| `CI_COMMIT_PRERELEASE` | release is a pre-release (empty if event is not `release`) | `false` | +| | **Current pipeline** | | +| `CI_PIPELINE_NUMBER` | pipeline number | `8` | +| `CI_PIPELINE_PARENT` | number of parent pipeline | `0` | +| `CI_PIPELINE_EVENT` | pipeline event (see [`event`](../20-usage/20-workflow-syntax.md#event)) | `push`, `pull_request`, `pull_request_closed`, `tag`, `release`, `manual`, `cron` | +| `CI_PIPELINE_URL` | link to the web UI for the pipeline | `https://ci.example.com/repos/7/pipeline/8` | +| `CI_PIPELINE_FORGE_URL` | link to the forge's web UI for the commit(s) or tag that triggered the pipeline | `https://git.example.com/john-doe/my-repo/commit/eba09b46064473a1d345da7abf28b477468e8dbd` | +| `CI_PIPELINE_DEPLOY_TARGET` | pipeline deploy target for `deployment` events | `production` | +| `CI_PIPELINE_DEPLOY_TASK` | pipeline deploy task for `deployment` events | `migration` | +| `CI_PIPELINE_CREATED` | pipeline created UNIX timestamp | `1722617519` | +| `CI_PIPELINE_STARTED` | pipeline started UNIX timestamp | `1722617519` | +| `CI_PIPELINE_FILES` | changed files (empty if event is not `push` or `pull_request`), it is undefined if more than 500 files are touched | `[]`, `[".woodpecker.yml","README.md"]` | +| | **Current workflow** | | +| `CI_WORKFLOW_NAME` | workflow name | `release` | +| | **Current step** | | +| `CI_STEP_NAME` | step name | `build package` | +| `CI_STEP_NUMBER` | step number | `0` | +| `CI_STEP_STARTED` | step started UNIX timestamp | `1722617519` | +| `CI_STEP_URL` | URL to step in UI | `https://ci.example.com/repos/7/pipeline/8` | +| | **Previous commit** | | +| `CI_PREV_COMMIT_SHA` | previous commit SHA | `15784117e4e103f36cba75a9e29da48046eb82c4` | +| `CI_PREV_COMMIT_REF` | previous commit ref | `refs/heads/main` | +| `CI_PREV_COMMIT_REFSPEC` | previous commit ref spec | `issue-branch:main` | +| `CI_PREV_COMMIT_BRANCH` | previous commit branch | `main` | +| `CI_PREV_COMMIT_SOURCE_BRANCH` | previous commit source branch (set only for `pull_request` and `pull_request_closed` events) | `issue-branch` | +| `CI_PREV_COMMIT_TARGET_BRANCH` | previous commit target branch (set only for `pull_request` and `pull_request_closed` events) | `main` | +| `CI_PREV_COMMIT_URL` | previous commit link in forge | `https://git.example.com/john-doe/my-repo/commit/15784117e4e103f36cba75a9e29da48046eb82c4` | +| `CI_PREV_COMMIT_MESSAGE` | previous commit message | `test` | +| `CI_PREV_COMMIT_AUTHOR` | previous commit author username | `john-doe` | +| `CI_PREV_COMMIT_AUTHOR_EMAIL` | previous commit author email address | `john-doe@example.com` | +| `CI_PREV_COMMIT_AUTHOR_AVATAR` | previous commit author avatar | `https://git.example.com/avatars/12` | +| | **Previous pipeline** | | +| `CI_PREV_PIPELINE_NUMBER` | previous pipeline number | `7` | +| `CI_PREV_PIPELINE_PARENT` | previous pipeline number of parent pipeline | `0` | +| `CI_PREV_PIPELINE_EVENT` | previous pipeline event (see [`event`](../20-usage/20-workflow-syntax.md#event)) | `push`, `pull_request`, `pull_request_closed`, `tag`, `release`, `manual`, `cron` | +| `CI_PREV_PIPELINE_URL` | previous pipeline link in CI | `https://ci.example.com/repos/7/pipeline/7` | +| `CI_PREV_PIPELINE_FORGE_URL` | previous pipeline link to event in forge | `https://git.example.com/john-doe/my-repo/commit/15784117e4e103f36cba75a9e29da48046eb82c4` | +| `CI_PREV_PIPELINE_DEPLOY_TARGET` | previous pipeline deploy target for `deployment` events | `production` | +| `CI_PREV_PIPELINE_DEPLOY_TASK` | previous pipeline deploy task for `deployment` events | `migration` | +| `CI_PREV_PIPELINE_STATUS` | previous pipeline status | `success`, `failure` | +| `CI_PREV_PIPELINE_CREATED` | previous pipeline created UNIX timestamp | `1722610173` | +| `CI_PREV_PIPELINE_STARTED` | previous pipeline started UNIX timestamp | `1722610173` | +| `CI_PREV_PIPELINE_FINISHED` | previous pipeline finished UNIX timestamp | `1722610383` | +| |   | | +| `CI_WORKSPACE` | Path of the workspace where source code gets cloned to | `/woodpecker/src/git.example.com/john-doe/my-repo` | +| | **System** | | +| `CI_SYSTEM_NAME` | name of the CI system | `woodpecker` | +| `CI_SYSTEM_URL` | link to CI system | `https://ci.example.com` | +| `CI_SYSTEM_HOST` | hostname of CI server | `ci.example.com` | +| `CI_SYSTEM_VERSION` | version of the server | `2.7.0` | +| | **Forge** | | +| `CI_FORGE_TYPE` | name of forge | `bitbucket` , `bitbucket_dc` , `forgejo` , `gitea` , `github` , `gitlab` | +| `CI_FORGE_URL` | root URL of configured forge | `https://git.example.com` | +| | **Internal** - Please don't use! | | +| `CI_SCRIPT` | Internal script path. Used to call pipeline step commands. | | +| `CI_NETRC_USERNAME` | Credentials for private repos to be able to clone data. (Only available for specific images) | | +| `CI_NETRC_PASSWORD` | Credentials for private repos to be able to clone data. (Only available for specific images) | | +| `CI_NETRC_MACHINE` | Credentials for private repos to be able to clone data. (Only available for specific images) | | ## Global environment variables @@ -172,7 +169,7 @@ Example commit substitution: ```diff steps: - name: docker - image: plugins/docker + image: woodpeckerci/plugin-kaniko settings: + tags: ${CI_COMMIT_SHA} ``` @@ -182,7 +179,7 @@ Example tag substitution: ```diff steps: - name: docker - image: plugins/docker + image: woodpeckerci/plugin-kaniko settings: + tags: ${CI_COMMIT_TAG} ``` @@ -210,7 +207,7 @@ Example variable substitution with substring: ```diff steps: - name: docker - image: plugins/docker + image: woodpeckerci/plugin-kaniko settings: + tags: ${CI_COMMIT_SHA:0:8} ``` @@ -220,7 +217,7 @@ Example variable substitution strips `v` prefix from `v.1.0.0`: ```diff steps: - name: docker - image: plugins/docker + image: woodpeckerci/plugin-kaniko settings: + tags: ${CI_COMMIT_TAG##v} ``` diff --git a/docs/docs/20-usage/51-plugins/20-creating-plugins.md b/docs/docs/20-usage/51-plugins/20-creating-plugins.md index 8a0ea5920..4591a1d1a 100644 --- a/docs/docs/20-usage/51-plugins/20-creating-plugins.md +++ b/docs/docs/20-usage/51-plugins/20-creating-plugins.md @@ -10,7 +10,7 @@ These are passed to your plugin as uppercase env vars with a `PLUGIN_` prefix. Using a setting like `url` results in an env var named `PLUGIN_URL`. Characters like `-` are converted to an underscore (`_`). `some_String` gets `PLUGIN_SOME_STRING`. -CamelCase is not respected, `anInt` get `PLUGIN_ANINT`. +CamelCase is not respected, `anInt` get `PLUGIN_ANINT`. ### Basic settings @@ -42,7 +42,7 @@ Values like this are converted to JSON and then passed to your plugin. In the ex ### Secrets -Secrets should be passed as settings too. Therefore, users should use [`from_secret`](../40-secrets.md#use-secrets-in-settings-and-environment). +Secrets should be passed as settings too. Therefore, users should use [`from_secret`](../40-secrets.md#usage). ## Plugin library diff --git a/docs/docs/20-usage/51-plugins/51-overview.md b/docs/docs/20-usage/51-plugins/51-overview.md index ab8db3df3..ea43d66e7 100644 --- a/docs/docs/20-usage/51-plugins/51-overview.md +++ b/docs/docs/20-usage/51-plugins/51-overview.md @@ -1,9 +1,28 @@ # Plugins -Plugins are pipeline steps that perform pre-defined tasks and are configured as steps in your pipeline. Plugins can be used to deploy code, publish artifacts, send notification, and more. +Plugins are pipeline steps that perform pre-defined tasks and are configured as steps in your pipeline. +Plugins can be used to deploy code, publish artifacts, send notification, and more. They are automatically pulled from the default container registry the agent's have configured. +```dockerfile title="Dockerfile" +FROM cloud/kubectl +COPY deploy /usr/local/deploy +ENTRYPOINT ["/usr/local/deploy"] +``` + +```bash title="deploy" +kubectl apply -f $PLUGIN_TEMPLATE +``` + +```yaml title=".woodpecker.yaml" +steps: + - name: deploy-to-k8s + image: cloud/my-k8s-plugin + settings: + template: config/k8s/service.yaml +``` + Example pipeline using the Docker and Slack plugins: ```yaml @@ -15,7 +34,7 @@ steps: - go test - name: publish - image: plugins/docker + image: woodpeckerci/plugin-kaniko settings: repo: foo/bar tags: latest @@ -29,6 +48,12 @@ steps: ## Plugin Isolation Plugins are just pipeline steps. They share the build workspace, mounted as a volume, and therefore have access to your source tree. +While normal steps are all about arbitrary code execution, plugins should only allow the functions intended by the plugin author. + +That's why there are a few limitations. The workspace base is always mounted at `/woodpecker`, but the working directory is dynamically +adjusted accordingly, as user of a plugin you should not have to care about this. Also, you cannot use the plugin together with `commands` +or `entrypoint` which will fail. Using `environment` is possible, but in this case, the plugin is internally not treated as plugin +anymore. The container then cannot access secrets with plugin filter anymore and the containers won't be privileged without explicit definition. ## Finding Plugins diff --git a/docs/docs/20-usage/60-services.md b/docs/docs/20-usage/60-services.md index 4992e5dc2..14262855c 100644 --- a/docs/docs/20-usage/60-services.md +++ b/docs/docs/20-usage/60-services.md @@ -110,5 +110,5 @@ steps: commands: - ( apt update && apt dist-upgrade -y && apt install -y mysql-client 2>&1 )> /dev/null - sleep 30s # need to wait for mysql-server init - - echo 'SHOW VARIABLES LIKE "version"' | mysql -uroot -hdatabase test -pexample + - echo 'SHOW VARIABLES LIKE "version"' | mysql -u root -h database test -p example ``` diff --git a/docs/docs/20-usage/75-project-settings.md b/docs/docs/20-usage/75-project-settings.md index 24bdbe605..d4495c41b 100644 --- a/docs/docs/20-usage/75-project-settings.md +++ b/docs/docs/20-usage/75-project-settings.md @@ -25,10 +25,9 @@ Only activate this option if you trust all users who have push access to your re Otherwise, these users will be able to steal secrets that are only available for `deploy` events. ::: -## Protected +## Require approval for -Every pipeline initiated by an webhook event needs to be approved by a project members with push permissions before being executed. -The protected option can be used as an additional review process before running potentially harmful pipelines. Especially if pipelines can be executed by third-parties through pull-requests. +To prevent malicious pipelines from extracting secrets or running harmful commands or to prevent accidental pipeline runs, you can require approval for an additional review process. Depending on the enabled option, a pipeline will be put on hold after creation and will only continue after approval. The default restrictive setting is `Approvals for forked repositories`. ## Trusted @@ -40,9 +39,21 @@ Only server admins can set this option. If you are not a server admin this optio ::: -## Only inject netrc credentials into trusted containers +## Custom trusted clone plugins -Cloning pipeline step may need git credentials. They are injected via netrc. By default, they're only injected if this option is enabled, the repo is trusted ([see above](#trusted)) or the image is a trusted clone image. If you uncheck the option, git credentials will be injected into any container in clone step. +During the clone process, Git credentials (e.g., for private repositories) may be required. +These credentials are provided via [`netrc`](https://everything.curl.dev/usingcurl/netrc.html). + +These credentials are injected only into trusted plugins specified in the environment variable `WOODPECKER_PLUGINS_TRUSTED_CLONE` (an instance-wide Woodpecker server setting) or declared in this repository-level setting. + +With these credentials, it’s possible to perform any Git operations, including pushing changes back to the repo. +To prevent unauthorized access or misuse, a plugin allowlist is required, either on the instance level or the repository level. +Without an explicit allowlist, a malicious contributor could exploit a custom clone plugin in a Pull Request to reveal or transfer these credentials during the clone step. + +:::info +This setting does not affect subsequent steps, nor does it allow direct pushes to the repository. +To enable pushing changes, you can inject Git credentials as a secret or use a dedicated plugin, such as [appleboy/drone-git-push](https://woodpecker-ci.org/plugins/Git%20Push). +::: ## Project visibility diff --git a/docs/docs/20-usage/90-advanced-usage.md b/docs/docs/20-usage/90-advanced-usage.md index 065386fdf..e8a691de0 100644 --- a/docs/docs/20-usage/90-advanced-usage.md +++ b/docs/docs/20-usage/90-advanced-usage.md @@ -97,7 +97,7 @@ steps: ### References - [Official YAML specification](https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases) -- [YAML Cheatsheet](https://learnxinyminutes.com/docs/yaml) +- [YAML cheat sheet](https://learnxinyminutes.com/docs/yaml) ## Persisting environment data between steps @@ -127,3 +127,96 @@ WOODPECKER_ENVIRONMENT=first_var:value1,second_var:value2 ``` Note that this tightly couples the server and app configurations (where the app is a completely separate application). But this is a good option for truly global variables which should apply to all steps in all pipelines for all apps. + +## Docker in docker (dind) setup + +:::warning +This set up will only work on trusted repositories and for security reasons should only be used in private environments. +See [project settings](./75-project-settings.md#trusted) to enable "trusted" mode. +::: + +The snippet below shows how a step can communicate with the docker daemon running in a `docker:dind` service. + +:::note +If your goal is to build/publish OCI images, consider using the [Docker Buildx Plugin](https://woodpecker-ci.org/plugins/Docker%20Buildx) instead. +::: + +First we need to define a service running a docker with the `dind` tag. +This service must run in `privileged` mode: + +```yaml +services: + - name: docker + image: docker:dind # use 'docker:-dind' or similar in production + privileged: true + ports: + - 2376 +``` + +Next, we need to set up TLS communication between the `dind` service and the step that wants to communicate with the docker daemon (unauthenticated TCP connections have been deprecated [as of docker v27](https://github.com/docker/cli/blob/v27.4.0/docs/deprecated.md#unauthenticated-tcp-connections) and will result in an error in v28). + +This can be achieved by letting the daemon generate TLS certificates and share them with the client through an agent volume mount (`/opt/woodpeckerci/dind-certs` in the example below). + +```diff +services: + - name: docker + image: docker:dind # use 'docker:-dind' or similar in production + privileged: true ++ environment: ++ DOCKER_TLS_CERTDIR: /dind-certs ++ volumes: ++ - /opt/woodpeckerci/dind-certs:/dind-certs + ports: + - 2376 +``` + +In the docker client step: + +1. Set the `DOCKER_*` environment variables shown below to configure the connection with the daemon. + These generic docker environment variables that are framework-agnostic (e.g. frameworks like [TestContainers](https://testcontainers.com/), [Spring Boot Docker Compose](https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-docker-compose) do all respect them). +2. Mount the volume to the location where the daemon has created the certificates (`/opt/woodpeckerci/dind-certs`) + +Test the connection with the docker client: + +```diff +steps: + - name: test + image: docker:cli # in production use something like 'docker:-cli' ++ environment: ++ DOCKER_HOST: "tcp://docker:2376" ++ DOCKER_CERT_PATH: "/dind-certs/client" ++ DOCKER_TLS_VERIFY: "1" ++ volumes: ++ - /opt/woodpeckerci/dind-certs:/dind-certs + commands: + - docker version +``` + +This step should output the server and client version information if everything has been set up correctly. + +Full example: + +```yaml +steps: + - name: test + image: docker:cli # use 'docker:-cli' or similar in production + environment: + DOCKER_HOST: 'tcp://docker:2376' + DOCKER_CERT_PATH: '/dind-certs/client' + DOCKER_TLS_VERIFY: '1' + volumes: + - /opt/woodpeckerci/dind-certs:/dind-certs + commands: + - docker version + +services: + - name: docker + image: docker:dind # use 'docker:-dind' or similar in production + privileged: true + environment: + DOCKER_TLS_CERTDIR: /dind-certs + volumes: + - /opt/woodpeckerci/dind-certs:/dind-certs + ports: + - 2376 +``` diff --git a/docs/docs/20-usage/pipeline.png b/docs/docs/20-usage/pipeline.png new file mode 100644 index 000000000..dd4063c9a Binary files /dev/null and b/docs/docs/20-usage/pipeline.png differ diff --git a/docs/docs/20-usage/project-settings.png b/docs/docs/20-usage/project-settings.png index fc29daac8..f3ce025f2 100644 Binary files a/docs/docs/20-usage/project-settings.png and b/docs/docs/20-usage/project-settings.png differ diff --git a/docs/docs/20-usage/repo-list.png b/docs/docs/20-usage/repo-list.png deleted file mode 100644 index b47380087..000000000 Binary files a/docs/docs/20-usage/repo-list.png and /dev/null differ diff --git a/docs/docs/20-usage/repo-new.png b/docs/docs/20-usage/repo-new.png new file mode 100644 index 000000000..e6136bc12 Binary files /dev/null and b/docs/docs/20-usage/repo-new.png differ diff --git a/docs/docs/20-usage/secrets-plugins-filter.png b/docs/docs/20-usage/secrets-plugins-filter.png new file mode 100644 index 000000000..460d852fb Binary files /dev/null and b/docs/docs/20-usage/secrets-plugins-filter.png differ diff --git a/docs/docs/30-administration/00-deployment/00-overview.md b/docs/docs/30-administration/00-deployment/00-overview.md deleted file mode 100644 index d17ca3501..000000000 --- a/docs/docs/30-administration/00-deployment/00-overview.md +++ /dev/null @@ -1,77 +0,0 @@ -# Deployment - -A Woodpecker deployment consists of two parts: - -- A server which is the heart of Woodpecker and ships the web interface. -- Next to one server, you can deploy any number of agents which will run the pipelines. - -Each agent is able to process one pipeline step by default. -If you have four agents installed and connected to the Woodpecker server, your system will process four workflows in parallel. - -:::tip -You can add more agents to increase the number of parallel workflows or set the agent's `WOODPECKER_MAX_WORKFLOWS=1` environment variable to increase the number of parallel workflows for that agent. -::: - -## Which version of Woodpecker should I use? - -Woodpecker is having two different kinds of releases: **stable** and **next**. - -Find more information about the different versions [here](/versions). - -## Hardware Requirements - -Below are minimal resources requirements for Woodpecker components itself: - -| Component | Memory | CPU | -| --------- | ------ | --- | -| Server | 200 MB | 1 | -| Agent | 32 MB | 1 | - -Note, that those values do not include the operating system or workload (pipelines execution) resources consumption. - -In addition you need at least some kind of database which requires additional resources depending on the selected database system. - -## Installation - -You can install Woodpecker on multiple ways: - -- Using [docker-compose](./10-docker-compose.md) with the official [container images](./10-docker-compose.md#docker-images) -- Using [Kubernetes](./20-kubernetes.md) via the Woodpecker Helm chart -- Using binaries, DEBs or RPMs you can download from [latest release](https://github.com/woodpecker-ci/woodpecker/releases/latest) - -## Authentication - -Authentication is done using OAuth and is delegated to your forge which is configured using environment variables. - -See the complete reference for all supported forges [here](../11-forges/11-overview.md). - -## Database - -By default Woodpecker uses a SQLite database which requires zero installation or configuration. See the [database settings](../30-database.md) page to further configure it or use MySQL or Postgres. - -## SSL - -Woodpecker supports SSL configuration by using Let's encrypt or by using own certificates. See the [SSL guide](../60-ssl.md). You can also put it behind a [reverse proxy](#behind-a-proxy) - -## Metrics - -A [Prometheus endpoint](../90-prometheus.md) is exposed. - -## Behind a proxy - -See the [proxy guide](../70-proxy.md) if you want to see a setup behind Apache, Nginx, Caddy or ngrok. - -In the case you need to use Woodpecker with a URL path prefix (like: ), add the root path to [`WOODPECKER_HOST`](../10-server-config.md#woodpecker_host). - -## Third-party installation methods - -:::info -These installation methods are not officially supported. If you experience issues with them, please open issues in the specific repositories. -::: - -- [Using NixOS](./30-nixos.md) via the [NixOS module](https://search.nixos.org/options?channel=unstable&size=200&sort=relevance&query=woodpecker) -- [On Alpine Edge](https://pkgs.alpinelinux.org/packages?name=woodpecker&branch=edge&repo=&arch=&maintainer=) -- [On Arch Linux](https://archlinux.org/packages/?q=woodpecker) -- [On openSUSE](https://software.opensuse.org/package/woodpecker) -- [Using YunoHost](https://apps.yunohost.org/app/woodpecker) -- [On Cloudron](https://www.cloudron.io/store/org.woodpecker_ci.cloudronapp.html) diff --git a/docs/docs/30-administration/00-getting-started.md b/docs/docs/30-administration/00-getting-started.md new file mode 100644 index 000000000..e5d573e56 --- /dev/null +++ b/docs/docs/30-administration/00-getting-started.md @@ -0,0 +1,59 @@ +# Getting started + +A Woodpecker deployment consists of two parts: + +- A server which is the heart of Woodpecker and ships the web interface. +- Next to one server, you can deploy any number of agents which will run the pipelines. + +Each agent is able to process one [workflow](../20-usage/15-terminology/index.md) by default. If you have 4 agents installed and connected to the Woodpecker server, your system will process four workflows (not pipelines) in parallel. + +:::tip +You can add more agents to increase the number of parallel workflows or set the agent's `WOODPECKER_MAX_WORKFLOWS=1` environment variable to increase the number of parallel workflows per agent. +::: + +## Which version of Woodpecker should I use? + +Woodpecker is having two different kinds of releases: **stable** and **next**. + +Find more information about the different versions [here](/versions). + +## Hardware Requirements + +Below are minimal resources requirements for Woodpecker components itself: + +| Component | Memory | CPU | +| --------- | ------ | --- | +| Server | 200 MB | 1 | +| Agent | 32 MB | 1 | + +Note, that those values do not include the operating system or workload (pipelines execution) resource consumption. + +In addition you need at least some kind of database which requires additional resources depending on the selected database system. + +## Installation + +You can install Woodpecker on multiple ways. If you are not sure which one to choose, we recommend using the [docker compose](./05-deployment-methods/10-docker-compose.md) method for the beginning: + +- Using [docker compose](./05-deployment-methods/10-docker-compose.md) with the official [container images](./05-deployment-methods/10-docker-compose.md#docker-images) +- Using [Kubernetes](./05-deployment-methods/20-kubernetes.md) via the Woodpecker Helm chart +- Using binaries, DEBs or RPMs you can download from [latest release](https://github.com/woodpecker-ci/woodpecker/releases/latest) +- Or using a [third-party installation method](./05-deployment-methods/30-third-party.md) + +## Database + +By default Woodpecker uses a SQLite database which requires zero installation or configuration. See the [database settings](./10-database.md) page if you want to use a different database system like MySQL or PostgreSQL. + +## Forge + +What would be a CI/CD system without any code? By connecting Woodpecker to your [forge](../20-usage/15-terminology/index.md) like GitHub or Gitea you can start running pipelines on events like pushes or pull requests. Woodpecker will also use your forge for authentication and to report back the status of your pipelines. See the [forge settings](./11-forges/11-overview.md) to connect it to Woodpecker. + +## Configuration + +Check the [server configuration](./10-server-config.md) and [agent configuration](./15-agent-config.md) pages to see if you need to adjust any additional parts and after that you should be ready to start with [your first pipeline](../20-usage/10-intro.md). + +## Agent + +The agent is the worker which executes the [workflows](../20-usage/15-terminology/index.md). +Woodpecker agents can execute work using a [backend](../20-usage/15-terminology/index.md) like [docker](./22-backends/10-docker.md) or [kubernetes](./22-backends/40-kubernetes.md). +By default if you choose to deploy an agent using [docker compose](./05-deployment-methods/10-docker-compose.md) the agent simply use docker for the backend as well. +So nothing to worry about here. If you still prefer to adjust the agent to your needs, check the [agent configuration](./15-agent-config.md) page. diff --git a/docs/docs/30-administration/04-image-variants.md b/docs/docs/30-administration/04-image-variants.md new file mode 100644 index 000000000..2d946a9df --- /dev/null +++ b/docs/docs/30-administration/04-image-variants.md @@ -0,0 +1,30 @@ +# Image variants + +:::info +The `latest` tag has been deprecated as of v3.0 and will be completely removed in the future. +This was done to prevent accidental major version upgrades. +::: + +- `vX.Y.Z`: SemVer tags for specific releases, no entrypoint shell (scratch image) + - `vX.Y` + - `vX` +- `vX.Y.Z-alpine`: SemVer tags for specific releases, based on Alpine, rootless for Server and CLI (as of v3.0). + - `vX.Y-alpine` + - `vX-alpine` +- `next`: Built from the `main` branch +- `pull_`: Images built from Pull Request branches. + +## Image registries + +Images are pushed to DockerHub and Quay. + +[woodpecker-server (DockerHub)](https://hub.docker.com/repository/docker/woodpeckerci/woodpecker-server) +[woodpecker-server (Quay)](https://quay.io/repository/woodpeckerci/woodpecker-server) + +[woodpecker-agent (DockerHub)](https://hub.docker.com/repository/docker/woodpeckerci/woodpecker-agent) +[woodpecker-agent (Quay)](https://quay.io/repository/woodpeckerci/woodpecker-agent) + +[woodpecker-cli (DockerHub)](https://hub.docker.com/repository/docker/woodpeckerci/woodpecker-cli) +[woodpecker-cli (Quay)](https://quay.io/repository/woodpeckerci/woodpecker-cli) + +[woodpecker-autoscaler (DockerHub)](https://hub.docker.com/repository/docker/woodpeckerci/autoscaler) diff --git a/docs/docs/30-administration/00-deployment/10-docker-compose.md b/docs/docs/30-administration/05-deployment-methods/10-docker-compose.md similarity index 72% rename from docs/docs/30-administration/00-deployment/10-docker-compose.md rename to docs/docs/30-administration/05-deployment-methods/10-docker-compose.md index a9c2bb6ab..a9d59c9d7 100644 --- a/docs/docs/30-administration/00-deployment/10-docker-compose.md +++ b/docs/docs/30-administration/05-deployment-methods/10-docker-compose.md @@ -1,12 +1,10 @@ -# docker-compose +# docker compose -The below [docker-compose](https://docs.docker.com/compose/) configuration can be used to start a Woodpecker server with a single agent. +The below [docker compose](https://docs.docker.com/compose/) configuration can be used to start a Woodpecker server with a single agent. -It relies on a number of environment variables that you must set before running `docker-compose up`. The variables are described below. +It relies on a number of environment variables that you must set before running `docker compose up`. The variables are described below. ```yaml title="docker-compose.yaml" -version: '3' - services: woodpecker-server: image: woodpeckerci/woodpecker-server:latest @@ -43,8 +41,6 @@ volumes: Woodpecker needs to know its own address. You must therefore provide the public address of it in `://` format. Please omit trailing slashes: ```diff title="docker-compose.yaml" - version: '3' - services: woodpecker-server: [...] @@ -53,11 +49,10 @@ Woodpecker needs to know its own address. You must therefore provide the public + - WOODPECKER_HOST=${WOODPECKER_HOST} ``` -Woodpecker can also have its port's configured. It uses a separate port for gRPC and for HTTP. The agent performs gRPC calls and connects to the gRPC port. +Woodpecker can also have its ports configured. It uses a separate port for gRPC and for HTTP. The agent performs gRPC calls and connects to the gRPC port. They can be configured with `*_ADDR` variables: ```diff title="docker-compose.yaml" - version: '3' services: woodpecker-server: [...] @@ -67,10 +62,9 @@ They can be configured with `*_ADDR` variables: + - WOODPECKER_SERVER_ADDR=${WOODPECKER_HTTP_ADDR} ``` -Reverse proxying can also be [configured for gRPC](../70-proxy.md#caddy). If the agents are connecting over the internet, it should also be SSL encrypted. The agent then needs to be configured to be secure: +Reverse proxying can also be [configured for gRPC](../40-advanced/10-proxy.md#caddy). If the agents are connecting over the internet, it should also be SSL encrypted. The agent then needs to be configured to be secure: ```diff title="docker-compose.yaml" - version: '3' services: woodpecker-server: [...] @@ -83,8 +77,6 @@ Reverse proxying can also be [configured for gRPC](../70-proxy.md#caddy). If the As agents run pipeline steps as docker containers they require access to the host machine's Docker daemon: ```diff title="docker-compose.yaml" - version: '3' - services: [...] woodpecker-agent: @@ -96,8 +88,6 @@ As agents run pipeline steps as docker containers they require access to the hos Agents require the server address for agent-to-server communication. The agent connects to the server's gRPC port: ```diff title="docker-compose.yaml" - version: '3' - services: woodpecker-agent: [...] @@ -108,8 +98,6 @@ Agents require the server address for agent-to-server communication. The agent c The server and agents use a shared secret to authenticate communication. This should be a random string of your choosing and should be kept private. You can generate such string with `openssl rand -hex 32`: ```diff title="docker-compose.yaml" - version: '3' - services: woodpecker-server: [...] @@ -127,21 +115,26 @@ The server and agents use a shared secret to authenticate communication. This sh Image variants: -- The `latest` image is the latest stable release - The `vX.X.X` images are stable releases -- The `vX.X` images are based on the current release branch (e.g. `release/v1.0`) and can be used to get bugfixes asap +- The `vX.X` images are based on the current release branch (e.g. `release/v1.0`) and can be used to get bug fixes asap +- The `vX` same as `vX.X` variant but also includes feature releases - The `next` images are based on the current `main` branch +:::note +The `latest` tag is not available on purpose (and has been dropped with the 3.x release) to prevent accidental major version upgrades. +Hence, users are forced to specify a fixed or rolling tag, omitting the tag identifier (which equals to pulling `latest` implicitly) won't work. +::: + ```bash # server -docker pull woodpeckerci/woodpecker-server:latest -docker pull woodpeckerci/woodpecker-server:latest-alpine +docker pull woodpeckerci/woodpecker-server:v3 +docker pull woodpeckerci/woodpecker-server:v3-alpine # agent -docker pull woodpeckerci/woodpecker-agent:latest -docker pull woodpeckerci/woodpecker-agent:latest-alpine +docker pull woodpeckerci/woodpecker-agent:v3 +docker pull woodpeckerci/woodpecker-agent:v3-alpine # cli -docker pull woodpeckerci/woodpecker-cli:latest -docker pull woodpeckerci/woodpecker-cli:latest-alpine +docker pull woodpeckerci/woodpecker-cli:v3 +docker pull woodpeckerci/woodpecker-cli:v3-alpine ``` diff --git a/docs/docs/30-administration/05-deployment-methods/20-kubernetes.md b/docs/docs/30-administration/05-deployment-methods/20-kubernetes.md new file mode 100644 index 000000000..a3a92e043 --- /dev/null +++ b/docs/docs/30-administration/05-deployment-methods/20-kubernetes.md @@ -0,0 +1,50 @@ +# Kubernetes + +We recommended to deploy Woodpecker using the [Woodpecker helm chart](https://github.com/woodpecker-ci/helm). +Have a look at the [`values.yaml`](https://github.com/woodpecker-ci/helm/blob/main/charts/woodpecker/values.yaml) config files for all available settings. + +The chart contains two sub-charts, `server` and `agent` which are automatically configured as needed. +The chart started off with two independent charts but was merged into one to simplify the deployment at start of 2023. + +A couple of backend-specific config env vars exists which are described in the [kubernetes backend docs](../22-backends/40-kubernetes.md). + +## Metrics + +Please see [Prometheus](../40-advanced/90-prometheus.md) for general information on configuration and usage. + +For Kubernetes, you must set the following values when deploying via Helm chart to enable in-cluster metrics gathering: + +```yaml +metrics: + enabled: true + port: 9001 +``` + +This activates the `/metrics` endpoint on port `9001` without authentication. This port is not exposed externally by default. Use the instructions at [Prometheus](../40-advanced/90-prometheus.md) if you want to enable authenticated external access to metrics. + +To enable Prometheus pod monitoring discovery, you must also make the following settings: + + + +```yaml +prometheus: + podmonitor: + enabled: true + interval: 60s + labels: {} +``` + + + +### Troubleshooting Metrics + +If you are not receiving metrics despite the steps above, ensure that in your Prometheus configuration either your namespace is explicitly configured in `podMonitorNamespaceSelector` or the selectors are disabled. + +```yaml +# Search all available namespaces +podMonitorNamespaceSelector: + matchLabels: {} +# Enable all available pod monitors +podMonitorSelector: + matchLabels: {} +``` diff --git a/docs/docs/30-administration/05-deployment-methods/30-third-party.md b/docs/docs/30-administration/05-deployment-methods/30-third-party.md new file mode 100644 index 000000000..8eabded39 --- /dev/null +++ b/docs/docs/30-administration/05-deployment-methods/30-third-party.md @@ -0,0 +1,12 @@ +# Distribution packages + +:::info +Woodpecker itself is not responsible for creating these packages. Please reach out to the people responsible for packaging Woodpecker for the individual distributions. +::: + +- [NixOS](./40-nixos.md) via the [NixOS module](https://search.nixos.org/options?channel=unstable&size=200&sort=relevance&query=woodpecker) +- [Alpine (Edge)](https://pkgs.alpinelinux.org/packages?name=woodpecker&branch=edge&repo=&arch=&maintainer=) +- [Arch Linux](https://archlinux.org/packages/?q=woodpecker) +- [openSUSE](https://software.opensuse.org/package/woodpecker) +- [YunoHost](https://apps.yunohost.org/app/woodpecker) +- [Cloudron](https://www.cloudron.io/store/org.woodpecker_ci.cloudronapp.html) diff --git a/docs/versioned_docs/version-2.4/30-administration/00-deployment/30-nixos.md b/docs/docs/30-administration/05-deployment-methods/40-nixos.md similarity index 92% rename from docs/versioned_docs/version-2.4/30-administration/00-deployment/30-nixos.md rename to docs/docs/30-administration/05-deployment-methods/40-nixos.md index 148fbd581..063016985 100644 --- a/docs/versioned_docs/version-2.4/30-administration/00-deployment/30-nixos.md +++ b/docs/docs/30-administration/05-deployment-methods/40-nixos.md @@ -1,7 +1,7 @@ # NixOS :::info -Note that this module is not maintained by the woodpecker-developers. +Note that this module is not maintained by the Woodpecker developers. If you experience issues please open a bug report in the [nixpkgs repo](https://github.com/NixOS/nixpkgs/issues/new/choose) where the module is maintained. ::: @@ -10,6 +10,8 @@ In practice, the settings are specified declaratively in the NixOS configuration ## General Configuration + + ```nix { config , ... @@ -85,4 +87,4 @@ All configuration options can be found via [NixOS Search](https://search.nixos.o ## Tips and tricks -There are some resources on how to utilize Woodpecker more effectively with NixOS on the [Awesome Woodpecker](../../92-awesome.md) page, like using the runners nix-store in the pipeline +There are some resources on how to utilize Woodpecker more effectively with NixOS on the [Awesome Woodpecker](/awesome) page, like using the runners nix-store in the pipeline. diff --git a/docs/docs/30-administration/05-deployment-methods/_category_.yaml b/docs/docs/30-administration/05-deployment-methods/_category_.yaml new file mode 100644 index 000000000..3907838b0 --- /dev/null +++ b/docs/docs/30-administration/05-deployment-methods/_category_.yaml @@ -0,0 +1,3 @@ +label: 'Deployment methods' +collapsible: true +collapsed: true diff --git a/docs/versioned_docs/version-2.5/30-administration/30-database.md b/docs/docs/30-administration/10-database.md similarity index 99% rename from docs/versioned_docs/version-2.5/30-administration/30-database.md rename to docs/docs/30-administration/10-database.md index e3e33ba7d..b1b5aa688 100644 --- a/docs/versioned_docs/version-2.5/30-administration/30-database.md +++ b/docs/docs/30-administration/10-database.md @@ -7,8 +7,6 @@ The default database engine of Woodpecker is an embedded SQLite database which r By default Woodpecker uses a SQLite database stored under `/var/lib/woodpecker/`. If using containers, you can mount a [data volume](https://docs.docker.com/storage/volumes/#create-and-manage-volumes) to persist the SQLite database. ```diff title="docker-compose.yaml" - version: '3' - services: woodpecker-server: [...] diff --git a/docs/docs/30-administration/10-server-config.md b/docs/docs/30-administration/10-server-config.md index 5be1feed7..a308123eb 100644 --- a/docs/docs/30-administration/10-server-config.md +++ b/docs/docs/30-administration/10-server-config.md @@ -26,14 +26,14 @@ You can **also restrict** registration, by keep registration closed and: ```ini WOODPECKER_OPEN=false -WOODPECKER_ADMIN=johnsmith,janedoe +WOODPECKER_ADMIN=john.smith,jane_doe ``` ### Only allow registration of users, who are members of approved organizations ```ini WOODPECKER_OPEN=true -WOODPECKER_ORGS=dolores,dogpatch +WOODPECKER_ORGS=dolores,dog-patch ``` ## Administrators @@ -41,7 +41,7 @@ WOODPECKER_ORGS=dolores,dogpatch Administrators should also be enumerated in your configuration. ```ini -WOODPECKER_ADMIN=johnsmith,janedoe +WOODPECKER_ADMIN=john.smith,jane_doe ``` ## Filtering repositories @@ -51,7 +51,21 @@ Woodpecker operates with the user's OAuth permission. Due to the coarse permissi Use the `WOODPECKER_REPO_OWNERS` variable to filter which GitHub user's repos should be synced only. You typically want to put here your company's GitHub name. ```ini -WOODPECKER_REPO_OWNERS=mycompany,mycompanyossgithubuser +WOODPECKER_REPO_OWNERS=my_company,my_company_oss_github_user +``` + +## Disallow normal users to create agents + +By default, users can create new agents for their repos they have admin access to. +If an instance admin doesn't want this feature enabled, they can disable the API and hide the Web UI elements. + +:::note +You should set this option if you have, for example, +global secrets and don't trust your users to create a rogue agent and pipeline for secret extraction. +::: + +```ini +WOODPECKER_DISABLE_USER_AGENT_REGISTRATION=true ``` ## Global registry setting @@ -63,17 +77,15 @@ Point it to your server's docker config. WOODPECKER_DOCKER_CONFIG=/root/.docker/config.json ``` -## Handling sensitive data in docker-compose and docker-swarm +## Handling sensitive data in **docker compose** and **docker swarm** -To handle sensitive data in docker-compose or docker-swarm configurations there are several options: +To handle sensitive data in `docker compose` or `docker swarm` configurations there are several options: -For docker-compose you can use a `.env` file next to your compose configuration to store the secrets outside of the compose file. While this separates configuration from secrets it is still not very secure. +For docker compose you can use a `.env` file next to your compose configuration to store the secrets outside of the compose file. While this separates configuration from secrets it is still not very secure. Alternatively use docker-secrets. As it may be difficult to use docker secrets for environment variables Woodpecker allows to read sensible data from files by providing a `*_FILE` option of all sensible configuration variables. Woodpecker will try to read the value directly from this file. Keep in mind that when the original environment variable gets specified at the same time it will override the value read from the file. ```diff title="docker-compose.yaml" - version: '3' - services: woodpecker-server: [...] @@ -133,7 +145,7 @@ The examples below show how to place a banner message in the top navigation bar ### `woodpecker.js` ```javascript -// place/copy a minified version of jQuery or ZeptoJS here ... +// place/copy a minified version of your preferred lightweight JavaScript library here ... !(function () { 'use strict'; function e() {} /*...*/ @@ -161,17 +173,35 @@ Configures the logging level. Possible values are `trace`, `debug`, `info`, `war Output destination for logs. 'stdout' and 'stderr' can be used as special keywords. -### `WOODPECKER_LOG_XORM` +### `WOODPECKER_DATABASE_LOG` > Default: `false` -Enable XORM logs. +Enable logging in database engine (currently xorm). -### `WOODPECKER_LOG_XORM_SQL` +### `WOODPECKER_DATABASE_LOG_SQL` > Default: `false` -Enable XORM SQL command logs. +Enable logging of sql commands. + +### `WOODPECKER_DATABASE_MAX_CONNECTIONS` + +> Default: `100` + +Max database connections xorm is allowed create. + +### `WOODPECKER_DATABASE_IDLE_CONNECTIONS` + +> Default: `2` + +Amount of database connections xorm will hold open. + +### `WOODPECKER_DATABASE_CONNECTION_TIMEOUT` + +> Default: `3 Seconds` + +Time an active database connection is allowed to stay open. ### `WOODPECKER_DEBUG_PRETTY` @@ -197,14 +227,6 @@ Examples: - `WOODPECKER_HOST=http://example.org/woodpecker` - `WOODPECKER_HOST=http://example.org:1234/woodpecker` -### `WOODPECKER_WEBHOOK_HOST` - -> Default: value from `WOODPECKER_HOST` config env - -Server fully qualified URL of the Webhook-facing hostname and path prefix. - -Example: `WOODPECKER_WEBHOOK_HOST=http://woodpecker-server.cicd.svc.cluster.local:8000` - ### `WOODPECKER_SERVER_ADDR` > Default: `:8000` @@ -253,12 +275,6 @@ The file must be UTF-8 encoded, to ensure all special characters are preserved. Example: `WOODPECKER_CUSTOM_JS_FILE=/usr/local/www/woodpecker.js` -### `WOODPECKER_LETS_ENCRYPT` - -> Default: `false` - -Automatically generates an SSL certificate using Let's Encrypt, and configures the server to accept HTTPS requests. - ### `WOODPECKER_GRPC_ADDR` > Default: `:9000` @@ -305,7 +321,7 @@ Example: `org1,org2` > Default: empty -Comma-separated list of syncable repo owners. ??? +Repositories by those owners will be allowed to be used in woodpecker. Example: `user1,user2` @@ -327,11 +343,21 @@ Always use authentication to clone repositories even if they are public. Needed List of event names that will be canceled when a new pipeline for the same context (tag, branch) is created. -### `WOODPECKER_DEFAULT_CLONE_IMAGE` +### `WOODPECKER_DEFAULT_CLONE_PLUGIN` > Default is defined in [shared/constant/constant.go](https://github.com/woodpecker-ci/woodpecker/blob/main/shared/constant/constant.go) -The default docker image to be used when cloning the repo +The default docker image to be used when cloning the repo. + +It is also added to the trusted clone plugin list. + +### `WOODPECKER_DEFAULT_WORKFLOW_LABELS` + +> By default run workflows on any agent if no label conditions are set in workflow definition. + +You can specify default label/platform conditions that will be used for agent selection for workflows that does not have labels conditions set. + +Example: `platform=linux/amd64,backend=docker` ### `WOODPECKER_DEFAULT_PIPELINE_TIMEOUT` @@ -354,11 +380,20 @@ Context: when someone does log into Woodpecker, a temporary session token is cre As long as the session is valid (until it expires or log-out), a user can log into Woodpecker, without re-authentication. -### `WOODPECKER_ESCALATE` +### `WOODPECKER_PLUGINS_PRIVILEGED` + +Docker images to run in privileged mode. Only change if you are sure what you do! + +You should specify the tag of your images too, as this enforces exact matches. + +### WOODPECKER_PLUGINS_TRUSTED_CLONE > Defaults are defined in [shared/constant/constant.go](https://github.com/woodpecker-ci/woodpecker/blob/main/shared/constant/constant.go) -Docker images to run in privileged mode. Only change if you are sure what you do! +Plugins which are trusted to handle the Git credential info in clone steps. +If a clone step use an image not in this list, Git credentials will not be injected and users have to use other methods (e.g. secrets) to clone non-public repos. + +You should specify the tag of your images too, as this enforces exact matches. + ```bash docker image rm $(docker images --filter "dangling=true" -q --no-trunc) ``` @@ -42,6 +59,12 @@ docker image rm $(docker images --filter "dangling=true" -q --no-trunc) docker volume rm $(docker volume ls --filter name=^wp_* --filter dangling=true -q) ``` +## Tips and tricks + +### Podman + +There is no official support for Podman, but one can try to set the environment variable `DOCKER_HOST` to point to the Podman socket. It might work. See also the [Blog posts](https://woodpecker-ci.org/blog). + ## Configuration ### `WOODPECKER_BACKEND_DOCKER_NETWORK` @@ -62,3 +85,41 @@ Enable IPv6 for the networks used by pipeline containers (steps). Make sure you List of default volumes separated by comma to be mounted to all pipeline containers (steps). For example to use custom CA certificates installed on host and host timezone use `/etc/ssl/certs:/etc/ssl/certs:ro,/etc/timezone:/etc/timezone`. + +### `WOODPECKER_BACKEND_DOCKER_LIMIT_MEM_SWAP` + +> Default: `0` + +The maximum amount of memory a single pipeline container is allowed to swap to disk, configured in bytes. There is no limit if `0`. + +### `WOODPECKER_BACKEND_DOCKER_LIMIT_MEM` + +> Default: `0` + +The maximum amount of memory a single pipeline container can use, configured in bytes. There is no limit if `0`. + +### `WOODPECKER_BACKEND_DOCKER_LIMIT_SHM_SIZE` + +> Default: `0` + +The maximum amount of memory of `/dev/shm` allowed in bytes. There is no limit if `0`. + +### `WOODPECKER_BACKEND_DOCKER_LIMIT_CPU_QUOTA` + +> Default: `0` + +The number of microseconds per CPU period that the container is limited to before throttled. There is no limit if `0`. + +### `WOODPECKER_BACKEND_DOCKER_LIMIT_CPU_SHARES` + +> Default: `0` + +The relative weight vs. other containers. + +### `WOODPECKER_BACKEND_DOCKER_LIMIT_CPU_SET` + +> Default: empty + +Comma-separated list to limit the specific CPUs or cores a pipeline container can use. + +Example: `WOODPECKER_BACKEND_DOCKER_LIMIT_CPU_SET=1,2` diff --git a/docs/docs/30-administration/22-backends/40-kubernetes.md b/docs/docs/30-administration/22-backends/40-kubernetes.md index 48f4a622d..4247774ab 100644 --- a/docs/docs/30-administration/22-backends/40-kubernetes.md +++ b/docs/docs/30-administration/22-backends/40-kubernetes.md @@ -8,11 +8,11 @@ The Kubernetes backend executes steps inside standalone Pods. A temporary PVC is ## Images from private registries -In order to pull private container images defined in your pipeline YAML you must provide [registry credentials in Kubernetes Secret](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/). -As the Secret is Agent-wide, it has to be placed in namespace defined by `WOODPECKER_BACKEND_K8S_NAMESPACE`. -Besides, you need to provide the Secret name to Agent via `WOODPECKER_BACKEND_K8S_PULL_SECRET_NAMES`. +In addition to [registries specified in the UI](../../20-usage/41-registries.md), you may provide [registry credentials in Kubernetes Secrets](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/) to pull private container images defined in your pipeline YAML. -## Job specific configuration +Place these Secrets in namespace defined by `WOODPECKER_BACKEND_K8S_NAMESPACE` and provide the Secret names to Agents via `WOODPECKER_BACKEND_K8S_PULL_SECRET_NAMES`. + +## Step specific configuration ### Resources @@ -50,9 +50,24 @@ See the [Kubernetes documentation](https://kubernetes.io/docs/concepts/container `serviceAccountName` specifies the name of the ServiceAccount which the Pod will mount. This service account must be created externally. See the [Kubernetes documentation](https://kubernetes.io/docs/concepts/security/service-accounts/) for more information on using service accounts. +```yaml +steps: + - name: 'My kubernetes step' + image: alpine + commands: + - echo "Hello world" + backend_options: + kubernetes: + # Use the service account `default` in the current namespace. + # This usually the same as wherever woodpecker is deployed. + serviceAccountName: default +``` + +To give steps access to the Kubernetes API via service account, take a look at [RBAC Authorization](https://kubernetes.io/docs/reference/access-authn-authz/rbac/) + ### Node selector -`nodeSelector` specifies the labels which are used to select the node on which the job will be executed. +`nodeSelector` specifies the labels which are used to select the node on which the step will be executed. Labels defined here will be appended to a list which already contains `"kubernetes.io/arch"`. By default `"kubernetes.io/arch"` is inferred from the agents' platform. One can override it by setting that label in the `nodeSelector` section of the `backend_options`. @@ -107,7 +122,7 @@ steps: limits: memory: 256Mi nodeSelector: - beta.kubernetes.io/instance-type: p3.8xlarge + beta.kubernetes.io/instance-type: Standard_D2_v3 tolerations: - key: 'key1' operator: 'Equal' @@ -119,7 +134,19 @@ steps: ### Volumes To mount volumes a PersistentVolume (PV) and PersistentVolumeClaim (PVC) are needed on the cluster which can be referenced in steps via the `volumes` option. -Assuming a PVC named `woodpecker-cache` exists, it can be referenced as follows in a step: + +Persistent volumes must be created manually. Use the Kubernetes [Persistent Volumes](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) documentation as a reference. + +_If your PVC is not highly available or NFS-based, you may also need to integrate affinity settings to ensure that your steps are executed on the correct node._ + +NOTE: If you plan to use this volume in more than one workflow concurrently, make sure you have configured the PVC in `RWX` mode. Keep in mind that this feature must be supported by the used CSI driver: + +```yaml +accessModes: + - ReadWriteMany +``` + +Assuming a PVC named `woodpecker-cache` exists, it can be referenced as follows in a plugin step: ```yaml steps: @@ -133,6 +160,19 @@ steps: [...] ``` +Or as follows when using a normal image: + +```yaml +steps: + - name: "Edit cache" + image: alpine:latest + volumes: + - woodpecker-cache:/woodpecker/src/cache + commands: + - echo "Hello World" > /woodpecker/src/cache/output.txt + [...] +``` + ### Security context Use the following configuration to set the [Security Context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) for the Pod/container running a given pipeline step: @@ -156,6 +196,8 @@ Note that the `backend_options.kubernetes.securityContext` object allows you to By default, the properties will be set at the Pod level. Properties that are only supported on the container level will be set there instead. So, the configuration shown above will result in something like the following Pod spec: + + ```yaml kind: Pod spec: @@ -170,7 +212,9 @@ spec: [...] ``` -You can also restrict a container's syscalls with [seccomp](https://kubernetes.io/docs/tutorials/security/seccomp/) profile + + +You can also restrict a syscalls of containers with [seccomp](https://kubernetes.io/docs/tutorials/security/seccomp/) profile. ```yaml backend_options: @@ -193,7 +237,7 @@ backend_options: ``` :::note -AppArmor syntax follows [KEP-24](https://github.com/kubernetes/enhancements/blob/fddcbb9cbf3df39ded03bad71228265ac6e5215f/keps/sig-node/24-apparmor/README.md). +The feature requires Kubernetes v1.30 or above. ::: ### Annotations and labels @@ -230,7 +274,8 @@ See [this issue](https://github.com/woodpecker-ci/woodpecker/issues/2510) for mo ### `KUBERNETES_SERVICE_HOST` environment variable -Like the below env vars used for configuration, this can be set in the environment fonfiguration of the agent. It configures the address of the Kubernetes API server to connect to. +Like the below env vars used for configuration, this can be set in the environment for configuration of the agent. +It configures the address of the Kubernetes API server to connect to. If running the agent within Kubernetes, this will already be set and you don't have to add it manually. @@ -292,7 +337,7 @@ Determines if Pod annotations can be defined from a step's backend options. Additional node selector to apply to worker pods. Must be a YAML object, e.g. `{"topology.kubernetes.io/region":"eu-central-1"}`. -### `WOODPECKER_BACKEND_K8S_SECCTX_NONROOT` +### `WOODPECKER_BACKEND_K8S_SECCTX_NONROOT` > Default: `false` diff --git a/docs/docs/30-administration/22-backends/50-custom-backends.md b/docs/docs/30-administration/22-backends/50-custom-backends.md index 3c771c4ef..c95956559 100644 --- a/docs/docs/30-administration/22-backends/50-custom-backends.md +++ b/docs/docs/30-administration/22-backends/50-custom-backends.md @@ -1,6 +1,6 @@ # Custom backends -If none of our backends fits your usecases, you can write your own. +If none of our backends fits your usecase, you can write your own. Therefore, implement the interface `"go.woodpecker-ci.org/woodpecker/woodpecker/v2/pipeline/backend/types".Backend` and build a custom agent using your backend with this `main.go`: @@ -9,8 +9,8 @@ build a custom agent using your backend with this `main.go`: package main import ( - "go.woodpecker-ci.org/woodpecker/v2/cmd/agent/core" - backendTypes "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" + "go.woodpecker-ci.org/woodpecker/v3/cmd/agent/core" + backendTypes "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" ) func main() { diff --git a/docs/versioned_docs/version-2.4/30-administration/70-proxy.md b/docs/docs/30-administration/40-advanced/10-proxy.md similarity index 93% rename from docs/versioned_docs/version-2.4/30-administration/70-proxy.md rename to docs/docs/30-administration/40-advanced/10-proxy.md index 4aae2fbe1..8771eed44 100644 --- a/docs/versioned_docs/version-2.4/30-administration/70-proxy.md +++ b/docs/docs/30-administration/40-advanced/10-proxy.md @@ -4,6 +4,8 @@ This guide provides a brief overview for installing Woodpecker server behind the Apache2 web-server. This is an example configuration: + + ```apacheconf ProxyPreserveHost On @@ -31,7 +33,7 @@ You must configure Apache to set `X-Forwarded-Proto` when using https. ## Nginx -This guide provides a basic overview for installing Woodpecker server behind the Nginx web-server. For more advanced configuration options please consult the official Nginx [documentation](https://www.nginx.com/resources/admin-guide/). +This guide provides a basic overview for installing Woodpecker server behind the Nginx web-server. For more advanced configuration options please consult the official Nginx [documentation](https://docs.nginx.com/nginx/admin-guide). Example configuration: @@ -87,13 +89,13 @@ woodpecker.example.com { } # expose gRPC -woodpeckeragent.example.com { +woodpecker-agent.example.com { reverse_proxy h2c://woodpecker-server:9000 } ``` :::note -Above configuration shows how to create reverse-proxies for web and agent communication. If your agent uses SSL do not forget to enable [`WOODPECKER_GRPC_SECURE`](./15-agent-config.md#woodpecker_grpc_secure). +Above configuration shows how to create reverse-proxies for web and agent communication. If your agent uses SSL do not forget to enable [`WOODPECKER_GRPC_SECURE`](../15-agent-config.md#woodpecker_grpc_secure). ::: ## Tunnelmole @@ -132,9 +134,9 @@ Set `WOODPECKER_HOST` to the ngrok URL (usually xxx.ngrok.io) and start the serv To install the Woodpecker server behind a [Traefik](https://traefik.io/) load balancer, you must expose both the `http` and the `gRPC` ports. Here is a comprehensive example, considering you are running Traefik with docker swarm and want to do TLS termination and automatic redirection from http to https. -```yaml -version: '3.8' + +```yaml services: server: image: woodpeckerci/woodpecker-server:latest @@ -155,13 +157,13 @@ services: # web server - traefik.http.services.woodpecker-service.loadbalancer.server.port=8000 - - traefik.http.routers.woodpecker-secure.rule=Host(`cd.yourdomain.com`) + - traefik.http.routers.woodpecker-secure.rule=Host(`cd.your-domain.com`) - traefik.http.routers.woodpecker-secure.tls=true - traefik.http.routers.woodpecker-secure.tls.certresolver=letsencrypt - - traefik.http.routers.woodpecker-secure.entrypoints=websecure + - traefik.http.routers.woodpecker-secure.entrypoints=web-secure - traefik.http.routers.woodpecker-secure.service=woodpecker-service - - traefik.http.routers.woodpecker.rule=Host(`cd.yourdomain.com`) + - traefik.http.routers.woodpecker.rule=Host(`cd.your-domain.com`) - traefik.http.routers.woodpecker.entrypoints=web - traefik.http.routers.woodpecker.service=woodpecker-service @@ -173,13 +175,13 @@ services: - traefik.http.services.woodpecker-grpc.loadbalancer.server.port=9000 - traefik.http.services.woodpecker-grpc.loadbalancer.server.scheme=h2c - - traefik.http.routers.woodpecker-grpc-secure.rule=Host(`woodpecker-grpc.yourdomain.com`) + - traefik.http.routers.woodpecker-grpc-secure.rule=Host(`woodpecker-grpc.your-domain.com`) - traefik.http.routers.woodpecker-grpc-secure.tls=true - traefik.http.routers.woodpecker-grpc-secure.tls.certresolver=letsencrypt - - traefik.http.routers.woodpecker-grpc-secure.entrypoints=websecure + - traefik.http.routers.woodpecker-grpc-secure.entrypoints=web-secure - traefik.http.routers.woodpecker-grpc-secure.service=woodpecker-grpc - - traefik.http.routers.woodpecker-grpc.rule=Host(`woodpecker-grpc.yourdomain.com`) + - traefik.http.routers.woodpecker-grpc.rule=Host(`woodpecker-grpc.your-domain.com`) - traefik.http.routers.woodpecker-grpc.entrypoints=web - traefik.http.routers.woodpecker-grpc.service=woodpecker-grpc diff --git a/docs/versioned_docs/version-2.5/30-administration/100-external-configuration-api.md b/docs/docs/30-administration/40-advanced/100-external-configuration-api.md similarity index 83% rename from docs/versioned_docs/version-2.5/30-administration/100-external-configuration-api.md rename to docs/docs/30-administration/40-advanced/100-external-configuration-api.md index f951b1478..a24abd3bd 100644 --- a/docs/versioned_docs/version-2.5/30-administration/100-external-configuration-api.md +++ b/docs/docs/30-administration/40-advanced/100-external-configuration-api.md @@ -3,7 +3,7 @@ To provide additional management and preprocessing capabilities for pipeline configurations Woodpecker supports an HTTP API which can be enabled to call an external config service. Before the run or restart of any pipeline Woodpecker will make a POST request to an external HTTP API sending the current repository, build information and all current config files retrieved from the repository. The external API can then send back new pipeline configurations that will be used immediately or respond with `HTTP 204` to tell the system to use the existing configuration. -Every request sent by Woodpecker is signed using a [http-signature](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures) by a private key (ed25519) generated on the first start of the Woodpecker server. You can get the public key for the verification of the http-signature from `http(s)://your-woodpecker-server/api/signature/public-key`. +Every request sent by Woodpecker is signed using a [http-signature](https://datatracker.ietf.org/doc/html/rfc9421) by a private key (ed25519) generated on the first start of the Woodpecker server. You can get the public key for the verification of the http-signature from `http(s)://your-woodpecker-server/api/signature/public-key`. A simplistic example configuration service can be found here: [https://github.com/woodpecker-ci/example-config-service](https://github.com/woodpecker-ci/example-config-service) @@ -26,7 +26,7 @@ WOODPECKER_CONFIG_SERVICE_ENDPOINT=https://example.com/ciconfig "uid": "", "user_id": 0, "namespace": "", - "name": "woodpecker-testpipe", + "name": "woodpecker-test-pipe", "slug": "", "scm": "git", "git_http_url": "", @@ -54,7 +54,7 @@ WOODPECKER_CONFIG_SERVICE_ENDPOINT=https://example.com/ciconfig "author_avatar": "https://myforge.com/avatars/d6b3f7787a685fcdf2a44e2c685c7e03", "author_email": "my@email.com", "branch": "main", - "changed_files": ["somefilename.txt"], + "changed_files": ["some-file-name.txt"], "commit": "2fff90f8d288a4640e90f05049fe30e61a14fd50", "created_at": 0, "deploy_to": "", @@ -81,12 +81,11 @@ WOODPECKER_CONFIG_SERVICE_ENDPOINT=https://example.com/ciconfig "updated_at": 0, "verified": false }, - "configs": [ - { - "name": ".woodpecker.yaml", - "data": "steps:\n - name: backend\n image: alpine\n commands:\n - echo \"Hello there from Repo (.woodpecker.yaml)\"\n" - } - ] + "netrc": { + "machine": "https://example.com", + "login": "user", + "password": "password" + } } ``` diff --git a/docs/versioned_docs/version-2.5/30-administration/60-ssl.md b/docs/docs/30-administration/40-advanced/20-ssl.md similarity index 51% rename from docs/versioned_docs/version-2.5/30-administration/60-ssl.md rename to docs/docs/30-administration/40-advanced/20-ssl.md index 755ba205d..36f2573db 100644 --- a/docs/versioned_docs/version-2.5/30-administration/60-ssl.md +++ b/docs/docs/30-administration/40-advanced/20-ssl.md @@ -1,35 +1,5 @@ # SSL -Woodpecker supports two ways of enabling SSL communication. You can either use Let's Encrypt to get automated SSL support with -renewal or provide your own SSL certificates. - -## Let's Encrypt - -Woodpecker supports automated SSL configuration and updates using Let's Encrypt. - -You can enable Let's Encrypt by making the following modifications to your server configuration: - -```ini -WOODPECKER_LETS_ENCRYPT=true -WOODPECKER_LETS_ENCRYPT_EMAIL=ssl-admin@example.tld -``` - -Note that Woodpecker uses the hostname from the `WOODPECKER_HOST` environment variable when requesting certificates. For example, if `WOODPECKER_HOST=https://example.com` is set the certificate is requested for `example.com`. To receive emails before certificates expire Let's Encrypt requires an email address. You can set it with `WOODPECKER_LETS_ENCRYPT_EMAIL=ssl-admin@example.tld`. - -The SSL certificates are stored in `$HOME/.local/share/certmagic` for binary versions of Woodpecker and in `/var/lib/woodpecker` for the Container versions of it. You can set a custom path by setting `XDG_DATA_HOME` if required. - -> Once enabled you can visit the Woodpecker UI with http and the HTTPS address. HTTP will be redirected to HTTPS. - -### Certificate Cache - -Woodpecker writes the certificates to `/var/lib/woodpecker/certmagic/`. - -### Certificate Updates - -Woodpecker uses the official Go acme library which will handle certificate upgrades. There should be no addition configuration or management required. - -## SSL with own certificates - Woodpecker supports SSL configuration by mounting certificates into your container. ```ini @@ -37,23 +7,21 @@ WOODPECKER_SERVER_CERT=/etc/certs/woodpecker.example.com/server.crt WOODPECKER_SERVER_KEY=/etc/certs/woodpecker.example.com/server.key ``` -### Certificate Chain +## Certificate Chain The most common problem encountered is providing a certificate file without the intermediate chain. > LoadX509KeyPair reads and parses a public/private key pair from a pair of files. The files must contain PEM encoded data. The certificate file may contain intermediate certificates following the leaf certificate to form a certificate chain. -### Certificate Errors +## Certificate Errors SSL support is provided using the [ListenAndServeTLS](https://golang.org/pkg/net/http/#ListenAndServeTLS) function from the Go standard library. If you receive certificate errors or warnings please examine your configuration more closely. -### Running in containers +## Running in containers Update your configuration to expose the following ports: ```diff title="docker-compose.yaml" - version: '3' - services: woodpecker-server: [...] @@ -66,8 +34,6 @@ Update your configuration to expose the following ports: Update your configuration to mount your certificate and key: ```diff title="docker-compose.yaml" - version: '3' - services: woodpecker-server: [...] @@ -79,8 +45,6 @@ Update your configuration to mount your certificate and key: Update your configuration to provide the paths of your certificate and key: ```diff title="docker-compose.yaml" - version: '3' - services: woodpecker-server: [...] diff --git a/docs/versioned_docs/version-2.5/30-administration/80-autoscaler.md b/docs/docs/30-administration/40-advanced/30-autoscaler.md similarity index 87% rename from docs/versioned_docs/version-2.5/30-administration/80-autoscaler.md rename to docs/docs/30-administration/40-advanced/30-autoscaler.md index 6ac581c2e..0ad43a30b 100644 --- a/docs/versioned_docs/version-2.5/30-administration/80-autoscaler.md +++ b/docs/docs/30-administration/40-advanced/30-autoscaler.md @@ -6,13 +6,11 @@ Please note that the autoscaler is not feature-complete yet. You can follow the ## Setup -### docker-compose +### docker compose -If you are using docker-compose you can add the following to your `docker-compose.yaml` file: +If you are using docker compose you can add the following to your `docker-compose.yaml` file: ```yaml -version: '3' - services: woodpecker-server: image: woodpeckerci/woodpecker-server:next @@ -29,8 +27,8 @@ services: - WOODPECKER_MIN_AGENTS=0 - WOODPECKER_MAX_AGENTS=3 - WOODPECKER_WORKFLOWS_PER_AGENT=2 # the number of workflows each agent can run at the same time - - WOODEPCKER_GRPC_ADDR=https://grpc.your-woodpecker-server.tld # the grpc address of your woodpecker server, publicly accessible from the agents - - WOODEPCKER_GRPC_SECURE=true + - WOODPECKER_GRPC_ADDR=https://grpc.your-woodpecker-server.tld # the grpc address of your woodpecker server, publicly accessible from the agents + - WOODPECKER_GRPC_SECURE=true - WOODPECKER_AGENT_ENV= # optional environment variables to pass to the agents - WOODPECKER_PROVIDER=hetznercloud # set the provider, you can find all the available ones down below - WOODPECKER_HETZNERCLOUD_API_TOKEN=${WOODPECKER_HETZNERCLOUD_API_TOKEN} # your api token for the Hetzner cloud diff --git a/docs/docs/30-administration/40-advanced/40-advanced.md b/docs/docs/30-administration/40-advanced/40-advanced.md new file mode 100644 index 000000000..4261bbeea --- /dev/null +++ b/docs/docs/30-administration/40-advanced/40-advanced.md @@ -0,0 +1,25 @@ +# Advanced options + +Why should we be happy with a default setup? We should not! Woodpecker offers a lot of advanced options to configure it to your needs. + +## Behind a proxy + +See the [proxy guide](./10-proxy.md) if you want to see a setup behind Apache, Nginx, Caddy or ngrok. + +In the case you need to use Woodpecker with a URL path prefix (like: ), add the root path to [`WOODPECKER_HOST`](../10-server-config.md#woodpecker_host). + +## SSL + +Woodpecker supports SSL configuration by using Let's encrypt or by using own certificates. See the [SSL guide](./20-ssl.md). + +## Metrics + +A [Prometheus endpoint](./90-prometheus.md) is exposed by Woodpecker to collect metrics. + +## Autoscaling + +The [autoscaler](./30-autoscaler.md) can be used to deploy new agents to a cloud provider based on the current workload your server is experiencing. + +## Configuration service + +Sometime the normal yaml configuration compiler isn't enough. You can use the [configuration service](./100-external-configuration-api.md) to process your configuration files by your own. diff --git a/docs/versioned_docs/version-2.5/30-administration/90-prometheus.md b/docs/docs/30-administration/40-advanced/90-prometheus.md similarity index 91% rename from docs/versioned_docs/version-2.5/30-administration/90-prometheus.md rename to docs/docs/30-administration/40-advanced/90-prometheus.md index 2264f3b09..3f974ccb0 100644 --- a/docs/versioned_docs/version-2.5/30-administration/90-prometheus.md +++ b/docs/docs/30-administration/40-advanced/90-prometheus.md @@ -52,11 +52,11 @@ List of Prometheus metrics specific to Woodpecker: # HELP woodpecker_pipeline_count Pipeline count. # TYPE woodpecker_pipeline_count counter woodpecker_pipeline_count{branch="main",pipeline="total",repo="woodpecker-ci/woodpecker",status="success"} 3 -woodpecker_pipeline_count{branch="mkdocs",pipeline="total",repo="woodpecker-ci/woodpecker",status="success"} 3 +woodpecker_pipeline_count{branch="dev",pipeline="total",repo="woodpecker-ci/woodpecker",status="success"} 3 # HELP woodpecker_pipeline_time Build time. # TYPE woodpecker_pipeline_time gauge woodpecker_pipeline_time{branch="main",pipeline="total",repo="woodpecker-ci/woodpecker",status="success"} 116 -woodpecker_pipeline_time{branch="mkdocs",pipeline="total",repo="woodpecker-ci/woodpecker",status="success"} 155 +woodpecker_pipeline_time{branch="dev",pipeline="total",repo="woodpecker-ci/woodpecker",status="success"} 155 # HELP woodpecker_pipeline_total_count Total number of builds. # TYPE woodpecker_pipeline_total_count gauge woodpecker_pipeline_total_count 1025 diff --git a/docs/versioned_docs/version-2.4/30-administration/75-addons/_category_.yaml b/docs/docs/30-administration/40-advanced/_category_.yaml similarity index 60% rename from docs/versioned_docs/version-2.4/30-administration/75-addons/_category_.yaml rename to docs/docs/30-administration/40-advanced/_category_.yaml index 4cd7380c5..e6c6ba0f7 100644 --- a/docs/versioned_docs/version-2.4/30-administration/75-addons/_category_.yaml +++ b/docs/docs/30-administration/40-advanced/_category_.yaml @@ -1,6 +1,6 @@ -label: 'Addons' +label: 'Advanced' collapsible: true collapsed: true link: type: 'doc' - id: 'overview' + id: 'advanced' diff --git a/docs/docs/50-about.md b/docs/docs/50-about.md index bec3304a1..fe7b4a55f 100644 --- a/docs/docs/50-about.md +++ b/docs/docs/50-about.md @@ -1,10 +1,10 @@ # About -Woodpecker has been originally forked from Drone 0.8 as the Drone CI license was changed after the 0.8 release from Apache 2.0 to a proprietary license. Woodpecker is based on this latest freely available version. +Woodpecker is a community-driven open source CI/CD tool. It is lightweight, fast and simple to use. It can be used with many different Git providers and runners. ## History -Woodpecker was originally forked by [@laszlocph](https://github.com/laszlocph) in 2019. +Woodpecker was originally forked from Drone 0.8 by [@laszlocph](https://github.com/laszlocph) in 2019 after its license model was changed. A few important time points: @@ -15,4 +15,5 @@ A few important time points: ## Differences to Drone -Woodpecker is a community-focused software that still stay free and open source forever, while Drone is managed by [Harness](https://harness.io/) and published under [Polyform Small Business](https://polyformproject.org/licenses/small-business/1.0.0/) license. +Woodpecker is a community-driven open source software published under the Apache License 2.0 and will always remain so. Drone CI is managed by [Harness](https://harness.io/) and is available in two editions: [Open Source under Apache License 2.0 and Enterprise under Polyform Small Business license](https://docs.drone.io/enterprise/#is-drone-open-source). +In terms of Drone's feature set, Woodpecker is somewhere between [Drone Enterprise and OSS](https://docs.drone.io/enterprise/#what-is-the-difference-between-open-source-and-enterprise), but also has some unique features. diff --git a/docs/docs/92-development/01-getting-started.md b/docs/docs/92-development/01-getting-started.md index 255b92a48..4e1322955 100644 --- a/docs/docs/92-development/01-getting-started.md +++ b/docs/docs/92-development/01-getting-started.md @@ -4,12 +4,12 @@ You can develop on your local computer by following the [steps below](#preparati ## Gitpod -If you want to start development or updating docs as easy as possible, you can use our preconfigured setup for Woodpecker using [Gitpod](https://github.com/gitpod-io/gitpod). Gitpod starts a complete development setup in the cloud containing: +If you want to start development or updating docs as easy as possible, you can use our pre-configured setup for Woodpecker using [Gitpod](https://github.com/gitpod-io/gitpod). Gitpod starts a complete development setup in the cloud containing: - An IDE in the browser or bridged to your local VS-Code or Jetbrains -- A preconfigured [Gitea](https://github.com/go-gitea/gitea) instance as forge -- A preconfigured Woodpecker server -- A single preconfigured Woodpecker agent node +- A pre-configured [Gitea](https://github.com/go-gitea/gitea) instance as forge +- A pre-configured Woodpecker server +- A single pre-configured Woodpecker agent node - Our docs preview server Start Woodpecker in Gitpod by clicking on the following badge. You can log in with `woodpecker` and `password`. @@ -34,7 +34,7 @@ Install make on: ### Install Node.js & `pnpm` -Install [Node.js (>=14)](https://nodejs.org/en/download/) if you want to build Woodpecker's UI or documentation. +Install [Node.js (>=20)](https://nodejs.org/en/download/package-manager) if you want to build Woodpecker's UI or documentation. For dependency installation (`node_modules`) of UI and documentation of Woodpecker the package manager pnpm is used. [This guide](https://pnpm.io/installation) describes the installation of `pnpm`. @@ -54,8 +54,7 @@ A common config for debugging would look like this: WOODPECKER_OPEN=true WOODPECKER_ADMIN=your-username -# if you want to test webhooks with an online forge like GitHub this address needs to be accessible from public server -WOODPECKER_HOST=http://your-dev-address.com +WOODPECKER_HOST=http://localhost:8000 # github (sample for a forge config - see /docs/administration/forge/overview for other forges) WOODPECKER_GITHUB=true @@ -70,8 +69,8 @@ WOODPECKER_MAX_WORKFLOWS=1 # enable if you want to develop the UI # WOODPECKER_DEV_WWW_PROXY=http://localhost:8010 -# used so you can login without using a public address -WOODPECKER_DEV_OAUTH_HOST=http://localhost:8000 +# if you want to test webhooks with an online forge like GitHub this address needs to be set and accessible from public server +WOODPECKER_EXPERT_WEBHOOK_HOST=http://your-address.com # disable health-checks while debugging (normally not needed while developing) WOODPECKER_HEALTHCHECK=false @@ -82,7 +81,7 @@ WOODPECKER_HEALTHCHECK=false ### Setup OAuth -Create an OAuth app for your forge as described in the [forges documentation](../30-administration/11-forges/11-overview.md). If you set `WOODPECKER_DEV_OAUTH_HOST=http://localhost:8000` you can use that address with the path as explained for the specific forge to login without the need for a public address. For example for GitHub you would use `http://localhost:8000/authorize` as authorization callback URL. +Create an OAuth app for your forge as described in the [forges documentation](../30-administration/11-forges/11-overview.md). ## Developing with VS Code @@ -129,7 +128,7 @@ make test-frontend If you want to test a specific Go file, you can also use: ```bash -go test -race -timeout 30s go.woodpecker-ci.org/woodpecker/v2/ +go test -race -timeout 30s go.woodpecker-ci.org/woodpecker/v3/ ``` Or you can open the test-file inside [VS-Code](#developing-with-vs-code) and run or debug the test by clicking on the inline commands: diff --git a/docs/docs/92-development/02-core-ideas.md b/docs/docs/92-development/02-core-ideas.md index 8e0d6e292..a88470f0a 100644 --- a/docs/docs/92-development/02-core-ideas.md +++ b/docs/docs/92-development/02-core-ideas.md @@ -8,7 +8,7 @@ ## Addons and extensions If you are wondering whether your contribution will be accepted to be merged in the Woodpecker core, or whether it's better to write an -[addon forge](../30-administration/11-forges/100-addon.md), [extension](../30-administration/100-external-configuration-api.md) or an +[addon forge](../30-administration/11-forges/100-addon.md), [extension](../30-administration/40-advanced/100-external-configuration-api.md) or an [external custom backend](../30-administration/22-backends/50-custom-backends.md), please check these points: - Is your change very specific to your setup and unlikely to be used by anyone else? diff --git a/docs/docs/92-development/03-ui.md b/docs/docs/92-development/03-ui.md index 6a01584c2..1931ba99a 100644 --- a/docs/docs/92-development/03-ui.md +++ b/docs/docs/92-development/03-ui.md @@ -21,9 +21,10 @@ The following list contains some tools and frameworks used by the Woodpecker UI. - use `setup` and composition api - place (re-usable) components in `web/src/components/` - views should have a route in `web/src/router.ts` and are located in `web/src/views/` -- [Windicss](https://windicss.org/) (similar to Tailwind) - - use Windicss classes where possible - - if needed extend the Windicss config to use new classes +- [Tailwind CSS](https://tailwindcss.com/) + - use Tailwind classes where possible + - if needed extend the Tailwind config to use new classes + - classes are sorted following the [prettier tailwind sort plugin](https://tailwindcss.com/blog/automatic-class-sorting-with-prettier) - [Vite](https://vitejs.dev/) (similar to Webpack) - [Typescript](https://www.typescriptlang.org/) - avoid using `any` and `unknown` (the linter will prevent you from doing so anyways :wink:) diff --git a/docs/docs/92-development/05-architecture.md b/docs/docs/92-development/05-architecture.md index ccfd410f3..535ec6a80 100644 --- a/docs/docs/92-development/05-architecture.md +++ b/docs/docs/92-development/05-architecture.md @@ -19,23 +19,23 @@ ### Server -| package | meaning | imports | -| -------------------- | ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `server/api/**` | handle web requests from `server/router` | `pipeline`, `../badges`, `../ccmenue`, `../logging`, `../model`, `../pubsub`, `../queue`, `../forge`, `../shared`, `../store`, `shared`, (TODO: mv `server/router/middleware/session`) | -| `server/badges/**` | generate svg badges for pipelines | `../model` | -| `server/ccmenu/**` | generate xml ccmenu for pipelines | `../model` | -| `server/grpc/**` | gRPC server agents can connect to | `pipeline/rpc/**`, `../logging`, `../model`, `../pubsub`, `../queue`, `../forge`, `../pipeline`, `../store` | -| `server/logging/**` | logging lib for gPRC server to stream logs while running | std | -| `server/model/**` | structs for store (db) and api (json) | std | -| `server/plugins/**` | plugins for server | `../model`, `../forge` | -| `server/pipeline/**` | orchestrate pipelines | `pipeline`, `../model`, `../pubsub`, `../queue`, `../forge`, `../store`, `../plugins` | -| `server/pubsub/**` | pubsub lib for server to push changes to the WebUI | std | -| `server/queue/**` | queue lib for server where agents pull new pipelines from via gRPC | `server/model` | -| `server/forge/**` | forge lib for server to connect and handle forge specific stuff | `shared`, `server/model` | -| `server/router/**` | handle requests to REST API (and all middleware) and serve UI and WebUI config | `shared`, `../api`, `../model`, `../forge`, `../store`, `../web` | -| `server/store/**` | handle database | `server/model` | -| `server/shared/**` | TODO: move and split [#974](https://github.com/woodpecker-ci/woodpecker/issues/974) | | -| `server/web/**` | server SPA | | +| package | meaning | imports | +| -------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `server/api/**` | handle web requests from `server/router` | `pipeline`, `../badges`, `../ccmenu`, `../logging`, `../model`, `../pubsub`, `../queue`, `../forge`, `../shared`, `../store`, `shared`, (TODO: mv `server/router/middleware/session`) | +| `server/badges/**` | generate svg badges for pipelines | `../model` | +| `server/ccmenu/**` | generate xml ccmenu for pipelines | `../model` | +| `server/grpc/**` | gRPC server agents can connect to | `pipeline/rpc/**`, `../logging`, `../model`, `../pubsub`, `../queue`, `../forge`, `../pipeline`, `../store` | +| `server/logging/**` | logging lib for gPRC server to stream logs while running | std | +| `server/model/**` | structs for store (db) and api (json) | std | +| `server/plugins/**` | plugins for server | `../model`, `../forge` | +| `server/pipeline/**` | orchestrate pipelines | `pipeline`, `../model`, `../pubsub`, `../queue`, `../forge`, `../store`, `../plugins` | +| `server/pubsub/**` | pubsub lib for server to push changes to the WebUI | std | +| `server/queue/**` | queue lib for server where agents pull new pipelines from via gRPC | `server/model` | +| `server/forge/**` | forge lib for server to connect and handle forge specific stuff | `shared`, `server/model` | +| `server/router/**` | handle requests to REST API (and all middleware) and serve UI and WebUI config | `shared`, `../api`, `../model`, `../forge`, `../store`, `../web` | +| `server/store/**` | handle database | `server/model` | +| `server/shared/**` | TODO: move and split [#974](https://github.com/woodpecker-ci/woodpecker/issues/974) | | +| `server/web/**` | server SPA | | - `../` = `server/` diff --git a/docs/docs/92-development/07-guides.md b/docs/docs/92-development/07-guides.md index c70a9ec93..c8612f576 100644 --- a/docs/docs/92-development/07-guides.md +++ b/docs/docs/92-development/07-guides.md @@ -21,3 +21,49 @@ To automatically execute the migration after the start of the server, the new mi ## Constants of official images All official default images, are saved in [shared/constant/constant.go](https://github.com/woodpecker-ci/woodpecker/blob/main/shared/constant/constant.go) and must be pinned by an exact tag. + +## Building images locally + +### Server + +```sh +### build web component +make vendor +cd web/ +pnpm install --frozen-lockfile +pnpm build +cd .. + +### define the platforms to build for (e.g. linux/amd64) +# (the | is not a typo here) +export PLATFORMS='linux|amd64' +make cross-compile-server + +### build the image +docker buildx build --platform linux/amd64 -t username/repo:tag -f docker/Dockerfile.server.multiarch.rootless --push . +``` + +:::info +The `cross-compile-server` rule makes use of `xgo`, a go cross-compiler. You need to be on a `amd64` host to do this, as `xgo` is only available for `amd64` (see [xgo#213](https://github.com/techknowlogick/xgo/issues/213)). +You can try to use the `build-server` rule instead, however this one fails for some OS (e.g. macOS). +::: + +### Agent + +```sh +### build the agent +make build-agent + +### build the image +docker buildx build --platform linux/amd64 -t username/repo:tag -f docker/Dockerfile.agent.multiarch --push . +``` + +### CLI + +```sh +### build the CLI +make build-cli + +### build the image +docker buildx build --platform linux/amd64 -t username/repo:tag -f docker/Dockerfile.cli.multiarch.rootless --push . +``` diff --git a/docs/versioned_docs/version-2.5/92-development/08-swagger.md b/docs/docs/92-development/09-openapi.md similarity index 84% rename from docs/versioned_docs/version-2.5/92-development/08-swagger.md rename to docs/docs/92-development/09-openapi.md index 9a3775c41..1d7e58533 100644 --- a/docs/versioned_docs/version-2.5/92-development/08-swagger.md +++ b/docs/docs/92-development/09-openapi.md @@ -7,7 +7,7 @@ and then being using on the community's website documentation. It's paramount important to keep the gin handler function's godoc documentation up-to-date, to always have accurate API documentation. -Whenever you change, add or enhance an API endpoint, please update the godocs. +Whenever you change, add or enhance an API endpoint, please update the godoc. You don't require any extra tools on your machine, all Swagger tooling is automatically fetched by standard Go tools. @@ -36,13 +36,13 @@ type User struct { } // @name User ``` -These guidelines aim to have consistent wording in the swagger doc: +These guidelines aim to have consistent wording in the OpenAPI doc: - first word after `@Summary` and `@Summary` are always uppercase - `@Summary` has no `.` (dot) at the end of the line - model structs shall use custom short names, to ease life for API consumers, using `@name` -- `@Success` object or array declarations shall be short, this means the actual `model.User` struct must have a `@name` annotation, so that the model can be renderend in Swagger -- when pagination is used, `@Parame page` and `@Parame perPage` must be added manually +- `@Success` object or array declarations shall be short, this means the actual `model.User` struct must have a `@name` annotation, so that the model can be rendered in OpenAPI +- when pagination is used, `@Param page` and `@Param perPage` must be added manually - `@Param Authorization` is almost always present, there are just a few un-protected endpoints There are many examples in the `server/api` package, which you can use a blueprint. @@ -50,14 +50,10 @@ More enhanced information you can find here + ```none 9:18PM DBG pipeline/pipeline.go:94 > executing 2 stages, in order of: CLI=exec 9:18PM DBG pipeline/pipeline.go:104 > stage CLI=exec StagePos=0 Steps=echo @@ -69,7 +71,9 @@ This could be executed via `woodpecker-cli --log-level trace exec --backend-engi 9:18PM TRC pipeline/backend/dummy/dummy.go:208 > delete workflow environment taskUUID=01J10P578JQE6E25VV1EQF0745 ``` -There are also environment variables to alter step behaviour: + + +There are also environment variables to alter step behavior: - `SLEEP: 10` will let the step wait 10 seconds - `EXPECT_TYPE` allows to check if a step is a `clone`, `service`, `plugin` or `commands` diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts index e49a1efb5..3964bedbc 100644 --- a/docs/docusaurus.config.ts +++ b/docs/docusaurus.config.ts @@ -1,11 +1,11 @@ -import { themes } from 'prism-react-renderer'; -import type { Config } from '@docusaurus/types'; -import type * as Preset from '@docusaurus/preset-classic'; import * as path from 'path'; +import type * as Preset from '@docusaurus/preset-classic'; +import type { Config } from '@docusaurus/types'; +import { themes } from 'prism-react-renderer'; -const config: Config = { +const config = { title: 'Woodpecker CI', - tagline: 'Woodpecker is a simple yet powerful CI/CD engine with great extensibility.', + tagline: 'Woodpecker is a simple, yet powerful CI/CD engine with great extensibility.', url: 'https://woodpecker-ci.org', baseUrl: '/', onBrokenLinks: 'throw', @@ -34,7 +34,7 @@ const config: Config = { items: [ { type: 'doc', - docId: 'intro', + docId: 'intro/index', activeBaseRegex: 'docs/(?!migrations|awesome)', position: 'left', label: 'Docs', @@ -50,20 +50,19 @@ const config: Config = { position: 'left', items: [ { - to: '/docs/next/migrations', // Always point to newest migration guide - activeBaseRegex: 'docs/(next/)?migrations', + to: '/migrations', // Always point to newest migration guide + activeBaseRegex: 'migrations', label: 'Migrations', }, { - to: '/docs/next/awesome', // Always point to newest awesome list - activeBaseRegex: 'docs/(next/)?awesome', + to: '/awesome', // Always point to newest awesome list + activeBaseRegex: 'awesome', label: 'Awesome', }, { to: '/api', label: 'API', }, - { to: 'cookbook', label: 'Cookbook' }, ], }, { @@ -105,7 +104,7 @@ const config: Config = { }, { label: 'Server setup', - to: '/docs/administration/deployment/overview', + to: '/docs/administration/getting-started', }, ], }, @@ -148,7 +147,7 @@ const config: Config = { ], }, ], - copyright: `Copyright © ${new Date().getFullYear()} Woodpecker CI. Built with Docusaurus.`, + copyright: `Copyright © ${new Date().getFullYear()} Woodpecker Authors. Built with Docusaurus.`, }, prism: { theme: themes.github, @@ -219,21 +218,6 @@ const config: Config = { } as any; }, }), - [ - '@docusaurus/plugin-content-blog', - { - id: 'cookbook-blog', - /** - * URL route for the blog section of your site. - * *DO NOT* include a trailing slash. - */ - routeBasePath: 'cookbook', - /** - * Path to data on filesystem relative to site dir. - */ - path: './cookbook', - }, - ], ], themes: [ path.resolve(__dirname, 'plugins', 'woodpecker-plugins', 'dist'), @@ -252,23 +236,23 @@ const config: Config = { sidebarPath: require.resolve('./sidebars.js'), editUrl: 'https://github.com/woodpecker-ci/woodpecker/edit/main/docs/', includeCurrentVersion: true, - lastVersion: '2.6', + lastVersion: '2.8', onlyIncludeVersions: - process.env.NODE_ENV === 'development' ? ['current', '2.6'] : ['current', '2.6', '2.5', '2.4', '1.0'], + process.env.NODE_ENV === 'development' ? ['current', '2.8'] : ['current', '2.8', '2.7', '2.6', '1.0'], versions: { current: { label: 'Next 🚧', banner: 'unreleased', }, - '2.6': { - label: '2.6.x', + '2.8': { + label: '2.8.x', }, - '2.5': { - label: '2.5.x 💀', + '2.7': { + label: '2.7.x 💀', banner: 'unmaintained', }, - '2.4': { - label: '2.4.x 💀', + '2.6': { + label: '2.6.x 💀', banner: 'unmaintained', }, '1.0': { @@ -280,8 +264,7 @@ const config: Config = { blog: { blogTitle: 'Blog', blogDescription: 'A blog for release announcements, turorials...', - // postsPerPage: 'ALL', - // blogSidebarCount: 0, + onInlineAuthors: 'ignore', }, theme: { customCss: require.resolve('./src/css/custom.css'), @@ -294,7 +277,7 @@ const config: Config = { // Plugin Options for loading OpenAPI files specs: [ { - spec: 'swagger.json', + spec: 'openapi.json', route: '/api/', }, ], @@ -306,19 +289,12 @@ const config: Config = { }, ], ], - webpack: { - jsLoader: (isServer) => ({ - loader: require.resolve('esbuild-loader'), - options: { - loader: 'tsx', - target: isServer ? 'node12' : 'es2017', - supported: { 'dynamic-import': false }, - }, - }), - }, markdown: { format: 'detect', }, -}; + future: { + experimental_faster: true, + }, +} satisfies Config; export default config; diff --git a/docs/package.json b/docs/package.json index a8a7aeaa3..3f097f99b 100644 --- a/docs/package.json +++ b/docs/package.json @@ -14,20 +14,16 @@ "write-heading-ids": "docusaurus write-heading-ids" }, "dependencies": { - "@docusaurus/core": "^3.1.0", - "@docusaurus/plugin-content-blog": "^3.1.0", - "@docusaurus/preset-classic": "^3.1.0", - "@easyops-cn/docusaurus-search-local": "^0.44.0", - "@mdx-js/react": "^3.0.0", - "@svgr/webpack": "^8.1.0", - "clsx": "^2.1.0", - "esbuild-loader": "^4.1.0", - "file-loader": "^6.2.0", - "prism-react-renderer": "^2.3.1", - "react": "^18.3.1", - "react-dom": "^18.2.0", - "redocusaurus": "^2.0.2", - "url-loader": "^4.1.1" + "@docusaurus/core": "^3.7.0", + "@docusaurus/faster": "^3.7.0", + "@docusaurus/plugin-content-blog": "^3.7.0", + "@docusaurus/preset-classic": "^3.7.0", + "@easyops-cn/docusaurus-search-local": "^0.46.1", + "clsx": "^2.1.1", + "prism-react-renderer": "^2.4.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "redocusaurus": "^2.2.0" }, "browserslist": { "production": [ @@ -42,19 +38,13 @@ ] }, "devDependencies": { - "@docusaurus/module-type-aliases": "^3.1.0", - "@docusaurus/tsconfig": "3.4.0", - "@docusaurus/types": "^3.1.0", - "@types/node": "^20.11.30", - "@types/react": "^18.2.67", + "@docusaurus/module-type-aliases": "^3.7.0", + "@docusaurus/tsconfig": "3.7.0", + "@docusaurus/types": "^3.7.0", + "@types/node": "^22.10.5", + "@types/react": "^19.0.0", "@types/react-helmet": "^6.1.11", "@types/react-router-dom": "^5.3.3", - "typescript": "^5.4.3" - }, - "pnpm": { - "overrides": { - "trim": "^1.0.0", - "got": "^14.0.0" - } + "typescript": "^5.7.2" } } diff --git a/docs/plugins/woodpecker-plugins/package.json b/docs/plugins/woodpecker-plugins/package.json index 993422e88..82a850b23 100644 --- a/docs/plugins/woodpecker-plugins/package.json +++ b/docs/plugins/woodpecker-plugins/package.json @@ -10,24 +10,24 @@ "style": "mkdir -p dist/theme/ && cp src/theme/style.css dist/theme/style.css" }, "devDependencies": { - "@docusaurus/module-type-aliases": "^3.3.2", - "@docusaurus/theme-classic": "^3.3.2", - "@docusaurus/types": "^3.3.2", + "@docusaurus/module-type-aliases": "^3.7.0", + "@docusaurus/theme-classic": "^3.7.0", + "@docusaurus/types": "^3.7.0", "@tsconfig/docusaurus": "^2.0.3", - "@types/node": "^20.12.13", - "axios": "^1.7.2", - "concurrently": "^8.2.2", - "isomorphic-dompurify": "^2.11.0", - "marked": "^13.0.0", - "tslib": "^2.6.2", - "typescript": "^5.4.5" + "@types/node": "^22.10.5", + "axios": "^1.7.9", + "concurrently": "^9.1.2", + "isomorphic-dompurify": "^2.19.0", + "marked": "^15.0.5", + "tslib": "^2.8.1", + "typescript": "^5.7.2" }, "peerDependencies": { - "react": "^17.0.2 || ^18.0.0", - "react-dom": "^17.0.2 || ^18.0.0" + "react": "^17.0.2 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.2 || ^18.0.0 || ^19.0.0" }, "dependencies": { "fuse.js": "^7.0.0", - "yaml": "^2.4.2" + "yaml": "^2.7.0" } } diff --git a/docs/plugins/woodpecker-plugins/plugins.json b/docs/plugins/woodpecker-plugins/plugins.json index 775916c7b..63c800f4c 100644 --- a/docs/plugins/woodpecker-plugins/plugins.json +++ b/docs/plugins/woodpecker-plugins/plugins.json @@ -32,7 +32,7 @@ }, { "name": "Prettier", - "docs": "https://raw.githubusercontent.com/woodpecker-ci/plugin-prettier/main/docs.md", + "docs": "https://codeberg.org/woodpecker-plugins/prettier/raw/branch/main/docs.md", "verified": true }, { @@ -40,11 +40,6 @@ "docs": "https://raw.githubusercontent.com/woodpecker-ci/plugin-extend-env/main/docs.md", "verified": true }, - { - "name": "Block Git changes", - "docs": "https://codeberg.org/qwerty287/woodpecker-block-git-changes/raw/branch/main/docs.md", - "verified": false - }, { "name": "Regex check", "docs": "https://codeberg.org/qwerty287/woodpecker-regex-check/raw/branch/main/docs.md", @@ -219,6 +214,21 @@ "name": "Docker Tags", "docs": "https://raw.githubusercontent.com/dvjn/woodpecker-docker-tags-plugin/main/docs.md", "verified": false + }, + { + "name": "Telegram", + "docs": "https://raw.githubusercontent.com/appleboy/drone-telegram/refs/heads/master/DOCS.md", + "verified": false + }, + { + "name": "EditorConfig Checker", + "docs": "https://codeberg.org/woodpecker-plugins/editorconfig-checker/raw/branch/main/docs.md", + "verified": true + }, + { + "name": "Microsoft Teams Notify", + "docs": "https://raw.githubusercontent.com/GECO-IT/woodpecker-plugin-teams-notify/refs/heads/main/docs.md", + "verified": false } ] } diff --git a/docs/plugins/woodpecker-plugins/src/index.ts b/docs/plugins/woodpecker-plugins/src/index.ts index d39a90071..83b4d6cee 100644 --- a/docs/plugins/woodpecker-plugins/src/index.ts +++ b/docs/plugins/woodpecker-plugins/src/index.ts @@ -1,9 +1,10 @@ -import { LoadContext, Plugin, PluginContentLoadedActions } from '@docusaurus/types'; -import path from 'path'; import fs from 'fs'; +import path from 'path'; +import { LoadContext, Plugin, PluginContentLoadedActions } from '@docusaurus/types'; import axios, { AxiosError } from 'axios'; -import { Content, WoodpeckerPlugin, WoodpeckerPluginHeader, WoodpeckerPluginIndexEntry } from './types'; + import * as markdown from './markdown'; +import { Content, WoodpeckerPlugin, WoodpeckerPluginHeader, WoodpeckerPluginIndexEntry } from './types'; async function loadContent(): Promise { const file = path.join(__dirname, '..', 'plugins.json'); diff --git a/docs/plugins/woodpecker-plugins/src/theme/WoodpeckerPlugin.tsx b/docs/plugins/woodpecker-plugins/src/theme/WoodpeckerPlugin.tsx index a80cab9f0..0aeaa86b1 100644 --- a/docs/plugins/woodpecker-plugins/src/theme/WoodpeckerPlugin.tsx +++ b/docs/plugins/woodpecker-plugins/src/theme/WoodpeckerPlugin.tsx @@ -1,12 +1,13 @@ -import React from 'react'; import Layout from '@theme/Layout'; +import React from 'react'; + import { WoodpeckerPlugin as WoodpeckerPluginType } from '../types'; import { IconContainer, IconPlugin, IconVerified, IconWebsite } from './Icons'; export function WoodpeckerPlugin({ plugin }: { plugin: WoodpeckerPluginType }) { return ( -
+
diff --git a/docs/plugins/woodpecker-plugins/src/theme/WoodpeckerPluginList.tsx b/docs/plugins/woodpecker-plugins/src/theme/WoodpeckerPluginList.tsx index 3fd3e37b3..266236f7e 100644 --- a/docs/plugins/woodpecker-plugins/src/theme/WoodpeckerPluginList.tsx +++ b/docs/plugins/woodpecker-plugins/src/theme/WoodpeckerPluginList.tsx @@ -1,7 +1,9 @@ -import React, { useState, useRef } from 'react'; -import Fuse from 'fuse.js'; import Layout from '@theme/Layout'; +import Fuse from 'fuse.js'; +import React, { useRef, useState } from 'react'; + import './style.css'; + import { WoodpeckerPlugin } from '../types'; import { IconPlugin, IconVerified } from './Icons'; @@ -41,7 +43,7 @@ export function WoodpeckerPluginList({ plugins }: { plugins: WoodpeckerPlugin[] const fuse = useRef( new Fuse(plugins, { - keys: ['name', 'description'], + keys: ['name', 'description', 'tags'], threshold: 0.3, }), ); @@ -50,7 +52,7 @@ export function WoodpeckerPluginList({ plugins }: { plugins: WoodpeckerPlugin[] return ( -
+

Woodpecker CI plugins

diff --git a/docs/plugins/woodpecker-plugins/src/theme/style.css b/docs/plugins/woodpecker-plugins/src/theme/style.css index e16940ee4..6ab31f196 100644 --- a/docs/plugins/woodpecker-plugins/src/theme/style.css +++ b/docs/plugins/woodpecker-plugins/src/theme/style.css @@ -57,8 +57,8 @@ padding: 1rem 1rem 1rem 2.25rem; font-size: 1.1rem; appearance: none; - background: var(--ifm-navbar-search-input-background-color) var(--ifm-navbar-search-input-icon) no-repeat 0.75rem 1rem / - 1.1rem 1.1rem; + background: var(--ifm-navbar-search-input-background-color) var(--ifm-navbar-search-input-icon) no-repeat 0.75rem + 1rem / 1.1rem 1.1rem; border-radius: 0.5rem; border: 1px solid var(--ifm-card-background-color); color: var(--ifm-navbar-search-input-color); diff --git a/docs/pnpm-lock.yaml b/docs/pnpm-lock.yaml index a8943ebc4..5c984f712 100644 --- a/docs/pnpm-lock.yaml +++ b/docs/pnpm-lock.yaml @@ -4,72 +4,56 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false -overrides: - trim: ^1.0.0 - got: ^14.0.0 - importers: .: dependencies: '@docusaurus/core': - specifier: ^3.1.0 - version: 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(debug@4.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) + specifier: ^3.7.0 + version: 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(debug@4.4.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/faster': + specifier: ^3.7.0 + version: 3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)) '@docusaurus/plugin-content-blog': - specifier: ^3.1.0 - version: 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) + specifier: ^3.7.0 + version: 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) '@docusaurus/preset-classic': - specifier: ^3.1.0 - version: 3.4.0(@algolia/client-search@4.24.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.14.0)(typescript@5.5.3) + specifier: ^3.7.0 + version: 3.7.0(@algolia/client-search@5.18.0)(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(@types/react@19.0.2)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3)(typescript@5.7.2) '@easyops-cn/docusaurus-search-local': - specifier: ^0.44.0 - version: 0.44.2(@docusaurus/theme-common@3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3))(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@mdx-js/react': - specifier: ^3.0.0 - version: 3.0.1(@types/react@18.3.3)(react@18.3.1) - '@svgr/webpack': - specifier: ^8.1.0 - version: 8.1.0(typescript@5.5.3) + specifier: ^0.46.1 + version: 0.46.1(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@docusaurus/theme-common@3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) clsx: - specifier: ^2.1.0 + specifier: ^2.1.1 version: 2.1.1 - esbuild-loader: - specifier: ^4.1.0 - version: 4.2.0(webpack@5.92.1) - file-loader: - specifier: ^6.2.0 - version: 6.2.0(webpack@5.92.1) prism-react-renderer: - specifier: ^2.3.1 - version: 2.3.1(react@18.3.1) + specifier: ^2.4.1 + version: 2.4.1(react@19.0.0) react: - specifier: ^18.3.1 - version: 18.3.1 + specifier: ^19.0.0 + version: 19.0.0 react-dom: - specifier: ^18.2.0 - version: 18.3.1(react@18.3.1) + specifier: ^19.0.0 + version: 19.0.0(react@19.0.0) redocusaurus: - specifier: ^2.0.2 - version: 2.1.1(@docusaurus/theme-common@3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3))(@docusaurus/utils@3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3))(core-js@3.37.1)(enzyme@3.11.0)(mobx@6.13.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@6.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(webpack@5.92.1) - url-loader: - specifier: ^4.1.1 - version: 4.1.1(file-loader@6.2.0(webpack@5.92.1))(webpack@5.92.1) + specifier: ^2.2.0 + version: 2.2.0(@docusaurus/theme-common@3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@docusaurus/utils@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(core-js@3.39.0)(enzyme@3.11.0)(mobx@6.13.5)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(styled-components@6.1.14(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(webpack@5.97.1(@swc/core@1.10.4)) devDependencies: '@docusaurus/module-type-aliases': - specifier: ^3.1.0 - version: 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^3.7.0 + version: 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@docusaurus/tsconfig': - specifier: 3.4.0 - version: 3.4.0 + specifier: 3.7.0 + version: 3.7.0 '@docusaurus/types': - specifier: ^3.1.0 - version: 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^3.7.0 + version: 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@types/node': - specifier: ^20.11.30 - version: 20.14.10 + specifier: ^22.10.5 + version: 22.10.5 '@types/react': - specifier: ^18.2.67 - version: 18.3.3 + specifier: ^19.0.0 + version: 19.0.2 '@types/react-helmet': specifier: ^6.1.11 version: 6.1.11 @@ -77,8 +61,8 @@ importers: specifier: ^5.3.3 version: 5.3.3 typescript: - specifier: ^5.4.3 - version: 5.5.3 + specifier: ^5.7.2 + version: 5.7.2 plugins/woodpecker-plugins: dependencies: @@ -86,275 +70,260 @@ importers: specifier: ^7.0.0 version: 7.0.0 react: - specifier: ^17.0.2 || ^18.0.0 + specifier: ^17.0.2 || ^18.0.0 || ^19.0.0 version: 18.3.1 react-dom: - specifier: ^17.0.2 || ^18.0.0 + specifier: ^17.0.2 || ^18.0.0 || ^19.0.0 version: 18.3.1(react@18.3.1) yaml: - specifier: ^2.4.2 - version: 2.4.5 + specifier: ^2.7.0 + version: 2.7.0 devDependencies: '@docusaurus/module-type-aliases': - specifier: ^3.3.2 - version: 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^3.7.0 + version: 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@docusaurus/theme-classic': - specifier: ^3.3.2 - version: 3.4.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) + specifier: ^3.7.0 + version: 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@swc/core@1.10.4)(@types/react@19.0.2)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) '@docusaurus/types': - specifier: ^3.3.2 - version: 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^3.7.0 + version: 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@tsconfig/docusaurus': specifier: ^2.0.3 version: 2.0.3 '@types/node': - specifier: ^20.12.13 - version: 20.14.10 + specifier: ^22.10.5 + version: 22.10.5 axios: - specifier: ^1.7.2 - version: 1.7.2 + specifier: ^1.7.9 + version: 1.7.9 concurrently: - specifier: ^8.2.2 - version: 8.2.2 + specifier: ^9.1.2 + version: 9.1.2 isomorphic-dompurify: - specifier: ^2.11.0 - version: 2.12.0 + specifier: ^2.19.0 + version: 2.19.0 marked: - specifier: ^13.0.0 - version: 13.0.2 + specifier: ^15.0.5 + version: 15.0.5 tslib: - specifier: ^2.6.2 - version: 2.6.3 + specifier: ^2.8.1 + version: 2.8.1 typescript: - specifier: ^5.4.5 - version: 5.5.3 + specifier: ^5.7.2 + version: 5.7.2 packages: - '@algolia/autocomplete-core@1.9.3': - resolution: {integrity: sha512-009HdfugtGCdC4JdXUbVJClA0q0zh24yyePn+KUGk3rP7j8FEe/m5Yo/z65gn6nP/cM39PxpzqKrL7A6fP6PPw==} + '@algolia/autocomplete-core@1.17.7': + resolution: {integrity: sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==} - '@algolia/autocomplete-plugin-algolia-insights@1.9.3': - resolution: {integrity: sha512-a/yTUkcO/Vyy+JffmAnTWbr4/90cLzw+CC3bRbhnULr/EM0fGNvM13oQQ14f2moLMcVDyAx/leczLlAOovhSZg==} + '@algolia/autocomplete-plugin-algolia-insights@1.17.7': + resolution: {integrity: sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==} peerDependencies: search-insights: '>= 1 < 3' - '@algolia/autocomplete-preset-algolia@1.9.3': - resolution: {integrity: sha512-d4qlt6YmrLMYy95n5TB52wtNDr6EgAIPH81dvvvW8UmuWRgxEtY0NJiPwl/h95JtG2vmRM804M0DSwMCNZlzRA==} + '@algolia/autocomplete-preset-algolia@1.17.7': + resolution: {integrity: sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==} peerDependencies: '@algolia/client-search': '>= 4.9.1 < 6' algoliasearch: '>= 4.9.1 < 6' - '@algolia/autocomplete-shared@1.9.3': - resolution: {integrity: sha512-Wnm9E4Ye6Rl6sTTqjoymD+l8DjSTHsHboVRYrKgEt8Q7UHm9nYbqhN/i0fhUYA3OAEH7WA8x3jfpnmJm3rKvaQ==} + '@algolia/autocomplete-shared@1.17.7': + resolution: {integrity: sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==} peerDependencies: '@algolia/client-search': '>= 4.9.1 < 6' algoliasearch: '>= 4.9.1 < 6' - '@algolia/cache-browser-local-storage@4.24.0': - resolution: {integrity: sha512-t63W9BnoXVrGy9iYHBgObNXqYXM3tYXCjDSHeNwnsc324r4o5UiVKUiAB4THQ5z9U5hTj6qUvwg/Ez43ZD85ww==} + '@algolia/client-abtesting@5.18.0': + resolution: {integrity: sha512-DLIrAukjsSrdMNNDx1ZTks72o4RH/1kOn8Wx5zZm8nnqFexG+JzY4SANnCNEjnFQPJTTvC+KpgiNW/CP2lumng==} + engines: {node: '>= 14.0.0'} - '@algolia/cache-common@4.24.0': - resolution: {integrity: sha512-emi+v+DmVLpMGhp0V9q9h5CdkURsNmFC+cOS6uK9ndeJm9J4TiqSvPYVu+THUP8P/S08rxf5x2P+p3CfID0Y4g==} + '@algolia/client-analytics@5.18.0': + resolution: {integrity: sha512-0VpGG2uQW+h2aejxbG8VbnMCQ9ary9/ot7OASXi6OjE0SRkYQ/+pkW+q09+IScif3pmsVVYggmlMPtAsmYWHng==} + engines: {node: '>= 14.0.0'} - '@algolia/cache-in-memory@4.24.0': - resolution: {integrity: sha512-gDrt2so19jW26jY3/MkFg5mEypFIPbPoXsQGQWAi6TrCPsNOSEYepBMPlucqWigsmEy/prp5ug2jy/N3PVG/8w==} + '@algolia/client-common@5.18.0': + resolution: {integrity: sha512-X1WMSC+1ve2qlMsemyTF5bIjwipOT+m99Ng1Tyl36ZjQKTa54oajBKE0BrmM8LD8jGdtukAgkUhFoYOaRbMcmQ==} + engines: {node: '>= 14.0.0'} - '@algolia/client-account@4.24.0': - resolution: {integrity: sha512-adcvyJ3KjPZFDybxlqnf+5KgxJtBjwTPTeyG2aOyoJvx0Y8dUQAEOEVOJ/GBxX0WWNbmaSrhDURMhc+QeevDsA==} + '@algolia/client-insights@5.18.0': + resolution: {integrity: sha512-FAJRNANUOSs/FgYOJ/Njqp+YTe4TMz2GkeZtfsw1TMiA5mVNRS/nnMpxas9771aJz7KTEWvK9GwqPs0K6RMYWg==} + engines: {node: '>= 14.0.0'} - '@algolia/client-analytics@4.24.0': - resolution: {integrity: sha512-y8jOZt1OjwWU4N2qr8G4AxXAzaa8DBvyHTWlHzX/7Me1LX8OayfgHexqrsL4vSBcoMmVw2XnVW9MhL+Y2ZDJXg==} + '@algolia/client-personalization@5.18.0': + resolution: {integrity: sha512-I2dc94Oiwic3SEbrRp8kvTZtYpJjGtg5y5XnqubgnA15AgX59YIY8frKsFG8SOH1n2rIhUClcuDkxYQNXJLg+w==} + engines: {node: '>= 14.0.0'} - '@algolia/client-common@4.24.0': - resolution: {integrity: sha512-bc2ROsNL6w6rqpl5jj/UywlIYC21TwSSoFHKl01lYirGMW+9Eek6r02Tocg4gZ8HAw3iBvu6XQiM3BEbmEMoiA==} + '@algolia/client-query-suggestions@5.18.0': + resolution: {integrity: sha512-x6XKIQgKFTgK/bMasXhghoEjHhmgoP61pFPb9+TaUJ32aKOGc65b12usiGJ9A84yS73UDkXS452NjyP50Knh/g==} + engines: {node: '>= 14.0.0'} - '@algolia/client-personalization@4.24.0': - resolution: {integrity: sha512-l5FRFm/yngztweU0HdUzz1rC4yoWCFo3IF+dVIVTfEPg906eZg5BOd1k0K6rZx5JzyyoP4LdmOikfkfGsKVE9w==} - - '@algolia/client-search@4.24.0': - resolution: {integrity: sha512-uRW6EpNapmLAD0mW47OXqTP8eiIx5F6qN9/x/7HHO6owL3N1IXqydGwW5nhDFBrV+ldouro2W1VX3XlcUXEFCA==} + '@algolia/client-search@5.18.0': + resolution: {integrity: sha512-qI3LcFsVgtvpsBGR7aNSJYxhsR+Zl46+958ODzg8aCxIcdxiK7QEVLMJMZAR57jGqW0Lg/vrjtuLFDMfSE53qA==} + engines: {node: '>= 14.0.0'} '@algolia/events@4.0.1': resolution: {integrity: sha512-FQzvOCgoFXAbf5Y6mYozw2aj5KCJoA3m4heImceldzPSMbdyS4atVjJzXKMsfX3wnZTFYwkkt8/z8UesLHlSBQ==} - '@algolia/logger-common@4.24.0': - resolution: {integrity: sha512-LLUNjkahj9KtKYrQhFKCzMx0BY3RnNP4FEtO+sBybCjJ73E8jNdaKJ/Dd8A/VA4imVHP5tADZ8pn5B8Ga/wTMA==} + '@algolia/ingestion@1.18.0': + resolution: {integrity: sha512-bGvJg7HnGGm+XWYMDruZXWgMDPVt4yCbBqq8DM6EoaMBK71SYC4WMfIdJaw+ABqttjBhe6aKNRkWf/bbvYOGyw==} + engines: {node: '>= 14.0.0'} - '@algolia/logger-console@4.24.0': - resolution: {integrity: sha512-X4C8IoHgHfiUROfoRCV+lzSy+LHMgkoEEU1BbKcsfnV0i0S20zyy0NLww9dwVHUWNfPPxdMU+/wKmLGYf96yTg==} + '@algolia/monitoring@1.18.0': + resolution: {integrity: sha512-lBssglINIeGIR+8KyzH05NAgAmn1BCrm5D2T6pMtr/8kbTHvvrm1Zvcltc5dKUQEFyyx3J5+MhNc7kfi8LdjVw==} + engines: {node: '>= 14.0.0'} - '@algolia/recommend@4.24.0': - resolution: {integrity: sha512-P9kcgerfVBpfYHDfVZDvvdJv0lEoCvzNlOy2nykyt5bK8TyieYyiD0lguIJdRZZYGre03WIAFf14pgE+V+IBlw==} + '@algolia/recommend@5.18.0': + resolution: {integrity: sha512-uSnkm0cdAuFwdMp4pGT5vHVQ84T6AYpTZ3I0b3k/M3wg4zXDhl3aCiY8NzokEyRLezz/kHLEEcgb/tTTobOYVw==} + engines: {node: '>= 14.0.0'} - '@algolia/requester-browser-xhr@4.24.0': - resolution: {integrity: sha512-Z2NxZMb6+nVXSjF13YpjYTdvV3032YTBSGm2vnYvYPA6mMxzM3v5rsCiSspndn9rzIW4Qp1lPHBvuoKJV6jnAA==} + '@algolia/requester-browser-xhr@5.18.0': + resolution: {integrity: sha512-1XFjW0C3pV0dS/9zXbV44cKI+QM4ZIz9cpatXpsjRlq6SUCpLID3DZHsXyE6sTb8IhyPaUjk78GEJT8/3hviqg==} + engines: {node: '>= 14.0.0'} - '@algolia/requester-common@4.24.0': - resolution: {integrity: sha512-k3CXJ2OVnvgE3HMwcojpvY6d9kgKMPRxs/kVohrwF5WMr2fnqojnycZkxPoEg+bXm8fi5BBfFmOqgYztRtHsQA==} + '@algolia/requester-fetch@5.18.0': + resolution: {integrity: sha512-0uodeNdAHz1YbzJh6C5xeQ4T6x5WGiUxUq3GOaT/R4njh5t78dq+Rb187elr7KtnjUmETVVuCvmEYaThfTHzNg==} + engines: {node: '>= 14.0.0'} - '@algolia/requester-node-http@4.24.0': - resolution: {integrity: sha512-JF18yTjNOVYvU/L3UosRcvbPMGT9B+/GQWNWnenIImglzNVGpyzChkXLnrSf6uxwVNO6ESGu6oN8MqcGQcjQJw==} - - '@algolia/transporter@4.24.0': - resolution: {integrity: sha512-86nI7w6NzWxd1Zp9q3413dRshDqAzSbsQjhcDhPIatEFiZrL1/TjnHL8S7jVKFePlIMzDsZWXAXwXzcok9c5oA==} + '@algolia/requester-node-http@5.18.0': + resolution: {integrity: sha512-tZCqDrqJ2YE2I5ukCQrYN8oiF6u3JIdCxrtKq+eniuLkjkO78TKRnXrVcKZTmfFJyyDK8q47SfDcHzAA3nHi6w==} + engines: {node: '>= 14.0.0'} '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@babel/code-frame@7.24.7': - resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.24.7': - resolution: {integrity: sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==} + '@babel/compat-data@7.26.3': + resolution: {integrity: sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==} engines: {node: '>=6.9.0'} - '@babel/core@7.24.7': - resolution: {integrity: sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==} + '@babel/core@7.26.0': + resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} engines: {node: '>=6.9.0'} - '@babel/generator@7.24.7': - resolution: {integrity: sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==} + '@babel/generator@7.26.3': + resolution: {integrity: sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==} engines: {node: '>=6.9.0'} - '@babel/helper-annotate-as-pure@7.24.7': - resolution: {integrity: sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==} + '@babel/helper-annotate-as-pure@7.25.9': + resolution: {integrity: sha512-gv7320KBUFJz1RnylIg5WWYPRXKZ884AGkYpgpWW02TH66Dl+HaC1t1CKd0z3R4b6hdYEcmrNZHUmfCP+1u3/g==} engines: {node: '>=6.9.0'} - '@babel/helper-builder-binary-assignment-operator-visitor@7.24.7': - resolution: {integrity: sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==} + '@babel/helper-compilation-targets@7.25.9': + resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.24.7': - resolution: {integrity: sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-create-class-features-plugin@7.24.7': - resolution: {integrity: sha512-kTkaDl7c9vO80zeX1rJxnuRpEsD5tA81yh11X1gQo+PhSti3JS+7qeZo9U4RHobKRiFPKaGK3svUAeb8D0Q7eg==} + '@babel/helper-create-class-features-plugin@7.25.9': + resolution: {integrity: sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-create-regexp-features-plugin@7.24.7': - resolution: {integrity: sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA==} + '@babel/helper-create-regexp-features-plugin@7.26.3': + resolution: {integrity: sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-define-polyfill-provider@0.6.2': - resolution: {integrity: sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==} + '@babel/helper-define-polyfill-provider@0.6.3': + resolution: {integrity: sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - '@babel/helper-environment-visitor@7.24.7': - resolution: {integrity: sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==} + '@babel/helper-member-expression-to-functions@7.25.9': + resolution: {integrity: sha512-wbfdZ9w5vk0C0oyHqAJbc62+vet5prjj01jjJ8sKn3j9h3MQQlflEdXYvuqRWjHnM12coDEqiC1IRCi0U/EKwQ==} engines: {node: '>=6.9.0'} - '@babel/helper-function-name@7.24.7': - resolution: {integrity: sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==} + '@babel/helper-module-imports@7.25.9': + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} engines: {node: '>=6.9.0'} - '@babel/helper-hoist-variables@7.24.7': - resolution: {integrity: sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-member-expression-to-functions@7.24.7': - resolution: {integrity: sha512-LGeMaf5JN4hAT471eJdBs/GK1DoYIJ5GCtZN/EsL6KUiiDZOvO/eKE11AMZJa2zP4zk4qe9V2O/hxAmkRc8p6w==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.24.7': - resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.24.7': - resolution: {integrity: sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==} + '@babel/helper-module-transforms@7.26.0': + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-optimise-call-expression@7.24.7': - resolution: {integrity: sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==} + '@babel/helper-optimise-call-expression@7.25.9': + resolution: {integrity: sha512-FIpuNaz5ow8VyrYcnXQTDRGvV6tTjkNtCK/RYNDXGSLlUD6cBuQTSw43CShGxjvfBTfcUA/r6UhUCbtYqkhcuQ==} engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@7.24.7': - resolution: {integrity: sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==} + '@babel/helper-plugin-utils@7.25.9': + resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} engines: {node: '>=6.9.0'} - '@babel/helper-remap-async-to-generator@7.24.7': - resolution: {integrity: sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA==} + '@babel/helper-remap-async-to-generator@7.25.9': + resolution: {integrity: sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-replace-supers@7.24.7': - resolution: {integrity: sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg==} + '@babel/helper-replace-supers@7.25.9': + resolution: {integrity: sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-simple-access@7.24.7': - resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} + '@babel/helper-skip-transparent-expression-wrappers@7.25.9': + resolution: {integrity: sha512-K4Du3BFa3gvyhzgPcntrkDgZzQaq6uozzcpGbOO1OEJaI+EJdqWIMTLgFgQf6lrfiDFo5FU+BxKepI9RmZqahA==} engines: {node: '>=6.9.0'} - '@babel/helper-skip-transparent-expression-wrappers@7.24.7': - resolution: {integrity: sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==} + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} engines: {node: '>=6.9.0'} - '@babel/helper-split-export-declaration@7.24.7': - resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.24.7': - resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==} + '@babel/helper-validator-option@7.25.9': + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.24.7': - resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + '@babel/helper-wrap-function@7.25.9': + resolution: {integrity: sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.24.7': - resolution: {integrity: sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==} + '@babel/helpers@7.26.0': + resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} engines: {node: '>=6.9.0'} - '@babel/helper-wrap-function@7.24.7': - resolution: {integrity: sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.24.7': - resolution: {integrity: sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==} - engines: {node: '>=6.9.0'} - - '@babel/highlight@7.24.7': - resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.24.7': - resolution: {integrity: sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==} + '@babel/parser@7.26.3': + resolution: {integrity: sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.24.7': - resolution: {integrity: sha512-TiT1ss81W80eQsN+722OaeQMY/G4yTb4G9JrqeiDADs3N8lbPMGldWi9x8tyqCW5NLx1Jh2AvkE6r6QvEltMMQ==} + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9': + resolution: {integrity: sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.24.7': - resolution: {integrity: sha512-unaQgZ/iRu/By6tsjMZzpeBZjChYfLYry6HrEXPoz3KmfF0sVBQ1l8zKMQ4xRGLWVsjuvB8nQfjNP/DcfEOCsg==} + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9': + resolution: {integrity: sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.24.7': - resolution: {integrity: sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==} + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9': + resolution: {integrity: sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9': + resolution: {integrity: sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.13.0 - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.24.7': - resolution: {integrity: sha512-utA4HuR6F4Vvcr+o4DnjL8fCOlgRFGbeeBEGNg3ZTrLFw6VWG5XmUrvcQ0FjIYMU2ST4XcR2Wsp7t9qOAPnxMg==} + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9': + resolution: {integrity: sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -365,104 +334,31 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-async-generators@7.8.4': - resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-class-properties@7.12.13': - resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-class-static-block@7.14.5': - resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-dynamic-import@7.8.3': resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-export-namespace-from@7.8.3': - resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-import-assertions@7.24.7': - resolution: {integrity: sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==} + '@babel/plugin-syntax-import-assertions@7.26.0': + resolution: {integrity: sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-import-attributes@7.24.7': - resolution: {integrity: sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==} + '@babel/plugin-syntax-import-attributes@7.26.0': + resolution: {integrity: sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-import-meta@7.10.4': - resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-json-strings@7.8.3': - resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-jsx@7.24.7': - resolution: {integrity: sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==} + '@babel/plugin-syntax-jsx@7.25.9': + resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4': - resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': - resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-numeric-separator@7.10.4': - resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-object-rest-spread@7.8.3': - resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-optional-catch-binding@7.8.3': - resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-optional-chaining@7.8.3': - resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-private-property-in-object@7.14.5': - resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-top-level-await@7.14.5': - resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-typescript@7.24.7': - resolution: {integrity: sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==} + '@babel/plugin-syntax-typescript@7.25.9': + resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -473,338 +369,350 @@ packages: peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-transform-arrow-functions@7.24.7': - resolution: {integrity: sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==} + '@babel/plugin-transform-arrow-functions@7.25.9': + resolution: {integrity: sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-async-generator-functions@7.24.7': - resolution: {integrity: sha512-o+iF77e3u7ZS4AoAuJvapz9Fm001PuD2V3Lp6OSE4FYQke+cSewYtnek+THqGRWyQloRCyvWL1OkyfNEl9vr/g==} + '@babel/plugin-transform-async-generator-functions@7.25.9': + resolution: {integrity: sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-async-to-generator@7.24.7': - resolution: {integrity: sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==} + '@babel/plugin-transform-async-to-generator@7.25.9': + resolution: {integrity: sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-block-scoped-functions@7.24.7': - resolution: {integrity: sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==} + '@babel/plugin-transform-block-scoped-functions@7.25.9': + resolution: {integrity: sha512-toHc9fzab0ZfenFpsyYinOX0J/5dgJVA2fm64xPewu7CoYHWEivIWKxkK2rMi4r3yQqLnVmheMXRdG+k239CgA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-block-scoping@7.24.7': - resolution: {integrity: sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ==} + '@babel/plugin-transform-block-scoping@7.25.9': + resolution: {integrity: sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-class-properties@7.24.7': - resolution: {integrity: sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==} + '@babel/plugin-transform-class-properties@7.25.9': + resolution: {integrity: sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-class-static-block@7.24.7': - resolution: {integrity: sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==} + '@babel/plugin-transform-class-static-block@7.26.0': + resolution: {integrity: sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.12.0 - '@babel/plugin-transform-classes@7.24.7': - resolution: {integrity: sha512-CFbbBigp8ln4FU6Bpy6g7sE8B/WmCmzvivzUC6xDAdWVsjYTXijpuuGJmYkAaoWAzcItGKT3IOAbxRItZ5HTjw==} + '@babel/plugin-transform-classes@7.25.9': + resolution: {integrity: sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-computed-properties@7.24.7': - resolution: {integrity: sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==} + '@babel/plugin-transform-computed-properties@7.25.9': + resolution: {integrity: sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-destructuring@7.24.7': - resolution: {integrity: sha512-19eJO/8kdCQ9zISOf+SEUJM/bAUIsvY3YDnXZTupUCQ8LgrWnsG/gFB9dvXqdXnRXMAM8fvt7b0CBKQHNGy1mw==} + '@babel/plugin-transform-destructuring@7.25.9': + resolution: {integrity: sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-dotall-regex@7.24.7': - resolution: {integrity: sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==} + '@babel/plugin-transform-dotall-regex@7.25.9': + resolution: {integrity: sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-duplicate-keys@7.24.7': - resolution: {integrity: sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==} + '@babel/plugin-transform-duplicate-keys@7.25.9': + resolution: {integrity: sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-dynamic-import@7.24.7': - resolution: {integrity: sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-exponentiation-operator@7.24.7': - resolution: {integrity: sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-export-namespace-from@7.24.7': - resolution: {integrity: sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-for-of@7.24.7': - resolution: {integrity: sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-function-name@7.24.7': - resolution: {integrity: sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-json-strings@7.24.7': - resolution: {integrity: sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-literals@7.24.7': - resolution: {integrity: sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-logical-assignment-operators@7.24.7': - resolution: {integrity: sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-member-expression-literals@7.24.7': - resolution: {integrity: sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-modules-amd@7.24.7': - resolution: {integrity: sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-modules-commonjs@7.24.7': - resolution: {integrity: sha512-iFI8GDxtevHJ/Z22J5xQpVqFLlMNstcLXh994xifFwxxGslr2ZXXLWgtBeLctOD63UFDArdvN6Tg8RFw+aEmjQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-modules-systemjs@7.24.7': - resolution: {integrity: sha512-GYQE0tW7YoaN13qFh3O1NCY4MPkUiAH3fiF7UcV/I3ajmDKEdG3l+UOcbAm4zUE3gnvUU+Eni7XrVKo9eO9auw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-modules-umd@7.24.7': - resolution: {integrity: sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-named-capturing-groups-regex@7.24.7': - resolution: {integrity: sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==} + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9': + resolution: {integrity: sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/plugin-transform-new-target@7.24.7': - resolution: {integrity: sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==} + '@babel/plugin-transform-dynamic-import@7.25.9': + resolution: {integrity: sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-nullish-coalescing-operator@7.24.7': - resolution: {integrity: sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==} + '@babel/plugin-transform-exponentiation-operator@7.26.3': + resolution: {integrity: sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-numeric-separator@7.24.7': - resolution: {integrity: sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==} + '@babel/plugin-transform-export-namespace-from@7.25.9': + resolution: {integrity: sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-object-rest-spread@7.24.7': - resolution: {integrity: sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==} + '@babel/plugin-transform-for-of@7.25.9': + resolution: {integrity: sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-object-super@7.24.7': - resolution: {integrity: sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==} + '@babel/plugin-transform-function-name@7.25.9': + resolution: {integrity: sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-optional-catch-binding@7.24.7': - resolution: {integrity: sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==} + '@babel/plugin-transform-json-strings@7.25.9': + resolution: {integrity: sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-optional-chaining@7.24.7': - resolution: {integrity: sha512-tK+0N9yd4j+x/4hxF3F0e0fu/VdcxU18y5SevtyM/PCFlQvXbR0Zmlo2eBrKtVipGNFzpq56o8WsIIKcJFUCRQ==} + '@babel/plugin-transform-literals@7.25.9': + resolution: {integrity: sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-parameters@7.24.7': - resolution: {integrity: sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==} + '@babel/plugin-transform-logical-assignment-operators@7.25.9': + resolution: {integrity: sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-private-methods@7.24.7': - resolution: {integrity: sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==} + '@babel/plugin-transform-member-expression-literals@7.25.9': + resolution: {integrity: sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-private-property-in-object@7.24.7': - resolution: {integrity: sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==} + '@babel/plugin-transform-modules-amd@7.25.9': + resolution: {integrity: sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-property-literals@7.24.7': - resolution: {integrity: sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==} + '@babel/plugin-transform-modules-commonjs@7.26.3': + resolution: {integrity: sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-constant-elements@7.24.7': - resolution: {integrity: sha512-7LidzZfUXyfZ8/buRW6qIIHBY8wAZ1OrY9c/wTr8YhZ6vMPo+Uc/CVFLYY1spZrEQlD4w5u8wjqk5NQ3OVqQKA==} + '@babel/plugin-transform-modules-systemjs@7.25.9': + resolution: {integrity: sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-display-name@7.24.7': - resolution: {integrity: sha512-H/Snz9PFxKsS1JLI4dJLtnJgCJRoo0AUm3chP6NYr+9En1JMKloheEiLIhlp5MDVznWo+H3AAC1Mc8lmUEpsgg==} + '@babel/plugin-transform-modules-umd@7.25.9': + resolution: {integrity: sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-react-jsx-development@7.24.7': - resolution: {integrity: sha512-QG9EnzoGn+Qar7rxuW+ZOsbWOt56FvvI93xInqsZDC5fsekx1AlIO4KIJ5M+D0p0SqSH156EpmZyXq630B8OlQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx@7.24.7': - resolution: {integrity: sha512-+Dj06GDZEFRYvclU6k4bme55GKBEWUmByM/eoKuqg4zTNQHiApWRhQph5fxQB2wAEFvRzL1tOEj1RJ19wJrhoA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-pure-annotations@7.24.7': - resolution: {integrity: sha512-PLgBVk3fzbmEjBJ/u8kFzOqS9tUeDjiaWud/rRym/yjCo/M9cASPlnrd2ZmmZpQT40fOOrvR8jh+n8jikrOhNA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-regenerator@7.24.7': - resolution: {integrity: sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-reserved-words@7.24.7': - resolution: {integrity: sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-runtime@7.24.7': - resolution: {integrity: sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-shorthand-properties@7.24.7': - resolution: {integrity: sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-spread@7.24.7': - resolution: {integrity: sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-sticky-regex@7.24.7': - resolution: {integrity: sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-template-literals@7.24.7': - resolution: {integrity: sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-typeof-symbol@7.24.7': - resolution: {integrity: sha512-VtR8hDy7YLB7+Pet9IarXjg/zgCMSF+1mNS/EQEiEaUPoFXCVsHG64SIxcaaI2zJgRiv+YmgaQESUfWAdbjzgg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-typescript@7.24.7': - resolution: {integrity: sha512-iLD3UNkgx2n/HrjBesVbYX6j0yqn/sJktvbtKKgcaLIQ4bTTQ8obAypc1VpyHPD2y4Phh9zHOaAt8e/L14wCpw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-unicode-escapes@7.24.7': - resolution: {integrity: sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-unicode-property-regex@7.24.7': - resolution: {integrity: sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-unicode-regex@7.24.7': - resolution: {integrity: sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-unicode-sets-regex@7.24.7': - resolution: {integrity: sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==} + '@babel/plugin-transform-named-capturing-groups-regex@7.25.9': + resolution: {integrity: sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/preset-env@7.24.7': - resolution: {integrity: sha512-1YZNsc+y6cTvWlDHidMBsQZrZfEFjRIo/BZCT906PMdzOyXtSLTgqGdrpcuTDCXyd11Am5uQULtDIcCfnTc8fQ==} + '@babel/plugin-transform-new-target@7.25.9': + resolution: {integrity: sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-nullish-coalescing-operator@7.25.9': + resolution: {integrity: sha512-ENfftpLZw5EItALAD4WsY/KUWvhUlZndm5GC7G3evUsVeSJB6p0pBeLQUnRnBCBx7zV0RKQjR9kCuwrsIrjWog==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-numeric-separator@7.25.9': + resolution: {integrity: sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-rest-spread@7.25.9': + resolution: {integrity: sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-super@7.25.9': + resolution: {integrity: sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-catch-binding@7.25.9': + resolution: {integrity: sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-chaining@7.25.9': + resolution: {integrity: sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-parameters@7.25.9': + resolution: {integrity: sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-methods@7.25.9': + resolution: {integrity: sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-property-in-object@7.25.9': + resolution: {integrity: sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-property-literals@7.25.9': + resolution: {integrity: sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-constant-elements@7.25.9': + resolution: {integrity: sha512-Ncw2JFsJVuvfRsa2lSHiC55kETQVLSnsYGQ1JDDwkUeWGTL/8Tom8aLTnlqgoeuopWrbbGndrc9AlLYrIosrow==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-display-name@7.25.9': + resolution: {integrity: sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-development@7.25.9': + resolution: {integrity: sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx@7.25.9': + resolution: {integrity: sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-pure-annotations@7.25.9': + resolution: {integrity: sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regenerator@7.25.9': + resolution: {integrity: sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regexp-modifiers@7.26.0': + resolution: {integrity: sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-reserved-words@7.25.9': + resolution: {integrity: sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-runtime@7.25.9': + resolution: {integrity: sha512-nZp7GlEl+yULJrClz0SwHPqir3lc0zsPrDHQUcxGspSL7AKrexNSEfTbfqnDNJUO13bgKyfuOLMF8Xqtu8j3YQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-shorthand-properties@7.25.9': + resolution: {integrity: sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-spread@7.25.9': + resolution: {integrity: sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-sticky-regex@7.25.9': + resolution: {integrity: sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-template-literals@7.25.9': + resolution: {integrity: sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typeof-symbol@7.25.9': + resolution: {integrity: sha512-v61XqUMiueJROUv66BVIOi0Fv/CUuZuZMl5NkRoCVxLAnMexZ0A3kMe7vvZ0nulxMuMp0Mk6S5hNh48yki08ZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.26.3': + resolution: {integrity: sha512-6+5hpdr6mETwSKjmJUdYw0EIkATiQhnELWlE3kJFBwSg/BGIVwVaVbX+gOXBCdc7Ln1RXZxyWGecIXhUfnl7oA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-escapes@7.25.9': + resolution: {integrity: sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-property-regex@7.25.9': + resolution: {integrity: sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-regex@7.25.9': + resolution: {integrity: sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-sets-regex@7.25.9': + resolution: {integrity: sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/preset-env@7.26.0': + resolution: {integrity: sha512-H84Fxq0CQJNdPFT2DrfnylZ3cf5K43rGfWK4LJGPpjKHiZlk0/RzwEus3PDDZZg+/Er7lCA03MVacueUuXdzfw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -814,39 +722,36 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 - '@babel/preset-react@7.24.7': - resolution: {integrity: sha512-AAH4lEkpmzFWrGVlHaxJB7RLH21uPQ9+He+eFLWHmF9IuFQVugz8eAsamaW0DXRrTfco5zj1wWtpdcXJUOfsag==} + '@babel/preset-react@7.26.3': + resolution: {integrity: sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/preset-typescript@7.24.7': - resolution: {integrity: sha512-SyXRe3OdWwIwalxDg5UtJnJQO+YPcTfwiIY2B0Xlddh9o7jpWLvv8X1RthIeDOxQ+O1ML5BLPCONToObyVQVuQ==} + '@babel/preset-typescript@7.26.0': + resolution: {integrity: sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/regjsgen@0.8.0': - resolution: {integrity: sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==} - - '@babel/runtime-corejs3@7.24.7': - resolution: {integrity: sha512-eytSX6JLBY6PVAeQa2bFlDx/7Mmln/gaEpsit5a3WEvjGfiIytEsgAwuIXCPM0xvw0v0cJn3ilq0/TvXrW0kgA==} + '@babel/runtime-corejs3@7.26.0': + resolution: {integrity: sha512-YXHu5lN8kJCb1LOb9PgV6pvak43X2h4HvRApcN5SdWeaItQOzfn1hgP6jasD6KWQyJDBxrVmA9o9OivlnNJK/w==} engines: {node: '>=6.9.0'} - '@babel/runtime@7.24.7': - resolution: {integrity: sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==} + '@babel/runtime@7.26.0': + resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} engines: {node: '>=6.9.0'} - '@babel/template@7.24.7': - resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==} + '@babel/template@7.25.9': + resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.24.7': - resolution: {integrity: sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==} + '@babel/traverse@7.26.4': + resolution: {integrity: sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==} engines: {node: '>=6.9.0'} - '@babel/types@7.24.7': - resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==} + '@babel/types@7.26.3': + resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==} engines: {node: '>=6.9.0'} '@cfaester/enzyme-adapter-react-18@0.8.0': @@ -860,15 +765,267 @@ packages: resolution: {integrity: sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==} engines: {node: '>=0.1.90'} + '@csstools/cascade-layer-name-parser@2.0.4': + resolution: {integrity: sha512-7DFHlPuIxviKYZrOiwVU/PiHLm3lLUR23OMuEEtfEOQTOp9hzQ2JjdY6X5H18RVuUPJqSCI+qNnD5iOLMVE0bA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/color-helpers@5.0.1': + resolution: {integrity: sha512-MKtmkA0BX87PKaO1NFRTFH+UnkgnmySQOvNxJubsadusqPEC2aJ9MOQiMceZJJ6oitUl/i0L6u0M1IrmAOmgBA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.1': + resolution: {integrity: sha512-rL7kaUnTkL9K+Cvo2pnCieqNpTKgQzy5f+N+5Iuko9HAoasP+xgprVh7KN/MaJVvVL1l0EzQq2MoqBHKSrDrag==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/css-color-parser@3.0.7': + resolution: {integrity: sha512-nkMp2mTICw32uE5NN+EsJ4f5N+IGFeCFu4bGpiKgb2Pq/7J/MpyLBeQ5ry4KKtRFZaYs6sTmcMYrSRIyj5DFKA==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/css-parser-algorithms@3.0.4': + resolution: {integrity: sha512-Up7rBoV77rv29d3uKHUIVubz1BTcgyUK72IvCQAbfbMv584xHcGKCKbWh7i8hPrRJ7qU4Y8IO3IY9m+iTB7P3A==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/css-tokenizer@3.0.3': + resolution: {integrity: sha512-UJnjoFsmxfKUdNYdWgOB0mWUypuLvAfQPH1+pyvRJs6euowbFkFC6P13w1l8mJyi3vxYMxc9kld5jZEGRQs6bw==} + engines: {node: '>=18'} + + '@csstools/media-query-list-parser@4.0.2': + resolution: {integrity: sha512-EUos465uvVvMJehckATTlNqGj4UJWkTmdWuDMjqvSUkjGpmOyFZBVwb4knxCm/k2GMTXY+c/5RkdndzFYWeX5A==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.4 + '@csstools/css-tokenizer': ^3.0.3 + + '@csstools/postcss-cascade-layers@5.0.1': + resolution: {integrity: sha512-XOfhI7GShVcKiKwmPAnWSqd2tBR0uxt+runAxttbSp/LY2U16yAVPmAf7e9q4JJ0d+xMNmpwNDLBXnmRCl3HMQ==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-color-function@4.0.7': + resolution: {integrity: sha512-aDHYmhNIHR6iLw4ElWhf+tRqqaXwKnMl0YsQ/X105Zc4dQwe6yJpMrTN6BwOoESrkDjOYMOfORviSSLeDTJkdQ==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-color-mix-function@3.0.7': + resolution: {integrity: sha512-e68Nev4CxZYCLcrfWhHH4u/N1YocOfTmw67/kVX5Rb7rnguqqLyxPjhHWjSBX8o4bmyuukmNf3wrUSU3//kT7g==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-content-alt-text@2.0.4': + resolution: {integrity: sha512-YItlZUOuZJCBlRaCf8Aucc1lgN41qYGALMly0qQllrxYJhiyzlI6RxOTMUvtWk+KhS8GphMDsDhKQ7KTPfEMSw==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-exponential-functions@2.0.6': + resolution: {integrity: sha512-IgJA5DQsQLu/upA3HcdvC6xEMR051ufebBTIXZ5E9/9iiaA7juXWz1ceYj814lnDYP/7eWjZnw0grRJlX4eI6g==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-font-format-keywords@4.0.0': + resolution: {integrity: sha512-usBzw9aCRDvchpok6C+4TXC57btc4bJtmKQWOHQxOVKen1ZfVqBUuCZ/wuqdX5GHsD0NRSr9XTP+5ID1ZZQBXw==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-gamut-mapping@2.0.7': + resolution: {integrity: sha512-gzFEZPoOkY0HqGdyeBXR3JP218Owr683u7KOZazTK7tQZBE8s2yhg06W1tshOqk7R7SWvw9gkw2TQogKpIW8Xw==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-gradients-interpolation-method@5.0.7': + resolution: {integrity: sha512-WgEyBeg6glUeTdS2XT7qeTFBthTJuXlS9GFro/DVomj7W7WMTamAwpoP4oQCq/0Ki2gvfRYFi/uZtmRE14/DFA==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-hwb-function@4.0.7': + resolution: {integrity: sha512-LKYqjO+wGwDCfNIEllessCBWfR4MS/sS1WXO+j00KKyOjm7jDW2L6jzUmqASEiv/kkJO39GcoIOvTTfB3yeBUA==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-ic-unit@4.0.0': + resolution: {integrity: sha512-9QT5TDGgx7wD3EEMN3BSUG6ckb6Eh5gSPT5kZoVtUuAonfPmLDJyPhqR4ntPpMYhUKAMVKAg3I/AgzqHMSeLhA==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-initial@2.0.0': + resolution: {integrity: sha512-dv2lNUKR+JV+OOhZm9paWzYBXOCi+rJPqJ2cJuhh9xd8USVrd0cBEPczla81HNOyThMQWeCcdln3gZkQV2kYxA==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-is-pseudo-class@5.0.1': + resolution: {integrity: sha512-JLp3POui4S1auhDR0n8wHd/zTOWmMsmK3nQd3hhL6FhWPaox5W7j1se6zXOG/aP07wV2ww0lxbKYGwbBszOtfQ==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-light-dark-function@2.0.7': + resolution: {integrity: sha512-ZZ0rwlanYKOHekyIPaU+sVm3BEHCe+Ha0/px+bmHe62n0Uc1lL34vbwrLYn6ote8PHlsqzKeTQdIejQCJ05tfw==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-logical-float-and-clear@3.0.0': + resolution: {integrity: sha512-SEmaHMszwakI2rqKRJgE+8rpotFfne1ZS6bZqBoQIicFyV+xT1UF42eORPxJkVJVrH9C0ctUgwMSn3BLOIZldQ==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-logical-overflow@2.0.0': + resolution: {integrity: sha512-spzR1MInxPuXKEX2csMamshR4LRaSZ3UXVaRGjeQxl70ySxOhMpP2252RAFsg8QyyBXBzuVOOdx1+bVO5bPIzA==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-logical-overscroll-behavior@2.0.0': + resolution: {integrity: sha512-e/webMjoGOSYfqLunyzByZj5KKe5oyVg/YSbie99VEaSDE2kimFm0q1f6t/6Jo+VVCQ/jbe2Xy+uX+C4xzWs4w==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-logical-resize@3.0.0': + resolution: {integrity: sha512-DFbHQOFW/+I+MY4Ycd/QN6Dg4Hcbb50elIJCfnwkRTCX05G11SwViI5BbBlg9iHRl4ytB7pmY5ieAFk3ws7yyg==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-logical-viewport-units@3.0.3': + resolution: {integrity: sha512-OC1IlG/yoGJdi0Y+7duz/kU/beCwO+Gua01sD6GtOtLi7ByQUpcIqs7UE/xuRPay4cHgOMatWdnDdsIDjnWpPw==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-media-minmax@2.0.6': + resolution: {integrity: sha512-J1+4Fr2W3pLZsfxkFazK+9kr96LhEYqoeBszLmFjb6AjYs+g9oDAw3J5oQignLKk3rC9XHW+ebPTZ9FaW5u5pg==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.4': + resolution: {integrity: sha512-AnGjVslHMm5xw9keusQYvjVWvuS7KWK+OJagaG0+m9QnIjZsrysD2kJP/tr/UJIyYtMCtu8OkUd+Rajb4DqtIQ==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-nested-calc@4.0.0': + resolution: {integrity: sha512-jMYDdqrQQxE7k9+KjstC3NbsmC063n1FTPLCgCRS2/qHUbHM0mNy9pIn4QIiQGs9I/Bg98vMqw7mJXBxa0N88A==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-normalize-display-values@4.0.0': + resolution: {integrity: sha512-HlEoG0IDRoHXzXnkV4in47dzsxdsjdz6+j7MLjaACABX2NfvjFS6XVAnpaDyGesz9gK2SC7MbNwdCHusObKJ9Q==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-oklab-function@4.0.7': + resolution: {integrity: sha512-I6WFQIbEKG2IO3vhaMGZDkucbCaUSXMxvHNzDdnfsTCF5tc0UlV3Oe2AhamatQoKFjBi75dSEMrgWq3+RegsOQ==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-progressive-custom-properties@4.0.0': + resolution: {integrity: sha512-XQPtROaQjomnvLUSy/bALTR5VCtTVUFwYs1SblvYgLSeTo2a/bMNwUwo2piXw5rTv/FEYiy5yPSXBqg9OKUx7Q==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-random-function@1.0.2': + resolution: {integrity: sha512-vBCT6JvgdEkvRc91NFoNrLjgGtkLWt47GKT6E2UDn3nd8ZkMBiziQ1Md1OiKoSsgzxsSnGKG3RVdhlbdZEkHjA==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-relative-color-syntax@3.0.7': + resolution: {integrity: sha512-apbT31vsJVd18MabfPOnE977xgct5B1I+Jpf+Munw3n6kKb1MMuUmGGH+PT9Hm/fFs6fe61Q/EWnkrb4bNoNQw==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-scope-pseudo-class@4.0.1': + resolution: {integrity: sha512-IMi9FwtH6LMNuLea1bjVMQAsUhFxJnyLSgOp/cpv5hrzWmrUYU5fm0EguNDIIOHUqzXode8F/1qkC/tEo/qN8Q==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-sign-functions@1.1.1': + resolution: {integrity: sha512-MslYkZCeMQDxetNkfmmQYgKCy4c+w9pPDfgOBCJOo/RI1RveEUdZQYtOfrC6cIZB7sD7/PHr2VGOcMXlZawrnA==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-stepped-value-functions@4.0.6': + resolution: {integrity: sha512-/dwlO9w8vfKgiADxpxUbZOWlL5zKoRIsCymYoh1IPuBsXODKanKnfuZRr32DEqT0//3Av1VjfNZU9yhxtEfIeA==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-text-decoration-shorthand@4.0.1': + resolution: {integrity: sha512-xPZIikbx6jyzWvhms27uugIc0I4ykH4keRvoa3rxX5K7lEhkbd54rjj/dv60qOCTisoS+3bmwJTeyV1VNBrXaw==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-trigonometric-functions@4.0.6': + resolution: {integrity: sha512-c4Y1D2Why/PeccaSouXnTt6WcNHJkoJRidV2VW9s5gJ97cNxnLgQ4Qj8qOqkIR9VmTQKJyNcbF4hy79ZQnWD7A==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/postcss-unset-value@4.0.0': + resolution: {integrity: sha512-cBz3tOCI5Fw6NIFEwU3RiwK6mn3nKegjpJuzCndoGq3BZPkUjnsq7uQmIeMNeMbMk7YD2MfKcgCpZwX5jyXqCA==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + '@csstools/selector-resolve-nested@3.0.0': + resolution: {integrity: sha512-ZoK24Yku6VJU1gS79a5PFmC8yn3wIapiKmPgun0hZgEI5AOqgH2kiPRsPz1qkGv4HL+wuDLH83yQyk6inMYrJQ==} + engines: {node: '>=18'} + peerDependencies: + postcss-selector-parser: ^7.0.0 + + '@csstools/selector-specificity@5.0.0': + resolution: {integrity: sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==} + engines: {node: '>=18'} + peerDependencies: + postcss-selector-parser: ^7.0.0 + + '@csstools/utilities@2.0.0': + resolution: {integrity: sha512-5VdOr0Z71u+Yp3ozOx8T11N703wIFGVRgOWbOZMKgglPJsWA54MRIoMNVMa7shUToIhx5J8vX4sOZgD2XiihiQ==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + '@discoveryjs/json-ext@0.5.7': resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} engines: {node: '>=10.0.0'} - '@docsearch/css@3.6.0': - resolution: {integrity: sha512-+sbxb71sWre+PwDK7X2T8+bhS6clcVMLwBPznX45Qu6opJcgRjAp7gYSDzVFp187J+feSj5dNBN1mJoi6ckkUQ==} + '@docsearch/css@3.8.2': + resolution: {integrity: sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==} - '@docsearch/react@3.6.0': - resolution: {integrity: sha512-HUFut4ztcVNmqy9gp/wxNbC7pTOHhgVVkHVGCACTuLhUKUhKAF9KYHJtMiLUJxEqiFLQiuri1fWF8zqwM/cu1w==} + '@docsearch/react@3.8.2': + resolution: {integrity: sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==} peerDependencies: '@types/react': '>= 16.8.0 < 19.0.0' react: '>= 16.8.0 < 19.0.0' @@ -884,175 +1041,194 @@ packages: search-insights: optional: true - '@docusaurus/core@3.4.0': - resolution: {integrity: sha512-g+0wwmN2UJsBqy2fQRQ6fhXruoEa62JDeEa5d8IdTJlMoaDaEDfHh7WjwGRn4opuTQWpjAwP/fbcgyHKlE+64w==} + '@docusaurus/babel@3.7.0': + resolution: {integrity: sha512-0H5uoJLm14S/oKV3Keihxvh8RV+vrid+6Gv+2qhuzbqHanawga8tYnsdpjEyt36ucJjqlby2/Md2ObWjA02UXQ==} + engines: {node: '>=18.0'} + + '@docusaurus/bundler@3.7.0': + resolution: {integrity: sha512-CUUT9VlSGukrCU5ctZucykvgCISivct+cby28wJwCC/fkQFgAHRp/GKv2tx38ZmXb7nacrKzFTcp++f9txUYGg==} + engines: {node: '>=18.0'} + peerDependencies: + '@docusaurus/faster': '*' + peerDependenciesMeta: + '@docusaurus/faster': + optional: true + + '@docusaurus/core@3.7.0': + resolution: {integrity: sha512-b0fUmaL+JbzDIQaamzpAFpTviiaU4cX3Qz8cuo14+HGBCwa0evEK0UYCBFY3n4cLzL8Op1BueeroUD2LYAIHbQ==} engines: {node: '>=18.0'} hasBin: true peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 + '@mdx-js/react': ^3.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/cssnano-preset@3.4.0': - resolution: {integrity: sha512-qwLFSz6v/pZHy/UP32IrprmH5ORce86BGtN0eBtG75PpzQJAzp9gefspox+s8IEOr0oZKuQ/nhzZ3xwyc3jYJQ==} + '@docusaurus/cssnano-preset@3.7.0': + resolution: {integrity: sha512-X9GYgruZBSOozg4w4dzv9uOz8oK/EpPVQXkp0MM6Tsgp/nRIU9hJzJ0Pxg1aRa3xCeEQTOimZHcocQFlLwYajQ==} engines: {node: '>=18.0'} - '@docusaurus/logger@3.4.0': - resolution: {integrity: sha512-bZwkX+9SJ8lB9kVRkXw+xvHYSMGG4bpYHKGXeXFvyVc79NMeeBSGgzd4TQLHH+DYeOJoCdl8flrFJVxlZ0wo/Q==} - engines: {node: '>=18.0'} - - '@docusaurus/mdx-loader@3.4.0': - resolution: {integrity: sha512-kSSbrrk4nTjf4d+wtBA9H+FGauf2gCax89kV8SUSJu3qaTdSIKdWERlngsiHaCFgZ7laTJ8a67UFf+xlFPtuTw==} + '@docusaurus/faster@3.7.0': + resolution: {integrity: sha512-d+7uyOEs3SBk38i2TL79N6mFaP7J4knc5lPX/W9od+jplXZhnDdl5ZMh2u2Lg7JxGV/l33Bd7h/xwv4mr21zag==} engines: {node: '>=18.0'} peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 + '@docusaurus/types': '*' - '@docusaurus/module-type-aliases@3.4.0': - resolution: {integrity: sha512-A1AyS8WF5Bkjnb8s+guTDuYmUiwJzNrtchebBHpc0gz0PyHJNMaybUlSrmJjHVcGrya0LKI4YcR3lBDQfXRYLw==} + '@docusaurus/logger@3.7.0': + resolution: {integrity: sha512-z7g62X7bYxCYmeNNuO9jmzxLQG95q9QxINCwpboVcNff3SJiHJbGrarxxOVMVmAh1MsrSfxWkVGv4P41ktnFsA==} + engines: {node: '>=18.0'} + + '@docusaurus/mdx-loader@3.7.0': + resolution: {integrity: sha512-OFBG6oMjZzc78/U3WNPSHs2W9ZJ723ewAcvVJaqS0VgyeUfmzUV8f1sv+iUHA0DtwiR5T5FjOxj6nzEE8LY6VA==} + engines: {node: '>=18.0'} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + '@docusaurus/module-type-aliases@3.7.0': + resolution: {integrity: sha512-g7WdPqDNaqA60CmBrr0cORTrsOit77hbsTj7xE2l71YhBn79sxdm7WMK7wfhcaafkbpIh7jv5ef5TOpf1Xv9Lg==} peerDependencies: react: '*' react-dom: '*' - '@docusaurus/plugin-content-blog@3.4.0': - resolution: {integrity: sha512-vv6ZAj78ibR5Jh7XBUT4ndIjmlAxkijM3Sx5MAAzC1gyv0vupDQNhzuFg1USQmQVj3P5I6bquk12etPV3LJ+Xw==} + '@docusaurus/plugin-content-blog@3.7.0': + resolution: {integrity: sha512-EFLgEz6tGHYWdPU0rK8tSscZwx+AsyuBW/r+tNig2kbccHYGUJmZtYN38GjAa3Fda4NU+6wqUO5kTXQSRBQD3g==} engines: {node: '>=18.0'} peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 + '@docusaurus/plugin-content-docs': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-content-docs@3.4.0': - resolution: {integrity: sha512-HkUCZffhBo7ocYheD9oZvMcDloRnGhBMOZRyVcAQRFmZPmNqSyISlXA1tQCIxW+r478fty97XXAGjNYzBjpCsg==} + '@docusaurus/plugin-content-docs@3.7.0': + resolution: {integrity: sha512-GXg5V7kC9FZE4FkUZA8oo/NrlRb06UwuICzI6tcbzj0+TVgjq/mpUXXzSgKzMS82YByi4dY2Q808njcBCyy6tQ==} engines: {node: '>=18.0'} peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-content-pages@3.4.0': - resolution: {integrity: sha512-h2+VN/0JjpR8fIkDEAoadNjfR3oLzB+v1qSXbIAKjQ46JAHx3X22n9nqS+BWSQnTnp1AjkjSvZyJMekmcwxzxg==} + '@docusaurus/plugin-content-pages@3.7.0': + resolution: {integrity: sha512-YJSU3tjIJf032/Aeao8SZjFOrXJbz/FACMveSMjLyMH4itQyZ2XgUIzt4y+1ISvvk5zrW4DABVT2awTCqBkx0Q==} engines: {node: '>=18.0'} peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-debug@3.4.0': - resolution: {integrity: sha512-uV7FDUNXGyDSD3PwUaf5YijX91T5/H9SX4ErEcshzwgzWwBtK37nUWPU3ZLJfeTavX3fycTOqk9TglpOLaWkCg==} + '@docusaurus/plugin-debug@3.7.0': + resolution: {integrity: sha512-Qgg+IjG/z4svtbCNyTocjIwvNTNEwgRjSXXSJkKVG0oWoH0eX/HAPiu+TS1HBwRPQV+tTYPWLrUypYFepfujZA==} engines: {node: '>=18.0'} peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-google-analytics@3.4.0': - resolution: {integrity: sha512-mCArluxEGi3cmYHqsgpGGt3IyLCrFBxPsxNZ56Mpur0xSlInnIHoeLDH7FvVVcPJRPSQ9/MfRqLsainRw+BojA==} + '@docusaurus/plugin-google-analytics@3.7.0': + resolution: {integrity: sha512-otIqiRV/jka6Snjf+AqB360XCeSv7lQC+DKYW+EUZf6XbuE8utz5PeUQ8VuOcD8Bk5zvT1MC4JKcd5zPfDuMWA==} engines: {node: '>=18.0'} peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-google-gtag@3.4.0': - resolution: {integrity: sha512-Dsgg6PLAqzZw5wZ4QjUYc8Z2KqJqXxHxq3vIoyoBWiLEEfigIs7wHR+oiWUQy3Zk9MIk6JTYj7tMoQU0Jm3nqA==} + '@docusaurus/plugin-google-gtag@3.7.0': + resolution: {integrity: sha512-M3vrMct1tY65ModbyeDaMoA+fNJTSPe5qmchhAbtqhDD/iALri0g9LrEpIOwNaoLmm6lO88sfBUADQrSRSGSWA==} engines: {node: '>=18.0'} peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-google-tag-manager@3.4.0': - resolution: {integrity: sha512-O9tX1BTwxIhgXpOLpFDueYA9DWk69WCbDRrjYoMQtFHSkTyE7RhNgyjSPREUWJb9i+YUg3OrsvrBYRl64FCPCQ==} + '@docusaurus/plugin-google-tag-manager@3.7.0': + resolution: {integrity: sha512-X8U78nb8eiMiPNg3jb9zDIVuuo/rE1LjGDGu+5m5CX4UBZzjMy+klOY2fNya6x8ACyE/L3K2erO1ErheP55W/w==} engines: {node: '>=18.0'} peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/plugin-sitemap@3.4.0': - resolution: {integrity: sha512-+0VDvx9SmNrFNgwPoeoCha+tRoAjopwT0+pYO1xAbyLcewXSemq+eLxEa46Q1/aoOaJQ0qqHELuQM7iS2gp33Q==} + '@docusaurus/plugin-sitemap@3.7.0': + resolution: {integrity: sha512-bTRT9YLZ/8I/wYWKMQke18+PF9MV8Qub34Sku6aw/vlZ/U+kuEuRpQ8bTcNOjaTSfYsWkK4tTwDMHK2p5S86cA==} engines: {node: '>=18.0'} peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/preset-classic@3.4.0': - resolution: {integrity: sha512-Ohj6KB7siKqZaQhNJVMBBUzT3Nnp6eTKqO+FXO3qu/n1hJl3YLwVKTWBg28LF7MWrKu46UuYavwMRxud0VyqHg==} + '@docusaurus/plugin-svgr@3.7.0': + resolution: {integrity: sha512-HByXIZTbc4GV5VAUkZ2DXtXv1Qdlnpk3IpuImwSnEzCDBkUMYcec5282hPjn6skZqB25M1TYCmWS91UbhBGxQg==} engines: {node: '>=18.0'} peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + + '@docusaurus/preset-classic@3.7.0': + resolution: {integrity: sha512-nPHj8AxDLAaQXs+O6+BwILFuhiWbjfQWrdw2tifOClQoNfuXDjfjogee6zfx6NGHWqshR23LrcN115DmkHC91Q==} + engines: {node: '>=18.0'} + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 '@docusaurus/react-loadable@6.0.0': resolution: {integrity: sha512-YMMxTUQV/QFSnbgrP3tjDzLHRg7vsbMn8e9HAa8o/1iXoiomo48b7sk/kkmWEuWNDPJVlKSJRB6Y2fHqdJk+SQ==} peerDependencies: react: '*' - '@docusaurus/theme-classic@3.4.0': - resolution: {integrity: sha512-0IPtmxsBYv2adr1GnZRdMkEQt1YW6tpzrUPj02YxNpvJ5+ju4E13J5tB4nfdaen/tfR1hmpSPlTFPvTf4kwy8Q==} + '@docusaurus/theme-classic@3.7.0': + resolution: {integrity: sha512-MnLxG39WcvLCl4eUzHr0gNcpHQfWoGqzADCly54aqCofQX6UozOS9Th4RK3ARbM9m7zIRv3qbhggI53dQtx/hQ==} engines: {node: '>=18.0'} peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/theme-common@3.4.0': - resolution: {integrity: sha512-0A27alXuv7ZdCg28oPE8nH/Iz73/IUejVaCazqu9elS4ypjiLhK3KfzdSQBnL/g7YfHSlymZKdiOHEo8fJ0qMA==} + '@docusaurus/theme-common@3.7.0': + resolution: {integrity: sha512-8eJ5X0y+gWDsURZnBfH0WabdNm8XMCXHv8ENy/3Z/oQKwaB/EHt5lP9VsTDTf36lKEp0V6DjzjFyFIB+CetL0A==} engines: {node: '>=18.0'} peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 + '@docusaurus/plugin-content-docs': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/theme-search-algolia@3.4.0': - resolution: {integrity: sha512-aiHFx7OCw4Wck1z6IoShVdUWIjntC8FHCw9c5dR8r3q4Ynh+zkS8y2eFFunN/DL6RXPzpnvKCg3vhLQYJDmT9Q==} + '@docusaurus/theme-search-algolia@3.7.0': + resolution: {integrity: sha512-Al/j5OdzwRU1m3falm+sYy9AaB93S1XF1Lgk9Yc6amp80dNxJVplQdQTR4cYdzkGtuQqbzUA8+kaoYYO0RbK6g==} engines: {node: '>=18.0'} peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/theme-translations@3.4.0': - resolution: {integrity: sha512-zSxCSpmQCCdQU5Q4CnX/ID8CSUUI3fvmq4hU/GNP/XoAWtXo9SAVnM3TzpU8Gb//H3WCsT8mJcTfyOk3d9ftNg==} + '@docusaurus/theme-translations@3.7.0': + resolution: {integrity: sha512-Ewq3bEraWDmienM6eaNK7fx+/lHMtGDHQyd1O+4+3EsDxxUmrzPkV7Ct3nBWTuE0MsoZr3yNwQVKjllzCMuU3g==} engines: {node: '>=18.0'} - '@docusaurus/tsconfig@3.4.0': - resolution: {integrity: sha512-0qENiJ+TRaeTzcg4olrnh0BQ7eCxTgbYWBnWUeQDc84UYkt/T3pDNnm3SiQkqPb+YQ1qtYFlC0RriAElclo8Dg==} + '@docusaurus/tsconfig@3.7.0': + resolution: {integrity: sha512-vRsyj3yUZCjscgfgcFYjIsTcAru/4h4YH2/XAE8Rs7wWdnng98PgWKvP5ovVc4rmRpRg2WChVW0uOy2xHDvDBQ==} - '@docusaurus/types@3.4.0': - resolution: {integrity: sha512-4jcDO8kXi5Cf9TcyikB/yKmz14f2RZ2qTRerbHAsS+5InE9ZgSLBNLsewtFTcTOXSVcbU3FoGOzcNWAmU1TR0A==} + '@docusaurus/types@3.7.0': + resolution: {integrity: sha512-kOmZg5RRqJfH31m+6ZpnwVbkqMJrPOG5t0IOl4i/+3ruXyNfWzZ0lVtVrD0u4ONc/0NOsS9sWYaxxWNkH1LdLQ==} peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 - '@docusaurus/utils-common@3.4.0': - resolution: {integrity: sha512-NVx54Wr4rCEKsjOH5QEVvxIqVvm+9kh7q8aYTU5WzUU9/Hctd6aTrcZ3G0Id4zYJ+AeaG5K5qHA4CY5Kcm2iyQ==} - engines: {node: '>=18.0'} - peerDependencies: - '@docusaurus/types': '*' - peerDependenciesMeta: - '@docusaurus/types': - optional: true - - '@docusaurus/utils-validation@3.4.0': - resolution: {integrity: sha512-hYQ9fM+AXYVTWxJOT1EuNaRnrR2WGpRdLDQG07O8UOpsvCPWUVOeo26Rbm0JWY2sGLfzAb+tvJ62yF+8F+TV0g==} + '@docusaurus/utils-common@3.7.0': + resolution: {integrity: sha512-IZeyIfCfXy0Mevj6bWNg7DG7B8G+S6o6JVpddikZtWyxJguiQ7JYr0SIZ0qWd8pGNuMyVwriWmbWqMnK7Y5PwA==} engines: {node: '>=18.0'} - '@docusaurus/utils@3.4.0': - resolution: {integrity: sha512-fRwnu3L3nnWaXOgs88BVBmG1yGjcQqZNHG+vInhEa2Sz2oQB+ZjbEMO5Rh9ePFpZ0YDiDUhpaVjwmS+AU2F14g==} + '@docusaurus/utils-validation@3.7.0': + resolution: {integrity: sha512-w8eiKk8mRdN+bNfeZqC4nyFoxNyI1/VExMKAzD9tqpJfLLbsa46Wfn5wcKH761g9WkKh36RtFV49iL9lh1DYBA==} + engines: {node: '>=18.0'} + + '@docusaurus/utils@3.7.0': + resolution: {integrity: sha512-e7zcB6TPnVzyUaHMJyLSArKa2AG3h9+4CfvKXKKWNx6hRs+p0a+u7HHTJBgo6KW2m+vqDnuIHK4X+bhmoghAFA==} engines: {node: '>=18.0'} - peerDependencies: - '@docusaurus/types': '*' - peerDependenciesMeta: - '@docusaurus/types': - optional: true '@easyops-cn/autocomplete.js@0.38.1': resolution: {integrity: sha512-drg76jS6syilOUmVNkyo1c7ZEBPcPuK+aJA7AksM5ZIIbV57DMHCywiCr+uHyv8BE5jUTU98j/H7gVrkHrWW3Q==} - '@easyops-cn/docusaurus-search-local@0.44.2': - resolution: {integrity: sha512-4tMBU54R1O6ITxkMGwOEifSHNkZLa2fb4ajGc8rd6TYZ0a8+jlu/u/5gYtw1s6sGGMRkwyG+QI6HD0bEnCRa1w==} + '@easyops-cn/docusaurus-search-local@0.46.1': + resolution: {integrity: sha512-kgenn5+pctVlJg8s1FOAm9KuZLRZvkBTMMGJvTTcvNTmnFIHVVYzYfA2Eg+yVefzsC8/cSZGKKJ0kLf8I+mQyw==} engines: {node: '>=12'} peerDependencies: '@docusaurus/theme-common': ^2 || ^3 react: ^16.14.0 || ^17 || ^18 react-dom: ^16.14.0 || 17 || ^18 - '@emnapi/core@1.2.0': - resolution: {integrity: sha512-E7Vgw78I93we4ZWdYCb4DGAwRROGkMIXk7/y87UmANR+J6qsWusmC3gLt0H+O0KOt5e6O38U8oJamgbudrES/w==} + '@emnapi/core@1.3.1': + resolution: {integrity: sha512-pVGjBIt1Y6gg3EJN8jTcfpP/+uuRksIo055oE/OBkDNcjZqVbfkWCksG1Jp4yZnj3iKWyWX8fdG/j6UDYPbFog==} - '@emnapi/runtime@1.2.0': - resolution: {integrity: sha512-bV21/9LQmcQeCPEg3BDFtvwL6cwiTMksYNWQQ4KOxCZikEGalWtenoZ0wCiukJINlGCIi2KXx01g4FoH/LxpzQ==} + '@emnapi/runtime@1.3.1': + resolution: {integrity: sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==} '@emnapi/wasi-threads@1.0.1': resolution: {integrity: sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==} @@ -1066,144 +1242,6 @@ packages: '@emotion/unitless@0.8.1': resolution: {integrity: sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==} - '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - '@exodus/schemasafe@1.3.0': resolution: {integrity: sha512-5Aap/GaRupgNx/feGBwLLTVv8OQFfv3pq2lPRzPg9R+IOBnDgghTGW7l7EuVXOvg5cc/xSAlRW8rBrjIC3Nvqw==} @@ -1221,8 +1259,8 @@ packages: resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - '@jridgewell/gen-mapping@0.3.5': - resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} '@jridgewell/resolve-uri@3.1.2': @@ -1236,8 +1274,8 @@ packages: '@jridgewell/source-map@0.3.6': resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} - '@jridgewell/sourcemap-codec@1.4.15': - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} @@ -1245,103 +1283,118 @@ packages: '@leichtgewicht/ip-codec@2.0.5': resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==} - '@mdx-js/mdx@3.0.1': - resolution: {integrity: sha512-eIQ4QTrOWyL3LWEe/bu6Taqzq2HQvHcyTMaOrI95P2/LmJE7AsfPfgJGuFLPVqBUE1BC1rik3VIhU+s9u72arA==} + '@mdx-js/mdx@3.1.0': + resolution: {integrity: sha512-/QxEhPAvGwbQmy1Px8F899L5Uc2KZ6JtXwlCgJmjSTBedwOZkByYcBG4GceIGPXRDsmfxhHazuS+hlOShRLeDw==} - '@mdx-js/react@3.0.1': - resolution: {integrity: sha512-9ZrPIU4MGf6et1m1ov3zKf+q9+deetI51zprKB1D/z3NOb+rUxxtEl3mCjW5wTGh6VhRdwPueh1oRzi6ezkA8A==} + '@mdx-js/react@3.1.0': + resolution: {integrity: sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ==} peerDependencies: '@types/react': '>=16' react: '>=16' - '@napi-rs/wasm-runtime@0.2.4': - resolution: {integrity: sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==} + '@module-federation/error-codes@0.8.4': + resolution: {integrity: sha512-55LYmrDdKb4jt+qr8qE8U3al62ZANp3FhfVaNPOaAmdTh0jHdD8M3yf5HKFlr5xVkVO4eV/F/J2NCfpbh+pEXQ==} - '@node-rs/jieba-android-arm-eabi@1.10.3': - resolution: {integrity: sha512-fuqVtaYlUKZg3cqagYFxj1DSa7ZHKXLle4iGH2kbQWg7Kw6cf7aCYBHIUZuH5sliK10M/CWccZ+SGRUwcSGfbg==} + '@module-federation/runtime-tools@0.8.4': + resolution: {integrity: sha512-fjVOsItJ1u5YY6E9FnS56UDwZgqEQUrWFnouRiPtK123LUuqUI9FH4redZoKWlE1PB0ir1Z3tnqy8eFYzPO38Q==} + + '@module-federation/runtime@0.8.4': + resolution: {integrity: sha512-yZeZ7z2Rx4gv/0E97oLTF3V6N25vglmwXGgoeju/W2YjsFvWzVtCDI7zRRb0mJhU6+jmSM8jP1DeQGbea/AiZQ==} + + '@module-federation/sdk@0.8.4': + resolution: {integrity: sha512-waABomIjg/5m1rPDBWYG4KUhS5r7OUUY7S+avpaVIY/tkPWB3ibRDKy2dNLLAMaLKq0u+B1qIdEp4NIWkqhqpg==} + + '@module-federation/webpack-bundler-runtime@0.8.4': + resolution: {integrity: sha512-HggROJhvHPUX7uqBD/XlajGygMNM1DG0+4OAkk8MBQe4a18QzrRNzZt6XQbRTSG4OaEoyRWhQHvYD3Yps405tQ==} + + '@napi-rs/wasm-runtime@0.2.6': + resolution: {integrity: sha512-z8YVS3XszxFTO73iwvFDNpQIzdMmSDTP/mB3E/ucR37V3Sx57hSExcXyMoNwaucWxnsWf4xfbZv0iZ30jr0M4Q==} + + '@node-rs/jieba-android-arm-eabi@1.10.4': + resolution: {integrity: sha512-MhyvW5N3Fwcp385d0rxbCWH42kqDBatQTyP8XbnYbju2+0BO/eTeCCLYj7Agws4pwxn2LtdldXRSKavT7WdzNA==} engines: {node: '>= 10'} cpu: [arm] os: [android] - '@node-rs/jieba-android-arm64@1.10.3': - resolution: {integrity: sha512-iuZZZq5yD9lT+AgaXpFe19gtAsIecUODRLLaBFbavjgjLk5cumv38ytWjS36s/eqptwI15MQfysSYOlWtMEG5g==} + '@node-rs/jieba-android-arm64@1.10.4': + resolution: {integrity: sha512-XyDwq5+rQ+Tk55A+FGi6PtJbzf974oqnpyCcCPzwU3QVXJCa2Rr4Lci+fx8oOpU4plT3GuD+chXMYLsXipMgJA==} engines: {node: '>= 10'} cpu: [arm64] os: [android] - '@node-rs/jieba-darwin-arm64@1.10.3': - resolution: {integrity: sha512-dwPhkav1tEARskwPz91UUXL2NXy4h0lJYTuJzpGgwXxm552zBM2JJ41kjah1364j+EOq5At3NQvf5r5rH89phQ==} + '@node-rs/jieba-darwin-arm64@1.10.4': + resolution: {integrity: sha512-G++RYEJ2jo0rxF9626KUy90wp06TRUjAsvY/BrIzEOX/ingQYV/HjwQzNPRR1P1o32a6/U8RGo7zEBhfdybL6w==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@node-rs/jieba-darwin-x64@1.10.3': - resolution: {integrity: sha512-kjxvV6G1baQo/2I3mELv5qGv4Q0rhd5srwXhypSxMWZFtSpNwCDsLcIOR5bvMBci6QVFfZOs6WD6DKiWVz0SlA==} + '@node-rs/jieba-darwin-x64@1.10.4': + resolution: {integrity: sha512-MmDNeOb2TXIZCPyWCi2upQnZpPjAxw5ZGEj6R8kNsPXVFALHIKMa6ZZ15LCOkSTsKXVC17j2t4h+hSuyYb6qfQ==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@node-rs/jieba-freebsd-x64@1.10.3': - resolution: {integrity: sha512-QYTsn+zlWRil+MuBeLfTK5Md4GluOf2lHnFqjrOZW2oMgNOvxB3qoLV4TUf70S/E2XHeP6PUdjCKItX8C7GQPg==} + '@node-rs/jieba-freebsd-x64@1.10.4': + resolution: {integrity: sha512-/x7aVQ8nqUWhpXU92RZqd333cq639i/olNpd9Z5hdlyyV5/B65LLy+Je2B2bfs62PVVm5QXRpeBcZqaHelp/bg==} engines: {node: '>= 10'} cpu: [x64] os: [freebsd] - '@node-rs/jieba-linux-arm-gnueabihf@1.10.3': - resolution: {integrity: sha512-UFB43kDOvqmbRl99e3GPwaTuwJZaAvgLaMTvBkmxww4MpQH6G1k31RLzMW/S21uSQso2lj6W/Mm59gaJk2FiyA==} + '@node-rs/jieba-linux-arm-gnueabihf@1.10.4': + resolution: {integrity: sha512-crd2M35oJBRLkoESs0O6QO3BBbhpv+tqXuKsqhIG94B1d02RVxtRIvSDwO33QurxqSdvN9IeSnVpHbDGkuXm3g==} engines: {node: '>= 10'} cpu: [arm] os: [linux] - '@node-rs/jieba-linux-arm64-gnu@1.10.3': - resolution: {integrity: sha512-bu++yWi10wZtnS5uLcwxzxKmHVT77NgQMK8JiQr1TWCl3Y1Th7CnEHQtxfVB489edDK8l644h1/4zSTe5fRnOQ==} + '@node-rs/jieba-linux-arm64-gnu@1.10.4': + resolution: {integrity: sha512-omIzNX1psUzPcsdnUhGU6oHeOaTCuCjUgOA/v/DGkvWC1jLcnfXe4vdYbtXMh4XOCuIgS1UCcvZEc8vQLXFbXQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@node-rs/jieba-linux-arm64-musl@1.10.3': - resolution: {integrity: sha512-pJh+SzrK1HaKakhdFM+ew9vXwpZqMxy9u0U7J4GT+3GvOwnAZ+KjeaHebIfgOz7ZHvp/T4YBNf8oWW4zwj3AJw==} + '@node-rs/jieba-linux-arm64-musl@1.10.4': + resolution: {integrity: sha512-Y/tiJ1+HeS5nnmLbZOE+66LbsPOHZ/PUckAYVeLlQfpygLEpLYdlh0aPpS5uiaWMjAXYZYdFkpZHhxDmSLpwpw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@node-rs/jieba-linux-x64-gnu@1.10.3': - resolution: {integrity: sha512-GF5cfvu/0wXO2fVX/XV3WYH/xEGWzMBvfqLhGiA1OA1xHIufnA1T7uU3ZXkyoNi5Bzf6dmxnwtE4CJL0nvhwjQ==} + '@node-rs/jieba-linux-x64-gnu@1.10.4': + resolution: {integrity: sha512-WZO8ykRJpWGE9MHuZpy1lu3nJluPoeB+fIJJn5CWZ9YTVhNDWoCF4i/7nxz1ntulINYGQ8VVuCU9LD86Mek97g==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@node-rs/jieba-linux-x64-musl@1.10.3': - resolution: {integrity: sha512-h45HMVU/hgzQ0saXNsK9fKlGdah1i1cXZULpB5vQRlRL2ZIaGp+ULtWTogS7vkoo2K8s2l4tqakWMg9eUjIJ2A==} + '@node-rs/jieba-linux-x64-musl@1.10.4': + resolution: {integrity: sha512-uBBD4S1rGKcgCyAk6VCKatEVQb6EDD5I40v/DxODi5CuZVCANi9m5oee/MQbAoaX7RydA2f0OSCE9/tcwXEwUg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@node-rs/jieba-wasm32-wasi@1.10.3': - resolution: {integrity: sha512-vuoQ62vVoedNGcBmIi4UWdtNBOZG8B+vDYfjx3FD6rNg6g/RgwbVjYXbOVMOQwX06Ob9CfrutICXdUGHgoxzEQ==} + '@node-rs/jieba-wasm32-wasi@1.10.4': + resolution: {integrity: sha512-Y2umiKHjuIJy0uulNDz9SDYHdfq5Hmy7jY5nORO99B4pySKkcrMjpeVrmWXJLIsEKLJwcCXHxz8tjwU5/uhz0A==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@node-rs/jieba-win32-arm64-msvc@1.10.3': - resolution: {integrity: sha512-B8t4dh56TZnMLBoYWDkopf1ed37Ru/iU1qiIeBkbZWXGmNBChNZUOd//eaPOFjx8m9Sfc8bkj3FBRWt/kTAhmw==} + '@node-rs/jieba-win32-arm64-msvc@1.10.4': + resolution: {integrity: sha512-nwMtViFm4hjqhz1it/juQnxpXgqlGltCuWJ02bw70YUDMDlbyTy3grCJPpQQpueeETcALUnTxda8pZuVrLRcBA==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@node-rs/jieba-win32-ia32-msvc@1.10.3': - resolution: {integrity: sha512-SKuPGZJ5T+X4jOn1S8LklOSZ6HC7UBiw0hwi2z9uqX6WgElquLjGi/xfZ2gPqffeR/5K/PUu7aqYUUPL1XonVQ==} + '@node-rs/jieba-win32-ia32-msvc@1.10.4': + resolution: {integrity: sha512-DCAvLx7Z+W4z5oKS+7vUowAJr0uw9JBw8x1Y23Xs/xMA4Em+OOSiaF5/tCJqZUCJ8uC4QeImmgDFiBqGNwxlyA==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] - '@node-rs/jieba-win32-x64-msvc@1.10.3': - resolution: {integrity: sha512-j9I4+a/tf2hsLu8Sr0NhcLBVNBBQctO2mzcjemMpRa1SlEeODyic9RIyP8Ljz3YTN6MYqKh1KA9iR1xvxjxYFg==} + '@node-rs/jieba-win32-x64-msvc@1.10.4': + resolution: {integrity: sha512-+sqemSfS1jjb+Tt7InNbNzrRh1Ua3vProVvC4BZRPg010/leCbGFFiQHpzcPRfpxAXZrzG5Y0YBTsPzN/I4yHQ==} engines: {node: '>= 10'} cpu: [x64] os: [win32] - '@node-rs/jieba@1.10.3': - resolution: {integrity: sha512-SG0CWHmhIveH6upJURgymDKLertEPYbOc5NSFIpbZWW1W2MpqgumVteQO+5YBlkmpR6jMNDPWNQyQwkB6HoeNg==} + '@node-rs/jieba@1.10.4': + resolution: {integrity: sha512-GvDgi8MnBiyWd6tksojej8anIx18244NmIOc1ovEw8WKNUejcccLfyu8vj66LWSuoZuKILVtNsOy4jvg3aoxIw==} engines: {node: '>= 10'} '@nodelib/fs.scandir@2.1.5': @@ -1364,25 +1417,83 @@ packages: resolution: {integrity: sha512-YcPQ8a0jwYU9bTdJDpXjMi7Brhkr1mXsXrUJvjqM2mQDgkRiz8jFaQGOdaLxgjtUfQgZhKy/O3cG/YwmgKaxLA==} engines: {node: '>=12.22.0'} - '@pnpm/npm-conf@2.2.2': - resolution: {integrity: sha512-UA91GwWPhFExt3IizW6bOeY/pQ0BkuNwKjk9iQW9KqxluGCrg4VenZ0/L+2Y0+ZOtme72EVvg6v0zo3AMQRCeA==} + '@pnpm/npm-conf@2.3.1': + resolution: {integrity: sha512-c83qWb22rNRuB0UaVCI0uRPNRr8Z0FWnEIvT47jiHAmOIUHbBOg5XvV7pM5x+rKn9HRpjxquDbXYSXr3fAKFcw==} engines: {node: '>=12'} - '@polka/url@1.0.0-next.25': - resolution: {integrity: sha512-j7P6Rgr3mmtdkeDGTe0E/aYyWEWVtc5yFXtHCRHs28/jptDEWfaVOc5T7cblqy1XKPPfCxJc/8DwQ5YgLOZOVQ==} + '@polka/url@1.0.0-next.28': + resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} - '@redocly/ajv@8.11.0': - resolution: {integrity: sha512-9GWx27t7xWhDIR02PA18nzBdLcKQRgc46xNQvjFkrYk4UOmvKhJ/dawwiX0cCOeetN5LcaaiqQbVOWYK62SGHw==} + '@redocly/ajv@8.11.2': + resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==} - '@redocly/config@0.6.2': - resolution: {integrity: sha512-c3K5u64eMnr2ootPcpEI0ioIRLE8QP8ptvLxG9MwAmb2sU8HMRfVwXDU3AZiMVY2w4Ts0mDc+Xv4HTIk8DRqFw==} + '@redocly/config@0.6.3': + resolution: {integrity: sha512-hGWJgCsXRw0Ow4rplqRlUQifZvoSwZipkYnt11e3SeH1Eb23VUIDBcRuaQOUqy1wn0eevXkU2GzzQ8fbKdQ7Mg==} '@redocly/openapi-core@1.16.0': resolution: {integrity: sha512-z06h+svyqbUcdAaePq8LPSwTPlm6Ig7j2VlL8skPBYnJvyaQ2IN7x/JkOvRL4ta+wcOCBdAex5JWnZbKaNktJg==} engines: {node: '>=14.19.0', npm: '>=7.0.0'} - '@sec-ant/readable-stream@0.4.1': - resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + '@rspack/binding-darwin-arm64@1.2.0-alpha.0': + resolution: {integrity: sha512-EPprIe6BrkJ9XuWL5HBXJFaH4vvt5C2kBTvyu+t5E3wacyH9A0gIDaMOEmH30Kt3zl4B07OCBC1nCiJ1sTtimw==} + cpu: [arm64] + os: [darwin] + + '@rspack/binding-darwin-x64@1.2.0-alpha.0': + resolution: {integrity: sha512-ACwdgWg0V9j0o3gs1wvhqRJ4xui82L+Fii9Fa74az7P974iWO0ZHw4QIUaO5r434+v9OWMqpyBRN1M7cBrx3GA==} + cpu: [x64] + os: [darwin] + + '@rspack/binding-linux-arm64-gnu@1.2.0-alpha.0': + resolution: {integrity: sha512-Ex9SviDikz9E36R4I5si/626FsYOJ35l1Lb+DCRUijjjsvoq4k8Shi8csyBfubR+JZ1M0uOXjJftu1Gm5z8Q0Q==} + cpu: [arm64] + os: [linux] + + '@rspack/binding-linux-arm64-musl@1.2.0-alpha.0': + resolution: {integrity: sha512-U320xZmTcTwQ0BR8yIzE1L4olMCqzYkT3VFjXPR6iok/Mj0xjfk/SiKhLoZml473qQrHSGaFJ321cp02zgTFJg==} + cpu: [arm64] + os: [linux] + + '@rspack/binding-linux-x64-gnu@1.2.0-alpha.0': + resolution: {integrity: sha512-GNur7VXJ29NtJhY8PYgv3Fv1Zxbx0XZhDUj/+7Wp40CAXRFsLgXScZIRh2U30TECYaihboZ7BD+xugv8MQPDoA==} + cpu: [x64] + os: [linux] + + '@rspack/binding-linux-x64-musl@1.2.0-alpha.0': + resolution: {integrity: sha512-0IdswzpG9+sgxvGu7KTwSeqfV0hvciaHMoZvGklfZa2txpcUqAg4ASp7uxrNaUo+G2a1fTUMOtP9351Cnl8DBg==} + cpu: [x64] + os: [linux] + + '@rspack/binding-win32-arm64-msvc@1.2.0-alpha.0': + resolution: {integrity: sha512-FcFgoWGjSrCfJwDZY5bDA2aO02l5BP7qdyW6ehjwBiMxNZyeSbGvKz3jXl5TtTHR1IgdLzi9kEJkTPYLLMiE1A==} + cpu: [arm64] + os: [win32] + + '@rspack/binding-win32-ia32-msvc@1.2.0-alpha.0': + resolution: {integrity: sha512-cZYFJw6DKCaPPz9VDJPndZ9KSp+/eedgt11Mv8OTpq+MJTUjB2HjtcjqJh8xxVcp3IuwvSMndTkC69WWt/4feA==} + cpu: [ia32] + os: [win32] + + '@rspack/binding-win32-x64-msvc@1.2.0-alpha.0': + resolution: {integrity: sha512-gfOqb/rq5716NV+Vbk5MteBhV4VhJeSoh2+dRQjdy4EN1wPZ+Uebs9ORVrT9uRjY3JrPn/5PkAHJXtgaOA9Uyg==} + cpu: [x64] + os: [win32] + + '@rspack/binding@1.2.0-alpha.0': + resolution: {integrity: sha512-rtmDScjtGUxv1zA1m3jXecuX2LsgNp4aWaAjOowHasoO1YqfHK0fMyprCiPowTjoHtpZ7Xt/tnMhii0GlGIITQ==} + + '@rspack/core@1.2.0-alpha.0': + resolution: {integrity: sha512-YiD0vFDj+PfHs3ZqJwPNhTYyVTb4xR6FpOI5WJ4jJHV4lgdErS+RChTCPhf1xeqxfuTSSnFA7UeqosLhBuNSqQ==} + engines: {node: '>=16.0.0'} + peerDependencies: + '@swc/helpers': '>=0.5.1' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@rspack/lite-tapable@1.0.1': + resolution: {integrity: sha512-VynGOEsVw2s8TAlLf/uESfrgfrq2+rcXB1muPJYBWbsm1Oa6r5qVQhjA5ggM6z/coYPrsVMgovl3Ff7Q7OCp1w==} + engines: {node: '>=16.0.0'} '@sideway/address@4.1.5': resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} @@ -1400,9 +1511,15 @@ packages: resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} - '@sindresorhus/is@6.3.1': - resolution: {integrity: sha512-FX4MfcifwJyFOI2lPoX7PQxCqx8BG1HCho7WdiXwpEQx1Ycij0JxkfYtGK7yqNScrZGSlt6RE6sw8QYoH7eKnQ==} - engines: {node: '>=16'} + '@sindresorhus/is@5.6.0': + resolution: {integrity: sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==} + engines: {node: '>=14.16'} + + '@slorber/react-helmet-async@1.3.0': + resolution: {integrity: sha512-e9/OK8VhwUSc67diWI8Rb3I0YgI9/SBQtnhe9aEuK6MhZm7ntZZimXgwXnd8W96YTmSOb9M4d8LwhRZyhWr/1A==} + peerDependencies: + react: ^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 '@slorber/remark-comment@1.0.0': resolution: {integrity: sha512-RCE24n7jsOj1M0UPvIQCHTe7fI0sFL4S2nwKVWwHyVr/wI/H8GosgsJGyhnsZoGFnD/P2hLf1mSbrrgSLN93NA==} @@ -1485,6 +1602,145 @@ packages: resolution: {integrity: sha512-LnhVjMWyMQV9ZmeEy26maJk+8HTIbd59cH4F2MJ439k9DqejRisfFNGAPvRYlKETuh9LrImlS8aKsBgKjMA8WA==} engines: {node: '>=14'} + '@swc/core-darwin-arm64@1.10.4': + resolution: {integrity: sha512-sV/eurLhkjn/197y48bxKP19oqcLydSel42Qsy2zepBltqUx+/zZ8+/IS0Bi7kaWVFxerbW1IPB09uq8Zuvm3g==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/core-darwin-x64@1.10.4': + resolution: {integrity: sha512-gjYNU6vrAUO4+FuovEo9ofnVosTFXkF0VDuo1MKPItz6e2pxc2ale4FGzLw0Nf7JB1sX4a8h06CN16/pLJ8Q2w==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/core-linux-arm-gnueabihf@1.10.4': + resolution: {integrity: sha512-zd7fXH5w8s+Sfvn2oO464KDWl+ZX1MJiVmE4Pdk46N3PEaNwE0koTfgx2vQRqRG4vBBobzVvzICC3618WcefOA==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/core-linux-arm64-gnu@1.10.4': + resolution: {integrity: sha512-+UGfoHDxsMZgFD3tABKLeEZHqLNOkxStu+qCG7atGBhS4Slri6h6zijVvf4yI5X3kbXdvc44XV/hrP/Klnui2A==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-arm64-musl@1.10.4': + resolution: {integrity: sha512-cDDj2/uYsOH0pgAnDkovLZvKJpFmBMyXkxEG6Q4yw99HbzO6QzZ5HDGWGWVq/6dLgYKlnnmpjZCPPQIu01mXEg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/core-linux-x64-gnu@1.10.4': + resolution: {integrity: sha512-qJXh9D6Kf5xSdGWPINpLGixAbB5JX8JcbEJpRamhlDBoOcQC79dYfOMEIxWPhTS1DGLyFakAx2FX/b2VmQmj0g==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-linux-x64-musl@1.10.4': + resolution: {integrity: sha512-A76lIAeyQnHCVt0RL/pG+0er8Qk9+acGJqSZOZm67Ve3B0oqMd871kPtaHBM0BW3OZAhoILgfHW3Op9Q3mx3Cw==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/core-win32-arm64-msvc@1.10.4': + resolution: {integrity: sha512-e6j5kBu4fIY7fFxFxnZI0MlEovRvp50Lg59Fw+DVbtqHk3C85dckcy5xKP+UoXeuEmFceauQDczUcGs19SRGSQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/core-win32-ia32-msvc@1.10.4': + resolution: {integrity: sha512-RSYHfdKgNXV/amY5Tqk1EWVsyQnhlsM//jeqMLw5Fy9rfxP592W9UTumNikNRPdjI8wKKzNMXDb1U29tQjN0dg==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/core-win32-x64-msvc@1.10.4': + resolution: {integrity: sha512-1ujYpaqfqNPYdwKBlvJnOqcl+Syn3UrQ4XE0Txz6zMYgyh6cdU6a3pxqLqIUSJ12MtXRA9ZUhEz1ekU3LfLWXw==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/core@1.10.4': + resolution: {integrity: sha512-ut3zfiTLORMxhr6y/GBxkHmzcGuVpwJYX4qyXWuBKkpw/0g0S5iO1/wW7RnLnZbAi8wS/n0atRZoaZlXWBkeJg==} + engines: {node: '>=10'} + peerDependencies: + '@swc/helpers': '*' + peerDependenciesMeta: + '@swc/helpers': + optional: true + + '@swc/counter@0.1.3': + resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} + + '@swc/html-darwin-arm64@1.10.4': + resolution: {integrity: sha512-LbzhJ3mPG3jy2dw6+A96U2hPCOzrJGJDn+Nq8RG5gjBIHVGXSBwMtdoHE4Li1kdXRXSwHW/YISCht1/OZShIRw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + + '@swc/html-darwin-x64@1.10.4': + resolution: {integrity: sha512-Fo0ntLo6ETIQHoRgB50Kwu2lzWUDWkStt2C3AEPmmAZBk62Anfu1BioPqMekPXxCiQhiUWKraASp8fVUhQ/ZJw==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + + '@swc/html-linux-arm-gnueabihf@1.10.4': + resolution: {integrity: sha512-XFiFeT9I4+rUa9QYn/HEhrOF23aMATWpmvIbsnzVUjDrqKatu1SZsZ6tI0+iD4tgv5z3JvkZ0wxypkyamsBKSQ==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + + '@swc/html-linux-arm64-gnu@1.10.4': + resolution: {integrity: sha512-CzDiqhAvZXZfphGXwwrMHE7DlJfOROhCQC6lLZbPm2HAuGHx9YZugo0HyrlCnhjwDj46GPiNv+Kw4Jd9EEzkaw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/html-linux-arm64-musl@1.10.4': + resolution: {integrity: sha512-k3Edp38x+xAhY1/UQ3LNtHKl11dEmgQfj3y9NH4q5ttS6jIQajmF5owLRKGBsw1OJwb26YDn16AbOVh0LmvDUw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + + '@swc/html-linux-x64-gnu@1.10.4': + resolution: {integrity: sha512-GjZClUoBwq9sI44WkNnCNJ6uwhFfRDrK8Z/9OfDDvbk4Md9B4SmI+tTnzdhTA1nImGlf55vZwMZkGYrWbL1EJQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/html-linux-x64-musl@1.10.4': + resolution: {integrity: sha512-F9r7K3Ae1d5K4rw0xb7eXopUfL4tv97VBGdchuv56TgzwHZJc7boOlpyGqzTgiVtB2Zaig/fuYbecon7sV01Jg==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + + '@swc/html-win32-arm64-msvc@1.10.4': + resolution: {integrity: sha512-UbkuOz485Ou2Dmic8SNv68Jes8geSnllk+gK5Mpxr7mErVEOTNfm8hYDmcvuiVNv0+swzL36IRu6Q8xZFHmd/A==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@swc/html-win32-ia32-msvc@1.10.4': + resolution: {integrity: sha512-/ks5ukvqt6OW9wczKe9ceeiJJWmOBG2sQUszeVyq+rj6Qsce12sKdm0gwWx8OclBpTswO4P6Hy8tnWPveiLKlw==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + + '@swc/html-win32-x64-msvc@1.10.4': + resolution: {integrity: sha512-ouYQeuShy5OtgUaCEOg915YvbUnn1gVO2cHJ4Y4+9LnR7cfAa2DZ+86kJZc94ABcI5pkeJWxeZAdt4NPCbIleg==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@swc/html@1.10.4': + resolution: {integrity: sha512-X6XJ63R7JGz2FfZBwyS7ketPf2rcPU7miaPIDeQZsKeu18KLSLc4/lJ5uZn9KKjn8BYcuteW1VQPwfvzNzw33g==} + engines: {node: '>=14'} + + '@swc/types@0.1.17': + resolution: {integrity: sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==} + '@szmarczak/http-timer@5.0.1': resolution: {integrity: sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==} engines: {node: '>=14.16'} @@ -1517,23 +1773,23 @@ packages: '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} - '@types/dompurify@3.0.5': - resolution: {integrity: sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==} - '@types/eslint-scope@3.7.7': resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} - '@types/eslint@8.56.10': - resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==} + '@types/eslint@9.6.1': + resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} '@types/estree-jsx@1.0.5': resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} - '@types/estree@1.0.5': - resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} - '@types/express-serve-static-core@4.19.5': - resolution: {integrity: sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==} + '@types/express-serve-static-core@4.19.6': + resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} + + '@types/express-serve-static-core@5.0.3': + resolution: {integrity: sha512-JEhMNwUJt7bw728CydvYzntD0XJeTmDnvwLlbfbAhE7Tbslm/ax6bdIiUwTgeVlZTsJQPwZwKpAkyDtIjsvx3g==} '@types/express@4.17.21': resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} @@ -1556,8 +1812,8 @@ packages: '@types/http-errors@2.0.4': resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} - '@types/http-proxy@1.17.14': - resolution: {integrity: sha512-SSrD0c1OQzlFX7pGu1eXxSEjemej64aaNPRhhVYUGqXh0BtldAAx37MG8btcumvpgKyZp1F5Gn3JkktdxiFv6w==} + '@types/http-proxy@1.17.15': + resolution: {integrity: sha512-25g5atgiVNTIv0LBDTg1H74Hvayx0ajtJPLLcYE3whFv75J0pWNtOBzaXJQgDTmrX1bx5U9YC2w/n65BN1HwRQ==} '@types/istanbul-lib-coverage@2.0.6': resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} @@ -1589,20 +1845,20 @@ packages: '@types/node@17.0.45': resolution: {integrity: sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==} - '@types/node@20.14.10': - resolution: {integrity: sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==} + '@types/node@22.10.5': + resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==} '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} - '@types/prismjs@1.26.4': - resolution: {integrity: sha512-rlAnzkW2sZOjbqZ743IHUhFcvzaGbqijwOu8QZnZCjfQzBqFE3s4lOTJEsxikImav9uzz/42I+O7YUs1mWgMlg==} + '@types/prismjs@1.26.5': + resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} - '@types/prop-types@15.7.12': - resolution: {integrity: sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==} + '@types/prop-types@15.7.14': + resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} - '@types/qs@6.9.15': - resolution: {integrity: sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==} + '@types/qs@6.9.17': + resolution: {integrity: sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==} '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} @@ -1619,8 +1875,11 @@ packages: '@types/react-router@5.1.20': resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==} - '@types/react@18.3.3': - resolution: {integrity: sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==} + '@types/react@18.3.18': + resolution: {integrity: sha512-t4yC+vtgnkYjNSKlFx1jkAhH8LgTo2N/7Qvi83kdEaUtMDiwpbLAktKDaAMlRcJ5eSxZkH74eEGt1ky31d7kfQ==} + + '@types/react@19.0.2': + resolution: {integrity: sha512-USU8ZI/xyKJwFTpjSVIrSeHBVAGagkHQKPNbxeWwql/vDmnTIBgx+TJnhFnj1NXgz8XfprU0egV2dROLGpsBEg==} '@types/retry@0.12.0': resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} @@ -1646,68 +1905,68 @@ packages: '@types/trusted-types@2.0.7': resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} - '@types/unist@2.0.10': - resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} - '@types/unist@3.0.2': - resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==} + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} - '@types/ws@8.5.10': - resolution: {integrity: sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==} + '@types/ws@8.5.13': + resolution: {integrity: sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==} '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} - '@types/yargs@17.0.32': - resolution: {integrity: sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==} + '@types/yargs@17.0.33': + resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} - '@ungap/structured-clone@1.2.0': - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + '@ungap/structured-clone@1.2.1': + resolution: {integrity: sha512-fEzPV3hSkSMltkw152tJKNARhOupqbH96MZWyRjNaYZOMIzbrTeQDG+MTc6Mr2pgzFQzFxAfmhGDNP5QK++2ZA==} - '@webassemblyjs/ast@1.12.1': - resolution: {integrity: sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==} + '@webassemblyjs/ast@1.14.1': + resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} - '@webassemblyjs/floating-point-hex-parser@1.11.6': - resolution: {integrity: sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==} + '@webassemblyjs/floating-point-hex-parser@1.13.2': + resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} - '@webassemblyjs/helper-api-error@1.11.6': - resolution: {integrity: sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==} + '@webassemblyjs/helper-api-error@1.13.2': + resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} - '@webassemblyjs/helper-buffer@1.12.1': - resolution: {integrity: sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==} + '@webassemblyjs/helper-buffer@1.14.1': + resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} - '@webassemblyjs/helper-numbers@1.11.6': - resolution: {integrity: sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==} + '@webassemblyjs/helper-numbers@1.13.2': + resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} - '@webassemblyjs/helper-wasm-bytecode@1.11.6': - resolution: {integrity: sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==} + '@webassemblyjs/helper-wasm-bytecode@1.13.2': + resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} - '@webassemblyjs/helper-wasm-section@1.12.1': - resolution: {integrity: sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==} + '@webassemblyjs/helper-wasm-section@1.14.1': + resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} - '@webassemblyjs/ieee754@1.11.6': - resolution: {integrity: sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==} + '@webassemblyjs/ieee754@1.13.2': + resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} - '@webassemblyjs/leb128@1.11.6': - resolution: {integrity: sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==} + '@webassemblyjs/leb128@1.13.2': + resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} - '@webassemblyjs/utf8@1.11.6': - resolution: {integrity: sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==} + '@webassemblyjs/utf8@1.13.2': + resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} - '@webassemblyjs/wasm-edit@1.12.1': - resolution: {integrity: sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==} + '@webassemblyjs/wasm-edit@1.14.1': + resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} - '@webassemblyjs/wasm-gen@1.12.1': - resolution: {integrity: sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==} + '@webassemblyjs/wasm-gen@1.14.1': + resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} - '@webassemblyjs/wasm-opt@1.12.1': - resolution: {integrity: sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==} + '@webassemblyjs/wasm-opt@1.14.1': + resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} - '@webassemblyjs/wasm-parser@1.12.1': - resolution: {integrity: sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==} + '@webassemblyjs/wasm-parser@1.14.1': + resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} - '@webassemblyjs/wast-printer@1.12.1': - resolution: {integrity: sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==} + '@webassemblyjs/wast-printer@1.14.1': + resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} '@xtuc/ieee754@1.2.0': resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} @@ -1719,22 +1978,17 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} - acorn-import-attributes@1.9.5: - resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} - peerDependencies: - acorn: ^8 - acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn-walk@8.3.3: - resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==} + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} engines: {node: '>=0.4.0'} - acorn@8.12.1: - resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} engines: {node: '>=0.4.0'} hasBin: true @@ -1742,8 +1996,8 @@ packages: resolution: {integrity: sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==} engines: {node: '>= 10.0.0'} - agent-base@7.1.1: - resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} + agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} aggregate-error@3.1.0: @@ -1771,20 +2025,25 @@ packages: ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} - ajv@8.16.0: - resolution: {integrity: sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==} + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} - algoliasearch-helper@3.22.2: - resolution: {integrity: sha512-3YQ6eo7uYOCHeQ2ZpD+OoT3aJJwMNKEnwtu8WMzm81XmBOSCwRjQditH9CeSOQ38qhHkuGw23pbq+kULkIJLcw==} + algoliasearch-helper@3.22.6: + resolution: {integrity: sha512-F2gSb43QHyvZmvH/2hxIjbk/uFdO2MguQYTFP7J+RowMW1csjIODMobEnpLI8nbLQuzZnGZdIxl5Bpy1k9+CFQ==} peerDependencies: algoliasearch: '>= 3.1 < 6' - algoliasearch@4.24.0: - resolution: {integrity: sha512-bf0QV/9jVejssFBmz2HQLxUadxk574t4iwjCKp5E7NBzwKkrDEhKPISIIjAU/p6K5qDx3qoeh4+26zWN1jmw3g==} + algoliasearch@5.18.0: + resolution: {integrity: sha512-/tfpK2A4FpS0o+S78o3YSdlqXr0MavJIDlFK3XZrlXLy7vaRXJvW5jYg3v5e/wCaF8y0IpMjkYLhoV6QqfpOgw==} + engines: {node: '>= 14.0.0'} ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + ansi-html-community@0.0.8: resolution: {integrity: sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==} engines: {'0': node >= 0.8.0} @@ -1794,14 +2053,10 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-regex@6.0.1: - resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} engines: {node: '>=12'} - ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -1823,8 +2078,8 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - array-buffer-byte-length@1.0.1: - resolution: {integrity: sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==} + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} array-flatten@1.1.1: @@ -1838,16 +2093,16 @@ packages: resolution: {integrity: sha512-r+mCJ7zXgXElgR4IRC+fkvNCeoaavWBs6EdCso5Tbcf+iEMKzBU/His60lt34WEZ9vlb8wDkZvQGcVI5GwkfoQ==} engines: {node: '>= 0.4'} - array.prototype.flat@1.3.2: - resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} engines: {node: '>= 0.4'} - arraybuffer.prototype.slice@1.0.3: - resolution: {integrity: sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==} + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} engines: {node: '>= 0.4'} - astring@1.8.6: - resolution: {integrity: sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==} + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} hasBin: true asynckit@0.4.0: @@ -1857,8 +2112,8 @@ packages: resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} engines: {node: '>= 4.0.0'} - autoprefixer@10.4.19: - resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==} + autoprefixer@10.4.20: + resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: @@ -1868,11 +2123,11 @@ packages: resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} engines: {node: '>= 0.4'} - axios@1.7.2: - resolution: {integrity: sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==} + axios@1.7.9: + resolution: {integrity: sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==} - babel-loader@9.1.3: - resolution: {integrity: sha512-xG3ST4DglodGf8qSwv0MdeWLhrDsw/32QMdTO5T1ZIp9gQur0HkCyFs7Awskr10JKXFXwpAhiCuYX5oGXnRGbw==} + babel-loader@9.2.1: + resolution: {integrity: sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==} engines: {node: '>= 14.15.0'} peerDependencies: '@babel/core': ^7.12.0 @@ -1881,18 +2136,18 @@ packages: babel-plugin-dynamic-import-node@2.3.3: resolution: {integrity: sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==} - babel-plugin-polyfill-corejs2@0.4.11: - resolution: {integrity: sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==} + babel-plugin-polyfill-corejs2@0.4.12: + resolution: {integrity: sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-polyfill-corejs3@0.10.4: - resolution: {integrity: sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==} + babel-plugin-polyfill-corejs3@0.10.6: + resolution: {integrity: sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - babel-plugin-polyfill-regenerator@0.6.2: - resolution: {integrity: sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==} + babel-plugin-polyfill-regenerator@0.6.3: + resolution: {integrity: sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q==} peerDependencies: '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 @@ -1912,12 +2167,12 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - body-parser@1.20.2: - resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} + body-parser@1.20.3: + resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - bonjour-service@1.2.1: - resolution: {integrity: sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw==} + bonjour-service@1.3.0: + resolution: {integrity: sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==} boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} @@ -1940,8 +2195,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.23.1: - resolution: {integrity: sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==} + browserslist@4.24.3: + resolution: {integrity: sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -1960,12 +2215,20 @@ packages: resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==} engines: {node: '>=14.16'} - cacheable-request@12.0.1: - resolution: {integrity: sha512-Yo9wGIQUaAfIbk+qY0X4cDQgCosecfBe3V9NSyeY4qPC2SAkbCS4Xj79VP8WOzitpJUZKc/wsRCYF5ariDIwkg==} - engines: {node: '>=18'} + cacheable-request@10.2.14: + resolution: {integrity: sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==} + engines: {node: '>=14.16'} - call-bind@1.0.7: - resolution: {integrity: sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==} + call-bind-apply-helpers@1.0.1: + resolution: {integrity: sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.3: + resolution: {integrity: sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==} engines: {node: '>= 0.4'} call-me-maybe@1.0.2: @@ -1992,22 +2255,18 @@ packages: caniuse-api@3.0.0: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} - caniuse-lite@1.0.30001640: - resolution: {integrity: sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA==} + caniuse-lite@1.0.30001690: + resolution: {integrity: sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chalk@5.3.0: - resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + chalk@5.4.1: + resolution: {integrity: sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==} engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} char-regex@1.0.2: @@ -2029,6 +2288,10 @@ packages: cheerio-select@2.1.0: resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==} + cheerio@1.0.0: + resolution: {integrity: sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==} + engines: {node: '>=18.17'} + cheerio@1.0.0-rc.12: resolution: {integrity: sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==} engines: {node: '>= 6'} @@ -2083,16 +2346,10 @@ packages: collapse-white-space@2.1.0: resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} - color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -2113,6 +2370,9 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + comlink@4.4.2: + resolution: {integrity: sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g==} + comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} @@ -2142,16 +2402,16 @@ packages: resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} engines: {node: '>= 0.6'} - compression@1.7.4: - resolution: {integrity: sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==} + compression@1.7.5: + resolution: {integrity: sha512-bQJ0YRck5ak3LgtnpKkiabX5pNF7tMUh1BSy2ZBOTh0Dim0BUu6aPPwByIns6/A5Prh8PufSPerMDUklpzes2Q==} engines: {node: '>= 0.8.0'} concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - concurrently@8.2.2: - resolution: {integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==} - engines: {node: ^14.13.0 || >=16.0.0} + concurrently@9.1.2: + resolution: {integrity: sha512-H9MWcoPsYddwbOGM6difjVwVZHl63nwMEwDJG/L7VGtuaJhb12h2caPG2tVPWs7emuYix252iGfqOyrz1GczTQ==} + engines: {node: '>=18'} hasBin: true config-chain@1.1.13: @@ -2165,8 +2425,9 @@ packages: resolution: {integrity: sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==} engines: {node: '>=0.8'} - consola@2.15.3: - resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} + consola@3.3.3: + resolution: {integrity: sha512-Qil5KwghMzlqd51UXM0b6fyaGHtOC22scxrwrz4A2882LyUMwQjnvaedN1HAeXzphspQ6CpHkzMAWxBTUruDLg==} + engines: {node: ^14.18.0 || >=16.10.0} content-disposition@0.5.2: resolution: {integrity: sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==} @@ -2186,8 +2447,8 @@ packages: cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} - cookie@0.6.0: - resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} + cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} copy-text-to-clipboard@3.2.0: @@ -2200,14 +2461,14 @@ packages: peerDependencies: webpack: ^5.1.0 - core-js-compat@3.37.1: - resolution: {integrity: sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==} + core-js-compat@3.39.0: + resolution: {integrity: sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==} - core-js-pure@3.37.1: - resolution: {integrity: sha512-J/r5JTHSmzTxbiYYrzXg9w1VpqrYt+gexenBE9pugeyhwPZTAEJddyiReJWsLO6uNQ8xJZFbod6XC7KKwatCiA==} + core-js-pure@3.39.0: + resolution: {integrity: sha512-7fEcWwKI4rJinnK+wLTezeg2smbFFdSBP6E2kQZNbnzM2s1rpKQ6aaRteZSSg7FLU3P0HGGVo/gbpfanU36urg==} - core-js@3.37.1: - resolution: {integrity: sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==} + core-js@3.39.0: + resolution: {integrity: sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==} core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -2225,14 +2486,20 @@ packages: typescript: optional: true - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} crypto-random-string@4.0.0: resolution: {integrity: sha512-x8dy3RnvYdlUcPOjkEHqozhiwzKNSq7GcPuXFbnyMOCHxX8V3OgIg/pYuabl2sbUPfIJaeAQB7PMOK8DFIdoRA==} engines: {node: '>=12'} + css-blank-pseudo@7.0.1: + resolution: {integrity: sha512-jf+twWGDf6LDoXDUode+nc7ZlrqfaNphrBIBrcmeP3D8yw1uPaix1gCC8LUQUGQ6CycuK2opkbFFWFuq/a94ag==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + css-color-keywords@1.0.0: resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==} engines: {node: '>=4'} @@ -2243,6 +2510,12 @@ packages: peerDependencies: postcss: ^8.0.9 + css-has-pseudo@7.0.2: + resolution: {integrity: sha512-nzol/h+E0bId46Kn2dQH5VElaknX2Sr0hFuB/1EomdC7j+OISt2ZzK7EHX9DZDY53WbIVAR7FYKSO2XnSf07MQ==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + css-loader@6.11.0: resolution: {integrity: sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==} engines: {node: '>= 12.13.0'} @@ -2280,6 +2553,12 @@ packages: lightningcss: optional: true + css-prefers-color-scheme@10.0.0: + resolution: {integrity: sha512-VCtXZAWivRglTZditUfB4StnsWr6YVZ2PRtuxQLKTNRdtAf8tpzaVPE9zXIF3VaSc7O70iK/j1+NXxyQCqdPjQ==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + css-select@4.3.0: resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} @@ -2301,6 +2580,9 @@ packages: resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} engines: {node: '>= 6'} + cssdb@8.2.3: + resolution: {integrity: sha512-9BDG5XmJrJQQnJ51VFxXCAtpZ5ebDlAREmO8sxMOVU0aSxN/gocbctjIG5LMh3WBUq+xTlb/jw2LoljBEqraTA==} + cssesc@3.0.0: resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} engines: {node: '>=4'} @@ -2334,8 +2616,8 @@ packages: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} - cssstyle@4.0.1: - resolution: {integrity: sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==} + cssstyle@4.1.0: + resolution: {integrity: sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==} engines: {node: '>=18'} csstype@3.1.3: @@ -2345,22 +2627,18 @@ packages: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} - data-view-buffer@1.0.1: - resolution: {integrity: sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==} + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} engines: {node: '>= 0.4'} - data-view-byte-length@1.0.1: - resolution: {integrity: sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==} + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} engines: {node: '>= 0.4'} - data-view-byte-offset@1.0.0: - resolution: {integrity: sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==} + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} engines: {node: '>= 0.4'} - date-fns@2.30.0: - resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} - engines: {node: '>=0.11'} - debounce@1.2.1: resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==} @@ -2372,8 +2650,8 @@ packages: supports-color: optional: true - debug@4.3.5: - resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==} + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -2446,6 +2724,11 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + detect-node@2.1.0: resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} @@ -2479,8 +2762,8 @@ packages: peerDependencies: '@docusaurus/utils': ^3.0.0 - docusaurus-theme-redoc@2.1.1: - resolution: {integrity: sha512-a9yuYyGVhj7NgBYiqJyjLEkJg/yTdsqg9Rn/cG8YXMIFwxIpn4tanIplUqwisK2PS81ZxOv7SfSgvGm/FSi/wA==} + docusaurus-theme-redoc@2.2.0: + resolution: {integrity: sha512-oeREQZ7xf3qbkHSAvPVciGlssSb80zx+1GkiymM0sZwuZbD6FbTc6g1Dz81j8oCv0asSiRbsGo62KnpAatjnOg==} engines: {node: '>=18'} peerDependencies: '@docusaurus/theme-common': ^3.0.0 @@ -2506,14 +2789,14 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} - dompurify@3.1.6: - resolution: {integrity: sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==} + dompurify@3.2.3: + resolution: {integrity: sha512-U1U5Hzc2MO0oW3DF+G9qYN0aT7atAou4AgI0XjWz061nyBPbdxkfdhfy5uMgGn6+oLFCfn44ZGbdDqCzVmlOWA==} domutils@2.8.0: resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} - domutils@3.1.0: - resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} + domutils@3.2.1: + resolution: {integrity: sha512-xWXmuRnN9OMP6ptPd2+H0cCbcYBULa5YDTbMm/2lvkWvNA3O4wcW+GvzooqBuNM8yy6pl3VIAeJTUUWUbfI5Fw==} dot-case@3.0.4: resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} @@ -2522,6 +2805,10 @@ packages: resolution: {integrity: sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==} engines: {node: '>=10'} + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + duplexer@0.1.2: resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} @@ -2531,8 +2818,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.4.817: - resolution: {integrity: sha512-3znu+lZMIbTe8ZOs360OMJvVroVF2NpNI8T5jfLnDetVvj0uNmIucZzQVYMSJfsu9f47Ssox1Gt46PR+R+1JUg==} + electron-to-chromium@1.5.76: + resolution: {integrity: sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2547,15 +2834,22 @@ packages: resolution: {integrity: sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==} engines: {node: '>= 4'} - emoticon@4.0.1: - resolution: {integrity: sha512-dqx7eA9YaqyvYtUhJwT4rC1HIp82j5ybS1/vQ42ur+jBe17dJMwZE4+gvL1XadSFfxaPFFGt3Xsw+Y8akThDlw==} + emoticon@4.1.0: + resolution: {integrity: sha512-VWZfnxqwNcc51hIy/sbOdEem6D+cVtpPzEEtVAFdaas30+1dgkyaOQ4sQ6Bp0tOMqWO1v+HQfYaoodOkdhK6SQ==} encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} - enhanced-resolve@5.17.0: - resolution: {integrity: sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + encoding-sniffer@0.2.0: + resolution: {integrity: sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==} + + enhanced-resolve@5.18.0: + resolution: {integrity: sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==} engines: {node: '>=10.13.0'} entities@2.2.0: @@ -2574,54 +2868,50 @@ packages: error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - es-abstract@1.23.3: - resolution: {integrity: sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==} + es-abstract@1.23.9: + resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==} engines: {node: '>= 0.4'} es-array-method-boxes-properly@1.0.0: resolution: {integrity: sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==} - es-define-property@1.0.0: - resolution: {integrity: sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==} + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} es-errors@1.3.0: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-module-lexer@1.5.4: - resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} + es-module-lexer@1.6.0: + resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} es-object-atoms@1.0.0: resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} engines: {node: '>= 0.4'} - es-set-tostringtag@2.0.3: - resolution: {integrity: sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==} + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} es-shim-unscopables@1.0.2: resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} - es-to-primitive@1.2.1: - resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} es6-promise@3.3.1: resolution: {integrity: sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==} - esbuild-loader@4.2.0: - resolution: {integrity: sha512-BhwHchuDknxIa69AqOPeZh2fIFqj2AzZKC1E3RBRvXSuyk5drsqMrwsgYZJufX41yrauLYjDM3KBmruoGl1NWQ==} - peerDependencies: - webpack: ^4.40.0 || ^5.0.0 + esast-util-from-estree@2.0.0: + resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} - esbuild@0.21.5: - resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} - engines: {node: '>=12'} - hasBin: true + esast-util-from-js@2.0.1: + resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} - escalade@3.1.2: - resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} escape-goat@4.0.0: @@ -2673,11 +2963,14 @@ packages: estree-util-is-identifier-name@3.0.0: resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + estree-util-scope@1.0.0: + resolution: {integrity: sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==} + estree-util-to-js@2.0.0: resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} - estree-util-value-to-estree@3.1.2: - resolution: {integrity: sha512-S0gW2+XZkmsx00tU2uJ4L9hUT7IFabbml9pHh2WQqFmAbxit++YGZne0sKJbNwkj9Wvg9E4uqWl4nCIFQMmfag==} + estree-util-value-to-estree@3.2.1: + resolution: {integrity: sha512-Vt2UOjyPbNQQgT5eJh+K5aATti0OjCIAGc9SgMdOFYbohuifsWclR74l0iZTJwePMgWYdX1hlVS+dedH9XV8kw==} estree-util-visit@2.0.0: resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} @@ -2715,8 +3008,8 @@ packages: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} - express@4.19.2: - resolution: {integrity: sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==} + express@4.21.2: + resolution: {integrity: sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==} engines: {node: '>= 0.10.0'} extend-shallow@2.0.1: @@ -2729,8 +3022,8 @@ packages: fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} fast-json-stable-stringify@2.1.0: @@ -2739,11 +3032,15 @@ packages: fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} - fast-url-parser@1.1.3: - resolution: {integrity: sha512-5jOCVXADYNuRkKFzNJ0dCCewsZiYo0dz8QNYljkOpFC6r2U4OBmKtvm/Tsuh4w1YYdDqDb31a8TVhBJ2OJKdqQ==} + fast-uri@3.0.4: + resolution: {integrity: sha512-G3iTQw1DizJQ5eEqj1CbFCWhq+pzum7qepkxU7rS1FGZDqjYKcrguo9XDRbV7EgPnn8CgaPigTq+NEjyioeYZQ==} - fastq@1.17.1: - resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + fast-xml-parser@4.5.1: + resolution: {integrity: sha512-y655CeyUQ+jj7KBbYMc4FG01V8ZQqjN+gDYGJ50RtfsUB8iG9AmwmwoAgeKLJdmueKKMrH1RJ7yXHTSoczdv5w==} + hasBin: true + + fastq@1.18.0: + resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==} fault@2.0.1: resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} @@ -2756,6 +3053,10 @@ packages: resolution: {integrity: sha512-u5/sxGfiMfZNtJ3OvQpXcvotFpYkL0n9u9mM2vkui2nGo8b4wvDkJ8gAkYqbA8QpGyFCv3RK0Z+Iv+9veCS9bQ==} engines: {node: '>=0.4.0'} + figures@3.2.0: + resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} + engines: {node: '>=8'} + file-loader@6.2.0: resolution: {integrity: sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw==} engines: {node: '>= 10.13.0'} @@ -2770,8 +3071,8 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - finalhandler@1.2.0: - resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} + finalhandler@1.3.1: + resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} engines: {node: '>= 0.8'} find-cache-dir@4.0.0: @@ -2794,8 +3095,8 @@ packages: resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} hasBin: true - follow-redirects@1.15.6: - resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -2823,12 +3124,12 @@ packages: vue-template-compiler: optional: true - form-data-encoder@4.0.2: - resolution: {integrity: sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==} - engines: {node: '>= 18'} + form-data-encoder@2.1.4: + resolution: {integrity: sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==} + engines: {node: '>= 14.17'} - form-data@4.0.0: - resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + form-data@4.0.1: + resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} engines: {node: '>= 6'} format@0.2.2: @@ -2872,8 +3173,8 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - function.prototype.name@1.1.6: - resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} engines: {node: '>= 0.4'} functions-have-names@1.2.3: @@ -2891,32 +3192,25 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-intrinsic@1.2.4: - resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} + get-intrinsic@1.2.7: + resolution: {integrity: sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==} engines: {node: '>= 0.4'} get-own-enumerable-property-symbols@3.0.2: resolution: {integrity: sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==} + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + get-stream@6.0.1: resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} engines: {node: '>=10'} - get-stream@8.0.1: - resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} - engines: {node: '>=16'} - - get-stream@9.0.1: - resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} - engines: {node: '>=18'} - - get-symbol-description@1.0.2: - resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} engines: {node: '>= 0.4'} - get-tsconfig@4.7.5: - resolution: {integrity: sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==} - github-slugger@1.5.0: resolution: {integrity: sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==} @@ -2963,12 +3257,13 @@ packages: resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - gopd@1.0.1: - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} - got@14.4.1: - resolution: {integrity: sha512-IvDJbJBUeexX74xNQuMIVgCRRuNOm5wuK+OC3Dc2pnSoh1AOmgc7JVj7WC+cJ4u0aPcO9KZ2frTXcqK4W/5qTQ==} - engines: {node: '>=20'} + got@12.6.1: + resolution: {integrity: sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==} + engines: {node: '>=14.16'} graceful-fs@4.2.10: resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} @@ -2987,12 +3282,9 @@ packages: handle-thing@2.0.1: resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==} - has-bigints@1.0.2: - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} - - has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} @@ -3001,12 +3293,12 @@ packages: has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - has-proto@1.0.3: - resolution: {integrity: sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==} + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} engines: {node: '>= 0.4'} - has-symbols@1.0.3: - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} has-tostringtag@1.0.2: @@ -3025,20 +3317,20 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - hast-util-from-parse5@8.0.1: - resolution: {integrity: sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==} + hast-util-from-parse5@8.0.2: + resolution: {integrity: sha512-SfMzfdAi/zAoZ1KkFEyyeXBn7u/ShQrfd675ZEE9M3qj+PMFX05xubzRyF76CCSJu8au9jgVxDV1+okFvgZU4A==} hast-util-parse-selector@4.0.0: resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} - hast-util-raw@9.0.4: - resolution: {integrity: sha512-LHE65TD2YiNsHD3YuXcKPHXPLuYh/gjp12mOfU8jxSrm1f/yJpsb0F/KKljS6U9LJoP0Ux+tCe8iJ2AsPzTdgA==} + hast-util-raw@9.1.0: + resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} - hast-util-to-estree@3.1.0: - resolution: {integrity: sha512-lfX5g6hqVh9kjS/B9E2gSkvHH4SZNiQFiqWS0x9fENzEl+8W12RqdRxX6d/Cwxi30tPQs3bIO+aolQJNp1bIyw==} + hast-util-to-estree@3.1.1: + resolution: {integrity: sha512-IWtwwmPskfSmma9RpzCappDUitC8t5jhAynHhc1m2+5trOgsrp7txscUSavc5Ic8PATyAjfrCK1wgtxh2cICVQ==} - hast-util-to-jsx-runtime@2.3.0: - resolution: {integrity: sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ==} + hast-util-to-jsx-runtime@2.3.2: + resolution: {integrity: sha512-1ngXYb+V9UT5h+PxNRa1O1FYguZK/XL+gkeqvp7EdHlB9oHUG0eYRo/vY5inBdcqo3RkPMC58/H94HvkbfGdyg==} hast-util-to-parse5@8.0.0: resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==} @@ -3046,8 +3338,8 @@ packages: hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} - hastscript@8.0.0: - resolution: {integrity: sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==} + hastscript@9.0.0: + resolution: {integrity: sha512-jzaLBGavEDKHrc5EfFImKN7nZKKBdSLIdGvCwDZ9TfzbF2ffXiov8CKE445L2Z1Ek2t/m4SKQ2j6Ipv7NyUolw==} he@1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} @@ -3092,8 +3384,8 @@ packages: html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} - html-webpack-plugin@5.6.0: - resolution: {integrity: sha512-iwaY4wzbe48AfKLZ/Cc8k0L+FKG6oSNRaZ8x5A/T/IVDGyXcbHncM9TdDa93wn0FsSm82FhTKW7f3vS61thXAw==} + html-webpack-plugin@5.6.3: + resolution: {integrity: sha512-QSf1yjtSAsmf7rYBV7XX86uua4W/vkhIt0xNXKbsi2foEeW7vjJQz4bhnpL3xH+l1ryl1680uNv968Z+X6jSYg==} engines: {node: '>=10.13.0'} peerDependencies: '@rspack/core': 0.x || 1.x @@ -3110,6 +3402,9 @@ packages: htmlparser2@8.0.2: resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==} + htmlparser2@9.1.0: + resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==} + http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} @@ -3131,8 +3426,8 @@ packages: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} - http-proxy-middleware@2.0.6: - resolution: {integrity: sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==} + http-proxy-middleware@2.0.7: + resolution: {integrity: sha512-fgVY8AV7qU7z/MmXJ/rxwbrtQH4jBQ9m7kp3llF0liB7glmFeVZFBepQb32T3y8n8k2+AEYuMPCpinYW+/CuRA==} engines: {node: '>=12.0.0'} peerDependencies: '@types/express': ^4.17.13 @@ -3151,8 +3446,8 @@ packages: resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==} engines: {node: '>=10.19.0'} - https-proxy-agent@7.0.5: - resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} human-signals@2.1.0: @@ -3173,12 +3468,12 @@ packages: peerDependencies: postcss: ^8.1.0 - ignore@5.3.1: - resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - image-size@1.1.1: - resolution: {integrity: sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==} + image-size@1.2.0: + resolution: {integrity: sha512-4S8fwbO6w3GeCVN6OPtA9I5IGKkcDMPcKndtUlpJuCwu7JLjtj7JZpwqLuyY2nrmQT3AWsCJLSKPsc2mPBSl3w==} engines: {node: '>=16.x'} hasBin: true @@ -3204,8 +3499,8 @@ packages: resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} engines: {node: '>=8'} - infima@0.2.0-alpha.43: - resolution: {integrity: sha512-2uw57LvUqW0rK/SWYnd/2rRfxNA5DDNOh33jxF7fy46VWoNhGxiUQyVZHbBMjQ33mQem0cjdDVwgWVAmlRfgyQ==} + infima@0.2.0-alpha.45: + resolution: {integrity: sha512-uyH0zfr1erU1OohLk0fT4Rrb94AOhguWNOcD9uGrSpRvNB+6gZXUoJX5J0NtvzBO10YZ9PgvA4NFgt+fYg8ojw==} engines: {node: '>=12'} inflight@1.0.6: @@ -3225,14 +3520,11 @@ packages: resolution: {integrity: sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==} engines: {node: '>=10'} - inline-style-parser@0.1.1: - resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} + inline-style-parser@0.2.4: + resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} - inline-style-parser@0.2.3: - resolution: {integrity: sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g==} - - internal-slot@1.0.7: - resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} interpret@1.4.0: @@ -3256,22 +3548,27 @@ packages: is-alphanumerical@2.0.1: resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} - is-array-buffer@3.0.4: - resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} - is-bigint@1.0.4: - resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + is-async-function@2.1.0: + resolution: {integrity: sha512-GExz9MtyhlZyXYLxzlJRj5WUCE661zhDa1Yna52CN57AJsymh+DvXXjyveSioqSRdxvUrdKdvqB1b5cVKsNpWQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} is-binary-path@2.1.0: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} - is-boolean-object@1.1.2: - resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + is-boolean-object@1.2.1: + resolution: {integrity: sha512-l9qO6eFlUETHtuihLcYOaLKByJ1f+N4kthcU9YjHy3N+B3hWv0y/2Nd0mu/7lTFnRQHTrSdXF50HQ3bl5fEnng==} engines: {node: '>= 0.4'} is-callable@1.2.7: @@ -3282,16 +3579,16 @@ packages: resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} hasBin: true - is-core-module@2.14.0: - resolution: {integrity: sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==} + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} - is-data-view@1.0.1: - resolution: {integrity: sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==} + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} engines: {node: '>= 0.4'} - is-date-object@1.0.5: - resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} engines: {node: '>= 0.4'} is-decimal@2.0.1: @@ -3310,10 +3607,18 @@ packages: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-generator-function@1.1.0: + resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==} + engines: {node: '>= 0.4'} + is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -3325,16 +3630,16 @@ packages: resolution: {integrity: sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==} engines: {node: '>=10'} - is-negative-zero@2.0.3: - resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} is-npm@6.0.0: resolution: {integrity: sha512-JEjxbSmtPSt1c8XTkVrlujcXdKV1/tvuQ7GwKcAlyiVLeYFQ2VHat8xfrDJsIkhCdF/tZ7CiIR3sy141c6+gPQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - is-number-object@1.0.7: - resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} engines: {node: '>= 0.4'} is-number@7.0.0: @@ -3372,11 +3677,8 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} - is-reference@3.0.2: - resolution: {integrity: sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==} - - is-regex@1.1.4: - resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} engines: {node: '>= 0.4'} is-regexp@1.0.0: @@ -3387,38 +3689,47 @@ packages: resolution: {integrity: sha512-AGOriNp96vNBd3HtU+RzFEc75FfR5ymiYv8E553I71SCeXBiMsVDUtdio1OEFvrPyLIQ9tVR5RxXIFe5PUFjMg==} engines: {node: '>=6'} - is-shared-array-buffer@1.0.3: - resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} engines: {node: '>= 0.4'} is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} - is-stream@4.0.1: - resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} - engines: {node: '>=18'} - - is-string@1.0.7: - resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} engines: {node: '>= 0.4'} is-subset@0.1.1: resolution: {integrity: sha512-6Ybun0IkarhmEqxXCNw/C0bna6Zb/TkfUX9UbwJtK6ObwAVCxmAP308WWTHviM/zAqXk05cdhYsUsZeGQh99iw==} - is-symbol@1.0.4: - resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} engines: {node: '>= 0.4'} - is-typed-array@1.1.13: - resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==} + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} is-typedarray@1.0.0: resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} - is-weakref@1.0.2: - resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.0: + resolution: {integrity: sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} is-wsl@2.2.0: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} @@ -3444,10 +3755,14 @@ packages: resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} engines: {node: '>=0.10.0'} - isomorphic-dompurify@2.12.0: - resolution: {integrity: sha512-jJm6VgJ9toBLqNUHuLudn+2Q3NBBaoPbsh5SzzO2dp9Zq9+p6fEg4Ffuq9RZsofb8OnqE6FJVVq3MRDLlmBHpA==} + isomorphic-dompurify@2.19.0: + resolution: {integrity: sha512-ppcgeRlEwOQ+v/JDctcjnOsBwEoJlAWVDH5+LisLHphQFeWCrBiVvK6XF4wF0MJM5tJA6RxJSlpbmthnmonxOQ==} engines: {node: '>=18'} + isomorphic-rslog@0.0.6: + resolution: {integrity: sha512-HM0q6XqQ93psDlqvuViNs/Ea3hAyGDkIdVAHlrEocjjAwGrs1fZ+EdQjS9eUPacnYB7Y8SoDdSY3H8p3ce205A==} + engines: {node: '>=14.17.6'} + jest-util@29.7.0: resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3460,8 +3775,8 @@ packages: resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - jiti@1.21.6: - resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} hasBin: true joi@17.13.3: @@ -3482,8 +3797,8 @@ packages: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true - jsdom@24.1.0: - resolution: {integrity: sha512-6gpM7pRXCwIOKxX47cgOyvyQDN/Eh0f1MeKySBV2xGdKtqJBLj8P25eY3EVCWo2mglDDzozR2r2MW4T+JiNUZA==} + jsdom@25.0.1: + resolution: {integrity: sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==} engines: {node: '>=18'} peerDependencies: canvas: ^2.11.2 @@ -3491,13 +3806,14 @@ packages: canvas: optional: true - jsesc@0.5.0: - resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} + jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} hasBin: true - jsesc@2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} - engines: {node: '>=4'} + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} hasBin: true json-buffer@3.0.1: @@ -3541,15 +3857,79 @@ packages: resolution: {integrity: sha512-KvNT4XqAMzdcL6ka6Tl3i2lYeFDgXNCuIX+xNx6ZMVR1dFq+idXd9FLKNMOIx0t9mJ9/HudyX4oZWXZQ0UJHeg==} engines: {node: '>=14.16'} - launch-editor@2.8.0: - resolution: {integrity: sha512-vJranOAJrI/llyWGRQqiDM+adrw+k83fvmmx3+nV47g3+36xM15jE+zyZ6Ffel02+xSvuM0b2GDRosXZkbb6wA==} + launch-editor@2.9.1: + resolution: {integrity: sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w==} leven@3.1.0: resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} engines: {node: '>=6'} - lilconfig@3.1.2: - resolution: {integrity: sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==} + lightningcss-darwin-arm64@1.28.2: + resolution: {integrity: sha512-/8cPSqZiusHSS+WQz0W4NuaqFjquys1x+NsdN/XOHb+idGHJSoJ7SoQTVl3DZuAgtPZwFZgRfb/vd1oi8uX6+g==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.28.2: + resolution: {integrity: sha512-R7sFrXlgKjvoEG8umpVt/yutjxOL0z8KWf0bfPT3cYMOW4470xu5qSHpFdIOpRWwl3FKNMUdbKtMUjYt0h2j4g==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.28.2: + resolution: {integrity: sha512-l2qrCT+x7crAY+lMIxtgvV10R8VurzHAoUZJaVFSlHrN8kRLTvEg9ObojIDIexqWJQvJcVVV3vfzsEynpiuvgA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.28.2: + resolution: {integrity: sha512-DKMzpICBEKnL53X14rF7hFDu8KKALUJtcKdFUCW5YOlGSiwRSgVoRjM97wUm/E0NMPkzrTi/rxfvt7ruNK8meg==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.28.2: + resolution: {integrity: sha512-nhfjYkfymWZSxdtTNMWyhFk2ImUm0X7NAgJWFwnsYPOfmtWQEapzG/DXZTfEfMjSzERNUNJoQjPAbdqgB+sjiw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.28.2: + resolution: {integrity: sha512-1SPG1ZTNnphWvAv8RVOymlZ8BDtAg69Hbo7n4QxARvkFVCJAt0cgjAw1Fox0WEhf4PwnyoOBaVH0Z5YNgzt4dA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.28.2: + resolution: {integrity: sha512-ZhQy0FcO//INWUdo/iEdbefntTdpPVQ0XJwwtdbBuMQe+uxqZoytm9M+iqR9O5noWFaxK+nbS2iR/I80Q2Ofpg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.28.2: + resolution: {integrity: sha512-alb/j1NMrgQmSFyzTbN1/pvMPM+gdDw7YBuQ5VSgcFDypN3Ah0BzC2dTZbzwzaMdUVDszX6zH5MzjfVN1oGuww==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.28.2: + resolution: {integrity: sha512-WnwcjcBeAt0jGdjlgbT9ANf30pF0C/QMb1XnLnH272DQU8QXh+kmpi24R55wmWBwaTtNAETZ+m35ohyeMiNt+g==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.28.2: + resolution: {integrity: sha512-3piBifyT3avz22o6mDKywQC/OisH2yDK+caHWkiMsF82i3m5wDBadyCjlCQ5VNgzYkxrWZgiaxHDdd5uxsi0/A==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.28.2: + resolution: {integrity: sha512-ePLRrbt3fgjXI5VFZOLbvkLD5ZRuxGKm+wJ3ujCqBtL3NanDHPo/5zicR5uEKAPiIjBYF99BM4K4okvMznjkVA==} + engines: {node: '>= 12.0.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} lines-and-columns@1.2.4: @@ -3630,11 +4010,14 @@ packages: resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} engines: {node: '>=16'} - markdown-table@3.0.3: - resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} + markdown-table@2.0.0: + resolution: {integrity: sha512-Ezda85ToJUBhM6WGaG6veasyym+Tbs3cMAw/ZhOPqXiYsr0jgocBV3j3nx+4lk47plLlIqjwuTm/ywVI+zjJ/A==} - marked@13.0.2: - resolution: {integrity: sha512-J6CPjP8pS5sgrRqxVRvkCIkZ6MFdRIjDkwUwgJ9nL2fbmM6qGQeB2C16hi8Cc9BOzj6xXzy0jyi0iPIfnMHYzA==} + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + marked@15.0.5: + resolution: {integrity: sha512-xN+kSuqHjxWg+Q47yhhZMUP+kO1qHobvXkkm6FX+7N6lDvanLDd8H7AQ0jWDDyq+fDt/cSrJaBGyWYHXy0KQWA==} engines: {node: '>= 18'} hasBin: true @@ -3643,20 +4026,24 @@ packages: engines: {node: '>= 12'} hasBin: true + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + mdast-util-directive@3.0.0: resolution: {integrity: sha512-JUpYOqKI4mM3sZcNxmF/ox04XYFFkNwr0CFlrQIkCwbvH0xzMCqkMqAde9wRd80VAhaUrwFwKm2nxretdT1h7Q==} - mdast-util-find-and-replace@3.0.1: - resolution: {integrity: sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA==} + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} - mdast-util-from-markdown@2.0.1: - resolution: {integrity: sha512-aJEUyzZ6TzlsX2s5B4Of7lN7EQtAxvtradMMglCQDyaTFgse6CmtmdJ15ElnVRlCg1vpNyVtbem0PWzlNieZsA==} + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} mdast-util-frontmatter@2.0.1: resolution: {integrity: sha512-LRqI9+wdgC25P0URIJY9vwocIzCcksduHQ9OF2joxQoyTNVduwLAFUzjoopuRJbJAReaKrNQKAZKL3uCMugWJA==} - mdast-util-gfm-autolink-literal@2.0.0: - resolution: {integrity: sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg==} + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} mdast-util-gfm-footnote@2.0.0: resolution: {integrity: sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==} @@ -3673,11 +4060,11 @@ packages: mdast-util-gfm@3.0.0: resolution: {integrity: sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==} - mdast-util-mdx-expression@2.0.0: - resolution: {integrity: sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw==} + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} - mdast-util-mdx-jsx@3.1.2: - resolution: {integrity: sha512-eKMQDeywY2wlHc97k5eD8VC+9ASMjN8ItEZQNGwJ6E0XWKiW/Z0V5/H8pvoXUf+y+Mj0VIgeRRbujBmFn4FTyA==} + mdast-util-mdx-jsx@3.1.3: + resolution: {integrity: sha512-bfOjvNt+1AcbPLTFMFWY149nJz0OjmewJs3LQQ5pIyVGxP4CdOqNVJL6kTaM5c68p8q82Xv3nCyFfUnuEcH3UQ==} mdast-util-mdx@3.0.0: resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} @@ -3691,8 +4078,8 @@ packages: mdast-util-to-hast@13.2.0: resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} - mdast-util-to-markdown@2.1.0: - resolution: {integrity: sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==} + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} @@ -3711,8 +4098,8 @@ packages: resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} engines: {node: '>= 4.0.0'} - merge-descriptors@1.0.1: - resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -3725,11 +4112,11 @@ packages: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} - micromark-core-commonmark@2.0.1: - resolution: {integrity: sha512-CUQyKr1e///ZODyD1U3xit6zXwy1a8q2a1S1HKtIlmgvurrEpaw/Y9y6KSIbF8P59cn/NjzHyO+Q2fAyYLQrAA==} + micromark-core-commonmark@2.0.2: + resolution: {integrity: sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==} - micromark-extension-directive@3.0.0: - resolution: {integrity: sha512-61OI07qpQrERc+0wEysLHMvoiO3s2R56x5u7glHq2Yqq6EHbH4dW25G9GfDdGCDYqA21KE6DWgNSzxSwHc2hSg==} + micromark-extension-directive@3.0.2: + resolution: {integrity: sha512-wjcXHgk+PPdmvR58Le9d7zQYWy+vKEU9Se44p2CrCDPiLr2FMyiT4Fyb5UFKFC66wGB3kPlgD7q3TnoqPS7SZA==} micromark-extension-frontmatter@2.0.0: resolution: {integrity: sha512-C4AkuM3dA58cgZha7zVnuVxBhDsbttIMiytjgsM2XbHAB2faRVaHRle40558FBN+DJcrLNCoqG5mlrpdU4cRtg==} @@ -3758,8 +4145,8 @@ packages: micromark-extension-mdx-expression@3.0.0: resolution: {integrity: sha512-sI0nwhUDz97xyzqJAbHQhp5TfaxEvZZZ2JDqUo+7NvyIYG6BZ5CPPqj2ogUoPJlmXHBnyZUzISg9+oUmU6tUjQ==} - micromark-extension-mdx-jsx@3.0.0: - resolution: {integrity: sha512-uvhhss8OGuzR4/N17L1JwvmJIpPhAd8oByMawEKx6NVdBCbesjH4t+vjEp3ZXft9DwvlKSD07fCeI44/N0Vf2w==} + micromark-extension-mdx-jsx@3.0.1: + resolution: {integrity: sha512-vNuFb9czP8QCtAQcEJn0UJQJZA8Dk6DXKBqx+bg/w0WGuSxDxNr7hErW89tHUY31dUW4NqEOWwmEUNhjTFmHkg==} micromark-extension-mdx-md@2.0.0: resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} @@ -3770,86 +4157,86 @@ packages: micromark-extension-mdxjs@3.0.0: resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} - micromark-factory-destination@2.0.0: - resolution: {integrity: sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA==} + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} - micromark-factory-label@2.0.0: - resolution: {integrity: sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw==} + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} - micromark-factory-mdx-expression@2.0.1: - resolution: {integrity: sha512-F0ccWIUHRLRrYp5TC9ZYXmZo+p2AM13ggbsW4T0b5CRKP8KHVRB8t4pwtBgTxtjRmwrK0Irwm7vs2JOZabHZfg==} + micromark-factory-mdx-expression@2.0.2: + resolution: {integrity: sha512-5E5I2pFzJyg2CtemqAbcyCktpHXuJbABnsb32wX2U8IQKhhVFBqkcZR5LRm1WVoFqa4kTueZK4abep7wdo9nrw==} micromark-factory-space@1.1.0: resolution: {integrity: sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==} - micromark-factory-space@2.0.0: - resolution: {integrity: sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg==} + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} - micromark-factory-title@2.0.0: - resolution: {integrity: sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A==} + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} - micromark-factory-whitespace@2.0.0: - resolution: {integrity: sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA==} + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} micromark-util-character@1.2.0: resolution: {integrity: sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==} - micromark-util-character@2.1.0: - resolution: {integrity: sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==} + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} - micromark-util-chunked@2.0.0: - resolution: {integrity: sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg==} + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} - micromark-util-classify-character@2.0.0: - resolution: {integrity: sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw==} + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} - micromark-util-combine-extensions@2.0.0: - resolution: {integrity: sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ==} + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} - micromark-util-decode-numeric-character-reference@2.0.1: - resolution: {integrity: sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ==} + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} - micromark-util-decode-string@2.0.0: - resolution: {integrity: sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA==} + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} - micromark-util-encode@2.0.0: - resolution: {integrity: sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==} + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} micromark-util-events-to-acorn@2.0.2: resolution: {integrity: sha512-Fk+xmBrOv9QZnEDguL9OI9/NQQp6Hz4FuQ4YmCb/5V7+9eAh1s6AYSvL20kHkD67YIg7EpE54TiSlcsf3vyZgA==} - micromark-util-html-tag-name@2.0.0: - resolution: {integrity: sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw==} + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} - micromark-util-normalize-identifier@2.0.0: - resolution: {integrity: sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w==} + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} - micromark-util-resolve-all@2.0.0: - resolution: {integrity: sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA==} + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} - micromark-util-sanitize-uri@2.0.0: - resolution: {integrity: sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==} + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} - micromark-util-subtokenize@2.0.1: - resolution: {integrity: sha512-jZNtiFl/1aY73yS3UGQkutD0UbhTt68qnRpw2Pifmz5wV9h8gOVsN70v+Lq/f1rKaU/W8pxRe8y8Q9FX1AOe1Q==} + micromark-util-subtokenize@2.0.3: + resolution: {integrity: sha512-VXJJuNxYWSoYL6AJ6OQECCFGhIU2GGHMw8tahogePBrjkG8aCCas3ibkp7RnVOSTClg2is05/R7maAhF1XyQMg==} micromark-util-symbol@1.1.0: resolution: {integrity: sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==} - micromark-util-symbol@2.0.0: - resolution: {integrity: sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==} + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} micromark-util-types@1.1.0: resolution: {integrity: sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==} - micromark-util-types@2.0.0: - resolution: {integrity: sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==} + micromark-util-types@2.0.1: + resolution: {integrity: sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==} - micromark@4.0.0: - resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==} + micromark@4.0.1: + resolution: {integrity: sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==} - micromatch@4.0.7: - resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} mime-db@1.33.0: @@ -3860,6 +4247,10 @@ packages: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} engines: {node: '>= 0.6'} + mime-db@1.53.0: + resolution: {integrity: sha512-oHlN/w+3MQ3rba9rqFr6V/ypF10LSkdwUysQL7GkXoTgIWeV+tcXGA852TBxH+gsh8UWoyhR1hKcoMJTuWflpg==} + engines: {node: '>= 0.6'} + mime-types@2.1.18: resolution: {integrity: sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==} engines: {node: '>= 0.6'} @@ -3885,8 +4276,8 @@ packages: resolution: {integrity: sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - mini-css-extract-plugin@2.9.0: - resolution: {integrity: sha512-Zs1YsZVfemekSZG+44vBsYTLQORkPMwnlv+aehcxK/NLKC+EGhDB39/YePYYqx/sTk6NnYpuqikhSn7+JIevTA==} + mini-css-extract-plugin@2.9.2: + resolution: {integrity: sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==} engines: {node: '>= 12.13.0'} peerDependencies: webpack: ^5.0.0 @@ -3904,11 +4295,11 @@ packages: minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - mobx-react-lite@4.0.7: - resolution: {integrity: sha512-RjwdseshK9Mg8On5tyJZHtGD+J78ZnCnRaxeQDSiciKVQDUbfZcXhmld0VMxAwvcTnPEHZySGGewm467Fcpreg==} + mobx-react-lite@4.1.0: + resolution: {integrity: sha512-QEP10dpHHBeQNv1pks3WnHRCem2Zp636lq54M2nKO2Sarr13pL4u6diQXf65yzXUn0mkk18SyIDCm9UOJYTi1w==} peerDependencies: mobx: ^6.9.0 - react: ^16.8.0 || ^17 || ^18 + react: ^16.8.0 || ^17 || ^18 || ^19 react-dom: '*' react-native: '*' peerDependenciesMeta: @@ -3917,11 +4308,11 @@ packages: react-native: optional: true - mobx-react@9.1.1: - resolution: {integrity: sha512-gVV7AdSrAAxqXOJ2bAbGa5TkPqvITSzaPiiEkzpW4rRsMhSec7C2NBCJYILADHKp2tzOAIETGRsIY0UaCV5aEw==} + mobx-react@9.2.0: + resolution: {integrity: sha512-dkGWCx+S0/1mfiuFfHRH8D9cplmwhxOV5CkXMp38u6rQGG2Pv3FWYztS0M7ncR6TyPRQKaTG/pnitInoYE9Vrw==} peerDependencies: mobx: ^6.9.0 - react: ^16.8.0 || ^17 || ^18 + react: ^16.8.0 || ^17 || ^18 || ^19 react-dom: '*' react-native: '*' peerDependenciesMeta: @@ -3930,8 +4321,8 @@ packages: react-native: optional: true - mobx@6.13.0: - resolution: {integrity: sha512-1laWODrBWmB7mDJ8EClCjUQTyLwJ0ydJgE4FtK7t9r3JnjXgc9OhmYs2P4RtHrY1co5+4T6cKP2UswX2SU29mA==} + mobx@6.13.5: + resolution: {integrity: sha512-/HTWzW2s8J1Gqt+WmUj5Y0mddZk+LInejADc79NJadrWla3rHzmRHki/mnEUH1AvOmbNTZ1BRbKxr8DSgfdjMA==} moo@0.5.2: resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==} @@ -3943,9 +4334,6 @@ packages: ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} - ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -3953,8 +4341,8 @@ packages: resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==} hasBin: true - nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true @@ -3966,14 +4354,18 @@ packages: resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} engines: {node: '>= 0.6'} + negotiator@0.6.4: + resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} + engines: {node: '>= 0.6'} + neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} - node-emoji@2.1.3: - resolution: {integrity: sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==} + node-emoji@2.2.0: + resolution: {integrity: sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==} engines: {node: '>=18'} node-fetch-h2@2.3.0: @@ -3996,8 +4388,8 @@ packages: node-readfiles@0.2.0: resolution: {integrity: sha512-SU00ZarexNlE4Rjdm83vglt5Y9yiQ+XI1XpflWlb7q7UTN1JUItm69xMeiQCTxtTfnzt+83T8Cx+vI2ED++VDA==} - node-releases@2.0.14: - resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} @@ -4021,8 +4413,14 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - nwsapi@2.2.10: - resolution: {integrity: sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==} + null-loader@4.0.1: + resolution: {integrity: sha512-pxqVbi4U6N26lq+LmgIbB5XATP0VdZKOG25DhHi8btMmJJefGArFyDg1yc4U3hWCJbMqSrw0qyrz1UQX+qYXqg==} + engines: {node: '>= 10.13.0'} + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + + nwsapi@2.2.16: + resolution: {integrity: sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==} oas-kit-common@1.0.8: resolution: {integrity: sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==} @@ -4044,8 +4442,8 @@ packages: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - object-inspect@1.13.2: - resolution: {integrity: sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==} + object-inspect@1.13.3: + resolution: {integrity: sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==} engines: {node: '>= 0.4'} object-is@1.1.6: @@ -4056,16 +4454,16 @@ packages: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} - object.assign@4.1.5: - resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} engines: {node: '>= 0.4'} object.entries@1.1.8: resolution: {integrity: sha512-cmopxi8VwRIAw/fkijJohSfpef5PdN0pMQJN6VC/ZKvn0LIknWD8KtgY6KlQdEc4tIjcQ3HxSMmnvtzIscdaYQ==} engines: {node: '>= 0.4'} - object.values@1.2.0: - resolution: {integrity: sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==} + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} obuf@1.1.2: @@ -4090,16 +4488,20 @@ packages: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} engines: {node: '>=12'} - openapi-sampler@1.5.1: - resolution: {integrity: sha512-tIWIrZUKNAsbqf3bd9U1oH6JEXo8LNYuDlXw26By67EygpjT+ArFnsxxyTMjFWRfbqo5ozkvgSQDK69Gd8CddA==} + openapi-sampler@1.6.1: + resolution: {integrity: sha512-s1cIatOqrrhSj2tmJ4abFYZQK6l5v+V4toO5q1Pa0DyN8mtyqy2I+Qrj5W9vOELEtybIMQs/TBZGVO/DtTFK8w==} opener@1.5.2: resolution: {integrity: sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==} hasBin: true - p-cancelable@4.0.1: - resolution: {integrity: sha512-wBowNApzd45EIKdO1LaU+LrMBwAcjfPaYtVzV3lmfM3gf8Z4CHZsiIqlM8TZZ8okYvh5A1cP6gTfCRQtwUpaUg==} - engines: {node: '>=14.16'} + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-cancelable@3.0.0: + resolution: {integrity: sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==} + engines: {node: '>=12.20'} p-limit@2.3.0: resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} @@ -4148,8 +4550,8 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} - parse-entities@4.0.1: - resolution: {integrity: sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w==} + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} @@ -4158,11 +4560,14 @@ packages: parse-numeric-range@1.3.0: resolution: {integrity: sha512-twN+njEipszzlMJd4ONUYgSfZPDxgHhT9Ahed5uTigpQn90FggW4SA/AIPq/6a149fTbE9qBEcSwE3FAEp6wQQ==} - parse5-htmlparser2-tree-adapter@7.0.0: - resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==} + parse5-htmlparser2-tree-adapter@7.1.0: + resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==} - parse5@7.1.2: - resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + parse5-parser-stream@7.1.2: + resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==} + + parse5@7.2.1: + resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} @@ -4200,30 +4605,27 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - path-to-regexp@0.1.7: - resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} + path-to-regexp@0.1.12: + resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} - path-to-regexp@1.8.0: - resolution: {integrity: sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==} + path-to-regexp@1.9.0: + resolution: {integrity: sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==} - path-to-regexp@2.2.1: - resolution: {integrity: sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==} + path-to-regexp@3.3.0: + resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - perfect-scrollbar@1.5.5: - resolution: {integrity: sha512-dzalfutyP3e/FOpdlhVryN4AJ5XDVauVWxybSkLZmakFE2sS3y3pc4JnSprw8tGmHvkaG5Edr5T7LBTZ+WWU2g==} + perfect-scrollbar@1.5.6: + resolution: {integrity: sha512-rixgxw3SxyJbCaSpo1n35A/fwI1r2rdwMKOTCg/AcG+xOEyZcE8UHVjpZMFCVImzsFoCZeJTT+M/rdEIQYO2nw==} performance-now@2.1.0: resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==} - periscopic@3.1.0: - resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} - - picocolors@1.0.1: - resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} @@ -4249,12 +4651,42 @@ packages: resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==} engines: {node: '>= 0.4'} + postcss-attribute-case-insensitive@7.0.1: + resolution: {integrity: sha512-Uai+SupNSqzlschRyNx3kbCTWgY/2hcwtHEI/ej2LJWc9JJ77qKgGptd8DHwY1mXtZ7Aoh4z4yxfwMBue9eNgw==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + postcss-calc@9.0.1: resolution: {integrity: sha512-TipgjGyzP5QzEhsOZUaIkeO5mKeMFpebWzRogWG/ysonUlnHcq5aJe0jOjpfzUU8PeSaBQnrE8ehR0QA5vs8PQ==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.2.2 + postcss-clamp@4.1.0: + resolution: {integrity: sha512-ry4b1Llo/9zz+PKC+030KUnPITTJAHeOwjfAyyB60eT0AorGLdzp52s31OsPRHRf8NchkgFoG2y6fCfn1IV1Ow==} + engines: {node: '>=7.6.0'} + peerDependencies: + postcss: ^8.4.6 + + postcss-color-functional-notation@7.0.7: + resolution: {integrity: sha512-EZvAHsvyASX63vXnyXOIynkxhaHRSsdb7z6yiXKIovGXAolW4cMZ3qoh7k3VdTsLBS6VGdksGfIo3r6+waLoOw==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-color-hex-alpha@10.0.0: + resolution: {integrity: sha512-1kervM2cnlgPs2a8Vt/Qbe5cQ++N7rkYo/2rz2BkqJZIHQwaVuJgQH38REHrAi4uM0b1fqxMkWYmese94iMp3w==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-color-rebeccapurple@10.0.0: + resolution: {integrity: sha512-JFta737jSP+hdAIEhk1Vs0q0YF5P8fFcj+09pweS8ktuGuZ8pPlykHsk6mPxZ8awDl4TrcxUqJo9l1IhVr/OjQ==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + postcss-colormin@6.1.0: resolution: {integrity: sha512-x9yX7DOxeMAR+BgGVnNSAxmAj98NX/YxEMNFP+SDCEeNLb2r3i6Hh1ksMsnW8Ub5SLCpbescQqn9YEbE9554Sw==} engines: {node: ^14 || ^16 || >=18.0} @@ -4267,6 +4699,30 @@ packages: peerDependencies: postcss: ^8.4.31 + postcss-custom-media@11.0.5: + resolution: {integrity: sha512-SQHhayVNgDvSAdX9NQ/ygcDQGEY+aSF4b/96z7QUX6mqL5yl/JgG/DywcF6fW9XbnCRE+aVYk+9/nqGuzOPWeQ==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-custom-properties@14.0.4: + resolution: {integrity: sha512-QnW8FCCK6q+4ierwjnmXF9Y9KF8q0JkbgVfvQEMa93x1GT8FvOiUevWCN2YLaOWyByeDX8S6VFbZEeWoAoXs2A==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-custom-selectors@8.0.4: + resolution: {integrity: sha512-ASOXqNvDCE0dAJ/5qixxPeL1aOVGHGW2JwSy7HyjWNbnWTQCl+fDc968HY1jCmZI0+BaYT5CxsOiUhavpG/7eg==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-dir-pseudo-class@9.0.1: + resolution: {integrity: sha512-tRBEK0MHYvcMUrAuYMEOa0zg9APqirBcgzi6P21OhxtJyJADo/SWBwY1CAwEohQ/6HDaa9jCjLRG7K3PVQYHEA==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + postcss-discard-comments@6.0.2: resolution: {integrity: sha512-65w/uIqhSBBfQmYnG92FO1mWZjJ4GL5b8atm5Yw2UgrwD7HiNiSSNwJor1eCFGzUgYnN/iIknhNRVqjrrpuglw==} engines: {node: ^14 || ^16 || >=18.0} @@ -4297,6 +4753,47 @@ packages: peerDependencies: postcss: ^8.4.31 + postcss-double-position-gradients@6.0.0: + resolution: {integrity: sha512-JkIGah3RVbdSEIrcobqj4Gzq0h53GG4uqDPsho88SgY84WnpkTpI0k50MFK/sX7XqVisZ6OqUfFnoUO6m1WWdg==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-focus-visible@10.0.1: + resolution: {integrity: sha512-U58wyjS/I1GZgjRok33aE8juW9qQgQUNwTSdxQGuShHzwuYdcklnvK/+qOWX1Q9kr7ysbraQ6ht6r+udansalA==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-focus-within@9.0.1: + resolution: {integrity: sha512-fzNUyS1yOYa7mOjpci/bR+u+ESvdar6hk8XNK/TRR0fiGTp2QT5N+ducP0n3rfH/m9I7H/EQU6lsa2BrgxkEjw==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-font-variant@5.0.0: + resolution: {integrity: sha512-1fmkBaCALD72CK2a9i468mA/+tr9/1cBxRRMXOUaZqO43oWPR5imcyPjXwuv7PXbCid4ndlP5zWhidQVVa3hmA==} + peerDependencies: + postcss: ^8.1.0 + + postcss-gap-properties@6.0.0: + resolution: {integrity: sha512-Om0WPjEwiM9Ru+VhfEDPZJAKWUd0mV1HmNXqp2C29z80aQ2uP9UVhLc7e3aYMIor/S5cVhoPgYQ7RtfeZpYTRw==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-image-set-function@7.0.0: + resolution: {integrity: sha512-QL7W7QNlZuzOwBTeXEmbVckNt1FSmhQtbMRvGGqqU4Nf4xk6KUEQhAoWuMzwbSv5jxiRiSZ5Tv7eiDB9U87znA==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-lab-function@7.0.7: + resolution: {integrity: sha512-+ONj2bpOQfsCKZE2T9VGMyVVdGcGUpr7u3SVfvkJlvhTRmDCfY25k4Jc8fubB9DclAPR4+w8uVtDZmdRgdAHig==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + postcss-loader@7.3.4: resolution: {integrity: sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==} engines: {node: '>= 14.15.0'} @@ -4304,6 +4801,12 @@ packages: postcss: ^7.0.0 || ^8.0.1 webpack: ^5.0.0 + postcss-logical@8.0.0: + resolution: {integrity: sha512-HpIdsdieClTjXLOyYdUPAX/XQASNIwdKt5hoZW08ZOAiI+tbV0ta1oclkpVkW5ANU+xJvk3KkA0FejkjGLXUkg==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + postcss-merge-idents@6.0.3: resolution: {integrity: sha512-1oIoAsODUs6IHQZkLQGO15uGEbK3EAl5wi9SS8hs45VgsxQfMnxvt+L+zIr7ifZFIH14cfAeVe2uCTa+SPRa3g==} engines: {node: ^14 || ^16 || >=18.0} @@ -4352,14 +4855,14 @@ packages: peerDependencies: postcss: ^8.1.0 - postcss-modules-local-by-default@4.0.5: - resolution: {integrity: sha512-6MieY7sIfTK0hYfafw1OMEG+2bg8Q1ocHCpoWLqOKj3JXlKu4G7btkmM/B7lFubYkYWmRSPLZi5chid63ZaZYw==} + postcss-modules-local-by-default@4.2.0: + resolution: {integrity: sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 - postcss-modules-scope@3.2.0: - resolution: {integrity: sha512-oq+g1ssrsZOsx9M96c5w8laRmvEu9C3adDSjI8oTcbfkrTE8hx/zfyobUoWIxaKPO8bt6S62kxpw5GqypEw1QQ==} + postcss-modules-scope@3.2.1: + resolution: {integrity: sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==} engines: {node: ^10 || ^12 || >= 14} peerDependencies: postcss: ^8.1.0 @@ -4370,6 +4873,12 @@ packages: peerDependencies: postcss: ^8.1.0 + postcss-nesting@13.0.1: + resolution: {integrity: sha512-VbqqHkOBOt4Uu3G8Dm8n6lU5+9cJFxiuty9+4rcoyRPO9zZS1JIs6td49VIoix3qYqELHlJIn46Oih9SAKo+yQ==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + postcss-normalize-charset@6.0.2: resolution: {integrity: sha512-a8N9czmdnrjPHa3DeFlwqst5eaL5W8jYu3EBbTTkI5FHkfMhFZh1EGbku6jhHhIzTA6tquI2P42NtZ59M/H/kQ==} engines: {node: ^14 || ^16 || >=18.0} @@ -4424,12 +4933,52 @@ packages: peerDependencies: postcss: ^8.4.31 + postcss-opacity-percentage@3.0.0: + resolution: {integrity: sha512-K6HGVzyxUxd/VgZdX04DCtdwWJ4NGLG212US4/LA1TLAbHgmAsTWVR86o+gGIbFtnTkfOpb9sCRBx8K7HO66qQ==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + postcss-ordered-values@6.0.2: resolution: {integrity: sha512-VRZSOB+JU32RsEAQrO94QPkClGPKJEL/Z9PCBImXMhIeK5KAYo6slP/hBYlLgrCjFxyqvn5VC81tycFEDBLG1Q==} engines: {node: ^14 || ^16 || >=18.0} peerDependencies: postcss: ^8.4.31 + postcss-overflow-shorthand@6.0.0: + resolution: {integrity: sha512-BdDl/AbVkDjoTofzDQnwDdm/Ym6oS9KgmO7Gr+LHYjNWJ6ExORe4+3pcLQsLA9gIROMkiGVjjwZNoL/mpXHd5Q==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-page-break@3.0.4: + resolution: {integrity: sha512-1JGu8oCjVXLa9q9rFTo4MbeeA5FMe00/9C7lN4va606Rdb+HkxXtXsmEDrIraQ11fGz/WvKWa8gMuCKkrXpTsQ==} + peerDependencies: + postcss: ^8 + + postcss-place@10.0.0: + resolution: {integrity: sha512-5EBrMzat2pPAxQNWYavwAfoKfYcTADJ8AXGVPcUZ2UkNloUTWzJQExgrzrDkh3EKzmAx1evfTAzF9I8NGcc+qw==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-prefix-selector@1.16.1: + resolution: {integrity: sha512-Umxu+FvKMwlY6TyDzGFoSUnzW+NOfMBLyC1tAkIjgX+Z/qGspJeRjVC903D7mx7TuBpJlwti2ibXtWuA7fKMeQ==} + peerDependencies: + postcss: '>4 <9' + + postcss-preset-env@10.1.3: + resolution: {integrity: sha512-9qzVhcMFU/MnwYHyYpJz4JhGku/4+xEiPTmhn0hj3IxnUYlEF9vbh7OC1KoLAnenS6Fgg43TKNp9xcuMeAi4Zw==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-pseudo-class-any-link@10.0.1: + resolution: {integrity: sha512-3el9rXlBOqTFaMFkWDOkHUTQekFIYnaQY55Rsp8As8QQkpiSgIYEcF/6Ond93oHiDsGb4kad8zjt+NPlOC1H0Q==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + postcss-reduce-idents@6.0.3: resolution: {integrity: sha512-G3yCqZDpsNPoQgbDUy3T0E6hqOQ5xigUtBQyrmq3tn2GxlyiL0yyl7H+T8ulQR6kOcHJ9t7/9H4/R2tv8tJbMA==} engines: {node: ^14 || ^16 || >=18.0} @@ -4448,8 +4997,23 @@ packages: peerDependencies: postcss: ^8.4.31 - postcss-selector-parser@6.1.0: - resolution: {integrity: sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==} + postcss-replace-overflow-wrap@4.0.0: + resolution: {integrity: sha512-KmF7SBPphT4gPPcKZc7aDkweHiKEEO8cla/GjcBK+ckKxiZslIu3C4GCRW3DNfL0o7yW7kMQu9xlZ1kXRXLXtw==} + peerDependencies: + postcss: ^8.0.3 + + postcss-selector-not@8.0.1: + resolution: {integrity: sha512-kmVy/5PYVb2UOhy0+LqUYAhKj7DUGDpSWa5LZqlkWJaaAV+dxxsOG3+St0yNLu6vsKD7Dmqx+nWQt0iil89+WA==} + engines: {node: '>=18'} + peerDependencies: + postcss: ^8.4 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-selector-parser@7.0.0: + resolution: {integrity: sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==} engines: {node: '>=4'} postcss-sort-media-queries@5.2.0: @@ -4483,8 +5047,8 @@ packages: resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} engines: {node: ^10 || ^12 || >=14} - postcss@8.4.39: - resolution: {integrity: sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==} + postcss@8.4.49: + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} engines: {node: ^10 || ^12 || >=14} pretty-error@4.0.0: @@ -4494,8 +5058,8 @@ packages: resolution: {integrity: sha512-28iF6xPQrP8Oa6uxE6a1biz+lWeTOAPKggvjB8HAs6nVMKZwf5bG++632Dx614hIWgUPkgivRfG+a8uAXGTIbA==} engines: {node: '>=4'} - prism-react-renderer@2.3.1: - resolution: {integrity: sha512-Rdf+HzBLR7KYjzpJ1rSoxT9ioO85nZngQEoFIhL07XhtJHlCU3SOz0GJ6+qvMyQe0Se+BV3qpe6Yd/NmQF5Juw==} + prism-react-renderer@2.4.1: + resolution: {integrity: sha512-ey8Ls/+Di31eqzUxC46h8MksNuGx/n0AAC8uKpwFau4RPDYLuE3EXTp8N8G2vX2N7UC/+IXeNUnlWBGGcAG+Ig==} peerDependencies: react: '>=16.0.0' @@ -4526,12 +5090,6 @@ packages: proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - psl@1.9.0: - resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} - - punycode@1.4.1: - resolution: {integrity: sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -4540,13 +5098,10 @@ packages: resolution: {integrity: sha512-FLpr4flz5xZTSJxSeaheeMKN/EDzMdK7b8PTOC6a5PYFKTucWbdqjgqaEyH0shFiSJrVB1+Qqi4Tk19ccU6Aug==} engines: {node: '>=12.20'} - qs@6.11.0: - resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} + qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} - querystringify@2.2.0: - resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -4601,31 +5156,25 @@ packages: peerDependencies: react: ^18.3.1 + react-dom@19.0.0: + resolution: {integrity: sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==} + peerDependencies: + react: ^19.0.0 + react-error-overlay@6.0.11: resolution: {integrity: sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==} react-fast-compare@3.2.2: resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} - react-helmet-async@1.3.0: - resolution: {integrity: sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg==} - peerDependencies: - react: ^16.6.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.6.0 || ^17.0.0 || ^18.0.0 - - react-helmet-async@2.0.5: - resolution: {integrity: sha512-rYUYHeus+i27MvFE+Jaa4WsyBKGkL6qVgbJvSBoX8mbsWoABJXdEO0bZyi0F6i+4f0NuIb8AvqPMj3iXFHkMwg==} - peerDependencies: - react: ^16.6.0 || ^17.0.0 || ^18.0.0 - react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} - react-json-view-lite@1.4.0: - resolution: {integrity: sha512-wh6F6uJyYAmQ4fK0e8dSQMEWuvTs2Wr3el3sLD9bambX1+pSWUVXIz1RFaoy3TI1mZ0FqdpKq9YgbgTTgyrmXA==} + react-json-view-lite@1.5.0: + resolution: {integrity: sha512-nWqA1E4jKPklL2jvHWs6s+7Na0qNgw9HCP6xehdQJeg6nPBTFZgGwyko9Q0oj+jQWKTTVRS30u0toM5wiuL3iw==} engines: {node: '>=14'} peerDependencies: react: ^16.13.1 || ^17.0.0 || ^18.0.0 @@ -4658,15 +5207,19 @@ packages: peerDependencies: react: ^16.0.0 || ^17.0.0 || ^18.0.0 - react-tabs@6.0.2: - resolution: {integrity: sha512-aQXTKolnM28k3KguGDBSAbJvcowOQr23A+CUJdzJtOSDOtTwzEaJA+1U4KwhNL9+Obe+jFS7geuvA7ICQPXOnQ==} + react-tabs@6.1.0: + resolution: {integrity: sha512-6QtbTRDKM+jA/MZTTefvigNxo0zz+gnBTVFw2CFVvq+f2BuH0nF0vDLNClL045nuTAdOoK/IL1vTP0ZLX0DAyQ==} peerDependencies: - react: ^18.0.0 + react: ^18.0.0 || ^19.0.0 react@18.3.1: resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} engines: {node: '>=0.10.0'} + react@19.0.0: + resolution: {integrity: sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==} + engines: {node: '>=0.10.0'} + readable-stream@2.3.8: resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} @@ -4685,6 +5238,18 @@ packages: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} + recma-build-jsx@1.0.0: + resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} + + recma-jsx@1.0.0: + resolution: {integrity: sha512-5vwkv65qWwYxg+Atz95acp8DMu1JDSqdGkA2Of1j6rCreyFUE/gp15fC8MnGEuG1W68UKjM6x6+YTWIh7hZM/Q==} + + recma-parse@1.0.0: + resolution: {integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==} + + recma-stringify@1.0.0: + resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + recursive-readdir@2.2.3: resolution: {integrity: sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==} engines: {node: '>=6.0.0'} @@ -4699,18 +5264,22 @@ packages: react-dom: ^16.8.4 || ^17.0.0 || ^18.0.0 styled-components: ^4.1.1 || ^5.1.1 || ^6.0.5 - redocusaurus@2.1.1: - resolution: {integrity: sha512-uaiuSsty0TcYuibabEw72DzN5JL6eF9KTIR5dL61qP7smFwIY8THEsNogzKTfcKCb6MJ8ug4vohrnrANn3K3cg==} + redocusaurus@2.2.0: + resolution: {integrity: sha512-cf7kq5RRlwiLNtB4tMH6DBAhmLpZJ3UAOP9QkCHodvf2d46O9m5DOq1o7u6O4XZF65weCm3oDW8eFk6UvLrrtg==} engines: {node: '>=14'} peerDependencies: '@docusaurus/theme-common': ^3.0.0 '@docusaurus/utils': ^3.0.0 + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + reftools@1.1.9: resolution: {integrity: sha512-OVede/NQE13xBQ+ob5CKd5KyeJYU2YInb1bmV4nRoOfquZPkAkxuOXicSe1PvqIuZZ4kD13sPKBbR7UFDmli6w==} - regenerate-unicode-properties@10.1.1: - resolution: {integrity: sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==} + regenerate-unicode-properties@10.2.0: + resolution: {integrity: sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==} engines: {node: '>=4'} regenerate@1.4.2: @@ -4722,29 +5291,35 @@ packages: regenerator-transform@0.15.2: resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} - regexp.prototype.flags@1.5.2: - resolution: {integrity: sha512-NcDiDkTLuPR+++OCKB0nWafEmhg/Da8aUPLPMQbK+bxKKCm1/S5he+AqYa4PlMCVBalb4/yxIRub6qkEx5yJbw==} + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} - regexpu-core@5.3.2: - resolution: {integrity: sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==} + regexpu-core@6.2.0: + resolution: {integrity: sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==} engines: {node: '>=4'} - registry-auth-token@5.0.2: - resolution: {integrity: sha512-o/3ikDxtXaA59BmZuZrJZDJv8NMDGSj+6j6XaeBmHw8eY1i1qd9+6H+LjVvQXx3HN6aRCGa1cUdJ9RaJZUugnQ==} + registry-auth-token@5.0.3: + resolution: {integrity: sha512-1bpc9IyC+e+CNFRaWyn77tk4xGG4PPUyfakSmA6F6cvUDjrm58dfyJ3II+9yb10EDkHoy1LaPSmHaWLOH3m6HA==} engines: {node: '>=14'} registry-url@6.0.1: resolution: {integrity: sha512-+crtS5QjFRqFCoQmvGduwYWEBng99ZvmFvF+cUJkGYF1L1BfU8C6Zp9T7f5vPAwyLkUExpvK+ANVZmGU49qi4Q==} engines: {node: '>=12'} - regjsparser@0.9.1: - resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==} + regjsgen@0.8.0: + resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} + + regjsparser@0.12.0: + resolution: {integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==} hasBin: true rehype-raw@7.0.0: resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + rehype-recma@1.0.0: + resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} + relateurl@0.2.7: resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==} engines: {node: '>= 0.10'} @@ -4762,14 +5337,14 @@ packages: remark-gfm@4.0.0: resolution: {integrity: sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==} - remark-mdx@3.0.1: - resolution: {integrity: sha512-3Pz3yPQ5Rht2pM5R+0J2MrGoBSrzf+tJG94N+t/ilfdh8YLyyKYtidAYwTveB20BoHAcwIopOUqhcmh2F7hGYA==} + remark-mdx@3.1.0: + resolution: {integrity: sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==} remark-parse@11.0.0: resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} - remark-rehype@11.1.0: - resolution: {integrity: sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g==} + remark-rehype@11.1.1: + resolution: {integrity: sha512-g/osARvjkBXb6Wo0XvAeXQohVta8i84ACbenPpoSsxTOQH/Ae0/RGP4WZgnMH5pMLpsj4FG7OHmcIcXxpza8eQ==} remark-stringify@11.0.0: resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} @@ -4777,6 +5352,10 @@ packages: renderkid@3.0.0: resolution: {integrity: sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==} + repeat-string@1.6.1: + resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==} + engines: {node: '>=0.10'} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -4801,11 +5380,9 @@ packages: resolve-pathname@3.0.0: resolution: {integrity: sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng==} - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - - resolve@1.22.8: - resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} hasBin: true responselike@3.0.0: @@ -4829,20 +5406,14 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rrweb-cssom@0.6.0: - resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==} - rrweb-cssom@0.7.1: resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} rst-selector-parser@2.2.3: resolution: {integrity: sha512-nDG1rZeP6oFTLN6yNDV/uiAvs1+FS/KlrEwh7+y7dpuApDBy6bI2HTBcc0/V8lv9OTqfyD34eF7au2pm8aBbhA==} - rtl-detect@1.1.2: - resolution: {integrity: sha512-PGMBq03+TTG/p/cRB7HCLKJ1MgDIi07+QU1faSjiYRfmY5UsAttV9Hs08jDAHVwcOwmVLcSJkpwyfXszVjWfIQ==} - - rtlcss@4.1.1: - resolution: {integrity: sha512-/oVHgBtnPNcggP2aVXQjSy6N1mMAfHg4GSag0QtZBlD5bdDgAHwr4pydqJGd+SUCu9260+Pjqbjwtvu7EMH1KQ==} + rtlcss@4.3.0: + resolution: {integrity: sha512-FI+pHEn7Wc4NqKXMXFM+VAYKEj/mRIcW4h24YVwVtyjI+EqGrLc2Hx/Ny0lrZ21cBWU2goLy36eqMcNj3AQJig==} engines: {node: '>=12.0.0'} hasBin: true @@ -4852,8 +5423,8 @@ packages: rxjs@7.8.1: resolution: {integrity: sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==} - safe-array-concat@1.1.2: - resolution: {integrity: sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==} + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} safe-buffer@5.1.2: @@ -4862,8 +5433,12 @@ packages: safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - safe-regex-test@1.0.3: - resolution: {integrity: sha512-CdASjNJPvRa7roO6Ra/gLYBTzYzzPyyBXxIMdGW3USQLyjWEls2RgW5UBTXaQVp+OrpeCK3bLem8smtmheoRuw==} + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} safer-buffer@2.1.2: @@ -4879,6 +5454,9 @@ packages: scheduler@0.23.2: resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + scheduler@0.25.0: + resolution: {integrity: sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==} + schema-utils@2.7.0: resolution: {integrity: sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==} engines: {node: '>= 8.9.0'} @@ -4887,12 +5465,12 @@ packages: resolution: {integrity: sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==} engines: {node: '>= 10.13.0'} - schema-utils@4.2.0: - resolution: {integrity: sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==} - engines: {node: '>= 12.13.0'} + schema-utils@4.3.0: + resolution: {integrity: sha512-Gf9qqc58SpCA/xdziiHz35F4GNIWYWZrEshUc/G/r5BnLph6xpKuLeoJoQuj5WfBIx/eQLf+hmVPYHaxJu7V2g==} + engines: {node: '>= 10.13.0'} - search-insights@2.14.0: - resolution: {integrity: sha512-OLN6MsPMCghDOqlCtsIsYgtsC0pnwVTyT9Mu6A3ewOj1DxvzZF6COrn2g86E/c05xbktB0XN04m/t1Z+n+fTGw==} + search-insights@2.17.3: + resolution: {integrity: sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==} section-matter@1.0.0: resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} @@ -4913,27 +5491,27 @@ packages: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.6.2: - resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} engines: {node: '>=10'} hasBin: true - send@0.18.0: - resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} + send@0.19.0: + resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} - serve-handler@6.1.5: - resolution: {integrity: sha512-ijPFle6Hwe8zfmBxJdE+5fta53fdIY0lHISJvuikXB3VYFafRjMRpOffSPvCYsbKyBA7pvy9oYr/BT1O3EArlg==} + serve-handler@6.1.6: + resolution: {integrity: sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==} serve-index@1.9.1: resolution: {integrity: sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==} engines: {node: '>= 0.8.0'} - serve-static@1.15.0: - resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} + serve-static@1.16.2: + resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} set-function-length@1.2.2: @@ -4944,6 +5522,10 @@ packages: resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} engines: {node: '>= 0.4'} + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + setprototypeof@1.1.0: resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} @@ -4965,8 +5547,9 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - shell-quote@1.8.1: - resolution: {integrity: sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==} + shell-quote@1.8.2: + resolution: {integrity: sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==} + engines: {node: '>= 0.4'} shelljs@0.8.5: resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==} @@ -4991,8 +5574,20 @@ packages: should@13.2.3: resolution: {integrity: sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==} - side-channel@1.0.6: - resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} signal-exit@3.0.7: @@ -5036,11 +5631,8 @@ packages: resolution: {integrity: sha512-0xtkGhWCC9MGt/EzgnvbbbKhqWjl1+/rncmhTh5qCpbYguXh6S/qwePfv/JQ8jePXXmqingylxoC49pCkSPIbA==} engines: {node: '>= 6.3.0'} - source-list-map@2.0.1: - resolution: {integrity: sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==} - - source-map-js@1.2.0: - resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} source-map-support@0.5.21: @@ -5057,9 +5649,6 @@ packages: space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - spawn-command@0.0.2: - resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==} - spdy-transport@3.0.0: resolution: {integrity: sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==} @@ -5082,8 +5671,8 @@ packages: resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} engines: {node: '>= 0.8'} - std-env@3.7.0: - resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} + std-env@3.8.0: + resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} stickyfill@1.1.1: resolution: {integrity: sha512-GCp7vHAfpao+Qh/3Flh9DXEJ/qSi0KJwJw6zYlZOtRYXWUIpMM6mC2rIep/dK8RQqwW0KxGJIllmjPIBOGN8AA==} @@ -5096,12 +5685,13 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} - string.prototype.trim@1.2.9: - resolution: {integrity: sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==} + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} engines: {node: '>= 0.4'} - string.prototype.trimend@1.0.8: - resolution: {integrity: sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==} + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} string.prototype.trimstart@1.0.8: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} @@ -5144,14 +5734,14 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - style-to-object@0.4.4: - resolution: {integrity: sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==} + strnum@1.0.5: + resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} - style-to-object@1.0.6: - resolution: {integrity: sha512-khxq+Qm3xEyZfKd/y9L3oIWQimxuc4STrQKtQn8aSDRHb8mFgpukgX1hdzfrMEW6JCjyJ8p89x+IUMVnCBI1PA==} + style-to-object@1.0.8: + resolution: {integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==} - styled-components@6.1.11: - resolution: {integrity: sha512-Ui0jXPzbp1phYij90h12ksljKGqF8ncGx+pjrNPsSPhbUUjWT2tD1FwGo2LF6USCnbrsIhNngDfodhxbegfEOA==} + styled-components@6.1.14: + resolution: {integrity: sha512-KtfwhU5jw7UoxdM0g6XU9VZQFV4do+KrM8idiVCH5h4v49W+3p3yMe0icYwJgZQZepa5DbH04Qv8P0/RdcLcgg==} engines: {node: '>= 16'} peerDependencies: react: '>= 16.8.0' @@ -5166,10 +5756,6 @@ packages: stylis@4.3.2: resolution: {integrity: sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==} - supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -5194,6 +5780,12 @@ packages: resolution: {integrity: sha512-upi/0ZGkYgEcLeGieoz8gT74oWHA0E7JivX7aN9mAf+Tc7BQoRBvnIGHoPDw+f9TXTW4s6kGYCZJtauP6OYp7g==} hasBin: true + swc-loader@0.2.6: + resolution: {integrity: sha512-9Zi9UP2YmDpgmQVbyOPJClY0dwf58JDyDMQ7uRc4krmc72twNI2fvlBWHLqVekBpPc7h5NJkGVT1zNDxFrqhvg==} + peerDependencies: + '@swc/core': ^1.2.147 + webpack: '>=2' + symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} @@ -5205,8 +5797,8 @@ packages: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} - terser-webpack-plugin@5.3.10: - resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} + terser-webpack-plugin@5.3.11: + resolution: {integrity: sha512-RVCsMfuD0+cTt3EwX8hSl2Ks56EbFHWmhluwcqoPKtBnfjiT6olaq7PRIRfhyU8nnC2MrnDrBLfrD/RGE+cVXQ==} engines: {node: '>= 10.13.0'} peerDependencies: '@swc/core': '*' @@ -5221,8 +5813,8 @@ packages: uglify-js: optional: true - terser@5.31.1: - resolution: {integrity: sha512-37upzU1+viGvuFtBo9NPufCb9dwM0+l9hMxYyWfBA+fbwrPqNJAhbZ6W47bBFnZHKHTUBnMvi87434qq+qnxOg==} + terser@5.37.0: + resolution: {integrity: sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==} engines: {node: '>=10'} hasBin: true @@ -5238,9 +5830,12 @@ packages: tiny-warning@1.0.3: resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} - to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} + tldts-core@6.1.70: + resolution: {integrity: sha512-RNnIXDB1FD4T9cpQRErEqw6ZpjLlGdMOitdV+0xtbsnwr4YFka1zpc7D4KD+aAn8oSG5JyFrdasZTE04qDE9Yg==} + + tldts@6.1.70: + resolution: {integrity: sha512-/W1YVgYVJd9ZDjey5NXadNh0mJXkiUMUue9Zebd0vpdo1sU+H4zFFTaJ1RKD4N6KFoHfcXy6l+Vu7bh+bdWCzA==} + hasBin: true to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} @@ -5254,9 +5849,9 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} - tough-cookie@4.1.4: - resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} - engines: {node: '>=6'} + tough-cookie@5.0.0: + resolution: {integrity: sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==} + engines: {node: '>=16'} tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -5278,8 +5873,12 @@ packages: tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - tslib@2.6.3: - resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} type-fest@1.4.0: resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} @@ -5289,46 +5888,47 @@ packages: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} - type-fest@4.21.0: - resolution: {integrity: sha512-ADn2w7hVPcK6w1I0uWnM//y1rLXZhzB9mr0a3OirzclKF1Wp6VzevUmzz/NRAWunOT6E8HrnpGY7xOfc6K57fA==} - engines: {node: '>=16'} - type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} - typed-array-buffer@1.0.2: - resolution: {integrity: sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==} + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} - typed-array-byte-length@1.0.1: - resolution: {integrity: sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==} + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} engines: {node: '>= 0.4'} - typed-array-byte-offset@1.0.2: - resolution: {integrity: sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==} + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} engines: {node: '>= 0.4'} - typed-array-length@1.0.6: - resolution: {integrity: sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==} + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} typedarray-to-buffer@3.1.5: resolution: {integrity: sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==} - typescript@5.5.3: - resolution: {integrity: sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==} + typescript@5.7.2: + resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} engines: {node: '>=14.17'} hasBin: true - unbox-primitive@1.0.2: - resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} - unicode-canonical-property-names-ecmascript@2.0.0: - resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} + undici@6.21.0: + resolution: {integrity: sha512-BUgJXc752Kou3oOIuU1i+yZZypyZRqNPW0vqoMPl8VaoalSfeR0D8/t4iAS3yirs79SSMTxTag+ZC86uswv+Cw==} + engines: {node: '>=18.17'} + + unicode-canonical-property-names-ecmascript@2.0.1: + resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} engines: {node: '>=4'} unicode-emoji-modifier-base@1.0.0: @@ -5339,8 +5939,8 @@ packages: resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} engines: {node: '>=4'} - unicode-match-property-value-ecmascript@2.1.0: - resolution: {integrity: sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==} + unicode-match-property-value-ecmascript@2.2.0: + resolution: {integrity: sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==} engines: {node: '>=4'} unicode-property-aliases-ecmascript@2.1.0: @@ -5363,9 +5963,6 @@ packages: unist-util-position@5.0.0: resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} - unist-util-remove-position@5.0.0: - resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} - unist-util-stringify-position@4.0.0: resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} @@ -5375,10 +5972,6 @@ packages: unist-util-visit@5.0.0: resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} - universalify@0.2.0: - resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} - engines: {node: '>= 4.0.0'} - universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -5387,8 +5980,8 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} - update-browserslist-db@1.1.0: - resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==} + update-browserslist-db@1.1.1: + resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -5397,6 +5990,9 @@ packages: resolution: {integrity: sha512-EDxhTEVPZZRLWYcJ4ZXjGFN0oP7qYvbXWzEgRm/Yql4dHX5wDbvh89YHP6PK1lzZJYrMtXUuZZz8XGK+U6U1og==} engines: {node: '>=14.16'} + uri-js-replace@1.0.1: + resolution: {integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==} + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -5410,16 +6006,13 @@ packages: file-loader: optional: true - url-parse@1.5.10: - resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - url-template@2.0.8: resolution: {integrity: sha512-XdVKMF4SJ0nP/O7XIPB0JwAEuT9lDIYnNsK8yGVe43y0AWoKeJNdv3ZNWh7ksJ6KqQFjOO6ox/VEitLnaVNufw==} - use-sync-external-store@1.2.2: - resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} + use-sync-external-store@1.4.0: + resolution: {integrity: sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -5446,21 +6039,21 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - vfile-location@5.0.2: - resolution: {integrity: sha512-NXPYyxyBSH7zB5U6+3uDdd6Nybz6o6/od9rk8bp9H8GR3L+cm/fC0uUTbqBmUTnMCUDslAGBOIKNfvvb+gGlDg==} + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} vfile-message@4.0.2: resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} - vfile@6.0.1: - resolution: {integrity: sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw==} + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} w3c-xmlserializer@5.0.0: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} - watchpack@2.4.1: - resolution: {integrity: sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg==} + watchpack@2.4.2: + resolution: {integrity: sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==} engines: {node: '>=10.13.0'} wbuf@1.7.3: @@ -5504,15 +6097,16 @@ packages: resolution: {integrity: sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==} engines: {node: '>=10.0.0'} - webpack-sources@1.4.3: - resolution: {integrity: sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==} + webpack-merge@6.0.1: + resolution: {integrity: sha512-hXXvrjtx2PLYx4qruKl+kyRSLc52V+cCvMxRjmKwoA+CBbbF5GfIBtR6kCvl0fYGqTUPKB+1ktVmTHqMOzgCBg==} + engines: {node: '>=18.0.0'} webpack-sources@3.2.3: resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} engines: {node: '>=10.13.0'} - webpack@5.92.1: - resolution: {integrity: sha512-JECQ7IwJb+7fgUFBlrJzbyu3GEuNBcdqr1LD7IbSzwkSmIevTm8PF+wej3Oxuz/JFBUZ6O1o43zsPkwm1C4TmA==} + webpack@5.97.1: + resolution: {integrity: sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==} engines: {node: '>=10.13.0'} hasBin: true peerDependencies: @@ -5521,9 +6115,9 @@ packages: webpack-cli: optional: true - webpackbar@5.0.2: - resolution: {integrity: sha512-BmFJo7veBDgQzfWXl/wwYXr/VFus0614qZ8i9znqcl9fnEdiVkdbi0TedLQ6xAK92HZHDJ0QmyQ0fmuZPAgCYQ==} - engines: {node: '>=12'} + webpackbar@6.0.1: + resolution: {integrity: sha512-TnErZpmuKdwWBdMoexjio3KKX6ZtoKHRVvLIU0A47R0VVBDtx3ZyOJDktgYixhoJokZTYTt1Z37OkO9pnGJa9Q==} + engines: {node: '>=14.21.3'} peerDependencies: webpack: 3 || 4 || 5 @@ -5543,18 +6137,27 @@ packages: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} engines: {node: '>=18'} - whatwg-url@14.0.0: - resolution: {integrity: sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==} + whatwg-url@14.1.0: + resolution: {integrity: sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==} engines: {node: '>=18'} whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - which-boxed-primitive@1.0.2: - resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} - which-typed-array@1.1.15: - resolution: {integrity: sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==} + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.18: + resolution: {integrity: sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==} engines: {node: '>= 0.4'} which@1.3.1: @@ -5640,8 +6243,8 @@ packages: resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} engines: {node: '>= 6'} - yaml@2.4.5: - resolution: {integrity: sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==} + yaml@2.7.0: + resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==} engines: {node: '>= 14'} hasBin: true @@ -5666,1089 +6269,1343 @@ packages: snapshots: - '@algolia/autocomplete-core@1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0)(search-insights@2.14.0)': + '@algolia/autocomplete-core@1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0)(search-insights@2.17.3)': dependencies: - '@algolia/autocomplete-plugin-algolia-insights': 1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0)(search-insights@2.14.0) - '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0) + '@algolia/autocomplete-plugin-algolia-insights': 1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0)(search-insights@2.17.3) + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0) transitivePeerDependencies: - '@algolia/client-search' - algoliasearch - search-insights - '@algolia/autocomplete-plugin-algolia-insights@1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0)(search-insights@2.14.0)': + '@algolia/autocomplete-plugin-algolia-insights@1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0)(search-insights@2.17.3)': dependencies: - '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0) - search-insights: 2.14.0 + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0) + search-insights: 2.17.3 transitivePeerDependencies: - '@algolia/client-search' - algoliasearch - '@algolia/autocomplete-preset-algolia@1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0)': + '@algolia/autocomplete-preset-algolia@1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0)': dependencies: - '@algolia/autocomplete-shared': 1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0) - '@algolia/client-search': 4.24.0 - algoliasearch: 4.24.0 + '@algolia/autocomplete-shared': 1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0) + '@algolia/client-search': 5.18.0 + algoliasearch: 5.18.0 - '@algolia/autocomplete-shared@1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0)': + '@algolia/autocomplete-shared@1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0)': dependencies: - '@algolia/client-search': 4.24.0 - algoliasearch: 4.24.0 + '@algolia/client-search': 5.18.0 + algoliasearch: 5.18.0 - '@algolia/cache-browser-local-storage@4.24.0': + '@algolia/client-abtesting@5.18.0': dependencies: - '@algolia/cache-common': 4.24.0 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 - '@algolia/cache-common@4.24.0': {} - - '@algolia/cache-in-memory@4.24.0': + '@algolia/client-analytics@5.18.0': dependencies: - '@algolia/cache-common': 4.24.0 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 - '@algolia/client-account@4.24.0': - dependencies: - '@algolia/client-common': 4.24.0 - '@algolia/client-search': 4.24.0 - '@algolia/transporter': 4.24.0 + '@algolia/client-common@5.18.0': {} - '@algolia/client-analytics@4.24.0': + '@algolia/client-insights@5.18.0': dependencies: - '@algolia/client-common': 4.24.0 - '@algolia/client-search': 4.24.0 - '@algolia/requester-common': 4.24.0 - '@algolia/transporter': 4.24.0 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 - '@algolia/client-common@4.24.0': + '@algolia/client-personalization@5.18.0': dependencies: - '@algolia/requester-common': 4.24.0 - '@algolia/transporter': 4.24.0 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 - '@algolia/client-personalization@4.24.0': + '@algolia/client-query-suggestions@5.18.0': dependencies: - '@algolia/client-common': 4.24.0 - '@algolia/requester-common': 4.24.0 - '@algolia/transporter': 4.24.0 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 - '@algolia/client-search@4.24.0': + '@algolia/client-search@5.18.0': dependencies: - '@algolia/client-common': 4.24.0 - '@algolia/requester-common': 4.24.0 - '@algolia/transporter': 4.24.0 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 '@algolia/events@4.0.1': {} - '@algolia/logger-common@4.24.0': {} - - '@algolia/logger-console@4.24.0': + '@algolia/ingestion@1.18.0': dependencies: - '@algolia/logger-common': 4.24.0 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 - '@algolia/recommend@4.24.0': + '@algolia/monitoring@1.18.0': dependencies: - '@algolia/cache-browser-local-storage': 4.24.0 - '@algolia/cache-common': 4.24.0 - '@algolia/cache-in-memory': 4.24.0 - '@algolia/client-common': 4.24.0 - '@algolia/client-search': 4.24.0 - '@algolia/logger-common': 4.24.0 - '@algolia/logger-console': 4.24.0 - '@algolia/requester-browser-xhr': 4.24.0 - '@algolia/requester-common': 4.24.0 - '@algolia/requester-node-http': 4.24.0 - '@algolia/transporter': 4.24.0 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 - '@algolia/requester-browser-xhr@4.24.0': + '@algolia/recommend@5.18.0': dependencies: - '@algolia/requester-common': 4.24.0 + '@algolia/client-common': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 - '@algolia/requester-common@4.24.0': {} - - '@algolia/requester-node-http@4.24.0': + '@algolia/requester-browser-xhr@5.18.0': dependencies: - '@algolia/requester-common': 4.24.0 + '@algolia/client-common': 5.18.0 - '@algolia/transporter@4.24.0': + '@algolia/requester-fetch@5.18.0': dependencies: - '@algolia/cache-common': 4.24.0 - '@algolia/logger-common': 4.24.0 - '@algolia/requester-common': 4.24.0 + '@algolia/client-common': 5.18.0 + + '@algolia/requester-node-http@5.18.0': + dependencies: + '@algolia/client-common': 5.18.0 '@ampproject/remapping@2.3.0': dependencies: - '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 - '@babel/code-frame@7.24.7': + '@babel/code-frame@7.26.2': dependencies: - '@babel/highlight': 7.24.7 - picocolors: 1.0.1 + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 - '@babel/compat-data@7.24.7': {} + '@babel/compat-data@7.26.3': {} - '@babel/core@7.24.7': + '@babel/core@7.26.0': dependencies: '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.24.7 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) - '@babel/helpers': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/template': 7.24.7 - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.3 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helpers': 7.26.0 + '@babel/parser': 7.26.3 + '@babel/template': 7.25.9 + '@babel/traverse': 7.26.4 + '@babel/types': 7.26.3 convert-source-map: 2.0.0 - debug: 4.3.5 + debug: 4.4.0 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/generator@7.24.7': + '@babel/generator@7.26.3': dependencies: - '@babel/types': 7.24.7 - '@jridgewell/gen-mapping': 0.3.5 + '@babel/parser': 7.26.3 + '@babel/types': 7.26.3 + '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 - jsesc: 2.5.2 + jsesc: 3.1.0 - '@babel/helper-annotate-as-pure@7.24.7': + '@babel/helper-annotate-as-pure@7.25.9': dependencies: - '@babel/types': 7.24.7 + '@babel/types': 7.26.3 - '@babel/helper-builder-binary-assignment-operator-visitor@7.24.7': + '@babel/helper-compilation-targets@7.25.9': dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color - - '@babel/helper-compilation-targets@7.24.7': - dependencies: - '@babel/compat-data': 7.24.7 - '@babel/helper-validator-option': 7.24.7 - browserslist: 4.23.1 + '@babel/compat-data': 7.26.3 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.3 lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.24.7(@babel/core@7.24.7)': + '@babel/helper-create-class-features-plugin@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-function-name': 7.24.7 - '@babel/helper-member-expression-to-functions': 7.24.7 - '@babel/helper-optimise-call-expression': 7.24.7 - '@babel/helper-replace-supers': 7.24.7(@babel/core@7.24.7) - '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 - '@babel/helper-split-export-declaration': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-member-expression-to-functions': 7.25.9 + '@babel/helper-optimise-call-expression': 7.25.9 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/traverse': 7.26.4 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/helper-create-regexp-features-plugin@7.24.7(@babel/core@7.24.7)': + '@babel/helper-create-regexp-features-plugin@7.26.3(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-annotate-as-pure': 7.24.7 - regexpu-core: 5.3.2 + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + regexpu-core: 6.2.0 semver: 6.3.1 - '@babel/helper-define-polyfill-provider@0.6.2(@babel/core@7.24.7)': + '@babel/helper-define-polyfill-provider@0.6.3(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - debug: 4.3.5 + '@babel/core': 7.26.0 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + debug: 4.4.0 lodash.debounce: 4.0.8 - resolve: 1.22.8 + resolve: 1.22.10 transitivePeerDependencies: - supports-color - '@babel/helper-environment-visitor@7.24.7': + '@babel/helper-member-expression-to-functions@7.25.9': dependencies: - '@babel/types': 7.24.7 - - '@babel/helper-function-name@7.24.7': - dependencies: - '@babel/template': 7.24.7 - '@babel/types': 7.24.7 - - '@babel/helper-hoist-variables@7.24.7': - dependencies: - '@babel/types': 7.24.7 - - '@babel/helper-member-expression-to-functions@7.24.7': - dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 + '@babel/traverse': 7.26.4 + '@babel/types': 7.26.3 transitivePeerDependencies: - supports-color - '@babel/helper-module-imports@7.24.7': + '@babel/helper-module-imports@7.25.9': dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 + '@babel/traverse': 7.26.4 + '@babel/types': 7.26.3 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.24.7(@babel/core@7.24.7)': + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-simple-access': 7.24.7 - '@babel/helper-split-export-declaration': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.26.4 transitivePeerDependencies: - supports-color - '@babel/helper-optimise-call-expression@7.24.7': + '@babel/helper-optimise-call-expression@7.25.9': dependencies: - '@babel/types': 7.24.7 + '@babel/types': 7.26.3 - '@babel/helper-plugin-utils@7.24.7': {} + '@babel/helper-plugin-utils@7.25.9': {} - '@babel/helper-remap-async-to-generator@7.24.7(@babel/core@7.24.7)': + '@babel/helper-remap-async-to-generator@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-wrap-function': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-wrap-function': 7.25.9 + '@babel/traverse': 7.26.4 transitivePeerDependencies: - supports-color - '@babel/helper-replace-supers@7.24.7(@babel/core@7.24.7)': + '@babel/helper-replace-supers@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-member-expression-to-functions': 7.24.7 - '@babel/helper-optimise-call-expression': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-member-expression-to-functions': 7.25.9 + '@babel/helper-optimise-call-expression': 7.25.9 + '@babel/traverse': 7.26.4 transitivePeerDependencies: - supports-color - '@babel/helper-simple-access@7.24.7': + '@babel/helper-skip-transparent-expression-wrappers@7.25.9': dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 + '@babel/traverse': 7.26.4 + '@babel/types': 7.26.3 transitivePeerDependencies: - supports-color - '@babel/helper-skip-transparent-expression-wrappers@7.24.7': + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/helper-validator-option@7.25.9': {} + + '@babel/helper-wrap-function@7.25.9': dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 + '@babel/template': 7.25.9 + '@babel/traverse': 7.26.4 + '@babel/types': 7.26.3 transitivePeerDependencies: - supports-color - '@babel/helper-split-export-declaration@7.24.7': + '@babel/helpers@7.26.0': dependencies: - '@babel/types': 7.24.7 + '@babel/template': 7.25.9 + '@babel/types': 7.26.3 - '@babel/helper-string-parser@7.24.7': {} - - '@babel/helper-validator-identifier@7.24.7': {} - - '@babel/helper-validator-option@7.24.7': {} - - '@babel/helper-wrap-function@7.24.7': + '@babel/parser@7.26.3': dependencies: - '@babel/helper-function-name': 7.24.7 - '@babel/template': 7.24.7 - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 + '@babel/types': 7.26.3 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/traverse': 7.26.4 transitivePeerDependencies: - supports-color - '@babel/helpers@7.24.7': + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/template': 7.24.7 - '@babel/types': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/highlight@7.24.7': + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/helper-validator-identifier': 7.24.7 - chalk: 2.4.2 - js-tokens: 4.0.0 - picocolors: 1.0.1 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/parser@7.24.7': + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/types': 7.24.7 - - '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 - '@babel/plugin-transform-optional-chaining': 7.24.7(@babel/core@7.24.7) + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.0) transitivePeerDependencies: - supports-color - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-syntax-import-assertions@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-syntax-import-attributes@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-syntax-jsx@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-syntax-typescript@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-arrow-functions@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-async-generator-functions@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-remap-async-to-generator': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.7) + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/traverse': 7.26.4 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-async-to-generator@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-remap-async-to-generator': 7.24.7(@babel/core@7.24.7) + '@babel/core': 7.26.0 + + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-import-assertions@7.26.0(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-import-attributes@7.26.0(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-arrow-functions@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-async-generator-functions@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.0) + '@babel/traverse': 7.26.4 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-block-scoped-functions@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-async-to-generator@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-block-scoping@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-class-properties@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-remap-async-to-generator': 7.25.9(@babel/core@7.26.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-class-static-block@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-block-scoped-functions@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.7) + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-block-scoping@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-class-properties@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-classes@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-class-static-block@7.26.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-function-name': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-replace-supers': 7.24.7(@babel/core@7.24.7) - '@babel/helper-split-export-declaration': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-classes@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0) + '@babel/traverse': 7.26.4 globals: 11.12.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-computed-properties@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-computed-properties@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/template': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/template': 7.25.9 - '@babel/plugin-transform-destructuring@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-destructuring@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-dotall-regex@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-dotall-regex@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-duplicate-keys@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-duplicate-keys@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-dynamic-import@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.7) + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-exponentiation-operator@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-dynamic-import@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-builder-binary-assignment-operator-visitor': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-exponentiation-operator@7.26.3(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-export-namespace-from@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-for-of@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-export-namespace-from@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-function-name@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.7) - - '@babel/plugin-transform-for-of@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/traverse': 7.26.4 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-function-name@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-json-strings@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-function-name': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-json-strings@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-literals@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.7) + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-literals@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-logical-assignment-operators@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-logical-assignment-operators@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-member-expression-literals@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.7) + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-member-expression-literals@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-modules-amd@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - - '@babel/plugin-transform-modules-amd@7.24.7(@babel/core@7.24.7)': - dependencies: - '@babel/core': 7.24.7 - '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-commonjs@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-modules-commonjs@7.26.3(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-simple-access': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-systemjs@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-modules-systemjs@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-hoist-variables': 7.24.7 - '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.26.4 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-modules-umd@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-modules-umd@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-named-capturing-groups-regex@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-named-capturing-groups-regex@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-new-target@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-new-target@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-nullish-coalescing-operator@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-nullish-coalescing-operator@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.7) + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-numeric-separator@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-numeric-separator@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.7) + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-object-rest-spread@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-object-rest-spread@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.24.7) + '@babel/core': 7.26.0 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-object-super@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-object-super@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-replace-supers': 7.24.7(@babel/core@7.24.7) + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-replace-supers': 7.25.9(@babel/core@7.26.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-optional-catch-binding@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-optional-catch-binding@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.7) + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-optional-chaining@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-optional-chaining@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.7) + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-parameters@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-parameters@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-private-methods@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-private-methods@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-private-property-in-object@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-private-property-in-object@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.7) + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-property-literals@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-property-literals@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-react-constant-elements@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-react-constant-elements@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-react-display-name@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-react-display-name@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-react-jsx-development@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-react-jsx-development@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/plugin-transform-react-jsx': 7.24.7(@babel/core@7.24.7) + '@babel/core': 7.26.0 + '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-jsx@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-react-jsx@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.24.7) - '@babel/types': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) + '@babel/types': 7.26.3 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-pure-annotations@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-react-pure-annotations@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-regenerator@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-regenerator@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 regenerator-transform: 0.15.2 - '@babel/plugin-transform-reserved-words@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-regexp-modifiers@7.26.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-runtime@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-reserved-words@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.24.7) - babel-plugin-polyfill-corejs3: 0.10.4(@babel/core@7.24.7) - babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.24.7) + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + + '@babel/plugin-transform-runtime@7.25.9(@babel/core@7.26.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + babel-plugin-polyfill-corejs2: 0.4.12(@babel/core@7.26.0) + babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.26.0) + babel-plugin-polyfill-regenerator: 0.6.3(@babel/core@7.26.0) semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-shorthand-properties@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-shorthand-properties@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-spread@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-spread@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-skip-transparent-expression-wrappers': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-sticky-regex@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-sticky-regex@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-template-literals@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-template-literals@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-typeof-symbol@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-typeof-symbol@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-typescript@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-typescript@7.26.3(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-annotate-as-pure': 7.24.7 - '@babel/helper-create-class-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 - '@babel/plugin-syntax-typescript': 7.24.7(@babel/core@7.24.7) + '@babel/core': 7.26.0 + '@babel/helper-annotate-as-pure': 7.25.9 + '@babel/helper-create-class-features-plugin': 7.25.9(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-skip-transparent-expression-wrappers': 7.25.9 + '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-unicode-escapes@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-unicode-escapes@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-unicode-property-regex@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-unicode-property-regex@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-unicode-regex@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-unicode-regex@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 - '@babel/plugin-transform-unicode-sets-regex@7.24.7(@babel/core@7.24.7)': + '@babel/plugin-transform-unicode-sets-regex@7.25.9(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-create-regexp-features-plugin': 7.24.7(@babel/core@7.24.7) - '@babel/helper-plugin-utils': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-create-regexp-features-plugin': 7.26.3(@babel/core@7.26.0) + '@babel/helper-plugin-utils': 7.25.9 - '@babel/preset-env@7.24.7(@babel/core@7.24.7)': + '@babel/preset-env@7.26.0(@babel/core@7.26.0)': dependencies: - '@babel/compat-data': 7.24.7 - '@babel/core': 7.24.7 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-validator-option': 7.24.7 - '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.24.7) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.24.7) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.24.7) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.24.7) - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-import-assertions': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-syntax-import-attributes': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.24.7) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.24.7) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.24.7) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.24.7) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.7) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.24.7) - '@babel/plugin-transform-arrow-functions': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-async-generator-functions': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-async-to-generator': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-block-scoped-functions': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-block-scoping': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-class-properties': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-class-static-block': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-classes': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-computed-properties': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-destructuring': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-dotall-regex': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-duplicate-keys': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-dynamic-import': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-exponentiation-operator': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-export-namespace-from': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-for-of': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-function-name': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-json-strings': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-literals': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-logical-assignment-operators': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-member-expression-literals': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-modules-amd': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-modules-commonjs': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-modules-systemjs': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-modules-umd': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-named-capturing-groups-regex': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-new-target': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-nullish-coalescing-operator': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-numeric-separator': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-object-rest-spread': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-object-super': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-optional-catch-binding': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-optional-chaining': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-parameters': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-private-methods': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-private-property-in-object': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-property-literals': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-regenerator': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-reserved-words': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-shorthand-properties': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-spread': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-sticky-regex': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-template-literals': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-typeof-symbol': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-unicode-escapes': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-unicode-property-regex': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-unicode-regex': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-unicode-sets-regex': 7.24.7(@babel/core@7.24.7) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.24.7) - babel-plugin-polyfill-corejs2: 0.4.11(@babel/core@7.24.7) - babel-plugin-polyfill-corejs3: 0.10.4(@babel/core@7.24.7) - babel-plugin-polyfill-regenerator: 0.6.2(@babel/core@7.24.7) - core-js-compat: 3.37.1 + '@babel/compat-data': 7.26.3 + '@babel/core': 7.26.0 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-validator-option': 7.25.9 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.26.0) + '@babel/plugin-syntax-import-assertions': 7.26.0(@babel/core@7.26.0) + '@babel/plugin-syntax-import-attributes': 7.26.0(@babel/core@7.26.0) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.26.0) + '@babel/plugin-transform-arrow-functions': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-async-generator-functions': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-async-to-generator': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-block-scoped-functions': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-block-scoping': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-class-properties': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-class-static-block': 7.26.0(@babel/core@7.26.0) + '@babel/plugin-transform-classes': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-computed-properties': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-destructuring': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-dotall-regex': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-duplicate-keys': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-dynamic-import': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-exponentiation-operator': 7.26.3(@babel/core@7.26.0) + '@babel/plugin-transform-export-namespace-from': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-for-of': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-function-name': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-json-strings': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-literals': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-logical-assignment-operators': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-member-expression-literals': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-modules-amd': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.0) + '@babel/plugin-transform-modules-systemjs': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-modules-umd': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-named-capturing-groups-regex': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-new-target': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-nullish-coalescing-operator': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-numeric-separator': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-object-rest-spread': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-object-super': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-optional-catch-binding': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-optional-chaining': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-parameters': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-private-methods': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-private-property-in-object': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-property-literals': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-regenerator': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-regexp-modifiers': 7.26.0(@babel/core@7.26.0) + '@babel/plugin-transform-reserved-words': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-shorthand-properties': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-spread': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-sticky-regex': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-template-literals': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-typeof-symbol': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-unicode-escapes': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-unicode-property-regex': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-unicode-regex': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-unicode-sets-regex': 7.25.9(@babel/core@7.26.0) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.26.0) + babel-plugin-polyfill-corejs2: 0.4.12(@babel/core@7.26.0) + babel-plugin-polyfill-corejs3: 0.10.6(@babel/core@7.26.0) + babel-plugin-polyfill-regenerator: 0.6.3(@babel/core@7.26.0) + core-js-compat: 3.39.0 semver: 6.3.1 transitivePeerDependencies: - supports-color - '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.24.7)': + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/types': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/types': 7.26.3 esutils: 2.0.3 - '@babel/preset-react@7.24.7(@babel/core@7.24.7)': + '@babel/preset-react@7.26.3(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-validator-option': 7.24.7 - '@babel/plugin-transform-react-display-name': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-react-jsx': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-react-jsx-development': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-react-pure-annotations': 7.24.7(@babel/core@7.24.7) + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-validator-option': 7.25.9 + '@babel/plugin-transform-react-display-name': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-react-jsx': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-react-jsx-development': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-react-pure-annotations': 7.25.9(@babel/core@7.26.0) transitivePeerDependencies: - supports-color - '@babel/preset-typescript@7.24.7(@babel/core@7.24.7)': + '@babel/preset-typescript@7.26.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 - '@babel/helper-validator-option': 7.24.7 - '@babel/plugin-syntax-jsx': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-modules-commonjs': 7.24.7(@babel/core@7.24.7) - '@babel/plugin-transform-typescript': 7.24.7(@babel/core@7.24.7) + '@babel/core': 7.26.0 + '@babel/helper-plugin-utils': 7.25.9 + '@babel/helper-validator-option': 7.25.9 + '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) + '@babel/plugin-transform-modules-commonjs': 7.26.3(@babel/core@7.26.0) + '@babel/plugin-transform-typescript': 7.26.3(@babel/core@7.26.0) transitivePeerDependencies: - supports-color - '@babel/regjsgen@0.8.0': {} - - '@babel/runtime-corejs3@7.24.7': + '@babel/runtime-corejs3@7.26.0': dependencies: - core-js-pure: 3.37.1 + core-js-pure: 3.39.0 regenerator-runtime: 0.14.1 - '@babel/runtime@7.24.7': + '@babel/runtime@7.26.0': dependencies: regenerator-runtime: 0.14.1 - '@babel/template@7.24.7': + '@babel/template@7.25.9': dependencies: - '@babel/code-frame': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.26.3 + '@babel/types': 7.26.3 - '@babel/traverse@7.24.7': + '@babel/traverse@7.26.4': dependencies: - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-function-name': 7.24.7 - '@babel/helper-hoist-variables': 7.24.7 - '@babel/helper-split-export-declaration': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 - debug: 4.3.5 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.3 + '@babel/parser': 7.26.3 + '@babel/template': 7.25.9 + '@babel/types': 7.26.3 + debug: 4.4.0 globals: 11.12.0 transitivePeerDependencies: - supports-color - '@babel/types@7.24.7': + '@babel/types@7.26.3': dependencies: - '@babel/helper-string-parser': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 - to-fast-properties: 2.0.0 + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 - '@cfaester/enzyme-adapter-react-18@0.8.0(enzyme@3.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@cfaester/enzyme-adapter-react-18@0.8.0(enzyme@3.11.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: enzyme: 3.11.0 enzyme-shallow-equal: 1.0.7 - function.prototype.name: 1.1.6 + function.prototype.name: 1.1.8 has: 1.0.4 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) react-is: 18.3.1 - react-shallow-renderer: 16.15.0(react@18.3.1) + react-shallow-renderer: 16.15.0(react@19.0.0) '@colors/colors@1.5.0': optional: true + '@csstools/cascade-layer-name-parser@2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/color-helpers@5.0.1': {} + + '@csstools/css-calc@2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/css-color-parser@3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/color-helpers': 5.0.1 + '@csstools/css-calc': 2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/css-tokenizer@3.0.3': {} + + '@csstools/media-query-list-parser@4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + + '@csstools/postcss-cascade-layers@5.0.1(postcss@8.4.49)': + dependencies: + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.0.0) + postcss: 8.4.49 + postcss-selector-parser: 7.0.0 + + '@csstools/postcss-color-function@4.0.7(postcss@8.4.49)': + dependencies: + '@csstools/css-color-parser': 3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.4.49) + '@csstools/utilities': 2.0.0(postcss@8.4.49) + postcss: 8.4.49 + + '@csstools/postcss-color-mix-function@3.0.7(postcss@8.4.49)': + dependencies: + '@csstools/css-color-parser': 3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.4.49) + '@csstools/utilities': 2.0.0(postcss@8.4.49) + postcss: 8.4.49 + + '@csstools/postcss-content-alt-text@2.0.4(postcss@8.4.49)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.4.49) + '@csstools/utilities': 2.0.0(postcss@8.4.49) + postcss: 8.4.49 + + '@csstools/postcss-exponential-functions@2.0.6(postcss@8.4.49)': + dependencies: + '@csstools/css-calc': 2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + postcss: 8.4.49 + + '@csstools/postcss-font-format-keywords@4.0.0(postcss@8.4.49)': + dependencies: + '@csstools/utilities': 2.0.0(postcss@8.4.49) + postcss: 8.4.49 + postcss-value-parser: 4.2.0 + + '@csstools/postcss-gamut-mapping@2.0.7(postcss@8.4.49)': + dependencies: + '@csstools/css-color-parser': 3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + postcss: 8.4.49 + + '@csstools/postcss-gradients-interpolation-method@5.0.7(postcss@8.4.49)': + dependencies: + '@csstools/css-color-parser': 3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.4.49) + '@csstools/utilities': 2.0.0(postcss@8.4.49) + postcss: 8.4.49 + + '@csstools/postcss-hwb-function@4.0.7(postcss@8.4.49)': + dependencies: + '@csstools/css-color-parser': 3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.4.49) + '@csstools/utilities': 2.0.0(postcss@8.4.49) + postcss: 8.4.49 + + '@csstools/postcss-ic-unit@4.0.0(postcss@8.4.49)': + dependencies: + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.4.49) + '@csstools/utilities': 2.0.0(postcss@8.4.49) + postcss: 8.4.49 + postcss-value-parser: 4.2.0 + + '@csstools/postcss-initial@2.0.0(postcss@8.4.49)': + dependencies: + postcss: 8.4.49 + + '@csstools/postcss-is-pseudo-class@5.0.1(postcss@8.4.49)': + dependencies: + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.0.0) + postcss: 8.4.49 + postcss-selector-parser: 7.0.0 + + '@csstools/postcss-light-dark-function@2.0.7(postcss@8.4.49)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.4.49) + '@csstools/utilities': 2.0.0(postcss@8.4.49) + postcss: 8.4.49 + + '@csstools/postcss-logical-float-and-clear@3.0.0(postcss@8.4.49)': + dependencies: + postcss: 8.4.49 + + '@csstools/postcss-logical-overflow@2.0.0(postcss@8.4.49)': + dependencies: + postcss: 8.4.49 + + '@csstools/postcss-logical-overscroll-behavior@2.0.0(postcss@8.4.49)': + dependencies: + postcss: 8.4.49 + + '@csstools/postcss-logical-resize@3.0.0(postcss@8.4.49)': + dependencies: + postcss: 8.4.49 + postcss-value-parser: 4.2.0 + + '@csstools/postcss-logical-viewport-units@3.0.3(postcss@8.4.49)': + dependencies: + '@csstools/css-tokenizer': 3.0.3 + '@csstools/utilities': 2.0.0(postcss@8.4.49) + postcss: 8.4.49 + + '@csstools/postcss-media-minmax@2.0.6(postcss@8.4.49)': + dependencies: + '@csstools/css-calc': 2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/media-query-list-parser': 4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + postcss: 8.4.49 + + '@csstools/postcss-media-queries-aspect-ratio-number-values@3.0.4(postcss@8.4.49)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/media-query-list-parser': 4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + postcss: 8.4.49 + + '@csstools/postcss-nested-calc@4.0.0(postcss@8.4.49)': + dependencies: + '@csstools/utilities': 2.0.0(postcss@8.4.49) + postcss: 8.4.49 + postcss-value-parser: 4.2.0 + + '@csstools/postcss-normalize-display-values@4.0.0(postcss@8.4.49)': + dependencies: + postcss: 8.4.49 + postcss-value-parser: 4.2.0 + + '@csstools/postcss-oklab-function@4.0.7(postcss@8.4.49)': + dependencies: + '@csstools/css-color-parser': 3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.4.49) + '@csstools/utilities': 2.0.0(postcss@8.4.49) + postcss: 8.4.49 + + '@csstools/postcss-progressive-custom-properties@4.0.0(postcss@8.4.49)': + dependencies: + postcss: 8.4.49 + postcss-value-parser: 4.2.0 + + '@csstools/postcss-random-function@1.0.2(postcss@8.4.49)': + dependencies: + '@csstools/css-calc': 2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + postcss: 8.4.49 + + '@csstools/postcss-relative-color-syntax@3.0.7(postcss@8.4.49)': + dependencies: + '@csstools/css-color-parser': 3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.4.49) + '@csstools/utilities': 2.0.0(postcss@8.4.49) + postcss: 8.4.49 + + '@csstools/postcss-scope-pseudo-class@4.0.1(postcss@8.4.49)': + dependencies: + postcss: 8.4.49 + postcss-selector-parser: 7.0.0 + + '@csstools/postcss-sign-functions@1.1.1(postcss@8.4.49)': + dependencies: + '@csstools/css-calc': 2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + postcss: 8.4.49 + + '@csstools/postcss-stepped-value-functions@4.0.6(postcss@8.4.49)': + dependencies: + '@csstools/css-calc': 2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + postcss: 8.4.49 + + '@csstools/postcss-text-decoration-shorthand@4.0.1(postcss@8.4.49)': + dependencies: + '@csstools/color-helpers': 5.0.1 + postcss: 8.4.49 + postcss-value-parser: 4.2.0 + + '@csstools/postcss-trigonometric-functions@4.0.6(postcss@8.4.49)': + dependencies: + '@csstools/css-calc': 2.1.1(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + postcss: 8.4.49 + + '@csstools/postcss-unset-value@4.0.0(postcss@8.4.49)': + dependencies: + postcss: 8.4.49 + + '@csstools/selector-resolve-nested@3.0.0(postcss-selector-parser@7.0.0)': + dependencies: + postcss-selector-parser: 7.0.0 + + '@csstools/selector-specificity@5.0.0(postcss-selector-parser@7.0.0)': + dependencies: + postcss-selector-parser: 7.0.0 + + '@csstools/utilities@2.0.0(postcss@8.4.49)': + dependencies: + postcss: 8.4.49 + '@discoveryjs/json-ext@0.5.7': {} - '@docsearch/css@3.6.0': {} + '@docsearch/css@3.8.2': {} - '@docsearch/react@3.6.0(@algolia/client-search@4.24.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.14.0)': + '@docsearch/react@3.8.2(@algolia/client-search@5.18.0)(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3)': dependencies: - '@algolia/autocomplete-core': 1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0)(search-insights@2.14.0) - '@algolia/autocomplete-preset-algolia': 1.9.3(@algolia/client-search@4.24.0)(algoliasearch@4.24.0) - '@docsearch/css': 3.6.0 - algoliasearch: 4.24.0 + '@algolia/autocomplete-core': 1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0)(search-insights@2.17.3) + '@algolia/autocomplete-preset-algolia': 1.17.7(@algolia/client-search@5.18.0)(algoliasearch@5.18.0) + '@docsearch/css': 3.8.2 + algoliasearch: 5.18.0 optionalDependencies: - '@types/react': 18.3.3 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - search-insights: 2.14.0 + '@types/react': 19.0.2 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + search-insights: 2.17.3 transitivePeerDependencies: - '@algolia/client-search' - '@docusaurus/core@3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(debug@4.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3)': + '@docusaurus/babel@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/core': 7.24.7 - '@babel/generator': 7.24.7 - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.24.7) - '@babel/plugin-transform-runtime': 7.24.7(@babel/core@7.24.7) - '@babel/preset-env': 7.24.7(@babel/core@7.24.7) - '@babel/preset-react': 7.24.7(@babel/core@7.24.7) - '@babel/preset-typescript': 7.24.7(@babel/core@7.24.7) - '@babel/runtime': 7.24.7 - '@babel/runtime-corejs3': 7.24.7 - '@babel/traverse': 7.24.7 - '@docusaurus/cssnano-preset': 3.4.0 - '@docusaurus/logger': 3.4.0 - '@docusaurus/mdx-loader': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) - '@docusaurus/utils-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) - '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) - autoprefixer: 10.4.19(postcss@8.4.39) - babel-loader: 9.1.3(@babel/core@7.24.7)(webpack@5.92.1) + '@babel/core': 7.26.0 + '@babel/generator': 7.26.3 + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-transform-runtime': 7.25.9(@babel/core@7.26.0) + '@babel/preset-env': 7.26.0(@babel/core@7.26.0) + '@babel/preset-react': 7.26.3(@babel/core@7.26.0) + '@babel/preset-typescript': 7.26.0(@babel/core@7.26.0) + '@babel/runtime': 7.26.0 + '@babel/runtime-corejs3': 7.26.0 + '@babel/traverse': 7.26.4 + '@docusaurus/logger': 3.7.0 + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) babel-plugin-dynamic-import-node: 2.3.3 + fs-extra: 11.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - '@swc/core' + - acorn + - esbuild + - react + - react-dom + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/babel@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@babel/core': 7.26.0 + '@babel/generator': 7.26.3 + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.26.0) + '@babel/plugin-transform-runtime': 7.25.9(@babel/core@7.26.0) + '@babel/preset-env': 7.26.0(@babel/core@7.26.0) + '@babel/preset-react': 7.26.3(@babel/core@7.26.0) + '@babel/preset-typescript': 7.26.0(@babel/core@7.26.0) + '@babel/runtime': 7.26.0 + '@babel/runtime-corejs3': 7.26.0 + '@babel/traverse': 7.26.4 + '@docusaurus/logger': 3.7.0 + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + babel-plugin-dynamic-import-node: 2.3.3 + fs-extra: 11.2.0 + tslib: 2.8.1 + transitivePeerDependencies: + - '@swc/core' + - acorn + - esbuild + - react + - react-dom + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/bundler@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)': + dependencies: + '@babel/core': 7.26.0 + '@docusaurus/babel': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/cssnano-preset': 3.7.0 + '@docusaurus/logger': 3.7.0 + '@docusaurus/types': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + babel-loader: 9.2.1(@babel/core@7.26.0)(webpack@5.97.1(@swc/core@1.10.4)) + clean-css: 5.3.3 + copy-webpack-plugin: 11.0.0(webpack@5.97.1(@swc/core@1.10.4)) + css-loader: 6.11.0(webpack@5.97.1(@swc/core@1.10.4)) + css-minimizer-webpack-plugin: 5.0.1(clean-css@5.3.3)(webpack@5.97.1(@swc/core@1.10.4)) + cssnano: 6.1.2(postcss@8.4.49) + file-loader: 6.2.0(webpack@5.97.1(@swc/core@1.10.4)) + html-minifier-terser: 7.2.0 + mini-css-extract-plugin: 2.9.2(webpack@5.97.1(@swc/core@1.10.4)) + null-loader: 4.0.1(webpack@5.97.1(@swc/core@1.10.4)) + postcss: 8.4.49 + postcss-loader: 7.3.4(postcss@8.4.49)(typescript@5.7.2)(webpack@5.97.1(@swc/core@1.10.4)) + postcss-preset-env: 10.1.3(postcss@8.4.49) + react-dev-utils: 12.0.1(typescript@5.7.2)(webpack@5.97.1(@swc/core@1.10.4)) + terser-webpack-plugin: 5.3.11(@swc/core@1.10.4)(webpack@5.97.1(@swc/core@1.10.4)) + tslib: 2.8.1 + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.97.1(@swc/core@1.10.4)))(webpack@5.97.1(@swc/core@1.10.4)) + webpack: 5.97.1(@swc/core@1.10.4) + webpackbar: 6.0.1(webpack@5.97.1(@swc/core@1.10.4)) + optionalDependencies: + '@docusaurus/faster': 3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)) + transitivePeerDependencies: + - '@parcel/css' + - '@rspack/core' + - '@swc/core' + - '@swc/css' + - acorn + - csso + - esbuild + - eslint + - lightningcss + - react + - react-dom + - supports-color + - typescript + - uglify-js + - vue-template-compiler + - webpack-cli + + '@docusaurus/bundler@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2)': + dependencies: + '@babel/core': 7.26.0 + '@docusaurus/babel': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/cssnano-preset': 3.7.0 + '@docusaurus/logger': 3.7.0 + '@docusaurus/types': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + babel-loader: 9.2.1(@babel/core@7.26.0)(webpack@5.97.1(@swc/core@1.10.4)) + clean-css: 5.3.3 + copy-webpack-plugin: 11.0.0(webpack@5.97.1(@swc/core@1.10.4)) + css-loader: 6.11.0(webpack@5.97.1(@swc/core@1.10.4)) + css-minimizer-webpack-plugin: 5.0.1(clean-css@5.3.3)(webpack@5.97.1(@swc/core@1.10.4)) + cssnano: 6.1.2(postcss@8.4.49) + file-loader: 6.2.0(webpack@5.97.1(@swc/core@1.10.4)) + html-minifier-terser: 7.2.0 + mini-css-extract-plugin: 2.9.2(webpack@5.97.1(@swc/core@1.10.4)) + null-loader: 4.0.1(webpack@5.97.1(@swc/core@1.10.4)) + postcss: 8.4.49 + postcss-loader: 7.3.4(postcss@8.4.49)(typescript@5.7.2)(webpack@5.97.1(@swc/core@1.10.4)) + postcss-preset-env: 10.1.3(postcss@8.4.49) + react-dev-utils: 12.0.1(typescript@5.7.2)(webpack@5.97.1(@swc/core@1.10.4)) + terser-webpack-plugin: 5.3.11(@swc/core@1.10.4)(webpack@5.97.1(@swc/core@1.10.4)) + tslib: 2.8.1 + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.97.1(@swc/core@1.10.4)))(webpack@5.97.1(@swc/core@1.10.4)) + webpack: 5.97.1(@swc/core@1.10.4) + webpackbar: 6.0.1(webpack@5.97.1(@swc/core@1.10.4)) + optionalDependencies: + '@docusaurus/faster': 3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)) + transitivePeerDependencies: + - '@parcel/css' + - '@rspack/core' + - '@swc/core' + - '@swc/css' + - acorn + - csso + - esbuild + - eslint + - lightningcss + - react + - react-dom + - supports-color + - typescript + - uglify-js + - vue-template-compiler + - webpack-cli + + '@docusaurus/core@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@18.3.1))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)': + dependencies: + '@docusaurus/babel': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/bundler': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) + '@docusaurus/logger': 3.7.0 + '@docusaurus/mdx-loader': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mdx-js/react': 3.1.0(@types/react@19.0.2)(react@18.3.1) boxen: 6.2.1 chalk: 4.1.2 chokidar: 3.6.0 - clean-css: 5.3.3 cli-table3: 0.6.5 combine-promises: 1.2.0 commander: 5.1.0 - copy-webpack-plugin: 11.0.0(webpack@5.92.1) - core-js: 3.37.1 - css-loader: 6.11.0(webpack@5.92.1) - css-minimizer-webpack-plugin: 5.0.1(clean-css@5.3.3)(webpack@5.92.1) - cssnano: 6.1.2(postcss@8.4.39) + core-js: 3.39.0 del: 6.1.1 detect-port: 1.6.1 escape-html: 1.0.3 eta: 2.2.0 eval: 0.1.8 - file-loader: 6.2.0(webpack@5.92.1) fs-extra: 11.2.0 - html-minifier-terser: 7.2.0 html-tags: 3.3.1 - html-webpack-plugin: 5.6.0(webpack@5.92.1) + html-webpack-plugin: 5.6.3(webpack@5.97.1(@swc/core@1.10.4)) leven: 3.1.0 lodash: 4.17.21 - mini-css-extract-plugin: 2.9.0(webpack@5.92.1) p-map: 4.0.0 - postcss: 8.4.39 - postcss-loader: 7.3.4(postcss@8.4.39)(typescript@5.5.3)(webpack@5.92.1) prompts: 2.4.2 react: 18.3.1 - react-dev-utils: 12.0.1(typescript@5.5.3)(webpack@5.92.1) + react-dev-utils: 12.0.1(typescript@5.7.2)(webpack@5.97.1(@swc/core@1.10.4)) react-dom: 18.3.1(react@18.3.1) - react-helmet-async: 1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)' react-loadable: '@docusaurus/react-loadable@6.0.0(react@18.3.1)' - react-loadable-ssr-addon-v5-slorber: 1.0.1(@docusaurus/react-loadable@6.0.0(react@18.3.1))(webpack@5.92.1) + react-loadable-ssr-addon-v5-slorber: 1.0.1(@docusaurus/react-loadable@6.0.0(react@19.0.0))(webpack@5.97.1(@swc/core@1.10.4)) react-router: 5.3.4(react@18.3.1) - react-router-config: 5.1.1(react-router@5.3.4(react@18.3.1))(react@18.3.1) + react-router-config: 5.1.1(react-router@5.3.4(react@19.0.0))(react@18.3.1) react-router-dom: 5.3.4(react@18.3.1) - rtl-detect: 1.1.2 - semver: 7.6.2 - serve-handler: 6.1.5 + semver: 7.6.3 + serve-handler: 6.1.6 shelljs: 0.8.5 - terser-webpack-plugin: 5.3.10(webpack@5.92.1) - tslib: 2.6.3 + tslib: 2.8.1 update-notifier: 6.0.2 - url-loader: 4.1.1(file-loader@6.2.0(webpack@5.92.1))(webpack@5.92.1) - webpack: 5.92.1 + webpack: 5.97.1(@swc/core@1.10.4) webpack-bundle-analyzer: 4.10.2 - webpack-dev-server: 4.15.2(debug@4.3.5)(webpack@5.92.1) - webpack-merge: 5.10.0 - webpackbar: 5.0.2(webpack@5.92.1) + webpack-dev-server: 4.15.2(debug@4.4.0)(webpack@5.97.1(@swc/core@1.10.4)) + webpack-merge: 6.0.1 transitivePeerDependencies: - - '@docusaurus/types' + - '@docusaurus/faster' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - acorn - bufferutil - csso - debug @@ -6762,30 +7619,181 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/cssnano-preset@3.4.0': + '@docusaurus/core@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(debug@4.4.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2)': dependencies: - cssnano-preset-advanced: 6.1.2(postcss@8.4.39) - postcss: 8.4.39 - postcss-sort-media-queries: 5.2.0(postcss@8.4.39) - tslib: 2.6.3 + '@docusaurus/babel': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/bundler': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/logger': 3.7.0 + '@docusaurus/mdx-loader': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@mdx-js/react': 3.1.0(@types/react@19.0.2)(react@19.0.0) + boxen: 6.2.1 + chalk: 4.1.2 + chokidar: 3.6.0 + cli-table3: 0.6.5 + combine-promises: 1.2.0 + commander: 5.1.0 + core-js: 3.39.0 + del: 6.1.1 + detect-port: 1.6.1 + escape-html: 1.0.3 + eta: 2.2.0 + eval: 0.1.8 + fs-extra: 11.2.0 + html-tags: 3.3.1 + html-webpack-plugin: 5.6.3(webpack@5.97.1(@swc/core@1.10.4)) + leven: 3.1.0 + lodash: 4.17.21 + p-map: 4.0.0 + prompts: 2.4.2 + react: 19.0.0 + react-dev-utils: 12.0.1(typescript@5.7.2)(webpack@5.97.1(@swc/core@1.10.4)) + react-dom: 19.0.0(react@19.0.0) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)' + react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.0.0)' + react-loadable-ssr-addon-v5-slorber: 1.0.1(@docusaurus/react-loadable@6.0.0(react@19.0.0))(webpack@5.97.1(@swc/core@1.10.4)) + react-router: 5.3.4(react@19.0.0) + react-router-config: 5.1.1(react-router@5.3.4(react@19.0.0))(react@19.0.0) + react-router-dom: 5.3.4(react@19.0.0) + semver: 7.6.3 + serve-handler: 6.1.6 + shelljs: 0.8.5 + tslib: 2.8.1 + update-notifier: 6.0.2 + webpack: 5.97.1(@swc/core@1.10.4) + webpack-bundle-analyzer: 4.10.2 + webpack-dev-server: 4.15.2(debug@4.4.0)(webpack@5.97.1(@swc/core@1.10.4)) + webpack-merge: 6.0.1 + transitivePeerDependencies: + - '@docusaurus/faster' + - '@parcel/css' + - '@rspack/core' + - '@swc/core' + - '@swc/css' + - acorn + - bufferutil + - csso + - debug + - esbuild + - eslint + - lightningcss + - supports-color + - typescript + - uglify-js + - utf-8-validate + - vue-template-compiler + - webpack-cli - '@docusaurus/logger@3.4.0': + '@docusaurus/core@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2)': + dependencies: + '@docusaurus/babel': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/bundler': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/logger': 3.7.0 + '@docusaurus/mdx-loader': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@mdx-js/react': 3.1.0(@types/react@19.0.2)(react@19.0.0) + boxen: 6.2.1 + chalk: 4.1.2 + chokidar: 3.6.0 + cli-table3: 0.6.5 + combine-promises: 1.2.0 + commander: 5.1.0 + core-js: 3.39.0 + del: 6.1.1 + detect-port: 1.6.1 + escape-html: 1.0.3 + eta: 2.2.0 + eval: 0.1.8 + fs-extra: 11.2.0 + html-tags: 3.3.1 + html-webpack-plugin: 5.6.3(webpack@5.97.1(@swc/core@1.10.4)) + leven: 3.1.0 + lodash: 4.17.21 + p-map: 4.0.0 + prompts: 2.4.2 + react: 19.0.0 + react-dev-utils: 12.0.1(typescript@5.7.2)(webpack@5.97.1(@swc/core@1.10.4)) + react-dom: 19.0.0(react@19.0.0) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)' + react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.0.0)' + react-loadable-ssr-addon-v5-slorber: 1.0.1(@docusaurus/react-loadable@6.0.0(react@19.0.0))(webpack@5.97.1(@swc/core@1.10.4)) + react-router: 5.3.4(react@19.0.0) + react-router-config: 5.1.1(react-router@5.3.4(react@19.0.0))(react@19.0.0) + react-router-dom: 5.3.4(react@19.0.0) + semver: 7.6.3 + serve-handler: 6.1.6 + shelljs: 0.8.5 + tslib: 2.8.1 + update-notifier: 6.0.2 + webpack: 5.97.1(@swc/core@1.10.4) + webpack-bundle-analyzer: 4.10.2 + webpack-dev-server: 4.15.2(debug@4.4.0)(webpack@5.97.1(@swc/core@1.10.4)) + webpack-merge: 6.0.1 + transitivePeerDependencies: + - '@docusaurus/faster' + - '@parcel/css' + - '@rspack/core' + - '@swc/core' + - '@swc/css' + - acorn + - bufferutil + - csso + - debug + - esbuild + - eslint + - lightningcss + - supports-color + - typescript + - uglify-js + - utf-8-validate + - vue-template-compiler + - webpack-cli + + '@docusaurus/cssnano-preset@3.7.0': + dependencies: + cssnano-preset-advanced: 6.1.2(postcss@8.4.49) + postcss: 8.4.49 + postcss-sort-media-queries: 5.2.0(postcss@8.4.49) + tslib: 2.8.1 + + '@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))': + dependencies: + '@docusaurus/types': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@rspack/core': 1.2.0-alpha.0 + '@swc/core': 1.10.4 + '@swc/html': 1.10.4 + browserslist: 4.24.3 + lightningcss: 1.28.2 + swc-loader: 0.2.6(@swc/core@1.10.4)(webpack@5.97.1(@swc/core@1.10.4)) + tslib: 2.8.1 + webpack: 5.97.1(@swc/core@1.10.4) + transitivePeerDependencies: + - '@swc/helpers' + - esbuild + - uglify-js + - webpack-cli + + '@docusaurus/logger@3.7.0': dependencies: chalk: 4.1.2 - tslib: 2.6.3 + tslib: 2.8.1 - '@docusaurus/mdx-loader@3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3)': + '@docusaurus/mdx-loader@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@docusaurus/logger': 3.4.0 - '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) - '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) - '@mdx-js/mdx': 3.0.1 + '@docusaurus/logger': 3.7.0 + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mdx-js/mdx': 3.1.0(acorn@8.14.0) '@slorber/remark-comment': 1.0.0 escape-html: 1.0.3 - estree-util-value-to-estree: 3.1.2 - file-loader: 6.2.0(webpack@5.92.1) + estree-util-value-to-estree: 3.2.1 + file-loader: 6.2.0(webpack@5.97.1(@swc/core@1.10.4)) fs-extra: 11.2.0 - image-size: 1.1.1 + image-size: 1.2.0 mdast-util-mdx: 3.0.0 mdast-util-to-string: 4.0.0 react: 18.3.1 @@ -6796,48 +7804,105 @@ snapshots: remark-frontmatter: 5.0.0 remark-gfm: 4.0.0 stringify-object: 3.3.0 - tslib: 2.6.3 + tslib: 2.8.1 unified: 11.0.5 unist-util-visit: 5.0.0 - url-loader: 4.1.1(file-loader@6.2.0(webpack@5.92.1))(webpack@5.92.1) - vfile: 6.0.1 - webpack: 5.92.1 + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.97.1(@swc/core@1.10.4)))(webpack@5.97.1(@swc/core@1.10.4)) + vfile: 6.0.3 + webpack: 5.97.1(@swc/core@1.10.4) transitivePeerDependencies: - - '@docusaurus/types' - '@swc/core' + - acorn - esbuild - supports-color - - typescript - uglify-js - webpack-cli - '@docusaurus/module-type-aliases@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@docusaurus/mdx-loader@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@docusaurus/types': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/logger': 3.7.0 + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@mdx-js/mdx': 3.1.0(acorn@8.14.0) + '@slorber/remark-comment': 1.0.0 + escape-html: 1.0.3 + estree-util-value-to-estree: 3.2.1 + file-loader: 6.2.0(webpack@5.97.1(@swc/core@1.10.4)) + fs-extra: 11.2.0 + image-size: 1.2.0 + mdast-util-mdx: 3.0.0 + mdast-util-to-string: 4.0.0 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + rehype-raw: 7.0.0 + remark-directive: 3.0.0 + remark-emoji: 4.0.1 + remark-frontmatter: 5.0.0 + remark-gfm: 4.0.0 + stringify-object: 3.3.0 + tslib: 2.8.1 + unified: 11.0.5 + unist-util-visit: 5.0.0 + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.97.1(@swc/core@1.10.4)))(webpack@5.97.1(@swc/core@1.10.4)) + vfile: 6.0.3 + webpack: 5.97.1(@swc/core@1.10.4) + transitivePeerDependencies: + - '@swc/core' + - acorn + - esbuild + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/module-type-aliases@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@docusaurus/types': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/history': 4.7.11 - '@types/react': 18.3.3 + '@types/react': 18.3.18 '@types/react-router-config': 5.0.11 '@types/react-router-dom': 5.3.3 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-helmet-async: 2.0.5(react@18.3.1) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)' react-loadable: '@docusaurus/react-loadable@6.0.0(react@18.3.1)' transitivePeerDependencies: - '@swc/core' + - acorn - esbuild - supports-color - uglify-js - webpack-cli - '@docusaurus/plugin-content-blog@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3)': + '@docusaurus/module-type-aliases@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(debug@4.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/logger': 3.4.0 - '@docusaurus/mdx-loader': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/types': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) - '@docusaurus/utils-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) - '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) + '@docusaurus/types': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@types/history': 4.7.11 + '@types/react': 18.3.18 + '@types/react-router-config': 5.0.11 + '@types/react-router-dom': 5.3.3 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)' + react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.0.0)' + transitivePeerDependencies: + - '@swc/core' + - acorn + - esbuild + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/plugin-content-blog@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@18.3.1))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@18.3.1))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)': + dependencies: + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@18.3.1))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) + '@docusaurus/logger': 3.7.0 + '@docusaurus/mdx-loader': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/plugin-content-docs': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@18.3.1))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) + '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@18.3.1))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/types': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) cheerio: 1.0.0-rc.12 feed: 4.2.2 fs-extra: 11.2.0 @@ -6846,15 +7911,18 @@ snapshots: react-dom: 18.3.1(react@18.3.1) reading-time: 1.5.0 srcset: 4.0.0 - tslib: 2.6.3 + tslib: 2.8.1 unist-util-visit: 5.0.0 utility-types: 3.11.0 - webpack: 5.92.1 + webpack: 5.97.1(@swc/core@1.10.4) transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - acorn - bufferutil - csso - debug @@ -6868,16 +7936,61 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-content-docs@3.4.0(debug@4.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3)': + '@docusaurus/plugin-content-blog@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2)': dependencies: - '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(debug@4.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/logger': 3.4.0 - '@docusaurus/mdx-loader': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/module-type-aliases': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/types': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) - '@docusaurus/utils-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) - '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/logger': 3.7.0 + '@docusaurus/mdx-loader': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/plugin-content-docs': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/types': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + cheerio: 1.0.0-rc.12 + feed: 4.2.2 + fs-extra: 11.2.0 + lodash: 4.17.21 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + reading-time: 1.5.0 + srcset: 4.0.0 + tslib: 2.8.1 + unist-util-visit: 5.0.0 + utility-types: 3.11.0 + webpack: 5.97.1(@swc/core@1.10.4) + transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' + - '@parcel/css' + - '@rspack/core' + - '@swc/core' + - '@swc/css' + - acorn + - bufferutil + - csso + - debug + - esbuild + - eslint + - lightningcss + - supports-color + - typescript + - uglify-js + - utf-8-validate + - vue-template-compiler + - webpack-cli + + '@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@18.3.1))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)': + dependencies: + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@18.3.1))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) + '@docusaurus/logger': 3.7.0 + '@docusaurus/mdx-loader': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/module-type-aliases': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@18.3.1))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/types': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@types/react-router-config': 5.0.11 combine-promises: 1.2.0 fs-extra: 11.2.0 @@ -6885,14 +7998,17 @@ snapshots: lodash: 4.17.21 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - tslib: 2.6.3 + tslib: 2.8.1 utility-types: 3.11.0 - webpack: 5.92.1 + webpack: 5.97.1(@swc/core@1.10.4) transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - acorn - bufferutil - csso - debug @@ -6906,23 +8022,110 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-content-pages@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3)': + '@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(debug@4.4.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2)': dependencies: - '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(debug@4.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/mdx-loader': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/types': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) - '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(debug@4.4.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/logger': 3.7.0 + '@docusaurus/mdx-loader': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/module-type-aliases': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/types': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@types/react-router-config': 5.0.11 + combine-promises: 1.2.0 + fs-extra: 11.2.0 + js-yaml: 4.1.0 + lodash: 4.17.21 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + tslib: 2.8.1 + utility-types: 3.11.0 + webpack: 5.97.1(@swc/core@1.10.4) + transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' + - '@parcel/css' + - '@rspack/core' + - '@swc/core' + - '@swc/css' + - acorn + - bufferutil + - csso + - debug + - esbuild + - eslint + - lightningcss + - supports-color + - typescript + - uglify-js + - utf-8-validate + - vue-template-compiler + - webpack-cli + + '@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2)': + dependencies: + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/logger': 3.7.0 + '@docusaurus/mdx-loader': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/module-type-aliases': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/types': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@types/react-router-config': 5.0.11 + combine-promises: 1.2.0 + fs-extra: 11.2.0 + js-yaml: 4.1.0 + lodash: 4.17.21 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + tslib: 2.8.1 + utility-types: 3.11.0 + webpack: 5.97.1(@swc/core@1.10.4) + transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' + - '@parcel/css' + - '@rspack/core' + - '@swc/core' + - '@swc/css' + - acorn + - bufferutil + - csso + - debug + - esbuild + - eslint + - lightningcss + - supports-color + - typescript + - uglify-js + - utf-8-validate + - vue-template-compiler + - webpack-cli + + '@docusaurus/plugin-content-pages@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@18.3.1))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)': + dependencies: + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@18.3.1))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) + '@docusaurus/mdx-loader': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/types': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) fs-extra: 11.2.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - tslib: 2.6.3 - webpack: 5.92.1 + tslib: 2.8.1 + webpack: 5.97.1(@swc/core@1.10.4) transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - acorn - bufferutil - csso - debug @@ -6936,21 +8139,26 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-debug@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3)': + '@docusaurus/plugin-content-pages@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2)': dependencies: - '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(debug@4.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/types': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/mdx-loader': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/types': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) fs-extra: 11.2.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-json-view-lite: 1.4.0(react@18.3.1) - tslib: 2.6.3 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + tslib: 2.8.1 + webpack: 5.97.1(@swc/core@1.10.4) transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - acorn - bufferutil - csso - debug @@ -6964,19 +8172,24 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-google-analytics@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3)': + '@docusaurus/plugin-debug@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2)': dependencies: - '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(debug@4.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/types': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - tslib: 2.6.3 + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/types': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + fs-extra: 11.2.0 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-json-view-lite: 1.5.0(react@19.0.0) + tslib: 2.8.1 transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - acorn - bufferutil - csso - debug @@ -6990,20 +8203,52 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-google-gtag@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3)': + '@docusaurus/plugin-google-analytics@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2)': dependencies: - '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(debug@4.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/types': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/types': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + tslib: 2.8.1 + transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' + - '@parcel/css' + - '@rspack/core' + - '@swc/core' + - '@swc/css' + - acorn + - bufferutil + - csso + - debug + - esbuild + - eslint + - lightningcss + - supports-color + - typescript + - uglify-js + - utf-8-validate + - vue-template-compiler + - webpack-cli + + '@docusaurus/plugin-google-gtag@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2)': + dependencies: + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/types': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@types/gtag.js': 0.0.12 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - tslib: 2.6.3 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + tslib: 2.8.1 transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - acorn - bufferutil - csso - debug @@ -7017,19 +8262,22 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-google-tag-manager@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3)': + '@docusaurus/plugin-google-tag-manager@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2)': dependencies: - '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(debug@4.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/types': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - tslib: 2.6.3 + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/types': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + tslib: 2.8.1 transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - acorn - bufferutil - csso - debug @@ -7043,24 +8291,27 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/plugin-sitemap@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3)': + '@docusaurus/plugin-sitemap@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2)': dependencies: - '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(debug@4.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/logger': 3.4.0 - '@docusaurus/types': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) - '@docusaurus/utils-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) - '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/logger': 3.7.0 + '@docusaurus/types': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) fs-extra: 11.2.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) sitemap: 7.1.2 - tslib: 2.6.3 + tslib: 2.8.1 transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - acorn - bufferutil - csso - debug @@ -7074,30 +8325,67 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/preset-classic@3.4.0(@algolia/client-search@4.24.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.14.0)(typescript@5.5.3)': + '@docusaurus/plugin-svgr@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2)': dependencies: - '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(debug@4.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/plugin-content-blog': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/plugin-content-docs': 3.4.0(debug@4.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/plugin-content-pages': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/plugin-debug': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/plugin-google-analytics': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/plugin-google-gtag': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/plugin-google-tag-manager': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/plugin-sitemap': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/theme-classic': 3.4.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/theme-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/theme-search-algolia': 3.4.0(@algolia/client-search@4.24.0)(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.14.0)(typescript@5.5.3) - '@docusaurus/types': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/types': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@svgr/core': 8.1.0(typescript@5.7.2) + '@svgr/webpack': 8.1.0(typescript@5.7.2) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + tslib: 2.8.1 + webpack: 5.97.1(@swc/core@1.10.4) + transitivePeerDependencies: + - '@docusaurus/faster' + - '@mdx-js/react' + - '@parcel/css' + - '@rspack/core' + - '@swc/core' + - '@swc/css' + - acorn + - bufferutil + - csso + - debug + - esbuild + - eslint + - lightningcss + - supports-color + - typescript + - uglify-js + - utf-8-validate + - vue-template-compiler + - webpack-cli + + '@docusaurus/preset-classic@3.7.0(@algolia/client-search@5.18.0)(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(@types/react@19.0.2)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3)(typescript@5.7.2)': + dependencies: + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/plugin-content-blog': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/plugin-content-docs': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/plugin-content-pages': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/plugin-debug': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/plugin-google-analytics': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/plugin-google-gtag': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/plugin-google-tag-manager': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/plugin-sitemap': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/plugin-svgr': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/theme-classic': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@swc/core@1.10.4)(@types/react@19.0.2)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/theme-search-algolia': 3.7.0(@algolia/client-search@5.18.0)(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(@types/react@19.0.2)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3)(typescript@5.7.2) + '@docusaurus/types': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) transitivePeerDependencies: - '@algolia/client-search' + - '@docusaurus/faster' + - '@mdx-js/react' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' - '@types/react' + - acorn - bufferutil - csso - debug @@ -7114,44 +8402,52 @@ snapshots: '@docusaurus/react-loadable@6.0.0(react@18.3.1)': dependencies: - '@types/react': 18.3.3 + '@types/react': 19.0.2 react: 18.3.1 - '@docusaurus/theme-classic@3.4.0(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3)': + '@docusaurus/react-loadable@6.0.0(react@19.0.0)': dependencies: - '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(debug@4.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/mdx-loader': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/module-type-aliases': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/plugin-content-blog': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/plugin-content-docs': 3.4.0(debug@4.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/plugin-content-pages': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/theme-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/theme-translations': 3.4.0 - '@docusaurus/types': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) - '@docusaurus/utils-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) - '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) - '@mdx-js/react': 3.0.1(@types/react@18.3.3)(react@18.3.1) + '@types/react': 19.0.2 + react: 19.0.0 + + '@docusaurus/theme-classic@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@swc/core@1.10.4)(@types/react@19.0.2)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2)': + dependencies: + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@18.3.1))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) + '@docusaurus/logger': 3.7.0 + '@docusaurus/mdx-loader': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/module-type-aliases': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/plugin-content-blog': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@18.3.1))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@18.3.1))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) + '@docusaurus/plugin-content-docs': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@18.3.1))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) + '@docusaurus/plugin-content-pages': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@18.3.1))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) + '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@18.3.1))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/theme-translations': 3.7.0 + '@docusaurus/types': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mdx-js/react': 3.1.0(@types/react@19.0.2)(react@18.3.1) clsx: 2.1.1 copy-text-to-clipboard: 3.2.0 - infima: 0.2.0-alpha.43 + infima: 0.2.0-alpha.45 lodash: 4.17.21 nprogress: 0.2.0 - postcss: 8.4.39 - prism-react-renderer: 2.3.1(react@18.3.1) + postcss: 8.4.49 + prism-react-renderer: 2.4.1(react@18.3.1) prismjs: 1.29.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-router-dom: 5.3.4(react@18.3.1) - rtlcss: 4.1.1 - tslib: 2.6.3 + rtlcss: 4.3.0 + tslib: 2.8.1 utility-types: 3.11.0 transitivePeerDependencies: + - '@docusaurus/faster' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' - '@types/react' + - acorn - bufferutil - csso - debug @@ -7165,31 +8461,44 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/theme-common@3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3)': + '@docusaurus/theme-classic@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@swc/core@1.10.4)(@types/react@19.0.2)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2)': dependencies: - '@docusaurus/mdx-loader': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/module-type-aliases': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@docusaurus/plugin-content-blog': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/plugin-content-docs': 3.4.0(debug@4.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/plugin-content-pages': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) - '@docusaurus/utils-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) - '@types/history': 4.7.11 - '@types/react': 18.3.3 - '@types/react-router-config': 5.0.11 + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/logger': 3.7.0 + '@docusaurus/mdx-loader': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/module-type-aliases': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/plugin-content-blog': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/plugin-content-docs': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/plugin-content-pages': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/theme-translations': 3.7.0 + '@docusaurus/types': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@mdx-js/react': 3.1.0(@types/react@19.0.2)(react@19.0.0) clsx: 2.1.1 - parse-numeric-range: 1.3.0 - prism-react-renderer: 2.3.1(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - tslib: 2.6.3 + copy-text-to-clipboard: 3.2.0 + infima: 0.2.0-alpha.45 + lodash: 4.17.21 + nprogress: 0.2.0 + postcss: 8.4.49 + prism-react-renderer: 2.4.1(react@19.0.0) + prismjs: 1.29.0 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-router-dom: 5.3.4(react@19.0.0) + rtlcss: 4.3.0 + tslib: 2.8.1 utility-types: 3.11.0 transitivePeerDependencies: - - '@docusaurus/types' + - '@docusaurus/faster' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - '@types/react' + - acorn - bufferutil - csso - debug @@ -7203,34 +8512,86 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/theme-search-algolia@3.4.0(@algolia/client-search@4.24.0)(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.14.0)(typescript@5.5.3)': + '@docusaurus/theme-common@3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@18.3.1))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@docsearch/react': 3.6.0(@algolia/client-search@4.24.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(search-insights@2.14.0) - '@docusaurus/core': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(debug@4.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/logger': 3.4.0 - '@docusaurus/plugin-content-docs': 3.4.0(debug@4.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/theme-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/theme-translations': 3.4.0 - '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) - '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) - algoliasearch: 4.24.0 - algoliasearch-helper: 3.22.2(algoliasearch@4.24.0) + '@docusaurus/mdx-loader': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/module-type-aliases': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/plugin-content-docs': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@18.3.1))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.7.2) + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@types/history': 4.7.11 + '@types/react': 19.0.2 + '@types/react-router-config': 5.0.11 + clsx: 2.1.1 + parse-numeric-range: 1.3.0 + prism-react-renderer: 2.4.1(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + tslib: 2.8.1 + utility-types: 3.11.0 + transitivePeerDependencies: + - '@swc/core' + - acorn + - esbuild + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/theme-common@3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@docusaurus/mdx-loader': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/module-type-aliases': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/plugin-content-docs': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@types/history': 4.7.11 + '@types/react': 19.0.2 + '@types/react-router-config': 5.0.11 + clsx: 2.1.1 + parse-numeric-range: 1.3.0 + prism-react-renderer: 2.4.1(react@19.0.0) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + tslib: 2.8.1 + utility-types: 3.11.0 + transitivePeerDependencies: + - '@swc/core' + - acorn + - esbuild + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/theme-search-algolia@3.7.0(@algolia/client-search@5.18.0)(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(@types/react@19.0.2)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3)(typescript@5.7.2)': + dependencies: + '@docsearch/react': 3.8.2(@algolia/client-search@5.18.0)(@types/react@19.0.2)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(search-insights@2.17.3) + '@docusaurus/core': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/logger': 3.7.0 + '@docusaurus/plugin-content-docs': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/theme-translations': 3.7.0 + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + algoliasearch: 5.18.0 + algoliasearch-helper: 3.22.6(algoliasearch@5.18.0) clsx: 2.1.1 eta: 2.2.0 fs-extra: 11.2.0 lodash: 4.17.21 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - tslib: 2.6.3 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + tslib: 2.8.1 utility-types: 3.11.0 transitivePeerDependencies: - '@algolia/client-search' - - '@docusaurus/types' + - '@docusaurus/faster' + - '@mdx-js/react' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' - '@types/react' + - acorn - bufferutil - csso - debug @@ -7245,87 +8606,184 @@ snapshots: - vue-template-compiler - webpack-cli - '@docusaurus/theme-translations@3.4.0': + '@docusaurus/theme-translations@3.7.0': dependencies: fs-extra: 11.2.0 - tslib: 2.6.3 + tslib: 2.8.1 - '@docusaurus/tsconfig@3.4.0': {} + '@docusaurus/tsconfig@3.7.0': {} - '@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@mdx-js/mdx': 3.0.1 + '@mdx-js/mdx': 3.1.0(acorn@8.14.0) '@types/history': 4.7.11 - '@types/react': 18.3.3 + '@types/react': 18.3.18 commander: 5.1.0 joi: 17.13.3 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-helmet-async: 1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)' utility-types: 3.11.0 - webpack: 5.92.1 + webpack: 5.97.1(@swc/core@1.10.4) webpack-merge: 5.10.0 transitivePeerDependencies: - '@swc/core' + - acorn - esbuild - supports-color - uglify-js - webpack-cli - '@docusaurus/utils-common@3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))': + '@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - tslib: 2.6.3 - optionalDependencies: - '@docusaurus/types': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mdx-js/mdx': 3.1.0(acorn@8.14.0) + '@types/history': 4.7.11 + '@types/react': 18.3.18 + commander: 5.1.0 + joi: 17.13.3 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-helmet-async: '@slorber/react-helmet-async@1.3.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)' + utility-types: 3.11.0 + webpack: 5.97.1(@swc/core@1.10.4) + webpack-merge: 5.10.0 + transitivePeerDependencies: + - '@swc/core' + - acorn + - esbuild + - supports-color + - uglify-js + - webpack-cli - '@docusaurus/utils-validation@3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3)': + '@docusaurus/utils-common@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@docusaurus/logger': 3.4.0 - '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) - '@docusaurus/utils-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + '@docusaurus/types': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + tslib: 2.8.1 + transitivePeerDependencies: + - '@swc/core' + - acorn + - esbuild + - react + - react-dom + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/utils-common@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@docusaurus/types': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + tslib: 2.8.1 + transitivePeerDependencies: + - '@swc/core' + - acorn + - esbuild + - react + - react-dom + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/utils-validation@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@docusaurus/logger': 3.7.0 + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) fs-extra: 11.2.0 joi: 17.13.3 js-yaml: 4.1.0 lodash: 4.17.21 - tslib: 2.6.3 + tslib: 2.8.1 transitivePeerDependencies: - - '@docusaurus/types' - '@swc/core' + - acorn - esbuild + - react + - react-dom - supports-color - - typescript - uglify-js - webpack-cli - '@docusaurus/utils@3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3)': + '@docusaurus/utils-validation@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': dependencies: - '@docusaurus/logger': 3.4.0 - '@docusaurus/utils-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) - '@svgr/webpack': 8.1.0(typescript@5.5.3) + '@docusaurus/logger': 3.7.0 + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + fs-extra: 11.2.0 + joi: 17.13.3 + js-yaml: 4.1.0 + lodash: 4.17.21 + tslib: 2.8.1 + transitivePeerDependencies: + - '@swc/core' + - acorn + - esbuild + - react + - react-dom + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/utils@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@docusaurus/logger': 3.7.0 + '@docusaurus/types': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) escape-string-regexp: 4.0.0 - file-loader: 6.2.0(webpack@5.92.1) + file-loader: 6.2.0(webpack@5.97.1(@swc/core@1.10.4)) fs-extra: 11.2.0 github-slugger: 1.5.0 globby: 11.1.0 gray-matter: 4.0.3 - jiti: 1.21.6 + jiti: 1.21.7 js-yaml: 4.1.0 lodash: 4.17.21 - micromatch: 4.0.7 + micromatch: 4.0.8 prompts: 2.4.2 resolve-pathname: 3.0.0 shelljs: 0.8.5 - tslib: 2.6.3 - url-loader: 4.1.1(file-loader@6.2.0(webpack@5.92.1))(webpack@5.92.1) + tslib: 2.8.1 + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.97.1(@swc/core@1.10.4)))(webpack@5.97.1(@swc/core@1.10.4)) utility-types: 3.11.0 - webpack: 5.92.1 - optionalDependencies: - '@docusaurus/types': 3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + webpack: 5.97.1(@swc/core@1.10.4) transitivePeerDependencies: - '@swc/core' + - acorn - esbuild + - react + - react-dom + - supports-color + - uglify-js + - webpack-cli + + '@docusaurus/utils@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@docusaurus/logger': 3.7.0 + '@docusaurus/types': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + escape-string-regexp: 4.0.0 + file-loader: 6.2.0(webpack@5.97.1(@swc/core@1.10.4)) + fs-extra: 11.2.0 + github-slugger: 1.5.0 + globby: 11.1.0 + gray-matter: 4.0.3 + jiti: 1.21.7 + js-yaml: 4.1.0 + lodash: 4.17.21 + micromatch: 4.0.8 + prompts: 2.4.2 + resolve-pathname: 3.0.0 + shelljs: 0.8.5 + tslib: 2.8.1 + url-loader: 4.1.1(file-loader@6.2.0(webpack@5.97.1(@swc/core@1.10.4)))(webpack@5.97.1(@swc/core@1.10.4)) + utility-types: 3.11.0 + webpack: 5.97.1(@swc/core@1.10.4) + transitivePeerDependencies: + - '@swc/core' + - acorn + - esbuild + - react + - react-dom - supports-color - - typescript - uglify-js - webpack-cli @@ -7334,33 +8792,36 @@ snapshots: cssesc: 3.0.0 immediate: 3.3.0 - '@easyops-cn/docusaurus-search-local@0.44.2(@docusaurus/theme-common@3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3))(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3)': + '@easyops-cn/docusaurus-search-local@0.46.1(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@docusaurus/theme-common@3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2)': dependencies: - '@docusaurus/plugin-content-docs': 3.4.0(debug@4.3.5)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/theme-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/theme-translations': 3.4.0 - '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) - '@docusaurus/utils-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) - '@docusaurus/utils-validation': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) + '@docusaurus/plugin-content-docs': 3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(debug@4.4.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2) + '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/theme-translations': 3.7.0 + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-common': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils-validation': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@easyops-cn/autocomplete.js': 0.38.1 - '@node-rs/jieba': 1.10.3 - cheerio: 1.0.0-rc.12 + '@node-rs/jieba': 1.10.4 + cheerio: 1.0.0 clsx: 1.2.1 - debug: 4.3.5 + comlink: 4.4.2 + debug: 4.4.0 fs-extra: 10.1.0 klaw-sync: 6.0.0 lunr: 2.3.9 lunr-languages: 1.14.0 mark.js: 8.11.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - tslib: 2.6.3 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + tslib: 2.8.1 transitivePeerDependencies: - - '@docusaurus/types' + - '@docusaurus/faster' + - '@mdx-js/react' - '@parcel/css' - '@rspack/core' - '@swc/core' - '@swc/css' + - acorn - bufferutil - csso - esbuild @@ -7373,20 +8834,20 @@ snapshots: - vue-template-compiler - webpack-cli - '@emnapi/core@1.2.0': + '@emnapi/core@1.3.1': dependencies: '@emnapi/wasi-threads': 1.0.1 - tslib: 2.6.3 + tslib: 2.8.1 optional: true - '@emnapi/runtime@1.2.0': + '@emnapi/runtime@1.3.1': dependencies: - tslib: 2.6.3 + tslib: 2.8.1 optional: true '@emnapi/wasi-threads@1.0.1': dependencies: - tslib: 2.6.3 + tslib: 2.8.1 optional: true '@emotion/is-prop-valid@1.2.2': @@ -7397,75 +8858,6 @@ snapshots: '@emotion/unitless@0.8.1': {} - '@esbuild/aix-ppc64@0.21.5': - optional: true - - '@esbuild/android-arm64@0.21.5': - optional: true - - '@esbuild/android-arm@0.21.5': - optional: true - - '@esbuild/android-x64@0.21.5': - optional: true - - '@esbuild/darwin-arm64@0.21.5': - optional: true - - '@esbuild/darwin-x64@0.21.5': - optional: true - - '@esbuild/freebsd-arm64@0.21.5': - optional: true - - '@esbuild/freebsd-x64@0.21.5': - optional: true - - '@esbuild/linux-arm64@0.21.5': - optional: true - - '@esbuild/linux-arm@0.21.5': - optional: true - - '@esbuild/linux-ia32@0.21.5': - optional: true - - '@esbuild/linux-loong64@0.21.5': - optional: true - - '@esbuild/linux-mips64el@0.21.5': - optional: true - - '@esbuild/linux-ppc64@0.21.5': - optional: true - - '@esbuild/linux-riscv64@0.21.5': - optional: true - - '@esbuild/linux-s390x@0.21.5': - optional: true - - '@esbuild/linux-x64@0.21.5': - optional: true - - '@esbuild/netbsd-x64@0.21.5': - optional: true - - '@esbuild/openbsd-x64@0.21.5': - optional: true - - '@esbuild/sunos-x64@0.21.5': - optional: true - - '@esbuild/win32-arm64@0.21.5': - optional: true - - '@esbuild/win32-ia32@0.21.5': - optional: true - - '@esbuild/win32-x64@0.21.5': - optional: true - '@exodus/schemasafe@1.3.0': {} '@hapi/hoek@9.3.0': {} @@ -7483,14 +8875,14 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.14.10 - '@types/yargs': 17.0.32 + '@types/node': 22.10.5 + '@types/yargs': 17.0.33 chalk: 4.1.2 - '@jridgewell/gen-mapping@0.3.5': + '@jridgewell/gen-mapping@0.3.8': dependencies: '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/trace-mapping': 0.3.25 '@jridgewell/resolve-uri@3.1.2': {} @@ -7499,119 +8891,148 @@ snapshots: '@jridgewell/source-map@0.3.6': dependencies: - '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 - '@jridgewell/sourcemap-codec@1.4.15': {} + '@jridgewell/sourcemap-codec@1.5.0': {} '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 '@leichtgewicht/ip-codec@2.0.5': {} - '@mdx-js/mdx@3.0.1': + '@mdx-js/mdx@3.1.0(acorn@8.14.0)': dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 '@types/estree-jsx': 1.0.5 '@types/hast': 3.0.4 '@types/mdx': 2.0.13 collapse-white-space: 2.1.0 devlop: 1.1.0 - estree-util-build-jsx: 3.0.1 estree-util-is-identifier-name: 3.0.0 - estree-util-to-js: 2.0.0 + estree-util-scope: 1.0.0 estree-walker: 3.0.3 - hast-util-to-estree: 3.1.0 - hast-util-to-jsx-runtime: 2.3.0 + hast-util-to-jsx-runtime: 2.3.2 markdown-extensions: 2.0.0 - periscopic: 3.1.0 - remark-mdx: 3.0.1 + recma-build-jsx: 1.0.0 + recma-jsx: 1.0.0(acorn@8.14.0) + recma-stringify: 1.0.0 + rehype-recma: 1.0.0 + remark-mdx: 3.1.0 remark-parse: 11.0.0 - remark-rehype: 11.1.0 + remark-rehype: 11.1.1 source-map: 0.7.4 unified: 11.0.5 unist-util-position-from-estree: 2.0.0 unist-util-stringify-position: 4.0.0 unist-util-visit: 5.0.0 - vfile: 6.0.1 + vfile: 6.0.3 transitivePeerDependencies: + - acorn - supports-color - '@mdx-js/react@3.0.1(@types/react@18.3.3)(react@18.3.1)': + '@mdx-js/react@3.1.0(@types/react@19.0.2)(react@18.3.1)': dependencies: '@types/mdx': 2.0.13 - '@types/react': 18.3.3 + '@types/react': 19.0.2 react: 18.3.1 - '@napi-rs/wasm-runtime@0.2.4': + '@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0)': dependencies: - '@emnapi/core': 1.2.0 - '@emnapi/runtime': 1.2.0 + '@types/mdx': 2.0.13 + '@types/react': 19.0.2 + react: 19.0.0 + + '@module-federation/error-codes@0.8.4': {} + + '@module-federation/runtime-tools@0.8.4': + dependencies: + '@module-federation/runtime': 0.8.4 + '@module-federation/webpack-bundler-runtime': 0.8.4 + + '@module-federation/runtime@0.8.4': + dependencies: + '@module-federation/error-codes': 0.8.4 + '@module-federation/sdk': 0.8.4 + + '@module-federation/sdk@0.8.4': + dependencies: + isomorphic-rslog: 0.0.6 + + '@module-federation/webpack-bundler-runtime@0.8.4': + dependencies: + '@module-federation/runtime': 0.8.4 + '@module-federation/sdk': 0.8.4 + + '@napi-rs/wasm-runtime@0.2.6': + dependencies: + '@emnapi/core': 1.3.1 + '@emnapi/runtime': 1.3.1 '@tybys/wasm-util': 0.9.0 optional: true - '@node-rs/jieba-android-arm-eabi@1.10.3': + '@node-rs/jieba-android-arm-eabi@1.10.4': optional: true - '@node-rs/jieba-android-arm64@1.10.3': + '@node-rs/jieba-android-arm64@1.10.4': optional: true - '@node-rs/jieba-darwin-arm64@1.10.3': + '@node-rs/jieba-darwin-arm64@1.10.4': optional: true - '@node-rs/jieba-darwin-x64@1.10.3': + '@node-rs/jieba-darwin-x64@1.10.4': optional: true - '@node-rs/jieba-freebsd-x64@1.10.3': + '@node-rs/jieba-freebsd-x64@1.10.4': optional: true - '@node-rs/jieba-linux-arm-gnueabihf@1.10.3': + '@node-rs/jieba-linux-arm-gnueabihf@1.10.4': optional: true - '@node-rs/jieba-linux-arm64-gnu@1.10.3': + '@node-rs/jieba-linux-arm64-gnu@1.10.4': optional: true - '@node-rs/jieba-linux-arm64-musl@1.10.3': + '@node-rs/jieba-linux-arm64-musl@1.10.4': optional: true - '@node-rs/jieba-linux-x64-gnu@1.10.3': + '@node-rs/jieba-linux-x64-gnu@1.10.4': optional: true - '@node-rs/jieba-linux-x64-musl@1.10.3': + '@node-rs/jieba-linux-x64-musl@1.10.4': optional: true - '@node-rs/jieba-wasm32-wasi@1.10.3': + '@node-rs/jieba-wasm32-wasi@1.10.4': dependencies: - '@napi-rs/wasm-runtime': 0.2.4 + '@napi-rs/wasm-runtime': 0.2.6 optional: true - '@node-rs/jieba-win32-arm64-msvc@1.10.3': + '@node-rs/jieba-win32-arm64-msvc@1.10.4': optional: true - '@node-rs/jieba-win32-ia32-msvc@1.10.3': + '@node-rs/jieba-win32-ia32-msvc@1.10.4': optional: true - '@node-rs/jieba-win32-x64-msvc@1.10.3': + '@node-rs/jieba-win32-x64-msvc@1.10.4': optional: true - '@node-rs/jieba@1.10.3': + '@node-rs/jieba@1.10.4': optionalDependencies: - '@node-rs/jieba-android-arm-eabi': 1.10.3 - '@node-rs/jieba-android-arm64': 1.10.3 - '@node-rs/jieba-darwin-arm64': 1.10.3 - '@node-rs/jieba-darwin-x64': 1.10.3 - '@node-rs/jieba-freebsd-x64': 1.10.3 - '@node-rs/jieba-linux-arm-gnueabihf': 1.10.3 - '@node-rs/jieba-linux-arm64-gnu': 1.10.3 - '@node-rs/jieba-linux-arm64-musl': 1.10.3 - '@node-rs/jieba-linux-x64-gnu': 1.10.3 - '@node-rs/jieba-linux-x64-musl': 1.10.3 - '@node-rs/jieba-wasm32-wasi': 1.10.3 - '@node-rs/jieba-win32-arm64-msvc': 1.10.3 - '@node-rs/jieba-win32-ia32-msvc': 1.10.3 - '@node-rs/jieba-win32-x64-msvc': 1.10.3 + '@node-rs/jieba-android-arm-eabi': 1.10.4 + '@node-rs/jieba-android-arm64': 1.10.4 + '@node-rs/jieba-darwin-arm64': 1.10.4 + '@node-rs/jieba-darwin-x64': 1.10.4 + '@node-rs/jieba-freebsd-x64': 1.10.4 + '@node-rs/jieba-linux-arm-gnueabihf': 1.10.4 + '@node-rs/jieba-linux-arm64-gnu': 1.10.4 + '@node-rs/jieba-linux-arm64-musl': 1.10.4 + '@node-rs/jieba-linux-x64-gnu': 1.10.4 + '@node-rs/jieba-linux-x64-musl': 1.10.4 + '@node-rs/jieba-wasm32-wasi': 1.10.4 + '@node-rs/jieba-win32-arm64-msvc': 1.10.4 + '@node-rs/jieba-win32-ia32-msvc': 1.10.4 + '@node-rs/jieba-win32-x64-msvc': 1.10.4 '@nodelib/fs.scandir@2.1.5': dependencies: @@ -7623,7 +9044,7 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.17.1 + fastq: 1.18.0 '@pnpm/config.env-replace@1.1.0': {} @@ -7631,29 +9052,29 @@ snapshots: dependencies: graceful-fs: 4.2.10 - '@pnpm/npm-conf@2.2.2': + '@pnpm/npm-conf@2.3.1': dependencies: '@pnpm/config.env-replace': 1.1.0 '@pnpm/network.ca-file': 1.0.2 config-chain: 1.1.13 - '@polka/url@1.0.0-next.25': {} + '@polka/url@1.0.0-next.28': {} - '@redocly/ajv@8.11.0': + '@redocly/ajv@8.11.2': dependencies: fast-deep-equal: 3.1.3 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - uri-js: 4.4.1 + uri-js-replace: 1.0.1 - '@redocly/config@0.6.2': {} + '@redocly/config@0.6.3': {} '@redocly/openapi-core@1.16.0': dependencies: - '@redocly/ajv': 8.11.0 - '@redocly/config': 0.6.2 + '@redocly/ajv': 8.11.2 + '@redocly/config': 0.6.3 colorette: 1.4.0 - https-proxy-agent: 7.0.5 + https-proxy-agent: 7.0.6 js-levenshtein: 1.1.6 js-yaml: 4.1.0 lodash.isequal: 4.5.0 @@ -7665,7 +9086,53 @@ snapshots: - encoding - supports-color - '@sec-ant/readable-stream@0.4.1': {} + '@rspack/binding-darwin-arm64@1.2.0-alpha.0': + optional: true + + '@rspack/binding-darwin-x64@1.2.0-alpha.0': + optional: true + + '@rspack/binding-linux-arm64-gnu@1.2.0-alpha.0': + optional: true + + '@rspack/binding-linux-arm64-musl@1.2.0-alpha.0': + optional: true + + '@rspack/binding-linux-x64-gnu@1.2.0-alpha.0': + optional: true + + '@rspack/binding-linux-x64-musl@1.2.0-alpha.0': + optional: true + + '@rspack/binding-win32-arm64-msvc@1.2.0-alpha.0': + optional: true + + '@rspack/binding-win32-ia32-msvc@1.2.0-alpha.0': + optional: true + + '@rspack/binding-win32-x64-msvc@1.2.0-alpha.0': + optional: true + + '@rspack/binding@1.2.0-alpha.0': + optionalDependencies: + '@rspack/binding-darwin-arm64': 1.2.0-alpha.0 + '@rspack/binding-darwin-x64': 1.2.0-alpha.0 + '@rspack/binding-linux-arm64-gnu': 1.2.0-alpha.0 + '@rspack/binding-linux-arm64-musl': 1.2.0-alpha.0 + '@rspack/binding-linux-x64-gnu': 1.2.0-alpha.0 + '@rspack/binding-linux-x64-musl': 1.2.0-alpha.0 + '@rspack/binding-win32-arm64-msvc': 1.2.0-alpha.0 + '@rspack/binding-win32-ia32-msvc': 1.2.0-alpha.0 + '@rspack/binding-win32-x64-msvc': 1.2.0-alpha.0 + + '@rspack/core@1.2.0-alpha.0': + dependencies: + '@module-federation/runtime-tools': 0.8.4 + '@rspack/binding': 1.2.0-alpha.0 + '@rspack/lite-tapable': 1.0.1 + caniuse-lite: 1.0.30001690 + + '@rspack/lite-tapable@1.0.1': {} '@sideway/address@4.1.5': dependencies: @@ -7679,7 +9146,27 @@ snapshots: '@sindresorhus/is@4.6.0': {} - '@sindresorhus/is@6.3.1': {} + '@sindresorhus/is@5.6.0': {} + + '@slorber/react-helmet-async@1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.26.0 + invariant: 2.2.4 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-fast-compare: 3.2.2 + shallowequal: 1.1.0 + + '@slorber/react-helmet-async@1.3.0(react-dom@19.0.0(react@19.0.0))(react@19.0.0)': + dependencies: + '@babel/runtime': 7.26.0 + invariant: 2.2.4 + prop-types: 15.8.1 + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-fast-compare: 3.2.2 + shallowequal: 1.1.0 '@slorber/remark-comment@1.0.0': dependencies: @@ -7687,56 +9174,56 @@ snapshots: micromark-util-character: 1.2.0 micromark-util-symbol: 1.1.0 - '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.24.7)': + '@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 + '@babel/core': 7.26.0 - '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.24.7)': + '@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 + '@babel/core': 7.26.0 - '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.24.7)': + '@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 + '@babel/core': 7.26.0 - '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.24.7)': + '@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 + '@babel/core': 7.26.0 - '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.24.7)': + '@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 + '@babel/core': 7.26.0 - '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.24.7)': + '@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 + '@babel/core': 7.26.0 - '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.24.7)': + '@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 + '@babel/core': 7.26.0 - '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.24.7)': + '@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 + '@babel/core': 7.26.0 - '@svgr/babel-preset@8.1.0(@babel/core@7.24.7)': + '@svgr/babel-preset@8.1.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.24.7) - '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.24.7) - '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.24.7) - '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.24.7) - '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.24.7) - '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.24.7) - '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.24.7) - '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.24.7) + '@babel/core': 7.26.0 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.26.0) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.26.0) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.26.0) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.26.0) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.26.0) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.26.0) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.26.0) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.26.0) - '@svgr/core@8.1.0(typescript@5.5.3)': + '@svgr/core@8.1.0(typescript@5.7.2)': dependencies: - '@babel/core': 7.24.7 - '@svgr/babel-preset': 8.1.0(@babel/core@7.24.7) + '@babel/core': 7.26.0 + '@svgr/babel-preset': 8.1.0(@babel/core@7.26.0) camelcase: 6.3.0 - cosmiconfig: 8.3.6(typescript@5.5.3) + cosmiconfig: 8.3.6(typescript@5.7.2) snake-case: 3.0.4 transitivePeerDependencies: - supports-color @@ -7744,42 +9231,139 @@ snapshots: '@svgr/hast-util-to-babel-ast@8.0.0': dependencies: - '@babel/types': 7.24.7 + '@babel/types': 7.26.3 entities: 4.5.0 - '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.5.3))': + '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@5.7.2))': dependencies: - '@babel/core': 7.24.7 - '@svgr/babel-preset': 8.1.0(@babel/core@7.24.7) - '@svgr/core': 8.1.0(typescript@5.5.3) + '@babel/core': 7.26.0 + '@svgr/babel-preset': 8.1.0(@babel/core@7.26.0) + '@svgr/core': 8.1.0(typescript@5.7.2) '@svgr/hast-util-to-babel-ast': 8.0.0 svg-parser: 2.0.4 transitivePeerDependencies: - supports-color - '@svgr/plugin-svgo@8.1.0(@svgr/core@8.1.0(typescript@5.5.3))(typescript@5.5.3)': + '@svgr/plugin-svgo@8.1.0(@svgr/core@8.1.0(typescript@5.7.2))(typescript@5.7.2)': dependencies: - '@svgr/core': 8.1.0(typescript@5.5.3) - cosmiconfig: 8.3.6(typescript@5.5.3) + '@svgr/core': 8.1.0(typescript@5.7.2) + cosmiconfig: 8.3.6(typescript@5.7.2) deepmerge: 4.3.1 svgo: 3.3.2 transitivePeerDependencies: - typescript - '@svgr/webpack@8.1.0(typescript@5.5.3)': + '@svgr/webpack@8.1.0(typescript@5.7.2)': dependencies: - '@babel/core': 7.24.7 - '@babel/plugin-transform-react-constant-elements': 7.24.7(@babel/core@7.24.7) - '@babel/preset-env': 7.24.7(@babel/core@7.24.7) - '@babel/preset-react': 7.24.7(@babel/core@7.24.7) - '@babel/preset-typescript': 7.24.7(@babel/core@7.24.7) - '@svgr/core': 8.1.0(typescript@5.5.3) - '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.5.3)) - '@svgr/plugin-svgo': 8.1.0(@svgr/core@8.1.0(typescript@5.5.3))(typescript@5.5.3) + '@babel/core': 7.26.0 + '@babel/plugin-transform-react-constant-elements': 7.25.9(@babel/core@7.26.0) + '@babel/preset-env': 7.26.0(@babel/core@7.26.0) + '@babel/preset-react': 7.26.3(@babel/core@7.26.0) + '@babel/preset-typescript': 7.26.0(@babel/core@7.26.0) + '@svgr/core': 8.1.0(typescript@5.7.2) + '@svgr/plugin-jsx': 8.1.0(@svgr/core@8.1.0(typescript@5.7.2)) + '@svgr/plugin-svgo': 8.1.0(@svgr/core@8.1.0(typescript@5.7.2))(typescript@5.7.2) transitivePeerDependencies: - supports-color - typescript + '@swc/core-darwin-arm64@1.10.4': + optional: true + + '@swc/core-darwin-x64@1.10.4': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.10.4': + optional: true + + '@swc/core-linux-arm64-gnu@1.10.4': + optional: true + + '@swc/core-linux-arm64-musl@1.10.4': + optional: true + + '@swc/core-linux-x64-gnu@1.10.4': + optional: true + + '@swc/core-linux-x64-musl@1.10.4': + optional: true + + '@swc/core-win32-arm64-msvc@1.10.4': + optional: true + + '@swc/core-win32-ia32-msvc@1.10.4': + optional: true + + '@swc/core-win32-x64-msvc@1.10.4': + optional: true + + '@swc/core@1.10.4': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.17 + optionalDependencies: + '@swc/core-darwin-arm64': 1.10.4 + '@swc/core-darwin-x64': 1.10.4 + '@swc/core-linux-arm-gnueabihf': 1.10.4 + '@swc/core-linux-arm64-gnu': 1.10.4 + '@swc/core-linux-arm64-musl': 1.10.4 + '@swc/core-linux-x64-gnu': 1.10.4 + '@swc/core-linux-x64-musl': 1.10.4 + '@swc/core-win32-arm64-msvc': 1.10.4 + '@swc/core-win32-ia32-msvc': 1.10.4 + '@swc/core-win32-x64-msvc': 1.10.4 + + '@swc/counter@0.1.3': {} + + '@swc/html-darwin-arm64@1.10.4': + optional: true + + '@swc/html-darwin-x64@1.10.4': + optional: true + + '@swc/html-linux-arm-gnueabihf@1.10.4': + optional: true + + '@swc/html-linux-arm64-gnu@1.10.4': + optional: true + + '@swc/html-linux-arm64-musl@1.10.4': + optional: true + + '@swc/html-linux-x64-gnu@1.10.4': + optional: true + + '@swc/html-linux-x64-musl@1.10.4': + optional: true + + '@swc/html-win32-arm64-msvc@1.10.4': + optional: true + + '@swc/html-win32-ia32-msvc@1.10.4': + optional: true + + '@swc/html-win32-x64-msvc@1.10.4': + optional: true + + '@swc/html@1.10.4': + dependencies: + '@swc/counter': 0.1.3 + optionalDependencies: + '@swc/html-darwin-arm64': 1.10.4 + '@swc/html-darwin-x64': 1.10.4 + '@swc/html-linux-arm-gnueabihf': 1.10.4 + '@swc/html-linux-arm64-gnu': 1.10.4 + '@swc/html-linux-arm64-musl': 1.10.4 + '@swc/html-linux-x64-gnu': 1.10.4 + '@swc/html-linux-x64-musl': 1.10.4 + '@swc/html-win32-arm64-msvc': 1.10.4 + '@swc/html-win32-ia32-msvc': 1.10.4 + '@swc/html-win32-x64-msvc': 1.10.4 + + '@swc/types@0.1.17': + dependencies: + '@swc/counter': 0.1.3 + '@szmarczak/http-timer@5.0.1': dependencies: defer-to-connect: 2.0.1 @@ -7790,74 +9374,77 @@ snapshots: '@tybys/wasm-util@0.9.0': dependencies: - tslib: 2.6.3 + tslib: 2.8.1 optional: true '@types/acorn@4.0.6': dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.38 - '@types/node': 20.14.10 + '@types/node': 22.10.5 '@types/bonjour@3.5.13': dependencies: - '@types/node': 20.14.10 + '@types/node': 22.10.5 '@types/connect-history-api-fallback@1.5.4': dependencies: - '@types/express-serve-static-core': 4.19.5 - '@types/node': 20.14.10 + '@types/express-serve-static-core': 5.0.3 + '@types/node': 22.10.5 '@types/connect@3.4.38': dependencies: - '@types/node': 20.14.10 + '@types/node': 22.10.5 '@types/debug@4.1.12': dependencies: '@types/ms': 0.7.34 - '@types/dompurify@3.0.5': - dependencies: - '@types/trusted-types': 2.0.7 - '@types/eslint-scope@3.7.7': dependencies: - '@types/eslint': 8.56.10 - '@types/estree': 1.0.5 + '@types/eslint': 9.6.1 + '@types/estree': 1.0.6 - '@types/eslint@8.56.10': + '@types/eslint@9.6.1': dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 '@types/json-schema': 7.0.15 '@types/estree-jsx@1.0.5': dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 - '@types/estree@1.0.5': {} + '@types/estree@1.0.6': {} - '@types/express-serve-static-core@4.19.5': + '@types/express-serve-static-core@4.19.6': dependencies: - '@types/node': 20.14.10 - '@types/qs': 6.9.15 + '@types/node': 22.10.5 + '@types/qs': 6.9.17 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.4 + + '@types/express-serve-static-core@5.0.3': + dependencies: + '@types/node': 22.10.5 + '@types/qs': 6.9.17 '@types/range-parser': 1.2.7 '@types/send': 0.17.4 '@types/express@4.17.21': dependencies: '@types/body-parser': 1.19.5 - '@types/express-serve-static-core': 4.19.5 - '@types/qs': 6.9.15 + '@types/express-serve-static-core': 4.19.6 + '@types/qs': 6.9.17 '@types/serve-static': 1.15.7 '@types/gtag.js@0.0.12': {} '@types/hast@3.0.4': dependencies: - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 '@types/history@4.7.11': {} @@ -7867,9 +9454,9 @@ snapshots: '@types/http-errors@2.0.4': {} - '@types/http-proxy@1.17.14': + '@types/http-proxy@1.17.15': dependencies: - '@types/node': 20.14.10 + '@types/node': 22.10.5 '@types/istanbul-lib-coverage@2.0.6': {} @@ -7885,7 +9472,7 @@ snapshots: '@types/mdast@4.0.4': dependencies: - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 '@types/mdx@2.0.13': {} @@ -7895,60 +9482,64 @@ snapshots: '@types/node-forge@1.3.11': dependencies: - '@types/node': 20.14.10 + '@types/node': 22.10.5 '@types/node@17.0.45': {} - '@types/node@20.14.10': + '@types/node@22.10.5': dependencies: - undici-types: 5.26.5 + undici-types: 6.20.0 '@types/parse-json@4.0.2': {} - '@types/prismjs@1.26.4': {} + '@types/prismjs@1.26.5': {} - '@types/prop-types@15.7.12': {} + '@types/prop-types@15.7.14': {} - '@types/qs@6.9.15': {} + '@types/qs@6.9.17': {} '@types/range-parser@1.2.7': {} '@types/react-helmet@6.1.11': dependencies: - '@types/react': 18.3.3 + '@types/react': 19.0.2 '@types/react-router-config@5.0.11': dependencies: '@types/history': 4.7.11 - '@types/react': 18.3.3 + '@types/react': 19.0.2 '@types/react-router': 5.1.20 '@types/react-router-dom@5.3.3': dependencies: '@types/history': 4.7.11 - '@types/react': 18.3.3 + '@types/react': 19.0.2 '@types/react-router': 5.1.20 '@types/react-router@5.1.20': dependencies: '@types/history': 4.7.11 - '@types/react': 18.3.3 + '@types/react': 19.0.2 - '@types/react@18.3.3': + '@types/react@18.3.18': + dependencies: + '@types/prop-types': 15.7.14 + csstype: 3.1.3 + + '@types/react@19.0.2': dependencies: - '@types/prop-types': 15.7.12 csstype: 3.1.3 '@types/retry@0.12.0': {} '@types/sax@1.2.7': dependencies: - '@types/node': 20.14.10 + '@types/node': 22.10.5 '@types/send@0.17.4': dependencies: '@types/mime': 1.3.5 - '@types/node': 20.14.10 + '@types/node': 22.10.5 '@types/serve-index@1.9.4': dependencies: @@ -7957,107 +9548,108 @@ snapshots: '@types/serve-static@1.15.7': dependencies: '@types/http-errors': 2.0.4 - '@types/node': 20.14.10 + '@types/node': 22.10.5 '@types/send': 0.17.4 '@types/sockjs@0.3.36': dependencies: - '@types/node': 20.14.10 + '@types/node': 22.10.5 '@types/stylis@4.2.5': {} - '@types/trusted-types@2.0.7': {} + '@types/trusted-types@2.0.7': + optional: true - '@types/unist@2.0.10': {} + '@types/unist@2.0.11': {} - '@types/unist@3.0.2': {} + '@types/unist@3.0.3': {} - '@types/ws@8.5.10': + '@types/ws@8.5.13': dependencies: - '@types/node': 20.14.10 + '@types/node': 22.10.5 '@types/yargs-parser@21.0.3': {} - '@types/yargs@17.0.32': + '@types/yargs@17.0.33': dependencies: '@types/yargs-parser': 21.0.3 - '@ungap/structured-clone@1.2.0': {} + '@ungap/structured-clone@1.2.1': {} - '@webassemblyjs/ast@1.12.1': + '@webassemblyjs/ast@1.14.1': dependencies: - '@webassemblyjs/helper-numbers': 1.11.6 - '@webassemblyjs/helper-wasm-bytecode': 1.11.6 + '@webassemblyjs/helper-numbers': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 - '@webassemblyjs/floating-point-hex-parser@1.11.6': {} + '@webassemblyjs/floating-point-hex-parser@1.13.2': {} - '@webassemblyjs/helper-api-error@1.11.6': {} + '@webassemblyjs/helper-api-error@1.13.2': {} - '@webassemblyjs/helper-buffer@1.12.1': {} + '@webassemblyjs/helper-buffer@1.14.1': {} - '@webassemblyjs/helper-numbers@1.11.6': + '@webassemblyjs/helper-numbers@1.13.2': dependencies: - '@webassemblyjs/floating-point-hex-parser': 1.11.6 - '@webassemblyjs/helper-api-error': 1.11.6 + '@webassemblyjs/floating-point-hex-parser': 1.13.2 + '@webassemblyjs/helper-api-error': 1.13.2 '@xtuc/long': 4.2.2 - '@webassemblyjs/helper-wasm-bytecode@1.11.6': {} + '@webassemblyjs/helper-wasm-bytecode@1.13.2': {} - '@webassemblyjs/helper-wasm-section@1.12.1': + '@webassemblyjs/helper-wasm-section@1.14.1': dependencies: - '@webassemblyjs/ast': 1.12.1 - '@webassemblyjs/helper-buffer': 1.12.1 - '@webassemblyjs/helper-wasm-bytecode': 1.11.6 - '@webassemblyjs/wasm-gen': 1.12.1 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/wasm-gen': 1.14.1 - '@webassemblyjs/ieee754@1.11.6': + '@webassemblyjs/ieee754@1.13.2': dependencies: '@xtuc/ieee754': 1.2.0 - '@webassemblyjs/leb128@1.11.6': + '@webassemblyjs/leb128@1.13.2': dependencies: '@xtuc/long': 4.2.2 - '@webassemblyjs/utf8@1.11.6': {} + '@webassemblyjs/utf8@1.13.2': {} - '@webassemblyjs/wasm-edit@1.12.1': + '@webassemblyjs/wasm-edit@1.14.1': dependencies: - '@webassemblyjs/ast': 1.12.1 - '@webassemblyjs/helper-buffer': 1.12.1 - '@webassemblyjs/helper-wasm-bytecode': 1.11.6 - '@webassemblyjs/helper-wasm-section': 1.12.1 - '@webassemblyjs/wasm-gen': 1.12.1 - '@webassemblyjs/wasm-opt': 1.12.1 - '@webassemblyjs/wasm-parser': 1.12.1 - '@webassemblyjs/wast-printer': 1.12.1 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/helper-wasm-section': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-opt': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + '@webassemblyjs/wast-printer': 1.14.1 - '@webassemblyjs/wasm-gen@1.12.1': + '@webassemblyjs/wasm-gen@1.14.1': dependencies: - '@webassemblyjs/ast': 1.12.1 - '@webassemblyjs/helper-wasm-bytecode': 1.11.6 - '@webassemblyjs/ieee754': 1.11.6 - '@webassemblyjs/leb128': 1.11.6 - '@webassemblyjs/utf8': 1.11.6 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 - '@webassemblyjs/wasm-opt@1.12.1': + '@webassemblyjs/wasm-opt@1.14.1': dependencies: - '@webassemblyjs/ast': 1.12.1 - '@webassemblyjs/helper-buffer': 1.12.1 - '@webassemblyjs/wasm-gen': 1.12.1 - '@webassemblyjs/wasm-parser': 1.12.1 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 - '@webassemblyjs/wasm-parser@1.12.1': + '@webassemblyjs/wasm-parser@1.14.1': dependencies: - '@webassemblyjs/ast': 1.12.1 - '@webassemblyjs/helper-api-error': 1.11.6 - '@webassemblyjs/helper-wasm-bytecode': 1.11.6 - '@webassemblyjs/ieee754': 1.11.6 - '@webassemblyjs/leb128': 1.11.6 - '@webassemblyjs/utf8': 1.11.6 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-api-error': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 - '@webassemblyjs/wast-printer@1.12.1': + '@webassemblyjs/wast-printer@1.14.1': dependencies: - '@webassemblyjs/ast': 1.12.1 + '@webassemblyjs/ast': 1.14.1 '@xtuc/long': 4.2.2 '@xtuc/ieee754@1.2.0': {} @@ -8069,44 +9661,36 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 - acorn-import-attributes@1.9.5(acorn@8.12.1): + acorn-jsx@5.3.2(acorn@8.14.0): dependencies: - acorn: 8.12.1 + acorn: 8.14.0 - acorn-jsx@5.3.2(acorn@8.12.1): + acorn-walk@8.3.4: dependencies: - acorn: 8.12.1 + acorn: 8.14.0 - acorn-walk@8.3.3: - dependencies: - acorn: 8.12.1 - - acorn@8.12.1: {} + acorn@8.14.0: {} address@1.2.2: {} - agent-base@7.1.1: - dependencies: - debug: 4.3.5 - transitivePeerDependencies: - - supports-color + agent-base@7.1.3: {} aggregate-error@3.1.0: dependencies: clean-stack: 2.2.0 indent-string: 4.0.0 - ajv-formats@2.1.1(ajv@8.16.0): + ajv-formats@2.1.1(ajv@8.17.1): optionalDependencies: - ajv: 8.16.0 + ajv: 8.17.1 ajv-keywords@3.5.2(ajv@6.12.6): dependencies: ajv: 6.12.6 - ajv-keywords@5.1.0(ajv@8.16.0): + ajv-keywords@5.1.0(ajv@8.17.1): dependencies: - ajv: 8.16.0 + ajv: 8.17.1 fast-deep-equal: 3.1.3 ajv@6.12.6: @@ -8116,49 +9700,47 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.16.0: + ajv@8.17.1: dependencies: fast-deep-equal: 3.1.3 + fast-uri: 3.0.4 json-schema-traverse: 1.0.0 require-from-string: 2.0.2 - uri-js: 4.4.1 - algoliasearch-helper@3.22.2(algoliasearch@4.24.0): + algoliasearch-helper@3.22.6(algoliasearch@5.18.0): dependencies: '@algolia/events': 4.0.1 - algoliasearch: 4.24.0 + algoliasearch: 5.18.0 - algoliasearch@4.24.0: + algoliasearch@5.18.0: dependencies: - '@algolia/cache-browser-local-storage': 4.24.0 - '@algolia/cache-common': 4.24.0 - '@algolia/cache-in-memory': 4.24.0 - '@algolia/client-account': 4.24.0 - '@algolia/client-analytics': 4.24.0 - '@algolia/client-common': 4.24.0 - '@algolia/client-personalization': 4.24.0 - '@algolia/client-search': 4.24.0 - '@algolia/logger-common': 4.24.0 - '@algolia/logger-console': 4.24.0 - '@algolia/recommend': 4.24.0 - '@algolia/requester-browser-xhr': 4.24.0 - '@algolia/requester-common': 4.24.0 - '@algolia/requester-node-http': 4.24.0 - '@algolia/transporter': 4.24.0 + '@algolia/client-abtesting': 5.18.0 + '@algolia/client-analytics': 5.18.0 + '@algolia/client-common': 5.18.0 + '@algolia/client-insights': 5.18.0 + '@algolia/client-personalization': 5.18.0 + '@algolia/client-query-suggestions': 5.18.0 + '@algolia/client-search': 5.18.0 + '@algolia/ingestion': 1.18.0 + '@algolia/monitoring': 1.18.0 + '@algolia/recommend': 5.18.0 + '@algolia/requester-browser-xhr': 5.18.0 + '@algolia/requester-fetch': 5.18.0 + '@algolia/requester-node-http': 5.18.0 ansi-align@3.0.1: dependencies: string-width: 4.2.3 + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + ansi-html-community@0.0.8: {} ansi-regex@5.0.1: {} - ansi-regex@6.0.1: {} - - ansi-styles@3.2.1: - dependencies: - color-convert: 1.9.3 + ansi-regex@6.1.0: {} ansi-styles@4.3.0: dependencies: @@ -8179,10 +9761,10 @@ snapshots: argparse@2.0.1: {} - array-buffer-byte-length@1.0.1: + array-buffer-byte-length@1.0.2: dependencies: - call-bind: 1.0.7 - is-array-buffer: 3.0.4 + call-bound: 1.0.3 + is-array-buffer: 3.0.5 array-flatten@1.1.1: {} @@ -8190,91 +9772,90 @@ snapshots: array.prototype.filter@1.0.4: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.3 + es-abstract: 1.23.9 es-array-method-boxes-properly: 1.0.0 es-object-atoms: 1.0.0 - is-string: 1.0.7 + is-string: 1.1.1 - array.prototype.flat@1.3.2: + array.prototype.flat@1.3.3: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.3 + es-abstract: 1.23.9 es-shim-unscopables: 1.0.2 - arraybuffer.prototype.slice@1.0.3: + arraybuffer.prototype.slice@1.0.4: dependencies: - array-buffer-byte-length: 1.0.1 - call-bind: 1.0.7 + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 define-properties: 1.2.1 - es-abstract: 1.23.3 + es-abstract: 1.23.9 es-errors: 1.3.0 - get-intrinsic: 1.2.4 - is-array-buffer: 3.0.4 - is-shared-array-buffer: 1.0.3 + get-intrinsic: 1.2.7 + is-array-buffer: 3.0.5 - astring@1.8.6: {} + astring@1.9.0: {} asynckit@0.4.0: {} at-least-node@1.0.0: {} - autoprefixer@10.4.19(postcss@8.4.39): + autoprefixer@10.4.20(postcss@8.4.49): dependencies: - browserslist: 4.23.1 - caniuse-lite: 1.0.30001640 + browserslist: 4.24.3 + caniuse-lite: 1.0.30001690 fraction.js: 4.3.7 normalize-range: 0.1.2 - picocolors: 1.0.1 - postcss: 8.4.39 + picocolors: 1.1.1 + postcss: 8.4.49 postcss-value-parser: 4.2.0 available-typed-arrays@1.0.7: dependencies: possible-typed-array-names: 1.0.0 - axios@1.7.2: + axios@1.7.9: dependencies: - follow-redirects: 1.15.6(debug@4.3.5) - form-data: 4.0.0 + follow-redirects: 1.15.9(debug@4.4.0) + form-data: 4.0.1 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug - babel-loader@9.1.3(@babel/core@7.24.7)(webpack@5.92.1): + babel-loader@9.2.1(@babel/core@7.26.0)(webpack@5.97.1(@swc/core@1.10.4)): dependencies: - '@babel/core': 7.24.7 + '@babel/core': 7.26.0 find-cache-dir: 4.0.0 - schema-utils: 4.2.0 - webpack: 5.92.1 + schema-utils: 4.3.0 + webpack: 5.97.1(@swc/core@1.10.4) babel-plugin-dynamic-import-node@2.3.3: dependencies: - object.assign: 4.1.5 + object.assign: 4.1.7 - babel-plugin-polyfill-corejs2@0.4.11(@babel/core@7.24.7): + babel-plugin-polyfill-corejs2@0.4.12(@babel/core@7.26.0): dependencies: - '@babel/compat-data': 7.24.7 - '@babel/core': 7.24.7 - '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.7) + '@babel/compat-data': 7.26.3 + '@babel/core': 7.26.0 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.0) semver: 6.3.1 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-corejs3@0.10.4(@babel/core@7.24.7): + babel-plugin-polyfill-corejs3@0.10.6(@babel/core@7.26.0): dependencies: - '@babel/core': 7.24.7 - '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.7) - core-js-compat: 3.37.1 + '@babel/core': 7.26.0 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.0) + core-js-compat: 3.39.0 transitivePeerDependencies: - supports-color - babel-plugin-polyfill-regenerator@0.6.2(@babel/core@7.24.7): + babel-plugin-polyfill-regenerator@0.6.3(@babel/core@7.26.0): dependencies: - '@babel/core': 7.24.7 - '@babel/helper-define-polyfill-provider': 0.6.2(@babel/core@7.24.7) + '@babel/core': 7.26.0 + '@babel/helper-define-polyfill-provider': 0.6.3(@babel/core@7.26.0) transitivePeerDependencies: - supports-color @@ -8288,7 +9869,7 @@ snapshots: binary-extensions@2.3.0: {} - body-parser@1.20.2: + body-parser@1.20.3: dependencies: bytes: 3.1.2 content-type: 1.0.5 @@ -8298,14 +9879,14 @@ snapshots: http-errors: 2.0.0 iconv-lite: 0.4.24 on-finished: 2.4.1 - qs: 6.11.0 + qs: 6.13.0 raw-body: 2.5.2 type-is: 1.6.18 unpipe: 1.0.0 transitivePeerDependencies: - supports-color - bonjour-service@1.2.1: + bonjour-service@1.3.0: dependencies: fast-deep-equal: 3.1.3 multicast-dns: 7.2.5 @@ -8327,7 +9908,7 @@ snapshots: dependencies: ansi-align: 3.0.1 camelcase: 7.0.1 - chalk: 5.3.0 + chalk: 5.4.1 cli-boxes: 3.0.0 string-width: 5.1.2 type-fest: 2.19.0 @@ -8347,12 +9928,12 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.23.1: + browserslist@4.24.3: dependencies: - caniuse-lite: 1.0.30001640 - electron-to-chromium: 1.4.817 - node-releases: 2.0.14 - update-browserslist-db: 1.1.0(browserslist@4.23.1) + caniuse-lite: 1.0.30001690 + electron-to-chromium: 1.5.76 + node-releases: 2.0.19 + update-browserslist-db: 1.1.1(browserslist@4.24.3) buffer-from@1.1.2: {} @@ -8362,24 +9943,33 @@ snapshots: cacheable-lookup@7.0.0: {} - cacheable-request@12.0.1: + cacheable-request@10.2.14: dependencies: '@types/http-cache-semantics': 4.0.4 - get-stream: 9.0.1 + get-stream: 6.0.1 http-cache-semantics: 4.1.1 keyv: 4.5.4 mimic-response: 4.0.0 normalize-url: 8.0.1 responselike: 3.0.0 - call-bind@1.0.7: + call-bind-apply-helpers@1.0.1: dependencies: - es-define-property: 1.0.0 es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.4 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.1 + es-define-property: 1.0.1 + get-intrinsic: 1.2.7 set-function-length: 1.2.2 + call-bound@1.0.3: + dependencies: + call-bind-apply-helpers: 1.0.1 + get-intrinsic: 1.2.7 + call-me-maybe@1.0.2: {} callsites@3.1.0: {} @@ -8387,7 +9977,7 @@ snapshots: camel-case@4.1.2: dependencies: pascal-case: 3.1.2 - tslib: 2.6.3 + tslib: 2.8.1 camelcase@6.3.0: {} @@ -8397,27 +9987,21 @@ snapshots: caniuse-api@3.0.0: dependencies: - browserslist: 4.23.1 - caniuse-lite: 1.0.30001640 + browserslist: 4.24.3 + caniuse-lite: 1.0.30001690 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 - caniuse-lite@1.0.30001640: {} + caniuse-lite@1.0.30001690: {} ccount@2.0.1: {} - chalk@2.4.2: - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - chalk@5.3.0: {} + chalk@5.4.1: {} char-regex@1.0.2: {} @@ -8436,17 +10020,31 @@ snapshots: css-what: 6.1.0 domelementtype: 2.3.0 domhandler: 5.0.3 - domutils: 3.1.0 + domutils: 3.2.1 + + cheerio@1.0.0: + dependencies: + cheerio-select: 2.1.0 + dom-serializer: 2.0.0 + domhandler: 5.0.3 + domutils: 3.2.1 + encoding-sniffer: 0.2.0 + htmlparser2: 9.1.0 + parse5: 7.2.1 + parse5-htmlparser2-tree-adapter: 7.1.0 + parse5-parser-stream: 7.1.2 + undici: 6.21.0 + whatwg-mimetype: 4.0.0 cheerio@1.0.0-rc.12: dependencies: cheerio-select: 2.1.0 dom-serializer: 2.0.0 domhandler: 5.0.3 - domutils: 3.1.0 + domutils: 3.2.1 htmlparser2: 8.0.2 - parse5: 7.1.2 - parse5-htmlparser2-tree-adapter: 7.0.0 + parse5: 7.2.1 + parse5-htmlparser2-tree-adapter: 7.1.0 chokidar@3.6.0: dependencies: @@ -8498,16 +10096,10 @@ snapshots: collapse-white-space@2.1.0: {} - color-convert@1.9.3: - dependencies: - color-name: 1.1.3 - color-convert@2.0.1: dependencies: color-name: 1.1.4 - color-name@1.1.3: {} - color-name@1.1.4: {} colord@2.9.3: {} @@ -8522,6 +10114,8 @@ snapshots: dependencies: delayed-stream: 1.0.0 + comlink@4.4.2: {} + comma-separated-tokens@2.0.3: {} commander@10.0.1: {} @@ -8538,30 +10132,28 @@ snapshots: compressible@2.0.18: dependencies: - mime-db: 1.52.0 + mime-db: 1.53.0 - compression@1.7.4: + compression@1.7.5: dependencies: - accepts: 1.3.8 - bytes: 3.0.0 + bytes: 3.1.2 compressible: 2.0.18 debug: 2.6.9 + negotiator: 0.6.4 on-headers: 1.0.2 - safe-buffer: 5.1.2 + safe-buffer: 5.2.1 vary: 1.1.2 transitivePeerDependencies: - supports-color concat-map@0.0.1: {} - concurrently@8.2.2: + concurrently@9.1.2: dependencies: chalk: 4.1.2 - date-fns: 2.30.0 lodash: 4.17.21 rxjs: 7.8.1 - shell-quote: 1.8.1 - spawn-command: 0.0.2 + shell-quote: 1.8.2 supports-color: 8.1.1 tree-kill: 1.2.2 yargs: 17.7.2 @@ -8581,7 +10173,7 @@ snapshots: connect-history-api-fallback@2.0.0: {} - consola@2.15.3: {} + consola@3.3.3: {} content-disposition@0.5.2: {} @@ -8595,27 +10187,27 @@ snapshots: cookie-signature@1.0.6: {} - cookie@0.6.0: {} + cookie@0.7.1: {} copy-text-to-clipboard@3.2.0: {} - copy-webpack-plugin@11.0.0(webpack@5.92.1): + copy-webpack-plugin@11.0.0(webpack@5.97.1(@swc/core@1.10.4)): dependencies: - fast-glob: 3.3.2 + fast-glob: 3.3.3 glob-parent: 6.0.2 globby: 13.2.2 normalize-path: 3.0.0 - schema-utils: 4.2.0 + schema-utils: 4.3.0 serialize-javascript: 6.0.2 - webpack: 5.92.1 + webpack: 5.97.1(@swc/core@1.10.4) - core-js-compat@3.37.1: + core-js-compat@3.39.0: dependencies: - browserslist: 4.23.1 + browserslist: 4.24.3 - core-js-pure@3.37.1: {} + core-js-pure@3.39.0: {} - core-js@3.37.1: {} + core-js@3.39.0: {} core-util-is@1.0.3: {} @@ -8627,16 +10219,16 @@ snapshots: path-type: 4.0.0 yaml: 1.10.2 - cosmiconfig@8.3.6(typescript@5.5.3): + cosmiconfig@8.3.6(typescript@5.7.2): dependencies: import-fresh: 3.3.0 js-yaml: 4.1.0 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: - typescript: 5.5.3 + typescript: 5.7.2 - cross-spawn@7.0.3: + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 @@ -8646,37 +10238,53 @@ snapshots: dependencies: type-fest: 1.4.0 + css-blank-pseudo@7.0.1(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + postcss-selector-parser: 7.0.0 + css-color-keywords@1.0.0: {} - css-declaration-sorter@7.2.0(postcss@8.4.39): + css-declaration-sorter@7.2.0(postcss@8.4.49): dependencies: - postcss: 8.4.39 + postcss: 8.4.49 - css-loader@6.11.0(webpack@5.92.1): + css-has-pseudo@7.0.2(postcss@8.4.49): dependencies: - icss-utils: 5.1.0(postcss@8.4.39) - postcss: 8.4.39 - postcss-modules-extract-imports: 3.1.0(postcss@8.4.39) - postcss-modules-local-by-default: 4.0.5(postcss@8.4.39) - postcss-modules-scope: 3.2.0(postcss@8.4.39) - postcss-modules-values: 4.0.0(postcss@8.4.39) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.0.0) + postcss: 8.4.49 + postcss-selector-parser: 7.0.0 postcss-value-parser: 4.2.0 - semver: 7.6.2 - optionalDependencies: - webpack: 5.92.1 - css-minimizer-webpack-plugin@5.0.1(clean-css@5.3.3)(webpack@5.92.1): + css-loader@6.11.0(webpack@5.97.1(@swc/core@1.10.4)): + dependencies: + icss-utils: 5.1.0(postcss@8.4.49) + postcss: 8.4.49 + postcss-modules-extract-imports: 3.1.0(postcss@8.4.49) + postcss-modules-local-by-default: 4.2.0(postcss@8.4.49) + postcss-modules-scope: 3.2.1(postcss@8.4.49) + postcss-modules-values: 4.0.0(postcss@8.4.49) + postcss-value-parser: 4.2.0 + semver: 7.6.3 + optionalDependencies: + webpack: 5.97.1(@swc/core@1.10.4) + + css-minimizer-webpack-plugin@5.0.1(clean-css@5.3.3)(webpack@5.97.1(@swc/core@1.10.4)): dependencies: '@jridgewell/trace-mapping': 0.3.25 - cssnano: 6.1.2(postcss@8.4.39) + cssnano: 6.1.2(postcss@8.4.49) jest-worker: 29.7.0 - postcss: 8.4.39 - schema-utils: 4.2.0 + postcss: 8.4.49 + schema-utils: 4.3.0 serialize-javascript: 6.0.2 - webpack: 5.92.1 + webpack: 5.97.1(@swc/core@1.10.4) optionalDependencies: clean-css: 5.3.3 + css-prefers-color-scheme@10.0.0(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + css-select@4.3.0: dependencies: boolbase: 1.0.0 @@ -8690,7 +10298,7 @@ snapshots: boolbase: 1.0.0 css-what: 6.1.0 domhandler: 5.0.3 - domutils: 3.1.0 + domutils: 3.2.1 nth-check: 2.1.1 css-to-react-native@3.2.0: @@ -8702,108 +10310,106 @@ snapshots: css-tree@2.2.1: dependencies: mdn-data: 2.0.28 - source-map-js: 1.2.0 + source-map-js: 1.2.1 css-tree@2.3.1: dependencies: mdn-data: 2.0.30 - source-map-js: 1.2.0 + source-map-js: 1.2.1 css-what@6.1.0: {} + cssdb@8.2.3: {} + cssesc@3.0.0: {} - cssnano-preset-advanced@6.1.2(postcss@8.4.39): + cssnano-preset-advanced@6.1.2(postcss@8.4.49): dependencies: - autoprefixer: 10.4.19(postcss@8.4.39) - browserslist: 4.23.1 - cssnano-preset-default: 6.1.2(postcss@8.4.39) - postcss: 8.4.39 - postcss-discard-unused: 6.0.5(postcss@8.4.39) - postcss-merge-idents: 6.0.3(postcss@8.4.39) - postcss-reduce-idents: 6.0.3(postcss@8.4.39) - postcss-zindex: 6.0.2(postcss@8.4.39) + autoprefixer: 10.4.20(postcss@8.4.49) + browserslist: 4.24.3 + cssnano-preset-default: 6.1.2(postcss@8.4.49) + postcss: 8.4.49 + postcss-discard-unused: 6.0.5(postcss@8.4.49) + postcss-merge-idents: 6.0.3(postcss@8.4.49) + postcss-reduce-idents: 6.0.3(postcss@8.4.49) + postcss-zindex: 6.0.2(postcss@8.4.49) - cssnano-preset-default@6.1.2(postcss@8.4.39): + cssnano-preset-default@6.1.2(postcss@8.4.49): dependencies: - browserslist: 4.23.1 - css-declaration-sorter: 7.2.0(postcss@8.4.39) - cssnano-utils: 4.0.2(postcss@8.4.39) - postcss: 8.4.39 - postcss-calc: 9.0.1(postcss@8.4.39) - postcss-colormin: 6.1.0(postcss@8.4.39) - postcss-convert-values: 6.1.0(postcss@8.4.39) - postcss-discard-comments: 6.0.2(postcss@8.4.39) - postcss-discard-duplicates: 6.0.3(postcss@8.4.39) - postcss-discard-empty: 6.0.3(postcss@8.4.39) - postcss-discard-overridden: 6.0.2(postcss@8.4.39) - postcss-merge-longhand: 6.0.5(postcss@8.4.39) - postcss-merge-rules: 6.1.1(postcss@8.4.39) - postcss-minify-font-values: 6.1.0(postcss@8.4.39) - postcss-minify-gradients: 6.0.3(postcss@8.4.39) - postcss-minify-params: 6.1.0(postcss@8.4.39) - postcss-minify-selectors: 6.0.4(postcss@8.4.39) - postcss-normalize-charset: 6.0.2(postcss@8.4.39) - postcss-normalize-display-values: 6.0.2(postcss@8.4.39) - postcss-normalize-positions: 6.0.2(postcss@8.4.39) - postcss-normalize-repeat-style: 6.0.2(postcss@8.4.39) - postcss-normalize-string: 6.0.2(postcss@8.4.39) - postcss-normalize-timing-functions: 6.0.2(postcss@8.4.39) - postcss-normalize-unicode: 6.1.0(postcss@8.4.39) - postcss-normalize-url: 6.0.2(postcss@8.4.39) - postcss-normalize-whitespace: 6.0.2(postcss@8.4.39) - postcss-ordered-values: 6.0.2(postcss@8.4.39) - postcss-reduce-initial: 6.1.0(postcss@8.4.39) - postcss-reduce-transforms: 6.0.2(postcss@8.4.39) - postcss-svgo: 6.0.3(postcss@8.4.39) - postcss-unique-selectors: 6.0.4(postcss@8.4.39) + browserslist: 4.24.3 + css-declaration-sorter: 7.2.0(postcss@8.4.49) + cssnano-utils: 4.0.2(postcss@8.4.49) + postcss: 8.4.49 + postcss-calc: 9.0.1(postcss@8.4.49) + postcss-colormin: 6.1.0(postcss@8.4.49) + postcss-convert-values: 6.1.0(postcss@8.4.49) + postcss-discard-comments: 6.0.2(postcss@8.4.49) + postcss-discard-duplicates: 6.0.3(postcss@8.4.49) + postcss-discard-empty: 6.0.3(postcss@8.4.49) + postcss-discard-overridden: 6.0.2(postcss@8.4.49) + postcss-merge-longhand: 6.0.5(postcss@8.4.49) + postcss-merge-rules: 6.1.1(postcss@8.4.49) + postcss-minify-font-values: 6.1.0(postcss@8.4.49) + postcss-minify-gradients: 6.0.3(postcss@8.4.49) + postcss-minify-params: 6.1.0(postcss@8.4.49) + postcss-minify-selectors: 6.0.4(postcss@8.4.49) + postcss-normalize-charset: 6.0.2(postcss@8.4.49) + postcss-normalize-display-values: 6.0.2(postcss@8.4.49) + postcss-normalize-positions: 6.0.2(postcss@8.4.49) + postcss-normalize-repeat-style: 6.0.2(postcss@8.4.49) + postcss-normalize-string: 6.0.2(postcss@8.4.49) + postcss-normalize-timing-functions: 6.0.2(postcss@8.4.49) + postcss-normalize-unicode: 6.1.0(postcss@8.4.49) + postcss-normalize-url: 6.0.2(postcss@8.4.49) + postcss-normalize-whitespace: 6.0.2(postcss@8.4.49) + postcss-ordered-values: 6.0.2(postcss@8.4.49) + postcss-reduce-initial: 6.1.0(postcss@8.4.49) + postcss-reduce-transforms: 6.0.2(postcss@8.4.49) + postcss-svgo: 6.0.3(postcss@8.4.49) + postcss-unique-selectors: 6.0.4(postcss@8.4.49) - cssnano-utils@4.0.2(postcss@8.4.39): + cssnano-utils@4.0.2(postcss@8.4.49): dependencies: - postcss: 8.4.39 + postcss: 8.4.49 - cssnano@6.1.2(postcss@8.4.39): + cssnano@6.1.2(postcss@8.4.49): dependencies: - cssnano-preset-default: 6.1.2(postcss@8.4.39) - lilconfig: 3.1.2 - postcss: 8.4.39 + cssnano-preset-default: 6.1.2(postcss@8.4.49) + lilconfig: 3.1.3 + postcss: 8.4.49 csso@5.0.5: dependencies: css-tree: 2.2.1 - cssstyle@4.0.1: + cssstyle@4.1.0: dependencies: - rrweb-cssom: 0.6.0 + rrweb-cssom: 0.7.1 csstype@3.1.3: {} data-urls@5.0.0: dependencies: whatwg-mimetype: 4.0.0 - whatwg-url: 14.0.0 + whatwg-url: 14.1.0 - data-view-buffer@1.0.1: + data-view-buffer@1.0.2: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 es-errors: 1.3.0 - is-data-view: 1.0.1 + is-data-view: 1.0.2 - data-view-byte-length@1.0.1: + data-view-byte-length@1.0.2: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 es-errors: 1.3.0 - is-data-view: 1.0.1 + is-data-view: 1.0.2 - data-view-byte-offset@1.0.0: + data-view-byte-offset@1.0.1: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 es-errors: 1.3.0 - is-data-view: 1.0.1 - - date-fns@2.30.0: - dependencies: - '@babel/runtime': 7.24.7 + is-data-view: 1.0.2 debounce@1.2.1: {} @@ -8811,9 +10417,9 @@ snapshots: dependencies: ms: 2.0.0 - debug@4.3.5: + debug@4.4.0: dependencies: - ms: 2.1.2 + ms: 2.1.3 decimal.js@10.4.3: {} @@ -8839,9 +10445,9 @@ snapshots: define-data-property@1.1.4: dependencies: - es-define-property: 1.0.0 + es-define-property: 1.0.1 es-errors: 1.3.0 - gopd: 1.0.1 + gopd: 1.2.0 define-lazy-prop@2.0.0: {} @@ -8872,6 +10478,8 @@ snapshots: destroy@1.2.0: {} + detect-libc@1.0.3: {} + detect-node@2.1.0: {} detect-port-alt@1.1.6: @@ -8884,7 +10492,7 @@ snapshots: detect-port@1.6.1: dependencies: address: 1.2.2 - debug: 4.3.5 + debug: 4.4.0 transitivePeerDependencies: - supports-color @@ -8902,11 +10510,11 @@ snapshots: dependencies: '@leichtgewicht/ip-codec': 2.0.5 - docusaurus-plugin-redoc@2.1.1(@docusaurus/utils@3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3))(core-js@3.37.1)(enzyme@3.11.0)(mobx@6.13.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@6.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): + docusaurus-plugin-redoc@2.1.1(@docusaurus/utils@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(core-js@3.39.0)(enzyme@3.11.0)(mobx@6.13.5)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(styled-components@6.1.14(react-dom@19.0.0(react@19.0.0))(react@19.0.0)): dependencies: - '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@redocly/openapi-core': 1.16.0 - redoc: 2.1.5(core-js@3.37.1)(enzyme@3.11.0)(mobx@6.13.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@6.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + redoc: 2.1.5(core-js@3.39.0)(enzyme@3.11.0)(mobx@6.13.5)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(styled-components@6.1.14(react-dom@19.0.0(react@19.0.0))(react@19.0.0)) transitivePeerDependencies: - core-js - encoding @@ -8918,16 +10526,18 @@ snapshots: - styled-components - supports-color - docusaurus-theme-redoc@2.1.1(@docusaurus/theme-common@3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3))(core-js@3.37.1)(enzyme@3.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(webpack@5.92.1): + docusaurus-theme-redoc@2.2.0(@docusaurus/theme-common@3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(core-js@3.39.0)(enzyme@3.11.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(webpack@5.97.1(@swc/core@1.10.4)): dependencies: - '@docusaurus/theme-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) + '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@redocly/openapi-core': 1.16.0 clsx: 1.2.1 lodash: 4.17.21 - mobx: 6.13.0 - redoc: 2.1.5(core-js@3.37.1)(enzyme@3.11.0)(mobx@6.13.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@6.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) - styled-components: 6.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - webpack: 5.92.1 + mobx: 6.13.5 + postcss: 8.4.49 + postcss-prefix-selector: 1.16.1(postcss@8.4.49) + redoc: 2.1.5(core-js@3.39.0)(enzyme@3.11.0)(mobx@6.13.5)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(styled-components@6.1.14(react-dom@19.0.0(react@19.0.0))(react@19.0.0)) + styled-components: 6.1.14(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + webpack: 5.97.1(@swc/core@1.10.4) transitivePeerDependencies: - core-js - encoding @@ -8963,7 +10573,9 @@ snapshots: dependencies: domelementtype: 2.3.0 - dompurify@3.1.6: {} + dompurify@3.2.3: + optionalDependencies: + '@types/trusted-types': 2.0.7 domutils@2.8.0: dependencies: @@ -8971,7 +10583,7 @@ snapshots: domelementtype: 2.3.0 domhandler: 4.3.1 - domutils@3.1.0: + domutils@3.2.1: dependencies: dom-serializer: 2.0.0 domelementtype: 2.3.0 @@ -8980,19 +10592,25 @@ snapshots: dot-case@3.0.4: dependencies: no-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.8.1 dot-prop@6.0.1: dependencies: is-obj: 2.0.0 + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + duplexer@0.1.2: {} eastasianwidth@0.2.0: {} ee-first@1.1.1: {} - electron-to-chromium@1.4.817: {} + electron-to-chromium@1.5.76: {} emoji-regex@8.0.0: {} @@ -9002,11 +10620,18 @@ snapshots: emojis-list@3.0.0: {} - emoticon@4.0.1: {} + emoticon@4.1.0: {} encodeurl@1.0.2: {} - enhanced-resolve@5.17.0: + encodeurl@2.0.0: {} + + encoding-sniffer@0.2.0: + dependencies: + iconv-lite: 0.6.3 + whatwg-encoding: 3.1.1 + + enhanced-resolve@5.18.0: dependencies: graceful-fs: 4.2.11 tapable: 2.2.1 @@ -9022,99 +10647,103 @@ snapshots: enzyme@3.11.0: dependencies: - array.prototype.flat: 1.3.2 - cheerio: 1.0.0-rc.12 + array.prototype.flat: 1.3.3 + cheerio: 1.0.0 enzyme-shallow-equal: 1.0.7 - function.prototype.name: 1.1.6 + function.prototype.name: 1.1.8 has: 1.0.4 html-element-map: 1.3.1 - is-boolean-object: 1.1.2 + is-boolean-object: 1.2.1 is-callable: 1.2.7 - is-number-object: 1.0.7 - is-regex: 1.1.4 - is-string: 1.0.7 + is-number-object: 1.1.1 + is-regex: 1.2.1 + is-string: 1.1.1 is-subset: 0.1.1 lodash.escape: 4.0.1 lodash.isequal: 4.5.0 - object-inspect: 1.13.2 + object-inspect: 1.13.3 object-is: 1.1.6 - object.assign: 4.1.5 + object.assign: 4.1.7 object.entries: 1.1.8 - object.values: 1.2.0 + object.values: 1.2.1 raf: 3.4.1 rst-selector-parser: 2.2.3 - string.prototype.trim: 1.2.9 + string.prototype.trim: 1.2.10 error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 - es-abstract@1.23.3: + es-abstract@1.23.9: dependencies: - array-buffer-byte-length: 1.0.1 - arraybuffer.prototype.slice: 1.0.3 + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 available-typed-arrays: 1.0.7 - call-bind: 1.0.7 - data-view-buffer: 1.0.1 - data-view-byte-length: 1.0.1 - data-view-byte-offset: 1.0.0 - es-define-property: 1.0.0 + call-bind: 1.0.8 + call-bound: 1.0.3 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 es-errors: 1.3.0 es-object-atoms: 1.0.0 - es-set-tostringtag: 2.0.3 - es-to-primitive: 1.2.1 - function.prototype.name: 1.1.6 - get-intrinsic: 1.2.4 - get-symbol-description: 1.0.2 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.2.7 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 globalthis: 1.0.4 - gopd: 1.0.1 + gopd: 1.2.0 has-property-descriptors: 1.0.2 - has-proto: 1.0.3 - has-symbols: 1.0.3 + has-proto: 1.2.0 + has-symbols: 1.1.0 hasown: 2.0.2 - internal-slot: 1.0.7 - is-array-buffer: 3.0.4 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 is-callable: 1.2.7 - is-data-view: 1.0.1 - is-negative-zero: 2.0.3 - is-regex: 1.1.4 - is-shared-array-buffer: 1.0.3 - is-string: 1.0.7 - is-typed-array: 1.1.13 - is-weakref: 1.0.2 - object-inspect: 1.13.2 + is-data-view: 1.0.2 + is-regex: 1.2.1 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.0 + math-intrinsics: 1.1.0 + object-inspect: 1.13.3 object-keys: 1.1.1 - object.assign: 4.1.5 - regexp.prototype.flags: 1.5.2 - safe-array-concat: 1.1.2 - safe-regex-test: 1.0.3 - string.prototype.trim: 1.2.9 - string.prototype.trimend: 1.0.8 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 string.prototype.trimstart: 1.0.8 - typed-array-buffer: 1.0.2 - typed-array-byte-length: 1.0.1 - typed-array-byte-offset: 1.0.2 - typed-array-length: 1.0.6 - unbox-primitive: 1.0.2 - which-typed-array: 1.1.15 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.18 es-array-method-boxes-properly@1.0.0: {} - es-define-property@1.0.0: - dependencies: - get-intrinsic: 1.2.4 + es-define-property@1.0.1: {} es-errors@1.3.0: {} - es-module-lexer@1.5.4: {} + es-module-lexer@1.6.0: {} es-object-atoms@1.0.0: dependencies: es-errors: 1.3.0 - es-set-tostringtag@2.0.3: + es-set-tostringtag@2.1.0: dependencies: - get-intrinsic: 1.2.4 + es-errors: 1.3.0 + get-intrinsic: 1.2.7 has-tostringtag: 1.0.2 hasown: 2.0.2 @@ -9122,49 +10751,29 @@ snapshots: dependencies: hasown: 2.0.2 - es-to-primitive@1.2.1: + es-to-primitive@1.3.0: dependencies: is-callable: 1.2.7 - is-date-object: 1.0.5 - is-symbol: 1.0.4 + is-date-object: 1.1.0 + is-symbol: 1.1.1 es6-promise@3.3.1: {} - esbuild-loader@4.2.0(webpack@5.92.1): + esast-util-from-estree@2.0.0: dependencies: - esbuild: 0.21.5 - get-tsconfig: 4.7.5 - loader-utils: 2.0.4 - webpack: 5.92.1 - webpack-sources: 1.4.3 + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + unist-util-position-from-estree: 2.0.0 - esbuild@0.21.5: - optionalDependencies: - '@esbuild/aix-ppc64': 0.21.5 - '@esbuild/android-arm': 0.21.5 - '@esbuild/android-arm64': 0.21.5 - '@esbuild/android-x64': 0.21.5 - '@esbuild/darwin-arm64': 0.21.5 - '@esbuild/darwin-x64': 0.21.5 - '@esbuild/freebsd-arm64': 0.21.5 - '@esbuild/freebsd-x64': 0.21.5 - '@esbuild/linux-arm': 0.21.5 - '@esbuild/linux-arm64': 0.21.5 - '@esbuild/linux-ia32': 0.21.5 - '@esbuild/linux-loong64': 0.21.5 - '@esbuild/linux-mips64el': 0.21.5 - '@esbuild/linux-ppc64': 0.21.5 - '@esbuild/linux-riscv64': 0.21.5 - '@esbuild/linux-s390x': 0.21.5 - '@esbuild/linux-x64': 0.21.5 - '@esbuild/netbsd-x64': 0.21.5 - '@esbuild/openbsd-x64': 0.21.5 - '@esbuild/sunos-x64': 0.21.5 - '@esbuild/win32-arm64': 0.21.5 - '@esbuild/win32-ia32': 0.21.5 - '@esbuild/win32-x64': 0.21.5 + esast-util-from-js@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + acorn: 8.14.0 + esast-util-from-estree: 2.0.0 + vfile-message: 4.0.2 - escalade@3.1.2: {} + escalade@3.2.0: {} escape-goat@4.0.0: {} @@ -9193,7 +10802,7 @@ snapshots: estree-util-attach-comments@3.0.0: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 estree-util-build-jsx@3.0.1: dependencies: @@ -9204,24 +10813,29 @@ snapshots: estree-util-is-identifier-name@3.0.0: {} + estree-util-scope@1.0.0: + dependencies: + '@types/estree': 1.0.6 + devlop: 1.1.0 + estree-util-to-js@2.0.0: dependencies: '@types/estree-jsx': 1.0.5 - astring: 1.8.6 + astring: 1.9.0 source-map: 0.7.4 - estree-util-value-to-estree@3.1.2: + estree-util-value-to-estree@3.2.1: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 estree-util-visit@2.0.0: dependencies: '@types/estree-jsx': 1.0.5 - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 esutils@2.0.3: {} @@ -9231,7 +10845,7 @@ snapshots: eval@0.1.8: dependencies: - '@types/node': 20.14.10 + '@types/node': 22.10.5 require-like: 0.1.2 eventemitter3@4.0.7: {} @@ -9242,7 +10856,7 @@ snapshots: execa@5.1.1: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 get-stream: 6.0.1 human-signals: 2.1.0 is-stream: 2.0.1 @@ -9252,34 +10866,34 @@ snapshots: signal-exit: 3.0.7 strip-final-newline: 2.0.0 - express@4.19.2: + express@4.21.2: dependencies: accepts: 1.3.8 array-flatten: 1.1.1 - body-parser: 1.20.2 + body-parser: 1.20.3 content-disposition: 0.5.4 content-type: 1.0.5 - cookie: 0.6.0 + cookie: 0.7.1 cookie-signature: 1.0.6 debug: 2.6.9 depd: 2.0.0 - encodeurl: 1.0.2 + encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 1.2.0 + finalhandler: 1.3.1 fresh: 0.5.2 http-errors: 2.0.0 - merge-descriptors: 1.0.1 + merge-descriptors: 1.0.3 methods: 1.1.2 on-finished: 2.4.1 parseurl: 1.3.3 - path-to-regexp: 0.1.7 + path-to-regexp: 0.1.12 proxy-addr: 2.0.7 - qs: 6.11.0 + qs: 6.13.0 range-parser: 1.2.1 safe-buffer: 5.2.1 - send: 0.18.0 - serve-static: 1.15.0 + send: 0.19.0 + serve-static: 1.16.2 setprototypeof: 1.2.0 statuses: 2.0.1 type-is: 1.6.18 @@ -9296,23 +10910,25 @@ snapshots: fast-deep-equal@3.1.3: {} - fast-glob@3.3.2: + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 - micromatch: 4.0.7 + micromatch: 4.0.8 fast-json-stable-stringify@2.1.0: {} fast-safe-stringify@2.1.1: {} - fast-url-parser@1.1.3: - dependencies: - punycode: 1.4.1 + fast-uri@3.0.4: {} - fastq@1.17.1: + fast-xml-parser@4.5.1: + dependencies: + strnum: 1.0.5 + + fastq@1.18.0: dependencies: reusify: 1.0.4 @@ -9328,11 +10944,15 @@ snapshots: dependencies: xml-js: 1.6.11 - file-loader@6.2.0(webpack@5.92.1): + figures@3.2.0: + dependencies: + escape-string-regexp: 1.0.5 + + file-loader@6.2.0(webpack@5.97.1(@swc/core@1.10.4)): dependencies: loader-utils: 2.0.4 schema-utils: 3.3.0 - webpack: 5.92.1 + webpack: 5.97.1(@swc/core@1.10.4) filesize@8.0.7: {} @@ -9340,10 +10960,10 @@ snapshots: dependencies: to-regex-range: 5.0.1 - finalhandler@1.2.0: + finalhandler@1.3.1: dependencies: debug: 2.6.9 - encodeurl: 1.0.2 + encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 parseurl: 1.3.3 @@ -9373,9 +10993,9 @@ snapshots: flat@5.0.2: {} - follow-redirects@1.15.6(debug@4.3.5): + follow-redirects@1.15.9(debug@4.4.0): optionalDependencies: - debug: 4.3.5 + debug: 4.4.0 for-each@0.3.3: dependencies: @@ -9383,9 +11003,9 @@ snapshots: foreach@2.0.6: {} - fork-ts-checker-webpack-plugin@6.5.3(typescript@5.5.3)(webpack@5.92.1): + fork-ts-checker-webpack-plugin@6.5.3(typescript@5.7.2)(webpack@5.97.1(@swc/core@1.10.4)): dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 '@types/json-schema': 7.0.15 chalk: 4.1.2 chokidar: 3.6.0 @@ -9396,14 +11016,14 @@ snapshots: memfs: 3.5.3 minimatch: 3.1.2 schema-utils: 2.7.0 - semver: 7.6.2 + semver: 7.6.3 tapable: 1.1.3 - typescript: 5.5.3 - webpack: 5.92.1 + typescript: 5.7.2 + webpack: 5.97.1(@swc/core@1.10.4) - form-data-encoder@4.0.2: {} + form-data-encoder@2.1.4: {} - form-data@4.0.0: + form-data@4.0.1: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 @@ -9445,12 +11065,14 @@ snapshots: function-bind@1.1.2: {} - function.prototype.name@1.1.6: + function.prototype.name@1.1.8: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.3 define-properties: 1.2.1 - es-abstract: 1.23.3 functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 functions-have-names@1.2.3: {} @@ -9460,34 +11082,33 @@ snapshots: get-caller-file@2.0.5: {} - get-intrinsic@1.2.4: + get-intrinsic@1.2.7: dependencies: + call-bind-apply-helpers: 1.0.1 + es-define-property: 1.0.1 es-errors: 1.3.0 + es-object-atoms: 1.0.0 function-bind: 1.1.2 - has-proto: 1.0.3 - has-symbols: 1.0.3 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 hasown: 2.0.2 + math-intrinsics: 1.1.0 get-own-enumerable-property-symbols@3.0.2: {} + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.0.0 + get-stream@6.0.1: {} - get-stream@8.0.1: {} - - get-stream@9.0.1: + get-symbol-description@1.1.0: dependencies: - '@sec-ant/readable-stream': 0.4.1 - is-stream: 4.0.1 - - get-symbol-description@1.0.2: - dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 es-errors: 1.3.0 - get-intrinsic: 1.2.4 - - get-tsconfig@4.7.5: - dependencies: - resolve-pkg-maps: 1.0.0 + get-intrinsic: 1.2.7 github-slugger@1.5.0: {} @@ -9529,43 +11150,40 @@ snapshots: globalthis@1.0.4: dependencies: define-properties: 1.2.1 - gopd: 1.0.1 + gopd: 1.2.0 globby@11.1.0: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 - fast-glob: 3.3.2 - ignore: 5.3.1 + fast-glob: 3.3.3 + ignore: 5.3.2 merge2: 1.4.1 slash: 3.0.0 globby@13.2.2: dependencies: dir-glob: 3.0.1 - fast-glob: 3.3.2 - ignore: 5.3.1 + fast-glob: 3.3.3 + ignore: 5.3.2 merge2: 1.4.1 slash: 4.0.0 - gopd@1.0.1: - dependencies: - get-intrinsic: 1.2.4 + gopd@1.2.0: {} - got@14.4.1: + got@12.6.1: dependencies: - '@sindresorhus/is': 6.3.1 + '@sindresorhus/is': 5.6.0 '@szmarczak/http-timer': 5.0.1 cacheable-lookup: 7.0.0 - cacheable-request: 12.0.1 + cacheable-request: 10.2.14 decompress-response: 6.0.0 - form-data-encoder: 4.0.2 - get-stream: 8.0.1 + form-data-encoder: 2.1.4 + get-stream: 6.0.1 http2-wrapper: 2.2.1 lowercase-keys: 3.0.0 - p-cancelable: 4.0.1 + p-cancelable: 3.0.0 responselike: 3.0.0 - type-fest: 4.21.0 graceful-fs@4.2.10: {} @@ -9584,23 +11202,23 @@ snapshots: handle-thing@2.0.1: {} - has-bigints@1.0.2: {} - - has-flag@3.0.0: {} + has-bigints@1.1.0: {} has-flag@4.0.0: {} has-property-descriptors@1.0.2: dependencies: - es-define-property: 1.0.0 + es-define-property: 1.0.1 - has-proto@1.0.3: {} + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 - has-symbols@1.0.3: {} + has-symbols@1.1.0: {} has-tostringtag@1.0.2: dependencies: - has-symbols: 1.0.3 + has-symbols: 1.1.0 has-yarn@3.0.0: {} @@ -9610,40 +11228,40 @@ snapshots: dependencies: function-bind: 1.1.2 - hast-util-from-parse5@8.0.1: + hast-util-from-parse5@8.0.2: dependencies: '@types/hast': 3.0.4 - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 devlop: 1.1.0 - hastscript: 8.0.0 + hastscript: 9.0.0 property-information: 6.5.0 - vfile: 6.0.1 - vfile-location: 5.0.2 + vfile: 6.0.3 + vfile-location: 5.0.3 web-namespaces: 2.0.1 hast-util-parse-selector@4.0.0: dependencies: '@types/hast': 3.0.4 - hast-util-raw@9.0.4: + hast-util-raw@9.1.0: dependencies: '@types/hast': 3.0.4 - '@types/unist': 3.0.2 - '@ungap/structured-clone': 1.2.0 - hast-util-from-parse5: 8.0.1 + '@types/unist': 3.0.3 + '@ungap/structured-clone': 1.2.1 + hast-util-from-parse5: 8.0.2 hast-util-to-parse5: 8.0.0 html-void-elements: 3.0.0 mdast-util-to-hast: 13.2.0 - parse5: 7.1.2 + parse5: 7.2.1 unist-util-position: 5.0.0 unist-util-visit: 5.0.0 - vfile: 6.0.1 + vfile: 6.0.3 web-namespaces: 2.0.1 zwitch: 2.0.4 - hast-util-to-estree@3.1.0: + hast-util-to-estree@3.1.1: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 '@types/estree-jsx': 1.0.5 '@types/hast': 3.0.4 comma-separated-tokens: 2.0.3 @@ -9651,32 +11269,32 @@ snapshots: estree-util-attach-comments: 3.0.0 estree-util-is-identifier-name: 3.0.0 hast-util-whitespace: 3.0.0 - mdast-util-mdx-expression: 2.0.0 - mdast-util-mdx-jsx: 3.1.2 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.1.3 mdast-util-mdxjs-esm: 2.0.1 property-information: 6.5.0 space-separated-tokens: 2.0.2 - style-to-object: 0.4.4 + style-to-object: 1.0.8 unist-util-position: 5.0.0 zwitch: 2.0.4 transitivePeerDependencies: - supports-color - hast-util-to-jsx-runtime@2.3.0: + hast-util-to-jsx-runtime@2.3.2: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 '@types/hast': 3.0.4 - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 comma-separated-tokens: 2.0.3 devlop: 1.1.0 estree-util-is-identifier-name: 3.0.0 hast-util-whitespace: 3.0.0 - mdast-util-mdx-expression: 2.0.0 - mdast-util-mdx-jsx: 3.1.2 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.1.3 mdast-util-mdxjs-esm: 2.0.1 property-information: 6.5.0 space-separated-tokens: 2.0.2 - style-to-object: 1.0.6 + style-to-object: 1.0.8 unist-util-position: 5.0.0 vfile-message: 4.0.2 transitivePeerDependencies: @@ -9696,7 +11314,7 @@ snapshots: dependencies: '@types/hast': 3.0.4 - hastscript@8.0.0: + hastscript@9.0.0: dependencies: '@types/hast': 3.0.4 comma-separated-tokens: 2.0.3 @@ -9708,7 +11326,7 @@ snapshots: history@4.10.1: dependencies: - '@babel/runtime': 7.24.7 + '@babel/runtime': 7.26.0 loose-envify: 1.4.0 resolve-pathname: 3.0.0 tiny-invariant: 1.3.3 @@ -9729,7 +11347,7 @@ snapshots: html-element-map@1.3.1: dependencies: array.prototype.filter: 1.0.4 - call-bind: 1.0.7 + call-bind: 1.0.8 html-encoding-sniffer@4.0.0: dependencies: @@ -9747,7 +11365,7 @@ snapshots: he: 1.2.0 param-case: 3.0.4 relateurl: 0.2.7 - terser: 5.31.1 + terser: 5.37.0 html-minifier-terser@7.2.0: dependencies: @@ -9757,13 +11375,13 @@ snapshots: entities: 4.5.0 param-case: 3.0.4 relateurl: 0.2.7 - terser: 5.31.1 + terser: 5.37.0 html-tags@3.3.1: {} html-void-elements@3.0.0: {} - html-webpack-plugin@5.6.0(webpack@5.92.1): + html-webpack-plugin@5.6.3(webpack@5.97.1(@swc/core@1.10.4)): dependencies: '@types/html-minifier-terser': 6.1.0 html-minifier-terser: 6.1.0 @@ -9771,7 +11389,7 @@ snapshots: pretty-error: 4.0.0 tapable: 2.2.1 optionalDependencies: - webpack: 5.92.1 + webpack: 5.97.1(@swc/core@1.10.4) htmlparser2@6.1.0: dependencies: @@ -9784,7 +11402,14 @@ snapshots: dependencies: domelementtype: 2.3.0 domhandler: 5.0.3 - domutils: 3.1.0 + domutils: 3.2.1 + entities: 4.5.0 + + htmlparser2@9.1.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + domutils: 3.2.1 entities: 4.5.0 http-cache-semantics@4.1.1: {} @@ -9810,27 +11435,27 @@ snapshots: http-proxy-agent@7.0.2: dependencies: - agent-base: 7.1.1 - debug: 4.3.5 + agent-base: 7.1.3 + debug: 4.4.0 transitivePeerDependencies: - supports-color - http-proxy-middleware@2.0.6(@types/express@4.17.21)(debug@4.3.5): + http-proxy-middleware@2.0.7(@types/express@4.17.21)(debug@4.4.0): dependencies: - '@types/http-proxy': 1.17.14 - http-proxy: 1.18.1(debug@4.3.5) + '@types/http-proxy': 1.17.15 + http-proxy: 1.18.1(debug@4.4.0) is-glob: 4.0.3 is-plain-obj: 3.0.0 - micromatch: 4.0.7 + micromatch: 4.0.8 optionalDependencies: '@types/express': 4.17.21 transitivePeerDependencies: - debug - http-proxy@1.18.1(debug@4.3.5): + http-proxy@1.18.1(debug@4.4.0): dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.15.6(debug@4.3.5) + follow-redirects: 1.15.9(debug@4.4.0) requires-port: 1.0.0 transitivePeerDependencies: - debug @@ -9842,10 +11467,10 @@ snapshots: quick-lru: 5.1.1 resolve-alpn: 1.2.1 - https-proxy-agent@7.0.5: + https-proxy-agent@7.0.6: dependencies: - agent-base: 7.1.1 - debug: 4.3.5 + agent-base: 7.1.3 + debug: 4.4.0 transitivePeerDependencies: - supports-color @@ -9859,13 +11484,13 @@ snapshots: dependencies: safer-buffer: 2.1.2 - icss-utils@5.1.0(postcss@8.4.39): + icss-utils@5.1.0(postcss@8.4.49): dependencies: - postcss: 8.4.39 + postcss: 8.4.49 - ignore@5.3.1: {} + ignore@5.3.2: {} - image-size@1.1.1: + image-size@1.2.0: dependencies: queue: 6.0.2 @@ -9884,7 +11509,7 @@ snapshots: indent-string@4.0.0: {} - infima@0.2.0-alpha.43: {} + infima@0.2.0-alpha.45: {} inflight@1.0.6: dependencies: @@ -9899,15 +11524,13 @@ snapshots: ini@2.0.0: {} - inline-style-parser@0.1.1: {} + inline-style-parser@0.2.4: {} - inline-style-parser@0.2.3: {} - - internal-slot@1.0.7: + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 hasown: 2.0.2 - side-channel: 1.0.6 + side-channel: 1.1.0 interpret@1.4.0: {} @@ -9926,24 +11549,32 @@ snapshots: is-alphabetical: 2.0.1 is-decimal: 2.0.1 - is-array-buffer@3.0.4: + is-array-buffer@3.0.5: dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 + call-bind: 1.0.8 + call-bound: 1.0.3 + get-intrinsic: 1.2.7 is-arrayish@0.2.1: {} - is-bigint@1.0.4: + is-async-function@2.1.0: dependencies: - has-bigints: 1.0.2 + call-bound: 1.0.3 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 - is-boolean-object@1.1.2: + is-boolean-object@1.2.1: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 has-tostringtag: 1.0.2 is-callable@1.2.7: {} @@ -9952,16 +11583,19 @@ snapshots: dependencies: ci-info: 3.9.0 - is-core-module@2.14.0: + is-core-module@2.16.1: dependencies: hasown: 2.0.2 - is-data-view@1.0.1: + is-data-view@1.0.2: dependencies: - is-typed-array: 1.1.13 + call-bound: 1.0.3 + get-intrinsic: 1.2.7 + is-typed-array: 1.1.15 - is-date-object@1.0.5: + is-date-object@1.1.0: dependencies: + call-bound: 1.0.3 has-tostringtag: 1.0.2 is-decimal@2.0.1: {} @@ -9972,8 +11606,19 @@ snapshots: is-extglob@2.1.1: {} + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.3 + is-fullwidth-code-point@3.0.0: {} + is-generator-function@1.1.0: + dependencies: + call-bound: 1.0.3 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + is-glob@4.0.3: dependencies: is-extglob: 2.1.1 @@ -9985,12 +11630,13 @@ snapshots: global-dirs: 3.0.1 is-path-inside: 3.0.3 - is-negative-zero@2.0.3: {} + is-map@2.0.3: {} is-npm@6.0.0: {} - is-number-object@1.0.7: + is-number-object@1.1.1: dependencies: + call-bound: 1.0.3 has-tostringtag: 1.0.2 is-number@7.0.0: {} @@ -10013,46 +11659,54 @@ snapshots: is-potential-custom-element-name@1.0.1: {} - is-reference@3.0.2: + is-regex@1.2.1: dependencies: - '@types/estree': 1.0.5 - - is-regex@1.1.4: - dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 + gopd: 1.2.0 has-tostringtag: 1.0.2 + hasown: 2.0.2 is-regexp@1.0.0: {} is-root@2.1.0: {} - is-shared-array-buffer@1.0.3: + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 is-stream@2.0.1: {} - is-stream@4.0.1: {} - - is-string@1.0.7: + is-string@1.1.1: dependencies: + call-bound: 1.0.3 has-tostringtag: 1.0.2 is-subset@0.1.1: {} - is-symbol@1.0.4: + is-symbol@1.1.1: dependencies: - has-symbols: 1.0.3 + call-bound: 1.0.3 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 - is-typed-array@1.1.13: + is-typed-array@1.1.15: dependencies: - which-typed-array: 1.1.15 + which-typed-array: 1.1.18 is-typedarray@1.0.0: {} - is-weakref@1.0.2: + is-weakmap@2.0.2: {} + + is-weakref@1.1.0: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.3 + get-intrinsic: 1.2.7 is-wsl@2.2.0: dependencies: @@ -10070,21 +11724,22 @@ snapshots: isobject@3.0.1: {} - isomorphic-dompurify@2.12.0: + isomorphic-dompurify@2.19.0: dependencies: - '@types/dompurify': 3.0.5 - dompurify: 3.1.6 - jsdom: 24.1.0 + dompurify: 3.2.3 + jsdom: 25.0.1 transitivePeerDependencies: - bufferutil - canvas - supports-color - utf-8-validate + isomorphic-rslog@0.0.6: {} + jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.14.10 + '@types/node': 22.10.5 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -10092,18 +11747,18 @@ snapshots: jest-worker@27.5.1: dependencies: - '@types/node': 20.14.10 + '@types/node': 22.10.5 merge-stream: 2.0.0 supports-color: 8.1.1 jest-worker@29.7.0: dependencies: - '@types/node': 20.14.10 + '@types/node': 22.10.5 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jiti@1.21.6: {} + jiti@1.21.7: {} joi@17.13.3: dependencies: @@ -10126,27 +11781,27 @@ snapshots: dependencies: argparse: 2.0.1 - jsdom@24.1.0: + jsdom@25.0.1: dependencies: - cssstyle: 4.0.1 + cssstyle: 4.1.0 data-urls: 5.0.0 decimal.js: 10.4.3 - form-data: 4.0.0 + form-data: 4.0.1 html-encoding-sniffer: 4.0.0 http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.5 + https-proxy-agent: 7.0.6 is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.10 - parse5: 7.1.2 + nwsapi: 2.2.16 + parse5: 7.2.1 rrweb-cssom: 0.7.1 saxes: 6.0.0 symbol-tree: 3.2.4 - tough-cookie: 4.1.4 + tough-cookie: 5.0.0 w3c-xmlserializer: 5.0.0 webidl-conversions: 7.0.0 whatwg-encoding: 3.1.1 whatwg-mimetype: 4.0.0 - whatwg-url: 14.0.0 + whatwg-url: 14.1.0 ws: 8.18.0 xml-name-validator: 5.0.0 transitivePeerDependencies: @@ -10154,9 +11809,9 @@ snapshots: - supports-color - utf-8-validate - jsesc@0.5.0: {} + jsesc@3.0.2: {} - jsesc@2.5.2: {} + jsesc@3.1.0: {} json-buffer@3.0.1: {} @@ -10194,14 +11849,59 @@ snapshots: dependencies: package-json: 8.1.1 - launch-editor@2.8.0: + launch-editor@2.9.1: dependencies: - picocolors: 1.0.1 - shell-quote: 1.8.1 + picocolors: 1.1.1 + shell-quote: 1.8.2 leven@3.1.0: {} - lilconfig@3.1.2: {} + lightningcss-darwin-arm64@1.28.2: + optional: true + + lightningcss-darwin-x64@1.28.2: + optional: true + + lightningcss-freebsd-x64@1.28.2: + optional: true + + lightningcss-linux-arm-gnueabihf@1.28.2: + optional: true + + lightningcss-linux-arm64-gnu@1.28.2: + optional: true + + lightningcss-linux-arm64-musl@1.28.2: + optional: true + + lightningcss-linux-x64-gnu@1.28.2: + optional: true + + lightningcss-linux-x64-musl@1.28.2: + optional: true + + lightningcss-win32-arm64-msvc@1.28.2: + optional: true + + lightningcss-win32-x64-msvc@1.28.2: + optional: true + + lightningcss@1.28.2: + dependencies: + detect-libc: 1.0.3 + optionalDependencies: + lightningcss-darwin-arm64: 1.28.2 + lightningcss-darwin-x64: 1.28.2 + lightningcss-freebsd-x64: 1.28.2 + lightningcss-linux-arm-gnueabihf: 1.28.2 + lightningcss-linux-arm64-gnu: 1.28.2 + lightningcss-linux-arm64-musl: 1.28.2 + lightningcss-linux-x64-gnu: 1.28.2 + lightningcss-linux-x64-musl: 1.28.2 + lightningcss-win32-arm64-msvc: 1.28.2 + lightningcss-win32-x64-msvc: 1.28.2 + + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -10250,7 +11950,7 @@ snapshots: lower-case@2.0.2: dependencies: - tslib: 2.6.3 + tslib: 2.8.1 lowercase-keys@3.0.0: {} @@ -10266,45 +11966,51 @@ snapshots: markdown-extensions@2.0.0: {} - markdown-table@3.0.3: {} + markdown-table@2.0.0: + dependencies: + repeat-string: 1.6.1 - marked@13.0.2: {} + markdown-table@3.0.4: {} + + marked@15.0.5: {} marked@4.3.0: {} + math-intrinsics@1.1.0: {} + mdast-util-directive@3.0.0: dependencies: '@types/mdast': 4.0.4 - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.1 - mdast-util-to-markdown: 2.1.0 - parse-entities: 4.0.1 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 stringify-entities: 4.0.4 unist-util-visit-parents: 6.0.1 transitivePeerDependencies: - supports-color - mdast-util-find-and-replace@3.0.1: + mdast-util-find-and-replace@3.0.2: dependencies: '@types/mdast': 4.0.4 escape-string-regexp: 5.0.0 unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 - mdast-util-from-markdown@2.0.1: + mdast-util-from-markdown@2.0.2: dependencies: '@types/mdast': 4.0.4 - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 decode-named-character-reference: 1.0.2 devlop: 1.1.0 mdast-util-to-string: 4.0.0 - micromark: 4.0.0 - micromark-util-decode-numeric-character-reference: 2.0.1 - micromark-util-decode-string: 2.0.0 - micromark-util-normalize-identifier: 2.0.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark: 4.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 unist-util-stringify-position: 4.0.0 transitivePeerDependencies: - supports-color @@ -10314,35 +12020,35 @@ snapshots: '@types/mdast': 4.0.4 devlop: 1.1.0 escape-string-regexp: 5.0.0 - mdast-util-from-markdown: 2.0.1 - mdast-util-to-markdown: 2.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 micromark-extension-frontmatter: 2.0.0 transitivePeerDependencies: - supports-color - mdast-util-gfm-autolink-literal@2.0.0: + mdast-util-gfm-autolink-literal@2.0.1: dependencies: '@types/mdast': 4.0.4 ccount: 2.0.1 devlop: 1.1.0 - mdast-util-find-and-replace: 3.0.1 - micromark-util-character: 2.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 mdast-util-gfm-footnote@2.0.0: dependencies: '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.1 - mdast-util-to-markdown: 2.1.0 - micromark-util-normalize-identifier: 2.0.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 transitivePeerDependencies: - supports-color mdast-util-gfm-strikethrough@2.0.0: dependencies: '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.1 - mdast-util-to-markdown: 2.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -10350,9 +12056,9 @@ snapshots: dependencies: '@types/mdast': 4.0.4 devlop: 1.1.0 - markdown-table: 3.0.3 - mdast-util-from-markdown: 2.0.1 - mdast-util-to-markdown: 2.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -10360,47 +12066,46 @@ snapshots: dependencies: '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.1 - mdast-util-to-markdown: 2.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color mdast-util-gfm@3.0.0: dependencies: - mdast-util-from-markdown: 2.0.1 - mdast-util-gfm-autolink-literal: 2.0.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.1 mdast-util-gfm-footnote: 2.0.0 mdast-util-gfm-strikethrough: 2.0.0 mdast-util-gfm-table: 2.0.0 mdast-util-gfm-task-list-item: 2.0.0 - mdast-util-to-markdown: 2.1.0 + mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color - mdast-util-mdx-expression@2.0.0: + mdast-util-mdx-expression@2.0.1: dependencies: '@types/estree-jsx': 1.0.5 '@types/hast': 3.0.4 '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.1 - mdast-util-to-markdown: 2.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color - mdast-util-mdx-jsx@3.1.2: + mdast-util-mdx-jsx@3.1.3: dependencies: '@types/estree-jsx': 1.0.5 '@types/hast': 3.0.4 '@types/mdast': 4.0.4 - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 ccount: 2.0.1 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.1 - mdast-util-to-markdown: 2.1.0 - parse-entities: 4.0.1 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 stringify-entities: 4.0.4 - unist-util-remove-position: 5.0.0 unist-util-stringify-position: 4.0.0 vfile-message: 4.0.2 transitivePeerDependencies: @@ -10408,11 +12113,11 @@ snapshots: mdast-util-mdx@3.0.0: dependencies: - mdast-util-from-markdown: 2.0.1 - mdast-util-mdx-expression: 2.0.0 - mdast-util-mdx-jsx: 3.1.2 + mdast-util-from-markdown: 2.0.2 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.1.3 mdast-util-mdxjs-esm: 2.0.1 - mdast-util-to-markdown: 2.1.0 + mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -10422,8 +12127,8 @@ snapshots: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 devlop: 1.1.0 - mdast-util-from-markdown: 2.0.1 - mdast-util-to-markdown: 2.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 transitivePeerDependencies: - supports-color @@ -10436,22 +12141,23 @@ snapshots: dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 - '@ungap/structured-clone': 1.2.0 + '@ungap/structured-clone': 1.2.1 devlop: 1.1.0 - micromark-util-sanitize-uri: 2.0.0 + micromark-util-sanitize-uri: 2.0.1 trim-lines: 3.0.1 unist-util-position: 5.0.0 unist-util-visit: 5.0.0 - vfile: 6.0.1 + vfile: 6.0.3 - mdast-util-to-markdown@2.1.0: + mdast-util-to-markdown@2.1.2: dependencies: '@types/mdast': 4.0.4 - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 longest-streak: 3.1.0 mdast-util-phrasing: 4.1.0 mdast-util-to-string: 4.0.0 - micromark-util-decode-string: 2.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 unist-util-visit: 5.0.0 zwitch: 2.0.4 @@ -10469,7 +12175,7 @@ snapshots: dependencies: fs-monkey: 1.0.6 - merge-descriptors@1.0.1: {} + merge-descriptors@1.0.3: {} merge-stream@2.0.0: {} @@ -10477,88 +12183,88 @@ snapshots: methods@1.1.2: {} - micromark-core-commonmark@2.0.1: + micromark-core-commonmark@2.0.2: dependencies: decode-named-character-reference: 1.0.2 devlop: 1.1.0 - micromark-factory-destination: 2.0.0 - micromark-factory-label: 2.0.0 - micromark-factory-space: 2.0.0 - micromark-factory-title: 2.0.0 - micromark-factory-whitespace: 2.0.0 - micromark-util-character: 2.1.0 - micromark-util-chunked: 2.0.0 - micromark-util-classify-character: 2.0.0 - micromark-util-html-tag-name: 2.0.0 - micromark-util-normalize-identifier: 2.0.0 - micromark-util-resolve-all: 2.0.0 - micromark-util-subtokenize: 2.0.1 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 - micromark-extension-directive@3.0.0: + micromark-extension-directive@3.0.2: dependencies: devlop: 1.1.0 - micromark-factory-space: 2.0.0 - micromark-factory-whitespace: 2.0.0 - micromark-util-character: 2.1.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 - parse-entities: 4.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + parse-entities: 4.0.2 micromark-extension-frontmatter@2.0.0: dependencies: fault: 2.0.1 - micromark-util-character: 2.1.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 micromark-extension-gfm-autolink-literal@2.1.0: dependencies: - micromark-util-character: 2.1.0 - micromark-util-sanitize-uri: 2.0.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 micromark-extension-gfm-footnote@2.1.0: dependencies: devlop: 1.1.0 - micromark-core-commonmark: 2.0.1 - micromark-factory-space: 2.0.0 - micromark-util-character: 2.1.0 - micromark-util-normalize-identifier: 2.0.0 - micromark-util-sanitize-uri: 2.0.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-core-commonmark: 2.0.2 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 micromark-extension-gfm-strikethrough@2.1.0: dependencies: devlop: 1.1.0 - micromark-util-chunked: 2.0.0 - micromark-util-classify-character: 2.0.0 - micromark-util-resolve-all: 2.0.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 micromark-extension-gfm-table@2.1.0: dependencies: devlop: 1.1.0 - micromark-factory-space: 2.0.0 - micromark-util-character: 2.1.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 micromark-extension-gfm-tagfilter@2.0.0: dependencies: - micromark-util-types: 2.0.0 + micromark-util-types: 2.0.1 micromark-extension-gfm-task-list-item@2.1.0: dependencies: devlop: 1.1.0 - micromark-factory-space: 2.0.0 - micromark-util-character: 2.1.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 micromark-extension-gfm@3.0.0: dependencies: @@ -10568,81 +12274,83 @@ snapshots: micromark-extension-gfm-table: 2.1.0 micromark-extension-gfm-tagfilter: 2.0.0 micromark-extension-gfm-task-list-item: 2.1.0 - micromark-util-combine-extensions: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.1 micromark-extension-mdx-expression@3.0.0: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 devlop: 1.1.0 - micromark-factory-mdx-expression: 2.0.1 - micromark-factory-space: 2.0.0 - micromark-util-character: 2.1.0 + micromark-factory-mdx-expression: 2.0.2 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 micromark-util-events-to-acorn: 2.0.2 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 - micromark-extension-mdx-jsx@3.0.0: + micromark-extension-mdx-jsx@3.0.1: dependencies: '@types/acorn': 4.0.6 - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 devlop: 1.1.0 estree-util-is-identifier-name: 3.0.0 - micromark-factory-mdx-expression: 2.0.1 - micromark-factory-space: 2.0.0 - micromark-util-character: 2.1.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-factory-mdx-expression: 2.0.2 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.2 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 vfile-message: 4.0.2 micromark-extension-mdx-md@2.0.0: dependencies: - micromark-util-types: 2.0.0 + micromark-util-types: 2.0.1 micromark-extension-mdxjs-esm@3.0.0: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 devlop: 1.1.0 - micromark-core-commonmark: 2.0.1 - micromark-util-character: 2.1.0 + micromark-core-commonmark: 2.0.2 + micromark-util-character: 2.1.1 micromark-util-events-to-acorn: 2.0.2 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 unist-util-position-from-estree: 2.0.0 vfile-message: 4.0.2 micromark-extension-mdxjs@3.0.0: dependencies: - acorn: 8.12.1 - acorn-jsx: 5.3.2(acorn@8.12.1) + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) micromark-extension-mdx-expression: 3.0.0 - micromark-extension-mdx-jsx: 3.0.0 + micromark-extension-mdx-jsx: 3.0.1 micromark-extension-mdx-md: 2.0.0 micromark-extension-mdxjs-esm: 3.0.0 - micromark-util-combine-extensions: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.1 - micromark-factory-destination@2.0.0: + micromark-factory-destination@2.0.1: dependencies: - micromark-util-character: 2.1.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 - micromark-factory-label@2.0.0: + micromark-factory-label@2.0.1: dependencies: devlop: 1.1.0 - micromark-util-character: 2.1.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 - micromark-factory-mdx-expression@2.0.1: + micromark-factory-mdx-expression@2.0.2: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 devlop: 1.1.0 - micromark-util-character: 2.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 micromark-util-events-to-acorn: 2.0.2 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 unist-util-position-from-estree: 2.0.0 vfile-message: 4.0.2 @@ -10651,128 +12359,128 @@ snapshots: micromark-util-character: 1.2.0 micromark-util-types: 1.1.0 - micromark-factory-space@2.0.0: + micromark-factory-space@2.0.1: dependencies: - micromark-util-character: 2.1.0 - micromark-util-types: 2.0.0 + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.1 - micromark-factory-title@2.0.0: + micromark-factory-title@2.0.1: dependencies: - micromark-factory-space: 2.0.0 - micromark-util-character: 2.1.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 - micromark-factory-whitespace@2.0.0: + micromark-factory-whitespace@2.0.1: dependencies: - micromark-factory-space: 2.0.0 - micromark-util-character: 2.1.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 micromark-util-character@1.2.0: dependencies: micromark-util-symbol: 1.1.0 micromark-util-types: 1.1.0 - micromark-util-character@2.1.0: + micromark-util-character@2.1.1: dependencies: - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 - micromark-util-chunked@2.0.0: + micromark-util-chunked@2.0.1: dependencies: - micromark-util-symbol: 2.0.0 + micromark-util-symbol: 2.0.1 - micromark-util-classify-character@2.0.0: + micromark-util-classify-character@2.0.1: dependencies: - micromark-util-character: 2.1.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 - micromark-util-combine-extensions@2.0.0: + micromark-util-combine-extensions@2.0.1: dependencies: - micromark-util-chunked: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.1 - micromark-util-decode-numeric-character-reference@2.0.1: + micromark-util-decode-numeric-character-reference@2.0.2: dependencies: - micromark-util-symbol: 2.0.0 + micromark-util-symbol: 2.0.1 - micromark-util-decode-string@2.0.0: + micromark-util-decode-string@2.0.1: dependencies: decode-named-character-reference: 1.0.2 - micromark-util-character: 2.1.0 - micromark-util-decode-numeric-character-reference: 2.0.1 - micromark-util-symbol: 2.0.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 - micromark-util-encode@2.0.0: {} + micromark-util-encode@2.0.1: {} micromark-util-events-to-acorn@2.0.2: dependencies: '@types/acorn': 4.0.6 - '@types/estree': 1.0.5 - '@types/unist': 3.0.2 + '@types/estree': 1.0.6 + '@types/unist': 3.0.3 devlop: 1.1.0 estree-util-visit: 2.0.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 vfile-message: 4.0.2 - micromark-util-html-tag-name@2.0.0: {} + micromark-util-html-tag-name@2.0.1: {} - micromark-util-normalize-identifier@2.0.0: + micromark-util-normalize-identifier@2.0.1: dependencies: - micromark-util-symbol: 2.0.0 + micromark-util-symbol: 2.0.1 - micromark-util-resolve-all@2.0.0: + micromark-util-resolve-all@2.0.1: dependencies: - micromark-util-types: 2.0.0 + micromark-util-types: 2.0.1 - micromark-util-sanitize-uri@2.0.0: + micromark-util-sanitize-uri@2.0.1: dependencies: - micromark-util-character: 2.1.0 - micromark-util-encode: 2.0.0 - micromark-util-symbol: 2.0.0 + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 - micromark-util-subtokenize@2.0.1: + micromark-util-subtokenize@2.0.3: dependencies: devlop: 1.1.0 - micromark-util-chunked: 2.0.0 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 micromark-util-symbol@1.1.0: {} - micromark-util-symbol@2.0.0: {} + micromark-util-symbol@2.0.1: {} micromark-util-types@1.1.0: {} - micromark-util-types@2.0.0: {} + micromark-util-types@2.0.1: {} - micromark@4.0.0: + micromark@4.0.1: dependencies: '@types/debug': 4.1.12 - debug: 4.3.5 + debug: 4.4.0 decode-named-character-reference: 1.0.2 devlop: 1.1.0 - micromark-core-commonmark: 2.0.1 - micromark-factory-space: 2.0.0 - micromark-util-character: 2.1.0 - micromark-util-chunked: 2.0.0 - micromark-util-combine-extensions: 2.0.0 - micromark-util-decode-numeric-character-reference: 2.0.1 - micromark-util-encode: 2.0.0 - micromark-util-normalize-identifier: 2.0.0 - micromark-util-resolve-all: 2.0.0 - micromark-util-sanitize-uri: 2.0.0 - micromark-util-subtokenize: 2.0.1 - micromark-util-symbol: 2.0.0 - micromark-util-types: 2.0.0 + micromark-core-commonmark: 2.0.2 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 transitivePeerDependencies: - supports-color - micromatch@4.0.7: + micromatch@4.0.8: dependencies: braces: 3.0.3 picomatch: 2.3.1 @@ -10781,6 +12489,8 @@ snapshots: mime-db@1.52.0: {} + mime-db@1.53.0: {} + mime-types@2.1.18: dependencies: mime-db: 1.33.0 @@ -10797,11 +12507,11 @@ snapshots: mimic-response@4.0.0: {} - mini-css-extract-plugin@2.9.0(webpack@5.92.1): + mini-css-extract-plugin@2.9.2(webpack@5.97.1(@swc/core@1.10.4)): dependencies: - schema-utils: 4.2.0 + schema-utils: 4.3.0 tapable: 2.2.1 - webpack: 5.92.1 + webpack: 5.97.1(@swc/core@1.10.4) minimalistic-assert@1.0.1: {} @@ -10815,23 +12525,23 @@ snapshots: minimist@1.2.8: {} - mobx-react-lite@4.0.7(mobx@6.13.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + mobx-react-lite@4.1.0(mobx@6.13.5)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - mobx: 6.13.0 - react: 18.3.1 - use-sync-external-store: 1.2.2(react@18.3.1) + mobx: 6.13.5 + react: 19.0.0 + use-sync-external-store: 1.4.0(react@19.0.0) optionalDependencies: - react-dom: 18.3.1(react@18.3.1) + react-dom: 19.0.0(react@19.0.0) - mobx-react@9.1.1(mobx@6.13.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + mobx-react@9.2.0(mobx@6.13.5)(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: - mobx: 6.13.0 - mobx-react-lite: 4.0.7(mobx@6.13.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: 18.3.1 + mobx: 6.13.5 + mobx-react-lite: 4.1.0(mobx@6.13.5)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + react: 19.0.0 optionalDependencies: - react-dom: 18.3.1(react@18.3.1) + react-dom: 19.0.0(react@19.0.0) - mobx@6.13.0: {} + mobx@6.13.5: {} moo@0.5.2: {} @@ -10839,8 +12549,6 @@ snapshots: ms@2.0.0: {} - ms@2.1.2: {} - ms@2.1.3: {} multicast-dns@7.2.5: @@ -10848,7 +12556,7 @@ snapshots: dns-packet: 5.6.1 thunky: 1.1.0 - nanoid@3.3.7: {} + nanoid@3.3.8: {} nearley@2.20.1: dependencies: @@ -10859,14 +12567,16 @@ snapshots: negotiator@0.6.3: {} + negotiator@0.6.4: {} + neo-async@2.6.2: {} no-case@3.0.4: dependencies: lower-case: 2.0.2 - tslib: 2.6.3 + tslib: 2.8.1 - node-emoji@2.1.3: + node-emoji@2.2.0: dependencies: '@sindresorhus/is': 4.6.0 char-regex: 1.0.2 @@ -10887,7 +12597,7 @@ snapshots: dependencies: es6-promise: 3.3.1 - node-releases@2.0.14: {} + node-releases@2.0.19: {} normalize-path@3.0.0: {} @@ -10905,7 +12615,13 @@ snapshots: dependencies: boolbase: 1.0.0 - nwsapi@2.2.10: {} + null-loader@4.0.1(webpack@5.97.1(@swc/core@1.10.4)): + dependencies: + loader-utils: 2.0.4 + schema-utils: 3.3.0 + webpack: 5.97.1(@swc/core@1.10.4) + + nwsapi@2.2.16: {} oas-kit-common@1.0.8: dependencies: @@ -10940,31 +12656,34 @@ snapshots: object-assign@4.1.1: {} - object-inspect@1.13.2: {} + object-inspect@1.13.3: {} object-is@1.1.6: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 object-keys@1.1.1: {} - object.assign@4.1.5: + object.assign@4.1.7: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.3 define-properties: 1.2.1 - has-symbols: 1.0.3 + es-object-atoms: 1.0.0 + has-symbols: 1.1.0 object-keys: 1.1.1 object.entries@1.1.8: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 es-object-atoms: 1.0.0 - object.values@1.2.0: + object.values@1.2.1: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.3 define-properties: 1.2.1 es-object-atoms: 1.0.0 @@ -10990,14 +12709,21 @@ snapshots: is-docker: 2.2.1 is-wsl: 2.2.0 - openapi-sampler@1.5.1: + openapi-sampler@1.6.1: dependencies: '@types/json-schema': 7.0.15 + fast-xml-parser: 4.5.1 json-pointer: 0.6.2 opener@1.5.2: {} - p-cancelable@4.0.1: {} + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.2.7 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-cancelable@3.0.0: {} p-limit@2.3.0: dependencies: @@ -11036,24 +12762,23 @@ snapshots: package-json@8.1.1: dependencies: - got: 14.4.1 - registry-auth-token: 5.0.2 + got: 12.6.1 + registry-auth-token: 5.0.3 registry-url: 6.0.1 - semver: 7.6.2 + semver: 7.6.3 param-case@3.0.4: dependencies: dot-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.8.1 parent-module@1.0.1: dependencies: callsites: 3.1.0 - parse-entities@4.0.1: + parse-entities@4.0.2: dependencies: - '@types/unist': 2.0.10 - character-entities: 2.0.2 + '@types/unist': 2.0.11 character-entities-legacy: 3.0.0 character-reference-invalid: 2.0.1 decode-named-character-reference: 1.0.2 @@ -11063,19 +12788,23 @@ snapshots: parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 parse-numeric-range@1.3.0: {} - parse5-htmlparser2-tree-adapter@7.0.0: + parse5-htmlparser2-tree-adapter@7.1.0: dependencies: domhandler: 5.0.3 - parse5: 7.1.2 + parse5: 7.2.1 - parse5@7.1.2: + parse5-parser-stream@7.1.2: + dependencies: + parse5: 7.2.1 + + parse5@7.2.1: dependencies: entities: 4.5.0 @@ -11084,7 +12813,7 @@ snapshots: pascal-case@3.1.2: dependencies: no-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.8.1 path-browserify@1.0.1: {} @@ -11102,27 +12831,21 @@ snapshots: path-parse@1.0.7: {} - path-to-regexp@0.1.7: {} + path-to-regexp@0.1.12: {} - path-to-regexp@1.8.0: + path-to-regexp@1.9.0: dependencies: isarray: 0.0.1 - path-to-regexp@2.2.1: {} + path-to-regexp@3.3.0: {} path-type@4.0.0: {} - perfect-scrollbar@1.5.5: {} + perfect-scrollbar@1.5.6: {} performance-now@2.1.0: {} - periscopic@3.1.0: - dependencies: - '@types/estree': 1.0.5 - estree-walker: 3.0.3 - is-reference: 3.0.2 - - picocolors@1.0.1: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} @@ -11138,231 +12861,452 @@ snapshots: polished@4.3.1: dependencies: - '@babel/runtime': 7.24.7 + '@babel/runtime': 7.26.0 possible-typed-array-names@1.0.0: {} - postcss-calc@9.0.1(postcss@8.4.39): + postcss-attribute-case-insensitive@7.0.1(postcss@8.4.49): dependencies: - postcss: 8.4.39 - postcss-selector-parser: 6.1.0 + postcss: 8.4.49 + postcss-selector-parser: 7.0.0 + + postcss-calc@9.0.1(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + postcss-selector-parser: 6.1.2 postcss-value-parser: 4.2.0 - postcss-colormin@6.1.0(postcss@8.4.39): + postcss-clamp@4.1.0(postcss@8.4.49): dependencies: - browserslist: 4.23.1 + postcss: 8.4.49 + postcss-value-parser: 4.2.0 + + postcss-color-functional-notation@7.0.7(postcss@8.4.49): + dependencies: + '@csstools/css-color-parser': 3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.4.49) + '@csstools/utilities': 2.0.0(postcss@8.4.49) + postcss: 8.4.49 + + postcss-color-hex-alpha@10.0.0(postcss@8.4.49): + dependencies: + '@csstools/utilities': 2.0.0(postcss@8.4.49) + postcss: 8.4.49 + postcss-value-parser: 4.2.0 + + postcss-color-rebeccapurple@10.0.0(postcss@8.4.49): + dependencies: + '@csstools/utilities': 2.0.0(postcss@8.4.49) + postcss: 8.4.49 + postcss-value-parser: 4.2.0 + + postcss-colormin@6.1.0(postcss@8.4.49): + dependencies: + browserslist: 4.24.3 caniuse-api: 3.0.0 colord: 2.9.3 - postcss: 8.4.39 + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-convert-values@6.1.0(postcss@8.4.39): + postcss-convert-values@6.1.0(postcss@8.4.49): dependencies: - browserslist: 4.23.1 - postcss: 8.4.39 + browserslist: 4.24.3 + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-discard-comments@6.0.2(postcss@8.4.39): + postcss-custom-media@11.0.5(postcss@8.4.49): dependencies: - postcss: 8.4.39 + '@csstools/cascade-layer-name-parser': 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/media-query-list-parser': 4.0.2(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + postcss: 8.4.49 - postcss-discard-duplicates@6.0.3(postcss@8.4.39): + postcss-custom-properties@14.0.4(postcss@8.4.49): dependencies: - postcss: 8.4.39 + '@csstools/cascade-layer-name-parser': 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/utilities': 2.0.0(postcss@8.4.49) + postcss: 8.4.49 + postcss-value-parser: 4.2.0 - postcss-discard-empty@6.0.3(postcss@8.4.39): + postcss-custom-selectors@8.0.4(postcss@8.4.49): dependencies: - postcss: 8.4.39 + '@csstools/cascade-layer-name-parser': 2.0.4(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + postcss: 8.4.49 + postcss-selector-parser: 7.0.0 - postcss-discard-overridden@6.0.2(postcss@8.4.39): + postcss-dir-pseudo-class@9.0.1(postcss@8.4.49): dependencies: - postcss: 8.4.39 + postcss: 8.4.49 + postcss-selector-parser: 7.0.0 - postcss-discard-unused@6.0.5(postcss@8.4.39): + postcss-discard-comments@6.0.2(postcss@8.4.49): dependencies: - postcss: 8.4.39 - postcss-selector-parser: 6.1.0 + postcss: 8.4.49 - postcss-loader@7.3.4(postcss@8.4.39)(typescript@5.5.3)(webpack@5.92.1): + postcss-discard-duplicates@6.0.3(postcss@8.4.49): dependencies: - cosmiconfig: 8.3.6(typescript@5.5.3) - jiti: 1.21.6 - postcss: 8.4.39 - semver: 7.6.2 - webpack: 5.92.1 + postcss: 8.4.49 + + postcss-discard-empty@6.0.3(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + + postcss-discard-overridden@6.0.2(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + + postcss-discard-unused@6.0.5(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + postcss-selector-parser: 6.1.2 + + postcss-double-position-gradients@6.0.0(postcss@8.4.49): + dependencies: + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.4.49) + '@csstools/utilities': 2.0.0(postcss@8.4.49) + postcss: 8.4.49 + postcss-value-parser: 4.2.0 + + postcss-focus-visible@10.0.1(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + postcss-selector-parser: 7.0.0 + + postcss-focus-within@9.0.1(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + postcss-selector-parser: 7.0.0 + + postcss-font-variant@5.0.0(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + + postcss-gap-properties@6.0.0(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + + postcss-image-set-function@7.0.0(postcss@8.4.49): + dependencies: + '@csstools/utilities': 2.0.0(postcss@8.4.49) + postcss: 8.4.49 + postcss-value-parser: 4.2.0 + + postcss-lab-function@7.0.7(postcss@8.4.49): + dependencies: + '@csstools/css-color-parser': 3.0.7(@csstools/css-parser-algorithms@3.0.4(@csstools/css-tokenizer@3.0.3))(@csstools/css-tokenizer@3.0.3) + '@csstools/css-parser-algorithms': 3.0.4(@csstools/css-tokenizer@3.0.3) + '@csstools/css-tokenizer': 3.0.3 + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.4.49) + '@csstools/utilities': 2.0.0(postcss@8.4.49) + postcss: 8.4.49 + + postcss-loader@7.3.4(postcss@8.4.49)(typescript@5.7.2)(webpack@5.97.1(@swc/core@1.10.4)): + dependencies: + cosmiconfig: 8.3.6(typescript@5.7.2) + jiti: 1.21.7 + postcss: 8.4.49 + semver: 7.6.3 + webpack: 5.97.1(@swc/core@1.10.4) transitivePeerDependencies: - typescript - postcss-merge-idents@6.0.3(postcss@8.4.39): + postcss-logical@8.0.0(postcss@8.4.49): dependencies: - cssnano-utils: 4.0.2(postcss@8.4.39) - postcss: 8.4.39 + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-merge-longhand@6.0.5(postcss@8.4.39): + postcss-merge-idents@6.0.3(postcss@8.4.49): dependencies: - postcss: 8.4.39 + cssnano-utils: 4.0.2(postcss@8.4.49) + postcss: 8.4.49 postcss-value-parser: 4.2.0 - stylehacks: 6.1.1(postcss@8.4.39) - postcss-merge-rules@6.1.1(postcss@8.4.39): + postcss-merge-longhand@6.0.5(postcss@8.4.49): dependencies: - browserslist: 4.23.1 + postcss: 8.4.49 + postcss-value-parser: 4.2.0 + stylehacks: 6.1.1(postcss@8.4.49) + + postcss-merge-rules@6.1.1(postcss@8.4.49): + dependencies: + browserslist: 4.24.3 caniuse-api: 3.0.0 - cssnano-utils: 4.0.2(postcss@8.4.39) - postcss: 8.4.39 - postcss-selector-parser: 6.1.0 + cssnano-utils: 4.0.2(postcss@8.4.49) + postcss: 8.4.49 + postcss-selector-parser: 6.1.2 - postcss-minify-font-values@6.1.0(postcss@8.4.39): + postcss-minify-font-values@6.1.0(postcss@8.4.49): dependencies: - postcss: 8.4.39 + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-minify-gradients@6.0.3(postcss@8.4.39): + postcss-minify-gradients@6.0.3(postcss@8.4.49): dependencies: colord: 2.9.3 - cssnano-utils: 4.0.2(postcss@8.4.39) - postcss: 8.4.39 + cssnano-utils: 4.0.2(postcss@8.4.49) + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-minify-params@6.1.0(postcss@8.4.39): + postcss-minify-params@6.1.0(postcss@8.4.49): dependencies: - browserslist: 4.23.1 - cssnano-utils: 4.0.2(postcss@8.4.39) - postcss: 8.4.39 + browserslist: 4.24.3 + cssnano-utils: 4.0.2(postcss@8.4.49) + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-minify-selectors@6.0.4(postcss@8.4.39): + postcss-minify-selectors@6.0.4(postcss@8.4.49): dependencies: - postcss: 8.4.39 - postcss-selector-parser: 6.1.0 + postcss: 8.4.49 + postcss-selector-parser: 6.1.2 - postcss-modules-extract-imports@3.1.0(postcss@8.4.39): + postcss-modules-extract-imports@3.1.0(postcss@8.4.49): dependencies: - postcss: 8.4.39 + postcss: 8.4.49 - postcss-modules-local-by-default@4.0.5(postcss@8.4.39): + postcss-modules-local-by-default@4.2.0(postcss@8.4.49): dependencies: - icss-utils: 5.1.0(postcss@8.4.39) - postcss: 8.4.39 - postcss-selector-parser: 6.1.0 + icss-utils: 5.1.0(postcss@8.4.49) + postcss: 8.4.49 + postcss-selector-parser: 7.0.0 postcss-value-parser: 4.2.0 - postcss-modules-scope@3.2.0(postcss@8.4.39): + postcss-modules-scope@3.2.1(postcss@8.4.49): dependencies: - postcss: 8.4.39 - postcss-selector-parser: 6.1.0 + postcss: 8.4.49 + postcss-selector-parser: 7.0.0 - postcss-modules-values@4.0.0(postcss@8.4.39): + postcss-modules-values@4.0.0(postcss@8.4.49): dependencies: - icss-utils: 5.1.0(postcss@8.4.39) - postcss: 8.4.39 + icss-utils: 5.1.0(postcss@8.4.49) + postcss: 8.4.49 - postcss-normalize-charset@6.0.2(postcss@8.4.39): + postcss-nesting@13.0.1(postcss@8.4.49): dependencies: - postcss: 8.4.39 + '@csstools/selector-resolve-nested': 3.0.0(postcss-selector-parser@7.0.0) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.0.0) + postcss: 8.4.49 + postcss-selector-parser: 7.0.0 - postcss-normalize-display-values@6.0.2(postcss@8.4.39): + postcss-normalize-charset@6.0.2(postcss@8.4.49): dependencies: - postcss: 8.4.39 + postcss: 8.4.49 + + postcss-normalize-display-values@6.0.2(postcss@8.4.49): + dependencies: + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-normalize-positions@6.0.2(postcss@8.4.39): + postcss-normalize-positions@6.0.2(postcss@8.4.49): dependencies: - postcss: 8.4.39 + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-normalize-repeat-style@6.0.2(postcss@8.4.39): + postcss-normalize-repeat-style@6.0.2(postcss@8.4.49): dependencies: - postcss: 8.4.39 + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-normalize-string@6.0.2(postcss@8.4.39): + postcss-normalize-string@6.0.2(postcss@8.4.49): dependencies: - postcss: 8.4.39 + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-normalize-timing-functions@6.0.2(postcss@8.4.39): + postcss-normalize-timing-functions@6.0.2(postcss@8.4.49): dependencies: - postcss: 8.4.39 + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-normalize-unicode@6.1.0(postcss@8.4.39): + postcss-normalize-unicode@6.1.0(postcss@8.4.49): dependencies: - browserslist: 4.23.1 - postcss: 8.4.39 + browserslist: 4.24.3 + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-normalize-url@6.0.2(postcss@8.4.39): + postcss-normalize-url@6.0.2(postcss@8.4.49): dependencies: - postcss: 8.4.39 + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-normalize-whitespace@6.0.2(postcss@8.4.39): + postcss-normalize-whitespace@6.0.2(postcss@8.4.49): dependencies: - postcss: 8.4.39 + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-ordered-values@6.0.2(postcss@8.4.39): + postcss-opacity-percentage@3.0.0(postcss@8.4.49): dependencies: - cssnano-utils: 4.0.2(postcss@8.4.39) - postcss: 8.4.39 + postcss: 8.4.49 + + postcss-ordered-values@6.0.2(postcss@8.4.49): + dependencies: + cssnano-utils: 4.0.2(postcss@8.4.49) + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-reduce-idents@6.0.3(postcss@8.4.39): + postcss-overflow-shorthand@6.0.0(postcss@8.4.49): dependencies: - postcss: 8.4.39 + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-reduce-initial@6.1.0(postcss@8.4.39): + postcss-page-break@3.0.4(postcss@8.4.49): dependencies: - browserslist: 4.23.1 + postcss: 8.4.49 + + postcss-place@10.0.0(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + postcss-value-parser: 4.2.0 + + postcss-prefix-selector@1.16.1(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + + postcss-preset-env@10.1.3(postcss@8.4.49): + dependencies: + '@csstools/postcss-cascade-layers': 5.0.1(postcss@8.4.49) + '@csstools/postcss-color-function': 4.0.7(postcss@8.4.49) + '@csstools/postcss-color-mix-function': 3.0.7(postcss@8.4.49) + '@csstools/postcss-content-alt-text': 2.0.4(postcss@8.4.49) + '@csstools/postcss-exponential-functions': 2.0.6(postcss@8.4.49) + '@csstools/postcss-font-format-keywords': 4.0.0(postcss@8.4.49) + '@csstools/postcss-gamut-mapping': 2.0.7(postcss@8.4.49) + '@csstools/postcss-gradients-interpolation-method': 5.0.7(postcss@8.4.49) + '@csstools/postcss-hwb-function': 4.0.7(postcss@8.4.49) + '@csstools/postcss-ic-unit': 4.0.0(postcss@8.4.49) + '@csstools/postcss-initial': 2.0.0(postcss@8.4.49) + '@csstools/postcss-is-pseudo-class': 5.0.1(postcss@8.4.49) + '@csstools/postcss-light-dark-function': 2.0.7(postcss@8.4.49) + '@csstools/postcss-logical-float-and-clear': 3.0.0(postcss@8.4.49) + '@csstools/postcss-logical-overflow': 2.0.0(postcss@8.4.49) + '@csstools/postcss-logical-overscroll-behavior': 2.0.0(postcss@8.4.49) + '@csstools/postcss-logical-resize': 3.0.0(postcss@8.4.49) + '@csstools/postcss-logical-viewport-units': 3.0.3(postcss@8.4.49) + '@csstools/postcss-media-minmax': 2.0.6(postcss@8.4.49) + '@csstools/postcss-media-queries-aspect-ratio-number-values': 3.0.4(postcss@8.4.49) + '@csstools/postcss-nested-calc': 4.0.0(postcss@8.4.49) + '@csstools/postcss-normalize-display-values': 4.0.0(postcss@8.4.49) + '@csstools/postcss-oklab-function': 4.0.7(postcss@8.4.49) + '@csstools/postcss-progressive-custom-properties': 4.0.0(postcss@8.4.49) + '@csstools/postcss-random-function': 1.0.2(postcss@8.4.49) + '@csstools/postcss-relative-color-syntax': 3.0.7(postcss@8.4.49) + '@csstools/postcss-scope-pseudo-class': 4.0.1(postcss@8.4.49) + '@csstools/postcss-sign-functions': 1.1.1(postcss@8.4.49) + '@csstools/postcss-stepped-value-functions': 4.0.6(postcss@8.4.49) + '@csstools/postcss-text-decoration-shorthand': 4.0.1(postcss@8.4.49) + '@csstools/postcss-trigonometric-functions': 4.0.6(postcss@8.4.49) + '@csstools/postcss-unset-value': 4.0.0(postcss@8.4.49) + autoprefixer: 10.4.20(postcss@8.4.49) + browserslist: 4.24.3 + css-blank-pseudo: 7.0.1(postcss@8.4.49) + css-has-pseudo: 7.0.2(postcss@8.4.49) + css-prefers-color-scheme: 10.0.0(postcss@8.4.49) + cssdb: 8.2.3 + postcss: 8.4.49 + postcss-attribute-case-insensitive: 7.0.1(postcss@8.4.49) + postcss-clamp: 4.1.0(postcss@8.4.49) + postcss-color-functional-notation: 7.0.7(postcss@8.4.49) + postcss-color-hex-alpha: 10.0.0(postcss@8.4.49) + postcss-color-rebeccapurple: 10.0.0(postcss@8.4.49) + postcss-custom-media: 11.0.5(postcss@8.4.49) + postcss-custom-properties: 14.0.4(postcss@8.4.49) + postcss-custom-selectors: 8.0.4(postcss@8.4.49) + postcss-dir-pseudo-class: 9.0.1(postcss@8.4.49) + postcss-double-position-gradients: 6.0.0(postcss@8.4.49) + postcss-focus-visible: 10.0.1(postcss@8.4.49) + postcss-focus-within: 9.0.1(postcss@8.4.49) + postcss-font-variant: 5.0.0(postcss@8.4.49) + postcss-gap-properties: 6.0.0(postcss@8.4.49) + postcss-image-set-function: 7.0.0(postcss@8.4.49) + postcss-lab-function: 7.0.7(postcss@8.4.49) + postcss-logical: 8.0.0(postcss@8.4.49) + postcss-nesting: 13.0.1(postcss@8.4.49) + postcss-opacity-percentage: 3.0.0(postcss@8.4.49) + postcss-overflow-shorthand: 6.0.0(postcss@8.4.49) + postcss-page-break: 3.0.4(postcss@8.4.49) + postcss-place: 10.0.0(postcss@8.4.49) + postcss-pseudo-class-any-link: 10.0.1(postcss@8.4.49) + postcss-replace-overflow-wrap: 4.0.0(postcss@8.4.49) + postcss-selector-not: 8.0.1(postcss@8.4.49) + + postcss-pseudo-class-any-link@10.0.1(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + postcss-selector-parser: 7.0.0 + + postcss-reduce-idents@6.0.3(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + postcss-value-parser: 4.2.0 + + postcss-reduce-initial@6.1.0(postcss@8.4.49): + dependencies: + browserslist: 4.24.3 caniuse-api: 3.0.0 - postcss: 8.4.39 + postcss: 8.4.49 - postcss-reduce-transforms@6.0.2(postcss@8.4.39): + postcss-reduce-transforms@6.0.2(postcss@8.4.49): dependencies: - postcss: 8.4.39 + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-selector-parser@6.1.0: + postcss-replace-overflow-wrap@4.0.0(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + + postcss-selector-not@8.0.1(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + postcss-selector-parser: 7.0.0 + + postcss-selector-parser@6.1.2: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-sort-media-queries@5.2.0(postcss@8.4.39): + postcss-selector-parser@7.0.0: dependencies: - postcss: 8.4.39 + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-sort-media-queries@5.2.0(postcss@8.4.49): + dependencies: + postcss: 8.4.49 sort-css-media-queries: 2.2.0 - postcss-svgo@6.0.3(postcss@8.4.39): + postcss-svgo@6.0.3(postcss@8.4.49): dependencies: - postcss: 8.4.39 + postcss: 8.4.49 postcss-value-parser: 4.2.0 svgo: 3.3.2 - postcss-unique-selectors@6.0.4(postcss@8.4.39): + postcss-unique-selectors@6.0.4(postcss@8.4.49): dependencies: - postcss: 8.4.39 - postcss-selector-parser: 6.1.0 + postcss: 8.4.49 + postcss-selector-parser: 6.1.2 postcss-value-parser@4.2.0: {} - postcss-zindex@6.0.2(postcss@8.4.39): + postcss-zindex@6.0.2(postcss@8.4.49): dependencies: - postcss: 8.4.39 + postcss: 8.4.49 postcss@8.4.38: dependencies: - nanoid: 3.3.7 - picocolors: 1.0.1 - source-map-js: 1.2.0 + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 - postcss@8.4.39: + postcss@8.4.49: dependencies: - nanoid: 3.3.7 - picocolors: 1.0.1 - source-map-js: 1.2.0 + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 pretty-error@4.0.0: dependencies: @@ -11371,12 +13315,18 @@ snapshots: pretty-time@1.1.0: {} - prism-react-renderer@2.3.1(react@18.3.1): + prism-react-renderer@2.4.1(react@18.3.1): dependencies: - '@types/prismjs': 1.26.4 + '@types/prismjs': 1.26.5 clsx: 2.1.1 react: 18.3.1 + prism-react-renderer@2.4.1(react@19.0.0): + dependencies: + '@types/prismjs': 1.26.5 + clsx: 2.1.1 + react: 19.0.0 + prismjs@1.29.0: {} process-nextick-args@2.0.1: {} @@ -11403,21 +13353,15 @@ snapshots: proxy-from-env@1.1.0: {} - psl@1.9.0: {} - - punycode@1.4.1: {} - punycode@2.3.1: {} pupa@3.1.0: dependencies: escape-goat: 4.0.0 - qs@6.11.0: + qs@6.13.0: dependencies: - side-channel: 1.0.6 - - querystringify@2.2.0: {} + side-channel: 1.1.0 queue-microtask@1.2.3: {} @@ -11460,18 +13404,18 @@ snapshots: minimist: 1.2.8 strip-json-comments: 2.0.1 - react-dev-utils@12.0.1(typescript@5.5.3)(webpack@5.92.1): + react-dev-utils@12.0.1(typescript@5.7.2)(webpack@5.97.1(@swc/core@1.10.4)): dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 address: 1.2.2 - browserslist: 4.23.1 + browserslist: 4.24.3 chalk: 4.1.2 - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 detect-port-alt: 1.1.6 escape-string-regexp: 4.0.0 filesize: 8.0.7 find-up: 5.0.0 - fork-ts-checker-webpack-plugin: 6.5.3(typescript@5.5.3)(webpack@5.92.1) + fork-ts-checker-webpack-plugin: 6.5.3(typescript@5.7.2)(webpack@5.97.1(@swc/core@1.10.4)) global-modules: 2.0.0 globby: 11.1.0 gzip-size: 6.0.0 @@ -11483,12 +13427,12 @@ snapshots: prompts: 2.4.2 react-error-overlay: 6.0.11 recursive-readdir: 2.2.3 - shell-quote: 1.8.1 + shell-quote: 1.8.2 strip-ansi: 6.0.1 text-table: 0.2.0 - webpack: 5.92.1 + webpack: 5.97.1(@swc/core@1.10.4) optionalDependencies: - typescript: 5.5.3 + typescript: 5.7.2 transitivePeerDependencies: - eslint - supports-color @@ -11500,50 +13444,44 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 + react-dom@19.0.0(react@19.0.0): + dependencies: + react: 19.0.0 + scheduler: 0.25.0 + react-error-overlay@6.0.11: {} react-fast-compare@3.2.2: {} - react-helmet-async@1.3.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.24.7 - invariant: 2.2.4 - prop-types: 15.8.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-fast-compare: 3.2.2 - shallowequal: 1.1.0 - - react-helmet-async@2.0.5(react@18.3.1): - dependencies: - invariant: 2.2.4 - react: 18.3.1 - react-fast-compare: 3.2.2 - shallowequal: 1.1.0 - react-is@16.13.1: {} react-is@18.3.1: {} - react-json-view-lite@1.4.0(react@18.3.1): + react-json-view-lite@1.5.0(react@19.0.0): dependencies: - react: 18.3.1 + react: 19.0.0 - react-loadable-ssr-addon-v5-slorber@1.0.1(@docusaurus/react-loadable@6.0.0(react@18.3.1))(webpack@5.92.1): + react-loadable-ssr-addon-v5-slorber@1.0.1(@docusaurus/react-loadable@6.0.0(react@19.0.0))(webpack@5.97.1(@swc/core@1.10.4)): dependencies: - '@babel/runtime': 7.24.7 - react-loadable: '@docusaurus/react-loadable@6.0.0(react@18.3.1)' - webpack: 5.92.1 + '@babel/runtime': 7.26.0 + react-loadable: '@docusaurus/react-loadable@6.0.0(react@19.0.0)' + webpack: 5.97.1(@swc/core@1.10.4) - react-router-config@5.1.1(react-router@5.3.4(react@18.3.1))(react@18.3.1): + react-router-config@5.1.1(react-router@5.3.4(react@19.0.0))(react@18.3.1): dependencies: - '@babel/runtime': 7.24.7 + '@babel/runtime': 7.26.0 react: 18.3.1 - react-router: 5.3.4(react@18.3.1) + react-router: 5.3.4(react@19.0.0) + + react-router-config@5.1.1(react-router@5.3.4(react@19.0.0))(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + react: 19.0.0 + react-router: 5.3.4(react@19.0.0) react-router-dom@5.3.4(react@18.3.1): dependencies: - '@babel/runtime': 7.24.7 + '@babel/runtime': 7.26.0 history: 4.10.1 loose-envify: 1.4.0 prop-types: 15.8.1 @@ -11552,35 +13490,61 @@ snapshots: tiny-invariant: 1.3.3 tiny-warning: 1.0.3 + react-router-dom@5.3.4(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + history: 4.10.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 19.0.0 + react-router: 5.3.4(react@19.0.0) + tiny-invariant: 1.3.3 + tiny-warning: 1.0.3 + react-router@5.3.4(react@18.3.1): dependencies: - '@babel/runtime': 7.24.7 + '@babel/runtime': 7.26.0 history: 4.10.1 hoist-non-react-statics: 3.3.2 loose-envify: 1.4.0 - path-to-regexp: 1.8.0 + path-to-regexp: 1.9.0 prop-types: 15.8.1 react: 18.3.1 react-is: 16.13.1 tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - react-shallow-renderer@16.15.0(react@18.3.1): + react-router@5.3.4(react@19.0.0): + dependencies: + '@babel/runtime': 7.26.0 + history: 4.10.1 + hoist-non-react-statics: 3.3.2 + loose-envify: 1.4.0 + path-to-regexp: 1.9.0 + prop-types: 15.8.1 + react: 19.0.0 + react-is: 16.13.1 + tiny-invariant: 1.3.3 + tiny-warning: 1.0.3 + + react-shallow-renderer@16.15.0(react@19.0.0): dependencies: object-assign: 4.1.1 - react: 18.3.1 + react: 19.0.0 react-is: 18.3.1 - react-tabs@6.0.2(react@18.3.1): + react-tabs@6.1.0(react@19.0.0): dependencies: clsx: 2.1.1 prop-types: 15.8.1 - react: 18.3.1 + react: 19.0.0 react@18.3.1: dependencies: loose-envify: 1.4.0 + react@19.0.0: {} + readable-stream@2.3.8: dependencies: core-util-is: 1.0.3 @@ -11605,39 +13569,69 @@ snapshots: rechoir@0.6.2: dependencies: - resolve: 1.22.8 + resolve: 1.22.10 + + recma-build-jsx@1.0.0: + dependencies: + '@types/estree': 1.0.6 + estree-util-build-jsx: 3.0.1 + vfile: 6.0.3 + + recma-jsx@1.0.0(acorn@8.14.0): + dependencies: + acorn-jsx: 5.3.2(acorn@8.14.0) + estree-util-to-js: 2.0.0 + recma-parse: 1.0.0 + recma-stringify: 1.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - acorn + + recma-parse@1.0.0: + dependencies: + '@types/estree': 1.0.6 + esast-util-from-js: 2.0.1 + unified: 11.0.5 + vfile: 6.0.3 + + recma-stringify@1.0.0: + dependencies: + '@types/estree': 1.0.6 + estree-util-to-js: 2.0.0 + unified: 11.0.5 + vfile: 6.0.3 recursive-readdir@2.2.3: dependencies: minimatch: 3.1.2 - redoc@2.1.5(core-js@3.37.1)(enzyme@3.11.0)(mobx@6.13.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@6.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): + redoc@2.1.5(core-js@3.39.0)(enzyme@3.11.0)(mobx@6.13.5)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(styled-components@6.1.14(react-dom@19.0.0(react@19.0.0))(react@19.0.0)): dependencies: - '@cfaester/enzyme-adapter-react-18': 0.8.0(enzyme@3.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@cfaester/enzyme-adapter-react-18': 0.8.0(enzyme@3.11.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@redocly/openapi-core': 1.16.0 classnames: 2.5.1 - core-js: 3.37.1 + core-js: 3.39.0 decko: 1.2.0 - dompurify: 3.1.6 + dompurify: 3.2.3 eventemitter3: 5.0.1 json-pointer: 0.6.2 lunr: 2.3.9 mark.js: 8.11.1 marked: 4.3.0 - mobx: 6.13.0 - mobx-react: 9.1.1(mobx@6.13.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - openapi-sampler: 1.5.1 + mobx: 6.13.5 + mobx-react: 9.2.0(mobx@6.13.5)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + openapi-sampler: 1.6.1 path-browserify: 1.0.1 - perfect-scrollbar: 1.5.5 + perfect-scrollbar: 1.5.6 polished: 4.3.1 prismjs: 1.29.0 prop-types: 15.8.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-tabs: 6.0.2(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) + react-tabs: 6.1.0(react@19.0.0) slugify: 1.4.7 stickyfill: 1.1.1 - styled-components: 6.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + styled-components: 6.1.14(react-dom@19.0.0(react@19.0.0))(react@19.0.0) swagger2openapi: 7.0.8 url-template: 2.0.8 transitivePeerDependencies: @@ -11646,12 +13640,12 @@ snapshots: - react-native - supports-color - redocusaurus@2.1.1(@docusaurus/theme-common@3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3))(@docusaurus/utils@3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3))(core-js@3.37.1)(enzyme@3.11.0)(mobx@6.13.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@6.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(webpack@5.92.1): + redocusaurus@2.2.0(@docusaurus/theme-common@3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(@docusaurus/utils@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(core-js@3.39.0)(enzyme@3.11.0)(mobx@6.13.5)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(styled-components@6.1.14(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(webpack@5.97.1(@swc/core@1.10.4)): dependencies: - '@docusaurus/theme-common': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3) - '@docusaurus/utils': 3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3) - docusaurus-plugin-redoc: 2.1.1(@docusaurus/utils@3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.5.3))(core-js@3.37.1)(enzyme@3.11.0)(mobx@6.13.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@6.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) - docusaurus-theme-redoc: 2.1.1(@docusaurus/theme-common@3.4.0(@docusaurus/types@3.4.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.5.3))(core-js@3.37.1)(enzyme@3.11.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(webpack@5.92.1) + '@docusaurus/theme-common': 3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + '@docusaurus/utils': 3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) + docusaurus-plugin-redoc: 2.1.1(@docusaurus/utils@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(core-js@3.39.0)(enzyme@3.11.0)(mobx@6.13.5)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(styled-components@6.1.14(react-dom@19.0.0(react@19.0.0))(react@19.0.0)) + docusaurus-theme-redoc: 2.2.0(@docusaurus/theme-common@3.7.0(@docusaurus/plugin-content-docs@3.7.0(@docusaurus/faster@3.7.0(@docusaurus/types@3.7.0(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)))(@mdx-js/react@3.1.0(@types/react@19.0.2)(react@19.0.0))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(typescript@5.7.2))(@swc/core@1.10.4)(acorn@8.14.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(core-js@3.39.0)(enzyme@3.11.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(webpack@5.97.1(@swc/core@1.10.4)) transitivePeerDependencies: - core-js - encoding @@ -11664,9 +13658,20 @@ snapshots: - supports-color - webpack + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.23.9 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + get-intrinsic: 1.2.7 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + reftools@1.1.9: {} - regenerate-unicode-properties@10.1.1: + regenerate-unicode-properties@10.2.0: dependencies: regenerate: 1.4.2 @@ -11676,41 +13681,53 @@ snapshots: regenerator-transform@0.15.2: dependencies: - '@babel/runtime': 7.24.7 + '@babel/runtime': 7.26.0 - regexp.prototype.flags@1.5.2: + regexp.prototype.flags@1.5.4: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 set-function-name: 2.0.2 - regexpu-core@5.3.2: + regexpu-core@6.2.0: dependencies: - '@babel/regjsgen': 0.8.0 regenerate: 1.4.2 - regenerate-unicode-properties: 10.1.1 - regjsparser: 0.9.1 + regenerate-unicode-properties: 10.2.0 + regjsgen: 0.8.0 + regjsparser: 0.12.0 unicode-match-property-ecmascript: 2.0.0 - unicode-match-property-value-ecmascript: 2.1.0 + unicode-match-property-value-ecmascript: 2.2.0 - registry-auth-token@5.0.2: + registry-auth-token@5.0.3: dependencies: - '@pnpm/npm-conf': 2.2.2 + '@pnpm/npm-conf': 2.3.1 registry-url@6.0.1: dependencies: rc: 1.2.8 - regjsparser@0.9.1: + regjsgen@0.8.0: {} + + regjsparser@0.12.0: dependencies: - jsesc: 0.5.0 + jsesc: 3.0.2 rehype-raw@7.0.0: dependencies: '@types/hast': 3.0.4 - hast-util-raw: 9.0.4 - vfile: 6.0.1 + hast-util-raw: 9.1.0 + vfile: 6.0.3 + + rehype-recma@1.0.0: + dependencies: + '@types/estree': 1.0.6 + '@types/hast': 3.0.4 + hast-util-to-estree: 3.1.1 + transitivePeerDependencies: + - supports-color relateurl@0.2.7: {} @@ -11718,7 +13735,7 @@ snapshots: dependencies: '@types/mdast': 4.0.4 mdast-util-directive: 3.0.0 - micromark-extension-directive: 3.0.0 + micromark-extension-directive: 3.0.2 unified: 11.0.5 transitivePeerDependencies: - supports-color @@ -11726,9 +13743,9 @@ snapshots: remark-emoji@4.0.1: dependencies: '@types/mdast': 4.0.4 - emoticon: 4.0.1 - mdast-util-find-and-replace: 3.0.1 - node-emoji: 2.1.3 + emoticon: 4.1.0 + mdast-util-find-and-replace: 3.0.2 + node-emoji: 2.2.0 unified: 11.0.5 remark-frontmatter@5.0.0: @@ -11751,7 +13768,7 @@ snapshots: transitivePeerDependencies: - supports-color - remark-mdx@3.0.1: + remark-mdx@3.1.0: dependencies: mdast-util-mdx: 3.0.0 micromark-extension-mdxjs: 3.0.0 @@ -11761,24 +13778,24 @@ snapshots: remark-parse@11.0.0: dependencies: '@types/mdast': 4.0.4 - mdast-util-from-markdown: 2.0.1 - micromark-util-types: 2.0.0 + mdast-util-from-markdown: 2.0.2 + micromark-util-types: 2.0.1 unified: 11.0.5 transitivePeerDependencies: - supports-color - remark-rehype@11.1.0: + remark-rehype@11.1.1: dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 mdast-util-to-hast: 13.2.0 unified: 11.0.5 - vfile: 6.0.1 + vfile: 6.0.3 remark-stringify@11.0.0: dependencies: '@types/mdast': 4.0.4 - mdast-util-to-markdown: 2.1.0 + mdast-util-to-markdown: 2.1.2 unified: 11.0.5 renderkid@3.0.0: @@ -11789,6 +13806,8 @@ snapshots: lodash: 4.17.21 strip-ansi: 6.0.1 + repeat-string@1.6.1: {} + require-directory@2.1.1: {} require-from-string@2.0.2: {} @@ -11803,11 +13822,9 @@ snapshots: resolve-pathname@3.0.0: {} - resolve-pkg-maps@1.0.0: {} - - resolve@1.22.8: + resolve@1.22.10: dependencies: - is-core-module: 2.14.0 + is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -11825,8 +13842,6 @@ snapshots: dependencies: glob: 7.2.3 - rrweb-cssom@0.6.0: {} - rrweb-cssom@0.7.1: {} rst-selector-parser@2.2.3: @@ -11834,13 +13849,11 @@ snapshots: lodash.flattendeep: 4.4.0 nearley: 2.20.1 - rtl-detect@1.1.2: {} - - rtlcss@4.1.1: + rtlcss@4.3.0: dependencies: - escalade: 3.1.2 - picocolors: 1.0.1 - postcss: 8.4.39 + escalade: 3.2.0 + picocolors: 1.1.1 + postcss: 8.4.49 strip-json-comments: 3.1.1 run-parallel@1.2.0: @@ -11849,24 +13862,30 @@ snapshots: rxjs@7.8.1: dependencies: - tslib: 2.6.3 + tslib: 2.8.1 - safe-array-concat@1.1.2: + safe-array-concat@1.1.3: dependencies: - call-bind: 1.0.7 - get-intrinsic: 1.2.4 - has-symbols: 1.0.3 + call-bind: 1.0.8 + call-bound: 1.0.3 + get-intrinsic: 1.2.7 + has-symbols: 1.1.0 isarray: 2.0.5 safe-buffer@5.1.2: {} safe-buffer@5.2.1: {} - safe-regex-test@1.0.3: + safe-push-apply@1.0.0: dependencies: - call-bind: 1.0.7 es-errors: 1.3.0 - is-regex: 1.1.4 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + is-regex: 1.2.1 safer-buffer@2.1.2: {} @@ -11880,6 +13899,8 @@ snapshots: dependencies: loose-envify: 1.4.0 + scheduler@0.25.0: {} + schema-utils@2.7.0: dependencies: '@types/json-schema': 7.0.15 @@ -11892,14 +13913,14 @@ snapshots: ajv: 6.12.6 ajv-keywords: 3.5.2(ajv@6.12.6) - schema-utils@4.2.0: + schema-utils@4.3.0: dependencies: '@types/json-schema': 7.0.15 - ajv: 8.16.0 - ajv-formats: 2.1.1(ajv@8.16.0) - ajv-keywords: 5.1.0(ajv@8.16.0) + ajv: 8.17.1 + ajv-formats: 2.1.1(ajv@8.17.1) + ajv-keywords: 5.1.0(ajv@8.17.1) - search-insights@2.14.0: {} + search-insights@2.17.3: {} section-matter@1.0.0: dependencies: @@ -11915,13 +13936,13 @@ snapshots: semver-diff@4.0.0: dependencies: - semver: 7.6.2 + semver: 7.6.3 semver@6.3.1: {} - semver@7.6.2: {} + semver@7.6.3: {} - send@0.18.0: + send@0.19.0: dependencies: debug: 2.6.9 depd: 2.0.0 @@ -11943,15 +13964,14 @@ snapshots: dependencies: randombytes: 2.1.0 - serve-handler@6.1.5: + serve-handler@6.1.6: dependencies: bytes: 3.0.0 content-disposition: 0.5.2 - fast-url-parser: 1.1.3 mime-types: 2.1.18 minimatch: 3.1.2 path-is-inside: 1.0.2 - path-to-regexp: 2.2.1 + path-to-regexp: 3.3.0 range-parser: 1.2.0 serve-index@1.9.1: @@ -11966,12 +13986,12 @@ snapshots: transitivePeerDependencies: - supports-color - serve-static@1.15.0: + serve-static@1.16.2: dependencies: - encodeurl: 1.0.2 + encodeurl: 2.0.0 escape-html: 1.0.3 parseurl: 1.3.3 - send: 0.18.0 + send: 0.19.0 transitivePeerDependencies: - supports-color @@ -11980,8 +14000,8 @@ snapshots: define-data-property: 1.1.4 es-errors: 1.3.0 function-bind: 1.1.2 - get-intrinsic: 1.2.4 - gopd: 1.0.1 + get-intrinsic: 1.2.7 + gopd: 1.2.0 has-property-descriptors: 1.0.2 set-function-name@2.0.2: @@ -11991,6 +14011,12 @@ snapshots: functions-have-names: 1.2.3 has-property-descriptors: 1.0.2 + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.0.0 + setprototypeof@1.1.0: {} setprototypeof@1.2.0: {} @@ -12007,7 +14033,7 @@ snapshots: shebang-regex@3.0.0: {} - shell-quote@1.8.1: {} + shell-quote@1.8.2: {} shelljs@0.8.5: dependencies: @@ -12041,18 +14067,39 @@ snapshots: should-type-adaptors: 1.1.0 should-util: 1.0.1 - side-channel@1.0.6: + side-channel-list@1.0.0: dependencies: - call-bind: 1.0.7 es-errors: 1.3.0 - get-intrinsic: 1.2.4 - object-inspect: 1.13.2 + object-inspect: 1.13.3 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.7 + object-inspect: 1.13.3 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.3 + es-errors: 1.3.0 + get-intrinsic: 1.2.7 + object-inspect: 1.13.3 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.3 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 signal-exit@3.0.7: {} sirv@2.0.4: dependencies: - '@polka/url': 1.0.0-next.25 + '@polka/url': 1.0.0-next.28 mrmime: 2.0.0 totalist: 3.0.1 @@ -12078,7 +14125,7 @@ snapshots: snake-case@3.0.4: dependencies: dot-case: 3.0.4 - tslib: 2.6.3 + tslib: 2.8.1 sockjs@0.3.24: dependencies: @@ -12088,9 +14135,7 @@ snapshots: sort-css-media-queries@2.2.0: {} - source-list-map@2.0.1: {} - - source-map-js@1.2.0: {} + source-map-js@1.2.1: {} source-map-support@0.5.21: dependencies: @@ -12103,11 +14148,9 @@ snapshots: space-separated-tokens@2.0.2: {} - spawn-command@0.0.2: {} - spdy-transport@3.0.0: dependencies: - debug: 4.3.5 + debug: 4.4.0 detect-node: 2.1.0 hpack.js: 2.1.6 obuf: 1.1.2 @@ -12118,7 +14161,7 @@ snapshots: spdy@4.0.2: dependencies: - debug: 4.3.5 + debug: 4.4.0 handle-thing: 2.0.1 http-deceiver: 1.2.7 select-hose: 2.0.0 @@ -12134,7 +14177,7 @@ snapshots: statuses@2.0.1: {} - std-env@3.7.0: {} + std-env@3.8.0: {} stickyfill@1.1.1: {} @@ -12150,22 +14193,26 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 - string.prototype.trim@1.2.9: + string.prototype.trim@1.2.10: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.3 + define-data-property: 1.1.4 define-properties: 1.2.1 - es-abstract: 1.23.3 + es-abstract: 1.23.9 es-object-atoms: 1.0.0 + has-property-descriptors: 1.0.2 - string.prototype.trimend@1.0.8: + string.prototype.trimend@1.0.9: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.3 define-properties: 1.2.1 es-object-atoms: 1.0.0 string.prototype.trimstart@1.0.8: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 define-properties: 1.2.1 es-object-atoms: 1.0.0 @@ -12194,7 +14241,7 @@ snapshots: strip-ansi@7.1.0: dependencies: - ansi-regex: 6.0.1 + ansi-regex: 6.1.0 strip-bom-string@1.0.0: {} @@ -12204,15 +14251,13 @@ snapshots: strip-json-comments@3.1.1: {} - style-to-object@0.4.4: - dependencies: - inline-style-parser: 0.1.1 + strnum@1.0.5: {} - style-to-object@1.0.6: + style-to-object@1.0.8: dependencies: - inline-style-parser: 0.2.3 + inline-style-parser: 0.2.4 - styled-components@6.1.11(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + styled-components@6.1.14(react-dom@19.0.0(react@19.0.0))(react@19.0.0): dependencies: '@emotion/is-prop-valid': 1.2.2 '@emotion/unitless': 0.8.1 @@ -12220,24 +14265,20 @@ snapshots: css-to-react-native: 3.2.0 csstype: 3.1.3 postcss: 8.4.38 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) + react: 19.0.0 + react-dom: 19.0.0(react@19.0.0) shallowequal: 1.1.0 stylis: 4.3.2 tslib: 2.6.2 - stylehacks@6.1.1(postcss@8.4.39): + stylehacks@6.1.1(postcss@8.4.49): dependencies: - browserslist: 4.23.1 - postcss: 8.4.39 - postcss-selector-parser: 6.1.0 + browserslist: 4.24.3 + postcss: 8.4.49 + postcss-selector-parser: 6.1.2 stylis@4.3.2: {} - supports-color@5.5.0: - dependencies: - has-flag: 3.0.0 - supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -12258,7 +14299,7 @@ snapshots: css-tree: 2.3.1 css-what: 6.1.0 csso: 5.0.5 - picocolors: 1.0.1 + picocolors: 1.1.1 swagger2openapi@7.0.8: dependencies: @@ -12276,25 +14317,33 @@ snapshots: transitivePeerDependencies: - encoding + swc-loader@0.2.6(@swc/core@1.10.4)(webpack@5.97.1(@swc/core@1.10.4)): + dependencies: + '@swc/core': 1.10.4 + '@swc/counter': 0.1.3 + webpack: 5.97.1(@swc/core@1.10.4) + symbol-tree@3.2.4: {} tapable@1.1.3: {} tapable@2.2.1: {} - terser-webpack-plugin@5.3.10(webpack@5.92.1): + terser-webpack-plugin@5.3.11(@swc/core@1.10.4)(webpack@5.97.1(@swc/core@1.10.4)): dependencies: '@jridgewell/trace-mapping': 0.3.25 jest-worker: 27.5.1 - schema-utils: 3.3.0 + schema-utils: 4.3.0 serialize-javascript: 6.0.2 - terser: 5.31.1 - webpack: 5.92.1 + terser: 5.37.0 + webpack: 5.97.1(@swc/core@1.10.4) + optionalDependencies: + '@swc/core': 1.10.4 - terser@5.31.1: + terser@5.37.0: dependencies: '@jridgewell/source-map': 0.3.6 - acorn: 8.12.1 + acorn: 8.14.0 commander: 2.20.3 source-map-support: 0.5.21 @@ -12306,7 +14355,11 @@ snapshots: tiny-warning@1.0.3: {} - to-fast-properties@2.0.0: {} + tldts-core@6.1.70: {} + + tldts@6.1.70: + dependencies: + tldts-core: 6.1.70 to-regex-range@5.0.1: dependencies: @@ -12316,12 +14369,9 @@ snapshots: totalist@3.0.1: {} - tough-cookie@4.1.4: + tough-cookie@5.0.0: dependencies: - psl: 1.9.0 - punycode: 2.3.1 - universalify: 0.2.0 - url-parse: 1.5.10 + tldts: 6.1.70 tr46@0.0.3: {} @@ -12337,88 +14387,91 @@ snapshots: tslib@2.6.2: {} - tslib@2.6.3: {} + tslib@2.8.1: {} + + type-fest@0.21.3: {} type-fest@1.4.0: {} type-fest@2.19.0: {} - type-fest@4.21.0: {} - type-is@1.6.18: dependencies: media-typer: 0.3.0 mime-types: 2.1.35 - typed-array-buffer@1.0.2: + typed-array-buffer@1.0.3: dependencies: - call-bind: 1.0.7 + call-bound: 1.0.3 es-errors: 1.3.0 - is-typed-array: 1.1.13 + is-typed-array: 1.1.15 - typed-array-byte-length@1.0.1: + typed-array-byte-length@1.0.3: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 for-each: 0.3.3 - gopd: 1.0.1 - has-proto: 1.0.3 - is-typed-array: 1.1.13 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 - typed-array-byte-offset@1.0.2: + typed-array-byte-offset@1.0.4: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.7 + call-bind: 1.0.8 for-each: 0.3.3 - gopd: 1.0.1 - has-proto: 1.0.3 - is-typed-array: 1.1.13 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 - typed-array-length@1.0.6: + typed-array-length@1.0.7: dependencies: - call-bind: 1.0.7 + call-bind: 1.0.8 for-each: 0.3.3 - gopd: 1.0.1 - has-proto: 1.0.3 - is-typed-array: 1.1.13 + gopd: 1.2.0 + is-typed-array: 1.1.15 possible-typed-array-names: 1.0.0 + reflect.getprototypeof: 1.0.10 typedarray-to-buffer@3.1.5: dependencies: is-typedarray: 1.0.0 - typescript@5.5.3: {} + typescript@5.7.2: {} - unbox-primitive@1.0.2: + unbox-primitive@1.1.0: dependencies: - call-bind: 1.0.7 - has-bigints: 1.0.2 - has-symbols: 1.0.3 - which-boxed-primitive: 1.0.2 + call-bound: 1.0.3 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 - undici-types@5.26.5: {} + undici-types@6.20.0: {} - unicode-canonical-property-names-ecmascript@2.0.0: {} + undici@6.21.0: {} + + unicode-canonical-property-names-ecmascript@2.0.1: {} unicode-emoji-modifier-base@1.0.0: {} unicode-match-property-ecmascript@2.0.0: dependencies: - unicode-canonical-property-names-ecmascript: 2.0.0 + unicode-canonical-property-names-ecmascript: 2.0.1 unicode-property-aliases-ecmascript: 2.1.0 - unicode-match-property-value-ecmascript@2.1.0: {} + unicode-match-property-value-ecmascript@2.2.0: {} unicode-property-aliases-ecmascript@2.1.0: {} unified@11.0.5: dependencies: - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 bail: 2.0.2 devlop: 1.1.0 extend: 3.0.2 is-plain-obj: 4.1.0 trough: 2.2.0 - vfile: 6.0.1 + vfile: 6.0.3 unique-string@3.0.0: dependencies: @@ -12426,52 +14479,45 @@ snapshots: unist-util-is@6.0.0: dependencies: - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 unist-util-position-from-estree@2.0.0: dependencies: - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 unist-util-position@5.0.0: dependencies: - '@types/unist': 3.0.2 - - unist-util-remove-position@5.0.0: - dependencies: - '@types/unist': 3.0.2 - unist-util-visit: 5.0.0 + '@types/unist': 3.0.3 unist-util-stringify-position@4.0.0: dependencies: - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 unist-util-visit-parents@6.0.1: dependencies: - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 unist-util-is: 6.0.0 unist-util-visit@5.0.0: dependencies: - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 unist-util-is: 6.0.0 unist-util-visit-parents: 6.0.1 - universalify@0.2.0: {} - universalify@2.0.1: {} unpipe@1.0.0: {} - update-browserslist-db@1.1.0(browserslist@4.23.1): + update-browserslist-db@1.1.1(browserslist@4.24.3): dependencies: - browserslist: 4.23.1 - escalade: 3.1.2 - picocolors: 1.0.1 + browserslist: 4.24.3 + escalade: 3.2.0 + picocolors: 1.1.1 update-notifier@6.0.2: dependencies: boxen: 7.1.1 - chalk: 5.3.0 + chalk: 5.4.1 configstore: 6.0.0 has-yarn: 3.0.0 import-lazy: 4.0.0 @@ -12481,33 +14527,30 @@ snapshots: is-yarn-global: 0.4.1 latest-version: 7.0.0 pupa: 3.1.0 - semver: 7.6.2 + semver: 7.6.3 semver-diff: 4.0.0 xdg-basedir: 5.1.0 + uri-js-replace@1.0.1: {} + uri-js@4.4.1: dependencies: punycode: 2.3.1 - url-loader@4.1.1(file-loader@6.2.0(webpack@5.92.1))(webpack@5.92.1): + url-loader@4.1.1(file-loader@6.2.0(webpack@5.97.1(@swc/core@1.10.4)))(webpack@5.97.1(@swc/core@1.10.4)): dependencies: loader-utils: 2.0.4 mime-types: 2.1.35 schema-utils: 3.3.0 - webpack: 5.92.1 + webpack: 5.97.1(@swc/core@1.10.4) optionalDependencies: - file-loader: 6.2.0(webpack@5.92.1) - - url-parse@1.5.10: - dependencies: - querystringify: 2.2.0 - requires-port: 1.0.0 + file-loader: 6.2.0(webpack@5.97.1(@swc/core@1.10.4)) url-template@2.0.8: {} - use-sync-external-store@1.2.2(react@18.3.1): + use-sync-external-store@1.4.0(react@19.0.0): dependencies: - react: 18.3.1 + react: 19.0.0 util-deprecate@1.0.2: {} @@ -12523,27 +14566,26 @@ snapshots: vary@1.1.2: {} - vfile-location@5.0.2: + vfile-location@5.0.3: dependencies: - '@types/unist': 3.0.2 - vfile: 6.0.1 + '@types/unist': 3.0.3 + vfile: 6.0.3 vfile-message@4.0.2: dependencies: - '@types/unist': 3.0.2 + '@types/unist': 3.0.3 unist-util-stringify-position: 4.0.0 - vfile@6.0.1: + vfile@6.0.3: dependencies: - '@types/unist': 3.0.2 - unist-util-stringify-position: 4.0.0 + '@types/unist': 3.0.3 vfile-message: 4.0.2 w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 - watchpack@2.4.1: + watchpack@2.4.2: dependencies: glob-to-regexp: 0.4.1 graceful-fs: 4.2.11 @@ -12561,31 +14603,31 @@ snapshots: webpack-bundle-analyzer@4.10.2: dependencies: '@discoveryjs/json-ext': 0.5.7 - acorn: 8.12.1 - acorn-walk: 8.3.3 + acorn: 8.14.0 + acorn-walk: 8.3.4 commander: 7.2.0 debounce: 1.2.1 escape-string-regexp: 4.0.0 gzip-size: 6.0.0 html-escaper: 2.0.2 opener: 1.5.2 - picocolors: 1.0.1 + picocolors: 1.1.1 sirv: 2.0.4 ws: 7.5.10 transitivePeerDependencies: - bufferutil - utf-8-validate - webpack-dev-middleware@5.3.4(webpack@5.92.1): + webpack-dev-middleware@5.3.4(webpack@5.97.1(@swc/core@1.10.4)): dependencies: colorette: 2.0.20 memfs: 3.5.3 mime-types: 2.1.35 range-parser: 1.2.1 - schema-utils: 4.2.0 - webpack: 5.92.1 + schema-utils: 4.3.0 + webpack: 5.97.1(@swc/core@1.10.4) - webpack-dev-server@4.15.2(debug@4.3.5)(webpack@5.92.1): + webpack-dev-server@4.15.2(debug@4.4.0)(webpack@5.97.1(@swc/core@1.10.4)): dependencies: '@types/bonjour': 3.5.13 '@types/connect-history-api-fallback': 1.5.4 @@ -12593,32 +14635,32 @@ snapshots: '@types/serve-index': 1.9.4 '@types/serve-static': 1.15.7 '@types/sockjs': 0.3.36 - '@types/ws': 8.5.10 + '@types/ws': 8.5.13 ansi-html-community: 0.0.8 - bonjour-service: 1.2.1 + bonjour-service: 1.3.0 chokidar: 3.6.0 colorette: 2.0.20 - compression: 1.7.4 + compression: 1.7.5 connect-history-api-fallback: 2.0.0 default-gateway: 6.0.3 - express: 4.19.2 + express: 4.21.2 graceful-fs: 4.2.11 html-entities: 2.5.2 - http-proxy-middleware: 2.0.6(@types/express@4.17.21)(debug@4.3.5) + http-proxy-middleware: 2.0.7(@types/express@4.17.21)(debug@4.4.0) ipaddr.js: 2.2.0 - launch-editor: 2.8.0 + launch-editor: 2.9.1 open: 8.4.2 p-retry: 4.6.2 rimraf: 3.0.2 - schema-utils: 4.2.0 + schema-utils: 4.3.0 selfsigned: 2.4.1 serve-index: 1.9.1 sockjs: 0.3.24 spdy: 4.0.2 - webpack-dev-middleware: 5.3.4(webpack@5.92.1) + webpack-dev-middleware: 5.3.4(webpack@5.97.1(@swc/core@1.10.4)) ws: 8.18.0 optionalDependencies: - webpack: 5.92.1 + webpack: 5.97.1(@swc/core@1.10.4) transitivePeerDependencies: - bufferutil - debug @@ -12631,26 +14673,26 @@ snapshots: flat: 5.0.2 wildcard: 2.0.1 - webpack-sources@1.4.3: + webpack-merge@6.0.1: dependencies: - source-list-map: 2.0.1 - source-map: 0.6.1 + clone-deep: 4.0.1 + flat: 5.0.2 + wildcard: 2.0.1 webpack-sources@3.2.3: {} - webpack@5.92.1: + webpack@5.97.1(@swc/core@1.10.4): dependencies: '@types/eslint-scope': 3.7.7 - '@types/estree': 1.0.5 - '@webassemblyjs/ast': 1.12.1 - '@webassemblyjs/wasm-edit': 1.12.1 - '@webassemblyjs/wasm-parser': 1.12.1 - acorn: 8.12.1 - acorn-import-attributes: 1.9.5(acorn@8.12.1) - browserslist: 4.23.1 + '@types/estree': 1.0.6 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.14.0 + browserslist: 4.24.3 chrome-trace-event: 1.0.4 - enhanced-resolve: 5.17.0 - es-module-lexer: 1.5.4 + enhanced-resolve: 5.18.0 + es-module-lexer: 1.6.0 eslint-scope: 5.1.1 events: 3.3.0 glob-to-regexp: 0.4.1 @@ -12661,21 +14703,25 @@ snapshots: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(webpack@5.92.1) - watchpack: 2.4.1 + terser-webpack-plugin: 5.3.11(@swc/core@1.10.4)(webpack@5.97.1(@swc/core@1.10.4)) + watchpack: 2.4.2 webpack-sources: 3.2.3 transitivePeerDependencies: - '@swc/core' - esbuild - uglify-js - webpackbar@5.0.2(webpack@5.92.1): + webpackbar@6.0.1(webpack@5.97.1(@swc/core@1.10.4)): dependencies: + ansi-escapes: 4.3.2 chalk: 4.1.2 - consola: 2.15.3 + consola: 3.3.3 + figures: 3.2.0 + markdown-table: 2.0.0 pretty-time: 1.1.0 - std-env: 3.7.0 - webpack: 5.92.1 + std-env: 3.8.0 + webpack: 5.97.1(@swc/core@1.10.4) + wrap-ansi: 7.0.0 websocket-driver@0.7.4: dependencies: @@ -12691,7 +14737,7 @@ snapshots: whatwg-mimetype@4.0.0: {} - whatwg-url@14.0.0: + whatwg-url@14.1.0: dependencies: tr46: 5.0.0 webidl-conversions: 7.0.0 @@ -12701,20 +14747,44 @@ snapshots: tr46: 0.0.3 webidl-conversions: 3.0.1 - which-boxed-primitive@1.0.2: + which-boxed-primitive@1.1.1: dependencies: - is-bigint: 1.0.4 - is-boolean-object: 1.1.2 - is-number-object: 1.0.7 - is-string: 1.0.7 - is-symbol: 1.0.4 + is-bigint: 1.1.0 + is-boolean-object: 1.2.1 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 - which-typed-array@1.1.15: + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.3 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.0 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.0 + is-regex: 1.2.1 + is-weakref: 1.1.0 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.18 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.18: dependencies: available-typed-arrays: 1.0.7 - call-bind: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.3 for-each: 0.3.3 - gopd: 1.0.1 + gopd: 1.2.0 has-tostringtag: 1.0.2 which@1.3.1: @@ -12774,14 +14844,14 @@ snapshots: yaml@1.10.2: {} - yaml@2.4.5: {} + yaml@2.7.0: {} yargs-parser@21.1.1: {} yargs@17.7.2: dependencies: cliui: 8.0.1 - escalade: 3.1.2 + escalade: 3.2.0 get-caller-file: 2.0.5 require-directory: 2.1.1 string-width: 4.2.3 diff --git a/docs/src/components/HomepageFeatures.js b/docs/src/components/HomepageFeatures.js index 4e2f1fbf7..b79fd9ec3 100644 --- a/docs/src/components/HomepageFeatures.js +++ b/docs/src/components/HomepageFeatures.js @@ -1,5 +1,6 @@ -import React from 'react'; import clsx from 'clsx'; +import React from 'react'; + import styles from './HomepageFeatures.module.css'; const FeatureList = [ diff --git a/docs/src/pages/about.md b/docs/src/pages/about.md new file mode 100644 index 000000000..bec3304a1 --- /dev/null +++ b/docs/src/pages/about.md @@ -0,0 +1,18 @@ +# About + +Woodpecker has been originally forked from Drone 0.8 as the Drone CI license was changed after the 0.8 release from Apache 2.0 to a proprietary license. Woodpecker is based on this latest freely available version. + +## History + +Woodpecker was originally forked by [@laszlocph](https://github.com/laszlocph) in 2019. + +A few important time points: + +- [`2fbaa56`](https://github.com/woodpecker-ci/woodpecker/commit/2fbaa56eee0f4be7a3ca4be03dbd00c1bf5d1274) is the first commit of the fork, made on Apr 3, 2019. +- The first release [v0.8.91](https://github.com/woodpecker-ci/woodpecker/releases/tag/v0.8.91) was published on Apr 6, 2019. +- On Aug 27, 2019, the project was renamed to "Woodpecker" ([`630c383`](https://github.com/woodpecker-ci/woodpecker/commit/630c383181b10c4ec375e500c812c4b76b3c52b8)). +- The first release under the name "Woodpecker" was published on Sep 9, 2019 ([v0.8.104](https://github.com/woodpecker-ci/woodpecker/releases/tag/v0.8.104)). + +## Differences to Drone + +Woodpecker is a community-focused software that still stay free and open source forever, while Drone is managed by [Harness](https://harness.io/) and published under [Polyform Small Business](https://polyformproject.org/licenses/small-business/1.0.0/) license. diff --git a/docs/versioned_docs/version-2.4/92-awesome.md b/docs/src/pages/awesome.md similarity index 71% rename from docs/versioned_docs/version-2.4/92-awesome.md rename to docs/src/pages/awesome.md index b13862209..94dc8cd7a 100644 --- a/docs/versioned_docs/version-2.4/92-awesome.md +++ b/docs/src/pages/awesome.md @@ -1,8 +1,8 @@ # Awesome Woodpecker -A curated list of awesome things related to Woodpecker-CI. +A curated list of assets (tools, projects, blog posts) related to Woodpecker CI. -If you have some missing resources, please feel free to [open a pull-request](https://github.com/woodpecker-ci/woodpecker/edit/main/docs/docs/92-awesome.md) and add them. +If you want to add a new entry, open a [pull-request](https://github.com/woodpecker-ci/woodpecker/edit/main/docs/docs/92-awesome.md). ## Official Resources @@ -14,7 +14,7 @@ If you have some missing resources, please feel free to [open a pull-request](ht ## Projects using Woodpecker -- [Woodpecker-CI](https://github.com/woodpecker-ci/woodpecker/tree/main/.woodpecker) itself +- [Woodpecker CI](https://github.com/woodpecker-ci/woodpecker/tree/main/.woodpecker) itself - [All official plugins](https://github.com/woodpecker-ci?q=plugin&type=all) - [dessalines/thumb-key](https://github.com/dessalines/thumb-key/blob/main/.woodpecker.yml) - Android Jetpack compose linting and building - [Vieter](https://git.rustybever.be/vieter-v/vieter) - Archlinux/Pacman repository server & automated package build system @@ -24,12 +24,12 @@ If you have some missing resources, please feel free to [open a pull-request](ht ## Tools - [Convert Drone CI pipelines to Woodpecker CI](https://codeberg.org/lafriks/woodpecker-pipeline-transform) -- [Ansible NAS](https://github.com/davestephens/ansible-nas/) - a homelab Ansible playbook that can set up Woodpecker-CI and Gitea +- [Ansible NAS](https://github.com/davestephens/ansible-nas/) - a homelab Ansible playbook that can set up Woodpecker CI and Gitea - [picus](https://github.com/windsource/picus) - Picus connects to a Woodpecker CI server and creates an agent in the cloud when there are pending workflows. - [Hetzner cloud](https://www.hetzner.com/cloud) based [Woodpecker compatible autoscaler](https://git.ljoonal.xyz/ljoonal/hetzner-ci-autoscaler) - Creates and destroys VPS instances based on the count of pending & running jobs. -- [woodpecker-lint](https://git.schmidl.dev/schtobia/woodpecker-lint) - A repository for linting a woodpecker config file via pre-commit hook -- [Grafana Dashboard](https://github.com/Janik-Haag/woodpecker-grafana-dashboard) - A dashboard visualizing information exposed by the woodpecker prometheus endpoint. -- [woodpecker-autoscaler](https://github.com/Lerentis/woodpecker-autoscaler) - Yet another woodpecker autoscaler currently targeting [Hetzner cloud](https://www.hetzner.com/cloud) that works in parallel to other autoscaler implementations. +- [woodpecker-lint](https://git.schmidl.dev/schtobia/woodpecker-lint) - A repository for linting a Woodpecker config file via pre-commit hook +- [Grafana Dashboard](https://github.com/Janik-Haag/woodpecker-grafana-dashboard) - A dashboard visualizing information exposed by the Woodpecker prometheus endpoint. +- [woodpecker-autoscaler](https://github.com/Lerentis/woodpecker-autoscaler) - Yet another Woodpecker autoscaler currently targeting [Hetzner cloud](https://www.hetzner.com/cloud) that works in parallel to other autoscaler implementations. ## Configuration Services @@ -50,14 +50,23 @@ If you have some missing resources, please feel free to [open a pull-request](ht - [Locally Cached Nix CI with Woodpecker](https://blog.kotatsu.dev/posts/2023-04-21-woodpecker-nix-caching/) - [How to run Cypress auto-tests on Woodpecker CI and report results to Slack](https://devforth.io/blog/how-to-run-cypress-auto-tests-on-woodpecker-ci-and-report-results-to-slack/) - [Quest For CICD - WoodpeckerCI](https://omaramin.me/posts/woodpecker/) -- [Getting started with Woodpecker CI](https://blog.lutra-it.eu/getting-started-with-woodpecker-ci.html) +- [Getting started with Woodpecker CI](https://systeemkabouter.eu/getting-started-with-woodpecker-ci.html) - [Installing gitea and woodpecker using binary packages](https://neelex.com/2023/03/26/Installing-gitea-using-binary-packages/) +- [Deploying mdbook to codeberg pages using Woodpecker CI](https://www.markpitblado.me/blog/deploying-mdbook-to-codeberg-pages-using-woodpecker-ci/) +- [Deploy a Fly app with Woodpecker CI](https://joeroe.io/2024/01/09/deploy-fly-woodpecker-ci.html) +- [Ansible - using Woodpecker as an alternative to Semaphore](https://pat-s.me/ansible-using-woodpecker-as-an-alternative-to-semaphore/) +- [Simple selfhosted CI/CD with Woodpecker](https://xyquadrat.ch/blog/simple-ci-with-woodpecker/) +- [Notes to self on Woodpecker-CI](https://jpmens.net/2023/09/22/notes-to-self-on-woodpecker-ci/) +- [CI/CD with Woodpecker and Gitea](https://wilw.dev/blog/2023/04/23/woodpecker-ci/) ## Videos - [Replace Ansible Semaphore with Woodpecker CI](https://www.youtube.com/watch?v=d610YPvCB0E) - ["unexpected EOF" error when trying to pair Woodpecker CI served through the Caddy with Gitea](https://www.youtube.com/watch?v=n7Hyvt71Np0) - [CICD Environment in Docker Swarm behind Caddy Server - Part 2 Woodpeckerci](https://www.youtube.com/watch?v=rkbw_k7JvS0) +- [How to Build & Publish Custom Docker Container using Gitea & Woodpecker behind Caddy Server | TUNEIT](https://www.youtube.com/watch?v=9m7DbgL1mNk) +- [Radicle Woodpecker CI Integration](https://www.youtube.com/watch?v=Ks1nbYLn4P8) +- [woodpecker-ci/woodpecker - Gource visualisation](https://www.youtube.com/watch?v=38JuakZ6m5s) ## Plugins diff --git a/docs/src/pages/index.tsx b/docs/src/pages/index.tsx index 9fc47c2e5..f6b91a000 100644 --- a/docs/src/pages/index.tsx +++ b/docs/src/pages/index.tsx @@ -1,10 +1,11 @@ -import React from 'react'; -import clsx from 'clsx'; -import Layout from '@theme/Layout'; import Link from '@docusaurus/Link'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; -import styles from './index.module.css'; +import Layout from '@theme/Layout'; +import clsx from 'clsx'; +import React from 'react'; + import HomepageFeatures from '../components/HomepageFeatures'; +import styles from './index.module.css'; function HomepageHeader() { const { siteConfig } = useDocusaurusContext(); @@ -14,7 +15,7 @@ function HomepageHeader() {

{siteConfig.title}

{siteConfig.tagline}

- + Woodpecker Tutorial - 5min ⏱️
@@ -28,7 +29,7 @@ export default function Home() { return (
diff --git a/docs/src/pages/migrations.md b/docs/src/pages/migrations.md new file mode 100644 index 000000000..a6e3d4ccd --- /dev/null +++ b/docs/src/pages/migrations.md @@ -0,0 +1,344 @@ +# Migrations + +## `next` + +- No changes + +## 3.0.0 + +### User-facing migrations + +#### Security + +- The "gated" option, which restricted which pipelines can start right away without requiring approval, has been replaced by "require-approval" option. Even though this feature ([#3348](https://github.com/woodpecker-ci/woodpecker/pull/3348)) was backported to 2.8, no default is explicitly set. + The new default in 3.0 is to require approval only for forked repositories. + This allows easier management of dependency bots and other trusted entities having write access to the repository. + +#### Environment variables + +- Environment variables must now be defined as maps. List definitions are disallowed. ([#4016](https://github.com/woodpecker-ci/woodpecker/pull/4016)) + + 2.x: + + ```yaml + environment: + - ENV1=value1 + ``` + + 3.x: + + ```yaml + environment: + ENV1: value1 + ``` + +The following built-in environment variables have been removed/replaced: + +- `CI_COMMIT_URL` has been deprecated in favor of `CI_PIPELINE_FORGE_URL` +- `CI_STEP_FINISHED` as it was empty during execution +- `CI_PIPELINE_FINISHED` as it was empty during execution +- `CI_PIPELINE_STATUS` due to always being set to `success` +- `CI_STEP_STATUS` due to always being set to `success` +- `WOODPECKER_WEBHOOK_HOST` in favor of `WOODPECKER_EXPERT_WEBHOOK_HOST` + +Environment variables which are empty after workflow parsing are not being injected into the build but filtered out beforehand ([#4193](https://github.com/woodpecker-ci/woodpecker/pull/4193)) + +#### Former deprecations + +The following syntax deprecations will now result in an error: + +- `pipeline:` ([#3916](https://github.com/woodpecker-ci/woodpecker/pull/3916)) +- `platform:` ([#3916](https://github.com/woodpecker-ci/woodpecker/pull/3916)) +- `branches:` ([#3916](https://github.com/woodpecker-ci/woodpecker/pull/3916)) + +#### Workflow syntax changes + +- Grouping of steps via `steps.[name].group` should now be done using `steps.[name].depends_on` +- The `includes` and `excludes` event filter options have been removed +- Previously, env vars have been automatically sanitized to uppercase. + As this has been confusing, the type-case of the secret definition is now respected ([#3375](https://github.com/woodpecker-ci/woodpecker/pull/3375)). +- `secrets` have been entirely removed in favor of `environment` combined with the `from_secret` syntax. + As `secrets` are just normal env vars which are masked, the goal was to allow them to be declared next to normal env vars and at the same time reduce the keyword syntax count. + Additionally, the `from_secret` syntax gives more flexibility in naming. + Whereas beforehand `secrets` where always named after their initial secret name, the `from_secret` reference can now be different. + Last, one can inject multiple different env vars from the same secret reference. + + 2.x: + + ```yaml + secrets: [my_token] + ``` + + 3.x: + + ```yaml + environment: + MY_TOKEN: + from_secret: my_token + ``` + +- The `environment` filter option has been removed in favor of `when.evaluate` + +#### API changes + +- Removed deprecated `registry/` endpoint. Use `registries`, `/authorize/token` + +#### CLI changes + +The following restructuring was done to achieve a more consistent grouping: + +| Old Command | New Command | +| ------------------------------------------- | ------------------------------------------- | +| `woodpecker-cli registry` | `woodpecker-cli repo registry` | +| `woodpecker-cli secret --global` | `woodpecker-cli admin secret` | +| `woodpecker-cli user` | `woodpecker-cli admin user` | +| `woodpecker-cli log-level` | `woodpecker-cli admin log-level` | +| `woodpecker-cli secret --organization` | `woodpecker-cli org secret` | +| `woodpecker-cli deploy` | `woodpecker-cli pipeline deploy` | +| `woodpecker-cli log` | `woodpecker-cli pipeline log` | +| `woodpecker-cli cron` | `woodpecker-cli repo cron` | +| `woodpecker-cli secret --repository` | `woodpecker-cli repo secret` | +| `woodpecker-cli pipeline logs` | `woodpecker-cli pipeline log show` | +| `woodpecker-cli (registry,secret,...) info` | `woodpecker-cli (registry,secret,...) show` | + +#### Miscellaneous + +- For `woodpecker-cli` containers, `/woodpecker` has been set as the default `workdir` + +- Plugin filters for secrets (in the "secrets" repo settings) can now validate against tags. + Additionally, the description has been updated to reflect that these filters only apply to plugins ([#4069](https://github.com/woodpecker-ci/woodpecker/pull/4069)). + +- SDK changes: + + - The SDK fields `start_time`, `end_time`, `created_at`, `started_at`, `finished_at` and `reviewed_at` have been renamed to `started`, `finished`, `created`, `started`, `finished`, `reviewed` ([#3968](https://github.com/woodpecker-ci/woodpecker/pull/3968)) + - The `trusted` field of the repo model was changed from `boolean` to `object` + +- CRON definitions now use standard Linux syntax without seconds. + All custom CRON definitions which do not use keywords such as `@daily` or `@weekly` must be updated. + + Example definition for a CRON job running at 8 am daily: + + 2.x: + + ```sh + 0 0 8 * * * + ``` + + 3.x: + + ```sh + 0 8 * * * + ``` + +- Native Let's Encrypt certificate support has been dropped as it was almost unused and causing frequent issues. + Let's Encrypt needs to be set up standalone now. The SSL key pair can still be used in `WOODPECKER_SERVER_CERT` and `WOODPECKER_SERVER_KEY` as an alternative to using a reverse proxy for TLS termination. + +- The filename of the CLI binary changed for DEB and RPM packages, it is now called `woodpecker-cli` instead of `woodpecker`. + +### Admin-facing migrations + +#### Image tags + +- The `latest` tag has been dropped to avoid accidental major version upgrades. + A dedicated semver tag specification must be used, i.e., either a fixed version (like `v3.0.0`) or a rolling tag (e.g. `v3.0` or `v3`). + +- Git is now the only officially supported SCM. + No others were supported previously, but the existence of the env var `CI_REPO_SCM` indicated that others might be. + The env var has now been removed including unused code associated with it. + +- Previously, some (official) plugins were granted the `privileged` option by default to allow simplified usage. + To streamline this process and enhance security transparency, no plugin is granted the `privileged` options by default anymore. + To allow the use of these plugins in >= 3.0, they must be set explicitly through `WOODPECKER_PLUGINS_PRIVILEGED` on the admin side. + This change mainly impacts the use of the `woodpeckerci/plugin-docker-buildx` plugin, which now will not work anymore unless explicitly listed through this env var ([#4053](https://github.com/woodpecker-ci/woodpecker/pull/4053)) + +- Environment variable deprecations: + + | Deprecated Variable | New Variable | + | -------------------------------- | ------------------------------------ | + | `WOODPECKER_LOG_XORM` | `WOODPECKER_DATABASE_LOG` | + | `WOODPECKER_LOG_XORM_SQL` | `WOODPECKER_DATABASE_LOG_SQL` | + | `WOODPECKER_FILTER_LABELS` | `WOODPECKER_AGENT_LABELS` | + | `WOODPECKER_ESCALATE` | `WOODPECKER_PLUGINS_PRIVILEGED` | + | `WOODPECKER_DEFAULT_CLONE_IMAGE` | `WOODPECKER_DEFAULT_CLONE_PLUGIN` | + | `WOODPECKER_DEV_OAUTH_HOST` | `WOODPECKER_EXPERT_FORGE_OAUTH_HOST` | + | `WOODPECKER_DEV_GITEA_OAUTH_URL` | `WOODPECKER_EXPERT_FORGE_OAUTH_HOST` | + | `WOODPECKER_ROOT_PATH` | `WOODPECKER_HOST` | + | `WOODPECKER_ROOT_URL` | `WOODPECKER_HOST` | + +- The resource limit settings for the "docker" backend were moved from the server into agent configuration. + This allows setting limits on an agent-level which allows greater resource definition granularity ([#3174](https://github.com/woodpecker-ci/woodpecker/pull/3174)) + +- "Kubernetes" backend: previously the image pull secret name was hard-coded to `regcred`. + To allow more flexibility and specifying multiple pull secrets, the default has been removed. + Image pull secrets must now be set explicitly via env var `WOODPECKER_BACKEND_K8S_PULL_SECRET_NAMES` ([#4005](https://github.com/woodpecker-ci/woodpecker/pull/4005)) + +- Webhook signatures now use the `rfc9421` protocol + +#### Rootless images + +The `server` and `cli` images now use a non-privileged user (`woodpecker`) by default. +If you have volume mounts attached to containers, you might need to update the ownership of these directories from `root` to `woodpecker`. + +:::note +The agent image must remain rootful by default to be able to mount the Docker socket when Woodpecker is used with the `docker` backend. +The helm chart will start to use a non-privileged user by utilizing `securityContext`. +Running a completely rootless agent with the `docker` backend may be possible by using a rootless docker daemon. +However, this requires more work and is currently not supported. +::: + +## 2.7.2 + +To secure your instance, set `WOODPECKER_PLUGINS_PRIVILEGED` to only allow specific versions of the `woodpeckerci/plugin-docker-buildx` plugin, use version 5.0.0 or above. This prevents older, potentially unstable versions from being privileged. + +For example, to allow only version 5.0.0, use: + +```bash +WOODPECKER_PLUGINS_PRIVILEGED=woodpeckerci/plugin-docker-buildx:5.0.0 +``` + +To allow multiple versions, you can separate them with commas: + +```bash +WOODPECKER_PLUGINS_PRIVILEGED=woodpeckerci/plugin-docker-buildx:5.0.0,woodpeckerci/plugin-docker-buildx:5.1.0 +``` + +This setup ensures only specified, stable plugin versions are given privileged access. + +Read more about it in [#4213](https://github.com/woodpecker-ci/woodpecker/pull/4213) + +## 2.0.0 + +- Dropped deprecated `CI_BUILD_*`, `CI_PREV_BUILD_*`, `CI_JOB_*`, `*_LINK`, `CI_SYSTEM_ARCH`, `CI_REPO_REMOTE` built-in environment variables +- Deprecated `platform:` filter in favor of `labels:`, [read more](/docs/usage/workflow-syntax#filter-by-platform) +- Secrets `event` property was renamed to `events` and `image` to `images` as both are lists. The new property `events` / `images` has to be used in the api. The old properties `event` and `image` were removed. +- The secrets `plugin_only` option was removed. Secrets with images are now always only available for plugins using listed by the `images` property. Existing secrets with a list of `images` will now only be available to the listed images if they are used as a plugin. +- Removed `build` alias for `pipeline` command in CLI +- Removed `ssh` backend. Use an agent directly on the SSH machine using the `local` backend. +- Removed `/hook` and `/stream` API paths in favor of `/api/(hook|stream)`. You may need to use the "Repair repository" button in the repo settings or "Repair all" in the admin settings to recreate the forge hook. +- Removed `WOODPECKER_DOCS` config variable +- Renamed `link` to `url` (including all API fields) +- Deprecated `CI_COMMIT_URL` env var, use `CI_PIPELINE_FORGE_URL` + +## 1.0.0 + +- The signature used to verify extension calls (like those used for the [config-extension](/docs/administration/advanced/external-configuration-api)) done by the Woodpecker server switched from using a shared-secret HMac to an ed25519 key-pair. Read more about it at the [config-extensions](/docs/administration/advanced/external-configuration-api) documentation. +- Refactored support for old agent filter labels and expressions. Learn how to use the new [filter](/docs/usage/workflow-syntax#labels) +- Renamed step environment variable `CI_SYSTEM_ARCH` to `CI_SYSTEM_PLATFORM`. Same applies for the cli exec variable. +- Renamed environment variables `CI_BUILD_*` and `CI_PREV_BUILD_*` to `CI_PIPELINE_*` and `CI_PREV_PIPELINE_*`, old ones are still available but deprecated +- Renamed environment variables `CI_JOB_*` to `CI_STEP_*`, old ones are still available but deprecated +- Renamed environment variable `CI_REPO_REMOTE` to `CI_REPO_CLONE_URL`, old is still available but deprecated +- Renamed environment variable `*_LINK` to `*_URL`, old ones are still available but deprecated +- Renamed API endpoints for pipelines (`//builds/` -> `//pipelines/`), old ones are still available but deprecated +- Updated Prometheus gauge `build_*` to `pipeline_*` +- Updated Prometheus gauge `*_job_*` to `*_step_*` +- Renamed config env `WOODPECKER_MAX_PROCS` to `WOODPECKER_MAX_WORKFLOWS` (still available as fallback) +- The pipelines are now also read from `.yaml` files, the new default order is `.woodpecker/*.yml` and `.woodpecker/*.yaml` (without any prioritization) -> `.woodpecker.yml` -> `.woodpecker.yaml` +- Dropped support for [Coding](https://coding.net/), [Gogs](https://gogs.io) and Bitbucket Server (Stash). +- `/api/queue/resume` & `/api/queue/pause` endpoint methods were changed from `GET` to `POST` +- rename `pipeline:` key in your workflow config to `steps:` +- If you want to migrate old logs to the new format, watch the error messages on start. If there are none we are good to go, else you have to plan a migration that can take hours. Set `WOODPECKER_MIGRATIONS_ALLOW_LONG` to true and let it run. +- Using `repo-id` in favor of `owner/repo` combination + - :warning: The api endpoints `/api/repos/{owner}/{repo}/...` were replaced by new endpoints using the repos id `/api/repos/{repo-id}` + - To find the id of a repo use the `/api/repos/lookup/{repo-full-name-with-slashes}` endpoint. + - The existing badge endpoint `/api/badges/{owner}/{repo}` will still work, but whenever possible try to use the new endpoint using the `repo-id`: `/api/badges/{repo-id}`. + - The UI urls for a repository changed from `/repos/{owner}/{repo}/...` to `/repos/{repo-id}/...`. You will be redirected automatically when using the old url. + - The woodpecker-go api-client is now using the `repo-id` instead of `owner/repo` for all functions +- Using `org-id` in favour of `owner` name + - :warning: The api endpoints `/api/orgs/{owner}/...` were replaced by new endpoints using the orgs id `/api/repos/{org-id}` + - To find the id of orgs use the `/api/orgs/lookup/{org_full_name}` endpoint. + - The UI urls for a organization changed from `/org/{owner}/...` to `/orgs/{org-id}/...`. You will be redirected automatically when using the old url. + - The woodpecker-go api-client is now using the `org-id` instead of `org name` for all functions +- The `command:` field has been removed from steps. If you were using it, please check if the entrypoint of the image you used is a shell. + - If it is a shell, simply rename `command:` to `commands:`. + - If it's not, you need to prepend the entrypoint before and also rename it (e.g., `commands: `). + +## 0.15.0 + +- Default value for custom pipeline path is now empty / un-set which results in following resolution: + + `.woodpecker/*.yml` -> `.woodpecker.yml` -> `.drone.yml` + + Only projects created after updating will have an empty value by default. Existing projects will stick to the current pipeline path which is `.drone.yml` in most cases. + + Read more about it at the [Project Settings](/docs/usage/project-settings#pipeline-path) + +- From version `0.15.0` ongoing there will be three types of docker images: `latest`, `next` and `x.x.x` with an alpine variant for each type like `latest-alpine`. + If you used `latest` before to try pre-release features you should switch to `next` after this release. + +- Dropped support for `DRONE_*` environment variables. The according `WOODPECKER_*` variables must be used instead. + Additionally some alternative namings have been removed to simplify maintenance: + + - `WOODPECKER_AGENT_SECRET` replaces `WOODPECKER_SECRET`, `DRONE_SECRET`, `WOODPECKER_PASSWORD`, `DRONE_PASSWORD` and `DRONE_AGENT_SECRET`. + - `WOODPECKER_HOST` replaces `DRONE_HOST` and `DRONE_SERVER_HOST`. + - `WOODPECKER_DATABASE_DRIVER` replaces `DRONE_DATABASE_DRIVER` and `DATABASE_DRIVER`. + - `WOODPECKER_DATABASE_DATASOURCE` replaces `DRONE_DATABASE_DATASOURCE` and `DATABASE_CONFIG`. + +- Dropped support for `DRONE_*` environment variables in pipeline steps. Pipeline meta-data can be accessed with `CI_*` variables. + + - `CI_*` prefix replaces `DRONE_*` + - `CI` value is now `woodpecker` + - `DRONE=true` has been removed + - Some variables got deprecated and will be removed in future versions. Please migrate to the new names. Same applies for `DRONE_` of them. + - CI_ARCH => use CI_SYSTEM_ARCH + - CI_COMMIT => CI_COMMIT_SHA + - CI_TAG => CI_COMMIT_TAG + - CI_PULL_REQUEST => CI_COMMIT_PULL_REQUEST + - CI_REMOTE_URL => use CI_REPO_REMOTE + - CI_REPO_BRANCH => use CI_REPO_DEFAULT_BRANCH + - CI_PARENT_BUILD_NUMBER => use CI_BUILD_PARENT + - CI_BUILD_TARGET => use CI_BUILD_DEPLOY_TARGET + - CI_DEPLOY_TO => use CI_BUILD_DEPLOY_TARGET + - CI_COMMIT_AUTHOR_NAME => use CI_COMMIT_AUTHOR + - CI_PREV_COMMIT_AUTHOR_NAME => use CI_PREV_COMMIT_AUTHOR + - CI_SYSTEM => use CI_SYSTEM_NAME + - CI_BRANCH => use CI_COMMIT_BRANCH + - CI_SOURCE_BRANCH => use CI_COMMIT_SOURCE_BRANCH + - CI_TARGET_BRANCH => use CI_COMMIT_TARGET_BRANCH + + For all available variables and their descriptions have a look at [built-in-environment-variables](/docs/usage/environment#built-in-environment-variables). + +- Prometheus metrics have been changed from `drone_*` to `woodpecker_*` + +- Base path has moved from `/var/lib/drone` to `/var/lib/woodpecker` + +- Default workspace base path has moved from `/drone` to `/woodpecker` + +- Default SQLite database location has changed: + + - `/var/lib/drone/drone.sqlite` -> `/var/lib/woodpecker/woodpecker.sqlite` + - `drone.sqlite` -> `woodpecker.sqlite` + +- Plugin Settings moved into `settings` section: + + ```diff + steps: + something: + image: my/plugin + - setting1: foo + - setting2: bar + + settings: + + setting1: foo + + setting2: bar + ``` + +- `WOODPECKER_DEBUG` option for server and agent got removed in favor of `WOODPECKER_LOG_LEVEL=debug` + +- Remove unused server flags which can safely be removed from your server config: `WOODPECKER_QUIC`, `WOODPECKER_GITHUB_SCOPE`, `WOODPECKER_GITHUB_GIT_USERNAME`, `WOODPECKER_GITHUB_GIT_PASSWORD`, `WOODPECKER_GITHUB_PRIVATE_MODE`, `WOODPECKER_GITEA_GIT_USERNAME`, `WOODPECKER_GITEA_GIT_PASSWORD`, `WOODPECKER_GITEA_PRIVATE_MODE`, `WOODPECKER_GITLAB_GIT_USERNAME`, `WOODPECKER_GITLAB_GIT_PASSWORD`, `WOODPECKER_GITLAB_PRIVATE_MODE` + +- Dropped support for manually setting the agents platform with `WOODPECKER_PLATFORM`. The platform is now automatically detected. + +- Use `WOODPECKER_STATUS_CONTEXT` instead of the deprecated options `WOODPECKER_GITHUB_CONTEXT` and `WOODPECKER_GITEA_CONTEXT`. + +## 0.14.0 + +No breaking changes + +## From Drone + +:::warning +Migration from Drone is only possible if you were running Drone <= v0.8. +::: + +1. Make sure you are already running Drone v0.8 +2. Upgrade to Woodpecker v0.14.4, migration will be done during startup +3. Upgrade to the latest Woodpecker version. Pay attention to the breaking changes listed above. diff --git a/docs/src/pages/versions.md b/docs/src/pages/versions.md index 1b9778e4d..081f94495 100644 --- a/docs/src/pages/versions.md +++ b/docs/src/pages/versions.md @@ -19,7 +19,7 @@ the actual release will be about a week later. ### Deprecations & migrations -All deprecations and migrations for Woodpecker users and instance admins are documented in the [migration guide](/docs/next/migrations). +All deprecations and migrations for Woodpecker users and instance admins are documented in the [migration guide](/migrations). ## Next version (current state of the `main` branch) @@ -33,6 +33,12 @@ Here you can find documentation for previous versions of Woodpecker. | | | | | ------- | ---------- | ------------------------------------------------------------------------------------- | +| 2.7.3 | 2024-11-28 | [Documentation](https://github.com/woodpecker-ci/woodpecker/tree/v2.7.3/docs/docs/) | +| 2.7.2 | 2024-11-03 | [Documentation](https://github.com/woodpecker-ci/woodpecker/tree/v2.7.2/docs/docs/) | +| 2.7.1 | 2024-09-07 | [Documentation](https://github.com/woodpecker-ci/woodpecker/tree/v2.7.1/docs/docs/) | +| 2.7.0 | 2024-07-18 | [Documentation](https://github.com/woodpecker-ci/woodpecker/tree/v2.7.0/docs/docs/) | +| 2.6.1 | 2024-07-19 | [Documentation](https://github.com/woodpecker-ci/woodpecker/tree/v2.6.1/docs/docs/) | +| 2.6.0 | 2024-06-13 | [Documentation](https://github.com/woodpecker-ci/woodpecker/tree/v2.6.0/docs/docs/) | | 2.5.0 | 2024-06-01 | [Documentation](https://github.com/woodpecker-ci/woodpecker/tree/v2.5.0/docs/docs/) | | 2.4.1 | 2024-03-20 | [Documentation](https://github.com/woodpecker-ci/woodpecker/tree/v2.4.1/docs/docs/) | | 2.4.0 | 2024-03-19 | [Documentation](https://github.com/woodpecker-ci/woodpecker/tree/v2.4.0/docs/docs/) | diff --git a/docs/static/img/feat-docker.svg b/docs/static/img/feat-docker.svg index 1498e22c8..31c054cf9 100644 --- a/docs/static/img/feat-docker.svg +++ b/docs/static/img/feat-docker.svg @@ -1,2 +1,2 @@ - + diff --git a/docs/static/img/feat-opensource.svg b/docs/static/img/feat-opensource.svg index 89366e23d..61f3b413b 100644 --- a/docs/static/img/feat-opensource.svg +++ b/docs/static/img/feat-opensource.svg @@ -1,2 +1,2 @@ - + diff --git a/docs/static/img/workflows.svg b/docs/static/img/workflows.svg index 966b99cc5..597fbb476 100644 --- a/docs/static/img/workflows.svg +++ b/docs/static/img/workflows.svg @@ -1,2 +1,2 @@ - + diff --git a/docs/versioned_docs/version-1.0/20-usage/10-intro.md b/docs/versioned_docs/version-1.0/20-usage/10-intro.md index fcefb8195..8512ed621 100644 --- a/docs/versioned_docs/version-1.0/20-usage/10-intro.md +++ b/docs/versioned_docs/version-1.0/20-usage/10-intro.md @@ -10,7 +10,7 @@ Webhooks are used to trigger pipeline executions. When you push code to your rep > Required Permissions > ->The user who enables a repo in Woodpecker must have `Admin` rights on that repo, so that Woodpecker can add the webhook. +> The user who enables a repo in Woodpecker must have `Admin` rights on that repo, so that Woodpecker can add the webhook. > > Note that manually creating webhooks yourself is not possible. This is because webhooks are signed using a per-repository secret key which is not exposed to end users. diff --git a/docs/versioned_docs/version-1.0/20-usage/15-terminology.md b/docs/versioned_docs/version-1.0/20-usage/15-terminology.md index 68fc5ef0b..71d83557e 100644 --- a/docs/versioned_docs/version-1.0/20-usage/15-terminology.md +++ b/docs/versioned_docs/version-1.0/20-usage/15-terminology.md @@ -32,11 +32,11 @@ Sometimes there exist multiple terms that can be used for a thing, we try to def - **Pipelines** were previously called **builds** - **Steps** were previously called **jobs** -[Pipeline]: ./20-pipeline-syntax.md -[Workflow]: ./25-workflows.md -[Forge]: ../30-administration/11-forges/10-overview.md -[Plugin]: ./51-plugins/10-plugins.md +[Pipeline]: ./20-pipeline-syntax.md +[Workflow]: ./25-workflows.md +[Forge]: ../30-administration/11-forges/10-overview.md +[Plugin]: ./51-plugins/10-plugins.md [Workspace]: ./20-pipeline-syntax.md#workspace -[Matrix]: ./30-matrix-workflows.md -[Docker]: ../30-administration/22-backends/10-docker.md -[Local]: ../30-administration/22-backends/20-local.md +[Matrix]: ./30-matrix-workflows.md +[Docker]: ../30-administration/22-backends/10-docker.md +[Local]: ../30-administration/22-backends/20-local.md diff --git a/docs/versioned_docs/version-1.0/20-usage/20-pipeline-syntax.md b/docs/versioned_docs/version-1.0/20-usage/20-pipeline-syntax.md index dd8bc6f55..7b682f5ed 100644 --- a/docs/versioned_docs/version-1.0/20-usage/20-pipeline-syntax.md +++ b/docs/versioned_docs/version-1.0/20-usage/20-pipeline-syntax.md @@ -297,8 +297,8 @@ Execute a step using custom include and exclude logic: ```yaml when: - branch: - include: [ master, release/* ] - exclude: [ release/1.0.0, release/1.1.* ] + include: [master, release/*] + exclude: [release/1.0.0, release/1.1.*] ``` #### `event` @@ -383,7 +383,7 @@ Execute a step for a specific platform using wildcards: ```yaml when: - - platform: [ linux/*, windows/amd64 ] + - platform: [linux/*, windows/amd64] ``` #### `environment` @@ -427,14 +427,14 @@ Execute a step only when certain files were changed: ```yaml when: - - path: "src/*.js" + - path: 'src/*.js' ``` One can also use [glob patterns](https://github.com/bmatcuk/doublestar#patterns): ```yaml when: - - path: "src/**/*.js" + - path: 'src/**/*.js' ``` To match whether the files have been changed or not changed, use `include` or `exclude` respectively: @@ -442,9 +442,9 @@ To match whether the files have been changed or not changed, use `include` or `e ```yaml when: - path: - include: [ '.woodpecker/*.yml', '*.ini' ] - exclude: [ '*.md', 'docs/**' ] - ignore_message: "[ALL]" + include: ['.woodpecker/*.yml', '*.ini'] + exclude: ['*.md', 'docs/**'] + ignore_message: '[ALL]' ``` **Hint:** Passing a defined ignore-message like `[ALL]` inside the commit message will ignore all path conditions. diff --git a/docs/versioned_docs/version-1.0/20-usage/35-advanced-yaml-syntax.md b/docs/versioned_docs/version-1.0/20-usage/35-advanced-yaml-syntax.md index b5ea914c9..0951c6e2d 100644 --- a/docs/versioned_docs/version-1.0/20-usage/35-advanced-yaml-syntax.md +++ b/docs/versioned_docs/version-1.0/20-usage/35-advanced-yaml-syntax.md @@ -5,6 +5,7 @@ You can use [YAML anchors & aliases](https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases) as variables in your pipeline config. To convert this: + ```yml steps: test: @@ -67,24 +68,24 @@ steps: ```yaml variables: pre_cmds: &pre_cmds - - echo start - - whoami + - echo start + - whoami post_cmds: &post_cmds - - echo stop + - echo stop hello_cmd: &hello_cmd - - echo hello + - echo hello steps: step1: image: debian commands: - - <<: *pre_cmds # prepend a sequence - - echo exec step now do dedicated things - - <<: *post_cmds # append a sequence + - <<: *pre_cmds # prepend a sequence + - echo exec step now do dedicated things + - <<: *post_cmds # append a sequence step2: image: debian commands: - - <<: [*pre_cmds, *hello_cmd] # prepend two sequences - - echo echo from second step - - <<: *post_cmds + - <<: [*pre_cmds, *hello_cmd] # prepend two sequences + - echo echo from second step + - <<: *post_cmds ``` diff --git a/docs/versioned_docs/version-1.0/20-usage/40-secrets.md b/docs/versioned_docs/version-1.0/20-usage/40-secrets.md index 613a03fdb..2d1ba12b2 100644 --- a/docs/versioned_docs/version-1.0/20-usage/40-secrets.md +++ b/docs/versioned_docs/version-1.0/20-usage/40-secrets.md @@ -28,7 +28,6 @@ steps: + from_secret: secret_token ``` - Please note parameter expressions are subject to pre-processing. When using secrets in parameter expressions they should be escaped. ```diff @@ -84,7 +83,7 @@ Please be careful when exposing secrets to pull requests. If your repository is To prevent abusing your secrets with malicious pull requests, you can limit a secret to a list of images. They are not available to any other container. In addition, you can make the secret available only for plugins (steps without user-defined commands). :::warning -If you enable the option "Only available for plugins", always set an image filter too. Otherwise, the secret can be accessed by a very simple self-developed plugin and is thus *not* safe. +If you enable the option "Only available for plugins", always set an image filter too. Otherwise, the secret can be accessed by a very simple self-developed plugin and is thus _not_ safe. If you only set an image filter, you could still access the secret using the same image and by specifying a command that prints it. ::: diff --git a/docs/versioned_docs/version-1.0/20-usage/45-cron.md b/docs/versioned_docs/version-1.0/20-usage/45-cron.md index d954cb2ca..f90c77c1a 100644 --- a/docs/versioned_docs/version-1.0/20-usage/45-cron.md +++ b/docs/versioned_docs/version-1.0/20-usage/45-cron.md @@ -6,29 +6,29 @@ To configure cron jobs you need at least push access to the repository. 1. To create a new cron job adjust your pipeline config(s) and add the event filter to all steps you would like to run by the cron job: - ```diff - steps: - sync_locales: - image: weblate_sync - settings: - url: example.com - token: - from_secret: weblate_token - + when: - + event: cron - + cron: "name of the cron job" # if you only want to execute this step by a specific cron job - ``` + ```diff + steps: + sync_locales: + image: weblate_sync + settings: + url: example.com + token: + from_secret: weblate_token + + when: + + event: cron + + cron: "name of the cron job" # if you only want to execute this step by a specific cron job + ``` 1. Create a new cron job in the repository settings: - ![cron settings](./cron-settings.png) + ![cron settings](./cron-settings.png) - The supported schedule syntax can be found at . If you need general understanding of the cron syntax is a good place to start and experiment. + The supported schedule syntax can be found at . If you need general understanding of the cron syntax is a good place to start and experiment. - Examples: `@every 5m`, `@daily`, `0 30 * * * *` ... + Examples: `@every 5m`, `@daily`, `0 30 * * * *` ... - :::info - Woodpeckers cron syntax starts with seconds instead of minutes as used by most linux cron schedulers. + :::info + Woodpeckers cron syntax starts with seconds instead of minutes as used by most linux cron schedulers. - Example: "At minute 30 every hour" would be `0 30 * * * *` instead of `30 * * * *` - ::: + Example: "At minute 30 every hour" would be `0 30 * * * *` instead of `30 * * * *` + ::: diff --git a/docs/versioned_docs/version-1.0/20-usage/51-plugins/10-plugins.md b/docs/versioned_docs/version-1.0/20-usage/51-plugins/10-plugins.md index b580095d6..3d5ab9790 100644 --- a/docs/versioned_docs/version-1.0/20-usage/51-plugins/10-plugins.md +++ b/docs/versioned_docs/version-1.0/20-usage/51-plugins/10-plugins.md @@ -41,7 +41,7 @@ There are also other plugin lists with additional plugins. Keep in mind that [Dr - [Drone Plugins](http://plugins.drone.io) - [The Geek Lab Drone Plugins](https://drone-plugin-index.geekdocs.de/plugins/drone-matrix/) -::: + ::: ## Creating a plugin diff --git a/docs/versioned_docs/version-1.0/20-usage/71-project-settings.md b/docs/versioned_docs/version-1.0/20-usage/71-project-settings.md index 319a07e61..6626cf1a3 100644 --- a/docs/versioned_docs/version-1.0/20-usage/71-project-settings.md +++ b/docs/versioned_docs/version-1.0/20-usage/71-project-settings.md @@ -33,9 +33,16 @@ Only server admins can set this option. If you are not a server admin this optio ::: -### Only inject netrc credentials into trusted containers +### Only inject Git credentials into trusted clone plugins -Cloning pipeline step may need git credentials. They are injected via netrc. By default, they're only injected if this option is enabled, the repo is trusted ([see above](#trusted)) or the image is a trusted clone image. If you uncheck the option, git credentials will be injected into any container in clone step. +The clone step may require Git credentials (e.g. for private repos) which are injected via `netrc`. + +By default, they are only injected into trusted clone plugins listed in the env var `WOODPECKER_PLUGINS_TRUSTED_CLONE`. +If this option is disabled, the Git credentials are injected into every clone plugin, regardless of whether it is trusted or not. + +:::note +This option has no effect on steps other than the clone step. +::: ## Project visibility diff --git a/docs/versioned_docs/version-1.0/20-usage/90-pipeline-management.md b/docs/versioned_docs/version-1.0/20-usage/90-pipeline-management.md index e160922f3..cd44400a2 100644 --- a/docs/versioned_docs/version-1.0/20-usage/90-pipeline-management.md +++ b/docs/versioned_docs/version-1.0/20-usage/90-pipeline-management.md @@ -27,10 +27,10 @@ Another approach using YAML extensions: ```yml variables: - global_env: &global_env - - BASH_VERSION=1.2.3 - - PATH_SRC=src/ - - PATH_TEST=test/ - - FOO=something + - BASH_VERSION=1.2.3 + - PATH_SRC=src/ + - PATH_TEST=test/ + - FOO=something steps: build: @@ -57,8 +57,7 @@ One can create a file containing environment variables, and then source it in ea steps: init: image: bash - commands: - echo "FOO=hello" >> envvars + commands: echo "FOO=hello" >> envvars echo "BAR=world" >> envvars debug: diff --git a/docs/versioned_docs/version-1.0/30-administration/00-setup.md b/docs/versioned_docs/version-1.0/30-administration/00-setup.md index 193153226..522927986 100644 --- a/docs/versioned_docs/version-1.0/30-administration/00-setup.md +++ b/docs/versioned_docs/version-1.0/30-administration/00-setup.md @@ -22,7 +22,7 @@ Below are resources requirements for Woodpecker components itself: | Component | Memory | CPU | | --------- | ------ | --- | | Server | 200 MB | 1 | -| Agent | 32 MB | 1 | +| Agent | 32 MB | 1 | Note, that those values do not include the operating system or workload (pipelines execution) resources consumption. diff --git a/docs/versioned_docs/version-1.0/30-administration/10-server-config.md b/docs/versioned_docs/version-1.0/30-administration/10-server-config.md index 9e05c79a9..a5850195a 100644 --- a/docs/versioned_docs/version-1.0/30-administration/10-server-config.md +++ b/docs/versioned_docs/version-1.0/30-administration/10-server-config.md @@ -154,17 +154,18 @@ WOODPECKER_CUSTOM_CSS_FILE=/usr/local/www/woodpecker.js The examples below show how to place a banner message in the top navigation bar of Woodpecker. ##### woodpecker.css + ```css .banner-message { - position: absolute; - width: 280px; - height: 40px; - margin-left: 240px; - margin-top: 5px; - padding-top: 5px; - font-weight: bold; - background: red no-repeat; - text-align: center; + position: absolute; + width: 280px; + height: 40px; + margin-left: 240px; + margin-top: 5px; + padding-top: 5px; + font-weight: bold; + background: red no-repeat; + text-align: center; } ``` @@ -172,44 +173,52 @@ The examples below show how to place a banner message in the top navigation bar ```javascript // place/copy a minified version of jQuery or ZeptoJS here ... -!function(){"use strict";function e(){};/*...*/}(); +!(function () { + 'use strict'; + function e() {} /*...*/ +})(); -$().ready(function(){ - $(".app nav img").first().htmlAfter("") +$().ready(function () { + $('.app nav img').first().htmlAfter(""); }); ``` - ## All server configuration options The following list describes all available server configuration options. ### `WOODPECKER_LOG_LEVEL` + > Default: empty Configures the logging level. Possible values are `trace`, `debug`, `info`, `warn`, `error`, `fatal`, `panic`, `disabled` and empty. ### `WOODPECKER_LOG_XORM` + > Default: `false` Enable XORM logs. ### `WOODPECKER_LOG_XORM_SQL` + > Default: `false` Enable XORM SQL command logs. ### `WOODPECKER_DEBUG_PRETTY` + > Default: `false` Enable pretty-printed debug output. ### `WOODPECKER_DEBUG_NOCOLOR` + > Default: `true` Disable colored debug output. ### `WOODPECKER_HOST` + > Default: empty Server fully qualified URL of the user-facing hostname. @@ -217,6 +226,7 @@ Server fully qualified URL of the user-facing hostname. Example: `WOODPECKER_HOST=http://woodpecker.example.org` ### `WOODPECKER_WEBHOOK_HOST` + > Default: value from `WOODPECKER_HOST` config env Server fully qualified URL of the Webhook-facing hostname. @@ -224,16 +234,19 @@ Server fully qualified URL of the Webhook-facing hostname. Example: `WOODPECKER_WEBHOOK_HOST=http://woodpecker-server.cicd.svc.cluster.local:8000` ### `WOODPECKER_SERVER_ADDR` + > Default: `:8000` Configures the HTTP listener port. ### `WOODPECKER_SERVER_ADDR_TLS` + > Default: `:443` Configures the HTTPS listener port when SSL is enabled. ### `WOODPECKER_SERVER_CERT` + > Default: empty Path to an SSL certificate used by the server to accept HTTPS requests. @@ -241,6 +254,7 @@ Path to an SSL certificate used by the server to accept HTTPS requests. Example: `WOODPECKER_SERVER_CERT=/path/to/cert.pem` ### `WOODPECKER_SERVER_KEY` + > Default: empty Path to an SSL certificate key used by the server to accept HTTPS requests. @@ -248,6 +262,7 @@ Path to an SSL certificate key used by the server to accept HTTPS requests. Example: `WOODPECKER_SERVER_KEY=/path/to/key.pem` ### `WOODPECKER_CUSTOM_CSS_FILE` + > Default: empty File path for the server to serve a custom .CSS file, used for customizing the UI. @@ -257,6 +272,7 @@ The file must be UTF-8 encoded, to ensure all special characters are preserved. Example: `WOODPECKER_CUSTOM_CSS_FILE=/usr/local/www/woodpecker.css` ### `WOODPECKER_CUSTOM_JS_FILE` + > Default: empty File path for the server to serve a custom .JS file, used for customizing the UI. @@ -266,26 +282,31 @@ The file must be UTF-8 encoded, to ensure all special characters are preserved. Example: `WOODPECKER_CUSTOM_JS_FILE=/usr/local/www/woodpecker.js` ### `WOODPECKER_LETS_ENCRYPT` + > Default: `false` Automatically generates an SSL certificate using Let's Encrypt, and configures the server to accept HTTPS requests. ### `WOODPECKER_GRPC_ADDR` + > Default: `:9000` Configures the gRPC listener port. ### `WOODPECKER_GRPC_SECRET` + > Default: `secret` Configures the gRPC JWT secret. ### `WOODPECKER_GRPC_SECRET_FILE` + > Default: empty Read the value for `WOODPECKER_GRPC_SECRET` from the specified filepath. ### `WOODPECKER_METRICS_SERVER_ADDR` + > Default: empty Configures an unprotected metrics endpoint. An empty value disables the metrics endpoint completely. @@ -293,6 +314,7 @@ Configures an unprotected metrics endpoint. An empty value disables the metrics Example: `:9001` ### `WOODPECKER_ADMIN` + > Default: empty Comma-separated list of admin accounts. @@ -300,6 +322,7 @@ Comma-separated list of admin accounts. Example: `WOODPECKER_ADMIN=user1,user2` ### `WOODPECKER_ORGS` + > Default: empty Comma-separated list of approved organizations. @@ -307,6 +330,7 @@ Comma-separated list of approved organizations. Example: `org1,org2` ### `WOODPECKER_REPO_OWNERS` + > Default: empty Comma-separated list of syncable repo owners. ??? @@ -314,41 +338,49 @@ Comma-separated list of syncable repo owners. ??? Example: `user1,user2` ### `WOODPECKER_OPEN` + > Default: `false` Enable to allow user registration. ### `WOODPECKER_DOCS` + > Default: `https://woodpecker-ci.org/` Link to documentation in the UI. ### `WOODPECKER_AUTHENTICATE_PUBLIC_REPOS` + > Default: `false` Always use authentication to clone repositories even if they are public. Needed if the forge requires to always authenticate as used by many companies. ### `WOODPECKER_DEFAULT_CANCEL_PREVIOUS_PIPELINE_EVENTS` + > Default: `pull_request, push` List of event names that will be canceled when a new pipeline for the same context (tag, branch) is created. ### `WOODPECKER_DEFAULT_CLONE_IMAGE` + > Default is defined in [shared/constant/constant.go](https://github.com/woodpecker-ci/woodpecker/blob/master/shared/constant/constant.go) The default docker image to be used when cloning the repo ### `WOODPECKER_DEFAULT_PIPELINE_TIMEOUT` + > 60 (minutes) The default time for a repo in minutes before a pipeline gets killed ### `WOODPECKER_MAX_PIPELINE_TIMEOUT` + > 120 (minutes) The maximum time in minutes you can set in the repo settings before a pipeline gets killed ### `WOODPECKER_SESSION_EXPIRES` + > Default: `72h` Configures the session expiration time. @@ -357,6 +389,7 @@ As long as the session is valid (until it expires or log-out), a user can log into Woodpecker, without re-authentication. ### `WOODPECKER_ESCALATE` + > Defaults are defined in [shared/constant/constant.go](https://github.com/woodpecker-ci/woodpecker/blob/master/shared/constant/constant.go) Docker images to run in privileged mode. Only change if you are sure what you do! @@ -371,6 +404,7 @@ Example: `WOODPECKER_VOLUME=/path/on/host:/path/in/container:rw`| --> ### `WOODPECKER_DOCKER_CONFIG` + > Default: empty Configures a specific private registry config for all pipelines. @@ -392,16 +426,19 @@ Example: `WOODPECKER_NETWORK=network1,network2` --> ### `WOODPECKER_AGENT_SECRET` + > Default: empty A shared secret used by server and agents to authenticate communication. A secret can be generated by `openssl rand -hex 32`. ### `WOODPECKER_AGENT_SECRET_FILE` + > Default: empty Read the value for `WOODPECKER_AGENT_SECRET` from the specified filepath ### `WOODPECKER_KEEPALIVE_MIN_TIME` + > Default: empty Server-side enforcement policy on the minimum amount of time a client should wait before sending a keepalive ping. @@ -409,16 +446,19 @@ Server-side enforcement policy on the minimum amount of time a client should wai Example: `WOODPECKER_KEEPALIVE_MIN_TIME=10s` ### `WOODPECKER_DATABASE_DRIVER` + > Default: `sqlite3` The database driver name. Possible values are `sqlite3`, `mysql` or `postgres`. ### `WOODPECKER_DATABASE_DATASOURCE` + > Default: `woodpecker.sqlite` The database connection string. The default value is the path of the embedded SQLite database file. Example: + ```bash # MySQL # https://github.com/go-sql-driver/mysql#dsn-data-source-name @@ -430,47 +470,56 @@ WOODPECKER_DATABASE_DATASOURCE=postgres://root:password@1.2.3.4:5432/woodpecker? ``` ### `WOODPECKER_DATABASE_DATASOURCE_FILE` + > Default: empty Read the value for `WOODPECKER_DATABASE_DATASOURCE` from the specified filepath ### `WOODPECKER_ENCRYPTION_KEY` + > Default: empty Encryption key used to encrypt secrets in DB. See [secrets encryption](./40-encryption.md) ### `WOODPECKER_ENCRYPTION_KEY_FILE` + > Default: empty Read the value for `WOODPECKER_ENCRYPTION_KEY` from the specified filepath ### `WOODPECKER_ENCRYPTION_TINK_KEYSET_FILE` + > Default: empty Filepath to encryption keyset used to encrypt secrets in DB. See [secrets encryption](./40-encryption.md) ### `WOODPECKER_ENCRYPTION_DISABLE` + > Default: empty Boolean flag to decrypt secrets in DB and disable server encryption. See [secrets encryption](./40-encryption.md) ### `WOODPECKER_PROMETHEUS_AUTH_TOKEN` + > Default: empty Token to secure the Prometheus metrics endpoint. Must be set to enable the endpoint. ### `WOODPECKER_PROMETHEUS_AUTH_TOKEN_FILE` + > Default: empty Read the value for `WOODPECKER_PROMETHEUS_AUTH_TOKEN` from the specified filepath ### `WOODPECKER_STATUS_CONTEXT` + > Default: `ci/woodpecker` Context prefix Woodpecker will use to publish status messages to SCM. You probably will only need to change it if you run multiple Woodpecker instances for a single repository. ### `WOODPECKER_STATUS_CONTEXT_FORMAT` + > Default: `{{ .context }}/{{ .event }}/{{ .workflow }}` Template for the status messages published to forges, uses [Go templates](https://pkg.go.dev/text/template) as template language. @@ -485,50 +534,57 @@ Supported variables: --- ### `WOODPECKER_LIMIT_MEM_SWAP` + > Default: `0` The maximum amount of memory a single pipeline container is allowed to swap to disk, configured in bytes. There is no limit if `0`. ### `WOODPECKER_LIMIT_MEM` + > Default: `0` The maximum amount of memory a single pipeline container can use, configured in bytes. There is no limit if `0`. ### `WOODPECKER_LIMIT_SHM_SIZE` + > Default: `0` The maximum amount of memory of `/dev/shm` allowed in bytes. There is no limit if `0`. ### `WOODPECKER_LIMIT_CPU_QUOTA` + > Default: `0` The number of microseconds per CPU period that the container is limited to before throttled. There is no limit if `0`. ### `WOODPECKER_LIMIT_CPU_SHARES` + > Default: `0` The relative weight vs. other containers. ### `WOODPECKER_LIMIT_CPU_SET` + > Default: empty Comma-separated list to limit the specific CPUs or cores a pipeline container can use. Example: `WOODPECKER_LIMIT_CPU_SET=1,2` - ### `WOODPECKER_CONFIG_SERVICE_ENDPOINT` + > Default: `` Specify a configuration service endpoint, see [Configuration Extension](./100-external-configuration-api.md) - ### `WOODPECKER_FORGE_TIMEOUT` + > Default: 3sec Specify how many seconds before timeout when fetching the Woodpecker configuration from a Forge ### `WOODPECKER_ROOT_URL` + > Default: `` Server URL path prefix (used for statics loading when having a url path prefix), should start with `/` diff --git a/docs/versioned_docs/version-1.0/30-administration/100-external-configuration-api.md b/docs/versioned_docs/version-1.0/30-administration/100-external-configuration-api.md index 504ef5a83..023a2c400 100644 --- a/docs/versioned_docs/version-1.0/30-administration/100-external-configuration-api.md +++ b/docs/versioned_docs/version-1.0/30-administration/100-external-configuration-api.md @@ -52,9 +52,7 @@ WOODPECKER_CONFIG_SERVICE_ENDPOINT=https://example.com/ciconfig "author_avatar": "https://myforge.com/avatars/d6b3f7787a685fcdf2a44e2c685c7e03", "author_email": "my@email.com", "branch": "master", - "changed_files": [ - "somefilename.txt" - ], + "changed_files": ["somefilename.txt"], "commit": "2fff90f8d288a4640e90f05049fe30e61a14fd50", "created_at": 0, "deploy_to": "", diff --git a/docs/versioned_docs/version-1.0/30-administration/11-forges/10-overview.md b/docs/versioned_docs/version-1.0/30-administration/11-forges/10-overview.md index ea31738fb..0f92eb1a1 100644 --- a/docs/versioned_docs/version-1.0/30-administration/11-forges/10-overview.md +++ b/docs/versioned_docs/version-1.0/30-administration/11-forges/10-overview.md @@ -2,13 +2,13 @@ ## Supported features -| Feature | [GitHub](github/) | [Gitea / Forgejo](gitea/) | [Gitlab](gitlab/) | [Bitbucket](bitbucket/) | -| --- | :---: | :---: | :---: | :---: | -| Event: Push | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| Event: Tag | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| Event: Pull-Request | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| Event: Deploy | :white_check_mark: | :x: | :x: | :x: | -| [Multiple workflows](../../20-usage/25-workflows.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | -| [when.path filter](../../20-usage/20-pipeline-syntax.md#path) | :white_check_mark: | :white_check_mark:¹ | :white_check_mark: | :x: | +| Feature | [GitHub](github/) | [Gitea / Forgejo](gitea/) | [Gitlab](gitlab/) | [Bitbucket](bitbucket/) | +| ------------------------------------------------------------- | :----------------: | :-----------------------: | :----------------: | :---------------------: | +| Event: Push | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Event: Tag | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Event: Pull-Request | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Event: Deploy | :white_check_mark: | :x: | :x: | :x: | +| [Multiple workflows](../../20-usage/25-workflows.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | +| [when.path filter](../../20-usage/20-pipeline-syntax.md#path) | :white_check_mark: | :white_check_mark:¹ | :white_check_mark: | :x: | ¹ for pull requests at least Gitea version 1.17 is required diff --git a/docs/versioned_docs/version-1.0/30-administration/11-forges/20-github.md b/docs/versioned_docs/version-1.0/30-administration/11-forges/20-github.md index 9ed47580d..f3203d3d1 100644 --- a/docs/versioned_docs/version-1.0/30-administration/11-forges/20-github.md +++ b/docs/versioned_docs/version-1.0/30-administration/11-forges/20-github.md @@ -32,41 +32,49 @@ Please use this screenshot for reference: This is a full list of configuration options. Please note that many of these options use default configuration values that should work for the majority of installations. ### `WOODPECKER_GITHUB` + > Default: `false` Enables the GitHub driver. ### `WOODPECKER_GITHUB_URL` + > Default: `https://github.com` Configures the GitHub server address. ### `WOODPECKER_GITHUB_CLIENT` + > Default: empty Configures the GitHub OAuth client id. This is used to authorize access. ### `WOODPECKER_GITHUB_CLIENT_FILE` + > Default: empty Read the value for `WOODPECKER_GITHUB_CLIENT` from the specified filepath ### `WOODPECKER_GITHUB_SECRET` + > Default: empty Configures the GitHub OAuth client secret. This is used to authorize access. ### `WOODPECKER_GITHUB_SECRET_FILE` + > Default: empty Read the value for `WOODPECKER_GITHUB_SECRET` from the specified filepath ### `WOODPECKER_GITHUB_MERGE_REF` + > Default: `true` TODO ### `WOODPECKER_GITHUB_SKIP_VERIFY` + > Default: `false` Configure if SSL verification should be skipped. diff --git a/docs/versioned_docs/version-1.0/30-administration/11-forges/30-gitea.md b/docs/versioned_docs/version-1.0/30-administration/11-forges/30-gitea.md index 24a500d3b..54e94a651 100644 --- a/docs/versioned_docs/version-1.0/30-administration/11-forges/30-gitea.md +++ b/docs/versioned_docs/version-1.0/30-administration/11-forges/30-gitea.md @@ -25,51 +25,59 @@ services: Register your application with Gitea to create your client id and secret. You can find the OAuth applications settings of Gitea at `https://gitea./user/settings/`. It is very import the authorization callback URL matches your http(s) scheme and hostname exactly with `https:///authorize` as the path. If you run the Woodpecker CI server on the same host as the Gitea instance, you might also need to allow local connections in Gitea, since version `v1.16`. Otherwise webhooks will fail. Add the following lines to your Gitea configuration (usually at `/etc/gitea/conf/app.ini`). + ```ini ... [webhook] ALLOWED_HOST_LIST=external,loopback ``` + For reference see [Configuration Cheat Sheet](https://docs.gitea.io/en-us/config-cheat-sheet/#webhook-webhook). ![gitea oauth setup](gitea_oauth.gif) - ## Configuration This is a full list of configuration options. Please note that many of these options use default configuration values that should work for the majority of installations. ### `WOODPECKER_GITEA` + > Default: `false` Enables the Gitea driver. ### `WOODPECKER_GITEA_URL` + > Default: `https://try.gitea.io` Configures the Gitea server address. ### `WOODPECKER_GITEA_CLIENT` + > Default: empty Configures the Gitea OAuth client id. This is used to authorize access. ### `WOODPECKER_GITEA_CLIENT_FILE` + > Default: empty Read the value for `WOODPECKER_GITEA_CLIENT` from the specified filepath ### `WOODPECKER_GITEA_SECRET` + > Default: empty Configures the Gitea OAuth client secret. This is used to authorize access. ### `WOODPECKER_GITEA_SECRET_FILE` + > Default: empty Read the value for `WOODPECKER_GITEA_SECRET` from the specified filepath ### `WOODPECKER_GITEA_SKIP_VERIFY` + > Default: `false` Configure if SSL verification should be skipped. diff --git a/docs/versioned_docs/version-1.0/30-administration/11-forges/40-gitlab.md b/docs/versioned_docs/version-1.0/30-administration/11-forges/40-gitlab.md index 46b40a0f1..6753bfafe 100644 --- a/docs/versioned_docs/version-1.0/30-administration/11-forges/40-gitlab.md +++ b/docs/versioned_docs/version-1.0/30-administration/11-forges/40-gitlab.md @@ -32,36 +32,43 @@ If you run the Woodpecker CI server on the same host as the GitLab instance, you This is a full list of configuration options. Please note that many of these options use default configuration values that should work for the majority of installations. ### `WOODPECKER_GITLAB` + > Default: `false` Enables the GitLab driver. ### `WOODPECKER_GITLAB_URL` + > Default: `https://gitlab.com` Configures the GitLab server address. ### `WOODPECKER_GITLAB_CLIENT` + > Default: empty Configures the GitLab OAuth client id. This is used to authorize access. ### `WOODPECKER_GITLAB_CLIENT_FILE` + > Default: empty Read the value for `WOODPECKER_GITLAB_CLIENT` from the specified filepath ### `WOODPECKER_GITLAB_SECRET` + > Default: empty Configures the GitLab OAuth client secret. This is used to authorize access. ### `WOODPECKER_GITLAB_SECRET_FILE` + > Default: empty Read the value for `WOODPECKER_GITLAB_SECRET` from the specified filepath ### `WOODPECKER_GITLAB_SKIP_VERIFY` + > Default: `false` Configure if SSL verification should be skipped. diff --git a/docs/versioned_docs/version-1.0/30-administration/11-forges/50-bitbucket.md b/docs/versioned_docs/version-1.0/30-administration/11-forges/50-bitbucket.md index 909da4d69..81f71519b 100644 --- a/docs/versioned_docs/version-1.0/30-administration/11-forges/50-bitbucket.md +++ b/docs/versioned_docs/version-1.0/30-administration/11-forges/50-bitbucket.md @@ -44,26 +44,31 @@ Webhooks:Read and Write This is a full list of configuration options. Please note that many of these options use default configuration values that should work for the majority of installations. ### `WOODPECKER_BITBUCKET` + > Default: `false` Enables the Bitbucket driver. ### `WOODPECKER_BITBUCKET_CLIENT` + > Default: empty Configures the Bitbucket OAuth client id. This is used to authorize access. ### `WOODPECKER_BITBUCKET_CLIENT_FILE` + > Default: empty Read the value for `WOODPECKER_BITBUCKET_CLIENT` from the specified filepath ### `WOODPECKER_BITBUCKET_SECRET` + > Default: empty Configures the Bitbucket OAuth client secret. This is used to authorize access. ### `WOODPECKER_BITBUCKET_SECRET_FILE` + > Default: empty Read the value for `WOODPECKER_BITBUCKET_SECRET` from the specified filepath diff --git a/docs/versioned_docs/version-1.0/30-administration/15-agent-config.md b/docs/versioned_docs/version-1.0/30-administration/15-agent-config.md index 94fb03f67..6f16b9135 100644 --- a/docs/versioned_docs/version-1.0/30-administration/15-agent-config.md +++ b/docs/versioned_docs/version-1.0/30-administration/15-agent-config.md @@ -62,12 +62,12 @@ _Agent token_ is a token that is used by only particular agent. This unique toke In that case you probably doesn't configure `WOODPECKER_AGENT_SECRET` on the server side. The registration process would be as follows: 1. Administrator registers Agent manually in _Server settings - Agents - Add agent_; -![Agent creation](./new-agent-registration.png) -![Agent created](./new-agent-created.png) + ![Agent creation](./new-agent-registration.png) + ![Agent created](./new-agent-created.png) 2. The token generated in previous step have to be provided to Agent in `WOODPECKER_AGENT_SECRET`; 3. First time Agent communicates with Server using agent token; 4. Server identifies Agent by the token and fills additional information provided by Agent; -![Agent connected](./new-agent-connected.png) + ![Agent connected](./new-agent-connected.png) At following startups Agent uses own token only. @@ -76,91 +76,109 @@ At following startups Agent uses own token only. Here is the full list of configuration options and their default variables. ### `WOODPECKER_SERVER` + > Default: `localhost:9000` Configures gRPC address of the server. ### `WOODPECKER_USERNAME` + > Default: `x-oauth-basic` The gRPC username. ### `WOODPECKER_AGENT_SECRET` + > Default: empty A shared secret used by server and agents to authenticate communication. A secret can be generated by `openssl rand -hex 32`. ### `WOODPECKER_AGENT_SECRET_FILE` + > Default: empty Read the value for `WOODPECKER_AGENT_SECRET` from the specified filepath, e.g. `/etc/woodpecker/agent-secret.conf` ### `WOODPECKER_LOG_LEVEL` + > Default: empty Configures the logging level. Possible values are `trace`, `debug`, `info`, `warn`, `error`, `fatal`, `panic`, `disabled` and empty. ### `WOODPECKER_DEBUG_PRETTY` + > Default: `false` Enable pretty-printed debug output. ### `WOODPECKER_DEBUG_NOCOLOR` + > Default: `true` Disable colored debug output. ### `WOODPECKER_HOSTNAME` + > Default: empty Configures the agent hostname. ### `WOODPECKER_AGENT_CONFIG_FILE` + > Default: `/etc/woodpecker/agent.conf` Configures the path of the agent config file. ### `WOODPECKER_MAX_WORKFLOWS` + > Default: `1` Configures the number of parallel workflows. ### `WOODPECKER_FILTER_LABELS` + > Default: empty Configures labels to filter pipeline pick up. Use a list of key-value pairs like `key=value,second-key=*`. `*` can be used as a wildcard. By default agents provide three additional labels `platform=os/arch`, `hostname=my-agent` and `repo=*` which can be overwritten if needed. To learn how labels work check out the [pipeline syntax page](../20-usage/20-pipeline-syntax.md#labels). ### `WOODPECKER_HEALTHCHECK` + > Default: `true` Enable healthcheck endpoint. ### `WOODPECKER_HEALTHCHECK_ADDR` + > Default: `:3000` Configures healthcheck endpoint address. ### `WOODPECKER_KEEPALIVE_TIME` + > Default: empty After a duration of this time of no activity, the agent pings the server to check if the transport is still alive. ### `WOODPECKER_KEEPALIVE_TIMEOUT` + > Default: `20s` After pinging for a keepalive check, the agent waits for a duration of this time before closing the connection if no activity. ### `WOODPECKER_GRPC_SECURE` + > Default: `false` Configures if the connection to `WOODPECKER_SERVER` should be made using a secure transport. ### `WOODPECKER_GRPC_VERIFY` + > Default: `true` Configures if the gRPC server certificate should be verified, only valid when `WOODPECKER_GRPC_SECURE` is `true`. ### `WOODPECKER_BACKEND` + > Default: `auto-detect` Configures the backend engine to run pipelines on. Possible values are `auto-detect`, `docker`, `local`, `ssh` or `kubernetes`. diff --git a/docs/versioned_docs/version-1.0/30-administration/22-backends/10-docker.md b/docs/versioned_docs/version-1.0/30-administration/22-backends/10-docker.md index 8651d2594..6eac9395c 100644 --- a/docs/versioned_docs/version-1.0/30-administration/22-backends/10-docker.md +++ b/docs/versioned_docs/version-1.0/30-administration/22-backends/10-docker.md @@ -5,16 +5,19 @@ This is the original backend used with Woodpecker. The docker backend executes e ## Configuration ### `WOODPECKER_BACKEND_DOCKER_NETWORK` + > Default: empty Set to the name of an existing network which will be attached to all your pipeline containers (steps). Please be careful as this allows the containers of different pipelines to access each other! ### `WOODPECKER_BACKEND_DOCKER_ENABLE_IPV6` + > Default: `false` Enable IPv6 for the networks used by pipeline containers (steps). Make sure you configured your docker daemon to support IPv6. ### `WOODPECKER_BACKEND_DOCKER_VOLUMES` + > Default: empty List of default volumes separated by comma to be mounted to all pipeline containers (steps). For example to use custom CA diff --git a/docs/versioned_docs/version-1.0/30-administration/22-backends/20-local.md b/docs/versioned_docs/version-1.0/30-administration/22-backends/20-local.md index 1d5053d7a..aa23215f4 100644 --- a/docs/versioned_docs/version-1.0/30-administration/22-backends/20-local.md +++ b/docs/versioned_docs/version-1.0/30-administration/22-backends/20-local.md @@ -77,15 +77,13 @@ manual clone step. The `image` entry is used to specify the shell, such as Bash or Fish, that is used to run the commands. - ```yaml # .woodpecker.yml steps: build: image: bash - commands: - [...] + commands: [...] ``` ### Using labels to filter tasks @@ -111,6 +109,5 @@ only run on this agent: labels: type: exec -steps: - [...] +steps: [...] ``` diff --git a/docs/versioned_docs/version-1.0/30-administration/22-backends/30-ssh.md b/docs/versioned_docs/version-1.0/30-administration/22-backends/30-ssh.md index e66cf1629..298bbded1 100644 --- a/docs/versioned_docs/version-1.0/30-administration/22-backends/30-ssh.md +++ b/docs/versioned_docs/version-1.0/30-administration/22-backends/30-ssh.md @@ -16,26 +16,31 @@ The backend will use a random directory in $TMPDIR to store the clone code and e ## Configuration ### `WOODPECKER_BACKEND_SSH_ADDRESS` + > Default: empty The SSH host to run steps with `ssh` backend. ### `WOODPECKER_BACKEND_SSH_USER` + > Default: empty The SSH user to run steps with `ssh` backend. ### `WOODPECKER_BACKEND_SSH_KEY` + > Default: empty Path to the private SSH key to run steps with `ssh` backend. ### `WOODPECKER_BACKEND_SSH_KEY_PASSWORD` + > Default: empty The password for the private key to run steps with `ssh` backend. ### `WOODPECKER_BACKEND_SSH_PASSWORD` + > Default empty The SSH password to run steps with `ssh` backend. diff --git a/docs/versioned_docs/version-1.0/30-administration/22-backends/40-kubernetes.md b/docs/versioned_docs/version-1.0/30-administration/22-backends/40-kubernetes.md index 9ab79b7e6..7c0bcd5f6 100644 --- a/docs/versioned_docs/version-1.0/30-administration/22-backends/40-kubernetes.md +++ b/docs/versioned_docs/version-1.0/30-administration/22-backends/40-kubernetes.md @@ -11,31 +11,37 @@ The kubernetes backend executes each step inside a newly created pod. A PVC is a ## Configuration ### `WOODPECKER_BACKEND_K8S_NAMESPACE` + > Default: `woodpecker` The namespace to create worker pods in. ### `WOODPECKER_BACKEND_K8S_VOLUME_SIZE` + > Default: `10G` The volume size of the pipeline volume. ### `WOODPECKER_BACKEND_K8S_STORAGE_CLASS` + > Default: empty The storage class to use for the pipeline volume. ### `WOODPECKER_BACKEND_K8S_STORAGE_RWX` + > Default: `true` Determines if `RWX` should be used for the pipeline volume's [access mode](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes). If false, `RWO` is used instead. ### `WOODPECKER_BACKEND_K8S_POD_LABELS` + > Default: empty Additional labels to apply to worker pods. Must be a YAML object, e.g. `{"example.com/test-label":"test-value"}`. ### `WOODPECKER_BACKEND_K8S_POD_ANNOTATIONS` + > Default: empty Additional annotations to apply to worker pods. Must be a YAML object, e.g. `{"example.com/test-annotation":"test-value"}`. @@ -59,6 +65,7 @@ By default the pod will use "kubernetes.io/arch" inferred from top-level "platfo See the [kubernetes documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector) for more information on using nodeSelector. Example pipeline configuration: + ```yaml steps: build: diff --git a/docs/versioned_docs/version-1.0/30-administration/40-encryption.md b/docs/versioned_docs/version-1.0/30-administration/40-encryption.md index e48ece3ee..f9f3132f4 100644 --- a/docs/versioned_docs/version-1.0/30-administration/40-encryption.md +++ b/docs/versioned_docs/version-1.0/30-administration/40-encryption.md @@ -22,25 +22,31 @@ enabled encryption method, and `WOODPECKER_ENCRYPTION_DISABLE` set to true. After secrets was decrypted server will proceed working in unencrypted mode. You will not need to use "disable encryption" variable or encryption keys to start server anymore. - ## AES + Simple AES encryption. ### Configuration + You can manage encryption on server using these environment variables: + - `WOODPECKER_ENCRYPTION_KEY` - encryption key - `WOODPECKER_ENCRYPTION_KEY_FILE` - file to read encryption key from - `WOODPECKER_ENCRYPTION_DISABLE` - disable encryption flag used to decrypt all data on server ## TINK + TINK uses AEAD encryption instead of simple AES and supports key rotation. ### Configuration + You can manage encryption on server using these two environment variables: + - `WOODPECKER_ENCRYPTION_TINK_KEYSET_FILE` - keyset filepath - `WOODPECKER_ENCRYPTION_DISABLE` - disable encryption flag used to decrypt all data on server ### Encryption keys + You will need plaintext AEAD-compatible Google TINK keyset to encrypt your data. To generate it and then rotate keys if needed, install `tinkey`([installation guide](https://developers.google.com/tink/install-tinkey)) @@ -49,12 +55,15 @@ Keyset contains one or more keys, used to encrypt or decrypt your data, and prim to use while encrypting new data. Keyset generation example: + ```shell tinkey create-keyset --key-template AES256_GCM --out-format json --out keyset.json ``` ### Key rotation + Use `tinkey` to rotate encryption keys in your existing keyset: + ```shell tinkey rotate-keyset --in keyset_v1.json --out keyset_v2.json --key-template AES256_GCM ``` diff --git a/docs/versioned_docs/version-1.0/30-administration/70-proxy.md b/docs/versioned_docs/version-1.0/30-administration/70-proxy.md index b57b93442..89573db5c 100644 --- a/docs/versioned_docs/version-1.0/30-administration/70-proxy.md +++ b/docs/versioned_docs/version-1.0/30-administration/70-proxy.md @@ -108,7 +108,6 @@ ngrok http 8000 Set `WOODPECKER_HOST` (for example in `docker-compose.yml`) to the ngrok URL (usually xxx.ngrok.io) and start the server. - ## Traefik To install the Woodpecker server behind a [Traefik](https://traefik.io/) load balancer, you must expose both the `http` and the `gRPC` ports. Here is a comprehensive example, considering you are running Traefik with docker swarm and want to do TLS termination and automatic redirection from http to https. @@ -168,7 +167,6 @@ services: - traefik.http.middlewares.woodpecker-grpc-redirect.redirectscheme.permanent=true - traefik.http.routers.woodpecker-grpc.middlewares=woodpecker-grpc-redirect@docker - volumes: woodpecker-server-data: driver: local diff --git a/docs/versioned_docs/version-1.0/30-administration/90-prometheus.md b/docs/versioned_docs/version-1.0/30-administration/90-prometheus.md index 20c0a5096..6cbd70733 100644 --- a/docs/versioned_docs/version-1.0/30-administration/90-prometheus.md +++ b/docs/versioned_docs/version-1.0/30-administration/90-prometheus.md @@ -11,7 +11,7 @@ scrape_configs: bearer_token: dummyToken... static_configs: - - targets: ['woodpecker.domain.com'] + - targets: ['woodpecker.domain.com'] ``` ## Authorization diff --git a/docs/versioned_docs/version-1.0/40-cli.md b/docs/versioned_docs/version-1.0/40-cli.md index 4829d84e1..e0d23f72a 100644 --- a/docs/versioned_docs/version-1.0/40-cli.md +++ b/docs/versioned_docs/version-1.0/40-cli.md @@ -28,7 +28,6 @@ woodpecker-cli [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] **--token, -t**="": server auth token - # COMMANDS ## pipeline, build @@ -338,19 +337,19 @@ execute a local pipeline **--network**="": external networks -**--pipeline-created**="": (default: 0) +**--pipeline-created**="": (default: 0) -**--pipeline-event**="": (default: manual) +**--pipeline-event**="": (default: manual) -**--pipeline-finished**="": (default: 0) +**--pipeline-finished**="": (default: 0) **--pipeline-link**="": -**--pipeline-number**="": (default: 0) +**--pipeline-number**="": (default: 0) -**--pipeline-parent**="": (default: 0) +**--pipeline-parent**="": (default: 0) -**--pipeline-started**="": (default: 0) +**--pipeline-started**="": (default: 0) **--pipeline-status**="": @@ -372,17 +371,17 @@ execute a local pipeline **--prev-commit-sha**="": -**--prev-pipeline-created**="": (default: 0) +**--prev-pipeline-created**="": (default: 0) **--prev-pipeline-event**="": -**--prev-pipeline-finished**="": (default: 0) +**--prev-pipeline-finished**="": (default: 0) **--prev-pipeline-link**="": -**--prev-pipeline-number**="": (default: 0) +**--prev-pipeline-number**="": (default: 0) -**--prev-pipeline-started**="": (default: 0) +**--prev-pipeline-started**="": (default: 0) **--prev-pipeline-status**="": @@ -402,11 +401,11 @@ execute a local pipeline **--server, -s**="": server address -**--step-name**="": (default: 0) +**--step-name**="": (default: 0) -**--system-link**="": (default: https://github.com/woodpecker-ci/woodpecker) +**--system-link**="": (default: https://github.com/woodpecker-ci/woodpecker) -**--system-name**="": (default: woodpecker) +**--system-name**="": (default: woodpecker) **--system-platform**="": @@ -416,13 +415,13 @@ execute a local pipeline **--volumes**="": pipeline volumes -**--workflow-name**="": (default: 0) +**--workflow-name**="": (default: 0) -**--workflow-number**="": (default: 0) +**--workflow-number**="": (default: 0) -**--workspace-base**="": (default: /woodpecker) +**--workspace-base**="": (default: /woodpecker) -**--workspace-path**="": (default: src) +**--workspace-path**="": (default: src) ## info diff --git a/docs/versioned_docs/version-1.0/80-downloads.md b/docs/versioned_docs/version-1.0/80-downloads.md index 662027354..6ad54f26a 100644 --- a/docs/versioned_docs/version-1.0/80-downloads.md +++ b/docs/versioned_docs/version-1.0/80-downloads.md @@ -17,7 +17,7 @@ Image variants: - The `vX.X` images are based on the current release branch (e.g. `release/v1.0`) and can be used to get bugfixes asap - The `next` images are based on the current `main` branch and should not be used for production environments -``` bash +```bash # server docker pull woodpeckerci/woodpecker-server:latest docker pull woodpeckerci/woodpecker-server:latest-alpine diff --git a/docs/versioned_docs/version-1.0/91-migrations.md b/docs/versioned_docs/version-1.0/91-migrations.md index 556d4f5b5..53e15df36 100644 --- a/docs/versioned_docs/version-1.0/91-migrations.md +++ b/docs/versioned_docs/version-1.0/91-migrations.md @@ -19,7 +19,7 @@ No breaking changes - Updated Prometheus gauge `build_*` to `pipeline_*` - Updated Prometheus gauge `*_job_*` to `*_step_*` - Renamed config env `WOODPECKER_MAX_PROCS` to `WOODPECKER_MAX_WORKFLOWS` (still available as fallback) -- The pipelines are now also read from `.yaml` files, the new default order is `.woodpecker/*.yml` and `.woodpecker/*.yaml` (without any prioritization) -> `.woodpecker.yml` -> `.woodpecker.yaml` +- The pipelines are now also read from `.yaml` files, the new default order is `.woodpecker/*.yml` and `.woodpecker/*.yaml` (without any prioritization) -> `.woodpecker.yml` -> `.woodpecker.yaml` - Dropped support for [Coding](https://coding.net/), [Gogs](https://gogs.io) and Bitbucket Server (Stash). - `/api/queue/resume` & `/api/queue/pause` endpoint methods were changed from `GET` to `POST` - rename `pipeline:` key in your workflow config to `steps:` @@ -54,12 +54,14 @@ No breaking changes - Dropped support for `DRONE_*` environment variables. The according `WOODPECKER_*` variables must be used instead. Additionally some alternative namings have been removed to simplify maintenance: + - `WOODPECKER_AGENT_SECRET` replaces `WOODPECKER_SECRET`, `DRONE_SECRET`, `WOODPECKER_PASSWORD`, `DRONE_PASSWORD` and `DRONE_AGENT_SECRET`. - `WOODPECKER_HOST` replaces `DRONE_HOST` and `DRONE_SERVER_HOST`. - `WOODPECKER_DATABASE_DRIVER` replaces `DRONE_DATABASE_DRIVER` and `DATABASE_DRIVER`. - `WOODPECKER_DATABASE_DATASOURCE` replaces `DRONE_DATABASE_DATASOURCE` and `DATABASE_CONFIG`. - Dropped support for `DRONE_*` environment variables in pipeline steps. Pipeline meta-data can be accessed with `CI_*` variables. + - `CI_*` prefix replaces `DRONE_*` - `CI` value is now `woodpecker` - `DRONE=true` has been removed @@ -89,6 +91,7 @@ No breaking changes - Default workspace base path has moved from `/drone` to `/woodpecker` - Default SQLite database location has changed: + - `/var/lib/drone/drone.sqlite` -> `/var/lib/woodpecker/woodpecker.sqlite` - `drone.sqlite` -> `woodpecker.sqlite` @@ -107,7 +110,7 @@ No breaking changes - `WOODPECKER_DEBUG` option for server and agent got removed in favor of `WOODPECKER_LOG_LEVEL=debug` -- Remove unused server flags which can safely be removed from your server config: `WOODPECKER_QUIC`, `WOODPECKER_GITHUB_SCOPE`, `WOODPECKER_GITHUB_GIT_USERNAME`, `WOODPECKER_GITHUB_GIT_PASSWORD`, `WOODPECKER_GITHUB_PRIVATE_MODE`, `WOODPECKER_GITEA_GIT_USERNAME`, `WOODPECKER_GITEA_GIT_PASSWORD`, `WOODPECKER_GITEA_PRIVATE_MODE`, `WOODPECKER_GITLAB_GIT_USERNAME`, `WOODPECKER_GITLAB_GIT_PASSWORD`, `WOODPECKER_GITLAB_PRIVATE_MODE` +- Remove unused server flags which can safely be removed from your server config: `WOODPECKER_QUIC`, `WOODPECKER_GITHUB_SCOPE`, `WOODPECKER_GITHUB_GIT_USERNAME`, `WOODPECKER_GITHUB_GIT_PASSWORD`, `WOODPECKER_GITHUB_PRIVATE_MODE`, `WOODPECKER_GITEA_GIT_USERNAME`, `WOODPECKER_GITEA_GIT_PASSWORD`, `WOODPECKER_GITEA_PRIVATE_MODE`, `WOODPECKER_GITLAB_GIT_USERNAME`, `WOODPECKER_GITLAB_GIT_PASSWORD`, `WOODPECKER_GITLAB_PRIVATE_MODE` - Dropped support for manually setting the agents platform with `WOODPECKER_PLATFORM`. The platform is now automatically detected. diff --git a/docs/versioned_docs/version-1.0/92-development/01-getting-started.md b/docs/versioned_docs/version-1.0/92-development/01-getting-started.md index 748c27710..dbb94be5b 100644 --- a/docs/versioned_docs/version-1.0/92-development/01-getting-started.md +++ b/docs/versioned_docs/version-1.0/92-development/01-getting-started.md @@ -27,9 +27,10 @@ Install Golang (>=1.20) as described by [this guide](https://go.dev/doc/install) > GNU Make is a tool which controls the generation of executables and other non-source files of a program from the program's source files. (https://www.gnu.org/software/make/) Install make on: - - Ubuntu: `apt install make` - [Docs](https://wiki.ubuntuusers.de/Makefile/) - - [Windows](https://stackoverflow.com/a/32127632/8461267) - - Mac OS: `brew install make` + +- Ubuntu: `apt install make` - [Docs](https://wiki.ubuntuusers.de/Makefile/) +- [Windows](https://stackoverflow.com/a/32127632/8461267) +- Mac OS: `brew install make` ### Install Node.js & pnpm diff --git a/docs/versioned_docs/version-1.0/92-development/03-ui.md b/docs/versioned_docs/version-1.0/92-development/03-ui.md index b58ba1ed7..664749b07 100644 --- a/docs/versioned_docs/version-1.0/92-development/03-ui.md +++ b/docs/versioned_docs/version-1.0/92-development/03-ui.md @@ -3,6 +3,7 @@ To develop the UI you need to install [Node.js and pnpm](./01-getting-started.md#install-nodejs--pnpm). In addition it is recommended to use VS-Code with the recommended plugin selection to get features like auto-formatting, linting and typechecking. The UI is written with [Vue 3](https://v3.vuejs.org/) as Single-Page-Application accessing the Woodpecker REST api. ## Setup + The UI code is placed in `web/`. Change to that folder in your terminal with `cd web/` and install all dependencies by running `pnpm install`. For production builds the generated UI code is integrated into the Woodpecker server by using [go-embed](https://pkg.go.dev/embed). Testing UI changes would require us to rebuild the UI after each adjustment to the code by running `pnpm build` and restarting the Woodpecker server. To avoid this you can make use of the dev-proxy integrated into the Woodpecker server. This integrated dev-proxy will forward all none api request to a separate http-server which will only serve the UI files. @@ -32,7 +33,7 @@ The following list contains some tools and frameworks used by the Woodpecker UI. ## Messages and Translations -Woodpecker uses [Vue I18n](https://vue-i18n.intlify.dev/) as translation library. New translations have to be added to `web/src/assets/locales/en.json`. The English source file will be automatically imported into [Weblate](https://translate.woodpecker-ci.org/) (the translation system used by Woodpecker) where all other languages will be translated by the community based on the English source. +Woodpecker uses [Vue I18n](https://vue-i18n.intlify.dev/) as translation library. New translations have to be added to `web/src/assets/locales/en.json`. The English source file will be automatically imported into [Weblate](https://translate.woodpecker-ci.org/) (the translation system used by Woodpecker) where all other languages will be translated by the community based on the English source. You must not provide translations except English in PRs, otherwise weblate could put git into conflicts (when someone has translated in that language file and changes are not into master branch yet) For more information about translations see [Translations](./07-translations.md). diff --git a/docs/versioned_docs/version-1.0/92-development/05-architecture.md b/docs/versioned_docs/version-1.0/92-development/05-architecture.md index d0093e24a..466ab2171 100644 --- a/docs/versioned_docs/version-1.0/92-development/05-architecture.md +++ b/docs/versioned_docs/version-1.0/92-development/05-architecture.md @@ -8,36 +8,36 @@ ### main package hierarchy -| package | meaning | imports -|------------|--------------------------------------------------------------|---------- -| `cmd/**` | parse command-line args & environment to stat server/cli/agent | all other -| `agent/**` | code only agent (remote worker) will need | `pipeline`, `shared` -| `cli/**` | code only cli tool does need | `pipeline`, `shared`, `woodpecker-go` -| `server/**`| code only server will need | `pipeline`, `shared` -| `shared/**`| code shared for all three main tools (go help utils) | only std and external libs -| `woodpecker-go/**` | go client for server rest api | std +| package | meaning | imports | +| ------------------ | -------------------------------------------------------------- | ------------------------------------- | +| `cmd/**` | parse command-line args & environment to stat server/cli/agent | all other | +| `agent/**` | code only agent (remote worker) will need | `pipeline`, `shared` | +| `cli/**` | code only cli tool does need | `pipeline`, `shared`, `woodpecker-go` | +| `server/**` | code only server will need | `pipeline`, `shared` | +| `shared/**` | code shared for all three main tools (go help utils) | only std and external libs | +| `woodpecker-go/**` | go client for server rest api | std | ### Server -| package | meaning | imports -|----------------------|-------------------------------------------------------------------------------------|---------- -| `server/api/**` | handle web requests from `server/router` | `pipeline`, `../badges`, `../ccmenue`, `../logging`, `../model`, `../pubsub`, `../queue`, `../forge`, `../shared`, `../store`, `shared`, (TODO: mv `server/router/middleware/session`) -| `server/badges/**` | generate svg badges for pipelines | `../model` -| `server/ccmenu/**` | generate xml ccmenu for pipelines | `../model` -| `server/grpc/**` | gRPC server agents can connect to | `pipeline/rpc/**`, `../logging`, `../model`, `../pubsub`, `../queue`, `../forge`, `../pipeline`, `../store` -| `server/logging/**` | logging lib for gPRC server to stream logs while running | std -| `server/model/**` | structs for store (db) and api (json) | std -| `server/plugins/**` | plugins for server | `../model`, `../forge` -| `server/pipeline/**` | orchestrate pipelines | `pipeline`, `../model`, `../pubsub`, `../queue`, `../forge`, `../store`, `../plugins` -| `server/pubsub/**` | pubsub lib for server to push changes to the WebUI | std -| `server/queue/**` | queue lib for server where agents pull new pipelines from via gRPC | `server/model` -| `server/forge/**` | forge lib for server to connect and handle forge specific stuff | `shared`, `server/model` -| `server/router/**` | handle requests to REST API (and all middleware) and serve UI and WebUI config | `shared`, `../api`, `../model`, `../forge`, `../store`, `../web` -| `server/store/**` | handle database | `server/model` +| package | meaning | imports | +| -------------------- | ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `server/api/**` | handle web requests from `server/router` | `pipeline`, `../badges`, `../ccmenue`, `../logging`, `../model`, `../pubsub`, `../queue`, `../forge`, `../shared`, `../store`, `shared`, (TODO: mv `server/router/middleware/session`) | +| `server/badges/**` | generate svg badges for pipelines | `../model` | +| `server/ccmenu/**` | generate xml ccmenu for pipelines | `../model` | +| `server/grpc/**` | gRPC server agents can connect to | `pipeline/rpc/**`, `../logging`, `../model`, `../pubsub`, `../queue`, `../forge`, `../pipeline`, `../store` | +| `server/logging/**` | logging lib for gPRC server to stream logs while running | std | +| `server/model/**` | structs for store (db) and api (json) | std | +| `server/plugins/**` | plugins for server | `../model`, `../forge` | +| `server/pipeline/**` | orchestrate pipelines | `pipeline`, `../model`, `../pubsub`, `../queue`, `../forge`, `../store`, `../plugins` | +| `server/pubsub/**` | pubsub lib for server to push changes to the WebUI | std | +| `server/queue/**` | queue lib for server where agents pull new pipelines from via gRPC | `server/model` | +| `server/forge/**` | forge lib for server to connect and handle forge specific stuff | `shared`, `server/model` | +| `server/router/**` | handle requests to REST API (and all middleware) and serve UI and WebUI config | `shared`, `../api`, `../model`, `../forge`, `../store`, `../web` | +| `server/store/**` | handle database | `server/model` | | `server/shared/**` | TODO: move and split [#974](https://github.com/woodpecker-ci/woodpecker/issues/974) | | `server/web/**` | server SPA | -* `../` = `server/` +- `../` = `server/` ### Agent diff --git a/docs/versioned_docs/version-1.0/92-development/06-guides.md b/docs/versioned_docs/version-1.0/92-development/06-guides.md index 3752cd2ad..2e4644c5d 100644 --- a/docs/versioned_docs/version-1.0/92-development/06-guides.md +++ b/docs/versioned_docs/version-1.0/92-development/06-guides.md @@ -7,7 +7,7 @@ You can find its documentation at [gobook.io/read/gitea.com/xorm](https://gobook ## Add a new migration -Woodpecker uses migrations to change the database schema if a database model has been changed. If for example a developer removes a property `Counter` from the model `Repo` in `server/model/` they would need to add a new migration task like the following example to a file like `server/store/datastore/migration/004_repos_drop_repo_counter.go`: +Woodpecker uses migrations to change the database schema if a database model has been changed. If for example a developer removes a property `Counter` from the model `Repo` in `server/model/` they would need to add a new migration task like the following example to a file like `server/store/datastore/migration/004_repos_drop_repo_counter.go`: ```go package migration diff --git a/docs/versioned_docs/version-1.0/92-development/08-swagger.md b/docs/versioned_docs/version-1.0/92-development/08-swagger.md index 6d8d99f6f..4dd7250a7 100644 --- a/docs/versioned_docs/version-1.0/92-development/08-swagger.md +++ b/docs/versioned_docs/version-1.0/92-development/08-swagger.md @@ -39,12 +39,13 @@ type User struct { ``` These guidelines aim to have consistent wording in the swagger doc: -* first word after `@Summary` and `@Summary` are always uppercase -* `@Summary` has no . (dot) at the end of the line -* model structs shall use custom short names, to ease life for API consumers, using `@name` -* `@Success` object or array declarations shall be short, this means the actual `model.User` struct must have a `@name` annotation, so that the model can be renderend in Swagger -* when pagination is used, `@Parame page` and `@Parame perPage` must be added manually -* `@Param Authorization` is almost always present, there are just a few un-protected endpoints + +- first word after `@Summary` and `@Summary` are always uppercase +- `@Summary` has no . (dot) at the end of the line +- model structs shall use custom short names, to ease life for API consumers, using `@name` +- `@Success` object or array declarations shall be short, this means the actual `model.User` struct must have a `@name` annotation, so that the model can be renderend in Swagger +- when pagination is used, `@Parame page` and `@Parame perPage` must be added manually +- `@Param Authorization` is almost always present, there are just a few un-protected endpoints There are many examples in the server/api package, which you can use a blueprint. More enhanced information you can find here https://github.com/swaggo/swag/blob/master/README.md#declarative-comments-format diff --git a/docs/versioned_docs/version-2.4/10-intro.md b/docs/versioned_docs/version-2.4/10-intro.md deleted file mode 100644 index 276dcb000..000000000 --- a/docs/versioned_docs/version-2.4/10-intro.md +++ /dev/null @@ -1,89 +0,0 @@ -# Welcome to Woodpecker - -Woodpecker is a simple yet powerful CI/CD engine with great extensibility. It focuses on executing pipelines inside [containers](https://opencontainers.org/). -If you are already using containers in your daily workflow, you'll for sure love Woodpecker. - -![woodpecker](woodpecker.png) - -## `.woodpecker.yaml` - -- Place your pipeline in a file named `.woodpecker.yaml` in your repository -- Pipeline steps can be named as you like -- Run any command in the commands section - -```yaml title=".woodpecker.yaml" -steps: - - name: build - image: debian - commands: - - echo "This is the build step" - - name: a-test-step - image: debian - commands: - - echo "Testing.." -``` - -### Steps are containers - -- Define any container image as context - - either use your own and install the needed tools in a custom image - - or search for available images that are already tailored for your needs in image registries like [Docker Hub](https://hub.docker.com/search?type=image) -- List the commands that should be executed in the container - -```diff - steps: - - name: build -- image: debian -+ image: mycompany/image-with-awscli - commands: - - aws help -``` - -### File changes are incremental - -- Woodpecker clones the source code in the beginning -- File changes are persisted throughout individual steps as the same volume is being mounted in all steps - -```yaml title=".woodpecker.yaml" -steps: - - name: build - image: debian - commands: - - touch myfile - - name: a-test-step - image: debian - commands: - - cat myfile -``` - -## Plugins are straightforward - -- If you copy the same shell script from project to project -- Pack it into a plugin instead -- And make the yaml declarative -- Plugins are Docker images with your script as an entrypoint - -```dockerfile title="Dockerfile" -FROM laszlocloud/kubectl -COPY deploy /usr/local/deploy -ENTRYPOINT ["/usr/local/deploy"] -``` - -```bash title="deploy" -kubectl apply -f $PLUGIN_TEMPLATE -``` - -```yaml title=".woodpecker.yaml" -steps: - deploy-to-k8s: - image: laszlocloud/my-k8s-plugin - settings: - template: config/k8s/service.yaml -``` - -See [plugin docs](./20-usage/51-plugins/10-overview.md). - -## Continue reading - -- [Create a Woodpecker pipeline for your repository](./20-usage/10-intro.md) -- [Setup your own Woodpecker instance](./30-administration/00-deployment/00-overview.md) diff --git a/docs/versioned_docs/version-2.4/20-usage/10-intro.md b/docs/versioned_docs/version-2.4/20-usage/10-intro.md deleted file mode 100644 index 477466173..000000000 --- a/docs/versioned_docs/version-2.4/20-usage/10-intro.md +++ /dev/null @@ -1,72 +0,0 @@ -# Getting started - -## Repository Activation - -To activate your project navigate to your account settings. You will see a list of repositories which can be activated with a simple toggle. When you activate your repository, Woodpecker automatically adds webhooks to your forge (e.g. GitHub, Gitea, ...). - -Webhooks are used to trigger pipeline executions. When you push code to your repository, open a pull request, or create a tag, your forge will automatically send a webhook to Woodpecker which will in turn trigger the pipeline execution. - -![repository list](repo-list.png) - -## Required Permissions - -The user who enables a repo in Woodpecker must have `Admin` rights on that repo, so that Woodpecker can add the webhook. - -:::note -Note that manually creating webhooks yourself is not possible. -This is because webhooks are signed using a per-repository secret key which is not exposed to end users. -::: - -## Configuration - -To configure your pipeline you must create a `.woodpecker.yaml` file in the root of your repository. The `.woodpecker.yaml` file is used to define your pipeline steps. - -:::note -We support most of YAML 1.2, but preserve some behavior from 1.1 for backward compatibility. -Read more at: [https://github.com/go-yaml/yaml](https://github.com/go-yaml/yaml/tree/v3) -::: - -Example pipeline configuration: - -```yaml -steps: - - name: build - image: golang - commands: - - go get - - go build - - go test - -services: - - name: postgres - image: postgres:9.4.5 - environment: - - POSTGRES_USER=myapp -``` - -Example pipeline configuration with multiple, serial steps: - -```yaml -steps: - - name: backend - image: golang - commands: - - go get - - go build - - go test - - - name: frontend - image: node:6 - commands: - - npm install - - npm test - - - name: notify - image: plugins/slack - channel: developers - username: woodpecker -``` - -## Execution - -To trigger your first pipeline execution you can push code to your repository, open a pull request, or push a tag. Any of these events triggers a webhook from your forge and execute your pipeline. diff --git a/docs/versioned_docs/version-2.4/20-usage/repo-list.png b/docs/versioned_docs/version-2.4/20-usage/repo-list.png deleted file mode 100644 index b47380087..000000000 Binary files a/docs/versioned_docs/version-2.4/20-usage/repo-list.png and /dev/null differ diff --git a/docs/versioned_docs/version-2.4/30-administration/00-deployment/00-overview.md b/docs/versioned_docs/version-2.4/30-administration/00-deployment/00-overview.md deleted file mode 100644 index b2b6dadfd..000000000 --- a/docs/versioned_docs/version-2.4/30-administration/00-deployment/00-overview.md +++ /dev/null @@ -1,90 +0,0 @@ -# Deployment - -A Woodpecker deployment consists of two parts: - -- A server which is the heart of Woodpecker and ships the web interface. -- Next to one server, you can deploy any number of agents which will run the pipelines. - -Each agent is able to process one pipeline step by default. -If you have four agents installed and connected to the Woodpecker server, your system will process four workflows in parallel. - -:::tip -You can add more agents to increase the number of parallel workflows or set the agent's `WOODPECKER_MAX_WORKFLOWS=1` environment variable to increase the number of parallel workflows for that agent. -::: - -## Which version of Woodpecker should I use? - -Woodpecker is having two different kinds of releases: **stable** and **next**. - -### Stable releases - -We release a new version every four weeks and will release the current state of the `main` branch. -If there are security fixes or critical bug fixes, we'll release them directly. -There are no backports or similar. - -#### Versioning - -We use [Semantic Versioning](https://semver.org/) to be able, -to communicate when admins have to do manual migration steps and when they can just bump versions up. - -#### Breaking changes - -As of semver guidelines, breaking changes will be released as a major version. We will hold back -breaking changes to not release many majors each containing just a few breaking changes. -Prior to the release of a major version, a release candidate (RC) will be published to allow easy testing, -the actual release will be about a week later. - -## Hardware Requirements - -Below are minimal resources requirements for Woodpecker components itself: - -| Component | Memory | CPU | -| --------- | ------ | --- | -| Server | 200 MB | 1 | -| Agent | 32 MB | 1 | - -Note, that those values do not include the operating system or workload (pipelines execution) resources consumption. - -In addition you need at least some kind of database which requires additional resources depending on the selected database system. - -## Installation - -You can install Woodpecker on multiple ways: - -- Using [docker-compose](./10-docker-compose.md) with the official [container images](./10-docker-compose.md#docker-images) -- Using [Kubernetes](./20-kubernetes.md) via the Woodpecker Helm chart -- Using binaries, DEBs or RPMs you can download from [latest release](https://github.com/woodpecker-ci/woodpecker/releases/latest) - -## Authentication - -Authentication is done using OAuth and is delegated to your forge which is configured using environment variables. - -See the complete reference for all supported forges [here](../11-forges/10-overview.md). - -## Database - -By default Woodpecker uses a SQLite database which requires zero installation or configuration. See the [database settings](../30-database.md) page to further configure it or use MySQL or Postgres. - -## SSL - -Woodpecker supports SSL configuration by using Let's encrypt or by using own certificates. See the [SSL guide](../60-ssl.md). You can also put it behind a [reverse proxy](#behind-a-proxy) - -## Metrics - -A [Prometheus endpoint](../90-prometheus.md) is exposed. - -## Behind a proxy - -See the [proxy guide](../70-proxy.md) if you want to see a setup behind Apache, Nginx, Caddy or ngrok. - -In the case you need to use Woodpecker with a URL path prefix (like: ), add the root path to [`WOODPECKER_HOST`](../10-server-config.md#woodpecker_host). - -## Third-party installation methods - -:::info -These installation methods are not officially supported. If you experience issues with them, please open issues in the specific repositories. -::: - -- Using [NixOS](./30-nixos.md) via the [NixOS module](https://search.nixos.org/options?channel=unstable&size=200&sort=relevance&query=woodpecker) -- [Using YunoHost](https://apps.yunohost.org/app/woodpecker) -- [On Cloudron](https://www.cloudron.io/store/org.woodpecker_ci.cloudronapp.html) diff --git a/docs/versioned_docs/version-2.4/30-administration/11-forges/10-overview.md b/docs/versioned_docs/version-2.4/30-administration/11-forges/10-overview.md deleted file mode 100644 index 4446896f0..000000000 --- a/docs/versioned_docs/version-2.4/30-administration/11-forges/10-overview.md +++ /dev/null @@ -1,13 +0,0 @@ -# Forges - -## Supported features - -| Feature | [GitHub](20-github.md) | [Gitea / Forgejo](30-gitea.md) | [Gitlab](40-gitlab.md) | [Bitbucket](50-bitbucket.md) | [Bitbucket Datacenter](60-bitbucket_datacenter.md) | -| ------------------------------------------------------------- | :--------------------: | :----------------------------: | :--------------------: | :--------------------------: | :------------------------------------------------: | -| Event: Push | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| Event: Tag | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| Event: Pull-Request | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| Event: Release | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :x: | -| Event: Deploy | :white_check_mark: | :x: | :x: | :x: | :x: | -| [Multiple workflows](../../20-usage/25-workflows.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| [when.path filter](../../20-usage/20-workflow-syntax.md#path) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :x: | diff --git a/docs/versioned_docs/version-2.4/30-administration/75-addons/00-overview.md b/docs/versioned_docs/version-2.4/30-administration/75-addons/00-overview.md deleted file mode 100644 index 747dc4b36..000000000 --- a/docs/versioned_docs/version-2.4/30-administration/75-addons/00-overview.md +++ /dev/null @@ -1,40 +0,0 @@ -# Addons - -:::warning -Addons are still experimental. Their implementation can change and break at any time. -::: - -:::danger -You need to trust the author of the addons you use. Depending on their type, addons can access forge authentication codes, your secrets or other sensitive information. -::: - -To adapt Woodpecker to your needs beyond the [configuration](../10-server-config.md), Woodpecker has its own **addon** system, built ontop of [Go's internal plugin system](https://go.dev/pkg/plugin). - -Addons can be used for: - -- Forges - -## Restrictions - -Addons are restricted by how Go plugins work. This includes the following restrictions: - -- only supported on Linux, FreeBSD, and macOS -- addons must have been built for the correct Woodpecker version. If an addon is not provided specifically for this version, you likely won't be able to use it. - -## Usage - -To use an addon, download the addon version built for your Woodpecker version. Then, you can add the following to your configuration: - -```ini -WOODPECKER_ADDONS=/path/to/your/addon/file.so -``` - -In case you run Woodpecker as container, you probably want to mount the addon binaries to `/opt/addons/`. - -You can list multiple addons, Woodpecker will automatically determine their type. If you specify multiple addons with the same type, only the first one will be used. - -Using an addon always overwrites Woodpecker's internal setup. This means, that a forge addon will be used if specified, no matter what's configured for the forges natively supported by Woodpecker. - -### Bug reports - -If you experience bugs, please check which component has the issue. If it's the addon, **do not raise an issue in the main repository**, but rather use the separate addon repositories. To check which component is responsible for the bug, look at the logs. Logs from addons are marked with a special field `addon` containing their addon file name. diff --git a/docs/versioned_docs/version-2.4/30-administration/75-addons/20-creating-addons.md b/docs/versioned_docs/version-2.4/30-administration/75-addons/20-creating-addons.md deleted file mode 100644 index 283c456f4..000000000 --- a/docs/versioned_docs/version-2.4/30-administration/75-addons/20-creating-addons.md +++ /dev/null @@ -1,97 +0,0 @@ -# Creating addons - -Addons are written in Go. - -## Writing your code - -An addon consists of two variables/functions in Go. - -1. The `Type` variable. Specifies the type of the addon and must be directly accessed from `shared/addons/types/types.go`. -2. The `Addon` function which is the main point of your addon. - This function takes the `zerolog` logger you should use to log errors, warnings, etc. as argument. - - It returns two values: - - 1. The actual addon. For type reference see [table below](#return-types). - 2. An error. If this error is not `nil`, Woodpecker exits. - -Directly import Woodpecker's Go package (`go.woodpecker-ci.org/woodpecker/woodpecker/v2`) and use the interfaces and types defined there. - -### Return types - -| Addon type | Return type | -| ---------- | -------------------------------------------------------------------- | -| `Forge` | `"go.woodpecker-ci.org/woodpecker/woodpecker/v2/server/forge".Forge` | - -### Using configurations - -If you write a plugin for the server (`Forge` and the services), you can access the server config. - -Therefore, use the `"go.woodpecker-ci.org/woodpecker/v2/server".Config` variable. - -:::warning -The config is not available when your addon is initialized, i.e., the `Addon` function is called. -Only use the config in the interface methods. -::: - -## Compiling - -After you write your addon code, compile your addon: - -```sh -go build -buildmode plugin -``` - -The output file is your addon that is now ready to be used. - -## Restrictions - -Addons must directly depend on Woodpecker's core (`go.woodpecker-ci.org/woodpecker/woodpecker/v2`). -The addon must have been built with **exactly the same code** as the Woodpecker instance you'd like to use it on. This means: If you build your addon with a specific commit from Woodpecker `next`, you can likely only use it with the Woodpecker version compiled from this commit. -Also, if you change something inside Woodpecker without committing, it might fail because you need to recompile your addon with this code first. - -In addition to this, addons are only supported on Linux, FreeBSD, and macOS. - -:::info -It is recommended to at least support the latest version of Woodpecker. -::: - -### Compile for different versions - -As long as there are no changes to Woodpecker's interfaces, -or they are backwards-compatible, you can compile the addon for multiple versions -by changing the version of `go.woodpecker-ci.org/woodpecker/woodpecker/v2` using `go get` before compiling. - -## Logging - -The entrypoint receives a `zerolog.Logger` as input. **Do not use any other logging solution.** This logger follows the configuration of the Woodpecker instance and adds a special field `addon` to the log entries which allows users to find out which component is writing the log messages. - -## Example structure - -```go -package main - -import ( - "context" - "net/http" - - "github.com/rs/zerolog" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - addon_types "go.woodpecker-ci.org/woodpecker/v2/shared/addon/types" -) - -var Type = addon_types.TypeForge - -func Addon(logger zerolog.Logger) (forge.Forge, error) { - logger.Info().Msg("hello world from addon") - return &config{l: logger}, nil -} - -type config struct { - l zerolog.Logger -} - -// In this case, `config` must implement `forge.Forge`. You must directly use Woodpecker's packages - see imports above. -``` diff --git a/docs/versioned_docs/version-2.4/40-cli.md b/docs/versioned_docs/version-2.4/40-cli.md deleted file mode 100644 index 639c2ebfb..000000000 --- a/docs/versioned_docs/version-2.4/40-cli.md +++ /dev/null @@ -1,595 +0,0 @@ -# CLI - -# NAME - -woodpecker-cli - A new cli application - -# SYNOPSIS - -woodpecker-cli - -``` -[--config|-c]=[value] -[--disable-update-check] -[--log-file]=[value] -[--log-level]=[value] -[--nocolor] -[--pretty] -[--server|-s]=[value] -[--token|-t]=[value] -``` - -# DESCRIPTION - -Woodpecker command line utility - -**Usage**: - -``` -woodpecker-cli [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] -``` - -# GLOBAL OPTIONS - -**--config, -c**="": path to config file - -**--disable-update-check**: disable update check - -**--log-file**="": Output destination for logs. 'stdout' and 'stderr' can be used as special keywords. (default: "stderr") - -**--log-level**="": set logging level (default: "info") - -**--nocolor**: disable colored debug output, only has effect if pretty output is set too - -**--pretty**: enable pretty-printed debug output - -**--server, -s**="": server address - -**--token, -t**="": server auth token - - -# COMMANDS - -## pipeline - -manage pipelines - -### ls - -show pipeline history - -**--branch**="": branch filter - -**--event**="": event filter - -**--format**="": format output (default: "\x1b[33mPipeline #{{ .Number }} \x1b[0m\nStatus: {{ .Status }}\nEvent: {{ .Event }}\nCommit: {{ .Commit }}\nBranch: {{ .Branch }}\nRef: {{ .Ref }}\nAuthor: {{ .Author }} {{ if .Email }}<{{.Email}}>{{ end }}\nMessage: {{ .Message }}\n") - -**--limit**="": limit the list size (default: 25) - -**--status**="": status filter - -### last - -show latest pipeline details - -**--branch**="": branch name (default: "main") - -**--format**="": format output (default: "Number: {{ .Number }}\nStatus: {{ .Status }}\nEvent: {{ .Event }}\nCommit: {{ .Commit }}\nBranch: {{ .Branch }}\nRef: {{ .Ref }}\nMessage: {{ .Message }}\nAuthor: {{ .Author }}\n") - -### logs - -show pipeline logs - -### info - -show pipeline details - -**--format**="": format output (default: "Number: {{ .Number }}\nStatus: {{ .Status }}\nEvent: {{ .Event }}\nCommit: {{ .Commit }}\nBranch: {{ .Branch }}\nRef: {{ .Ref }}\nMessage: {{ .Message }}\nAuthor: {{ .Author }}\n") - -### stop - -stop a pipeline - -### start - -start a pipeline - -**--param, -p**="": custom parameters to be injected into the step environment. Format: KEY=value - -### approve - -approve a pipeline - -### decline - -decline a pipeline - -### queue - -show pipeline queue - -**--format**="": format output (default: "\x1b[33m{{ .FullName }} #{{ .Number }} \x1b[0m\nStatus: {{ .Status }}\nEvent: {{ .Event }}\nCommit: {{ .Commit }}\nBranch: {{ .Branch }}\nRef: {{ .Ref }}\nAuthor: {{ .Author }} {{ if .Email }}<{{.Email}}>{{ end }}\nMessage: {{ .Message }}\n") - -### ps - -show pipeline steps - -**--format**="": format output (default: "\x1b[33mStep #{{ .PID }} \x1b[0m\nStep: {{ .Name }}\nState: {{ .State }}\n") - -### create - -create new pipeline - -**--branch**="": branch to create pipeline from - -**--format**="": format output (default: "\x1b[33mPipeline #{{ .Number }} \x1b[0m\nStatus: {{ .Status }}\nEvent: {{ .Event }}\nCommit: {{ .Commit }}\nBranch: {{ .Branch }}\nRef: {{ .Ref }}\nAuthor: {{ .Author }} {{ if .Email }}<{{.Email}}>{{ end }}\nMessage: {{ .Message }}\n") - -**--var**="": key=value - -## log - -manage logs - -### purge - -purge a log - -## deploy - -deploy code - -**--branch**="": branch filter (default: "main") - -**--event**="": event filter (default: "push") - -**--format**="": format output (default: "Number: {{ .Number }}\nStatus: {{ .Status }}\nCommit: {{ .Commit }}\nBranch: {{ .Branch }}\nRef: {{ .Ref }}\nMessage: {{ .Message }}\nAuthor: {{ .Author }}\nTarget: {{ .Deploy }}\n") - -**--param, -p**="": custom parameters to be injected into the step environment. Format: KEY=value - -**--status**="": status filter (default: "success") - -## exec - -execute a local pipeline - -**--backend-docker-api-version**="": the version of the API to reach, leave empty for latest. - -**--backend-docker-cert**="": path to load the TLS certificates for connecting to docker server - -**--backend-docker-host**="": path to docker socket or url to the docker server - -**--backend-docker-ipv6**: backend docker enable IPV6 - -**--backend-docker-network**="": backend docker network - -**--backend-docker-tls-verify**: enable or disable TLS verification for connecting to docker server - -**--backend-docker-volumes**="": backend docker volumes (comma separated) - -**--backend-engine**="": backend engine to run pipelines on (default: "auto-detect") - -**--backend-http-proxy**="": if set, pass the environment variable down as "HTTP_PROXY" to steps - -**--backend-https-proxy**="": if set, pass the environment variable down as "HTTPS_PROXY" to steps - -**--backend-k8s-namespace**="": backend k8s namespace (default: "woodpecker") - -**--backend-k8s-pod-annotations**="": backend k8s additional worker pod annotations - -**--backend-k8s-pod-image-pull-secret-names**="": backend k8s pull secret names for private registries (default: "regcred") - -**--backend-k8s-pod-labels**="": backend k8s additional worker pod labels - -**--backend-k8s-secctx-nonroot**: `run as non root` Kubernetes security context option - -**--backend-k8s-storage-class**="": backend k8s storage class - -**--backend-k8s-storage-rwx**: backend k8s storage access mode, should ReadWriteMany (RWX) instead of ReadWriteOnce (RWO) be used? (default: true) - -**--backend-k8s-volume-size**="": backend k8s volume size (default 10G) (default: "10G") - -**--backend-local-temp-dir**="": set a different temp dir to clone workflows into (default: "/tmp") - -**--backend-no-proxy**="": if set, pass the environment variable down as "NO_PROXY" to steps - -**--commit-author-avatar**="": - -**--commit-author-email**="": - -**--commit-author-name**="": - -**--commit-branch**="": - -**--commit-message**="": - -**--commit-ref**="": - -**--commit-refspec**="": - -**--commit-sha**="": - -**--env**="": - -**--forge-type**="": - -**--forge-url**="": - -**--local**: run from local directory - -**--netrc-machine**="": - -**--netrc-password**="": - -**--netrc-username**="": - -**--network**="": external networks - -**--pipeline-created**="": (default: 0) - -**--pipeline-event**="": (default: "manual") - -**--pipeline-finished**="": (default: 0) - -**--pipeline-number**="": (default: 0) - -**--pipeline-parent**="": (default: 0) - -**--pipeline-started**="": (default: 0) - -**--pipeline-status**="": - -**--pipeline-target**="": - -**--pipeline-url**="": - -**--prev-commit-author-avatar**="": - -**--prev-commit-author-email**="": - -**--prev-commit-author-name**="": - -**--prev-commit-branch**="": - -**--prev-commit-message**="": - -**--prev-commit-ref**="": - -**--prev-commit-refspec**="": - -**--prev-commit-sha**="": - -**--prev-pipeline-created**="": (default: 0) - -**--prev-pipeline-event**="": - -**--prev-pipeline-finished**="": (default: 0) - -**--prev-pipeline-number**="": (default: 0) - -**--prev-pipeline-started**="": (default: 0) - -**--prev-pipeline-status**="": - -**--prev-pipeline-url**="": - -**--privileged**="": privileged plugins (default: "plugins/docker", "plugins/gcr", "plugins/ecr", "woodpeckerci/plugin-docker-buildx", "codeberg.org/woodpecker-plugins/docker-buildx") - -**--repo**="": full repo name - -**--repo-clone-ssh-url**="": - -**--repo-clone-url**="": - -**--repo-path**="": path to local repository - -**--repo-private**="": - -**--repo-remote-id**="": - -**--repo-trusted**: - -**--repo-url**="": - -**--step-name**="": (default: 0) - -**--system-name**="": (default: "woodpecker") - -**--system-platform**="": - -**--system-url**="": (default: "https://github.com/woodpecker-ci/woodpecker") - -**--timeout**="": pipeline timeout (default: 1h0m0s) - -**--volumes**="": pipeline volumes - -**--workflow-name**="": (default: 0) - -**--workflow-number**="": (default: 0) - -**--workspace-base**="": (default: "/woodpecker") - -**--workspace-path**="": (default: "src") - -## info - -show information about the current user - -## registry - -manage registries - -### add - -adds a registry - -**--hostname**="": registry hostname (default: "docker.io") - -**--password**="": registry password - -**--repository, --repo**="": repository id or full-name (e.g. 134 or octocat/hello-world) - -**--username**="": registry username - -### rm - -remove a registry - -**--hostname**="": registry hostname (default: "docker.io") - -**--repository, --repo**="": repository id or full-name (e.g. 134 or octocat/hello-world) - -### update - -update a registry - -**--hostname**="": registry hostname (default: "docker.io") - -**--password**="": registry password - -**--repository, --repo**="": repository id or full-name (e.g. 134 or octocat/hello-world) - -**--username**="": registry username - -### info - -display registry info - -**--hostname**="": registry hostname (default: "docker.io") - -**--repository, --repo**="": repository id or full-name (e.g. 134 or octocat/hello-world) - -### ls - -list registries - -**--repository, --repo**="": repository id or full-name (e.g. 134 or octocat/hello-world) - -## secret - -manage secrets - -### add - -adds a secret - -**--event**="": secret limited to these events - -**--global**: global secret - -**--image**="": secret limited to these images - -**--name**="": secret name - -**--organization, --org**="": organization id or full-name (e.g. 123 or octocat) - -**--repository, --repo**="": repository id or full-name (e.g. 134 or octocat/hello-world) - -**--value**="": secret value - -### rm - -remove a secret - -**--global**: global secret - -**--name**="": secret name - -**--organization, --org**="": organization id or full-name (e.g. 123 or octocat) - -**--repository, --repo**="": repository id or full-name (e.g. 134 or octocat/hello-world) - -### update - -update a secret - -**--event**="": secret limited to these events - -**--global**: global secret - -**--image**="": secret limited to these images - -**--name**="": secret name - -**--organization, --org**="": organization id or full-name (e.g. 123 or octocat) - -**--repository, --repo**="": repository id or full-name (e.g. 134 or octocat/hello-world) - -**--value**="": secret value - -### info - -display secret info - -**--global**: global secret - -**--name**="": secret name - -**--organization, --org**="": organization id or full-name (e.g. 123 or octocat) - -**--repository, --repo**="": repository id or full-name (e.g. 134 or octocat/hello-world) - -### ls - -list secrets - -**--global**: global secret - -**--organization, --org**="": organization id or full-name (e.g. 123 or octocat) - -**--repository, --repo**="": repository id or full-name (e.g. 134 or octocat/hello-world) - -## repo - -manage repositories - -### ls - -list all repos - -**--format**="": format output (default: "\x1b[33m{{ .FullName }}\x1b[0m (id: {{ .ID }}, forgeRemoteID: {{ .ForgeRemoteID }})") - -**--org**="": filter by organization - -### info - -show repository details - -**--format**="": format output (default: "Owner: {{ .Owner }}\nRepo: {{ .Name }}\nURL: {{ .ForgeURL }}\nConfig path: {{ .Config }}\nVisibility: {{ .Visibility }}\nPrivate: {{ .IsSCMPrivate }}\nTrusted: {{ .IsTrusted }}\nGated: {{ .IsGated }}\nClone url: {{ .Clone }}\nAllow pull-requests: {{ .AllowPullRequests }}\n") - -### add - -add a repository - -### update - -update a repository - -**--config**="": repository configuration path (e.g. .woodpecker.yml) - -**--gated**: repository is gated - -**--pipeline-counter**="": repository starting pipeline number (default: 0) - -**--timeout**="": repository timeout (default: 0s) - -**--trusted**: repository is trusted - -**--unsafe**: validate updating the pipeline-counter is unsafe - -**--visibility**="": repository visibility - -### rm - -remove a repository - -### repair - -repair repository webhooks - -### chown - -assume ownership of a repository - -### sync - -synchronize the repository list - -**--format**="": format output (default: "\x1b[33m{{ .FullName }}\x1b[0m (id: {{ .ID }}, forgeRemoteID: {{ .ForgeRemoteID }})") - -## user - -manage users - -### ls - -list all users - -**--format**="": format output (default: "{{ .Login }}") - -### info - -show user details - -**--format**="": format output (default: "User: {{ .Login }}\nEmail: {{ .Email }}") - -### add - -adds a user - -### rm - -remove a user - -## lint - -lint a pipeline configuration file - -## log-level - -get the logging level of the server, or set it with [level] - -## cron - -manage cron jobs - -### add - -add a cron job - -**--branch**="": cron branch - -**--name**="": cron name - -**--repository, --repo**="": repository id or full-name (e.g. 134 or octocat/hello-world) - -**--schedule**="": cron schedule - -### rm - -remove a cron job - -**--id**="": cron id - -**--repository, --repo**="": repository id or full-name (e.g. 134 or octocat/hello-world) - -### update - -update a cron job - -**--branch**="": cron branch - -**--id**="": cron id - -**--name**="": cron name - -**--repository, --repo**="": repository id or full-name (e.g. 134 or octocat/hello-world) - -**--schedule**="": cron schedule - -### info - -display info about a cron job - -**--id**="": cron id - -**--repository, --repo**="": repository id or full-name (e.g. 134 or octocat/hello-world) - -### ls - -list cron jobs - -**--repository, --repo**="": repository id or full-name (e.g. 134 or octocat/hello-world) - -## setup - -setup the woodpecker-cli for the first time - -**--server-url**="": The URL of the woodpecker server - -**--token**="": The token to authenticate with the woodpecker server - -## update - -update the woodpecker-cli to the latest version - -**--force**: force update even if the latest version is already installed diff --git a/docs/versioned_docs/version-2.4/91-migrations.md b/docs/versioned_docs/version-2.4/91-migrations.md deleted file mode 100644 index 9b6fa8854..000000000 --- a/docs/versioned_docs/version-2.4/91-migrations.md +++ /dev/null @@ -1,150 +0,0 @@ -# Migrations - -Some versions need some changes to the server configuration or the pipeline configuration files. - -## `next` - -- Deprecated `steps.[name].group` in favor of `steps.[name].depends_on` (see [workflow syntax](./20-usage/20-workflow-syntax.md#depends_on) to learn how to set dependencies) -- Removed `WOODPECKER_ROOT_PATH` and `WOODPECKER_ROOT_URL` config variables. Use `WOODPECKER_HOST` with a path instead -- Pipelines without a config file will now be skipped instead of failing -- Deprecated `includes` and `excludes` support from **event** filter -- Deprecated uppercasing all secret env vars, instead, the value of the `secrets` property is used. [Read more](./20-usage/40-secrets.md#use-secrets-in-commands) -- Deprecated alternative names for secrets, use `environment` with `from_secret` -- Deprecated slice definition for env vars - -## 2.0.0 - -- Dropped deprecated `CI_BUILD_*`, `CI_PREV_BUILD_*`, `CI_JOB_*`, `*_LINK`, `CI_SYSTEM_ARCH`, `CI_REPO_REMOTE` built-in environment variables -- Deprecated `platform:` filter in favor of `labels:`, [read more](./20-usage/20-workflow-syntax.md#filter-by-platform) -- Secrets `event` property was renamed to `events` and `image` to `images` as both are lists. The new property `events` / `images` has to be used in the api. The old properties `event` and `image` were removed. -- The secrets `plugin_only` option was removed. Secrets with images are now always only available for plugins using listed by the `images` property. Existing secrets with a list of `images` will now only be available to the listed images if they are used as a plugin. -- Removed `build` alias for `pipeline` command in CLI -- Removed `ssh` backend. Use an agent directly on the SSH machine using the `local` backend. -- Removed `/hook` and `/stream` API paths in favor of `/api/(hook|stream)`. You may need to use the "Repair repository" button in the repo settings or "Repair all" in the admin settings to recreate the forge hook. -- Removed `WOODPECKER_DOCS` config variable -- Renamed `link` to `url` (including all API fields) -- Deprecated `CI_COMMIT_URL` env var, use `CI_PIPELINE_FORGE_URL` - -## 1.0.0 - -- The signature used to verify extension calls (like those used for the [config-extension](./30-administration/100-external-configuration-api.md)) done by the Woodpecker server switched from using a shared-secret HMac to an ed25519 key-pair. Read more about it at the [config-extensions](./30-administration/100-external-configuration-api.md) documentation. -- Refactored support for old agent filter labels and expressions. Learn how to use the new [filter](./20-usage/20-workflow-syntax.md#labels) -- Renamed step environment variable `CI_SYSTEM_ARCH` to `CI_SYSTEM_PLATFORM`. Same applies for the cli exec variable. -- Renamed environment variables `CI_BUILD_*` and `CI_PREV_BUILD_*` to `CI_PIPELINE_*` and `CI_PREV_PIPELINE_*`, old ones are still available but deprecated -- Renamed environment variables `CI_JOB_*` to `CI_STEP_*`, old ones are still available but deprecated -- Renamed environment variable `CI_REPO_REMOTE` to `CI_REPO_CLONE_URL`, old is still available but deprecated -- Renamed environment variable `*_LINK` to `*_URL`, old ones are still available but deprecated -- Renamed API endpoints for pipelines (`//builds/` -> `//pipelines/`), old ones are still available but deprecated -- Updated Prometheus gauge `build_*` to `pipeline_*` -- Updated Prometheus gauge `*_job_*` to `*_step_*` -- Renamed config env `WOODPECKER_MAX_PROCS` to `WOODPECKER_MAX_WORKFLOWS` (still available as fallback) -- The pipelines are now also read from `.yaml` files, the new default order is `.woodpecker/*.yml` and `.woodpecker/*.yaml` (without any prioritization) -> `.woodpecker.yml` -> `.woodpecker.yaml` -- Dropped support for [Coding](https://coding.net/), [Gogs](https://gogs.io) and Bitbucket Server (Stash). -- `/api/queue/resume` & `/api/queue/pause` endpoint methods were changed from `GET` to `POST` -- rename `pipeline:` key in your workflow config to `steps:` -- If you want to migrate old logs to the new format, watch the error messages on start. If there are none we are good to go, else you have to plan a migration that can take hours. Set `WOODPECKER_MIGRATIONS_ALLOW_LONG` to true and let it run. -- Using `repo-id` in favor of `owner/repo` combination - - :warning: The api endpoints `/api/repos/{owner}/{repo}/...` were replaced by new endpoints using the repos id `/api/repos/{repo-id}` - - To find the id of a repo use the `/api/repos/lookup/{repo-full-name-with-slashes}` endpoint. - - The existing badge endpoint `/api/badges/{owner}/{repo}` will still work, but whenever possible try to use the new endpoint using the `repo-id`: `/api/badges/{repo-id}`. - - The UI urls for a repository changed from `/repos/{owner}/{repo}/...` to `/repos/{repo-id}/...`. You will be redirected automatically when using the old url. - - The woodpecker-go api-client is now using the `repo-id` instead of `owner/repo` for all functions -- Using `org-id` in favour of `owner` name - - :warning: The api endpoints `/api/orgs/{owner}/...` were replaced by new endpoints using the orgs id `/api/repos/{org-id}` - - To find the id of orgs use the `/api/orgs/lookup/{org_full_name}` endpoint. - - The UI urls for a organization changed from `/org/{owner}/...` to `/orgs/{org-id}/...`. You will be redirected automatically when using the old url. - - The woodpecker-go api-client is now using the `org-id` instead of `org name` for all functions -- The `command:` field has been removed from steps. If you were using it, please check if the entrypoint of the image you used is a shell. - - If it is a shell, simply rename `command:` to `commands:`. - - If it's not, you need to prepend the entrypoint before and also rename it (e.g., `commands: `). - -## 0.15.0 - -- Default value for custom pipeline path is now empty / un-set which results in following resolution: - - `.woodpecker/*.yml` -> `.woodpecker.yml` -> `.drone.yml` - - Only projects created after updating will have an empty value by default. Existing projects will stick to the current pipeline path which is `.drone.yml` in most cases. - - Read more about it at the [Project Settings](./20-usage/71-project-settings.md#pipeline-path) - -- From version `0.15.0` ongoing there will be three types of docker images: `latest`, `next` and `x.x.x` with an alpine variant for each type like `latest-alpine`. - If you used `latest` before to try pre-release features you should switch to `next` after this release. - -- Dropped support for `DRONE_*` environment variables. The according `WOODPECKER_*` variables must be used instead. - Additionally some alternative namings have been removed to simplify maintenance: - - - `WOODPECKER_AGENT_SECRET` replaces `WOODPECKER_SECRET`, `DRONE_SECRET`, `WOODPECKER_PASSWORD`, `DRONE_PASSWORD` and `DRONE_AGENT_SECRET`. - - `WOODPECKER_HOST` replaces `DRONE_HOST` and `DRONE_SERVER_HOST`. - - `WOODPECKER_DATABASE_DRIVER` replaces `DRONE_DATABASE_DRIVER` and `DATABASE_DRIVER`. - - `WOODPECKER_DATABASE_DATASOURCE` replaces `DRONE_DATABASE_DATASOURCE` and `DATABASE_CONFIG`. - -- Dropped support for `DRONE_*` environment variables in pipeline steps. Pipeline meta-data can be accessed with `CI_*` variables. - - - `CI_*` prefix replaces `DRONE_*` - - `CI` value is now `woodpecker` - - `DRONE=true` has been removed - - Some variables got deprecated and will be removed in future versions. Please migrate to the new names. Same applies for `DRONE_` of them. - - CI_ARCH => use CI_SYSTEM_ARCH - - CI_COMMIT => CI_COMMIT_SHA - - CI_TAG => CI_COMMIT_TAG - - CI_PULL_REQUEST => CI_COMMIT_PULL_REQUEST - - CI_REMOTE_URL => use CI_REPO_REMOTE - - CI_REPO_BRANCH => use CI_REPO_DEFAULT_BRANCH - - CI_PARENT_BUILD_NUMBER => use CI_BUILD_PARENT - - CI_BUILD_TARGET => use CI_BUILD_DEPLOY_TARGET - - CI_DEPLOY_TO => use CI_BUILD_DEPLOY_TARGET - - CI_COMMIT_AUTHOR_NAME => use CI_COMMIT_AUTHOR - - CI_PREV_COMMIT_AUTHOR_NAME => use CI_PREV_COMMIT_AUTHOR - - CI_SYSTEM => use CI_SYSTEM_NAME - - CI_BRANCH => use CI_COMMIT_BRANCH - - CI_SOURCE_BRANCH => use CI_COMMIT_SOURCE_BRANCH - - CI_TARGET_BRANCH => use CI_COMMIT_TARGET_BRANCH - - For all available variables and their descriptions have a look at [built-in-environment-variables](./20-usage/50-environment.md#built-in-environment-variables). - -- Prometheus metrics have been changed from `drone_*` to `woodpecker_*` - -- Base path has moved from `/var/lib/drone` to `/var/lib/woodpecker` - -- Default workspace base path has moved from `/drone` to `/woodpecker` - -- Default SQLite database location has changed: - - - `/var/lib/drone/drone.sqlite` -> `/var/lib/woodpecker/woodpecker.sqlite` - - `drone.sqlite` -> `woodpecker.sqlite` - -- Plugin Settings moved into `settings` section: - - ```diff - steps: - something: - image: my/plugin - - setting1: foo - - setting2: bar - + settings: - + setting1: foo - + setting2: bar - ``` - -- `WOODPECKER_DEBUG` option for server and agent got removed in favor of `WOODPECKER_LOG_LEVEL=debug` - -- Remove unused server flags which can safely be removed from your server config: `WOODPECKER_QUIC`, `WOODPECKER_GITHUB_SCOPE`, `WOODPECKER_GITHUB_GIT_USERNAME`, `WOODPECKER_GITHUB_GIT_PASSWORD`, `WOODPECKER_GITHUB_PRIVATE_MODE`, `WOODPECKER_GITEA_GIT_USERNAME`, `WOODPECKER_GITEA_GIT_PASSWORD`, `WOODPECKER_GITEA_PRIVATE_MODE`, `WOODPECKER_GITLAB_GIT_USERNAME`, `WOODPECKER_GITLAB_GIT_PASSWORD`, `WOODPECKER_GITLAB_PRIVATE_MODE` - -- Dropped support for manually setting the agents platform with `WOODPECKER_PLATFORM`. The platform is now automatically detected. - -- Use `WOODPECKER_STATUS_CONTEXT` instead of the deprecated options `WOODPECKER_GITHUB_CONTEXT` and `WOODPECKER_GITEA_CONTEXT`. - -## 0.14.0 - -No breaking changes - -## From Drone - -:::warning -Migration from Drone is only possible if you were running Drone <= v0.8. -::: - -1. Make sure you are already running Drone v0.8 -2. Upgrade to Woodpecker v0.14.4, migration will be done during startup -3. Upgrade to the latest Woodpecker version. Pay attention to the breaking changes listed above. diff --git a/docs/versioned_docs/version-2.5/10-intro.md b/docs/versioned_docs/version-2.5/10-intro.md deleted file mode 100644 index 309c6f1af..000000000 --- a/docs/versioned_docs/version-2.5/10-intro.md +++ /dev/null @@ -1,89 +0,0 @@ -# Welcome to Woodpecker - -Woodpecker is a simple yet powerful CI/CD engine with great extensibility. It focuses on executing pipelines inside [containers](https://opencontainers.org/). -If you are already using containers in your daily workflow, you'll for sure love Woodpecker. - -![woodpecker](woodpecker.png) - -## `.woodpecker.yaml` - -- Place your pipeline in a file named `.woodpecker.yaml` in your repository -- Pipeline steps can be named as you like -- Run any command in the commands section - -```yaml title=".woodpecker.yaml" -steps: - - name: build - image: debian - commands: - - echo "This is the build step" - - name: a-test-step - image: debian - commands: - - echo "Testing.." -``` - -### Steps are containers - -- Define any container image as context - - either use your own and install the needed tools in a custom image - - or search for available images that are already tailored for your needs in image registries like [Docker Hub](https://hub.docker.com/search?type=image) -- List the commands that should be executed in the container - -```diff - steps: - - name: build -- image: debian -+ image: mycompany/image-with-awscli - commands: - - aws help -``` - -### File changes are incremental - -- Woodpecker clones the source code in the beginning -- File changes are persisted throughout individual steps as the same volume is being mounted in all steps - -```yaml title=".woodpecker.yaml" -steps: - - name: build - image: debian - commands: - - touch myfile - - name: a-test-step - image: debian - commands: - - cat myfile -``` - -## Plugins are straightforward - -- If you copy the same shell script from project to project -- Pack it into a plugin instead -- And make the yaml declarative -- Plugins are Docker images with your script as an entrypoint - -```dockerfile title="Dockerfile" -FROM laszlocloud/kubectl -COPY deploy /usr/local/deploy -ENTRYPOINT ["/usr/local/deploy"] -``` - -```bash title="deploy" -kubectl apply -f $PLUGIN_TEMPLATE -``` - -```yaml title=".woodpecker.yaml" -steps: - - name: deploy-to-k8s - image: laszlocloud/my-k8s-plugin - settings: - template: config/k8s/service.yaml -``` - -See [plugin docs](./20-usage/51-plugins/51-overview.md). - -## Continue reading - -- [Create a Woodpecker pipeline for your repository](./20-usage/10-intro.md) -- [Setup your own Woodpecker instance](./30-administration/00-deployment/00-overview.md) diff --git a/docs/versioned_docs/version-2.5/20-usage/10-intro.md b/docs/versioned_docs/version-2.5/20-usage/10-intro.md deleted file mode 100644 index 477466173..000000000 --- a/docs/versioned_docs/version-2.5/20-usage/10-intro.md +++ /dev/null @@ -1,72 +0,0 @@ -# Getting started - -## Repository Activation - -To activate your project navigate to your account settings. You will see a list of repositories which can be activated with a simple toggle. When you activate your repository, Woodpecker automatically adds webhooks to your forge (e.g. GitHub, Gitea, ...). - -Webhooks are used to trigger pipeline executions. When you push code to your repository, open a pull request, or create a tag, your forge will automatically send a webhook to Woodpecker which will in turn trigger the pipeline execution. - -![repository list](repo-list.png) - -## Required Permissions - -The user who enables a repo in Woodpecker must have `Admin` rights on that repo, so that Woodpecker can add the webhook. - -:::note -Note that manually creating webhooks yourself is not possible. -This is because webhooks are signed using a per-repository secret key which is not exposed to end users. -::: - -## Configuration - -To configure your pipeline you must create a `.woodpecker.yaml` file in the root of your repository. The `.woodpecker.yaml` file is used to define your pipeline steps. - -:::note -We support most of YAML 1.2, but preserve some behavior from 1.1 for backward compatibility. -Read more at: [https://github.com/go-yaml/yaml](https://github.com/go-yaml/yaml/tree/v3) -::: - -Example pipeline configuration: - -```yaml -steps: - - name: build - image: golang - commands: - - go get - - go build - - go test - -services: - - name: postgres - image: postgres:9.4.5 - environment: - - POSTGRES_USER=myapp -``` - -Example pipeline configuration with multiple, serial steps: - -```yaml -steps: - - name: backend - image: golang - commands: - - go get - - go build - - go test - - - name: frontend - image: node:6 - commands: - - npm install - - npm test - - - name: notify - image: plugins/slack - channel: developers - username: woodpecker -``` - -## Execution - -To trigger your first pipeline execution you can push code to your repository, open a pull request, or push a tag. Any of these events triggers a webhook from your forge and execute your pipeline. diff --git a/docs/versioned_docs/version-2.5/20-usage/90-advanced-usage.md b/docs/versioned_docs/version-2.5/20-usage/90-advanced-usage.md deleted file mode 100644 index 065386fdf..000000000 --- a/docs/versioned_docs/version-2.5/20-usage/90-advanced-usage.md +++ /dev/null @@ -1,129 +0,0 @@ -# Advanced usage - -## Advanced YAML syntax - -YAML has some advanced syntax features that can be used like variables to reduce duplication in your pipeline config: - -### Anchors & aliases - -You can use [YAML anchors & aliases](https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases) as variables in your pipeline config. - -To convert this: - -```yaml -steps: - - name: test - image: golang:1.18 - commands: go test ./... - - name: build - image: golang:1.18 - commands: build -``` - -Just add a new section called **variables** like this: - -```diff -+variables: -+ - &golang_image 'golang:1.18' - - steps: - - name: test -- image: golang:1.18 -+ image: *golang_image - commands: go test ./... - - name: build -- image: golang:1.18 -+ image: *golang_image - commands: build -``` - -### Map merges and overwrites - -```yaml -variables: - - &base-plugin-settings - target: dist - recursive: false - try: true - - &special-setting - special: true - - &some-plugin codeberg.org/6543/docker-images/print_env - -steps: - - name: develop - image: *some-plugin - settings: - <<: [*base-plugin-settings, *special-setting] # merge two maps into an empty map - when: - branch: develop - - - name: main - image: *some-plugin - settings: - <<: *base-plugin-settings # merge one map and ... - try: false # ... overwrite original value - ongoing: false # ... adding a new value - when: - branch: main -``` - -### Sequence merges - -```yaml -variables: - pre_cmds: &pre_cmds - - echo start - - whoami - post_cmds: &post_cmds - - echo stop - hello_cmd: &hello_cmd - - echo hello - -steps: - - name: step1 - image: debian - commands: - - <<: *pre_cmds # prepend a sequence - - echo exec step now do dedicated things - - <<: *post_cmds # append a sequence - - name: step2 - image: debian - commands: - - <<: [*pre_cmds, *hello_cmd] # prepend two sequences - - echo echo from second step - - <<: *post_cmds -``` - -### References - -- [Official YAML specification](https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases) -- [YAML Cheatsheet](https://learnxinyminutes.com/docs/yaml) - -## Persisting environment data between steps - -One can create a file containing environment variables, and then source it in each step that needs them. - -```yaml -steps: - - name: init - image: bash - commands: - - echo "FOO=hello" >> envvars - - echo "BAR=world" >> envvars - - - name: debug - image: bash - commands: - - source envvars - - echo $FOO -``` - -## Declaring global variables - -As described in [Global environment variables](./50-environment.md#global-environment-variables), you can define global variables: - -```ini -WOODPECKER_ENVIRONMENT=first_var:value1,second_var:value2 -``` - -Note that this tightly couples the server and app configurations (where the app is a completely separate application). But this is a good option for truly global variables which should apply to all steps in all pipelines for all apps. diff --git a/docs/versioned_docs/version-2.5/20-usage/project-settings.png b/docs/versioned_docs/version-2.5/20-usage/project-settings.png deleted file mode 100644 index fc29daac8..000000000 Binary files a/docs/versioned_docs/version-2.5/20-usage/project-settings.png and /dev/null differ diff --git a/docs/versioned_docs/version-2.5/20-usage/repo-list.png b/docs/versioned_docs/version-2.5/20-usage/repo-list.png deleted file mode 100644 index b47380087..000000000 Binary files a/docs/versioned_docs/version-2.5/20-usage/repo-list.png and /dev/null differ diff --git a/docs/versioned_docs/version-2.5/30-administration/00-deployment/00-overview.md b/docs/versioned_docs/version-2.5/30-administration/00-deployment/00-overview.md deleted file mode 100644 index bdb64a594..000000000 --- a/docs/versioned_docs/version-2.5/30-administration/00-deployment/00-overview.md +++ /dev/null @@ -1,90 +0,0 @@ -# Deployment - -A Woodpecker deployment consists of two parts: - -- A server which is the heart of Woodpecker and ships the web interface. -- Next to one server, you can deploy any number of agents which will run the pipelines. - -Each agent is able to process one pipeline step by default. -If you have four agents installed and connected to the Woodpecker server, your system will process four workflows in parallel. - -:::tip -You can add more agents to increase the number of parallel workflows or set the agent's `WOODPECKER_MAX_WORKFLOWS=1` environment variable to increase the number of parallel workflows for that agent. -::: - -## Which version of Woodpecker should I use? - -Woodpecker is having two different kinds of releases: **stable** and **next**. - -### Stable releases - -We release a new version every four weeks and will release the current state of the `main` branch. -If there are security fixes or critical bug fixes, we'll release them directly. -There are no backports or similar. - -#### Versioning - -We use [Semantic Versioning](https://semver.org/) to be able, -to communicate when admins have to do manual migration steps and when they can just bump versions up. - -#### Breaking changes - -As of semver guidelines, breaking changes will be released as a major version. We will hold back -breaking changes to not release many majors each containing just a few breaking changes. -Prior to the release of a major version, a release candidate (RC) will be published to allow easy testing, -the actual release will be about a week later. - -## Hardware Requirements - -Below are minimal resources requirements for Woodpecker components itself: - -| Component | Memory | CPU | -| --------- | ------ | --- | -| Server | 200 MB | 1 | -| Agent | 32 MB | 1 | - -Note, that those values do not include the operating system or workload (pipelines execution) resources consumption. - -In addition you need at least some kind of database which requires additional resources depending on the selected database system. - -## Installation - -You can install Woodpecker on multiple ways: - -- Using [docker-compose](./10-docker-compose.md) with the official [container images](./10-docker-compose.md#docker-images) -- Using [Kubernetes](./20-kubernetes.md) via the Woodpecker Helm chart -- Using binaries, DEBs or RPMs you can download from [latest release](https://github.com/woodpecker-ci/woodpecker/releases/latest) - -## Authentication - -Authentication is done using OAuth and is delegated to your forge which is configured using environment variables. - -See the complete reference for all supported forges [here](../11-forges/11-overview.md). - -## Database - -By default Woodpecker uses a SQLite database which requires zero installation or configuration. See the [database settings](../30-database.md) page to further configure it or use MySQL or Postgres. - -## SSL - -Woodpecker supports SSL configuration by using Let's encrypt or by using own certificates. See the [SSL guide](../60-ssl.md). You can also put it behind a [reverse proxy](#behind-a-proxy) - -## Metrics - -A [Prometheus endpoint](../90-prometheus.md) is exposed. - -## Behind a proxy - -See the [proxy guide](../70-proxy.md) if you want to see a setup behind Apache, Nginx, Caddy or ngrok. - -In the case you need to use Woodpecker with a URL path prefix (like: ), add the root path to [`WOODPECKER_HOST`](../10-server-config.md#woodpecker_host). - -## Third-party installation methods - -:::info -These installation methods are not officially supported. If you experience issues with them, please open issues in the specific repositories. -::: - -- Using [NixOS](./30-nixos.md) via the [NixOS module](https://search.nixos.org/options?channel=unstable&size=200&sort=relevance&query=woodpecker) -- [Using YunoHost](https://apps.yunohost.org/app/woodpecker) -- [On Cloudron](https://www.cloudron.io/store/org.woodpecker_ci.cloudronapp.html) diff --git a/docs/versioned_docs/version-2.5/30-administration/00-deployment/20-kubernetes.md b/docs/versioned_docs/version-2.5/30-administration/00-deployment/20-kubernetes.md deleted file mode 100644 index f931c3e78..000000000 --- a/docs/versioned_docs/version-2.5/30-administration/00-deployment/20-kubernetes.md +++ /dev/null @@ -1,9 +0,0 @@ -# Kubernetes - -We recommended to deploy Woodpecker using the [Woodpecker helm chart](https://github.com/woodpecker-ci/helm). -Have a look at the [`values.yaml`](https://github.com/woodpecker-ci/helm/blob/main/charts/woodpecker/values.yaml) config files for all available settings. - -The chart contains two subcharts, `server` and `agent` which are automatically configured as needed. -The chart started off with two independent charts but was merged into one to simplify the deployment at start of 2023. - -A couple of backend-specific config env vars exists which are described in the [kubernetes backend docs](../22-backends/40-kubernetes.md). diff --git a/docs/versioned_docs/version-2.5/30-administration/00-deployment/_category_.yaml b/docs/versioned_docs/version-2.5/30-administration/00-deployment/_category_.yaml deleted file mode 100644 index 728434969..000000000 --- a/docs/versioned_docs/version-2.5/30-administration/00-deployment/_category_.yaml +++ /dev/null @@ -1,6 +0,0 @@ -label: 'Deployment' -collapsible: true -collapsed: true -link: - type: 'doc' - id: 'overview' diff --git a/docs/versioned_docs/version-2.5/30-administration/11-forges/11-overview.md b/docs/versioned_docs/version-2.5/30-administration/11-forges/11-overview.md deleted file mode 100644 index 4446896f0..000000000 --- a/docs/versioned_docs/version-2.5/30-administration/11-forges/11-overview.md +++ /dev/null @@ -1,13 +0,0 @@ -# Forges - -## Supported features - -| Feature | [GitHub](20-github.md) | [Gitea / Forgejo](30-gitea.md) | [Gitlab](40-gitlab.md) | [Bitbucket](50-bitbucket.md) | [Bitbucket Datacenter](60-bitbucket_datacenter.md) | -| ------------------------------------------------------------- | :--------------------: | :----------------------------: | :--------------------: | :--------------------------: | :------------------------------------------------: | -| Event: Push | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| Event: Tag | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| Event: Pull-Request | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| Event: Release | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :x: | -| Event: Deploy | :white_check_mark: | :x: | :x: | :x: | :x: | -| [Multiple workflows](../../20-usage/25-workflows.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | -| [when.path filter](../../20-usage/20-workflow-syntax.md#path) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :x: | diff --git a/docs/versioned_docs/version-2.5/woodpecker.png b/docs/versioned_docs/version-2.5/woodpecker.png deleted file mode 100644 index b92f3589f..000000000 Binary files a/docs/versioned_docs/version-2.5/woodpecker.png and /dev/null differ diff --git a/docs/versioned_docs/version-2.6/10-intro.md b/docs/versioned_docs/version-2.6/10-intro.md index 309c6f1af..e4624e623 100644 --- a/docs/versioned_docs/version-2.6/10-intro.md +++ b/docs/versioned_docs/version-2.6/10-intro.md @@ -1,6 +1,6 @@ # Welcome to Woodpecker -Woodpecker is a simple yet powerful CI/CD engine with great extensibility. It focuses on executing pipelines inside [containers](https://opencontainers.org/). +Woodpecker is a simple, yet powerful CI/CD engine with great extensibility. It focuses on executing pipelines inside [containers](https://opencontainers.org/). If you are already using containers in your daily workflow, you'll for sure love Woodpecker. ![woodpecker](woodpecker.png) diff --git a/docs/versioned_docs/version-2.6/20-usage/75-project-settings.md b/docs/versioned_docs/version-2.6/20-usage/75-project-settings.md index 24bdbe605..d57b3a932 100644 --- a/docs/versioned_docs/version-2.6/20-usage/75-project-settings.md +++ b/docs/versioned_docs/version-2.6/20-usage/75-project-settings.md @@ -40,9 +40,16 @@ Only server admins can set this option. If you are not a server admin this optio ::: -## Only inject netrc credentials into trusted containers +## Only inject Git credentials into trusted clone plugins -Cloning pipeline step may need git credentials. They are injected via netrc. By default, they're only injected if this option is enabled, the repo is trusted ([see above](#trusted)) or the image is a trusted clone image. If you uncheck the option, git credentials will be injected into any container in clone step. +The clone step may require Git credentials (e.g. for private repos) which are injected via `netrc`. + +By default, they are only injected into trusted clone plugins listed in the env var `WOODPECKER_PLUGINS_TRUSTED_CLONE`. +If this option is disabled, the Git credentials are injected into every clone plugin, regardless of whether it is trusted or not. + +:::note +This option has no effect on steps other than the clone step. +::: ## Project visibility diff --git a/docs/versioned_docs/version-2.7/10-intro/index.md b/docs/versioned_docs/version-2.7/10-intro/index.md new file mode 100644 index 000000000..7d9ced179 --- /dev/null +++ b/docs/versioned_docs/version-2.7/10-intro/index.md @@ -0,0 +1,26 @@ +# Welcome to Woodpecker + +Woodpecker is a CI/CD tool. It is designed to be lightweight, simple to use and fast. Before we dive into the details, let's have a look at some of the basics. + +## Have you ever heard of CI/CD or pipelines? + +Don't worry if you haven't. We'll guide you through the basics. CI/CD stands for Continuous Integration and Continuous Deployment. It's basically like a conveyor belt that moves your code from development to production doing all kinds of +checks, tests and routines along the way. A typical pipeline might include the following steps: + +1. Running tests +2. Building your application +3. Deploying your application + +[Have a deeper look into the idea of CI/CD](https://www.redhat.com/en/topics/devops/what-is-ci-cd) + +## Do you know containers? + +If you are already using containers in your daily workflow, you'll for sure love Woodpecker. If not yet, you'll be amazed how easy it is to get started with [containers](https://opencontainers.org/). + +## Already have access to a Woodpecker instance? + +Then you might want to jump directly into it and [start creating your first pipelines](../20-usage/10-intro.md). + +## Want to start from scratch and deploy your own Woodpecker instance? + +Woodpecker is [pretty lightweight](../30-administration/00-getting-started.md#hardware-requirements) and will even run on your Raspberry Pi. You can follow the [deployment guide](../30-administration/00-getting-started.md) to set up your own Woodpecker instance. diff --git a/docs/versioned_docs/version-2.7/20-usage/10-intro.md b/docs/versioned_docs/version-2.7/20-usage/10-intro.md new file mode 100644 index 000000000..9c4cb3c21 --- /dev/null +++ b/docs/versioned_docs/version-2.7/20-usage/10-intro.md @@ -0,0 +1,109 @@ +# Your first pipeline + +Let's get started and create your first pipeline. + +## 1. Repository Activation + +To activate your repository in Woodpecker navigate to the repository list and `New repository`. You will see a list of repositories from your forge (GitHub, Gitlab, ...) which can be activated with a simple click. + +![new repository list](repo-new.png) + +To enable a repository in Woodpecker you must have `Admin` rights on that repository, so that Woodpecker can add something +that is called a webhook (Woodpecker needs it to know about actions like pushes, pull requests, tags, etc.). + +## 2. Define first workflow + +After enabling a repository Woodpecker will listen for changes in your repository. When a change is detected, Woodpecker will check for a pipeline configuration. So let's create a file at `.woodpecker/my-first-workflow.yaml` inside your repository: + +```yaml title=".woodpecker/my-first-workflow.yaml" +when: + - event: push + branch: main + +steps: + - name: build + image: debian + commands: + - echo "This is the build step" + - echo "binary-data-123" > executable + - name: a-test-step + image: golang:1.16 + commands: + - echo "Testing ..." + - ./executable +``` + +**So what did we do here?** + +1. We defined your first workflow file `my-first-workflow.yaml`. +2. This workflow will be executed when a push event happens on the `main` branch, + because we added a filter using the `when` section: + + ```diff + + when: + + - event: push + + branch: main + + ... + ``` + +3. We defined two steps: `build` and `a-test-step` + +The steps are executed in the order they are defined, so `build` will be executed first and then `a-test-step`. + +In the `build` step we use the `debian` image and build a "binary file" called `executable`. + +In the `a-test-step` we use the `golang:1.16` image and run the `executable` file to test it. + +You can use any image from registries like the [Docker Hub](https://hub.docker.com/search?type=image) you have access to: + +```diff + steps: + - name: build +- image: debian ++ image: mycompany/image-with-awscli + commands: + - aws help +``` + +## 3. Push the file and trigger first pipeline + +If you push this file to your repository now, Woodpecker will already execute your first pipeline. + +You can check the pipeline execution in the Woodpecker UI by navigating to the `Pipelines` section of your repository. + +![pipeline view](./pipeline.png) + +As you probably noticed, there is another step in called `clone` which is executed before your steps. This step clones your repository into a folder called `workspace` which is available throughout all steps. + +This for example allows the first step to build your application using your source code and as the second step will receive +the same workspace it can use the previously built binary and test it. + +## 4. Use a plugin for reusable tasks + +Sometimes you have some tasks that you need to do in every project. For example, deploying to Kubernetes or sending a Slack message. Therefore you can use one of the [official and community plugins](/plugins) or simply [create your own](./51-plugins/20-creating-plugins.md). + +If you want to get a Slack notification after your pipeline has finished, you can add a Slack plugin to your pipeline: + +```yaml +--- +- name: notify me on Slack + image: plugins/slack + settings: + channel: developers + username: woodpecker + password: + from_secret: slack_token + when: + status: [success, failure] # This will execute the step on success and failure +``` + +To configure a plugin you can use the `settings` section. + +Sometime you need to provide secrets to the plugin. You can do this by using the `from_secret` key. The secret must be defined in the Woodpecker UI. You can find more information about secrets [here](./40-secrets.md). + +Similar to the `when` section at the top of the file which is for the complete workflow, you can use the `when` section for each step to define when a step should be executed. + +Learn more about [plugins](./51-plugins/51-overview.md). + +As you now have a basic understanding of how to create a pipeline, you can dive deeper into the [workflow syntax](./20-workflow-syntax.md) and [plugins](./51-plugins/51-overview.md). diff --git a/docs/versioned_docs/version-2.7/20-usage/100-troubleshooting.md b/docs/versioned_docs/version-2.7/20-usage/100-troubleshooting.md new file mode 100644 index 000000000..b961530f4 --- /dev/null +++ b/docs/versioned_docs/version-2.7/20-usage/100-troubleshooting.md @@ -0,0 +1,37 @@ +# Troubleshooting + +## How to debug clone issues + +(And what to do with an error message like `fatal: could not read Username for 'https://': No such device or address`) + +This error can have multiple causes. If you use internal repositories you might have to enable `WOODPECKER_AUTHENTICATE_PUBLIC_REPOS`: + +```ini +WOODPECKER_AUTHENTICATE_PUBLIC_REPOS=true +``` + +If that does not work, try to make sure the container can reach your git server. In order to do that disable git checkout and make the container "hang": + +```yaml +skip_clone: true + +steps: + build: + image: debian:stable-backports + commands: + - apt update + - apt install -y inetutils-ping wget + - ping -c 4 git.example.com + - wget git.example.com + - sleep 9999999 +``` + +Get the container id using `docker ps` and copy the id from the first column. Enter the container with: `docker exec -it 1234asdf bash` (replace `1234asdf` with the docker id). Then try to clone the git repository with the commands from the failing pipeline: + +```bash +git init +git remote add origin https://git.example.com/username/repo.git +git fetch --no-tags origin +refs/heads/branch: +``` + +(replace the url AND the branch with the correct values, use your username and password as log in values) diff --git a/docs/versioned_docs/version-2.4/20-usage/15-terminiology/architecture.excalidraw b/docs/versioned_docs/version-2.7/20-usage/15-terminology/architecture.excalidraw similarity index 100% rename from docs/versioned_docs/version-2.4/20-usage/15-terminiology/architecture.excalidraw rename to docs/versioned_docs/version-2.7/20-usage/15-terminology/architecture.excalidraw diff --git a/docs/versioned_docs/version-2.4/20-usage/15-terminiology/architecture.svg b/docs/versioned_docs/version-2.7/20-usage/15-terminology/architecture.svg similarity index 100% rename from docs/versioned_docs/version-2.4/20-usage/15-terminiology/architecture.svg rename to docs/versioned_docs/version-2.7/20-usage/15-terminology/architecture.svg diff --git a/docs/versioned_docs/version-2.5/20-usage/15-terminology/index.md b/docs/versioned_docs/version-2.7/20-usage/15-terminology/index.md similarity index 100% rename from docs/versioned_docs/version-2.5/20-usage/15-terminology/index.md rename to docs/versioned_docs/version-2.7/20-usage/15-terminology/index.md index 5e9d8e5de..f05f18207 100644 --- a/docs/versioned_docs/version-2.5/20-usage/15-terminology/index.md +++ b/docs/versioned_docs/version-2.7/20-usage/15-terminology/index.md @@ -1,13 +1,5 @@ # Terminology -## Woodpecker architecture - -![Woodpecker architecture](architecture.svg) - -## Pipeline, workflow & step - -![Relation between pipelines, workflows and steps](pipeline-workflow-step.svg) - ## Glossary - **Woodpecker CI**: The project name around Woodpecker. @@ -33,6 +25,14 @@ - **Status**: Status refers to the outcome of a step or [workflow][Workflow] after it has been executed, determined by the internal command exit code. At the end of a [workflow][Workflow], its status is sent to the [forge][Forge]. - **Service extension**: Some parts of Woodpecker internal services like secrets storage or config fetcher can be replaced through service extensions. +## Woodpecker architecture + +![Woodpecker architecture](architecture.svg) + +## Pipeline, workflow & step + +![Relation between pipelines, workflows and steps](pipeline-workflow-step.svg) + ## Pipeline events - `push`: A push event is triggered when a commit is pushed to a branch. diff --git a/docs/versioned_docs/version-2.4/20-usage/15-terminiology/pipeline-workflow-step.excalidraw b/docs/versioned_docs/version-2.7/20-usage/15-terminology/pipeline-workflow-step.excalidraw similarity index 100% rename from docs/versioned_docs/version-2.4/20-usage/15-terminiology/pipeline-workflow-step.excalidraw rename to docs/versioned_docs/version-2.7/20-usage/15-terminology/pipeline-workflow-step.excalidraw diff --git a/docs/versioned_docs/version-2.4/20-usage/15-terminiology/pipeline-workflow-step.svg b/docs/versioned_docs/version-2.7/20-usage/15-terminology/pipeline-workflow-step.svg similarity index 100% rename from docs/versioned_docs/version-2.4/20-usage/15-terminiology/pipeline-workflow-step.svg rename to docs/versioned_docs/version-2.7/20-usage/15-terminology/pipeline-workflow-step.svg diff --git a/docs/versioned_docs/version-2.4/20-usage/20-workflow-syntax.md b/docs/versioned_docs/version-2.7/20-usage/20-workflow-syntax.md similarity index 90% rename from docs/versioned_docs/version-2.4/20-usage/20-workflow-syntax.md rename to docs/versioned_docs/version-2.7/20-usage/20-workflow-syntax.md index 84f2b54f7..956401c7e 100644 --- a/docs/versioned_docs/version-2.4/20-usage/20-workflow-syntax.md +++ b/docs/versioned_docs/version-2.7/20-usage/20-workflow-syntax.md @@ -1,6 +1,15 @@ # Workflow syntax -The workflow section defines a list of steps to build, test and deploy your code. Steps are executed serially, in the order in which they are defined. If a step returns a non-zero exit code, the workflow and therefore all other workflows and the pipeline immediately aborts and returns a failure status. +The Workflow section defines a list of steps to build, test and deploy your code. The steps are executed serially in the order in which they are defined. If a step returns a non-zero exit code, the workflow and therefore the entire pipeline terminates immediately and returns an error status. + +:::note +An exception to this rule are steps with a [`status: [failure]`](#status) condition, which ensures that they are executed in the case of a failed run. +::: + +:::note +We support most of YAML 1.2, but preserve some behavior from 1.1 for backward compatibility. +Read more at: [https://github.com/go-yaml/yaml](https://github.com/go-yaml/yaml/tree/v3) +::: Example steps: @@ -161,6 +170,9 @@ Only build steps can define commands. You cannot use commands with plugins or se Allows you to specify the entrypoint for containers. Note that this must be a list of the command and its arguments (e.g. `["/bin/sh", "-c"]`). +If you define [`commands`](#commands), the default entrypoint will be `["/bin/sh", "-c", "echo $CI_SCRIPT | base64 -d | /bin/sh -e"]`. +You can also use a custom shell with `CI_SCRIPT` (Base64-encoded) if you set `commands`. + ### `environment` Woodpecker provides the ability to pass environment variables to individual steps. @@ -189,7 +201,8 @@ Some of the steps may be allowed to fail without causing the whole workflow and ### `when` - Conditional Execution -Woodpecker supports defining a list of conditions for a step by using a `when` block. If at least one of the conditions in the `when` block evaluate to true the step is executed, otherwise it is skipped. A condition can be a check like: +Woodpecker supports defining a list of conditions for a step by using a `when` block. If at least one of the conditions in the `when` block evaluate to true the step is executed, otherwise it is skipped. A condition is evaluated to true if _all_ subconditions are true. +A condition can be a check like: ```diff steps: @@ -204,6 +217,11 @@ Woodpecker supports defining a list of conditions for a step by using a `when` b + branch: main ``` +The `slack` step is executed if one of these conditions is met: + +1. The pipeline is executed from a pull request in the repo `test/test` +2. The pipeline is executed from a push to `main` + #### `repo` Example conditional execution by repository: @@ -353,20 +371,6 @@ when: - platform: [linux/*, windows/amd64] ``` - - -#### `environment` - - - -Execute a step for deployment events matching the target deployment environment: - -```yaml -when: - - environment: production - - event: deployment -``` - #### `matrix` Execute a step for a single matrix permutation: @@ -403,16 +407,19 @@ when: You can use [glob patterns](https://github.com/bmatcuk/doublestar#patterns) to match the changed files and specify if the step should run if a file matching that pattern has been changed `include` or if some files have **not** been changed `exclude`. +For pipelines without file changes (empty commits or on events without file changes like `tag`), you can use `on_empty` to set whether this condition should be **true** _(default)_ or **false** in these cases. + ```yaml when: - path: include: ['.woodpecker/*.yaml', '*.ini'] exclude: ['*.md', 'docs/**'] ignore_message: '[ALL]' + on_empty: true ``` :::info -Passing a defined ignore-message like `[ALL]` inside the commit message will ignore all path conditions. +Passing a defined ignore-message like `[ALL]` inside the commit message will ignore all path conditions and the `on_empty` setting. ::: #### `evaluate` @@ -516,7 +523,9 @@ For more details check the [services docs](./60-services.md). ## `workspace` -The workspace defines the shared volume and working directory shared by all workflow steps. The default workspace matches the pattern `/woodpecker/src/github.com/octocat/hello-world`, based on your repository URL. +The workspace defines the shared volume and working directory shared by all workflow steps. +The default workspace base is `/woodpecker` and the path is extended with the repository URL (`src/{url-without-schema}`). +So an example would be `/woodpecker/src/github.com/octocat/hello-world`. The workspace can be customized using the workspace block in the YAML file: @@ -533,6 +542,10 @@ The workspace can be customized using the workspace block in the YAML file: - go test ``` +:::note +Plugins will always have the workspace base at `/woodpecker` +::: + The base attribute defines a shared base volume available to all steps. This ensures your source code, dependencies and compiled binaries are persisted and shared between steps. ```diff @@ -640,7 +653,7 @@ You can manually configure the clone step in your workflow for customization: ```diff +clone: -+ - name: git ++ git: + image: woodpeckerci/plugin-git steps: @@ -716,7 +729,7 @@ skip_clone: true ## `when` - Global workflow conditions -Woodpecker gives the ability to skip whole workflows (not just steps #when---conditional-execution-1) based on certain conditions by a `when` block. If all conditions in the `when` block evaluate to true the workflow is executed, otherwise it is skipped, but treated as successful and other workflows depending on it will still continue. +Woodpecker gives the ability to skip whole workflows ([not just steps](#when---conditional-execution)) based on certain conditions by a `when` block. If all conditions in the `when` block evaluate to true the workflow is executed, otherwise it is skipped, but treated as successful and other workflows depending on it will still continue. For more information about the specific filters, take a look at the [step-specific `when` filters](#when---conditional-execution). @@ -752,7 +765,7 @@ Workflows that should run even on failure should set the `runs_on` tag. See [her Woodpecker gives the ability to configure privileged mode in the YAML. You can use this parameter to launch containers with escalated capabilities. :::info -Privileged mode is only available to trusted repositories and for security reasons should only be used in private environments. See [project settings](./71-project-settings.md#trusted) to enable trusted mode. +Privileged mode is only available to trusted repositories and for security reasons should only be used in private environments. See [project settings](./75-project-settings.md#trusted) to enable trusted mode. ::: ```diff diff --git a/docs/versioned_docs/version-2.5/20-usage/25-workflows.md b/docs/versioned_docs/version-2.7/20-usage/25-workflows.md similarity index 100% rename from docs/versioned_docs/version-2.5/20-usage/25-workflows.md rename to docs/versioned_docs/version-2.7/20-usage/25-workflows.md diff --git a/docs/versioned_docs/version-2.4/20-usage/30-matrix-workflows.md b/docs/versioned_docs/version-2.7/20-usage/30-matrix-workflows.md similarity index 100% rename from docs/versioned_docs/version-2.4/20-usage/30-matrix-workflows.md rename to docs/versioned_docs/version-2.7/20-usage/30-matrix-workflows.md diff --git a/docs/versioned_docs/version-2.4/20-usage/40-secrets.md b/docs/versioned_docs/version-2.7/20-usage/40-secrets.md similarity index 100% rename from docs/versioned_docs/version-2.4/20-usage/40-secrets.md rename to docs/versioned_docs/version-2.7/20-usage/40-secrets.md diff --git a/docs/versioned_docs/version-2.4/20-usage/41-registries.md b/docs/versioned_docs/version-2.7/20-usage/41-registries.md similarity index 100% rename from docs/versioned_docs/version-2.4/20-usage/41-registries.md rename to docs/versioned_docs/version-2.7/20-usage/41-registries.md diff --git a/docs/versioned_docs/version-2.5/20-usage/45-cron.md b/docs/versioned_docs/version-2.7/20-usage/45-cron.md similarity index 100% rename from docs/versioned_docs/version-2.5/20-usage/45-cron.md rename to docs/versioned_docs/version-2.7/20-usage/45-cron.md diff --git a/docs/versioned_docs/version-2.5/20-usage/50-environment.md b/docs/versioned_docs/version-2.7/20-usage/50-environment.md similarity index 100% rename from docs/versioned_docs/version-2.5/20-usage/50-environment.md rename to docs/versioned_docs/version-2.7/20-usage/50-environment.md diff --git a/docs/versioned_docs/version-2.5/20-usage/51-plugins/20-creating-plugins.md b/docs/versioned_docs/version-2.7/20-usage/51-plugins/20-creating-plugins.md similarity index 100% rename from docs/versioned_docs/version-2.5/20-usage/51-plugins/20-creating-plugins.md rename to docs/versioned_docs/version-2.7/20-usage/51-plugins/20-creating-plugins.md diff --git a/docs/versioned_docs/version-2.5/20-usage/51-plugins/51-overview.md b/docs/versioned_docs/version-2.7/20-usage/51-plugins/51-overview.md similarity index 58% rename from docs/versioned_docs/version-2.5/20-usage/51-plugins/51-overview.md rename to docs/versioned_docs/version-2.7/20-usage/51-plugins/51-overview.md index ab8db3df3..0e5905036 100644 --- a/docs/versioned_docs/version-2.5/20-usage/51-plugins/51-overview.md +++ b/docs/versioned_docs/version-2.7/20-usage/51-plugins/51-overview.md @@ -4,6 +4,24 @@ Plugins are pipeline steps that perform pre-defined tasks and are configured as They are automatically pulled from the default container registry the agent's have configured. +```dockerfile title="Dockerfile" +FROM laszlocloud/kubectl +COPY deploy /usr/local/deploy +ENTRYPOINT ["/usr/local/deploy"] +``` + +```bash title="deploy" +kubectl apply -f $PLUGIN_TEMPLATE +``` + +```yaml title=".woodpecker.yaml" +steps: + - name: deploy-to-k8s + image: laszlocloud/my-k8s-plugin + settings: + template: config/k8s/service.yaml +``` + Example pipeline using the Docker and Slack plugins: ```yaml @@ -29,6 +47,11 @@ steps: ## Plugin Isolation Plugins are just pipeline steps. They share the build workspace, mounted as a volume, and therefore have access to your source tree. +While normal steps are all about arbitrary code execution, plugins should only allow the functions intended by the plugin author. + +So there are a few limitations, like the workspace base is always mounted at `/woodpecker`, but the working directory is dynamically adjusted accordingly. So as user of a plugin you should not have to care about this. + +Also instead of using environment variables the plugin should only care about one prefixed with `PLUGIN_` witch are the internal representation of the **settings** ([read more](./20-creating-plugins.md)). ## Finding Plugins diff --git a/docs/versioned_docs/version-2.4/20-usage/51-plugins/_category_.yaml b/docs/versioned_docs/version-2.7/20-usage/51-plugins/_category_.yaml similarity index 100% rename from docs/versioned_docs/version-2.4/20-usage/51-plugins/_category_.yaml rename to docs/versioned_docs/version-2.7/20-usage/51-plugins/_category_.yaml diff --git a/docs/versioned_docs/version-2.4/20-usage/60-services.md b/docs/versioned_docs/version-2.7/20-usage/60-services.md similarity index 100% rename from docs/versioned_docs/version-2.4/20-usage/60-services.md rename to docs/versioned_docs/version-2.7/20-usage/60-services.md diff --git a/docs/versioned_docs/version-2.5/20-usage/70-volumes.md b/docs/versioned_docs/version-2.7/20-usage/70-volumes.md similarity index 100% rename from docs/versioned_docs/version-2.5/20-usage/70-volumes.md rename to docs/versioned_docs/version-2.7/20-usage/70-volumes.md diff --git a/docs/versioned_docs/version-2.5/20-usage/72-linter.md b/docs/versioned_docs/version-2.7/20-usage/72-linter.md similarity index 100% rename from docs/versioned_docs/version-2.5/20-usage/72-linter.md rename to docs/versioned_docs/version-2.7/20-usage/72-linter.md diff --git a/docs/versioned_docs/version-2.4/20-usage/71-project-settings.md b/docs/versioned_docs/version-2.7/20-usage/75-project-settings.md similarity index 75% rename from docs/versioned_docs/version-2.4/20-usage/71-project-settings.md rename to docs/versioned_docs/version-2.7/20-usage/75-project-settings.md index 573e85eee..d57b3a932 100644 --- a/docs/versioned_docs/version-2.4/20-usage/71-project-settings.md +++ b/docs/versioned_docs/version-2.7/20-usage/75-project-settings.md @@ -16,6 +16,15 @@ Your Version-Control-System will notify Woodpecker about events via webhooks. If Enables handling webhook's pull request event. If disabled, then pipeline won't run for pull requests. +## Allow deployments + +Enables a pipeline to be started with the `deploy` event from a successful pipeline. + +:::danger +Only activate this option if you trust all users who have push access to your repository. +Otherwise, these users will be able to steal secrets that are only available for `deploy` events. +::: + ## Protected Every pipeline initiated by an webhook event needs to be approved by a project members with push permissions before being executed. @@ -31,9 +40,16 @@ Only server admins can set this option. If you are not a server admin this optio ::: -## Only inject netrc credentials into trusted containers +## Only inject Git credentials into trusted clone plugins -Cloning pipeline step may need git credentials. They are injected via netrc. By default, they're only injected if this option is enabled, the repo is trusted ([see above](#trusted)) or the image is a trusted clone image. If you uncheck the option, git credentials will be injected into any container in clone step. +The clone step may require Git credentials (e.g. for private repos) which are injected via `netrc`. + +By default, they are only injected into trusted clone plugins listed in the env var `WOODPECKER_PLUGINS_TRUSTED_CLONE`. +If this option is disabled, the Git credentials are injected into every clone plugin, regardless of whether it is trusted or not. + +:::note +This option has no effect on steps other than the clone step. +::: ## Project visibility diff --git a/docs/versioned_docs/version-2.4/20-usage/80-badges.md b/docs/versioned_docs/version-2.7/20-usage/80-badges.md similarity index 100% rename from docs/versioned_docs/version-2.4/20-usage/80-badges.md rename to docs/versioned_docs/version-2.7/20-usage/80-badges.md diff --git a/docs/versioned_docs/version-2.4/20-usage/90-advanced-usage.md b/docs/versioned_docs/version-2.7/20-usage/90-advanced-usage.md similarity index 100% rename from docs/versioned_docs/version-2.4/20-usage/90-advanced-usage.md rename to docs/versioned_docs/version-2.7/20-usage/90-advanced-usage.md diff --git a/docs/versioned_docs/version-2.4/20-usage/_category_.yaml b/docs/versioned_docs/version-2.7/20-usage/_category_.yaml similarity index 100% rename from docs/versioned_docs/version-2.4/20-usage/_category_.yaml rename to docs/versioned_docs/version-2.7/20-usage/_category_.yaml diff --git a/docs/versioned_docs/version-2.4/20-usage/cron-settings.png b/docs/versioned_docs/version-2.7/20-usage/cron-settings.png similarity index 100% rename from docs/versioned_docs/version-2.4/20-usage/cron-settings.png rename to docs/versioned_docs/version-2.7/20-usage/cron-settings.png diff --git a/docs/versioned_docs/version-2.5/20-usage/linter-warnings-errors.png b/docs/versioned_docs/version-2.7/20-usage/linter-warnings-errors.png similarity index 100% rename from docs/versioned_docs/version-2.5/20-usage/linter-warnings-errors.png rename to docs/versioned_docs/version-2.7/20-usage/linter-warnings-errors.png diff --git a/docs/versioned_docs/version-2.7/20-usage/pipeline.png b/docs/versioned_docs/version-2.7/20-usage/pipeline.png new file mode 100644 index 000000000..dd4063c9a Binary files /dev/null and b/docs/versioned_docs/version-2.7/20-usage/pipeline.png differ diff --git a/docs/versioned_docs/version-2.4/20-usage/project-settings.png b/docs/versioned_docs/version-2.7/20-usage/project-settings.png similarity index 100% rename from docs/versioned_docs/version-2.4/20-usage/project-settings.png rename to docs/versioned_docs/version-2.7/20-usage/project-settings.png diff --git a/docs/versioned_docs/version-2.7/20-usage/repo-new.png b/docs/versioned_docs/version-2.7/20-usage/repo-new.png new file mode 100644 index 000000000..e6136bc12 Binary files /dev/null and b/docs/versioned_docs/version-2.7/20-usage/repo-new.png differ diff --git a/docs/versioned_docs/version-2.7/30-administration/00-getting-started.md b/docs/versioned_docs/version-2.7/30-administration/00-getting-started.md new file mode 100644 index 000000000..8bb1b0a71 --- /dev/null +++ b/docs/versioned_docs/version-2.7/30-administration/00-getting-started.md @@ -0,0 +1,59 @@ +# Getting started + +A Woodpecker deployment consists of two parts: + +- A server which is the heart of Woodpecker and ships the web interface. +- Next to one server, you can deploy any number of agents which will run the pipelines. + +Each agent is able to process one [workflow](../20-usage/15-terminology/index.md) by default. If you have 4 agents installed and connected to the Woodpecker server, your system will process four workflows (not pipelines) in parallel. + +:::tip +You can add more agents to increase the number of parallel workflows or set the agent's `WOODPECKER_MAX_WORKFLOWS=1` environment variable to increase the number of parallel workflows per agent. +::: + +## Which version of Woodpecker should I use? + +Woodpecker is having two different kinds of releases: **stable** and **next**. + +Find more information about the different versions [here](/versions). + +## Hardware Requirements + +Below are minimal resources requirements for Woodpecker components itself: + +| Component | Memory | CPU | +| --------- | ------ | --- | +| Server | 200 MB | 1 | +| Agent | 32 MB | 1 | + +Note, that those values do not include the operating system or workload (pipelines execution) resource consumption. + +In addition you need at least some kind of database which requires additional resources depending on the selected database system. + +## Installation + +You can install Woodpecker on multiple ways. If you are not sure which one to choose, we recommend using the [docker-compose](./05-deployment-methods/10-docker-compose.md) method for the beginning: + +- Using [docker-compose](./05-deployment-methods/10-docker-compose.md) with the official [container images](./05-deployment-methods/10-docker-compose.md#docker-images) +- Using [Kubernetes](./05-deployment-methods/20-kubernetes.md) via the Woodpecker Helm chart +- Using binaries, DEBs or RPMs you can download from [latest release](https://github.com/woodpecker-ci/woodpecker/releases/latest) +- Or using a [third-party installation method](./05-deployment-methods/30-third-party.md) + +## Database + +By default Woodpecker uses a SQLite database which requires zero installation or configuration. See the [database settings](./10-database.md) page if you want to use a different database system like MySQL or PostgreSQL. + +## Forge + +What would be a CI/CD system without any code? By connecting Woodpecker to your [forge](../20-usage/15-terminology/index.md) like GitHub or Gitea you can start running pipelines on events like pushes or pull requests. Woodpecker will also use your forge for authentication and to report back the status of your pipelines. See the [forge settings](./11-forges/11-overview.md) to connect it to Woodpecker. + +## Configuration + +Check the [server configuration](./10-server-config.md) and [agent configuration](./15-agent-config.md) pages to see if you need to adjust any additional parts and after that you should be ready to start with [your first pipeline](../20-usage/10-intro.md). + +## Agent + +The agent is the worker which executes the [workflows](../20-usage/15-terminology/index.md). +Woodpecker agents can execute work using a [backend](../20-usage/15-terminology/index.md) like [docker](./22-backends/10-docker.md) or [kubernetes](./22-backends/40-kubernetes.md). +By default if you choose to deploy an agent using [docker-compose](./05-deployment-methods/10-docker-compose.md) the agent simply use docker for the backend as well. +So nothing to worry about here. If you still prefer to adjust the agent to your needs, check the [agent configuration](./15-agent-config.md) page. diff --git a/docs/versioned_docs/version-2.4/30-administration/00-deployment/10-docker-compose.md b/docs/versioned_docs/version-2.7/30-administration/05-deployment-methods/10-docker-compose.md similarity index 94% rename from docs/versioned_docs/version-2.4/30-administration/00-deployment/10-docker-compose.md rename to docs/versioned_docs/version-2.7/30-administration/05-deployment-methods/10-docker-compose.md index a9c2bb6ab..5af7e85fc 100644 --- a/docs/versioned_docs/version-2.4/30-administration/00-deployment/10-docker-compose.md +++ b/docs/versioned_docs/version-2.7/30-administration/05-deployment-methods/10-docker-compose.md @@ -67,7 +67,7 @@ They can be configured with `*_ADDR` variables: + - WOODPECKER_SERVER_ADDR=${WOODPECKER_HTTP_ADDR} ``` -Reverse proxying can also be [configured for gRPC](../70-proxy.md#caddy). If the agents are connecting over the internet, it should also be SSL encrypted. The agent then needs to be configured to be secure: +Reverse proxying can also be [configured for gRPC](../40-advanced/10-proxy.md#caddy). If the agents are connecting over the internet, it should also be SSL encrypted. The agent then needs to be configured to be secure: ```diff title="docker-compose.yaml" version: '3' diff --git a/docs/docs/30-administration/00-deployment/20-kubernetes.md b/docs/versioned_docs/version-2.7/30-administration/05-deployment-methods/20-kubernetes.md similarity index 100% rename from docs/docs/30-administration/00-deployment/20-kubernetes.md rename to docs/versioned_docs/version-2.7/30-administration/05-deployment-methods/20-kubernetes.md diff --git a/docs/versioned_docs/version-2.7/30-administration/05-deployment-methods/30-third-party.md b/docs/versioned_docs/version-2.7/30-administration/05-deployment-methods/30-third-party.md new file mode 100644 index 000000000..acad9c0fd --- /dev/null +++ b/docs/versioned_docs/version-2.7/30-administration/05-deployment-methods/30-third-party.md @@ -0,0 +1,12 @@ +# Third-party installation methods + +:::info +These installation methods are not officially supported. If you experience issues with them, please open issues in the specific repositories. +::: + +- [Using NixOS](./40-nixos.md) via the [NixOS module](https://search.nixos.org/options?channel=unstable&size=200&sort=relevance&query=woodpecker) +- [On Alpine Edge](https://pkgs.alpinelinux.org/packages?name=woodpecker&branch=edge&repo=&arch=&maintainer=) +- [On Arch Linux](https://archlinux.org/packages/?q=woodpecker) +- [On openSUSE](https://software.opensuse.org/package/woodpecker) +- [Using YunoHost](https://apps.yunohost.org/app/woodpecker) +- [On Cloudron](https://www.cloudron.io/store/org.woodpecker_ci.cloudronapp.html) diff --git a/docs/docs/30-administration/00-deployment/30-nixos.md b/docs/versioned_docs/version-2.7/30-administration/05-deployment-methods/40-nixos.md similarity index 100% rename from docs/docs/30-administration/00-deployment/30-nixos.md rename to docs/versioned_docs/version-2.7/30-administration/05-deployment-methods/40-nixos.md diff --git a/docs/versioned_docs/version-2.7/30-administration/05-deployment-methods/_category_.yaml b/docs/versioned_docs/version-2.7/30-administration/05-deployment-methods/_category_.yaml new file mode 100644 index 000000000..3907838b0 --- /dev/null +++ b/docs/versioned_docs/version-2.7/30-administration/05-deployment-methods/_category_.yaml @@ -0,0 +1,3 @@ +label: 'Deployment methods' +collapsible: true +collapsed: true diff --git a/docs/docs/30-administration/30-database.md b/docs/versioned_docs/version-2.7/30-administration/10-database.md similarity index 100% rename from docs/docs/30-administration/30-database.md rename to docs/versioned_docs/version-2.7/30-administration/10-database.md diff --git a/docs/versioned_docs/version-2.5/30-administration/10-server-config.md b/docs/versioned_docs/version-2.7/30-administration/10-server-config.md similarity index 97% rename from docs/versioned_docs/version-2.5/30-administration/10-server-config.md rename to docs/versioned_docs/version-2.7/30-administration/10-server-config.md index 8bd7d49c2..69bd2bb7d 100644 --- a/docs/versioned_docs/version-2.5/30-administration/10-server-config.md +++ b/docs/versioned_docs/version-2.7/30-administration/10-server-config.md @@ -305,7 +305,7 @@ Example: `org1,org2` > Default: empty -Comma-separated list of syncable repo owners. ??? +Repositories by those owners will be allowed to be used in woodpecker. Example: `user1,user2` @@ -517,7 +517,7 @@ Example: `WOODPECKER_LIMIT_CPU_SET=1,2` > Default: empty -Specify a configuration service endpoint, see [Configuration Extension](./100-external-configuration-api.md) +Specify a configuration service endpoint, see [Configuration Extension](./40-advanced/100-external-configuration-api.md) ### `WOODPECKER_FORGE_TIMEOUT` @@ -543,6 +543,18 @@ Enable the Swagger UI for API documentation. Disable version check in admin web UI. +### `WOODPECKER_LOG_STORE` + +> Default: `database` + +Where to store logs. Possible values: `database` or `file`. + +### `WOODPECKER_LOG_STORE_FILE_PATH` + +> Default empty + +Directory to store logs in if [`WOODPECKER_LOG_STORE`](#woodpecker_log_store) is `file`. + --- ### `WOODPECKER_GITHUB_...` diff --git a/docs/versioned_docs/version-2.5/30-administration/11-forges/100-addon.md b/docs/versioned_docs/version-2.7/30-administration/11-forges/100-addon.md similarity index 100% rename from docs/versioned_docs/version-2.5/30-administration/11-forges/100-addon.md rename to docs/versioned_docs/version-2.7/30-administration/11-forges/100-addon.md diff --git a/docs/versioned_docs/version-2.7/30-administration/11-forges/11-overview.md b/docs/versioned_docs/version-2.7/30-administration/11-forges/11-overview.md new file mode 100644 index 000000000..ba45adf87 --- /dev/null +++ b/docs/versioned_docs/version-2.7/30-administration/11-forges/11-overview.md @@ -0,0 +1,13 @@ +# Forges + +## Supported features + +| Feature | [GitHub](20-github.md) | [Gitea](30-gitea.md) | [Forgejo](35-forgejo.md) | [Gitlab](40-gitlab.md) | [Bitbucket](50-bitbucket.md) | [Bitbucket Datacenter](60-bitbucket_datacenter.md) | +| ------------------------------------------------------------- | :--------------------: | :------------------: | :----------------------: | :--------------------: | :--------------------------: | :------------------------------------------------: | +| Event: Push | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Event: Tag | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Event: Pull-Request | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| Event: Release | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :x: | +| Event: Deploy | :white_check_mark: | :x: | :x: | :x: | :x: | :x: | +| [Multiple workflows](../../20-usage/25-workflows.md) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | +| [when.path filter](../../20-usage/20-workflow-syntax.md#path) | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :x: | :x: | diff --git a/docs/versioned_docs/version-2.5/30-administration/11-forges/20-github.md b/docs/versioned_docs/version-2.7/30-administration/11-forges/20-github.md similarity index 100% rename from docs/versioned_docs/version-2.5/30-administration/11-forges/20-github.md rename to docs/versioned_docs/version-2.7/30-administration/11-forges/20-github.md diff --git a/docs/versioned_docs/version-2.5/30-administration/11-forges/30-gitea.md b/docs/versioned_docs/version-2.7/30-administration/11-forges/30-gitea.md similarity index 93% rename from docs/versioned_docs/version-2.5/30-administration/11-forges/30-gitea.md rename to docs/versioned_docs/version-2.7/30-administration/11-forges/30-gitea.md index 46249b986..bb8e93c2a 100644 --- a/docs/versioned_docs/version-2.5/30-administration/11-forges/30-gitea.md +++ b/docs/versioned_docs/version-2.7/30-administration/11-forges/30-gitea.md @@ -2,9 +2,9 @@ toc_max_heading_level: 2 --- -# Gitea / Forgejo +# Gitea -Woodpecker comes with built-in support for Gitea and the "soft" fork Forgejo. To enable Gitea you should configure the Woodpecker container using the following environment variables: +Woodpecker comes with built-in support for Gitea. To enable Gitea you should configure the Woodpecker container using the following environment variables: ```ini WOODPECKER_GITEA=true diff --git a/docs/versioned_docs/version-2.7/30-administration/11-forges/35-forgejo.md b/docs/versioned_docs/version-2.7/30-administration/11-forges/35-forgejo.md new file mode 100644 index 000000000..df7793118 --- /dev/null +++ b/docs/versioned_docs/version-2.7/30-administration/11-forges/35-forgejo.md @@ -0,0 +1,97 @@ +--- +toc_max_heading_level: 2 +--- + +# Forgejo + +:::warning +Forgejo support is experimental. +::: + +Woodpecker comes with built-in support for Forgejo. To enable Forgejo you should configure the Woodpecker container using the following environment variables: + +```ini +WOODPECKER_FORGEJO=true +WOODPECKER_FORGEJO_URL=YOUR_FORGEJO_URL +WOODPECKER_FORGEJO_CLIENT=YOUR_FORGEJO_CLIENT +WOODPECKER_FORGEJO_SECRET=YOUR_FORGEJO_CLIENT_SECRET +``` + +## Forgejo on the same host with containers + +If you have Forgejo also running on the same host within a container, make sure the agent does have access to it. +The agent tries to clone using the URL which Forgejo reports through its API. For simplified connectivity, you should add the Woodpecker agent to the same docker network as Forgejo is in. +Otherwise, the communication should go via the `docker0` gateway (usually 172.17.0.1). + +To configure the Docker network if the network's name is `forgejo`, configure it like this: + +```diff title="docker-compose.yaml" + services: + [...] + woodpecker-agent: + [...] + environment: + - [...] ++ - WOODPECKER_BACKEND_DOCKER_NETWORK=forgejo +``` + +## Registration + +Register your application with Forgejo to create your client id and secret. You can find the OAuth applications settings of Forgejo at `https://forgejo./user/settings/`. It is very import the authorization callback URL matches your http(s) scheme and hostname exactly with `https:///authorize` as the path. + +If you run the Woodpecker CI server on the same host as the Forgejo instance, you might also need to allow local connections in Forgejo. Otherwise webhooks will fail. Add the following lines to your Forgejo configuration (usually at `/etc/forgejo/conf/app.ini`). + +```ini +[webhook] +ALLOWED_HOST_LIST=external,loopback +``` + +For reference see [Configuration Cheat Sheet](https://forgejo.org/docs/latest/admin/config-cheat-sheet/#webhook-webhook). + +![forgejo oauth setup](gitea_oauth.gif) + +## Configuration + +This is a full list of configuration options. Please note that many of these options use default configuration values that should work for the majority of installations. + +### `WOODPECKER_FORGEJO` + +> Default: `false` + +Enables the Forgejo driver. + +### `WOODPECKER_FORGEJO_URL` + +> Default: `https://next.forgejo.org` + +Configures the Forgejo server address. + +### `WOODPECKER_FORGEJO_CLIENT` + +> Default: empty + +Configures the Forgejo OAuth client id. This is used to authorize access. + +### `WOODPECKER_FORGEJO_CLIENT_FILE` + +> Default: empty + +Read the value for `WOODPECKER_FORGEJO_CLIENT` from the specified filepath + +### `WOODPECKER_FORGEJO_SECRET` + +> Default: empty + +Configures the Forgejo OAuth client secret. This is used to authorize access. + +### `WOODPECKER_FORGEJO_SECRET_FILE` + +> Default: empty + +Read the value for `WOODPECKER_FORGEJO_SECRET` from the specified filepath + +### `WOODPECKER_FORGEJO_SKIP_VERIFY` + +> Default: `false` + +Configure if SSL verification should be skipped. diff --git a/docs/versioned_docs/version-2.4/30-administration/11-forges/40-gitlab.md b/docs/versioned_docs/version-2.7/30-administration/11-forges/40-gitlab.md similarity index 100% rename from docs/versioned_docs/version-2.4/30-administration/11-forges/40-gitlab.md rename to docs/versioned_docs/version-2.7/30-administration/11-forges/40-gitlab.md diff --git a/docs/versioned_docs/version-2.5/30-administration/11-forges/50-bitbucket.md b/docs/versioned_docs/version-2.7/30-administration/11-forges/50-bitbucket.md similarity index 100% rename from docs/versioned_docs/version-2.5/30-administration/11-forges/50-bitbucket.md rename to docs/versioned_docs/version-2.7/30-administration/11-forges/50-bitbucket.md diff --git a/docs/versioned_docs/version-2.4/30-administration/11-forges/60-bitbucket_datacenter.md b/docs/versioned_docs/version-2.7/30-administration/11-forges/60-bitbucket_datacenter.md similarity index 74% rename from docs/versioned_docs/version-2.4/30-administration/11-forges/60-bitbucket_datacenter.md rename to docs/versioned_docs/version-2.7/30-administration/11-forges/60-bitbucket_datacenter.md index e85242c05..53926fa73 100644 --- a/docs/versioned_docs/version-2.4/30-administration/11-forges/60-bitbucket_datacenter.md +++ b/docs/versioned_docs/version-2.7/30-administration/11-forges/60-bitbucket_datacenter.md @@ -10,24 +10,23 @@ Woodpecker comes with experimental support for Bitbucket Datacenter / Server, fo To enable Bitbucket Server you should configure the Woodpecker container using the following environment variables: -```diff -# docker-compose.yml -version: '3' +```diff title="docker-compose.yaml" + version: '3' -services: - woodpecker-server: - [...] - environment: - - [...] -+ - WOODPECKER_BITBUCKET_DC=true -+ - WOODPECKER_BITBUCKET_DC_GIT_USERNAME=foo -+ - WOODPECKER_BITBUCKET_DC_GIT_PASSWORD=bar -+ - WOODPECKER_BITBUCKET_DC_CLIENT_ID=xxx -+ - WOODPECKER_BITBUCKET_DC_CLIENT_SECRET=yyy -+ - WOODPECKER_BITBUCKET_DC_URL=http://stash.mycompany.com + services: + woodpecker-server: + [...] + environment: + - [...] ++ - WOODPECKER_BITBUCKET_DC=true ++ - WOODPECKER_BITBUCKET_DC_GIT_USERNAME=foo ++ - WOODPECKER_BITBUCKET_DC_GIT_PASSWORD=bar ++ - WOODPECKER_BITBUCKET_DC_CLIENT_ID=xxx ++ - WOODPECKER_BITBUCKET_DC_CLIENT_SECRET=yyy ++ - WOODPECKER_BITBUCKET_DC_URL=http://stash.mycompany.com - woodpecker-agent: - [...] + woodpecker-agent: + [...] ``` ## Service Account @@ -36,7 +35,10 @@ Woodpecker uses `git+https` to clone repositories, however, Bitbucket Server doe ## Registration -Woodpecker must be registered with Bitbucket Datacenter / Server. In the administration section of Bitbucket choose "Application Links" and then "Create link". Woodpecker should be listed as "External Application" and the direction should be set to "Incomming". Note the client id and client secret of the registration to be used in the configuration of Woodpecker. +Woodpecker must be registered with Bitbucket Datacenter / Server. +In the administration section of Bitbucket choose "Application Links" and then "Create link". +Woodpecker should be listed as "External Application" and the direction should be set to "Incoming". +Note the client id and client secret of the registration to be used in the configuration of Woodpecker. See also [Configure an incoming link](https://confluence.atlassian.com/bitbucketserver/configure-an-incoming-link-1108483657.html). diff --git a/docs/versioned_docs/version-2.4/30-administration/11-forges/_category_.yaml b/docs/versioned_docs/version-2.7/30-administration/11-forges/_category_.yaml similarity index 100% rename from docs/versioned_docs/version-2.4/30-administration/11-forges/_category_.yaml rename to docs/versioned_docs/version-2.7/30-administration/11-forges/_category_.yaml diff --git a/docs/versioned_docs/version-2.4/30-administration/11-forges/bitbucket_oauth.png b/docs/versioned_docs/version-2.7/30-administration/11-forges/bitbucket_oauth.png similarity index 100% rename from docs/versioned_docs/version-2.4/30-administration/11-forges/bitbucket_oauth.png rename to docs/versioned_docs/version-2.7/30-administration/11-forges/bitbucket_oauth.png diff --git a/docs/versioned_docs/version-2.4/30-administration/11-forges/bitbucket_permissions.png b/docs/versioned_docs/version-2.7/30-administration/11-forges/bitbucket_permissions.png similarity index 100% rename from docs/versioned_docs/version-2.4/30-administration/11-forges/bitbucket_permissions.png rename to docs/versioned_docs/version-2.7/30-administration/11-forges/bitbucket_permissions.png diff --git a/docs/versioned_docs/version-2.4/30-administration/11-forges/gitea_oauth.gif b/docs/versioned_docs/version-2.7/30-administration/11-forges/gitea_oauth.gif similarity index 100% rename from docs/versioned_docs/version-2.4/30-administration/11-forges/gitea_oauth.gif rename to docs/versioned_docs/version-2.7/30-administration/11-forges/gitea_oauth.gif diff --git a/docs/versioned_docs/version-2.4/30-administration/11-forges/github_oauth.png b/docs/versioned_docs/version-2.7/30-administration/11-forges/github_oauth.png similarity index 100% rename from docs/versioned_docs/version-2.4/30-administration/11-forges/github_oauth.png rename to docs/versioned_docs/version-2.7/30-administration/11-forges/github_oauth.png diff --git a/docs/versioned_docs/version-2.4/30-administration/15-agent-config.md b/docs/versioned_docs/version-2.7/30-administration/15-agent-config.md similarity index 100% rename from docs/versioned_docs/version-2.4/30-administration/15-agent-config.md rename to docs/versioned_docs/version-2.7/30-administration/15-agent-config.md diff --git a/docs/versioned_docs/version-2.4/30-administration/22-backends/10-docker.md b/docs/versioned_docs/version-2.7/30-administration/22-backends/10-docker.md similarity index 100% rename from docs/versioned_docs/version-2.4/30-administration/22-backends/10-docker.md rename to docs/versioned_docs/version-2.7/30-administration/22-backends/10-docker.md diff --git a/docs/versioned_docs/version-2.4/30-administration/22-backends/20-local.md b/docs/versioned_docs/version-2.7/30-administration/22-backends/20-local.md similarity index 58% rename from docs/versioned_docs/version-2.4/30-administration/22-backends/20-local.md rename to docs/versioned_docs/version-2.7/30-administration/22-backends/20-local.md index 7ec95f0f8..c0faf9c2f 100644 --- a/docs/versioned_docs/version-2.4/30-administration/22-backends/20-local.md +++ b/docs/versioned_docs/version-2.7/30-administration/22-backends/20-local.md @@ -5,33 +5,31 @@ toc_max_heading_level: 3 # Local backend :::danger -The local backend will execute the pipelines on the local system without any isolation of any kind. +The local backend executes pipelines on the local system without any isolation. ::: :::note -Currently we do not support services for this backend. +Currently we do not support [services](../../20-usage/60-services.md) for this backend. [Read more here](https://github.com/woodpecker-ci/woodpecker/issues/3095). ::: -Since the code runs directly in the same context as the agent (same user, same +Since the commands run directly in the same context as the agent (same user, same filesystem), a malicious pipeline could be used to access the agent configuration especially the `WOODPECKER_AGENT_SECRET` variable. It is recommended to use this backend only for private setup where the code and -pipeline can be trusted. You shouldn't use it for a public facing CI where -anyone can submit code or add new repositories. You shouldn't execute the agent -as a privileged user (root). +pipeline can be trusted. It should not be used in a public instance where +anyone can submit code or add new repositories. The agent should not run as a privileged user (root). The local backend will use a random directory in `$TMPDIR` to store the cloned code and execute commands. In order to use this backend, you need to download (or build) the -[binary](https://github.com/woodpecker-ci/woodpecker/releases/latest) of the -agent, configure it and run it on the host machine. +[agent](https://github.com/woodpecker-ci/woodpecker/releases/latest), configure it and run it on the host machine. ## Usage -To enable the local backend, add this to your configuration: +To enable the local backend, set the following: ```ini WOODPECKER_BACKEND=local @@ -39,7 +37,7 @@ WOODPECKER_BACKEND=local ### Shell -The `image` entry is used to specify the shell, such as Bash or Fish, that is +The `image` entrypoint is used to specify the shell, such as `bash` or `fish`, that is used to run the commands. ```yaml title=".woodpecker.yaml" @@ -51,15 +49,13 @@ steps: ### Plugins -Plugins are just executable binaries: - ```yaml steps: - name: build image: /usr/bin/tree ``` -If no commands are provided, we treat them as plugins in the usual manner. +If no commands are provided, plugins are treated in the usual manner. In the context of the local backend, plugins are simply executable binaries, which can be located using their name if they are listed in `$PATH`, or through an absolute path. ### Options diff --git a/docs/versioned_docs/version-2.4/30-administration/22-backends/40-kubernetes.md b/docs/versioned_docs/version-2.7/30-administration/22-backends/40-kubernetes.md similarity index 66% rename from docs/versioned_docs/version-2.4/30-administration/22-backends/40-kubernetes.md rename to docs/versioned_docs/version-2.7/30-administration/22-backends/40-kubernetes.md index e878b6a4f..72524946b 100644 --- a/docs/versioned_docs/version-2.4/30-administration/22-backends/40-kubernetes.md +++ b/docs/versioned_docs/version-2.7/30-administration/22-backends/40-kubernetes.md @@ -4,19 +4,19 @@ toc_max_heading_level: 2 # Kubernetes backend -The kubernetes backend executes steps inside standalone pods. A temporary PVC is created for the lifetime of the pipeline to transfer files between steps. +The Kubernetes backend executes steps inside standalone Pods. A temporary PVC is created for the lifetime of the pipeline to transfer files between steps. ## Images from private registries -In order to pull private container images defined in your pipeline YAML you must provide [registry credentials in Kubernetes secret](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/). -As the secret is Agent-wide, it has to be placed in namespace defined by `WOODPECKER_BACKEND_K8S_NAMESPACE`. -Besides, you need to provide the secret name to Agent via `WOODPECKER_BACKEND_K8S_PULL_SECRET_NAMES`. +In order to pull private container images defined in your pipeline YAML you must provide [registry credentials in Kubernetes Secret](https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/). +As the Secret is Agent-wide, it has to be placed in namespace defined by `WOODPECKER_BACKEND_K8S_NAMESPACE`. +Besides, you need to provide the Secret name to Agent via `WOODPECKER_BACKEND_K8S_PULL_SECRET_NAMES`. ## Job specific configuration ### Resources -The kubernetes backend also allows for specifying requests and limits on a per-step basic, most commonly for CPU and memory. +The Kubernetes backend also allows for specifying requests and limits on a per-step basic, most commonly for CPU and memory. We recommend to add a `resources` definition to all steps to ensure efficient scheduling. Here is an example definition with an arbitrary `resources` definition below the `backend_options` section: @@ -40,10 +40,15 @@ steps: You can use [Limit Ranges](https://kubernetes.io/docs/concepts/policy/limit-range/) if you want to set the limits by per-namespace basis. +### Runtime class + +`runtimeClassName` specifies the name of the RuntimeClass which will be used to run this Pod. If no `runtimeClassName` is specified, the default RuntimeHandler will be used. +See the [Kubernetes documentation](https://kubernetes.io/docs/concepts/containers/runtime-class/) for more information on specifying runtime classes. + ### Service account -`serviceAccountName` specifies the name of the ServiceAccount which the pod will mount. This service account must be created externally. -See the [kubernetes documentation](https://kubernetes.io/docs/concepts/security/service-accounts/) for more information on using service accounts. +`serviceAccountName` specifies the name of the ServiceAccount which the Pod will mount. This service account must be created externally. +See the [Kubernetes documentation](https://kubernetes.io/docs/concepts/security/service-accounts/) for more information on using service accounts. ### Node selector @@ -74,12 +79,13 @@ And then overwrite the `nodeSelector` in the `backend_options` section of the st kubernetes.io/arch: "${ARCH}" ``` -You can use [PodNodeSelector](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#podnodeselector) admission controller if you want to set the node selector by per-namespace basis. +You can use [WOODPECKER_BACKEND_K8S_POD_NODE_SELECTOR](#woodpecker_backend_k8s_pod_node_selector) if you want to set the node selector per Agent +or [PodNodeSelector](https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#podnodeselector) admission controller if you want to set the node selector by per-namespace basis. ### Tolerations -When you use `nodeSelector` and the node pool is configured with Taints, you need to specify the Tolerations. Tolerations allow the scheduler to schedule pods with matching taints. -See the [kubernetes documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) for more information on using tolerations. +When you use `nodeSelector` and the node pool is configured with Taints, you need to specify the Tolerations. Tolerations allow the scheduler to schedule Pods with matching taints. +See the [Kubernetes documentation](https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/) for more information on using tolerations. Example pipeline configuration: @@ -112,7 +118,7 @@ steps: ### Volumes -To mount volumes a persistent volume (PV) and persistent volume claim (PVC) are needed on the cluster which can be referenced in steps via the `volumes` option. +To mount volumes a PersistentVolume (PV) and PersistentVolumeClaim (PVC) are needed on the cluster which can be referenced in steps via the `volumes` option. Assuming a PVC named `woodpecker-cache` exists, it can be referenced as follows in a step: ```yaml @@ -129,7 +135,7 @@ steps: ### Security context -Use the following configuration to set the [Security Context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) for the pod/container running a given pipeline step: +Use the following configuration to set the [Security Context](https://kubernetes.io/docs/tasks/configure-pod-container/security-context/) for the Pod/container running a given pipeline step: ```yaml steps: @@ -146,9 +152,9 @@ steps: [...] ``` -Note that the `backend_options.kubernetes.securityContext` object allows you to set both pod and container level security context options in one object. -By default, the properties will be set at the pod level. Properties that are only supported on the container level will be set there instead. So, the -configuration shown above will result in something like the following pod spec: +Note that the `backend_options.kubernetes.securityContext` object allows you to set both Pod and container level security context options in one object. +By default, the properties will be set at the Pod level. Properties that are only supported on the container level will be set there instead. So, the +configuration shown above will result in something like the following Pod spec: ```yaml kind: Pod @@ -190,6 +196,24 @@ backend_options: AppArmor syntax follows [KEP-24](https://github.com/kubernetes/enhancements/blob/fddcbb9cbf3df39ded03bad71228265ac6e5215f/keps/sig-node/24-apparmor/README.md). ::: +### Annotations and labels + +You can specify arbitrary [annotations](https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations/) and [labels](https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/) to be set on the Pod definition for a given workflow step using the following configuration: + +```yaml +backend_options: + kubernetes: + annotations: + workflow-group: alpha + io.kubernetes.cri-o.Devices: /dev/fuse + labels: + environment: ci + app.kubernetes.io/name: builder +``` + +In order to enable this configuration you need to set the appropriate environment variables to `true` on the woodpecker agent: +[WOODPECKER_BACKEND_K8S_POD_ANNOTATIONS_ALLOW_FROM_STEP](#woodpecker_backend_k8s_pod_annotations_allow_from_step) and/or [WOODPECKER_BACKEND_K8S_POD_LABELS_ALLOW_FROM_STEP](#woodpecker_backend_k8s_pod_labels_allow_from_step). + ## Tips and tricks ### CRI-O @@ -204,6 +228,13 @@ workspace: See [this issue](https://github.com/woodpecker-ci/woodpecker/issues/2510) for more details. +### `KUBERNETES_SERVICE_HOST` environment variable + +Like the below env vars used for configuration, this can be set in the environment for configuration of the agent. +It configures the address of the Kubernetes API server to connect to. + +If running the agent within Kubernetes, this will already be set and you don't have to add it manually. + ## Configuration These env vars can be set in the `env:` sections of the agent. @@ -212,7 +243,7 @@ These env vars can be set in the `env:` sections of the agent. > Default: `woodpecker` -The namespace to create worker pods in. +The namespace to create worker Pods in. ### `WOODPECKER_BACKEND_K8S_VOLUME_SIZE` @@ -236,13 +267,31 @@ Determines if `RWX` should be used for the pipeline volume's [access mode](https > Default: empty -Additional labels to apply to worker pods. Must be a YAML object, e.g. `{"example.com/test-label":"test-value"}`. +Additional labels to apply to worker Pods. Must be a YAML object, e.g. `{"example.com/test-label":"test-value"}`. + +### `WOODPECKER_BACKEND_K8S_POD_LABELS_ALLOW_FROM_STEP` + +> Default: `false` + +Determines if additional Pod labels can be defined from a step's backend options. ### `WOODPECKER_BACKEND_K8S_POD_ANNOTATIONS` > Default: empty -Additional annotations to apply to worker pods. Must be a YAML object, e.g. `{"example.com/test-annotation":"test-value"}`. +Additional annotations to apply to worker Pods. Must be a YAML object, e.g. `{"example.com/test-annotation":"test-value"}`. + +### `WOODPECKER_BACKEND_K8S_POD_ANNOTATIONS_ALLOW_FROM_STEP` + +> Default: `false` + +Determines if Pod annotations can be defined from a step's backend options. + +### `WOODPECKER_BACKEND_K8S_POD_NODE_SELECTOR` + +> Default: empty + +Additional node selector to apply to worker pods. Must be a YAML object, e.g. `{"topology.kubernetes.io/region":"eu-central-1"}`. ### `WOODPECKER_BACKEND_K8S_SECCTX_NONROOT` diff --git a/docs/versioned_docs/version-2.5/30-administration/22-backends/50-custom-backends.md b/docs/versioned_docs/version-2.7/30-administration/22-backends/50-custom-backends.md similarity index 89% rename from docs/versioned_docs/version-2.5/30-administration/22-backends/50-custom-backends.md rename to docs/versioned_docs/version-2.7/30-administration/22-backends/50-custom-backends.md index 3c771c4ef..a35a4f1ee 100644 --- a/docs/versioned_docs/version-2.5/30-administration/22-backends/50-custom-backends.md +++ b/docs/versioned_docs/version-2.7/30-administration/22-backends/50-custom-backends.md @@ -1,6 +1,6 @@ # Custom backends -If none of our backends fits your usecases, you can write your own. +If none of our backends fits your usecase, you can write your own. Therefore, implement the interface `"go.woodpecker-ci.org/woodpecker/woodpecker/v2/pipeline/backend/types".Backend` and build a custom agent using your backend with this `main.go`: diff --git a/docs/versioned_docs/version-2.4/30-administration/22-backends/_category_.yaml b/docs/versioned_docs/version-2.7/30-administration/22-backends/_category_.yaml similarity index 100% rename from docs/versioned_docs/version-2.4/30-administration/22-backends/_category_.yaml rename to docs/versioned_docs/version-2.7/30-administration/22-backends/_category_.yaml diff --git a/docs/versioned_docs/version-2.5/30-administration/70-proxy.md b/docs/versioned_docs/version-2.7/30-administration/40-advanced/10-proxy.md similarity index 98% rename from docs/versioned_docs/version-2.5/30-administration/70-proxy.md rename to docs/versioned_docs/version-2.7/30-administration/40-advanced/10-proxy.md index 1e253e457..607cd8bd4 100644 --- a/docs/versioned_docs/version-2.5/30-administration/70-proxy.md +++ b/docs/versioned_docs/version-2.7/30-administration/40-advanced/10-proxy.md @@ -93,7 +93,7 @@ woodpeckeragent.example.com { ``` :::note -Above configuration shows how to create reverse-proxies for web and agent communication. If your agent uses SSL do not forget to enable [`WOODPECKER_GRPC_SECURE`](./15-agent-config.md#woodpecker_grpc_secure). +Above configuration shows how to create reverse-proxies for web and agent communication. If your agent uses SSL do not forget to enable [`WOODPECKER_GRPC_SECURE`](../15-agent-config.md#woodpecker_grpc_secure). ::: ## Tunnelmole diff --git a/docs/docs/30-administration/100-external-configuration-api.md b/docs/versioned_docs/version-2.7/30-administration/40-advanced/100-external-configuration-api.md similarity index 100% rename from docs/docs/30-administration/100-external-configuration-api.md rename to docs/versioned_docs/version-2.7/30-administration/40-advanced/100-external-configuration-api.md diff --git a/docs/docs/30-administration/60-ssl.md b/docs/versioned_docs/version-2.7/30-administration/40-advanced/20-ssl.md similarity index 100% rename from docs/docs/30-administration/60-ssl.md rename to docs/versioned_docs/version-2.7/30-administration/40-advanced/20-ssl.md diff --git a/docs/versioned_docs/version-2.4/30-administration/80-autoscaler.md b/docs/versioned_docs/version-2.7/30-administration/40-advanced/30-autoscaler.md similarity index 93% rename from docs/versioned_docs/version-2.4/30-administration/80-autoscaler.md rename to docs/versioned_docs/version-2.7/30-administration/40-advanced/30-autoscaler.md index 6ac581c2e..ce9ee914a 100644 --- a/docs/versioned_docs/version-2.4/30-administration/80-autoscaler.md +++ b/docs/versioned_docs/version-2.7/30-administration/40-advanced/30-autoscaler.md @@ -29,8 +29,8 @@ services: - WOODPECKER_MIN_AGENTS=0 - WOODPECKER_MAX_AGENTS=3 - WOODPECKER_WORKFLOWS_PER_AGENT=2 # the number of workflows each agent can run at the same time - - WOODEPCKER_GRPC_ADDR=https://grpc.your-woodpecker-server.tld # the grpc address of your woodpecker server, publicly accessible from the agents - - WOODEPCKER_GRPC_SECURE=true + - WOODPECKER_GRPC_ADDR=https://grpc.your-woodpecker-server.tld # the grpc address of your woodpecker server, publicly accessible from the agents + - WOODPECKER_GRPC_SECURE=true - WOODPECKER_AGENT_ENV= # optional environment variables to pass to the agents - WOODPECKER_PROVIDER=hetznercloud # set the provider, you can find all the available ones down below - WOODPECKER_HETZNERCLOUD_API_TOKEN=${WOODPECKER_HETZNERCLOUD_API_TOKEN} # your api token for the Hetzner cloud diff --git a/docs/versioned_docs/version-2.7/30-administration/40-advanced/40-advanced.md b/docs/versioned_docs/version-2.7/30-administration/40-advanced/40-advanced.md new file mode 100644 index 000000000..4261bbeea --- /dev/null +++ b/docs/versioned_docs/version-2.7/30-administration/40-advanced/40-advanced.md @@ -0,0 +1,25 @@ +# Advanced options + +Why should we be happy with a default setup? We should not! Woodpecker offers a lot of advanced options to configure it to your needs. + +## Behind a proxy + +See the [proxy guide](./10-proxy.md) if you want to see a setup behind Apache, Nginx, Caddy or ngrok. + +In the case you need to use Woodpecker with a URL path prefix (like: ), add the root path to [`WOODPECKER_HOST`](../10-server-config.md#woodpecker_host). + +## SSL + +Woodpecker supports SSL configuration by using Let's encrypt or by using own certificates. See the [SSL guide](./20-ssl.md). + +## Metrics + +A [Prometheus endpoint](./90-prometheus.md) is exposed by Woodpecker to collect metrics. + +## Autoscaling + +The [autoscaler](./30-autoscaler.md) can be used to deploy new agents to a cloud provider based on the current workload your server is experiencing. + +## Configuration service + +Sometime the normal yaml configuration compiler isn't enough. You can use the [configuration service](./100-external-configuration-api.md) to process your configuration files by your own. diff --git a/docs/docs/30-administration/90-prometheus.md b/docs/versioned_docs/version-2.7/30-administration/40-advanced/90-prometheus.md similarity index 100% rename from docs/docs/30-administration/90-prometheus.md rename to docs/versioned_docs/version-2.7/30-administration/40-advanced/90-prometheus.md diff --git a/docs/docs/30-administration/00-deployment/_category_.yaml b/docs/versioned_docs/version-2.7/30-administration/40-advanced/_category_.yaml similarity index 59% rename from docs/docs/30-administration/00-deployment/_category_.yaml rename to docs/versioned_docs/version-2.7/30-administration/40-advanced/_category_.yaml index 728434969..e6c6ba0f7 100644 --- a/docs/docs/30-administration/00-deployment/_category_.yaml +++ b/docs/versioned_docs/version-2.7/30-administration/40-advanced/_category_.yaml @@ -1,6 +1,6 @@ -label: 'Deployment' +label: 'Advanced' collapsible: true collapsed: true link: type: 'doc' - id: 'overview' + id: 'advanced' diff --git a/docs/versioned_docs/version-2.4/30-administration/_category_.yaml b/docs/versioned_docs/version-2.7/30-administration/_category_.yaml similarity index 100% rename from docs/versioned_docs/version-2.4/30-administration/_category_.yaml rename to docs/versioned_docs/version-2.7/30-administration/_category_.yaml diff --git a/docs/versioned_docs/version-2.4/30-administration/new-agent-connected.png b/docs/versioned_docs/version-2.7/30-administration/new-agent-connected.png similarity index 100% rename from docs/versioned_docs/version-2.4/30-administration/new-agent-connected.png rename to docs/versioned_docs/version-2.7/30-administration/new-agent-connected.png diff --git a/docs/versioned_docs/version-2.4/30-administration/new-agent-created.png b/docs/versioned_docs/version-2.7/30-administration/new-agent-created.png similarity index 100% rename from docs/versioned_docs/version-2.4/30-administration/new-agent-created.png rename to docs/versioned_docs/version-2.7/30-administration/new-agent-created.png diff --git a/docs/versioned_docs/version-2.4/30-administration/new-agent-registration.png b/docs/versioned_docs/version-2.7/30-administration/new-agent-registration.png similarity index 100% rename from docs/versioned_docs/version-2.4/30-administration/new-agent-registration.png rename to docs/versioned_docs/version-2.7/30-administration/new-agent-registration.png diff --git a/docs/versioned_docs/version-2.5/40-cli.md b/docs/versioned_docs/version-2.7/40-cli.md similarity index 56% rename from docs/versioned_docs/version-2.5/40-cli.md rename to docs/versioned_docs/version-2.7/40-cli.md index 252017fa7..8e4e62358 100644 --- a/docs/versioned_docs/version-2.5/40-cli.md +++ b/docs/versioned_docs/version-2.7/40-cli.md @@ -2,7 +2,7 @@ # NAME -woodpecker-cli - A new cli application +woodpecker-cli - command line utility # SYNOPSIS @@ -26,7 +26,7 @@ Woodpecker command line utility **Usage**: ``` -woodpecker-cli [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] +woodpecker-cli [GLOBAL OPTIONS] [command [COMMAND OPTIONS]] [ARGUMENTS...] ``` # GLOBAL OPTIONS @@ -35,9 +35,9 @@ woodpecker-cli [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] **--disable-update-check**: disable update check -**--log-file**="": Output destination for logs. 'stdout' and 'stderr' can be used as special keywords. (default: "stderr") +**--log-file**="": Output destination for logs. 'stdout' and 'stderr' can be used as special keywords. (default: stderr) -**--log-level**="": set logging level (default: "info") +**--log-level**="": set logging level (default: info) **--nocolor**: disable colored debug output, only has effect if pretty output is set too @@ -47,411 +47,108 @@ woodpecker-cli [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] **--token, -t**="": server auth token - # COMMANDS -## pipeline +## admin -manage pipelines +administer server settings -### ls +### registry -show pipeline history +manage global registries -**--branch**="": branch filter - -**--event**="": event filter - -**--limit**="": limit the list size (default: 25) - -**--output**="": output format (default: "table") - -**--output-no-headers**: don't print headers - -**--status**="": status filter - -### last - -show latest pipeline details - -**--branch**="": branch name (default: "main") - -**--output**="": output format (default: "table") - -**--output-no-headers**: don't print headers - -### logs - -show pipeline logs - -### info - -show pipeline details - -**--output**="": output format (default: "table") - -**--output-no-headers**: don't print headers - -### stop - -stop a pipeline - -### start - -start a pipeline - -**--param, -p**="": custom parameters to be injected into the step environment. Format: KEY=value - -### approve - -approve a pipeline - -### decline - -decline a pipeline - -### queue - -show pipeline queue - -**--format**="": format output (default: "\x1b[33m{{ .FullName }} #{{ .Number }} \x1b[0m\nStatus: {{ .Status }}\nEvent: {{ .Event }}\nCommit: {{ .Commit }}\nBranch: {{ .Branch }}\nRef: {{ .Ref }}\nAuthor: {{ .Author }} {{ if .Email }}<{{.Email}}>{{ end }}\nMessage: {{ .Message }}\n") - -### ps - -show pipeline steps - -**--format**="": format output (default: "\x1b[33mStep #{{ .PID }} \x1b[0m\nStep: {{ .Name }}\nState: {{ .State }}\n") - -### create - -create new pipeline - -**--branch**="": branch to create pipeline from - -**--output**="": output format (default: "table") - -**--output-no-headers**: don't print headers - -**--var**="": key=value - -## log - -manage logs - -### purge - -purge a log - -## deploy - -deploy code - -**--branch**="": branch filter (default: "main") - -**--event**="": event filter (default: "push") - -**--format**="": format output (default: "Number: {{ .Number }}\nStatus: {{ .Status }}\nCommit: {{ .Commit }}\nBranch: {{ .Branch }}\nRef: {{ .Ref }}\nMessage: {{ .Message }}\nAuthor: {{ .Author }}\nTarget: {{ .Deploy }}\n") - -**--param, -p**="": custom parameters to be injected into the step environment. Format: KEY=value - -**--status**="": status filter (default: "success") - -## exec - -execute a local pipeline - -**--backend-docker-api-version**="": the version of the API to reach, leave empty for latest. - -**--backend-docker-cert**="": path to load the TLS certificates for connecting to docker server - -**--backend-docker-host**="": path to docker socket or url to the docker server - -**--backend-docker-ipv6**: backend docker enable IPV6 - -**--backend-docker-network**="": backend docker network - -**--backend-docker-tls-verify**: enable or disable TLS verification for connecting to docker server - -**--backend-docker-volumes**="": backend docker volumes (comma separated) - -**--backend-engine**="": backend engine to run pipelines on (default: "auto-detect") - -**--backend-http-proxy**="": if set, pass the environment variable down as "HTTP_PROXY" to steps - -**--backend-https-proxy**="": if set, pass the environment variable down as "HTTPS_PROXY" to steps - -**--backend-k8s-namespace**="": backend k8s namespace (default: "woodpecker") - -**--backend-k8s-pod-annotations**="": backend k8s additional Agent-wide worker pod annotations - -**--backend-k8s-pod-annotations-allow-from-step**: whether to allow using annotations from step's backend options - -**--backend-k8s-pod-image-pull-secret-names**="": backend k8s pull secret names for private registries (default: "regcred") - -**--backend-k8s-pod-labels**="": backend k8s additional Agent-wide worker pod labels - -**--backend-k8s-pod-labels-allow-from-step**: whether to allow using labels from step's backend options - -**--backend-k8s-secctx-nonroot**: `run as non root` Kubernetes security context option - -**--backend-k8s-storage-class**="": backend k8s storage class - -**--backend-k8s-storage-rwx**: backend k8s storage access mode, should ReadWriteMany (RWX) instead of ReadWriteOnce (RWO) be used? (default: true) - -**--backend-k8s-volume-size**="": backend k8s volume size (default 10G) (default: "10G") - -**--backend-local-temp-dir**="": set a different temp dir to clone workflows into (default: "/tmp") - -**--backend-no-proxy**="": if set, pass the environment variable down as "NO_PROXY" to steps - -**--commit-author-avatar**="": - -**--commit-author-email**="": - -**--commit-author-name**="": - -**--commit-branch**="": - -**--commit-message**="": - -**--commit-ref**="": - -**--commit-refspec**="": - -**--commit-sha**="": - -**--env**="": - -**--forge-type**="": - -**--forge-url**="": - -**--local**: run from local directory - -**--netrc-machine**="": - -**--netrc-password**="": - -**--netrc-username**="": - -**--network**="": external networks - -**--pipeline-created**="": (default: 0) - -**--pipeline-event**="": (default: "manual") - -**--pipeline-finished**="": (default: 0) - -**--pipeline-number**="": (default: 0) - -**--pipeline-parent**="": (default: 0) - -**--pipeline-started**="": (default: 0) - -**--pipeline-status**="": - -**--pipeline-target**="": - -**--pipeline-task**="": - -**--pipeline-url**="": - -**--prev-commit-author-avatar**="": - -**--prev-commit-author-email**="": - -**--prev-commit-author-name**="": - -**--prev-commit-branch**="": - -**--prev-commit-message**="": - -**--prev-commit-ref**="": - -**--prev-commit-refspec**="": - -**--prev-commit-sha**="": - -**--prev-pipeline-created**="": (default: 0) - -**--prev-pipeline-event**="": - -**--prev-pipeline-finished**="": (default: 0) - -**--prev-pipeline-number**="": (default: 0) - -**--prev-pipeline-started**="": (default: 0) - -**--prev-pipeline-status**="": - -**--prev-pipeline-url**="": - -**--privileged**="": privileged plugins (default: "plugins/docker", "plugins/gcr", "plugins/ecr", "woodpeckerci/plugin-docker-buildx", "codeberg.org/woodpecker-plugins/docker-buildx") - -**--repo**="": full repo name - -**--repo-clone-ssh-url**="": - -**--repo-clone-url**="": - -**--repo-path**="": path to local repository - -**--repo-private**="": - -**--repo-remote-id**="": - -**--repo-trusted**: - -**--repo-url**="": - -**--step-name**="": (default: 0) - -**--system-name**="": (default: "woodpecker") - -**--system-platform**="": - -**--system-url**="": (default: "https://github.com/woodpecker-ci/woodpecker") - -**--timeout**="": pipeline timeout (default: 1h0m0s) - -**--volumes**="": pipeline volumes - -**--workflow-name**="": (default: 0) - -**--workflow-number**="": (default: 0) - -**--workspace-base**="": (default: "/woodpecker") - -**--workspace-path**="": (default: "src") - -## info - -show information about the current user - -## registry - -manage registries - -### add +#### add adds a registry -**--hostname**="": registry hostname (default: "docker.io") +**--hostname**="": registry hostname (default: docker.io) **--password**="": registry password -**--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) - **--username**="": registry username -### rm +#### rm remove a registry -**--hostname**="": registry hostname (default: "docker.io") +**--hostname**="": registry hostname (default: docker.io) -**--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) - -### update +#### update update a registry -**--hostname**="": registry hostname (default: "docker.io") +**--hostname**="": registry hostname (default: docker.io) + +**--organization, --org**="": organization id or full name (e.g. 123 or octocat) **--password**="": registry password -**--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) - **--username**="": registry username -### info +#### info display registry info -**--hostname**="": registry hostname (default: "docker.io") +**--hostname**="": registry hostname (default: docker.io) -**--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) - -### ls +#### ls list registries -**--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) +## org -## secret +manage organizations -manage secrets +### registry -### add +manage organization registries -adds a secret +#### add -**--event**="": secret limited to these events +adds a registry -**--global**: global secret - -**--image**="": secret limited to these images - -**--name**="": secret name +**--hostname**="": registry hostname (default: docker.io) **--organization, --org**="": organization id or full name (e.g. 123 or octocat) -**--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) +**--password**="": registry password -**--value**="": secret value +**--username**="": registry username -### rm +#### rm -remove a secret +remove a registry -**--global**: global secret - -**--name**="": secret name +**--hostname**="": registry hostname (default: docker.io) **--organization, --org**="": organization id or full name (e.g. 123 or octocat) -**--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) +#### update -### update +update a registry -update a secret - -**--event**="": secret limited to these events - -**--global**: global secret - -**--image**="": secret limited to these images - -**--name**="": secret name +**--hostname**="": registry hostname (default: docker.io) **--organization, --org**="": organization id or full name (e.g. 123 or octocat) -**--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) +**--password**="": registry password -**--value**="": secret value +**--username**="": registry username -### info +#### info -display secret info +display registry info -**--global**: global secret - -**--name**="": secret name +**--hostname**="": registry hostname (default: docker.io) **--organization, --org**="": organization id or full name (e.g. 123 or octocat) -**--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) +#### ls -### ls - -list secrets - -**--global**: global secret +list registries **--organization, --org**="": organization id or full name (e.g. 123 or octocat) -**--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) - ## repo manage repositories @@ -460,7 +157,7 @@ manage repositories list all repos -**--format**="": format output (default: "\x1b[33m{{ .FullName }}\x1b[0m (id: {{ .ID }}, forgeRemoteID: {{ .ForgeRemoteID }})") +**--format**="": format output (default: {{ .FullName }} (id: {{ .ID }}, forgeRemoteID: {{ .ForgeRemoteID }})) **--org**="": filter by organization @@ -468,7 +165,17 @@ list all repos show repository details -**--format**="": format output (default: "Owner: {{ .Owner }}\nRepo: {{ .Name }}\nURL: {{ .ForgeURL }}\nConfig path: {{ .Config }}\nVisibility: {{ .Visibility }}\nPrivate: {{ .IsSCMPrivate }}\nTrusted: {{ .IsTrusted }}\nGated: {{ .IsGated }}\nClone url: {{ .Clone }}\nAllow pull-requests: {{ .AllowPullRequests }}\n") +**--format**="": format output (default: Owner: {{ .Owner }} +Repo: {{ .Name }} +URL: {{ .ForgeURL }} +Config path: {{ .Config }} +Visibility: {{ .Visibility }} +Private: {{ .IsSCMPrivate }} +Trusted: {{ .IsTrusted }} +Gated: {{ .IsGated }} +Clone url: {{ .Clone }} +Allow pull-requests: {{ .AllowPullRequests }} +) ### add @@ -508,7 +215,485 @@ assume ownership of a repository synchronize the repository list -**--format**="": format output (default: "\x1b[33m{{ .FullName }}\x1b[0m (id: {{ .ID }}, forgeRemoteID: {{ .ForgeRemoteID }})") +**--format**="": format output (default: {{ .FullName }} (id: {{ .ID }}, forgeRemoteID: {{ .ForgeRemoteID }})) + +### registry + +manage registries + +#### add + +adds a registry + +**--hostname**="": registry hostname (default: docker.io) + +**--password**="": registry password + +**--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) + +**--username**="": registry username + +#### rm + +remove a registry + +**--hostname**="": registry hostname (default: docker.io) + +**--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) + +#### update + +update a registry + +**--hostname**="": registry hostname (default: docker.io) + +**--password**="": registry password + +**--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) + +**--username**="": registry username + +#### info + +display registry info + +**--hostname**="": registry hostname (default: docker.io) + +**--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) + +#### ls + +list registries + +**--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) + +## pipeline + +manage pipelines + +### ls + +show pipeline history + +**--branch**="": branch filter + +**--event**="": event filter + +**--limit**="": limit the list size (default: 25) + +**--output**="": output format (default: table) + +**--output-no-headers**: don't print headers + +**--status**="": status filter + +### last + +show latest pipeline details + +**--branch**="": branch name (default: main) + +**--output**="": output format (default: table) + +**--output-no-headers**: don't print headers + +### logs + +show pipeline logs + +### info + +show pipeline details + +**--output**="": output format (default: table) + +**--output-no-headers**: don't print headers + +### stop + +stop a pipeline + +### start + +start a pipeline + +**--param, -p**="": custom parameters to be injected into the step environment. Format: KEY=value (default: []) + +### approve + +approve a pipeline + +### decline + +decline a pipeline + +### queue + +show pipeline queue + +**--format**="": format output (default: {{ .FullName }} #{{ .Number }}  +Status: {{ .Status }} +Event: {{ .Event }} +Commit: {{ .Commit }} +Branch: {{ .Branch }} +Ref: {{ .Ref }} +Author: {{ .Author }} {{ if .Email }}<{{.Email}}>{{ end }} +Message: {{ .Message }} +) + +### ps + +show pipeline steps + +**--format**="": format output (default: {{ .workflow.Name }} > {{ .step.Name }} (#{{ .step.PID }}): +Step: {{ .step.Name }} +Started: {{ .step.Started }} +Stopped: {{ .step.Stopped }} +Type: {{ .step.Type }} +State: {{ .step.State }} +) + +### create + +create new pipeline + +**--branch**="": branch to create pipeline from + +**--output**="": output format (default: table) + +**--output-no-headers**: don't print headers + +**--var**="": key=value (default: []) + +## log + +manage logs + +### purge + +purge a log + +## deploy + +trigger a pipeline with the 'deployment' event + +**--branch**="": branch filter + +**--event**="": event filter (default: push) + +**--format**="": format output (default: Number: {{ .Number }} +Status: {{ .Status }} +Commit: {{ .Commit }} +Branch: {{ .Branch }} +Ref: {{ .Ref }} +Message: {{ .Message }} +Author: {{ .Author }} +Target: {{ .Deploy }} +) + +**--param, -p**="": custom parameters to be injected into the step environment. Format: KEY=value (default: []) + +**--status**="": status filter (default: success) + +## exec + +execute a local pipeline + +**--backend-docker-api-version**="": the version of the API to reach, leave empty for latest. + +**--backend-docker-cert**="": path to load the TLS certificates for connecting to docker server + +**--backend-docker-host**="": path to docker socket or url to the docker server + +**--backend-docker-ipv6**: backend docker enable IPV6 + +**--backend-docker-network**="": backend docker network + +**--backend-docker-tls-verify**: enable or disable TLS verification for connecting to docker server + +**--backend-docker-volumes**="": backend docker volumes (comma separated) + +**--backend-engine**="": backend engine to run pipelines on (default: auto-detect) + +**--backend-http-proxy**="": if set, pass the environment variable down as "HTTP_PROXY" to steps + +**--backend-https-proxy**="": if set, pass the environment variable down as "HTTPS_PROXY" to steps + +**--backend-k8s-allow-native-secrets**: whether to allow existing Kubernetes secrets to be referenced from steps + +**--backend-k8s-namespace**="": backend k8s namespace (default: woodpecker) + +**--backend-k8s-pod-annotations**="": backend k8s additional Agent-wide worker pod annotations + +**--backend-k8s-pod-annotations-allow-from-step**: whether to allow using annotations from step's backend options + +**--backend-k8s-pod-image-pull-secret-names**="": backend k8s pull secret names for private registries (default: [regcred]) + +**--backend-k8s-pod-labels**="": backend k8s additional Agent-wide worker pod labels + +**--backend-k8s-pod-labels-allow-from-step**: whether to allow using labels from step's backend options + +**--backend-k8s-pod-node-selector**="": backend k8s Agent-wide worker pod node selector + +**--backend-k8s-secctx-nonroot**: `run as non root` Kubernetes security context option + +**--backend-k8s-storage-class**="": backend k8s storage class + +**--backend-k8s-storage-rwx**: backend k8s storage access mode, should ReadWriteMany (RWX) instead of ReadWriteOnce (RWO) be used? (default: true) + +**--backend-k8s-volume-size**="": backend k8s volume size (default 10G) (default: 10G) + +**--backend-local-temp-dir**="": set a different temp dir to clone workflows into (default: /tmp/nix-shell.kGX6ZV) + +**--backend-no-proxy**="": if set, pass the environment variable down as "NO_PROXY" to steps + +**--commit-author-avatar**="": + +**--commit-author-email**="": + +**--commit-author-name**="": + +**--commit-branch**="": + +**--commit-message**="": + +**--commit-ref**="": + +**--commit-refspec**="": + +**--commit-sha**="": + +**--env**="": (default: []) + +**--forge-type**="": + +**--forge-url**="": + +**--local**: run from local directory + +**--netrc-machine**="": + +**--netrc-password**="": + +**--netrc-username**="": + +**--network**="": external networks (default: []) + +**--pipeline-created**="": (default: 0) + +**--pipeline-deploy-task**="": + +**--pipeline-deploy-to**="": + +**--pipeline-event**="": (default: manual) + +**--pipeline-finished**="": (default: 0) + +**--pipeline-number**="": (default: 0) + +**--pipeline-parent**="": (default: 0) + +**--pipeline-started**="": (default: 0) + +**--pipeline-status**="": + +**--pipeline-url**="": + +**--prev-commit-author-avatar**="": + +**--prev-commit-author-email**="": + +**--prev-commit-author-name**="": + +**--prev-commit-branch**="": + +**--prev-commit-message**="": + +**--prev-commit-ref**="": + +**--prev-commit-refspec**="": + +**--prev-commit-sha**="": + +**--prev-pipeline-created**="": (default: 0) + +**--prev-pipeline-event**="": + +**--prev-pipeline-finished**="": (default: 0) + +**--prev-pipeline-number**="": (default: 0) + +**--prev-pipeline-started**="": (default: 0) + +**--prev-pipeline-status**="": + +**--prev-pipeline-url**="": + +**--privileged**="": privileged plugins (default: [plugins/docker plugins/gcr plugins/ecr woodpeckerci/plugin-docker-buildx codeberg.org/woodpecker-plugins/docker-buildx]) + +**--repo**="": full repo name + +**--repo-clone-ssh-url**="": + +**--repo-clone-url**="": + +**--repo-path**="": path to local repository + +**--repo-private**="": + +**--repo-remote-id**="": + +**--repo-trusted**: + +**--repo-url**="": + +**--step-name**="": (default: 0) + +**--system-name**="": (default: woodpecker) + +**--system-platform**="": + +**--system-url**="": (default: https://github.com/woodpecker-ci/woodpecker) + +**--timeout**="": pipeline timeout (default: 1h0m0s) + +**--volumes**="": pipeline volumes (default: []) + +**--workflow-name**="": (default: 0) + +**--workflow-number**="": (default: 0) + +**--workspace-base**="": (default: /woodpecker) + +**--workspace-path**="": (default: src) + +## info + +show information about the current user + +## registry + +manage registries + +### add + +adds a registry + +**--hostname**="": registry hostname (default: docker.io) + +**--password**="": registry password + +**--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) + +**--username**="": registry username + +### rm + +remove a registry + +**--hostname**="": registry hostname (default: docker.io) + +**--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) + +### update + +update a registry + +**--hostname**="": registry hostname (default: docker.io) + +**--password**="": registry password + +**--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) + +**--username**="": registry username + +### info + +display registry info + +**--hostname**="": registry hostname (default: docker.io) + +**--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) + +### ls + +list registries + +**--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) + +## secret + +manage secrets + +### add + +adds a secret + +**--event**="": secret limited to these events (default: []) + +**--global**: global secret + +**--image**="": secret limited to these images (default: []) + +**--name**="": secret name + +**--organization, --org**="": organization id or full name (e.g. 123 or octocat) + +**--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) + +**--value**="": secret value + +### rm + +remove a secret + +**--global**: global secret + +**--name**="": secret name + +**--organization, --org**="": organization id or full name (e.g. 123 or octocat) + +**--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) + +### update + +update a secret + +**--event**="": secret limited to these events (default: []) + +**--global**: global secret + +**--image**="": secret limited to these images (default: []) + +**--name**="": secret name + +**--organization, --org**="": organization id or full name (e.g. 123 or octocat) + +**--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) + +**--value**="": secret value + +### info + +display secret info + +**--global**: global secret + +**--name**="": secret name + +**--organization, --org**="": organization id or full name (e.g. 123 or octocat) + +**--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) + +### ls + +list secrets + +**--global**: global secret + +**--organization, --org**="": organization id or full name (e.g. 123 or octocat) + +**--repository, --repo**="": repository id or full name (e.g. 134 or octocat/hello-world) ## user @@ -518,13 +703,14 @@ manage users list all users -**--format**="": format output (default: "{{ .Login }}") +**--format**="": format output (default: {{ .Login }}) ### info show user details -**--format**="": format output (default: "User: {{ .Login }}\nEmail: {{ .Email }}") +**--format**="": format output (default: User: {{ .Login }} +Email: {{ .Email }}) ### add @@ -598,7 +784,7 @@ list cron jobs setup the woodpecker-cli for the first time -**--server-url**="": The URL of the woodpecker server +**--server**="": The URL of the woodpecker server **--token**="": The token to authenticate with the woodpecker server diff --git a/docs/versioned_docs/version-2.7/50-about.md b/docs/versioned_docs/version-2.7/50-about.md new file mode 100644 index 000000000..bec3304a1 --- /dev/null +++ b/docs/versioned_docs/version-2.7/50-about.md @@ -0,0 +1,18 @@ +# About + +Woodpecker has been originally forked from Drone 0.8 as the Drone CI license was changed after the 0.8 release from Apache 2.0 to a proprietary license. Woodpecker is based on this latest freely available version. + +## History + +Woodpecker was originally forked by [@laszlocph](https://github.com/laszlocph) in 2019. + +A few important time points: + +- [`2fbaa56`](https://github.com/woodpecker-ci/woodpecker/commit/2fbaa56eee0f4be7a3ca4be03dbd00c1bf5d1274) is the first commit of the fork, made on Apr 3, 2019. +- The first release [v0.8.91](https://github.com/woodpecker-ci/woodpecker/releases/tag/v0.8.91) was published on Apr 6, 2019. +- On Aug 27, 2019, the project was renamed to "Woodpecker" ([`630c383`](https://github.com/woodpecker-ci/woodpecker/commit/630c383181b10c4ec375e500c812c4b76b3c52b8)). +- The first release under the name "Woodpecker" was published on Sep 9, 2019 ([v0.8.104](https://github.com/woodpecker-ci/woodpecker/releases/tag/v0.8.104)). + +## Differences to Drone + +Woodpecker is a community-focused software that still stay free and open source forever, while Drone is managed by [Harness](https://harness.io/) and published under [Polyform Small Business](https://polyformproject.org/licenses/small-business/1.0.0/) license. diff --git a/docs/docs/91-migrations.md b/docs/versioned_docs/version-2.7/91-migrations.md similarity index 96% rename from docs/docs/91-migrations.md rename to docs/versioned_docs/version-2.7/91-migrations.md index 841dee3d0..84a79ac5e 100644 --- a/docs/docs/91-migrations.md +++ b/docs/versioned_docs/version-2.7/91-migrations.md @@ -37,7 +37,7 @@ Some versions need some changes to the server configuration or the pipeline conf ## 1.0.0 -- The signature used to verify extension calls (like those used for the [config-extension](./30-administration/100-external-configuration-api.md)) done by the Woodpecker server switched from using a shared-secret HMac to an ed25519 key-pair. Read more about it at the [config-extensions](./30-administration/100-external-configuration-api.md) documentation. +- The signature used to verify extension calls (like those used for the [config-extension](./30-administration/40-advanced/100-external-configuration-api.md)) done by the Woodpecker server switched from using a shared-secret HMac to an ed25519 key-pair. Read more about it at the [config-extensions](./30-administration/40-advanced/100-external-configuration-api.md) documentation. - Refactored support for old agent filter labels and expressions. Learn how to use the new [filter](./20-usage/20-workflow-syntax.md#labels) - Renamed step environment variable `CI_SYSTEM_ARCH` to `CI_SYSTEM_PLATFORM`. Same applies for the cli exec variable. - Renamed environment variables `CI_BUILD_*` and `CI_PREV_BUILD_*` to `CI_PIPELINE_*` and `CI_PREV_PIPELINE_*`, old ones are still available but deprecated diff --git a/docs/docs/92-awesome.md b/docs/versioned_docs/version-2.7/92-awesome.md similarity index 100% rename from docs/docs/92-awesome.md rename to docs/versioned_docs/version-2.7/92-awesome.md diff --git a/docs/versioned_docs/version-2.5/92-development/01-getting-started.md b/docs/versioned_docs/version-2.7/92-development/01-getting-started.md similarity index 100% rename from docs/versioned_docs/version-2.5/92-development/01-getting-started.md rename to docs/versioned_docs/version-2.7/92-development/01-getting-started.md diff --git a/docs/versioned_docs/version-2.5/92-development/02-core-ideas.md b/docs/versioned_docs/version-2.7/92-development/02-core-ideas.md similarity index 93% rename from docs/versioned_docs/version-2.5/92-development/02-core-ideas.md rename to docs/versioned_docs/version-2.7/92-development/02-core-ideas.md index 8e0d6e292..a88470f0a 100644 --- a/docs/versioned_docs/version-2.5/92-development/02-core-ideas.md +++ b/docs/versioned_docs/version-2.7/92-development/02-core-ideas.md @@ -8,7 +8,7 @@ ## Addons and extensions If you are wondering whether your contribution will be accepted to be merged in the Woodpecker core, or whether it's better to write an -[addon forge](../30-administration/11-forges/100-addon.md), [extension](../30-administration/100-external-configuration-api.md) or an +[addon forge](../30-administration/11-forges/100-addon.md), [extension](../30-administration/40-advanced/100-external-configuration-api.md) or an [external custom backend](../30-administration/22-backends/50-custom-backends.md), please check these points: - Is your change very specific to your setup and unlikely to be used by anyone else? diff --git a/docs/versioned_docs/version-2.4/92-development/03-ui.md b/docs/versioned_docs/version-2.7/92-development/03-ui.md similarity index 98% rename from docs/versioned_docs/version-2.4/92-development/03-ui.md rename to docs/versioned_docs/version-2.7/92-development/03-ui.md index e8999b0be..6a01584c2 100644 --- a/docs/versioned_docs/version-2.4/92-development/03-ui.md +++ b/docs/versioned_docs/version-2.7/92-development/03-ui.md @@ -36,4 +36,4 @@ The following list contains some tools and frameworks used by the Woodpecker UI. Woodpecker uses [Vue I18n](https://vue-i18n.intlify.dev/) as translation library. New translations have to be added to `web/src/assets/locales/en.json`. The English source file will be automatically imported into [Weblate](https://translate.woodpecker-ci.org/) (the translation system used by Woodpecker) where all other languages will be translated by the community based on the English source. You must not provide translations except English in PRs, otherwise weblate could put git into conflicts (when someone has translated in that language file and changes are not into main branch yet) -For more information about translations see [Translations](./07-translations.md). +For more information about translations see [Translations](./08-translations.md). diff --git a/docs/versioned_docs/version-2.4/92-development/04-docs.md b/docs/versioned_docs/version-2.7/92-development/04-docs.md similarity index 100% rename from docs/versioned_docs/version-2.4/92-development/04-docs.md rename to docs/versioned_docs/version-2.7/92-development/04-docs.md diff --git a/docs/versioned_docs/version-2.5/92-development/05-architecture.md b/docs/versioned_docs/version-2.7/92-development/05-architecture.md similarity index 87% rename from docs/versioned_docs/version-2.5/92-development/05-architecture.md rename to docs/versioned_docs/version-2.7/92-development/05-architecture.md index ccfd410f3..535ec6a80 100644 --- a/docs/versioned_docs/version-2.5/92-development/05-architecture.md +++ b/docs/versioned_docs/version-2.7/92-development/05-architecture.md @@ -19,23 +19,23 @@ ### Server -| package | meaning | imports | -| -------------------- | ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `server/api/**` | handle web requests from `server/router` | `pipeline`, `../badges`, `../ccmenue`, `../logging`, `../model`, `../pubsub`, `../queue`, `../forge`, `../shared`, `../store`, `shared`, (TODO: mv `server/router/middleware/session`) | -| `server/badges/**` | generate svg badges for pipelines | `../model` | -| `server/ccmenu/**` | generate xml ccmenu for pipelines | `../model` | -| `server/grpc/**` | gRPC server agents can connect to | `pipeline/rpc/**`, `../logging`, `../model`, `../pubsub`, `../queue`, `../forge`, `../pipeline`, `../store` | -| `server/logging/**` | logging lib for gPRC server to stream logs while running | std | -| `server/model/**` | structs for store (db) and api (json) | std | -| `server/plugins/**` | plugins for server | `../model`, `../forge` | -| `server/pipeline/**` | orchestrate pipelines | `pipeline`, `../model`, `../pubsub`, `../queue`, `../forge`, `../store`, `../plugins` | -| `server/pubsub/**` | pubsub lib for server to push changes to the WebUI | std | -| `server/queue/**` | queue lib for server where agents pull new pipelines from via gRPC | `server/model` | -| `server/forge/**` | forge lib for server to connect and handle forge specific stuff | `shared`, `server/model` | -| `server/router/**` | handle requests to REST API (and all middleware) and serve UI and WebUI config | `shared`, `../api`, `../model`, `../forge`, `../store`, `../web` | -| `server/store/**` | handle database | `server/model` | -| `server/shared/**` | TODO: move and split [#974](https://github.com/woodpecker-ci/woodpecker/issues/974) | | -| `server/web/**` | server SPA | | +| package | meaning | imports | +| -------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `server/api/**` | handle web requests from `server/router` | `pipeline`, `../badges`, `../ccmenu`, `../logging`, `../model`, `../pubsub`, `../queue`, `../forge`, `../shared`, `../store`, `shared`, (TODO: mv `server/router/middleware/session`) | +| `server/badges/**` | generate svg badges for pipelines | `../model` | +| `server/ccmenu/**` | generate xml ccmenu for pipelines | `../model` | +| `server/grpc/**` | gRPC server agents can connect to | `pipeline/rpc/**`, `../logging`, `../model`, `../pubsub`, `../queue`, `../forge`, `../pipeline`, `../store` | +| `server/logging/**` | logging lib for gPRC server to stream logs while running | std | +| `server/model/**` | structs for store (db) and api (json) | std | +| `server/plugins/**` | plugins for server | `../model`, `../forge` | +| `server/pipeline/**` | orchestrate pipelines | `pipeline`, `../model`, `../pubsub`, `../queue`, `../forge`, `../store`, `../plugins` | +| `server/pubsub/**` | pubsub lib for server to push changes to the WebUI | std | +| `server/queue/**` | queue lib for server where agents pull new pipelines from via gRPC | `server/model` | +| `server/forge/**` | forge lib for server to connect and handle forge specific stuff | `shared`, `server/model` | +| `server/router/**` | handle requests to REST API (and all middleware) and serve UI and WebUI config | `shared`, `../api`, `../model`, `../forge`, `../store`, `../web` | +| `server/store/**` | handle database | `server/model` | +| `server/shared/**` | TODO: move and split [#974](https://github.com/woodpecker-ci/woodpecker/issues/974) | | +| `server/web/**` | server SPA | | - `../` = `server/` diff --git a/docs/versioned_docs/version-2.7/92-development/06-conventions.md b/docs/versioned_docs/version-2.7/92-development/06-conventions.md new file mode 100644 index 000000000..e94a90c43 --- /dev/null +++ b/docs/versioned_docs/version-2.7/92-development/06-conventions.md @@ -0,0 +1,7 @@ +# Conventions + +## Database naming + +Database tables are named plural, columns don't have any prefix. + +Example: Table name `agent`, columns `id`, `name`. diff --git a/docs/versioned_docs/version-2.5/92-development/06-guides.md b/docs/versioned_docs/version-2.7/92-development/07-guides.md similarity index 100% rename from docs/versioned_docs/version-2.5/92-development/06-guides.md rename to docs/versioned_docs/version-2.7/92-development/07-guides.md diff --git a/docs/versioned_docs/version-2.4/92-development/07-translations.md b/docs/versioned_docs/version-2.7/92-development/08-translations.md similarity index 100% rename from docs/versioned_docs/version-2.4/92-development/07-translations.md rename to docs/versioned_docs/version-2.7/92-development/08-translations.md diff --git a/docs/versioned_docs/version-2.4/92-development/08-swagger.md b/docs/versioned_docs/version-2.7/92-development/09-swagger.md similarity index 94% rename from docs/versioned_docs/version-2.4/92-development/08-swagger.md rename to docs/versioned_docs/version-2.7/92-development/09-swagger.md index 9a3775c41..5bf303c7b 100644 --- a/docs/versioned_docs/version-2.4/92-development/08-swagger.md +++ b/docs/versioned_docs/version-2.7/92-development/09-swagger.md @@ -7,7 +7,7 @@ and then being using on the community's website documentation. It's paramount important to keep the gin handler function's godoc documentation up-to-date, to always have accurate API documentation. -Whenever you change, add or enhance an API endpoint, please update the godocs. +Whenever you change, add or enhance an API endpoint, please update the godoc. You don't require any extra tools on your machine, all Swagger tooling is automatically fetched by standard Go tools. @@ -41,8 +41,8 @@ These guidelines aim to have consistent wording in the swagger doc: - first word after `@Summary` and `@Summary` are always uppercase - `@Summary` has no `.` (dot) at the end of the line - model structs shall use custom short names, to ease life for API consumers, using `@name` -- `@Success` object or array declarations shall be short, this means the actual `model.User` struct must have a `@name` annotation, so that the model can be renderend in Swagger -- when pagination is used, `@Parame page` and `@Parame perPage` must be added manually +- `@Success` object or array declarations shall be short, this means the actual `model.User` struct must have a `@name` annotation, so that the model can be rendered in Swagger +- when pagination is used, `@Param page` and `@Param perPage` must be added manually - `@Param Authorization` is almost always present, there are just a few un-protected endpoints There are many examples in the `server/api` package, which you can use a blueprint. diff --git a/docs/versioned_docs/version-2.7/92-development/09-testing.md b/docs/versioned_docs/version-2.7/92-development/09-testing.md new file mode 100644 index 000000000..b5b8ed2f2 --- /dev/null +++ b/docs/versioned_docs/version-2.7/92-development/09-testing.md @@ -0,0 +1,81 @@ +# Testing + +## Backend + +### Unit Tests + +[We use default golang unit tests](https://go.dev/doc/tutorial/add-a-test) +with [`"github.com/stretchr/testify/assert"`](https://pkg.go.dev/github.com/stretchr/testify@v1.9.0/assert) to simplify testing. + +### Integration Tests + +### Dummy backend + +There is a special backend called **`dummy`** which does not execute any commands, but emulates how a typical backend should behave. +To enable it you need to build the agent or cli with the `test` build tag. + +An example pipeline config would be: + +```yaml +when: + event: manual + +steps: + - name: echo + image: dummy + commands: echo "hello woodpecker" + environment: + SLEEP: '1s' + +services: + echo: + image: dummy + commands: echo "i am a service" +``` + +This could be executed via `woodpecker-cli --log-level trace exec --backend-engine dummy example.yaml`: + +```none +9:18PM DBG pipeline/pipeline.go:94 > executing 2 stages, in order of: CLI=exec +9:18PM DBG pipeline/pipeline.go:104 > stage CLI=exec StagePos=0 Steps=echo +9:18PM DBG pipeline/pipeline.go:104 > stage CLI=exec StagePos=1 Steps=echo +9:18PM TRC pipeline/backend/dummy/dummy.go:75 > create workflow environment taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM DBG pipeline/pipeline.go:176 > prepare CLI=exec step=echo +9:18PM DBG pipeline/pipeline.go:203 > executing CLI=exec step=echo +9:18PM TRC pipeline/backend/dummy/dummy.go:81 > start step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM TRC pipeline/backend/dummy/dummy.go:167 > tail logs of step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM DBG pipeline/pipeline.go:209 > complete CLI=exec step=echo +[echo:L0:0s] StepName: echo +[echo:L1:0s] StepType: service +[echo:L2:0s] StepUUID: 01J10P578JQE6E25VV1A2DNQN9 +[echo:L3:0s] StepCommands: +[echo:L4:0s] ------------------ +[echo:L5:0s] echo ja +[echo:L6:0s] ------------------ +[echo:L7:0s] 9:18PM DBG pipeline/pipeline.go:176 > prepare CLI=exec step=echo +9:18PM DBG pipeline/pipeline.go:203 > executing CLI=exec step=echo +9:18PM TRC pipeline/backend/dummy/dummy.go:81 > start step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM TRC pipeline/backend/dummy/dummy.go:167 > tail logs of step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +[echo:L0:0s] StepName: echo +[echo:L1:0s] StepType: commands +[echo:L2:0s] StepUUID: 01J10P578JQE6E25VV1DFSXX1Y +[echo:L3:0s] StepCommands: +[echo:L4:0s] ------------------ +[echo:L5:0s] echo ja +[echo:L6:0s] ------------------ +[echo:L7:0s] 9:18PM TRC pipeline/backend/dummy/dummy.go:108 > wait for step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM TRC pipeline/backend/dummy/dummy.go:187 > stop step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM DBG pipeline/pipeline.go:209 > complete CLI=exec step=echo +9:18PM TRC pipeline/backend/dummy/dummy.go:208 > delete workflow environment taskUUID=01J10P578JQE6E25VV1EQF0745 +``` + +There are also environment variables to alter step behavior: + +- `SLEEP: 10` will let the step wait 10 seconds +- `EXPECT_TYPE` allows to check if a step is a `clone`, `service`, `plugin` or `commands` +- `STEP_START_FAIL: true` if set will simulate a step to fail before actually being started (e.g. happens when the container image can not be pulled) +- `STEP_TAIL_FAIL: true` if set will error when we simulate to read from stdout for logs +- `STEP_EXIT_CODE: 2` if set will be used as exit code, default is 0 +- `STEP_OOM_KILLED: true` simulates a step being killed by memory constrains + +You can let the setup of a whole workflow fail by setting it's UUID to `WorkflowSetupShouldFail`. diff --git a/docs/versioned_docs/version-2.4/92-development/_category_.yaml b/docs/versioned_docs/version-2.7/92-development/_category_.yaml similarity index 100% rename from docs/versioned_docs/version-2.4/92-development/_category_.yaml rename to docs/versioned_docs/version-2.7/92-development/_category_.yaml diff --git a/docs/versioned_docs/version-2.4/92-development/ui-proxy.svg b/docs/versioned_docs/version-2.7/92-development/ui-proxy.svg similarity index 100% rename from docs/versioned_docs/version-2.4/92-development/ui-proxy.svg rename to docs/versioned_docs/version-2.7/92-development/ui-proxy.svg diff --git a/docs/versioned_docs/version-2.4/92-development/vscode-debug.png b/docs/versioned_docs/version-2.7/92-development/vscode-debug.png similarity index 100% rename from docs/versioned_docs/version-2.4/92-development/vscode-debug.png rename to docs/versioned_docs/version-2.7/92-development/vscode-debug.png diff --git a/docs/versioned_docs/version-2.4/92-development/vscode-run-test.png b/docs/versioned_docs/version-2.7/92-development/vscode-run-test.png similarity index 100% rename from docs/versioned_docs/version-2.4/92-development/vscode-run-test.png rename to docs/versioned_docs/version-2.7/92-development/vscode-run-test.png diff --git a/docs/versioned_docs/version-2.4/92-development/woodpecker-architecture.png b/docs/versioned_docs/version-2.7/92-development/woodpecker-architecture.png similarity index 100% rename from docs/versioned_docs/version-2.4/92-development/woodpecker-architecture.png rename to docs/versioned_docs/version-2.7/92-development/woodpecker-architecture.png diff --git a/docs/versioned_docs/version-2.7/pipeline-list.png b/docs/versioned_docs/version-2.7/pipeline-list.png new file mode 100644 index 000000000..f501fe0e6 Binary files /dev/null and b/docs/versioned_docs/version-2.7/pipeline-list.png differ diff --git a/docs/docs/woodpecker.png b/docs/versioned_docs/version-2.7/woodpecker.png similarity index 100% rename from docs/docs/woodpecker.png rename to docs/versioned_docs/version-2.7/woodpecker.png diff --git a/docs/versioned_docs/version-2.8/10-intro/index.md b/docs/versioned_docs/version-2.8/10-intro/index.md new file mode 100644 index 000000000..7d9ced179 --- /dev/null +++ b/docs/versioned_docs/version-2.8/10-intro/index.md @@ -0,0 +1,26 @@ +# Welcome to Woodpecker + +Woodpecker is a CI/CD tool. It is designed to be lightweight, simple to use and fast. Before we dive into the details, let's have a look at some of the basics. + +## Have you ever heard of CI/CD or pipelines? + +Don't worry if you haven't. We'll guide you through the basics. CI/CD stands for Continuous Integration and Continuous Deployment. It's basically like a conveyor belt that moves your code from development to production doing all kinds of +checks, tests and routines along the way. A typical pipeline might include the following steps: + +1. Running tests +2. Building your application +3. Deploying your application + +[Have a deeper look into the idea of CI/CD](https://www.redhat.com/en/topics/devops/what-is-ci-cd) + +## Do you know containers? + +If you are already using containers in your daily workflow, you'll for sure love Woodpecker. If not yet, you'll be amazed how easy it is to get started with [containers](https://opencontainers.org/). + +## Already have access to a Woodpecker instance? + +Then you might want to jump directly into it and [start creating your first pipelines](../20-usage/10-intro.md). + +## Want to start from scratch and deploy your own Woodpecker instance? + +Woodpecker is [pretty lightweight](../30-administration/00-getting-started.md#hardware-requirements) and will even run on your Raspberry Pi. You can follow the [deployment guide](../30-administration/00-getting-started.md) to set up your own Woodpecker instance. diff --git a/docs/versioned_docs/version-2.8/20-usage/10-intro.md b/docs/versioned_docs/version-2.8/20-usage/10-intro.md new file mode 100644 index 000000000..9c4cb3c21 --- /dev/null +++ b/docs/versioned_docs/version-2.8/20-usage/10-intro.md @@ -0,0 +1,109 @@ +# Your first pipeline + +Let's get started and create your first pipeline. + +## 1. Repository Activation + +To activate your repository in Woodpecker navigate to the repository list and `New repository`. You will see a list of repositories from your forge (GitHub, Gitlab, ...) which can be activated with a simple click. + +![new repository list](repo-new.png) + +To enable a repository in Woodpecker you must have `Admin` rights on that repository, so that Woodpecker can add something +that is called a webhook (Woodpecker needs it to know about actions like pushes, pull requests, tags, etc.). + +## 2. Define first workflow + +After enabling a repository Woodpecker will listen for changes in your repository. When a change is detected, Woodpecker will check for a pipeline configuration. So let's create a file at `.woodpecker/my-first-workflow.yaml` inside your repository: + +```yaml title=".woodpecker/my-first-workflow.yaml" +when: + - event: push + branch: main + +steps: + - name: build + image: debian + commands: + - echo "This is the build step" + - echo "binary-data-123" > executable + - name: a-test-step + image: golang:1.16 + commands: + - echo "Testing ..." + - ./executable +``` + +**So what did we do here?** + +1. We defined your first workflow file `my-first-workflow.yaml`. +2. This workflow will be executed when a push event happens on the `main` branch, + because we added a filter using the `when` section: + + ```diff + + when: + + - event: push + + branch: main + + ... + ``` + +3. We defined two steps: `build` and `a-test-step` + +The steps are executed in the order they are defined, so `build` will be executed first and then `a-test-step`. + +In the `build` step we use the `debian` image and build a "binary file" called `executable`. + +In the `a-test-step` we use the `golang:1.16` image and run the `executable` file to test it. + +You can use any image from registries like the [Docker Hub](https://hub.docker.com/search?type=image) you have access to: + +```diff + steps: + - name: build +- image: debian ++ image: mycompany/image-with-awscli + commands: + - aws help +``` + +## 3. Push the file and trigger first pipeline + +If you push this file to your repository now, Woodpecker will already execute your first pipeline. + +You can check the pipeline execution in the Woodpecker UI by navigating to the `Pipelines` section of your repository. + +![pipeline view](./pipeline.png) + +As you probably noticed, there is another step in called `clone` which is executed before your steps. This step clones your repository into a folder called `workspace` which is available throughout all steps. + +This for example allows the first step to build your application using your source code and as the second step will receive +the same workspace it can use the previously built binary and test it. + +## 4. Use a plugin for reusable tasks + +Sometimes you have some tasks that you need to do in every project. For example, deploying to Kubernetes or sending a Slack message. Therefore you can use one of the [official and community plugins](/plugins) or simply [create your own](./51-plugins/20-creating-plugins.md). + +If you want to get a Slack notification after your pipeline has finished, you can add a Slack plugin to your pipeline: + +```yaml +--- +- name: notify me on Slack + image: plugins/slack + settings: + channel: developers + username: woodpecker + password: + from_secret: slack_token + when: + status: [success, failure] # This will execute the step on success and failure +``` + +To configure a plugin you can use the `settings` section. + +Sometime you need to provide secrets to the plugin. You can do this by using the `from_secret` key. The secret must be defined in the Woodpecker UI. You can find more information about secrets [here](./40-secrets.md). + +Similar to the `when` section at the top of the file which is for the complete workflow, you can use the `when` section for each step to define when a step should be executed. + +Learn more about [plugins](./51-plugins/51-overview.md). + +As you now have a basic understanding of how to create a pipeline, you can dive deeper into the [workflow syntax](./20-workflow-syntax.md) and [plugins](./51-plugins/51-overview.md). diff --git a/docs/versioned_docs/version-2.8/20-usage/100-troubleshooting.md b/docs/versioned_docs/version-2.8/20-usage/100-troubleshooting.md new file mode 100644 index 000000000..b961530f4 --- /dev/null +++ b/docs/versioned_docs/version-2.8/20-usage/100-troubleshooting.md @@ -0,0 +1,37 @@ +# Troubleshooting + +## How to debug clone issues + +(And what to do with an error message like `fatal: could not read Username for 'https://': No such device or address`) + +This error can have multiple causes. If you use internal repositories you might have to enable `WOODPECKER_AUTHENTICATE_PUBLIC_REPOS`: + +```ini +WOODPECKER_AUTHENTICATE_PUBLIC_REPOS=true +``` + +If that does not work, try to make sure the container can reach your git server. In order to do that disable git checkout and make the container "hang": + +```yaml +skip_clone: true + +steps: + build: + image: debian:stable-backports + commands: + - apt update + - apt install -y inetutils-ping wget + - ping -c 4 git.example.com + - wget git.example.com + - sleep 9999999 +``` + +Get the container id using `docker ps` and copy the id from the first column. Enter the container with: `docker exec -it 1234asdf bash` (replace `1234asdf` with the docker id). Then try to clone the git repository with the commands from the failing pipeline: + +```bash +git init +git remote add origin https://git.example.com/username/repo.git +git fetch --no-tags origin +refs/heads/branch: +``` + +(replace the url AND the branch with the correct values, use your username and password as log in values) diff --git a/docs/versioned_docs/version-2.5/20-usage/15-terminology/architecture.excalidraw b/docs/versioned_docs/version-2.8/20-usage/15-terminology/architecture.excalidraw similarity index 100% rename from docs/versioned_docs/version-2.5/20-usage/15-terminology/architecture.excalidraw rename to docs/versioned_docs/version-2.8/20-usage/15-terminology/architecture.excalidraw diff --git a/docs/versioned_docs/version-2.5/20-usage/15-terminology/architecture.svg b/docs/versioned_docs/version-2.8/20-usage/15-terminology/architecture.svg similarity index 100% rename from docs/versioned_docs/version-2.5/20-usage/15-terminology/architecture.svg rename to docs/versioned_docs/version-2.8/20-usage/15-terminology/architecture.svg diff --git a/docs/versioned_docs/version-2.4/20-usage/15-terminiology/index.md b/docs/versioned_docs/version-2.8/20-usage/15-terminology/index.md similarity index 94% rename from docs/versioned_docs/version-2.4/20-usage/15-terminiology/index.md rename to docs/versioned_docs/version-2.8/20-usage/15-terminology/index.md index 3d8f12772..f05f18207 100644 --- a/docs/versioned_docs/version-2.4/20-usage/15-terminiology/index.md +++ b/docs/versioned_docs/version-2.8/20-usage/15-terminology/index.md @@ -1,13 +1,5 @@ # Terminology -## Woodpecker architecture - -![Woodpecker architecture](architecture.svg) - -## Pipeline, workflow & step - -![Relation between pipelines, workflows and steps](pipeline-workflow-step.svg) - ## Glossary - **Woodpecker CI**: The project name around Woodpecker. @@ -31,7 +23,15 @@ - **YAML File**: A file format used to define and configure [workflows][Workflow]. - **Dependency**: [Workflows][Workflow] can depend on each other, and if possible, they are executed in parallel. - **Status**: Status refers to the outcome of a step or [workflow][Workflow] after it has been executed, determined by the internal command exit code. At the end of a [workflow][Workflow], its status is sent to the [forge][Forge]. -- **Service extension**: Some parts of woodpecker internal services like secrets storage or config fetcher can be replaced through service extensions. +- **Service extension**: Some parts of Woodpecker internal services like secrets storage or config fetcher can be replaced through service extensions. + +## Woodpecker architecture + +![Woodpecker architecture](architecture.svg) + +## Pipeline, workflow & step + +![Relation between pipelines, workflows and steps](pipeline-workflow-step.svg) ## Pipeline events @@ -50,13 +50,14 @@ Sometimes there are multiple terms that can be used to describe something. This - Environment variables `*_LINK` should be called `*_URL`. In the code use `URL()` instead of `Link()` - Use the term **pipelines** instead of the previous **builds** - Use the term **steps** instead of the previous **jobs** +- Use the prefix `WOODPECKER_EXPERT_` for advanced environment variables that are normally not required to be set by users [Pipeline]: ../20-workflow-syntax.md [Workflow]: ../25-workflows.md -[Forge]: ../../30-administration/11-forges/10-overview.md -[Plugin]: ../51-plugins/10-overview.md +[Forge]: ../../30-administration/11-forges/11-overview.md +[Plugin]: ../51-plugins/51-overview.md [Workspace]: ../20-workflow-syntax.md#workspace [Matrix]: ../30-matrix-workflows.md [Docker]: ../../30-administration/22-backends/10-docker.md diff --git a/docs/versioned_docs/version-2.5/20-usage/15-terminology/pipeline-workflow-step.excalidraw b/docs/versioned_docs/version-2.8/20-usage/15-terminology/pipeline-workflow-step.excalidraw similarity index 100% rename from docs/versioned_docs/version-2.5/20-usage/15-terminology/pipeline-workflow-step.excalidraw rename to docs/versioned_docs/version-2.8/20-usage/15-terminology/pipeline-workflow-step.excalidraw diff --git a/docs/versioned_docs/version-2.5/20-usage/15-terminology/pipeline-workflow-step.svg b/docs/versioned_docs/version-2.8/20-usage/15-terminology/pipeline-workflow-step.svg similarity index 100% rename from docs/versioned_docs/version-2.5/20-usage/15-terminology/pipeline-workflow-step.svg rename to docs/versioned_docs/version-2.8/20-usage/15-terminology/pipeline-workflow-step.svg diff --git a/docs/versioned_docs/version-2.5/20-usage/20-workflow-syntax.md b/docs/versioned_docs/version-2.8/20-usage/20-workflow-syntax.md similarity index 97% rename from docs/versioned_docs/version-2.5/20-usage/20-workflow-syntax.md rename to docs/versioned_docs/version-2.8/20-usage/20-workflow-syntax.md index 7b966fc48..876812e3c 100644 --- a/docs/versioned_docs/version-2.5/20-usage/20-workflow-syntax.md +++ b/docs/versioned_docs/version-2.8/20-usage/20-workflow-syntax.md @@ -6,6 +6,11 @@ The Workflow section defines a list of steps to build, test and deploy your code An exception to this rule are steps with a [`status: [failure]`](#status) condition, which ensures that they are executed in the case of a failed run. ::: +:::note +We support most of YAML 1.2, but preserve some behavior from 1.1 for backward compatibility. +Read more at: [https://github.com/go-yaml/yaml](https://github.com/go-yaml/yaml/tree/v3) +::: + Example steps: ```yaml @@ -518,7 +523,9 @@ For more details check the [services docs](./60-services.md). ## `workspace` -The workspace defines the shared volume and working directory shared by all workflow steps. The default workspace matches the pattern `/woodpecker/src/github.com/octocat/hello-world`, based on your repository URL. +The workspace defines the shared volume and working directory shared by all workflow steps. +The default workspace base is `/woodpecker` and the path is extended with the repository URL (`src/{url-without-schema}`). +So an example would be `/woodpecker/src/github.com/octocat/hello-world`. The workspace can be customized using the workspace block in the YAML file: @@ -535,6 +542,10 @@ The workspace can be customized using the workspace block in the YAML file: - go test ``` +:::note +Plugins will always have the workspace base at `/woodpecker` +::: + The base attribute defines a shared base volume available to all steps. This ensures your source code, dependencies and compiled binaries are persisted and shared between steps. ```diff @@ -766,8 +777,8 @@ Privileged mode is only available to trusted repositories and for security reaso commands: - docker --tls=false ps - - name: services - docker: + services: + - name: docker image: docker:dind commands: dockerd-entrypoint.sh --storage-driver=vfs --tls=false + privileged: true diff --git a/docs/versioned_docs/version-2.4/20-usage/25-workflows.md b/docs/versioned_docs/version-2.8/20-usage/25-workflows.md similarity index 96% rename from docs/versioned_docs/version-2.4/20-usage/25-workflows.md rename to docs/versioned_docs/version-2.8/20-usage/25-workflows.md index 1a3f40fc8..5adc39f85 100644 --- a/docs/versioned_docs/version-2.4/20-usage/25-workflows.md +++ b/docs/versioned_docs/version-2.8/20-usage/25-workflows.md @@ -6,7 +6,7 @@ In case there is a single configuration in `.woodpecker.yaml` Woodpecker will cr By placing the configurations in a folder which is by default named `.woodpecker/` Woodpecker will create a pipeline with multiple workflows each named by the file they are defined in. Only `.yml` and `.yaml` files will be used and files in any subfolders like `.woodpecker/sub-folder/test.yaml` will be ignored. -You can also set some custom path like `.my-ci/pipelines/` instead of `.woodpecker/` in the [project settings](./71-project-settings.md). +You can also set some custom path like `.my-ci/pipelines/` instead of `.woodpecker/` in the [project settings](./75-project-settings.md). ## Benefits of using workflows @@ -18,7 +18,7 @@ You can also set some custom path like `.my-ci/pipelines/` instead of `.woodpeck :::warning Please note that files are only shared between steps of the same workflow (see [File changes are incremental](./20-workflow-syntax.md#file-changes-are-incremental)). That means you cannot access artifacts e.g. from the `build` workflow in the `deploy` workflow. -If you still need to pass artifacts between the workflows you need use some storage [plugin](./51-plugins/10-overview.md) (e.g. one which stores files in an Amazon S3 bucket). +If you still need to pass artifacts between the workflows you need use some storage [plugin](./51-plugins/51-overview.md) (e.g. one which stores files in an Amazon S3 bucket). ::: ```bash diff --git a/docs/versioned_docs/version-2.5/20-usage/30-matrix-workflows.md b/docs/versioned_docs/version-2.8/20-usage/30-matrix-workflows.md similarity index 100% rename from docs/versioned_docs/version-2.5/20-usage/30-matrix-workflows.md rename to docs/versioned_docs/version-2.8/20-usage/30-matrix-workflows.md diff --git a/docs/versioned_docs/version-2.5/20-usage/40-secrets.md b/docs/versioned_docs/version-2.8/20-usage/40-secrets.md similarity index 82% rename from docs/versioned_docs/version-2.5/20-usage/40-secrets.md rename to docs/versioned_docs/version-2.8/20-usage/40-secrets.md index 1b55d9ce1..177147f52 100644 --- a/docs/versioned_docs/version-2.5/20-usage/40-secrets.md +++ b/docs/versioned_docs/version-2.8/20-usage/40-secrets.md @@ -13,6 +13,12 @@ Woodpecker provides three different levels to add secrets to your pipeline. The ### Use secrets in commands +:::warning +The use of secrets is deprecated as of version 2.8 and planned to be removed with version 3. +Instead, you can use the _secrets in settings and environment_ approach outlined below. +You can already migrate to this strategy with version 2.8. +::: + Secrets are exposed to your pipeline steps and plugins as uppercase environment variables and can therefore be referenced in the commands section of your pipeline, once their usage is declared in the `secrets` section: @@ -30,17 +36,27 @@ The case of the environment variables is not changed, but secret matching is don ### Use secrets in settings and environment -You can set an setting or environment value from secrets using the `from_secret` syntax. +You can set a setting or environment value from secrets using the `from_secret` syntax. -In this example, the secret named `secret_token` would be passed to the setting named `token`,which will be available in the plugin as environment variable named `PLUGIN_TOKEN` (See [plugins](./51-plugins/20-creating-plugins.md#settings) for details), and to the environment variable `TOKEN_ENV`. +The example below passes a secret called `secret_token` as an environment variable that will be called `TOKEN_ENV`: ```diff steps: - - name: docker - image: my-plugin + env-secret-example: + image: alpine + commands: ++ - echo "The secret is $TOKEN_ENV" + environment: + TOKEN_ENV: + from_secret: secret_token +``` + +You can use the same syntax to pass secrets to settings. For example, you can pass a secret named `secret_token` to the settings called `token`, which will then be available in the plugin as environment variable named `PLUGIN_TOKEN` (See [plugins](./51-plugins/20-creating-plugins.md#settings) for details). + +```diff + steps: + - name: settings-secret-example + image: my-plugin + settings: + token: + from_secret: secret_token diff --git a/docs/versioned_docs/version-2.5/20-usage/41-registries.md b/docs/versioned_docs/version-2.8/20-usage/41-registries.md similarity index 100% rename from docs/versioned_docs/version-2.5/20-usage/41-registries.md rename to docs/versioned_docs/version-2.8/20-usage/41-registries.md diff --git a/docs/versioned_docs/version-2.4/20-usage/45-cron.md b/docs/versioned_docs/version-2.8/20-usage/45-cron.md similarity index 88% rename from docs/versioned_docs/version-2.4/20-usage/45-cron.md rename to docs/versioned_docs/version-2.8/20-usage/45-cron.md index 994e022bc..95ee8202e 100644 --- a/docs/versioned_docs/version-2.4/20-usage/45-cron.md +++ b/docs/versioned_docs/version-2.8/20-usage/45-cron.md @@ -19,11 +19,11 @@ To configure cron jobs you need at least push access to the repository. + cron: "name of the cron job" # if you only want to execute this step by a specific cron job ``` -1. Create a new cron job in the repository settings: +2. Create a new cron job in the repository settings: ![cron settings](./cron-settings.png) - The supported schedule syntax can be found at . If you need general understanding of the cron syntax is a good place to start and experiment. + The supported schedule syntax can be found at . If you need general understanding of the cron syntax is a good place to start and experiment. Examples: `@every 5m`, `@daily`, `0 30 * * * *` ... diff --git a/docs/versioned_docs/version-2.4/20-usage/50-environment.md b/docs/versioned_docs/version-2.8/20-usage/50-environment.md similarity index 97% rename from docs/versioned_docs/version-2.4/20-usage/50-environment.md rename to docs/versioned_docs/version-2.8/20-usage/50-environment.md index ce05812e4..299bb8f53 100644 --- a/docs/versioned_docs/version-2.4/20-usage/50-environment.md +++ b/docs/versioned_docs/version-2.8/20-usage/50-environment.md @@ -81,10 +81,11 @@ This is the reference list of all environment variables available to your pipeli | | **Current pipeline** | | `CI_PIPELINE_NUMBER` | pipeline number | | `CI_PIPELINE_PARENT` | number of parent pipeline | -| `CI_PIPELINE_EVENT` | pipeline event (see [pipeline events](../20-usage/15-terminiology/index.md#pipeline-events)) | +| `CI_PIPELINE_EVENT` | pipeline event (see [pipeline events](../20-usage/15-terminology/index.md#pipeline-events)) | | `CI_PIPELINE_URL` | link to the web UI for the pipeline | | `CI_PIPELINE_FORGE_URL` | link to the forge's web UI for the commit(s) or tag that triggered the pipeline | | `CI_PIPELINE_DEPLOY_TARGET` | pipeline deploy target for `deployment` events (i.e. production) | +| `CI_PIPELINE_DEPLOY_TASK` | pipeline deploy task for `deployment` events (i.e. migration) | | `CI_PIPELINE_STATUS` | pipeline status (success, failure) | | `CI_PIPELINE_CREATED` | pipeline created UNIX timestamp | | `CI_PIPELINE_STARTED` | pipeline started UNIX timestamp | @@ -114,10 +115,11 @@ This is the reference list of all environment variables available to your pipeli | | **Previous pipeline** | | `CI_PREV_PIPELINE_NUMBER` | previous pipeline number | | `CI_PREV_PIPELINE_PARENT` | previous pipeline number of parent pipeline | -| `CI_PREV_PIPELINE_EVENT` | previous pipeline event (see [pipeline events](../20-usage/15-terminiology/index.md#pipeline-events)) | +| `CI_PREV_PIPELINE_EVENT` | previous pipeline event (see [pipeline events](../20-usage/15-terminology/index.md#pipeline-events)) | | `CI_PREV_PIPELINE_URL` | previous pipeline link in CI | | `CI_PREV_PIPELINE_FORGE_URL` | previous pipeline link to event in forge | | `CI_PREV_PIPELINE_DEPLOY_TARGET` | previous pipeline deploy target for `deployment` events (ie production) | +| `CI_PREV_PIPELINE_DEPLOY_TASK` | previous pipeline deploy task for `deployment` events (ie migration) | | `CI_PREV_PIPELINE_STATUS` | previous pipeline status (success, failure) | | `CI_PREV_PIPELINE_CREATED` | previous pipeline created UNIX timestamp | | `CI_PREV_PIPELINE_STARTED` | previous pipeline started UNIX timestamp | diff --git a/docs/versioned_docs/version-2.4/20-usage/51-plugins/20-creating-plugins.md b/docs/versioned_docs/version-2.8/20-usage/51-plugins/20-creating-plugins.md similarity index 83% rename from docs/versioned_docs/version-2.4/20-usage/51-plugins/20-creating-plugins.md rename to docs/versioned_docs/version-2.8/20-usage/51-plugins/20-creating-plugins.md index cd74ea455..8a0ea5920 100644 --- a/docs/versioned_docs/version-2.4/20-usage/51-plugins/20-creating-plugins.md +++ b/docs/versioned_docs/version-2.8/20-usage/51-plugins/20-creating-plugins.md @@ -48,6 +48,23 @@ Secrets should be passed as settings too. Therefore, users should use [`from_sec For Go, we provide a plugin library you can use to get easy access to internal env vars and your settings. See . +## Metadata + +In your documentation, you can use a Markdown header to define metadata for your plugin. This data is used by [our plugin index](/plugins). + +Supported metadata: + +- `name`: The plugin's full name +- `icon`: URL to your plugin's icon +- `description`: A short description of what it's doing +- `author`: Your name +- `tags`: List of keywords (e.g. `[git, clone]` for the clone plugin) +- `containerImage`: name of the container image +- `containerImageUrl`: link to the container image +- `url`: homepage or repository of your plugin + +If you want your plugin to be listed in the index, you should add as many fields as possible, but only `name` is required. + ## Example plugin This provides a brief tutorial for creating a Woodpecker webhook plugin, using simple shell scripting, to make HTTP requests during the build pipeline. @@ -118,5 +135,5 @@ docker run --rm \ These should also be built for different OS/architectures. - Use [built-in env vars](../50-environment.md#built-in-environment-variables) where possible. - Do not use any configuration except settings (and internal env vars). This means: Don't require using [`environment`](../50-environment.md) and don't require specific secret names. -- Add a `docs.md` file, listing all your settings and plugin metadata ([example](https://codeberg.org/woodpecker-plugins/plugin-docker-buildx/src/branch/main/docs.md)). -- Add your plugin to the [plugin index](/plugins) using your `docs.md` ([the example above in the index](https://woodpecker-ci.org/plugins/Docker%20Buildx)). +- Add a `docs.md` file, listing all your settings and plugin metadata ([example](https://github.com/woodpecker-ci/plugin-git/blob/main/docs.md)). +- Add your plugin to the [plugin index](/plugins) using your `docs.md` ([the example above in the index](https://woodpecker-ci.org/plugins/Git%20Clone)). diff --git a/docs/versioned_docs/version-2.4/20-usage/51-plugins/10-overview.md b/docs/versioned_docs/version-2.8/20-usage/51-plugins/51-overview.md similarity index 54% rename from docs/versioned_docs/version-2.4/20-usage/51-plugins/10-overview.md rename to docs/versioned_docs/version-2.8/20-usage/51-plugins/51-overview.md index ab8db3df3..8dc3c6d63 100644 --- a/docs/versioned_docs/version-2.4/20-usage/51-plugins/10-overview.md +++ b/docs/versioned_docs/version-2.8/20-usage/51-plugins/51-overview.md @@ -4,6 +4,24 @@ Plugins are pipeline steps that perform pre-defined tasks and are configured as They are automatically pulled from the default container registry the agent's have configured. +```dockerfile title="Dockerfile" +FROM laszlocloud/kubectl +COPY deploy /usr/local/deploy +ENTRYPOINT ["/usr/local/deploy"] +``` + +```bash title="deploy" +kubectl apply -f $PLUGIN_TEMPLATE +``` + +```yaml title=".woodpecker.yaml" +steps: + - name: deploy-to-k8s + image: laszlocloud/my-k8s-plugin + settings: + template: config/k8s/service.yaml +``` + Example pipeline using the Docker and Slack plugins: ```yaml @@ -29,6 +47,12 @@ steps: ## Plugin Isolation Plugins are just pipeline steps. They share the build workspace, mounted as a volume, and therefore have access to your source tree. +While normal steps are all about arbitrary code execution, plugins should only allow the functions intended by the plugin author. + +That's why there are a few limitations. The workspace base is always mounted at `/woodpecker`, but the working directory is dynamically +adjusted accordingly, as user of a plugin you should not have to care about this. Also, you cannot use the plugin together with `commands` +or `entrypoint` which will fail. Using `secrets` or `environment` is possible, but in this case, the plugin is internally not treated as plugin +anymore. The container then cannot access secrets with plugin filter anymore and the containers won't be privileged without explicit definition. ## Finding Plugins diff --git a/docs/versioned_docs/version-2.5/20-usage/51-plugins/_category_.yaml b/docs/versioned_docs/version-2.8/20-usage/51-plugins/_category_.yaml similarity index 100% rename from docs/versioned_docs/version-2.5/20-usage/51-plugins/_category_.yaml rename to docs/versioned_docs/version-2.8/20-usage/51-plugins/_category_.yaml diff --git a/docs/versioned_docs/version-2.5/20-usage/60-services.md b/docs/versioned_docs/version-2.8/20-usage/60-services.md similarity index 100% rename from docs/versioned_docs/version-2.5/20-usage/60-services.md rename to docs/versioned_docs/version-2.8/20-usage/60-services.md diff --git a/docs/versioned_docs/version-2.4/20-usage/70-volumes.md b/docs/versioned_docs/version-2.8/20-usage/70-volumes.md similarity index 93% rename from docs/versioned_docs/version-2.4/20-usage/70-volumes.md rename to docs/versioned_docs/version-2.8/20-usage/70-volumes.md index 3397e879c..6897053fb 100644 --- a/docs/versioned_docs/version-2.4/20-usage/70-volumes.md +++ b/docs/versioned_docs/version-2.8/20-usage/70-volumes.md @@ -3,7 +3,7 @@ Woodpecker gives the ability to define Docker volumes in the YAML. You can use this parameter to mount files or folders on the host machine into your containers. :::note -Volumes are only available to trusted repositories and for security reasons should only be used in private environments. See [project settings](./71-project-settings.md#trusted) to enable trusted mode. +Volumes are only available to trusted repositories and for security reasons should only be used in private environments. See [project settings](./75-project-settings.md#trusted) to enable trusted mode. ::: ```diff diff --git a/docs/versioned_docs/version-2.8/20-usage/72-linter.md b/docs/versioned_docs/version-2.8/20-usage/72-linter.md new file mode 100644 index 000000000..4fae3d643 --- /dev/null +++ b/docs/versioned_docs/version-2.8/20-usage/72-linter.md @@ -0,0 +1,62 @@ +# Linter + +Woodpecker automatically lints your workflow files for errors, deprecations and bad habits. Errors and warnings are shown in the UI for any pipelines. + +![errors and warnings in UI](./linter-warnings-errors.png) + +## Running the linter from CLI + +You can run the linter also manually from the CLI: + +```shell +woodpecker-cli lint +``` + +## Bad habit warnings + +Woodpecker warns you if your configuration contains some bad habits. + +### Event filter for all steps + +All your items in `when` blocks should have an `event` filter, so no step runs on all events. This is recommended because if new events are added, your steps probably shouldn't run on those as well. + +Examples of an **incorrect** config for this rule: + +```yaml +when: + - branch: main + - event: tag +``` + +This will trigger the warning because the first item (`branch: main`) does not filter with an event. + +```yaml +steps: + - name: test + when: + branch: main + + - name: deploy + when: + event: tag +``` + +Examples of a **correct** config for this rule: + +```yaml +when: + - branch: main + event: push + - event: tag +``` + +```yaml +steps: + - name: test + when: + event: [tag, push] + + - name: deploy + when: + - event: tag +``` diff --git a/docs/versioned_docs/version-2.5/20-usage/75-project-settings.md b/docs/versioned_docs/version-2.8/20-usage/75-project-settings.md similarity index 87% rename from docs/versioned_docs/version-2.5/20-usage/75-project-settings.md rename to docs/versioned_docs/version-2.8/20-usage/75-project-settings.md index 24bdbe605..12b505dbd 100644 --- a/docs/versioned_docs/version-2.5/20-usage/75-project-settings.md +++ b/docs/versioned_docs/version-2.8/20-usage/75-project-settings.md @@ -25,10 +25,9 @@ Only activate this option if you trust all users who have push access to your re Otherwise, these users will be able to steal secrets that are only available for `deploy` events. ::: -## Protected +## Require approval for -Every pipeline initiated by an webhook event needs to be approved by a project members with push permissions before being executed. -The protected option can be used as an additional review process before running potentially harmful pipelines. Especially if pipelines can be executed by third-parties through pull-requests. +To prevent malicious pipelines from extracting secrets or running harmful commands or to prevent accidental pipeline runs, you can require approval for an additional review process. Depending on the enabled option, a pipeline will be put on hold after creation and will only continue after approval. The default restrictive setting is `All pull requests`. ## Trusted diff --git a/docs/versioned_docs/version-2.5/20-usage/80-badges.md b/docs/versioned_docs/version-2.8/20-usage/80-badges.md similarity index 100% rename from docs/versioned_docs/version-2.5/20-usage/80-badges.md rename to docs/versioned_docs/version-2.8/20-usage/80-badges.md diff --git a/docs/versioned_docs/version-2.8/20-usage/90-advanced-usage.md b/docs/versioned_docs/version-2.8/20-usage/90-advanced-usage.md new file mode 100644 index 000000000..2cfb1b7a4 --- /dev/null +++ b/docs/versioned_docs/version-2.8/20-usage/90-advanced-usage.md @@ -0,0 +1,220 @@ +# Advanced usage + +## Advanced YAML syntax + +YAML has some advanced syntax features that can be used like variables to reduce duplication in your pipeline config: + +### Anchors & aliases + +You can use [YAML anchors & aliases](https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases) as variables in your pipeline config. + +To convert this: + +```yaml +steps: + - name: test + image: golang:1.18 + commands: go test ./... + - name: build + image: golang:1.18 + commands: build +``` + +Just add a new section called **variables** like this: + +```diff ++variables: ++ - &golang_image 'golang:1.18' + + steps: + - name: test +- image: golang:1.18 ++ image: *golang_image + commands: go test ./... + - name: build +- image: golang:1.18 ++ image: *golang_image + commands: build +``` + +### Map merges and overwrites + +```yaml +variables: + - &base-plugin-settings + target: dist + recursive: false + try: true + - &special-setting + special: true + - &some-plugin codeberg.org/6543/docker-images/print_env + +steps: + - name: develop + image: *some-plugin + settings: + <<: [*base-plugin-settings, *special-setting] # merge two maps into an empty map + when: + branch: develop + + - name: main + image: *some-plugin + settings: + <<: *base-plugin-settings # merge one map and ... + try: false # ... overwrite original value + ongoing: false # ... adding a new value + when: + branch: main +``` + +### Sequence merges + +```yaml +variables: + pre_cmds: &pre_cmds + - echo start + - whoami + post_cmds: &post_cmds + - echo stop + hello_cmd: &hello_cmd + - echo hello + +steps: + - name: step1 + image: debian + commands: + - <<: *pre_cmds # prepend a sequence + - echo exec step now do dedicated things + - <<: *post_cmds # append a sequence + - name: step2 + image: debian + commands: + - <<: [*pre_cmds, *hello_cmd] # prepend two sequences + - echo echo from second step + - <<: *post_cmds +``` + +### References + +- [Official YAML specification](https://yaml.org/spec/1.2.2/#3222-anchors-and-aliases) +- [YAML Cheatsheet](https://learnxinyminutes.com/docs/yaml) + +## Persisting environment data between steps + +One can create a file containing environment variables, and then source it in each step that needs them. + +```yaml +steps: + - name: init + image: bash + commands: + - echo "FOO=hello" >> envvars + - echo "BAR=world" >> envvars + + - name: debug + image: bash + commands: + - source envvars + - echo $FOO +``` + +## Declaring global variables + +As described in [Global environment variables](./50-environment.md#global-environment-variables), you can define global variables: + +```ini +WOODPECKER_ENVIRONMENT=first_var:value1,second_var:value2 +``` + +Note that this tightly couples the server and app configurations (where the app is a completely separate application). But this is a good option for truly global variables which should apply to all steps in all pipelines for all apps. + +## Docker in docker (dind) setup + +:::warning +This set up will only work on trusted repositories and for security reasons should only be used in private environments. +See [project settings](./75-project-settings.md#trusted) to enable trusted mode. +::: + +The snippet below shows how a step can communicate with the docker daemon via a `docker:dind` service. + +:::note +If your aim ist to build/publish OCI images, consider using the [Docker Buildx Plugin](https://woodpecker-ci.org/plugins/Docker%20Buildx) instead. +::: + +First we need to define a servie running a docker with the `dind` tag. This service must run in privileged mode: + +```yaml +services: + - name: docker + image: docker:27.4-dind + privileged: true + ports: + - 2376 +``` + +Next we need to set up TLS communication between the `dind` service and the step that wants to communicate with the docker daemon (since Unauthenticated TCP connections have been deprecated [as of docker v26](https://github.com/docker/cli/blob/v27.4.0/docs/deprecated.md#unauthenticated-tcp-connections) and will ve removed in release v28). + +We can achieve this by letting the daemon generate TLS certificates for us and share them with the client via a volume mount in the agent (`/opt/woodpeckerci/dind-certs` in the example below). + +```diff +services: + - name: docker + image: docker:27.4-dind + privileged: true ++ environment: ++ DOCKER_TLS_CERTDIR: /dind-certs ++ volumes: ++ - /opt/woodpeckerci/dind-certs:/dind-certs + ports: + - 2376 +``` + +In the step that needs access to the daemon we need to: + +1. Set the `DOCKER_*` environment variables shown below, setting up the connection with the daemon. These are standardized environment variables that should work with the docker client used by your framework of choice (e.g. [TestContainers](https://testcontainers.com/), [Spring Boot Docker Compose](https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-docker-compose) or similar). +2. Mount the volume where the daemon has created the certificates (`/opt/woodpeckerci/dind-certs`) + +In this example we test the connection with the vanilla docker client: + +```diff +steps: + - name: test + image: docker:27.4-cli ++ environment: ++ DOCKER_HOST: "tcp://docker:2376" ++ DOCKER_CERT_PATH: "/dind-certs/client"27.4-cli ++ DOCKER_TLS_VERIFY: "1" ++ volumes: ++ - /opt/woodpeckerci/dind-certs:/dind-certs + commands: + - docker version +``` + +This step should output version information of the client and the server if everything has been set correctly. + +Complete example: + +```yaml +steps: + - name: test + image: docker:27.4-cli + environment: + DOCKER_HOST: "tcp://docker:2376" + DOCKER_CERT_PATH: "/dind-certs/client"27.4-cli + DOCKER_TLS_VERIFY: "1" + volumes: + - /opt/woodpeckerci/dind-certs:/dind-certs + commands: + - docker version + +services: + - name: docker + image: docker:27.4-dind + privileged: true + environment: + DOCKER_TLS_CERTDIR: /dind-certs + volumes: + - /opt/woodpeckerci/dind-certs:/dind-certs + ports: + - 2376 +``` diff --git a/docs/versioned_docs/version-2.5/20-usage/_category_.yaml b/docs/versioned_docs/version-2.8/20-usage/_category_.yaml similarity index 100% rename from docs/versioned_docs/version-2.5/20-usage/_category_.yaml rename to docs/versioned_docs/version-2.8/20-usage/_category_.yaml diff --git a/docs/versioned_docs/version-2.5/20-usage/cron-settings.png b/docs/versioned_docs/version-2.8/20-usage/cron-settings.png similarity index 100% rename from docs/versioned_docs/version-2.5/20-usage/cron-settings.png rename to docs/versioned_docs/version-2.8/20-usage/cron-settings.png diff --git a/docs/versioned_docs/version-2.8/20-usage/linter-warnings-errors.png b/docs/versioned_docs/version-2.8/20-usage/linter-warnings-errors.png new file mode 100644 index 000000000..663e49704 Binary files /dev/null and b/docs/versioned_docs/version-2.8/20-usage/linter-warnings-errors.png differ diff --git a/docs/versioned_docs/version-2.8/20-usage/pipeline.png b/docs/versioned_docs/version-2.8/20-usage/pipeline.png new file mode 100644 index 000000000..dd4063c9a Binary files /dev/null and b/docs/versioned_docs/version-2.8/20-usage/pipeline.png differ diff --git a/docs/versioned_docs/version-2.8/20-usage/project-settings.png b/docs/versioned_docs/version-2.8/20-usage/project-settings.png new file mode 100644 index 000000000..f3ce025f2 Binary files /dev/null and b/docs/versioned_docs/version-2.8/20-usage/project-settings.png differ diff --git a/docs/versioned_docs/version-2.8/20-usage/repo-new.png b/docs/versioned_docs/version-2.8/20-usage/repo-new.png new file mode 100644 index 000000000..e6136bc12 Binary files /dev/null and b/docs/versioned_docs/version-2.8/20-usage/repo-new.png differ diff --git a/docs/versioned_docs/version-2.8/30-administration/00-getting-started.md b/docs/versioned_docs/version-2.8/30-administration/00-getting-started.md new file mode 100644 index 000000000..8bb1b0a71 --- /dev/null +++ b/docs/versioned_docs/version-2.8/30-administration/00-getting-started.md @@ -0,0 +1,59 @@ +# Getting started + +A Woodpecker deployment consists of two parts: + +- A server which is the heart of Woodpecker and ships the web interface. +- Next to one server, you can deploy any number of agents which will run the pipelines. + +Each agent is able to process one [workflow](../20-usage/15-terminology/index.md) by default. If you have 4 agents installed and connected to the Woodpecker server, your system will process four workflows (not pipelines) in parallel. + +:::tip +You can add more agents to increase the number of parallel workflows or set the agent's `WOODPECKER_MAX_WORKFLOWS=1` environment variable to increase the number of parallel workflows per agent. +::: + +## Which version of Woodpecker should I use? + +Woodpecker is having two different kinds of releases: **stable** and **next**. + +Find more information about the different versions [here](/versions). + +## Hardware Requirements + +Below are minimal resources requirements for Woodpecker components itself: + +| Component | Memory | CPU | +| --------- | ------ | --- | +| Server | 200 MB | 1 | +| Agent | 32 MB | 1 | + +Note, that those values do not include the operating system or workload (pipelines execution) resource consumption. + +In addition you need at least some kind of database which requires additional resources depending on the selected database system. + +## Installation + +You can install Woodpecker on multiple ways. If you are not sure which one to choose, we recommend using the [docker-compose](./05-deployment-methods/10-docker-compose.md) method for the beginning: + +- Using [docker-compose](./05-deployment-methods/10-docker-compose.md) with the official [container images](./05-deployment-methods/10-docker-compose.md#docker-images) +- Using [Kubernetes](./05-deployment-methods/20-kubernetes.md) via the Woodpecker Helm chart +- Using binaries, DEBs or RPMs you can download from [latest release](https://github.com/woodpecker-ci/woodpecker/releases/latest) +- Or using a [third-party installation method](./05-deployment-methods/30-third-party.md) + +## Database + +By default Woodpecker uses a SQLite database which requires zero installation or configuration. See the [database settings](./10-database.md) page if you want to use a different database system like MySQL or PostgreSQL. + +## Forge + +What would be a CI/CD system without any code? By connecting Woodpecker to your [forge](../20-usage/15-terminology/index.md) like GitHub or Gitea you can start running pipelines on events like pushes or pull requests. Woodpecker will also use your forge for authentication and to report back the status of your pipelines. See the [forge settings](./11-forges/11-overview.md) to connect it to Woodpecker. + +## Configuration + +Check the [server configuration](./10-server-config.md) and [agent configuration](./15-agent-config.md) pages to see if you need to adjust any additional parts and after that you should be ready to start with [your first pipeline](../20-usage/10-intro.md). + +## Agent + +The agent is the worker which executes the [workflows](../20-usage/15-terminology/index.md). +Woodpecker agents can execute work using a [backend](../20-usage/15-terminology/index.md) like [docker](./22-backends/10-docker.md) or [kubernetes](./22-backends/40-kubernetes.md). +By default if you choose to deploy an agent using [docker-compose](./05-deployment-methods/10-docker-compose.md) the agent simply use docker for the backend as well. +So nothing to worry about here. If you still prefer to adjust the agent to your needs, check the [agent configuration](./15-agent-config.md) page. diff --git a/docs/versioned_docs/version-2.5/30-administration/00-deployment/10-docker-compose.md b/docs/versioned_docs/version-2.8/30-administration/05-deployment-methods/10-docker-compose.md similarity index 94% rename from docs/versioned_docs/version-2.5/30-administration/00-deployment/10-docker-compose.md rename to docs/versioned_docs/version-2.8/30-administration/05-deployment-methods/10-docker-compose.md index a9c2bb6ab..5af7e85fc 100644 --- a/docs/versioned_docs/version-2.5/30-administration/00-deployment/10-docker-compose.md +++ b/docs/versioned_docs/version-2.8/30-administration/05-deployment-methods/10-docker-compose.md @@ -67,7 +67,7 @@ They can be configured with `*_ADDR` variables: + - WOODPECKER_SERVER_ADDR=${WOODPECKER_HTTP_ADDR} ``` -Reverse proxying can also be [configured for gRPC](../70-proxy.md#caddy). If the agents are connecting over the internet, it should also be SSL encrypted. The agent then needs to be configured to be secure: +Reverse proxying can also be [configured for gRPC](../40-advanced/10-proxy.md#caddy). If the agents are connecting over the internet, it should also be SSL encrypted. The agent then needs to be configured to be secure: ```diff title="docker-compose.yaml" version: '3' diff --git a/docs/versioned_docs/version-2.4/30-administration/00-deployment/20-kubernetes.md b/docs/versioned_docs/version-2.8/30-administration/05-deployment-methods/20-kubernetes.md similarity index 100% rename from docs/versioned_docs/version-2.4/30-administration/00-deployment/20-kubernetes.md rename to docs/versioned_docs/version-2.8/30-administration/05-deployment-methods/20-kubernetes.md diff --git a/docs/versioned_docs/version-2.8/30-administration/05-deployment-methods/30-third-party.md b/docs/versioned_docs/version-2.8/30-administration/05-deployment-methods/30-third-party.md new file mode 100644 index 000000000..acad9c0fd --- /dev/null +++ b/docs/versioned_docs/version-2.8/30-administration/05-deployment-methods/30-third-party.md @@ -0,0 +1,12 @@ +# Third-party installation methods + +:::info +These installation methods are not officially supported. If you experience issues with them, please open issues in the specific repositories. +::: + +- [Using NixOS](./40-nixos.md) via the [NixOS module](https://search.nixos.org/options?channel=unstable&size=200&sort=relevance&query=woodpecker) +- [On Alpine Edge](https://pkgs.alpinelinux.org/packages?name=woodpecker&branch=edge&repo=&arch=&maintainer=) +- [On Arch Linux](https://archlinux.org/packages/?q=woodpecker) +- [On openSUSE](https://software.opensuse.org/package/woodpecker) +- [Using YunoHost](https://apps.yunohost.org/app/woodpecker) +- [On Cloudron](https://www.cloudron.io/store/org.woodpecker_ci.cloudronapp.html) diff --git a/docs/versioned_docs/version-2.5/30-administration/00-deployment/30-nixos.md b/docs/versioned_docs/version-2.8/30-administration/05-deployment-methods/40-nixos.md similarity index 100% rename from docs/versioned_docs/version-2.5/30-administration/00-deployment/30-nixos.md rename to docs/versioned_docs/version-2.8/30-administration/05-deployment-methods/40-nixos.md diff --git a/docs/versioned_docs/version-2.8/30-administration/05-deployment-methods/_category_.yaml b/docs/versioned_docs/version-2.8/30-administration/05-deployment-methods/_category_.yaml new file mode 100644 index 000000000..3907838b0 --- /dev/null +++ b/docs/versioned_docs/version-2.8/30-administration/05-deployment-methods/_category_.yaml @@ -0,0 +1,3 @@ +label: 'Deployment methods' +collapsible: true +collapsed: true diff --git a/docs/versioned_docs/version-2.4/30-administration/30-database.md b/docs/versioned_docs/version-2.8/30-administration/10-database.md similarity index 100% rename from docs/versioned_docs/version-2.4/30-administration/30-database.md rename to docs/versioned_docs/version-2.8/30-administration/10-database.md diff --git a/docs/versioned_docs/version-2.4/30-administration/10-server-config.md b/docs/versioned_docs/version-2.8/30-administration/10-server-config.md similarity index 94% rename from docs/versioned_docs/version-2.4/30-administration/10-server-config.md rename to docs/versioned_docs/version-2.8/30-administration/10-server-config.md index 4b6e0ce10..ec5abeb09 100644 --- a/docs/versioned_docs/version-2.4/30-administration/10-server-config.md +++ b/docs/versioned_docs/version-2.8/30-administration/10-server-config.md @@ -6,7 +6,7 @@ toc_max_heading_level: 2 ## User registration -Woodpecker does not have its own user registry; users are provided from your [forge](./11-forges/10-overview.md) (using OAuth2). +Woodpecker does not have its own user registry; users are provided from your [forge](./11-forges/11-overview.md) (using OAuth2). Registration is closed by default (`WOODPECKER_OPEN=false`). If registration is open (`WOODPECKER_OPEN=true`) then every user with an account at the configured forge can login to Woodpecker. @@ -69,7 +69,7 @@ To handle sensitive data in docker-compose or docker-swarm configurations there For docker-compose you can use a `.env` file next to your compose configuration to store the secrets outside of the compose file. While this separates configuration from secrets it is still not very secure. -Alternatively use docker-secrets. As it may be difficult to use docker secrets for environment variables woodpecker allows to read sensible data from files by providing a `*_FILE` option of all sensible configuration variables. Woodpecker will try to read the value directly from this file. Keep in mind that when the original environment variable gets specified at the same time it will override the value read from the file. +Alternatively use docker-secrets. As it may be difficult to use docker secrets for environment variables Woodpecker allows to read sensible data from files by providing a `*_FILE` option of all sensible configuration variables. Woodpecker will try to read the value directly from this file. Keep in mind that when the original environment variable gets specified at the same time it will override the value read from the file. ```diff title="docker-compose.yaml" version: '3' @@ -360,6 +360,8 @@ a user can log into Woodpecker, without re-authentication. Docker images to run in privileged mode. Only change if you are sure what you do! +You should specify the tag of your images too, as this enforces exact matches. + + ## `next` +- Deprecated `gated` repo settings option, use `require-approval` - Deprecated `steps.[name].group` in favor of `steps.[name].depends_on` (see [workflow syntax](./20-usage/20-workflow-syntax.md#depends_on) to learn how to set dependencies) - Removed `WOODPECKER_ROOT_PATH` and `WOODPECKER_ROOT_URL` config variables. Use `WOODPECKER_HOST` with a path instead - Pipelines without a config file will now be skipped instead of failing @@ -30,7 +38,7 @@ Some versions need some changes to the server configuration or the pipeline conf ## 1.0.0 -- The signature used to verify extension calls (like those used for the [config-extension](./30-administration/100-external-configuration-api.md)) done by the Woodpecker server switched from using a shared-secret HMac to an ed25519 key-pair. Read more about it at the [config-extensions](./30-administration/100-external-configuration-api.md) documentation. +- The signature used to verify extension calls (like those used for the [config-extension](./30-administration/40-advanced/100-external-configuration-api.md)) done by the Woodpecker server switched from using a shared-secret HMac to an ed25519 key-pair. Read more about it at the [config-extensions](./30-administration/40-advanced/100-external-configuration-api.md) documentation. - Refactored support for old agent filter labels and expressions. Learn how to use the new [filter](./20-usage/20-workflow-syntax.md#labels) - Renamed step environment variable `CI_SYSTEM_ARCH` to `CI_SYSTEM_PLATFORM`. Same applies for the cli exec variable. - Renamed environment variables `CI_BUILD_*` and `CI_PREV_BUILD_*` to `CI_PIPELINE_*` and `CI_PREV_PIPELINE_*`, old ones are still available but deprecated diff --git a/docs/versioned_docs/version-2.5/92-awesome.md b/docs/versioned_docs/version-2.8/92-awesome.md similarity index 97% rename from docs/versioned_docs/version-2.5/92-awesome.md rename to docs/versioned_docs/version-2.8/92-awesome.md index b9b593580..920341d33 100644 --- a/docs/versioned_docs/version-2.5/92-awesome.md +++ b/docs/versioned_docs/version-2.8/92-awesome.md @@ -43,7 +43,7 @@ If you have some missing resources, please feel free to [open a pull-request](ht - [Setup Gitea with Woodpecker CI](https://containers.fan/posts/setup-gitea-with-woodpecker-ci/) - [Step-by-step guide to modern, secure and Open-source CI setup](https://devforth.io/blog/step-by-step-guide-to-modern-secure-ci-setup/) -- [Using Woodpecker CI for my static sites](https://web.archive.org/web/20240212182516/https://jan.wildeboer.net/2022/07/Woodpecker-CI-Jekyll/) +- [Using Woodpecker CI for my static sites](https://jan.wildeboer.net/2022/07/Woodpecker-CI-Jekyll/) - [Woodpecker CI @ Codeberg](https://www.sarkasti.eu/articles/post/woodpecker/) - [Deploy Docker/Compose using Woodpecker CI](https://hinty.io/vverenko/deploy-docker-compose-using-woodpecker-ci/) - [Installing Woodpecker CI in your personal homelab](https://pwa.io/articles/installing-woodpecker-in-your-homelab/) diff --git a/docs/versioned_docs/version-2.4/92-development/01-getting-started.md b/docs/versioned_docs/version-2.8/92-development/01-getting-started.md similarity index 99% rename from docs/versioned_docs/version-2.4/92-development/01-getting-started.md rename to docs/versioned_docs/version-2.8/92-development/01-getting-started.md index 5b2f31513..255b92a48 100644 --- a/docs/versioned_docs/version-2.4/92-development/01-getting-started.md +++ b/docs/versioned_docs/version-2.8/92-development/01-getting-started.md @@ -82,7 +82,7 @@ WOODPECKER_HEALTHCHECK=false ### Setup OAuth -Create an OAuth app for your forge as described in the [forges documentation](../30-administration/11-forges/10-overview.md). If you set `WOODPECKER_DEV_OAUTH_HOST=http://localhost:8000` you can use that address with the path as explained for the specific forge to login without the need for a public address. For example for GitHub you would use `http://localhost:8000/authorize` as authorization callback URL. +Create an OAuth app for your forge as described in the [forges documentation](../30-administration/11-forges/11-overview.md). If you set `WOODPECKER_DEV_OAUTH_HOST=http://localhost:8000` you can use that address with the path as explained for the specific forge to login without the need for a public address. For example for GitHub you would use `http://localhost:8000/authorize` as authorization callback URL. ## Developing with VS Code diff --git a/docs/versioned_docs/version-2.4/92-development/02-core-ideas.md b/docs/versioned_docs/version-2.8/92-development/02-core-ideas.md similarity index 87% rename from docs/versioned_docs/version-2.4/92-development/02-core-ideas.md rename to docs/versioned_docs/version-2.8/92-development/02-core-ideas.md index c4527e6c7..a88470f0a 100644 --- a/docs/versioned_docs/version-2.4/92-development/02-core-ideas.md +++ b/docs/versioned_docs/version-2.8/92-development/02-core-ideas.md @@ -8,7 +8,7 @@ ## Addons and extensions If you are wondering whether your contribution will be accepted to be merged in the Woodpecker core, or whether it's better to write an -[addon](../30-administration/75-addons/00-overview.md), [extension](../30-administration/100-external-configuration-api.md) or an +[addon forge](../30-administration/11-forges/100-addon.md), [extension](../30-administration/40-advanced/100-external-configuration-api.md) or an [external custom backend](../30-administration/22-backends/50-custom-backends.md), please check these points: - Is your change very specific to your setup and unlikely to be used by anyone else? diff --git a/docs/versioned_docs/version-2.5/92-development/03-ui.md b/docs/versioned_docs/version-2.8/92-development/03-ui.md similarity index 98% rename from docs/versioned_docs/version-2.5/92-development/03-ui.md rename to docs/versioned_docs/version-2.8/92-development/03-ui.md index e8999b0be..6a01584c2 100644 --- a/docs/versioned_docs/version-2.5/92-development/03-ui.md +++ b/docs/versioned_docs/version-2.8/92-development/03-ui.md @@ -36,4 +36,4 @@ The following list contains some tools and frameworks used by the Woodpecker UI. Woodpecker uses [Vue I18n](https://vue-i18n.intlify.dev/) as translation library. New translations have to be added to `web/src/assets/locales/en.json`. The English source file will be automatically imported into [Weblate](https://translate.woodpecker-ci.org/) (the translation system used by Woodpecker) where all other languages will be translated by the community based on the English source. You must not provide translations except English in PRs, otherwise weblate could put git into conflicts (when someone has translated in that language file and changes are not into main branch yet) -For more information about translations see [Translations](./07-translations.md). +For more information about translations see [Translations](./08-translations.md). diff --git a/docs/versioned_docs/version-2.5/92-development/04-docs.md b/docs/versioned_docs/version-2.8/92-development/04-docs.md similarity index 100% rename from docs/versioned_docs/version-2.5/92-development/04-docs.md rename to docs/versioned_docs/version-2.8/92-development/04-docs.md diff --git a/docs/versioned_docs/version-2.4/92-development/05-architecture.md b/docs/versioned_docs/version-2.8/92-development/05-architecture.md similarity index 100% rename from docs/versioned_docs/version-2.4/92-development/05-architecture.md rename to docs/versioned_docs/version-2.8/92-development/05-architecture.md diff --git a/docs/versioned_docs/version-2.8/92-development/06-conventions.md b/docs/versioned_docs/version-2.8/92-development/06-conventions.md new file mode 100644 index 000000000..e94a90c43 --- /dev/null +++ b/docs/versioned_docs/version-2.8/92-development/06-conventions.md @@ -0,0 +1,7 @@ +# Conventions + +## Database naming + +Database tables are named plural, columns don't have any prefix. + +Example: Table name `agent`, columns `id`, `name`. diff --git a/docs/versioned_docs/version-2.4/92-development/06-guides.md b/docs/versioned_docs/version-2.8/92-development/07-guides.md similarity index 92% rename from docs/versioned_docs/version-2.4/92-development/06-guides.md rename to docs/versioned_docs/version-2.8/92-development/07-guides.md index e8db28a53..c70a9ec93 100644 --- a/docs/versioned_docs/version-2.4/92-development/06-guides.md +++ b/docs/versioned_docs/version-2.8/92-development/07-guides.md @@ -3,7 +3,6 @@ ## ORM Woodpecker uses [Xorm](https://xorm.io/) as ORM for the database connection. -You can find its documentation at [gobook.io/read/gitea.com/xorm](https://gobook.io/read/gitea.com/xorm/manual-en-US/). ## Add a new migration diff --git a/docs/versioned_docs/version-2.5/92-development/07-translations.md b/docs/versioned_docs/version-2.8/92-development/08-translations.md similarity index 100% rename from docs/versioned_docs/version-2.5/92-development/07-translations.md rename to docs/versioned_docs/version-2.8/92-development/08-translations.md diff --git a/docs/docs/92-development/09-swagger.md b/docs/versioned_docs/version-2.8/92-development/09-swagger.md similarity index 100% rename from docs/docs/92-development/09-swagger.md rename to docs/versioned_docs/version-2.8/92-development/09-swagger.md diff --git a/docs/versioned_docs/version-2.8/92-development/09-testing.md b/docs/versioned_docs/version-2.8/92-development/09-testing.md new file mode 100644 index 000000000..ba613c108 --- /dev/null +++ b/docs/versioned_docs/version-2.8/92-development/09-testing.md @@ -0,0 +1,81 @@ +# Testing + +## Backend + +### Unit Tests + +[We use default golang unit tests](https://go.dev/doc/tutorial/add-a-test) +with [`"github.com/stretchr/testify/assert"`](https://pkg.go.dev/github.com/stretchr/testify@v1.9.0/assert) to simplify testing. + +### Integration Tests + +### Dummy backend + +There is a special backend called **`dummy`** which does not execute any commands, but emulates how a typical backend should behave. +To enable it you need to build the agent or cli with the `test` build tag. + +An example pipeline config would be: + +```yaml +when: + event: manual + +steps: + - name: echo + image: dummy + commands: echo "hello woodpecker" + environment: + SLEEP: '1s' + +services: + echo: + image: dummy + commands: echo "i am a sevice" +``` + +This could be executed via `woodpecker-cli --log-level trace exec --backend-engine dummy example.yaml`: + +```none +9:18PM DBG pipeline/pipeline.go:94 > executing 2 stages, in order of: CLI=exec +9:18PM DBG pipeline/pipeline.go:104 > stage CLI=exec StagePos=0 Steps=echo +9:18PM DBG pipeline/pipeline.go:104 > stage CLI=exec StagePos=1 Steps=echo +9:18PM TRC pipeline/backend/dummy/dummy.go:75 > create workflow environment taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM DBG pipeline/pipeline.go:176 > prepare CLI=exec step=echo +9:18PM DBG pipeline/pipeline.go:203 > executing CLI=exec step=echo +9:18PM TRC pipeline/backend/dummy/dummy.go:81 > start step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM TRC pipeline/backend/dummy/dummy.go:167 > tail logs of step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM DBG pipeline/pipeline.go:209 > complete CLI=exec step=echo +[echo:L0:0s] StepName: echo +[echo:L1:0s] StepType: service +[echo:L2:0s] StepUUID: 01J10P578JQE6E25VV1A2DNQN9 +[echo:L3:0s] StepCommands: +[echo:L4:0s] ------------------ +[echo:L5:0s] echo ja +[echo:L6:0s] ------------------ +[echo:L7:0s] 9:18PM DBG pipeline/pipeline.go:176 > prepare CLI=exec step=echo +9:18PM DBG pipeline/pipeline.go:203 > executing CLI=exec step=echo +9:18PM TRC pipeline/backend/dummy/dummy.go:81 > start step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM TRC pipeline/backend/dummy/dummy.go:167 > tail logs of step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +[echo:L0:0s] StepName: echo +[echo:L1:0s] StepType: commands +[echo:L2:0s] StepUUID: 01J10P578JQE6E25VV1DFSXX1Y +[echo:L3:0s] StepCommands: +[echo:L4:0s] ------------------ +[echo:L5:0s] echo ja +[echo:L6:0s] ------------------ +[echo:L7:0s] 9:18PM TRC pipeline/backend/dummy/dummy.go:108 > wait for step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM TRC pipeline/backend/dummy/dummy.go:187 > stop step echo taskUUID=01J10P578JQE6E25VV1EQF0745 +9:18PM DBG pipeline/pipeline.go:209 > complete CLI=exec step=echo +9:18PM TRC pipeline/backend/dummy/dummy.go:208 > delete workflow environment taskUUID=01J10P578JQE6E25VV1EQF0745 +``` + +There are also environment variables to alter step behaviour: + +- `SLEEP: 10` will let the step wait 10 seconds +- `EXPECT_TYPE` allows to check if a step is a `clone`, `service`, `plugin` or `commands` +- `STEP_START_FAIL: true` if set will simulate a step to fail before actually being started (e.g. happens when the container image can not be pulled) +- `STEP_TAIL_FAIL: true` if set will error when we simulate to read from stdout for logs +- `STEP_EXIT_CODE: 2` if set will be used as exit code, default is 0 +- `STEP_OOM_KILLED: true` simulates a step being killed by memory constrains + +You can let the setup of a whole workflow fail by setting it's UUID to `WorkflowSetupShouldFail`. diff --git a/docs/versioned_docs/version-2.5/92-development/_category_.yaml b/docs/versioned_docs/version-2.8/92-development/_category_.yaml similarity index 100% rename from docs/versioned_docs/version-2.5/92-development/_category_.yaml rename to docs/versioned_docs/version-2.8/92-development/_category_.yaml diff --git a/docs/versioned_docs/version-2.5/92-development/ui-proxy.svg b/docs/versioned_docs/version-2.8/92-development/ui-proxy.svg similarity index 100% rename from docs/versioned_docs/version-2.5/92-development/ui-proxy.svg rename to docs/versioned_docs/version-2.8/92-development/ui-proxy.svg diff --git a/docs/versioned_docs/version-2.5/92-development/vscode-debug.png b/docs/versioned_docs/version-2.8/92-development/vscode-debug.png similarity index 100% rename from docs/versioned_docs/version-2.5/92-development/vscode-debug.png rename to docs/versioned_docs/version-2.8/92-development/vscode-debug.png diff --git a/docs/versioned_docs/version-2.5/92-development/vscode-run-test.png b/docs/versioned_docs/version-2.8/92-development/vscode-run-test.png similarity index 100% rename from docs/versioned_docs/version-2.5/92-development/vscode-run-test.png rename to docs/versioned_docs/version-2.8/92-development/vscode-run-test.png diff --git a/docs/versioned_docs/version-2.5/92-development/woodpecker-architecture.png b/docs/versioned_docs/version-2.8/92-development/woodpecker-architecture.png similarity index 100% rename from docs/versioned_docs/version-2.5/92-development/woodpecker-architecture.png rename to docs/versioned_docs/version-2.8/92-development/woodpecker-architecture.png diff --git a/docs/versioned_docs/version-2.8/pipeline-list.png b/docs/versioned_docs/version-2.8/pipeline-list.png new file mode 100644 index 000000000..f501fe0e6 Binary files /dev/null and b/docs/versioned_docs/version-2.8/pipeline-list.png differ diff --git a/docs/versioned_docs/version-2.4/woodpecker.png b/docs/versioned_docs/version-2.8/woodpecker.png similarity index 100% rename from docs/versioned_docs/version-2.4/woodpecker.png rename to docs/versioned_docs/version-2.8/woodpecker.png diff --git a/docs/versioned_sidebars/version-2.4-sidebars.json b/docs/versioned_sidebars/version-2.7-sidebars.json similarity index 100% rename from docs/versioned_sidebars/version-2.4-sidebars.json rename to docs/versioned_sidebars/version-2.7-sidebars.json diff --git a/docs/versioned_sidebars/version-2.5-sidebars.json b/docs/versioned_sidebars/version-2.8-sidebars.json similarity index 100% rename from docs/versioned_sidebars/version-2.5-sidebars.json rename to docs/versioned_sidebars/version-2.8-sidebars.json diff --git a/docs/versions.json b/docs/versions.json index 317e4f49d..62cbcf9ab 100644 --- a/docs/versions.json +++ b/docs/versions.json @@ -1 +1 @@ -["2.6", "2.5", "2.4", "1.0"] +["2.8", "2.7", "2.6", "1.0"] diff --git a/docs/woodpecker.png b/docs/woodpecker.png new file mode 100644 index 000000000..93b4d47af Binary files /dev/null and b/docs/woodpecker.png differ diff --git a/flake.lock b/flake.lock index 9eadad2b9..ed36ceded 100644 --- a/flake.lock +++ b/flake.lock @@ -5,11 +5,11 @@ "systems": "systems" }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "lastModified": 1726560853, + "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", "type": "github" }, "original": { @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1720542800, - "narHash": "sha256-ZgnNHuKV6h2+fQ5LuqnUaqZey1Lqqt5dTUAiAnqH0QQ=", + "lastModified": 1729665710, + "narHash": "sha256-AlcmCXJZPIlO5dmFzV3V2XF6x/OpNWUV8Y/FMPGd8Z4=", "owner": "nixos", "repo": "nixpkgs", - "rev": "feb2849fdeb70028c70d73b848214b00d324a497", + "rev": "2768c7d042a37de65bb1b5b3268fc987e534c49d", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index f494c25f4..69257ff00 100644 --- a/flake.nix +++ b/flake.nix @@ -17,6 +17,8 @@ # generic gnumake gnutar + zip + tree # frontend nodejs_20 @@ -32,6 +34,11 @@ go-mockery protobuf sqlite + go-swag # for generate-openapi + addlicense + protoc-gen-go + protoc-gen-go-grpc + gcc ]; CFLAGS = "-I${pkgs.glibc.dev}/include"; LDFLAGS = "-L${pkgs.glibc}/lib"; diff --git a/go.mod b/go.mod index d2704f78a..83237c8c6 100644 --- a/go.mod +++ b/go.mod @@ -1,79 +1,75 @@ -module go.woodpecker-ci.org/woodpecker/v2 +module go.woodpecker-ci.org/woodpecker/v3 -go 1.22.0 - -toolchain go1.22.4 +go 1.23.4 require ( - code.gitea.io/sdk/gitea v0.18.0 + al.essio.dev/pkg/shellescape v1.5.1 + code.gitea.io/sdk/gitea v0.19.0 codeberg.org/6543/go-yaml2json v1.0.0 codeberg.org/6543/xyaml v1.1.0 - codeberg.org/mvdkleijn/forgejo-sdk/forgejo v1.1.0 + codeberg.org/mvdkleijn/forgejo-sdk/forgejo v1.2.0 github.com/6543/logfile-open v1.2.1 - github.com/adrg/xdg v0.4.0 - github.com/alessio/shellescape v1.4.2 - github.com/bmatcuk/doublestar/v4 v4.6.1 - github.com/caddyserver/certmagic v0.21.3 - github.com/cenkalti/backoff/v4 v4.3.0 - github.com/charmbracelet/huh v0.4.2 + github.com/adrg/xdg v0.5.3 + github.com/bmatcuk/doublestar/v4 v4.7.1 + github.com/cenkalti/backoff/v5 v5.0.0 + github.com/charmbracelet/huh v0.6.0 github.com/charmbracelet/huh/spinner v0.0.0-20240327025511-ec643317aa10 - github.com/distribution/reference v0.5.0 - github.com/docker/cli v24.0.9+incompatible - github.com/docker/docker v24.0.9+incompatible + github.com/distribution/reference v0.6.0 + github.com/docker/cli v27.4.1+incompatible + github.com/docker/docker v27.4.1+incompatible github.com/docker/go-connections v0.5.0 github.com/docker/go-units v0.5.0 github.com/drone/envsubst v1.0.3 github.com/expr-lang/expr v1.16.9 - github.com/franela/goblin v0.0.0-20211003143422-0a4f594942bf - github.com/fsnotify/fsnotify v1.7.0 - github.com/getkin/kin-openapi v0.126.0 + github.com/fsnotify/fsnotify v1.8.0 + github.com/gdgvda/cron v0.3.0 + github.com/getkin/kin-openapi v0.128.1-0.20241224102021-cea0a13b906a github.com/gin-gonic/gin v1.10.0 github.com/gitsight/go-vcsurl v1.0.1 - github.com/go-ap/httpsig v0.0.0-20221203064646-3647b4d88fdf github.com/go-sql-driver/mysql v1.8.1 + github.com/go-viper/mapstructure/v2 v2.2.1 github.com/golang-jwt/jwt/v5 v5.2.1 - github.com/google/go-github/v62 v62.0.0 + github.com/google/go-github/v68 v68.0.0 github.com/google/tink/go v1.7.0 github.com/gorilla/securecookie v1.1.2 github.com/hashicorp/go-hclog v1.6.3 - github.com/hashicorp/go-plugin v1.6.1 - github.com/jellydator/ttlcache/v3 v3.2.0 + github.com/hashicorp/go-plugin v1.6.2 + github.com/jellydator/ttlcache/v3 v3.3.0 github.com/joho/godotenv v1.5.1 - github.com/kinbiko/jsonassert v1.1.1 + github.com/kinbiko/jsonassert v1.2.0 github.com/lib/pq v1.10.9 - github.com/mattn/go-sqlite3 v1.14.22 - github.com/mitchellh/mapstructure v1.5.0 - github.com/moby/moby v24.0.9+incompatible - github.com/moby/term v0.5.0 - github.com/muesli/termenv v0.15.2 - github.com/neticdk/go-bitbucket v1.0.0 + github.com/mattn/go-sqlite3 v1.14.24 + github.com/moby/moby v27.4.1+incompatible + github.com/moby/term v0.5.2 + github.com/muesli/termenv v0.15.3-0.20241212154518-8c990cd6cf4b + github.com/neticdk/go-bitbucket v1.0.1 github.com/oklog/ulid/v2 v2.1.0 github.com/pkg/errors v0.9.1 - github.com/prometheus/client_golang v1.19.1 - github.com/robfig/cron v1.2.0 + github.com/prometheus/client_golang v1.20.5 github.com/rs/zerolog v1.33.0 - github.com/stretchr/testify v1.9.0 + github.com/stretchr/testify v1.10.0 github.com/swaggo/files v1.0.1 github.com/swaggo/gin-swagger v1.6.0 - github.com/swaggo/swag v1.16.3 - github.com/tevino/abool/v2 v2.1.0 - github.com/urfave/cli/v2 v2.27.2 - github.com/xanzy/go-gitlab v0.106.0 + github.com/swaggo/swag v1.16.4 + github.com/urfave/cli-docs/v3 v3.0.0-alpha6 + github.com/urfave/cli/v3 v3.0.0-beta1 github.com/xeipuuv/gojsonschema v1.2.0 - github.com/zalando/go-keyring v0.2.5 + github.com/yaronf/httpsign v0.3.2 + github.com/zalando/go-keyring v0.2.6 + gitlab.com/gitlab-org/api/client-go v0.119.0 go.uber.org/multierr v1.11.0 - golang.org/x/crypto v0.25.0 - golang.org/x/net v0.27.0 - golang.org/x/oauth2 v0.21.0 - golang.org/x/sync v0.7.0 - golang.org/x/term v0.22.0 - golang.org/x/text v0.16.0 - google.golang.org/grpc v1.65.0 - google.golang.org/protobuf v1.34.2 + golang.org/x/crypto v0.32.0 + golang.org/x/net v0.34.0 + golang.org/x/oauth2 v0.25.0 + golang.org/x/sync v0.10.0 + golang.org/x/term v0.28.0 + golang.org/x/text v0.21.0 + google.golang.org/grpc v1.69.2 + google.golang.org/protobuf v1.36.2 gopkg.in/yaml.v3 v3.0.1 - k8s.io/api v0.30.2 - k8s.io/apimachinery v0.30.2 - k8s.io/client-go v0.30.2 + k8s.io/api v0.32.0 + k8s.io/apimachinery v0.32.0 + k8s.io/client-go v0.32.0 src.techknowlogick.com/xormigrate v1.7.1 xorm.io/builder v0.3.13 xorm.io/xorm v1.3.9 @@ -81,7 +77,8 @@ require ( require ( filippo.io/edwards25519 v1.1.0 // indirect - github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/42wim/httpsig v1.2.2 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/KyleBanks/depth v1.2.1 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect github.com/atotto/clipboard v0.1.4 // indirect @@ -89,41 +86,43 @@ require ( github.com/beorn7/perks v1.0.1 // indirect github.com/bytedance/sonic v1.11.6 // indirect github.com/bytedance/sonic/loader v0.1.1 // indirect - github.com/caddyserver/zerossl v0.1.3 // indirect github.com/catppuccin/go v0.2.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/charmbracelet/bubbles v0.18.0 // indirect - github.com/charmbracelet/bubbletea v0.26.3 // indirect - github.com/charmbracelet/lipgloss v0.11.0 // indirect - github.com/charmbracelet/x/ansi v0.1.1 // indirect - github.com/charmbracelet/x/exp/strings v0.0.0-20240524151031-ff83003bf67a // indirect - github.com/charmbracelet/x/input v0.1.1 // indirect - github.com/charmbracelet/x/term v0.1.1 // indirect - github.com/charmbracelet/x/windows v0.1.2 // indirect + github.com/charmbracelet/bubbles v0.20.0 // indirect + github.com/charmbracelet/bubbletea v1.1.0 // indirect + github.com/charmbracelet/lipgloss v0.13.0 // indirect + github.com/charmbracelet/x/ansi v0.2.3 // indirect + github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect + github.com/charmbracelet/x/term v0.2.0 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect + github.com/containerd/log v0.1.0 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect - github.com/danieljoos/wincred v1.2.0 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/danieljoos/wincred v1.2.2 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davidmz/go-pageant v1.0.2 // indirect - github.com/docker/distribution v2.8.3+incompatible // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/docker/docker-credential-helpers v0.8.0 // indirect + github.com/dunglas/httpsfv v1.0.2 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect - github.com/fatih/color v1.16.0 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-fed/httpsig v1.1.0 // indirect - github.com/go-logr/logr v1.4.1 // indirect + github.com/go-logr/logr v1.4.2 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect - github.com/go-openapi/jsonreference v0.20.4 // indirect - github.com/go-openapi/spec v0.20.13 // indirect + github.com/go-openapi/jsonreference v0.21.0 // indirect + github.com/go-openapi/spec v0.21.0 // indirect github.com/go-openapi/swag v0.23.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect - github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-json v0.10.3 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect @@ -137,23 +136,26 @@ require ( github.com/hashicorp/go-retryablehttp v0.7.7 // indirect github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/yamux v0.1.1 // indirect - github.com/imdario/mergo v0.3.16 // indirect - github.com/invopop/yaml v0.3.1 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/julienschmidt/httprouter v1.3.0 // indirect - github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/klauspost/compress v1.17.9 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/leodido/go-urn v1.4.0 // indirect - github.com/libdns/libdns v0.2.2 // indirect + github.com/lestrrat-go/blackmagic v1.0.2 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/httprc v1.0.6 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/jwx/v2 v2.1.2 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-localereader v0.0.1 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/mholt/acmez/v2 v2.0.1 // indirect - github.com/miekg/dns v1.1.59 // indirect - github.com/mitchellh/go-testing-interface v1.0.0 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect @@ -161,42 +163,51 @@ require ( github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/oasdiff/yaml v0.0.0-20241210131133-6b86fb107d80 // indirect + github.com/oasdiff/yaml3 v0.0.0-20241210130736-a94c01f36349 // indirect github.com/oklog/run v1.0.0 // indirect + github.com/onsi/ginkgo v1.16.4 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/prometheus/client_model v0.5.0 // indirect - github.com/prometheus/common v0.48.0 // indirect - github.com/prometheus/procfs v0.12.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/prometheus/client_model v0.6.1 // indirect + github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/segmentio/asm v1.2.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/syndtr/goleveldb v1.0.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect + github.com/urfave/cli/v2 v2.3.0 // indirect + github.com/x448/float16 v0.8.4 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect - github.com/zeebo/blake3 v0.2.3 // indirect - go.uber.org/zap v1.27.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 // indirect + go.opentelemetry.io/otel v1.31.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.31.0 // indirect + go.opentelemetry.io/otel/trace v1.31.0 // indirect golang.org/x/arch v0.8.0 // indirect - golang.org/x/mod v0.17.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/time v0.5.0 // indirect - golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/sys v0.29.0 // indirect + golang.org/x/time v0.8.0 // indirect + golang.org/x/tools v0.27.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect + gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect gotest.tools/v3 v3.4.0 // indirect - k8s.io/klog/v2 v2.120.1 // indirect - k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect - k8s.io/utils v0.0.0-20231127182322-b307cd553661 // indirect - sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect + k8s.io/klog/v2 v2.130.1 // indirect + k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect + k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect + sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.4.2 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 5e9fd498f..0e07e6abb 100644 --- a/go.sum +++ b/go.sum @@ -1,79 +1,77 @@ -code.gitea.io/sdk/gitea v0.18.0 h1:+zZrwVmujIrgobt6wVBWCqITz6bn1aBjnCUHmpZrerI= -code.gitea.io/sdk/gitea v0.18.0/go.mod h1:IG9xZJoltDNeDSW0qiF2Vqx5orMWa7OhVWrjvrd5NpI= +al.essio.dev/pkg/shellescape v1.5.1 h1:86HrALUujYS/h+GtqoB26SBEdkWfmMI6FubjXlsXyho= +al.essio.dev/pkg/shellescape v1.5.1/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890= +code.gitea.io/sdk/gitea v0.19.0 h1:8I6s1s4RHgzxiPHhOQdgim1RWIRcr0LVMbHBjBFXq4Y= +code.gitea.io/sdk/gitea v0.19.0/go.mod h1:IG9xZJoltDNeDSW0qiF2Vqx5orMWa7OhVWrjvrd5NpI= codeberg.org/6543/go-yaml2json v1.0.0 h1:heGqo9VEi7gY2yNqjj7X4ADs5nzlFIbGsJtgYDLrnig= codeberg.org/6543/go-yaml2json v1.0.0/go.mod h1:mz61q14LWF4ZABrgMEDMmk3t9dPi6zgR1uBh2VKV2RQ= codeberg.org/6543/xyaml v1.1.0 h1:0PWTy8OUqshshjrrnAXFWXSPUEa8R49DIh2ah07SxFc= codeberg.org/6543/xyaml v1.1.0/go.mod h1:jI7afXLZUxeL4rNNsG1SlHh78L+gma9lK1bIebyFZwA= -codeberg.org/mvdkleijn/forgejo-sdk/forgejo v1.1.0 h1:t5vPmL8JNfg1syWSZUS7miw8pwUHW+l2AB2IkFZuOiw= -codeberg.org/mvdkleijn/forgejo-sdk/forgejo v1.1.0/go.mod h1:09wAYX9H0+wBo1baX9DdSqdfreZc6ji5aELsnu9m14M= +codeberg.org/mvdkleijn/forgejo-sdk/forgejo v1.2.0 h1:/z7iYYHgTuP+BnJY5qnWj6UjlA1YsAFYhi36JZY1S7Y= +codeberg.org/mvdkleijn/forgejo-sdk/forgejo v1.2.0/go.mod h1:qGzzS6EYParzgmXvnf1QANdnQmHFSyAlJN3SiRZp7e8= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE= +github.com/42wim/httpsig v1.2.2 h1:ofAYoHUNs/MJOLqQ8hIxeyz2QxOz8qdSVvp3PX/oPgA= +github.com/42wim/httpsig v1.2.2/go.mod h1:P/UYo7ytNBFwc+dg35IubuAUIs8zj5zzFIgUCEl55WY= github.com/6543/logfile-open v1.2.1 h1:az+TtNHclTAKaHfFCTSbuduMllANox1gM9qLQr7LV5I= github.com/6543/logfile-open v1.2.1/go.mod h1:ZoEy7pW2mexmQxiZIqPCeh8vUxVuiHYXmSZNbvEb51g= github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/adrg/xdg v0.4.0 h1:RzRqFcjH4nE5C6oTAxhBtoE2IRyjBSa62SCbyPidvls= -github.com/adrg/xdg v0.4.0/go.mod h1:N6ag73EX4wyxeaoeHctc1mas01KZgsj5tYiAIwqJE/E= -github.com/alessio/shellescape v1.4.2 h1:MHPfaU+ddJ0/bYWpgIeUnQUqKrlJ1S7BfEYPM4uEoM0= -github.com/alessio/shellescape v1.4.2/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78= +github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= -github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0CXv75Q= +github.com/bmatcuk/doublestar/v4 v4.7.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/caddyserver/certmagic v0.21.3 h1:pqRRry3yuB4CWBVq9+cUqu+Y6E2z8TswbhNx1AZeYm0= -github.com/caddyserver/certmagic v0.21.3/go.mod h1:Zq6pklO9nVRl3DIFUw9gVUfXKdpc/0qwTUAQMBlfgtI= -github.com/caddyserver/zerossl v0.1.3 h1:onS+pxp3M8HnHpN5MMbOMyNjmTheJyWRaZYwn+YTAyA= -github.com/caddyserver/zerossl v0.1.3/go.mod h1:CxA0acn7oEGO6//4rtrRjYgEoa4MFw/XofZnrYwGqG4= github.com/catppuccin/go v0.2.0 h1:ktBeIrIP42b/8FGiScP9sgrWOss3lw0Z5SktRoithGA= github.com/catppuccin/go v0.2.0/go.mod h1:8IHJuMGaUUjQM82qBrGNBv7LFq6JI3NnQCF6MOlZjpc= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.0 h1:4ziwFuaVJicDO1ah1Nz1aXXV1caM28PFgf1V5TTFXew= +github.com/cenkalti/backoff/v5 v5.0.0/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/39KLfy0= -github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw= -github.com/charmbracelet/bubbletea v0.26.3 h1:iXyGvI+FfOWqkB2V07m1DF3xxQijxjY2j8PqiXYqasg= -github.com/charmbracelet/bubbletea v0.26.3/go.mod h1:bpZHfDHTYJC5g+FBK+ptJRCQotRC+Dhh3AoMxa/2+3Q= -github.com/charmbracelet/huh v0.4.2 h1:5wLkwrA58XDAfEZsJzNQlfJ+K8N9+wYwvR5FOM7jXFM= -github.com/charmbracelet/huh v0.4.2/go.mod h1:g9OXBgtY3zRV4ahnVih9bZE+1yGYN+y2C9Q6L2P+WM0= +github.com/charmbracelet/bubbles v0.20.0 h1:jSZu6qD8cRQ6k9OMfR1WlM+ruM8fkPWkHvQWD9LIutE= +github.com/charmbracelet/bubbles v0.20.0/go.mod h1:39slydyswPy+uVOHZ5x/GjwVAFkCsV8IIVy+4MhzwwU= +github.com/charmbracelet/bubbletea v1.1.0 h1:FjAl9eAL3HBCHenhz/ZPjkKdScmaS5SK69JAK2YJK9c= +github.com/charmbracelet/bubbletea v1.1.0/go.mod h1:9Ogk0HrdbHolIKHdjfFpyXJmiCzGwy+FesYkZr7hYU4= +github.com/charmbracelet/huh v0.6.0 h1:mZM8VvZGuE0hoDXq6XLxRtgfWyTI3b2jZNKh0xWmax8= +github.com/charmbracelet/huh v0.6.0/go.mod h1:GGNKeWCeNzKpEOh/OJD8WBwTQjV3prFAtQPpLv+AVwU= github.com/charmbracelet/huh/spinner v0.0.0-20240327025511-ec643317aa10 h1:/HZJSyFVH5rB1MlCDfkhQhRbLPD2Er29ngWXiUQ8bik= github.com/charmbracelet/huh/spinner v0.0.0-20240327025511-ec643317aa10/go.mod h1:nrBG0YEHaxdbqHXW1xvG1hPqkuac9Eg7RTMvogiXuz0= -github.com/charmbracelet/lipgloss v0.11.0 h1:UoAcbQ6Qml8hDwSWs0Y1cB5TEQuZkDPH/ZqwWWYTG4g= -github.com/charmbracelet/lipgloss v0.11.0/go.mod h1:1UdRTH9gYgpcdNN5oBtjbu/IzNKtzVtb7sqN1t9LNn8= -github.com/charmbracelet/x/ansi v0.1.1 h1:CGAduulr6egay/YVbGc8Hsu8deMg1xZ/bkaXTPi1JDk= -github.com/charmbracelet/x/ansi v0.1.1/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= -github.com/charmbracelet/x/exp/strings v0.0.0-20240524151031-ff83003bf67a h1:lOpqe2UvPmlln41DGoii7wlSZ/q8qGIon5JJ8Biu46I= -github.com/charmbracelet/x/exp/strings v0.0.0-20240524151031-ff83003bf67a/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ= -github.com/charmbracelet/x/exp/term v0.0.0-20240524151031-ff83003bf67a h1:k/s6UoOSVynWiw7PlclyGO2VdVs5ZLbMIHiGp4shFZE= -github.com/charmbracelet/x/exp/term v0.0.0-20240524151031-ff83003bf67a/go.mod h1:YBotIGhfoWhHDlnUpJMkjebGV2pdGRCn1Y4/Nk/vVcU= -github.com/charmbracelet/x/input v0.1.1 h1:YDOJaTUKCqtGnq9PHzx3pkkl4pXDOANUHmhH3DqMtM4= -github.com/charmbracelet/x/input v0.1.1/go.mod h1:jvdTVUnNWj/RD6hjC4FsoB0SeZCJ2ZBkiuFP9zXvZI0= -github.com/charmbracelet/x/term v0.1.1 h1:3cosVAiPOig+EV4X9U+3LDgtwwAoEzJjNdwbXDjF6yI= -github.com/charmbracelet/x/term v0.1.1/go.mod h1:wB1fHt5ECsu3mXYusyzcngVWWlu1KKUmmLhfgr/Flxw= -github.com/charmbracelet/x/windows v0.1.2 h1:Iumiwq2G+BRmgoayww/qfcvof7W/3uLoelhxojXlRWg= -github.com/charmbracelet/x/windows v0.1.2/go.mod h1:GLEO/l+lizvFDBPLIOk+49gdX49L9YWMB5t+DZd0jkQ= +github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw= +github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY= +github.com/charmbracelet/x/ansi v0.2.3 h1:VfFN0NUpcjBRd4DnKfRaIRo53KRgey/nhOoEqosGDEY= +github.com/charmbracelet/x/ansi v0.2.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= +github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 h1:qko3AQ4gK1MTS/de7F5hPGx6/k1u0w4TeYmBFwzYVP4= +github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0/go.mod h1:pBhA0ybfXv6hDjQUZ7hk1lVxBiUbupdw5R31yPUViVQ= +github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0= +github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0= github.com/chzyer/logex v1.2.0/go.mod h1:9+9sk7u7pGNWYMkh0hdiL++6OeibzJccyQU4p4MedaY= github.com/chzyer/readline v1.5.0/go.mod h1:x22KAscuvRqlLoK9CsoYsmxoXZMMFVyOl86cAH8qUic= github.com/chzyer/test v0.0.0-20210722231415-061457976a23/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -82,32 +80,36 @@ github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJ github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= -github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE= -github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/danieljoos/wincred v1.2.2 h1:774zMFJrqaeYCK2W57BgAem/MLi6mtSE47MB6BOJ0i0= +github.com/danieljoos/wincred v1.2.2/go.mod h1:w7w4Utbrz8lqeMbDAK0lkNJUv5sAOkFi7nd/ogr0Uh8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw= github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo= -github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= -github.com/distribution/reference v0.5.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= -github.com/docker/cli v24.0.9+incompatible h1:OxbimnP/z+qVjDLpq9wbeFU3Nc30XhSe+LkwYQisD50= -github.com/docker/cli v24.0.9+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= -github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= -github.com/docker/docker v24.0.9+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/cli v27.4.1+incompatible h1:VzPiUlRJ/xh+otB75gva3r05isHMo5wXDfPRi5/b4hI= +github.com/docker/cli v27.4.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/docker v27.4.1+incompatible h1:ZJvcY7gfwHn1JF48PfbyXg7Jyt9ZCWDW+GGXOIxEwp4= +github.com/docker/docker v27.4.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.8.0 h1:YQFtbBQb4VrpoPxhFuzEBPQ9E16qz5SpHLS+uswaCp8= github.com/docker/docker-credential-helpers v0.8.0/go.mod h1:UGFXcuoQ5TxPiB54nHOZ32AWRqQdECoh/Mg0AlEYb40= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= @@ -116,6 +118,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g= github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= +github.com/dunglas/httpsfv v1.0.2 h1:iERDp/YAfnojSDJ7PW3dj1AReJz4MrwbECSSE59JWL0= +github.com/dunglas/httpsfv v1.0.2/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= @@ -126,17 +130,22 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97 github.com/expr-lang/expr v1.16.9 h1:WUAzmR0JNI9JCiF0/ewwHB1gmcGw5wW7nWt8gc6PpCI= github.com/expr-lang/expr v1.16.9/go.mod h1:8/vRC7+7HBzESEqt5kKpYXxrxkr31SaO8r40VO/1IT4= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= -github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= -github.com/franela/goblin v0.0.0-20211003143422-0a4f594942bf h1:NrF81UtW8gG2LBGkXFQFqlfNnvMt9WdB46sfdJY4oqc= -github.com/franela/goblin v0.0.0-20211003143422-0a4f594942bf/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= -github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= +github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= +github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= -github.com/getkin/kin-openapi v0.126.0 h1:c2cSgLnAsS0xYfKsgt5oBV6MYRM/giU8/RtwUY4wyfY= -github.com/getkin/kin-openapi v0.126.0/go.mod h1:7mONz8IwmSRg6RttPu6v8U/OJ+gr+J99qSFNjPGSQqw= +github.com/gdgvda/cron v0.3.0 h1:Wjj9NSYGzvtjkdZxOjpU749OuJGpcqr/tSxzeeBQGVI= +github.com/gdgvda/cron v0.3.0/go.mod h1:caBF+mzTZGtQqFE05T1m6u9OmCASY3EK51XAICf3wio= +github.com/getkin/kin-openapi v0.128.1-0.20241224102021-cea0a13b906a h1:z7n7YXcmLsh6Bfl9b28vrzS+RlK9W2PuAgcUK6TJ8QI= +github.com/getkin/kin-openapi v0.128.1-0.20241224102021-cea0a13b906a/go.mod h1:gmWI+b/J45xqpyK5wJmRRZse5wefA5H0RDMK46kLUtI= github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -145,20 +154,21 @@ github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/gitsight/go-vcsurl v1.0.1 h1:wkijKsbVg9R2IBP97U7wOANeIW9WJJKkBwS9XqllzWo= github.com/gitsight/go-vcsurl v1.0.1/go.mod h1:qRFdKDa/0Lh9MT0xE+qQBYZ/01+mY1H40rZUHR24X9U= -github.com/go-ap/httpsig v0.0.0-20221203064646-3647b4d88fdf h1:Ab5yBsD/dXhFmgf2hX7T/YYr+VK0Df7SrIxyNztT9YE= -github.com/go-ap/httpsig v0.0.0-20221203064646-3647b4d88fdf/go.mod h1:+4SUDMvPlRMUPW5PlMTbxj3U5a4fWasBIbakUw7Kp6c= github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= -github.com/go-openapi/jsonreference v0.20.4 h1:bKlDxQxQJgwpUSgOENiMPzCTBVuc7vTdXSSgNeAhojU= -github.com/go-openapi/jsonreference v0.20.4/go.mod h1:5pZJyJP2MnYCpoeoMAql78cCHauHj0V9Lhc506VOpw4= -github.com/go-openapi/spec v0.20.13 h1:XJDIN+dLH6vqXgafnl5SUIMnzaChQ6QTo0/UPMbkIaE= -github.com/go-openapi/spec v0.20.13/go.mod h1:8EOhTpBoFiask8rrgwbLC3zmJfz4zsCUueRuPM6GNkw= +github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= +github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= +github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY= +github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= @@ -173,13 +183,18 @@ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9 github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= +github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -194,6 +209,12 @@ github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2V github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -203,22 +224,28 @@ github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-github/v62 v62.0.0 h1:/6mGCaRywZz9MuHyw9gD1CwsbmBX8GWsbFkwMmHdhl4= -github.com/google/go-github/v62 v62.0.0/go.mod h1:EMxeUqGJq2xRu9DYBMwel/mr7kZrzUOfQmmpYrZn2a4= +github.com/google/go-github/v68 v68.0.0 h1:ZW57zeNZiXTdQ16qrDiZ0k6XucrxZ2CGmoTvcCyQG6s= +github.com/google/go-github/v68 v68.0.0/go.mod h1:K9HAUBovM2sLwM408A18h+wd9vqdLOEqTUCbnRIcx68= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgYQBbFN4U4JNXUNYpxael3UzMyo= +github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/tink/go v1.7.0 h1:6Eox8zONGebBFcCBqkVmt60LaWZa6xg1cl/DwAh/J1w= github.com/google/tink/go v1.7.0/go.mod h1:GAUOd+QE3pgj9q8VKIGTCP33c/B7eb4NhxLcgTJZStM= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= @@ -226,25 +253,22 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-plugin v1.6.1 h1:P7MR2UP6gNKGPp+y7EZw2kOiq4IR9WiqLvp0XOsVdwI= -github.com/hashicorp/go-plugin v1.6.1/go.mod h1:XPHFku2tFo3o3QKFgSYo+cghcUhw1NA1hZyMK0PWAw0= +github.com/hashicorp/go-plugin v1.6.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog= +github.com/hashicorp/go-plugin v1.6.2/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= -github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20220319035150-800ac71e25c2/go.mod h1:aYm2/VgdVmcIU8iMfdMvDMsRAQjcfZSKFby6HOFvi/w= -github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= -github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= -github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= -github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= @@ -284,8 +308,8 @@ github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0f github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jellydator/ttlcache/v3 v3.2.0 h1:6lqVJ8X3ZaUwvzENqPAobDsXNExfUJd61u++uW8a3LE= -github.com/jellydator/ttlcache/v3 v3.2.0/go.mod h1:hi7MGFdMAwZna5n2tuvh63DvFLzVKySzCVW6+0gA2n4= +github.com/jellydator/ttlcache/v3 v3.3.0 h1:BdoC9cE81qXfrxeb9eoJi9dWrdhSuwXMAnHTbnBm4Wc= +github.com/jellydator/ttlcache/v3 v3.3.0/go.mod h1:bj2/e0l4jRnQdrnSTaGTsh4GSXvMjQcy41i7th0GVGw= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= @@ -299,14 +323,15 @@ github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4d github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= -github.com/kinbiko/jsonassert v1.1.1 h1:DB12divY+YB+cVpHULLuKePSi6+ui4M/shHSzJISkSE= -github.com/kinbiko/jsonassert v1.1.1/go.mod h1:NO4lzrogohtIdNUNzx8sdzB55M4R4Q1bsrWVdqQ7C+A= +github.com/kinbiko/jsonassert v1.2.0 h1:+/JthIVXdIrThrOtSN9ry0mNtWKXMWuvxR0nU7gQ+tI= +github.com/kinbiko/jsonassert v1.2.0/go.mod h1:pCc3uudOt+lVAbkji9O0uw8MSVt4s+1ZJ0y8Ux2F1Og= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= +github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c= -github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= -github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -318,8 +343,22 @@ github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= +github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/httprc v1.0.6 h1:qgmgIRhpvBqexMJjA/PmwSvhNk679oqD1RbovdCGW8k= +github.com/lestrrat-go/httprc v1.0.6/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx/v2 v2.1.2 h1:6poete4MPsO8+LAEVhpdrNI4Xp2xdiafgl2RD89moBc= +github.com/lestrrat-go/jwx/v2 v2.1.2/go.mod h1:pO+Gz9whn7MPdbsqSJzG8TlEpMZCwQDXnFJ+zsUVh8Y= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -327,8 +366,6 @@ github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/libdns/libdns v0.2.2 h1:O6ws7bAfRPaBsgAYt8MDe2HcNBGC29hkZ9MX2eUSX3s= -github.com/libdns/libdns v0.2.2/go.mod h1:4Bj9+5CQiNMVGf87wjX4CY3HQJypUHRuLvlsfsZqLWQ= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -349,24 +386,20 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= -github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= -github.com/mholt/acmez/v2 v2.0.1 h1:3/3N0u1pLjMK4sNEAFSI+bcvzbPhRpY383sy1kLHJ6k= -github.com/mholt/acmez/v2 v2.0.1/go.mod h1:fX4c9r5jYwMyMsC+7tkYRxHibkOTgta5DIFGoe67e1U= -github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs= -github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk= -github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= -github.com/moby/moby v24.0.9+incompatible h1:Z/hFbZJqC5Fmuf6jesMLdHU71CMAgdiSJ1ZYey+bFmg= -github.com/moby/moby v24.0.9+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM= +github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= +github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= +github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/moby v27.4.1+incompatible h1:z6detzbcLRt7U+w4ovHV+8oYpJfpHKTmUbFWPG6cudA= +github.com/moby/moby v27.4.1+incompatible/go.mod h1:fDXVQ6+S340veQPv35CzDahGBmHsiclFwfEygB/TWMc= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -381,69 +414,85 @@ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= -github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo= -github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= +github.com/muesli/termenv v0.15.3-0.20241212154518-8c990cd6cf4b h1:gmVbquSG+bANneniKyO7R2DkOlCat7XDaSfXxHKKQBY= +github.com/muesli/termenv v0.15.3-0.20241212154518-8c990cd6cf4b/go.mod h1:hxSnBBYLK21Vtq/PHd0S2FYCxBXzBua8ov5s1RobyRQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/neticdk/go-bitbucket v1.0.0 h1:FPvHEgPHoDwD2VHbpyu2R2gnoWQ867RxZd2FivS4wSw= -github.com/neticdk/go-bitbucket v1.0.0/go.mod h1:IrHeWO1CrNi0DlOvfhAA9bGRSeNSUB6/SAfzmwbA5aU= +github.com/neticdk/go-bitbucket v1.0.1 h1:cMJZad0DCSKIqALrAjZ+ywJLr1yY3HpGfRzASdS3UUk= +github.com/neticdk/go-bitbucket v1.0.1/go.mod h1:xeaFIGg8YtT009xUdnyGqy0zm9hwLRsc1cBkDkPO014= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oasdiff/yaml v0.0.0-20241210131133-6b86fb107d80 h1:nZspmSkneBbtxU9TopEAE0CY+SBJLxO8LPUlw2vG4pU= +github.com/oasdiff/yaml v0.0.0-20241210131133-6b86fb107d80/go.mod h1:7tFDb+Y51LcDpn26GccuUgQXUk6t0CXZsivKjyimYX8= +github.com/oasdiff/yaml3 v0.0.0-20241210130736-a94c01f36349 h1:t05Ww3DxZutOqbMN+7OIuqDwXbhl32HiZGpLy26BAPc= +github.com/oasdiff/yaml3 v0.0.0-20241210130736-a94c01f36349/go.mod h1:y5+oSEHCPT/DGrS++Wc/479ERge0zTFxaF8PbGKcg2o= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo/v2 v2.15.0 h1:79HwNRBAZHOEwrczrgSOPy+eFTTlIGELKy5as+ClttY= -github.com/onsi/ginkgo/v2 v2.15.0/go.mod h1:HlxMHtYF57y6Dpf+mc5529KKmSq9h2FpCF+/ZkwUxKM= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM= +github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.31.0 h1:54UJxxj6cPInHS3a35wm6BK/F9nHYueZ1NVujHDrnXE= -github.com/onsi/gomega v1.31.0/go.mod h1:DW9aCi7U6Yi40wNVAvT6kzFnEVEI5n3DloYBiKiT6zk= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4= +github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= -github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= -github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= +github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE= -github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho= -github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= -github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= -github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE= -github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc= -github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= -github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y= +github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= +github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= +github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= +github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= +github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= -github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -461,33 +510,35 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= -github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE= github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg= github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M= github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo= -github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= -github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= +github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A= +github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= -github.com/tevino/abool/v2 v2.1.0 h1:7w+Vf9f/5gmKT4m4qkayb33/92M+Um45F2BkHOR+L/c= -github.com/tevino/abool/v2 v2.1.0/go.mod h1:+Lmlqk6bHDWHqN1cbxqhwEAwMPXgc8I1SDEamtseuXY= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI= -github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM= -github.com/xanzy/go-gitlab v0.106.0 h1:EDfD03K74cIlQo2EducfiupVrip+Oj02bq9ofw5F8sA= -github.com/xanzy/go-gitlab v0.106.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI= +github.com/urfave/cli-docs/v3 v3.0.0-alpha6 h1:w/l/N0xw1rO/aHRIGXJ0lDwwYFOzilup1qGvIytP3BI= +github.com/urfave/cli-docs/v3 v3.0.0-alpha6/go.mod h1:p7Z4lg8FSTrPB9GTaNyTrK3ygffHZcK3w0cU2VE+mzU= +github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg= +github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -495,23 +546,35 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= -github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw= -github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk= +github.com/yaronf/httpsign v0.3.2 h1:rBYYx9eHm60noI4oC24JAD2tJuM8AUDh9ErJO/FfzBs= +github.com/yaronf/httpsign v0.3.2/go.mod h1:cYB/6toJrJnf4JTLVoo6IHzFH9/Zu1dcKmah4xfX2WA= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zalando/go-keyring v0.2.5 h1:Bc2HHpjALryKD62ppdEzaFG6VxL6Bc+5v0LYpN8Lba8= -github.com/zalando/go-keyring v0.2.5/go.mod h1:HL4k+OXQfJUWaMnqyuSOc0drfGPX2b51Du6K+MRgZMk= -github.com/zeebo/assert v1.1.0 h1:hU1L1vLTHsnO8x8c9KAR5GmM5QscxHg5RNU5z5qbUWY= -github.com/zeebo/assert v1.1.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= -github.com/zeebo/blake3 v0.2.3 h1:TFoLXsjeXqRNFxSbk35Dk4YtszE/MQQGK10BH4ptoTg= -github.com/zeebo/blake3 v0.2.3/go.mod h1:mjJjZpnsyIVtVgTOSpJ9vmRE4wgDeyt2HU3qXvvKCaQ= -github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= -github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= +github.com/zalando/go-keyring v0.2.6 h1:r7Yc3+H+Ux0+M72zacZoItR3UDxeWfKTcabvkI8ua9s= +github.com/zalando/go-keyring v0.2.6/go.mod h1:2TCrxYrbUNYfNS/Kgy/LSrkSQzZ5UPVH85RwfczwvcI= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= +gitlab.com/gitlab-org/api/client-go v0.119.0 h1:YBZyx9XUTtEDBBYtY36cZWz6JmT7om/8HPSk37IS95g= +gitlab.com/gitlab-org/api/client-go v0.119.0/go.mod h1:ygHmS3AU3TpvK+AC6DYO1QuAxLlv6yxYK+/Votr/WFQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0 h1:TT4fX+nBOA/+LUkobKGW1ydGcn+G3vRw9+g5HwCphpk= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.54.0/go.mod h1:L7UH0GbB0p47T4Rri3uHjbpCFYrVrwc1I25QhNPiGK8= +go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0 h1:j9+03ymgYhPKmeXGk5Zu+cIZOlVzd9Zv7QIiyItjFBU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.28.0/go.mod h1:Y5+XiUG4Emn1hTfciPzGPJaSI+RpDts6BnCIir0SLqk= +go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= +go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= +go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= +go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= +go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= +go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= +go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -527,8 +590,6 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= @@ -546,24 +607,23 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= -golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E= -golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561/go.mod h1:cyybsKvd6eL0RnXn6p/Grxp8F5bW7iYuBgsNCOHpMYE= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= -golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= @@ -571,17 +631,17 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= -golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs= -golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70= +golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= -golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -590,11 +650,16 @@ golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -602,7 +667,6 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220310020820-b874c991c1a5/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -612,14 +676,14 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= -golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= +golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg= +golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -627,10 +691,10 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -642,29 +706,39 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= -golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= +golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157 h1:Zy9XzmMEflZ/MAaA7vNcoebnRAld7FsPW1EeBB7V0m8= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157/go.mod h1:EfXuqaE1J41VCDicxHzUDm+8rk+7ZdXzHV0IhO/I6s0= -google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc= -google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ= -google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg= -google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw= +google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53 h1:fVoAXEKA4+yufmbdVYv+SE73+cPZbbbe8paLsHfkK+U= +google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53/go.mod h1:riSXTwQ4+nqmPGtobMFyW5FqVAmIs0St6VPp4Ug7CE4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU= +google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.36.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU= +google.golang.org/protobuf v1.36.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= +gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= +gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= @@ -673,8 +747,10 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= @@ -683,18 +759,18 @@ gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o= gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -k8s.io/api v0.30.2 h1:+ZhRj+28QT4UOH+BKznu4CBgPWgkXO7XAvMcMl0qKvI= -k8s.io/api v0.30.2/go.mod h1:ULg5g9JvOev2dG0u2hig4Z7tQ2hHIuS+m8MNZ+X6EmI= -k8s.io/apimachinery v0.30.2 h1:fEMcnBj6qkzzPGSVsAZtQThU62SmQ4ZymlXRC5yFSCg= -k8s.io/apimachinery v0.30.2/go.mod h1:iexa2somDaxdnj7bha06bhb43Zpa6eWH8N8dbqVjTUc= -k8s.io/client-go v0.30.2 h1:sBIVJdojUNPDU/jObC+18tXWcTJVcwyqS9diGdWHk50= -k8s.io/client-go v0.30.2/go.mod h1:JglKSWULm9xlJLx4KCkfLLQ7XwtlbflV6uFFSHTMgVs= -k8s.io/klog/v2 v2.120.1 h1:QXU6cPEOIslTGvZaXvFWiP9VKyeet3sawzTOvdXb4Vw= -k8s.io/klog/v2 v2.120.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag= -k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98= -k8s.io/utils v0.0.0-20231127182322-b307cd553661 h1:FepOBzJ0GXm8t0su67ln2wAZjbQ6RxQGZDnzuLcrUTI= -k8s.io/utils v0.0.0-20231127182322-b307cd553661/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +k8s.io/api v0.32.0 h1:OL9JpbvAU5ny9ga2fb24X8H6xQlVp+aJMFlgtQjR9CE= +k8s.io/api v0.32.0/go.mod h1:4LEwHZEf6Q/cG96F3dqR965sYOfmPM7rq81BLgsE0p0= +k8s.io/apimachinery v0.32.0 h1:cFSE7N3rmEEtv4ei5X6DaJPHHX0C+upp+v5lVPiEwpg= +k8s.io/apimachinery v0.32.0/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE= +k8s.io/client-go v0.32.0 h1:DimtMcnN/JIKZcrSrstiwvvZvLjG0aSxy8PxN8IChp8= +k8s.io/client-go v0.32.0/go.mod h1:boDWvdM1Drk4NJj/VddSLnx59X3OPgwrOo0vGbtq9+8= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y= +k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= +k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= @@ -734,10 +810,10 @@ modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo= -sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1 h1:150L+0vs/8DA78h1u02ooW1/fFq/Lwr+sGiqlzvrtq4= -sigs.k8s.io/structured-merge-diff/v4 v4.4.1/go.mod h1:N8hJocpFajUSSeSJ9bOZ77VzejKZaXsTtZo4/u7Io08= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= +sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2 h1:MdmvkGuXi/8io6ixD5wud3vOLwc1rj0aNqRlpuvjmwA= +sigs.k8s.io/structured-merge-diff/v4 v4.4.2/go.mod h1:N8f93tFZh9U6vpxwRArLiikrE5/2tiu1w1AGfACIGE4= sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= src.techknowlogick.com/xormigrate v1.7.1 h1:RKGLLUAqJ+zO8iZ7eOc7oLH7f0cs2gfXSZSvBRBHnlY= diff --git a/nfpm/agent.yaml b/nfpm/agent.yaml index 934dc7aab..725bb75da 100644 --- a/nfpm/agent.yaml +++ b/nfpm/agent.yaml @@ -10,3 +10,7 @@ section: daemon/system contents: - src: ./dist/agent/linux_amd64/woodpecker-agent dst: /usr/local/bin/woodpecker-agent + - src: ./nfpm/woodpecker-agent.service + dst: /usr/local/lib/systemd/system/ + - src: ./nfpm/woodpecker-agent.env.example + dst: /etc/woodpecker/ diff --git a/nfpm/cli.yaml b/nfpm/cli.yaml index a51cdfd35..3f7bc2429 100644 --- a/nfpm/cli.yaml +++ b/nfpm/cli.yaml @@ -9,4 +9,4 @@ maintainer: Woodpecker Authors section: utils contents: - src: ./dist/cli/linux_amd64/woodpecker-cli - dst: /usr/local/bin/woodpecker + dst: /usr/local/bin/woodpecker-cli diff --git a/nfpm/server.yaml b/nfpm/server.yaml index f125da291..2c51167fd 100644 --- a/nfpm/server.yaml +++ b/nfpm/server.yaml @@ -10,3 +10,7 @@ section: daemon/system contents: - src: ./dist/server/linux_amd64/woodpecker-server dst: /usr/local/bin/woodpecker-server + - src: ./nfpm/woodpecker-server.service + dst: /usr/local/lib/systemd/system/ + - src: ./nfpm/woodpecker-server.env.example + dst: /etc/woodpecker/ diff --git a/nfpm/woodpecker-agent.env.example b/nfpm/woodpecker-agent.env.example new file mode 100644 index 000000000..59c13c968 --- /dev/null +++ b/nfpm/woodpecker-agent.env.example @@ -0,0 +1,7 @@ +# Example for a woodpecker-agent.env file + +# Check the documentation for the agent: +# https://woodpecker-ci.org/docs/administration/agent-config + +# Add all required environment variables for your setup in the form of VARIABLE=value +VARIABLE=value diff --git a/nfpm/woodpecker-agent.service b/nfpm/woodpecker-agent.service new file mode 100644 index 000000000..20d7db4d7 --- /dev/null +++ b/nfpm/woodpecker-agent.service @@ -0,0 +1,19 @@ +[Unit] +Description=WoodpeckerCI agent +Documentation=https://woodpecker-ci.org/docs/administration/agent-config +Requires=network.target +After=network.target +ConditionFileNotEmpty=/etc/woodpecker/woodpecker-agent.env +ConditionPathExists=/etc/woodpecker/woodpecker-agent.env + +[Service] +Type=simple +EnvironmentFile=/etc/woodpecker/woodpecker-agent.env +User=woodpecker +Group=woodpecker +ExecStart=/usr/local/bin/woodpecker-agent +WorkingDirectory=/var/lib/woodpecker/ +StateDirectory=woodpecker + +[Install] +WantedBy=multi-user.target diff --git a/nfpm/woodpecker-server.env.example b/nfpm/woodpecker-server.env.example new file mode 100644 index 000000000..32418c677 --- /dev/null +++ b/nfpm/woodpecker-server.env.example @@ -0,0 +1,7 @@ +# Example for a woodpecker-server.env file + +# Check the documentation for the server: +# https://woodpecker-ci.org/docs/administration/server-config + +# Add all required environment variables for your setup in the form of VARIABLE=value +VARIABLE=value diff --git a/nfpm/woodpecker-server.service b/nfpm/woodpecker-server.service new file mode 100644 index 000000000..3968ee5d5 --- /dev/null +++ b/nfpm/woodpecker-server.service @@ -0,0 +1,19 @@ +[Unit] +Description=WoodpeckerCI server +Documentation=https://woodpecker-ci.org/docs/administration/server-config +Requires=network.target +After=network.target +ConditionFileNotEmpty=/etc/woodpecker/woodpecker-server.env +ConditionPathExists=/etc/woodpecker/woodpecker-server.env + +[Service] +Type=simple +EnvironmentFile=/etc/woodpecker/woodpecker-server.env +User=woodpecker +Group=woodpecker +ExecStart=/usr/local/bin/woodpecker-server +WorkingDirectory=/var/lib/woodpecker/ +StateDirectory=woodpecker + +[Install] +WantedBy=multi-user.target diff --git a/pipeline/backend/backend.go b/pipeline/backend/backend.go index ffb370ed7..960808d9c 100644 --- a/pipeline/backend/backend.go +++ b/pipeline/backend/backend.go @@ -18,7 +18,7 @@ import ( "context" "fmt" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" ) func FindBackend(ctx context.Context, backends []types.Backend, backendName string) (types.Backend, error) { diff --git a/pipeline/backend/common/script.go b/pipeline/backend/common/script.go index 5ad13a477..e7c4a2b98 100644 --- a/pipeline/backend/common/script.go +++ b/pipeline/backend/common/script.go @@ -18,17 +18,15 @@ import ( "encoding/base64" ) -func GenerateContainerConf(commands []string, goos string) (env map[string]string, entry []string) { +func GenerateContainerConf(commands []string, osType, workDir string) (env map[string]string, entry []string) { env = make(map[string]string) - if goos == "windows" { - env["CI_SCRIPT"] = base64.StdEncoding.EncodeToString([]byte(generateScriptWindows(commands))) - env["HOME"] = "c:\\root" + if osType == "windows" { + env["CI_SCRIPT"] = base64.StdEncoding.EncodeToString([]byte(generateScriptWindows(commands, workDir))) env["SHELL"] = "powershell.exe" // cspell:disable-next-line entry = []string{"powershell", "-noprofile", "-noninteractive", "-command", "[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Env:CI_SCRIPT)) | iex"} } else { - env["CI_SCRIPT"] = base64.StdEncoding.EncodeToString([]byte(generateScriptPosix(commands))) - env["HOME"] = "/root" + env["CI_SCRIPT"] = base64.StdEncoding.EncodeToString([]byte(generateScriptPosix(commands, workDir))) env["SHELL"] = "/bin/sh" entry = []string{"/bin/sh", "-c", "echo $CI_SCRIPT | base64 -d | /bin/sh -e"} } diff --git a/pipeline/backend/common/script_posix.go b/pipeline/backend/common/script_posix.go index 56bf06111..221648784 100644 --- a/pipeline/backend/common/script_posix.go +++ b/pipeline/backend/common/script_posix.go @@ -17,16 +17,22 @@ package common import ( "bytes" "fmt" + "text/template" - "github.com/alessio/shellescape" + "al.essio.dev/pkg/shellescape" ) // generateScriptPosix is a helper function that generates a step script // for a linux container using the given. -func generateScriptPosix(commands []string) string { +func generateScriptPosix(commands []string, workDir string) string { var buf bytes.Buffer - buf.WriteString(setupScript) + if err := setupScriptTmpl.Execute(&buf, map[string]string{ + "WorkDir": workDir, + }); err != nil { + // should never happen but well we have an error to trance + return fmt.Sprintf("echo 'failed to generate posix script from commands: %s'; exit 1", err.Error()) + } for _, command := range commands { buf.WriteString(fmt.Sprintf( @@ -39,9 +45,9 @@ func generateScriptPosix(commands []string) string { return buf.String() } -// setupScript is a helper script this is added to the step script to ensure +// setupScriptProto is a helper script this is added to the step script to ensure // a minimum set of environment variables are set correctly. -const setupScript = ` +const setupScriptProto = ` if [ -n "$CI_NETRC_MACHINE" ]; then cat < $HOME/.netrc machine $CI_NETRC_MACHINE @@ -53,8 +59,12 @@ fi unset CI_NETRC_USERNAME unset CI_NETRC_PASSWORD unset CI_SCRIPT +mkdir -p "{{.WorkDir}}" +cd "{{.WorkDir}}" ` +var setupScriptTmpl, _ = template.New("").Parse(setupScriptProto) + // traceScript is a helper script that is added to the step script // to trace a command. const traceScript = ` diff --git a/pipeline/backend/common/script_posix_test.go b/pipeline/backend/common/script_posix_test.go index 497932ed8..088182b89 100644 --- a/pipeline/backend/common/script_posix_test.go +++ b/pipeline/backend/common/script_posix_test.go @@ -16,6 +16,7 @@ package common import ( "testing" + "text/template" "github.com/stretchr/testify/assert" ) @@ -39,6 +40,8 @@ fi unset CI_NETRC_USERNAME unset CI_NETRC_PASSWORD unset CI_SCRIPT +mkdir -p "/woodpecker/some" +cd "/woodpecker/some" echo + 'echo ${PATH}' echo ${PATH} @@ -52,7 +55,13 @@ go test }, } for _, test := range testdata { - script := generateScriptPosix(test.from) + script := generateScriptPosix(test.from, "/woodpecker/some") assert.EqualValues(t, test.want, script, "Want encoded script for %s", test.from) } } + +func TestSetupScriptProtoParse(t *testing.T) { + // just ensure that we have a working `setupScriptTmpl` on runntime + _, err := template.New("").Parse(setupScriptProto) + assert.NoError(t, err) +} diff --git a/pipeline/backend/common/script_test.go b/pipeline/backend/common/script_test.go index 37911713e..cb36c0ea1 100644 --- a/pipeline/backend/common/script_test.go +++ b/pipeline/backend/common/script_test.go @@ -1,3 +1,17 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package common import ( @@ -7,19 +21,17 @@ import ( ) const ( - windowsScriptBase64 = "CiRFcnJvckFjdGlvblByZWZlcmVuY2UgPSAnU3RvcCc7CiZjbWQgL2MgIm1rZGlyIGM6XHJvb3QiOwppZiAoJEVudjpDSV9ORVRSQ19NQUNISU5FKSB7CiRuZXRyYz1bc3RyaW5nXTo6Rm9ybWF0KCJ7MH1cX25ldHJjIiwkRW52OkhPTUUpOwoibWFjaGluZSAkRW52OkNJX05FVFJDX01BQ0hJTkUiID4+ICRuZXRyYzsKImxvZ2luICRFbnY6Q0lfTkVUUkNfVVNFUk5BTUUiID4+ICRuZXRyYzsKInBhc3N3b3JkICRFbnY6Q0lfTkVUUkNfUEFTU1dPUkQiID4+ICRuZXRyYzsKfTsKW0Vudmlyb25tZW50XTo6U2V0RW52aXJvbm1lbnRWYXJpYWJsZSgiQ0lfTkVUUkNfUEFTU1dPUkQiLCRudWxsKTsKW0Vudmlyb25tZW50XTo6U2V0RW52aXJvbm1lbnRWYXJpYWJsZSgiQ0lfU0NSSVBUIiwkbnVsbCk7CgpXcml0ZS1PdXRwdXQgKCcrICJlY2hvIGhlbGxvIHdvcmxkIicpOwomIGVjaG8gaGVsbG8gd29ybGQ7IGlmICgkTEFTVEVYSVRDT0RFIC1uZSAwKSB7ZXhpdCAkTEFTVEVYSVRDT0RFfQoK" - posixScriptBase64 = "CmlmIFsgLW4gIiRDSV9ORVRSQ19NQUNISU5FIiBdOyB0aGVuCmNhdCA8PEVPRiA+ICRIT01FLy5uZXRyYwptYWNoaW5lICRDSV9ORVRSQ19NQUNISU5FCmxvZ2luICRDSV9ORVRSQ19VU0VSTkFNRQpwYXNzd29yZCAkQ0lfTkVUUkNfUEFTU1dPUkQKRU9GCmNobW9kIDA2MDAgJEhPTUUvLm5ldHJjCmZpCnVuc2V0IENJX05FVFJDX1VTRVJOQU1FCnVuc2V0IENJX05FVFJDX1BBU1NXT1JECnVuc2V0IENJX1NDUklQVAoKZWNobyArICdlY2hvIGhlbGxvIHdvcmxkJwplY2hvIGhlbGxvIHdvcmxkCg==" + windowsScriptBase64 = "CiRFcnJvckFjdGlvblByZWZlcmVuY2UgPSAnU3RvcCc7CmlmICgtbm90IChUZXN0LVBhdGggIi93b29kcGVja2VyL3NvbWUiKSkgeyBOZXctSXRlbSAtUGF0aCAiL3dvb2RwZWNrZXIvc29tZSIgLUl0ZW1UeXBlIERpcmVjdG9yeSAtRm9yY2UgfTsKaWYgKC1ub3QgW0Vudmlyb25tZW50XTo6R2V0RW52aXJvbm1lbnRWYXJpYWJsZSgnSE9NRScpKSB7IFtFbnZpcm9ubWVudF06OlNldEVudmlyb25tZW50VmFyaWFibGUoJ0hPTUUnLCAnYzpccm9vdCcpIH07CmlmICgtbm90IChUZXN0LVBhdGggIiRlbnY6SE9NRSIpKSB7IE5ldy1JdGVtIC1QYXRoICIkZW52OkhPTUUiIC1JdGVtVHlwZSBEaXJlY3RvcnkgLUZvcmNlIH07CmlmICgkRW52OkNJX05FVFJDX01BQ0hJTkUpIHsKJG5ldHJjPVtzdHJpbmddOjpGb3JtYXQoInswfVxfbmV0cmMiLCRFbnY6SE9NRSk7CiJtYWNoaW5lICRFbnY6Q0lfTkVUUkNfTUFDSElORSIgPj4gJG5ldHJjOwoibG9naW4gJEVudjpDSV9ORVRSQ19VU0VSTkFNRSIgPj4gJG5ldHJjOwoicGFzc3dvcmQgJEVudjpDSV9ORVRSQ19QQVNTV09SRCIgPj4gJG5ldHJjOwp9OwpbRW52aXJvbm1lbnRdOjpTZXRFbnZpcm9ubWVudFZhcmlhYmxlKCJDSV9ORVRSQ19QQVNTV09SRCIsJG51bGwpOwpbRW52aXJvbm1lbnRdOjpTZXRFbnZpcm9ubWVudFZhcmlhYmxlKCJDSV9TQ1JJUFQiLCRudWxsKTsKY2QgIi93b29kcGVja2VyL3NvbWUiOwoKV3JpdGUtT3V0cHV0ICgnKyAiZWNobyBoZWxsbyB3b3JsZCInKTsKJiBlY2hvIGhlbGxvIHdvcmxkOyBpZiAoJExBU1RFWElUQ09ERSAtbmUgMCkge2V4aXQgJExBU1RFWElUQ09ERX0K" + posixScriptBase64 = "CmlmIFsgLW4gIiRDSV9ORVRSQ19NQUNISU5FIiBdOyB0aGVuCmNhdCA8PEVPRiA+ICRIT01FLy5uZXRyYwptYWNoaW5lICRDSV9ORVRSQ19NQUNISU5FCmxvZ2luICRDSV9ORVRSQ19VU0VSTkFNRQpwYXNzd29yZCAkQ0lfTkVUUkNfUEFTU1dPUkQKRU9GCmNobW9kIDA2MDAgJEhPTUUvLm5ldHJjCmZpCnVuc2V0IENJX05FVFJDX1VTRVJOQU1FCnVuc2V0IENJX05FVFJDX1BBU1NXT1JECnVuc2V0IENJX1NDUklQVApta2RpciAtcCAiL3dvb2RwZWNrZXIvc29tZSIKY2QgIi93b29kcGVja2VyL3NvbWUiCgplY2hvICsgJ2VjaG8gaGVsbG8gd29ybGQnCmVjaG8gaGVsbG8gd29ybGQK" ) func TestGenerateContainerConf(t *testing.T) { - gotEnv, gotEntry := GenerateContainerConf([]string{"echo hello world"}, "windows") + gotEnv, gotEntry := GenerateContainerConf([]string{"echo hello world"}, "windows", "/woodpecker/some") assert.Equal(t, windowsScriptBase64, gotEnv["CI_SCRIPT"]) - assert.Equal(t, "c:\\root", gotEnv["HOME"]) assert.Equal(t, "powershell.exe", gotEnv["SHELL"]) assert.Equal(t, []string{"powershell", "-noprofile", "-noninteractive", "-command", "[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Env:CI_SCRIPT)) | iex"}, gotEntry) - gotEnv, gotEntry = GenerateContainerConf([]string{"echo hello world"}, "linux") + gotEnv, gotEntry = GenerateContainerConf([]string{"echo hello world"}, "linux", "/woodpecker/some") assert.Equal(t, posixScriptBase64, gotEnv["CI_SCRIPT"]) - assert.Equal(t, "/root", gotEnv["HOME"]) assert.Equal(t, "/bin/sh", gotEnv["SHELL"]) assert.Equal(t, []string{"/bin/sh", "-c", "echo $CI_SCRIPT | base64 -d | /bin/sh -e"}, gotEntry) } diff --git a/pipeline/backend/common/script_win.go b/pipeline/backend/common/script_win.go index 17d3b8279..8fb40044f 100644 --- a/pipeline/backend/common/script_win.go +++ b/pipeline/backend/common/script_win.go @@ -18,10 +18,19 @@ import ( "bytes" "fmt" "strings" + "text/template" ) -func generateScriptWindows(commands []string) string { +func generateScriptWindows(commands []string, workDir string) string { var buf bytes.Buffer + + if err := setupScriptWinTmpl.Execute(&buf, map[string]string{ + "WorkDir": workDir, + }); err != nil { + // should never happen but well we have an error to trance + return fmt.Sprintf("echo 'failed to generate posix script from commands: %s'; exit 1", err.Error()) + } + for _, command := range commands { escaped := fmt.Sprintf("%q", command) escaped = strings.ReplaceAll(escaped, "$", `\$`) @@ -31,16 +40,15 @@ func generateScriptWindows(commands []string) string { command, )) } - script := fmt.Sprintf( - setupScriptWin, - buf.String(), - ) - return script + + return buf.String() } -const setupScriptWin = ` +const setupScriptWinProto = ` $ErrorActionPreference = 'Stop'; -&cmd /c "mkdir c:\root"; +if (-not (Test-Path "{{.WorkDir}}")) { New-Item -Path "{{.WorkDir}}" -ItemType Directory -Force }; +if (-not [Environment]::GetEnvironmentVariable('HOME')) { [Environment]::SetEnvironmentVariable('HOME', 'c:\root') }; +if (-not (Test-Path "$env:HOME")) { New-Item -Path "$env:HOME" -ItemType Directory -Force }; if ($Env:CI_NETRC_MACHINE) { $netrc=[string]::Format("{0}\_netrc",$Env:HOME); "machine $Env:CI_NETRC_MACHINE" >> $netrc; @@ -49,9 +57,11 @@ $netrc=[string]::Format("{0}\_netrc",$Env:HOME); }; [Environment]::SetEnvironmentVariable("CI_NETRC_PASSWORD",$null); [Environment]::SetEnvironmentVariable("CI_SCRIPT",$null); -%s +cd "{{.WorkDir}}"; ` +var setupScriptWinTmpl, _ = template.New("").Parse(setupScriptWinProto) + // traceScript is a helper script that is added to the step script // to trace a command. const traceScriptWin = ` diff --git a/pipeline/backend/common/script_win_test.go b/pipeline/backend/common/script_win_test.go index bbd1bfb41..64797ac68 100644 --- a/pipeline/backend/common/script_win_test.go +++ b/pipeline/backend/common/script_win_test.go @@ -16,6 +16,7 @@ package common import ( "testing" + "text/template" "github.com/stretchr/testify/assert" ) @@ -29,7 +30,9 @@ func TestGenerateScriptWin(t *testing.T) { from: []string{"echo %PATH%", "go build", "go test"}, want: ` $ErrorActionPreference = 'Stop'; -&cmd /c "mkdir c:\root"; +if (-not (Test-Path "/woodpecker/some")) { New-Item -Path "/woodpecker/some" -ItemType Directory -Force }; +if (-not [Environment]::GetEnvironmentVariable('HOME')) { [Environment]::SetEnvironmentVariable('HOME', 'c:\root') }; +if (-not (Test-Path "$env:HOME")) { New-Item -Path "$env:HOME" -ItemType Directory -Force }; if ($Env:CI_NETRC_MACHINE) { $netrc=[string]::Format("{0}\_netrc",$Env:HOME); "machine $Env:CI_NETRC_MACHINE" >> $netrc; @@ -38,6 +41,7 @@ $netrc=[string]::Format("{0}\_netrc",$Env:HOME); }; [Environment]::SetEnvironmentVariable("CI_NETRC_PASSWORD",$null); [Environment]::SetEnvironmentVariable("CI_SCRIPT",$null); +cd "/woodpecker/some"; Write-Output ('+ "echo %PATH%"'); & echo %PATH%; if ($LASTEXITCODE -ne 0) {exit $LASTEXITCODE} @@ -47,12 +51,17 @@ Write-Output ('+ "go build"'); Write-Output ('+ "go test"'); & go test; if ($LASTEXITCODE -ne 0) {exit $LASTEXITCODE} - `, }, } for _, test := range testdata { - script := generateScriptWindows(test.from) + script := generateScriptWindows(test.from, "/woodpecker/some") assert.EqualValues(t, test.want, script, "Want encoded script for %s", test.from) } } + +func TestSetupScriptWinProtoParse(t *testing.T) { + // just ensure that we have a working `setupScriptWinTmpl` on runntime + _, err := template.New("").Parse(setupScriptWinProto) + assert.NoError(t, err) +} diff --git a/pipeline/backend/docker/backend_options.go b/pipeline/backend/docker/backend_options.go new file mode 100644 index 000000000..b7b2a10e1 --- /dev/null +++ b/pipeline/backend/docker/backend_options.go @@ -0,0 +1,21 @@ +package docker + +import ( + "github.com/go-viper/mapstructure/v2" + + backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" +) + +// BackendOptions defines all the advanced options for the docker backend. +type BackendOptions struct { + User string `mapstructure:"user"` +} + +func parseBackendOptions(step *backend.Step) (BackendOptions, error) { + var result BackendOptions + if step == nil || step.BackendOptions == nil { + return result, nil + } + err := mapstructure.WeakDecode(step.BackendOptions[EngineName], &result) + return result, err +} diff --git a/pipeline/backend/docker/backend_options_test.go b/pipeline/backend/docker/backend_options_test.go new file mode 100644 index 000000000..50fb7f73e --- /dev/null +++ b/pipeline/backend/docker/backend_options_test.go @@ -0,0 +1,56 @@ +package docker + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" +) + +func Test_parseBackendOptions(t *testing.T) { + tests := []struct { + name string + step *backend.Step + want BackendOptions + wantErr bool + }{ + { + name: "nil options", + step: &backend.Step{BackendOptions: nil}, + want: BackendOptions{}, + }, + { + name: "empty options", + step: &backend.Step{BackendOptions: map[string]any{}}, + want: BackendOptions{}, + }, + { + name: "with user option", + step: &backend.Step{BackendOptions: map[string]any{ + "docker": map[string]any{ + "user": "1000:1000", + }, + }}, + want: BackendOptions{User: "1000:1000"}, + }, + { + name: "invalid backend options", + step: &backend.Step{BackendOptions: map[string]any{"docker": "invalid"}}, + want: BackendOptions{}, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parseBackendOptions(tt.step) + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pipeline/backend/docker/config.go b/pipeline/backend/docker/config.go new file mode 100644 index 000000000..299ce31b8 --- /dev/null +++ b/pipeline/backend/docker/config.go @@ -0,0 +1,71 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package docker + +import ( + "fmt" + "strings" + + "github.com/rs/zerolog/log" + "github.com/urfave/cli/v3" +) + +type config struct { + enableIPv6 bool + network string + volumes []string + resourceLimit resourceLimit +} + +type resourceLimit struct { + MemSwapLimit int64 + MemLimit int64 + ShmSize int64 + CPUQuota int64 + CPUShares int64 + CPUSet string +} + +func configFromCli(c *cli.Command) (config, error) { + conf := config{ + enableIPv6: c.Bool("backend-docker-ipv6"), + network: c.String("backend-docker-network"), + resourceLimit: resourceLimit{ + MemSwapLimit: c.Int("backend-docker-limit-mem-swap"), + MemLimit: c.Int("backend-docker-limit-mem"), + ShmSize: c.Int("backend-docker-limit-shm-size"), + CPUQuota: c.Int("backend-docker-limit-cpu-quota"), + CPUShares: c.Int("backend-docker-limit-cpu-shares"), + CPUSet: c.String("backend-docker-limit-cpu-set"), + }, + } + + volumes := strings.Split(c.String("backend-docker-volumes"), ",") + conf.volumes = make([]string, 0, len(volumes)) + // Validate provided volume definitions + for _, v := range volumes { + if v == "" { + continue + } + parts, err := splitVolumeParts(v) + if err != nil { + log.Error().Err(err).Msgf("can not parse volume config") + return conf, fmt.Errorf("invalid volume '%s' provided in WOODPECKER_BACKEND_DOCKER_VOLUMES: %w", v, err) + } + conf.volumes = append(conf.volumes, strings.Join(parts, ":")) + } + + return conf, nil +} diff --git a/pipeline/backend/docker/convert.go b/pipeline/backend/docker/convert.go index a862418ad..5e20c7149 100644 --- a/pipeline/backend/docker/convert.go +++ b/pipeline/backend/docker/convert.go @@ -23,15 +23,17 @@ import ( "github.com/docker/docker/api/types/container" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/common" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/common" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" ) // Valid container volumes must have at least two components, source and destination. const minVolumeComponents = 2 // returns a container configuration. -func (e *docker) toConfig(step *types.Step) *container.Config { +func (e *docker) toConfig(step *types.Step, options BackendOptions) *container.Config { + e.windowsPathPatch(step) + config := &container.Config{ Image: step.Image, Labels: map[string]string{ @@ -41,16 +43,21 @@ func (e *docker) toConfig(step *types.Step) *container.Config { WorkingDir: step.WorkingDir, AttachStdout: true, AttachStderr: true, + Volumes: toVol(step.Volumes), + User: options.User, } configEnv := make(map[string]string) maps.Copy(configEnv, step.Environment) if len(step.Commands) > 0 { - env, entry := common.GenerateContainerConf(step.Commands, e.info.OSType) + env, entry := common.GenerateContainerConf(step.Commands, e.info.OSType, step.WorkingDir) for k, v := range env { configEnv[k] = v } config.Entrypoint = entry + + // step.WorkingDir will be respected by the generated script + config.WorkingDir = step.WorkspaceBase } if len(step.Entrypoint) > 0 { config.Entrypoint = step.Entrypoint @@ -59,9 +66,6 @@ func (e *docker) toConfig(step *types.Step) *container.Config { if len(configEnv) != 0 { config.Env = toEnv(configEnv) } - if len(step.Volumes) != 0 { - config.Volumes = toVol(step.Volumes) - } return config } @@ -70,20 +74,20 @@ func toContainerName(step *types.Step) string { } // returns a container host configuration. -func toHostConfig(step *types.Step) *container.HostConfig { +func toHostConfig(step *types.Step, conf *config) *container.HostConfig { config := &container.HostConfig{ Resources: container.Resources{ - CPUQuota: step.CPUQuota, - CPUShares: step.CPUShares, - CpusetCpus: step.CPUSet, - Memory: step.MemLimit, - MemorySwap: step.MemSwapLimit, + CPUQuota: conf.resourceLimit.CPUQuota, + CPUShares: conf.resourceLimit.CPUShares, + CpusetCpus: conf.resourceLimit.CPUSet, + Memory: conf.resourceLimit.MemLimit, + MemorySwap: conf.resourceLimit.MemSwapLimit, }, + ShmSize: conf.resourceLimit.ShmSize, LogConfig: container.LogConfig{ Type: "json-file", }, Privileged: step.Privileged, - ShmSize: step.ShmSize, } if len(step.NetworkMode) != 0 { @@ -127,7 +131,10 @@ func toHostConfig(step *types.Step) *container.HostConfig { // helper function that converts a slice of volume paths to a set of // unique volume names. func toVol(paths []string) map[string]struct{} { - set := map[string]struct{}{} + if len(paths) == 0 { + return nil + } + set := make(map[string]struct{}) for _, path := range paths { parts, err := splitVolumeParts(path) if err != nil { @@ -146,7 +153,9 @@ func toVol(paths []string) map[string]struct{} { func toEnv(env map[string]string) []string { var envs []string for k, v := range env { - envs = append(envs, k+"="+v) + if k != "" { + envs = append(envs, k+"="+v) + } } return envs } diff --git a/pipeline/backend/docker/convert_test.go b/pipeline/backend/docker/convert_test.go index b114398f6..f2948a4a7 100644 --- a/pipeline/backend/docker/convert_test.go +++ b/pipeline/backend/docker/convert_test.go @@ -15,15 +15,17 @@ package docker import ( + "encoding/base64" "reflect" "sort" + "strings" "testing" - "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/system" "github.com/stretchr/testify/assert" - backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" + backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" ) func TestSplitVolumeParts(t *testing.T) { @@ -91,14 +93,88 @@ func TestSplitVolumeParts(t *testing.T) { } } +// dummy vars to test against. +var ( + testCmdStep = &backend.Step{ + Name: "hello", + UUID: "f51821af-4cb8-435e-a3c2-3a684185d828", + Type: backend.StepTypeCommands, + Commands: []string{"echo \"hello world\"", "ls"}, + Image: "alpine", + Environment: map[string]string{"SHELL": "/bin/zsh"}, + } + + testPluginStep = &backend.Step{ + Name: "lint", + UUID: "d841ee40-e66e-4275-bb3f-55bf89744b21", + Type: backend.StepTypePlugin, + Image: "mstruebing/editorconfig-checker", + Environment: make(map[string]string), + } + + testEngine = &docker{ + info: system.Info{ + Architecture: "x86_64", + OSType: "linux", + DefaultRuntime: "runc", + DockerRootDir: "/var/lib/docker", + OperatingSystem: "Archlinux", + Name: "SOME_HOSTNAME", + }, + } +) + +func TestToContainerName(t *testing.T) { + assert.EqualValues(t, "wp_f51821af-4cb8-435e-a3c2-3a684185d828", toContainerName(testCmdStep)) + assert.EqualValues(t, "wp_d841ee40-e66e-4275-bb3f-55bf89744b21", toContainerName(testPluginStep)) +} + +func TestStepToConfig(t *testing.T) { + // StepTypeCommands + conf := testEngine.toConfig(testCmdStep, BackendOptions{}) + if assert.NotNil(t, conf) { + assert.EqualValues(t, []string{"/bin/sh", "-c", "echo $CI_SCRIPT | base64 -d | /bin/sh -e"}, conf.Entrypoint) + assert.Nil(t, conf.Cmd) + assert.EqualValues(t, testCmdStep.UUID, conf.Labels["wp_uuid"]) + } + + // StepTypePlugin + conf = testEngine.toConfig(testPluginStep, BackendOptions{}) + if assert.NotNil(t, conf) { + assert.Nil(t, conf.Cmd) + assert.EqualValues(t, testPluginStep.UUID, conf.Labels["wp_uuid"]) + } +} + +func TestToEnv(t *testing.T) { + assert.Nil(t, toEnv(nil)) + assert.EqualValues(t, []string{"A=B"}, toEnv(map[string]string{"A": "B"})) + assert.ElementsMatch(t, []string{"A=B=C", "T=T"}, toEnv(map[string]string{"A": "B=C", "": "Z", "T": "T"})) +} + +func TestToVol(t *testing.T) { + assert.Nil(t, toVol(nil)) + assert.EqualValues(t, map[string]struct{}{"/test": {}}, toVol([]string{"test:/test"})) +} + +func TestEncodeAuthToBase64(t *testing.T) { + res, err := encodeAuthToBase64(backend.Auth{}) + assert.NoError(t, err) + assert.EqualValues(t, "e30=", res) + + res, err = encodeAuthToBase64(backend.Auth{Username: "user", Password: "pwd"}) + assert.NoError(t, err) + assert.EqualValues(t, "eyJ1c2VybmFtZSI6InVzZXIiLCJwYXNzd29yZCI6InB3ZCJ9", res) +} + func TestToConfigSmall(t *testing.T) { - engine := docker{info: types.Info{OSType: "linux/riscv64"}} + engine := docker{info: system.Info{OSType: "linux", Architecture: "riscv64"}} conf := engine.toConfig(&backend.Step{ Name: "test", UUID: "09238932", Commands: []string{"go test"}, - }) + }, BackendOptions{}) assert.NotNil(t, conf) sort.Strings(conf.Env) @@ -111,55 +187,59 @@ func TestToConfigSmall(t *testing.T) { "wp_uuid": "09238932", }, Env: []string{ - "CI_SCRIPT=CmlmIFsgLW4gIiRDSV9ORVRSQ19NQUNISU5FIiBdOyB0aGVuCmNhdCA8PEVPRiA+ICRIT01FLy5uZXRyYwptYWNoaW" + - "5lICRDSV9ORVRSQ19NQUNISU5FCmxvZ2luICRDSV9ORVRSQ19VU0VSTkFNRQpwYXNzd29yZCAkQ0lfTkVUUkNfUEFTU1dPUkQKRU9" + - "GCmNobW9kIDA2MDAgJEhPTUUvLm5ldHJjCmZpCnVuc2V0IENJX05FVFJDX1VTRVJOQU1FCnVuc2V0IENJX05FVFJDX1BBU1NXT1JE" + - "CnVuc2V0IENJX1NDUklQVAoKZWNobyArICdnbyB0ZXN0JwpnbyB0ZXN0Cg==", - "HOME=/root", + "CI_SCRIPT=CmlmIFsgLW4gIiRDSV9ORVRSQ19NQUNISU5FIiBdOyB0aGVuCmNhdCA8PEVPRiA+ICRIT01FLy5uZXRyYwptYWNoaW5lICRDSV9ORVRSQ19NQUNISU5FCmxvZ2luICRDSV9ORVRSQ19VU0VSTkFNRQpwYXNzd29yZCAkQ0lfTkVUUkNfUEFTU1dPUkQKRU9GCmNobW9kIDA2MDAgJEhPTUUvLm5ldHJjCmZpCnVuc2V0IENJX05FVFJDX1VTRVJOQU1FCnVuc2V0IENJX05FVFJDX1BBU1NXT1JECnVuc2V0IENJX1NDUklQVApta2RpciAtcCAiIgpjZCAiIgoKZWNobyArICdnbyB0ZXN0JwpnbyB0ZXN0Cg==", "SHELL=/bin/sh", }, }, conf) } func TestToConfigFull(t *testing.T) { - engine := docker{info: types.Info{OSType: "linux/riscv64"}} + engine := docker{ + info: system.Info{OSType: "linux", Architecture: "riscv64"}, + config: config{ + enableIPv6: true, + resourceLimit: resourceLimit{ + MemSwapLimit: 12, + MemLimit: 13, + ShmSize: 14, + CPUQuota: 15, + CPUShares: 16, + }, + }, + } conf := engine.toConfig(&backend.Step{ - Name: "test", - UUID: "09238932", - Type: backend.StepTypeCommands, - Image: "golang:1.2.3", - Pull: true, - Detached: true, - Privileged: true, - WorkingDir: "/src/abc", - Environment: map[string]string{"TAGS": "sqlite"}, - Commands: []string{"go test", "go vet ./..."}, - ExtraHosts: []backend.HostAlias{{Name: "t", IP: "1.2.3.4"}}, - Volumes: []string{"/cache:/cache"}, - Tmpfs: []string{"/tmp"}, - Devices: []string{"/dev/sdc"}, - Networks: []backend.Conn{{Name: "extra-net", Aliases: []string{"extra.net"}}}, - DNS: []string{"9.9.9.9", "8.8.8.8"}, - DNSSearch: nil, - MemSwapLimit: 12, - MemLimit: 13, - ShmSize: 14, - CPUQuota: 15, - CPUShares: 16, - OnFailure: true, - OnSuccess: true, - Failure: "fail", - AuthConfig: backend.Auth{Username: "user", Password: "123456"}, - NetworkMode: "bridge", - Ports: []backend.Port{{Number: 21}, {Number: 22}}, - }) + Name: "test", + UUID: "09238932", + Type: backend.StepTypeCommands, + Image: "golang:1.2.3", + Pull: true, + Detached: true, + Privileged: true, + WorkingDir: "/src/abc", + WorkspaceBase: "/src", + Environment: map[string]string{"TAGS": "sqlite"}, + Commands: []string{"go test", "go vet ./..."}, + ExtraHosts: []backend.HostAlias{{Name: "t", IP: "1.2.3.4"}}, + Volumes: []string{"/cache:/cache"}, + Tmpfs: []string{"/tmp"}, + Devices: []string{"/dev/sdc"}, + Networks: []backend.Conn{{Name: "extra-net", Aliases: []string{"extra.net"}}}, + DNS: []string{"9.9.9.9", "8.8.8.8"}, + DNSSearch: nil, + OnFailure: true, + OnSuccess: true, + Failure: "fail", + AuthConfig: backend.Auth{Username: "user", Password: "123456"}, + NetworkMode: "bridge", + Ports: []backend.Port{{Number: 21}, {Number: 22}}, + }, BackendOptions{}) assert.NotNil(t, conf) sort.Strings(conf.Env) assert.EqualValues(t, &container.Config{ Image: "golang:1.2.3", - WorkingDir: "/src/abc", + WorkingDir: "/src", AttachStdout: true, AttachStderr: true, Entrypoint: []string{"/bin/sh", "-c", "echo $CI_SCRIPT | base64 -d | /bin/sh -e"}, @@ -168,12 +248,7 @@ func TestToConfigFull(t *testing.T) { "wp_uuid": "09238932", }, Env: []string{ - "CI_SCRIPT=CmlmIFsgLW4gIiRDSV9ORVRSQ19NQUNISU5FIiBdOyB0aGVuCmNhdCA8PEVPRiA+ICRIT01FLy5uZXRyYwptYWNoaW" + - "5lICRDSV9ORVRSQ19NQUNISU5FCmxvZ2luICRDSV9ORVRSQ19VU0VSTkFNRQpwYXNzd29yZCAkQ0lfTkVUUkNfUEFTU1dPUkQKRU" + - "9GCmNobW9kIDA2MDAgJEhPTUUvLm5ldHJjCmZpCnVuc2V0IENJX05FVFJDX1VTRVJOQU1FCnVuc2V0IENJX05FVFJDX1BBU1NXT1" + - "JECnVuc2V0IENJX1NDUklQVAoKZWNobyArICdnbyB0ZXN0JwpnbyB0ZXN0CgplY2hvICsgJ2dvIHZldCAuLy4uLicKZ28gdmV0IC" + - "4vLi4uCg==", - "HOME=/root", + "CI_SCRIPT=CmlmIFsgLW4gIiRDSV9ORVRSQ19NQUNISU5FIiBdOyB0aGVuCmNhdCA8PEVPRiA+ICRIT01FLy5uZXRyYwptYWNoaW5lICRDSV9ORVRSQ19NQUNISU5FCmxvZ2luICRDSV9ORVRSQ19VU0VSTkFNRQpwYXNzd29yZCAkQ0lfTkVUUkNfUEFTU1dPUkQKRU9GCmNobW9kIDA2MDAgJEhPTUUvLm5ldHJjCmZpCnVuc2V0IENJX05FVFJDX1VTRVJOQU1FCnVuc2V0IENJX05FVFJDX1BBU1NXT1JECnVuc2V0IENJX1NDUklQVApta2RpciAtcCAiL3NyYy9hYmMiCmNkICIvc3JjL2FiYyIKCmVjaG8gKyAnZ28gdGVzdCcKZ28gdGVzdAoKZWNobyArICdnbyB2ZXQgLi8uLi4nCmdvIHZldCAuLy4uLgo=", "SHELL=/bin/sh", "TAGS=sqlite", }, @@ -182,3 +257,84 @@ func TestToConfigFull(t *testing.T) { }, }, conf) } + +func TestToWindowsConfig(t *testing.T) { + engine := docker{ + info: system.Info{OSType: "windows", Architecture: "x86_64"}, + config: config{ + enableIPv6: true, + }, + } + + conf := engine.toConfig(&backend.Step{ + Name: "test", + UUID: "23434553", + Type: backend.StepTypeCommands, + Image: "golang:1.2.3", + WorkingDir: "/src/abc", + WorkspaceBase: "/src", + Environment: map[string]string{ + "TAGS": "sqlite", + "CI_WORKSPACE": "/src", + }, + Commands: []string{"go test", "go vet ./..."}, + ExtraHosts: []backend.HostAlias{{Name: "t", IP: "1.2.3.4"}}, + Volumes: []string{"wp_default_abc:/src", "/cache:/cache/some/more", "test:/test"}, + Networks: []backend.Conn{{Name: "extra-net", Aliases: []string{"extra.net"}}}, + DNS: []string{"9.9.9.9", "8.8.8.8"}, + Failure: "fail", + AuthConfig: backend.Auth{Username: "user", Password: "123456"}, + NetworkMode: "nat", + Ports: []backend.Port{{Number: 21}, {Number: 22}}, + }, BackendOptions{}) + + assert.NotNil(t, conf) + sort.Strings(conf.Env) + assert.EqualValues(t, &container.Config{ + Image: "golang:1.2.3", + WorkingDir: "C:/src", + AttachStdout: true, + AttachStderr: true, + Entrypoint: []string{"powershell", "-noprofile", "-noninteractive", "-command", "[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Env:CI_SCRIPT)) | iex"}, + Labels: map[string]string{ + "wp_step": "test", + "wp_uuid": "23434553", + }, + Env: []string{ + "CI_SCRIPT=CiRFcnJvckFjdGlvblByZWZlcmVuY2UgPSAnU3RvcCc7CmlmICgtbm90IChUZXN0LVBhdGggIkM6L3NyYy9hYmMiKSkgeyBOZXctSXRlbSAtUGF0aCAiQzovc3JjL2FiYyIgLUl0ZW1UeXBlIERpcmVjdG9yeSAtRm9yY2UgfTsKaWYgKC1ub3QgW0Vudmlyb25tZW50XTo6R2V0RW52aXJvbm1lbnRWYXJpYWJsZSgnSE9NRScpKSB7IFtFbnZpcm9ubWVudF06OlNldEVudmlyb25tZW50VmFyaWFibGUoJ0hPTUUnLCAnYzpccm9vdCcpIH07CmlmICgtbm90IChUZXN0LVBhdGggIiRlbnY6SE9NRSIpKSB7IE5ldy1JdGVtIC1QYXRoICIkZW52OkhPTUUiIC1JdGVtVHlwZSBEaXJlY3RvcnkgLUZvcmNlIH07CmlmICgkRW52OkNJX05FVFJDX01BQ0hJTkUpIHsKJG5ldHJjPVtzdHJpbmddOjpGb3JtYXQoInswfVxfbmV0cmMiLCRFbnY6SE9NRSk7CiJtYWNoaW5lICRFbnY6Q0lfTkVUUkNfTUFDSElORSIgPj4gJG5ldHJjOwoibG9naW4gJEVudjpDSV9ORVRSQ19VU0VSTkFNRSIgPj4gJG5ldHJjOwoicGFzc3dvcmQgJEVudjpDSV9ORVRSQ19QQVNTV09SRCIgPj4gJG5ldHJjOwp9OwpbRW52aXJvbm1lbnRdOjpTZXRFbnZpcm9ubWVudFZhcmlhYmxlKCJDSV9ORVRSQ19QQVNTV09SRCIsJG51bGwpOwpbRW52aXJvbm1lbnRdOjpTZXRFbnZpcm9ubWVudFZhcmlhYmxlKCJDSV9TQ1JJUFQiLCRudWxsKTsKY2QgIkM6L3NyYy9hYmMiOwoKV3JpdGUtT3V0cHV0ICgnKyAiZ28gdGVzdCInKTsKJiBnbyB0ZXN0OyBpZiAoJExBU1RFWElUQ09ERSAtbmUgMCkge2V4aXQgJExBU1RFWElUQ09ERX0KCldyaXRlLU91dHB1dCAoJysgImdvIHZldCAuLy4uLiInKTsKJiBnbyB2ZXQgLi8uLi47IGlmICgkTEFTVEVYSVRDT0RFIC1uZSAwKSB7ZXhpdCAkTEFTVEVYSVRDT0RFfQo=", + "CI_WORKSPACE=C:/src", + "SHELL=powershell.exe", + "TAGS=sqlite", + }, + Volumes: map[string]struct{}{ + "C:/cache/some/more": {}, + "C:/src": {}, + "C:/test": {}, + }, + }, conf) + + ciScript, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(conf.Env[0], "CI_SCRIPT=")) + if assert.NoError(t, err) { + assert.EqualValues(t, ` +$ErrorActionPreference = 'Stop'; +if (-not (Test-Path "C:/src/abc")) { New-Item -Path "C:/src/abc" -ItemType Directory -Force }; +if (-not [Environment]::GetEnvironmentVariable('HOME')) { [Environment]::SetEnvironmentVariable('HOME', 'c:\root') }; +if (-not (Test-Path "$env:HOME")) { New-Item -Path "$env:HOME" -ItemType Directory -Force }; +if ($Env:CI_NETRC_MACHINE) { +$netrc=[string]::Format("{0}\_netrc",$Env:HOME); +"machine $Env:CI_NETRC_MACHINE" >> $netrc; +"login $Env:CI_NETRC_USERNAME" >> $netrc; +"password $Env:CI_NETRC_PASSWORD" >> $netrc; +}; +[Environment]::SetEnvironmentVariable("CI_NETRC_PASSWORD",$null); +[Environment]::SetEnvironmentVariable("CI_SCRIPT",$null); +cd "C:/src/abc"; + +Write-Output ('+ "go test"'); +& go test; if ($LASTEXITCODE -ne 0) {exit $LASTEXITCODE} + +Write-Output ('+ "go vet ./..."'); +& go vet ./...; if ($LASTEXITCODE -ne 0) {exit $LASTEXITCODE} +`, string(ciScript)) + } +} diff --git a/pipeline/backend/docker/convert_win.go b/pipeline/backend/docker/convert_win.go new file mode 100644 index 000000000..19cda5116 --- /dev/null +++ b/pipeline/backend/docker/convert_win.go @@ -0,0 +1,82 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package docker + +import ( + "path/filepath" + "regexp" + "strings" + + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" +) + +const ( + osTypeWindows = "windows" + defaultWindowsDriverLetter = "C:" +) + +var MustNotAddWindowsLetterPattern = regexp.MustCompile(`^(?:` + + // Drive letter followed by colon and optional backslash (C: or C:\) + `[a-zA-Z]:(?:\\|$)|` + + + // Device path starting with \\ or // followed by .\ or ./ (\\.\ or //./ or \\./ or //.\ ) + `(?:\\\\|//)\.(?:\\|/).*|` + + + // UNC path starting with \\ or // followed by non-dot (\server or //server) + `(?:\\\\|//)[^.]|` + + + // Relative path starting with .\ or ./ (.\path or ./path) + `\.(?:\\|/)` + + `)`) + +func (e *docker) windowsPathPatch(step *types.Step) { + // only patch if target is windows + if strings.ToLower(e.info.OSType) != osTypeWindows { + return + } + + // patch volumes to have an letter if not already set + for i, vol := range step.Volumes { + volParts, err := splitVolumeParts(vol) + if err != nil || len(volParts) < 2 { + // ignore non valid volumes for now + continue + } + + // fix source destination + if strings.HasPrefix(volParts[0], "/") { + volParts[0] = filepath.Join(defaultWindowsDriverLetter, volParts[0]) + } + + // fix mount destination + if !MustNotAddWindowsLetterPattern.MatchString(volParts[1]) { + volParts[1] = filepath.Join(defaultWindowsDriverLetter, volParts[1]) + } + step.Volumes[i] = strings.Join(volParts, ":") + } + + // patch workspace + if !MustNotAddWindowsLetterPattern.MatchString(step.WorkspaceBase) { + step.WorkspaceBase = filepath.Join(defaultWindowsDriverLetter, step.WorkspaceBase) + } + if !MustNotAddWindowsLetterPattern.MatchString(step.WorkingDir) { + step.WorkingDir = filepath.Join(defaultWindowsDriverLetter, step.WorkingDir) + } + if ciWorkspace, ok := step.Environment["CI_WORKSPACE"]; ok { + if !MustNotAddWindowsLetterPattern.MatchString(ciWorkspace) { + step.Environment["CI_WORKSPACE"] = filepath.Join(defaultWindowsDriverLetter, ciWorkspace) + } + } +} diff --git a/pipeline/backend/docker/convert_win_test.go b/pipeline/backend/docker/convert_win_test.go new file mode 100644 index 000000000..bfc71b148 --- /dev/null +++ b/pipeline/backend/docker/convert_win_test.go @@ -0,0 +1,44 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package docker + +import "testing" + +func TestMustNotAddWindowsLetterPattern(t *testing.T) { + tests := map[string]bool{ + `C:\Users`: true, + `D:\Data`: true, + `\\.\PhysicalDrive0`: true, + `//./COM1`: true, + `E:`: true, + `\\server\share`: true, // UNC path + `.\relative\path`: true, // Relative path + `./path`: true, // Relative with forward slash + `//server/share`: true, // UNC with forward slashes + `not/a/windows/path`: false, + ``: false, + `/usr/local`: false, + `COM1`: false, + `\\.`: false, // Incomplete device path + `//`: false, + } + + for testCase, expected := range tests { + result := MustNotAddWindowsLetterPattern.MatchString(testCase) + if result != expected { + t.Errorf("Test case %q: expected %v but got %v", testCase, expected, result) + } + } +} diff --git a/pipeline/backend/docker/docker.go b/pipeline/backend/docker/docker.go index 7b7d31326..eacc59d5a 100644 --- a/pipeline/backend/docker/docker.go +++ b/pipeline/backend/docker/docker.go @@ -22,8 +22,10 @@ import ( "path/filepath" "strings" - "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/image" "github.com/docker/docker/api/types/network" + "github.com/docker/docker/api/types/system" "github.com/docker/docker/api/types/volume" tls_config "github.com/docker/go-connections/tlsconfig" "github.com/moby/moby/client" @@ -31,21 +33,20 @@ import ( std_copy "github.com/moby/moby/pkg/stdcopy" "github.com/moby/term" "github.com/rs/zerolog/log" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" - "go.woodpecker-ci.org/woodpecker/v2/shared/utils" + backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" + "go.woodpecker-ci.org/woodpecker/v3/shared/utils" ) type docker struct { - client client.APIClient - enableIPv6 bool - network string - volumes []string - info types.Info + client client.APIClient + info system.Info + config config } const ( + EngineName = "docker" networkDriverNAT = "nat" networkDriverBridge = "bridge" volumeDriver = "local" @@ -59,11 +60,11 @@ func New() backend.Backend { } func (e *docker) Name() string { - return "docker" + return EngineName } func (e *docker) IsAvailable(ctx context.Context) bool { - if c, ok := ctx.Value(backend.CliContext).(*cli.Context); ok { + if c, ok := ctx.Value(backend.CliCommand).(*cli.Command); ok { if c.IsSet("backend-docker-host") { return true } @@ -101,7 +102,7 @@ func (e *docker) Flags() []cli.Flag { // Load new client for Docker Backend using environment variables. func (e *docker) Load(ctx context.Context) (*backend.BackendInfo, error) { - c, ok := ctx.Value(backend.CliContext).(*cli.Context) + c, ok := ctx.Value(backend.CliCommand).(*cli.Command) if !ok { return nil, backend.ErrNoCliContextFound } @@ -130,22 +131,9 @@ func (e *docker) Load(ctx context.Context) (*backend.BackendInfo, error) { return nil, err } - e.enableIPv6 = c.Bool("backend-docker-ipv6") - e.network = c.String("backend-docker-network") - - volumes := strings.Split(c.String("backend-docker-volumes"), ",") - e.volumes = make([]string, 0, len(volumes)) - // Validate provided volume definitions - for _, v := range volumes { - if v == "" { - continue - } - parts, err := splitVolumeParts(v) - if err != nil { - log.Error().Err(err).Msgf("invalid volume '%s' provided in WOODPECKER_BACKEND_DOCKER_VOLUMES", v) - continue - } - e.volumes = append(e.volumes, strings.Join(parts, ":")) + e.config, err = configFromCli(c) + if err != nil { + return nil, err } return &backend.BackendInfo{ @@ -156,41 +144,39 @@ func (e *docker) Load(ctx context.Context) (*backend.BackendInfo, error) { func (e *docker) SetupWorkflow(ctx context.Context, conf *backend.Config, taskUUID string) error { log.Trace().Str("taskUUID", taskUUID).Msg("create workflow environment") - for _, vol := range conf.Volumes { - _, err := e.client.VolumeCreate(ctx, volume.CreateOptions{ - Name: vol.Name, - Driver: volumeDriver, - }) - if err != nil { - return err - } + _, err := e.client.VolumeCreate(ctx, volume.CreateOptions{ + Name: conf.Volume.Name, + Driver: volumeDriver, + }) + if err != nil { + return err } networkDriver := networkDriverBridge if e.info.OSType == "windows" { networkDriver = networkDriverNAT } - for _, n := range conf.Networks { - _, err := e.client.NetworkCreate(ctx, n.Name, types.NetworkCreate{ - Driver: networkDriver, - EnableIPv6: e.enableIPv6, - }) - if err != nil { - return err - } - } - return nil + _, err = e.client.NetworkCreate(ctx, conf.Network.Name, network.CreateOptions{ + Driver: networkDriver, + EnableIPv6: &e.config.enableIPv6, + }) + return err } func (e *docker) StartStep(ctx context.Context, step *backend.Step, taskUUID string) error { + options, err := parseBackendOptions(step) + if err != nil { + log.Error().Err(err).Msg("could not parse backend options") + } + log.Trace().Str("taskUUID", taskUUID).Msgf("start step %s", step.Name) - config := e.toConfig(step) - hostConfig := toHostConfig(step) + config := e.toConfig(step, options) + hostConfig := toHostConfig(step, &e.config) containerName := toContainerName(step) // create pull options with encoded authorization credentials. - pullOpts := types.ImagePullOptions{} + pullOpts := image.PullOptions{} if step.AuthConfig.Username != "" && step.AuthConfig.Password != "" { pullOpts.RegistryAuth, _ = encodeAuthToBase64(step.AuthConfig) } @@ -215,9 +201,9 @@ func (e *docker) StartStep(ctx context.Context, step *backend.Step, taskUUID str } // add default volumes to the host configuration - hostConfig.Binds = utils.DeduplicateStrings(append(hostConfig.Binds, e.volumes...)) + hostConfig.Binds = utils.DeduplicateStrings(append(hostConfig.Binds, e.config.volumes...)) - _, err := e.client.ContainerCreate(ctx, config, hostConfig, nil, nil, containerName) + _, err = e.client.ContainerCreate(ctx, config, hostConfig, nil, nil, containerName) if client.IsErrNotFound(err) { // automatically pull and try to re-create the image if the // failure is caused because the image does not exist. @@ -249,15 +235,15 @@ func (e *docker) StartStep(ctx context.Context, step *backend.Step, taskUUID str } // join the container to an existing network - if e.network != "" { - err = e.client.NetworkConnect(ctx, e.network, containerName, &network.EndpointSettings{}) + if e.config.network != "" { + err = e.client.NetworkConnect(ctx, e.config.network, containerName, &network.EndpointSettings{}) if err != nil { return err } } } - return e.client.ContainerStart(ctx, containerName, startOpts) + return e.client.ContainerStart(ctx, containerName, container.StartOptions{}) } func (e *docker) WaitStep(ctx context.Context, step *backend.Step, taskUUID string) (*backend.State, error) { @@ -286,7 +272,13 @@ func (e *docker) WaitStep(ctx context.Context, step *backend.Step, taskUUID stri func (e *docker) TailStep(ctx context.Context, step *backend.Step, taskUUID string) (io.ReadCloser, error) { log.Trace().Str("taskUUID", taskUUID).Msgf("tail logs of step %s", step.Name) - logs, err := e.client.ContainerLogs(ctx, toContainerName(step), logsOpts) + logs, err := e.client.ContainerLogs(ctx, toContainerName(step), container.LogsOptions{ + Follow: true, + ShowStdout: true, + ShowStderr: true, + Details: false, + Timestamps: false, + }) if err != nil { return nil, err } @@ -331,36 +323,20 @@ func (e *docker) DestroyWorkflow(ctx context.Context, conf *backend.Config, task } } } - for _, v := range conf.Volumes { - if err := e.client.VolumeRemove(ctx, v.Name, true); err != nil { - log.Error().Err(err).Msgf("could not remove volume '%s'", v.Name) - } + if err := e.client.VolumeRemove(ctx, conf.Volume.Name, true); err != nil { + log.Error().Err(err).Msgf("could not remove volume '%s'", conf.Volume.Name) } - for _, n := range conf.Networks { - if err := e.client.NetworkRemove(ctx, n.Name); err != nil { - log.Error().Err(err).Msgf("could not remove network '%s'", n.Name) - } + if err := e.client.NetworkRemove(ctx, conf.Network.Name); err != nil { + log.Error().Err(err).Msgf("could not remove network '%s'", conf.Network.Name) } return nil } -var ( - startOpts = types.ContainerStartOptions{} - - removeOpts = types.ContainerRemoveOptions{ - RemoveVolumes: true, - RemoveLinks: false, - Force: false, - } - - logsOpts = types.ContainerLogsOptions{ - Follow: true, - ShowStdout: true, - ShowStderr: true, - Details: false, - Timestamps: false, - } -) +var removeOpts = container.RemoveOptions{ + RemoveVolumes: true, + RemoveLinks: false, + Force: false, +} func isErrContainerNotFoundOrNotRunning(err error) bool { // Error response from daemon: Cannot kill container: ...: No such container: ... @@ -377,6 +353,8 @@ func normalizeArchType(s string) string { switch s { case "x86_64": return "amd64" + case "aarch64": + return "arm64" default: return s } diff --git a/pipeline/backend/docker/flags.go b/pipeline/backend/docker/flags.go index 293bbdfba..e1ac036e2 100644 --- a/pipeline/backend/docker/flags.go +++ b/pipeline/backend/docker/flags.go @@ -15,45 +15,78 @@ package docker import ( - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var Flags = []cli.Flag{ &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_BACKEND_DOCKER_HOST", "DOCKER_HOST"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND_DOCKER_HOST", "DOCKER_HOST"), Name: "backend-docker-host", Usage: "path to docker socket or url to the docker server", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_BACKEND_DOCKER_API_VERSION", "DOCKER_API_VERSION"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND_DOCKER_API_VERSION", "DOCKER_API_VERSION"), Name: "backend-docker-api-version", Usage: "the version of the API to reach, leave empty for latest.", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_BACKEND_DOCKER_CERT_PATH", "DOCKER_CERT_PATH"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND_DOCKER_CERT_PATH", "DOCKER_CERT_PATH"), Name: "backend-docker-cert", Usage: "path to load the TLS certificates for connecting to docker server", }, &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_BACKEND_DOCKER_TLS_VERIFY", "DOCKER_TLS_VERIFY"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND_DOCKER_TLS_VERIFY", "DOCKER_TLS_VERIFY"), Name: "backend-docker-tls-verify", Usage: "enable or disable TLS verification for connecting to docker server", Value: true, }, &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_BACKEND_DOCKER_ENABLE_IPV6"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND_DOCKER_ENABLE_IPV6"), Name: "backend-docker-ipv6", Usage: "backend docker enable IPV6", Value: false, }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_BACKEND_DOCKER_NETWORK"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND_DOCKER_NETWORK"), Name: "backend-docker-network", Usage: "backend docker network", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_BACKEND_DOCKER_VOLUMES"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND_DOCKER_VOLUMES"), Name: "backend-docker-volumes", Usage: "backend docker volumes (comma separated)", }, + // + // resource limit parameters + // + &cli.IntFlag{ + Sources: cli.EnvVars("WOODPECKER_BACKEND_DOCKER_LIMIT_MEM_SWAP", "WOODPECKER_LIMIT_MEM_SWAP"), + Name: "backend-docker-limit-mem-swap", + Usage: "maximum memory used for swap in bytes", + }, + &cli.IntFlag{ + Sources: cli.EnvVars("WOODPECKER_BACKEND_DOCKER_LIMIT_MEM", "WOODPECKER_LIMIT_MEM"), + Name: "backend-docker-limit-mem", + Usage: "maximum memory allowed in bytes", + }, + &cli.IntFlag{ + Sources: cli.EnvVars("WOODPECKER_BACKEND_DOCKER_LIMIT_SHM_SIZE", "WOODPECKER_LIMIT_SHM_SIZE"), + Name: "backend-docker-limit-shm-size", + Usage: "docker /dev/shm allowed in bytes", + }, + &cli.IntFlag{ + Sources: cli.EnvVars("WOODPECKER_BACKEND_DOCKER_LIMIT_CPU_QUOTA", "WOODPECKER_LIMIT_CPU_QUOTA"), + Name: "backend-docker-limit-cpu-quota", + Usage: "impose a cpu quota", + }, + &cli.IntFlag{ + Sources: cli.EnvVars("WOODPECKER_BACKEND_DOCKER_LIMIT_CPU_SHARES", "WOODPECKER_LIMIT_CPU_SHARES"), + Name: "backend-docker-limit-cpu-shares", + Usage: "change the cpu shares", + }, + &cli.StringFlag{ + Sources: cli.EnvVars("WOODPECKER_BACKEND_DOCKER_LIMIT_CPU_SET", "WOODPECKER_LIMIT_CPU_SET"), + Name: "backend-docker-limit-cpu-set", + Usage: "set the cpus allowed to execute containers", + }, } diff --git a/pipeline/backend/dummy/dummy.go b/pipeline/backend/dummy/dummy.go index 4eadd1c65..5e56f7fbb 100644 --- a/pipeline/backend/dummy/dummy.go +++ b/pipeline/backend/dummy/dummy.go @@ -27,9 +27,9 @@ import ( "time" "github.com/rs/zerolog/log" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" + backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" ) type dummy struct { diff --git a/pipeline/backend/dummy/dummy_test.go b/pipeline/backend/dummy/dummy_test.go index f0c3d8586..44e4034ae 100644 --- a/pipeline/backend/dummy/dummy_test.go +++ b/pipeline/backend/dummy/dummy_test.go @@ -21,8 +21,8 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/dummy" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/dummy" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" ) func TestSmalPipelineDummyRun(t *testing.T) { diff --git a/pipeline/backend/kubernetes/backend_options.go b/pipeline/backend/kubernetes/backend_options.go index fa64da9f1..4dbc2c14a 100644 --- a/pipeline/backend/kubernetes/backend_options.go +++ b/pipeline/backend/kubernetes/backend_options.go @@ -1,9 +1,9 @@ package kubernetes import ( - "github.com/mitchellh/mapstructure" + "github.com/go-viper/mapstructure/v2" - backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" + backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" ) // BackendOptions defines all the advanced options for the kubernetes backend. @@ -86,9 +86,9 @@ const ( func parseBackendOptions(step *backend.Step) (BackendOptions, error) { var result BackendOptions - if step.BackendOptions == nil { + if step == nil || step.BackendOptions == nil { return result, nil } - err := mapstructure.Decode(step.BackendOptions[EngineName], &result) + err := mapstructure.WeakDecode(step.BackendOptions[EngineName], &result) return result, err } diff --git a/pipeline/backend/kubernetes/backend_options_test.go b/pipeline/backend/kubernetes/backend_options_test.go index 3d868ced1..ff44202c8 100644 --- a/pipeline/backend/kubernetes/backend_options_test.go +++ b/pipeline/backend/kubernetes/backend_options_test.go @@ -5,101 +5,143 @@ import ( "github.com/stretchr/testify/assert" - backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" + backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" ) func Test_parseBackendOptions(t *testing.T) { - got, err := parseBackendOptions(&backend.Step{BackendOptions: nil}) - assert.NoError(t, err) - assert.Equal(t, BackendOptions{}, got) - got, err = parseBackendOptions(&backend.Step{BackendOptions: map[string]any{}}) - assert.NoError(t, err) - assert.Equal(t, BackendOptions{}, got) - got, err = parseBackendOptions(&backend.Step{ - BackendOptions: map[string]any{ - "kubernetes": map[string]any{ - "nodeSelector": map[string]string{"storage": "ssd"}, - "serviceAccountName": "wp-svc-acc", - "labels": map[string]string{"app": "test"}, - "annotations": map[string]string{"apps.kubernetes.io/pod-index": "0"}, - "tolerations": []map[string]any{ - {"key": "net-port", "value": "100Mbit", "effect": TaintEffectNoSchedule}, - }, - "resources": map[string]any{ - "requests": map[string]string{"memory": "128Mi", "cpu": "1000m"}, - "limits": map[string]string{"memory": "256Mi", "cpu": "2"}, - }, - "securityContext": map[string]any{ - "privileged": newBool(true), - "runAsNonRoot": newBool(true), - "runAsUser": newInt64(101), - "runAsGroup": newInt64(101), - "fsGroup": newInt64(101), - "seccompProfile": map[string]any{ - "type": "Localhost", - "localhostProfile": "profiles/audit.json", - }, - "apparmorProfile": map[string]any{ - "type": "Localhost", - "localhostProfile": "k8s-apparmor-example-deny-write", - }, - }, - "secrets": []map[string]any{ - { - "name": "aws", - "key": "access-key", - "target": map[string]any{ - "env": "AWS_SECRET_ACCESS_KEY", + tests := []struct { + name string + step *backend.Step + want BackendOptions + wantErr bool + }{ + { + name: "nil options", + step: &backend.Step{BackendOptions: nil}, + want: BackendOptions{}, + }, + { + name: "empty options", + step: &backend.Step{BackendOptions: map[string]any{}}, + want: BackendOptions{}, + }, + { + name: "full k8s options", + step: &backend.Step{ + BackendOptions: map[string]any{ + "kubernetes": map[string]any{ + "nodeSelector": map[string]string{"storage": "ssd"}, + "serviceAccountName": "wp-svc-acc", + "labels": map[string]string{"app": "test"}, + "annotations": map[string]string{"apps.kubernetes.io/pod-index": "0"}, + "tolerations": []map[string]any{ + {"key": "net-port", "value": "100Mbit", "effect": TaintEffectNoSchedule}, }, - }, - { - "name": "reg-cred", - "key": ".dockerconfigjson", - "target": map[string]any{ - "file": "~/.docker/config.json", + "resources": map[string]any{ + "requests": map[string]string{"memory": "128Mi", "cpu": "1000m"}, + "limits": map[string]string{"memory": "256Mi", "cpu": "2"}, + }, + "securityContext": map[string]any{ + "privileged": newBool(true), + "runAsNonRoot": newBool(true), + "runAsUser": newInt64(101), + "runAsGroup": newInt64(101), + "fsGroup": newInt64(101), + "seccompProfile": map[string]any{ + "type": "Localhost", + "localhostProfile": "profiles/audit.json", + }, + "apparmorProfile": map[string]any{ + "type": "Localhost", + "localhostProfile": "k8s-apparmor-example-deny-write", + }, + }, + "secrets": []map[string]any{ + { + "name": "aws", + "key": "access-key", + "target": map[string]any{ + "env": "AWS_SECRET_ACCESS_KEY", + }, + }, + { + "name": "reg-cred", + "key": ".dockerconfigjson", + "target": map[string]any{ + "file": "~/.docker/config.json", + }, + }, }, }, }, }, - }, - }) - assert.NoError(t, err) - assert.Equal(t, BackendOptions{ - NodeSelector: map[string]string{"storage": "ssd"}, - ServiceAccountName: "wp-svc-acc", - Labels: map[string]string{"app": "test"}, - Annotations: map[string]string{"apps.kubernetes.io/pod-index": "0"}, - Tolerations: []Toleration{{Key: "net-port", Value: "100Mbit", Effect: TaintEffectNoSchedule}}, - Resources: Resources{ - Requests: map[string]string{"memory": "128Mi", "cpu": "1000m"}, - Limits: map[string]string{"memory": "256Mi", "cpu": "2"}, - }, - SecurityContext: &SecurityContext{ - Privileged: newBool(true), - RunAsNonRoot: newBool(true), - RunAsUser: newInt64(101), - RunAsGroup: newInt64(101), - FSGroup: newInt64(101), - SeccompProfile: &SecProfile{ - Type: "Localhost", - LocalhostProfile: "profiles/audit.json", - }, - ApparmorProfile: &SecProfile{ - Type: "Localhost", - LocalhostProfile: "k8s-apparmor-example-deny-write", + want: BackendOptions{ + NodeSelector: map[string]string{"storage": "ssd"}, + ServiceAccountName: "wp-svc-acc", + Labels: map[string]string{"app": "test"}, + Annotations: map[string]string{"apps.kubernetes.io/pod-index": "0"}, + Tolerations: []Toleration{{Key: "net-port", Value: "100Mbit", Effect: TaintEffectNoSchedule}}, + Resources: Resources{ + Requests: map[string]string{"memory": "128Mi", "cpu": "1000m"}, + Limits: map[string]string{"memory": "256Mi", "cpu": "2"}, + }, + SecurityContext: &SecurityContext{ + Privileged: newBool(true), + RunAsNonRoot: newBool(true), + RunAsUser: newInt64(101), + RunAsGroup: newInt64(101), + FSGroup: newInt64(101), + SeccompProfile: &SecProfile{ + Type: "Localhost", + LocalhostProfile: "profiles/audit.json", + }, + ApparmorProfile: &SecProfile{ + Type: "Localhost", + LocalhostProfile: "k8s-apparmor-example-deny-write", + }, + }, + Secrets: []SecretRef{ + { + Name: "aws", + Key: "access-key", + Target: SecretTarget{Env: "AWS_SECRET_ACCESS_KEY"}, + }, + { + Name: "reg-cred", + Key: ".dockerconfigjson", + Target: SecretTarget{File: "~/.docker/config.json"}, + }, + }, }, }, - Secrets: []SecretRef{ - { - Name: "aws", - Key: "access-key", - Target: SecretTarget{Env: "AWS_SECRET_ACCESS_KEY"}, - }, - { - Name: "reg-cred", - Key: ".dockerconfigjson", - Target: SecretTarget{File: "~/.docker/config.json"}, + { + name: "number options", + step: &backend.Step{BackendOptions: map[string]any{ + "kubernetes": map[string]any{ + "resources": map[string]any{ + "requests": map[string]int{"memory": 128, "cpu": 1000}, + "limits": map[string]int{"memory": 256, "cpu": 2}, + }, + }, + }}, + want: BackendOptions{ + Resources: Resources{ + Requests: map[string]string{"memory": "128", "cpu": "1000"}, + Limits: map[string]string{"memory": "256", "cpu": "2"}, + }, }, }, - }, got) + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := parseBackendOptions(tt.step) + if tt.wantErr { + assert.Error(t, err) + return + } + assert.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } } diff --git a/pipeline/backend/kubernetes/flags.go b/pipeline/backend/kubernetes/flags.go index 7b69ff540..910f52624 100644 --- a/pipeline/backend/kubernetes/flags.go +++ b/pipeline/backend/kubernetes/flags.go @@ -14,76 +14,77 @@ package kubernetes -import "github.com/urfave/cli/v2" +import ( + "github.com/urfave/cli/v3" +) var Flags = []cli.Flag{ &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_BACKEND_K8S_NAMESPACE"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND_K8S_NAMESPACE"), Name: "backend-k8s-namespace", Usage: "backend k8s namespace", Value: "woodpecker", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_BACKEND_K8S_VOLUME_SIZE"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND_K8S_VOLUME_SIZE"), Name: "backend-k8s-volume-size", Usage: "backend k8s volume size (default 10G)", Value: "10G", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_BACKEND_K8S_STORAGE_CLASS"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND_K8S_STORAGE_CLASS"), Name: "backend-k8s-storage-class", Usage: "backend k8s storage class", Value: "", }, &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_BACKEND_K8S_STORAGE_RWX"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND_K8S_STORAGE_RWX"), Name: "backend-k8s-storage-rwx", Usage: "backend k8s storage access mode, should ReadWriteMany (RWX) instead of ReadWriteOnce (RWO) be used? (default: true)", Value: true, }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_BACKEND_K8S_POD_LABELS"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND_K8S_POD_LABELS"), Name: "backend-k8s-pod-labels", Usage: "backend k8s additional Agent-wide worker pod labels", Value: "", }, &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_BACKEND_K8S_POD_LABELS_ALLOW_FROM_STEP"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND_K8S_POD_LABELS_ALLOW_FROM_STEP"), Name: "backend-k8s-pod-labels-allow-from-step", Usage: "whether to allow using labels from step's backend options", Value: false, }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_BACKEND_K8S_POD_ANNOTATIONS"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND_K8S_POD_ANNOTATIONS"), Name: "backend-k8s-pod-annotations", Usage: "backend k8s additional Agent-wide worker pod annotations", Value: "", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_BACKEND_K8S_POD_NODE_SELECTOR"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND_K8S_POD_NODE_SELECTOR"), Name: "backend-k8s-pod-node-selector", Usage: "backend k8s Agent-wide worker pod node selector", Value: "", }, &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_BACKEND_K8S_POD_ANNOTATIONS_ALLOW_FROM_STEP"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND_K8S_POD_ANNOTATIONS_ALLOW_FROM_STEP"), Name: "backend-k8s-pod-annotations-allow-from-step", Usage: "whether to allow using annotations from step's backend options", Value: false, }, &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_BACKEND_K8S_SECCTX_NONROOT"}, // cspell:words secctx nonroot + Sources: cli.EnvVars("WOODPECKER_BACKEND_K8S_SECCTX_NONROOT"), // cspell:words secctx nonroot Name: "backend-k8s-secctx-nonroot", Usage: "`run as non root` Kubernetes security context option", }, &cli.StringSliceFlag{ - EnvVars: []string{"WOODPECKER_BACKEND_K8S_PULL_SECRET_NAMES"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND_K8S_PULL_SECRET_NAMES"), Name: "backend-k8s-pod-image-pull-secret-names", Usage: "backend k8s pull secret names for private registries", - Value: cli.NewStringSlice("regcred"), }, &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_BACKEND_K8S_ALLOW_NATIVE_SECRETS"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND_K8S_ALLOW_NATIVE_SECRETS"), Name: "backend-k8s-allow-native-secrets", Usage: "whether to allow existing Kubernetes secrets to be referenced from steps", Value: false, diff --git a/pipeline/backend/kubernetes/kubernetes.go b/pipeline/backend/kubernetes/kubernetes.go index e0dc18b0d..7902b3785 100644 --- a/pipeline/backend/kubernetes/kubernetes.go +++ b/pipeline/backend/kubernetes/kubernetes.go @@ -16,6 +16,7 @@ package kubernetes import ( "context" + std_errs "errors" "fmt" "io" "maps" @@ -25,7 +26,7 @@ import ( "time" "github.com/rs/zerolog/log" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" "gopkg.in/yaml.v3" v1 "k8s.io/api/core/v1" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -36,7 +37,7 @@ import ( "k8s.io/client-go/rest" "k8s.io/client-go/tools/cache" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" ) const ( @@ -69,6 +70,7 @@ type config struct { } type SecurityContextConfig struct { RunAsNonRoot bool + FSGroup *int64 } func newDefaultDeleteOptions() meta_v1.DeleteOptions { @@ -83,7 +85,7 @@ func newDefaultDeleteOptions() meta_v1.DeleteOptions { func configFromCliContext(ctx context.Context) (*config, error) { if ctx != nil { - if c, ok := ctx.Value(types.CliContext).(*cli.Context); ok { + if c, ok := ctx.Value(types.CliCommand).(*cli.Command); ok { config := config{ Namespace: c.String("backend-k8s-namespace"), StorageClass: c.String("backend-k8s-storage-class"), @@ -97,13 +99,10 @@ func configFromCliContext(ctx context.Context) (*config, error) { ImagePullSecretNames: c.StringSlice("backend-k8s-pod-image-pull-secret-names"), SecurityContext: SecurityContextConfig{ RunAsNonRoot: c.Bool("backend-k8s-secctx-nonroot"), // cspell:words secctx nonroot + FSGroup: newInt64(defaultFSGroup), }, NativeSecretsAllowFromStep: c.Bool("backend-k8s-allow-native-secrets"), } - // TODO: remove in next major - if len(config.ImagePullSecretNames) == 1 && config.ImagePullSecretNames[0] == "regcred" { - log.Warn().Msg("WOODPECKER_BACKEND_K8S_PULL_SECRET_NAMES is set to the default ('regcred'). It will default to empty in Woodpecker 3.0. Set it explicitly before then.") - } // Unmarshal label and annotation settings here to ensure they're valid on startup if labels := c.String("backend-k8s-pod-labels"); labels != "" { if err := yaml.Unmarshal([]byte(labels), &config.PodLabels); err != nil { @@ -192,17 +191,15 @@ func (e *kube) getConfig() *config { func (e *kube) SetupWorkflow(ctx context.Context, conf *types.Config, taskUUID string) error { log.Trace().Str("taskUUID", taskUUID).Msgf("Setting up Kubernetes primitives") - for _, vol := range conf.Volumes { - _, err := startVolume(ctx, e, vol.Name) - if err != nil { - return err - } + _, err := startVolume(ctx, e, conf.Volume.Name) + if err != nil { + return err } var extraHosts []types.HostAlias for _, stage := range conf.Stages { for _, step := range stage.Steps { - if step.Type == types.StepTypeService { + if step.Type == types.StepTypeService || step.Detached { svc, err := startService(ctx, e, step) if err != nil { return err @@ -229,6 +226,13 @@ func (e *kube) StartStep(ctx context.Context, step *types.Step, taskUUID string) log.Error().Err(err).Msg("could not parse backend options") } + if needsRegistrySecret(step) { + err = startRegistrySecret(ctx, e, step) + if err != nil { + return err + } + } + log.Trace().Str("taskUUID", taskUUID).Msgf("starting step: %s", step.Name) _, err = startPod(ctx, e, step, options) return err @@ -246,15 +250,15 @@ func (e *kube) WaitStep(ctx context.Context, step *types.Step, taskUUID string) finished := make(chan bool) - podUpdated := func(_, new any) { - pod, ok := new.(*v1.Pod) + podUpdated := func(_, newPod any) { + pod, ok := newPod.(*v1.Pod) if !ok { - log.Error().Msgf("could not parse pod: %v", new) + log.Error().Msgf("could not parse pod: %v", newPod) return } if pod.Name == podName { - if isImagePullBackOffState(pod) { + if isImagePullBackOffState(pod) || isInvalidImageName(pod) { finished <- true } @@ -286,7 +290,7 @@ func (e *kube) WaitStep(ctx context.Context, step *types.Step, taskUUID string) return nil, err } - if isImagePullBackOffState(pod) { + if isImagePullBackOffState(pod) || isInvalidImageName(pod) { return nil, fmt.Errorf("could not pull image for pod %s", podName) } @@ -322,15 +326,15 @@ func (e *kube) TailStep(ctx context.Context, step *types.Step, taskUUID string) up := make(chan bool) - podUpdated := func(_, new any) { - pod, ok := new.(*v1.Pod) + podUpdated := func(_, newPod any) { + pod, ok := newPod.(*v1.Pod) if !ok { - log.Error().Msgf("could not parse pod: %v", new) + log.Error().Msgf("could not parse pod: %v", newPod) return } if pod.Name == podName { - if isImagePullBackOffState(pod) { + if isImagePullBackOffState(pod) || isInvalidImageName(pod) { up <- true } switch pod.Status.Phase { @@ -375,7 +379,6 @@ func (e *kube) TailStep(ctx context.Context, step *types.Step, taskUUID string) go func() { defer logs.Close() defer wc.Close() - defer rc.Close() _, err = io.Copy(wc, logs) if err != nil { @@ -386,16 +389,26 @@ func (e *kube) TailStep(ctx context.Context, step *types.Step, taskUUID string) } func (e *kube) DestroyStep(ctx context.Context, step *types.Step, taskUUID string) error { + var errs []error log.Trace().Str("taskUUID", taskUUID).Msgf("Stopping step: %s", step.Name) + if needsRegistrySecret(step) { + err := stopRegistrySecret(ctx, e, step, defaultDeleteOptions) + if err != nil { + errs = append(errs, err) + } + } + err := stopPod(ctx, e, step, defaultDeleteOptions) - return err + if err != nil { + errs = append(errs, err) + } + return std_errs.Join(errs...) } // DestroyWorkflow destroys the pipeline environment. func (e *kube) DestroyWorkflow(ctx context.Context, conf *types.Config, taskUUID string) error { log.Trace().Str("taskUUID", taskUUID).Msg("deleting Kubernetes primitives") - // Use noContext because the ctx sent to this function will be canceled/done in case of error or canceled by user. for _, stage := range conf.Stages { for _, step := range stage.Steps { err := stopPod(ctx, e, step, defaultDeleteOptions) @@ -412,11 +425,9 @@ func (e *kube) DestroyWorkflow(ctx context.Context, conf *types.Config, taskUUID } } - for _, vol := range conf.Volumes { - err := stopVolume(ctx, e, vol.Name, defaultDeleteOptions) - if err != nil { - return err - } + err := stopVolume(ctx, e, conf.Volume.Name, defaultDeleteOptions) + if err != nil { + return err } return nil diff --git a/pipeline/backend/kubernetes/kubernetes_test.go b/pipeline/backend/kubernetes/kubernetes_test.go index 5a0c3b75f..bd3e3bee3 100644 --- a/pipeline/backend/kubernetes/kubernetes_test.go +++ b/pipeline/backend/kubernetes/kubernetes_test.go @@ -45,9 +45,9 @@ func TestGettingConfig(t *testing.T) { assert.Equal(t, "default", engine.config.Namespace) assert.Equal(t, "hdd", engine.config.StorageClass) assert.Equal(t, "1G", engine.config.VolumeSize) - assert.Equal(t, false, engine.config.StorageRwx) - assert.Equal(t, 1, len(engine.config.PodLabels)) - assert.Equal(t, 1, len(engine.config.PodAnnotations)) - assert.Equal(t, 1, len(engine.config.ImagePullSecretNames)) - assert.Equal(t, false, engine.config.SecurityContext.RunAsNonRoot) + assert.False(t, engine.config.StorageRwx) + assert.Len(t, engine.config.PodLabels, 1) + assert.Len(t, engine.config.PodAnnotations, 1) + assert.Len(t, engine.config.ImagePullSecretNames, 1) + assert.False(t, engine.config.SecurityContext.RunAsNonRoot) } diff --git a/pipeline/backend/kubernetes/pod.go b/pipeline/backend/kubernetes/pod.go index 679543470..42a04356c 100644 --- a/pipeline/backend/kubernetes/pod.go +++ b/pipeline/backend/kubernetes/pod.go @@ -26,13 +26,14 @@ import ( "k8s.io/apimachinery/pkg/api/resource" meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/common" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/common" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" ) const ( - StepLabel = "step" - podPrefix = "wp-" + StepLabel = "step" + podPrefix = "wp-" + defaultFSGroup int64 = 1000 ) func mkPod(step *types.Step, config *config, podName, goos string, options BackendOptions) (*v1.Pod, error) { @@ -84,7 +85,7 @@ func podMeta(step *types.Step, config *config, options BackendOptions, podName s meta := meta_v1.ObjectMeta{ Name: podName, Namespace: config.Namespace, - Annotations: podAnnotations(config, options, podName), + Annotations: podAnnotations(config, options), } meta.Labels, err = podLabels(step, config, options) @@ -126,7 +127,7 @@ func stepLabel(step *types.Step) (string, error) { return toDNSName(step.Name) } -func podAnnotations(config *config, options BackendOptions, podName string) map[string]string { +func podAnnotations(config *config, options BackendOptions) map[string]string { annotations := make(map[string]string) if len(options.Annotations) > 0 { @@ -141,13 +142,6 @@ func podAnnotations(config *config, options BackendOptions, podName string) map[ log.Trace().Msgf("using annotations from the configuration: %v", config.PodAnnotations) maps.Copy(annotations, config.PodAnnotations) } - securityContext := options.SecurityContext - if securityContext != nil { - key, value := apparmorAnnotation(podName, securityContext.ApparmorProfile) - if key != nil && value != nil { - annotations[*key] = *value - } - } return annotations } @@ -168,8 +162,26 @@ func podSpec(step *types.Step, config *config, options BackendOptions, nsp nativ return spec, err } + if len(step.DNS) != 0 || len(step.DNSSearch) != 0 { + spec.DNSConfig = &v1.PodDNSConfig{} + if len(step.DNS) != 0 { + spec.DNSConfig.Nameservers = step.DNS + } + if len(step.DNSSearch) != 0 { + spec.DNSConfig.Searches = step.DNSSearch + } + } + log.Trace().Msgf("using the image pull secrets: %v", config.ImagePullSecretNames) spec.ImagePullSecrets = secretsReferences(config.ImagePullSecretNames) + if needsRegistrySecret(step) { + log.Trace().Msgf("using an image pull secret from registries") + name, err := registrySecretName(step) + if err != nil { + return spec, err + } + spec.ImagePullSecrets = append(spec.ImagePullSecrets, secretReference(name)) + } spec.Volumes = append(spec.Volumes, nsp.volumes...) @@ -191,9 +203,12 @@ func podContainer(step *types.Step, podName, goos string, options BackendOptions } if len(step.Commands) > 0 { - scriptEnv, command := common.GenerateContainerConf(step.Commands, goos) + scriptEnv, command := common.GenerateContainerConf(step.Commands, goos, step.WorkingDir) container.Command = command maps.Copy(step.Environment, scriptEnv) + + // step.WorkingDir will be respected by the generated script + container.WorkingDir = step.WorkspaceBase } if len(step.Entrypoint) > 0 { container.Command = step.Entrypoint @@ -377,16 +392,20 @@ func toleration(backendToleration Toleration) v1.Toleration { func podSecurityContext(sc *SecurityContext, secCtxConf SecurityContextConfig, stepPrivileged bool) *v1.PodSecurityContext { var ( - nonRoot *bool - user *int64 - group *int64 - fsGroup *int64 - seccomp *v1.SeccompProfile + nonRoot *bool + user *int64 + group *int64 + fsGroup *int64 + seccomp *v1.SeccompProfile + apparmor *v1.AppArmorProfile ) if secCtxConf.RunAsNonRoot { nonRoot = newBool(true) } + if secCtxConf.FSGroup != nil { + fsGroup = secCtxConf.FSGroup + } if sc != nil { // only allow to set user if its not root or step is privileged @@ -404,24 +423,31 @@ func podSecurityContext(sc *SecurityContext, secCtxConf SecurityContextConfig, s fsGroup = sc.FSGroup } + // if unset, set fsGroup to 1000 by default to support non-root images + if sc.FSGroup != nil { + fsGroup = sc.FSGroup + } + // only allow to set nonRoot if it's not set globally already if nonRoot == nil && sc.RunAsNonRoot != nil { nonRoot = sc.RunAsNonRoot } seccomp = seccompProfile(sc.SeccompProfile) + apparmor = apparmorProfile(sc.ApparmorProfile) } - if nonRoot == nil && user == nil && group == nil && fsGroup == nil && seccomp == nil { + if nonRoot == nil && user == nil && group == nil && fsGroup == nil && seccomp == nil && apparmor == nil { return nil } securityContext := &v1.PodSecurityContext{ - RunAsNonRoot: nonRoot, - RunAsUser: user, - RunAsGroup: group, - FSGroup: fsGroup, - SeccompProfile: seccomp, + RunAsNonRoot: nonRoot, + RunAsUser: user, + RunAsGroup: group, + FSGroup: fsGroup, + SeccompProfile: seccomp, + AppArmorProfile: apparmor, } log.Trace().Msgf("pod security context that will be used: %v", securityContext) return securityContext @@ -443,6 +469,22 @@ func seccompProfile(scp *SecProfile) *v1.SeccompProfile { return seccompProfile } +func apparmorProfile(scp *SecProfile) *v1.AppArmorProfile { + if scp == nil || len(scp.Type) == 0 { + return nil + } + log.Trace().Msgf("using AppArmor profile: %v", scp) + + apparmorProfile := &v1.AppArmorProfile{ + Type: v1.AppArmorProfileType(scp.Type), + } + if len(scp.LocalhostProfile) > 0 { + apparmorProfile.LocalhostProfile = &scp.LocalhostProfile + } + + return apparmorProfile +} + func containerSecurityContext(sc *SecurityContext, stepPrivileged bool) *v1.SecurityContext { if !stepPrivileged { return nil @@ -471,36 +513,6 @@ func containerSecurityContext(sc *SecurityContext, stepPrivileged bool) *v1.Secu return nil } -func apparmorAnnotation(containerName string, scp *SecProfile) (*string, *string) { - if scp == nil { - return nil, nil - } - log.Trace().Msgf("using AppArmor profile: %v", scp) - - var ( - profileType string - profilePath string - ) - - if scp.Type == SecProfileTypeRuntimeDefault { - profileType = "runtime" - profilePath = "default" - } - - if scp.Type == SecProfileTypeLocalhost { - profileType = "localhost" - profilePath = scp.LocalhostProfile - } - - if len(profileType) == 0 { - return nil, nil - } - - key := v1.DeprecatedAppArmorBetaContainerAnnotationKeyPrefix + containerName - value := profileType + "/" + profilePath - return &key, &value -} - func mapToEnvVars(m map[string]string) []v1.EnvVar { var ev []v1.EnvVar for k, v := range m { @@ -532,6 +544,7 @@ func stopPod(ctx context.Context, engine *kube, step *types.Step, deleteOpts met if err != nil { return err } + log.Trace().Str("name", podName).Msg("deleting pod") err = engine.client.CoreV1().Pods(engine.config.Namespace).Delete(ctx, podName, deleteOpts) diff --git a/pipeline/backend/kubernetes/pod_test.go b/pipeline/backend/kubernetes/pod_test.go index 8b8241545..01aa95ecb 100644 --- a/pipeline/backend/kubernetes/pod_test.go +++ b/pipeline/backend/kubernetes/pod_test.go @@ -22,7 +22,7 @@ import ( "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" ) func TestPodName(t *testing.T) { @@ -65,7 +65,7 @@ func TestStepLabel(t *testing.T) { } func TestTinyPod(t *testing.T) { - expected := ` + const expected = ` { "metadata": { "name": "wp-01he8bebctabr3kgk0qj36d2me-0", @@ -93,24 +93,19 @@ func TestTinyPod(t *testing.T) { "-c", "echo $CI_SCRIPT | base64 -d | /bin/sh -e" ], - "workingDir": "/woodpecker/src", "env": [ "<>", { "name": "CI", "value": "woodpecker" }, - { - "name": "HOME", - "value": "/root" - }, { "name": "SHELL", "value": "/bin/sh" }, { "name": "CI_SCRIPT", - "value": "CmlmIFsgLW4gIiRDSV9ORVRSQ19NQUNISU5FIiBdOyB0aGVuCmNhdCA8PEVPRiA+ICRIT01FLy5uZXRyYwptYWNoaW5lICRDSV9ORVRSQ19NQUNISU5FCmxvZ2luICRDSV9ORVRSQ19VU0VSTkFNRQpwYXNzd29yZCAkQ0lfTkVUUkNfUEFTU1dPUkQKRU9GCmNobW9kIDA2MDAgJEhPTUUvLm5ldHJjCmZpCnVuc2V0IENJX05FVFJDX1VTRVJOQU1FCnVuc2V0IENJX05FVFJDX1BBU1NXT1JECnVuc2V0IENJX1NDUklQVAoKZWNobyArICdncmFkbGUgYnVpbGQnCmdyYWRsZSBidWlsZAo=" + "value": "CmlmIFsgLW4gIiRDSV9ORVRSQ19NQUNISU5FIiBdOyB0aGVuCmNhdCA8PEVPRiA+ICRIT01FLy5uZXRyYwptYWNoaW5lICRDSV9ORVRSQ19NQUNISU5FCmxvZ2luICRDSV9ORVRSQ19VU0VSTkFNRQpwYXNzd29yZCAkQ0lfTkVUUkNfUEFTU1dPUkQKRU9GCmNobW9kIDA2MDAgJEhPTUUvLm5ldHJjCmZpCnVuc2V0IENJX05FVFJDX1VTRVJOQU1FCnVuc2V0IENJX05FVFJDX1BBU1NXT1JECnVuc2V0IENJX1NDUklQVApta2RpciAtcCAiL3dvb2RwZWNrZXIvc3JjIgpjZCAiL3dvb2RwZWNrZXIvc3JjIgoKZWNobyArICdncmFkbGUgYnVpbGQnCmdyYWRsZSBidWlsZAo=" } ], "resources": {}, @@ -149,7 +144,7 @@ func TestTinyPod(t *testing.T) { } func TestFullPod(t *testing.T) { - expected := ` + const expected = ` { "metadata": { "name": "wp-01he8bebctabr3kgk0qj36d2me-0", @@ -162,7 +157,6 @@ func TestFullPod(t *testing.T) { }, "annotations": { "apps.kubernetes.io/pod-index": "0", - "container.apparmor.security.beta.kubernetes.io/wp-01he8bebctabr3kgk0qj36d2me-0": "localhost/k8s-apparmor-example-deny-write", "kubernetes.io/limit-ranger": "LimitRanger plugin set: cpu, memory request and limit for container" } }, @@ -183,7 +177,6 @@ func TestFullPod(t *testing.T) { "/bin/sh", "-c" ], - "workingDir": "/woodpecker/src", "ports": [ { "containerPort": 1234 @@ -205,11 +198,7 @@ func TestFullPod(t *testing.T) { }, { "name": "CI_SCRIPT", - "value": "CmlmIFsgLW4gIiRDSV9ORVRSQ19NQUNISU5FIiBdOyB0aGVuCmNhdCA8PEVPRiA+ICRIT01FLy5uZXRyYwptYWNoaW5lICRDSV9ORVRSQ19NQUNISU5FCmxvZ2luICRDSV9ORVRSQ19VU0VSTkFNRQpwYXNzd29yZCAkQ0lfTkVUUkNfUEFTU1dPUkQKRU9GCmNobW9kIDA2MDAgJEhPTUUvLm5ldHJjCmZpCnVuc2V0IENJX05FVFJDX1VTRVJOQU1FCnVuc2V0IENJX05FVFJDX1BBU1NXT1JECnVuc2V0IENJX1NDUklQVAoKZWNobyArICdnbyBnZXQnCmdvIGdldAoKZWNobyArICdnbyB0ZXN0JwpnbyB0ZXN0Cg==" - }, - { - "name": "HOME", - "value": "/root" + "value": "CmlmIFsgLW4gIiRDSV9ORVRSQ19NQUNISU5FIiBdOyB0aGVuCmNhdCA8PEVPRiA+ICRIT01FLy5uZXRyYwptYWNoaW5lICRDSV9ORVRSQ19NQUNISU5FCmxvZ2luICRDSV9ORVRSQ19VU0VSTkFNRQpwYXNzd29yZCAkQ0lfTkVUUkNfUEFTU1dPUkQKRU9GCmNobW9kIDA2MDAgJEhPTUUvLm5ldHJjCmZpCnVuc2V0IENJX05FVFJDX1VTRVJOQU1FCnVuc2V0IENJX05FVFJDX1BBU1NXT1JECnVuc2V0IENJX1NDUklQVApta2RpciAtcCAiL3dvb2RwZWNrZXIvc3JjIgpjZCAiL3dvb2RwZWNrZXIvc3JjIgoKZWNobyArICdnbyBnZXQnCmdvIGdldAoKZWNobyArICdnbyB0ZXN0JwpnbyB0ZXN0Cg==" }, { "name": "SHELL", @@ -250,9 +239,13 @@ func TestFullPod(t *testing.T) { "runAsGroup": 101, "runAsNonRoot": true, "fsGroup": 101, + "appArmorProfile": { + "type": "Localhost", + "localhostProfile": "k8s-apparmor-example-deny-write" + }, "seccompProfile": { - "type": "Localhost", - "localhostProfile": "profiles/audit.json" + "type": "Localhost", + "localhostProfile": "profiles/audit.json" } }, "imagePullSecrets": [ @@ -261,6 +254,9 @@ func TestFullPod(t *testing.T) { }, { "name": "another-pull-secret" + }, + { + "name": "wp-01he8bebctabr3kgk0qj36d2me-0" } ], "tolerations": [ @@ -314,6 +310,7 @@ func TestFullPod(t *testing.T) { }, } pod, err := mkPod(&types.Step{ + UUID: "01he8bebctabr3kgk0qj36d2me-0", Name: "go-test", Image: "meltwater/drone-cache", WorkingDir: "/woodpecker/src", @@ -325,6 +322,10 @@ func TestFullPod(t *testing.T) { Environment: map[string]string{"CGO": "0"}, ExtraHosts: hostAliases, Ports: ports, + AuthConfig: types.Auth{ + Username: "foo", + Password: "bar", + }, }, &config{ Namespace: "woodpecker", ImagePullSecretNames: []string{"regcred", "another-pull-secret"}, @@ -390,7 +391,20 @@ func TestPodPrivilege(t *testing.T) { } pod, err = createTestPod(false, false, secCtx) assert.NoError(t, err) - assert.Nil(t, pod.Spec.SecurityContext) + assert.Equal(t, &v1.PodSecurityContext{ + SELinuxOptions: (*v1.SELinuxOptions)(nil), + WindowsOptions: (*v1.WindowsSecurityContextOptions)(nil), + RunAsUser: (*int64)(nil), + RunAsGroup: (*int64)(nil), + RunAsNonRoot: (*bool)(nil), + SupplementalGroups: []int64(nil), + SupplementalGroupsPolicy: (*v1.SupplementalGroupsPolicy)(nil), + FSGroup: newInt64(0), + Sysctls: []v1.Sysctl(nil), + FSGroupChangePolicy: (*v1.PodFSGroupChangePolicy)(nil), + SeccompProfile: (*v1.SeccompProfile)(nil), + AppArmorProfile: (*v1.AppArmorProfile)(nil), + }, pod.Spec.SecurityContext) assert.Nil(t, pod.Spec.Containers[0].SecurityContext) // step is not privileged, but security context is requesting privileged @@ -400,7 +414,7 @@ func TestPodPrivilege(t *testing.T) { pod, err = createTestPod(false, false, secCtx) assert.NoError(t, err) assert.Nil(t, pod.Spec.SecurityContext) - assert.Nil(t, pod.Spec.Containers[0].SecurityContext) + assert.Equal(t, (*v1.PodSecurityContext)(nil), pod.Spec.SecurityContext) // step is privileged and security context is requesting privileged secCtx = SecurityContext{ @@ -426,7 +440,7 @@ func TestPodPrivilege(t *testing.T) { } func TestScratchPod(t *testing.T) { - expected := ` + const expected = ` { "metadata": { "name": "wp-01he8bebctabr3kgk0qj36d2me-0", @@ -471,7 +485,7 @@ func TestScratchPod(t *testing.T) { } func TestSecrets(t *testing.T) { - expected := ` + const expected = ` { "metadata": { "name": "wp-3kgk0qj36d2me01he8bebctabr-0", diff --git a/pipeline/backend/kubernetes/secrets.go b/pipeline/backend/kubernetes/secrets.go index 47fb401da..3acba1e07 100644 --- a/pipeline/backend/kubernetes/secrets.go +++ b/pipeline/backend/kubernetes/secrets.go @@ -15,11 +15,21 @@ package kubernetes import ( + "context" + "encoding/json" "fmt" "strings" + "github.com/distribution/reference" + config_file "github.com/docker/cli/cli/config/configfile" + config_file_types "github.com/docker/cli/cli/config/types" "github.com/rs/zerolog/log" v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/utils" ) type nativeSecretsProcessor struct { @@ -189,3 +199,96 @@ func secretReference(name string) v1.LocalObjectReference { Name: name, } } + +func needsRegistrySecret(step *types.Step) bool { + return step.AuthConfig.Username != "" && step.AuthConfig.Password != "" +} + +func mkRegistrySecret(step *types.Step, config *config) (*v1.Secret, error) { + name, err := registrySecretName(step) + if err != nil { + return nil, err + } + + labels, err := registrySecretLabels(step) + if err != nil { + return nil, err + } + + named, err := utils.ParseNamed(step.Image) + if err != nil { + return nil, err + } + + authConfig := config_file.ConfigFile{ + AuthConfigs: map[string]config_file_types.AuthConfig{ + reference.Domain(named): { + Username: step.AuthConfig.Username, + Password: step.AuthConfig.Password, + }, + }, + } + + configFileJSON, err := json.Marshal(authConfig) + if err != nil { + return nil, err + } + + return &v1.Secret{ + ObjectMeta: meta_v1.ObjectMeta{ + Namespace: config.Namespace, + Name: name, + Labels: labels, + }, + Type: v1.SecretTypeDockerConfigJson, + Data: map[string][]byte{ + v1.DockerConfigJsonKey: configFileJSON, + }, + }, nil +} + +func registrySecretName(step *types.Step) (string, error) { + return podName(step) +} + +func registrySecretLabels(step *types.Step) (map[string]string, error) { + var err error + labels := make(map[string]string) + + if step.Type == types.StepTypeService { + labels[ServiceLabel], _ = serviceName(step) + } + labels[StepLabel], err = stepLabel(step) + if err != nil { + return labels, err + } + + return labels, nil +} + +func startRegistrySecret(ctx context.Context, engine *kube, step *types.Step) error { + secret, err := mkRegistrySecret(step, engine.config) + if err != nil { + return err + } + log.Trace().Msgf("creating secret: %s", secret.Name) + _, err = engine.client.CoreV1().Secrets(engine.config.Namespace).Create(ctx, secret, meta_v1.CreateOptions{}) + if err != nil { + return err + } + return nil +} + +func stopRegistrySecret(ctx context.Context, engine *kube, step *types.Step, deleteOpts meta_v1.DeleteOptions) error { + name, err := registrySecretName(step) + if err != nil { + return err + } + log.Trace().Str("name", name).Msg("deleting secret") + + err = engine.client.CoreV1().Secrets(engine.config.Namespace).Delete(ctx, name, deleteOpts) + if errors.IsNotFound(err) { + return nil + } + return err +} diff --git a/pipeline/backend/kubernetes/secrets_test.go b/pipeline/backend/kubernetes/secrets_test.go index fe0c76097..c918fc741 100644 --- a/pipeline/backend/kubernetes/secrets_test.go +++ b/pipeline/backend/kubernetes/secrets_test.go @@ -15,17 +15,21 @@ package kubernetes import ( + "encoding/json" "testing" + "github.com/kinbiko/jsonassert" "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" + + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" ) func TestNativeSecretsEnabled(t *testing.T) { nsp := newNativeSecretsProcessor(&config{ NativeSecretsAllowFromStep: true, }, nil) - assert.Equal(t, true, nsp.isEnabled()) + assert.True(t, nsp.isEnabled()) } func TestNativeSecretsDisabled(t *testing.T) { @@ -50,7 +54,7 @@ func TestNativeSecretsDisabled(t *testing.T) { }, }, }) - assert.Equal(t, false, nsp.isEnabled()) + assert.False(t, nsp.isEnabled()) err := nsp.process() assert.NoError(t, err) @@ -178,3 +182,61 @@ func TestFileSecret(t *testing.T) { }, }, nsp.mounts) } + +func TestNoAuthNoSecret(t *testing.T) { + assert.False(t, needsRegistrySecret(&types.Step{})) +} + +func TestNoPasswordNoSecret(t *testing.T) { + assert.False(t, needsRegistrySecret(&types.Step{ + AuthConfig: types.Auth{Username: "foo"}, + })) +} + +func TestNoUsernameNoSecret(t *testing.T) { + assert.False(t, needsRegistrySecret(&types.Step{ + AuthConfig: types.Auth{Password: "foo"}, + })) +} + +func TestUsernameAndPasswordNeedsSecret(t *testing.T) { + assert.True(t, needsRegistrySecret(&types.Step{ + AuthConfig: types.Auth{Username: "foo", Password: "bar"}, + })) +} + +func TestRegistrySecret(t *testing.T) { + const expected = `{ + "metadata": { + "name": "wp-01he8bebctabr3kgk0qj36d2me-0", + "namespace": "woodpecker", + "creationTimestamp": null, + "labels": { + "step": "go-test" + } + }, + "type": "kubernetes.io/dockerconfigjson", + "data": { + ".dockerconfigjson": "eyJhdXRocyI6eyJkb2NrZXIuaW8iOnsidXNlcm5hbWUiOiJmb28iLCJwYXNzd29yZCI6ImJhciJ9fX0=" + } + }` + + secret, err := mkRegistrySecret(&types.Step{ + UUID: "01he8bebctabr3kgk0qj36d2me-0", + Name: "go-test", + Image: "meltwater/drone-cache", + AuthConfig: types.Auth{ + Username: "foo", + Password: "bar", + }, + }, &config{ + Namespace: "woodpecker", + }) + assert.NoError(t, err) + + secretJSON, err := json.Marshal(secret) + assert.NoError(t, err) + + ja := jsonassert.New(t) + ja.Assertf(string(secretJSON), expected) +} diff --git a/pipeline/backend/kubernetes/service.go b/pipeline/backend/kubernetes/service.go index e187aa4bc..428684648 100644 --- a/pipeline/backend/kubernetes/service.go +++ b/pipeline/backend/kubernetes/service.go @@ -25,7 +25,7 @@ import ( meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" int_str "k8s.io/apimachinery/pkg/util/intstr" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" ) const ( diff --git a/pipeline/backend/kubernetes/service_test.go b/pipeline/backend/kubernetes/service_test.go index 91ce54c1b..ab0217534 100644 --- a/pipeline/backend/kubernetes/service_test.go +++ b/pipeline/backend/kubernetes/service_test.go @@ -20,7 +20,7 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" ) func TestServiceName(t *testing.T) { diff --git a/pipeline/backend/kubernetes/utils.go b/pipeline/backend/kubernetes/utils.go index 1393c423b..ec601bccc 100644 --- a/pipeline/backend/kubernetes/utils.go +++ b/pipeline/backend/kubernetes/utils.go @@ -65,6 +65,18 @@ func isImagePullBackOffState(pod *v1.Pod) bool { return false } +func isInvalidImageName(pod *v1.Pod) bool { + for _, containerState := range pod.Status.ContainerStatuses { + if containerState.State.Waiting != nil { + if containerState.State.Waiting.Reason == "InvalidImageName" { + return true + } + } + } + + return false +} + // getClientOutOfCluster returns a k8s client set to the request from outside of cluster. func getClientOutOfCluster() (kubernetes.Interface, error) { kubeConfigPath := os.Getenv("KUBECONFIG") // cspell:words KUBECONFIG diff --git a/pipeline/backend/local/clone.go b/pipeline/backend/local/clone.go index 4a9bb5ff4..53fa15034 100644 --- a/pipeline/backend/local/clone.go +++ b/pipeline/backend/local/clone.go @@ -27,7 +27,7 @@ import ( "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" ) // checkGitCloneCap check if we have the git binary on hand. @@ -63,10 +63,6 @@ func (e *local) setupClone(state *workflowState) error { // execClone executes a clone-step locally. func (e *local) execClone(ctx context.Context, step *types.Step, state *workflowState, env []string) error { - if scm := step.Environment["CI_REPO_SCM"]; scm != "git" { - return fmt.Errorf("local backend can only clone from git repos, but this repo use '%s'", scm) - } - if err := checkGitCloneCap(); err != nil { return fmt.Errorf("check for git clone capabilities failed: %w", err) } diff --git a/pipeline/backend/local/command.go b/pipeline/backend/local/command.go index e36fe2451..917c8ccf3 100644 --- a/pipeline/backend/local/command.go +++ b/pipeline/backend/local/command.go @@ -21,7 +21,7 @@ import ( "os" "strings" - "github.com/alessio/shellescape" + "al.essio.dev/pkg/shellescape" ) func (e *local) genCmdByShell(shell string, cmdList []string) (args []string, err error) { diff --git a/pipeline/backend/local/flags.go b/pipeline/backend/local/flags.go index 64ec61fb9..f4b621ea7 100644 --- a/pipeline/backend/local/flags.go +++ b/pipeline/backend/local/flags.go @@ -17,13 +17,13 @@ package local import ( "os" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var Flags = []cli.Flag{ &cli.StringFlag{ Name: "backend-local-temp-dir", - EnvVars: []string{"WOODPECKER_BACKEND_LOCAL_TEMP_DIR"}, + Sources: cli.EnvVars("WOODPECKER_BACKEND_LOCAL_TEMP_DIR"), Usage: "set a different temp dir to clone workflows into", Value: os.TempDir(), }, diff --git a/pipeline/backend/local/local.go b/pipeline/backend/local/local.go index 08f6abd16..ab5eff802 100644 --- a/pipeline/backend/local/local.go +++ b/pipeline/backend/local/local.go @@ -27,11 +27,11 @@ import ( "sync" "github.com/rs/zerolog/log" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" "golang.org/x/text/encoding/unicode" "golang.org/x/text/transform" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" ) type workflowState struct { @@ -62,8 +62,14 @@ func (e *local) Name() string { return "local" } -func (e *local) IsAvailable(context.Context) bool { - return true +func (e *local) IsAvailable(ctx context.Context) bool { + if c, ok := ctx.Value(types.CliCommand).(*cli.Command); ok { + if c.String("backend-engine") == e.Name() { + return true + } + } + _, inContainer := os.LookupEnv("WOODPECKER_IN_CONTAINER") + return !inContainer } func (e *local) Flags() []cli.Flag { @@ -71,7 +77,7 @@ func (e *local) Flags() []cli.Flag { } func (e *local) Load(ctx context.Context) (*types.BackendInfo, error) { - c, ok := ctx.Value(types.CliContext).(*cli.Context) + c, ok := ctx.Value(types.CliCommand).(*cli.Command) if ok { e.tempDir = c.String("backend-local-temp-dir") } diff --git a/pipeline/backend/types/backend.go b/pipeline/backend/types/backend.go index 0ef6b6859..4687018f8 100644 --- a/pipeline/backend/types/backend.go +++ b/pipeline/backend/types/backend.go @@ -18,7 +18,7 @@ import ( "context" "io" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) // Backend defines a container orchestration backend and is used diff --git a/pipeline/backend/types/config.go b/pipeline/backend/types/config.go index 16843a830..862f734a6 100644 --- a/pipeline/backend/types/config.go +++ b/pipeline/backend/types/config.go @@ -16,16 +16,16 @@ package types // Config defines the runtime configuration of a workflow. type Config struct { - Stages []*Stage `json:"pipeline"` // workflow stages - Networks []*Network `json:"networks"` // network definitions - Volumes []*Volume `json:"volumes"` // volume definitions - Secrets []*Secret `json:"secrets"` // secret definitions + Stages []*Stage `json:"pipeline"` // workflow stages + Network *Network `json:"network"` // network definition + Volume *Volume `json:"volume"` // volume definition + Secrets []*Secret `json:"secrets"` // secret definitions } -// CliContext is the context key to pass cli context to backends if needed. -var CliContext ContextKey +// CliCommand is the context key to pass cli context to backends if needed. +var CliCommand contextKey -// ContextKey is just an empty struct. It exists so CliContext can be +// contextKey is just an empty struct. It exists so CliCommand can be // an immutable public variable with a unique type. It's immutable // because nobody else can create a ContextKey, being unexported. -type ContextKey struct{} +type contextKey struct{} diff --git a/pipeline/backend/types/step.go b/pipeline/backend/types/step.go index 6eb3471b9..2f86be172 100644 --- a/pipeline/backend/types/step.go +++ b/pipeline/backend/types/step.go @@ -24,6 +24,7 @@ type Step struct { Detached bool `json:"detach,omitempty"` Privileged bool `json:"privileged,omitempty"` WorkingDir string `json:"working_dir,omitempty"` + WorkspaceBase string `json:"workspace_base,omitempty"` Environment map[string]string `json:"environment,omitempty"` Entrypoint []string `json:"entrypoint,omitempty"` Commands []string `json:"commands,omitempty"` @@ -34,12 +35,6 @@ type Step struct { Networks []Conn `json:"networks,omitempty"` DNS []string `json:"dns,omitempty"` DNSSearch []string `json:"dns_search,omitempty"` - MemSwapLimit int64 `json:"memswap_limit,omitempty"` - MemLimit int64 `json:"mem_limit,omitempty"` - ShmSize int64 `json:"shm_size,omitempty"` - CPUQuota int64 `json:"cpu_quota,omitempty"` - CPUShares int64 `json:"cpu_shares,omitempty"` - CPUSet string `json:"cpu_set,omitempty"` OnFailure bool `json:"on_failure,omitempty"` OnSuccess bool `json:"on_success,omitempty"` Failure string `json:"failure,omitempty"` diff --git a/pipeline/const.go b/pipeline/const.go index a4bec9789..473e961ab 100644 --- a/pipeline/const.go +++ b/pipeline/const.go @@ -14,4 +14,10 @@ package pipeline -const ExitCodeKilled int = 137 +const ( + ExitCodeKilled int = 137 + + // Store no more than 1mb in a log-line as 4mb is the limit of a grpc message + // and log-lines needs to be parsed by the browsers later on. + MaxLogLineLength int = 1 * 1024 * 1024 // 1mb +) diff --git a/pipeline/errors/error.go b/pipeline/errors/error.go index 729839483..4adf7fc62 100644 --- a/pipeline/errors/error.go +++ b/pipeline/errors/error.go @@ -5,7 +5,7 @@ import ( "go.uber.org/multierr" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/errors/types" ) type LinterErrorData struct { diff --git a/pipeline/errors/error_test.go b/pipeline/errors/error_test.go index 8648c4048..a1658a797 100644 --- a/pipeline/errors/error_test.go +++ b/pipeline/errors/error_test.go @@ -7,8 +7,8 @@ import ( "github.com/stretchr/testify/assert" "go.uber.org/multierr" - pipeline_errors "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors/types" + pipeline_errors "go.woodpecker-ci.org/woodpecker/v3/pipeline/errors" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/errors/types" ) func TestGetPipelineErrors(t *testing.T) { diff --git a/pipeline/frontend/metadata/drone_compatibility.go b/pipeline/frontend/metadata/drone_compatibility.go index 16dae443a..d93d14d90 100644 --- a/pipeline/frontend/metadata/drone_compatibility.go +++ b/pipeline/frontend/metadata/drone_compatibility.go @@ -20,6 +20,7 @@ func SetDroneEnviron(env map[string]string) { // webhook copyEnv("CI_COMMIT_BRANCH", "DRONE_BRANCH", env) copyEnv("CI_COMMIT_PULL_REQUEST", "DRONE_PULL_REQUEST", env) + copyEnv("CI_COMMIT_PULL_REQUEST", "PULLREQUEST_DRONE_PULL_REQUEST", env) copyEnv("CI_COMMIT_TAG", "DRONE_TAG", env) copyEnv("CI_COMMIT_SOURCE_BRANCH", "DRONE_SOURCE_BRANCH", env) copyEnv("CI_COMMIT_TARGET_BRANCH", "DRONE_TARGET_BRANCH", env) @@ -27,18 +28,16 @@ func SetDroneEnviron(env map[string]string) { copyEnv("CI_PIPELINE_NUMBER", "DRONE_BUILD_NUMBER", env) copyEnv("CI_PIPELINE_PARENT", "DRONE_BUILD_PARENT", env) copyEnv("CI_PIPELINE_EVENT", "DRONE_BUILD_EVENT", env) - copyEnv("CI_PIPELINE_STATUS", "DRONE_BUILD_STATUS", env) copyEnv("CI_PIPELINE_URL", "DRONE_BUILD_LINK", env) copyEnv("CI_PIPELINE_CREATED", "DRONE_BUILD_CREATED", env) copyEnv("CI_PIPELINE_STARTED", "DRONE_BUILD_STARTED", env) - copyEnv("CI_PIPELINE_FINISHED", "DRONE_BUILD_FINISHED", env) // commit copyEnv("CI_COMMIT_SHA", "DRONE_COMMIT", env) copyEnv("CI_COMMIT_SHA", "DRONE_COMMIT_SHA", env) copyEnv("CI_PREV_COMMIT_SHA", "DRONE_COMMIT_BEFORE", env) copyEnv("CI_COMMIT_REF", "DRONE_COMMIT_REF", env) copyEnv("CI_COMMIT_BRANCH", "DRONE_COMMIT_BRANCH", env) - copyEnv("CI_COMMIT_URL", "DRONE_COMMIT_LINK", env) + copyEnv("CI_PIPELINE_FORGE_URL", "DRONE_COMMIT_LINK", env) copyEnv("CI_COMMIT_MESSAGE", "DRONE_COMMIT_MESSAGE", env) copyEnv("CI_COMMIT_AUTHOR", "DRONE_COMMIT_AUTHOR", env) copyEnv("CI_COMMIT_AUTHOR", "DRONE_COMMIT_AUTHOR_NAME", env) @@ -46,7 +45,6 @@ func SetDroneEnviron(env map[string]string) { copyEnv("CI_COMMIT_AUTHOR_AVATAR", "DRONE_COMMIT_AUTHOR_AVATAR", env) // repo copyEnv("CI_REPO", "DRONE_REPO", env) - copyEnv("CI_REPO_SCM", "DRONE_REPO_SCM", env) copyEnv("CI_REPO_OWNER", "DRONE_REPO_OWNER", env) copyEnv("CI_REPO_NAME", "DRONE_REPO_NAME", env) copyEnv("CI_REPO_URL", "DRONE_REPO_LINK", env) @@ -58,6 +56,19 @@ func SetDroneEnviron(env map[string]string) { // misc copyEnv("CI_SYSTEM_HOST", "DRONE_SYSTEM_HOST", env) copyEnv("CI_STEP_NUMBER", "DRONE_STEP_NUMBER", env) + + env["DRONE_BUILD_STATUS"] = "success" + env["DRONE_REPO_SCM"] = "git" + + // some quirks + + // Legacy env var to prevent the plugin from throwing an error + // when converting an empty string to a number + // + // plugins affected: "plugins/manifest" + if env["CI_COMMIT_PULL_REQUEST"] == "" { + env["PULLREQUEST_DRONE_PULL_REQUEST"] = "0" + } } func copyEnv(woodpecker, drone string, env map[string]string) { diff --git a/pipeline/frontend/metadata/drone_compatibility_test.go b/pipeline/frontend/metadata/drone_compatibility_test.go index c2334585c..e31d0a7ac 100644 --- a/pipeline/frontend/metadata/drone_compatibility_test.go +++ b/pipeline/frontend/metadata/drone_compatibility_test.go @@ -20,16 +20,17 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata" ) -func TestSetDroneEnviron(t *testing.T) { +func TestSetDroneEnvironOnPull(t *testing.T) { woodpeckerVars := `CI=woodpecker CI_COMMIT_AUTHOR=6543 CI_COMMIT_AUTHOR_AVATAR=https://codeberg.org/avatars/09a234c768cb9bca78f6b2f82d6af173 CI_COMMIT_BRANCH=main CI_COMMIT_MESSAGE=fix testscript CI_COMMIT_PULL_REQUEST=9 +CI_COMMIT_PULL_REQUEST_LABELS=tests,bugfix CI_COMMIT_REF=refs/pull/9/head CI_COMMIT_REFSPEC=fix_fail-on-err:main CI_COMMIT_SHA=a778b069d9f5992786d2db9be493b43868cfce76 @@ -38,10 +39,8 @@ CI_COMMIT_TARGET_BRANCH=main CI_MACHINE=7939910e431b CI_PIPELINE_CREATED=1685749339 CI_PIPELINE_EVENT=pull_request -CI_PIPELINE_FINISHED=1685749350 CI_PIPELINE_NUMBER=41 CI_PIPELINE_STARTED=1685749339 -CI_PIPELINE_STATUS=success CI_PREV_COMMIT_AUTHOR=6543 CI_PREV_COMMIT_AUTHOR_AVATAR=https://codeberg.org/avatars/09a234c768cb9bca78f6b2f82d6af173 CI_PREV_COMMIT_BRANCH=main @@ -60,11 +59,8 @@ CI_REPO_CLONE_URL=https://codeberg.org/Epsilon_02/todo-checker.git CI_REPO_DEFAULT_BRANCH=main CI_REPO_NAME=todo-checker CI_REPO_OWNER=Epsilon_02 -CI_REPO_SCM=git -CI_STEP_FINISHED=1685749350 CI_STEP_NAME=wp_01h1z7v5d1tskaqjexw0ng6w7d_0_step_3 CI_STEP_STARTED=1685749339 -CI_STEP_STATUS=success CI_SYSTEM_PLATFORM=linux/amd64 CI_SYSTEM_HOST=ci.codeberg.org CI_SYSTEM_NAME=woodpecker @@ -76,7 +72,6 @@ CI_WORKSPACE=/woodpecker/src/codeberg.org/Epsilon_02/todo-checker` droneVars := `DRONE_BRANCH=main DRONE_BUILD_CREATED=1685749339 DRONE_BUILD_EVENT=pull_request -DRONE_BUILD_FINISHED=1685749350 DRONE_BUILD_NUMBER=41 DRONE_BUILD_STARTED=1685749339 DRONE_BUILD_STATUS=success @@ -99,7 +94,128 @@ DRONE_REPO_OWNER=Epsilon_02 DRONE_REPO_SCM=git DRONE_SOURCE_BRANCH=fix_fail-on-err DRONE_SYSTEM_HOST=ci.codeberg.org -DRONE_TARGET_BRANCH=main` +DRONE_TARGET_BRANCH=main +PULLREQUEST_DRONE_PULL_REQUEST=9` + + env := convertListToEnvMap(t, woodpeckerVars) + metadata.SetDroneEnviron(env) + // filter only new added env vars + for k := range convertListToEnvMap(t, woodpeckerVars) { + delete(env, k) + } + assert.EqualValues(t, convertListToEnvMap(t, droneVars), env) +} + +func TestSetDroneEnvironOnPush(t *testing.T) { + woodpeckerVars := `CI_COMMIT_AUTHOR=test +CI_COMMIT_AUTHOR_AVATAR=http://1.2.3.4:3000/avatars/dd46a756faad4727fb679320751f6dea +CI_COMMIT_AUTHOR_EMAIL=test@noreply.localhost +CI_COMMIT_BRANCH=main +CI_COMMIT_MESSAGE=revert 9b2aed1392fc097ef7b027712977722fb004d463 +CI_COMMIT_PULL_REQUEST= +CI_COMMIT_PULL_REQUEST_LABELS= +CI_COMMIT_REF=refs/heads/main +CI_COMMIT_REFSPEC= +CI_COMMIT_SHA=8826c98181353075bbeee8f99b400496488e3523 +CI_COMMIT_SOURCE_BRANCH= +CI_COMMIT_TAG= +CI_COMMIT_TARGET_BRANCH= +CI_FORGE_TYPE=gitea +CI_FORGE_URL=http://1.2.3.4:3000 +CI_MACHINE=hagalaz +CI_PIPELINE_CREATED=1721328737 +CI_PIPELINE_DEPLOY_TARGET= +CI_PIPELINE_DEPLOY_TASK= +CI_PIPELINE_EVENT=push +CI_PIPELINE_FILES=[".woodpecker.yaml"] +CI_PIPELINE_FORGE_URL=http://1.2.3.4:3000/test/woodpecker-test/commit/8826c98181353075bbeee8f99b400496488e3523 +CI_PIPELINE_NUMBER=24 +CI_PIPELINE_PARENT=23 +CI_PIPELINE_STARTED=1721328737 +CI_PIPELINE_URL=http://1.2.3.4:8000/repos/2/pipeline/24 +CI_PREV_COMMIT_AUTHOR=test +CI_PREV_COMMIT_AUTHOR_AVATAR=http://1.2.3.4:3000/avatars/dd46a756faad4727fb679320751f6dea +CI_PREV_COMMIT_AUTHOR_EMAIL=test@noreply.localhost +CI_PREV_COMMIT_BRANCH=main +CI_PREV_COMMIT_MESSAGE=revert 9b2aed1392fc097ef7b027712977722fb004d463 +CI_PREV_COMMIT_REF=refs/heads/main +CI_PREV_COMMIT_REFSPEC= +CI_PREV_COMMIT_SHA=8826c98181353075bbeee8f99b400496488e3523 +CI_PREV_COMMIT_URL=http://1.2.3.4:3000/test/woodpecker-test/commit/8826c98181353075bbeee8f99b400496488e3523 +CI_PREV_COMMIT_SOURCE_BRANCH= +CI_PREV_COMMIT_TARGET_BRANCH= +CI_PREV_PIPELINE_CREATED=1721086039 +CI_PREV_PIPELINE_DEPLOY_TARGET= +CI_PREV_PIPELINE_DEPLOY_TASK= +CI_PREV_PIPELINE_EVENT=push +CI_PREV_PIPELINE_FINISHED=1721086056 +CI_PREV_PIPELINE_FORGE_URL=http://1.2.3.4:3000/test/woodpecker-test/commit/8826c98181353075bbeee8f99b400496488e3523 +CI_PREV_PIPELINE_NUMBER=23 +CI_PREV_PIPELINE_PARENT=0 +CI_PREV_PIPELINE_STARTED=1721086039 +CI_PREV_PIPELINE_STATUS=failure +CI_PREV_PIPELINE_URL=http://1.2.3.4:8000/repos/2/pipeline/23 +CI_REPO=test/woodpecker-test +CI_REPO_CLONE_SSH_URL=user@1.2.3.4:test/woodpecker-test.git +CI_REPO_CLONE_URL=http://1.2.3.4:3000/test/woodpecker-test.git +CI_REPO_DEFAULT_BRANCH=main +CI_REPO_NAME=woodpecker-test +CI_REPO_OWNER=test +CI_REPO_PRIVATE=false +CI_REPO_REMOTE_ID=4 +CI_REPO_TRUSTED=false +CI_REPO_TRUSTED_NETWORK=false +CI_REPO_TRUSTED_VOLUMES=false +CI_REPO_TRUSTED_SECURITY=false +CI_REPO_URL=http://1.2.3.4:3000/test/woodpecker-test +CI_STEP_NAME= +CI_STEP_NUMBER=0 +CI_STEP_STARTED=1721328737 +CI_STEP_URL=http://1.2.3.4:8000/repos/2/pipeline/24 +CI_SYSTEM_HOST=1.2.3.4:8000 +CI_SYSTEM_NAME=woodpecker +CI_SYSTEM_PLATFORM=linux/amd64 +CI_SYSTEM_URL=http://1.2.3.4:8000 +CI_SYSTEM_VERSION=2.7.0 +CI_WORKFLOW_NAME=woodpecker +CI_WORKFLOW_NUMBER=1 +CI_WORKSPACE=/usr/local/src/1.2.3.4/test/woodpecker-test` + + droneVars := `DRONE_BRANCH=main +DRONE_BUILD_CREATED=1721328737 +DRONE_BUILD_EVENT=push +DRONE_BUILD_LINK=http://1.2.3.4:8000/repos/2/pipeline/24 +DRONE_BUILD_NUMBER=24 +DRONE_BUILD_PARENT=23 +DRONE_BUILD_STARTED=1721328737 +DRONE_BUILD_STATUS=success +DRONE_COMMIT=8826c98181353075bbeee8f99b400496488e3523 +DRONE_COMMIT_AUTHOR=test +DRONE_COMMIT_AUTHOR_AVATAR=http://1.2.3.4:3000/avatars/dd46a756faad4727fb679320751f6dea +DRONE_COMMIT_AUTHOR_EMAIL=test@noreply.localhost +DRONE_COMMIT_AUTHOR_NAME=test +DRONE_COMMIT_BEFORE=8826c98181353075bbeee8f99b400496488e3523 +DRONE_COMMIT_BRANCH=main +DRONE_COMMIT_LINK=http://1.2.3.4:3000/test/woodpecker-test/commit/8826c98181353075bbeee8f99b400496488e3523 +DRONE_COMMIT_MESSAGE=revert 9b2aed1392fc097ef7b027712977722fb004d463 +DRONE_COMMIT_REF=refs/heads/main +DRONE_COMMIT_SHA=8826c98181353075bbeee8f99b400496488e3523 +DRONE_GIT_HTTP_URL=http://1.2.3.4:3000/test/woodpecker-test.git +DRONE_PULL_REQUEST= +DRONE_REMOTE_URL=http://1.2.3.4:3000/test/woodpecker-test.git +DRONE_REPO=test/woodpecker-test +DRONE_REPO_BRANCH=main +DRONE_REPO_LINK=http://1.2.3.4:3000/test/woodpecker-test +DRONE_REPO_NAME=woodpecker-test +DRONE_REPO_OWNER=test +DRONE_REPO_PRIVATE=false +DRONE_REPO_SCM=git +DRONE_SOURCE_BRANCH= +DRONE_STEP_NUMBER=0 +DRONE_SYSTEM_HOST=1.2.3.4:8000 +DRONE_TAG= +DRONE_TARGET_BRANCH= +PULLREQUEST_DRONE_PULL_REQUEST=0` env := convertListToEnvMap(t, woodpeckerVars) metadata.SetDroneEnviron(env) @@ -114,7 +230,7 @@ func convertListToEnvMap(t *testing.T, list string) map[string]string { result := make(map[string]string) for _, s := range strings.Split(list, "\n") { before, after, _ := strings.Cut(strings.TrimSpace(s), "=") - if before == "" || after == "" { + if before == "" { t.Fatal("helper function got invalid test data") } result[before] = after diff --git a/pipeline/frontend/metadata/environment.go b/pipeline/frontend/metadata/environment.go index ccb4396b8..d95ac8762 100644 --- a/pipeline/frontend/metadata/environment.go +++ b/pipeline/frontend/metadata/environment.go @@ -25,131 +25,131 @@ import ( "github.com/rs/zerolog/log" ) -var ( - pullRegexp = regexp.MustCompile(`\d+`) - maxChangedFiles = 500 +const ( + initialEnvMapSize = 100 + maxChangedFiles = 500 ) +var pullRegexp = regexp.MustCompile(`\d+`) + // Environ returns the metadata as a map of environment variables. func (m *Metadata) Environ() map[string]string { - var ( - sourceBranch string - targetBranch string - ) + params := make(map[string]string, initialEnvMapSize) - branchParts := strings.Split(m.Curr.Commit.Refspec, ":") - if len(branchParts) == 2 { //nolint:mnd - sourceBranch = branchParts[0] - targetBranch = branchParts[1] + system := m.Sys + setNonEmptyEnvVar(params, "CI", system.Name) + setNonEmptyEnvVar(params, "CI_SYSTEM_NAME", system.Name) + setNonEmptyEnvVar(params, "CI_SYSTEM_URL", system.URL) + setNonEmptyEnvVar(params, "CI_SYSTEM_HOST", system.Host) + setNonEmptyEnvVar(params, "CI_SYSTEM_PLATFORM", system.Platform) // will be set by pipeline platform option or by agent + setNonEmptyEnvVar(params, "CI_SYSTEM_VERSION", system.Version) + + forge := m.Forge + setNonEmptyEnvVar(params, "CI_FORGE_TYPE", forge.Type) + setNonEmptyEnvVar(params, "CI_FORGE_URL", forge.URL) + + repo := m.Repo + setNonEmptyEnvVar(params, "CI_REPO", path.Join(repo.Owner, repo.Name)) + setNonEmptyEnvVar(params, "CI_REPO_NAME", repo.Name) + setNonEmptyEnvVar(params, "CI_REPO_OWNER", repo.Owner) + setNonEmptyEnvVar(params, "CI_REPO_REMOTE_ID", repo.RemoteID) + setNonEmptyEnvVar(params, "CI_REPO_URL", repo.ForgeURL) + setNonEmptyEnvVar(params, "CI_REPO_CLONE_URL", repo.CloneURL) + setNonEmptyEnvVar(params, "CI_REPO_CLONE_SSH_URL", repo.CloneSSHURL) + setNonEmptyEnvVar(params, "CI_REPO_DEFAULT_BRANCH", repo.Branch) + setNonEmptyEnvVar(params, "CI_REPO_PRIVATE", strconv.FormatBool(repo.Private)) + setNonEmptyEnvVar(params, "CI_REPO_TRUSTED_NETWORK", strconv.FormatBool(repo.Trusted.Network)) + setNonEmptyEnvVar(params, "CI_REPO_TRUSTED_VOLUMES", strconv.FormatBool(repo.Trusted.Volumes)) + setNonEmptyEnvVar(params, "CI_REPO_TRUSTED_SECURITY", strconv.FormatBool(repo.Trusted.Security)) + // Deprecated remove in 4.x + setNonEmptyEnvVar(params, "CI_REPO_TRUSTED", strconv.FormatBool(m.Repo.Trusted.Security && m.Repo.Trusted.Network && m.Repo.Trusted.Volumes)) + + pipeline := m.Curr + setNonEmptyEnvVar(params, "CI_PIPELINE_NUMBER", strconv.FormatInt(pipeline.Number, 10)) + setNonEmptyEnvVar(params, "CI_PIPELINE_PARENT", strconv.FormatInt(pipeline.Parent, 10)) + setNonEmptyEnvVar(params, "CI_PIPELINE_EVENT", pipeline.Event) + setNonEmptyEnvVar(params, "CI_PIPELINE_URL", m.getPipelineWebURL(pipeline, 0)) + setNonEmptyEnvVar(params, "CI_PIPELINE_FORGE_URL", pipeline.ForgeURL) + setNonEmptyEnvVar(params, "CI_PIPELINE_DEPLOY_TARGET", pipeline.DeployTo) + setNonEmptyEnvVar(params, "CI_PIPELINE_DEPLOY_TASK", pipeline.DeployTask) + setNonEmptyEnvVar(params, "CI_PIPELINE_CREATED", strconv.FormatInt(pipeline.Created, 10)) + setNonEmptyEnvVar(params, "CI_PIPELINE_STARTED", strconv.FormatInt(pipeline.Started, 10)) + + workflow := m.Workflow + setNonEmptyEnvVar(params, "CI_WORKFLOW_NAME", workflow.Name) + setNonEmptyEnvVar(params, "CI_WORKFLOW_NUMBER", strconv.Itoa(workflow.Number)) + + step := m.Step + setNonEmptyEnvVar(params, "CI_STEP_NAME", step.Name) + setNonEmptyEnvVar(params, "CI_STEP_NUMBER", strconv.Itoa(step.Number)) + setNonEmptyEnvVar(params, "CI_STEP_URL", m.getPipelineWebURL(pipeline, step.Number)) + // CI_STEP_STARTED will be set by agent + + commit := pipeline.Commit + setNonEmptyEnvVar(params, "CI_COMMIT_SHA", commit.Sha) + setNonEmptyEnvVar(params, "CI_COMMIT_REF", commit.Ref) + setNonEmptyEnvVar(params, "CI_COMMIT_REFSPEC", commit.Refspec) + setNonEmptyEnvVar(params, "CI_COMMIT_MESSAGE", commit.Message) + setNonEmptyEnvVar(params, "CI_COMMIT_BRANCH", commit.Branch) + setNonEmptyEnvVar(params, "CI_COMMIT_AUTHOR", commit.Author.Name) + setNonEmptyEnvVar(params, "CI_COMMIT_AUTHOR_EMAIL", commit.Author.Email) + setNonEmptyEnvVar(params, "CI_COMMIT_AUTHOR_AVATAR", commit.Author.Avatar) + if pipeline.Event == EventTag || pipeline.Event == EventRelease || strings.HasPrefix(pipeline.Commit.Ref, "refs/tags/") { + setNonEmptyEnvVar(params, "CI_COMMIT_TAG", strings.TrimPrefix(pipeline.Commit.Ref, "refs/tags/")) } - - params := map[string]string{ - "CI": m.Sys.Name, - "CI_REPO": path.Join(m.Repo.Owner, m.Repo.Name), - "CI_REPO_NAME": m.Repo.Name, - "CI_REPO_OWNER": m.Repo.Owner, - "CI_REPO_REMOTE_ID": m.Repo.RemoteID, - "CI_REPO_SCM": "git", - "CI_REPO_URL": m.Repo.ForgeURL, - "CI_REPO_CLONE_URL": m.Repo.CloneURL, - "CI_REPO_CLONE_SSH_URL": m.Repo.CloneSSHURL, - "CI_REPO_DEFAULT_BRANCH": m.Repo.Branch, - "CI_REPO_PRIVATE": strconv.FormatBool(m.Repo.Private), - "CI_REPO_TRUSTED": strconv.FormatBool(m.Repo.Trusted), - - "CI_COMMIT_SHA": m.Curr.Commit.Sha, - "CI_COMMIT_REF": m.Curr.Commit.Ref, - "CI_COMMIT_REFSPEC": m.Curr.Commit.Refspec, - "CI_COMMIT_BRANCH": m.Curr.Commit.Branch, - "CI_COMMIT_SOURCE_BRANCH": sourceBranch, - "CI_COMMIT_TARGET_BRANCH": targetBranch, - "CI_COMMIT_MESSAGE": m.Curr.Commit.Message, - "CI_COMMIT_AUTHOR": m.Curr.Commit.Author.Name, - "CI_COMMIT_AUTHOR_EMAIL": m.Curr.Commit.Author.Email, - "CI_COMMIT_AUTHOR_AVATAR": m.Curr.Commit.Author.Avatar, - "CI_COMMIT_TAG": "", // will be set if event is tag - "CI_COMMIT_PULL_REQUEST": "", // will be set if event is pull_request or pull_request_closed - "CI_COMMIT_PULL_REQUEST_LABELS": "", // will be set if event is pull_request or pull_request_closed - - "CI_PIPELINE_NUMBER": strconv.FormatInt(m.Curr.Number, 10), - "CI_PIPELINE_PARENT": strconv.FormatInt(m.Curr.Parent, 10), - "CI_PIPELINE_EVENT": m.Curr.Event, - "CI_PIPELINE_URL": m.getPipelineWebURL(m.Curr, 0), - "CI_PIPELINE_FORGE_URL": m.Curr.ForgeURL, - "CI_PIPELINE_DEPLOY_TARGET": m.Curr.DeployTo, - "CI_PIPELINE_DEPLOY_TASK": m.Curr.DeployTask, - "CI_PIPELINE_STATUS": m.Curr.Status, - "CI_PIPELINE_CREATED": strconv.FormatInt(m.Curr.Created, 10), - "CI_PIPELINE_STARTED": strconv.FormatInt(m.Curr.Started, 10), - "CI_PIPELINE_FINISHED": strconv.FormatInt(m.Curr.Finished, 10), - - "CI_WORKFLOW_NAME": m.Workflow.Name, - "CI_WORKFLOW_NUMBER": strconv.Itoa(m.Workflow.Number), - - "CI_STEP_NAME": m.Step.Name, - "CI_STEP_NUMBER": strconv.Itoa(m.Step.Number), - "CI_STEP_STATUS": "", // will be set by agent - "CI_STEP_STARTED": "", // will be set by agent - "CI_STEP_FINISHED": "", // will be set by agent - "CI_STEP_URL": m.getPipelineWebURL(m.Curr, m.Step.Number), - - "CI_PREV_COMMIT_SHA": m.Prev.Commit.Sha, - "CI_PREV_COMMIT_REF": m.Prev.Commit.Ref, - "CI_PREV_COMMIT_REFSPEC": m.Prev.Commit.Refspec, - "CI_PREV_COMMIT_BRANCH": m.Prev.Commit.Branch, - "CI_PREV_COMMIT_URL": m.Prev.ForgeURL, - "CI_PREV_COMMIT_MESSAGE": m.Prev.Commit.Message, - "CI_PREV_COMMIT_AUTHOR": m.Prev.Commit.Author.Name, - "CI_PREV_COMMIT_AUTHOR_EMAIL": m.Prev.Commit.Author.Email, - "CI_PREV_COMMIT_AUTHOR_AVATAR": m.Prev.Commit.Author.Avatar, - - "CI_PREV_PIPELINE_NUMBER": strconv.FormatInt(m.Prev.Number, 10), - "CI_PREV_PIPELINE_PARENT": strconv.FormatInt(m.Prev.Parent, 10), - "CI_PREV_PIPELINE_EVENT": m.Prev.Event, - "CI_PREV_PIPELINE_URL": m.getPipelineWebURL(m.Prev, 0), - "CI_PREV_PIPELINE_FORGE_URL": m.Prev.ForgeURL, - "CI_PREV_PIPELINE_DEPLOY_TARGET": m.Prev.DeployTo, - "CI_PREV_PIPELINE_DEPLOY_TASK": m.Prev.DeployTask, - "CI_PREV_PIPELINE_STATUS": m.Prev.Status, - "CI_PREV_PIPELINE_CREATED": strconv.FormatInt(m.Prev.Created, 10), - "CI_PREV_PIPELINE_STARTED": strconv.FormatInt(m.Prev.Started, 10), - "CI_PREV_PIPELINE_FINISHED": strconv.FormatInt(m.Prev.Finished, 10), - - "CI_SYSTEM_NAME": m.Sys.Name, - "CI_SYSTEM_URL": m.Sys.URL, - "CI_SYSTEM_HOST": m.Sys.Host, - "CI_SYSTEM_PLATFORM": m.Sys.Platform, // will be set by pipeline platform option or by agent - "CI_SYSTEM_VERSION": m.Sys.Version, - - "CI_FORGE_TYPE": m.Forge.Type, - "CI_FORGE_URL": m.Forge.URL, - - // TODO: Deprecated, remove in 3.x - "CI_COMMIT_URL": m.Curr.ForgeURL, + if pipeline.Event == EventRelease { + setNonEmptyEnvVar(params, "CI_COMMIT_PRERELEASE", strconv.FormatBool(pipeline.Commit.IsPrerelease)) } - if m.Curr.Event == EventTag || m.Curr.Event == EventRelease || strings.HasPrefix(m.Curr.Commit.Ref, "refs/tags/") { - params["CI_COMMIT_TAG"] = strings.TrimPrefix(m.Curr.Commit.Ref, "refs/tags/") - } - if m.Curr.Event == EventRelease { - params["CI_COMMIT_PRERELEASE"] = strconv.FormatBool(m.Curr.Commit.IsPrerelease) - } - if m.Curr.Event == EventPull || m.Curr.Event == EventPullClosed { - params["CI_COMMIT_PULL_REQUEST"] = pullRegexp.FindString(m.Curr.Commit.Ref) - params["CI_COMMIT_PULL_REQUEST_LABELS"] = strings.Join(m.Curr.Commit.PullRequestLabels, ",") + if pipeline.Event == EventPull || pipeline.Event == EventPullClosed { + sourceBranch, targetBranch := getSourceTargetBranches(commit.Refspec) + setNonEmptyEnvVar(params, "CI_COMMIT_SOURCE_BRANCH", sourceBranch) + setNonEmptyEnvVar(params, "CI_COMMIT_TARGET_BRANCH", targetBranch) + setNonEmptyEnvVar(params, "CI_COMMIT_PULL_REQUEST", pullRegexp.FindString(pipeline.Commit.Ref)) + setNonEmptyEnvVar(params, "CI_COMMIT_PULL_REQUEST_LABELS", strings.Join(pipeline.Commit.PullRequestLabels, ",")) } // Only export changed files if maxChangedFiles is not exceeded - if len(m.Curr.Commit.ChangedFiles) == 0 { + changedFiles := commit.ChangedFiles + if len(changedFiles) == 0 { params["CI_PIPELINE_FILES"] = "[]" - } else if len(m.Curr.Commit.ChangedFiles) <= maxChangedFiles { + } else if len(changedFiles) <= maxChangedFiles { // we have to use json, as other separators like ;, or space are valid filename chars - changedFiles, err := json.Marshal(m.Curr.Commit.ChangedFiles) + changedFiles, err := json.Marshal(changedFiles) if err != nil { log.Error().Err(err).Msg("marshal changed files") } params["CI_PIPELINE_FILES"] = string(changedFiles) } + prevPipeline := m.Prev + setNonEmptyEnvVar(params, "CI_PREV_PIPELINE_NUMBER", strconv.FormatInt(prevPipeline.Number, 10)) + setNonEmptyEnvVar(params, "CI_PREV_PIPELINE_PARENT", strconv.FormatInt(prevPipeline.Parent, 10)) + setNonEmptyEnvVar(params, "CI_PREV_PIPELINE_EVENT", prevPipeline.Event) + setNonEmptyEnvVar(params, "CI_PREV_PIPELINE_URL", m.getPipelineWebURL(prevPipeline, 0)) + setNonEmptyEnvVar(params, "CI_PREV_PIPELINE_FORGE_URL", prevPipeline.ForgeURL) + setNonEmptyEnvVar(params, "CI_PREV_COMMIT_URL", prevPipeline.ForgeURL) // why commit url? + setNonEmptyEnvVar(params, "CI_PREV_PIPELINE_DEPLOY_TARGET", prevPipeline.DeployTo) + setNonEmptyEnvVar(params, "CI_PREV_PIPELINE_DEPLOY_TASK", prevPipeline.DeployTask) + setNonEmptyEnvVar(params, "CI_PREV_PIPELINE_STATUS", prevPipeline.Status) + setNonEmptyEnvVar(params, "CI_PREV_PIPELINE_CREATED", strconv.FormatInt(prevPipeline.Created, 10)) + setNonEmptyEnvVar(params, "CI_PREV_PIPELINE_STARTED", strconv.FormatInt(prevPipeline.Started, 10)) + setNonEmptyEnvVar(params, "CI_PREV_PIPELINE_FINISHED", strconv.FormatInt(prevPipeline.Finished, 10)) + + prevCommit := prevPipeline.Commit + setNonEmptyEnvVar(params, "CI_PREV_COMMIT_SHA", prevCommit.Sha) + setNonEmptyEnvVar(params, "CI_PREV_COMMIT_REF", prevCommit.Ref) + setNonEmptyEnvVar(params, "CI_PREV_COMMIT_REFSPEC", prevCommit.Refspec) + setNonEmptyEnvVar(params, "CI_PREV_COMMIT_MESSAGE", prevCommit.Message) + setNonEmptyEnvVar(params, "CI_PREV_COMMIT_BRANCH", prevCommit.Branch) + setNonEmptyEnvVar(params, "CI_PREV_COMMIT_AUTHOR", prevCommit.Author.Name) + setNonEmptyEnvVar(params, "CI_PREV_COMMIT_AUTHOR_EMAIL", prevCommit.Author.Email) + setNonEmptyEnvVar(params, "CI_PREV_COMMIT_AUTHOR_AVATAR", prevCommit.Author.Avatar) + if prevPipeline.Event == EventPull || prevPipeline.Event == EventPullClosed { + prevSourceBranch, prevTargetBranch := getSourceTargetBranches(prevCommit.Refspec) + setNonEmptyEnvVar(params, "CI_PREV_COMMIT_SOURCE_BRANCH", prevSourceBranch) + setNonEmptyEnvVar(params, "CI_PREV_COMMIT_TARGET_BRANCH", prevTargetBranch) + } + return params } @@ -160,3 +160,26 @@ func (m *Metadata) getPipelineWebURL(pipeline Pipeline, stepNumber int) string { return fmt.Sprintf("%s/repos/%d/pipeline/%d/%d", m.Sys.URL, m.Repo.ID, pipeline.Number, stepNumber) } + +func getSourceTargetBranches(refspec string) (string, string) { + var ( + sourceBranch string + targetBranch string + ) + + branchParts := strings.Split(refspec, ":") + if len(branchParts) == 2 { //nolint:mnd + sourceBranch = branchParts[0] + targetBranch = branchParts[1] + } + + return sourceBranch, targetBranch +} + +func setNonEmptyEnvVar(env map[string]string, key, value string) { + if len(value) > 0 { + env[key] = value + } else { + log.Trace().Str("variable", key).Msg("env var is filtered as it's empty") + } +} diff --git a/pipeline/frontend/metadata/types.go b/pipeline/frontend/metadata/types.go index 9cf7e64a1..7b1084f65 100644 --- a/pipeline/frontend/metadata/types.go +++ b/pipeline/frontend/metadata/types.go @@ -29,17 +29,16 @@ type ( // Repo defines runtime metadata for a repository. Repo struct { - ID int64 `json:"id,omitempty"` - Name string `json:"name,omitempty"` - Owner string `json:"owner,omitempty"` - RemoteID string `json:"remote_id,omitempty"` - ForgeURL string `json:"forge_url,omitempty"` - CloneURL string `json:"clone_url,omitempty"` - CloneSSHURL string `json:"clone_url_ssh,omitempty"` - Private bool `json:"private,omitempty"` - Secrets []Secret `json:"secrets,omitempty"` - Branch string `json:"default_branch,omitempty"` - Trusted bool `json:"trusted,omitempty"` + ID int64 `json:"id,omitempty"` + Name string `json:"name,omitempty"` + Owner string `json:"owner,omitempty"` + RemoteID string `json:"remote_id,omitempty"` + ForgeURL string `json:"forge_url,omitempty"` + CloneURL string `json:"clone_url,omitempty"` + CloneSSHURL string `json:"clone_url_ssh,omitempty"` + Private bool `json:"private,omitempty"` + Branch string `json:"default_branch,omitempty"` + Trusted TrustedConfiguration `json:"trusted,omitempty"` } // Pipeline defines runtime metadata for a pipeline. @@ -91,14 +90,6 @@ type ( Number int `json:"number,omitempty"` } - // Secret defines a runtime secret. - Secret struct { - Name string `json:"name,omitempty"` - Value string `json:"value,omitempty"` - Mount string `json:"mount,omitempty"` - Mask bool `json:"mask,omitempty"` - } - // System defines runtime metadata for a ci/cd system. System struct { Name string `json:"name,omitempty"` @@ -121,4 +112,10 @@ type ( // URL returns the root url of a configured forge URL() string } + + TrustedConfiguration struct { + Network bool `json:"network,omitempty"` + Volumes bool `json:"volumes,omitempty"` + Security bool `json:"security,omitempty"` + } ) diff --git a/pipeline/frontend/yaml/compiler/compiler.go b/pipeline/frontend/yaml/compiler/compiler.go index 5db566513..76ccf3c97 100644 --- a/pipeline/frontend/yaml/compiler/compiler.go +++ b/pipeline/frontend/yaml/compiler/compiler.go @@ -16,12 +16,13 @@ package compiler import ( "fmt" + "path" - backend_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata" - yaml_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/types" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/utils" - "go.woodpecker-ci.org/woodpecker/v2/shared/constant" + backend_types "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata" + yaml_types "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/utils" + "go.woodpecker-ci.org/woodpecker/v3/shared/constant" ) const ( @@ -45,10 +46,10 @@ type Secret struct { func (s *Secret) Available(event string, container *yaml_types.Container) error { onlyAllowSecretForPlugins := len(s.AllowedPlugins) > 0 if onlyAllowSecretForPlugins && !container.IsPlugin() { - return fmt.Errorf("secret %q only allowed to be used by plugins by step %q", s.Name, container.Name) + return fmt.Errorf("secret %q is only allowed to be used by plugins (a filter has been set on the secret). Note: Image filters do not work for normal steps", s.Name) } - if onlyAllowSecretForPlugins && !utils.MatchImage(container.Image, s.AllowedPlugins...) { + if onlyAllowSecretForPlugins && !utils.MatchImageDynamic(container.Image, s.AllowedPlugins...) { return fmt.Errorf("secret %q is not allowed to be used with image %q by step %q", s.Name, container.Image, container.Name) } @@ -80,41 +81,33 @@ func (s *Secret) Match(event string) bool { return false } -type ResourceLimit struct { - MemSwapLimit int64 - MemLimit int64 - ShmSize int64 - CPUQuota int64 - CPUShares int64 - CPUSet string -} - // Compiler compiles the yaml. type Compiler struct { - local bool - escalated []string - prefix string - volumes []string - networks []string - env map[string]string - cloneEnv map[string]string - base string - path string - metadata metadata.Metadata - registries []Registry - secrets map[string]Secret - reslimit ResourceLimit - defaultCloneImage string - trustedPipeline bool - netrcOnlyTrusted bool + local bool + escalated []string + prefix string + volumes []string + networks []string + env map[string]string + cloneEnv map[string]string + workspaceBase string + workspacePath string + metadata metadata.Metadata + registries []Registry + secrets map[string]Secret + defaultClonePlugin string + trustedClonePlugins []string + securityTrustedPipeline bool } // New creates a new Compiler with options. func New(opts ...Option) *Compiler { compiler := &Compiler{ - env: map[string]string{}, - cloneEnv: map[string]string{}, - secrets: map[string]Secret{}, + env: map[string]string{}, + cloneEnv: map[string]string{}, + secrets: map[string]Secret{}, + defaultClonePlugin: constant.DefaultClonePlugin, + trustedClonePlugins: constant.TrustedClonePlugins, } for _, opt := range opts { opt(compiler) @@ -136,14 +129,14 @@ func (c *Compiler) Compile(conf *yaml_types.Workflow) (*backend_types.Config, er } // create a default volume - config.Volumes = append(config.Volumes, &backend_types.Volume{ + config.Volume = &backend_types.Volume{ Name: fmt.Sprintf("%s_default", c.prefix), - }) + } // create a default network - config.Networks = append(config.Networks, &backend_types.Network{ + config.Network = &backend_types.Network{ Name: fmt.Sprintf("%s_default", c.prefix), - }) + } // create secrets for mask for _, sec := range c.secrets { @@ -156,26 +149,21 @@ func (c *Compiler) Compile(conf *yaml_types.Workflow) (*backend_types.Config, er // overrides the default workspace paths when specified // in the YAML file. if len(conf.Workspace.Base) != 0 { - c.base = conf.Workspace.Base + c.workspaceBase = path.Clean(conf.Workspace.Base) } if len(conf.Workspace.Path) != 0 { - c.path = conf.Workspace.Path - } - - cloneImage := constant.DefaultCloneImage - if len(c.defaultCloneImage) > 0 { - cloneImage = c.defaultCloneImage + c.workspacePath = path.Clean(conf.Workspace.Path) } // add default clone step - if !c.local && len(conf.Clone.ContainerList) == 0 && !conf.SkipClone { + if !c.local && len(conf.Clone.ContainerList) == 0 && !conf.SkipClone && len(c.defaultClonePlugin) != 0 { cloneSettings := map[string]any{"depth": "0"} if c.metadata.Curr.Event == metadata.EventTag { cloneSettings["tags"] = "true" } container := &yaml_types.Container{ Name: defaultCloneName, - Image: cloneImage, + Image: c.defaultClonePlugin, Settings: cloneSettings, Environment: make(map[string]any), } @@ -207,7 +195,7 @@ func (c *Compiler) Compile(conf *yaml_types.Workflow) (*backend_types.Config, er } // only inject netrc if it's a trusted repo or a trusted plugin - if !c.netrcOnlyTrusted || c.trustedPipeline || (container.IsPlugin() && container.IsTrustedCloneImage()) { + if c.securityTrustedPipeline || (container.IsPlugin() && container.IsTrustedCloneImage(c.trustedClonePlugins)) { for k, v := range c.cloneEnv { step.Environment[k] = v } @@ -263,8 +251,8 @@ func (c *Compiler) Compile(conf *yaml_types.Workflow) (*backend_types.Config, er return nil, err } - // inject netrc if it's a trusted repo or a trusted clone-plugin - if c.trustedPipeline || (container.IsPlugin() && container.IsTrustedCloneImage()) { + // only inject netrc if it's a trusted repo or a trusted plugin + if c.securityTrustedPipeline || (container.IsPlugin() && container.IsTrustedCloneImage(c.trustedClonePlugins)) { for k, v := range c.cloneEnv { step.Environment[k] = v } @@ -274,7 +262,6 @@ func (c *Compiler) Compile(conf *yaml_types.Workflow) (*backend_types.Config, er step: step, position: pos, name: container.Name, - group: container.Group, dependsOn: container.DependsOn, }) } diff --git a/pipeline/frontend/yaml/compiler/compiler_test.go b/pipeline/frontend/yaml/compiler/compiler_test.go index 41e055b36..f8179e7c3 100644 --- a/pipeline/frontend/yaml/compiler/compiler_test.go +++ b/pipeline/frontend/yaml/compiler/compiler_test.go @@ -19,11 +19,11 @@ import ( "github.com/stretchr/testify/assert" - backend_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata" - yaml_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/types" - yaml_base_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/types/base" - "go.woodpecker-ci.org/woodpecker/v2/shared/constant" + backend_types "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata" + yaml_types "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/types" + yaml_base_types "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/types/base" + "go.woodpecker-ci.org/woodpecker/v3/shared/constant" ) func TestSecretAvailable(t *testing.T) { @@ -50,7 +50,7 @@ func TestSecretAvailable(t *testing.T) { assert.ErrorContains(t, secret.Available("push", &yaml_types.Container{ Image: "golang", Commands: yaml_base_types.StringOrSlice{"echo 'this is not a plugin'"}, - }), "only allowed to be used by plugins by step") + }), "is only allowed to be used by plugins (a filter has been set on the secret). Note: Image filters do not work for normal steps") assert.ErrorContains(t, secret.Available("push", &yaml_types.Container{ Image: "not-golang", Commands: yaml_base_types.StringOrSlice{}, @@ -61,13 +61,14 @@ func TestSecretAvailable(t *testing.T) { } func TestCompilerCompile(t *testing.T) { + repoURL := "https://github.com/octocat/hello-world" compiler := New( WithMetadata(metadata.Metadata{ Repo: metadata.Repo{ Owner: "octacat", Name: "hello-world", Private: true, - ForgeURL: "https://github.com/octocat/hello-world", + ForgeURL: repoURL, CloneURL: "https://github.com/octocat/hello-world.git", }, }), @@ -76,25 +77,29 @@ func TestCompilerCompile(t *testing.T) { "COLORED": "true", }), WithPrefix("test"), + // we use "/test" as custom workspace base to ensure the enforcement of the pluginWorkspaceBase is applied + WithWorkspaceFromURL("/test", repoURL), ) - defaultNetworks := []*backend_types.Network{{ + defaultNetwork := &backend_types.Network{ Name: "test_default", - }} - defaultVolumes := []*backend_types.Volume{{ + } + defaultVolume := &backend_types.Volume{ Name: "test_default", - }} + } defaultCloneStage := &backend_types.Stage{ Steps: []*backend_types.Step{{ - Name: "clone", - Type: backend_types.StepTypeClone, - Image: constant.DefaultCloneImage, - OnSuccess: true, - Failure: "fail", - Volumes: []string{defaultVolumes[0].Name + ":"}, - Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"clone"}}}, - ExtraHosts: []backend_types.HostAlias{}, + Name: "clone", + Type: backend_types.StepTypeClone, + Image: constant.DefaultClonePlugin, + OnSuccess: true, + Failure: "fail", + Volumes: []string{defaultVolume.Name + ":/woodpecker"}, + WorkingDir: "/woodpecker/src/github.com/octocat/hello-world", + WorkspaceBase: "/woodpecker", + Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"clone"}}}, + ExtraHosts: []backend_types.HostAlias{}, }}, } @@ -108,17 +113,17 @@ func TestCompilerCompile(t *testing.T) { name: "empty workflow, no clone", fronConf: &yaml_types.Workflow{SkipClone: true}, backConf: &backend_types.Config{ - Networks: defaultNetworks, - Volumes: defaultVolumes, + Network: defaultNetwork, + Volume: defaultVolume, }, }, { name: "empty workflow, default clone", fronConf: &yaml_types.Workflow{}, backConf: &backend_types.Config{ - Networks: defaultNetworks, - Volumes: defaultVolumes, - Stages: []*backend_types.Stage{defaultCloneStage}, + Network: defaultNetwork, + Volume: defaultVolume, + Stages: []*backend_types.Stage{defaultCloneStage}, }, }, { @@ -128,77 +133,87 @@ func TestCompilerCompile(t *testing.T) { Image: "dummy_img", }}}}, backConf: &backend_types.Config{ - Networks: defaultNetworks, - Volumes: defaultVolumes, + Network: defaultNetwork, + Volume: defaultVolume, Stages: []*backend_types.Stage{defaultCloneStage, { Steps: []*backend_types.Step{{ - Name: "dummy", - Type: backend_types.StepTypePlugin, - Image: "dummy_img", - OnSuccess: true, - Failure: "fail", - Volumes: []string{defaultVolumes[0].Name + ":"}, - Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"dummy"}}}, - ExtraHosts: []backend_types.HostAlias{}, + Name: "dummy", + Type: backend_types.StepTypePlugin, + Image: "dummy_img", + OnSuccess: true, + Failure: "fail", + Volumes: []string{defaultVolume.Name + ":/woodpecker"}, + WorkingDir: "/woodpecker/src/github.com/octocat/hello-world", + WorkspaceBase: "/woodpecker", + Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"dummy"}}}, + ExtraHosts: []backend_types.HostAlias{}, }}, }}, }, }, { - name: "workflow with three steps and one group", + name: "workflow with three steps", fronConf: &yaml_types.Workflow{Steps: yaml_types.ContainerList{ContainerList: []*yaml_types.Container{{ Name: "echo env", Image: "bash", Commands: []string{"env"}, }, { Name: "parallel echo 1", - Group: "parallel", Image: "bash", Commands: []string{"echo 1"}, }, { Name: "parallel echo 2", - Group: "parallel", Image: "bash", Commands: []string{"echo 2"}, }}}}, backConf: &backend_types.Config{ - Networks: defaultNetworks, - Volumes: defaultVolumes, - Stages: []*backend_types.Stage{defaultCloneStage, { - Steps: []*backend_types.Step{{ - Name: "echo env", - Type: backend_types.StepTypeCommands, - Image: "bash", - Commands: []string{"env"}, - OnSuccess: true, - Failure: "fail", - Volumes: []string{defaultVolumes[0].Name + ":"}, - Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo env"}}}, - ExtraHosts: []backend_types.HostAlias{}, - }}, - }, { - Steps: []*backend_types.Step{{ - Name: "parallel echo 1", - Type: backend_types.StepTypeCommands, - Image: "bash", - Commands: []string{"echo 1"}, - OnSuccess: true, - Failure: "fail", - Volumes: []string{defaultVolumes[0].Name + ":"}, - Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"parallel echo 1"}}}, - ExtraHosts: []backend_types.HostAlias{}, + Network: defaultNetwork, + Volume: defaultVolume, + Stages: []*backend_types.Stage{ + defaultCloneStage, { + Steps: []*backend_types.Step{{ + Name: "echo env", + Type: backend_types.StepTypeCommands, + Image: "bash", + Commands: []string{"env"}, + OnSuccess: true, + Failure: "fail", + Volumes: []string{defaultVolume.Name + ":/test"}, + WorkingDir: "/test/src/github.com/octocat/hello-world", + WorkspaceBase: "/test", + Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo env"}}}, + ExtraHosts: []backend_types.HostAlias{}, + }}, }, { - Name: "parallel echo 2", - Type: backend_types.StepTypeCommands, - Image: "bash", - Commands: []string{"echo 2"}, - OnSuccess: true, - Failure: "fail", - Volumes: []string{defaultVolumes[0].Name + ":"}, - Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"parallel echo 2"}}}, - ExtraHosts: []backend_types.HostAlias{}, - }}, - }}, + Steps: []*backend_types.Step{{ + Name: "parallel echo 1", + Type: backend_types.StepTypeCommands, + Image: "bash", + Commands: []string{"echo 1"}, + OnSuccess: true, + Failure: "fail", + Volumes: []string{defaultVolume.Name + ":/test"}, + WorkingDir: "/test/src/github.com/octocat/hello-world", + WorkspaceBase: "/test", + Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"parallel echo 1"}}}, + ExtraHosts: []backend_types.HostAlias{}, + }}, + }, { + Steps: []*backend_types.Step{{ + Name: "parallel echo 2", + Type: backend_types.StepTypeCommands, + Image: "bash", + Commands: []string{"echo 2"}, + OnSuccess: true, + Failure: "fail", + Volumes: []string{defaultVolume.Name + ":/test"}, + WorkingDir: "/test/src/github.com/octocat/hello-world", + WorkspaceBase: "/test", + Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"parallel echo 2"}}}, + ExtraHosts: []backend_types.HostAlias{}, + }}, + }, + }, }, }, { @@ -218,41 +233,47 @@ func TestCompilerCompile(t *testing.T) { Commands: []string{"echo 2"}, }}}}, backConf: &backend_types.Config{ - Networks: defaultNetworks, - Volumes: defaultVolumes, + Network: defaultNetwork, + Volume: defaultVolume, Stages: []*backend_types.Stage{defaultCloneStage, { Steps: []*backend_types.Step{{ - Name: "echo env", - Type: backend_types.StepTypeCommands, - Image: "bash", - Commands: []string{"env"}, - OnSuccess: true, - Failure: "fail", - Volumes: []string{defaultVolumes[0].Name + ":"}, - Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo env"}}}, - ExtraHosts: []backend_types.HostAlias{}, + Name: "echo env", + Type: backend_types.StepTypeCommands, + Image: "bash", + Commands: []string{"env"}, + OnSuccess: true, + Failure: "fail", + Volumes: []string{defaultVolume.Name + ":/test"}, + WorkingDir: "/test/src/github.com/octocat/hello-world", + WorkspaceBase: "/test", + Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo env"}}}, + ExtraHosts: []backend_types.HostAlias{}, }, { - Name: "echo 2", - Type: backend_types.StepTypeCommands, - Image: "bash", - Commands: []string{"echo 2"}, - OnSuccess: true, - Failure: "fail", - Volumes: []string{defaultVolumes[0].Name + ":"}, - Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo 2"}}}, - ExtraHosts: []backend_types.HostAlias{}, + Name: "echo 2", + Type: backend_types.StepTypeCommands, + Image: "bash", + Commands: []string{"echo 2"}, + OnSuccess: true, + Failure: "fail", + Volumes: []string{defaultVolume.Name + ":/test"}, + WorkingDir: "/test/src/github.com/octocat/hello-world", + WorkspaceBase: "/test", + Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo 2"}}}, + ExtraHosts: []backend_types.HostAlias{}, }}, }, { Steps: []*backend_types.Step{{ - Name: "echo 1", - Type: backend_types.StepTypeCommands, - Image: "bash", - Commands: []string{"echo 1"}, - OnSuccess: true, - Failure: "fail", - Volumes: []string{defaultVolumes[0].Name + ":"}, - Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo 1"}}}, - ExtraHosts: []backend_types.HostAlias{}, + Name: "echo 1", + Type: backend_types.StepTypeCommands, + Image: "bash", + Commands: []string{"echo 1"}, + OnSuccess: true, + Failure: "fail", + Volumes: []string{defaultVolume.Name + ":/test"}, + WorkingDir: "/test/src/github.com/octocat/hello-world", + WorkspaceBase: "/test", + Networks: []backend_types.Conn{{Name: "test_default", Aliases: []string{"echo 1"}}}, + ExtraHosts: []backend_types.HostAlias{}, }}, }}, }, @@ -263,7 +284,9 @@ func TestCompilerCompile(t *testing.T) { Name: "step", Image: "bash", Commands: []string{"env"}, - Secrets: yaml_types.Secrets{Secrets: []*yaml_types.Secret{{Source: "missing", Target: "missing"}}}, + Environment: yaml_base_types.EnvironmentMap{ + "MISSING": map[string]any{"from_secret": "missing"}, + }, }}}}, backConf: nil, expectedErr: "secret \"missing\" not found", @@ -285,14 +308,14 @@ func TestCompilerCompile(t *testing.T) { backConf, err := compiler.Compile(test.fronConf) if test.expectedErr != "" { assert.Error(t, err) - assert.Equal(t, err.Error(), test.expectedErr) + assert.Equal(t, test.expectedErr, err.Error()) } else { // we ignore uuids in steps and only check if global env got set ... for _, st := range backConf.Stages { for _, s := range st.Steps { s.UUID = "" - assert.Truef(t, s.Environment["VERBOSE"] == "true", "expect to get value of global set environment") - assert.Truef(t, len(s.Environment) > 50, "expect to have a lot of build in variables") + assert.Truef(t, s.Environment["VERBOSE"] == "true", "expected to get value of global set environment") + assert.Truef(t, len(s.Environment) > 10, "expected to have a lot of built-in variables") s.Environment = nil } } diff --git a/pipeline/frontend/yaml/compiler/convert.go b/pipeline/frontend/yaml/compiler/convert.go index 92d68fe45..57042d68f 100644 --- a/pipeline/frontend/yaml/compiler/convert.go +++ b/pipeline/frontend/yaml/compiler/convert.go @@ -23,11 +23,18 @@ import ( "github.com/oklog/ulid/v2" - backend_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/compiler/settings" - yaml_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/types" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/utils" + backend_types "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/compiler/settings" + yaml_types "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/utils" +) + +const ( + // The pluginWorkspaceBase should not be changed, only if you are sure what you do. + pluginWorkspaceBase = "/woodpecker" + // DefaultWorkspaceBase is set if not altered by the user. + DefaultWorkspaceBase = pluginWorkspaceBase ) func (c *Compiler) createProcess(container *yaml_types.Container, stepType backend_types.StepType) (*backend_types.Step, error) { @@ -37,12 +44,17 @@ func (c *Compiler) createProcess(container *yaml_types.Container, stepType backe detached bool workingDir string - workspace = fmt.Sprintf("%s_default:%s", c.prefix, c.base) privileged = container.Privileged networkMode = container.NetworkMode - // network = container.Network ) + workspaceBase := c.workspaceBase + if container.IsPlugin() { + // plugins have a predefined workspace base to not tamper with entrypoint executables + workspaceBase = pluginWorkspaceBase + } + workspaceVolume := fmt.Sprintf("%s_default:%s", c.prefix, workspaceBase) + networks := []backend_types.Conn{ { Name: fmt.Sprintf("%s_default", c.prefix), @@ -67,7 +79,7 @@ func (c *Compiler) createProcess(container *yaml_types.Container, stepType backe var volumes []string if !c.local { - volumes = append(volumes, workspace) + volumes = append(volumes, workspaceVolume) } volumes = append(volumes, c.volumes...) for _, volume := range container.Volumes.Volumes { @@ -78,15 +90,13 @@ func (c *Compiler) createProcess(container *yaml_types.Container, stepType backe environment := map[string]string{} maps.Copy(environment, c.env) - environment["CI_WORKSPACE"] = path.Join(c.base, c.path) + environment["CI_WORKSPACE"] = path.Join(workspaceBase, c.workspacePath) if stepType == backend_types.StepTypeService || container.Detached { detached = true } - if !detached || len(container.Commands) != 0 { - workingDir = c.stepWorkingDir(container) - } + workingDir = c.stepWorkingDir(container) getSecretValue := func(name string) (string, error) { name = strings.ToLower(name) @@ -104,29 +114,15 @@ func (c *Compiler) createProcess(container *yaml_types.Container, stepType backe return secret.Value, nil } - // TODO: why don't we pass secrets to detached steps? - if !detached { - if err := settings.ParamsToEnv(container.Settings, environment, "PLUGIN_", true, getSecretValue); err != nil { - return nil, err - } + if err := settings.ParamsToEnv(container.Settings, environment, "PLUGIN_", true, getSecretValue); err != nil { + return nil, err } if err := settings.ParamsToEnv(container.Environment, environment, "", false, getSecretValue); err != nil { return nil, err } - for _, requested := range container.Secrets.Secrets { - secretValue, err := getSecretValue(requested.Source) - if err != nil { - return nil, err - } - - environment[requested.Target] = secretValue - // TODO: deprecated, remove in 3.x - environment[strings.ToUpper(requested.Target)] = secretValue - } - - if utils.MatchImage(container.Image, c.escalated...) && container.IsPlugin() { + if utils.MatchImageDynamic(container.Image, c.escalated...) && container.IsPlugin() { privileged = true } @@ -139,31 +135,6 @@ func (c *Compiler) createProcess(container *yaml_types.Container, stepType backe } } - memSwapLimit := int64(container.MemSwapLimit) - if c.reslimit.MemSwapLimit != 0 { - memSwapLimit = c.reslimit.MemSwapLimit - } - memLimit := int64(container.MemLimit) - if c.reslimit.MemLimit != 0 { - memLimit = c.reslimit.MemLimit - } - shmSize := int64(container.ShmSize) - if c.reslimit.ShmSize != 0 { - shmSize = c.reslimit.ShmSize - } - cpuQuota := int64(container.CPUQuota) - if c.reslimit.CPUQuota != 0 { - cpuQuota = c.reslimit.CPUQuota - } - cpuShares := int64(container.CPUShares) - if c.reslimit.CPUShares != 0 { - cpuShares = c.reslimit.CPUShares - } - cpuSet := container.CPUSet - if c.reslimit.CPUSet != "" { - cpuSet = c.reslimit.CPUSet - } - var ports []backend_types.Port for _, portDef := range container.Ports { port, err := convertPort(portDef) @@ -192,6 +163,7 @@ func (c *Compiler) createProcess(container *yaml_types.Container, stepType backe Detached: detached, Privileged: privileged, WorkingDir: workingDir, + WorkspaceBase: workspaceBase, Environment: environment, Commands: container.Commands, Entrypoint: container.Entrypoint, @@ -202,12 +174,6 @@ func (c *Compiler) createProcess(container *yaml_types.Container, stepType backe Networks: networks, DNS: container.DNS, DNSSearch: container.DNSSearch, - MemSwapLimit: memSwapLimit, - MemLimit: memLimit, - ShmSize: shmSize, - CPUQuota: cpuQuota, - CPUShares: cpuShares, - CPUSet: cpuSet, AuthConfig: authConfig, OnSuccess: onSuccess, OnFailure: onFailure, @@ -222,7 +188,11 @@ func (c *Compiler) stepWorkingDir(container *yaml_types.Container) string { if path.IsAbs(container.Directory) { return container.Directory } - return path.Join(c.base, c.path, container.Directory) + base := c.workspaceBase + if container.IsPlugin() { + base = pluginWorkspaceBase + } + return path.Join(base, c.workspacePath, container.Directory) } func convertPort(portDef string) (backend_types.Port, error) { diff --git a/pipeline/frontend/yaml/compiler/convert_test.go b/pipeline/frontend/yaml/compiler/convert_test.go index 39f01e20b..051cb6a87 100644 --- a/pipeline/frontend/yaml/compiler/convert_test.go +++ b/pipeline/frontend/yaml/compiler/convert_test.go @@ -19,7 +19,7 @@ import ( "github.com/stretchr/testify/assert" - backend_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" + backend_types "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" ) func TestConvertPortNumber(t *testing.T) { diff --git a/pipeline/frontend/yaml/compiler/dag.go b/pipeline/frontend/yaml/compiler/dag.go index 461807259..fc3e10efc 100644 --- a/pipeline/frontend/yaml/compiler/dag.go +++ b/pipeline/frontend/yaml/compiler/dag.go @@ -17,14 +17,13 @@ package compiler import ( "sort" - backend_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" + backend_types "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" ) type dagCompilerStep struct { step *backend_types.Step position int name string - group string dependsOn []string } @@ -51,25 +50,16 @@ func (c dagCompiler) compile() ([]*backend_types.Stage, error) { if c.isDAG() { return c.compileByDependsOn() } - return c.compileByGroup() + return c.compileSequence() } -func (c dagCompiler) compileByGroup() ([]*backend_types.Stage, error) { +func (c dagCompiler) compileSequence() ([]*backend_types.Stage, error) { stages := make([]*backend_types.Stage, 0, len(c.steps)) - var currentStage *backend_types.Stage - var currentGroup string for _, s := range c.steps { - // create a new stage if current step is in a new group compared to last one - if currentStage == nil || currentGroup != s.group || s.group == "" { - currentGroup = s.group - - currentStage = new(backend_types.Stage) - stages = append(stages, currentStage) - } - - // add step to current stage - currentStage.Steps = append(currentStage.Steps, s.step) + stages = append(stages, &backend_types.Stage{ + Steps: []*backend_types.Step{s.step}, + }) } return stages, nil diff --git a/pipeline/frontend/yaml/compiler/dag_test.go b/pipeline/frontend/yaml/compiler/dag_test.go index 51dd3b5a6..d0360dab1 100644 --- a/pipeline/frontend/yaml/compiler/dag_test.go +++ b/pipeline/frontend/yaml/compiler/dag_test.go @@ -19,7 +19,7 @@ import ( "github.com/stretchr/testify/assert" - backend_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" + backend_types "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" ) func TestConvertDAGToStages(t *testing.T) { @@ -85,7 +85,6 @@ func TestConvertDAGToStages(t *testing.T) { "echo env": { position: 0, name: "echo env", - group: "", step: &backend_types.Step{ UUID: "01HJDPEW6R7J0JBE3F1T7Q0TYX", Type: "commands", @@ -96,7 +95,6 @@ func TestConvertDAGToStages(t *testing.T) { "echo 1": { position: 1, name: "echo 1", - group: "", dependsOn: []string{"echo env", "echo 2"}, step: &backend_types.Step{ UUID: "01HJDPF770QGRZER8RF79XVS4M", @@ -108,7 +106,6 @@ func TestConvertDAGToStages(t *testing.T) { "echo 2": { position: 2, name: "echo 2", - group: "", step: &backend_types.Step{ UUID: "01HJDPFF5RMEYZW0YTGR1Y1ZR0", Type: "commands", diff --git a/pipeline/frontend/yaml/compiler/option.go b/pipeline/frontend/yaml/compiler/option.go index d222ffaa9..20b419364 100644 --- a/pipeline/frontend/yaml/compiler/option.go +++ b/pipeline/frontend/yaml/compiler/option.go @@ -19,7 +19,7 @@ import ( "path" "strings" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata" ) // Option configures a compiler option. @@ -97,8 +97,8 @@ func WithNetrc(username, password, machine string) Option { // plugin steps in the pipeline. func WithWorkspace(base, path string) Option { return func(compiler *Compiler) { - compiler.base = base - compiler.path = path + compiler.workspaceBase = base + compiler.workspacePath = path } } @@ -157,38 +157,22 @@ func WithNetworks(networks ...string) Option { } } -// WithResourceLimit configures the compiler with default resource limits that -// are applied each container in the pipeline. -func WithResourceLimit(swap, mem, shmSize, cpuQuota, cpuShares int64, cpuSet string) Option { +func WithDefaultClonePlugin(cloneImage string) Option { return func(compiler *Compiler) { - compiler.reslimit = ResourceLimit{ - MemSwapLimit: swap, - MemLimit: mem, - ShmSize: shmSize, - CPUQuota: cpuQuota, - CPUShares: cpuShares, - CPUSet: cpuSet, - } + compiler.defaultClonePlugin = cloneImage } } -func WithDefaultCloneImage(cloneImage string) Option { +func WithTrustedClonePlugins(images []string) Option { return func(compiler *Compiler) { - compiler.defaultCloneImage = cloneImage + compiler.trustedClonePlugins = images } } -// WithTrusted configures the compiler with the trusted repo option. -func WithTrusted(trusted bool) Option { +// WithTrustedSecurity configures the compiler with the trusted repo option. +func WithTrustedSecurity(trusted bool) Option { return func(compiler *Compiler) { - compiler.trustedPipeline = trusted - } -} - -// WithNetrcOnlyTrusted configures the compiler with the netrcOnlyTrusted repo option. -func WithNetrcOnlyTrusted(only bool) Option { - return func(compiler *Compiler) { - compiler.netrcOnlyTrusted = only + compiler.securityTrustedPipeline = trusted } } diff --git a/pipeline/frontend/yaml/compiler/option_test.go b/pipeline/frontend/yaml/compiler/option_test.go index 4f8e08e5d..061d63e26 100644 --- a/pipeline/frontend/yaml/compiler/option_test.go +++ b/pipeline/frontend/yaml/compiler/option_test.go @@ -19,7 +19,8 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata" + "go.woodpecker-ci.org/woodpecker/v3/shared/constant" ) func TestWithWorkspace(t *testing.T) { @@ -29,8 +30,8 @@ func TestWithWorkspace(t *testing.T) { "src/github.com/octocat/hello-world", ), ) - assert.Equal(t, "/pipeline", compiler.base) - assert.Equal(t, "src/github.com/octocat/hello-world", compiler.path) + assert.Equal(t, "/pipeline", compiler.workspaceBase) + assert.Equal(t, "src/github.com/octocat/hello-world", compiler.workspacePath) } func TestWithEscalated(t *testing.T) { @@ -66,25 +67,6 @@ func TestWithNetworks(t *testing.T) { assert.Equal(t, "overlay_bar", compiler.networks[1]) } -func TestWithResourceLimit(t *testing.T) { - compiler := New( - WithResourceLimit( - 1, - 2, - 3, - 4, - 5, - "0,2-5", - ), - ) - assert.EqualValues(t, 1, compiler.reslimit.MemSwapLimit) - assert.EqualValues(t, 2, compiler.reslimit.MemLimit) - assert.EqualValues(t, 3, compiler.reslimit.ShmSize) - assert.EqualValues(t, 4, compiler.reslimit.CPUQuota) - assert.EqualValues(t, 5, compiler.reslimit.CPUShares) - assert.Equal(t, "0,2-5", compiler.reslimit.CPUSet) -} - func TestWithPrefix(t *testing.T) { assert.Equal(t, "someprefix_", New(WithPrefix("someprefix_")).prefix) } @@ -166,9 +148,17 @@ func TestWithEnviron(t *testing.T) { assert.Equal(t, "true", compiler.env["SHOW"]) } -func TestWithDefaultCloneImage(t *testing.T) { +func TestDefaultClonePlugin(t *testing.T) { compiler := New( - WithDefaultCloneImage("not-an-image"), + WithDefaultClonePlugin("not-an-image"), ) - assert.Equal(t, "not-an-image", compiler.defaultCloneImage) + assert.Equal(t, "not-an-image", compiler.defaultClonePlugin) +} + +func TestWithTrustedClonePlugins(t *testing.T) { + compiler := New(WithTrustedClonePlugins([]string{"not-an-image"})) + assert.ElementsMatch(t, []string{"not-an-image"}, compiler.trustedClonePlugins) + + compiler = New() + assert.ElementsMatch(t, constant.TrustedClonePlugins, compiler.trustedClonePlugins) } diff --git a/pipeline/frontend/yaml/compiler/settings/params.go b/pipeline/frontend/yaml/compiler/settings/params.go index 08cdf26e9..f078643e6 100644 --- a/pipeline/frontend/yaml/compiler/settings/params.go +++ b/pipeline/frontend/yaml/compiler/settings/params.go @@ -44,8 +44,9 @@ func ParamsToEnv(from map[string]any, to map[string]string, prefix string, upper // sanitizeParamKey formats the environment variable key. func sanitizeParamKey(prefix string, upper bool, k string) string { - r := strings.ReplaceAll(strings.ReplaceAll(k, ".", "_"), "-", "_") + r := k if upper { + r = strings.ReplaceAll(strings.ReplaceAll(k, ".", "_"), "-", "_") r = strings.ToUpper(r) } return prefix + r diff --git a/pipeline/frontend/yaml/compiler/settings/params_test.go b/pipeline/frontend/yaml/compiler/settings/params_test.go index 5e3e6da12..386b58d02 100644 --- a/pipeline/frontend/yaml/compiler/settings/params_test.go +++ b/pipeline/frontend/yaml/compiler/settings/params_test.go @@ -110,8 +110,9 @@ func TestSanitizeParamKey(t *testing.T) { assert.EqualValues(t, "PLUGIN_DRY_RUN", sanitizeParamKey("PLUGIN_", true, "dry-run")) assert.EqualValues(t, "PLUGIN_DRY_RUN", sanitizeParamKey("PLUGIN_", true, "dry_Run")) assert.EqualValues(t, "PLUGIN_DRY_RUN", sanitizeParamKey("PLUGIN_", true, "dry.run")) - assert.EqualValues(t, "PLUGIN_dry_run", sanitizeParamKey("PLUGIN_", false, "dry-run")) + assert.EqualValues(t, "PLUGIN_dry-run", sanitizeParamKey("PLUGIN_", false, "dry-run")) assert.EqualValues(t, "PLUGIN_dry_Run", sanitizeParamKey("PLUGIN_", false, "dry_Run")) + assert.EqualValues(t, "PLUGIN_dry.run", sanitizeParamKey("PLUGIN_", false, "dry.run")) } func TestYAMLToParamsToEnv(t *testing.T) { diff --git a/pipeline/frontend/yaml/constraint/constraint.go b/pipeline/frontend/yaml/constraint/constraint.go index 205f21fd3..aade4bef2 100644 --- a/pipeline/frontend/yaml/constraint/constraint.go +++ b/pipeline/frontend/yaml/constraint/constraint.go @@ -18,6 +18,7 @@ import ( "fmt" "maps" "path" + "slices" "strings" "github.com/bmatcuk/doublestar/v4" @@ -25,8 +26,8 @@ import ( "go.uber.org/multierr" "gopkg.in/yaml.v3" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata" - yamlBaseTypes "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/types/base" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata" + yamlBaseTypes "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/types/base" ) type ( @@ -37,20 +38,18 @@ type ( } Constraint struct { - Ref List - Repo List - Instance List - Platform List - Environment List - Branch List - Cron List - Status List - Matrix Map - Local yamlBaseTypes.BoolTrue - Path Path - Evaluate string `yaml:"evaluate,omitempty"` - // TODO: change to StringOrSlice in 3.x - Event List + Ref List + Repo List + Instance List + Platform List + Branch List + Cron List + Status List + Matrix Map + Local yamlBaseTypes.BoolTrue + Path Path + Evaluate string `yaml:"evaluate,omitempty"` + Event yamlBaseTypes.StringOrSlice } // List defines a runtime constraint for exclude & include string slices. @@ -164,8 +163,7 @@ func (c *Constraint) Match(m metadata.Metadata, global bool, env map[string]stri } match = match && c.Platform.Match(m.Sys.Platform) && - c.Environment.Match(m.Curr.DeployTo) && - c.Event.Match(m.Curr.Event) && + (len(c.Event) == 0 || slices.Contains(c.Event, m.Curr.Event)) && c.Repo.Match(path.Join(m.Repo.Owner, m.Repo.Name)) && c.Ref.Match(m.Curr.Commit.Ref) && c.Instance.Match(m.Sys.Host) diff --git a/pipeline/frontend/yaml/constraint/constraint_test.go b/pipeline/frontend/yaml/constraint/constraint_test.go index 60936d8c7..a6c68dffa 100644 --- a/pipeline/frontend/yaml/constraint/constraint_test.go +++ b/pipeline/frontend/yaml/constraint/constraint_test.go @@ -20,7 +20,7 @@ import ( "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata" ) func TestConstraint(t *testing.T) { diff --git a/pipeline/frontend/yaml/linter/error.go b/pipeline/frontend/yaml/linter/error.go index 09e243016..96c96ddc0 100644 --- a/pipeline/frontend/yaml/linter/error.go +++ b/pipeline/frontend/yaml/linter/error.go @@ -15,8 +15,8 @@ package linter import ( - "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors" - errorTypes "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/errors" + errorTypes "go.woodpecker-ci.org/woodpecker/v3/pipeline/errors/types" ) func newLinterError(message, file, field string, isWarning bool) *errorTypes.PipelineError { diff --git a/pipeline/frontend/yaml/linter/linter.go b/pipeline/frontend/yaml/linter/linter.go index 7120d2ad8..79cfa1168 100644 --- a/pipeline/frontend/yaml/linter/linter.go +++ b/pipeline/frontend/yaml/linter/linter.go @@ -20,15 +20,25 @@ import ( "codeberg.org/6543/xyaml" "go.uber.org/multierr" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors" - errorTypes "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors/types" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/linter/schema" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/errors" + errorTypes "go.woodpecker-ci.org/woodpecker/v3/pipeline/errors/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/linter/schema" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/utils" + "go.woodpecker-ci.org/woodpecker/v3/shared/constant" ) // A Linter lints a pipeline configuration. type Linter struct { - trusted bool + trusted TrustedConfiguration + privilegedPlugins *[]string + trustedClonePlugins *[]string +} + +type TrustedConfiguration struct { + Network bool + Volumes bool + Security bool } // New creates a new Linter with options. @@ -68,7 +78,11 @@ func (l *Linter) lintFile(config *WorkflowConfig) error { var linterErr error if len(config.Workflow.Steps.ContainerList) == 0 { - linterErr = multierr.Append(linterErr, newLinterError("Invalid or missing steps section", config.File, "steps", false)) + linterErr = multierr.Append(linterErr, newLinterError("Invalid or missing `steps` section", config.File, "steps", false)) + } + + if err := l.lintCloneSteps(config); err != nil { + linterErr = multierr.Append(linterErr, err) } if err := l.lintContainers(config, "clone"); err != nil { @@ -94,6 +108,29 @@ func (l *Linter) lintFile(config *WorkflowConfig) error { return linterErr } +func (l *Linter) lintCloneSteps(config *WorkflowConfig) error { + if len(config.Workflow.Clone.ContainerList) == 0 { + return nil + } + + trustedClonePlugins := constant.TrustedClonePlugins + if l.trustedClonePlugins != nil { + trustedClonePlugins = *l.trustedClonePlugins + } + + var linterErr error + for _, container := range config.Workflow.Clone.ContainerList { + if !utils.MatchImageDynamic(container.Image, trustedClonePlugins...) { + linterErr = multierr.Append(linterErr, + newLinterError( + "Specified clone image does not match allow list, netrc is not injected", + config.File, fmt.Sprintf("clone.%s", container.Name), true), + ) + } + } + return linterErr +} + func (l *Linter) lintContainers(config *WorkflowConfig, area string) error { var linterErr error @@ -112,12 +149,16 @@ func (l *Linter) lintContainers(config *WorkflowConfig, area string) error { if err := l.lintImage(config, container, area); err != nil { linterErr = multierr.Append(linterErr, err) } - if !l.trusted { - if err := l.lintTrusted(config, container, area); err != nil { - linterErr = multierr.Append(linterErr, err) - } + if err := l.lintTrusted(config, container, area); err != nil { + linterErr = multierr.Append(linterErr, err) } - if err := l.lintCommands(config, container, area); err != nil { + if err := l.lintSettings(config, container, area); err != nil { + linterErr = multierr.Append(linterErr, err) + } + if err := l.lintPrivilegedPlugins(config, container, area); err != nil { + linterErr = multierr.Append(linterErr, err) + } + if err := l.lintContainerDeprecations(config, container, area); err != nil { linterErr = multierr.Append(linterErr, err) } } @@ -132,52 +173,86 @@ func (l *Linter) lintImage(config *WorkflowConfig, c *types.Container, area stri return nil } -func (l *Linter) lintCommands(config *WorkflowConfig, c *types.Container, field string) error { - if len(c.Commands) == 0 { +func (l *Linter) lintPrivilegedPlugins(config *WorkflowConfig, c *types.Container, area string) error { + // lint for conflicts of https://github.com/woodpecker-ci/woodpecker/pull/3918 + if utils.MatchImage(c.Image, "plugins/docker", "plugins/gcr", "plugins/ecr", "woodpeckerci/plugin-docker-buildx") { + msg := fmt.Sprintf("The formerly privileged plugin `%s` is no longer privileged by default, if required, add it to `WOODPECKER_PLUGINS_PRIVILEGED`", c.Image) + // check first if user did not add them back + if l.privilegedPlugins != nil && !utils.MatchImageDynamic(c.Image, *l.privilegedPlugins...) { + return newLinterError(msg, config.File, fmt.Sprintf("%s.%s", area, c.Name), false) + } else if l.privilegedPlugins == nil { + // if linter has no info of current privileged plugins, it's just a warning + return newLinterError(msg, config.File, fmt.Sprintf("%s.%s", area, c.Name), true) + } + } + + return nil +} + +func (l *Linter) lintSettings(config *WorkflowConfig, c *types.Container, field string) error { + if len(c.Settings) == 0 { return nil } - if len(c.Settings) != 0 { - var keys []string - for key := range c.Settings { - keys = append(keys, key) - } - return newLinterError(fmt.Sprintf("Cannot configure both commands and custom attributes %v", keys), config.File, fmt.Sprintf("%s.%s", field, c.Name), false) + if len(c.Commands) != 0 { + return newLinterError("Cannot configure both `commands` and `settings`", config.File, fmt.Sprintf("%s.%s", field, c.Name), false) + } + if len(c.Entrypoint) != 0 { + return newLinterError("Cannot configure both `entrypoint` and `settings`", config.File, fmt.Sprintf("%s.%s", field, c.Name), false) + } + if len(c.Environment) != 0 { + return newLinterError("Should not configure both `environment` and `settings`", config.File, fmt.Sprintf("%s.%s", field, c.Name), true) } return nil } +func (l *Linter) lintContainerDeprecations(config *WorkflowConfig, c *types.Container, field string) (err error) { + if len(c.Secrets) != 0 { + err = multierr.Append(err, &errorTypes.PipelineError{ + Type: errorTypes.PipelineErrorTypeDeprecation, + Message: "Usage of `secrets` is deprecated, use `environment` in combination with `from_secret`", + Data: errors.DeprecationErrorData{ + File: config.File, + Field: fmt.Sprintf("%s.%s.secrets", field, c.Name), + Docs: "https://woodpecker-ci.org/docs/usage/secrets#use-secrets-in-settings-and-environment", + }, + }) + } + + return err +} + func (l *Linter) lintTrusted(config *WorkflowConfig, c *types.Container, area string) error { yamlPath := fmt.Sprintf("%s.%s", area, c.Name) errors := []string{} - if c.Privileged { - errors = append(errors, "Insufficient privileges to use privileged mode") + if !l.trusted.Security { + if c.Privileged { + errors = append(errors, "Insufficient trust level to use `privileged` mode") + } } - if c.ShmSize != 0 { - errors = append(errors, "Insufficient privileges to override shm_size") + if !l.trusted.Network { + if len(c.DNS) != 0 { + errors = append(errors, "Insufficient trust level to use custom `dns`") + } + if len(c.DNSSearch) != 0 { + errors = append(errors, "Insufficient trust level to use `dns_search`") + } + if len(c.ExtraHosts) != 0 { + errors = append(errors, "Insufficient trust level to use `extra_hosts`") + } + if len(c.NetworkMode) != 0 { + errors = append(errors, "Insufficient trust level to use `network_mode`") + } } - if len(c.DNS) != 0 { - errors = append(errors, "Insufficient privileges to use custom dns") - } - if len(c.DNSSearch) != 0 { - errors = append(errors, "Insufficient privileges to use dns_search") - } - if len(c.Devices) != 0 { - errors = append(errors, "Insufficient privileges to use devices") - } - if len(c.ExtraHosts) != 0 { - errors = append(errors, "Insufficient privileges to use extra_hosts") - } - if len(c.NetworkMode) != 0 { - errors = append(errors, "Insufficient privileges to use network_mode") - } - if c.Networks.Networks != nil && len(c.Networks.Networks) != 0 { - errors = append(errors, "Insufficient privileges to use networks") - } - if c.Volumes.Volumes != nil && len(c.Volumes.Volumes) != 0 { - errors = append(errors, "Insufficient privileges to use volumes") - } - if len(c.Tmpfs) != 0 { - errors = append(errors, "Insufficient privileges to use tmpfs") + if !l.trusted.Volumes { + if len(c.Devices) != 0 { + errors = append(errors, "Insufficient trust level to use `devices`") + } + if len(c.Volumes.Volumes) != 0 { + errors = append(errors, "Insufficient trust level to use `volumes`") + } + if len(c.Tmpfs) != 0 { + errors = append(errors, "Insufficient trust level to use `tmpfs`") + } } if len(errors) > 0 { @@ -216,142 +291,7 @@ func (l *Linter) lintDeprecations(config *WorkflowConfig) (err error) { return err } - if parsed.PipelineDoNotUseIt.ContainerList != nil { - err = multierr.Append(err, &errorTypes.PipelineError{ - Type: errorTypes.PipelineErrorTypeDeprecation, - Message: "Please use 'steps:' instead of deprecated 'pipeline:' list", - Data: errors.DeprecationErrorData{ - File: config.File, - Field: "pipeline", - Docs: "https://woodpecker-ci.org/docs/next/migrations#next-200", - }, - IsWarning: true, - }) - } - - if parsed.PlatformDoNotUseIt != "" { - err = multierr.Append(err, &errorTypes.PipelineError{ - Type: errorTypes.PipelineErrorTypeDeprecation, - Message: "Please use labels instead of deprecated 'platform' filters", - Data: errors.DeprecationErrorData{ - File: config.File, - Field: "platform", - Docs: "https://woodpecker-ci.org/docs/next/migrations#next-200", - }, - IsWarning: true, - }) - } - - if parsed.BranchesDoNotUseIt != nil { - err = multierr.Append(err, &errorTypes.PipelineError{ - Type: errorTypes.PipelineErrorTypeDeprecation, - Message: "Please use global when instead of deprecated 'branches' filter", - Data: errors.DeprecationErrorData{ - File: config.File, - Field: "branches", - Docs: "https://woodpecker-ci.org/docs/next/migrations#next-200", - }, - IsWarning: true, - }) - } - - for _, step := range parsed.Steps.ContainerList { - if step.Group != "" { - err = multierr.Append(err, &errorTypes.PipelineError{ - Type: errorTypes.PipelineErrorTypeDeprecation, - Message: "Please use depends_on instead of deprecated 'group' setting", - Data: errors.DeprecationErrorData{ - File: config.File, - Field: "steps." + step.Name + ".group", - Docs: "https://woodpecker-ci.org/docs/next/usage/workflow-syntax#depends_on", - }, - IsWarning: true, - }) - } - } - - for i, c := range parsed.When.Constraints { - if len(c.Event.Exclude) != 0 { - err = multierr.Append(err, &errorTypes.PipelineError{ - Type: errorTypes.PipelineErrorTypeDeprecation, - Message: "Please only use allow lists for events", - Data: errors.DeprecationErrorData{ - File: config.File, - Field: fmt.Sprintf("when[%d].event", i), - Docs: "https://woodpecker-ci.org/docs/usage/workflow-syntax#event-1", - }, - IsWarning: true, - }) - } - } - - for _, step := range parsed.Steps.ContainerList { - for i, c := range step.When.Constraints { - if len(c.Event.Exclude) != 0 { - err = multierr.Append(err, &errorTypes.PipelineError{ - Type: errorTypes.PipelineErrorTypeDeprecation, - Message: "Please only use allow lists for events", - Data: errors.DeprecationErrorData{ - File: config.File, - Field: fmt.Sprintf("steps.%s.when[%d].event", step.Name, i), - Docs: "https://woodpecker-ci.org/docs/usage/workflow-syntax#event", - }, - IsWarning: true, - }) - } - } - } - - for _, step := range parsed.Steps.ContainerList { - for i, c := range step.Secrets.Secrets { - if c.Source != c.Target { - err = multierr.Append(err, &errorTypes.PipelineError{ - Type: errorTypes.PipelineErrorTypeDeprecation, - Message: "Secrets alternative names are deprecated, use environment with from_secret", - Data: errors.DeprecationErrorData{ - File: config.File, - Field: fmt.Sprintf("steps.%s.secrets[%d]", step.Name, i), - Docs: "https://woodpecker-ci.org/docs/usage/secrets#use-secrets-in-settings-and-environment", - }, - IsWarning: true, - }) - } - } - } - - for i, c := range parsed.When.Constraints { - if !c.Environment.IsEmpty() { - err = multierr.Append(err, &errorTypes.PipelineError{ - Type: errorTypes.PipelineErrorTypeDeprecation, - Message: "environment filters are deprecated, use evaluate with CI_PIPELINE_DEPLOY_TARGET", - Data: errors.DeprecationErrorData{ - File: config.File, - Field: fmt.Sprintf("when[%d].environment", i), - Docs: "https://woodpecker-ci.org/docs/usage/workflow-syntax#evaluate", - }, - IsWarning: true, - }) - } - } - - for _, step := range parsed.Steps.ContainerList { - for i, c := range step.When.Constraints { - if !c.Environment.IsEmpty() { - err = multierr.Append(err, &errorTypes.PipelineError{ - Type: errorTypes.PipelineErrorTypeDeprecation, - Message: "environment filters are deprecated, use evaluate with CI_PIPELINE_DEPLOY_TARGET", - Data: errors.DeprecationErrorData{ - File: config.File, - Field: fmt.Sprintf("steps.%s.when[%d].environment", step.Name, i), - Docs: "https://woodpecker-ci.org/docs/usage/workflow-syntax#evaluate", - }, - IsWarning: true, - }) - } - } - } - - return err + return nil } func (l *Linter) lintBadHabits(config *WorkflowConfig) (err error) { @@ -363,7 +303,7 @@ func (l *Linter) lintBadHabits(config *WorkflowConfig) (err error) { rootEventFilters := len(parsed.When.Constraints) > 0 for _, c := range parsed.When.Constraints { - if len(c.Event.Include) == 0 { + if len(c.Event) == 0 { rootEventFilters = false break } @@ -377,7 +317,7 @@ func (l *Linter) lintBadHabits(config *WorkflowConfig) (err error) { } else { stepEventIndex := -1 for i, c := range step.When.Constraints { - if len(c.Event.Include) == 0 { + if len(c.Event) == 0 { stepEventIndex = i break } @@ -389,7 +329,7 @@ func (l *Linter) lintBadHabits(config *WorkflowConfig) (err error) { if field != "" { err = multierr.Append(err, &errorTypes.PipelineError{ Type: errorTypes.PipelineErrorTypeBadHabit, - Message: "Please set an event filter for all steps or the whole workflow on all items of the when block", + Message: "Set an event filter for all steps or the entire workflow on all items of the `when` block", Data: errors.BadHabitErrorData{ File: config.File, Field: field, diff --git a/pipeline/frontend/yaml/linter/linter_test.go b/pipeline/frontend/yaml/linter/linter_test.go index 84afb44d8..9f78fc9c6 100644 --- a/pipeline/frontend/yaml/linter/linter_test.go +++ b/pipeline/frontend/yaml/linter/linter_test.go @@ -19,9 +19,9 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/linter" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/errors" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/linter" ) func TestLint(t *testing.T) { @@ -39,7 +39,7 @@ steps: - go build - go test publish: - image: plugins/docker + image: woodpeckerci/plugin-kaniko settings: repo: foo/bar foo: bar @@ -61,7 +61,7 @@ steps: - go build - go test - name: publish - image: plugins/docker + image: woodpeckerci/plugin-kaniko settings: repo: foo/bar foo: bar @@ -94,7 +94,11 @@ steps: conf, err := yaml.ParseString(testd.Data) assert.NoError(t, err) - assert.NoError(t, linter.New(linter.WithTrusted(true)).Lint([]*linter.WorkflowConfig{{ + assert.NoError(t, linter.New(linter.WithTrusted(linter.TrustedConfiguration{ + Network: true, + Volumes: true, + Security: true, + })).Lint([]*linter.WorkflowConfig{{ File: testd.Title, RawConfig: testd.Data, Workflow: conf, @@ -110,7 +114,7 @@ func TestLintErrors(t *testing.T) { }{ { from: "", - want: "Invalid or missing steps section", + want: "Invalid or missing `steps` section", }, { from: "steps: { build: { image: '' } }", @@ -118,44 +122,68 @@ func TestLintErrors(t *testing.T) { }, { from: "steps: { build: { image: golang, privileged: true } }", - want: "Insufficient privileges to use privileged mode", - }, - { - from: "steps: { build: { image: golang, shm_size: 10gb } }", - want: "Insufficient privileges to override shm_size", + want: "Insufficient trust level to use `privileged` mode", }, { from: "steps: { build: { image: golang, dns: [ 8.8.8.8 ] } }", - want: "Insufficient privileges to use custom dns", + want: "Insufficient trust level to use custom `dns`", }, { from: "steps: { build: { image: golang, dns_search: [ example.com ] } }", - want: "Insufficient privileges to use dns_search", + want: "Insufficient trust level to use `dns_search`", }, { from: "steps: { build: { image: golang, devices: [ '/dev/tty0:/dev/tty0' ] } }", - want: "Insufficient privileges to use devices", + want: "Insufficient trust level to use `devices`", }, { from: "steps: { build: { image: golang, extra_hosts: [ 'somehost:162.242.195.82' ] } }", - want: "Insufficient privileges to use extra_hosts", + want: "Insufficient trust level to use `extra_hosts`", }, { from: "steps: { build: { image: golang, network_mode: host } }", - want: "Insufficient privileges to use network_mode", - }, - { - from: "steps: { build: { image: golang, networks: [ outside, default ] } }", - want: "Insufficient privileges to use networks", + want: "Insufficient trust level to use `network_mode`", }, { from: "steps: { build: { image: golang, volumes: [ '/opt/data:/var/lib/mysql' ] } }", - want: "Insufficient privileges to use volumes", + want: "Insufficient trust level to use `volumes`", }, { from: "steps: { build: { image: golang, network_mode: 'container:name' } }", - want: "Insufficient privileges to use network_mode", + want: "Insufficient trust level to use `network_mode`", + }, + { + from: "steps: { build: { image: golang, settings: { test: 'true' }, commands: [ 'echo ja', 'echo nein' ] } }", + want: "Cannot configure both `commands` and `settings`", + }, + { + from: "steps: { build: { image: golang, settings: { test: 'true' }, entrypoint: [ '/bin/fish' ] } }", + want: "Cannot configure both `entrypoint` and `settings`", + }, + { + from: "steps: { build: { image: golang, settings: { test: 'true' }, environment: { 'TEST': 'true' } } }", + want: "Should not configure both `environment` and `settings`", + }, + { + from: "{pipeline: { build: { image: golang, settings: { test: 'true' } } }, when: { branch: main, event: push } }", + want: "Additional property pipeline is not allowed", + }, + { + from: "{steps: { build: { image: plugins/docker, settings: { test: 'true' } } }, when: { branch: main, event: push } } }", + want: "The formerly privileged plugin `plugins/docker` is no longer privileged by default, if required, add it to `WOODPECKER_PLUGINS_PRIVILEGED`", + }, + { + from: "{steps: { build: { image: golang, settings: { test: 'true' } } }, when: { branch: main, event: push }, clone: { git: { image: some-other/plugin-git:v1.1.0 } } }", + want: "Specified clone image does not match allow list, netrc is not injected", + }, + { + from: "steps: { build: { image: golang, secrets: [ { source: mysql_username, target: mysql_username } ] } }", + want: "Usage of `secrets` is deprecated, use `environment` in combination with `from_secret`", + }, + { + from: "steps: { build: { image: golang, secrets: [ 'mysql_username' ] } }", + want: "Usage of `secrets` is deprecated, use `environment` in combination with `from_secret`", }, } @@ -189,11 +217,11 @@ func TestBadHabits(t *testing.T) { }{ { from: "steps: { build: { image: golang } }", - want: "Please set an event filter for all steps or the whole workflow on all items of the when block", + want: "Set an event filter for all steps or the entire workflow on all items of the `when` block", }, { from: "when: [{branch: xyz}, {event: push}]\nsteps: { build: { image: golang } }", - want: "Please set an event filter for all steps or the whole workflow on all items of the when block", + want: "Set an event filter for all steps or the entire workflow on all items of the `when` block", }, } diff --git a/pipeline/frontend/yaml/linter/option.go b/pipeline/frontend/yaml/linter/option.go index 0fe2af76f..5c297152a 100644 --- a/pipeline/frontend/yaml/linter/option.go +++ b/pipeline/frontend/yaml/linter/option.go @@ -18,8 +18,22 @@ package linter type Option func(*Linter) // WithTrusted adds the trusted option to the linter. -func WithTrusted(trusted bool) Option { +func WithTrusted(trusted TrustedConfiguration) Option { return func(linter *Linter) { linter.trusted = trusted } } + +// PrivilegedPlugins adds the list of privileged plugins. +func PrivilegedPlugins(plugins []string) Option { + return func(linter *Linter) { + linter.privilegedPlugins = &plugins + } +} + +// WithTrustedClonePlugins adds the list of trusted clone plugins. +func WithTrustedClonePlugins(plugins []string) Option { + return func(linter *Linter) { + linter.trustedClonePlugins = &plugins + } +} diff --git a/pipeline/frontend/yaml/linter/schema/.woodpecker/test-array-syntax.yaml b/pipeline/frontend/yaml/linter/schema/.woodpecker/test-array-syntax.yaml index a21b77261..569e5529c 100644 --- a/pipeline/frontend/yaml/linter/schema/.woodpecker/test-array-syntax.yaml +++ b/pipeline/frontend/yaml/linter/schema/.woodpecker/test-array-syntax.yaml @@ -1,5 +1,3 @@ -version: 1 - clone: - name: git image: woodpeckerci/plugin-git diff --git a/pipeline/frontend/yaml/linter/schema/.woodpecker/test-branches-array.yaml b/pipeline/frontend/yaml/linter/schema/.woodpecker/test-branches-array.yaml deleted file mode 100644 index d530db055..000000000 --- a/pipeline/frontend/yaml/linter/schema/.woodpecker/test-branches-array.yaml +++ /dev/null @@ -1,7 +0,0 @@ -branches: [main, pages] - -steps: - build: - image: golang - commands: - - go test diff --git a/pipeline/frontend/yaml/linter/schema/.woodpecker/test-branches-exclude-include.yaml b/pipeline/frontend/yaml/linter/schema/.woodpecker/test-branches-exclude-include.yaml deleted file mode 100644 index 03b534d23..000000000 --- a/pipeline/frontend/yaml/linter/schema/.woodpecker/test-branches-exclude-include.yaml +++ /dev/null @@ -1,9 +0,0 @@ -branches: - include: main - exclude: [develop, feature/*] - -steps: - build: - image: golang - commands: - - go test diff --git a/pipeline/frontend/yaml/linter/schema/.woodpecker/test-branches.yaml b/pipeline/frontend/yaml/linter/schema/.woodpecker/test-branches.yaml deleted file mode 100644 index 75b72bbcf..000000000 --- a/pipeline/frontend/yaml/linter/schema/.woodpecker/test-branches.yaml +++ /dev/null @@ -1,7 +0,0 @@ -branches: main - -steps: - build: - image: golang - commands: - - go test diff --git a/pipeline/frontend/yaml/linter/schema/.woodpecker/test-broken-plugin.yaml b/pipeline/frontend/yaml/linter/schema/.woodpecker/test-broken-plugin.yaml new file mode 100644 index 000000000..886dee977 --- /dev/null +++ b/pipeline/frontend/yaml/linter/schema/.woodpecker/test-broken-plugin.yaml @@ -0,0 +1,8 @@ +steps: + publish: + image: plugins/docker + settings: + repo: foo/bar + tags: latest + environment: + CGO: 0 diff --git a/pipeline/frontend/yaml/linter/schema/.woodpecker/test-broken-plugin2.yaml b/pipeline/frontend/yaml/linter/schema/.woodpecker/test-broken-plugin2.yaml new file mode 100644 index 000000000..2dbba2e90 --- /dev/null +++ b/pipeline/frontend/yaml/linter/schema/.woodpecker/test-broken-plugin2.yaml @@ -0,0 +1,8 @@ +steps: + publish: + image: plugins/docker + settings: + repo: foo/bar + tags: latest + commands: + - env diff --git a/pipeline/frontend/yaml/linter/schema/.woodpecker/test-platform.yaml b/pipeline/frontend/yaml/linter/schema/.woodpecker/test-platform.yaml deleted file mode 100644 index 7b4abf027..000000000 --- a/pipeline/frontend/yaml/linter/schema/.woodpecker/test-platform.yaml +++ /dev/null @@ -1,7 +0,0 @@ -platform: linux/amd64 - -steps: - build: - image: golang:latest - commands: - - go test diff --git a/pipeline/frontend/yaml/linter/schema/.woodpecker/test-service.yaml b/pipeline/frontend/yaml/linter/schema/.woodpecker/test-service.yaml index 03564e9fc..16429df75 100644 --- a/pipeline/frontend/yaml/linter/schema/.woodpecker/test-service.yaml +++ b/pipeline/frontend/yaml/linter/schema/.woodpecker/test-service.yaml @@ -10,3 +10,4 @@ services: image: mysql cache: image: redis + directory: /tmp/ diff --git a/pipeline/frontend/yaml/linter/schema/.woodpecker/test-step.yaml b/pipeline/frontend/yaml/linter/schema/.woodpecker/test-step.yaml index fc63e8d6e..1aadfa601 100644 --- a/pipeline/frontend/yaml/linter/schema/.woodpecker/test-step.yaml +++ b/pipeline/frontend/yaml/linter/schema/.woodpecker/test-step.yaml @@ -18,7 +18,7 @@ steps: image: alpine entrypoint: ['some_entry', '--some-flag'] - singla-entrypoint: + single-entrypoint: image: alpine entrypoint: some_entry @@ -38,31 +38,6 @@ steps: commands: - go test - environment-array: - image: golang - environment: - - CGO=0 - - GOOS=linux - - GOARCH=amd64 - commands: - - go test - - secrets: - image: docker - commands: - - echo $DOCKER_USERNAME - - echo $DOCKER_PASSWORD - secrets: - - docker_username - - source: docker_prod_password - target: docker_password - - group: - group: test - image: golang - commands: - - go test - detached: image: redis detach: true diff --git a/pipeline/frontend/yaml/linter/schema/.woodpecker/test-when.yaml b/pipeline/frontend/yaml/linter/schema/.woodpecker/test-when.yaml index b08385a77..a8235b65e 100644 --- a/pipeline/frontend/yaml/linter/schema/.woodpecker/test-when.yaml +++ b/pipeline/frontend/yaml/linter/schema/.woodpecker/test-when.yaml @@ -19,6 +19,9 @@ steps: - echo "test" when: event: push + branch: + include: main + exclude: [develop, feature/*] when-event-array: image: alpine @@ -34,14 +37,6 @@ steps: - deployment - release - when-event-exclude-pull_request_closed: - image: alpine - commands: - - echo "test" - when: - event: - exclude: pull_request_closed - when-ref: image: alpine commands: @@ -75,7 +70,6 @@ steps: commands: - echo "test" when: - environment: production event: deployment when-matrix: diff --git a/pipeline/frontend/yaml/linter/schema/schema.json b/pipeline/frontend/yaml/linter/schema/schema.json index 79f5cb21c..6fcb37630 100644 --- a/pipeline/frontend/yaml/linter/schema/schema.json +++ b/pipeline/frontend/yaml/linter/schema/schema.json @@ -20,19 +20,12 @@ "skip_clone": { "type": "boolean" }, - "branches": { - "$ref": "#/definitions/branches" - }, "when": { - "$ref": "#/definitions/pipeline_when" + "$ref": "#/definitions/workflow_when" }, "steps": { "$ref": "#/definitions/step_list" }, - "pipeline": { - "$ref": "#/definitions/step_list", - "description": "deprecated, use steps" - }, "services": { "$ref": "#/definitions/services" }, @@ -42,9 +35,6 @@ "matrix": { "$ref": "#/definitions/matrix" }, - "platform": { - "$ref": "#/definitions/platform" - }, "labels": { "$ref": "#/definitions/labels" }, @@ -61,13 +51,23 @@ "items": { "type": "string" } - }, - "version": { - "type": "number", - "default": 1 } }, "definitions": { + "string_or_string_slice": { + "oneOf": [ + { + "type": "array", + "minLength": 1, + "items": { + "type": "string" + } + }, + { + "type": "string" + } + ] + }, "clone": { "description": "Configures the clone step. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#clone", "oneOf": [ @@ -128,55 +128,6 @@ "type": ["boolean", "string", "number", "array", "object"] } }, - "branches": { - "description": "deprecated, use when.branch", - "oneOf": [ - { - "type": "array", - "items": { - "type": "string" - }, - "minProperties": 1 - }, - { - "type": "string" - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "exclude": { - "oneOf": [ - { - "type": "array", - "items": { - "type": "string" - }, - "minLength": 1 - }, - { - "type": "string" - } - ] - }, - "include": { - "oneOf": [ - { - "type": "array", - "items": { - "type": "string" - }, - "minLength": 1 - }, - { - "type": "string" - } - ] - } - } - } - ] - }, "step_list": { "description": "The steps section defines a list of steps which will be executed serially, in the order in which they are defined. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#steps", "oneOf": [ @@ -196,22 +147,22 @@ } ] }, - "pipeline_when": { - "description": "Whole pipelines can be skipped based on conditions. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#when---global-workflow-conditions", + "workflow_when": { + "description": "Whole workflow can be skipped based on conditions. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#when---global-workflow-conditions", "oneOf": [ { "type": "array", "minLength": 1, "items": { - "$ref": "#/definitions/pipeline_when_condition" + "$ref": "#/definitions/workflow_when_condition" } }, { - "$ref": "#/definitions/pipeline_when_condition" + "$ref": "#/definitions/workflow_when_condition" } ] }, - "pipeline_when_condition": { + "workflow_when_condition": { "type": "object", "additionalProperties": false, "properties": { @@ -226,18 +177,7 @@ "event": { "description": "Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#event", "default": [], - "oneOf": [ - { - "type": "array", - "minLength": 1, - "items": { - "$ref": "#/definitions/event_enum" - } - }, - { - "$ref": "#/definitions/event_enum" - } - ] + "$ref": "#/definitions/event_constraint_list" }, "ref": { "description": "Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#ref", @@ -251,10 +191,6 @@ "description": "Execute a step only on a specific platform. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#platform", "$ref": "#/definitions/constraint_list" }, - "environment": { - "description": "Execute a step only for a specific environment. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#environment", - "$ref": "#/definitions/constraint_list" - }, "instance": { "description": "Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#instance", "$ref": "#/definitions/constraint_list" @@ -304,10 +240,43 @@ } }, "step": { + "description": "A step of your workflow executes either arbitrary commands or uses a plugin. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#steps", + "anyOf": [ + { + "$ref": "#/definitions/commands_step" + }, + { + "$ref": "#/definitions/plugin_step" + } + ] + }, + "commands_step": { "description": "Every step of your pipeline executes arbitrary commands inside a specified docker container. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#steps", "type": "object", "additionalProperties": false, "required": ["image"], + "allOf": [ + { + "if": { + "properties": { + "detach": { + "const": true + } + } + }, + "then": {}, + "else": { + "anyOf": [ + { + "required": ["commands"] + }, + { + "required": ["entrypoint"] + } + ] + } + } + ], "properties": { "name": { "description": "The name of the step. Can be used if using the array style steps list.", @@ -331,36 +300,15 @@ "directory": { "$ref": "#/definitions/step_directory" }, - "secrets": { - "$ref": "#/definitions/step_secrets" - }, - "settings": { - "$ref": "#/definitions/step_settings" - }, "when": { "$ref": "#/definitions/step_when" }, "volumes": { "$ref": "#/definitions/step_volumes" }, - "group": { - "description": "deprecated, use depends_on", - "type": "string" - }, "depends_on": { "description": "Execute a step after another step has finished.", - "oneOf": [ - { - "type": "array", - "minLength": 1, - "items": { - "type": "string" - } - }, - { - "type": "string" - } - ] + "$ref": "#/definitions/string_or_string_slice" }, "detach": { "description": "Detach a step to run in background until pipeline finishes. Read more: https://woodpecker-ci.org/docs/usage/services#detachment", @@ -377,18 +325,65 @@ }, "entrypoint": { "description": "Defines container entrypoint.", - "oneOf": [ - { - "type": "array", - "minLength": 1, - "items": { - "type": "string" - } - }, - { - "type": "string" - } - ] + "$ref": "#/definitions/string_or_string_slice" + }, + "dns": { + "description": "Change DNS server for step. Only allowed if 'Trusted Network' option is enabled in repo settings by an admin. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#dns", + "$ref": "#/definitions/string_or_string_slice" + }, + "dns_search": { + "description": "Change DNS lookup domain for step. Only allowed if 'Trusted Network' option is enabled in repo settings by an admin. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#dns", + "$ref": "#/definitions/string_or_string_slice" + } + } + }, + "plugin_step": { + "description": "Plugins let you execute predefined functions in a more secure context. Read more: https://woodpecker-ci.org/docs/usage/plugins/overview", + "type": "object", + "additionalProperties": false, + "required": ["image"], + "properties": { + "name": { + "description": "The name of the step. Can be used if using the array style steps list.", + "type": "string" + }, + "image": { + "$ref": "#/definitions/step_image" + }, + "privileged": { + "$ref": "#/definitions/step_privileged" + }, + "pull": { + "$ref": "#/definitions/step_pull" + }, + "directory": { + "$ref": "#/definitions/step_directory" + }, + "settings": { + "$ref": "#/definitions/step_settings" + }, + "when": { + "$ref": "#/definitions/step_when" + }, + "volumes": { + "$ref": "#/definitions/step_volumes" + }, + "depends_on": { + "description": "Execute a step after another step has finished.", + "$ref": "#/definitions/string_or_string_slice" + }, + "detach": { + "description": "Detach a step to run in background until pipeline finishes. Read more: https://woodpecker-ci.org/docs/usage/services#detachment", + "type": "boolean" + }, + "failure": { + "description": "How to handle the failure of this step. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#failure", + "type": "string", + "enum": ["fail", "ignore"], + "default": "fail" + }, + "backend_options": { + "$ref": "#/definitions/step_backend_options" } } }, @@ -452,10 +447,6 @@ "description": "Execute a step only on a specific platform. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#platform", "$ref": "#/definitions/constraint_list" }, - "environment": { - "description": "Execute a step only for a specific environment. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#environment", - "$ref": "#/definitions/constraint_list" - }, "matrix": { "description": "Read more: https://woodpecker-ci.org/docs/usage/matrix-workflows", "type": "object", @@ -525,40 +516,6 @@ "items": { "$ref": "#/definitions/event_enum" } - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "include": { - "oneOf": [ - { - "$ref": "#/definitions/event_enum" - }, - { - "type": "array", - "minLength": 1, - "items": { - "$ref": "#/definitions/event_enum" - } - } - ] - }, - "exclude": { - "oneOf": [ - { - "$ref": "#/definitions/event_enum" - }, - { - "type": "array", - "minLength": 1, - "items": { - "$ref": "#/definitions/event_enum" - } - } - ] - } - } } ] }, @@ -640,45 +597,10 @@ }, "step_environment": { "description": "Pass environment variables to a pipeline step. Read more: https://woodpecker-ci.org/docs/usage/environment", - "oneOf": [ - { - "type": "array", - "items": { - "type": "string" - }, - "minLength": 1 - }, - { - "type": "object", - "additionalProperties": { - "type": ["boolean", "string", "number", "array", "object"] - } - } - ] - }, - "step_secrets": { - "description": "Pass secrets to a pipeline step at runtime. Read more: https://woodpecker-ci.org/docs/usage/secrets", - "type": "array", - "items": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object", - "required": ["source", "target"], - "properties": { - "source": { - "type": "string" - }, - "target": { - "type": "string" - } - } - } - ] - }, - "minLength": 1 + "type": "object", + "additionalProperties": { + "type": ["boolean", "string", "number", "array", "object"] + } }, "step_settings": { "description": "Change the settings of your plugin. Read more: https://woodpecker-ci.org/docs/usage/plugins/overview", @@ -898,8 +820,8 @@ "environment": { "$ref": "#/definitions/step_environment" }, - "secrets": { - "$ref": "#/definitions/step_secrets" + "directory": { + "$ref": "#/definitions/step_directory" }, "settings": { "$ref": "#/definitions/step_settings" @@ -955,11 +877,6 @@ "minLength": 1 } }, - "platform": { - "description": "deprecated, use labels.platform", - "type": "string", - "additionalProperties": false - }, "labels": { "description": "Configures the labels used for the agent selection. Read more: https://woodpecker-ci.org/docs/usage/workflow-syntax#labels", "type": "object", diff --git a/pipeline/frontend/yaml/linter/schema/schema_test.go b/pipeline/frontend/yaml/linter/schema/schema_test.go index d423a9e3d..63ddcdb85 100644 --- a/pipeline/frontend/yaml/linter/schema/schema_test.go +++ b/pipeline/frontend/yaml/linter/schema/schema_test.go @@ -21,7 +21,7 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/linter/schema" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/linter/schema" ) func TestSchema(t *testing.T) { @@ -32,18 +32,6 @@ func TestSchema(t *testing.T) { testFile string fail bool }{ - { - name: "Branches", - testFile: ".woodpecker/test-branches.yaml", - }, - { - name: "Branches Array", - testFile: ".woodpecker/test-branches-array.yaml", - }, - { - name: "Branches exclude & include", - testFile: ".woodpecker/test-branches-exclude-include.yaml", - }, { name: "Clone", testFile: ".woodpecker/test-clone.yaml", @@ -84,10 +72,6 @@ func TestSchema(t *testing.T) { name: "Workspace", testFile: ".woodpecker/test-workspace.yaml", }, - { - name: "Platform", - testFile: ".woodpecker/test-platform.yaml", - }, { name: "Labels", testFile: ".woodpecker/test-labels.yaml", @@ -116,6 +100,16 @@ func TestSchema(t *testing.T) { testFile: ".woodpecker/test-custom-backend.yaml", fail: false, }, + { + name: "Broken Plugin by environment", + testFile: ".woodpecker/test-broken-plugin.yaml", + fail: true, + }, + { + name: "Broken Plugin by commands", + testFile: ".woodpecker/test-broken-plugin2.yaml", + fail: true, + }, } for _, tt := range testTable { diff --git a/pipeline/frontend/yaml/matrix/matrix.go b/pipeline/frontend/yaml/matrix/matrix.go index 55855513d..a48ad31d8 100644 --- a/pipeline/frontend/yaml/matrix/matrix.go +++ b/pipeline/frontend/yaml/matrix/matrix.go @@ -19,7 +19,7 @@ import ( "codeberg.org/6543/xyaml" - errorTypes "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors/types" + errorTypes "go.woodpecker-ci.org/woodpecker/v3/pipeline/errors/types" ) const ( diff --git a/pipeline/frontend/yaml/matrix/matrix_test.go b/pipeline/frontend/yaml/matrix/matrix_test.go index 9bf0b1417..bda186722 100644 --- a/pipeline/frontend/yaml/matrix/matrix_test.go +++ b/pipeline/frontend/yaml/matrix/matrix_test.go @@ -17,42 +17,34 @@ package matrix import ( "testing" - "github.com/franela/goblin" + "github.com/stretchr/testify/assert" ) func TestMatrix(t *testing.T) { - g := goblin.Goblin(t) - g.Describe("Calculate matrix", func() { - axis, _ := ParseString(fakeMatrix) + axis, _ := ParseString(fakeMatrix) + assert.Len(t, axis, 24) - g.It("Should calculate permutations", func() { - g.Assert(len(axis)).Equal(24) - }) + set := map[string]bool{} + for _, perm := range axis { + set[perm.String()] = true + } + assert.Len(t, set, 24) +} - g.It("Should not duplicate permutations", func() { - set := map[string]bool{} - for _, perm := range axis { - set[perm.String()] = true - } - g.Assert(len(set)).Equal(24) - }) +func TestMatrixEmpty(t *testing.T) { + axis, err := ParseString("") + assert.NoError(t, err) + assert.Empty(t, axis) +} - g.It("Should return empty array if no matrix", func() { - axis, err := ParseString("") - g.Assert(err).IsNil() - g.Assert(len(axis) == 0).IsTrue() - }) - - g.It("Should return included axis", func() { - axis, err := ParseString(fakeMatrixInclude) - g.Assert(err).IsNil() - g.Assert(len(axis)).Equal(2) - g.Assert(axis[0]["go_version"]).Equal("1.5") - g.Assert(axis[1]["go_version"]).Equal("1.6") - g.Assert(axis[0]["python_version"]).Equal("3.4") - g.Assert(axis[1]["python_version"]).Equal("3.4") - }) - }) +func TestMatrixIncluded(t *testing.T) { + axis, err := ParseString(fakeMatrixInclude) + assert.NoError(t, err) + assert.Len(t, axis, 2) + assert.Equal(t, "1.5", axis[0]["go_version"]) + assert.Equal(t, "1.6", axis[1]["go_version"]) + assert.Equal(t, "3.4", axis[0]["python_version"]) + assert.Equal(t, "3.4", axis[1]["python_version"]) } var fakeMatrix = ` diff --git a/pipeline/frontend/yaml/parse.go b/pipeline/frontend/yaml/parse.go index 8607dd537..07452b6d9 100644 --- a/pipeline/frontend/yaml/parse.go +++ b/pipeline/frontend/yaml/parse.go @@ -15,12 +15,9 @@ package yaml import ( - "fmt" - "codeberg.org/6543/xyaml" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/constraint" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/types" ) // ParseBytes parses the configuration from bytes b. @@ -30,37 +27,6 @@ func ParseBytes(b []byte) (*types.Workflow, error) { if err != nil { return nil, err } - - // support deprecated branch filter - if out.BranchesDoNotUseIt != nil { - switch { - case out.When.Constraints == nil: - out.When.Constraints = []constraint.Constraint{{Branch: *out.BranchesDoNotUseIt}} - case len(out.When.Constraints) == 1 && out.When.Constraints[0].Branch.IsEmpty(): - out.When.Constraints[0].Branch = *out.BranchesDoNotUseIt - default: - return nil, fmt.Errorf("could not apply deprecated branches filter into global when filter") - } - out.BranchesDoNotUseIt = nil - } - - // support deprecated pipeline keyword - if len(out.PipelineDoNotUseIt.ContainerList) != 0 && len(out.Steps.ContainerList) == 0 { - out.Steps.ContainerList = out.PipelineDoNotUseIt.ContainerList - } - - // support deprecated platform filter - if out.PlatformDoNotUseIt != "" { - if out.Labels == nil { - out.Labels = make(map[string]string) - } - if _, set := out.Labels["platform"]; !set { - out.Labels["platform"] = out.PlatformDoNotUseIt - } - out.PlatformDoNotUseIt = "" - } - out.PipelineDoNotUseIt.ContainerList = nil - return out, nil } diff --git a/pipeline/frontend/yaml/parse_test.go b/pipeline/frontend/yaml/parse_test.go index 5402cd03c..dc9eca5ef 100644 --- a/pipeline/frontend/yaml/parse_test.go +++ b/pipeline/frontend/yaml/parse_test.go @@ -17,132 +17,115 @@ package yaml import ( "testing" - "github.com/franela/goblin" "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata" - yaml_base_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/types/base" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata" + yaml_base_types "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/types/base" ) func TestParse(t *testing.T) { - g := goblin.Goblin(t) + t.Run("Should unmarshal a string", func(t *testing.T) { + out, err := ParseString(sampleYaml) + assert.NoError(t, err) - g.Describe("Parser", func() { - g.Describe("Given a yaml file", func() { - g.It("Should unmarshal a string", func() { - out, err := ParseString(sampleYaml) - if err != nil { - g.Fail(err) - } + assert.Contains(t, out.When.Constraints[0].Event, "tester") - g.Assert(out.When.Constraints[0].Event.Match("tester")).Equal(true) + assert.Equal(t, "/go", out.Workspace.Base) + assert.Equal(t, "src/github.com/octocat/hello-world", out.Workspace.Path) + assert.Equal(t, "database", out.Services.ContainerList[0].Name) + assert.Equal(t, "mysql", out.Services.ContainerList[0].Image) + assert.Equal(t, "test", out.Steps.ContainerList[0].Name) + assert.Equal(t, "golang", out.Steps.ContainerList[0].Image) + assert.Equal(t, yaml_base_types.StringOrSlice{"go install", "go test"}, out.Steps.ContainerList[0].Commands) + assert.Equal(t, "build", out.Steps.ContainerList[1].Name) + assert.Equal(t, "golang", out.Steps.ContainerList[1].Image) + assert.Equal(t, yaml_base_types.StringOrSlice{"go build"}, out.Steps.ContainerList[1].Commands) + assert.Equal(t, "notify", out.Steps.ContainerList[2].Name) + assert.Equal(t, "slack", out.Steps.ContainerList[2].Image) + assert.Equal(t, "frontend", out.Labels["com.example.team"]) + assert.Equal(t, "build", out.Labels["com.example.type"]) + assert.Equal(t, "lint", out.DependsOn[0]) + assert.Equal(t, "test", out.DependsOn[1]) + assert.Equal(t, ("success"), out.RunsOn[0]) + assert.Equal(t, ("failure"), out.RunsOn[1]) + assert.False(t, out.SkipClone) + }) - g.Assert(out.Workspace.Base).Equal("/go") - g.Assert(out.Workspace.Path).Equal("src/github.com/octocat/hello-world") - g.Assert(out.Volumes.WorkflowVolumes[0].Name).Equal("custom") - g.Assert(out.Volumes.WorkflowVolumes[0].Driver).Equal("blockbridge") - g.Assert(out.Networks.WorkflowNetworks[0].Name).Equal("custom") - g.Assert(out.Networks.WorkflowNetworks[0].Driver).Equal("overlay") - g.Assert(out.Services.ContainerList[0].Name).Equal("database") - g.Assert(out.Services.ContainerList[0].Image).Equal("mysql") - g.Assert(out.Steps.ContainerList[0].Name).Equal("test") - g.Assert(out.Steps.ContainerList[0].Image).Equal("golang") - g.Assert(out.Steps.ContainerList[0].Commands).Equal(yaml_base_types.StringOrSlice{"go install", "go test"}) - g.Assert(out.Steps.ContainerList[1].Name).Equal("build") - g.Assert(out.Steps.ContainerList[1].Image).Equal("golang") - g.Assert(out.Steps.ContainerList[1].Commands).Equal(yaml_base_types.StringOrSlice{"go build"}) - g.Assert(out.Steps.ContainerList[2].Name).Equal("notify") - g.Assert(out.Steps.ContainerList[2].Image).Equal("slack") - // g.Assert(out.Steps.ContainerList[2].NetworkMode).Equal("container:name") - g.Assert(out.Labels["com.example.team"]).Equal("frontend") - g.Assert(out.Labels["com.example.type"]).Equal("build") - g.Assert(out.DependsOn[0]).Equal("lint") - g.Assert(out.DependsOn[1]).Equal("test") - g.Assert(out.RunsOn[0]).Equal("success") - g.Assert(out.RunsOn[1]).Equal("failure") - g.Assert(out.SkipClone).Equal(false) - }) + t.Run("Should handle simple yaml anchors", func(t *testing.T) { + out, err := ParseString(simpleYamlAnchors) + assert.NoError(t, err) + assert.Equal(t, "notify_success", out.Steps.ContainerList[0].Name) + assert.Equal(t, "plugins/slack", out.Steps.ContainerList[0].Image) + }) - g.It("Should handle simple yaml anchors", func() { - out, err := ParseString(simpleYamlAnchors) - if err != nil { - g.Fail(err) - } - g.Assert(out.Steps.ContainerList[0].Name).Equal("notify_success") - g.Assert(out.Steps.ContainerList[0].Image).Equal("plugins/slack") - }) + t.Run("Should unmarshal variables", func(t *testing.T) { + out, err := ParseString(sampleVarYaml) + assert.NoError(t, err) + assert.Equal(t, "notify_fail", out.Steps.ContainerList[0].Name) + assert.Equal(t, "plugins/slack", out.Steps.ContainerList[0].Image) + assert.Equal(t, "notify_success", out.Steps.ContainerList[1].Name) + assert.Equal(t, "plugins/slack", out.Steps.ContainerList[1].Image) - g.It("Should unmarshal variables", func() { - out, err := ParseString(sampleVarYaml) - if err != nil { - g.Fail(err) - } - g.Assert(out.Steps.ContainerList[0].Name).Equal("notify_fail") - g.Assert(out.Steps.ContainerList[0].Image).Equal("plugins/slack") - g.Assert(out.Steps.ContainerList[1].Name).Equal("notify_success") - g.Assert(out.Steps.ContainerList[1].Image).Equal("plugins/slack") + assert.Empty(t, out.Steps.ContainerList[0].When.Constraints) + assert.Equal(t, "notify_success", out.Steps.ContainerList[1].Name) + assert.Equal(t, "plugins/slack", out.Steps.ContainerList[1].Image) + assert.Equal(t, yaml_base_types.StringOrSlice{"success"}, out.Steps.ContainerList[1].When.Constraints[0].Event) + }) +} - g.Assert(len(out.Steps.ContainerList[0].When.Constraints)).Equal(0) - g.Assert(out.Steps.ContainerList[1].Name).Equal("notify_success") - g.Assert(out.Steps.ContainerList[1].Image).Equal("plugins/slack") - g.Assert(out.Steps.ContainerList[1].When.Constraints[0].Event.Include).Equal([]string{"success"}) - }) +func TestMatch(t *testing.T) { + matchConfig, err := ParseString(sampleYaml) + assert.NoError(t, err) - matchConfig, err := ParseString(sampleYaml) - if err != nil { - g.Fail(err) - } + t.Run("Should match event tester", func(t *testing.T) { + match, err := matchConfig.When.Match(metadata.Metadata{ + Curr: metadata.Pipeline{ + Event: "tester", + }, + }, false, nil) + assert.True(t, match) + assert.NoError(t, err) + }) - g.It("Should match event tester", func() { - match, err := matchConfig.When.Match(metadata.Metadata{ - Curr: metadata.Pipeline{ - Event: "tester", - }, - }, false, nil) - g.Assert(match).Equal(true) - g.Assert(err).IsNil() - }) + t.Run("Should match event tester2", func(t *testing.T) { + match, err := matchConfig.When.Match(metadata.Metadata{ + Curr: metadata.Pipeline{ + Event: "tester2", + }, + }, false, nil) + assert.True(t, match) + assert.NoError(t, err) + }) - g.It("Should match event tester2", func() { - match, err := matchConfig.When.Match(metadata.Metadata{ - Curr: metadata.Pipeline{ - Event: "tester2", - }, - }, false, nil) - g.Assert(match).Equal(true) - g.Assert(err).IsNil() - }) + t.Run("Should match branch tester", func(t *testing.T) { + match, err := matchConfig.When.Match(metadata.Metadata{ + Curr: metadata.Pipeline{ + Commit: metadata.Commit{ + Branch: "tester", + }, + }, + }, true, nil) + assert.True(t, match) + assert.NoError(t, err) + }) - g.It("Should match branch tester", func() { - match, err := matchConfig.When.Match(metadata.Metadata{ - Curr: metadata.Pipeline{ - Commit: metadata.Commit{ - Branch: "tester", - }, - }, - }, true, nil) - g.Assert(match).Equal(true) - g.Assert(err).IsNil() - }) - - g.It("Should not match event push", func() { - match, err := matchConfig.When.Match(metadata.Metadata{ - Curr: metadata.Pipeline{ - Event: "push", - }, - }, false, nil) - g.Assert(match).Equal(false) - g.Assert(err).IsNil() - }) - }) + t.Run("Should not match event push", func(t *testing.T) { + match, err := matchConfig.When.Match(metadata.Metadata{ + Curr: metadata.Pipeline{ + Event: "push", + }, + }, false, nil) + assert.False(t, match) + assert.NoError(t, err) }) } func TestParseLegacy(t *testing.T) { - sampleYamlPipelineLegacy := ` -platform: linux/amd64 + sampleYamlPipeline := ` +labels: + platform: linux/amd64 -pipeline: +steps: say hello: image: bash commands: echo hello @@ -164,7 +147,7 @@ pipeline: commands: meh! ` - workflow1, err := ParseString(sampleYamlPipelineLegacy) + workflow1, err := ParseString(sampleYamlPipeline) if !assert.NoError(t, err) { return } @@ -214,12 +197,6 @@ steps: services: database: image: mysql -networks: - custom: - driver: overlay -volumes: - custom: - driver: blockbridge labels: com.example.type: "build" com.example.team: "frontend" @@ -260,27 +237,19 @@ steps: ` func TestSlice(t *testing.T) { - g := goblin.Goblin(t) + t.Run("should marshal a not set slice to nil", func(t *testing.T) { + out, err := ParseString(sampleSliceYaml) + assert.NoError(t, err) - g.Describe("Parser", func() { - g.It("should marshal a not set slice to nil", func() { - out, err := ParseString(sampleSliceYaml) - if err != nil { - g.Fail(err) - } + assert.Nil(t, out.Steps.ContainerList[0].DependsOn) + assert.Empty(t, out.Steps.ContainerList[0].DependsOn) + }) - g.Assert(out.Steps.ContainerList[0].DependsOn).IsNil() - g.Assert(len(out.Steps.ContainerList[0].DependsOn)).Equal(0) - }) + t.Run("should marshal an empty slice", func(t *testing.T) { + out, err := ParseString(sampleSliceYaml) + assert.NoError(t, err) - g.It("should marshal an empty slice", func() { - out, err := ParseString(sampleSliceYaml) - if err != nil { - g.Fail(err) - } - - g.Assert(out.Steps.ContainerList[1].DependsOn).IsNotNil() - g.Assert(len(out.Steps.ContainerList[1].DependsOn)).Equal(0) - }) + assert.NotNil(t, out.Steps.ContainerList[1].DependsOn) + assert.Empty(t, (out.Steps.ContainerList[1].DependsOn)) }) } diff --git a/pipeline/frontend/yaml/types/base/bool_test.go b/pipeline/frontend/yaml/types/base/bool_test.go index 9c574d1cb..8264f59f6 100644 --- a/pipeline/frontend/yaml/types/base/bool_test.go +++ b/pipeline/frontend/yaml/types/base/bool_test.go @@ -17,51 +17,39 @@ package base import ( "testing" - "github.com/franela/goblin" + "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" ) func TestBoolTrue(t *testing.T) { - g := goblin.Goblin(t) + t.Run("unmarshal true", func(t *testing.T) { + in := []byte("true") + out := BoolTrue{} + err := yaml.Unmarshal(in, &out) + assert.NoError(t, err) + assert.True(t, out.Bool()) + }) - g.Describe("Yaml bool type", func() { - g.Describe("given a yaml file", func() { - g.It("should unmarshal true", func() { - in := []byte("true") - out := BoolTrue{} - err := yaml.Unmarshal(in, &out) - if err != nil { - g.Fail(err) - } - g.Assert(out.Bool()).Equal(true) - }) + t.Run("unmarshal false", func(t *testing.T) { + in := []byte("false") + out := BoolTrue{} + err := yaml.Unmarshal(in, &out) + assert.NoError(t, err) + assert.False(t, out.Bool()) + }) - g.It("should unmarshal false", func() { - in := []byte("false") - out := BoolTrue{} - err := yaml.Unmarshal(in, &out) - if err != nil { - g.Fail(err) - } - g.Assert(out.Bool()).Equal(false) - }) + t.Run("unmarshal true when empty", func(t *testing.T) { + in := []byte("") + out := BoolTrue{} + err := yaml.Unmarshal(in, &out) + assert.NoError(t, err) + assert.True(t, out.Bool()) + }) - g.It("should unmarshal true when empty", func() { - in := []byte("") - out := BoolTrue{} - err := yaml.Unmarshal(in, &out) - if err != nil { - g.Fail(err) - } - g.Assert(out.Bool()).Equal(true) - }) - - g.It("should throw error when invalid", func() { - in := []byte("abc") // string value should fail parse - out := BoolTrue{} - err := yaml.Unmarshal(in, &out) - g.Assert(err).IsNotNil("expects error") - }) - }) + t.Run("throw error when invalid", func(t *testing.T) { + in := []byte("abc") // string value should fail parse + out := BoolTrue{} + err := yaml.Unmarshal(in, &out) + assert.Error(t, err) }) } diff --git a/pipeline/frontend/yaml/types/base/map.go b/pipeline/frontend/yaml/types/base/deprecations.go similarity index 54% rename from pipeline/frontend/yaml/types/base/map.go rename to pipeline/frontend/yaml/types/base/deprecations.go index 2cdd35b8b..8504ae39a 100644 --- a/pipeline/frontend/yaml/types/base/map.go +++ b/pipeline/frontend/yaml/types/base/deprecations.go @@ -1,4 +1,4 @@ -// Copyright 2023 Woodpecker Authors +// Copyright 2024 Woodpecker Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,40 +12,29 @@ // See the License for the specific language governing permissions and // limitations under the License. +// TODO: delete file after v3.0.0 release + package base import ( - "errors" "fmt" - "strings" ) -// SliceOrMap represents a map of strings, string slice are converted into a map. -type SliceOrMap map[string]any +type EnvironmentMap map[string]any // UnmarshalYAML implements the Unmarshaler interface. -func (s *SliceOrMap) UnmarshalYAML(unmarshal func(any) error) error { - var sliceType []any - if err := unmarshal(&sliceType); err == nil { - parts := map[string]any{} - for _, s := range sliceType { - if str, ok := s.(string); ok { - str := strings.TrimSpace(str) - key, val, _ := strings.Cut(str, "=") - parts[key] = val - } else { - return fmt.Errorf("cannot unmarshal '%v' of type %T into a string value", s, s) - } - } - *s = parts - return nil - } - +func (s *EnvironmentMap) UnmarshalYAML(unmarshal func(any) error) error { var mapType map[string]any - if err := unmarshal(&mapType); err == nil { + err := unmarshal(&mapType) + if err == nil { *s = mapType return nil } - return errors.New("failed to unmarshal SliceOrMap") + var sliceType []any + if err := unmarshal(&sliceType); err == nil { + return fmt.Errorf("list syntax for 'environment' has been removed, use map syntax instead (https://woodpecker-ci.org/docs/usage/environment)") + } + + return err } diff --git a/pipeline/frontend/yaml/types/base/map_test.go b/pipeline/frontend/yaml/types/base/deprecations_test.go similarity index 53% rename from pipeline/frontend/yaml/types/base/map_test.go rename to pipeline/frontend/yaml/types/base/deprecations_test.go index 750fd1714..cb87f084c 100644 --- a/pipeline/frontend/yaml/types/base/map_test.go +++ b/pipeline/frontend/yaml/types/base/deprecations_test.go @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// TODO: delete file after v3.0.0 release + package base import ( @@ -21,38 +23,29 @@ import ( "gopkg.in/yaml.v3" ) -type StructSliceOrMap struct { - Foos SliceOrMap `yaml:"foos,omitempty"` - Bars []string `yaml:"bars"` +type StructMap struct { + Foos EnvironmentMap `yaml:"foos,omitempty"` } -func TestSliceOrMapYaml(t *testing.T) { +func TestEnvironmentMapYaml(t *testing.T) { str := `{foos: [bar=baz, far=faz]}` + s := StructMap{} + err := yaml.Unmarshal([]byte(str), &s) + if assert.Error(t, err) { + assert.EqualValues(t, "list syntax for 'environment' has been removed, use map syntax instead (https://woodpecker-ci.org/docs/usage/environment)", err.Error()) + } - s := StructSliceOrMap{} - assert.NoError(t, yaml.Unmarshal([]byte(str), &s)) - - assert.Equal(t, SliceOrMap{"bar": "baz", "far": "faz"}, s.Foos) - + s.Foos = EnvironmentMap{"bar": "baz", "far": "faz"} d, err := yaml.Marshal(&s) assert.NoError(t, err) + str = `foos: + bar: baz + far: faz +` + assert.EqualValues(t, str, string(d)) - s2 := StructSliceOrMap{} + s2 := StructMap{} assert.NoError(t, yaml.Unmarshal(d, &s2)) - assert.Equal(t, SliceOrMap{"bar": "baz", "far": "faz"}, s2.Foos) -} - -func TestStr2SliceOrMapPtrMap(t *testing.T) { - s := map[string]*StructSliceOrMap{"udav": { - Foos: SliceOrMap{"io.rancher.os.bar": "baz", "io.rancher.os.far": "true"}, - Bars: []string{}, - }} - d, err := yaml.Marshal(&s) - assert.NoError(t, err) - - s2 := map[string]*StructSliceOrMap{} - assert.NoError(t, yaml.Unmarshal(d, &s2)) - - assert.Equal(t, s, s2) + assert.Equal(t, EnvironmentMap{"bar": "baz", "far": "faz"}, s2.Foos) } diff --git a/pipeline/frontend/yaml/types/container.go b/pipeline/frontend/yaml/types/container.go index 6de8ab9e7..6b6b665d5 100644 --- a/pipeline/frontend/yaml/types/container.go +++ b/pipeline/frontend/yaml/types/container.go @@ -19,10 +19,9 @@ import ( "gopkg.in/yaml.v3" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/constraint" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/types/base" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/utils" - "go.woodpecker-ci.org/woodpecker/v2/shared/constant" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/constraint" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/types/base" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/utils" ) type ( @@ -33,44 +32,44 @@ type ( // Container defines a container. Container struct { - BackendOptions map[string]any `yaml:"backend_options,omitempty"` - Commands base.StringOrSlice `yaml:"commands,omitempty"` - Entrypoint base.StringOrSlice `yaml:"entrypoint,omitempty"` - Detached bool `yaml:"detach,omitempty"` - Directory string `yaml:"directory,omitempty"` - Failure string `yaml:"failure,omitempty"` - Group string `yaml:"group,omitempty"` - Image string `yaml:"image,omitempty"` - Name string `yaml:"name,omitempty"` - Pull bool `yaml:"pull,omitempty"` - Settings map[string]any `yaml:"settings"` - Volumes Volumes `yaml:"volumes,omitempty"` - When constraint.When `yaml:"when,omitempty"` - Ports []string `yaml:"ports,omitempty"` - DependsOn base.StringOrSlice `yaml:"depends_on,omitempty"` + // common + Name string `yaml:"name,omitempty"` + Image string `yaml:"image,omitempty"` + Pull bool `yaml:"pull,omitempty"` + Commands base.StringOrSlice `yaml:"commands,omitempty"` + Entrypoint base.StringOrSlice `yaml:"entrypoint,omitempty"` + Directory string `yaml:"directory,omitempty"` + Settings map[string]any `yaml:"settings"` + // flow control + DependsOn base.StringOrSlice `yaml:"depends_on,omitempty"` + When constraint.When `yaml:"when,omitempty"` + Failure string `yaml:"failure,omitempty"` + Detached bool `yaml:"detach,omitempty"` + // state + Volumes Volumes `yaml:"volumes,omitempty"` + // network + Ports []string `yaml:"ports,omitempty"` + DNS base.StringOrSlice `yaml:"dns,omitempty"` + DNSSearch base.StringOrSlice `yaml:"dns_search,omitempty"` + // backend specific + BackendOptions map[string]any `yaml:"backend_options,omitempty"` - // TODO: make []string in 3.x - Secrets Secrets `yaml:"secrets,omitempty"` - // TODO: make map[string]any in 3.x - Environment base.SliceOrMap `yaml:"environment,omitempty"` + // ACTIVE DEVELOPMENT BELOW + + // TODO: remove base.EnvironmentMap and use map[string]any after v3.0.0 release + Environment base.EnvironmentMap `yaml:"environment,omitempty"` + + // Remove after v3.1.0 + Secrets []any `yaml:"secrets,omitempty"` // Docker and Kubernetes Specific Privileged bool `yaml:"privileged,omitempty"` // Undocumented - CPUQuota base.StringOrInt `yaml:"cpu_quota,omitempty"` - CPUSet string `yaml:"cpuset,omitempty"` - CPUShares base.StringOrInt `yaml:"cpu_shares,omitempty"` - Devices []string `yaml:"devices,omitempty"` - DNSSearch base.StringOrSlice `yaml:"dns_search,omitempty"` - DNS base.StringOrSlice `yaml:"dns,omitempty"` - ExtraHosts []string `yaml:"extra_hosts,omitempty"` - MemLimit base.MemStringOrInt `yaml:"mem_limit,omitempty"` - MemSwapLimit base.MemStringOrInt `yaml:"memswap_limit,omitempty"` - NetworkMode string `yaml:"network_mode,omitempty"` - Networks Networks `yaml:"networks,omitempty"` - ShmSize base.MemStringOrInt `yaml:"shm_size,omitempty"` - Tmpfs []string `yaml:"tmpfs,omitempty"` + Devices []string `yaml:"devices,omitempty"` + ExtraHosts []string `yaml:"extra_hosts,omitempty"` + NetworkMode string `yaml:"network_mode,omitempty"` + Tmpfs []string `yaml:"tmpfs,omitempty"` } ) @@ -123,9 +122,12 @@ func (c *ContainerList) UnmarshalYAML(value *yaml.Node) error { } func (c *Container) IsPlugin() bool { - return len(c.Commands) == 0 && len(c.Entrypoint) == 0 + return len(c.Commands) == 0 && + len(c.Entrypoint) == 0 && + len(c.Environment) == 0 && + len(c.Secrets) == 0 } -func (c *Container) IsTrustedCloneImage() bool { - return c.IsPlugin() && utils.MatchImage(c.Image, constant.TrustedCloneImages...) +func (c *Container) IsTrustedCloneImage(trustedClonePlugins []string) bool { + return c.IsPlugin() && utils.MatchImageDynamic(c.Image, trustedClonePlugins...) } diff --git a/pipeline/frontend/yaml/types/container_test.go b/pipeline/frontend/yaml/types/container_test.go index 5d8fbb0cc..537ba5089 100644 --- a/pipeline/frontend/yaml/types/container_test.go +++ b/pipeline/frontend/yaml/types/container_test.go @@ -21,8 +21,8 @@ import ( "github.com/stretchr/testify/assert" "gopkg.in/yaml.v3" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/constraint" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/types/base" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/constraint" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/types/base" ) var containerYaml = []byte(` @@ -30,9 +30,6 @@ image: golang:latest commands: - go build - go test -cpu_quota: 11 -cpuset: 1,2 -cpu_shares: 99 detach: true devices: - /dev/ttyUSB0:/dev/ttyUSB0 @@ -41,8 +38,8 @@ dns: 8.8.8.8 dns_search: example.com entrypoint: [/bin/sh, -c] environment: - - RACK_ENV=development - - SHOW=true + RACK_ENV: development + SHOW: true extra_hosts: - somehost:162.242.195.82 - otherhost:50.31.209.229 @@ -54,9 +51,6 @@ networks: - other-network pull: true privileged: true -shm_size: 1kb -mem_limit: 1kb -memswap_limit: 1kb volumes: - /var/lib/mysql - /opt/data:/var/lib/mysql @@ -78,32 +72,20 @@ ports: func TestUnmarshalContainer(t *testing.T) { want := Container{ - Commands: base.StringOrSlice{"go build", "go test"}, - CPUQuota: base.StringOrInt(11), - CPUSet: "1,2", - CPUShares: base.StringOrInt(99), - Detached: true, - Devices: []string{"/dev/ttyUSB0:/dev/ttyUSB0"}, - Directory: "example/", - DNS: base.StringOrSlice{"8.8.8.8"}, - DNSSearch: base.StringOrSlice{"example.com"}, - Entrypoint: []string{"/bin/sh", "-c"}, - Environment: base.SliceOrMap{"RACK_ENV": "development", "SHOW": "true"}, - ExtraHosts: []string{"somehost:162.242.195.82", "otherhost:50.31.209.229", "ipv6:2001:db8::10"}, - Image: "golang:latest", - MemLimit: base.MemStringOrInt(1024), - MemSwapLimit: base.MemStringOrInt(1024), - Name: "my-build-container", - Networks: Networks{ - Networks: []*Network{ - {Name: "some-network"}, - {Name: "other-network"}, - }, - }, + Commands: base.StringOrSlice{"go build", "go test"}, + Detached: true, + Devices: []string{"/dev/ttyUSB0:/dev/ttyUSB0"}, + Directory: "example/", + DNS: base.StringOrSlice{"8.8.8.8"}, + DNSSearch: base.StringOrSlice{"example.com"}, + Entrypoint: []string{"/bin/sh", "-c"}, + Environment: map[string]any{"RACK_ENV": "development", "SHOW": true}, + ExtraHosts: []string{"somehost:162.242.195.82", "otherhost:50.31.209.229", "ipv6:2001:db8::10"}, + Image: "golang:latest", + Name: "my-build-container", NetworkMode: "bridge", Pull: true, Privileged: true, - ShmSize: base.MemStringOrInt(1024), Tmpfs: base.StringOrSlice{"/var/lib/test"}, Volumes: Volumes{ Volumes: []*Volume{ @@ -120,9 +102,7 @@ func TestUnmarshalContainer(t *testing.T) { }, }, { - Event: constraint.List{ - Include: []string{"cron"}, - }, + Event: base.StringOrSlice{"cron"}, Cron: constraint.List{ Include: []string{"job1"}, }, @@ -172,14 +152,12 @@ func TestUnmarshalContainers(t *testing.T) { }, { from: `publish-agent: - group: bundle image: print/env settings: repo: woodpeckerci/woodpecker-agent dry_run: true dockerfile: docker/Dockerfile.agent tag: [next, latest] - secrets: [docker_username, docker_password] when: branch: ${CI_REPO_DEFAULT_BRANCH} event: push`, @@ -187,14 +165,6 @@ func TestUnmarshalContainers(t *testing.T) { { Name: "publish-agent", Image: "print/env", - Group: "bundle", - Secrets: Secrets{Secrets: []*Secret{{ - Source: "docker_username", - Target: "docker_username", - }, { - Source: "docker_password", - Target: "docker_password", - }}}, Settings: map[string]any{ "repo": "woodpeckerci/woodpecker-agent", "dockerfile": "docker/Dockerfile.agent", @@ -204,7 +174,7 @@ func TestUnmarshalContainers(t *testing.T) { When: constraint.When{ Constraints: []constraint.Constraint{ { - Event: constraint.List{Include: []string{"push"}}, + Event: base.StringOrSlice{"push"}, Branch: constraint.List{Include: []string{"${CI_REPO_DEFAULT_BRANCH}"}}, }, }, @@ -214,7 +184,6 @@ func TestUnmarshalContainers(t *testing.T) { }, { from: `publish-cli: - group: docker image: print/env settings: repo: woodpeckerci/woodpecker-cli @@ -227,7 +196,6 @@ func TestUnmarshalContainers(t *testing.T) { { Name: "publish-cli", Image: "print/env", - Group: "docker", Settings: map[string]any{ "repo": "woodpeckerci/woodpecker-cli", "dockerfile": "docker/Dockerfile.cli", @@ -236,7 +204,7 @@ func TestUnmarshalContainers(t *testing.T) { When: constraint.When{ Constraints: []constraint.Constraint{ { - Event: constraint.List{Include: []string{"push"}}, + Event: base.StringOrSlice{"push"}, Branch: constraint.List{Include: []string{"${CI_REPO_DEFAULT_BRANCH}"}}, }, }, @@ -258,11 +226,11 @@ func TestUnmarshalContainers(t *testing.T) { When: constraint.When{ Constraints: []constraint.Constraint{ { - Event: constraint.List{Include: []string{"push"}}, + Event: base.StringOrSlice{"push"}, Branch: constraint.List{Include: []string{"${CI_REPO_DEFAULT_BRANCH}"}}, }, { - Event: constraint.List{Include: []string{"pull_request"}}, + Event: base.StringOrSlice{"pull_request"}, }, }, }, diff --git a/pipeline/frontend/yaml/types/secret.go b/pipeline/frontend/yaml/types/secret.go deleted file mode 100644 index 9958b41aa..000000000 --- a/pipeline/frontend/yaml/types/secret.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2023 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package types - -import "gopkg.in/yaml.v3" - -type ( - // Secrets defines a collection of secrets. - Secrets struct { - Secrets []*Secret - } - - // Secret defines a container secret. - Secret struct { - Source string `yaml:"source"` - Target string `yaml:"target"` - } -) - -// UnmarshalYAML implements the Unmarshaler interface. -func (s *Secrets) UnmarshalYAML(value *yaml.Node) error { - y, _ := yaml.Marshal(value) - - var secrets []string - err := yaml.Unmarshal(y, &secrets) - if err == nil { - for _, str := range secrets { - s.Secrets = append(s.Secrets, &Secret{ - Source: str, - Target: str, - }) - } - return nil - } - return yaml.Unmarshal(y, &s.Secrets) -} diff --git a/pipeline/frontend/yaml/types/secret_test.go b/pipeline/frontend/yaml/types/secret_test.go deleted file mode 100644 index fcfc6d212..000000000 --- a/pipeline/frontend/yaml/types/secret_test.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2023 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package types - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v3" -) - -func TestUnmarshalSecrets(t *testing.T) { - testdata := []struct { - from string - want []*Secret - }{ - { - from: "[ mysql_username, mysql_password]", - want: []*Secret{ - { - Source: "mysql_username", - Target: "mysql_username", - }, - { - Source: "mysql_password", - Target: "mysql_password", - }, - }, - }, - { - from: "[ { source: mysql_prod_username, target: mysql_username } ]", - want: []*Secret{ - { - Source: "mysql_prod_username", - Target: "mysql_username", - }, - }, - }, - { - from: "[ { source: mysql_prod_username, target: mysql_username }, { source: redis_username, target: redis_username } ]", - want: []*Secret{ - { - Source: "mysql_prod_username", - Target: "mysql_username", - }, - { - Source: "redis_username", - Target: "redis_username", - }, - }, - }, - } - - for _, test := range testdata { - in := []byte(test.from) - got := Secrets{} - err := yaml.Unmarshal(in, &got) - assert.NoError(t, err) - assert.EqualValues(t, test.want, got.Secrets, "problem parsing secrets %q", test.from) - } -} diff --git a/pipeline/frontend/yaml/types/workflow.go b/pipeline/frontend/yaml/types/workflow.go index 4f291dec3..134aa47c6 100644 --- a/pipeline/frontend/yaml/types/workflow.go +++ b/pipeline/frontend/yaml/types/workflow.go @@ -15,7 +15,7 @@ package types import ( - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/constraint" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/constraint" ) type ( @@ -30,17 +30,6 @@ type ( DependsOn []string `yaml:"depends_on,omitempty"` RunsOn []string `yaml:"runs_on,omitempty"` SkipClone bool `yaml:"skip_clone"` - - // Undocumented - Networks WorkflowNetworks `yaml:"networks,omitempty"` - Volumes WorkflowVolumes `yaml:"volumes,omitempty"` - - // Deprecated - PlatformDoNotUseIt string `yaml:"platform,omitempty"` // TODO: remove in next major version - // Deprecated - BranchesDoNotUseIt *constraint.List `yaml:"branches,omitempty"` // TODO: remove in next major version - // Deprecated - PipelineDoNotUseIt ContainerList `yaml:"pipeline,omitempty"` // TODO: remove in next major version } // Workspace defines a pipeline workspace. diff --git a/pipeline/frontend/yaml/types/workflow_network.go b/pipeline/frontend/yaml/types/workflow_network.go deleted file mode 100644 index 737dad3dc..000000000 --- a/pipeline/frontend/yaml/types/workflow_network.go +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2023 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package types - -import ( - "fmt" - - "gopkg.in/yaml.v3" -) - -type ( - // WorkflowNetworks defines a collection of networks. - WorkflowNetworks struct { - WorkflowNetworks []*WorkflowNetwork - } - - // WorkflowNetwork defines a container network. - WorkflowNetwork struct { - Name string `yaml:"name,omitempty"` - Driver string `yaml:"driver,omitempty"` - DriverOpts map[string]string `yaml:"driver_opts,omitempty"` - } -) - -// UnmarshalYAML implements the Unmarshaler interface. -func (n *WorkflowNetworks) UnmarshalYAML(value *yaml.Node) error { - networks := map[string]WorkflowNetwork{} - err := value.Decode(&networks) - - for key, nn := range networks { - if nn.Name == "" { - nn.Name = fmt.Sprintf("%v", key) - } - if nn.Driver == "" { - nn.Driver = "bridge" - } - n.WorkflowNetworks = append(n.WorkflowNetworks, &nn) - } - return err -} diff --git a/pipeline/frontend/yaml/types/workflow_network_test.go b/pipeline/frontend/yaml/types/workflow_network_test.go deleted file mode 100644 index ae1141402..000000000 --- a/pipeline/frontend/yaml/types/workflow_network_test.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2023 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package types - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v3" -) - -func TestUnmarshalNetwork(t *testing.T) { - testdata := []struct { - from string - want WorkflowNetwork - }{ - { - from: "{ name: foo, driver: bar }", - want: WorkflowNetwork{ - Name: "foo", - Driver: "bar", - }, - }, - { - from: "{ name: foo, driver: bar, driver_opts: { baz: qux } }", - want: WorkflowNetwork{ - Name: "foo", - Driver: "bar", - DriverOpts: map[string]string{ - "baz": "qux", - }, - }, - }, - } - - for _, test := range testdata { - in := []byte(test.from) - got := WorkflowNetwork{} - err := yaml.Unmarshal(in, &got) - assert.NoError(t, err) - assert.EqualValues(t, test.want, got, "problem parsing network %q", test.from) - } -} - -func TestUnmarshalWorkflowNetworks(t *testing.T) { - testdata := []struct { - from string - want []*WorkflowNetwork - }{ - { - from: "foo: { driver: bar }", - want: []*WorkflowNetwork{ - { - Name: "foo", - Driver: "bar", - }, - }, - }, - { - from: "foo: { name: baz }", - want: []*WorkflowNetwork{ - { - Name: "baz", - Driver: "bridge", - }, - }, - }, - { - from: "foo: { name: baz, driver: bar }", - want: []*WorkflowNetwork{ - { - Name: "baz", - Driver: "bar", - }, - }, - }, - } - - for _, test := range testdata { - in := []byte(test.from) - got := WorkflowNetworks{} - err := yaml.Unmarshal(in, &got) - assert.NoError(t, err) - assert.EqualValues(t, test.want, got.WorkflowNetworks, "problem parsing network %q", test.from) - } -} - -func TestUnmarshalNetworkErr(t *testing.T) { - testdata := []string{ - "foo: { name: [ foo, bar] }", - "- foo", - } - - for _, test := range testdata { - in := []byte(test) - err := yaml.Unmarshal(in, new(WorkflowNetworks)) - assert.Error(t, err, "wanted error for networks %q", test) - } -} diff --git a/pipeline/frontend/yaml/types/workflow_volume.go b/pipeline/frontend/yaml/types/workflow_volume.go deleted file mode 100644 index 46578617b..000000000 --- a/pipeline/frontend/yaml/types/workflow_volume.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2023 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package types - -import ( - "fmt" - - "gopkg.in/yaml.v3" -) - -type ( - // WorkflowVolumes defines a collection of volumes. - WorkflowVolumes struct { - WorkflowVolumes []*WorkflowVolume - } - - // WorkflowVolume defines a container volume. - WorkflowVolume struct { - Name string `yaml:"name,omitempty"` - Driver string `yaml:"driver,omitempty"` - DriverOpts map[string]string `yaml:"driver_opts,omitempty"` - } -) - -// UnmarshalYAML implements the Unmarshaler interface. -func (v *WorkflowVolumes) UnmarshalYAML(value *yaml.Node) error { - y, _ := yaml.Marshal(value) - - volumes := map[string]WorkflowVolume{} - err := yaml.Unmarshal(y, &volumes) - if err != nil { - return err - } - - for key, vv := range volumes { - if vv.Name == "" { - vv.Name = fmt.Sprintf("%v", key) - } - if vv.Driver == "" { - vv.Driver = "local" - } - v.WorkflowVolumes = append(v.WorkflowVolumes, &vv) - } - return err -} diff --git a/pipeline/frontend/yaml/types/workflow_volume_test.go b/pipeline/frontend/yaml/types/workflow_volume_test.go deleted file mode 100644 index de9fe42f5..000000000 --- a/pipeline/frontend/yaml/types/workflow_volume_test.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2023 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package types - -import ( - "testing" - - "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v3" -) - -func TestUnmarshalVolume(t *testing.T) { - testdata := []struct { - from string - want WorkflowVolume - }{ - { - from: "{ name: foo, driver: bar }", - want: WorkflowVolume{ - Name: "foo", - Driver: "bar", - }, - }, - { - from: "{ name: foo, driver: bar, driver_opts: { baz: qux } }", - want: WorkflowVolume{ - Name: "foo", - Driver: "bar", - DriverOpts: map[string]string{ - "baz": "qux", - }, - }, - }, - } - - for _, test := range testdata { - in := []byte(test.from) - got := WorkflowVolume{} - err := yaml.Unmarshal(in, &got) - assert.NoError(t, err) - assert.EqualValues(t, test.want, got, "problem parsing volume %q", test.from) - } -} - -func TestUnmarshalWorkflowVolumes(t *testing.T) { - testdata := []struct { - from string - want []*WorkflowVolume - }{ - { - from: "foo: { driver: bar }", - want: []*WorkflowVolume{ - { - Name: "foo", - Driver: "bar", - }, - }, - }, - { - from: "foo: { name: baz }", - want: []*WorkflowVolume{ - { - Name: "baz", - Driver: "local", - }, - }, - }, - { - from: "foo: { name: baz, driver: bar }", - want: []*WorkflowVolume{ - { - Name: "baz", - Driver: "bar", - }, - }, - }, - } - - for _, test := range testdata { - in := []byte(test.from) - got := WorkflowVolumes{} - err := yaml.Unmarshal(in, &got) - assert.NoError(t, err) - assert.EqualValues(t, test.want, got.WorkflowVolumes, "problem parsing volumes %q", test.from) - } -} - -func TestUnmarshalVolumesErr(t *testing.T) { - testdata := []string{ - "foo: { name: [ foo, bar] }", - "- foo", - } - - for _, test := range testdata { - in := []byte(test) - err := yaml.Unmarshal(in, new(WorkflowVolumes)) - assert.Error(t, err, "wanted error for volumes %q", test) - } -} diff --git a/pipeline/frontend/yaml/utils/image.go b/pipeline/frontend/yaml/utils/image.go index 63054c21d..43d139ee0 100644 --- a/pipeline/frontend/yaml/utils/image.go +++ b/pipeline/frontend/yaml/utils/image.go @@ -14,7 +14,11 @@ package utils -import "github.com/distribution/reference" +import ( + "strings" + + "github.com/distribution/reference" +) // trimImage returns the short image name without tag. func trimImage(name string) string { @@ -57,14 +61,42 @@ func MatchImage(from string, to ...string) bool { return false } +// MatchImageDynamic check if image is in list based on list. +// If an list entry has a tag specified it only will match if both are the same, else the tag is ignored. +func MatchImageDynamic(from string, to ...string) bool { + fullFrom := expandImage(from) + trimFrom := trimImage(from) + for _, match := range to { + if imageHasTag(match) { + if fullFrom == expandImage(match) { + return true + } + } else { + if trimFrom == trimImage(match) { + return true + } + } + } + return false +} + +func imageHasTag(name string) bool { + return strings.Contains(name, ":") +} + +// ParseNamed parses an image as a reference to validate it then parses it as a named reference. +func ParseNamed(image string) (reference.Named, error) { + ref, err := reference.ParseAnyReference(image) + if err != nil { + return nil, err + } + return reference.ParseNamed(ref.String()) +} + // MatchHostname returns true if the image hostname // matches the specified hostname. func MatchHostname(image, hostname string) bool { - ref, err := reference.ParseAnyReference(image) - if err != nil { - return false - } - named, err := reference.ParseNamed(ref.String()) + named, err := ParseNamed(image) if err != nil { return false } diff --git a/pipeline/frontend/yaml/utils/image_test.go b/pipeline/frontend/yaml/utils/image_test.go index 17fab45d5..a1250a49d 100644 --- a/pipeline/frontend/yaml/utils/image_test.go +++ b/pipeline/frontend/yaml/utils/image_test.go @@ -113,6 +113,10 @@ func Test_expandImage(t *testing.T) { from: "gcr.io/golang:1.0.0", want: "gcr.io/golang:1.0.0", }, + { + from: "codeberg.org/6543/hello:latest@2c98dce11f78c2b4e40f513ca82f75035eb8cfa4957a6d8eb3f917ecaf77803", + want: "codeberg.org/6543/hello:latest@2c98dce11f78c2b4e40f513ca82f75035eb8cfa4957a6d8eb3f917ecaf77803", + }, // error cases, return input unmodified { from: "foo/bar?baz:boo", @@ -124,6 +128,57 @@ func Test_expandImage(t *testing.T) { } } +func Test_imageHasTag(t *testing.T) { + testdata := []struct { + from string + want bool + }{ + { + from: "golang", + want: false, + }, + { + from: "golang:latest", + want: true, + }, + { + from: "golang:1.0.0", + want: true, + }, + { + from: "library/golang", + want: false, + }, + { + from: "library/golang:latest", + want: true, + }, + { + from: "library/golang:1.0.0", + want: true, + }, + { + from: "index.docker.io/library/golang:1.0.0", + want: true, + }, + { + from: "gcr.io/golang", + want: false, + }, + { + from: "gcr.io/golang:1.0.0", + want: true, + }, + { + from: "codeberg.org/6543/hello:latest@2c98dce11f78c2b4e40f513ca82f75035eb8cfa4957a6d8eb3f917ecaf77803", + want: true, + }, + } + for _, test := range testdata { + assert.Equal(t, test.want, imageHasTag(test.from)) + } +} + func Test_matchImage(t *testing.T) { testdata := []struct { from, to string @@ -199,12 +254,67 @@ func Test_matchImage(t *testing.T) { to: "gcr.io/golang", want: false, }, + { + from: "woodpeckerci/plugin-kaniko", + to: "docker.io/woodpeckerci/plugin-kaniko", + want: true, + }, } for _, test := range testdata { assert.Equal(t, test.want, MatchImage(test.from, test.to)) } } +func Test_matchImageDynamic(t *testing.T) { + testdata := []struct { + name, from string + to []string + want bool + }{ + { + name: "simple compare", + from: "golang", + to: []string{"golang"}, + want: true, + }, + { + name: "compare non-taged image whit list who tag requirement", + from: "golang", + to: []string{"golang:v3.0"}, + want: false, + }, + { + name: "compare taged image whit list who tag no requirement", + from: "golang:v3.0", + to: []string{"golang"}, + want: true, + }, + { + name: "compare taged image whit list who has image with no tag requirement", + from: "golang:1.0", + to: []string{"golang", "golang:2.0"}, + want: true, + }, + { + name: "compare taged image whit list who only has images with tag requirement", + from: "golang:1.0", + to: []string{"golang:latest", "golang:2.0"}, + want: false, + }, + { + name: "compare taged image whit list who only has images with tag requirement", + from: "golang:1.0", + to: []string{"golang:latest", "golang:1.0"}, + want: true, + }, + } + for _, test := range testdata { + if !assert.Equal(t, test.want, MatchImageDynamic(test.from, test.to...)) { + t.Logf("test data: '%s' -> '%s'", test.from, test.to) + } + } +} + func Test_matchHostname(t *testing.T) { testdata := []struct { image, hostname string diff --git a/pipeline/log/line_writer.go b/pipeline/log/line_writer.go index 0c5058f45..b88cbd671 100644 --- a/pipeline/log/line_writer.go +++ b/pipeline/log/line_writer.go @@ -16,7 +16,6 @@ package log import ( - "context" "io" "strings" "sync" @@ -24,8 +23,8 @@ import ( "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/shared" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/shared" ) // LineWriter sends logs to the client. @@ -67,9 +66,6 @@ func (w *LineWriter) Write(p []byte) (n int, err error) { w.num++ - if err := w.peer.Log(context.Background(), line); err != nil { - return 0, err - } - + w.peer.EnqueueLog(line) return len(data), nil } diff --git a/pipeline/log/line_writer_test.go b/pipeline/log/line_writer_test.go index 8bf8f6251..bcff046da 100644 --- a/pipeline/log/line_writer_test.go +++ b/pipeline/log/line_writer_test.go @@ -20,14 +20,14 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/log" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc/mocks" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/log" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc/mocks" ) func TestLineWriter(t *testing.T) { peer := mocks.NewPeer(t) - peer.On("Log", mock.Anything, mock.Anything).Return(nil) + peer.On("EnqueueLog", mock.Anything) secrets := []string{"world"} lw := log.NewLineWriter(peer, "e9ea76a5-44a1-4059-9c4a-6956c478b26d", secrets...) @@ -37,7 +37,7 @@ func TestLineWriter(t *testing.T) { _, err = lw.Write([]byte("the previous line had no newline at the end")) assert.NoError(t, err) - peer.AssertCalled(t, "Log", mock.Anything, &rpc.LogEntry{ + peer.AssertCalled(t, "EnqueueLog", &rpc.LogEntry{ StepUUID: "e9ea76a5-44a1-4059-9c4a-6956c478b26d", Time: 0, Type: rpc.LogEntryStdout, @@ -45,7 +45,7 @@ func TestLineWriter(t *testing.T) { Data: []byte("hello ********"), }) - peer.AssertCalled(t, "Log", mock.Anything, &rpc.LogEntry{ + peer.AssertCalled(t, "EnqueueLog", &rpc.LogEntry{ StepUUID: "e9ea76a5-44a1-4059-9c4a-6956c478b26d", Time: 0, Type: rpc.LogEntryStdout, diff --git a/pipeline/log/utils_test.go b/pipeline/log/utils_test.go index 9861b4aef..9dbd985f0 100644 --- a/pipeline/log/utils_test.go +++ b/pipeline/log/utils_test.go @@ -23,7 +23,7 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/log" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/log" ) type testWriter struct { @@ -83,7 +83,7 @@ func TestCopyLineByLine(t *testing.T) { assert.Lenf(t, writes, 2, "expected 2 writes, got: %v", writes) // wait for the goroutine to write the data - time.Sleep(time.Millisecond) + time.Sleep(10 * time.Millisecond) writtenData := strings.Join(writes, "-") assert.Equal(t, "12345\n-678\n", writtenData, "unexpected writtenData: %s", writtenData) @@ -131,6 +131,8 @@ func TestCopyLineByLineSizeLimit(t *testing.T) { if _, err := w.Write([]byte("67\n89")); err != nil { t.Fatalf("unexpected error: %v", err) } + // wait for writer to write + time.Sleep(time.Millisecond) writes = testWriter.GetWrites() assert.Lenf(t, testWriter.GetWrites(), 2, "expected 2 writes, got: %v", writes) diff --git a/pipeline/logger.go b/pipeline/logger.go index 6eea180d1..765a0333b 100644 --- a/pipeline/logger.go +++ b/pipeline/logger.go @@ -17,7 +17,7 @@ package pipeline import ( "io" - backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" + backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" ) // Logger handles the process logging. diff --git a/pipeline/option.go b/pipeline/option.go index 6be8f3c3d..784cfdbbd 100644 --- a/pipeline/option.go +++ b/pipeline/option.go @@ -17,7 +17,7 @@ package pipeline import ( "context" - backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" + backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" ) // Option configures a runtime option. diff --git a/pipeline/pipeline.go b/pipeline/pipeline.go index ada817b02..c7429dbf3 100644 --- a/pipeline/pipeline.go +++ b/pipeline/pipeline.go @@ -26,8 +26,8 @@ import ( "github.com/rs/zerolog/log" "golang.org/x/sync/errgroup" - backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata" + backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata" ) // TODO: move runtime into "runtime" subpackage @@ -105,7 +105,11 @@ func (r *Runtime) Run(runnerCtx context.Context) error { } defer func() { - if err := r.engine.DestroyWorkflow(runnerCtx, r.spec, r.taskUUID); err != nil { + ctx := runnerCtx //nolint:contextcheck + if ctx.Err() != nil { + ctx = GetShutdownCtx() + } + if err := r.engine.DestroyWorkflow(ctx, r.spec, r.taskUUID); err != nil { logger.Error().Err(err).Msg("could not destroy engine") } }() diff --git a/pipeline/rpc/mocks/peer.go b/pipeline/rpc/mocks/peer.go index 8d0c45d18..e8383505b 100644 --- a/pipeline/rpc/mocks/peer.go +++ b/pipeline/rpc/mocks/peer.go @@ -9,7 +9,7 @@ import ( context "context" mock "github.com/stretchr/testify/mock" - rpc "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc" + rpc "go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc" ) // Peer is an autogenerated mock type for the Peer type @@ -35,6 +35,11 @@ func (_m *Peer) Done(c context.Context, workflowID string, state rpc.WorkflowSta return r0 } +// EnqueueLog provides a mock function with given fields: logEntry +func (_m *Peer) EnqueueLog(logEntry *rpc.LogEntry) { + _m.Called(logEntry) +} + // Extend provides a mock function with given fields: c, workflowID func (_m *Peer) Extend(c context.Context, workflowID string) error { ret := _m.Called(c, workflowID) @@ -71,24 +76,6 @@ func (_m *Peer) Init(c context.Context, workflowID string, state rpc.WorkflowSta return r0 } -// Log provides a mock function with given fields: c, logEntry -func (_m *Peer) Log(c context.Context, logEntry *rpc.LogEntry) error { - ret := _m.Called(c, logEntry) - - if len(ret) == 0 { - panic("no return value specified for Log") - } - - var r0 error - if rf, ok := ret.Get(0).(func(context.Context, *rpc.LogEntry) error); ok { - r0 = rf(c, logEntry) - } else { - r0 = ret.Error(0) - } - - return r0 -} - // Next provides a mock function with given fields: c, f func (_m *Peer) Next(c context.Context, f rpc.Filter) (*rpc.Workflow, error) { ret := _m.Called(c, f) @@ -119,9 +106,9 @@ func (_m *Peer) Next(c context.Context, f rpc.Filter) (*rpc.Workflow, error) { return r0, r1 } -// RegisterAgent provides a mock function with given fields: ctx, platform, backend, version, capacity -func (_m *Peer) RegisterAgent(ctx context.Context, platform string, backend string, version string, capacity int) (int64, error) { - ret := _m.Called(ctx, platform, backend, version, capacity) +// RegisterAgent provides a mock function with given fields: ctx, info +func (_m *Peer) RegisterAgent(ctx context.Context, info rpc.AgentInfo) (int64, error) { + ret := _m.Called(ctx, info) if len(ret) == 0 { panic("no return value specified for RegisterAgent") @@ -129,17 +116,17 @@ func (_m *Peer) RegisterAgent(ctx context.Context, platform string, backend stri var r0 int64 var r1 error - if rf, ok := ret.Get(0).(func(context.Context, string, string, string, int) (int64, error)); ok { - return rf(ctx, platform, backend, version, capacity) + if rf, ok := ret.Get(0).(func(context.Context, rpc.AgentInfo) (int64, error)); ok { + return rf(ctx, info) } - if rf, ok := ret.Get(0).(func(context.Context, string, string, string, int) int64); ok { - r0 = rf(ctx, platform, backend, version, capacity) + if rf, ok := ret.Get(0).(func(context.Context, rpc.AgentInfo) int64); ok { + r0 = rf(ctx, info) } else { r0 = ret.Get(0).(int64) } - if rf, ok := ret.Get(1).(func(context.Context, string, string, string, int) error); ok { - r1 = rf(ctx, platform, backend, version, capacity) + if rf, ok := ret.Get(1).(func(context.Context, rpc.AgentInfo) error); ok { + r1 = rf(ctx, info) } else { r1 = ret.Error(1) } diff --git a/pipeline/rpc/peer.go b/pipeline/rpc/peer.go index b38b01051..06790ffec 100644 --- a/pipeline/rpc/peer.go +++ b/pipeline/rpc/peer.go @@ -18,7 +18,7 @@ package rpc import ( "context" - backend "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" + backend "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" ) type ( @@ -55,6 +55,15 @@ type ( GrpcVersion int32 `json:"grpc_version,omitempty"` ServerVersion string `json:"server_version,omitempty"` } + + // AgentInfo represents all the metadata that should be known about an agent. + AgentInfo struct { + Version string `json:"version"` + Platform string `json:"platform"` + Backend string `json:"backend"` + Capacity int `json:"capacity"` + CustomLabels map[string]string `json:"custom_labels"` + } ) //go:generate mockery --name Peer --output mocks --case underscore --note "+build test" @@ -82,11 +91,11 @@ type Peer interface { // Update updates the step state Update(c context.Context, workflowID string, state StepState) error - // Log writes the step log entry - Log(c context.Context, logEntry *LogEntry) error + // EnqueueLog queues the step log entry for delayed sending + EnqueueLog(logEntry *LogEntry) // RegisterAgent register our agent to the server - RegisterAgent(ctx context.Context, platform, backend, version string, capacity int) (int64, error) + RegisterAgent(ctx context.Context, info AgentInfo) (int64, error) // UnregisterAgent unregister our agent from the server UnregisterAgent(ctx context.Context) error diff --git a/pipeline/rpc/proto/version.go b/pipeline/rpc/proto/version.go index 2de7645f3..c479ef92d 100644 --- a/pipeline/rpc/proto/version.go +++ b/pipeline/rpc/proto/version.go @@ -16,4 +16,4 @@ package proto // Version is the version of the woodpecker.proto file, // IMPORTANT: increased by 1 each time it get changed. -const Version int32 = 9 +const Version int32 = 12 diff --git a/pipeline/rpc/proto/woodpecker.pb.go b/pipeline/rpc/proto/woodpecker.pb.go index f66e9be5b..e3cf1040e 100644 --- a/pipeline/rpc/proto/woodpecker.pb.go +++ b/pipeline/rpc/proto/woodpecker.pb.go @@ -15,8 +15,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.34.1 -// protoc v4.25.1 +// protoc-gen-go v1.36.1 +// protoc v5.28.3 // source: woodpecker.proto package proto @@ -36,25 +36,22 @@ const ( ) type StepState struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + StepUuid string `protobuf:"bytes,1,opt,name=step_uuid,json=stepUuid,proto3" json:"step_uuid,omitempty"` + Started int64 `protobuf:"varint,2,opt,name=started,proto3" json:"started,omitempty"` + Finished int64 `protobuf:"varint,3,opt,name=finished,proto3" json:"finished,omitempty"` + Exited bool `protobuf:"varint,4,opt,name=exited,proto3" json:"exited,omitempty"` + ExitCode int32 `protobuf:"varint,5,opt,name=exit_code,json=exitCode,proto3" json:"exit_code,omitempty"` + Error string `protobuf:"bytes,6,opt,name=error,proto3" json:"error,omitempty"` unknownFields protoimpl.UnknownFields - - StepUuid string `protobuf:"bytes,1,opt,name=step_uuid,json=stepUuid,proto3" json:"step_uuid,omitempty"` - Started int64 `protobuf:"varint,2,opt,name=started,proto3" json:"started,omitempty"` - Finished int64 `protobuf:"varint,3,opt,name=finished,proto3" json:"finished,omitempty"` - Exited bool `protobuf:"varint,4,opt,name=exited,proto3" json:"exited,omitempty"` - ExitCode int32 `protobuf:"varint,5,opt,name=exit_code,json=exitCode,proto3" json:"exit_code,omitempty"` - Error string `protobuf:"bytes,6,opt,name=error,proto3" json:"error,omitempty"` + sizeCache protoimpl.SizeCache } func (x *StepState) Reset() { *x = StepState{} - if protoimpl.UnsafeEnabled { - mi := &file_woodpecker_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_woodpecker_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *StepState) String() string { @@ -65,7 +62,7 @@ func (*StepState) ProtoMessage() {} func (x *StepState) ProtoReflect() protoreflect.Message { mi := &file_woodpecker_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -123,22 +120,19 @@ func (x *StepState) GetError() string { } type WorkflowState struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Started int64 `protobuf:"varint,4,opt,name=started,proto3" json:"started,omitempty"` + Finished int64 `protobuf:"varint,5,opt,name=finished,proto3" json:"finished,omitempty"` + Error string `protobuf:"bytes,6,opt,name=error,proto3" json:"error,omitempty"` unknownFields protoimpl.UnknownFields - - Started int64 `protobuf:"varint,4,opt,name=started,proto3" json:"started,omitempty"` - Finished int64 `protobuf:"varint,5,opt,name=finished,proto3" json:"finished,omitempty"` - Error string `protobuf:"bytes,6,opt,name=error,proto3" json:"error,omitempty"` + sizeCache protoimpl.SizeCache } func (x *WorkflowState) Reset() { *x = WorkflowState{} - if protoimpl.UnsafeEnabled { - mi := &file_woodpecker_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_woodpecker_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *WorkflowState) String() string { @@ -149,7 +143,7 @@ func (*WorkflowState) ProtoMessage() {} func (x *WorkflowState) ProtoReflect() protoreflect.Message { mi := &file_woodpecker_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -186,24 +180,21 @@ func (x *WorkflowState) GetError() string { } type LogEntry struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + StepUuid string `protobuf:"bytes,1,opt,name=step_uuid,json=stepUuid,proto3" json:"step_uuid,omitempty"` + Time int64 `protobuf:"varint,2,opt,name=time,proto3" json:"time,omitempty"` + Line int32 `protobuf:"varint,3,opt,name=line,proto3" json:"line,omitempty"` + Type int32 `protobuf:"varint,4,opt,name=type,proto3" json:"type,omitempty"` // 0 = stdout, 1 = stderr, 2 = exit-code, 3 = metadata, 4 = progress + Data []byte `protobuf:"bytes,5,opt,name=data,proto3" json:"data,omitempty"` unknownFields protoimpl.UnknownFields - - StepUuid string `protobuf:"bytes,1,opt,name=step_uuid,json=stepUuid,proto3" json:"step_uuid,omitempty"` - Time int64 `protobuf:"varint,2,opt,name=time,proto3" json:"time,omitempty"` - Line int32 `protobuf:"varint,3,opt,name=line,proto3" json:"line,omitempty"` - Type int32 `protobuf:"varint,4,opt,name=type,proto3" json:"type,omitempty"` // 0 = stdout, 1 = stderr, 2 = exit-code, 3 = metadata, 4 = progress - Data []byte `protobuf:"bytes,5,opt,name=data,proto3" json:"data,omitempty"` + sizeCache protoimpl.SizeCache } func (x *LogEntry) Reset() { *x = LogEntry{} - if protoimpl.UnsafeEnabled { - mi := &file_woodpecker_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_woodpecker_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *LogEntry) String() string { @@ -214,7 +205,7 @@ func (*LogEntry) ProtoMessage() {} func (x *LogEntry) ProtoReflect() protoreflect.Message { mi := &file_woodpecker_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -265,20 +256,17 @@ func (x *LogEntry) GetData() []byte { } type Filter struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Labels map[string]string `protobuf:"bytes,1,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields - - Labels map[string]string `protobuf:"bytes,1,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + sizeCache protoimpl.SizeCache } func (x *Filter) Reset() { *x = Filter{} - if protoimpl.UnsafeEnabled { - mi := &file_woodpecker_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_woodpecker_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Filter) String() string { @@ -289,7 +277,7 @@ func (*Filter) ProtoMessage() {} func (x *Filter) ProtoReflect() protoreflect.Message { mi := &file_woodpecker_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -312,22 +300,19 @@ func (x *Filter) GetLabels() map[string]string { } type Workflow struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Timeout int64 `protobuf:"varint,2,opt,name=timeout,proto3" json:"timeout,omitempty"` + Payload []byte `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"` unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - Timeout int64 `protobuf:"varint,2,opt,name=timeout,proto3" json:"timeout,omitempty"` - Payload []byte `protobuf:"bytes,3,opt,name=payload,proto3" json:"payload,omitempty"` + sizeCache protoimpl.SizeCache } func (x *Workflow) Reset() { *x = Workflow{} - if protoimpl.UnsafeEnabled { - mi := &file_woodpecker_proto_msgTypes[4] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_woodpecker_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Workflow) String() string { @@ -338,7 +323,7 @@ func (*Workflow) ProtoMessage() {} func (x *Workflow) ProtoReflect() protoreflect.Message { mi := &file_woodpecker_proto_msgTypes[4] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -375,20 +360,17 @@ func (x *Workflow) GetPayload() []byte { } type NextRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Filter *Filter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` unknownFields protoimpl.UnknownFields - - Filter *Filter `protobuf:"bytes,1,opt,name=filter,proto3" json:"filter,omitempty"` + sizeCache protoimpl.SizeCache } func (x *NextRequest) Reset() { *x = NextRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_woodpecker_proto_msgTypes[5] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_woodpecker_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NextRequest) String() string { @@ -399,7 +381,7 @@ func (*NextRequest) ProtoMessage() {} func (x *NextRequest) ProtoReflect() protoreflect.Message { mi := &file_woodpecker_proto_msgTypes[5] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -422,21 +404,18 @@ func (x *NextRequest) GetFilter() *Filter { } type InitRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + State *WorkflowState `protobuf:"bytes,2,opt,name=state,proto3" json:"state,omitempty"` unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - State *WorkflowState `protobuf:"bytes,2,opt,name=state,proto3" json:"state,omitempty"` + sizeCache protoimpl.SizeCache } func (x *InitRequest) Reset() { *x = InitRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_woodpecker_proto_msgTypes[6] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_woodpecker_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *InitRequest) String() string { @@ -447,7 +426,7 @@ func (*InitRequest) ProtoMessage() {} func (x *InitRequest) ProtoReflect() protoreflect.Message { mi := &file_woodpecker_proto_msgTypes[6] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -477,20 +456,17 @@ func (x *InitRequest) GetState() *WorkflowState { } type WaitRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + sizeCache protoimpl.SizeCache } func (x *WaitRequest) Reset() { *x = WaitRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_woodpecker_proto_msgTypes[7] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_woodpecker_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *WaitRequest) String() string { @@ -501,7 +477,7 @@ func (*WaitRequest) ProtoMessage() {} func (x *WaitRequest) ProtoReflect() protoreflect.Message { mi := &file_woodpecker_proto_msgTypes[7] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -524,21 +500,18 @@ func (x *WaitRequest) GetId() string { } type DoneRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + State *WorkflowState `protobuf:"bytes,2,opt,name=state,proto3" json:"state,omitempty"` unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - State *WorkflowState `protobuf:"bytes,2,opt,name=state,proto3" json:"state,omitempty"` + sizeCache protoimpl.SizeCache } func (x *DoneRequest) Reset() { *x = DoneRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_woodpecker_proto_msgTypes[8] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_woodpecker_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *DoneRequest) String() string { @@ -549,7 +522,7 @@ func (*DoneRequest) ProtoMessage() {} func (x *DoneRequest) ProtoReflect() protoreflect.Message { mi := &file_woodpecker_proto_msgTypes[8] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -579,20 +552,17 @@ func (x *DoneRequest) GetState() *WorkflowState { } type ExtendRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ExtendRequest) Reset() { *x = ExtendRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_woodpecker_proto_msgTypes[9] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_woodpecker_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ExtendRequest) String() string { @@ -603,7 +573,7 @@ func (*ExtendRequest) ProtoMessage() {} func (x *ExtendRequest) ProtoReflect() protoreflect.Message { mi := &file_woodpecker_proto_msgTypes[9] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -626,21 +596,18 @@ func (x *ExtendRequest) GetId() string { } type UpdateRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + State *StepState `protobuf:"bytes,2,opt,name=state,proto3" json:"state,omitempty"` unknownFields protoimpl.UnknownFields - - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` - State *StepState `protobuf:"bytes,2,opt,name=state,proto3" json:"state,omitempty"` + sizeCache protoimpl.SizeCache } func (x *UpdateRequest) Reset() { *x = UpdateRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_woodpecker_proto_msgTypes[10] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_woodpecker_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *UpdateRequest) String() string { @@ -651,7 +618,7 @@ func (*UpdateRequest) ProtoMessage() {} func (x *UpdateRequest) ProtoReflect() protoreflect.Message { mi := &file_woodpecker_proto_msgTypes[10] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -681,20 +648,17 @@ func (x *UpdateRequest) GetState() *StepState { } type LogRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + LogEntries []*LogEntry `protobuf:"bytes,1,rep,name=logEntries,proto3" json:"logEntries,omitempty"` unknownFields protoimpl.UnknownFields - - LogEntry *LogEntry `protobuf:"bytes,1,opt,name=logEntry,proto3" json:"logEntry,omitempty"` + sizeCache protoimpl.SizeCache } func (x *LogRequest) Reset() { *x = LogRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_woodpecker_proto_msgTypes[11] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_woodpecker_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *LogRequest) String() string { @@ -705,7 +669,7 @@ func (*LogRequest) ProtoMessage() {} func (x *LogRequest) ProtoReflect() protoreflect.Message { mi := &file_woodpecker_proto_msgTypes[11] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -720,26 +684,24 @@ func (*LogRequest) Descriptor() ([]byte, []int) { return file_woodpecker_proto_rawDescGZIP(), []int{11} } -func (x *LogRequest) GetLogEntry() *LogEntry { +func (x *LogRequest) GetLogEntries() []*LogEntry { if x != nil { - return x.LogEntry + return x.LogEntries } return nil } type Empty struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Empty) Reset() { *x = Empty{} - if protoimpl.UnsafeEnabled { - mi := &file_woodpecker_proto_msgTypes[12] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_woodpecker_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Empty) String() string { @@ -750,7 +712,7 @@ func (*Empty) ProtoMessage() {} func (x *Empty) ProtoReflect() protoreflect.Message { mi := &file_woodpecker_proto_msgTypes[12] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -766,20 +728,17 @@ func (*Empty) Descriptor() ([]byte, []int) { } type ReportHealthRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` unknownFields protoimpl.UnknownFields - - Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + sizeCache protoimpl.SizeCache } func (x *ReportHealthRequest) Reset() { *x = ReportHealthRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_woodpecker_proto_msgTypes[13] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_woodpecker_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *ReportHealthRequest) String() string { @@ -790,7 +749,7 @@ func (*ReportHealthRequest) ProtoMessage() {} func (x *ReportHealthRequest) ProtoReflect() protoreflect.Message { mi := &file_woodpecker_proto_msgTypes[13] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -812,24 +771,94 @@ func (x *ReportHealthRequest) GetStatus() string { return "" } -type RegisterAgentRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache +type AgentInfo struct { + state protoimpl.MessageState `protogen:"open.v1"` + Platform string `protobuf:"bytes,1,opt,name=platform,proto3" json:"platform,omitempty"` + Capacity int32 `protobuf:"varint,2,opt,name=capacity,proto3" json:"capacity,omitempty"` + Backend string `protobuf:"bytes,3,opt,name=backend,proto3" json:"backend,omitempty"` + Version string `protobuf:"bytes,4,opt,name=version,proto3" json:"version,omitempty"` + CustomLabels map[string]string `protobuf:"bytes,5,rep,name=customLabels,proto3" json:"customLabels,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} - Platform string `protobuf:"bytes,1,opt,name=platform,proto3" json:"platform,omitempty"` - Capacity int32 `protobuf:"varint,2,opt,name=capacity,proto3" json:"capacity,omitempty"` - Backend string `protobuf:"bytes,3,opt,name=backend,proto3" json:"backend,omitempty"` - Version string `protobuf:"bytes,4,opt,name=version,proto3" json:"version,omitempty"` +func (x *AgentInfo) Reset() { + *x = AgentInfo{} + mi := &file_woodpecker_proto_msgTypes[14] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *AgentInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*AgentInfo) ProtoMessage() {} + +func (x *AgentInfo) ProtoReflect() protoreflect.Message { + mi := &file_woodpecker_proto_msgTypes[14] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use AgentInfo.ProtoReflect.Descriptor instead. +func (*AgentInfo) Descriptor() ([]byte, []int) { + return file_woodpecker_proto_rawDescGZIP(), []int{14} +} + +func (x *AgentInfo) GetPlatform() string { + if x != nil { + return x.Platform + } + return "" +} + +func (x *AgentInfo) GetCapacity() int32 { + if x != nil { + return x.Capacity + } + return 0 +} + +func (x *AgentInfo) GetBackend() string { + if x != nil { + return x.Backend + } + return "" +} + +func (x *AgentInfo) GetVersion() string { + if x != nil { + return x.Version + } + return "" +} + +func (x *AgentInfo) GetCustomLabels() map[string]string { + if x != nil { + return x.CustomLabels + } + return nil +} + +type RegisterAgentRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Info *AgentInfo `protobuf:"bytes,1,opt,name=info,proto3" json:"info,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *RegisterAgentRequest) Reset() { *x = RegisterAgentRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_woodpecker_proto_msgTypes[14] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_woodpecker_proto_msgTypes[15] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RegisterAgentRequest) String() string { @@ -839,8 +868,8 @@ func (x *RegisterAgentRequest) String() string { func (*RegisterAgentRequest) ProtoMessage() {} func (x *RegisterAgentRequest) ProtoReflect() protoreflect.Message { - mi := &file_woodpecker_proto_msgTypes[14] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_woodpecker_proto_msgTypes[15] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -852,53 +881,29 @@ func (x *RegisterAgentRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisterAgentRequest.ProtoReflect.Descriptor instead. func (*RegisterAgentRequest) Descriptor() ([]byte, []int) { - return file_woodpecker_proto_rawDescGZIP(), []int{14} + return file_woodpecker_proto_rawDescGZIP(), []int{15} } -func (x *RegisterAgentRequest) GetPlatform() string { +func (x *RegisterAgentRequest) GetInfo() *AgentInfo { if x != nil { - return x.Platform + return x.Info } - return "" -} - -func (x *RegisterAgentRequest) GetCapacity() int32 { - if x != nil { - return x.Capacity - } - return 0 -} - -func (x *RegisterAgentRequest) GetBackend() string { - if x != nil { - return x.Backend - } - return "" -} - -func (x *RegisterAgentRequest) GetVersion() string { - if x != nil { - return x.Version - } - return "" + return nil } type VersionResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + GrpcVersion int32 `protobuf:"varint,1,opt,name=grpc_version,json=grpcVersion,proto3" json:"grpc_version,omitempty"` + ServerVersion string `protobuf:"bytes,2,opt,name=server_version,json=serverVersion,proto3" json:"server_version,omitempty"` unknownFields protoimpl.UnknownFields - - GrpcVersion int32 `protobuf:"varint,1,opt,name=grpc_version,json=grpcVersion,proto3" json:"grpc_version,omitempty"` - ServerVersion string `protobuf:"bytes,2,opt,name=server_version,json=serverVersion,proto3" json:"server_version,omitempty"` + sizeCache protoimpl.SizeCache } func (x *VersionResponse) Reset() { *x = VersionResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_woodpecker_proto_msgTypes[15] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_woodpecker_proto_msgTypes[16] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *VersionResponse) String() string { @@ -908,8 +913,8 @@ func (x *VersionResponse) String() string { func (*VersionResponse) ProtoMessage() {} func (x *VersionResponse) ProtoReflect() protoreflect.Message { - mi := &file_woodpecker_proto_msgTypes[15] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_woodpecker_proto_msgTypes[16] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -921,7 +926,7 @@ func (x *VersionResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use VersionResponse.ProtoReflect.Descriptor instead. func (*VersionResponse) Descriptor() ([]byte, []int) { - return file_woodpecker_proto_rawDescGZIP(), []int{15} + return file_woodpecker_proto_rawDescGZIP(), []int{16} } func (x *VersionResponse) GetGrpcVersion() int32 { @@ -939,20 +944,17 @@ func (x *VersionResponse) GetServerVersion() string { } type NextResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Workflow *Workflow `protobuf:"bytes,1,opt,name=workflow,proto3" json:"workflow,omitempty"` unknownFields protoimpl.UnknownFields - - Workflow *Workflow `protobuf:"bytes,1,opt,name=workflow,proto3" json:"workflow,omitempty"` + sizeCache protoimpl.SizeCache } func (x *NextResponse) Reset() { *x = NextResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_woodpecker_proto_msgTypes[16] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_woodpecker_proto_msgTypes[17] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *NextResponse) String() string { @@ -962,8 +964,8 @@ func (x *NextResponse) String() string { func (*NextResponse) ProtoMessage() {} func (x *NextResponse) ProtoReflect() protoreflect.Message { - mi := &file_woodpecker_proto_msgTypes[16] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_woodpecker_proto_msgTypes[17] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -975,7 +977,7 @@ func (x *NextResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use NextResponse.ProtoReflect.Descriptor instead. func (*NextResponse) Descriptor() ([]byte, []int) { - return file_woodpecker_proto_rawDescGZIP(), []int{16} + return file_woodpecker_proto_rawDescGZIP(), []int{17} } func (x *NextResponse) GetWorkflow() *Workflow { @@ -986,20 +988,17 @@ func (x *NextResponse) GetWorkflow() *Workflow { } type RegisterAgentResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + AgentId int64 `protobuf:"varint,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` unknownFields protoimpl.UnknownFields - - AgentId int64 `protobuf:"varint,1,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + sizeCache protoimpl.SizeCache } func (x *RegisterAgentResponse) Reset() { *x = RegisterAgentResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_woodpecker_proto_msgTypes[17] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_woodpecker_proto_msgTypes[18] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *RegisterAgentResponse) String() string { @@ -1009,8 +1008,8 @@ func (x *RegisterAgentResponse) String() string { func (*RegisterAgentResponse) ProtoMessage() {} func (x *RegisterAgentResponse) ProtoReflect() protoreflect.Message { - mi := &file_woodpecker_proto_msgTypes[17] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_woodpecker_proto_msgTypes[18] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1022,7 +1021,7 @@ func (x *RegisterAgentResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use RegisterAgentResponse.ProtoReflect.Descriptor instead. func (*RegisterAgentResponse) Descriptor() ([]byte, []int) { - return file_woodpecker_proto_rawDescGZIP(), []int{17} + return file_woodpecker_proto_rawDescGZIP(), []int{18} } func (x *RegisterAgentResponse) GetAgentId() int64 { @@ -1033,21 +1032,18 @@ func (x *RegisterAgentResponse) GetAgentId() int64 { } type AuthRequest struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + AgentToken string `protobuf:"bytes,1,opt,name=agent_token,json=agentToken,proto3" json:"agent_token,omitempty"` + AgentId int64 `protobuf:"varint,2,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` unknownFields protoimpl.UnknownFields - - AgentToken string `protobuf:"bytes,1,opt,name=agent_token,json=agentToken,proto3" json:"agent_token,omitempty"` - AgentId int64 `protobuf:"varint,2,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + sizeCache protoimpl.SizeCache } func (x *AuthRequest) Reset() { *x = AuthRequest{} - if protoimpl.UnsafeEnabled { - mi := &file_woodpecker_proto_msgTypes[18] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_woodpecker_proto_msgTypes[19] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AuthRequest) String() string { @@ -1057,8 +1053,8 @@ func (x *AuthRequest) String() string { func (*AuthRequest) ProtoMessage() {} func (x *AuthRequest) ProtoReflect() protoreflect.Message { - mi := &file_woodpecker_proto_msgTypes[18] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_woodpecker_proto_msgTypes[19] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1070,7 +1066,7 @@ func (x *AuthRequest) ProtoReflect() protoreflect.Message { // Deprecated: Use AuthRequest.ProtoReflect.Descriptor instead. func (*AuthRequest) Descriptor() ([]byte, []int) { - return file_woodpecker_proto_rawDescGZIP(), []int{18} + return file_woodpecker_proto_rawDescGZIP(), []int{19} } func (x *AuthRequest) GetAgentToken() string { @@ -1088,22 +1084,19 @@ func (x *AuthRequest) GetAgentId() int64 { } type AuthResponse struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` + AgentId int64 `protobuf:"varint,2,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` + AccessToken string `protobuf:"bytes,3,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` unknownFields protoimpl.UnknownFields - - Status string `protobuf:"bytes,1,opt,name=status,proto3" json:"status,omitempty"` - AgentId int64 `protobuf:"varint,2,opt,name=agent_id,json=agentId,proto3" json:"agent_id,omitempty"` - AccessToken string `protobuf:"bytes,3,opt,name=access_token,json=accessToken,proto3" json:"access_token,omitempty"` + sizeCache protoimpl.SizeCache } func (x *AuthResponse) Reset() { *x = AuthResponse{} - if protoimpl.UnsafeEnabled { - mi := &file_woodpecker_proto_msgTypes[19] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_woodpecker_proto_msgTypes[20] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *AuthResponse) String() string { @@ -1113,8 +1106,8 @@ func (x *AuthResponse) String() string { func (*AuthResponse) ProtoMessage() {} func (x *AuthResponse) ProtoReflect() protoreflect.Message { - mi := &file_woodpecker_proto_msgTypes[19] - if protoimpl.UnsafeEnabled && x != nil { + mi := &file_woodpecker_proto_msgTypes[20] + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -1126,7 +1119,7 @@ func (x *AuthResponse) ProtoReflect() protoreflect.Message { // Deprecated: Use AuthResponse.ProtoReflect.Descriptor instead. func (*AuthResponse) Descriptor() ([]byte, []int) { - return file_woodpecker_proto_rawDescGZIP(), []int{19} + return file_woodpecker_proto_rawDescGZIP(), []int{20} } func (x *AuthResponse) GetStatus() string { @@ -1212,22 +1205,34 @@ var file_woodpecker_proto_rawDesc = []byte{ 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x26, 0x0a, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x53, 0x74, 0x65, 0x70, 0x53, 0x74, 0x61, 0x74, - 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x39, 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2b, 0x0a, 0x08, 0x6c, 0x6f, 0x67, 0x45, 0x6e, 0x74, - 0x72, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x2e, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x08, 0x6c, 0x6f, 0x67, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x2d, 0x0a, 0x13, - 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x82, 0x01, 0x0a, 0x14, - 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, - 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x08, 0x63, 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x18, 0x0a, 0x07, - 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, - 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x65, 0x52, 0x05, 0x73, 0x74, 0x61, 0x74, 0x65, 0x22, 0x3d, 0x0a, 0x0a, 0x4c, 0x6f, 0x67, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2f, 0x0a, 0x0a, 0x6c, 0x6f, 0x67, 0x45, 0x6e, 0x74, + 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x2e, 0x4c, 0x6f, 0x67, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x0a, 0x6c, 0x6f, 0x67, + 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x22, 0x07, 0x0a, 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x22, 0x2d, 0x0a, 0x13, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, + 0x80, 0x02, 0x0a, 0x09, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x1a, 0x0a, + 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x61, 0x70, + 0x61, 0x63, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x08, 0x63, 0x61, 0x70, + 0x61, 0x63, 0x69, 0x74, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x61, 0x63, 0x6b, 0x65, 0x6e, 0x64, 0x12, + 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x0c, 0x63, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x22, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, + 0x6f, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x0c, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4c, 0x61, 0x62, 0x65, 0x6c, + 0x73, 0x1a, 0x3f, 0x0a, 0x11, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x4c, 0x61, 0x62, 0x65, 0x6c, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x22, 0x3c, 0x0a, 0x14, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x65, 0x72, 0x41, 0x67, + 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x69, 0x6e, + 0x66, 0x6f, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2e, 0x41, 0x67, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x22, 0x5b, 0x0a, 0x0f, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x67, 0x72, 0x70, 0x63, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0b, 0x67, 0x72, 0x70, 0x63, 0x56, @@ -1311,8 +1316,8 @@ func file_woodpecker_proto_rawDescGZIP() []byte { return file_woodpecker_proto_rawDescData } -var file_woodpecker_proto_msgTypes = make([]protoimpl.MessageInfo, 21) -var file_woodpecker_proto_goTypes = []interface{}{ +var file_woodpecker_proto_msgTypes = make([]protoimpl.MessageInfo, 23) +var file_woodpecker_proto_goTypes = []any{ (*StepState)(nil), // 0: proto.StepState (*WorkflowState)(nil), // 1: proto.WorkflowState (*LogEntry)(nil), // 2: proto.LogEntry @@ -1327,51 +1332,55 @@ var file_woodpecker_proto_goTypes = []interface{}{ (*LogRequest)(nil), // 11: proto.LogRequest (*Empty)(nil), // 12: proto.Empty (*ReportHealthRequest)(nil), // 13: proto.ReportHealthRequest - (*RegisterAgentRequest)(nil), // 14: proto.RegisterAgentRequest - (*VersionResponse)(nil), // 15: proto.VersionResponse - (*NextResponse)(nil), // 16: proto.NextResponse - (*RegisterAgentResponse)(nil), // 17: proto.RegisterAgentResponse - (*AuthRequest)(nil), // 18: proto.AuthRequest - (*AuthResponse)(nil), // 19: proto.AuthResponse - nil, // 20: proto.Filter.LabelsEntry + (*AgentInfo)(nil), // 14: proto.AgentInfo + (*RegisterAgentRequest)(nil), // 15: proto.RegisterAgentRequest + (*VersionResponse)(nil), // 16: proto.VersionResponse + (*NextResponse)(nil), // 17: proto.NextResponse + (*RegisterAgentResponse)(nil), // 18: proto.RegisterAgentResponse + (*AuthRequest)(nil), // 19: proto.AuthRequest + (*AuthResponse)(nil), // 20: proto.AuthResponse + nil, // 21: proto.Filter.LabelsEntry + nil, // 22: proto.AgentInfo.CustomLabelsEntry } var file_woodpecker_proto_depIdxs = []int32{ - 20, // 0: proto.Filter.labels:type_name -> proto.Filter.LabelsEntry + 21, // 0: proto.Filter.labels:type_name -> proto.Filter.LabelsEntry 3, // 1: proto.NextRequest.filter:type_name -> proto.Filter 1, // 2: proto.InitRequest.state:type_name -> proto.WorkflowState 1, // 3: proto.DoneRequest.state:type_name -> proto.WorkflowState 0, // 4: proto.UpdateRequest.state:type_name -> proto.StepState - 2, // 5: proto.LogRequest.logEntry:type_name -> proto.LogEntry - 4, // 6: proto.NextResponse.workflow:type_name -> proto.Workflow - 12, // 7: proto.Woodpecker.Version:input_type -> proto.Empty - 5, // 8: proto.Woodpecker.Next:input_type -> proto.NextRequest - 6, // 9: proto.Woodpecker.Init:input_type -> proto.InitRequest - 7, // 10: proto.Woodpecker.Wait:input_type -> proto.WaitRequest - 8, // 11: proto.Woodpecker.Done:input_type -> proto.DoneRequest - 9, // 12: proto.Woodpecker.Extend:input_type -> proto.ExtendRequest - 10, // 13: proto.Woodpecker.Update:input_type -> proto.UpdateRequest - 11, // 14: proto.Woodpecker.Log:input_type -> proto.LogRequest - 14, // 15: proto.Woodpecker.RegisterAgent:input_type -> proto.RegisterAgentRequest - 12, // 16: proto.Woodpecker.UnregisterAgent:input_type -> proto.Empty - 13, // 17: proto.Woodpecker.ReportHealth:input_type -> proto.ReportHealthRequest - 18, // 18: proto.WoodpeckerAuth.Auth:input_type -> proto.AuthRequest - 15, // 19: proto.Woodpecker.Version:output_type -> proto.VersionResponse - 16, // 20: proto.Woodpecker.Next:output_type -> proto.NextResponse - 12, // 21: proto.Woodpecker.Init:output_type -> proto.Empty - 12, // 22: proto.Woodpecker.Wait:output_type -> proto.Empty - 12, // 23: proto.Woodpecker.Done:output_type -> proto.Empty - 12, // 24: proto.Woodpecker.Extend:output_type -> proto.Empty - 12, // 25: proto.Woodpecker.Update:output_type -> proto.Empty - 12, // 26: proto.Woodpecker.Log:output_type -> proto.Empty - 17, // 27: proto.Woodpecker.RegisterAgent:output_type -> proto.RegisterAgentResponse - 12, // 28: proto.Woodpecker.UnregisterAgent:output_type -> proto.Empty - 12, // 29: proto.Woodpecker.ReportHealth:output_type -> proto.Empty - 19, // 30: proto.WoodpeckerAuth.Auth:output_type -> proto.AuthResponse - 19, // [19:31] is the sub-list for method output_type - 7, // [7:19] is the sub-list for method input_type - 7, // [7:7] is the sub-list for extension type_name - 7, // [7:7] is the sub-list for extension extendee - 0, // [0:7] is the sub-list for field type_name + 2, // 5: proto.LogRequest.logEntries:type_name -> proto.LogEntry + 22, // 6: proto.AgentInfo.customLabels:type_name -> proto.AgentInfo.CustomLabelsEntry + 14, // 7: proto.RegisterAgentRequest.info:type_name -> proto.AgentInfo + 4, // 8: proto.NextResponse.workflow:type_name -> proto.Workflow + 12, // 9: proto.Woodpecker.Version:input_type -> proto.Empty + 5, // 10: proto.Woodpecker.Next:input_type -> proto.NextRequest + 6, // 11: proto.Woodpecker.Init:input_type -> proto.InitRequest + 7, // 12: proto.Woodpecker.Wait:input_type -> proto.WaitRequest + 8, // 13: proto.Woodpecker.Done:input_type -> proto.DoneRequest + 9, // 14: proto.Woodpecker.Extend:input_type -> proto.ExtendRequest + 10, // 15: proto.Woodpecker.Update:input_type -> proto.UpdateRequest + 11, // 16: proto.Woodpecker.Log:input_type -> proto.LogRequest + 15, // 17: proto.Woodpecker.RegisterAgent:input_type -> proto.RegisterAgentRequest + 12, // 18: proto.Woodpecker.UnregisterAgent:input_type -> proto.Empty + 13, // 19: proto.Woodpecker.ReportHealth:input_type -> proto.ReportHealthRequest + 19, // 20: proto.WoodpeckerAuth.Auth:input_type -> proto.AuthRequest + 16, // 21: proto.Woodpecker.Version:output_type -> proto.VersionResponse + 17, // 22: proto.Woodpecker.Next:output_type -> proto.NextResponse + 12, // 23: proto.Woodpecker.Init:output_type -> proto.Empty + 12, // 24: proto.Woodpecker.Wait:output_type -> proto.Empty + 12, // 25: proto.Woodpecker.Done:output_type -> proto.Empty + 12, // 26: proto.Woodpecker.Extend:output_type -> proto.Empty + 12, // 27: proto.Woodpecker.Update:output_type -> proto.Empty + 12, // 28: proto.Woodpecker.Log:output_type -> proto.Empty + 18, // 29: proto.Woodpecker.RegisterAgent:output_type -> proto.RegisterAgentResponse + 12, // 30: proto.Woodpecker.UnregisterAgent:output_type -> proto.Empty + 12, // 31: proto.Woodpecker.ReportHealth:output_type -> proto.Empty + 20, // 32: proto.WoodpeckerAuth.Auth:output_type -> proto.AuthResponse + 21, // [21:33] is the sub-list for method output_type + 9, // [9:21] is the sub-list for method input_type + 9, // [9:9] is the sub-list for extension type_name + 9, // [9:9] is the sub-list for extension extendee + 0, // [0:9] is the sub-list for field type_name } func init() { file_woodpecker_proto_init() } @@ -1379,255 +1388,13 @@ func file_woodpecker_proto_init() { if File_woodpecker_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_woodpecker_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*StepState); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_woodpecker_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WorkflowState); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_woodpecker_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LogEntry); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_woodpecker_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Filter); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_woodpecker_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Workflow); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_woodpecker_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NextRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_woodpecker_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*InitRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_woodpecker_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*WaitRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_woodpecker_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*DoneRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_woodpecker_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ExtendRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_woodpecker_proto_msgTypes[10].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*UpdateRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_woodpecker_proto_msgTypes[11].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*LogRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_woodpecker_proto_msgTypes[12].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Empty); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_woodpecker_proto_msgTypes[13].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*ReportHealthRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_woodpecker_proto_msgTypes[14].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RegisterAgentRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_woodpecker_proto_msgTypes[15].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*VersionResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_woodpecker_proto_msgTypes[16].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*NextResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_woodpecker_proto_msgTypes[17].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*RegisterAgentResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_woodpecker_proto_msgTypes[18].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AuthRequest); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_woodpecker_proto_msgTypes[19].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*AuthResponse); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_woodpecker_proto_rawDesc, NumEnums: 0, - NumMessages: 21, + NumMessages: 23, NumExtensions: 0, NumServices: 2, }, diff --git a/pipeline/rpc/proto/woodpecker.proto b/pipeline/rpc/proto/woodpecker.proto index 8fc577586..f9fef191b 100644 --- a/pipeline/rpc/proto/woodpecker.proto +++ b/pipeline/rpc/proto/woodpecker.proto @@ -106,7 +106,7 @@ message UpdateRequest { } message LogRequest { - LogEntry logEntry = 1; + repeated LogEntry logEntries = 1; } message Empty { @@ -116,11 +116,16 @@ message ReportHealthRequest { string status = 1; } -message RegisterAgentRequest { +message AgentInfo { string platform = 1; int32 capacity = 2; string backend = 3; string version = 4; + map customLabels = 5; +} + +message RegisterAgentRequest { + AgentInfo info = 1; } // diff --git a/pipeline/rpc/proto/woodpecker_grpc.pb.go b/pipeline/rpc/proto/woodpecker_grpc.pb.go index 837ce74e0..fc0a478af 100644 --- a/pipeline/rpc/proto/woodpecker_grpc.pb.go +++ b/pipeline/rpc/proto/woodpecker_grpc.pb.go @@ -15,8 +15,8 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.3.0 -// - protoc v4.25.1 +// - protoc-gen-go-grpc v1.5.1 +// - protoc v5.28.3 // source: woodpecker.proto package proto @@ -30,8 +30,8 @@ import ( // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 +// Requires gRPC-Go v1.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 const ( Woodpecker_Version_FullMethodName = "/proto.Woodpecker/Version" @@ -50,6 +50,8 @@ const ( // WoodpeckerClient is the client API for Woodpecker service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// Woodpecker Server Service type WoodpeckerClient interface { Version(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VersionResponse, error) Next(ctx context.Context, in *NextRequest, opts ...grpc.CallOption) (*NextResponse, error) @@ -73,8 +75,9 @@ func NewWoodpeckerClient(cc grpc.ClientConnInterface) WoodpeckerClient { } func (c *woodpeckerClient) Version(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*VersionResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(VersionResponse) - err := c.cc.Invoke(ctx, Woodpecker_Version_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, Woodpecker_Version_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -82,8 +85,9 @@ func (c *woodpeckerClient) Version(ctx context.Context, in *Empty, opts ...grpc. } func (c *woodpeckerClient) Next(ctx context.Context, in *NextRequest, opts ...grpc.CallOption) (*NextResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(NextResponse) - err := c.cc.Invoke(ctx, Woodpecker_Next_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, Woodpecker_Next_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -91,8 +95,9 @@ func (c *woodpeckerClient) Next(ctx context.Context, in *NextRequest, opts ...gr } func (c *woodpeckerClient) Init(ctx context.Context, in *InitRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) - err := c.cc.Invoke(ctx, Woodpecker_Init_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, Woodpecker_Init_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -100,8 +105,9 @@ func (c *woodpeckerClient) Init(ctx context.Context, in *InitRequest, opts ...gr } func (c *woodpeckerClient) Wait(ctx context.Context, in *WaitRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) - err := c.cc.Invoke(ctx, Woodpecker_Wait_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, Woodpecker_Wait_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -109,8 +115,9 @@ func (c *woodpeckerClient) Wait(ctx context.Context, in *WaitRequest, opts ...gr } func (c *woodpeckerClient) Done(ctx context.Context, in *DoneRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) - err := c.cc.Invoke(ctx, Woodpecker_Done_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, Woodpecker_Done_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -118,8 +125,9 @@ func (c *woodpeckerClient) Done(ctx context.Context, in *DoneRequest, opts ...gr } func (c *woodpeckerClient) Extend(ctx context.Context, in *ExtendRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) - err := c.cc.Invoke(ctx, Woodpecker_Extend_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, Woodpecker_Extend_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -127,8 +135,9 @@ func (c *woodpeckerClient) Extend(ctx context.Context, in *ExtendRequest, opts . } func (c *woodpeckerClient) Update(ctx context.Context, in *UpdateRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) - err := c.cc.Invoke(ctx, Woodpecker_Update_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, Woodpecker_Update_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -136,8 +145,9 @@ func (c *woodpeckerClient) Update(ctx context.Context, in *UpdateRequest, opts . } func (c *woodpeckerClient) Log(ctx context.Context, in *LogRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) - err := c.cc.Invoke(ctx, Woodpecker_Log_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, Woodpecker_Log_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -145,8 +155,9 @@ func (c *woodpeckerClient) Log(ctx context.Context, in *LogRequest, opts ...grpc } func (c *woodpeckerClient) RegisterAgent(ctx context.Context, in *RegisterAgentRequest, opts ...grpc.CallOption) (*RegisterAgentResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(RegisterAgentResponse) - err := c.cc.Invoke(ctx, Woodpecker_RegisterAgent_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, Woodpecker_RegisterAgent_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -154,8 +165,9 @@ func (c *woodpeckerClient) RegisterAgent(ctx context.Context, in *RegisterAgentR } func (c *woodpeckerClient) UnregisterAgent(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) - err := c.cc.Invoke(ctx, Woodpecker_UnregisterAgent_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, Woodpecker_UnregisterAgent_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -163,8 +175,9 @@ func (c *woodpeckerClient) UnregisterAgent(ctx context.Context, in *Empty, opts } func (c *woodpeckerClient) ReportHealth(ctx context.Context, in *ReportHealthRequest, opts ...grpc.CallOption) (*Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(Empty) - err := c.cc.Invoke(ctx, Woodpecker_ReportHealth_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, Woodpecker_ReportHealth_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -173,7 +186,9 @@ func (c *woodpeckerClient) ReportHealth(ctx context.Context, in *ReportHealthReq // WoodpeckerServer is the server API for Woodpecker service. // All implementations must embed UnimplementedWoodpeckerServer -// for forward compatibility +// for forward compatibility. +// +// Woodpecker Server Service type WoodpeckerServer interface { Version(context.Context, *Empty) (*VersionResponse, error) Next(context.Context, *NextRequest) (*NextResponse, error) @@ -189,9 +204,12 @@ type WoodpeckerServer interface { mustEmbedUnimplementedWoodpeckerServer() } -// UnimplementedWoodpeckerServer must be embedded to have forward compatible implementations. -type UnimplementedWoodpeckerServer struct { -} +// UnimplementedWoodpeckerServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedWoodpeckerServer struct{} func (UnimplementedWoodpeckerServer) Version(context.Context, *Empty) (*VersionResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Version not implemented") @@ -227,6 +245,7 @@ func (UnimplementedWoodpeckerServer) ReportHealth(context.Context, *ReportHealth return nil, status.Errorf(codes.Unimplemented, "method ReportHealth not implemented") } func (UnimplementedWoodpeckerServer) mustEmbedUnimplementedWoodpeckerServer() {} +func (UnimplementedWoodpeckerServer) testEmbeddedByValue() {} // UnsafeWoodpeckerServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to WoodpeckerServer will @@ -236,6 +255,13 @@ type UnsafeWoodpeckerServer interface { } func RegisterWoodpeckerServer(s grpc.ServiceRegistrar, srv WoodpeckerServer) { + // If the following call pancis, it indicates UnimplementedWoodpeckerServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } s.RegisterService(&Woodpecker_ServiceDesc, srv) } @@ -513,8 +539,9 @@ func NewWoodpeckerAuthClient(cc grpc.ClientConnInterface) WoodpeckerAuthClient { } func (c *woodpeckerAuthClient) Auth(ctx context.Context, in *AuthRequest, opts ...grpc.CallOption) (*AuthResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(AuthResponse) - err := c.cc.Invoke(ctx, WoodpeckerAuth_Auth_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, WoodpeckerAuth_Auth_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -523,20 +550,24 @@ func (c *woodpeckerAuthClient) Auth(ctx context.Context, in *AuthRequest, opts . // WoodpeckerAuthServer is the server API for WoodpeckerAuth service. // All implementations must embed UnimplementedWoodpeckerAuthServer -// for forward compatibility +// for forward compatibility. type WoodpeckerAuthServer interface { Auth(context.Context, *AuthRequest) (*AuthResponse, error) mustEmbedUnimplementedWoodpeckerAuthServer() } -// UnimplementedWoodpeckerAuthServer must be embedded to have forward compatible implementations. -type UnimplementedWoodpeckerAuthServer struct { -} +// UnimplementedWoodpeckerAuthServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedWoodpeckerAuthServer struct{} func (UnimplementedWoodpeckerAuthServer) Auth(context.Context, *AuthRequest) (*AuthResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Auth not implemented") } func (UnimplementedWoodpeckerAuthServer) mustEmbedUnimplementedWoodpeckerAuthServer() {} +func (UnimplementedWoodpeckerAuthServer) testEmbeddedByValue() {} // UnsafeWoodpeckerAuthServer may be embedded to opt out of forward compatibility for this service. // Use of this interface is not recommended, as added methods to WoodpeckerAuthServer will @@ -546,6 +577,13 @@ type UnsafeWoodpeckerAuthServer interface { } func RegisterWoodpeckerAuthServer(s grpc.ServiceRegistrar, srv WoodpeckerAuthServer) { + // If the following call pancis, it indicates UnimplementedWoodpeckerAuthServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } s.RegisterService(&WoodpeckerAuth_ServiceDesc, srv) } diff --git a/pipeline/shutdown.go b/pipeline/shutdown.go new file mode 100644 index 000000000..b9fd98384 --- /dev/null +++ b/pipeline/shutdown.go @@ -0,0 +1,48 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pipeline + +import ( + "context" + "sync" + "time" +) + +const shutdownTimeout = time.Second * 5 + +var ( + shutdownCtx context.Context + shutdownCtxCancel context.CancelFunc + shutdownCtxLock sync.Mutex +) + +func GetShutdownCtx() context.Context { + shutdownCtxLock.Lock() + defer shutdownCtxLock.Unlock() + if shutdownCtx == nil { + shutdownCtx, shutdownCtxCancel = context.WithTimeout(context.Background(), shutdownTimeout) + } + return shutdownCtx +} + +func CancelShutdown() { + shutdownCtxLock.Lock() + defer shutdownCtxLock.Unlock() + if shutdownCtxCancel == nil { + // we create an canceled context + shutdownCtx, shutdownCtxCancel = context.WithCancel(context.Background()) //nolint:forbidigo + } + shutdownCtxCancel() +} diff --git a/pipeline/tracer.go b/pipeline/tracer.go index fb9194be1..f48a7c562 100644 --- a/pipeline/tracer.go +++ b/pipeline/tracer.go @@ -16,7 +16,6 @@ package pipeline import ( "strconv" - "time" ) // Tracer handles process tracing. @@ -43,17 +42,9 @@ var DefaultTracer = TraceFunc(func(state *State) error { if state.Pipeline.Step.Environment == nil { return nil } - state.Pipeline.Step.Environment["CI_PIPELINE_STATUS"] = "success" state.Pipeline.Step.Environment["CI_PIPELINE_STARTED"] = strconv.FormatInt(state.Pipeline.Started, 10) - state.Pipeline.Step.Environment["CI_PIPELINE_FINISHED"] = strconv.FormatInt(time.Now().Unix(), 10) - state.Pipeline.Step.Environment["CI_STEP_STATUS"] = "success" state.Pipeline.Step.Environment["CI_STEP_STARTED"] = strconv.FormatInt(state.Pipeline.Started, 10) - state.Pipeline.Step.Environment["CI_STEP_FINISHED"] = strconv.FormatInt(time.Now().Unix(), 10) - if state.Pipeline.Error != nil { - state.Pipeline.Step.Environment["CI_PIPELINE_STATUS"] = "failure" - state.Pipeline.Step.Environment["CI_STEP_STATUS"] = "failure" - } return nil }) diff --git a/release-config.ts b/release-config.ts index 13d36fd55..cabbcba0a 100644 --- a/release-config.ts +++ b/release-config.ts @@ -1,3 +1,4 @@ export default { commentOnReleasedPullRequests: false, + skipLabels: ['skip-release', 'skip-changelog', 'regression', 'backport-done'], }; diff --git a/server/api/agent.go b/server/api/agent.go index 7b2d5f8d6..116fe5cac 100644 --- a/server/api/agent.go +++ b/server/api/agent.go @@ -15,19 +15,21 @@ package api import ( - "encoding/base32" "net/http" "strconv" "github.com/gin-gonic/gin" - "github.com/gorilla/securecookie" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/router/middleware/session" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) +// +// Global Agents. +// + // GetAgents // // @Summary List agents @@ -50,14 +52,14 @@ func GetAgents(c *gin.Context) { // GetAgent // // @Summary Get an agent -// @Router /agents/{agent} [get] +// @Router /agents/{agent_id} [get] // @Produce json // @Success 200 {object} Agent // @Tags Agents // @Param Authorization header string true "Insert your personal access token" default(Bearer ) -// @Param agent path int true "the agent's id" +// @Param agent_id path int true "the agent's id" func GetAgent(c *gin.Context) { - agentID, err := strconv.ParseInt(c.Param("agent"), 10, 64) + agentID, err := strconv.ParseInt(c.Param("agent_id"), 10, 64) if err != nil { _ = c.AbortWithError(http.StatusBadRequest, err) return @@ -74,14 +76,14 @@ func GetAgent(c *gin.Context) { // GetAgentTasks // // @Summary List agent tasks -// @Router /agents/{agent}/tasks [get] +// @Router /agents/{agent_id}/tasks [get] // @Produce json // @Success 200 {array} Task // @Tags Agents // @Param Authorization header string true "Insert your personal access token" default(Bearer ) -// @Param agent path int true "the agent's id" +// @Param agent_id path int true "the agent's id" func GetAgentTasks(c *gin.Context) { - agentID, err := strconv.ParseInt(c.Param("agent"), 10, 64) + agentID, err := strconv.ParseInt(c.Param("agent_id"), 10, 64) if err != nil { _ = c.AbortWithError(http.StatusBadRequest, err) return @@ -107,12 +109,12 @@ func GetAgentTasks(c *gin.Context) { // PatchAgent // // @Summary Update an agent -// @Router /agents/{agent} [patch] +// @Router /agents/{agent_id} [patch] // @Produce json // @Success 200 {object} Agent // @Tags Agents // @Param Authorization header string true "Insert your personal access token" default(Bearer ) -// @Param agent path int true "the agent's id" +// @Param agent_id path int true "the agent's id" // @Param agentData body Agent true "the agent's data" func PatchAgent(c *gin.Context) { _store := store.FromContext(c) @@ -124,7 +126,7 @@ func PatchAgent(c *gin.Context) { return } - agentID, err := strconv.ParseInt(c.Param("agent"), 10, 64) + agentID, err := strconv.ParseInt(c.Param("agent_id"), 10, 64) if err != nil { _ = c.AbortWithError(http.StatusBadRequest, err) return @@ -135,6 +137,8 @@ func PatchAgent(c *gin.Context) { handleDBError(c, err) return } + + // Update allowed fields agent.Name = in.Name agent.NoSchedule = in.NoSchedule if agent.NoSchedule { @@ -152,14 +156,14 @@ func PatchAgent(c *gin.Context) { // PostAgent // -// @Summary Create a new agent -// @Description Creates a new agent with a random token -// @Router /agents [post] -// @Produce json -// @Success 200 {object} Agent -// @Tags Agents -// @Param Authorization header string true "Insert your personal access token" default(Bearer ) -// @Param agent body Agent true "the agent's data (only 'name' and 'no_schedule' are read)" +// @Summary Create a new agent +// @Description Creates a new agent with a random token +// @Router /agents [post] +// @Produce json +// @Success 200 {object} Agent +// @Tags Agents +// @Param Authorization header string true "Insert your personal access token" default(Bearer ) +// @Param agent body Agent true "the agent's data (only 'name' and 'no_schedule' are read)" func PostAgent(c *gin.Context) { in := &model.Agent{} err := c.Bind(in) @@ -172,11 +176,10 @@ func PostAgent(c *gin.Context) { agent := &model.Agent{ Name: in.Name, - NoSchedule: in.NoSchedule, OwnerID: user.ID, - Token: base32.StdEncoding.EncodeToString( - securecookie.GenerateRandomKey(32), - ), + OrgID: model.IDNotSet, + NoSchedule: in.NoSchedule, + Token: model.GenerateNewAgentToken(), } if err = store.FromContext(c).AgentCreate(agent); err != nil { c.String(http.StatusInternalServerError, err.Error()) @@ -188,16 +191,16 @@ func PostAgent(c *gin.Context) { // DeleteAgent // // @Summary Delete an agent -// @Router /agents/{agent} [delete] +// @Router /agents/{agent_id} [delete] // @Produce plain // @Success 200 // @Tags Agents // @Param Authorization header string true "Insert your personal access token" default(Bearer ) -// @Param agent path int true "the agent's id" +// @Param agent_id path int true "the agent's id" func DeleteAgent(c *gin.Context) { _store := store.FromContext(c) - agentID, err := strconv.ParseInt(c.Param("agent"), 10, 64) + agentID, err := strconv.ParseInt(c.Param("agent_id"), 10, 64) if err != nil { _ = c.AbortWithError(http.StatusBadRequest, err) return @@ -227,3 +230,179 @@ func DeleteAgent(c *gin.Context) { } c.Status(http.StatusNoContent) } + +// +// Org/User Agents. +// + +// PostOrgAgent +// +// @Summary Create a new organization-scoped agent +// @Description Creates a new agent with a random token, scoped to the specified organization +// @Router /orgs/{org_id}/agents [post] +// @Produce json +// @Success 200 {object} Agent +// @Tags Agents +// @Param Authorization header string true "Insert your personal access token" default(Bearer ) +// @Param org_id path int true "the organization's id" +// @Param agent body Agent true "the agent's data (only 'name' and 'no_schedule' are read)" +func PostOrgAgent(c *gin.Context) { + _store := store.FromContext(c) + user := session.User(c) + + orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64) + if err != nil { + c.String(http.StatusBadRequest, "Invalid organization ID") + return + } + + in := new(model.Agent) + err = c.Bind(in) + if err != nil { + c.String(http.StatusBadRequest, err.Error()) + return + } + + agent := &model.Agent{ + Name: in.Name, + OwnerID: user.ID, + OrgID: orgID, + NoSchedule: in.NoSchedule, + Token: model.GenerateNewAgentToken(), + } + + if err = _store.AgentCreate(agent); err != nil { + c.String(http.StatusInternalServerError, err.Error()) + return + } + + c.JSON(http.StatusOK, agent) +} + +// GetOrgAgents +// +// @Summary List agents for an organization +// @Router /orgs/{org_id}/agents [get] +// @Produce json +// @Success 200 {array} Agent +// @Tags Agents +// @Param Authorization header string true "Insert your personal access token" default(Bearer ) +// @Param org_id path int true "the organization's id" +// @Param page query int false "for response pagination, page offset number" default(1) +// @Param perPage query int false "for response pagination, max items per page" default(50) +func GetOrgAgents(c *gin.Context) { + _store := store.FromContext(c) + org := session.Org(c) + + agents, err := _store.AgentListForOrg(org.ID, session.Pagination(c)) + if err != nil { + c.String(http.StatusInternalServerError, "Error getting agent list. %s", err) + return + } + + c.JSON(http.StatusOK, agents) +} + +// PatchOrgAgent +// +// @Summary Update an organization-scoped agent +// @Router /orgs/{org_id}/agents/{agent_id} [patch] +// @Produce json +// @Success 200 {object} Agent +// @Tags Agents +// @Param Authorization header string true "Insert your personal access token" default(Bearer ) +// @Param org_id path int true "the organization's id" +// @Param agent_id path int true "the agent's id" +// @Param agent body Agent true "the agent's updated data" +func PatchOrgAgent(c *gin.Context) { + _store := store.FromContext(c) + org := session.Org(c) + + agentID, err := strconv.ParseInt(c.Param("agent_id"), 10, 64) + if err != nil { + c.String(http.StatusBadRequest, "Invalid agent ID") + return + } + + agent, err := _store.AgentFind(agentID) + if err != nil { + c.String(http.StatusNotFound, "Agent not found") + return + } + + if agent.OrgID != org.ID { + c.String(http.StatusBadRequest, "Agent does not belong to this organization") + return + } + + in := new(model.Agent) + if err := c.Bind(in); err != nil { + c.String(http.StatusBadRequest, err.Error()) + return + } + + // Update allowed fields + agent.Name = in.Name + agent.NoSchedule = in.NoSchedule + if agent.NoSchedule { + server.Config.Services.Queue.KickAgentWorkers(agent.ID) + } + + if err := _store.AgentUpdate(agent); err != nil { + c.String(http.StatusInternalServerError, err.Error()) + return + } + + c.JSON(http.StatusOK, agent) +} + +// DeleteOrgAgent +// +// @Summary Delete an organization-scoped agent +// @Router /orgs/{org_id}/agents/{agent_id} [delete] +// @Produce plain +// @Success 204 +// @Tags Agents +// @Param Authorization header string true "Insert your personal access token" default(Bearer ) +// @Param org_id path int true "the organization's id" +// @Param agent_id path int true "the agent's id" +func DeleteOrgAgent(c *gin.Context) { + _store := store.FromContext(c) + org := session.Org(c) + + agentID, err := strconv.ParseInt(c.Param("agent_id"), 10, 64) + if err != nil { + c.String(http.StatusBadRequest, "Invalid agent ID") + return + } + + agent, err := _store.AgentFind(agentID) + if err != nil { + c.String(http.StatusNotFound, "Agent not found") + return + } + + if agent.OrgID != org.ID { + c.String(http.StatusBadRequest, "Agent does not belong to this organization") + return + } + + // Check if the agent has any running tasks + info := server.Config.Services.Queue.Info(c) + for _, task := range info.Running { + if task.AgentID == agent.ID { + c.String(http.StatusConflict, "Agent has running tasks") + return + } + } + + // Kick workers to remove the agent from the queue + server.Config.Services.Queue.KickAgentWorkers(agent.ID) + + if err := _store.AgentDelete(agent); err != nil { + c.String(http.StatusInternalServerError, "Error deleting agent. %s", err) + return + } + + c.Status(http.StatusNoContent) +} diff --git a/server/api/agent_test.go b/server/api/agent_test.go new file mode 100644 index 000000000..82d5e7c3f --- /dev/null +++ b/server/api/agent_test.go @@ -0,0 +1,275 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package api + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/queue" + queue_mocks "go.woodpecker-ci.org/woodpecker/v3/server/queue/mocks" + mocks_manager "go.woodpecker-ci.org/woodpecker/v3/server/services/mocks" + store_mocks "go.woodpecker-ci.org/woodpecker/v3/server/store/mocks" + "go.woodpecker-ci.org/woodpecker/v3/server/store/types" +) + +var fakeAgent = &model.Agent{ + ID: 1, + Name: "test-agent", + OwnerID: 1, + NoSchedule: false, +} + +func TestGetAgents(t *testing.T) { + gin.SetMode(gin.TestMode) + + t.Run("should get agents", func(t *testing.T) { + agents := []*model.Agent{fakeAgent} + + mockStore := store_mocks.NewStore(t) + mockStore.On("AgentList", mock.Anything).Return(agents, nil) + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Set("store", mockStore) + + GetAgents(c) + c.Writer.WriteHeaderNow() + + mockStore.AssertCalled(t, "AgentList", mock.Anything) + assert.Equal(t, http.StatusOK, w.Code) + + var response []*model.Agent + err := json.Unmarshal(w.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, agents, response) + }) +} + +func TestGetAgent(t *testing.T) { + gin.SetMode(gin.TestMode) + + t.Run("should get agent", func(t *testing.T) { + mockStore := store_mocks.NewStore(t) + mockStore.On("AgentFind", int64(1)).Return(fakeAgent, nil) + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Set("store", mockStore) + c.Params = gin.Params{{Key: "agent_id", Value: "1"}} + + GetAgent(c) + c.Writer.WriteHeaderNow() + + mockStore.AssertCalled(t, "AgentFind", int64(1)) + assert.Equal(t, http.StatusOK, w.Code) + + var response model.Agent + err := json.Unmarshal(w.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, fakeAgent, &response) + }) + + t.Run("should return bad request for invalid agent id", func(t *testing.T) { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Params = gin.Params{{Key: "agent_id", Value: "invalid"}} + + GetAgent(c) + c.Writer.WriteHeaderNow() + + assert.Equal(t, http.StatusBadRequest, w.Code) + }) + + t.Run("should return not found for non-existent agent", func(t *testing.T) { + mockStore := store_mocks.NewStore(t) + mockStore.On("AgentFind", int64(2)).Return((*model.Agent)(nil), types.RecordNotExist) + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Set("store", mockStore) + c.Params = gin.Params{{Key: "agent_id", Value: "2"}} + + GetAgent(c) + c.Writer.WriteHeaderNow() + + assert.Equal(t, http.StatusNotFound, w.Code) + }) +} + +func TestPatchAgent(t *testing.T) { + gin.SetMode(gin.TestMode) + + t.Run("should update agent", func(t *testing.T) { + updatedAgent := *fakeAgent + updatedAgent.Name = "updated-agent" + + mockStore := store_mocks.NewStore(t) + mockStore.On("AgentFind", int64(1)).Return(fakeAgent, nil) + mockStore.On("AgentUpdate", mock.AnythingOfType("*model.Agent")).Return(nil) + + mockManager := mocks_manager.NewManager(t) + server.Config.Services.Manager = mockManager + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Set("store", mockStore) + c.Params = gin.Params{{Key: "agent_id", Value: "1"}} + c.Request, _ = http.NewRequest(http.MethodPatch, "/", strings.NewReader(`{"name":"updated-agent"}`)) + c.Request.Header.Set("Content-Type", "application/json") + + PatchAgent(c) + c.Writer.WriteHeaderNow() + + mockStore.AssertCalled(t, "AgentFind", int64(1)) + mockStore.AssertCalled(t, "AgentUpdate", mock.AnythingOfType("*model.Agent")) + assert.Equal(t, http.StatusOK, w.Code) + + var response model.Agent + err := json.Unmarshal(w.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "updated-agent", response.Name) + }) +} + +func TestPostAgent(t *testing.T) { + gin.SetMode(gin.TestMode) + + t.Run("should create agent", func(t *testing.T) { + newAgent := &model.Agent{ + Name: "new-agent", + NoSchedule: false, + } + + mockStore := store_mocks.NewStore(t) + mockStore.On("AgentCreate", mock.AnythingOfType("*model.Agent")).Return(nil) + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Set("store", mockStore) + c.Set("user", &model.User{ID: 1}) + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader(`{"name":"new-agent"}`)) + c.Request.Header.Set("Content-Type", "application/json") + + PostAgent(c) + c.Writer.WriteHeaderNow() + + mockStore.AssertCalled(t, "AgentCreate", mock.AnythingOfType("*model.Agent")) + assert.Equal(t, http.StatusOK, w.Code) + + var response model.Agent + err := json.Unmarshal(w.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, newAgent.Name, response.Name) + assert.NotEmpty(t, response.Token) + }) +} + +func TestDeleteAgent(t *testing.T) { + gin.SetMode(gin.TestMode) + + t.Run("should delete agent", func(t *testing.T) { + mockStore := store_mocks.NewStore(t) + mockStore.On("AgentFind", int64(1)).Return(fakeAgent, nil) + mockStore.On("AgentDelete", mock.AnythingOfType("*model.Agent")).Return(nil) + + mockManager := mocks_manager.NewManager(t) + server.Config.Services.Manager = mockManager + + mockQueue := queue_mocks.NewQueue(t) + mockQueue.On("Info", mock.Anything).Return(queue.InfoT{}) + mockQueue.On("KickAgentWorkers", int64(1)).Return() + server.Config.Services.Queue = mockQueue + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Set("store", mockStore) + c.Params = gin.Params{{Key: "agent_id", Value: "1"}} + + DeleteAgent(c) + c.Writer.WriteHeaderNow() + + mockStore.AssertCalled(t, "AgentFind", int64(1)) + mockStore.AssertCalled(t, "AgentDelete", mock.AnythingOfType("*model.Agent")) + mockQueue.AssertCalled(t, "KickAgentWorkers", int64(1)) + assert.Equal(t, http.StatusNoContent, w.Code) + }) + + t.Run("should not delete agent with running tasks", func(t *testing.T) { + mockStore := store_mocks.NewStore(t) + mockStore.On("AgentFind", int64(1)).Return(fakeAgent, nil) + + mockManager := mocks_manager.NewManager(t) + server.Config.Services.Manager = mockManager + + mockQueue := queue_mocks.NewQueue(t) + mockQueue.On("Info", mock.Anything).Return(queue.InfoT{ + Running: []*model.Task{{AgentID: 1}}, + }) + server.Config.Services.Queue = mockQueue + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Set("store", mockStore) + c.Params = gin.Params{{Key: "agent_id", Value: "1"}} + + DeleteAgent(c) + c.Writer.WriteHeaderNow() + + mockStore.AssertCalled(t, "AgentFind", int64(1)) + mockStore.AssertNotCalled(t, "AgentDelete", mock.Anything) + assert.Equal(t, http.StatusConflict, w.Code) + }) +} + +func TestPostOrgAgent(t *testing.T) { + gin.SetMode(gin.TestMode) + + t.Run("create org agent should succeed", func(t *testing.T) { + mockStore := store_mocks.NewStore(t) + mockStore.On("AgentCreate", mock.AnythingOfType("*model.Agent")).Return(nil) + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Set("store", mockStore) + + // Set up a non-admin user + c.Set("user", &model.User{ + ID: 1, + Admin: false, + }) + + c.Params = gin.Params{{Key: "org_id", Value: "1"}} + c.Request, _ = http.NewRequest(http.MethodPost, "/", strings.NewReader(`{"name":"new-agent"}`)) + c.Request.Header.Set("Content-Type", "application/json") + + PostOrgAgent(c) + c.Writer.WriteHeaderNow() + + assert.Equal(t, http.StatusOK, w.Code) + + // Ensure an agent was created + mockStore.AssertCalled(t, "AgentCreate", mock.AnythingOfType("*model.Agent")) + }) +} diff --git a/server/api/badge.go b/server/api/badge.go index a06fd8bb2..74378a7b5 100644 --- a/server/api/badge.go +++ b/server/api/badge.go @@ -25,12 +25,12 @@ import ( "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/badges" - "go.woodpecker-ci.org/woodpecker/v2/server/ccmenu" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" - "go.woodpecker-ci.org/woodpecker/v2/server/store/types" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/badges" + "go.woodpecker-ci.org/woodpecker/v3/server/ccmenu" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/store/types" ) // GetBadge @@ -72,7 +72,7 @@ func GetBadge(c *gin.Context) { branch = repo.Branch } - pipeline, err := _store.GetPipelineLast(repo, branch) + pipeline, err := _store.GetPipelineBadge(repo, branch) if err != nil { if !errors.Is(err, types.RecordNotExist) { log.Warn().Err(err).Msg("could not get last pipeline for badge") diff --git a/server/api/cron.go b/server/api/cron.go index 7a3909ec9..78b05b6fe 100644 --- a/server/api/cron.go +++ b/server/api/cron.go @@ -22,12 +22,12 @@ import ( "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server" - cronScheduler "go.woodpecker-ci.org/woodpecker/v2/server/cron" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/pipeline" - "go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server" + cronScheduler "go.woodpecker-ci.org/woodpecker/v3/server/cron" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/pipeline" + "go.woodpecker-ci.org/woodpecker/v3/server/router/middleware/session" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) // GetCron diff --git a/server/api/forge.go b/server/api/forge.go index cf82e3aa5..11981bc39 100644 --- a/server/api/forge.go +++ b/server/api/forge.go @@ -20,9 +20,9 @@ import ( "github.com/gin-gonic/gin" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/router/middleware/session" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) // GetForges @@ -144,14 +144,14 @@ func PatchForge(c *gin.Context) { // PostForge // -// @Summary Create a new forge -// @Description Creates a new forge with a random token -// @Router /forges [post] -// @Produce json -// @Success 200 {object} Forge -// @Tags Forges -// @Param Authorization header string true "Insert your personal access token" default(Bearer ) -// @Param forge body Forge true "the forge's data (only 'name' and 'no_schedule' are read)" +// @Summary Create a new forge +// @Description Creates a new forge with a random token +// @Router /forges [post] +// @Produce json +// @Success 200 {object} Forge +// @Tags Forges +// @Param Authorization header string true "Insert your personal access token" default(Bearer ) +// @Param forge body Forge true "the forge's data (only 'name' and 'no_schedule' are read)" func PostForge(c *gin.Context) { in := &model.Forge{} err := c.Bind(in) diff --git a/server/api/global_registry.go b/server/api/global_registry.go index a9ffd9a89..5108a76c0 100644 --- a/server/api/global_registry.go +++ b/server/api/global_registry.go @@ -19,9 +19,9 @@ import ( "github.com/gin-gonic/gin" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/router/middleware/session" ) // GetGlobalRegistryList @@ -32,7 +32,7 @@ import ( // @Success 200 {array} Registry // @Tags Registries // @Param Authorization header string true "Insert your personal access token" default(Bearer ) -// @Param page query int false "for response pagination, page offset number" default(1) +// @Param page query int false "for response pagination, page offset number" default(1) // @Param perPage query int false "for response pagination, max items per page" default(50) func GetGlobalRegistryList(c *gin.Context) { registryService := server.Config.Services.Manager.RegistryService() @@ -57,7 +57,7 @@ func GetGlobalRegistryList(c *gin.Context) { // @Success 200 {object} Registry // @Tags Registries // @Param Authorization header string true "Insert your personal access token" default(Bearer ) -// @Param registry path string true "the registry's name" +// @Param registry path string true "the registry's name" func GetGlobalRegistry(c *gin.Context) { addr := c.Param("registry") registryService := server.Config.Services.Manager.RegistryService() @@ -76,8 +76,8 @@ func GetGlobalRegistry(c *gin.Context) { // @Produce json // @Success 200 {object} Registry // @Tags Registries -// @Param Authorization header string true "Insert your personal access token" default(Bearer ) -// @Param registry body Registry true "the registry object data" +// @Param Authorization header string true "Insert your personal access token" default(Bearer ) +// @Param registry body Registry true "the registry object data" func PostGlobalRegistry(c *gin.Context) { in := new(model.Registry) if err := c.Bind(in); err != nil { @@ -109,8 +109,8 @@ func PostGlobalRegistry(c *gin.Context) { // @Produce json // @Success 200 {object} Registry // @Tags Registries -// @Param Authorization header string true "Insert your personal access token" default(Bearer ) -// @Param registry path string true "the registry's name" +// @Param Authorization header string true "Insert your personal access token" default(Bearer ) +// @Param registry path string true "the registry's name" // @Param registryData body Registry true "the registry's data" func PatchGlobalRegistry(c *gin.Context) { addr := c.Param("registry") @@ -158,7 +158,7 @@ func PatchGlobalRegistry(c *gin.Context) { // @Success 204 // @Tags Registries // @Param Authorization header string true "Insert your personal access token" default(Bearer ) -// @Param registry path string true "the registry's name" +// @Param registry path string true "the registry's name" func DeleteGlobalRegistry(c *gin.Context) { addr := c.Param("registry") registryService := server.Config.Services.Manager.RegistryService() diff --git a/server/api/global_secret.go b/server/api/global_secret.go index 3ede60d7f..1bcc6a825 100644 --- a/server/api/global_secret.go +++ b/server/api/global_secret.go @@ -19,9 +19,9 @@ import ( "github.com/gin-gonic/gin" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/router/middleware/session" ) // GetGlobalSecretList diff --git a/server/api/helper.go b/server/api/helper.go index dbe6ce6e2..591e8f621 100644 --- a/server/api/helper.go +++ b/server/api/helper.go @@ -21,12 +21,12 @@ import ( "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/pipeline" - "go.woodpecker-ci.org/woodpecker/v2/server/store" - "go.woodpecker-ci.org/woodpecker/v2/server/store/types" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/pipeline" + "go.woodpecker-ci.org/woodpecker/v3/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/store/types" ) func handlePipelineErr(c *gin.Context, err error) { diff --git a/server/api/helper_test.go b/server/api/helper_test.go index 50721f9b3..4dd17b1b8 100644 --- a/server/api/helper_test.go +++ b/server/api/helper_test.go @@ -8,7 +8,7 @@ import ( "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/pipeline" + "go.woodpecker-ci.org/woodpecker/v3/server/pipeline" ) func TestHandlePipelineError(t *testing.T) { diff --git a/server/api/hook.go b/server/api/hook.go index 654acc93d..fa79ff04f 100644 --- a/server/api/hook.go +++ b/server/api/hook.go @@ -25,13 +25,13 @@ import ( "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/pipeline" - "go.woodpecker-ci.org/woodpecker/v2/server/store" - "go.woodpecker-ci.org/woodpecker/v2/shared/token" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/pipeline" + "go.woodpecker-ci.org/woodpecker/v3/server/store" + "go.woodpecker-ci.org/woodpecker/v3/shared/token" ) // GetQueueInfo @@ -250,11 +250,8 @@ func PostHook(c *gin.Context) { func getRepoFromToken(store store.Store, t *token.Token) (*model.Repo, error) { // try to get the repo by the repo-id repoID, err := strconv.ParseInt(t.Get("repo-id"), 10, 64) - if err == nil { - return store.GetRepo(repoID) + if err != nil { + return nil, err } - - // try to get the repo by the repo name or by its redirection - repoName := t.Get("text") - return store.GetRepoName(repoName) + return store.GetRepo(repoID) } diff --git a/server/api/hook_test.go b/server/api/hook_test.go index 681edf0c8..6620440f3 100644 --- a/server/api/hook_test.go +++ b/server/api/hook_test.go @@ -7,98 +7,90 @@ import ( "net/url" "testing" - "github.com/franela/goblin" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/api" - mocks_forge "go.woodpecker-ci.org/woodpecker/v2/server/forge/mocks" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - mocks_config_service "go.woodpecker-ci.org/woodpecker/v2/server/services/config/mocks" - mocks_services "go.woodpecker-ci.org/woodpecker/v2/server/services/mocks" - "go.woodpecker-ci.org/woodpecker/v2/server/services/permissions" - mocks_registry_service "go.woodpecker-ci.org/woodpecker/v2/server/services/registry/mocks" - mocks_secret_service "go.woodpecker-ci.org/woodpecker/v2/server/services/secret/mocks" - mocks_store "go.woodpecker-ci.org/woodpecker/v2/server/store/mocks" - "go.woodpecker-ci.org/woodpecker/v2/shared/token" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/api" + mocks_forge "go.woodpecker-ci.org/woodpecker/v3/server/forge/mocks" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + mocks_config_service "go.woodpecker-ci.org/woodpecker/v3/server/services/config/mocks" + mocks_services "go.woodpecker-ci.org/woodpecker/v3/server/services/mocks" + "go.woodpecker-ci.org/woodpecker/v3/server/services/permissions" + mocks_registry_service "go.woodpecker-ci.org/woodpecker/v3/server/services/registry/mocks" + mocks_secret_service "go.woodpecker-ci.org/woodpecker/v3/server/services/secret/mocks" + mocks_store "go.woodpecker-ci.org/woodpecker/v3/server/store/mocks" + "go.woodpecker-ci.org/woodpecker/v3/shared/token" ) func TestHook(t *testing.T) { gin.SetMode(gin.TestMode) - g := goblin.Goblin(t) - g.Describe("Hook", func() { - g.It("should handle a correct webhook payload", func() { - _manager := mocks_services.NewManager(t) - _forge := mocks_forge.NewForge(t) - _store := mocks_store.NewStore(t) - _configService := mocks_config_service.NewService(t) - _secretService := mocks_secret_service.NewService(t) - _registryService := mocks_registry_service.NewService(t) - server.Config.Services.Manager = _manager - server.Config.Permissions.Open = true - server.Config.Permissions.Orgs = permissions.NewOrgs(nil) - server.Config.Permissions.Admins = permissions.NewAdmins(nil) - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("store", _store) - user := &model.User{ - ID: 123, - } - repo := &model.Repo{ - ID: 123, - ForgeRemoteID: "123", - Owner: "owner", - Name: "name", - IsActive: true, - UserID: user.ID, - Hash: "secret-123-this-is-a-secret", - } - pipeline := &model.Pipeline{ - ID: 123, - RepoID: repo.ID, - Event: model.EventPush, - } + _manager := mocks_services.NewManager(t) + _forge := mocks_forge.NewForge(t) + _store := mocks_store.NewStore(t) + _configService := mocks_config_service.NewService(t) + _secretService := mocks_secret_service.NewService(t) + _registryService := mocks_registry_service.NewService(t) + server.Config.Services.Manager = _manager + server.Config.Permissions.Open = true + server.Config.Permissions.Orgs = permissions.NewOrgs(nil) + server.Config.Permissions.Admins = permissions.NewAdmins(nil) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Set("store", _store) + user := &model.User{ + ID: 123, + } + repo := &model.Repo{ + ID: 123, + ForgeRemoteID: "123", + Owner: "owner", + Name: "name", + IsActive: true, + UserID: user.ID, + Hash: "secret-123-this-is-a-secret", + } + pipeline := &model.Pipeline{ + ID: 123, + RepoID: repo.ID, + Event: model.EventPush, + } - repoToken := token.New(token.HookToken) - repoToken.Set("repo-id", fmt.Sprintf("%d", repo.ID)) - signedToken, err := repoToken.Sign("secret-123-this-is-a-secret") - if err != nil { - g.Fail(err) - } + repoToken := token.New(token.HookToken) + repoToken.Set("repo-id", fmt.Sprintf("%d", repo.ID)) + signedToken, err := repoToken.Sign("secret-123-this-is-a-secret") + assert.NoError(t, err) - header := http.Header{} - header.Set("Authorization", fmt.Sprintf("Bearer %s", signedToken)) - c.Request = &http.Request{ - Header: header, - URL: &url.URL{ - Scheme: "https", - }, - } + header := http.Header{} + header.Set("Authorization", fmt.Sprintf("Bearer %s", signedToken)) + c.Request = &http.Request{ + Header: header, + URL: &url.URL{ + Scheme: "https", + }, + } - _manager.On("ForgeFromRepo", repo).Return(_forge, nil) - _forge.On("Hook", mock.Anything, mock.Anything).Return(repo, pipeline, nil) - _store.On("GetRepo", repo.ID).Return(repo, nil) - _store.On("GetUser", user.ID).Return(user, nil) - _store.On("UpdateRepo", repo).Return(nil) - _store.On("CreatePipeline", mock.Anything).Return(nil) - _manager.On("ConfigServiceFromRepo", repo).Return(_configService) - _configService.On("Fetch", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil) - _forge.On("Netrc", mock.Anything, mock.Anything).Return(&model.Netrc{}, nil) - _store.On("GetPipelineLastBefore", mock.Anything, mock.Anything, mock.Anything).Return(nil, nil) - _manager.On("SecretServiceFromRepo", repo).Return(_secretService) - _secretService.On("SecretListPipeline", repo, mock.Anything, mock.Anything).Return(nil, nil) - _manager.On("RegistryServiceFromRepo", repo).Return(_registryService) - _registryService.On("RegistryListPipeline", repo, mock.Anything).Return(nil, nil) - _manager.On("EnvironmentService").Return(nil) - _store.On("DeletePipeline", mock.Anything).Return(nil) + _manager.On("ForgeFromRepo", repo).Return(_forge, nil) + _forge.On("Hook", mock.Anything, mock.Anything).Return(repo, pipeline, nil) + _store.On("GetRepo", repo.ID).Return(repo, nil) + _store.On("GetUser", user.ID).Return(user, nil) + _store.On("UpdateRepo", repo).Return(nil) + _store.On("CreatePipeline", mock.Anything).Return(nil) + _manager.On("ConfigServiceFromRepo", repo).Return(_configService) + _configService.On("Fetch", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil, nil) + _forge.On("Netrc", mock.Anything, mock.Anything).Return(&model.Netrc{}, nil) + _store.On("GetPipelineLastBefore", mock.Anything, mock.Anything, mock.Anything).Return(nil, nil) + _manager.On("SecretServiceFromRepo", repo).Return(_secretService) + _secretService.On("SecretListPipeline", repo, mock.Anything, mock.Anything).Return(nil, nil) + _manager.On("RegistryServiceFromRepo", repo).Return(_registryService) + _registryService.On("RegistryListPipeline", repo, mock.Anything).Return(nil, nil) + _manager.On("EnvironmentService").Return(nil) + _store.On("DeletePipeline", mock.Anything).Return(nil) - api.PostHook(c) + api.PostHook(c) - assert.Equal(g, http.StatusNoContent, c.Writer.Status()) - assert.Equal(g, "true", w.Header().Get("Pipeline-Filtered")) - }) - }) + assert.Equal(t, http.StatusNoContent, c.Writer.Status()) + assert.Equal(t, "true", w.Header().Get("Pipeline-Filtered")) } diff --git a/server/api/login.go b/server/api/login.go index ebe6cf7b3..06134b27d 100644 --- a/server/api/login.go +++ b/server/api/login.go @@ -27,14 +27,14 @@ import ( "github.com/gorilla/securecookie" "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" - "go.woodpecker-ci.org/woodpecker/v2/server/store/types" - "go.woodpecker-ci.org/woodpecker/v2/shared/httputil" - "go.woodpecker-ci.org/woodpecker/v2/shared/token" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/store/types" + "go.woodpecker-ci.org/woodpecker/v3/shared/httputil" + "go.woodpecker-ci.org/woodpecker/v3/shared/token" ) const stateTokenDuration = time.Minute * 5 @@ -159,13 +159,14 @@ func HandleAuth(c *gin.Context) { // create the user account user = &model.User{ - Login: userFromForge.Login, + ForgeID: forgeID, ForgeRemoteID: userFromForge.ForgeRemoteID, - Token: userFromForge.Token, - Secret: userFromForge.Secret, + Login: userFromForge.Login, + AccessToken: userFromForge.AccessToken, + RefreshToken: userFromForge.RefreshToken, + Expiry: userFromForge.Expiry, Email: userFromForge.Email, Avatar: userFromForge.Avatar, - ForgeID: forgeID, Hash: base32.StdEncoding.EncodeToString( securecookie.GenerateRandomKey(32), ), @@ -225,8 +226,8 @@ func HandleAuth(c *gin.Context) { } // update the user meta data and authorization data. - user.Token = userFromForge.Token - user.Secret = userFromForge.Secret + user.AccessToken = userFromForge.AccessToken + user.RefreshToken = userFromForge.RefreshToken user.Email = userFromForge.Email user.Avatar = userFromForge.Avatar user.ForgeID = forgeID @@ -297,54 +298,3 @@ func GetLogout(c *gin.Context) { httputil.DelCookie(c.Writer, c.Request, "user_last") c.Redirect(http.StatusSeeOther, server.Config.Server.RootPath+"/") } - -// TODO: remove in 3.0 -func DeprecatedGetLoginToken(c *gin.Context) { - _store := store.FromContext(c) - - _forge, err := server.Config.Services.Manager.ForgeByID(1) - if err != nil { - log.Error().Err(err).Msg("Cannot get main forge") - c.AbortWithStatus(http.StatusInternalServerError) - return - } - - in := &tokenPayload{} - err = c.Bind(in) - if err != nil { - _ = c.AbortWithError(http.StatusBadRequest, err) - return - } - - login, err := _forge.Auth(c, in.Access, in.Refresh) - if err != nil { - _ = c.AbortWithError(http.StatusUnauthorized, err) - return - } - - user, err := _store.GetUserLogin(login) - if err != nil { - handleDBError(c, err) - return - } - - exp := time.Now().Add(server.Config.Server.SessionExpires).Unix() - newToken := token.New(token.SessToken) - newToken.Set("user-id", strconv.FormatInt(user.ID, 10)) - tokenStr, err := newToken.SignExpires(user.Hash, exp) - if err != nil { - _ = c.AbortWithError(http.StatusInternalServerError, err) - return - } - - c.JSON(http.StatusOK, &tokenPayload{ - Access: tokenStr, - Expires: exp - time.Now().Unix(), - }) -} - -type tokenPayload struct { - Access string `json:"access_token,omitempty"` - Refresh string `json:"refresh_token,omitempty"` - Expires int64 `json:"expires_in,omitempty"` -} diff --git a/server/api/login_test.go b/server/api/login_test.go index f76dd721d..cca5c991d 100644 --- a/server/api/login_test.go +++ b/server/api/login_test.go @@ -9,366 +9,360 @@ import ( "testing" "time" - "github.com/franela/goblin" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/api" - mocks_forge "go.woodpecker-ci.org/woodpecker/v2/server/forge/mocks" - forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - mocks_services "go.woodpecker-ci.org/woodpecker/v2/server/services/mocks" - "go.woodpecker-ci.org/woodpecker/v2/server/services/permissions" - mocks_store "go.woodpecker-ci.org/woodpecker/v2/server/store/mocks" - "go.woodpecker-ci.org/woodpecker/v2/server/store/types" - "go.woodpecker-ci.org/woodpecker/v2/shared/token" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/api" + mocks_forge "go.woodpecker-ci.org/woodpecker/v3/server/forge/mocks" + forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + mocks_services "go.woodpecker-ci.org/woodpecker/v3/server/services/mocks" + "go.woodpecker-ci.org/woodpecker/v3/server/services/permissions" + mocks_store "go.woodpecker-ci.org/woodpecker/v3/server/store/mocks" + "go.woodpecker-ci.org/woodpecker/v3/server/store/types" + "go.woodpecker-ci.org/woodpecker/v3/shared/token" ) func TestHandleAuth(t *testing.T) { gin.SetMode(gin.TestMode) - g := goblin.Goblin(t) - g.Describe("Login", func() { - user := &model.User{ - ID: 1, - OrgID: 1, - ForgeID: 1, - ForgeRemoteID: "remote-id-1", - Login: "test", - Email: "test@example.com", - Admin: false, - } - org := &model.Org{ - ID: 1, - Name: user.Login, + user := &model.User{ + ID: 1, + OrgID: 1, + ForgeID: 1, + ForgeRemoteID: "remote-id-1", + Login: "test", + Email: "test@example.com", + Admin: false, + } + org := &model.Org{ + ID: 1, + Name: user.Login, + } + + server.Config.Server.SessionExpires = time.Hour + + t.Run("should handle errors from the callback", func(t *testing.T) { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + + query := url.Values{} + query.Set("error", "invalid_scope") + query.Set("error_description", "The requested scope is invalid, unknown, or malformed") + query.Set("error_uri", "https://developer.atlassian.com/cloud/jira/platform/rest/#api-group-OAuth2-ErrorHandling") + + c.Request = &http.Request{ + Header: make(http.Header), + Method: http.MethodGet, + URL: &url.URL{ + Scheme: "https", + Path: "/authorize", + RawQuery: query.Encode(), + }, } - server.Config.Server.SessionExpires = time.Hour + api.HandleAuth(c) - g.It("should handle errors from the callback", func() { - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) + assert.Equal(t, http.StatusSeeOther, c.Writer.Status()) + assert.Equal(t, fmt.Sprintf("/login?%s", query.Encode()), c.Writer.Header().Get("Location")) + }) - query := url.Values{} - query.Set("error", "invalid_scope") - query.Set("error_description", "The requested scope is invalid, unknown, or malformed") - query.Set("error_uri", "https://developer.atlassian.com/cloud/jira/platform/rest/#api-group-OAuth2-ErrorHandling") + t.Run("should fail if the state is wrong", func(t *testing.T) { + _manager := mocks_services.NewManager(t) + _store := mocks_store.NewStore(t) + server.Config.Services.Manager = _manager + server.Config.Permissions.Open = true + server.Config.Permissions.Orgs = permissions.NewOrgs(nil) + server.Config.Permissions.Admins = permissions.NewAdmins(nil) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Set("store", _store) - c.Request = &http.Request{ - Header: make(http.Header), - Method: http.MethodGet, - URL: &url.URL{ - Scheme: "https", - Path: "/authorize", - RawQuery: query.Encode(), - }, + query := url.Values{} + query.Set("code", "assumed_to_be_valid_code") + + wrongToken := token.New(token.OAuthStateToken) + wrongToken.Set("forge_id", "1") + signedWrongToken, _ := wrongToken.Sign("wrong_secret") + query.Set("state", signedWrongToken) + + c.Request = &http.Request{ + Header: make(http.Header), + URL: &url.URL{ + Scheme: "https", + RawQuery: query.Encode(), + }, + } + + api.HandleAuth(c) + + assert.Equal(t, http.StatusSeeOther, c.Writer.Status()) + assert.Equal(t, "/login?error=invalid_state", c.Writer.Header().Get("Location")) + }) + + t.Run("should redirect to forge login page", func(t *testing.T) { + _manager := mocks_services.NewManager(t) + _forge := mocks_forge.NewForge(t) + _store := mocks_store.NewStore(t) + server.Config.Services.Manager = _manager + server.Config.Permissions.Open = true + server.Config.Permissions.Orgs = permissions.NewOrgs(nil) + server.Config.Permissions.Admins = permissions.NewAdmins(nil) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Set("store", _store) + c.Request = &http.Request{ + Header: make(http.Header), + URL: &url.URL{ + Scheme: "https", + }, + } + + _manager.On("ForgeByID", int64(1)).Return(_forge, nil) + + forgeRedirectURL := "" + _forge.On("Login", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { + state, ok := args.Get(1).(*forge_types.OAuthRequest) + if ok { + forgeRedirectURL = fmt.Sprintf("https://my-awesome-forge.com/oauth/authorize?client_id=client-id&state=%s", state.State) } + }).Return(nil, func(context.Context, *forge_types.OAuthRequest) string { + return forgeRedirectURL + }, nil) - api.HandleAuth(c) + api.HandleAuth(c) - assert.Equal(g, http.StatusSeeOther, c.Writer.Status()) - assert.Equal(g, fmt.Sprintf("/login?%s", query.Encode()), c.Writer.Header().Get("Location")) - }) + assert.Equal(t, http.StatusSeeOther, c.Writer.Status()) + assert.Equal(t, forgeRedirectURL, c.Writer.Header().Get("Location")) + }) - g.It("should fail if the state is wrong", func() { - _manager := mocks_services.NewManager(t) - _store := mocks_store.NewStore(t) - server.Config.Services.Manager = _manager - server.Config.Permissions.Open = true - server.Config.Permissions.Orgs = permissions.NewOrgs(nil) - server.Config.Permissions.Admins = permissions.NewAdmins(nil) - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("store", _store) + t.Run("should register a new user", func(t *testing.T) { + _manager := mocks_services.NewManager(t) + _forge := mocks_forge.NewForge(t) + _store := mocks_store.NewStore(t) + server.Config.Services.Manager = _manager + server.Config.Permissions.Open = true + server.Config.Permissions.Orgs = permissions.NewOrgs(nil) + server.Config.Permissions.Admins = permissions.NewAdmins(nil) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Set("store", _store) + c.Request = &http.Request{ + Header: make(http.Header), + URL: &url.URL{ + Scheme: "https", + }, + } - query := url.Values{} - query.Set("code", "assumed_to_be_valid_code") + _manager.On("ForgeByID", int64(1)).Return(_forge, nil) + _forge.On("Login", mock.Anything, mock.Anything).Return(user, "", nil) + _store.On("GetUserRemoteID", user.ForgeRemoteID, user.Login).Return(nil, types.RecordNotExist) + _store.On("CreateUser", mock.Anything).Return(nil) + _store.On("OrgFindByName", user.Login).Return(nil, nil) + _store.On("OrgCreate", mock.Anything).Return(nil) + _store.On("UpdateUser", mock.Anything).Return(nil) + _forge.On("Repos", mock.Anything, mock.Anything).Return(nil, nil) - wrongToken := token.New(token.OAuthStateToken) - wrongToken.Set("forge_id", "1") - signedWrongToken, _ := wrongToken.Sign("wrong_secret") - query.Set("state", signedWrongToken) + api.HandleAuth(c) - c.Request = &http.Request{ - Header: make(http.Header), - URL: &url.URL{ - Scheme: "https", - RawQuery: query.Encode(), - }, - } + assert.Equal(t, http.StatusSeeOther, c.Writer.Status()) + assert.Equal(t, "/", c.Writer.Header().Get("Location")) + assert.NotEmpty(t, c.Writer.Header().Get("Set-Cookie")) + }) - api.HandleAuth(c) + t.Run("should login an existing user", func(t *testing.T) { + _manager := mocks_services.NewManager(t) + _forge := mocks_forge.NewForge(t) + _store := mocks_store.NewStore(t) + server.Config.Services.Manager = _manager + server.Config.Permissions.Open = true + server.Config.Permissions.Orgs = permissions.NewOrgs(nil) + server.Config.Permissions.Admins = permissions.NewAdmins(nil) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Set("store", _store) + c.Request = &http.Request{ + Header: make(http.Header), + URL: &url.URL{ + Scheme: "https", + }, + } - assert.Equal(g, http.StatusSeeOther, c.Writer.Status()) - assert.Equal(g, "/login?error=invalid_state", c.Writer.Header().Get("Location")) - }) + _manager.On("ForgeByID", int64(1)).Return(_forge, nil) + _forge.On("Login", mock.Anything, mock.Anything).Return(user, "", nil) + _store.On("GetUserRemoteID", user.ForgeRemoteID, user.Login).Return(user, nil) + _store.On("OrgGet", org.ID).Return(org, nil) + _store.On("UpdateUser", mock.Anything).Return(nil) + _forge.On("Repos", mock.Anything, mock.Anything).Return(nil, nil) - g.It("should redirect to forge login page", func() { - _manager := mocks_services.NewManager(t) - _forge := mocks_forge.NewForge(t) - _store := mocks_store.NewStore(t) - server.Config.Services.Manager = _manager - server.Config.Permissions.Open = true - server.Config.Permissions.Orgs = permissions.NewOrgs(nil) - server.Config.Permissions.Admins = permissions.NewAdmins(nil) - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("store", _store) - c.Request = &http.Request{ - Header: make(http.Header), - URL: &url.URL{ - Scheme: "https", - }, - } + api.HandleAuth(c) - _manager.On("ForgeByID", int64(1)).Return(_forge, nil) + assert.Equal(t, http.StatusSeeOther, c.Writer.Status()) + assert.Equal(t, "/", c.Writer.Header().Get("Location")) + assert.NotEmpty(t, c.Writer.Header().Get("Set-Cookie")) + }) - forgeRedirectURL := "" - _forge.On("Login", mock.Anything, mock.Anything).Run(func(args mock.Arguments) { - state, ok := args.Get(1).(*forge_types.OAuthRequest) - if ok { - forgeRedirectURL = fmt.Sprintf("https://my-awesome-forge.com/oauth/authorize?client_id=client-id&state=%s", state.State) - } - }).Return(nil, func(context.Context, *forge_types.OAuthRequest) string { - return forgeRedirectURL - }, nil) + t.Run("should deny a new user if registration is closed", func(t *testing.T) { + _manager := mocks_services.NewManager(t) + _forge := mocks_forge.NewForge(t) + _store := mocks_store.NewStore(t) + server.Config.Services.Manager = _manager + server.Config.Permissions.Open = false + server.Config.Permissions.Orgs = permissions.NewOrgs(nil) + server.Config.Permissions.Admins = permissions.NewAdmins(nil) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Set("store", _store) + c.Request = &http.Request{ + Header: make(http.Header), + URL: &url.URL{ + Scheme: "https", + }, + } - api.HandleAuth(c) + _manager.On("ForgeByID", int64(1)).Return(_forge, nil) + _forge.On("Login", mock.Anything, mock.Anything).Return(user, "", nil) + _store.On("GetUserRemoteID", user.ForgeRemoteID, user.Login).Return(nil, types.RecordNotExist) - assert.Equal(g, http.StatusSeeOther, c.Writer.Status()) - assert.Equal(g, forgeRedirectURL, c.Writer.Header().Get("Location")) - }) + api.HandleAuth(c) - g.It("should register a new user", func() { - _manager := mocks_services.NewManager(t) - _forge := mocks_forge.NewForge(t) - _store := mocks_store.NewStore(t) - server.Config.Services.Manager = _manager - server.Config.Permissions.Open = true - server.Config.Permissions.Orgs = permissions.NewOrgs(nil) - server.Config.Permissions.Admins = permissions.NewAdmins(nil) - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("store", _store) - c.Request = &http.Request{ - Header: make(http.Header), - URL: &url.URL{ - Scheme: "https", - }, - } + assert.Equal(t, http.StatusSeeOther, c.Writer.Status()) + assert.Equal(t, "/login?error=registration_closed", c.Writer.Header().Get("Location")) + }) - _manager.On("ForgeByID", int64(1)).Return(_forge, nil) - _forge.On("Login", mock.Anything, mock.Anything).Return(user, "", nil) - _store.On("GetUserRemoteID", user.ForgeRemoteID, user.Login).Return(nil, types.RecordNotExist) - _store.On("CreateUser", mock.Anything).Return(nil) - _store.On("OrgFindByName", user.Login).Return(nil, nil) - _store.On("OrgCreate", mock.Anything).Return(nil) - _store.On("UpdateUser", mock.Anything).Return(nil) - _forge.On("Repos", mock.Anything, mock.Anything).Return(nil, nil) + t.Run("should deny a user with missing org access", func(t *testing.T) { + _manager := mocks_services.NewManager(t) + _forge := mocks_forge.NewForge(t) + _store := mocks_store.NewStore(t) + server.Config.Services.Manager = _manager + server.Config.Permissions.Open = true + server.Config.Permissions.Orgs = permissions.NewOrgs([]string{"org1"}) + server.Config.Permissions.Admins = permissions.NewAdmins(nil) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Set("store", _store) + c.Request = &http.Request{ + Header: make(http.Header), + URL: &url.URL{ + Scheme: "https", + }, + } - api.HandleAuth(c) + _manager.On("ForgeByID", int64(1)).Return(_forge, nil) + _forge.On("Login", mock.Anything, mock.Anything).Return(user, "", nil) + _forge.On("Teams", mock.Anything, user).Return([]*model.Team{ + { + Login: "org2", + }, + }, nil) - assert.Equal(g, http.StatusSeeOther, c.Writer.Status()) - assert.Equal(g, "/", c.Writer.Header().Get("Location")) - assert.NotEmpty(g, c.Writer.Header().Get("Set-Cookie")) - }) + api.HandleAuth(c) - g.It("should login an existing user", func() { - _manager := mocks_services.NewManager(t) - _forge := mocks_forge.NewForge(t) - _store := mocks_store.NewStore(t) - server.Config.Services.Manager = _manager - server.Config.Permissions.Open = true - server.Config.Permissions.Orgs = permissions.NewOrgs(nil) - server.Config.Permissions.Admins = permissions.NewAdmins(nil) - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("store", _store) - c.Request = &http.Request{ - Header: make(http.Header), - URL: &url.URL{ - Scheme: "https", - }, - } + assert.Equal(t, http.StatusSeeOther, c.Writer.Status()) + assert.Equal(t, "/login?error=org_access_denied", c.Writer.Header().Get("Location")) + }) - _manager.On("ForgeByID", int64(1)).Return(_forge, nil) - _forge.On("Login", mock.Anything, mock.Anything).Return(user, "", nil) - _store.On("GetUserRemoteID", user.ForgeRemoteID, user.Login).Return(user, nil) - _store.On("OrgGet", org.ID).Return(org, nil) - _store.On("UpdateUser", mock.Anything).Return(nil) - _forge.On("Repos", mock.Anything, mock.Anything).Return(nil, nil) + t.Run("User org should be created if it does not exists", func(t *testing.T) { + _manager := mocks_services.NewManager(t) + _forge := mocks_forge.NewForge(t) + _store := mocks_store.NewStore(t) + server.Config.Services.Manager = _manager + server.Config.Permissions.Open = true + server.Config.Permissions.Orgs = permissions.NewOrgs(nil) + server.Config.Permissions.Admins = permissions.NewAdmins(nil) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Set("store", _store) + c.Request = &http.Request{ + Header: make(http.Header), + URL: &url.URL{ + Scheme: "https", + }, + } + user.OrgID = 0 - api.HandleAuth(c) + _manager.On("ForgeByID", int64(1)).Return(_forge, nil) + _forge.On("Login", mock.Anything, mock.Anything).Return(user, "", nil) + _store.On("GetUserRemoteID", user.ForgeRemoteID, user.Login).Return(user, nil) + _store.On("OrgFindByName", user.Login).Return(nil, types.RecordNotExist) + _store.On("OrgCreate", mock.Anything).Return(nil) + _store.On("UpdateUser", mock.Anything).Return(nil) + _forge.On("Repos", mock.Anything, mock.Anything).Return(nil, nil) - assert.Equal(g, http.StatusSeeOther, c.Writer.Status()) - assert.Equal(g, "/", c.Writer.Header().Get("Location")) - assert.NotEmpty(g, c.Writer.Header().Get("Set-Cookie")) - }) + api.HandleAuth(c) - g.It("should deny a new user if registration is closed", func() { - _manager := mocks_services.NewManager(t) - _forge := mocks_forge.NewForge(t) - _store := mocks_store.NewStore(t) - server.Config.Services.Manager = _manager - server.Config.Permissions.Open = false - server.Config.Permissions.Orgs = permissions.NewOrgs(nil) - server.Config.Permissions.Admins = permissions.NewAdmins(nil) - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("store", _store) - c.Request = &http.Request{ - Header: make(http.Header), - URL: &url.URL{ - Scheme: "https", - }, - } + assert.Equal(t, http.StatusSeeOther, c.Writer.Status()) + assert.Equal(t, "/", c.Writer.Header().Get("Location")) + assert.NotEmpty(t, c.Writer.Header().Get("Set-Cookie")) + }) - _manager.On("ForgeByID", int64(1)).Return(_forge, nil) - _forge.On("Login", mock.Anything, mock.Anything).Return(user, "", nil) - _store.On("GetUserRemoteID", user.ForgeRemoteID, user.Login).Return(nil, types.RecordNotExist) + t.Run("User org should be linked if it has the same name as the user", func(t *testing.T) { + _manager := mocks_services.NewManager(t) + _forge := mocks_forge.NewForge(t) + _store := mocks_store.NewStore(t) + server.Config.Services.Manager = _manager + server.Config.Permissions.Open = true + server.Config.Permissions.Orgs = permissions.NewOrgs(nil) + server.Config.Permissions.Admins = permissions.NewAdmins(nil) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Set("store", _store) + c.Request = &http.Request{ + Header: make(http.Header), + URL: &url.URL{ + Scheme: "https", + }, + } + user.OrgID = 0 - api.HandleAuth(c) + _manager.On("ForgeByID", int64(1)).Return(_forge, nil) + _forge.On("Login", mock.Anything, mock.Anything).Return(user, "", nil) + _store.On("GetUserRemoteID", user.ForgeRemoteID, user.Login).Return(user, nil) + _store.On("OrgFindByName", user.Login).Return(org, nil) + _store.On("OrgUpdate", mock.Anything).Return(nil) + _store.On("UpdateUser", mock.Anything).Return(nil) + _forge.On("Repos", mock.Anything, mock.Anything).Return(nil, nil) - assert.Equal(g, http.StatusSeeOther, c.Writer.Status()) - assert.Equal(g, "/login?error=registration_closed", c.Writer.Header().Get("Location")) - }) + api.HandleAuth(c) - g.It("should deny a user with missing org access", func() { - _manager := mocks_services.NewManager(t) - _forge := mocks_forge.NewForge(t) - _store := mocks_store.NewStore(t) - server.Config.Services.Manager = _manager - server.Config.Permissions.Open = true - server.Config.Permissions.Orgs = permissions.NewOrgs([]string{"org1"}) - server.Config.Permissions.Admins = permissions.NewAdmins(nil) - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("store", _store) - c.Request = &http.Request{ - Header: make(http.Header), - URL: &url.URL{ - Scheme: "https", - }, - } + assert.Equal(t, http.StatusSeeOther, c.Writer.Status()) + assert.Equal(t, "/", c.Writer.Header().Get("Location")) + assert.NotEmpty(t, c.Writer.Header().Get("Set-Cookie")) + }) - _manager.On("ForgeByID", int64(1)).Return(_forge, nil) - _forge.On("Login", mock.Anything, mock.Anything).Return(user, "", nil) - _forge.On("Teams", mock.Anything, user).Return([]*model.Team{ - { - Login: "org2", - }, - }, nil) + t.Run("User org should be updated if the user name was changed", func(t *testing.T) { + _manager := mocks_services.NewManager(t) + _forge := mocks_forge.NewForge(t) + _store := mocks_store.NewStore(t) + server.Config.Services.Manager = _manager + server.Config.Permissions.Open = true + server.Config.Permissions.Orgs = permissions.NewOrgs(nil) + server.Config.Permissions.Admins = permissions.NewAdmins(nil) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Set("store", _store) + c.Request = &http.Request{ + Header: make(http.Header), + URL: &url.URL{ + Scheme: "https", + }, + } + org.Name = "not-the-user-name" - api.HandleAuth(c) + _manager.On("ForgeByID", int64(1)).Return(_forge, nil) + _forge.On("Login", mock.Anything, mock.Anything).Return(user, "", nil) + _store.On("GetUserRemoteID", user.ForgeRemoteID, user.Login).Return(user, nil) + _store.On("OrgGet", user.OrgID).Return(org, nil) + _store.On("OrgUpdate", mock.Anything).Return(nil) + _store.On("UpdateUser", mock.Anything).Return(nil) + _forge.On("Repos", mock.Anything, mock.Anything).Return(nil, nil) - assert.Equal(g, http.StatusSeeOther, c.Writer.Status()) - assert.Equal(g, "/login?error=org_access_denied", c.Writer.Header().Get("Location")) - }) + api.HandleAuth(c) - g.Describe("User org", func() { - g.It("should be created if it does not exists", func() { - _manager := mocks_services.NewManager(t) - _forge := mocks_forge.NewForge(t) - _store := mocks_store.NewStore(t) - server.Config.Services.Manager = _manager - server.Config.Permissions.Open = true - server.Config.Permissions.Orgs = permissions.NewOrgs(nil) - server.Config.Permissions.Admins = permissions.NewAdmins(nil) - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("store", _store) - c.Request = &http.Request{ - Header: make(http.Header), - URL: &url.URL{ - Scheme: "https", - }, - } - user.OrgID = 0 - - _manager.On("ForgeByID", int64(1)).Return(_forge, nil) - _forge.On("Login", mock.Anything, mock.Anything).Return(user, "", nil) - _store.On("GetUserRemoteID", user.ForgeRemoteID, user.Login).Return(user, nil) - _store.On("OrgFindByName", user.Login).Return(nil, types.RecordNotExist) - _store.On("OrgCreate", mock.Anything).Return(nil) - _store.On("UpdateUser", mock.Anything).Return(nil) - _forge.On("Repos", mock.Anything, mock.Anything).Return(nil, nil) - - api.HandleAuth(c) - - assert.Equal(g, http.StatusSeeOther, c.Writer.Status()) - assert.Equal(g, "/", c.Writer.Header().Get("Location")) - assert.NotEmpty(g, c.Writer.Header().Get("Set-Cookie")) - }) - - g.It("should be linked if it has the same name as the user", func() { - _manager := mocks_services.NewManager(t) - _forge := mocks_forge.NewForge(t) - _store := mocks_store.NewStore(t) - server.Config.Services.Manager = _manager - server.Config.Permissions.Open = true - server.Config.Permissions.Orgs = permissions.NewOrgs(nil) - server.Config.Permissions.Admins = permissions.NewAdmins(nil) - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("store", _store) - c.Request = &http.Request{ - Header: make(http.Header), - URL: &url.URL{ - Scheme: "https", - }, - } - user.OrgID = 0 - - _manager.On("ForgeByID", int64(1)).Return(_forge, nil) - _forge.On("Login", mock.Anything, mock.Anything).Return(user, "", nil) - _store.On("GetUserRemoteID", user.ForgeRemoteID, user.Login).Return(user, nil) - _store.On("OrgFindByName", user.Login).Return(org, nil) - _store.On("OrgUpdate", mock.Anything).Return(nil) - _store.On("UpdateUser", mock.Anything).Return(nil) - _forge.On("Repos", mock.Anything, mock.Anything).Return(nil, nil) - - api.HandleAuth(c) - - assert.Equal(g, http.StatusSeeOther, c.Writer.Status()) - assert.Equal(g, "/", c.Writer.Header().Get("Location")) - assert.NotEmpty(g, c.Writer.Header().Get("Set-Cookie")) - }) - - g.It("should be updated if the user name was changed", func() { - _manager := mocks_services.NewManager(t) - _forge := mocks_forge.NewForge(t) - _store := mocks_store.NewStore(t) - server.Config.Services.Manager = _manager - server.Config.Permissions.Open = true - server.Config.Permissions.Orgs = permissions.NewOrgs(nil) - server.Config.Permissions.Admins = permissions.NewAdmins(nil) - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("store", _store) - c.Request = &http.Request{ - Header: make(http.Header), - URL: &url.URL{ - Scheme: "https", - }, - } - org.Name = "not-the-user-name" - - _manager.On("ForgeByID", int64(1)).Return(_forge, nil) - _forge.On("Login", mock.Anything, mock.Anything).Return(user, "", nil) - _store.On("GetUserRemoteID", user.ForgeRemoteID, user.Login).Return(user, nil) - _store.On("OrgGet", user.OrgID).Return(org, nil) - _store.On("OrgUpdate", mock.Anything).Return(nil) - _store.On("UpdateUser", mock.Anything).Return(nil) - _forge.On("Repos", mock.Anything, mock.Anything).Return(nil, nil) - - api.HandleAuth(c) - - assert.Equal(g, http.StatusSeeOther, c.Writer.Status()) - assert.Equal(g, "/", c.Writer.Header().Get("Location")) - assert.NotEmpty(g, c.Writer.Header().Get("Set-Cookie")) - }) - }) + assert.Equal(t, http.StatusSeeOther, c.Writer.Status()) + assert.Equal(t, "/", c.Writer.Header().Get("Location")) + assert.NotEmpty(t, c.Writer.Header().Get("Set-Cookie")) }) } diff --git a/server/api/metrics/prometheus.go b/server/api/metrics/prometheus.go index b8f65e67b..1564f3926 100644 --- a/server/api/metrics/prometheus.go +++ b/server/api/metrics/prometheus.go @@ -22,7 +22,7 @@ import ( "github.com/gin-gonic/gin" prometheus_http "github.com/prometheus/client_golang/prometheus/promhttp" - "go.woodpecker-ci.org/woodpecker/v2/server" + "go.woodpecker-ci.org/woodpecker/v3/server" ) // errInvalidToken is returned when the api request token is invalid. diff --git a/server/api/org.go b/server/api/org.go index 227859558..765ee3df8 100644 --- a/server/api/org.go +++ b/server/api/org.go @@ -16,16 +16,15 @@ package api import ( "net/http" - "strconv" "strings" "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/router/middleware/session" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) // GetOrgs @@ -58,20 +57,7 @@ func GetOrgs(c *gin.Context) { // @Param Authorization header string true "Insert your personal access token" default(Bearer ) // @Param org_id path string true "the organization's id" func GetOrg(c *gin.Context) { - _store := store.FromContext(c) - - orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64) - if err != nil { - c.String(http.StatusBadRequest, "Error parsing org id. %s", err) - return - } - - org, err := _store.OrgGet(orgID) - if err != nil { - handleDBError(c, err) - return - } - + org := session.Org(c) c.JSON(http.StatusOK, org) } @@ -86,7 +72,7 @@ func GetOrg(c *gin.Context) { // @Param org_id path string true "the organization's id" func GetOrgPermissions(c *gin.Context) { user := session.User(c) - _store := store.FromContext(c) + org := session.Org(c) _forge, err := server.Config.Services.Manager.ForgeFromUser(user) if err != nil { @@ -95,23 +81,11 @@ func GetOrgPermissions(c *gin.Context) { return } - orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64) - if err != nil { - c.String(http.StatusBadRequest, "Error parsing org id. %s", err) - return - } - if user == nil { c.JSON(http.StatusOK, &model.OrgPerm{}) return } - org, err := _store.OrgGet(orgID) - if err != nil { - c.String(http.StatusInternalServerError, "Error getting org %d. %s", orgID, err) - return - } - if (org.IsUser && org.Name == user.Login) || (user.Admin && !org.IsUser) { c.JSON(http.StatusOK, &model.OrgPerm{ Member: true, @@ -125,7 +99,7 @@ func GetOrgPermissions(c *gin.Context) { perm, err := server.Config.Services.Membership.Get(c, _forge, user, org.Name) if err != nil { - c.String(http.StatusInternalServerError, "Error getting membership for %d. %s", orgID, err) + c.String(http.StatusInternalServerError, "Error getting membership for %d. %s", org.ID, err) return } @@ -200,15 +174,9 @@ func LookupOrg(c *gin.Context) { // @Param id path string true "the org's id" func DeleteOrg(c *gin.Context) { _store := store.FromContext(c) + org := session.Org(c) - orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64) - if err != nil { - c.String(http.StatusBadRequest, "Error parsing org id. %s", err) - return - } - - err = _store.OrgDelete(orgID) - if err != nil { + if err := _store.OrgDelete(org.ID); err != nil { handleDBError(c, err) return } diff --git a/server/api/org_registry.go b/server/api/org_registry.go index b52782675..47c6c77c8 100644 --- a/server/api/org_registry.go +++ b/server/api/org_registry.go @@ -16,13 +16,12 @@ package api import ( "net/http" - "strconv" "github.com/gin-gonic/gin" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/router/middleware/session" ) // GetOrgRegistry @@ -36,16 +35,11 @@ import ( // @Param org_id path string true "the org's id" // @Param registry path string true "the registry's address" func GetOrgRegistry(c *gin.Context) { + org := session.Org(c) addr := c.Param("registry") - orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64) - if err != nil { - c.String(http.StatusBadRequest, "Error parsing org id. %s", err) - return - } - registryService := server.Config.Services.Manager.RegistryService() - registry, err := registryService.OrgRegistryFind(orgID, addr) + registry, err := registryService.OrgRegistryFind(org.ID, addr) if err != nil { handleDBError(c, err) return @@ -62,19 +56,15 @@ func GetOrgRegistry(c *gin.Context) { // @Tags Organization registries // @Param Authorization header string true "Insert your personal access token" default(Bearer ) // @Param org_id path string true "the org's id" -// @Param page query int false "for response pagination, page offset number" default(1) -// @Param perPage query int false "for response pagination, max items per page" default(50) +// @Param page query int false "for response pagination, page offset number" default(1) +// @Param perPage query int false "for response pagination, max items per page" default(50) func GetOrgRegistryList(c *gin.Context) { - orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64) - if err != nil { - c.String(http.StatusBadRequest, "Error parsing org id. %s", err) - return - } + org := session.Org(c) registryService := server.Config.Services.Manager.RegistryService() - list, err := registryService.OrgRegistryList(orgID, session.Pagination(c)) + list, err := registryService.OrgRegistryList(org.ID, session.Pagination(c)) if err != nil { - c.String(http.StatusInternalServerError, "Error getting registry list for %q. %s", orgID, err) + c.String(http.StatusInternalServerError, "Error getting registry list for %q. %s", org.ID, err) return } // copy the registry detail to remove the sensitive @@ -93,34 +83,30 @@ func GetOrgRegistryList(c *gin.Context) { // @Success 200 {object} Registry // @Tags Organization registries // @Param Authorization header string true "Insert your personal access token" default(Bearer ) -// @Param org_id path string true "the org's id" -// @Param registryData body Registry true "the new registry" +// @Param org_id path string true "the org's id" +// @Param registryData body Registry true "the new registry" func PostOrgRegistry(c *gin.Context) { - orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64) - if err != nil { - c.String(http.StatusBadRequest, "Error parsing org id. %s", err) - return - } + org := session.Org(c) in := new(model.Registry) if err := c.Bind(in); err != nil { - c.String(http.StatusBadRequest, "Error parsing org %q registry. %s", orgID, err) + c.String(http.StatusBadRequest, "Error parsing org %q registry. %s", org.ID, err) return } registry := &model.Registry{ - OrgID: orgID, + OrgID: org.ID, Address: in.Address, Username: in.Username, Password: in.Password, } if err := registry.Validate(); err != nil { - c.String(http.StatusUnprocessableEntity, "Error inserting org %q registry. %s", orgID, err) + c.String(http.StatusUnprocessableEntity, "Error inserting org %q registry. %s", org.ID, err) return } registryService := server.Config.Services.Manager.RegistryService() - if err := registryService.OrgRegistryCreate(orgID, registry); err != nil { - c.String(http.StatusInternalServerError, "Error inserting org %q registry %q. %s", orgID, in.Address, err) + if err := registryService.OrgRegistryCreate(org.ID, registry); err != nil { + c.String(http.StatusInternalServerError, "Error inserting org %q registry %q. %s", org.ID, in.Address, err) return } c.JSON(http.StatusOK, registry.Copy()) @@ -133,27 +119,22 @@ func PostOrgRegistry(c *gin.Context) { // @Produce json // @Success 200 {object} Registry // @Tags Organization registries -// @Param Authorization header string true "Insert your personal access token" default(Bearer ) -// @Param org_id path string true "the org's id" -// @Param registry path string true "the registry's name" +// @Param Authorization header string true "Insert your personal access token" default(Bearer ) +// @Param org_id path string true "the org's id" +// @Param registry path string true "the registry's name" // @Param registryData body Registry true "the update registry data" func PatchOrgRegistry(c *gin.Context) { + org := session.Org(c) addr := c.Param("registry") - orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64) - if err != nil { - c.String(http.StatusBadRequest, "Error parsing org id. %s", err) - return - } in := new(model.Registry) - err = c.Bind(in) - if err != nil { + if err := c.Bind(in); err != nil { c.String(http.StatusBadRequest, "Error parsing registry. %s", err) return } registryService := server.Config.Services.Manager.RegistryService() - registry, err := registryService.OrgRegistryFind(orgID, addr) + registry, err := registryService.OrgRegistryFind(org.ID, addr) if err != nil { handleDBError(c, err) return @@ -169,12 +150,12 @@ func PatchOrgRegistry(c *gin.Context) { } if err := registry.Validate(); err != nil { - c.String(http.StatusUnprocessableEntity, "Error updating org %q registry. %s", orgID, err) + c.String(http.StatusUnprocessableEntity, "Error updating org %q registry. %s", org.ID, err) return } - if err := registryService.OrgRegistryUpdate(orgID, registry); err != nil { - c.String(http.StatusInternalServerError, "Error updating org %q registry %q. %s", orgID, in.Address, err) + if err := registryService.OrgRegistryUpdate(org.ID, registry); err != nil { + c.String(http.StatusInternalServerError, "Error updating org %q registry %q. %s", org.ID, in.Address, err) return } c.JSON(http.StatusOK, registry.Copy()) @@ -191,15 +172,11 @@ func PatchOrgRegistry(c *gin.Context) { // @Param org_id path string true "the org's id" // @Param registry path string true "the registry's name" func DeleteOrgRegistry(c *gin.Context) { + org := session.Org(c) addr := c.Param("registry") - orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64) - if err != nil { - c.String(http.StatusBadRequest, "Error parsing org id. %s", err) - return - } registryService := server.Config.Services.Manager.RegistryService() - if err := registryService.OrgRegistryDelete(orgID, addr); err != nil { + if err := registryService.OrgRegistryDelete(org.ID, addr); err != nil { handleDBError(c, err) return } diff --git a/server/api/org_secret.go b/server/api/org_secret.go index 98f67e089..47b9dfc53 100644 --- a/server/api/org_secret.go +++ b/server/api/org_secret.go @@ -16,13 +16,12 @@ package api import ( "net/http" - "strconv" "github.com/gin-gonic/gin" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/router/middleware/session" ) // GetOrgSecret @@ -36,16 +35,11 @@ import ( // @Param org_id path string true "the org's id" // @Param secret path string true "the secret's name" func GetOrgSecret(c *gin.Context) { + org := session.Org(c) name := c.Param("secret") - orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64) - if err != nil { - c.String(http.StatusBadRequest, "Error parsing org id. %s", err) - return - } - secretService := server.Config.Services.Manager.SecretService() - secret, err := secretService.OrgSecretFind(orgID, name) + secret, err := secretService.OrgSecretFind(org.ID, name) if err != nil { handleDBError(c, err) return @@ -65,16 +59,12 @@ func GetOrgSecret(c *gin.Context) { // @Param page query int false "for response pagination, page offset number" default(1) // @Param perPage query int false "for response pagination, max items per page" default(50) func GetOrgSecretList(c *gin.Context) { - orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64) - if err != nil { - c.String(http.StatusBadRequest, "Error parsing org id. %s", err) - return - } + org := session.Org(c) secretService := server.Config.Services.Manager.SecretService() - list, err := secretService.OrgSecretList(orgID, session.Pagination(c)) + list, err := secretService.OrgSecretList(org.ID, session.Pagination(c)) if err != nil { - c.String(http.StatusInternalServerError, "Error getting secret list for %q. %s", orgID, err) + c.String(http.StatusInternalServerError, "Error getting secret list for %q. %s", org.ID, err) return } // copy the secret detail to remove the sensitive @@ -96,32 +86,28 @@ func GetOrgSecretList(c *gin.Context) { // @Param org_id path string true "the org's id" // @Param secretData body Secret true "the new secret" func PostOrgSecret(c *gin.Context) { - orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64) - if err != nil { - c.String(http.StatusBadRequest, "Error parsing org id. %s", err) - return - } + org := session.Org(c) in := new(model.Secret) if err := c.Bind(in); err != nil { - c.String(http.StatusBadRequest, "Error parsing org %q secret. %s", orgID, err) + c.String(http.StatusBadRequest, "Error parsing org %q secret. %s", org.ID, err) return } secret := &model.Secret{ - OrgID: orgID, + OrgID: org.ID, Name: in.Name, Value: in.Value, Events: in.Events, Images: in.Images, } if err := secret.Validate(); err != nil { - c.String(http.StatusUnprocessableEntity, "Error inserting org %q secret. %s", orgID, err) + c.String(http.StatusUnprocessableEntity, "Error inserting org %q secret. %s", org.ID, err) return } secretService := server.Config.Services.Manager.SecretService() - if err := secretService.OrgSecretCreate(orgID, secret); err != nil { - c.String(http.StatusInternalServerError, "Error inserting org %q secret %q. %s", orgID, in.Name, err) + if err := secretService.OrgSecretCreate(org.ID, secret); err != nil { + c.String(http.StatusInternalServerError, "Error inserting org %q secret %q. %s", org.ID, in.Name, err) return } c.JSON(http.StatusOK, secret.Copy()) @@ -139,22 +125,17 @@ func PostOrgSecret(c *gin.Context) { // @Param secret path string true "the secret's name" // @Param secretData body Secret true "the update secret data" func PatchOrgSecret(c *gin.Context) { + org := session.Org(c) name := c.Param("secret") - orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64) - if err != nil { - c.String(http.StatusBadRequest, "Error parsing org id. %s", err) - return - } in := new(model.Secret) - err = c.Bind(in) - if err != nil { + if err := c.Bind(in); err != nil { c.String(http.StatusBadRequest, "Error parsing secret. %s", err) return } secretService := server.Config.Services.Manager.SecretService() - secret, err := secretService.OrgSecretFind(orgID, name) + secret, err := secretService.OrgSecretFind(org.ID, name) if err != nil { handleDBError(c, err) return @@ -170,12 +151,12 @@ func PatchOrgSecret(c *gin.Context) { } if err := secret.Validate(); err != nil { - c.String(http.StatusUnprocessableEntity, "Error updating org %q secret. %s", orgID, err) + c.String(http.StatusUnprocessableEntity, "Error updating org %q secret. %s", org.ID, err) return } - if err := secretService.OrgSecretUpdate(orgID, secret); err != nil { - c.String(http.StatusInternalServerError, "Error updating org %q secret %q. %s", orgID, in.Name, err) + if err := secretService.OrgSecretUpdate(org.ID, secret); err != nil { + c.String(http.StatusInternalServerError, "Error updating org %q secret %q. %s", org.ID, in.Name, err) return } c.JSON(http.StatusOK, secret.Copy()) @@ -192,15 +173,11 @@ func PatchOrgSecret(c *gin.Context) { // @Param org_id path string true "the org's id" // @Param secret path string true "the secret's name" func DeleteOrgSecret(c *gin.Context) { + org := session.Org(c) name := c.Param("secret") - orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64) - if err != nil { - c.String(http.StatusBadRequest, "Error parsing org id. %s", err) - return - } secretService := server.Config.Services.Manager.SecretService() - if err := secretService.OrgSecretDelete(orgID, name); err != nil { + if err := secretService.OrgSecretDelete(org.ID, name); err != nil { handleDBError(c, err) return } diff --git a/server/api/pipeline.go b/server/api/pipeline.go index 591a6db66..ce3d1396f 100644 --- a/server/api/pipeline.go +++ b/server/api/pipeline.go @@ -22,16 +22,19 @@ import ( "fmt" "net/http" "strconv" + "strings" "time" "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/pipeline" - "go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/pipeline" + "go.woodpecker-ci.org/woodpecker/v3/server/pipeline/stepbuilder" + "go.woodpecker-ci.org/woodpecker/v3/server/router/middleware/session" + "go.woodpecker-ci.org/woodpecker/v3/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/store/types" ) // CreatePipeline @@ -64,7 +67,11 @@ func CreatePipeline(c *gin.Context) { user := session.User(c) - lastCommit, _ := _forge.BranchHead(c, user, repo, opts.Branch) + lastCommit, err := _forge.BranchHead(c, user, repo, opts.Branch) + if err != nil { + _ = c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("could not fetch branch head: %w", err)) + return + } tmpPipeline := createTmpPipeline(model.EventManual, lastCommit, user, &opts) @@ -98,26 +105,54 @@ func createTmpPipeline(event model.WebhookEvent, commit *model.Commit, user *mod // GetPipelines // -// @Summary List repository pipelines +// @Summary List repository pipelines // @Description Get a list of pipelines for a repository. -// @Router /repos/{repo_id}/pipelines [get] -// @Produce json -// @Success 200 {array} Pipeline -// @Tags Pipelines -// @Param Authorization header string true "Insert your personal access token" default(Bearer ) -// @Param repo_id path int true "the repository id" -// @Param page query int false "for response pagination, page offset number" default(1) -// @Param perPage query int false "for response pagination, max items per page" default(50) -// @Param before query string false "only return pipelines before this RFC3339 date" -// @Param after query string false "only return pipelines after this RFC3339 date" +// @Router /repos/{repo_id}/pipelines [get] +// @Produce json +// @Success 200 {array} Pipeline +// @Tags Pipelines +// @Param Authorization header string true "Insert your personal access token" default(Bearer ) +// @Param repo_id path int true "the repository id" +// @Param page query int false "for response pagination, page offset number" default(1) +// @Param perPage query int false "for response pagination, max items per page" default(50) +// @Param before query string false "only return pipelines before this RFC3339 date" +// @Param after query string false "only return pipelines after this RFC3339 date" +// @Param branch query string false "filter pipelines by branch" +// @Param event query string false "filter pipelines by webhook events (comma separated)" +// @Param ref query string false "filter pipelines by strings contained in ref" +// @Param status query string false "filter pipelines by status" func GetPipelines(c *gin.Context) { repo := session.Repo(c) - before := c.Query("before") - after := c.Query("after") - filter := new(model.PipelineFilter) + filter := &model.PipelineFilter{ + Branch: c.Query("branch"), + RefContains: c.Query("ref"), + } - if before != "" { + if events := c.Query("event"); events != "" { + eventList := strings.Split(events, ",") + wel := make(model.WebhookEventList, 0, len(eventList)) + for _, event := range eventList { + we := model.WebhookEvent(event) + if err := we.Validate(); err != nil { + _ = c.AbortWithError(http.StatusBadRequest, err) + return + } + wel = append(wel, we) + } + filter.Events = wel + } + + if status := c.Query("status"); status != "" { + ps := model.StatusValue(status) + if err := ps.Validate(); err != nil { + _ = c.AbortWithError(http.StatusBadRequest, err) + return + } + filter.Status = ps + } + + if before := c.Query("before"); before != "" { beforeDt, err := time.Parse(time.RFC3339, before) if err != nil { _ = c.AbortWithError(http.StatusBadRequest, err) @@ -126,7 +161,7 @@ func GetPipelines(c *gin.Context) { filter.Before = beforeDt.Unix() } - if after != "" { + if after := c.Query("after"); after != "" { afterDt, err := time.Parse(time.RFC3339, after) if err != nil { _ = c.AbortWithError(http.StatusBadRequest, err) @@ -388,6 +423,47 @@ func GetPipelineConfig(c *gin.Context) { c.JSON(http.StatusOK, configs) } +// GetPipelineMetadata +// +// @Summary Get metadata for a pipeline or a specific workflow, including previous pipeline info +// @Router /repos/{repo_id}/pipelines/{number}/metadata [get] +// @Produce json +// @Success 200 {object} metadata.Metadata +// @Tags Pipelines +// @Param Authorization header string true "Insert your personal access token" default(Bearer ) +// @Param repo_id path int true "the repository id" +// @Param number path int true "the number of the pipeline" +func GetPipelineMetadata(c *gin.Context) { + repo := session.Repo(c) + num, err := strconv.ParseInt(c.Param("number"), 10, 64) + if err != nil { + _ = c.AbortWithError(http.StatusBadRequest, err) + return + } + + _store := store.FromContext(c) + currentPipeline, err := _store.GetPipelineNumber(repo, num) + if err != nil { + handleDBError(c, err) + return + } + + forge, err := server.Config.Services.Manager.ForgeFromRepo(repo) + if err != nil { + c.AbortWithStatus(http.StatusInternalServerError) + return + } + + prevPipeline, err := _store.GetPipelineLastBefore(repo, currentPipeline.Branch, currentPipeline.ID) + if err != nil && !errors.Is(err, types.RecordNotExist) { + handleDBError(c, err) + return + } + + metadata := stepbuilder.MetadataFromStruct(forge, repo, currentPipeline, prevPipeline, nil, server.Config.Server.Host) + c.JSON(http.StatusOK, metadata) +} + // CancelPipeline // // @Summary Cancel a pipeline diff --git a/server/api/pipeline_test.go b/server/api/pipeline_test.go index d66301170..04c1ba52c 100644 --- a/server/api/pipeline_test.go +++ b/server/api/pipeline_test.go @@ -1,129 +1,235 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package api import ( + "encoding/json" "net/http" "net/http/httptest" "testing" - "github.com/franela/goblin" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store/mocks" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata" + "go.woodpecker-ci.org/woodpecker/v3/server" + forge_mocks "go.woodpecker-ci.org/woodpecker/v3/server/forge/mocks" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + mocks_manager "go.woodpecker-ci.org/woodpecker/v3/server/services/mocks" + store_mocks "go.woodpecker-ci.org/woodpecker/v3/server/store/mocks" + "go.woodpecker-ci.org/woodpecker/v3/server/store/types" ) var fakePipeline = &model.Pipeline{ + ID: 2, + Number: 2, Status: model.StatusSuccess, } func TestGetPipelines(t *testing.T) { gin.SetMode(gin.TestMode) - g := goblin.Goblin(t) - g.Describe("Pipeline", func() { - g.It("should get pipelines", func() { - pipelines := []*model.Pipeline{fakePipeline} + t.Run("should get pipelines", func(t *testing.T) { + pipelines := []*model.Pipeline{fakePipeline} - mockStore := mocks.NewStore(t) - mockStore.On("GetPipelineList", mock.Anything, mock.Anything, mock.Anything).Return(pipelines, nil) + mockStore := store_mocks.NewStore(t) + mockStore.On("GetPipelineList", mock.Anything, mock.Anything, mock.Anything).Return(pipelines, nil) - w := httptest.NewRecorder() - c, _ := gin.CreateTestContext(w) - c.Set("store", mockStore) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Set("store", mockStore) - GetPipelines(c) + GetPipelines(c) - mockStore.AssertCalled(t, "GetPipelineList", mock.Anything, mock.Anything, mock.Anything) - assert.Equal(t, http.StatusOK, c.Writer.Status()) - }) - - g.It("should not parse pipeline filter", func() { - c, _ := gin.CreateTestContext(httptest.NewRecorder()) - c.Request, _ = http.NewRequest(http.MethodDelete, "/?before=2023-01-16&after=2023-01-15", nil) - - GetPipelines(c) - - assert.Equal(t, http.StatusBadRequest, c.Writer.Status()) - }) - - g.It("should parse pipeline filter", func() { - pipelines := []*model.Pipeline{fakePipeline} - - mockStore := mocks.NewStore(t) - mockStore.On("GetPipelineList", mock.Anything, mock.Anything, mock.Anything).Return(pipelines, nil) - - c, _ := gin.CreateTestContext(httptest.NewRecorder()) - c.Set("store", mockStore) - c.Request, _ = http.NewRequest(http.MethodDelete, "/?2023-01-16T15:00:00Z&after=2023-01-15T15:00:00Z", nil) - - GetPipelines(c) - - assert.Equal(t, http.StatusOK, c.Writer.Status()) - }) - - g.It("should parse pipeline filter with tz offset", func() { - pipelines := []*model.Pipeline{fakePipeline} - - mockStore := mocks.NewStore(t) - mockStore.On("GetPipelineList", mock.Anything, mock.Anything, mock.Anything).Return(pipelines, nil) - - c, _ := gin.CreateTestContext(httptest.NewRecorder()) - c.Set("store", mockStore) - c.Request, _ = http.NewRequest(http.MethodDelete, "/?before=2023-01-16T15:00:00%2B01:00&after=2023-01-15T15:00:00%2B01:00", nil) - - GetPipelines(c) - - assert.Equal(t, http.StatusOK, c.Writer.Status()) + mockStore.AssertCalled(t, "GetPipelineList", mock.Anything, mock.Anything, mock.Anything) + assert.Equal(t, http.StatusOK, c.Writer.Status()) + }) + + t.Run("should not parse pipeline filter", func(t *testing.T) { + c, _ := gin.CreateTestContext(httptest.NewRecorder()) + c.Request, _ = http.NewRequest(http.MethodDelete, "/?before=2023-01-16&after=2023-01-15", nil) + + GetPipelines(c) + + assert.Equal(t, http.StatusBadRequest, c.Writer.Status()) + }) + + t.Run("should parse pipeline filter", func(t *testing.T) { + pipelines := []*model.Pipeline{fakePipeline} + + mockStore := store_mocks.NewStore(t) + mockStore.On("GetPipelineList", mock.Anything, mock.Anything, mock.Anything).Return(pipelines, nil) + + c, _ := gin.CreateTestContext(httptest.NewRecorder()) + c.Set("store", mockStore) + c.Request, _ = http.NewRequest(http.MethodDelete, "/?2023-01-16T15:00:00Z&after=2023-01-15T15:00:00Z", nil) + + GetPipelines(c) + + assert.Equal(t, http.StatusOK, c.Writer.Status()) + }) + + t.Run("should parse pipeline filter with tz offset", func(t *testing.T) { + pipelines := []*model.Pipeline{fakePipeline} + + mockStore := store_mocks.NewStore(t) + mockStore.On("GetPipelineList", mock.Anything, mock.Anything, mock.Anything).Return(pipelines, nil) + + c, _ := gin.CreateTestContext(httptest.NewRecorder()) + c.Set("store", mockStore) + c.Request, _ = http.NewRequest(http.MethodDelete, "/?before=2023-01-16T15:00:00%2B01:00&after=2023-01-15T15:00:00%2B01:00", nil) + + GetPipelines(c) + + assert.Equal(t, http.StatusOK, c.Writer.Status()) + }) + + t.Run("should filter pipelines by events", func(t *testing.T) { + pipelines := []*model.Pipeline{fakePipeline} + mockStore := store_mocks.NewStore(t) + mockStore.On("GetPipelineList", mock.Anything, mock.Anything, mock.Anything).Return(pipelines, nil) + + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Set("store", mockStore) + c.Request, _ = http.NewRequest(http.MethodGet, "/?event=push,pull_request", nil) + + GetPipelines(c) + + mockStore.AssertCalled(t, "GetPipelineList", mock.Anything, mock.Anything, &model.PipelineFilter{ + Events: model.WebhookEventList{model.EventPush, model.EventPull}, }) + assert.Equal(t, http.StatusOK, c.Writer.Status()) }) } func TestDeletePipeline(t *testing.T) { gin.SetMode(gin.TestMode) - g := goblin.Goblin(t) - g.Describe("Pipeline", func() { - g.It("should delete pipeline", func() { - mockStore := mocks.NewStore(t) - mockStore.On("GetPipelineNumber", mock.Anything, mock.Anything).Return(fakePipeline, nil) - mockStore.On("DeletePipeline", mock.Anything).Return(nil) + t.Run("should delete pipeline", func(t *testing.T) { + mockStore := store_mocks.NewStore(t) + mockStore.On("GetPipelineNumber", mock.Anything, mock.Anything).Return(fakePipeline, nil) + mockStore.On("DeletePipeline", mock.Anything).Return(nil) - c, _ := gin.CreateTestContext(httptest.NewRecorder()) + c, _ := gin.CreateTestContext(httptest.NewRecorder()) + c.Set("store", mockStore) + c.Params = gin.Params{{Key: "number", Value: "2"}} + + DeletePipeline(c) + + mockStore.AssertCalled(t, "GetPipelineNumber", mock.Anything, mock.Anything) + mockStore.AssertCalled(t, "DeletePipeline", mock.Anything) + assert.Equal(t, http.StatusNoContent, c.Writer.Status()) + }) + + t.Run("should not delete without pipeline number", func(t *testing.T) { + c, _ := gin.CreateTestContext(httptest.NewRecorder()) + + DeletePipeline(c) + + assert.Equal(t, http.StatusBadRequest, c.Writer.Status()) + }) + + t.Run("should not delete pending", func(t *testing.T) { + fakePipeline := *fakePipeline + fakePipeline.Status = model.StatusPending + + mockStore := store_mocks.NewStore(t) + mockStore.On("GetPipelineNumber", mock.Anything, mock.Anything).Return(&fakePipeline, nil) + + c, _ := gin.CreateTestContext(httptest.NewRecorder()) + c.Set("store", mockStore) + c.Params = gin.Params{{Key: "number", Value: "2"}} + + DeletePipeline(c) + + mockStore.AssertCalled(t, "GetPipelineNumber", mock.Anything, mock.Anything) + mockStore.AssertNotCalled(t, "DeletePipeline", mock.Anything) + assert.Equal(t, http.StatusUnprocessableEntity, c.Writer.Status()) + }) +} + +func TestGetPipelineMetadata(t *testing.T) { + gin.SetMode(gin.TestMode) + + prevPipeline := &model.Pipeline{ + ID: 1, + Number: 1, + Status: model.StatusFailure, + } + + fakeRepo := &model.Repo{ID: 1} + + mockForge := forge_mocks.NewForge(t) + mockForge.On("Name").Return("mock") + mockForge.On("URL").Return("https://codeberg.org") + + mockManager := mocks_manager.NewManager(t) + mockManager.On("ForgeFromRepo", fakeRepo).Return(mockForge, nil) + server.Config.Services.Manager = mockManager + + mockStore := store_mocks.NewStore(t) + mockStore.On("GetPipelineNumber", mock.Anything, int64(2)).Return(fakePipeline, nil) + mockStore.On("GetPipelineLastBefore", mock.Anything, mock.Anything, int64(2)).Return(prevPipeline, nil) + + t.Run("PipelineMetadata", func(t *testing.T) { + t.Run("should get pipeline metadata", func(t *testing.T) { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Params = gin.Params{{Key: "number", Value: "2"}} c.Set("store", mockStore) - c.Params = gin.Params{{Key: "number", Value: "1"}} + c.Set("forge", mockForge) + c.Set("repo", fakeRepo) - DeletePipeline(c) + GetPipelineMetadata(c) - mockStore.AssertCalled(t, "GetPipelineNumber", mock.Anything, mock.Anything) - mockStore.AssertCalled(t, "DeletePipeline", mock.Anything) - assert.Equal(t, http.StatusNoContent, c.Writer.Status()) + assert.Equal(t, http.StatusOK, w.Code) + + var response metadata.Metadata + err := json.Unmarshal(w.Body.Bytes(), &response) + assert.NoError(t, err) + + assert.Equal(t, int64(1), response.Repo.ID) + assert.Equal(t, int64(2), response.Curr.Number) + assert.Equal(t, int64(1), response.Prev.Number) }) - g.It("should not delete without pipeline number", func() { - c, _ := gin.CreateTestContext(httptest.NewRecorder()) + t.Run("should return bad request for invalid pipeline number", func(t *testing.T) { + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Params = gin.Params{{Key: "number", Value: "invalid"}} - DeletePipeline(c) + GetPipelineMetadata(c) - assert.Equal(t, http.StatusBadRequest, c.Writer.Status()) + assert.Equal(t, http.StatusBadRequest, w.Code) }) - g.It("should not delete pending", func() { - fakePipeline.Status = model.StatusPending + t.Run("should return not found for non-existent pipeline", func(t *testing.T) { + mockStore := store_mocks.NewStore(t) + mockStore.On("GetPipelineNumber", mock.Anything, int64(3)).Return((*model.Pipeline)(nil), types.RecordNotExist) - mockStore := mocks.NewStore(t) - mockStore.On("GetPipelineNumber", mock.Anything, mock.Anything).Return(fakePipeline, nil) - - c, _ := gin.CreateTestContext(httptest.NewRecorder()) + w := httptest.NewRecorder() + c, _ := gin.CreateTestContext(w) + c.Params = gin.Params{{Key: "number", Value: "3"}} c.Set("store", mockStore) - c.Params = gin.Params{{Key: "number", Value: "1"}} + c.Set("repo", fakeRepo) - DeletePipeline(c) + GetPipelineMetadata(c) - mockStore.AssertCalled(t, "GetPipelineNumber", mock.Anything, mock.Anything) - mockStore.AssertNotCalled(t, "DeletePipeline", mock.Anything) - assert.Equal(t, http.StatusUnprocessableEntity, c.Writer.Status()) + assert.Equal(t, http.StatusNotFound, w.Code) }) }) } diff --git a/server/api/registry.go b/server/api/registry.go index 360db1483..79e1dad03 100644 --- a/server/api/registry.go +++ b/server/api/registry.go @@ -19,9 +19,9 @@ import ( "github.com/gin-gonic/gin" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/router/middleware/session" ) // GetRegistry diff --git a/server/api/repo.go b/server/api/repo.go index b5cefa699..c9127c219 100644 --- a/server/api/repo.go +++ b/server/api/repo.go @@ -27,12 +27,13 @@ import ( "github.com/gorilla/securecookie" "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session" - "go.woodpecker-ci.org/woodpecker/v2/server/store" - "go.woodpecker-ci.org/woodpecker/v2/server/store/types" - "go.woodpecker-ci.org/woodpecker/v2/shared/token" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/router/middleware/session" + "go.woodpecker-ci.org/woodpecker/v3/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/store/types" + "go.woodpecker-ci.org/woodpecker/v3/shared/token" ) // PostRepo @@ -90,9 +91,9 @@ func PostRepo(c *gin.Context) { repo.Update(from) } else { repo = from + repo.RequireApproval = model.RequireApprovalForks repo.AllowPull = true repo.AllowDeploy = false - repo.NetrcOnlyTrusted = true repo.CancelPreviousPipelineEvents = server.Config.Pipeline.DefaultCancelPreviousPipelineEvents } repo.IsActive = true @@ -129,7 +130,7 @@ func PostRepo(c *gin.Context) { if errors.Is(err, types.RecordNotExist) { org, err = _forge.Org(c, user, repo.Owner) if err != nil { - msg := "could not fetch organization from forge." + msg := fmt.Sprintf("Organization %s not found in DB. Attempting to create new one.", repo.Owner) log.Error().Err(err).Msg(msg) c.String(http.StatusInternalServerError, msg) return @@ -138,7 +139,7 @@ func PostRepo(c *gin.Context) { org.ForgeID = user.ForgeID err = _store.OrgCreate(org) if err != nil { - msg := "could not create organization in store." + msg := fmt.Sprintf("Failed to create organization %s.", repo.Owner) log.Error().Err(err).Msg(msg) c.String(http.StatusInternalServerError, msg) return @@ -224,10 +225,23 @@ func PatchRepo(c *gin.Context) { c.String(http.StatusForbidden, fmt.Sprintf("Timeout is not allowed to be higher than max timeout (%d min)", server.Config.Pipeline.MaxTimeout)) return } - if in.IsTrusted != nil && *in.IsTrusted != repo.IsTrusted && !user.Admin { - log.Trace().Msgf("user '%s' wants to make repo trusted without being an instance admin", user.Login) - c.String(http.StatusForbidden, "Insufficient privileges") - return + + if in.Trusted != nil { + if (*in.Trusted.Network != repo.Trusted.Network || *in.Trusted.Volumes != repo.Trusted.Volumes || *in.Trusted.Security != repo.Trusted.Security) && !user.Admin { + log.Trace().Msgf("user '%s' wants to change trusted without being an instance admin", user.Login) + c.String(http.StatusForbidden, "Insufficient privileges") + return + } + + if in.Trusted.Network != nil { + repo.Trusted.Network = *in.Trusted.Network + } + if in.Trusted.Security != nil { + repo.Trusted.Security = *in.Trusted.Security + } + if in.Trusted.Volumes != nil { + repo.Trusted.Volumes = *in.Trusted.Volumes + } } if in.AllowPull != nil { @@ -236,11 +250,14 @@ func PatchRepo(c *gin.Context) { if in.AllowDeploy != nil { repo.AllowDeploy = *in.AllowDeploy } - if in.IsGated != nil { - repo.IsGated = *in.IsGated - } - if in.IsTrusted != nil { - repo.IsTrusted = *in.IsTrusted + + if in.RequireApproval != nil { + if mode := model.ApprovalMode(*in.RequireApproval); mode.Valid() { + repo.RequireApproval = mode + } else { + c.String(http.StatusBadRequest, "Invalid require-approval setting") + return + } } if in.Timeout != nil { repo.Timeout = *in.Timeout @@ -251,8 +268,8 @@ func PatchRepo(c *gin.Context) { if in.CancelPreviousPipelineEvents != nil { repo.CancelPreviousPipelineEvents = *in.CancelPreviousPipelineEvents } - if in.NetrcOnlyTrusted != nil { - repo.NetrcOnlyTrusted = *in.NetrcOnlyTrusted + if in.NetrcTrusted != nil { + repo.NetrcTrustedPlugins = *in.NetrcTrusted } if in.Visibility != nil { switch *in.Visibility { @@ -349,8 +366,8 @@ func GetRepoPermissions(c *gin.Context) { // @Param page query int false "for response pagination, page offset number" default(1) // @Param perPage query int false "for response pagination, max items per page" default(50) func GetRepoBranches(c *gin.Context) { + _store := store.FromContext(c) repo := session.Repo(c) - user := session.User(c) _forge, err := server.Config.Services.Manager.ForgeFromRepo(repo) if err != nil { log.Error().Err(err).Msg("Cannot get forge from repo") @@ -358,9 +375,18 @@ func GetRepoBranches(c *gin.Context) { return } - branches, err := _forge.Branches(c, user, repo, session.Pagination(c)) + repoUser, err := _store.GetUser(repo.UserID) if err != nil { - _ = c.AbortWithError(http.StatusInternalServerError, err) + handleDBError(c, err) + return + } + + forge.Refresh(c, _forge, _store, repoUser) + + branches, err := _forge.Branches(c, repoUser, repo, session.Pagination(c)) + if err != nil { + log.Error().Err(err).Msg("failed to load branches") + c.String(http.StatusInternalServerError, "failed to load branches: %s", err) return } @@ -379,8 +405,8 @@ func GetRepoBranches(c *gin.Context) { // @Param page query int false "for response pagination, page offset number" default(1) // @Param perPage query int false "for response pagination, max items per page" default(50) func GetRepoPullRequests(c *gin.Context) { + _store := store.FromContext(c) repo := session.Repo(c) - user := session.User(c) _forge, err := server.Config.Services.Manager.ForgeFromRepo(repo) if err != nil { log.Error().Err(err).Msg("Cannot get forge from repo") @@ -388,7 +414,15 @@ func GetRepoPullRequests(c *gin.Context) { return } - prs, err := _forge.PullRequests(c, user, repo, session.Pagination(c)) + repoUser, err := _store.GetUser(repo.UserID) + if err != nil { + handleDBError(c, err) + return + } + + forge.Refresh(c, _forge, _store, repoUser) + + prs, err := _forge.PullRequests(c, repoUser, repo, session.Pagination(c)) if err != nil { _ = c.AbortWithError(http.StatusInternalServerError, err) return @@ -550,16 +584,16 @@ func MoveRepo(c *gin.Context) { // GetAllRepos // -// @Summary List all repositories on the server +// @Summary List all repositories on the server // @Description Returns a list of all repositories. Requires admin rights. -// @Router /repos [get] -// @Produce json -// @Success 200 {array} Repo -// @Tags Repositories -// @Param Authorization header string true "Insert your personal access token" default(Bearer ) -// @Param active query bool false "only list active repos" -// @Param page query int false "for response pagination, page offset number" default(1) -// @Param perPage query int false "for response pagination, max items per page" default(50) +// @Router /repos [get] +// @Produce json +// @Success 200 {array} Repo +// @Tags Repositories +// @Param Authorization header string true "Insert your personal access token" default(Bearer ) +// @Param active query bool false "only list active repos" +// @Param page query int false "for response pagination, page offset number" default(1) +// @Param perPage query int false "for response pagination, max items per page" default(50) func GetAllRepos(c *gin.Context) { _store := store.FromContext(c) @@ -576,13 +610,13 @@ func GetAllRepos(c *gin.Context) { // RepairAllRepos // -// @Summary Repair all repositories on the server -// @Description Executes a repair process on all repositories. Requires admin rights. -// @Router /repos/repair [post] -// @Produce plain -// @Success 204 -// @Tags Repositories -// @Param Authorization header string true "Insert your personal access token" default(Bearer ) +// @Summary Repair all repositories on the server +// @Description Executes a repair process on all repositories. Requires admin rights. +// @Router /repos/repair [post] +// @Produce plain +// @Success 204 +// @Tags Repositories +// @Param Authorization header string true "Insert your personal access token" default(Bearer ) func RepairAllRepos(c *gin.Context) { _store := store.FromContext(c) @@ -614,10 +648,14 @@ func repairRepo(c *gin.Context, repo *model.Repo, withPerms, skipOnErr bool) { user, err := _store.GetUser(repo.UserID) if err != nil { if errors.Is(err, types.RecordNotExist) { - if !skipOnErr { - c.AbortWithStatus(http.StatusNotFound) + oldUserID := repo.UserID + user = session.User(c) + repo.UserID = user.ID + err = _store.UpdateRepo(repo) + if err != nil { + _ = c.AbortWithError(http.StatusInternalServerError, err) } - log.Error().Err(err).Msg("could not get user on repo repair") + log.Debug().Msgf("Could not find repo user with ID %d during repo repair, set to repair request user with ID %d", oldUserID, user.ID) } else { _ = c.AbortWithError(http.StatusInternalServerError, err) } diff --git a/server/api/repo_secret.go b/server/api/repo_secret.go index cdc58876d..59c73b4ff 100644 --- a/server/api/repo_secret.go +++ b/server/api/repo_secret.go @@ -19,9 +19,9 @@ import ( "github.com/gin-gonic/gin" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/router/middleware/session" ) // GetSecret diff --git a/server/api/signature_public_key.go b/server/api/signature_public_key.go index b9364166b..c3dd717e3 100644 --- a/server/api/signature_public_key.go +++ b/server/api/signature_public_key.go @@ -22,7 +22,7 @@ import ( "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server" + "go.woodpecker-ci.org/woodpecker/v3/server" ) // GetSignaturePublicKey diff --git a/server/api/stream.go b/server/api/stream.go index 2d3c7ee86..c948af171 100644 --- a/server/api/stream.go +++ b/server/api/stream.go @@ -27,11 +27,18 @@ import ( "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/pubsub" - "go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/logging" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/pubsub" + "go.woodpecker-ci.org/woodpecker/v3/server/router/middleware/session" + "go.woodpecker-ci.org/woodpecker/v3/server/store" +) + +const ( + // How many batches of logs to keep for each client before starting to + // drop them if the client is not consuming them faster than they arrive. + maxQueuedBatchesPerClient int = 30 ) // EventStreamSSE @@ -213,17 +220,32 @@ func LogStreamSSE(c *gin.Context) { } go func() { - err := server.Config.Services.Logs.Tail(ctx, step.ID, func(entries ...*model.LogEntry) { - for _, entry := range entries { - select { - case <-ctx.Done(): - return - default: - ee, _ := json.Marshal(entry) - logChan <- ee + batches := make(logging.LogChan, maxQueuedBatchesPerClient) + + go func() { + defer func() { + if r := recover(); r != nil { + log.Error().Msgf("error sending log message: %v", r) + } + }() + + for entries := range batches { + for _, entry := range entries { + select { + case <-ctx.Done(): + return + default: + if ee, err := json.Marshal(entry); err == nil { + logChan <- ee + } else { + log.Error().Err(err).Msg("unable to serialize log entry") + } + } } } - }) + }() + + err := server.Config.Services.Logs.Tail(ctx, step.ID, batches) if err != nil { log.Error().Err(err).Msg("tail of logs failed") } diff --git a/server/api/user.go b/server/api/user.go index 7a20da863..9b3690887 100644 --- a/server/api/user.go +++ b/server/api/user.go @@ -23,11 +23,11 @@ import ( "github.com/gorilla/securecookie" "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session" - "go.woodpecker-ci.org/woodpecker/v2/server/store" - "go.woodpecker-ci.org/woodpecker/v2/shared/token" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/router/middleware/session" + "go.woodpecker-ci.org/woodpecker/v3/server/store" + "go.woodpecker-ci.org/woodpecker/v3/shared/token" ) // GetSelf diff --git a/server/api/users.go b/server/api/users.go index 1a591d127..35df2724a 100644 --- a/server/api/users.go +++ b/server/api/users.go @@ -21,9 +21,9 @@ import ( "github.com/gin-gonic/gin" "github.com/gorilla/securecookie" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/router/middleware/session" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) // GetUsers diff --git a/server/api/z.go b/server/api/z.go index b1557733d..363040bfd 100644 --- a/server/api/z.go +++ b/server/api/z.go @@ -21,8 +21,8 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server/store" - "go.woodpecker-ci.org/woodpecker/v2/version" + "go.woodpecker-ci.org/woodpecker/v3/server/store" + "go.woodpecker-ci.org/woodpecker/v3/version" ) // Health @@ -48,7 +48,7 @@ func Health(c *gin.Context) { // @Description Endpoint returns the server version and build information. // @Router /version [get] // @Produce json -// @Success 200 {object} string{source=string,version=string} +// @Success 200 {object} object{source=string,version=string} // @Tags System func Version(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ diff --git a/server/badges/badges.go b/server/badges/badges.go index 99d82bd90..e05b356fb 100644 --- a/server/badges/badges.go +++ b/server/badges/badges.go @@ -14,7 +14,7 @@ package badges -import "go.woodpecker-ci.org/woodpecker/v2/server/model" +import "go.woodpecker-ci.org/woodpecker/v3/server/model" // cspell:words Verdana diff --git a/server/badges/badges_test.go b/server/badges/badges_test.go index 4f34488c4..7829be16d 100644 --- a/server/badges/badges_test.go +++ b/server/badges/badges_test.go @@ -19,7 +19,7 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) // Generate an SVG badge based on a pipeline. diff --git a/server/cache/membership.go b/server/cache/membership.go index c29aafdb0..0e7138eaa 100644 --- a/server/cache/membership.go +++ b/server/cache/membership.go @@ -21,9 +21,9 @@ import ( "github.com/jellydator/ttlcache/v3" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) // MembershipService is a service to check for user membership. diff --git a/server/ccmenu/cc.go b/server/ccmenu/cc.go index 6c8b77763..bfaf015c0 100644 --- a/server/ccmenu/cc.go +++ b/server/ccmenu/cc.go @@ -20,7 +20,7 @@ import ( "strconv" "time" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) // CCMenu displays the pipeline status of projects on a ci server as an item in the Mac's menu bar. diff --git a/server/ccmenu/cc_test.go b/server/ccmenu/cc_test.go index 316b68394..7cecdad63 100644 --- a/server/ccmenu/cc_test.go +++ b/server/ccmenu/cc_test.go @@ -19,82 +19,79 @@ import ( "testing" "time" - "github.com/franela/goblin" + "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func TestCC(t *testing.T) { - g := goblin.Goblin(t) - g.Describe("CC", func() { - g.It("Should create a project", func() { - now := time.Now().Unix() - nowFmt := time.Unix(now, 0).Format(time.RFC3339) - r := &model.Repo{ - FullName: "foo/bar", - } - b := &model.Pipeline{ - Status: model.StatusSuccess, - Number: 1, - Started: now, - } - cc := New(r, b, "http://localhost/foo/bar/1") + t.Run("create a project", func(t *testing.T) { + now := time.Now().Unix() + nowFmt := time.Unix(now, 0).Format(time.RFC3339) + r := &model.Repo{ + FullName: "foo/bar", + } + b := &model.Pipeline{ + Status: model.StatusSuccess, + Number: 1, + Started: now, + } + cc := New(r, b, "http://localhost/foo/bar/1") - g.Assert(cc.Project.Name).Equal("foo/bar") - g.Assert(cc.Project.Activity).Equal("Sleeping") - g.Assert(cc.Project.LastBuildStatus).Equal("Success") - g.Assert(cc.Project.LastBuildLabel).Equal("1") - g.Assert(cc.Project.LastBuildTime).Equal(nowFmt) - g.Assert(cc.Project.WebURL).Equal("http://localhost/foo/bar/1") - }) + assert.Equal(t, "foo/bar", cc.Project.Name) + assert.Equal(t, "Sleeping", cc.Project.Activity) + assert.Equal(t, "Success", cc.Project.LastBuildStatus) + assert.Equal(t, "1", cc.Project.LastBuildLabel) + assert.Equal(t, nowFmt, cc.Project.LastBuildTime) + assert.Equal(t, "http://localhost/foo/bar/1", cc.Project.WebURL) + }) - g.It("Should properly label exceptions", func() { - r := &model.Repo{FullName: "foo/bar"} - b := &model.Pipeline{ - Status: model.StatusError, - Number: 1, - Started: 1257894000, - } - cc := New(r, b, "http://localhost/foo/bar/1") - g.Assert(cc.Project.LastBuildStatus).Equal("Exception") - g.Assert(cc.Project.Activity).Equal("Sleeping") - }) + t.Run("properly label exceptions", func(t *testing.T) { + r := &model.Repo{FullName: "foo/bar"} + b := &model.Pipeline{ + Status: model.StatusError, + Number: 1, + Started: 1257894000, + } + cc := New(r, b, "http://localhost/foo/bar/1") + assert.Equal(t, "Exception", cc.Project.LastBuildStatus) + assert.Equal(t, "Sleeping", cc.Project.Activity) + }) - g.It("Should properly label success", func() { - r := &model.Repo{FullName: "foo/bar"} - b := &model.Pipeline{ - Status: model.StatusSuccess, - Number: 1, - Started: 1257894000, - } - cc := New(r, b, "http://localhost/foo/bar/1") - g.Assert(cc.Project.LastBuildStatus).Equal("Success") - g.Assert(cc.Project.Activity).Equal("Sleeping") - }) + t.Run("properly label success", func(t *testing.T) { + r := &model.Repo{FullName: "foo/bar"} + b := &model.Pipeline{ + Status: model.StatusSuccess, + Number: 1, + Started: 1257894000, + } + cc := New(r, b, "http://localhost/foo/bar/1") + assert.Equal(t, "Success", cc.Project.LastBuildStatus) + assert.Equal(t, "Sleeping", cc.Project.Activity) + }) - g.It("Should properly label failure", func() { - r := &model.Repo{FullName: "foo/bar"} - b := &model.Pipeline{ - Status: model.StatusFailure, - Number: 1, - Started: 1257894000, - } - cc := New(r, b, "http://localhost/foo/bar/1") - g.Assert(cc.Project.LastBuildStatus).Equal("Failure") - g.Assert(cc.Project.Activity).Equal("Sleeping") - }) + t.Run("properly label failure", func(t *testing.T) { + r := &model.Repo{FullName: "foo/bar"} + b := &model.Pipeline{ + Status: model.StatusFailure, + Number: 1, + Started: 1257894000, + } + cc := New(r, b, "http://localhost/foo/bar/1") + assert.Equal(t, "Failure", cc.Project.LastBuildStatus) + assert.Equal(t, "Sleeping", cc.Project.Activity) + }) - g.It("Should properly label running", func() { - r := &model.Repo{FullName: "foo/bar"} - b := &model.Pipeline{ - Status: model.StatusRunning, - Number: 1, - Started: 1257894000, - } - cc := New(r, b, "http://localhost/foo/bar/1") - g.Assert(cc.Project.Activity).Equal("Building") - g.Assert(cc.Project.LastBuildStatus).Equal("Unknown") - g.Assert(cc.Project.LastBuildLabel).Equal("Unknown") - }) + t.Run("properly label running", func(t *testing.T) { + r := &model.Repo{FullName: "foo/bar"} + b := &model.Pipeline{ + Status: model.StatusRunning, + Number: 1, + Started: 1257894000, + } + cc := New(r, b, "http://localhost/foo/bar/1") + assert.Equal(t, "Building", cc.Project.Activity) + assert.Equal(t, "Unknown", cc.Project.LastBuildStatus) + assert.Equal(t, "Unknown", cc.Project.LastBuildLabel) }) } diff --git a/server/config.go b/server/config.go index bb5ba721e..7f9d51a2e 100644 --- a/server/config.go +++ b/server/config.go @@ -18,14 +18,14 @@ package server import ( "time" - "go.woodpecker-ci.org/woodpecker/v2/server/cache" - "go.woodpecker-ci.org/woodpecker/v2/server/logging" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/pubsub" - "go.woodpecker-ci.org/woodpecker/v2/server/queue" - "go.woodpecker-ci.org/woodpecker/v2/server/services" - "go.woodpecker-ci.org/woodpecker/v2/server/services/log" - "go.woodpecker-ci.org/woodpecker/v2/server/services/permissions" + "go.woodpecker-ci.org/woodpecker/v3/server/cache" + "go.woodpecker-ci.org/woodpecker/v3/server/logging" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/pubsub" + "go.woodpecker-ci.org/woodpecker/v3/server/queue" + "go.woodpecker-ci.org/woodpecker/v3/server/services" + "go.woodpecker-ci.org/woodpecker/v3/server/services/log" + "go.woodpecker-ci.org/woodpecker/v3/server/services/permissions" ) var Config = struct { @@ -54,6 +54,9 @@ var Config = struct { CustomCSSFile string CustomJsFile string } + Agent struct { + DisableUserRegisteredAgentRegistration bool + } WebUI struct { EnableSwagger bool SkipVersionCheck bool @@ -64,11 +67,12 @@ var Config = struct { Pipeline struct { AuthenticatePublicRepos bool DefaultCancelPreviousPipelineEvents []model.WebhookEvent - DefaultCloneImage string - Limits model.ResourceLimit + DefaultWorkflowLabels map[string]string + DefaultClonePlugin string + TrustedClonePlugins []string Volumes []string Networks []string - Privileged []string + PrivilegedPlugins []string DefaultTimeout int64 MaxTimeout int64 Proxy struct { diff --git a/server/cron/cron.go b/server/cron/cron.go index a1dcddd29..3460fbaa4 100644 --- a/server/cron/cron.go +++ b/server/cron/cron.go @@ -19,26 +19,26 @@ import ( "fmt" "time" - "github.com/robfig/cron" + "github.com/gdgvda/cron" "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/pipeline" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/pipeline" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) const ( // Specifies the interval woodpecker checks for new crons to exec. - checkTime = 10 * time.Second + checkTime = time.Minute // Specifies the batch size of crons to retrieve per check from database. checkItems = 10 ) -// Start starts the cron scheduler loop. -func Start(ctx context.Context, store store.Store) error { +// Run starts the cron scheduler loop. +func Run(ctx context.Context, store store.Store) error { for { select { case <-ctx.Done(): @@ -71,7 +71,7 @@ func CalcNewNext(schedule string, now time.Time) (time.Time, error) { // TODO: allow the users / the admin to set a specific timezone - c, err := cron.Parse(schedule) + c, err := cron.ParseStandard(schedule) if err != nil { return time.Time{}, fmt.Errorf("cron parse schedule: %w", err) } diff --git a/server/cron/cron_test.go b/server/cron/cron_test.go index 61f1a2538..ca39839f1 100644 --- a/server/cron/cron_test.go +++ b/server/cron/cron_test.go @@ -22,11 +22,11 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "go.woodpecker-ci.org/woodpecker/v2/server" - mocks_forge "go.woodpecker-ci.org/woodpecker/v2/server/forge/mocks" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - mocks_manager "go.woodpecker-ci.org/woodpecker/v2/server/services/mocks" - mocks_store "go.woodpecker-ci.org/woodpecker/v2/server/store/mocks" + "go.woodpecker-ci.org/woodpecker/v3/server" + mocks_forge "go.woodpecker-ci.org/woodpecker/v3/server/forge/mocks" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + mocks_manager "go.woodpecker-ci.org/woodpecker/v3/server/services/mocks" + mocks_store "go.woodpecker-ci.org/woodpecker/v3/server/store/mocks" ) func TestCreatePipeline(t *testing.T) { diff --git a/server/forge/addon/args.go b/server/forge/addon/args.go index c0ef43d46..a8677c6b1 100644 --- a/server/forge/addon/args.go +++ b/server/forge/addon/args.go @@ -15,7 +15,7 @@ package addon import ( - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) type argumentsAuth struct { @@ -110,20 +110,26 @@ type modelUser struct { } func (m *modelUser) asModel() *model.User { + if m == nil { + return nil + } m.User.ForgeRemoteID = m.ForgeRemoteID - m.User.Token = m.Token - m.User.Secret = m.Secret + m.User.AccessToken = m.Token + m.User.RefreshToken = m.Secret m.User.Expiry = m.Expiry m.User.Hash = m.Hash return m.User } func modelUserFromModel(u *model.User) *modelUser { + if u == nil { + return nil + } return &modelUser{ User: u, ForgeRemoteID: u.ForgeRemoteID, - Token: u.Token, - Secret: u.Secret, + Token: u.AccessToken, + Secret: u.RefreshToken, Expiry: u.Expiry, Hash: u.Hash, } @@ -138,6 +144,9 @@ type modelRepo struct { } func (m *modelRepo) asModel() *model.Repo { + if m == nil { + return nil + } m.Repo.UserID = m.UserID m.Repo.Hash = m.Hash m.Repo.Perm = m.Perm @@ -145,6 +154,9 @@ func (m *modelRepo) asModel() *model.Repo { } func modelRepoFromModel(r *model.Repo) *modelRepo { + if r == nil { + return nil + } return &modelRepo{ Repo: r, UserID: r.UserID, diff --git a/server/forge/addon/client.go b/server/forge/addon/client.go index 46d600e6e..9640eb782 100644 --- a/server/forge/addon/client.go +++ b/server/forge/addon/client.go @@ -25,9 +25,9 @@ import ( "github.com/hashicorp/go-plugin" "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) // make sure RPC implements forge.Forge. @@ -139,8 +139,8 @@ func (g *RPC) Repo(_ context.Context, u *model.User, remoteID model.ForgeRemoteI return nil, err } - var resp *modelRepo - err = json.Unmarshal(jsonResp, resp) + var resp modelRepo + err = json.Unmarshal(jsonResp, &resp) if err != nil { return nil, err } diff --git a/server/forge/addon/plugin.go b/server/forge/addon/plugin.go index 21099cf68..7c7faa833 100644 --- a/server/forge/addon/plugin.go +++ b/server/forge/addon/plugin.go @@ -19,7 +19,7 @@ import ( "github.com/hashicorp/go-plugin" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" ) const pluginKey = "forge" diff --git a/server/forge/addon/server.go b/server/forge/addon/server.go index a65967eaa..abf14e214 100644 --- a/server/forge/addon/server.go +++ b/server/forge/addon/server.go @@ -22,8 +22,8 @@ import ( "github.com/hashicorp/go-plugin" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" ) func Serve(impl forge.Forge) { @@ -54,8 +54,8 @@ func (s *RPCServer) URL(_ []byte, resp *string) error { } func (s *RPCServer) Teams(args []byte, resp *[]byte) error { - var a *modelUser - err := json.Unmarshal(args, a) + var a modelUser + err := json.Unmarshal(args, &a) if err != nil { return err } @@ -82,8 +82,8 @@ func (s *RPCServer) Repo(args []byte, resp *[]byte) error { } func (s *RPCServer) Repos(args []byte, resp *[]byte) error { - var a *modelUser - err := json.Unmarshal(args, a) + var a modelUser + err := json.Unmarshal(args, &a) if err != nil { return err } @@ -261,12 +261,12 @@ func (s *RPCServer) Hook(args []byte, resp *[]byte) error { } func (s *RPCServer) Login(args []byte, resp *[]byte) error { - var a *types.OAuthRequest - err := json.Unmarshal(args, a) + var a types.OAuthRequest + err := json.Unmarshal(args, &a) if err != nil { return err } - user, red, err := s.Impl.Login(mkCtx(), a) + user, red, err := s.Impl.Login(mkCtx(), &a) if err != nil { return err } diff --git a/server/forge/bitbucket/bitbucket.go b/server/forge/bitbucket/bitbucket.go index 2fc95a8d1..bbb5ac43b 100644 --- a/server/forge/bitbucket/bitbucket.go +++ b/server/forge/bitbucket/bitbucket.go @@ -26,13 +26,13 @@ import ( "golang.org/x/oauth2" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/bitbucket/internal" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/common" - forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - shared_utils "go.woodpecker-ci.org/woodpecker/v2/shared/utils" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/bitbucket/internal" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/common" + forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + shared_utils "go.woodpecker-ci.org/woodpecker/v3/shared/utils" ) // Bitbucket cloud endpoints. @@ -117,15 +117,15 @@ func (c *config) Auth(ctx context.Context, token, secret string) (string, error) func (c *config) Refresh(ctx context.Context, user *model.User) (bool, error) { config := c.newOAuth2Config() source := config.TokenSource( - ctx, &oauth2.Token{RefreshToken: user.Secret}) + ctx, &oauth2.Token{RefreshToken: user.RefreshToken}) token, err := source.Token() if err != nil || len(token.AccessToken) == 0 { return false, err } - user.Token = token.AccessToken - user.Secret = token.RefreshToken + user.AccessToken = token.AccessToken + user.RefreshToken = token.RefreshToken user.Expiry = token.Expiry.UTC().Unix() return true, nil } @@ -143,7 +143,7 @@ func (c *config) Teams(ctx context.Context, u *model.User) ([]*model.Team, error return nil, err } return convertWorkspaceList(resp.Values), nil - }) + }, -1) } // Repo returns the named Bitbucket repository. @@ -190,7 +190,7 @@ func (c *config) Repos(ctx context.Context, u *model.User) ([]*model.Repo, error return nil, err } return resp.Values, nil - }) + }, -1) if err != nil { return nil, err } @@ -290,11 +290,11 @@ func (c *config) Dir(ctx context.Context, u *model.User, r *model.Repo, p *model } // Status creates a pipeline status for the Bitbucket commit. -func (c *config) Status(ctx context.Context, user *model.User, repo *model.Repo, pipeline *model.Pipeline, _ *model.Workflow) error { +func (c *config) Status(ctx context.Context, user *model.User, repo *model.Repo, pipeline *model.Pipeline, workflow *model.Workflow) error { status := internal.PipelineStatus{ State: convertStatus(pipeline.Status), Desc: common.GetPipelineStatusDescription(pipeline.Status), - Key: "Woodpecker", + Key: common.GetPipelineStatusContext(repo, pipeline, workflow), URL: common.GetPipelineStatusURL(repo, pipeline, nil), } return c.newClient(ctx, user).CreateStatus(repo.Owner, repo.Name, pipeline.Commit, &status) @@ -313,7 +313,7 @@ func (c *config) Activate(ctx context.Context, u *model.User, r *model.Repo, lin return c.newClient(ctx, u).CreateHook(r.Owner, r.Name, &internal.Hook{ Active: true, Desc: rawURL.Host, - Events: []string{"repo:push", "pullrequest:created"}, + Events: []string{"repo:push", "pullrequest:created", "pullrequest:updated", "pullrequest:fulfilled", "pullrequest:rejected"}, URL: link, }) } @@ -331,7 +331,7 @@ func (c *config) Deactivate(ctx context.Context, u *model.User, r *model.Repo, l return nil, err } return hooks.Values, nil - }) + }, -1) if err != nil { return err } @@ -348,7 +348,7 @@ func (c *config) Netrc(u *model.User, _ *model.Repo) (*model.Netrc, error) { return &model.Netrc{ Machine: "bitbucket.org", Login: "x-token-auth", - Password: u.Token, + Password: u.AccessToken, }, nil } @@ -428,7 +428,7 @@ func (c *config) newClient(ctx context.Context, u *model.User) *internal.Client if u == nil { return c.newClientToken(ctx, "", "") } - return c.newClientToken(ctx, u.Token, u.Secret) + return c.newClientToken(ctx, u.AccessToken, u.RefreshToken) } // helper function to return the bitbucket oauth2 client. diff --git a/server/forge/bitbucket/bitbucket_test.go b/server/forge/bitbucket/bitbucket_test.go index 4a9760819..c074cfcf2 100644 --- a/server/forge/bitbucket/bitbucket_test.go +++ b/server/forge/bitbucket/bitbucket_test.go @@ -18,313 +18,231 @@ package bitbucket import ( "bytes" "context" - "errors" "net/http" "net/http/httptest" "testing" - "github.com/franela/goblin" "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/bitbucket/fixtures" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/bitbucket/internal" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/bitbucket/fixtures" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/bitbucket/internal" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) -func Test_bitbucket(t *testing.T) { +func TestNew(t *testing.T) { + forge, _ := New(&Opts{Client: "4vyW6b49Z", Secret: "a5012f6c6"}) + + f, _ := forge.(*config) + assert.Equal(t, DefaultURL, f.url) + assert.Equal(t, DefaultAPI, f.API) + assert.Equal(t, "4vyW6b49Z", f.Client) + assert.Equal(t, "a5012f6c6", f.Secret) +} + +func TestBitbucket(t *testing.T) { gin.SetMode(gin.TestMode) s := httptest.NewServer(fixtures.Handler()) + defer s.Close() c := &config{url: s.URL, API: s.URL} - g := goblin.Goblin(t) ctx := context.Background() - g.Describe("Bitbucket client", func() { - g.After(func() { - s.Close() - }) - g.It("Should return client with default endpoint", func() { - forge, _ := New(&Opts{Client: "4vyW6b49Z", Secret: "a5012f6c6"}) + forge, _ := New(&Opts{}) + netrc, _ := forge.Netrc(fakeUser, fakeRepo) + assert.Equal(t, "bitbucket.org", netrc.Machine) + assert.Equal(t, "x-token-auth", netrc.Login) + assert.Equal(t, fakeUser.AccessToken, netrc.Password) - f, _ := forge.(*config) - g.Assert(f.url).Equal(DefaultURL) - g.Assert(f.API).Equal(DefaultAPI) - g.Assert(f.Client).Equal("4vyW6b49Z") - g.Assert(f.Secret).Equal("a5012f6c6") - }) + user, _, err := c.Login(ctx, &types.OAuthRequest{}) + assert.NoError(t, err) + assert.Nil(t, user) - g.It("Should return the netrc file", func() { - forge, _ := New(&Opts{}) - netrc, _ := forge.Netrc(fakeUser, fakeRepo) - g.Assert(netrc.Machine).Equal("bitbucket.org") - g.Assert(netrc.Login).Equal("x-token-auth") - g.Assert(netrc.Password).Equal(fakeUser.Token) - }) - - g.Describe("Given an authorization request", func() { - g.It("Should redirect to authorize", func() { - user, _, err := c.Login(ctx, &types.OAuthRequest{}) - g.Assert(err).IsNil() - g.Assert(user).IsNil() - }) - g.It("Should return authenticated user", func() { - u, _, err := c.Login(ctx, &types.OAuthRequest{ - Code: "code", - }) - g.Assert(err).IsNil() - g.Assert(u.Login).Equal(fakeUser.Login) - g.Assert(u.Token).Equal("2YotnFZFEjr1zCsicMWpAA") - g.Assert(u.Secret).Equal("tGzv3JOkF0XG5Qx2TlKWIA") - }) - g.It("Should handle failure to exchange code", func() { - _, _, err := c.Login(ctx, &types.OAuthRequest{ - Code: "code_bad_request", - }) - g.Assert(err).IsNotNil() - }) - g.It("Should handle failure to resolve user", func() { - _, _, err := c.Login(ctx, &types.OAuthRequest{ - Code: "code_user_not_found", - }) - g.Assert(err).IsNotNil() - }) - }) - - g.Describe("Given an access token", func() { - g.It("Should return the authenticated user", func() { - login, err := c.Auth(ctx, fakeUser.Token, fakeUser.Secret) - g.Assert(err).IsNil() - g.Assert(login).Equal(fakeUser.Login) - }) - g.It("Should handle a failure to resolve user", func() { - _, err := c.Auth(ctx, fakeUserNotFound.Token, fakeUserNotFound.Secret) - g.Assert(err).IsNotNil() - }) - }) - - g.Describe("Given a refresh token", func() { - g.It("Should return a refresh access token", func() { - ok, err := c.Refresh(ctx, fakeUserRefresh) - g.Assert(err).IsNil() - g.Assert(ok).IsTrue() - g.Assert(fakeUserRefresh.Token).Equal("2YotnFZFEjr1zCsicMWpAA") - g.Assert(fakeUserRefresh.Secret).Equal("tGzv3JOkF0XG5Qx2TlKWIA") - }) - g.It("Should handle an empty access token", func() { - ok, err := c.Refresh(ctx, fakeUserRefreshEmpty) - g.Assert(err).IsNotNil() - g.Assert(ok).IsFalse() - }) - g.It("Should handle a failure to refresh", func() { - ok, err := c.Refresh(ctx, fakeUserRefreshFail) - g.Assert(err).IsNotNil() - g.Assert(ok).IsFalse() - }) - }) - - g.Describe("When requesting a repository", func() { - g.It("Should return the details", func() { - repo, err := c.Repo(ctx, fakeUser, "", fakeRepo.Owner, fakeRepo.Name) - g.Assert(err).IsNil() - g.Assert(repo.FullName).Equal(fakeRepo.FullName) - }) - g.It("Should handle not found errors", func() { - _, err := c.Repo(ctx, fakeUser, "", fakeRepoNotFound.Owner, fakeRepoNotFound.Name) - g.Assert(err).IsNotNil() - }) - }) - - g.Describe("When requesting user repositories", func() { - g.It("Should return the details", func() { - repos, err := c.Repos(ctx, fakeUser) - g.Assert(err).IsNil() - g.Assert(repos[0].FullName).Equal(fakeRepo.FullName) - }) - g.It("Should handle organization not found errors", func() { - _, err := c.Repos(ctx, fakeUserNoTeams) - g.Assert(err).IsNotNil() - }) - g.It("Should handle not found errors", func() { - _, err := c.Repos(ctx, fakeUserNoRepos) - g.Assert(err).IsNotNil() - }) - }) - - g.Describe("When requesting user teams", func() { - g.It("Should return the details", func() { - teams, err := c.Teams(ctx, fakeUser) - g.Assert(err).IsNil() - g.Assert(teams[0].Login).Equal("ueberdev42") - g.Assert(teams[0].Avatar).Equal("https://bitbucket.org/workspaces/ueberdev42/avatar/?ts=1658761964") - }) - g.It("Should handle not found error", func() { - _, err := c.Teams(ctx, fakeUserNoTeams) - g.Assert(err).IsNotNil() - }) - }) - - g.Describe("When downloading a file", func() { - g.It("Should return the bytes", func() { - raw, err := c.File(ctx, fakeUser, fakeRepo, fakePipeline, "file") - g.Assert(err).IsNil() - g.Assert(len(raw) != 0).IsTrue() - }) - g.It("Should handle not found error", func() { - _, err := c.File(ctx, fakeUser, fakeRepo, fakePipeline, "file_not_found") - g.Assert(err).IsNotNil() - g.Assert(errors.Is(err, &types.ErrConfigNotFound{})).IsTrue() - }) - }) - - g.Describe("When requesting repo branch HEAD", func() { - g.It("Should return the details", func() { - branchHead, err := c.BranchHead(ctx, fakeUser, fakeRepo, "branch_name") - g.Assert(err).IsNil() - g.Assert(branchHead.SHA).Equal("branch_head_name") - g.Assert(branchHead.ForgeURL).Equal("https://bitbucket.org/commitlink") - }) - g.It("Should handle not found errors", func() { - _, err := c.BranchHead(ctx, fakeUser, fakeRepo, "branch_not_found") - g.Assert(err).IsNotNil() - }) - }) - - g.Describe("When requesting repo pull requests", func() { - listOpts := model.ListOptions{ - All: false, - Page: 1, - PerPage: 10, - } - g.It("Should return the details", func() { - repoPRs, err := c.PullRequests(ctx, fakeUser, fakeRepo, &listOpts) - g.Assert(err).IsNil() - g.Assert(repoPRs[0].Title).Equal("PRs title") - g.Assert(repoPRs[0].Index).Equal(model.ForgeRemoteID("123")) - }) - g.It("Should handle not found errors", func() { - _, err := c.PullRequests(ctx, fakeUser, fakeRepoNotFound, &listOpts) - g.Assert(err).IsNotNil() - }) - }) - - g.Describe("When requesting repo directory contents", func() { - g.It("Should return the details", func() { - files, err := c.Dir(ctx, fakeUser, fakeRepo, fakePipeline, "dir") - g.Assert(err).IsNil() - g.Assert(len(files)).Equal(3) - g.Assert(files[0].Name).Equal("README.md") - g.Assert(string(files[0].Data)).Equal("dummy payload") - }) - g.It("Should handle not found errors", func() { - _, err := c.Dir(ctx, fakeUser, fakeRepo, fakePipeline, "dir_not_found") - g.Assert(err).IsNotNil() - g.Assert(errors.Is(err, &types.ErrConfigNotFound{})).IsTrue() - }) - }) - - g.Describe("When activating a repository", func() { - g.It("Should error when malformed hook", func() { - err := c.Activate(ctx, fakeUser, fakeRepo, "%gh&%ij") - g.Assert(err).IsNotNil() - }) - g.It("Should create the hook", func() { - err := c.Activate(ctx, fakeUser, fakeRepo, "http://127.0.0.1") - g.Assert(err).IsNil() - }) - }) - - g.Describe("When deactivating a repository", func() { - g.It("Should error when listing hooks fails", func() { - err := c.Deactivate(ctx, fakeUser, fakeRepoNoHooks, "http://127.0.0.1") - g.Assert(err).IsNotNil() - }) - g.It("Should successfully remove hooks", func() { - err := c.Deactivate(ctx, fakeUser, fakeRepo, "http://127.0.0.1") - g.Assert(err).IsNil() - }) - g.It("Should successfully deactivate when hook already removed", func() { - err := c.Deactivate(ctx, fakeUser, fakeRepoEmptyHook, "http://127.0.0.1") - g.Assert(err).IsNil() - }) - }) - - g.Describe("Given a list of hooks", func() { - g.It("Should return the matching hook", func() { - hooks := []*internal.Hook{ - {URL: "http://127.0.0.1/hook"}, - } - hook := matchingHooks(hooks, "http://127.0.0.1/") - g.Assert(hook).Equal(hooks[0]) - }) - g.It("Should handle no matches", func() { - hooks := []*internal.Hook{ - {URL: "http://localhost/hook"}, - } - hook := matchingHooks(hooks, "http://127.0.0.1/") - g.Assert(hook).IsNil() - }) - g.It("Should handle malformed hook urls", func() { - var hooks []*internal.Hook - hook := matchingHooks(hooks, "%gh&%ij") - g.Assert(hook).IsNil() - }) - }) - - g.It("Should update the status", func() { - err := c.Status(ctx, fakeUser, fakeRepo, fakePipeline, fakeWorkflow) - g.Assert(err).IsNil() - }) - - g.It("Should parse the hook", func() { - buf := bytes.NewBufferString(fixtures.HookPush) - req, _ := http.NewRequest(http.MethodPost, "/hook", buf) - req.Header = http.Header{} - req.Header.Set(hookEvent, hookPush) - - r, b, err := c.Hook(ctx, req) - g.Assert(err).IsNil() - g.Assert(r.FullName).Equal("martinherren1984/publictestrepo") - g.Assert(b.Commit).Equal("c14c1bb05dfb1fdcdf06b31485fff61b0ea44277") - }) + u, _, err := c.Login(ctx, &types.OAuthRequest{ + Code: "code", }) + assert.NoError(t, err) + assert.Equal(t, fakeUser.Login, u.Login) + assert.Equal(t, "2YotnFZFEjr1zCsicMWpAA", u.AccessToken) + assert.Equal(t, "tGzv3JOkF0XG5Qx2TlKWIA", u.RefreshToken) + + _, _, err = c.Login(ctx, &types.OAuthRequest{ + Code: "code_bad_request", + }) + assert.Error(t, err) + + _, _, err = c.Login(ctx, &types.OAuthRequest{ + Code: "code_user_not_found", + }) + assert.Error(t, err) + + login, err := c.Auth(ctx, fakeUser.AccessToken, fakeUser.RefreshToken) + assert.NoError(t, err) + assert.Equal(t, fakeUser.Login, login) + + _, err = c.Auth(ctx, fakeUserNotFound.AccessToken, fakeUserNotFound.RefreshToken) + assert.Error(t, err) + + ok, err := c.Refresh(ctx, fakeUserRefresh) + assert.NoError(t, err) + assert.True(t, ok) + assert.Equal(t, "2YotnFZFEjr1zCsicMWpAA", fakeUserRefresh.AccessToken) + assert.Equal(t, "tGzv3JOkF0XG5Qx2TlKWIA", fakeUserRefresh.RefreshToken) + + ok, err = c.Refresh(ctx, fakeUserRefreshEmpty) + assert.Error(t, err) + assert.False(t, ok) + + ok, err = c.Refresh(ctx, fakeUserRefreshFail) + assert.Error(t, err) + assert.False(t, ok) + + repo, err := c.Repo(ctx, fakeUser, "", fakeRepo.Owner, fakeRepo.Name) + assert.NoError(t, err) + assert.Equal(t, fakeRepo.FullName, repo.FullName) + + _, err = c.Repo(ctx, fakeUser, "", fakeRepoNotFound.Owner, fakeRepoNotFound.Name) + assert.Error(t, err) + + repos, err := c.Repos(ctx, fakeUser) + assert.NoError(t, err) + assert.Equal(t, fakeRepo.FullName, repos[0].FullName) + + _, err = c.Repos(ctx, fakeUserNoTeams) + assert.Error(t, err) + + _, err = c.Repos(ctx, fakeUserNoRepos) + assert.Error(t, err) + + teams, err := c.Teams(ctx, fakeUser) + assert.NoError(t, err) + assert.Equal(t, "ueberdev42", teams[0].Login) + assert.Equal(t, "https://bitbucket.org/workspaces/ueberdev42/avatar/?ts=1658761964", teams[0].Avatar) + + _, err = c.Teams(ctx, fakeUserNoTeams) + assert.Error(t, err) + + raw, err := c.File(ctx, fakeUser, fakeRepo, fakePipeline, "file") + assert.NoError(t, err) + assert.True(t, len(raw) != 0) + + _, err = c.File(ctx, fakeUser, fakeRepo, fakePipeline, "file_not_found") + assert.Error(t, err) + assert.ErrorIs(t, err, &types.ErrConfigNotFound{}) + + branchHead, err := c.BranchHead(ctx, fakeUser, fakeRepo, "branch_name") + assert.NoError(t, err) + assert.Equal(t, "branch_head_name", branchHead.SHA) + assert.Equal(t, "https://bitbucket.org/commitlink", branchHead.ForgeURL) + + _, err = c.BranchHead(ctx, fakeUser, fakeRepo, "branch_not_found") + assert.Error(t, err) + + listOpts := model.ListOptions{ + All: false, + Page: 1, + PerPage: 10, + } + + repoPRs, err := c.PullRequests(ctx, fakeUser, fakeRepo, &listOpts) + assert.NoError(t, err) + assert.Equal(t, "PRs title", repoPRs[0].Title) + assert.Equal(t, model.ForgeRemoteID("123"), repoPRs[0].Index) + + _, err = c.PullRequests(ctx, fakeUser, fakeRepoNotFound, &listOpts) + assert.Error(t, err) + + files, err := c.Dir(ctx, fakeUser, fakeRepo, fakePipeline, "dir") + assert.NoError(t, err) + assert.Len(t, files, 3) + assert.Equal(t, "README.md", files[0].Name) + assert.Equal(t, "dummy payload", string(files[0].Data)) + + _, err = c.Dir(ctx, fakeUser, fakeRepo, fakePipeline, "dir_not_found") + assert.Error(t, err) + assert.ErrorIs(t, err, &types.ErrConfigNotFound{}) + + err = c.Activate(ctx, fakeUser, fakeRepo, "%gh&%ij") + assert.Error(t, err) + + err = c.Activate(ctx, fakeUser, fakeRepo, "http://127.0.0.1") + assert.NoError(t, err) + + err = c.Deactivate(ctx, fakeUser, fakeRepoNoHooks, "http://127.0.0.1") + assert.Error(t, err) + + err = c.Deactivate(ctx, fakeUser, fakeRepo, "http://127.0.0.1") + assert.NoError(t, err) + + err = c.Deactivate(ctx, fakeUser, fakeRepoEmptyHook, "http://127.0.0.1") + assert.NoError(t, err) + + hooks := []*internal.Hook{ + {URL: "http://127.0.0.1/hook"}, + } + hook := matchingHooks(hooks, "http://127.0.0.1/") + assert.Equal(t, hooks[0], hook) + + hooks = []*internal.Hook{ + {URL: "http://localhost/hook"}, + } + hook = matchingHooks(hooks, "http://127.0.0.1/") + assert.Nil(t, hook) + + hooks = nil + hook = matchingHooks(hooks, "%gh&%ij") + assert.Nil(t, hook) + + err = c.Status(ctx, fakeUser, fakeRepo, fakePipeline, fakeWorkflow) + assert.NoError(t, err) + + buf := bytes.NewBufferString(fixtures.HookPush) + req, _ := http.NewRequest(http.MethodPost, "/hook", buf) + req.Header = http.Header{} + req.Header.Set(hookEvent, hookPush) + + r, b, err := c.Hook(ctx, req) + assert.NoError(t, err) + assert.Equal(t, "martinherren1984/publictestrepo", r.FullName) + assert.Equal(t, "c14c1bb05dfb1fdcdf06b31485fff61b0ea44277", b.Commit) } var ( fakeUser = &model.User{ - Login: "superman", - Token: "cfcd2084", + Login: "superman", + AccessToken: "cfcd2084", } fakeUserRefresh = &model.User{ - Login: "superman", - Secret: "cfcd2084", + Login: "superman", + RefreshToken: "cfcd2084", } fakeUserRefreshFail = &model.User{ - Login: "superman", - Secret: "refresh_token_not_found", + Login: "superman", + RefreshToken: "refresh_token_not_found", } fakeUserRefreshEmpty = &model.User{ - Login: "superman", - Secret: "refresh_token_is_empty", + Login: "superman", + RefreshToken: "refresh_token_is_empty", } fakeUserNotFound = &model.User{ - Login: "superman", - Token: "user_not_found", + Login: "superman", + AccessToken: "user_not_found", } fakeUserNoTeams = &model.User{ - Login: "superman", - Token: "teams_not_found", + Login: "superman", + AccessToken: "teams_not_found", } fakeUserNoRepos = &model.User{ - Login: "superman", - Token: "repos_not_found", + Login: "superman", + AccessToken: "repos_not_found", } fakeRepo = &model.Repo{ diff --git a/server/forge/bitbucket/convert.go b/server/forge/bitbucket/convert.go index af573cbff..b7b13c6b4 100644 --- a/server/forge/bitbucket/convert.go +++ b/server/forge/bitbucket/convert.go @@ -23,8 +23,8 @@ import ( "golang.org/x/oauth2" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/bitbucket/internal" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/bitbucket/internal" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) const ( @@ -59,14 +59,10 @@ func convertRepo(from *internal.Repo, perm *internal.RepoPerm) *model.Repo { ForgeURL: from.Links.HTML.Href, IsSCMPrivate: from.IsPrivate, Avatar: from.Owner.Links.Avatar.Href, - SCMKind: model.SCMKind(from.Scm), Branch: from.MainBranch.Name, Perm: convertPerm(perm), PREnabled: true, } - if repo.SCMKind == model.RepoHg { - repo.Branch = "default" - } return &repo } @@ -133,8 +129,8 @@ func sshCloneLink(repo *internal.Repo) string { func convertUser(from *internal.Account, token *oauth2.Token) *model.User { return &model.User{ Login: from.Login, - Token: token.AccessToken, - Secret: token.RefreshToken, + AccessToken: token.AccessToken, + RefreshToken: token.RefreshToken, Expiry: token.Expiry.UTC().Unix(), Avatar: from.Links.Avatar.Href, ForgeRemoteID: model.ForgeRemoteID(fmt.Sprint(from.UUID)), @@ -168,22 +164,31 @@ func convertPullHook(from *internal.PullRequestHook) *model.Pipeline { event = model.EventPullClosed } - return &model.Pipeline{ + pipeline := &model.Pipeline{ Event: event, - Commit: from.PullRequest.Dest.Commit.Hash, - Ref: fmt.Sprintf("refs/heads/%s", from.PullRequest.Dest.Branch.Name), + Commit: from.PullRequest.Source.Commit.Hash, + Ref: fmt.Sprintf("refs/pull-requests/%d/from", from.PullRequest.ID), Refspec: fmt.Sprintf("%s:%s", from.PullRequest.Source.Branch.Name, from.PullRequest.Dest.Branch.Name, ), ForgeURL: from.PullRequest.Links.HTML.Href, - Branch: from.PullRequest.Dest.Branch.Name, - Message: from.PullRequest.Desc, + Branch: from.PullRequest.Source.Branch.Name, + Message: from.PullRequest.Title, Avatar: from.Actor.Links.Avatar.Href, Author: from.Actor.Login, Sender: from.Actor.Login, Timestamp: from.PullRequest.Updated.UTC().Unix(), + FromFork: from.PullRequest.Source.Repo.UUID != from.PullRequest.Dest.Repo.UUID, } + + if from.PullRequest.State == stateClosed { + pipeline.Commit = from.PullRequest.MergeCommit.Hash + pipeline.Ref = fmt.Sprintf("refs/heads/%s", from.PullRequest.Dest.Branch.Name) + pipeline.Branch = from.PullRequest.Dest.Branch.Name + } + + return pipeline } // convertPushHook is a helper function used to convert a Bitbucket push diff --git a/server/forge/bitbucket/convert_test.go b/server/forge/bitbucket/convert_test.go index 2ce3e27f6..5f450cc03 100644 --- a/server/forge/bitbucket/convert_test.go +++ b/server/forge/bitbucket/convert_test.go @@ -19,169 +19,158 @@ import ( "testing" "time" - "github.com/franela/goblin" + "github.com/stretchr/testify/assert" "golang.org/x/oauth2" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/bitbucket/internal" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/bitbucket/internal" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) -func Test_helper(t *testing.T) { - g := goblin.Goblin(t) - g.Describe("Bitbucket converter", func() { - g.It("should convert passing status", func() { - g.Assert(convertStatus(model.StatusSuccess)).Equal(statusSuccess) - }) - - g.It("should convert pending status", func() { - g.Assert(convertStatus(model.StatusPending)).Equal(statusPending) - g.Assert(convertStatus(model.StatusRunning)).Equal(statusPending) - }) - - g.It("should convert failing status", func() { - g.Assert(convertStatus(model.StatusFailure)).Equal(statusFailure) - g.Assert(convertStatus(model.StatusKilled)).Equal(statusFailure) - g.Assert(convertStatus(model.StatusError)).Equal(statusFailure) - }) - - g.It("should convert repository", func() { - from := &internal.Repo{ - FullName: "octocat/hello-world", - IsPrivate: true, - Scm: "hg", - } - from.Owner.Links.Avatar.Href = "http://..." - from.Links.HTML.Href = "https://bitbucket.org/foo/bar" - fromPerm := &internal.RepoPerm{ - Permission: "write", - } - - to := convertRepo(from, fromPerm) - g.Assert(to.Avatar).Equal(from.Owner.Links.Avatar.Href) - g.Assert(to.FullName).Equal(from.FullName) - g.Assert(to.Owner).Equal("octocat") - g.Assert(to.Name).Equal("hello-world") - g.Assert(to.Branch).Equal("default") - g.Assert(string(to.SCMKind)).Equal(from.Scm) - g.Assert(to.IsSCMPrivate).Equal(from.IsPrivate) - g.Assert(to.Clone).Equal(from.Links.HTML.Href) - g.Assert(to.ForgeURL).Equal(from.Links.HTML.Href) - g.Assert(to.Perm.Push).IsTrue() - g.Assert(to.Perm.Admin).IsFalse() - }) - - g.It("should convert team", func() { - from := &internal.Workspace{Slug: "octocat"} - from.Links.Avatar.Href = "http://..." - to := convertWorkspace(from) - g.Assert(to.Avatar).Equal(from.Links.Avatar.Href) - g.Assert(to.Login).Equal(from.Slug) - }) - - g.It("should convert team list", func() { - from := &internal.Workspace{Slug: "octocat"} - from.Links.Avatar.Href = "http://..." - to := convertWorkspaceList([]*internal.Workspace{from}) - g.Assert(to[0].Avatar).Equal(from.Links.Avatar.Href) - g.Assert(to[0].Login).Equal(from.Slug) - }) - - g.It("should convert user", func() { - token := &oauth2.Token{ - AccessToken: "foo", - RefreshToken: "bar", - Expiry: time.Now(), - } - user := &internal.Account{Login: "octocat"} - user.Links.Avatar.Href = "http://..." - - result := convertUser(user, token) - g.Assert(result.Avatar).Equal(user.Links.Avatar.Href) - g.Assert(result.Login).Equal(user.Login) - g.Assert(result.Token).Equal(token.AccessToken) - g.Assert(result.Secret).Equal(token.RefreshToken) - g.Assert(result.Expiry).Equal(token.Expiry.UTC().Unix()) - }) - - g.It("should use clone url", func() { - repo := &internal.Repo{} - repo.Links.Clone = append(repo.Links.Clone, internal.Link{ - Name: "https", - Href: "https://bitbucket.org/foo/bar.git", - }) - link := cloneLink(repo) - g.Assert(link).Equal(repo.Links.Clone[0].Href) - }) - - g.It("should build clone url", func() { - repo := &internal.Repo{} - repo.Links.HTML.Href = "https://foo:bar@bitbucket.org/foo/bar.git" - link := cloneLink(repo) - g.Assert(link).Equal("https://bitbucket.org/foo/bar.git") - }) - - g.It("should convert pull hook to pipeline", func() { - hook := &internal.PullRequestHook{} - hook.Actor.Login = "octocat" - hook.Actor.Links.Avatar.Href = "https://..." - hook.PullRequest.Dest.Commit.Hash = "73f9c44d" - hook.PullRequest.Dest.Branch.Name = "main" - hook.PullRequest.Dest.Repo.Links.HTML.Href = "https://bitbucket.org/foo/bar" - hook.PullRequest.Source.Branch.Name = "change" - hook.PullRequest.Source.Repo.FullName = "baz/bar" - hook.PullRequest.Links.HTML.Href = "https://bitbucket.org/foo/bar/pulls/5" - hook.PullRequest.Desc = "updated README" - hook.PullRequest.Updated = time.Now() - - pipeline := convertPullHook(hook) - g.Assert(pipeline.Event).Equal(model.EventPull) - g.Assert(pipeline.Author).Equal(hook.Actor.Login) - g.Assert(pipeline.Avatar).Equal(hook.Actor.Links.Avatar.Href) - g.Assert(pipeline.Commit).Equal(hook.PullRequest.Dest.Commit.Hash) - g.Assert(pipeline.Branch).Equal(hook.PullRequest.Dest.Branch.Name) - g.Assert(pipeline.ForgeURL).Equal(hook.PullRequest.Links.HTML.Href) - g.Assert(pipeline.Ref).Equal("refs/heads/main") - g.Assert(pipeline.Refspec).Equal("change:main") - g.Assert(pipeline.Message).Equal(hook.PullRequest.Desc) - g.Assert(pipeline.Timestamp).Equal(hook.PullRequest.Updated.Unix()) - }) - - g.It("should convert push hook to pipeline", func() { - change := internal.Change{} - change.New.Target.Hash = "73f9c44d" - change.New.Name = "main" - change.New.Target.Links.HTML.Href = "https://bitbucket.org/foo/bar/commits/73f9c44d" - change.New.Target.Message = "updated README" - change.New.Target.Date = time.Now() - change.New.Target.Author.Raw = "Test " - - hook := internal.PushHook{} - hook.Actor.Login = "octocat" - hook.Actor.Links.Avatar.Href = "https://..." - - pipeline := convertPushHook(&hook, &change) - g.Assert(pipeline.Event).Equal(model.EventPush) - g.Assert(pipeline.Email).Equal("test@domain.tld") - g.Assert(pipeline.Author).Equal(hook.Actor.Login) - g.Assert(pipeline.Avatar).Equal(hook.Actor.Links.Avatar.Href) - g.Assert(pipeline.Commit).Equal(change.New.Target.Hash) - g.Assert(pipeline.Branch).Equal(change.New.Name) - g.Assert(pipeline.ForgeURL).Equal(change.New.Target.Links.HTML.Href) - g.Assert(pipeline.Ref).Equal("refs/heads/main") - g.Assert(pipeline.Message).Equal(change.New.Target.Message) - g.Assert(pipeline.Timestamp).Equal(change.New.Target.Date.Unix()) - }) - - g.It("should convert tag hook to pipeline", func() { - change := internal.Change{} - change.New.Name = "v1.0.0" - change.New.Type = "tag" - - hook := internal.PushHook{} - - pipeline := convertPushHook(&hook, &change) - g.Assert(pipeline.Event).Equal(model.EventTag) - g.Assert(pipeline.Ref).Equal("refs/tags/v1.0.0") - }) - }) +func Test_convertStatus(t *testing.T) { + assert.Equal(t, statusSuccess, convertStatus(model.StatusSuccess)) + assert.Equal(t, statusPending, convertStatus(model.StatusPending)) + assert.Equal(t, statusPending, convertStatus(model.StatusRunning)) + assert.Equal(t, statusFailure, convertStatus(model.StatusFailure)) + assert.Equal(t, statusFailure, convertStatus(model.StatusKilled)) + assert.Equal(t, statusFailure, convertStatus(model.StatusError)) +} + +func Test_convertRepo(t *testing.T) { + from := &internal.Repo{ + FullName: "octocat/hello-world", + IsPrivate: true, + Scm: "git", + } + from.Owner.Links.Avatar.Href = "http://..." + from.Links.HTML.Href = "https://bitbucket.org/foo/bar" + from.MainBranch.Name = "default" + fromPerm := &internal.RepoPerm{ + Permission: "write", + } + + to := convertRepo(from, fromPerm) + assert.Equal(t, from.Owner.Links.Avatar.Href, to.Avatar) + assert.Equal(t, from.FullName, to.FullName) + assert.Equal(t, "octocat", to.Owner) + assert.Equal(t, "hello-world", to.Name) + assert.Equal(t, "default", to.Branch) + assert.Equal(t, from.IsPrivate, to.IsSCMPrivate) + assert.Equal(t, from.Links.HTML.Href, to.Clone) + assert.Equal(t, from.Links.HTML.Href, to.ForgeURL) + assert.True(t, to.Perm.Push) + assert.False(t, to.Perm.Admin) +} + +func Test_convertWorkspace(t *testing.T) { + from := &internal.Workspace{Slug: "octocat"} + from.Links.Avatar.Href = "http://..." + to := convertWorkspace(from) + assert.Equal(t, from.Links.Avatar.Href, to.Avatar) + assert.Equal(t, from.Slug, to.Login) +} + +func Test_convertWorkspaceList(t *testing.T) { + from := &internal.Workspace{Slug: "octocat"} + from.Links.Avatar.Href = "http://..." + to := convertWorkspaceList([]*internal.Workspace{from}) + assert.Equal(t, from.Links.Avatar.Href, to[0].Avatar) + assert.Equal(t, from.Slug, to[0].Login) +} + +func Test_convertUser(t *testing.T) { + token := &oauth2.Token{ + AccessToken: "foo", + RefreshToken: "bar", + Expiry: time.Now(), + } + user := &internal.Account{Login: "octocat"} + user.Links.Avatar.Href = "http://..." + + result := convertUser(user, token) + assert.Equal(t, user.Links.Avatar.Href, result.Avatar) + assert.Equal(t, user.Login, result.Login) + assert.Equal(t, token.AccessToken, result.AccessToken) + assert.Equal(t, token.RefreshToken, result.RefreshToken) + assert.Equal(t, token.Expiry.UTC().Unix(), result.Expiry) +} + +func Test_cloneLink(t *testing.T) { + repo := &internal.Repo{} + repo.Links.Clone = append(repo.Links.Clone, internal.Link{ + Name: "https", + Href: "https://bitbucket.org/foo/bar.git", + }) + link := cloneLink(repo) + assert.Equal(t, repo.Links.Clone[0].Href, link) + + repo = &internal.Repo{} + repo.Links.HTML.Href = "https://foo:bar@bitbucket.org/foo/bar.git" + link = cloneLink(repo) + assert.Equal(t, "https://bitbucket.org/foo/bar.git", link) +} + +func Test_convertPullHook(t *testing.T) { + hook := &internal.PullRequestHook{} + hook.Actor.Login = "octocat" + hook.Actor.Links.Avatar.Href = "https://..." + hook.PullRequest.Dest.Commit.Hash = "73f9c44d" + hook.PullRequest.Dest.Branch.Name = "main" + hook.PullRequest.Dest.Repo.Links.HTML.Href = "https://bitbucket.org/foo/bar" + hook.PullRequest.Source.Branch.Name = "change" + hook.PullRequest.Source.Repo.FullName = "baz/bar" + hook.PullRequest.Source.Commit.Hash = "c8411d7" + hook.PullRequest.Links.HTML.Href = "https://bitbucket.org/foo/bar/pulls/5" + hook.PullRequest.Title = "updated README" + hook.PullRequest.Updated = time.Now() + hook.PullRequest.ID = 1 + + pipeline := convertPullHook(hook) + assert.Equal(t, model.EventPull, pipeline.Event) + assert.Equal(t, hook.Actor.Login, pipeline.Author) + assert.Equal(t, hook.Actor.Links.Avatar.Href, pipeline.Avatar) + assert.Equal(t, hook.PullRequest.Source.Commit.Hash, pipeline.Commit) + assert.Equal(t, hook.PullRequest.Source.Branch.Name, pipeline.Branch) + assert.Equal(t, hook.PullRequest.Links.HTML.Href, pipeline.ForgeURL) + assert.Equal(t, "refs/pull-requests/1/from", pipeline.Ref) + assert.Equal(t, "change:main", pipeline.Refspec) + assert.Equal(t, hook.PullRequest.Title, pipeline.Message) + assert.Equal(t, hook.PullRequest.Updated.Unix(), pipeline.Timestamp) +} + +func Test_convertPushHook(t *testing.T) { + change := internal.Change{} + change.New.Target.Hash = "73f9c44d" + change.New.Name = "main" + change.New.Target.Links.HTML.Href = "https://bitbucket.org/foo/bar/commits/73f9c44d" + change.New.Target.Message = "updated README" + change.New.Target.Date = time.Now() + change.New.Target.Author.Raw = "Test " + + hook := internal.PushHook{} + hook.Actor.Login = "octocat" + hook.Actor.Links.Avatar.Href = "https://..." + + pipeline := convertPushHook(&hook, &change) + assert.Equal(t, model.EventPush, pipeline.Event) + assert.Equal(t, "test@domain.tld", pipeline.Email) + assert.Equal(t, hook.Actor.Login, pipeline.Author) + assert.Equal(t, hook.Actor.Links.Avatar.Href, pipeline.Avatar) + assert.Equal(t, change.New.Target.Hash, pipeline.Commit) + assert.Equal(t, change.New.Name, pipeline.Branch) + assert.Equal(t, change.New.Target.Links.HTML.Href, pipeline.ForgeURL) + assert.Equal(t, "refs/heads/main", pipeline.Ref) + assert.Equal(t, change.New.Target.Message, pipeline.Message) + assert.Equal(t, change.New.Target.Date.Unix(), pipeline.Timestamp) +} + +func Test_convertPushHookTag(t *testing.T) { + change := internal.Change{} + change.New.Name = "v1.0.0" + change.New.Type = "tag" + + hook := internal.PushHook{} + + pipeline := convertPushHook(&hook, &change) + assert.Equal(t, model.EventTag, pipeline.Event) + assert.Equal(t, "refs/tags/v1.0.0", pipeline.Ref) } diff --git a/server/forge/bitbucket/internal/client.go b/server/forge/bitbucket/internal/client.go index 3083482c7..e7a5a74b0 100644 --- a/server/forge/bitbucket/internal/client.go +++ b/server/forge/bitbucket/internal/client.go @@ -26,7 +26,7 @@ import ( "golang.org/x/oauth2" "golang.org/x/oauth2/bitbucket" - shared_utils "go.woodpecker-ci.org/woodpecker/v2/shared/utils" + shared_utils "go.woodpecker-ci.org/woodpecker/v3/shared/utils" ) const ( @@ -115,7 +115,7 @@ func (c *Client) ListReposAll(workspace string) ([]*Repo, error) { return nil, err } return resp.Values, nil - }) + }, -1) } func (c *Client) FindHook(owner, name, id string) (*Hook, error) { @@ -183,7 +183,7 @@ func (c *Client) ListPermissionsAll() ([]*RepoPerm, error) { return nil, err } return resp.Values, nil - }) + }, -1) } func (c *Client) ListBranches(owner, name string, opts *ListOpts) ([]*Branch, error) { diff --git a/server/forge/bitbucket/internal/types.go b/server/forge/bitbucket/internal/types.go index a5dc4acb4..bbb03ab1c 100644 --- a/server/forge/bitbucket/internal/types.go +++ b/server/forge/bitbucket/internal/types.go @@ -164,6 +164,10 @@ type PullRequestHook struct { Created time.Time `json:"created_on"` Updated time.Time `json:"updated_on"` + MergeCommit struct { + Hash string `json:"hash"` + } `json:"merge_commit"` + Source struct { Repo Repo `json:"repository"` Commit struct { diff --git a/server/forge/bitbucket/parse.go b/server/forge/bitbucket/parse.go index 82cf8d5cc..7f8b6710b 100644 --- a/server/forge/bitbucket/parse.go +++ b/server/forge/bitbucket/parse.go @@ -19,9 +19,9 @@ import ( "io" "net/http" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/bitbucket/internal" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/bitbucket/internal" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) const ( diff --git a/server/forge/bitbucket/parse_test.go b/server/forge/bitbucket/parse_test.go index aa9b81117..f0afb5102 100644 --- a/server/forge/bitbucket/parse_test.go +++ b/server/forge/bitbucket/parse_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 Drone.IO Inc. +// // Copyright 2018 Drone.IO Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,121 +19,108 @@ import ( "net/http" "testing" - "github.com/franela/goblin" "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/bitbucket/fixtures" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/bitbucket/fixtures" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) -func Test_parser(t *testing.T) { - g := goblin.Goblin(t) - g.Describe("Bitbucket parser", func() { - g.It("should ignore unsupported hook", func() { - buf := bytes.NewBufferString(fixtures.HookPush) - req, _ := http.NewRequest(http.MethodPost, "/hook", buf) - req.Header = http.Header{} - req.Header.Set(hookEvent, "issue:created") +func Test_parseHook(t *testing.T) { + t.Run("unsupported hook", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookPush) + req, _ := http.NewRequest(http.MethodPost, "/hook", buf) + req.Header = http.Header{} + req.Header.Set(hookEvent, "issue:created") - r, b, err := parseHook(req) - g.Assert(r).IsNil() - g.Assert(b).IsNil() - assert.ErrorIs(t, err, &types.ErrIgnoreEvent{}) - }) + r, b, err := parseHook(req) + assert.Nil(t, r) + assert.Nil(t, b) + assert.ErrorIs(t, err, &types.ErrIgnoreEvent{}) + }) - g.Describe("Given a pull-request hook payload", func() { - g.It("should return err when malformed", func() { - buf := bytes.NewBufferString("[]") - req, _ := http.NewRequest(http.MethodPost, "/hook", buf) - req.Header = http.Header{} - req.Header.Set(hookEvent, hookPullCreated) + t.Run("malformed pull-request hook", func(t *testing.T) { + buf := bytes.NewBufferString("[]") + req, _ := http.NewRequest(http.MethodPost, "/hook", buf) + req.Header = http.Header{} + req.Header.Set(hookEvent, hookPullCreated) - _, _, err := parseHook(req) - g.Assert(err).IsNotNil() - }) + _, _, err := parseHook(req) + assert.Error(t, err) + }) - g.It("should return pull-request details", func() { - buf := bytes.NewBufferString(fixtures.HookPull) - req, _ := http.NewRequest(http.MethodPost, "/hook", buf) - req.Header = http.Header{} - req.Header.Set(hookEvent, hookPullCreated) + t.Run("pull-request", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookPull) + req, _ := http.NewRequest(http.MethodPost, "/hook", buf) + req.Header = http.Header{} + req.Header.Set(hookEvent, hookPullCreated) - r, b, err := parseHook(req) - g.Assert(err).IsNil() - g.Assert(r.FullName).Equal("user_name/repo_name") - g.Assert(b.Event).Equal(model.EventPull) - g.Assert(b.Commit).Equal("ce5965ddd289") - }) + r, b, err := parseHook(req) + assert.NoError(t, err) + assert.Equal(t, "user_name/repo_name", r.FullName) + assert.Equal(t, model.EventPull, b.Event) + assert.Equal(t, "d3022fc0ca3d", b.Commit) + }) - g.It("should return pull-request details for a pull-request merged payload", func() { - buf := bytes.NewBufferString(fixtures.HookPullRequestMerged) - req, _ := http.NewRequest(http.MethodPost, "/hook", buf) - req.Header = http.Header{} - req.Header.Set(hookEvent, hookPullMerged) + t.Run("pull-request merged", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookPullRequestMerged) + req, _ := http.NewRequest(http.MethodPost, "/hook", buf) + req.Header = http.Header{} + req.Header.Set(hookEvent, hookPullMerged) - r, b, err := parseHook(req) - g.Assert(err).IsNil() - g.Assert(r.FullName).Equal("anbraten/test-2") - g.Assert(b.Event).Equal(model.EventPullClosed) - g.Assert(b.Commit).Equal("6c5f0bc9b2aa") - }) + r, b, err := parseHook(req) + assert.NoError(t, err) + assert.Equal(t, "anbraten/test-2", r.FullName) + assert.Equal(t, model.EventPullClosed, b.Event) + assert.Equal(t, "006704dbeab2", b.Commit) + }) - g.It("should return pull-request details for a pull-request closed payload", func() { - buf := bytes.NewBufferString(fixtures.HookPullRequestDeclined) - req, _ := http.NewRequest(http.MethodPost, "/hook", buf) - req.Header = http.Header{} - req.Header.Set(hookEvent, hookPullDeclined) + t.Run("pull-request closed", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookPullRequestDeclined) + req, _ := http.NewRequest(http.MethodPost, "/hook", buf) + req.Header = http.Header{} + req.Header.Set(hookEvent, hookPullDeclined) - r, b, err := parseHook(req) - g.Assert(err).IsNil() - g.Assert(r.FullName).Equal("anbraten/test-2") - g.Assert(b.Event).Equal(model.EventPullClosed) - g.Assert(b.Commit).Equal("006704dbeab2") - }) - }) + r, b, err := parseHook(req) + assert.NoError(t, err) + assert.Equal(t, "anbraten/test-2", r.FullName) + assert.Equal(t, model.EventPullClosed, b.Event) + assert.Equal(t, "f90e18fc9d45", b.Commit) + }) - g.Describe("Given a push hook payload", func() { - g.It("should return err when malformed", func() { - buf := bytes.NewBufferString("[]") - req, _ := http.NewRequest(http.MethodPost, "/hook", buf) - req.Header = http.Header{} - req.Header.Set(hookEvent, hookPush) + t.Run("malformed push", func(t *testing.T) { + buf := bytes.NewBufferString("[]") + req, _ := http.NewRequest(http.MethodPost, "/hook", buf) + req.Header = http.Header{} + req.Header.Set(hookEvent, hookPush) - _, _, err := parseHook(req) - g.Assert(err).IsNotNil() - }) + _, _, err := parseHook(req) + assert.Error(t, err) + }) - g.It("should return nil if missing commit sha", func() { - buf := bytes.NewBufferString(fixtures.HookPushEmptyHash) - req, _ := http.NewRequest(http.MethodPost, "/hook", buf) - req.Header = http.Header{} - req.Header.Set(hookEvent, hookPush) + t.Run("missing commit sha", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookPushEmptyHash) + req, _ := http.NewRequest(http.MethodPost, "/hook", buf) + req.Header = http.Header{} + req.Header.Set(hookEvent, hookPush) - r, b, err := parseHook(req) - g.Assert(r).IsNil() - g.Assert(b).IsNil() - g.Assert(err).IsNil() - }) + r, b, err := parseHook(req) + assert.Nil(t, r) + assert.Nil(t, b) + assert.NoError(t, err) + }) - g.It("should return push details", func() { - buf := bytes.NewBufferString(fixtures.HookPush) - req, _ := http.NewRequest(http.MethodPost, "/hook", buf) - req.Header = http.Header{} - req.Header.Set(hookEvent, hookPush) + t.Run("push hook", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookPush) + req, _ := http.NewRequest(http.MethodPost, "/hook", buf) + req.Header = http.Header{} + req.Header.Set(hookEvent, hookPush) - r, b, err := parseHook(req) - g.Assert(err).IsNil() - g.Assert(r.FullName).Equal("martinherren1984/publictestrepo") - g.Assert(r.SCMKind).Equal(model.RepoGit) - g.Assert(r.Clone).Equal("https://bitbucket.org/martinherren1984/publictestrepo") - g.Assert(b.Commit).Equal("c14c1bb05dfb1fdcdf06b31485fff61b0ea44277") - g.Assert(b.Message).Equal("a\n") - }) - }) - - g.Describe("Given a tag hook payload", func() { - // TODO - }) + r, b, err := parseHook(req) + assert.NoError(t, err) + assert.Equal(t, "martinherren1984/publictestrepo", r.FullName) + assert.Equal(t, "https://bitbucket.org/martinherren1984/publictestrepo", r.Clone) + assert.Equal(t, "c14c1bb05dfb1fdcdf06b31485fff61b0ea44277", b.Commit) + assert.Equal(t, "a\n", b.Message) }) } diff --git a/server/forge/bitbucketdatacenter/bitbucketdatacenter.go b/server/forge/bitbucketdatacenter/bitbucketdatacenter.go index cd3b58bc4..354df61b6 100644 --- a/server/forge/bitbucketdatacenter/bitbucketdatacenter.go +++ b/server/forge/bitbucketdatacenter/bitbucketdatacenter.go @@ -25,13 +25,13 @@ import ( "github.com/rs/zerolog/log" "golang.org/x/oauth2" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/bitbucketdatacenter/internal" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/common" - forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/bitbucketdatacenter/internal" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/common" + forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) const listLimit = 250 @@ -116,7 +116,7 @@ func (c *client) Login(ctx context.Context, req *forge_types.OAuthRequest) (*mod return nil, "", err } - bc, err := c.newClient(ctx, &model.User{Token: token.AccessToken}) + bc, err := c.newClient(ctx, &model.User{AccessToken: token.AccessToken}) if err != nil { return nil, "", fmt.Errorf("unable to create bitbucket client: %w", err) } @@ -143,7 +143,7 @@ func (c *client) Auth(ctx context.Context, accessToken, _ string) (string, error func (c *client) Refresh(ctx context.Context, u *model.User) (bool, error) { config := c.newOAuth2Config() t := &oauth2.Token{ - RefreshToken: u.Secret, + RefreshToken: u.RefreshToken, } ts := config.TokenSource(ctx, t) @@ -211,7 +211,7 @@ func (c *client) Repos(ctx context.Context, u *model.User) ([]*model.Repo, error } opts := &bb.RepositorySearchOptions{Permission: bb.PermissionRepoWrite, ListOptions: bb.ListOptions{Limit: listLimit}} - var all []*model.Repo + all := make([]*model.Repo, 0) for { repos, resp, err := bc.Projects.SearchRepositories(ctx, opts) if err != nil { @@ -277,7 +277,7 @@ func (c *client) Dir(ctx context.Context, u *model.User, r *model.Repo, p *model } opts := &bb.FilesListOptions{At: p.Commit} - var all []*forge_types.FileMeta + all := make([]*forge_types.FileMeta, 0) for { list, resp, err := bc.Projects.ListFiles(ctx, r.Owner, r.Name, path, opts) if err != nil { @@ -341,7 +341,7 @@ func (c *client) Branches(ctx context.Context, u *model.User, r *model.Repo, p * } opts := &bb.BranchSearchOptions{ListOptions: convertListOptions(p)} - var all []string + all := make([]string, 0) for { branches, resp, err := bc.Projects.SearchBranches(ctx, r.Owner, r.Name, opts) if err != nil { @@ -389,7 +389,7 @@ func (c *client) PullRequests(ctx context.Context, u *model.User, r *model.Repo, } opts := &bb.PullRequestSearchOptions{ListOptions: convertListOptions(p)} - var all []*model.PullRequest + all := make([]*model.PullRequest, 0) for { prs, resp, err := bc.Projects.SearchPullRequests(ctx, r.Owner, r.Name, opts) if err != nil { @@ -570,10 +570,32 @@ func (c *client) updatePipelineFromCommit(ctx context.Context, u *model.User, r return p, nil } -// Teams is not supported. -func (*client) Teams(_ context.Context, _ *model.User) ([]*model.Team, error) { - var teams []*model.Team - return teams, nil +// Teams fetches all the projects for a given user and converts them into teams. +func (c *client) Teams(ctx context.Context, u *model.User) ([]*model.Team, error) { + opts := &bb.ListOptions{Limit: listLimit} + allProjects := make([]*bb.Project, 0) + + bc, err := c.newClient(ctx, u) + if err != nil { + return nil, fmt.Errorf("unable to create client: %w", err) + } + + for { + projects, resp, err := bc.Projects.ListProjects(ctx, opts) + if err != nil { + return nil, fmt.Errorf("unable to fetch projects: %w", err) + } + + allProjects = append(allProjects, projects...) + + if resp.LastPage { + break + } + + opts.Start = resp.NextPageStart + } + + return convertProjectsToTeams(allProjects, bc), nil } // TeamPerm is not supported. @@ -623,7 +645,7 @@ func (c *client) newOAuth2Config() *oauth2.Config { func (c *client) newClient(ctx context.Context, u *model.User) (*bb.Client, error) { config := c.newOAuth2Config() t := &oauth2.Token{ - AccessToken: u.Token, + AccessToken: u.AccessToken, } client := config.Client(ctx, t) return bb.NewClient(c.urlAPI, client) diff --git a/server/forge/bitbucketdatacenter/bitbucketdatacenter_test.go b/server/forge/bitbucketdatacenter/bitbucketdatacenter_test.go index e1779a295..b85c1ebd3 100644 --- a/server/forge/bitbucketdatacenter/bitbucketdatacenter_test.go +++ b/server/forge/bitbucketdatacenter/bitbucketdatacenter_test.go @@ -19,78 +19,77 @@ import ( "testing" "time" - "github.com/franela/goblin" "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/bitbucketdatacenter/fixtures" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/bitbucketdatacenter/fixtures" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) +func TestNew(t *testing.T) { + forge, err := New(Opts{ + URL: "http://localhost:8080", + Username: "0ZXh0IjoiI", + Password: "I1NiIsInR5", + ClientID: "client-id", + ClientSecret: "client-secret", + }) + assert.NoError(t, err) + assert.NotNil(t, forge) + cl, ok := forge.(*client) + assert.True(t, ok) + assert.Equal(t, &client{ + url: "http://localhost:8080", + urlAPI: "http://localhost:8080/rest", + username: "0ZXh0IjoiI", + password: "I1NiIsInR5", + clientID: "client-id", + clientSecret: "client-secret", + }, cl) +} + func TestBitbucketDC(t *testing.T) { gin.SetMode(gin.TestMode) s := fixtures.Server() + defer s.Close() c := &client{ urlAPI: s.URL, } ctx := context.Background() - g := goblin.Goblin(t) - g.Describe("Bitbucket DataCenter/Server", func() { - g.After(func() { - s.Close() - }) - g.Describe("Creating a forge", func() { - g.It("Should return client with specified options", func() { - forge, err := New(Opts{ - URL: "http://localhost:8080", - Username: "0ZXh0IjoiI", - Password: "I1NiIsInR5", - ClientID: "client-id", - ClientSecret: "client-secret", - }) - g.Assert(err).IsNil() - g.Assert(forge).IsNotNil() - cl, ok := forge.(*client) - g.Assert(ok).IsTrue() - g.Assert(cl.url).Equal("http://localhost:8080") - g.Assert(cl.username).Equal("0ZXh0IjoiI") - g.Assert(cl.password).Equal("I1NiIsInR5") - g.Assert(cl.clientID).Equal("client-id") - g.Assert(cl.clientSecret).Equal("client-secret") - }) - }) + repo, err := c.Repo(ctx, fakeUser, model.ForgeRemoteID("1234"), "PRJ", "repo-slug") + assert.NoError(t, err) + assert.Equal(t, &model.Repo{ + Name: "repo-slug-2", + Owner: "PRJ", + Perm: &model.Perm{Pull: true, Push: true}, + Branch: "main", + IsSCMPrivate: true, + PREnabled: true, + ForgeRemoteID: model.ForgeRemoteID("1234"), + FullName: "PRJ/repo-slug-2", + }, repo) - g.Describe("Requesting a repository", func() { - g.It("should return repository details", func() { - repo, err := c.Repo(ctx, fakeUser, model.ForgeRemoteID("1234"), "PRJ", "repo-slug") - g.Assert(err).IsNil() - g.Assert(repo.Name).Equal("repo-slug-2") - g.Assert(repo.Owner).Equal("PRJ") - g.Assert(repo.Perm).Equal(&model.Perm{Pull: true, Push: true}) - g.Assert(repo.Branch).Equal("main") - }) - }) + // org + org, err := c.Org(ctx, fakeUser, "ORG") + assert.NoError(t, err) + assert.Equal(t, &model.Org{ + Name: "ORG", + IsUser: false, + }, org) - g.Describe("Getting organization", func() { - g.It("should map organization", func() { - org, err := c.Org(ctx, fakeUser, "ORG") - g.Assert(err).IsNil() - g.Assert(org.Name).Equal("ORG") - g.Assert(org.IsUser).IsFalse() - }) - g.It("should map user organization", func() { - org, err := c.Org(ctx, fakeUser, "~ORG") - g.Assert(err).IsNil() - g.Assert(org.Name).Equal("~ORG") - g.Assert(org.IsUser).IsTrue() - }) - }) - }) + // user + org, err = c.Org(ctx, fakeUser, "~ORG") + assert.NoError(t, err) + assert.Equal(t, &model.Org{ + Name: "~ORG", + IsUser: true, + }, org) } var fakeUser = &model.User{ - Token: "fake", - Expiry: time.Now().Add(1 * time.Hour).Unix(), + AccessToken: "fake", + Expiry: time.Now().Add(1 * time.Hour).Unix(), } diff --git a/server/forge/bitbucketdatacenter/convert.go b/server/forge/bitbucketdatacenter/convert.go index bef0c30d5..d1fa741d4 100644 --- a/server/forge/bitbucketdatacenter/convert.go +++ b/server/forge/bitbucketdatacenter/convert.go @@ -23,7 +23,7 @@ import ( bb "github.com/neticdk/go-bitbucket/bitbucket" "golang.org/x/oauth2" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func convertStatus(status model.StatusValue) bb.BuildStatusState { @@ -56,7 +56,6 @@ func convertRepo(from *bb.Repository, perm *model.Perm, branch string) *model.Re Name: from.Slug, Owner: from.Project.Key, Branch: branch, - SCMKind: model.RepoGit, IsSCMPrivate: true, // Since we have to use Netrc it has to always be private :/ TODO: Is this really true? FullName: fmt.Sprintf("%s/%s", from.Project.Key, from.Slug), Perm: perm, @@ -123,6 +122,7 @@ func convertPullRequestEvent(ev *bb.PullRequestEvent, baseURL string) *model.Pip Ref: fmt.Sprintf("refs/pull-requests/%d/from", ev.PullRequest.ID), ForgeURL: fmt.Sprintf("%s/projects/%s/repos/%s/commits/%s", baseURL, ev.PullRequest.Source.Repository.Project.Key, ev.PullRequest.Source.Repository.Slug, ev.PullRequest.Source.Latest), Refspec: fmt.Sprintf("%s:%s", ev.PullRequest.Source.DisplayID, ev.PullRequest.Target.DisplayID), + FromFork: ev.PullRequest.Source.Repository.ID != ev.PullRequest.Target.Repository.ID, } if ev.EventKey == bb.EventKeyPullRequestMerged || ev.EventKey == bb.EventKeyPullRequestDeclined || ev.EventKey == bb.EventKeyPullRequestDeleted { @@ -168,7 +168,19 @@ func convertListOptions(p *model.ListOptions) bb.ListOptions { } func updateUserCredentials(u *model.User, t *oauth2.Token) { - u.Token = t.AccessToken - u.Secret = t.RefreshToken + u.AccessToken = t.AccessToken + u.RefreshToken = t.RefreshToken u.Expiry = t.Expiry.UTC().Unix() } + +func convertProjectsToTeams(projects []*bb.Project, client *bb.Client) []*model.Team { + teams := make([]*model.Team, 0) + for _, project := range projects { + team := &model.Team{ + Login: project.Key, + Avatar: fmt.Sprintf("%s/projects/%s/avatar.png", client.BaseURL, project.Key), + } + teams = append(teams, team) + } + return teams +} diff --git a/server/forge/bitbucketdatacenter/convert_test.go b/server/forge/bitbucketdatacenter/convert_test.go index 564f00530..74ee30b98 100644 --- a/server/forge/bitbucketdatacenter/convert_test.go +++ b/server/forge/bitbucketdatacenter/convert_test.go @@ -15,292 +15,349 @@ package bitbucketdatacenter import ( + "net/url" "testing" "time" - "github.com/franela/goblin" bb "github.com/neticdk/go-bitbucket/bitbucket" + "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) -//nolint:misspell -func TestHelper(t *testing.T) { - g := goblin.Goblin(t) - g.Describe("Bitbucket Server converter", func() { - g.It("should convert status", func() { - tests := []struct { - from model.StatusValue - to bb.BuildStatusState - }{ - { - from: model.StatusPending, - to: bb.BuildStatusStateInProgress, - }, - { - from: model.StatusRunning, - to: bb.BuildStatusStateInProgress, - }, - { - from: model.StatusSuccess, - to: bb.BuildStatusStateSuccessful, - }, - { - from: model.StatusValue("other"), - to: bb.BuildStatusStateFailed, - }, - } - for _, tt := range tests { - to := convertStatus(tt.from) - g.Assert(to).Equal(tt.to) - } - }) - - g.It("should convert repository", func() { - from := &bb.Repository{ - ID: uint64(1234), - Slug: "REPO", - Project: &bb.Project{ - Key: "PRJ", - }, - Links: map[string][]bb.Link{ - "clone": { - { - Name: "http", - Href: "https://user@git.domain/clone", - }, - }, - "self": { - { - Href: "https://git.domain/self", - }, - }, - }, - } - perm := &model.Perm{} - to := convertRepo(from, perm, "main") - g.Assert(to.ForgeRemoteID).Equal(model.ForgeRemoteID("1234")) - g.Assert(to.Name).Equal("REPO") - g.Assert(to.Owner).Equal("PRJ") - g.Assert(to.Branch).Equal("main") - g.Assert(to.SCMKind).Equal(model.RepoGit) - g.Assert(to.FullName).Equal("PRJ/REPO") - g.Assert(to.Perm).Equal(perm) - g.Assert(to.Clone).Equal("https://git.domain/clone") - }) - - g.It("should convert repository push event", func() { - now := time.Now() - tests := []struct { - from *bb.RepositoryPushEvent - to *model.Pipeline - }{ - { - from: &bb.RepositoryPushEvent{}, - to: nil, - }, - { - from: &bb.RepositoryPushEvent{ - Changes: []bb.RepositoryPushEventChange{ - { - FromHash: "1234567890abcdef", - ToHash: "0000000000000000000000000000000000000000", - }, - }, - }, - to: nil, - }, - { - from: &bb.RepositoryPushEvent{ - Changes: []bb.RepositoryPushEventChange{ - { - FromHash: "0000000000000000000000000000000000000000", - ToHash: "1234567890abcdef", - Type: bb.RepositoryPushEventChangeTypeDelete, - }, - }, - }, - to: nil, - }, - { - from: &bb.RepositoryPushEvent{ - Event: bb.Event{ - Date: bb.ISOTime(now), - Actor: bb.User{ - Name: "John Doe", - Email: "john.doe@mail.com", - Slug: "john.doe_mail.com", - }, - }, - Repository: bb.Repository{ - Slug: "REPO", - Project: &bb.Project{ - Key: "PRJ", - }, - }, - Changes: []bb.RepositoryPushEventChange{ - { - Ref: bb.RepositoryPushEventRef{ - ID: "refs/head/branch", - DisplayID: "branch", - }, - RefId: "refs/head/branch", - ToHash: "1234567890abcdef", - }, - }, - }, - to: &model.Pipeline{ - Commit: "1234567890abcdef", - Branch: "branch", - Message: "", - Avatar: "https://base.url/users/john.doe_mail.com/avatar.png", - Author: "John Doe", - Email: "john.doe@mail.com", - Timestamp: now.UTC().Unix(), - Ref: "refs/head/branch", - ForgeURL: "https://base.url/projects/PRJ/repos/REPO/commits/1234567890abcdef", - Event: model.EventPush, - }, - }, - } - for _, tt := range tests { - to := convertRepositoryPushEvent(tt.from, "https://base.url") - g.Assert(to).Equal(tt.to) - } - }) - - g.It("should convert pull request event", func() { - now := time.Now() - from := &bb.PullRequestEvent{ - Event: bb.Event{ - Date: bb.ISOTime(now), - EventKey: bb.EventKeyPullRequestFrom, - Actor: bb.User{ - Name: "John Doe", - Email: "john.doe@mail.com", - Slug: "john.doe_mail.com", - }, - }, - PullRequest: bb.PullRequest{ - ID: 123, - Title: "my title", - Source: bb.PullRequestRef{ - ID: "refs/head/branch", - DisplayID: "branch", - Latest: "1234567890abcdef", - Repository: bb.Repository{ - Slug: "REPO", - Project: &bb.Project{ - Key: "PRJ", - }, - }, - }, - Target: bb.PullRequestRef{ - ID: "refs/head/main", - DisplayID: "main", - Latest: "abcdef1234567890", - Repository: bb.Repository{ - Slug: "REPO", - Project: &bb.Project{ - Key: "PRJ", - }, - }, - }, - }, - } - to := convertPullRequestEvent(from, "https://base.url") - g.Assert(to.Commit).Equal("1234567890abcdef") - g.Assert(to.Branch).Equal("branch") - g.Assert(to.Avatar).Equal("https://base.url/users/john.doe_mail.com/avatar.png") - g.Assert(to.Author).Equal("John Doe") - g.Assert(to.Email).Equal("john.doe@mail.com") - g.Assert(to.Timestamp).Equal(now.UTC().Unix()) - g.Assert(to.Ref).Equal("refs/pull-requests/123/from") - g.Assert(to.ForgeURL).Equal("https://base.url/projects/PRJ/repos/REPO/commits/1234567890abcdef") - g.Assert(to.Event).Equal(model.EventPull) - g.Assert(to.Refspec).Equal("branch:main") - }) - - g.It("should close pull request", func() { - now := time.Now() - from := &bb.PullRequestEvent{ - Event: bb.Event{ - Date: bb.ISOTime(now), - EventKey: bb.EventKeyPullRequestMerged, - Actor: bb.User{ - Name: "John Doe", - Email: "john.doe@mail.com", - Slug: "john.doe_mail.com", - }, - }, - PullRequest: bb.PullRequest{ - ID: 123, - Title: "my title", - Source: bb.PullRequestRef{ - ID: "refs/head/branch", - DisplayID: "branch", - Latest: "1234567890abcdef", - Repository: bb.Repository{ - Slug: "REPO", - Project: &bb.Project{ - Key: "PRJ", - }, - }, - }, - Target: bb.PullRequestRef{ - ID: "refs/head/main", - DisplayID: "main", - Latest: "abcdef1234567890", - Repository: bb.Repository{ - Slug: "REPO", - Project: &bb.Project{ - Key: "PRJ", - }, - }, - }, - }, - } - to := convertPullRequestEvent(from, "https://base.url") - g.Assert(to.Commit).Equal("1234567890abcdef") - g.Assert(to.Branch).Equal("branch") - g.Assert(to.Avatar).Equal("https://base.url/users/john.doe_mail.com/avatar.png") - g.Assert(to.Author).Equal("John Doe") - g.Assert(to.Email).Equal("john.doe@mail.com") - g.Assert(to.Timestamp).Equal(now.UTC().Unix()) - g.Assert(to.Ref).Equal("refs/pull-requests/123/from") - g.Assert(to.ForgeURL).Equal("https://base.url/projects/PRJ/repos/REPO/commits/1234567890abcdef") - g.Assert(to.Event).Equal(model.EventPullClosed) - g.Assert(to.Refspec).Equal("branch:main") - }) - - g.It("should truncate author", func() { - tests := []struct { - from string - to string - }{ - { - from: "Some Short Author", - to: "Some Short Author", - }, - { - from: "Some Very Long Author That May Include Multiple Names Here", - to: "Some Very Long Author That May Includ...", - }, - } - for _, tt := range tests { - g.Assert(authorLabel(tt.from)).Equal(tt.to) - } - }) - - g.It("should convert user", func() { - from := &bb.User{ - Slug: "slug", - Email: "john.doe@mail.com", - } - to := convertUser(from, "https://base.url") - g.Assert(to.Login).Equal("slug") - g.Assert(to.Avatar).Equal("https://base.url/users/slug/avatar.png") - g.Assert(to.Email).Equal("john.doe@mail.com") - }) - }) +func Test_convertStatus(t *testing.T) { + tests := []struct { + from model.StatusValue + to bb.BuildStatusState + }{ + { + from: model.StatusPending, + to: bb.BuildStatusStateInProgress, + }, + { + from: model.StatusRunning, + to: bb.BuildStatusStateInProgress, + }, + { + from: model.StatusSuccess, + to: bb.BuildStatusStateSuccessful, + }, + { + from: model.StatusValue("other"), + to: bb.BuildStatusStateFailed, + }, + } + for _, tt := range tests { + to := convertStatus(tt.from) + assert.Equal(t, tt.to, to) + } +} + +func Test_convertRepo(t *testing.T) { + from := &bb.Repository{ + ID: uint64(1234), + Slug: "REPO", + Project: &bb.Project{ + Key: "PRJ", + }, + Links: map[string][]bb.Link{ + "clone": { + { + Name: "http", + Href: "https://user@git.domain/clone", + }, + }, + "self": { + { + Href: "https://git.domain/self", + }, + }, + }, + } + perm := &model.Perm{} + to := convertRepo(from, perm, "main") + + assert.Equal(t, &model.Repo{ + ForgeRemoteID: model.ForgeRemoteID("1234"), + Name: "REPO", + Owner: "PRJ", + Branch: "main", + FullName: "PRJ/REPO", + Perm: perm, + Clone: "https://git.domain/clone", + ForgeURL: "https://git.domain/self", + PREnabled: true, + IsSCMPrivate: true, + }, to) +} + +func Test_convertRepositoryPushEvent(t *testing.T) { + now := time.Now() + tests := []struct { + from *bb.RepositoryPushEvent + to *model.Pipeline + }{ + { + from: &bb.RepositoryPushEvent{}, + to: nil, + }, + { + from: &bb.RepositoryPushEvent{ + Changes: []bb.RepositoryPushEventChange{ + { + FromHash: "1234567890abcdef", + ToHash: "0000000000000000000000000000000000000000", + }, + }, + }, + to: nil, + }, + { + from: &bb.RepositoryPushEvent{ + Changes: []bb.RepositoryPushEventChange{ + { + FromHash: "0000000000000000000000000000000000000000", + ToHash: "1234567890abcdef", + Type: bb.RepositoryPushEventChangeTypeDelete, + }, + }, + }, + to: nil, + }, + { + from: &bb.RepositoryPushEvent{ + Event: bb.Event{ + Date: bb.ISOTime(now), + Actor: bb.User{ + Name: "John Doe", + Email: "john.doe@mail.com", + Slug: "john.doe_mail.com", + }, + }, + Repository: bb.Repository{ + Slug: "REPO", + Project: &bb.Project{ + Key: "PRJ", + }, + }, + Changes: []bb.RepositoryPushEventChange{ + { + Ref: bb.RepositoryPushEventRef{ + ID: "refs/head/branch", + DisplayID: "branch", + }, + RefId: "refs/head/branch", + ToHash: "1234567890abcdef", + }, + }, + }, + to: &model.Pipeline{ + Commit: "1234567890abcdef", + Branch: "branch", + Message: "", + Avatar: "https://base.url/users/john.doe_mail.com/avatar.png", + Author: "John Doe", + Email: "john.doe@mail.com", + Timestamp: now.UTC().Unix(), + Ref: "refs/head/branch", + ForgeURL: "https://base.url/projects/PRJ/repos/REPO/commits/1234567890abcdef", + Event: model.EventPush, + }, + }, + } + for _, tt := range tests { + to := convertRepositoryPushEvent(tt.from, "https://base.url") + assert.Equal(t, tt.to, to) + } +} + +func Test_convertPullRequestEvent(t *testing.T) { + now := time.Now() + from := &bb.PullRequestEvent{ + Event: bb.Event{ + Date: bb.ISOTime(now), + EventKey: bb.EventKeyPullRequestFrom, + Actor: bb.User{ + Name: "John Doe", + Email: "john.doe@mail.com", + Slug: "john.doe_mail.com", + }, + }, + PullRequest: bb.PullRequest{ + ID: 123, + Title: "my title", + Source: bb.PullRequestRef{ + ID: "refs/head/branch", + DisplayID: "branch", + Latest: "1234567890abcdef", + Repository: bb.Repository{ + Slug: "REPO", + Project: &bb.Project{ + Key: "PRJ", + }, + }, + }, + Target: bb.PullRequestRef{ + ID: "refs/head/main", + DisplayID: "main", + Latest: "abcdef1234567890", + Repository: bb.Repository{ + Slug: "REPO", + Project: &bb.Project{ + Key: "PRJ", + }, + }, + }, + }, + } + to := convertPullRequestEvent(from, "https://base.url") + assert.Equal(t, &model.Pipeline{ + Commit: "1234567890abcdef", + Branch: "branch", + Avatar: "https://base.url/users/john.doe_mail.com/avatar.png", + Author: "John Doe", + Email: "john.doe@mail.com", + Timestamp: now.UTC().Unix(), + Ref: "refs/pull-requests/123/from", + ForgeURL: "https://base.url/projects/PRJ/repos/REPO/commits/1234567890abcdef", + Event: model.EventPull, + Refspec: "branch:main", + Title: "my title", + }, to) +} + +func Test_convertPullRequestCloseEvent(t *testing.T) { + now := time.Now() + from := &bb.PullRequestEvent{ + Event: bb.Event{ + Date: bb.ISOTime(now), + EventKey: bb.EventKeyPullRequestMerged, + Actor: bb.User{ + Name: "John Doe", + Email: "john.doe@mail.com", + Slug: "john.doe_mail.com", + }, + }, + PullRequest: bb.PullRequest{ + ID: 123, + Title: "my title", + Source: bb.PullRequestRef{ + ID: "refs/head/branch", + DisplayID: "branch", + Latest: "1234567890abcdef", + Repository: bb.Repository{ + Slug: "REPO", + Project: &bb.Project{ + Key: "PRJ", + }, + }, + }, + Target: bb.PullRequestRef{ + ID: "refs/head/main", + DisplayID: "main", + Latest: "abcdef1234567890", + Repository: bb.Repository{ + Slug: "REPO", + Project: &bb.Project{ + Key: "PRJ", + }, + }, + }, + }, + } + to := convertPullRequestEvent(from, "https://base.url") + assert.Equal(t, &model.Pipeline{ + Commit: "1234567890abcdef", + Branch: "branch", + Avatar: "https://base.url/users/john.doe_mail.com/avatar.png", + Author: "John Doe", + Email: "john.doe@mail.com", + Timestamp: now.UTC().Unix(), + Ref: "refs/pull-requests/123/from", + ForgeURL: "https://base.url/projects/PRJ/repos/REPO/commits/1234567890abcdef", + Event: model.EventPullClosed, + Refspec: "branch:main", + Title: "my title", + }, to) +} + +func Test_authorLabel(t *testing.T) { + tests := []struct { + from string + to string + }{ + { + from: "Some Short Author", + to: "Some Short Author", + }, + { + from: "Some Very Long Author That May Include Multiple Names Here", + //nolint:misspell + to: "Some Very Long Author That May Includ...", + }, + } + for _, tt := range tests { + assert.Equal(t, tt.to, authorLabel(tt.from)) + } +} + +func Test_convertUser(t *testing.T) { + from := &bb.User{ + Slug: "slug", + Email: "john.doe@mail.com", + ID: 1, + } + to := convertUser(from, "https://base.url") + assert.Equal(t, &model.User{ + Login: "slug", + Avatar: "https://base.url/users/slug/avatar.png", + Email: "john.doe@mail.com", + ForgeRemoteID: "1", + }, to) +} + +func Test_convertProjectsToTeams(t *testing.T) { + tests := []struct { + projects []*bb.Project + baseURL string + expected []*model.Team + }{ + { + projects: []*bb.Project{ + { + Key: "PRJ1", + }, + { + Key: "PRJ2", + }, + }, + baseURL: "https://base.url", + expected: []*model.Team{ + { + Login: "PRJ1", + Avatar: "https://base.url/projects/PRJ1/avatar.png", + }, + { + Login: "PRJ2", + Avatar: "https://base.url/projects/PRJ2/avatar.png", + }, + }, + }, + { + projects: []*bb.Project{}, + baseURL: "https://base.url", + expected: []*model.Team{}, + }, + } + + for _, tt := range tests { + // Parse the baseURL string into a *url.URL + parsedURL, err := url.Parse(tt.baseURL) + assert.NoError(t, err) + + mockClient := &bb.Client{BaseURL: parsedURL} + actual := convertProjectsToTeams(tt.projects, mockClient) + + assert.Equal(t, tt.expected, actual) + } } diff --git a/server/forge/bitbucketdatacenter/internal/client_test.go b/server/forge/bitbucketdatacenter/internal/client_test.go index d8412e3c6..94f01f084 100644 --- a/server/forge/bitbucketdatacenter/internal/client_test.go +++ b/server/forge/bitbucketdatacenter/internal/client_test.go @@ -20,7 +20,7 @@ import ( "net/http/httptest" "testing" - "github.com/franela/goblin" + "github.com/stretchr/testify/assert" "golang.org/x/oauth2" ) @@ -30,20 +30,14 @@ func TestCurrentUser(t *testing.T) { _, _ = w.Write([]byte(`tal@netic.dk`)) })) - g := goblin.Goblin(t) - g.Describe("Bitbucket Current User", func() { - g.After(func() { - s.Close() - }) - g.It("should return current user id", func() { - ctx := context.Background() - ts := mockSource("bearer-token") - client := NewClientWithToken(ctx, ts, s.URL) - uid, err := client.FindCurrentUser(ctx) - g.Assert(err).IsNil() - g.Assert(uid).Equal("tal_netic.dk") - }) - }) + defer s.Close() + + ctx := context.Background() + ts := mockSource("bearer-token") + client := NewClientWithToken(ctx, ts, s.URL) + uid, err := client.FindCurrentUser(ctx) + assert.NoError(t, err) + assert.Equal(t, "tal_netic.dk", uid) } type mockSource string diff --git a/server/forge/common/status.go b/server/forge/common/status.go index 845edc2bf..3e07f041b 100644 --- a/server/forge/common/status.go +++ b/server/forge/common/status.go @@ -21,8 +21,8 @@ import ( "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func GetPipelineStatusContext(repo *model.Repo, pipeline *model.Pipeline, workflow *model.Workflow) string { diff --git a/server/forge/common/status_test.go b/server/forge/common/status_test.go index cbf3fd9e1..04e834355 100644 --- a/server/forge/common/status_test.go +++ b/server/forge/common/status_test.go @@ -19,8 +19,8 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func TestGetPipelineStatusContext(t *testing.T) { diff --git a/server/forge/common/utils.go b/server/forge/common/utils.go index 6fb768587..549e048cc 100644 --- a/server/forge/common/utils.go +++ b/server/forge/common/utils.go @@ -22,8 +22,8 @@ import ( "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) func ExtractHostFromCloneURL(cloneURL string) (string, error) { @@ -46,7 +46,7 @@ func ExtractHostFromCloneURL(cloneURL string) (string, error) { func UserToken(ctx context.Context, r *model.Repo, u *model.User) string { if u != nil { - return u.Token + return u.AccessToken } _store, ok := store.TryFromContext(ctx) @@ -62,5 +62,5 @@ func UserToken(ctx context.Context, r *model.Repo, u *model.User) string { if err != nil { return "" } - return user.Token + return user.AccessToken } diff --git a/server/forge/common/utils_test.go b/server/forge/common/utils_test.go index 159902912..c979b25b4 100644 --- a/server/forge/common/utils_test.go +++ b/server/forge/common/utils_test.go @@ -19,7 +19,7 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/common" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/common" ) func Test_Netrc(t *testing.T) { diff --git a/server/forge/forge.go b/server/forge/forge.go index 4169e60d0..f5102a73e 100644 --- a/server/forge/forge.go +++ b/server/forge/forge.go @@ -21,8 +21,8 @@ import ( "context" "net/http" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) // TODO: use pagination diff --git a/server/forge/forgejo/forgejo.go b/server/forge/forgejo/forgejo.go index 4185a04cc..1e5a0d418 100644 --- a/server/forge/forgejo/forgejo.go +++ b/server/forge/forgejo/forgejo.go @@ -30,13 +30,13 @@ import ( "github.com/rs/zerolog/log" "golang.org/x/oauth2" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/common" - forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" - shared_utils "go.woodpecker-ci.org/woodpecker/v2/shared/utils" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/common" + forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" + shared_utils "go.woodpecker-ci.org/woodpecker/v3/shared/utils" ) const ( @@ -133,8 +133,8 @@ func (c *Forgejo) Login(ctx context.Context, req *forge_types.OAuthRequest) (*mo } return &model.User{ - Token: token.AccessToken, - Secret: token.RefreshToken, + AccessToken: token.AccessToken, + RefreshToken: token.RefreshToken, Expiry: token.Expiry.UTC().Unix(), Login: account.UserName, Email: account.Email, @@ -164,8 +164,8 @@ func (c *Forgejo) Refresh(ctx context.Context, user *model.User) (bool, error) { config.RedirectURL = "" source := config.TokenSource(oauth2Ctx, &oauth2.Token{ - AccessToken: user.Token, - RefreshToken: user.Secret, + AccessToken: user.AccessToken, + RefreshToken: user.RefreshToken, Expiry: time.Unix(user.Expiry, 0), }) @@ -174,15 +174,15 @@ func (c *Forgejo) Refresh(ctx context.Context, user *model.User) (bool, error) { return false, err } - user.Token = token.AccessToken - user.Secret = token.RefreshToken + user.AccessToken = token.AccessToken + user.RefreshToken = token.RefreshToken user.Expiry = token.Expiry.UTC().Unix() return true, nil } // Teams is supported by the Forgejo driver. func (c *Forgejo) Teams(ctx context.Context, u *model.User) ([]*model.Team, error) { - client, err := c.newClientToken(ctx, u.Token) + client, err := c.newClientToken(ctx, u.AccessToken) if err != nil { return nil, err } @@ -201,7 +201,7 @@ func (c *Forgejo) Teams(ctx context.Context, u *model.User) ([]*model.Team, erro teams = append(teams, toTeam(org, c.url)) } return teams, err - }) + }, -1) } // TeamPerm is not supported by the Forgejo driver. @@ -211,7 +211,7 @@ func (c *Forgejo) TeamPerm(_ *model.User, _ string) (*model.Perm, error) { // Repo returns the Forgejo repository. func (c *Forgejo) Repo(ctx context.Context, u *model.User, remoteID model.ForgeRemoteID, owner, name string) (*model.Repo, error) { - client, err := c.newClientToken(ctx, u.Token) + client, err := c.newClientToken(ctx, u.AccessToken) if err != nil { return nil, err } @@ -238,7 +238,7 @@ func (c *Forgejo) Repo(ctx context.Context, u *model.User, remoteID model.ForgeR // Repos returns a list of all repositories for the Forgejo account, including // organization repositories. func (c *Forgejo) Repos(ctx context.Context, u *model.User) ([]*model.Repo, error) { - client, err := c.newClientToken(ctx, u.Token) + client, err := c.newClientToken(ctx, u.AccessToken) if err != nil { return nil, err } @@ -253,7 +253,7 @@ func (c *Forgejo) Repos(ctx context.Context, u *model.User) ([]*model.Repo, erro }, ) return repos, err - }) + }, -1) result := make([]*model.Repo, 0, len(repos)) for _, repo := range repos { @@ -267,7 +267,7 @@ func (c *Forgejo) Repos(ctx context.Context, u *model.User) ([]*model.Repo, erro // File fetches the file from the Forgejo repository and returns its contents. func (c *Forgejo) File(ctx context.Context, u *model.User, r *model.Repo, b *model.Pipeline, f string) ([]byte, error) { - client, err := c.newClientToken(ctx, u.Token) + client, err := c.newClientToken(ctx, u.AccessToken) if err != nil { return nil, err } @@ -282,7 +282,7 @@ func (c *Forgejo) File(ctx context.Context, u *model.User, r *model.Repo, b *mod func (c *Forgejo) Dir(ctx context.Context, u *model.User, r *model.Repo, b *model.Pipeline, f string) ([]*forge_types.FileMeta, error) { var configs []*forge_types.FileMeta - client, err := c.newClientToken(ctx, u.Token) + client, err := c.newClientToken(ctx, u.AccessToken) if err != nil { return nil, err } @@ -318,7 +318,7 @@ func (c *Forgejo) Dir(ctx context.Context, u *model.User, r *model.Repo, b *mode // Status is supported by the Forgejo driver. func (c *Forgejo) Status(ctx context.Context, user *model.User, repo *model.Repo, pipeline *model.Pipeline, workflow *model.Workflow) error { - client, err := c.newClientToken(ctx, user.Token) + client, err := c.newClientToken(ctx, user.AccessToken) if err != nil { return err } @@ -346,7 +346,7 @@ func (c *Forgejo) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) { if u != nil { login = u.Login - token = u.Token + token = u.AccessToken } host, err := common.ExtractHostFromCloneURL(r.Clone) @@ -376,7 +376,7 @@ func (c *Forgejo) Activate(ctx context.Context, u *model.User, r *model.Repo, li Active: true, } - client, err := c.newClientToken(ctx, u.Token) + client, err := c.newClientToken(ctx, u.AccessToken) if err != nil { return err } @@ -398,7 +398,7 @@ func (c *Forgejo) Activate(ctx context.Context, u *model.User, r *model.Repo, li // Deactivate deactivates the repository be removing repository push hooks from // the Forgejo repository. func (c *Forgejo) Deactivate(ctx context.Context, u *model.User, r *model.Repo, link string) error { - client, err := c.newClientToken(ctx, u.Token) + client, err := c.newClientToken(ctx, u.AccessToken) if err != nil { return err } @@ -411,7 +411,7 @@ func (c *Forgejo) Deactivate(ctx context.Context, u *model.User, r *model.Repo, }, }) return hooks, err - }) + }, -1) if err != nil { return err } @@ -522,7 +522,7 @@ func (c *Forgejo) Hook(ctx context.Context, r *http.Request) (*model.Repo, *mode // OrgMembership returns if user is member of organization and if user // is admin/owner in this organization. func (c *Forgejo) OrgMembership(ctx context.Context, u *model.User, owner string) (*model.OrgPerm, error) { - client, err := c.newClientToken(ctx, u.Token) + client, err := c.newClientToken(ctx, u.AccessToken) if err != nil { return nil, err } @@ -545,7 +545,7 @@ func (c *Forgejo) OrgMembership(ctx context.Context, u *model.User, owner string } func (c *Forgejo) Org(ctx context.Context, u *model.User, owner string) (*model.Org, error) { - client, err := c.newClientToken(ctx, u.Token) + client, err := c.newClientToken(ctx, u.AccessToken) if err != nil { return nil, err } @@ -630,7 +630,7 @@ func (c *Forgejo) getChangedFilesForPR(ctx context.Context, repo *model.Repo, in return nil, err } - client, err := c.newClientToken(ctx, user.Token) + client, err := c.newClientToken(ctx, user.AccessToken) if err != nil { return nil, err } @@ -647,7 +647,7 @@ func (c *Forgejo) getChangedFilesForPR(ctx context.Context, repo *model.Repo, in files = append(files, file.Filename) } return files, nil - }) + }, -1) } func (c *Forgejo) getTagCommitSHA(ctx context.Context, repo *model.Repo, tagName string) (string, error) { @@ -667,7 +667,7 @@ func (c *Forgejo) getTagCommitSHA(ctx context.Context, repo *model.Repo, tagName return "", err } - client, err := c.newClientToken(ctx, user.Token) + client, err := c.newClientToken(ctx, user.AccessToken) if err != nil { return "", err } diff --git a/server/forge/forgejo/forgejo_test.go b/server/forge/forgejo/forgejo_test.go index 67cbdc88b..0dcbc49fb 100644 --- a/server/forge/forgejo/forgejo_test.go +++ b/server/forge/forgejo/forgejo_test.go @@ -21,21 +21,32 @@ import ( "net/http/httptest" "testing" - "github.com/franela/goblin" "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/forgejo/fixtures" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" - mocks_store "go.woodpecker-ci.org/woodpecker/v2/server/store/mocks" - "go.woodpecker-ci.org/woodpecker/v2/shared/utils" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/forgejo/fixtures" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" + mocks_store "go.woodpecker-ci.org/woodpecker/v3/server/store/mocks" ) +func TestNew(t *testing.T) { + forge, _ := New(Opts{ + URL: "http://localhost:8080", + SkipVerify: true, + }) + + f, _ := forge.(*Forgejo) + assert.Equal(t, "http://localhost:8080", f.url) + assert.True(t, f.SkipVerify) +} + func Test_forgejo(t *testing.T) { gin.SetMode(gin.TestMode) s := httptest.NewServer(fixtures.Handler()) + defer s.Close() c, _ := New(Opts{ URL: s.URL, SkipVerify: true, @@ -44,134 +55,95 @@ func Test_forgejo(t *testing.T) { mockStore := mocks_store.NewStore(t) ctx := store.InjectToContext(context.Background(), mockStore) - g := goblin.Goblin(t) - g.Describe("Forgejo", func() { - g.After(func() { - s.Close() - }) + t.Run("netrc with user token", func(t *testing.T) { + forge, _ := New(Opts{}) + netrc, _ := forge.Netrc(fakeUser, fakeRepo) + assert.Equal(t, "forgejo.org", netrc.Machine) + assert.Equal(t, fakeUser.Login, netrc.Login) + assert.Equal(t, fakeUser.AccessToken, netrc.Password) + }) + t.Run("netrc with machine account", func(t *testing.T) { + forge, _ := New(Opts{}) + netrc, _ := forge.Netrc(nil, fakeRepo) + assert.Equal(t, "forgejo.org", netrc.Machine) + assert.Empty(t, netrc.Login) + assert.Empty(t, netrc.Password) + }) - g.Describe("Creating a forge", func() { - g.It("Should return client with specified options", func() { - forge, _ := New(Opts{ - URL: "http://localhost:8080", - SkipVerify: true, - }) + t.Run("repository details", func(t *testing.T) { + repo, err := c.Repo(ctx, fakeUser, fakeRepo.ForgeRemoteID, fakeRepo.Owner, fakeRepo.Name) + assert.NoError(t, err) + assert.Equal(t, fakeRepo.Owner, repo.Owner) + assert.Equal(t, fakeRepo.Name, repo.Name) + assert.Equal(t, fakeRepo.Owner+"/"+fakeRepo.Name, repo.FullName) + assert.True(t, repo.IsSCMPrivate) + assert.Equal(t, "http://localhost/test_name/repo_name.git", repo.Clone) + assert.Equal(t, "http://localhost/test_name/repo_name", repo.ForgeURL) + }) + t.Run("repo not found", func(t *testing.T) { + _, err := c.Repo(ctx, fakeUser, "0", fakeRepoNotFound.Owner, fakeRepoNotFound.Name) + assert.Error(t, err) + }) - f, _ := forge.(*Forgejo) - g.Assert(f.url).Equal("http://localhost:8080") - g.Assert(f.SkipVerify).Equal(true) - }) - }) + t.Run("repository list", func(t *testing.T) { + repos, err := c.Repos(ctx, fakeUser) + assert.NoError(t, err) + assert.Equal(t, fakeRepo.ForgeRemoteID, repos[0].ForgeRemoteID) + assert.Equal(t, fakeRepo.Owner, repos[0].Owner) + assert.Equal(t, fakeRepo.Name, repos[0].Name) + assert.Equal(t, fakeRepo.Owner+"/"+fakeRepo.Name, repos[0].FullName) + }) + t.Run("not found error", func(t *testing.T) { + _, err := c.Repos(ctx, fakeUserNoRepos) + assert.Error(t, err) + }) - g.Describe("Generating a netrc file", func() { - g.It("Should return a netrc with the user token", func() { - forge, _ := New(Opts{}) - netrc, _ := forge.Netrc(fakeUser, fakeRepo) - g.Assert(netrc.Machine).Equal("forgejo.org") - g.Assert(netrc.Login).Equal(fakeUser.Login) - g.Assert(netrc.Password).Equal(fakeUser.Token) - }) - g.It("Should return a netrc with the machine account", func() { - forge, _ := New(Opts{}) - netrc, _ := forge.Netrc(nil, fakeRepo) - g.Assert(netrc.Machine).Equal("forgejo.org") - g.Assert(netrc.Login).Equal("") - g.Assert(netrc.Password).Equal("") - }) - }) + t.Run("register repository", func(t *testing.T) { + err := c.Activate(ctx, fakeUser, fakeRepo, "http://localhost") + assert.NoError(t, err) + }) - g.Describe("Requesting a repository", func() { - g.It("Should return the repository details", func() { - repo, err := c.Repo(ctx, fakeUser, fakeRepo.ForgeRemoteID, fakeRepo.Owner, fakeRepo.Name) - g.Assert(err).IsNil() - g.Assert(repo.Owner).Equal(fakeRepo.Owner) - g.Assert(repo.Name).Equal(fakeRepo.Name) - g.Assert(repo.FullName).Equal(fakeRepo.Owner + "/" + fakeRepo.Name) - g.Assert(repo.IsSCMPrivate).IsTrue() - g.Assert(repo.Clone).Equal("http://localhost/test_name/repo_name.git") - g.Assert(repo.ForgeURL).Equal("http://localhost/test_name/repo_name") - }) - g.It("Should handle a not found error", func() { - _, err := c.Repo(ctx, fakeUser, "0", fakeRepoNotFound.Owner, fakeRepoNotFound.Name) - g.Assert(err).IsNotNil() - }) - }) + t.Run("remove hooks", func(t *testing.T) { + err := c.Deactivate(ctx, fakeUser, fakeRepo, "http://localhost") + assert.NoError(t, err) + }) - g.Describe("Requesting a repository list", func() { - g.It("Should return the repository list", func() { - repos, err := c.Repos(ctx, fakeUser) - g.Assert(err).IsNil() - g.Assert(repos[0].ForgeRemoteID).Equal(fakeRepo.ForgeRemoteID) - g.Assert(repos[0].Owner).Equal(fakeRepo.Owner) - g.Assert(repos[0].Name).Equal(fakeRepo.Name) - g.Assert(repos[0].FullName).Equal(fakeRepo.Owner + "/" + fakeRepo.Name) - }) - g.It("Should handle a not found error", func() { - _, err := c.Repos(ctx, fakeUserNoRepos) - g.Assert(err).IsNotNil() - }) - }) + t.Run("repository file", func(t *testing.T) { + raw, err := c.File(ctx, fakeUser, fakeRepo, fakePipeline, ".woodpecker.yml") + assert.NoError(t, err) + assert.Equal(t, "{ platform: linux/amd64 }", string(raw)) + }) - g.It("Should register repository hooks", func() { - err := c.Activate(ctx, fakeUser, fakeRepo, "http://localhost") - g.Assert(err).IsNil() - }) + t.Run("pipeline status", func(t *testing.T) { + err := c.Status(ctx, fakeUser, fakeRepo, fakePipeline, fakeWorkflow) + assert.NoError(t, err) + }) - g.It("Should remove repository hooks", func() { - err := c.Deactivate(ctx, fakeUser, fakeRepo, "http://localhost") - g.Assert(err).IsNil() - }) - - g.It("Should return a repository file", func() { - raw, err := c.File(ctx, fakeUser, fakeRepo, fakePipeline, ".woodpecker.yml") - g.Assert(err).IsNil() - g.Assert(string(raw)).Equal("{ platform: linux/amd64 }") - }) - - g.It("Should return nil from send pipeline status", func() { - err := c.Status(ctx, fakeUser, fakeRepo, fakePipeline, fakeWorkflow) - g.Assert(err).IsNil() - }) - - g.Describe("Given an authentication request", func() { - g.It("Should redirect to login form") - g.It("Should create an access token") - g.It("Should handle an access token error") - g.It("Should return the authenticated user") - }) - - g.Describe("Given a repository hook", func() { - g.It("Should skip non-push events") - g.It("Should return push details") - g.It("Should handle a parsing error") - }) - - g.It("Given a PR hook", func() { - buf := bytes.NewBufferString(fixtures.HookPullRequest) - req, _ := http.NewRequest(http.MethodPost, "/hook", buf) - req.Header = http.Header{} - req.Header.Set(hookEvent, hookPullRequest) - mockStore.On("GetRepoNameFallback", mock.Anything, mock.Anything).Return(fakeRepo, nil) - mockStore.On("GetUser", mock.Anything).Return(fakeUser, nil) - r, b, err := c.Hook(ctx, req) - g.Assert(r).IsNotNil() - g.Assert(b).IsNotNil() - g.Assert(err).IsNil() - g.Assert(b.Event).Equal(model.EventPull) - g.Assert(utils.EqualSliceValues(b.ChangedFiles, []string{"README.md"})).IsTrue() - }) + t.Run("PR hook", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookPullRequest) + req, _ := http.NewRequest(http.MethodPost, "/hook", buf) + req.Header = http.Header{} + req.Header.Set(hookEvent, hookPullRequest) + mockStore.On("GetRepoNameFallback", mock.Anything, mock.Anything).Return(fakeRepo, nil) + mockStore.On("GetUser", mock.Anything).Return(fakeUser, nil) + r, b, err := c.Hook(ctx, req) + assert.NotNil(t, r) + assert.NotNil(t, b) + assert.NoError(t, err) + assert.Equal(t, model.EventPull, b.Event) + assert.Equal(t, []string{"README.md"}, b.ChangedFiles) }) } var ( fakeUser = &model.User{ - Login: "someuser", - Token: "cfcd2084", + Login: "someuser", + AccessToken: "cfcd2084", } fakeUserNoRepos = &model.User{ - Login: "someuser", - Token: "repos_not_found", + Login: "someuser", + AccessToken: "repos_not_found", } fakeRepo = &model.Repo{ diff --git a/server/forge/forgejo/helper.go b/server/forge/forgejo/helper.go index 0ab2b24e0..394f6a133 100644 --- a/server/forge/forgejo/helper.go +++ b/server/forge/forgejo/helper.go @@ -24,8 +24,8 @@ import ( "codeberg.org/mvdkleijn/forgejo-sdk/forgejo" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/shared/utils" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/shared/utils" ) // toRepo converts a Forgejo repository to a Woodpecker repository. @@ -37,7 +37,6 @@ func toRepo(from *forgejo.Repository) *model.Repo { ) return &model.Repo{ ForgeRemoteID: model.ForgeRemoteID(fmt.Sprint(from.ID)), - SCMKind: model.RepoGit, Name: name, Owner: from.Owner.UserName, FullName: from.FullName, @@ -171,6 +170,7 @@ func pipelineFromPullRequest(hook *pullRequestHook) *model.Pipeline { hook.PullRequest.Base.Ref, ), PullRequestLabels: convertLabels(hook.PullRequest.Labels), + FromFork: hook.PullRequest.Head.RepoID != hook.PullRequest.Base.RepoID, } return pipeline diff --git a/server/forge/forgejo/helper_test.go b/server/forge/forgejo/helper_test.go index b87996d1e..b7414a504 100644 --- a/server/forge/forgejo/helper_test.go +++ b/server/forge/forgejo/helper_test.go @@ -19,255 +19,252 @@ import ( "testing" "codeberg.org/mvdkleijn/forgejo-sdk/forgejo" - "github.com/franela/goblin" + "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/forgejo/fixtures" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/shared/utils" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/forgejo/fixtures" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) -func Test_parse(t *testing.T) { - g := goblin.Goblin(t) - g.Describe("Forgejo", func() { - g.It("Should parse push hook payload", func() { - buf := bytes.NewBufferString(fixtures.HookPush) - hook, err := parsePush(buf) - g.Assert(err).IsNil() - g.Assert(hook.Ref).Equal("refs/heads/main") - g.Assert(hook.After).Equal("ef98532add3b2feb7a137426bba1248724367df5") - g.Assert(hook.Before).Equal("4b2626259b5a97b6b4eab5e6cca66adb986b672b") - g.Assert(hook.Compare).Equal("http://forgejo.golang.org/gordon/hello-world/compare/4b2626259b5a97b6b4eab5e6cca66adb986b672b...ef98532add3b2feb7a137426bba1248724367df5") - g.Assert(hook.Repo.Name).Equal("hello-world") - g.Assert(hook.Repo.HTMLURL).Equal("http://forgejo.golang.org/gordon/hello-world") - g.Assert(hook.Repo.Owner.UserName).Equal("gordon") - g.Assert(hook.Repo.FullName).Equal("gordon/hello-world") - g.Assert(hook.Repo.Owner.Email).Equal("gordon@golang.org") - g.Assert(hook.Repo.Private).Equal(true) - g.Assert(hook.Pusher.Email).Equal("gordon@golang.org") - g.Assert(hook.Pusher.UserName).Equal("gordon") - g.Assert(hook.Sender.UserName).Equal("gordon") - g.Assert(hook.Sender.AvatarURL).Equal("http://forgejo.golang.org///1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87") - }) +func Test_parsePush(t *testing.T) { + t.Run("Should parse push hook payload", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookPush) + hook, err := parsePush(buf) + assert.NoError(t, err) + assert.Equal(t, "refs/heads/main", hook.Ref) + assert.Equal(t, "ef98532add3b2feb7a137426bba1248724367df5", hook.After) + assert.Equal(t, "4b2626259b5a97b6b4eab5e6cca66adb986b672b", hook.Before) + assert.Equal(t, "http://forgejo.golang.org/gordon/hello-world/compare/4b2626259b5a97b6b4eab5e6cca66adb986b672b...ef98532add3b2feb7a137426bba1248724367df5", hook.Compare) + assert.Equal(t, "hello-world", hook.Repo.Name) + assert.Equal(t, "http://forgejo.golang.org/gordon/hello-world", hook.Repo.HTMLURL) + assert.Equal(t, "gordon", hook.Repo.Owner.UserName) + assert.Equal(t, "gordon/hello-world", hook.Repo.FullName) + assert.Equal(t, "gordon@golang.org", hook.Repo.Owner.Email) + assert.True(t, hook.Repo.Private) + assert.Equal(t, "gordon@golang.org", hook.Pusher.Email) + assert.Equal(t, "gordon", hook.Pusher.UserName) + assert.Equal(t, "gordon", hook.Sender.UserName) + assert.Equal(t, "http://forgejo.golang.org///1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", hook.Sender.AvatarURL) + }) + t.Run("Should parse tag hook payload", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookTag) + hook, err := parsePush(buf) + assert.NoError(t, err) + assert.Equal(t, "v1.0.0", hook.Ref) + assert.Equal(t, "ef98532add3b2feb7a137426bba1248724367df5", hook.Sha) + assert.Equal(t, "hello-world", hook.Repo.Name) + assert.Equal(t, "http://forgejo.golang.org/gordon/hello-world", hook.Repo.HTMLURL) + assert.Equal(t, "gordon/hello-world", hook.Repo.FullName) + assert.Equal(t, "gordon@golang.org", hook.Repo.Owner.Email) + assert.Equal(t, "gordon", hook.Repo.Owner.UserName) + assert.True(t, hook.Repo.Private) + assert.Equal(t, "gordon", hook.Sender.UserName) + assert.Equal(t, "https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", hook.Sender.AvatarURL) + }) - g.It("Should parse tag hook payload", func() { - buf := bytes.NewBufferString(fixtures.HookTag) - hook, err := parsePush(buf) - g.Assert(err).IsNil() - g.Assert(hook.Ref).Equal("v1.0.0") - g.Assert(hook.Sha).Equal("ef98532add3b2feb7a137426bba1248724367df5") - g.Assert(hook.Repo.Name).Equal("hello-world") - g.Assert(hook.Repo.HTMLURL).Equal("http://forgejo.golang.org/gordon/hello-world") - g.Assert(hook.Repo.FullName).Equal("gordon/hello-world") - g.Assert(hook.Repo.Owner.Email).Equal("gordon@golang.org") - g.Assert(hook.Repo.Owner.UserName).Equal("gordon") - g.Assert(hook.Repo.Private).Equal(true) - g.Assert(hook.Sender.UserName).Equal("gordon") - g.Assert(hook.Sender.AvatarURL).Equal("https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87") - }) + t.Run("Should return a Pipeline struct from a push hook", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookPush) + hook, _ := parsePush(buf) + pipeline := pipelineFromPush(hook) + assert.Equal(t, model.EventPush, pipeline.Event) + assert.Equal(t, hook.After, pipeline.Commit) + assert.Equal(t, hook.Ref, pipeline.Ref) + assert.Equal(t, hook.Commits[0].URL, pipeline.ForgeURL) + assert.Equal(t, "main", pipeline.Branch) + assert.Equal(t, hook.Commits[0].Message, pipeline.Message) + assert.Equal(t, "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", pipeline.Avatar) + assert.Equal(t, hook.Sender.UserName, pipeline.Author) + assert.Equal(t, []string{"CHANGELOG.md", "app/controller/application.rb"}, pipeline.ChangedFiles) + }) - g.It("Should parse pull_request hook payload", func() { - buf := bytes.NewBufferString(fixtures.HookPullRequest) - hook, err := parsePullRequest(buf) - g.Assert(err).IsNil() - g.Assert(hook.Action).Equal("opened") - g.Assert(hook.Number).Equal(int64(1)) + t.Run("Should return a Repo struct from a push hook", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookPush) + hook, _ := parsePush(buf) + repo := toRepo(hook.Repo) + assert.Equal(t, hook.Repo.Name, repo.Name) + assert.Equal(t, hook.Repo.Owner.UserName, repo.Owner) + assert.Equal(t, "gordon/hello-world", repo.FullName) + assert.Equal(t, hook.Repo.HTMLURL, repo.ForgeURL) + }) - g.Assert(hook.Repo.Name).Equal("hello-world") - g.Assert(hook.Repo.HTMLURL).Equal("http://forgejo.golang.org/gordon/hello-world") - g.Assert(hook.Repo.FullName).Equal("gordon/hello-world") - g.Assert(hook.Repo.Owner.Email).Equal("gordon@golang.org") - g.Assert(hook.Repo.Owner.UserName).Equal("gordon") - g.Assert(hook.Repo.Private).Equal(true) - g.Assert(hook.Sender.UserName).Equal("gordon") - g.Assert(hook.Sender.AvatarURL).Equal("https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87") - - g.Assert(hook.PullRequest.Title).Equal("Update the README with new information") - g.Assert(hook.PullRequest.Body).Equal("please merge") - g.Assert(hook.PullRequest.State).Equal(forgejo.StateOpen) - g.Assert(hook.PullRequest.Poster.UserName).Equal("gordon") - g.Assert(hook.PullRequest.Base.Name).Equal("main") - g.Assert(hook.PullRequest.Base.Ref).Equal("main") - g.Assert(hook.PullRequest.Head.Name).Equal("feature/changes") - g.Assert(hook.PullRequest.Head.Ref).Equal("feature/changes") - }) - - g.It("Should return a Pipeline struct from a push hook", func() { - buf := bytes.NewBufferString(fixtures.HookPush) - hook, _ := parsePush(buf) - pipeline := pipelineFromPush(hook) - g.Assert(pipeline.Event).Equal(model.EventPush) - g.Assert(pipeline.Commit).Equal(hook.After) - g.Assert(pipeline.Ref).Equal(hook.Ref) - g.Assert(pipeline.ForgeURL).Equal(hook.Commits[0].URL) - g.Assert(pipeline.Branch).Equal("main") - g.Assert(pipeline.Message).Equal(hook.Commits[0].Message) - g.Assert(pipeline.Avatar).Equal("http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87") - g.Assert(pipeline.Author).Equal(hook.Sender.UserName) - g.Assert(utils.EqualSliceValues(pipeline.ChangedFiles, []string{"CHANGELOG.md", "app/controller/application.rb"})).IsTrue() - }) - - g.It("Should return a Repo struct from a push hook", func() { - buf := bytes.NewBufferString(fixtures.HookPush) - hook, _ := parsePush(buf) - repo := toRepo(hook.Repo) - g.Assert(repo.Name).Equal(hook.Repo.Name) - g.Assert(repo.Owner).Equal(hook.Repo.Owner.UserName) - g.Assert(repo.FullName).Equal("gordon/hello-world") - g.Assert(repo.ForgeURL).Equal(hook.Repo.HTMLURL) - }) - - g.It("Should return a Pipeline struct from a tag hook", func() { - buf := bytes.NewBufferString(fixtures.HookTag) - hook, _ := parsePush(buf) - pipeline := pipelineFromTag(hook) - g.Assert(pipeline.Event).Equal(model.EventTag) - g.Assert(pipeline.Commit).Equal(hook.Sha) - g.Assert(pipeline.Ref).Equal("refs/tags/v1.0.0") - g.Assert(pipeline.Branch).Equal("") - g.Assert(pipeline.ForgeURL).Equal("http://forgejo.golang.org/gordon/hello-world/src/tag/v1.0.0") - g.Assert(pipeline.Message).Equal("created tag v1.0.0") - }) - - g.It("Should return a Pipeline struct from a pull_request hook", func() { - buf := bytes.NewBufferString(fixtures.HookPullRequest) - hook, _ := parsePullRequest(buf) - pipeline := pipelineFromPullRequest(hook) - g.Assert(pipeline.Event).Equal(model.EventPull) - g.Assert(pipeline.Commit).Equal(hook.PullRequest.Head.Sha) - g.Assert(pipeline.Ref).Equal("refs/pull/1/head") - g.Assert(pipeline.ForgeURL).Equal("http://forgejo.golang.org/gordon/hello-world/pull/1") - g.Assert(pipeline.Branch).Equal("main") - g.Assert(pipeline.Refspec).Equal("feature/changes:main") - g.Assert(pipeline.Message).Equal(hook.PullRequest.Title) - g.Assert(pipeline.Avatar).Equal("http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87") - g.Assert(pipeline.Author).Equal(hook.PullRequest.Poster.UserName) - }) - - g.It("Should return a Repo struct from a pull_request hook", func() { - buf := bytes.NewBufferString(fixtures.HookPullRequest) - hook, _ := parsePullRequest(buf) - repo := toRepo(hook.Repo) - g.Assert(repo.Name).Equal(hook.Repo.Name) - g.Assert(repo.Owner).Equal(hook.Repo.Owner.UserName) - g.Assert(repo.FullName).Equal("gordon/hello-world") - g.Assert(repo.ForgeURL).Equal(hook.Repo.HTMLURL) - }) - - g.It("Should return a Perm struct from a Forgejo Perm", func() { - perms := []forgejo.Permission{ - { - Admin: true, - Push: true, - Pull: true, - }, - { - Admin: true, - Push: true, - Pull: false, - }, - { - Admin: true, - Push: false, - Pull: false, - }, - } - for _, from := range perms { - perm := toPerm(&from) - g.Assert(perm.Pull).Equal(from.Pull) - g.Assert(perm.Push).Equal(from.Push) - g.Assert(perm.Admin).Equal(from.Admin) - } - }) - - g.It("Should return a Team struct from a Forgejo Org", func() { - from := &forgejo.Organization{ - UserName: "woodpecker", - AvatarURL: "/avatars/1", - } - - to := toTeam(from, "http://localhost:80") - g.Assert(to.Login).Equal(from.UserName) - g.Assert(to.Avatar).Equal("http://localhost:80/avatars/1") - }) - - g.It("Should return a Repo struct from a Forgejo Repo", func() { - from := forgejo.Repository{ - FullName: "gophers/hello-world", - Owner: &forgejo.User{ - UserName: "gordon", - AvatarURL: "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", - }, - CloneURL: "http://forgejo.golang.org/gophers/hello-world.git", - HTMLURL: "http://forgejo.golang.org/gophers/hello-world", - Private: true, - DefaultBranch: "main", - Permissions: &forgejo.Permission{Admin: true}, - } - repo := toRepo(&from) - g.Assert(repo.FullName).Equal(from.FullName) - g.Assert(repo.Owner).Equal(from.Owner.UserName) - g.Assert(repo.Name).Equal("hello-world") - g.Assert(repo.Branch).Equal("main") - g.Assert(repo.ForgeURL).Equal(from.HTMLURL) - g.Assert(repo.Clone).Equal(from.CloneURL) - g.Assert(repo.Avatar).Equal(from.Owner.AvatarURL) - g.Assert(repo.IsSCMPrivate).Equal(from.Private) - g.Assert(repo.Perm.Admin).IsTrue() - }) - - g.It("Should correct a malformed avatar url", func() { - urls := []struct { - Before string - After string - }{ - { - "http://forgejo.golang.org///1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", - "//1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", - }, - { - "//1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", - "//1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", - }, - { - "http://forgejo.golang.org/avatars/1", - "http://forgejo.golang.org/avatars/1", - }, - { - "http://forgejo.golang.org//avatars/1", - "http://forgejo.golang.org/avatars/1", - }, - } - - for _, url := range urls { - got := fixMalformedAvatar(url.Before) - g.Assert(got).Equal(url.After) - } - }) - - g.It("Should expand the avatar url", func() { - urls := []struct { - Before string - After string - }{ - { - "/avatars/1", - "http://forgejo.io/avatars/1", - }, - { - "//1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", - "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", - }, - { - "/forgejo/avatars/2", - "http://forgejo.io/forgejo/avatars/2", - }, - } - - repo := "http://forgejo.io/foo/bar" - for _, url := range urls { - got := expandAvatar(repo, url.Before) - g.Assert(got).Equal(url.After) - } - }) + t.Run("Should return a Pipeline struct from a tag hook", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookTag) + hook, _ := parsePush(buf) + pipeline := pipelineFromTag(hook) + assert.Equal(t, model.EventTag, pipeline.Event) + assert.Equal(t, hook.Sha, pipeline.Commit) + assert.Equal(t, "refs/tags/v1.0.0", pipeline.Ref) + assert.Empty(t, pipeline.Branch) + assert.Equal(t, "http://forgejo.golang.org/gordon/hello-world/src/tag/v1.0.0", pipeline.ForgeURL) + assert.Equal(t, "created tag v1.0.0", pipeline.Message) }) } + +func Test_parsePullRequest(t *testing.T) { + t.Run("Should parse pull_request hook payload", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookPullRequest) + hook, err := parsePullRequest(buf) + assert.NoError(t, err) + assert.Equal(t, "opened", hook.Action) + assert.Equal(t, int64(1), hook.Number) + + assert.Equal(t, "hello-world", hook.Repo.Name) + assert.Equal(t, "http://forgejo.golang.org/gordon/hello-world", hook.Repo.HTMLURL) + assert.Equal(t, "gordon/hello-world", hook.Repo.FullName) + assert.Equal(t, "gordon@golang.org", hook.Repo.Owner.Email) + assert.Equal(t, "gordon", hook.Repo.Owner.UserName) + assert.True(t, hook.Repo.Private) + assert.Equal(t, "gordon", hook.Sender.UserName) + assert.Equal(t, "https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", hook.Sender.AvatarURL) + + assert.Equal(t, "Update the README with new information", hook.PullRequest.Title) + assert.Equal(t, "please merge", hook.PullRequest.Body) + assert.Equal(t, forgejo.StateOpen, hook.PullRequest.State) + assert.Equal(t, "gordon", hook.PullRequest.Poster.UserName) + assert.Equal(t, "main", hook.PullRequest.Base.Name) + assert.Equal(t, "main", hook.PullRequest.Base.Ref) + assert.Equal(t, "feature/changes", hook.PullRequest.Head.Name) + assert.Equal(t, "feature/changes", hook.PullRequest.Head.Ref) + }) + + t.Run("Should return a Pipeline struct from a pull_request hook", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookPullRequest) + hook, _ := parsePullRequest(buf) + pipeline := pipelineFromPullRequest(hook) + assert.Equal(t, model.EventPull, pipeline.Event) + assert.Equal(t, hook.PullRequest.Head.Sha, pipeline.Commit) + assert.Equal(t, "refs/pull/1/head", pipeline.Ref) + assert.Equal(t, "http://forgejo.golang.org/gordon/hello-world/pull/1", pipeline.ForgeURL) + assert.Equal(t, "main", pipeline.Branch) + assert.Equal(t, "feature/changes:main", pipeline.Refspec) + assert.Equal(t, hook.PullRequest.Title, pipeline.Message) + assert.Equal(t, "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", pipeline.Avatar) + assert.Equal(t, hook.PullRequest.Poster.UserName, pipeline.Author) + }) + + t.Run("Should return a Repo struct from a pull_request hook", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookPullRequest) + hook, _ := parsePullRequest(buf) + repo := toRepo(hook.Repo) + assert.Equal(t, hook.Repo.Name, repo.Name) + assert.Equal(t, hook.Repo.Owner.UserName, repo.Owner) + assert.Equal(t, "gordon/hello-world", repo.FullName) + assert.Equal(t, hook.Repo.HTMLURL, repo.ForgeURL) + }) +} + +func Test_toPerm(t *testing.T) { + perms := []forgejo.Permission{ + { + Admin: true, + Push: true, + Pull: true, + }, + { + Admin: true, + Push: true, + Pull: false, + }, + { + Admin: true, + Push: false, + Pull: false, + }, + } + for _, from := range perms { + perm := toPerm(&from) + assert.Equal(t, from.Pull, perm.Pull) + assert.Equal(t, from.Push, perm.Push) + assert.Equal(t, from.Admin, perm.Admin) + } +} + +func Test_toTeam(t *testing.T) { + from := &forgejo.Organization{ + UserName: "woodpecker", + AvatarURL: "/avatars/1", + } + + to := toTeam(from, "http://localhost:80") + assert.Equal(t, from.UserName, to.Login) + assert.Equal(t, "http://localhost:80/avatars/1", to.Avatar) +} + +func Test_toRepo(t *testing.T) { + from := forgejo.Repository{ + FullName: "gophers/hello-world", + Owner: &forgejo.User{ + UserName: "gordon", + AvatarURL: "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", + }, + CloneURL: "http://forgejo.golang.org/gophers/hello-world.git", + HTMLURL: "http://forgejo.golang.org/gophers/hello-world", + Private: true, + DefaultBranch: "main", + Permissions: &forgejo.Permission{Admin: true}, + } + repo := toRepo(&from) + assert.Equal(t, from.FullName, repo.FullName) + assert.Equal(t, from.Owner.UserName, repo.Owner) + assert.Equal(t, "hello-world", repo.Name) + assert.Equal(t, "main", repo.Branch) + assert.Equal(t, from.HTMLURL, repo.ForgeURL) + assert.Equal(t, from.CloneURL, repo.Clone) + assert.Equal(t, from.Owner.AvatarURL, repo.Avatar) + assert.Equal(t, from.Private, repo.IsSCMPrivate) + assert.True(t, repo.Perm.Admin) +} + +func Test_fixMalformedAvatar(t *testing.T) { + urls := []struct { + Before string + After string + }{ + { + "http://forgejo.golang.org///1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", + "//1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", + }, + { + "//1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", + "//1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", + }, + { + "http://forgejo.golang.org/avatars/1", + "http://forgejo.golang.org/avatars/1", + }, + { + "http://forgejo.golang.org//avatars/1", + "http://forgejo.golang.org/avatars/1", + }, + } + + for _, url := range urls { + got := fixMalformedAvatar(url.Before) + assert.Equal(t, url.After, got) + } +} + +func Test_expandAvatar(t *testing.T) { + urls := []struct { + Before string + After string + }{ + { + "/avatars/1", + "http://forgejo.io/avatars/1", + }, + { + "//1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", + "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", + }, + { + "/forgejo/avatars/2", + "http://forgejo.io/forgejo/avatars/2", + }, + } + + repo := "http://forgejo.io/foo/bar" + for _, url := range urls { + got := expandAvatar(repo, url.Before) + assert.Equal(t, url.After, got) + } +} diff --git a/server/forge/forgejo/parse.go b/server/forge/forgejo/parse.go index 88be0cf4b..609c73a40 100644 --- a/server/forge/forgejo/parse.go +++ b/server/forge/forgejo/parse.go @@ -21,8 +21,8 @@ import ( "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) const ( diff --git a/server/forge/forgejo/parse_test.go b/server/forge/forgejo/parse_test.go index 9415d9975..a40777521 100644 --- a/server/forge/forgejo/parse_test.go +++ b/server/forge/forgejo/parse_test.go @@ -21,9 +21,9 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/forgejo/fixtures" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/forgejo/fixtures" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func TestForgejoParser(t *testing.T) { @@ -55,7 +55,6 @@ func TestForgejoParser(t *testing.T) { Clone: "https://codeberg.org/meisam/woodpecktester.git", CloneSSH: "git@codeberg.org:meisam/woodpecktester.git", Branch: "main", - SCMKind: "git", PREnabled: true, Perm: &model.Perm{ Pull: true, @@ -90,7 +89,6 @@ func TestForgejoParser(t *testing.T) { ForgeURL: "http://forgejo.golang.org/gordon/hello-world", Clone: "http://forgejo.golang.org/gordon/hello-world.git", CloneSSH: "git@forgejo.golang.org:gordon/hello-world.git", - SCMKind: "git", IsSCMPrivate: true, Perm: &model.Perm{ Pull: true, @@ -126,7 +124,6 @@ func TestForgejoParser(t *testing.T) { Clone: "http://127.0.0.1:3000/Test-CI/multi-line-secrets.git", CloneSSH: "ssh://git@127.0.0.1:2200/Test-CI/multi-line-secrets.git", Branch: "main", - SCMKind: "git", Perm: &model.Perm{ Pull: true, Push: true, @@ -161,7 +158,6 @@ func TestForgejoParser(t *testing.T) { Clone: "http://forgejo.golang.org/gordon/hello-world.git", CloneSSH: "git@forgejo.golang.org:gordon/hello-world.git", Branch: "main", - SCMKind: "git", IsSCMPrivate: true, Perm: &model.Perm{ Pull: true, @@ -195,7 +191,6 @@ func TestForgejoParser(t *testing.T) { Clone: "https://forgejo.golang.org/gordon/hello-world.git", CloneSSH: "", Branch: "main", - SCMKind: "git", IsSCMPrivate: true, Perm: &model.Perm{ Pull: true, @@ -233,7 +228,6 @@ func TestForgejoParser(t *testing.T) { Clone: "http://127.0.0.1:3000/Test-CI/multi-line-secrets.git", CloneSSH: "ssh://git@127.0.0.1:2200/Test-CI/multi-line-secrets.git", Branch: "main", - SCMKind: "git", PREnabled: true, IsSCMPrivate: false, Perm: &model.Perm{ @@ -275,7 +269,6 @@ func TestForgejoParser(t *testing.T) { Clone: "https://forgejo.com/anbraten/test-repo.git", CloneSSH: "git@forgejo.com:anbraten/test-repo.git", Branch: "main", - SCMKind: "git", PREnabled: true, Perm: &model.Perm{ Pull: true, @@ -313,7 +306,6 @@ func TestForgejoParser(t *testing.T) { Clone: "https://forgejo.com/anbraten/test-repo.git", CloneSSH: "git@forgejo.com:anbraten/test-repo.git", Branch: "main", - SCMKind: "git", PREnabled: true, Perm: &model.Perm{ Pull: true, @@ -351,7 +343,6 @@ func TestForgejoParser(t *testing.T) { Clone: "https://git.xxx/anbraten/demo.git", CloneSSH: "ssh://git@git.xxx:22/anbraten/demo.git", Branch: "main", - SCMKind: "git", PREnabled: true, IsSCMPrivate: true, Perm: &model.Perm{ diff --git a/server/forge/gitea/gitea.go b/server/forge/gitea/gitea.go index a489e66a8..7b3788d06 100644 --- a/server/forge/gitea/gitea.go +++ b/server/forge/gitea/gitea.go @@ -32,13 +32,13 @@ import ( "github.com/rs/zerolog/log" "golang.org/x/oauth2" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/common" - forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" - shared_utils "go.woodpecker-ci.org/woodpecker/v2/shared/utils" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/common" + forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" + shared_utils "go.woodpecker-ci.org/woodpecker/v3/shared/utils" ) const ( @@ -135,8 +135,8 @@ func (c *Gitea) Login(ctx context.Context, req *forge_types.OAuthRequest) (*mode } return &model.User{ - Token: token.AccessToken, - Secret: token.RefreshToken, + AccessToken: token.AccessToken, + RefreshToken: token.RefreshToken, Expiry: token.Expiry.UTC().Unix(), Login: account.UserName, Email: account.Email, @@ -166,8 +166,8 @@ func (c *Gitea) Refresh(ctx context.Context, user *model.User) (bool, error) { config.RedirectURL = "" source := config.TokenSource(oauth2Ctx, &oauth2.Token{ - AccessToken: user.Token, - RefreshToken: user.Secret, + AccessToken: user.AccessToken, + RefreshToken: user.RefreshToken, Expiry: time.Unix(user.Expiry, 0), }) @@ -176,15 +176,15 @@ func (c *Gitea) Refresh(ctx context.Context, user *model.User) (bool, error) { return false, err } - user.Token = token.AccessToken - user.Secret = token.RefreshToken + user.AccessToken = token.AccessToken + user.RefreshToken = token.RefreshToken user.Expiry = token.Expiry.UTC().Unix() return true, nil } // Teams is supported by the Gitea driver. func (c *Gitea) Teams(ctx context.Context, u *model.User) ([]*model.Team, error) { - client, err := c.newClientToken(ctx, u.Token) + client, err := c.newClientToken(ctx, u.AccessToken) if err != nil { return nil, err } @@ -203,7 +203,7 @@ func (c *Gitea) Teams(ctx context.Context, u *model.User) ([]*model.Team, error) teams = append(teams, toTeam(org, c.url)) } return teams, err - }) + }, -1) } // TeamPerm is not supported by the Gitea driver. @@ -213,7 +213,7 @@ func (c *Gitea) TeamPerm(_ *model.User, _ string) (*model.Perm, error) { // Repo returns the Gitea repository. func (c *Gitea) Repo(ctx context.Context, u *model.User, remoteID model.ForgeRemoteID, owner, name string) (*model.Repo, error) { - client, err := c.newClientToken(ctx, u.Token) + client, err := c.newClientToken(ctx, u.AccessToken) if err != nil { return nil, err } @@ -240,7 +240,7 @@ func (c *Gitea) Repo(ctx context.Context, u *model.User, remoteID model.ForgeRem // Repos returns a list of all repositories for the Gitea account, including // organization repositories. func (c *Gitea) Repos(ctx context.Context, u *model.User) ([]*model.Repo, error) { - client, err := c.newClientToken(ctx, u.Token) + client, err := c.newClientToken(ctx, u.AccessToken) if err != nil { return nil, err } @@ -255,7 +255,7 @@ func (c *Gitea) Repos(ctx context.Context, u *model.User) ([]*model.Repo, error) }, ) return repos, err - }) + }, -1) result := make([]*model.Repo, 0, len(repos)) for _, repo := range repos { @@ -269,7 +269,7 @@ func (c *Gitea) Repos(ctx context.Context, u *model.User) ([]*model.Repo, error) // File fetches the file from the Gitea repository and returns its contents. func (c *Gitea) File(ctx context.Context, u *model.User, r *model.Repo, b *model.Pipeline, f string) ([]byte, error) { - client, err := c.newClientToken(ctx, u.Token) + client, err := c.newClientToken(ctx, u.AccessToken) if err != nil { return nil, err } @@ -284,7 +284,7 @@ func (c *Gitea) File(ctx context.Context, u *model.User, r *model.Repo, b *model func (c *Gitea) Dir(ctx context.Context, u *model.User, r *model.Repo, b *model.Pipeline, f string) ([]*forge_types.FileMeta, error) { var configs []*forge_types.FileMeta - client, err := c.newClientToken(ctx, u.Token) + client, err := c.newClientToken(ctx, u.AccessToken) if err != nil { return nil, err } @@ -320,7 +320,7 @@ func (c *Gitea) Dir(ctx context.Context, u *model.User, r *model.Repo, b *model. // Status is supported by the Gitea driver. func (c *Gitea) Status(ctx context.Context, user *model.User, repo *model.Repo, pipeline *model.Pipeline, workflow *model.Workflow) error { - client, err := c.newClientToken(ctx, user.Token) + client, err := c.newClientToken(ctx, user.AccessToken) if err != nil { return err } @@ -348,7 +348,7 @@ func (c *Gitea) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) { if u != nil { login = u.Login - token = u.Token + token = u.AccessToken } host, err := common.ExtractHostFromCloneURL(r.Clone) @@ -378,7 +378,7 @@ func (c *Gitea) Activate(ctx context.Context, u *model.User, r *model.Repo, link Active: true, } - client, err := c.newClientToken(ctx, u.Token) + client, err := c.newClientToken(ctx, u.AccessToken) if err != nil { return err } @@ -400,7 +400,7 @@ func (c *Gitea) Activate(ctx context.Context, u *model.User, r *model.Repo, link // Deactivate deactivates the repository be removing repository push hooks from // the Gitea repository. func (c *Gitea) Deactivate(ctx context.Context, u *model.User, r *model.Repo, link string) error { - client, err := c.newClientToken(ctx, u.Token) + client, err := c.newClientToken(ctx, u.AccessToken) if err != nil { return err } @@ -413,7 +413,7 @@ func (c *Gitea) Deactivate(ctx context.Context, u *model.User, r *model.Repo, li }, }) return hooks, err - }) + }, -1) if err != nil { return err } @@ -472,12 +472,17 @@ func (c *Gitea) PullRequests(ctx context.Context, u *model.User, r *model.Repo, return nil, err } - pullRequests, _, err := client.ListRepoPullRequests(r.Owner, r.Name, gitea.ListPullRequestsOptions{ + pullRequests, resp, err := client.ListRepoPullRequests(r.Owner, r.Name, gitea.ListPullRequestsOptions{ ListOptions: gitea.ListOptions{Page: p.Page, PageSize: p.PerPage}, State: gitea.StateOpen, }) if err != nil { - return nil, err + // Repositories without commits return empty list with status code 404 + if pullRequests != nil && resp != nil && resp.StatusCode == http.StatusNotFound { + err = nil + } else { + return nil, err + } } result := make([]*model.PullRequest, len(pullRequests)) @@ -524,7 +529,7 @@ func (c *Gitea) Hook(ctx context.Context, r *http.Request) (*model.Repo, *model. // OrgMembership returns if user is member of organization and if user // is admin/owner in this organization. func (c *Gitea) OrgMembership(ctx context.Context, u *model.User, owner string) (*model.OrgPerm, error) { - client, err := c.newClientToken(ctx, u.Token) + client, err := c.newClientToken(ctx, u.AccessToken) if err != nil { return nil, err } @@ -547,7 +552,7 @@ func (c *Gitea) OrgMembership(ctx context.Context, u *model.User, owner string) } func (c *Gitea) Org(ctx context.Context, u *model.User, owner string) (*model.Org, error) { - client, err := c.newClientToken(ctx, u.Token) + client, err := c.newClientToken(ctx, u.AccessToken) if err != nil { return nil, err } @@ -632,7 +637,7 @@ func (c *Gitea) getChangedFilesForPR(ctx context.Context, repo *model.Repo, inde return nil, err } - client, err := c.newClientToken(ctx, user.Token) + client, err := c.newClientToken(ctx, user.AccessToken) if err != nil { return nil, err } @@ -649,7 +654,7 @@ func (c *Gitea) getChangedFilesForPR(ctx context.Context, repo *model.Repo, inde files = append(files, file.Filename) } return files, nil - }) + }, -1) } func (c *Gitea) getTagCommitSHA(ctx context.Context, repo *model.Repo, tagName string) (string, error) { @@ -669,7 +674,7 @@ func (c *Gitea) getTagCommitSHA(ctx context.Context, repo *model.Repo, tagName s return "", err } - client, err := c.newClientToken(ctx, user.Token) + client, err := c.newClientToken(ctx, user.AccessToken) if err != nil { return "", err } diff --git a/server/forge/gitea/gitea_test.go b/server/forge/gitea/gitea_test.go index 17582af73..1892c5fcc 100644 --- a/server/forge/gitea/gitea_test.go +++ b/server/forge/gitea/gitea_test.go @@ -22,21 +22,32 @@ import ( "net/http/httptest" "testing" - "github.com/franela/goblin" "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/gitea/fixtures" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" - mocks_store "go.woodpecker-ci.org/woodpecker/v2/server/store/mocks" - "go.woodpecker-ci.org/woodpecker/v2/shared/utils" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/gitea/fixtures" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" + mocks_store "go.woodpecker-ci.org/woodpecker/v3/server/store/mocks" ) +func TestNew(t *testing.T) { + forge, _ := New(Opts{ + URL: "http://localhost:8080", + SkipVerify: true, + }) + + f, _ := forge.(*Gitea) + assert.Equal(t, "http://localhost:8080", f.url) + assert.True(t, f.SkipVerify) +} + func Test_gitea(t *testing.T) { gin.SetMode(gin.TestMode) s := httptest.NewServer(fixtures.Handler()) + defer s.Close() c, _ := New(Opts{ URL: s.URL, SkipVerify: true, @@ -45,134 +56,95 @@ func Test_gitea(t *testing.T) { mockStore := mocks_store.NewStore(t) ctx := store.InjectToContext(context.Background(), mockStore) - g := goblin.Goblin(t) - g.Describe("Gitea", func() { - g.After(func() { - s.Close() - }) + t.Run("netrc with user token", func(t *testing.T) { + forge, _ := New(Opts{}) + netrc, _ := forge.Netrc(fakeUser, fakeRepo) + assert.Equal(t, "gitea.com", netrc.Machine) + assert.Equal(t, fakeUser.Login, netrc.Login) + assert.Equal(t, fakeUser.AccessToken, netrc.Password) + }) + t.Run("netrc with machine account", func(t *testing.T) { + forge, _ := New(Opts{}) + netrc, _ := forge.Netrc(nil, fakeRepo) + assert.Equal(t, "gitea.com", netrc.Machine) + assert.Empty(t, netrc.Login) + assert.Empty(t, netrc.Password) + }) - g.Describe("Creating a forge", func() { - g.It("Should return client with specified options", func() { - forge, _ := New(Opts{ - URL: "http://localhost:8080", - SkipVerify: true, - }) + t.Run("repository details", func(t *testing.T) { + repo, err := c.Repo(ctx, fakeUser, fakeRepo.ForgeRemoteID, fakeRepo.Owner, fakeRepo.Name) + assert.NoError(t, err) + assert.Equal(t, fakeRepo.Owner, repo.Owner) + assert.Equal(t, fakeRepo.Name, repo.Name) + assert.Equal(t, fakeRepo.Owner+"/"+fakeRepo.Name, repo.FullName) + assert.True(t, repo.IsSCMPrivate) + assert.Equal(t, "http://localhost/test_name/repo_name.git", repo.Clone) + assert.Equal(t, "http://localhost/test_name/repo_name", repo.ForgeURL) + }) + t.Run("repo not found", func(t *testing.T) { + _, err := c.Repo(ctx, fakeUser, "0", fakeRepoNotFound.Owner, fakeRepoNotFound.Name) + assert.Error(t, err) + }) - f, _ := forge.(*Gitea) - g.Assert(f.url).Equal("http://localhost:8080") - g.Assert(f.SkipVerify).Equal(true) - }) - }) + t.Run("repository list", func(t *testing.T) { + repos, err := c.Repos(ctx, fakeUser) + assert.NoError(t, err) + assert.Equal(t, fakeRepo.ForgeRemoteID, repos[0].ForgeRemoteID) + assert.Equal(t, fakeRepo.Owner, repos[0].Owner) + assert.Equal(t, fakeRepo.Name, repos[0].Name) + assert.Equal(t, fakeRepo.Owner+"/"+fakeRepo.Name, repos[0].FullName) + }) + t.Run("not found error", func(t *testing.T) { + _, err := c.Repos(ctx, fakeUserNoRepos) + assert.Error(t, err) + }) - g.Describe("Generating a netrc file", func() { - g.It("Should return a netrc with the user token", func() { - forge, _ := New(Opts{}) - netrc, _ := forge.Netrc(fakeUser, fakeRepo) - g.Assert(netrc.Machine).Equal("gitea.com") - g.Assert(netrc.Login).Equal(fakeUser.Login) - g.Assert(netrc.Password).Equal(fakeUser.Token) - }) - g.It("Should return a netrc with the machine account", func() { - forge, _ := New(Opts{}) - netrc, _ := forge.Netrc(nil, fakeRepo) - g.Assert(netrc.Machine).Equal("gitea.com") - g.Assert(netrc.Login).Equal("") - g.Assert(netrc.Password).Equal("") - }) - }) + t.Run("register repository", func(t *testing.T) { + err := c.Activate(ctx, fakeUser, fakeRepo, "http://localhost") + assert.NoError(t, err) + }) - g.Describe("Requesting a repository", func() { - g.It("Should return the repository details", func() { - repo, err := c.Repo(ctx, fakeUser, fakeRepo.ForgeRemoteID, fakeRepo.Owner, fakeRepo.Name) - g.Assert(err).IsNil() - g.Assert(repo.Owner).Equal(fakeRepo.Owner) - g.Assert(repo.Name).Equal(fakeRepo.Name) - g.Assert(repo.FullName).Equal(fakeRepo.Owner + "/" + fakeRepo.Name) - g.Assert(repo.IsSCMPrivate).IsTrue() - g.Assert(repo.Clone).Equal("http://localhost/test_name/repo_name.git") - g.Assert(repo.ForgeURL).Equal("http://localhost/test_name/repo_name") - }) - g.It("Should handle a not found error", func() { - _, err := c.Repo(ctx, fakeUser, "0", fakeRepoNotFound.Owner, fakeRepoNotFound.Name) - g.Assert(err).IsNotNil() - }) - }) + t.Run("remove hooks", func(t *testing.T) { + err := c.Deactivate(ctx, fakeUser, fakeRepo, "http://localhost") + assert.NoError(t, err) + }) - g.Describe("Requesting a repository list", func() { - g.It("Should return the repository list", func() { - repos, err := c.Repos(ctx, fakeUser) - g.Assert(err).IsNil() - g.Assert(repos[0].ForgeRemoteID).Equal(fakeRepo.ForgeRemoteID) - g.Assert(repos[0].Owner).Equal(fakeRepo.Owner) - g.Assert(repos[0].Name).Equal(fakeRepo.Name) - g.Assert(repos[0].FullName).Equal(fakeRepo.Owner + "/" + fakeRepo.Name) - }) - g.It("Should handle a not found error", func() { - _, err := c.Repos(ctx, fakeUserNoRepos) - g.Assert(err).IsNotNil() - }) - }) + t.Run("repository file", func(t *testing.T) { + raw, err := c.File(ctx, fakeUser, fakeRepo, fakePipeline, ".woodpecker.yml") + assert.NoError(t, err) + assert.Equal(t, "{ platform: linux/amd64 }", string(raw)) + }) - g.It("Should register repository hooks", func() { - err := c.Activate(ctx, fakeUser, fakeRepo, "http://localhost") - g.Assert(err).IsNil() - }) + t.Run("pipeline status", func(t *testing.T) { + err := c.Status(ctx, fakeUser, fakeRepo, fakePipeline, fakeWorkflow) + assert.NoError(t, err) + }) - g.It("Should remove repository hooks", func() { - err := c.Deactivate(ctx, fakeUser, fakeRepo, "http://localhost") - g.Assert(err).IsNil() - }) - - g.It("Should return a repository file", func() { - raw, err := c.File(ctx, fakeUser, fakeRepo, fakePipeline, ".woodpecker.yml") - g.Assert(err).IsNil() - g.Assert(string(raw)).Equal("{ platform: linux/amd64 }") - }) - - g.It("Should return nil from send pipeline status", func() { - err := c.Status(ctx, fakeUser, fakeRepo, fakePipeline, fakeWorkflow) - g.Assert(err).IsNil() - }) - - g.Describe("Given an authentication request", func() { - g.It("Should redirect to login form") - g.It("Should create an access token") - g.It("Should handle an access token error") - g.It("Should return the authenticated user") - }) - - g.Describe("Given a repository hook", func() { - g.It("Should skip non-push events") - g.It("Should return push details") - g.It("Should handle a parsing error") - }) - - g.It("Given a PR hook", func() { - buf := bytes.NewBufferString(fixtures.HookPullRequest) - req, _ := http.NewRequest(http.MethodPost, "/hook", buf) - req.Header = http.Header{} - req.Header.Set(hookEvent, hookPullRequest) - mockStore.On("GetRepoNameFallback", mock.Anything, mock.Anything).Return(fakeRepo, nil) - mockStore.On("GetUser", mock.Anything).Return(fakeUser, nil) - r, b, err := c.Hook(ctx, req) - g.Assert(r).IsNotNil() - g.Assert(b).IsNotNil() - g.Assert(err).IsNil() - g.Assert(b.Event).Equal(model.EventPull) - g.Assert(utils.EqualSliceValues(b.ChangedFiles, []string{"README.md"})).IsTrue() - }) + t.Run("PR hook", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookPullRequest) + req, _ := http.NewRequest(http.MethodPost, "/hook", buf) + req.Header = http.Header{} + req.Header.Set(hookEvent, hookPullRequest) + mockStore.On("GetRepoNameFallback", mock.Anything, mock.Anything).Return(fakeRepo, nil) + mockStore.On("GetUser", mock.Anything).Return(fakeUser, nil) + r, b, err := c.Hook(ctx, req) + assert.NotNil(t, r) + assert.NotNil(t, b) + assert.NoError(t, err) + assert.Equal(t, model.EventPull, b.Event) + assert.Equal(t, []string{"README.md"}, b.ChangedFiles) }) } var ( fakeUser = &model.User{ - Login: "someuser", - Token: "cfcd2084", + Login: "someuser", + AccessToken: "cfcd2084", } fakeUserNoRepos = &model.User{ - Login: "someuser", - Token: "repos_not_found", + Login: "someuser", + AccessToken: "repos_not_found", } fakeRepo = &model.Repo{ diff --git a/server/forge/gitea/helper.go b/server/forge/gitea/helper.go index 69b2a885b..9420d5fff 100644 --- a/server/forge/gitea/helper.go +++ b/server/forge/gitea/helper.go @@ -25,8 +25,8 @@ import ( "code.gitea.io/sdk/gitea" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/shared/utils" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/shared/utils" ) // toRepo converts a Gitea repository to a Woodpecker repository. @@ -38,7 +38,6 @@ func toRepo(from *gitea.Repository) *model.Repo { ) return &model.Repo{ ForgeRemoteID: model.ForgeRemoteID(fmt.Sprint(from.ID)), - SCMKind: model.RepoGit, Name: name, Owner: from.Owner.UserName, FullName: from.FullName, @@ -172,6 +171,7 @@ func pipelineFromPullRequest(hook *pullRequestHook) *model.Pipeline { hook.PullRequest.Base.Ref, ), PullRequestLabels: convertLabels(hook.PullRequest.Labels), + FromFork: hook.PullRequest.Head.RepoID != hook.PullRequest.Base.RepoID, } return pipeline diff --git a/server/forge/gitea/helper_test.go b/server/forge/gitea/helper_test.go index f8d36f371..b728699ae 100644 --- a/server/forge/gitea/helper_test.go +++ b/server/forge/gitea/helper_test.go @@ -20,255 +20,252 @@ import ( "testing" "code.gitea.io/sdk/gitea" - "github.com/franela/goblin" + "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/gitea/fixtures" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/shared/utils" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/gitea/fixtures" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) -func Test_parse(t *testing.T) { - g := goblin.Goblin(t) - g.Describe("Gitea", func() { - g.It("Should parse push hook payload", func() { - buf := bytes.NewBufferString(fixtures.HookPush) - hook, err := parsePush(buf) - g.Assert(err).IsNil() - g.Assert(hook.Ref).Equal("refs/heads/main") - g.Assert(hook.After).Equal("ef98532add3b2feb7a137426bba1248724367df5") - g.Assert(hook.Before).Equal("4b2626259b5a97b6b4eab5e6cca66adb986b672b") - g.Assert(hook.Compare).Equal("http://gitea.golang.org/gordon/hello-world/compare/4b2626259b5a97b6b4eab5e6cca66adb986b672b...ef98532add3b2feb7a137426bba1248724367df5") - g.Assert(hook.Repo.Name).Equal("hello-world") - g.Assert(hook.Repo.HTMLURL).Equal("http://gitea.golang.org/gordon/hello-world") - g.Assert(hook.Repo.Owner.UserName).Equal("gordon") - g.Assert(hook.Repo.FullName).Equal("gordon/hello-world") - g.Assert(hook.Repo.Owner.Email).Equal("gordon@golang.org") - g.Assert(hook.Repo.Private).Equal(true) - g.Assert(hook.Pusher.Email).Equal("gordon@golang.org") - g.Assert(hook.Pusher.UserName).Equal("gordon") - g.Assert(hook.Sender.UserName).Equal("gordon") - g.Assert(hook.Sender.AvatarURL).Equal("http://gitea.golang.org///1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87") - }) +func Test_parsePush(t *testing.T) { + t.Run("Should parse push hook payload", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookPush) + hook, err := parsePush(buf) + assert.NoError(t, err) + assert.Equal(t, "refs/heads/main", hook.Ref) + assert.Equal(t, "ef98532add3b2feb7a137426bba1248724367df5", hook.After) + assert.Equal(t, "4b2626259b5a97b6b4eab5e6cca66adb986b672b", hook.Before) + assert.Equal(t, "http://gitea.golang.org/gordon/hello-world/compare/4b2626259b5a97b6b4eab5e6cca66adb986b672b...ef98532add3b2feb7a137426bba1248724367df5", hook.Compare) + assert.Equal(t, "hello-world", hook.Repo.Name) + assert.Equal(t, "http://gitea.golang.org/gordon/hello-world", hook.Repo.HTMLURL) + assert.Equal(t, "gordon", hook.Repo.Owner.UserName) + assert.Equal(t, "gordon/hello-world", hook.Repo.FullName) + assert.Equal(t, "gordon@golang.org", hook.Repo.Owner.Email) + assert.True(t, hook.Repo.Private) + assert.Equal(t, "gordon@golang.org", hook.Pusher.Email) + assert.Equal(t, "gordon", hook.Pusher.UserName) + assert.Equal(t, "gordon", hook.Sender.UserName) + assert.Equal(t, "http://gitea.golang.org///1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", hook.Sender.AvatarURL) + }) + t.Run("Should parse tag hook payload", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookTag) + hook, err := parsePush(buf) + assert.NoError(t, err) + assert.Equal(t, "v1.0.0", hook.Ref) + assert.Equal(t, "ef98532add3b2feb7a137426bba1248724367df5", hook.Sha) + assert.Equal(t, "hello-world", hook.Repo.Name) + assert.Equal(t, "http://gitea.golang.org/gordon/hello-world", hook.Repo.HTMLURL) + assert.Equal(t, "gordon/hello-world", hook.Repo.FullName) + assert.Equal(t, "gordon@golang.org", hook.Repo.Owner.Email) + assert.Equal(t, "gordon", hook.Repo.Owner.UserName) + assert.True(t, hook.Repo.Private) + assert.Equal(t, "gordon", hook.Sender.UserName) + assert.Equal(t, "https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", hook.Sender.AvatarURL) + }) - g.It("Should parse tag hook payload", func() { - buf := bytes.NewBufferString(fixtures.HookTag) - hook, err := parsePush(buf) - g.Assert(err).IsNil() - g.Assert(hook.Ref).Equal("v1.0.0") - g.Assert(hook.Sha).Equal("ef98532add3b2feb7a137426bba1248724367df5") - g.Assert(hook.Repo.Name).Equal("hello-world") - g.Assert(hook.Repo.HTMLURL).Equal("http://gitea.golang.org/gordon/hello-world") - g.Assert(hook.Repo.FullName).Equal("gordon/hello-world") - g.Assert(hook.Repo.Owner.Email).Equal("gordon@golang.org") - g.Assert(hook.Repo.Owner.UserName).Equal("gordon") - g.Assert(hook.Repo.Private).Equal(true) - g.Assert(hook.Sender.UserName).Equal("gordon") - g.Assert(hook.Sender.AvatarURL).Equal("https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87") - }) + t.Run("Should return a Pipeline struct from a push hook", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookPush) + hook, _ := parsePush(buf) + pipeline := pipelineFromPush(hook) + assert.Equal(t, model.EventPush, pipeline.Event) + assert.Equal(t, hook.After, pipeline.Commit) + assert.Equal(t, hook.Ref, pipeline.Ref) + assert.Equal(t, hook.Commits[0].URL, pipeline.ForgeURL) + assert.Equal(t, "main", pipeline.Branch) + assert.Equal(t, hook.Commits[0].Message, pipeline.Message) + assert.Equal(t, "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", pipeline.Avatar) + assert.Equal(t, hook.Sender.UserName, pipeline.Author) + assert.Equal(t, []string{"CHANGELOG.md", "app/controller/application.rb"}, pipeline.ChangedFiles) + }) - g.It("Should parse pull_request hook payload", func() { - buf := bytes.NewBufferString(fixtures.HookPullRequest) - hook, err := parsePullRequest(buf) - g.Assert(err).IsNil() - g.Assert(hook.Action).Equal("opened") - g.Assert(hook.Number).Equal(int64(1)) + t.Run("Should return a Repo struct from a push hook", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookPush) + hook, _ := parsePush(buf) + repo := toRepo(hook.Repo) + assert.Equal(t, hook.Repo.Name, repo.Name) + assert.Equal(t, hook.Repo.Owner.UserName, repo.Owner) + assert.Equal(t, "gordon/hello-world", repo.FullName) + assert.Equal(t, hook.Repo.HTMLURL, repo.ForgeURL) + }) - g.Assert(hook.Repo.Name).Equal("hello-world") - g.Assert(hook.Repo.HTMLURL).Equal("http://gitea.golang.org/gordon/hello-world") - g.Assert(hook.Repo.FullName).Equal("gordon/hello-world") - g.Assert(hook.Repo.Owner.Email).Equal("gordon@golang.org") - g.Assert(hook.Repo.Owner.UserName).Equal("gordon") - g.Assert(hook.Repo.Private).Equal(true) - g.Assert(hook.Sender.UserName).Equal("gordon") - g.Assert(hook.Sender.AvatarURL).Equal("https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87") - - g.Assert(hook.PullRequest.Title).Equal("Update the README with new information") - g.Assert(hook.PullRequest.Body).Equal("please merge") - g.Assert(hook.PullRequest.State).Equal(gitea.StateOpen) - g.Assert(hook.PullRequest.Poster.UserName).Equal("gordon") - g.Assert(hook.PullRequest.Base.Name).Equal("main") - g.Assert(hook.PullRequest.Base.Ref).Equal("main") - g.Assert(hook.PullRequest.Head.Name).Equal("feature/changes") - g.Assert(hook.PullRequest.Head.Ref).Equal("feature/changes") - }) - - g.It("Should return a Pipeline struct from a push hook", func() { - buf := bytes.NewBufferString(fixtures.HookPush) - hook, _ := parsePush(buf) - pipeline := pipelineFromPush(hook) - g.Assert(pipeline.Event).Equal(model.EventPush) - g.Assert(pipeline.Commit).Equal(hook.After) - g.Assert(pipeline.Ref).Equal(hook.Ref) - g.Assert(pipeline.ForgeURL).Equal(hook.Commits[0].URL) - g.Assert(pipeline.Branch).Equal("main") - g.Assert(pipeline.Message).Equal(hook.Commits[0].Message) - g.Assert(pipeline.Avatar).Equal("http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87") - g.Assert(pipeline.Author).Equal(hook.Sender.UserName) - g.Assert(utils.EqualSliceValues(pipeline.ChangedFiles, []string{"CHANGELOG.md", "app/controller/application.rb"})).IsTrue() - }) - - g.It("Should return a Repo struct from a push hook", func() { - buf := bytes.NewBufferString(fixtures.HookPush) - hook, _ := parsePush(buf) - repo := toRepo(hook.Repo) - g.Assert(repo.Name).Equal(hook.Repo.Name) - g.Assert(repo.Owner).Equal(hook.Repo.Owner.UserName) - g.Assert(repo.FullName).Equal("gordon/hello-world") - g.Assert(repo.ForgeURL).Equal(hook.Repo.HTMLURL) - }) - - g.It("Should return a Pipeline struct from a tag hook", func() { - buf := bytes.NewBufferString(fixtures.HookTag) - hook, _ := parsePush(buf) - pipeline := pipelineFromTag(hook) - g.Assert(pipeline.Event).Equal(model.EventTag) - g.Assert(pipeline.Commit).Equal(hook.Sha) - g.Assert(pipeline.Ref).Equal("refs/tags/v1.0.0") - g.Assert(pipeline.Branch).Equal("") - g.Assert(pipeline.ForgeURL).Equal("http://gitea.golang.org/gordon/hello-world/src/tag/v1.0.0") - g.Assert(pipeline.Message).Equal("created tag v1.0.0") - }) - - g.It("Should return a Pipeline struct from a pull_request hook", func() { - buf := bytes.NewBufferString(fixtures.HookPullRequest) - hook, _ := parsePullRequest(buf) - pipeline := pipelineFromPullRequest(hook) - g.Assert(pipeline.Event).Equal(model.EventPull) - g.Assert(pipeline.Commit).Equal(hook.PullRequest.Head.Sha) - g.Assert(pipeline.Ref).Equal("refs/pull/1/head") - g.Assert(pipeline.ForgeURL).Equal("http://gitea.golang.org/gordon/hello-world/pull/1") - g.Assert(pipeline.Branch).Equal("main") - g.Assert(pipeline.Refspec).Equal("feature/changes:main") - g.Assert(pipeline.Message).Equal(hook.PullRequest.Title) - g.Assert(pipeline.Avatar).Equal("http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87") - g.Assert(pipeline.Author).Equal(hook.PullRequest.Poster.UserName) - }) - - g.It("Should return a Repo struct from a pull_request hook", func() { - buf := bytes.NewBufferString(fixtures.HookPullRequest) - hook, _ := parsePullRequest(buf) - repo := toRepo(hook.Repo) - g.Assert(repo.Name).Equal(hook.Repo.Name) - g.Assert(repo.Owner).Equal(hook.Repo.Owner.UserName) - g.Assert(repo.FullName).Equal("gordon/hello-world") - g.Assert(repo.ForgeURL).Equal(hook.Repo.HTMLURL) - }) - - g.It("Should return a Perm struct from a Gitea Perm", func() { - perms := []gitea.Permission{ - { - Admin: true, - Push: true, - Pull: true, - }, - { - Admin: true, - Push: true, - Pull: false, - }, - { - Admin: true, - Push: false, - Pull: false, - }, - } - for _, from := range perms { - perm := toPerm(&from) - g.Assert(perm.Pull).Equal(from.Pull) - g.Assert(perm.Push).Equal(from.Push) - g.Assert(perm.Admin).Equal(from.Admin) - } - }) - - g.It("Should return a Team struct from a Gitea Org", func() { - from := &gitea.Organization{ - UserName: "woodpecker", - AvatarURL: "/avatars/1", - } - - to := toTeam(from, "http://localhost:80") - g.Assert(to.Login).Equal(from.UserName) - g.Assert(to.Avatar).Equal("http://localhost:80/avatars/1") - }) - - g.It("Should return a Repo struct from a Gitea Repo", func() { - from := gitea.Repository{ - FullName: "gophers/hello-world", - Owner: &gitea.User{ - UserName: "gordon", - AvatarURL: "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", - }, - CloneURL: "http://gitea.golang.org/gophers/hello-world.git", - HTMLURL: "http://gitea.golang.org/gophers/hello-world", - Private: true, - DefaultBranch: "main", - Permissions: &gitea.Permission{Admin: true}, - } - repo := toRepo(&from) - g.Assert(repo.FullName).Equal(from.FullName) - g.Assert(repo.Owner).Equal(from.Owner.UserName) - g.Assert(repo.Name).Equal("hello-world") - g.Assert(repo.Branch).Equal("main") - g.Assert(repo.ForgeURL).Equal(from.HTMLURL) - g.Assert(repo.Clone).Equal(from.CloneURL) - g.Assert(repo.Avatar).Equal(from.Owner.AvatarURL) - g.Assert(repo.IsSCMPrivate).Equal(from.Private) - g.Assert(repo.Perm.Admin).IsTrue() - }) - - g.It("Should correct a malformed avatar url", func() { - urls := []struct { - Before string - After string - }{ - { - "http://gitea.golang.org///1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", - "//1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", - }, - { - "//1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", - "//1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", - }, - { - "http://gitea.golang.org/avatars/1", - "http://gitea.golang.org/avatars/1", - }, - { - "http://gitea.golang.org//avatars/1", - "http://gitea.golang.org/avatars/1", - }, - } - - for _, url := range urls { - got := fixMalformedAvatar(url.Before) - g.Assert(got).Equal(url.After) - } - }) - - g.It("Should expand the avatar url", func() { - urls := []struct { - Before string - After string - }{ - { - "/avatars/1", - "http://gitea.io/avatars/1", - }, - { - "//1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", - "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", - }, - { - "/gitea/avatars/2", - "http://gitea.io/gitea/avatars/2", - }, - } - - repo := "http://gitea.io/foo/bar" - for _, url := range urls { - got := expandAvatar(repo, url.Before) - g.Assert(got).Equal(url.After) - } - }) + t.Run("Should return a Pipeline struct from a tag hook", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookTag) + hook, _ := parsePush(buf) + pipeline := pipelineFromTag(hook) + assert.Equal(t, model.EventTag, pipeline.Event) + assert.Equal(t, hook.Sha, pipeline.Commit) + assert.Equal(t, "refs/tags/v1.0.0", pipeline.Ref) + assert.Empty(t, pipeline.Branch) + assert.Equal(t, "http://gitea.golang.org/gordon/hello-world/src/tag/v1.0.0", pipeline.ForgeURL) + assert.Equal(t, "created tag v1.0.0", pipeline.Message) }) } + +func Test_parsePullRequest(t *testing.T) { + t.Run("Should parse pull_request hook payload", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookPullRequest) + hook, err := parsePullRequest(buf) + assert.NoError(t, err) + assert.Equal(t, "opened", hook.Action) + assert.Equal(t, int64(1), hook.Number) + + assert.Equal(t, "hello-world", hook.Repo.Name) + assert.Equal(t, "http://gitea.golang.org/gordon/hello-world", hook.Repo.HTMLURL) + assert.Equal(t, "gordon/hello-world", hook.Repo.FullName) + assert.Equal(t, "gordon@golang.org", hook.Repo.Owner.Email) + assert.Equal(t, "gordon", hook.Repo.Owner.UserName) + assert.True(t, hook.Repo.Private) + assert.Equal(t, "gordon", hook.Sender.UserName) + assert.Equal(t, "https://secure.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", hook.Sender.AvatarURL) + + assert.Equal(t, "Update the README with new information", hook.PullRequest.Title) + assert.Equal(t, "please merge", hook.PullRequest.Body) + assert.Equal(t, gitea.StateOpen, hook.PullRequest.State) + assert.Equal(t, "gordon", hook.PullRequest.Poster.UserName) + assert.Equal(t, "main", hook.PullRequest.Base.Name) + assert.Equal(t, "main", hook.PullRequest.Base.Ref) + assert.Equal(t, "feature/changes", hook.PullRequest.Head.Name) + assert.Equal(t, "feature/changes", hook.PullRequest.Head.Ref) + }) + + t.Run("Should return a Pipeline struct from a pull_request hook", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookPullRequest) + hook, _ := parsePullRequest(buf) + pipeline := pipelineFromPullRequest(hook) + assert.Equal(t, model.EventPull, pipeline.Event) + assert.Equal(t, hook.PullRequest.Head.Sha, pipeline.Commit) + assert.Equal(t, "refs/pull/1/head", pipeline.Ref) + assert.Equal(t, "http://gitea.golang.org/gordon/hello-world/pull/1", pipeline.ForgeURL) + assert.Equal(t, "main", pipeline.Branch) + assert.Equal(t, "feature/changes:main", pipeline.Refspec) + assert.Equal(t, hook.PullRequest.Title, pipeline.Message) + assert.Equal(t, "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", pipeline.Avatar) + assert.Equal(t, hook.PullRequest.Poster.UserName, pipeline.Author) + }) + + t.Run("Should return a Repo struct from a pull_request hook", func(t *testing.T) { + buf := bytes.NewBufferString(fixtures.HookPullRequest) + hook, _ := parsePullRequest(buf) + repo := toRepo(hook.Repo) + assert.Equal(t, hook.Repo.Name, repo.Name) + assert.Equal(t, hook.Repo.Owner.UserName, repo.Owner) + assert.Equal(t, "gordon/hello-world", repo.FullName) + assert.Equal(t, hook.Repo.HTMLURL, repo.ForgeURL) + }) +} + +func Test_toPerm(t *testing.T) { + perms := []gitea.Permission{ + { + Admin: true, + Push: true, + Pull: true, + }, + { + Admin: true, + Push: true, + Pull: false, + }, + { + Admin: true, + Push: false, + Pull: false, + }, + } + for _, from := range perms { + perm := toPerm(&from) + assert.Equal(t, from.Pull, perm.Pull) + assert.Equal(t, from.Push, perm.Push) + assert.Equal(t, from.Admin, perm.Admin) + } +} + +func Test_toTeam(t *testing.T) { + from := &gitea.Organization{ + UserName: "woodpecker", + AvatarURL: "/avatars/1", + } + + to := toTeam(from, "http://localhost:80") + assert.Equal(t, from.UserName, to.Login) + assert.Equal(t, "http://localhost:80/avatars/1", to.Avatar) +} + +func Test_toRepo(t *testing.T) { + from := gitea.Repository{ + FullName: "gophers/hello-world", + Owner: &gitea.User{ + UserName: "gordon", + AvatarURL: "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", + }, + CloneURL: "http://gitea.golang.org/gophers/hello-world.git", + HTMLURL: "http://gitea.golang.org/gophers/hello-world", + Private: true, + DefaultBranch: "main", + Permissions: &gitea.Permission{Admin: true}, + } + repo := toRepo(&from) + assert.Equal(t, from.FullName, repo.FullName) + assert.Equal(t, from.Owner.UserName, repo.Owner) + assert.Equal(t, "hello-world", repo.Name) + assert.Equal(t, "main", repo.Branch) + assert.Equal(t, from.HTMLURL, repo.ForgeURL) + assert.Equal(t, from.CloneURL, repo.Clone) + assert.Equal(t, from.Owner.AvatarURL, repo.Avatar) + assert.Equal(t, from.Private, repo.IsSCMPrivate) + assert.True(t, repo.Perm.Admin) +} + +func Test_fixMalformedAvatar(t *testing.T) { + urls := []struct { + Before string + After string + }{ + { + "http://gitea.golang.org///1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", + "//1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", + }, + { + "//1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", + "//1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", + }, + { + "http://gitea.golang.org/avatars/1", + "http://gitea.golang.org/avatars/1", + }, + { + "http://gitea.golang.org//avatars/1", + "http://gitea.golang.org/avatars/1", + }, + } + + for _, url := range urls { + got := fixMalformedAvatar(url.Before) + assert.Equal(t, url.After, got) + } +} + +func Test_expandAvatar(t *testing.T) { + urls := []struct { + Before string + After string + }{ + { + "/avatars/1", + "http://gitea.io/avatars/1", + }, + { + "//1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", + "http://1.gravatar.com/avatar/8c58a0be77ee441bb8f8595b7f1b4e87", + }, + { + "/gitea/avatars/2", + "http://gitea.io/gitea/avatars/2", + }, + } + + repo := "http://gitea.io/foo/bar" + for _, url := range urls { + got := expandAvatar(repo, url.Before) + assert.Equal(t, url.After, got) + } +} diff --git a/server/forge/gitea/parse.go b/server/forge/gitea/parse.go index a8856a611..ae873d64d 100644 --- a/server/forge/gitea/parse.go +++ b/server/forge/gitea/parse.go @@ -16,14 +16,15 @@ package gitea import ( + "fmt" "io" "net/http" "strings" "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) const ( @@ -111,6 +112,11 @@ func parsePullRequestHook(payload io.Reader) (*model.Repo, *model.Pipeline, erro return nil, nil, err } + if pr.PullRequest == nil { + // this should never have happened but it did - so we check + return nil, nil, fmt.Errorf("parsed pull_request webhook does not contain pull_request info") + } + // Don't trigger pipelines for non-code changes ... if pr.Action != actionOpen && pr.Action != actionSync && pr.Action != actionClose { log.Debug().Msgf("pull_request action is '%s' and no open or sync", pr.Action) diff --git a/server/forge/gitea/parse_test.go b/server/forge/gitea/parse_test.go index a37384464..437376611 100644 --- a/server/forge/gitea/parse_test.go +++ b/server/forge/gitea/parse_test.go @@ -22,9 +22,9 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/gitea/fixtures" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/gitea/fixtures" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func TestGiteaParser(t *testing.T) { @@ -56,7 +56,6 @@ func TestGiteaParser(t *testing.T) { Clone: "https://codeberg.org/meisam/woodpecktester.git", CloneSSH: "git@codeberg.org:meisam/woodpecktester.git", Branch: "main", - SCMKind: "git", PREnabled: true, Perm: &model.Perm{ Pull: true, @@ -91,7 +90,6 @@ func TestGiteaParser(t *testing.T) { ForgeURL: "http://gitea.golang.org/gordon/hello-world", Clone: "http://gitea.golang.org/gordon/hello-world.git", CloneSSH: "git@gitea.golang.org:gordon/hello-world.git", - SCMKind: "git", IsSCMPrivate: true, Perm: &model.Perm{ Pull: true, @@ -127,7 +125,6 @@ func TestGiteaParser(t *testing.T) { Clone: "http://127.0.0.1:3000/Test-CI/multi-line-secrets.git", CloneSSH: "ssh://git@127.0.0.1:2200/Test-CI/multi-line-secrets.git", Branch: "main", - SCMKind: "git", Perm: &model.Perm{ Pull: true, Push: true, @@ -162,7 +159,6 @@ func TestGiteaParser(t *testing.T) { Clone: "http://gitea.golang.org/gordon/hello-world.git", CloneSSH: "git@gitea.golang.org:gordon/hello-world.git", Branch: "main", - SCMKind: "git", IsSCMPrivate: true, Perm: &model.Perm{ Pull: true, @@ -196,7 +192,6 @@ func TestGiteaParser(t *testing.T) { Clone: "https://gitea.golang.org/gordon/hello-world.git", CloneSSH: "", Branch: "main", - SCMKind: "git", IsSCMPrivate: true, Perm: &model.Perm{ Pull: true, @@ -234,7 +229,6 @@ func TestGiteaParser(t *testing.T) { Clone: "http://127.0.0.1:3000/Test-CI/multi-line-secrets.git", CloneSSH: "ssh://git@127.0.0.1:2200/Test-CI/multi-line-secrets.git", Branch: "main", - SCMKind: "git", PREnabled: true, IsSCMPrivate: false, Perm: &model.Perm{ @@ -276,7 +270,6 @@ func TestGiteaParser(t *testing.T) { Clone: "https://gitea.com/anbraten/test-repo.git", CloneSSH: "git@gitea.com:anbraten/test-repo.git", Branch: "main", - SCMKind: "git", PREnabled: true, Perm: &model.Perm{ Pull: true, @@ -314,7 +307,6 @@ func TestGiteaParser(t *testing.T) { Clone: "https://gitea.com/anbraten/test-repo.git", CloneSSH: "git@gitea.com:anbraten/test-repo.git", Branch: "main", - SCMKind: "git", PREnabled: true, Perm: &model.Perm{ Pull: true, @@ -352,7 +344,6 @@ func TestGiteaParser(t *testing.T) { Clone: "https://git.xxx/anbraten/demo.git", CloneSSH: "ssh://git@git.xxx:22/anbraten/demo.git", Branch: "main", - SCMKind: "git", PREnabled: true, IsSCMPrivate: true, Perm: &model.Perm{ diff --git a/server/forge/github/convert.go b/server/forge/github/convert.go index fbeaf7f3f..d76002ab3 100644 --- a/server/forge/github/convert.go +++ b/server/forge/github/convert.go @@ -18,9 +18,9 @@ package github import ( "fmt" - "github.com/google/go-github/v62/github" + "github.com/google/go-github/v68/github" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) const ( @@ -94,7 +94,6 @@ func convertRepo(from *github.Repository) *model.Repo { Owner: from.GetOwner().GetLogin(), Avatar: from.GetOwner().GetAvatarURL(), Perm: convertPerm(from.GetPermissions()), - SCMKind: model.RepoGit, PREnabled: true, } return repo @@ -152,7 +151,6 @@ func convertRepoHook(eventRepo *github.PushEventRepository) *model.Repo { Clone: eventRepo.GetCloneURL(), CloneSSH: eventRepo.GetSSHURL(), Branch: eventRepo.GetDefaultBranch(), - SCMKind: model.RepoGit, PREnabled: true, } if repo.FullName == "" { diff --git a/server/forge/github/convert_test.go b/server/forge/github/convert_test.go index 33abce887..816cec9d0 100644 --- a/server/forge/github/convert_test.go +++ b/server/forge/github/convert_test.go @@ -18,281 +18,261 @@ package github import ( "testing" - "github.com/franela/goblin" - "github.com/google/go-github/v62/github" + "github.com/google/go-github/v68/github" + "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) -func Test_helper(t *testing.T) { - g := goblin.Goblin(t) - g.Describe("GitHub converter", func() { - g.It("should convert passing status", func() { - g.Assert(convertStatus(model.StatusSuccess)).Equal(statusSuccess) - }) +func Test_convertStatus(t *testing.T) { + assert.Equal(t, statusSuccess, convertStatus(model.StatusSuccess)) + assert.Equal(t, statusPending, convertStatus(model.StatusPending)) + assert.Equal(t, statusPending, convertStatus(model.StatusRunning)) + assert.Equal(t, statusFailure, convertStatus(model.StatusFailure)) + assert.Equal(t, statusError, convertStatus(model.StatusKilled)) + assert.Equal(t, statusError, convertStatus(model.StatusError)) +} - g.It("should convert pending status", func() { - g.Assert(convertStatus(model.StatusPending)).Equal(statusPending) - g.Assert(convertStatus(model.StatusRunning)).Equal(statusPending) - }) +func Test_convertDesc(t *testing.T) { + assert.Equal(t, descSuccess, convertDesc(model.StatusSuccess)) + assert.Equal(t, descPending, convertDesc(model.StatusPending)) + assert.Equal(t, descPending, convertDesc(model.StatusRunning)) + assert.Equal(t, descFailure, convertDesc(model.StatusFailure)) + assert.Equal(t, descError, convertDesc(model.StatusKilled)) + assert.Equal(t, descError, convertDesc(model.StatusError)) +} - g.It("should convert failing status", func() { - g.Assert(convertStatus(model.StatusFailure)).Equal(statusFailure) - }) +func Test_convertRepoList(t *testing.T) { + from := []*github.Repository{ + { + Private: github.Ptr(false), + FullName: github.Ptr("octocat/hello-world"), + Name: github.Ptr("hello-world"), + Owner: &github.User{ + AvatarURL: github.Ptr("http://..."), + Login: github.Ptr("octocat"), + }, + HTMLURL: github.Ptr("https://github.com/octocat/hello-world"), + CloneURL: github.Ptr("https://github.com/octocat/hello-world.git"), + Permissions: map[string]bool{ + "push": true, + "pull": true, + "admin": true, + }, + }, + } - g.It("should convert error status", func() { - g.Assert(convertStatus(model.StatusKilled)).Equal(statusError) - g.Assert(convertStatus(model.StatusError)).Equal(statusError) - }) + to := convertRepoList(from) + assert.Equal(t, "http://...", to[0].Avatar) + assert.Equal(t, "octocat/hello-world", to[0].FullName) + assert.Equal(t, "octocat", to[0].Owner) + assert.Equal(t, "hello-world", to[0].Name) +} - g.It("should convert passing desc", func() { - g.Assert(convertDesc(model.StatusSuccess)).Equal(descSuccess) - }) +func Test_convertRepo(t *testing.T) { + from := github.Repository{ + FullName: github.Ptr("octocat/hello-world"), + Name: github.Ptr("hello-world"), + HTMLURL: github.Ptr("https://github.com/octocat/hello-world"), + CloneURL: github.Ptr("https://github.com/octocat/hello-world.git"), + DefaultBranch: github.Ptr("develop"), + Private: github.Ptr(true), + Owner: &github.User{ + AvatarURL: github.Ptr("http://..."), + Login: github.Ptr("octocat"), + }, + Permissions: map[string]bool{ + "push": true, + "pull": true, + "admin": true, + }, + } - g.It("should convert pending desc", func() { - g.Assert(convertDesc(model.StatusPending)).Equal(descPending) - g.Assert(convertDesc(model.StatusRunning)).Equal(descPending) - }) + to := convertRepo(&from) + assert.Equal(t, "http://...", to.Avatar) + assert.Equal(t, "octocat/hello-world", to.FullName) + assert.Equal(t, "octocat", to.Owner) + assert.Equal(t, "hello-world", to.Name) + assert.Equal(t, "develop", to.Branch) + assert.True(t, to.IsSCMPrivate) + assert.Equal(t, "https://github.com/octocat/hello-world.git", to.Clone) + assert.Equal(t, "https://github.com/octocat/hello-world", to.ForgeURL) +} - g.It("should convert failing desc", func() { - g.Assert(convertDesc(model.StatusFailure)).Equal(descFailure) - }) +func Test_convertPerm(t *testing.T) { + from := &github.Repository{ + Permissions: map[string]bool{ + "admin": true, + "push": true, + "pull": true, + }, + } - g.It("should convert error desc", func() { - g.Assert(convertDesc(model.StatusKilled)).Equal(descError) - g.Assert(convertDesc(model.StatusError)).Equal(descError) - }) + to := convertPerm(from.GetPermissions()) + assert.True(t, to.Push) + assert.True(t, to.Pull) + assert.True(t, to.Admin) +} - g.It("should convert repository list", func() { - from := []*github.Repository{ - { - Private: github.Bool(false), - FullName: github.String("octocat/hello-world"), - Name: github.String("hello-world"), - Owner: &github.User{ - AvatarURL: github.String("http://..."), - Login: github.String("octocat"), - }, - HTMLURL: github.String("https://github.com/octocat/hello-world"), - CloneURL: github.String("https://github.com/octocat/hello-world.git"), - Permissions: map[string]bool{ - "push": true, - "pull": true, - "admin": true, - }, - }, - } +func Test_convertTeam(t *testing.T) { + from := &github.Organization{ + Login: github.Ptr("octocat"), + AvatarURL: github.Ptr("http://..."), + } + to := convertTeam(from) + assert.Equal(t, "octocat", to.Login) + assert.Equal(t, "http://...", to.Avatar) +} - to := convertRepoList(from) - g.Assert(to[0].Avatar).Equal("http://...") - g.Assert(to[0].FullName).Equal("octocat/hello-world") - g.Assert(to[0].Owner).Equal("octocat") - g.Assert(to[0].Name).Equal("hello-world") - }) +func Test_convertTeamList(t *testing.T) { + from := []*github.Organization{ + { + Login: github.Ptr("octocat"), + AvatarURL: github.Ptr("http://..."), + }, + } + to := convertTeamList(from) + assert.Equal(t, "octocat", to[0].Login) + assert.Equal(t, "http://...", to[0].Avatar) +} - g.It("should convert repository", func() { - from := github.Repository{ - FullName: github.String("octocat/hello-world"), - Name: github.String("hello-world"), - HTMLURL: github.String("https://github.com/octocat/hello-world"), - CloneURL: github.String("https://github.com/octocat/hello-world.git"), - DefaultBranch: github.String("develop"), - Private: github.Bool(true), - Owner: &github.User{ - AvatarURL: github.String("http://..."), - Login: github.String("octocat"), - }, - Permissions: map[string]bool{ - "push": true, - "pull": true, - "admin": true, - }, - } +func Test_convertRepoHook(t *testing.T) { + t.Run("should convert a repository from webhook", func(t *testing.T) { + from := &github.PushEventRepository{Owner: &github.User{}} + from.Owner.Login = github.Ptr("octocat") + from.Owner.Name = github.Ptr("octocat") + from.Name = github.Ptr("hello-world") + from.FullName = github.Ptr("octocat/hello-world") + from.Private = github.Ptr(true) + from.HTMLURL = github.Ptr("https://github.com/octocat/hello-world") + from.CloneURL = github.Ptr("https://github.com/octocat/hello-world.git") + from.DefaultBranch = github.Ptr("develop") - to := convertRepo(&from) - g.Assert(to.Avatar).Equal("http://...") - g.Assert(to.FullName).Equal("octocat/hello-world") - g.Assert(to.Owner).Equal("octocat") - g.Assert(to.Name).Equal("hello-world") - g.Assert(to.Branch).Equal("develop") - g.Assert(string(to.SCMKind)).Equal("git") - g.Assert(to.IsSCMPrivate).IsTrue() - g.Assert(to.Clone).Equal("https://github.com/octocat/hello-world.git") - g.Assert(to.ForgeURL).Equal("https://github.com/octocat/hello-world") - }) - - g.It("should convert repository permissions", func() { - from := &github.Repository{ - Permissions: map[string]bool{ - "admin": true, - "push": true, - "pull": true, - }, - } - - to := convertPerm(from.GetPermissions()) - g.Assert(to.Push).IsTrue() - g.Assert(to.Pull).IsTrue() - g.Assert(to.Admin).IsTrue() - }) - - g.It("should convert team", func() { - from := &github.Organization{ - Login: github.String("octocat"), - AvatarURL: github.String("http://..."), - } - to := convertTeam(from) - g.Assert(to.Login).Equal("octocat") - g.Assert(to.Avatar).Equal("http://...") - }) - - g.It("should convert team list", func() { - from := []*github.Organization{ - { - Login: github.String("octocat"), - AvatarURL: github.String("http://..."), - }, - } - to := convertTeamList(from) - g.Assert(to[0].Login).Equal("octocat") - g.Assert(to[0].Avatar).Equal("http://...") - }) - - g.It("should convert a repository from webhook", func() { - from := &github.PushEventRepository{Owner: &github.User{}} - from.Owner.Login = github.String("octocat") - from.Owner.Name = github.String("octocat") - from.Name = github.String("hello-world") - from.FullName = github.String("octocat/hello-world") - from.Private = github.Bool(true) - from.HTMLURL = github.String("https://github.com/octocat/hello-world") - from.CloneURL = github.String("https://github.com/octocat/hello-world.git") - from.DefaultBranch = github.String("develop") - - repo := convertRepoHook(from) - g.Assert(repo.Owner).Equal(*from.Owner.Login) - g.Assert(repo.Name).Equal(*from.Name) - g.Assert(repo.FullName).Equal(*from.FullName) - g.Assert(repo.IsSCMPrivate).Equal(*from.Private) - g.Assert(repo.ForgeURL).Equal(*from.HTMLURL) - g.Assert(repo.Clone).Equal(*from.CloneURL) - g.Assert(repo.Branch).Equal(*from.DefaultBranch) - }) - - g.It("should convert a pull request from webhook", func() { - from := &github.PullRequestEvent{ - Action: github.String(actionOpen), - PullRequest: &github.PullRequest{ - State: github.String(stateOpen), - HTMLURL: github.String("https://github.com/octocat/hello-world/pulls/42"), - Number: github.Int(42), - Title: github.String("Updated README.md"), - Base: &github.PullRequestBranch{ - Ref: github.String("main"), - }, - Head: &github.PullRequestBranch{ - Ref: github.String("changes"), - SHA: github.String("f72fc19"), - Repo: &github.Repository{ - CloneURL: github.String("https://github.com/octocat/hello-world-fork"), - }, - }, - User: &github.User{ - Login: github.String("octocat"), - AvatarURL: github.String("https://avatars1.githubusercontent.com/u/583231"), - }, - }, Sender: &github.User{ - Login: github.String("octocat"), - }, - } - pull, _, pipeline, err := parsePullHook(from, true) - g.Assert(err).IsNil() - g.Assert(pull).IsNotNil() - g.Assert(pipeline.Event).Equal(model.EventPull) - g.Assert(pipeline.Branch).Equal(*from.PullRequest.Base.Ref) - g.Assert(pipeline.Ref).Equal("refs/pull/42/merge") - g.Assert(pipeline.Refspec).Equal("changes:main") - g.Assert(pipeline.Commit).Equal(*from.PullRequest.Head.SHA) - g.Assert(pipeline.Message).Equal(*from.PullRequest.Title) - g.Assert(pipeline.Title).Equal(*from.PullRequest.Title) - g.Assert(pipeline.Author).Equal(*from.PullRequest.User.Login) - g.Assert(pipeline.Avatar).Equal(*from.PullRequest.User.AvatarURL) - g.Assert(pipeline.Sender).Equal(*from.Sender.Login) - }) - - g.It("should convert a deployment from webhook", func() { - from := &github.DeploymentEvent{Deployment: &github.Deployment{}, Sender: &github.User{}} - from.Deployment.Description = github.String(":shipit:") - from.Deployment.Environment = github.String("production") - from.Deployment.Task = github.String("deploy") - from.Deployment.ID = github.Int64(42) - from.Deployment.Ref = github.String("main") - from.Deployment.SHA = github.String("f72fc19") - from.Deployment.URL = github.String("https://github.com/octocat/hello-world") - from.Sender.Login = github.String("octocat") - from.Sender.AvatarURL = github.String("https://avatars1.githubusercontent.com/u/583231") - - _, pipeline := parseDeployHook(from) - g.Assert(pipeline.Event).Equal(model.EventDeploy) - g.Assert(pipeline.Branch).Equal("main") - g.Assert(pipeline.Ref).Equal("refs/heads/main") - g.Assert(pipeline.Commit).Equal(*from.Deployment.SHA) - g.Assert(pipeline.Message).Equal(*from.Deployment.Description) - g.Assert(pipeline.ForgeURL).Equal(*from.Deployment.URL) - g.Assert(pipeline.Author).Equal(*from.Sender.Login) - g.Assert(pipeline.Avatar).Equal(*from.Sender.AvatarURL) - }) - - g.It("should convert a push from webhook", func() { - from := &github.PushEvent{Sender: &github.User{}, Repo: &github.PushEventRepository{}, HeadCommit: &github.HeadCommit{Author: &github.CommitAuthor{}}} - from.Sender.Login = github.String("octocat") - from.Sender.AvatarURL = github.String("https://avatars1.githubusercontent.com/u/583231") - from.Repo.CloneURL = github.String("https://github.com/octocat/hello-world.git") - from.HeadCommit.Author.Email = github.String("github.String(octocat@github.com") - from.HeadCommit.Message = github.String("updated README.md") - from.HeadCommit.URL = github.String("https://github.com/octocat/hello-world") - from.HeadCommit.ID = github.String("f72fc19") - from.Ref = github.String("refs/heads/main") - - _, pipeline := parsePushHook(from) - g.Assert(pipeline.Event).Equal(model.EventPush) - g.Assert(pipeline.Branch).Equal("main") - g.Assert(pipeline.Ref).Equal("refs/heads/main") - g.Assert(pipeline.Commit).Equal(*from.HeadCommit.ID) - g.Assert(pipeline.Message).Equal(*from.HeadCommit.Message) - g.Assert(pipeline.ForgeURL).Equal(*from.HeadCommit.URL) - g.Assert(pipeline.Author).Equal(*from.Sender.Login) - g.Assert(pipeline.Avatar).Equal(*from.Sender.AvatarURL) - g.Assert(pipeline.Email).Equal(*from.HeadCommit.Author.Email) - }) - - g.It("should convert a tag from webhook", func() { - from := &github.PushEvent{} - from.Ref = github.String("refs/tags/v1.0.0") - - _, pipeline := parsePushHook(from) - g.Assert(pipeline.Event).Equal(model.EventTag) - g.Assert(pipeline.Ref).Equal("refs/tags/v1.0.0") - }) - - g.It("should convert tag's base branch from webhook to pipeline's branch ", func() { - from := &github.PushEvent{} - from.Ref = github.String("refs/tags/v1.0.0") - from.BaseRef = github.String("refs/heads/main") - - _, pipeline := parsePushHook(from) - g.Assert(pipeline.Event).Equal(model.EventTag) - g.Assert(pipeline.Branch).Equal("main") - }) - - g.It("should not convert tag's base_ref from webhook if not prefixed with 'ref/heads/'", func() { - from := &github.PushEvent{} - from.Ref = github.String("refs/tags/v1.0.0") - from.BaseRef = github.String("refs/refs/main") - - _, pipeline := parsePushHook(from) - g.Assert(pipeline.Event).Equal(model.EventTag) - g.Assert(pipeline.Branch).Equal("refs/tags/v1.0.0") - }) + repo := convertRepoHook(from) + assert.Equal(t, *from.Owner.Login, repo.Owner) + assert.Equal(t, *from.Name, repo.Name) + assert.Equal(t, *from.FullName, repo.FullName) + assert.Equal(t, *from.Private, repo.IsSCMPrivate) + assert.Equal(t, *from.HTMLURL, repo.ForgeURL) + assert.Equal(t, *from.CloneURL, repo.Clone) + assert.Equal(t, *from.DefaultBranch, repo.Branch) + }) +} + +func Test_parsePullHook(t *testing.T) { + from := &github.PullRequestEvent{ + Action: github.Ptr(actionOpen), + PullRequest: &github.PullRequest{ + State: github.Ptr(stateOpen), + HTMLURL: github.Ptr("https://github.com/octocat/hello-world/pulls/42"), + Number: github.Ptr(42), + Title: github.Ptr("Updated README.md"), + Base: &github.PullRequestBranch{ + Ref: github.Ptr("main"), + }, + Head: &github.PullRequestBranch{ + Ref: github.Ptr("changes"), + SHA: github.Ptr("f72fc19"), + Repo: &github.Repository{ + CloneURL: github.Ptr("https://github.com/octocat/hello-world-fork"), + }, + }, + User: &github.User{ + Login: github.Ptr("octocat"), + AvatarURL: github.Ptr("https://avatars1.githubusercontent.com/u/583231"), + }, + }, Sender: &github.User{ + Login: github.Ptr("octocat"), + }, + } + pull, _, pipeline, err := parsePullHook(from, true) + assert.NoError(t, err) + assert.NotNil(t, pull) + assert.Equal(t, model.EventPull, pipeline.Event) + assert.Equal(t, *from.PullRequest.Base.Ref, pipeline.Branch) + assert.Equal(t, "refs/pull/42/merge", pipeline.Ref) + assert.Equal(t, "changes:main", pipeline.Refspec) + assert.Equal(t, *from.PullRequest.Head.SHA, pipeline.Commit) + assert.Equal(t, *from.PullRequest.Title, pipeline.Message) + assert.Equal(t, *from.PullRequest.Title, pipeline.Title) + assert.Equal(t, *from.PullRequest.User.Login, pipeline.Author) + assert.Equal(t, *from.PullRequest.User.AvatarURL, pipeline.Avatar) + assert.Equal(t, *from.Sender.Login, pipeline.Sender) +} + +func Test_parseDeployHook(t *testing.T) { + from := &github.DeploymentEvent{Deployment: &github.Deployment{}, Sender: &github.User{}} + from.Deployment.Description = github.Ptr(":shipit:") + from.Deployment.Environment = github.Ptr("production") + from.Deployment.Task = github.Ptr("deploy") + from.Deployment.ID = github.Ptr(int64(42)) + from.Deployment.Ref = github.Ptr("main") + from.Deployment.SHA = github.Ptr("f72fc19") + from.Deployment.URL = github.Ptr("https://github.com/octocat/hello-world") + from.Sender.Login = github.Ptr("octocat") + from.Sender.AvatarURL = github.Ptr("https://avatars1.githubusercontent.com/u/583231") + + _, pipeline := parseDeployHook(from) + assert.Equal(t, model.EventDeploy, pipeline.Event) + assert.Equal(t, "main", pipeline.Branch) + assert.Equal(t, "refs/heads/main", pipeline.Ref) + assert.Equal(t, *from.Deployment.SHA, pipeline.Commit) + assert.Equal(t, *from.Deployment.Description, pipeline.Message) + assert.Equal(t, *from.Deployment.URL, pipeline.ForgeURL) + assert.Equal(t, *from.Sender.Login, pipeline.Author) + assert.Equal(t, *from.Sender.AvatarURL, pipeline.Avatar) +} + +func Test_parsePushHook(t *testing.T) { + t.Run("convert push from webhook", func(t *testing.T) { + from := &github.PushEvent{Sender: &github.User{}, Repo: &github.PushEventRepository{}, HeadCommit: &github.HeadCommit{Author: &github.CommitAuthor{}}} + from.Sender.Login = github.Ptr("octocat") + from.Sender.AvatarURL = github.Ptr("https://avatars1.githubusercontent.com/u/583231") + from.Repo.CloneURL = github.Ptr("https://github.com/octocat/hello-world.git") + from.HeadCommit.Author.Email = github.Ptr("github.Ptr(octocat@github.com") + from.HeadCommit.Message = github.Ptr("updated README.md") + from.HeadCommit.URL = github.Ptr("https://github.com/octocat/hello-world") + from.HeadCommit.ID = github.Ptr("f72fc19") + from.Ref = github.Ptr("refs/heads/main") + + _, pipeline := parsePushHook(from) + assert.Equal(t, model.EventPush, pipeline.Event) + assert.Equal(t, "main", pipeline.Branch) + assert.Equal(t, "refs/heads/main", pipeline.Ref) + assert.Equal(t, *from.HeadCommit.ID, pipeline.Commit) + assert.Equal(t, *from.HeadCommit.Message, pipeline.Message) + assert.Equal(t, *from.HeadCommit.URL, pipeline.ForgeURL) + assert.Equal(t, *from.Sender.Login, pipeline.Author) + assert.Equal(t, *from.Sender.AvatarURL, pipeline.Avatar) + assert.Equal(t, *from.HeadCommit.Author.Email, pipeline.Email) + }) + + t.Run("convert tag from webhook", func(t *testing.T) { + from := &github.PushEvent{} + from.Ref = github.Ptr("refs/tags/v1.0.0") + + _, pipeline := parsePushHook(from) + assert.Equal(t, model.EventTag, pipeline.Event) + assert.Equal(t, "refs/tags/v1.0.0", pipeline.Ref) + }) + + t.Run("convert tag's base branch to pipeline's branch ", func(t *testing.T) { + from := &github.PushEvent{} + from.Ref = github.Ptr("refs/tags/v1.0.0") + from.BaseRef = github.Ptr("refs/heads/main") + + _, pipeline := parsePushHook(from) + assert.Equal(t, model.EventTag, pipeline.Event) + assert.Equal(t, "main", pipeline.Branch) + }) + + t.Run("not convert tag's base_ref from webhook if not prefixed with 'ref/heads/'", func(t *testing.T) { + from := &github.PushEvent{} + from.Ref = github.Ptr("refs/tags/v1.0.0") + from.BaseRef = github.Ptr("refs/refs/main") + + _, pipeline := parsePushHook(from) + assert.Equal(t, model.EventTag, pipeline.Event) + assert.Equal(t, "refs/tags/v1.0.0", pipeline.Branch) }) } diff --git a/server/forge/github/fixtures/hooks.go b/server/forge/github/fixtures/hooks.go index bae47d9e1..895c537ba 100644 --- a/server/forge/github/fixtures/hooks.go +++ b/server/forge/github/fixtures/hooks.go @@ -49,7 +49,7 @@ const HookPush = `{ "site_admin": false }, "html_url": "https://github.com/woodpecker-ci/woodpecker", - "description": "Woodpecker is a simple yet powerful CI/CD engine with great extensibility.", + "description": "Woodpecker is a simple, yet powerful CI/CD engine with great extensibility.", "fork": false, "url": "https://github.com/woodpecker-ci/woodpecker", "forks_url": "https://api.github.com/repos/woodpecker-ci/woodpecker/forks", @@ -152,7 +152,7 @@ const HookPush = `{ "members_url": "https://api.github.com/orgs/woodpecker-ci/members{/member}", "public_members_url": "https://api.github.com/orgs/woodpecker-ci/public_members{/member}", "avatar_url": "https://avatars.githubusercontent.com/u/84780935?v=4", - "description": "Woodpecker is a simple yet powerful CI/CD engine with great extensibility." + "description": "Woodpecker is a simple, yet powerful CI/CD engine with great extensibility." }, "sender": { "login": "6543", diff --git a/server/forge/github/github.go b/server/forge/github/github.go index 93ba8cbed..31ad44c3a 100644 --- a/server/forge/github/github.go +++ b/server/forge/github/github.go @@ -25,18 +25,19 @@ import ( "regexp" "strconv" "strings" + "time" - "github.com/google/go-github/v62/github" + "github.com/google/go-github/v68/github" "github.com/rs/zerolog/log" "golang.org/x/oauth2" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/common" - forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" - "go.woodpecker-ci.org/woodpecker/v2/shared/utils" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/common" + forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" + "go.woodpecker-ci.org/woodpecker/v3/shared/utils" ) const ( @@ -133,7 +134,9 @@ func (c *client) Login(ctx context.Context, req *forge_types.OAuthRequest) (*mod return &model.User{ Login: user.GetLogin(), Email: email.GetEmail(), - Token: token.AccessToken, + AccessToken: token.AccessToken, + RefreshToken: token.RefreshToken, + Expiry: token.Expiry.UTC().Unix(), Avatar: user.GetAvatarURL(), ForgeRemoteID: model.ForgeRemoteID(fmt.Sprint(user.GetID())), }, redirectURL, nil @@ -149,9 +152,36 @@ func (c *client) Auth(ctx context.Context, token, _ string) (string, error) { return *user.Login, nil } +// Refresh refreshes the Gitlab oauth2 access token. If the token is +// refreshed the user is updated and a true value is returned. +func (c *client) Refresh(ctx context.Context, user *model.User) (bool, error) { + // when using Github oAuth app no refresh token is provided + if user.RefreshToken == "" { + return false, nil + } + + config := c.newConfig() + + source := config.TokenSource(ctx, &oauth2.Token{ + AccessToken: user.AccessToken, + RefreshToken: user.RefreshToken, + Expiry: time.Unix(user.Expiry, 0), + }) + + token, err := source.Token() + if err != nil || len(token.AccessToken) == 0 { + return false, err + } + + user.AccessToken = token.AccessToken + user.RefreshToken = token.RefreshToken + user.Expiry = token.Expiry.UTC().Unix() + return true, nil +} + // Teams returns a list of all team membership for the GitHub account. func (c *client) Teams(ctx context.Context, u *model.User) ([]*model.Team, error) { - client := c.newClientToken(ctx, u.Token) + client := c.newClientToken(ctx, u.AccessToken) opts := new(github.ListOptions) opts.Page = 1 @@ -170,7 +200,7 @@ func (c *client) Teams(ctx context.Context, u *model.User) ([]*model.Team, error // Repo returns the GitHub repository. func (c *client) Repo(ctx context.Context, u *model.User, id model.ForgeRemoteID, owner, name string) (*model.Repo, error) { - client := c.newClientToken(ctx, u.Token) + client := c.newClientToken(ctx, u.AccessToken) if id.IsValid() { intID, err := strconv.ParseInt(string(id), 10, 64) @@ -194,7 +224,7 @@ func (c *client) Repo(ctx context.Context, u *model.User, id model.ForgeRemoteID // Repos returns a list of all repositories for GitHub account, including // organization repositories. func (c *client) Repos(ctx context.Context, u *model.User) ([]*model.Repo, error) { - client := c.newClientToken(ctx, u.Token) + client := c.newClientToken(ctx, u.AccessToken) opts := new(github.RepositoryListByAuthenticatedUserOptions) opts.PerPage = 100 @@ -219,7 +249,7 @@ func (c *client) Repos(ctx context.Context, u *model.User) ([]*model.Repo, error // File fetches the file from the GitHub repository and returns its contents. func (c *client) File(ctx context.Context, u *model.User, r *model.Repo, b *model.Pipeline, f string) ([]byte, error) { - client := c.newClientToken(ctx, u.Token) + client := c.newClientToken(ctx, u.AccessToken) opts := new(github.RepositoryContentGetOptions) opts.Ref = b.Commit @@ -238,7 +268,7 @@ func (c *client) File(ctx context.Context, u *model.User, r *model.Repo, b *mode } func (c *client) Dir(ctx context.Context, u *model.User, r *model.Repo, b *model.Pipeline, f string) ([]*forge_types.FileMeta, error) { - client := c.newClientToken(ctx, u.Token) + client := c.newClientToken(ctx, u.AccessToken) opts := new(github.RepositoryContentGetOptions) opts.Ref = b.Commit @@ -317,7 +347,7 @@ func (c *client) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) { token := "" if u != nil { - login = u.Token + login = u.AccessToken token = "x-oauth-basic" } @@ -336,7 +366,7 @@ func (c *client) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) { // Deactivate deactivates the repository be removing registered push hooks from // the GitHub repository. func (c *client) Deactivate(ctx context.Context, u *model.User, r *model.Repo, link string) error { - client := c.newClientToken(ctx, u.Token) + client := c.newClientToken(ctx, u.AccessToken) hooks, _, err := client.Repositories.ListHooks(ctx, r.Owner, r.Name, nil) if err != nil { return err @@ -352,7 +382,7 @@ func (c *client) Deactivate(ctx context.Context, u *model.User, r *model.Repo, l // OrgMembership returns if user is member of organization and if user // is admin/owner in this organization. func (c *client) OrgMembership(ctx context.Context, u *model.User, owner string) (*model.OrgPerm, error) { - client := c.newClientToken(ctx, u.Token) + client := c.newClientToken(ctx, u.AccessToken) org, _, err := client.Organizations.GetOrgMembership(ctx, u.Login, owner) if err != nil { return nil, err @@ -362,7 +392,7 @@ func (c *client) OrgMembership(ctx context.Context, u *model.User, owner string) } func (c *client) Org(ctx context.Context, u *model.User, owner string) (*model.Org, error) { - client := c.newClientToken(ctx, u.Token) + client := c.newClientToken(ctx, u.AccessToken) user, _, err := client.Users.Get(ctx, owner) log.Trace().Msgf("GitHub user for owner %s = %v", owner, user) @@ -487,7 +517,7 @@ var reDeploy = regexp.MustCompile(`.+/deployments/(\d+)`) // Status sends the commit status to the forge. // An example would be the GitHub pull request status. func (c *client) Status(ctx context.Context, user *model.User, repo *model.Repo, pipeline *model.Pipeline, workflow *model.Workflow) error { - client := c.newClientToken(ctx, user.Token) + client := c.newClientToken(ctx, user.AccessToken) if pipeline.Event == model.EventDeploy { // Get id from url. If not found, skip. @@ -499,18 +529,18 @@ func (c *client) Status(ctx context.Context, user *model.User, repo *model.Repo, id, _ := strconv.Atoi(matches[1]) _, _, err := client.Repositories.CreateDeploymentStatus(ctx, repo.Owner, repo.Name, int64(id), &github.DeploymentStatusRequest{ - State: github.String(convertStatus(pipeline.Status)), - Description: github.String(common.GetPipelineStatusDescription(pipeline.Status)), - LogURL: github.String(common.GetPipelineStatusURL(repo, pipeline, nil)), + State: github.Ptr(convertStatus(pipeline.Status)), + Description: github.Ptr(common.GetPipelineStatusDescription(pipeline.Status)), + LogURL: github.Ptr(common.GetPipelineStatusURL(repo, pipeline, nil)), }) return err } _, _, err := client.Repositories.CreateStatus(ctx, repo.Owner, repo.Name, pipeline.Commit, &github.RepoStatus{ - Context: github.String(common.GetPipelineStatusContext(repo, pipeline, workflow)), - State: github.String(convertStatus(workflow.State)), - Description: github.String(common.GetPipelineStatusDescription(workflow.State)), - TargetURL: github.String(common.GetPipelineStatusURL(repo, pipeline, workflow)), + Context: github.Ptr(common.GetPipelineStatusContext(repo, pipeline, workflow)), + State: github.Ptr(convertStatus(workflow.State)), + Description: github.Ptr(common.GetPipelineStatusDescription(workflow.State)), + TargetURL: github.Ptr(common.GetPipelineStatusURL(repo, pipeline, workflow)), }) return err } @@ -521,9 +551,9 @@ func (c *client) Activate(ctx context.Context, u *model.User, r *model.Repo, lin if err := c.Deactivate(ctx, u, r, link); err != nil { return err } - client := c.newClientToken(ctx, u.Token) + client := c.newClientToken(ctx, u.AccessToken) hook := &github.Hook{ - Name: github.String("web"), + Name: github.Ptr("web"), Events: []string{ "push", "pull_request", @@ -531,7 +561,7 @@ func (c *client) Activate(ctx context.Context, u *model.User, r *model.Repo, lin }, Config: &github.HookConfig{ URL: &link, - ContentType: github.String("form"), + ContentType: github.Ptr("form"), }, } _, _, err := client.Repositories.CreateHook(ctx, r.Owner, r.Name, hook) @@ -618,7 +648,7 @@ func (c *client) loadChangedFilesFromPullRequest(ctx context.Context, pull *gith opts := &github.ListOptions{Page: page} fileList := make([]string, 0, 16) for opts.Page > 0 { - files, resp, err := c.newClientToken(ctx, user.Token).PullRequests.ListFiles(ctx, repo.Owner, repo.Name, pull.GetNumber(), opts) + files, resp, err := c.newClientToken(ctx, user.AccessToken).PullRequests.ListFiles(ctx, repo.Owner, repo.Name, pull.GetNumber(), opts) if err != nil { return nil, err } @@ -630,7 +660,7 @@ func (c *client) loadChangedFilesFromPullRequest(ctx context.Context, pull *gith opts.Page = resp.NextPage } return utils.DeduplicateStrings(fileList), nil - }) + }, -1) return pipeline, err } @@ -652,7 +682,7 @@ func (c *client) getTagCommitSHA(ctx context.Context, repo *model.Repo, tagName return "", err } - gh := c.newClientToken(ctx, user.Token) + gh := c.newClientToken(ctx, user.AccessToken) page := 1 var tag *github.RepositoryTag diff --git a/server/forge/github/github_test.go b/server/forge/github/github_test.go index 74f338dd8..59751978b 100644 --- a/server/forge/github/github_test.go +++ b/server/forge/github/github_test.go @@ -20,13 +20,28 @@ import ( "net/http/httptest" "testing" - "github.com/franela/goblin" "github.com/gin-gonic/gin" + "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/github/fixtures" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/github/fixtures" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) +func TestNew(t *testing.T) { + forge, _ := New(Opts{ + URL: "http://localhost:8080/", + Client: "0ZXh0IjoiI", + Secret: "I1NiIsInR5", + SkipVerify: true, + }) + f, _ := forge.(*client) + assert.Equal(t, "http://localhost:8080", f.url) + assert.Equal(t, "http://localhost:8080/api/v3/", f.API) + assert.Equal(t, "0ZXh0IjoiI", f.Client) + assert.Equal(t, "I1NiIsInR5", f.Secret) + assert.True(t, f.SkipVerify) +} + func Test_github(t *testing.T) { gin.SetMode(gin.TestMode) @@ -36,87 +51,46 @@ func Test_github(t *testing.T) { SkipVerify: true, }) + defer s.Close() + ctx := context.Background() - g := goblin.Goblin(t) - g.Describe("GitHub", func() { - g.After(func() { - s.Close() - }) - g.Describe("Creating a forge", func() { - g.It("Should return client with specified options", func() { - forge, _ := New(Opts{ - URL: "http://localhost:8080/", - Client: "0ZXh0IjoiI", - Secret: "I1NiIsInR5", - SkipVerify: true, - }) - f, _ := forge.(*client) - g.Assert(f.url).Equal("http://localhost:8080") - g.Assert(f.API).Equal("http://localhost:8080/api/v3/") - g.Assert(f.Client).Equal("0ZXh0IjoiI") - g.Assert(f.Secret).Equal("I1NiIsInR5") - g.Assert(f.SkipVerify).Equal(true) - }) - }) + t.Run("netrc with user token", func(t *testing.T) { + forge, _ := New(Opts{}) + netrc, _ := forge.Netrc(fakeUser, fakeRepo) + assert.Equal(t, "github.com", netrc.Machine) + assert.Equal(t, fakeUser.AccessToken, netrc.Login) + assert.Equal(t, "x-oauth-basic", netrc.Password) + }) + t.Run("netrc with machine account", func(t *testing.T) { + forge, _ := New(Opts{}) + netrc, _ := forge.Netrc(nil, fakeRepo) + assert.Equal(t, "github.com", netrc.Machine) + assert.Empty(t, netrc.Login) + assert.Empty(t, netrc.Password) + }) - g.Describe("Generating a netrc file", func() { - g.It("Should return a netrc with the user token", func() { - forge, _ := New(Opts{}) - netrc, _ := forge.Netrc(fakeUser, fakeRepo) - g.Assert(netrc.Machine).Equal("github.com") - g.Assert(netrc.Login).Equal(fakeUser.Token) - g.Assert(netrc.Password).Equal("x-oauth-basic") - }) - g.It("Should return a netrc with the machine account", func() { - forge, _ := New(Opts{}) - netrc, _ := forge.Netrc(nil, fakeRepo) - g.Assert(netrc.Machine).Equal("github.com") - g.Assert(netrc.Login).Equal("") - g.Assert(netrc.Password).Equal("") - }) - }) - - g.Describe("Requesting a repository", func() { - g.It("Should return the repository details", func() { - repo, err := c.Repo(ctx, fakeUser, fakeRepo.ForgeRemoteID, fakeRepo.Owner, fakeRepo.Name) - g.Assert(err).IsNil() - g.Assert(repo.ForgeRemoteID).Equal(fakeRepo.ForgeRemoteID) - g.Assert(repo.Owner).Equal(fakeRepo.Owner) - g.Assert(repo.Name).Equal(fakeRepo.Name) - g.Assert(repo.FullName).Equal(fakeRepo.FullName) - g.Assert(repo.IsSCMPrivate).IsTrue() - g.Assert(repo.Clone).Equal(fakeRepo.Clone) - g.Assert(repo.ForgeURL).Equal(fakeRepo.ForgeURL) - }) - g.It("Should handle a not found error", func() { - _, err := c.Repo(ctx, fakeUser, "0", fakeRepoNotFound.Owner, fakeRepoNotFound.Name) - g.Assert(err).IsNotNil() - }) - }) - - g.It("Should return a user repository list") - - g.It("Should return a user team list") - - g.It("Should register repository hooks") - - g.It("Should return a repository file") - - g.Describe("Given an authentication request", func() { - g.It("Should redirect to the GitHub login page") - g.It("Should create an access token") - g.It("Should handle an access token error") - g.It("Should return the authenticated user") - g.It("Should handle authentication errors") - }) + t.Run("Should return the repository details", func(t *testing.T) { + repo, err := c.Repo(ctx, fakeUser, fakeRepo.ForgeRemoteID, fakeRepo.Owner, fakeRepo.Name) + assert.NoError(t, err) + assert.Equal(t, fakeRepo.ForgeRemoteID, repo.ForgeRemoteID) + assert.Equal(t, fakeRepo.Owner, repo.Owner) + assert.Equal(t, fakeRepo.Name, repo.Name) + assert.Equal(t, fakeRepo.FullName, repo.FullName) + assert.True(t, repo.IsSCMPrivate) + assert.Equal(t, fakeRepo.Clone, repo.Clone) + assert.Equal(t, fakeRepo.ForgeURL, repo.ForgeURL) + }) + t.Run("repo not found error", func(t *testing.T) { + _, err := c.Repo(ctx, fakeUser, "0", fakeRepoNotFound.Owner, fakeRepoNotFound.Name) + assert.Error(t, err) }) } var ( fakeUser = &model.User{ - Login: "octocat", - Token: "cfcd2084", + Login: "octocat", + AccessToken: "cfcd2084", } fakeRepo = &model.Repo{ diff --git a/server/forge/github/parse.go b/server/forge/github/parse.go index c8d33bf8f..7df728984 100644 --- a/server/forge/github/parse.go +++ b/server/forge/github/parse.go @@ -22,11 +22,11 @@ import ( "net/http" "strings" - "github.com/google/go-github/v62/github" + "github.com/google/go-github/v68/github" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/shared/utils" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/shared/utils" ) const ( @@ -157,6 +157,8 @@ func parsePullHook(hook *github.PullRequestEvent, merge bool) (*github.PullReque event = model.EventPullClosed } + fromFork := hook.GetPullRequest().GetHead().GetRepo().GetID() != hook.GetPullRequest().GetBase().GetRepo().GetID() + pipeline := &model.Pipeline{ Event: event, Commit: hook.GetPullRequest().GetHead().GetSHA(), @@ -173,6 +175,7 @@ func parsePullHook(hook *github.PullRequestEvent, merge bool) (*github.PullReque hook.GetPullRequest().GetBase().GetRef(), ), PullRequestLabels: convertLabels(hook.GetPullRequest().Labels), + FromFork: fromFork, } if merge { pipeline.Ref = fmt.Sprintf(mergeRefs, hook.GetPullRequest().GetNumber()) diff --git a/server/forge/github/parse_test.go b/server/forge/github/parse_test.go index 225bfd3c9..a48f0ff5a 100644 --- a/server/forge/github/parse_test.go +++ b/server/forge/github/parse_test.go @@ -22,12 +22,11 @@ import ( "strings" "testing" - "github.com/franela/goblin" "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/github/fixtures" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/github/fixtures" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) const ( @@ -46,96 +45,85 @@ func testHookRequest(payload []byte, event string) *http.Request { return req } -func Test_parser(t *testing.T) { - g := goblin.Goblin(t) - g.Describe("GitHub parser", func() { - g.It("should ignore unsupported hook events", func() { - req := testHookRequest([]byte(fixtures.HookPullRequest), "issues") - p, r, b, err := parseHook(req, false) - g.Assert(r).IsNil() - g.Assert(b).IsNil() - g.Assert(p).IsNil() - assert.ErrorIs(t, err, &types.ErrIgnoreEvent{}) - }) +func Test_parseHook(t *testing.T) { + t.Run("ignore unsupported hook events", func(t *testing.T) { + req := testHookRequest([]byte(fixtures.HookPullRequest), "issues") + p, r, b, err := parseHook(req, false) + assert.Nil(t, r) + assert.Nil(t, b) + assert.Nil(t, p) + assert.ErrorIs(t, err, &types.ErrIgnoreEvent{}) + }) - g.Describe("given a push hook", func() { - g.It("should skip when action is deleted", func() { - req := testHookRequest([]byte(fixtures.HookPushDeleted), hookPush) - p, r, b, err := parseHook(req, false) - g.Assert(r).IsNil() - g.Assert(b).IsNil() - g.Assert(err).IsNil() - g.Assert(p).IsNil() - }) - g.It("should extract repository and pipeline details", func() { - req := testHookRequest([]byte(fixtures.HookPush), hookPush) - p, r, b, err := parseHook(req, false) - g.Assert(err).IsNil() - g.Assert(p).IsNil() - g.Assert(r).IsNotNil() - g.Assert(b).IsNotNil() - g.Assert(b.Event).Equal(model.EventPush) - sort.Strings(b.ChangedFiles) - g.Assert(b.ChangedFiles).Equal([]string{"pipeline/shared/replace_secrets.go", "pipeline/shared/replace_secrets_test.go"}) - }) - }) + t.Run("skip skip push hook when action is deleted", func(t *testing.T) { + req := testHookRequest([]byte(fixtures.HookPushDeleted), hookPush) + p, r, b, err := parseHook(req, false) + assert.Nil(t, r) + assert.Nil(t, b) + assert.NoError(t, err) + assert.Nil(t, p) + }) + t.Run("push hook", func(t *testing.T) { + req := testHookRequest([]byte(fixtures.HookPush), hookPush) + p, r, b, err := parseHook(req, false) + assert.NoError(t, err) + assert.Nil(t, p) + assert.NotNil(t, r) + assert.NotNil(t, b) + assert.Equal(t, model.EventPush, b.Event) + sort.Strings(b.ChangedFiles) + assert.Equal(t, []string{"pipeline/shared/replace_secrets.go", "pipeline/shared/replace_secrets_test.go"}, b.ChangedFiles) + }) - g.Describe("given a pull request hook", func() { - g.It("should handle a PR hook when PR got opened or pushed to", func() { - req := testHookRequest([]byte(fixtures.HookPullRequest), hookPull) - p, r, b, err := parseHook(req, false) - g.Assert(err).IsNil() - g.Assert(r).IsNotNil() - g.Assert(b).IsNotNil() - g.Assert(p).IsNotNil() - g.Assert(b.Event).Equal(model.EventPull) - }) - g.It("should handle a PR closed hook when PR got closed", func() { - req := testHookRequest([]byte(fixtures.HookPullRequestClosed), hookPull) - p, r, b, err := parseHook(req, false) - g.Assert(err).IsNil() - g.Assert(r).IsNotNil() - g.Assert(b).IsNotNil() - g.Assert(p).IsNotNil() - g.Assert(b.Event).Equal(model.EventPullClosed) - }) - g.It("should handle a PR closed hook when PR got merged", func() { - req := testHookRequest([]byte(fixtures.HookPullRequestMerged), hookPull) - p, r, b, err := parseHook(req, false) - g.Assert(err).IsNil() - g.Assert(r).IsNotNil() - g.Assert(b).IsNotNil() - g.Assert(p).IsNotNil() - g.Assert(b.Event).Equal(model.EventPullClosed) - }) - }) + t.Run("PR hook", func(t *testing.T) { + req := testHookRequest([]byte(fixtures.HookPullRequest), hookPull) + p, r, b, err := parseHook(req, false) + assert.NoError(t, err) + assert.NotNil(t, r) + assert.NotNil(t, b) + assert.NotNil(t, p) + assert.Equal(t, model.EventPull, b.Event) + }) + t.Run("PR closed hook", func(t *testing.T) { + req := testHookRequest([]byte(fixtures.HookPullRequestClosed), hookPull) + p, r, b, err := parseHook(req, false) + assert.NoError(t, err) + assert.NotNil(t, r) + assert.NotNil(t, b) + assert.NotNil(t, p) + assert.Equal(t, model.EventPullClosed, b.Event) + }) + t.Run("PR merged hook", func(t *testing.T) { + req := testHookRequest([]byte(fixtures.HookPullRequestMerged), hookPull) + p, r, b, err := parseHook(req, false) + assert.NoError(t, err) + assert.NotNil(t, r) + assert.NotNil(t, b) + assert.NotNil(t, p) + assert.Equal(t, model.EventPullClosed, b.Event) + }) - g.Describe("given a deployment hook", func() { - g.It("should extract repository and pipeline details", func() { - req := testHookRequest([]byte(fixtures.HookDeploy), hookDeploy) - p, r, b, err := parseHook(req, false) - g.Assert(err).IsNil() - g.Assert(r).IsNotNil() - g.Assert(b).IsNotNil() - g.Assert(p).IsNil() - g.Assert(b.Event).Equal(model.EventDeploy) - g.Assert(b.DeployTo).Equal("production") - g.Assert(b.DeployTask).Equal("deploy") - }) - }) + t.Run("deploy hook", func(t *testing.T) { + req := testHookRequest([]byte(fixtures.HookDeploy), hookDeploy) + p, r, b, err := parseHook(req, false) + assert.NoError(t, err) + assert.NotNil(t, r) + assert.NotNil(t, b) + assert.Nil(t, p) + assert.Equal(t, model.EventDeploy, b.Event) + assert.Equal(t, "production", b.DeployTo) + assert.Equal(t, "deploy", b.DeployTask) + }) - g.Describe("given a release hook", func() { - g.It("should extract repository and build details", func() { - req := testHookRequest([]byte(fixtures.HookRelease), hookRelease) - p, r, b, err := parseHook(req, false) - g.Assert(err).IsNil() - g.Assert(r).IsNotNil() - g.Assert(b).IsNotNil() - g.Assert(p).IsNil() - g.Assert(b.Event).Equal(model.EventRelease) - g.Assert(len(strings.Split(b.Ref, "/")) == 3).IsTrue() - g.Assert(strings.HasPrefix(b.Ref, "refs/tags/")).IsTrue() - }) - }) + t.Run("release hook", func(t *testing.T) { + req := testHookRequest([]byte(fixtures.HookRelease), hookRelease) + p, r, b, err := parseHook(req, false) + assert.NoError(t, err) + assert.NotNil(t, r) + assert.NotNil(t, b) + assert.Nil(t, p) + assert.Equal(t, model.EventRelease, b.Event) + assert.Len(t, strings.Split(b.Ref, "/"), 3) + assert.True(t, strings.HasPrefix(b.Ref, "refs/tags/")) }) } diff --git a/server/forge/gitlab/convert.go b/server/forge/gitlab/convert.go index 793e81248..c8ccdc119 100644 --- a/server/forge/gitlab/convert.go +++ b/server/forge/gitlab/convert.go @@ -21,10 +21,10 @@ import ( "net/http" "strings" - "github.com/xanzy/go-gitlab" + "gitlab.com/gitlab-org/api/client-go" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/shared/utils" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/shared/utils" ) const ( @@ -124,6 +124,7 @@ func convertMergeRequestHook(hook *gitlab.MergeEvent, req *http.Request) (int, * pipeline.Ref = fmt.Sprintf(mergeRefs, obj.IID) pipeline.Branch = obj.SourceBranch + pipeline.Refspec = fmt.Sprintf("%s:%s", obj.SourceBranch, obj.TargetBranch) author := lastCommit.Author @@ -137,6 +138,7 @@ func convertMergeRequestHook(hook *gitlab.MergeEvent, req *http.Request) (int, * pipeline.Title = obj.Title pipeline.ForgeURL = obj.URL pipeline.PullRequestLabels = convertLabels(hook.Labels) + pipeline.FromFork = target.PathWithNamespace != source.PathWithNamespace return obj.IID, repo, pipeline, nil } @@ -302,7 +304,9 @@ func extractFromPath(str string) (string, string, error) { if len(s) < minPathComponents { return "", "", fmt.Errorf("minimum match not found") } - return s[0], s[1], nil + owner := strings.Join(s[:len(s)-1], "/") + name := s[len(s)-1] + return owner, name, nil } func convertLabels(from []*gitlab.EventLabel) []string { diff --git a/server/forge/gitlab/gitlab.go b/server/forge/gitlab/gitlab.go index 1929aef66..fbe145002 100644 --- a/server/forge/gitlab/gitlab.go +++ b/server/forge/gitlab/gitlab.go @@ -28,16 +28,16 @@ import ( "time" "github.com/rs/zerolog/log" - "github.com/xanzy/go-gitlab" + "gitlab.com/gitlab-org/api/client-go" "golang.org/x/oauth2" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/common" - forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" - "go.woodpecker-ci.org/woodpecker/v2/shared/utils" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/common" + forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" + "go.woodpecker-ci.org/woodpecker/v3/shared/utils" ) const ( @@ -142,8 +142,9 @@ func (g *GitLab) Login(ctx context.Context, req *forge_types.OAuthRequest) (*mod Email: login.Email, Avatar: login.AvatarURL, ForgeRemoteID: model.ForgeRemoteID(fmt.Sprint(login.ID)), - Token: token.AccessToken, - Secret: token.RefreshToken, + AccessToken: token.AccessToken, + RefreshToken: token.RefreshToken, + Expiry: token.Expiry.UTC().Unix(), } if !strings.HasPrefix(user.Avatar, "http") { user.Avatar = g.url + "/" + login.AvatarURL @@ -159,8 +160,8 @@ func (g *GitLab) Refresh(ctx context.Context, user *model.User) (bool, error) { config.RedirectURL = "" source := config.TokenSource(oauth2Ctx, &oauth2.Token{ - AccessToken: user.Token, - RefreshToken: user.Secret, + AccessToken: user.AccessToken, + RefreshToken: user.RefreshToken, Expiry: time.Unix(user.Expiry, 0), }) @@ -169,8 +170,8 @@ func (g *GitLab) Refresh(ctx context.Context, user *model.User) (bool, error) { return false, err } - user.Token = token.AccessToken - user.Secret = token.RefreshToken + user.AccessToken = token.AccessToken + user.RefreshToken = token.RefreshToken user.Expiry = token.Expiry.UTC().Unix() return true, nil } @@ -191,7 +192,7 @@ func (g *GitLab) Auth(ctx context.Context, token, _ string) (string, error) { // Teams fetches a list of team memberships from the forge. func (g *GitLab) Teams(ctx context.Context, user *model.User) ([]*model.Team, error) { - client, err := newClient(g.url, user.Token, g.SkipVerify) + client, err := newClient(g.url, user.AccessToken, g.SkipVerify) if err != nil { return nil, err } @@ -260,7 +261,7 @@ func (g *GitLab) getInheritedProjectMember(ctx context.Context, client *gitlab.C // Repo fetches the repository from the forge. func (g *GitLab) Repo(ctx context.Context, user *model.User, remoteID model.ForgeRemoteID, owner, name string) (*model.Repo, error) { - client, err := newClient(g.url, user.Token, g.SkipVerify) + client, err := newClient(g.url, user.AccessToken, g.SkipVerify) if err != nil { return nil, err } @@ -285,7 +286,7 @@ func (g *GitLab) Repo(ctx context.Context, user *model.User, remoteID model.Forg // Repos fetches a list of repos from the forge. func (g *GitLab) Repos(ctx context.Context, user *model.User) ([]*model.Repo, error) { - client, err := newClient(g.url, user.Token, g.SkipVerify) + client, err := newClient(g.url, user.AccessToken, g.SkipVerify) if err != nil { return nil, err } @@ -365,7 +366,7 @@ func (g *GitLab) PullRequests(ctx context.Context, u *model.User, r *model.Repo, // File fetches a file from the forge repository and returns in string format. func (g *GitLab) File(ctx context.Context, user *model.User, repo *model.Repo, pipeline *model.Pipeline, fileName string) ([]byte, error) { - client, err := newClient(g.url, user.Token, g.SkipVerify) + client, err := newClient(g.url, user.AccessToken, g.SkipVerify) if err != nil { return nil, err } @@ -382,7 +383,7 @@ func (g *GitLab) File(ctx context.Context, user *model.User, repo *model.Repo, p // Dir fetches a folder from the forge repository. func (g *GitLab) Dir(ctx context.Context, user *model.User, repo *model.Repo, pipeline *model.Pipeline, path string) ([]*forge_types.FileMeta, error) { - client, err := newClient(g.url, user.Token, g.SkipVerify) + client, err := newClient(g.url, user.AccessToken, g.SkipVerify) if err != nil { return nil, err } @@ -434,7 +435,7 @@ func (g *GitLab) Dir(ctx context.Context, user *model.User, repo *model.Repo, pi // Status sends the commit status back to gitlab. func (g *GitLab) Status(ctx context.Context, user *model.User, repo *model.Repo, pipeline *model.Pipeline, workflow *model.Workflow) error { - client, err := newClient(g.url, user.Token, g.SkipVerify) + client, err := newClient(g.url, user.AccessToken, g.SkipVerify) if err != nil { return err } @@ -463,7 +464,7 @@ func (g *GitLab) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) { if u != nil { login = "oauth2" - token = u.Token + token = u.AccessToken } host, err := common.ExtractHostFromCloneURL(r.Clone) @@ -491,7 +492,7 @@ func (g *GitLab) getTokenAndWebURL(link string) (token, webURL string, err error // Activate activates a repository by adding a Post-commit hook and // a Public Deploy key, if applicable. func (g *GitLab) Activate(ctx context.Context, user *model.User, repo *model.Repo, link string) error { - client, err := newClient(g.url, user.Token, g.SkipVerify) + client, err := newClient(g.url, user.AccessToken, g.SkipVerify) if err != nil { return err } @@ -526,7 +527,7 @@ func (g *GitLab) Activate(ctx context.Context, user *model.User, repo *model.Rep // Deactivate removes a repository by removing all the post-commit hooks // which are equal to link and removing the SSH deploy key. func (g *GitLab) Deactivate(ctx context.Context, user *model.User, repo *model.Repo, link string) error { - client, err := newClient(g.url, user.Token, g.SkipVerify) + client, err := newClient(g.url, user.AccessToken, g.SkipVerify) if err != nil { return err } @@ -541,7 +542,6 @@ func (g *GitLab) Deactivate(ctx context.Context, user *model.User, repo *model.R return err } - hookID := -1 listProjectHooksOptions := &gitlab.ListProjectHooksOptions{ PerPage: perPage, Page: 1, @@ -553,13 +553,15 @@ func (g *GitLab) Deactivate(ctx context.Context, user *model.User, repo *model.R } for _, hook := range hooks { - if hook.URL == webURL { - hookID = hook.ID - break + if strings.Contains(hook.URL, webURL) { + _, err = client.Projects.DeleteProjectHook(_repo.ID, hook.ID, gitlab.WithContext(ctx)) + log.Info().Msg(fmt.Sprintf("successfully deleted hook with ID %d for repo %s", hook.ID, repo.FullName)) + if err != nil { + return err + } } } - // Exit the loop when we've seen all pages if resp.CurrentPage >= resp.TotalPages { break } @@ -568,13 +570,7 @@ func (g *GitLab) Deactivate(ctx context.Context, user *model.User, repo *model.R listProjectHooksOptions.Page = resp.NextPage } - if hookID == -1 { - return fmt.Errorf("could not find hook to delete") - } - - _, err = client.Projects.DeleteProjectHook(_repo.ID, hookID, gitlab.WithContext(ctx)) - - return err + return nil } // Branches returns the names of all branches for the named repository. @@ -676,7 +672,7 @@ func (g *GitLab) Hook(ctx context.Context, req *http.Request) (*model.Repo, *mod // OrgMembership returns if user is member of organization and if user // is admin/owner in this organization. func (g *GitLab) OrgMembership(ctx context.Context, u *model.User, owner string) (*model.OrgPerm, error) { - client, err := newClient(g.url, u.Token, g.SkipVerify) + client, err := newClient(g.url, u.AccessToken, g.SkipVerify) if err != nil { return nil, err } @@ -730,7 +726,7 @@ func (g *GitLab) OrgMembership(ctx context.Context, u *model.User, owner string) } func (g *GitLab) Org(ctx context.Context, u *model.User, owner string) (*model.Org, error) { - client, err := newClient(g.url, u.Token, g.SkipVerify) + client, err := newClient(g.url, u.AccessToken, g.SkipVerify) if err != nil { return nil, err } @@ -753,7 +749,7 @@ func (g *GitLab) Org(ctx context.Context, u *model.User, owner string) (*model.O groups, _, err := client.Groups.ListGroups(&gitlab.ListGroupsOptions{ ListOptions: gitlab.ListOptions{ Page: 1, - PerPage: 1, + PerPage: perPage, }, Search: gitlab.Ptr(owner), }, gitlab.WithContext(ctx)) @@ -761,13 +757,21 @@ func (g *GitLab) Org(ctx context.Context, u *model.User, owner string) (*model.O return nil, err } - if len(groups) != 1 { + var matchedGroup *gitlab.Group + for _, group := range groups { + if group.FullPath == owner { + matchedGroup = group + break + } + } + + if matchedGroup == nil { return nil, fmt.Errorf("could not find org %s", owner) } return &model.Org{ - Name: groups[0].FullPath, - Private: groups[0].Visibility != gitlab.PublicVisibility, + Name: matchedGroup.FullPath, + Private: matchedGroup.Visibility != gitlab.PublicVisibility, }, nil } @@ -788,7 +792,7 @@ func (g *GitLab) loadChangedFilesFromMergeRequest(ctx context.Context, tmpRepo * return nil, err } - client, err := newClient(g.url, user.Token, g.SkipVerify) + client, err := newClient(g.url, user.AccessToken, g.SkipVerify) if err != nil { return nil, err } diff --git a/server/forge/gitlab/gitlab_test.go b/server/forge/gitlab/gitlab_test.go index 5197b9d01..876696ca7 100644 --- a/server/forge/gitlab/gitlab_test.go +++ b/server/forge/gitlab/gitlab_test.go @@ -23,12 +23,11 @@ import ( "strconv" "testing" - "github.com/franela/goblin" "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/gitlab/testdata" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/gitlab/testdata" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func load(config string) *GitLab { @@ -60,7 +59,7 @@ func Test_GitLab(t *testing.T) { user := model.User{ Login: "test_user", - Token: "e3b0c44298fc1c149afbf4c8996fb", + AccessToken: "e3b0c44298fc1c149afbf4c8996fb", ForgeRemoteID: "3", } @@ -70,233 +69,278 @@ func Test_GitLab(t *testing.T) { } ctx := context.Background() - g := goblin.Goblin(t) - g.Describe("GitLab Plugin", func() { - // Test projects method - g.Describe("AllProjects", func() { - g.It("Should return only non-archived projects is hidden", func() { - client.HideArchives = true - _projects, err := client.Repos(ctx, &user) - assert.NoError(t, err) - assert.Len(t, _projects, 1) - }) + // Test projects method + t.Run("Should return only non-archived projects is hidden", func(t *testing.T) { + client.HideArchives = true + _projects, err := client.Repos(ctx, &user) + assert.NoError(t, err) + assert.Len(t, _projects, 1) + }) - g.It("Should return all the projects", func() { - client.HideArchives = false - _projects, err := client.Repos(ctx, &user) + t.Run("Should return all the projects", func(t *testing.T) { + client.HideArchives = false + _projects, err := client.Repos(ctx, &user) - g.Assert(err).IsNil() - g.Assert(len(_projects)).Equal(2) - }) - }) + assert.NoError(t, err) + assert.Len(t, _projects, 2) + }) - // Test repository method - g.Describe("Repo", func() { - g.It("Should return valid repo", func() { - _repo, err := client.Repo(ctx, &user, "0", "diaspora", "diaspora-client") - assert.NoError(t, err) - assert.Equal(t, "diaspora-client", _repo.Name) - assert.Equal(t, "diaspora", _repo.Owner) - assert.True(t, _repo.IsSCMPrivate) - }) + // Test repository method + t.Run("Should return valid repo", func(t *testing.T) { + _repo, err := client.Repo(ctx, &user, "0", "diaspora", "diaspora-client") + assert.NoError(t, err) + assert.Equal(t, "diaspora-client", _repo.Name) + assert.Equal(t, "diaspora", _repo.Owner) + assert.True(t, _repo.IsSCMPrivate) + }) - g.It("Should return error, when repo not exist", func() { - _, err := client.Repo(ctx, &user, "0", "not-existed", "not-existed") - assert.Error(t, err) - }) + t.Run("Should return error, when repo not exist", func(t *testing.T) { + _, err := client.Repo(ctx, &user, "0", "not-existed", "not-existed") + assert.Error(t, err) + }) - g.It("Should return repo with push access, when user inherits membership from namespace", func() { - _repo, err := client.Repo(ctx, &user, "6", "brightbox", "puppet") - assert.NoError(t, err) - assert.True(t, _repo.Perm.Push) - }) - }) + t.Run("Should return repo with push access, when user inherits membership from namespace", func(t *testing.T) { + _repo, err := client.Repo(ctx, &user, "6", "brightbox", "puppet") + assert.NoError(t, err) + assert.True(t, _repo.Perm.Push) + }) - // Test activate method - g.Describe("Activate", func() { - g.It("Should be success", func() { - err := client.Activate(ctx, &user, &repo, "http://example.com/api/hook?access_token=token") - assert.NoError(t, err) - }) + // Test activate method + t.Run("Activate, success", func(t *testing.T) { + err := client.Activate(ctx, &user, &repo, "http://example.com/api/hook?access_token=token") + assert.NoError(t, err) + }) - g.It("Should be failed, when token not given", func() { - err := client.Activate(ctx, &user, &repo, "http://example.com/api/hook") + t.Run("Activate, failed no token", func(t *testing.T) { + err := client.Activate(ctx, &user, &repo, "http://example.com/api/hook") - g.Assert(err).IsNotNil() - }) - }) + assert.Error(t, err) + }) - // Test deactivate method - g.Describe("Deactivate", func() { - g.It("Should be success", func() { - err := client.Deactivate(ctx, &user, &repo, "http://example.com/api/hook?access_token=token") + // Test deactivate method + t.Run("Deactivate", func(t *testing.T) { + err := client.Deactivate(ctx, &user, &repo, "http://example.com/api/hook?access_token=token") + assert.NoError(t, err) + }) - g.Assert(err).IsNil() - }) - }) + // Test hook method + t.Run("parse push hook", func(t *testing.T) { + req, _ := http.NewRequest( + testdata.ServiceHookMethod, + testdata.ServiceHookURL.String(), + bytes.NewReader(testdata.HookPush), + ) + req.Header = testdata.ServiceHookHeaders - // Test hook method - g.Describe("Hook", func() { - g.Describe("Push hook", func() { - g.It("Should parse actual push hook", func() { - req, _ := http.NewRequest( - testdata.ServiceHookMethod, - testdata.ServiceHookURL.String(), - bytes.NewReader(testdata.HookPush), - ) - req.Header = testdata.ServiceHookHeaders + hookRepo, pipeline, err := client.Hook(ctx, req) + assert.NoError(t, err) + if assert.NotNil(t, hookRepo) && assert.NotNil(t, pipeline) { + assert.Equal(t, pipeline.Event, model.EventPush) + assert.Equal(t, "test", hookRepo.Owner) + assert.Equal(t, "woodpecker", hookRepo.Name) + assert.Equal(t, "http://example.com/uploads/project/avatar/555/Outh-20-Logo.jpg", hookRepo.Avatar) + assert.Equal(t, "develop", hookRepo.Branch) + assert.Equal(t, "refs/heads/main", pipeline.Ref) + assert.Equal(t, []string{"cmd/cli/main.go"}, pipeline.ChangedFiles) + assert.Equal(t, model.EventPush, pipeline.Event) + } + }) - hookRepo, pipeline, err := client.Hook(ctx, req) - assert.NoError(t, err) - if assert.NotNil(t, hookRepo) && assert.NotNil(t, pipeline) { - assert.Equal(t, pipeline.Event, model.EventPush) - assert.Equal(t, "test", hookRepo.Owner) - assert.Equal(t, "woodpecker", hookRepo.Name) - assert.Equal(t, "http://example.com/uploads/project/avatar/555/Outh-20-Logo.jpg", hookRepo.Avatar) - assert.Equal(t, "develop", hookRepo.Branch) - assert.Equal(t, "refs/heads/main", pipeline.Ref) - assert.Equal(t, []string{"cmd/cli/main.go"}, pipeline.ChangedFiles) - assert.Equal(t, model.EventPush, pipeline.Event) - } - }) - }) + t.Run("tag push hook", func(t *testing.T) { + req, _ := http.NewRequest( + testdata.ServiceHookMethod, + testdata.ServiceHookURL.String(), + bytes.NewReader(testdata.HookTag), + ) + req.Header = testdata.ServiceHookHeaders - g.Describe("Tag push hook", func() { - g.It("Should parse tag push hook", func() { - req, _ := http.NewRequest( - testdata.ServiceHookMethod, - testdata.ServiceHookURL.String(), - bytes.NewReader(testdata.HookTag), - ) - req.Header = testdata.ServiceHookHeaders + hookRepo, pipeline, err := client.Hook(ctx, req) + assert.NoError(t, err) + if assert.NotNil(t, hookRepo) && assert.NotNil(t, pipeline) { + assert.Equal(t, "test", hookRepo.Owner) + assert.Equal(t, "woodpecker", hookRepo.Name) + assert.Equal(t, "http://example.com/uploads/project/avatar/555/Outh-20-Logo.jpg", hookRepo.Avatar) + assert.Equal(t, "develop", hookRepo.Branch) + assert.Equal(t, "refs/tags/v22", pipeline.Ref) + assert.Len(t, pipeline.ChangedFiles, 0) + assert.Equal(t, model.EventTag, pipeline.Event) + } + }) - hookRepo, pipeline, err := client.Hook(ctx, req) - assert.NoError(t, err) - if assert.NotNil(t, hookRepo) && assert.NotNil(t, pipeline) { - assert.Equal(t, "test", hookRepo.Owner) - assert.Equal(t, "woodpecker", hookRepo.Name) - assert.Equal(t, "http://example.com/uploads/project/avatar/555/Outh-20-Logo.jpg", hookRepo.Avatar) - assert.Equal(t, "develop", hookRepo.Branch) - assert.Equal(t, "refs/tags/v22", pipeline.Ref) - assert.Len(t, pipeline.ChangedFiles, 0) - assert.Equal(t, model.EventTag, pipeline.Event) - } - }) - }) + t.Run("merge request hook", func(t *testing.T) { + req, _ := http.NewRequest( + testdata.ServiceHookMethod, + testdata.ServiceHookURL.String(), + bytes.NewReader(testdata.HookPullRequest), + ) + req.Header = testdata.ServiceHookHeaders - g.Describe("Merge request hook", func() { - g.It("Should parse merge request hook", func() { - req, _ := http.NewRequest( - testdata.ServiceHookMethod, - testdata.ServiceHookURL.String(), - bytes.NewReader(testdata.HookPullRequest), - ) - req.Header = testdata.ServiceHookHeaders + // TODO: insert fake store into context to retrieve user & repo, this will activate fetching of ChangedFiles + hookRepo, pipeline, err := client.Hook(ctx, req) + assert.NoError(t, err) + if assert.NotNil(t, hookRepo) && assert.NotNil(t, pipeline) { + assert.Equal(t, "http://example.com/uploads/project/avatar/555/Outh-20-Logo.jpg", hookRepo.Avatar) + assert.Equal(t, "main", hookRepo.Branch) + assert.Equal(t, "anbraten", hookRepo.Owner) + assert.Equal(t, "woodpecker", hookRepo.Name) + assert.Equal(t, "Update client.go 🎉", pipeline.Title) + assert.Len(t, pipeline.ChangedFiles, 0) // see L217 + assert.Equal(t, model.EventPull, pipeline.Event) + } + }) - // TODO: insert fake store into context to retrieve user & repo, this will activate fetching of ChangedFiles - hookRepo, pipeline, err := client.Hook(ctx, req) - assert.NoError(t, err) - if assert.NotNil(t, hookRepo) && assert.NotNil(t, pipeline) { - assert.Equal(t, "http://example.com/uploads/project/avatar/555/Outh-20-Logo.jpg", hookRepo.Avatar) - assert.Equal(t, "main", hookRepo.Branch) - assert.Equal(t, "anbraten", hookRepo.Owner) - assert.Equal(t, "woodpecker", hookRepo.Name) - assert.Equal(t, "Update client.go 🎉", pipeline.Title) - assert.Len(t, pipeline.ChangedFiles, 0) // see L217 - assert.Equal(t, model.EventPull, pipeline.Event) - } - }) + t.Run("ignore merge request hook without changes", func(t *testing.T) { + req, _ := http.NewRequest( + testdata.ServiceHookMethod, + testdata.ServiceHookURL.String(), + bytes.NewReader(testdata.HookPullRequestWithoutChanges), + ) + req.Header = testdata.ServiceHookHeaders - g.It("Should ignore merge request hook without changes", func() { - req, _ := http.NewRequest( - testdata.ServiceHookMethod, - testdata.ServiceHookURL.String(), - bytes.NewReader(testdata.HookPullRequestWithoutChanges), - ) - req.Header = testdata.ServiceHookHeaders + // TODO: insert fake store into context to retrieve user & repo, this will activate fetching of ChangedFiles + hookRepo, pipeline, err := client.Hook(ctx, req) + assert.Nil(t, hookRepo) + assert.Nil(t, pipeline) + assert.ErrorIs(t, err, &types.ErrIgnoreEvent{}) + }) - // TODO: insert fake store into context to retrieve user & repo, this will activate fetching of ChangedFiles - hookRepo, pipeline, err := client.Hook(ctx, req) - assert.Nil(t, hookRepo) - assert.Nil(t, pipeline) - assert.ErrorIs(t, err, &types.ErrIgnoreEvent{}) - }) + t.Run("ignore merge request approval", func(t *testing.T) { + req, _ := http.NewRequest( + testdata.ServiceHookMethod, + testdata.ServiceHookURL.String(), + bytes.NewReader(testdata.HookPullRequestApproved), + ) + req.Header = testdata.ServiceHookHeaders - g.It("Should ignore merge request approval", func() { - req, _ := http.NewRequest( - testdata.ServiceHookMethod, - testdata.ServiceHookURL.String(), - bytes.NewReader(testdata.HookPullRequestApproved), - ) - req.Header = testdata.ServiceHookHeaders + // TODO: insert fake store into context to retrieve user & repo, this will activate fetching of ChangedFiles + hookRepo, pipeline, err := client.Hook(ctx, req) + assert.Nil(t, hookRepo) + assert.Nil(t, pipeline) + assert.ErrorIs(t, err, &types.ErrIgnoreEvent{}) + }) - // TODO: insert fake store into context to retrieve user & repo, this will activate fetching of ChangedFiles - hookRepo, pipeline, err := client.Hook(ctx, req) - assert.Nil(t, hookRepo) - assert.Nil(t, pipeline) - assert.ErrorIs(t, err, &types.ErrIgnoreEvent{}) - }) + t.Run("parse merge request closed", func(t *testing.T) { + req, _ := http.NewRequest( + testdata.ServiceHookMethod, + testdata.ServiceHookURL.String(), + bytes.NewReader(testdata.HookPullRequestClosed), + ) + req.Header = testdata.ServiceHookHeaders - g.It("Should parse merge request hook when MR closed", func() { - req, _ := http.NewRequest( - testdata.ServiceHookMethod, - testdata.ServiceHookURL.String(), - bytes.NewReader(testdata.HookPullRequestClosed), - ) - req.Header = testdata.ServiceHookHeaders + // TODO: insert fake store into context to retrieve user & repo, this will activate fetching of ChangedFiles + hookRepo, pipeline, err := client.Hook(ctx, req) + assert.NoError(t, err) + if assert.NotNil(t, hookRepo) && assert.NotNil(t, pipeline) { + assert.Equal(t, "main", hookRepo.Branch) + assert.Equal(t, "anbraten", hookRepo.Owner) + assert.Equal(t, "woodpecker-test", hookRepo.Name) + assert.Equal(t, "Add new file", pipeline.Title) + assert.Len(t, pipeline.ChangedFiles, 0) // see L217 + assert.Equal(t, model.EventPullClosed, pipeline.Event) + } + }) - // TODO: insert fake store into context to retrieve user & repo, this will activate fetching of ChangedFiles - hookRepo, pipeline, err := client.Hook(ctx, req) - assert.NoError(t, err) - if assert.NotNil(t, hookRepo) && assert.NotNil(t, pipeline) { - assert.Equal(t, "main", hookRepo.Branch) - assert.Equal(t, "anbraten", hookRepo.Owner) - assert.Equal(t, "woodpecker-test", hookRepo.Name) - assert.Equal(t, "Add new file", pipeline.Title) - assert.Len(t, pipeline.ChangedFiles, 0) // see L217 - assert.Equal(t, model.EventPullClosed, pipeline.Event) - } - }) + t.Run("parse merge request merged", func(t *testing.T) { + req, _ := http.NewRequest( + testdata.ServiceHookMethod, + testdata.ServiceHookURL.String(), + bytes.NewReader(testdata.HookPullRequestMerged), + ) + req.Header = testdata.ServiceHookHeaders - g.It("Should parse merge request hook when merged", func() { - req, _ := http.NewRequest( - testdata.ServiceHookMethod, - testdata.ServiceHookURL.String(), - bytes.NewReader(testdata.HookPullRequestMerged), - ) - req.Header = testdata.ServiceHookHeaders + // TODO: insert fake store into context to retrieve user & repo, this will activate fetching of ChangedFiles + hookRepo, pipeline, err := client.Hook(ctx, req) + assert.NoError(t, err) + if assert.NotNil(t, hookRepo) && assert.NotNil(t, pipeline) { + assert.Equal(t, "main", hookRepo.Branch) + assert.Equal(t, "anbraten", hookRepo.Owner) + assert.Equal(t, "woodpecker-test", hookRepo.Name) + assert.Equal(t, "Add new file", pipeline.Title) + assert.Len(t, pipeline.ChangedFiles, 0) // see L217 + assert.Equal(t, model.EventPullClosed, pipeline.Event) + } + }) - // TODO: insert fake store into context to retrieve user & repo, this will activate fetching of ChangedFiles - hookRepo, pipeline, err := client.Hook(ctx, req) - assert.NoError(t, err) - if assert.NotNil(t, hookRepo) && assert.NotNil(t, pipeline) { - assert.Equal(t, "main", hookRepo.Branch) - assert.Equal(t, "anbraten", hookRepo.Owner) - assert.Equal(t, "woodpecker-test", hookRepo.Name) - assert.Equal(t, "Add new file", pipeline.Title) - assert.Len(t, pipeline.ChangedFiles, 0) // see L217 - assert.Equal(t, model.EventPullClosed, pipeline.Event) - } - }) + t.Run("release hook", func(t *testing.T) { + req, _ := http.NewRequest( + testdata.ServiceHookMethod, + testdata.ServiceHookURL.String(), + bytes.NewReader(testdata.WebhookReleaseBody), + ) + req.Header = testdata.ReleaseHookHeaders - g.It("Should parse release request hook", func() { - req, _ := http.NewRequest( - testdata.ServiceHookMethod, - testdata.ServiceHookURL.String(), - bytes.NewReader(testdata.WebhookReleaseBody), - ) - req.Header = testdata.ReleaseHookHeaders - - hookRepo, pipeline, err := client.Hook(ctx, req) - assert.NoError(t, err) - if assert.NotNil(t, hookRepo) && assert.NotNil(t, pipeline) { - assert.Equal(t, "refs/tags/0.0.2", pipeline.Ref) - assert.Equal(t, "ci", hookRepo.Name) - assert.Equal(t, "created release Awesome version 0.0.2", pipeline.Message) - assert.Equal(t, model.EventRelease, pipeline.Event) - } - }) - }) - }) + hookRepo, pipeline, err := client.Hook(ctx, req) + assert.NoError(t, err) + if assert.NotNil(t, hookRepo) && assert.NotNil(t, pipeline) { + assert.Equal(t, "refs/tags/0.0.2", pipeline.Ref) + assert.Equal(t, "ci", hookRepo.Name) + assert.Equal(t, "created release Awesome version 0.0.2", pipeline.Message) + assert.Equal(t, model.EventRelease, pipeline.Event) + } }) } + +func TestExtractFromPath(t *testing.T) { + type testCase struct { + name string + input string + wantOwner string + wantName string + errContains string + } + + tests := []testCase{ + { + name: "basic two components", + input: "owner/repo", + wantOwner: "owner", + wantName: "repo", + }, + { + name: "three components", + input: "owner/group/repo", + wantOwner: "owner/group", + wantName: "repo", + }, + { + name: "many components", + input: "owner/group/subgroup/deep/repo", + wantOwner: "owner/group/subgroup/deep", + wantName: "repo", + }, + { + name: "empty string", + input: "", + errContains: "minimum match not found", + }, + { + name: "single component", + input: "onlyrepo", + errContains: "minimum match not found", + }, + { + name: "trailing slash", + input: "owner/repo/", + wantOwner: "owner/repo", + wantName: "", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + owner, name, err := extractFromPath(tc.input) + + // Check error expectations + if tc.errContains != "" { + if assert.Error(t, err) { + assert.Contains(t, err.Error(), tc.errContains) + } + return + } + + assert.NoError(t, err) + assert.EqualValues(t, tc.wantOwner, owner) + assert.EqualValues(t, tc.wantName, name) + }) + } +} diff --git a/server/forge/gitlab/helper.go b/server/forge/gitlab/helper.go index db6bc1759..203b64a43 100644 --- a/server/forge/gitlab/helper.go +++ b/server/forge/gitlab/helper.go @@ -19,7 +19,7 @@ import ( "crypto/tls" "net/http" - "github.com/xanzy/go-gitlab" + "gitlab.com/gitlab-org/api/client-go" ) const ( diff --git a/server/forge/gitlab/status.go b/server/forge/gitlab/status.go index cc21b2463..be03b7c9d 100644 --- a/server/forge/gitlab/status.go +++ b/server/forge/gitlab/status.go @@ -15,9 +15,9 @@ package gitlab import ( - "github.com/xanzy/go-gitlab" + "gitlab.com/gitlab-org/api/client-go" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) // getStatus is a helper that converts a Woodpecker status to a Gitlab status. diff --git a/server/forge/mocks/forge.go b/server/forge/mocks/forge.go index 3deb6b960..0c6ec4421 100644 --- a/server/forge/mocks/forge.go +++ b/server/forge/mocks/forge.go @@ -12,9 +12,9 @@ import ( mock "github.com/stretchr/testify/mock" - model "go.woodpecker-ci.org/woodpecker/v2/server/model" + model "go.woodpecker-ci.org/woodpecker/v3/server/model" - types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" + types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" ) // Forge is an autogenerated mock type for the Forge type @@ -282,7 +282,7 @@ func (_m *Forge) Login(ctx context.Context, r *types.OAuthRequest) (*model.User, return r0, r1, r2 } -// Name provides a mock function with given fields: +// Name provides a mock function with no fields func (_m *Forge) Name() string { ret := _m.Called() @@ -528,7 +528,7 @@ func (_m *Forge) Teams(ctx context.Context, u *model.User) ([]*model.Team, error return r0, r1 } -// URL provides a mock function with given fields: +// URL provides a mock function with no fields func (_m *Forge) URL() string { ret := _m.Called() diff --git a/server/forge/refresh.go b/server/forge/refresh.go index 931b67d6e..0b0e5fac5 100644 --- a/server/forge/refresh.go +++ b/server/forge/refresh.go @@ -20,8 +20,8 @@ import ( "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) // Refresher refreshes an oauth token and expiration for the given user. It diff --git a/server/forge/setup/setup.go b/server/forge/setup/setup.go index 510a71e8f..e534ab112 100644 --- a/server/forge/setup/setup.go +++ b/server/forge/setup/setup.go @@ -7,15 +7,15 @@ import ( "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/addon" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/bitbucket" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/bitbucketdatacenter" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/forgejo" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/gitea" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/github" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/gitlab" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/addon" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/bitbucket" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/bitbucketdatacenter" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/forgejo" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/gitea" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/github" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/gitlab" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func Forge(forge *model.Forge) (forge.Forge, error) { @@ -44,7 +44,11 @@ func setupBitbucket(forge *model.Forge) (forge.Forge, error) { Client: forge.Client, Secret: forge.ClientSecret, } - log.Trace().Msgf("Forge (bitbucket) opts: %#v", opts) + log.Debug(). + Bool("client-set", opts.Client != ""). + Bool("secret-set", opts.Secret != ""). + Str("type", string(forge.Type)). + Msg("setting up forge") return bitbucket.New(opts) } @@ -64,7 +68,14 @@ func setupGitea(forge *model.Forge) (forge.Forge, error) { if len(opts.URL) == 0 { return nil, fmt.Errorf("WOODPECKER_GITEA_URL must be set") } - log.Trace().Msgf("Forge (gitea) opts: %#v", opts) + log.Debug(). + Str("url", opts.URL). + Str("oauth-host", opts.OAuthHost). + Bool("skip-verify", opts.SkipVerify). + Bool("client-set", opts.Client != ""). + Bool("secret-set", opts.Secret != ""). + Str("type", string(forge.Type)). + Msg("setting up forge") return gitea.New(opts) } @@ -84,18 +95,34 @@ func setupForgejo(forge *model.Forge) (forge.Forge, error) { if len(opts.URL) == 0 { return nil, fmt.Errorf("WOODPECKER_FORGEJO_URL must be set") } - log.Trace().Msgf("Forge (forgejo) opts: %#v", opts) + log.Debug(). + Str("url", opts.URL). + Str("oauth2-url", opts.OAuth2URL). + Bool("skip-verify", opts.SkipVerify). + Bool("client-set", opts.Client != ""). + Bool("secret-set", opts.Secret != ""). + Str("type", string(forge.Type)). + Msg("setting up forge") return forgejo.New(opts) } func setupGitLab(forge *model.Forge) (forge.Forge, error) { - return gitlab.New(gitlab.Opts{ + opts := gitlab.Opts{ URL: forge.URL, ClientID: forge.Client, ClientSecret: forge.ClientSecret, SkipVerify: forge.SkipVerify, OAuthHost: forge.OAuthHost, - }) + } + log.Debug(). + Str("url", opts.URL). + Str("oauth-host", opts.OAuthHost). + Bool("skip-verify", opts.SkipVerify). + Bool("client-id-set", opts.ClientID != ""). + Bool("client-secret-set", opts.ClientSecret != ""). + Str("type", string(forge.Type)). + Msg("setting up forge") + return gitlab.New(opts) } func setupGitHub(forge *model.Forge) (forge.Forge, error) { @@ -118,7 +145,16 @@ func setupGitHub(forge *model.Forge) (forge.Forge, error) { OnlyPublic: publicOnly, OAuthHost: forge.OAuthHost, } - log.Trace().Msgf("Forge (github) opts: %#v", opts) + log.Debug(). + Str("url", opts.URL). + Str("oauth-host", opts.OAuthHost). + Bool("merge-ref", opts.MergeRef). + Bool("only-public", opts.OnlyPublic). + Bool("skip-verify", opts.SkipVerify). + Bool("client-set", opts.Client != ""). + Bool("secret-set", opts.Secret != ""). + Str("type", string(forge.Type)). + Msg("setting up forge") return github.New(opts) } @@ -140,16 +176,22 @@ func setupBitbucketDatacenter(forge *model.Forge) (forge.Forge, error) { Password: gitPassword, OAuthHost: forge.OAuthHost, } - log.Trace().Msgf("Forge (bitbucketdatacenter) opts: %#v", opts) + log.Debug(). + Str("url", opts.URL). + Str("oauth-host", opts.OAuthHost). + Bool("client-id-set", opts.ClientID != ""). + Bool("client-secret-set", opts.ClientSecret != ""). + Str("type", string(forge.Type)). + Msg("setting up forge") return bitbucketdatacenter.New(opts) } func setupAddon(forge *model.Forge) (forge.Forge, error) { executable, ok := forge.AdditionalOptions["executable"].(string) if !ok { - return nil, fmt.Errorf("missing git-username") + return nil, fmt.Errorf("missing addon executable") } - log.Trace().Msgf("Forge (addon) executable: %#v", executable) + log.Debug().Str("executable", executable).Msg("setting up forge") return addon.Load(executable) } diff --git a/server/grpc/auth_server.go b/server/grpc/auth_server.go index f9652c85f..c009b6eca 100644 --- a/server/grpc/auth_server.go +++ b/server/grpc/auth_server.go @@ -21,10 +21,10 @@ import ( "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc/proto" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" - "go.woodpecker-ci.org/woodpecker/v2/server/store/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc/proto" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/store/types" ) type WoodpeckerAuthServer struct { @@ -60,13 +60,12 @@ func (s *WoodpeckerAuthServer) getAgent(agentID int64, agentToken string) (*mode // global agent secret auth if s.agentMasterToken != "" { if agentToken == s.agentMasterToken && agentID == -1 { - agent := new(model.Agent) - agent.Name = "" - agent.OwnerID = -1 // system agent - agent.Token = s.agentMasterToken - agent.Backend = "" - agent.Platform = "" - agent.Capacity = -1 + agent := &model.Agent{ + OwnerID: model.IDNotSet, + OrgID: model.IDNotSet, + Token: s.agentMasterToken, + Capacity: -1, + } err := s.store.AgentCreate(agent) if err != nil { log.Error().Err(err).Msg("error creating system agent") diff --git a/server/grpc/filter.go b/server/grpc/filter.go index 9cf2d87fa..6df125bcd 100644 --- a/server/grpc/filter.go +++ b/server/grpc/filter.go @@ -15,34 +15,38 @@ package grpc import ( - "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/queue" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/queue" ) func createFilterFunc(agentFilter rpc.Filter) queue.FilterFn { - return func(task *model.Task) bool { + return func(task *model.Task) (bool, int) { + score := 0 for taskLabel, taskLabelValue := range task.Labels { // if a task label is empty it will be ignored if taskLabelValue == "" { continue } + // all task labels are required to be present for an agent to match agentLabelValue, ok := agentFilter.Labels[taskLabel] - if !ok { - return false + return false, 0 } + switch { // if agent label has a wildcard - if agentLabelValue == "*" { - continue - } - - if taskLabelValue != agentLabelValue { - return false + case agentLabelValue == "*": + score++ + // if agent label has an exact match + case agentLabelValue == taskLabelValue: + score += 10 + // agent doesn't match + default: + return false, 0 } } - return true + return true, score } } diff --git a/server/grpc/filter_test.go b/server/grpc/filter_test.go index e4c7eeae6..1599a58ca 100644 --- a/server/grpc/filter_test.go +++ b/server/grpc/filter_test.go @@ -19,73 +19,115 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func TestCreateFilterFunc(t *testing.T) { - t.Parallel() - tests := []struct { name string - agentLabels map[string]string - task model.Task - exp bool + agentFilter rpc.Filter + task *model.Task + wantMatched bool + wantScore int }{ { - name: "agent with missing labels", - agentLabels: map[string]string{"repo": "test/woodpecker"}, - task: model.Task{ - Labels: map[string]string{"platform": "linux/amd64", "repo": "test/woodpecker"}, + name: "Two exact matches", + agentFilter: rpc.Filter{ + Labels: map[string]string{"org-id": "123", "platform": "linux"}, }, - exp: false, + task: &model.Task{ + Labels: map[string]string{"org-id": "123", "platform": "linux"}, + }, + wantMatched: true, + wantScore: 20, }, { - name: "agent with wrong labels", - agentLabels: map[string]string{"platform": "linux/arm64"}, - task: model.Task{ - Labels: map[string]string{"platform": "linux/amd64"}, + name: "Wildcard and exact match", + agentFilter: rpc.Filter{ + Labels: map[string]string{"org-id": "*", "platform": "linux"}, }, - exp: false, + task: &model.Task{ + Labels: map[string]string{"org-id": "123", "platform": "linux"}, + }, + wantMatched: true, + wantScore: 11, }, { - name: "agent with correct labels", - agentLabels: map[string]string{"platform": "linux/amd64", "location": "europe"}, - task: model.Task{ - Labels: map[string]string{"platform": "linux/amd64", "location": "europe"}, + name: "Partial match", + agentFilter: rpc.Filter{ + Labels: map[string]string{"org-id": "123", "platform": "linux"}, }, - exp: true, + task: &model.Task{ + Labels: map[string]string{"org-id": "123", "platform": "windows"}, + }, + wantMatched: false, + wantScore: 0, }, { - name: "agent with additional labels", - agentLabels: map[string]string{"platform": "linux/amd64", "location": "europe"}, - task: model.Task{ - Labels: map[string]string{"platform": "linux/amd64"}, + name: "No match", + agentFilter: rpc.Filter{ + Labels: map[string]string{"org-id": "456", "platform": "linux"}, }, - exp: true, + task: &model.Task{ + Labels: map[string]string{"org-id": "123", "platform": "windows"}, + }, + wantMatched: false, + wantScore: 0, }, { - name: "agent with wildcard label", - agentLabels: map[string]string{"platform": "linux/amd64", "location": "*"}, - task: model.Task{ - Labels: map[string]string{"platform": "linux/amd64", "location": "america"}, + name: "Missing label", + agentFilter: rpc.Filter{ + Labels: map[string]string{"platform": "linux"}, }, - exp: true, + task: &model.Task{ + Labels: map[string]string{"needed": "some"}, + }, + wantMatched: false, + wantScore: 0, }, { - name: "agent with platform label and task without", - agentLabels: map[string]string{"platform": "linux/amd64"}, - task: model.Task{ - Labels: map[string]string{"platform": ""}, + name: "Empty task labels", + agentFilter: rpc.Filter{ + Labels: map[string]string{"org-id": "123", "platform": "linux"}, }, - exp: true, + task: &model.Task{ + Labels: map[string]string{}, + }, + wantMatched: true, + wantScore: 0, + }, + { + name: "Agent with additional label", + agentFilter: rpc.Filter{ + Labels: map[string]string{"org-id": "123", "platform": "linux", "extra": "value"}, + }, + task: &model.Task{ + Labels: map[string]string{"org-id": "123", "platform": "linux", "empty": ""}, + }, + wantMatched: true, + wantScore: 20, + }, + { + name: "Two wildcard matches", + agentFilter: rpc.Filter{ + Labels: map[string]string{"org-id": "*", "platform": "*"}, + }, + task: &model.Task{ + Labels: map[string]string{"org-id": "123", "platform": "linux"}, + }, + wantMatched: true, + wantScore: 2, }, } - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - fn := createFilterFunc(rpc.Filter{Labels: test.agentLabels}) - assert.EqualValues(t, test.exp, fn(&test.task)) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + filterFunc := createFilterFunc(tt.agentFilter) + gotMatched, gotScore := filterFunc(tt.task) + + assert.Equal(t, tt.wantMatched, gotMatched, "Matched result") + assert.Equal(t, tt.wantScore, gotScore, "Score") }) } } diff --git a/server/grpc/rpc.go b/server/grpc/rpc.go index 7a193674f..fa66e93b3 100644 --- a/server/grpc/rpc.go +++ b/server/grpc/rpc.go @@ -28,17 +28,20 @@ import ( "github.com/rs/zerolog/log" grpcMetadata "google.golang.org/grpc/metadata" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - "go.woodpecker-ci.org/woodpecker/v2/server/logging" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/pipeline" - "go.woodpecker-ci.org/woodpecker/v2/server/pubsub" - "go.woodpecker-ci.org/woodpecker/v2/server/queue" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/logging" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/pipeline" + "go.woodpecker-ci.org/woodpecker/v3/server/pubsub" + "go.woodpecker-ci.org/woodpecker/v3/server/queue" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) +// updateAgentLastWorkDelay the delay before the LastWork info should be updated. +const updateAgentLastWorkDelay = time.Minute + type RPC struct { queue queue.Queue pubsub *pubsub.Publisher @@ -54,8 +57,6 @@ func (s *RPC) Next(c context.Context, agentFilter rpc.Filter) (*rpc.Workflow, er log.Debug().Msgf("agent connected: %s: polling", hostname) } - filterFn := createFilterFunc(agentFilter) - agent, err := s.getAgentFromContext(c) if err != nil { return nil, err @@ -66,6 +67,20 @@ func (s *RPC) Next(c context.Context, agentFilter rpc.Filter) (*rpc.Workflow, er return nil, nil } + agentServerLabels, err := agent.GetServerLabels() + if err != nil { + return nil, err + } + + // enforce labels from server by overwriting agent labels + for k, v := range agentServerLabels { + agentFilter.Labels[k] = v + } + + log.Trace().Msgf("Agent %s[%d] tries to pull task with labels: %v", agent.Name, agent.ID, agentFilter.Labels) + + filterFn := createFilterFunc(agentFilter) + for { // poll blocks until a task is available or the context is canceled / worker is kicked task, err := s.queue.Poll(c, agent.ID, filterFn) @@ -88,6 +103,15 @@ func (s *RPC) Next(c context.Context, agentFilter rpc.Filter) (*rpc.Workflow, er // Wait blocks until the workflow with the given ID is done. func (s *RPC) Wait(c context.Context, workflowID string) error { + agent, err := s.getAgentFromContext(c) + if err != nil { + return err + } + + if err := s.checkAgentPermissionByWorkflow(c, agent, workflowID, nil, nil); err != nil { + return err + } + return s.queue.Wait(c, workflowID) } @@ -98,17 +122,20 @@ func (s *RPC) Extend(c context.Context, workflowID string) error { return err } - agent.LastWork = time.Now().Unix() - err = s.store.AgentUpdate(agent) + err = s.updateAgentLastWork(agent) if err != nil { return err } - return s.queue.Extend(c, workflowID) + if err := s.checkAgentPermissionByWorkflow(c, agent, workflowID, nil, nil); err != nil { + return err + } + + return s.queue.Extend(c, agent.ID, workflowID) } // Update updates the state of a step. -func (s *RPC) Update(_ context.Context, strWorkflowID string, state rpc.StepState) error { +func (s *RPC) Update(c context.Context, strWorkflowID string, state rpc.StepState) error { workflowID, err := strconv.ParseInt(strWorkflowID, 10, 64) if err != nil { return err @@ -126,6 +153,11 @@ func (s *RPC) Update(_ context.Context, strWorkflowID string, state rpc.StepStat return err } + agent, err := s.getAgentFromContext(c) + if err != nil { + return err + } + step, err := s.store.StepByUUID(state.StepUUID) if err != nil { log.Error().Err(err).Msgf("cannot find step with uuid %s", state.StepUUID) @@ -138,7 +170,7 @@ func (s *RPC) Update(_ context.Context, strWorkflowID string, state rpc.StepStat Int64("stepPipelineID", step.PipelineID). Int64("currentPipelineID", currentPipeline.ID). Msg(msg) - return fmt.Errorf(msg) + return errors.New(msg) } repo, err := s.store.GetRepo(currentPipeline.RepoID) @@ -147,6 +179,11 @@ func (s *RPC) Update(_ context.Context, strWorkflowID string, state rpc.StepStat return err } + // check before agent can alter some state + if err := s.checkAgentPermissionByWorkflow(c, agent, strWorkflowID, currentPipeline, repo); err != nil { + return err + } + if state.Finished == 0 { if _, err := pipeline.UpdateStepStatusToRunning(s.store, *step, state); err != nil { log.Error().Err(err).Msg("rpc.update: cannot update step") @@ -196,6 +233,7 @@ func (s *RPC) Init(c context.Context, strWorkflowID string, state rpc.WorkflowSt if err != nil { return err } + workflow.AgentID = agent.ID currentPipeline, err := s.store.GetPipeline(workflow.PipelineID) @@ -210,6 +248,11 @@ func (s *RPC) Init(c context.Context, strWorkflowID string, state rpc.WorkflowSt return err } + // check before agent can alter some state + if err := s.checkAgentPermissionByWorkflow(c, agent, strWorkflowID, currentPipeline, repo); err != nil { + return err + } + // Init should only be called on pending pipelines if currentPipeline.Status != model.StatusPending { log.Error().Msgf("pipeline %d is not pending", currentPipeline.ID) @@ -220,7 +263,7 @@ func (s *RPC) Init(c context.Context, strWorkflowID string, state rpc.WorkflowSt log.Error().Err(err).Msgf("init: cannot update pipeline %d state", currentPipeline.ID) } - workflow, err = pipeline.UpdateWorkflowToStatusRunning(s.store, *workflow, state) + workflow, err = pipeline.UpdateWorkflowStatusToRunning(s.store, *workflow, state) if err != nil { return err } @@ -252,8 +295,7 @@ func (s *RPC) Init(c context.Context, strWorkflowID string, state rpc.WorkflowSt } s.updateForgeStatus(c, repo, currentPipeline, workflow) - agent.LastWork = time.Now().Unix() - return s.store.AgentUpdate(agent) + return s.updateAgentLastWork(agent) } // Done marks the workflow with the given ID as done. @@ -286,6 +328,16 @@ func (s *RPC) Done(c context.Context, strWorkflowID string, state rpc.WorkflowSt return err } + agent, err := s.getAgentFromContext(c) + if err != nil { + return err + } + + // check before agent can alter some state + if err := s.checkAgentPermissionByWorkflow(c, agent, strWorkflowID, currentPipeline, repo); err != nil { + return err + } + logger := log.With(). Str("repo_id", fmt.Sprint(repo.ID)). Str("pipeline_id", fmt.Sprint(currentPipeline.ID)). @@ -342,50 +394,69 @@ func (s *RPC) Done(c context.Context, strWorkflowID string, state rpc.WorkflowSt s.pipelineTime.WithLabelValues(repo.FullName, currentPipeline.Branch, string(workflow.State), workflow.Name).Set(float64(workflow.Finished - workflow.Started)) } + return s.updateAgentLastWork(agent) +} + +// Log writes a log entry to the database and publishes it to the pubsub. +// An explicit stepUUID makes it obvious that all entries must come from the same step. +func (s *RPC) Log(c context.Context, stepUUID string, rpcLogEntries []*rpc.LogEntry) error { + step, err := s.store.StepByUUID(stepUUID) + if err != nil { + return fmt.Errorf("could not find step with uuid %s in store: %w", stepUUID, err) + } + agent, err := s.getAgentFromContext(c) if err != nil { return err } - agent.LastWork = time.Now().Unix() - return s.store.AgentUpdate(agent) -} -// Log writes a log entry to the database and publishes it to the pubsub. -func (s *RPC) Log(c context.Context, rpcLogEntry *rpc.LogEntry) error { - // convert rpc log_entry to model.log_entry - step, err := s.store.StepByUUID(rpcLogEntry.StepUUID) + currentPipeline, err := s.store.GetPipeline(step.PipelineID) if err != nil { - return fmt.Errorf("could not find step with uuid %s in store: %w", rpcLogEntry.StepUUID, err) + log.Error().Err(err).Msgf("cannot find pipeline with id %d", step.PipelineID) + return err } - logEntry := &model.LogEntry{ - StepID: step.ID, - Time: rpcLogEntry.Time, - Line: rpcLogEntry.Line, - Data: rpcLogEntry.Data, - Type: model.LogEntryType(rpcLogEntry.Type), + + // check before agent can alter some state + if err := s.checkAgentPermissionByWorkflow(c, agent, "", currentPipeline, nil); err != nil { + return err + } + + err = s.updateAgentLastWork(agent) + if err != nil { + return err + } + + var logEntries []*model.LogEntry + + for _, rpcLogEntry := range rpcLogEntries { + if rpcLogEntry.StepUUID != stepUUID { + return fmt.Errorf("expected step UUID %s, got %s", stepUUID, rpcLogEntry.StepUUID) + } + logEntries = append(logEntries, &model.LogEntry{ + StepID: step.ID, + Time: rpcLogEntry.Time, + Line: rpcLogEntry.Line, + Data: rpcLogEntry.Data, + Type: model.LogEntryType(rpcLogEntry.Type), + }) } // make sure writes to pubsub are non blocking (https://github.com/woodpecker-ci/woodpecker/blob/c919f32e0b6432a95e1a6d3d0ad662f591adf73f/server/logging/log.go#L9) go func() { // write line to listening web clients - if err := s.logger.Write(c, logEntry.StepID, logEntry); err != nil { + if err := s.logger.Write(c, step.ID, logEntries); err != nil { log.Error().Err(err).Msgf("rpc server could not write to logger") } }() - agent, err := s.getAgentFromContext(c) - if err != nil { - return err - } - agent.LastWork = time.Now().Unix() - if err := s.store.AgentUpdate(agent); err != nil { - return err + if err = server.Config.Services.LogStore.LogAppend(step, logEntries); err != nil { + log.Error().Err(err).Msg("could not store log entries") } - return server.Config.Services.LogStore.LogAppend(logEntry) + return nil } -func (s *RPC) RegisterAgent(ctx context.Context, platform, backend, version string, capacity int32) (int64, error) { +func (s *RPC) RegisterAgent(ctx context.Context, info rpc.AgentInfo) (int64, error) { agent, err := s.getAgentFromContext(ctx) if err != nil { return -1, err @@ -397,10 +468,11 @@ func (s *RPC) RegisterAgent(ctx context.Context, platform, backend, version stri } } - agent.Backend = backend - agent.Platform = platform - agent.Capacity = capacity - agent.Version = version + agent.Backend = info.Backend + agent.Platform = info.Platform + agent.Capacity = int32(info.Capacity) + agent.Version = info.Version + agent.CustomLabels = info.CustomLabels err = s.store.AgentUpdate(agent) if err != nil { @@ -443,10 +515,48 @@ func (s *RPC) ReportHealth(ctx context.Context, status string) error { return s.store.AgentUpdate(agent) } +func (s *RPC) checkAgentPermissionByWorkflow(_ context.Context, agent *model.Agent, strWorkflowID string, pipeline *model.Pipeline, repo *model.Repo) error { + var err error + if repo == nil && pipeline == nil { + workflowID, err := strconv.ParseInt(strWorkflowID, 10, 64) + if err != nil { + return err + } + + workflow, err := s.store.WorkflowLoad(workflowID) + if err != nil { + log.Error().Err(err).Msgf("cannot find workflow with id %d", workflowID) + return err + } + + pipeline, err = s.store.GetPipeline(workflow.PipelineID) + if err != nil { + log.Error().Err(err).Msgf("cannot find pipeline with id %d", workflow.PipelineID) + return err + } + } + + if repo == nil { + repo, err = s.store.GetRepo(pipeline.RepoID) + if err != nil { + log.Error().Err(err).Msgf("cannot find repo with id %d", pipeline.RepoID) + return err + } + } + + if agent.CanAccessRepo(repo) { + return nil + } + + msg := fmt.Sprintf("agent '%d' is not allowed to interact with repo[%d] '%s'", agent.ID, repo.ID, repo.FullName) + log.Error().Int64("repoId", repo.ID).Msg(msg) + return errors.New(msg) +} + func (s *RPC) completeChildrenIfParentCompleted(completedWorkflow *model.Workflow) { for _, c := range completedWorkflow.Children { if c.Running() { - if _, err := pipeline.UpdateStepToStatusSkipped(s.store, *c, completedWorkflow.Finished); err != nil { + if _, err := pipeline.UpdateStepStatusToSkipped(s.store, *c, completedWorkflow.Finished); err != nil { log.Error().Err(err).Msgf("done: cannot update step_id %d child state", c.ID) } } @@ -525,3 +635,17 @@ func (s *RPC) getHostnameFromContext(ctx context.Context) (string, error) { } return "", errors.New("no hostname in metadata") } + +func (s *RPC) updateAgentLastWork(agent *model.Agent) error { + // only update agent.LastWork if not recently updated + if time.Unix(agent.LastWork, 0).Add(updateAgentLastWorkDelay).After(time.Now()) { + return nil + } + + agent.LastWork = time.Now().Unix() + if err := s.store.AgentUpdate(agent); err != nil { + return err + } + + return nil +} diff --git a/server/grpc/rpc_test.go b/server/grpc/rpc_test.go index 1fd850b8a..798c39e35 100644 --- a/server/grpc/rpc_test.go +++ b/server/grpc/rpc_test.go @@ -17,93 +17,146 @@ package grpc import ( "context" "testing" + "time" - "github.com/franela/goblin" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" "google.golang.org/grpc/metadata" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - mocks_store "go.woodpecker-ci.org/woodpecker/v2/server/store/mocks" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + mocks_store "go.woodpecker-ci.org/woodpecker/v3/server/store/mocks" ) func TestRegisterAgent(t *testing.T) { - g := goblin.Goblin(t) - g.Describe("When existing agent Name is empty", func() { - g.It("Should update Name with hostname from metadata", func() { - store := mocks_store.NewStore(t) - storeAgent := new(model.Agent) - storeAgent.ID = 1337 - updatedAgent := model.Agent{ - ID: 1337, - Created: 0, - Updated: 0, - Name: "hostname", - OwnerID: 0, - Token: "", - LastContact: 0, - Platform: "platform", - Backend: "backend", - Capacity: 2, - Version: "version", - NoSchedule: false, - } + t.Run("When existing agent Name is empty it should update Name with hostname from metadata", func(t *testing.T) { + store := mocks_store.NewStore(t) + storeAgent := new(model.Agent) + storeAgent.ID = 1337 + updatedAgent := model.Agent{ + ID: 1337, + Created: 0, + Updated: 0, + Name: "hostname", + OwnerID: 0, + Token: "", + LastContact: 0, + Platform: "platform", + Backend: "backend", + Capacity: 2, + Version: "version", + NoSchedule: false, + } - store.On("AgentFind", int64(1337)).Once().Return(storeAgent, nil) - store.On("AgentUpdate", &updatedAgent).Once().Return(nil) - rpc := RPC{ - store: store, - } - ctx := metadata.NewIncomingContext( - context.Background(), - metadata.Pairs("hostname", "hostname", "agent_id", "1337"), - ) - capacity := int32(2) - agentID, err := rpc.RegisterAgent(ctx, "platform", "backend", "version", capacity) - if !assert.NoError(t, err) { - return - } - - assert.EqualValues(t, 1337, agentID) + store.On("AgentFind", int64(1337)).Once().Return(storeAgent, nil) + store.On("AgentUpdate", &updatedAgent).Once().Return(nil) + grpc := RPC{ + store: store, + } + ctx := metadata.NewIncomingContext( + context.Background(), + metadata.Pairs("hostname", "hostname", "agent_id", "1337"), + ) + agentID, err := grpc.RegisterAgent(ctx, rpc.AgentInfo{ + Version: "version", + Platform: "platform", + Backend: "backend", + Capacity: 2, }) + if !assert.NoError(t, err) { + return + } + + assert.EqualValues(t, 1337, agentID) }) - g.Describe("When existing agent hostname is present", func() { - g.It("Should not update the hostname", func() { - store := mocks_store.NewStore(t) - storeAgent := new(model.Agent) - storeAgent.ID = 1337 - storeAgent.Name = "originalHostname" - updatedAgent := model.Agent{ - ID: 1337, - Created: 0, - Updated: 0, - Name: "originalHostname", - OwnerID: 0, - Token: "", - LastContact: 0, - Platform: "platform", - Backend: "backend", - Capacity: 2, - Version: "version", - NoSchedule: false, - } + t.Run("When existing agent hostname is present it should not update the hostname", func(t *testing.T) { + store := mocks_store.NewStore(t) + storeAgent := new(model.Agent) + storeAgent.ID = 1337 + storeAgent.Name = "originalHostname" + updatedAgent := model.Agent{ + ID: 1337, + Created: 0, + Updated: 0, + Name: "originalHostname", + OwnerID: 0, + Token: "", + LastContact: 0, + Platform: "platform", + Backend: "backend", + Capacity: 2, + Version: "version", + NoSchedule: false, + } - store.On("AgentFind", int64(1337)).Once().Return(storeAgent, nil) - store.On("AgentUpdate", &updatedAgent).Once().Return(nil) - rpc := RPC{ - store: store, - } - ctx := metadata.NewIncomingContext( - context.Background(), - metadata.Pairs("hostname", "newHostname", "agent_id", "1337"), - ) - capacity := int32(2) - agentID, err := rpc.RegisterAgent(ctx, "platform", "backend", "version", capacity) - if !assert.NoError(t, err) { - return - } - - assert.EqualValues(t, 1337, agentID) + store.On("AgentFind", int64(1337)).Once().Return(storeAgent, nil) + store.On("AgentUpdate", &updatedAgent).Once().Return(nil) + grpc := RPC{ + store: store, + } + ctx := metadata.NewIncomingContext( + context.Background(), + metadata.Pairs("hostname", "newHostname", "agent_id", "1337"), + ) + agentID, err := grpc.RegisterAgent(ctx, rpc.AgentInfo{ + Version: "version", + Platform: "platform", + Backend: "backend", + Capacity: 2, }) + if !assert.NoError(t, err) { + return + } + + assert.EqualValues(t, 1337, agentID) + }) +} + +func TestUpdateAgentLastWork(t *testing.T) { + t.Run("When last work was never updated it should update last work timestamp", func(t *testing.T) { + agent := model.Agent{ + LastWork: 0, + } + store := mocks_store.NewStore(t) + rpc := RPC{ + store: store, + } + store.On("AgentUpdate", mock.Anything).Once().Return(nil) + + err := rpc.updateAgentLastWork(&agent) + assert.NoError(t, err) + + assert.NotZero(t, agent.LastWork) + }) + + t.Run("When last work was updated over a minute ago it should update last work timestamp", func(t *testing.T) { + lastWork := time.Now().Add(-time.Hour).Unix() + agent := model.Agent{ + LastWork: lastWork, + } + store := mocks_store.NewStore(t) + rpc := RPC{ + store: store, + } + store.On("AgentUpdate", mock.Anything).Once().Return(nil) + + err := rpc.updateAgentLastWork(&agent) + assert.NoError(t, err) + + assert.NotEqual(t, lastWork, agent.LastWork) + }) + + t.Run("When last work was updated in the last minute it should not update last work timestamp again", func(t *testing.T) { + lastWork := time.Now().Add(-time.Second * 30).Unix() + agent := model.Agent{ + LastWork: lastWork, + } + rpc := RPC{} + + err := rpc.updateAgentLastWork(&agent) + assert.NoError(t, err) + + assert.Equal(t, lastWork, agent.LastWork) }) } diff --git a/server/grpc/server.go b/server/grpc/server.go index d6882c0f8..4a79ef447 100644 --- a/server/grpc/server.go +++ b/server/grpc/server.go @@ -20,14 +20,15 @@ import ( "github.com/prometheus/client_golang/prometheus" prometheus_auto "github.com/prometheus/client_golang/prometheus/promauto" + "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc/proto" - "go.woodpecker-ci.org/woodpecker/v2/server/logging" - "go.woodpecker-ci.org/woodpecker/v2/server/pubsub" - "go.woodpecker-ci.org/woodpecker/v2/server/queue" - "go.woodpecker-ci.org/woodpecker/v2/server/store" - "go.woodpecker-ci.org/woodpecker/v2/version" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc/proto" + "go.woodpecker-ci.org/woodpecker/v3/server/logging" + "go.woodpecker-ci.org/woodpecker/v3/server/pubsub" + "go.woodpecker-ci.org/woodpecker/v3/server/queue" + "go.woodpecker-ci.org/woodpecker/v3/server/store" + "go.woodpecker-ci.org/woodpecker/v3/version" ) // WoodpeckerServer is a grpc server implementation. @@ -133,21 +134,52 @@ func (s *WoodpeckerServer) Extend(c context.Context, req *proto.ExtendRequest) ( } func (s *WoodpeckerServer) Log(c context.Context, req *proto.LogRequest) (*proto.Empty, error) { - logEntry := &rpc.LogEntry{ - Data: req.GetLogEntry().GetData(), - Line: int(req.GetLogEntry().GetLine()), - Time: req.GetLogEntry().GetTime(), - StepUUID: req.GetLogEntry().GetStepUuid(), - Type: int(req.GetLogEntry().GetType()), + var ( + entries []*rpc.LogEntry + stepUUID string + ) + + write := func() error { + if len(entries) > 0 { + if err := s.peer.Log(c, stepUUID, entries); err != nil { + log.Error().Err(err).Msg("could not write log entries") + return err + } + } + return nil } + + for _, reqEntry := range req.GetLogEntries() { + entry := &rpc.LogEntry{ + Data: reqEntry.GetData(), + Line: int(reqEntry.GetLine()), + Time: reqEntry.GetTime(), + StepUUID: reqEntry.GetStepUuid(), + Type: int(reqEntry.GetType()), + } + if entry.StepUUID != stepUUID { + _ = write() + stepUUID = entry.StepUUID + entries = entries[:0] + } + entries = append(entries, entry) + } + res := new(proto.Empty) - err := s.peer.Log(c, logEntry) + err := write() return res, err } func (s *WoodpeckerServer) RegisterAgent(c context.Context, req *proto.RegisterAgentRequest) (*proto.RegisterAgentResponse, error) { res := new(proto.RegisterAgentResponse) - agentID, err := s.peer.RegisterAgent(c, req.GetPlatform(), req.GetBackend(), req.GetVersion(), req.GetCapacity()) + agentInfo := req.GetInfo() + agentID, err := s.peer.RegisterAgent(c, rpc.AgentInfo{ + Version: agentInfo.GetVersion(), + Platform: agentInfo.GetPlatform(), + Backend: agentInfo.GetBackend(), + Capacity: int(agentInfo.GetCapacity()), + CustomLabels: agentInfo.GetCustomLabels(), + }) res.AgentId = agentID return res, err } diff --git a/server/logging/log.go b/server/logging/log.go index 1db5475e5..ec7afe861 100644 --- a/server/logging/log.go +++ b/server/logging/log.go @@ -18,7 +18,9 @@ import ( "context" "sync" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + logger "github.com/rs/zerolog/log" + + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) // TODO: (bradrydzewski) writing to subscribers is currently a blocking @@ -38,7 +40,7 @@ import ( // sub.start()... event loop type subscriber struct { - handler Handler + receiver LogChan } type stream struct { @@ -77,7 +79,7 @@ func (l *log) Open(_ context.Context, stepID int64) error { return nil } -func (l *log) Write(ctx context.Context, stepID int64, logEntry *model.LogEntry) error { +func (l *log) Write(ctx context.Context, stepID int64, entries []*model.LogEntry) error { l.Lock() s, ok := l.streams[stepID] l.Unlock() @@ -92,15 +94,20 @@ func (l *log) Write(ctx context.Context, stepID int64, logEntry *model.LogEntry) } s.Lock() - s.list = append(s.list, logEntry) + s.list = append(s.list, entries...) for sub := range s.subs { - go sub.handler(logEntry) + select { + case sub.receiver <- entries: + default: + logger.Info().Msgf("subscriber channel is full -- dropping logs for step %d", stepID) + } } s.Unlock() + return nil } -func (l *log) Tail(c context.Context, stepID int64, handler Handler) error { +func (l *log) Tail(c context.Context, stepID int64, receiver LogChan) error { l.Lock() s, ok := l.streams[stepID] l.Unlock() @@ -109,11 +116,11 @@ func (l *log) Tail(c context.Context, stepID int64, handler Handler) error { } sub := &subscriber{ - handler: handler, + receiver: receiver, } s.Lock() if len(s.list) != 0 { - sub.handler(s.list...) + sub.receiver <- s.list } s.subs[sub] = struct{}{} s.Unlock() diff --git a/server/logging/log_test.go b/server/logging/log_test.go index 90565a48f..52020e6cc 100644 --- a/server/logging/log_test.go +++ b/server/logging/log_test.go @@ -22,7 +22,7 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func TestLogging(t *testing.T) { @@ -39,28 +39,37 @@ func TestLogging(t *testing.T) { context.Background(), ) + receiver := make(LogChan, 10) + defer close(receiver) + + go func() { + for range receiver { + wg.Done() + } + }() + logger := New() assert.NoError(t, logger.Open(ctx, testStepID)) go func() { - assert.NoError(t, logger.Tail(ctx, testStepID, func(_ ...*model.LogEntry) { wg.Done() })) + assert.NoError(t, logger.Tail(ctx, testStepID, receiver)) }() go func() { - assert.NoError(t, logger.Tail(ctx, testStepID, func(_ ...*model.LogEntry) { wg.Done() })) + assert.NoError(t, logger.Tail(ctx, testStepID, receiver)) }() <-time.After(500 * time.Millisecond) wg.Add(4) go func() { - assert.NoError(t, logger.Write(ctx, testStepID, testEntry)) - assert.NoError(t, logger.Write(ctx, testStepID, testEntry)) + assert.NoError(t, logger.Write(ctx, testStepID, []*model.LogEntry{testEntry})) + assert.NoError(t, logger.Write(ctx, testStepID, []*model.LogEntry{testEntry})) }() wg.Wait() wg.Add(1) go func() { - assert.NoError(t, logger.Tail(ctx, testStepID, func(_ ...*model.LogEntry) { wg.Done() })) + assert.NoError(t, logger.Tail(ctx, testStepID, receiver)) }() <-time.After(500 * time.Millisecond) diff --git a/server/logging/logging.go b/server/logging/logging.go index 400273def..80f1854f8 100644 --- a/server/logging/logging.go +++ b/server/logging/logging.go @@ -18,14 +18,14 @@ import ( "context" "errors" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) // ErrNotFound is returned when the log does not exist. var ErrNotFound = errors.New("stream: not found") -// Handler defines a callback function for handling log entries. -type Handler func(...*model.LogEntry) +// LogChan defines a channel type for receiving ordered batches of log entries. +type LogChan chan []*model.LogEntry // Log defines a log multiplexer. type Log interface { @@ -33,10 +33,10 @@ type Log interface { Open(c context.Context, stepID int64) error // Write writes the entry to the log. - Write(c context.Context, stepID int64, entry *model.LogEntry) error + Write(c context.Context, stepID int64, entries []*model.LogEntry) error // Tail tails the log. - Tail(c context.Context, stepID int64, handler Handler) error + Tail(c context.Context, stepID int64, handler LogChan) error // Close closes the log. Close(c context.Context, stepID int64) error diff --git a/server/model/agent.go b/server/model/agent.go index 31655cbb8..b57c56baa 100644 --- a/server/model/agent.go +++ b/server/model/agent.go @@ -14,27 +14,73 @@ package model +import ( + "encoding/base32" + "fmt" + + "github.com/gorilla/securecookie" +) + type Agent struct { - ID int64 `json:"id" xorm:"pk autoincr 'id'"` - Created int64 `json:"created" xorm:"created"` - Updated int64 `json:"updated" xorm:"updated"` - Name string `json:"name" xorm:"name"` - OwnerID int64 `json:"owner_id" xorm:"'owner_id'"` - Token string `json:"token" xorm:"token"` - LastContact int64 `json:"last_contact" xorm:"last_contact"` - LastWork int64 `json:"last_work" xorm:"last_work"` // last time the agent did something, this value is used to determine if the agent is still doing work used by the autoscaler - Platform string `json:"platform" xorm:"VARCHAR(100) 'platform'"` - Backend string `json:"backend" xorm:"VARCHAR(100) 'backend'"` - Capacity int32 `json:"capacity" xorm:"capacity"` - Version string `json:"version" xorm:"'version'"` - NoSchedule bool `json:"no_schedule" xorm:"no_schedule"` + ID int64 `json:"id" xorm:"pk autoincr 'id'"` + Created int64 `json:"created" xorm:"created"` + Updated int64 `json:"updated" xorm:"updated"` + Name string `json:"name" xorm:"name"` + OwnerID int64 `json:"owner_id" xorm:"'owner_id'"` + Token string `json:"token" xorm:"token"` + LastContact int64 `json:"last_contact" xorm:"last_contact"` + LastWork int64 `json:"last_work" xorm:"last_work"` // last time the agent did something, this value is used to determine if the agent is still doing work used by the autoscaler + Platform string `json:"platform" xorm:"VARCHAR(100) 'platform'"` + Backend string `json:"backend" xorm:"VARCHAR(100) 'backend'"` + Capacity int32 `json:"capacity" xorm:"capacity"` + Version string `json:"version" xorm:"'version'"` + NoSchedule bool `json:"no_schedule" xorm:"no_schedule"` + CustomLabels map[string]string `json:"custom_labels" xorm:"JSON 'custom_labels'"` + // OrgID is counted as unset if set to -1, this is done to ensure a new(Agent) still enforce the OrgID check by default + OrgID int64 `json:"org_id" xorm:"INDEX 'org_id'"` } // @name Agent +const ( + IDNotSet = -1 + agentFilterOrgID = "org-id" +) + // TableName return database table name for xorm. func (Agent) TableName() string { return "agents" } func (a *Agent) IsSystemAgent() bool { - return a.OwnerID == -1 + return a.OwnerID == IDNotSet +} + +func GenerateNewAgentToken() string { + return base32.StdEncoding.EncodeToString(securecookie.GenerateRandomKey(32)) +} + +func (a *Agent) GetServerLabels() (map[string]string, error) { + filters := make(map[string]string) + + // enforce filters for user and organization agents + if a.OrgID != IDNotSet { + filters[agentFilterOrgID] = fmt.Sprintf("%d", a.OrgID) + } else { + filters[agentFilterOrgID] = "*" + } + + return filters, nil +} + +func (a *Agent) CanAccessRepo(repo *Repo) bool { + // global agent + if a.OrgID == IDNotSet { + return true + } + + // agent has access to the organization + if a.OrgID == repo.OrgID { + return true + } + + return false } diff --git a/server/model/agent_test.go b/server/model/agent_test.go new file mode 100644 index 000000000..90356c456 --- /dev/null +++ b/server/model/agent_test.go @@ -0,0 +1,90 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGenerateNewAgentToken(t *testing.T) { + token1 := GenerateNewAgentToken() + token2 := GenerateNewAgentToken() + + assert.NotEmpty(t, token1) + assert.NotEmpty(t, token2) + assert.NotEqual(t, token1, token2) + assert.Len(t, token1, 56) +} + +func TestAgent_GetServerLabels(t *testing.T) { + t.Run("EmptyAgent", func(t *testing.T) { + agent := &Agent{} + filters, err := agent.GetServerLabels() + assert.NoError(t, err) + assert.Equal(t, map[string]string{ + agentFilterOrgID: "0", + }, filters) + }) + + t.Run("GlobalAgent", func(t *testing.T) { + agent := &Agent{ + OrgID: IDNotSet, + } + filters, err := agent.GetServerLabels() + assert.NoError(t, err) + assert.Equal(t, map[string]string{ + agentFilterOrgID: "*", + }, filters) + }) + + t.Run("OrgAgent", func(t *testing.T) { + agent := &Agent{ + OrgID: 123, + } + filters, err := agent.GetServerLabels() + assert.NoError(t, err) + assert.Equal(t, map[string]string{ + agentFilterOrgID: "123", + }, filters) + }) +} + +func TestAgent_CanAccessRepo(t *testing.T) { + repo := &Repo{ID: 123, OrgID: 12} + otherRepo := &Repo{ID: 456, OrgID: 45} + + t.Run("EmptyAgent", func(t *testing.T) { + agent := &Agent{} + assert.False(t, agent.CanAccessRepo(repo)) + }) + + t.Run("GlobalAgent", func(t *testing.T) { + agent := &Agent{ + OrgID: IDNotSet, + } + + assert.True(t, agent.CanAccessRepo(repo)) + }) + + t.Run("OrgAgent", func(t *testing.T) { + agent := &Agent{ + OrgID: 12, + } + assert.True(t, agent.CanAccessRepo(repo)) + assert.False(t, agent.CanAccessRepo(otherRepo)) + }) +} diff --git a/server/model/const.go b/server/model/const.go index 0707473b8..d0ab526e7 100644 --- a/server/model/const.go +++ b/server/model/const.go @@ -66,15 +66,16 @@ const ( StatusCreated StatusValue = "created" // created / internal use only ) -// SCMKind represent different version control systems. -type SCMKind string // @name SCMKind +var ErrInvalidStatusValue = errors.New("invalid status value") -const ( - RepoGit SCMKind = "git" - RepoHg SCMKind = "hg" - RepoFossil SCMKind = "fossil" - RepoPerforce SCMKind = "perforce" -) +func (s StatusValue) Validate() error { + switch s { + case StatusSkipped, StatusPending, StatusRunning, StatusSuccess, StatusFailure, StatusKilled, StatusError, StatusBlocked, StatusDeclined, StatusCreated: + return nil + default: + return fmt.Errorf("%w: %s", ErrInvalidStatusValue, s) + } +} // RepoVisibility represent to what state a repo in woodpecker is visible to others. type RepoVisibility string // @name RepoVisibility diff --git a/server/model/cron.go b/server/model/cron.go index 42467de35..cdce73ef3 100644 --- a/server/model/cron.go +++ b/server/model/cron.go @@ -17,18 +17,18 @@ package model import ( "fmt" - "github.com/robfig/cron" + "github.com/gdgvda/cron" ) type Cron struct { - ID int64 `json:"id" xorm:"pk autoincr 'id'"` - Name string `json:"name" xorm:"name UNIQUE(s) INDEX"` - RepoID int64 `json:"repo_id" xorm:"repo_id UNIQUE(s) INDEX"` - CreatorID int64 `json:"creator_id" xorm:"creator_id INDEX"` - NextExec int64 `json:"next_exec" xorm:"next_exec"` - Schedule string `json:"schedule" xorm:"schedule NOT NULL"` // @weekly, 3min, ... - Created int64 `json:"created_at" xorm:"created NOT NULL DEFAULT 0"` // TODO change JSON field to "created" in 3.0 - Branch string `json:"branch" xorm:"branch"` + ID int64 `json:"id" xorm:"pk autoincr 'id'"` + Name string `json:"name" xorm:"name UNIQUE(s) INDEX"` + RepoID int64 `json:"repo_id" xorm:"repo_id UNIQUE(s) INDEX"` + CreatorID int64 `json:"creator_id" xorm:"creator_id INDEX"` + NextExec int64 `json:"next_exec" xorm:"next_exec"` + Schedule string `json:"schedule" xorm:"schedule NOT NULL"` // @weekly, 3min, ... + Created int64 `json:"created" xorm:"created NOT NULL DEFAULT 0"` + Branch string `json:"branch" xorm:"branch"` } // @name Cron // TableName returns the database table name for xorm. @@ -46,7 +46,7 @@ func (c *Cron) Validate() error { return fmt.Errorf("schedule is required") } - _, err := cron.Parse(c.Schedule) + _, err := cron.ParseStandard(c.Schedule) if err != nil { return fmt.Errorf("can't parse schedule: %w", err) } diff --git a/server/model/feed.go b/server/model/feed.go index 0dd4b5d3a..04357fc4e 100644 --- a/server/model/feed.go +++ b/server/model/feed.go @@ -22,9 +22,9 @@ type Feed struct { Number int64 `json:"number,omitempty" xorm:"pipeline_number"` Event string `json:"event,omitempty" xorm:"pipeline_event"` Status string `json:"status,omitempty" xorm:"pipeline_status"` - Created int64 `json:"created_at,omitempty" xorm:"pipeline_created"` // TODO change JSON field to "created" in 3.0 - Started int64 `json:"started_at,omitempty" xorm:"pipeline_started"` // TODO change JSON field to "started" in 3.0 - Finished int64 `json:"finished_at,omitempty" xorm:"pipeline_finished"` // TODO change JSON field to "finished" in 3.0 + Created int64 `json:"created,omitempty" xorm:"pipeline_created"` + Started int64 `json:"started,omitempty" xorm:"pipeline_started"` + Finished int64 `json:"finished,omitempty" xorm:"pipeline_finished"` Commit string `json:"commit,omitempty" xorm:"pipeline_commit"` Branch string `json:"branch,omitempty" xorm:"pipeline_branch"` Ref string `json:"ref,omitempty" xorm:"pipeline_ref"` diff --git a/server/model/pipeline.go b/server/model/pipeline.go index 5106308de..b74e8184a 100644 --- a/server/model/pipeline.go +++ b/server/model/pipeline.go @@ -16,7 +16,7 @@ package model import ( - "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/errors/types" ) type Pipeline struct { @@ -28,10 +28,10 @@ type Pipeline struct { Event WebhookEvent `json:"event" xorm:"event"` Status StatusValue `json:"status" xorm:"INDEX 'status'"` Errors []*types.PipelineError `json:"errors" xorm:"json 'errors'"` - Created int64 `json:"created_at" xorm:"'created' NOT NULL DEFAULT 0 created"` // TODO change JSON field to "created" in 3.0 - Updated int64 `json:"updated_at" xorm:"'updated' NOT NULL DEFAULT 0 updated"` // TODO change JSON field to "updated" in 3.0 - Started int64 `json:"started_at" xorm:"started"` // TODO change JSON field to "started" in 3.0 - Finished int64 `json:"finished_at" xorm:"finished"` // TODO change JSON field to "finished" in 3.0 + Created int64 `json:"created" xorm:"'created' NOT NULL DEFAULT 0 created"` + Updated int64 `json:"updated" xorm:"'updated' NOT NULL DEFAULT 0 updated"` + Started int64 `json:"started" xorm:"started"` + Finished int64 `json:"finished" xorm:"finished"` DeployTo string `json:"deploy_to" xorm:"deploy"` DeployTask string `json:"deploy_task" xorm:"deploy_task"` Commit string `json:"commit" xorm:"commit"` @@ -42,16 +42,17 @@ type Pipeline struct { Message string `json:"message" xorm:"TEXT 'message'"` Timestamp int64 `json:"timestamp" xorm:"'timestamp'"` Sender string `json:"sender" xorm:"sender"` // uses reported user for webhooks and name of cron for cron pipelines - Avatar string `json:"author_avatar" xorm:"avatar"` - Email string `json:"author_email" xorm:"email"` + Avatar string `json:"author_avatar" xorm:"varchar(500) avatar"` + Email string `json:"author_email" xorm:"varchar(500) email"` ForgeURL string `json:"forge_url" xorm:"forge_url"` Reviewer string `json:"reviewed_by" xorm:"reviewer"` - Reviewed int64 `json:"reviewed_at" xorm:"reviewed"` // TODO change JSON field to "reviewed" in 3.0 + Reviewed int64 `json:"reviewed" xorm:"reviewed"` Workflows []*Workflow `json:"workflows,omitempty" xorm:"-"` ChangedFiles []string `json:"changed_files,omitempty" xorm:"LONGTEXT 'changed_files'"` AdditionalVariables map[string]string `json:"variables,omitempty" xorm:"json 'additional_variables'"` PullRequestLabels []string `json:"pr_labels,omitempty" xorm:"json 'pr_labels'"` IsPrerelease bool `json:"is_prerelease,omitempty" xorm:"is_prerelease"` + FromFork bool `json:"from_fork,omitempty" xorm:"from_fork"` } // @name Pipeline // TableName return database table name for xorm. @@ -60,8 +61,12 @@ func (Pipeline) TableName() string { } type PipelineFilter struct { - Before int64 - After int64 + Before int64 + After int64 + Branch string + Events []WebhookEvent + RefContains string + Status StatusValue } // IsMultiPipeline checks if step list contain more than one parent step. diff --git a/server/model/repo.go b/server/model/repo.go index 8cbdb86db..e61646216 100644 --- a/server/model/repo.go +++ b/server/model/repo.go @@ -20,37 +20,57 @@ import ( "strings" ) +type ApprovalMode string + +const ( + RequireApprovalNone ApprovalMode = "none" // require approval for no events + RequireApprovalForks ApprovalMode = "forks" // require approval for PRs from forks (default) + RequireApprovalPullRequests ApprovalMode = "pull_requests" // require approval for all PRs + RequireApprovalAllEvents ApprovalMode = "all_events" // require approval for all external events +) + +func (mode ApprovalMode) Valid() bool { + switch mode { + case RequireApprovalNone, + RequireApprovalForks, + RequireApprovalPullRequests, + RequireApprovalAllEvents: + return true + default: + return false + } +} + // Repo represents a repository. type Repo struct { ID int64 `json:"id,omitempty" xorm:"pk autoincr 'id'"` - UserID int64 `json:"-" xorm:"user_id"` + UserID int64 `json:"-" xorm:"INDEX 'user_id'"` ForgeID int64 `json:"forge_id,omitempty" xorm:"forge_id"` // ForgeRemoteID is the unique identifier for the repository on the forge. - ForgeRemoteID ForgeRemoteID `json:"forge_remote_id" xorm:"forge_remote_id"` - OrgID int64 `json:"org_id" xorm:"org_id"` - Owner string `json:"owner" xorm:"UNIQUE(name) 'owner'"` - Name string `json:"name" xorm:"UNIQUE(name) 'name'"` - FullName string `json:"full_name" xorm:"UNIQUE 'full_name'"` - Avatar string `json:"avatar_url,omitempty" xorm:"varchar(500) 'avatar'"` - ForgeURL string `json:"forge_url,omitempty" xorm:"varchar(1000) 'forge_url'"` - Clone string `json:"clone_url,omitempty" xorm:"varchar(1000) 'clone'"` - CloneSSH string `json:"clone_url_ssh" xorm:"varchar(1000) 'clone_ssh'"` - Branch string `json:"default_branch,omitempty" xorm:"varchar(500) 'branch'"` - SCMKind SCMKind `json:"scm,omitempty" xorm:"varchar(50) 'scm'"` - PREnabled bool `json:"pr_enabled" xorm:"DEFAULT TRUE 'pr_enabled'"` - Timeout int64 `json:"timeout,omitempty" xorm:"timeout"` - Visibility RepoVisibility `json:"visibility" xorm:"varchar(10) 'visibility'"` - IsSCMPrivate bool `json:"private" xorm:"private"` - IsTrusted bool `json:"trusted" xorm:"trusted"` - IsGated bool `json:"gated" xorm:"gated"` - IsActive bool `json:"active" xorm:"active"` - AllowPull bool `json:"allow_pr" xorm:"allow_pr"` - AllowDeploy bool `json:"allow_deploy" xorm:"allow_deploy"` - Config string `json:"config_file" xorm:"varchar(500) 'config_path'"` - Hash string `json:"-" xorm:"varchar(500) 'hash'"` - Perm *Perm `json:"-" xorm:"-"` - CancelPreviousPipelineEvents []WebhookEvent `json:"cancel_previous_pipeline_events" xorm:"json 'cancel_previous_pipeline_events'"` - NetrcOnlyTrusted bool `json:"netrc_only_trusted" xorm:"NOT NULL DEFAULT true 'netrc_only_trusted'"` + ForgeRemoteID ForgeRemoteID `json:"forge_remote_id" xorm:"forge_remote_id"` + OrgID int64 `json:"org_id" xorm:"INDEX 'org_id'"` + Owner string `json:"owner" xorm:"UNIQUE(name) 'owner'"` + Name string `json:"name" xorm:"UNIQUE(name) 'name'"` + FullName string `json:"full_name" xorm:"UNIQUE 'full_name'"` + Avatar string `json:"avatar_url,omitempty" xorm:"varchar(500) 'avatar'"` + ForgeURL string `json:"forge_url,omitempty" xorm:"varchar(1000) 'forge_url'"` + Clone string `json:"clone_url,omitempty" xorm:"varchar(1000) 'clone'"` + CloneSSH string `json:"clone_url_ssh" xorm:"varchar(1000) 'clone_ssh'"` + Branch string `json:"default_branch,omitempty" xorm:"varchar(500) 'branch'"` + PREnabled bool `json:"pr_enabled" xorm:"DEFAULT TRUE 'pr_enabled'"` + Timeout int64 `json:"timeout,omitempty" xorm:"timeout"` + Visibility RepoVisibility `json:"visibility" xorm:"varchar(10) 'visibility'"` + IsSCMPrivate bool `json:"private" xorm:"private"` + Trusted TrustedConfiguration `json:"trusted" xorm:"json 'trusted'"` + RequireApproval ApprovalMode `json:"require_approval" xorm:"varchar(50) require_approval"` + IsActive bool `json:"active" xorm:"active"` + AllowPull bool `json:"allow_pr" xorm:"allow_pr"` + AllowDeploy bool `json:"allow_deploy" xorm:"allow_deploy"` + Config string `json:"config_file" xorm:"varchar(500) 'config_path'"` + Hash string `json:"-" xorm:"varchar(500) 'hash'"` + Perm *Perm `json:"-" xorm:"-"` + CancelPreviousPipelineEvents []WebhookEvent `json:"cancel_previous_pipeline_events" xorm:"json 'cancel_previous_pipeline_events'"` + NetrcTrustedPlugins []string `json:"netrc_trusted" xorm:"json 'netrc_trusted'"` } // @name Repo // TableName return database table name for xorm. @@ -87,7 +107,6 @@ func (r *Repo) Update(from *Repo) { r.FullName = from.FullName r.Avatar = from.Avatar r.ForgeURL = from.ForgeURL - r.SCMKind = from.SCMKind r.PREnabled = from.PREnabled if len(from.Clone) > 0 { r.Clone = from.Clone @@ -108,15 +127,15 @@ func (r *Repo) Update(from *Repo) { // RepoPatch represents a repository patch object. type RepoPatch struct { - Config *string `json:"config_file,omitempty"` - IsTrusted *bool `json:"trusted,omitempty"` - IsGated *bool `json:"gated,omitempty"` - Timeout *int64 `json:"timeout,omitempty"` - Visibility *string `json:"visibility,omitempty"` - AllowPull *bool `json:"allow_pr,omitempty"` - AllowDeploy *bool `json:"allow_deploy,omitempty"` - CancelPreviousPipelineEvents *[]WebhookEvent `json:"cancel_previous_pipeline_events"` - NetrcOnlyTrusted *bool `json:"netrc_only_trusted"` + Config *string `json:"config_file,omitempty"` + RequireApproval *string `json:"require_approval,omitempty"` + Timeout *int64 `json:"timeout,omitempty"` + Visibility *string `json:"visibility,omitempty"` + AllowPull *bool `json:"allow_pr,omitempty"` + AllowDeploy *bool `json:"allow_deploy,omitempty"` + CancelPreviousPipelineEvents *[]WebhookEvent `json:"cancel_previous_pipeline_events"` + NetrcTrusted *[]string `json:"netrc_trusted"` + Trusted *TrustedConfigurationPatch `json:"trusted"` } // @name RepoPatch type ForgeRemoteID string @@ -124,3 +143,15 @@ type ForgeRemoteID string func (r ForgeRemoteID) IsValid() bool { return r != "" && r != "0" } + +type TrustedConfiguration struct { + Network bool `json:"network"` + Volumes bool `json:"volumes"` + Security bool `json:"security"` +} + +type TrustedConfigurationPatch struct { + Network *bool `json:"network"` + Volumes *bool `json:"volumes"` + Security *bool `json:"security"` +} diff --git a/server/model/resource_limit.go b/server/model/resource_limit.go deleted file mode 100644 index 90a302993..000000000 --- a/server/model/resource_limit.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2018 Drone.IO Inc. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package model - -// ResourceLimit is the resource limit to set on pipeline steps. -type ResourceLimit struct { - MemSwapLimit int64 - MemLimit int64 - ShmSize int64 - CPUQuota int64 - CPUShares int64 - CPUSet string -} diff --git a/server/model/secret_test.go b/server/model/secret_test.go index 915b432b0..d1195f16b 100644 --- a/server/model/secret_test.go +++ b/server/model/secret_test.go @@ -17,90 +17,90 @@ package model import ( "testing" - "github.com/franela/goblin" + "github.com/stretchr/testify/assert" ) func TestSecretValidate(t *testing.T) { - g := goblin.Goblin(t) - g.Describe("Secret", func() { - g.It("should pass validation", func() { - secret := Secret{ + tests := []struct { + s Secret + err bool + }{ + { + s: Secret{ Name: "secretname", Value: "secretvalue", Events: []WebhookEvent{EventPush}, Images: []string{"docker.io/library/mysql:latest", "alpine:latest", "localregistry.test:8443/mysql:latest", "localregistry.test:8443/library/mysql:latest", "docker.io/library/mysql", "alpine", "localregistry.test:8443/mysql", "localregistry.test:8443/library/mysql"}, - } - err := secret.Validate() - g.Assert(err).IsNil() - }) - g.Describe("should fail validation", func() { - g.It("when no name", func() { - secret := Secret{ - Value: "secretvalue", - Events: []WebhookEvent{EventPush}, - Images: []string{"docker.io/library/mysql:latest", "alpine:latest", "localregistry.test:8443/mysql:latest", "localregistry.test:8443/library/mysql:latest", "docker.io/library/mysql", "alpine", "localregistry.test:8443/mysql", "localregistry.test:8443/library/mysql"}, - } - err := secret.Validate() - g.Assert(err).IsNotNil() - }) - g.It("when no value", func() { - secret := Secret{ - Name: "secretname", - Events: []WebhookEvent{EventPush}, - Images: []string{"docker.io/library/mysql:latest", "alpine:latest", "localregistry.test:8443/mysql:latest", "localregistry.test:8443/library/mysql:latest", "docker.io/library/mysql", "alpine", "localregistry.test:8443/mysql", "localregistry.test:8443/library/mysql"}, - } - err := secret.Validate() - g.Assert(err).IsNotNil() - }) - g.It("when no events", func() { - secret := Secret{ - Name: "secretname", - Value: "secretvalue", - Images: []string{"docker.io/library/mysql:latest", "alpine:latest", "localregistry.test:8443/mysql:latest", "localregistry.test:8443/library/mysql:latest", "docker.io/library/mysql", "alpine", "localregistry.test:8443/mysql", "localregistry.test:8443/library/mysql"}, - } - err := secret.Validate() - g.Assert(err).IsNotNil() - }) - g.It("wrong image: no value", func() { - secret := Secret{ - Name: "secretname", - Value: "secretvalue", - Events: []WebhookEvent{EventPush}, - Images: []string{"wrong image:no"}, - } - err := secret.Validate() - g.Assert(err).IsNotNil() - }) - g.It("wrong image: no hostname", func() { - secret := Secret{ - Name: "secretname", - Value: "secretvalue", - Events: []WebhookEvent{EventPush}, - Images: []string{"/library/mysql:latest", ":8443/mysql:latest", ":8443/library/mysql:latest", "/library/mysql", ":8443/mysql", ":8443/library/mysql"}, - } - err := secret.Validate() - g.Assert(err).IsNotNil() - }) - g.It("wrong image: no port number", func() { - secret := Secret{ - Name: "secretname", - Value: "secretvalue", - Events: []WebhookEvent{EventPush}, - Images: []string{"localregistry.test:/mysql:latest", "localregistry.test:/mysql"}, - } - err := secret.Validate() - g.Assert(err).IsNotNil() - }) - g.It("wrong image: no tag name", func() { - secret := Secret{ - Name: "secretname", - Value: "secretvalue", - Events: []WebhookEvent{EventPush}, - Images: []string{"docker.io/library/mysql:", "alpine:", "localregistry.test:8443/mysql:", "localregistry.test:8443/library/mysql:"}, - } - err := secret.Validate() - g.Assert(err).IsNotNil() - }) - }) - }) + }, + err: false, + }, + { + s: Secret{ + Value: "secretvalue", + Events: []WebhookEvent{EventPush}, + Images: []string{"docker.io/library/mysql:latest", "alpine:latest", "localregistry.test:8443/mysql:latest", "localregistry.test:8443/library/mysql:latest", "docker.io/library/mysql", "alpine", "localregistry.test:8443/mysql", "localregistry.test:8443/library/mysql"}, + }, + err: true, + }, + { + s: Secret{ + Name: "secretname", + Events: []WebhookEvent{EventPush}, + Images: []string{"docker.io/library/mysql:latest", "alpine:latest", "localregistry.test:8443/mysql:latest", "localregistry.test:8443/library/mysql:latest", "docker.io/library/mysql", "alpine", "localregistry.test:8443/mysql", "localregistry.test:8443/library/mysql"}, + }, + err: true, + }, + { + s: Secret{ + Name: "secretname", + Value: "secretvalue", + Images: []string{"docker.io/library/mysql:latest", "alpine:latest", "localregistry.test:8443/mysql:latest", "localregistry.test:8443/library/mysql:latest", "docker.io/library/mysql", "alpine", "localregistry.test:8443/mysql", "localregistry.test:8443/library/mysql"}, + }, + err: true, + }, + { + s: Secret{ + Name: "secretname", + Value: "secretvalue", + Events: []WebhookEvent{EventPush}, + Images: []string{"wrong image:no"}, + }, + err: true, + }, + { + s: Secret{ + Name: "secretname", + Value: "secretvalue", + Events: []WebhookEvent{EventPush}, + Images: []string{"/library/mysql:latest", ":8443/mysql:latest", ":8443/library/mysql:latest", "/library/mysql", ":8443/mysql", ":8443/library/mysql"}, + }, + err: true, + }, + { + s: Secret{ + Name: "secretname", + Value: "secretvalue", + Events: []WebhookEvent{EventPush}, + Images: []string{"localregistry.test:/mysql:latest", "localregistry.test:/mysql"}, + }, + err: true, + }, + { + s: Secret{ + Name: "secretname", + Value: "secretvalue", + Events: []WebhookEvent{EventPush}, + Images: []string{"docker.io/library/mysql:", "alpine:", "localregistry.test:8443/mysql:", "localregistry.test:8443/library/mysql:"}, + }, + err: true, + }, + } + for i, tt := range tests { + err := tt.s.Validate() + if tt.err { + assert.Errorf(t, err, "expected secret validation error on index %d", i) + } else { + assert.NoErrorf(t, err, "unexpected secret validation error on index %d", i) + } + } } diff --git a/server/model/step.go b/server/model/step.go index 97189e733..dba0d3bac 100644 --- a/server/model/step.go +++ b/server/model/step.go @@ -36,8 +36,8 @@ type Step struct { Error string `json:"error,omitempty" xorm:"TEXT 'error'"` Failure string `json:"-" xorm:"failure"` ExitCode int `json:"exit_code" xorm:"exit_code"` - Started int64 `json:"start_time,omitempty" xorm:"started"` - Finished int64 `json:"end_time,omitempty" xorm:"stopped"` + Started int64 `json:"started,omitempty" xorm:"started"` + Finished int64 `json:"finished,omitempty" xorm:"finished"` Type StepType `json:"type,omitempty" xorm:"type"` } // @name Step diff --git a/server/model/task.go b/server/model/task.go index bd24ca665..58cf69c28 100644 --- a/server/model/task.go +++ b/server/model/task.go @@ -22,7 +22,7 @@ import ( // Task defines scheduled pipeline Task. type Task struct { ID string `json:"id" xorm:"PK UNIQUE 'id'"` - Data []byte `json:"data" xorm:"LONGBLOB 'data'"` + Data []byte `json:"-" xorm:"LONGBLOB 'data'"` Labels map[string]string `json:"labels" xorm:"json 'labels'"` Dependencies []string `json:"dependencies" xorm:"json 'dependencies'"` RunOn []string `json:"run_on" xorm:"json 'run_on'"` @@ -41,6 +41,18 @@ func (t *Task) String() string { return sb.String() } +func (t *Task) ApplyLabelsFromRepo(r *Repo) error { + if r == nil { + return fmt.Errorf("repo is nil but needed to get task labels") + } + if t.Labels == nil { + t.Labels = make(map[string]string) + } + t.Labels["repo"] = r.FullName + t.Labels[agentFilterOrgID] = fmt.Sprintf("%d", r.OrgID) + return nil +} + // ShouldRun tells if a task should be run or skipped, based on dependencies. func (t *Task) ShouldRun() bool { if t.runsOnFailure() && t.runsOnSuccess() { diff --git a/server/model/task_test.go b/server/model/task_test.go new file mode 100644 index 000000000..c774934e4 --- /dev/null +++ b/server/model/task_test.go @@ -0,0 +1,87 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package model + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestTask_GetLabels(t *testing.T) { + t.Run("Nil Repo", func(t *testing.T) { + task := &Task{} + err := task.ApplyLabelsFromRepo(nil) + + assert.Error(t, err) + assert.Nil(t, task.Labels) + assert.EqualError(t, err, "repo is nil but needed to get task labels") + }) + + t.Run("Empty Repo", func(t *testing.T) { + task := &Task{} + repo := &Repo{} + + err := task.ApplyLabelsFromRepo(repo) + + assert.NoError(t, err) + assert.NotNil(t, task.Labels) + assert.Equal(t, map[string]string{ + "repo": "", + agentFilterOrgID: "0", + }, task.Labels) + }) + + t.Run("Empty Labels", func(t *testing.T) { + task := &Task{} + repo := &Repo{ + FullName: "test/repo", + ID: 123, + OrgID: 456, + } + + err := task.ApplyLabelsFromRepo(repo) + + assert.NoError(t, err) + assert.NotNil(t, task.Labels) + assert.Equal(t, map[string]string{ + "repo": "test/repo", + agentFilterOrgID: "456", + }, task.Labels) + }) + + t.Run("Existing Labels", func(t *testing.T) { + task := &Task{ + Labels: map[string]string{ + "existing": "label", + }, + } + repo := &Repo{ + FullName: "test/repo", + ID: 123, + OrgID: 456, + } + + err := task.ApplyLabelsFromRepo(repo) + + assert.NoError(t, err) + assert.NotNil(t, task.Labels) + assert.Equal(t, map[string]string{ + "existing": "label", + "repo": "test/repo", + agentFilterOrgID: "456", + }, task.Labels) + }) +} diff --git a/server/model/user.go b/server/model/user.go index 78166b45e..4cc49b6c5 100644 --- a/server/model/user.go +++ b/server/model/user.go @@ -43,13 +43,13 @@ type User struct { // required: true Login string `json:"login" xorm:"UNIQUE 'login'"` - // Token is the oauth2 token. - Token string `json:"-" xorm:"TEXT 'token'"` + // AccessToken is the oauth2 access token. + AccessToken string `json:"-" xorm:"TEXT 'access_token'"` - // Secret is the oauth2 token secret. - Secret string `json:"-" xorm:"TEXT 'secret'"` + // RefreshToken is the oauth2 refresh token. + RefreshToken string `json:"-" xorm:"TEXT 'refresh_token'"` - // Expiry is the token and secret expiration timestamp. + // Expiry is the AccessToken expiration timestamp (unix seconds). Expiry int64 `json:"-" xorm:"expiry"` // Email is the email address for this user. diff --git a/server/model/workflow.go b/server/model/workflow.go index 7efd5f151..e6029ab69 100644 --- a/server/model/workflow.go +++ b/server/model/workflow.go @@ -23,8 +23,8 @@ type Workflow struct { Name string `json:"name" xorm:"name"` State StatusValue `json:"state" xorm:"state"` Error string `json:"error,omitempty" xorm:"TEXT 'error'"` - Started int64 `json:"start_time,omitempty" xorm:"started"` - Finished int64 `json:"end_time,omitempty" xorm:"stopped"` + Started int64 `json:"started,omitempty" xorm:"started"` + Finished int64 `json:"finished,omitempty" xorm:"finished"` AgentID int64 `json:"agent_id,omitempty" xorm:"agent_id"` Platform string `json:"platform,omitempty" xorm:"platform"` Environ map[string]string `json:"environ,omitempty" xorm:"json 'environ'"` diff --git a/server/pipeline/approve.go b/server/pipeline/approve.go index 374693a2c..d66b128ac 100644 --- a/server/pipeline/approve.go +++ b/server/pipeline/approve.go @@ -16,18 +16,18 @@ package pipeline import ( "context" + "errors" "fmt" "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server" - forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server" + forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) -// Approve update the status to pending for a blocked pipeline because of a gated repo -// and start them afterward. +// Approve update the status to pending for a blocked pipeline so it can be executed. func Approve(ctx context.Context, store store.Store, currentPipeline *model.Pipeline, user *model.User, repo *model.Repo) (*model.Pipeline, error) { if currentPipeline.Status != model.StatusBlocked { return nil, ErrBadRequest{Msg: fmt.Sprintf("cannot approve a pipeline with status %s", currentPipeline.Status)} @@ -37,7 +37,7 @@ func Approve(ctx context.Context, store store.Store, currentPipeline *model.Pipe if err != nil { msg := fmt.Sprintf("failure to load forge for repo '%s'", repo.FullName) log.Error().Err(err).Str("repo", repo.FullName).Msg(msg) - return nil, fmt.Errorf(msg) + return nil, errors.New(msg) } // fetch the pipeline file from the database @@ -84,7 +84,7 @@ func Approve(ctx context.Context, store store.Store, currentPipeline *model.Pipe if err != nil { msg := fmt.Sprintf("failure to createPipelineItems for %s", repo.FullName) log.Error().Err(err).Msg(msg) - return nil, fmt.Errorf(msg) + return nil, errors.New(msg) } // we have no way to link old workflows and steps in database to new engine generated steps, @@ -100,7 +100,7 @@ func Approve(ctx context.Context, store store.Store, currentPipeline *model.Pipe if err != nil { msg := fmt.Sprintf("failure to start pipeline for %s: %v", repo.FullName, err) log.Error().Err(err).Msg(msg) - return nil, fmt.Errorf(msg) + return nil, errors.New(msg) } return currentPipeline, nil diff --git a/server/pipeline/cancel.go b/server/pipeline/cancel.go index b2c2c0e03..f1ed9ece7 100644 --- a/server/pipeline/cancel.go +++ b/server/pipeline/cancel.go @@ -20,11 +20,11 @@ import ( "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/queue" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/queue" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) // Cancel the pipeline and returns the status. diff --git a/server/pipeline/config.go b/server/pipeline/config.go index f54066593..0e550d54c 100644 --- a/server/pipeline/config.go +++ b/server/pipeline/config.go @@ -15,10 +15,10 @@ package pipeline import ( - forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/pipeline/stepbuilder" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/pipeline/stepbuilder" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) func findOrPersistPipelineConfig(store store.Store, currentPipeline *model.Pipeline, forgeYamlConfig *forge_types.FileMeta) (*model.Config, error) { diff --git a/server/pipeline/create.go b/server/pipeline/create.go index 2692d1117..2a5ebb6da 100644 --- a/server/pipeline/create.go +++ b/server/pipeline/create.go @@ -22,12 +22,12 @@ import ( "github.com/rs/zerolog/log" - pipeline_errors "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + pipeline_errors "go.woodpecker-ci.org/woodpecker/v3/pipeline/errors" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) var skipPipelineRegex = regexp.MustCompile(`\[(?i:ci *skip|skip *ci)\]`) @@ -38,7 +38,7 @@ func Create(ctx context.Context, _store store.Store, repo *model.Repo, pipeline if err != nil { msg := fmt.Sprintf("failure to find repo owner via id '%d'", repo.UserID) log.Error().Err(err).Str("repo", repo.FullName).Msg(msg) - return nil, fmt.Errorf(msg) + return nil, errors.New(msg) } if pipeline.Event == model.EventPush || pipeline.Event == model.EventPull || pipeline.Event == model.EventPullClosed { @@ -57,7 +57,7 @@ func Create(ctx context.Context, _store store.Store, repo *model.Repo, pipeline if err != nil { msg := fmt.Sprintf("failure to load forge for repo '%s'", repo.FullName) log.Error().Err(err).Str("repo", repo.FullName).Msg(msg) - return nil, fmt.Errorf(msg) + return nil, errors.New(msg) } // If the forge has a refresh token, the current access token @@ -68,7 +68,7 @@ func Create(ctx context.Context, _store store.Store, repo *model.Repo, pipeline // update some pipeline fields pipeline.RepoID = repo.ID pipeline.Status = model.StatusCreated - setGatedState(repo, pipeline) + setApprovalState(repo, pipeline) err = _store.CreatePipeline(pipeline) if err != nil { msg := fmt.Errorf("failed to save pipeline for %s", repo.FullName) @@ -87,8 +87,8 @@ func Create(ctx context.Context, _store store.Store, repo *model.Repo, pipeline return nil, ErrFiltered } else if configFetchErr != nil { - log.Debug().Str("repo", repo.FullName).Err(configFetchErr).Msgf("error while fetching config '%s' in '%s' with user: '%s'", repo.Config, pipeline.Ref, repoUser.Login) - return nil, updatePipelineWithErr(ctx, _forge, _store, pipeline, repo, repoUser, fmt.Errorf("pipeline definition not found in %s", repo.FullName)) + log.Error().Str("repo", repo.FullName).Err(configFetchErr).Msgf("error while fetching config '%s' in '%s' with user: '%s'", repo.Config, pipeline.Ref, repoUser.Login) + return nil, updatePipelineWithErr(ctx, _forge, _store, pipeline, repo, repoUser, fmt.Errorf("could not load config from forge: %w", err)) } pipelineItems, parseErr := parsePipeline(_forge, _store, pipeline, repoUser, repo, forgeYamlConfigs, nil) @@ -117,7 +117,7 @@ func Create(ctx context.Context, _store store.Store, repo *model.Repo, pipeline if err != nil { msg := fmt.Sprintf("failed to find or persist pipeline config for %s", repo.FullName) log.Error().Err(err).Msg(msg) - return nil, fmt.Errorf(msg) + return nil, errors.New(msg) } configs = append(configs, config) } @@ -125,7 +125,7 @@ func Create(ctx context.Context, _store store.Store, repo *model.Repo, pipeline if err := linkPipelineConfigs(_store, configs, pipeline.ID); err != nil { msg := fmt.Sprintf("failed to find or persist pipeline config for %s", repo.FullName) log.Error().Err(err).Msg(msg) - return nil, fmt.Errorf(msg) + return nil, errors.New(msg) } if err := prepareStart(ctx, _forge, _store, pipeline, repoUser, repo); err != nil { @@ -145,7 +145,7 @@ func Create(ctx context.Context, _store store.Store, repo *model.Repo, pipeline if err != nil { msg := fmt.Sprintf("failed to start pipeline for %s", repo.FullName) log.Error().Err(err).Msg(msg) - return nil, fmt.Errorf(msg) + return nil, errors.New(msg) } return pipeline, nil diff --git a/server/pipeline/decline.go b/server/pipeline/decline.go index 1f9611c5c..d717f878f 100644 --- a/server/pipeline/decline.go +++ b/server/pipeline/decline.go @@ -16,22 +16,23 @@ package pipeline import ( "context" + "errors" "fmt" "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) -// Decline updates the status to declined for blocked pipelines because of a gated repo. +// Decline updates the status to declined for blocked pipelines. func Decline(ctx context.Context, store store.Store, pipeline *model.Pipeline, user *model.User, repo *model.Repo) (*model.Pipeline, error) { forge, err := server.Config.Services.Manager.ForgeFromRepo(repo) if err != nil { msg := fmt.Sprintf("failure to load forge for repo '%s'", repo.FullName) log.Error().Err(err).Str("repo", repo.FullName).Msg(msg) - return nil, fmt.Errorf(msg) + return nil, errors.New(msg) } if pipeline.Status != model.StatusBlocked { diff --git a/server/pipeline/gated.go b/server/pipeline/gated.go index ef424e720..4a4f3dc36 100644 --- a/server/pipeline/gated.go +++ b/server/pipeline/gated.go @@ -14,13 +14,44 @@ package pipeline -import "go.woodpecker-ci.org/woodpecker/v2/server/model" +import "go.woodpecker-ci.org/woodpecker/v3/server/model" -func setGatedState(repo *model.Repo, pipeline *model.Pipeline) { - // TODO(336): extend gated feature with an allow/block List - if repo.IsGated && - // events created by woodpecker itself should run right away - pipeline.Event != model.EventCron && pipeline.Event != model.EventManual { - pipeline.Status = model.StatusBlocked +func setApprovalState(repo *model.Repo, pipeline *model.Pipeline) { + if !needsApproval(repo, pipeline) { + return } + + // set pipeline status to blocked and require approval + pipeline.Status = model.StatusBlocked +} + +func needsApproval(repo *model.Repo, pipeline *model.Pipeline) bool { + // skip events created by woodpecker itself + if pipeline.Event == model.EventCron || pipeline.Event == model.EventManual { + return false + } + + switch repo.RequireApproval { + // repository allows all events without approval + case model.RequireApprovalNone: + return false + + // repository requires approval for pull requests from forks + case model.RequireApprovalForks: + if pipeline.Event == model.EventPull && pipeline.FromFork { + return true + } + + // repository requires approval for pull requests + case model.RequireApprovalPullRequests: + if pipeline.Event == model.EventPull { + return true + } + + // repository requires approval for all events + case model.RequireApprovalAllEvents: + return true + } + + return false } diff --git a/server/pipeline/gated_test.go b/server/pipeline/gated_test.go new file mode 100644 index 000000000..76004ff3d --- /dev/null +++ b/server/pipeline/gated_test.go @@ -0,0 +1,78 @@ +package pipeline + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "go.woodpecker-ci.org/woodpecker/v3/server/model" +) + +func TestSetGatedState(t *testing.T) { + t.Parallel() + + testCases := []struct { + name string + repo *model.Repo + pipeline *model.Pipeline + expectBlocked bool + }{ + { + name: "by-pass for cron", + repo: &model.Repo{ + RequireApproval: model.RequireApprovalAllEvents, + }, + pipeline: &model.Pipeline{ + Event: model.EventCron, + }, + expectBlocked: false, + }, + { + name: "by-pass for manual pipeline", + repo: &model.Repo{ + RequireApproval: model.RequireApprovalAllEvents, + }, + pipeline: &model.Pipeline{ + Event: model.EventManual, + }, + expectBlocked: false, + }, + { + name: "require approval for fork PRs", + repo: &model.Repo{ + RequireApproval: model.RequireApprovalForks, + }, + pipeline: &model.Pipeline{ + Event: model.EventPull, + FromFork: true, + }, + expectBlocked: true, + }, + { + name: "require approval for PRs", + repo: &model.Repo{ + RequireApproval: model.RequireApprovalPullRequests, + }, + pipeline: &model.Pipeline{ + Event: model.EventPull, + FromFork: false, + }, + expectBlocked: true, + }, + { + name: "require approval for everything", + repo: &model.Repo{ + RequireApproval: model.RequireApprovalAllEvents, + }, + pipeline: &model.Pipeline{ + Event: model.EventPush, + }, + expectBlocked: true, + }, + } + + for _, tc := range testCases { + setApprovalState(tc.repo, tc.pipeline) + assert.Equal(t, tc.expectBlocked, tc.pipeline.Status == model.StatusBlocked) + } +} diff --git a/server/pipeline/helper.go b/server/pipeline/helper.go index e8e3400a5..bc1417793 100644 --- a/server/pipeline/helper.go +++ b/server/pipeline/helper.go @@ -19,8 +19,8 @@ import ( "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func updatePipelineStatus(ctx context.Context, forge forge.Forge, pipeline *model.Pipeline, repo *model.Repo, user *model.User) { diff --git a/server/pipeline/items.go b/server/pipeline/items.go index b6b2dc917..72080414b 100644 --- a/server/pipeline/items.go +++ b/server/pipeline/items.go @@ -21,14 +21,14 @@ import ( "github.com/rs/zerolog/log" - pipeline_errors "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/compiler" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/pipeline/stepbuilder" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + pipeline_errors "go.woodpecker-ci.org/woodpecker/v3/pipeline/errors" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/compiler" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/pipeline/stepbuilder" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) func parsePipeline(forge forge.Forge, store store.Store, currentPipeline *model.Pipeline, user *model.User, repo *model.Repo, yamls []*forge_types.FileMeta, envs map[string]string) ([]*stepbuilder.Item, error) { @@ -38,7 +38,7 @@ func parsePipeline(forge forge.Forge, store store.Store, currentPipeline *model. } // get the previous pipeline so that we can send status change notifications - last, err := store.GetPipelineLastBefore(repo, currentPipeline.Branch, currentPipeline.ID) + prev, err := store.GetPipelineLastBefore(repo, currentPipeline.Branch, currentPipeline.ID) if err != nil && !errors.Is(err, sql.ErrNoRows) { log.Error().Err(err).Str("repo", repo.FullName).Msgf("error getting last pipeline before pipeline number '%d'", currentPipeline.Number) } @@ -72,16 +72,17 @@ func parsePipeline(forge forge.Forge, store store.Store, currentPipeline *model. } b := stepbuilder.StepBuilder{ - Repo: repo, - Curr: currentPipeline, - Last: last, - Netrc: netrc, - Secs: secs, - Regs: regs, - Envs: envs, - Host: server.Config.Server.Host, - Yamls: yamls, - Forge: forge, + Repo: repo, + Curr: currentPipeline, + Prev: prev, + Netrc: netrc, + Secs: secs, + Regs: regs, + Envs: envs, + Host: server.Config.Server.Host, + Yamls: yamls, + Forge: forge, + DefaultLabels: server.Config.Pipeline.DefaultWorkflowLabels, ProxyOpts: compiler.ProxyOptions{ NoProxy: server.Config.Pipeline.Proxy.No, HTTPProxy: server.Config.Pipeline.Proxy.HTTP, diff --git a/server/pipeline/items_test.go b/server/pipeline/items_test.go index bec0c2337..53b9db126 100644 --- a/server/pipeline/items_test.go +++ b/server/pipeline/items_test.go @@ -3,9 +3,9 @@ package pipeline import ( "testing" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - sharedPipeline "go.woodpecker-ci.org/woodpecker/v2/server/pipeline/stepbuilder" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + sharedPipeline "go.woodpecker-ci.org/woodpecker/v3/server/pipeline/stepbuilder" ) func TestSetPipelineStepsOnPipeline(t *testing.T) { diff --git a/server/pipeline/pipeline_status.go b/server/pipeline/pipeline_status.go index 95ed21817..fbac2776f 100644 --- a/server/pipeline/pipeline_status.go +++ b/server/pipeline/pipeline_status.go @@ -18,9 +18,9 @@ package pipeline import ( "time" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/errors" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) func UpdateToStatusRunning(store store.Store, pipeline model.Pipeline, started int64) (*model.Pipeline, error) { diff --git a/server/pipeline/pipeline_status_test.go b/server/pipeline/pipeline_status_test.go index ce4ba657b..1f9b87580 100644 --- a/server/pipeline/pipeline_status_test.go +++ b/server/pipeline/pipeline_status_test.go @@ -23,9 +23,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" - "go.woodpecker-ci.org/woodpecker/v2/server/store/mocks" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/store/mocks" ) func mockStorePipeline(t *testing.T) store.Store { diff --git a/server/pipeline/queue.go b/server/pipeline/queue.go index 73bf30fd8..002dbf3a9 100644 --- a/server/pipeline/queue.go +++ b/server/pipeline/queue.go @@ -18,11 +18,12 @@ import ( "context" "encoding/json" "fmt" + "maps" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/pipeline/stepbuilder" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/pipeline/stepbuilder" ) func queuePipeline(ctx context.Context, repo *model.Repo, pipelineItems []*stepbuilder.Item) error { @@ -31,18 +32,19 @@ func queuePipeline(ctx context.Context, repo *model.Repo, pipelineItems []*stepb if item.Workflow.State == model.StatusSkipped { continue } - task := new(model.Task) - task.ID = fmt.Sprint(item.Workflow.ID) - task.Labels = map[string]string{} - for k, v := range item.Labels { - task.Labels[k] = v + task := &model.Task{ + ID: fmt.Sprint(item.Workflow.ID), + Labels: make(map[string]string), + } + maps.Copy(task.Labels, item.Labels) + err := task.ApplyLabelsFromRepo(repo) + if err != nil { + return err } - task.Labels["repo"] = repo.FullName task.Dependencies = taskIDs(item.DependsOn, pipelineItems) task.RunOn = item.RunsOn task.DepStatus = make(map[string]model.StatusValue) - var err error task.Data, err = json.Marshal(rpc.Workflow{ ID: fmt.Sprint(item.Workflow.ID), Config: item.Config, diff --git a/server/pipeline/restart.go b/server/pipeline/restart.go index e8b841bf1..844f437b3 100644 --- a/server/pipeline/restart.go +++ b/server/pipeline/restart.go @@ -21,10 +21,10 @@ import ( "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server" - forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server" + forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) // Restart a pipeline by creating a new one out of the old and start it. @@ -33,13 +33,11 @@ func Restart(ctx context.Context, store store.Store, lastPipeline *model.Pipelin if err != nil { msg := fmt.Sprintf("failure to load forge for repo '%s'", repo.FullName) log.Error().Err(err).Str("repo", repo.FullName).Msg(msg) - return nil, fmt.Errorf(msg) + return nil, errors.New(msg) } - switch lastPipeline.Status { - case model.StatusDeclined, - model.StatusBlocked: - return nil, &ErrBadRequest{Msg: fmt.Sprintf("cannot restart a pipeline with status %s", lastPipeline.Status)} + if lastPipeline.Status == model.StatusBlocked { + return nil, &ErrBadRequest{Msg: "cannot restart a pipeline with status blocked"} } // fetch the old pipeline config from the database @@ -70,7 +68,7 @@ func Restart(ctx context.Context, store store.Store, lastPipeline *model.Pipelin if err != nil { msg := fmt.Sprintf("failure to save pipeline for %s", repo.FullName) log.Error().Err(err).Msg(msg) - return nil, fmt.Errorf(msg) + return nil, errors.New(msg) } if len(configs) == 0 { @@ -85,27 +83,27 @@ func Restart(ctx context.Context, store store.Store, lastPipeline *model.Pipelin if err := linkPipelineConfigs(store, configs, newPipeline.ID); err != nil { msg := fmt.Sprintf("failure to persist pipeline config for %s.", repo.FullName) log.Error().Err(err).Msg(msg) - return nil, fmt.Errorf(msg) + return nil, errors.New(msg) } newPipeline, pipelineItems, err := createPipelineItems(ctx, forge, store, newPipeline, user, repo, pipelineFiles, envs) if err != nil { msg := fmt.Sprintf("failure to createPipelineItems for %s", repo.FullName) log.Error().Err(err).Msg(msg) - return nil, fmt.Errorf(msg) + return nil, errors.New(msg) } if err := prepareStart(ctx, forge, store, newPipeline, user, repo); err != nil { msg := fmt.Sprintf("failure to prepare pipeline for %s", repo.FullName) log.Error().Err(err).Msg(msg) - return nil, fmt.Errorf(msg) + return nil, errors.New(msg) } newPipeline, err = start(ctx, forge, store, newPipeline, user, repo, pipelineItems) if err != nil { msg := fmt.Sprintf("failure to start pipeline for %s", repo.FullName) log.Error().Err(err).Msg(msg) - return nil, fmt.Errorf(msg) + return nil, errors.New(msg) } return newPipeline, nil diff --git a/server/pipeline/start.go b/server/pipeline/start.go index ae4a24a62..82625319f 100644 --- a/server/pipeline/start.go +++ b/server/pipeline/start.go @@ -19,10 +19,10 @@ import ( "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/pipeline/stepbuilder" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/pipeline/stepbuilder" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) // start a pipeline, make sure it was stored persistent in the store before. diff --git a/server/pipeline/step_status.go b/server/pipeline/step_status.go index d8f40fce2..8c1fe051d 100644 --- a/server/pipeline/step_status.go +++ b/server/pipeline/step_status.go @@ -18,10 +18,10 @@ package pipeline import ( "time" - "go.woodpecker-ci.org/woodpecker/v2/pipeline" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/pipeline" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) func UpdateStepStatus(store store.Store, step *model.Step, state rpc.StepState) error { @@ -49,7 +49,7 @@ func UpdateStepToStatusStarted(store store.Store, step model.Step, state rpc.Ste return &step, store.StepUpdate(&step) } -func UpdateStepToStatusSkipped(store store.Store, step model.Step, finished int64) (*model.Step, error) { +func UpdateStepStatusToSkipped(store store.Store, step model.Step, finished int64) (*model.Step, error) { step.State = model.StatusSkipped if step.Started != 0 { step.State = model.StatusSuccess // for daemons that are killed diff --git a/server/pipeline/step_status_test.go b/server/pipeline/step_status_test.go index 2f9dab685..a1713df73 100644 --- a/server/pipeline/step_status_test.go +++ b/server/pipeline/step_status_test.go @@ -22,11 +22,11 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "go.woodpecker-ci.org/woodpecker/v2/pipeline" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" - "go.woodpecker-ci.org/woodpecker/v2/server/store/mocks" + "go.woodpecker-ci.org/woodpecker/v3/pipeline" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/store/mocks" ) func mockStoreStep(t *testing.T) store.Store { diff --git a/server/pipeline/stepbuilder/metadata.go b/server/pipeline/stepbuilder/metadata.go index 6e48e4158..8ceb0dbcd 100644 --- a/server/pipeline/stepbuilder/metadata.go +++ b/server/pipeline/stepbuilder/metadata.go @@ -19,13 +19,13 @@ import ( "net/url" "strings" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/version" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/version" ) // MetadataFromStruct return the metadata from a pipeline will run with. -func MetadataFromStruct(forge metadata.ServerForge, repo *model.Repo, pipeline, last *model.Pipeline, workflow *model.Workflow, sysURL string) metadata.Metadata { +func MetadataFromStruct(forge metadata.ServerForge, repo *model.Repo, pipeline, prev *model.Pipeline, workflow *model.Workflow, sysURL string) metadata.Metadata { host := sysURL uri, err := url.Parse(sysURL) if err == nil { @@ -52,7 +52,11 @@ func MetadataFromStruct(forge metadata.ServerForge, repo *model.Repo, pipeline, CloneSSHURL: repo.CloneSSH, Private: repo.IsSCMPrivate, Branch: repo.Branch, - Trusted: repo.IsTrusted, + Trusted: metadata.TrustedConfiguration{ + Network: repo.Trusted.Network, + Volumes: repo.Trusted.Volumes, + Security: repo.Trusted.Security, + }, } if idx := strings.LastIndex(repo.FullName, "/"); idx != -1 { @@ -77,7 +81,7 @@ func MetadataFromStruct(forge metadata.ServerForge, repo *model.Repo, pipeline, return metadata.Metadata{ Repo: fRepo, Curr: metadataPipelineFromModelPipeline(pipeline, true), - Prev: metadataPipelineFromModelPipeline(last, false), + Prev: metadataPipelineFromModelPipeline(prev, false), Workflow: fWorkflow, Step: metadata.Step{}, Sys: metadata.System{ diff --git a/server/pipeline/stepbuilder/metadata_test.go b/server/pipeline/stepbuilder/metadata_test.go index a0d451425..c63680d94 100644 --- a/server/pipeline/stepbuilder/metadata_test.go +++ b/server/pipeline/stepbuilder/metadata_test.go @@ -19,9 +19,9 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/mocks" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/mocks" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func TestMetadataFromStruct(t *testing.T) { @@ -33,7 +33,7 @@ func TestMetadataFromStruct(t *testing.T) { name string forge metadata.ServerForge repo *model.Repo - pipeline, last *model.Pipeline + pipeline, prev *model.Pipeline workflow *model.Workflow sysURL string expectedMetadata metadata.Metadata @@ -43,19 +43,15 @@ func TestMetadataFromStruct(t *testing.T) { name: "Test with empty info", expectedMetadata: metadata.Metadata{Sys: metadata.System{Name: "woodpecker"}}, expectedEnviron: map[string]string{ - "CI": "woodpecker", - "CI_COMMIT_AUTHOR": "", "CI_COMMIT_AUTHOR_AVATAR": "", "CI_COMMIT_AUTHOR_EMAIL": "", "CI_COMMIT_BRANCH": "", - "CI_COMMIT_MESSAGE": "", "CI_COMMIT_PULL_REQUEST": "", "CI_COMMIT_PULL_REQUEST_LABELS": "", "CI_COMMIT_REF": "", "CI_COMMIT_REFSPEC": "", "CI_COMMIT_SHA": "", "CI_COMMIT_SOURCE_BRANCH": "", - "CI_COMMIT_TAG": "", "CI_COMMIT_TARGET_BRANCH": "", "CI_COMMIT_URL": "", "CI_FORGE_TYPE": "", "CI_FORGE_URL": "", - "CI_PIPELINE_CREATED": "0", "CI_PIPELINE_DEPLOY_TARGET": "", "CI_PIPELINE_DEPLOY_TASK": "", "CI_PIPELINE_EVENT": "", "CI_PIPELINE_FINISHED": "0", "CI_PIPELINE_FILES": "[]", "CI_PIPELINE_NUMBER": "0", - "CI_PIPELINE_PARENT": "0", "CI_PIPELINE_STARTED": "0", "CI_PIPELINE_STATUS": "", "CI_PIPELINE_URL": "/repos/0/pipeline/0", "CI_PIPELINE_FORGE_URL": "", - "CI_PREV_COMMIT_AUTHOR": "", "CI_PREV_COMMIT_AUTHOR_AVATAR": "", "CI_PREV_COMMIT_AUTHOR_EMAIL": "", "CI_PREV_COMMIT_BRANCH": "", - "CI_PREV_COMMIT_MESSAGE": "", "CI_PREV_COMMIT_REF": "", "CI_PREV_COMMIT_REFSPEC": "", "CI_PREV_COMMIT_SHA": "", "CI_PREV_COMMIT_URL": "", "CI_PREV_PIPELINE_CREATED": "0", - "CI_PREV_PIPELINE_DEPLOY_TARGET": "", "CI_PREV_PIPELINE_DEPLOY_TASK": "", "CI_PREV_PIPELINE_EVENT": "", "CI_PREV_PIPELINE_FINISHED": "0", "CI_PREV_PIPELINE_NUMBER": "0", "CI_PREV_PIPELINE_PARENT": "0", - "CI_PREV_PIPELINE_STARTED": "0", "CI_PREV_PIPELINE_STATUS": "", "CI_PREV_PIPELINE_URL": "/repos/0/pipeline/0", "CI_PREV_PIPELINE_FORGE_URL": "", "CI_REPO": "", "CI_REPO_CLONE_URL": "", "CI_REPO_CLONE_SSH_URL": "", "CI_REPO_DEFAULT_BRANCH": "", "CI_REPO_REMOTE_ID": "", - "CI_REPO_NAME": "", "CI_REPO_OWNER": "", "CI_REPO_PRIVATE": "false", "CI_REPO_SCM": "git", "CI_REPO_TRUSTED": "false", "CI_REPO_URL": "", "CI_STEP_FINISHED": "", - "CI_STEP_NAME": "", "CI_STEP_NUMBER": "0", "CI_STEP_STARTED": "", "CI_STEP_STATUS": "", "CI_STEP_URL": "/repos/0/pipeline/0", "CI_SYSTEM_HOST": "", "CI_SYSTEM_NAME": "woodpecker", - "CI_SYSTEM_PLATFORM": "", "CI_SYSTEM_URL": "", "CI_SYSTEM_VERSION": "", "CI_WORKFLOW_NAME": "", "CI_WORKFLOW_NUMBER": "0", + "CI": "woodpecker", + "CI_PIPELINE_CREATED": "0", "CI_PIPELINE_FILES": "[]", "CI_PIPELINE_NUMBER": "0", + "CI_PIPELINE_PARENT": "0", "CI_PIPELINE_STARTED": "0", "CI_PIPELINE_URL": "/repos/0/pipeline/0", + "CI_PREV_PIPELINE_CREATED": "0", + "CI_PREV_PIPELINE_FINISHED": "0", "CI_PREV_PIPELINE_NUMBER": "0", "CI_PREV_PIPELINE_PARENT": "0", + "CI_PREV_PIPELINE_STARTED": "0", "CI_PREV_PIPELINE_URL": "/repos/0/pipeline/0", + "CI_REPO_PRIVATE": "false", "CI_REPO_TRUSTED": "false", "CI_REPO_TRUSTED_NETWORK": "false", "CI_REPO_TRUSTED_SECURITY": "false", "CI_REPO_TRUSTED_VOLUMES": "false", + "CI_STEP_NUMBER": "0", "CI_STEP_URL": "/repos/0/pipeline/0", "CI_SYSTEM_NAME": "woodpecker", + "CI_WORKFLOW_NUMBER": "0", }, }, { @@ -63,7 +59,7 @@ func TestMetadataFromStruct(t *testing.T) { forge: forge, repo: &model.Repo{FullName: "testUser/testRepo", ForgeURL: "https://gitea.com/testUser/testRepo", Clone: "https://gitea.com/testUser/testRepo.git", CloneSSH: "git@gitea.com:testUser/testRepo.git", Branch: "main", IsSCMPrivate: true}, pipeline: &model.Pipeline{Number: 3, ChangedFiles: []string{"test.go", "markdown file.md"}}, - last: &model.Pipeline{Number: 2}, + prev: &model.Pipeline{Number: 2}, workflow: &model.Workflow{Name: "hello"}, sysURL: "https://example.com", expectedMetadata: metadata.Metadata{ @@ -78,27 +74,24 @@ func TestMetadataFromStruct(t *testing.T) { Workflow: metadata.Workflow{Name: "hello"}, }, expectedEnviron: map[string]string{ - "CI": "woodpecker", - "CI_COMMIT_AUTHOR": "", "CI_COMMIT_AUTHOR_AVATAR": "", "CI_COMMIT_AUTHOR_EMAIL": "", "CI_COMMIT_BRANCH": "", - "CI_COMMIT_MESSAGE": "", "CI_COMMIT_PULL_REQUEST": "", "CI_COMMIT_PULL_REQUEST_LABELS": "", "CI_COMMIT_REF": "", "CI_COMMIT_REFSPEC": "", "CI_COMMIT_SHA": "", "CI_COMMIT_SOURCE_BRANCH": "", - "CI_COMMIT_TAG": "", "CI_COMMIT_TARGET_BRANCH": "", "CI_COMMIT_URL": "", "CI_FORGE_TYPE": "gitea", "CI_FORGE_URL": "https://gitea.com", - "CI_PIPELINE_CREATED": "0", "CI_PIPELINE_DEPLOY_TARGET": "", "CI_PIPELINE_DEPLOY_TASK": "", "CI_PIPELINE_EVENT": "", "CI_PIPELINE_FINISHED": "0", "CI_PIPELINE_FILES": `["test.go","markdown file.md"]`, - "CI_PIPELINE_NUMBER": "3", "CI_PIPELINE_PARENT": "0", "CI_PIPELINE_STARTED": "0", "CI_PIPELINE_STATUS": "", "CI_PIPELINE_URL": "https://example.com/repos/0/pipeline/3", "CI_PIPELINE_FORGE_URL": "", - "CI_PREV_COMMIT_AUTHOR": "", "CI_PREV_COMMIT_AUTHOR_AVATAR": "", "CI_PREV_COMMIT_AUTHOR_EMAIL": "", "CI_PREV_COMMIT_BRANCH": "", - "CI_PREV_COMMIT_MESSAGE": "", "CI_PREV_COMMIT_REF": "", "CI_PREV_COMMIT_REFSPEC": "", "CI_PREV_COMMIT_SHA": "", "CI_PREV_COMMIT_URL": "", "CI_PREV_PIPELINE_CREATED": "0", - "CI_PREV_PIPELINE_DEPLOY_TARGET": "", "CI_PREV_PIPELINE_DEPLOY_TASK": "", "CI_PREV_PIPELINE_EVENT": "", "CI_PREV_PIPELINE_FINISHED": "0", "CI_PREV_PIPELINE_NUMBER": "2", "CI_PREV_PIPELINE_PARENT": "0", - "CI_PREV_PIPELINE_STARTED": "0", "CI_PREV_PIPELINE_STATUS": "", "CI_PREV_PIPELINE_URL": "https://example.com/repos/0/pipeline/2", "CI_PREV_PIPELINE_FORGE_URL": "", "CI_REPO": "testUser/testRepo", "CI_REPO_CLONE_URL": "https://gitea.com/testUser/testRepo.git", "CI_REPO_CLONE_SSH_URL": "git@gitea.com:testUser/testRepo.git", - "CI_REPO_DEFAULT_BRANCH": "main", "CI_REPO_NAME": "testRepo", "CI_REPO_OWNER": "testUser", "CI_REPO_PRIVATE": "true", "CI_REPO_REMOTE_ID": "", - "CI_REPO_SCM": "git", "CI_REPO_TRUSTED": "false", "CI_REPO_URL": "https://gitea.com/testUser/testRepo", "CI_STEP_FINISHED": "", - "CI_STEP_NAME": "", "CI_STEP_NUMBER": "0", "CI_STEP_STARTED": "", "CI_STEP_STATUS": "", "CI_STEP_URL": "https://example.com/repos/0/pipeline/3", "CI_SYSTEM_HOST": "example.com", - "CI_SYSTEM_NAME": "woodpecker", "CI_SYSTEM_PLATFORM": "", "CI_SYSTEM_URL": "https://example.com", "CI_SYSTEM_VERSION": "", "CI_WORKFLOW_NAME": "hello", "CI_WORKFLOW_NUMBER": "0", + "CI": "woodpecker", + "CI_FORGE_TYPE": "gitea", "CI_FORGE_URL": "https://gitea.com", + "CI_PIPELINE_CREATED": "0", "CI_PIPELINE_FILES": `["test.go","markdown file.md"]`, + "CI_PIPELINE_NUMBER": "3", "CI_PIPELINE_PARENT": "0", "CI_PIPELINE_STARTED": "0", "CI_PIPELINE_URL": "https://example.com/repos/0/pipeline/3", + "CI_PREV_PIPELINE_CREATED": "0", + "CI_PREV_PIPELINE_FINISHED": "0", "CI_PREV_PIPELINE_NUMBER": "2", "CI_PREV_PIPELINE_PARENT": "0", + "CI_PREV_PIPELINE_STARTED": "0", "CI_PREV_PIPELINE_URL": "https://example.com/repos/0/pipeline/2", "CI_REPO": "testUser/testRepo", "CI_REPO_CLONE_URL": "https://gitea.com/testUser/testRepo.git", "CI_REPO_CLONE_SSH_URL": "git@gitea.com:testUser/testRepo.git", + "CI_REPO_DEFAULT_BRANCH": "main", "CI_REPO_NAME": "testRepo", "CI_REPO_OWNER": "testUser", "CI_REPO_PRIVATE": "true", + "CI_REPO_TRUSTED": "false", "CI_REPO_TRUSTED_NETWORK": "false", "CI_REPO_TRUSTED_SECURITY": "false", "CI_REPO_TRUSTED_VOLUMES": "false", + "CI_REPO_URL": "https://gitea.com/testUser/testRepo", "CI_STEP_NUMBER": "0", "CI_STEP_URL": "https://example.com/repos/0/pipeline/3", "CI_SYSTEM_HOST": "example.com", + "CI_SYSTEM_NAME": "woodpecker", "CI_SYSTEM_URL": "https://example.com", "CI_WORKFLOW_NAME": "hello", "CI_WORKFLOW_NUMBER": "0", }, }, } for _, testCase := range testCases { t.Run(testCase.name, func(t *testing.T) { - result := MetadataFromStruct(testCase.forge, testCase.repo, testCase.pipeline, testCase.last, testCase.workflow, testCase.sysURL) + result := MetadataFromStruct(testCase.forge, testCase.repo, testCase.pipeline, testCase.prev, testCase.workflow, testCase.sysURL) assert.EqualValues(t, testCase.expectedMetadata, result) assert.EqualValues(t, testCase.expectedEnviron, result.Environ()) }) diff --git a/server/pipeline/stepbuilder/stepBuilder.go b/server/pipeline/stepbuilder/stepBuilder.go index 586fad595..3d0b55e7e 100644 --- a/server/pipeline/stepbuilder/stepBuilder.go +++ b/server/pipeline/stepbuilder/stepBuilder.go @@ -17,6 +17,7 @@ package stepbuilder import ( "fmt" + "maps" "path/filepath" "strings" @@ -24,33 +25,34 @@ import ( "github.com/rs/zerolog/log" "go.uber.org/multierr" - backend_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/backend/types" - pipeline_errors "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors" - errorTypes "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors/types" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/metadata" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/compiler" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/linter" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/matrix" - yaml_types "go.woodpecker-ci.org/woodpecker/v2/pipeline/frontend/yaml/types" - "go.woodpecker-ci.org/woodpecker/v2/server" - forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + backend_types "go.woodpecker-ci.org/woodpecker/v3/pipeline/backend/types" + pipeline_errors "go.woodpecker-ci.org/woodpecker/v3/pipeline/errors" + errorTypes "go.woodpecker-ci.org/woodpecker/v3/pipeline/errors/types" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/metadata" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/compiler" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/linter" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/matrix" + yaml_types "go.woodpecker-ci.org/woodpecker/v3/pipeline/frontend/yaml/types" + "go.woodpecker-ci.org/woodpecker/v3/server" + forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) // StepBuilder Takes the hook data and the yaml and returns in internal data model. type StepBuilder struct { - Repo *model.Repo - Curr *model.Pipeline - Last *model.Pipeline - Netrc *model.Netrc - Secs []*model.Secret - Regs []*model.Registry - Host string - Yamls []*forge_types.FileMeta - Envs map[string]string - Forge metadata.ServerForge - ProxyOpts compiler.ProxyOptions + Repo *model.Repo + Curr *model.Pipeline + Prev *model.Pipeline + Netrc *model.Netrc + Secs []*model.Secret + Regs []*model.Registry + Host string + Yamls []*forge_types.FileMeta + Envs map[string]string + Forge metadata.ServerForge + DefaultLabels map[string]string + ProxyOpts compiler.ProxyOptions } type Item struct { @@ -115,7 +117,7 @@ func (b *StepBuilder) Build() (items []*Item, errorsAndWarnings error) { } func (b *StepBuilder) genItemForWorkflow(workflow *model.Workflow, axis matrix.Axis, data string) (item *Item, errorsAndWarnings error) { - workflowMetadata := MetadataFromStruct(b.Forge, b.Repo, b.Curr, b.Last, workflow, b.Host) + workflowMetadata := MetadataFromStruct(b.Forge, b.Repo, b.Curr, b.Prev, workflow, b.Host) environ := b.environmentVariables(workflowMetadata, axis) // add global environment variables for substituting @@ -141,7 +143,13 @@ func (b *StepBuilder) genItemForWorkflow(workflow *model.Workflow, axis matrix.A // lint pipeline errorsAndWarnings = multierr.Append(errorsAndWarnings, linter.New( - linter.WithTrusted(b.Repo.IsTrusted), + linter.WithTrusted(linter.TrustedConfiguration{ + Network: b.Repo.Trusted.Network, + Volumes: b.Repo.Trusted.Volumes, + Security: b.Repo.Trusted.Security, + }), + linter.PrivilegedPlugins(server.Config.Pipeline.PrivilegedPlugins), + linter.WithTrustedClonePlugins(server.Config.Pipeline.TrustedClonePlugins), ).Lint([]*linter.WorkflowConfig{{ Workflow: parsed, File: workflow.Name, @@ -180,8 +188,10 @@ func (b *StepBuilder) genItemForWorkflow(workflow *model.Workflow, axis matrix.A DependsOn: parsed.DependsOn, RunsOn: parsed.RunsOn, } - if item.Labels == nil { - item.Labels = map[string]string{} + if len(item.Labels) == 0 { + item.Labels = make(map[string]string, len(b.DefaultLabels)) + // Set default labels if no labels are defined in the pipeline + maps.Copy(item.Labels, b.DefaultLabels) } return item, errorsAndWarnings @@ -267,8 +277,7 @@ func (b *StepBuilder) toInternalRepresentation(parsed *yaml_types.Workflow, envi compiler.WithEnviron(environ), compiler.WithEnviron(b.Envs), // TODO: server deps should be moved into StepBuilder fields and set on StepBuilder creation - compiler.WithEscalated(server.Config.Pipeline.Privileged...), - compiler.WithResourceLimit(server.Config.Pipeline.Limits.MemSwapLimit, server.Config.Pipeline.Limits.MemLimit, server.Config.Pipeline.Limits.ShmSize, server.Config.Pipeline.Limits.CPUQuota, server.Config.Pipeline.Limits.CPUShares, server.Config.Pipeline.Limits.CPUSet), + compiler.WithEscalated(server.Config.Pipeline.PrivilegedPlugins...), compiler.WithVolumes(server.Config.Pipeline.Volumes...), compiler.WithNetworks(server.Config.Pipeline.Networks...), compiler.WithLocal(false), @@ -280,7 +289,8 @@ func (b *StepBuilder) toInternalRepresentation(parsed *yaml_types.Workflow, envi ), b.Repo.IsSCMPrivate || server.Config.Pipeline.AuthenticatePublicRepos, ), - compiler.WithDefaultCloneImage(server.Config.Pipeline.DefaultCloneImage), + compiler.WithDefaultClonePlugin(server.Config.Pipeline.DefaultClonePlugin), + compiler.WithTrustedClonePlugins(append(b.Repo.NetrcTrustedPlugins, server.Config.Pipeline.TrustedClonePlugins...)), compiler.WithRegistry(registries...), compiler.WithSecret(secrets...), compiler.WithPrefix( @@ -291,10 +301,9 @@ func (b *StepBuilder) toInternalRepresentation(parsed *yaml_types.Workflow, envi ), ), compiler.WithProxy(b.ProxyOpts), - compiler.WithWorkspaceFromURL("/woodpecker", b.Repo.ForgeURL), + compiler.WithWorkspaceFromURL(compiler.DefaultWorkspaceBase, b.Repo.ForgeURL), compiler.WithMetadata(metadata), - compiler.WithTrusted(b.Repo.IsTrusted), - compiler.WithNetrcOnlyTrusted(b.Repo.NetrcOnlyTrusted), + compiler.WithTrustedSecurity(b.Repo.Trusted.Security), ).Compile(parsed) } diff --git a/server/pipeline/stepbuilder/stepBuilder_test.go b/server/pipeline/stepbuilder/stepBuilder_test.go index 98cb79129..bca38e798 100644 --- a/server/pipeline/stepbuilder/stepBuilder_test.go +++ b/server/pipeline/stepbuilder/stepBuilder_test.go @@ -21,11 +21,11 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/mocks" - forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/errors" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/mocks" + forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func TestGlobalEnvsubst(t *testing.T) { @@ -42,7 +42,7 @@ func TestGlobalEnvsubst(t *testing.T) { Message: "aaa", Event: model.EventPush, }, - Last: &model.Pipeline{}, + Prev: &model.Pipeline{}, Netrc: &model.Netrc{}, Secs: []*model.Secret{}, Regs: []*model.Registry{}, @@ -81,7 +81,7 @@ func TestMissingGlobalEnvsubst(t *testing.T) { Message: "aaa", Event: model.EventPush, }, - Last: &model.Pipeline{}, + Prev: &model.Pipeline{}, Netrc: &model.Netrc{}, Secs: []*model.Secret{}, Regs: []*model.Registry{}, @@ -116,7 +116,7 @@ func TestMultilineEnvsubst(t *testing.T) { Message: `aaa bbb`, }, - Last: &model.Pipeline{}, + Prev: &model.Pipeline{}, Netrc: &model.Netrc{}, Secs: []*model.Secret{}, Regs: []*model.Registry{}, @@ -159,7 +159,7 @@ func TestMultiPipeline(t *testing.T) { Curr: &model.Pipeline{ Event: model.EventPush, }, - Last: &model.Pipeline{}, + Prev: &model.Pipeline{}, Netrc: &model.Netrc{}, Secs: []*model.Secret{}, Regs: []*model.Registry{}, @@ -200,7 +200,7 @@ func TestDependsOn(t *testing.T) { Curr: &model.Pipeline{ Event: model.EventPush, }, - Last: &model.Pipeline{}, + Prev: &model.Pipeline{}, Netrc: &model.Netrc{}, Secs: []*model.Secret{}, Regs: []*model.Registry{}, @@ -255,7 +255,7 @@ func TestRunsOn(t *testing.T) { Curr: &model.Pipeline{ Event: model.EventPush, }, - Last: &model.Pipeline{}, + Prev: &model.Pipeline{}, Netrc: &model.Netrc{}, Secs: []*model.Secret{}, Regs: []*model.Registry{}, @@ -296,7 +296,7 @@ func TestPipelineName(t *testing.T) { Curr: &model.Pipeline{ Event: model.EventPush, }, - Last: &model.Pipeline{}, + Prev: &model.Pipeline{}, Netrc: &model.Netrc{}, Secs: []*model.Secret{}, Regs: []*model.Registry{}, @@ -339,7 +339,7 @@ func TestBranchFilter(t *testing.T) { Branch: "dev", Event: model.EventPush, }, - Last: &model.Pipeline{}, + Prev: &model.Pipeline{}, Netrc: &model.Netrc{}, Secs: []*model.Secret{}, Regs: []*model.Registry{}, @@ -348,10 +348,10 @@ func TestBranchFilter(t *testing.T) { {Data: []byte(` when: event: push + branch: main steps: xxx: image: scratch -branches: main `)}, {Data: []byte(` when: @@ -382,7 +382,7 @@ func TestRootWhenFilter(t *testing.T) { Forge: getMockForge(t), Repo: &model.Repo{}, Curr: &model.Pipeline{Event: "tag"}, - Last: &model.Pipeline{}, + Prev: &model.Pipeline{}, Netrc: &model.Netrc{}, Secs: []*model.Secret{}, Regs: []*model.Registry{}, @@ -432,7 +432,7 @@ func TestZeroSteps(t *testing.T) { Forge: getMockForge(t), Repo: &model.Repo{}, Curr: pipeline, - Last: &model.Pipeline{}, + Prev: &model.Pipeline{}, Netrc: &model.Netrc{}, Secs: []*model.Secret{}, Regs: []*model.Registry{}, @@ -472,7 +472,7 @@ func TestZeroStepsAsMultiPipelineDeps(t *testing.T) { Forge: getMockForge(t), Repo: &model.Repo{}, Curr: pipeline, - Last: &model.Pipeline{}, + Prev: &model.Pipeline{}, Netrc: &model.Netrc{}, Secs: []*model.Secret{}, Regs: []*model.Registry{}, @@ -530,7 +530,7 @@ func TestZeroStepsAsMultiPipelineTransitiveDeps(t *testing.T) { Forge: getMockForge(t), Repo: &model.Repo{}, Curr: pipeline, - Last: &model.Pipeline{}, + Prev: &model.Pipeline{}, Netrc: &model.Netrc{}, Secs: []*model.Secret{}, Regs: []*model.Registry{}, diff --git a/server/pipeline/topic.go b/server/pipeline/topic.go index 26c9f432b..8bcb52efd 100644 --- a/server/pipeline/topic.go +++ b/server/pipeline/topic.go @@ -20,9 +20,9 @@ import ( "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/pubsub" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/pubsub" ) // publishToTopic publishes message to UI clients. diff --git a/server/pipeline/workflow_status.go b/server/pipeline/workflow_status.go index a99ef8ddd..12d2c15a7 100644 --- a/server/pipeline/workflow_status.go +++ b/server/pipeline/workflow_status.go @@ -15,9 +15,9 @@ package pipeline import ( - "go.woodpecker-ci.org/woodpecker/v2/pipeline/rpc" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/pipeline/rpc" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) func UpdateWorkflowStatusToRunning(store store.Store, workflow model.Workflow, state rpc.WorkflowState) (*model.Workflow, error) { diff --git a/server/queue/fifo.go b/server/queue/fifo.go index d6fe664c9..1ee07370d 100644 --- a/server/queue/fifo.go +++ b/server/queue/fifo.go @@ -23,7 +23,8 @@ import ( "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/shared/constant" ) type entry struct { @@ -43,6 +44,7 @@ type worker struct { type fifo struct { sync.Mutex + ctx context.Context workers map[*worker]struct{} running map[string]*entry pending *list.List @@ -51,18 +53,25 @@ type fifo struct { paused bool } -// New returns a new fifo queue. -// -//nolint:mnd -func New(_ context.Context) Queue { - return &fifo{ +// processTimeInterval is the time till the queue rearranges things, +// as the agent pull in 10 milliseconds we should also give them work asap. +const processTimeInterval = 100 * time.Millisecond + +var ErrWorkerKicked = fmt.Errorf("worker was kicked") + +// NewMemoryQueue returns a new fifo queue. +func NewMemoryQueue(ctx context.Context) Queue { + q := &fifo{ + ctx: ctx, workers: map[*worker]struct{}{}, running: map[string]*entry{}, pending: list.New(), waitingOnDeps: list.New(), - extension: time.Minute * 10, + extension: constant.TaskTimeout, paused: false, } + go q.process() + return q } // Push pushes a task to the tail of this queue. @@ -70,7 +79,6 @@ func (q *fifo) Push(_ context.Context, task *model.Task) error { q.Lock() q.pending.PushBack(task) q.Unlock() - go q.process() return nil } @@ -81,33 +89,31 @@ func (q *fifo) PushAtOnce(_ context.Context, tasks []*model.Task) error { q.pending.PushBack(task) } q.Unlock() - go q.process() return nil } // Poll retrieves and removes a task head of this queue. -func (q *fifo) Poll(c context.Context, agentID int64, f FilterFn) (*model.Task, error) { +func (q *fifo) Poll(c context.Context, agentID int64, filter FilterFn) (*model.Task, error) { q.Lock() ctx, stop := context.WithCancelCause(c) - w := &worker{ + _worker := &worker{ agentID: agentID, channel: make(chan *model.Task, 1), - filter: f, + filter: filter, stop: stop, } - q.workers[w] = struct{}{} + q.workers[_worker] = struct{}{} q.Unlock() - go q.process() for { select { case <-ctx.Done(): q.Lock() - delete(q.workers, w) + delete(q.workers, _worker) q.Unlock() return nil, ctx.Err() - case t := <-w.channel: + case t := <-_worker.channel: return t, nil } } @@ -148,22 +154,22 @@ func (q *fifo) finished(ids []string, exitStatus model.StatusValue, err error) e } // Evict removes a pending task from the queue. -func (q *fifo) Evict(c context.Context, id string) error { - return q.EvictAtOnce(c, []string{id}) +func (q *fifo) Evict(ctx context.Context, taskID string) error { + return q.EvictAtOnce(ctx, []string{taskID}) } // EvictAtOnce removes multiple pending tasks from the queue. -func (q *fifo) EvictAtOnce(_ context.Context, ids []string) error { +func (q *fifo) EvictAtOnce(_ context.Context, taskIDs []string) error { q.Lock() defer q.Unlock() - for _, id := range ids { + for _, id := range taskIDs { var next *list.Element - for e := q.pending.Front(); e != nil; e = next { - next = e.Next() - task, ok := e.Value.(*model.Task) + for element := q.pending.Front(); element != nil; element = next { + next = element.Next() + task, ok := element.Value.(*model.Task) if ok && task.ID == id { - q.pending.Remove(e) + q.pending.Remove(element) return nil } } @@ -172,13 +178,13 @@ func (q *fifo) EvictAtOnce(_ context.Context, ids []string) error { } // Wait waits until the item is done executing. -func (q *fifo) Wait(c context.Context, id string) error { +func (q *fifo) Wait(ctx context.Context, taskID string) error { q.Lock() - state := q.running[id] + state := q.running[taskID] q.Unlock() if state != nil { select { - case <-c.Done(): + case <-ctx.Done(): case <-state.done: return state.error } @@ -187,12 +193,16 @@ func (q *fifo) Wait(c context.Context, id string) error { } // Extend extends the task execution deadline. -func (q *fifo) Extend(_ context.Context, id string) error { +func (q *fifo) Extend(_ context.Context, agentID int64, taskID string) error { q.Lock() defer q.Unlock() - state, ok := q.running[id] + state, ok := q.running[taskID] if ok { + if state.item.AgentID != agentID { + return ErrAgentMissMatch + } + state.deadline = time.Now().Add(q.extension) return nil } @@ -208,12 +218,12 @@ func (q *fifo) Info(_ context.Context) InfoT { stats.Stats.WaitingOnDeps = q.waitingOnDeps.Len() stats.Stats.Running = len(q.running) - for e := q.pending.Front(); e != nil; e = e.Next() { - task, _ := e.Value.(*model.Task) + for element := q.pending.Front(); element != nil; element = element.Next() { + task, _ := element.Value.(*model.Task) stats.Pending = append(stats.Pending, task) } - for e := q.waitingOnDeps.Front(); e != nil; e = e.Next() { - task, _ := e.Value.(*model.Task) + for element := q.waitingOnDeps.Front(); element != nil; element = element.Next() { + task, _ := element.Value.(*model.Task) stats.WaitingOnDeps = append(stats.WaitingOnDeps, task) } for _, entry := range q.running { @@ -237,7 +247,6 @@ func (q *fifo) Resume() { q.Lock() q.paused = false q.Unlock() - go q.process() } // KickAgentWorkers kicks all workers for a given agent. @@ -245,37 +254,45 @@ func (q *fifo) KickAgentWorkers(agentID int64) { q.Lock() defer q.Unlock() - for w := range q.workers { - if w.agentID == agentID { - w.stop(fmt.Errorf("worker was kicked")) - delete(q.workers, w) + for worker := range q.workers { + if worker.agentID == agentID { + worker.stop(ErrWorkerKicked) + delete(q.workers, worker) } } } // helper function that loops through the queue and attempts to -// match the item to a single subscriber. +// match the item to a single subscriber until context got cancel. func (q *fifo) process() { - q.Lock() - defer q.Unlock() - - if q.paused { - return - } - - q.resubmitExpiredPipelines() - q.filterWaiting() - for pending, worker := q.assignToWorker(); pending != nil && worker != nil; pending, worker = q.assignToWorker() { - task, _ := pending.Value.(*model.Task) - task.AgentID = worker.agentID - delete(q.workers, worker) - q.pending.Remove(pending) - q.running[task.ID] = &entry{ - item: task, - done: make(chan bool), - deadline: time.Now().Add(q.extension), + for { + select { + case <-time.After(processTimeInterval): + case <-q.ctx.Done(): + return } - worker.channel <- task + + q.Lock() + if q.paused { + q.Unlock() + continue + } + + q.resubmitExpiredPipelines() + q.filterWaiting() + for pending, worker := q.assignToWorker(); pending != nil && worker != nil; pending, worker = q.assignToWorker() { + task, _ := pending.Value.(*model.Task) + task.AgentID = worker.agentID + delete(q.workers, worker) + q.pending.Remove(pending) + q.running[task.ID] = &entry{ + item: task, + done: make(chan bool), + deadline: time.Now().Add(q.extension), + } + worker.channel <- task + } + q.Unlock() } } @@ -292,13 +309,13 @@ func (q *fifo) filterWaiting() { q.waitingOnDeps = list.New() var filtered []*list.Element var nextPending *list.Element - for e := q.pending.Front(); e != nil; e = nextPending { - nextPending = e.Next() - task, _ := e.Value.(*model.Task) + for element := q.pending.Front(); element != nil; element = nextPending { + nextPending = element.Next() + task, _ := element.Value.(*model.Task) if q.depsInQueue(task) { log.Debug().Msgf("queue: waiting due to unmet dependencies %v", task.ID) q.waitingOnDeps.PushBack(task) - filtered = append(filtered, e) + filtered = append(filtered, element) } } @@ -310,37 +327,45 @@ func (q *fifo) filterWaiting() { func (q *fifo) assignToWorker() (*list.Element, *worker) { var next *list.Element - for e := q.pending.Front(); e != nil; e = next { - next = e.Next() - task, _ := e.Value.(*model.Task) + var bestWorker *worker + var bestScore int + + for element := q.pending.Front(); element != nil; element = next { + next = element.Next() + task, _ := element.Value.(*model.Task) log.Debug().Msgf("queue: trying to assign task: %v with deps %v", task.ID, task.Dependencies) - for w := range q.workers { - if w.filter(task) { - log.Debug().Msgf("queue: assigned task: %v with deps %v", task.ID, task.Dependencies) - return e, w + for worker := range q.workers { + matched, score := worker.filter(task) + if matched && score > bestScore { + bestWorker = worker + bestScore = score } } + if bestWorker != nil { + log.Debug().Msgf("queue: assigned task: %v with deps %v to worker with score %d", task.ID, task.Dependencies, bestScore) + return element, bestWorker + } } return nil, nil } func (q *fifo) resubmitExpiredPipelines() { - for id, state := range q.running { - if time.Now().After(state.deadline) { - q.pending.PushFront(state.item) - delete(q.running, id) - close(state.done) + for taskID, taskState := range q.running { + if time.Now().After(taskState.deadline) { + q.pending.PushFront(taskState.item) + delete(q.running, taskID) + close(taskState.done) } } } func (q *fifo) depsInQueue(task *model.Task) bool { var next *list.Element - for e := q.pending.Front(); e != nil; e = next { - next = e.Next() - possibleDep, ok := e.Value.(*model.Task) + for element := q.pending.Front(); element != nil; element = next { + next = element.Next() + possibleDep, ok := element.Value.(*model.Task) log.Debug().Msgf("queue: pending right now: %v", possibleDep.ID) for _, dep := range task.Dependencies { if ok && possibleDep.ID == dep { @@ -361,9 +386,9 @@ func (q *fifo) depsInQueue(task *model.Task) bool { func (q *fifo) updateDepStatusInQueue(taskID string, status model.StatusValue) { var next *list.Element - for e := q.pending.Front(); e != nil; e = next { - next = e.Next() - pending, ok := e.Value.(*model.Task) + for element := q.pending.Front(); element != nil; element = next { + next = element.Next() + pending, ok := element.Value.(*model.Task) for _, dep := range pending.Dependencies { if ok && taskID == dep { pending.DepStatus[dep] = status @@ -379,9 +404,9 @@ func (q *fifo) updateDepStatusInQueue(taskID string, status model.StatusValue) { } } - for e := q.waitingOnDeps.Front(); e != nil; e = next { - next = e.Next() - waiting, ok := e.Value.(*model.Task) + for element := q.waitingOnDeps.Front(); element != nil; element = next { + next = element.Next() + waiting, ok := element.Value.(*model.Task) for _, dep := range waiting.Dependencies { if ok && taskID == dep { waiting.DepStatus[dep] = status @@ -393,12 +418,12 @@ func (q *fifo) updateDepStatusInQueue(taskID string, status model.StatusValue) { func (q *fifo) removeFromPending(taskID string) { log.Debug().Msgf("queue: trying to remove %s", taskID) var next *list.Element - for e := q.pending.Front(); e != nil; e = next { - next = e.Next() - task, _ := e.Value.(*model.Task) + for element := q.pending.Front(); element != nil; element = next { + next = element.Next() + task, _ := element.Value.(*model.Task) if task.ID == taskID { log.Debug().Msgf("queue: %s is removed from pending", taskID) - q.pending.Remove(e) + q.pending.Remove(element) return } } diff --git a/server/queue/fifo_test.go b/server/queue/fifo_test.go index d1d3a2ebc..c25d4a966 100644 --- a/server/queue/fifo_test.go +++ b/server/queue/fifo_test.go @@ -16,6 +16,7 @@ package queue import ( "context" + "errors" "fmt" "sync" "testing" @@ -23,123 +24,165 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) -var noContext = context.Background() +var ( + filterFnTrue = func(*model.Task) (bool, int) { return true, 1 } + genDummyTask = func() *model.Task { + return &model.Task{ + ID: "1", + Data: []byte("{}"), + } + } + waitForProcess = func() { time.Sleep(processTimeInterval + 50*time.Millisecond) } +) func TestFifo(t *testing.T) { - want := &model.Task{ID: "1"} + ctx, cancel := context.WithCancelCause(context.Background()) + t.Cleanup(func() { cancel(nil) }) - q := New(context.Background()) - assert.NoError(t, q.Push(noContext, want)) - info := q.Info(noContext) + q := NewMemoryQueue(ctx) + dummyTask := genDummyTask() + + assert.NoError(t, q.Push(ctx, dummyTask)) + waitForProcess() + info := q.Info(ctx) assert.Len(t, info.Pending, 1, "expect task in pending queue") - got, err := q.Poll(noContext, 1, func(*model.Task) bool { return true }) + got, err := q.Poll(ctx, 1, filterFnTrue) assert.NoError(t, err) - assert.Equal(t, want, got) + assert.Equal(t, dummyTask, got) - info = q.Info(noContext) + waitForProcess() + info = q.Info(ctx) assert.Len(t, info.Pending, 0, "expect task removed from pending queue") assert.Len(t, info.Running, 1, "expect task in running queue") - assert.NoError(t, q.Done(noContext, got.ID, model.StatusSuccess)) - info = q.Info(noContext) + assert.NoError(t, q.Done(ctx, got.ID, model.StatusSuccess)) + + waitForProcess() + info = q.Info(ctx) assert.Len(t, info.Pending, 0, "expect task removed from pending queue") assert.Len(t, info.Running, 0, "expect task removed from running queue") } func TestFifoExpire(t *testing.T) { - want := &model.Task{ID: "1"} + ctx, cancel := context.WithCancelCause(context.Background()) + t.Cleanup(func() { cancel(nil) }) + + q, _ := NewMemoryQueue(ctx).(*fifo) + assert.NotNil(t, q) + + dummyTask := genDummyTask() - q, _ := New(context.Background()).(*fifo) q.extension = 0 - assert.NoError(t, q.Push(noContext, want)) - info := q.Info(noContext) + assert.NoError(t, q.Push(ctx, dummyTask)) + waitForProcess() + info := q.Info(ctx) assert.Len(t, info.Pending, 1, "expect task in pending queue") - got, err := q.Poll(noContext, 1, func(*model.Task) bool { return true }) + got, err := q.Poll(ctx, 1, filterFnTrue) assert.NoError(t, err) - assert.Equal(t, want, got) + assert.Equal(t, dummyTask, got) - q.process() + waitForProcess() + info = q.Info(ctx) assert.Len(t, info.Pending, 1, "expect task re-added to pending queue") } func TestFifoWait(t *testing.T) { - want := &model.Task{ID: "1"} + ctx, cancel := context.WithCancelCause(context.Background()) + t.Cleanup(func() { cancel(nil) }) - q, _ := New(context.Background()).(*fifo) - assert.NoError(t, q.Push(noContext, want)) + q, _ := NewMemoryQueue(ctx).(*fifo) + assert.NotNil(t, q) - got, err := q.Poll(noContext, 1, func(*model.Task) bool { return true }) + dummyTask := genDummyTask() + + assert.NoError(t, q.Push(ctx, dummyTask)) + + waitForProcess() + got, err := q.Poll(ctx, 1, filterFnTrue) assert.NoError(t, err) - assert.Equal(t, want, got) + assert.Equal(t, dummyTask, got) var wg sync.WaitGroup wg.Add(1) go func() { - assert.NoError(t, q.Wait(noContext, got.ID)) + assert.NoError(t, q.Wait(ctx, got.ID)) wg.Done() }() <-time.After(time.Millisecond) - assert.NoError(t, q.Done(noContext, got.ID, model.StatusSuccess)) + assert.NoError(t, q.Done(ctx, got.ID, model.StatusSuccess)) wg.Wait() } func TestFifoEvict(t *testing.T) { - t1 := &model.Task{ID: "1"} + ctx, cancel := context.WithCancelCause(context.Background()) + t.Cleanup(func() { cancel(nil) }) - q := New(context.Background()) - assert.NoError(t, q.Push(noContext, t1)) - info := q.Info(noContext) + q := NewMemoryQueue(ctx) + dummyTask := genDummyTask() + + assert.NoError(t, q.Push(ctx, dummyTask)) + + waitForProcess() + info := q.Info(ctx) assert.Len(t, info.Pending, 1, "expect task in pending queue") - err := q.Evict(noContext, t1.ID) + + err := q.Evict(ctx, dummyTask.ID) assert.NoError(t, err) - info = q.Info(noContext) + + waitForProcess() + info = q.Info(ctx) assert.Len(t, info.Pending, 0) - err = q.Evict(noContext, t1.ID) + + err = q.Evict(ctx, dummyTask.ID) assert.ErrorIs(t, err, ErrNotFound) } func TestFifoDependencies(t *testing.T) { - task1 := &model.Task{ - ID: "1", - } + ctx, cancel := context.WithCancelCause(context.Background()) + t.Cleanup(func() { cancel(nil) }) + task1 := genDummyTask() task2 := &model.Task{ ID: "2", Dependencies: []string{"1"}, DepStatus: make(map[string]model.StatusValue), } - q, _ := New(context.Background()).(*fifo) - assert.NoError(t, q.PushAtOnce(noContext, []*model.Task{task2, task1})) + q, _ := NewMemoryQueue(ctx).(*fifo) + assert.NotNil(t, q) - got, err := q.Poll(noContext, 1, func(*model.Task) bool { return true }) + assert.NoError(t, q.PushAtOnce(ctx, []*model.Task{task2, task1})) + + waitForProcess() + got, err := q.Poll(ctx, 1, filterFnTrue) assert.NoError(t, err) assert.Equal(t, task1, got) - assert.NoError(t, q.Done(noContext, got.ID, model.StatusSuccess)) + waitForProcess() + assert.NoError(t, q.Done(ctx, got.ID, model.StatusSuccess)) - got, err = q.Poll(noContext, 1, func(*model.Task) bool { return true }) + waitForProcess() + got, err = q.Poll(ctx, 1, filterFnTrue) assert.NoError(t, err) assert.Equal(t, task2, got) } func TestFifoErrors(t *testing.T) { - task1 := &model.Task{ - ID: "1", - } + ctx, cancel := context.WithCancelCause(context.Background()) + t.Cleanup(func() { cancel(nil) }) + task1 := genDummyTask() task2 := &model.Task{ ID: "2", Dependencies: []string{"1"}, DepStatus: make(map[string]model.StatusValue), } - task3 := &model.Task{ ID: "3", Dependencies: []string{"1"}, @@ -147,90 +190,103 @@ func TestFifoErrors(t *testing.T) { RunOn: []string{"success", "failure"}, } - q, _ := New(context.Background()).(*fifo) - assert.NoError(t, q.PushAtOnce(noContext, []*model.Task{task2, task3, task1})) + q, _ := NewMemoryQueue(ctx).(*fifo) + assert.NotNil(t, q) - got, err := q.Poll(noContext, 1, func(*model.Task) bool { return true }) + assert.NoError(t, q.PushAtOnce(ctx, []*model.Task{task2, task3, task1})) + + waitForProcess() + got, err := q.Poll(ctx, 1, filterFnTrue) assert.NoError(t, err) assert.Equal(t, task1, got) - assert.NoError(t, q.Error(noContext, got.ID, fmt.Errorf("exit code 1, there was an error"))) + assert.NoError(t, q.Error(ctx, got.ID, fmt.Errorf("exit code 1, there was an error"))) - got, err = q.Poll(noContext, 1, func(*model.Task) bool { return true }) + waitForProcess() + got, err = q.Poll(ctx, 1, filterFnTrue) assert.NoError(t, err) assert.Equal(t, task2, got) assert.False(t, got.ShouldRun()) - got, err = q.Poll(noContext, 1, func(*model.Task) bool { return true }) + waitForProcess() + got, err = q.Poll(ctx, 1, filterFnTrue) assert.NoError(t, err) assert.Equal(t, task3, got) assert.True(t, got.ShouldRun()) } func TestFifoErrors2(t *testing.T) { - task1 := &model.Task{ - ID: "1", - } + ctx, cancel := context.WithCancelCause(context.Background()) + t.Cleanup(func() { cancel(nil) }) + task1 := genDummyTask() task2 := &model.Task{ ID: "2", } - task3 := &model.Task{ ID: "3", Dependencies: []string{"1", "2"}, DepStatus: make(map[string]model.StatusValue), } - q, _ := New(context.Background()).(*fifo) - assert.NoError(t, q.PushAtOnce(noContext, []*model.Task{task2, task3, task1})) + q, _ := NewMemoryQueue(ctx).(*fifo) + assert.NotNil(t, q) + + assert.NoError(t, q.PushAtOnce(ctx, []*model.Task{task2, task3, task1})) for i := 0; i < 2; i++ { - got, err := q.Poll(noContext, 1, func(*model.Task) bool { return true }) + waitForProcess() + got, err := q.Poll(ctx, 1, filterFnTrue) assert.NoError(t, err) assert.False(t, got != task1 && got != task2, "expect task1 or task2 returned from queue as task3 depends on them") if got != task1 { - assert.NoError(t, q.Done(noContext, got.ID, model.StatusSuccess)) + assert.NoError(t, q.Done(ctx, got.ID, model.StatusSuccess)) } if got != task2 { - assert.NoError(t, q.Error(noContext, got.ID, fmt.Errorf("exit code 1, there was an error"))) + assert.NoError(t, q.Error(ctx, got.ID, fmt.Errorf("exit code 1, there was an error"))) } } - got, err := q.Poll(noContext, 1, func(*model.Task) bool { return true }) + waitForProcess() + got, err := q.Poll(ctx, 1, filterFnTrue) assert.NoError(t, err) assert.Equal(t, task3, got) assert.False(t, got.ShouldRun()) } func TestFifoErrorsMultiThread(t *testing.T) { - task1 := &model.Task{ - ID: "1", - } + ctx, cancel := context.WithCancelCause(context.Background()) + t.Cleanup(func() { cancel(nil) }) + task1 := genDummyTask() task2 := &model.Task{ ID: "2", Dependencies: []string{"1"}, DepStatus: make(map[string]model.StatusValue), } - task3 := &model.Task{ ID: "3", Dependencies: []string{"1", "2"}, DepStatus: make(map[string]model.StatusValue), } - q, _ := New(context.Background()).(*fifo) - assert.NoError(t, q.PushAtOnce(noContext, []*model.Task{task2, task3, task1})) + q, _ := NewMemoryQueue(ctx).(*fifo) + assert.NotNil(t, q) + + assert.NoError(t, q.PushAtOnce(ctx, []*model.Task{task2, task3, task1})) obtainedWorkCh := make(chan *model.Task) + defer func() { close(obtainedWorkCh) }() for i := 0; i < 10; i++ { go func(i int) { for { fmt.Printf("Worker %d started\n", i) - got, err := q.Poll(noContext, 1, func(*model.Task) bool { return true }) + got, err := q.Poll(ctx, 1, filterFnTrue) + if err != nil && errors.Is(err, context.Canceled) { + return + } assert.NoError(t, err) obtainedWorkCh <- got } @@ -249,22 +305,30 @@ func TestFifoErrorsMultiThread(t *testing.T) { case !task1Processed: assert.Equal(t, task1, got) task1Processed = true - assert.NoError(t, q.Error(noContext, got.ID, fmt.Errorf("exit code 1, there was an error"))) + assert.NoError(t, q.Error(ctx, got.ID, fmt.Errorf("exit code 1, there was an error"))) go func() { for { fmt.Printf("Worker spawned\n") - got, _ := q.Poll(noContext, 1, func(*model.Task) bool { return true }) + got, err := q.Poll(ctx, 1, filterFnTrue) + if err != nil && errors.Is(err, context.Canceled) { + return + } + assert.NoError(t, err) obtainedWorkCh <- got } }() case !task2Processed: assert.Equal(t, task2, got) task2Processed = true - assert.NoError(t, q.Done(noContext, got.ID, model.StatusSuccess)) + assert.NoError(t, q.Done(ctx, got.ID, model.StatusSuccess)) go func() { for { fmt.Printf("Worker spawned\n") - got, _ := q.Poll(noContext, 1, func(*model.Task) bool { return true }) + got, err := q.Poll(ctx, 1, filterFnTrue) + if err != nil && errors.Is(err, context.Canceled) { + return + } + assert.NoError(t, err) obtainedWorkCh <- got } }() @@ -275,7 +339,7 @@ func TestFifoErrorsMultiThread(t *testing.T) { } case <-time.After(5 * time.Second): - info := q.Info(noContext) + info := q.Info(ctx) fmt.Println(info.String()) t.Errorf("test timed out") return @@ -284,53 +348,56 @@ func TestFifoErrorsMultiThread(t *testing.T) { } func TestFifoTransitiveErrors(t *testing.T) { - task1 := &model.Task{ - ID: "1", - } + ctx, cancel := context.WithCancelCause(context.Background()) + t.Cleanup(func() { cancel(nil) }) + task1 := genDummyTask() task2 := &model.Task{ ID: "2", Dependencies: []string{"1"}, DepStatus: make(map[string]model.StatusValue), } - task3 := &model.Task{ ID: "3", Dependencies: []string{"2"}, DepStatus: make(map[string]model.StatusValue), } - q, _ := New(context.Background()).(*fifo) - assert.NoError(t, q.PushAtOnce(noContext, []*model.Task{task2, task3, task1})) + q, _ := NewMemoryQueue(ctx).(*fifo) + assert.NotNil(t, q) - got, err := q.Poll(noContext, 1, func(*model.Task) bool { return true }) + assert.NoError(t, q.PushAtOnce(ctx, []*model.Task{task2, task3, task1})) + + waitForProcess() + got, err := q.Poll(ctx, 1, filterFnTrue) assert.NoError(t, err) assert.Equal(t, task1, got) - assert.NoError(t, q.Error(noContext, got.ID, fmt.Errorf("exit code 1, there was an error"))) + assert.NoError(t, q.Error(ctx, got.ID, fmt.Errorf("exit code 1, there was an error"))) - got, err = q.Poll(noContext, 1, func(*model.Task) bool { return true }) + waitForProcess() + got, err = q.Poll(ctx, 1, filterFnTrue) assert.NoError(t, err) assert.Equal(t, task2, got) assert.False(t, got.ShouldRun(), "expect task2 should not run, since task1 failed") - assert.NoError(t, q.Done(noContext, got.ID, model.StatusSkipped)) + assert.NoError(t, q.Done(ctx, got.ID, model.StatusSkipped)) - got, err = q.Poll(noContext, 1, func(*model.Task) bool { return true }) + waitForProcess() + got, err = q.Poll(ctx, 1, filterFnTrue) assert.NoError(t, err) assert.Equal(t, task3, got) assert.False(t, got.ShouldRun(), "expect task3 should not run, task1 failed, thus task2 was skipped, task3 should be skipped too") } func TestFifoCancel(t *testing.T) { - task1 := &model.Task{ - ID: "1", - } + ctx, cancel := context.WithCancelCause(context.Background()) + t.Cleanup(func() { cancel(nil) }) + task1 := genDummyTask() task2 := &model.Task{ ID: "2", Dependencies: []string{"1"}, DepStatus: make(map[string]model.StatusValue), } - task3 := &model.Task{ ID: "3", Dependencies: []string{"1"}, @@ -338,35 +405,45 @@ func TestFifoCancel(t *testing.T) { RunOn: []string{"success", "failure"}, } - q, _ := New(context.Background()).(*fifo) - assert.NoError(t, q.PushAtOnce(noContext, []*model.Task{task2, task3, task1})) + q, _ := NewMemoryQueue(ctx).(*fifo) + assert.NotNil(t, q) - _, _ = q.Poll(noContext, 1, func(*model.Task) bool { return true }) - assert.NoError(t, q.Error(noContext, task1.ID, fmt.Errorf("canceled"))) - assert.NoError(t, q.Error(noContext, task2.ID, fmt.Errorf("canceled"))) - assert.NoError(t, q.Error(noContext, task3.ID, fmt.Errorf("canceled"))) + assert.NoError(t, q.PushAtOnce(ctx, []*model.Task{task2, task3, task1})) - info := q.Info(noContext) + _, _ = q.Poll(ctx, 1, filterFnTrue) + assert.NoError(t, q.Error(ctx, task1.ID, fmt.Errorf("canceled"))) + assert.NoError(t, q.Error(ctx, task2.ID, fmt.Errorf("canceled"))) + assert.NoError(t, q.Error(ctx, task3.ID, fmt.Errorf("canceled"))) + info := q.Info(ctx) assert.Len(t, info.Pending, 0, "all pipelines should be canceled") + + time.Sleep(processTimeInterval * 2) + info = q.Info(ctx) + assert.Len(t, info.Pending, 2, "canceled are rescheduled") + assert.Len(t, info.Running, 0, "canceled are rescheduled") + assert.Len(t, info.WaitingOnDeps, 0, "canceled are rescheduled") } func TestFifoPause(t *testing.T) { - task1 := &model.Task{ - ID: "1", - } + ctx, cancel := context.WithCancelCause(context.Background()) + t.Cleanup(func() { cancel(nil) }) + + q, _ := NewMemoryQueue(ctx).(*fifo) + assert.NotNil(t, q) + + dummyTask := genDummyTask() - q, _ := New(context.Background()).(*fifo) var wg sync.WaitGroup wg.Add(1) go func() { - _, _ = q.Poll(noContext, 1, func(*model.Task) bool { return true }) + _, _ = q.Poll(ctx, 1, filterFnTrue) wg.Done() }() q.Pause() t0 := time.Now() - assert.NoError(t, q.Push(noContext, task1)) - time.Sleep(20 * time.Millisecond) + assert.NoError(t, q.Push(ctx, dummyTask)) + waitForProcess() q.Resume() wg.Wait() @@ -375,35 +452,37 @@ func TestFifoPause(t *testing.T) { assert.Greater(t, t1.Sub(t0), 20*time.Millisecond, "should have waited til resume") q.Pause() - assert.NoError(t, q.Push(noContext, task1)) + assert.NoError(t, q.Push(ctx, dummyTask)) q.Resume() - _, _ = q.Poll(noContext, 1, func(*model.Task) bool { return true }) + _, _ = q.Poll(ctx, 1, filterFnTrue) } func TestFifoPauseResume(t *testing.T) { - task1 := &model.Task{ - ID: "1", - } + ctx, cancel := context.WithCancelCause(context.Background()) + t.Cleanup(func() { cancel(nil) }) + + q, _ := NewMemoryQueue(ctx).(*fifo) + assert.NotNil(t, q) + + dummyTask := genDummyTask() - q, _ := New(context.Background()).(*fifo) q.Pause() - assert.NoError(t, q.Push(noContext, task1)) + assert.NoError(t, q.Push(ctx, dummyTask)) q.Resume() - _, _ = q.Poll(noContext, 1, func(*model.Task) bool { return true }) + _, _ = q.Poll(ctx, 1, filterFnTrue) } func TestWaitingVsPending(t *testing.T) { - task1 := &model.Task{ - ID: "1", - } + ctx, cancel := context.WithCancelCause(context.Background()) + t.Cleanup(func() { cancel(nil) }) + task1 := genDummyTask() task2 := &model.Task{ ID: "2", Dependencies: []string{"1"}, DepStatus: make(map[string]model.StatusValue), } - task3 := &model.Task{ ID: "3", Dependencies: []string{"1"}, @@ -411,20 +490,24 @@ func TestWaitingVsPending(t *testing.T) { RunOn: []string{"success", "failure"}, } - q, _ := New(context.Background()).(*fifo) - assert.NoError(t, q.PushAtOnce(noContext, []*model.Task{task2, task3, task1})) + q, _ := NewMemoryQueue(ctx).(*fifo) + assert.NotNil(t, q) - got, _ := q.Poll(noContext, 1, func(*model.Task) bool { return true }) + assert.NoError(t, q.PushAtOnce(ctx, []*model.Task{task2, task3, task1})) - info := q.Info(noContext) + got, _ := q.Poll(ctx, 1, filterFnTrue) + + waitForProcess() + info := q.Info(ctx) assert.Equal(t, 2, info.Stats.WaitingOnDeps) - assert.NoError(t, q.Error(noContext, got.ID, fmt.Errorf("exit code 1, there was an error"))) - got, err := q.Poll(noContext, 1, func(*model.Task) bool { return true }) + assert.NoError(t, q.Error(ctx, got.ID, fmt.Errorf("exit code 1, there was an error"))) + got, err := q.Poll(ctx, 1, filterFnTrue) assert.NoError(t, err) assert.EqualValues(t, task2, got) - info = q.Info(noContext) + waitForProcess() + info = q.Info(ctx) assert.Equal(t, 0, info.Stats.WaitingOnDeps) assert.Equal(t, 1, info.Stats.Pending) } @@ -498,3 +581,108 @@ func TestShouldRun(t *testing.T) { } assert.True(t, task.ShouldRun(), "on failure, tasks should run on skipped deps, something failed higher up the chain") } + +func TestFifoWithScoring(t *testing.T) { + ctx, cancel := context.WithCancelCause(context.Background()) + t.Cleanup(func() { cancel(nil) }) + + q := NewMemoryQueue(ctx) + + // Create tasks with different labels + tasks := []*model.Task{ + {ID: "1", Labels: map[string]string{"org-id": "123", "platform": "linux"}}, + {ID: "2", Labels: map[string]string{"org-id": "456", "platform": "linux"}}, + {ID: "3", Labels: map[string]string{"org-id": "789", "platform": "windows"}}, + {ID: "4", Labels: map[string]string{"org-id": "123", "platform": "linux"}}, + {ID: "5", Labels: map[string]string{"org-id": "*", "platform": "linux"}}, + } + + assert.NoError(t, q.PushAtOnce(ctx, tasks)) + + // Create filter functions for different workers + filters := map[int]FilterFn{ + 1: func(task *model.Task) (bool, int) { + if task.Labels["org-id"] == "123" { + return true, 20 + } + if task.Labels["platform"] == "linux" { + return true, 10 + } + return true, 1 + }, + 2: func(task *model.Task) (bool, int) { + if task.Labels["org-id"] == "456" { + return true, 20 + } + if task.Labels["platform"] == "linux" { + return true, 10 + } + return true, 1 + }, + 3: func(task *model.Task) (bool, int) { + if task.Labels["platform"] == "windows" { + return true, 20 + } + return true, 1 + }, + 4: func(task *model.Task) (bool, int) { + if task.Labels["org-id"] == "123" { + return true, 20 + } + if task.Labels["platform"] == "linux" { + return true, 10 + } + return true, 1 + }, + 5: func(task *model.Task) (bool, int) { + if task.Labels["org-id"] == "*" { + return true, 15 + } + return true, 1 + }, + } + + // Start polling in separate goroutines + results := make(chan *model.Task, 5) + for i := 1; i <= 5; i++ { + go func(n int) { + task, err := q.Poll(ctx, int64(n), filters[n]) + assert.NoError(t, err) + results <- task + }(i) + } + + // Collect results + receivedTasks := make(map[string]int64) + for i := 0; i < 5; i++ { + select { + case task := <-results: + receivedTasks[task.ID] = task.AgentID + case <-time.After(time.Second): + t.Fatal("Timeout waiting for tasks") + } + } + + assert.Len(t, receivedTasks, 5, "All tasks should be assigned") + + // Define expected agent assignments + // Map structure: {taskID: []possible agentIDs} + // - taskID "1" and "4" can be assigned to agents 1 or 4 (org-id "123") + // - taskID "2" should be assigned to agent 2 (org-id "456") + // - taskID "3" should be assigned to agent 3 (platform "windows") + // - taskID "5" should be assigned to agent 5 (org-id "*") + expectedAssignments := map[string][]int64{ + "1": {1, 4}, + "2": {2}, + "3": {3}, + "4": {1, 4}, + "5": {5}, + } + + // Check if tasks are assigned as expected + for taskID, expectedAgents := range expectedAssignments { + agentID, ok := receivedTasks[taskID] + assert.True(t, ok, "Task %s should be assigned", taskID) + assert.Contains(t, expectedAgents, agentID, "Task %s should be assigned to one of the expected agents", taskID) + } +} diff --git a/server/queue/mocks/queue.go b/server/queue/mocks/queue.go new file mode 100644 index 000000000..a851b782b --- /dev/null +++ b/server/queue/mocks/queue.go @@ -0,0 +1,259 @@ +// Code generated by mockery. DO NOT EDIT. + +//go:build test +// +build test + +package mocks + +import ( + context "context" + + mock "github.com/stretchr/testify/mock" + model "go.woodpecker-ci.org/woodpecker/v3/server/model" + + queue "go.woodpecker-ci.org/woodpecker/v3/server/queue" +) + +// Queue is an autogenerated mock type for the Queue type +type Queue struct { + mock.Mock +} + +// Done provides a mock function with given fields: c, id, exitStatus +func (_m *Queue) Done(c context.Context, id string, exitStatus model.StatusValue) error { + ret := _m.Called(c, id, exitStatus) + + if len(ret) == 0 { + panic("no return value specified for Done") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, model.StatusValue) error); ok { + r0 = rf(c, id, exitStatus) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Error provides a mock function with given fields: c, id, err +func (_m *Queue) Error(c context.Context, id string, err error) error { + ret := _m.Called(c, id, err) + + if len(ret) == 0 { + panic("no return value specified for Error") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string, error) error); ok { + r0 = rf(c, id, err) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// ErrorAtOnce provides a mock function with given fields: c, ids, err +func (_m *Queue) ErrorAtOnce(c context.Context, ids []string, err error) error { + ret := _m.Called(c, ids, err) + + if len(ret) == 0 { + panic("no return value specified for ErrorAtOnce") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []string, error) error); ok { + r0 = rf(c, ids, err) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Evict provides a mock function with given fields: c, id +func (_m *Queue) Evict(c context.Context, id string) error { + ret := _m.Called(c, id) + + if len(ret) == 0 { + panic("no return value specified for Evict") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(c, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// EvictAtOnce provides a mock function with given fields: c, ids +func (_m *Queue) EvictAtOnce(c context.Context, ids []string) error { + ret := _m.Called(c, ids) + + if len(ret) == 0 { + panic("no return value specified for EvictAtOnce") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []string) error); ok { + r0 = rf(c, ids) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Extend provides a mock function with given fields: c, agentID, workflowID +func (_m *Queue) Extend(c context.Context, agentID int64, workflowID string) error { + ret := _m.Called(c, agentID, workflowID) + + if len(ret) == 0 { + panic("no return value specified for Extend") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, int64, string) error); ok { + r0 = rf(c, agentID, workflowID) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Info provides a mock function with given fields: c +func (_m *Queue) Info(c context.Context) queue.InfoT { + ret := _m.Called(c) + + if len(ret) == 0 { + panic("no return value specified for Info") + } + + var r0 queue.InfoT + if rf, ok := ret.Get(0).(func(context.Context) queue.InfoT); ok { + r0 = rf(c) + } else { + r0 = ret.Get(0).(queue.InfoT) + } + + return r0 +} + +// KickAgentWorkers provides a mock function with given fields: agentID +func (_m *Queue) KickAgentWorkers(agentID int64) { + _m.Called(agentID) +} + +// Pause provides a mock function with no fields +func (_m *Queue) Pause() { + _m.Called() +} + +// Poll provides a mock function with given fields: c, agentID, f +func (_m *Queue) Poll(c context.Context, agentID int64, f queue.FilterFn) (*model.Task, error) { + ret := _m.Called(c, agentID, f) + + if len(ret) == 0 { + panic("no return value specified for Poll") + } + + var r0 *model.Task + var r1 error + if rf, ok := ret.Get(0).(func(context.Context, int64, queue.FilterFn) (*model.Task, error)); ok { + return rf(c, agentID, f) + } + if rf, ok := ret.Get(0).(func(context.Context, int64, queue.FilterFn) *model.Task); ok { + r0 = rf(c, agentID, f) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.Task) + } + } + + if rf, ok := ret.Get(1).(func(context.Context, int64, queue.FilterFn) error); ok { + r1 = rf(c, agentID, f) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Push provides a mock function with given fields: c, task +func (_m *Queue) Push(c context.Context, task *model.Task) error { + ret := _m.Called(c, task) + + if len(ret) == 0 { + panic("no return value specified for Push") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, *model.Task) error); ok { + r0 = rf(c, task) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// PushAtOnce provides a mock function with given fields: c, tasks +func (_m *Queue) PushAtOnce(c context.Context, tasks []*model.Task) error { + ret := _m.Called(c, tasks) + + if len(ret) == 0 { + panic("no return value specified for PushAtOnce") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, []*model.Task) error); ok { + r0 = rf(c, tasks) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Resume provides a mock function with no fields +func (_m *Queue) Resume() { + _m.Called() +} + +// Wait provides a mock function with given fields: c, id +func (_m *Queue) Wait(c context.Context, id string) error { + ret := _m.Called(c, id) + + if len(ret) == 0 { + panic("no return value specified for Wait") + } + + var r0 error + if rf, ok := ret.Get(0).(func(context.Context, string) error); ok { + r0 = rf(c, id) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// NewQueue creates a new instance of Queue. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewQueue(t interface { + mock.TestingT + Cleanup(func()) +}) *Queue { + mock := &Queue{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/server/queue/persistent.go b/server/queue/persistent.go index 96d9fff9f..0f2ae4ae2 100644 --- a/server/queue/persistent.go +++ b/server/queue/persistent.go @@ -21,15 +21,15 @@ import ( "github.com/pkg/errors" "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) // WithTaskStore returns a queue that is backed by the TaskStore. This // ensures the task Queue can be restored when the system starts. -func WithTaskStore(q Queue, s store.Store) Queue { +func WithTaskStore(ctx context.Context, q Queue, s store.Store) Queue { tasks, _ := s.TaskList() - if err := q.PushAtOnce(context.Background(), tasks); err != nil { + if err := q.PushAtOnce(ctx, tasks); err != nil { log.Error().Err(err).Msg("PushAtOnce failed") } return &persistentQueue{q, s} diff --git a/server/queue/queue.go b/server/queue/queue.go index 9cb695c24..e575bafcd 100644 --- a/server/queue/queue.go +++ b/server/queue/queue.go @@ -17,9 +17,11 @@ package queue import ( "context" "errors" + "fmt" "strings" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) var ( @@ -28,6 +30,9 @@ var ( // ErrNotFound indicates the task was not found in the queue. ErrNotFound = errors.New("queue: task not found") + + // ErrAgentMissMatch indicates a task is assigned to a different agent. + ErrAgentMissMatch = errors.New("task assigned to different agent") ) // InfoT provides runtime information. @@ -64,7 +69,10 @@ func (t *InfoT) String() string { // Filter filters tasks in the queue. If the Filter returns false, // the Task is skipped and not returned to the subscriber. -type FilterFn func(*model.Task) bool +// The int return value represents the matching score (higher is better). +type FilterFn func(*model.Task) (bool, int) + +//go:generate mockery --name Queue --output mocks --case underscore --note "+build test" // Queue defines a task queue for scheduling tasks among // a pool of workers. @@ -79,7 +87,7 @@ type Queue interface { Poll(c context.Context, agentID int64, f FilterFn) (*model.Task, error) // Extend extends the deadline for a task. - Extend(c context.Context, id string) error + Extend(c context.Context, agentID int64, workflowID string) error // Done signals the task is complete. Done(c context.Context, id string, exitStatus model.StatusValue) error @@ -111,3 +119,33 @@ type Queue interface { // KickAgentWorkers kicks all workers for a given agent. KickAgentWorkers(agentID int64) } + +// Config holds the configuration for the queue. +type Config struct { + Backend Type + Store store.Store +} + +// Queue type. +type Type string + +const ( + TypeMemory Type = "memory" +) + +// New creates a new queue based on the provided configuration. +func New(ctx context.Context, config Config) (Queue, error) { + var q Queue + + switch config.Backend { + case TypeMemory: + q = NewMemoryQueue(ctx) + if config.Store != nil { + q = WithTaskStore(ctx, q, config.Store) + } + default: + return nil, fmt.Errorf("unsupported queue backend: %s", config.Backend) + } + + return q, nil +} diff --git a/server/router/api.go b/server/router/api.go index cce04ef0e..23f306aa3 100644 --- a/server/router/api.go +++ b/server/router/api.go @@ -15,14 +15,13 @@ package router import ( - "net/http" - "github.com/gin-gonic/gin" "github.com/rs/zerolog" - "go.woodpecker-ci.org/woodpecker/v2/server/api" - "go.woodpecker-ci.org/woodpecker/v2/server/api/debug" - "go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/api" + "go.woodpecker-ci.org/woodpecker/v3/server/api/debug" + "go.woodpecker-ci.org/woodpecker/v3/server/router/middleware/session" ) func apiRoutes(e *gin.RouterGroup) { @@ -54,13 +53,15 @@ func apiRoutes(e *gin.RouterGroup) { orgs.GET("/lookup/*org_full_name", api.LookupOrg) orgBase := orgs.Group("/:org_id") { + orgBase.Use(session.SetOrg()) + orgBase.Use(session.MustOrg()) orgBase.GET("/permissions", api.GetOrgPermissions) + orgBase.GET("", session.MustOrgMember(false), api.GetOrg) org := orgBase.Group("") { org.Use(session.MustOrgMember(true)) org.DELETE("", session.MustAdmin(), api.DeleteOrg) - org.GET("", api.GetOrg) org.GET("/secrets", api.GetOrgSecretList) org.POST("/secrets", api.PostOrgSecret) @@ -73,6 +74,13 @@ func apiRoutes(e *gin.RouterGroup) { org.GET("/registries/:registry", api.GetOrgRegistry) org.PATCH("/registries/:registry", api.PatchOrgRegistry) org.DELETE("/registries/:registry", api.DeleteOrgRegistry) + + if !server.Config.Agent.DisableUserRegisteredAgentRegistration { + org.GET("/agents", api.GetOrgAgents) + org.POST("/agents", api.PostOrgAgent) + org.PATCH("/agents/:agent_id", api.PatchOrgAgent) + org.DELETE("/agents/:agent_id", api.DeleteOrgAgent) + } } } } @@ -104,6 +112,7 @@ func apiRoutes(e *gin.RouterGroup) { repo.DELETE("/pipelines/:number", session.MustRepoAdmin(), api.DeletePipeline) repo.GET("/pipelines/:number", api.GetPipeline) repo.GET("/pipelines/:number/config", api.GetPipelineConfig) + repo.GET("/pipelines/:number/metadata", session.MustPush, api.GetPipelineMetadata) // requires push permissions repo.POST("/pipelines/:number", session.MustPush, api.PostPipeline) @@ -131,13 +140,6 @@ func apiRoutes(e *gin.RouterGroup) { repo.PATCH("/registries/:registry", session.MustPush, api.PatchRegistry) repo.DELETE("/registries/:registry", session.MustPush, api.DeleteRegistry) - // TODO: remove with 3.x - repo.GET("/registry", session.MustPush, api.GetRegistryList) - repo.POST("/registry", session.MustPush, api.PostRegistry) - repo.GET("/registry/:registry", session.MustPush, api.GetRegistry) - repo.PATCH("/registry/:registry", session.MustPush, api.PatchRegistry) - repo.DELETE("/registry/:registry", session.MustPush, api.DeleteRegistry) - // requires push permissions repo.GET("/cron", session.MustPush, api.GetCronList) repo.POST("/cron", session.MustPush, api.PostCron) @@ -225,10 +227,10 @@ func apiRoutes(e *gin.RouterGroup) { agentBase.Use(session.MustAdmin()) agentBase.GET("", api.GetAgents) agentBase.POST("", api.PostAgent) - agentBase.GET("/:agent", api.GetAgent) - agentBase.GET("/:agent/tasks", api.GetAgentTasks) - agentBase.PATCH("/:agent", api.PatchAgent) - agentBase.DELETE("/:agent", api.DeleteAgent) + agentBase.GET("/:agent_id", api.GetAgent) + agentBase.GET("/:agent_id/tasks", api.GetAgentTasks) + agentBase.PATCH("/:agent_id", api.PatchAgent) + agentBase.DELETE("/:agent_id", api.DeleteAgent) } apiBase.GET("/forges", api.GetForges) @@ -272,12 +274,4 @@ func apiRoutes(e *gin.RouterGroup) { } } } - - // TODO: remove with 3.x - e.Any("/hook", func(c *gin.Context) { - c.String(http.StatusGone, "use /api/hook") - }) - e.Any("/stream/events", func(c *gin.Context) { - c.String(http.StatusGone, "use /api/stream/events") - }) } diff --git a/server/router/middleware/session/agent.go b/server/router/middleware/session/agent.go index 0cbfa2a33..24d9bae39 100644 --- a/server/router/middleware/session/agent.go +++ b/server/router/middleware/session/agent.go @@ -20,7 +20,7 @@ import ( "github.com/gin-gonic/gin" - "go.woodpecker-ci.org/woodpecker/v2/shared/token" + "go.woodpecker-ci.org/woodpecker/v3/shared/token" ) // AuthorizeAgent authorizes requests from agent to access the queue. diff --git a/server/router/middleware/session/org.go b/server/router/middleware/session/org.go new file mode 100644 index 000000000..a02f0e50f --- /dev/null +++ b/server/router/middleware/session/org.go @@ -0,0 +1,86 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package session + +import ( + "errors" + "net/http" + "strconv" + + "github.com/gin-gonic/gin" + + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/store/types" +) + +func Org(c *gin.Context) *model.Org { + v, ok := c.Get("org") + if !ok { + return nil + } + r, ok := v.(*model.Org) + if !ok { + return nil + } + return r +} + +func SetOrg() gin.HandlerFunc { + return func(c *gin.Context) { + var ( + orgID int64 + err error + ) + + orgParam := c.Param("org_id") + if orgParam != "" { + orgID, err = strconv.ParseInt(orgParam, 10, 64) + if err != nil { + c.String(http.StatusBadRequest, "Invalid organization ID") + c.Abort() + return + } + } + + org, err := store.FromContext(c).OrgGet(orgID) + if err != nil && !errors.Is(err, types.RecordNotExist) { + _ = c.AbortWithError(http.StatusInternalServerError, err) + return + } + + if org == nil { + c.String(http.StatusNotFound, "Organization not found") + c.Abort() + return + } + + c.Set("org", org) + c.Next() + } +} + +func MustOrg() gin.HandlerFunc { + return func(c *gin.Context) { + org := Org(c) + switch { + case org == nil: + c.String(http.StatusNotFound, "Organization not loaded") + c.Abort() + default: + c.Next() + } + } +} diff --git a/server/router/middleware/session/pagination.go b/server/router/middleware/session/pagination.go index 9bf4455c1..a9bffcd5f 100644 --- a/server/router/middleware/session/pagination.go +++ b/server/router/middleware/session/pagination.go @@ -19,7 +19,7 @@ import ( "github.com/gin-gonic/gin" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) const maxPageSize = 50 diff --git a/server/router/middleware/session/repo.go b/server/router/middleware/session/repo.go index 0ec3cfd65..788ae1792 100644 --- a/server/router/middleware/session/repo.go +++ b/server/router/middleware/session/repo.go @@ -24,10 +24,10 @@ import ( "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" - "go.woodpecker-ci.org/woodpecker/v2/server/store/types" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/store/types" ) func Repo(c *gin.Context) *model.Repo { diff --git a/server/router/middleware/session/user.go b/server/router/middleware/session/user.go index 2f098b5cb..7cc35de66 100644 --- a/server/router/middleware/session/user.go +++ b/server/router/middleware/session/user.go @@ -21,10 +21,10 @@ import ( "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" - "go.woodpecker-ci.org/woodpecker/v2/shared/token" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" + "go.woodpecker-ci.org/woodpecker/v3/shared/token" ) func User(c *gin.Context) *model.User { @@ -122,8 +122,6 @@ func MustUser() gin.HandlerFunc { func MustOrgMember(admin bool) gin.HandlerFunc { return func(c *gin.Context) { - _store := store.FromContext(c) - user := User(c) if user == nil { c.String(http.StatusUnauthorized, "User not authorized") @@ -131,15 +129,10 @@ func MustOrgMember(admin bool) gin.HandlerFunc { return } - orgID, err := strconv.ParseInt(c.Param("org_id"), 10, 64) - if err != nil { - c.String(http.StatusBadRequest, "Error parsing org id. %s", err) - return - } - - org, err := _store.OrgGet(orgID) - if err != nil { - c.String(http.StatusNotFound, "Organization not found") + org := Org(c) + if org == nil { + c.String(http.StatusBadRequest, "Organization not loaded") + c.Abort() return } diff --git a/server/router/middleware/store.go b/server/router/middleware/store.go index 6ab61167e..915b1a33c 100644 --- a/server/router/middleware/store.go +++ b/server/router/middleware/store.go @@ -17,7 +17,7 @@ package middleware import ( "github.com/gin-gonic/gin" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) // Store is a middleware function that initializes the Datastore and attaches to diff --git a/server/router/middleware/token/token.go b/server/router/middleware/token/token.go index 8d45adda6..600e4fb15 100644 --- a/server/router/middleware/token/token.go +++ b/server/router/middleware/token/token.go @@ -19,10 +19,10 @@ import ( "github.com/gin-gonic/gin" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - "go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/router/middleware/session" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) func Refresh(c *gin.Context) { diff --git a/server/router/middleware/version.go b/server/router/middleware/version.go index b5c8ec1e2..0fac8225a 100644 --- a/server/router/middleware/version.go +++ b/server/router/middleware/version.go @@ -17,7 +17,7 @@ package middleware import ( "github.com/gin-gonic/gin" - "go.woodpecker-ci.org/woodpecker/v2/version" + "go.woodpecker-ci.org/woodpecker/v3/version" ) // Version is a middleware function that appends the Woodpecker version information diff --git a/server/router/router.go b/server/router/router.go index 89e1046f9..041876d1a 100644 --- a/server/router/router.go +++ b/server/router/router.go @@ -20,17 +20,17 @@ import ( "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" - swagger_files "github.com/swaggo/files" + openapi_files "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" - "go.woodpecker-ci.org/woodpecker/v2/cmd/server/docs" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/api" - "go.woodpecker-ci.org/woodpecker/v2/server/api/metrics" - "go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/header" - "go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session" - "go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/token" - "go.woodpecker-ci.org/woodpecker/v2/server/web" + "go.woodpecker-ci.org/woodpecker/v3/cmd/server/openapi" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/api" + "go.woodpecker-ci.org/woodpecker/v3/server/api/metrics" + "go.woodpecker-ci.org/woodpecker/v3/server/router/middleware/header" + "go.woodpecker-ci.org/woodpecker/v3/server/router/middleware/session" + "go.woodpecker-ci.org/woodpecker/v3/server/router/middleware/token" + "go.woodpecker-ci.org/woodpecker/v3/server/web" ) // Load loads the router. @@ -62,7 +62,6 @@ func Load(noRouteHandler http.HandlerFunc, middleware ...gin.HandlerFunc) http.H { auth.GET("", api.HandleAuth) auth.POST("", api.HandleAuth) - auth.POST("/token", api.DeprecatedGetLoginToken) } base.GET("/metrics", metrics.PromHandler()) @@ -79,9 +78,9 @@ func Load(noRouteHandler http.HandlerFunc, middleware ...gin.HandlerFunc) http.H } func setupSwaggerConfigAndRoutes(e *gin.Engine) { - docs.SwaggerInfo.Host = getHost(server.Config.Server.Host) - docs.SwaggerInfo.BasePath = server.Config.Server.RootPath + "/api" - e.GET(server.Config.Server.RootPath+"/swagger/*any", ginSwagger.WrapHandler(swagger_files.Handler)) + openapi.SwaggerInfo.Host = getHost(server.Config.Server.Host) + openapi.SwaggerInfo.BasePath = server.Config.Server.RootPath + "/api" + e.GET(server.Config.Server.RootPath+"/swagger/*any", ginSwagger.WrapHandler(openapi_files.Handler)) } func getHost(s string) string { diff --git a/server/services/config/combined.go b/server/services/config/combined.go index 5daebb281..5a6936eb5 100644 --- a/server/services/config/combined.go +++ b/server/services/config/combined.go @@ -17,9 +17,9 @@ package config import ( "context" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) type combined struct { diff --git a/server/services/config/combined_test.go b/server/services/config/combined_test.go index d2a767cac..6097f9805 100644 --- a/server/services/config/combined_test.go +++ b/server/services/config/combined_test.go @@ -27,14 +27,14 @@ import ( "testing" "time" - "github.com/go-ap/httpsig" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/yaronf/httpsign" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/mocks" - forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/services/config" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/mocks" + forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/services/config" ) func TestFetchFromConfigService(t *testing.T) { @@ -120,25 +120,19 @@ func TestFetchFromConfigService(t *testing.T) { fixtureHandler := func(w http.ResponseWriter, r *http.Request) { // check signature - pubKeyID := "woodpecker-ci-plugins" + pubKeyID := "woodpecker-ci-extensions" - keystore := httpsig.NewMemoryKeyStore() - keystore.SetKey(pubKeyID, pubEd25519Key) + verifier, err := httpsign.NewEd25519Verifier(pubEd25519Key, + httpsign.NewVerifyConfig(), + httpsign.Headers("@request-target", "content-digest")) // The Content-Digest header will be auto-generated + assert.NoError(t, err) - verifier := httpsig.NewVerifier(keystore) - verifier.SetRequiredHeaders([]string{"(request-target)", "date"}) - - keyID, err := verifier.Verify(r) + err = httpsign.VerifyRequest(pubKeyID, *verifier, r) if err != nil { http.Error(w, "Invalid signature", http.StatusBadRequest) return } - if keyID != pubKeyID { - http.Error(w, "Used wrong key", http.StatusBadRequest) - return - } - type config struct { Name string `json:"name"` Data string `json:"data"` @@ -163,12 +157,12 @@ func TestFetchFromConfigService(t *testing.T) { } if req.Repo.Name == "Fetch empty" { - w.WriteHeader(404) + w.WriteHeader(http.StatusNotFound) return } if req.Repo.Name == "Use old config" { - w.WriteHeader(204) + w.WriteHeader(http.StatusNoContent) return } @@ -192,7 +186,7 @@ func TestFetchFromConfigService(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(fixtureHandler)) defer ts.Close() - httpFetcher := config.NewHTTP(ts.URL, privEd25519Key) + httpFetcher := config.NewHTTP(ts.URL+"/", privEd25519Key) for _, tt := range testTable { t.Run(tt.name, func(t *testing.T) { @@ -226,7 +220,7 @@ func TestFetchFromConfigService(t *testing.T) { files, err := configFetcher.Fetch( context.Background(), f, - &model.User{Token: "xxx"}, + &model.User{AccessToken: "xxx"}, repo, &model.Pipeline{Commit: "89ab7b2d6bfb347144ac7c557e638ab402848fee"}, []*forge_types.FileMeta{}, diff --git a/server/services/config/forge.go b/server/services/config/forge.go index c88a30ca6..93affef73 100644 --- a/server/services/config/forge.go +++ b/server/services/config/forge.go @@ -23,10 +23,10 @@ import ( "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/shared/constant" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/shared/constant" ) type forgeFetcher struct { @@ -59,10 +59,9 @@ func (f *forgeFetcher) Fetch(ctx context.Context, forge forge.Forge, user *model for i := 0; i < int(f.retryCount); i++ { files, err = ffc.fetch(ctx, strings.TrimSpace(repo.Config)) if err != nil { - log.Trace().Err(err).Msgf("%d. try failed", i+1) - } - if errors.Is(err, context.DeadlineExceeded) { - continue + log.Trace().Err(err).Msgf("Fetching config files: Attempt #%d failed", i+1) + } else { + break } } @@ -141,13 +140,14 @@ func (f *forgeFetcherContext) checkPipelineFile(c context.Context, config string func (f *forgeFetcherContext) getFirstAvailableConfig(c context.Context, configs []string) ([]*types.FileMeta, error) { var forgeErr []error for _, fileOrFolder := range configs { + log.Trace().Msgf("fetching %s from forge", fileOrFolder) if strings.HasSuffix(fileOrFolder, "/") { // config is a folder files, err := f.forge.Dir(c, f.user, f.repo, f.pipeline, strings.TrimSuffix(fileOrFolder, "/")) // if folder is not supported we will get a "Not implemented" error and continue if err != nil { if !(errors.Is(err, types.ErrNotImplemented) || errors.Is(err, &types.ErrConfigNotFound{})) { - log.Error().Err(err).Str("repo", f.repo.FullName).Str("user", f.user.Login).Msg("could not get folder from forge") + log.Error().Err(err).Str("repo", f.repo.FullName).Str("user", f.user.Login).Msgf("could not get folder from forge: %s", err) forgeErr = append(forgeErr, err) } continue diff --git a/server/services/config/forge_test.go b/server/services/config/forge_test.go index 6a8651203..898d511e4 100644 --- a/server/services/config/forge_test.go +++ b/server/services/config/forge_test.go @@ -24,10 +24,10 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/mocks" - forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/services/config" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/mocks" + forge_types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/services/config" ) func TestFetch(t *testing.T) { @@ -287,7 +287,7 @@ func TestFetch(t *testing.T) { f := new(mocks.Forge) dirs := map[string][]*forge_types.FileMeta{} for _, file := range tt.files { - f.On("File", mock.Anything, mock.Anything, mock.Anything, mock.Anything, file.name).Return(file.data, nil) + f.On("File", mock.Anything, mock.Anything, mock.Anything, mock.Anything, file.name).Once().Return(file.data, nil) path := filepath.Dir(file.name) if path != "." { dirs[path] = append(dirs[path], &forge_types.FileMeta{ @@ -298,7 +298,7 @@ func TestFetch(t *testing.T) { } for path, files := range dirs { - f.On("Dir", mock.Anything, mock.Anything, mock.Anything, mock.Anything, path).Return(files, nil) + f.On("Dir", mock.Anything, mock.Anything, mock.Anything, mock.Anything, path).Once().Return(files, nil) } // if the previous mocks do not match return not found errors @@ -312,7 +312,7 @@ func TestFetch(t *testing.T) { files, err := configFetcher.Fetch( context.Background(), f, - &model.User{Token: "xxx"}, + &model.User{AccessToken: "xxx"}, repo, &model.Pipeline{Commit: "89ab7b2d6bfb347144ac7c557e638ab402848fee"}, nil, diff --git a/server/services/config/http.go b/server/services/config/http.go index de3a0844a..9e6c04f8e 100644 --- a/server/services/config/http.go +++ b/server/services/config/http.go @@ -16,19 +16,19 @@ package config import ( "context" - "crypto" + "crypto/ed25519" "fmt" net_http "net/http" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/services/utils" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/services/utils" ) type http struct { endpoint string - privateKey crypto.PrivateKey + privateKey ed25519.PrivateKey } // configData same as forge.FileMeta but with json tags and string data. @@ -38,26 +38,20 @@ type configData struct { } type requestStructure struct { - Repo *model.Repo `json:"repo"` - Pipeline *model.Pipeline `json:"pipeline"` - Netrc *model.Netrc `json:"netrc"` - Configuration []*configData `json:"configs"` // TODO: deprecate in favor of netrc and remove in next major release + Repo *model.Repo `json:"repo"` + Pipeline *model.Pipeline `json:"pipeline"` + Netrc *model.Netrc `json:"netrc"` } type responseStructure struct { Configs []*configData `json:"configs"` } -func NewHTTP(endpoint string, privateKey crypto.PrivateKey) Service { +func NewHTTP(endpoint string, privateKey ed25519.PrivateKey) Service { return &http{endpoint, privateKey} } func (h *http) Fetch(ctx context.Context, forge forge.Forge, user *model.User, repo *model.Repo, pipeline *model.Pipeline, oldConfigData []*types.FileMeta, _ bool) ([]*types.FileMeta, error) { - currentConfigs := make([]*configData, len(oldConfigData)) - for i, pipe := range oldConfigData { - currentConfigs[i] = &configData{Name: pipe.Name, Data: string(pipe.Data)} - } - netrc, err := forge.Netrc(user, repo) if err != nil { return nil, fmt.Errorf("could not get Netrc data from forge: %w", err) @@ -65,10 +59,9 @@ func (h *http) Fetch(ctx context.Context, forge forge.Forge, user *model.User, r response := new(responseStructure) body := requestStructure{ - Repo: repo, - Pipeline: pipeline, - Configuration: currentConfigs, - Netrc: netrc, + Repo: repo, + Pipeline: pipeline, + Netrc: netrc, } status, err := utils.Send(ctx, net_http.MethodPost, h.endpoint, h.privateKey, body, response) diff --git a/server/services/config/mocks/service.go b/server/services/config/mocks/service.go index 3f08d71c7..ca6989af1 100644 --- a/server/services/config/mocks/service.go +++ b/server/services/config/mocks/service.go @@ -6,11 +6,11 @@ import ( context "context" mock "github.com/stretchr/testify/mock" - forge "go.woodpecker-ci.org/woodpecker/v2/server/forge" + forge "go.woodpecker-ci.org/woodpecker/v3/server/forge" - model "go.woodpecker-ci.org/woodpecker/v2/server/model" + model "go.woodpecker-ci.org/woodpecker/v3/server/model" - types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" + types "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" ) // Service is an autogenerated mock type for the Service type diff --git a/server/services/config/service.go b/server/services/config/service.go index 9f857a796..b1d100d7a 100644 --- a/server/services/config/service.go +++ b/server/services/config/service.go @@ -17,9 +17,9 @@ package config import ( "context" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/forge/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) //go:generate mockery --name Service --output mocks --case underscore diff --git a/server/services/encryption/aes.go b/server/services/encryption/aes.go index 1cae9dd38..00d6e3983 100644 --- a/server/services/encryption/aes.go +++ b/server/services/encryption/aes.go @@ -21,8 +21,8 @@ import ( "github.com/google/tink/go/subtle/random" - "go.woodpecker-ci.org/woodpecker/v2/server/services/encryption/types" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/services/encryption/types" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) type aesEncryptionService struct { diff --git a/server/services/encryption/aes_builder.go b/server/services/encryption/aes_builder.go index 1da6df8a0..e742b2e6c 100644 --- a/server/services/encryption/aes_builder.go +++ b/server/services/encryption/aes_builder.go @@ -18,10 +18,10 @@ import ( "errors" "fmt" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/server/services/encryption/types" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/services/encryption/types" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) type aesConfiguration struct { @@ -30,8 +30,8 @@ type aesConfiguration struct { clients []types.EncryptionClient } -func newAES(ctx *cli.Context, s store.Store) types.EncryptionServiceBuilder { - key := ctx.String(rawKeyConfigFlag) +func newAES(c *cli.Command, s store.Store) types.EncryptionServiceBuilder { + key := c.String(rawKeyConfigFlag) return &aesConfiguration{key, s, nil} } diff --git a/server/services/encryption/aes_encryption.go b/server/services/encryption/aes_encryption.go index b7b5016ff..3eb1ce1c8 100644 --- a/server/services/encryption/aes_encryption.go +++ b/server/services/encryption/aes_encryption.go @@ -23,7 +23,7 @@ import ( "golang.org/x/crypto/bcrypt" "golang.org/x/crypto/sha3" - "go.woodpecker-ci.org/woodpecker/v2/server/store/types" + "go.woodpecker-ci.org/woodpecker/v3/server/store/types" ) func (svc *aesEncryptionService) loadCipher(password string) error { diff --git a/server/services/encryption/encryption.go b/server/services/encryption/encryption.go index 4548aa8a3..a6c9ded1e 100644 --- a/server/services/encryption/encryption.go +++ b/server/services/encryption/encryption.go @@ -17,20 +17,20 @@ package encryption import ( "fmt" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/server/services/encryption/types" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/services/encryption/types" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) type builder struct { store store.Store - ctx *cli.Context + c *cli.Command clients []types.EncryptionClient } -func Encryption(ctx *cli.Context, s store.Store) types.EncryptionBuilder { - return &builder{store: s, ctx: ctx} +func Encryption(c *cli.Command, s store.Store) types.EncryptionBuilder { + return &builder{store: s, c: c} } func (b builder) WithClient(client types.EncryptionClient) types.EncryptionBuilder { @@ -44,7 +44,7 @@ func (b builder) Build() error { return err } - disableFlag := b.ctx.Bool(disableEncryptionConfigFlag) + disableFlag := b.c.Bool(disableEncryptionConfigFlag) keyType, err := b.detectKeyType() if err != nil { diff --git a/server/services/encryption/encryption_builder.go b/server/services/encryption/encryption_builder.go index 700fbdb77..2ccf14b8f 100644 --- a/server/services/encryption/encryption_builder.go +++ b/server/services/encryption/encryption_builder.go @@ -18,8 +18,8 @@ import ( "errors" "fmt" - "go.woodpecker-ci.org/woodpecker/v2/server/services/encryption/types" - storeTypes "go.woodpecker-ci.org/woodpecker/v2/server/store/types" + "go.woodpecker-ci.org/woodpecker/v3/server/services/encryption/types" + storeTypes "go.woodpecker-ci.org/woodpecker/v3/server/store/types" ) func (b builder) getService(keyType string) (types.EncryptionService, error) { @@ -48,8 +48,8 @@ func (b builder) isEnabled() (bool, error) { } func (b builder) detectKeyType() (string, error) { - rawKeyPresent := b.ctx.IsSet(rawKeyConfigFlag) - tinkKeysetPresent := b.ctx.IsSet(tinkKeysetFilepathConfigFlag) + rawKeyPresent := b.c.IsSet(rawKeyConfigFlag) + tinkKeysetPresent := b.c.IsSet(tinkKeysetFilepathConfigFlag) switch { case rawKeyPresent && tinkKeysetPresent: return "", errors.New(errMessageCantUseBothServices) @@ -64,9 +64,9 @@ func (b builder) detectKeyType() (string, error) { func (b builder) serviceBuilder(keyType string) (types.EncryptionServiceBuilder, error) { switch { case keyType == keyTypeTink: - return newTink(b.ctx, b.store), nil + return newTink(b.c, b.store), nil case keyType == keyTypeRaw: - return newAES(b.ctx, b.store), nil + return newAES(b.c, b.store), nil case keyType == keyTypeNone: return &noEncryptionBuilder{}, nil } diff --git a/server/services/encryption/no_encryption.go b/server/services/encryption/no_encryption.go index 6bc9230d0..dd81865ba 100644 --- a/server/services/encryption/no_encryption.go +++ b/server/services/encryption/no_encryption.go @@ -14,7 +14,7 @@ package encryption -import "go.woodpecker-ci.org/woodpecker/v2/server/services/encryption/types" +import "go.woodpecker-ci.org/woodpecker/v3/server/services/encryption/types" type noEncryptionBuilder struct { clients []types.EncryptionClient diff --git a/server/services/encryption/tink.go b/server/services/encryption/tink.go index 3c210ac7a..033315be7 100644 --- a/server/services/encryption/tink.go +++ b/server/services/encryption/tink.go @@ -21,8 +21,8 @@ import ( "github.com/fsnotify/fsnotify" "github.com/google/tink/go/tink" - "go.woodpecker-ci.org/woodpecker/v2/server/services/encryption/types" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/services/encryption/types" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) type tinkEncryptionService struct { diff --git a/server/services/encryption/tink_builder.go b/server/services/encryption/tink_builder.go index 9dd1ddd79..6bb9457c1 100644 --- a/server/services/encryption/tink_builder.go +++ b/server/services/encryption/tink_builder.go @@ -18,10 +18,10 @@ import ( "errors" "fmt" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/server/services/encryption/types" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/services/encryption/types" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) type tinkConfiguration struct { @@ -30,8 +30,8 @@ type tinkConfiguration struct { clients []types.EncryptionClient } -func newTink(ctx *cli.Context, s store.Store) types.EncryptionServiceBuilder { - filepath := ctx.String(tinkKeysetFilepathConfigFlag) +func newTink(c *cli.Command, s store.Store) types.EncryptionServiceBuilder { + filepath := c.String(tinkKeysetFilepathConfigFlag) return &tinkConfiguration{filepath, s, nil} } diff --git a/server/services/encryption/tink_keyset.go b/server/services/encryption/tink_keyset.go index aaf7b84ae..a3e7ae4b3 100644 --- a/server/services/encryption/tink_keyset.go +++ b/server/services/encryption/tink_keyset.go @@ -25,7 +25,7 @@ import ( "github.com/google/tink/go/keyset" "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server/store/types" + "go.woodpecker-ci.org/woodpecker/v3/server/store/types" ) func (svc *tinkEncryptionService) loadKeyset() error { diff --git a/server/services/encryption/wrapper/store/secret_store.go b/server/services/encryption/wrapper/store/secret_store.go index b2ba26a38..36f58c194 100644 --- a/server/services/encryption/wrapper/store/secret_store.go +++ b/server/services/encryption/wrapper/store/secret_store.go @@ -17,7 +17,7 @@ package store import ( "fmt" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func (wrapper *EncryptedSecretStore) SecretFind(repo *model.Repo, s string) (*model.Secret, error) { diff --git a/server/services/encryption/wrapper/store/secret_store_wrapper.go b/server/services/encryption/wrapper/store/secret_store_wrapper.go index e88cc51e3..92707ebc1 100644 --- a/server/services/encryption/wrapper/store/secret_store_wrapper.go +++ b/server/services/encryption/wrapper/store/secret_store_wrapper.go @@ -21,8 +21,8 @@ import ( "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/services/encryption/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/services/encryption/types" ) type EncryptedSecretStore struct { diff --git a/server/services/environment/mocks/service.go b/server/services/environment/mocks/service.go index b7cb0776d..9d85dff54 100644 --- a/server/services/environment/mocks/service.go +++ b/server/services/environment/mocks/service.go @@ -4,7 +4,7 @@ package mocks import ( mock "github.com/stretchr/testify/mock" - model "go.woodpecker-ci.org/woodpecker/v2/server/model" + model "go.woodpecker-ci.org/woodpecker/v3/server/model" ) // Service is an autogenerated mock type for the Service type diff --git a/server/services/environment/parse.go b/server/services/environment/parse.go index 0a1ccb0c9..e7cbe7e74 100644 --- a/server/services/environment/parse.go +++ b/server/services/environment/parse.go @@ -19,7 +19,7 @@ import ( "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) type builtin struct { diff --git a/server/services/environment/service.go b/server/services/environment/service.go index 0b63471fd..ac8bfff98 100644 --- a/server/services/environment/service.go +++ b/server/services/environment/service.go @@ -14,7 +14,7 @@ package environment -import "go.woodpecker-ci.org/woodpecker/v2/server/model" +import "go.woodpecker-ci.org/woodpecker/v3/server/model" //go:generate mockery --name Service --output mocks --case underscore diff --git a/server/services/log/file/file.go b/server/services/log/file/file.go index 276c31798..7a24dc5f7 100644 --- a/server/services/log/file/file.go +++ b/server/services/log/file/file.go @@ -8,8 +8,16 @@ import ( "path/filepath" "strings" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/services/log" + logger "github.com/rs/zerolog/log" + + "go.woodpecker-ci.org/woodpecker/v3/pipeline" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/services/log" +) + +const ( + // Add base64 overhead and space for other JSON fields (just to be safe). + maxLineLength int = (pipeline.MaxLogLineLength/3)*4 + (64 * 1024) //nolint:mnd ) type logStore struct { @@ -43,7 +51,10 @@ func (l logStore) LogFind(step *model.Step) ([]*model.LogEntry, error) { return nil, err } + buf := make([]byte, 0, bufio.MaxScanTokenSize) s := bufio.NewScanner(file) + s.Buffer(buf, maxLineLength) + var entries []*model.LogEntry for s.Scan() { j := s.Text() @@ -61,19 +72,30 @@ func (l logStore) LogFind(step *model.Step) ([]*model.LogEntry, error) { return entries, nil } -func (l logStore) LogAppend(logEntry *model.LogEntry) error { - file, err := os.OpenFile(l.filePath(logEntry.StepID), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600) +func (l logStore) LogAppend(step *model.Step, logEntries []*model.LogEntry) error { + path := l.filePath(step.ID) + + file, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0o600) if err != nil { + logger.Error().Err(err).Msgf("could not open log file %s", path) return err } - jsonData, err := json.Marshal(logEntry) - if err != nil { - return err + + var bytes []byte + + for _, logEntry := range logEntries { + if jsonLine, err := json.Marshal(logEntry); err == nil { + bytes = append(bytes, jsonLine...) + bytes = append(bytes, byte('\n')) + } else { + logger.Error().Err(err).Msg("could not convert log entry to JSON") + } } - _, err = file.Write(append(jsonData, byte('\n'))) - if err != nil { - return err + + if _, err = file.Write(bytes); err != nil { + logger.Error().Err(err).Msg("could not write out log entries") } + return file.Close() } diff --git a/server/services/log/service.go b/server/services/log/service.go index 5cc53d1e9..e922b89ee 100644 --- a/server/services/log/service.go +++ b/server/services/log/service.go @@ -1,9 +1,9 @@ package log -import "go.woodpecker-ci.org/woodpecker/v2/server/model" +import "go.woodpecker-ci.org/woodpecker/v3/server/model" type Service interface { LogFind(step *model.Step) ([]*model.LogEntry, error) - LogAppend(logEntry *model.LogEntry) error + LogAppend(step *model.Step, logEntries []*model.LogEntry) error LogDelete(step *model.Step) error } diff --git a/server/services/manager.go b/server/services/manager.go index 3107f827d..ca101f2bf 100644 --- a/server/services/manager.go +++ b/server/services/manager.go @@ -19,15 +19,15 @@ import ( "time" "github.com/jellydator/ttlcache/v3" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/server/forge" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/services/config" - "go.woodpecker-ci.org/woodpecker/v2/server/services/environment" - "go.woodpecker-ci.org/woodpecker/v2/server/services/registry" - "go.woodpecker-ci.org/woodpecker/v2/server/services/secret" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/forge" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/services/config" + "go.woodpecker-ci.org/woodpecker/v3/server/services/environment" + "go.woodpecker-ci.org/woodpecker/v3/server/services/registry" + "go.woodpecker-ci.org/woodpecker/v3/server/services/secret" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) //go:generate mockery --name Manager --output mocks --case underscore --note "+build test" @@ -61,7 +61,7 @@ type manager struct { setupForge SetupForge } -func NewManager(c *cli.Context, store store.Store, setupForge SetupForge) (Manager, error) { +func NewManager(c *cli.Command, store store.Store, setupForge SetupForge) (Manager, error) { signaturePrivateKey, signaturePublicKey, err := setupSignatureKeys(store) if err != nil { return nil, err diff --git a/server/services/mocks/manager.go b/server/services/mocks/manager.go index 1d94cdec8..c2e071296 100644 --- a/server/services/mocks/manager.go +++ b/server/services/mocks/manager.go @@ -8,19 +8,19 @@ package mocks import ( crypto "crypto" - config "go.woodpecker-ci.org/woodpecker/v2/server/services/config" + config "go.woodpecker-ci.org/woodpecker/v3/server/services/config" - environment "go.woodpecker-ci.org/woodpecker/v2/server/services/environment" + environment "go.woodpecker-ci.org/woodpecker/v3/server/services/environment" - forge "go.woodpecker-ci.org/woodpecker/v2/server/forge" + forge "go.woodpecker-ci.org/woodpecker/v3/server/forge" mock "github.com/stretchr/testify/mock" - model "go.woodpecker-ci.org/woodpecker/v2/server/model" + model "go.woodpecker-ci.org/woodpecker/v3/server/model" - registry "go.woodpecker-ci.org/woodpecker/v2/server/services/registry" + registry "go.woodpecker-ci.org/woodpecker/v3/server/services/registry" - secret "go.woodpecker-ci.org/woodpecker/v2/server/services/secret" + secret "go.woodpecker-ci.org/woodpecker/v3/server/services/secret" ) // Manager is an autogenerated mock type for the Manager type @@ -48,7 +48,7 @@ func (_m *Manager) ConfigServiceFromRepo(repo *model.Repo) config.Service { return r0 } -// EnvironmentService provides a mock function with given fields: +// EnvironmentService provides a mock function with no fields func (_m *Manager) EnvironmentService() environment.Service { ret := _m.Called() @@ -158,7 +158,7 @@ func (_m *Manager) ForgeFromUser(user *model.User) (forge.Forge, error) { return r0, r1 } -// RegistryService provides a mock function with given fields: +// RegistryService provides a mock function with no fields func (_m *Manager) RegistryService() registry.Service { ret := _m.Called() @@ -198,7 +198,7 @@ func (_m *Manager) RegistryServiceFromRepo(repo *model.Repo) registry.Service { return r0 } -// SecretService provides a mock function with given fields: +// SecretService provides a mock function with no fields func (_m *Manager) SecretService() secret.Service { ret := _m.Called() @@ -238,7 +238,7 @@ func (_m *Manager) SecretServiceFromRepo(repo *model.Repo) secret.Service { return r0 } -// SignaturePublicKey provides a mock function with given fields: +// SignaturePublicKey provides a mock function with no fields func (_m *Manager) SignaturePublicKey() crypto.PublicKey { ret := _m.Called() diff --git a/server/services/permissions/admins.go b/server/services/permissions/admins.go index cccad77c8..86bd5500a 100644 --- a/server/services/permissions/admins.go +++ b/server/services/permissions/admins.go @@ -1,8 +1,8 @@ package permissions import ( - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/shared/utils" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/shared/utils" ) func NewAdmins(admins []string) *Admins { diff --git a/server/services/permissions/admins_test.go b/server/services/permissions/admins_test.go index d1e590580..f7cbb55b9 100644 --- a/server/services/permissions/admins_test.go +++ b/server/services/permissions/admins_test.go @@ -5,7 +5,7 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func TestAdmins(t *testing.T) { diff --git a/server/services/permissions/orgs.go b/server/services/permissions/orgs.go index 370b6e170..312ed8ea9 100644 --- a/server/services/permissions/orgs.go +++ b/server/services/permissions/orgs.go @@ -1,8 +1,8 @@ package permissions import ( - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/shared/utils" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/shared/utils" ) func NewOrgs(orgs []string) *Orgs { diff --git a/server/services/permissions/orgs_test.go b/server/services/permissions/orgs_test.go index 431a52c96..e7ead62b6 100644 --- a/server/services/permissions/orgs_test.go +++ b/server/services/permissions/orgs_test.go @@ -5,7 +5,7 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func TestOrgs(t *testing.T) { diff --git a/server/services/permissions/repo_owners.go b/server/services/permissions/repo_owners.go index 2610ce8a3..07db15a1e 100644 --- a/server/services/permissions/repo_owners.go +++ b/server/services/permissions/repo_owners.go @@ -1,8 +1,8 @@ package permissions import ( - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/shared/utils" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/shared/utils" ) func NewOwnersAllowlist(owners []string) *OwnersAllowlist { diff --git a/server/services/permissions/repo_owners_test.go b/server/services/permissions/repo_owners_test.go index c34821255..77206eb79 100644 --- a/server/services/permissions/repo_owners_test.go +++ b/server/services/permissions/repo_owners_test.go @@ -5,7 +5,7 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func TestOwnersAllowlist(t *testing.T) { diff --git a/server/services/registry/combined.go b/server/services/registry/combined.go index bdb249bcd..0cb551156 100644 --- a/server/services/registry/combined.go +++ b/server/services/registry/combined.go @@ -17,8 +17,8 @@ package registry import ( "errors" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store/types" ) type combined struct { diff --git a/server/services/registry/db.go b/server/services/registry/db.go index 6e566d27c..efdb2c8c4 100644 --- a/server/services/registry/db.go +++ b/server/services/registry/db.go @@ -15,8 +15,8 @@ package registry import ( - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) type db struct { diff --git a/server/services/registry/filesystem.go b/server/services/registry/filesystem.go index 959c69834..cfcb0c883 100644 --- a/server/services/registry/filesystem.go +++ b/server/services/registry/filesystem.go @@ -24,8 +24,8 @@ import ( config_file "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/config/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - model_types "go.woodpecker-ci.org/woodpecker/v2/server/store/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + model_types "go.woodpecker-ci.org/woodpecker/v3/server/store/types" ) type filesystem struct { diff --git a/server/services/registry/mocks/service.go b/server/services/registry/mocks/service.go index ecce0a414..49cf1e8e2 100644 --- a/server/services/registry/mocks/service.go +++ b/server/services/registry/mocks/service.go @@ -4,7 +4,7 @@ package mocks import ( mock "github.com/stretchr/testify/mock" - model "go.woodpecker-ci.org/woodpecker/v2/server/model" + model "go.woodpecker-ci.org/woodpecker/v3/server/model" ) // Service is an autogenerated mock type for the Service type diff --git a/server/services/registry/service.go b/server/services/registry/service.go index 39e1e3fa9..a2fc03979 100644 --- a/server/services/registry/service.go +++ b/server/services/registry/service.go @@ -14,7 +14,7 @@ package registry -import "go.woodpecker-ci.org/woodpecker/v2/server/model" +import "go.woodpecker-ci.org/woodpecker/v3/server/model" //go:generate mockery --name Service --output mocks --case underscore diff --git a/server/services/secret/db.go b/server/services/secret/db.go index 52231ca9e..25e779e3d 100644 --- a/server/services/secret/db.go +++ b/server/services/secret/db.go @@ -15,8 +15,8 @@ package secret import ( - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store" ) type db struct { diff --git a/server/services/secret/db_test.go b/server/services/secret/db_test.go index cd1112373..c966a7f9b 100644 --- a/server/services/secret/db_test.go +++ b/server/services/secret/db_test.go @@ -17,20 +17,16 @@ package secret_test import ( "testing" - "github.com/franela/goblin" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/services/secret" - mocks_store "go.woodpecker-ci.org/woodpecker/v2/server/store/mocks" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/services/secret" + mocks_store "go.woodpecker-ci.org/woodpecker/v3/server/store/mocks" ) -func TestSecretListPipeline(t *testing.T) { - g := goblin.Goblin(t) - mockStore := mocks_store.NewStore(t) - - // global secret - globalSecret := &model.Secret{ +var ( + globalSecret = &model.Secret{ ID: 1, OrgID: 0, RepoID: 0, @@ -38,8 +34,7 @@ func TestSecretListPipeline(t *testing.T) { Value: "value-global", } - // org secret - orgSecret := &model.Secret{ + orgSecret = &model.Secret{ ID: 2, OrgID: 1, RepoID: 0, @@ -47,53 +42,48 @@ func TestSecretListPipeline(t *testing.T) { Value: "value-org", } - // repo secret - repoSecret := &model.Secret{ + repoSecret = &model.Secret{ ID: 3, OrgID: 0, RepoID: 1, Name: "secret", Value: "value-repo", } +) - g.Describe("Priority of secrets", func() { - g.It("should get the repo secret", func() { - mockStore.On("SecretList", mock.Anything, mock.Anything, mock.Anything).Once().Return([]*model.Secret{ - globalSecret, - orgSecret, - repoSecret, - }, nil) +func TestSecretListPipeline(t *testing.T) { + mockStore := mocks_store.NewStore(t) - s, err := secret.NewDB(mockStore).SecretListPipeline(&model.Repo{}, &model.Pipeline{}) - g.Assert(err).IsNil() + mockStore.On("SecretList", mock.Anything, mock.Anything, mock.Anything).Once().Return([]*model.Secret{ + globalSecret, + orgSecret, + repoSecret, + }, nil) - g.Assert(len(s)).Equal(1) - g.Assert(s[0].Value).Equal("value-repo") - }) + s, err := secret.NewDB(mockStore).SecretListPipeline(&model.Repo{}, &model.Pipeline{}) + assert.NoError(t, err) - g.It("should get the org secret", func() { - mockStore.On("SecretList", mock.Anything, mock.Anything, mock.Anything).Once().Return([]*model.Secret{ - globalSecret, - orgSecret, - }, nil) + assert.Len(t, s, 1) + assert.Equal(t, "value-repo", s[0].Value) - s, err := secret.NewDB(mockStore).SecretListPipeline(&model.Repo{}, &model.Pipeline{}) - g.Assert(err).IsNil() + mockStore.On("SecretList", mock.Anything, mock.Anything, mock.Anything).Once().Return([]*model.Secret{ + globalSecret, + orgSecret, + }, nil) - g.Assert(len(s)).Equal(1) - g.Assert(s[0].Value).Equal("value-org") - }) + s, err = secret.NewDB(mockStore).SecretListPipeline(&model.Repo{}, &model.Pipeline{}) + assert.NoError(t, err) - g.It("should get the global secret", func() { - mockStore.On("SecretList", mock.Anything, mock.Anything, mock.Anything).Once().Return([]*model.Secret{ - globalSecret, - }, nil) + assert.Len(t, s, 1) + assert.Equal(t, "value-org", s[0].Value) - s, err := secret.NewDB(mockStore).SecretListPipeline(&model.Repo{}, &model.Pipeline{}) - g.Assert(err).IsNil() + mockStore.On("SecretList", mock.Anything, mock.Anything, mock.Anything).Once().Return([]*model.Secret{ + globalSecret, + }, nil) - g.Assert(len(s)).Equal(1) - g.Assert(s[0].Value).Equal("value-global") - }) - }) + s, err = secret.NewDB(mockStore).SecretListPipeline(&model.Repo{}, &model.Pipeline{}) + assert.NoError(t, err) + + assert.Len(t, s, 1) + assert.Equal(t, "value-global", s[0].Value) } diff --git a/server/services/secret/mocks/service.go b/server/services/secret/mocks/service.go index 37f301d53..d918d625b 100644 --- a/server/services/secret/mocks/service.go +++ b/server/services/secret/mocks/service.go @@ -4,7 +4,7 @@ package mocks import ( mock "github.com/stretchr/testify/mock" - model "go.woodpecker-ci.org/woodpecker/v2/server/model" + model "go.woodpecker-ci.org/woodpecker/v3/server/model" ) // Service is an autogenerated mock type for the Service type diff --git a/server/services/secret/service.go b/server/services/secret/service.go index c2b25ba0f..8bc691d2e 100644 --- a/server/services/secret/service.go +++ b/server/services/secret/service.go @@ -14,7 +14,7 @@ package secret -import "go.woodpecker-ci.org/woodpecker/v2/server/model" +import "go.woodpecker-ci.org/woodpecker/v3/server/model" //go:generate mockery --name Service --output mocks --case underscore diff --git a/server/services/setup.go b/server/services/setup.go index 305541c02..dca0c4195 100644 --- a/server/services/setup.go +++ b/server/services/setup.go @@ -24,14 +24,14 @@ import ( "strings" "github.com/rs/zerolog/log" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/services/config" - "go.woodpecker-ci.org/woodpecker/v2/server/services/registry" - "go.woodpecker-ci.org/woodpecker/v2/server/services/secret" - "go.woodpecker-ci.org/woodpecker/v2/server/store" - "go.woodpecker-ci.org/woodpecker/v2/server/store/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/services/config" + "go.woodpecker-ci.org/woodpecker/v3/server/services/registry" + "go.woodpecker-ci.org/woodpecker/v3/server/services/secret" + "go.woodpecker-ci.org/woodpecker/v3/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/store/types" ) func setupRegistryService(store store.Store, dockerConfig string) registry.Service { @@ -57,13 +57,13 @@ func setupSecretService(store store.Store) secret.Service { return secret.NewDB(store) } -func setupConfigService(c *cli.Context, privateSignatureKey crypto.PrivateKey) (config.Service, error) { +func setupConfigService(c *cli.Command, privateSignatureKey ed25519.PrivateKey) (config.Service, error) { timeout := c.Duration("forge-timeout") retries := c.Uint("forge-retry") if retries == 0 { return nil, fmt.Errorf("WOODPECKER_FORGE_RETRY can not be 0") } - configFetcher := config.NewForge(timeout, retries) + configFetcher := config.NewForge(timeout, uint(retries)) if endpoint := c.String("config-service-endpoint"); endpoint != "" { httpFetcher := config.NewHTTP(endpoint, privateSignatureKey) @@ -74,7 +74,7 @@ func setupConfigService(c *cli.Context, privateSignatureKey crypto.PrivateKey) ( } // setupSignatureKeys generate or load key pair to sign webhooks requests (i.e. used for service extensions). -func setupSignatureKeys(_store store.Store) (crypto.PrivateKey, crypto.PublicKey, error) { +func setupSignatureKeys(_store store.Store) (ed25519.PrivateKey, crypto.PublicKey, error) { privKeyID := "signature-private-key" privKey, err := _store.ServerConfigGet(privKeyID) @@ -100,7 +100,7 @@ func setupSignatureKeys(_store store.Store) (crypto.PrivateKey, crypto.PublicKey return privateKey, privateKey.Public(), nil } -func setupForgeService(c *cli.Context, _store store.Store) error { +func setupForgeService(c *cli.Command, _store store.Store) error { _forge, err := _store.ForgeGet(1) if err != nil && !errors.Is(err, types.RecordNotExist) { return err diff --git a/server/services/utils/http.go b/server/services/utils/http.go index c11bfe27f..40a50269c 100644 --- a/server/services/utils/http.go +++ b/server/services/utils/http.go @@ -17,19 +17,19 @@ package utils import ( "bytes" "context" - "crypto" + "crypto/ed25519" "encoding/json" "fmt" "io" "net/http" "net/url" - "github.com/go-ap/httpsig" + "github.com/yaronf/httpsign" ) // Send makes an http request to the given endpoint, writing the input // to the request body and un-marshaling the output from the response body. -func Send(ctx context.Context, method, path string, privateKey crypto.PrivateKey, in, out any) (int, error) { +func Send(ctx context.Context, method, path string, privateKey ed25519.PrivateKey, in, out any) (int, error) { uri, err := url.Parse(path) if err != nil { return 0, err @@ -54,12 +54,12 @@ func Send(ctx context.Context, method, path string, privateKey crypto.PrivateKey req.Header.Set("Content-Type", "application/json") } - err = SignHTTPRequest(privateKey, req) + client, err := signClient(privateKey) if err != nil { return 0, err } - resp, err := http.DefaultClient.Do(req) + resp, err := client.Do(req) if err != nil { return 0, err } @@ -79,10 +79,14 @@ func Send(ctx context.Context, method, path string, privateKey crypto.PrivateKey return resp.StatusCode, err } -func SignHTTPRequest(privateKey crypto.PrivateKey, req *http.Request) error { - pubKeyID := "woodpecker-ci-plugins" +func signClient(privateKey ed25519.PrivateKey) (*httpsign.Client, error) { + pubKeyID := "woodpecker-ci-extensions" - signer := httpsig.NewEd25519Signer(pubKeyID, privateKey, nil) - - return signer.Sign(req) + signer, err := httpsign.NewEd25519Signer(privateKey, + httpsign.NewSignConfig(), + httpsign.Headers("@request-target", "content-digest")) // The Content-Digest header will be auto-generated + if err != nil { + return nil, err + } + return httpsign.NewDefaultClient(httpsign.NewClientConfig().SetSignatureName(pubKeyID).SetSigner(signer)), nil // sign requests, don't verify responses } diff --git a/server/services/utils/http_test.go b/server/services/utils/http_test.go index 3b47e8cc7..57d7eece2 100644 --- a/server/services/utils/http_test.go +++ b/server/services/utils/http_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package utils_test +package utils import ( "bytes" @@ -21,15 +21,14 @@ import ( "net/http" "net/http/httptest" "testing" + "time" - "github.com/go-ap/httpsig" "github.com/stretchr/testify/assert" - - "go.woodpecker-ci.org/woodpecker/v2/server/services/utils" + "github.com/yaronf/httpsign" ) -func TestSign(t *testing.T) { - pubKeyID := "woodpecker-ci-plugins" +func TestSignClient(t *testing.T) { + pubKeyID := "woodpecker-ci-extensions" pubEd25519Key, privEd25519Key, err := ed25519.GenerateKey(rand.Reader) if !assert.NoError(t, err) { @@ -38,36 +37,36 @@ func TestSign(t *testing.T) { body := []byte("{\"foo\":\"bar\"}") - req, err := http.NewRequest(http.MethodGet, "http://example.com", bytes.NewBuffer(body)) - if err != nil { - t.Fatal(err) - } - - req.Header.Set("Content-Type", "application/json") - - err = utils.SignHTTPRequest(privEd25519Key, req) - if err != nil { - t.Fatal(err) - } - verifyHandler := func(w http.ResponseWriter, r *http.Request) { - keystore := httpsig.NewMemoryKeyStore() - keystore.SetKey(pubKeyID, pubEd25519Key) - - verifier := httpsig.NewVerifier(keystore) - verifier.SetRequiredHeaders([]string{"(request-target)", "date"}) - - keyID, err := verifier.Verify(r) + verifier, err := httpsign.NewEd25519Verifier(pubEd25519Key, + httpsign.NewVerifyConfig(), + httpsign.Headers("@request-target", "content-digest")) // The Content-Digest header will be auto-generated + assert.NoError(t, err) + + err = httpsign.VerifyRequest(pubKeyID, *verifier, r) assert.NoError(t, err) - assert.Equal(t, pubKeyID, keyID) w.WriteHeader(http.StatusOK) } - rr := httptest.NewRecorder() - handler := http.HandlerFunc(verifyHandler) + server := httptest.NewServer(http.HandlerFunc(verifyHandler)) - handler.ServeHTTP(rr, req) + req, err := http.NewRequest("GET", server.URL+"/", bytes.NewBuffer(body)) + if !assert.NoError(t, err) { + return + } - assert.Equal(t, http.StatusOK, rr.Code) + req.Header.Set("Date", time.Now().Format(time.RFC3339)) + req.Header.Set("Content-Type", "application/json") + + client, err := signClient(privEd25519Key) + if !assert.NoError(t, err) { + return + } + + rr, err := client.Do(req) + assert.NoError(t, err) + defer rr.Body.Close() + + assert.Equal(t, http.StatusOK, rr.StatusCode) } diff --git a/server/store/common.go b/server/store/common.go index 4a051d838..025cf82cc 100644 --- a/server/store/common.go +++ b/server/store/common.go @@ -14,9 +14,14 @@ package store +import "time" + type XORM struct { - Log bool - ShowSQL bool + Log bool + ShowSQL bool + MaxIdleConns int + MaxOpenConns int + ConnMaxLifetime time.Duration } // Opts are options for a new database connection. diff --git a/server/store/datastore/agent.go b/server/store/datastore/agent.go index 383692289..d3289abd9 100644 --- a/server/store/datastore/agent.go +++ b/server/store/datastore/agent.go @@ -17,13 +17,12 @@ package datastore import ( "errors" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) var ErrNoTokenProvided = errors.New("please provide a token") -func (s storage) AgentList(p *model.ListOptions) ([]*model.Agent, error) { - var agents []*model.Agent +func (s storage) AgentList(p *model.ListOptions) (agents []*model.Agent, _ error) { return agents, s.paginate(p).OrderBy("id").Find(&agents) } @@ -55,3 +54,7 @@ func (s storage) AgentUpdate(agent *model.Agent) error { func (s storage) AgentDelete(agent *model.Agent) error { return wrapDelete(s.engine.ID(agent.ID).Delete(new(model.Agent))) } + +func (s storage) AgentListForOrg(orgID int64, p *model.ListOptions) (agents []*model.Agent, _ error) { + return agents, s.paginate(p).Where("org_id = ?", orgID).OrderBy("id").Find(&agents) +} diff --git a/server/store/datastore/agent_test.go b/server/store/datastore/agent_test.go index f08a5ee74..71495b460 100644 --- a/server/store/datastore/agent_test.go +++ b/server/store/datastore/agent_test.go @@ -19,7 +19,7 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func TestAgentFindByToken(t *testing.T) { @@ -65,14 +65,12 @@ func TestAgentList(t *testing.T) { defer closer() agent1 := &model.Agent{ - ID: int64(1), - Name: "test-1", - Token: "secret-token-1", + ID: int64(1), + Name: "test-1", } agent2 := &model.Agent{ - ID: int64(2), - Name: "test-2", - Token: "secret-token-2", + ID: int64(2), + Name: "test-2", } err := store.AgentCreate(agent1) assert.NoError(t, err) @@ -106,3 +104,43 @@ func TestAgentUpdate(t *testing.T) { err = store.AgentUpdate(agent) assert.NoError(t, err) } + +func TestAgentListForOrg(t *testing.T) { + store, closer := newTestStore(t, new(model.Agent)) + defer closer() + + agent1 := &model.Agent{ + ID: int64(1), + Name: "test-1", + OrgID: int64(100), + } + agent2 := &model.Agent{ + ID: int64(2), + Name: "test-2", + OrgID: int64(100), + } + agent3 := &model.Agent{ + ID: int64(3), + Name: "test-3", + OrgID: int64(200), + } + assert.NoError(t, store.AgentCreate(agent1)) + assert.NoError(t, store.AgentCreate(agent2)) + assert.NoError(t, store.AgentCreate(agent3)) + + agents, err := store.AgentListForOrg(100, &model.ListOptions{All: true}) + assert.NoError(t, err) + assert.Equal(t, 2, len(agents)) + assert.Equal(t, "test-1", agents[0].Name) + assert.Equal(t, "test-2", agents[1].Name) + + agents, err = store.AgentListForOrg(200, &model.ListOptions{All: true}) + assert.NoError(t, err) + assert.Equal(t, 1, len(agents)) + assert.Equal(t, "test-3", agents[0].Name) + + agents, err = store.AgentListForOrg(100, &model.ListOptions{Page: 1, PerPage: 1}) + assert.NoError(t, err) + assert.Equal(t, 1, len(agents)) + assert.Equal(t, "test-1", agents[0].Name) +} diff --git a/server/store/datastore/config.go b/server/store/datastore/config.go index b16c415a8..765d5ed86 100644 --- a/server/store/datastore/config.go +++ b/server/store/datastore/config.go @@ -22,8 +22,8 @@ import ( "xorm.io/builder" "xorm.io/xorm" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store/types" ) func (s storage) ConfigsForPipeline(pipelineID int64) ([]*model.Config, error) { diff --git a/server/store/datastore/config_test.go b/server/store/datastore/config_test.go index a08c789df..81a0114d0 100644 --- a/server/store/datastore/config_test.go +++ b/server/store/datastore/config_test.go @@ -19,7 +19,7 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) var ( diff --git a/server/store/datastore/cron.go b/server/store/datastore/cron.go index ce2609269..e790c74c9 100644 --- a/server/store/datastore/cron.go +++ b/server/store/datastore/cron.go @@ -17,7 +17,7 @@ package datastore import ( "xorm.io/builder" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func (s storage) CronCreate(cron *model.Cron) error { @@ -35,7 +35,7 @@ func (s storage) CronFind(repo *model.Repo, id int64) (*model.Cron, error) { func (s storage) CronList(repo *model.Repo, p *model.ListOptions) ([]*model.Cron, error) { var crons []*model.Cron - return crons, s.paginate(p).Where("repo_id = ?", repo.ID).Find(&crons) + return crons, s.paginate(p).Where("repo_id = ?", repo.ID).OrderBy("name").Find(&crons) } func (s storage) CronUpdate(_ *model.Repo, cron *model.Cron) error { diff --git a/server/store/datastore/cron_test.go b/server/store/datastore/cron_test.go index 28d9a7a59..88710f91f 100644 --- a/server/store/datastore/cron_test.go +++ b/server/store/datastore/cron_test.go @@ -20,7 +20,7 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func TestCronCreate(t *testing.T) { diff --git a/server/store/datastore/engine.go b/server/store/datastore/engine.go index bee837a1d..e7494b484 100644 --- a/server/store/datastore/engine.go +++ b/server/store/datastore/engine.go @@ -15,12 +15,14 @@ package datastore import ( + "context" + "github.com/rs/zerolog" "xorm.io/xorm" xlog "xorm.io/xorm/log" - "go.woodpecker-ci.org/woodpecker/v2/server/store" - "go.woodpecker-ci.org/woodpecker/v2/server/store/datastore/migration" + "go.woodpecker-ci.org/woodpecker/v3/server/store" + "go.woodpecker-ci.org/woodpecker/v3/server/store/datastore/migration" ) type storage struct { @@ -43,6 +45,9 @@ func NewEngine(opts *store.Opts) (store.Store, error) { logger := newXORMLogger(level) engine.SetLogger(logger) engine.ShowSQL(opts.XORM.ShowSQL) + engine.SetMaxOpenConns(opts.XORM.MaxOpenConns) + engine.SetMaxIdleConns(opts.XORM.MaxIdleConns) + engine.SetConnMaxLifetime(opts.XORM.ConnMaxLifetime) return &storage{ engine: engine, @@ -54,8 +59,8 @@ func (s storage) Ping() error { } // Migrate old storage or init new one. -func (s storage) Migrate(allowLong bool) error { - return migration.Migrate(s.engine, allowLong) +func (s storage) Migrate(ctx context.Context, allowLong bool) error { + return migration.Migrate(ctx, s.engine, allowLong) } func (s storage) Close() error { diff --git a/server/store/datastore/errors.go b/server/store/datastore/errors.go index a7d89388a..c2668e8aa 100644 --- a/server/store/datastore/errors.go +++ b/server/store/datastore/errors.go @@ -17,7 +17,7 @@ package datastore import ( "fmt" - "go.woodpecker-ci.org/woodpecker/v2/server/store/types" + "go.woodpecker-ci.org/woodpecker/v3/server/store/types" ) type ErrorRepoNotExist struct { diff --git a/server/store/datastore/feed.go b/server/store/datastore/feed.go index d06897bf8..8be597f05 100644 --- a/server/store/datastore/feed.go +++ b/server/store/datastore/feed.go @@ -17,7 +17,7 @@ package datastore import ( "xorm.io/builder" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) var feedItemSelect = `repos.id as repo_id, diff --git a/server/store/datastore/feed_test.go b/server/store/datastore/feed_test.go index 7d44eda74..d8adb8c53 100644 --- a/server/store/datastore/feed_test.go +++ b/server/store/datastore/feed_test.go @@ -19,7 +19,7 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func TestGetPipelineQueue(t *testing.T) { @@ -27,9 +27,9 @@ func TestGetPipelineQueue(t *testing.T) { defer closer() user := &model.User{ - Login: "joe", - Email: "foo@bar.com", - Token: "e42080dddf012c718e476da161d21ad5", + Login: "joe", + Email: "foo@bar.com", + AccessToken: "e42080dddf012c718e476da161d21ad5", } assert.NoError(t, store.CreateUser(user)) @@ -63,9 +63,9 @@ func TestUserFeed(t *testing.T) { defer closer() user := &model.User{ - Login: "joe", - Email: "foo@bar.com", - Token: "e42080dddf012c718e476da161d21ad5", + Login: "joe", + Email: "foo@bar.com", + AccessToken: "e42080dddf012c718e476da161d21ad5", } assert.NoError(t, store.CreateUser(user)) @@ -109,9 +109,9 @@ func TestRepoListLatest(t *testing.T) { defer closer() user := &model.User{ - Login: "joe", - Email: "foo@bar.com", - Token: "e42080dddf012c718e476da161d21ad5", + Login: "joe", + Email: "foo@bar.com", + AccessToken: "e42080dddf012c718e476da161d21ad5", } assert.NoError(t, store.CreateUser(user)) diff --git a/server/store/datastore/forge.go b/server/store/datastore/forge.go index 5c1a29efc..8e30bd974 100644 --- a/server/store/datastore/forge.go +++ b/server/store/datastore/forge.go @@ -15,7 +15,7 @@ package datastore import ( - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func (s storage) ForgeGet(id int64) (*model.Forge, error) { diff --git a/server/store/datastore/forge_test.go b/server/store/datastore/forge_test.go index a7583d8db..1f163bf08 100644 --- a/server/store/datastore/forge_test.go +++ b/server/store/datastore/forge_test.go @@ -19,7 +19,7 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func TestForgeCRUD(t *testing.T) { diff --git a/server/store/datastore/helper.go b/server/store/datastore/helper.go index a883cf516..4f2c2650a 100644 --- a/server/store/datastore/helper.go +++ b/server/store/datastore/helper.go @@ -21,8 +21,8 @@ import ( "xorm.io/xorm" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store/types" ) // wrapGet return error if err not nil or if requested entry do not exist. @@ -52,7 +52,7 @@ func wrapDelete(c int64, err error) error { } func (s storage) paginate(p *model.ListOptions) *xorm.Session { - if p.All { + if p == nil || p.All { return s.engine.NewSession() } if p.PerPage < 1 { diff --git a/server/store/datastore/helper_test.go b/server/store/datastore/helper_test.go index be8b835e6..c694d46bf 100644 --- a/server/store/datastore/helper_test.go +++ b/server/store/datastore/helper_test.go @@ -20,7 +20,7 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/store/types" + "go.woodpecker-ci.org/woodpecker/v3/server/store/types" ) func TestWrapGet(t *testing.T) { diff --git a/server/store/datastore/log.go b/server/store/datastore/log.go index 68c708c63..ade01c08c 100644 --- a/server/store/datastore/log.go +++ b/server/store/datastore/log.go @@ -15,20 +15,44 @@ package datastore import ( - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "github.com/rs/zerolog/log" + "xorm.io/xorm" + + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) +// Maximum number of records to store in one PostgreSQL statement. +// Too large a value results in `pq: got XX parameters but PostgreSQL only supports 65535 parameters`. +const pgBatchSize = 1000 + func (s storage) LogFind(step *model.Step) ([]*model.LogEntry, error) { var logEntries []*model.LogEntry return logEntries, s.engine.Asc("id").Where("step_id = ?", step.ID).Find(&logEntries) } -func (s storage) LogAppend(logEntry *model.LogEntry) error { - _, err := s.engine.Insert(logEntry) +func (s storage) LogAppend(_ *model.Step, logEntries []*model.LogEntry) error { + var err error + + // TODO: adapted from slices.Chunk(); switch to it in Go 1.23+ + for i := 0; i < len(logEntries); i += pgBatchSize { + end := min(pgBatchSize, len(logEntries[i:])) + chunk := logEntries[i : i+end] + + if _, err = s.engine.Insert(chunk); err != nil { + log.Error().Err(err).Msg("could not store log entries to db") + } + } + return err } func (s storage) LogDelete(step *model.Step) error { - _, err := s.engine.Where("step_id = ?", step.ID).Delete(new(model.LogEntry)) + sess := s.engine.NewSession() + defer sess.Close() + return logDelete(sess, step.ID) +} + +func logDelete(sess *xorm.Session, stepID int64) error { + _, err := sess.Where("step_id = ?", stepID).Delete(new(model.LogEntry)) return err } diff --git a/server/store/datastore/log_test.go b/server/store/datastore/log_test.go index f2ee1ffc6..1c331ff78 100644 --- a/server/store/datastore/log_test.go +++ b/server/store/datastore/log_test.go @@ -19,7 +19,7 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func TestLogCreateFindDelete(t *testing.T) { @@ -45,9 +45,7 @@ func TestLogCreateFindDelete(t *testing.T) { }, } - for _, logEntry := range logEntries { - assert.NoError(t, store.LogAppend(logEntry)) - } + assert.NoError(t, store.LogAppend(&step, logEntries)) // we want to find our inserted logs _logEntries, err := store.LogFind(&step) @@ -83,9 +81,7 @@ func TestLogAppend(t *testing.T) { }, } - for _, logEntry := range logEntries { - assert.NoError(t, store.LogAppend(logEntry)) - } + assert.NoError(t, store.LogAppend(&step, logEntries)) logEntry := &model.LogEntry{ StepID: step.ID, @@ -94,7 +90,7 @@ func TestLogAppend(t *testing.T) { Time: 20, } - assert.NoError(t, store.LogAppend(logEntry)) + assert.NoError(t, store.LogAppend(&step, []*model.LogEntry{logEntry})) _logEntries, err := store.LogFind(&step) assert.NoError(t, err) diff --git a/server/store/datastore/migration/000_legacy_to_xormigrate.go b/server/store/datastore/migration/000_legacy_to_xormigrate.go index 260e06321..d3b59117c 100644 --- a/server/store/datastore/migration/000_legacy_to_xormigrate.go +++ b/server/store/datastore/migration/000_legacy_to_xormigrate.go @@ -5,18 +5,14 @@ import ( "xorm.io/xorm" ) -type v000Migrations struct { - Name string `xorm:"UNIQUE"` -} - -func (m *v000Migrations) TableName() string { - return "migrations" -} - var legacyToXormigrate = xormigrate.Migration{ ID: "legacy-to-xormigrate", MigrateSession: func(sess *xorm.Session) error { - var mig []*v000Migrations + type migrations struct { + Name string `xorm:"UNIQUE"` + } + + var mig []*migrations if err := sess.Find(&mig); err != nil { return err } diff --git a/server/store/datastore/migration/023_add_org_id.go b/server/store/datastore/migration/001_add_org_id.go similarity index 74% rename from server/store/datastore/migration/023_add_org_id.go rename to server/store/datastore/migration/001_add_org_id.go index 37d59833a..5d8eb0ada 100644 --- a/server/store/datastore/migration/023_add_org_id.go +++ b/server/store/datastore/migration/001_add_org_id.go @@ -19,30 +19,39 @@ import ( "src.techknowlogick.com/xormigrate" "xorm.io/xorm" - - "go.woodpecker-ci.org/woodpecker/v2/server/model" ) var addOrgID = xormigrate.Migration{ ID: "add-org-id", MigrateSession: func(sess *xorm.Session) error { - if err := sess.Sync(new(userV031)); err != nil { + type users struct { + ID int64 `xorm:"pk autoincr 'user_id'"` + Login string `xorm:"UNIQUE 'user_login'"` + OrgID int64 `xorm:"user_org_id"` + } + type orgs struct { + ID int64 `xorm:"pk autoincr 'id'"` + Name string `xorm:"UNIQUE 'name'"` + IsUser bool `xorm:"is_user"` + } + + if err := sess.Sync(new(users), new(orgs)); err != nil { return fmt.Errorf("sync new models failed: %w", err) } // get all users - var users []*userV031 - if err := sess.Find(&users); err != nil { + var us []*users + if err := sess.Find(&us); err != nil { return fmt.Errorf("find all repos failed: %w", err) } - for _, user := range users { - org := &model.Org{} + for _, user := range us { + org := &orgs{} has, err := sess.Where("name = ?", user.Login).Get(org) if err != nil { return fmt.Errorf("getting org failed: %w", err) } else if !has { - org = &model.Org{ + org = &orgs{ Name: user.Login, IsUser: true, } diff --git a/server/store/datastore/migration/001_legacy_to_xorm.go b/server/store/datastore/migration/001_legacy_to_xorm.go deleted file mode 100644 index 47a526147..000000000 --- a/server/store/datastore/migration/001_legacy_to_xorm.go +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2021 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package migration - -import ( - "fmt" - - "github.com/rs/zerolog/log" - "src.techknowlogick.com/xormigrate" - "xorm.io/xorm" - "xorm.io/xorm/schemas" -) - -var legacy2Xorm = xormigrate.Migration{ - ID: "xorm", - MigrateSession: func(sess *xorm.Session) error { - // make sure we have required migrations - else fail and point to last major version - for _, mig := range []string{ - // users - "create-table-users", - "update-table-set-users-token-and-secret-length", - // repos - "create-table-repos", - "alter-table-add-repo-visibility", - "update-table-set-repo-visibility", - "alter-table-add-repo-seq", - "update-table-set-repo-seq", - "update-table-set-repo-seq-default", - "alter-table-add-repo-active", - "update-table-set-repo-active", - "alter-table-add-repo-fallback", // needed to drop col - // builds - "create-table-builds", - "create-index-builds-repo", - "create-index-builds-author", - // procs - "create-table-procs", - "create-index-procs-build", - // files - "create-table-files", - "create-index-files-builds", - "create-index-files-procs", - "alter-table-add-file-pid", - "alter-table-add-file-meta-passed", - "alter-table-add-file-meta-failed", - "alter-table-add-file-meta-skipped", - "alter-table-update-file-meta", - // secrets - "create-table-secrets", - "create-index-secrets-repo", - // registry - "create-table-registry", - "create-index-registry-repo", - // senders - "create-table-senders", - "create-index-sender-repos", - // perms - "create-table-perms", - "create-index-perms-repo", - "create-index-perms-user", - // build_config - "create-table-build-config", - "populate-build-config", - } { - exist, err := sess.Exist(&xormigrate.Migration{ID: mig}) - if err != nil { - return fmt.Errorf("test migration existence: %w", err) - } - if !exist { - log.Error().Msgf("migration step '%s' missing, please upgrade to last stable v0.14.x version first", mig) - return fmt.Errorf("legacy migration step missing") - } - } - - { // recreate build_config - type BuildConfig struct { - ConfigID int64 `xorm:"NOT NULL 'config_id'"` // xorm.Sync() do not use index info of sess -> so it tries to create it twice - BuildID int64 `xorm:"NOT NULL 'build_id'"` - } - if err := renameTable(sess, "build_config", "old_build_config"); err != nil { - return err - } - if err := sess.Sync(new(BuildConfig)); err != nil { - return err - } - if _, err := sess.Exec("INSERT INTO build_config (config_id, build_id) SELECT config_id,build_id FROM old_build_config;"); err != nil { - return fmt.Errorf("unable to set copy data into temp table %s. Error: %w", "old_build_config", err) - } - if err := sess.DropTable("old_build_config"); err != nil { - return fmt.Errorf("could not drop table '%s': %w", "old_build_config", err) - } - } - - dialect := sess.Engine().Dialect().URI().DBType - switch dialect { - case schemas.MYSQL: - for _, exec := range []string{ - "DROP INDEX IF EXISTS build_number ON builds;", - "DROP INDEX IF EXISTS ix_build_repo ON builds;", - "DROP INDEX IF EXISTS ix_build_author ON builds;", - "DROP INDEX IF EXISTS proc_build_ix ON procs;", - "DROP INDEX IF EXISTS file_build_ix ON files;", - "DROP INDEX IF EXISTS file_proc_ix ON files;", - "DROP INDEX IF EXISTS ix_secrets_repo ON secrets;", - "DROP INDEX IF EXISTS ix_registry_repo ON registry;", - "DROP INDEX IF EXISTS sender_repo_ix ON senders;", - "DROP INDEX IF EXISTS ix_perms_repo ON perms;", - "DROP INDEX IF EXISTS ix_perms_user ON perms;", - } { - if _, err := sess.Exec(exec); err != nil { - return fmt.Errorf("exec: '%s' failed: %w", exec, err) - } - } - case schemas.SQLITE, schemas.POSTGRES: - for _, exec := range []string{ - "DROP INDEX IF EXISTS ix_build_status_running;", - "DROP INDEX IF EXISTS ix_build_repo;", - "DROP INDEX IF EXISTS ix_build_author;", - "DROP INDEX IF EXISTS proc_build_ix;", - "DROP INDEX IF EXISTS file_build_ix;", - "DROP INDEX IF EXISTS file_proc_ix;", - "DROP INDEX IF EXISTS ix_secrets_repo;", - "DROP INDEX IF EXISTS ix_registry_repo;", - "DROP INDEX IF EXISTS sender_repo_ix;", - "DROP INDEX IF EXISTS ix_perms_repo;", - "DROP INDEX IF EXISTS ix_perms_user;", - } { - if _, err := sess.Exec(exec); err != nil { - return fmt.Errorf("exec: '%s' failed: %w", exec, err) - } - } - default: - return fmt.Errorf("dialect '%s' not supported", dialect) - } - - return nil - }, -} diff --git a/server/store/datastore/migration/002_repos_drop_fallback.go b/server/store/datastore/migration/002_repos_drop_fallback.go deleted file mode 100644 index 111bd3eb0..000000000 --- a/server/store/datastore/migration/002_repos_drop_fallback.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2021 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package migration - -import ( - "src.techknowlogick.com/xormigrate" - "xorm.io/xorm" -) - -var alterTableReposDropFallback = xormigrate.Migration{ - ID: "alter-table-drop-repo-fallback", - MigrateSession: func(sess *xorm.Session) error { - return dropTableColumns(sess, "repos", "repo_fallback") - }, -} diff --git a/server/store/datastore/migration/024_task_data_type.go b/server/store/datastore/migration/002_task_data_type.go similarity index 100% rename from server/store/datastore/migration/024_task_data_type.go rename to server/store/datastore/migration/002_task_data_type.go diff --git a/server/store/datastore/migration/025_config_data_type.go b/server/store/datastore/migration/003_config_data_type.go similarity index 100% rename from server/store/datastore/migration/025_config_data_type.go rename to server/store/datastore/migration/003_config_data_type.go diff --git a/server/store/datastore/migration/003_repos_drop_allow_deploys_allow_tags.go b/server/store/datastore/migration/003_repos_drop_allow_deploys_allow_tags.go deleted file mode 100644 index d99610f56..000000000 --- a/server/store/datastore/migration/003_repos_drop_allow_deploys_allow_tags.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2021 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package migration - -import ( - "src.techknowlogick.com/xormigrate" - "xorm.io/xorm" -) - -var alterTableReposDropAllowDeploysAllowTags = xormigrate.Migration{ - ID: "drop-allow-push-tags-deploys-columns", - MigrateSession: func(sess *xorm.Session) error { - return dropTableColumns(sess, "repos", - "repo_allow_deploys", - "repo_allow_tags", - ) - }, -} diff --git a/server/store/datastore/migration/004_fix_pr_secret_event_name.go b/server/store/datastore/migration/004_fix_pr_secret_event_name.go deleted file mode 100644 index 66406a07b..000000000 --- a/server/store/datastore/migration/004_fix_pr_secret_event_name.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2021 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package migration - -import ( - "src.techknowlogick.com/xormigrate" - "xorm.io/xorm" - - "go.woodpecker-ci.org/woodpecker/v2/server/model" -) - -var fixPRSecretEventName = xormigrate.Migration{ - ID: "fix-pr-secret-event-name", - MigrateSession: func(sess *xorm.Session) error { - const batchSize = 100 - for start := 0; ; start += batchSize { - secrets := make([]*model.Secret, 0, batchSize) - if err := sess.Limit(batchSize, start).Table("secrets").Cols("secret_id", "secret_events").Where("secret_events LIKE '%pull-request%'").Find(&secrets); err != nil { - return err - } - - if len(secrets) == 0 { - break - } - - for _, secret := range secrets { - for i, event := range secret.Events { - if event == "pull-request" { - secret.Events[i] = "pull_request" - } - } - if _, err := sess.ID(secret.ID).Cols("secret_events").Update(secret); err != nil { - return err - } - } - } - return nil - }, -} diff --git a/server/store/datastore/migration/026_remove_secrets_plugin_only_col.go b/server/store/datastore/migration/004_remove_secrets_plugin_only_col.go similarity index 66% rename from server/store/datastore/migration/026_remove_secrets_plugin_only_col.go rename to server/store/datastore/migration/004_remove_secrets_plugin_only_col.go index 082744cca..25d94e744 100644 --- a/server/store/datastore/migration/026_remove_secrets_plugin_only_col.go +++ b/server/store/datastore/migration/004_remove_secrets_plugin_only_col.go @@ -19,23 +19,19 @@ import ( "xorm.io/xorm" ) -type oldSecret026 struct { - ID int64 `json:"id" xorm:"pk autoincr 'secret_id'"` - PluginsOnly bool `json:"plugins_only" xorm:"secret_plugins_only"` - SkipVerify bool `json:"-" xorm:"secret_skip_verify"` - Conceal bool `json:"-" xorm:"secret_conceal"` - Images []string `json:"images" xorm:"json 'secret_images'"` -} - -func (oldSecret026) TableName() string { - return "secrets" -} - var removePluginOnlyOptionFromSecretsTable = xormigrate.Migration{ ID: "remove-plugin-only-option-from-secrets-table", MigrateSession: func(sess *xorm.Session) (err error) { + type secrets struct { + ID int64 `json:"id" xorm:"pk autoincr 'secret_id'"` + PluginsOnly bool `json:"plugins_only" xorm:"secret_plugins_only"` + SkipVerify bool `json:"-" xorm:"secret_skip_verify"` + Conceal bool `json:"-" xorm:"secret_conceal"` + Images []string `json:"images" xorm:"json 'secret_images'"` + } + // make sure plugin_only column exists - if err := sess.Sync(new(oldSecret026)); err != nil { + if err := sess.Sync(new(secrets)); err != nil { return err } diff --git a/server/store/datastore/migration/027_convert_to_new_pipeline_errors_format.go b/server/store/datastore/migration/005_convert_to_new_pipeline_errors_format.go similarity index 58% rename from server/store/datastore/migration/027_convert_to_new_pipeline_errors_format.go rename to server/store/datastore/migration/005_convert_to_new_pipeline_errors_format.go index a51ac1c6a..d852b363e 100644 --- a/server/store/datastore/migration/027_convert_to_new_pipeline_errors_format.go +++ b/server/store/datastore/migration/005_convert_to_new_pipeline_errors_format.go @@ -17,54 +17,48 @@ package migration import ( "src.techknowlogick.com/xormigrate" "xorm.io/xorm" - - errorTypes "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors/types" ) -// perPage027 set the size of the slice to read per page. -var perPage027 = 100 - -type pipeline027 struct { - ID int64 `json:"id" xorm:"pk autoincr 'pipeline_id'"` - Error string `json:"error" xorm:"LONGTEXT 'pipeline_error'"` // old error format - Errors []*errorTypes.PipelineError `json:"errors" xorm:"json 'pipeline_errors'"` // new error format -} - -func (pipeline027) TableName() string { - return "pipelines" -} - -type PipelineError027 struct { - Type string `json:"type"` - Message string `json:"message"` - IsWarning bool `json:"is_warning"` - Data any `json:"data"` -} +// perPage005 set the size of the slice to read per page. +var perPage005 = 100 var convertToNewPipelineErrorFormat = xormigrate.Migration{ ID: "convert-to-new-pipeline-error-format", Long: true, MigrateSession: func(sess *xorm.Session) (err error) { + type pipelineError struct { + Type string `json:"type"` + Message string `json:"message"` + IsWarning bool `json:"is_warning"` + Data any `json:"data"` + } + + type pipelines struct { + ID int64 `json:"id" xorm:"pk autoincr 'pipeline_id'"` + Error string `json:"error" xorm:"LONGTEXT 'pipeline_error'"` // old error format + Errors []*pipelineError `json:"errors" xorm:"json 'pipeline_errors'"` // new error format + } + // make sure pipeline_error column exists - if err := sess.Sync(new(pipeline027)); err != nil { + if err := sess.Sync(new(pipelines)); err != nil { return err } page := 0 - oldPipelines := make([]*pipeline027, 0, perPage027) + oldPipelines := make([]*pipelines, 0, perPage005) for { oldPipelines = oldPipelines[:0] - err := sess.Limit(perPage027, page*perPage027).Cols("pipeline_id", "pipeline_error").Where("pipeline_error != ''").Find(&oldPipelines) + err := sess.Limit(perPage005, page*perPage005).Cols("pipeline_id", "pipeline_error").Where("pipeline_error != ''").Find(&oldPipelines) if err != nil { return err } for _, oldPipeline := range oldPipelines { - var newPipeline pipeline027 + var newPipeline pipelines newPipeline.ID = oldPipeline.ID - newPipeline.Errors = []*errorTypes.PipelineError{{ + newPipeline.Errors = []*pipelineError{{ Type: "generic", Message: oldPipeline.Error, }} @@ -74,7 +68,7 @@ var convertToNewPipelineErrorFormat = xormigrate.Migration{ } } - if len(oldPipelines) < perPage027 { + if len(oldPipelines) < perPage005 { break } diff --git a/server/store/datastore/migration/006_drop_senders.go b/server/store/datastore/migration/006_drop_senders.go deleted file mode 100644 index f547bda09..000000000 --- a/server/store/datastore/migration/006_drop_senders.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2022 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package migration - -import ( - "src.techknowlogick.com/xormigrate" - "xorm.io/xorm" -) - -var dropSenders = xormigrate.Migration{ - ID: "drop-senders", - MigrateSession: func(sess *xorm.Session) error { - return sess.DropTable("senders") - }, -} diff --git a/server/store/datastore/migration/028_link_to_url.go b/server/store/datastore/migration/006_link_to_url.go similarity index 100% rename from server/store/datastore/migration/028_link_to_url.go rename to server/store/datastore/migration/006_link_to_url.go diff --git a/server/store/datastore/migration/029_clean_registry_pipeline.go b/server/store/datastore/migration/007_clean_registry_pipeline.go similarity index 56% rename from server/store/datastore/migration/029_clean_registry_pipeline.go rename to server/store/datastore/migration/007_clean_registry_pipeline.go index 21eb4277b..b5bf9e09e 100644 --- a/server/store/datastore/migration/029_clean_registry_pipeline.go +++ b/server/store/datastore/migration/007_clean_registry_pipeline.go @@ -19,32 +19,24 @@ import ( "xorm.io/xorm" ) -type oldRegistry029 struct { - ID int64 `json:"id" xorm:"pk autoincr 'registry_id'"` - Token string `json:"token" xorm:"TEXT 'registry_token'"` - Email string `json:"email" xorm:"varchar(500) 'registry_email'"` -} - -func (oldRegistry029) TableName() string { - return "registry" -} - -type oldPipeline029 struct { - ID int64 `json:"id" xorm:"pk autoincr 'pipeline_id'"` - ConfigID int64 `json:"-" xorm:"pipeline_config_id"` - Enqueued int64 `json:"enqueued_at" xorm:"pipeline_enqueued"` - CloneURL string `json:"clone_url" xorm:"pipeline_clone_url"` -} - -// TableName return database table name for xorm. -func (oldPipeline029) TableName() string { - return "pipelines" -} - var cleanRegistryPipeline = xormigrate.Migration{ ID: "clean-registry-pipeline", MigrateSession: func(sess *xorm.Session) (err error) { - if err := sess.Sync(new(oldRegistry029), new(oldPipeline029)); err != nil { + type registry struct { + ID int64 `json:"id" xorm:"pk autoincr 'registry_id'"` + Token string `json:"token" xorm:"TEXT 'registry_token'"` + Email string `json:"email" xorm:"varchar(500) 'registry_email'"` + } + + type pipelines struct { + ID int64 `json:"id" xorm:"pk autoincr 'pipeline_id'"` + ConfigID int64 `json:"-" xorm:"pipeline_config_id"` + Enqueued int64 `json:"enqueued_at" xorm:"pipeline_enqueued"` + CloneURL string `json:"clone_url" xorm:"pipeline_clone_url"` + } + + // ensure columns to drop exist + if err := sess.Sync(new(registry), new(pipelines)); err != nil { return err } diff --git a/server/store/datastore/migration/008_secrets_add_user.go b/server/store/datastore/migration/008_secrets_add_user.go deleted file mode 100644 index 6755a3b62..000000000 --- a/server/store/datastore/migration/008_secrets_add_user.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2022 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package migration - -import ( - "src.techknowlogick.com/xormigrate" - "xorm.io/xorm" -) - -type SecretV008 struct { - Owner string `json:"-" xorm:"NOT NULL DEFAULT '' UNIQUE(s) INDEX 'secret_owner'"` - RepoID int64 `json:"-" xorm:"NOT NULL DEFAULT 0 UNIQUE(s) INDEX 'secret_repo_id'"` - Name string `json:"name" xorm:"NOT NULL UNIQUE(s) INDEX 'secret_name'"` -} - -// TableName return database table name for xorm. -func (SecretV008) TableName() string { - return "secrets" -} - -var alterTableSecretsAddUserCol = xormigrate.Migration{ - ID: "alter-table-add-secrets-user-id", - MigrateSession: func(sess *xorm.Session) error { - if err := sess.Sync(new(SecretV008)); err != nil { - return err - } - if err := alterColumnDefault(sess, "secrets", "secret_repo_id", "0"); err != nil { - return err - } - if err := alterColumnNull(sess, "secrets", "secret_repo_id", false); err != nil { - return err - } - return alterColumnNull(sess, "secrets", "secret_name", false) - }, -} diff --git a/server/store/datastore/migration/030_set_default_forge_id.go b/server/store/datastore/migration/008_set_default_forge_id.go similarity index 91% rename from server/store/datastore/migration/030_set_default_forge_id.go rename to server/store/datastore/migration/008_set_default_forge_id.go index bbd9300c3..5387a8ae0 100644 --- a/server/store/datastore/migration/030_set_default_forge_id.go +++ b/server/store/datastore/migration/008_set_default_forge_id.go @@ -20,10 +20,10 @@ import ( "src.techknowlogick.com/xormigrate" "xorm.io/xorm" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) -type userV030 struct { +type userV008 struct { ID int64 `xorm:"pk autoincr 'user_id'"` ForgeID int64 `xorm:"forge_id"` ForgeRemoteID model.ForgeRemoteID `xorm:"forge_remote_id"` @@ -38,11 +38,11 @@ type userV030 struct { OrgID int64 `xorm:"user_org_id"` } -func (userV030) TableName() string { +func (userV008) TableName() string { return "users" } -type repoV030 struct { +type repoV008 struct { ID int64 `xorm:"pk autoincr 'repo_id'"` UserID int64 `xorm:"repo_user_id"` ForgeID int64 `xorm:"forge_id"` @@ -56,7 +56,6 @@ type repoV030 struct { Clone string `xorm:"varchar(1000) 'repo_clone'"` CloneSSH string `xorm:"varchar(1000) 'repo_clone_ssh'"` Branch string `xorm:"varchar(500) 'repo_branch'"` - SCMKind model.SCMKind `xorm:"varchar(50) 'repo_scm'"` PREnabled bool `xorm:"DEFAULT TRUE 'repo_pr_enabled'"` Timeout int64 `xorm:"repo_timeout"` Visibility model.RepoVisibility `xorm:"varchar(10) 'repo_visibility'"` @@ -73,11 +72,11 @@ type repoV030 struct { NetrcOnlyTrusted bool `xorm:"NOT NULL DEFAULT true 'netrc_only_trusted'"` } -func (repoV030) TableName() string { +func (repoV008) TableName() string { return "repos" } -type forgeV030 struct { +type forge struct { ID int64 `xorm:"pk autoincr 'id'"` Type model.ForgeType `xorm:"VARCHAR(250) 'type'"` URL string `xorm:"VARCHAR(500) 'url'"` @@ -88,18 +87,18 @@ type forgeV030 struct { AdditionalOptions map[string]any `xorm:"json 'additional_options'"` } -func (forgeV030) TableName() string { +func (forge) TableName() string { return "forge" } var setForgeID = xormigrate.Migration{ ID: "set-forge-id", MigrateSession: func(sess *xorm.Session) (err error) { - if err := sess.Sync(new(userV030), new(repoV030), new(forgeV030), new(model.Org)); err != nil { + if err := sess.Sync(new(userV008), new(repoV008), new(forge), new(model.Org)); err != nil { return fmt.Errorf("sync new models failed: %w", err) } - _, err = sess.Exec(fmt.Sprintf("UPDATE `%s` SET forge_id=1;", userV030{}.TableName())) + _, err = sess.Exec(fmt.Sprintf("UPDATE `%s` SET forge_id=1;", userV008{}.TableName())) if err != nil { return err } @@ -109,7 +108,7 @@ var setForgeID = xormigrate.Migration{ return err } - _, err = sess.Exec(fmt.Sprintf("UPDATE `%s` SET forge_id=1;", repoV030{}.TableName())) + _, err = sess.Exec(fmt.Sprintf("UPDATE `%s` SET forge_id=1;", repoV008{}.TableName())) return err }, } diff --git a/server/store/datastore/migration/031_unify_columns_tables.go b/server/store/datastore/migration/009_unify_columns_tables.go similarity index 60% rename from server/store/datastore/migration/031_unify_columns_tables.go rename to server/store/datastore/migration/009_unify_columns_tables.go index 881832b0f..9bf38012c 100644 --- a/server/store/datastore/migration/031_unify_columns_tables.go +++ b/server/store/datastore/migration/009_unify_columns_tables.go @@ -19,226 +19,182 @@ import ( "src.techknowlogick.com/xormigrate" "xorm.io/xorm" - - "go.woodpecker-ci.org/woodpecker/v2/pipeline/errors/types" - "go.woodpecker-ci.org/woodpecker/v2/server/model" ) -type configV031 struct { - ID int64 `xorm:"pk autoincr 'config_id'"` - RepoID int64 `xorm:"UNIQUE(s) 'config_repo_id'"` - Hash string `xorm:"UNIQUE(s) 'config_hash'"` - Name string `xorm:"UNIQUE(s) 'config_name'"` - Data []byte `xorm:"LONGBLOB 'config_data'"` -} - -func (configV031) TableName() string { - return "config" -} - -type cronV031 struct { - ID int64 `xorm:"pk autoincr 'i_d'"` - Name string `xorm:"name UNIQUE(s) INDEX"` - RepoID int64 `xorm:"repo_id UNIQUE(s) INDEX"` - CreatorID int64 `xorm:"creator_id INDEX"` - NextExec int64 `xorm:"next_exec"` - Schedule string `xorm:"schedule NOT NULL"` - Created int64 `xorm:"created NOT NULL DEFAULT 0"` - Branch string `xorm:"branch"` -} - -func (cronV031) TableName() string { - return "crons" -} - -type permV031 struct { - UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL 'perm_user_id'"` - RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL 'perm_repo_id'"` - Pull bool `xorm:"perm_pull"` - Push bool `xorm:"perm_push"` - Admin bool `xorm:"perm_admin"` - Synced int64 `xorm:"perm_synced"` -} - -func (permV031) TableName() string { - return "perms" -} - -type pipelineV031 struct { - ID int64 `xorm:"pk autoincr 'pipeline_id'"` - RepoID int64 `xorm:"UNIQUE(s) INDEX 'pipeline_repo_id'"` - Number int64 `xorm:"UNIQUE(s) 'pipeline_number'"` - Author string `xorm:"INDEX 'pipeline_author'"` - Parent int64 `xorm:"pipeline_parent"` - Event model.WebhookEvent `xorm:"pipeline_event"` - Status model.StatusValue `xorm:"INDEX 'pipeline_status'"` - Errors []*types.PipelineError `xorm:"json 'pipeline_errors'"` - Created int64 `xorm:"pipeline_created"` - Started int64 `xorm:"pipeline_started"` - Finished int64 `xorm:"pipeline_finished"` - Deploy string `xorm:"pipeline_deploy"` - DeployTask string `xorm:"pipeline_deploy_task"` - Commit string `xorm:"pipeline_commit"` - Branch string `xorm:"pipeline_branch"` - Ref string `xorm:"pipeline_ref"` - Refspec string `xorm:"pipeline_refspec"` - Title string `xorm:"pipeline_title"` - Message string `xorm:"TEXT 'pipeline_message'"` - Timestamp int64 `xorm:"pipeline_timestamp"` - Sender string `xorm:"pipeline_sender"` // uses reported user for webhooks and name of cron for cron pipelines - Avatar string `xorm:"pipeline_avatar"` - Email string `xorm:"pipeline_email"` - ForgeURL string `xorm:"pipeline_forge_url"` - Reviewer string `xorm:"pipeline_reviewer"` - Reviewed int64 `xorm:"pipeline_reviewed"` -} - -func (pipelineV031) TableName() string { - return "pipelines" -} - -type redirectionV031 struct { - ID int64 `xorm:"pk autoincr 'redirection_id'"` -} - -func (r redirectionV031) TableName() string { - return "redirections" -} - -type registryV031 struct { - ID int64 `xorm:"pk autoincr 'registry_id'"` - RepoID int64 `xorm:"UNIQUE(s) INDEX 'registry_repo_id'"` - Address string `xorm:"UNIQUE(s) INDEX 'registry_addr'"` - Username string `xorm:"varchar(2000) 'registry_username'"` - Password string `xorm:"TEXT 'registry_password'"` -} - -type repoV031 struct { - ID int64 `xorm:"pk autoincr 'repo_id'"` - UserID int64 `xorm:"repo_user_id"` - OrgID int64 `xorm:"repo_org_id"` - Owner string `xorm:"UNIQUE(name) 'repo_owner'"` - Name string `xorm:"UNIQUE(name) 'repo_name'"` - FullName string `xorm:"UNIQUE 'repo_full_name'"` - Avatar string `xorm:"varchar(500) 'repo_avatar'"` - ForgeURL string `xorm:"varchar(1000) 'repo_forge_url'"` - Clone string `xorm:"varchar(1000) 'repo_clone'"` - CloneSSH string `xorm:"varchar(1000) 'repo_clone_ssh'"` - Branch string `xorm:"varchar(500) 'repo_branch'"` - SCMKind model.SCMKind `xorm:"varchar(50) 'repo_scm'"` - PREnabled bool `xorm:"DEFAULT TRUE 'repo_pr_enabled'"` - Timeout int64 `xorm:"repo_timeout"` - Visibility model.RepoVisibility `xorm:"varchar(10) 'repo_visibility'"` - IsSCMPrivate bool `xorm:"repo_private"` - IsTrusted bool `xorm:"repo_trusted"` - IsGated bool `xorm:"repo_gated"` - IsActive bool `xorm:"repo_active"` - AllowPull bool `xorm:"repo_allow_pr"` - AllowDeploy bool `xorm:"repo_allow_deploy"` - Config string `xorm:"varchar(500) 'repo_config_path'"` - Hash string `xorm:"varchar(500) 'repo_hash'"` -} - -func (repoV031) TableName() string { - return "repos" -} - -type secretV031 struct { - ID int64 `xorm:"pk autoincr 'secret_id'"` - OrgID int64 `xorm:"NOT NULL DEFAULT 0 UNIQUE(s) INDEX 'secret_org_id'"` - RepoID int64 `xorm:"NOT NULL DEFAULT 0 UNIQUE(s) INDEX 'secret_repo_id'"` - Name string `xorm:"NOT NULL UNIQUE(s) INDEX 'secret_name'"` - Value string `xorm:"TEXT 'secret_value'"` - Images []string `xorm:"json 'secret_images'"` - Events []model.WebhookEvent `xorm:"json 'secret_events'"` -} - -func (secretV031) TableName() string { - return "secrets" -} - -type stepV031 struct { - ID int64 `xorm:"pk autoincr 'step_id'"` - UUID string `xorm:"INDEX 'step_uuid'"` - PipelineID int64 `xorm:"UNIQUE(s) INDEX 'step_pipeline_id'"` - PID int `xorm:"UNIQUE(s) 'step_pid'"` - PPID int `xorm:"step_ppid"` - Name string `xorm:"step_name"` - State model.StatusValue `xorm:"step_state"` - Error string `xorm:"TEXT 'step_error'"` - Failure string `xorm:"step_failure"` - ExitCode int `xorm:"step_exit_code"` - Started int64 `xorm:"step_started"` - Stopped int64 `xorm:"step_stopped"` - Type model.StepType `xorm:"step_type"` -} - -func (stepV031) TableName() string { - return "steps" -} - -type taskV031 struct { - ID string `xorm:"PK UNIQUE 'task_id'"` - Data []byte `xorm:"LONGBLOB 'task_data'"` - Labels map[string]string `xorm:"json 'task_labels'"` - Dependencies []string `xorm:"json 'task_dependencies'"` - RunOn []string `xorm:"json 'task_run_on'"` - DepStatus map[string]model.StatusValue `xorm:"json 'task_dep_status'"` -} - -func (taskV031) TableName() string { - return "tasks" -} - -type userV031 struct { - ID int64 `xorm:"pk autoincr 'user_id'"` - Login string `xorm:"UNIQUE 'user_login'"` - Token string `xorm:"TEXT 'user_token'"` - Secret string `xorm:"TEXT 'user_secret'"` - Expiry int64 `xorm:"user_expiry"` - Email string `xorm:" varchar(500) 'user_email'"` - Avatar string `xorm:" varchar(500) 'user_avatar'"` - Admin bool `xorm:"user_admin"` - Hash string `xorm:"UNIQUE varchar(500) 'user_hash'"` - OrgID int64 `xorm:"user_org_id"` -} - -func (userV031) TableName() string { - return "users" -} - -type workflowV031 struct { - ID int64 `xorm:"pk autoincr 'workflow_id'"` - PipelineID int64 `xorm:"UNIQUE(s) INDEX 'workflow_pipeline_id'"` - PID int `xorm:"UNIQUE(s) 'workflow_pid'"` - Name string `xorm:"workflow_name"` - State model.StatusValue `xorm:"workflow_state"` - Error string `xorm:"TEXT 'workflow_error'"` - Started int64 `xorm:"workflow_started"` - Stopped int64 `xorm:"workflow_stopped"` - AgentID int64 `xorm:"workflow_agent_id"` - Platform string `xorm:"workflow_platform"` - Environ map[string]string `xorm:"json 'workflow_environ'"` - AxisID int `xorm:"workflow_axis_id"` -} - -func (workflowV031) TableName() string { - return "workflows" -} - -type serverConfigV031 struct { - Key string `xorm:"pk 'key'"` - Value string `xorm:"value"` -} - -func (serverConfigV031) TableName() string { - return "server_config" -} - var unifyColumnsTables = xormigrate.Migration{ ID: "unify-columns-tables", MigrateSession: func(sess *xorm.Session) (err error) { - if err := sess.Sync(new(configV031), new(cronV031), new(permV031), new(pipelineV031), new(redirectionV031), new(registryV031), new(repoV031), new(secretV031), new(stepV031), new(taskV031), new(userV031), new(workflowV031), new(serverConfigV031)); err != nil { + type config struct { + ID int64 `xorm:"pk autoincr 'config_id'"` + RepoID int64 `xorm:"UNIQUE(s) 'config_repo_id'"` + Hash string `xorm:"UNIQUE(s) 'config_hash'"` + Name string `xorm:"UNIQUE(s) 'config_name'"` + Data []byte `xorm:"LONGBLOB 'config_data'"` + } + + type crons struct { + ID int64 `xorm:"pk autoincr 'i_d'"` + Name string `xorm:"name UNIQUE(s) INDEX"` + RepoID int64 `xorm:"repo_id UNIQUE(s) INDEX"` + CreatorID int64 `xorm:"creator_id INDEX"` + NextExec int64 `xorm:"next_exec"` + Schedule string `xorm:"schedule NOT NULL"` + Created int64 `xorm:"created NOT NULL DEFAULT 0"` + Branch string `xorm:"branch"` + } + + type perms struct { + UserID int64 `xorm:"UNIQUE(s) INDEX NOT NULL 'perm_user_id'"` + RepoID int64 `xorm:"UNIQUE(s) INDEX NOT NULL 'perm_repo_id'"` + Pull bool `xorm:"perm_pull"` + Push bool `xorm:"perm_push"` + Admin bool `xorm:"perm_admin"` + Synced int64 `xorm:"perm_synced"` + } + + type pipelineError struct { + Type string `json:"type"` + Message string `json:"message"` + IsWarning bool `json:"is_warning"` + Data any `json:"data"` + } + + type pipelines struct { + ID int64 `xorm:"pk autoincr 'pipeline_id'"` + RepoID int64 `xorm:"UNIQUE(s) INDEX 'pipeline_repo_id'"` + Number int64 `xorm:"UNIQUE(s) 'pipeline_number'"` + Author string `xorm:"INDEX 'pipeline_author'"` + Parent int64 `xorm:"pipeline_parent"` + Event string `xorm:"pipeline_event"` + Status string `xorm:"INDEX 'pipeline_status'"` + Errors []*pipelineError `xorm:"json 'pipeline_errors'"` + Created int64 `xorm:"pipeline_created"` + Started int64 `xorm:"pipeline_started"` + Finished int64 `xorm:"pipeline_finished"` + Deploy string `xorm:"pipeline_deploy"` + DeployTask string `xorm:"pipeline_deploy_task"` + Commit string `xorm:"pipeline_commit"` + Branch string `xorm:"pipeline_branch"` + Ref string `xorm:"pipeline_ref"` + Refspec string `xorm:"pipeline_refspec"` + Title string `xorm:"pipeline_title"` + Message string `xorm:"TEXT 'pipeline_message'"` + Timestamp int64 `xorm:"pipeline_timestamp"` + Sender string `xorm:"pipeline_sender"` // uses reported user for webhooks and name of cron for cron pipelines + Avatar string `xorm:"pipeline_avatar"` + Email string `xorm:"pipeline_email"` + ForgeURL string `xorm:"pipeline_forge_url"` + Reviewer string `xorm:"pipeline_reviewer"` + Reviewed int64 `xorm:"pipeline_reviewed"` + } + + type redirections struct { + ID int64 `xorm:"pk autoincr 'redirection_id'"` + } + + type registry struct { + ID int64 `xorm:"pk autoincr 'registry_id'"` + RepoID int64 `xorm:"UNIQUE(s) INDEX 'registry_repo_id'"` + Address string `xorm:"UNIQUE(s) INDEX 'registry_addr'"` + Username string `xorm:"varchar(2000) 'registry_username'"` + Password string `xorm:"TEXT 'registry_password'"` + } + + type repos struct { + ID int64 `xorm:"pk autoincr 'repo_id'"` + UserID int64 `xorm:"repo_user_id"` + OrgID int64 `xorm:"repo_org_id"` + Owner string `xorm:"UNIQUE(name) 'repo_owner'"` + Name string `xorm:"UNIQUE(name) 'repo_name'"` + FullName string `xorm:"UNIQUE 'repo_full_name'"` + Avatar string `xorm:"varchar(500) 'repo_avatar'"` + ForgeURL string `xorm:"varchar(1000) 'repo_forge_url'"` + Clone string `xorm:"varchar(1000) 'repo_clone'"` + CloneSSH string `xorm:"varchar(1000) 'repo_clone_ssh'"` + Branch string `xorm:"varchar(500) 'repo_branch'"` + SCMKind string `xorm:"varchar(50) 'repo_scm'"` + PREnabled bool `xorm:"DEFAULT TRUE 'repo_pr_enabled'"` + Timeout int64 `xorm:"repo_timeout"` + Visibility string `xorm:"varchar(10) 'repo_visibility'"` + IsSCMPrivate bool `xorm:"repo_private"` + IsTrusted bool `xorm:"repo_trusted"` + IsGated bool `xorm:"repo_gated"` + IsActive bool `xorm:"repo_active"` + AllowPull bool `xorm:"repo_allow_pr"` + AllowDeploy bool `xorm:"repo_allow_deploy"` + Config string `xorm:"varchar(500) 'repo_config_path'"` + Hash string `xorm:"varchar(500) 'repo_hash'"` + } + + type secrets struct { + ID int64 `xorm:"pk autoincr 'secret_id'"` + OrgID int64 `xorm:"NOT NULL DEFAULT 0 UNIQUE(s) INDEX 'secret_org_id'"` + RepoID int64 `xorm:"NOT NULL DEFAULT 0 UNIQUE(s) INDEX 'secret_repo_id'"` + Name string `xorm:"NOT NULL UNIQUE(s) INDEX 'secret_name'"` + Value string `xorm:"TEXT 'secret_value'"` + Images []string `xorm:"json 'secret_images'"` + Events []string `xorm:"json 'secret_events'"` + } + + type steps struct { + ID int64 `xorm:"pk autoincr 'step_id'"` + UUID string `xorm:"INDEX 'step_uuid'"` + PipelineID int64 `xorm:"UNIQUE(s) INDEX 'step_pipeline_id'"` + PID int `xorm:"UNIQUE(s) 'step_pid'"` + PPID int `xorm:"step_ppid"` + Name string `xorm:"step_name"` + State string `xorm:"step_state"` + Error string `xorm:"TEXT 'step_error'"` + Failure string `xorm:"step_failure"` + ExitCode int `xorm:"step_exit_code"` + Started int64 `xorm:"step_started"` + Stopped int64 `xorm:"step_stopped"` + Type string `xorm:"step_type"` + } + + type tasks struct { + ID string `xorm:"PK UNIQUE 'task_id'"` + Data []byte `xorm:"LONGBLOB 'task_data'"` + Labels map[string]string `xorm:"json 'task_labels'"` + Dependencies []string `xorm:"json 'task_dependencies'"` + RunOn []string `xorm:"json 'task_run_on'"` + DepStatus map[string]string `xorm:"json 'task_dep_status'"` + } + + type users struct { + ID int64 `xorm:"pk autoincr 'user_id'"` + Login string `xorm:"UNIQUE 'user_login'"` + Token string `xorm:"TEXT 'user_token'"` + Secret string `xorm:"TEXT 'user_secret'"` + Expiry int64 `xorm:"user_expiry"` + Email string `xorm:" varchar(500) 'user_email'"` + Avatar string `xorm:" varchar(500) 'user_avatar'"` + Admin bool `xorm:"user_admin"` + Hash string `xorm:"UNIQUE varchar(500) 'user_hash'"` + OrgID int64 `xorm:"user_org_id"` + } + + type workflows struct { + ID int64 `xorm:"pk autoincr 'workflow_id'"` + PipelineID int64 `xorm:"UNIQUE(s) INDEX 'workflow_pipeline_id'"` + PID int `xorm:"UNIQUE(s) 'workflow_pid'"` + Name string `xorm:"workflow_name"` + State string `xorm:"workflow_state"` + Error string `xorm:"TEXT 'workflow_error'"` + Started int64 `xorm:"workflow_started"` + Stopped int64 `xorm:"workflow_stopped"` + AgentID int64 `xorm:"workflow_agent_id"` + Platform string `xorm:"workflow_platform"` + Environ map[string]string `xorm:"json 'workflow_environ'"` + AxisID int `xorm:"workflow_axis_id"` + } + + type serverConfig struct { + Key string `xorm:"pk 'key'"` + Value string `xorm:"value"` + } + + if err := sess.Sync(new(config), new(crons), new(perms), new(pipelines), new(redirections), new(registry), new(repos), new(secrets), new(steps), new(tasks), new(users), new(workflows), new(serverConfig)); err != nil { return fmt.Errorf("sync models failed: %w", err) } diff --git a/server/store/datastore/migration/032_registries_add_user.go b/server/store/datastore/migration/010_registries_add_user.go similarity index 100% rename from server/store/datastore/migration/032_registries_add_user.go rename to server/store/datastore/migration/010_registries_add_user.go diff --git a/server/store/datastore/migration/011_columns_rename_builds_to_pipeline.go b/server/store/datastore/migration/011_columns_rename_builds_to_pipeline.go deleted file mode 100644 index b80a465e5..000000000 --- a/server/store/datastore/migration/011_columns_rename_builds_to_pipeline.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2022 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package migration - -import ( - "strings" - - "src.techknowlogick.com/xormigrate" - "xorm.io/xorm" -) - -type oldTable struct { - table string - columns []string -} - -var renameColumnsBuildsToPipeline = xormigrate.Migration{ - ID: "rename-columns-builds-to-pipeline", - MigrateSession: func(sess *xorm.Session) error { - var oldColumns []*oldTable - - oldColumns = append(oldColumns, &oldTable{ - table: "pipelines", - columns: []string{ - "build_id", - "build_repo_id", - "build_number", - "build_author", - "build_config_id", - "build_parent", - "build_event", - "build_status", - "build_error", - "build_enqueued", - "build_created", - "build_started", - "build_finished", - "build_deploy", - "build_commit", - "build_branch", - "build_ref", - "build_refspec", - "build_remote", - "build_title", - "build_message", - "build_timestamp", - "build_sender", - "build_avatar", - "build_email", - "build_link", - "build_signed", - "build_verified", - "build_reviewer", - "build_reviewed", - }, - }, - ) - - oldColumns = append(oldColumns, &oldTable{ - table: "pipeline_config", - columns: []string{"build_id"}, - }) - - oldColumns = append(oldColumns, &oldTable{ - table: "files", - columns: []string{"file_build_id"}, - }) - - oldColumns = append(oldColumns, &oldTable{ - table: "procs", - columns: []string{"proc_build_id"}, - }) - - for _, table := range oldColumns { - for _, column := range table.columns { - err := renameColumn(sess, table.table, column, strings.Replace(column, "build_", "pipeline_", 1)) - if err != nil { - return err - } - } - } - - return nil - }, -} diff --git a/server/store/datastore/migration/011_cron_without_sec.go b/server/store/datastore/migration/011_cron_without_sec.go new file mode 100644 index 000000000..c99290259 --- /dev/null +++ b/server/store/datastore/migration/011_cron_without_sec.go @@ -0,0 +1,54 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package migration + +import ( + "fmt" + "strings" + + "src.techknowlogick.com/xormigrate" + "xorm.io/xorm" + + "go.woodpecker-ci.org/woodpecker/v3/server/model" +) + +var cronWithoutSec = xormigrate.Migration{ + ID: "cron-without-sec", + MigrateSession: func(sess *xorm.Session) error { + if err := sess.Sync(new(model.Cron)); err != nil { + return fmt.Errorf("sync new models failed: %w", err) + } + + var crons []*model.Cron + if err := sess.Find(&crons); err != nil { + return err + } + + for _, c := range crons { + if strings.HasPrefix(strings.TrimSpace(c.Schedule), "@") { + // something like "@daily" + continue + } + + if _, err := sess.Update(&model.Cron{ + Schedule: strings.SplitN(strings.TrimSpace(c.Schedule), " ", 2)[1], + }, c); err != nil { + return err + } + } + + return nil + }, +} diff --git a/server/store/datastore/migration/012_columns_rename_procs_to_steps.go b/server/store/datastore/migration/012_columns_rename_procs_to_steps.go deleted file mode 100644 index 015bdac4f..000000000 --- a/server/store/datastore/migration/012_columns_rename_procs_to_steps.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2022 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package migration - -import ( - "strings" - - "src.techknowlogick.com/xormigrate" - "xorm.io/xorm" -) - -var renameTableProcsToSteps = xormigrate.Migration{ - ID: "rename-procs-to-steps", - MigrateSession: func(sess *xorm.Session) error { - err := renameTable(sess, "procs", "steps") - if err != nil { - return err - } - - oldProcColumns := []*oldTable{ - { - table: "steps", - columns: []string{ - "proc_id", - "proc_pipeline_id", - "proc_pid", - "proc_ppid", - "proc_pgid", - "proc_name", - "proc_state", - "proc_error", - "proc_exit_code", - "proc_started", - "proc_stopped", - "proc_machine", - "proc_platform", - "proc_environ", - }, - }, - { - table: "files", - columns: []string{"file_proc_id"}, - }, - } - - for _, table := range oldProcColumns { - for _, column := range table.columns { - err := renameColumn(sess, table.table, column, strings.Replace(column, "proc_", "step_", 1)) - if err != nil { - return err - } - } - } - - oldJobColumns := []*oldTable{ - { - table: "logs", - columns: []string{ - "log_job_id", - }, - }, - } - - for _, table := range oldJobColumns { - for _, column := range table.columns { - err := renameColumn(sess, table.table, column, strings.Replace(column, "job_", "step_", 1)) - if err != nil { - return err - } - } - } - - return nil - }, -} diff --git a/server/store/datastore/migration/012_rename_start_end_time.go b/server/store/datastore/migration/012_rename_start_end_time.go new file mode 100644 index 000000000..9004aa83d --- /dev/null +++ b/server/store/datastore/migration/012_rename_start_end_time.go @@ -0,0 +1,50 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package migration + +import ( + "fmt" + + "src.techknowlogick.com/xormigrate" + "xorm.io/xorm" +) + +var renameStartEndTime = xormigrate.Migration{ + ID: "rename-start-end-time", + MigrateSession: func(sess *xorm.Session) (err error) { + type steps struct { + Finished int64 `xorm:"stopped"` + } + type workflows struct { + Finished int64 `xorm:"stopped"` + } + + if err := sess.Sync(new(steps), new(workflows)); err != nil { + return fmt.Errorf("sync models failed: %w", err) + } + + // Step + if err := renameColumn(sess, "steps", "stopped", "finished"); err != nil { + return err + } + + // Workflow + if err := renameColumn(sess, "workflows", "stopped", "finished"); err != nil { + return err + } + + return nil + }, +} diff --git a/server/store/datastore/migration/009_lowercase_secret_names.go b/server/store/datastore/migration/013_fix_v31_registries.go similarity index 67% rename from server/store/datastore/migration/009_lowercase_secret_names.go rename to server/store/datastore/migration/013_fix_v31_registries.go index 5402dc36c..988e49541 100644 --- a/server/store/datastore/migration/009_lowercase_secret_names.go +++ b/server/store/datastore/migration/013_fix_v31_registries.go @@ -1,10 +1,10 @@ -// Copyright 2022 Woodpecker Authors +// Copyright 2024 Woodpecker Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -19,10 +19,17 @@ import ( "xorm.io/xorm" ) -var lowercaseSecretNames = xormigrate.Migration{ - ID: "lowercase-secret-names", +var fixV31Registries = xormigrate.Migration{ + ID: "fix-v31-registries", MigrateSession: func(sess *xorm.Session) (err error) { - _, err = sess.Exec("UPDATE secrets SET secret_name = LOWER(secret_name);") - return err + has, err := sess.IsTableExist("registry_v031") + if err != nil { + return err + } + if has { + return sess.DropTable("registry_v031") + } + + return nil }, } diff --git a/server/store/datastore/migration/013_rename_remote_to_forge.go b/server/store/datastore/migration/013_rename_remote_to_forge.go deleted file mode 100644 index 78eff328b..000000000 --- a/server/store/datastore/migration/013_rename_remote_to_forge.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2022 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package migration - -import ( - "src.techknowlogick.com/xormigrate" - "xorm.io/xorm" -) - -type oldRepo013 struct { - ID int64 `xorm:"pk autoincr 'repo_id'"` - RemoteID string `xorm:"remote_id"` -} - -func (oldRepo013) TableName() string { - return "repos" -} - -var renameRemoteToForge = xormigrate.Migration{ - ID: "rename-remote-to-forge", - MigrateSession: func(sess *xorm.Session) error { - if err := renameColumn(sess, "pipelines", "pipeline_remote", "pipeline_clone_url"); err != nil { - return err - } - - // make sure the column exist before rename it - if err := sess.Sync(new(oldRepo013)); err != nil { - return err - } - - return renameColumn(sess, "repos", "remote_id", "forge_id") - }, -} diff --git a/server/store/datastore/migration/014_remove_old_migrations_of_v1.go b/server/store/datastore/migration/014_remove_old_migrations_of_v1.go new file mode 100644 index 000000000..5f49d559b --- /dev/null +++ b/server/store/datastore/migration/014_remove_old_migrations_of_v1.go @@ -0,0 +1,54 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package migration + +import ( + "src.techknowlogick.com/xormigrate" + "xorm.io/xorm" +) + +var removeOldMigrationsOfV1 = xormigrate.Migration{ + ID: "remove-old-migrations-of-v1", + MigrateSession: func(sess *xorm.Session) (err error) { + _, err = sess.Table(&xormigrate.Migration{}).In("id", []string{ + "xorm", + "alter-table-drop-repo-fallback", + "drop-allow-push-tags-deploys-columns", + "fix-pr-secret-event-name", + "alter-table-drop-counter", + "drop-senders", + "alter-table-logs-update-type-of-data", + "alter-table-add-secrets-user-id", + "lowercase-secret-names", + "recreate-agents-table", + "rename-builds-to-pipeline", + "rename-columns-builds-to-pipeline", + "rename-procs-to-steps", + "rename-remote-to-forge", + "rename-forge-id-to-forge-remote-id", + "remove-active-from-users", + "remove-inactive-repos", + "drop-files", + "remove-machine-col", + "drop-old-col", + "init-log_entries", + "migrate-logs-to-log_entries", + "parent-steps-to-workflows", + "add-orgs", + }).Delete() + + return err + }, +} diff --git a/server/store/datastore/migration/014_rename_forge_id_to_forge_remote_id.go b/server/store/datastore/migration/014_rename_forge_id_to_forge_remote_id.go deleted file mode 100644 index 60a58660d..000000000 --- a/server/store/datastore/migration/014_rename_forge_id_to_forge_remote_id.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2022 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package migration - -import ( - "src.techknowlogick.com/xormigrate" - "xorm.io/xorm" -) - -var renameForgeIDToForgeRemoteID = xormigrate.Migration{ - ID: "rename-forge-id-to-forge-remote-id", - MigrateSession: func(sess *xorm.Session) error { - return renameColumn(sess, "repos", "forge_id", "forge_remote_id") - }, -} diff --git a/server/store/datastore/migration/015_add_org_agents.go b/server/store/datastore/migration/015_add_org_agents.go new file mode 100644 index 000000000..045ce980e --- /dev/null +++ b/server/store/datastore/migration/015_add_org_agents.go @@ -0,0 +1,45 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package migration + +import ( + "fmt" + + "src.techknowlogick.com/xormigrate" + "xorm.io/xorm" + + "go.woodpecker-ci.org/woodpecker/v3/server/model" +) + +var addOrgAgents = xormigrate.Migration{ + ID: "add-org-agents", + MigrateSession: func(sess *xorm.Session) (err error) { + type agents struct { + ID int64 `xorm:"pk autoincr 'id'"` + OwnerID int64 `xorm:"INDEX 'owner_id'"` + OrgID int64 `xorm:"INDEX 'org_id'"` + } + + if err := sess.Sync(new(agents)); err != nil { + return fmt.Errorf("sync models failed: %w", err) + } + + // Update all existing agents to be global agents + _, err = sess.Cols("org_id").Update(&agents{ + OrgID: model.IDNotSet, + }) + return err + }, +} diff --git a/server/store/datastore/migration/015_remove_active_from_users.go b/server/store/datastore/migration/015_remove_active_from_users.go deleted file mode 100644 index d0eb785d1..000000000 --- a/server/store/datastore/migration/015_remove_active_from_users.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2022 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package migration - -import ( - "src.techknowlogick.com/xormigrate" - "xorm.io/xorm" -) - -var removeActiveFromUsers = xormigrate.Migration{ - ID: "remove-active-from-users", - MigrateSession: func(sess *xorm.Session) error { - return dropTableColumns(sess, "users", "user_active") - }, -} diff --git a/server/store/datastore/migration/016_add_custom_labels_to_agent.go b/server/store/datastore/migration/016_add_custom_labels_to_agent.go new file mode 100644 index 000000000..0e7f15de8 --- /dev/null +++ b/server/store/datastore/migration/016_add_custom_labels_to_agent.go @@ -0,0 +1,37 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package migration + +import ( + "fmt" + + "src.techknowlogick.com/xormigrate" + "xorm.io/xorm" +) + +var addCustomLabelsToAgent = xormigrate.Migration{ + ID: "add-custom-labels-to-agent", + MigrateSession: func(sess *xorm.Session) (err error) { + type agents struct { + ID int64 `xorm:"pk autoincr 'id'"` + CustomLabels map[string]string `xorm:"JSON 'custom_labels'"` + } + + if err := sess.Sync(new(agents)); err != nil { + return fmt.Errorf("sync models failed: %w", err) + } + return nil + }, +} diff --git a/server/store/datastore/migration/016_remove_inactive_repos.go b/server/store/datastore/migration/016_remove_inactive_repos.go deleted file mode 100644 index 48742f89b..000000000 --- a/server/store/datastore/migration/016_remove_inactive_repos.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2022 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package migration - -import ( - "src.techknowlogick.com/xormigrate" - "xorm.io/xorm" -) - -var removeInactiveRepos = xormigrate.Migration{ - ID: "remove-inactive-repos", - MigrateSession: func(sess *xorm.Session) error { - // If the timeout is 0, the repo was never activated, so we remove it. - _, err := sess.Table("repos").Where("repo_active = ?", false).And("repo_timeout = ?", 0).Delete() - if err != nil { - return err - } - - return dropTableColumns(sess, "users", "user_synced") - }, -} diff --git a/server/store/datastore/migration/017_split_trusted.go b/server/store/datastore/migration/017_split_trusted.go new file mode 100644 index 000000000..a03fa86c2 --- /dev/null +++ b/server/store/datastore/migration/017_split_trusted.go @@ -0,0 +1,69 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package migration + +import ( + "fmt" + + "src.techknowlogick.com/xormigrate" + "xorm.io/xorm" + + "go.woodpecker-ci.org/woodpecker/v3/server/model" +) + +var splitTrusted = xormigrate.Migration{ + ID: "split-trusted", + MigrateSession: func(sess *xorm.Session) error { + type repos struct { + ID int64 `xorm:"pk autoincr 'id'"` + IsTrusted bool `xorm:"'trusted'"` + Trusted model.TrustedConfiguration `xorm:"json 'trusted_conf'"` + } + + if err := sess.Sync(new(repos)); err != nil { + return fmt.Errorf("sync new models failed: %w", err) + } + + if _, err := sess.Where("trusted = ?", false).Cols("trusted_conf").Update(&repos{ + Trusted: model.TrustedConfiguration{ + Network: false, + Security: false, + Volumes: false, + }, + }); err != nil { + return err + } + + if _, err := sess.Where("trusted = ?", true).Cols("trusted_conf").Update(&repos{ + Trusted: model.TrustedConfiguration{ + Network: true, + Security: true, + Volumes: true, + }, + }); err != nil { + return err + } + + if err := dropTableColumns(sess, "repos", "trusted"); err != nil { + return err + } + + if err := sess.Commit(); err != nil { + return err + } + + return renameColumn(sess, "repos", "trusted_conf", "trusted") + }, +} diff --git a/server/store/datastore/migration/018_fix-orgs-users-match.go b/server/store/datastore/migration/018_fix-orgs-users-match.go new file mode 100644 index 000000000..f99e2af15 --- /dev/null +++ b/server/store/datastore/migration/018_fix-orgs-users-match.go @@ -0,0 +1,59 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package migration + +import ( + "fmt" + + "src.techknowlogick.com/xormigrate" + "xorm.io/xorm" + "xorm.io/xorm/schemas" +) + +var correctPotentialCorruptOrgsUsersRelation = xormigrate.Migration{ + ID: "correct-potential-corrupt-orgs-users-relation", + MigrateSession: func(sess *xorm.Session) error { + type users struct { + ID int64 `xorm:"pk autoincr 'id'"` + ForgeID int64 `xorm:"forge_id"` + Login string `xorm:"UNIQUE 'login'"` + OrgID int64 `xorm:"org_id"` + } + + type orgs struct { + ID int64 `xorm:"pk autoincr 'id'"` + ForgeID int64 `xorm:"forge_id"` + Name string `xorm:"UNIQUE 'name'"` + } + + if err := sess.Sync(new(users), new(orgs)); err != nil { + return fmt.Errorf("sync new models failed: %w", err) + } + + dialect := sess.Engine().Dialect().URI().DBType + var err error + switch dialect { + case schemas.MYSQL: + _, err = sess.Exec(`UPDATE users u JOIN orgs o ON o.name = u.login AND o.forge_id = u.forge_id SET u.org_id = o.id;`) + case schemas.POSTGRES: + _, err = sess.Exec(`UPDATE users u SET org_id = o.id FROM orgs o WHERE o.name = u.login AND o.forge_id = u.forge_id;`) + case schemas.SQLITE: + _, err = sess.Exec(`UPDATE users SET org_id = ( SELECT orgs.id FROM orgs WHERE orgs.name = users.login AND orgs.forge_id = users.forge_id ) WHERE users.login IN (SELECT orgs.name FROM orgs);`) + default: + err = fmt.Errorf("dialect '%s' not supported", dialect) + } + return err + }, +} diff --git a/server/store/datastore/migration/018_remove_machine_col.go b/server/store/datastore/migration/018_remove_machine_col.go deleted file mode 100644 index 791104479..000000000 --- a/server/store/datastore/migration/018_remove_machine_col.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2023 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package migration - -import ( - "src.techknowlogick.com/xormigrate" - "xorm.io/xorm" -) - -type oldStep018 struct { - ID int64 `xorm:"pk autoincr 'step_id'"` - Machine string `xorm:"step_machine"` -} - -func (oldStep018) TableName() string { - return "steps" -} - -var removeMachineCol = xormigrate.Migration{ - ID: "remove-machine-col", - MigrateSession: func(sess *xorm.Session) error { - // make sure step_machine column exists - if err := sess.Sync(new(oldStep018)); err != nil { - return err - } - return dropTableColumns(sess, "steps", "step_machine") - }, -} diff --git a/server/store/datastore/migration/019_drop_old_cols.go b/server/store/datastore/migration/019_drop_old_cols.go deleted file mode 100644 index f380c924e..000000000 --- a/server/store/datastore/migration/019_drop_old_cols.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2023 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package migration - -import ( - "src.techknowlogick.com/xormigrate" - "xorm.io/xorm" -) - -type oldPipeline019 struct { - ID int64 `xorm:"pk autoincr 'pipeline_id'"` - Signed bool `xorm:"pipeline_signed"` - Verified bool `xorm:"pipeline_verified"` -} - -func (oldPipeline019) TableName() string { - return "pipelines" -} - -var dropOldCols = xormigrate.Migration{ - ID: "drop-old-col", - MigrateSession: func(sess *xorm.Session) error { - // make sure columns on pipelines exist - if err := sess.Sync(new(oldPipeline019)); err != nil { - return err - } - if err := dropTableColumns(sess, "steps", "step_pgid"); err != nil { - return err - } - - return dropTableColumns(sess, "pipelines", "pipeline_signed", "pipeline_verified") - }, -} diff --git a/server/store/datastore/migration/019_gated_to_require_approval.go b/server/store/datastore/migration/019_gated_to_require_approval.go new file mode 100644 index 000000000..d025d1e76 --- /dev/null +++ b/server/store/datastore/migration/019_gated_to_require_approval.go @@ -0,0 +1,62 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package migration + +import ( + "fmt" + + "src.techknowlogick.com/xormigrate" + "xorm.io/builder" + "xorm.io/xorm" +) + +var gatedToRequireApproval = xormigrate.Migration{ + ID: "gated-to-require-approval", + MigrateSession: func(sess *xorm.Session) (err error) { + const ( + requireApprovalOldNotGated string = "old_not_gated" + requireApprovalAllEvents string = "all_events" + ) + + type repos struct { + ID int64 `xorm:"pk autoincr 'id'"` + IsGated bool `xorm:"gated"` + RequireApproval string `xorm:"require_approval"` + Visibility string `xorm:"varchar(10) 'visibility'"` + } + + if err := sess.Sync(new(repos)); err != nil { + return fmt.Errorf("sync new models failed: %w", err) + } + + // migrate gated repos + if _, err := sess.Exec( + builder.Update(builder.Eq{"require_approval": requireApprovalAllEvents}). + From("repos"). + Where(builder.Eq{"gated": true})); err != nil { + return err + } + + // migrate non gated repos to old_not_gated (no approval required) + if _, err := sess.Exec( + builder.Update(builder.Eq{"require_approval": requireApprovalOldNotGated}). + From("repos"). + Where(builder.Eq{"gated": false})); err != nil { + return err + } + + return dropTableColumns(sess, "repos", "gated") + }, +} diff --git a/server/store/datastore/migration/020_alter_logs_table.go b/server/store/datastore/migration/020_alter_logs_table.go deleted file mode 100644 index 33e050f32..000000000 --- a/server/store/datastore/migration/020_alter_logs_table.go +++ /dev/null @@ -1,171 +0,0 @@ -// Copyright 2023 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package migration - -import ( - "context" - "encoding/json" - "fmt" - "runtime" - - "github.com/rs/zerolog/log" - "github.com/tevino/abool/v2" - "src.techknowlogick.com/xormigrate" - "xorm.io/xorm" - - "go.woodpecker-ci.org/woodpecker/v2/shared/utils" -) - -// perPage020 sets the size of the slice to read per page. -var perPage020 = 100 - -type oldLogs020 struct { - ID int64 `xorm:"pk autoincr 'log_id'"` - StepID int64 `xorm:"UNIQUE 'log_step_id'"` - Data []byte `xorm:"LONGBLOB 'log_data'"` -} - -func (oldLogs020) TableName() string { - return "logs" -} - -type oldLogEntry020 struct { - Step string `json:"step,omitempty"` - Time int64 `json:"time,omitempty"` - Type int `json:"type,omitempty"` - Pos int `json:"pos,omitempty"` - Out string `json:"out,omitempty"` -} - -type newLogEntry020 struct { - ID int64 `xorm:"pk autoincr 'id'"` - StepID int64 `xorm:"'step_id'"` - Time int64 - Line int - Data []byte `xorm:"LONGBLOB"` - Created int64 `xorm:"created"` - Type int -} - -func (newLogEntry020) TableName() string { - return "log_entries" -} - -var initLogsEntriesTable = xormigrate.Migration{ - ID: "init-log_entries", - MigrateSession: func(sess *xorm.Session) error { - return sess.Sync(new(newLogEntry020)) - }, -} - -var migrateLogs2LogEntries = xormigrate.Migration{ - ID: "migrate-logs-to-log_entries", - Long: true, - Migrate: func(e *xorm.Engine) error { - // make sure old logs table exists - if exist, err := e.IsTableExist(new(oldLogs020)); !exist || err != nil { - return err - } - - if err := e.Sync(new(oldLogs020)); err != nil { - return err - } - - hasJSONErrors := false - - page := 0 - offset := 0 - logs := make([]*oldLogs020, 0, perPage020) - logEntries := make([]*oldLogEntry020, 0, 50) - - sigterm := abool.New() - ctx, cancelCtx := context.WithCancelCause(context.Background()) - defer cancelCtx(nil) - _ = utils.WithContextSigtermCallback(ctx, func() { - log.Info().Msg("ctrl+c received, stopping current migration") - sigterm.Set() - }) - - for { - if sigterm.IsSet() { - return fmt.Errorf("migration 'migrate-logs-to-log_entries' gracefully aborted") - } - - sess := e.NewSession().NoCache() - defer sess.Close() - if err := sess.Begin(); err != nil { - return err - } - logs = logs[:0] - - err := sess.Limit(perPage020, offset).Find(&logs) - if err != nil { - return err - } - - log.Trace().Msgf("migrate-logs-to-log_entries: process page %d", page) - - for _, l := range logs { - logEntries = logEntries[:0] - if err := json.Unmarshal(l.Data, &logEntries); err != nil { - hasJSONErrors = true - offset++ - continue - } - - time := int64(0) - for _, logEntry := range logEntries { - - if logEntry.Time > time { - time = logEntry.Time - } - - log := &newLogEntry020{ - StepID: l.StepID, - Data: []byte(logEntry.Out), - Line: logEntry.Pos, - Time: time, - Type: logEntry.Type, - } - - if _, err := sess.Insert(log); err != nil { - return err - } - } - - if _, err := sess.Delete(l); err != nil { - return err - } - } - - if err := sess.Commit(); err != nil { - return err - } - - if len(logs) < perPage020 { - break - } - - runtime.GC() - page++ - } - - if hasJSONErrors { - return fmt.Errorf("skipped some logs as json could not be deserialized for them") - } - - return e.DropTables("logs") - }, -} diff --git a/server/store/datastore/migration/009_recreate_agents_table.go b/server/store/datastore/migration/020_remove_repo_netrc_only_trusted.go similarity index 57% rename from server/store/datastore/migration/009_recreate_agents_table.go rename to server/store/datastore/migration/020_remove_repo_netrc_only_trusted.go index b0a8d1f60..f93c61bc5 100644 --- a/server/store/datastore/migration/009_recreate_agents_table.go +++ b/server/store/datastore/migration/020_remove_repo_netrc_only_trusted.go @@ -1,10 +1,10 @@ -// Copyright 2022 Woodpecker Authors +// Copyright 2024 Woodpecker Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -17,16 +17,20 @@ package migration import ( "src.techknowlogick.com/xormigrate" "xorm.io/xorm" - - "go.woodpecker-ci.org/woodpecker/v2/server/model" ) -var recreateAgentsTable = xormigrate.Migration{ - ID: "recreate-agents-table", - MigrateSession: func(sess *xorm.Session) error { - if err := sess.DropTable("agents"); err != nil { +var removeRepoNetrcOnlyTrusted = xormigrate.Migration{ + ID: "remove-repo-netrc-only-trusted", + MigrateSession: func(sess *xorm.Session) (err error) { + type repos struct { + NetrcOnlyTrusted string `xorm:"netrc_only_trusted"` + } + + // ensure columns to drop exist + if err := sess.Sync(new(repos)); err != nil { return err } - return sess.Sync(new(model.Agent)) + + return dropTableColumns(sess, "repos", "netrc_only_trusted") }, } diff --git a/server/store/datastore/migration/021_parent_steps_to_workflows.go b/server/store/datastore/migration/021_parent_steps_to_workflows.go deleted file mode 100644 index a97efb1d2..000000000 --- a/server/store/datastore/migration/021_parent_steps_to_workflows.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2022 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package migration - -import ( - "src.techknowlogick.com/xormigrate" - "xorm.io/xorm" - - "go.woodpecker-ci.org/woodpecker/v2/server/model" -) - -type oldStep021 struct { - ID int64 `xorm:"pk autoincr 'step_id'"` - PipelineID int64 `xorm:"UNIQUE(s) INDEX 'step_pipeline_id'"` - PID int `xorm:"UNIQUE(s) 'step_pid'"` - PPID int `xorm:"step_ppid"` - Name string `xorm:"step_name"` - State model.StatusValue `xorm:"step_state"` - Error string `xorm:"TEXT 'step_error'"` - Started int64 `xorm:"step_started"` - Stopped int64 `xorm:"step_stopped"` - AgentID int64 `xorm:"step_agent_id"` - Platform string `xorm:"step_platform"` - Environ map[string]string `xorm:"json 'step_environ'"` -} - -func (oldStep021) TableName() string { - return "steps" -} - -var parentStepsToWorkflows = xormigrate.Migration{ - ID: "parent-steps-to-workflows", - MigrateSession: func(sess *xorm.Session) error { - if err := sess.Sync(new(workflowV031)); err != nil { - return err - } - // make sure the columns exist before removing them - if err := sess.Sync(new(oldStep021)); err != nil { - return err - } - - var parentSteps []*oldStep021 - err := sess.Where("step_ppid = ?", 0).Find(&parentSteps) - if err != nil { - return err - } - - for _, p := range parentSteps { - asWorkflow := &workflowV031{ - PipelineID: p.PipelineID, - PID: p.PID, - Name: p.Name, - State: p.State, - Error: p.Error, - Started: p.Started, - Stopped: p.Stopped, - AgentID: p.AgentID, - Platform: p.Platform, - Environ: p.Environ, - } - - _, err = sess.Insert(asWorkflow) - if err != nil { - return err - } - - _, err = sess.Delete(&oldStep021{ID: p.ID}) - if err != nil { - return err - } - } - - return dropTableColumns(sess, "steps", "step_agent_id", "step_platform", "step_environ") - }, -} diff --git a/server/store/datastore/migration/007_log_data_type.go b/server/store/datastore/migration/021_rename_token_fields.go similarity index 57% rename from server/store/datastore/migration/007_log_data_type.go rename to server/store/datastore/migration/021_rename_token_fields.go index cb70c5d2c..34d07b387 100644 --- a/server/store/datastore/migration/007_log_data_type.go +++ b/server/store/datastore/migration/021_rename_token_fields.go @@ -1,4 +1,4 @@ -// Copyright 2023 Woodpecker Authors +// Copyright 2024 Woodpecker Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -17,24 +17,25 @@ package migration import ( "src.techknowlogick.com/xormigrate" "xorm.io/xorm" - "xorm.io/xorm/schemas" ) -var alterTableLogUpdateColumnLogDataType = xormigrate.Migration{ - ID: "alter-table-logs-update-type-of-data", +var renameTokenFields = xormigrate.Migration{ + ID: "rename-token-fields", MigrateSession: func(sess *xorm.Session) (err error) { - dialect := sess.Engine().Dialect().URI().DBType - - switch dialect { - case schemas.POSTGRES: - _, err = sess.Exec("ALTER TABLE logs ALTER COLUMN log_data TYPE BYTEA") - case schemas.MYSQL: - _, err = sess.Exec("ALTER TABLE logs MODIFY COLUMN log_data LONGBLOB") - default: - // sqlite does only know BLOB in all cases - return nil + type users struct { + AccessToken string `xorm:"TEXT 'token'"` + RefreshToken string `xorm:"TEXT 'secret'"` } - return err + // ensure columns to rename exist + if err := sess.Sync(new(users)); err != nil { + return err + } + + if err := renameColumn(sess, "users", "token", "access_token"); err != nil { + return err + } + + return renameColumn(sess, "users", "secret", "refresh_token") }, } diff --git a/server/store/datastore/migration/022_add_orgs.go b/server/store/datastore/migration/022_add_orgs.go deleted file mode 100644 index 9d93e0537..000000000 --- a/server/store/datastore/migration/022_add_orgs.go +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2022 Woodpecker Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package migration - -import ( - "fmt" - "strings" - - "src.techknowlogick.com/xormigrate" - "xorm.io/builder" - "xorm.io/xorm" - - "go.woodpecker-ci.org/woodpecker/v2/server/model" -) - -type oldSecret022 struct { - ID int64 `xorm:"pk autoincr 'secret_id'"` - Owner string `xorm:"'secret_owner'"` - OrgID int64 `xorm:"NOT NULL DEFAULT 0 'secret_org_id'"` - RepoID int64 `xorm:"NOT NULL DEFAULT 0 'secret_repo_id'"` - Name string `xorm:"NOT NULL INDEX 'secret_name'"` -} - -func (oldSecret022) TableName() string { - return "secrets" -} - -type syncRepo022 struct { - OrgID int64 `json:"org_id" xorm:"repo_org_id"` -} - -// TableName return database table name for xorm. -func (syncRepo022) TableName() string { - return "repos" -} - -type repo022 struct { - ID int64 `json:"id,omitempty" xorm:"pk autoincr 'repo_id'"` - OrgID int64 `json:"org_id" xorm:"repo_org_id"` - Owner string `json:"owner" xorm:"UNIQUE(name) 'repo_owner'"` -} - -// TableName return database table name for xorm. -func (repo022) TableName() string { - return "repos" -} - -var addOrgs = xormigrate.Migration{ - ID: "add-orgs", - MigrateSession: func(sess *xorm.Session) error { - if exist, err := sess.IsTableExist("orgs"); exist && err == nil { - if err := sess.DropTable("orgs"); err != nil { - return fmt.Errorf("drop old orgs table failed: %w", err) - } - } - - if err := sess.Sync(new(model.Org), new(syncRepo022), new(userV031)); err != nil { - return fmt.Errorf("sync new models failed: %w", err) - } - - // make sure the columns exist before removing them - if _, err := sess.SyncWithOptions(xorm.SyncOptions{IgnoreConstrains: true, IgnoreIndices: true}, new(oldSecret022)); err != nil { - return fmt.Errorf("sync old secrets models failed: %w", err) - } - - // get all org names from repos - var repos []*repo022 - if err := sess.Find(&repos); err != nil { - return fmt.Errorf("find all repos failed: %w", err) - } - - orgs := make(map[string]*model.Org) - users := make(map[string]bool) - for _, repo := range repos { - orgName := strings.ToLower(repo.Owner) - - // check if it's a registered user - if _, ok := users[orgName]; !ok { - exist, err := sess.Where("user_login = ?", orgName).Exist(new(userV031)) - if err != nil { - return fmt.Errorf("check if user '%s' exist failed: %w", orgName, err) - } - users[orgName] = exist - } - - // create org if not already created - if _, ok := orgs[orgName]; !ok { - org := &model.Org{ - Name: orgName, - IsUser: users[orgName], - } - if _, err := sess.Insert(org); err != nil { - return fmt.Errorf("insert org %#v failed: %w", org, err) - } - orgs[orgName] = org - - // update org secrets - var secrets []*oldSecret022 - if err := sess.Where(builder.Eq{"secret_owner": orgName, "secret_repo_id": 0}).Find(&secrets); err != nil { - return fmt.Errorf("get org secrets failed: %w", err) - } - - for _, secret := range secrets { - secret.OrgID = org.ID - if _, err := sess.ID(secret.ID).Cols("secret_org_id").Update(secret); err != nil { - return fmt.Errorf("update org secret %d failed: %w", secret.ID, err) - } - } - } - - // update the repo - repo.OrgID = orgs[orgName].ID - if _, err := sess.ID(repo.ID).Cols("repo_org_id").Update(repo); err != nil { - return fmt.Errorf("update repos failed: %w", err) - } - } - - return dropTableColumns(sess, "secrets", "secret_owner") - }, -} diff --git a/server/store/datastore/migration/022_set-new-defaults-for-require_approval.go b/server/store/datastore/migration/022_set-new-defaults-for-require_approval.go new file mode 100644 index 000000000..b71709218 --- /dev/null +++ b/server/store/datastore/migration/022_set-new-defaults-for-require_approval.go @@ -0,0 +1,62 @@ +// Copyright 2024 Woodpecker Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package migration + +import ( + "fmt" + + "src.techknowlogick.com/xormigrate" + "xorm.io/builder" + "xorm.io/xorm" +) + +var setNewDefaultsForRequireApproval = xormigrate.Migration{ + ID: "set-new-defaults-for-require-approval", + MigrateSession: func(sess *xorm.Session) (err error) { + const ( + RequireApprovalOldNotGated string = "old_not_gated" + RequireApprovalNone string = "none" + RequireApprovalForks string = "forks" + RequireApprovalAllEvents string = "all_events" + ) + + type repos struct { + RequireApproval string `xorm:"require_approval"` + Visibility string `xorm:"varchar(10) 'visibility'"` + } + + if err := sess.Sync(new(repos)); err != nil { + return fmt.Errorf("sync new models failed: %w", err) + } + + // migrate public repos to require approval for forks + if _, err := sess.Exec( + builder.Update(builder.Eq{"require_approval": RequireApprovalForks}). + From("repos"). + Where(builder.Eq{"require_approval": RequireApprovalOldNotGated, "visibility": "public"})); err != nil { + return err + } + + // migrate private repos to require no approval + if _, err := sess.Exec( + builder.Update(builder.Eq{"require_approval": RequireApprovalNone}). + From("repos"). + Where(builder.Eq{"require_approval": RequireApprovalOldNotGated}.And(builder.Neq{"visibility": "public"}))); err != nil { + return err + } + + return nil + }, +} diff --git a/server/store/datastore/migration/010_rename_builds_to_pipeline.go b/server/store/datastore/migration/023_remove_repo_scm.go similarity index 60% rename from server/store/datastore/migration/010_rename_builds_to_pipeline.go rename to server/store/datastore/migration/023_remove_repo_scm.go index 9fd042425..196d4f9c9 100644 --- a/server/store/datastore/migration/010_rename_builds_to_pipeline.go +++ b/server/store/datastore/migration/023_remove_repo_scm.go @@ -1,10 +1,10 @@ -// Copyright 2022 Woodpecker Authors +// Copyright 2024 Woodpecker Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // -// http://www.apache.org/licenses/LICENSE-2.0 +// http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, @@ -19,17 +19,18 @@ import ( "xorm.io/xorm" ) -var renameBuildsToPipeline = xormigrate.Migration{ - ID: "rename-builds-to-pipeline", - MigrateSession: func(sess *xorm.Session) error { - err := renameTable(sess, "builds", "pipelines") - if err != nil { +var removeRepoScm = xormigrate.Migration{ + ID: "remove-repo-scm", + MigrateSession: func(sess *xorm.Session) (err error) { + type repos struct { + SCMKind string `xorm:"varchar(50) 'scm'"` + } + + // ensure columns to drop exist + if err := sess.Sync(new(repos)); err != nil { return err } - err = renameTable(sess, "build_config", "pipeline_config") - if err != nil { - return err - } - return nil + + return dropTableColumns(sess, "repos", "scm") }, } diff --git a/server/store/datastore/migration/common.go b/server/store/datastore/migration/common.go index d705f99be..ddc2502a0 100644 --- a/server/store/datastore/migration/common.go +++ b/server/store/datastore/migration/common.go @@ -23,14 +23,14 @@ import ( "xorm.io/xorm/schemas" ) -func renameTable(sess *xorm.Session, old, new string) error { +func renameTable(sess *xorm.Session, oldTable, newTable string) error { dialect := sess.Engine().Dialect().URI().DBType switch dialect { case schemas.MYSQL: - _, err := sess.Exec(fmt.Sprintf("RENAME TABLE `%s` TO `%s`;", old, new)) + _, err := sess.Exec(fmt.Sprintf("RENAME TABLE `%s` TO `%s`;", oldTable, newTable)) return err case schemas.POSTGRES, schemas.SQLITE: - _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` RENAME TO `%s`;", old, new)) + _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` RENAME TO `%s`;", oldTable, newTable)) return err default: return fmt.Errorf("dialect '%s' not supported", dialect) @@ -76,66 +76,14 @@ func dropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin } } - // Here we need to get the columns from the original table - sql := fmt.Sprintf("SELECT sql FROM sqlite_master WHERE tbl_name='%s' and type='table'", tableName) - res, err := sess.Query(sql) - if err != nil { - return err - } - tableSQL := normalizeSQLiteTableSchema(string(res[0]["sql"])) - - // Separate out the column definitions - sqlIndex := strings.Index(tableSQL, "(") - if sqlIndex < 0 { - return fmt.Errorf("could not separate column definitions") - } - tableSQL = tableSQL[sqlIndex:] - - // Remove the required columnNames - tableSQL = removeColumnFromSQLITETableSchema(tableSQL, columnNames...) - - // Ensure the query is ended properly - tableSQL = strings.TrimSpace(tableSQL) - if tableSQL[len(tableSQL)-1] != ')' { - if tableSQL[len(tableSQL)-1] == ',' { - tableSQL = tableSQL[:len(tableSQL)-1] + // Now drop the columns + for _, columnName := range columnNames { + _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` DROP COLUMN `%s`;", tableName, columnName)) + if err != nil { + return fmt.Errorf("table `%s`, drop column %v: %w", tableName, columnName, err) } - tableSQL += ")" } - // Find all the columns in the table - var columns []string - for _, rawColumn := range strings.Split(strings.ReplaceAll(tableSQL[1:len(tableSQL)-1], ", ", ",\n"), "\n") { - if strings.ContainsAny(rawColumn, "()") { - continue - } - rawColumn = strings.TrimSpace(rawColumn) - columns = append(columns, - strings.ReplaceAll(rawColumn[0:strings.Index(rawColumn, " ")], "`", ""), - ) - } - - tableSQL = fmt.Sprintf("CREATE TABLE `new_%s_new` ", tableName) + tableSQL - if _, err := sess.Exec(tableSQL); err != nil { - return err - } - - // Now restore the data - columnsSeparated := strings.Join(columns, ",") - insertSQL := fmt.Sprintf("INSERT INTO `new_%s_new` (%s) SELECT %s FROM %s", tableName, columnsSeparated, columnsSeparated, tableName) - if _, err := sess.Exec(insertSQL); err != nil { - return err - } - - // Now drop the old table - if _, err := sess.Exec(fmt.Sprintf("DROP TABLE `%s`", tableName)); err != nil { - return err - } - - // Rename the table - if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `new_%s_new` RENAME TO `%s`", tableName, tableName)); err != nil { - return err - } case schemas.POSTGRES: cols := "" for _, col := range columnNames { @@ -145,7 +93,7 @@ func dropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin cols += "DROP COLUMN `" + col + "` CASCADE" } if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil { - return fmt.Errorf("drop table `%s` columns %v: %w", tableName, columnNames, err) + return fmt.Errorf("table `%s`, drop columns %v: %w", tableName, columnNames, err) } case schemas.MYSQL: // Drop indexes on columns first @@ -173,7 +121,7 @@ func dropTableColumns(sess *xorm.Session, tableName string, columnNames ...strin cols += "DROP COLUMN `" + col + "`" } if _, err := sess.Exec(fmt.Sprintf("ALTER TABLE `%s` %s", tableName, cols)); err != nil { - return fmt.Errorf("drop table `%s` columns %v: %w", tableName, columnNames, err) + return fmt.Errorf("table `%s`, drop columns %v: %w", tableName, columnNames, err) } default: return fmt.Errorf("dialect '%s' not supported", dialect) @@ -214,7 +162,6 @@ func alterColumnDefault(sess *xorm.Session, table, column, defValue string) erro } } -//nolint:unparam func alterColumnNull(sess *xorm.Session, table, column string, null bool) error { val := "NULL" if !null { diff --git a/server/store/datastore/migration/migration.go b/server/store/datastore/migration/migration.go index bdcead5d9..d056aad56 100644 --- a/server/store/datastore/migration/migration.go +++ b/server/store/datastore/migration/migration.go @@ -15,43 +15,20 @@ package migration import ( + "context" "fmt" "reflect" "src.techknowlogick.com/xormigrate" "xorm.io/xorm" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) // APPEND NEW MIGRATIONS // They are executed in order and if one fails Xormigrate will try to rollback that specific one and quits. var migrationTasks = []*xormigrate.Migration{ &legacyToXormigrate, - &legacy2Xorm, - &alterTableReposDropFallback, - &alterTableReposDropAllowDeploysAllowTags, - &fixPRSecretEventName, - &alterTableReposDropCounter, - &dropSenders, - &alterTableLogUpdateColumnLogDataType, - &alterTableSecretsAddUserCol, - &recreateAgentsTable, - &lowercaseSecretNames, - &renameBuildsToPipeline, - &renameColumnsBuildsToPipeline, - &renameTableProcsToSteps, - &renameRemoteToForge, - &renameForgeIDToForgeRemoteID, - &removeActiveFromUsers, - &removeInactiveRepos, - &dropFiles, - &removeMachineCol, - &dropOldCols, - &initLogsEntriesTable, - &migrateLogs2LogEntries, - &parentStepsToWorkflows, - &addOrgs, &addOrgID, &alterTableTasksUpdateColumnTaskDataType, &alterTableConfigUpdateColumnConfigDataType, @@ -62,6 +39,19 @@ var migrationTasks = []*xormigrate.Migration{ &setForgeID, &unifyColumnsTables, &alterTableRegistriesFixRequiredFields, + &cronWithoutSec, + &renameStartEndTime, + &fixV31Registries, + &removeOldMigrationsOfV1, + &addOrgAgents, + &addCustomLabelsToAgent, + &splitTrusted, + &correctPotentialCorruptOrgsUsersRelation, + &gatedToRequireApproval, + &removeRepoNetrcOnlyTrusted, + &renameTokenFields, + &setNewDefaultsForRequireApproval, + &removeRepoScm, } var allBeans = []any{ @@ -85,7 +75,8 @@ var allBeans = []any{ new(model.Org), } -func Migrate(e *xorm.Engine, allowLong bool) error { +// TODO: make xormigrate context aware +func Migrate(_ context.Context, e *xorm.Engine, allowLong bool) error { e.SetDisableGlobalCache(true) m := xormigrate.New(e, migrationTasks) diff --git a/server/store/datastore/migration/migration_test.go b/server/store/datastore/migration/migration_test.go index cf4b1a761..41461217a 100644 --- a/server/store/datastore/migration/migration_test.go +++ b/server/store/datastore/migration/migration_test.go @@ -15,6 +15,7 @@ package migration import ( + "context" "os" "testing" "time" @@ -56,14 +57,14 @@ func createSQLiteDB(t *testing.T) string { return tmpF.Name() } -func testDB(t *testing.T, new bool) (engine *xorm.Engine, closeDB func()) { +func testDB(t *testing.T, initNewDB bool) (engine *xorm.Engine, closeDB func()) { driver := testDriver() var err error closeDB = func() {} switch driver { case "sqlite3": config := ":memory:" - if !new { + if !initNewDB { config = createSQLiteDB(t) closeDB = func() { _ = os.Remove(config) @@ -76,7 +77,7 @@ func testDB(t *testing.T, new bool) (engine *xorm.Engine, closeDB func()) { return case "mysql", "postgres": config := os.Getenv("WOODPECKER_DATABASE_DATASOURCE") - if !new { + if !initNewDB { t.Logf("do not have dump to test against") t.SkipNow() } @@ -95,7 +96,7 @@ func testDB(t *testing.T, new bool) (engine *xorm.Engine, closeDB func()) { func TestMigrate(t *testing.T) { // init new db engine, closeDB := testDB(t, true) - assert.NoError(t, Migrate(engine, true)) + assert.NoError(t, Migrate(context.Background(), engine, true)) closeDB() dbType := engine.Dialect().URI().DBType @@ -106,6 +107,6 @@ func TestMigrate(t *testing.T) { // migrate old db engine, closeDB = testDB(t, false) - assert.NoError(t, Migrate(engine, true)) + assert.NoError(t, Migrate(context.Background(), engine, true)) closeDB() } diff --git a/server/store/datastore/migration/test-files/sqlite.db b/server/store/datastore/migration/test-files/sqlite.db index 219e5184a..731dfe5cd 100644 Binary files a/server/store/datastore/migration/test-files/sqlite.db and b/server/store/datastore/migration/test-files/sqlite.db differ diff --git a/server/store/datastore/org.go b/server/store/datastore/org.go index b432fb65c..59fa18f80 100644 --- a/server/store/datastore/org.go +++ b/server/store/datastore/org.go @@ -20,7 +20,7 @@ import ( "xorm.io/xorm" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func (s storage) OrgCreate(org *model.Org) error { @@ -77,9 +77,15 @@ func (s storage) orgDelete(sess *xorm.Session, id int64) error { func (s storage) OrgFindByName(name string) (*model.Org, error) { // sanitize name = strings.ToLower(name) - // find org := new(model.Org) - return org, wrapGet(s.engine.Where("name = ?", name).Get(org)) + has, err := s.engine.Where("name = ?", name).Get(org) + if err != nil { + return nil, fmt.Errorf("failed to check if org exists: %w", err) + } + if !has { + return nil, nil + } + return org, nil } func (s storage) OrgRepoList(org *model.Org, p *model.ListOptions) ([]*model.Repo, error) { diff --git a/server/store/datastore/org_test.go b/server/store/datastore/org_test.go index a2a06f868..5136708f5 100644 --- a/server/store/datastore/org_test.go +++ b/server/store/datastore/org_test.go @@ -19,7 +19,7 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func TestOrgCRUD(t *testing.T) { diff --git a/server/store/datastore/permission.go b/server/store/datastore/permission.go index 22736fdc3..63a6e3dc6 100644 --- a/server/store/datastore/permission.go +++ b/server/store/datastore/permission.go @@ -20,7 +20,7 @@ import ( "xorm.io/builder" "xorm.io/xorm" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func (s storage) PermFind(user *model.User, repo *model.Repo) (*model.Perm, error) { diff --git a/server/store/datastore/permission_test.go b/server/store/datastore/permission_test.go index 2e7e8107a..45882c7c2 100644 --- a/server/store/datastore/permission_test.go +++ b/server/store/datastore/permission_test.go @@ -19,7 +19,7 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func TestPermFind(t *testing.T) { diff --git a/server/store/datastore/pipeline.go b/server/store/datastore/pipeline.go index 41cc1e780..14eff95c8 100644 --- a/server/store/datastore/pipeline.go +++ b/server/store/datastore/pipeline.go @@ -20,7 +20,7 @@ import ( "xorm.io/builder" "xorm.io/xorm" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func (s storage) GetPipeline(id int64) (*model.Pipeline, error) { @@ -35,6 +35,15 @@ func (s storage) GetPipelineNumber(repo *model.Repo, num int64) (*model.Pipeline ).Get(pipeline)) } +func (s storage) GetPipelineBadge(repo *model.Repo, branch string) (*model.Pipeline, error) { + pipeline := new(model.Pipeline) + return pipeline, wrapGet(s.engine. + Desc("number"). + Where(builder.Eq{"repo_id": repo.ID, "branch": branch, "event": model.EventPush}). + Where(builder.Neq{"status": model.StatusBlocked}). + Get(pipeline)) +} + func (s storage) GetPipelineLast(repo *model.Repo, branch string) (*model.Pipeline, error) { pipeline := new(model.Pipeline) return pipeline, wrapGet(s.engine. @@ -65,6 +74,22 @@ func (s storage) GetPipelineList(repo *model.Repo, p *model.ListOptions, f *mode if f.Before != 0 { cond = cond.And(builder.Lt{"created": f.Before}) } + + if f.Branch != "" { + cond = cond.And(builder.Eq{"branch": f.Branch}) + } + + if f.Status != "" { + cond = cond.And(builder.Eq{"status": f.Status}) + } + + if len(f.Events) != 0 { + cond = cond.And(builder.In("event", f.Events)) + } + + if f.RefContains != "" { + cond = cond.And(builder.Like{"ref", f.RefContains}) + } } return pipelines, s.paginate(p).Where(cond). diff --git a/server/store/datastore/pipeline_test.go b/server/store/datastore/pipeline_test.go index 091e44720..fed95149b 100644 --- a/server/store/datastore/pipeline_test.go +++ b/server/store/datastore/pipeline_test.go @@ -16,15 +16,13 @@ package datastore import ( - "fmt" "testing" "time" - "github.com/franela/goblin" "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store/types" ) func TestPipelines(t *testing.T) { @@ -38,218 +36,170 @@ func TestPipelines(t *testing.T) { store, closer := newTestStore(t, new(model.Repo), new(model.Step), new(model.Pipeline)) defer closer() - g := goblin.Goblin(t) - g.Describe("Pipelines", func() { - g.Before(func() { - _, err := store.engine.Exec("DELETE FROM repos") - g.Assert(err).IsNil() - g.Assert(store.CreateRepo(repo)).IsNil() - }) - g.After(func() { - _, err := store.engine.Exec("DELETE FROM repos") - g.Assert(err).IsNil() - }) + assert.NoError(t, store.CreateRepo(repo)) - // before each test be sure to purge the package - // table data from the database. - g.BeforeEach(func() { - _, err := store.engine.Exec("DELETE FROM pipelines") - g.Assert(err).IsNil() - _, err = store.engine.Exec("DELETE FROM steps") - g.Assert(err).IsNil() - }) + // Fail when the repo is not existing + pipeline := model.Pipeline{ + RepoID: 100, + Status: model.StatusSuccess, + } + err := store.CreatePipeline(&pipeline) + assert.Error(t, err) - g.It("Should Fail early when the repo is not existing", func() { - pipeline := model.Pipeline{ - RepoID: 100, - Status: model.StatusSuccess, - } - err := store.CreatePipeline(&pipeline) - g.Assert(err).IsNotNil() + count, err := store.GetPipelineCount() + assert.NoError(t, err) + assert.Zero(t, count) - count, err := store.GetPipelineCount() - g.Assert(err).IsNil() - g.Assert(count == 0).IsTrue() - fmt.Println("GOT COUNT", count) - }) + // add pipeline + pipeline = model.Pipeline{ + RepoID: repo.ID, + Status: model.StatusSuccess, + Commit: "85f8c029b902ed9400bc600bac301a0aadb144ac", + Branch: "some-branch", + } + err = store.CreatePipeline(&pipeline) + assert.NoError(t, err) + assert.NotZero(t, pipeline.ID) + assert.EqualValues(t, 1, pipeline.Number) + assert.Equal(t, "85f8c029b902ed9400bc600bac301a0aadb144ac", pipeline.Commit) - g.It("Should Post a Pipeline", func() { - pipeline := model.Pipeline{ - RepoID: repo.ID, - Status: model.StatusSuccess, - Commit: "85f8c029b902ed9400bc600bac301a0aadb144ac", - } - err := store.CreatePipeline(&pipeline) - g.Assert(err).IsNil() - g.Assert(pipeline.ID != 0).IsTrue() - g.Assert(pipeline.Number).Equal(int64(1)) - g.Assert(pipeline.Commit).Equal("85f8c029b902ed9400bc600bac301a0aadb144ac") + count, err = store.GetPipelineCount() + assert.NoError(t, err) + assert.NotZero(t, count) - count, err := store.GetPipelineCount() - g.Assert(err).IsNil() - g.Assert(count > 0).IsTrue() - fmt.Println("GOT COUNT", count) - }) + GetPipeline, err := store.GetPipeline(pipeline.ID) + assert.NoError(t, err) + assert.Equal(t, pipeline.ID, GetPipeline.ID) + assert.Equal(t, pipeline.RepoID, GetPipeline.RepoID) + assert.Equal(t, pipeline.Status, GetPipeline.Status) - g.It("Should Put a Pipeline", func() { - pipeline := model.Pipeline{ - RepoID: repo.ID, - Number: 5, - Status: model.StatusSuccess, - Commit: "85f8c029b902ed9400bc600bac301a0aadb144ac", - } - err := store.CreatePipeline(&pipeline) - g.Assert(err).IsNil() - pipeline.Status = model.StatusRunning - err1 := store.UpdatePipeline(&pipeline) - GetPipeline, err2 := store.GetPipeline(pipeline.ID) - g.Assert(err1).IsNil() - g.Assert(err2).IsNil() - g.Assert(pipeline.ID).Equal(GetPipeline.ID) - g.Assert(pipeline.RepoID).Equal(GetPipeline.RepoID) - g.Assert(pipeline.Status).Equal(GetPipeline.Status) - g.Assert(pipeline.Number).Equal(GetPipeline.Number) - }) + // update pipeline + pipeline.Status = model.StatusRunning + err1 := store.UpdatePipeline(&pipeline) + GetPipeline, err2 := store.GetPipeline(pipeline.ID) + assert.NoError(t, err1) + assert.NoError(t, err2) + assert.Equal(t, pipeline.ID, GetPipeline.ID) + assert.Equal(t, pipeline.RepoID, GetPipeline.RepoID) + assert.Equal(t, pipeline.Status, GetPipeline.Status) + assert.Equal(t, pipeline.Number, GetPipeline.Number) - g.It("Should Get a Pipeline", func() { - pipeline := model.Pipeline{ - RepoID: repo.ID, - Status: model.StatusSuccess, - } - err := store.CreatePipeline(&pipeline, []*model.Step{}...) - g.Assert(err).IsNil() - GetPipeline, err := store.GetPipeline(pipeline.ID) - g.Assert(err).IsNil() - g.Assert(pipeline.ID).Equal(GetPipeline.ID) - g.Assert(pipeline.RepoID).Equal(GetPipeline.RepoID) - g.Assert(pipeline.Status).Equal(GetPipeline.Status) - }) + pipeline2 := &model.Pipeline{ + RepoID: repo.ID, + Status: model.StatusPending, + Event: model.EventPush, + Branch: "main", + } + err2 = store.CreatePipeline(pipeline2, []*model.Step{}...) + assert.NoError(t, err2) + GetPipeline, err3 := store.GetPipelineNumber(&model.Repo{ID: 1}, pipeline2.Number) + assert.NoError(t, err3) + assert.Equal(t, pipeline2.ID, GetPipeline.ID) + assert.Equal(t, pipeline2.RepoID, GetPipeline.RepoID) + assert.Equal(t, pipeline2.Number, GetPipeline.Number) - g.It("Should Get a Pipeline by Number", func() { - pipeline1 := &model.Pipeline{ - RepoID: repo.ID, - Status: model.StatusPending, - } - pipeline2 := &model.Pipeline{ - RepoID: repo.ID, - Status: model.StatusPending, - } - err1 := store.CreatePipeline(pipeline1, []*model.Step{}...) - g.Assert(err1).IsNil() - err2 := store.CreatePipeline(pipeline2, []*model.Step{}...) - g.Assert(err2).IsNil() - GetPipeline, err3 := store.GetPipelineNumber(&model.Repo{ID: 1}, pipeline2.Number) - g.Assert(err3).IsNil() - g.Assert(pipeline2.ID).Equal(GetPipeline.ID) - g.Assert(pipeline2.RepoID).Equal(GetPipeline.RepoID) - g.Assert(pipeline2.Number).Equal(GetPipeline.Number) - }) + GetPipeline, err3 = store.GetPipelineLast(&model.Repo{ID: repo.ID}, pipeline2.Branch) + assert.NoError(t, err3) + assert.Equal(t, pipeline2.ID, GetPipeline.ID) + assert.Equal(t, pipeline2.RepoID, GetPipeline.RepoID) + assert.Equal(t, pipeline2.Number, GetPipeline.Number) + assert.Equal(t, pipeline2.Status, GetPipeline.Status) - g.It("Should Get the last Pipeline", func() { - pipeline1 := &model.Pipeline{ - RepoID: repo.ID, - Status: model.StatusFailure, - Branch: "main", - Commit: "85f8c029b902ed9400bc600bac301a0aadb144ac", - Event: model.EventPush, - } - pipeline2 := &model.Pipeline{ - RepoID: repo.ID, - Status: model.StatusSuccess, - Branch: "main", - Commit: "85f8c029b902ed9400bc600bac301a0aadb144aa", - Event: model.EventPush, - } - err1 := store.CreatePipeline(pipeline1, []*model.Step{}...) - err2 := store.CreatePipeline(pipeline2, []*model.Step{}...) - GetPipeline, err3 := store.GetPipelineLast(&model.Repo{ID: 1}, pipeline2.Branch) - g.Assert(err1).IsNil() - g.Assert(err2).IsNil() - g.Assert(err3).IsNil() - g.Assert(pipeline2.ID).Equal(GetPipeline.ID) - g.Assert(pipeline2.RepoID).Equal(GetPipeline.RepoID) - g.Assert(pipeline2.Number).Equal(GetPipeline.Number) - g.Assert(pipeline2.Status).Equal(GetPipeline.Status) - g.Assert(pipeline2.Branch).Equal(GetPipeline.Branch) - g.Assert(pipeline2.Commit).Equal(GetPipeline.Commit) - }) + pipeline3 := &model.Pipeline{ + RepoID: repo.ID, + Status: model.StatusRunning, + Branch: "main", + Commit: "85f8c029b902ed9400bc600bac301a0aadb144aa", + } + err1 = store.CreatePipeline(pipeline3, []*model.Step{}...) + assert.NoError(t, err1) - g.It("Should Get the last Pipeline Before Pipeline N", func() { - pipeline1 := &model.Pipeline{ - RepoID: repo.ID, - Status: model.StatusFailure, - Branch: "main", - Commit: "85f8c029b902ed9400bc600bac301a0aadb144ac", - } - pipeline2 := &model.Pipeline{ - RepoID: repo.ID, - Status: model.StatusSuccess, - Branch: "main", - Commit: "85f8c029b902ed9400bc600bac301a0aadb144aa", - } - pipeline3 := &model.Pipeline{ - RepoID: repo.ID, - Status: model.StatusRunning, - Branch: "main", - Commit: "85f8c029b902ed9400bc600bac301a0aadb144aa", - } - err1 := store.CreatePipeline(pipeline1, []*model.Step{}...) - g.Assert(err1).IsNil() - err2 := store.CreatePipeline(pipeline2, []*model.Step{}...) - g.Assert(err2).IsNil() - err3 := store.CreatePipeline(pipeline3, []*model.Step{}...) - g.Assert(err3).IsNil() - GetPipeline, err4 := store.GetPipelineLastBefore(&model.Repo{ID: 1}, pipeline3.Branch, pipeline3.ID) - g.Assert(err4).IsNil() - g.Assert(pipeline2.ID).Equal(GetPipeline.ID) - g.Assert(pipeline2.RepoID).Equal(GetPipeline.RepoID) - g.Assert(pipeline2.Number).Equal(GetPipeline.Number) - g.Assert(pipeline2.Status).Equal(GetPipeline.Status) - g.Assert(pipeline2.Branch).Equal(GetPipeline.Branch) - g.Assert(pipeline2.Commit).Equal(GetPipeline.Commit) - }) + GetPipeline, err4 := store.GetPipelineLastBefore(&model.Repo{ID: 1}, pipeline3.Branch, pipeline3.ID) + assert.NoError(t, err4) + assert.Equal(t, pipeline2.ID, GetPipeline.ID) + assert.Equal(t, pipeline2.RepoID, GetPipeline.RepoID) + assert.Equal(t, pipeline2.Number, GetPipeline.Number) + assert.Equal(t, pipeline2.Status, GetPipeline.Status) + assert.Equal(t, pipeline2.Branch, GetPipeline.Branch) + assert.Equal(t, pipeline2.Commit, GetPipeline.Commit) +} - g.It("Should get recent pipelines", func() { - pipeline1 := &model.Pipeline{ - RepoID: repo.ID, - Status: model.StatusFailure, - } - pipeline2 := &model.Pipeline{ - RepoID: repo.ID, - Status: model.StatusSuccess, - } - err1 := store.CreatePipeline(pipeline1, []*model.Step{}...) - g.Assert(err1).IsNil() - err2 := store.CreatePipeline(pipeline2, []*model.Step{}...) - g.Assert(err2).IsNil() - pipelines, err3 := store.GetPipelineList(&model.Repo{ID: 1}, &model.ListOptions{Page: 1, PerPage: 50}, nil) - g.Assert(err3).IsNil() - g.Assert(len(pipelines)).Equal(2) - g.Assert(pipelines[0].ID).Equal(pipeline2.ID) - g.Assert(pipelines[0].RepoID).Equal(pipeline2.RepoID) - g.Assert(pipelines[0].Status).Equal(pipeline2.Status) - }) +func TestPipelineListFilter(t *testing.T) { + repo := &model.Repo{ + UserID: 1, + FullName: "bradrydzewski/test", + Owner: "bradrydzewski", + Name: "test", + } - g.It("Should get filtered pipelines", func() { - pipeline1 := &model.Pipeline{ - RepoID: repo.ID, - } - pipeline2 := &model.Pipeline{ - RepoID: repo.ID, - } - err1 := store.CreatePipeline(pipeline1, []*model.Step{}...) - g.Assert(err1).IsNil() - time.Sleep(1 * time.Second) - before := time.Now().Unix() - err2 := store.CreatePipeline(pipeline2, []*model.Step{}...) - g.Assert(err2).IsNil() - pipelines, err3 := store.GetPipelineList(&model.Repo{ID: 1}, &model.ListOptions{Page: 1, PerPage: 50}, &model.PipelineFilter{Before: before}) - g.Assert(err3).IsNil() - g.Assert(len(pipelines)).Equal(1) - g.Assert(pipelines[0].ID).Equal(pipeline1.ID) - g.Assert(pipelines[0].RepoID).Equal(pipeline1.RepoID) - }) + store, closer := newTestStore(t, new(model.Repo), new(model.Step), new(model.Pipeline)) + defer closer() + + assert.NoError(t, store.CreateRepo(repo)) + + pipeline1 := &model.Pipeline{ + RepoID: repo.ID, + Status: model.StatusFailure, + Event: model.EventCron, + Ref: "refs/heads/some-branch", + Branch: "some-branch", + } + pipeline2 := &model.Pipeline{ + RepoID: repo.ID, + Status: model.StatusSuccess, + Event: model.EventPull, + Ref: "refs/pull/32", + Branch: "main", + } + err := store.CreatePipeline(pipeline1, []*model.Step{}...) + assert.NoError(t, err) + time.Sleep(1 * time.Second) + before := time.Now().Unix() + err = store.CreatePipeline(pipeline2, []*model.Step{}...) + assert.NoError(t, err) + + pipelines, err := store.GetPipelineList(&model.Repo{ID: 1}, &model.ListOptions{Page: 1, PerPage: 50}, nil) + assert.NoError(t, err) + assert.Len(t, (pipelines), 2) + assert.Equal(t, pipeline2.ID, pipelines[0].ID) + assert.Equal(t, pipeline2.RepoID, pipelines[0].RepoID) + assert.Equal(t, pipeline2.Status, pipelines[0].Status) + + pipelines, err = store.GetPipelineList(&model.Repo{ID: 1}, nil, &model.PipelineFilter{ + Branch: "main", }) + assert.NoError(t, err) + assert.Len(t, pipelines, 1) + assert.Equal(t, pipeline2.ID, pipelines[0].ID) + + pipelines, err = store.GetPipelineList(&model.Repo{ID: 1}, nil, &model.PipelineFilter{ + Events: []model.WebhookEvent{model.EventCron}, + }) + assert.NoError(t, err) + assert.Len(t, pipelines, 1) + assert.Equal(t, pipeline1.ID, pipelines[0].ID) + + pipelines, err = store.GetPipelineList(&model.Repo{ID: 1}, nil, &model.PipelineFilter{ + Events: []model.WebhookEvent{model.EventCron, model.EventPull}, + RefContains: "32", + }) + assert.NoError(t, err) + assert.Len(t, (pipelines), 1) + assert.Equal(t, pipeline2.ID, pipelines[0].ID) + + pipelines, err3 := store.GetPipelineList(&model.Repo{ID: 1}, &model.ListOptions{Page: 1, PerPage: 50}, &model.PipelineFilter{Before: before}) + assert.NoError(t, err3) + assert.Len(t, pipelines, 1) + assert.Equal(t, pipeline1.ID, pipelines[0].ID) + assert.Equal(t, pipeline1.RepoID, pipelines[0].RepoID) + + pipelines, err = store.GetPipelineList(&model.Repo{ID: 1}, nil, &model.PipelineFilter{ + Status: model.StatusSuccess, + }) + assert.NoError(t, err) + assert.Len(t, pipelines, 1) + assert.Equal(t, pipeline2.ID, pipelines[0].ID) + assert.Equal(t, model.StatusSuccess, pipelines[0].Status) } func TestPipelineIncrement(t *testing.T) { diff --git a/server/store/datastore/redirection.go b/server/store/datastore/redirection.go index f9dd1a34a..637c36d65 100644 --- a/server/store/datastore/redirection.go +++ b/server/store/datastore/redirection.go @@ -18,7 +18,7 @@ import ( "xorm.io/builder" "xorm.io/xorm" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func (s storage) getRedirection(e *xorm.Session, fullName string) (*model.Redirection, error) { diff --git a/server/store/datastore/redirection_test.go b/server/store/datastore/redirection_test.go index 8e72cd048..ba59c6a24 100644 --- a/server/store/datastore/redirection_test.go +++ b/server/store/datastore/redirection_test.go @@ -19,7 +19,7 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func TestCreateRedirection(t *testing.T) { diff --git a/server/store/datastore/registry.go b/server/store/datastore/registry.go index 49687783e..e1ba9368a 100644 --- a/server/store/datastore/registry.go +++ b/server/store/datastore/registry.go @@ -17,7 +17,7 @@ package datastore import ( "xorm.io/builder" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) const orderRegistriesBy = "id" diff --git a/server/store/datastore/registry_test.go b/server/store/datastore/registry_test.go index e6f3f5f05..a1cbfa0d8 100644 --- a/server/store/datastore/registry_test.go +++ b/server/store/datastore/registry_test.go @@ -20,8 +20,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store/types" ) func TestRegistryFind(t *testing.T) { diff --git a/server/store/datastore/repo.go b/server/store/datastore/repo.go index f373b17cd..559591bd4 100644 --- a/server/store/datastore/repo.go +++ b/server/store/datastore/repo.go @@ -22,8 +22,8 @@ import ( "xorm.io/builder" "xorm.io/xorm" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store/types" ) func (s storage) GetRepo(id int64) (*model.Repo, error) { diff --git a/server/store/datastore/repo_test.go b/server/store/datastore/repo_test.go index 630c4200f..97ac3723f 100644 --- a/server/store/datastore/repo_test.go +++ b/server/store/datastore/repo_test.go @@ -18,146 +18,106 @@ package datastore import ( "testing" - "github.com/franela/goblin" "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) -func TestRepos(t *testing.T) { - store, closer := newTestStore(t, new(model.Repo), new(model.User), new(model.Pipeline)) +func TestCreateRepo(t *testing.T) { + store, closer := newTestStore(t, new(model.Repo)) defer closer() - g := goblin.Goblin(t) - g.Describe("Repo", func() { - // before each test be sure to purge the package - // table data from the database. - g.BeforeEach(func() { - _, err := store.engine.Exec("DELETE FROM pipelines") - g.Assert(err).IsNil() - _, err = store.engine.Exec("DELETE FROM repos") - g.Assert(err).IsNil() - _, err = store.engine.Exec("DELETE FROM users") - g.Assert(err).IsNil() - }) + repo := model.Repo{ + UserID: 1, + FullName: "bradrydzewski/test", + Owner: "bradrydzewski", + Name: "test", + } + err := store.CreateRepo(&repo) + assert.NoError(t, err) + assert.NotZero(t, repo.ID) - g.It("Should Set a Repo", func() { - repo := model.Repo{ - UserID: 1, - FullName: "bradrydzewski/test", - Owner: "bradrydzewski", - Name: "test", - } - err1 := store.CreateRepo(&repo) - err2 := store.UpdateRepo(&repo) - getRepo, err3 := store.GetRepo(repo.ID) + err2 := store.UpdateRepo(&repo) + getRepo, err3 := store.GetRepo(repo.ID) - g.Assert(err1).IsNil() - g.Assert(err2).IsNil() - g.Assert(err3).IsNil() - g.Assert(repo.ID).Equal(getRepo.ID) - }) + assert.NoError(t, err2) + assert.NoError(t, err3) + assert.Equal(t, repo.ID, getRepo.ID) - g.It("Should Add a Repo", func() { - repo := model.Repo{ - UserID: 1, - FullName: "bradrydzewski/test", - Owner: "bradrydzewski", - Name: "test", - } - err := store.CreateRepo(&repo) - g.Assert(err).IsNil() - g.Assert(repo.ID != 0).IsTrue() - }) + // test that repo has name/owner/fullname + assert.Error(t, store.CreateRepo(&model.Repo{ + UserID: 1, + FullName: "bradrydzewski/", + Owner: "bradrydzewski", + Name: "", + })) + assert.Error(t, store.CreateRepo(&model.Repo{ + UserID: 1, + FullName: "/test", + Owner: "", + Name: "test", + })) + assert.Error(t, store.CreateRepo(&model.Repo{ + UserID: 1, + FullName: "", + Owner: "bradrydzewski", + Name: "test", + })) - g.It("Should fail if repo has no name / owner / fullname", func() { - g.Assert(store.CreateRepo(&model.Repo{ - UserID: 1, - FullName: "bradrydzewski/", - Owner: "bradrydzewski", - Name: "", - })).IsNotNil() - g.Assert(store.CreateRepo(&model.Repo{ - UserID: 1, - FullName: "/test", - Owner: "", - Name: "test", - })).IsNotNil() - g.Assert(store.CreateRepo(&model.Repo{ - UserID: 1, - FullName: "", - Owner: "bradrydzewski", - Name: "test", - })).IsNotNil() - }) + // test unique name + repo2 := model.Repo{ + UserID: 2, + FullName: "bradrydzewski/test", + Owner: "bradrydzewski", + Name: "test", + } + err2 = store.CreateRepo(&repo2) + assert.Error(t, err2) +} - g.It("Should Get a Repo by ID", func() { - repo := model.Repo{ - UserID: 1, - FullName: "bradrydzewski/test", - Owner: "bradrydzewski", - Name: "test", - } - g.Assert(store.CreateRepo(&repo)).IsNil() - getrepo, err := store.GetRepo(repo.ID) - g.Assert(err).IsNil() - g.Assert(repo.ID).Equal(getrepo.ID) - g.Assert(repo.UserID).Equal(getrepo.UserID) - g.Assert(repo.Owner).Equal(getrepo.Owner) - g.Assert(repo.Name).Equal(getrepo.Name) - }) +func TestGetRepo(t *testing.T) { + store, closer := newTestStore(t, new(model.Repo)) + defer closer() + repo := model.Repo{ + UserID: 1, + FullName: "bradrydzewski/test", + Owner: "bradrydzewski", + Name: "test", + } + assert.NoError(t, store.CreateRepo(&repo)) + getrepo, err := store.GetRepo(repo.ID) + assert.NoError(t, err) + assert.Equal(t, repo.ID, getrepo.ID) + assert.Equal(t, repo.UserID, getrepo.UserID) + assert.Equal(t, repo.Owner, getrepo.Owner) + assert.Equal(t, repo.Name, getrepo.Name) +} - g.It("Should Get a Repo by Name", func() { - repo := model.Repo{ - UserID: 1, - FullName: "bradrydzewski/test", - Owner: "bradrydzewski", - Name: "test", - } - g.Assert(store.CreateRepo(&repo)).IsNil() - getrepo, err := store.GetRepoName(repo.FullName) - g.Assert(err).IsNil() - g.Assert(repo.ID).Equal(getrepo.ID) - g.Assert(repo.UserID).Equal(getrepo.UserID) - g.Assert(repo.Owner).Equal(getrepo.Owner) - g.Assert(repo.Name).Equal(getrepo.Name) - }) +func TestGetRepoName(t *testing.T) { + store, closer := newTestStore(t, new(model.Repo)) + defer closer() + repo := model.Repo{ + UserID: 1, + FullName: "bradrydzewski/TEST", + Owner: "bradrydzewski", + Name: "TEST", + } - g.It("Should Get a Repo by Name (case-insensitive)", func() { - repo := model.Repo{ - UserID: 1, - FullName: "bradrydzewski/TEST", - Owner: "bradrydzewski", - Name: "TEST", - } - g.Assert(store.CreateRepo(&repo)).IsNil() - getrepo, err := store.GetRepoName("Bradrydzewski/test") - g.Assert(err).IsNil() - g.Assert(repo.ID).Equal(getrepo.ID) - g.Assert(repo.UserID).Equal(getrepo.UserID) - g.Assert(repo.Owner).Equal(getrepo.Owner) - g.Assert(repo.Name).Equal(getrepo.Name) - }) + assert.NoError(t, store.CreateRepo(&repo)) + getrepo, err := store.GetRepoName(repo.FullName) + assert.NoError(t, err) + assert.Equal(t, repo.ID, getrepo.ID) + assert.Equal(t, repo.UserID, getrepo.UserID) + assert.Equal(t, repo.Owner, getrepo.Owner) + assert.Equal(t, repo.Name, getrepo.Name) - g.It("Should Enforce Unique Repo Name", func() { - repo1 := model.Repo{ - UserID: 1, - FullName: "bradrydzewski/test", - Owner: "bradrydzewski", - Name: "test", - } - repo2 := model.Repo{ - UserID: 2, - FullName: "bradrydzewski/test", - Owner: "bradrydzewski", - Name: "test", - } - err1 := store.CreateRepo(&repo1) - err2 := store.CreateRepo(&repo2) - g.Assert(err1).IsNil() - g.Assert(err2 == nil).IsFalse() - }) - }) + // case-insensitive + getrepo, err = store.GetRepoName("Bradrydzewski/test") + assert.NoError(t, err) + assert.Equal(t, repo.ID, getrepo.ID) + assert.Equal(t, repo.UserID, getrepo.UserID) + assert.Equal(t, repo.Owner, getrepo.Owner) + assert.Equal(t, repo.Name, getrepo.Name) } func TestRepoList(t *testing.T) { @@ -165,9 +125,9 @@ func TestRepoList(t *testing.T) { defer closer() user := &model.User{ - Login: "joe", - Email: "foo@bar.com", - Token: "e42080dddf012c718e476da161d21ad5", + Login: "joe", + Email: "foo@bar.com", + AccessToken: "e42080dddf012c718e476da161d21ad5", } assert.NoError(t, store.CreateUser(user)) @@ -212,9 +172,9 @@ func TestOwnedRepoList(t *testing.T) { defer closer() user := &model.User{ - Login: "joe", - Email: "foo@bar.com", - Token: "e42080dddf012c718e476da161d21ad5", + Login: "joe", + Email: "foo@bar.com", + AccessToken: "e42080dddf012c718e476da161d21ad5", } assert.NoError(t, store.CreateUser(user)) diff --git a/server/store/datastore/secret.go b/server/store/datastore/secret.go index 9a481ab1b..0b508c85b 100644 --- a/server/store/datastore/secret.go +++ b/server/store/datastore/secret.go @@ -17,7 +17,7 @@ package datastore import ( "xorm.io/builder" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) const orderSecretsBy = "name" diff --git a/server/store/datastore/secret_test.go b/server/store/datastore/secret_test.go index 88171ea9c..d96438660 100644 --- a/server/store/datastore/secret_test.go +++ b/server/store/datastore/secret_test.go @@ -20,7 +20,7 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func TestSecretFind(t *testing.T) { diff --git a/server/store/datastore/server_config.go b/server/store/datastore/server_config.go index 2dd52f406..c1985deb5 100644 --- a/server/store/datastore/server_config.go +++ b/server/store/datastore/server_config.go @@ -14,7 +14,7 @@ package datastore -import "go.woodpecker-ci.org/woodpecker/v2/server/model" +import "go.woodpecker-ci.org/woodpecker/v3/server/model" func (s storage) ServerConfigGet(key string) (string, error) { config := new(model.ServerConfig) @@ -31,7 +31,13 @@ func (s storage) ServerConfigSet(key, value string) error { Key: key, } - count, err := s.engine.Count(config) + sess := s.engine.NewSession() + defer sess.Close() + if err := sess.Begin(); err != nil { + return err + } + + count, err := sess.Count(config) if err != nil { return err } @@ -39,12 +45,15 @@ func (s storage) ServerConfigSet(key, value string) error { config.Value = value if count == 0 { - _, err := s.engine.Insert(config) + _, err = sess.Insert(config) + } else { + _, err = sess.Where("`key` = ?", config.Key).Cols("value").Update(config) + } + if err != nil { return err } - _, err = s.engine.Where("`key` = ?", config.Key).Cols("value").Update(config) - return err + return sess.Commit() } func (s storage) ServerConfigDelete(key string) error { diff --git a/server/store/datastore/server_config_test.go b/server/store/datastore/server_config_test.go index dbbeddde9..f37ef4c68 100644 --- a/server/store/datastore/server_config_test.go +++ b/server/store/datastore/server_config_test.go @@ -19,7 +19,7 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func TestServerConfigGetSet(t *testing.T) { diff --git a/server/store/datastore/step.go b/server/store/datastore/step.go index 3a4a43b80..6f81bea11 100644 --- a/server/store/datastore/step.go +++ b/server/store/datastore/step.go @@ -18,7 +18,7 @@ import ( "xorm.io/builder" "xorm.io/xorm" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func (s storage) StepLoad(id int64) (*model.Step, error) { @@ -84,7 +84,7 @@ func (s storage) StepUpdate(step *model.Step) error { } func deleteStep(sess *xorm.Session, stepID int64) error { - if _, err := sess.Where("id = ?", stepID).Delete(new(model.LogEntry)); err != nil { + if err := logDelete(sess, stepID); err != nil { return err } return wrapDelete(sess.ID(stepID).Delete(new(model.Step))) diff --git a/server/store/datastore/step_test.go b/server/store/datastore/step_test.go index 74840d2e2..95d7bba88 100644 --- a/server/store/datastore/step_test.go +++ b/server/store/datastore/step_test.go @@ -20,8 +20,8 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" - "go.woodpecker-ci.org/woodpecker/v2/server/store/types" + "go.woodpecker-ci.org/woodpecker/v3/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/store/types" ) func TestStepFind(t *testing.T) { diff --git a/server/store/datastore/task.go b/server/store/datastore/task.go index 44296dff1..40f95963b 100644 --- a/server/store/datastore/task.go +++ b/server/store/datastore/task.go @@ -15,7 +15,7 @@ package datastore import ( - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func (s storage) TaskList() ([]*model.Task, error) { diff --git a/server/store/datastore/task_test.go b/server/store/datastore/task_test.go index 61e16bb81..c32ae191b 100644 --- a/server/store/datastore/task_test.go +++ b/server/store/datastore/task_test.go @@ -19,7 +19,7 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func TestTaskList(t *testing.T) { diff --git a/server/store/datastore/user.go b/server/store/datastore/user.go index c1900062e..deeddf542 100644 --- a/server/store/datastore/user.go +++ b/server/store/datastore/user.go @@ -15,9 +15,11 @@ package datastore import ( + "fmt" + "xorm.io/xorm" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func (s storage) GetUser(id int64) (*model.User, error) { @@ -59,9 +61,24 @@ func (s storage) CreateUser(user *model.User) error { Name: user.Login, IsUser: true, } - err := s.orgCreate(org, sess) + + existingOrg, err := s.OrgFindByName(org.Name) if err != nil { - return err + return fmt.Errorf("failed to check if org exists: %w", err) + } + + if existingOrg != nil { + if existingOrg.Name == user.Login { + err = s.OrgUpdate(org) + if err != nil { + return fmt.Errorf("failed to update existing org: %w", err) + } + } + } else { + err = s.orgCreate(org, sess) + if err != nil { + return fmt.Errorf("failed to create new org: %w", err) + } } user.OrgID = org.ID // only Insert set auto created ID back to object diff --git a/server/store/datastore/users_test.go b/server/store/datastore/users_test.go index d8225bda4..410daa36f 100644 --- a/server/store/datastore/users_test.go +++ b/server/store/datastore/users_test.go @@ -18,238 +18,118 @@ package datastore import ( "testing" - "github.com/franela/goblin" + "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func TestUsers(t *testing.T) { - store, closer := newTestStore(t, new(model.User), new(model.Repo), new(model.Pipeline), new(model.Step), new(model.Perm), new(model.Org), new(model.Secret)) + store, closer := newTestStore(t, new(model.User), new(model.Org), new(model.Secret), new(model.Repo), new(model.Perm)) defer closer() - g := goblin.Goblin(t) - g.Describe("User", func() { - // before each test be sure to purge the package - // table data from the database. - g.BeforeEach(func() { - _, err := store.engine.Exec("DELETE FROM users") - g.Assert(err).IsNil() - _, err = store.engine.Exec("DELETE FROM repos") - g.Assert(err).IsNil() - _, err = store.engine.Exec("DELETE FROM pipelines") - g.Assert(err).IsNil() - _, err = store.engine.Exec("DELETE FROM steps") - g.Assert(err).IsNil() - _, err = store.engine.Exec("DELETE FROM orgs") - g.Assert(err).IsNil() - }) + count, err := store.GetUserCount() + assert.NoError(t, err) + assert.Zero(t, count) - g.It("Should Update a User", func() { - user := model.User{ - Login: "joe", - Email: "foo@bar.com", - Token: "e42080dddf012c718e476da161d21ad5", - } - err1 := store.CreateUser(&user) - err2 := store.UpdateUser(&user) - getUser, err3 := store.GetUser(user.ID) - g.Assert(err1).IsNil() - g.Assert(err2).IsNil() - g.Assert(err3).IsNil() - g.Assert(user.ID).Equal(getUser.ID) - }) + user := model.User{ + Login: "joe", + AccessToken: "f0b461ca586c27872b43a0685cbc2847", + RefreshToken: "976f22a5eef7caacb7e678d6c52f49b1", + Email: "foo@bar.com", + Avatar: "b9015b0857e16ac4d94a0ffd9a0b79c8", + } + err = store.CreateUser(&user) + assert.NoError(t, err) + assert.NotZero(t, user.ID) - g.It("Should Add a new User", func() { - user := model.User{ - Login: "joe", - Email: "foo@bar.com", - Token: "e42080dddf012c718e476da161d21ad5", - } - err := store.CreateUser(&user) - g.Assert(err).IsNil() - g.Assert(user.ID != 0).IsTrue() - }) + err2 := store.UpdateUser(&user) + assert.NoError(t, err2) - g.It("Should Get a User", func() { - user := &model.User{ - Login: "joe", - Token: "f0b461ca586c27872b43a0685cbc2847", - Secret: "976f22a5eef7caacb7e678d6c52f49b1", - Email: "foo@bar.com", - Avatar: "b9015b0857e16ac4d94a0ffd9a0b79c8", - } + getUser, err := store.GetUser(user.ID) + assert.NoError(t, err) + assert.Equal(t, user.ID, getUser.ID) + assert.Equal(t, user.Login, getUser.Login) + assert.Equal(t, user.AccessToken, getUser.AccessToken) + assert.Equal(t, user.RefreshToken, getUser.RefreshToken) + assert.Equal(t, user.Email, getUser.Email) + assert.Equal(t, user.Avatar, getUser.Avatar) - g.Assert(store.CreateUser(user)).IsNil() - getUser, err := store.GetUser(user.ID) - g.Assert(err).IsNil() - g.Assert(user.ID).Equal(getUser.ID) - g.Assert(user.Login).Equal(getUser.Login) - g.Assert(user.Token).Equal(getUser.Token) - g.Assert(user.Secret).Equal(getUser.Secret) - g.Assert(user.Email).Equal(getUser.Email) - g.Assert(user.Avatar).Equal(getUser.Avatar) - }) + getUser, err = store.GetUserLogin(user.Login) + assert.NoError(t, err) + assert.Equal(t, user.ID, getUser.ID) + assert.Equal(t, user.Login, getUser.Login) - g.It("Should Get a User By Login", func() { - user := &model.User{ - Login: "joe", - Email: "foo@bar.com", - Token: "e42080dddf012c718e476da161d21ad5", - } - g.Assert(store.CreateUser(user)) - getUser, err := store.GetUserLogin(user.Login) - g.Assert(err).IsNil() - g.Assert(user.ID).Equal(getUser.ID) - g.Assert(user.Login).Equal(getUser.Login) - }) + // check unique login + user2 := model.User{ + Login: "joe", + Email: "foo@bar.com", + AccessToken: "ab20g0ddaf012c744e136da16aa21ad9", + } + err2 = store.CreateUser(&user2) + assert.Error(t, err2) - g.It("Should Enforce Unique User Login", func() { - user1 := model.User{ - Login: "joe", - Email: "foo@bar.com", - Token: "e42080dddf012c718e476da161d21ad5", - } - user2 := model.User{ - Login: "joe", - Email: "foo@bar.com", - Token: "ab20g0ddaf012c744e136da16aa21ad9", - } - err1 := store.CreateUser(&user1) - err2 := store.CreateUser(&user2) - g.Assert(err1).IsNil() - g.Assert(err2 == nil).IsFalse() - }) + user2 = model.User{ + Login: "jane", + Email: "foo@bar.com", + AccessToken: "ab20g0ddaf012c744e136da16aa21ad9", + Hash: "A", + } + assert.NoError(t, store.CreateUser(&user2)) + users, err := store.GetUserList(&model.ListOptions{Page: 1, PerPage: 50}) + assert.NoError(t, err) + assert.Len(t, users, 2) + // "jane" user is first due to alphabetic sorting + assert.Equal(t, user2.Login, users[0].Login) + assert.Equal(t, user2.Email, users[0].Email) + assert.Equal(t, user2.AccessToken, users[0].AccessToken) - g.It("Should Get a User List", func() { - user1 := model.User{ - Login: "jane", - Email: "foo@bar.com", - Token: "ab20g0ddaf012c744e136da16aa21ad9", - Hash: "A", - } - user2 := model.User{ - Login: "joe", - Email: "foo@bar.com", - Token: "e42080dddf012c718e476da161d21ad5", - } - g.Assert(store.CreateUser(&user1)).IsNil() - g.Assert(store.CreateUser(&user2)).IsNil() - users, err := store.GetUserList(&model.ListOptions{Page: 1, PerPage: 50}) - g.Assert(err).IsNil() - g.Assert(len(users)).Equal(2) - g.Assert(users[0].Login).Equal(user1.Login) - g.Assert(users[0].Email).Equal(user1.Email) - g.Assert(users[0].Token).Equal(user1.Token) - }) + count, err = store.GetUserCount() + assert.NoError(t, err) + assert.EqualValues(t, 2, count) - g.It("Should Get a User Count", func() { - user1 := model.User{ - Login: "jane", - Email: "foo@bar.com", - Token: "ab20g0ddaf012c744e136da16aa21ad9", - Hash: "A", - } - user2 := model.User{ - Login: "joe", - Email: "foo@bar.com", - Token: "e42080dddf012c718e476da161d21ad5", - Hash: "B", - } - g.Assert(store.CreateUser(&user1)).IsNil() - g.Assert(store.CreateUser(&user2)).IsNil() - count, err := store.GetUserCount() - g.Assert(err).IsNil() - g.Assert(count).Equal(int64(2)) - }) - - g.It("Should Get a User Count Zero", func() { - count, err := store.GetUserCount() - g.Assert(err).IsNil() - g.Assert(count).Equal(int64(0)) - }) - - g.It("Should Del a User", func() { - user := &model.User{ - Login: "joe", - Email: "foo@bar.com", - Token: "e42080dddf012c718e476da161d21ad5", - } - g.Assert(store.CreateUser(user)).IsNil() - user, err1 := store.GetUser(user.ID) - g.Assert(err1).IsNil() - err2 := store.DeleteUser(user) - g.Assert(err2).IsNil() - _, err3 := store.GetUser(user.ID) - g.Assert(err3).IsNotNil() - }) - - g.It("Should get the Pipeline feed for a User", func() { - user := &model.User{ - Login: "joe", - Email: "foo@bar.com", - Token: "e42080dddf012c718e476da161d21ad5", - } - g.Assert(store.CreateUser(user)).IsNil() - - repo1 := &model.Repo{ - Owner: "bradrydzewski", - Name: "test", - FullName: "bradrydzewski/test", - IsActive: true, - ForgeRemoteID: "1", - } - repo2 := &model.Repo{ - Owner: "test", - Name: "test", - FullName: "test/test", - IsActive: true, - ForgeRemoteID: "2", - } - repo3 := &model.Repo{ - Owner: "octocat", - Name: "hello-world", - FullName: "octocat/hello-world", - IsActive: true, - ForgeRemoteID: "3", - } - g.Assert(store.CreateRepo(repo1)).IsNil() - g.Assert(store.CreateRepo(repo2)).IsNil() - g.Assert(store.CreateRepo(repo3)).IsNil() - - for _, perm := range []*model.Perm{ - {UserID: user.ID, Repo: repo1, Push: true, Admin: false}, - {UserID: user.ID, Repo: repo2, Push: false, Admin: true}, - } { - g.Assert(store.PermUpsert(perm)).IsNil() - } - - pipeline1 := &model.Pipeline{ - RepoID: repo1.ID, - Status: model.StatusFailure, - } - pipeline2 := &model.Pipeline{ - RepoID: repo1.ID, - Status: model.StatusSuccess, - } - pipeline3 := &model.Pipeline{ - RepoID: repo2.ID, - Status: model.StatusSuccess, - } - pipeline4 := &model.Pipeline{ - RepoID: repo3.ID, - Status: model.StatusSuccess, - } - g.Assert(store.CreatePipeline(pipeline1)).IsNil() - g.Assert(store.CreatePipeline(pipeline2)).IsNil() - g.Assert(store.CreatePipeline(pipeline3)).IsNil() - g.Assert(store.CreatePipeline(pipeline4)).IsNil() - - pipelines, err := store.UserFeed(user) - g.Assert(err).IsNil() - g.Assert(len(pipelines)).Equal(3) - g.Assert(pipelines[0].RepoID).Equal(repo2.ID) - g.Assert(pipelines[1].RepoID).Equal(repo1.ID) - g.Assert(pipelines[2].RepoID).Equal(repo1.ID) - }) - }) + getUser, err1 := store.GetUser(user.ID) + assert.NoError(t, err1) + err2 = store.DeleteUser(getUser) + assert.NoError(t, err2) + _, err3 := store.GetUser(getUser.ID) + assert.Error(t, err3) +} + +func TestCreateUserWithExistingOrg(t *testing.T) { + store, closer := newTestStore(t, new(model.User), new(model.Org), new(model.Perm)) + defer closer() + + existingOrg := &model.Org{ + ForgeID: 1, + IsUser: true, + Name: "existingorg", + Private: false, + } + + err := store.OrgCreate(existingOrg) + assert.NoError(t, err) + assert.EqualValues(t, "existingorg", existingOrg.Name) + + // Create a new user with the same name as the existing organization + newUser := &model.User{ + Login: "existingOrg", + Hash: "A", + } + err = store.CreateUser(newUser) + assert.NoError(t, err) + + updatedOrg, err := store.OrgGet(existingOrg.ID) + assert.NoError(t, err) + assert.Equal(t, "existingorg", updatedOrg.Name) + + newUser2 := &model.User{ + Login: "new-user", + Hash: "B", + } + err = store.CreateUser(newUser2) + assert.NoError(t, err) + + newOrg, err := store.OrgFindByName("new-user") + assert.NoError(t, err) + assert.Equal(t, "new-user", newOrg.Name) } diff --git a/server/store/datastore/workflow.go b/server/store/datastore/workflow.go index 8e76df90c..714c7a674 100644 --- a/server/store/datastore/workflow.go +++ b/server/store/datastore/workflow.go @@ -17,7 +17,7 @@ package datastore import ( "xorm.io/xorm" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func (s storage) WorkflowGetTree(pipeline *model.Pipeline) ([]*model.Workflow, error) { diff --git a/server/store/datastore/workflow_test.go b/server/store/datastore/workflow_test.go index b0a6e2b68..5a31646a2 100644 --- a/server/store/datastore/workflow_test.go +++ b/server/store/datastore/workflow_test.go @@ -20,7 +20,7 @@ import ( "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) func TestWorkflowLoad(t *testing.T) { diff --git a/server/store/mocks/store.go b/server/store/mocks/store.go index e0c2663ff..a35d8a655 100644 --- a/server/store/mocks/store.go +++ b/server/store/mocks/store.go @@ -6,8 +6,10 @@ package mocks import ( + context "context" + mock "github.com/stretchr/testify/mock" - model "go.woodpecker-ci.org/woodpecker/v2/server/model" + model "go.woodpecker-ci.org/woodpecker/v3/server/model" ) // Store is an autogenerated mock type for the Store type @@ -141,6 +143,36 @@ func (_m *Store) AgentList(p *model.ListOptions) ([]*model.Agent, error) { return r0, r1 } +// AgentListForOrg provides a mock function with given fields: orgID, opt +func (_m *Store) AgentListForOrg(orgID int64, opt *model.ListOptions) ([]*model.Agent, error) { + ret := _m.Called(orgID, opt) + + if len(ret) == 0 { + panic("no return value specified for AgentListForOrg") + } + + var r0 []*model.Agent + var r1 error + if rf, ok := ret.Get(0).(func(int64, *model.ListOptions) ([]*model.Agent, error)); ok { + return rf(orgID, opt) + } + if rf, ok := ret.Get(0).(func(int64, *model.ListOptions) []*model.Agent); ok { + r0 = rf(orgID, opt) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*model.Agent) + } + } + + if rf, ok := ret.Get(1).(func(int64, *model.ListOptions) error); ok { + r1 = rf(orgID, opt) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // AgentUpdate provides a mock function with given fields: _a0 func (_m *Store) AgentUpdate(_a0 *model.Agent) error { ret := _m.Called(_a0) @@ -159,7 +191,7 @@ func (_m *Store) AgentUpdate(_a0 *model.Agent) error { return r0 } -// Close provides a mock function with given fields: +// Close provides a mock function with no fields func (_m *Store) Close() error { ret := _m.Called() @@ -716,7 +748,37 @@ func (_m *Store) GetPipeline(_a0 int64) (*model.Pipeline, error) { return r0, r1 } -// GetPipelineCount provides a mock function with given fields: +// GetPipelineBadge provides a mock function with given fields: _a0, _a1 +func (_m *Store) GetPipelineBadge(_a0 *model.Repo, _a1 string) (*model.Pipeline, error) { + ret := _m.Called(_a0, _a1) + + if len(ret) == 0 { + panic("no return value specified for GetPipelineBadge") + } + + var r0 *model.Pipeline + var r1 error + if rf, ok := ret.Get(0).(func(*model.Repo, string) (*model.Pipeline, error)); ok { + return rf(_a0, _a1) + } + if rf, ok := ret.Get(0).(func(*model.Repo, string) *model.Pipeline); ok { + r0 = rf(_a0, _a1) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.Pipeline) + } + } + + if rf, ok := ret.Get(1).(func(*model.Repo, string) error); ok { + r1 = rf(_a0, _a1) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetPipelineCount provides a mock function with no fields func (_m *Store) GetPipelineCount() (int64, error) { ret := _m.Called() @@ -864,7 +926,7 @@ func (_m *Store) GetPipelineNumber(_a0 *model.Repo, _a1 int64) (*model.Pipeline, return r0, r1 } -// GetPipelineQueue provides a mock function with given fields: +// GetPipelineQueue provides a mock function with no fields func (_m *Store) GetPipelineQueue() ([]*model.Feed, error) { ret := _m.Called() @@ -924,7 +986,7 @@ func (_m *Store) GetRepo(_a0 int64) (*model.Repo, error) { return r0, r1 } -// GetRepoCount provides a mock function with given fields: +// GetRepoCount provides a mock function with no fields func (_m *Store) GetRepoCount() (int64, error) { ret := _m.Called() @@ -1072,7 +1134,7 @@ func (_m *Store) GetUser(_a0 int64) (*model.User, error) { return r0, r1 } -// GetUserCount provides a mock function with given fields: +// GetUserCount provides a mock function with no fields func (_m *Store) GetUserCount() (int64, error) { ret := _m.Called() @@ -1338,17 +1400,17 @@ func (_m *Store) HasRedirectionForRepo(_a0 int64, _a1 string) (bool, error) { return r0, r1 } -// LogAppend provides a mock function with given fields: logEntry -func (_m *Store) LogAppend(logEntry *model.LogEntry) error { - ret := _m.Called(logEntry) +// LogAppend provides a mock function with given fields: _a0, _a1 +func (_m *Store) LogAppend(_a0 *model.Step, _a1 []*model.LogEntry) error { + ret := _m.Called(_a0, _a1) if len(ret) == 0 { panic("no return value specified for LogAppend") } var r0 error - if rf, ok := ret.Get(0).(func(*model.LogEntry) error); ok { - r0 = rf(logEntry) + if rf, ok := ret.Get(0).(func(*model.Step, []*model.LogEntry) error); ok { + r0 = rf(_a0, _a1) } else { r0 = ret.Error(0) } @@ -1404,17 +1466,17 @@ func (_m *Store) LogFind(_a0 *model.Step) ([]*model.LogEntry, error) { return r0, r1 } -// Migrate provides a mock function with given fields: _a0 -func (_m *Store) Migrate(_a0 bool) error { - ret := _m.Called(_a0) +// Migrate provides a mock function with given fields: _a0, _a1 +func (_m *Store) Migrate(_a0 context.Context, _a1 bool) error { + ret := _m.Called(_a0, _a1) if len(ret) == 0 { panic("no return value specified for Migrate") } var r0 error - if rf, ok := ret.Get(0).(func(bool) error); ok { - r0 = rf(_a0) + if rf, ok := ret.Get(0).(func(context.Context, bool) error); ok { + r0 = rf(_a0, _a1) } else { r0 = ret.Error(0) } @@ -1764,7 +1826,7 @@ func (_m *Store) PermUpsert(perm *model.Perm) error { return r0 } -// Ping provides a mock function with given fields: +// Ping provides a mock function with no fields func (_m *Store) Ping() error { ret := _m.Called() @@ -1896,7 +1958,7 @@ func (_m *Store) RegistryList(_a0 *model.Repo, _a1 bool, _a2 *model.ListOptions) return r0, r1 } -// RegistryListAll provides a mock function with given fields: +// RegistryListAll provides a mock function with no fields func (_m *Store) RegistryListAll() ([]*model.Registry, error) { ret := _m.Called() @@ -2130,7 +2192,7 @@ func (_m *Store) SecretList(_a0 *model.Repo, _a1 bool, _a2 *model.ListOptions) ( return r0, r1 } -// SecretListAll provides a mock function with given fields: +// SecretListAll provides a mock function with no fields func (_m *Store) SecretListAll() ([]*model.Secret, error) { ret := _m.Called() @@ -2476,7 +2538,7 @@ func (_m *Store) TaskInsert(_a0 *model.Task) error { return r0 } -// TaskList provides a mock function with given fields: +// TaskList provides a mock function with no fields func (_m *Store) TaskList() ([]*model.Task, error) { ret := _m.Called() diff --git a/server/store/store.go b/server/store/store.go index 8ff3b1052..18a6f766a 100644 --- a/server/store/store.go +++ b/server/store/store.go @@ -17,7 +17,9 @@ package store //go:generate mockery --name Store --output mocks --case underscore --note "+build test" import ( - "go.woodpecker-ci.org/woodpecker/v2/server/model" + "context" + + "go.woodpecker-ci.org/woodpecker/v3/server/model" ) // TODO: CreateX func should return new object to not indirect let storage change an existing object (alter ID etc...) @@ -70,6 +72,8 @@ type Store interface { GetPipeline(int64) (*model.Pipeline, error) // GetPipelineNumber gets a pipeline by number. GetPipelineNumber(*model.Repo, int64) (*model.Pipeline, error) + // GetPipelineBadge gets the last relevant pipeline for the badge. + GetPipelineBadge(*model.Repo, string) (*model.Pipeline, error) // GetPipelineLast gets the last pipeline for the branch. GetPipelineLast(*model.Repo, string) (*model.Pipeline, error) // GetPipelineLastBefore gets the last pipeline before pipeline number N. @@ -141,7 +145,7 @@ type Store interface { // Logs LogFind(*model.Step) ([]*model.LogEntry, error) - LogAppend(logEntry *model.LogEntry) error + LogAppend(*model.Step, []*model.LogEntry) error LogDelete(*model.Step) error // Tasks @@ -178,6 +182,7 @@ type Store interface { AgentList(p *model.ListOptions) ([]*model.Agent, error) AgentUpdate(*model.Agent) error AgentDelete(*model.Agent) error + AgentListForOrg(orgID int64, opt *model.ListOptions) ([]*model.Agent, error) // Workflow WorkflowGetTree(*model.Pipeline) ([]*model.Workflow, error) @@ -200,5 +205,5 @@ type Store interface { // Store operations Ping() error Close() error - Migrate(bool) error + Migrate(context.Context, bool) error } diff --git a/server/web/config.go b/server/web/config.go index 44c6f1fe5..48fed96ea 100644 --- a/server/web/config.go +++ b/server/web/config.go @@ -23,10 +23,10 @@ import ( "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/server/router/middleware/session" - "go.woodpecker-ci.org/woodpecker/v2/shared/token" - "go.woodpecker-ci.org/woodpecker/v2/version" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/server/router/middleware/session" + "go.woodpecker-ci.org/woodpecker/v3/shared/token" + "go.woodpecker-ci.org/woodpecker/v3/version" ) func Config(c *gin.Context) { @@ -40,12 +40,13 @@ func Config(c *gin.Context) { } configData := map[string]any{ - "user": user, - "csrf": csrf, - "version": version.String(), - "skip_version_check": server.Config.WebUI.SkipVersionCheck, - "root_path": server.Config.Server.RootPath, - "enable_swagger": server.Config.WebUI.EnableSwagger, + "user": user, + "csrf": csrf, + "version": version.String(), + "skip_version_check": server.Config.WebUI.SkipVersionCheck, + "root_path": server.Config.Server.RootPath, + "enable_swagger": server.Config.WebUI.EnableSwagger, + "user_registered_agents": !server.Config.Agent.DisableUserRegisteredAgentRegistration, } // default func map with json parser. @@ -79,4 +80,5 @@ window.WOODPECKER_VERSION = "{{ .version }}"; window.WOODPECKER_ROOT_PATH = "{{ .root_path }}"; window.WOODPECKER_ENABLE_SWAGGER = {{ .enable_swagger }}; window.WOODPECKER_SKIP_VERSION_CHECK = {{ .skip_version_check }} +window.WOODPECKER_USER_REGISTERED_AGENTS = {{ .user_registered_agents }} ` diff --git a/server/web/web.go b/server/web/web.go index f827ab60e..68a7be487 100644 --- a/server/web/web.go +++ b/server/web/web.go @@ -27,8 +27,8 @@ import ( "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" - "go.woodpecker-ci.org/woodpecker/v2/server" - "go.woodpecker-ci.org/woodpecker/v2/web" + "go.woodpecker-ci.org/woodpecker/v3/server" + "go.woodpecker-ci.org/woodpecker/v3/web" ) var indexHTML []byte diff --git a/server/web/web_test.go b/server/web/web_test.go index 52e7b255e..c23c704d1 100644 --- a/server/web/web_test.go +++ b/server/web/web_test.go @@ -23,7 +23,7 @@ import ( "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/server" + "go.woodpecker-ci.org/woodpecker/v3/server" ) func Test_custom_file_returns_OK_and_empty_content_and_fitting_mimetype(t *testing.T) { diff --git a/shared/constant/constant.go b/shared/constant/constant.go index 0034b49fe..e6cfa63ed 100644 --- a/shared/constant/constant.go +++ b/shared/constant/constant.go @@ -14,14 +14,7 @@ package constant -// PrivilegedPlugins can be changed by 'WOODPECKER_ESCALATE' at runtime. -var PrivilegedPlugins = []string{ - "plugins/docker", - "plugins/gcr", - "plugins/ecr", - "woodpeckerci/plugin-docker-buildx", - "codeberg.org/woodpecker-plugins/docker-buildx", -} +import "time" // DefaultConfigOrder represent the priority in witch woodpecker search for a pipeline config by default // folders are indicated by supplying a trailing slash. @@ -32,12 +25,17 @@ var DefaultConfigOrder = [...]string{ } const ( - // DefaultCloneImage can be changed by 'WOODPECKER_DEFAULT_CLONE_IMAGE' at runtime. + // DefaultClonePlugin can be changed by 'WOODPECKER_DEFAULT_CLONE_PLUGIN' at runtime. // renovate: datasource=docker depName=woodpeckerci/plugin-git - DefaultCloneImage = "docker.io/woodpeckerci/plugin-git:2.5.1" + DefaultClonePlugin = "docker.io/woodpeckerci/plugin-git:2.6.0" ) -var TrustedCloneImages = []string{ - DefaultCloneImage, +// TrustedClonePlugins can be changed by 'WOODPECKER_PLUGINS_TRUSTED_CLONE' at runtime. +var TrustedClonePlugins = []string{ + DefaultClonePlugin, + "docker.io/woodpeckerci/plugin-git", "quay.io/woodpeckerci/plugin-git", } + +// TaskTimeout is the time till a running task is counted as dead. +var TaskTimeout = time.Minute diff --git a/shared/logger/logger.go b/shared/logger/logger.go index 916ec1b01..266777265 100644 --- a/shared/logger/logger.go +++ b/shared/logger/logger.go @@ -15,6 +15,7 @@ package logger import ( + "context" "fmt" "io" "os" @@ -22,37 +23,37 @@ import ( "github.com/6543/logfile-open" "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "github.com/urfave/cli/v2" + "github.com/urfave/cli/v3" ) var GlobalLoggerFlags = []cli.Flag{ &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_LOG_LEVEL"}, + Sources: cli.EnvVars("WOODPECKER_LOG_LEVEL"), Name: "log-level", Usage: "set logging level", Value: "info", }, &cli.StringFlag{ - EnvVars: []string{"WOODPECKER_LOG_FILE"}, + Sources: cli.EnvVars("WOODPECKER_LOG_FILE"), Name: "log-file", Usage: "Output destination for logs. 'stdout' and 'stderr' can be used as special keywords.", Value: "stderr", }, &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_DEBUG_PRETTY"}, + Sources: cli.EnvVars("WOODPECKER_DEBUG_PRETTY"), Name: "pretty", Usage: "enable pretty-printed debug output", Value: isInteractiveTerminal(), // make pretty on interactive terminal by default }, &cli.BoolFlag{ - EnvVars: []string{"WOODPECKER_DEBUG_NOCOLOR"}, + Sources: cli.EnvVars("WOODPECKER_DEBUG_NOCOLOR"), Name: "nocolor", Usage: "disable colored debug output, only has effect if pretty output is set too", Value: !isInteractiveTerminal(), // do color on interactive terminal by default }, } -func SetupGlobalLogger(c *cli.Context, outputLvl bool) error { +func SetupGlobalLogger(ctx context.Context, c *cli.Command, outputLvl bool) error { logLevel := c.String("log-level") pretty := c.Bool("pretty") noColor := c.Bool("nocolor") @@ -65,7 +66,7 @@ func SetupGlobalLogger(c *cli.Context, outputLvl bool) error { case "stdout": file = os.Stdout default: // a file was set - openFile, err := logfile.OpenFileWithContext(c.Context, logFile, 0o660) + openFile, err := logfile.OpenFileWithContext(ctx, logFile, 0o660) if err != nil { return fmt.Errorf("could not open log file '%s': %w", logFile, err) } diff --git a/shared/token/token_test.go b/shared/token/token_test.go index fb77ef609..0c1f3b973 100644 --- a/shared/token/token_test.go +++ b/shared/token/token_test.go @@ -3,60 +3,51 @@ package token_test import ( "testing" - "github.com/franela/goblin" - "github.com/gin-gonic/gin" "github.com/golang-jwt/jwt/v5" "github.com/stretchr/testify/assert" - "go.woodpecker-ci.org/woodpecker/v2/shared/token" + "go.woodpecker-ci.org/woodpecker/v3/shared/token" ) -func TestToken(t *testing.T) { - gin.SetMode(gin.TestMode) +const jwtSecret = "secret-to-sign-the-token" - g := goblin.Goblin(t) - g.Describe("Token", func() { - jwtSecret := "secret-to-sign-the-token" +func TestTokenValid(t *testing.T) { + _token := token.New(token.UserToken) + _token.Set("user-id", "1") + signedToken, err := _token.Sign(jwtSecret) + assert.NoError(t, err) - g.It("should parse a valid token", func() { - _token := token.New(token.UserToken) - _token.Set("user-id", "1") - signedToken, err := _token.Sign(jwtSecret) - assert.NoError(g, err) - - parsed, err := token.Parse([]token.Type{token.UserToken}, signedToken, func(_ *token.Token) (string, error) { - return jwtSecret, nil - }) - - assert.NoError(g, err) - assert.NotNil(g, parsed) - assert.Equal(g, "1", parsed.Get("user-id")) - }) - - g.It("should fail to parse a token with a wrong type", func() { - _token := token.New(token.UserToken) - _token.Set("user-id", "1") - signedToken, err := _token.Sign(jwtSecret) - assert.NoError(g, err) - - _, err = token.Parse([]token.Type{token.AgentToken}, signedToken, func(_ *token.Token) (string, error) { - return jwtSecret, nil - }) - - assert.ErrorIs(g, err, jwt.ErrInvalidType) - }) - - g.It("should fail to parse a token with a wrong secret", func() { - _token := token.New(token.UserToken) - _token.Set("user-id", "1") - signedToken, err := _token.Sign(jwtSecret) - assert.NoError(g, err) - - _, err = token.Parse([]token.Type{token.UserToken}, signedToken, func(_ *token.Token) (string, error) { - return "this-is-a-wrong-secret", nil - }) - - assert.ErrorIs(g, err, jwt.ErrSignatureInvalid) - }) + parsed, err := token.Parse([]token.Type{token.UserToken}, signedToken, func(_ *token.Token) (string, error) { + return jwtSecret, nil }) + + assert.NoError(t, err) + assert.NotNil(t, parsed) + assert.Equal(t, "1", parsed.Get("user-id")) +} + +func TestTokenWrongType(t *testing.T) { + _token := token.New(token.UserToken) + _token.Set("user-id", "1") + signedToken, err := _token.Sign(jwtSecret) + assert.NoError(t, err) + + _, err = token.Parse([]token.Type{token.AgentToken}, signedToken, func(_ *token.Token) (string, error) { + return jwtSecret, nil + }) + + assert.ErrorIs(t, err, jwt.ErrInvalidType) +} + +func TestTokenWrongSecret(t *testing.T) { + _token := token.New(token.UserToken) + _token.Set("user-id", "1") + signedToken, err := _token.Sign(jwtSecret) + assert.NoError(t, err) + + _, err = token.Parse([]token.Type{token.UserToken}, signedToken, func(_ *token.Token) (string, error) { + return "this-is-a-wrong-secret", nil + }) + + assert.ErrorIs(t, err, jwt.ErrSignatureInvalid) } diff --git a/shared/utils/paginate.go b/shared/utils/paginate.go index b4c280445..01305c5f5 100644 --- a/shared/utils/paginate.go +++ b/shared/utils/paginate.go @@ -15,19 +15,37 @@ package utils // Paginate iterates over a func call until it does not return new items and return it as list. -func Paginate[T any](get func(page int) ([]T, error)) ([]T, error) { +func Paginate[T any](get func(page int) ([]T, error), limit int) ([]T, error) { items := make([]T, 0, 10) page := 1 lenFirstBatch := -1 for { + // limit < 1 means get all results + remaining := 0 + if limit > 0 { + remaining = limit - len(items) + if remaining <= 0 { + break + } + } + batch, err := get(page) if err != nil { return nil, err } + + // Take only what we need from this batch if limit > 0 + if limit > 0 && len(batch) > remaining { + batch = batch[:remaining] + } + items = append(items, batch...) if page == 1 { + if len(batch) == 0 { + return items, nil + } lenFirstBatch = len(batch) } else if len(batch) < lenFirstBatch || len(batch) == 0 { break diff --git a/shared/utils/paginate_test.go b/shared/utils/paginate_test.go index 870aba079..ab495deeb 100644 --- a/shared/utils/paginate_test.go +++ b/shared/utils/paginate_test.go @@ -21,27 +21,72 @@ import ( ) func TestPaginate(t *testing.T) { - apiExec := 0 - apiMock := func(page int) []int { - apiExec++ - switch page { - case 0, 1: - return []int{11, 12, 13} - case 2: - return []int{21, 22, 23} - case 3: - return []int{31, 32} - default: - return []int{} + // Generic mock generator that can handle all cases + createMock := func(pages [][]int) func(page int) []int { + return func(page int) []int { + if page <= 0 { + page = 0 + } else { + page-- + } + + if page >= len(pages) { + return []int{} + } + + return pages[page] } } - result, _ := Paginate(func(page int) ([]int, error) { - return apiMock(page), nil - }) + tests := []struct { + name string + limit int + pages [][]int + expected []int + apiCalls int + }{ + { + name: "multiple pages", + limit: -1, + pages: [][]int{{11, 12, 13}, {21, 22, 23}, {31, 32}}, + expected: []int{11, 12, 13, 21, 22, 23, 31, 32}, + apiCalls: 3, + }, + { + name: "zero limit", + limit: 0, + pages: [][]int{{1, 2, 3}, {1, 2, 3}, {1, 2, 3}}, + expected: []int{1, 2, 3, 1, 2, 3, 1, 2, 3}, + apiCalls: 4, + }, + { + name: "empty result", + limit: 5, + pages: [][]int{{}}, + expected: []int{}, + apiCalls: 1, + }, + { + name: "limit less than batch", + limit: 2, + pages: [][]int{{1, 2, 3, 4, 5}}, + expected: []int{1, 2}, + apiCalls: 1, + }, + } - assert.EqualValues(t, 3, apiExec) - if assert.Len(t, result, 8) { - assert.EqualValues(t, []int{11, 12, 13, 21, 22, 23, 31, 32}, result) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + apiExec := 0 + mock := createMock(tt.pages) + + result, _ := Paginate(func(page int) ([]int, error) { + apiExec++ + return mock(page), nil + }, tt.limit) + + assert.EqualValues(t, tt.apiCalls, apiExec) + assert.EqualValues(t, tt.expected, result) + }) } } diff --git a/server/store/datastore/migration/017_remove_files_table.go b/tools/tools.go similarity index 69% rename from server/store/datastore/migration/017_remove_files_table.go rename to tools/tools.go index 8cc99b806..80d4e2cd8 100644 --- a/server/store/datastore/migration/017_remove_files_table.go +++ b/tools/tools.go @@ -1,4 +1,4 @@ -// Copyright 2022 Woodpecker Authors +// Copyright 2024 Woodpecker Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,16 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -package migration +//go:build tools +// +build tools + +package main import ( - "src.techknowlogick.com/xormigrate" - "xorm.io/xorm" + _ "github.com/getkin/kin-openapi/cmd/validate" + _ "github.com/swaggo/swag/cmd/swag" ) - -var dropFiles = xormigrate.Migration{ - ID: "drop-files", - MigrateSession: func(sess *xorm.Session) error { - return sess.DropTable("files") - }, -} diff --git a/web/.gitignore b/web/.gitignore index 4998cb543..d451ff16c 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -3,4 +3,3 @@ node_modules dist dist-ssr *.local -src/assets/dayjsLocales diff --git a/web/.prettierignore b/web/.prettierignore index 203898907..2087370c5 100644 --- a/web/.prettierignore +++ b/web/.prettierignore @@ -4,5 +4,4 @@ coverage/ LICENSE components.d.ts src/assets/locales/*.json -src/assets/dayjsLocales/ !src/assets/locales/en.json diff --git a/web/.prettierrc.js b/web/.prettierrc.js index d44e794b4..51832a96e 100644 --- a/web/.prettierrc.js +++ b/web/.prettierrc.js @@ -1,10 +1,11 @@ import { readFile } from 'node:fs/promises'; +// eslint-disable-next-line antfu/no-top-level-await const config = JSON.parse(await readFile(new URL('../.prettierrc.json', import.meta.url))); export default { ...config, - plugins: ['@ianvs/prettier-plugin-sort-imports'], + plugins: ['@ianvs/prettier-plugin-sort-imports', 'prettier-plugin-tailwindcss'], importOrder: [ '', // Imports not matched by other special words or groups. '', // Empty string will match any import not matched by other special words or groups. diff --git a/web/.yamlignore b/web/.yamlignore new file mode 100644 index 000000000..1e7cedab9 --- /dev/null +++ b/web/.yamlignore @@ -0,0 +1 @@ +.pnpm-lock.yaml diff --git a/web/eslint.config.js b/web/eslint.config.js index d218b3d99..ac3bf0827 100644 --- a/web/eslint.config.js +++ b/web/eslint.config.js @@ -4,6 +4,7 @@ import antfu from '@antfu/eslint-config'; import js from '@eslint/js'; import vueI18n from '@intlify/eslint-plugin-vue-i18n'; +import eslintPromise from 'eslint-plugin-promise'; import eslintPluginVueScopedCSS from 'eslint-plugin-vue-scoped-css'; export default antfu( @@ -20,23 +21,54 @@ export default antfu( }, js.configs.recommended, - // eslintPromise.configs.recommended, - - // TypeScript - //...tseslint.configs.recommended, - //...tseslint.configs.recommendedTypeChecked, - //...tseslint.configs.strictTypeChecked, - //...tseslint.configs.stylisticTypeChecked, + eslintPromise.configs['flat/recommended'], + ...eslintPluginVueScopedCSS.configs['flat/recommended'], + ...vueI18n.configs['flat/recommended'], { rules: { 'import/order': 'off', 'sort-imports': 'off', + 'perfectionist/sort-imports': 'off', + 'perfectionist/sort-named-imports': 'off', + 'promise/prefer-await-to-callbacks': 'error', + + // Vue I18n + '@intlify/vue-i18n/no-raw-text': [ + 'error', + { + attributes: { + '/.+/': ['label'], + }, + }, + ], + '@intlify/vue-i18n/key-format-style': ['error', 'snake_case'], + '@intlify/vue-i18n/no-duplicate-keys-in-locale': 'error', + '@intlify/vue-i18n/no-dynamic-keys': 'error', + '@intlify/vue-i18n/no-deprecated-i18n-component': 'error', + '@intlify/vue-i18n/no-deprecated-tc': 'error', + '@intlify/vue-i18n/no-i18n-t-path-prop': 'error', + '@intlify/vue-i18n/no-missing-keys-in-other-locales': 'off', + '@intlify/vue-i18n/valid-message-syntax': 'error', + '@intlify/vue-i18n/no-missing-keys': 'error', + '@intlify/vue-i18n/no-unknown-locale': 'error', + '@intlify/vue-i18n/no-unused-keys': ['error', { extensions: ['.ts', '.vue'] }], + '@intlify/vue-i18n/prefer-sfc-lang-attr': 'error', + '@intlify/vue-i18n/no-html-messages': 'error', + '@intlify/vue-i18n/prefer-linked-key-with-paren': 'error', + '@intlify/vue-i18n/sfc-locale-attr': 'error', + }, + settings: { + // Vue I18n + 'vue-i18n': { + localeDir: './src/assets/locales/en.json', + // Specify the version of `vue-i18n` you are using. + // If not specified, the message will be parsed twice. + messageSyntaxVersion: '^9.0.0', + }, }, }, - ...eslintPluginVueScopedCSS.configs['flat/recommended'], - // Vue { files: ['**/*.vue'], @@ -64,44 +96,6 @@ export default antfu( }, }, - // Vue I18n - ...vueI18n.configs['flat/recommended'], - { - rules: { - '@intlify/vue-i18n/no-raw-text': [ - 'error', - { - attributes: { - '/.+/': ['label'], - }, - }, - ], - '@intlify/vue-i18n/key-format-style': ['error', 'snake_case'], - '@intlify/vue-i18n/no-duplicate-keys-in-locale': 'error', - '@intlify/vue-i18n/no-dynamic-keys': 'error', - '@intlify/vue-i18n/no-deprecated-i18n-component': 'error', - '@intlify/vue-i18n/no-deprecated-tc': 'error', - '@intlify/vue-i18n/no-i18n-t-path-prop': 'error', - '@intlify/vue-i18n/no-missing-keys-in-other-locales': 'off', - '@intlify/vue-i18n/valid-message-syntax': 'error', - '@intlify/vue-i18n/no-missing-keys': 'error', - '@intlify/vue-i18n/no-unknown-locale': 'error', - '@intlify/vue-i18n/no-unused-keys': ['error', { extensions: ['.ts', '.vue'] }], - '@intlify/vue-i18n/prefer-sfc-lang-attr': 'error', - '@intlify/vue-i18n/no-html-messages': 'error', - '@intlify/vue-i18n/prefer-linked-key-with-paren': 'error', - '@intlify/vue-i18n/sfc-locale-attr': 'error', - }, - settings: { - 'vue-i18n': { - localeDir: './src/assets/locales/en.json', - // Specify the version of `vue-i18n` you are using. - // If not specified, the message will be parsed twice. - messageSyntaxVersion: '^9.0.0', - }, - }, - }, - // Ignore list { ignores: [ @@ -112,7 +106,6 @@ export default antfu( 'tsconfig.json', 'src/assets/locales/**/*', '!src/assets/locales/en.json', - 'src/assets/dayjsLocales/', 'components.d.ts', ], }, diff --git a/web/package.json b/web/package.json index d2f951c5d..8352f641a 100644 --- a/web/package.json +++ b/web/package.json @@ -5,7 +5,7 @@ "license": "Apache-2.0", "type": "module", "engines": { - "node": ">=14" + "node": ">=20" }, "scripts": { "start": "vite", @@ -18,56 +18,59 @@ "test": "vitest" }, "dependencies": { - "@intlify/unplugin-vue-i18n": "^4.0.0", - "@kyvg/vue3-notification": "^3.2.1", - "@vueuse/core": "^10.10.0", + "@kyvg/vue3-notification": "^3.4.1", + "@mdi/js": "^7.4.47", + "@tailwindcss/postcss": "4.0.0-beta.8", + "@tailwindcss/typography": "^0.5.15", + "@tailwindcss/vite": "4.0.0-beta.8", + "@vueuse/core": "^12.3.0", "ansi_up": "^6.0.2", - "dayjs": "^1.11.11", + "autoprefixer": "^10.4.20", + "dompurify": "^3.2.3", "fuse.js": "^7.0.0", "js-base64": "^3.7.7", "lodash": "^4.17.21", - "node-emoji": "^2.1.3", - "pinia": "^2.1.7", + "marked": "^15.0.5", + "node-emoji": "^2.2.0", + "pinia": "^2.3.0", + "postcss": "^8.4.49", + "prettier-plugin-tailwindcss": "^0.6.9", "prismjs": "^1.29.0", - "semver": "^7.6.2", - "vue": "^3.4.27", - "vue-i18n": "^9.13.1", - "vue-router": "^4.3.2" + "semver": "^7.6.3", + "simple-icons": "^14.1.0", + "tailwindcss": "^3.4.17", + "vue": "^3.5.13", + "vue-i18n": "^11.0.1", + "vue-router": "^4.5.0" }, "devDependencies": { - "@antfu/eslint-config": "^2.20.0", - "@eslint/js": "^9.4.0", - "@ianvs/prettier-plugin-sort-imports": "^4.2.1", - "@iconify/json": "^2.2.216", - "@intlify/eslint-plugin-vue-i18n": "3.0.0-next.13", + "@antfu/eslint-config": "^3.12.1", + "@eslint/js": "^9.17.0", + "@ianvs/prettier-plugin-sort-imports": "^4.4.0", + "@intlify/eslint-plugin-vue-i18n": "3.2.0", + "@intlify/unplugin-vue-i18n": "^6.0.3", "@types/eslint__js": "^8.42.3", - "@types/lodash": "^4.17.4", - "@types/node": "^20.14.2", - "@types/node-emoji": "^2.1.0", - "@types/prismjs": "^1.26.4", + "@types/lodash": "^4.17.14", + "@types/node": "^22.10.5", + "@types/prismjs": "^1.26.5", "@types/semver": "^7.5.8", "@types/tinycolor2": "^1.4.6", - "@vitejs/plugin-vue": "^5.0.5", - "@vue/compiler-sfc": "^3.4.27", + "@vitejs/plugin-vue": "^5.2.1", + "@vue/compiler-sfc": "^3.5.13", "@vue/test-utils": "^2.4.6", - "eslint": "^9.4.0", - "eslint-plugin-promise": "^6.2.0", - "eslint-plugin-vue-scoped-css": "^2.8.0", - "jsdom": "^24.1.0", - "prettier": "^3.3.0", - "replace-in-file": "^8.0.0", + "eslint": "^9.17.0", + "eslint-plugin-promise": "^7.2.1", + "eslint-plugin-vue-scoped-css": "^2.9.0", + "jsdom": "^25.0.1", + "prettier": "^3.4.2", "tinycolor2": "^1.6.0", - "typescript": "5.4.5", - "typescript-eslint": "^7.12.0", - "unplugin-icons": "^0.18.5", - "unplugin-vue-components": "^0.26.0", - "vite": "^5.2.12", + "ts-node": "^10.9.2", + "typescript": "5.7.2", + "vite": "^6.0.7", "vite-plugin-prismjs": "^0.0.11", - "vite-plugin-windicss": "^1.9.3", "vite-svg-loader": "^5.1.0", - "vitest": "^1.6.0", - "vue-tsc": "^2.0.19", - "windicss": "^3.5.6" + "vitest": "^2.1.8", + "vue-tsc": "^2.2.0" }, "pnpm": { "overrides": { diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index 64dfba1f9..d6683db75 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -11,21 +11,33 @@ importers: .: dependencies: - '@intlify/unplugin-vue-i18n': - specifier: ^4.0.0 - version: 4.0.0(rollup@4.18.0)(vue-i18n@9.13.1(vue@3.4.31(typescript@5.4.5))) '@kyvg/vue3-notification': - specifier: ^3.2.1 - version: 3.2.1(vue@3.4.31(typescript@5.4.5)) + specifier: ^3.4.1 + version: 3.4.1(vue@3.5.13(typescript@5.7.2)) + '@mdi/js': + specifier: ^7.4.47 + version: 7.4.47 + '@tailwindcss/postcss': + specifier: 4.0.0-beta.8 + version: 4.0.0-beta.8 + '@tailwindcss/typography': + specifier: ^0.5.15 + version: 0.5.15(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.7.2))) + '@tailwindcss/vite': + specifier: 4.0.0-beta.8 + version: 4.0.0-beta.8(vite@6.0.7(@types/node@22.10.5)(jiti@2.4.2)(lightningcss@1.28.2)(stylus@0.57.0)(yaml@2.7.0)) '@vueuse/core': - specifier: ^10.10.0 - version: 10.11.0(vue@3.4.31(typescript@5.4.5)) + specifier: ^12.3.0 + version: 12.3.0(typescript@5.7.2) ansi_up: specifier: ^6.0.2 version: 6.0.2 - dayjs: - specifier: ^1.11.11 - version: 1.11.11 + autoprefixer: + specifier: ^10.4.20 + version: 10.4.20(postcss@8.4.49) + dompurify: + specifier: ^3.2.3 + version: 3.2.3 fuse.js: specifier: ^7.0.0 version: 7.0.0 @@ -35,58 +47,70 @@ importers: lodash: specifier: ^4.17.21 version: 4.17.21 + marked: + specifier: ^15.0.5 + version: 15.0.5 node-emoji: - specifier: ^2.1.3 - version: 2.1.3 + specifier: ^2.2.0 + version: 2.2.0 pinia: - specifier: ^2.1.7 - version: 2.1.7(typescript@5.4.5)(vue@3.4.31(typescript@5.4.5)) + specifier: ^2.3.0 + version: 2.3.0(typescript@5.7.2)(vue@3.5.13(typescript@5.7.2)) + postcss: + specifier: ^8.4.49 + version: 8.4.49 + prettier-plugin-tailwindcss: + specifier: ^0.6.9 + version: 0.6.9(@ianvs/prettier-plugin-sort-imports@4.4.0(@vue/compiler-sfc@3.5.13)(prettier@3.4.2))(prettier@3.4.2) prismjs: specifier: ^1.29.0 version: 1.29.0 semver: - specifier: ^7.6.2 - version: 7.6.2 + specifier: ^7.6.3 + version: 7.6.3 + simple-icons: + specifier: ^14.1.0 + version: 14.1.0 + tailwindcss: + specifier: ^3.4.17 + version: 3.4.17(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.7.2)) vue: - specifier: ^3.4.27 - version: 3.4.31(typescript@5.4.5) + specifier: ^3.5.13 + version: 3.5.13(typescript@5.7.2) vue-i18n: - specifier: ^9.13.1 - version: 9.13.1(vue@3.4.31(typescript@5.4.5)) + specifier: ^11.0.1 + version: 11.0.1(vue@3.5.13(typescript@5.7.2)) vue-router: - specifier: ^4.3.2 - version: 4.4.0(vue@3.4.31(typescript@5.4.5)) + specifier: ^4.5.0 + version: 4.5.0(vue@3.5.13(typescript@5.7.2)) devDependencies: '@antfu/eslint-config': - specifier: ^2.20.0 - version: 2.21.2(@vue/compiler-sfc@3.4.31)(eslint@9.6.0)(typescript@5.4.5)(vitest@1.6.0(@types/node@20.14.10)(jsdom@24.1.0)(stylus@0.57.0)) + specifier: ^3.12.1 + version: 3.12.1(@typescript-eslint/utils@8.19.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2))(@vue/compiler-sfc@3.5.13)(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2)(vitest@2.1.8(@types/node@22.10.5)(jsdom@25.0.1)(lightningcss@1.28.2)(stylus@0.57.0)) '@eslint/js': - specifier: ^9.4.0 - version: 9.6.0 + specifier: ^9.17.0 + version: 9.17.0 '@ianvs/prettier-plugin-sort-imports': - specifier: ^4.2.1 - version: 4.3.0(@vue/compiler-sfc@3.4.31)(prettier@3.3.2) - '@iconify/json': - specifier: ^2.2.216 - version: 2.2.225 + specifier: ^4.4.0 + version: 4.4.0(@vue/compiler-sfc@3.5.13)(prettier@3.4.2) '@intlify/eslint-plugin-vue-i18n': - specifier: 3.0.0-next.13 - version: 3.0.0-next.13(eslint@9.6.0) + specifier: 3.2.0 + version: 3.2.0(eslint@9.17.0(jiti@2.4.2)) + '@intlify/unplugin-vue-i18n': + specifier: ^6.0.3 + version: 6.0.3(@vue/compiler-dom@3.5.13)(eslint@9.17.0(jiti@2.4.2))(rollup@4.30.0)(typescript@5.7.2)(vue-i18n@11.0.1(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2)) '@types/eslint__js': specifier: ^8.42.3 version: 8.42.3 '@types/lodash': - specifier: ^4.17.4 - version: 4.17.6 + specifier: ^4.17.14 + version: 4.17.14 '@types/node': - specifier: ^20.14.2 - version: 20.14.10 - '@types/node-emoji': - specifier: ^2.1.0 - version: 2.1.0 + specifier: ^22.10.5 + version: 22.10.5 '@types/prismjs': - specifier: ^1.26.4 - version: 1.26.4 + specifier: ^1.26.5 + version: 1.26.5 '@types/semver': specifier: ^7.5.8 version: 7.5.8 @@ -94,91 +118,80 @@ importers: specifier: ^1.4.6 version: 1.4.6 '@vitejs/plugin-vue': - specifier: ^5.0.5 - version: 5.0.5(vite@5.3.3(@types/node@20.14.10)(stylus@0.57.0))(vue@3.4.31(typescript@5.4.5)) + specifier: ^5.2.1 + version: 5.2.1(vite@6.0.7(@types/node@22.10.5)(jiti@2.4.2)(lightningcss@1.28.2)(stylus@0.57.0)(yaml@2.7.0))(vue@3.5.13(typescript@5.7.2)) '@vue/compiler-sfc': - specifier: ^3.4.27 - version: 3.4.31 + specifier: ^3.5.13 + version: 3.5.13 '@vue/test-utils': specifier: ^2.4.6 version: 2.4.6 eslint: - specifier: ^9.4.0 - version: 9.6.0 + specifier: ^9.17.0 + version: 9.17.0(jiti@2.4.2) eslint-plugin-promise: - specifier: ^6.2.0 - version: 6.4.0(eslint@9.6.0) + specifier: ^7.2.1 + version: 7.2.1(eslint@9.17.0(jiti@2.4.2)) eslint-plugin-vue-scoped-css: - specifier: ^2.8.0 - version: 2.8.1(eslint@9.6.0)(vue-eslint-parser@9.4.3(eslint@9.6.0)) + specifier: ^2.9.0 + version: 2.9.0(eslint@9.17.0(jiti@2.4.2))(vue-eslint-parser@9.4.3(eslint@9.17.0(jiti@2.4.2))) jsdom: - specifier: ^24.1.0 - version: 24.1.0 + specifier: ^25.0.1 + version: 25.0.1 prettier: - specifier: ^3.3.0 - version: 3.3.2 - replace-in-file: - specifier: ^8.0.0 - version: 8.1.0 + specifier: ^3.4.2 + version: 3.4.2 tinycolor2: specifier: ^1.6.0 version: 1.6.0 + ts-node: + specifier: ^10.9.2 + version: 10.9.2(@types/node@22.10.5)(typescript@5.7.2) typescript: - specifier: 5.4.5 - version: 5.4.5 - typescript-eslint: - specifier: ^7.12.0 - version: 7.15.0(eslint@9.6.0)(typescript@5.4.5) - unplugin-icons: - specifier: ^0.18.5 - version: 0.18.5(@vue/compiler-sfc@3.4.31)(vue-template-compiler@2.7.16) - unplugin-vue-components: - specifier: ^0.26.0 - version: 0.26.0(@babel/parser@7.24.7)(rollup@4.18.0)(vue@3.4.31(typescript@5.4.5)) + specifier: 5.7.2 + version: 5.7.2 vite: - specifier: ^5.2.12 - version: 5.3.3(@types/node@20.14.10)(stylus@0.57.0) + specifier: ^6.0.7 + version: 6.0.7(@types/node@22.10.5)(jiti@2.4.2)(lightningcss@1.28.2)(stylus@0.57.0)(yaml@2.7.0) vite-plugin-prismjs: specifier: ^0.0.11 version: 0.0.11(prismjs@1.29.0) - vite-plugin-windicss: - specifier: ^1.9.3 - version: 1.9.3(vite@5.3.3(@types/node@20.14.10)(stylus@0.57.0)) vite-svg-loader: specifier: ^5.1.0 - version: 5.1.0(vue@3.4.31(typescript@5.4.5)) + version: 5.1.0(vue@3.5.13(typescript@5.7.2)) vitest: - specifier: ^1.6.0 - version: 1.6.0(@types/node@20.14.10)(jsdom@24.1.0)(stylus@0.57.0) + specifier: ^2.1.8 + version: 2.1.8(@types/node@22.10.5)(jsdom@25.0.1)(lightningcss@1.28.2)(stylus@0.57.0) vue-tsc: - specifier: ^2.0.19 - version: 2.0.26(typescript@5.4.5) - windicss: - specifier: ^3.5.6 - version: 3.5.6 + specifier: ^2.2.0 + version: 2.2.0(typescript@5.7.2) packages: + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} - '@antfu/eslint-config@2.21.2': - resolution: {integrity: sha512-qaKf+af5GeSNTvTzxtSmpitwLZWIwl/uURxQZhhoHCoA1PxofFHSpCNVYLSvPlj17lwT/DzWgovgL/08uXG9aQ==} + '@antfu/eslint-config@3.12.1': + resolution: {integrity: sha512-6sRgO4u63GK75xeZ2MfCSRT9GcfLti4ZN3Xw+bIu39oo6HY50fBY+rXnWvgwNimzHBOh3yV5xUHfTqcHq1M5AA==} hasBin: true peerDependencies: - '@eslint-react/eslint-plugin': ^1.5.8 + '@eslint-react/eslint-plugin': ^1.19.0 '@prettier/plugin-xml': ^3.4.1 '@unocss/eslint-plugin': '>=0.50.0' astro-eslint-parser: ^1.0.2 - eslint: '>=8.40.0' + eslint: ^9.10.0 eslint-plugin-astro: ^1.2.0 eslint-plugin-format: '>=0.1.0' - eslint-plugin-react-hooks: ^4.6.0 + eslint-plugin-react-hooks: ^5.0.0 eslint-plugin-react-refresh: ^0.4.4 - eslint-plugin-solid: ^0.13.2 + eslint-plugin-solid: ^0.14.3 eslint-plugin-svelte: '>=2.35.1' - prettier-plugin-astro: ^0.13.0 + prettier-plugin-astro: ^0.14.0 prettier-plugin-slidev: ^1.0.5 svelte-eslint-parser: '>=0.37.0' peerDependenciesMeta: @@ -209,116 +222,91 @@ packages: svelte-eslint-parser: optional: true - '@antfu/install-pkg@0.1.1': - resolution: {integrity: sha512-LyB/8+bSfa0DFGC06zpCEfs89/XoWZwws5ygEa5D+Xsm3OfI+aXQ86VgVG7Acyef+rSZ5HE7J8rrxzrQeM3PjQ==} - - '@antfu/install-pkg@0.3.3': - resolution: {integrity: sha512-nHHsk3NXQ6xkCfiRRC8Nfrg8pU5kkr3P3Y9s9dKqiuRmBD0Yap7fymNDjGFKeWhZQHqqbCS5CfeMy9wtExM24w==} + '@antfu/install-pkg@0.5.0': + resolution: {integrity: sha512-dKnk2xlAyC7rvTkpkHmu+Qy/2Zc3Vm/l8PtNyIOGDBtXPY3kThfU4ORNEp3V7SXw5XSOb+tOJaUYpfquPzL/Tg==} '@antfu/utils@0.7.10': resolution: {integrity: sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==} - '@babel/code-frame@7.24.7': - resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} + '@babel/code-frame@7.26.2': + resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.24.7': - resolution: {integrity: sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==} + '@babel/compat-data@7.26.3': + resolution: {integrity: sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==} engines: {node: '>=6.9.0'} - '@babel/core@7.24.7': - resolution: {integrity: sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==} + '@babel/core@7.26.0': + resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} engines: {node: '>=6.9.0'} - '@babel/generator@7.24.7': - resolution: {integrity: sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==} + '@babel/generator@7.26.3': + resolution: {integrity: sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.24.7': - resolution: {integrity: sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==} + '@babel/helper-compilation-targets@7.25.9': + resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} engines: {node: '>=6.9.0'} - '@babel/helper-environment-visitor@7.24.7': - resolution: {integrity: sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==} + '@babel/helper-module-imports@7.25.9': + resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} engines: {node: '>=6.9.0'} - '@babel/helper-function-name@7.24.7': - resolution: {integrity: sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-hoist-variables@7.24.7': - resolution: {integrity: sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.24.7': - resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.24.7': - resolution: {integrity: sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==} + '@babel/helper-module-transforms@7.26.0': + resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-simple-access@7.24.7': - resolution: {integrity: sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==} + '@babel/helper-string-parser@7.25.9': + resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} engines: {node: '>=6.9.0'} - '@babel/helper-split-export-declaration@7.24.7': - resolution: {integrity: sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==} + '@babel/helper-validator-identifier@7.25.9': + resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.24.7': - resolution: {integrity: sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg==} + '@babel/helper-validator-option@7.25.9': + resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.24.7': - resolution: {integrity: sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==} + '@babel/helpers@7.26.0': + resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-option@7.24.7': - resolution: {integrity: sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.24.7': - resolution: {integrity: sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==} - engines: {node: '>=6.9.0'} - - '@babel/highlight@7.24.7': - resolution: {integrity: sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.24.7': - resolution: {integrity: sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==} + '@babel/parser@7.26.3': + resolution: {integrity: sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/runtime@7.24.7': - resolution: {integrity: sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==} + '@babel/runtime@7.26.0': + resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} engines: {node: '>=6.9.0'} - '@babel/template@7.24.7': - resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==} + '@babel/template@7.25.9': + resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.24.7': - resolution: {integrity: sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==} + '@babel/traverse@7.26.4': + resolution: {integrity: sha512-fH+b7Y4p3yqvApJALCPJcwb0/XaOSgtK4pzV6WVjPR5GLFQBRI7pfoX2V2iM48NXvX07NUxxm1Vw98YjqTcU5w==} engines: {node: '>=6.9.0'} - '@babel/types@7.24.7': - resolution: {integrity: sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q==} + '@babel/types@7.26.3': + resolution: {integrity: sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==} engines: {node: '>=6.9.0'} - '@clack/core@0.3.4': - resolution: {integrity: sha512-H4hxZDXgHtWTwV3RAVenqcC4VbJZNegbBjlPvzOzCouXtS2y3sDvlO3IsbrPNWuLWPPlYVYPghQdSF64683Ldw==} + '@clack/core@0.4.0': + resolution: {integrity: sha512-YJCYBsyJfNDaTbvDUVSJ3SgSuPrcujarRgkJ5NLjexDZKvaOiVVJvAQYx8lIgG0qRT8ff0fPgqyBCVivanIZ+A==} - '@clack/prompts@0.7.0': - resolution: {integrity: sha512-0MhX9/B4iL6Re04jPrttDm+BsP8y6mS7byuv0BvXgdXhbV5PdlsHt55dvNsuBCPZ7xq1oTAOOuotR9NFbQyMSA==} - bundledDependencies: - - is-unicode-supported + '@clack/prompts@0.9.0': + resolution: {integrity: sha512-nGsytiExgUr4FL0pR/LeqxA28nz3E0cW7eLTSh3Iod9TGrbBt8Y7BHbV3mmkNC4G0evdYyQ3ZsbiBkk7ektArA==} - '@es-joy/jsdoccomment@0.43.1': - resolution: {integrity: sha512-I238eDtOolvCuvtxrnqtlBaw0BwdQuYqK7eA6XIonicMdOOOb75mqdIzkGDUbS04+1Di007rgm9snFRNeVrOog==} + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@es-joy/jsdoccomment@0.49.0': + resolution: {integrity: sha512-xjZTSFgECpb9Ohuk5yMX5RhUEbfeQcuOp8IF60e+wyzWEF0M5xeSgqsfLtvPEX8BIyOX9saZqzuGPmZ8oWc+5Q==} engines: {node: '>=16'} '@esbuild/aix-ppc64@0.21.5': @@ -327,174 +315,363 @@ packages: cpu: [ppc64] os: [aix] + '@esbuild/aix-ppc64@0.24.2': + resolution: {integrity: sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + '@esbuild/android-arm64@0.21.5': resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} cpu: [arm64] os: [android] + '@esbuild/android-arm64@0.24.2': + resolution: {integrity: sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm@0.21.5': resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} cpu: [arm] os: [android] + '@esbuild/android-arm@0.24.2': + resolution: {integrity: sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + '@esbuild/android-x64@0.21.5': resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} cpu: [x64] os: [android] + '@esbuild/android-x64@0.24.2': + resolution: {integrity: sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + '@esbuild/darwin-arm64@0.21.5': resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-arm64@0.24.2': + resolution: {integrity: sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-x64@0.21.5': resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] + '@esbuild/darwin-x64@0.24.2': + resolution: {integrity: sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + '@esbuild/freebsd-arm64@0.21.5': resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-arm64@0.24.2': + resolution: {integrity: sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-x64@0.21.5': resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] + '@esbuild/freebsd-x64@0.24.2': + resolution: {integrity: sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + '@esbuild/linux-arm64@0.21.5': resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm64@0.24.2': + resolution: {integrity: sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm@0.21.5': resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} cpu: [arm] os: [linux] + '@esbuild/linux-arm@0.24.2': + resolution: {integrity: sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + '@esbuild/linux-ia32@0.21.5': resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] + '@esbuild/linux-ia32@0.24.2': + resolution: {integrity: sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-loong64@0.21.5': resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} cpu: [loong64] os: [linux] + '@esbuild/linux-loong64@0.24.2': + resolution: {integrity: sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-mips64el@0.21.5': resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] + '@esbuild/linux-mips64el@0.24.2': + resolution: {integrity: sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-ppc64@0.21.5': resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] + '@esbuild/linux-ppc64@0.24.2': + resolution: {integrity: sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-riscv64@0.21.5': resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] + '@esbuild/linux-riscv64@0.24.2': + resolution: {integrity: sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-s390x@0.21.5': resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} cpu: [s390x] os: [linux] + '@esbuild/linux-s390x@0.24.2': + resolution: {integrity: sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-x64@0.21.5': resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] + '@esbuild/linux-x64@0.24.2': + resolution: {integrity: sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.24.2': + resolution: {integrity: sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + '@esbuild/netbsd-x64@0.21.5': resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] + '@esbuild/netbsd-x64@0.24.2': + resolution: {integrity: sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.24.2': + resolution: {integrity: sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + '@esbuild/openbsd-x64@0.21.5': resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] + '@esbuild/openbsd-x64@0.24.2': + resolution: {integrity: sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + '@esbuild/sunos-x64@0.21.5': resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] + '@esbuild/sunos-x64@0.24.2': + resolution: {integrity: sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + '@esbuild/win32-arm64@0.21.5': resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] + '@esbuild/win32-arm64@0.24.2': + resolution: {integrity: sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-ia32@0.21.5': resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} cpu: [ia32] os: [win32] + '@esbuild/win32-ia32@0.24.2': + resolution: {integrity: sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-x64@0.21.5': resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} cpu: [x64] os: [win32] - '@eslint-community/eslint-utils@4.4.0': - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + '@esbuild/win32-x64@0.24.2': + resolution: {integrity: sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-plugin-eslint-comments@4.4.1': + resolution: {integrity: sha512-lb/Z/MzbTf7CaVYM9WCFNQZ4L1yi3ev2fsFPF99h31ljhSEyUoyEsKsNWiU+qD1glbYTDJdqgyaLKtyTkkqtuQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + + '@eslint-community/eslint-utils@4.4.1': + resolution: {integrity: sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@eslint-community/regexpp@4.11.0': - resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==} + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/config-array@0.17.0': - resolution: {integrity: sha512-A68TBu6/1mHHuc5YJL0U0VVeGNiklLAL6rRmhTCP2B5XjWLMnrX+HkO+IAXyHvks5cyyY1jjK5ITPQ1HGS2EVA==} + '@eslint/compat@1.2.4': + resolution: {integrity: sha512-S8ZdQj/N69YAtuqFt7653jwcvuUj131+6qGLUyDqfDg1OIoBQ66OCuXC473YQfO2AaxITTutiRQiDwoo7ZLYyg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^9.10.0 + peerDependenciesMeta: + eslint: + optional: true + + '@eslint/config-array@0.19.1': + resolution: {integrity: sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/eslintrc@3.1.0': - resolution: {integrity: sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==} + '@eslint/core@0.9.1': + resolution: {integrity: sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/js@9.6.0': - resolution: {integrity: sha512-D9B0/3vNg44ZeWbYMpBoXqNP4j6eQD5vNwIlGAuFRRzK/WtT/jvDQW3Bi9kkf3PMDMlM7Yi+73VLUsn5bJcl8A==} + '@eslint/eslintrc@3.2.0': + resolution: {integrity: sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/object-schema@2.1.4': - resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} + '@eslint/js@9.17.0': + resolution: {integrity: sha512-Sxc4hqcs1kTu0iID3kcZDW3JHq2a77HO9P8CP6YEA/FpH3Ll8UXE2r/86Rz9YJLKme39S9vU5OWNjC6Xl0Cr3w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/markdown@6.2.1': + resolution: {integrity: sha512-cKVd110hG4ICHmWhIwZJfKmmJBvbiDWyrHODJknAtudKgZtlROGoLX9UEOA0o746zC0hCY4UV4vR+aOGW9S6JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.5': + resolution: {integrity: sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.2.4': + resolution: {integrity: sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} engines: {node: '>=12.22'} - '@humanwhocodes/retry@0.3.0': - resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==} + '@humanwhocodes/retry@0.3.1': + resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==} engines: {node: '>=18.18'} - '@ianvs/prettier-plugin-sort-imports@4.3.0': - resolution: {integrity: sha512-OOMtUcO4J3LoL63dOKAe7bn+lSRRPeit2DqNHpx+wvBp3Grejo2PMaK4Mp1mwy8pnat64ccSgk/lBZbsAdLErw==} + '@humanwhocodes/retry@0.4.1': + resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} + engines: {node: '>=18.18'} + + '@ianvs/prettier-plugin-sort-imports@4.4.0': + resolution: {integrity: sha512-f4/e+/ANGk3tHuwRW0uh2YuBR50I4h1ZjGQ+5uD8sWfinHTivQsnieR5cz24t8M6Vx4rYvZ5v/IEKZhYpzQm9Q==} peerDependencies: '@vue/compiler-sfc': 2.7.x || 3.x prettier: 2 || 3 @@ -502,18 +679,9 @@ packages: '@vue/compiler-sfc': optional: true - '@iconify/json@2.2.225': - resolution: {integrity: sha512-3MxzXdrxDxItlGxvTmLnbZGlvGcwuBIOofJfyobq097lysZasvsNddFguiWecmKP91nRR4I2ik7enFVQ9Lz3mg==} - - '@iconify/types@2.0.0': - resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} - - '@iconify/utils@2.1.25': - resolution: {integrity: sha512-Y+iGko8uv/Fz5bQLLJyNSZGOdMW0G7cnlEX1CiNcKsRXX9cq/y/vwxrIAtLCZhKHr3m0VJmsjVPsvnM4uX8YLg==} - - '@intlify/bundle-utils@8.0.0': - resolution: {integrity: sha512-1B++zykRnMwQ+20SpsZI1JCnV/YJt9Oq7AGlEurzkWJOFtFAVqaGc/oV36PBRYeiKnTbY9VYfjBimr2Vt42wLQ==} - engines: {node: '>= 14.16'} + '@intlify/bundle-utils@10.0.0': + resolution: {integrity: sha512-BR5yLOkF2dzrARTbAg7RGAIPcx9Aark7p1K/0O285F7rfzso9j2dsa+S4dA67clZ0rToZ10NSSTfbyUptVu7Bg==} + engines: {node: '>= 18'} peerDependencies: petite-vue-i18n: '*' vue-i18n: '*' @@ -523,49 +691,81 @@ packages: vue-i18n: optional: true - '@intlify/core-base@9.13.1': - resolution: {integrity: sha512-+bcQRkJO9pcX8d0gel9ZNfrzU22sZFSA0WVhfXrf5jdJOS24a+Bp8pozuS9sBI9Hk/tGz83pgKfmqcn/Ci7/8w==} + '@intlify/core-base@11.0.1': + resolution: {integrity: sha512-NAmhw1l/llM0HZRpagR/ChJTNymW4ll6/4EDSJML5c8L5Hl/+k6UyF8EIgE6DeHpfheQujkSRngauViHqq6jJQ==} engines: {node: '>= 16'} - '@intlify/eslint-plugin-vue-i18n@3.0.0-next.13': - resolution: {integrity: sha512-Z0JNZ47LcskNNSRfjLaS4OuSwJ1YDLtQW5s6xIpiRUqE+ehGD25tLMv3or+9Ajh9uiN6/Zu8emC3+Q5ao4sFqA==} + '@intlify/core-base@9.14.2': + resolution: {integrity: sha512-DZyQ4Hk22sC81MP4qiCDuU+LdaYW91A6lCjq8AWPvY3+mGMzhGDfOCzvyR6YBQxtlPjFqMoFk9ylnNYRAQwXtQ==} + engines: {node: '>= 16'} + + '@intlify/eslint-plugin-vue-i18n@3.2.0': + resolution: {integrity: sha512-TOIrD4tJE48WMyVIB8bNeQJJPYo1Prpqnm9Xpn1UZmcqlELhm8hmP8QyJnkgesfbG7hyiX/kvo63W7ClEQmhpg==} engines: {node: '>=18.0.0'} peerDependencies: eslint: ^8.0.0 || ^9.0.0-0 - '@intlify/message-compiler@9.13.1': - resolution: {integrity: sha512-SKsVa4ajYGBVm7sHMXd5qX70O2XXjm55zdZB3VeMFCvQyvLew/dLvq3MqnaIsTMF1VkkOb9Ttr6tHcMlyPDL9w==} + '@intlify/message-compiler@11.0.0-rc.1': + resolution: {integrity: sha512-TGw2uBfuTFTegZf/BHtUQBEKxl7Q/dVGLoqRIdw8lFsp9g/53sYn5iD+0HxIzdYjbWL6BTJMXCPUHp9PxDTRPw==} engines: {node: '>= 16'} - '@intlify/shared@9.13.1': - resolution: {integrity: sha512-u3b6BKGhE6j/JeRU6C/RL2FgyJfy6LakbtfeVF8fJXURpZZTzfh3e05J0bu0XPw447Q6/WUp3C4ajv4TMS4YsQ==} + '@intlify/message-compiler@11.0.1': + resolution: {integrity: sha512-5RFH8x+Mn3mbjcHXnb6KCXGiczBdiQkWkv99iiA0JpKrNuTAQeW59Pjq/uObMB0eR0shnKYGTkIJxum+DbL3sw==} engines: {node: '>= 16'} - '@intlify/unplugin-vue-i18n@4.0.0': - resolution: {integrity: sha512-q2Mhqa/mLi0tulfLFO4fMXXvEbkSZpI5yGhNNsLTNJJ41icEGUuyDe+j5zRZIKSkOJRgX6YbCyibTDJdRsukmw==} - engines: {node: '>= 14.16'} + '@intlify/message-compiler@9.14.2': + resolution: {integrity: sha512-YsKKuV4Qv4wrLNsvgWbTf0E40uRv+Qiw1BeLQ0LAxifQuhiMe+hfTIzOMdWj/ZpnTDj4RSZtkXjJM7JDiiB5LQ==} + engines: {node: '>= 16'} + + '@intlify/shared@11.0.0-rc.1': + resolution: {integrity: sha512-8tR1xe7ZEbkabTuE/tNhzpolygUn9OaYp9yuYAF4MgDNZg06C3Qny80bes2/e9/Wm3aVkPUlCw6WgU7mQd0yEg==} + engines: {node: '>= 16'} + + '@intlify/shared@11.0.1': + resolution: {integrity: sha512-lH164+aDDptHZ3dBDbIhRa1dOPQUp+83iugpc+1upTOWCnwyC1PVis6rSWNMMJ8VQxvtHQB9JMib48K55y0PvQ==} + engines: {node: '>= 16'} + + '@intlify/shared@9.14.2': + resolution: {integrity: sha512-uRAHAxYPeF+G5DBIboKpPgC/Waecd4Jz8ihtkpJQD5ycb5PwXp0k/+hBGl5dAjwF7w+l74kz/PKA8r8OK//RUw==} + engines: {node: '>= 16'} + + '@intlify/unplugin-vue-i18n@6.0.3': + resolution: {integrity: sha512-9ZDjBlhUHtgjRl23TVcgfJttgu8cNepwVhWvOv3mUMRDAhjW0pur1mWKEUKr1I8PNwE4Gvv2IQ1xcl4RL0nG0g==} + engines: {node: '>= 18'} peerDependencies: petite-vue-i18n: '*' + vue: ^3.2.25 vue-i18n: '*' - vue-i18n-bridge: '*' peerDependenciesMeta: petite-vue-i18n: optional: true vue-i18n: optional: true - vue-i18n-bridge: + + '@intlify/vue-i18n-extensions@8.0.0': + resolution: {integrity: sha512-w0+70CvTmuqbskWfzeYhn0IXxllr6mU+IeM2MU0M+j9OW64jkrvqY+pYFWrUnIIC9bEdij3NICruicwd5EgUuQ==} + engines: {node: '>= 18'} + peerDependencies: + '@intlify/shared': ^9.0.0 || ^10.0.0 || ^11.0.0 + '@vue/compiler-dom': ^3.0.0 + vue: ^3.0.0 + vue-i18n: ^9.0.0 || ^10.0.0 || ^11.0.0 + peerDependenciesMeta: + '@intlify/shared': + optional: true + '@vue/compiler-dom': + optional: true + vue: + optional: true + vue-i18n: optional: true '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} - '@jest/schemas@29.6.3': - resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - - '@jridgewell/gen-mapping@0.3.5': - resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + '@jridgewell/gen-mapping@0.3.8': + resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} engines: {node: '>=6.0.0'} '@jridgewell/resolve-uri@3.1.2': @@ -576,21 +776,23 @@ packages: resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} engines: {node: '>=6.0.0'} - '@jridgewell/sourcemap-codec@1.4.15': - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + '@jridgewell/sourcemap-codec@1.5.0': + resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@jsdevtools/ez-spawn@3.0.4': - resolution: {integrity: sha512-f5DRIOZf7wxogefH03RjMPMdBF7ADTWUMoOs9kaJo06EfwF+aFhMZMDZxHg/Xe12hptN9xoZjGso2fdjapBRIA==} - engines: {node: '>=10'} + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@kyvg/vue3-notification@3.2.1': - resolution: {integrity: sha512-qn4bCBBCxW0Ya+RmHXu2SYBVwGXWsAGdlDKqCqyLryaZTbtFXi32iSSLnuKjSUVxFqQRToFc6g1zp1XLTyHrvw==} + '@kyvg/vue3-notification@3.4.1': + resolution: {integrity: sha512-WhTWCbF36JHLJR5UdKmJF7KXGOGVy4tLeaJuKTHZhwttZWnbF9w1/c2d32tvCSwY9CdeX/n9uoaKWLMKK3vOyg==} peerDependencies: vue: ^3.0.0 + '@mdi/js@7.4.47': + resolution: {integrity: sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -614,8 +816,8 @@ packages: resolution: {integrity: sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@rollup/pluginutils@5.1.0': - resolution: {integrity: sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==} + '@rollup/pluginutils@5.1.4': + resolution: {integrity: sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 @@ -623,156 +825,248 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.18.0': - resolution: {integrity: sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==} + '@rollup/rollup-android-arm-eabi@4.30.0': + resolution: {integrity: sha512-qFcFto9figFLz2g25DxJ1WWL9+c91fTxnGuwhToCl8BaqDsDYMl/kOnBXAyAqkkzAWimYMSWNPWEjt+ADAHuoQ==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.18.0': - resolution: {integrity: sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==} + '@rollup/rollup-android-arm64@4.30.0': + resolution: {integrity: sha512-vqrQdusvVl7dthqNjWCL043qelBK+gv9v3ZiqdxgaJvmZyIAAXMjeGVSqZynKq69T7062T5VrVTuikKSAAVP6A==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.18.0': - resolution: {integrity: sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==} + '@rollup/rollup-darwin-arm64@4.30.0': + resolution: {integrity: sha512-617pd92LhdA9+wpixnzsyhVft3szYiN16aNUMzVkf2N+yAk8UXY226Bfp36LvxYTUt7MO/ycqGFjQgJ0wlMaWQ==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.18.0': - resolution: {integrity: sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==} + '@rollup/rollup-darwin-x64@4.30.0': + resolution: {integrity: sha512-Y3b4oDoaEhCypg8ajPqigKDcpi5ZZovemQl9Edpem0uNv6UUjXv7iySBpGIUTSs2ovWOzYpfw9EbFJXF/fJHWw==} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.18.0': - resolution: {integrity: sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==} + '@rollup/rollup-freebsd-arm64@4.30.0': + resolution: {integrity: sha512-3REQJ4f90sFIBfa0BUokiCdrV/E4uIjhkWe1bMgCkhFXbf4D8YN6C4zwJL881GM818qVYE9BO3dGwjKhpo2ABA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.30.0': + resolution: {integrity: sha512-ZtY3Y8icbe3Cc+uQicsXG5L+CRGUfLZjW6j2gn5ikpltt3Whqjfo5mkyZ86UiuHF9Q3ZsaQeW7YswlHnN+lAcg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.30.0': + resolution: {integrity: sha512-bsPGGzfiHXMhQGuFGpmo2PyTwcrh2otL6ycSZAFTESviUoBOuxF7iBbAL5IJXc/69peXl5rAtbewBFeASZ9O0g==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.18.0': - resolution: {integrity: sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==} + '@rollup/rollup-linux-arm-musleabihf@4.30.0': + resolution: {integrity: sha512-kvyIECEhs2DrrdfQf++maCWJIQ974EI4txlz1nNSBaCdtf7i5Xf1AQCEJWOC5rEBisdaMFFnOWNLYt7KpFqy5A==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.18.0': - resolution: {integrity: sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==} + '@rollup/rollup-linux-arm64-gnu@4.30.0': + resolution: {integrity: sha512-CFE7zDNrokaotXu+shwIrmWrFxllg79vciH4E/zeK7NitVuWEaXRzS0mFfFvyhZfn8WfVOG/1E9u8/DFEgK7WQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.18.0': - resolution: {integrity: sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==} + '@rollup/rollup-linux-arm64-musl@4.30.0': + resolution: {integrity: sha512-MctNTBlvMcIBP0t8lV/NXiUwFg9oK5F79CxLU+a3xgrdJjfBLVIEHSAjQ9+ipofN2GKaMLnFFXLltg1HEEPaGQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': - resolution: {integrity: sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==} + '@rollup/rollup-linux-loongarch64-gnu@4.30.0': + resolution: {integrity: sha512-fBpoYwLEPivL3q368+gwn4qnYnr7GVwM6NnMo8rJ4wb0p/Y5lg88vQRRP077gf+tc25akuqd+1Sxbn9meODhwA==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-powerpc64le-gnu@4.30.0': + resolution: {integrity: sha512-1hiHPV6dUaqIMXrIjN+vgJqtfkLpqHS1Xsg0oUfUVD98xGp1wX89PIXgDF2DWra1nxAd8dfE0Dk59MyeKaBVAw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.18.0': - resolution: {integrity: sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==} + '@rollup/rollup-linux-riscv64-gnu@4.30.0': + resolution: {integrity: sha512-U0xcC80SMpEbvvLw92emHrNjlS3OXjAM0aVzlWfar6PR0ODWCTQtKeeB+tlAPGfZQXicv1SpWwRz9Hyzq3Jx3g==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.18.0': - resolution: {integrity: sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==} + '@rollup/rollup-linux-s390x-gnu@4.30.0': + resolution: {integrity: sha512-VU/P/IODrNPasgZDLIFJmMiLGez+BN11DQWfTVlViJVabyF3JaeaJkP6teI8760f18BMGCQOW9gOmuzFaI1pUw==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.18.0': - resolution: {integrity: sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==} + '@rollup/rollup-linux-x64-gnu@4.30.0': + resolution: {integrity: sha512-laQVRvdbKmjXuFA3ZiZj7+U24FcmoPlXEi2OyLfbpY2MW1oxLt9Au8q9eHd0x6Pw/Kw4oe9gwVXWwIf2PVqblg==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.18.0': - resolution: {integrity: sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==} + '@rollup/rollup-linux-x64-musl@4.30.0': + resolution: {integrity: sha512-3wzKzduS7jzxqcOvy/ocU/gMR3/QrHEFLge5CD7Si9fyHuoXcidyYZ6jyx8OPYmCcGm3uKTUl+9jUSAY74Ln5A==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.18.0': - resolution: {integrity: sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==} + '@rollup/rollup-win32-arm64-msvc@4.30.0': + resolution: {integrity: sha512-jROwnI1+wPyuv696rAFHp5+6RFhXGGwgmgSfzE8e4xfit6oLRg7GyMArVUoM3ChS045OwWr9aTnU+2c1UdBMyw==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.18.0': - resolution: {integrity: sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==} + '@rollup/rollup-win32-ia32-msvc@4.30.0': + resolution: {integrity: sha512-duzweyup5WELhcXx5H1jokpr13i3BV9b48FMiikYAwk/MT1LrMYYk2TzenBd0jj4ivQIt58JWSxc19y4SvLP4g==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.18.0': - resolution: {integrity: sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==} + '@rollup/rollup-win32-x64-msvc@4.30.0': + resolution: {integrity: sha512-DYvxS0M07PvgvavMIybCOBYheyrqlui6ZQBHJs6GqduVzHSZ06TPPvlfvnYstjODHQ8UUXFwt5YE+h0jFI8kwg==} cpu: [x64] os: [win32] - '@sinclair/typebox@0.27.8': - resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} - '@sindresorhus/is@4.6.0': resolution: {integrity: sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==} engines: {node: '>=10'} - '@stylistic/eslint-plugin-js@2.3.0': - resolution: {integrity: sha512-lQwoiYb0Fs6Yc5QS3uT8+T9CPKK2Eoxc3H8EnYJgM26v/DgtW+1lvy2WNgyBflU+ThShZaHm3a6CdD9QeKx23w==} + '@stylistic/eslint-plugin@2.12.1': + resolution: {integrity: sha512-fubZKIHSPuo07FgRTn6S4Nl0uXPRPYVNpyZzIDGfp7Fny6JjNus6kReLD7NI380JXi4HtUTSOZ34LBuNPO1XLQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: '>=8.40.0' - '@stylistic/eslint-plugin-jsx@2.3.0': - resolution: {integrity: sha512-tsQ0IEKB195H6X9A4iUSgLLLKBc8gUBWkBIU8tp1/3g2l8stu+PtMQVV/VmK1+3bem5FJCyvfcZIQ/WF1fsizA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: '>=8.40.0' + '@tailwindcss/node@4.0.0-beta.8': + resolution: {integrity: sha512-ZbicJgFxo83IIH5eBm7CU3K1olsfud7/zg3+yG7P6+fZiufhh8FllM5QOJVxUEJ5zeB1V94Y+hTq5UOfu8ZloA==} - '@stylistic/eslint-plugin-plus@2.3.0': - resolution: {integrity: sha512-xboPWGUU5yaPlR+WR57GwXEuY4PSlPqA0C3IdNA/+1o2MuBi95XgDJcZiJ9N+aXsqBXAPIpFFb+WQ7QEHo4f7g==} - peerDependencies: - eslint: '*' + '@tailwindcss/oxide-android-arm64@4.0.0-beta.8': + resolution: {integrity: sha512-YY4g6INIl8VfDMig12pleAVRf1JPvYCNgIXfcvm9g9lxIGq2zkGPsp81BpMSTS+pGJmTGhOZq8ab/TOprtNkAQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] - '@stylistic/eslint-plugin-ts@2.3.0': - resolution: {integrity: sha512-wqOR38/uz/0XPnHX68ftp8sNMSAqnYGjovOTN7w00xnjS6Lxr3Sk7q6AaxWWqbMvOj7V2fQiMC5HWAbTruJsCg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: '>=8.40.0' + '@tailwindcss/oxide-darwin-arm64@4.0.0-beta.8': + resolution: {integrity: sha512-XUCjDaecPOt+mL7EngO6Yhj/ybNgxg9wi2oFuBECz3fj/VV9WQ8MwMDIdjEwrIm43BtwTvEugLIRO9I4KBbuuA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] - '@stylistic/eslint-plugin@2.3.0': - resolution: {integrity: sha512-rtiz6u5gRyyEZp36FcF1/gHJbsbT3qAgXZ1qkad6Nr/xJ9wrSJkiSFFQhpYVTIZ7FJNRJurEcumZDCwN9dEI4g==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@tailwindcss/oxide-darwin-x64@4.0.0-beta.8': + resolution: {integrity: sha512-iMBDpcRBJPt30iohlqJ+slpV+YoR7vL609Zsvzl432lEt6UWEwtKpvPXNuMUEVi7jjLLyyQ/tgM62alVzG1Hug==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.0.0-beta.8': + resolution: {integrity: sha512-iZY+svFyJHllFSaBOfASzOaSU6TLEx8sX+pZwpDExsDHG61o1xh69QJRAL4TJVW288y9kfNsrvcv4yRyn5fwfw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.0-beta.8': + resolution: {integrity: sha512-IqEJggh5x+WgJYz2pG5r5+sOTU1D7Tb/92bQdQGYU618b9hgLhigLIBlbLEuZIC89aTK+aDYvgeqTbKX8X2iuA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.0.0-beta.8': + resolution: {integrity: sha512-WieWtmho/wdI3gowTyJWtvqn921BtVDwzaKKFjPACZmX4a7UM0T4t4xDINc8M84lSzCzFBpk2wVykSIyqCXJZA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.0.0-beta.8': + resolution: {integrity: sha512-P+apWSDGGgCGbTHfyNxUe4+n3lIH6kV+7Y4QGCkBUx5o3L2RzZ2I2/kQNA5z60Moac0tUqX9mKF8AyCmGpBFCg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.0.0-beta.8': + resolution: {integrity: sha512-6Xj+lHcW0WrsrtRtiHbBFFoJYfHDhscNKumYFyv6THFP9AMwrB/9jp3xPfx9q7Pp3OJf3l0VP8KhdI5MPEMBpw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.0.0-beta.8': + resolution: {integrity: sha512-RWeMlHrcS0Rj3tFhbwxkhnsLmsw8E6g0nHjDawNY0lTYi6PP5RZF7ghgzUbzMkjw6QcBJthycpXYXUCKPIZlpA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-win32-arm64-msvc@4.0.0-beta.8': + resolution: {integrity: sha512-+FQFS2XjsHGlh+U/paIcUULLfkSmcBp9QzXkTu8UsEH6Ygp7L8RmMZshAr5dQDjXFKBvKHKJX4oIg/SP+VThgA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.0.0-beta.8': + resolution: {integrity: sha512-5cuAwlDMlnUgzGdZjr+U3ILGbRh9JGmlALgSKo/92qm02NAjNjSSQ4vvh/hMv+mRk5RQDE5lXwDK5/+fGejOBg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.0.0-beta.8': + resolution: {integrity: sha512-fpZkAwKDFuRNbxQZrXViij2D38R6qqgAnctBR9NPyHxZqYDjn3uyk75alrDnSGj4wUCTAhOCEX4HCI9xCgKGdA==} + engines: {node: '>= 10'} + + '@tailwindcss/postcss@4.0.0-beta.8': + resolution: {integrity: sha512-fdxUB9iRm4QocxXiAX/6NlU/VQG/IU1NlDMSGcwvR91/lZNVmWElialB4AVzdsZXIpv5xgrbmz9bb4nvPrT2Ug==} + + '@tailwindcss/typography@0.5.15': + resolution: {integrity: sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA==} peerDependencies: - eslint: '>=8.40.0' + tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20' + + '@tailwindcss/vite@4.0.0-beta.8': + resolution: {integrity: sha512-ZNlj0fdeH4/uWXafrXklZY+TgmN7wOHWHHBL4i3xzD4BflcCDZJkgJER/8baJCpagMzwWDnA6CyXDX+2q7lMRQ==} + peerDependencies: + vite: ^5.2.0 || ^6 '@trysound/sax@0.2.0': resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} engines: {node: '>=10.13.0'} - '@types/eslint@8.56.10': - resolution: {integrity: sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==} + '@tsconfig/node10@1.0.11': + resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} + + '@tsconfig/node12@1.0.11': + resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} + + '@tsconfig/node14@1.0.3': + resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} + + '@tsconfig/node16@1.0.4': + resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/doctrine@0.0.9': + resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} + + '@types/eslint@9.6.1': + resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} '@types/eslint__js@8.42.3': resolution: {integrity: sha512-alfG737uhmPdnvkrLdZLcEKJ/B8s9Y4hrZ+YAdzUeoArBlSUERA2E87ROfOaS4jd/C45fzOoZzidLc1IPwLqOw==} - '@types/estree@1.0.5': - resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + '@types/estree@1.0.6': + resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - '@types/lodash@4.17.6': - resolution: {integrity: sha512-OpXEVoCKSS3lQqjx9GGGOapBeuW5eUboYHRlHP9urXPX25IKZ6AnP5ZRxtVf63iieUbsHxLn8NQ5Nlftc6yzAA==} + '@types/lodash@4.17.14': + resolution: {integrity: sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==} - '@types/mdast@3.0.15': - resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} - '@types/node-emoji@2.1.0': - resolution: {integrity: sha512-LBGWP2LL5A+PpcvzrgXCFcHt9N1l5bqQn05ZUQFFM625k/tmc2w9ghT4kUwQN6gIPlX6qnDOfekmJmV9BywV9g==} - deprecated: This is a stub types definition. node-emoji provides its own type definitions, so you do not need this installed. + '@types/ms@0.7.34': + resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} - '@types/node@20.14.10': - resolution: {integrity: sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==} + '@types/node@22.10.5': + resolution: {integrity: sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==} '@types/normalize-package-data@2.4.4': resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} - '@types/prismjs@1.26.4': - resolution: {integrity: sha512-rlAnzkW2sZOjbqZ743IHUhFcvzaGbqijwOu8QZnZCjfQzBqFE3s4lOTJEsxikImav9uzz/42I+O7YUs1mWgMlg==} + '@types/prismjs@1.26.5': + resolution: {integrity: sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==} '@types/semver@7.5.8': resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} @@ -780,158 +1074,174 @@ packages: '@types/tinycolor2@1.4.6': resolution: {integrity: sha512-iEN8J0BoMnsWBqjVbWH/c0G0Hh7O21lpR2/+PrvAVgWdzL7eexIFm4JN/Wn10PTcmNdtS6U67r499mlWMXOxNw==} - '@types/unist@2.0.10': - resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@types/unist@3.0.3': + resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} '@types/web-bluetooth@0.0.20': resolution: {integrity: sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==} - '@typescript-eslint/eslint-plugin@7.15.0': - resolution: {integrity: sha512-uiNHpyjZtFrLwLDpHnzaDlP3Tt6sGMqTCiqmxaN4n4RP0EfYZDODJyddiFDF44Hjwxr5xAcaYxVKm9QKQFJFLA==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/eslint-plugin@8.19.0': + resolution: {integrity: sha512-NggSaEZCdSrFddbctrVjkVZvFC6KGfKfNK0CU7mNK/iKHGKbzT4Wmgm08dKpcZECBu9f5FypndoMyRHkdqfT1Q==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^7.0.0 - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/parser@7.15.0': - resolution: {integrity: sha512-k9fYuQNnypLFcqORNClRykkGOMOj+pV6V91R4GO/l1FDGwpqmSwoOQrOHo3cGaH63e+D3ZiCAOsuS/D2c99j/A==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/parser@8.19.0': + resolution: {integrity: sha512-6M8taKyOETY1TKHp0x8ndycipTVgmp4xtg5QpEZzXxDhNvvHOJi5rLRkLr8SK3jTgD5l4fTlvBiRdfsuWydxBw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/scope-manager@7.15.0': - resolution: {integrity: sha512-Q/1yrF/XbxOTvttNVPihxh1b9fxamjEoz2Os/Pe38OHwxC24CyCqXxGTOdpb4lt6HYtqw9HetA/Rf6gDGaMPlw==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/scope-manager@8.19.0': + resolution: {integrity: sha512-hkoJiKQS3GQ13TSMEiuNmSCvhz7ujyqD1x3ShbaETATHrck+9RaDdUbt+osXaUuns9OFwrDTTrjtwsU8gJyyRA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/type-utils@7.15.0': - resolution: {integrity: sha512-SkgriaeV6PDvpA6253PDVep0qCqgbO1IOBiycjnXsszNTVQe5flN5wR5jiczoEoDEnAqYFSFFc9al9BSGVltkg==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/type-utils@8.19.0': + resolution: {integrity: sha512-TZs0I0OSbd5Aza4qAMpp1cdCYVnER94IziudE3JU328YUHgWu9gwiwhag+fuLeJ2LkWLXI+F/182TbG+JaBdTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/types@7.15.0': - resolution: {integrity: sha512-aV1+B1+ySXbQH0pLK0rx66I3IkiZNidYobyfn0WFsdGhSXw+P3YOqeTq5GED458SfB24tg+ux3S+9g118hjlTw==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/types@8.19.0': + resolution: {integrity: sha512-8XQ4Ss7G9WX8oaYvD4OOLCjIQYgRQxO+qCiR2V2s2GxI9AUpo7riNwo6jDhKtTcaJjT8PY54j2Yb33kWtSJsmA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@7.15.0': - resolution: {integrity: sha512-gjyB/rHAopL/XxfmYThQbXbzRMGhZzGw6KpcMbfe8Q3nNQKStpxnUKeXb0KiN/fFDR42Z43szs6rY7eHk0zdGQ==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/typescript-estree@8.19.0': + resolution: {integrity: sha512-WW9PpDaLIFW9LCbucMSdYUuGeFUz1OkWYS/5fwZwTA+l2RwlWFdJvReQqMUMBw4yJWJOfqd7An9uwut2Oj8sLw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true + typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/utils@7.15.0': - resolution: {integrity: sha512-hfDMDqaqOqsUVGiEPSMLR/AjTSCsmJwjpKkYQRo1FNbmW4tBwBspYDwO9eh7sKSTwMQgBw9/T4DHudPaqshRWA==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/utils@8.19.0': + resolution: {integrity: sha512-PTBG+0oEMPH9jCZlfg07LCB2nYI0I317yyvXGfxnvGvw4SHIOuRnQ3kadyyXY6tGdChusIHIbM5zfIbp4M6tCg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: ^8.56.0 + eslint: ^8.57.0 || ^9.0.0 + typescript: '>=4.8.4 <5.8.0' - '@typescript-eslint/visitor-keys@7.15.0': - resolution: {integrity: sha512-Hqgy/ETgpt2L5xueA/zHHIl4fJI2O4XUE9l4+OIfbJIRSnTJb/QscncdqqZzofQegIJugRIF57OJea1khw2SDw==} - engines: {node: ^18.18.0 || >=20.0.0} + '@typescript-eslint/visitor-keys@8.19.0': + resolution: {integrity: sha512-mCFtBbFBJDCNCWUl5y6sZSCHXw1DEFEk3c/M3nRK2a4XUB8StGFtmcEMizdjKuBzB6e/smJAAWYug3VrdLMr1w==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@vitejs/plugin-vue@5.0.5': - resolution: {integrity: sha512-LOjm7XeIimLBZyzinBQ6OSm3UBCNVCpLkxGC0oWmm2YPzVZoxMsdvNVimLTBzpAnR9hl/yn1SHGuRfe6/Td9rQ==} + '@vitejs/plugin-vue@5.2.1': + resolution: {integrity: sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==} engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: - vite: ^5.0.0 + vite: ^5.0.0 || ^6.0.0 vue: ^3.2.25 - '@vitest/expect@1.6.0': - resolution: {integrity: sha512-ixEvFVQjycy/oNgHjqsL6AZCDduC+tflRluaHIzKIsdbzkLn2U/iBnVeJwB6HsIjQBdfMR8Z0tRxKUsvFJEeWQ==} + '@vitest/eslint-plugin@1.1.24': + resolution: {integrity: sha512-7IaENe4NNy33g0iuuy5bHY69JYYRjpv4lMx6H5Wp30W7ez2baLHwxsXF5TM4wa8JDYZt8ut99Ytoj7GiDO01hw==} + peerDependencies: + '@typescript-eslint/utils': '>= 8.0' + eslint: '>= 8.57.0' + typescript: '>= 5.0.0' + vitest: '*' + peerDependenciesMeta: + typescript: + optional: true + vitest: + optional: true - '@vitest/runner@1.6.0': - resolution: {integrity: sha512-P4xgwPjwesuBiHisAVz/LSSZtDjOTPYZVmNAnpHHSR6ONrf8eCJOFRvUwdHn30F5M1fxhqtl7QZQUk2dprIXAg==} + '@vitest/expect@2.1.8': + resolution: {integrity: sha512-8ytZ/fFHq2g4PJVAtDX57mayemKgDR6X3Oa2Foro+EygiOJHUXhCqBAAKQYYajZpFoIfvBCF1j6R6IYRSIUFuw==} - '@vitest/snapshot@1.6.0': - resolution: {integrity: sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==} + '@vitest/mocker@2.1.8': + resolution: {integrity: sha512-7guJ/47I6uqfttp33mgo6ga5Gr1VnL58rcqYKyShoRK9ebu8T5Rs6HN3s1NABiBeVTdWNrwUMcHH54uXZBN4zA==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true - '@vitest/spy@1.6.0': - resolution: {integrity: sha512-leUTap6B/cqi/bQkXUu6bQV5TZPx7pmMBKBQiI0rJA8c3pB56ZsaTbREnF7CJfmvAS4V2cXIBAh/3rVwrrCYgw==} + '@vitest/pretty-format@2.1.8': + resolution: {integrity: sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==} - '@vitest/utils@1.6.0': - resolution: {integrity: sha512-21cPiuGMoMZwiOHa2i4LXkMkMkCGzA+MVFV70jRwHo95dL4x/ts5GZhML1QWuy7yfp3WzK3lRvZi3JnXTYqrBw==} + '@vitest/runner@2.1.8': + resolution: {integrity: sha512-17ub8vQstRnRlIU5k50bG+QOMLHRhYPAna5tw8tYbj+jzjcspnwnwtPtiOlkuKC4+ixDPTuLZiqiWWQ2PSXHVg==} - '@volar/language-core@2.4.0-alpha.15': - resolution: {integrity: sha512-mt8z4Fm2WxfQYoQHPcKVjLQV6PgPqyKLbkCVY2cr5RSaamqCHjhKEpsFX66aL4D/7oYguuaUw9Bx03Vt0TpIIA==} + '@vitest/snapshot@2.1.8': + resolution: {integrity: sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==} - '@volar/source-map@2.4.0-alpha.15': - resolution: {integrity: sha512-8Htngw5TmBY4L3ClDqBGyfLhsB8EmoEXUH1xydyEtEoK0O6NX5ur4Jw8jgvscTlwzizyl/wsN1vn0cQXVbbXYg==} + '@vitest/spy@2.1.8': + resolution: {integrity: sha512-5swjf2q95gXeYPevtW0BLk6H8+bPlMb4Vw/9Em4hFxDcaOxS+e0LOX4yqNxoHzMR2akEB2xfpnWUzkZokmgWDg==} - '@volar/typescript@2.4.0-alpha.15': - resolution: {integrity: sha512-U3StRBbDuxV6Woa4hvGS4kz3XcOzrWUKgFdEFN+ba1x3eaYg7+ytau8ul05xgA+UNGLXXsKur7fTUhDFyISk0w==} + '@vitest/utils@2.1.8': + resolution: {integrity: sha512-dwSoui6djdwbfFmIgbIjX2ZhIoG7Ex/+xpxyiEgIGzjliY8xGkcpITKTlp6B4MgtGkF2ilvm97cPM96XZaAgcA==} - '@vue/compiler-core@3.4.31': - resolution: {integrity: sha512-skOiodXWTV3DxfDhB4rOf3OGalpITLlgCeOwb+Y9GJpfQ8ErigdBUHomBzvG78JoVE8MJoQsb+qhZiHfKeNeEg==} + '@volar/language-core@2.4.11': + resolution: {integrity: sha512-lN2C1+ByfW9/JRPpqScuZt/4OrUUse57GLI6TbLgTIqBVemdl1wNcZ1qYGEo2+Gw8coYLgCy7SuKqn6IrQcQgg==} - '@vue/compiler-dom@3.4.31': - resolution: {integrity: sha512-wK424WMXsG1IGMyDGyLqB+TbmEBFM78hIsOJ9QwUVLGrcSk0ak6zYty7Pj8ftm7nEtdU/DGQxAXp0/lM/2cEpQ==} + '@volar/source-map@2.4.11': + resolution: {integrity: sha512-ZQpmafIGvaZMn/8iuvCFGrW3smeqkq/IIh9F1SdSx9aUl0J4Iurzd6/FhmjNO5g2ejF3rT45dKskgXWiofqlZQ==} - '@vue/compiler-sfc@3.4.31': - resolution: {integrity: sha512-einJxqEw8IIJxzmnxmJBuK2usI+lJonl53foq+9etB2HAzlPjAS/wa7r0uUpXw5ByX3/0uswVSrjNb17vJm1kQ==} + '@volar/typescript@2.4.11': + resolution: {integrity: sha512-2DT+Tdh88Spp5PyPbqhyoYavYCPDsqbHLFwcUI9K1NlY1YgUJvujGdrqUp0zWxnW7KWNTr3xSpMuv2WnaTKDAw==} - '@vue/compiler-ssr@3.4.31': - resolution: {integrity: sha512-RtefmITAje3fJ8FSg1gwgDhdKhZVntIVbwupdyZDSifZTRMiWxWehAOTCc8/KZDnBOcYQ4/9VWxsTbd3wT0hAA==} + '@vue/compiler-core@3.5.13': + resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==} - '@vue/devtools-api@6.6.3': - resolution: {integrity: sha512-0MiMsFma/HqA6g3KLKn+AGpL1kgKhFWszC9U29NfpWK5LE7bjeXxySWJrOJ77hBz+TBrBQ7o4QJqbPbqbs8rJw==} + '@vue/compiler-dom@3.5.13': + resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==} - '@vue/language-core@2.0.26': - resolution: {integrity: sha512-/lt6SfQ3O1yDAhPsnLv9iSUgXd1dMHqUm/t3RctfqjuwQf1LnftZ414X3UBn6aXT4MiwXWtbNJ4Z0NZWwDWgJQ==} + '@vue/compiler-sfc@3.5.13': + resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==} + + '@vue/compiler-ssr@3.5.13': + resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==} + + '@vue/compiler-vue2@2.7.16': + resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} + + '@vue/devtools-api@6.6.4': + resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} + + '@vue/language-core@2.2.0': + resolution: {integrity: sha512-O1ZZFaaBGkKbsRfnVH1ifOK1/1BUkyK+3SQsfnh6PmMmD4qJcTU8godCeA96jjDRTL6zgnK7YzCHfaUlH2r0Mw==} peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true - '@vue/reactivity@3.4.31': - resolution: {integrity: sha512-VGkTani8SOoVkZNds1PfJ/T1SlAIOf8E58PGAhIOUDYPC4GAmFA2u/E14TDAFcf3vVDKunc4QqCe/SHr8xC65Q==} + '@vue/reactivity@3.5.13': + resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==} - '@vue/runtime-core@3.4.31': - resolution: {integrity: sha512-LDkztxeUPazxG/p8c5JDDKPfkCDBkkiNLVNf7XZIUnJ+66GVGkP+TIh34+8LtPisZ+HMWl2zqhIw0xN5MwU1cw==} + '@vue/runtime-core@3.5.13': + resolution: {integrity: sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==} - '@vue/runtime-dom@3.4.31': - resolution: {integrity: sha512-2Auws3mB7+lHhTFCg8E9ZWopA6Q6L455EcU7bzcQ4x6Dn4cCPuqj6S2oBZgN2a8vJRS/LSYYxwFFq2Hlx3Fsaw==} + '@vue/runtime-dom@3.5.13': + resolution: {integrity: sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==} - '@vue/server-renderer@3.4.31': - resolution: {integrity: sha512-D5BLbdvrlR9PE3by9GaUp1gQXlCNadIZytMIb8H2h3FMWJd4oUfkUTEH2wAr3qxoRz25uxbTcbqd3WKlm9EHQA==} + '@vue/server-renderer@3.5.13': + resolution: {integrity: sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==} peerDependencies: - vue: 3.4.31 + vue: 3.5.13 - '@vue/shared@3.4.31': - resolution: {integrity: sha512-Yp3wtJk//8cO4NItOPpi3QkLExAr/aLBGZMmTtW9WpdwBCJpRM6zj9WgWktXAl8IDIozwNMByT45JP3tO3ACWA==} + '@vue/shared@3.5.13': + resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} '@vue/test-utils@2.4.6': resolution: {integrity: sha512-FMxEjOpYNYiFe0GkaHsnJPXFHxQ6m4t8vI/ElPGpMWxZKpmRvQ33OIrvRXemy6yha03RxhOlQuy+gZMC3CQSow==} - '@vueuse/core@10.11.0': - resolution: {integrity: sha512-x3sD4Mkm7PJ+pcq3HX8PLPBadXCAlSDR/waK87dz0gQE+qJnaaFhc/dZVfJz+IUYzTMVGum2QlR7ImiJQN4s6g==} + '@vueuse/core@12.3.0': + resolution: {integrity: sha512-cnV8QDKZrsyKC7tWjPbeEUz2cD9sa9faxF2YkR8QqNwfofgbOhmfIgvSYmkp+ttSvfOw4E6hLcQx15mRPr0yBA==} - '@vueuse/metadata@10.11.0': - resolution: {integrity: sha512-kQX7l6l8dVWNqlqyN3ePW3KmjCQO3ZMgXuBMddIu83CmucrsBfXlH+JoviYyRBws/yLTQO8g3Pbw+bdIoVm4oQ==} + '@vueuse/metadata@12.3.0': + resolution: {integrity: sha512-M/iQHHjMffOv2npsw2ihlUx1CTiBwPEgb7DzByLq7zpg1+Ke8r7s9p5ybUWc5OIeGewtpY4Xy0R2cKqFqM8hFg==} - '@vueuse/shared@10.11.0': - resolution: {integrity: sha512-fyNoIXEq3PfX1L3NkNhtVQUSRtqYwJtJg+Bp9rIzculIZWHTkKSysujrOk2J+NrRulLTQH9+3gGSfYLWSEWU1A==} - - '@windicss/config@1.9.3': - resolution: {integrity: sha512-u8GUjsfC9r5X1AGYhzb1lX3zZj8wqk6SH1DYex8XUGmZ1M2UpvnUPOFi63XFViduspQ6l2xTX84QtG+lUzhEoQ==} - - '@windicss/plugin-utils@1.9.3': - resolution: {integrity: sha512-3VG5HEGeuIfG/9iTwLyzWWm/aGKNTbtSVkpkAabdRuDP/2lEmf6Hpo4uo5drwE+2O9gXfc6nSYgAwBjotx5CfQ==} + '@vueuse/shared@12.3.0': + resolution: {integrity: sha512-X3YD35GUeW0d5Gajcwv9jdLAJTV2Jdb/Ll6Ii2JIYcKLYZqv5wxyLeKtiQkqWmHg3v0J0ZWjDUMVOw2E7RCXfA==} abbrev@2.0.0: resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} @@ -942,42 +1252,37 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn-walk@8.3.3: - resolution: {integrity: sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw==} + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} engines: {node: '>=0.4.0'} - acorn@8.12.1: - resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} engines: {node: '>=0.4.0'} hasBin: true - agent-base@7.1.1: - resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} + agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} engines: {node: '>= 14'} ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + alien-signals@0.4.12: + resolution: {integrity: sha512-Og0PgAihxlp1R22bsoBsyhhMG4+qhU+fkkLPoGBQkYVc3qt9rYnrwYTf+M6kqUqUZpf3rXDnpL90iKa0QcSVVg==} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-regex@6.0.1: - resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + ansi-regex@6.1.0: + resolution: {integrity: sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==} engines: {node: '>=12'} - ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - ansi-styles@5.2.0: - resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} - engines: {node: '>=10'} - ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} @@ -985,6 +1290,9 @@ packages: ansi_up@6.0.2: resolution: {integrity: sha512-3G3vKvl1ilEp7J1u6BmULpMA0xVoW/f4Ekqhl8RTrJrhEBkonKn5k3bUc5Xt+qDayA6iDX0jyUh3AbZjB/l0tw==} + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} @@ -993,15 +1301,18 @@ packages: resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} engines: {node: '>=14'} + arg@4.1.3: + resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - - assertion-error@1.1.0: - resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==} + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} @@ -1011,6 +1322,13 @@ packages: engines: {node: '>= 4.5.0'} hasBin: true + autoprefixer@10.4.20: + resolution: {integrity: sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + babel-plugin-prismjs@2.1.0: resolution: {integrity: sha512-ehzSKYfeAz4U78zi/sfwsjDPlq0LvDKxNefcZTJ/iKBu+plsHsLqZhUeGf1+82LAcA35UZGbU6ksEx2Utphc/g==} peerDependencies: @@ -1036,8 +1354,8 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.23.1: - resolution: {integrity: sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==} + browserslist@4.24.3: + resolution: {integrity: sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true @@ -1049,54 +1367,45 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - call-me-maybe@1.0.2: - resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} - callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - caniuse-lite@1.0.30001640: - resolution: {integrity: sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA==} + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} - chai@4.4.1: - resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} - engines: {node: '>=4'} + caniuse-lite@1.0.30001690: + resolution: {integrity: sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==} - chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chai@5.1.2: + resolution: {integrity: sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==} + engines: {node: '>=12'} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chalk@5.3.0: - resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - char-regex@1.0.2: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} - character-entities-legacy@1.1.4: - resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} - character-entities@1.2.4: - resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==} - - character-reference-invalid@1.1.4: - resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} - - check-error@1.0.3: - resolution: {integrity: sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==} + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} - ci-info@4.0.0: - resolution: {integrity: sha512-TdHqgGf9odd8SXNuxtUBVx8Nv+qZOejE6qyqiy5NtbYYQOeFa6zmHkxlPzmaLxWWHsU6nJmB7AETdVPi+2NBUg==} + ci-info@4.1.0: + resolution: {integrity: sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==} engines: {node: '>=8'} clean-regexp@1.0.0: @@ -1107,16 +1416,10 @@ packages: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} - color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -1128,6 +1431,10 @@ packages: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + commander@7.2.0: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} @@ -1136,14 +1443,11 @@ packages: resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==} engines: {node: '>= 12.0.0'} - computeds@0.0.1: - resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==} - concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - confbox@0.1.7: - resolution: {integrity: sha512-uJcB/FKZtBMCJpK8MQji6bJHgu1tixKPxRLeGkNzBoOZzpnZUJm0jm2/sBDWcuBx1dYgxV4JU+g5hmNxCyAmdA==} + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} config-chain@1.1.13: resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} @@ -1151,11 +1455,14 @@ packages: convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - core-js-compat@3.37.1: - resolution: {integrity: sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==} + core-js-compat@3.39.0: + resolution: {integrity: sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==} - cross-spawn@7.0.3: - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} + create-require@1.1.1: + resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} css-select@5.1.0: @@ -1185,8 +1492,8 @@ packages: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} - cssstyle@4.0.1: - resolution: {integrity: sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==} + cssstyle@4.1.0: + resolution: {integrity: sha512-h66W1URKpBS5YMI/V8PyXvTMFT8SupJ1IzoIV8IeBC/ji8WVmrO8dGlTi+2dh6whmdk6BiKJLD/ZBkhWbcg6nA==} engines: {node: '>=18'} csstype@3.1.3: @@ -1196,9 +1503,6 @@ packages: resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} engines: {node: '>=18'} - dayjs@1.11.11: - resolution: {integrity: sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==} - de-indent@1.0.2: resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==} @@ -1210,8 +1514,8 @@ packages: supports-color: optional: true - debug@4.3.5: - resolution: {integrity: sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==} + debug@4.4.0: + resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==} engines: {node: '>=6.0'} peerDependencies: supports-color: '*' @@ -1222,12 +1526,15 @@ packages: decimal.js@10.4.3: resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} + decode-named-character-reference@1.0.2: + resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} + decode-uri-component@0.2.2: resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} engines: {node: '>=0.10'} - deep-eql@4.1.4: - resolution: {integrity: sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==} + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} engines: {node: '>=6'} deep-is@0.1.4: @@ -1237,13 +1544,27 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} - diff-sequences@29.6.3: - resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} + detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + diff@4.0.2: + resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} + engines: {node: '>=0.3.1'} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} doctrine@3.0.0: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} @@ -1259,8 +1580,11 @@ packages: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} engines: {node: '>= 4'} - domutils@3.1.0: - resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} + dompurify@3.2.3: + resolution: {integrity: sha512-U1U5Hzc2MO0oW3DF+G9qYN0aT7atAou4AgI0XjWz061nyBPbdxkfdhfy5uMgGn6+oLFCfn44ZGbdDqCzVmlOWA==} + + domutils@3.2.1: + resolution: {integrity: sha512-xWXmuRnN9OMP6ptPd2+H0cCbcYBULa5YDTbMm/2lvkWvNA3O4wcW+GvzooqBuNM8yy6pl3VIAeJTUUWUbfI5Fw==} eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} @@ -1270,8 +1594,8 @@ packages: engines: {node: '>=14'} hasBin: true - electron-to-chromium@1.4.817: - resolution: {integrity: sha512-3znu+lZMIbTe8ZOs360OMJvVroVF2NpNI8T5jfLnDetVvj0uNmIucZzQVYMSJfsu9f47Ssox1Gt46PR+R+1JUg==} + electron-to-chromium@1.5.76: + resolution: {integrity: sha512-CjVQyG7n7Sr+eBXE86HIulnL5N8xZY1sgmOPGuq/F0Rr0FJq63lg0kEtOIDfZBk44FnDLf6FUJ+dsJcuiUDdDQ==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1282,8 +1606,8 @@ packages: emojilib@2.4.0: resolution: {integrity: sha512-5U0rVMU5Y2n2+ykNLQqMoqklN9ICBT/KsvC1Gz6vqHbz2AXXGkG+Pm5rMWk/8Vjrr/mY9985Hi8DYzn1F09Nyw==} - enhanced-resolve@5.17.0: - resolution: {integrity: sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==} + enhanced-resolve@5.18.0: + resolution: {integrity: sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==} engines: {node: '>=10.13.0'} entities@4.5.0: @@ -1293,16 +1617,21 @@ packages: error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} - es-module-lexer@1.5.4: - resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} + es-module-lexer@1.6.0: + resolution: {integrity: sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==} esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} hasBin: true - escalade@3.1.2: - resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + esbuild@0.24.2: + resolution: {integrity: sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} escape-string-regexp@1.0.5: @@ -1313,6 +1642,10 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + escodegen@2.1.0: resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} engines: {node: '>=6.0'} @@ -1324,27 +1657,46 @@ packages: peerDependencies: eslint: '>=6.0.0' - eslint-config-flat-gitignore@0.1.5: - resolution: {integrity: sha512-hEZLwuZjDBGDERA49c2q7vxc8sCGv8EdBp6PQYzGOMcHIgrfG9YOM6s/4jx24zhD+wnK9AI8mgN5RxSss5nClQ==} + eslint-compat-utils@0.6.4: + resolution: {integrity: sha512-/u+GQt8NMfXO8w17QendT4gvO5acfxQsAKirAt0LVxDnr2N8YLCVbregaNc/Yhp7NM128DwCaRvr8PLDfeNkQw==} + engines: {node: '>=12'} + peerDependencies: + eslint: '>=6.0.0' - eslint-flat-config-utils@0.2.5: - resolution: {integrity: sha512-iO+yLZtC/LKgACerkpvsZ6NoRVB2sxT04mOpnNcEM1aTwKy+6TsT46PUvrML4y2uVBS6I67hRCd2JiKAPaL/Uw==} + eslint-config-flat-gitignore@0.3.0: + resolution: {integrity: sha512-0Ndxo4qGhcewjTzw52TK06Mc00aDtHNTdeeW2JfONgDcLkRO/n/BteMRzNVpLQYxdCC/dFEilfM9fjjpGIJ9Og==} + peerDependencies: + eslint: ^9.5.0 + + eslint-flat-config-utils@0.4.0: + resolution: {integrity: sha512-kfd5kQZC+BMO0YwTol6zxjKX1zAsk8JfSAopbKjKqmENTJcew+yBejuvccAg37cvOrN0Mh+DVbeyznuNWEjt4A==} eslint-import-resolver-node@0.3.9: resolution: {integrity: sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==} + eslint-json-compat-utils@0.2.1: + resolution: {integrity: sha512-YzEodbDyW8DX8bImKhAcCeu/L31Dd/70Bidx2Qex9OFUtgzXLqtfWL4Hr5fM/aCCB8QUZLuJur0S9k6UfgFkfg==} + engines: {node: '>=12'} + peerDependencies: + '@eslint/json': '*' + eslint: '*' + jsonc-eslint-parser: ^2.4.0 + peerDependenciesMeta: + '@eslint/json': + optional: true + eslint-merge-processors@0.1.0: resolution: {integrity: sha512-IvRXXtEajLeyssvW4wJcZ2etxkR9mUf4zpNwgI+m/Uac9RfXHskuJefkHUcawVzePnd6xp24enp5jfgdHzjRdQ==} peerDependencies: eslint: '*' - eslint-plugin-antfu@2.3.4: - resolution: {integrity: sha512-5RIjJpBK1tuNHuLyFyZ90/iW9s439dP1u2cxA4dH70djx9sKq1CqI+O6Q95aVjgFNTDtQzSC9uYdAD5uEEKciQ==} + eslint-plugin-antfu@2.7.0: + resolution: {integrity: sha512-gZM3jq3ouqaoHmUNszb1Zo2Ux7RckSvkGksjLWz9ipBYGSv1EwwBETN6AdiUXn+RpVHXTbEMPAPlXJazcA6+iA==} peerDependencies: eslint: '*' - eslint-plugin-command@0.2.3: - resolution: {integrity: sha512-1bBYNfjZg60N2ZpLV5ATYSYyueIJ+zl5yKrTs0UFDdnyu07dNSZ7Xplnc+Wb6SXTdc1sIaoIrnuyhvztcltX6A==} + eslint-plugin-command@0.2.7: + resolution: {integrity: sha512-UXJ/1R6kdKDcHhiRqxHJ9RZ3juMR1IWQuSrnwt56qCjxt/am+5+YDt6GKs1FJPnppe6/geEYsO3CR9jc63i0xw==} peerDependencies: eslint: '*' @@ -1354,126 +1706,88 @@ packages: peerDependencies: eslint: '>=8' - eslint-plugin-eslint-comments@3.2.0: - resolution: {integrity: sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==} - engines: {node: '>=6.5.0'} + eslint-plugin-import-x@4.6.1: + resolution: {integrity: sha512-wluSUifMIb7UfwWXqx7Yx0lE/SGCcGXECLx/9bCmbY2nneLwvAZ4vkd1IXDjPKFvdcdUgr1BaRnaRpx3k2+Pfw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - eslint: '>=4.19.1' + eslint: ^8.57.0 || ^9.0.0 - eslint-plugin-import-x@0.5.3: - resolution: {integrity: sha512-hJ/wkMcsLQXAZL3+txXIDpbW5cqwdm1rLTqV4VRY03aIbzE3zWE7rPZKW6Gzf7xyl1u3V1iYC6tOG77d9NF4GQ==} - engines: {node: '>=16'} - peerDependencies: - eslint: ^8.56.0 || ^9.0.0-0 - - eslint-plugin-jsdoc@48.5.2: - resolution: {integrity: sha512-VXBJFviQz30rynlOEQ+dNWLmeopjoAgutUVrWOZwm6Ki4EVDm4XkyIqAV/Zhf7FcDr0AG0aGmRn5FxxCtAF0tA==} + eslint-plugin-jsdoc@50.6.1: + resolution: {integrity: sha512-UWyaYi6iURdSfdVVqvfOs2vdCVz0J40O/z/HTsv2sFjdjmdlUI/qlKLOTmwbPQ2tAfQnE5F9vqx+B+poF71DBQ==} engines: {node: '>=18'} peerDependencies: eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 - eslint-plugin-jsonc@2.16.0: - resolution: {integrity: sha512-Af/ZL5mgfb8FFNleH6KlO4/VdmDuTqmM+SPnWcdoWywTetv7kq+vQe99UyQb9XO3b0OWLVuTH7H0d/PXYCMdSg==} + eslint-plugin-jsonc@2.18.2: + resolution: {integrity: sha512-SDhJiSsWt3nItl/UuIv+ti4g3m4gpGkmnUJS9UWR3TrpyNsIcnJoBRD7Kof6cM4Rk3L0wrmY5Tm3z7ZPjR2uGg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: '>=6.0.0' - eslint-plugin-markdown@5.0.0: - resolution: {integrity: sha512-kY2u9yDhzvfZ0kmRTsvgm3mTnvZgTSGIIPeHg3yesSx4R5CTCnITUjCPhzCD1MUhNcqHU5Tr6lzx+02EclVPbw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - peerDependencies: - eslint: '>=8' - - eslint-plugin-n@17.9.0: - resolution: {integrity: sha512-CPSaXDXdrT4nsrOrO4mT4VB6FMUkoySRkHWuuJJHVqsIEjIeZgMY1H7AzSwPbDScikBmLN82KeM1u7ixV7PzGg==} + eslint-plugin-n@17.15.1: + resolution: {integrity: sha512-KFw7x02hZZkBdbZEFQduRGH4VkIH4MW97ClsbAM4Y4E6KguBJWGfWG1P4HEIpZk2bkoWf0bojpnjNAhYQP8beA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: '>=8.23.0' - eslint-plugin-no-only-tests@3.1.0: - resolution: {integrity: sha512-Lf4YW/bL6Un1R6A76pRZyE1dl1vr31G/ev8UzIc/geCgFWyrKil8hVjYqWVKGB/UIGmb6Slzs9T0wNezdSVegw==} + eslint-plugin-no-only-tests@3.3.0: + resolution: {integrity: sha512-brcKcxGnISN2CcVhXJ/kEQlNa0MEfGRtwKtWA16SkqXHKitaKIMrfemJKLKX1YqDU5C/5JY3PvZXd5jEW04e0Q==} engines: {node: '>=5.0.0'} - eslint-plugin-perfectionist@2.11.0: - resolution: {integrity: sha512-XrtBtiu5rbQv88gl+1e2RQud9te9luYNvKIgM9emttQ2zutHPzY/AQUucwxscDKV4qlTkvLTxjOFvxqeDpPorw==} + eslint-plugin-perfectionist@4.6.0: + resolution: {integrity: sha512-kOswTebUK0LlYExRwqz7YQtvyTUIRsKfp8XrwBBeHGh2e8MBOS6K+7VvG6HpmNckyKySi1I96uPeAlptMFGcRQ==} + engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: - astro-eslint-parser: ^1.0.2 eslint: '>=8.0.0' - svelte: '>=3.0.0' - svelte-eslint-parser: ^0.37.0 - vue-eslint-parser: '>=9.0.0' - peerDependenciesMeta: - astro-eslint-parser: - optional: true - svelte: - optional: true - svelte-eslint-parser: - optional: true - vue-eslint-parser: - optional: true - eslint-plugin-promise@6.4.0: - resolution: {integrity: sha512-/KWWRaD3fGkVCZsdR0RU53PSthFmoHVhZl+y9+6DqeDLSikLdlUVpVEAmI6iCRR5QyOjBYBqHZV/bdv4DJ4Gtw==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-plugin-promise@7.2.1: + resolution: {integrity: sha512-SWKjd+EuvWkYaS+uN2csvj0KoP43YTu7+phKQ5v+xw6+A0gutVX2yqCeCkC3uLCJFiPfR2dD8Es5L7yUsmvEaA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 - eslint-plugin-regexp@2.6.0: - resolution: {integrity: sha512-FCL851+kislsTEQEMioAlpDuK5+E5vs0hi1bF8cFlPlHcEjeRhuAzEsGikXRreE+0j4WhW2uO54MqTjXtYOi3A==} + eslint-plugin-regexp@2.7.0: + resolution: {integrity: sha512-U8oZI77SBtH8U3ulZ05iu0qEzIizyEDXd+BWHvyVxTOjGwcDcvy/kEpgFG4DYca2ByRLiVPFZ2GeH7j1pdvZTA==} engines: {node: ^18 || >=20} peerDependencies: eslint: '>=8.44.0' - eslint-plugin-toml@0.11.1: - resolution: {integrity: sha512-Y1WuMSzfZpeMIrmlP1nUh3kT8p96mThIq4NnHrYUhg10IKQgGfBZjAWnrg9fBqguiX4iFps/x/3Hb5TxBisfdw==} + eslint-plugin-toml@0.12.0: + resolution: {integrity: sha512-+/wVObA9DVhwZB1nG83D2OAQRrcQZXy+drqUnFJKymqnmbnbfg/UPmEMCKrJNcEboUGxUjYrJlgy+/Y930mURQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: '>=6.0.0' - eslint-plugin-unicorn@54.0.0: - resolution: {integrity: sha512-XxYLRiYtAWiAjPv6z4JREby1TAE2byBC7wlh0V4vWDCpccOSU1KovWV//jqPXF6bq3WKxqX9rdjoRQ1EhdmNdQ==} + eslint-plugin-unicorn@56.0.1: + resolution: {integrity: sha512-FwVV0Uwf8XPfVnKSGpMg7NtlZh0G0gBarCaFcMUOoqPxXryxdYxTRRv4kH6B9TFCVIrjRXG+emcxIk2ayZilog==} engines: {node: '>=18.18'} peerDependencies: eslint: '>=8.56.0' - eslint-plugin-unused-imports@3.2.0: - resolution: {integrity: sha512-6uXyn6xdINEpxE1MtDjxQsyXB37lfyO2yKGVVgtD7WEWQGORSOZjgrD6hBhvGv4/SO+TOlS+UnC6JppRqbuwGQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + eslint-plugin-unused-imports@4.1.4: + resolution: {integrity: sha512-YptD6IzQjDardkl0POxnnRBhU1OEePMV0nd6siHaRBbd+lyh6NAhFEobiznKU7kTsSsDeSD62Pe7kAM1b7dAZQ==} peerDependencies: - '@typescript-eslint/eslint-plugin': 6 - 7 - eslint: '8' + '@typescript-eslint/eslint-plugin': ^8.0.0-0 || ^7.0.0 || ^6.0.0 || ^5.0.0 + eslint: ^9.0.0 || ^8.0.0 peerDependenciesMeta: '@typescript-eslint/eslint-plugin': optional: true - eslint-plugin-vitest@0.5.4: - resolution: {integrity: sha512-um+odCkccAHU53WdKAw39MY61+1x990uXjSPguUCq3VcEHdqJrOb8OTMrbYlY6f9jAKx7x98kLVlIe3RJeJqoQ==} - engines: {node: ^18.0.0 || >= 20.0.0} - peerDependencies: - '@typescript-eslint/eslint-plugin': '*' - eslint: ^8.57.0 || ^9.0.0 - vitest: '*' - peerDependenciesMeta: - '@typescript-eslint/eslint-plugin': - optional: true - vitest: - optional: true - - eslint-plugin-vue-scoped-css@2.8.1: - resolution: {integrity: sha512-V6B+zZE60ykYvHTDzdhJ3xa4C83ntmGXqFsylc8l1jdVR9PSgod2+bGFNL7OwRKgZj82ij/o904xa04z1bfCRA==} + eslint-plugin-vue-scoped-css@2.9.0: + resolution: {integrity: sha512-zXeKtEUpfk3PlsgKnr9/2U8K2xcsCV1M9hXWRhKbl3wipVowGXfHrhqUzHFVWNAHzEQv0DCDXGFWrmsGFqhGGA==} engines: {node: ^12.22 || ^14.17 || >=16} peerDependencies: eslint: '>=5.0.0' vue-eslint-parser: '>=7.1.0' - eslint-plugin-vue@9.27.0: - resolution: {integrity: sha512-5Dw3yxEyuBSXTzT5/Ge1X5kIkRTQ3nvBn/VwPwInNiZBSJOO/timWMUaflONnFBzU6NhB68lxnCda7ULV5N7LA==} + eslint-plugin-vue@9.32.0: + resolution: {integrity: sha512-b/Y05HYmnB/32wqVcjxjHZzNpwxj1onBOvqW89W+V+XNG1dRuaFbNd3vT9CLbr2LXjEoq+3vn8DanWf7XU22Ug==} engines: {node: ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 - eslint-plugin-yml@1.14.0: - resolution: {integrity: sha512-ESUpgYPOcAYQO9czugcX5OqRvn/ydDVwGCPXY4YjPqc09rHaUVUA6IE6HLQys4rXk/S+qx3EwTd1wHCwam/OWQ==} + eslint-plugin-yml@1.16.0: + resolution: {integrity: sha512-t4MNCetPjTn18/fUDlQ/wKkcYjnuLYKChBrZ0qUaNqRigVqChHWzTP8SrfFi5s4keX3vdlkWRSu8zHJMdKwxWQ==} engines: {node: ^14.17.0 || >=16.0.0} peerDependencies: eslint: '>=6.0.0' @@ -1484,33 +1798,34 @@ packages: '@vue/compiler-sfc': ^3.3.0 eslint: ^8.50.0 || ^9.0.0 - eslint-rule-composer@0.3.0: - resolution: {integrity: sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==} - engines: {node: '>=4.0.0'} - eslint-scope@7.2.2: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-scope@8.0.1: - resolution: {integrity: sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==} + eslint-scope@8.2.0: + resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-visitor-keys@4.0.0: - resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==} + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - eslint@9.6.0: - resolution: {integrity: sha512-ElQkdLMEEqQNM9Njff+2Y4q2afHk7JpkPvrd7Xh7xefwgQynqPxwf55J7di9+MEibWUGdNjFF9ITG9Pck5M84w==} + eslint@9.17.0: + resolution: {integrity: sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true - espree@10.1.0: - resolution: {integrity: sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==} + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} espree@9.6.1: @@ -1522,8 +1837,8 @@ packages: engines: {node: '>=4'} hasBin: true - esquery@1.5.0: - resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + esquery@1.6.0: + resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} esrecurse@4.3.0: @@ -1544,13 +1859,9 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} - - execa@8.0.1: - resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} - engines: {node: '>=16.17'} + expect-type@1.1.0: + resolution: {integrity: sha512-bFi65yM+xZgk+u/KRIpekdSYkTB5W1pEf0Lt8Q8Msh7b+eQ7LXVtIB1Bkm4fvclDEL1b2CZkMhv2mOeF8tMdkA==} + engines: {node: '>=12.0.0'} fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1558,8 +1869,8 @@ packages: fast-diff@1.3.0: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} - fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} engines: {node: '>=8.6.0'} fast-json-stable-stringify@2.1.0: @@ -1568,8 +1879,8 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - fastq@1.17.1: - resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} + fastq@1.18.0: + resolution: {integrity: sha512-QKHXPW0hD8g4UET03SdOdunzSouc9N4AuHdsX8XNcTsuz+yYFILVNIX4l9yHABMhiEI9Db0JTTIpu0wB+Y1QQw==} file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} @@ -1579,6 +1890,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + find-up-simple@1.0.0: + resolution: {integrity: sha512-q7Us7kcjj2VMePAa02hDAF6d+MzsdsAWEwYyOpwUtlerRBkOEPBCRZrAV4XfcSN8fHAgaD0hP7miwoay6DCprw==} + engines: {node: '>=18'} + find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} @@ -1587,25 +1902,24 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - find-up@7.0.0: - resolution: {integrity: sha512-YyZM99iHrqLKjmt4LJDj58KI+fYyufRLBSYcqycxf//KpBk9FoewoGX0450m9nB44qrZnovzC2oeP5hUibxc/g==} - engines: {node: '>=18'} - flat-cache@4.0.1: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flatted@3.3.1: - resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==} + flatted@3.3.2: + resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} - foreground-child@3.2.1: - resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} + foreground-child@3.3.0: + resolution: {integrity: sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==} engines: {node: '>=14'} - form-data@4.0.0: - resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + form-data@4.0.1: + resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} engines: {node: '>= 6'} + fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -1629,19 +1943,8 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-func-name@2.0.2: - resolution: {integrity: sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==} - - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - - get-stream@8.0.1: - resolution: {integrity: sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==} - engines: {node: '>=16'} - - get-tsconfig@4.7.5: - resolution: {integrity: sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==} + get-tsconfig@4.8.1: + resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} @@ -1651,9 +1954,8 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob@10.4.2: - resolution: {integrity: sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w==} - engines: {node: '>=16 || 14 >=14.18'} + glob@10.4.5: + resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} hasBin: true glob@7.2.3: @@ -1672,24 +1974,16 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - globals@15.8.0: - resolution: {integrity: sha512-VZAJ4cewHTExBWDHR6yptdIBlx9YSSZuwojj9Nt5mBRXQzrKakDsVKQ1J63sklLvzAJm0X5+RpO4i3Y2hcOnFw==} + globals@15.14.0: + resolution: {integrity: sha512-OkToC372DtlQeje9/zHIo5CT8lRP/FUgEOKBEhU4e0abL7J7CD24fD9ohiLN5hagG/kWCYj4K5oaxxtj2Z0Dig==} engines: {node: '>=18'} - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -1713,24 +2007,20 @@ packages: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} - https-proxy-agent@7.0.5: - resolution: {integrity: sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} engines: {node: '>= 14'} - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - - human-signals@5.0.0: - resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} - engines: {node: '>=16.17.0'} - iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} - ignore@5.3.1: - resolution: {integrity: sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@6.0.2: + resolution: {integrity: sha512-InwqeHHN2XpumIkMvpl/DCJVrAHgCsG5+cn1XlnLWGwtZBm8QJfSusItfrwx81CTp5agNZqpKU2J/ccC5nGT4A==} engines: {node: '>= 4'} import-fresh@3.3.0: @@ -1755,12 +2045,6 @@ packages: ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - is-alphabetical@1.0.4: - resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} - - is-alphanumerical@1.0.4: - resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==} - is-arrayish@0.2.1: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} @@ -1772,13 +2056,10 @@ packages: resolution: {integrity: sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==} engines: {node: '>=6'} - is-core-module@2.14.0: - resolution: {integrity: sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==} + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} - is-decimal@1.0.4: - resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==} - is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -1791,9 +2072,6 @@ packages: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} - is-hexadecimal@1.0.4: - resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} - is-language-code@3.1.0: resolution: {integrity: sha512-zJdQ3QTeLye+iphMeK3wks+vXSRFKh68/Pnlw7aOfApFSEIOhYa8P9vwwa6QrImNNBMJTiL1PpYF0f4BxDuEgA==} @@ -1801,30 +2079,21 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - - is-stream@3.0.0: - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - jackspeak@3.4.0: - resolution: {integrity: sha512-JVYhQnN59LVPFCEcVa2C3CrEKYacvjRfqIQl+h8oi91aLYQVWRYbxjPcv1bUiUy/kLmQaANrYfNMCO3kuEDHfw==} - engines: {node: '>=14'} + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jiti@1.21.6: - resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==} + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} hasBin: true js-base64@3.7.7: @@ -1842,19 +2111,16 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - js-tokens@9.0.0: - resolution: {integrity: sha512-WriZw1luRMlmV3LGJaR6QOJjWwgLUTf89OwT2lUOyjX2dJGBwgmIkbcz+7WFZjrZM635JOIR517++e/67CP9dQ==} - js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true - jsdoc-type-pratt-parser@4.0.0: - resolution: {integrity: sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==} + jsdoc-type-pratt-parser@4.1.0: + resolution: {integrity: sha512-Hicd6JK5Njt2QB6XYFS7ok9e37O8AYk3jTcppG4YVQnYjOemymvTcmc7OWsmq/Qqj5TdRFO5/x/tIPmBeRtGHg==} engines: {node: '>=12.0.0'} - jsdom@24.1.0: - resolution: {integrity: sha512-6gpM7pRXCwIOKxX47cgOyvyQDN/Eh0f1MeKySBV2xGdKtqJBLj8P25eY3EVCWo2mglDDzozR2r2MW4T+JiNUZA==} + jsdom@25.0.1: + resolution: {integrity: sha512-8i7LzZj7BF8uplX+ZyOlIz86V6TAsSs+np6m1kpW9u0JWi4z/1t+FzcK1aek+ybTnAC4KhBL4uXCNT0wcUIeCw==} engines: {node: '>=18'} peerDependencies: canvas: ^2.11.2 @@ -1866,13 +2132,8 @@ packages: resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} hasBin: true - jsesc@2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} - engines: {node: '>=4'} - hasBin: true - - jsesc@3.0.2: - resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} hasBin: true @@ -1900,22 +2161,83 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - kolorist@1.8.0: - resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==} - levn@0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lightningcss-darwin-arm64@1.28.2: + resolution: {integrity: sha512-/8cPSqZiusHSS+WQz0W4NuaqFjquys1x+NsdN/XOHb+idGHJSoJ7SoQTVl3DZuAgtPZwFZgRfb/vd1oi8uX6+g==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.28.2: + resolution: {integrity: sha512-R7sFrXlgKjvoEG8umpVt/yutjxOL0z8KWf0bfPT3cYMOW4470xu5qSHpFdIOpRWwl3FKNMUdbKtMUjYt0h2j4g==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.28.2: + resolution: {integrity: sha512-l2qrCT+x7crAY+lMIxtgvV10R8VurzHAoUZJaVFSlHrN8kRLTvEg9ObojIDIexqWJQvJcVVV3vfzsEynpiuvgA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.28.2: + resolution: {integrity: sha512-DKMzpICBEKnL53X14rF7hFDu8KKALUJtcKdFUCW5YOlGSiwRSgVoRjM97wUm/E0NMPkzrTi/rxfvt7ruNK8meg==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.28.2: + resolution: {integrity: sha512-nhfjYkfymWZSxdtTNMWyhFk2ImUm0X7NAgJWFwnsYPOfmtWQEapzG/DXZTfEfMjSzERNUNJoQjPAbdqgB+sjiw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.28.2: + resolution: {integrity: sha512-1SPG1ZTNnphWvAv8RVOymlZ8BDtAg69Hbo7n4QxARvkFVCJAt0cgjAw1Fox0WEhf4PwnyoOBaVH0Z5YNgzt4dA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.28.2: + resolution: {integrity: sha512-ZhQy0FcO//INWUdo/iEdbefntTdpPVQ0XJwwtdbBuMQe+uxqZoytm9M+iqR9O5noWFaxK+nbS2iR/I80Q2Ofpg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.28.2: + resolution: {integrity: sha512-alb/j1NMrgQmSFyzTbN1/pvMPM+gdDw7YBuQ5VSgcFDypN3Ah0BzC2dTZbzwzaMdUVDszX6zH5MzjfVN1oGuww==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.28.2: + resolution: {integrity: sha512-WnwcjcBeAt0jGdjlgbT9ANf30pF0C/QMb1XnLnH272DQU8QXh+kmpi24R55wmWBwaTtNAETZ+m35ohyeMiNt+g==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.28.2: + resolution: {integrity: sha512-3piBifyT3avz22o6mDKywQC/OisH2yDK+caHWkiMsF82i3m5wDBadyCjlCQ5VNgzYkxrWZgiaxHDdd5uxsi0/A==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.28.2: + resolution: {integrity: sha512-ePLRrbt3fgjXI5VFZOLbvkLD5ZRuxGKm+wJ3ujCqBtL3NanDHPo/5zicR5uEKAPiIjBYF99BM4K4okvMznjkVA==} + engines: {node: '>= 12.0.0'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - local-pkg@0.4.3: - resolution: {integrity: sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==} - engines: {node: '>=14'} - - local-pkg@0.5.0: - resolution: {integrity: sha512-ok6z3qlYyCDS4ZEU27HaU6x/xZa9Whf8jD4ptH5UZTQYZVYeb9bnZ3ojVhiJNLiXK1Hfc0GNbLXcmZ5plLDDBg==} + local-pkg@0.5.1: + resolution: {integrity: sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==} engines: {node: '>=14'} locate-path@5.0.0: @@ -1926,9 +2248,11 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - locate-path@7.2.0: - resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + lodash.castarray@4.4.0: + resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -1939,24 +2263,64 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} - loupe@2.3.7: - resolution: {integrity: sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==} + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} - lru-cache@10.3.0: - resolution: {integrity: sha512-CQl19J/g+Hbjbv4Y3mFNNXFEL/5t/KCg8POCuUqd4rMKjGG+j1ybER83hxV58zL+dFI1PTkt3GNFSHRt+d8qEQ==} - engines: {node: 14 || >=16.14} + loupe@3.1.2: + resolution: {integrity: sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - magic-string@0.30.10: - resolution: {integrity: sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==} + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} - mdast-util-from-markdown@0.8.5: - resolution: {integrity: sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==} + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} - mdast-util-to-string@2.0.0: - resolution: {integrity: sha512-AW4DRS3QbBayY/jJmD8437V1Gombjf8RSOUCMFBuo5iHi58AGEgVCKQ+ezHkZZDpAQS75hcBMpLqjpJTjtUL7w==} + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + marked@15.0.5: + resolution: {integrity: sha512-xN+kSuqHjxWg+Q47yhhZMUP+kO1qHobvXkkm6FX+7N6lDvanLDd8H7AQ0jWDDyq+fDt/cSrJaBGyWYHXy0KQWA==} + engines: {node: '>= 18'} + hasBin: true + + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.0.0: + resolution: {integrity: sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.0.0: + resolution: {integrity: sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + + mdast-util-to-markdown@2.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} mdn-data@2.0.28: resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} @@ -1964,18 +2328,96 @@ packages: mdn-data@2.0.30: resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - micromark@2.11.4: - resolution: {integrity: sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==} + micromark-core-commonmark@2.0.2: + resolution: {integrity: sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==} - micromatch@4.0.7: - resolution: {integrity: sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==} + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.0: + resolution: {integrity: sha512-Ub2ncQv+fwD70/l4ou27b4YzfNaCJOvyX4HxXU15m7mpYY+rjuWzsLIPZHJL253Z643RpbcP1oeIJlQ/SKW67g==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + + micromark-util-character@2.1.1: + resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + + micromark-util-sanitize-uri@2.0.1: + resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + + micromark-util-subtokenize@2.0.3: + resolution: {integrity: sha512-VXJJuNxYWSoYL6AJ6OQECCFGhIU2GGHMw8tahogePBrjkG8aCCas3ibkp7RnVOSTClg2is05/R7maAhF1XyQMg==} + + micromark-util-symbol@2.0.1: + resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} + + micromark-util-types@2.0.1: + resolution: {integrity: sha512-534m2WhVTddrcKVepwmVEVnUAmtrx9bfIjNoQHRqfnvdaHQiFytEhJoTgpWJvDEXCO5gLTQh3wYC1PgOJA4NSQ==} + + micromark@4.0.1: + resolution: {integrity: sha512-eBPdkcoCNvYcxQOAKAlceo5SNdzZWfF+FcSupREAzdAh9rRmE239CEQAiTwIgblwnoM8zzj35sZ5ZwvSEOF6Kw==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} engines: {node: '>=8.6'} mime-db@1.52.0: @@ -1986,14 +2428,6 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - - mimic-fn@4.0.0: - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} - engines: {node: '>=12'} - min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -2013,11 +2447,8 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} - mlly@1.7.1: - resolution: {integrity: sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA==} - - ms@2.1.2: - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + mlly@1.7.3: + resolution: {integrity: sha512-xUsx5n/mN0uQf4V548PKQ+YShA4/IW0KI1dZhrNrPCLG+xizETbHTkOa1f8/xut9JRPp8kQuMnz0oqwkTiLo/A==} ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} @@ -2025,23 +2456,27 @@ packages: muggle-string@0.4.1: resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} - nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.8: + resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - natural-compare-lite@1.4.0: - resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} - natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - node-emoji@2.1.3: - resolution: {integrity: sha512-E2WEOVsgs7O16zsURJ/eH8BqhF029wGpEOnv7Urwdo2wmQanOACwJQh0devF9D9RhoZru0+9JXIS0dBXIAz+lA==} + natural-orderby@5.0.0: + resolution: {integrity: sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg==} engines: {node: '>=18'} - node-releases@2.0.14: - resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + node-emoji@2.2.0: + resolution: {integrity: sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw==} + engines: {node: '>=18'} + + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} nopt@7.2.1: resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} @@ -2055,31 +2490,27 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - - npm-run-path@5.3.0: - resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - nwsapi@2.2.10: - resolution: {integrity: sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==} + nwsapi@2.2.16: + resolution: {integrity: sha512-F1I/bimDpj3ncaNDhfyMWuFqmQDBwDB0Fogc2qpL3BWvkQteFD/8BzWuIRl83rq0DXfm8SGt/HFhLXZyljTXcQ==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - - onetime@6.0.0: - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} - engines: {node: '>=12'} - optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -2092,14 +2523,6 @@ packages: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} - p-limit@4.0.0: - resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - - p-limit@5.0.0: - resolution: {integrity: sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==} - engines: {node: '>=18'} - p-locate@4.1.0: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} @@ -2108,38 +2531,34 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} - p-locate@6.0.0: - resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} - package-json-from-dist@1.0.0: - resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==} + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + package-manager-detector@0.2.8: + resolution: {integrity: sha512-ts9KSdroZisdvKMWVAVCXiKqnqNfXz4+IbrBG8/BWx/TR5le+jfenvoBuIZ6UWM9nz47W7AbD9qYfAwfWMIwzA==} parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} - parse-entities@2.0.0: - resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==} - parse-gitignore@2.0.0: resolution: {integrity: sha512-RmVuCHWsfu0QPNW+mraxh/xjQVw/lhUCUru8Zni3Ctq3AoMhpDTq0OVdKS6iesd6Kqb7viCV3isAL43dciOSog==} engines: {node: '>=14'} - parse-imports@2.1.1: - resolution: {integrity: sha512-TDT4HqzUiTMO1wJRwg/t/hYk8Wdp3iF/ToMIlAoVQfL1Xs/sTxq1dKWSMjMbQmIarfWKymOyly40+zmPHXMqCA==} + parse-imports@2.2.1: + resolution: {integrity: sha512-OL/zLggRp8mFhKL0rNORUTR4yBYujK/uU+xZL+/0Rgm2QE4nLO9v8PzEweSJEbMGKmDRjJE4R3IMJlL2di4JeQ==} engines: {node: '>= 18'} parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} - parse5@7.1.2: - resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + parse5@7.2.1: + resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} path-browserify@1.0.1: resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} @@ -2148,10 +2567,6 @@ packages: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} - path-exists@5.0.0: - resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - path-is-absolute@1.0.1: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} @@ -2160,10 +2575,6 @@ packages: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - path-key@4.0.0: - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} - engines: {node: '>=12'} - path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} @@ -2171,18 +2582,15 @@ packages: resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} engines: {node: '>=16 || 14 >=14.18'} - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - pathval@1.1.1: - resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} + pathval@2.0.0: + resolution: {integrity: sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==} + engines: {node: '>= 14.16'} - picocolors@1.0.1: - resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} @@ -2192,25 +2600,60 @@ packages: resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} engines: {node: '>=12'} - pinia@2.1.7: - resolution: {integrity: sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==} + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pinia@2.3.0: + resolution: {integrity: sha512-ohZj3jla0LL0OH5PlLTDMzqKiVw2XARmC1XYLdLWIPBMdhDW/123ZWr4zVAhtJm+aoSkFa13pYXskAvAscIkhQ==} peerDependencies: - '@vue/composition-api': ^1.4.0 typescript: '>=4.4.4' - vue: ^2.6.14 || ^3.3.0 + vue: ^2.7.0 || ^3.5.11 peerDependenciesMeta: - '@vue/composition-api': - optional: true typescript: optional: true - pkg-types@1.1.3: - resolution: {integrity: sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==} + pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + + pkg-types@1.3.0: + resolution: {integrity: sha512-kS7yWjVFCkIw9hqdJBoMxDdzEngmkr5FXeWZZfQ6GoYacjVnsW6l2CcYW/0ThD0vF4LPJgVYnrg4d0uuhwYQbg==} pluralize@8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.0.1: + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@4.0.2: + resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + postcss-safe-parser@6.0.0: resolution: {integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==} engines: {node: '>=12.0'} @@ -2223,31 +2666,89 @@ packages: peerDependencies: postcss: ^8.4.29 - postcss-selector-parser@6.1.0: - resolution: {integrity: sha512-UMz42UD0UY0EApS0ZL9o1XnLhSTtvvvLe5Dc2H2O56fvRZi+KulDyf5ctDhhtYJBGKStV2FL1fy6253cmLgqVQ==} + postcss-selector-parser@6.0.10: + resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} + engines: {node: '>=4'} + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} engines: {node: '>=4'} postcss-styl@0.12.3: resolution: {integrity: sha512-8I7Cd8sxiEITIp32xBK4K/Aj1ukX6vuWnx8oY/oAH35NfQI4OZaY5nd68Yx8HeN5S49uhQ6DL0rNk0ZBu/TaLg==} engines: {node: ^8.10.0 || ^10.13.0 || ^11.10.1 || >=12.13.0} - postcss@8.4.39: - resolution: {integrity: sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==} + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.4.49: + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} engines: {node: ^10 || ^12 || >=14} prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier@3.3.2: - resolution: {integrity: sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==} + prettier-plugin-tailwindcss@0.6.9: + resolution: {integrity: sha512-r0i3uhaZAXYP0At5xGfJH876W3HHGHDp+LCRUJrs57PBeQ6mYHMwr25KH8NPX44F2yGTvdnH7OqCshlQx183Eg==} + engines: {node: '>=14.21.3'} + peerDependencies: + '@ianvs/prettier-plugin-sort-imports': '*' + '@prettier/plugin-pug': '*' + '@shopify/prettier-plugin-liquid': '*' + '@trivago/prettier-plugin-sort-imports': '*' + '@zackad/prettier-plugin-twig-melody': '*' + prettier: ^3.0 + prettier-plugin-astro: '*' + prettier-plugin-css-order: '*' + prettier-plugin-import-sort: '*' + prettier-plugin-jsdoc: '*' + prettier-plugin-marko: '*' + prettier-plugin-multiline-arrays: '*' + prettier-plugin-organize-attributes: '*' + prettier-plugin-organize-imports: '*' + prettier-plugin-sort-imports: '*' + prettier-plugin-style-order: '*' + prettier-plugin-svelte: '*' + peerDependenciesMeta: + '@ianvs/prettier-plugin-sort-imports': + optional: true + '@prettier/plugin-pug': + optional: true + '@shopify/prettier-plugin-liquid': + optional: true + '@trivago/prettier-plugin-sort-imports': + optional: true + '@zackad/prettier-plugin-twig-melody': + optional: true + prettier-plugin-astro: + optional: true + prettier-plugin-css-order: + optional: true + prettier-plugin-import-sort: + optional: true + prettier-plugin-jsdoc: + optional: true + prettier-plugin-marko: + optional: true + prettier-plugin-multiline-arrays: + optional: true + prettier-plugin-organize-attributes: + optional: true + prettier-plugin-organize-imports: + optional: true + prettier-plugin-sort-imports: + optional: true + prettier-plugin-style-order: + optional: true + prettier-plugin-svelte: + optional: true + + prettier@3.4.2: + resolution: {integrity: sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==} engines: {node: '>=14'} hasBin: true - pretty-format@29.7.0: - resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} - engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - prismjs@1.29.0: resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} engines: {node: '>=6'} @@ -2255,21 +2756,15 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} - psl@1.9.0: - resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - querystringify@2.2.0: - resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} - queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - react-is@18.3.1: - resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} read-pkg-up@7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} @@ -2302,18 +2797,10 @@ packages: resolution: {integrity: sha512-qx+xQGZVsy55CH0a1hiVwHmqjLryfh7wQyF5HO07XJ9f7dQMY/gPQHhlyDkIzJKC+x2fUCpCcUODUUUFrm7SHA==} hasBin: true - replace-in-file@8.1.0: - resolution: {integrity: sha512-ySYfKDH6uFVC4Wt6DcZ2UX0UMCqE7xhWGVqje/15WFVwHxnel1gdZjV8nOW9Nms6h/yQE2MWyeIToeqFLZkp4w==} - engines: {node: '>=18'} - hasBin: true - require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} - requires-port@1.0.0: - resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} - resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -2321,22 +2808,20 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - resolve@1.22.8: - resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} hasBin: true reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rollup@4.18.0: - resolution: {integrity: sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==} + rollup@4.30.0: + resolution: {integrity: sha512-sDnr1pcjTgUT69qBksNF1N1anwfbyYG6TBQ22b03bII8EdiUQ7J0TlozVaTMjT/eEJAO49e1ndV7t+UZfL1+vA==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true - rrweb-cssom@0.6.0: - resolution: {integrity: sha512-APM0Gt1KoXBz0iIkkdB/kfvGOwC4UuJFeG/c+yV7wSc7q96cG/kJ0HiYCnzivD9SB53cLV1MlHFNfOuPaadYSw==} - rrweb-cssom@0.7.1: resolution: {integrity: sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==} @@ -2357,8 +2842,8 @@ packages: resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==} engines: {node: ^14.0.0 || >=16.0.0} - semver@7.6.2: - resolution: {integrity: sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==} + semver@7.6.3: + resolution: {integrity: sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==} engines: {node: '>=10'} hasBin: true @@ -2373,13 +2858,14 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + simple-icons@14.1.0: + resolution: {integrity: sha512-4/jkVUqhNpsyx0nqYe4QvBkg6NSxdnOnfs6AfFC5IiFsIqrE7YQveLPrbdEh40PZjEQZ20RG8HKbRjEcigJbPg==} + engines: {node: '>=0.12.18'} + sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} @@ -2387,15 +2873,11 @@ packages: resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==} engines: {node: '>=8'} - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - slashes@3.0.12: resolution: {integrity: sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==} - source-map-js@1.2.0: - resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} source-map-resolve@0.6.0: @@ -2422,8 +2904,8 @@ packages: spdx-expression-parse@4.0.0: resolution: {integrity: sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==} - spdx-license-ids@3.0.18: - resolution: {integrity: sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==} + spdx-license-ids@3.0.20: + resolution: {integrity: sha512-jg25NiDV/1fLtSgEgyvVyDunvaNHbuwF9lfNV17gSmPFAlYzdfNBlLtLzXTevwkPj7DhGbmN9VnmJIgLnhvaBw==} stable-hash@0.0.4: resolution: {integrity: sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==} @@ -2431,12 +2913,8 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} - std-env@3.7.0: - resolution: {integrity: sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==} - - string-argv@0.3.2: - resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} - engines: {node: '>=0.6.19'} + std-env@3.8.0: + resolution: {integrity: sha512-Bc3YwwCB+OzldMxOXJIIvC6cPRWr/LxOp48CdQTOkPyk/t4JWWJbrilwBd7RJzKV8QW7tJkcgAmeuLLJugl5/w==} string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} @@ -2454,14 +2932,6 @@ packages: resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} engines: {node: '>=12'} - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - - strip-final-newline@3.0.0: - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} - engines: {node: '>=12'} - strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -2470,16 +2940,14 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - strip-literal@2.1.0: - resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==} - stylus@0.57.0: resolution: {integrity: sha512-yOI6G8WYfr0q8v8rRvE91wbxFU+rJPo760Va4MF6K0I6BZjO4r+xSynkvyPBP9tV1CIEUeRsiidjIs2rzb1CnQ==} hasBin: true - supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} + sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} @@ -2501,34 +2969,56 @@ packages: resolution: {integrity: sha512-Vhf+bUa//YSTYKseDiiEuQmhGCoIF3CVBhunm3r/DQnYiGT4JssmnKQc44BIyOZRK2pKjXXAgbhfmbeoC9CJpA==} engines: {node: '>=12.20'} - synckit@0.9.0: - resolution: {integrity: sha512-7RnqIMq572L8PeEzKeBINYEJDDxpcH8JEgLwUqBd3TkofhFRbkq4QLR0u+36avGAhCRbk2nnmjcW9SE531hPDg==} + synckit@0.9.2: + resolution: {integrity: sha512-vrozgXDQwYO72vHjUb/HnFbQx1exDjoKzqx23aXEg2a9VIg2TSFZ8FmeZpTjUCFMYw7mpX4BE2SFu8wI7asYsw==} engines: {node: ^14.18.0 || >=16.0.0} + tailwindcss@3.4.17: + resolution: {integrity: sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==} + engines: {node: '>=14.0.0'} + hasBin: true + + tailwindcss@4.0.0-beta.8: + resolution: {integrity: sha512-21HmdRq9tHDLJZavb2cRBGJxBvRODpwb0/t3tRbMOl65hJE6zG6K6lD6lLS3IOC35u4SOjKjdZiJJi9AuWCf+Q==} + tapable@2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} - tinybench@2.8.0: - resolution: {integrity: sha512-1/eK7zUnIklz4JUUlL+658n58XO2hHLQfSk1Zf2LKieUjxidN16eKFEoDEfjHc3ohofSSqK3X5yO6VGb6iW8Lw==} + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} tinycolor2@1.6.0: resolution: {integrity: sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==} - tinypool@0.8.4: - resolution: {integrity: sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==} + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinypool@1.0.2: + resolution: {integrity: sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@1.2.0: + resolution: {integrity: sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==} engines: {node: '>=14.0.0'} - tinyspy@2.2.1: - resolution: {integrity: sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==} + tinyspy@3.0.2: + resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} - to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} + tldts-core@6.1.70: + resolution: {integrity: sha512-RNnIXDB1FD4T9cpQRErEqw6ZpjLlGdMOitdV+0xtbsnwr4YFka1zpc7D4KD+aAn8oSG5JyFrdasZTE04qDE9Yg==} + + tldts@6.1.70: + resolution: {integrity: sha512-/W1YVgYVJd9ZDjey5NXadNh0mJXkiUMUue9Zebd0vpdo1sU+H4zFFTaJ1RKD4N6KFoHfcXy6l+Vu7bh+bdWCzA==} + hasBin: true to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} @@ -2538,31 +3028,44 @@ packages: resolution: {integrity: sha512-khrZo4buq4qVmsGzS5yQjKe/WsFvV8fGfOjDQN0q4iy9FjRfPWRgTFrU8u1R2iu/SfWLhY9WnCi4Jhdrcbtg+g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - tough-cookie@4.1.4: - resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} - engines: {node: '>=6'} + tough-cookie@5.0.0: + resolution: {integrity: sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==} + engines: {node: '>=16'} tr46@5.0.0: resolution: {integrity: sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==} engines: {node: '>=18'} - ts-api-utils@1.3.0: - resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} + ts-api-utils@1.4.3: + resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} engines: {node: '>=16'} peerDependencies: typescript: '>=4.2.0' - tslib@2.6.3: - resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==} + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + ts-node@10.9.2: + resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==} + hasBin: true + peerDependencies: + '@swc/core': '>=1.2.50' + '@swc/wasm': '>=1.2.50' + '@types/node': '*' + typescript: '>=2.7' + peerDependenciesMeta: + '@swc/core': + optional: true + '@swc/wasm': + optional: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} - type-detect@4.0.8: - resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} - engines: {node: '>=4'} - type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} @@ -2575,81 +3078,39 @@ packages: resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} engines: {node: '>=8'} - typescript-eslint@7.15.0: - resolution: {integrity: sha512-Ta40FhMXBCwHura4X4fncaCVkVcnJ9jnOq5+Lp4lN8F4DzHZtOwZdRvVBiNUGznUDHPwdGnrnwxmUOU2fFQqFA==} - engines: {node: ^18.18.0 || >=20.0.0} - peerDependencies: - eslint: ^8.56.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - typescript@5.4.5: - resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} + typescript@5.7.2: + resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==} engines: {node: '>=14.17'} hasBin: true - ufo@1.5.3: - resolution: {integrity: sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==} + ufo@1.5.4: + resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==} - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + undici-types@6.20.0: + resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==} unicode-emoji-modifier-base@1.0.0: resolution: {integrity: sha512-yLSH4py7oFH3oG/9K+XWrz1pSi3dfUrWEnInbxMfArOfc1+33BlGPQtLsOYwvdMy11AwUBetYuaRxSPqgkq+8g==} engines: {node: '>=4'} - unicorn-magic@0.1.0: - resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==} - engines: {node: '>=18'} + unist-util-is@6.0.0: + resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} - unist-util-stringify-position@2.0.3: - resolution: {integrity: sha512-3faScn5I+hy9VleOq/qNbAd6pAx7iH5jYBMS9I1HgQVijz/4mv5Bvw5iw1sC/90CODiKo81G/ps8AJrISn687g==} + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} - universalify@0.2.0: - resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} - engines: {node: '>= 4.0.0'} + unist-util-visit-parents@6.0.1: + resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} - unplugin-icons@0.18.5: - resolution: {integrity: sha512-KVNAohXbZ7tVcG1C3p6QaC7wU9Qrj7etv4XvsMMJAxr5LccQZ+Iuv5LOIv/7GtqXaGN1BuFCqRO1ErsHEgEXdQ==} - peerDependencies: - '@svgr/core': '>=7.0.0' - '@svgx/core': ^1.0.1 - '@vue/compiler-sfc': ^3.0.2 || ^2.7.0 - vue-template-compiler: ^2.6.12 - vue-template-es2015-compiler: ^1.9.0 - peerDependenciesMeta: - '@svgr/core': - optional: true - '@svgx/core': - optional: true - '@vue/compiler-sfc': - optional: true - vue-template-compiler: - optional: true - vue-template-es2015-compiler: - optional: true + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} - unplugin-vue-components@0.26.0: - resolution: {integrity: sha512-s7IdPDlnOvPamjunVxw8kNgKNK8A5KM1YpK5j/p97jEKTjlPNrA0nZBiSfAKKlK1gWZuyWXlKL5dk3EDw874LQ==} - engines: {node: '>=14'} - peerDependencies: - '@babel/parser': ^7.15.8 - '@nuxt/kit': ^3.2.2 - vue: 2 || 3 - peerDependenciesMeta: - '@babel/parser': - optional: true - '@nuxt/kit': - optional: true - - unplugin@1.11.0: - resolution: {integrity: sha512-3r7VWZ/webh0SGgJScpWl2/MRCZK5d3ZYFcNaeci/GQ7Teop7zf0Nl2pUuz7G21BwPd9pcUPOC5KmJ2L3WgC5g==} + unplugin@1.16.0: + resolution: {integrity: sha512-5liCNPuJW8dqh3+DM6uNM2EI3MLLpCKp/KY+9pB5M2S2SR2qvvDHhKgBOaTWEbZTAws3CXfB0rKTIolWKL05VQ==} engines: {node: '>=14.0.0'} - update-browserslist-db@1.1.0: - resolution: {integrity: sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==} + update-browserslist-db@1.1.1: + resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -2657,17 +3118,17 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - url-parse@1.5.10: - resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} - util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + v8-compile-cache-lib@3.0.1: + resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} + validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - vite-node@1.6.0: - resolution: {integrity: sha512-de6HJgzC+TFzOu0NTC4RAIsyf/DY/ibWDYQUcuEA84EMHhcefTUGkjFHKKEJhQN4A+6I0u++kr3l36ZF2d7XRw==} + vite-node@2.1.8: + resolution: {integrity: sha512-uPAwSr57kYjAUux+8E2j0q0Fxpn8M9VoyfGiRI8Kfktz9NcYMCenwY5RnZxnF1WTu3TGiYipirIzacLL3VVGFg==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true @@ -2675,18 +3136,13 @@ packages: resolution: {integrity: sha512-20NBQxg/zH+3FTrlU6BQTob720xkuXNYtrx7psAQ4E6pMcRDeLEK77QU9kXURU587+f2To7ASH1JVTGbXVV/vQ==} engines: {node: '>=12.0.0'} - vite-plugin-windicss@1.9.3: - resolution: {integrity: sha512-PqNiIsrEftCrgn0xIpj8ZMSdpz8NZn+OJ3gKXnOF+hFzbHFrKGJA49ViOUKCHDOquxoGBZMmTjepWr8GrftKcQ==} - peerDependencies: - vite: ^2.0.1 || ^3.0.0 || ^4.0.0 || ^5.0.0 - vite-svg-loader@5.1.0: resolution: {integrity: sha512-M/wqwtOEjgb956/+m5ZrYT/Iq6Hax0OakWbokj8+9PXOnB7b/4AxESHieEtnNEy7ZpjsjYW1/5nK8fATQMmRxw==} peerDependencies: vue: '>=3.2.13' - vite@5.3.3: - resolution: {integrity: sha512-NPQdeCU0Dv2z5fu+ULotpuq5yfCS1BzKUIPhNbP3YBfAMGJXbt2nS+sbTFu+qchaqWTD+H3JK++nRwr6XIcp6A==} + vite@5.4.11: + resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -2694,6 +3150,7 @@ packages: less: '*' lightningcss: ^1.21.0 sass: '*' + sass-embedded: '*' stylus: '*' sugarss: '*' terser: ^5.4.0 @@ -2706,6 +3163,8 @@ packages: optional: true sass: optional: true + sass-embedded: + optional: true stylus: optional: true sugarss: @@ -2713,15 +3172,55 @@ packages: terser: optional: true - vitest@1.6.0: - resolution: {integrity: sha512-H5r/dN06swuFnzNFhq/dnz37bPXnq8xB2xB5JOVk8K09rUtoeNN+LHWkoQ0A/i3hvbUKKcCei9KpbxqHMLhLLA==} + vite@6.0.7: + resolution: {integrity: sha512-RDt8r/7qx9940f8FcOIAH9PTViRrghKaK2K1jY3RaAURrEUbm9Du1mJ72G+jlhtG3WwodnfzY8ORQZbBavZEAQ==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@2.1.8: + resolution: {integrity: sha512-1vBKTZskHw/aosXqQUlVWWlGUxSJR8YtiyZDJAFeW2kPAeX6S3Sool0mjspO+kXLuxVWlEDDowBAeqeAQefqLQ==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@types/node': ^18.0.0 || >=20.0.0 - '@vitest/browser': 1.6.0 - '@vitest/ui': 1.6.0 + '@vitest/browser': 2.1.8 + '@vitest/ui': 2.1.8 happy-dom: '*' jsdom: '*' peerDependenciesMeta: @@ -2741,11 +3240,11 @@ packages: vscode-uri@3.0.8: resolution: {integrity: sha512-AyFQ0EVmsOZOlAnxoFOGOq1SQDWAB7C6aqMGS23svWAllfOaxbuFvcT8D1i8z3Gyn8fraVeZNNmN6e9bxxXkKw==} - vue-component-type-helpers@2.0.26: - resolution: {integrity: sha512-sO9qQ8oC520SW6kqlls0iqDak53gsTVSrYylajgjmkt1c0vcgjsGSy1KzlDrbEx8pm02IEYhlUkU5hCYf8rwtg==} + vue-component-type-helpers@2.2.0: + resolution: {integrity: sha512-cYrAnv2me7bPDcg9kIcGwjJiSB6Qyi08+jLDo9yuvoFQjzHiPTzML7RnkJB1+3P6KMsX/KbCD4QE3Tv/knEllw==} - vue-demi@0.14.8: - resolution: {integrity: sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==} + vue-demi@0.14.10: + resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} engines: {node: '>=12'} hasBin: true peerDependencies: @@ -2761,28 +3260,25 @@ packages: peerDependencies: eslint: '>=6.0.0' - vue-i18n@9.13.1: - resolution: {integrity: sha512-mh0GIxx0wPtPlcB1q4k277y0iKgo25xmDPWioVVYanjPufDBpvu5ySTjP5wOrSvlYQ2m1xI+CFhGdauv/61uQg==} + vue-i18n@11.0.1: + resolution: {integrity: sha512-pWAT8CusK8q9/EpN7V3oxwHwxWm6+Kp2PeTZmRGvdZTkUzMQDpbbmHp0TwQ8xw04XKm23cr6B4GL72y3W8Yekg==} engines: {node: '>= 16'} peerDependencies: vue: ^3.0.0 - vue-router@4.4.0: - resolution: {integrity: sha512-HB+t2p611aIZraV2aPSRNXf0Z/oLZFrlygJm+sZbdJaW6lcFqEDQwnzUBXn+DApw+/QzDU/I9TeWx9izEjTmsA==} + vue-router@4.5.0: + resolution: {integrity: sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==} peerDependencies: vue: ^3.2.0 - vue-template-compiler@2.7.16: - resolution: {integrity: sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==} - - vue-tsc@2.0.26: - resolution: {integrity: sha512-tOhuwy2bIXbMhz82ef37qeiaQHMXKQkD6mOF6CCPl3/uYtST3l6fdNyfMxipudrQTxTfXVPlgJdMENBFfC1CfQ==} + vue-tsc@2.2.0: + resolution: {integrity: sha512-gtmM1sUuJ8aSb0KoAFmK9yMxb8TxjewmxqTJ1aKphD5Cbu0rULFY6+UQT51zW7SpUcenfPUuflKyVwyx9Qdnxg==} hasBin: true peerDependencies: typescript: '>=5.0.0' - vue@3.4.31: - resolution: {integrity: sha512-njqRrOy7W3YLAlVqSKpBebtZpDVg21FPoaq1I7f/+qqBThK9ChAIjkRWgeP6Eat+8C+iia4P3OYqpATP21BCoQ==} + vue@3.5.13: + resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==} peerDependencies: typescript: '*' peerDependenciesMeta: @@ -2797,10 +3293,6 @@ packages: resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} engines: {node: '>=12'} - webpack-sources@3.2.3: - resolution: {integrity: sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==} - engines: {node: '>=10.13.0'} - webpack-virtual-modules@0.6.2: resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} @@ -2812,8 +3304,8 @@ packages: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} engines: {node: '>=18'} - whatwg-url@14.0.0: - resolution: {integrity: sha512-1lfMEm2IEr7RIV+f4lUNPOqfFL+pO+Xw3fJSqmjX9AbXcXcYOkCe1P6+9VBZB6n94af16NfZf+sSk0JCBZC9aw==} + whatwg-url@14.1.0: + resolution: {integrity: sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==} engines: {node: '>=18'} which@2.0.2: @@ -2821,16 +3313,11 @@ packages: engines: {node: '>= 8'} hasBin: true - why-is-node-running@2.2.2: - resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} engines: {node: '>=8'} hasBin: true - windicss@3.5.6: - resolution: {integrity: sha512-P1mzPEjgFMZLX0ZqfFht4fhV/FX8DTG7ERG1fBLiWvd34pTLVReS5CVsewKn9PApSgXnVfPWwvq+qUsRwpnwFA==} - engines: {node: '>= 12'} - hasBin: true - word-wrap@1.2.5: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} @@ -2880,8 +3367,8 @@ packages: resolution: {integrity: sha512-4wZWvE398hCP7O8n3nXKu/vdq1HcH01ixYlCREaJL5NUMwQ0g3MaGFUBNSlmBtKmhbtVG/Cm6lyYmSVTEVil8A==} engines: {node: ^14.17.0 || >=16.0.0} - yaml@2.4.5: - resolution: {integrity: sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==} + yaml@2.7.0: + resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==} engines: {node: '>= 14'} hasBin: true @@ -2893,325 +3380,382 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + yn@3.1.1: + resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} + engines: {node: '>=6'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - yocto-queue@1.1.1: - resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==} - engines: {node: '>=12.20'} + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} snapshots: + '@alloc/quick-lru@5.2.0': {} + '@ampproject/remapping@2.3.0': dependencies: - '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 - '@antfu/eslint-config@2.21.2(@vue/compiler-sfc@3.4.31)(eslint@9.6.0)(typescript@5.4.5)(vitest@1.6.0(@types/node@20.14.10)(jsdom@24.1.0)(stylus@0.57.0))': + '@antfu/eslint-config@3.12.1(@typescript-eslint/utils@8.19.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2))(@vue/compiler-sfc@3.5.13)(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2)(vitest@2.1.8(@types/node@22.10.5)(jsdom@25.0.1)(lightningcss@1.28.2)(stylus@0.57.0))': dependencies: - '@antfu/install-pkg': 0.3.3 - '@clack/prompts': 0.7.0 - '@stylistic/eslint-plugin': 2.3.0(eslint@9.6.0)(typescript@5.4.5) - '@typescript-eslint/eslint-plugin': 7.15.0(@typescript-eslint/parser@7.15.0(eslint@9.6.0)(typescript@5.4.5))(eslint@9.6.0)(typescript@5.4.5) - '@typescript-eslint/parser': 7.15.0(eslint@9.6.0)(typescript@5.4.5) - eslint: 9.6.0 - eslint-config-flat-gitignore: 0.1.5 - eslint-flat-config-utils: 0.2.5 - eslint-merge-processors: 0.1.0(eslint@9.6.0) - eslint-plugin-antfu: 2.3.4(eslint@9.6.0) - eslint-plugin-command: 0.2.3(eslint@9.6.0) - eslint-plugin-eslint-comments: 3.2.0(eslint@9.6.0) - eslint-plugin-import-x: 0.5.3(eslint@9.6.0)(typescript@5.4.5) - eslint-plugin-jsdoc: 48.5.2(eslint@9.6.0) - eslint-plugin-jsonc: 2.16.0(eslint@9.6.0) - eslint-plugin-markdown: 5.0.0(eslint@9.6.0) - eslint-plugin-n: 17.9.0(eslint@9.6.0) - eslint-plugin-no-only-tests: 3.1.0 - eslint-plugin-perfectionist: 2.11.0(eslint@9.6.0)(typescript@5.4.5)(vue-eslint-parser@9.4.3(eslint@9.6.0)) - eslint-plugin-regexp: 2.6.0(eslint@9.6.0) - eslint-plugin-toml: 0.11.1(eslint@9.6.0) - eslint-plugin-unicorn: 54.0.0(eslint@9.6.0) - eslint-plugin-unused-imports: 3.2.0(@typescript-eslint/eslint-plugin@7.15.0(@typescript-eslint/parser@7.15.0(eslint@9.6.0)(typescript@5.4.5))(eslint@9.6.0)(typescript@5.4.5))(eslint@9.6.0) - eslint-plugin-vitest: 0.5.4(@typescript-eslint/eslint-plugin@7.15.0(@typescript-eslint/parser@7.15.0(eslint@9.6.0)(typescript@5.4.5))(eslint@9.6.0)(typescript@5.4.5))(eslint@9.6.0)(typescript@5.4.5)(vitest@1.6.0(@types/node@20.14.10)(jsdom@24.1.0)(stylus@0.57.0)) - eslint-plugin-vue: 9.27.0(eslint@9.6.0) - eslint-plugin-yml: 1.14.0(eslint@9.6.0) - eslint-processor-vue-blocks: 0.1.2(@vue/compiler-sfc@3.4.31)(eslint@9.6.0) - globals: 15.8.0 + '@antfu/install-pkg': 0.5.0 + '@clack/prompts': 0.9.0 + '@eslint-community/eslint-plugin-eslint-comments': 4.4.1(eslint@9.17.0(jiti@2.4.2)) + '@eslint/markdown': 6.2.1 + '@stylistic/eslint-plugin': 2.12.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2) + '@typescript-eslint/eslint-plugin': 8.19.0(@typescript-eslint/parser@8.19.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2))(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2) + '@typescript-eslint/parser': 8.19.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2) + '@vitest/eslint-plugin': 1.1.24(@typescript-eslint/utils@8.19.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2))(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2)(vitest@2.1.8(@types/node@22.10.5)(jsdom@25.0.1)(lightningcss@1.28.2)(stylus@0.57.0)) + eslint: 9.17.0(jiti@2.4.2) + eslint-config-flat-gitignore: 0.3.0(eslint@9.17.0(jiti@2.4.2)) + eslint-flat-config-utils: 0.4.0 + eslint-merge-processors: 0.1.0(eslint@9.17.0(jiti@2.4.2)) + eslint-plugin-antfu: 2.7.0(eslint@9.17.0(jiti@2.4.2)) + eslint-plugin-command: 0.2.7(eslint@9.17.0(jiti@2.4.2)) + eslint-plugin-import-x: 4.6.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2) + eslint-plugin-jsdoc: 50.6.1(eslint@9.17.0(jiti@2.4.2)) + eslint-plugin-jsonc: 2.18.2(eslint@9.17.0(jiti@2.4.2)) + eslint-plugin-n: 17.15.1(eslint@9.17.0(jiti@2.4.2)) + eslint-plugin-no-only-tests: 3.3.0 + eslint-plugin-perfectionist: 4.6.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2) + eslint-plugin-regexp: 2.7.0(eslint@9.17.0(jiti@2.4.2)) + eslint-plugin-toml: 0.12.0(eslint@9.17.0(jiti@2.4.2)) + eslint-plugin-unicorn: 56.0.1(eslint@9.17.0(jiti@2.4.2)) + eslint-plugin-unused-imports: 4.1.4(@typescript-eslint/eslint-plugin@8.19.0(@typescript-eslint/parser@8.19.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2))(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2))(eslint@9.17.0(jiti@2.4.2)) + eslint-plugin-vue: 9.32.0(eslint@9.17.0(jiti@2.4.2)) + eslint-plugin-yml: 1.16.0(eslint@9.17.0(jiti@2.4.2)) + eslint-processor-vue-blocks: 0.1.2(@vue/compiler-sfc@3.5.13)(eslint@9.17.0(jiti@2.4.2)) + globals: 15.14.0 jsonc-eslint-parser: 2.4.0 - local-pkg: 0.5.0 + local-pkg: 0.5.1 parse-gitignore: 2.0.0 - picocolors: 1.0.1 + picocolors: 1.1.1 toml-eslint-parser: 0.10.0 - vue-eslint-parser: 9.4.3(eslint@9.6.0) + vue-eslint-parser: 9.4.3(eslint@9.17.0(jiti@2.4.2)) yaml-eslint-parser: 1.2.3 yargs: 17.7.2 transitivePeerDependencies: + - '@eslint/json' + - '@typescript-eslint/utils' - '@vue/compiler-sfc' - supports-color - - svelte - typescript - vitest - '@antfu/install-pkg@0.1.1': + '@antfu/install-pkg@0.5.0': dependencies: - execa: 5.1.1 - find-up: 5.0.0 - - '@antfu/install-pkg@0.3.3': - dependencies: - '@jsdevtools/ez-spawn': 3.0.4 + package-manager-detector: 0.2.8 + tinyexec: 0.3.2 '@antfu/utils@0.7.10': {} - '@babel/code-frame@7.24.7': + '@babel/code-frame@7.26.2': dependencies: - '@babel/highlight': 7.24.7 - picocolors: 1.0.1 + '@babel/helper-validator-identifier': 7.25.9 + js-tokens: 4.0.0 + picocolors: 1.1.1 - '@babel/compat-data@7.24.7': {} + '@babel/compat-data@7.26.3': {} - '@babel/core@7.24.7': + '@babel/core@7.26.0': dependencies: '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.24.7 - '@babel/helper-compilation-targets': 7.24.7 - '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) - '@babel/helpers': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/template': 7.24.7 - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.3 + '@babel/helper-compilation-targets': 7.25.9 + '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) + '@babel/helpers': 7.26.0 + '@babel/parser': 7.26.3 + '@babel/template': 7.25.9 + '@babel/traverse': 7.26.4 + '@babel/types': 7.26.3 convert-source-map: 2.0.0 - debug: 4.3.5 + debug: 4.4.0 gensync: 1.0.0-beta.2 json5: 2.2.3 - semver: 7.6.2 + semver: 7.6.3 transitivePeerDependencies: - supports-color - '@babel/generator@7.24.7': + '@babel/generator@7.26.3': dependencies: - '@babel/types': 7.24.7 - '@jridgewell/gen-mapping': 0.3.5 + '@babel/parser': 7.26.3 + '@babel/types': 7.26.3 + '@jridgewell/gen-mapping': 0.3.8 '@jridgewell/trace-mapping': 0.3.25 - jsesc: 2.5.2 + jsesc: 3.1.0 - '@babel/helper-compilation-targets@7.24.7': + '@babel/helper-compilation-targets@7.25.9': dependencies: - '@babel/compat-data': 7.24.7 - '@babel/helper-validator-option': 7.24.7 - browserslist: 4.23.1 + '@babel/compat-data': 7.26.3 + '@babel/helper-validator-option': 7.25.9 + browserslist: 4.24.3 lru-cache: 5.1.1 - semver: 7.6.2 + semver: 7.6.3 - '@babel/helper-environment-visitor@7.24.7': + '@babel/helper-module-imports@7.25.9': dependencies: - '@babel/types': 7.24.7 - - '@babel/helper-function-name@7.24.7': - dependencies: - '@babel/template': 7.24.7 - '@babel/types': 7.24.7 - - '@babel/helper-hoist-variables@7.24.7': - dependencies: - '@babel/types': 7.24.7 - - '@babel/helper-module-imports@7.24.7': - dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 + '@babel/traverse': 7.26.4 + '@babel/types': 7.26.3 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.24.7(@babel/core@7.24.7)': + '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': dependencies: - '@babel/core': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-module-imports': 7.24.7 - '@babel/helper-simple-access': 7.24.7 - '@babel/helper-split-export-declaration': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 + '@babel/core': 7.26.0 + '@babel/helper-module-imports': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 + '@babel/traverse': 7.26.4 transitivePeerDependencies: - supports-color - '@babel/helper-simple-access@7.24.7': + '@babel/helper-string-parser@7.25.9': {} + + '@babel/helper-validator-identifier@7.25.9': {} + + '@babel/helper-validator-option@7.25.9': {} + + '@babel/helpers@7.26.0': dependencies: - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - transitivePeerDependencies: - - supports-color + '@babel/template': 7.25.9 + '@babel/types': 7.26.3 - '@babel/helper-split-export-declaration@7.24.7': + '@babel/parser@7.26.3': dependencies: - '@babel/types': 7.24.7 + '@babel/types': 7.26.3 - '@babel/helper-string-parser@7.24.7': {} - - '@babel/helper-validator-identifier@7.24.7': {} - - '@babel/helper-validator-option@7.24.7': {} - - '@babel/helpers@7.24.7': - dependencies: - '@babel/template': 7.24.7 - '@babel/types': 7.24.7 - - '@babel/highlight@7.24.7': - dependencies: - '@babel/helper-validator-identifier': 7.24.7 - chalk: 2.4.2 - js-tokens: 4.0.0 - picocolors: 1.0.1 - - '@babel/parser@7.24.7': - dependencies: - '@babel/types': 7.24.7 - - '@babel/runtime@7.24.7': + '@babel/runtime@7.26.0': dependencies: regenerator-runtime: 0.14.1 - '@babel/template@7.24.7': + '@babel/template@7.25.9': dependencies: - '@babel/code-frame': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 + '@babel/code-frame': 7.26.2 + '@babel/parser': 7.26.3 + '@babel/types': 7.26.3 - '@babel/traverse@7.24.7': + '@babel/traverse@7.26.4': dependencies: - '@babel/code-frame': 7.24.7 - '@babel/generator': 7.24.7 - '@babel/helper-environment-visitor': 7.24.7 - '@babel/helper-function-name': 7.24.7 - '@babel/helper-hoist-variables': 7.24.7 - '@babel/helper-split-export-declaration': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 - debug: 4.3.5 + '@babel/code-frame': 7.26.2 + '@babel/generator': 7.26.3 + '@babel/parser': 7.26.3 + '@babel/template': 7.25.9 + '@babel/types': 7.26.3 + debug: 4.4.0 globals: 11.12.0 transitivePeerDependencies: - supports-color - '@babel/types@7.24.7': + '@babel/types@7.26.3': dependencies: - '@babel/helper-string-parser': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 - to-fast-properties: 2.0.0 + '@babel/helper-string-parser': 7.25.9 + '@babel/helper-validator-identifier': 7.25.9 - '@clack/core@0.3.4': + '@clack/core@0.4.0': dependencies: - picocolors: 1.0.1 + picocolors: 1.1.1 sisteransi: 1.0.5 - '@clack/prompts@0.7.0': + '@clack/prompts@0.9.0': dependencies: - '@clack/core': 0.3.4 - picocolors: 1.0.1 + '@clack/core': 0.4.0 + picocolors: 1.1.1 sisteransi: 1.0.5 - '@es-joy/jsdoccomment@0.43.1': + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@es-joy/jsdoccomment@0.49.0': dependencies: - '@types/eslint': 8.56.10 - '@types/estree': 1.0.5 - '@typescript-eslint/types': 7.15.0 comment-parser: 1.4.1 - esquery: 1.5.0 - jsdoc-type-pratt-parser: 4.0.0 + esquery: 1.6.0 + jsdoc-type-pratt-parser: 4.1.0 '@esbuild/aix-ppc64@0.21.5': optional: true + '@esbuild/aix-ppc64@0.24.2': + optional: true + '@esbuild/android-arm64@0.21.5': optional: true + '@esbuild/android-arm64@0.24.2': + optional: true + '@esbuild/android-arm@0.21.5': optional: true + '@esbuild/android-arm@0.24.2': + optional: true + '@esbuild/android-x64@0.21.5': optional: true + '@esbuild/android-x64@0.24.2': + optional: true + '@esbuild/darwin-arm64@0.21.5': optional: true + '@esbuild/darwin-arm64@0.24.2': + optional: true + '@esbuild/darwin-x64@0.21.5': optional: true + '@esbuild/darwin-x64@0.24.2': + optional: true + '@esbuild/freebsd-arm64@0.21.5': optional: true + '@esbuild/freebsd-arm64@0.24.2': + optional: true + '@esbuild/freebsd-x64@0.21.5': optional: true + '@esbuild/freebsd-x64@0.24.2': + optional: true + '@esbuild/linux-arm64@0.21.5': optional: true + '@esbuild/linux-arm64@0.24.2': + optional: true + '@esbuild/linux-arm@0.21.5': optional: true + '@esbuild/linux-arm@0.24.2': + optional: true + '@esbuild/linux-ia32@0.21.5': optional: true + '@esbuild/linux-ia32@0.24.2': + optional: true + '@esbuild/linux-loong64@0.21.5': optional: true + '@esbuild/linux-loong64@0.24.2': + optional: true + '@esbuild/linux-mips64el@0.21.5': optional: true + '@esbuild/linux-mips64el@0.24.2': + optional: true + '@esbuild/linux-ppc64@0.21.5': optional: true + '@esbuild/linux-ppc64@0.24.2': + optional: true + '@esbuild/linux-riscv64@0.21.5': optional: true + '@esbuild/linux-riscv64@0.24.2': + optional: true + '@esbuild/linux-s390x@0.21.5': optional: true + '@esbuild/linux-s390x@0.24.2': + optional: true + '@esbuild/linux-x64@0.21.5': optional: true + '@esbuild/linux-x64@0.24.2': + optional: true + + '@esbuild/netbsd-arm64@0.24.2': + optional: true + '@esbuild/netbsd-x64@0.21.5': optional: true + '@esbuild/netbsd-x64@0.24.2': + optional: true + + '@esbuild/openbsd-arm64@0.24.2': + optional: true + '@esbuild/openbsd-x64@0.21.5': optional: true + '@esbuild/openbsd-x64@0.24.2': + optional: true + '@esbuild/sunos-x64@0.21.5': optional: true + '@esbuild/sunos-x64@0.24.2': + optional: true + '@esbuild/win32-arm64@0.21.5': optional: true + '@esbuild/win32-arm64@0.24.2': + optional: true + '@esbuild/win32-ia32@0.21.5': optional: true + '@esbuild/win32-ia32@0.24.2': + optional: true + '@esbuild/win32-x64@0.21.5': optional: true - '@eslint-community/eslint-utils@4.4.0(eslint@9.6.0)': + '@esbuild/win32-x64@0.24.2': + optional: true + + '@eslint-community/eslint-plugin-eslint-comments@4.4.1(eslint@9.17.0(jiti@2.4.2))': dependencies: - eslint: 9.6.0 + escape-string-regexp: 4.0.0 + eslint: 9.17.0(jiti@2.4.2) + ignore: 5.3.2 + + '@eslint-community/eslint-utils@4.4.1(eslint@9.17.0(jiti@2.4.2))': + dependencies: + eslint: 9.17.0(jiti@2.4.2) eslint-visitor-keys: 3.4.3 - '@eslint-community/regexpp@4.11.0': {} + '@eslint-community/regexpp@4.12.1': {} - '@eslint/config-array@0.17.0': + '@eslint/compat@1.2.4(eslint@9.17.0(jiti@2.4.2))': + optionalDependencies: + eslint: 9.17.0(jiti@2.4.2) + + '@eslint/config-array@0.19.1': dependencies: - '@eslint/object-schema': 2.1.4 - debug: 4.3.5 + '@eslint/object-schema': 2.1.5 + debug: 4.4.0 minimatch: 3.1.2 transitivePeerDependencies: - supports-color - '@eslint/eslintrc@3.1.0': + '@eslint/core@0.9.1': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.2.0': dependencies: ajv: 6.12.6 - debug: 4.3.5 - espree: 10.1.0 + debug: 4.4.0 + espree: 10.3.0 globals: 14.0.0 - ignore: 5.3.1 + ignore: 5.3.2 import-fresh: 3.3.0 js-yaml: 4.1.0 minimatch: 3.1.2 @@ -3219,117 +3763,154 @@ snapshots: transitivePeerDependencies: - supports-color - '@eslint/js@9.6.0': {} + '@eslint/js@9.17.0': {} - '@eslint/object-schema@2.1.4': {} + '@eslint/markdown@6.2.1': + dependencies: + '@eslint/plugin-kit': 0.2.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm: 3.0.0 + micromark-extension-gfm: 3.0.0 + transitivePeerDependencies: + - supports-color + + '@eslint/object-schema@2.1.5': {} + + '@eslint/plugin-kit@0.2.4': + dependencies: + levn: 0.4.1 + + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.1 '@humanwhocodes/module-importer@1.0.1': {} - '@humanwhocodes/retry@0.3.0': {} + '@humanwhocodes/retry@0.3.1': {} - '@ianvs/prettier-plugin-sort-imports@4.3.0(@vue/compiler-sfc@3.4.31)(prettier@3.3.2)': + '@humanwhocodes/retry@0.4.1': {} + + '@ianvs/prettier-plugin-sort-imports@4.4.0(@vue/compiler-sfc@3.5.13)(prettier@3.4.2)': dependencies: - '@babel/core': 7.24.7 - '@babel/generator': 7.24.7 - '@babel/parser': 7.24.7 - '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 - prettier: 3.3.2 - semver: 7.6.2 + '@babel/generator': 7.26.3 + '@babel/parser': 7.26.3 + '@babel/traverse': 7.26.4 + '@babel/types': 7.26.3 + prettier: 3.4.2 + semver: 7.6.3 optionalDependencies: - '@vue/compiler-sfc': 3.4.31 + '@vue/compiler-sfc': 3.5.13 transitivePeerDependencies: - supports-color - '@iconify/json@2.2.225': + '@intlify/bundle-utils@10.0.0(vue-i18n@11.0.1(vue@3.5.13(typescript@5.7.2)))': dependencies: - '@iconify/types': 2.0.0 - pathe: 1.1.2 - - '@iconify/types@2.0.0': {} - - '@iconify/utils@2.1.25': - dependencies: - '@antfu/install-pkg': 0.1.1 - '@antfu/utils': 0.7.10 - '@iconify/types': 2.0.0 - debug: 4.3.5 - kolorist: 1.8.0 - local-pkg: 0.5.0 - mlly: 1.7.1 - transitivePeerDependencies: - - supports-color - - '@intlify/bundle-utils@8.0.0(vue-i18n@9.13.1(vue@3.4.31(typescript@5.4.5)))': - dependencies: - '@intlify/message-compiler': 9.13.1 - '@intlify/shared': 9.13.1 - acorn: 8.12.1 + '@intlify/message-compiler': 11.0.0-rc.1 + '@intlify/shared': 11.0.0-rc.1 + acorn: 8.14.0 escodegen: 2.1.0 estree-walker: 2.0.2 jsonc-eslint-parser: 2.4.0 - mlly: 1.7.1 - source-map-js: 1.2.0 + mlly: 1.7.3 + source-map-js: 1.2.1 yaml-eslint-parser: 1.2.3 optionalDependencies: - vue-i18n: 9.13.1(vue@3.4.31(typescript@5.4.5)) + vue-i18n: 11.0.1(vue@3.5.13(typescript@5.7.2)) - '@intlify/core-base@9.13.1': + '@intlify/core-base@11.0.1': dependencies: - '@intlify/message-compiler': 9.13.1 - '@intlify/shared': 9.13.1 + '@intlify/message-compiler': 11.0.1 + '@intlify/shared': 11.0.1 - '@intlify/eslint-plugin-vue-i18n@3.0.0-next.13(eslint@9.6.0)': + '@intlify/core-base@9.14.2': dependencies: - '@eslint/eslintrc': 3.1.0 - '@intlify/core-base': 9.13.1 - '@intlify/message-compiler': 9.13.1 - debug: 4.3.5 - eslint: 9.6.0 - eslint-compat-utils: 0.5.1(eslint@9.6.0) - glob: 10.4.2 - globals: 15.8.0 - ignore: 5.3.1 + '@intlify/message-compiler': 9.14.2 + '@intlify/shared': 9.14.2 + + '@intlify/eslint-plugin-vue-i18n@3.2.0(eslint@9.17.0(jiti@2.4.2))': + dependencies: + '@eslint/eslintrc': 3.2.0 + '@intlify/core-base': 9.14.2 + '@intlify/message-compiler': 9.14.2 + debug: 4.4.0 + eslint: 9.17.0(jiti@2.4.2) + eslint-compat-utils: 0.6.4(eslint@9.17.0(jiti@2.4.2)) + glob: 10.4.5 + globals: 15.14.0 + ignore: 6.0.2 import-fresh: 3.3.0 is-language-code: 3.1.0 js-yaml: 4.1.0 json5: 2.2.3 jsonc-eslint-parser: 2.4.0 lodash: 4.17.21 - parse5: 7.1.2 - semver: 7.6.2 - synckit: 0.9.0 - vue-eslint-parser: 9.4.3(eslint@9.6.0) + parse5: 7.2.1 + semver: 7.6.3 + synckit: 0.9.2 + vue-eslint-parser: 9.4.3(eslint@9.17.0(jiti@2.4.2)) yaml-eslint-parser: 1.2.3 transitivePeerDependencies: - supports-color - '@intlify/message-compiler@9.13.1': + '@intlify/message-compiler@11.0.0-rc.1': dependencies: - '@intlify/shared': 9.13.1 - source-map-js: 1.2.0 + '@intlify/shared': 11.0.0-rc.1 + source-map-js: 1.2.1 - '@intlify/shared@9.13.1': {} - - '@intlify/unplugin-vue-i18n@4.0.0(rollup@4.18.0)(vue-i18n@9.13.1(vue@3.4.31(typescript@5.4.5)))': + '@intlify/message-compiler@11.0.1': dependencies: - '@intlify/bundle-utils': 8.0.0(vue-i18n@9.13.1(vue@3.4.31(typescript@5.4.5))) - '@intlify/shared': 9.13.1 - '@rollup/pluginutils': 5.1.0(rollup@4.18.0) - '@vue/compiler-sfc': 3.4.31 - debug: 4.3.5 - fast-glob: 3.3.2 + '@intlify/shared': 11.0.1 + source-map-js: 1.2.1 + + '@intlify/message-compiler@9.14.2': + dependencies: + '@intlify/shared': 9.14.2 + source-map-js: 1.2.1 + + '@intlify/shared@11.0.0-rc.1': {} + + '@intlify/shared@11.0.1': {} + + '@intlify/shared@9.14.2': {} + + '@intlify/unplugin-vue-i18n@6.0.3(@vue/compiler-dom@3.5.13)(eslint@9.17.0(jiti@2.4.2))(rollup@4.30.0)(typescript@5.7.2)(vue-i18n@11.0.1(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))': + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@2.4.2)) + '@intlify/bundle-utils': 10.0.0(vue-i18n@11.0.1(vue@3.5.13(typescript@5.7.2))) + '@intlify/shared': 11.0.1 + '@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.0.1)(@vue/compiler-dom@3.5.13)(vue-i18n@11.0.1(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2)) + '@rollup/pluginutils': 5.1.4(rollup@4.30.0) + '@typescript-eslint/scope-manager': 8.19.0 + '@typescript-eslint/typescript-estree': 8.19.0(typescript@5.7.2) + debug: 4.4.0 + fast-glob: 3.3.3 js-yaml: 4.1.0 json5: 2.2.3 pathe: 1.1.2 - picocolors: 1.0.1 - source-map-js: 1.2.0 - unplugin: 1.11.0 + picocolors: 1.1.1 + source-map-js: 1.2.1 + unplugin: 1.16.0 + vue: 3.5.13(typescript@5.7.2) optionalDependencies: - vue-i18n: 9.13.1(vue@3.4.31(typescript@5.4.5)) + vue-i18n: 11.0.1(vue@3.5.13(typescript@5.7.2)) transitivePeerDependencies: + - '@vue/compiler-dom' + - eslint - rollup - supports-color + - typescript + + '@intlify/vue-i18n-extensions@8.0.0(@intlify/shared@11.0.1)(@vue/compiler-dom@3.5.13)(vue-i18n@11.0.1(vue@3.5.13(typescript@5.7.2)))(vue@3.5.13(typescript@5.7.2))': + dependencies: + '@babel/parser': 7.26.3 + optionalDependencies: + '@intlify/shared': 11.0.1 + '@vue/compiler-dom': 3.5.13 + vue: 3.5.13(typescript@5.7.2) + vue-i18n: 11.0.1(vue@3.5.13(typescript@5.7.2)) '@isaacs/cliui@8.0.2': dependencies: @@ -3340,37 +3921,33 @@ snapshots: wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 - '@jest/schemas@29.6.3': - dependencies: - '@sinclair/typebox': 0.27.8 - - '@jridgewell/gen-mapping@0.3.5': + '@jridgewell/gen-mapping@0.3.8': dependencies: '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 '@jridgewell/trace-mapping': 0.3.25 '@jridgewell/resolve-uri@3.1.2': {} '@jridgewell/set-array@1.2.1': {} - '@jridgewell/sourcemap-codec@1.4.15': {} + '@jridgewell/sourcemap-codec@1.5.0': {} '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 - '@jsdevtools/ez-spawn@3.0.4': + '@jridgewell/trace-mapping@0.3.9': dependencies: - call-me-maybe: 1.0.2 - cross-spawn: 7.0.3 - string-argv: 0.3.2 - type-detect: 4.0.8 + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.0 - '@kyvg/vue3-notification@3.2.1(vue@3.4.31(typescript@5.4.5))': + '@kyvg/vue3-notification@3.4.1(vue@3.5.13(typescript@5.7.2))': dependencies: - vue: 3.4.31(typescript@5.4.5) + vue: 3.5.13(typescript@5.7.2) + + '@mdi/js@7.4.47': {} '@nodelib/fs.scandir@2.1.5': dependencies: @@ -3382,7 +3959,7 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.17.1 + fastq: 1.18.0 '@one-ini/wasm@0.1.1': {} @@ -3391,411 +3968,470 @@ snapshots: '@pkgr/core@0.1.1': {} - '@rollup/pluginutils@5.1.0(rollup@4.18.0)': + '@rollup/pluginutils@5.1.4(rollup@4.30.0)': dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 estree-walker: 2.0.2 - picomatch: 2.3.1 + picomatch: 4.0.2 optionalDependencies: - rollup: 4.18.0 + rollup: 4.30.0 - '@rollup/rollup-android-arm-eabi@4.18.0': + '@rollup/rollup-android-arm-eabi@4.30.0': optional: true - '@rollup/rollup-android-arm64@4.18.0': + '@rollup/rollup-android-arm64@4.30.0': optional: true - '@rollup/rollup-darwin-arm64@4.18.0': + '@rollup/rollup-darwin-arm64@4.30.0': optional: true - '@rollup/rollup-darwin-x64@4.18.0': + '@rollup/rollup-darwin-x64@4.30.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.18.0': + '@rollup/rollup-freebsd-arm64@4.30.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.18.0': + '@rollup/rollup-freebsd-x64@4.30.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.18.0': + '@rollup/rollup-linux-arm-gnueabihf@4.30.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.18.0': + '@rollup/rollup-linux-arm-musleabihf@4.30.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.18.0': + '@rollup/rollup-linux-arm64-gnu@4.30.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.18.0': + '@rollup/rollup-linux-arm64-musl@4.30.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.18.0': + '@rollup/rollup-linux-loongarch64-gnu@4.30.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.18.0': + '@rollup/rollup-linux-powerpc64le-gnu@4.30.0': optional: true - '@rollup/rollup-linux-x64-musl@4.18.0': + '@rollup/rollup-linux-riscv64-gnu@4.30.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.18.0': + '@rollup/rollup-linux-s390x-gnu@4.30.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.18.0': + '@rollup/rollup-linux-x64-gnu@4.30.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.18.0': + '@rollup/rollup-linux-x64-musl@4.30.0': optional: true - '@sinclair/typebox@0.27.8': {} + '@rollup/rollup-win32-arm64-msvc@4.30.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.30.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.30.0': + optional: true '@sindresorhus/is@4.6.0': {} - '@stylistic/eslint-plugin-js@2.3.0(eslint@9.6.0)': + '@stylistic/eslint-plugin@2.12.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2)': dependencies: - '@types/eslint': 8.56.10 - acorn: 8.12.1 - eslint: 9.6.0 - eslint-visitor-keys: 4.0.0 - espree: 10.1.0 - - '@stylistic/eslint-plugin-jsx@2.3.0(eslint@9.6.0)': - dependencies: - '@stylistic/eslint-plugin-js': 2.3.0(eslint@9.6.0) - '@types/eslint': 8.56.10 - eslint: 9.6.0 + '@typescript-eslint/utils': 8.19.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2) + eslint: 9.17.0(jiti@2.4.2) + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 estraverse: 5.3.0 picomatch: 4.0.2 - - '@stylistic/eslint-plugin-plus@2.3.0(eslint@9.6.0)(typescript@5.4.5)': - dependencies: - '@types/eslint': 8.56.10 - '@typescript-eslint/utils': 7.15.0(eslint@9.6.0)(typescript@5.4.5) - eslint: 9.6.0 transitivePeerDependencies: - supports-color - typescript - '@stylistic/eslint-plugin-ts@2.3.0(eslint@9.6.0)(typescript@5.4.5)': + '@tailwindcss/node@4.0.0-beta.8': dependencies: - '@stylistic/eslint-plugin-js': 2.3.0(eslint@9.6.0) - '@types/eslint': 8.56.10 - '@typescript-eslint/utils': 7.15.0(eslint@9.6.0)(typescript@5.4.5) - eslint: 9.6.0 - transitivePeerDependencies: - - supports-color - - typescript + enhanced-resolve: 5.18.0 + jiti: 2.4.2 + tailwindcss: 4.0.0-beta.8 - '@stylistic/eslint-plugin@2.3.0(eslint@9.6.0)(typescript@5.4.5)': + '@tailwindcss/oxide-android-arm64@4.0.0-beta.8': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.0.0-beta.8': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.0.0-beta.8': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.0.0-beta.8': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.0.0-beta.8': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.0.0-beta.8': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.0.0-beta.8': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.0.0-beta.8': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.0.0-beta.8': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.0.0-beta.8': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.0.0-beta.8': + optional: true + + '@tailwindcss/oxide@4.0.0-beta.8': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.0.0-beta.8 + '@tailwindcss/oxide-darwin-arm64': 4.0.0-beta.8 + '@tailwindcss/oxide-darwin-x64': 4.0.0-beta.8 + '@tailwindcss/oxide-freebsd-x64': 4.0.0-beta.8 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.0.0-beta.8 + '@tailwindcss/oxide-linux-arm64-gnu': 4.0.0-beta.8 + '@tailwindcss/oxide-linux-arm64-musl': 4.0.0-beta.8 + '@tailwindcss/oxide-linux-x64-gnu': 4.0.0-beta.8 + '@tailwindcss/oxide-linux-x64-musl': 4.0.0-beta.8 + '@tailwindcss/oxide-win32-arm64-msvc': 4.0.0-beta.8 + '@tailwindcss/oxide-win32-x64-msvc': 4.0.0-beta.8 + + '@tailwindcss/postcss@4.0.0-beta.8': dependencies: - '@stylistic/eslint-plugin-js': 2.3.0(eslint@9.6.0) - '@stylistic/eslint-plugin-jsx': 2.3.0(eslint@9.6.0) - '@stylistic/eslint-plugin-plus': 2.3.0(eslint@9.6.0)(typescript@5.4.5) - '@stylistic/eslint-plugin-ts': 2.3.0(eslint@9.6.0)(typescript@5.4.5) - '@types/eslint': 8.56.10 - eslint: 9.6.0 - transitivePeerDependencies: - - supports-color - - typescript + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.0.0-beta.8 + '@tailwindcss/oxide': 4.0.0-beta.8 + lightningcss: 1.28.2 + postcss: 8.4.49 + tailwindcss: 4.0.0-beta.8 + + '@tailwindcss/typography@0.5.15(tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.7.2)))': + dependencies: + lodash.castarray: 4.4.0 + lodash.isplainobject: 4.0.6 + lodash.merge: 4.6.2 + postcss-selector-parser: 6.0.10 + tailwindcss: 3.4.17(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.7.2)) + + '@tailwindcss/vite@4.0.0-beta.8(vite@6.0.7(@types/node@22.10.5)(jiti@2.4.2)(lightningcss@1.28.2)(stylus@0.57.0)(yaml@2.7.0))': + dependencies: + '@tailwindcss/node': 4.0.0-beta.8 + '@tailwindcss/oxide': 4.0.0-beta.8 + lightningcss: 1.28.2 + tailwindcss: 4.0.0-beta.8 + vite: 6.0.7(@types/node@22.10.5)(jiti@2.4.2)(lightningcss@1.28.2)(stylus@0.57.0)(yaml@2.7.0) '@trysound/sax@0.2.0': {} - '@types/eslint@8.56.10': + '@tsconfig/node10@1.0.11': {} + + '@tsconfig/node12@1.0.11': {} + + '@tsconfig/node14@1.0.3': {} + + '@tsconfig/node16@1.0.4': {} + + '@types/debug@4.1.12': dependencies: - '@types/estree': 1.0.5 + '@types/ms': 0.7.34 + + '@types/doctrine@0.0.9': {} + + '@types/eslint@9.6.1': + dependencies: + '@types/estree': 1.0.6 '@types/json-schema': 7.0.15 '@types/eslint__js@8.42.3': dependencies: - '@types/eslint': 8.56.10 + '@types/eslint': 9.6.1 - '@types/estree@1.0.5': {} + '@types/estree@1.0.6': {} '@types/json-schema@7.0.15': {} - '@types/lodash@4.17.6': {} + '@types/lodash@4.17.14': {} - '@types/mdast@3.0.15': + '@types/mdast@4.0.4': dependencies: - '@types/unist': 2.0.10 + '@types/unist': 3.0.3 - '@types/node-emoji@2.1.0': - dependencies: - node-emoji: 2.1.3 + '@types/ms@0.7.34': {} - '@types/node@20.14.10': + '@types/node@22.10.5': dependencies: - undici-types: 5.26.5 + undici-types: 6.20.0 '@types/normalize-package-data@2.4.4': {} - '@types/prismjs@1.26.4': {} + '@types/prismjs@1.26.5': {} '@types/semver@7.5.8': {} '@types/tinycolor2@1.4.6': {} - '@types/unist@2.0.10': {} + '@types/trusted-types@2.0.7': + optional: true + + '@types/unist@3.0.3': {} '@types/web-bluetooth@0.0.20': {} - '@typescript-eslint/eslint-plugin@7.15.0(@typescript-eslint/parser@7.15.0(eslint@9.6.0)(typescript@5.4.5))(eslint@9.6.0)(typescript@5.4.5)': + '@typescript-eslint/eslint-plugin@8.19.0(@typescript-eslint/parser@8.19.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2))(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2)': dependencies: - '@eslint-community/regexpp': 4.11.0 - '@typescript-eslint/parser': 7.15.0(eslint@9.6.0)(typescript@5.4.5) - '@typescript-eslint/scope-manager': 7.15.0 - '@typescript-eslint/type-utils': 7.15.0(eslint@9.6.0)(typescript@5.4.5) - '@typescript-eslint/utils': 7.15.0(eslint@9.6.0)(typescript@5.4.5) - '@typescript-eslint/visitor-keys': 7.15.0 - eslint: 9.6.0 + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.19.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2) + '@typescript-eslint/scope-manager': 8.19.0 + '@typescript-eslint/type-utils': 8.19.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2) + '@typescript-eslint/utils': 8.19.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2) + '@typescript-eslint/visitor-keys': 8.19.0 + eslint: 9.17.0(jiti@2.4.2) graphemer: 1.4.0 - ignore: 5.3.1 + ignore: 5.3.2 natural-compare: 1.4.0 - ts-api-utils: 1.3.0(typescript@5.4.5) - optionalDependencies: - typescript: 5.4.5 + ts-api-utils: 1.4.3(typescript@5.7.2) + typescript: 5.7.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.15.0(eslint@9.6.0)(typescript@5.4.5)': + '@typescript-eslint/parser@8.19.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2)': dependencies: - '@typescript-eslint/scope-manager': 7.15.0 - '@typescript-eslint/types': 7.15.0 - '@typescript-eslint/typescript-estree': 7.15.0(typescript@5.4.5) - '@typescript-eslint/visitor-keys': 7.15.0 - debug: 4.3.5 - eslint: 9.6.0 - optionalDependencies: - typescript: 5.4.5 + '@typescript-eslint/scope-manager': 8.19.0 + '@typescript-eslint/types': 8.19.0 + '@typescript-eslint/typescript-estree': 8.19.0(typescript@5.7.2) + '@typescript-eslint/visitor-keys': 8.19.0 + debug: 4.4.0 + eslint: 9.17.0(jiti@2.4.2) + typescript: 5.7.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@7.15.0': + '@typescript-eslint/scope-manager@8.19.0': dependencies: - '@typescript-eslint/types': 7.15.0 - '@typescript-eslint/visitor-keys': 7.15.0 + '@typescript-eslint/types': 8.19.0 + '@typescript-eslint/visitor-keys': 8.19.0 - '@typescript-eslint/type-utils@7.15.0(eslint@9.6.0)(typescript@5.4.5)': + '@typescript-eslint/type-utils@8.19.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2)': dependencies: - '@typescript-eslint/typescript-estree': 7.15.0(typescript@5.4.5) - '@typescript-eslint/utils': 7.15.0(eslint@9.6.0)(typescript@5.4.5) - debug: 4.3.5 - eslint: 9.6.0 - ts-api-utils: 1.3.0(typescript@5.4.5) - optionalDependencies: - typescript: 5.4.5 + '@typescript-eslint/typescript-estree': 8.19.0(typescript@5.7.2) + '@typescript-eslint/utils': 8.19.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2) + debug: 4.4.0 + eslint: 9.17.0(jiti@2.4.2) + ts-api-utils: 1.4.3(typescript@5.7.2) + typescript: 5.7.2 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@7.15.0': {} + '@typescript-eslint/types@8.19.0': {} - '@typescript-eslint/typescript-estree@7.15.0(typescript@5.4.5)': + '@typescript-eslint/typescript-estree@8.19.0(typescript@5.7.2)': dependencies: - '@typescript-eslint/types': 7.15.0 - '@typescript-eslint/visitor-keys': 7.15.0 - debug: 4.3.5 - globby: 11.1.0 + '@typescript-eslint/types': 8.19.0 + '@typescript-eslint/visitor-keys': 8.19.0 + debug: 4.4.0 + fast-glob: 3.3.3 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.6.2 - ts-api-utils: 1.3.0(typescript@5.4.5) + semver: 7.6.3 + ts-api-utils: 1.4.3(typescript@5.7.2) + typescript: 5.7.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.19.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2)': + dependencies: + '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.19.0 + '@typescript-eslint/types': 8.19.0 + '@typescript-eslint/typescript-estree': 8.19.0(typescript@5.7.2) + eslint: 9.17.0(jiti@2.4.2) + typescript: 5.7.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.19.0': + dependencies: + '@typescript-eslint/types': 8.19.0 + eslint-visitor-keys: 4.2.0 + + '@vitejs/plugin-vue@5.2.1(vite@6.0.7(@types/node@22.10.5)(jiti@2.4.2)(lightningcss@1.28.2)(stylus@0.57.0)(yaml@2.7.0))(vue@3.5.13(typescript@5.7.2))': + dependencies: + vite: 6.0.7(@types/node@22.10.5)(jiti@2.4.2)(lightningcss@1.28.2)(stylus@0.57.0)(yaml@2.7.0) + vue: 3.5.13(typescript@5.7.2) + + '@vitest/eslint-plugin@1.1.24(@typescript-eslint/utils@8.19.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2))(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2)(vitest@2.1.8(@types/node@22.10.5)(jsdom@25.0.1)(lightningcss@1.28.2)(stylus@0.57.0))': + dependencies: + '@typescript-eslint/utils': 8.19.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2) + eslint: 9.17.0(jiti@2.4.2) optionalDependencies: - typescript: 5.4.5 - transitivePeerDependencies: - - supports-color + typescript: 5.7.2 + vitest: 2.1.8(@types/node@22.10.5)(jsdom@25.0.1)(lightningcss@1.28.2)(stylus@0.57.0) - '@typescript-eslint/utils@7.15.0(eslint@9.6.0)(typescript@5.4.5)': + '@vitest/expect@2.1.8': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.6.0) - '@typescript-eslint/scope-manager': 7.15.0 - '@typescript-eslint/types': 7.15.0 - '@typescript-eslint/typescript-estree': 7.15.0(typescript@5.4.5) - eslint: 9.6.0 - transitivePeerDependencies: - - supports-color - - typescript + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 + chai: 5.1.2 + tinyrainbow: 1.2.0 - '@typescript-eslint/visitor-keys@7.15.0': + '@vitest/mocker@2.1.8(vite@5.4.11(@types/node@22.10.5)(lightningcss@1.28.2)(stylus@0.57.0))': dependencies: - '@typescript-eslint/types': 7.15.0 - eslint-visitor-keys: 3.4.3 - - '@vitejs/plugin-vue@5.0.5(vite@5.3.3(@types/node@20.14.10)(stylus@0.57.0))(vue@3.4.31(typescript@5.4.5))': - dependencies: - vite: 5.3.3(@types/node@20.14.10)(stylus@0.57.0) - vue: 3.4.31(typescript@5.4.5) - - '@vitest/expect@1.6.0': - dependencies: - '@vitest/spy': 1.6.0 - '@vitest/utils': 1.6.0 - chai: 4.4.1 - - '@vitest/runner@1.6.0': - dependencies: - '@vitest/utils': 1.6.0 - p-limit: 5.0.0 - pathe: 1.1.2 - - '@vitest/snapshot@1.6.0': - dependencies: - magic-string: 0.30.10 - pathe: 1.1.2 - pretty-format: 29.7.0 - - '@vitest/spy@1.6.0': - dependencies: - tinyspy: 2.2.1 - - '@vitest/utils@1.6.0': - dependencies: - diff-sequences: 29.6.3 + '@vitest/spy': 2.1.8 estree-walker: 3.0.3 - loupe: 2.3.7 - pretty-format: 29.7.0 + magic-string: 0.30.17 + optionalDependencies: + vite: 5.4.11(@types/node@22.10.5)(lightningcss@1.28.2)(stylus@0.57.0) - '@volar/language-core@2.4.0-alpha.15': + '@vitest/pretty-format@2.1.8': dependencies: - '@volar/source-map': 2.4.0-alpha.15 + tinyrainbow: 1.2.0 - '@volar/source-map@2.4.0-alpha.15': {} - - '@volar/typescript@2.4.0-alpha.15': + '@vitest/runner@2.1.8': dependencies: - '@volar/language-core': 2.4.0-alpha.15 + '@vitest/utils': 2.1.8 + pathe: 1.1.2 + + '@vitest/snapshot@2.1.8': + dependencies: + '@vitest/pretty-format': 2.1.8 + magic-string: 0.30.17 + pathe: 1.1.2 + + '@vitest/spy@2.1.8': + dependencies: + tinyspy: 3.0.2 + + '@vitest/utils@2.1.8': + dependencies: + '@vitest/pretty-format': 2.1.8 + loupe: 3.1.2 + tinyrainbow: 1.2.0 + + '@volar/language-core@2.4.11': + dependencies: + '@volar/source-map': 2.4.11 + + '@volar/source-map@2.4.11': {} + + '@volar/typescript@2.4.11': + dependencies: + '@volar/language-core': 2.4.11 path-browserify: 1.0.1 vscode-uri: 3.0.8 - '@vue/compiler-core@3.4.31': + '@vue/compiler-core@3.5.13': dependencies: - '@babel/parser': 7.24.7 - '@vue/shared': 3.4.31 + '@babel/parser': 7.26.3 + '@vue/shared': 3.5.13 entities: 4.5.0 estree-walker: 2.0.2 - source-map-js: 1.2.0 + source-map-js: 1.2.1 - '@vue/compiler-dom@3.4.31': + '@vue/compiler-dom@3.5.13': dependencies: - '@vue/compiler-core': 3.4.31 - '@vue/shared': 3.4.31 + '@vue/compiler-core': 3.5.13 + '@vue/shared': 3.5.13 - '@vue/compiler-sfc@3.4.31': + '@vue/compiler-sfc@3.5.13': dependencies: - '@babel/parser': 7.24.7 - '@vue/compiler-core': 3.4.31 - '@vue/compiler-dom': 3.4.31 - '@vue/compiler-ssr': 3.4.31 - '@vue/shared': 3.4.31 + '@babel/parser': 7.26.3 + '@vue/compiler-core': 3.5.13 + '@vue/compiler-dom': 3.5.13 + '@vue/compiler-ssr': 3.5.13 + '@vue/shared': 3.5.13 estree-walker: 2.0.2 - magic-string: 0.30.10 - postcss: 8.4.39 - source-map-js: 1.2.0 + magic-string: 0.30.17 + postcss: 8.4.49 + source-map-js: 1.2.1 - '@vue/compiler-ssr@3.4.31': + '@vue/compiler-ssr@3.5.13': dependencies: - '@vue/compiler-dom': 3.4.31 - '@vue/shared': 3.4.31 + '@vue/compiler-dom': 3.5.13 + '@vue/shared': 3.5.13 - '@vue/devtools-api@6.6.3': {} - - '@vue/language-core@2.0.26(typescript@5.4.5)': + '@vue/compiler-vue2@2.7.16': dependencies: - '@volar/language-core': 2.4.0-alpha.15 - '@vue/compiler-dom': 3.4.31 - '@vue/shared': 3.4.31 - computeds: 0.0.1 + de-indent: 1.0.2 + he: 1.2.0 + + '@vue/devtools-api@6.6.4': {} + + '@vue/language-core@2.2.0(typescript@5.7.2)': + dependencies: + '@volar/language-core': 2.4.11 + '@vue/compiler-dom': 3.5.13 + '@vue/compiler-vue2': 2.7.16 + '@vue/shared': 3.5.13 + alien-signals: 0.4.12 minimatch: 9.0.5 muggle-string: 0.4.1 path-browserify: 1.0.1 - vue-template-compiler: 2.7.16 optionalDependencies: - typescript: 5.4.5 + typescript: 5.7.2 - '@vue/reactivity@3.4.31': + '@vue/reactivity@3.5.13': dependencies: - '@vue/shared': 3.4.31 + '@vue/shared': 3.5.13 - '@vue/runtime-core@3.4.31': + '@vue/runtime-core@3.5.13': dependencies: - '@vue/reactivity': 3.4.31 - '@vue/shared': 3.4.31 + '@vue/reactivity': 3.5.13 + '@vue/shared': 3.5.13 - '@vue/runtime-dom@3.4.31': + '@vue/runtime-dom@3.5.13': dependencies: - '@vue/reactivity': 3.4.31 - '@vue/runtime-core': 3.4.31 - '@vue/shared': 3.4.31 + '@vue/reactivity': 3.5.13 + '@vue/runtime-core': 3.5.13 + '@vue/shared': 3.5.13 csstype: 3.1.3 - '@vue/server-renderer@3.4.31(vue@3.4.31(typescript@5.4.5))': + '@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.7.2))': dependencies: - '@vue/compiler-ssr': 3.4.31 - '@vue/shared': 3.4.31 - vue: 3.4.31(typescript@5.4.5) + '@vue/compiler-ssr': 3.5.13 + '@vue/shared': 3.5.13 + vue: 3.5.13(typescript@5.7.2) - '@vue/shared@3.4.31': {} + '@vue/shared@3.5.13': {} '@vue/test-utils@2.4.6': dependencies: js-beautify: 1.15.1 - vue-component-type-helpers: 2.0.26 + vue-component-type-helpers: 2.2.0 - '@vueuse/core@10.11.0(vue@3.4.31(typescript@5.4.5))': + '@vueuse/core@12.3.0(typescript@5.7.2)': dependencies: '@types/web-bluetooth': 0.0.20 - '@vueuse/metadata': 10.11.0 - '@vueuse/shared': 10.11.0(vue@3.4.31(typescript@5.4.5)) - vue-demi: 0.14.8(vue@3.4.31(typescript@5.4.5)) + '@vueuse/metadata': 12.3.0 + '@vueuse/shared': 12.3.0(typescript@5.7.2) + vue: 3.5.13(typescript@5.7.2) transitivePeerDependencies: - - '@vue/composition-api' - - vue + - typescript - '@vueuse/metadata@10.11.0': {} + '@vueuse/metadata@12.3.0': {} - '@vueuse/shared@10.11.0(vue@3.4.31(typescript@5.4.5))': + '@vueuse/shared@12.3.0(typescript@5.7.2)': dependencies: - vue-demi: 0.14.8(vue@3.4.31(typescript@5.4.5)) + vue: 3.5.13(typescript@5.7.2) transitivePeerDependencies: - - '@vue/composition-api' - - vue - - '@windicss/config@1.9.3': - dependencies: - debug: 4.3.5 - jiti: 1.21.6 - windicss: 3.5.6 - transitivePeerDependencies: - - supports-color - - '@windicss/plugin-utils@1.9.3': - dependencies: - '@antfu/utils': 0.7.10 - '@windicss/config': 1.9.3 - debug: 4.3.5 - fast-glob: 3.3.2 - magic-string: 0.30.10 - micromatch: 4.0.7 - windicss: 3.5.6 - transitivePeerDependencies: - - supports-color + - typescript abbrev@2.0.0: {} - acorn-jsx@5.3.2(acorn@8.12.1): + acorn-jsx@5.3.2(acorn@8.14.0): dependencies: - acorn: 8.12.1 + acorn: 8.14.0 - acorn-walk@8.3.3: + acorn-walk@8.3.4: dependencies: - acorn: 8.12.1 + acorn: 8.14.0 - acorn@8.12.1: {} + acorn@8.14.0: {} - agent-base@7.1.1: - dependencies: - debug: 4.3.5 - transitivePeerDependencies: - - supports-color + agent-base@7.1.3: {} ajv@6.12.6: dependencies: @@ -3804,24 +4440,22 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + alien-signals@0.4.12: {} + ansi-regex@5.0.1: {} - ansi-regex@6.0.1: {} - - ansi-styles@3.2.1: - dependencies: - color-convert: 1.9.3 + ansi-regex@6.1.0: {} ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 - ansi-styles@5.2.0: {} - ansi-styles@6.2.1: {} ansi_up@6.0.2: {} + any-promise@1.3.0: {} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 @@ -3829,16 +4463,28 @@ snapshots: are-docs-informative@0.0.2: {} + arg@4.1.3: {} + + arg@5.0.2: {} + argparse@2.0.1: {} - array-union@2.1.0: {} - - assertion-error@1.1.0: {} + assertion-error@2.0.1: {} asynckit@0.4.0: {} atob@2.1.2: {} + autoprefixer@10.4.20(postcss@8.4.49): + dependencies: + browserslist: 4.24.3 + caniuse-lite: 1.0.30001690 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.1.1 + postcss: 8.4.49 + postcss-value-parser: 4.2.0 + babel-plugin-prismjs@2.1.0(prismjs@1.29.0): dependencies: prismjs: 1.29.0 @@ -3862,57 +4508,43 @@ snapshots: dependencies: fill-range: 7.1.1 - browserslist@4.23.1: + browserslist@4.24.3: dependencies: - caniuse-lite: 1.0.30001640 - electron-to-chromium: 1.4.817 - node-releases: 2.0.14 - update-browserslist-db: 1.1.0(browserslist@4.23.1) + caniuse-lite: 1.0.30001690 + electron-to-chromium: 1.5.76 + node-releases: 2.0.19 + update-browserslist-db: 1.1.1(browserslist@4.24.3) builtin-modules@3.3.0: {} cac@6.7.14: {} - call-me-maybe@1.0.2: {} - callsites@3.1.0: {} - caniuse-lite@1.0.30001640: {} + camelcase-css@2.0.1: {} - chai@4.4.1: - dependencies: - assertion-error: 1.1.0 - check-error: 1.0.3 - deep-eql: 4.1.4 - get-func-name: 2.0.2 - loupe: 2.3.7 - pathval: 1.1.1 - type-detect: 4.0.8 + caniuse-lite@1.0.30001690: {} - chalk@2.4.2: + ccount@2.0.1: {} + + chai@5.1.2: dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.1.2 + pathval: 2.0.0 chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - chalk@5.3.0: {} - char-regex@1.0.2: {} - character-entities-legacy@1.1.4: {} + character-entities@2.0.2: {} - character-entities@1.2.4: {} - - character-reference-invalid@1.1.4: {} - - check-error@1.0.3: - dependencies: - get-func-name: 2.0.2 + check-error@2.1.1: {} chokidar@3.6.0: dependencies: @@ -3926,7 +4558,7 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - ci-info@4.0.0: {} + ci-info@4.1.0: {} clean-regexp@1.0.0: dependencies: @@ -3938,16 +4570,10 @@ snapshots: strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - color-convert@1.9.3: - dependencies: - color-name: 1.1.3 - color-convert@2.0.1: dependencies: color-name: 1.1.4 - color-name@1.1.3: {} - color-name@1.1.4: {} combined-stream@1.0.8: @@ -3956,15 +4582,15 @@ snapshots: commander@10.0.1: {} + commander@4.1.1: {} + commander@7.2.0: {} comment-parser@1.4.1: {} - computeds@0.0.1: {} - concat-map@0.0.1: {} - confbox@0.1.7: {} + confbox@0.1.8: {} config-chain@1.1.13: dependencies: @@ -3973,11 +4599,13 @@ snapshots: convert-source-map@2.0.0: {} - core-js-compat@3.37.1: + core-js-compat@3.39.0: dependencies: - browserslist: 4.23.1 + browserslist: 4.24.3 - cross-spawn@7.0.3: + create-require@1.1.1: {} + + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 shebang-command: 2.0.0 @@ -3988,18 +4616,18 @@ snapshots: boolbase: 1.0.0 css-what: 6.1.0 domhandler: 5.0.3 - domutils: 3.1.0 + domutils: 3.2.1 nth-check: 2.1.1 css-tree@2.2.1: dependencies: mdn-data: 2.0.28 - source-map-js: 1.2.0 + source-map-js: 1.2.1 css-tree@2.3.1: dependencies: mdn-data: 2.0.30 - source-map-js: 1.2.0 + source-map-js: 1.2.1 css-what@6.1.0: {} @@ -4015,18 +4643,16 @@ snapshots: dependencies: css-tree: 2.2.1 - cssstyle@4.0.1: + cssstyle@4.1.0: dependencies: - rrweb-cssom: 0.6.0 + rrweb-cssom: 0.7.1 csstype@3.1.3: {} data-urls@5.0.0: dependencies: whatwg-mimetype: 4.0.0 - whatwg-url: 14.0.0 - - dayjs@1.11.11: {} + whatwg-url: 14.1.0 de-indent@1.0.2: {} @@ -4034,27 +4660,37 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.3.5: + debug@4.4.0: dependencies: - ms: 2.1.2 + ms: 2.1.3 decimal.js@10.4.3: {} + decode-named-character-reference@1.0.2: + dependencies: + character-entities: 2.0.2 + decode-uri-component@0.2.2: {} - deep-eql@4.1.4: - dependencies: - type-detect: 4.0.8 + deep-eql@5.0.2: {} deep-is@0.1.4: {} delayed-stream@1.0.0: {} - diff-sequences@29.6.3: {} + dequal@2.0.3: {} - dir-glob@3.0.1: + detect-libc@1.0.3: {} + + devlop@1.1.0: dependencies: - path-type: 4.0.0 + dequal: 2.0.3 + + didyoumean@1.2.2: {} + + diff@4.0.2: {} + + dlv@1.1.3: {} doctrine@3.0.0: dependencies: @@ -4072,7 +4708,11 @@ snapshots: dependencies: domelementtype: 2.3.0 - domutils@3.1.0: + dompurify@3.2.3: + optionalDependencies: + '@types/trusted-types': 2.0.7 + + domutils@3.2.1: dependencies: dom-serializer: 2.0.0 domelementtype: 2.3.0 @@ -4085,9 +4725,9 @@ snapshots: '@one-ini/wasm': 0.1.1 commander: 10.0.1 minimatch: 9.0.1 - semver: 7.6.2 + semver: 7.6.3 - electron-to-chromium@1.4.817: {} + electron-to-chromium@1.5.76: {} emoji-regex@8.0.0: {} @@ -4095,7 +4735,7 @@ snapshots: emojilib@2.4.0: {} - enhanced-resolve@5.17.0: + enhanced-resolve@5.18.0: dependencies: graceful-fs: 4.2.11 tapable: 2.2.1 @@ -4106,7 +4746,7 @@ snapshots: dependencies: is-arrayish: 0.2.1 - es-module-lexer@1.5.4: {} + es-module-lexer@1.6.0: {} esbuild@0.21.5: optionalDependencies: @@ -4134,12 +4774,42 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 - escalade@3.1.2: {} + esbuild@0.24.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.24.2 + '@esbuild/android-arm': 0.24.2 + '@esbuild/android-arm64': 0.24.2 + '@esbuild/android-x64': 0.24.2 + '@esbuild/darwin-arm64': 0.24.2 + '@esbuild/darwin-x64': 0.24.2 + '@esbuild/freebsd-arm64': 0.24.2 + '@esbuild/freebsd-x64': 0.24.2 + '@esbuild/linux-arm': 0.24.2 + '@esbuild/linux-arm64': 0.24.2 + '@esbuild/linux-ia32': 0.24.2 + '@esbuild/linux-loong64': 0.24.2 + '@esbuild/linux-mips64el': 0.24.2 + '@esbuild/linux-ppc64': 0.24.2 + '@esbuild/linux-riscv64': 0.24.2 + '@esbuild/linux-s390x': 0.24.2 + '@esbuild/linux-x64': 0.24.2 + '@esbuild/netbsd-arm64': 0.24.2 + '@esbuild/netbsd-x64': 0.24.2 + '@esbuild/openbsd-arm64': 0.24.2 + '@esbuild/openbsd-x64': 0.24.2 + '@esbuild/sunos-x64': 0.24.2 + '@esbuild/win32-arm64': 0.24.2 + '@esbuild/win32-ia32': 0.24.2 + '@esbuild/win32-x64': 0.24.2 + + escalade@3.2.0: {} escape-string-regexp@1.0.5: {} escape-string-regexp@4.0.0: {} + escape-string-regexp@5.0.0: {} + escodegen@2.1.0: dependencies: esprima: 4.0.1 @@ -4148,313 +4818,303 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-compat-utils@0.5.1(eslint@9.6.0): + eslint-compat-utils@0.5.1(eslint@9.17.0(jiti@2.4.2)): dependencies: - eslint: 9.6.0 - semver: 7.6.2 + eslint: 9.17.0(jiti@2.4.2) + semver: 7.6.3 - eslint-config-flat-gitignore@0.1.5: + eslint-compat-utils@0.6.4(eslint@9.17.0(jiti@2.4.2)): dependencies: - find-up: 7.0.0 - parse-gitignore: 2.0.0 + eslint: 9.17.0(jiti@2.4.2) + semver: 7.6.3 - eslint-flat-config-utils@0.2.5: + eslint-config-flat-gitignore@0.3.0(eslint@9.17.0(jiti@2.4.2)): + dependencies: + '@eslint/compat': 1.2.4(eslint@9.17.0(jiti@2.4.2)) + eslint: 9.17.0(jiti@2.4.2) + find-up-simple: 1.0.0 + + eslint-flat-config-utils@0.4.0: dependencies: - '@types/eslint': 8.56.10 pathe: 1.1.2 eslint-import-resolver-node@0.3.9: dependencies: debug: 3.2.7 - is-core-module: 2.14.0 - resolve: 1.22.8 + is-core-module: 2.16.1 + resolve: 1.22.10 transitivePeerDependencies: - supports-color - eslint-merge-processors@0.1.0(eslint@9.6.0): + eslint-json-compat-utils@0.2.1(eslint@9.17.0(jiti@2.4.2))(jsonc-eslint-parser@2.4.0): dependencies: - eslint: 9.6.0 + eslint: 9.17.0(jiti@2.4.2) + esquery: 1.6.0 + jsonc-eslint-parser: 2.4.0 - eslint-plugin-antfu@2.3.4(eslint@9.6.0): + eslint-merge-processors@0.1.0(eslint@9.17.0(jiti@2.4.2)): + dependencies: + eslint: 9.17.0(jiti@2.4.2) + + eslint-plugin-antfu@2.7.0(eslint@9.17.0(jiti@2.4.2)): dependencies: '@antfu/utils': 0.7.10 - eslint: 9.6.0 + eslint: 9.17.0(jiti@2.4.2) - eslint-plugin-command@0.2.3(eslint@9.6.0): + eslint-plugin-command@0.2.7(eslint@9.17.0(jiti@2.4.2)): dependencies: - '@es-joy/jsdoccomment': 0.43.1 - eslint: 9.6.0 + '@es-joy/jsdoccomment': 0.49.0 + eslint: 9.17.0(jiti@2.4.2) - eslint-plugin-es-x@7.8.0(eslint@9.6.0): + eslint-plugin-es-x@7.8.0(eslint@9.17.0(jiti@2.4.2)): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.6.0) - '@eslint-community/regexpp': 4.11.0 - eslint: 9.6.0 - eslint-compat-utils: 0.5.1(eslint@9.6.0) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@2.4.2)) + '@eslint-community/regexpp': 4.12.1 + eslint: 9.17.0(jiti@2.4.2) + eslint-compat-utils: 0.5.1(eslint@9.17.0(jiti@2.4.2)) - eslint-plugin-eslint-comments@3.2.0(eslint@9.6.0): + eslint-plugin-import-x@4.6.1(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2): dependencies: - escape-string-regexp: 1.0.5 - eslint: 9.6.0 - ignore: 5.3.1 - - eslint-plugin-import-x@0.5.3(eslint@9.6.0)(typescript@5.4.5): - dependencies: - '@typescript-eslint/utils': 7.15.0(eslint@9.6.0)(typescript@5.4.5) - debug: 4.3.5 + '@types/doctrine': 0.0.9 + '@typescript-eslint/scope-manager': 8.19.0 + '@typescript-eslint/utils': 8.19.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2) + debug: 4.4.0 doctrine: 3.0.0 - eslint: 9.6.0 + enhanced-resolve: 5.18.0 + eslint: 9.17.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 - get-tsconfig: 4.7.5 + get-tsconfig: 4.8.1 is-glob: 4.0.3 minimatch: 9.0.5 - semver: 7.6.2 + semver: 7.6.3 stable-hash: 0.0.4 - tslib: 2.6.3 + tslib: 2.8.1 transitivePeerDependencies: - supports-color - typescript - eslint-plugin-jsdoc@48.5.2(eslint@9.6.0): + eslint-plugin-jsdoc@50.6.1(eslint@9.17.0(jiti@2.4.2)): dependencies: - '@es-joy/jsdoccomment': 0.43.1 + '@es-joy/jsdoccomment': 0.49.0 are-docs-informative: 0.0.2 comment-parser: 1.4.1 - debug: 4.3.5 + debug: 4.4.0 escape-string-regexp: 4.0.0 - eslint: 9.6.0 - esquery: 1.5.0 - parse-imports: 2.1.1 - semver: 7.6.2 + eslint: 9.17.0(jiti@2.4.2) + espree: 10.3.0 + esquery: 1.6.0 + parse-imports: 2.2.1 + semver: 7.6.3 spdx-expression-parse: 4.0.0 - synckit: 0.9.0 + synckit: 0.9.2 transitivePeerDependencies: - supports-color - eslint-plugin-jsonc@2.16.0(eslint@9.6.0): + eslint-plugin-jsonc@2.18.2(eslint@9.17.0(jiti@2.4.2)): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.6.0) - eslint: 9.6.0 - eslint-compat-utils: 0.5.1(eslint@9.6.0) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@2.4.2)) + eslint: 9.17.0(jiti@2.4.2) + eslint-compat-utils: 0.6.4(eslint@9.17.0(jiti@2.4.2)) + eslint-json-compat-utils: 0.2.1(eslint@9.17.0(jiti@2.4.2))(jsonc-eslint-parser@2.4.0) espree: 9.6.1 graphemer: 1.4.0 jsonc-eslint-parser: 2.4.0 natural-compare: 1.4.0 synckit: 0.6.2 - - eslint-plugin-markdown@5.0.0(eslint@9.6.0): - dependencies: - eslint: 9.6.0 - mdast-util-from-markdown: 0.8.5 transitivePeerDependencies: - - supports-color + - '@eslint/json' - eslint-plugin-n@17.9.0(eslint@9.6.0): + eslint-plugin-n@17.15.1(eslint@9.17.0(jiti@2.4.2)): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.6.0) - enhanced-resolve: 5.17.0 - eslint: 9.6.0 - eslint-plugin-es-x: 7.8.0(eslint@9.6.0) - get-tsconfig: 4.7.5 - globals: 15.8.0 - ignore: 5.3.1 + '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@2.4.2)) + enhanced-resolve: 5.18.0 + eslint: 9.17.0(jiti@2.4.2) + eslint-plugin-es-x: 7.8.0(eslint@9.17.0(jiti@2.4.2)) + get-tsconfig: 4.8.1 + globals: 15.14.0 + ignore: 5.3.2 minimatch: 9.0.5 - semver: 7.6.2 + semver: 7.6.3 - eslint-plugin-no-only-tests@3.1.0: {} + eslint-plugin-no-only-tests@3.3.0: {} - eslint-plugin-perfectionist@2.11.0(eslint@9.6.0)(typescript@5.4.5)(vue-eslint-parser@9.4.3(eslint@9.6.0)): + eslint-plugin-perfectionist@4.6.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2): dependencies: - '@typescript-eslint/utils': 7.15.0(eslint@9.6.0)(typescript@5.4.5) - eslint: 9.6.0 - minimatch: 9.0.5 - natural-compare-lite: 1.4.0 - optionalDependencies: - vue-eslint-parser: 9.4.3(eslint@9.6.0) + '@typescript-eslint/types': 8.19.0 + '@typescript-eslint/utils': 8.19.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2) + eslint: 9.17.0(jiti@2.4.2) + natural-orderby: 5.0.0 transitivePeerDependencies: - supports-color - typescript - eslint-plugin-promise@6.4.0(eslint@9.6.0): + eslint-plugin-promise@7.2.1(eslint@9.17.0(jiti@2.4.2)): dependencies: - eslint: 9.6.0 + '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@2.4.2)) + eslint: 9.17.0(jiti@2.4.2) - eslint-plugin-regexp@2.6.0(eslint@9.6.0): + eslint-plugin-regexp@2.7.0(eslint@9.17.0(jiti@2.4.2)): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.6.0) - '@eslint-community/regexpp': 4.11.0 + '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@2.4.2)) + '@eslint-community/regexpp': 4.12.1 comment-parser: 1.4.1 - eslint: 9.6.0 - jsdoc-type-pratt-parser: 4.0.0 + eslint: 9.17.0(jiti@2.4.2) + jsdoc-type-pratt-parser: 4.1.0 refa: 0.12.1 regexp-ast-analysis: 0.7.1 scslre: 0.3.0 - eslint-plugin-toml@0.11.1(eslint@9.6.0): + eslint-plugin-toml@0.12.0(eslint@9.17.0(jiti@2.4.2)): dependencies: - debug: 4.3.5 - eslint: 9.6.0 - eslint-compat-utils: 0.5.1(eslint@9.6.0) + debug: 4.4.0 + eslint: 9.17.0(jiti@2.4.2) + eslint-compat-utils: 0.6.4(eslint@9.17.0(jiti@2.4.2)) lodash: 4.17.21 toml-eslint-parser: 0.10.0 transitivePeerDependencies: - supports-color - eslint-plugin-unicorn@54.0.0(eslint@9.6.0): + eslint-plugin-unicorn@56.0.1(eslint@9.17.0(jiti@2.4.2)): dependencies: - '@babel/helper-validator-identifier': 7.24.7 - '@eslint-community/eslint-utils': 4.4.0(eslint@9.6.0) - '@eslint/eslintrc': 3.1.0 - ci-info: 4.0.0 + '@babel/helper-validator-identifier': 7.25.9 + '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@2.4.2)) + ci-info: 4.1.0 clean-regexp: 1.0.0 - core-js-compat: 3.37.1 - eslint: 9.6.0 - esquery: 1.5.0 + core-js-compat: 3.39.0 + eslint: 9.17.0(jiti@2.4.2) + esquery: 1.6.0 + globals: 15.14.0 indent-string: 4.0.0 is-builtin-module: 3.2.1 - jsesc: 3.0.2 + jsesc: 3.1.0 pluralize: 8.0.0 read-pkg-up: 7.0.1 regexp-tree: 0.1.27 regjsparser: 0.10.0 - semver: 7.6.2 + semver: 7.6.3 strip-indent: 3.0.0 - transitivePeerDependencies: - - supports-color - eslint-plugin-unused-imports@3.2.0(@typescript-eslint/eslint-plugin@7.15.0(@typescript-eslint/parser@7.15.0(eslint@9.6.0)(typescript@5.4.5))(eslint@9.6.0)(typescript@5.4.5))(eslint@9.6.0): + eslint-plugin-unused-imports@4.1.4(@typescript-eslint/eslint-plugin@8.19.0(@typescript-eslint/parser@8.19.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2))(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2))(eslint@9.17.0(jiti@2.4.2)): dependencies: - eslint: 9.6.0 - eslint-rule-composer: 0.3.0 + eslint: 9.17.0(jiti@2.4.2) optionalDependencies: - '@typescript-eslint/eslint-plugin': 7.15.0(@typescript-eslint/parser@7.15.0(eslint@9.6.0)(typescript@5.4.5))(eslint@9.6.0)(typescript@5.4.5) + '@typescript-eslint/eslint-plugin': 8.19.0(@typescript-eslint/parser@8.19.0(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2))(eslint@9.17.0(jiti@2.4.2))(typescript@5.7.2) - eslint-plugin-vitest@0.5.4(@typescript-eslint/eslint-plugin@7.15.0(@typescript-eslint/parser@7.15.0(eslint@9.6.0)(typescript@5.4.5))(eslint@9.6.0)(typescript@5.4.5))(eslint@9.6.0)(typescript@5.4.5)(vitest@1.6.0(@types/node@20.14.10)(jsdom@24.1.0)(stylus@0.57.0)): + eslint-plugin-vue-scoped-css@2.9.0(eslint@9.17.0(jiti@2.4.2))(vue-eslint-parser@9.4.3(eslint@9.17.0(jiti@2.4.2))): dependencies: - '@typescript-eslint/utils': 7.15.0(eslint@9.6.0)(typescript@5.4.5) - eslint: 9.6.0 - optionalDependencies: - '@typescript-eslint/eslint-plugin': 7.15.0(@typescript-eslint/parser@7.15.0(eslint@9.6.0)(typescript@5.4.5))(eslint@9.6.0)(typescript@5.4.5) - vitest: 1.6.0(@types/node@20.14.10)(jsdom@24.1.0)(stylus@0.57.0) - transitivePeerDependencies: - - supports-color - - typescript - - eslint-plugin-vue-scoped-css@2.8.1(eslint@9.6.0)(vue-eslint-parser@9.4.3(eslint@9.6.0)): - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.6.0) - eslint: 9.6.0 - eslint-compat-utils: 0.5.1(eslint@9.6.0) + '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@2.4.2)) + eslint: 9.17.0(jiti@2.4.2) + eslint-compat-utils: 0.6.4(eslint@9.17.0(jiti@2.4.2)) lodash: 4.17.21 - postcss: 8.4.39 - postcss-safe-parser: 6.0.0(postcss@8.4.39) - postcss-scss: 4.0.9(postcss@8.4.39) - postcss-selector-parser: 6.1.0 + postcss: 8.4.49 + postcss-safe-parser: 6.0.0(postcss@8.4.49) + postcss-scss: 4.0.9(postcss@8.4.49) + postcss-selector-parser: 6.1.2 postcss-styl: 0.12.3 - vue-eslint-parser: 9.4.3(eslint@9.6.0) + vue-eslint-parser: 9.4.3(eslint@9.17.0(jiti@2.4.2)) transitivePeerDependencies: - supports-color - eslint-plugin-vue@9.27.0(eslint@9.6.0): + eslint-plugin-vue@9.32.0(eslint@9.17.0(jiti@2.4.2)): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.6.0) - eslint: 9.6.0 + '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@2.4.2)) + eslint: 9.17.0(jiti@2.4.2) globals: 13.24.0 natural-compare: 1.4.0 nth-check: 2.1.1 - postcss-selector-parser: 6.1.0 - semver: 7.6.2 - vue-eslint-parser: 9.4.3(eslint@9.6.0) + postcss-selector-parser: 6.1.2 + semver: 7.6.3 + vue-eslint-parser: 9.4.3(eslint@9.17.0(jiti@2.4.2)) xml-name-validator: 4.0.0 transitivePeerDependencies: - supports-color - eslint-plugin-yml@1.14.0(eslint@9.6.0): + eslint-plugin-yml@1.16.0(eslint@9.17.0(jiti@2.4.2)): dependencies: - debug: 4.3.5 - eslint: 9.6.0 - eslint-compat-utils: 0.5.1(eslint@9.6.0) + debug: 4.4.0 + eslint: 9.17.0(jiti@2.4.2) + eslint-compat-utils: 0.6.4(eslint@9.17.0(jiti@2.4.2)) lodash: 4.17.21 natural-compare: 1.4.0 yaml-eslint-parser: 1.2.3 transitivePeerDependencies: - supports-color - eslint-processor-vue-blocks@0.1.2(@vue/compiler-sfc@3.4.31)(eslint@9.6.0): + eslint-processor-vue-blocks@0.1.2(@vue/compiler-sfc@3.5.13)(eslint@9.17.0(jiti@2.4.2)): dependencies: - '@vue/compiler-sfc': 3.4.31 - eslint: 9.6.0 - - eslint-rule-composer@0.3.0: {} + '@vue/compiler-sfc': 3.5.13 + eslint: 9.17.0(jiti@2.4.2) eslint-scope@7.2.2: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 - eslint-scope@8.0.1: + eslint-scope@8.2.0: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 eslint-visitor-keys@3.4.3: {} - eslint-visitor-keys@4.0.0: {} + eslint-visitor-keys@4.2.0: {} - eslint@9.6.0: + eslint@9.17.0(jiti@2.4.2): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.6.0) - '@eslint-community/regexpp': 4.11.0 - '@eslint/config-array': 0.17.0 - '@eslint/eslintrc': 3.1.0 - '@eslint/js': 9.6.0 + '@eslint-community/eslint-utils': 4.4.1(eslint@9.17.0(jiti@2.4.2)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.19.1 + '@eslint/core': 0.9.1 + '@eslint/eslintrc': 3.2.0 + '@eslint/js': 9.17.0 + '@eslint/plugin-kit': 0.2.4 + '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.3.0 - '@nodelib/fs.walk': 1.2.8 + '@humanwhocodes/retry': 0.4.1 + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 - cross-spawn: 7.0.3 - debug: 4.3.5 + cross-spawn: 7.0.6 + debug: 4.4.0 escape-string-regexp: 4.0.0 - eslint-scope: 8.0.1 - eslint-visitor-keys: 4.0.0 - espree: 10.1.0 - esquery: 1.5.0 + eslint-scope: 8.2.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 + esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 file-entry-cache: 8.0.0 find-up: 5.0.0 glob-parent: 6.0.2 - ignore: 5.3.1 + ignore: 5.3.2 imurmurhash: 0.1.4 is-glob: 4.0.3 - is-path-inside: 3.0.3 json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 lodash.merge: 4.6.2 minimatch: 3.1.2 natural-compare: 1.4.0 optionator: 0.9.4 - strip-ansi: 6.0.1 - text-table: 0.2.0 + optionalDependencies: + jiti: 2.4.2 transitivePeerDependencies: - supports-color - espree@10.1.0: + espree@10.3.0: dependencies: - acorn: 8.12.1 - acorn-jsx: 5.3.2(acorn@8.12.1) - eslint-visitor-keys: 4.0.0 + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) + eslint-visitor-keys: 4.2.0 espree@9.6.1: dependencies: - acorn: 8.12.1 - acorn-jsx: 5.3.2(acorn@8.12.1) + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) eslint-visitor-keys: 3.4.3 esprima@4.0.1: {} - esquery@1.5.0: + esquery@1.6.0: dependencies: estraverse: 5.3.0 @@ -4468,51 +5128,29 @@ snapshots: estree-walker@3.0.3: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 esutils@2.0.3: {} - execa@5.1.1: - dependencies: - cross-spawn: 7.0.3 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - - execa@8.0.1: - dependencies: - cross-spawn: 7.0.3 - get-stream: 8.0.1 - human-signals: 5.0.0 - is-stream: 3.0.0 - merge-stream: 2.0.0 - npm-run-path: 5.3.0 - onetime: 6.0.0 - signal-exit: 4.1.0 - strip-final-newline: 3.0.0 + expect-type@1.1.0: {} fast-deep-equal@3.1.3: {} fast-diff@1.3.0: {} - fast-glob@3.3.2: + fast-glob@3.3.3: dependencies: '@nodelib/fs.stat': 2.0.5 '@nodelib/fs.walk': 1.2.8 glob-parent: 5.1.2 merge2: 1.4.1 - micromatch: 4.0.7 + micromatch: 4.0.8 fast-json-stable-stringify@2.1.0: {} fast-levenshtein@2.0.6: {} - fastq@1.17.1: + fastq@1.18.0: dependencies: reusify: 1.0.4 @@ -4524,6 +5162,8 @@ snapshots: dependencies: to-regex-range: 5.0.1 + find-up-simple@1.0.0: {} + find-up@4.1.0: dependencies: locate-path: 5.0.0 @@ -4534,30 +5174,26 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - find-up@7.0.0: - dependencies: - locate-path: 7.2.0 - path-exists: 5.0.0 - unicorn-magic: 0.1.0 - flat-cache@4.0.1: dependencies: - flatted: 3.3.1 + flatted: 3.3.2 keyv: 4.5.4 - flatted@3.3.1: {} + flatted@3.3.2: {} - foreground-child@3.2.1: + foreground-child@3.3.0: dependencies: - cross-spawn: 7.0.3 + cross-spawn: 7.0.6 signal-exit: 4.1.0 - form-data@4.0.0: + form-data@4.0.1: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 mime-types: 2.1.35 + fraction.js@4.3.7: {} + fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -4571,13 +5207,7 @@ snapshots: get-caller-file@2.0.5: {} - get-func-name@2.0.2: {} - - get-stream@6.0.1: {} - - get-stream@8.0.1: {} - - get-tsconfig@4.7.5: + get-tsconfig@4.8.1: dependencies: resolve-pkg-maps: 1.0.0 @@ -4589,13 +5219,13 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@10.4.2: + glob@10.4.5: dependencies: - foreground-child: 3.2.1 - jackspeak: 3.4.0 + foreground-child: 3.3.0 + jackspeak: 3.4.3 minimatch: 9.0.5 minipass: 7.1.2 - package-json-from-dist: 1.0.0 + package-json-from-dist: 1.0.1 path-scurry: 1.11.1 glob@7.2.3: @@ -4615,23 +5245,12 @@ snapshots: globals@14.0.0: {} - globals@15.8.0: {} - - globby@11.1.0: - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.2 - ignore: 5.3.1 - merge2: 1.4.1 - slash: 3.0.0 + globals@15.14.0: {} graceful-fs@4.2.11: {} graphemer@1.4.0: {} - has-flag@3.0.0: {} - has-flag@4.0.0: {} hasown@2.0.2: @@ -4648,27 +5267,25 @@ snapshots: http-proxy-agent@7.0.2: dependencies: - agent-base: 7.1.1 - debug: 4.3.5 + agent-base: 7.1.3 + debug: 4.4.0 transitivePeerDependencies: - supports-color - https-proxy-agent@7.0.5: + https-proxy-agent@7.0.6: dependencies: - agent-base: 7.1.1 - debug: 4.3.5 + agent-base: 7.1.3 + debug: 4.4.0 transitivePeerDependencies: - supports-color - human-signals@2.1.0: {} - - human-signals@5.0.0: {} - iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 - ignore@5.3.1: {} + ignore@5.3.2: {} + + ignore@6.0.2: {} import-fresh@3.3.0: dependencies: @@ -4688,13 +5305,6 @@ snapshots: ini@1.3.8: {} - is-alphabetical@1.0.4: {} - - is-alphanumerical@1.0.4: - dependencies: - is-alphabetical: 1.0.4 - is-decimal: 1.0.4 - is-arrayish@0.2.1: {} is-binary-path@2.1.0: @@ -4705,12 +5315,10 @@ snapshots: dependencies: builtin-modules: 3.3.0 - is-core-module@2.14.0: + is-core-module@2.16.1: dependencies: hasown: 2.0.2 - is-decimal@1.0.4: {} - is-extglob@2.1.1: {} is-fullwidth-code-point@3.0.0: {} @@ -4719,31 +5327,25 @@ snapshots: dependencies: is-extglob: 2.1.1 - is-hexadecimal@1.0.4: {} - is-language-code@3.1.0: dependencies: - '@babel/runtime': 7.24.7 + '@babel/runtime': 7.26.0 is-number@7.0.0: {} - is-path-inside@3.0.3: {} - is-potential-custom-element-name@1.0.1: {} - is-stream@2.0.1: {} - - is-stream@3.0.0: {} - isexe@2.0.0: {} - jackspeak@3.4.0: + jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jiti@1.21.6: {} + jiti@1.21.7: {} + + jiti@2.4.2: {} js-base64@3.7.7: {} @@ -4751,7 +5353,7 @@ snapshots: dependencies: config-chain: 1.1.13 editorconfig: 1.0.4 - glob: 10.4.2 + glob: 10.4.5 js-cookie: 3.0.5 nopt: 7.2.1 @@ -4759,35 +5361,33 @@ snapshots: js-tokens@4.0.0: {} - js-tokens@9.0.0: {} - js-yaml@4.1.0: dependencies: argparse: 2.0.1 - jsdoc-type-pratt-parser@4.0.0: {} + jsdoc-type-pratt-parser@4.1.0: {} - jsdom@24.1.0: + jsdom@25.0.1: dependencies: - cssstyle: 4.0.1 + cssstyle: 4.1.0 data-urls: 5.0.0 decimal.js: 10.4.3 - form-data: 4.0.0 + form-data: 4.0.1 html-encoding-sniffer: 4.0.0 http-proxy-agent: 7.0.2 - https-proxy-agent: 7.0.5 + https-proxy-agent: 7.0.6 is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.10 - parse5: 7.1.2 + nwsapi: 2.2.16 + parse5: 7.2.1 rrweb-cssom: 0.7.1 saxes: 6.0.0 symbol-tree: 3.2.4 - tough-cookie: 4.1.4 + tough-cookie: 5.0.0 w3c-xmlserializer: 5.0.0 webidl-conversions: 7.0.0 whatwg-encoding: 3.1.1 whatwg-mimetype: 4.0.0 - whatwg-url: 14.0.0 + whatwg-url: 14.1.0 ws: 8.18.0 xml-name-validator: 5.0.0 transitivePeerDependencies: @@ -4797,9 +5397,7 @@ snapshots: jsesc@0.5.0: {} - jsesc@2.5.2: {} - - jsesc@3.0.2: {} + jsesc@3.1.0: {} json-buffer@3.0.1: {} @@ -4813,30 +5411,73 @@ snapshots: jsonc-eslint-parser@2.4.0: dependencies: - acorn: 8.12.1 + acorn: 8.14.0 eslint-visitor-keys: 3.4.3 espree: 9.6.1 - semver: 7.6.2 + semver: 7.6.3 keyv@4.5.4: dependencies: json-buffer: 3.0.1 - kolorist@1.8.0: {} - levn@0.4.1: dependencies: prelude-ls: 1.2.1 type-check: 0.4.0 + lightningcss-darwin-arm64@1.28.2: + optional: true + + lightningcss-darwin-x64@1.28.2: + optional: true + + lightningcss-freebsd-x64@1.28.2: + optional: true + + lightningcss-linux-arm-gnueabihf@1.28.2: + optional: true + + lightningcss-linux-arm64-gnu@1.28.2: + optional: true + + lightningcss-linux-arm64-musl@1.28.2: + optional: true + + lightningcss-linux-x64-gnu@1.28.2: + optional: true + + lightningcss-linux-x64-musl@1.28.2: + optional: true + + lightningcss-win32-arm64-msvc@1.28.2: + optional: true + + lightningcss-win32-x64-msvc@1.28.2: + optional: true + + lightningcss@1.28.2: + dependencies: + detect-libc: 1.0.3 + optionalDependencies: + lightningcss-darwin-arm64: 1.28.2 + lightningcss-darwin-x64: 1.28.2 + lightningcss-freebsd-x64: 1.28.2 + lightningcss-linux-arm-gnueabihf: 1.28.2 + lightningcss-linux-arm64-gnu: 1.28.2 + lightningcss-linux-arm64-musl: 1.28.2 + lightningcss-linux-x64-gnu: 1.28.2 + lightningcss-linux-x64-musl: 1.28.2 + lightningcss-win32-arm64-msvc: 1.28.2 + lightningcss-win32-x64-msvc: 1.28.2 + + lilconfig@3.1.3: {} + lines-and-columns@1.2.4: {} - local-pkg@0.4.3: {} - - local-pkg@0.5.0: + local-pkg@0.5.1: dependencies: - mlly: 1.7.1 - pkg-types: 1.1.3 + mlly: 1.7.3 + pkg-types: 1.3.0 locate-path@5.0.0: dependencies: @@ -4846,9 +5487,9 @@ snapshots: dependencies: p-locate: 5.0.0 - locate-path@7.2.0: - dependencies: - p-locate: 6.0.0 + lodash.castarray@4.4.0: {} + + lodash.isplainobject@4.0.6: {} lodash.merge@4.6.2: {} @@ -4856,48 +5497,326 @@ snapshots: lodash@4.17.21: {} - loupe@2.3.7: - dependencies: - get-func-name: 2.0.2 + longest-streak@3.1.0: {} - lru-cache@10.3.0: {} + loupe@3.1.2: {} + + lru-cache@10.4.3: {} lru-cache@5.1.1: dependencies: yallist: 3.1.1 - magic-string@0.30.10: + magic-string@0.30.17: dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 - mdast-util-from-markdown@0.8.5: + make-error@1.3.6: {} + + markdown-table@3.0.4: {} + + marked@15.0.5: {} + + mdast-util-find-and-replace@3.0.2: dependencies: - '@types/mdast': 3.0.15 - mdast-util-to-string: 2.0.0 - micromark: 2.11.4 - parse-entities: 2.0.0 - unist-util-stringify-position: 2.0.3 + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + mdast-util-from-markdown@2.0.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + unist-util-stringify-position: 4.0.0 transitivePeerDependencies: - supports-color - mdast-util-to-string@2.0.0: {} + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.0.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.0.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.0 + + mdast-util-to-markdown@2.1.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.0.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 mdn-data@2.0.28: {} mdn-data@2.0.30: {} - merge-stream@2.0.0: {} - merge2@1.4.1: {} - micromark@2.11.4: + micromark-core-commonmark@2.0.2: dependencies: - debug: 4.3.5 - parse-entities: 2.0.0 + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.2 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-extension-gfm-table@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.1 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.0 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.1 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.0.2 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.1 + + micromark-util-sanitize-uri@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-encode: 2.0.1 + micromark-util-symbol: 2.0.1 + + micromark-util-subtokenize@2.0.3: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.1: {} + + micromark@4.0.1: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.0 + decode-named-character-reference: 1.0.2 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.2 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.1 transitivePeerDependencies: - supports-color - micromatch@4.0.7: + micromatch@4.0.8: dependencies: braces: 3.0.3 picomatch: 2.3.1 @@ -4908,10 +5827,6 @@ snapshots: dependencies: mime-db: 1.52.0 - mimic-fn@2.1.0: {} - - mimic-fn@4.0.0: {} - min-indent@1.0.1: {} minimatch@3.1.2: @@ -4928,33 +5843,37 @@ snapshots: minipass@7.1.2: {} - mlly@1.7.1: + mlly@1.7.3: dependencies: - acorn: 8.12.1 + acorn: 8.14.0 pathe: 1.1.2 - pkg-types: 1.1.3 - ufo: 1.5.3 - - ms@2.1.2: {} + pkg-types: 1.3.0 + ufo: 1.5.4 ms@2.1.3: {} muggle-string@0.4.1: {} - nanoid@3.3.7: {} + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 - natural-compare-lite@1.4.0: {} + nanoid@3.3.8: {} natural-compare@1.4.0: {} - node-emoji@2.1.3: + natural-orderby@5.0.0: {} + + node-emoji@2.2.0: dependencies: '@sindresorhus/is': 4.6.0 char-regex: 1.0.2 emojilib: 2.4.0 skin-tone: 2.0.0 - node-releases@2.0.14: {} + node-releases@2.0.19: {} nopt@7.2.1: dependencies: @@ -4963,38 +5882,28 @@ snapshots: normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.8 - semver: 7.6.2 + resolve: 1.22.10 + semver: 7.6.3 validate-npm-package-license: 3.0.4 normalize-path@3.0.0: {} - npm-run-path@4.0.1: - dependencies: - path-key: 3.1.1 - - npm-run-path@5.3.0: - dependencies: - path-key: 4.0.0 + normalize-range@0.1.2: {} nth-check@2.1.1: dependencies: boolbase: 1.0.0 - nwsapi@2.2.10: {} + nwsapi@2.2.16: {} + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} once@1.4.0: dependencies: wrappy: 1.0.2 - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - - onetime@6.0.0: - dependencies: - mimic-fn: 4.0.0 - optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -5012,14 +5921,6 @@ snapshots: dependencies: yocto-queue: 0.1.0 - p-limit@4.0.0: - dependencies: - yocto-queue: 1.1.1 - - p-limit@5.0.0: - dependencies: - yocto-queue: 1.1.1 - p-locate@4.1.0: dependencies: p-limit: 2.3.0 @@ -5028,42 +5929,31 @@ snapshots: dependencies: p-limit: 3.1.0 - p-locate@6.0.0: - dependencies: - p-limit: 4.0.0 - p-try@2.2.0: {} - package-json-from-dist@1.0.0: {} + package-json-from-dist@1.0.1: {} + + package-manager-detector@0.2.8: {} parent-module@1.0.1: dependencies: callsites: 3.1.0 - parse-entities@2.0.0: - dependencies: - character-entities: 1.2.4 - character-entities-legacy: 1.1.4 - character-reference-invalid: 1.1.4 - is-alphanumerical: 1.0.4 - is-decimal: 1.0.4 - is-hexadecimal: 1.0.4 - parse-gitignore@2.0.0: {} - parse-imports@2.1.1: + parse-imports@2.2.1: dependencies: - es-module-lexer: 1.5.4 + es-module-lexer: 1.6.0 slashes: 3.0.12 parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.26.2 error-ex: 1.3.2 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 - parse5@7.1.2: + parse5@7.2.1: dependencies: entities: 4.5.0 @@ -5071,101 +5961,131 @@ snapshots: path-exists@4.0.0: {} - path-exists@5.0.0: {} - path-is-absolute@1.0.1: {} path-key@3.1.1: {} - path-key@4.0.0: {} - path-parse@1.0.7: {} path-scurry@1.11.1: dependencies: - lru-cache: 10.3.0 + lru-cache: 10.4.3 minipass: 7.1.2 - path-type@4.0.0: {} - pathe@1.1.2: {} - pathval@1.1.1: {} + pathval@2.0.0: {} - picocolors@1.0.1: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} picomatch@4.0.2: {} - pinia@2.1.7(typescript@5.4.5)(vue@3.4.31(typescript@5.4.5)): - dependencies: - '@vue/devtools-api': 6.6.3 - vue: 3.4.31(typescript@5.4.5) - vue-demi: 0.14.8(vue@3.4.31(typescript@5.4.5)) - optionalDependencies: - typescript: 5.4.5 + pify@2.3.0: {} - pkg-types@1.1.3: + pinia@2.3.0(typescript@5.7.2)(vue@3.5.13(typescript@5.7.2)): dependencies: - confbox: 0.1.7 - mlly: 1.7.1 + '@vue/devtools-api': 6.6.4 + vue: 3.5.13(typescript@5.7.2) + vue-demi: 0.14.10(vue@3.5.13(typescript@5.7.2)) + optionalDependencies: + typescript: 5.7.2 + transitivePeerDependencies: + - '@vue/composition-api' + + pirates@4.0.6: {} + + pkg-types@1.3.0: + dependencies: + confbox: 0.1.8 + mlly: 1.7.3 pathe: 1.1.2 pluralize@8.0.0: {} - postcss-safe-parser@6.0.0(postcss@8.4.39): + postcss-import@15.1.0(postcss@8.4.49): dependencies: - postcss: 8.4.39 + postcss: 8.4.49 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.10 - postcss-scss@4.0.9(postcss@8.4.39): + postcss-js@4.0.1(postcss@8.4.49): dependencies: - postcss: 8.4.39 + camelcase-css: 2.0.1 + postcss: 8.4.49 - postcss-selector-parser@6.1.0: + postcss-load-config@4.0.2(postcss@8.4.49)(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.7.2)): + dependencies: + lilconfig: 3.1.3 + yaml: 2.7.0 + optionalDependencies: + postcss: 8.4.49 + ts-node: 10.9.2(@types/node@22.10.5)(typescript@5.7.2) + + postcss-nested@6.2.0(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + postcss-selector-parser: 6.1.2 + + postcss-safe-parser@6.0.0(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + + postcss-scss@4.0.9(postcss@8.4.49): + dependencies: + postcss: 8.4.49 + + postcss-selector-parser@6.0.10: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-selector-parser@6.1.2: dependencies: cssesc: 3.0.0 util-deprecate: 1.0.2 postcss-styl@0.12.3: dependencies: - debug: 4.3.5 + debug: 4.4.0 fast-diff: 1.3.0 lodash.sortedlastindex: 4.1.0 - postcss: 8.4.39 + postcss: 8.4.49 stylus: 0.57.0 transitivePeerDependencies: - supports-color - postcss@8.4.39: + postcss-value-parser@4.2.0: {} + + postcss@8.4.49: dependencies: - nanoid: 3.3.7 - picocolors: 1.0.1 - source-map-js: 1.2.0 + nanoid: 3.3.8 + picocolors: 1.1.1 + source-map-js: 1.2.1 prelude-ls@1.2.1: {} - prettier@3.3.2: {} - - pretty-format@29.7.0: + prettier-plugin-tailwindcss@0.6.9(@ianvs/prettier-plugin-sort-imports@4.4.0(@vue/compiler-sfc@3.5.13)(prettier@3.4.2))(prettier@3.4.2): dependencies: - '@jest/schemas': 29.6.3 - ansi-styles: 5.2.0 - react-is: 18.3.1 + prettier: 3.4.2 + optionalDependencies: + '@ianvs/prettier-plugin-sort-imports': 4.4.0(@vue/compiler-sfc@3.5.13)(prettier@3.4.2) + + prettier@3.4.2: {} prismjs@1.29.0: {} proto-list@1.2.4: {} - psl@1.9.0: {} - punycode@2.3.1: {} - querystringify@2.2.0: {} - queue-microtask@1.2.3: {} - react-is@18.3.1: {} + read-cache@1.0.0: + dependencies: + pify: 2.3.0 read-pkg-up@7.0.1: dependencies: @@ -5186,13 +6106,13 @@ snapshots: refa@0.12.1: dependencies: - '@eslint-community/regexpp': 4.11.0 + '@eslint-community/regexpp': 4.12.1 regenerator-runtime@0.14.1: {} regexp-ast-analysis@0.7.1: dependencies: - '@eslint-community/regexpp': 4.11.0 + '@eslint-community/regexpp': 4.12.1 refa: 0.12.1 regexp-tree@0.1.27: {} @@ -5201,52 +6121,45 @@ snapshots: dependencies: jsesc: 0.5.0 - replace-in-file@8.1.0: - dependencies: - chalk: 5.3.0 - glob: 10.4.2 - yargs: 17.7.2 - require-directory@2.1.1: {} - requires-port@1.0.0: {} - resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} - resolve@1.22.8: + resolve@1.22.10: dependencies: - is-core-module: 2.14.0 + is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 reusify@1.0.4: {} - rollup@4.18.0: + rollup@4.30.0: dependencies: - '@types/estree': 1.0.5 + '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.18.0 - '@rollup/rollup-android-arm64': 4.18.0 - '@rollup/rollup-darwin-arm64': 4.18.0 - '@rollup/rollup-darwin-x64': 4.18.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.18.0 - '@rollup/rollup-linux-arm-musleabihf': 4.18.0 - '@rollup/rollup-linux-arm64-gnu': 4.18.0 - '@rollup/rollup-linux-arm64-musl': 4.18.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.18.0 - '@rollup/rollup-linux-riscv64-gnu': 4.18.0 - '@rollup/rollup-linux-s390x-gnu': 4.18.0 - '@rollup/rollup-linux-x64-gnu': 4.18.0 - '@rollup/rollup-linux-x64-musl': 4.18.0 - '@rollup/rollup-win32-arm64-msvc': 4.18.0 - '@rollup/rollup-win32-ia32-msvc': 4.18.0 - '@rollup/rollup-win32-x64-msvc': 4.18.0 + '@rollup/rollup-android-arm-eabi': 4.30.0 + '@rollup/rollup-android-arm64': 4.30.0 + '@rollup/rollup-darwin-arm64': 4.30.0 + '@rollup/rollup-darwin-x64': 4.30.0 + '@rollup/rollup-freebsd-arm64': 4.30.0 + '@rollup/rollup-freebsd-x64': 4.30.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.30.0 + '@rollup/rollup-linux-arm-musleabihf': 4.30.0 + '@rollup/rollup-linux-arm64-gnu': 4.30.0 + '@rollup/rollup-linux-arm64-musl': 4.30.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.30.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.30.0 + '@rollup/rollup-linux-riscv64-gnu': 4.30.0 + '@rollup/rollup-linux-s390x-gnu': 4.30.0 + '@rollup/rollup-linux-x64-gnu': 4.30.0 + '@rollup/rollup-linux-x64-musl': 4.30.0 + '@rollup/rollup-win32-arm64-msvc': 4.30.0 + '@rollup/rollup-win32-ia32-msvc': 4.30.0 + '@rollup/rollup-win32-x64-msvc': 4.30.0 fsevents: 2.3.3 - rrweb-cssom@0.6.0: {} - rrweb-cssom@0.7.1: {} run-parallel@1.2.0: @@ -5263,11 +6176,11 @@ snapshots: scslre@0.3.0: dependencies: - '@eslint-community/regexpp': 4.11.0 + '@eslint-community/regexpp': 4.12.1 refa: 0.12.1 regexp-ast-analysis: 0.7.1 - semver@7.6.2: {} + semver@7.6.3: {} shebang-command@2.0.0: dependencies: @@ -5277,21 +6190,19 @@ snapshots: siginfo@2.0.0: {} - signal-exit@3.0.7: {} - signal-exit@4.1.0: {} + simple-icons@14.1.0: {} + sisteransi@1.0.5: {} skin-tone@2.0.0: dependencies: unicode-emoji-modifier-base: 1.0.0 - slash@3.0.0: {} - slashes@3.0.12: {} - source-map-js@1.2.0: {} + source-map-js@1.2.1: {} source-map-resolve@0.6.0: dependencies: @@ -5305,29 +6216,27 @@ snapshots: spdx-correct@3.2.0: dependencies: spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.18 + spdx-license-ids: 3.0.20 spdx-exceptions@2.5.0: {} spdx-expression-parse@3.0.1: dependencies: spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.18 + spdx-license-ids: 3.0.20 spdx-expression-parse@4.0.0: dependencies: spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.18 + spdx-license-ids: 3.0.20 - spdx-license-ids@3.0.18: {} + spdx-license-ids@3.0.20: {} stable-hash@0.0.4: {} stackback@0.0.2: {} - std-env@3.7.0: {} - - string-argv@0.3.2: {} + std-env@3.8.0: {} string-width@4.2.3: dependencies: @@ -5347,11 +6256,7 @@ snapshots: strip-ansi@7.1.0: dependencies: - ansi-regex: 6.0.1 - - strip-final-newline@2.0.0: {} - - strip-final-newline@3.0.0: {} + ansi-regex: 6.1.0 strip-indent@3.0.0: dependencies: @@ -5359,14 +6264,10 @@ snapshots: strip-json-comments@3.1.1: {} - strip-literal@2.1.0: - dependencies: - js-tokens: 9.0.0 - stylus@0.57.0: dependencies: css: 3.0.0 - debug: 4.3.5 + debug: 4.4.0 glob: 7.2.3 safer-buffer: 2.1.2 sax: 1.2.4 @@ -5374,9 +6275,15 @@ snapshots: transitivePeerDependencies: - supports-color - supports-color@5.5.0: + sucrase@3.35.0: dependencies: - has-flag: 3.0.0 + '@jridgewell/gen-mapping': 0.3.8 + commander: 4.1.1 + glob: 10.4.5 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 supports-color@7.2.0: dependencies: @@ -5392,32 +6299,75 @@ snapshots: css-tree: 2.3.1 css-what: 6.1.0 csso: 5.0.5 - picocolors: 1.0.1 + picocolors: 1.1.1 symbol-tree@3.2.4: {} synckit@0.6.2: dependencies: - tslib: 2.6.3 + tslib: 2.8.1 - synckit@0.9.0: + synckit@0.9.2: dependencies: '@pkgr/core': 0.1.1 - tslib: 2.6.3 + tslib: 2.8.1 + + tailwindcss@3.4.17(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.7.2)): + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.4.49 + postcss-import: 15.1.0(postcss@8.4.49) + postcss-js: 4.0.1(postcss@8.4.49) + postcss-load-config: 4.0.2(postcss@8.4.49)(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.7.2)) + postcss-nested: 6.2.0(postcss@8.4.49) + postcss-selector-parser: 6.1.2 + resolve: 1.22.10 + sucrase: 3.35.0 + transitivePeerDependencies: + - ts-node + + tailwindcss@4.0.0-beta.8: {} tapable@2.2.1: {} - text-table@0.2.0: {} + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 - tinybench@2.8.0: {} + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + tinybench@2.9.0: {} tinycolor2@1.6.0: {} - tinypool@0.8.4: {} + tinyexec@0.3.2: {} - tinyspy@2.2.1: {} + tinypool@1.0.2: {} - to-fast-properties@2.0.0: {} + tinyrainbow@1.2.0: {} + + tinyspy@3.0.2: {} + + tldts-core@6.1.70: {} + + tldts@6.1.70: + dependencies: + tldts-core: 6.1.70 to-regex-range@5.0.1: dependencies: @@ -5427,137 +6377,114 @@ snapshots: dependencies: eslint-visitor-keys: 3.4.3 - tough-cookie@4.1.4: + tough-cookie@5.0.0: dependencies: - psl: 1.9.0 - punycode: 2.3.1 - universalify: 0.2.0 - url-parse: 1.5.10 + tldts: 6.1.70 tr46@5.0.0: dependencies: punycode: 2.3.1 - ts-api-utils@1.3.0(typescript@5.4.5): + ts-api-utils@1.4.3(typescript@5.7.2): dependencies: - typescript: 5.4.5 + typescript: 5.7.2 - tslib@2.6.3: {} + ts-interface-checker@0.1.13: {} + + ts-node@10.9.2(@types/node@22.10.5)(typescript@5.7.2): + dependencies: + '@cspotcode/source-map-support': 0.8.1 + '@tsconfig/node10': 1.0.11 + '@tsconfig/node12': 1.0.11 + '@tsconfig/node14': 1.0.3 + '@tsconfig/node16': 1.0.4 + '@types/node': 22.10.5 + acorn: 8.14.0 + acorn-walk: 8.3.4 + arg: 4.1.3 + create-require: 1.1.1 + diff: 4.0.2 + make-error: 1.3.6 + typescript: 5.7.2 + v8-compile-cache-lib: 3.0.1 + yn: 3.1.1 + + tslib@2.8.1: {} type-check@0.4.0: dependencies: prelude-ls: 1.2.1 - type-detect@4.0.8: {} - type-fest@0.20.2: {} type-fest@0.6.0: {} type-fest@0.8.1: {} - typescript-eslint@7.15.0(eslint@9.6.0)(typescript@5.4.5): - dependencies: - '@typescript-eslint/eslint-plugin': 7.15.0(@typescript-eslint/parser@7.15.0(eslint@9.6.0)(typescript@5.4.5))(eslint@9.6.0)(typescript@5.4.5) - '@typescript-eslint/parser': 7.15.0(eslint@9.6.0)(typescript@5.4.5) - '@typescript-eslint/utils': 7.15.0(eslint@9.6.0)(typescript@5.4.5) - eslint: 9.6.0 - optionalDependencies: - typescript: 5.4.5 - transitivePeerDependencies: - - supports-color + typescript@5.7.2: {} - typescript@5.4.5: {} + ufo@1.5.4: {} - ufo@1.5.3: {} - - undici-types@5.26.5: {} + undici-types@6.20.0: {} unicode-emoji-modifier-base@1.0.0: {} - unicorn-magic@0.1.0: {} - - unist-util-stringify-position@2.0.3: + unist-util-is@6.0.0: dependencies: - '@types/unist': 2.0.10 + '@types/unist': 3.0.3 - universalify@0.2.0: {} - - unplugin-icons@0.18.5(@vue/compiler-sfc@3.4.31)(vue-template-compiler@2.7.16): + unist-util-stringify-position@4.0.0: dependencies: - '@antfu/install-pkg': 0.3.3 - '@antfu/utils': 0.7.10 - '@iconify/utils': 2.1.25 - debug: 4.3.5 - kolorist: 1.8.0 - local-pkg: 0.5.0 - unplugin: 1.11.0 - optionalDependencies: - '@vue/compiler-sfc': 3.4.31 - vue-template-compiler: 2.7.16 - transitivePeerDependencies: - - supports-color + '@types/unist': 3.0.3 - unplugin-vue-components@0.26.0(@babel/parser@7.24.7)(rollup@4.18.0)(vue@3.4.31(typescript@5.4.5)): + unist-util-visit-parents@6.0.1: dependencies: - '@antfu/utils': 0.7.10 - '@rollup/pluginutils': 5.1.0(rollup@4.18.0) - chokidar: 3.6.0 - debug: 4.3.5 - fast-glob: 3.3.2 - local-pkg: 0.4.3 - magic-string: 0.30.10 - minimatch: 9.0.5 - resolve: 1.22.8 - unplugin: 1.11.0 - vue: 3.4.31(typescript@5.4.5) - optionalDependencies: - '@babel/parser': 7.24.7 - transitivePeerDependencies: - - rollup - - supports-color + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 - unplugin@1.11.0: + unist-util-visit@5.0.0: dependencies: - acorn: 8.12.1 - chokidar: 3.6.0 - webpack-sources: 3.2.3 + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + unist-util-visit-parents: 6.0.1 + + unplugin@1.16.0: + dependencies: + acorn: 8.14.0 webpack-virtual-modules: 0.6.2 - update-browserslist-db@1.1.0(browserslist@4.23.1): + update-browserslist-db@1.1.1(browserslist@4.24.3): dependencies: - browserslist: 4.23.1 - escalade: 3.1.2 - picocolors: 1.0.1 + browserslist: 4.24.3 + escalade: 3.2.0 + picocolors: 1.1.1 uri-js@4.4.1: dependencies: punycode: 2.3.1 - url-parse@1.5.10: - dependencies: - querystringify: 2.2.0 - requires-port: 1.0.0 - util-deprecate@1.0.2: {} + v8-compile-cache-lib@3.0.1: {} + validate-npm-package-license@3.0.4: dependencies: spdx-correct: 3.2.0 spdx-expression-parse: 3.0.1 - vite-node@1.6.0(@types/node@20.14.10)(stylus@0.57.0): + vite-node@2.1.8(@types/node@22.10.5)(lightningcss@1.28.2)(stylus@0.57.0): dependencies: cac: 6.7.14 - debug: 4.3.5 + debug: 4.4.0 + es-module-lexer: 1.6.0 pathe: 1.1.2 - picocolors: 1.0.1 - vite: 5.3.3(@types/node@20.14.10)(stylus@0.57.0) + vite: 5.4.11(@types/node@22.10.5)(lightningcss@1.28.2)(stylus@0.57.0) transitivePeerDependencies: - '@types/node' - less - lightningcss - sass + - sass-embedded - stylus - sugarss - supports-color @@ -5565,66 +6492,72 @@ snapshots: vite-plugin-prismjs@0.0.11(prismjs@1.29.0): dependencies: - '@babel/core': 7.24.7 + '@babel/core': 7.26.0 babel-plugin-prismjs: 2.1.0(prismjs@1.29.0) transitivePeerDependencies: - prismjs - supports-color - vite-plugin-windicss@1.9.3(vite@5.3.3(@types/node@20.14.10)(stylus@0.57.0)): - dependencies: - '@windicss/plugin-utils': 1.9.3 - debug: 4.3.5 - kolorist: 1.8.0 - vite: 5.3.3(@types/node@20.14.10)(stylus@0.57.0) - windicss: 3.5.6 - transitivePeerDependencies: - - supports-color - - vite-svg-loader@5.1.0(vue@3.4.31(typescript@5.4.5)): + vite-svg-loader@5.1.0(vue@3.5.13(typescript@5.7.2)): dependencies: svgo: 3.3.2 - vue: 3.4.31(typescript@5.4.5) + vue: 3.5.13(typescript@5.7.2) - vite@5.3.3(@types/node@20.14.10)(stylus@0.57.0): + vite@5.4.11(@types/node@22.10.5)(lightningcss@1.28.2)(stylus@0.57.0): dependencies: esbuild: 0.21.5 - postcss: 8.4.39 - rollup: 4.18.0 + postcss: 8.4.49 + rollup: 4.30.0 optionalDependencies: - '@types/node': 20.14.10 + '@types/node': 22.10.5 fsevents: 2.3.3 + lightningcss: 1.28.2 stylus: 0.57.0 - vitest@1.6.0(@types/node@20.14.10)(jsdom@24.1.0)(stylus@0.57.0): + vite@6.0.7(@types/node@22.10.5)(jiti@2.4.2)(lightningcss@1.28.2)(stylus@0.57.0)(yaml@2.7.0): dependencies: - '@vitest/expect': 1.6.0 - '@vitest/runner': 1.6.0 - '@vitest/snapshot': 1.6.0 - '@vitest/spy': 1.6.0 - '@vitest/utils': 1.6.0 - acorn-walk: 8.3.3 - chai: 4.4.1 - debug: 4.3.5 - execa: 8.0.1 - local-pkg: 0.5.0 - magic-string: 0.30.10 - pathe: 1.1.2 - picocolors: 1.0.1 - std-env: 3.7.0 - strip-literal: 2.1.0 - tinybench: 2.8.0 - tinypool: 0.8.4 - vite: 5.3.3(@types/node@20.14.10)(stylus@0.57.0) - vite-node: 1.6.0(@types/node@20.14.10)(stylus@0.57.0) - why-is-node-running: 2.2.2 + esbuild: 0.24.2 + postcss: 8.4.49 + rollup: 4.30.0 optionalDependencies: - '@types/node': 20.14.10 - jsdom: 24.1.0 + '@types/node': 22.10.5 + fsevents: 2.3.3 + jiti: 2.4.2 + lightningcss: 1.28.2 + stylus: 0.57.0 + yaml: 2.7.0 + + vitest@2.1.8(@types/node@22.10.5)(jsdom@25.0.1)(lightningcss@1.28.2)(stylus@0.57.0): + dependencies: + '@vitest/expect': 2.1.8 + '@vitest/mocker': 2.1.8(vite@5.4.11(@types/node@22.10.5)(lightningcss@1.28.2)(stylus@0.57.0)) + '@vitest/pretty-format': 2.1.8 + '@vitest/runner': 2.1.8 + '@vitest/snapshot': 2.1.8 + '@vitest/spy': 2.1.8 + '@vitest/utils': 2.1.8 + chai: 5.1.2 + debug: 4.4.0 + expect-type: 1.1.0 + magic-string: 0.30.17 + pathe: 1.1.2 + std-env: 3.8.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinypool: 1.0.2 + tinyrainbow: 1.2.0 + vite: 5.4.11(@types/node@22.10.5)(lightningcss@1.28.2)(stylus@0.57.0) + vite-node: 2.1.8(@types/node@22.10.5)(lightningcss@1.28.2)(stylus@0.57.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 22.10.5 + jsdom: 25.0.1 transitivePeerDependencies: - less - lightningcss + - msw - sass + - sass-embedded - stylus - sugarss - supports-color @@ -5632,58 +6565,52 @@ snapshots: vscode-uri@3.0.8: {} - vue-component-type-helpers@2.0.26: {} + vue-component-type-helpers@2.2.0: {} - vue-demi@0.14.8(vue@3.4.31(typescript@5.4.5)): + vue-demi@0.14.10(vue@3.5.13(typescript@5.7.2)): dependencies: - vue: 3.4.31(typescript@5.4.5) + vue: 3.5.13(typescript@5.7.2) - vue-eslint-parser@9.4.3(eslint@9.6.0): + vue-eslint-parser@9.4.3(eslint@9.17.0(jiti@2.4.2)): dependencies: - debug: 4.3.5 - eslint: 9.6.0 + debug: 4.4.0 + eslint: 9.17.0(jiti@2.4.2) eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 espree: 9.6.1 - esquery: 1.5.0 + esquery: 1.6.0 lodash: 4.17.21 - semver: 7.6.2 + semver: 7.6.3 transitivePeerDependencies: - supports-color - vue-i18n@9.13.1(vue@3.4.31(typescript@5.4.5)): + vue-i18n@11.0.1(vue@3.5.13(typescript@5.7.2)): dependencies: - '@intlify/core-base': 9.13.1 - '@intlify/shared': 9.13.1 - '@vue/devtools-api': 6.6.3 - vue: 3.4.31(typescript@5.4.5) + '@intlify/core-base': 11.0.1 + '@intlify/shared': 11.0.1 + '@vue/devtools-api': 6.6.4 + vue: 3.5.13(typescript@5.7.2) - vue-router@4.4.0(vue@3.4.31(typescript@5.4.5)): + vue-router@4.5.0(vue@3.5.13(typescript@5.7.2)): dependencies: - '@vue/devtools-api': 6.6.3 - vue: 3.4.31(typescript@5.4.5) + '@vue/devtools-api': 6.6.4 + vue: 3.5.13(typescript@5.7.2) - vue-template-compiler@2.7.16: + vue-tsc@2.2.0(typescript@5.7.2): dependencies: - de-indent: 1.0.2 - he: 1.2.0 + '@volar/typescript': 2.4.11 + '@vue/language-core': 2.2.0(typescript@5.7.2) + typescript: 5.7.2 - vue-tsc@2.0.26(typescript@5.4.5): + vue@3.5.13(typescript@5.7.2): dependencies: - '@volar/typescript': 2.4.0-alpha.15 - '@vue/language-core': 2.0.26(typescript@5.4.5) - semver: 7.6.2 - typescript: 5.4.5 - - vue@3.4.31(typescript@5.4.5): - dependencies: - '@vue/compiler-dom': 3.4.31 - '@vue/compiler-sfc': 3.4.31 - '@vue/runtime-dom': 3.4.31 - '@vue/server-renderer': 3.4.31(vue@3.4.31(typescript@5.4.5)) - '@vue/shared': 3.4.31 + '@vue/compiler-dom': 3.5.13 + '@vue/compiler-sfc': 3.5.13 + '@vue/runtime-dom': 3.5.13 + '@vue/server-renderer': 3.5.13(vue@3.5.13(typescript@5.7.2)) + '@vue/shared': 3.5.13 optionalDependencies: - typescript: 5.4.5 + typescript: 5.7.2 w3c-xmlserializer@5.0.0: dependencies: @@ -5691,8 +6618,6 @@ snapshots: webidl-conversions@7.0.0: {} - webpack-sources@3.2.3: {} - webpack-virtual-modules@0.6.2: {} whatwg-encoding@3.1.1: @@ -5701,7 +6626,7 @@ snapshots: whatwg-mimetype@4.0.0: {} - whatwg-url@14.0.0: + whatwg-url@14.1.0: dependencies: tr46: 5.0.0 webidl-conversions: 7.0.0 @@ -5710,13 +6635,11 @@ snapshots: dependencies: isexe: 2.0.0 - why-is-node-running@2.2.2: + why-is-node-running@2.3.0: dependencies: siginfo: 2.0.0 stackback: 0.0.2 - windicss@3.5.6: {} - word-wrap@1.2.5: {} wrap-ansi@7.0.0: @@ -5749,22 +6672,24 @@ snapshots: dependencies: eslint-visitor-keys: 3.4.3 lodash: 4.17.21 - yaml: 2.4.5 + yaml: 2.7.0 - yaml@2.4.5: {} + yaml@2.7.0: {} yargs-parser@21.1.1: {} yargs@17.7.2: dependencies: cliui: 8.0.1 - escalade: 3.1.2 + escalade: 3.2.0 get-caller-file: 2.0.5 require-directory: 2.1.1 string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 21.1.1 + yn@3.1.1: {} + yocto-queue@0.1.0: {} - yocto-queue@1.1.1: {} + zwitch@2.0.4: {} diff --git a/web/postcss.config.js b/web/postcss.config.js new file mode 100644 index 000000000..b0c6b4904 --- /dev/null +++ b/web/postcss.config.js @@ -0,0 +1,7 @@ +// postcss.config.js +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/web/src/App.vue b/web/src/App.vue index 90a04d174..61b22843c 100644 --- a/web/src/App.vue +++ b/web/src/App.vue @@ -1,14 +1,14 @@ + + diff --git a/web/src/components/repo/pipeline/PipelineItem.vue b/web/src/components/repo/pipeline/PipelineItem.vue index c34faa5f8..c17b6d0e5 100644 --- a/web/src/components/repo/pipeline/PipelineItem.vue +++ b/web/src/components/repo/pipeline/PipelineItem.vue @@ -1,30 +1,46 @@