diff --git a/packages/nuxt/package.json b/packages/nuxt/package.json index 57401fa838..1ab40d4c68 100644 --- a/packages/nuxt/package.json +++ b/packages/nuxt/package.json @@ -72,6 +72,8 @@ "acorn": "8.12.0", "c12": "^1.11.1", "chokidar": "^3.6.0", + "compatx": "^0.1.8", + "consola": "^3.2.3", "cookie-es": "^1.1.0", "defu": "^6.1.4", "destr": "^2.0.3", diff --git a/packages/nuxt/src/core/nuxt.ts b/packages/nuxt/src/core/nuxt.ts index 53aa4d6d84..692e9d8c24 100644 --- a/packages/nuxt/src/core/nuxt.ts +++ b/packages/nuxt/src/core/nuxt.ts @@ -10,12 +10,18 @@ import type { Nuxt, NuxtHooks, NuxtModule, NuxtOptions } from 'nuxt/schema' import type { PackageJson } from 'pkg-types' import { readPackageJSON, resolvePackageJSON } from 'pkg-types' import { hash } from 'ohash' +import consola from 'consola' +import { colorize } from 'consola/utils' +import { updateConfig } from 'c12/update' +import { formatDate } from 'compatx' +import type { DateString } from 'compatx' import escapeRE from 'escape-string-regexp' import { withTrailingSlash, withoutLeadingSlash } from 'ufo' import defu from 'defu' import { gt, satisfies } from 'semver' +import { hasTTY, isCI } from 'std-env' import pagesModule from '../pages/module' import metaModule from '../head/module' import componentsModule from '../components/module' @@ -60,6 +66,9 @@ export function createNuxt (options: NuxtOptions): Nuxt { return nuxt } +// TODO: update to nitro import +const fallbackCompatibilityDate = '2024-04-03' as DateString + const nightlies = { 'nitropack': 'nitropack-nightly', 'nitro': 'nitro-nightly', @@ -74,6 +83,8 @@ const keyDependencies = [ '@nuxt/schema', ] +let warnedAboutCompatDate = false + async function initNuxt (nuxt: Nuxt) { // Register user hooks for (const config of nuxt.options._layers.map(layer => layer.config).reverse()) { @@ -82,6 +93,59 @@ async function initNuxt (nuxt: Nuxt) { } } + // Prompt to set compatibility date + if (!nuxt.options.compatibilityDate) { + const todaysDate = formatDate(new Date()) + + if (!warnedAboutCompatDate) { + // Print warning + console.info(`Nuxt now supports pinning the behavior of provider and deployment presets with a compatibility date. We recommend you specify a \`compatibilityDate\` in your \`nuxt.config\` file.`) + } + + // Prompt to update in dev mode + if (!warnedAboutCompatDate && nuxt.options.dev && hasTTY && !isCI) { + const result = await consola.prompt(`Do you want to update your ${colorize('cyan', 'nuxt.config')} to set ${colorize('cyan', `compatibilityDate: '${todaysDate}'`)}?`, { + type: 'confirm', + default: true, + }) + if (result === true) { + const res = await updateConfig({ + configFile: 'nuxt.config', + cwd: nuxt.options.rootDir, + async onCreate ({ configFile }) { + const shallCreate = await consola.prompt(`Do you want to create ${colorize('cyan', relative(nuxt.options.rootDir, configFile))}?`, { + type: 'confirm', + default: true, + }) + if (shallCreate !== true) { + return false + } + return _getDefaultNuxtConfig() + }, + onUpdate (config) { + config.compatibilityDate = todaysDate + }, + }).catch((error) => { + consola.error(`Failed to update config: ${error.message}`) + return null + }) + if (res?.configFile) { + nuxt.options.compatibilityDate = todaysDate + consola.success(`Compatibility date set to \`${todaysDate}\` in \`${relative(nuxt.options.rootDir, res.configFile)}\``) + } + } + } + + if (!nuxt.options.compatibilityDate) { + nuxt.options.compatibilityDate = fallbackCompatibilityDate + if (!warnedAboutCompatDate) { + console.log(`Using \`${fallbackCompatibilityDate}\` as fallback compatibility date.`) + } + } + + warnedAboutCompatDate = true + } + // Restart Nuxt when layer directories are added or removed const layersDir = withTrailingSlash(resolve(nuxt.options.rootDir, 'layers')) nuxt.hook('builder:watch', (event, relativePath) => { @@ -762,3 +826,10 @@ function createPortalProperties (sourceValue: any, options: NuxtOptions, paths: }) } } + +const _getDefaultNuxtConfig = () => /* js */ +`// https://nuxt.com/docs/api/configuration/nuxt-config +export default defineNuxtConfig({ + devtools: { enabled: true } +}) +` diff --git a/playground/nuxt.config.ts b/playground/nuxt.config.ts index 9e36a9d496..9b576aa6e5 100644 --- a/playground/nuxt.config.ts +++ b/playground/nuxt.config.ts @@ -1,3 +1,4 @@ export default defineNuxtConfig({ + compatibilityDate: '2024-06-28', devtools: { enabled: true }, }) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d38d82f326..b243d07226 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -287,6 +287,12 @@ importers: chokidar: specifier: ^3.6.0 version: 3.6.0 + compatx: + specifier: ^0.1.8 + version: 0.1.8 + consola: + specifier: ^3.2.3 + version: 3.2.3 cookie-es: specifier: ^1.1.0 version: 1.1.0 diff --git a/test/fixtures/basic-types/nuxt.config.ts b/test/fixtures/basic-types/nuxt.config.ts index 2971df07b3..2a573895d0 100644 --- a/test/fixtures/basic-types/nuxt.config.ts +++ b/test/fixtures/basic-types/nuxt.config.ts @@ -1,6 +1,7 @@ import { addTypeTemplate, installModule } from 'nuxt/kit' export default defineNuxtConfig({ + compatibilityDate: '2024-06-28', experimental: { typedPages: true, appManifest: true, diff --git a/test/fixtures/basic/nuxt.config.ts b/test/fixtures/basic/nuxt.config.ts index bbdee24c87..340185b78a 100644 --- a/test/fixtures/basic/nuxt.config.ts +++ b/test/fixtures/basic/nuxt.config.ts @@ -12,6 +12,7 @@ declare module 'nitro/types' { } export default defineNuxtConfig({ + compatibilityDate: '2024-06-28', app: { pageTransition: true, layoutTransition: true, diff --git a/test/fixtures/minimal-types/nuxt.config.ts b/test/fixtures/minimal-types/nuxt.config.ts index df9525e993..7b480b19f2 100644 --- a/test/fixtures/minimal-types/nuxt.config.ts +++ b/test/fixtures/minimal-types/nuxt.config.ts @@ -1,3 +1,4 @@ export default defineNuxtConfig({ + compatibilityDate: '2024-06-28', experimental: { appManifest: true }, }) diff --git a/test/fixtures/minimal/nuxt.config.ts b/test/fixtures/minimal/nuxt.config.ts index 317311d645..bf1b59d106 100644 --- a/test/fixtures/minimal/nuxt.config.ts +++ b/test/fixtures/minimal/nuxt.config.ts @@ -3,6 +3,7 @@ import { fileURLToPath } from 'node:url' const testWithInlineVue = process.env.EXTERNAL_VUE === 'false' export default defineNuxtConfig({ + compatibilityDate: '2024-06-28', pages: false, experimental: { externalVue: !testWithInlineVue, diff --git a/test/fixtures/runtime-compiler/nuxt.config.ts b/test/fixtures/runtime-compiler/nuxt.config.ts index 9617ba123e..b61d642360 100644 --- a/test/fixtures/runtime-compiler/nuxt.config.ts +++ b/test/fixtures/runtime-compiler/nuxt.config.ts @@ -1,5 +1,6 @@ // https://nuxt.com/docs/api/nuxt-config export default defineNuxtConfig({ + compatibilityDate: '2024-06-28', experimental: { externalVue: false, }, diff --git a/test/fixtures/suspense/nuxt.config.ts b/test/fixtures/suspense/nuxt.config.ts index 9758731515..38e3ecfcbf 100644 --- a/test/fixtures/suspense/nuxt.config.ts +++ b/test/fixtures/suspense/nuxt.config.ts @@ -3,6 +3,7 @@ import { fileURLToPath } from 'node:url' const testWithInlineVue = process.env.EXTERNAL_VUE === 'false' export default defineNuxtConfig({ + compatibilityDate: '2024-06-28', experimental: { externalVue: !testWithInlineVue, },