@@ -4,13 +4,15 @@ import path from 'node:path';
44
55import { afterEach , beforeEach , describe , expect , it } from 'vitest' ;
66
7- import { PackageManager } from '../../types/index.ts' ;
7+ import { PackageManager , type WorkspaceInfo } from '../../types/index.ts' ;
88import {
99 collectOxlintOwnerDirs ,
1010 dropDeadOxlintPluginsDependency ,
1111 finalizeCoreMigrationForExistingVitePlus ,
1212 packageOwnsOxlintApi ,
1313 rewritePackageJson ,
14+ rewriteMonorepo ,
15+ rewriteStandaloneProject ,
1416 sourceTreeReferencesOxlintPluginsPackage ,
1517 usesVitestBrowserMode ,
1618} from '../migrator.ts' ;
@@ -64,6 +66,70 @@ describe('Oxlint plugin dependency cleanup', () => {
6466 } ,
6567 ) ;
6668
69+ it . each ( [ false , true ] ) (
70+ 'cleans up before newly injected browser packages are installed (monorepo: %s)' ,
71+ ( isMonorepo ) => {
72+ const packageJsonPath = path . join ( projectPath , 'package.json' ) ;
73+ fs . writeFileSync (
74+ packageJsonPath ,
75+ JSON . stringify ( {
76+ name : 'project' ,
77+ devDependencies : { 'vite-plus' : 'latest' , '@oxlint/plugins' : '^1.79.0' } ,
78+ } ) ,
79+ ) ;
80+ const browserProjectPath = isMonorepo
81+ ? path . join ( projectPath , 'packages' , 'app' )
82+ : projectPath ;
83+ if ( isMonorepo ) {
84+ fs . mkdirSync ( browserProjectPath , { recursive : true } ) ;
85+ fs . writeFileSync ( path . join ( browserProjectPath , 'package.json' ) , '{"name":"app"}' ) ;
86+ fs . writeFileSync (
87+ path . join ( projectPath , 'pnpm-workspace.yaml' ) ,
88+ 'packages:\n - packages/*\n' ,
89+ ) ;
90+ }
91+ fs . writeFileSync (
92+ path . join ( browserProjectPath , 'browser.ts' ) ,
93+ "import { playwright } from '@vitest/browser-playwright';" ,
94+ ) ;
95+ fs . writeFileSync (
96+ path . join ( projectPath , 'plugin.ts' ) ,
97+ "import { defineRule } from '@oxlint/plugins';" ,
98+ ) ;
99+ const workspace : WorkspaceInfo = {
100+ rootDir : projectPath ,
101+ isMonorepo,
102+ monorepoScope : '' ,
103+ workspacePatterns : isMonorepo ? [ 'packages/*' ] : [ ] ,
104+ parentDirs : [ ] ,
105+ packages : isMonorepo ? [ { name : 'app' , path : 'packages/app' } ] : [ ] ,
106+ packageManager : PackageManager . pnpm ,
107+ packageManagerVersion : '10.33.0' ,
108+ downloadPackageManager : {
109+ name : PackageManager . pnpm ,
110+ packageName : 'pnpm' ,
111+ version : '10.33.0' ,
112+ installDir : projectPath ,
113+ binPrefix : projectPath ,
114+ } ,
115+ } ;
116+
117+ if ( isMonorepo ) {
118+ rewriteMonorepo ( workspace , true , true ) ;
119+ } else {
120+ rewriteStandaloneProject ( projectPath , workspace , true , true ) ;
121+ }
122+
123+ expect (
124+ JSON . parse ( fs . readFileSync ( packageJsonPath , 'utf8' ) ) . devDependencies ,
125+ ) . not . toHaveProperty ( '@oxlint/plugins' ) ;
126+ expect (
127+ JSON . parse ( fs . readFileSync ( path . join ( browserProjectPath , 'package.json' ) , 'utf8' ) )
128+ . devDependencies ,
129+ ) . toHaveProperty ( '@vitest/browser-playwright' ) ;
130+ } ,
131+ ) ;
132+
67133 it . each ( [
68134 { scripts : { 'check-plugin' : `node -e "require('@oxlint/plugins')"` } } ,
69135 { imports : { '#plugin-api' : '@oxlint/plugins' } } ,
@@ -186,34 +252,103 @@ describe('Oxlint plugin dependency cleanup', () => {
186252 } ,
187253 ) ;
188254
189- it ( 'retains a root peer provider for a plugin used by a nested package' , ( ) => {
190- const pkg = { devDependencies : { 'vite-plus' : 'latest' , '@oxlint/plugins' : '^1.79.0' } } ;
191- const packageJsonPath = path . join ( projectPath , 'package.json' ) ;
192- fs . writeFileSync ( packageJsonPath , JSON . stringify ( pkg ) ) ;
193- const appPath = path . join ( projectPath , 'packages' , 'app' ) ;
194- fs . mkdirSync ( appPath , { recursive : true } ) ;
195- fs . writeFileSync (
196- path . join ( appPath , 'package.json' ) ,
197- JSON . stringify ( { dependencies : { 'review-oxlint-plugin' : '1.0.0' } } ) ,
198- ) ;
199- const pluginPath = path . join ( appPath , 'node_modules' , 'review-oxlint-plugin' ) ;
200- fs . mkdirSync ( pluginPath , { recursive : true } ) ;
201- fs . writeFileSync (
202- path . join ( pluginPath , 'package.json' ) ,
203- JSON . stringify ( {
204- name : 'review-oxlint-plugin' ,
205- peerDependencies : { '@oxlint/plugins' : '^1.79.0' } ,
206- } ) ,
207- ) ;
255+ it . each ( [ false , true ] ) (
256+ 'retains a root peer provider for a nested plugin (workspace: %s)' ,
257+ ( isWorkspacePackage ) => {
258+ const pkg = { devDependencies : { 'vite-plus' : 'latest' , '@oxlint/plugins' : '^1.79.0' } } ;
259+ const packageJsonPath = path . join ( projectPath , 'package.json' ) ;
260+ fs . writeFileSync ( packageJsonPath , JSON . stringify ( pkg ) ) ;
261+ const appPath = path . join ( projectPath , 'packages' , 'app' ) ;
262+ fs . mkdirSync ( appPath , { recursive : true } ) ;
263+ fs . writeFileSync (
264+ path . join ( appPath , 'package.json' ) ,
265+ JSON . stringify ( { dependencies : { 'review-oxlint-plugin' : '1.0.0' } } ) ,
266+ ) ;
267+ const pluginPath = path . join ( appPath , 'node_modules' , 'review-oxlint-plugin' ) ;
268+ fs . mkdirSync ( pluginPath , { recursive : true } ) ;
269+ fs . writeFileSync (
270+ path . join ( pluginPath , 'package.json' ) ,
271+ JSON . stringify ( {
272+ name : 'review-oxlint-plugin' ,
273+ peerDependencies : { '@oxlint/plugins' : '^1.79.0' } ,
274+ } ) ,
275+ ) ;
208276
209- const result = finalizeCoreMigrationForExistingVitePlus (
210- { rootDir : projectPath , packages : [ { name : 'app' , path : 'packages/app' } ] } ,
211- true ,
212- ) ;
277+ const result = finalizeCoreMigrationForExistingVitePlus (
278+ {
279+ rootDir : projectPath ,
280+ packages : isWorkspacePackage ? [ { name : 'app' , path : 'packages/app' } ] : undefined ,
281+ } ,
282+ true ,
283+ ) ;
213284
214- expect ( result . dependencies ) . toBe ( false ) ;
215- expect ( JSON . parse ( fs . readFileSync ( packageJsonPath , 'utf8' ) ) ) . toEqual ( pkg ) ;
216- } ) ;
285+ expect ( result . dependencies ) . toBe ( false ) ;
286+ expect ( JSON . parse ( fs . readFileSync ( packageJsonPath , 'utf8' ) ) ) . toEqual ( pkg ) ;
287+ } ,
288+ ) ;
289+
290+ it . each ( [ { '.' : { import : './index.js' } } , { '.' : './dist/index.js' } ] ) (
291+ 'reads installed peer metadata despite inaccessible exports %j' ,
292+ ( exports ) => {
293+ const packageJsonPath = path . join ( projectPath , 'package.json' ) ;
294+ fs . writeFileSync (
295+ packageJsonPath ,
296+ JSON . stringify ( {
297+ devDependencies : {
298+ 'vite-plus' : 'latest' ,
299+ '@oxlint/plugins' : '^1.79.0' ,
300+ 'review-oxlint-plugin' : '1.0.0' ,
301+ } ,
302+ } ) ,
303+ ) ;
304+ const pluginPath = path . join ( projectPath , 'node_modules' , 'review-oxlint-plugin' ) ;
305+ fs . mkdirSync ( pluginPath , { recursive : true } ) ;
306+ fs . writeFileSync (
307+ path . join ( pluginPath , 'package.json' ) ,
308+ JSON . stringify ( { name : 'review-oxlint-plugin' , version : '1.0.0' , exports } ) ,
309+ ) ;
310+ fs . writeFileSync ( path . join ( pluginPath , 'index.js' ) , 'export default {};' ) ;
311+
312+ const result = finalizeCoreMigrationForExistingVitePlus ( { rootDir : projectPath } , true ) ;
313+
314+ expect ( result . dependencies ) . toBe ( true ) ;
315+ expect (
316+ JSON . parse ( fs . readFileSync ( packageJsonPath , 'utf8' ) ) . devDependencies [ '@oxlint/plugins' ] ,
317+ ) . toBeUndefined ( ) ;
318+ } ,
319+ ) ;
320+
321+ it . each ( [ false , true ] ) (
322+ 'only retains unknown nested peer contracts for workspace packages (workspace: %s)' ,
323+ ( isWorkspacePackage ) => {
324+ const packageJsonPath = path . join ( projectPath , 'package.json' ) ;
325+ fs . writeFileSync (
326+ packageJsonPath ,
327+ JSON . stringify ( {
328+ devDependencies : { 'vite-plus' : 'latest' , '@oxlint/plugins' : '^1.79.0' } ,
329+ } ) ,
330+ ) ;
331+ const nestedPath = path . join ( projectPath , 'nested' ) ;
332+ fs . mkdirSync ( nestedPath ) ;
333+ fs . writeFileSync (
334+ path . join ( nestedPath , 'package.json' ) ,
335+ JSON . stringify ( { name : 'nested' , devDependencies : { 'uninstalled-plugin' : '1.0.0' } } ) ,
336+ ) ;
337+
338+ const result = finalizeCoreMigrationForExistingVitePlus (
339+ {
340+ rootDir : projectPath ,
341+ packages : isWorkspacePackage ? [ { name : 'nested' , path : 'nested' } ] : undefined ,
342+ } ,
343+ true ,
344+ ) ;
345+
346+ expect ( result . dependencies ) . toBe ( ! isWorkspacePackage ) ;
347+ expect (
348+ JSON . parse ( fs . readFileSync ( packageJsonPath , 'utf8' ) ) . devDependencies [ '@oxlint/plugins' ] ,
349+ ) . toBe ( isWorkspacePackage ? '^1.79.0' : undefined ) ;
350+ } ,
351+ ) ;
217352
218353 it . each ( [ '#!/usr/bin/env node\n' , '' ] ) (
219354 'retains an extensionless Node script with prefix %j' ,
0 commit comments