REPOSITORY / ScuroNeko/Laniakea

Pull Requests

PULL REQUESTS REPOSITORY

v1.0.0 #9

Merged
ScuroNeko merged 101 commits from dev into main 2026-05-20 13:43:34 +03:00
18 changed files with 283 additions and 66 deletions
Showing only changes of commit 7776acaf12 - Show all commits
+14
View File
@@ -0,0 +1,14 @@
version: "2"
run:
timeout: 5m
linters:
disable-all: true
enable:
- errcheck
- govet
- ineffassign
- staticcheck
- unused
issues:
max-issues-per-linter: 0
max-same-issues: 0
+41
View File
@@ -0,0 +1,41 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-merge-conflict
- id: check-yaml
- id: check-json
- id: mixed-line-ending
args: ["--fix=lf"]
- repo: local
hooks:
- id: gofmt
name: gofmt
entry: gofmt -w
language: system
types: [go]
- id: go-vet
name: go vet
entry: go vet ./...
language: system
pass_filenames: false
types: [go]
- id: golangci-lint
name: golangci-lint
entry: golangci-lint run
language: system
pass_filenames: false
types: [go]
- id: go-test
name: go test
entry: go test ./...
language: system
pass_filenames: false
stages: [pre-push]
types: [go]
-15
View File
@@ -1,15 +0,0 @@
# Проверка наличия golangci-lint
GO_LINT := $(shell command -v golangci-lint 2>/dev/null)
# Цель: запуск всех проверок кода
check:
@echo "🔍 Running code checks..."
@go mod tidy -v
@go vet ./...
@if [ -n "$(GO_LINT)" ]; then \
echo "✅ golangci-lint found, running..." && \
golangci-lint run --timeout=5m --verbose; \
else \
echo "⚠️ golangci-lint not installed. Install with: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.57.2"; \
fi
@go test -race -v ./... 2>/dev/null || echo "⚠️ Tests skipped or failed (run manually with 'go test -race ./...')"
+71 -22
View File
@@ -7,7 +7,6 @@ import (
"sort" "sort"
"strings" "strings"
"sync" "sync"
"time"
"git.nix13.pw/scuroneko/extypes" "git.nix13.pw/scuroneko/extypes"
"git.nix13.pw/scuroneko/laniakea/tgapi" "git.nix13.pw/scuroneko/laniakea/tgapi"
@@ -163,9 +162,7 @@ func NewBot[T any](opts *BotOpts) *Bot[T] {
// Fetch bot info to validate token and get username // Fetch bot info to validate token and get username
u, err := api.GetMe() u, err := api.GetMe()
if err != nil { if err != nil {
closeCtx, cancel := context.WithTimeout(context.Background(), time.Second*10) _ = bot.Close()
defer cancel()
_ = bot.Close(closeCtx)
bot.logger.Fatal(err) bot.logger.Fatal(err)
} }
bot.username = Val(u.Username, "") bot.username = Val(u.Username, "")
@@ -179,12 +176,9 @@ func NewBot[T any](opts *BotOpts) *Bot[T] {
// Close gracefully shuts down bot-owned resources. // Close gracefully shuts down bot-owned resources.
// //
// The provided context is used to close the API client's long-polling request.
// Upload shutdown is not context-aware and still waits for pending uploads.
//
// Close shuts down, in order: // Close shuts down, in order:
// - Registered plugins via Plugin.Close
// - Uploader (waits for pending uploads) // - Uploader (waits for pending uploads)
// - API client long-poll request via ctx
// - API client internals // - API client internals
// - RequestLogger (if enabled) // - RequestLogger (if enabled)
// - Main logger // - Main logger
@@ -193,18 +187,19 @@ func NewBot[T any](opts *BotOpts) *Bot[T] {
// for invoking Close after RunWithContext returns to release these resources. // for invoking Close after RunWithContext returns to release these resources.
// //
// Close returns a joined error containing all shutdown failures, if any. // Close returns a joined error containing all shutdown failures, if any.
func (bot *Bot[T]) Close(ctx context.Context) error { func (bot *Bot[T]) Close() error {
var e []error var e []error
for _, p := range bot.plugins {
if err := p.Close(); err != nil {
e = append(e, err)
}
}
if err := bot.uploader.Close(); err != nil { if err := bot.uploader.Close(); err != nil {
bot.logger.Errorln(err) bot.logger.Errorln(err)
e = append(e, err) e = append(e, err)
} }
if _, err := bot.api.CloseWithContext(ctx); err != nil { if err := bot.api.Close(); err != nil {
bot.logger.Errorln(err)
e = append(e, err)
}
if err := bot.api.CloseApi(); err != nil {
bot.logger.Errorln(err) bot.logger.Errorln(err)
e = append(e, err) e = append(e, err)
} }
@@ -220,6 +215,17 @@ func (bot *Bot[T]) Close(ctx context.Context) error {
return errors.Join(e...) return errors.Join(e...)
} }
// CloseRemote sends Telegram Bot API "close" request for the current bot
// instance using ctx for cancellation and deadlines.
//
// This is separate from Bot.Close(), which only releases local resources.
func (bot *Bot[T]) CloseRemote(ctx context.Context) error {
if _, err := bot.api.CloseRemoteWithContext(ctx); err != nil {
return err
}
return nil
}
// initLoggers configures the main and optional request loggers. // initLoggers configures the main and optional request loggers.
// //
// Uses DEBUG flag to set log level (DEBUG if true, FATAL otherwise). // Uses DEBUG flag to set log level (DEBUG if true, FATAL otherwise).
@@ -231,27 +237,25 @@ func (bot *Bot[T]) initLoggers(opts *BotOpts) {
level = slog.DEBUG level = slog.DEBUG
} }
bot.logger = slog.CreateLogger().Level(level).Prefix("BOT") bot.logger = utils.CreateLogger("BOT", level)
bot.logger.AddWriter(bot.logger.CreateJsonStdoutWriter())
if opts.WriteToFile { if opts.WriteToFile {
path := fmt.Sprintf("%s/main.log", strings.TrimRight(opts.LoggerBasePath, "/")) path := fmt.Sprintf("%s/main.log", strings.TrimRight(opts.LoggerBasePath, "/"))
fileWriter, err := bot.logger.CreateTextFileWriter(path) logger, err := utils.CreateFileLogger("BOT", level, path)
if err != nil { if err != nil {
bot.logger.Fatal(err) bot.logger.Fatal(err)
} }
bot.logger.AddWriter(fileWriter) bot.logger = logger
} }
if opts.UseRequestLogger { if opts.UseRequestLogger {
bot.RequestLogger = slog.CreateLogger().Level(level).Prefix("REQUESTS") bot.RequestLogger = utils.CreateLogger("REQUESTS", level)
bot.RequestLogger.AddWriter(bot.RequestLogger.CreateJsonStdoutWriter())
if opts.WriteToFile { if opts.WriteToFile {
path := fmt.Sprintf("%s/requests.log", strings.TrimRight(opts.LoggerBasePath, "/")) path := fmt.Sprintf("%s/requests.log", strings.TrimRight(opts.LoggerBasePath, "/"))
fileWriter, err := bot.RequestLogger.CreateTextFileWriter(path) logger, err := utils.CreateFileLogger("REQUESTS", level, path)
if err != nil { if err != nil {
bot.logger.Fatal(err) bot.logger.Fatal(err)
} }
bot.RequestLogger.AddWriter(fileWriter) bot.RequestLogger = logger
} }
} }
} }
@@ -280,6 +284,16 @@ func (bot *Bot[T]) GetLogger() *slog.Logger { return bot.logger }
// Returns nil if not set via DatabaseContext(). // Returns nil if not set via DatabaseContext().
func (bot *Bot[T]) GetDBContext() *T { return bot.dbContext } func (bot *Bot[T]) GetDBContext() *T { return bot.dbContext }
// GetLoggerLevel returns the effective log level derived from the bot's debug
// flag.
func (bot *Bot[T]) GetLoggerLevel() slog.LogLevel {
level := slog.FATAL
if bot.debug {
level = slog.DEBUG
}
return level
}
// L10n translates a key in the given language. // L10n translates a key in the given language.
// Returns empty string if translation not found. // Returns empty string if translation not found.
func (bot *Bot[T]) L10n(lang, key string) string { func (bot *Bot[T]) L10n(lang, key string) string {
@@ -341,13 +355,38 @@ func (bot *Bot[T]) ErrorTemplate(s string) *Bot[T] {
// Debug enables or disables debug logging. // Debug enables or disables debug logging.
func (bot *Bot[T]) Debug(debug bool) *Bot[T] { func (bot *Bot[T]) Debug(debug bool) *Bot[T] {
bot.debug = debug bot.debug = debug
level := slog.FATAL
if debug {
level = slog.DEBUG
}
bot.logger.Level(level)
if bot.RequestLogger != nil {
bot.RequestLogger.Level(level)
}
for _, p := range bot.plugins {
if p.logger == nil {
continue
}
p.logger.Level(level)
}
return bot return bot
} }
// AddPlugins registers one or more plugins. // AddPlugins registers one or more plugins.
// Plugins are executed in registration order unless filtered by middleware. // Plugins are executed in registration order unless filtered by middleware.
//
// Registration is a commit point for plugin configuration. The Bot stores
// plugin metadata internally, so plugins must be fully configured before they
// are passed here. Post-registration mutation through the original *Plugin is
// not a supported API, even if some changes appear to work due to shared maps.
func (bot *Bot[T]) AddPlugins(plugin ...*Plugin[T]) *Bot[T] { func (bot *Bot[T]) AddPlugins(plugin ...*Plugin[T]) *Bot[T] {
level := bot.GetLoggerLevel()
for _, p := range plugin { for _, p := range plugin {
if p.logger == nil {
logger := utils.CreateLogger(p.name, level)
p.SetLogger(logger)
}
bot.plugins = append(bot.plugins, *p) bot.plugins = append(bot.plugins, *p)
bot.logger.Debugln(fmt.Sprintf("plugins with name \"%s\" registered", p.name)) bot.logger.Debugln(fmt.Sprintf("plugins with name \"%s\" registered", p.name))
} }
@@ -443,6 +482,11 @@ func (bot *Bot[T]) AddL10n(l *L10n) *Bot[T] {
// - Main bot logger // - Main bot logger
// - Request logger (if enabled) // - Request logger (if enabled)
// - API and Uploader loggers // - API and Uploader loggers
// - Already registered plugin loggers
//
// Call this after AddPlugins if plugin loggers should also receive the writer.
// Plugins registered later do not automatically inherit previously added
// database writers; call AddDatabaseLoggerWriter again after adding them.
// //
// Example: // Example:
// //
@@ -458,6 +502,11 @@ func (bot *Bot[T]) AddDatabaseLoggerWriter(writer DbLogger[T]) *Bot[T] {
for _, l := range bot.extraLoggers { for _, l := range bot.extraLoggers {
l.AddWriter(w) l.AddWriter(w)
} }
for _, p := range bot.plugins {
if p.logger != nil {
p.logger.AddWriter(w)
}
}
return bot return bot
} }
+2 -2
View File
@@ -37,8 +37,8 @@ func TestAutoGenerateCommandsChecksLimitBeforeDelete(t *testing.T) {
SetHTTPClient(client), SetHTTPClient(client),
) )
defer func() { defer func() {
if err := api.CloseApi(); err != nil { if err := api.Close(); err != nil {
t.Fatalf("CloseApi returned error: %v", err) t.Fatalf("Close returned error: %v", err)
} }
}() }()
+9
View File
@@ -97,6 +97,11 @@ func (bot *Bot[T]) handleMessage(update *tgapi.Update, ctx *MsgContext) {
if !plugin.executeMiddlewares(ctx, bot.dbContext) { if !plugin.executeMiddlewares(ctx, bot.dbContext) {
return return
} }
ctx.Logger = plugin.logger
if ctx.Logger == nil {
ctx.Logger = ctx.botLogger
}
plugin.executeCmd(cmd, ctx, bot.dbContext) plugin.executeCmd(cmd, ctx, bot.dbContext)
return return
} }
@@ -131,6 +136,10 @@ func (bot *Bot[T]) handleCallback(update *tgapi.Update, ctx *MsgContext) {
if !plugin.executeMiddlewares(ctx, bot.dbContext) { if !plugin.executeMiddlewares(ctx, bot.dbContext) {
return return
} }
ctx.Logger = plugin.logger
if ctx.Logger == nil {
ctx.Logger = ctx.botLogger
}
plugin.executePayload(data.Command, ctx, bot.dbContext) plugin.executePayload(data.Command, ctx, bot.dbContext)
return return
} }
+4
View File
@@ -19,6 +19,10 @@ type MsgContext struct {
Msg *tgapi.Message Msg *tgapi.Message
From *tgapi.User From *tgapi.User
// Logger is the logger assigned by the matched plugin for the current handler call.
// It may fall back to the bot logger when the plugin has no dedicated logger.
Logger *slog.Logger
InlineMsgId string InlineMsgId string
CallbackMsgId int CallbackMsgId int
CallbackQueryId string CallbackQueryId string
+2 -2
View File
@@ -37,8 +37,8 @@ func TestAnswerPhotoIncludesDirectMessagesTopicID(t *testing.T) {
SetHTTPClient(client), SetHTTPClient(client),
) )
defer func() { defer func() {
if err := api.CloseApi(); err != nil { if err := api.Close(); err != nil {
t.Fatalf("CloseApi returned error: %v", err) t.Fatalf("Close returned error: %v", err)
} }
}() }()
+59 -2
View File
@@ -5,6 +5,7 @@ import (
"regexp" "regexp"
"git.nix13.pw/scuroneko/extypes" "git.nix13.pw/scuroneko/extypes"
"git.nix13.pw/scuroneko/slog"
) )
// CommandValueType defines the expected type of a command argument. // CommandValueType defines the expected type of a command argument.
@@ -151,19 +152,30 @@ func (c *Command[T]) validateArgs(args []string) error {
// Plugin represents a collection of commands and payloads (e.g., callback handlers), // Plugin represents a collection of commands and payloads (e.g., callback handlers),
// with shared middleware and configuration. // with shared middleware and configuration.
//
// A Plugin is intended to be fully configured before it is passed to Bot.AddPlugins.
// After registration, treat the plugin as committed and do not mutate it further.
// Post-registration changes through the original *Plugin are not a supported API.
type Plugin[T DbContext] struct { type Plugin[T DbContext] struct {
name string // Name of the plugin (e.g., "admin", "user") name string // Name of the plugin (e.g., "admin", "user")
commands map[string]*Command[T] // Registered commands (triggered by message) commands map[string]*Command[T] // Registered commands (triggered by message)
payloads map[string]*Command[T] // Registered payloads (triggered by callback data) payloads map[string]*Command[T] // Registered payloads (triggered by callback data)
middlewares extypes.Slice[Middleware[T]] // Shared middlewares for all commands/payloads middlewares extypes.Slice[Middleware[T]] // Shared middlewares for all commands/payloads
skipAutoCmd bool // If true, all commands in this plugin are excluded from auto-help skipAutoCmd bool // If true, all commands in this plugin are excluded from auto-help
logger *slog.Logger
onClose func() error
} }
// NewPlugin creates a new Plugin with the given name. // NewPlugin creates a new Plugin with the given name.
func NewPlugin[T DbContext](name string) *Plugin[T] { func NewPlugin[T DbContext](name string) *Plugin[T] {
return &Plugin[T]{ return &Plugin[T]{
name, make(map[string]*Command[T]), name: name,
make(map[string]*Command[T]), extypes.Slice[Middleware[T]]{}, false, commands: make(map[string]*Command[T]),
payloads: make(map[string]*Command[T]),
middlewares: make(extypes.Slice[Middleware[T]], 0),
skipAutoCmd: false,
logger: nil,
} }
} }
@@ -210,6 +222,51 @@ func (p *Plugin[T]) SkipCommandAutoGen() *Plugin[T] {
return p return p
} }
// SetLogger sets the logger used for this plugin's handlers.
//
// Call this before Bot.AddPlugins. If the plugin is already registered, changing
// the original *Plugin does not update the Bot's internal copy.
func (p *Plugin[T]) SetLogger(l *slog.Logger) *Plugin[T] {
p.logger = l
return p
}
// RemoveLogger clears the custom logger for this plugin.
//
// Call this before Bot.AddPlugins. If the plugin is already registered, changing
// the original *Plugin does not update the Bot's internal copy.
func (p *Plugin[T]) RemoveLogger() *Plugin[T] {
p.logger = nil
return p
}
// SetOnClose registers a callback invoked from Plugin.Close after the plugin
// logger is closed.
//
// Call this before Bot.AddPlugins. If the plugin is already registered, changing
// the original *Plugin does not update the Bot's internal copy.
func (p *Plugin[T]) SetOnClose(f func() error) *Plugin[T] {
p.onClose = f
return p
}
// Close releases plugin-owned resources such as its logger and optional
// OnClose callback.
func (p *Plugin[T]) Close() error {
var e []error
if p.logger != nil {
if err := p.logger.Close(); err != nil {
e = append(e, err)
}
}
if p.onClose != nil {
if err := p.onClose(); err != nil {
e = append(e, err)
}
}
return errors.Join(e...)
}
// executeCmd finds and executes a command by its trigger string. // executeCmd finds and executes a command by its trigger string.
// Validates arguments and runs middlewares before executor. // Validates arguments and runs middlewares before executor.
// On error, sends an error message to the user via ctx.error(). // On error, sends an error message to the user via ctx.error().
+4 -5
View File
@@ -95,10 +95,9 @@ type API struct {
} }
// NewAPI creates a new API client from options. // NewAPI creates a new API client from options.
// Always call CloseApi() when done to release resources. // Always call Close() when done to release resources.
func NewAPI(opts *APIOpts) *API { func NewAPI(opts *APIOpts) *API {
l := slog.CreateLogger().Level(utils.GetLoggerLevel()).Prefix("API") l := utils.CreateLogger("API", utils.GetLoggerLevel())
l.AddWriter(l.CreateJsonStdoutWriter())
client := opts.client client := opts.client
if client == nil { if client == nil {
@@ -120,10 +119,10 @@ func NewAPI(opts *APIOpts) *API {
} }
} }
// CloseApi shuts down the internal worker pool and closes the logger. // Close shuts down the internal worker pool and closes the logger.
// Must be called to avoid resource leaks. // Must be called to avoid resource leaks.
// See https://core.telegram.org/bots/api // See https://core.telegram.org/bots/api
func (api *API) CloseApi() error { func (api *API) Close() error {
api.pool.stop() api.pool.stop()
return api.logger.Close() return api.logger.Close()
} }
+2 -2
View File
@@ -35,8 +35,8 @@ func TestAPILeavesAcceptEncodingToHTTPTransport(t *testing.T) {
SetHTTPClient(client), SetHTTPClient(client),
) )
defer func() { defer func() {
if err := api.CloseApi(); err != nil { if err := api.Close(); err != nil {
t.Fatalf("CloseApi returned error: %v", err) t.Fatalf("Close returned error: %v", err)
} }
}() }()
+4 -4
View File
@@ -49,18 +49,18 @@ func (api *API) LogOutWithContext(ctx context.Context) (bool, error) {
return req.DoWithContext(ctx, api) return req.DoWithContext(ctx, api)
} }
// Close closes the bot instance on the local server. // CloseRemote closes the bot instance on the local server.
// Returns true on success. // Returns true on success.
// See https://core.telegram.org/bots/api#close // See https://core.telegram.org/bots/api#close
func (api *API) Close() (bool, error) { func (api *API) CloseRemote() (bool, error) {
req := NewRequest[bool, EmptyParams]("close", NoParams) req := NewRequest[bool, EmptyParams]("close", NoParams)
return req.Do(api) return req.Do(api)
} }
// CloseWithContext is the context-aware variant of Close. // CloseRemoteWithContext is the context-aware variant of CloseRemote.
// It executes the same request but uses ctx for cancellation and deadlines. // It executes the same request but uses ctx for cancellation and deadlines.
// See https://core.telegram.org/bots/api#close // See https://core.telegram.org/bots/api#close
func (api *API) CloseWithContext(ctx context.Context) (bool, error) { func (api *API) CloseRemoteWithContext(ctx context.Context) (bool, error) {
req := NewRequest[bool, EmptyParams]("close", NoParams) req := NewRequest[bool, EmptyParams]("close", NoParams)
return req.DoWithContext(ctx, api) return req.DoWithContext(ctx, api)
} }
+6 -6
View File
@@ -27,8 +27,8 @@ func TestGetFileByLinkUsesConfiguredAPIURL(t *testing.T) {
SetHTTPClient(client), SetHTTPClient(client),
) )
defer func() { defer func() {
if err := api.CloseApi(); err != nil { if err := api.Close(); err != nil {
t.Fatalf("CloseApi returned error: %v", err) t.Fatalf("Close returned error: %v", err)
} }
}() }()
@@ -60,8 +60,8 @@ func TestGetFileByLinkReturnsHTTPStatusError(t *testing.T) {
SetHTTPClient(client), SetHTTPClient(client),
) )
defer func() { defer func() {
if err := api.CloseApi(); err != nil { if err := api.Close(); err != nil {
t.Fatalf("CloseApi returned error: %v", err) t.Fatalf("Close returned error: %v", err)
} }
}() }()
@@ -97,8 +97,8 @@ func TestGetUpdatesOmitsAllowedUpdatesWhenEmpty(t *testing.T) {
SetHTTPClient(client), SetHTTPClient(client),
) )
defer func() { defer func() {
if err := api.CloseApi(); err != nil { if err := api.Close(); err != nil {
t.Fatalf("CloseApi returned error: %v", err) t.Fatalf("Close returned error: %v", err)
} }
}() }()
+1 -2
View File
@@ -69,8 +69,7 @@ type Uploader struct {
// NewUploader creates a multipart uploader bound to an API client. // NewUploader creates a multipart uploader bound to an API client.
func NewUploader(api *API) *Uploader { func NewUploader(api *API) *Uploader {
logger := slog.CreateLogger().Level(utils.GetLoggerLevel()).Prefix("UPLOADER") logger := utils.CreateLogger("UPLOADER", utils.GetLoggerLevel())
logger.AddWriter(logger.CreateJsonStdoutWriter())
return &Uploader{api, logger} return &Uploader{api, logger}
} }
+2 -2
View File
@@ -44,8 +44,8 @@ func TestUploaderEncodesJSONFieldsAndLeavesAcceptEncodingToHTTPTransport(t *test
SetHTTPClient(client), SetHTTPClient(client),
) )
defer func() { defer func() {
if err := api.CloseApi(); err != nil { if err := api.Close(); err != nil {
t.Fatalf("CloseApi returned error: %v", err) t.Fatalf("Close returned error: %v", err)
} }
}() }()
+26
View File
@@ -14,3 +14,29 @@ func GetLoggerLevel() slog.LogLevel {
} }
return level return level
} }
// CreateLogger creates a logger with the shared default policy:
// JSON stdout output, provided prefix, and provided level.
func CreateLogger(prefix string, level slog.LogLevel) *slog.Logger {
logger := slog.CreateLogger().Level(level)
if prefix != "" {
logger.Prefix(prefix)
}
logger.AddWriter(logger.CreateJsonStdoutWriter())
return logger
}
// CreateFileLogger creates a logger with the shared default policy and appends
// file output to the provided path.
//
// The returned logger is always non-nil. When file writer creation fails, the
// logger still writes to stdout and the error is returned to the caller.
func CreateFileLogger(prefix string, level slog.LogLevel, filePath string) (*slog.Logger, error) {
logger := CreateLogger(prefix, level)
fileWriter, err := logger.CreateTextFileWriter(filePath)
if err != nil {
return logger, err
}
logger.AddWriter(fileWriter)
return logger, nil
}
+34
View File
@@ -0,0 +1,34 @@
package utils
import (
"os"
"path/filepath"
"strings"
"testing"
"git.nix13.pw/scuroneko/slog"
)
func TestCreateFileLoggerWritesToConfiguredFile(t *testing.T) {
logPath := filepath.Join(t.TempDir(), "main.log")
logger, err := CreateFileLogger("TEST", slog.DEBUG, logPath)
if err != nil {
t.Fatalf("CreateFileLogger returned error: %v", err)
}
logger.Infoln("hello from file logger")
if err := logger.Close(); err != nil {
t.Fatalf("Close returned error: %v", err)
}
data, err := os.ReadFile(logPath)
if err != nil {
t.Fatalf("ReadFile returned error: %v", err)
}
if !strings.Contains(string(data), "hello from file logger") {
t.Fatalf("expected log message in file, got %q", string(data))
}
if !strings.Contains(string(data), "[TEST]") {
t.Fatalf("expected prefix in file, got %q", string(data))
}
}
+2 -2
View File
@@ -1,9 +1,9 @@
package utils package utils
const ( const (
VersionString = "1.0.0-rc.5" VersionString = "1.0.0-rc.7"
VersionMajor = 1 VersionMajor = 1
VersionMinor = 0 VersionMinor = 0
VersionPatch = 0 VersionPatch = 0
VersionBeta = 5 VersionBeta = 7
) )