Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 87 additions & 17 deletions src/commands/version.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,39 @@
import {isNullish} from '@dfinity/utils';
import {red} from 'kleur';
import {isEmptyString, isNullish} from '@dfinity/utils';
import {green, red} from 'kleur';
import {clean} from 'semver';
import {version as cliCurrentVersion} from '../../package.json';
import {githubCliLastRelease} from '../rest/github.rest';
import {checkVersion} from '../services/version.services';
import {
githubCliLastRelease,
githubJunoDockerLastRelease,
type GithubLastReleaseResult
} from '../rest/github.rest';
import {findEmulatorVersion} from '../services/emulator/version.services';
import {checkVersion, type CheckVersionResult} from '../services/version.services';
import {detectPackageManager} from '../utils/pm.utils';
export const version = async () => {
await cliVersion();
};

const cliVersion = async () => {
const githubRelease = await githubCliLastRelease();
export const version = async () => {
const check = await cliVersion();

if (githubRelease === undefined) {
console.log(red('Cannot fetch last release version of Juno on GitHub 😢.'));
if (check.diff === 'error') {
return;
}

const {tag_name} = githubRelease;
await emulatorVersion();
};

const latestVersion = clean(tag_name);
const cliVersion = async (): Promise<CheckVersionResult> => {
const result = await buildVersionFromGitHub({
release: 'CLI',
releaseFn: githubCliLastRelease
});

if (isNullish(latestVersion)) {
console.log(red(`Cannot extract version from release. Reach out Juno❗️`));
return;
if (result.result === 'error') {
return {diff: 'error'};
}

checkVersion({
const {latestVersion} = result;

return checkVersion({
currentVersion: cliCurrentVersion,
latestVersion,
displayHint: 'CLI',
Expand All @@ -46,3 +53,66 @@ const installHint = (): string => {
return 'npm i -g @junobuild/cli';
}
};

const emulatorVersion = async () => {
const emulatorResult = await findEmulatorVersion();

if (emulatorResult.status !== 'success') {
return;
}

const {version: emulatorCurrentVersion} = emulatorResult;

const result = await buildVersionFromGitHub({
release: 'Juno Docker',
releaseFn: githubJunoDockerLastRelease
});

if (result.result === 'error') {
return;
}

const {latestVersion} = result;

// Images prior to v0.6.3 lacked proper metadata in org.opencontainers.image.version.
// Earlier releases contained invalid values such as "0-arm64", while v0.6.2 returned an empty string.
// Note: sanitizing the version read via docker/podman inspect causes these cases to resolve to null.
if (isEmptyString(emulatorCurrentVersion)) {
console.log(`Your Emulator is behind the latest version (${green(`v${latestVersion}`)}).`);
return;
}

checkVersion({
currentVersion: emulatorCurrentVersion,
latestVersion,
displayHint: 'Emulator'
});
};

const buildVersionFromGitHub = async ({
releaseFn,
release
}: {
releaseFn: () => Promise<GithubLastReleaseResult>;
release: 'CLI' | 'Juno Docker';
}): Promise<{result: 'success'; latestVersion: string} | {result: 'error'}> => {
const githubRelease = await releaseFn();

if (githubRelease.status === 'error') {
console.log(red(`Cannot fetch the last version of ${release} on GitHub 😢.`));
return {result: 'error'};
}

const {
release: {tag_name}
} = githubRelease;

const latestVersion = clean(tag_name);

if (isNullish(latestVersion)) {
console.log(red(`Cannot extract version from the ${release} release. Reach out Juno❗️`));
return {result: 'error'};
}

return {result: 'success', latestVersion};
};
1 change: 1 addition & 0 deletions src/constants/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const ORBITER_WASM_NAME = 'orbiter';
export const NODE_VERSION = 20;
export const JUNO_CDN_URL = 'https://cdn.juno.build';
export const GITHUB_API_CLI_URL = 'https://api.github.com/repos/junobuild/cli';
export const GITHUB_API_JUNO_DOCKER_URL = 'https://api.github.com/repos/junobuild/juno-docker';

/**
* Revoked principals that must not be used.
Expand Down
31 changes: 22 additions & 9 deletions src/rest/github.rest.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {GITHUB_API_CLI_URL} from '../constants/constants';
import {GITHUB_API_CLI_URL, GITHUB_API_JUNO_DOCKER_URL} from '../constants/constants';

export interface GitHubAsset {
url: string; // 'https://api.github.com/repos/peterpeterparker/dummy/releases/assets/91555492'
Expand Down Expand Up @@ -38,16 +38,29 @@ const GITHUB_API_HEADERS: RequestInit = {
}
};

const githubLastRelease = async (apiUrl: string): Promise<GitHubRelease | undefined> => {
const response = await fetch(`${apiUrl}/releases/latest`, GITHUB_API_HEADERS);
export type GithubLastReleaseResult =
| {status: 'success'; release: GitHubRelease}
| {status: 'error'; err?: unknown};

if (!response.ok) {
return undefined;
}
const githubLastRelease = async (
apiUrl: string
): Promise<{status: 'success'; release: GitHubRelease} | {status: 'error'; err?: unknown}> => {
try {
const response = await fetch(`${apiUrl}/releases/latest`, GITHUB_API_HEADERS);

if (!response.ok) {
return {status: 'error'};
}

// eslint-disable-next-line @typescript-eslint/no-unsafe-return
return await response.json();
const release = await response.json();
return {status: 'success', release};
} catch (err: unknown) {
return {status: 'error', err};
}
};

export const githubCliLastRelease = async (): Promise<GitHubRelease | undefined> =>
export const githubCliLastRelease = async (): Promise<GithubLastReleaseResult> =>
await githubLastRelease(GITHUB_API_CLI_URL);

export const githubJunoDockerLastRelease = async (): Promise<GithubLastReleaseResult> =>
await githubLastRelease(GITHUB_API_JUNO_DOCKER_URL);
32 changes: 32 additions & 0 deletions src/services/emulator/version.services.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {notEmptyString} from '@dfinity/utils';
import {clean} from 'semver';
import {readEmulatorConfig} from '../../configs/emulator.config';
import {inspectImageVersion} from '../../utils/runner.utils';

export const findEmulatorVersion = async (): Promise<
| {status: 'skipped'}
| {status: 'error'; err: unknown}
| {status: 'success'; version: string | undefined | null}
> => {
const parsedResult = await readEmulatorConfig();

if (!parsedResult.success) {
return {status: 'skipped'};
}

const {
config: {derivedConfig}
} = parsedResult;

const inspectResult = await inspectImageVersion(derivedConfig);

if ('err' in inspectResult) {
return {status: 'error', err: inspectResult.err};
}

const {version: versionText} = inspectResult;

const version = notEmptyString(versionText) ? clean(versionText) : undefined;

return {status: 'success', version};
};
16 changes: 11 additions & 5 deletions src/services/version.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ const loadSatelliteVersion = async ({
return {result: 'success', version: legacyVersion};
};

export interface CheckVersionResult {
diff: 'up-to-date' | 'outdated' | 'error';
}

export const checkVersion = ({
currentVersion,
latestVersion,
Expand All @@ -86,23 +90,25 @@ export const checkVersion = ({
currentVersion: string;
latestVersion: string;
displayHint: string;
commandLineHint: string;
}) => {
commandLineHint?: string;
}): CheckVersionResult => {
const diff = compare(currentVersion, latestVersion);

if (diff === 0) {
console.log(`Your ${displayHint} (${green(`v${currentVersion}`)}) is up-to-date.`);
return;
return {diff: 'up-to-date'};
}

if (diff === 1) {
console.log(yellow(`Your ${displayHint} version is more recent than the latest available 🤔.`));
return;
return {diff: 'error'};
}

console.log(
`Your ${displayHint} (${yellow(`v${currentVersion}`)}) is behind the latest version (${green(
`v${latestVersion}`
)}) available. Run ${cyan(commandLineHint)} to update it.`
)}).${nonNullish(commandLineHint) ? ` Run ${cyan(commandLineHint)} to update it.` : ''}`
);

return {diff: 'outdated'};
};
27 changes: 27 additions & 0 deletions src/utils/runner.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,30 @@ export const isContainerRunning = async ({
return {err};
}
};

export const inspectImageVersion = async ({
runner,
image
}: Pick<CliEmulatorDerivedConfig, 'runner' | 'image'>): Promise<
{version: string} | {err: unknown}
> => {
try {
let output = '';

await spawn({
command: runner,
args: [
'inspect',
'--format',
'{{ index .Config.Labels "org.opencontainers.image.version"}}',
image
],
stdout: (o) => (output += o),
silentOut: true
});

return {version: output.trim()};
} catch (err: unknown) {
return {err};
}
};
Loading