Skip to content

Commit 15d73fe

Browse files
ci: removed 3rd party release dependency
1 parent 591751b commit 15d73fe

6 files changed

Lines changed: 186 additions & 25 deletions

File tree

.github/workflows/nightly.yaml

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ jobs:
6464
release:
6565
needs: build
6666
runs-on: ubuntu-latest
67+
env:
68+
GH_TOKEN: ${{ github.token }}
6769
steps:
6870
- uses: actions/checkout@v4
6971

@@ -72,27 +74,18 @@ jobs:
7274
pattern: binary-*
7375
merge-multiple: true
7476

75-
- name: Set release date
76-
run: echo "RELEASE_DATE=$(date -u +%Y-%m-%d)" >> "$GITHUB_ENV"
77+
- name: Write commit SHA
78+
run: echo -n "${{ github.sha }}" > commit.txt
7779

7880
- name: Delete existing nightly release
79-
env:
80-
GH_TOKEN: ${{ github.token }}
8181
run: |
8282
gh release delete nightly --repo ${{ github.repository }} --yes || true
8383
git push --delete origin nightly || true
84-
shell: bash
85-
86-
- name: Write commit SHA
87-
run: echo -n "${{ github.sha }}" > commit.txt
8884
8985
- name: Create nightly release
90-
uses: softprops/action-gh-release@v2
91-
with:
92-
tag_name: nightly
93-
name: "nightly (${{ env.RELEASE_DATE }})"
94-
prerelease: true
95-
body: "Automated nightly build from the latest commit on main."
96-
files: |
97-
createos-*
98-
commit.txt
86+
run: |
87+
gh release create nightly \
88+
--title "nightly ($(date -u +%Y-%m-%d))" \
89+
--notes "Automated nightly build from the latest commit on main." \
90+
--prerelease \
91+
createos-* commit.txt

.github/workflows/release.yaml

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,16 +64,15 @@ jobs:
6464
release:
6565
needs: build
6666
runs-on: ubuntu-latest
67+
env:
68+
GH_TOKEN: ${{ github.token }}
6769
steps:
70+
- uses: actions/checkout@v4
71+
6872
- uses: actions/download-artifact@v4
6973
with:
7074
pattern: binary-*
7175
merge-multiple: true
7276

73-
- name: Create GitHub Release
74-
uses: softprops/action-gh-release@v2
75-
with:
76-
tag_name: ${{ github.ref_name }}
77-
name: ${{ github.ref_name }}
78-
generate_release_notes: true
79-
files: createos-*
77+
- name: Upload release assets
78+
run: gh release upload "${{ github.ref_name }}" createos-* --clobber

