0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2024-12-30 22:03:56 -05:00

feat: add a new message telling the user that a new version of Astro is available (#10734)

* feat: add a new message telling the user that a new version of Astro is available

* chore: changeset

* fix: use gt

* fix: apply feednack

* fix: apply feedback

* nit: use private namespace

* fix: oops

* nit: apply feedback

* fix: build

* fix: don't check for updates in CI

* docs: update changeset
This commit is contained in:
Erika 2024-04-24 17:55:49 +02:00 committed by GitHub
parent 43ead8fbd5
commit 6fc4c0e420
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 265 additions and 73 deletions

View file

@ -0,0 +1,7 @@
---
"astro": minor
---
Astro will now automatically check for updates when you run the dev server. If a new version is available, a message will appear in the terminal with instructions on how to update. Updates will be checked once per 10 days, and the message will only appear if the project is multiple versions behind the latest release.
This behavior can be disabled by running `astro preferences disable checkUpdates` or setting the `ASTRO_DISABLE_UPDATE_CHECK` environment variable to `false`.

View file

@ -1667,7 +1667,7 @@ export interface AstroUserConfig {
* @version 4.5.0 * @version 4.5.0
* @description * @description
* Enables a more reliable strategy to prevent scripts from being executed in pages where they are not used. * Enables a more reliable strategy to prevent scripts from being executed in pages where they are not used.
* *
* Scripts will directly render as declared in Astro files (including existing features like TypeScript, importing `node_modules`, * Scripts will directly render as declared in Astro files (including existing features like TypeScript, importing `node_modules`,
* and deduplicating scripts). You can also now conditionally render scripts in your Astro file. * and deduplicating scripts). You can also now conditionally render scripts in your Astro file.
@ -1713,9 +1713,9 @@ export interface AstroUserConfig {
* @version 4.5.0 * @version 4.5.0
* @description * @description
* This feature will auto-generate a JSON schema for content collections of `type: 'data'` which can be used as the `$schema` value for TypeScript-style autocompletion/hints in tools like VSCode. * This feature will auto-generate a JSON schema for content collections of `type: 'data'` which can be used as the `$schema` value for TypeScript-style autocompletion/hints in tools like VSCode.
* *
* To enable this feature, add the experimental flag: * To enable this feature, add the experimental flag:
* *
* ```diff * ```diff
* import { defineConfig } from 'astro/config'; * import { defineConfig } from 'astro/config';
@ -1725,9 +1725,9 @@ export interface AstroUserConfig {
* } * }
* }); * });
* ``` * ```
* *
* This experimental implementation requires you to manually reference the schema in each data entry file of the collection: * This experimental implementation requires you to manually reference the schema in each data entry file of the collection:
* *
* ```diff * ```diff
* // src/content/test/entry.json * // src/content/test/entry.json
* { * {
@ -1735,9 +1735,9 @@ export interface AstroUserConfig {
* "test": "test" * "test": "test"
* } * }
* ``` * ```
* *
* Alternatively, you can set this in your [VSCode `json.schemas` settings](https://code.visualstudio.com/docs/languages/json#_json-schemas-and-settings): * Alternatively, you can set this in your [VSCode `json.schemas` settings](https://code.visualstudio.com/docs/languages/json#_json-schemas-and-settings):
* *
* ```diff * ```diff
* "json.schemas": [ * "json.schemas": [
* { * {
@ -1748,7 +1748,7 @@ export interface AstroUserConfig {
* } * }
* ] * ]
* ``` * ```
* *
* Note that this initial implementation uses a library with [known issues for advanced Zod schemas](https://github.com/StefanTerdell/zod-to-json-schema#known-issues), so you may wish to consult these limitations before enabling the experimental flag. * Note that this initial implementation uses a library with [known issues for advanced Zod schemas](https://github.com/StefanTerdell/zod-to-json-schema#known-issues), so you may wish to consult these limitations before enabling the experimental flag.
*/ */
contentCollectionJsonSchema?: boolean; contentCollectionJsonSchema?: boolean;
@ -2106,6 +2106,14 @@ export interface AstroSettings {
tsConfigPath: string | undefined; tsConfigPath: string | undefined;
watchFiles: string[]; watchFiles: string[];
timer: AstroTimer; timer: AstroTimer;
/**
* Latest version of Astro, will be undefined if:
* - unable to check
* - the user has disabled the check
* - the check has not completed yet
* - the user is on the latest version already
*/
latestAstroVersion: string | undefined;
} }
export type AsyncRendererComponentFn<U> = ( export type AsyncRendererComponentFn<U> = (
@ -3014,6 +3022,7 @@ export type DevToolbarMetadata = Window &
__astro_dev_toolbar__: { __astro_dev_toolbar__: {
root: string; root: string;
version: string; version: string;
latestAstroVersion: AstroSettings['latestAstroVersion'];
debugInfo: string; debugInfo: string;
}; };
}; };

View file

@ -33,6 +33,7 @@ import { createLoggerFromFlags, flagsToAstroInlineConfig } from '../flags.js';
import { generate, parse, t, visit } from './babel.js'; import { generate, parse, t, visit } from './babel.js';
import { ensureImport } from './imports.js'; import { ensureImport } from './imports.js';
import { wrapDefaultExport } from './wrapper.js'; import { wrapDefaultExport } from './wrapper.js';
import { fetchPackageVersions, fetchPackageJson } from '../install-package.js';
interface AddOptions { interface AddOptions {
flags: yargs.Arguments; flags: yargs.Arguments;
@ -95,26 +96,6 @@ const OFFICIAL_ADAPTER_TO_IMPORT_MAP: Record<string, string> = {
node: '@astrojs/node', node: '@astrojs/node',
}; };
// Users might lack access to the global npm registry, this function
// checks the user's project type and will return the proper npm registry
//
// A copy of this function also exists in the create-astro package
let _registry: string;
async function getRegistry(): Promise<string> {
if (_registry) return _registry;
const fallback = 'https://registry.npmjs.org';
const packageManager = (await preferredPM(process.cwd()))?.name || 'npm';
try {
const { stdout } = await execa(packageManager, ['config', 'get', 'registry']);
_registry = stdout?.trim()?.replace(/\/$/, '') || fallback;
// Detect cases where the shell command returned a non-URL (e.g. a warning)
if (!new URL(_registry).host) _registry = fallback;
} catch (e) {
_registry = fallback;
}
return _registry;
}
export async function add(names: string[], { flags }: AddOptions) { export async function add(names: string[], { flags }: AddOptions) {
ensureProcessNodeEnv('production'); ensureProcessNodeEnv('production');
applyPolyfill(); applyPolyfill();
@ -805,39 +786,6 @@ async function tryToInstallIntegrations({
} }
} }
async function fetchPackageJson(
scope: string | undefined,
name: string,
tag: string
): Promise<Record<string, any> | Error> {
const packageName = `${scope ? `${scope}/` : ''}${name}`;
const registry = await getRegistry();
const res = await fetch(`${registry}/${packageName}/${tag}`);
if (res.status >= 200 && res.status < 300) {
return await res.json();
} else if (res.status === 404) {
// 404 means the package doesn't exist, so we don't need an error message here
return new Error();
} else {
return new Error(`Failed to fetch ${registry}/${packageName}/${tag} - GET ${res.status}`);
}
}
async function fetchPackageVersions(packageName: string): Promise<string[] | Error> {
const registry = await getRegistry();
const res = await fetch(`${registry}/${packageName}`, {
headers: { accept: 'application/vnd.npm.install-v1+json' },
});
if (res.status >= 200 && res.status < 300) {
return await res.json().then((data) => Object.keys(data.versions));
} else if (res.status === 404) {
// 404 means the package doesn't exist, so we don't need an error message here
return new Error();
} else {
return new Error(`Failed to fetch ${registry}/${packageName} - GET ${res.status}`);
}
}
export async function validateIntegrations(integrations: string[]): Promise<IntegrationInfo[]> { export async function validateIntegrations(integrations: string[]): Promise<IntegrationInfo[]> {
const spinner = ora('Resolving packages...').start(); const spinner = ora('Resolving packages...').start();
try { try {

View file

@ -5,6 +5,7 @@ import ci from 'ci-info';
import { execa } from 'execa'; import { execa } from 'execa';
import { bold, cyan, dim, magenta } from 'kleur/colors'; import { bold, cyan, dim, magenta } from 'kleur/colors';
import ora from 'ora'; import ora from 'ora';
import preferredPM from 'preferred-pm';
import prompts from 'prompts'; import prompts from 'prompts';
import resolvePackage from 'resolve'; import resolvePackage from 'resolve';
import whichPm from 'which-pm'; import whichPm from 'which-pm';
@ -97,6 +98,30 @@ function getInstallCommand(packages: string[], packageManager: string) {
} }
} }
/**
* Get the command to execute and download a package (e.g. `npx`, `yarn dlx`, `pnpx`, etc.)
* @param packageManager - Optional package manager to use. If not provided, Astro will attempt to detect the preferred package manager.
* @returns The command to execute and download a package
*/
export async function getExecCommand(packageManager?: string): Promise<string> {
if (!packageManager) {
packageManager = (await preferredPM(process.cwd()))?.name ?? 'npm';
}
switch (packageManager) {
case 'npm':
return 'npx';
case 'yarn':
return 'yarn dlx';
case 'pnpm':
return 'pnpx';
case 'bun':
return 'bunx';
default:
return 'npx';
}
}
async function installPackage( async function installPackage(
packageNames: string[], packageNames: string[],
options: GetPackageOptions, options: GetPackageOptions,
@ -161,3 +186,56 @@ async function installPackage(
return false; return false;
} }
} }
export async function fetchPackageJson(
scope: string | undefined,
name: string,
tag: string
): Promise<Record<string, any> | Error> {
const packageName = `${scope ? `${scope}/` : ''}${name}`;
const registry = await getRegistry();
const res = await fetch(`${registry}/${packageName}/${tag}`);
if (res.status >= 200 && res.status < 300) {
return await res.json();
} else if (res.status === 404) {
// 404 means the package doesn't exist, so we don't need an error message here
return new Error();
} else {
return new Error(`Failed to fetch ${registry}/${packageName}/${tag} - GET ${res.status}`);
}
}
export async function fetchPackageVersions(packageName: string): Promise<string[] | Error> {
const registry = await getRegistry();
const res = await fetch(`${registry}/${packageName}`, {
headers: { accept: 'application/vnd.npm.install-v1+json' },
});
if (res.status >= 200 && res.status < 300) {
return await res.json().then((data) => Object.keys(data.versions));
} else if (res.status === 404) {
// 404 means the package doesn't exist, so we don't need an error message here
return new Error();
} else {
return new Error(`Failed to fetch ${registry}/${packageName} - GET ${res.status}`);
}
}
// Users might lack access to the global npm registry, this function
// checks the user's project type and will return the proper npm registry
//
// A copy of this function also exists in the create-astro package
let _registry: string;
export async function getRegistry(): Promise<string> {
if (_registry) return _registry;
const fallback = 'https://registry.npmjs.org';
const packageManager = (await preferredPM(process.cwd()))?.name || 'npm';
try {
const { stdout } = await execa(packageManager, ['config', 'get', 'registry']);
_registry = stdout?.trim()?.replace(/\/$/, '') || fallback;
// Detect cases where the shell command returned a non-URL (e.g. a warning)
if (!new URL(_registry).host) _registry = fallback;
} catch (e) {
_registry = fallback;
}
return _registry;
}

View file

@ -124,7 +124,7 @@ interface SubcommandOptions {
json?: boolean; json?: boolean;
} }
// Default `location` to "project" to avoid reading default preferencesa // Default `location` to "project" to avoid reading default preferences
async function getPreference( async function getPreference(
settings: AstroSettings, settings: AstroSettings,
key: PreferenceKey, key: PreferenceKey,

View file

@ -104,6 +104,7 @@ export function createBaseSettings(config: AstroConfig): AstroSettings {
watchFiles: [], watchFiles: [],
devToolbarApps: [], devToolbarApps: [],
timer: new AstroTimer(), timer: new AstroTimer(),
latestAstroVersion: undefined, // Will be set later if applicable when the dev server starts
}; };
} }

View file

@ -3,6 +3,7 @@ import type http from 'node:http';
import type { AddressInfo } from 'node:net'; import type { AddressInfo } from 'node:net';
import { green } from 'kleur/colors'; import { green } from 'kleur/colors';
import { performance } from 'perf_hooks'; import { performance } from 'perf_hooks';
import { gt, major, minor, patch } from 'semver';
import type * as vite from 'vite'; import type * as vite from 'vite';
import type { AstroInlineConfig } from '../../@types/astro.js'; import type { AstroInlineConfig } from '../../@types/astro.js';
import { attachContentServerListeners } from '../../content/index.js'; import { attachContentServerListeners } from '../../content/index.js';
@ -11,6 +12,11 @@ import * as msg from '../messages.js';
import { ensureProcessNodeEnv } from '../util.js'; import { ensureProcessNodeEnv } from '../util.js';
import { startContainer } from './container.js'; import { startContainer } from './container.js';
import { createContainerWithAutomaticRestart } from './restart.js'; import { createContainerWithAutomaticRestart } from './restart.js';
import {
MAX_PATCH_DISTANCE,
fetchLatestAstroVersion,
shouldCheckForUpdates,
} from './update-check.js';
export interface DevServer { export interface DevServer {
address: AddressInfo; address: AddressInfo;
@ -34,6 +40,45 @@ export default async function dev(inlineConfig: AstroInlineConfig): Promise<DevS
const restart = await createContainerWithAutomaticRestart({ inlineConfig, fs }); const restart = await createContainerWithAutomaticRestart({ inlineConfig, fs });
const logger = restart.container.logger; const logger = restart.container.logger;
const currentVersion = process.env.PACKAGE_VERSION ?? '0.0.0';
const isPrerelease = currentVersion.includes('-');
if (!isPrerelease) {
try {
// Don't await this, we don't want to block the dev server from starting
shouldCheckForUpdates(restart.container.settings.preferences).then(async (shouldCheck) => {
if (shouldCheck) {
const version = await fetchLatestAstroVersion(restart.container.settings.preferences);
if (gt(version, currentVersion)) {
// Only update the latestAstroVersion if the latest version is greater than the current version, that way we don't need to check that again
// whenever we check for the latest version elsewhere
restart.container.settings.latestAstroVersion = version;
const sameMajor = major(version) === major(currentVersion);
const sameMinor = minor(version) === minor(currentVersion);
const patchDistance = patch(version) - patch(currentVersion);
if (sameMajor && sameMinor && patchDistance < MAX_PATCH_DISTANCE) {
// Don't bother the user with a log if they're only a few patch versions behind
// We can still tell them in the dev toolbar, which has a more opt-in nature
return;
}
logger.warn(
'SKIP_FORMAT',
msg.newVersionAvailable({
latestVersion: version,
})
);
}
}
});
} catch (e) {
// Just ignore the error, we don't want to block the dev server from starting and this is just a nice-to-have feature
}
}
// Start listening to the port // Start listening to the port
const devServerAddressInfo = await startContainer(restart.container); const devServerAddressInfo = await startContainer(restart.container);
logger.info( logger.info(
@ -46,8 +91,7 @@ export default async function dev(inlineConfig: AstroInlineConfig): Promise<DevS
}) })
); );
const currentVersion = process.env.PACKAGE_VERSION ?? '0.0.0'; if (isPrerelease) {
if (currentVersion.includes('-')) {
logger.warn('SKIP_FORMAT', msg.prerelease({ currentVersion })); logger.warn('SKIP_FORMAT', msg.prerelease({ currentVersion }));
} }
if (restart.container.viteServer.config.server?.fs?.strict === false) { if (restart.container.viteServer.config.server?.fs?.strict === false) {

View file

@ -47,8 +47,13 @@ export function shouldRestartContainer(
// Otherwise, watch for any astro.config.* file changes in project root // Otherwise, watch for any astro.config.* file changes in project root
else { else {
const normalizedChangedFile = vite.normalizePath(changedFile); const normalizedChangedFile = vite.normalizePath(changedFile);
shouldRestart = shouldRestart = configRE.test(normalizedChangedFile);
configRE.test(normalizedChangedFile) || preferencesRE.test(normalizedChangedFile);
if (preferencesRE.test(normalizedChangedFile)) {
shouldRestart = settings.preferences.ignoreNextPreferenceReload ? false : true;
settings.preferences.ignoreNextPreferenceReload = false;
}
} }
if (!shouldRestart && settings.watchFiles.length > 0) { if (!shouldRestart && settings.watchFiles.length > 0) {

View file

@ -0,0 +1,49 @@
import ci from 'ci-info';
import { fetchPackageJson } from '../../cli/install-package.js';
import type { AstroPreferences } from '../../preferences/index.js';
export const MAX_PATCH_DISTANCE = 5; // If the patch distance is less than this, don't bother the user
const CHECK_MS_INTERVAL = 1_036_800_000; // 12 days, give or take
let _latestVersion: string | undefined = undefined;
export async function fetchLatestAstroVersion(
preferences: AstroPreferences | undefined
): Promise<string> {
if (_latestVersion) {
return _latestVersion;
}
const packageJson = await fetchPackageJson(undefined, 'astro', 'latest');
if (packageJson instanceof Error) {
throw packageJson;
}
const version = packageJson?.version;
if (!version) {
throw new Error('Failed to fetch latest Astro version');
}
if (preferences) {
await preferences.set('_variables.lastUpdateCheck', Date.now(), { reloadServer: false });
}
_latestVersion = version;
return version;
}
export async function shouldCheckForUpdates(preferences: AstroPreferences): Promise<boolean> {
if (ci.isCI) {
return false;
}
const timeSinceLastCheck = Date.now() - (await preferences.get('_variables.lastUpdateCheck'));
const hasCheckUpdatesEnabled = await preferences.get('checkUpdates.enabled');
return (
timeSinceLastCheck > CHECK_MS_INTERVAL &&
process.env.ASTRO_DISABLE_UPDATE_CHECK !== 'true' &&
hasCheckUpdatesEnabled
);
}

View file

@ -29,6 +29,7 @@ export type LoggerLabel =
| 'redirects' | 'redirects'
| 'toolbar' | 'toolbar'
| 'assets' | 'assets'
| 'update'
// SKIP_FORMAT: A special label that tells the logger not to apply any formatting. // SKIP_FORMAT: A special label that tells the logger not to apply any formatting.
// Useful for messages that are already formatted, like the server start message. // Useful for messages that are already formatted, like the server start message.
| 'SKIP_FORMAT'; | 'SKIP_FORMAT';

View file

@ -16,6 +16,7 @@ import {
} from 'kleur/colors'; } from 'kleur/colors';
import type { ResolvedServerUrls } from 'vite'; import type { ResolvedServerUrls } from 'vite';
import type { ZodError } from 'zod'; import type { ZodError } from 'zod';
import { getExecCommand } from '../cli/install-package.js';
import { getDocsForError, renderErrorMarkdown } from './errors/dev/utils.js'; import { getDocsForError, renderErrorMarkdown } from './errors/dev/utils.js';
import { import {
AstroError, AstroError,
@ -104,6 +105,15 @@ export function serverShortcuts({ key, label }: { key: string; label: string }):
return [dim(' Press'), key, dim('to'), label].join(' '); return [dim(' Press'), key, dim('to'), label].join(' ');
} }
export function newVersionAvailable({ latestVersion }: { latestVersion: string }) {
const badge = bgYellow(black(` update `));
const headline = yellow(`▶ New version of Astro available: ${latestVersion}`);
const execCommand = getExecCommand();
const details = ` Run ${cyan(`${execCommand} @astrojs/upgrade`)} to update`;
return ['', `${badge} ${headline}`, details, ''].join('\n');
}
export function telemetryNotice() { export function telemetryNotice() {
const headline = blue(`▶ Astro collects anonymous usage data.`); const headline = blue(`▶ Astro collects anonymous usage data.`);
const why = ' This information helps us improve Astro.'; const why = ' This information helps us improve Astro.';

View file

@ -3,6 +3,16 @@ export const DEFAULT_PREFERENCES = {
/** Specifies whether the user has the Dev Overlay enabled */ /** Specifies whether the user has the Dev Overlay enabled */
enabled: true, enabled: true,
}, },
checkUpdates: {
/** Specifies whether the user has the update check enabled */
enabled: true,
},
// Temporary variables that shouldn't be exposed to the users in the CLI, but are still useful to store in preferences
_variables: {
/** Time since last update check */
lastUpdateCheck: 0,
},
}; };
export type Preferences = typeof DEFAULT_PREFERENCES; export type Preferences = typeof DEFAULT_PREFERENCES;
export type PublicPreferences = Omit<Preferences, '_variables'>;

View file

@ -6,7 +6,7 @@ import process from 'node:process';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import dget from 'dlv'; import dget from 'dlv';
import { DEFAULT_PREFERENCES, type Preferences } from './defaults.js'; import { DEFAULT_PREFERENCES, type Preferences, type PublicPreferences } from './defaults.js';
import { PreferenceStore } from './store.js'; import { PreferenceStore } from './store.js';
type DotKeys<T> = T extends object type DotKeys<T> = T extends object
@ -25,6 +25,13 @@ export type GetDotKey<
export type PreferenceLocation = 'global' | 'project'; export type PreferenceLocation = 'global' | 'project';
export interface PreferenceOptions { export interface PreferenceOptions {
location?: PreferenceLocation; location?: PreferenceLocation;
/**
* If `true`, the server will be reloaded after setting the preference.
* If `false`, the server will not be reloaded after setting the preference.
*
* Defaults to `true`.
*/
reloadServer?: boolean;
} }
type DeepPartial<T> = T extends object type DeepPartial<T> = T extends object
@ -34,9 +41,9 @@ type DeepPartial<T> = T extends object
: T; : T;
export type PreferenceKey = DotKeys<Preferences>; export type PreferenceKey = DotKeys<Preferences>;
export interface PreferenceList extends Record<PreferenceLocation, DeepPartial<Preferences>> { export interface PreferenceList extends Record<PreferenceLocation, DeepPartial<PublicPreferences>> {
fromAstroConfig: DeepPartial<Preferences>; fromAstroConfig: DeepPartial<Preferences>;
defaults: Preferences; defaults: PublicPreferences;
} }
export interface AstroPreferences { export interface AstroPreferences {
@ -49,8 +56,9 @@ export interface AstroPreferences {
value: GetDotKey<Preferences, Key>, value: GetDotKey<Preferences, Key>,
opts?: PreferenceOptions opts?: PreferenceOptions
): Promise<void>; ): Promise<void>;
getAll(): Promise<Preferences>; getAll(): Promise<PublicPreferences>;
list(opts?: PreferenceOptions): Promise<PreferenceList>; list(opts?: PreferenceOptions): Promise<PreferenceList>;
ignoreNextPreferenceReload: boolean;
} }
export function isValidKey(key: string): key is PreferenceKey { export function isValidKey(key: string): key is PreferenceKey {
@ -84,23 +92,32 @@ export default function createPreferences(config: AstroConfig): AstroPreferences
if (!location) return project.get(key) ?? global.get(key) ?? dget(DEFAULT_PREFERENCES, key); if (!location) return project.get(key) ?? global.get(key) ?? dget(DEFAULT_PREFERENCES, key);
return stores[location].get(key); return stores[location].get(key);
}, },
async set(key, value, { location = 'project' } = {}) { async set(key, value, { location = 'project', reloadServer = true } = {}) {
stores[location].set(key, value); stores[location].set(key, value);
if (!reloadServer) {
this.ignoreNextPreferenceReload = true;
}
}, },
async getAll() { async getAll() {
return Object.assign( const allPrefs = Object.assign(
{}, {},
DEFAULT_PREFERENCES, DEFAULT_PREFERENCES,
stores['global'].getAll(), stores['global'].getAll(),
stores['project'].getAll() stores['project'].getAll()
); );
const { _variables, ...prefs } = allPrefs;
return prefs;
}, },
async list() { async list() {
const { _variables, ...defaultPrefs } = DEFAULT_PREFERENCES;
return { return {
global: stores['global'].getAll(), global: stores['global'].getAll(),
project: stores['project'].getAll(), project: stores['project'].getAll(),
fromAstroConfig: mapFrom(DEFAULT_PREFERENCES, config), fromAstroConfig: mapFrom(DEFAULT_PREFERENCES, config),
defaults: DEFAULT_PREFERENCES, defaults: defaultPrefs,
}; };
function mapFrom(defaults: Preferences, astroConfig: Record<string, any>) { function mapFrom(defaults: Preferences, astroConfig: Record<string, any>) {
@ -109,6 +126,7 @@ export default function createPreferences(config: AstroConfig): AstroPreferences
); );
} }
}, },
ignoreNextPreferenceReload: false,
}; };
} }

View file

@ -89,6 +89,9 @@ export default {
}, },
]; ];
const hasNewerVersion = (window as DevToolbarMetadata).__astro_dev_toolbar__
.latestAstroVersion;
const windowComponent = createWindowElement( const windowComponent = createWindowElement(
`<style> `<style>
#buttons-container { #buttons-container {
@ -333,6 +336,14 @@ export default {
<astro-dev-toolbar-badge badge-style="gray" size="large">${ <astro-dev-toolbar-badge badge-style="gray" size="large">${
(window as DevToolbarMetadata).__astro_dev_toolbar__.version (window as DevToolbarMetadata).__astro_dev_toolbar__.version
}</astro-dev-toolbar-badge> }</astro-dev-toolbar-badge>
${
hasNewerVersion
? `<astro-dev-toolbar-badge badge-style="green" size="large">${
(window as DevToolbarMetadata).__astro_dev_toolbar__.latestAstroVersion
} available!</astro-dev-toolbar-badge>
`
: ''
}
</section> </section>
<astro-dev-toolbar-button id="copy-debug-button">Copy debug info <astro-dev-toolbar-icon icon="copy" /></astro-dev-toolbar-button> <astro-dev-toolbar-button id="copy-debug-button">Copy debug info <astro-dev-toolbar-icon icon="copy" /></astro-dev-toolbar-button>
</header> </header>

View file

@ -83,6 +83,7 @@ export class DevPipeline extends Pipeline {
const additionalMetadata: DevToolbarMetadata['__astro_dev_toolbar__'] = { const additionalMetadata: DevToolbarMetadata['__astro_dev_toolbar__'] = {
root: url.fileURLToPath(settings.config.root), root: url.fileURLToPath(settings.config.root),
version: ASTRO_VERSION, version: ASTRO_VERSION,
latestAstroVersion: settings.latestAstroVersion,
debugInfo: await getInfoOutput({ userConfig: settings.config, print: false }), debugInfo: await getInfoOutput({ userConfig: settings.config, print: false }),
}; };