feat: absorb core/ci commands + add reusable workflows
Merge core/ci repo into go-devops: - cmd/ci: release publish, init, changelog, version commands - cmd/sdk: API diff and OpenAPI validation commands Add reusable Forgejo Actions workflows: - security-scan.yml: govulncheck + gitleaks + trivy - go-test.yml: test with optional race/coverage - docker-publish.yml: build + push to registry Other repos can call these via: uses: core/go-devops/.forgejo/workflows/security-scan.yml@main Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
400d8a7690
commit
cd7e728280
6 changed files with 564 additions and 0 deletions
50
.forgejo/workflows/docker-publish.yml
Normal file
50
.forgejo/workflows/docker-publish.yml
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# Reusable Docker build and publish workflow
|
||||
# Usage: uses: core/go-devops/.forgejo/workflows/docker-publish.yml@main
|
||||
|
||||
name: Docker Publish
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
image:
|
||||
description: Image name (e.g. host-uk/app)
|
||||
type: string
|
||||
required: true
|
||||
dockerfile:
|
||||
description: Path to Dockerfile
|
||||
type: string
|
||||
default: Dockerfile
|
||||
context:
|
||||
description: Docker build context
|
||||
type: string
|
||||
default: '.'
|
||||
registry:
|
||||
description: Container registry
|
||||
type: string
|
||||
default: dappco.re/osi
|
||||
secrets:
|
||||
REGISTRY_USER:
|
||||
required: true
|
||||
REGISTRY_TOKEN:
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
build-push:
|
||||
name: Build & Push
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Login to registry
|
||||
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login ${{ inputs.registry }} -u ${{ secrets.REGISTRY_USER }} --password-stdin
|
||||
|
||||
- name: Build and push
|
||||
run: |
|
||||
SHA=$(git rev-parse --short HEAD)
|
||||
docker build \
|
||||
-f ${{ inputs.dockerfile }} \
|
||||
-t ${{ inputs.registry }}/${{ inputs.image }}:${SHA} \
|
||||
-t ${{ inputs.registry }}/${{ inputs.image }}:latest \
|
||||
${{ inputs.context }}
|
||||
docker push ${{ inputs.registry }}/${{ inputs.image }}:${SHA}
|
||||
docker push ${{ inputs.registry }}/${{ inputs.image }}:latest
|
||||
45
.forgejo/workflows/go-test.yml
Normal file
45
.forgejo/workflows/go-test.yml
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# Reusable Go test workflow
|
||||
# Usage: uses: core/go-devops/.forgejo/workflows/go-test.yml@main
|
||||
|
||||
name: Go Test
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
go-version:
|
||||
description: Go version to use
|
||||
type: string
|
||||
default: '1.26'
|
||||
race:
|
||||
description: Enable race detector
|
||||
type: boolean
|
||||
default: false
|
||||
coverage:
|
||||
description: Generate coverage report
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ inputs.go-version }}
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
FLAGS="-v"
|
||||
if [ "${{ inputs.race }}" = "true" ]; then
|
||||
FLAGS="$FLAGS -race"
|
||||
fi
|
||||
if [ "${{ inputs.coverage }}" = "true" ]; then
|
||||
FLAGS="$FLAGS -coverprofile=coverage.out"
|
||||
fi
|
||||
go test $FLAGS ./...
|
||||
|
||||
- name: Coverage summary
|
||||
if: inputs.coverage
|
||||
run: go tool cover -func=coverage.out | tail -1
|
||||
70
.forgejo/workflows/security-scan.yml
Normal file
70
.forgejo/workflows/security-scan.yml
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# Reusable security scanning workflow for Go repos
|
||||
# Usage: uses: core/go-devops/.forgejo/workflows/security-scan.yml@main
|
||||
#
|
||||
# Runs: govulncheck, gitleaks, trivy
|
||||
|
||||
name: Security Scan
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
go-version:
|
||||
description: Go version to use
|
||||
type: string
|
||||
default: '1.26'
|
||||
trivy-severity:
|
||||
description: Trivy severity threshold
|
||||
type: string
|
||||
default: 'HIGH,CRITICAL'
|
||||
gitleaks-version:
|
||||
description: Gitleaks version
|
||||
type: string
|
||||
default: '8.24.3'
|
||||
|
||||
jobs:
|
||||
govulncheck:
|
||||
name: Go Vulnerability Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ inputs.go-version }}
|
||||
- name: Install govulncheck
|
||||
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
- name: Run govulncheck
|
||||
run: govulncheck ./...
|
||||
|
||||
gitleaks:
|
||||
name: Secret Detection
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Install gitleaks
|
||||
run: |
|
||||
set -euo pipefail
|
||||
GITLEAKS_VERSION="${{ inputs.gitleaks-version }}"
|
||||
ARCH=$(uname -m)
|
||||
case "$ARCH" in
|
||||
x86_64) ARCH_SUFFIX="x64" ;;
|
||||
aarch64) ARCH_SUFFIX="arm64" ;;
|
||||
*) echo "Unsupported arch: $ARCH"; exit 1 ;;
|
||||
esac
|
||||
URL="https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_${ARCH_SUFFIX}.tar.gz"
|
||||
curl -fsSL "$URL" | tar xz -C /usr/local/bin gitleaks
|
||||
gitleaks version
|
||||
- name: Scan for secrets
|
||||
run: gitleaks detect --source . --no-banner
|
||||
|
||||
trivy:
|
||||
name: Dependency & Config Scan
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Install Trivy
|
||||
run: |
|
||||
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin
|
||||
- name: Filesystem scan
|
||||
run: trivy fs --scanners vuln,secret,misconfig --severity ${{ inputs.trivy-severity }} --exit-code 1 .
|
||||
239
cmd/ci/ci.go
Normal file
239
cmd/ci/ci.go
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
package ci
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"forge.lthn.ai/core/go/pkg/cli"
|
||||
"forge.lthn.ai/core/go/pkg/i18n"
|
||||
"forge.lthn.ai/core/go-devops/release"
|
||||
)
|
||||
|
||||
// Style aliases from shared
|
||||
var (
|
||||
headerStyle = cli.RepoStyle
|
||||
successStyle = cli.SuccessStyle
|
||||
errorStyle = cli.ErrorStyle
|
||||
dimStyle = cli.DimStyle
|
||||
valueStyle = cli.ValueStyle
|
||||
)
|
||||
|
||||
// Flag variables for ci command
|
||||
var (
|
||||
ciGoForLaunch bool
|
||||
ciVersion string
|
||||
ciDraft bool
|
||||
ciPrerelease bool
|
||||
)
|
||||
|
||||
// Flag variables for changelog subcommand
|
||||
var (
|
||||
changelogFromRef string
|
||||
changelogToRef string
|
||||
)
|
||||
|
||||
var ciCmd = &cli.Command{
|
||||
Use: "ci",
|
||||
Short: i18n.T("cmd.ci.short"),
|
||||
Long: i18n.T("cmd.ci.long"),
|
||||
RunE: func(cmd *cli.Command, args []string) error {
|
||||
dryRun := !ciGoForLaunch
|
||||
return runCIPublish(dryRun, ciVersion, ciDraft, ciPrerelease)
|
||||
},
|
||||
}
|
||||
|
||||
var ciInitCmd = &cli.Command{
|
||||
Use: "init",
|
||||
Short: i18n.T("cmd.ci.init.short"),
|
||||
Long: i18n.T("cmd.ci.init.long"),
|
||||
RunE: func(cmd *cli.Command, args []string) error {
|
||||
return runCIReleaseInit()
|
||||
},
|
||||
}
|
||||
|
||||
var ciChangelogCmd = &cli.Command{
|
||||
Use: "changelog",
|
||||
Short: i18n.T("cmd.ci.changelog.short"),
|
||||
Long: i18n.T("cmd.ci.changelog.long"),
|
||||
RunE: func(cmd *cli.Command, args []string) error {
|
||||
return runChangelog(changelogFromRef, changelogToRef)
|
||||
},
|
||||
}
|
||||
|
||||
var ciVersionCmd = &cli.Command{
|
||||
Use: "version",
|
||||
Short: i18n.T("cmd.ci.version.short"),
|
||||
Long: i18n.T("cmd.ci.version.long"),
|
||||
RunE: func(cmd *cli.Command, args []string) error {
|
||||
return runCIReleaseVersion()
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
// Main ci command flags
|
||||
ciCmd.Flags().BoolVar(&ciGoForLaunch, "we-are-go-for-launch", false, i18n.T("cmd.ci.flag.go_for_launch"))
|
||||
ciCmd.Flags().StringVar(&ciVersion, "version", "", i18n.T("cmd.ci.flag.version"))
|
||||
ciCmd.Flags().BoolVar(&ciDraft, "draft", false, i18n.T("cmd.ci.flag.draft"))
|
||||
ciCmd.Flags().BoolVar(&ciPrerelease, "prerelease", false, i18n.T("cmd.ci.flag.prerelease"))
|
||||
|
||||
// Changelog subcommand flags
|
||||
ciChangelogCmd.Flags().StringVar(&changelogFromRef, "from", "", i18n.T("cmd.ci.changelog.flag.from"))
|
||||
ciChangelogCmd.Flags().StringVar(&changelogToRef, "to", "", i18n.T("cmd.ci.changelog.flag.to"))
|
||||
|
||||
// Add subcommands
|
||||
ciCmd.AddCommand(ciInitCmd)
|
||||
ciCmd.AddCommand(ciChangelogCmd)
|
||||
ciCmd.AddCommand(ciVersionCmd)
|
||||
}
|
||||
|
||||
// runCIPublish publishes pre-built artifacts from dist/.
|
||||
func runCIPublish(dryRun bool, version string, draft, prerelease bool) error {
|
||||
ctx := context.Background()
|
||||
|
||||
projectDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return cli.WrapVerb(err, "get", "working directory")
|
||||
}
|
||||
|
||||
cfg, err := release.LoadConfig(projectDir)
|
||||
if err != nil {
|
||||
return cli.WrapVerb(err, "load", "config")
|
||||
}
|
||||
|
||||
if version != "" {
|
||||
cfg.SetVersion(version)
|
||||
}
|
||||
|
||||
if draft || prerelease {
|
||||
for i := range cfg.Publishers {
|
||||
if draft {
|
||||
cfg.Publishers[i].Draft = true
|
||||
}
|
||||
if prerelease {
|
||||
cfg.Publishers[i].Prerelease = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cli.Print("%s %s\n", headerStyle.Render(i18n.T("cmd.ci.label.ci")), i18n.T("cmd.ci.publishing"))
|
||||
if dryRun {
|
||||
cli.Print(" %s\n", dimStyle.Render(i18n.T("cmd.ci.dry_run_hint")))
|
||||
} else {
|
||||
cli.Print(" %s\n", successStyle.Render(i18n.T("cmd.ci.go_for_launch")))
|
||||
}
|
||||
cli.Blank()
|
||||
|
||||
if len(cfg.Publishers) == 0 {
|
||||
return errors.New(i18n.T("cmd.ci.error.no_publishers"))
|
||||
}
|
||||
|
||||
rel, err := release.Publish(ctx, cfg, dryRun)
|
||||
if err != nil {
|
||||
cli.Print("%s %v\n", errorStyle.Render(i18n.Label("error")), err)
|
||||
return err
|
||||
}
|
||||
|
||||
cli.Blank()
|
||||
cli.Print("%s %s\n", successStyle.Render(i18n.T("i18n.done.pass")), i18n.T("cmd.ci.publish_completed"))
|
||||
cli.Print(" %s %s\n", i18n.Label("version"), valueStyle.Render(rel.Version))
|
||||
cli.Print(" %s %d\n", i18n.T("cmd.ci.label.artifacts"), len(rel.Artifacts))
|
||||
|
||||
if !dryRun {
|
||||
for _, pub := range cfg.Publishers {
|
||||
cli.Print(" %s %s\n", i18n.T("cmd.ci.label.published"), valueStyle.Render(pub.Type))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// runCIReleaseInit scaffolds a release config.
|
||||
func runCIReleaseInit() error {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
||||
}
|
||||
|
||||
cli.Print("%s %s\n\n", dimStyle.Render(i18n.Label("init")), i18n.T("cmd.ci.init.initializing"))
|
||||
|
||||
if release.ConfigExists(cwd) {
|
||||
cli.Text(i18n.T("cmd.ci.init.already_initialized"))
|
||||
return nil
|
||||
}
|
||||
|
||||
cfg := release.DefaultConfig()
|
||||
if err := release.WriteConfig(cfg, cwd); err != nil {
|
||||
return cli.Err("%s: %w", i18n.T("i18n.fail.create", "config"), err)
|
||||
}
|
||||
|
||||
cli.Blank()
|
||||
cli.Print("%s %s\n", successStyle.Render("v"), i18n.T("cmd.ci.init.created_config"))
|
||||
cli.Blank()
|
||||
cli.Text(i18n.T("cmd.ci.init.next_steps"))
|
||||
cli.Print(" %s\n", i18n.T("cmd.ci.init.edit_config"))
|
||||
cli.Print(" %s\n", i18n.T("cmd.ci.init.run_ci"))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// runChangelog generates a changelog between two git refs.
|
||||
func runChangelog(fromRef, toRef string) error {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return cli.Err("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
||||
}
|
||||
|
||||
if fromRef == "" || toRef == "" {
|
||||
tag, err := latestTag(cwd)
|
||||
if err == nil {
|
||||
if fromRef == "" {
|
||||
fromRef = tag
|
||||
}
|
||||
if toRef == "" {
|
||||
toRef = "HEAD"
|
||||
}
|
||||
} else {
|
||||
cli.Text(i18n.T("cmd.ci.changelog.no_tags"))
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
cli.Print("%s %s..%s\n\n", dimStyle.Render(i18n.T("cmd.ci.changelog.generating")), fromRef, toRef)
|
||||
|
||||
changelog, err := release.Generate(cwd, fromRef, toRef)
|
||||
if err != nil {
|
||||
return cli.Err("%s: %w", i18n.T("i18n.fail.generate", "changelog"), err)
|
||||
}
|
||||
|
||||
cli.Text(changelog)
|
||||
return nil
|
||||
}
|
||||
|
||||
// runCIReleaseVersion shows the determined version.
|
||||
func runCIReleaseVersion() error {
|
||||
projectDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return cli.WrapVerb(err, "get", "working directory")
|
||||
}
|
||||
|
||||
version, err := release.DetermineVersion(projectDir)
|
||||
if err != nil {
|
||||
return cli.WrapVerb(err, "determine", "version")
|
||||
}
|
||||
|
||||
cli.Print("%s %s\n", i18n.Label("version"), valueStyle.Render(version))
|
||||
return nil
|
||||
}
|
||||
|
||||
func latestTag(dir string) (string, error) {
|
||||
cmd := exec.Command("git", "describe", "--tags", "--abbrev=0")
|
||||
cmd.Dir = dir
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(string(out)), nil
|
||||
}
|
||||
23
cmd/ci/cmd.go
Normal file
23
cmd/ci/cmd.go
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// Package ci provides release lifecycle commands for CI/CD pipelines.
|
||||
//
|
||||
// Commands:
|
||||
// - ci init: scaffold release config
|
||||
// - ci changelog: generate changelog from git history
|
||||
// - ci version: show determined version
|
||||
// - ci publish: publish pre-built artifacts (dry-run by default)
|
||||
//
|
||||
// Configuration via .core/release.yaml.
|
||||
package ci
|
||||
|
||||
import (
|
||||
"forge.lthn.ai/core/go/pkg/cli"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cli.RegisterCommands(AddCICommands)
|
||||
}
|
||||
|
||||
// AddCICommands registers the 'ci' command and all subcommands.
|
||||
func AddCICommands(root *cli.Command) {
|
||||
root.AddCommand(ciCmd)
|
||||
}
|
||||
137
cmd/sdk/cmd.go
Normal file
137
cmd/sdk/cmd.go
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
// Package sdkcmd provides SDK validation and API compatibility commands.
|
||||
//
|
||||
// Commands:
|
||||
// - sdk diff: check for breaking API changes between spec versions
|
||||
// - sdk validate: validate OpenAPI spec syntax
|
||||
//
|
||||
// For SDK generation, use: core build sdk
|
||||
package sdkcmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"forge.lthn.ai/core/go-devops/sdk"
|
||||
"forge.lthn.ai/core/go/pkg/cli"
|
||||
"forge.lthn.ai/core/go/pkg/i18n"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
cli.RegisterCommands(AddSDKCommands)
|
||||
}
|
||||
|
||||
// SDK styles (aliases to shared)
|
||||
var (
|
||||
sdkHeaderStyle = cli.TitleStyle
|
||||
sdkSuccessStyle = cli.SuccessStyle
|
||||
sdkErrorStyle = cli.ErrorStyle
|
||||
sdkDimStyle = cli.DimStyle
|
||||
)
|
||||
|
||||
var sdkCmd = &cobra.Command{
|
||||
Use: "sdk",
|
||||
Short: i18n.T("cmd.sdk.short"),
|
||||
Long: i18n.T("cmd.sdk.long"),
|
||||
}
|
||||
|
||||
var diffBasePath string
|
||||
var diffSpecPath string
|
||||
|
||||
var sdkDiffCmd = &cobra.Command{
|
||||
Use: "diff",
|
||||
Short: i18n.T("cmd.sdk.diff.short"),
|
||||
Long: i18n.T("cmd.sdk.diff.long"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runSDKDiff(diffBasePath, diffSpecPath)
|
||||
},
|
||||
}
|
||||
|
||||
var validateSpecPath string
|
||||
|
||||
var sdkValidateCmd = &cobra.Command{
|
||||
Use: "validate",
|
||||
Short: i18n.T("cmd.sdk.validate.short"),
|
||||
Long: i18n.T("cmd.sdk.validate.long"),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
return runSDKValidate(validateSpecPath)
|
||||
},
|
||||
}
|
||||
|
||||
// AddSDKCommands registers the 'sdk' command and all subcommands.
|
||||
func AddSDKCommands(root *cobra.Command) {
|
||||
// sdk diff flags
|
||||
sdkDiffCmd.Flags().StringVar(&diffBasePath, "base", "", i18n.T("cmd.sdk.diff.flag.base"))
|
||||
sdkDiffCmd.Flags().StringVar(&diffSpecPath, "spec", "", i18n.T("cmd.sdk.diff.flag.spec"))
|
||||
|
||||
// sdk validate flags
|
||||
sdkValidateCmd.Flags().StringVar(&validateSpecPath, "spec", "", i18n.T("common.flag.spec"))
|
||||
|
||||
// Add subcommands
|
||||
sdkCmd.AddCommand(sdkDiffCmd)
|
||||
sdkCmd.AddCommand(sdkValidateCmd)
|
||||
|
||||
root.AddCommand(sdkCmd)
|
||||
}
|
||||
|
||||
func runSDKDiff(basePath, specPath string) error {
|
||||
projectDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
||||
}
|
||||
|
||||
if specPath == "" {
|
||||
s := sdk.New(projectDir, nil)
|
||||
specPath, err = s.DetectSpec()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if basePath == "" {
|
||||
return errors.New(i18n.T("cmd.sdk.diff.error.base_required"))
|
||||
}
|
||||
|
||||
fmt.Printf("%s %s\n", sdkHeaderStyle.Render(i18n.T("cmd.sdk.diff.label")), i18n.ProgressSubject("check", "breaking changes"))
|
||||
fmt.Printf(" %s %s\n", i18n.T("cmd.sdk.diff.base_label"), sdkDimStyle.Render(basePath))
|
||||
fmt.Printf(" %s %s\n", i18n.Label("current"), sdkDimStyle.Render(specPath))
|
||||
fmt.Println()
|
||||
|
||||
result, err := sdk.Diff(basePath, specPath)
|
||||
if err != nil {
|
||||
return cli.Exit(2, cli.Wrap(err, i18n.Label("error")))
|
||||
}
|
||||
|
||||
if result.Breaking {
|
||||
fmt.Printf("%s %s\n", sdkErrorStyle.Render(i18n.T("cmd.sdk.diff.breaking")), result.Summary)
|
||||
for _, change := range result.Changes {
|
||||
fmt.Printf(" - %s\n", change)
|
||||
}
|
||||
return cli.Exit(1, cli.Err("%s", result.Summary))
|
||||
}
|
||||
|
||||
fmt.Printf("%s %s\n", sdkSuccessStyle.Render(i18n.T("cmd.sdk.label.ok")), result.Summary)
|
||||
return nil
|
||||
}
|
||||
|
||||
func runSDKValidate(specPath string) error {
|
||||
projectDir, err := os.Getwd()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", i18n.T("i18n.fail.get", "working directory"), err)
|
||||
}
|
||||
|
||||
s := sdk.New(projectDir, &sdk.Config{Spec: specPath})
|
||||
|
||||
fmt.Printf("%s %s\n", sdkHeaderStyle.Render(i18n.T("cmd.sdk.label.sdk")), i18n.T("cmd.sdk.validate.validating"))
|
||||
|
||||
detectedPath, err := s.DetectSpec()
|
||||
if err != nil {
|
||||
fmt.Printf("%s %v\n", sdkErrorStyle.Render(i18n.Label("error")), err)
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Printf(" %s %s\n", i18n.Label("spec"), sdkDimStyle.Render(detectedPath))
|
||||
fmt.Printf("%s %s\n", sdkSuccessStyle.Render(i18n.T("cmd.sdk.label.ok")), i18n.T("cmd.sdk.validate.valid"))
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue