diff --git a/.github/workflows/tests-rc.yaml b/.github/workflows/tests-rc.yaml index 83c8ddc2..47cb462d 100644 --- a/.github/workflows/tests-rc.yaml +++ b/.github/workflows/tests-rc.yaml @@ -58,11 +58,11 @@ jobs: - name: Deposit to chequebook run: | - swarm-cli cheque deposit 100000000000000000 - swarm-cli cheque deposit 100000000000000000 --bee-api-url http://localhost:11633 - swarm-cli cheque deposit 100000000000000000 --bee-api-url http://localhost:21633 - swarm-cli cheque deposit 100000000000000000 --bee-api-url http://localhost:31633 - swarm-cli cheque deposit 100000000000000000 --bee-api-url http://localhost:41633 + swarm-cli cheque deposit 10 + swarm-cli cheque deposit 10 --bee-api-url http://localhost:11633 + swarm-cli cheque deposit 10 --bee-api-url http://localhost:21633 + swarm-cli cheque deposit 10 --bee-api-url http://localhost:31633 + swarm-cli cheque deposit 10 --bee-api-url http://localhost:41633 - name: Print swarm-cli status continue-on-error: true diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index e752252a..7e8863bc 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -42,11 +42,11 @@ jobs: - name: Deposit to chequebook run: | - swarm-cli cheque deposit 100000000000000000 - swarm-cli cheque deposit 100000000000000000 --bee-api-url http://localhost:11633 - swarm-cli cheque deposit 100000000000000000 --bee-api-url http://localhost:21633 - swarm-cli cheque deposit 100000000000000000 --bee-api-url http://localhost:31633 - swarm-cli cheque deposit 100000000000000000 --bee-api-url http://localhost:41633 + swarm-cli cheque deposit 10 + swarm-cli cheque deposit 10 --bee-api-url http://localhost:11633 + swarm-cli cheque deposit 10 --bee-api-url http://localhost:21633 + swarm-cli cheque deposit 10 --bee-api-url http://localhost:31633 + swarm-cli cheque deposit 10 --bee-api-url http://localhost:41633 - name: Print swarm-cli status continue-on-error: true diff --git a/jest.config.ts b/jest.config.ts index b6be5d57..136fdd76 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -17,6 +17,8 @@ export default async (): Promise => { */ const console = new CommandLog(VerbosityLevel.Normal) + process.env.SKIP_VERSION_CHECK = 'true' + if (!process.env.SKIP_WORKER) { process.env.WORKER_PSS_ADDRESS = (await getPssAddress('http://localhost:11633')).toCompressedHex() } diff --git a/src/command/root-command/command-config.ts b/src/command/root-command/command-config.ts index afd9ea5e..4b66619f 100644 --- a/src/command/root-command/command-config.ts +++ b/src/command/root-command/command-config.ts @@ -58,6 +58,10 @@ export class CommandConfig { return process.env.SWARM_CLI_HISTORY_FILE_PATH || join(this.configFolderPath, 'upload-history.json') } + public getVersionCheckFilePath(): string { + return process.env.SWARM_CLI_VERSION_CHECK_FILE_PATH || join(this.configFolderPath, 'version-check.json') + } + public setHistoryEnabled(enabled: boolean): void { this.config.historyEnabled = enabled this.saveConfig() diff --git a/src/command/root-command/index.ts b/src/command/root-command/index.ts index 11f62adf..0449af6a 100644 --- a/src/command/root-command/index.ts +++ b/src/command/root-command/index.ts @@ -6,6 +6,9 @@ import { parseHeaders } from '../../utils' import { ConfigOption } from '../../utils/types/config-option' import { CONFIG_OPTIONS, CommandConfig } from './command-config' import { CommandLog, VerbosityLevel } from './command-log' +import { checkForUpdates, getLatestVersionCheck } from '../../service/version_checker' +import PackageJson from '../../../package.json' +import { warningText } from '../../utils/text' export class RootCommand { @ExternalOption('bee-api-url') @@ -79,6 +82,20 @@ export class RootCommand { this.verbosity = VerbosityLevel.Verbose } this.console = new CommandLog(this.verbosity) + + if (!this.quiet) { + const latestVersionCheck = getLatestVersionCheck(this.commandConfig) + + if (latestVersionCheck === null) { + checkForUpdates(this.commandConfig) + } else if (latestVersionCheck.latestVersion !== PackageJson.version) { + this.console.log( + warningText( + `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.`, + ), + ) + } + } } private maybeSetFromConfig(option: ConfigOption): void { diff --git a/src/service/version_checker.ts b/src/service/version_checker.ts new file mode 100644 index 00000000..24b8deaa --- /dev/null +++ b/src/service/version_checker.ts @@ -0,0 +1,64 @@ +import { existsSync, readFileSync, writeFileSync } from 'fs' +import PackageJson from '../../package.json' +import { CommandConfig } from '../command/root-command/command-config' +import fetch from 'node-fetch' +import { Dates } from 'cafe-utility' + +const LATEST_RELEASE_URL = 'https://api.github.com/repos/ethersphere/swarm-cli/releases/latest' + +type VersionCheckData = { + latestVersion: string + expiresAt: number +} + +export async function checkForUpdates(config: CommandConfig) { + if (process.env.SKIP_VERSION_CHECK === 'true') { + return + } + + const filePath = config.getVersionCheckFilePath() + + try { + const response = await fetch(LATEST_RELEASE_URL) + + if (!response.ok) { + return + } + + const data = (await response.json()) as { tag_name: string } + const latestVersion = data.tag_name.replace(/^v/, '') + const versionCheckData = { + latestVersion, + expiresAt: Date.now() + Dates.days(1), + } + + if (latestVersion !== PackageJson.version) { + writeFileSync(filePath, JSON.stringify(versionCheckData)) + } + } catch { + return + } +} + +export function getLatestVersionCheck(config: CommandConfig): VersionCheckData | null { + const filePath = config.getVersionCheckFilePath() + + if (!existsSync(filePath)) { + return null + } + + try { + const data = JSON.parse(readFileSync(filePath).toString()) as VersionCheckData + + if (Date.now() > data.expiresAt) { + return null + } + + return { + latestVersion: data.latestVersion, + expiresAt: data.expiresAt, + } + } catch { + return null + } +} diff --git a/test/command/version-warning.spec.ts b/test/command/version-warning.spec.ts new file mode 100644 index 00000000..0198dfff --- /dev/null +++ b/test/command/version-warning.spec.ts @@ -0,0 +1,30 @@ +import fetch, { Response } from 'node-fetch' +import PackageJson from '../../package.json' +import { describeCommand, invokeTestCli } from '../utility' +import { existsSync, unlinkSync } from 'fs' + +jest.mock('node-fetch') +const mockedFetch = fetch as jest.MockedFunction + +describeCommand('Test Outdated Version Warning', ({ hasMessageContaining }) => { + afterEach(() => { + if (existsSync('./test/testconfig/version-check.json')) { + unlinkSync('./test/testconfig/version-check.json') + } + }) + it('should print warning when version is outdated', async () => { + process.env.SKIP_VERSION_CHECK = 'false' + mockedFetch.mockResolvedValue({ + ok: true, + json: () => Promise.resolve({ tag_name: 'v999.0.0' }), + } as unknown as Response) + await invokeTestCli(['status']) + await invokeTestCli(['status']) + expect( + hasMessageContaining( + `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.`, + ), + ).toBe(true) + process.env.SKIP_VERSION_CHECK = 'true' + }) +}) diff --git a/test/coverage/coverage-summary.json b/test/coverage/coverage-summary.json index ed815663..48b3729b 100644 --- a/test/coverage/coverage-summary.json +++ b/test/coverage/coverage-summary.json @@ -1,4 +1,4 @@ -{"total": {"lines":{"total":2863,"covered":2186,"skipped":0,"pct":76.35},"statements":{"total":2884,"covered":2200,"skipped":0,"pct":76.28},"functions":{"total":343,"covered":271,"skipped":0,"pct":79},"branches":{"total":621,"covered":353,"skipped":0,"pct":56.84},"branchesTrue":{"total":0,"covered":0,"skipped":0,"pct":100}} +{"total": {"lines":{"total":2902,"covered":2221,"skipped":0,"pct":76.53},"statements":{"total":2923,"covered":2235,"skipped":0,"pct":76.46},"functions":{"total":346,"covered":274,"skipped":0,"pct":79.19},"branches":{"total":632,"covered":361,"skipped":0,"pct":57.12},"branchesTrue":{"total":0,"covered":0,"skipped":0,"pct":100}} ,"/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}} ,"/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}} ,"/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 @@ ,"/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}} ,"/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}} ,"/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}} -,"/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}} +,"/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}} ,"/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}} -,"/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}} +,"/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}} ,"/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}} ,"/home/runner/work/swarm-cli/swarm-cli/src/command/stake/deposit.ts": {"lines":{"total":40,"covered":31,"skipped":0,"pct":77.5},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":40,"covered":31,"skipped":0,"pct":77.5},"branches":{"total":17,"covered":11,"skipped":0,"pct":64.7}} ,"/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 @@ ,"/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}} ,"/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}} ,"/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}} +,"/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}} ,"/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}} ,"/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}} ,"/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}} diff --git a/test/utility/index.ts b/test/utility/index.ts index 84847640..e96e5d9d 100644 --- a/test/utility/index.ts +++ b/test/utility/index.ts @@ -77,6 +77,7 @@ export function describeCommand( configFileName ? `${configFileName}-upload-history.json` : 'upload-history.json', ) process.env.SWARM_CLI_HISTORY_FILE_PATH = historyFilePath + process.env.SWARM_CLI_VERSION_CHECK_FILE_PATH = join(configFolderPath, 'version-check.json') //if own config is needed if (configFileName) {