Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@ import (
"knative.dev/func/pkg/config"
fn "knative.dev/func/pkg/functions"
"knative.dev/func/pkg/k8s"
"knative.dev/func/pkg/version"
)

// DefaultVersion when building source directly (bypassing the Makefile)
const DefaultVersion = "v0.0.0+source"
// DefaultVersion when building source directly (bypassing the Makefile).
// Delegates to version.DefaultVers so the fallback is defined in one place.
const DefaultVersion = version.DefaultVers

// DefaultNamespace is the global static default namespace, and is equivalent
// to the Kubernetes default namespace.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/BurntSushi/toml v1.6.0
github.com/Masterminds/semver v1.5.0
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/Microsoft/go-winio v0.6.2
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2
github.com/alecthomas/jsonschema v0.0.0-20220216202328-9eeeec9d044b
Expand Down Expand Up @@ -90,7 +91,6 @@ require (
github.com/Azure/go-autorest/autorest/date v0.3.1 // indirect
github.com/Azure/go-autorest/logger v0.2.2 // indirect
github.com/Azure/go-autorest/tracing v0.6.1 // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/OneOfOne/xxhash v1.2.8 // indirect
github.com/ProtonMail/go-crypto v1.4.0 // indirect
github.com/agext/levenshtein v1.2.3 // indirect
Expand Down
2 changes: 1 addition & 1 deletion pkg/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func Main() {
cfg := cmd.RootCommandConfig{
Name: "func",
Version: cmd.Version{
Vers: version.Vers,
Vers: version.Get().Original(),
Kver: version.Kver,
Hash: version.Hash,
}}
Expand Down
10 changes: 6 additions & 4 deletions pkg/mcp/mcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import (
"sync/atomic"

"github.com/modelcontextprotocol/go-sdk/mcp"
"knative.dev/func/pkg/version"
)

const (
name = "func"
title = "func"
version = "0.1.0"
name = "func"
title = "func"
)

// NOTE: Invoking prompts in some interfaces (such as Claude Code) when all
Expand All @@ -24,6 +24,7 @@ const (
type Server struct {
OnInit func(context.Context) // Invoked when the server is initialized
prefix string // Command prefix ("func" or "kn func")
version string // Computed once at construction; used by healthcheck
readonly atomic.Bool // disables deploy and delete when true
executor executor
transport mcp.Transport // Transport to use (defaults to StdioTransport)
Expand Down Expand Up @@ -79,6 +80,7 @@ func WithReadonly(readonly bool) Option {
func New(options ...Option) *Server {
s := &Server{
prefix: "func",
version: version.Get().String(),
transport: &mcp.StdioTransport{},
OnInit: func(_ context.Context) {},
}
Expand All @@ -91,7 +93,7 @@ func New(options ...Option) *Server {
&mcp.Implementation{
Name: name,
Title: title,
Version: version},
Version: s.version},
&mcp.ServerOptions{
Instructions: instructions(s.readonly.Load()),
HasPrompts: true,
Expand Down
2 changes: 1 addition & 1 deletion pkg/mcp/tools_healthcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func (s *Server) healthcheckHandler(ctx context.Context, r *mcp.CallToolRequest,
output = HealthcheckOutput{
Status: "ok",
Message: "The MCP server is running!",
Version: version,
Version: s.version,
}
return
}
Expand Down
40 changes: 40 additions & 0 deletions pkg/version/version.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,43 @@
package version

import (
"fmt"

"github.com/Masterminds/semver"
)

var Vers, Kver, Hash string

// DefaultVers is the fallback version used when no build-time version was
// injected (e.g. source builds that bypass the Makefile).
const DefaultVers = "v0.0.0+source"

// defaultVersion is DefaultVers parsed once at init time. A panic here means
// the DefaultVers constant was changed to an invalid semver string.
var defaultVersion *semver.Version

func init() {
var err error
defaultVersion, err = semver.NewVersion(DefaultVers)
if err != nil {
panic(fmt.Sprintf("version: DefaultVers constant %q is not valid semver: %v", DefaultVers, err))
}
}

// Get returns the parsed semver for this binary. When no build-time version
// was injected via ldflags, DefaultVers is used. If the injected string is
// unparseable, DefaultVers is used as a safe fallback.
// String() returns a clean semver without the leading "v" (e.g. "0.0.0+source"),
// suitable for machine-readable consumers such as the MCP server.
// Original() round-trips the injected string verbatim, preserving the leading
// "v" preferred by human-readable output.
func Get() *semver.Version {
if Vers == "" {
return defaultVersion
}
v, err := semver.NewVersion(Vers) // permissive: accepts leading 'v'
if err != nil {
return defaultVersion
}
return v
}
63 changes: 63 additions & 0 deletions pkg/version/version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package version_test

import (
"testing"

"knative.dev/func/pkg/version"
)

// TestGet_Empty verifies that Get returns the DefaultVers fallback when no
// build-time version has been injected (Vers == "").
func TestGet_Empty(t *testing.T) {
orig := version.Vers
version.Vers = ""
defer func() { version.Vers = orig }()

v := version.Get()
if v == nil {
t.Fatal("expected non-nil *semver.Version")
}
// String() must be clean semver without a leading 'v'
if got := v.String(); got != "0.0.0+source" {
t.Errorf("String() = %q; want %q", got, "0.0.0+source")
}
// Original() must round-trip the full default string including 'v'
if got := v.Original(); got != "v0.0.0+source" {
t.Errorf("Original() = %q; want %q", got, "v0.0.0+source")
}
}

// TestGet_InjectedVersion verifies that a build-time version is parsed and
// exposed correctly.
func TestGet_InjectedVersion(t *testing.T) {
orig := version.Vers
version.Vers = "v1.2.3"
defer func() { version.Vers = orig }()

v := version.Get()
if v == nil {
t.Fatal("expected non-nil *semver.Version")
}
if got := v.String(); got != "1.2.3" {
t.Errorf("String() = %q; want %q", got, "1.2.3")
}
if got := v.Original(); got != "v1.2.3" {
t.Errorf("Original() = %q; want %q", got, "v1.2.3")
}
}

// TestGet_InvalidFallsBack verifies that an unparseable injected version does
// not panic and falls back to DefaultVers.
func TestGet_InvalidFallsBack(t *testing.T) {
orig := version.Vers
version.Vers = "not-a-semver!!!"
defer func() { version.Vers = orig }()

v := version.Get()
if v == nil {
t.Fatal("expected non-nil *semver.Version even for invalid input")
}
if got := v.String(); got != "0.0.0+source" {
t.Errorf("String() = %q; want %q on invalid input", got, "0.0.0+source")
}
}
Loading