README.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,34 @@ The official command-line interface for [CreateOS](https://createos.nodeops.netw
1414

1515
## Installation
1616

17+
### Stable (recommended)
18+
19+
```sh
20+
curl -sfL https://raw.githubusercontent.com/NodeOps-app/createos-cli/main/install.sh | sh -
21+
```
22+
23+
### Nightly
24+
25+
Built daily from the latest commit on `main`. May contain unreleased features.
26+
27+
```sh
28+
curl -sfL https://raw.githubusercontent.com/NodeOps-app/createos-cli/main/install.sh | CREATEOS_CHANNEL=nightly sh -
29+
```
30+
31+
### Pin a specific version
32+
33+
```sh
34+
curl -sfL https://raw.githubusercontent.com/NodeOps-app/createos-cli/main/install.sh | CREATEOS_VERSION=v0.1.0 sh -
35+
```
36+
37+
### Upgrade
38+
39+
```sh
40+
createos upgrade
41+
```
42+
43+
### Build from source
44+
1745
Requires Go 1.21+.
1846

1947
```bash

cmd/root/root.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ func NewApp() *cli.App {
158158
} else {
159159
fmt.Println(" login Authenticate with CreateOS")
160160
}
161-
fmt.Println(" ask Ask the AI assistant to help manage your infrastructure")
161+
fmt.Println(" ask Ask the AI assistant to help manage your infrastructure")
162162
fmt.Println(" upgrade Upgrade createos to the latest version")
163163
fmt.Println(" version Print the current version")
164164
fmt.Println()

internal/intro/intro.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/pterm/pterm"
88

99
"github.com/NodeOps-app/createos-cli/internal/pkg/version"
10+
"github.com/NodeOps-app/createos-cli/internal/updater"
1011
)
1112

1213
const asciiLogo = ` ██████╗██████╗ ███████╗ █████╗ ████████╗███████╗ ██████╗ ███████╗
@@ -24,5 +25,12 @@ func Show() {
2425
style2 := pterm.NewStyle(pterm.FgGray)
2526
style2.Println(" Your intelligent infrastructure CLI (version: " + version.Version + ")")
2627
fmt.Println()
28+
29+
if latest := updater.LatestVersion(); latest != "" {
30+
pterm.Info.Printf("A new version is available: %s → %s\n", version.Version, latest)
31+
pterm.Println(pterm.Gray(" Run 'createos upgrade' to update."))
32+
fmt.Println()
33+
}
34+
2735
fmt.Println()
2836
}

internal/updater/updater.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Package updater checks for newer CLI versions and caches the result.
2+
package updater
3+
4+
import (
5+
"context"
6+
"encoding/json"
7+
"net/http"
8+
"os"
9+
"path/filepath"
10+
"time"
11+
12+
"golang.org/x/mod/semver"
13+
14+
"github.com/NodeOps-app/createos-cli/internal/pkg/version"
15+
)
16+
17+
const (
18+
githubRepo = "NodeOps-app/createos-cli"
19+
cacheFile = ".version-check"
20+
cacheTTL = 24 * time.Hour
21+
checkTimeout = 3 * time.Second
22+
)
23+
24+
type versionCache struct {
25+
CheckedAt int64 `json:"checked_at"`
26+
Latest string `json:"latest"`
27+
}
28+
29+
// LatestVersion returns the latest available stable version if it is newer
30+
// than the running binary. Returns empty string if up to date, offline, or
31+
// on the nightly channel.
32+
func LatestVersion() string {
33+
// Nightly users use commit-based comparison via `upgrade` — skip notification
34+
if version.Channel == "nightly" || version.Version == "dev" {
35+
return ""
36+
}
37+
38+
latest := cachedVersion()
39+
if latest == "" {
40+
latest = fetchLatest()
41+
saveCache(latest)
42+
}
43+
44+
if latest == "" {
45+
return ""
46+
}
47+
48+
if semver.Compare(version.Version, latest) < 0 {
49+
return latest
50+
}
51+
return ""
52+
}
53+
54+
func cacheFilePath() string {
55+
home, err := os.UserHomeDir()
56+
if err != nil {
57+
return ""
58+
}
59+
return filepath.Join(home, ".createos", cacheFile)
60+
}
61+
62+
// cachedVersion returns the cached latest version if the cache is fresh.
63+
func cachedVersion() string {
64+
path := cacheFilePath()
65+
if path == "" {
66+
return ""
67+
}
68+
69+
data, err := os.ReadFile(path) //nolint:gosec
70+
if err != nil {
71+
return ""
72+
}
73+
74+
var c versionCache
75+
if err := json.Unmarshal(data, &c); err != nil {
76+
return ""
77+
}
78+
79+
if time.Since(time.Unix(c.CheckedAt, 0)) > cacheTTL {
80+
return "" // stale — trigger a fresh fetch
81+
}
82+
83+
return c.Latest
84+
}
85+
86+
func saveCache(latest string) {
87+
path := cacheFilePath()
88+
if path == "" {
89+
return
90+
}
91+
92+
data, err := json.Marshal(versionCache{
93+
CheckedAt: time.Now().Unix(),
94+
Latest: latest,
95+
})
96+
if err != nil {
97+
return
98+
}
99+
100+
_ = os.WriteFile(path, data, 0o600)
101+
}
102+
103+
func fetchLatest() string {
104+
url := "https://api.github.com/repos/" + githubRepo + "/releases/latest"
105+
106+
ctx, cancel := context.WithTimeout(context.Background(), checkTimeout)
107+
defer cancel()
108+
109+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
110+
if err != nil {
111+
return ""
112+
}
113+
req.Header.Set("Accept", "application/vnd.github+json")
114+
115+
resp, err := http.DefaultClient.Do(req)
116+
if err != nil {
117+
return ""
118+
}
119+
defer resp.Body.Close() //nolint:errcheck
120+
121+
if resp.StatusCode != http.StatusOK {
122+
return ""
123+
}
124+
125+
var result struct {
126+
TagName string `json:"tag_name"`
127+
}
128+
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
129+
return ""
130+
}
131+
132+
return result.TagName
133+
}

0 commit comments

Comments
 (0)