-
Notifications
You must be signed in to change notification settings - Fork 373
feat(cli): add update checks #3464
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
51 commits
Select commit
Hold shift + click to select a range
883bd36
feat(cli): add update command
CorieW ce2ba7b
feat(cli/update): force flag
CorieW c56eaa0
feat(cli/update): check flag
CorieW bfdbd57
feat(cli/update): list flag
CorieW 6edd65f
fix(cli/update): don't show RC versions in list
CorieW bb6aad5
feat(cli/update): add node support, not just binary
CorieW 99dc301
feat(cli/update): installing a specific version
CorieW 25d5a5a
feat: add notifications, fixed npm update, and improved code
CorieW dfa1ce5
chore: improve the code and receieve list of version from different p…
CorieW 5aa35d9
Merge branch 'main' of https://github.com/firebase/genkit into @inver…
CorieW f861d3a
chore(cli/update): code adjustments
CorieW a62c010
chore(cli): format
CorieW 76cf3cc
fix(cli/update): version problem
CorieW e314b8d
fix(cli/update): issue that potentially could block event loop
CorieW 23f97bb
chore(cli/update): code improvements
CorieW 755d49a
fix(cli/update): fix updating issues and minor tweaks to console prin…
CorieW 71347b1
fix: remove list flag as not necessary
CorieW 268fc19
fix(cli/update): small logging issue
CorieW e3ec0e5
fix(cli): runningFromNpmLocally func
CorieW b5b470f
feat(cli/update): handle various package managers
CorieW eb311a0
feat(cli/update): instead of detecting package manager and global/loc…
CorieW d277237
fix: remove --force for --reinstall, as clearer
CorieW 38b9765
fix: only inquire package manager when non-binary
CorieW 9e65048
feat(cli/update): improve version not found error message, and throw …
CorieW afa24ad
chore(cli/update): improve some logs
CorieW ff4b2c8
feat(cli): add --no-update-notification flag
CorieW 9a285ca
feat(cli/update): clear message for not finding update version for bi…
CorieW f4ec5b5
feat(cli/update): add some tests
CorieW 7f95f8f
feat(cli/update): added more tests and improved robustness to error
CorieW 40a593a
Merge branch '@invertase/cli-add-update-cmd' of https://github.com/fi…
CorieW 46c3e78
feat(cli/update): when fail to update via package manager, suggest al…
CorieW 859656b
feat(cli/update): add update alternative for failed binary update
CorieW d987d4d
chore: format
CorieW 26b80ac
fix(cli/update): modify update notification silencing method
CorieW 8ea0df1
fix(cli/update): tests and feedback
CorieW 8496f24
chore(cli/update): adjust for feedback and format
CorieW d1eca4b
chore(cli/update): adjust for feedback
CorieW c5dd947
fix(cli/update): try and fix inquirer testing issue
CorieW d348927
chore: format
CorieW a3ca3d0
fix(cli/update): failing tests
CorieW adfa234
feat(cli): add update check
CorieW efee7c0
chore(cli): address feedback and format
CorieW c4c7de2
chore(cli): addressed feedback
CorieW 310c411
chore: format
CorieW 7626166
chore(cli): minor tweak for improved clarity
CorieW a7dd14a
Merge branch 'main' into @invertase/cli-add-update-checks
CorieW 0fb6d93
chore: format
CorieW 98273e5
Merge branch '@invertase/cli-add-update-checks' of https://github.com…
CorieW c2dc593
fix(cli): test
CorieW 2297cd4
Merge branch 'main' into @invertase/cli-add-update-checks
pavelgj ecde314
remove '1.23.x' from go test matrix
pavelgj File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,249 @@ | ||
/** | ||
* Copyright 2025 Google LLC | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
import { GenkitToolsError } from '@genkit-ai/tools-common/manager'; | ||
import { getUserSettings, logger } from '@genkit-ai/tools-common/utils'; | ||
import axios, { AxiosInstance } from 'axios'; | ||
import * as clc from 'colorette'; | ||
import { arch, platform } from 'os'; | ||
import semver from 'semver'; | ||
import { UPDATE_NOTIFICATIONS_OPT_OUT_CONFIG_TAG } from '../commands/config'; | ||
import { detectCLIRuntime } from '../utils/runtime-detector'; | ||
import { | ||
version as currentVersion, | ||
name as packageName, | ||
} from '../utils/version'; | ||
|
||
const GCS_BUCKET_URL = 'https://storage.googleapis.com/genkit-assets-cli'; | ||
const CLI_DOCS_URL = 'https://genkit.dev/docs/devtools/'; | ||
const AXIOS_INSTANCE: AxiosInstance = axios.create({ | ||
timeout: 3000, | ||
}); | ||
|
||
/** | ||
* Interface for update check result | ||
*/ | ||
export interface UpdateCheckResult { | ||
hasUpdate: boolean; | ||
currentVersion: string; | ||
latestVersion: string; | ||
} | ||
|
||
/** | ||
* Returns the current CLI version, normalized. | ||
*/ | ||
export function getCurrentVersion(): string { | ||
return normalizeVersion(currentVersion); | ||
} | ||
|
||
/** | ||
* Normalizes a version string by removing a leading 'v' if present. | ||
* @param version - The version string to normalize | ||
* @returns The normalized version string | ||
*/ | ||
function normalizeVersion(version: string): string { | ||
return version.replace(/^v/, ''); | ||
} | ||
|
||
/** | ||
* Interface for the Google Cloud Storage latest.json response | ||
*/ | ||
interface GCSLatestResponse { | ||
channel: string; | ||
latestVersion: string; | ||
lastUpdated: string; | ||
platforms: Record< | ||
string, | ||
{ | ||
url: string; | ||
version: string; | ||
versionedUrl: string; | ||
} | ||
>; | ||
} | ||
|
||
/** | ||
* Interface for npm registry response | ||
*/ | ||
interface NpmRegistryResponse { | ||
'dist-tags': { | ||
latest: string; | ||
[key: string]: string; | ||
}; | ||
versions: Record<string, unknown>; | ||
} | ||
|
||
/** | ||
* Fetches the latest release data from GCS. | ||
*/ | ||
async function getGCSLatestData(): Promise<GCSLatestResponse> { | ||
const response = await AXIOS_INSTANCE.get(`${GCS_BUCKET_URL}/latest.json`); | ||
|
||
if (response.status !== 200) { | ||
throw new GenkitToolsError( | ||
`Failed to fetch GCS latest.json: ${response.statusText}` | ||
); | ||
} | ||
|
||
return response.data as GCSLatestResponse; | ||
} | ||
|
||
/** | ||
* Gets the latest CLI version from npm registry for non-binary installations. | ||
* @param ignoreRC - If true, ignore prerelease versions (default: true) | ||
*/ | ||
export async function getLatestVersionFromNpm( | ||
ignoreRC: boolean = true | ||
): Promise<string | null> { | ||
try { | ||
const response = await AXIOS_INSTANCE.get( | ||
`https://registry.npmjs.org/${packageName}` | ||
); | ||
|
||
if (response.status !== 200) { | ||
throw new GenkitToolsError( | ||
`Failed to fetch npm versions: ${response.statusText}` | ||
); | ||
} | ||
|
||
const data: NpmRegistryResponse = response.data; | ||
|
||
// Prefer dist-tags.latest if valid and not a prerelease (if ignoreRC) | ||
const latest = data['dist-tags']?.latest; | ||
if (latest) { | ||
const clean = normalizeVersion(latest); | ||
if (semver.valid(clean) && (!ignoreRC || !semver.prerelease(clean))) { | ||
return clean; | ||
} | ||
} | ||
|
||
// Fallback: find the highest valid version in versions | ||
const versions = Object.keys(data.versions) | ||
.map(normalizeVersion) | ||
.filter((v) => semver.valid(v) && (!ignoreRC || !semver.prerelease(v))); | ||
|
||
if (versions.length === 0) { | ||
return null; | ||
} | ||
|
||
// Sort by semver descending (newest first) | ||
versions.sort(semver.rcompare); | ||
return versions[0]; | ||
} catch (error: unknown) { | ||
if (error instanceof GenkitToolsError) { | ||
throw error; | ||
} | ||
|
||
throw new GenkitToolsError( | ||
`Failed to fetch npm versions: ${(error as Error)?.message ?? String(error)}` | ||
); | ||
} | ||
} | ||
|
||
/** | ||
* Checks if update notifications are disabled via environment variable or user config. | ||
*/ | ||
function isUpdateNotificationsDisabled(): boolean { | ||
if (process.env.GENKIT_CLI_DISABLE_UPDATE_NOTIFICATIONS === 'true') { | ||
return true; | ||
} | ||
const userSettings = getUserSettings(); | ||
return Boolean(userSettings[UPDATE_NOTIFICATIONS_OPT_OUT_CONFIG_TAG]); | ||
} | ||
|
||
/** | ||
* Gets the latest version and update message for compiled binary installations. | ||
*/ | ||
async function getBinaryUpdateInfo(): Promise<string | null> { | ||
const gcsLatestData = await getGCSLatestData(); | ||
const machine = `${platform}-${arch}`; | ||
const platformData = gcsLatestData.platforms[machine]; | ||
|
||
if (!platformData) { | ||
logger.debug(`No update information for platform: ${machine}`); | ||
return null; | ||
} | ||
|
||
const latestVersion = normalizeVersion(gcsLatestData.latestVersion); | ||
return latestVersion; | ||
} | ||
|
||
/** | ||
* Gets the latest version and update message for npm installations. | ||
*/ | ||
async function getNpmUpdateInfo(): Promise<string | null> { | ||
const latestVersion = await getLatestVersionFromNpm(); | ||
if (!latestVersion) { | ||
logger.debug('No available versions found from npm.'); | ||
return null; | ||
} | ||
return latestVersion; | ||
} | ||
|
||
/** | ||
* Shows an update notification if a new version is available. | ||
* This function is designed to be called from the CLI entry point. | ||
* It can be disabled by the user's configuration or environment variable. | ||
*/ | ||
export async function showUpdateNotification(): Promise<void> { | ||
try { | ||
if (isUpdateNotificationsDisabled()) { | ||
return; | ||
} | ||
|
||
const { isCompiledBinary } = detectCLIRuntime(); | ||
const updateInfo = isCompiledBinary | ||
? await getBinaryUpdateInfo() | ||
: await getNpmUpdateInfo(); | ||
|
||
if (!updateInfo) { | ||
return; | ||
} | ||
|
||
const latestVersion = updateInfo; | ||
const current = normalizeVersion(currentVersion); | ||
|
||
if (!semver.valid(latestVersion) || !semver.valid(current)) { | ||
logger.debug( | ||
`Invalid semver: current=${current}, latest=${latestVersion}` | ||
); | ||
return; | ||
} | ||
|
||
if (!semver.gt(latestVersion, current)) { | ||
return; | ||
} | ||
|
||
// Determine install method and update command for message | ||
const installMethod = isCompiledBinary | ||
? 'installer script' | ||
: 'your package manager'; | ||
const updateCommand = isCompiledBinary | ||
? 'curl -sL cli.genkit.dev | uninstall=true bash' | ||
: 'npm install -g genkit-cli'; | ||
|
||
const updateNotificationMessage = | ||
`Update available ${clc.gray(`v${current}`)} → ${clc.green(`v${latestVersion}`)}\n` + | ||
`To update to the latest version using ${installMethod}, run\n${clc.cyan(updateCommand)}\n` + | ||
`For other CLI management options, visit ${CLI_DOCS_URL}\n` + | ||
`${clc.dim('Run')} ${clc.bold('genkit config set updateNotificationsOptOut true')} ${clc.dim('to disable these notifications')}\n`; | ||
|
||
logger.info(`\n${updateNotificationMessage}`); | ||
} catch (e) { | ||
// Silently fail - update notifications shouldn't break the CLI | ||
logger.debug('Failed to show update notification', e); | ||
} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.