Skip to content

Commit b3bf9bc

Browse files
slapec93Gergely Békésigithub-actions[bot]
authored
feat: show warning when new swarm-cli version is available (#708)
* feat: show warning when new swarm-cli version is available * fix: make version check switchable for tests * ci: update deposit command * test: update test coverage * refactor: use caching mechanism for version fetching * test: update test coverage * fix: remove debug statements * test: update test coverage * test: update test coverage * test: update test coverage --------- Co-authored-by: Gergely Békési <gergely.bekesi@ethswarm.org> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent d6a25cd commit b3bf9bc

7 files changed

Lines changed: 122 additions & 3 deletions

File tree

jest.config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ export default async (): Promise<Config.InitialOptions> => {
1717
*/
1818
const console = new CommandLog(VerbosityLevel.Normal)
1919

20+
process.env.SKIP_VERSION_CHECK = 'true'
21+
2022
if (!process.env.SKIP_WORKER) {
2123
process.env.WORKER_PSS_ADDRESS = (await getPssAddress('http://localhost:11633')).toCompressedHex()
2224
}

src/command/root-command/command-config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ export class CommandConfig {
5858
return process.env.SWARM_CLI_HISTORY_FILE_PATH || join(this.configFolderPath, 'upload-history.json')
5959
}
6060

61+
public getVersionCheckFilePath(): string {
62+
return process.env.SWARM_CLI_VERSION_CHECK_FILE_PATH || join(this.configFolderPath, 'version-check.json')
63+
}
64+
6165
public setHistoryEnabled(enabled: boolean): void {
6266
this.config.historyEnabled = enabled
6367
this.saveConfig()

src/command/root-command/index.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@ import { parseHeaders } from '../../utils'
66
import { ConfigOption } from '../../utils/types/config-option'
77
import { CONFIG_OPTIONS, CommandConfig } from './command-config'
88
import { CommandLog, VerbosityLevel } from './command-log'
9+
import { checkForUpdates, getLatestVersionCheck } from '../../service/version_checker'
10+
import PackageJson from '../../../package.json'
11+
import { warningText } from '../../utils/text'
912

1013
export class RootCommand {
1114
@ExternalOption('bee-api-url')
@@ -79,6 +82,20 @@ export class RootCommand {
7982
this.verbosity = VerbosityLevel.Verbose
8083
}
8184
this.console = new CommandLog(this.verbosity)
85+
86+
if (!this.quiet) {
87+
const latestVersionCheck = getLatestVersionCheck(this.commandConfig)
88+
89+
if (latestVersionCheck === null) {
90+
checkForUpdates(this.commandConfig)
91+
} else if (latestVersionCheck.latestVersion !== PackageJson.version) {
92+
this.console.log(
93+
warningText(
94+
`A new version of swarm-cli is available: ${latestVersionCheck.latestVersion}. You are using version ${PackageJson.version}. Please update to get the latest features and fixes.`,
95+
),
96+
)
97+
}
98+
}
8299
}
83100

84101
private maybeSetFromConfig(option: ConfigOption): void {

src/service/version_checker.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { existsSync, readFileSync, writeFileSync } from 'fs'
2+
import PackageJson from '../../package.json'
3+
import { CommandConfig } from '../command/root-command/command-config'
4+
import fetch from 'node-fetch'
5+
import { Dates } from 'cafe-utility'
6+
7+
const LATEST_RELEASE_URL = 'https://api.github.com/repos/ethersphere/swarm-cli/releases/latest'
8+
9+
type VersionCheckData = {
10+
latestVersion: string
11+
expiresAt: number
12+
}
13+
14+
export async function checkForUpdates(config: CommandConfig) {
15+
if (process.env.SKIP_VERSION_CHECK === 'true') {
16+
return
17+
}
18+
19+
const filePath = config.getVersionCheckFilePath()
20+
21+
try {
22+
const response = await fetch(LATEST_RELEASE_URL)
23+
24+
if (!response.ok) {
25+
return
26+
}
27+
28+
const data = (await response.json()) as { tag_name: string }
29+
const latestVersion = data.tag_name.replace(/^v/, '')
30+
const versionCheckData = {
31+
latestVersion,
32+
expiresAt: Date.now() + Dates.days(1),
33+
}
34+
35+
if (latestVersion !== PackageJson.version) {
36+
writeFileSync(filePath, JSON.stringify(versionCheckData))
37+
}
38+
} catch {
39+
return
40+
}
41+
}
42+
43+
export function getLatestVersionCheck(config: CommandConfig): VersionCheckData | null {
44+
const filePath = config.getVersionCheckFilePath()
45+
46+
if (!existsSync(filePath)) {
47+
return null
48+
}
49+
50+
try {
51+
const data = JSON.parse(readFileSync(filePath).toString()) as VersionCheckData
52+
53+
if (Date.now() > data.expiresAt) {
54+
return null
55+
}
56+
57+
return {
58+
latestVersion: data.latestVersion,
59+
expiresAt: data.expiresAt,
60+
}
61+
} catch {
62+
return null
63+
}
64+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import fetch, { Response } from 'node-fetch'
2+
import PackageJson from '../../package.json'
3+
import { describeCommand, invokeTestCli } from '../utility'
4+
import { existsSync, unlinkSync } from 'fs'
5+
6+
jest.mock('node-fetch')
7+
const mockedFetch = fetch as jest.MockedFunction<typeof fetch>
8+
9+
describeCommand('Test Outdated Version Warning', ({ hasMessageContaining }) => {
10+
afterEach(() => {
11+
if (existsSync('./test/testconfig/version-check.json')) {
12+
unlinkSync('./test/testconfig/version-check.json')
13+
}
14+
})
15+
it('should print warning when version is outdated', async () => {
16+
process.env.SKIP_VERSION_CHECK = 'false'
17+
mockedFetch.mockResolvedValue({
18+
ok: true,
19+
json: () => Promise.resolve({ tag_name: 'v999.0.0' }),
20+
} as unknown as Response)
21+
await invokeTestCli(['status'])
22+
await invokeTestCli(['status'])
23+
expect(
24+
hasMessageContaining(
25+
`A new version of swarm-cli is available: 999.0.0. You are using version ${PackageJson.version}. Please update to get the latest features and fixes.`,
26+
),
27+
).toBe(true)
28+
process.env.SKIP_VERSION_CHECK = 'true'
29+
})
30+
})

test/coverage/coverage-summary.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{"total": {"lines":{"total":2855,"covered":2182,"skipped":0,"pct":76.42},"statements":{"total":2876,"covered":2196,"skipped":0,"pct":76.35},"functions":{"total":342,"covered":271,"skipped":0,"pct":79.23},"branches":{"total":614,"covered":349,"skipped":0,"pct":56.84},"branchesTrue":{"total":0,"covered":0,"skipped":0,"pct":100}}
1+
{"total": {"lines":{"total":2894,"covered":2217,"skipped":0,"pct":76.6},"statements":{"total":2915,"covered":2231,"skipped":0,"pct":76.53},"functions":{"total":345,"covered":274,"skipped":0,"pct":79.42},"branches":{"total":625,"covered":357,"skipped":0,"pct":57.12},"branchesTrue":{"total":0,"covered":0,"skipped":0,"pct":100}}
22
,"/home/runner/work/swarm-cli/swarm-cli/src/application.ts": {"lines":{"total":2,"covered":0,"skipped":0,"pct":0},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":2,"covered":0,"skipped":0,"pct":0},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
33
,"/home/runner/work/swarm-cli/swarm-cli/src/config.ts": {"lines":{"total":33,"covered":32,"skipped":0,"pct":96.96},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":33,"covered":32,"skipped":0,"pct":96.96},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
44
,"/home/runner/work/swarm-cli/swarm-cli/src/curl.ts": {"lines":{"total":24,"covered":24,"skipped":0,"pct":100},"functions":{"total":7,"covered":7,"skipped":0,"pct":100},"statements":{"total":25,"covered":25,"skipped":0,"pct":100},"branches":{"total":13,"covered":12,"skipped":0,"pct":92.3}}
@@ -63,9 +63,9 @@
6363
,"/home/runner/work/swarm-cli/swarm-cli/src/command/pss/receive.ts": {"lines":{"total":24,"covered":18,"skipped":0,"pct":75},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":24,"covered":18,"skipped":0,"pct":75},"branches":{"total":6,"covered":2,"skipped":0,"pct":33.33}}
6464
,"/home/runner/work/swarm-cli/swarm-cli/src/command/pss/send.ts": {"lines":{"total":34,"covered":29,"skipped":0,"pct":85.29},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":34,"covered":29,"skipped":0,"pct":85.29},"branches":{"total":5,"covered":2,"skipped":0,"pct":40}}
6565
,"/home/runner/work/swarm-cli/swarm-cli/src/command/pss/subscribe.ts": {"lines":{"total":18,"covered":8,"skipped":0,"pct":44.44},"functions":{"total":5,"covered":1,"skipped":0,"pct":20},"statements":{"total":18,"covered":8,"skipped":0,"pct":44.44},"branches":{"total":4,"covered":0,"skipped":0,"pct":0}}
66-
,"/home/runner/work/swarm-cli/swarm-cli/src/command/root-command/command-config.ts": {"lines":{"total":39,"covered":33,"skipped":0,"pct":84.61},"functions":{"total":8,"covered":8,"skipped":0,"pct":100},"statements":{"total":42,"covered":35,"skipped":0,"pct":83.33},"branches":{"total":11,"covered":6,"skipped":0,"pct":54.54}}
66+
,"/home/runner/work/swarm-cli/swarm-cli/src/command/root-command/command-config.ts": {"lines":{"total":40,"covered":34,"skipped":0,"pct":85},"functions":{"total":9,"covered":9,"skipped":0,"pct":100},"statements":{"total":43,"covered":36,"skipped":0,"pct":83.72},"branches":{"total":13,"covered":7,"skipped":0,"pct":53.84}}
6767
,"/home/runner/work/swarm-cli/swarm-cli/src/command/root-command/command-log.ts": {"lines":{"total":78,"covered":60,"skipped":0,"pct":76.92},"functions":{"total":9,"covered":6,"skipped":0,"pct":66.66},"statements":{"total":78,"covered":60,"skipped":0,"pct":76.92},"branches":{"total":11,"covered":7,"skipped":0,"pct":63.63}}
68-
,"/home/runner/work/swarm-cli/swarm-cli/src/command/root-command/index.ts": {"lines":{"total":44,"covered":40,"skipped":0,"pct":90.9},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":44,"covered":40,"skipped":0,"pct":90.9},"branches":{"total":9,"covered":5,"skipped":0,"pct":55.55}}
68+
,"/home/runner/work/swarm-cli/swarm-cli/src/command/root-command/index.ts": {"lines":{"total":53,"covered":49,"skipped":0,"pct":92.45},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":53,"covered":49,"skipped":0,"pct":92.45},"branches":{"total":13,"covered":9,"skipped":0,"pct":69.23}}
6969
,"/home/runner/work/swarm-cli/swarm-cli/src/command/root-command/printer.ts": {"lines":{"total":9,"covered":9,"skipped":0,"pct":100},"functions":{"total":6,"covered":6,"skipped":0,"pct":100},"statements":{"total":9,"covered":9,"skipped":0,"pct":100},"branches":{"total":1,"covered":1,"skipped":0,"pct":100}}
7070
,"/home/runner/work/swarm-cli/swarm-cli/src/command/stake/deposit.ts": {"lines":{"total":39,"covered":30,"skipped":0,"pct":76.92},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":39,"covered":30,"skipped":0,"pct":76.92},"branches":{"total":16,"covered":10,"skipped":0,"pct":62.5}}
7171
,"/home/runner/work/swarm-cli/swarm-cli/src/command/stake/index.ts": {"lines":{"total":8,"covered":8,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":8,"covered":8,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
@@ -92,6 +92,7 @@
9292
,"/home/runner/work/swarm-cli/swarm-cli/src/command/wallet/status.ts": {"lines":{"total":11,"covered":11,"skipped":0,"pct":100},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":11,"covered":11,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}}
9393
,"/home/runner/work/swarm-cli/swarm-cli/src/command/wallet/withdraw-bzz.ts": {"lines":{"total":21,"covered":19,"skipped":0,"pct":90.47},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":21,"covered":19,"skipped":0,"pct":90.47},"branches":{"total":6,"covered":3,"skipped":0,"pct":50}}
9494
,"/home/runner/work/swarm-cli/swarm-cli/src/command/wallet/withdraw-dai.ts": {"lines":{"total":21,"covered":19,"skipped":0,"pct":90.47},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":21,"covered":19,"skipped":0,"pct":90.47},"branches":{"total":6,"covered":3,"skipped":0,"pct":50}}
95+
,"/home/runner/work/swarm-cli/swarm-cli/src/service/version_checker.ts": {"lines":{"total":29,"covered":25,"skipped":0,"pct":86.2},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":29,"covered":25,"skipped":0,"pct":86.2},"branches":{"total":5,"covered":3,"skipped":0,"pct":60}}
9596
,"/home/runner/work/swarm-cli/swarm-cli/src/service/history/index.ts": {"lines":{"total":21,"covered":19,"skipped":0,"pct":90.47},"functions":{"total":5,"covered":5,"skipped":0,"pct":100},"statements":{"total":22,"covered":20,"skipped":0,"pct":90.9},"branches":{"total":1,"covered":1,"skipped":0,"pct":100}}
9697
,"/home/runner/work/swarm-cli/swarm-cli/src/service/identity/index.ts": {"lines":{"total":36,"covered":32,"skipped":0,"pct":88.88},"functions":{"total":7,"covered":7,"skipped":0,"pct":100},"statements":{"total":36,"covered":32,"skipped":0,"pct":88.88},"branches":{"total":9,"covered":6,"skipped":0,"pct":66.66}}
9798
,"/home/runner/work/swarm-cli/swarm-cli/src/service/identity/types/identity.ts": {"lines":{"total":4,"covered":4,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":4,"covered":4,"skipped":0,"pct":100},"branches":{"total":2,"covered":2,"skipped":0,"pct":100}}

test/utility/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ export function describeCommand(
7777
configFileName ? `${configFileName}-upload-history.json` : 'upload-history.json',
7878
)
7979
process.env.SWARM_CLI_HISTORY_FILE_PATH = historyFilePath
80+
process.env.SWARM_CLI_VERSION_CHECK_FILE_PATH = join(configFolderPath, 'version-check.json')
8081

8182
//if own config is needed
8283
if (configFileName) {

0 commit comments

Comments
 (0)