Unverified Commit 12e2205c authored by memoclaw's avatar memoclaw Committed by GitHub

chore(backend): update Go toolchain and dependencies (#5730)

parent 6f5f0d94
......@@ -15,7 +15,7 @@ concurrency:
cancel-in-progress: true
env:
GO_VERSION: "1.25.7"
GO_VERSION: "1.26.1"
jobs:
static-checks:
......@@ -40,7 +40,7 @@ jobs:
- name: Run golangci-lint
uses: golangci/golangci-lint-action@v9
with:
version: v2.4.0
version: v2.11.3
args: --timeout=3m
tests:
......
......@@ -9,7 +9,7 @@ on:
# Environment variables for build configuration
env:
GO_VERSION: "1.25.7"
GO_VERSION: "1.26.1"
NODE_VERSION: "24"
PNPM_VERSION: "10"
ARTIFACT_RETENTION_DAYS: 60
......
......@@ -2,7 +2,7 @@
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Self-hosted note-taking tool. Go 1.25 backend (Echo v5, Connect RPC + gRPC-Gateway), React 18 + TypeScript 5.9 + Vite 7 frontend, Protocol Buffers API, SQLite/MySQL/PostgreSQL.
Self-hosted note-taking tool. Go 1.26 backend (Echo v5, Connect RPC + gRPC-Gateway), React 18 + TypeScript 5.9 + Vite 7 frontend, Protocol Buffers API, SQLite/MySQL/PostgreSQL.
## Commands
......@@ -96,7 +96,7 @@ web/src/
## CI/CD
- **backend-tests.yml:** Go 1.25.7, golangci-lint v2.4.0, tests parallelized by group (store, server, plugin, other)
- **backend-tests.yml:** Go 1.26.1, golangci-lint v2.4.0, tests parallelized by group (store, server, plugin, other)
- **frontend-tests.yml:** Node 24, pnpm 10, lint + build
- **proto-linter.yml:** buf lint + format check
- **Docker:** Multi-stage (`scripts/Dockerfile`), Alpine 3.21, non-root user, port 5230, multi-arch (amd64/arm64/arm/v7)
This diff is collapsed.
This diff is collapsed.
package version
import (
"sort"
"slices"
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/mod/semver"
)
func TestIsVersionGreaterOrEqualThan(t *testing.T) {
......@@ -97,7 +98,9 @@ func TestSortVersion(t *testing.T) {
},
}
for _, test := range tests {
sort.Sort(SortVersion(test.versionList))
slices.SortFunc(test.versionList, func(a, b string) int {
return semver.Compare("v"+a, "v"+b)
})
assert.Equal(t, test.versionList, test.want)
}
}
......@@ -10,6 +10,20 @@ import (
"time"
)
func waitFor(t *testing.T, timeout time.Duration, fn func() bool) {
t.Helper()
deadline := time.Now().Add(timeout)
for time.Now().Before(deadline) {
if fn() {
return
}
time.Sleep(time.Millisecond)
}
t.Fatal("condition not met before timeout")
}
func appendingJob(slice *[]int, value int) Job {
var m sync.Mutex
return FuncJob(func() {
......@@ -104,7 +118,8 @@ func TestChainDelayIfStillRunning(t *testing.T) {
var j countJob
wrappedJob := NewChain(DelayIfStillRunning(DiscardLogger)).Then(&j)
go wrappedJob.Run()
time.Sleep(2 * time.Millisecond) // Give the job 2ms to complete.
waitFor(t, 100*time.Millisecond, func() bool { return j.Done() == 1 })
if c := j.Done(); c != 1 {
t.Errorf("expected job run once, immediately, got %d", c)
}
......@@ -118,7 +133,8 @@ func TestChainDelayIfStillRunning(t *testing.T) {
time.Sleep(time.Millisecond)
go wrappedJob.Run()
}()
time.Sleep(3 * time.Millisecond) // Give both jobs 3ms to complete.
waitFor(t, 100*time.Millisecond, func() bool { return j.Done() == 2 })
if c := j.Done(); c != 2 {
t.Errorf("expected job run twice, immediately, got %d", c)
}
......@@ -134,16 +150,13 @@ func TestChainDelayIfStillRunning(t *testing.T) {
go wrappedJob.Run()
}()
// After 5ms, the first job is still in progress, and the second job was
// run but should be waiting for it to finish.
time.Sleep(5 * time.Millisecond)
waitFor(t, 100*time.Millisecond, func() bool { return j.Started() == 1 })
started, done := j.Started(), j.Done()
if started != 1 || done != 0 {
if done != 0 {
t.Error("expected first job started, but not finished, got", started, done)
}
// Verify that the second job completes.
time.Sleep(25 * time.Millisecond)
waitFor(t, 200*time.Millisecond, func() bool { return j.Done() == 2 })
started, done = j.Started(), j.Done()
if started != 2 || done != 2 {
t.Error("expected both jobs done, got", started, done)
......
......@@ -2,7 +2,7 @@ package cron
import (
"context"
"sort"
"slices"
"sync"
"time"
)
......@@ -74,25 +74,6 @@ type Entry struct {
// Valid returns true if this is not the zero entry.
func (e Entry) Valid() bool { return e.ID != 0 }
// byTime is a wrapper for sorting the entry array by time
// (with zero time at the end).
type byTime []*Entry
func (s byTime) Len() int { return len(s) }
func (s byTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s byTime) Less(i, j int) bool {
// Two zero times should return false.
// Otherwise, zero is "greater" than any other time.
// (To sort it at the end of the list.)
if s[i].Next.IsZero() {
return false
}
if s[j].Next.IsZero() {
return true
}
return s[i].Next.Before(s[j].Next)
}
// New returns a new Cron job runner, modified by the given options.
//
// Available Settings
......@@ -248,7 +229,22 @@ func (c *Cron) runScheduler() {
for {
// Determine the next entry to run.
sort.Sort(byTime(c.entries))
slices.SortFunc(c.entries, func(a, b *Entry) int {
switch {
case a.Next.IsZero() && b.Next.IsZero():
return 0
case a.Next.IsZero():
return 1
case b.Next.IsZero():
return -1
case a.Next.Before(b.Next):
return -1
case b.Next.Before(a.Next):
return 1
default:
return 0
}
})
var timer *time.Timer
if len(c.entries) == 0 || c.entries[0].Next.IsZero() {
......
......@@ -38,29 +38,29 @@ func (m *Message) Format(fromEmail, fromName string) string {
// From header
if fromName != "" {
sb.WriteString(fmt.Sprintf("From: %s <%s>\r\n", fromName, fromEmail))
fmt.Fprintf(&sb, "From: %s <%s>\r\n", fromName, fromEmail)
} else {
sb.WriteString(fmt.Sprintf("From: %s\r\n", fromEmail))
fmt.Fprintf(&sb, "From: %s\r\n", fromEmail)
}
// To header
sb.WriteString(fmt.Sprintf("To: %s\r\n", strings.Join(m.To, ", ")))
fmt.Fprintf(&sb, "To: %s\r\n", strings.Join(m.To, ", "))
// Cc header (optional)
if len(m.Cc) > 0 {
sb.WriteString(fmt.Sprintf("Cc: %s\r\n", strings.Join(m.Cc, ", ")))
fmt.Fprintf(&sb, "Cc: %s\r\n", strings.Join(m.Cc, ", "))
}
// Reply-To header (optional)
if m.ReplyTo != "" {
sb.WriteString(fmt.Sprintf("Reply-To: %s\r\n", m.ReplyTo))
fmt.Fprintf(&sb, "Reply-To: %s\r\n", m.ReplyTo)
}
// Subject header
sb.WriteString(fmt.Sprintf("Subject: %s\r\n", m.Subject))
fmt.Fprintf(&sb, "Subject: %s\r\n", m.Subject)
// Date header (RFC 5322 format)
sb.WriteString(fmt.Sprintf("Date: %s\r\n", time.Now().Format(time.RFC1123Z)))
fmt.Fprintf(&sb, "Date: %s\r\n", time.Now().Format(time.RFC1123Z))
// MIME headers
sb.WriteString("MIME-Version: 1.0\r\n")
......
......@@ -176,7 +176,7 @@ func rewriteNumericLogicalOperand(expr, op string) string {
}
if i > signStart {
numLiteral := expr[signStart:i]
builder.WriteString(fmt.Sprintf("(%s != 0)", numLiteral))
fmt.Fprintf(&builder, "(%s != 0)", numLiteral)
} else {
builder.WriteString(expr[signStart:i])
}
......
......@@ -287,6 +287,8 @@ func (s *service) GenerateSnippet(content []byte, maxLength int) (string, error)
case *mast.TagNode:
buf.WriteByte('#')
buf.Write(node.Tag)
default:
// Ignore other node types.
}
// Stop walking if we've exceeded double the max length
......
......@@ -8,7 +8,6 @@ import (
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/feature/s3/manager"
"github.com/aws/aws-sdk-go-v2/service/s3"
"github.com/pkg/errors"
......@@ -43,23 +42,16 @@ func NewClient(ctx context.Context, s3Config *storepb.StorageS3Config) (*Client,
// UploadObject uploads an object to S3.
func (c *Client) UploadObject(ctx context.Context, key string, fileType string, content io.Reader) (string, error) {
uploader := manager.NewUploader(c.Client)
putInput := s3.PutObjectInput{
Bucket: c.Bucket,
Key: aws.String(key),
ContentType: aws.String(fileType),
Body: content,
}
result, err := uploader.Upload(ctx, &putInput)
if err != nil {
if _, err := c.Client.PutObject(ctx, &putInput); err != nil {
return "", err
}
resultKey := result.Key
if resultKey == nil || *resultKey == "" {
return "", errors.New("failed to get file key")
}
return *resultKey, nil
return key, nil
}
// PresignGetObject presigns an object in S3.
......@@ -81,16 +73,19 @@ func (c *Client) PresignGetObject(ctx context.Context, key string) (string, erro
// GetObject retrieves an object from S3.
func (c *Client) GetObject(ctx context.Context, key string) ([]byte, error) {
downloader := manager.NewDownloader(c.Client)
buffer := manager.NewWriteAtBuffer([]byte{})
_, err := downloader.Download(ctx, buffer, &s3.GetObjectInput{
output, err := c.Client.GetObject(ctx, &s3.GetObjectInput{
Bucket: c.Bucket,
Key: aws.String(key),
})
if err != nil {
return nil, errors.Wrap(err, "failed to download object")
}
return buffer.Bytes(), nil
defer output.Body.Close()
data, err := io.ReadAll(output.Body)
if err != nil {
return nil, errors.Wrap(err, "failed to read object body")
}
return data, nil
}
// GetObjectStream retrieves an object from S3 as a stream.
......
......@@ -2,5 +2,5 @@
version: v2
deps:
- name: buf.build/googleapis/googleapis
commit: 61b203b9a9164be9a834f58c37be6f62
digest: b5:7811a98b35bd2e4ae5c3ac73c8b3d9ae429f3a790da15de188dc98fc2b77d6bb10e45711f14903af9553fa9821dff256054f2e4b7795789265bc476bec2f088c
commit: 004180b77378443887d3b55cabc00384
digest: b5:e8f475fe3330f31f5fd86ac689093bcd274e19611a09db91f41d637cb9197881ce89882b94d13a58738e53c91c6e4bae7dc1feba85f590164c975a89e25115dc
FROM --platform=$BUILDPLATFORM golang:1.25.7-alpine AS backend
FROM --platform=$BUILDPLATFORM golang:1.26.1-alpine AS backend
WORKDIR /backend-build
# Install build dependencies
......
......@@ -121,6 +121,8 @@ func convertInstanceSettingFromStore(setting *storepb.InstanceSetting) *v1pb.Ins
instanceSetting.Value = &v1pb.InstanceSetting_MemoRelatedSetting_{
MemoRelatedSetting: convertInstanceMemoRelatedSettingFromStore(setting.GetMemoRelatedSetting()),
}
default:
// Leave Value unset for unsupported setting variants.
}
return instanceSetting
}
......
......@@ -85,11 +85,11 @@ func (*MCPService) handleCapturePrompt(_ context.Context, req mcp.GetPromptReque
var sb strings.Builder
sb.WriteString("Save the following as a new memo using the create_memo tool.\n\n")
sb.WriteString(fmt.Sprintf("Visibility: %s\n\n", visibility))
fmt.Fprintf(&sb, "Visibility: %s\n\n", visibility)
sb.WriteString("Content:\n")
sb.WriteString(content)
if tags != "" {
sb.WriteString(fmt.Sprintf("\n\nAppend these tags inline using #tag syntax: %s", tags))
fmt.Fprintf(&sb, "\n\nAppend these tags inline using #tag syntax: %s", tags)
}
sb.WriteString("\n\nAfter creating the memo, confirm by showing the memo resource name (e.g. memo://memos/<uid>) so it can be referenced later.")
......
......@@ -3,7 +3,7 @@ package mcp
import (
"context"
"fmt"
"sort"
"slices"
"github.com/mark3labs/mcp-go/mcp"
mcpserver "github.com/mark3labs/mcp-go/server"
......@@ -53,11 +53,21 @@ func (s *MCPService) handleListTags(ctx context.Context, _ mcp.CallToolRequest)
for tag, count := range counts {
entries = append(entries, tagEntry{Tag: tag, Count: count})
}
sort.Slice(entries, func(i, j int) bool {
if entries[i].Count != entries[j].Count {
return entries[i].Count > entries[j].Count
slices.SortFunc(entries, func(a, b tagEntry) int {
if a.Count != b.Count {
if a.Count > b.Count {
return -1
}
return 1
}
switch {
case a.Tag < b.Tag:
return -1
case a.Tag > b.Tag:
return 1
default:
return 0
}
return entries[i].Tag < entries[j].Tag
})
out, err := marshalJSON(entries)
......
......@@ -8,7 +8,7 @@ import (
"io/fs"
"log/slog"
"path/filepath"
"sort"
"slices"
"strconv"
"strings"
......@@ -142,7 +142,7 @@ func (s *Store) applyMigrations(ctx context.Context, currentSchemaVersion, targe
if err != nil {
return errors.Wrap(err, "failed to read migration files")
}
sort.Strings(filePaths)
slices.Sort(filePaths)
// Start a transaction to apply migrations atomically
tx, err := s.driver.GetDB().Begin()
......@@ -275,7 +275,7 @@ func (s *Store) seed(ctx context.Context) error {
}
// Sort seed files by name. This is important to ensure that seed files are applied in order.
sort.Strings(filenames)
slices.Sort(filenames)
// Start a transaction to apply the seed files.
tx, err := s.driver.GetDB().Begin()
if err != nil {
......@@ -303,7 +303,7 @@ func (s *Store) GetCurrentSchemaVersion() (string, error) {
return "", errors.Wrap(err, "failed to read migration files")
}
sort.Strings(filePaths)
slices.Sort(filePaths)
if len(filePaths) == 0 {
return fmt.Sprintf("%s.0", minorVersion), nil
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment