mirror of
https://github.com/volcengine/mediakit-cli.git
synced 2026-09-14 20:06:30 +08:00
feat: skill 检查更新
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
package commands
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestRootCommandDoesNotExposeSkillsCommand(t *testing.T) {
|
||||
cmd := newRootCmd()
|
||||
for _, child := range cmd.Commands() {
|
||||
if child.Name() == "skills" {
|
||||
t.Fatal("root command exposes deprecated skills command")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,8 @@ import (
|
||||
"time"
|
||||
|
||||
buildinfo "mediakit-cli/internal/build"
|
||||
cliconfig "mediakit-cli/internal/config"
|
||||
"mediakit-cli/internal/skillstate"
|
||||
"mediakit-cli/internal/updatecheck"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@@ -54,6 +56,7 @@ func newUpdateCmd() *cobra.Command {
|
||||
payload["upgrade_command"] = "mediakit-cli update"
|
||||
if checkOnly {
|
||||
payload["action"] = "check"
|
||||
applySkillsStatus(payload, r.Current)
|
||||
if !asJSON {
|
||||
return writeUpdateCheckText(cmd, r)
|
||||
}
|
||||
@@ -78,11 +81,8 @@ func newUpdateCmd() *cobra.Command {
|
||||
payload["action"] = "noop"
|
||||
}
|
||||
|
||||
shouldInstallSkills := !r.HasUpdate && force
|
||||
shouldInstallSkills := !r.HasUpdate && shouldInstallSkillsForVersion(r.Current, force)
|
||||
if shouldInstallSkills {
|
||||
if !asJSON {
|
||||
fmt.Fprintln(cmd.OutOrStdout(), "\nInstalling skills from the current npm package ...")
|
||||
}
|
||||
if err := runSkillsInstall(cmd); err != nil {
|
||||
payload["skills_action"] = "failed"
|
||||
payload["skills_error"] = err.Error()
|
||||
@@ -132,7 +132,8 @@ func writeUpdateText(cmd *cobra.Command, r *updatecheck.Result, skillsInstalled
|
||||
fmt.Fprintf(cmd.OutOrStdout(), "mediakit-cli %s is already up to date\n", r.Current)
|
||||
}
|
||||
if skillsInstalled {
|
||||
_, err := fmt.Fprintln(cmd.OutOrStdout(), "✓ Skills installed from the current npm package")
|
||||
fmt.Fprintln(cmd.OutOrStdout(), "\nUpdating skills ...")
|
||||
_, err := fmt.Fprintln(cmd.OutOrStdout(), "✓ Skills updated")
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -147,12 +148,49 @@ func runNpmInstallLatest(cmd *cobra.Command) error {
|
||||
}
|
||||
|
||||
func runSkillsInstallFromPackage(cmd *cobra.Command) error {
|
||||
c := exec.Command("npx", "-y", updatecheck.PackageName, "install", "--skills-only", "-y")
|
||||
c := exec.Command("npx", "-y", currentPackageSpec(), "install", "--skills-only", "-y")
|
||||
c.Stdout = os.Stderr
|
||||
c.Stderr = os.Stderr
|
||||
return c.Run()
|
||||
}
|
||||
|
||||
func currentPackageSpec() string {
|
||||
version := normalizeVersion(buildinfo.Version)
|
||||
if version == "" {
|
||||
return updatecheck.PackageName
|
||||
}
|
||||
return fmt.Sprintf("%s@%s", updatecheck.PackageName, version)
|
||||
}
|
||||
|
||||
func shouldInstallSkillsForVersion(version string, force bool) bool {
|
||||
if force {
|
||||
return true
|
||||
}
|
||||
home, err := cliconfig.ResolveHomeDir()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return !skillstate.InSync(home, version)
|
||||
}
|
||||
|
||||
func applySkillsStatus(payload map[string]any, target string) {
|
||||
home, err := cliconfig.ResolveHomeDir()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
status, err := skillstate.ReadStatus(home, target)
|
||||
if err != nil || status == nil {
|
||||
return
|
||||
}
|
||||
payload["skills_status"] = map[string]any{
|
||||
"current": status.Current,
|
||||
"target": status.Target,
|
||||
"in_sync": status.InSync,
|
||||
"missing": status.Missing,
|
||||
"command": status.Command,
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeVersion(version string) string {
|
||||
version = strings.TrimSpace(version)
|
||||
version = strings.TrimPrefix(version, "v")
|
||||
|
||||
@@ -0,0 +1,390 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
buildinfo "mediakit-cli/internal/build"
|
||||
"mediakit-cli/internal/skillstate"
|
||||
"mediakit-cli/internal/updatecheck"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func TestUpdateCheckDoesNotInstallOrSyncSkills(t *testing.T) {
|
||||
restore := stubUpdateDeps(t, &updatecheck.Result{Current: "0.1.7", Latest: "0.1.8", HasUpdate: true})
|
||||
defer restore()
|
||||
|
||||
var npmCalled bool
|
||||
runNpmInstall = func(*cobra.Command) error {
|
||||
npmCalled = true
|
||||
return nil
|
||||
}
|
||||
var skillsCalled bool
|
||||
runSkillsInstall = func(*cobra.Command) error {
|
||||
skillsCalled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd := newUpdateCmd()
|
||||
var out bytes.Buffer
|
||||
cmd.SetOut(&out)
|
||||
cmd.SetErr(&bytes.Buffer{})
|
||||
cmd.SetArgs([]string{"--check", "--json"})
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if npmCalled {
|
||||
t.Fatal("npm install called during update --check")
|
||||
}
|
||||
if skillsCalled {
|
||||
t.Fatal("skills install called during update --check")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateCheckJsonIncludesSkillsStatus(t *testing.T) {
|
||||
restore := stubUpdateDeps(t, &updatecheck.Result{Current: "0.1.7", Latest: "0.1.7", HasUpdate: false})
|
||||
defer restore()
|
||||
home := mustHome(t)
|
||||
if err := skillstate.Save(home, &skillstate.State{
|
||||
PackageName: "@volcengine/mediakit-cli",
|
||||
Version: "0.1.6",
|
||||
SkillsDir: "/tmp/old-skills",
|
||||
InstalledAt: time.Date(
|
||||
2026, 7, 3, 12, 0, 0, 0, time.UTC,
|
||||
),
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cmd := newUpdateCmd()
|
||||
var out bytes.Buffer
|
||||
cmd.SetOut(&out)
|
||||
cmd.SetErr(&bytes.Buffer{})
|
||||
cmd.SetArgs([]string{"--check", "--json"})
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var payload map[string]any
|
||||
if err := json.Unmarshal(out.Bytes(), &payload); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
status, ok := payload["skills_status"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("payload missing skills_status: %#v", payload)
|
||||
}
|
||||
if status["current"] != "0.1.6" || status["target"] != "0.1.7" || status["in_sync"] != false {
|
||||
t.Fatalf("skills_status = %#v", status)
|
||||
}
|
||||
if status["command"] != "mediakit-cli update --force" {
|
||||
t.Fatalf("skills_status = %#v", status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateCheckDefaultOutputMatchesHumanNotice(t *testing.T) {
|
||||
restore := stubUpdateDeps(t, &updatecheck.Result{Current: "0.1.7", Latest: "0.1.8", HasUpdate: true})
|
||||
defer restore()
|
||||
|
||||
cmd := newUpdateCmd()
|
||||
var out bytes.Buffer
|
||||
cmd.SetOut(&out)
|
||||
cmd.SetErr(&bytes.Buffer{})
|
||||
cmd.SetArgs([]string{"--check"})
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := out.String()
|
||||
if !strings.Contains(got, "Update available: 0.1.7 → 0.1.8") {
|
||||
t.Fatalf("output = %q", got)
|
||||
}
|
||||
if !strings.Contains(got, "Run `mediakit-cli update` to install.") {
|
||||
t.Fatalf("output = %q", got)
|
||||
}
|
||||
if strings.Contains(got, "{") {
|
||||
t.Fatalf("default check output should not be JSON: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateDoesNotInstallSkillsWhenAlreadyLatest(t *testing.T) {
|
||||
restore := stubUpdateDeps(t, &updatecheck.Result{Current: "0.1.7", Latest: "0.1.7", HasUpdate: false})
|
||||
defer restore()
|
||||
home := mustHome(t)
|
||||
if err := skillstate.Save(home, &skillstate.State{
|
||||
PackageName: "@volcengine/mediakit-cli",
|
||||
Version: "0.1.7",
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var skillsCalled bool
|
||||
runSkillsInstall = func(*cobra.Command) error {
|
||||
skillsCalled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd := newUpdateCmd()
|
||||
var out bytes.Buffer
|
||||
cmd.SetOut(&out)
|
||||
cmd.SetErr(&bytes.Buffer{})
|
||||
cmd.SetArgs([]string{"--json"})
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var payload map[string]any
|
||||
if err := json.Unmarshal(out.Bytes(), &payload); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, ok := payload["skills_action"]; ok {
|
||||
t.Fatalf("payload = %#v", payload)
|
||||
}
|
||||
if skillsCalled {
|
||||
t.Fatal("skills install called when CLI is already latest")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateInstallsSkillsWhenAlreadyLatestButSkillsOutOfSync(t *testing.T) {
|
||||
restore := stubUpdateDeps(t, &updatecheck.Result{Current: "0.1.7", Latest: "0.1.7", HasUpdate: false})
|
||||
defer restore()
|
||||
home := mustHome(t)
|
||||
if err := skillstate.Save(home, &skillstate.State{
|
||||
PackageName: "@volcengine/mediakit-cli",
|
||||
Version: "0.1.6",
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var skillsCalled bool
|
||||
runSkillsInstall = func(*cobra.Command) error {
|
||||
skillsCalled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd := newUpdateCmd()
|
||||
var out bytes.Buffer
|
||||
cmd.SetOut(&out)
|
||||
cmd.SetErr(&bytes.Buffer{})
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := out.String()
|
||||
if !strings.Contains(got, "mediakit-cli 0.1.7 is already up to date") {
|
||||
t.Fatalf("output = %q", got)
|
||||
}
|
||||
if !strings.Contains(got, "Updating skills ...") || !strings.Contains(got, "✓ Skills updated") {
|
||||
t.Fatalf("output = %q", got)
|
||||
}
|
||||
if !skillsCalled {
|
||||
t.Fatal("skills install not called when skills are out of sync")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateForceInstallsSkillsWhenAlreadyLatest(t *testing.T) {
|
||||
restore := stubUpdateDeps(t, &updatecheck.Result{Current: "0.1.7", Latest: "0.1.7", HasUpdate: false})
|
||||
defer restore()
|
||||
var skillsCalled bool
|
||||
runSkillsInstall = func(*cobra.Command) error {
|
||||
skillsCalled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd := newUpdateCmd()
|
||||
var out bytes.Buffer
|
||||
cmd.SetOut(&out)
|
||||
cmd.SetErr(&bytes.Buffer{})
|
||||
cmd.SetArgs([]string{"--force"})
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := out.String()
|
||||
if !strings.Contains(got, "mediakit-cli 0.1.7 is already up to date") {
|
||||
t.Fatalf("output = %q", got)
|
||||
}
|
||||
if !strings.Contains(got, "Updating skills ...") || !strings.Contains(got, "✓ Skills updated") {
|
||||
t.Fatalf("output = %q", got)
|
||||
}
|
||||
if strings.Contains(got, "{") {
|
||||
t.Fatalf("default output should not be JSON: %q", got)
|
||||
}
|
||||
if !skillsCalled {
|
||||
t.Fatal("skills install not called under update --force")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCurrentPackageSpecUsesRunningVersionForSkillsReinstall(t *testing.T) {
|
||||
oldVersion := buildinfo.Version
|
||||
buildinfo.Version = "0.1.8-beta.2"
|
||||
t.Cleanup(func() {
|
||||
buildinfo.Version = oldVersion
|
||||
})
|
||||
|
||||
if got, want := currentPackageSpec(), "@volcengine/mediakit-cli@0.1.8-beta.2"; got != want {
|
||||
t.Fatalf("currentPackageSpec() = %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateReliesOnNpmPostinstallForSkillsAfterNpmUpdate(t *testing.T) {
|
||||
restore := stubUpdateDeps(t, &updatecheck.Result{Current: "0.1.7", Latest: "0.1.8", HasUpdate: true})
|
||||
defer restore()
|
||||
|
||||
var calls []string
|
||||
runNpmInstall = func(*cobra.Command) error {
|
||||
calls = append(calls, "npm")
|
||||
return nil
|
||||
}
|
||||
runSkillsInstall = func(*cobra.Command) error {
|
||||
calls = append(calls, "skills")
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd := newUpdateCmd()
|
||||
var out bytes.Buffer
|
||||
cmd.SetOut(&out)
|
||||
cmd.SetErr(&bytes.Buffer{})
|
||||
cmd.SetArgs([]string{"--json"})
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, want := strings.Join(calls, ","), "npm"; got != want {
|
||||
t.Fatalf("calls = %q, want %q", got, want)
|
||||
}
|
||||
var payload map[string]any
|
||||
if err := json.Unmarshal(out.Bytes(), &payload); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, ok := payload["skills_action"]; ok {
|
||||
t.Fatalf("payload = %#v", payload)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateDefaultOutputSeparatesCliAndSkillsStatus(t *testing.T) {
|
||||
restore := stubUpdateDeps(t, &updatecheck.Result{Current: "0.1.7", Latest: "0.1.8", HasUpdate: true})
|
||||
defer restore()
|
||||
|
||||
cmd := newUpdateCmd()
|
||||
var out bytes.Buffer
|
||||
cmd.SetOut(&out)
|
||||
cmd.SetErr(&bytes.Buffer{})
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := out.String()
|
||||
for _, want := range []string{
|
||||
"Updating mediakit-cli 0.1.7 → 0.1.8 via npm ...",
|
||||
"✓ Successfully updated mediakit-cli from 0.1.7 to 0.1.8",
|
||||
"Updating skills ...",
|
||||
"✓ Skills updated",
|
||||
} {
|
||||
if !strings.Contains(got, want) {
|
||||
t.Fatalf("output missing %q:\n%s", want, got)
|
||||
}
|
||||
}
|
||||
if strings.Contains(got, "Changelog:") {
|
||||
t.Fatalf("output should not include changelog:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateDoesNotRunSkillsInstallAfterNpmUpdate(t *testing.T) {
|
||||
restore := stubUpdateDeps(t, &updatecheck.Result{Current: "0.1.7", Latest: "0.1.8", HasUpdate: true})
|
||||
defer restore()
|
||||
|
||||
runSkillsInstall = func(*cobra.Command) error { return errors.New("skills should not run") }
|
||||
|
||||
cmd := newUpdateCmd()
|
||||
cmd.SetOut(&bytes.Buffer{})
|
||||
cmd.SetErr(&bytes.Buffer{})
|
||||
cmd.SetArgs([]string{"--json"})
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInjectNoticeIncludesSkillsNoticeWhenOutOfSync(t *testing.T) {
|
||||
restore := stubUpdateDeps(t, &updatecheck.Result{Current: "0.1.7", Latest: "0.1.7", HasUpdate: false})
|
||||
defer restore()
|
||||
home := mustHome(t)
|
||||
if err := skillstate.Save(home, &skillstate.State{
|
||||
PackageName: "@volcengine/mediakit-cli",
|
||||
Version: "0.1.6",
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
payload := map[string]any{"ok": true}
|
||||
updatecheck.InjectNotice(payload)
|
||||
|
||||
notice, ok := payload["_notice"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("payload missing _notice: %#v", payload)
|
||||
}
|
||||
skills, ok := notice["skills"].(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("notice missing skills: %#v", notice)
|
||||
}
|
||||
if skills["current"] != "0.1.6" || skills["target"] != buildinfo.Version {
|
||||
t.Fatalf("skills notice = %#v", skills)
|
||||
}
|
||||
if skills["command"] != "mediakit-cli update --force" {
|
||||
t.Fatalf("skills notice = %#v", skills)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateDoesNotSyncWhenNpmInstallFails(t *testing.T) {
|
||||
restore := stubUpdateDeps(t, &updatecheck.Result{Current: "0.1.7", Latest: "0.1.8", HasUpdate: true})
|
||||
defer restore()
|
||||
|
||||
runNpmInstall = func(*cobra.Command) error { return errors.New("npm failed") }
|
||||
var skillsCalled bool
|
||||
runSkillsInstall = func(*cobra.Command) error {
|
||||
skillsCalled = true
|
||||
return nil
|
||||
}
|
||||
|
||||
cmd := newUpdateCmd()
|
||||
cmd.SetOut(&bytes.Buffer{})
|
||||
cmd.SetErr(&bytes.Buffer{})
|
||||
|
||||
if err := cmd.Execute(); err == nil {
|
||||
t.Fatal("Execute returned nil error, want npm failure")
|
||||
}
|
||||
if skillsCalled {
|
||||
t.Fatal("skills install called after npm failure")
|
||||
}
|
||||
}
|
||||
|
||||
func stubUpdateDeps(t *testing.T, result *updatecheck.Result) func() {
|
||||
t.Helper()
|
||||
t.Setenv("HOME", t.TempDir())
|
||||
oldCheck := checkNow
|
||||
oldNpm := runNpmInstall
|
||||
oldSkills := runSkillsInstall
|
||||
checkNow = func() *updatecheck.Result { return result }
|
||||
runNpmInstall = func(*cobra.Command) error { return nil }
|
||||
runSkillsInstall = func(*cobra.Command) error { return nil }
|
||||
return func() {
|
||||
checkNow = oldCheck
|
||||
runNpmInstall = oldNpm
|
||||
runSkillsInstall = oldSkills
|
||||
}
|
||||
}
|
||||
|
||||
func mustHome(t *testing.T) string {
|
||||
t.Helper()
|
||||
home := os.Getenv("HOME")
|
||||
if home == "" {
|
||||
t.Fatal("HOME not set")
|
||||
}
|
||||
return filepath.Clean(home)
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package skillstate
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
cliconfig "mediakit-cli/internal/config"
|
||||
)
|
||||
|
||||
const (
|
||||
FileName = "skills-state.json"
|
||||
PackageName = "@volcengine/mediakit-cli"
|
||||
)
|
||||
|
||||
type State struct {
|
||||
PackageName string `json:"package_name"`
|
||||
Version string `json:"version"`
|
||||
SkillsDir string `json:"skills_dir,omitempty"`
|
||||
InstalledAt time.Time `json:"installed_at,omitempty"`
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
Current string `json:"current,omitempty"`
|
||||
Target string `json:"target"`
|
||||
InSync bool `json:"in_sync"`
|
||||
Command string `json:"command"`
|
||||
Missing bool `json:"missing,omitempty"`
|
||||
}
|
||||
|
||||
func File(home string) string {
|
||||
return filepath.Join(cliconfig.ConfigDir(home), FileName)
|
||||
}
|
||||
|
||||
func Load(home string) (*State, error) {
|
||||
data, err := os.ReadFile(File(home))
|
||||
if err != nil {
|
||||
if errors.Is(err, os.ErrNotExist) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
var state State
|
||||
if err := json.Unmarshal(data, &state); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &state, nil
|
||||
}
|
||||
|
||||
func Save(home string, state *State) error {
|
||||
if state == nil {
|
||||
return nil
|
||||
}
|
||||
if state.PackageName == "" {
|
||||
state.PackageName = PackageName
|
||||
}
|
||||
return cliconfig.WriteJSONAtomic(File(home), state)
|
||||
}
|
||||
|
||||
func ReadStatus(home, target string) (*Status, error) {
|
||||
target = normalizeVersion(target)
|
||||
state, err := Load(home)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
status := &Status{
|
||||
Target: target,
|
||||
Command: "mediakit-cli update --force",
|
||||
}
|
||||
if state == nil || strings.TrimSpace(state.Version) == "" {
|
||||
status.Missing = true
|
||||
return status, nil
|
||||
}
|
||||
status.Current = normalizeVersion(state.Version)
|
||||
status.InSync = status.Current == target
|
||||
return status, nil
|
||||
}
|
||||
|
||||
func InSync(home, target string) bool {
|
||||
status, err := ReadStatus(home, target)
|
||||
return err == nil && status != nil && status.InSync
|
||||
}
|
||||
|
||||
func normalizeVersion(version string) string {
|
||||
version = strings.TrimSpace(version)
|
||||
version = strings.TrimPrefix(version, "v")
|
||||
return strings.TrimPrefix(version, "V")
|
||||
}
|
||||
@@ -4,6 +4,10 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"mediakit-cli/internal/build"
|
||||
cliconfig "mediakit-cli/internal/config"
|
||||
"mediakit-cli/internal/skillstate"
|
||||
)
|
||||
|
||||
// NoticePayload returns a map suitable to be embedded into the JSON stdout
|
||||
@@ -27,18 +31,47 @@ func InjectNotice(result map[string]any) {
|
||||
if result == nil {
|
||||
return
|
||||
}
|
||||
payload := NoticePayload()
|
||||
if payload == nil {
|
||||
return
|
||||
}
|
||||
notice, _ := result["_notice"].(map[string]any)
|
||||
if notice == nil {
|
||||
notice = map[string]any{}
|
||||
}
|
||||
notice["update"] = payload
|
||||
if payload := NoticePayload(); payload != nil {
|
||||
notice["update"] = payload
|
||||
}
|
||||
if payload := SkillsNoticePayload(); payload != nil {
|
||||
notice["skills"] = payload
|
||||
}
|
||||
if len(notice) == 0 {
|
||||
return
|
||||
}
|
||||
result["_notice"] = notice
|
||||
}
|
||||
|
||||
func SkillsNoticePayload() map[string]any {
|
||||
home, err := cliconfig.ResolveHomeDir()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
status, err := skillstate.ReadStatus(home, build.Version)
|
||||
if err != nil || status == nil || status.InSync {
|
||||
return nil
|
||||
}
|
||||
return map[string]any{
|
||||
"current": status.Current,
|
||||
"target": status.Target,
|
||||
"command": status.Command,
|
||||
"message": fmt.Sprintf("MediaKit skills are not synced: current %s, target %s, run: %s",
|
||||
displayVersion(status.Current), status.Target, status.Command),
|
||||
}
|
||||
}
|
||||
|
||||
func displayVersion(version string) string {
|
||||
if version == "" {
|
||||
return "missing"
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
// PrintStderrNag prints an unobtrusive update hint to stderr when an update is
|
||||
// available and stderr is a TTY (character device).
|
||||
func PrintStderrNag(w io.Writer) {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@volcengine/mediakit-cli",
|
||||
"version": "0.1.8-beta.1",
|
||||
"version": "0.1.7",
|
||||
"description": "MediaKit CLI with multi-platform binary distribution via npm",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
const { spawnSync } = require('node:child_process')
|
||||
const fs = require('node:fs')
|
||||
const os = require('node:os')
|
||||
const path = require('node:path')
|
||||
|
||||
const pkg = require('../package.json')
|
||||
@@ -122,9 +123,28 @@ function runSkillsAdd(opts) {
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`npx skills add failed with exit code ${result.status}`)
|
||||
}
|
||||
writeSkillsState()
|
||||
log('✓ Skills installed')
|
||||
}
|
||||
|
||||
function writeSkillsState() {
|
||||
const stateFile = path.join(os.homedir(), '.mediakit', 'skills-state.json')
|
||||
fs.mkdirSync(path.dirname(stateFile), { recursive: true })
|
||||
fs.writeFileSync(
|
||||
stateFile,
|
||||
`${JSON.stringify(
|
||||
{
|
||||
package_name: PACKAGE_NAME,
|
||||
version: pkg.version,
|
||||
skills_dir: SKILLS_DIR,
|
||||
installed_at: new Date().toISOString(),
|
||||
},
|
||||
null,
|
||||
2,
|
||||
)}\n`,
|
||||
)
|
||||
}
|
||||
|
||||
async function runInstallWizard(rawArgs) {
|
||||
const opts = parseArgs(rawArgs)
|
||||
|
||||
|
||||
@@ -47,9 +47,12 @@ process.exit(0)
|
||||
|
||||
t.after(() => fs.rmSync(temp, { recursive: true, force: true }))
|
||||
const oldPath = process.env.PATH
|
||||
const oldHome = process.env.HOME
|
||||
process.env.PATH = `${bin}${path.delimiter}${oldPath || ''}`
|
||||
process.env.HOME = temp
|
||||
t.after(() => {
|
||||
process.env.PATH = oldPath
|
||||
process.env.HOME = oldHome
|
||||
})
|
||||
|
||||
await runInstallWizard(['--skills-only', '--skill', 'byted-mediakit-video', '-y'])
|
||||
@@ -72,6 +75,12 @@ process.exit(0)
|
||||
'-y',
|
||||
])
|
||||
assert(!calls[0].args.some((arg) => /^https?:\/\//.test(String(arg))))
|
||||
const state = JSON.parse(
|
||||
fs.readFileSync(path.join(temp, '.mediakit', 'skills-state.json'), 'utf8'),
|
||||
)
|
||||
assert.equal(state.package_name, '@volcengine/mediakit-cli')
|
||||
assert.equal(state.version, '0.1.7')
|
||||
assert.equal(state.skills_dir, path.join(__dirname, '..', 'skills'))
|
||||
})
|
||||
|
||||
test('regular install delegates skills installation to npm postinstall', async (t) => {
|
||||
|
||||
@@ -213,9 +213,28 @@ function installSkills() {
|
||||
if (result.status !== 0) {
|
||||
throw new Error(`npx skills add failed with exit code ${result.status}`);
|
||||
}
|
||||
writeSkillsState();
|
||||
console.log("[mediakit-cli] ✓ Skills installed");
|
||||
}
|
||||
|
||||
function writeSkillsState() {
|
||||
const stateFile = path.join(os.homedir(), ".mediakit", "skills-state.json");
|
||||
fs.mkdirSync(path.dirname(stateFile), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
stateFile,
|
||||
`${JSON.stringify(
|
||||
{
|
||||
package_name: pkg.name || "@volcengine/mediakit-cli",
|
||||
version: pkg.version,
|
||||
skills_dir: skillsDir,
|
||||
installed_at: new Date().toISOString(),
|
||||
},
|
||||
null,
|
||||
2
|
||||
)}\n`
|
||||
);
|
||||
}
|
||||
|
||||
async function install(options = {}) {
|
||||
if (readEnv("MEDIAKIT_CLI_SKIP_DOWNLOAD", "MEDIKIT_CLI_SKIP_DOWNLOAD") === "1") {
|
||||
console.log("[mediakit-cli] skip download because MEDIAKIT_CLI_SKIP_DOWNLOAD=1");
|
||||
|
||||
@@ -39,11 +39,14 @@ process.exit(0)
|
||||
|
||||
t.after(() => fs.rmSync(temp, { recursive: true, force: true }))
|
||||
const oldPath = process.env.PATH
|
||||
const oldHome = process.env.HOME
|
||||
const oldSkip = process.env.MEDIAKIT_CLI_SKIP_DOWNLOAD
|
||||
process.env.PATH = `${bin}${path.delimiter}${oldPath || ''}`
|
||||
process.env.HOME = temp
|
||||
delete process.env.MEDIAKIT_CLI_SKIP_DOWNLOAD
|
||||
t.after(() => {
|
||||
process.env.PATH = oldPath
|
||||
process.env.HOME = oldHome
|
||||
if (oldSkip === undefined) {
|
||||
delete process.env.MEDIAKIT_CLI_SKIP_DOWNLOAD
|
||||
} else {
|
||||
@@ -69,4 +72,10 @@ process.exit(0)
|
||||
])
|
||||
assert.match(output, /Installing skills \.\.\./)
|
||||
assert.match(output, /✓ Skills installed/)
|
||||
const state = JSON.parse(
|
||||
fs.readFileSync(path.join(temp, '.mediakit', 'skills-state.json'), 'utf8'),
|
||||
)
|
||||
assert.equal(state.package_name, '@volcengine/mediakit-cli')
|
||||
assert.equal(state.version, '0.1.7')
|
||||
assert.equal(state.skills_dir, path.join(__dirname, '..', 'skills'))
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user