Compare commits

...

1 Commits

Author SHA1 Message Date
zhangheng.023
77ad271fce fix: write skills state timestamps in local time without timezone
skills-state.json updated_at now uses a YYYY-MM-DDTHH:mm:ss local wall-clock layout with no timezone suffix, applied in both the incremental sync and full-install fallback write paths via a shared layout constant. Unit tests assert the no-suffix format on both paths.
2026-06-12 18:01:58 +08:00
2 changed files with 24 additions and 4 deletions

View File

@@ -14,6 +14,8 @@ import (
"github.com/larksuite/cli/internal/selfupdate"
)
const skillsStateUpdatedAtLayout = "2006-01-02T15:04:05"
var (
skillNamePattern = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9_:-]*(@[^\s]+)?$`)
ansiPattern = regexp.MustCompile(`\x1b\[[0-?]*[ -/]*[@-~]`)
@@ -335,7 +337,7 @@ func SyncSkills(opts SyncOptions) *SyncResult {
UpdatedSkills: plan.ToUpdate,
AddedOfficialSkills: plan.Added,
SkippedDeletedSkills: plan.SkippedDeleted,
UpdatedAt: opts.Now().UTC().Format(time.RFC3339),
UpdatedAt: opts.Now().Format(skillsStateUpdatedAtLayout),
}
if err := WriteState(state); err != nil {
result.Action = "failed"
@@ -428,7 +430,7 @@ func fallbackFullInstall(opts SyncOptions, reason string, official []string) *Sy
UpdatedSkills: official,
AddedOfficialSkills: official,
SkippedDeletedSkills: []string{},
UpdatedAt: opts.Now().UTC().Format(time.RFC3339),
UpdatedAt: opts.Now().Format(skillsStateUpdatedAtLayout),
}
if writeErr := WriteState(state); writeErr != nil {
return &SyncResult{

View File

@@ -339,7 +339,9 @@ func TestSyncSkills_WritesStateAndDoesNotWriteStamp(t *testing.T) {
result := SyncSkills(SyncOptions{
Version: "1.0.33",
Runner: runner,
Now: func() time.Time { return time.Date(2026, 5, 18, 12, 0, 0, 0, time.UTC) },
Now: func() time.Time {
return time.Date(2026, 5, 18, 12, 0, 0, 0, time.FixedZone("UTC+8", 8*60*60))
},
})
if result.Err != nil {
@@ -361,6 +363,9 @@ func TestSyncSkills_WritesStateAndDoesNotWriteStamp(t *testing.T) {
assertStrings(t, state.UpdatedSkills, []string{"lark-calendar", "lark-new"})
assertStrings(t, state.AddedOfficialSkills, []string{"lark-new"})
assertStrings(t, state.SkippedDeletedSkills, []string{"lark-mail"})
if state.UpdatedAt != "2026-05-18T12:00:00" {
t.Errorf("state.UpdatedAt = %q, want local wall-clock timestamp without timezone", state.UpdatedAt)
}
if _, err := os.Stat(filepath.Join(dir, "skills.stamp")); !os.IsNotExist(err) {
t.Fatalf("skills.stamp exists or stat failed with unexpected err: %v", err)
}
@@ -584,7 +589,13 @@ func TestSyncSkills_ParseEmptyLocalListsFallBackToFullInstall(t *testing.T) {
globalOut: "Some unrecognized output format\n",
}
result := SyncSkills(SyncOptions{Version: "1.0.33", Runner: runner, Now: time.Now})
result := SyncSkills(SyncOptions{
Version: "1.0.33",
Runner: runner,
Now: func() time.Time {
return time.Date(2026, 5, 18, 12, 0, 0, 0, time.FixedZone("UTC-7", -7*60*60))
},
})
if result.Action != "fallback_synced" {
t.Fatalf("SyncSkills() action = %q, want fallback_synced", result.Action)
}
@@ -594,6 +605,13 @@ func TestSyncSkills_ParseEmptyLocalListsFallBackToFullInstall(t *testing.T) {
if runner.installedAll != 1 {
t.Fatalf("installedAll = %d, want 1", runner.installedAll)
}
state, readable, err := ReadState()
if err != nil || !readable {
t.Fatalf("ReadState() = (_, %v, %v), want readable", readable, err)
}
if state.UpdatedAt != "2026-05-18T12:00:00" {
t.Errorf("state.UpdatedAt = %q, want local wall-clock timestamp without timezone", state.UpdatedAt)
}
}
func TestSyncSkills_EmptyToUpdateFallsBackToFullInstall(t *testing.T) {