diff --git a/.github/workflows/build_test_deploy.yml b/.github/workflows/build_test_deploy.yml index 6c43b333a5843..20cf54fb8bf28 100644 --- a/.github/workflows/build_test_deploy.yml +++ b/.github/workflows/build_test_deploy.yml @@ -1072,19 +1072,6 @@ jobs: if: ${{ steps.docs-change.outputs.DOCS_CHANGE == 'nope' }} run: node scripts/normalize-version-bump.js - # We use restore-key to pick latest cache. - # We will not get exact match, but doc says - # "If there are multiple partial matches for a restore key, the action returns the most recently created cache." - # So we get latest cache - # - name: Cache built files - # uses: actions/cache@v3 - # timeout-minutes: 5 - # with: - # path: ./packages/next-swc/target - # key: next-swc-cargo-cache-dev-ubuntu-latest-${{ hashFiles('**/Cargo.lock') }} - # restore-keys: | - # next-swc-cargo-cache-dev-ubuntu-latest - - name: Build in docker uses: addnab/docker-run-action@v3 if: ${{ steps.docs-change.outputs.DOCS_CHANGE == 'nope' }} @@ -1327,18 +1314,6 @@ jobs: # we use checkout here instead of the build cache since # it can fail to restore in different OS' - uses: actions/checkout@v3 - # We use restore-key to pick latest cache. - # We will not get exact match, but doc says - # "If there are multiple partial matches for a restore key, the action returns the most recently created cache." - # So we get latest cache - - name: Cache built files - uses: actions/cache@v3 - timeout-minutes: 5 - with: - path: ./packages/next-swc/target - key: next-swc-cargo-cache-${{ matrix.settings.target }}--${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - next-swc-cargo-cache-${{ matrix.settings.target }} - name: Setup node uses: actions/setup-node@v3 diff --git a/docs/api-reference/next/image.md b/docs/api-reference/next/image.md index 3174c42aced7b..f9418a3eea9b7 100644 --- a/docs/api-reference/next/image.md +++ b/docs/api-reference/next/image.md @@ -16,6 +16,7 @@ description: Enable Image Optimization with the built-in Image component. | Version | Changes | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `v13.2.0` | `contentDispositionType` configuration added. | | `v13.0.6` | `ref` prop added. | | `v13.0.0` | `` wrapper removed. `layout`, `objectFit`, `objectPosition`, `lazyBoundary`, `lazyRoot` props removed. `alt` is required. `onLoadingComplete` receives reference to `img` element. Built-in loader config removed. | | `v12.3.0` | `remotePatterns` and `unoptimized` configuration is stable. | @@ -503,17 +504,20 @@ module.exports = { The default [loader](#loader) does not optimize SVG images for a few reasons. First, SVG is a vector format meaning it can be resized losslessly. Second, SVG has many of the same features as HTML/CSS, which can lead to vulnerabilities without proper [Content Security Policy (CSP) headers](/docs/advanced-features/security-headers.md). -If you need to serve SVG images with the default Image Optimization API, you can set `dangerouslyAllowSVG` and `contentSecurityPolicy` inside your `next.config.js`: +If you need to serve SVG images with the default Image Optimization API, you can set `dangerouslyAllowSVG` inside your `next.config.js`: ```js module.exports = { images: { dangerouslyAllowSVG: true, + contentDispositionType: 'attachment', contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;", }, } ``` +In addition, it is strongly recommended to also set `contentDispositionType` to force the browser to download the image, as well as `contentSecurityPolicy` to prevent scripts embedded in the image from executing. + ### Animated Images The default [loader](#loader) will automatically bypass Image Optimization for animated images and serve the image as-is. diff --git a/docs/api-reference/next/legacy/image.md b/docs/api-reference/next/legacy/image.md index c6b8176c4d169..f5ef5d2468b13 100644 --- a/docs/api-reference/next/legacy/image.md +++ b/docs/api-reference/next/legacy/image.md @@ -571,17 +571,20 @@ module.exports = { The default [loader](#loader) does not optimize SVG images for a few reasons. First, SVG is a vector format meaning it can be resized losslessly. Second, SVG has many of the same features as HTML/CSS, which can lead to vulnerabilities without proper [Content Security Policy (CSP) headers](/docs/advanced-features/security-headers.md). -If you need to serve SVG images with the default Image Optimization API, you can set `dangerouslyAllowSVG` and `contentSecurityPolicy` inside your `next.config.js`: +If you need to serve SVG images with the default Image Optimization API, you can set `dangerouslyAllowSVG` inside your `next.config.js`: ```js module.exports = { images: { dangerouslyAllowSVG: true, + contentDispositionType: 'attachment', contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;", }, } ``` +In addition, it is strongly recommended to also set `contentDispositionType` to force the browser to download the image, as well as `contentSecurityPolicy` to prevent scripts embedded in the image from executing. + ### Animated Images The default [loader](#loader) will automatically bypass Image Optimization for animated images and serve the image as-is. diff --git a/errors/invalid-images-config.md b/errors/invalid-images-config.md index badaa4f3e1a88..d76741e61f3d9 100644 --- a/errors/invalid-images-config.md +++ b/errors/invalid-images-config.md @@ -33,6 +33,8 @@ module.exports = { dangerouslyAllowSVG: false, // set the Content-Security-Policy header contentSecurityPolicy: "default-src 'self'; script-src 'none'; sandbox;", + // sets the Content-Disposition header (inline or attachment) + contentDispositionType: 'inline', // limit of 50 objects remotePatterns: [], // when true, every image will be unoptimized diff --git a/packages/next-swc/Cargo.lock b/packages/next-swc/Cargo.lock index 3754d2e472572..b35a3d4a268a6 100644 --- a/packages/next-swc/Cargo.lock +++ b/packages/next-swc/Cargo.lock @@ -126,7 +126,7 @@ dependencies = [ [[package]] name = "auto-hash-map" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "serde", ] @@ -2285,7 +2285,7 @@ checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" [[package]] name = "next-binding" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "mdxjs", "modularize_imports", @@ -2301,7 +2301,7 @@ dependencies = [ [[package]] name = "next-core" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "anyhow", "auto-hash-map", @@ -2331,7 +2331,7 @@ dependencies = [ [[package]] name = "next-dev" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "anyhow", "dunce", @@ -2357,7 +2357,7 @@ dependencies = [ [[package]] name = "next-font" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "fxhash", "serde", @@ -2411,7 +2411,7 @@ dependencies = [ [[package]] name = "next-transform-dynamic" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "pathdiff", "swc_core", @@ -2420,7 +2420,7 @@ dependencies = [ [[package]] name = "next-transform-strip-page-exports" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "fxhash", "swc_core", @@ -2430,7 +2430,7 @@ dependencies = [ [[package]] name = "node-file-trace" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "anyhow", "clap", @@ -5458,7 +5458,7 @@ dependencies = [ [[package]] name = "turbo-malloc" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "mimalloc", ] @@ -5466,7 +5466,7 @@ dependencies = [ [[package]] name = "turbo-tasks" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "anyhow", "auto-hash-map", @@ -5496,7 +5496,7 @@ dependencies = [ [[package]] name = "turbo-tasks-build" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "anyhow", "cargo-lock", @@ -5508,7 +5508,7 @@ dependencies = [ [[package]] name = "turbo-tasks-env" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "anyhow", "dotenvy", @@ -5522,7 +5522,7 @@ dependencies = [ [[package]] name = "turbo-tasks-fetch" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "anyhow", "indexmap", @@ -5539,7 +5539,7 @@ dependencies = [ [[package]] name = "turbo-tasks-fs" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "anyhow", "auto-hash-map", @@ -5566,7 +5566,7 @@ dependencies = [ [[package]] name = "turbo-tasks-hash" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "base16", "hex", @@ -5578,7 +5578,7 @@ dependencies = [ [[package]] name = "turbo-tasks-macros" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "anyhow", "convert_case 0.5.0", @@ -5592,7 +5592,7 @@ dependencies = [ [[package]] name = "turbo-tasks-macros-shared" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "proc-macro2", "quote", @@ -5602,7 +5602,7 @@ dependencies = [ [[package]] name = "turbo-tasks-memory" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "anyhow", "auto-hash-map", @@ -5624,7 +5624,7 @@ dependencies = [ [[package]] name = "turbopack" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "anyhow", "indexmap", @@ -5649,7 +5649,7 @@ dependencies = [ [[package]] name = "turbopack-cli-utils" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "anyhow", "clap", @@ -5665,7 +5665,7 @@ dependencies = [ [[package]] name = "turbopack-core" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "anyhow", "async-trait", @@ -5691,7 +5691,7 @@ dependencies = [ [[package]] name = "turbopack-css" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "anyhow", "async-trait", @@ -5713,7 +5713,7 @@ dependencies = [ [[package]] name = "turbopack-dev-server" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "anyhow", "futures", @@ -5743,7 +5743,7 @@ dependencies = [ [[package]] name = "turbopack-ecmascript" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "anyhow", "async-trait", @@ -5782,7 +5782,7 @@ dependencies = [ [[package]] name = "turbopack-env" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "anyhow", "serde", @@ -5797,7 +5797,7 @@ dependencies = [ [[package]] name = "turbopack-json" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "anyhow", "serde", @@ -5812,7 +5812,7 @@ dependencies = [ [[package]] name = "turbopack-mdx" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "anyhow", "mdxjs", @@ -5827,7 +5827,7 @@ dependencies = [ [[package]] name = "turbopack-node" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "anyhow", "futures", @@ -5840,6 +5840,7 @@ dependencies = [ "tokio", "turbo-tasks", "turbo-tasks-build", + "turbo-tasks-env", "turbo-tasks-fs", "turbopack-core", "turbopack-dev-server", @@ -5850,7 +5851,7 @@ dependencies = [ [[package]] name = "turbopack-static" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "anyhow", "serde", @@ -5866,7 +5867,7 @@ dependencies = [ [[package]] name = "turbopack-swc-utils" version = "0.1.0" -source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230221.3#8fcf5d1123df0e00e381d13c8fc2e2b3e33131a8" +source = "git+https://github.com/vercel/turbo.git?tag=turbopack-230222.2#9942db86699dc191f6572a619daf3a5e23732a22" dependencies = [ "swc_core", "turbo-tasks", diff --git a/packages/next-swc/crates/core/Cargo.toml b/packages/next-swc/crates/core/Cargo.toml index 21c927b29e97d..dec593494c049 100644 --- a/packages/next-swc/crates/core/Cargo.toml +++ b/packages/next-swc/crates/core/Cargo.toml @@ -19,7 +19,7 @@ serde = "1" serde_json = "1" tracing = { version = "0.1.37", features = ["release_max_level_info"] } -next-binding = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230221.3", features = [ +next-binding = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230222.2", features = [ "__swc_core", "__swc_core_next_core", "__swc_transform_styled_jsx", @@ -29,7 +29,7 @@ next-binding = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-2 ] } [dev-dependencies] -next-binding = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230221.3", features = [ +next-binding = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230222.2", features = [ "__swc_core_testing_transform", "__swc_testing", ] } diff --git a/packages/next-swc/crates/napi/Cargo.toml b/packages/next-swc/crates/napi/Cargo.toml index a60d20fec26e1..f0128da1ec914 100644 --- a/packages/next-swc/crates/napi/Cargo.toml +++ b/packages/next-swc/crates/napi/Cargo.toml @@ -39,10 +39,10 @@ tracing = { version = "0.1.37", features = ["release_max_level_info"] } tracing-futures = "0.2.5" tracing-subscriber = "0.3.9" tracing-chrome = "0.5.0" -turbo-malloc = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230221.3" } -turbo-tasks = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230221.3" } -turbo-tasks-memory = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230221.3" } -next-binding = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230221.3", features = [ +turbo-malloc = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230222.2" } +turbo-tasks = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230222.2" } +turbo-tasks-memory = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230222.2" } +next-binding = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230222.2", features = [ "__swc_core_binding_napi", "__turbo_next_dev_server", "__turbo_node_file_trace", diff --git a/packages/next-swc/crates/wasm/Cargo.toml b/packages/next-swc/crates/wasm/Cargo.toml index a3c8d28e2fb1b..b13e9b825fabd 100644 --- a/packages/next-swc/crates/wasm/Cargo.toml +++ b/packages/next-swc/crates/wasm/Cargo.toml @@ -31,7 +31,7 @@ wasm-bindgen-futures = "0.4.8" getrandom = { version = "0.2.5", optional = true, default-features = false } js-sys = "0.3.59" serde-wasm-bindgen = "0.4.3" -next-binding = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230221.3", features = [ +next-binding = { git = "https://github.com/vercel/turbo.git", tag = "turbopack-230222.2", features = [ "__swc_core_binding_wasm", "__feature_mdx_rs", ] } diff --git a/packages/next/src/client/components/react-dev-overlay/internal/components/Terminal/Terminal.tsx b/packages/next/src/client/components/react-dev-overlay/internal/components/Terminal/Terminal.tsx index ed61d5fd13e3a..e563d69b26c6c 100644 --- a/packages/next/src/client/components/react-dev-overlay/internal/components/Terminal/Terminal.tsx +++ b/packages/next/src/client/components/react-dev-overlay/internal/components/Terminal/Terminal.tsx @@ -1,5 +1,6 @@ import Anser from 'next/dist/compiled/anser' import * as React from 'react' +import { HotlinkedText } from '../hot-linked-text' import { EditorLink } from './EditorLink' export type TerminalProps = { content: string } @@ -59,7 +60,7 @@ export const Terminal: React.FC = function Terminal({ : undefined), }} > - {entry.content} + ))} {editorLinks.map((file) => ( diff --git a/packages/next/src/client/components/react-dev-overlay/internal/components/Terminal/styles.tsx b/packages/next/src/client/components/react-dev-overlay/internal/components/Terminal/styles.tsx index f9bee2f8c5763..bf96e64248fa6 100644 --- a/packages/next/src/client/components/react-dev-overlay/internal/components/Terminal/styles.tsx +++ b/packages/next/src/client/components/react-dev-overlay/internal/components/Terminal/styles.tsx @@ -40,6 +40,9 @@ const styles = css` [data-with-open-in-editor-link] { margin-left: var(--size-gap-double); } + [data-nextjs-terminal] a { + color: inherit; + } ` export { styles } diff --git a/packages/next/src/client/components/react-dev-overlay/internal/components/hot-linked-text/get-words-and-whitespaces.test.ts b/packages/next/src/client/components/react-dev-overlay/internal/components/hot-linked-text/get-words-and-whitespaces.test.ts new file mode 100644 index 0000000000000..9105ff91c8fc8 --- /dev/null +++ b/packages/next/src/client/components/react-dev-overlay/internal/components/hot-linked-text/get-words-and-whitespaces.test.ts @@ -0,0 +1,17 @@ +import { getWordsAndWhitespaces } from './get-words-and-whitespaces' + +describe('getWordsAndWhitespaces', () => { + it('should return sequences of words and whitespaces', () => { + const text = ' \n\nhello world https://nextjs.org/\nhttps://nextjs.org/' + expect(getWordsAndWhitespaces(text)).toEqual([ + ' \n\n', + 'hello', + ' ', + 'world', + ' ', + 'https://nextjs.org/', + '\n', + 'https://nextjs.org/', + ]) + }) +}) diff --git a/packages/next/src/client/components/react-dev-overlay/internal/components/hot-linked-text/get-words-and-whitespaces.ts b/packages/next/src/client/components/react-dev-overlay/internal/components/hot-linked-text/get-words-and-whitespaces.ts new file mode 100644 index 0000000000000..a1632bdb12dbe --- /dev/null +++ b/packages/next/src/client/components/react-dev-overlay/internal/components/hot-linked-text/get-words-and-whitespaces.ts @@ -0,0 +1,37 @@ +function isWhitespace(char: string) { + return char === ' ' || char === '\n' +} + +/** + * Get sequences of words and whitespaces from a string. + * + * e.g. "Hello world \n\n" -> ["Hello", " ", "world", " \n\n"] + */ +export function getWordsAndWhitespaces(text: string) { + const wordsAndWhitespaces: string[] = [] + + let current = '' + let currentIsWhitespace = false + for (const char of text) { + if (current.length === 0) { + current += char + currentIsWhitespace = isWhitespace(char) + continue + } + + const nextIsWhitespace = isWhitespace(char) + if (currentIsWhitespace === nextIsWhitespace) { + current += char + } else { + wordsAndWhitespaces.push(current) + current = char + currentIsWhitespace = nextIsWhitespace + } + } + + if (current.length > 0) { + wordsAndWhitespaces.push(current) + } + + return wordsAndWhitespaces +} diff --git a/packages/next/src/client/components/react-dev-overlay/internal/components/hot-linked-text/index.tsx b/packages/next/src/client/components/react-dev-overlay/internal/components/hot-linked-text/index.tsx new file mode 100644 index 0000000000000..2dde5aba96952 --- /dev/null +++ b/packages/next/src/client/components/react-dev-overlay/internal/components/hot-linked-text/index.tsx @@ -0,0 +1,29 @@ +import React from 'react' +import { getWordsAndWhitespaces } from './get-words-and-whitespaces' + +const linkRegex = /https?:\/\/[^\s/$.?#].[^\s"]*/i + +export const HotlinkedText: React.FC<{ + text: string +}> = function HotlinkedText(props) { + const { text } = props + + const wordsAndWhitespaces = getWordsAndWhitespaces(text) + + return ( + <> + {linkRegex.test(text) + ? wordsAndWhitespaces.map((word, index) => { + if (linkRegex.test(word)) { + return ( + + {word} + + ) + } + return {word} + }) + : text} + + ) +} diff --git a/packages/next/src/client/components/react-dev-overlay/internal/container/Errors.tsx b/packages/next/src/client/components/react-dev-overlay/internal/container/Errors.tsx index 5a2cc24c5137c..0714bdd55b5ba 100644 --- a/packages/next/src/client/components/react-dev-overlay/internal/container/Errors.tsx +++ b/packages/next/src/client/components/react-dev-overlay/internal/container/Errors.tsx @@ -21,6 +21,7 @@ import { CloseIcon } from '../icons/CloseIcon' import { RuntimeError } from './RuntimeError' import { VersionStalenessInfo } from '../components/VersionStalenessInfo' import type { VersionInfo } from '../../../../../server/dev/parse-version-info' +import { HotlinkedText } from '../components/hot-linked-text' export type SupportedErrorEvent = { id: number @@ -52,35 +53,6 @@ function getErrorSignature(ev: SupportedErrorEvent): string { return '' } -const HotlinkedText: React.FC<{ - text: string -}> = function HotlinkedText(props) { - const { text } = props - - const linkRegex = /https?:\/\/[^\s/$.?#].[^\s"]*/i - return ( - <> - {linkRegex.test(text) - ? text.split(' ').map((word, index, array) => { - if (linkRegex.test(word)) { - return ( - - {word} - {index === array.length - 1 ? '' : ' '} - - ) - } - return index === array.length - 1 ? ( - {word} - ) : ( - {word} - ) - }) - : text} - - ) -} - export const Errors: React.FC = function Errors({ errors, initialDisplayState, diff --git a/packages/next/src/server/config-schema.ts b/packages/next/src/server/config-schema.ts index 10f6280dd57ff..d4b262fc9bc27 100644 --- a/packages/next/src/server/config-schema.ts +++ b/packages/next/src/server/config-schema.ts @@ -598,6 +598,10 @@ const configSchema = { minLength: 1, type: 'string', }, + contentDispositionType: { + enum: ['inline', 'attachment'] as any, // automatic typing does not like enum + type: 'string', + }, dangerouslyAllowSVG: { type: 'boolean', }, diff --git a/packages/next/src/server/image-optimizer.ts b/packages/next/src/server/image-optimizer.ts index 6f881fbb7eeef..f0fa31bd6d579 100644 --- a/packages/next/src/server/image-optimizer.ts +++ b/packages/next/src/server/image-optimizer.ts @@ -23,6 +23,7 @@ import { IncrementalCacheEntry, IncrementalCacheValue } from './response-cache' import { mockRequest } from './lib/mock-request' import { hasMatch } from '../shared/lib/match-remote-pattern' import { getImageBlurSvg } from '../shared/lib/image-blur-svg' +import { ImageConfigComplete } from '../shared/lib/image-config' type XCacheHeader = 'MISS' | 'HIT' | 'STALE' @@ -672,11 +673,11 @@ export async function imageOptimizer( function getFileNameWithExtension( url: string, contentType: string | null -): string | void { +): string { const [urlWithoutQueryParams] = url.split('?') const fileNameWithExtension = urlWithoutQueryParams.split('/').pop() if (!contentType || !fileNameWithExtension) { - return + return 'image.bin' } const [fileName] = fileNameWithExtension.split('.') @@ -692,7 +693,7 @@ function setResponseHeaders( contentType: string | null, isStatic: boolean, xCache: XCacheHeader, - contentSecurityPolicy: string, + imagesConfig: ImageConfigComplete, maxAge: number, isDev: boolean ) { @@ -712,16 +713,12 @@ function setResponseHeaders( } const fileName = getFileNameWithExtension(url, contentType) - if (fileName) { - res.setHeader( - 'Content-Disposition', - contentDisposition(fileName, { type: 'inline' }) - ) - } + res.setHeader( + 'Content-Disposition', + contentDisposition(fileName, { type: imagesConfig.contentDispositionType }) + ) - if (contentSecurityPolicy) { - res.setHeader('Content-Security-Policy', contentSecurityPolicy) - } + res.setHeader('Content-Security-Policy', imagesConfig.contentSecurityPolicy) res.setHeader('X-Nextjs-Cache', xCache) return { finished: false } @@ -735,7 +732,7 @@ export function sendResponse( buffer: Buffer, isStatic: boolean, xCache: XCacheHeader, - contentSecurityPolicy: string, + imagesConfig: ImageConfigComplete, maxAge: number, isDev: boolean ) { @@ -749,7 +746,7 @@ export function sendResponse( contentType, isStatic, xCache, - contentSecurityPolicy, + imagesConfig, maxAge, isDev ) diff --git a/packages/next/src/server/next-server.ts b/packages/next/src/server/next-server.ts index 6f5dec54a3f18..34808e1ceb7f5 100644 --- a/packages/next/src/server/next-server.ts +++ b/packages/next/src/server/next-server.ts @@ -466,7 +466,7 @@ export default class NextNodeServer extends BaseServer { cacheEntry.value.buffer, paramsResult.isStatic, cacheEntry.isMiss ? 'MISS' : cacheEntry.isStale ? 'STALE' : 'HIT', - imagesConfig.contentSecurityPolicy, + imagesConfig, cacheEntry.revalidate || 0, Boolean(this.renderOpts.dev) ) diff --git a/packages/next/src/shared/lib/image-config.ts b/packages/next/src/shared/lib/image-config.ts index e13cdb6943712..34566883ab647 100644 --- a/packages/next/src/shared/lib/image-config.ts +++ b/packages/next/src/shared/lib/image-config.ts @@ -88,6 +88,9 @@ export type ImageConfigComplete = { /** @see [Dangerously Allow SVG](https://nextjs.org/docs/api-reference/next/image#dangerously-allow-svg) */ contentSecurityPolicy: string + /** @see [Dangerously Allow SVG](https://nextjs.org/docs/api-reference/next/image#dangerously-allow-svg) */ + contentDispositionType: 'inline' | 'attachment' + /** @see [Remote Patterns](https://nextjs.org/docs/api-reference/next/image#remote-patterns) */ remotePatterns: RemotePattern[] @@ -109,6 +112,7 @@ export const imageConfigDefault: ImageConfigComplete = { formats: ['image/webp'], dangerouslyAllowSVG: false, contentSecurityPolicy: `script-src 'none'; frame-src 'none'; sandbox;`, + contentDispositionType: 'inline', remotePatterns: [], unoptimized: false, } diff --git a/test/development/acceptance-app/error-message-url.test.ts b/test/development/acceptance-app/error-message-url.test.ts new file mode 100644 index 0000000000000..138c102746c40 --- /dev/null +++ b/test/development/acceptance-app/error-message-url.test.ts @@ -0,0 +1,73 @@ +import { createNextDescribe, FileRef } from 'e2e-utils' +import path from 'path' +import { sandbox } from './helpers' + +createNextDescribe( + 'Error overlay - error message urls', + { + files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')), + dependencies: { + react: 'latest', + 'react-dom': 'latest', + }, + skipStart: true, + }, + ({ next }) => { + it('should be possible to click url in build error', async () => { + const { session, browser, cleanup } = await sandbox(next) + + const content = await next.readFile('app/page.js') + + await session.patch( + 'app/page.js', + content + '\nexport function getServerSideProps() {}' + ) + + expect(await session.hasRedbox(true)).toBe(true) + + const link = await browser.elementByCss('[data-nextjs-terminal] a') + const text = await link.text() + const href = await link.getAttribute('href') + expect(text).toEqual( + 'https://beta.nextjs.org/docs/data-fetching/fundamentals' + ) + expect(href).toEqual( + 'https://beta.nextjs.org/docs/data-fetching/fundamentals' + ) + + await cleanup() + }) + + it('should be possible to click url in runtime error', async () => { + const { session, browser, cleanup } = await sandbox( + next, + new Map([ + [ + 'app/page.js', + `'use client' + export default function Page() { + return typeof window === 'undefined' ? 'HELLO' : 'WORLD' + } + `, + ], + ]) + ) + + await session.waitForAndOpenRuntimeError() + + const link = await browser.elementByCss( + '#nextjs__container_errors_desc a' + ) + const text = await link.text() + const href = await link.getAttribute('href') + expect(text).toEqual( + 'https://nextjs.org/docs/messages/react-hydration-error' + ) + expect(href).toEqual( + 'https://nextjs.org/docs/messages/react-hydration-error' + ) + + await cleanup() + }) + } +) diff --git a/test/e2e/app-dir/metadata/app/async/layout.tsx b/test/e2e/app-dir/metadata/app/async/layout.tsx index 0c87e2431254e..8168b5728b5d7 100644 --- a/test/e2e/app-dir/metadata/app/async/layout.tsx +++ b/test/e2e/app-dir/metadata/app/async/layout.tsx @@ -7,3 +7,5 @@ export async function generateMetadata() { keywords: 'parent', } } + +export const revalidate = 0 diff --git a/test/e2e/app-dir/metadata/app/async/not-found/page.tsx b/test/e2e/app-dir/metadata/app/async/not-found/page.tsx index a47fce01d4007..2c47965dbeca1 100644 --- a/test/e2e/app-dir/metadata/app/async/not-found/page.tsx +++ b/test/e2e/app-dir/metadata/app/async/not-found/page.tsx @@ -1,7 +1,7 @@ import { notFound } from 'next/navigation' export default function page() { - return 'not found' + return 'not-found-text' } export async function generateMetadata() { diff --git a/test/e2e/app-dir/metadata/app/async/redirect/page.tsx b/test/e2e/app-dir/metadata/app/async/redirect/page.tsx new file mode 100644 index 0000000000000..8f32a92507fa0 --- /dev/null +++ b/test/e2e/app-dir/metadata/app/async/redirect/page.tsx @@ -0,0 +1,9 @@ +import { redirect } from 'next/navigation' + +export default function page() { + return 'redirect to basic' +} + +export async function generateMetadata() { + redirect('/basic') +} diff --git a/test/e2e/app-dir/metadata/metadata.test.ts b/test/e2e/app-dir/metadata/metadata.test.ts index a168ce32828b4..b01cad5b7dc79 100644 --- a/test/e2e/app-dir/metadata/metadata.test.ts +++ b/test/e2e/app-dir/metadata/metadata.test.ts @@ -330,6 +330,17 @@ createNextDescribe( ) }) + it('should support notFound and redirect in generateMetadata', async () => { + const resNotFound = await next.fetch('/async/not-found') + expect(resNotFound.status).toBe(404) + const notFoundHtml = await resNotFound.text() + expect(notFoundHtml).not.toBe('not-found-text') + expect(notFoundHtml).toContain('This page could not be found.') + + const resRedirect = await next.fetch('/async/redirect') + expect(resRedirect.status).toBe(307) + }) + if (isNextDev) { it('should freeze parent resolved metadata to avoid mutating in generateMetadata', async () => { const pagePath = 'app/mutate/page.tsx' diff --git a/test/e2e/app-dir/navigation/app/layout.js b/test/e2e/app-dir/navigation/app/layout.js index e39561ce4ae6b..661e1f90b9ae2 100644 --- a/test/e2e/app-dir/navigation/app/layout.js +++ b/test/e2e/app-dir/navigation/app/layout.js @@ -1,4 +1,4 @@ -export const revalidate = 0 +export const dynamic = 'force-dynamic' export default function Layout({ children }) { return ( diff --git a/test/integration/image-optimizer/test/content-disposition-type.test.ts b/test/integration/image-optimizer/test/content-disposition-type.test.ts new file mode 100644 index 0000000000000..606958b11a1ce --- /dev/null +++ b/test/integration/image-optimizer/test/content-disposition-type.test.ts @@ -0,0 +1,13 @@ +import { join } from 'path' +import { setupTests } from './util' + +const appDir = join(__dirname, '../app') +const imagesDir = join(appDir, '.next', 'cache', 'images') + +describe('with contentDispositionType attachment', () => { + setupTests({ + nextConfigImages: { contentDispositionType: 'attachment' }, + appDir, + imagesDir, + }) +}) diff --git a/test/integration/image-optimizer/test/util.ts b/test/integration/image-optimizer/test/util.ts index 065ea5410354f..4f75ab159c7fa 100644 --- a/test/integration/image-optimizer/test/util.ts +++ b/test/integration/image-optimizer/test/util.ts @@ -131,7 +131,8 @@ async function fetchWithDuration( } export function runTests(ctx) { - const { isDev, minimumCacheTTL = 60 } = ctx + const { isDev, minimumCacheTTL = 60, nextConfigImages } = ctx + const { contentDispositionType = 'inline' } = nextConfigImages || {} let slowImageServer: Awaited> beforeAll(async () => { slowImageServer = await serveSlowImage() @@ -178,7 +179,7 @@ export function runTests(ctx) { expect(res.headers.get('Vary')).toBe('Accept') expect(res.headers.get('etag')).toBeTruthy() expect(res.headers.get('Content-Disposition')).toBe( - `inline; filename="animated.gif"` + `${contentDispositionType}; filename="animated.gif"` ) await expectWidth(res, 50, { expectAnimated: true }) }) @@ -194,7 +195,7 @@ export function runTests(ctx) { expect(res.headers.get('Vary')).toBe('Accept') expect(res.headers.get('etag')).toBeTruthy() expect(res.headers.get('Content-Disposition')).toBe( - `inline; filename="animated.png"` + `${contentDispositionType}; filename="animated.png"` ) await expectWidth(res, 100, { expectAnimated: true }) }) @@ -210,7 +211,7 @@ export function runTests(ctx) { expect(res.headers.get('Vary')).toBe('Accept') expect(res.headers.get('etag')).toBeTruthy() expect(res.headers.get('Content-Disposition')).toBe( - `inline; filename="animated2.png"` + `${contentDispositionType}; filename="animated2.png"` ) await expectWidth(res, 1105, { expectAnimated: true }) }) @@ -226,7 +227,7 @@ export function runTests(ctx) { expect(res.headers.get('Vary')).toBe('Accept') expect(res.headers.get('etag')).toBeTruthy() expect(res.headers.get('Content-Disposition')).toBe( - `inline; filename="animated.webp"` + `${contentDispositionType}; filename="animated.webp"` ) await expectWidth(res, 400, { expectAnimated: true }) }) @@ -247,7 +248,7 @@ export function runTests(ctx) { expect(res.headers.get('Vary')).toMatch(/^Accept(,|$)/) expect(res.headers.get('etag')).toBeTruthy() expect(res.headers.get('Content-Disposition')).toBe( - `inline; filename="test.svg"` + `${contentDispositionType}; filename="test.svg"` ) const actual = await res.text() const expected = await fs.readFile( @@ -308,7 +309,7 @@ export function runTests(ctx) { expect(res.headers.get('Vary')).toMatch(/^Accept(,|$)/) expect(res.headers.get('etag')).toBeTruthy() expect(res.headers.get('Content-Disposition')).toBe( - `inline; filename="test.ico"` + `${contentDispositionType}; filename="test.ico"` ) const actual = await res.text() const expected = await fs.readFile( @@ -332,7 +333,7 @@ export function runTests(ctx) { expect(res.headers.get('Vary')).toBe('Accept') expect(res.headers.get('etag')).toBeTruthy() expect(res.headers.get('Content-Disposition')).toBe( - `inline; filename="test.jpeg"` + `${contentDispositionType}; filename="test.jpeg"` ) }) @@ -350,7 +351,7 @@ export function runTests(ctx) { expect(res.headers.get('Vary')).toBe('Accept') expect(res.headers.get('etag')).toBeTruthy() expect(res.headers.get('Content-Disposition')).toBe( - `inline; filename="test.png"` + `${contentDispositionType}; filename="test.png"` ) }) @@ -368,7 +369,7 @@ export function runTests(ctx) { expect(res.headers.get('Vary')).toBe('Accept') expect(res.headers.get('etag')).toBeTruthy() expect(res.headers.get('Content-Disposition')).toBe( - `inline; filename="test.jpeg"` + `${contentDispositionType}; filename="test.jpeg"` ) await expectWidth(res, ctx.w) }) @@ -388,7 +389,7 @@ export function runTests(ctx) { expect(res.headers.get('Vary')).toBe('Accept') expect(res.headers.get('etag')).toBeTruthy() expect(res.headers.get('Content-Disposition')).toBe( - `inline; filename="test.jpeg"` + `${contentDispositionType}; filename="test.jpeg"` ) await expectWidth(res, ctx.w) }) @@ -532,7 +533,7 @@ export function runTests(ctx) { expect(res.headers.get('Vary')).toBe('Accept') expect(res.headers.get('etag')).toBeTruthy() expect(res.headers.get('Content-Disposition')).toBe( - `inline; filename="test.webp"` + `${contentDispositionType}; filename="test.webp"` ) await expectWidth(res, ctx.w) }) @@ -549,7 +550,7 @@ export function runTests(ctx) { expect(res.headers.get('Vary')).toBe('Accept') expect(res.headers.get('etag')).toBeTruthy() expect(res.headers.get('Content-Disposition')).toBe( - `inline; filename="test.png"` + `${contentDispositionType}; filename="test.png"` ) await expectWidth(res, ctx.w) }) @@ -566,7 +567,7 @@ export function runTests(ctx) { expect(res.headers.get('Vary')).toBe('Accept') expect(res.headers.get('etag')).toBeTruthy() expect(res.headers.get('Content-Disposition')).toBe( - `inline; filename="test.png"` + `${contentDispositionType}; filename="test.png"` ) await expectWidth(res, ctx.w) }) @@ -583,7 +584,7 @@ export function runTests(ctx) { expect(res.headers.get('Vary')).toBe('Accept') expect(res.headers.get('etag')).toBeTruthy() expect(res.headers.get('Content-Disposition')).toBe( - `inline; filename="test.gif"` + `${contentDispositionType}; filename="test.gif"` ) // FIXME: await expectWidth(res, ctx.w) }) @@ -600,7 +601,7 @@ export function runTests(ctx) { expect(res.headers.get('Vary')).toBe('Accept') expect(res.headers.get('etag')).toBeTruthy() expect(res.headers.get('Content-Disposition')).toBe( - `inline; filename="test.tiff"` + `${contentDispositionType}; filename="test.tiff"` ) // FIXME: await expectWidth(res, ctx.w) }) @@ -619,7 +620,7 @@ export function runTests(ctx) { expect(res.headers.get('Vary')).toBe('Accept') expect(res.headers.get('etag')).toBeTruthy() expect(res.headers.get('Content-Disposition')).toBe( - `inline; filename="test.webp"` + `${contentDispositionType}; filename="test.webp"` ) await expectWidth(res, ctx.w) }) @@ -641,7 +642,7 @@ export function runTests(ctx) { expect(res.headers.get('Vary')).toBe('Accept') expect(res.headers.get('etag')).toBeTruthy() expect(res.headers.get('Content-Disposition')).toBe( - `inline; filename="test.avif"` + `${contentDispositionType}; filename="test.avif"` ) // TODO: upgrade "image-size" package to support AVIF // See https://github.com/image-size/image-size/issues/348 @@ -675,7 +676,7 @@ export function runTests(ctx) { expect(res.headers.get('Vary')).toBe('Accept') expect(res.headers.get('etag')).toBeTruthy() expect(res.headers.get('Content-Disposition')).toBe( - `inline; filename="test.webp"` + `${contentDispositionType}; filename="test.webp"` ) await expectWidth(res, ctx.w) }) @@ -698,7 +699,7 @@ export function runTests(ctx) { expect(res.headers.get('Vary')).toBe('Accept') expect(res.headers.get('etag')).toBeTruthy() expect(res.headers.get('Content-Disposition')).toBe( - `inline; filename="png-as-octet-stream.webp"` + `${contentDispositionType}; filename="png-as-octet-stream.webp"` ) await expectWidth(res, ctx.w) }) @@ -722,7 +723,7 @@ export function runTests(ctx) { expect(one.res.headers.get('X-Nextjs-Cache')).toBe('MISS') expect(one.res.headers.get('Content-Type')).toBe('image/webp') expect(one.res.headers.get('Content-Disposition')).toBe( - `inline; filename="slow.webp"` + `${contentDispositionType}; filename="slow.webp"` ) const etagOne = one.res.headers.get('etag') @@ -746,7 +747,7 @@ export function runTests(ctx) { expect(two.res.headers.get('X-Nextjs-Cache')).toBe('HIT') expect(two.res.headers.get('Content-Type')).toBe('image/webp') expect(two.res.headers.get('Content-Disposition')).toBe( - `inline; filename="slow.webp"` + `${contentDispositionType}; filename="slow.webp"` ) const json2 = await fsToJson(ctx.imagesDir) expect(json2).toStrictEqual(json1) @@ -765,7 +766,7 @@ export function runTests(ctx) { expect(three.res.headers.get('X-Nextjs-Cache')).toBe('STALE') expect(three.res.headers.get('Content-Type')).toBe('image/webp') expect(three.res.headers.get('Content-Disposition')).toBe( - `inline; filename="slow.webp"` + `${contentDispositionType}; filename="slow.webp"` ) expect(four.duration).toBeLessThan(one.duration) @@ -773,7 +774,7 @@ export function runTests(ctx) { expect(four.res.headers.get('X-Nextjs-Cache')).toBe('STALE') expect(four.res.headers.get('Content-Type')).toBe('image/webp') expect(four.res.headers.get('Content-Disposition')).toBe( - `inline; filename="slow.webp"` + `${contentDispositionType}; filename="slow.webp"` ) await check(async () => { const json4 = await fsToJson(ctx.imagesDir) @@ -796,7 +797,7 @@ export function runTests(ctx) { expect(five.res.headers.get('X-Nextjs-Cache')).toBe('HIT') expect(five.res.headers.get('Content-Type')).toBe('image/webp') expect(five.res.headers.get('Content-Disposition')).toBe( - `inline; filename="slow.webp"` + `${contentDispositionType}; filename="slow.webp"` ) await check(async () => { const json5 = await fsToJson(ctx.imagesDir) @@ -868,7 +869,7 @@ export function runTests(ctx) { expect(one.res.headers.get('X-Nextjs-Cache')).toBe('MISS') expect(one.res.headers.get('Content-Type')).toBe('image/webp') expect(one.res.headers.get('Content-Disposition')).toBe( - `inline; filename="test.webp"` + `${contentDispositionType}; filename="test.webp"` ) const etagOne = one.res.headers.get('etag') @@ -892,7 +893,7 @@ export function runTests(ctx) { expect(two.res.headers.get('X-Nextjs-Cache')).toBe('HIT') expect(two.res.headers.get('Content-Type')).toBe('image/webp') expect(two.res.headers.get('Content-Disposition')).toBe( - `inline; filename="test.webp"` + `${contentDispositionType}; filename="test.webp"` ) const json2 = await fsToJson(ctx.imagesDir) expect(json2).toStrictEqual(json1) @@ -911,7 +912,7 @@ export function runTests(ctx) { expect(three.res.headers.get('X-Nextjs-Cache')).toBe('STALE') expect(three.res.headers.get('Content-Type')).toBe('image/webp') expect(three.res.headers.get('Content-Disposition')).toBe( - `inline; filename="test.webp"` + `${contentDispositionType}; filename="test.webp"` ) expect(four.duration).toBeLessThan(one.duration) @@ -919,7 +920,7 @@ export function runTests(ctx) { expect(four.res.headers.get('X-Nextjs-Cache')).toBe('STALE') expect(four.res.headers.get('Content-Type')).toBe('image/webp') expect(four.res.headers.get('Content-Disposition')).toBe( - `inline; filename="test.webp"` + `${contentDispositionType}; filename="test.webp"` ) await check(async () => { const json3 = await fsToJson(ctx.imagesDir) @@ -942,7 +943,7 @@ export function runTests(ctx) { expect(five.res.headers.get('X-Nextjs-Cache')).toBe('HIT') expect(five.res.headers.get('Content-Type')).toBe('image/webp') expect(five.res.headers.get('Content-Disposition')).toBe( - `inline; filename="test.webp"` + `${contentDispositionType}; filename="test.webp"` ) await check(async () => { const json5 = await fsToJson(ctx.imagesDir) @@ -968,7 +969,7 @@ export function runTests(ctx) { expect(res1.headers.get('X-Nextjs-Cache')).toBe('MISS') expect(res1.headers.get('Content-Type')).toBe('image/svg+xml') expect(res1.headers.get('Content-Disposition')).toBe( - `inline; filename="test.svg"` + `${contentDispositionType}; filename="test.svg"` ) const etagOne = res1.headers.get('etag') @@ -987,7 +988,7 @@ export function runTests(ctx) { expect(res2.headers.get('X-Nextjs-Cache')).toBe('HIT') expect(res2.headers.get('Content-Type')).toBe('image/svg+xml') expect(res2.headers.get('Content-Disposition')).toBe( - `inline; filename="test.svg"` + `${contentDispositionType}; filename="test.svg"` ) const json2 = await fsToJson(ctx.imagesDir) expect(json2).toStrictEqual(json1) @@ -1005,7 +1006,7 @@ export function runTests(ctx) { expect(res1.headers.get('X-Nextjs-Cache')).toBe('MISS') expect(res1.headers.get('Content-Type')).toBe('image/gif') expect(res1.headers.get('Content-Disposition')).toBe( - `inline; filename="animated.gif"` + `${contentDispositionType}; filename="animated.gif"` ) let json1 @@ -1019,7 +1020,7 @@ export function runTests(ctx) { expect(res2.headers.get('X-Nextjs-Cache')).toBe('HIT') expect(res2.headers.get('Content-Type')).toBe('image/gif') expect(res2.headers.get('Content-Disposition')).toBe( - `inline; filename="animated.gif"` + `${contentDispositionType}; filename="animated.gif"` ) const json2 = await fsToJson(ctx.imagesDir) expect(json2).toStrictEqual(json1) @@ -1039,7 +1040,7 @@ export function runTests(ctx) { const etag = res1.headers.get('Etag') expect(etag).toBeTruthy() expect(res1.headers.get('Content-Disposition')).toBe( - `inline; filename="test.webp"` + `${contentDispositionType}; filename="test.webp"` ) await expectWidth(res1, ctx.w) @@ -1066,7 +1067,7 @@ export function runTests(ctx) { expect(res3.headers.get('Etag')).toBeTruthy() expect(res3.headers.get('Etag')).not.toBe(etag) expect(res3.headers.get('Content-Disposition')).toBe( - `inline; filename="test.webp"` + `${contentDispositionType}; filename="test.webp"` ) await expectWidth(res3, ctx.w) }) @@ -1088,7 +1089,7 @@ export function runTests(ctx) { expect(res.headers.get('Vary')).toMatch(/^Accept(,|$)/) expect(res.headers.get('etag')).toBeTruthy() expect(res.headers.get('Content-Disposition')).toBe( - `inline; filename="test.bmp"` + `${contentDispositionType}; filename="test.bmp"` ) await check(async () => { @@ -1113,7 +1114,7 @@ export function runTests(ctx) { expect(res.headers.get('Vary')).toBe('Accept') expect(res.headers.get('etag')).toBeTruthy() expect(res.headers.get('Content-Disposition')).toBe( - `inline; filename="test.webp"` + `${contentDispositionType}; filename="test.webp"` ) await expectWidth(res, 400) }) @@ -1134,7 +1135,7 @@ export function runTests(ctx) { ) expect(res.headers.get('Vary')).toBe('Accept') expect(res.headers.get('Content-Disposition')).toBe( - `inline; filename="grayscale.png"` + `${contentDispositionType}; filename="grayscale.png"` ) const png = await res.buffer() @@ -1163,7 +1164,7 @@ export function runTests(ctx) { ) expect(res1.headers.get('Vary')).toBe('Accept') expect(res1.headers.get('Content-Disposition')).toBe( - `inline; filename="${filename}.webp"` + `${contentDispositionType}; filename="${filename}.webp"` ) await expectWidth(res1, ctx.w) @@ -1175,7 +1176,7 @@ export function runTests(ctx) { ) expect(res2.headers.get('Vary')).toBe('Accept') expect(res2.headers.get('Content-Disposition')).toBe( - `inline; filename="${filename}.webp"` + `${contentDispositionType}; filename="${filename}.webp"` ) await expectWidth(res2, ctx.w) } @@ -1223,15 +1224,15 @@ export function runTests(ctx) { expect(res1.headers.get('Content-Type')).toBe('image/webp') expect(res1.headers.get('Content-Disposition')).toBe( - `inline; filename="slow.webp"` + `${contentDispositionType}; filename="slow.webp"` ) expect(res2.headers.get('Content-Type')).toBe('image/webp') expect(res2.headers.get('Content-Disposition')).toBe( - `inline; filename="slow.webp"` + `${contentDispositionType}; filename="slow.webp"` ) expect(res3.headers.get('Content-Type')).toBe('image/webp') expect(res3.headers.get('Content-Disposition')).toBe( - `inline; filename="slow.webp"` + `${contentDispositionType}; filename="slow.webp"` ) await expectWidth(res1, ctx.w) @@ -1290,6 +1291,10 @@ export const setupTests = (ctx) => { // only run one server config with outdated sharp if (!ctx.isOutdatedSharp) { describe('dev support w/o next.config.js', () => { + if (ctx.nextConfigImages) { + // skip this test because it requires next.config.js + return + } const size = 384 // defaults defined in server/config.ts const curCtx = { ...ctx, @@ -1337,6 +1342,7 @@ export const setupTests = (ctx) => { imageSizes: [size], domains: curCtx.domains, formats: ['image/avif', 'image/webp'], + ...ctx.nextConfigImages, }, }) curCtx.nextOutput = '' @@ -1364,6 +1370,10 @@ export const setupTests = (ctx) => { }) describe('Server support w/o next.config.js', () => { + if (ctx.nextConfigImages) { + // skip this test because it requires next.config.js + return + } const size = 384 // defaults defined in server/config.ts const curCtx = { ...ctx, @@ -1410,6 +1420,7 @@ export const setupTests = (ctx) => { formats: ['image/avif', 'image/webp'], deviceSizes: [size, largeSize], domains: ctx.domains, + ...ctx.nextConfigImages, }, }) curCtx.nextOutput = ''