diff --git a/.github/workflows/files-changed.yml b/.github/workflows/files-changed.yml index 78915d81ea..50aa2045e2 100644 --- a/.github/workflows/files-changed.yml +++ b/.github/workflows/files-changed.yml @@ -71,15 +71,15 @@ jobs: - "assets/emoji.json" - "package.json" - "pnpm-lock.yaml" + - "pnpm-workspace.yaml" - "Makefile" - - ".eslintrc.cjs" - - ".npmrc" docs: - "**/*.md" - ".markdownlint.yaml" - "package.json" - "pnpm-lock.yaml" + - "pnpm-workspace.yaml" actions: - ".github/workflows/*" @@ -108,6 +108,7 @@ jobs: - "Makefile" - "package.json" - "pnpm-lock.yaml" + - "pnpm-workspace.yaml" - ".spectral.yaml" yaml: diff --git a/.github/workflows/pull-compliance.yml b/.github/workflows/pull-compliance.yml index 3d7e9f8be8..6a3ec2d73e 100644 --- a/.github/workflows/pull-compliance.yml +++ b/.github/workflows/pull-compliance.yml @@ -44,7 +44,7 @@ jobs: go-version-file: go.mod check-latest: true cache: false - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + - uses: pnpm/action-setup@8912a9102ac27614460f54aedde9e1e7f9aec20d # v6.0.5 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24 @@ -130,7 +130,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + - uses: pnpm/action-setup@8912a9102ac27614460f54aedde9e1e7f9aec20d # v6.0.5 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24 diff --git a/.github/workflows/pull-e2e-tests.yml b/.github/workflows/pull-e2e-tests.yml index 6743e6c37a..f81026a5ae 100644 --- a/.github/workflows/pull-e2e-tests.yml +++ b/.github/workflows/pull-e2e-tests.yml @@ -29,7 +29,7 @@ jobs: with: cache-name: e2e build-cache: "false" - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + - uses: pnpm/action-setup@8912a9102ac27614460f54aedde9e1e7f9aec20d # v6.0.5 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24 diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml index ee5c4fcfb6..e1fd87b759 100644 --- a/.github/workflows/release-nightly.yml +++ b/.github/workflows/release-nightly.yml @@ -22,7 +22,7 @@ jobs: with: go-version-file: go.mod check-latest: true - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + - uses: pnpm/action-setup@8912a9102ac27614460f54aedde9e1e7f9aec20d # v6.0.5 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24 diff --git a/.github/workflows/release-tag-rc.yml b/.github/workflows/release-tag-rc.yml index 05b56b6a2b..b7d5cbcd22 100644 --- a/.github/workflows/release-tag-rc.yml +++ b/.github/workflows/release-tag-rc.yml @@ -23,7 +23,7 @@ jobs: with: go-version-file: go.mod check-latest: true - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + - uses: pnpm/action-setup@8912a9102ac27614460f54aedde9e1e7f9aec20d # v6.0.5 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24 diff --git a/.github/workflows/release-tag-version.yml b/.github/workflows/release-tag-version.yml index da99fb072d..82a1930f0a 100644 --- a/.github/workflows/release-tag-version.yml +++ b/.github/workflows/release-tag-version.yml @@ -26,7 +26,7 @@ jobs: with: go-version-file: go.mod check-latest: true - - uses: pnpm/action-setup@fc06bc1257f339d1d5d8b3a19a8cae5388b55320 # v5.0.0 + - uses: pnpm/action-setup@8912a9102ac27614460f54aedde9e1e7f9aec20d # v6.0.5 - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 with: node-version: 24 diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 790a49a6eb..0000000000 --- a/.npmrc +++ /dev/null @@ -1,7 +0,0 @@ -audit=false -fund=false -update-notifier=false -save-exact=true -auto-install-peers=true -dedupe-peer-dependents=false -enable-pre-post-scripts=true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c62950d84b..27103a991b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -195,7 +195,7 @@ PR titles must follow the [Conventional Commits](https://www.conventionalcommits type(scope)!: subject ``` -The allowed types are `build`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style`, and `test`. The generic `chore` type is intentionally not accepted; pick a more descriptive type instead. +The allowed types are `build`, `chore`, `ci`, `docs`, `feat`, `fix`, `perf`, `refactor`, `revert`, `style`, and `test`. The generic `chore` type is intentionally not accepted; pick a more descriptive type instead. Examples: diff --git a/Dockerfile b/Dockerfile index b4f3e21c64..c69b5a65d1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.26-alpine3.23 AS frontend-build RUN apk --no-cache add build-base git nodejs pnpm WORKDIR /src -COPY package.json pnpm-lock.yaml .npmrc ./ +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ RUN --mount=type=cache,target=/root/.local/share/pnpm/store pnpm install --frozen-lockfile COPY --exclude=.git/ . . RUN make frontend diff --git a/Dockerfile.rootless b/Dockerfile.rootless index 3edf85738a..6f029067de 100644 --- a/Dockerfile.rootless +++ b/Dockerfile.rootless @@ -3,7 +3,7 @@ FROM --platform=$BUILDPLATFORM docker.io/library/golang:1.26-alpine3.23 AS frontend-build RUN apk --no-cache add build-base git nodejs pnpm WORKDIR /src -COPY package.json pnpm-lock.yaml .npmrc ./ +COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./ RUN --mount=type=cache,target=/root/.local/share/pnpm/store pnpm install --frozen-lockfile COPY --exclude=.git/ . . RUN make frontend diff --git a/Makefile b/Makefile index a1f81738de..1087dc8410 100644 --- a/Makefile +++ b/Makefile @@ -11,15 +11,15 @@ COMMA := , XGO_VERSION := go-1.26.x -AIR_PACKAGE ?= github.com/air-verse/air@v1 # renovate: datasource=go -EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3 # renovate: datasource=go -GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.11.4 # renovate: datasource=go +AIR_PACKAGE ?= github.com/air-verse/air@v1.65.1 # renovate: datasource=go +EDITORCONFIG_CHECKER_PACKAGE ?= github.com/editorconfig-checker/editorconfig-checker/v3/cmd/editorconfig-checker@v3.6.1 # renovate: datasource=go +GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2 # renovate: datasource=go GXZ_PACKAGE ?= github.com/ulikunitz/xz/cmd/gxz@v0.5.15 # renovate: datasource=go MISSPELL_PACKAGE ?= github.com/golangci/misspell/cmd/misspell@v0.8.0 # renovate: datasource=go -SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.1 # renovate: datasource=go -XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest -GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1 # renovate: datasource=go -ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.11 # renovate: datasource=go +SWAGGER_PACKAGE ?= github.com/go-swagger/go-swagger/cmd/swagger@v0.33.2 # renovate: datasource=go +XGO_PACKAGE ?= src.techknowlogick.com/xgo@v1.9.0 # renovate: datasource=go +GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.3.0 # renovate: datasource=go +ACTIONLINT_PACKAGE ?= github.com/rhysd/actionlint/cmd/actionlint@v1.7.12 # renovate: datasource=go HAS_GO := $(shell hash $(GO) > /dev/null 2>&1 && echo yes) ifeq ($(HAS_GO), yes) @@ -613,6 +613,7 @@ update-js: node_modules ## update js dependencies .PHONY: nolyfill nolyfill: node_modules ## apply nolyfill overrides to package.json and relock pnpm exec nolyfill install + node tools/migrate-nolyfills.ts pnpm install @touch node_modules diff --git a/go.mod b/go.mod index bc88c0b79b..20d26abc23 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module code.gitea.io/gitea -go 1.26.2 +go 1.26.3 // rfc5280 said: "The serial number is an integer assigned by the CA to each certificate." // But some CAs use negative serial number, just relax the check. related: diff --git a/models/auth/source.go b/models/auth/source.go index 7a008f08a8..16f36eb0c2 100644 --- a/models/auth/source.go +++ b/models/auth/source.go @@ -100,7 +100,7 @@ var registeredConfigs = map[Type]func() Config{} // RegisterTypeConfig register a config for a provided type func RegisterTypeConfig(typ Type, exemplar Config) { - if reflect.TypeOf(exemplar).Kind() == reflect.Ptr { + if reflect.TypeOf(exemplar).Kind() == reflect.Pointer { // Pointer: registeredConfigs[typ] = func() Config { return reflect.New(reflect.ValueOf(exemplar).Elem().Type()).Interface().(Config) diff --git a/models/db/context.go b/models/db/context.go index 8bb14f1389..5e74f458ec 100644 --- a/models/db/context.go +++ b/models/db/context.go @@ -17,12 +17,15 @@ import ( "xorm.io/xorm" ) -type engineContextKeyType struct{} +type contextKey struct{ key string } -var engineContextKey = engineContextKeyType{} +var ( + contextKeyEngine = contextKey{"engine"} + ContextKeyTestFixtures = contextKey{"test-fixtures"} +) func withContextEngine(ctx context.Context, e Engine) context.Context { - return context.WithValue(ctx, engineContextKey, e) + return context.WithValue(ctx, contextKeyEngine, e) } var ( @@ -68,7 +71,7 @@ func contextSafetyCheck(e Engine) { // GetEngine gets an existing db Engine/Statement or creates a new Session func GetEngine(ctx context.Context) Engine { - if engine, ok := ctx.Value(engineContextKey).(Engine); ok { + if engine, ok := ctx.Value(contextKeyEngine).(Engine); ok { // if reusing the existing session, need to do "contextSafetyCheck" because the Iterate creates a "autoResetStatement=false" session contextSafetyCheck(engine) return engine @@ -309,7 +312,7 @@ func InTransaction(ctx context.Context) bool { } func getTransactionSession(ctx context.Context) *xorm.Session { - e, _ := ctx.Value(engineContextKey).(Engine) + e, _ := ctx.Value(contextKeyEngine).(Engine) if sess, ok := e.(*xorm.Session); ok && sess.IsInTx() { return sess } diff --git a/models/db/engine_hook.go b/models/db/engine_hook.go index 8709a2c2a1..8d8ed3992c 100644 --- a/models/db/engine_hook.go +++ b/models/db/engine_hook.go @@ -22,11 +22,17 @@ type EngineHook struct { var _ contexts.Hook = (*EngineHook)(nil) func (*EngineHook) BeforeProcess(c *contexts.ContextHook) (context.Context, error) { + if c.Ctx.Value(ContextKeyTestFixtures) != nil { + return c.Ctx, nil + } ctx, _ := gtprof.GetTracer().Start(c.Ctx, gtprof.TraceSpanDatabase) return ctx, nil } func (h *EngineHook) AfterProcess(c *contexts.ContextHook) error { + if c.Ctx.Value(ContextKeyTestFixtures) != nil { + return nil + } span := gtprof.GetContextSpan(c.Ctx) if span != nil { // Do not record SQL parameters here: diff --git a/models/migrations/migrationtest/tests.go b/models/migrations/migrationtest/tests.go index ed8bb16ef1..e0f7d04bb0 100644 --- a/models/migrations/migrationtest/tests.go +++ b/models/migrations/migrationtest/tests.go @@ -72,7 +72,7 @@ func PrepareTestEnv(t *testing.T, skip int, syncModels ...any) (*xorm.Engine, fu if err := unittest.InitFixtures( unittest.FixturesOptions{ Dir: fixturesDir, - }, x); err != nil { + }); err != nil { t.Errorf("error whilst initializing fixtures from %s: %v", fixturesDir, err) return x, deferFn } @@ -110,6 +110,7 @@ func mainTest(m *testing.M) int { if err = git.InitFull(); err != nil { return testlogger.MainErrorf("Unable to InitFull: %v", err) } + setting.Database.SlowQueryThreshold = 0 setting.LoadDBSetting() setting.InitLoggersForTest() return m.Run() diff --git a/models/unittest/fixtures.go b/models/unittest/fixtures.go index a9a01a3227..872bdffc6d 100644 --- a/models/unittest/fixtures.go +++ b/models/unittest/fixtures.go @@ -4,7 +4,10 @@ package unittest import ( + "context" "fmt" + "strings" + "unicode" "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/modules/auth/password/hash" @@ -12,11 +15,13 @@ import ( "code.gitea.io/gitea/modules/util" "xorm.io/xorm" + "xorm.io/xorm/contexts" "xorm.io/xorm/schemas" ) type FixturesLoader interface { Load() error + MarkTableChanged(tableName string) } var fixturesLoader FixturesLoader @@ -57,15 +62,101 @@ func loadFixtureResetSeqPgsql(e *xorm.Engine) error { return nil } +type fixturesHookStruct struct{} + +func cutSpaceForSQL(s string) (string, string, bool) { + s = strings.TrimSpace(s) + pos := strings.IndexFunc(s, unicode.IsSpace) + if pos == -1 { + return s, "", false + } + return s[:pos], strings.TrimSpace(s[pos+1:]), true +} + +func trimTableNameQuotes(s string) string { + pos := strings.IndexByte(s, '.') + if pos != -1 { + s = s[pos+1:] + } + return strings.Trim(s, "\"`[]") +} + +func (f fixturesHookStruct) BeforeProcess(c *contexts.ContextHook) (context.Context, error) { + if c.Ctx.Value(db.ContextKeyTestFixtures) != nil { + return c.Ctx, nil + } + ctx, sql := c.Ctx, c.SQL + cmdPart, cmdRemaining, ok := cutSpaceForSQL(sql) + if !ok { + return ctx, nil + } + + // ignore the SQLs which don't change data + if util.AsciiEqualFold(cmdPart, "SELECT") || + util.AsciiEqualFold(cmdPart, "SHOW") || + util.AsciiEqualFold(cmdPart, "PRAGMA") || + util.AsciiEqualFold(cmdPart, "ALTER") || + util.AsciiEqualFold(cmdPart, "CREATE") || + util.AsciiEqualFold(cmdPart, "DROP") || + util.AsciiEqualFold(cmdPart, "IF") || + util.AsciiEqualFold(cmdPart, "SET") || + util.AsciiEqualFold(cmdPart, "sp_rename") || + util.AsciiEqualFold(cmdPart, "BEGIN") || + util.AsciiEqualFold(cmdPart, "ROLLBACK") || + util.AsciiEqualFold(cmdPart, "COMMIT") { + return ctx, nil + } + + switch { + case util.AsciiEqualFold(cmdPart, "INSERT"): + cmdPart, cmdRemaining, _ = cutSpaceForSQL(cmdRemaining) + if util.AsciiEqualFold(cmdPart, "INTO") { + cmdPart, cmdRemaining, _ = cutSpaceForSQL(cmdRemaining) + } + fixturesLoader.MarkTableChanged(trimTableNameQuotes(cmdPart)) + case util.AsciiEqualFold(cmdPart, "MERGE"): + cmdPart, cmdRemaining, _ = cutSpaceForSQL(cmdRemaining) + if util.AsciiEqualFold(cmdPart, "INTO") { + cmdPart, cmdRemaining, _ = cutSpaceForSQL(cmdRemaining) + } + fixturesLoader.MarkTableChanged(trimTableNameQuotes(cmdPart)) + case util.AsciiEqualFold(cmdPart, "UPDATE"): + cmdPart, cmdRemaining, _ = cutSpaceForSQL(cmdRemaining) + fixturesLoader.MarkTableChanged(trimTableNameQuotes(cmdPart)) + case util.AsciiEqualFold(cmdPart, "DELETE"): + cmdPart, cmdRemaining, _ = cutSpaceForSQL(cmdRemaining) + if util.AsciiEqualFold(cmdPart, "FROM") { + cmdPart, cmdRemaining, _ = cutSpaceForSQL(cmdRemaining) + } + fixturesLoader.MarkTableChanged(trimTableNameQuotes(cmdPart)) + case util.AsciiEqualFold(cmdPart, "TRUNCATE"): + cmdPart, cmdRemaining, _ = cutSpaceForSQL(cmdRemaining) + if util.AsciiEqualFold(cmdPart, "TABLE") { + cmdPart, cmdRemaining, _ = cutSpaceForSQL(cmdRemaining) + } + fixturesLoader.MarkTableChanged(trimTableNameQuotes(cmdPart)) + default: + // should either parse the table name if it changes data, or ignore it + panic("unrecognized sql: " + sql) + } + _ = cmdRemaining + return ctx, nil +} + +func (f fixturesHookStruct) AfterProcess(c *contexts.ContextHook) error { + return nil +} + // InitFixtures initialize test fixtures for a test database -func InitFixtures(opts FixturesOptions, engine ...*xorm.Engine) (err error) { - xormEngine := util.IfZero(util.OptionalArg(engine), GetXORMEngine()) +func InitFixtures(opts FixturesOptions) (err error) { + xormEngine := GetXORMEngine() fixturesLoader, err = NewFixturesLoader(xormEngine, opts) // fixturesLoader = NewFixturesLoaderVendor(xormEngine, opts) // register the dummy hash algorithm function used in the test fixtures _ = hash.Register("dummy", hash.NewDummyHasher) setting.PasswordHashAlgo, _ = hash.SetDefaultPasswordHashAlgorithm("dummy") + xormEngine.AddHook(&fixturesHookStruct{}) return err } diff --git a/models/unittest/fixtures_loader.go b/models/unittest/fixtures_loader.go index 5b79cb5643..e2f2f5846f 100644 --- a/models/unittest/fixtures_loader.go +++ b/models/unittest/fixtures_loader.go @@ -4,6 +4,7 @@ package unittest import ( + "context" "database/sql" "encoding/hex" "fmt" @@ -11,6 +12,7 @@ import ( "path/filepath" "slices" "strings" + "sync" "code.gitea.io/gitea/models/db" @@ -32,7 +34,7 @@ type FixtureItem struct { type fixturesLoaderInternal struct { xormEngine *xorm.Engine - xormTableNames map[string]bool + tableSyncMap sync.Map db *sql.DB dbType schemas.DBType fixtures map[string]*FixtureItem @@ -148,25 +150,36 @@ func (f *fixturesLoaderInternal) Load() error { } defer func() { _ = tx.Rollback() }() + ctx := context.WithValue(context.Background(), db.ContextKeyTestFixtures, true) + for _, fixture := range f.fixtures { - if !f.xormTableNames[fixture.tableName] { + synced, existing := f.tableSyncMap.Load(fixture.tableName) + if synced == true || !existing { continue } if err := f.loadFixtures(tx, fixture); err != nil { return fmt.Errorf("failed to load fixtures from %s: %w", fixture.fileFullPath, err) } + f.tableSyncMap.Store(fixture.tableName, true) } if err = tx.Commit(); err != nil { return err } - for xormTableName := range f.xormTableNames { - if f.fixtures[xormTableName] == nil { - _, _ = f.xormEngine.Exec("DELETE FROM `" + xormTableName + "`") + f.tableSyncMap.Range(func(k, v any) bool { + tableName, synced := k.(string), v.(bool) + if !synced && f.fixtures[tableName] == nil { + _, _ = f.xormEngine.Context(ctx).Exec("DELETE FROM `" + tableName + "`") } - } + f.tableSyncMap.Store(tableName, true) + return true + }) return nil } +func (f *fixturesLoaderInternal) MarkTableChanged(tableName string) { + f.tableSyncMap.Store(tableName, false) +} + func FixturesFileFullPaths(dir string, files []string) (map[string]*FixtureItem, error) { if files != nil && len(files) == 0 { return nil, nil //nolint:nilnil // load nothing @@ -215,11 +228,12 @@ func NewFixturesLoader(x *xorm.Engine, opts FixturesOptions) (FixturesLoader, er f.paramPlaceholder = func(idx int) string { return "?" } } + // If a model is not imported in a package (no bean is registered), the table won't exist in database. + // So only use tables of registered models (beans). xormBeans, _ := db.NamesToBean() - f.xormTableNames = map[string]bool{} for _, bean := range xormBeans { - f.xormTableNames[x.TableName(bean)] = true + beanTableName := x.TableName(bean) + f.tableSyncMap.Store(trimTableNameQuotes(beanTableName), false) } - return f, nil } diff --git a/models/unittest/reflection.go b/models/unittest/reflection.go index bc96a05973..47145d6c03 100644 --- a/models/unittest/reflection.go +++ b/models/unittest/reflection.go @@ -9,7 +9,7 @@ import ( ) func fieldByName(v reflect.Value, field string) reflect.Value { - if v.Kind() == reflect.Ptr { + if v.Kind() == reflect.Pointer { v = v.Elem() } f := v.FieldByName(field) diff --git a/modules/packages/rubygems/marshal.go b/modules/packages/rubygems/marshal.go index 1505221acc..bf7b7e2c53 100644 --- a/modules/packages/rubygems/marshal.go +++ b/modules/packages/rubygems/marshal.go @@ -91,7 +91,7 @@ func (e *MarshalEncoder) marshal(v any) error { val := reflect.ValueOf(v) typ := reflect.TypeOf(v) - if typ.Kind() == reflect.Ptr { + if typ.Kind() == reflect.Pointer { val = val.Elem() typ = typ.Elem() } diff --git a/modules/setting/database.go b/modules/setting/database.go index a65c8e9495..edaaf32c14 100644 --- a/modules/setting/database.go +++ b/modules/setting/database.go @@ -41,7 +41,8 @@ var ( AutoMigration bool SlowQueryThreshold time.Duration }{ - IterateBufferSize: 50, + IterateBufferSize: 50, + SlowQueryThreshold: 5 * time.Second, } ) @@ -86,7 +87,7 @@ func loadDBSetting(rootCfg ConfigProvider) { Database.DBConnectRetries = sec.Key("DB_RETRIES").MustInt(10) Database.DBConnectBackoff = sec.Key("DB_RETRY_BACKOFF").MustDuration(3 * time.Second) Database.AutoMigration = sec.Key("AUTO_MIGRATION").MustBool(true) - Database.SlowQueryThreshold = sec.Key("SLOW_QUERY_THRESHOLD").MustDuration(5 * time.Second) + Database.SlowQueryThreshold = sec.Key("SLOW_QUERY_THRESHOLD").MustDuration(Database.SlowQueryThreshold) } // DatabaseType FIXME: it is also used directly with "schemas.DBType", so the names must be consistent diff --git a/modules/web/handler.go b/modules/web/handler.go index 46865d0f40..ce3b49eaef 100644 --- a/modules/web/handler.go +++ b/modules/web/handler.go @@ -9,6 +9,7 @@ import ( "net" "net/http" "reflect" + "slices" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/web/routing" @@ -131,8 +132,8 @@ type middlewareProvider = func(next http.Handler) http.Handler func executeMiddlewaresHandler(w http.ResponseWriter, r *http.Request, middlewares []middlewareProvider, endpoint http.HandlerFunc) { handler := endpoint - for i := len(middlewares) - 1; i >= 0; i-- { - handler = middlewares[i](handler).ServeHTTP + for _, middleware := range slices.Backward(middlewares) { + handler = middleware(handler).ServeHTTP } handler(w, r) } diff --git a/modules/web/middleware/binding.go b/modules/web/middleware/binding.go index 988beb47c5..85d8541081 100644 --- a/modules/web/middleware/binding.go +++ b/modules/web/middleware/binding.go @@ -29,7 +29,7 @@ func AssignForm(form any, data map[string]any) { typ := reflect.TypeOf(form) val := reflect.ValueOf(form) - for typ.Kind() == reflect.Ptr { + for typ.Kind() == reflect.Pointer { typ = typ.Elem() val = val.Elem() } @@ -104,7 +104,7 @@ func Validate(errs binding.Errors, data map[string]any, f Form, l translation.Lo data["ErrorMsg"] = l.TrString("form.unknown_error") typ := reflect.TypeOf(f) - if typ.Kind() == reflect.Ptr { + if typ.Kind() == reflect.Pointer { typ = typ.Elem() } diff --git a/package.json b/package.json index 76becc9ea0..3922d52922 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "type": "module", - "packageManager": "pnpm@10.33.2", + "packageManager": "pnpm@11.0.8", "engines": { "node": ">= 22.18.0", - "pnpm": ">= 10.0.0" + "pnpm": ">= 11.0.0" }, "dependencies": { "@citation-js/core": "0.7.21", @@ -124,34 +124,5 @@ "updates": "17.16.8", "vitest": "4.1.5", "vue-tsc": "3.2.8" - }, - "pnpm": { - "peerDependencyRules": { - "allowedVersions": { - "eslint-plugin-github>eslint": ">=9" - } - }, - "overrides": { - "array-includes": "npm:@nolyfill/array-includes@^1", - "array.prototype.findlastindex": "npm:@nolyfill/array.prototype.findlastindex@^1", - "array.prototype.flat": "npm:@nolyfill/array.prototype.flat@^1", - "array.prototype.flatmap": "npm:@nolyfill/array.prototype.flatmap@^1", - "es-aggregate-error": "npm:@nolyfill/es-aggregate-error@^1", - "hasown": "npm:@nolyfill/hasown@^1", - "is-core-module": "npm:@nolyfill/is-core-module@^1", - "object.assign": "npm:@nolyfill/object.assign@^1", - "object.fromentries": "npm:@nolyfill/object.fromentries@^1", - "object.groupby": "npm:@nolyfill/object.groupby@^1", - "object.values": "npm:@nolyfill/object.values@^1", - "safe-buffer": "npm:@nolyfill/safe-buffer@^1", - "safe-regex-test": "npm:@nolyfill/safe-regex-test@^1", - "safer-buffer": "npm:@nolyfill/safer-buffer@^1", - "string.prototype.includes": "npm:@nolyfill/string.prototype.includes@^1", - "string.prototype.trimend": "npm:@nolyfill/string.prototype.trimend@^1", - "object-keys": "npm:@nolyfill/object-keys@^1", - "object.entries": "npm:@nolyfill/object.entries@^1", - "abab": "npm:@nolyfill/abab@^1", - "es-set-tostringtag": "npm:@nolyfill/es-set-tostringtag@^1" - } } } diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000000..7f57b0898b --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,32 @@ +packages: [.] # workaround for https://github.com/SukkaW/nolyfill/issues/119 +savePrefix: '' +dedupePeerDependents: false +updateNotifier: false +strictDepBuilds: false +minimumReleaseAge: 0 + +peerDependencyRules: + allowedVersions: + eslint-plugin-github>eslint: '>=9' + +overrides: + array-includes: npm:@nolyfill/array-includes@^1 + array.prototype.findlastindex: npm:@nolyfill/array.prototype.findlastindex@^1 + array.prototype.flat: npm:@nolyfill/array.prototype.flat@^1 + array.prototype.flatmap: npm:@nolyfill/array.prototype.flatmap@^1 + es-aggregate-error: npm:@nolyfill/es-aggregate-error@^1 + hasown: npm:@nolyfill/hasown@^1 + is-core-module: npm:@nolyfill/is-core-module@^1 + object.assign: npm:@nolyfill/object.assign@^1 + object.fromentries: npm:@nolyfill/object.fromentries@^1 + object.groupby: npm:@nolyfill/object.groupby@^1 + object.values: npm:@nolyfill/object.values@^1 + safe-buffer: npm:@nolyfill/safe-buffer@^1 + safe-regex-test: npm:@nolyfill/safe-regex-test@^1 + safer-buffer: npm:@nolyfill/safer-buffer@^1 + string.prototype.includes: npm:@nolyfill/string.prototype.includes@^1 + string.prototype.trimend: npm:@nolyfill/string.prototype.trimend@^1 + object-keys: npm:@nolyfill/object-keys@^1 + object.entries: npm:@nolyfill/object.entries@^1 + abab: npm:@nolyfill/abab@^1 + es-set-tostringtag: npm:@nolyfill/es-set-tostringtag@^1 diff --git a/renovate.json5 b/renovate.json5 index 3362f52c23..aaffda2bc9 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -2,7 +2,7 @@ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": ["config:recommended", "helpers:pinGitHubActionDigests", "customManagers:githubActionsVersions"], "configMigration": true, - "enabledManagers": ["github-actions", "gomod", "npm", "pep621", "nix"], + "enabledManagers": ["github-actions", "gomod", "npm", "pep621", "nix", "custom.regex"], "labels": ["dependencies"], "branchPrefix": "renovate/", "schedule": ["* * * * 1"], // dependency update PRs weekly, vulnerabilityAlerts bypasses this @@ -60,19 +60,32 @@ }, { "groupName": "go dependencies", - "matchDatasources": ["go"], // covers gomod manager + Makefile go-tool customManager + "matchManagers": ["gomod"], "postUpgradeTasks": { "commands": ["make tidy"], "fileFilters": ["go.mod", "go.sum", "assets/go-licenses.json"], "executionMode": "branch", }, }, + { + "groupName": "tool dependencies", + "matchManagers": ["custom.regex"], + "matchFileNames": ["**/Makefile"], + }, + { + "matchManagers": ["gomod"], + "matchDepNames": ["go"], + "matchDepTypes": ["golang"], + "rangeStrategy": "bump", + "schedule": ["at any time"], + "minimumReleaseAge": "0", + }, { "groupName": "npm dependencies", "matchManagers": ["npm"], "postUpgradeTasks": { "commands": ["make svg nolyfill"], - "fileFilters": ["package.json", "pnpm-lock.yaml", "public/assets/img/svg/**"], + "fileFilters": ["package.json", "pnpm-lock.yaml", "pnpm-workspace.yaml", "public/assets/img/svg/**"], "executionMode": "branch", }, }, diff --git a/services/context/permission.go b/services/context/permission.go index 1f40e26153..16de86bbc6 100644 --- a/services/context/permission.go +++ b/services/context/permission.go @@ -57,14 +57,14 @@ func RequireUnitReader(unitTypes ...unit.Type) func(ctx *Context) { } } -// CheckRepoScopedToken check whether personal access token has repo scope +// CheckRepoScopedToken checks whether the authenticated API token has repo scope. func CheckRepoScopedToken(ctx *Context, repo *repo_model.Repository, level auth_model.AccessTokenScopeLevel) { - if !ctx.IsBasicAuth || ctx.Data["IsApiToken"] != true { + if ctx.Data["IsApiToken"] != true { return } scope, ok := ctx.Data["ApiTokenScope"].(auth_model.AccessTokenScope) - if ok { // it's a personal access token but not oauth2 token + if ok { var scopeMatched bool requiredScopes := auth_model.GetRequiredScopes(level, auth_model.AccessTokenScopeCategoryRepository) @@ -76,7 +76,7 @@ func CheckRepoScopedToken(ctx *Context, repo *repo_model.Repository, level auth_ return } - if publicOnly && repo.IsPrivate { + if publicOnly && repo != nil && repo.IsPrivate { ctx.HTTPError(http.StatusForbidden) return } diff --git a/services/convert/action_test.go b/services/convert/action_test.go index 9efc0e36a8..5d56d10a48 100644 --- a/services/convert/action_test.go +++ b/services/convert/action_test.go @@ -9,6 +9,7 @@ import ( "testing" actions_model "code.gitea.io/gitea/models/actions" + db "code.gitea.io/gitea/models/db" repo_model "code.gitea.io/gitea/models/repo" "code.gitea.io/gitea/models/unit" "code.gitea.io/gitea/models/unittest" @@ -124,3 +125,55 @@ func TestToActionWorkflowRun_UsesTriggerEvent(t *testing.T) { require.NoError(t, err) assert.Equal(t, "schedule", apiRun.Event) } + +func TestToActionWorkflowJob_StepStatusIsIndependentOfJobStatus(t *testing.T) { + assert.NoError(t, unittest.PrepareTestDatabase()) + ctx := t.Context() + + run := &actions_model.ActionRun{ + ID: 9001, + RepoID: 2, + TriggerUserID: 1, + WorkflowID: "test.yaml", + Index: 12345, + Ref: "refs/heads/main", + Status: actions_model.StatusFailure, + } + require.NoError(t, db.Insert(ctx, run)) + + task := &actions_model.ActionTask{ + ID: 900102, + JobID: 9001, + RepoID: 2, + Status: actions_model.StatusFailure, + } + require.NoError(t, db.Insert(ctx, task)) + + job := &actions_model.ActionRunJob{ + ID: 90010203, + RunID: 9001, + TaskID: 900102, + RepoID: 2, + Name: "test-job-name", + Attempt: 1, + JobID: "test-job-id", + Status: actions_model.StatusFailure, + } + require.NoError(t, db.Insert(ctx, job)) + + require.NoError(t, db.Insert(ctx, + &actions_model.ActionTaskStep{TaskID: task.ID, RepoID: 2, Index: 0, Name: "step-success", Status: actions_model.StatusSuccess}, + &actions_model.ActionTaskStep{TaskID: task.ID, RepoID: 2, Index: 1, Name: "step-failure", Status: actions_model.StatusFailure}, + )) + + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) + + apiJob, err := ToActionWorkflowJob(ctx, repo, task, job) + require.NoError(t, err) + require.Len(t, apiJob.Steps, 2) + + assert.Equal(t, "completed", apiJob.Steps[0].Status, "step 0 status") + assert.Equal(t, "success", apiJob.Steps[0].Conclusion, "step 0 conclusion (succeeded before the failure)") + assert.Equal(t, "completed", apiJob.Steps[1].Status, "step 1 status") + assert.Equal(t, "failure", apiJob.Steps[1].Conclusion, "step 1 conclusion") +} diff --git a/services/convert/convert.go b/services/convert/convert.go index 21fd1cadb4..dae0587ec4 100644 --- a/services/convert/convert.go +++ b/services/convert/convert.go @@ -314,33 +314,31 @@ func ToActionWorkflowRun(ctx context.Context, run *actions_model.ActionRun, atte }, nil } -func ToWorkflowRunAction(status actions_model.Status) string { - var action string +func ToWorkflowRunAction(status actions_model.Status) (action string) { switch status { case actions_model.StatusWaiting, actions_model.StatusBlocked: action = "requested" case actions_model.StatusRunning: action = "in_progress" - } - if status.IsDone() { - action = "completed" + default: + if status.IsDone() { + action = "completed" + } else { + setting.PanicInDevOrTesting("unknown action status: %v", status) + } } return action } -func ToActionsStatus(status actions_model.Status) (string, string) { - var action string - var conclusion string +func ToActionsStatus(status actions_model.Status) (action, conclusion string) { switch status { - // This is a naming conflict of the webhook between Gitea and GitHub Actions case actions_model.StatusWaiting: - action = "queued" + action = "queued" // "waiting" is a naming conflict of the webhook between Gitea and GitHub Actions case actions_model.StatusBlocked: - action = "waiting" + action = "waiting" // naming conflict (as above) case actions_model.StatusRunning: action = "in_progress" - } - if status.IsDone() { + default: action = "completed" switch status { case actions_model.StatusSuccess: @@ -351,6 +349,8 @@ func ToActionsStatus(status actions_model.Status) (string, string) { conclusion = "failure" case actions_model.StatusSkipped: conclusion = "skipped" + default: + setting.PanicInDevOrTesting("unknown action status: %v", status) } } return action, conclusion @@ -390,7 +390,7 @@ func ToActionWorkflowJob(ctx context.Context, repo *repo_model.Repository, task runnerName = runner.Name } for i, step := range task.Steps { - stepStatus, stepConclusion := ToActionsStatus(job.Status) + stepStatus, stepConclusion := ToActionsStatus(step.Status) steps = append(steps, &api.ActionWorkflowStep{ Name: step.Name, Number: int64(i), diff --git a/services/cron/tasks.go b/services/cron/tasks.go index 4b0660d9e9..f64c44f46b 100644 --- a/services/cron/tasks.go +++ b/services/cron/tasks.go @@ -57,7 +57,7 @@ func (t *Task) IsEnabled() bool { // GetConfig will return a copy of the task's config func (t *Task) GetConfig() Config { - if reflect.TypeOf(t.config).Kind() == reflect.Ptr { + if reflect.TypeOf(t.config).Kind() == reflect.Pointer { // Pointer: return reflect.New(reflect.ValueOf(t.config).Elem().Type()).Interface().(Config) } diff --git a/services/gitdiff/highlightdiff.go b/services/gitdiff/highlightdiff.go index 1de3963788..0fb4e6a92b 100644 --- a/services/gitdiff/highlightdiff.go +++ b/services/gitdiff/highlightdiff.go @@ -6,6 +6,7 @@ package gitdiff import ( "bytes" "html/template" + "slices" "strings" "unicode/utf8" @@ -385,8 +386,7 @@ func (hcd *highlightCodeDiff) recoverOneDiff(str string) template.HTML { } // close all opening tags - for i := len(tagStack) - 1; i >= 0; i-- { - tagToClose := tagStack[i] + for _, tagToClose := range slices.Backward(tagStack) { // get the closing tag "" from "" or "" pos := strings.IndexAny(tagToClose, " >") // pos must be positive, because the tags were pushed by us diff --git a/services/issue/commit.go b/services/issue/commit.go index 68ccc906b6..93bfa78ae6 100644 --- a/services/issue/commit.go +++ b/services/issue/commit.go @@ -10,6 +10,7 @@ import ( "html" "net/url" "regexp" + "slices" "strconv" "strings" "time" @@ -123,9 +124,7 @@ func getIssueFromRef(ctx context.Context, repo *repo_model.Repository, index int // UpdateIssuesCommit checks if issues are manipulated by commit message. func UpdateIssuesCommit(ctx context.Context, doer *user_model.User, repo *repo_model.Repository, commits []*repository.PushCommit, branchName string) error { // Commits are appended in the reverse order. - for i := len(commits) - 1; i >= 0; i-- { - c := commits[i] - + for _, c := range slices.Backward(commits) { type markKey struct { ID int64 Action references.XRefAction diff --git a/services/pull/pull.go b/services/pull/pull.go index 3524b4d9bf..4f76df31da 100644 --- a/services/pull/pull.go +++ b/services/pull/pull.go @@ -9,6 +9,7 @@ import ( "fmt" "io" "regexp" + "slices" "strings" "time" "unicode/utf8" @@ -845,8 +846,7 @@ func GetSquashMergeCommitMessages(ctx context.Context, pr *issues_model.PullRequ // use PR's commit messages as squash commit message // commits list is in reverse chronological order maxMsgSize := setting.Repository.PullRequest.DefaultMergeMessageSize - for i := len(commits) - 1; i >= 0; i-- { - commit := commits[i] + for _, commit := range slices.Backward(commits) { msg := strings.TrimSpace(commit.MessageUTF8()) if msg == "" { continue diff --git a/services/repository/gitgraph/parser.go b/services/repository/gitgraph/parser.go index 859deff113..ca555218d4 100644 --- a/services/repository/gitgraph/parser.go +++ b/services/repository/gitgraph/parser.go @@ -6,6 +6,7 @@ package gitgraph import ( "bytes" "fmt" + "slices" ) // Parser represents a git graph parser. It is stateful containing the previous @@ -163,8 +164,7 @@ func (parser *Parser) ParseGlyphs(glyphs []byte) { // release unused colors parser.releaseUnusedColors() - for i := len(glyphs) - 1; i >= 0; i-- { - glyph := glyphs[i] + for i, glyph := range slices.Backward(glyphs) { switch glyph { case '|': fallthrough diff --git a/stylelint.config.js b/stylelint.config.ts similarity index 91% rename from stylelint.config.js rename to stylelint.config.ts index 0aee1a5dac..d3baa1b4d0 100644 --- a/stylelint.config.js +++ b/stylelint.config.ts @@ -1,6 +1,5 @@ -// @ts-check -// TODO: Move to .ts after https://github.com/stylelint/stylelint/issues/8893 is fixed import {fileURLToPath} from 'node:url'; +import type {Config} from 'stylelint'; const cssVarFiles = [ fileURLToPath(new URL('web_src/css/base.css', import.meta.url)), @@ -8,7 +7,6 @@ const cssVarFiles = [ fileURLToPath(new URL('web_src/css/themes/theme-gitea-dark.css', import.meta.url)), ]; -/** @type {import('stylelint').Config} */ export default { extends: 'stylelint-config-recommended', reportUnscopedDisables: true, @@ -25,18 +23,6 @@ export default { '/web_src/fomantic', ], overrides: [ - { - files: ['**/chroma/*', '**/codemirror/*', '**/console.css', 'font_i18n.css'], - rules: { - 'scale-unlimited/declaration-strict-value': null, - }, - }, - { - files: ['**/chroma/*', '**/codemirror/*'], - rules: { - 'block-no-empty': null, - }, - }, { files: ['**/*.vue'], customSyntax: 'postcss-html', @@ -139,7 +125,7 @@ export default { 'no-unknown-custom-media': null, // disabled until stylelint supports multi-file linting 'no-unknown-custom-properties': null, // disabled until stylelint supports multi-file linting 'plugin/declaration-block-no-ignored-properties': true, - 'scale-unlimited/declaration-strict-value': [['/color$/', 'font-weight'], {ignoreValues: '/^(inherit|transparent|unset|initial|currentcolor|none)$/', ignoreFunctions: true, disableFix: true, expandShorthand: true}], + 'scale-unlimited/declaration-strict-value': [['/color$/', 'fill', 'stroke', 'font-weight'], {ignoreValues: '/^(inherit|transparent|unset|initial|currentcolor|none)$/', ignoreFunctions: true, disableFix: true, expandShorthand: true}], 'selector-attribute-quotes': 'always', 'selector-no-vendor-prefix': true, 'selector-pseudo-element-colon-notation': 'double', @@ -148,4 +134,4 @@ export default { 'shorthand-property-no-redundant-values': true, 'value-no-vendor-prefix': [true, {ignoreValues: ['box', 'inline-box']}], }, -}; +} satisfies Config; diff --git a/tests/integration/git_smart_http_test.go b/tests/integration/git_smart_http_test.go index b74eb528c0..c535838c8a 100644 --- a/tests/integration/git_smart_http_test.go +++ b/tests/integration/git_smart_http_test.go @@ -9,6 +9,9 @@ import ( "net/url" "testing" + auth_model "code.gitea.io/gitea/models/auth" + repo_model "code.gitea.io/gitea/models/repo" + "code.gitea.io/gitea/models/unittest" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/test" "code.gitea.io/gitea/modules/util" @@ -20,6 +23,7 @@ import ( func TestGitSmartHTTP(t *testing.T) { onGiteaRun(t, func(t *testing.T, u *url.URL) { testGitSmartHTTP(t, u) + testGitSmartHTTPTokenScopes(t) testRenamedRepoRedirect(t) testGitArchiveRemote(t, u) }) @@ -80,6 +84,42 @@ func testGitSmartHTTP(t *testing.T, u *url.URL) { } } +func testGitSmartHTTPTokenScopes(t *testing.T) { + repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2, OwnerName: "user2", Name: "repo2"}) + require.True(t, repo.IsPrivate) + + session := loginUser(t, "user2") + badToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadNotification) + readToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadRepository) + writeToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) + publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopePublicOnly, auth_model.AccessTokenScopeReadRepository) + + t.Run("upload-pack requires read repository scope", func(t *testing.T) { + path := "/user2/repo2/info/refs?service=git-upload-pack" + + MakeRequest(t, NewRequest(t, "GET", path).AddBasicAuth(badToken, "x-oauth-basic"), http.StatusForbidden) + MakeRequest(t, NewRequest(t, "GET", path).AddTokenAuth(badToken), http.StatusForbidden) + + resp := MakeRequest(t, NewRequest(t, "GET", path).AddTokenAuth(readToken), http.StatusOK) + assert.Contains(t, resp.Body.String(), "refs/heads/master") + }) + + t.Run("receive-pack requires write repository scope", func(t *testing.T) { + path := "/user2/repo2/info/refs?service=git-receive-pack" + + MakeRequest(t, NewRequest(t, "GET", path).AddBasicAuth(readToken, "x-oauth-basic"), http.StatusForbidden) + MakeRequest(t, NewRequest(t, "GET", path).AddTokenAuth(readToken), http.StatusForbidden) + + resp := MakeRequest(t, NewRequest(t, "GET", path).AddTokenAuth(writeToken), http.StatusOK) + assert.Contains(t, resp.Body.String(), "refs/heads/master") + }) + + t.Run("public-only scope rejects private repo", func(t *testing.T) { + path := "/user2/repo2/info/refs?service=git-upload-pack" + MakeRequest(t, NewRequest(t, "GET", path).AddTokenAuth(publicOnlyToken), http.StatusForbidden) + }) +} + func testRenamedRepoRedirect(t *testing.T) { defer test.MockVariableValue(&setting.Service.RequireSignInViewStrict, true)() diff --git a/tests/test_utils.go b/tests/test_utils.go index 11e5ac2434..1b7987527a 100644 --- a/tests/test_utils.go +++ b/tests/test_utils.go @@ -36,6 +36,7 @@ func InitIntegrationTest() error { return err } + setting.Database.SlowQueryThreshold = 0 setting.LoadDBSetting() cleanupDb, err := unittest.ResetTestDatabase() if err != nil { diff --git a/tools/lint-pr-title.js b/tools/lint-pr-title.js index 2df340f186..7caeb78085 100644 --- a/tools/lint-pr-title.js +++ b/tools/lint-pr-title.js @@ -1,7 +1,7 @@ #!/usr/bin/env node import {env, exit} from 'node:process'; -const allowedTypes = 'build, ci, docs, feat, fix, perf, refactor, revert, style, test'; +const allowedTypes = 'build, chore, ci, docs, feat, fix, perf, refactor, revert, style, test'; const title = env.PR_TITLE; if (!title) { diff --git a/tools/migrate-nolyfills.ts b/tools/migrate-nolyfills.ts new file mode 100644 index 0000000000..2e5f635272 --- /dev/null +++ b/tools/migrate-nolyfills.ts @@ -0,0 +1,33 @@ +#!/usr/bin/env node +// nolyfill writes overrides to package.json#pnpm.overrides which pnpm v11 ignores. +// This moves them to pnpm-workspace.yaml until SukkaW/nolyfill#119 is fixed. +import {readFileSync, writeFileSync} from 'node:fs'; +import {exit} from 'node:process'; +import {fileURLToPath} from 'node:url'; +import {dump} from 'js-yaml'; + +const packagePath = fileURLToPath(new URL('../package.json', import.meta.url)); +const workspacePath = fileURLToPath(new URL('../pnpm-workspace.yaml', import.meta.url)); + +const packageJson: {pnpm?: {overrides?: Record}} = JSON.parse(readFileSync(packagePath, 'utf8')); +const overrides = packageJson.pnpm?.overrides; + +if (!overrides || !Object.keys(overrides).length) { + exit(0); +} + +const block = dump({overrides}, {lineWidth: -1, quotingType: "'"}); +const workspace = readFileSync(workspacePath, 'utf8'); +const overridesRegex = /^overrides:[^\n]*(?:\n(?:[ \t][^\n]*|[ \t]*(?=\n[ \t])))*\n?/m; + +if (!overridesRegex.test(workspace)) { + console.error(`No 'overrides:' block found in pnpm-workspace.yaml`); + exit(1); +} + +writeFileSync(workspacePath, workspace.replace(overridesRegex, block)); + +const pnpm = packageJson.pnpm!; +delete pnpm.overrides; +if (!Object.keys(pnpm).length) delete packageJson.pnpm; +writeFileSync(packagePath, `${JSON.stringify(packageJson, null, 2)}\n`); diff --git a/web_src/css/features/console.css b/web_src/css/features/console.css index e2d3327cfa..17f3275ace 100644 --- a/web_src/css/features/console.css +++ b/web_src/css/features/console.css @@ -1,4 +1,5 @@ /* Based on https://github.com/buildkite/terminal-to-html/blob/697ff23bd8dc48b9d23f11f259f5256dae2455f0/assets/terminal.css */ +/* stylelint-disable scale-unlimited/declaration-strict-value -- terminal ANSI palette uses literal hex values */ .console { background: var(--color-console-bg); diff --git a/web_src/css/features/gitgraph.css b/web_src/css/features/gitgraph.css index 1e19c82656..575c6bb8f6 100644 --- a/web_src/css/features/gitgraph.css +++ b/web_src/css/features/gitgraph.css @@ -84,162 +84,82 @@ fill: var(--color-secondary-dark-5); } +#git-graph-container:not(.monochrome) #rel-container .flow-group.flow-color-16-0 { + stroke: var(--color-series-16-0); + fill: var(--color-series-16-0); +} + #git-graph-container:not(.monochrome) #rel-container .flow-group.flow-color-16-1 { - stroke: #499a37; - fill: #499a37; + stroke: var(--color-series-16-1); + fill: var(--color-series-16-1); } #git-graph-container:not(.monochrome) #rel-container .flow-group.flow-color-16-2 { - stroke: #ce4751; - fill: #ce4751; + stroke: var(--color-series-16-2); + fill: var(--color-series-16-2); } #git-graph-container:not(.monochrome) #rel-container .flow-group.flow-color-16-3 { - stroke: #8f9121; - fill: #8f9121; + stroke: var(--color-series-16-3); + fill: var(--color-series-16-3); } #git-graph-container:not(.monochrome) #rel-container .flow-group.flow-color-16-4 { - stroke: #ac32a6; - fill: #ac32a6; + stroke: var(--color-series-16-4); + fill: var(--color-series-16-4); } #git-graph-container:not(.monochrome) #rel-container .flow-group.flow-color-16-5 { - stroke: #7445e9; - fill: #7445e9; + stroke: var(--color-series-16-5); + fill: var(--color-series-16-5); } #git-graph-container:not(.monochrome) #rel-container .flow-group.flow-color-16-6 { - stroke: #c67d28; - fill: #c67d28; + stroke: var(--color-series-16-6); + fill: var(--color-series-16-6); } #git-graph-container:not(.monochrome) #rel-container .flow-group.flow-color-16-7 { - stroke: #4db392; - fill: #4db392; + stroke: var(--color-series-16-7); + fill: var(--color-series-16-7); } #git-graph-container:not(.monochrome) #rel-container .flow-group.flow-color-16-8 { - stroke: #aa4d30; - fill: #aa4d30; + stroke: var(--color-series-16-8); + fill: var(--color-series-16-8); } #git-graph-container:not(.monochrome) #rel-container .flow-group.flow-color-16-9 { - stroke: #2a6f84; - fill: #2a6f84; + stroke: var(--color-series-16-9); + fill: var(--color-series-16-9); } #git-graph-container:not(.monochrome) #rel-container .flow-group.flow-color-16-10 { - stroke: #c45327; - fill: #c45327; + stroke: var(--color-series-16-10); + fill: var(--color-series-16-10); } #git-graph-container:not(.monochrome) #rel-container .flow-group.flow-color-16-11 { - stroke: #3d965c; - fill: #3d965c; + stroke: var(--color-series-16-11); + fill: var(--color-series-16-11); } #git-graph-container:not(.monochrome) #rel-container .flow-group.flow-color-16-12 { - stroke: #792a93; - fill: #792a93; + stroke: var(--color-series-16-12); + fill: var(--color-series-16-12); } #git-graph-container:not(.monochrome) #rel-container .flow-group.flow-color-16-13 { - stroke: #439d73; - fill: #439d73; + stroke: var(--color-series-16-13); + fill: var(--color-series-16-13); } #git-graph-container:not(.monochrome) #rel-container .flow-group.flow-color-16-14 { - stroke: #103aad; - fill: #103aad; + stroke: var(--color-series-16-14); + fill: var(--color-series-16-14); } #git-graph-container:not(.monochrome) #rel-container .flow-group.flow-color-16-15 { - stroke: #982e85; - fill: #982e85; -} - -#git-graph-container:not(.monochrome) #rel-container .flow-group.flow-color-16-0 { - stroke: #7db233; - fill: #7db233; -} - -#git-graph-container:not(.monochrome) #rel-container .flow-group.highlight.flow-color-16-1 { - stroke: #5ac144; - fill: #5ac144; -} - -#git-graph-container:not(.monochrome) #rel-container .flow-group.highlight.flow-color-16-2 { - stroke: #ed5a8b; - fill: #ed5a8b; -} - -#git-graph-container:not(.monochrome) #rel-container .flow-group.highlight.flow-color-16-3 { - stroke: #ced049; - fill: #ced048; -} - -#git-graph-container:not(.monochrome) #rel-container .flow-group.highlight.flow-color-16-4 { - stroke: #db61d7; - fill: #db62d6; -} - -#git-graph-container:not(.monochrome) #rel-container .flow-group.highlight.flow-color-16-5 { - stroke: #8455f9; - fill: #8455f9; -} - -#git-graph-container:not(.monochrome) #rel-container .flow-group.highlight.flow-color-16-6 { - stroke: #e6a151; - fill: #e6a151; -} - -#git-graph-container:not(.monochrome) #rel-container .flow-group.highlight.flow-color-16-7 { - stroke: #44daaa; - fill: #44daaa; -} - -#git-graph-container:not(.monochrome) #rel-container .flow-group.highlight.flow-color-16-8 { - stroke: #dd7a5c; - fill: #dd7a5c; -} - -#git-graph-container:not(.monochrome) #rel-container .flow-group.highlight.flow-color-16-9 { - stroke: #38859c; - fill: #38859c; -} - -#git-graph-container:not(.monochrome) #rel-container .flow-group.highlight.flow-color-16-10 { - stroke: #d95520; - fill: #d95520; -} - -#git-graph-container:not(.monochrome) #rel-container .flow-group.highlight.flow-color-16-11 { - stroke: #42ae68; - fill: #42ae68; -} - -#git-graph-container:not(.monochrome) #rel-container .flow-group.highlight.flow-color-16-12 { - stroke: #9126b5; - fill: #9126b5; -} - -#git-graph-container:not(.monochrome) #rel-container .flow-group.highlight.flow-color-16-13 { - stroke: #4ab080; - fill: #4ab080; -} - -#git-graph-container:not(.monochrome) #rel-container .flow-group.highlight.flow-color-16-14 { - stroke: #284fb8; - fill: #284fb8; -} - -#git-graph-container:not(.monochrome) #rel-container .flow-group.highlight.flow-color-16-15 { - stroke: #971c80; - fill: #971c80; -} - -#git-graph-container:not(.monochrome) #rel-container .flow-group.highlight.flow-color-16-0 { - stroke: #87ca28; - fill: #87ca28; + stroke: var(--color-series-16-15); + fill: var(--color-series-16-15); } diff --git a/web_src/css/font_i18n.css b/web_src/css/font_i18n.css index 0ac4c83b58..f2c820dd64 100644 --- a/web_src/css/font_i18n.css +++ b/web_src/css/font_i18n.css @@ -51,7 +51,7 @@ local("SourceHanSans-Light"), local("Yu Gothic Regular"), local("YuGothic Regular"), local("Droid Sans Japanese"), local("Meiryo"), local("MS PGothic"); - font-weight: 300; + font-weight: 300; /* stylelint-disable-line scale-unlimited/declaration-strict-value */ unicode-range: U+11??, U+2E80-4DBF, U+4E00-9FFF, U+A960-A97F, U+AC00-D7FF, U+F900-FAFF, U+FE00-FE6F, U+FF00-FFEF, U+1F2??, U+2????; } @@ -66,7 +66,7 @@ local("SourceHanSans-Regular"), local("Yu Gothic Medium"), local("YuGothic Medium"), local("Droid Sans Japanese"), local("Meiryo"), local("MS PGothic"); - font-weight: 400; + font-weight: 400; /* stylelint-disable-line scale-unlimited/declaration-strict-value */ unicode-range: U+11??, U+2E80-4DBF, U+4E00-9FFF, U+A960-A97F, U+AC00-D7FF, U+F900-FAFF, U+FE00-FE6F, U+FF00-FFEF, U+1F2??, U+2????; } @@ -81,7 +81,7 @@ local("SourceHanSans-Medium"), local("Yu Gothic Medium"), local("YuGothic Medium"), local("Droid Sans Japanese"), local("Meiryo"), local("MS PGothic"); - font-weight: 500; + font-weight: 500; /* stylelint-disable-line scale-unlimited/declaration-strict-value */ unicode-range: U+11??, U+2E80-4DBF, U+4E00-9FFF, U+A960-A97F, U+AC00-D7FF, U+F900-FAFF, U+FE00-FE6F, U+FF00-FFEF, U+1F2??, U+2????; } @@ -95,7 +95,7 @@ local("NotoSansCJKJP-Bold"), local("Source Han Sans Bold"), local("SourceHanSans-Bold"), local("Yu Gothic Bold"), local("YuGothic Bold"), local("Droid Sans Japanese"), local("Meiryo Bold"), local("MS PGothic"); - font-weight: 600; + font-weight: 600; /* stylelint-disable-line scale-unlimited/declaration-strict-value */ unicode-range: U+11??, U+2E80-4DBF, U+4E00-9FFF, U+A960-A97F, U+AC00-D7FF, U+F900-FAFF, U+FE00-FE6F, U+FF00-FFEF, U+1F2??, U+2????; } @@ -124,7 +124,7 @@ local("NotoSansCJKSC-Light"), local("HiraginoSansGB-W3"), local("Hiragino Sans GB W3"), local("Microsoft YaHei Light"), local("Heiti SC Light"), local("SimHei"); - font-weight: 300; + font-weight: 300; /* stylelint-disable-line scale-unlimited/declaration-strict-value */ unicode-range: U+11??, U+2E80-4DBF, U+4E00-9FFF, U+A960-A97F, U+AC00-D7FF, U+F900-FAFF, U+FE00-FE6F, U+FF00-FFEF, U+1F2??, U+2????; } @@ -137,7 +137,7 @@ local("NotoSansCJKSC-Regular"), local("HiraginoSansGB-W3"), local("Hiragino Sans GB W3"), local("Microsoft YaHei"), local("Heiti SC Light"), local("SimHei"); - font-weight: 400; + font-weight: 400; /* stylelint-disable-line scale-unlimited/declaration-strict-value */ unicode-range: U+11??, U+2E80-4DBF, U+4E00-9FFF, U+A960-A97F, U+AC00-D7FF, U+F900-FAFF, U+FE00-FE6F, U+FF00-FFEF, U+1F2??, U+2????; } @@ -150,7 +150,7 @@ local("NotoSansCJKSC-Medium"), local("HiraginoSansGB-W3"), local("Hiragino Sans GB W3"), local("Microsoft YaHei"), local("Heiti SC Light"), local("SimHei"); - font-weight: 500; + font-weight: 500; /* stylelint-disable-line scale-unlimited/declaration-strict-value */ unicode-range: U+11??, U+2E80-4DBF, U+4E00-9FFF, U+A960-A97F, U+AC00-D7FF, U+F900-FAFF, U+FE00-FE6F, U+FF00-FFEF, U+1F2??, U+2????; } @@ -163,7 +163,7 @@ local("NotoSansCJKSC-Bold"), local("HiraginoSansGB-W6"), local("Hiragino Sans GB W6"), local("Microsoft YaHei Bold"), local("Heiti SC Medium"), local("SimHei"); - font-weight: 600; + font-weight: 600; /* stylelint-disable-line scale-unlimited/declaration-strict-value */ unicode-range: U+11??, U+2E80-4DBF, U+4E00-9FFF, U+A960-A97F, U+AC00-D7FF, U+F900-FAFF, U+FE00-FE6F, U+FF00-FFEF, U+1F2??, U+2????; } @@ -192,7 +192,7 @@ local("NotoSansCJKTC-Light"), local("HiraginoSansTC-W3"), local("Hiragino Sans TC W3"), local("Microsoft JhengHei Light"), local("Heiti TC Light"), local("PMingLiU"); - font-weight: 300; + font-weight: 300; /* stylelint-disable-line scale-unlimited/declaration-strict-value */ unicode-range: U+11??, U+2E80-4DBF, U+4E00-9FFF, U+A960-A97F, U+AC00-D7FF, U+F900-FAFF, U+FE00-FE6F, U+FF00-FFEF, U+1F2??, U+2????; } @@ -205,7 +205,7 @@ local("NotoSansCJKTC-Regular"), local("HiraginoSansTC-W3"), local("Hiragino Sans TC W3"), local("Microsoft JhengHei"), local("Heiti TC Light"), local("PMingLiU"); - font-weight: 400; + font-weight: 400; /* stylelint-disable-line scale-unlimited/declaration-strict-value */ unicode-range: U+11??, U+2E80-4DBF, U+4E00-9FFF, U+A960-A97F, U+AC00-D7FF, U+F900-FAFF, U+FE00-FE6F, U+FF00-FFEF, U+1F2??, U+2????; } @@ -218,7 +218,7 @@ local("NotoSansCJKTC-Medium"), local("HiraginoSansTC-W3"), local("Hiragino Sans TC W3"), local("Microsoft JhengHei"), local("Heiti TC Light"), local("PMingLiU"); - font-weight: 500; + font-weight: 500; /* stylelint-disable-line scale-unlimited/declaration-strict-value */ unicode-range: U+11??, U+2E80-4DBF, U+4E00-9FFF, U+A960-A97F, U+AC00-D7FF, U+F900-FAFF, U+FE00-FE6F, U+FF00-FFEF, U+1F2??, U+2????; } @@ -231,7 +231,7 @@ local("NotoSansCJKTC-Bold"), local("HiraginoSansTC-W6"), local("Hiragino Sans TC W6"), local("Microsoft JhengHei Bold"), local("Heiti TC Medium"), local("PMingLiU"); - font-weight: 600; + font-weight: 600; /* stylelint-disable-line scale-unlimited/declaration-strict-value */ unicode-range: U+11??, U+2E80-4DBF, U+4E00-9FFF, U+A960-A97F, U+AC00-D7FF, U+F900-FAFF, U+FE00-FE6F, U+FF00-FFEF, U+1F2??, U+2????; } @@ -262,7 +262,7 @@ local("NotoSansCJKTC-Light"), local("HiraginoSansTC-W3"), local("Hiragino Sans TC W3"), local("Microsoft JhengHei Light"), local("Heiti TC Light"), local("PMingLiU_HKSCS"), local("PMingLiU"); - font-weight: 300; + font-weight: 300; /* stylelint-disable-line scale-unlimited/declaration-strict-value */ unicode-range: U+11??, U+2E80-4DBF, U+4E00-9FFF, U+A960-A97F, U+AC00-D7FF, U+F900-FAFF, U+FE00-FE6F, U+FF00-FFEF, U+1F2??, U+2????; } @@ -277,7 +277,7 @@ local("NotoSansCJKTC-Regular"), local("HiraginoSansTC-W3"), local("Hiragino Sans TC W3"), local("Microsoft JhengHei"), local("Heiti TC Light"), local("PMingLiU_HKSCS"), local("PMingLiU"); - font-weight: 400; + font-weight: 400; /* stylelint-disable-line scale-unlimited/declaration-strict-value */ unicode-range: U+11??, U+2E80-4DBF, U+4E00-9FFF, U+A960-A97F, U+AC00-D7FF, U+F900-FAFF, U+FE00-FE6F, U+FF00-FFEF, U+1F2??, U+2????; } @@ -292,7 +292,7 @@ local("NotoSansCJKTC-Medium"), local("HiraginoSansTC-W3"), local("Hiragino Sans TC W3"), local("Microsoft JhengHei"), local("Heiti TC Light"), local("PMingLiU_HKSCS"), local("PMingLiU"); - font-weight: 500; + font-weight: 500; /* stylelint-disable-line scale-unlimited/declaration-strict-value */ unicode-range: U+11??, U+2E80-4DBF, U+4E00-9FFF, U+A960-A97F, U+AC00-D7FF, U+F900-FAFF, U+FE00-FE6F, U+FF00-FFEF, U+1F2??, U+2????; } @@ -307,7 +307,7 @@ local("NotoSansCJKTC-Bold"), local("HiraginoSansTC-W6"), local("Hiragino Sans TC W6"), local("Microsoft JhengHei Bold"), local("Heiti TC Medium"), local("PMingLiU_HKSCS"), local("PMingLiU"); - font-weight: 600; + font-weight: 600; /* stylelint-disable-line scale-unlimited/declaration-strict-value */ unicode-range: U+11??, U+2E80-4DBF, U+4E00-9FFF, U+A960-A97F, U+AC00-D7FF, U+F900-FAFF, U+FE00-FE6F, U+FF00-FFEF, U+1F2??, U+2????; } @@ -335,7 +335,7 @@ local("SourceHanSansK-Light"), local("Noto Sans CJK KR Light"), local("NotoSansCJKKR-Light"), local("NanumBarunGothic Light"), local("Malgun Gothic Semilight"), local("Nanum Gothic"), local("Dotum"); - font-weight: 300; + font-weight: 300; /* stylelint-disable-line scale-unlimited/declaration-strict-value */ unicode-range: U+11??, U+2E80-4DBF, U+4E00-9FFF, U+A960-A97F, U+AC00-D7FF, U+F900-FAFF, U+FE00-FE6F, U+FF00-FFEF, U+1F2??, U+2????; } @@ -347,7 +347,7 @@ local("SourceHanSansK-Regular"), local("Noto Sans CJK KR Regular"), local("NotoSansCJKKR-Regular"), local("NanumBarunGothic"), local("Malgun Gothic"), local("Nanum Gothic"), local("Dotum"); - font-weight: 400; + font-weight: 400; /* stylelint-disable-line scale-unlimited/declaration-strict-value */ unicode-range: U+11??, U+2E80-4DBF, U+4E00-9FFF, U+A960-A97F, U+AC00-D7FF, U+F900-FAFF, U+FE00-FE6F, U+FF00-FFEF, U+1F2??, U+2????; } @@ -359,7 +359,7 @@ local("SourceHanSansK-Medium"), local("Noto Sans CJK KR Medium"), local("NotoSansCJKKR-Medium"), local("NanumBarunGothic"), local("Malgun Gothic"), local("Nanum Gothic"), local("Dotum"); - font-weight: 500; + font-weight: 500; /* stylelint-disable-line scale-unlimited/declaration-strict-value */ unicode-range: U+11??, U+2E80-4DBF, U+4E00-9FFF, U+A960-A97F, U+AC00-D7FF, U+F900-FAFF, U+FE00-FE6F, U+FF00-FFEF, U+1F2??, U+2????; } @@ -371,7 +371,7 @@ local("SourceHanSansK-Bold"), local("Noto Sans CJK KR Bold"), local("NotoSansCJKKR-Bold"), local("NanumBarunGothic Bold"), local("Malgun Gothic Bold"), local("Nanum Gothic Bold"), local("Dotum"); - font-weight: 600; + font-weight: 600; /* stylelint-disable-line scale-unlimited/declaration-strict-value */ unicode-range: U+11??, U+2E80-4DBF, U+4E00-9FFF, U+A960-A97F, U+AC00-D7FF, U+F900-FAFF, U+FE00-FE6F, U+FF00-FFEF, U+1F2??, U+2????; } diff --git a/web_src/css/themes/theme-gitea-dark.css b/web_src/css/themes/theme-gitea-dark.css index 188a30cae0..f07d94cc63 100644 --- a/web_src/css/themes/theme-gitea-dark.css +++ b/web_src/css/themes/theme-gitea-dark.css @@ -141,6 +141,23 @@ gitea-theme-meta-info { --color-ansi-bright-magenta: #d74397; --color-ansi-bright-cyan: #00b6ad; --color-ansi-bright-white: var(--color-console-fg); + /* 16-color series */ + --color-series-16-0: #7db233; + --color-series-16-1: #499a37; + --color-series-16-2: #ce4751; + --color-series-16-3: #8f9121; + --color-series-16-4: #ac32a6; + --color-series-16-5: #7445e9; + --color-series-16-6: #c67d28; + --color-series-16-7: #4db392; + --color-series-16-8: #aa4d30; + --color-series-16-9: #2a6f84; + --color-series-16-10: #c45327; + --color-series-16-11: #3d965c; + --color-series-16-12: #792a93; + --color-series-16-13: #439d73; + --color-series-16-14: #103aad; + --color-series-16-15: #982e85; /* other colors */ --color-grey: #3d3f44; --color-grey-light: #898d96; diff --git a/web_src/css/themes/theme-gitea-light.css b/web_src/css/themes/theme-gitea-light.css index fb9b8979da..1bd7dd0848 100644 --- a/web_src/css/themes/theme-gitea-light.css +++ b/web_src/css/themes/theme-gitea-light.css @@ -141,6 +141,23 @@ gitea-theme-meta-info { --color-ansi-bright-magenta: #d74397; --color-ansi-bright-cyan: #00b6ad; --color-ansi-bright-white: var(--color-console-fg); + /* 16-color series */ + --color-series-16-0: #7db233; + --color-series-16-1: #499a37; + --color-series-16-2: #ce4751; + --color-series-16-3: #8f9121; + --color-series-16-4: #ac32a6; + --color-series-16-5: #7445e9; + --color-series-16-6: #c67d28; + --color-series-16-7: #4db392; + --color-series-16-8: #aa4d30; + --color-series-16-9: #2a6f84; + --color-series-16-10: #c45327; + --color-series-16-11: #3d965c; + --color-series-16-12: #792a93; + --color-series-16-13: #439d73; + --color-series-16-14: #103aad; + --color-series-16-15: #982e85; /* other colors */ --color-grey: #697077; --color-grey-light: #7c838a; diff --git a/web_src/js/vitest.setup.ts b/web_src/js/vitest.setup.ts index 190196cabd..a6ec019ff8 100644 --- a/web_src/js/vitest.setup.ts +++ b/web_src/js/vitest.setup.ts @@ -16,5 +16,3 @@ window.config = { }; window.testModules = {}; - -export {}; // mark as module for top-level await