* ci: consolidate duplicate workflows and merge CodeQL configs Remove 17 duplicate workflow files that were split copies of the combined originals. Each family (CI, CodeQL, Coverage, PR Build, Alpha Release) had the same job duplicated across separate push/pull_request/schedule/manual trigger files. Merge codeql.yml and codescan.yml into a single codeql.yml with a language matrix covering go, javascript-typescript, python, and actions — matching the previous default setup coverage. Remaining workflows (one per family): - ci.yml (push + PR + manual) - codeql.yml (push + PR + schedule, all languages) - coverage.yml (push + PR + manual) - alpha-release.yml (push + manual) - pr-build.yml (PR + manual) - release.yml (tag push) - agent-verify.yml, auto-label.yml, auto-project.yml Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: add collect, config, crypt, plugin packages and fix all lint issues Add four new infrastructure packages with CLI commands: - pkg/config: layered configuration (defaults → file → env → flags) - pkg/crypt: crypto primitives (Argon2id, AES-GCM, ChaCha20, HMAC, checksums) - pkg/plugin: plugin system with GitHub-based install/update/remove - pkg/collect: collection subsystem (GitHub, BitcoinTalk, market, papers, excavate) Fix all golangci-lint issues across the entire codebase (~100 errcheck, staticcheck SA1012/SA1019/ST1005, unused, ineffassign fixes) so that `core go qa` passes with 0 issues. Closes #167, #168, #170, #250, #251, #252, #253, #254, #255, #256 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
174 lines
4.5 KiB
Go
174 lines
4.5 KiB
Go
package dev
|
|
|
|
import (
|
|
"bytes"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"path/filepath"
|
|
"text/template"
|
|
|
|
"github.com/host-uk/core/pkg/cli" // Added
|
|
"github.com/host-uk/core/pkg/i18n" // Added
|
|
coreio "github.com/host-uk/core/pkg/io"
|
|
// Added
|
|
"golang.org/x/text/cases"
|
|
"golang.org/x/text/language"
|
|
)
|
|
|
|
// addSyncCommand adds the 'sync' command to the given parent command.
|
|
func addSyncCommand(parent *cli.Command) {
|
|
syncCmd := &cli.Command{
|
|
Use: "sync",
|
|
Short: i18n.T("cmd.dev.sync.short"),
|
|
Long: i18n.T("cmd.dev.sync.long"),
|
|
RunE: func(cmd *cli.Command, args []string) error {
|
|
if err := runSync(); err != nil {
|
|
return cli.Wrap(err, i18n.Label("error"))
|
|
}
|
|
cli.Text(i18n.T("i18n.done.sync", "public APIs"))
|
|
return nil
|
|
},
|
|
}
|
|
|
|
parent.AddCommand(syncCmd)
|
|
}
|
|
|
|
type symbolInfo struct {
|
|
Name string
|
|
Kind string // "var", "func", "type", "const"
|
|
}
|
|
|
|
func runSync() error {
|
|
pkgDir := "pkg"
|
|
internalDirs, err := coreio.Local.List(pkgDir)
|
|
if err != nil {
|
|
return cli.Wrap(err, "failed to read pkg directory")
|
|
}
|
|
|
|
for _, dir := range internalDirs {
|
|
if !dir.IsDir() || dir.Name() == "core" {
|
|
continue
|
|
}
|
|
|
|
serviceName := dir.Name()
|
|
internalFile := filepath.Join(pkgDir, serviceName, serviceName+".go")
|
|
publicDir := serviceName
|
|
publicFile := filepath.Join(publicDir, serviceName+".go")
|
|
|
|
if !coreio.Local.IsFile(internalFile) {
|
|
continue
|
|
}
|
|
|
|
symbols, err := getExportedSymbols(internalFile)
|
|
if err != nil {
|
|
return cli.Wrap(err, cli.Sprintf("error getting symbols for service '%s'", serviceName))
|
|
}
|
|
|
|
if err := generatePublicAPIFile(publicDir, publicFile, serviceName, symbols); err != nil {
|
|
return cli.Wrap(err, cli.Sprintf("error generating public API file for service '%s'", serviceName))
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func getExportedSymbols(path string) ([]symbolInfo, error) {
|
|
// ParseFile expects a filename/path and reads it using os.Open by default if content is nil.
|
|
// Since we want to use our Medium abstraction, we should read the file content first.
|
|
content, err := coreio.Local.Read(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
fset := token.NewFileSet()
|
|
// ParseFile can take content as string (src argument).
|
|
node, err := parser.ParseFile(fset, path, content, parser.ParseComments)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var symbols []symbolInfo
|
|
for name, obj := range node.Scope.Objects {
|
|
if ast.IsExported(name) {
|
|
kind := "unknown"
|
|
switch obj.Kind {
|
|
case ast.Con:
|
|
kind = "const"
|
|
case ast.Var:
|
|
kind = "var"
|
|
case ast.Fun:
|
|
kind = "func"
|
|
case ast.Typ:
|
|
kind = "type"
|
|
}
|
|
if kind != "unknown" {
|
|
symbols = append(symbols, symbolInfo{Name: name, Kind: kind})
|
|
}
|
|
}
|
|
}
|
|
return symbols, nil
|
|
}
|
|
|
|
const publicAPITemplate = `// package {{.ServiceName}} provides the public API for the {{.ServiceName}} service.
|
|
package {{.ServiceName}}
|
|
|
|
import (
|
|
// Import the internal implementation with an alias.
|
|
impl "github.com/host-uk/core/{{.ServiceName}}"
|
|
|
|
// Import the core contracts to re-export the interface.
|
|
"github.com/host-uk/core/core"
|
|
)
|
|
|
|
{{range .Symbols}}
|
|
{{- if eq .Kind "type"}}
|
|
// {{.Name}} is the public type for the {{.Name}} service. It is a type alias
|
|
// to the underlying implementation, making it transparent to the user.
|
|
type {{.Name}} = impl.{{.Name}}
|
|
{{else if eq .Kind "const"}}
|
|
// {{.Name}} is a public constant that points to the real constant in the implementation package.
|
|
const {{.Name}} = impl.{{.Name}}
|
|
{{else if eq .Kind "var"}}
|
|
// {{.Name}} is a public variable that points to the real variable in the implementation package.
|
|
var {{.Name}} = impl.{{.Name}}
|
|
{{else if eq .Kind "func"}}
|
|
// {{.Name}} is a public function that points to the real function in the implementation package.
|
|
var {{.Name}} = impl.{{.Name}}
|
|
{{end}}
|
|
{{end}}
|
|
|
|
// {{.InterfaceName}} is the public interface for the {{.ServiceName}} service.
|
|
type {{.InterfaceName}} = core.{{.InterfaceName}}
|
|
`
|
|
|
|
func generatePublicAPIFile(dir, path, serviceName string, symbols []symbolInfo) error {
|
|
if err := coreio.Local.EnsureDir(dir); err != nil {
|
|
return err
|
|
}
|
|
|
|
tmpl, err := template.New("publicAPI").Parse(publicAPITemplate)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
tcaser := cases.Title(language.English)
|
|
interfaceName := tcaser.String(serviceName)
|
|
|
|
data := struct {
|
|
ServiceName string
|
|
Symbols []symbolInfo
|
|
InterfaceName string
|
|
}{
|
|
ServiceName: serviceName,
|
|
Symbols: symbols,
|
|
InterfaceName: interfaceName,
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
if err := tmpl.Execute(&buf, data); err != nil {
|
|
return err
|
|
}
|
|
|
|
return coreio.Local.Write(path, buf.String())
|
|
}
|