diff --git a/.forgejo/workflows-composite/install-minimum-git-version/action.yaml b/.forgejo/workflows-composite/install-minimum-git-version/action.yaml new file mode 100644 index 0000000000..d4e6e3f2a7 --- /dev/null +++ b/.forgejo/workflows-composite/install-minimum-git-version/action.yaml @@ -0,0 +1,22 @@ +# +# Install the minimal version of Git supported by Forgejo +# +runs: + using: "composite" + steps: + - name: install git and git-lfs + run: | + set -x + + export DEBIAN_FRONTEND=noninteractive + + apt-get update -qq + apt-get -q install -y -qq curl ca-certificates + + curl -sS -o /tmp/git-man.deb http://archive.ubuntu.com/ubuntu/pool/main/g/git/git-man_2.34.1-1ubuntu1_all.deb + curl -sS -o /tmp/git.deb https://archive.ubuntu.com/ubuntu/pool/main/g/git/git_2.34.1-1ubuntu1_amd64.deb + curl -sS -o /tmp/git-lfs.deb https://archive.ubuntu.com/ubuntu/pool/universe/g/git-lfs/git-lfs_3.0.2-1_amd64.deb + + apt-get -q install --allow-downgrades -y -qq /tmp/git-man.deb + apt-get -q install --allow-downgrades -y -qq /tmp/git.deb + apt-get -q install --allow-downgrades -y -qq /tmp/git-lfs.deb diff --git a/.forgejo/workflows/renovate.yml b/.forgejo/workflows/renovate.yml index 5aa6c8cd98..729cba3723 100644 --- a/.forgejo/workflows/renovate.yml +++ b/.forgejo/workflows/renovate.yml @@ -28,7 +28,7 @@ jobs: runs-on: docker container: - image: data.forgejo.org/renovate/renovate:41.1.4 + image: data.forgejo.org/renovate/renovate:41.17.2 steps: - name: Load renovate repo cache diff --git a/.forgejo/workflows/testing-integration.yml b/.forgejo/workflows/testing-integration.yml index 9e5cfb92ed..102a2d9774 100644 --- a/.forgejo/workflows/testing-integration.yml +++ b/.forgejo/workflows/testing-integration.yml @@ -33,11 +33,8 @@ jobs: steps: - uses: https://data.forgejo.org/actions/checkout@v4 - uses: ./.forgejo/workflows-composite/setup-env - - name: install git 2.30 - uses: ./.forgejo/workflows-composite/apt-install-from - with: - packages: git/bullseye git-lfs/bullseye - release: bullseye + - name: install git 2.34.1 and git-lfs 3.0.2 + uses: ./.forgejo/workflows-composite/install-minimum-git-version - uses: ./.forgejo/workflows-composite/build-backend - run: | su forgejo -c 'make test-backend test-check' @@ -55,11 +52,8 @@ jobs: steps: - uses: https://data.forgejo.org/actions/checkout@v4 - uses: ./.forgejo/workflows-composite/setup-env - - name: install git 2.30 - uses: ./.forgejo/workflows-composite/apt-install-from - with: - packages: git/bullseye git-lfs/bullseye - release: bullseye + - name: install git 2.34.1 and git-lfs 3.0.2 + uses: ./.forgejo/workflows-composite/install-minimum-git-version - uses: ./.forgejo/workflows-composite/build-backend - run: | su forgejo -c 'make test-sqlite-migration test-sqlite' diff --git a/Makefile b/Makefile index e770f2a989..db4ec2fbd5 100644 --- a/Makefile +++ b/Makefile @@ -47,7 +47,7 @@ GO_LICENSES_PACKAGE ?= github.com/google/go-licenses@v1.6.0 # renovate: datasour GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go DEADCODE_PACKAGE ?= golang.org/x/tools/cmd/deadcode@v0.34.0 # renovate: datasource=go GOMOCK_PACKAGE ?= go.uber.org/mock/mockgen@v0.5.2 # renovate: datasource=go -RENOVATE_NPM_PACKAGE ?= renovate@41.1.4 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate +RENOVATE_NPM_PACKAGE ?= renovate@41.17.2 # renovate: datasource=docker packageName=data.forgejo.org/renovate/renovate # https://github.com/disposable-email-domains/disposable-email-domains/commits/main/ DISPOSABLE_EMAILS_SHA ?= 0c27e671231d27cf66370034d7f6818037416989 # renovate: ... diff --git a/cmd/dump_repo.go b/cmd/dump_repo.go index eb89273e7f..7159d55e99 100644 --- a/cmd/dump_repo.go +++ b/cmd/dump_repo.go @@ -82,6 +82,11 @@ wiki, issues, labels, releases, release_assets, milestones, pull_requests, comme } func runDumpRepository(stdCtx context.Context, ctx *cli.Command) error { + setupConsoleLogger(log.INFO, log.CanColorStderr, os.Stderr) + + // setting.DisableLoggerInit() + setting.LoadSettings() // cannot access skip_tls_verify settings otherwise + stdCtx, cancel := installSignals(stdCtx) defer cancel() diff --git a/cmd/hook.go b/cmd/hook.go index 909cdfdf84..7378dc21ad 100644 --- a/cmd/hook.go +++ b/cmd/hook.go @@ -231,8 +231,6 @@ Forgejo or set your environment appropriately.`, "") } } - supportProcReceive := git.CheckGitVersionAtLeast("2.29") == nil - for scanner.Scan() { // TODO: support news feeds for wiki if isWiki { @@ -250,31 +248,25 @@ Forgejo or set your environment appropriately.`, "") total++ lastline++ - // If the ref is a branch or tag, check if it's protected - // if supportProcReceive all ref should be checked because - // permission check was delayed - if supportProcReceive || refFullName.IsBranch() || refFullName.IsTag() { - oldCommitIDs[count] = oldCommitID - newCommitIDs[count] = newCommitID - refFullNames[count] = refFullName - count++ - fmt.Fprint(out, "*") + // All references should be checked because permission check was delayed. + oldCommitIDs[count] = oldCommitID + newCommitIDs[count] = newCommitID + refFullNames[count] = refFullName + count++ + fmt.Fprint(out, "*") - if count >= hookBatchSize { - fmt.Fprintf(out, " Checking %d references\n", count) + if count >= hookBatchSize { + fmt.Fprintf(out, " Checking %d references\n", count) - hookOptions.OldCommitIDs = oldCommitIDs - hookOptions.NewCommitIDs = newCommitIDs - hookOptions.RefFullNames = refFullNames - extra := private.HookPreReceive(ctx, username, reponame, hookOptions) - if extra.HasError() { - return fail(ctx, extra.UserMsg, "HookPreReceive(batch) failed: %v", extra.Error) - } - count = 0 - lastline = 0 + hookOptions.OldCommitIDs = oldCommitIDs + hookOptions.NewCommitIDs = newCommitIDs + hookOptions.RefFullNames = refFullNames + extra := private.HookPreReceive(ctx, username, reponame, hookOptions) + if extra.HasError() { + return fail(ctx, extra.UserMsg, "HookPreReceive(batch) failed: %v", extra.Error) } - } else { - fmt.Fprint(out, ".") + count = 0 + lastline = 0 } if lastline >= hookBatchSize { fmt.Fprint(out, "\n") @@ -513,10 +505,6 @@ Forgejo or set your environment appropriately.`, "") return nil } - if git.CheckGitVersionAtLeast("2.29") != nil { - return fail(ctx, "No proc-receive support", "current git version doesn't support proc-receive.") - } - reader := bufio.NewReader(os.Stdin) repoUser := os.Getenv(repo_module.EnvRepoUsername) repoName := os.Getenv(repo_module.EnvRepoName) diff --git a/cmd/serv.go b/cmd/serv.go index 1fac2d13f5..b0571a276c 100644 --- a/cmd/serv.go +++ b/cmd/serv.go @@ -193,12 +193,10 @@ func runServ(ctx context.Context, c *cli.Command) error { } if len(words) < 2 { - if git.CheckGitVersionAtLeast("2.29") == nil { - // for AGit Flow - if cmd == "ssh_info" { - fmt.Print(`{"type":"agit","version":1}`) - return nil - } + // for AGit Flow + if cmd == "ssh_info" { + fmt.Print(`{"type":"agit","version":1}`) + return nil } return fail(ctx, "Too few arguments", "Too few arguments in cmd: %s", cmd) } diff --git a/go.mod b/go.mod index 510ec9c3ae..33ad5dbc67 100644 --- a/go.mod +++ b/go.mod @@ -79,7 +79,7 @@ require ( github.com/minio/minio-go/v7 v7.0.94 github.com/msteinert/pam/v2 v2.1.0 github.com/nektos/act v0.2.52 - github.com/niklasfasching/go-org v1.8.0 + github.com/niklasfasching/go-org v1.9.0 github.com/olivere/elastic/v7 v7.0.32 github.com/opencontainers/go-digest v1.0.0 github.com/opencontainers/image-spec v1.1.1 diff --git a/go.sum b/go.sum index 53558fddd7..45497a1b49 100644 --- a/go.sum +++ b/go.sum @@ -426,8 +426,8 @@ github.com/msteinert/pam/v2 v2.1.0 h1:er5F9TKV5nGFuTt12ubtqPHEUdeBwReP7vd3wovidG github.com/msteinert/pam/v2 v2.1.0/go.mod h1:KT28NNIcDFf3PcBmNI2mIGO4zZJ+9RSs/At2PB3IDVc= 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/niklasfasching/go-org v1.8.0 h1:WyGLaajLLp8JbQzkmapZ1y0MOzKuKV47HkZRloi+HGY= -github.com/niklasfasching/go-org v1.8.0/go.mod h1:e2A9zJs7cdONrEGs3gvxCcaAEpwwPNPG7csDpXckMNg= +github.com/niklasfasching/go-org v1.9.0 h1:4/Sr68Qx06hjC9MVDB/4etGP67JionLHGscLMOClpnk= +github.com/niklasfasching/go-org v1.9.0/go.mod h1:ZAGFFkWvUQcpazmi/8nHqwvARpr1xpb+Es67oUGX/48= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= diff --git a/models/fixtures/comment.yml b/models/fixtures/comment.yml index 34407d6f81..6908d85dda 100644 --- a/models/fixtures/comment.yml +++ b/models/fixtures/comment.yml @@ -186,10 +186,46 @@ type: 8 # milestone poster_id: 1 issue_id: 1 # in repo_id 1 - milestone_id: 10 # not exsting milestone + milestone_id: 10 # not existing milestone old_milestone_id: 0 created_unix: 946685080 +- + id: 2004 + type: 8 # milestone + poster_id: 1 + issue_id: 1 # in repo_id 1 + milestone_id: 1 + old_milestone_id: 10 # not existing (ghost) milestone + created_unix: 946685085 + +- + id: 2005 + type: 8 # milestone + poster_id: 1 + issue_id: 1 # in repo_id 1 + milestone_id: 10 # not existing (ghost) milestone + old_milestone_id: 1 + created_unix: 946685090 + +- + id: 2006 + type: 8 # milestone + poster_id: 1 + issue_id: 1 # in repo_id 1 + milestone_id: 11 # not existing (ghost) milestone + old_milestone_id: 10 # not existing (ghost) milestone + created_unix: 946685095 + +- + id: 2007 + type: 8 # milestone + poster_id: 1 + issue_id: 1 # in repo_id 1 + milestone_id: 0 + old_milestone_id: 11 # not existing (ghost) milestone + created_unix: 946685100 + - id: 2010 type: 30 # project diff --git a/models/fixtures/repository.yml b/models/fixtures/repository.yml index c383fa43ac..2f104eed65 100644 --- a/models/fixtures/repository.yml +++ b/models/fixtures/repository.yml @@ -32,7 +32,7 @@ created_unix: 1731254961 updated_unix: 1731254961 topics: '[]' - + - id: 2 owner_id: 2 diff --git a/models/issues/comment_list.go b/models/issues/comment_list.go index 7285e347b4..9b502d1c91 100644 --- a/models/issues/comment_list.go +++ b/models/issues/comment_list.go @@ -101,7 +101,7 @@ func (comments CommentList) loadMilestones(ctx context.Context) error { return nil } - milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs)) + milestones := make(map[int64]*Milestone, len(milestoneIDs)) left := len(milestoneIDs) for left > 0 { limit := db.DefaultMaxInSize @@ -110,7 +110,7 @@ func (comments CommentList) loadMilestones(ctx context.Context) error { } err := db.GetEngine(ctx). In("id", milestoneIDs[:limit]). - Find(&milestoneMaps) + Find(&milestones) if err != nil { return err } @@ -118,8 +118,8 @@ func (comments CommentList) loadMilestones(ctx context.Context) error { milestoneIDs = milestoneIDs[limit:] } - for _, issue := range comments { - issue.Milestone = milestoneMaps[issue.MilestoneID] + for _, comment := range comments { + comment.Milestone = milestones[comment.MilestoneID] } return nil } @@ -140,7 +140,7 @@ func (comments CommentList) loadOldMilestones(ctx context.Context) error { return nil } - milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs)) + milestones := make(map[int64]*Milestone, len(milestoneIDs)) left := len(milestoneIDs) for left > 0 { limit := db.DefaultMaxInSize @@ -149,7 +149,7 @@ func (comments CommentList) loadOldMilestones(ctx context.Context) error { } err := db.GetEngine(ctx). In("id", milestoneIDs[:limit]). - Find(&milestoneMaps) + Find(&milestones) if err != nil { return err } @@ -157,8 +157,8 @@ func (comments CommentList) loadOldMilestones(ctx context.Context) error { milestoneIDs = milestoneIDs[limit:] } - for _, issue := range comments { - issue.OldMilestone = milestoneMaps[issue.MilestoneID] + for _, comment := range comments { + comment.OldMilestone = milestones[comment.OldMilestoneID] } return nil } diff --git a/models/issues/issue_search.go b/models/issues/issue_search.go index 91a69c26a7..529f0c15d4 100644 --- a/models/issues/issue_search.go +++ b/models/issues/issue_search.go @@ -48,7 +48,9 @@ type IssuesOptions struct { //nolint UpdatedBeforeUnix int64 // prioritize issues from this repo PriorityRepoID int64 - IsArchived optional.Option[bool] + // if this issue index (not ID) exists and matches the filters, *and* priorityrepo sort is used, show it first + PriorityIssueIndex int64 + IsArchived optional.Option[bool] // If combined with AllPublic, then private as well as public issues // that matches the criteria will be returned, if AllPublic is false @@ -60,7 +62,7 @@ type IssuesOptions struct { //nolint // applySorts sort an issues-related session based on the provided // sortType string -func applySorts(sess *xorm.Session, sortType string, priorityRepoID int64) { +func applySorts(sess *xorm.Session, sortType string, priorityRepoID, priorityIssueIndex int64) { switch sortType { case "oldest": sess.Asc("issue.created_unix").Asc("issue.id") @@ -97,8 +99,11 @@ func applySorts(sess *xorm.Session, sortType string, priorityRepoID int64) { case "priorityrepo": sess.OrderBy("CASE "+ "WHEN issue.repo_id = ? THEN 1 "+ - "ELSE 2 END ASC", priorityRepoID). - Desc("issue.created_unix"). + "ELSE 2 END ASC", priorityRepoID) + if priorityIssueIndex != 0 { + sess.OrderBy("issue.index = ? DESC", priorityIssueIndex) + } + sess.Desc("issue.created_unix"). Desc("issue.id") case "project-column-sorting": sess.Asc("project_issue.sorting").Desc("issue.created_unix").Desc("issue.id") @@ -470,7 +475,7 @@ func Issues(ctx context.Context, opts *IssuesOptions) (IssueList, error) { Join("INNER", "repository", "`issue`.repo_id = `repository`.id") applyLimit(sess, opts) applyConditions(sess, opts) - applySorts(sess, opts.SortType, opts.PriorityRepoID) + applySorts(sess, opts.SortType, opts.PriorityRepoID, opts.PriorityIssueIndex) issues := IssueList{} if err := sess.Find(&issues); err != nil { @@ -494,7 +499,7 @@ func IssueIDs(ctx context.Context, opts *IssuesOptions, otherConds ...builder.Co } applyLimit(sess, opts) - applySorts(sess, opts.SortType, opts.PriorityRepoID) + applySorts(sess, opts.SortType, opts.PriorityRepoID, opts.PriorityIssueIndex) var res []int64 total, err := sess.Select("`issue`.id").Table(&Issue{}).FindAndCount(&res) diff --git a/models/issues/pull_list.go b/models/issues/pull_list.go index 8fc0491026..ddb813cf44 100644 --- a/models/issues/pull_list.go +++ b/models/issues/pull_list.go @@ -149,7 +149,7 @@ func PullRequests(ctx context.Context, baseRepoID int64, opts *PullRequestsOptio } findSession := listPullRequestStatement(ctx, baseRepoID, opts) - applySorts(findSession, opts.SortType, 0) + applySorts(findSession, opts.SortType, 0, 0) findSession = db.SetSessionPagination(findSession, opts) prs := make([]*PullRequest, 0, opts.PageSize) found := findSession.Find(&prs) diff --git a/models/moderation/abuse_report.go b/models/moderation/abuse_report.go index dadd61a95e..3a6244ef4c 100644 --- a/models/moderation/abuse_report.go +++ b/models/moderation/abuse_report.go @@ -100,7 +100,7 @@ type AbuseReport struct { // The abuse category selected by the reporter. Category AbuseCategoryType `xorm:"INDEX NOT NULL"` // Remarks provided by the reporter. - Remarks string + Remarks string `xorm:"VARCHAR(500)"` // The ID of the corresponding shadow-copied content when exists; otherwise null. ShadowCopyID sql.NullInt64 `xorm:"DEFAULT NULL"` CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"` diff --git a/models/moderation/shadow_copy.go b/models/moderation/shadow_copy.go index cdd8f69c52..d363610a48 100644 --- a/models/moderation/shadow_copy.go +++ b/models/moderation/shadow_copy.go @@ -17,7 +17,7 @@ import ( type AbuseReportShadowCopy struct { ID int64 `xorm:"pk autoincr"` - RawValue string `xorm:"NOT NULL"` + RawValue string `xorm:"LONGTEXT NOT NULL"` // A JSON with relevant fields from user, repository, issue or comment table. CreatedUnix timeutil.TimeStamp `xorm:"created NOT NULL"` } diff --git a/modules/git/blame.go b/modules/git/blame.go index 4ff347e31b..868edab2b8 100644 --- a/modules/git/blame.go +++ b/modules/git/blame.go @@ -132,7 +132,7 @@ func (r *BlameReader) Close() error { // CreateBlameReader creates reader for given repository, commit and file func CreateBlameReader(ctx context.Context, objectFormat ObjectFormat, repoPath string, commit *Commit, file string, bypassBlameIgnore bool) (*BlameReader, error) { var ignoreRevsFile *string - if CheckGitVersionAtLeast("2.23") == nil && !bypassBlameIgnore { + if !bypassBlameIgnore { ignoreRevsFile = tryCreateBlameIgnoreRevsFile(commit) } diff --git a/modules/git/blob.go b/modules/git/blob.go index 30615afe32..14ca2b1445 100644 --- a/modules/git/blob.go +++ b/modules/git/blob.go @@ -8,6 +8,7 @@ import ( "bufio" "bytes" "encoding/base64" + "fmt" "io" "forgejo.org/modules/log" @@ -172,33 +173,43 @@ func (b *Blob) GetBlobContent(limit int64) (string, error) { return string(buf), err } -// GetBlobContentBase64 Reads the content of the blob with a base64 encode and returns the encoded string -func (b *Blob) GetBlobContentBase64() (string, error) { - dataRc, err := b.DataAsync() - if err != nil { - return "", err - } - defer dataRc.Close() +type BlobTooLargeError struct { + Size, Limit int64 +} - pr, pw := io.Pipe() - encoder := base64.NewEncoder(base64.StdEncoding, pw) +func (b BlobTooLargeError) Error() string { + return fmt.Sprintf("blob: content larger than limit (%d > %d)", b.Size, b.Limit) +} - go func() { - _, err := io.Copy(encoder, dataRc) - _ = encoder.Close() - - if err != nil { - _ = pw.CloseWithError(err) - } else { - _ = pw.Close() +// GetContentBase64 Reads the content of the blob and returns it as base64 encoded string. +// Returns [BlobTooLargeError] if the (unencoded) content is larger than the limit. +func (b *Blob) GetContentBase64(limit int64) (string, error) { + if b.Size() > limit { + return "", BlobTooLargeError{ + Size: b.Size(), + Limit: limit, } - }() + } - out, err := io.ReadAll(pr) + rc, size, err := b.NewTruncatedReader(limit) if err != nil { return "", err } - return string(out), nil + defer rc.Close() + + encoding := base64.StdEncoding + buf := bytes.NewBuffer(make([]byte, 0, encoding.EncodedLen(int(size)))) + + encoder := base64.NewEncoder(encoding, buf) + + if _, err := io.Copy(encoder, rc); err != nil { + return "", err + } + if err := encoder.Close(); err != nil { + return "", err + } + + return buf.String(), nil } // GuessContentType guesses the content type of the blob. diff --git a/modules/git/blob_test.go b/modules/git/blob_test.go index 54115013d3..a4b8033941 100644 --- a/modules/git/blob_test.go +++ b/modules/git/blob_test.go @@ -63,6 +63,24 @@ func TestBlob(t *testing.T) { require.Equal(t, "file2\n", r) }) + t.Run("GetContentBase64", func(t *testing.T) { + r, err := testBlob.GetContentBase64(100) + require.NoError(t, err) + require.Equal(t, "ZmlsZTIK", r) + + r, err = testBlob.GetContentBase64(-1) + require.ErrorAs(t, err, &BlobTooLargeError{}) + require.Empty(t, r) + + r, err = testBlob.GetContentBase64(4) + require.ErrorAs(t, err, &BlobTooLargeError{}) + require.Empty(t, r) + + r, err = testBlob.GetContentBase64(6) + require.NoError(t, err) + require.Equal(t, "ZmlsZTIK", r) + }) + t.Run("NewTruncatedReader", func(t *testing.T) { // read fewer than available rc, size, err := testBlob.NewTruncatedReader(100) diff --git a/modules/git/commit.go b/modules/git/commit.go index 96831e3ae4..1228b4523b 100644 --- a/modules/git/commit.go +++ b/modules/git/commit.go @@ -412,11 +412,7 @@ func (c *Commit) GetSubModule(entryname string) (string, error) { // GetBranchName gets the closest branch name (as returned by 'git name-rev --name-only') func (c *Commit) GetBranchName() (string, error) { - cmd := NewCommand(c.repo.Ctx, "name-rev") - if CheckGitVersionAtLeast("2.13.0") == nil { - cmd.AddArguments("--exclude", "refs/tags/*") - } - cmd.AddArguments("--name-only", "--no-undefined").AddDynamicArguments(c.ID.String()) + cmd := NewCommand(c.repo.Ctx, "name-rev", "--exclude", "refs/tags/*", "--name-only", "--no-undefined").AddDynamicArguments(c.ID.String()) data, _, err := cmd.RunStdString(&RunOpts{Dir: c.repo.Path}) if err != nil { // handle special case where git can not describe commit diff --git a/modules/git/git.go b/modules/git/git.go index 1dfd0b5134..851b090b53 100644 --- a/modules/git/git.go +++ b/modules/git/git.go @@ -23,7 +23,7 @@ import ( ) // RequiredVersion is the minimum Git version required -const RequiredVersion = "2.0.0" +const RequiredVersion = "2.34.1" var ( // GitExecutable is the command name of git @@ -33,7 +33,6 @@ var ( // DefaultContext is the default context to run git commands in, must be initialized by git.InitXxx DefaultContext context.Context - SupportProcReceive bool // >= 2.29 SupportHashSha256 bool // >= 2.42, SHA-256 repositories no longer an ‘experimental curiosity’ InvertedGitFlushEnv bool // 2.43.1 SupportCheckAttrOnBare bool // >= 2.40 @@ -113,7 +112,7 @@ func VersionInfo() string { format := "%s" args := []any{GitVersion.Original()} // Since git wire protocol has been released from git v2.18 - if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil { + if setting.Git.EnableAutoGitWireProtocol { format += ", Wire Protocol %s Enabled" args = append(args, "Version 2") // for focus color } @@ -172,16 +171,13 @@ func InitFull(ctx context.Context) (err error) { _ = os.Setenv("GNUPGHOME", filepath.Join(HomeDir(), ".gnupg")) } - // Since git wire protocol has been released from git v2.18 - if setting.Git.EnableAutoGitWireProtocol && CheckGitVersionAtLeast("2.18") == nil { + if setting.Git.EnableAutoGitWireProtocol { globalCommandArgs = append(globalCommandArgs, "-c", "protocol.version=2") } // Explicitly disable credential helper, otherwise Git credentials might leak - if CheckGitVersionAtLeast("2.9") == nil { - globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=") - } - SupportProcReceive = CheckGitVersionAtLeast("2.29") == nil + globalCommandArgs = append(globalCommandArgs, "-c", "credential.helper=") + SupportHashSha256 = CheckGitVersionAtLeast("2.42") == nil SupportCheckAttrOnBare = CheckGitVersionAtLeast("2.40") == nil if SupportHashSha256 { @@ -195,9 +191,6 @@ func InitFull(ctx context.Context) (err error) { SupportGrepMaxCount = CheckGitVersionAtLeast("2.38") == nil if setting.LFS.StartServer { - if CheckGitVersionAtLeast("2.1.2") != nil { - return errors.New("LFS server support requires Git >= 2.1.2") - } globalCommandArgs = append(globalCommandArgs, "-c", "filter.lfs.required=", "-c", "filter.lfs.smudge=", "-c", "filter.lfs.clean=") } @@ -234,38 +227,28 @@ func syncGitConfig() (err error) { } } - // Set git some configurations - these must be set to these values for gitea to work correctly + // Set git some configurations - these must be set to these values for forgejo to work correctly if err := configSet("core.quotePath", "false"); err != nil { return err } - if CheckGitVersionAtLeast("2.10") == nil { - if err := configSet("receive.advertisePushOptions", "true"); err != nil { - return err - } + if err := configSet("receive.advertisePushOptions", "true"); err != nil { + return err } - if CheckGitVersionAtLeast("2.18") == nil { - if err := configSet("core.commitGraph", "true"); err != nil { - return err - } - if err := configSet("gc.writeCommitGraph", "true"); err != nil { - return err - } - if err := configSet("fetch.writeCommitGraph", "true"); err != nil { - return err - } + if err := configSet("core.commitGraph", "true"); err != nil { + return err + } + if err := configSet("gc.writeCommitGraph", "true"); err != nil { + return err + } + if err := configSet("fetch.writeCommitGraph", "true"); err != nil { + return err } - if SupportProcReceive { - // set support for AGit flow - if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil { - return err - } - } else { - if err := configUnsetAll("receive.procReceiveRefs", "refs/for"); err != nil { - return err - } + // set support for AGit flow + if err := configAddNonExist("receive.procReceiveRefs", "refs/for"); err != nil { + return err } // Due to CVE-2022-24765, git now denies access to git directories which are not owned by current user @@ -284,11 +267,6 @@ func syncGitConfig() (err error) { switch setting.Repository.Signing.Format { case "ssh": - // First do a git version check. - if CheckGitVersionAtLeast("2.34.0") != nil { - return errors.New("ssh signing requires Git >= 2.34.0") - } - // Get the ssh-keygen binary that Git will use. // This can be overridden in app.ini in [git.config] section, so we must // query this information. @@ -325,8 +303,7 @@ func syncGitConfig() (err error) { } } - // By default partial clones are disabled, enable them from git v2.22 - if !setting.Git.DisablePartialClone && CheckGitVersionAtLeast("2.22") == nil { + if !setting.Git.DisablePartialClone { if err = configSet("uploadpack.allowfilter", "true"); err != nil { return err } diff --git a/modules/git/git_test.go b/modules/git/git_test.go index 01200dba68..38d4db169c 100644 --- a/modules/git/git_test.go +++ b/modules/git/git_test.go @@ -14,7 +14,6 @@ import ( "forgejo.org/modules/test" "forgejo.org/modules/util" - "github.com/hashicorp/go-version" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -105,10 +104,6 @@ func TestSyncConfigGPGFormat(t *testing.T) { }) t.Run("SSH format", func(t *testing.T) { - if CheckGitVersionAtLeast("2.34.0") != nil { - t.SkipNow() - } - r, err := os.OpenRoot(t.TempDir()) require.NoError(t, err) f, err := r.OpenFile("ssh-keygen", os.O_CREATE|os.O_TRUNC, 0o700) @@ -121,13 +116,6 @@ func TestSyncConfigGPGFormat(t *testing.T) { assert.True(t, gitConfigContains("[gpg]")) assert.True(t, gitConfigContains("format = ssh")) - t.Run("Old version", func(t *testing.T) { - oldVersion, err := version.NewVersion("2.33.0") - require.NoError(t, err) - defer test.MockVariableValue(&GitVersion, oldVersion)() - require.ErrorContains(t, syncGitConfig(), "ssh signing requires Git >= 2.34.0") - }) - t.Run("No ssh-keygen binary", func(t *testing.T) { require.NoError(t, r.Remove("ssh-keygen")) require.ErrorContains(t, syncGitConfig(), "git signing requires a ssh-keygen binary") diff --git a/modules/git/pipeline/revlist.go b/modules/git/pipeline/revlist.go index f39b7113bb..1ee8921854 100644 --- a/modules/git/pipeline/revlist.go +++ b/modules/git/pipeline/revlist.go @@ -16,26 +16,6 @@ import ( "forgejo.org/modules/log" ) -// RevListAllObjects runs rev-list --objects --all and writes to a pipewriter -func RevListAllObjects(ctx context.Context, revListWriter *io.PipeWriter, wg *sync.WaitGroup, basePath string, errChan chan<- error) { - defer wg.Done() - defer revListWriter.Close() - - stderr := new(bytes.Buffer) - var errbuf strings.Builder - cmd := git.NewCommand(ctx, "rev-list", "--objects", "--all") - if err := cmd.Run(&git.RunOpts{ - Dir: basePath, - Stdout: revListWriter, - Stderr: stderr, - }); err != nil { - log.Error("git rev-list --objects --all [%s]: %v - %s", basePath, err, errbuf.String()) - err = fmt.Errorf("git rev-list --objects --all [%s]: %w - %s", basePath, err, errbuf.String()) - _ = revListWriter.CloseWithError(err) - errChan <- err - } -} - // RevListObjects run rev-list --objects from headSHA to baseSHA func RevListObjects(ctx context.Context, revListWriter *io.PipeWriter, wg *sync.WaitGroup, tmpBasePath, headSHA, baseSHA string, errChan chan<- error) { defer wg.Done() diff --git a/modules/git/remote.go b/modules/git/remote.go index fb66d76ff0..83a02fe2be 100644 --- a/modules/git/remote.go +++ b/modules/git/remote.go @@ -12,14 +12,7 @@ import ( // GetRemoteAddress returns remote url of git repository in the repoPath with special remote name func GetRemoteAddress(ctx context.Context, repoPath, remoteName string) (string, error) { - var cmd *Command - if CheckGitVersionAtLeast("2.7") == nil { - cmd = NewCommand(ctx, "remote", "get-url").AddDynamicArguments(remoteName) - } else { - cmd = NewCommand(ctx, "config", "--get").AddDynamicArguments("remote." + remoteName + ".url") - } - - result, _, err := cmd.RunStdString(&RunOpts{Dir: repoPath}) + result, _, err := NewCommand(ctx, "remote", "get-url").AddDynamicArguments(remoteName).RunStdString(&RunOpts{Dir: repoPath}) if err != nil { return "", err } diff --git a/modules/git/repo_attribute_test.go b/modules/git/repo_attribute_test.go index c69382e245..3d2c845fa0 100644 --- a/modules/git/repo_attribute_test.go +++ b/modules/git/repo_attribute_test.go @@ -5,7 +5,6 @@ package git import ( "context" - "fmt" "io" "io/fs" "os" @@ -197,7 +196,7 @@ func TestGitAttributeCheckerError(t *testing.T) { path := t.TempDir() // we can't use unittest.CopyDir because of an import cycle (git.Init in unittest) - require.NoError(t, CopyFS(path, os.DirFS(filepath.Join(testReposDir, "language_stats_repo")))) + require.NoError(t, os.CopyFS(path, os.DirFS(filepath.Join(testReposDir, "language_stats_repo")))) gitRepo, err := openRepositoryWithDefaultContext(path) require.NoError(t, err) @@ -324,32 +323,3 @@ func TestGitAttributeCheckerError(t *testing.T) { require.ErrorIs(t, err, fs.ErrClosed) }) } - -// CopyFS is adapted from https://github.com/golang/go/issues/62484 -// which should be available with go1.23 -func CopyFS(dir string, fsys fs.FS) error { - return fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, _ error) error { - targ := filepath.Join(dir, filepath.FromSlash(path)) - if d.IsDir() { - return os.MkdirAll(targ, 0o777) - } - r, err := fsys.Open(path) - if err != nil { - return err - } - defer r.Close() - info, err := r.Stat() - if err != nil { - return err - } - w, err := os.OpenFile(targ, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o666|info.Mode()&0o777) - if err != nil { - return err - } - if _, err := io.Copy(w, r); err != nil { - w.Close() - return fmt.Errorf("copying %s: %v", path, err) - } - return w.Close() - }) -} diff --git a/modules/git/repo_commit.go b/modules/git/repo_commit.go index 4c8516f828..41ca0e39b1 100644 --- a/modules/git/repo_commit.go +++ b/modules/git/repo_commit.go @@ -443,42 +443,18 @@ func (repo *Repository) getCommitsBeforeLimit(id ObjectID, num int) ([]*Commit, } func (repo *Repository) getBranches(commit *Commit, limit int) ([]string, error) { - if CheckGitVersionAtLeast("2.7.0") == nil { - command := NewCommand(repo.Ctx, "for-each-ref", "--format=%(refname:strip=2)").AddOptionValues("--contains", commit.ID.String(), BranchPrefix) + command := NewCommand(repo.Ctx, "for-each-ref", "--format=%(refname:strip=2)").AddOptionValues("--contains", commit.ID.String(), BranchPrefix) - if limit != -1 { - command = command.AddOptionFormat("--count=%d", limit) - } - - stdout, _, err := command.RunStdString(&RunOpts{Dir: repo.Path}) - if err != nil { - return nil, err - } - - branches := strings.Fields(stdout) - return branches, nil + if limit != -1 { + command = command.AddOptionFormat("--count=%d", limit) } - stdout, _, err := NewCommand(repo.Ctx, "branch").AddOptionValues("--contains", commit.ID.String()).RunStdString(&RunOpts{Dir: repo.Path}) + stdout, _, err := command.RunStdString(&RunOpts{Dir: repo.Path}) if err != nil { return nil, err } - refs := strings.Split(stdout, "\n") - - var max int - if len(refs) > limit { - max = limit - } else { - max = len(refs) - 1 - } - - branches := make([]string, max) - for i, ref := range refs[:max] { - parts := strings.Fields(ref) - - branches[i] = parts[len(parts)-1] - } + branches := strings.Fields(stdout) return branches, nil } diff --git a/modules/git/repo_commitgraph.go b/modules/git/repo_commitgraph.go index 492438be37..c3647bd894 100644 --- a/modules/git/repo_commitgraph.go +++ b/modules/git/repo_commitgraph.go @@ -11,10 +11,8 @@ import ( // WriteCommitGraph write commit graph to speed up repo access // this requires git v2.18 to be installed func WriteCommitGraph(ctx context.Context, repoPath string) error { - if CheckGitVersionAtLeast("2.18") == nil { - if _, _, err := NewCommand(ctx, "commit-graph", "write").RunStdString(&RunOpts{Dir: repoPath}); err != nil { - return fmt.Errorf("unable to write commit-graph for '%s' : %w", repoPath, err) - } + if _, _, err := NewCommand(ctx, "commit-graph", "write").RunStdString(&RunOpts{Dir: repoPath}); err != nil { + return fmt.Errorf("unable to write commit-graph for '%s' : %w", repoPath, err) } return nil } diff --git a/modules/git/tree_entry.go b/modules/git/tree_entry.go index d51b7992fe..ec5c632ca0 100644 --- a/modules/git/tree_entry.go +++ b/modules/git/tree_entry.go @@ -116,32 +116,37 @@ func (te *TreeEntry) Type() string { } } +// LinkTarget returns the target of the symlink as string. +func (te *TreeEntry) LinkTarget() (string, error) { + if !te.IsLink() { + return "", ErrBadLink{te.Name(), "not a symlink"} + } + + const symlinkLimit = 4096 // according to git config core.longpaths https://stackoverflow.com/a/22575737 + blob := te.Blob() + if blob.Size() > symlinkLimit { + return "", ErrBadLink{te.Name(), "symlink too large"} + } + + rc, size, err := blob.NewTruncatedReader(symlinkLimit) + if err != nil { + return "", err + } + defer rc.Close() + + buf := make([]byte, int(size)) + _, err = io.ReadFull(rc, buf) + return string(buf), err +} + // FollowLink returns the entry pointed to by a symlink func (te *TreeEntry) FollowLink() (*TreeEntry, string, error) { - if !te.IsLink() { - return nil, "", ErrBadLink{te.Name(), "not a symlink"} - } - // read the link - r, err := te.Blob().DataAsync() + lnk, err := te.LinkTarget() if err != nil { return nil, "", err } - closed := false - defer func() { - if !closed { - _ = r.Close() - } - }() - buf := make([]byte, te.Size()) - _, err = io.ReadFull(r, buf) - if err != nil { - return nil, "", err - } - _ = r.Close() - closed = true - lnk := string(buf) t := te.ptree // traverse up directories diff --git a/modules/indexer/issues/bleve/bleve.go b/modules/indexer/issues/bleve/bleve.go index 573d63a446..8549ba8dfc 100644 --- a/modules/indexer/issues/bleve/bleve.go +++ b/modules/indexer/issues/bleve/bleve.go @@ -170,7 +170,7 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( if issueID, err := token.ParseIssueReference(); err == nil { idQuery := inner_bleve.NumericEqualityQuery(issueID, "index") - idQuery.SetBoost(5.0) + idQuery.SetBoost(20.0) innerQ.AddQuery(idQuery) } @@ -197,6 +197,15 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( queries = append(queries, bleve.NewDisjunctionQuery(repoQueries...)) } + if options.PriorityRepoID.Has() { + eq := inner_bleve.NumericEqualityQuery(options.PriorityRepoID.Value(), "repo_id") + eq.SetBoost(10.0) + meh := bleve.NewMatchAllQuery() + meh.SetBoost(0) + should := bleve.NewDisjunctionQuery(eq, meh) + queries = append(queries, should) + } + if options.IsPull.Has() { queries = append(queries, inner_bleve.BoolFieldQuery(options.IsPull.Value(), "is_pull")) } diff --git a/modules/indexer/issues/db/db.go b/modules/indexer/issues/db/db.go index 397daa3265..5f42bce9a1 100644 --- a/modules/indexer/issues/db/db.go +++ b/modules/indexer/issues/db/db.go @@ -53,6 +53,7 @@ func (i *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( cond := builder.NewCond() + var priorityIssueIndex int64 if options.Keyword != "" { repoCond := builder.In("repo_id", options.RepoIDs) if len(options.RepoIDs) == 1 { @@ -82,6 +83,7 @@ func (i *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( builder.Eq{"`index`": issueID}, cond, ) + priorityIssueIndex = issueID } } @@ -89,6 +91,7 @@ func (i *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( if err != nil { return nil, err } + opt.PriorityIssueIndex = priorityIssueIndex // If pagesize == 0, return total count only. It's a special case for search count. if options.Paginator != nil && options.Paginator.PageSize == 0 { diff --git a/modules/indexer/issues/db/options.go b/modules/indexer/issues/db/options.go index 4411cc1c37..55a471fc8e 100644 --- a/modules/indexer/issues/db/options.go +++ b/modules/indexer/issues/db/options.go @@ -78,6 +78,11 @@ func ToDBOptions(ctx context.Context, options *internal.SearchOptions) (*issue_m User: nil, } + if options.PriorityRepoID.Has() { + opts.SortType = "priorityrepo" + opts.PriorityRepoID = options.PriorityRepoID.Value() + } + if len(options.MilestoneIDs) == 1 && options.MilestoneIDs[0] == 0 { opts.MilestoneIDs = []int64{db.NoConditionID} } else { diff --git a/modules/indexer/issues/elasticsearch/elasticsearch.go b/modules/indexer/issues/elasticsearch/elasticsearch.go index 9d2786e101..d632a22b2a 100644 --- a/modules/indexer/issues/elasticsearch/elasticsearch.go +++ b/modules/indexer/issues/elasticsearch/elasticsearch.go @@ -165,7 +165,7 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( } var eitherQ elastic.Query = innerQ if issueID, err := token.ParseIssueReference(); err == nil { - indexQ := elastic.NewTermQuery("index", issueID).Boost(15.0) + indexQ := elastic.NewTermQuery("index", issueID).Boost(20) eitherQ = elastic.NewDisMaxQuery().Query(indexQ).Query(innerQ).TieBreaker(0.5) } switch token.Kind { @@ -188,6 +188,10 @@ func (b *Indexer) Search(ctx context.Context, options *internal.SearchOptions) ( } query.Must(q) } + if options.PriorityRepoID.Has() { + q := elastic.NewTermQuery("repo_id", options.PriorityRepoID.Value()).Boost(10) + query.Should(q) + } if options.IsPull.Has() { query.Must(elastic.NewTermQuery("is_pull", options.IsPull.Value())) diff --git a/modules/indexer/issues/internal/model.go b/modules/indexer/issues/internal/model.go index 6c55405179..cdd113212d 100644 --- a/modules/indexer/issues/internal/model.go +++ b/modules/indexer/issues/internal/model.go @@ -75,8 +75,9 @@ type SearchResult struct { type SearchOptions struct { Keyword string // keyword to search - RepoIDs []int64 // repository IDs which the issues belong to - AllPublic bool // if include all public repositories + RepoIDs []int64 // repository IDs which the issues belong to + AllPublic bool // if include all public repositories + PriorityRepoID optional.Option[int64] // issues from this repository will be prioritized when SortByScore IsPull optional.Option[bool] // if the issues is a pull request IsClosed optional.Option[bool] // if the issues is closed diff --git a/modules/indexer/issues/internal/tests/tests.go b/modules/indexer/issues/internal/tests/tests.go index ef75955a14..b63957ff84 100644 --- a/modules/indexer/issues/internal/tests/tests.go +++ b/modules/indexer/issues/internal/tests/tests.go @@ -742,6 +742,25 @@ var cases = []*testIndexerCase{ } }, }, + { + Name: "PriorityRepoID", + SearchOptions: &internal.SearchOptions{ + IsPull: optional.Some(false), + IsClosed: optional.Some(false), + PriorityRepoID: optional.Some(int64(3)), + Paginator: &db.ListOptionsAll, + SortBy: internal.SortByScore, + }, + Expected: func(t *testing.T, data map[int64]*internal.IndexerData, result *internal.SearchResult) { + for i, v := range result.Hits { + if i < 7 { + assert.Equal(t, int64(3), data[v.ID].RepoID) + } else { + assert.NotEqual(t, int64(3), data[v.ID].RepoID) + } + } + }, + }, } type testIndexerCase struct { diff --git a/modules/lfs/pointer_scanner.go b/modules/lfs/pointer_scanner.go index 632ecd19ae..80da8e5222 100644 --- a/modules/lfs/pointer_scanner.go +++ b/modules/lfs/pointer_scanner.go @@ -39,16 +39,7 @@ func SearchPointerBlobs(ctx context.Context, repo *git.Repository, pointerChan c go pipeline.BlobsLessThan1024FromCatFileBatchCheck(catFileCheckReader, shasToBatchWriter, &wg) // 1. Run batch-check on all objects in the repository - if git.CheckGitVersionAtLeast("2.6.0") != nil { - revListReader, revListWriter := io.Pipe() - shasToCheckReader, shasToCheckWriter := io.Pipe() - wg.Add(2) - go pipeline.CatFileBatchCheck(ctx, shasToCheckReader, catFileCheckWriter, &wg, basePath) - go pipeline.BlobsFromRevListObjects(revListReader, shasToCheckWriter, &wg) - go pipeline.RevListAllObjects(ctx, revListWriter, &wg, basePath, errChan) - } else { - go pipeline.CatFileBatchCheckAllObjects(ctx, catFileCheckWriter, &wg, basePath, errChan) - } + go pipeline.CatFileBatchCheckAllObjects(ctx, catFileCheckWriter, &wg, basePath, errChan) wg.Wait() close(pointerChan) diff --git a/options/locale_next/locale_en-US.json b/options/locale_next/locale_en-US.json index b1c98e4551..a551db87dc 100644 --- a/options/locale_next/locale_en-US.json +++ b/options/locale_next/locale_en-US.json @@ -102,5 +102,6 @@ "admin.dashboard.cleanup_offline_runners": "Cleanup offline runners", "settings.visibility.description": "Profile visibility affects others' ability to access your non-private repositories. Learn more", "avatar.constraints_hint": "Custom avatar may not exceed %[1]s in size or be larger than %[2]dx%[3]d pixels", + "og.repo.summary_card.alt_description": "Summary card of repository %[1]s, described as: %[2]s", "meta.last_line": "Thank you for translating Forgejo! This line isn't seen by the users but it serves other purposes in the translation management. You can place a fun fact in the translation instead of translating it." } diff --git a/package-lock.json b/package-lock.json index 604ff38c18..06a9497efc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,7 +28,7 @@ "esbuild-loader": "4.3.0", "escape-goat": "4.0.0", "fast-glob": "3.3.3", - "htmx.org": "1.9.12", + "htmx.org": "2.0.6", "idiomorph": "0.3.0", "jquery": "3.7.1", "katex": "0.16.22", @@ -65,21 +65,21 @@ "@eslint-community/eslint-plugin-eslint-comments": "4.5.0", "@playwright/test": "1.52.0", "@stoplight/spectral-cli": "6.15.0", - "@stylistic/eslint-plugin": "4.4.1", - "@stylistic/stylelint-plugin": "3.1.2", - "@vitejs/plugin-vue": "5.2.4", + "@stylistic/eslint-plugin": "5.0.0", + "@stylistic/stylelint-plugin": "3.1.3", + "@vitejs/plugin-vue": "6.0.0", "@vitest/coverage-v8": "3.2.3", "@vitest/eslint-plugin": "1.2.2", "@vue/test-utils": "2.4.6", - "eslint": "9.28.0", - "eslint-import-resolver-typescript": "4.4.3", + "eslint": "9.30.0", + "eslint-import-resolver-typescript": "4.4.4", "eslint-plugin-array-func": "5.0.2", - "eslint-plugin-import-x": "4.15.1", + "eslint-plugin-import-x": "4.16.1", "eslint-plugin-no-jquery": "3.1.1", "eslint-plugin-no-use-extend-native": "0.7.2", "eslint-plugin-playwright": "2.2.0", "eslint-plugin-regexp": "2.9.0", - "eslint-plugin-sonarjs": "3.0.2", + "eslint-plugin-sonarjs": "3.0.4", "eslint-plugin-toml": "0.12.0", "eslint-plugin-unicorn": "59.0.1", "eslint-plugin-vitest-globals": "1.5.0", @@ -87,18 +87,18 @@ "eslint-plugin-vue-scoped-css": "2.10.0", "eslint-plugin-wc": "3.0.1", "globals": "16.1.0", - "happy-dom": "18.0.0", + "happy-dom": "18.0.1", "license-checker-rseidelsohn": "4.4.2", "markdownlint-cli": "0.45.0", "postcss-html": "1.8.0", "sharp": "0.34.2", - "stylelint": "16.20.0", + "stylelint": "16.21.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-strict-value": "1.10.11", "stylelint-value-no-unknown-custom-properties": "6.0.1", - "svgo": "3.2.0", + "svgo": "4.0.0", "typescript": "5.8.3", - "typescript-eslint": "8.34.0", + "typescript-eslint": "8.35.0", "vite-string-plugin": "1.3.4", "vitest": "3.2.3" }, @@ -216,12 +216,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.27.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz", - "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.7.tgz", + "integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==", "license": "MIT", "dependencies": { - "@babel/types": "^7.27.3" + "@babel/types": "^7.27.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -240,9 +240,9 @@ } }, "node_modules/@babel/types": { - "version": "7.27.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz", - "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.7.tgz", + "integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -1021,9 +1021,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.1.tgz", - "integrity": "sha512-OL0RJzC/CBzli0DrrR31qzj6d6i6Mm3HByuhflhl4LOBiWxN+3i6/t/ZQQNii4tjksXi8r2CRW1wMpWA2ULUEw==", + "version": "0.21.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.0.tgz", + "integrity": "sha512-ENIdc4iLu0d93HeYirvKmrzshzofPw6VkZRKQGe9Nv46ZnWUzcF1xV01dcvEg/1wXUR61OmmlSfyeyO7EvjLxQ==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1049,9 +1049,9 @@ } }, "node_modules/@eslint/config-helpers": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.3.tgz", - "integrity": "sha512-u180qk2Um1le4yf0ruXH3PYFeEZeYC3p/4wCTKrr2U1CmGdzGi3KtY0nuPDH48UJxlKCC5RDzbcbh4X0XlqgHg==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.3.0.tgz", + "integrity": "sha512-ViuymvFmcJi04qdZeDc2whTHryouGcDlaxPqarTD0ZE10ISpxGUVZGZDx4w01upyIynL3iu6IXH2bS1NhclQMw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -1146,9 +1146,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.28.0.tgz", - "integrity": "sha512-fnqSjGWd/CoIp4EXIxWVK/sHA6DOHN4+8Ix2cX5ycOY7LG0UY8nHCU5pIp2eaE1Mc7Qd8kHspYNzYXT2ojPLzg==", + "version": "9.30.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.30.0.tgz", + "integrity": "sha512-Wzw3wQwPvc9sHM+NjakWTcPx11mbZyiYHuwWa/QfZ7cIRX7WK54PSk7bdyXDaoaopUcMatv1zaQvOAAO8hCdww==", "dev": true, "license": "MIT", "engines": { @@ -1169,13 +1169,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.2.tgz", - "integrity": "sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==", + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz", + "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.0", + "@eslint/core": "^0.15.1", "levn": "^0.4.1" }, "engines": { @@ -1183,9 +1183,9 @@ } }, "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.0.tgz", - "integrity": "sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==", + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", + "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1884,17 +1884,13 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.9.tgz", + "integrity": "sha512-xpz6C/vXOegF9VEtlMBlkNNIjHrLhKaFBsO4lmQGr00x5BHp7p+oliR6i7LwIcM5cZU2VjLSwm2R+/zj5IjPWg==", "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -1906,19 +1902,10 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.7.tgz", + "integrity": "sha512-maArE+jvYbj06DXh2iFlXSSDjTWXODlPTQHdDRQdGoYw7KvT4SfYCnPHfCyww8Z3JqFsW0BBjPLj8A2fwAvv7Q==", "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -1926,15 +1913,15 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.1.tgz", + "integrity": "sha512-mBLKRHc7Ffw/hObYb9+cunuGNjshQk+vZdwZBJoqiysK/mW3Jq0UXosq8aIhMnLevANhR9yoYfdUEOHg6M9y0g==", "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.26", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.26.tgz", + "integrity": "sha512-Z9rjt4BUVEbLFpw0qjCklVxxf421wrmcbP4w+LmBUxYCyJTYYSclgJD0YsCgGqQCtCIPiz7kjbYYJiAKhjJ3kA==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -2221,6 +2208,13 @@ "object-assign": "^4.1.1" } }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.19", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.19.tgz", + "integrity": "sha512-3FL3mnMbPu0muGOCaKAhhFEYmqv9eTfPSJRJmANrCwtgK8VuxpsZDGK+m0LYAGoyO8+0j5uRe4PeyPDK1yA/hA==", + "dev": true, + "license": "MIT" + }, "node_modules/@rollup/plugin-commonjs": { "version": "22.0.2", "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-22.0.2.tgz", @@ -2269,9 +2263,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.0.tgz", - "integrity": "sha512-xEiEE5oDW6tK4jXCAyliuntGR+amEMO7HLtdSshVuhFnKTYoeYMyXQK7pLouAJJj5KHdwdn87bfHAR2nSdNAUA==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.44.1.tgz", + "integrity": "sha512-JAcBr1+fgqx20m7Fwe1DxPUl/hPkee6jA6Pl7n1v2EFiktAHenTaXl5aIFjUIEsfn9w3HE4gK1lEgNGMzBDs1w==", "cpu": [ "arm" ], @@ -2283,9 +2277,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.0.tgz", - "integrity": "sha512-uNSk/TgvMbskcHxXYHzqwiyBlJ/lGcv8DaUfcnNwict8ba9GTTNxfn3/FAoFZYgkaXXAdrAA+SLyKplyi349Jw==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.44.1.tgz", + "integrity": "sha512-RurZetXqTu4p+G0ChbnkwBuAtwAbIwJkycw1n6GvlGlBuS4u5qlr5opix8cBAYFJgaY05TWtM+LaoFggUmbZEQ==", "cpu": [ "arm64" ], @@ -2297,9 +2291,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.0.tgz", - "integrity": "sha512-VGF3wy0Eq1gcEIkSCr8Ke03CWT+Pm2yveKLaDvq51pPpZza3JX/ClxXOCmTYYq3us5MvEuNRTaeyFThCKRQhOA==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.44.1.tgz", + "integrity": "sha512-fM/xPesi7g2M7chk37LOnmnSTHLG/v2ggWqKj3CCA1rMA4mm5KVBT1fNoswbo1JhPuNNZrVwpTvlCVggv8A2zg==", "cpu": [ "arm64" ], @@ -2311,9 +2305,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.0.tgz", - "integrity": "sha512-fBkyrDhwquRvrTxSGH/qqt3/T0w5Rg0L7ZIDypvBPc1/gzjJle6acCpZ36blwuwcKD/u6oCE/sRWlUAcxLWQbQ==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.44.1.tgz", + "integrity": "sha512-gDnWk57urJrkrHQ2WVx9TSVTH7lSlU7E3AFqiko+bgjlh78aJ88/3nycMax52VIVjIm3ObXnDL2H00e/xzoipw==", "cpu": [ "x64" ], @@ -2325,9 +2319,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.0.tgz", - "integrity": "sha512-u5AZzdQJYJXByB8giQ+r4VyfZP+walV+xHWdaFx/1VxsOn6eWJhK2Vl2eElvDJFKQBo/hcYIBg/jaKS8ZmKeNQ==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.44.1.tgz", + "integrity": "sha512-wnFQmJ/zPThM5zEGcnDcCJeYJgtSLjh1d//WuHzhf6zT3Md1BvvhJnWoy+HECKu2bMxaIcfWiu3bJgx6z4g2XA==", "cpu": [ "arm64" ], @@ -2339,9 +2333,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.0.tgz", - "integrity": "sha512-qC0kS48c/s3EtdArkimctY7h3nHicQeEUdjJzYVJYR3ct3kWSafmn6jkNCA8InbUdge6PVx6keqjk5lVGJf99g==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.44.1.tgz", + "integrity": "sha512-uBmIxoJ4493YATvU2c0upGz87f99e3wop7TJgOA/bXMFd2SvKCI7xkxY/5k50bv7J6dw1SXT4MQBQSLn8Bb/Uw==", "cpu": [ "x64" ], @@ -2353,9 +2347,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.0.tgz", - "integrity": "sha512-x+e/Z9H0RAWckn4V2OZZl6EmV0L2diuX3QB0uM1r6BvhUIv6xBPL5mrAX2E3e8N8rEHVPwFfz/ETUbV4oW9+lQ==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.44.1.tgz", + "integrity": "sha512-n0edDmSHlXFhrlmTK7XBuwKlG5MbS7yleS1cQ9nn4kIeW+dJH+ExqNgQ0RrFRew8Y+0V/x6C5IjsHrJmiHtkxQ==", "cpu": [ "arm" ], @@ -2367,9 +2361,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.0.tgz", - "integrity": "sha512-1exwiBFf4PU/8HvI8s80icyCcnAIB86MCBdst51fwFmH5dyeoWVPVgmQPcKrMtBQ0W5pAs7jBCWuRXgEpRzSCg==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.44.1.tgz", + "integrity": "sha512-8WVUPy3FtAsKSpyk21kV52HCxB+me6YkbkFHATzC2Yd3yuqHwy2lbFL4alJOLXKljoRw08Zk8/xEj89cLQ/4Nw==", "cpu": [ "arm" ], @@ -2381,9 +2375,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.0.tgz", - "integrity": "sha512-ZTR2mxBHb4tK4wGf9b8SYg0Y6KQPjGpR4UWwTFdnmjB4qRtoATZ5dWn3KsDwGa5Z2ZBOE7K52L36J9LueKBdOQ==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.44.1.tgz", + "integrity": "sha512-yuktAOaeOgorWDeFJggjuCkMGeITfqvPgkIXhDqsfKX8J3jGyxdDZgBV/2kj/2DyPaLiX6bPdjJDTu9RB8lUPQ==", "cpu": [ "arm64" ], @@ -2395,9 +2389,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.0.tgz", - "integrity": "sha512-GFWfAhVhWGd4r6UxmnKRTBwP1qmModHtd5gkraeW2G490BpFOZkFtem8yuX2NyafIP/mGpRJgTJ2PwohQkUY/Q==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.44.1.tgz", + "integrity": "sha512-W+GBM4ifET1Plw8pdVaecwUgxmiH23CfAUj32u8knq0JPFyK4weRy6H7ooxYFD19YxBulL0Ktsflg5XS7+7u9g==", "cpu": [ "arm64" ], @@ -2409,9 +2403,9 @@ ] }, "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.0.tgz", - "integrity": "sha512-xw+FTGcov/ejdusVOqKgMGW3c4+AgqrfvzWEVXcNP6zq2ue+lsYUgJ+5Rtn/OTJf7e2CbgTFvzLW2j0YAtj0Gg==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.44.1.tgz", + "integrity": "sha512-1zqnUEMWp9WrGVuVak6jWTl4fEtrVKfZY7CvcBmUUpxAJ7WcSowPSAWIKa/0o5mBL/Ij50SIf9tuirGx63Ovew==", "cpu": [ "loong64" ], @@ -2423,9 +2417,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.0.tgz", - "integrity": "sha512-bKGibTr9IdF0zr21kMvkZT4K6NV+jjRnBoVMt2uNMG0BYWm3qOVmYnXKzx7UhwrviKnmK46IKMByMgvpdQlyJQ==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.44.1.tgz", + "integrity": "sha512-Rl3JKaRu0LHIx7ExBAAnf0JcOQetQffaw34T8vLlg9b1IhzcBgaIdnvEbbsZq9uZp3uAH+JkHd20Nwn0h9zPjA==", "cpu": [ "ppc64" ], @@ -2437,9 +2431,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.0.tgz", - "integrity": "sha512-vV3cL48U5kDaKZtXrti12YRa7TyxgKAIDoYdqSIOMOFBXqFj2XbChHAtXquEn2+n78ciFgr4KIqEbydEGPxXgA==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.44.1.tgz", + "integrity": "sha512-j5akelU3snyL6K3N/iX7otLBIl347fGwmd95U5gS/7z6T4ftK288jKq3A5lcFKcx7wwzb5rgNvAg3ZbV4BqUSw==", "cpu": [ "riscv64" ], @@ -2451,9 +2445,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.0.tgz", - "integrity": "sha512-TDKO8KlHJuvTEdfw5YYFBjhFts2TR0VpZsnLLSYmB7AaohJhM8ctDSdDnUGq77hUh4m/djRafw+9zQpkOanE2Q==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.44.1.tgz", + "integrity": "sha512-ppn5llVGgrZw7yxbIm8TTvtj1EoPgYUAbfw0uDjIOzzoqlZlZrLJ/KuiE7uf5EpTpCTrNt1EdtzF0naMm0wGYg==", "cpu": [ "riscv64" ], @@ -2465,9 +2459,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.0.tgz", - "integrity": "sha512-8541GEyktXaw4lvnGp9m84KENcxInhAt6vPWJ9RodsB/iGjHoMB2Pp5MVBCiKIRxrxzJhGCxmNzdu+oDQ7kwRA==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.44.1.tgz", + "integrity": "sha512-Hu6hEdix0oxtUma99jSP7xbvjkUM/ycke/AQQ4EC5g7jNRLLIwjcNwaUy95ZKBJJwg1ZowsclNnjYqzN4zwkAw==", "cpu": [ "s390x" ], @@ -2479,9 +2473,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.0.tgz", - "integrity": "sha512-iUVJc3c0o8l9Sa/qlDL2Z9UP92UZZW1+EmQ4xfjTc1akr0iUFZNfxrXJ/R1T90h/ILm9iXEY6+iPrmYB3pXKjw==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.44.1.tgz", + "integrity": "sha512-EtnsrmZGomz9WxK1bR5079zee3+7a+AdFlghyd6VbAjgRJDbTANJ9dcPIPAi76uG05micpEL+gPGmAKYTschQw==", "cpu": [ "x64" ], @@ -2493,9 +2487,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.0.tgz", - "integrity": "sha512-PQUobbhLTQT5yz/SPg116VJBgz+XOtXt8D1ck+sfJJhuEsMj2jSej5yTdp8CvWBSceu+WW+ibVL6dm0ptG5fcA==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.44.1.tgz", + "integrity": "sha512-iAS4p+J1az6Usn0f8xhgL4PaU878KEtutP4hqw52I4IO6AGoyOkHCxcc4bqufv1tQLdDWFx8lR9YlwxKuv3/3g==", "cpu": [ "x64" ], @@ -2507,9 +2501,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.0.tgz", - "integrity": "sha512-M0CpcHf8TWn+4oTxJfh7LQuTuaYeXGbk0eageVjQCKzYLsajWS/lFC94qlRqOlyC2KvRT90ZrfXULYmukeIy7w==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.44.1.tgz", + "integrity": "sha512-NtSJVKcXwcqozOl+FwI41OH3OApDyLk3kqTJgx8+gp6On9ZEt5mYhIsKNPGuaZr3p9T6NWPKGU/03Vw4CNU9qg==", "cpu": [ "arm64" ], @@ -2521,9 +2515,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.0.tgz", - "integrity": "sha512-3XJ0NQtMAXTWFW8FqZKcw3gOQwBtVWP/u8TpHP3CRPXD7Pd6s8lLdH3sHWh8vqKCyyiI8xW5ltJScQmBU9j7WA==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.44.1.tgz", + "integrity": "sha512-JYA3qvCOLXSsnTR3oiyGws1Dm0YTuxAAeaYGVlGpUsHqloPcFjPg+X0Fj2qODGLNwQOAcCiQmHub/V007kiH5A==", "cpu": [ "ia32" ], @@ -2535,9 +2529,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.0.tgz", - "integrity": "sha512-Q2Mgwt+D8hd5FIPUuPDsvPR7Bguza6yTkJxspDGkZj7tBRn2y4KSWYuIXpftFSjBra76TbKerCV7rgFPQrn+wQ==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.44.1.tgz", + "integrity": "sha512-J8o22LuF0kTe7m+8PvW9wk3/bRq5+mRo5Dqo6+vXb7otCm3TPhYOJqOaQtGU9YMWQSL3krMnoOxMr0+9E6F3Ug==", "cpu": [ "x64" ], @@ -3056,15 +3050,16 @@ } }, "node_modules/@stylistic/eslint-plugin": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-4.4.1.tgz", - "integrity": "sha512-CEigAk7eOLyHvdgmpZsKFwtiqS2wFwI1fn4j09IU9GmD4euFM4jEBAViWeCqaNLlbX2k2+A/Fq9cje4HQBXuJQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.0.0.tgz", + "integrity": "sha512-nVV2FSzeTJ3oFKw+3t9gQYQcrgbopgCASSY27QOtkhEGgSfdQQjDmzZd41NeT1myQ8Wc6l+pZllST9qIu4NKzg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/utils": "^8.32.1", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/types": "^8.34.1", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "estraverse": "^5.3.0", "picomatch": "^4.0.2" }, @@ -3089,9 +3084,9 @@ } }, "node_modules/@stylistic/stylelint-plugin": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-3.1.2.tgz", - "integrity": "sha512-tylFJGMQo62alGazK74MNxFjMagYOHmBZiePZFOJK2n13JZta0uVkB3Bh5qodUmOLtRH+uxH297EibK14UKm8g==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@stylistic/stylelint-plugin/-/stylelint-plugin-3.1.3.tgz", + "integrity": "sha512-85fsmzgsIVmyG3/GFrjuYj6Cz8rAM7IZiPiXCMiSMfoDOC1lOrzrXPDk24WqviAghnPqGpx8b0caK2PuewWGFg==", "dev": true, "license": "MIT", "dependencies": { @@ -3099,10 +3094,10 @@ "@csstools/css-tokenizer": "^3.0.1", "@csstools/media-query-list-parser": "^3.0.1", "is-plain-object": "^5.0.0", + "postcss": "^8.4.41", "postcss-selector-parser": "^6.1.2", "postcss-value-parser": "^4.2.0", - "style-search": "^0.1.0", - "stylelint": "^16.8.2" + "style-search": "^0.1.0" }, "engines": { "node": "^18.12 || >=20.9" @@ -3117,16 +3112,6 @@ "integrity": "sha512-wpCQMhf5p5GhNg2MmGKXzUNwxe7zRiCsmqYsamez2beP7mKPCSiu+BjZcdN95yYSzO857kr0VfQewmGpS77nqA==", "license": "MIT" }, - "node_modules/@trysound/sax": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", - "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10.13.0" - } - }, "node_modules/@tybys/wasm-util": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", @@ -3509,9 +3494,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.19.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.1.tgz", - "integrity": "sha512-jJD50LtlD2dodAEO653i3YF04NWak6jN3ky+Ri3Em3mGR39/glWiboM/IePaRbgwSfqM1TpGXfAg8ohn/4dTgA==", + "version": "20.19.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.2.tgz", + "integrity": "sha512-9pLGGwdzOUBDYi0GNjM97FIA+f92fqSke6joWeBjWXllfNxZBs7qeMF7tvtOIsbY45xkWkxrdwUfUf3MnQa9gA==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -3561,17 +3546,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.34.0.tgz", - "integrity": "sha512-QXwAlHlbcAwNlEEMKQS2RCgJsgXrTJdjXT08xEgbPFa2yYQgVjBymxP5DrfrE7X7iodSzd9qBUHUycdyVJTW1w==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.35.0.tgz", + "integrity": "sha512-ijItUYaiWuce0N1SoSMrEd0b6b6lYkYt99pqCPfybd+HKVXtEvYhICfLdwp42MhiI5mp0oq7PKEL+g1cNiz/Eg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.34.0", - "@typescript-eslint/type-utils": "8.34.0", - "@typescript-eslint/utils": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0", + "@typescript-eslint/scope-manager": "8.35.0", + "@typescript-eslint/type-utils": "8.35.0", + "@typescript-eslint/utils": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0", "graphemer": "^1.4.0", "ignore": "^7.0.0", "natural-compare": "^1.4.0", @@ -3585,7 +3570,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.34.0", + "@typescript-eslint/parser": "^8.35.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } @@ -3601,16 +3586,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.34.0.tgz", - "integrity": "sha512-vxXJV1hVFx3IXz/oy2sICsJukaBrtDEQSBiV48/YIV5KWjX1dO+bcIr/kCPrW6weKXvsaGKFNlwH0v2eYdRRbA==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.35.0.tgz", + "integrity": "sha512-6sMvZePQrnZH2/cJkwRpkT7DxoAWh+g6+GFRK6bV3YQo7ogi3SX5rgF6099r5Q53Ma5qeT7LGmOmuIutF4t3lA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.34.0", - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/typescript-estree": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0", + "@typescript-eslint/scope-manager": "8.35.0", + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/typescript-estree": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0", "debug": "^4.3.4" }, "engines": { @@ -3626,14 +3611,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.0.tgz", - "integrity": "sha512-iEgDALRf970/B2YExmtPMPF54NenZUf4xpL3wsCRx/lgjz6ul/l13R81ozP/ZNuXfnLCS+oPmG7JIxfdNYKELw==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.35.0.tgz", + "integrity": "sha512-41xatqRwWZuhUMF/aZm2fcUsOFKNcG28xqRSS6ZVr9BVJtGExosLAm5A1OxTjRMagx8nJqva+P5zNIGt8RIgbQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.34.0", - "@typescript-eslint/types": "^8.34.0", + "@typescript-eslint/tsconfig-utils": "^8.35.0", + "@typescript-eslint/types": "^8.35.0", "debug": "^4.3.4" }, "engines": { @@ -3648,14 +3633,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.0.tgz", - "integrity": "sha512-9Ac0X8WiLykl0aj1oYQNcLZjHgBojT6cW68yAgZ19letYu+Hxd0rE0veI1XznSSst1X5lwnxhPbVdwjDRIomRw==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.35.0.tgz", + "integrity": "sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0" + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3666,9 +3651,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.0.tgz", - "integrity": "sha512-+W9VYHKFIzA5cBeooqQxqNriAP0QeQ7xTiDuIOr71hzgffm3EL2hxwWBIIj4GuofIbKxGNarpKqIq6Q6YrShOA==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.35.0.tgz", + "integrity": "sha512-04k/7247kZzFraweuEirmvUj+W3bJLI9fX6fbo1Qm2YykuBvEhRTPl8tcxlYO8kZZW+HIXfkZNoasVb8EV4jpA==", "dev": true, "license": "MIT", "engines": { @@ -3683,14 +3668,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.34.0.tgz", - "integrity": "sha512-n7zSmOcUVhcRYC75W2pnPpbO1iwhJY3NLoHEtbJwJSNlVAZuwqu05zY3f3s2SDWWDSo9FdN5szqc73DCtDObAg==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.35.0.tgz", + "integrity": "sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.34.0", - "@typescript-eslint/utils": "8.34.0", + "@typescript-eslint/typescript-estree": "8.35.0", + "@typescript-eslint/utils": "8.35.0", "debug": "^4.3.4", "ts-api-utils": "^2.1.0" }, @@ -3707,9 +3692,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.0.tgz", - "integrity": "sha512-9V24k/paICYPniajHfJ4cuAWETnt7Ssy+R0Rbcqo5sSFr3QEZ/8TSoUi9XeXVBGXCaLtwTOKSLGcInCAvyZeMA==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.35.0.tgz", + "integrity": "sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==", "dev": true, "license": "MIT", "engines": { @@ -3721,16 +3706,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.0.tgz", - "integrity": "sha512-rOi4KZxI7E0+BMqG7emPSK1bB4RICCpF7QD3KCLXn9ZvWoESsOMlHyZPAHyG04ujVplPaHbmEvs34m+wjgtVtg==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.35.0.tgz", + "integrity": "sha512-F+BhnaBemgu1Qf8oHrxyw14wq6vbL8xwWKKMwTMwYIRmFFY/1n/9T/jpbobZL8vp7QyEUcC6xGrnAO4ua8Kp7w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.34.0", - "@typescript-eslint/tsconfig-utils": "8.34.0", - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/visitor-keys": "8.34.0", + "@typescript-eslint/project-service": "8.35.0", + "@typescript-eslint/tsconfig-utils": "8.35.0", + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/visitor-keys": "8.35.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -3783,16 +3768,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.0.tgz", - "integrity": "sha512-8L4tWatGchV9A1cKbjaavS6mwYwp39jql8xUmIIKJdm+qiaeHy5KMKlBrf30akXAWBzn2SqKsNOtSENWUwg7XQ==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.35.0.tgz", + "integrity": "sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.34.0", - "@typescript-eslint/types": "8.34.0", - "@typescript-eslint/typescript-estree": "8.34.0" + "@typescript-eslint/scope-manager": "8.35.0", + "@typescript-eslint/types": "8.35.0", + "@typescript-eslint/typescript-estree": "8.35.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3807,14 +3792,14 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.0.tgz", - "integrity": "sha512-qHV7pW7E85A0x6qyrFn+O+q1k1p3tQCsqIZ1KZ5ESLXY57aTvUd3/a4rdPTeXisvhXn2VQG0VSKUqs8KHF2zcA==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.35.0.tgz", + "integrity": "sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.34.0", - "eslint-visitor-keys": "^4.2.0" + "@typescript-eslint/types": "8.35.0", + "eslint-visitor-keys": "^4.2.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -3825,9 +3810,9 @@ } }, "node_modules/@unrs/resolver-binding-android-arm-eabi": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.9.1.tgz", - "integrity": "sha512-dd7yIp1hfJFX9ZlVLQRrh/Re9WMUHHmF9hrKD1yIvxcyNr2BhQ3xc1upAVhy8NijadnCswAxWQu8MkkSMC1qXQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.9.2.tgz", + "integrity": "sha512-tS+lqTU3N0kkthU+rYp0spAYq15DU8ld9kXkaKg9sbQqJNF+WPMuNHZQGCgdxrUOEO0j22RKMwRVhF1HTl+X8A==", "cpu": [ "arm" ], @@ -3839,9 +3824,9 @@ ] }, "node_modules/@unrs/resolver-binding-android-arm64": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.9.1.tgz", - "integrity": "sha512-EzUPcMFtDVlo5yrbzMqUsGq3HnLXw+3ZOhSd7CUaDmbTtnrzM+RO2ntw2dm2wjbbc5djWj3yX0wzbbg8pLhx8g==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.9.2.tgz", + "integrity": "sha512-MffGiZULa/KmkNjHeuuflLVqfhqLv1vZLm8lWIyeADvlElJ/GLSOkoUX+5jf4/EGtfwrNFcEaB8BRas03KT0/Q==", "cpu": [ "arm64" ], @@ -3853,9 +3838,9 @@ ] }, "node_modules/@unrs/resolver-binding-darwin-arm64": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.9.1.tgz", - "integrity": "sha512-nB+dna3q4kOleKFcSZJ/wDXIsAd1kpMO9XrVAt8tG3RDWJ6vi+Ic6bpz4cmg5tWNeCfHEY4KuqJCB+pKejPEmQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.9.2.tgz", + "integrity": "sha512-dzJYK5rohS1sYl1DHdJ3mwfwClJj5BClQnQSyAgEfggbUwA9RlROQSSbKBLqrGfsiC/VyrDPtbO8hh56fnkbsQ==", "cpu": [ "arm64" ], @@ -3867,9 +3852,9 @@ ] }, "node_modules/@unrs/resolver-binding-darwin-x64": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.9.1.tgz", - "integrity": "sha512-aKWHCrOGaCGwZcekf3TnczQoBxk5w//W3RZ4EQyhux6rKDwBPgDU9Y2yGigCV1Z+8DWqZgVGQi+hdpnlSy3a1w==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.9.2.tgz", + "integrity": "sha512-gaIMWK+CWtXcg9gUyznkdV54LzQ90S3X3dn8zlh+QR5Xy7Y+Efqw4Rs4im61K1juy4YNb67vmJsCDAGOnIeffQ==", "cpu": [ "x64" ], @@ -3881,9 +3866,9 @@ ] }, "node_modules/@unrs/resolver-binding-freebsd-x64": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.9.1.tgz", - "integrity": "sha512-4dIEMXrXt0UqDVgrsUd1I+NoIzVQWXy/CNhgpfS75rOOMK/4Abn0Mx2M2gWH4Mk9+ds/ASAiCmqoUFynmMY5hA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.9.2.tgz", + "integrity": "sha512-S7QpkMbVoVJb0xwHFwujnwCAEDe/596xqY603rpi/ioTn9VDgBHnCCxh+UFrr5yxuMH+dliHfjwCZJXOPJGPnw==", "cpu": [ "x64" ], @@ -3895,9 +3880,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.9.1.tgz", - "integrity": "sha512-vtvS13IXPs1eE8DuS/soiosqMBeyh50YLRZ+p7EaIKAPPeevRnA9G/wu/KbVt01ZD5qiGjxS+CGIdVC7I6gTOw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.9.2.tgz", + "integrity": "sha512-+XPUMCuCCI80I46nCDFbGum0ZODP5NWGiwS3Pj8fOgsG5/ctz+/zzuBlq/WmGa+EjWZdue6CF0aWWNv84sE1uw==", "cpu": [ "arm" ], @@ -3909,9 +3894,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.9.1.tgz", - "integrity": "sha512-BfdnN6aZ7NcX8djW8SR6GOJc+K+sFhWRF4vJueVE0vbUu5N1bLnBpxJg1TGlhSyo+ImC4SR0jcNiKN0jdoxt+A==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.9.2.tgz", + "integrity": "sha512-sqvUyAd1JUpwbz33Ce2tuTLJKM+ucSsYpPGl2vuFwZnEIg0CmdxiZ01MHQ3j6ExuRqEDUCy8yvkDKvjYFPb8Zg==", "cpu": [ "arm" ], @@ -3923,9 +3908,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.9.1.tgz", - "integrity": "sha512-Jhge7lFtH0QqfRz2PyJjJXWENqywPteITd+nOS0L6AhbZli+UmEyGBd2Sstt1c+l9C+j/YvKTl9wJo9PPmsFNg==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.9.2.tgz", + "integrity": "sha512-UYA0MA8ajkEDCFRQdng/FVx3F6szBvk3EPnkTTQuuO9lV1kPGuTB+V9TmbDxy5ikaEgyWKxa4CI3ySjklZ9lFA==", "cpu": [ "arm64" ], @@ -3937,9 +3922,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-arm64-musl": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.9.1.tgz", - "integrity": "sha512-ofdK/ow+ZSbSU0pRoB7uBaiRHeaAOYQFU5Spp87LdcPL/P1RhbCTMSIYVb61XWzsVEmYKjHFtoIE0wxP6AFvrA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.9.2.tgz", + "integrity": "sha512-P/CO3ODU9YJIHFqAkHbquKtFst0COxdphc8TKGL5yCX75GOiVpGqd1d15ahpqu8xXVsqP4MGFP2C3LRZnnL5MA==", "cpu": [ "arm64" ], @@ -3951,9 +3936,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.9.1.tgz", - "integrity": "sha512-eC8SXVn8de67HacqU7PoGdHA+9tGbqfEdD05AEFRAB81ejeQtNi5Fx7lPcxpLH79DW0BnMAHau3hi4RVkHfSCw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.9.2.tgz", + "integrity": "sha512-uKStFlOELBxBum2s1hODPtgJhY4NxYJE9pAeyBgNEzHgTqTiVBPjfTlPFJkfxyTjQEuxZbbJlJnMCrRgD7ubzw==", "cpu": [ "ppc64" ], @@ -3965,9 +3950,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.9.1.tgz", - "integrity": "sha512-fIkwvAAQ41kfoGWfzeJ33iLGShl0JEDZHrMnwTHMErUcPkaaZRJYjQjsFhMl315NEQ4mmTlC+2nfK/J2IszDOw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.9.2.tgz", + "integrity": "sha512-LkbNnZlhINfY9gK30AHs26IIVEZ9PEl9qOScYdmY2o81imJYI4IMnJiW0vJVtXaDHvBvxeAgEy5CflwJFIl3tQ==", "cpu": [ "riscv64" ], @@ -3979,9 +3964,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.9.1.tgz", - "integrity": "sha512-RAAszxImSOFLk44aLwnSqpcOdce8sBcxASledSzuFAd8Q5ZhhVck472SisspnzHdc7THCvGXiUeZ2hOC7NUoBQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.9.2.tgz", + "integrity": "sha512-vI+e6FzLyZHSLFNomPi+nT+qUWN4YSj8pFtQZSFTtmgFoxqB6NyjxSjAxEC1m93qn6hUXhIsh8WMp+fGgxCoRg==", "cpu": [ "riscv64" ], @@ -3993,9 +3978,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.9.1.tgz", - "integrity": "sha512-QoP9vkY+THuQdZi05bA6s6XwFd6HIz3qlx82v9bTOgxeqin/3C12Ye7f7EOD00RQ36OtOPWnhEMMm84sv7d1XQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.9.2.tgz", + "integrity": "sha512-sSO4AlAYhSM2RAzBsRpahcJB1msc6uYLAtP6pesPbZtptF8OU/CbCPhSRW6cnYOGuVmEmWVW5xVboAqCnWTeHQ==", "cpu": [ "s390x" ], @@ -4007,9 +3992,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-gnu": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.9.1.tgz", - "integrity": "sha512-/p77cGN/h9zbsfCseAP5gY7tK+7+DdM8fkPfr9d1ye1fsF6bmtGbtZN6e/8j4jCZ9NEIBBkT0GhdgixSelTK9g==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.9.2.tgz", + "integrity": "sha512-jkSkwch0uPFva20Mdu8orbQjv2A3G88NExTN2oPTI1AJ+7mZfYW3cDCTyoH6OnctBKbBVeJCEqh0U02lTkqD5w==", "cpu": [ "x64" ], @@ -4021,9 +4006,9 @@ ] }, "node_modules/@unrs/resolver-binding-linux-x64-musl": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.9.1.tgz", - "integrity": "sha512-wInTqT3Bu9u50mDStEig1v8uxEL2Ht+K8pir/YhyyrM5ordJtxoqzsL1vR/CQzOJuDunUTrDkMM0apjW/d7/PA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.9.2.tgz", + "integrity": "sha512-Uk64NoiTpQbkpl+bXsbeyOPRpUoMdcUqa+hDC1KhMW7aN1lfW8PBlBH4mJ3n3Y47dYE8qi0XTxy1mBACruYBaw==", "cpu": [ "x64" ], @@ -4035,9 +4020,9 @@ ] }, "node_modules/@unrs/resolver-binding-wasm32-wasi": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.9.1.tgz", - "integrity": "sha512-eNwqO5kUa+1k7yFIircwwiniKWA0UFHo2Cfm8LYgkh9km7uMad+0x7X7oXbQonJXlqfitBTSjhA0un+DsHIrhw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.9.2.tgz", + "integrity": "sha512-EpBGwkcjDicjR/ybC0g8wO5adPNdVuMrNalVgYcWi+gYtC1XYNuxe3rufcO7dA76OHGeVabcO6cSkPJKVcbCXQ==", "cpu": [ "wasm32" ], @@ -4052,9 +4037,9 @@ } }, "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.9.1.tgz", - "integrity": "sha512-Eaz1xMUnoa2mFqh20mPqSdbYl6crnk8HnIXDu6nsla9zpgZJZO8w3c1gvNN/4Eb0RXRq3K9OG6mu8vw14gIqiA==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.9.2.tgz", + "integrity": "sha512-EdFbGn7o1SxGmN6aZw9wAkehZJetFPao0VGZ9OMBwKx6TkvDuj6cNeLimF/Psi6ts9lMOe+Dt6z19fZQ9Ye2fw==", "cpu": [ "arm64" ], @@ -4066,9 +4051,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.9.1.tgz", - "integrity": "sha512-H/+d+5BGlnEQif0gnwWmYbYv7HJj563PUKJfn8PlmzF8UmF+8KxdvXdwCsoOqh4HHnENnoLrav9NYBrv76x1wQ==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.9.2.tgz", + "integrity": "sha512-JY9hi1p7AG+5c/dMU8o2kWemM8I6VZxfGwn1GCtf3c5i+IKcMo2NQ8OjZ4Z3/itvY/Si3K10jOBQn7qsD/whUA==", "cpu": [ "ia32" ], @@ -4080,9 +4065,9 @@ ] }, "node_modules/@unrs/resolver-binding-win32-x64-msvc": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.9.1.tgz", - "integrity": "sha512-rS86wI4R6cknYM3is3grCb/laE8XBEbpWAMSIPjYfmYp75KL5dT87jXF2orDa4tQYg5aajP5G8Fgh34dRyR+Rw==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.9.2.tgz", + "integrity": "sha512-ryoo+EB19lMxAd80ln9BVf8pdOAxLb97amrQ3SFN9OCRn/5M5wvwDgAe4i8ZjhpbiHoDeP8yavcTEnpKBo7lZg==", "cpu": [ "x64" ], @@ -4094,16 +4079,19 @@ ] }, "node_modules/@vitejs/plugin-vue": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", - "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.0.tgz", + "integrity": "sha512-iAliE72WsdhjzTOp2DtvKThq1VBC4REhwRcaA+zPAAph6I+OQhUXv+Xu2KS7ElxYtb7Zc/3R30Hwv1DxEo7NXQ==", "dev": true, "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "1.0.0-beta.19" + }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0", "vue": "^3.2.25" } }, @@ -5155,9 +5143,9 @@ } }, "node_modules/browserslist": { - "version": "4.25.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", - "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", "funding": [ { "type": "opencollective", @@ -5174,8 +5162,8 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001718", - "electron-to-chromium": "^1.5.160", + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, @@ -5346,9 +5334,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001724", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001724.tgz", - "integrity": "sha512-WqJo7p0TbHDOythNTqYujmaJTvtYRZrjpP8TCvH6Vb9CYJerJNKamKzIWOM4BkQatWj9H2lYulpdAQNBe7QhNA==", + "version": "1.0.30001726", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", "funding": [ { "type": "opencollective", @@ -5894,9 +5882,9 @@ } }, "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -5925,9 +5913,9 @@ } }, "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -6916,9 +6904,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.171", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.171.tgz", - "integrity": "sha512-scWpzXEJEMrGJa4Y6m/tVotb0WuvNmasv3wWVzUAeCgKU0ToFOhUW6Z+xWnRQANMYGxN4ngJXIThgBJOqzVPCQ==", + "version": "1.5.177", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.177.tgz", + "integrity": "sha512-7EH2G59nLsEMj97fpDuvVcYi6lwTcM1xuWw3PssD8xzboAW7zj7iB3COEEEATUfjLHrs5uKBLQT03V/8URx06g==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -6937,9 +6925,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.18.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", - "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "version": "5.18.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.2.tgz", + "integrity": "sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -7262,19 +7250,19 @@ } }, "node_modules/eslint": { - "version": "9.28.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.28.0.tgz", - "integrity": "sha512-ocgh41VhRlf9+fVpe7QKzwLj9c92fDiqOj8Y3Sd4/ZmVA4Btx4PlUYPq4pp9JDyupkf1upbEXecxL2mwNV7jPQ==", + "version": "9.30.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.30.0.tgz", + "integrity": "sha512-iN/SiPxmQu6EVkf+m1qpBxzUhE12YqFLOSySuOyVLJLEF9nzTf+h/1AJYc1JWzCnktggeNrjvQGLngDzXirU6g==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.20.0", - "@eslint/config-helpers": "^0.2.1", + "@eslint/config-array": "^0.21.0", + "@eslint/config-helpers": "^0.3.0", "@eslint/core": "^0.14.0", "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.28.0", + "@eslint/js": "9.30.0", "@eslint/plugin-kit": "^0.3.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", @@ -7286,9 +7274,9 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.3.0", - "eslint-visitor-keys": "^4.2.0", - "espree": "^10.3.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -7339,14 +7327,14 @@ } }, "node_modules/eslint-import-context": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.8.tgz", - "integrity": "sha512-bq+F7nyc65sKpZGT09dY0S0QrOnQtuDVIfyTGQ8uuvtMIF7oHp6CEP3mouN0rrnYF3Jqo6Ke0BfU/5wASZue1w==", + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/eslint-import-context/-/eslint-import-context-0.1.9.tgz", + "integrity": "sha512-K9Hb+yRaGAGUbwjhFNHvSmmkZs9+zbuoe3kFQ4V1wYjrepUFYM2dZAfNtjbbj3qsPfUfsA68Bx/ICWQMi+C8Eg==", "dev": true, "license": "MIT", "dependencies": { "get-tsconfig": "^4.10.1", - "stable-hash-x": "^0.1.1" + "stable-hash-x": "^0.2.0" }, "engines": { "node": "^12.20.0 || ^14.18.0 || >=16.0.0" @@ -7364,9 +7352,9 @@ } }, "node_modules/eslint-import-resolver-typescript": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.4.3.tgz", - "integrity": "sha512-elVDn1eWKFrWlzxlWl9xMt8LltjKl161Ix50JFC50tHXI5/TRP32SNEqlJ/bo/HV+g7Rou/tlPQU2AcRtIhrOg==", + "version": "4.4.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-4.4.4.tgz", + "integrity": "sha512-1iM2zeBvrYmUNTj2vSC/90JTHDth+dfOfiNKkxApWRsTJYNrc8rOdxxIf5vazX+BiAXTeOT0UvWpGI/7qIWQOw==", "dev": true, "license": "ISC", "dependencies": { @@ -7374,7 +7362,7 @@ "eslint-import-context": "^0.1.8", "get-tsconfig": "^4.10.1", "is-bun-module": "^2.0.0", - "stable-hash-x": "^0.1.1", + "stable-hash-x": "^0.2.0", "tinyglobby": "^0.2.14", "unrs-resolver": "^1.7.11" }, @@ -7412,21 +7400,21 @@ } }, "node_modules/eslint-plugin-import-x": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.15.1.tgz", - "integrity": "sha512-JfVpNg1qMkPD66iaSgmMoSYeUCGS8UFSm3GwHV0IbuV3Knar/SyK5qqCct9+AxoMIzaM+KSO7KK5pOeOkC/3GQ==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import-x/-/eslint-plugin-import-x-4.16.1.tgz", + "integrity": "sha512-vPZZsiOKaBAIATpFE2uMI4w5IRwdv/FpQ+qZZMR4E+PeOcM4OeoEbqxRMnywdxP19TyB/3h6QBB0EWon7letSQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "^8.33.1", + "@typescript-eslint/types": "^8.35.0", "comment-parser": "^1.4.1", "debug": "^4.4.1", - "eslint-import-context": "^0.1.7", + "eslint-import-context": "^0.1.9", "is-glob": "^4.0.3", "minimatch": "^9.0.3 || ^10.0.1", "semver": "^7.7.2", - "stable-hash-x": "^0.1.1", - "unrs-resolver": "^1.7.10" + "stable-hash-x": "^0.2.0", + "unrs-resolver": "^1.9.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7535,9 +7523,9 @@ } }, "node_modules/eslint-plugin-sonarjs": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-3.0.2.tgz", - "integrity": "sha512-LxjbfwI7ypENeTmGyKmDyNux3COSkMi7H/6Cal5StSLQ6edf0naP45SZR43OclaNR7WfhVTZdhOn63q3/Y6puQ==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-sonarjs/-/eslint-plugin-sonarjs-3.0.4.tgz", + "integrity": "sha512-ftQcP811kRJNXapqpQXHErEoVOdTPfYPPYd7n3AExIPwv4qWKKHf4slFvXmodiOnfgy1Tl3waPZZLD7lcvJOtw==", "dev": true, "license": "LGPL-3.0-only", "dependencies": { @@ -7546,10 +7534,11 @@ "bytes": "3.1.2", "functional-red-black-tree": "1.0.1", "jsx-ast-utils": "3.3.5", + "lodash.merge": "4.6.2", "minimatch": "9.0.5", "scslre": "0.3.0", - "semver": "7.7.1", - "typescript": "^5" + "semver": "7.7.2", + "typescript": ">=5" }, "peerDependencies": { "eslint": "^8.0.0 || ^9.0.0" @@ -7588,19 +7577,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/eslint-plugin-sonarjs/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/eslint-plugin-toml": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/eslint-plugin-toml/-/eslint-plugin-toml-0.12.0.tgz", @@ -8563,9 +8539,9 @@ } }, "node_modules/happy-dom": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-18.0.0.tgz", - "integrity": "sha512-o3p2Axi1EdIfMaOUulDzO/5yXzLLV0g/54eLPVrkt3u20r3yOuOenHpyp2clAJ0eHMc+HyE139ulQxl+8pEJIw==", + "version": "18.0.1", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-18.0.1.tgz", + "integrity": "sha512-qn+rKOW7KWpVTtgIUi6RVmTBZJSe2k0Db0vh1f7CWrWclkkc7/Q+FrOfkZIb2eiErLyqu5AXEzE7XthO9JVxRA==", "dev": true, "license": "MIT", "dependencies": { @@ -8676,9 +8652,9 @@ } }, "node_modules/hookified": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.9.1.tgz", - "integrity": "sha512-u3pxtGhKjcSXnGm1CX6aXS9xew535j3lkOCegbA6jdyh0BaAjTbXI4aslKstCr6zUNtoCxFGFKwjbSHdGrMB8g==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.10.0.tgz", + "integrity": "sha512-dJw0492Iddsj56U1JsSTm9E/0B/29a1AuoSLRAte8vQg/kaTGF3IgjEWT8c8yG4cC10+HisE1x5QAwR0Xwc+DA==", "dev": true, "license": "MIT" }, @@ -8746,9 +8722,9 @@ } }, "node_modules/htmx.org": { - "version": "1.9.12", - "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.12.tgz", - "integrity": "sha512-VZAohXyF7xPGS52IM8d1T1283y+X4D+Owf3qY1NZ9RuBypyu9l8cGsxUMAG5fEAb/DhT7rDoJ9Hpu5/HxFD3cw==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-2.0.6.tgz", + "integrity": "sha512-7ythjYneGSk3yCHgtCnQeaoF+D+o7U2LF37WU3O0JYv3gTZSicdEFiI/Ai/NJyC5ZpYJWMpUb11OC5Lr6AfAqA==", "license": "0BSD" }, "node_modules/iconv-lite": { @@ -9993,9 +9969,9 @@ } }, "node_modules/known-css-properties": { - "version": "0.36.0", - "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.36.0.tgz", - "integrity": "sha512-A+9jP+IUmuQsNdsLdcg6Yt7voiMF/D4K83ew0OpJtpu+l34ef7LaohWV0Rc6KNvzw6ZDizkqfyB5JznZnzuKQA==", + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", + "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", "dev": true, "license": "MIT" }, @@ -11344,9 +11320,9 @@ } }, "node_modules/napi-postinstall": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.4.tgz", - "integrity": "sha512-ZEzHJwBhZ8qQSbknHqYcdtQVr8zUgGyM/q6h6qAyhtyVMNrSgDhrC4disf03dYW0e+czXyLnZINnCTEkWy0eJg==", + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.5.tgz", + "integrity": "sha512-kmsgUvCRIJohHjbZ3V8avP0I1Pekw329MVAMDzVxsrkjgdnqiwvMX5XwR+hWV66vsAtZ+iM+fVnq8RTQawUmCQ==", "dev": true, "license": "MIT", "bin": { @@ -11827,9 +11803,9 @@ "license": "MIT" }, "node_modules/pathval": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", - "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", "dev": true, "license": "MIT", "engines": { @@ -11943,13 +11919,13 @@ } }, "node_modules/pkg-types": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.1.0.tgz", - "integrity": "sha512-wmJwA+8ihJixSoHKxZJRBQG1oY8Yr9pGLzRmSsNms0iNWyHHAlZCa7mmKiFR10YPZuz/2k169JiS/inOjBCZ2A==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.1.1.tgz", + "integrity": "sha512-eY0QFb6eSwc9+0d/5D2lFFUq+A3n3QNGSy/X2Nvp+6MfzGw2u6EbA7S80actgjY1lkvvI0pqB+a4hioMh443Ew==", "license": "MIT", "dependencies": { - "confbox": "^0.2.1", - "exsolve": "^1.0.1", + "confbox": "^0.2.2", + "exsolve": "^1.0.7", "pathe": "^2.0.3" } }, @@ -13590,9 +13566,9 @@ } }, "node_modules/stable-hash-x": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.1.1.tgz", - "integrity": "sha512-l0x1D6vhnsNUGPFVDx45eif0y6eedVC8nm5uACTrVFJFtl2mLRW17aWtVyxFCpn5t94VUPkjU8vSLwIuwwqtJQ==", + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/stable-hash-x/-/stable-hash-x-0.2.0.tgz", + "integrity": "sha512-o3yWv49B/o4QZk5ZcsALc6t0+eCelPc44zZsLtCQnZPDwFpDYSWcDnrv2TtMmMbQ7uKo3J0HTURCqckw23czNQ==", "dev": true, "license": "MIT", "engines": { @@ -13801,9 +13777,9 @@ "license": "ISC" }, "node_modules/stylelint": { - "version": "16.20.0", - "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.20.0.tgz", - "integrity": "sha512-B5Myu9WRxrgKuLs3YyUXLP2H0mrbejwNxPmyADlACWwFsrL8Bmor/nTSh4OMae5sHjOz6gkSeccQH34gM4/nAw==", + "version": "16.21.0", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.21.0.tgz", + "integrity": "sha512-ki3PpJGG7xhm3WtINoWGnlvqAmbqSexoRMbEMJzlwewSIOqPRKPlq452c22xAdEJISVi80r+I7KL9GPUiwFgbg==", "dev": true, "funding": [ { @@ -13817,9 +13793,9 @@ ], "license": "MIT", "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.4", - "@csstools/css-tokenizer": "^3.0.3", - "@csstools/media-query-list-parser": "^4.0.2", + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/media-query-list-parser": "^4.0.3", "@csstools/selector-specificity": "^5.0.0", "@dual-bundle/import-meta-resolve": "^4.1.0", "balanced-match": "^2.0.0", @@ -13830,21 +13806,21 @@ "debug": "^4.4.1", "fast-glob": "^3.3.3", "fastest-levenshtein": "^1.0.16", - "file-entry-cache": "^10.1.0", + "file-entry-cache": "^10.1.1", "global-modules": "^2.0.0", "globby": "^11.1.0", "globjoin": "^0.1.4", "html-tags": "^3.3.1", - "ignore": "^7.0.4", + "ignore": "^7.0.5", "imurmurhash": "^0.1.4", "is-plain-object": "^5.0.0", - "known-css-properties": "^0.36.0", + "known-css-properties": "^0.37.0", "mathml-tag-names": "^2.1.3", "meow": "^13.2.0", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "picocolors": "^1.1.1", - "postcss": "^8.5.3", + "postcss": "^8.5.5", "postcss-resolve-nested-selector": "^0.1.6", "postcss-safe-parser": "^7.0.1", "postcss-selector-parser": "^7.1.0", @@ -14202,25 +14178,25 @@ "dev": true }, "node_modules/svgo": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.2.0.tgz", - "integrity": "sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz", + "integrity": "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==", "dev": true, "license": "MIT", "dependencies": { - "@trysound/sax": "0.2.0", - "commander": "^7.2.0", + "commander": "^11.1.0", "css-select": "^5.1.0", - "css-tree": "^2.3.1", + "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1", + "sax": "^1.4.1" }, "bin": { - "svgo": "bin/svgo" + "svgo": "bin/svgo.js" }, "engines": { - "node": ">=14.0.0" + "node": ">=16" }, "funding": { "type": "opencollective", @@ -14228,35 +14204,21 @@ } }, "node_modules/svgo/node_modules/commander": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", "dev": true, "license": "MIT", "engines": { - "node": ">= 10" + "node": ">=16" } }, - "node_modules/svgo/node_modules/css-tree": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", - "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "node_modules/svgo/node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", "dev": true, - "license": "MIT", - "dependencies": { - "mdn-data": "2.0.30", - "source-map-js": "^1.0.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/svgo/node_modules/mdn-data": { - "version": "2.0.30", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", - "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", - "dev": true, - "license": "CC0-1.0" + "license": "ISC" }, "node_modules/swagger-ui-dist": { "version": "5.17.14", @@ -14872,15 +14834,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.34.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.34.0.tgz", - "integrity": "sha512-MRpfN7uYjTrTGigFCt8sRyNqJFhjN0WwZecldaqhWm+wy0gaRt8Edb/3cuUy0zdq2opJWT6iXINKAtewnDOltQ==", + "version": "8.35.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.35.0.tgz", + "integrity": "sha512-uEnz70b7kBz6eg/j0Czy6K5NivaYopgxRjsnAJ2Fx5oTLo3wefTHIbL7AkQr1+7tJCRVpTs/wiM8JR/11Loq9A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.34.0", - "@typescript-eslint/parser": "8.34.0", - "@typescript-eslint/utils": "8.34.0" + "@typescript-eslint/eslint-plugin": "8.35.0", + "@typescript-eslint/parser": "8.35.0", + "@typescript-eslint/utils": "8.35.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -14955,38 +14917,38 @@ } }, "node_modules/unrs-resolver": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.9.1.tgz", - "integrity": "sha512-4AZVxP05JGN6DwqIkSP4VKLOcwQa5l37SWHF/ahcuqBMbfxbpN1L1QKafEhWCziHhzKex9H/AR09H0OuVyU+9g==", + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.9.2.tgz", + "integrity": "sha512-VUyWiTNQD7itdiMuJy+EuLEErLj3uwX/EpHQF8EOf33Dq3Ju6VW1GXm+swk6+1h7a49uv9fKZ+dft9jU7esdLA==", "dev": true, "hasInstallScript": true, "license": "MIT", "dependencies": { - "napi-postinstall": "^0.2.2" + "napi-postinstall": "^0.2.4" }, "funding": { "url": "https://opencollective.com/unrs-resolver" }, "optionalDependencies": { - "@unrs/resolver-binding-android-arm-eabi": "1.9.1", - "@unrs/resolver-binding-android-arm64": "1.9.1", - "@unrs/resolver-binding-darwin-arm64": "1.9.1", - "@unrs/resolver-binding-darwin-x64": "1.9.1", - "@unrs/resolver-binding-freebsd-x64": "1.9.1", - "@unrs/resolver-binding-linux-arm-gnueabihf": "1.9.1", - "@unrs/resolver-binding-linux-arm-musleabihf": "1.9.1", - "@unrs/resolver-binding-linux-arm64-gnu": "1.9.1", - "@unrs/resolver-binding-linux-arm64-musl": "1.9.1", - "@unrs/resolver-binding-linux-ppc64-gnu": "1.9.1", - "@unrs/resolver-binding-linux-riscv64-gnu": "1.9.1", - "@unrs/resolver-binding-linux-riscv64-musl": "1.9.1", - "@unrs/resolver-binding-linux-s390x-gnu": "1.9.1", - "@unrs/resolver-binding-linux-x64-gnu": "1.9.1", - "@unrs/resolver-binding-linux-x64-musl": "1.9.1", - "@unrs/resolver-binding-wasm32-wasi": "1.9.1", - "@unrs/resolver-binding-win32-arm64-msvc": "1.9.1", - "@unrs/resolver-binding-win32-ia32-msvc": "1.9.1", - "@unrs/resolver-binding-win32-x64-msvc": "1.9.1" + "@unrs/resolver-binding-android-arm-eabi": "1.9.2", + "@unrs/resolver-binding-android-arm64": "1.9.2", + "@unrs/resolver-binding-darwin-arm64": "1.9.2", + "@unrs/resolver-binding-darwin-x64": "1.9.2", + "@unrs/resolver-binding-freebsd-x64": "1.9.2", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.9.2", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.9.2", + "@unrs/resolver-binding-linux-arm64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-arm64-musl": "1.9.2", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-riscv64-musl": "1.9.2", + "@unrs/resolver-binding-linux-s390x-gnu": "1.9.2", + "@unrs/resolver-binding-linux-x64-gnu": "1.9.2", + "@unrs/resolver-binding-linux-x64-musl": "1.9.2", + "@unrs/resolver-binding-wasm32-wasi": "1.9.2", + "@unrs/resolver-binding-win32-arm64-msvc": "1.9.2", + "@unrs/resolver-binding-win32-ia32-msvc": "1.9.2", + "@unrs/resolver-binding-win32-x64-msvc": "1.9.2" } }, "node_modules/update-browserslist-db": { @@ -15093,24 +15055,24 @@ "license": "MIT" }, "node_modules/vite": { - "version": "6.3.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", - "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.0.tgz", + "integrity": "sha512-ixXJB1YRgDIw2OszKQS9WxGHKwLdCsbQNkpJN171udl6szi/rIySHL6/Os3s2+oE4P/FLD4dxg4mD7Wust+u5g==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", - "fdir": "^6.4.4", + "fdir": "^6.4.6", "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" + "postcss": "^8.5.6", + "rollup": "^4.40.0", + "tinyglobby": "^0.2.14" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -15119,14 +15081,14 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", - "less": "*", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" @@ -15247,10 +15209,39 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/vite/node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, "node_modules/vite/node_modules/rollup": { - "version": "4.44.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.0.tgz", - "integrity": "sha512-qHcdEzLCiktQIfwBq420pn2dP+30uzqYxv9ETm91wdt2R9AFcWfjNAmje4NWlnCIQ5RMTzVf0ZyisOKqHR6RwA==", + "version": "4.44.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.44.1.tgz", + "integrity": "sha512-x8H8aPvD+xbl0Do8oez5f5o8eMS3trfCghc4HhLAnCkj7Vl0d1JWGs0UF/D886zLW2rOj2QymV/JcSSsw+XDNg==", "dev": true, "license": "MIT", "dependencies": { @@ -15264,26 +15255,26 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.44.0", - "@rollup/rollup-android-arm64": "4.44.0", - "@rollup/rollup-darwin-arm64": "4.44.0", - "@rollup/rollup-darwin-x64": "4.44.0", - "@rollup/rollup-freebsd-arm64": "4.44.0", - "@rollup/rollup-freebsd-x64": "4.44.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.44.0", - "@rollup/rollup-linux-arm-musleabihf": "4.44.0", - "@rollup/rollup-linux-arm64-gnu": "4.44.0", - "@rollup/rollup-linux-arm64-musl": "4.44.0", - "@rollup/rollup-linux-loongarch64-gnu": "4.44.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.44.0", - "@rollup/rollup-linux-riscv64-gnu": "4.44.0", - "@rollup/rollup-linux-riscv64-musl": "4.44.0", - "@rollup/rollup-linux-s390x-gnu": "4.44.0", - "@rollup/rollup-linux-x64-gnu": "4.44.0", - "@rollup/rollup-linux-x64-musl": "4.44.0", - "@rollup/rollup-win32-arm64-msvc": "4.44.0", - "@rollup/rollup-win32-ia32-msvc": "4.44.0", - "@rollup/rollup-win32-x64-msvc": "4.44.0", + "@rollup/rollup-android-arm-eabi": "4.44.1", + "@rollup/rollup-android-arm64": "4.44.1", + "@rollup/rollup-darwin-arm64": "4.44.1", + "@rollup/rollup-darwin-x64": "4.44.1", + "@rollup/rollup-freebsd-arm64": "4.44.1", + "@rollup/rollup-freebsd-x64": "4.44.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.44.1", + "@rollup/rollup-linux-arm-musleabihf": "4.44.1", + "@rollup/rollup-linux-arm64-gnu": "4.44.1", + "@rollup/rollup-linux-arm64-musl": "4.44.1", + "@rollup/rollup-linux-loongarch64-gnu": "4.44.1", + "@rollup/rollup-linux-powerpc64le-gnu": "4.44.1", + "@rollup/rollup-linux-riscv64-gnu": "4.44.1", + "@rollup/rollup-linux-riscv64-musl": "4.44.1", + "@rollup/rollup-linux-s390x-gnu": "4.44.1", + "@rollup/rollup-linux-x64-gnu": "4.44.1", + "@rollup/rollup-linux-x64-musl": "4.44.1", + "@rollup/rollup-win32-arm64-msvc": "4.44.1", + "@rollup/rollup-win32-ia32-msvc": "4.44.1", + "@rollup/rollup-win32-x64-msvc": "4.44.1", "fsevents": "~2.3.2" } }, @@ -15478,9 +15469,9 @@ "license": "MIT" }, "node_modules/vue-eslint-parser": { - "version": "10.1.3", - "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.1.3.tgz", - "integrity": "sha512-dbCBnd2e02dYWsXoqX5yKUZlOt+ExIpq7hmHKPb5ZqKcjf++Eo0hMseFTZMLKThrUk61m+Uv6A2YSBve6ZvuDQ==", + "version": "10.1.4", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-10.1.4.tgz", + "integrity": "sha512-EIZvCukIEMHEb3mxOKemtvWR1fcUAdWWAgkfyjmRHzvyhrZvBvH9oz69+thDIWhGiIQjZnPkCn8yHqvjM+a9eg==", "dev": true, "license": "MIT", "peer": true, diff --git a/package.json b/package.json index 3d71e94cd3..88f904c44b 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "esbuild-loader": "4.3.0", "escape-goat": "4.0.0", "fast-glob": "3.3.3", - "htmx.org": "1.9.12", + "htmx.org": "2.0.6", "idiomorph": "0.3.0", "jquery": "3.7.1", "katex": "0.16.22", @@ -64,21 +64,21 @@ "@eslint-community/eslint-plugin-eslint-comments": "4.5.0", "@playwright/test": "1.52.0", "@stoplight/spectral-cli": "6.15.0", - "@stylistic/eslint-plugin": "4.4.1", - "@stylistic/stylelint-plugin": "3.1.2", - "@vitejs/plugin-vue": "5.2.4", + "@stylistic/eslint-plugin": "5.0.0", + "@stylistic/stylelint-plugin": "3.1.3", + "@vitejs/plugin-vue": "6.0.0", "@vitest/coverage-v8": "3.2.3", "@vitest/eslint-plugin": "1.2.2", "@vue/test-utils": "2.4.6", - "eslint": "9.28.0", - "eslint-import-resolver-typescript": "4.4.3", + "eslint": "9.30.0", + "eslint-import-resolver-typescript": "4.4.4", "eslint-plugin-array-func": "5.0.2", - "eslint-plugin-import-x": "4.15.1", + "eslint-plugin-import-x": "4.16.1", "eslint-plugin-no-jquery": "3.1.1", "eslint-plugin-no-use-extend-native": "0.7.2", "eslint-plugin-playwright": "2.2.0", "eslint-plugin-regexp": "2.9.0", - "eslint-plugin-sonarjs": "3.0.2", + "eslint-plugin-sonarjs": "3.0.4", "eslint-plugin-toml": "0.12.0", "eslint-plugin-unicorn": "59.0.1", "eslint-plugin-vitest-globals": "1.5.0", @@ -86,18 +86,18 @@ "eslint-plugin-vue-scoped-css": "2.10.0", "eslint-plugin-wc": "3.0.1", "globals": "16.1.0", - "happy-dom": "18.0.0", + "happy-dom": "18.0.1", "license-checker-rseidelsohn": "4.4.2", "markdownlint-cli": "0.45.0", "postcss-html": "1.8.0", "sharp": "0.34.2", - "stylelint": "16.20.0", + "stylelint": "16.21.0", "stylelint-declaration-block-no-ignored-properties": "2.8.0", "stylelint-declaration-strict-value": "1.10.11", "stylelint-value-no-unknown-custom-properties": "6.0.1", - "svgo": "3.2.0", + "svgo": "4.0.0", "typescript": "5.8.3", - "typescript-eslint": "8.34.0", + "typescript-eslint": "8.35.0", "vite-string-plugin": "1.3.4", "vitest": "3.2.3" }, diff --git a/public/assets/img/svg/gitea-alt.svg b/public/assets/img/svg/gitea-alt.svg index 53e3f17c13..efe4830a0b 100644 --- a/public/assets/img/svg/gitea-alt.svg +++ b/public/assets/img/svg/gitea-alt.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-chef.svg b/public/assets/img/svg/gitea-chef.svg index c5e8a721cc..8fd8ed325d 100644 --- a/public/assets/img/svg/gitea-chef.svg +++ b/public/assets/img/svg/gitea-chef.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-debian.svg b/public/assets/img/svg/gitea-debian.svg index fa2f2f49dc..e92d0b6937 100644 --- a/public/assets/img/svg/gitea-debian.svg +++ b/public/assets/img/svg/gitea-debian.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-gitbucket.svg b/public/assets/img/svg/gitea-gitbucket.svg index 62f603484e..b9e99724b2 100644 --- a/public/assets/img/svg/gitea-gitbucket.svg +++ b/public/assets/img/svg/gitea-gitbucket.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-gitlab.svg b/public/assets/img/svg/gitea-gitlab.svg index 03fcb0b87e..e2d708e7be 100644 --- a/public/assets/img/svg/gitea-gitlab.svg +++ b/public/assets/img/svg/gitea-gitlab.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-google.svg b/public/assets/img/svg/gitea-google.svg index 7dd2622df6..26ee04cb64 100644 --- a/public/assets/img/svg/gitea-google.svg +++ b/public/assets/img/svg/gitea-google.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-maven.svg b/public/assets/img/svg/gitea-maven.svg index 320d01a234..f6ece7dc28 100644 --- a/public/assets/img/svg/gitea-maven.svg +++ b/public/assets/img/svg/gitea-maven.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-microsoftonline.svg b/public/assets/img/svg/gitea-microsoftonline.svg index f2ce13ac22..c143eccbb6 100644 --- a/public/assets/img/svg/gitea-microsoftonline.svg +++ b/public/assets/img/svg/gitea-microsoftonline.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-npm.svg b/public/assets/img/svg/gitea-npm.svg index 7ef74e72bd..2b05c79353 100644 --- a/public/assets/img/svg/gitea-npm.svg +++ b/public/assets/img/svg/gitea-npm.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-onedev.svg b/public/assets/img/svg/gitea-onedev.svg index 94ad1bab31..7ecd18895d 100644 --- a/public/assets/img/svg/gitea-onedev.svg +++ b/public/assets/img/svg/gitea-onedev.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-openid.svg b/public/assets/img/svg/gitea-openid.svg index f4702d2cdf..10c37145a3 100644 --- a/public/assets/img/svg/gitea-openid.svg +++ b/public/assets/img/svg/gitea-openid.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-rubygems.svg b/public/assets/img/svg/gitea-rubygems.svg index 4e43bdf2f4..7cd9d34e6a 100644 --- a/public/assets/img/svg/gitea-rubygems.svg +++ b/public/assets/img/svg/gitea-rubygems.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-swift.svg b/public/assets/img/svg/gitea-swift.svg index 4182100185..891ac12b56 100644 --- a/public/assets/img/svg/gitea-swift.svg +++ b/public/assets/img/svg/gitea-swift.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/public/assets/img/svg/gitea-vagrant.svg b/public/assets/img/svg/gitea-vagrant.svg index ba50101d52..18b05e900d 100644 --- a/public/assets/img/svg/gitea-vagrant.svg +++ b/public/assets/img/svg/gitea-vagrant.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/routers/api/v1/repo/compare.go b/routers/api/v1/repo/compare.go index 9c941ea07f..7fc59ea171 100644 --- a/routers/api/v1/repo/compare.go +++ b/routers/api/v1/repo/compare.go @@ -64,7 +64,7 @@ func CompareDiff(ctx *context.APIContext) { } } - _, headGitRepo, ci, _, _ := parseCompareInfo(ctx, api.CreatePullRequestOption{ + headRepository, headGitRepo, ci, _, _ := parseCompareInfo(ctx, api.CreatePullRequestOption{ Base: infos[0], Head: infos[1], }) @@ -80,7 +80,7 @@ func CompareDiff(ctx *context.APIContext) { apiFiles := []*api.CommitAffectedFiles{} userCache := make(map[string]*user_model.User) for i := 0; i < len(ci.Commits); i++ { - apiCommit, err := convert.ToCommit(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, ci.Commits[i], userCache, + apiCommit, err := convert.ToCommit(ctx, headRepository, headGitRepo, ci.Commits[i], userCache, convert.ToCommitOptions{ Stat: true, Verification: verification, diff --git a/routers/api/v1/repo/issue.go b/routers/api/v1/repo/issue.go index 5495c4a6ba..442e109843 100644 --- a/routers/api/v1/repo/issue.go +++ b/routers/api/v1/repo/issue.go @@ -121,6 +121,12 @@ func SearchIssues(ctx *context.APIContext) { // description: Number of items per page // type: integer // minimum: 0 + // - name: sort + // in: query + // description: Type of sort + // type: string + // enum: [relevance, latest, oldest, recentupdate, leastupdate, mostcomment, leastcomment, nearduedate, farduedate] + // default: latest // responses: // "200": // "$ref": "#/responses/IssueList" @@ -276,7 +282,7 @@ func SearchIssues(ctx *context.APIContext) { IsClosed: isClosed, IncludedAnyLabelIDs: includedAnyLabels, MilestoneIDs: includedMilestones, - SortBy: issue_indexer.SortByCreatedDesc, + SortBy: issue_indexer.ParseSortBy(ctx.FormString("sort"), issue_indexer.SortByCreatedDesc), } if since != 0 { @@ -305,9 +311,10 @@ func SearchIssues(ctx *context.APIContext) { } } - // FIXME: It's unsupported to sort by priority repo when searching by indexer, - // it's indeed an regression, but I think it is worth to support filtering by indexer first. - _ = ctx.FormInt64("priority_repo_id") + priorityRepoID := ctx.FormInt64("priority_repo_id") + if priorityRepoID > 0 { + searchOpt.PriorityRepoID = optional.Some(priorityRepoID) + } ids, total, err := issue_indexer.SearchIssues(ctx, searchOpt) if err != nil { diff --git a/routers/api/v1/repo/pull.go b/routers/api/v1/repo/pull.go index c9dda124de..e7ff533d6a 100644 --- a/routers/api/v1/repo/pull.go +++ b/routers/api/v1/repo/pull.go @@ -1084,7 +1084,6 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) err error ) - // If there is no head repository, it means pull request between same repository. headInfos := strings.Split(form.Head, ":") if len(headInfos) == 1 { isSameRepo = true @@ -1094,7 +1093,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) headUser, err = user_model.GetUserByName(ctx, headInfos[0]) if err != nil { if user_model.IsErrUserNotExist(err) { - ctx.NotFound("GetUserByName") + ctx.NotFound(fmt.Errorf("the owner %s does not exist", headInfos[0])) } else { ctx.Error(http.StatusInternalServerError, "GetUserByName", err) } @@ -1104,7 +1103,7 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) // The head repository can also point to the same repo isSameRepo = ctx.Repo.Owner.ID == headUser.ID } else { - ctx.NotFound() + ctx.NotFound(fmt.Errorf("the head part of {basehead} %s must contain zero or one colon (:) but contains %d", form.Head, len(headInfos)-1)) return nil, nil, nil, "", "" } @@ -1116,16 +1115,10 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) baseIsBranch := ctx.Repo.GitRepo.IsBranchExist(baseBranch) baseIsTag := ctx.Repo.GitRepo.IsTagExist(baseBranch) if !baseIsCommit && !baseIsBranch && !baseIsTag { - // Check for short SHA usage - if baseCommit, _ := ctx.Repo.GitRepo.GetCommit(baseBranch); baseCommit != nil { - baseBranch = baseCommit.ID.String() - } else { - ctx.NotFound("BaseNotExist") - return nil, nil, nil, "", "" - } + ctx.NotFound(fmt.Errorf("could not find '%s' to be a commit, branch or tag in the base repository %s/%s", baseBranch, baseRepo.Owner.Name, baseRepo.Name)) + return nil, nil, nil, "", "" } - // Check if current user has fork of repository or in the same repository. headRepo := repo_model.GetForkedRepo(ctx, headUser.ID, baseRepo.ID) if headRepo == nil && !isSameRepo { err := baseRepo.GetBaseRepo(ctx) @@ -1134,13 +1127,11 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) return nil, nil, nil, "", "" } - // Check if baseRepo's base repository is the same as headUser's repository. if baseRepo.BaseRepo == nil || baseRepo.BaseRepo.OwnerID != headUser.ID { log.Trace("parseCompareInfo[%d]: does not have fork or in same repository", baseRepo.ID) - ctx.NotFound("GetBaseRepo") + ctx.NotFound(fmt.Errorf("%[1]s does not have a fork of %[2]s/%[3]s and %[2]s/%[3]s is not a fork of a repository from %[1]s", headUser.Name, baseRepo.Owner.Name, baseRepo.Name)) return nil, nil, nil, "", "" } - // Assign headRepo so it can be used below. headRepo = baseRepo.BaseRepo } @@ -1202,21 +1193,20 @@ func parseCompareInfo(ctx *context.APIContext, form api.CreatePullRequestOption) } // Check if head branch is valid. - headIsCommit := ctx.Repo.GitRepo.IsCommitExist(headBranch) - headIsBranch := ctx.Repo.GitRepo.IsBranchExist(headBranch) - headIsTag := ctx.Repo.GitRepo.IsTagExist(headBranch) + headIsCommit := headGitRepo.IsCommitExist(headBranch) + headIsBranch := headGitRepo.IsBranchExist(headBranch) + headIsTag := headGitRepo.IsTagExist(headBranch) if !headIsCommit && !headIsBranch && !headIsTag { - // Check if headBranch is short sha commit hash - if headCommit, _ := headGitRepo.GetCommit(headBranch); headCommit != nil { - headBranch = headCommit.ID.String() - } else { - headGitRepo.Close() - ctx.NotFound("IsRefExist", nil) - return nil, nil, nil, "", "" - } + ctx.NotFound(fmt.Errorf("could not find '%s' to be a commit, branch or tag in the head repository %s/%s", headBranch, headRepo.Owner.Name, headRepo.Name)) + return nil, nil, nil, "", "" } headBranchRef := headBranch + if headIsBranch { + headBranchRef = git.BranchPrefix + headBranch + } else if headIsTag { + headBranchRef = git.TagPrefix + headBranch + } compareInfo, err := headGitRepo.GetCompareInfo(repo_model.RepoPath(baseRepo.Owner.Name, baseRepo.Name), baseBranchRef, headBranchRef, false, false) if err != nil { diff --git a/routers/api/v1/repo/wiki.go b/routers/api/v1/repo/wiki.go index bb4cf0f211..7b6a00408a 100644 --- a/routers/api/v1/repo/wiki.go +++ b/routers/api/v1/repo/wiki.go @@ -5,6 +5,7 @@ package repo import ( "encoding/base64" + "errors" "fmt" "net/http" "net/url" @@ -506,11 +507,8 @@ func findWikiRepoCommit(ctx *context.APIContext) (*git.Repository, *git.Commit) // given tree entry, encoded with base64. Writes to ctx if an error occurs. func wikiContentsByEntry(ctx *context.APIContext, entry *git.TreeEntry) string { blob := entry.Blob() - if blob.Size() > setting.API.DefaultMaxBlobSize { - return "" - } - content, err := blob.GetBlobContentBase64() - if err != nil { + content, err := blob.GetContentBase64(setting.API.DefaultMaxBlobSize) + if err != nil && !errors.As(err, &git.BlobTooLargeError{}) { ctx.Error(http.StatusInternalServerError, "GetBlobContentBase64", err) return "" } diff --git a/routers/private/hook_post_receive.go b/routers/private/hook_post_receive.go index c7748b01c8..a856a7a00a 100644 --- a/routers/private/hook_post_receive.go +++ b/routers/private/hook_post_receive.go @@ -205,7 +205,7 @@ func HookPostReceive(ctx *gitea_context.PrivateContext) { // post update for agit pull request // FIXME: use pr.Flow to test whether it's an Agit PR or a GH PR - if git.SupportProcReceive && refFullName.IsPull() { + if refFullName.IsPull() { if repo == nil { repo = loadRepository(ctx, ownerName, repoName) if ctx.Written() { diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go index 4c0e9a8551..45992e8522 100644 --- a/routers/private/hook_pre_receive.go +++ b/routers/private/hook_pre_receive.go @@ -205,7 +205,7 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) { preReceiveBranch(ourCtx, oldCommitID, newCommitID, refFullName) case refFullName.IsTag(): preReceiveTag(ourCtx, oldCommitID, newCommitID, refFullName) - case git.SupportProcReceive && refFullName.IsFor(): + case refFullName.IsFor(): preReceiveFor(ourCtx, oldCommitID, newCommitID, refFullName) default: if ourCtx.isOverQuota { diff --git a/routers/private/hook_proc_receive.go b/routers/private/hook_proc_receive.go index cd45794261..9f6e23f158 100644 --- a/routers/private/hook_proc_receive.go +++ b/routers/private/hook_proc_receive.go @@ -7,7 +7,6 @@ import ( "net/http" repo_model "forgejo.org/models/repo" - "forgejo.org/modules/git" "forgejo.org/modules/log" "forgejo.org/modules/private" "forgejo.org/modules/web" @@ -18,10 +17,6 @@ import ( // HookProcReceive proc-receive hook - only handles agit Proc-Receive requests at present func HookProcReceive(ctx *gitea_context.PrivateContext) { opts := web.GetForm(ctx).(*private.HookOptions) - if !git.SupportProcReceive { - ctx.Status(http.StatusNotFound) - return - } results, err := agit.ProcReceive(ctx, ctx.Repo.Repository, ctx.Repo.GitRepo, opts) if err != nil { diff --git a/routers/private/serv.go b/routers/private/serv.go index 4c5b7bbccb..a4029e354c 100644 --- a/routers/private/serv.go +++ b/routers/private/serv.go @@ -14,7 +14,6 @@ import ( repo_model "forgejo.org/models/repo" "forgejo.org/models/unit" user_model "forgejo.org/models/user" - "forgejo.org/modules/git" "forgejo.org/modules/log" "forgejo.org/modules/private" "forgejo.org/modules/setting" @@ -303,7 +302,7 @@ func ServCommand(ctx *context.PrivateContext) { // the permission check to read. The pre-receive hook will do another // permission check which ensure for non AGit flow references the write // permission is checked. - if git.SupportProcReceive && unitType == unit.TypeCode && ctx.FormString("verb") == "git-receive-pack" { + if unitType == unit.TypeCode && ctx.FormString("verb") == "git-receive-pack" { mode = perm.AccessModeRead } diff --git a/routers/web/misc/misc.go b/routers/web/misc/misc.go index 87b5247599..22fdccf79f 100644 --- a/routers/web/misc/misc.go +++ b/routers/web/misc/misc.go @@ -7,7 +7,6 @@ import ( "net/http" "path" - "forgejo.org/modules/git" "forgejo.org/modules/httpcache" "forgejo.org/modules/log" "forgejo.org/modules/setting" @@ -15,10 +14,6 @@ import ( ) func SSHInfo(rw http.ResponseWriter, req *http.Request) { - if !git.SupportProcReceive { - rw.WriteHeader(http.StatusNotFound) - return - } rw.Header().Set("content-type", "text/json;charset=UTF-8") _, err := rw.Write([]byte(`{"type":"agit","version":1}`)) if err != nil { diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index de2e29ab9f..59538d8a0e 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -312,22 +312,16 @@ func ParseCompareInfo(ctx *context.Context) *common.CompareInfo { baseIsTag := ctx.Repo.GitRepo.IsTagExist(ci.BaseBranch) if !baseIsCommit && !baseIsBranch && !baseIsTag { - // Check if baseBranch is short sha commit hash - if baseCommit, _ := ctx.Repo.GitRepo.GetCommit(ci.BaseBranch); baseCommit != nil { - ci.BaseBranch = baseCommit.ID.String() - ctx.Data["BaseBranch"] = ci.BaseBranch - baseIsCommit = true - } else if ci.BaseBranch == ctx.Repo.GetObjectFormat().EmptyObjectID().String() { + if ci.BaseBranch == ctx.Repo.GetObjectFormat().EmptyObjectID().String() { if isSameRepo { ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ci.HeadBranch)) } else { ctx.Redirect(ctx.Repo.RepoLink + "/compare/" + util.PathEscapeSegments(ci.HeadRepo.FullName()) + ":" + util.PathEscapeSegments(ci.HeadBranch)) } - return nil } else { ctx.NotFound("IsRefExist", nil) - return nil } + return nil } ctx.Data["BaseIsCommit"] = baseIsCommit ctx.Data["BaseIsBranch"] = baseIsBranch @@ -514,15 +508,8 @@ func ParseCompareInfo(ctx *context.Context) *common.CompareInfo { headIsBranch := ci.HeadGitRepo.IsBranchExist(ci.HeadBranch) headIsTag := ci.HeadGitRepo.IsTagExist(ci.HeadBranch) if !headIsCommit && !headIsBranch && !headIsTag { - // Check if headBranch is short sha commit hash - if headCommit, _ := ci.HeadGitRepo.GetCommit(ci.HeadBranch); headCommit != nil { - ci.HeadBranch = headCommit.ID.String() - ctx.Data["HeadBranch"] = ci.HeadBranch - headIsCommit = true - } else { - ctx.NotFound("IsRefExist", nil) - return nil - } + ctx.NotFound("IsRefExist", nil) + return nil } ctx.Data["HeadIsCommit"] = headIsCommit ctx.Data["HeadIsBranch"] = headIsBranch diff --git a/routers/web/repo/githttp.go b/routers/web/repo/githttp.go index 650b1d88f4..42302d0e02 100644 --- a/routers/web/repo/githttp.go +++ b/routers/web/repo/githttp.go @@ -183,9 +183,7 @@ func httpBase(ctx *context.Context) *serviceHandler { if repoExist { // Because of special ref "refs/for" .. , need delay write permission check - if git.SupportProcReceive { - accessMode = perm.AccessModeRead - } + accessMode = perm.AccessModeRead if ctx.Data["IsActionsToken"] == true { taskID := ctx.Data["ActionsTaskID"].(int64) diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 5e228507c0..a34e3b7c78 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -2775,7 +2775,7 @@ func SearchIssues(ctx *context.Context) { IncludedAnyLabelIDs: includedAnyLabels, MilestoneIDs: includedMilestones, ProjectID: projectID, - SortBy: issue_indexer.SortByCreatedDesc, + SortBy: issue_indexer.ParseSortBy(ctx.FormString("sort"), issue_indexer.SortByCreatedDesc), } if since != 0 { @@ -2804,9 +2804,10 @@ func SearchIssues(ctx *context.Context) { } } - // FIXME: It's unsupported to sort by priority repo when searching by indexer, - // it's indeed an regression, but I think it is worth to support filtering by indexer first. - _ = ctx.FormInt64("priority_repo_id") + priorityRepoID := ctx.FormInt64("priority_repo_id") + if priorityRepoID > 0 { + searchOpt.PriorityRepoID = optional.Some(priorityRepoID) + } ids, total, err := issue_indexer.SearchIssues(ctx, searchOpt) if err != nil { @@ -2944,7 +2945,7 @@ func ListIssues(ctx *context.Context) { IsPull: isPull, IsClosed: isClosed, ProjectID: projectID, - SortBy: issue_indexer.SortByCreatedDesc, + SortBy: issue_indexer.ParseSortBy(ctx.FormString("sort"), issue_indexer.SortByCreatedDesc), } if since != 0 { searchOpt.UpdatedAfterUnix = optional.Some(since) diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index fd18646211..4e365f24ea 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -996,6 +996,13 @@ func viewPullFiles(ctx *context.Context, specifiedStartCommit, specifiedEndCommi ctx.Data["Verification"] = verification ctx.Data["Author"] = user_model.ValidateCommitWithEmail(ctx, curCommit) + if err := asymkey_model.CalculateTrustStatus(verification, ctx.Repo.Repository.GetTrustModel(), func(user *user_model.User) (bool, error) { + return repo_model.IsOwnerMemberCollaborator(ctx, ctx.Repo.Repository, user.ID) + }, nil); err != nil { + ctx.ServerError("CalculateTrustStatus", err) + return + } + note := &git.Note{} err = git.GetNote(ctx, ctx.Repo.GitRepo, specifiedEndCommit, note) if err == nil { diff --git a/services/context/repo.go b/services/context/repo.go index cce3a5fa70..c8876d7166 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -644,7 +644,11 @@ func RepoAssignment(ctx *Context) context.CancelFunc { ctx.Data["OpenGraphImageURL"] = repo.SummaryCardURL() ctx.Data["OpenGraphImageWidth"] = cardWidth ctx.Data["OpenGraphImageHeight"] = cardHeight - ctx.Data["OpenGraphImageAltText"] = ctx.Tr("repo.summary_card_alt", repo.FullName()) + if util.IsEmptyString(repo.Description) { + ctx.Data["OpenGraphImageAltText"] = ctx.Tr("repo.summary_card_alt", repo.FullName()) + } else { + ctx.Data["OpenGraphImageAltText"] = ctx.Tr("og.repo.summary_card.alt_description", repo.FullName(), repo.Description) + } if repo.IsFork { RetrieveBaseRepo(ctx, repo) diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index 6835dfbf36..7033264f18 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -1157,7 +1157,7 @@ func GetDiffSimple(ctx context.Context, gitRepo *git.Repository, opts *DiffOptio // so if we are using at least this version of git we don't have to tell ParsePatch to do // the skipping for us parsePatchSkipToFile := opts.SkipTo - if opts.SkipTo != "" && git.CheckGitVersionAtLeast("2.31") == nil { + if opts.SkipTo != "" { cmdDiff.AddOptionFormat("--skip-to=%s", opts.SkipTo) parsePatchSkipToFile = "" } diff --git a/services/pull/temp_repo.go b/services/pull/temp_repo.go index 1805ffc527..76ae0df018 100644 --- a/services/pull/temp_repo.go +++ b/services/pull/temp_repo.go @@ -103,11 +103,7 @@ func createTemporaryRepoForPR(ctx context.Context, pr *issues_model.PullRequest) remoteRepoName := "head_repo" baseBranch := "base" - fetchArgs := git.TrustedCmdArgs{"--no-tags"} - if git.CheckGitVersionAtLeast("2.25.0") == nil { - // Writing the commit graph can be slow and is not needed here - fetchArgs = append(fetchArgs, "--no-write-commit-graph") - } + fetchArgs := git.TrustedCmdArgs{"--no-tags", "--no-write-commit-graph"} // addCacheRepo adds git alternatives for the cacheRepoPath in the repoPath addCacheRepo := func(repoPath, cacheRepoPath string) error { diff --git a/services/repository/files/content.go b/services/repository/files/content.go index 3d2217df18..5a6006e9f2 100644 --- a/services/repository/files/content.go +++ b/services/repository/files/content.go @@ -5,6 +5,7 @@ package files import ( "context" + "errors" "fmt" "net/url" "path" @@ -205,7 +206,7 @@ func GetContents(ctx context.Context, repo *repo_model.Repository, treePath, ref } else if entry.IsLink() { contentsResponse.Type = string(ContentTypeLink) // The target of a symlink file is the content of the file - targetFromContent, err := entry.Blob().GetBlobContent(1024) + targetFromContent, err := entry.LinkTarget() if err != nil { return nil, err } @@ -273,13 +274,11 @@ func GetBlobBySHA(ctx context.Context, repo *repo_model.Repository, gitRepo *git if err != nil { return nil, err } - content := "" - if gitBlob.Size() <= setting.API.DefaultMaxBlobSize { - content, err = gitBlob.GetBlobContentBase64() - if err != nil { - return nil, err - } + content, err := gitBlob.GetContentBase64(setting.API.DefaultMaxBlobSize) + if err != nil && !errors.As(err, &git.BlobTooLargeError{}) { + return nil, err } + return &api.GitBlob{ SHA: gitBlob.ID.String(), URL: repo.APIURL() + "/git/blobs/" + url.PathEscape(gitBlob.ID.String()), diff --git a/services/repository/files/file.go b/services/repository/files/file.go index ef9a87dbcf..5b93258840 100644 --- a/services/repository/files/file.go +++ b/services/repository/files/file.go @@ -104,36 +104,35 @@ func GetAuthorAndCommitterUsers(author, committer *IdentityOptions, doer *user_m // then we use bogus User objects for them to store their FullName and Email. // If only one of the two are provided, we set both of them to it. // If neither are provided, both are the doer. - if committer != nil && committer.Email != "" { - if doer != nil && strings.EqualFold(doer.Email, committer.Email) { - committerUser = doer // the committer is the doer, so will use their user object - if committer.Name != "" { - committerUser.FullName = committer.Name + getUser := func(identity *IdentityOptions) *user_model.User { + if identity == nil || identity.Email == "" { + return nil + } + + if doer != nil && strings.EqualFold(doer.Email, identity.Email) { + user := doer // the committer is the doer, so will use their user object + if identity.Name != "" { + user.FullName = identity.Name } // Use the provided email and not revert to placeholder mail. - committerUser.KeepEmailPrivate = false - } else { - committerUser = &user_model.User{ - FullName: committer.Name, - Email: committer.Email, - } - } - } - if author != nil && author.Email != "" { - if doer != nil && strings.EqualFold(doer.Email, author.Email) { - authorUser = doer // the author is the doer, so will use their user object - if authorUser.Name != "" { - authorUser.FullName = author.Name - } - // Use the provided email and not revert to placeholder mail. - authorUser.KeepEmailPrivate = false - } else { - authorUser = &user_model.User{ - FullName: author.Name, - Email: author.Email, - } + user.KeepEmailPrivate = false + return user + } + + var id int64 + if doer != nil { + id = doer.ID + } + return &user_model.User{ + ID: id, // Needed to ensure the doer is checked to pass rules for instance signing of CRUD actions. + FullName: identity.Name, + Email: identity.Email, } } + + committerUser = getUser(committer) + authorUser = getUser(author) + if authorUser == nil { if committerUser != nil { authorUser = committerUser // No valid author was given so use the committer diff --git a/services/repository/files/patch.go b/services/repository/files/patch.go index 5b1dd65b5a..18b5226c02 100644 --- a/services/repository/files/patch.go +++ b/services/repository/files/patch.go @@ -147,11 +147,7 @@ func ApplyDiffPatch(ctx context.Context, repo *repo_model.Repository, doer *user stdout := &strings.Builder{} stderr := &strings.Builder{} - cmdApply := git.NewCommand(ctx, "apply", "--index", "--recount", "--cached", "--ignore-whitespace", "--whitespace=fix", "--binary") - if git.CheckGitVersionAtLeast("2.32") == nil { - cmdApply.AddArguments("-3") - } - + cmdApply := git.NewCommand(ctx, "apply", "--index", "--recount", "--cached", "--ignore-whitespace", "--whitespace=fix", "--binary", "-3") if err := cmdApply.Run(&git.RunOpts{ Dir: t.basePath, Stdout: stdout, diff --git a/services/webhook/discord.go b/services/webhook/discord.go index db98d40583..7259c4a995 100644 --- a/services/webhook/discord.go +++ b/services/webhook/discord.go @@ -350,7 +350,7 @@ func parseHookPullRequestEventType(event webhook_module.HookEventType) (string, case webhook_module.HookEventPullRequestReviewApproved: return "approved", nil case webhook_module.HookEventPullRequestReviewRejected: - return "rejected", nil + return "requested changes", nil case webhook_module.HookEventPullRequestReviewComment: return "comment", nil default: diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl index 59c13cd9e6..0e8382b8ab 100644 --- a/templates/swagger/v1_json.tmpl +++ b/templates/swagger/v1_json.tmpl @@ -4524,6 +4524,24 @@ "description": "Number of items per page", "name": "limit", "in": "query" + }, + { + "enum": [ + "relevance", + "latest", + "oldest", + "recentupdate", + "leastupdate", + "mostcomment", + "leastcomment", + "nearduedate", + "farduedate" + ], + "type": "string", + "default": "latest", + "description": "Type of sort", + "name": "sort", + "in": "query" } ], "responses": { diff --git a/tests/e2e/declare_repos_test.go b/tests/e2e/declare_repos_test.go index 4c68566a48..0747d896f3 100644 --- a/tests/e2e/declare_repos_test.go +++ b/tests/e2e/declare_repos_test.go @@ -9,16 +9,23 @@ import ( "testing" "time" + "forgejo.org/models/db" + issues_model "forgejo.org/models/issues" + repo_model "forgejo.org/models/repo" unit_model "forgejo.org/models/unit" "forgejo.org/models/unittest" user_model "forgejo.org/models/user" "forgejo.org/modules/git" "forgejo.org/modules/indexer/stats" + "forgejo.org/modules/optional" + "forgejo.org/modules/timeutil" + issue_service "forgejo.org/services/issue" files_service "forgejo.org/services/repository/files" "forgejo.org/tests" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "xorm.io/xorm/convert" ) // first entry represents filename @@ -29,19 +36,34 @@ type FileChanges struct { Versions []string } +// performs additional repo setup as needed +type SetupRepo func(*user_model.User, *repo_model.Repository) + // put your Git repo declarations in here // feel free to amend the helper function below or use the raw variant directly func DeclareGitRepos(t *testing.T) func() { + now := timeutil.TimeStampNow() + postIssue := func(repo *repo_model.Repository, user *user_model.User, age int64, title, content string) { + issue := &issues_model.Issue{ + RepoID: repo.ID, + PosterID: user.ID, + Title: title, + Content: content, + CreatedUnix: now.Add(-age), + } + require.NoError(t, issue_service.NewIssue(db.DefaultContext, repo, issue, nil, nil, nil)) + } + cleanupFunctions := []func(){ - newRepo(t, 2, "diff-test", []FileChanges{{ + newRepo(t, 2, "diff-test", nil, []FileChanges{{ Filename: "testfile", Versions: []string{"hello", "hallo", "hola", "native", "ubuntu-latest", "- runs-on: ubuntu-latest", "- runs-on: debian-latest"}, - }}), - newRepo(t, 2, "language-stats-test", []FileChanges{{ + }}, nil), + newRepo(t, 2, "language-stats-test", nil, []FileChanges{{ Filename: "main.rs", Versions: []string{"fn main() {", "println!(\"Hello World!\");", "}"}, - }}), - newRepo(t, 2, "mentions-highlighted", []FileChanges{ + }}, nil), + newRepo(t, 2, "mentions-highlighted", nil, []FileChanges{ { Filename: "history1.md", Versions: []string{""}, @@ -52,15 +74,38 @@ func DeclareGitRepos(t *testing.T) func() { Versions: []string{""}, CommitMsg: "Another commit which mentions @user1 in the title\nand @user2 in the text", }, - }), - newRepo(t, 2, "file-uploads", []FileChanges{{ + }, nil), + newRepo(t, 2, "file-uploads", nil, []FileChanges{{ Filename: "UPLOAD_TEST.md", Versions: []string{"# File upload test\nUse this repo to test various file upload features in new branches."}, - }}), - newRepo(t, 2, "unicode-escaping", []FileChanges{{ + }}, nil), + newRepo(t, 2, "unicode-escaping", nil, []FileChanges{{ Filename: "a-file", Versions: []string{"{a}{а}"}, - }}), + }}, nil), + newRepo(t, 11, "dependency-test", &tests.DeclarativeRepoOptions{ + UnitConfig: optional.Some(map[unit_model.Type]convert.Conversion{ + unit_model.TypeIssues: &repo_model.IssuesConfig{ + EnableDependencies: true, + }, + }), + }, []FileChanges{}, func(user *user_model.User, repo *repo_model.Repository) { + postIssue(repo, user, 500, "first issue here", "an issue created earlier") + postIssue(repo, user, 400, "second issue here (not 1)", "not the right issue, but in the right repo") + postIssue(repo, user, 300, "third issue here", "depends on things") + postIssue(repo, user, 200, "unrelated issue", "shrug emoji") + postIssue(repo, user, 100, "newest issue", "very new") + }), + newRepo(t, 11, "dependency-test-2", &tests.DeclarativeRepoOptions{ + UnitConfig: optional.Some(map[unit_model.Type]convert.Conversion{ + unit_model.TypeIssues: &repo_model.IssuesConfig{ + EnableDependencies: true, + }, + }), + }, []FileChanges{}, func(user *user_model.User, repo *repo_model.Repository) { + postIssue(repo, user, 450, "right issue", "an issue containing word right") + postIssue(repo, user, 150, "left issue", "an issue containing word left") + }), // add your repo declarations here } @@ -71,12 +116,18 @@ func DeclareGitRepos(t *testing.T) func() { } } -func newRepo(t *testing.T, userID int64, repoName string, fileChanges []FileChanges) func() { +func newRepo(t *testing.T, userID int64, repoName string, initOpts *tests.DeclarativeRepoOptions, fileChanges []FileChanges, setup SetupRepo) func() { user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: userID}) - somerepo, _, cleanupFunc := tests.CreateDeclarativeRepo(t, user, repoName, - []unit_model.Type{unit_model.TypeCode, unit_model.TypeIssues}, nil, - nil, - ) + + opts := tests.DeclarativeRepoOptions{} + if initOpts != nil { + opts = *initOpts + } + opts.Name = optional.Some(repoName) + if !opts.EnabledUnits.Has() { + opts.EnabledUnits = optional.Some([]unit_model.Type{unit_model.TypeCode, unit_model.TypeIssues}) + } + somerepo, _, cleanupFunc := tests.CreateDeclarativeRepoWithOptions(t, user, opts) var lastCommitID string for _, file := range fileChanges { @@ -122,6 +173,10 @@ func newRepo(t *testing.T, userID int64, repoName string, fileChanges []FileChan } } + if setup != nil { + setup(user, somerepo) + } + err := stats.UpdateRepoIndexer(somerepo) require.NoError(t, err) diff --git a/tests/e2e/issue-sidebar.test.e2e.ts b/tests/e2e/issue-sidebar.test.e2e.ts index bc65b0842c..34885d0d5d 100644 --- a/tests/e2e/issue-sidebar.test.e2e.ts +++ b/tests/e2e/issue-sidebar.test.e2e.ts @@ -262,3 +262,91 @@ test('New Issue: Milestone', async ({page}, workerInfo) => { await expect(selectedMilestone).toContainText('No milestone'); await save_visual(page); }); + +test.describe('Dependency dropdown', () => { + test.use({user: 'user11'}); + test('Issue: Dependencies', async ({page}) => { + const response = await page.goto('/user11/dependency-test/issues/3'); + expect(response?.status()).toBe(200); + + const depsBlock = page.locator('.issue-content-right .depending'); + const deleteDepBtn = page.locator('.issue-content-right .depending .delete-dependency-button'); + + const input = page.locator('#new-dependency-drop-list .search'); + const current = page.locator('#new-dependency-drop-list .text').first(); + const menu = page.locator('#new-dependency-drop-list .menu'); + const items = page.locator('#new-dependency-drop-list .menu .item'); + + const confirmDelete = async () => { + const modal = page.locator('.modal.remove-dependency'); + await expect(modal).toBeVisible(); + await expect(modal).toContainText('This will remove the dependency from this issue'); + await modal.locator('button.ok').click(); + }; + + // A kludge to set the dropdown to the *wrong* value so it lets us select the correct one next. + const resetDropdown = async () => { + if (await current.textContent().then((s) => s.includes('#4'))) return; + await input.click(); + await input.fill('unrelated'); + await expect(items.first()).toContainText('unrelated'); + await items.first().click(); + await expect(current).toContainText('#4'); + await input.click(); + }; + + await expect(depsBlock).toBeVisible(); + while (await deleteDepBtn.first().isVisible()) { + await deleteDepBtn.first().click(); // wipe added dependencies from any previously failed tests + await confirmDelete(); + } + await expect(depsBlock).toContainText('No dependencies set'); + + await input.scrollIntoViewIfNeeded(); + await input.click(); + + const first = 'first issue here'; + const second = 'second issue here'; + const newest = 'newest issue'; + + // Without query, it should show issues in the same repo, sorted by date, except current one. + await expect(menu).toBeVisible(); + await expect(items).toHaveCount(4); // 5 issues in this repo, minus current one + await expect(items.first()).toContainText(newest); + await expect(items.last()).toContainText(first); + await resetDropdown(); + + // With query, it should search all repos, but show current repo issues first. + await input.fill('right'); + await expect(items.first()).toContainText(second); + await expect.poll(() => items.count()).toBeGreaterThan(1); // there is an issue in user11/dependency-test-2 containing the word "right" + await resetDropdown(); + + // When entering an issue number, it should always show that one first, then all text matches. + await input.fill('1'); + await expect(items.first()).toContainText(first); + await expect(items.nth(1)).toBeVisible(); + await resetDropdown(); + + // Should behave the same with a prefix + await input.fill('#1'); + await expect(items.first()).toContainText(first); + + // Selecting an issue + await items.first().click(); + await expect(current).toContainText(first); + + // Add dependency + const link = page.locator('.issue-content-right .depending .dependency a.title'); + await page.locator('.issue-content-right .depending button').click(); + await expect(link).toHaveAttribute('href', '/user11/dependency-test/issues/1'); + + // Remove dependency + await expect(deleteDepBtn).toBeVisible(); + await deleteDepBtn.click(); + + await confirmDelete(); + + await expect(depsBlock).toContainText('No dependencies set'); + }); +}); diff --git a/tests/e2e/utils_e2e_test.go b/tests/e2e/utils_e2e_test.go index e121c604c3..efa1657cee 100644 --- a/tests/e2e/utils_e2e_test.go +++ b/tests/e2e/utils_e2e_test.go @@ -93,6 +93,7 @@ func createSessions(t testing.TB) { users := []string{ "user1", "user2", + "user11", "user12", "user18", "user29", diff --git a/tests/integration/api_block_test.go b/tests/integration/api_block_test.go index 8b25ce9283..0e2cf7ee25 100644 --- a/tests/integration/api_block_test.go +++ b/tests/integration/api_block_test.go @@ -4,8 +4,10 @@ package integration import ( + "cmp" "fmt" "net/http" + "slices" "testing" auth_model "forgejo.org/models/auth" @@ -46,6 +48,7 @@ func TestAPIUserBlock(t *testing.T) { var blockedUsers []api.BlockedUser DecodeJSON(t, resp, &blockedUsers) assert.Len(t, blockedUsers, 2) + slices.SortFunc(blockedUsers, func(a, b api.BlockedUser) int { return cmp.Compare(a.BlockID, b.BlockID) }) assert.EqualValues(t, 1, blockedUsers[0].BlockID) assert.EqualValues(t, 2, blockedUsers[1].BlockID) }) diff --git a/tests/integration/api_branch_test.go b/tests/integration/api_branch_test.go index 8e88501596..d8800217d3 100644 --- a/tests/integration/api_branch_test.go +++ b/tests/integration/api_branch_test.go @@ -116,7 +116,7 @@ func testAPICreateBranches(t *testing.T, giteaURL *url.URL) { ctx := NewAPITestContext(t, "user2", "my-noo-repo-"+objectFormat.Name(), auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) giteaURL.Path = ctx.GitPath() - t.Run("CreateRepo", doAPICreateRepository(ctx, false, objectFormat)) + t.Run("CreateRepo", doAPICreateRepository(ctx, nil, objectFormat)) testCases := []struct { OldBranch string NewBranch string diff --git a/tests/integration/api_helper_for_declarative_test.go b/tests/integration/api_helper_for_declarative_test.go index c9b6f84f4f..ada6a2c311 100644 --- a/tests/integration/api_helper_for_declarative_test.go +++ b/tests/integration/api_helper_for_declarative_test.go @@ -48,20 +48,22 @@ func (ctx APITestContext) GitPath() string { return fmt.Sprintf("%s/%s.git", ctx.Username, ctx.Reponame) } -func doAPICreateRepository(ctx APITestContext, empty bool, objectFormat git.ObjectFormat, callback ...func(*testing.T, api.Repository)) func(*testing.T) { +func doAPICreateRepository(ctx APITestContext, opts *api.CreateRepoOption, objectFormat git.ObjectFormat, callback ...func(*testing.T, api.Repository)) func(*testing.T) { return func(t *testing.T) { - createRepoOption := &api.CreateRepoOption{ - AutoInit: !empty, - Description: "Temporary repo", - Name: ctx.Reponame, - Private: true, - Template: true, - Gitignores: "", - License: "WTFPL", - Readme: "Default", - ObjectFormatName: objectFormat.Name(), + if opts == nil { + opts = &api.CreateRepoOption{ + AutoInit: true, + Description: "Temporary repo", + Name: ctx.Reponame, + Private: true, + Template: true, + Gitignores: "", + License: "WTFPL", + Readme: "Default", + } } - req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos", createRepoOption). + opts.ObjectFormatName = objectFormat.Name() + req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos", opts). AddTokenAuth(ctx.Token) if ctx.ExpectedCode != 0 { ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) @@ -237,8 +239,8 @@ func doAPICreatePullRequest(ctx APITestContext, owner, repo, baseBranch, headBra } } -func doAPIGetPullRequest(ctx APITestContext, owner, repo string, index int64) func(*testing.T) (api.PullRequest, error) { - return func(t *testing.T) (api.PullRequest, error) { +func doAPIGetPullRequest(ctx APITestContext, owner, repo string, index int64) func(*testing.T) api.PullRequest { + return func(t *testing.T) api.PullRequest { req := NewRequest(t, http.MethodGet, fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d", owner, repo, index)). AddTokenAuth(ctx.Token) @@ -248,10 +250,9 @@ func doAPIGetPullRequest(ctx APITestContext, owner, repo string, index int64) fu } resp := ctx.Session.MakeRequest(t, req, expected) - decoder := json.NewDecoder(resp.Body) pr := api.PullRequest{} - err := decoder.Decode(&pr) - return pr, err + DecodeJSON(t, resp, &pr) + return pr } } @@ -347,20 +348,40 @@ func doAPICancelAutoMergePullRequest(ctx APITestContext, owner, repo string, ind } } -func doAPIGetBranch(ctx APITestContext, branch string, callback ...func(*testing.T, api.Branch)) func(*testing.T) { - return func(t *testing.T) { +func doAPIGetBranch(ctx APITestContext, branch string) func(*testing.T) api.Branch { + return func(t *testing.T) api.Branch { req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/branches/%s", ctx.Username, ctx.Reponame, branch). AddTokenAuth(ctx.Token) + expected := http.StatusOK + if ctx.ExpectedCode != 0 { + expected = ctx.ExpectedCode + } + resp := ctx.Session.MakeRequest(t, req, expected) + + branch := api.Branch{} + DecodeJSON(t, resp, &branch) + return branch + } +} + +func doAPICreateTag(ctx APITestContext, tag, target, message string, callback ...func(*testing.T, api.Tag)) func(*testing.T) { + return func(t *testing.T) { + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/tags", ctx.Username, ctx.Reponame), &api.CreateTagOption{ + TagName: tag, + Message: message, + Target: target, + }). + AddTokenAuth(ctx.Token) if ctx.ExpectedCode != 0 { ctx.Session.MakeRequest(t, req, ctx.ExpectedCode) return } - resp := ctx.Session.MakeRequest(t, req, http.StatusOK) + resp := ctx.Session.MakeRequest(t, req, http.StatusCreated) - var branch api.Branch - DecodeJSON(t, resp, &branch) + var tag api.Tag + DecodeJSON(t, resp, &tag) if len(callback) > 0 { - callback[0](t, branch) + callback[0](t, tag) } } } diff --git a/tests/integration/api_repo_compare_test.go b/tests/integration/api_repo_compare_test.go index 35f0a21d82..1724924fdc 100644 --- a/tests/integration/api_repo_compare_test.go +++ b/tests/integration/api_repo_compare_test.go @@ -4,56 +4,268 @@ package integration import ( + "encoding/base64" + "fmt" "net/http" + "net/http/httptest" + "net/url" + "strings" "testing" + "time" auth_model "forgejo.org/models/auth" "forgejo.org/models/unittest" user_model "forgejo.org/models/user" + "forgejo.org/modules/git" api "forgejo.org/modules/structs" - "forgejo.org/tests" "github.com/stretchr/testify/assert" ) -func TestAPICompareBranches(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - // Login as User2. - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - repoName := "repo20" - - req := NewRequestf(t, "GET", "/api/v1/repos/user2/%s/compare/add-csv...remove-files-b", repoName). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - var apiResp *api.Compare - DecodeJSON(t, resp, &apiResp) - - assert.Equal(t, 2, apiResp.TotalCommits) - assert.Len(t, apiResp.Commits, 2) - assert.Len(t, apiResp.Files, 3) -} - func TestAPICompareCommits(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) - // Login as User2. - session := loginUser(t, user.Name) - token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) - - req := NewRequestf(t, "GET", "/api/v1/repos/user2/repo20/compare/c8e31bc...8babce9"). - AddTokenAuth(token) - resp := MakeRequest(t, req, http.StatusOK) - - var apiResp *api.Compare - DecodeJSON(t, resp, &apiResp) - - assert.Equal(t, 2, apiResp.TotalCommits) - assert.Len(t, apiResp.Commits, 2) - assert.Len(t, apiResp.Files, 3) + forEachObjectFormat(t, testAPICompareCommits) +} + +func testAPICompareCommits(t *testing.T, objectFormat git.ObjectFormat) { + onGiteaRun(t, func(t *testing.T, u *url.URL) { + newBranchAndFile := func(ctx APITestContext, user *user_model.User, branch, filename string) func(*testing.T) { + return func(t *testing.T) { + doAPICreateFile(ctx, filename, &api.CreateFileOptions{ + FileOptions: api.FileOptions{ + NewBranchName: branch, + Message: "create " + filename, + Author: api.Identity{ + Name: user.Name, + Email: user.Email, + }, + Committer: api.Identity{ + Name: user.Name, + Email: user.Email, + }, + Dates: api.CommitDateOptions{ + Author: time.Now(), + Committer: time.Now(), + }, + }, + ContentBase64: base64.StdEncoding.EncodeToString([]byte("content " + filename)), + })(t) + } + } + + requireErrorContains := func(t *testing.T, resp *httptest.ResponseRecorder, expected string) { + t.Helper() + + type response struct { + Message string `json:"message"` + Errors []string `json:"errors"` + } + var bodyResp response + DecodeJSON(t, resp, &bodyResp) + + if strings.Contains(bodyResp.Message, expected) { + return + } + for _, error := range bodyResp.Errors { + if strings.Contains(error, expected) { + return + } + } + t.Log(fmt.Sprintf("expected %s in %+v", expected, bodyResp)) + t.Fail() + } + + user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + user2repo := "repoA" + user2Ctx := NewAPITestContext(t, user2.Name, user2repo, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + t.Run("CreateUser2Repository", doAPICreateRepository(user2Ctx, &api.CreateRepoOption{ + AutoInit: true, + Description: "Temporary repo", + Name: user2Ctx.Reponame, + }, objectFormat)) + user2branchName := "user2branch" + t.Run("CreateUser2RepositoryBranch", newBranchAndFile(user2Ctx, user2, user2branchName, "user2branchfilename.txt")) + user2branch := doAPIGetBranch(user2Ctx, user2branchName)(t) + user2master := doAPIGetBranch(user2Ctx, "master")(t) + user2tag1 := "tag1" + t.Run("CreateUser2RepositoryTag1", doAPICreateTag(user2Ctx, user2tag1, "master", "user2branchtag1")) + user2tag2 := "tag2" + t.Run("CreateUser2RepositoryTag1", doAPICreateTag(user2Ctx, user2tag2, user2branchName, "user2branchtag2")) + + shortCommitLength := 7 + + for _, testCase := range []struct { + name string + a string + b string + }{ + { + name: "Commits", + a: user2master.Commit.ID, + b: user2branch.Commit.ID, + }, + { + name: "ShortCommits", + a: user2master.Commit.ID[:shortCommitLength], + b: user2branch.Commit.ID[:shortCommitLength], + }, + { + name: "Branches", + a: "master", + b: user2branchName, + }, + { + name: "Tags", + a: user2tag1, + b: user2tag2, + }, + } { + t.Run("SameRepo"+testCase.name, func(t *testing.T) { + // a...b + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/compare/%s...%s", user2.Name, user2repo, testCase.a, testCase.b). + AddTokenAuth(user2Ctx.Token) + resp := MakeRequest(t, req, http.StatusOK) + + var apiResp *api.Compare + DecodeJSON(t, resp, &apiResp) + + assert.Equal(t, 1, apiResp.TotalCommits) + assert.Len(t, apiResp.Commits, 1) + assert.Len(t, apiResp.Files, 1) + + // b...a + req = NewRequestf(t, "GET", "/api/v1/repos/%s/%s/compare/%s...%s", user2.Name, user2repo, testCase.b, testCase.a). + AddTokenAuth(user2Ctx.Token) + resp = MakeRequest(t, req, http.StatusOK) + + DecodeJSON(t, resp, &apiResp) + + assert.Equal(t, 0, apiResp.TotalCommits) + assert.Empty(t, apiResp.Commits) + assert.Empty(t, apiResp.Files) + }) + } + + user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}) + user4Ctx := NewAPITestContext(t, user4.Name, user2repo, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + + t.Run("ForkNotFound", func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/compare/%s...%s:%s", user2.Name, user2repo, "master", user4.Name, user2branchName). + AddTokenAuth(user2Ctx.Token) + resp := MakeRequest(t, req, http.StatusNotFound) + requireErrorContains(t, resp, "user4 does not have a fork of user2/repoA and user2/repoA is not a fork of a repository from user4") + }) + + t.Run("User4ForksUser2Repository", doAPIForkRepository(user4Ctx, user2.Name)) + user4branchName := "user4branch" + t.Run("CreateUser4RepositoryBranch", newBranchAndFile(user4Ctx, user4, user4branchName, "user4branchfilename.txt")) + user4branch := doAPIGetBranch(user4Ctx, user4branchName)(t) + user4tag4 := "tag4" + t.Run("CreateUser4RepositoryTag4", doAPICreateTag(user4Ctx, user4tag4, user4branchName, "user4branchtag4")) + + t.Run("FromTheForkedRepo", func(t *testing.T) { + // user4/repoA is a fork of user2/repoA and when evaluating + // + // user4/repoA/compare/master...user2:user2branch + // + // user2/repoA is not explicitly specified, it is implicitly the repository + // from which user4/repoA was forked + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/compare/%s...%s:%s", user4.Name, user2repo, "master", user2.Name, user2branchName). + AddTokenAuth(user4Ctx.Token) + resp := MakeRequest(t, req, http.StatusOK) + + var apiResp *api.Compare + DecodeJSON(t, resp, &apiResp) + + assert.Equal(t, 1, apiResp.TotalCommits) + assert.Len(t, apiResp.Commits, 1) + assert.Len(t, apiResp.Files, 1) + }) + + for _, testCase := range []struct { + name string + a string + b string + }{ + { + name: "Commits", + a: user2master.Commit.ID, + b: fmt.Sprintf("%s:%s", user4.Name, user4branch.Commit.ID), + }, + { + name: "ShortCommits", + a: user2master.Commit.ID[:shortCommitLength], + b: fmt.Sprintf("%s:%s", user4.Name, user4branch.Commit.ID[:shortCommitLength]), + }, + { + name: "Branches", + a: "master", + b: fmt.Sprintf("%s:%s", user4.Name, user4branchName), + }, + { + name: "Tags", + a: user2tag1, + b: fmt.Sprintf("%s:%s", user4.Name, user4tag4), + }, + { + name: "SameRepo", + a: "master", + b: fmt.Sprintf("%s:%s", user2.Name, user2branchName), + }, + } { + t.Run("ForkedRepo"+testCase.name, func(t *testing.T) { + // user2/repoA is forked into user4/repoA and when evaluating + // + // user2/repoA/compare/a...user4:b + // + // user4/repoA is not explicitly specified, it is implicitly the repository + // owned by user4 which is a fork of repoA + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/compare/%s...%s", user2.Name, user2repo, testCase.a, testCase.b). + AddTokenAuth(user2Ctx.Token) + resp := MakeRequest(t, req, http.StatusOK) + + var apiResp *api.Compare + DecodeJSON(t, resp, &apiResp) + + assert.Equal(t, 1, apiResp.TotalCommits) + assert.Len(t, apiResp.Commits, 1) + assert.Len(t, apiResp.Files, 1) + }) + } + + t.Run("ForkUserDoesNotExist", func(t *testing.T) { + notUser := "notauser" + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/compare/master...%s:branchname", user2.Name, user2repo, notUser). + AddTokenAuth(user2Ctx.Token) + resp := MakeRequest(t, req, http.StatusNotFound) + requireErrorContains(t, resp, fmt.Sprintf("the owner %s does not exist", notUser)) + }) + + t.Run("HeadHasTooManyColon", func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/compare/master...one:two:many", user2.Name, user2repo). + AddTokenAuth(user2Ctx.Token) + resp := MakeRequest(t, req, http.StatusNotFound) + requireErrorContains(t, resp, fmt.Sprintf("must contain zero or one colon (:) but contains 2")) + }) + + for _, testCase := range []struct { + what string + baseHead string + }{ + { + what: "base", + baseHead: "notexists...master", + }, + { + what: "head", + baseHead: "master...notexists", + }, + } { + t.Run("BaseHeadNotExists "+testCase.what, func(t *testing.T) { + req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/compare/%s", user2.Name, user2repo, testCase.baseHead). + AddTokenAuth(user2Ctx.Token) + resp := MakeRequest(t, req, http.StatusNotFound) + requireErrorContains(t, resp, fmt.Sprintf("could not find 'notexists' to be a commit, branch or tag in the %s", testCase.what)) + }) + } + }) } diff --git a/tests/integration/api_repo_file_create_test.go b/tests/integration/api_repo_file_create_test.go index c112653e11..4916ef97ef 100644 --- a/tests/integration/api_repo_file_create_test.go +++ b/tests/integration/api_repo_file_create_test.go @@ -287,7 +287,14 @@ func TestAPICreateFile(t *testing.T) { // Test creating a file in an empty repository forEachObjectFormat(t, func(t *testing.T, objectFormat git.ObjectFormat) { reponame := "empty-repo-" + objectFormat.Name() - doAPICreateRepository(NewAPITestContext(t, "user2", reponame, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser), true, objectFormat)(t) + ctx := NewAPITestContext(t, "user2", reponame, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + opts := &api.CreateRepoOption{ + Description: "Temporary repo", + Name: ctx.Reponame, + Private: true, + Template: true, + } + doAPICreateRepository(ctx, opts, objectFormat)(t) createFileOptions = getCreateFileOptions() fileID++ treePath = fmt.Sprintf("new/file%d.txt", fileID) diff --git a/tests/integration/api_repo_file_get_test.go b/tests/integration/api_repo_file_get_test.go index 7bd7393b01..ab82e5cf15 100644 --- a/tests/integration/api_repo_file_get_test.go +++ b/tests/integration/api_repo_file_get_test.go @@ -17,36 +17,40 @@ import ( ) func TestAPIGetRawFileOrLFS(t *testing.T) { - defer tests.PrepareTestEnv(t)() - - // Test with raw file - req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/media/README.md") - resp := MakeRequest(t, req, http.StatusOK) - assert.Equal(t, "# repo1\n\nDescription for repo1", resp.Body.String()) - - // Test with LFS onGiteaRun(t, func(t *testing.T, u *url.URL) { - httpContext := NewAPITestContext(t, "user2", "repo-lfs-test", auth_model.AccessTokenScopeWriteRepository) - doAPICreateRepository(httpContext, false, git.Sha1ObjectFormat, func(t *testing.T, repository api.Repository) { // FIXME: use forEachObjectFormat - u.Path = httpContext.GitPath() - dstPath := t.TempDir() + t.Run("Normal raw file", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - u.Path = httpContext.GitPath() - u.User = url.UserPassword("user2", userPassword) + req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/media/README.md") + resp := MakeRequest(t, req, http.StatusOK) + assert.Equal(t, "# repo1\n\nDescription for repo1", resp.Body.String()) + }) - t.Run("Clone", doGitClone(dstPath, u)) + t.Run("LFS raw file", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() - dstPath2 := t.TempDir() + httpContext := NewAPITestContext(t, "user2", "repo-lfs-test", auth_model.AccessTokenScopeWriteRepository) + doAPICreateRepository(httpContext, nil, git.Sha1ObjectFormat, func(t *testing.T, repository api.Repository) { // FIXME: use forEachObjectFormat + u.Path = httpContext.GitPath() + dstPath := t.TempDir() - t.Run("Partial Clone", doPartialGitClone(dstPath2, u)) + u.Path = httpContext.GitPath() + u.User = url.UserPassword("user2", userPassword) - lfs, _ := lfsCommitAndPushTest(t, dstPath) + t.Run("Clone", doGitClone(dstPath, u)) - reqLFS := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/media/"+lfs) - respLFS := MakeRequestNilResponseRecorder(t, reqLFS, http.StatusOK) - assert.Equal(t, littleSize, respLFS.Length) + dstPath2 := t.TempDir() - doAPIDeleteRepository(httpContext) + t.Run("Partial Clone", doPartialGitClone(dstPath2, u)) + + lfs, _ := lfsCommitAndPushTest(t, dstPath) + + reqLFS := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/media/"+lfs) + respLFS := MakeRequestNilResponseRecorder(t, reqLFS, http.StatusOK) + assert.Equal(t, littleSize, respLFS.Length) + + doAPIDeleteRepository(httpContext) + }) }) }) } diff --git a/tests/integration/api_repo_lfs_test.go b/tests/integration/api_repo_lfs_test.go index b8ba54f876..b0edf7b854 100644 --- a/tests/integration/api_repo_lfs_test.go +++ b/tests/integration/api_repo_lfs_test.go @@ -64,7 +64,7 @@ func TestAPILFSMediaType(t *testing.T) { func createLFSTestRepository(t *testing.T, name string) *repo_model.Repository { ctx := NewAPITestContext(t, "user2", "lfs-"+name+"-repo", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - t.Run("CreateRepo", doAPICreateRepository(ctx, false, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat + t.Run("CreateRepo", doAPICreateRepository(ctx, nil, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat repo, err := repo_model.GetRepositoryByOwnerAndName(db.DefaultContext, "user2", "lfs-"+name+"-repo") require.NoError(t, err) diff --git a/tests/integration/api_repo_test.go b/tests/integration/api_repo_test.go index fd62670eb3..e81f4307ee 100644 --- a/tests/integration/api_repo_test.go +++ b/tests/integration/api_repo_test.go @@ -415,7 +415,7 @@ func testAPIRepoMigrateConflict(t *testing.T, u *url.URL) { httpContext := baseAPITestContext httpContext.Reponame = "repo-tmp-17" - t.Run("CreateRepo", doAPICreateRepository(httpContext, false, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat + t.Run("CreateRepo", doAPICreateRepository(httpContext, nil, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat user, err := user_model.GetUserByName(db.DefaultContext, httpContext.Username) require.NoError(t, err) @@ -498,7 +498,7 @@ func testAPIRepoCreateConflict(t *testing.T, u *url.URL) { httpContext := baseAPITestContext httpContext.Reponame = "repo-tmp-17" - t.Run("CreateRepo", doAPICreateRepository(httpContext, false, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat + t.Run("CreateRepo", doAPICreateRepository(httpContext, nil, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat req := NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{ diff --git a/tests/integration/db_collation_test.go b/tests/integration/db_collation_test.go index bf55bdd8ee..c41209f1fe 100644 --- a/tests/integration/db_collation_test.go +++ b/tests/integration/db_collation_test.go @@ -7,6 +7,7 @@ package integration import ( "net/http" "testing" + "time" "forgejo.org/models/db" "forgejo.org/modules/setting" @@ -97,9 +98,13 @@ func TestDatabaseCollation(t *testing.T) { defer test.MockVariableValue(&setting.Database.CharsetCollation, "utf8mb4_bin")() require.NoError(t, db.ConvertDatabaseTable()) - r, err := db.CheckCollations(x) - require.NoError(t, err) - assert.Equal(t, "utf8mb4_bin", r.DatabaseCollation) + var r *db.CheckCollationsResult + assert.Eventually(t, func() bool { + r, err = db.CheckCollations(x) + require.NoError(t, err) + + return r.DatabaseCollation == "utf8mb4_bin" + }, time.Second*30, time.Second) assert.True(t, r.CollationEquals(r.ExpectedCollation, r.DatabaseCollation)) assert.Empty(t, r.InconsistentCollationColumns) @@ -117,9 +122,13 @@ func TestDatabaseCollation(t *testing.T) { defer test.MockVariableValue(&setting.Database.CharsetCollation, "utf8mb4_general_ci")() require.NoError(t, db.ConvertDatabaseTable()) - r, err := db.CheckCollations(x) - require.NoError(t, err) - assert.Equal(t, "utf8mb4_general_ci", r.DatabaseCollation) + var r *db.CheckCollationsResult + assert.Eventually(t, func() bool { + r, err = db.CheckCollations(x) + require.NoError(t, err) + + return r.DatabaseCollation == "utf8mb4_general_ci" + }, time.Second*30, time.Second) assert.True(t, r.CollationEquals(r.ExpectedCollation, r.DatabaseCollation)) assert.Empty(t, r.InconsistentCollationColumns) @@ -137,9 +146,15 @@ func TestDatabaseCollation(t *testing.T) { defer test.MockVariableValue(&setting.Database.CharsetCollation, "")() require.NoError(t, db.ConvertDatabaseTable()) + var r *db.CheckCollationsResult r, err := db.CheckCollations(x) require.NoError(t, err) - assert.True(t, r.IsCollationCaseSensitive(r.DatabaseCollation)) + assert.Eventually(t, func() bool { + r, err = db.CheckCollations(x) + require.NoError(t, err) + + return r.IsCollationCaseSensitive(r.DatabaseCollation) + }, time.Second*30, time.Second) assert.True(t, r.CollationEquals(r.ExpectedCollation, r.DatabaseCollation)) assert.Empty(t, r.InconsistentCollationColumns) }) diff --git a/tests/integration/git_test.go b/tests/integration/git_test.go index e79f6fe802..26cddf7288 100644 --- a/tests/integration/git_test.go +++ b/tests/integration/git_test.go @@ -69,7 +69,7 @@ func testGit(t *testing.T, u *url.URL) { dstPath := t.TempDir() - t.Run("CreateRepoInDifferentUser", doAPICreateRepository(forkedUserCtx, false, objectFormat)) + t.Run("CreateRepoInDifferentUser", doAPICreateRepository(forkedUserCtx, nil, objectFormat)) t.Run("AddUserAsCollaborator", doAPIAddCollaborator(forkedUserCtx, httpContext.Username, perm.AccessModeRead)) t.Run("ForkFromDifferentUser", doAPIForkRepository(httpContext, forkedUserCtx.Username)) @@ -110,7 +110,7 @@ func testGit(t *testing.T, u *url.URL) { sshContext.Reponame = "repo-tmp-18-" + objectFormat.Name() keyname := "my-testing-key" forkedUserCtx.Reponame = sshContext.Reponame - t.Run("CreateRepoInDifferentUser", doAPICreateRepository(forkedUserCtx, false, objectFormat)) + t.Run("CreateRepoInDifferentUser", doAPICreateRepository(forkedUserCtx, nil, objectFormat)) t.Run("AddUserAsCollaborator", doAPIAddCollaborator(forkedUserCtx, sshContext.Username, perm.AccessModeRead)) t.Run("ForkFromDifferentUser", doAPIForkRepository(sshContext, forkedUserCtx.Username)) @@ -529,8 +529,7 @@ func doMergeFork(ctx, baseCtx APITestContext, baseBranch, headBranch string) fun t.Run("EnsureCanSeePull", doEnsureCanSeePull(headCtx, pr, false)) t.Run("CheckPR", func(t *testing.T) { oldMergeBase := pr.MergeBase - pr2, err := doAPIGetPullRequest(baseCtx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) - require.NoError(t, err) + pr2 := doAPIGetPullRequest(baseCtx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) assert.Equal(t, oldMergeBase, pr2.MergeBase) }) t.Run("EnsurDiffNoChange", doEnsureDiffNoChange(baseCtx, pr, diffHash, diffLength)) @@ -730,24 +729,21 @@ func doAutoPRMerge(baseCtx *APITestContext, dstPath string) func(t *testing.T) { // Check pr status ctx.ExpectedCode = 0 - pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) - require.NoError(t, err) + pr = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) assert.False(t, pr.HasMerged) // Call API to add Failure status for commit t.Run("CreateStatus", addCommitStatus(api.CommitStatusFailure)) // Check pr status - pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) - require.NoError(t, err) + pr = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) assert.False(t, pr.HasMerged) // Call API to add Success status for commit t.Run("CreateStatus", addCommitStatus(api.CommitStatusSuccess)) // test pr status - pr, err = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) - require.NoError(t, err) + pr = doAPIGetPullRequest(ctx, baseCtx.Username, baseCtx.Reponame, pr.Index)(t) assert.True(t, pr.HasMerged) } } @@ -775,11 +771,6 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string return func(t *testing.T) { defer tests.PrintCurrentTest(t)() - // skip this test if git version is low - if git.CheckGitVersionAtLeast("2.29") != nil { - return - } - gitRepo, err := git.OpenRepository(git.DefaultContext, dstPath) require.NoError(t, err) @@ -836,8 +827,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string assert.Equal(t, 1, pr1.CommitsAhead) assert.Equal(t, 0, pr1.CommitsBehind) - prMsg, err := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t) - require.NoError(t, err) + prMsg := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t) assert.Equal(t, "user2/"+headBranch, pr1.HeadBranch) assert.False(t, prMsg.HasMerged) @@ -858,8 +848,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string } assert.Equal(t, 1, pr2.CommitsAhead) assert.Equal(t, 0, pr2.CommitsBehind) - prMsg, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr2.Index)(t) - require.NoError(t, err) + prMsg = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr2.Index)(t) assert.Equal(t, "user2/test/"+headBranch, pr2.HeadBranch) assert.False(t, prMsg.HasMerged) @@ -910,8 +899,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string require.NoError(t, err) unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2) - prMsg, err := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t) - require.NoError(t, err) + prMsg := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr1.Index)(t) assert.False(t, prMsg.HasMerged) assert.Equal(t, commit, prMsg.Head.Sha) @@ -928,8 +916,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string require.NoError(t, err) unittest.AssertCount(t, &issues_model.PullRequest{}, pullNum+2) - prMsg, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr2.Index)(t) - require.NoError(t, err) + prMsg = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr2.Index)(t) assert.False(t, prMsg.HasMerged) assert.Equal(t, commit, prMsg.Head.Sha) @@ -953,8 +940,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string err := pr3.LoadIssue(db.DefaultContext) require.NoError(t, err) - _, err2 := doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr3.Index)(t) - require.NoError(t, err2) + doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr3.Index)(t) assert.Equal(t, "Testing commit 2", pr3.Issue.Title) assert.Contains(t, pr3.Issue.Content, "Longer description.") @@ -975,8 +961,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string err := pr.LoadIssue(db.DefaultContext) require.NoError(t, err) - _, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr.Index)(t) - require.NoError(t, err) + doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr.Index)(t) assert.Equal(t, "my-shiny-title", pr.Issue.Title) assert.Contains(t, pr.Issue.Content, "Longer description.") @@ -998,8 +983,7 @@ func doCreateAgitFlowPull(dstPath string, ctx *APITestContext, headBranch string err := pr.LoadIssue(db.DefaultContext) require.NoError(t, err) - _, err = doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr.Index)(t) - require.NoError(t, err) + doAPIGetPullRequest(*ctx, ctx.Username, ctx.Reponame, pr.Index)(t) assert.Equal(t, "Testing commit 2", pr.Issue.Title) assert.Contains(t, pr.Issue.Content, "custom") diff --git a/tests/integration/issue_comment_test.go b/tests/integration/issue_comment_test.go index f77bfaa9bd..0c53c3028b 100644 --- a/tests/integration/issue_comment_test.go +++ b/tests/integration/issue_comment_test.go @@ -102,11 +102,35 @@ func TestIssueCommentChangeMilestone(t *testing.T) { []string{"user1 removed this from the milestone2 milestone"}, []string{"/user1", "/user2/repo1/milestone/2"}) - // Deleted milestone + // Added milestone that in the meantime was deleted testIssueCommentChangeEvent(t, htmlDoc, "2003", "octicon-milestone", "User One", "/user1", []string{"user1 added this to the (deleted) milestone"}, []string{"/user1"}) + + // Modified milestone - from a meantime deleted one to a valid one + testIssueCommentChangeEvent(t, htmlDoc, "2004", + "octicon-milestone", "User One", "/user1", + []string{"user1 modified the milestone from (deleted) to milestone1"}, + []string{"/user1", "/user2/repo1/milestone/1"}) + + // Modified milestone - from a valid one to a meantime deleted one + testIssueCommentChangeEvent(t, htmlDoc, "2005", + "octicon-milestone", "User One", "/user1", + []string{"user1 modified the milestone from milestone1 to (deleted)"}, + []string{"/user1", "/user2/repo1/milestone/1"}) + + // Modified milestone - from a meantime deleted one to a meantime deleted one + testIssueCommentChangeEvent(t, htmlDoc, "2006", + "octicon-milestone", "User One", "/user1", + []string{"user1 modified the milestone from (deleted) to (deleted)"}, + []string{"/user1"}) + + // Removed milestone that in the meantime was deleted + testIssueCommentChangeEvent(t, htmlDoc, "2007", + "octicon-milestone", "User One", "/user1", + []string{"user1 removed this from the (deleted) milestone"}, + []string{"/user1"}) } func TestIssueCommentChangeProject(t *testing.T) { diff --git a/tests/integration/issue_test.go b/tests/integration/issue_test.go index 120fe6534e..34a6e53a37 100644 --- a/tests/integration/issue_test.go +++ b/tests/integration/issue_test.go @@ -1350,8 +1350,6 @@ body: func TestIssueUnsubscription(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { - defer tests.PrepareTestEnv(t)() - user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) repo, _, f := tests.CreateDeclarativeRepoWithOptions(t, user, tests.DeclarativeRepoOptions{ AutoInit: optional.Some(false), diff --git a/tests/integration/mirror_push_test.go b/tests/integration/mirror_push_test.go index 1377d1eb1b..fea552e3de 100644 --- a/tests/integration/mirror_push_test.go +++ b/tests/integration/mirror_push_test.go @@ -43,7 +43,6 @@ func TestMirrorPush(t *testing.T) { } func testMirrorPush(t *testing.T, u *url.URL) { - defer tests.PrepareTestEnv(t)() defer test.MockVariableValue(&setting.Migrations.AllowLocalNetworks, true)() require.NoError(t, migrations.Init()) diff --git a/tests/integration/opengraph_test.go b/tests/integration/opengraph_test.go index 56fbedd351..aa6d8daf5c 100644 --- a/tests/integration/opengraph_test.go +++ b/tests/integration/opengraph_test.go @@ -98,7 +98,7 @@ func TestOpenGraphProperties(t *testing.T) { "og:url": setting.AppURL + "/user27/repo49/src/branch/master/test/test.txt", "og:type": "object", "og:image": setting.AppURL + "user27/repo49/-/summary-card", - "og:image:alt": "Summary card of repository user27/repo49", + "og:image:alt": "Summary card of repository user27/repo49, described as: A wonderful repository with more than just a README.md", "og:image:width": "1200", "og:image:height": "600", "og:site_name": siteName, @@ -141,7 +141,7 @@ func TestOpenGraphProperties(t *testing.T) { "og:description": "A wonderful repository with more than just a README.md", "og:type": "object", "og:image": setting.AppURL + "user27/repo49/-/summary-card", - "og:image:alt": "Summary card of repository user27/repo49", + "og:image:alt": "Summary card of repository user27/repo49, described as: A wonderful repository with more than just a README.md", "og:image:width": "1200", "og:image:height": "600", "og:site_name": siteName, diff --git a/tests/integration/pull_commit_test.go b/tests/integration/pull_commit_test.go index 8ca78f8147..f82fc08df4 100644 --- a/tests/integration/pull_commit_test.go +++ b/tests/integration/pull_commit_test.go @@ -4,13 +4,26 @@ package integration import ( + "context" + "encoding/base64" + "fmt" "net/http" + "net/url" + "os" "testing" + auth_model "forgejo.org/models/auth" + "forgejo.org/models/unittest" + user_model "forgejo.org/models/user" + "forgejo.org/modules/git" + "forgejo.org/modules/setting" + api "forgejo.org/modules/structs" + "forgejo.org/modules/test" pull_service "forgejo.org/services/pull" "forgejo.org/tests" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestListPullCommits(t *testing.T) { @@ -48,3 +61,83 @@ func TestPullCommitLinks(t *testing.T) { commitLinkHref, _ := commitLink.Attr("href") assert.Equal(t, "/user2/repo1/pulls/3/commits/5f22f7d0d95d614d25a5b68592adb345a4b5c7fd", commitLinkHref) } + +func TestPullCommitSignature(t *testing.T) { + t.Cleanup(func() { + // Cannot use t.Context(), it is in the done state. + require.NoError(t, git.InitFull(context.Background())) //nolint:usetesting + }) + + defer test.MockVariableValue(&setting.Repository.Signing.SigningName, "UwU")() + defer test.MockVariableValue(&setting.Repository.Signing.SigningEmail, "fox@example.com")() + defer test.MockVariableValue(&setting.Repository.Signing.CRUDActions, []string{"always"})() + defer test.MockVariableValue(&setting.Repository.Signing.InitialCommit, []string{"always"})() + + filePath := "signed.txt" + fromBranch := "master" + toBranch := "branch-signed" + + onGiteaRun(t, func(t *testing.T, u *url.URL) { + // Use a new GNUPGPHOME to avoid messing with the existing GPG keyring. + tmpDir := t.TempDir() + require.NoError(t, os.Chmod(tmpDir, 0o700)) + t.Setenv("GNUPGHOME", tmpDir) + + rootKeyPair, err := importTestingKey() + require.NoError(t, err) + defer test.MockVariableValue(&setting.Repository.Signing.SigningKey, rootKeyPair.PrimaryKey.KeyIdShortString())() + defer test.MockVariableValue(&setting.Repository.Signing.Format, "openpgp")() + + // Ensure the git config is updated with the new signing format. + require.NoError(t, git.InitFull(t.Context())) + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + testCtx := NewAPITestContext(t, user.Name, "pull-request-commit-header-signed", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + u.Path = testCtx.GitPath() + + t.Run("Create repository", doAPICreateRepository(testCtx, nil, git.Sha1ObjectFormat)) + + t.Run("Create commit", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + options := &api.CreateFileOptions{ + FileOptions: api.FileOptions{ + BranchName: fromBranch, + NewBranchName: toBranch, + Message: fmt.Sprintf("from:%s to:%s path:%s", fromBranch, toBranch, filePath), + Author: api.Identity{ + Name: user.FullName, + Email: user.Email, + }, + Committer: api.Identity{ + Name: user.FullName, + Email: user.Email, + }, + }, + ContentBase64: base64.StdEncoding.EncodeToString(fmt.Appendf(nil, "This is new text for %s", filePath)), + } + + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", testCtx.Username, testCtx.Reponame, filePath), &options). + AddTokenAuth(testCtx.Token) + resp := testCtx.Session.MakeRequest(t, req, http.StatusCreated) + + var contents api.FileResponse + DecodeJSON(t, resp, &contents) + + assert.True(t, contents.Verification.Verified) + }) + + t.Run("Create pull request", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + pr, err := doAPICreatePullRequest(testCtx, testCtx.Username, testCtx.Reponame, fromBranch, toBranch)(t) + require.NoError(t, err) + + req := NewRequest(t, "GET", fmt.Sprintf("/%s/%s/pulls/%d/commits/%s", testCtx.Username, testCtx.Reponame, pr.Index, pr.Head.Sha)) + resp := testCtx.Session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + htmlDoc.AssertElement(t, "#diff-commit-header .commit-header-row.message.isSigned.isVerified", true) + }) + }) +} diff --git a/tests/integration/pull_create_test.go b/tests/integration/pull_create_test.go index 43c9bef209..8c9bdcdfc6 100644 --- a/tests/integration/pull_create_test.go +++ b/tests/integration/pull_create_test.go @@ -287,8 +287,6 @@ func testDeleteRepository(t *testing.T, session *TestSession, ownerName, repoNam func TestPullBranchDelete(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { - defer tests.PrepareTestEnv(t)() - session := loginUser(t, "user1") testRepoFork(t, session, "user2", "repo1", "user1", "repo1") testCreateBranch(t, session, "user1", "repo1", "branch/master", "master1", http.StatusSeeOther) diff --git a/tests/integration/pull_update_test.go b/tests/integration/pull_update_test.go index 975b21a239..692699d24f 100644 --- a/tests/integration/pull_update_test.go +++ b/tests/integration/pull_update_test.go @@ -89,7 +89,6 @@ func TestAPIPullUpdateByRebase(t *testing.T) { func TestAPIViewUpdateSettings(t *testing.T) { onGiteaRun(t, func(t *testing.T, giteaURL *url.URL) { - defer tests.PrepareTestEnv(t)() // Create PR to test user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) org26 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 26}) @@ -136,7 +135,6 @@ func TestViewPullUpdateByRebase(t *testing.T) { func testViewPullUpdate(t *testing.T, updateStyle string) { defer test.MockVariableValue(&setting.Repository.PullRequest.DefaultUpdateStyle, updateStyle)() - defer tests.PrepareTestEnv(t)() // Create PR to test user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) org26 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 26}) diff --git a/tests/integration/repo_signed_tag_test.go b/tests/integration/repo_signed_tag_test.go index 686690bd19..16d8841304 100644 --- a/tests/integration/repo_signed_tag_test.go +++ b/tests/integration/repo_signed_tag_test.go @@ -25,10 +25,6 @@ import ( ) func TestRepoSSHSignedTags(t *testing.T) { - if git.CheckGitVersionAtLeast("2.34") != nil { - t.Skip("Skipping, does not support SSH signing") - return - } defer tests.PrepareTestEnv(t)() // Preparations diff --git a/tests/integration/repo_test.go b/tests/integration/repo_test.go index b66726a3e6..598a508294 100644 --- a/tests/integration/repo_test.go +++ b/tests/integration/repo_test.go @@ -5,15 +5,19 @@ package integration import ( + "context" + "encoding/base64" "fmt" "net/http" "net/url" + "os" "path" "regexp" "strings" "testing" "time" + auth_model "forgejo.org/models/auth" "forgejo.org/models/db" repo_model "forgejo.org/models/repo" unit_model "forgejo.org/models/unit" @@ -22,6 +26,7 @@ import ( "forgejo.org/modules/git" "forgejo.org/modules/optional" "forgejo.org/modules/setting" + api "forgejo.org/modules/structs" "forgejo.org/modules/test" "forgejo.org/modules/translation" repo_service "forgejo.org/services/repository" @@ -193,9 +198,9 @@ func TestViewRepoWithSymlinks(t *testing.T) { // TestViewAsRepoAdmin tests PR #2167 func TestViewAsRepoAdmin(t *testing.T) { - for _, user := range []string{"user2", "user4"} { - defer tests.PrepareTestEnv(t)() + defer tests.PrepareTestEnv(t)() + for _, user := range []string{"user2", "user4"} { session := loginUser(t, user) req := NewRequest(t, "GET", "/user2/repo1.git") @@ -682,6 +687,79 @@ func TestViewCommit(t *testing.T) { assert.True(t, test.IsNormalPageCompleted(resp.Body.String()), "non-existing commit should render 404 page") } +func TestViewCommitSignature(t *testing.T) { + t.Cleanup(func() { + // Cannot use t.Context(), it is in the done state. + require.NoError(t, git.InitFull(context.Background())) //nolint:usetesting + }) + + defer test.MockVariableValue(&setting.Repository.Signing.SigningName, "UwU")() + defer test.MockVariableValue(&setting.Repository.Signing.SigningEmail, "fox@example.com")() + defer test.MockVariableValue(&setting.Repository.Signing.CRUDActions, []string{"always"})() + defer test.MockVariableValue(&setting.Repository.Signing.InitialCommit, []string{"always"})() + + filePath := "signed.txt" + fromBranch := "master" + toBranch := "branch-signed" + + onGiteaRun(t, func(t *testing.T, u *url.URL) { + // Use a new GNUPGPHOME to avoid messing with the existing GPG keyring. + tmpDir := t.TempDir() + require.NoError(t, os.Chmod(tmpDir, 0o700)) + t.Setenv("GNUPGHOME", tmpDir) + + rootKeyPair, err := importTestingKey() + require.NoError(t, err) + defer test.MockVariableValue(&setting.Repository.Signing.SigningKey, rootKeyPair.PrimaryKey.KeyIdShortString())() + defer test.MockVariableValue(&setting.Repository.Signing.Format, "openpgp")() + + // Ensure the git config is updated with the new signing format. + require.NoError(t, git.InitFull(t.Context())) + + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}) + testCtx := NewAPITestContext(t, user.Name, "commit-header-signed", auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + u.Path = testCtx.GitPath() + + t.Run("Create repository", doAPICreateRepository(testCtx, nil, git.Sha1ObjectFormat)) + + t.Run("Create commit", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + options := &api.CreateFileOptions{ + FileOptions: api.FileOptions{ + BranchName: fromBranch, + NewBranchName: toBranch, + Message: fmt.Sprintf("from:%s to:%s path:%s", fromBranch, toBranch, filePath), + Author: api.Identity{ + Name: user.FullName, + Email: user.Email, + }, + Committer: api.Identity{ + Name: user.FullName, + Email: user.Email, + }, + }, + ContentBase64: base64.StdEncoding.EncodeToString(fmt.Appendf(nil, "This is new text for %s", filePath)), + } + + req := NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", testCtx.Username, testCtx.Reponame, filePath), &options). + AddTokenAuth(testCtx.Token) + resp := testCtx.Session.MakeRequest(t, req, http.StatusCreated) + + var contents api.FileResponse + DecodeJSON(t, resp, &contents) + + assert.True(t, contents.Verification.Verified) + + req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s/commit/%s", testCtx.Username, testCtx.Reponame, contents.Commit.SHA)) + resp = testCtx.Session.MakeRequest(t, req, http.StatusOK) + + htmlDoc := NewHTMLParser(t, resp.Body) + htmlDoc.AssertElement(t, ".commit-header-row.message.isSigned.isVerified", true) + }) + }) +} + func TestCommitView(t *testing.T) { defer tests.PrepareTestEnv(t)() diff --git a/tests/integration/signing_git_test.go b/tests/integration/signing_git_test.go index 9d69306e0a..8b6b30ecab 100644 --- a/tests/integration/signing_git_test.go +++ b/tests/integration/signing_git_test.go @@ -42,10 +42,6 @@ func TestInstanceSigning(t *testing.T) { defer test.MockProtect(&setting.Repository.Signing.CRUDActions)() t.Run("SSH", func(t *testing.T) { - if git.CheckGitVersionAtLeast("2.34") != nil { - t.Skip("Skipping, does not support git SSH signing") - return - } defer tests.PrintCurrentTest(t)() pubKeyContent, err := os.ReadFile("tests/integration/ssh-signing-key.pub") @@ -109,13 +105,14 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O defer tests.PrintCurrentTest(t)() testCtx := NewAPITestContext(t, username, "initial-unsigned"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) - t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) + t.Run("CheckMasterBranchUnsigned", func(t *testing.T) { + branch := doAPIGetBranch(testCtx, "master")(t) assert.NotNil(t, branch.Commit) assert.NotNil(t, branch.Commit.Verification) assert.False(t, branch.Commit.Verification.Verified) assert.Empty(t, branch.Commit.Verification.Signature) - })) + }) t.Run("CreateCRUDFile-Never", crudActionCreateFile( t, testCtx, user, "master", "never", "unsigned-never.txt", func(t *testing.T, response api.FileResponse) { assert.False(t, response.Verification.Verified) @@ -191,25 +188,27 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O defer tests.PrintCurrentTest(t)() testCtx := NewAPITestContext(t, username, "initial-pubkey"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) - t.Run("CheckMasterBranchSigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) + t.Run("CheckMasterBranchSigned", func(t *testing.T) { + branch := doAPIGetBranch(testCtx, "master")(t) require.NotNil(t, branch.Commit) require.NotNil(t, branch.Commit.Verification) assert.True(t, branch.Commit.Verification.Verified) assert.Equal(t, "fox@example.com", branch.Commit.Verification.Signer.Email) - })) + }) }) t.Run("No publickey", func(t *testing.T) { defer tests.PrintCurrentTest(t)() testCtx := NewAPITestContext(t, "user4", "initial-no-pubkey"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) - t.Run("CheckMasterBranchSigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) + t.Run("CheckMasterBranchSigned", func(t *testing.T) { + branch := doAPIGetBranch(testCtx, "master")(t) require.NotNil(t, branch.Commit) require.NotNil(t, branch.Commit.Verification) assert.False(t, branch.Commit.Verification.Verified) - })) + }) }) }) @@ -226,25 +225,27 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O testCtx := NewAPITestContext(t, username, "initial-2fa"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) unittest.AssertSuccessfulInsert(t, &auth_model.WebAuthnCredential{UserID: user.ID}) - t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) - t.Run("CheckMasterBranchSigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) + t.Run("CheckMasterBranchSigned", func(t *testing.T) { + branch := doAPIGetBranch(testCtx, "master")(t) require.NotNil(t, branch.Commit) require.NotNil(t, branch.Commit.Verification) assert.True(t, branch.Commit.Verification.Verified) assert.Equal(t, "fox@example.com", branch.Commit.Verification.Signer.Email) - })) + }) }) - t.Run("No publickey", func(t *testing.T) { + t.Run("No 2fa", func(t *testing.T) { defer tests.PrintCurrentTest(t)() testCtx := NewAPITestContext(t, "user4", "initial-no-2fa"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) - t.Run("CheckMasterBranchSigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) + t.Run("CheckMasterBranchSigned", func(t *testing.T) { + branch := doAPIGetBranch(testCtx, "master")(t) require.NotNil(t, branch.Commit) require.NotNil(t, branch.Commit.Verification) assert.False(t, branch.Commit.Verification.Verified) - })) + }) }) }) @@ -253,13 +254,14 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O setting.Repository.Signing.InitialCommit = []string{"always"} testCtx := NewAPITestContext(t, username, "initial-always"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) - t.Run("CheckMasterBranchSigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) + t.Run("CheckMasterBranchSigned", func(t *testing.T) { + branch := doAPIGetBranch(testCtx, "master")(t) require.NotNil(t, branch.Commit) require.NotNil(t, branch.Commit.Verification) assert.True(t, branch.Commit.Verification.Verified) assert.Equal(t, "fox@example.com", branch.Commit.Verification.Signer.Email) - })) + }) }) t.Run("AlwaysSign-Initial-CRUD-Never", func(t *testing.T) { @@ -267,7 +269,7 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O setting.Repository.Signing.CRUDActions = []string{"never"} testCtx := NewAPITestContext(t, username, "initial-always-never"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) t.Run("CreateCRUDFile-Never", crudActionCreateFile( t, testCtx, user, "master", "never", "unsigned-never.txt", func(t *testing.T, response api.FileResponse) { assert.False(t, response.Verification.Verified) @@ -279,7 +281,7 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O setting.Repository.Signing.CRUDActions = []string{"parentsigned"} testCtx := NewAPITestContext(t, username, "initial-always-parent"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) t.Run("CreateCRUDFile-ParentSigned", crudActionCreateFile( t, testCtx, user, "master", "parentsigned", "signed-parent.txt", func(t *testing.T, response api.FileResponse) { assert.True(t, response.Verification.Verified) @@ -287,12 +289,71 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O })) }) + t.Run("AlwaysSign-Initial-CRUD-Pubkey", func(t *testing.T) { + setting.Repository.Signing.CRUDActions = []string{"pubkey"} + + t.Run("Has publickey", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + testCtx := NewAPITestContext(t, username, "initial-always-pubkey"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) + t.Run("CreateCRUDFile-Pubkey", crudActionCreateFile( + t, testCtx, user, "master", "pubkey", "signed-pubkey.txt", func(t *testing.T, response api.FileResponse) { + assert.True(t, response.Verification.Verified) + assert.Equal(t, "fox@example.com", response.Verification.Signer.Email) + })) + }) + + t.Run("No publickey", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + testCtx := NewAPITestContext(t, "user4", "initial-always-no-pubkey"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) + t.Run("CreateCRUDFile-Pubkey", crudActionCreateFile( + t, testCtx, user, "master", "pubkey", "unsigned-pubkey.txt", func(t *testing.T, response api.FileResponse) { + assert.False(t, response.Verification.Verified) + })) + }) + }) + + t.Run("AlwaysSign-Initial-CRUD-Twofa", func(t *testing.T) { + setting.Repository.Signing.CRUDActions = []string{"twofa"} + + t.Run("Has 2fa", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + t.Cleanup(func() { + unittest.AssertSuccessfulDelete(t, &auth_model.WebAuthnCredential{UserID: user.ID}) + }) + + testCtx := NewAPITestContext(t, username, "initial-always-twofa"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + unittest.AssertSuccessfulInsert(t, &auth_model.WebAuthnCredential{UserID: user.ID}) + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) + t.Run("CreateCRUDFile-Twofa", crudActionCreateFile( + t, testCtx, user, "master", "twofa", "signed-twofa.txt", func(t *testing.T, response api.FileResponse) { + assert.True(t, response.Verification.Verified) + assert.Equal(t, "fox@example.com", response.Verification.Signer.Email) + })) + }) + + t.Run("No 2fa", func(t *testing.T) { + defer tests.PrintCurrentTest(t)() + + testCtx := NewAPITestContext(t, "user4", "initial-always-no-twofa"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) + t.Run("CreateCRUDFile-Pubkey", crudActionCreateFile( + t, testCtx, user, "master", "twofa", "unsigned-twofa.txt", func(t *testing.T, response api.FileResponse) { + assert.False(t, response.Verification.Verified) + })) + }) + }) + t.Run("AlwaysSign-Initial-CRUD-Always", func(t *testing.T) { defer tests.PrintCurrentTest(t)() setting.Repository.Signing.CRUDActions = []string{"always"} testCtx := NewAPITestContext(t, username, "initial-always-always"+suffix, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteUser) - t.Run("CreateRepository", doAPICreateRepository(testCtx, false, objectFormat)) + t.Run("CreateRepository", doAPICreateRepository(testCtx, nil, objectFormat)) t.Run("CreateCRUDFile-Always", crudActionCreateFile( t, testCtx, user, "master", "always", "signed-always.txt", func(t *testing.T, response api.FileResponse) { assert.True(t, response.Verification.Verified) @@ -310,12 +371,13 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O require.NoError(t, err) t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index)) }) - t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { + t.Run("CheckMasterBranchUnsigned", func(t *testing.T) { + branch := doAPIGetBranch(testCtx, "master")(t) require.NotNil(t, branch.Commit) require.NotNil(t, branch.Commit.Verification) assert.False(t, branch.Commit.Verification.Verified) assert.Empty(t, branch.Commit.Verification.Signature) - })) + }) }) t.Run("BaseSignedMerging", func(t *testing.T) { @@ -328,12 +390,13 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O require.NoError(t, err) t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index)) }) - t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { + t.Run("CheckMasterBranchUnsigned", func(t *testing.T) { + branch := doAPIGetBranch(testCtx, "master")(t) require.NotNil(t, branch.Commit) require.NotNil(t, branch.Commit.Verification) assert.False(t, branch.Commit.Verification.Verified) assert.Empty(t, branch.Commit.Verification.Signature) - })) + }) }) t.Run("CommitsSignedMerging", func(t *testing.T) { @@ -346,11 +409,12 @@ func testCRUD(t *testing.T, u *url.URL, signingFormat string, objectFormat git.O require.NoError(t, err) t.Run("MergePR", doAPIMergePullRequest(testCtx, testCtx.Username, testCtx.Reponame, pr.Index)) }) - t.Run("CheckMasterBranchUnsigned", doAPIGetBranch(testCtx, "master", func(t *testing.T, branch api.Branch) { + t.Run("CheckMasterBranchUnsigned", func(t *testing.T) { + branch := doAPIGetBranch(testCtx, "master")(t) require.NotNil(t, branch.Commit) require.NotNil(t, branch.Commit.Verification) assert.True(t, branch.Commit.Verification.Verified) - })) + }) }) } diff --git a/tests/integration/ssh_key_test.go b/tests/integration/ssh_key_test.go index aece9c3fd9..a92694d2fa 100644 --- a/tests/integration/ssh_key_test.go +++ b/tests/integration/ssh_key_test.go @@ -55,7 +55,13 @@ func testPushDeployKeyOnEmptyRepo(t *testing.T, u *url.URL) { keyname := fmt.Sprintf("%s-push", ctx.Reponame) u.Path = ctx.GitPath() - t.Run("CreateEmptyRepository", doAPICreateRepository(ctx, true, objectFormat)) + opts := &api.CreateRepoOption{ + Description: "Temporary repo", + Name: ctx.Reponame, + Private: true, + Template: true, + } + t.Run("CreateEmptyRepository", doAPICreateRepository(ctx, opts, objectFormat)) t.Run("CheckIsEmpty", doCheckRepositoryEmptyStatus(ctx, true)) @@ -105,8 +111,8 @@ func testKeyOnlyOneType(t *testing.T, u *url.URL) { failCtx := ctx failCtx.ExpectedCode = http.StatusUnprocessableEntity - t.Run("CreateRepository", doAPICreateRepository(ctx, false, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat - t.Run("CreateOtherRepository", doAPICreateRepository(otherCtx, false, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat + t.Run("CreateRepository", doAPICreateRepository(ctx, nil, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat + t.Run("CreateOtherRepository", doAPICreateRepository(otherCtx, nil, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat withKeyFile(t, keyname, func(keyFile string) { var userKeyPublicKeyID int64 @@ -180,7 +186,7 @@ func testKeyOnlyOneType(t *testing.T, u *url.URL) { t.Run("DeleteOtherRepository", doAPIDeleteRepository(otherCtxWithDeleteRepo)) - t.Run("RecreateRepository", doAPICreateRepository(ctxWithDeleteRepo, false, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat + t.Run("RecreateRepository", doAPICreateRepository(ctxWithDeleteRepo, nil, git.Sha1ObjectFormat)) // FIXME: use forEachObjectFormat t.Run("CreateUserKey", doAPICreateUserKey(ctx, keyname, keyFile, func(t *testing.T, publicKey api.PublicKey) { userKeyPublicKeyID = publicKey.ID diff --git a/tests/test_utils.go b/tests/test_utils.go index 75d1f98914..ee786624a2 100644 --- a/tests/test_utils.go +++ b/tests/test_utils.go @@ -13,6 +13,7 @@ import ( "path/filepath" "runtime" "strings" + "sync/atomic" "testing" "time" @@ -42,6 +43,7 @@ import ( "github.com/google/uuid" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "xorm.io/xorm/convert" ) func exitf(format string, args ...any) { @@ -311,9 +313,26 @@ func PrepareCleanPackageData(t testing.TB) { require.NoError(t, storage.Clean(storage.Packages)) } +// inTestEnv keeps track if we are current inside a test environment, this is +// used to detect if testing code tries to prepare a test environment more than +// once. +var inTestEnv atomic.Bool + func PrepareTestEnv(t testing.TB, skip ...int) func() { t.Helper() - deferFn := PrintCurrentTest(t, util.OptionalArg(skip)+1) + + if !inTestEnv.CompareAndSwap(false, true) { + t.Fatal("Cannot prepare a test environment if you are already in a test environment. This is a bug in your testing code.") + } + + deferPrintCurrentTest := PrintCurrentTest(t, util.OptionalArg(skip)+1) + deferFn := func() { + deferPrintCurrentTest() + + if !inTestEnv.CompareAndSwap(true, false) { + t.Fatal("Tried to leave test environment, but we are no longer in a test environment. This should not happen.") + } + } cancelProcesses(t, 30*time.Second) t.Cleanup(func() { cancelProcesses(t, 0) }) // cancel remaining processes in a non-blocking way @@ -342,6 +361,7 @@ type DeclarativeRepoOptions struct { Name optional.Option[string] EnabledUnits optional.Option[[]unit_model.Type] DisabledUnits optional.Option[[]unit_model.Type] + UnitConfig optional.Option[map[unit_model.Type]convert.Conversion] Files optional.Option[[]*files_service.ChangeRepoFile] WikiBranch optional.Option[string] AutoInit optional.Option[bool] @@ -390,9 +410,14 @@ func CreateDeclarativeRepoWithOptions(t *testing.T, owner *user_model.User, opts enabledUnits = make([]repo_model.RepoUnit, len(units)) for i, unitType := range units { + var config convert.Conversion + if cfg, ok := opts.UnitConfig.Value()[unitType]; ok { + config = cfg + } enabledUnits[i] = repo_model.RepoUnit{ RepoID: repo.ID, Type: unitType, + Config: config, } } } diff --git a/web_src/fomantic/package-lock.json b/web_src/fomantic/package-lock.json index cea707672e..d91eb98866 100644 --- a/web_src/fomantic/package-lock.json +++ b/web_src/fomantic/package-lock.json @@ -494,9 +494,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.0.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.3.tgz", - "integrity": "sha512-R4I/kzCYAdRLzfiCabn9hxWfbuHS573x+r0dJMkkzThEa7pbrcDWK+9zu3e7aBOouf+rQAciqPFMnxwr0aWgKg==", + "version": "24.0.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.7.tgz", + "integrity": "sha512-YIEUUr4yf8q8oQoXPpSlnvKNVKDQlPMWrmOcgzoduo7kvA2UF0/BwJ/eMKFTiTtkNL17I0M6Xe2tvwFU7be6iw==", "license": "MIT", "dependencies": { "undici-types": "~7.8.0" @@ -1117,9 +1117,9 @@ } }, "node_modules/browserslist": { - "version": "4.25.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.0.tgz", - "integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==", + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", "funding": [ { "type": "opencollective", @@ -1136,8 +1136,8 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001718", - "electron-to-chromium": "^1.5.160", + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, @@ -1249,9 +1249,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001724", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001724.tgz", - "integrity": "sha512-WqJo7p0TbHDOythNTqYujmaJTvtYRZrjpP8TCvH6Vb9CYJerJNKamKzIWOM4BkQatWj9H2lYulpdAQNBe7QhNA==", + "version": "1.0.30001726", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", "funding": [ { "type": "opencollective", @@ -2005,9 +2005,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.171", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.171.tgz", - "integrity": "sha512-scWpzXEJEMrGJa4Y6m/tVotb0WuvNmasv3wWVzUAeCgKU0ToFOhUW6Z+xWnRQANMYGxN4ngJXIThgBJOqzVPCQ==", + "version": "1.5.177", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.177.tgz", + "integrity": "sha512-7EH2G59nLsEMj97fpDuvVcYi6lwTcM1xuWw3PssD8xzboAW7zj7iB3COEEEATUfjLHrs5uKBLQT03V/8URx06g==", "license": "ISC" }, "node_modules/emoji-regex": { diff --git a/web_src/js/components/DashboardRepoList.vue b/web_src/js/components/DashboardRepoList.vue index 58c5461baa..35f1082a93 100644 --- a/web_src/js/components/DashboardRepoList.vue +++ b/web_src/js/components/DashboardRepoList.vue @@ -340,10 +340,10 @@ export default sfc; // activate the IDE's Vue plugin {{ textMyOrgs }} {{ organizationsTotalCount }}
-

-
+

+
{{ textMyRepos }} - {{ reposTotalCount }} + {{ reposTotalCount }}