RecordInstall adds or updates a skill entry in the lock file. It uses a file-based lock to prevent concurrent read-modify-write races when multiple install processes run simultaneously.
(host, skillName, owner, repo, skillPath, treeSHA, pinnedRef string)
| 95 | // It uses a file-based lock to prevent concurrent read-modify-write races |
| 96 | // when multiple install processes run simultaneously. |
| 97 | func RecordInstall(host, skillName, owner, repo, skillPath, treeSHA, pinnedRef string) error { |
| 98 | lockPath, err := lockfilePath() |
| 99 | if err != nil { |
| 100 | return err |
| 101 | } |
| 102 | if err := os.MkdirAll(filepath.Dir(lockPath), 0o755); err != nil { |
| 103 | return fmt.Errorf("could not create lock directory: %w", err) |
| 104 | } |
| 105 | |
| 106 | lockedFile, unlock, err := acquireFLock() |
| 107 | if err != nil { |
| 108 | return err |
| 109 | } |
| 110 | defer unlock() |
| 111 | |
| 112 | f, err := readFrom(lockedFile) |
| 113 | if err != nil { |
| 114 | return err |
| 115 | } |
| 116 | |
| 117 | now := time.Now().UTC().Format(time.RFC3339) |
| 118 | |
| 119 | existing, exists := f.Skills[skillName] |
| 120 | installedAt := now |
| 121 | if exists { |
| 122 | installedAt = existing.InstalledAt |
| 123 | } |
| 124 | |
| 125 | f.Skills[skillName] = entry{ |
| 126 | Source: owner + "/" + repo, |
| 127 | SourceType: "github", |
| 128 | SourceURL: ghinstance.HostPrefix(host) + owner + "/" + repo + ".git", |
| 129 | SkillPath: skillPath, |
| 130 | SkillFolderHash: treeSHA, |
| 131 | InstalledAt: installedAt, |
| 132 | UpdatedAt: now, |
| 133 | PinnedRef: pinnedRef, |
| 134 | } |
| 135 | |
| 136 | return writeTo(lockedFile, f) |
| 137 | } |
| 138 | |
| 139 | func newFile() *file { |
| 140 | return &file{ |