feat: generate manifest helper glue
This commit is contained in:
parent
20b5026f9d
commit
a79f73825f
3 changed files with 439 additions and 12 deletions
|
|
@ -1,8 +1,7 @@
|
|||
# Génération depuis `mcp.toml`
|
||||
|
||||
La commande `mcp-framework generate` génère la glue Go dérivée du manifeste
|
||||
racine d'un projet existant. Le premier usage couvert est le loader de
|
||||
manifeste embarqué.
|
||||
racine d'un projet existant.
|
||||
|
||||
## Usage
|
||||
|
||||
|
|
@ -18,9 +17,12 @@ génère :
|
|||
```text
|
||||
mcpgen/
|
||||
manifest.go
|
||||
metadata.go
|
||||
update.go
|
||||
secretstore.go
|
||||
```
|
||||
|
||||
Le fichier généré expose :
|
||||
Le package généré expose le loader de manifeste :
|
||||
|
||||
```go
|
||||
func LoadManifest(startDir string) (manifest.File, string, error)
|
||||
|
|
@ -30,6 +32,48 @@ Cette fonction appelle `manifest.LoadDefaultOrEmbedded`. En développement, un
|
|||
`mcp.toml` présent sur disque reste prioritaire. Pour un binaire copié seul,
|
||||
elle utilise le contenu du manifeste embarqué au moment de la génération.
|
||||
|
||||
Il expose aussi des helpers dérivés du manifeste :
|
||||
|
||||
```go
|
||||
const BinaryName = "my-mcp"
|
||||
const DefaultDescription = "..."
|
||||
const DocsURL = "..."
|
||||
|
||||
func BootstrapInfo(startDir string) (manifest.BootstrapMetadata, string, error)
|
||||
func ScaffoldInfo(startDir string) (manifest.ScaffoldMetadata, string, error)
|
||||
```
|
||||
|
||||
Pour l'auto-update :
|
||||
|
||||
```go
|
||||
func UpdateOptions(version string, stdout io.Writer) (update.Options, error)
|
||||
func UpdateOptionsFrom(startDir string, version string, stdout io.Writer) (update.Options, error)
|
||||
func RunUpdate(ctx context.Context, args []string, version string, stdout io.Writer) error
|
||||
func RunUpdateFrom(ctx context.Context, args []string, startDir string, version string, stdout io.Writer) error
|
||||
```
|
||||
|
||||
`RunUpdate` parse les flags de la commande `update`, refuse les arguments
|
||||
positionnels, charge le manifeste via `LoadManifest`, puis appelle
|
||||
`update.Run`.
|
||||
|
||||
Pour les secrets :
|
||||
|
||||
```go
|
||||
type SecretStoreOptions struct {
|
||||
ServiceName string
|
||||
LookupEnv func(string) (string, bool)
|
||||
}
|
||||
|
||||
func OpenSecretStore(options SecretStoreOptions) (secretstore.Store, error)
|
||||
func DescribeSecretRuntime(options SecretStoreOptions) (secretstore.RuntimeDescription, error)
|
||||
func PreflightSecretStore(options SecretStoreOptions) (secretstore.PreflightReport, error)
|
||||
```
|
||||
|
||||
`SecretStoreOptions` contient aussi les options techniques du package
|
||||
`secretstore` (`KWalletAppID`, `KWalletFolder`, `BitwardenCommand`,
|
||||
`BitwardenDebug`, `Shell`, `ExecutableResolver`). Si `ServiceName` est vide,
|
||||
le nom du binaire déclaré dans le manifeste est utilisé.
|
||||
|
||||
## Flags
|
||||
|
||||
- `--manifest` : chemin du `mcp.toml` à lire. Par défaut, `./mcp.toml`.
|
||||
|
|
@ -53,7 +97,12 @@ Pour remplacer un wrapper local du type `internal/manifest` :
|
|||
`example.com/my-mcp/mcpgen`.
|
||||
4. Remplacer les appels `manifest.Load(...)` du wrapper par
|
||||
`mcpgen.LoadManifest(...)`.
|
||||
5. Supprimer l'ancien wrapper manuel.
|
||||
5. Remplacer les reconstructions locales d'options update par
|
||||
`mcpgen.UpdateOptions(...)` ou `mcpgen.RunUpdate(...)`.
|
||||
6. Remplacer les wrappers secret store qui ne font que brancher le loader par
|
||||
`mcpgen.OpenSecretStore`, `mcpgen.DescribeSecretRuntime` et
|
||||
`mcpgen.PreflightSecretStore`.
|
||||
7. Supprimer l'ancien wrapper manuel.
|
||||
|
||||
Après génération, un simple `go build ./...` suffit. La compilation ne dépend
|
||||
pas de la commande `mcp-framework`.
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ func Generate(options Options) (Result, error) {
|
|||
return Result{}, err
|
||||
}
|
||||
|
||||
if _, err := manifest.Load(normalized.ManifestPath); err != nil {
|
||||
manifestFile, err := manifest.Load(normalized.ManifestPath)
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +46,19 @@ func Generate(options Options) (Result, error) {
|
|||
return Result{}, fmt.Errorf("read manifest %s: %w", normalized.ManifestPath, err)
|
||||
}
|
||||
|
||||
content, err := renderManifestLoader(normalized.PackageName, string(manifestContent))
|
||||
manifestLoader, err := renderManifestLoader(normalized.PackageName, string(manifestContent))
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
metadata, err := renderMetadata(normalized.PackageName, manifestFile)
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
update, err := renderUpdate(normalized.PackageName)
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
secretstore, err := renderSecretStore(normalized.PackageName)
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
|
|
@ -53,7 +66,22 @@ func Generate(options Options) (Result, error) {
|
|||
files := []generatedFile{
|
||||
{
|
||||
Path: filepath.Join(normalized.PackageDir, "manifest.go"),
|
||||
Content: content,
|
||||
Content: manifestLoader,
|
||||
Mode: 0o644,
|
||||
},
|
||||
{
|
||||
Path: filepath.Join(normalized.PackageDir, "metadata.go"),
|
||||
Content: metadata,
|
||||
Mode: 0o644,
|
||||
},
|
||||
{
|
||||
Path: filepath.Join(normalized.PackageDir, "update.go"),
|
||||
Content: update,
|
||||
Mode: 0o644,
|
||||
},
|
||||
{
|
||||
Path: filepath.Join(normalized.PackageDir, "secretstore.go"),
|
||||
Content: secretstore,
|
||||
Mode: 0o644,
|
||||
},
|
||||
}
|
||||
|
|
@ -189,6 +217,209 @@ func LoadManifest(startDir string) (fwmanifest.File, string, error) {
|
|||
return string(formatted), nil
|
||||
}
|
||||
|
||||
func renderMetadata(packageName string, manifestFile manifest.File) (string, error) {
|
||||
bootstrapInfo := manifestFile.BootstrapInfo()
|
||||
|
||||
source := fmt.Sprintf(`// Code generated by mcp-framework generate. DO NOT EDIT.
|
||||
|
||||
package %s
|
||||
|
||||
import fwmanifest "gitea.lclr.dev/AI/mcp-framework/manifest"
|
||||
|
||||
const BinaryName = %s
|
||||
const DefaultDescription = %s
|
||||
const DocsURL = %s
|
||||
|
||||
func BootstrapInfo(startDir string) (fwmanifest.BootstrapMetadata, string, error) {
|
||||
manifestFile, source, err := LoadManifest(startDir)
|
||||
if err != nil {
|
||||
return fwmanifest.BootstrapMetadata{}, "", err
|
||||
}
|
||||
|
||||
return manifestFile.BootstrapInfo(), source, nil
|
||||
}
|
||||
|
||||
func ScaffoldInfo(startDir string) (fwmanifest.ScaffoldMetadata, string, error) {
|
||||
manifestFile, source, err := LoadManifest(startDir)
|
||||
if err != nil {
|
||||
return fwmanifest.ScaffoldMetadata{}, "", err
|
||||
}
|
||||
|
||||
return manifestFile.ScaffoldInfo(), source, nil
|
||||
}
|
||||
`, packageName, strconv.Quote(manifestFile.BinaryName), strconv.Quote(bootstrapInfo.Description), strconv.Quote(manifestFile.DocsURL))
|
||||
|
||||
return formatGenerated("metadata", source)
|
||||
}
|
||||
|
||||
func renderUpdate(packageName string) (string, error) {
|
||||
source := fmt.Sprintf(`// Code generated by mcp-framework generate. DO NOT EDIT.
|
||||
|
||||
package %s
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
fwupdate "gitea.lclr.dev/AI/mcp-framework/update"
|
||||
)
|
||||
|
||||
func UpdateOptions(version string, stdout io.Writer) (fwupdate.Options, error) {
|
||||
return UpdateOptionsFrom(".", version, stdout)
|
||||
}
|
||||
|
||||
func UpdateOptionsFrom(startDir string, version string, stdout io.Writer) (fwupdate.Options, error) {
|
||||
manifestFile, _, err := LoadManifest(startDir)
|
||||
if err != nil {
|
||||
return fwupdate.Options{}, err
|
||||
}
|
||||
|
||||
binaryName := strings.TrimSpace(manifestFile.BinaryName)
|
||||
if binaryName == "" {
|
||||
binaryName = BinaryName
|
||||
}
|
||||
|
||||
return fwupdate.Options{
|
||||
CurrentVersion: version,
|
||||
Stdout: stdout,
|
||||
BinaryName: binaryName,
|
||||
ReleaseSource: manifestFile.Update.ReleaseSource(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func RunUpdate(ctx context.Context, args []string, version string, stdout io.Writer) error {
|
||||
return RunUpdateFrom(ctx, args, ".", version, stdout)
|
||||
}
|
||||
|
||||
func RunUpdateFrom(ctx context.Context, args []string, startDir string, version string, stdout io.Writer) error {
|
||||
fs := flag.NewFlagSet("update", flag.ContinueOnError)
|
||||
fs.SetOutput(io.Discard)
|
||||
|
||||
if err := fs.Parse(args); err != nil {
|
||||
return err
|
||||
}
|
||||
if fs.NArg() != 0 {
|
||||
return fmt.Errorf("update does not accept positional arguments: %%s", strings.Join(fs.Args(), ", "))
|
||||
}
|
||||
|
||||
options, err := UpdateOptionsFrom(startDir, version, stdout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return fwupdate.Run(ctx, options)
|
||||
}
|
||||
`, packageName)
|
||||
|
||||
return formatGenerated("update", source)
|
||||
}
|
||||
|
||||
func renderSecretStore(packageName string) (string, error) {
|
||||
source := fmt.Sprintf(`// Code generated by mcp-framework generate. DO NOT EDIT.
|
||||
|
||||
package %s
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
fwsecretstore "gitea.lclr.dev/AI/mcp-framework/secretstore"
|
||||
)
|
||||
|
||||
type SecretStoreOptions struct {
|
||||
ServiceName string
|
||||
LookupEnv func(string) (string, bool)
|
||||
KWalletAppID string
|
||||
KWalletFolder string
|
||||
BitwardenCommand string
|
||||
BitwardenDebug bool
|
||||
Shell string
|
||||
ExecutableResolver fwsecretstore.ExecutableResolver
|
||||
}
|
||||
|
||||
func OpenSecretStore(options SecretStoreOptions) (fwsecretstore.Store, error) {
|
||||
return fwsecretstore.OpenFromManifest(secretStoreOpenOptions(options))
|
||||
}
|
||||
|
||||
func DescribeSecretRuntime(options SecretStoreOptions) (fwsecretstore.RuntimeDescription, error) {
|
||||
return fwsecretstore.DescribeRuntime(secretStoreDescribeOptions(options))
|
||||
}
|
||||
|
||||
func PreflightSecretStore(options SecretStoreOptions) (fwsecretstore.PreflightReport, error) {
|
||||
return fwsecretstore.PreflightFromManifest(secretStoreDescribeOptions(options))
|
||||
}
|
||||
|
||||
func secretStoreOpenOptions(options SecretStoreOptions) fwsecretstore.OpenFromManifestOptions {
|
||||
return fwsecretstore.OpenFromManifestOptions{
|
||||
ServiceName: secretStoreServiceName(options),
|
||||
LookupEnv: options.LookupEnv,
|
||||
KWalletAppID: options.KWalletAppID,
|
||||
KWalletFolder: options.KWalletFolder,
|
||||
BitwardenCommand: options.BitwardenCommand,
|
||||
BitwardenDebug: options.BitwardenDebug,
|
||||
Shell: options.Shell,
|
||||
ManifestLoader: LoadManifest,
|
||||
ExecutableResolver: options.ExecutableResolver,
|
||||
}
|
||||
}
|
||||
|
||||
func secretStoreDescribeOptions(options SecretStoreOptions) fwsecretstore.DescribeRuntimeOptions {
|
||||
return fwsecretstore.DescribeRuntimeOptions{
|
||||
ServiceName: secretStoreServiceName(options),
|
||||
LookupEnv: options.LookupEnv,
|
||||
KWalletAppID: options.KWalletAppID,
|
||||
KWalletFolder: options.KWalletFolder,
|
||||
BitwardenCommand: options.BitwardenCommand,
|
||||
BitwardenDebug: options.BitwardenDebug,
|
||||
Shell: options.Shell,
|
||||
ManifestLoader: LoadManifest,
|
||||
ExecutableResolver: options.ExecutableResolver,
|
||||
}
|
||||
}
|
||||
|
||||
func secretStoreServiceName(options SecretStoreOptions) string {
|
||||
serviceName := strings.TrimSpace(options.ServiceName)
|
||||
if serviceName != "" {
|
||||
return serviceName
|
||||
}
|
||||
|
||||
startDir := "."
|
||||
executableResolver := options.ExecutableResolver
|
||||
if executableResolver == nil {
|
||||
executableResolver = os.Executable
|
||||
}
|
||||
if executablePath, err := executableResolver(); err == nil {
|
||||
if dir := strings.TrimSpace(filepath.Dir(strings.TrimSpace(executablePath))); dir != "" {
|
||||
startDir = dir
|
||||
}
|
||||
}
|
||||
|
||||
if manifestFile, _, err := LoadManifest(startDir); err == nil {
|
||||
if binaryName := strings.TrimSpace(manifestFile.BinaryName); binaryName != "" {
|
||||
return binaryName
|
||||
}
|
||||
}
|
||||
|
||||
return BinaryName
|
||||
}
|
||||
`, packageName)
|
||||
|
||||
return formatGenerated("secretstore", source)
|
||||
}
|
||||
|
||||
func formatGenerated(name, source string) (string, error) {
|
||||
formatted, err := format.Source([]byte(source))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("format generated %s: %w", name, err)
|
||||
}
|
||||
|
||||
return string(formatted), nil
|
||||
}
|
||||
|
||||
func writeGeneratedFile(path, content string, mode os.FileMode) error {
|
||||
current, err := os.ReadFile(path)
|
||||
if err == nil && bytes.Equal(current, []byte(content)) {
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ description = "Demo MCP"
|
|||
t.Fatalf("Generate returned error: %v", err)
|
||||
}
|
||||
|
||||
if !slices.Equal(result.Files, []string{filepath.Join("mcpgen", "manifest.go")}) {
|
||||
if !slices.Equal(result.Files, defaultGeneratedFiles("mcpgen")) {
|
||||
t.Fatalf("result files = %v", result.Files)
|
||||
}
|
||||
|
||||
|
|
@ -49,6 +49,87 @@ description = "Demo MCP"
|
|||
}
|
||||
}
|
||||
|
||||
func TestGenerateCreatesP1Helpers(t *testing.T) {
|
||||
projectDir := newProject(t, `
|
||||
binary_name = "demo-mcp"
|
||||
docs_url = "https://docs.example.com/demo"
|
||||
|
||||
[update]
|
||||
driver = "gitea"
|
||||
repository = "org/demo-mcp"
|
||||
base_url = "https://gitea.example.com"
|
||||
asset_name_template = "{binary}-{os}-{arch}{ext}"
|
||||
|
||||
[secret_store]
|
||||
backend_policy = "env-only"
|
||||
|
||||
[bootstrap]
|
||||
description = "Demo MCP"
|
||||
`)
|
||||
|
||||
result, err := Generate(Options{ProjectDir: projectDir})
|
||||
if err != nil {
|
||||
t.Fatalf("Generate returned error: %v", err)
|
||||
}
|
||||
|
||||
wantFiles := []string{
|
||||
filepath.Join("mcpgen", "manifest.go"),
|
||||
filepath.Join("mcpgen", "metadata.go"),
|
||||
filepath.Join("mcpgen", "secretstore.go"),
|
||||
filepath.Join("mcpgen", "update.go"),
|
||||
}
|
||||
if !slices.Equal(result.Files, wantFiles) {
|
||||
t.Fatalf("result files = %v, want %v", result.Files, wantFiles)
|
||||
}
|
||||
|
||||
metadata, err := os.ReadFile(filepath.Join(projectDir, "mcpgen", "metadata.go"))
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFile metadata.go: %v", err)
|
||||
}
|
||||
for _, snippet := range []string{
|
||||
`const BinaryName = "demo-mcp"`,
|
||||
`const DefaultDescription = "Demo MCP"`,
|
||||
`const DocsURL = "https://docs.example.com/demo"`,
|
||||
"func BootstrapInfo(startDir string) (fwmanifest.BootstrapMetadata, string, error) {",
|
||||
"func ScaffoldInfo(startDir string) (fwmanifest.ScaffoldMetadata, string, error) {",
|
||||
} {
|
||||
if !strings.Contains(string(metadata), snippet) {
|
||||
t.Fatalf("metadata.go missing snippet %q:\n%s", snippet, metadata)
|
||||
}
|
||||
}
|
||||
|
||||
update, err := os.ReadFile(filepath.Join(projectDir, "mcpgen", "update.go"))
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFile update.go: %v", err)
|
||||
}
|
||||
for _, snippet := range []string{
|
||||
"func UpdateOptions(version string, stdout io.Writer) (fwupdate.Options, error) {",
|
||||
"func UpdateOptionsFrom(startDir string, version string, stdout io.Writer) (fwupdate.Options, error) {",
|
||||
"func RunUpdate(ctx context.Context, args []string, version string, stdout io.Writer) error {",
|
||||
"ReleaseSource:",
|
||||
} {
|
||||
if !strings.Contains(string(update), snippet) {
|
||||
t.Fatalf("update.go missing snippet %q:\n%s", snippet, update)
|
||||
}
|
||||
}
|
||||
|
||||
secretstore, err := os.ReadFile(filepath.Join(projectDir, "mcpgen", "secretstore.go"))
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFile secretstore.go: %v", err)
|
||||
}
|
||||
for _, snippet := range []string{
|
||||
"type SecretStoreOptions struct {",
|
||||
"func OpenSecretStore(options SecretStoreOptions) (fwsecretstore.Store, error) {",
|
||||
"func DescribeSecretRuntime(options SecretStoreOptions) (fwsecretstore.RuntimeDescription, error) {",
|
||||
"func PreflightSecretStore(options SecretStoreOptions) (fwsecretstore.PreflightReport, error) {",
|
||||
"ManifestLoader:",
|
||||
} {
|
||||
if !strings.Contains(string(secretstore), snippet) {
|
||||
t.Fatalf("secretstore.go missing snippet %q:\n%s", snippet, secretstore)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateIsIdempotentAndCheckDetectsDrift(t *testing.T) {
|
||||
projectDir := newProject(t, `binary_name = "demo-mcp"`)
|
||||
|
||||
|
|
@ -106,7 +187,7 @@ func TestGenerateSupportsManifestAndPackageFlags(t *testing.T) {
|
|||
t.Fatalf("Generate returned error: %v", err)
|
||||
}
|
||||
|
||||
if !slices.Equal(result.Files, []string{filepath.Join("internal", "generated", "manifest.go")}) {
|
||||
if !slices.Equal(result.Files, defaultGeneratedFiles(filepath.Join("internal", "generated"))) {
|
||||
t.Fatalf("result files = %v", result.Files)
|
||||
}
|
||||
|
||||
|
|
@ -135,6 +216,17 @@ func TestGeneratedLoaderFallsBackToEmbeddedManifest(t *testing.T) {
|
|||
projectDir := newProject(t, `
|
||||
binary_name = "embedded-demo"
|
||||
docs_url = "https://docs.example.com/embedded"
|
||||
|
||||
[update]
|
||||
driver = "gitea"
|
||||
repository = "org/embedded-demo"
|
||||
base_url = "https://gitea.example.com"
|
||||
|
||||
[secret_store]
|
||||
backend_policy = "env-only"
|
||||
|
||||
[bootstrap]
|
||||
description = "Embedded Demo"
|
||||
`)
|
||||
writeModule(t, projectDir)
|
||||
|
||||
|
|
@ -146,7 +238,7 @@ docs_url = "https://docs.example.com/embedded"
|
|||
t.Fatalf("Remove runtime manifest: %v", err)
|
||||
}
|
||||
|
||||
cmd := exec.Command("go", "test", "./...")
|
||||
cmd := exec.Command("go", "test", "-mod=mod", "./...")
|
||||
cmd.Dir = projectDir
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
|
|
@ -164,6 +256,15 @@ func newProject(t *testing.T, manifest string) string {
|
|||
return projectDir
|
||||
}
|
||||
|
||||
func defaultGeneratedFiles(packageDir string) []string {
|
||||
return []string{
|
||||
filepath.Join(packageDir, "manifest.go"),
|
||||
filepath.Join(packageDir, "metadata.go"),
|
||||
filepath.Join(packageDir, "secretstore.go"),
|
||||
filepath.Join(packageDir, "update.go"),
|
||||
}
|
||||
}
|
||||
|
||||
func writeModule(t *testing.T, projectDir string) {
|
||||
t.Helper()
|
||||
|
||||
|
|
@ -172,7 +273,7 @@ func writeModule(t *testing.T, projectDir string) {
|
|||
t.Fatalf("Abs repo root: %v", err)
|
||||
}
|
||||
|
||||
goMod := "module example.com/generated-demo\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/BurntSushi/toml v1.6.0\n\tgitea.lclr.dev/AI/mcp-framework v0.0.0\n)\n\nreplace gitea.lclr.dev/AI/mcp-framework => " + filepath.ToSlash(repoRoot) + "\n"
|
||||
goMod := "module example.com/generated-demo\n\ngo 1.25.0\n\nrequire (\n\tgithub.com/99designs/keyring v1.2.2\n\tgithub.com/BurntSushi/toml v1.6.0\n\tgitea.lclr.dev/AI/mcp-framework v0.0.0\n)\n\nreplace gitea.lclr.dev/AI/mcp-framework => " + filepath.ToSlash(repoRoot) + "\n"
|
||||
if err := os.WriteFile(filepath.Join(projectDir, "go.mod"), []byte(goMod), 0o600); err != nil {
|
||||
t.Fatalf("WriteFile go.mod: %v", err)
|
||||
}
|
||||
|
|
@ -188,13 +289,15 @@ func writeModule(t *testing.T, projectDir string) {
|
|||
testFile := `package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
fwsecretstore "gitea.lclr.dev/AI/mcp-framework/secretstore"
|
||||
"example.com/generated-demo/mcpgen"
|
||||
fwmanifest "gitea.lclr.dev/AI/mcp-framework/manifest"
|
||||
)
|
||||
|
||||
func TestGeneratedLoaderUsesEmbeddedManifest(t *testing.T) {
|
||||
func TestGeneratedHelpersUseEmbeddedManifest(t *testing.T) {
|
||||
file, source, err := mcpgen.LoadManifest(".")
|
||||
if err != nil {
|
||||
t.Fatalf("LoadManifest returned error: %v", err)
|
||||
|
|
@ -205,6 +308,50 @@ func TestGeneratedLoaderUsesEmbeddedManifest(t *testing.T) {
|
|||
if file.BinaryName != "embedded-demo" {
|
||||
t.Fatalf("binary name = %q", file.BinaryName)
|
||||
}
|
||||
|
||||
info, source, err := mcpgen.BootstrapInfo(".")
|
||||
if err != nil {
|
||||
t.Fatalf("BootstrapInfo returned error: %v", err)
|
||||
}
|
||||
if source != fwmanifest.EmbeddedSource {
|
||||
t.Fatalf("bootstrap source = %q, want %q", source, fwmanifest.EmbeddedSource)
|
||||
}
|
||||
if info.Description != "Embedded Demo" {
|
||||
t.Fatalf("description = %q", info.Description)
|
||||
}
|
||||
|
||||
updateOptions, err := mcpgen.UpdateOptions("1.2.3", io.Discard)
|
||||
if err != nil {
|
||||
t.Fatalf("UpdateOptions returned error: %v", err)
|
||||
}
|
||||
if updateOptions.CurrentVersion != "1.2.3" {
|
||||
t.Fatalf("current version = %q", updateOptions.CurrentVersion)
|
||||
}
|
||||
if updateOptions.BinaryName != "embedded-demo" {
|
||||
t.Fatalf("update binary name = %q", updateOptions.BinaryName)
|
||||
}
|
||||
if updateOptions.ReleaseSource.Repository != "org/embedded-demo" {
|
||||
t.Fatalf("release repository = %q", updateOptions.ReleaseSource.Repository)
|
||||
}
|
||||
|
||||
store, err := mcpgen.OpenSecretStore(mcpgen.SecretStoreOptions{
|
||||
LookupEnv: func(name string) (string, bool) {
|
||||
return "secret-from-env", true
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("OpenSecretStore returned error: %v", err)
|
||||
}
|
||||
value, err := store.GetSecret("profile/default/api-token")
|
||||
if err != nil {
|
||||
t.Fatalf("GetSecret returned error: %v", err)
|
||||
}
|
||||
if value != "secret-from-env" {
|
||||
t.Fatalf("secret value = %q", value)
|
||||
}
|
||||
if fwsecretstore.EffectiveBackendPolicy(store) != fwsecretstore.BackendEnvOnly {
|
||||
t.Fatalf("effective backend = %q", fwsecretstore.EffectiveBackendPolicy(store))
|
||||
}
|
||||
}
|
||||
`
|
||||
if err := os.WriteFile(filepath.Join(projectDir, "main_test.go"), []byte(testFile), 0o600); err != nil {
|
||||
|
|
|
|||
Loading…
Reference in a new issue