1- import { mkdir , rm , writeFile } from "node:fs/promises" ;
1+ import { mkdir , mkdtemp , rm , writeFile } from "node:fs/promises" ;
22import { tmpdir } from "node:os" ;
3- import { join } from "node:path" ;
3+ import { dirname , join } from "node:path" ;
44
5- import type { PluginBuild } from "esbuild" ;
6- import { afterEach , beforeEach , describe , expect , test } from "vitest" ;
5+ import { build } from "esbuild" ;
6+ import { afterAll , beforeAll , describe , expect , test } from "vitest" ;
77
88import { openNextResolvePlugin } from "./resolve.js" ;
99
@@ -62,73 +62,138 @@ export async function resolveCdnInvalidation(cdnInvalidation) {
6262}
6363` . trim ( ) ;
6464
65- type OnLoadCallback = ( args : { path : string } ) => Promise < { contents : string } > ;
65+ // The default overrides imported by the fixture above, and the alternatives the
66+ // tests redirect to.
67+ const OVERRIDE_MODULES = [
68+ "overrides/converters/node.js" ,
69+ "overrides/converters/edge.js" ,
70+ "overrides/wrappers/node.js" ,
71+ "overrides/wrappers/cloudflare-edge.js" ,
72+ "overrides/tagCache/fs-dev-nextMode.js" ,
73+ "overrides/queue/direct.js" ,
74+ "overrides/incrementalCache/fs-dev.js" ,
75+ "overrides/imageLoader/fs-dev.js" ,
76+ "overrides/originResolver/pattern-env.js" ,
77+ "overrides/warmer/dummy.js" ,
78+ "overrides/proxyExternalRequest/node.js" ,
79+ "overrides/cdnInvalidation/dummy.js" ,
80+ ] ;
6681
67- function createStubBuild ( ) {
68- let capturedCb : OnLoadCallback | undefined ;
69- const stub = {
70- onLoad : ( _opts : { filter : RegExp } , cb : OnLoadCallback ) => {
71- capturedCb = cb ;
72- } ,
73- } as unknown as PluginBuild ;
74- return { stub, getCallback : ( ) => capturedCb ! } ;
82+ // Packages resolved through node_modules for the full-path override cases.
83+ const CORE_PKG_MODULES = [
84+ "overrides/converters/edge.js" ,
85+ "overrides/converters/dummy.js" ,
86+ "overrides/imageLoader/dummy.js" ,
87+ "overrides/originResolver/dummy.js" ,
88+ "overrides/proxyExternalRequest/fetch.js" ,
89+ ] ;
90+ const AWS_PKG_MODULES = [
91+ "overrides/wrappers/aws-lambda.js" ,
92+ "overrides/wrappers/aws-lambda-streaming.js" ,
93+ "overrides/converters/aws-apigw-v2.js" ,
94+ "overrides/tagCache/dynamodb.js" ,
95+ "overrides/queue/sqs.js" ,
96+ "overrides/incrementalCache/s3.js" ,
97+ "overrides/warmer/aws-lambda.js" ,
98+ "overrides/cdnInvalidation/cloudfront.js" ,
99+ ] ;
100+
101+ let root : string ;
102+
103+ /** Writes a module exporting a marker identifying it by its path in the fixture. */
104+ async function writeModule ( relPath : string ) {
105+ const fullPath = join ( root , relPath ) ;
106+ await mkdir ( dirname ( fullPath ) , { recursive : true } ) ;
107+ await writeFile ( fullPath , `export default "MARKER:${ relPath } ";` , "utf-8" ) ;
75108}
76109
77- describe ( "openNextResolvePlugin" , ( ) => {
78- let fixturePath : string ;
79- let fixtureDir : string ;
80-
81- beforeEach ( async ( ) => {
82- fixtureDir = join ( tmpdir ( ) , `resolve-test-${ Date . now ( ) } ` , "core" ) ;
83- await mkdir ( fixtureDir , { recursive : true } ) ;
84- fixturePath = join ( fixtureDir , "resolve.js" ) ;
85- await writeFile ( fixturePath , FIXTURE_CONTENT , "utf-8" ) ;
110+ /** Marker bundled for an override living next to the fixture `resolve.js`. */
111+ function local ( relPath : string ) {
112+ return `MARKER:overrides/${ relPath } ` ;
113+ }
114+
115+ /** Marker bundled for an override coming from a package in `node_modules`. */
116+ function pkg ( name : string , relPath : string ) {
117+ return `MARKER:node_modules/${ name } /overrides/${ relPath } ` ;
118+ }
119+
120+ /** Bundles the fixture with the plugin and returns the generated code. */
121+ async function bundleWithPlugin ( opts : Parameters < typeof openNextResolvePlugin > [ 0 ] , entry = "entry.js" ) {
122+ const result = await build ( {
123+ entryPoints : [ join ( root , entry ) ] ,
124+ absWorkingDir : root ,
125+ bundle : true ,
126+ write : false ,
127+ format : "esm" ,
128+ platform : "node" ,
129+ outfile : join ( root , "out.js" ) ,
130+ plugins : [ openNextResolvePlugin ( opts ) ] ,
86131 } ) ;
132+ return result . outputFiles [ 0 ] . text ;
133+ }
134+
135+ describe ( "openNextResolvePlugin" , ( ) => {
136+ beforeAll ( async ( ) => {
137+ root = await mkdtemp ( join ( tmpdir ( ) , "resolve-test-" ) ) ;
138+
139+ await mkdir ( join ( root , "core" ) , { recursive : true } ) ;
140+ await writeFile ( join ( root , "core" , "resolve.js" ) , FIXTURE_CONTENT , "utf-8" ) ;
141+ await writeFile ( join ( root , "entry.js" ) , `export * from "./core/resolve.js";` , "utf-8" ) ;
87142
88- afterEach ( async ( ) => {
89- // Clean up the temp directory (go up one level from "core")
90- await rm ( join ( fixtureDir , ".." ) , { recursive : true , force : true } ) ;
143+ for ( const mod of OVERRIDE_MODULES ) {
144+ await writeModule ( mod ) ;
145+ }
146+ for ( const [ name , modules ] of [
147+ [ "@opennextjs/core" , CORE_PKG_MODULES ] ,
148+ [ "@opennextjs/aws" , AWS_PKG_MODULES ] ,
149+ ] as const ) {
150+ await mkdir ( join ( root , "node_modules" , name ) , { recursive : true } ) ;
151+ await writeFile (
152+ join ( root , "node_modules" , name , "package.json" ) ,
153+ JSON . stringify ( { name, type : "module" } ) ,
154+ "utf-8"
155+ ) ;
156+ for ( const mod of modules ) {
157+ await writeModule ( join ( "node_modules" , name , mod ) ) ;
158+ }
159+ }
91160 } ) ;
92161
93- async function runPlugin ( opts : Parameters < typeof openNextResolvePlugin > [ 0 ] ) {
94- const plugin = openNextResolvePlugin ( opts ) ;
95- const { stub, getCallback } = createStubBuild ( ) ;
96- plugin . setup ( stub ) ;
97- const cb = getCallback ( ) ;
98- return cb ( { path : fixturePath } ) ;
99- }
162+ afterAll ( async ( ) => {
163+ await rm ( root , { recursive : true , force : true } ) ;
164+ } ) ;
100165
101166 test ( "A - full-path default verbatim: core full path default replaces anchor" , async ( ) => {
102- const result = await runPlugin ( {
167+ const contents = await bundleWithPlugin ( {
103168 overrides : { } ,
104169 defaultOverrides : { converter : "@opennextjs/core/overrides/converters/edge.js" } ,
105170 fnName : "test" ,
106171 } ) ;
107- expect ( result . contents ) . toContain ( "overrides/ converters/edge.js") ;
108- expect ( result . contents ) . not . toContain ( '"../overrides/ converters/node.js"' ) ;
172+ expect ( contents ) . toContain ( pkg ( "@opennextjs/core" , " converters/edge.js") ) ;
173+ expect ( contents ) . not . toContain ( local ( " converters/node.js") ) ;
109174 } ) ;
110175
111176 test ( "B - cross-package user full aws path wins over core default" , async ( ) => {
112- const result = await runPlugin ( {
177+ const contents = await bundleWithPlugin ( {
113178 overrides : { converter : "@opennextjs/aws/overrides/converters/aws-apigw-v2.js" } ,
114179 defaultOverrides : { converter : "@opennextjs/core/overrides/converters/edge.js" } ,
115180 fnName : "test" ,
116181 } ) ;
117- expect ( result . contents ) . toContain ( "overrides/ converters/aws-apigw-v2.js") ;
118- expect ( result . contents ) . not . toContain ( "overrides/ converters/edge.js") ;
182+ expect ( contents ) . toContain ( pkg ( "@opennextjs/aws" , " converters/aws-apigw-v2.js") ) ;
183+ expect ( contents ) . not . toContain ( pkg ( "@opennextjs/core" , " converters/edge.js") ) ;
119184 } ) ;
120185
121186 test ( "C - no-op anchor stays: no override no default keeps relative core path" , async ( ) => {
122- const result = await runPlugin ( {
187+ const contents = await bundleWithPlugin ( {
123188 overrides : { } ,
124189 defaultOverrides : { } ,
125190 fnName : "test" ,
126191 } ) ;
127- expect ( result . contents ) . toContain ( "../overrides/ converters/node.js") ;
192+ expect ( contents ) . toContain ( local ( " converters/node.js") ) ;
128193 } ) ;
129194
130195 test ( "D - 10-key mixed aws+core full paths all rewritten" , async ( ) => {
131- const result = await runPlugin ( {
196+ const contents = await bundleWithPlugin ( {
132197 overrides : { } ,
133198 defaultOverrides : {
134199 wrapper : "@opennextjs/aws/overrides/wrappers/aws-lambda.js" ,
@@ -144,42 +209,46 @@ describe("openNextResolvePlugin", () => {
144209 } ,
145210 fnName : "test" ,
146211 } ) ;
147- expect ( result . contents ) . toContain ( "overrides/wrappers/aws-lambda.js" ) ;
148- expect ( result . contents ) . toContain ( "overrides/converters/edge.js" ) ;
149- expect ( result . contents ) . toContain ( "overrides/tagCache/dynamodb.js" ) ;
150- expect ( result . contents ) . toContain ( "overrides/queue/sqs.js" ) ;
151- expect ( result . contents ) . toContain ( "overrides/incrementalCache/s3.js" ) ;
152- expect ( result . contents ) . toContain ( "overrides/imageLoader/dummy.js" ) ;
153- expect ( result . contents ) . toContain ( "overrides/originResolver/dummy.js" ) ;
154- expect ( result . contents ) . toContain ( "overrides/warmer/aws-lambda.js" ) ;
155- expect ( result . contents ) . toContain ( "overrides/proxyExternalRequest/fetch.js" ) ;
156- expect ( result . contents ) . toContain ( "overrides/cdnInvalidation/cloudfront.js" ) ;
212+ expect ( contents ) . toContain ( pkg ( "@opennextjs/aws" , "wrappers/aws-lambda.js" ) ) ;
213+ expect ( contents ) . toContain ( pkg ( "@opennextjs/core" , "converters/edge.js" ) ) ;
214+ expect ( contents ) . toContain ( pkg ( "@opennextjs/aws" , "tagCache/dynamodb.js" ) ) ;
215+ expect ( contents ) . toContain ( pkg ( "@opennextjs/aws" , "queue/sqs.js" ) ) ;
216+ expect ( contents ) . toContain ( pkg ( "@opennextjs/aws" , "incrementalCache/s3.js" ) ) ;
217+ expect ( contents ) . toContain ( pkg ( "@opennextjs/core" , "imageLoader/dummy.js" ) ) ;
218+ expect ( contents ) . toContain ( pkg ( "@opennextjs/core" , "originResolver/dummy.js" ) ) ;
219+ expect ( contents ) . toContain ( pkg ( "@opennextjs/aws" , "warmer/aws-lambda.js" ) ) ;
220+ expect ( contents ) . toContain ( pkg ( "@opennextjs/core" , "proxyExternalRequest/fetch.js" ) ) ;
221+ expect ( contents ) . toContain ( pkg ( "@opennextjs/aws" , "cdnInvalidation/cloudfront.js" ) ) ;
222+ // None of the defaults are bundled anymore
223+ expect ( contents ) . not . toContain ( local ( "wrappers/node.js" ) ) ;
224+ expect ( contents ) . not . toContain ( local ( "tagCache/fs-dev-nextMode.js" ) ) ;
225+ expect ( contents ) . not . toContain ( local ( "incrementalCache/fs-dev.js" ) ) ;
157226 } ) ;
158227
159228 test ( "E - deprecated cloudflare bare name becomes legacy relative core path" , async ( ) => {
160- const result = await runPlugin ( {
229+ const contents = await bundleWithPlugin ( {
161230 overrides : { wrapper : "cloudflare" } ,
162231 defaultOverrides : { } ,
163232 fnName : "test" ,
164233 } ) ;
165- expect ( result . contents ) . toContain ( "../overrides/ wrappers/cloudflare-edge.js") ;
166- expect ( result . contents ) . not . toContain ( "cloudflare .js") ;
234+ expect ( contents ) . toContain ( local ( " wrappers/cloudflare-edge.js") ) ;
235+ expect ( contents ) . not . toContain ( local ( "wrappers/node .js") ) ;
167236 } ) ;
168237
169238 test ( "F - function override becomes full dummy core path" , async ( ) => {
170239 // oxlint-disable-next-line @typescript-eslint/no-explicit-any - testing function override
171240 const fnOverride = ( ( ) => ( { } ) ) as any ;
172- const result = await runPlugin ( {
241+ const contents = await bundleWithPlugin ( {
173242 overrides : { converter : fnOverride } ,
174243 defaultOverrides : { converter : "@opennextjs/core/overrides/converters/edge.js" } ,
175244 fnName : "test" ,
176245 } ) ;
177- expect ( result . contents ) . toContain ( "@opennextjs/core/overrides/ converters/dummy.js" ) ;
178- expect ( result . contents ) . not . toContain ( "@opennextjs/core/overrides/ converters/edge.js" ) ;
246+ expect ( contents ) . toContain ( pkg ( "@opennextjs/core" , " converters/dummy.js") ) ;
247+ expect ( contents ) . not . toContain ( pkg ( "@opennextjs/core" , " converters/edge.js") ) ;
179248 } ) ;
180249
181250 test ( "G - AWS server defaults produce aws full paths" , async ( ) => {
182- const result = await runPlugin ( {
251+ const contents = await bundleWithPlugin ( {
183252 overrides : { } ,
184253 defaultOverrides : {
185254 wrapper : "@opennextjs/aws/overrides/wrappers/aws-lambda-streaming.js" ,
@@ -190,42 +259,67 @@ describe("openNextResolvePlugin", () => {
190259 } ,
191260 fnName : "server" ,
192261 } ) ;
193- expect ( result . contents ) . toContain ( "overrides/wrappers/aws-lambda-streaming.js" ) ;
194- expect ( result . contents ) . toContain ( "overrides/converters/aws-apigw-v2.js" ) ;
195- expect ( result . contents ) . toContain ( "overrides/incrementalCache/s3.js" ) ;
196- expect ( result . contents ) . toContain ( "overrides/tagCache/dynamodb.js" ) ;
197- expect ( result . contents ) . toContain ( "overrides/queue/sqs.js" ) ;
262+ expect ( contents ) . toContain ( pkg ( "@opennextjs/aws" , "wrappers/aws-lambda-streaming.js" ) ) ;
263+ expect ( contents ) . toContain ( pkg ( "@opennextjs/aws" , "converters/aws-apigw-v2.js" ) ) ;
264+ expect ( contents ) . toContain ( pkg ( "@opennextjs/aws" , "incrementalCache/s3.js" ) ) ;
265+ expect ( contents ) . toContain ( pkg ( "@opennextjs/aws" , "tagCache/dynamodb.js" ) ) ;
266+ expect ( contents ) . toContain ( pkg ( "@opennextjs/aws" , "queue/sqs.js" ) ) ;
267+ // Keys without an override keep their default
268+ expect ( contents ) . toContain ( local ( "imageLoader/fs-dev.js" ) ) ;
198269 } ) ;
199270
200271 test ( "H - bare-name user override becomes legacy relative core path" , async ( ) => {
201- const result = await runPlugin ( {
272+ const contents = await bundleWithPlugin ( {
202273 overrides : { converter : "edge" } ,
203274 defaultOverrides : { } ,
204275 fnName : "test" ,
205276 } ) ;
206- expect ( result . contents ) . toContain ( "../overrides/ converters/edge.js") ;
207- expect ( result . contents ) . not . toContain ( "@opennextjs/core/overrides/ converters/node.js") ;
277+ expect ( contents ) . toContain ( local ( " converters/edge.js") ) ;
278+ expect ( contents ) . not . toContain ( local ( " converters/node.js") ) ;
208279 } ) ;
209280
210- test ( "I - resolvable package specifier is converted to relative filesystem path" , async ( ) => {
211- const rootDir = join ( fixtureDir , ".." ) ;
212- const pkgDir = join ( rootDir , "node_modules" , "@test-pkg" , "wrapper" ) ;
213- await mkdir ( pkgDir , { recursive : true } ) ;
281+ test ( "I - resolvable package specifier is resolved through node_modules" , async ( ) => {
282+ await mkdir ( join ( root , "node_modules" , "@test-pkg" , "wrapper" ) , { recursive : true } ) ;
214283 await writeFile (
215- join ( pkgDir , "package.json" ) ,
284+ join ( root , "node_modules" , "@test-pkg" , "wrapper" , "package.json" ) ,
216285 JSON . stringify ( { name : "@test-pkg/wrapper" , main : "index.js" } ) ,
217286 "utf-8"
218287 ) ;
219- await writeFile ( join ( pkgDir , "index.js" ) , "module.exports = {}; " , "utf-8" ) ;
288+ await writeModule ( join ( "node_modules" , "@test-pkg" , "wrapper " , "index.js" ) ) ;
220289
221- const result = await runPlugin ( {
290+ const contents = await bundleWithPlugin ( {
222291 overrides : { wrapper : "@test-pkg/wrapper" } ,
223292 defaultOverrides : { } ,
224293 fnName : "test" ,
225294 } ) ;
226295
227- expect ( result . contents ) . not . toContain ( '"@test-pkg/wrapper"' ) ;
228- expect ( result . contents ) . toContain ( "node_modules/@test-pkg/wrapper/index.js" ) ;
229- expect ( result . contents ) . toMatch ( / " \. \/ .* n o d e _ m o d u l e s \/ @ t e s t - p k g \/ w r a p p e r \/ i n d e x \. j s " / ) ;
296+ expect ( contents ) . toContain ( "MARKER:node_modules/@test-pkg/wrapper/index.js" ) ;
297+ expect ( contents ) . not . toContain ( local ( "wrappers/node.js" ) ) ;
298+ } ) ;
299+
300+ test ( "J - overrides of other modules are left alone" , async ( ) => {
301+ await writeFile (
302+ join ( root , "core" , "other.js" ) ,
303+ `export const load = () => import("../overrides/converters/node.js");` ,
304+ "utf-8"
305+ ) ;
306+ await writeFile (
307+ join ( root , "entry-other.js" ) ,
308+ `export * from "./core/resolve.js";\nexport * from "./core/other.js";` ,
309+ "utf-8"
310+ ) ;
311+
312+ const contents = await bundleWithPlugin (
313+ {
314+ overrides : { converter : "edge" } ,
315+ defaultOverrides : { } ,
316+ fnName : "test" ,
317+ } ,
318+ "entry-other.js"
319+ ) ;
320+
321+ // `resolve.js` gets the override, `other.js` keeps importing the default
322+ expect ( contents ) . toContain ( local ( "converters/edge.js" ) ) ;
323+ expect ( contents ) . toContain ( local ( "converters/node.js" ) ) ;
230324 } ) ;
231325} ) ;
0 commit comments