diff --git a/packages/ckeditor5-dev-release-tools/lib/index.js b/packages/ckeditor5-dev-release-tools/lib/index.js index 13c83ea46..d056e3a1e 100644 --- a/packages/ckeditor5-dev-release-tools/lib/index.js +++ b/packages/ckeditor5-dev-release-tools/lib/index.js @@ -32,3 +32,4 @@ export { default as checkVersionAvailability } from './utils/checkversionavailab export { default as getNpmTagFromVersion } from './utils/getnpmtagfromversion.js'; export { default as isVersionPublishableForTag } from './utils/isversionpublishablefortag.js'; export { default as provideToken } from './utils/providetoken.js'; +export { default as findPathsToPackages } from './utils/findpathstopackages.js'; diff --git a/packages/ckeditor5-dev-release-tools/lib/tasks/cleanuppackages.js b/packages/ckeditor5-dev-release-tools/lib/tasks/cleanuppackages.js index 7b33fe1d9..2768d7ad8 100644 --- a/packages/ckeditor5-dev-release-tools/lib/tasks/cleanuppackages.js +++ b/packages/ckeditor5-dev-release-tools/lib/tasks/cleanuppackages.js @@ -6,6 +6,7 @@ import fs from 'fs-extra'; import upath from 'upath'; import { glob } from 'glob'; +import findPathsToPackages from '../utils/findpathstopackages.js'; /** * The purpose of the script is to clean all packages prepared for the release. The cleaning consists of two stages: @@ -29,12 +30,7 @@ import { glob } from 'glob'; */ export default async function cleanUpPackages( options ) { const { packagesDirectory, packageJsonFieldsToRemove, preservePostInstallHook, cwd } = parseOptions( options ); - - const packageJsonPaths = await glob( '*/package.json', { - cwd: upath.join( cwd, packagesDirectory ), - nodir: true, - absolute: true - } ); + const packageJsonPaths = await findPathsToPackages( cwd, packagesDirectory, { includePackageJson: true } ); for ( const packageJsonPath of packageJsonPaths ) { const packagePath = upath.dirname( packageJsonPath ); diff --git a/packages/ckeditor5-dev-release-tools/lib/tasks/publishpackages.js b/packages/ckeditor5-dev-release-tools/lib/tasks/publishpackages.js index 91e973a88..65b1b6efc 100644 --- a/packages/ckeditor5-dev-release-tools/lib/tasks/publishpackages.js +++ b/packages/ckeditor5-dev-release-tools/lib/tasks/publishpackages.js @@ -5,7 +5,6 @@ import upath from 'upath'; import fs from 'fs-extra'; -import { glob } from 'glob'; import assertNpmAuthorization from '../utils/assertnpmauthorization.js'; import assertPackages from '../utils/assertpackages.js'; import assertNpmTag from '../utils/assertnpmtag.js'; @@ -13,6 +12,7 @@ import assertFilesToPublish from '../utils/assertfilestopublish.js'; import executeInParallel from '../utils/executeinparallel.js'; import publishPackageOnNpmCallback from '../utils/publishpackageonnpmcallback.js'; import checkVersionAvailability from '../utils/checkversionavailability.js'; +import findPathsToPackages from '../utils/findpathstopackages.js'; /** * The purpose of the script is to validate the packages prepared for the release and then release them on npm. @@ -127,13 +127,6 @@ export default async function publishPackages( options ) { } ); } -function findPathsToPackages( cwd, packagesDirectory ) { - return glob( '*/', { - cwd: upath.join( cwd, packagesDirectory ), - absolute: true - } ); -} - async function removeAlreadyPublishedPackages( packagePaths ) { for ( const absolutePackagePath of packagePaths ) { const pkgJson = await fs.readJson( upath.join( absolutePackagePath, 'package.json' ) ); diff --git a/packages/ckeditor5-dev-release-tools/lib/tasks/updatedependencies.js b/packages/ckeditor5-dev-release-tools/lib/tasks/updatedependencies.js index 8e22c2a2f..ab66fade3 100644 --- a/packages/ckeditor5-dev-release-tools/lib/tasks/updatedependencies.js +++ b/packages/ckeditor5-dev-release-tools/lib/tasks/updatedependencies.js @@ -4,8 +4,10 @@ */ import fs from 'fs-extra'; -import { glob } from 'glob'; import upath from 'upath'; +import findPathsToPackages from '../utils/findpathstopackages.js'; + +const { normalizeTrim } = upath; /** * The purpose of this script is to update all eligible dependencies to a version specified in the `options.version`. The following packages @@ -38,15 +40,15 @@ export default async function updateDependencies( options ) { cwd = process.cwd() } = options; - const globPatterns = [ 'package.json' ]; - - if ( packagesDirectory ) { - const packagesDirectoryPattern = upath.join( packagesDirectory, '*', 'package.json' ); - - globPatterns.push( packagesDirectoryPattern ); - } - - const pkgJsonPaths = await getPackageJsonPaths( cwd, globPatterns, packagesDirectoryFilter ); + const pkgJsonPaths = await findPathsToPackages( + cwd, + packagesDirectory ? normalizeTrim( packagesDirectory ) : null, + { + includePackageJson: true, + includeCwd: true, + packagesDirectoryFilter + } + ); for ( const pkgJsonPath of pkgJsonPaths ) { const pkgJson = await fs.readJson( pkgJsonPath ); @@ -78,28 +80,6 @@ function updateVersion( version, callback, dependencies ) { } } -/** - * @param {string} cwd - * @param {Array.} globPatterns - * @param {UpdateDependenciesPackagesDirectoryFilter|null} packagesDirectoryFilter - * @returns {Promise.>} - */ -async function getPackageJsonPaths( cwd, globPatterns, packagesDirectoryFilter ) { - const globOptions = { - cwd, - nodir: true, - absolute: true - }; - - const pkgJsonPaths = await glob( globPatterns, globOptions ); - - if ( !packagesDirectoryFilter ) { - return pkgJsonPaths; - } - - return pkgJsonPaths.filter( packagesDirectoryFilter ); -} - /** * @callback UpdateVersionCallback * diff --git a/packages/ckeditor5-dev-release-tools/lib/tasks/updateversions.js b/packages/ckeditor5-dev-release-tools/lib/tasks/updateversions.js index 1e78ab0bf..99a452d8a 100644 --- a/packages/ckeditor5-dev-release-tools/lib/tasks/updateversions.js +++ b/packages/ckeditor5-dev-release-tools/lib/tasks/updateversions.js @@ -5,8 +5,8 @@ import upath from 'upath'; import fs from 'fs-extra'; -import { glob } from 'glob'; import semver from 'semver'; +import findPathsToPackages from '../utils/findpathstopackages.js'; const { normalizeTrim } = upath; @@ -37,10 +37,16 @@ export default async function updateVersions( options ) { packagesDirectoryFilter = null, cwd = process.cwd() } = options; - const normalizedPackagesDir = packagesDirectory ? normalizeTrim( packagesDirectory ) : null; - const globPatterns = getGlobPatterns( normalizedPackagesDir ); - const pkgJsonPaths = await getPackageJsonPaths( cwd, globPatterns, packagesDirectoryFilter ); + const pkgJsonPaths = await findPathsToPackages( + cwd, + packagesDirectory ? normalizeTrim( packagesDirectory ) : null, + { + includePackageJson: true, + includeCwd: true, + packagesDirectoryFilter + } + ); checkIfVersionIsValid( version ); @@ -52,40 +58,6 @@ export default async function updateVersions( options ) { } } -/** - * @param {string} cwd - * @param {Array.} globPatterns - * @param {UpdateVersionsPackagesDirectoryFilter|null} packagesDirectoryFilter - * @returns {Promise.>} - */ -async function getPackageJsonPaths( cwd, globPatterns, packagesDirectoryFilter ) { - const pkgJsonPaths = await glob( globPatterns, { - cwd, - absolute: true, - nodir: true - } ); - - if ( !packagesDirectoryFilter ) { - return pkgJsonPaths; - } - - return pkgJsonPaths.filter( packagesDirectoryFilter ); -} - -/** - * @param {string|null} packagesDirectory - * @returns {Array.} - */ -function getGlobPatterns( packagesDirectory ) { - const patterns = [ 'package.json' ]; - - if ( packagesDirectory ) { - patterns.push( packagesDirectory + '/*/package.json' ); - } - - return patterns; -} - /** * @param {string} version */ diff --git a/packages/ckeditor5-dev-release-tools/lib/utils/executeinparallel.js b/packages/ckeditor5-dev-release-tools/lib/utils/executeinparallel.js index 0ffac7661..c6af4b88a 100644 --- a/packages/ckeditor5-dev-release-tools/lib/utils/executeinparallel.js +++ b/packages/ckeditor5-dev-release-tools/lib/utils/executeinparallel.js @@ -10,8 +10,8 @@ import upath from 'upath'; import os from 'os'; import fs from 'fs/promises'; import { Worker } from 'worker_threads'; -import { glob } from 'glob'; import { registerAbortController, deregisterAbortController } from './abortcontroller.js'; +import findPathsToPackages from './findpathstopackages.js'; const WORKER_SCRIPT = new URL( './parallelworker.js', import.meta.url ); @@ -49,15 +49,9 @@ export default async function executeInParallel( options ) { } = options; const concurrencyAsInteger = Math.floor( concurrency ) || 1; - const normalizedCwd = upath.toUnix( cwd ); - const packages = ( await glob( `${ packagesDirectory }/*/`, { - cwd: normalizedCwd, - absolute: true - } ) ).map( upath.normalize ); - - const packagesToProcess = packagesDirectoryFilter ? - packages.filter( packagesDirectoryFilter ) : - packages; + const packagesToProcess = await findPathsToPackages( cwd, packagesDirectory, { + packagesDirectoryFilter + } ); const packagesInThreads = getPackagesGroupedByThreads( packagesToProcess, concurrencyAsInteger ); diff --git a/packages/ckeditor5-dev-release-tools/lib/utils/findpathstopackages.js b/packages/ckeditor5-dev-release-tools/lib/utils/findpathstopackages.js new file mode 100644 index 000000000..07128bf09 --- /dev/null +++ b/packages/ckeditor5-dev-release-tools/lib/utils/findpathstopackages.js @@ -0,0 +1,78 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import upath from 'upath'; +import { glob } from 'glob'; + +/** + * Returns an array containing paths to packages found in the `packagesDirectory` directory. + * + * @param {string} cwd + * @param {string|null} packagesDirectory + * @param {object} [options={}] + * @param {boolean} [options.includePackageJson=false] + * @param {boolean} [options.includeCwd=false] + * @param {PackagesDirectoryFilter|null} [options.packagesDirectoryFilter=null] + * @returns {Array.} + */ +export default async function findPathsToPackages( cwd, packagesDirectory, options = {} ) { + const { + includePackageJson = false, + includeCwd = false, + packagesDirectoryFilter = null + } = options; + + const packagePaths = await getPackages( cwd, packagesDirectory, includePackageJson ); + + if ( includeCwd ) { + if ( includePackageJson ) { + packagePaths.push( upath.join( cwd, 'package.json' ) ); + } else { + packagePaths.push( cwd ); + } + } + + const normalizedPaths = packagePaths.map( item => upath.normalize( item ) ); + + if ( packagesDirectoryFilter ) { + return normalizedPaths.filter( item => packagesDirectoryFilter( item ) ); + } + + return normalizedPaths; +} + +/** + * @param {string} cwd + * @param {string|null} packagesDirectory + * @param {boolean} includePackageJson + * @returns {Promise.>} + */ +function getPackages( cwd, packagesDirectory, includePackageJson ) { + if ( !packagesDirectory ) { + return []; + } + + const globOptions = { + cwd: upath.join( cwd, packagesDirectory ), + absolute: true + }; + + let pattern = '*/'; + + if ( includePackageJson ) { + pattern += 'package.json'; + globOptions.nodir = true; + } + + return glob( pattern, globOptions ); +} + +/** + * @callback PackagesDirectoryFilter + * + * @param {string} packageJsonPath An absolute path to a `package.json` file. + * + * @returns {boolean} Whether to include (`true`) or skip (`false`) processing the given directory/package. + */ diff --git a/packages/ckeditor5-dev-release-tools/tests/index.js b/packages/ckeditor5-dev-release-tools/tests/index.js index eeb234fc8..e68cf0267 100644 --- a/packages/ckeditor5-dev-release-tools/tests/index.js +++ b/packages/ckeditor5-dev-release-tools/tests/index.js @@ -33,6 +33,7 @@ import checkVersionAvailability from '../lib/utils/checkversionavailability.js'; import getNpmTagFromVersion from '../lib/utils/getnpmtagfromversion.js'; import isVersionPublishableForTag from '../lib/utils/isversionpublishablefortag.js'; import provideToken from '../lib/utils/providetoken.js'; +import findPathsToPackages from '../lib/utils/findpathstopackages.js'; import * as index from '../lib/index.js'; @@ -53,7 +54,8 @@ vi.mock( '../lib/utils/changelog' ); vi.mock( '../lib/utils/executeinparallel' ); vi.mock( '../lib/utils/validaterepositorytorelease' ); vi.mock( '../lib/utils/isversionpublishablefortag' ); -vi.mock( '../lib/utils/provideToken' ); +vi.mock( '../lib/utils/providetoken' ); +vi.mock( '../lib/utils/findpathstopackages' ); describe( 'dev-release-tools/index', () => { describe( 'generateChangelogForSinglePackage()', () => { @@ -244,4 +246,11 @@ describe( 'dev-release-tools/index', () => { expect( index.provideToken ).to.equal( provideToken ); } ); } ); + + describe( 'findPathsToPackages()', () => { + it( 'should be a function', () => { + expect( findPathsToPackages ).to.be.a( 'function' ); + expect( index.findPathsToPackages ).to.equal( findPathsToPackages ); + } ); + } ); } ); diff --git a/packages/ckeditor5-dev-release-tools/tests/tasks/cleanuppackages.js b/packages/ckeditor5-dev-release-tools/tests/tasks/cleanuppackages.js index c9ebd4f71..1e3d294f0 100644 --- a/packages/ckeditor5-dev-release-tools/tests/tasks/cleanuppackages.js +++ b/packages/ckeditor5-dev-release-tools/tests/tasks/cleanuppackages.js @@ -8,6 +8,7 @@ import fs from 'fs-extra'; import upath from 'upath'; import { glob } from 'glob'; import mockFs from 'mock-fs'; +import findPathsToPackages from '../../lib/utils/findpathstopackages.js'; describe( 'cleanUpPackages()', () => { let cleanUpPackages, stubs; @@ -27,10 +28,14 @@ describe( 'cleanUpPackages()', () => { readdir: vi.fn().mockImplementation( fs.readdir ) } } ) ); + vi.doMock( '../../lib/utils/findpathstopackages.js', () => ( { + default: vi.fn().mockImplementation( findPathsToPackages ) + } ) ); stubs = { ...await import( 'glob' ), - ...( await import( 'fs-extra' ) ).default + ...( await import( 'fs-extra' ) ).default, + findPathsToPackages: ( await import( '../../lib/utils/findpathstopackages.js' ) ).default }; cleanUpPackages = ( await import( '../../lib/tasks/cleanuppackages.js' ) ).default; @@ -52,9 +57,11 @@ describe( 'cleanUpPackages()', () => { cwd: '/work/another/project' } ); - expect( stubs.glob ).toHaveBeenCalledExactlyOnceWith( expect.any( String ), expect.objectContaining( { - cwd: '/work/another/project/release' - } ) ); + expect( stubs.findPathsToPackages ).toHaveBeenCalledExactlyOnceWith( + '/work/another/project', + 'release', + { includePackageJson: true } + ); } ); it( 'should use `process.cwd()` to search for packages if `cwd` option is not provided', async () => { @@ -64,37 +71,11 @@ describe( 'cleanUpPackages()', () => { packagesDirectory: 'release' } ); - expect( stubs.glob ).toHaveBeenCalledExactlyOnceWith( expect.any( String ), expect.objectContaining( { - cwd: '/work/project/release' - } ) ); - } ); - - it( 'should match only files', async () => { - await cleanUpPackages( { - packagesDirectory: 'release' - } ); - - expect( stubs.glob ).toHaveBeenCalledExactlyOnceWith( expect.any( String ), expect.objectContaining( { - nodir: true - } ) ); - } ); - - it( 'should always receive absolute paths for matched files', async () => { - await cleanUpPackages( { - packagesDirectory: 'release' - } ); - - expect( stubs.glob ).toHaveBeenCalledExactlyOnceWith( expect.any( String ), expect.objectContaining( { - absolute: true - } ) ); - } ); - - it( 'should search for `package.json` in `cwd`', async () => { - await cleanUpPackages( { - packagesDirectory: 'release' - } ); - - expect( stubs.glob ).toHaveBeenCalledExactlyOnceWith( '*/package.json', expect.any( Object ) ); + expect( stubs.findPathsToPackages ).toHaveBeenCalledExactlyOnceWith( + '/work/project', + 'release', + { includePackageJson: true } + ); } ); } ); diff --git a/packages/ckeditor5-dev-release-tools/tests/tasks/publishpackages.js b/packages/ckeditor5-dev-release-tools/tests/tasks/publishpackages.js index 05f630f53..fe0b1af39 100644 --- a/packages/ckeditor5-dev-release-tools/tests/tasks/publishpackages.js +++ b/packages/ckeditor5-dev-release-tools/tests/tasks/publishpackages.js @@ -4,9 +4,7 @@ */ import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest'; -import upath from 'upath'; import fs from 'fs-extra'; -import { glob } from 'glob'; import { differenceInMilliseconds } from 'date-fns'; import assertNpmAuthorization from '../../lib/utils/assertnpmauthorization.js'; import assertPackages from '../../lib/utils/assertpackages.js'; @@ -16,8 +14,8 @@ import executeInParallel from '../../lib/utils/executeinparallel.js'; import publishPackageOnNpmCallback from '../../lib/utils/publishpackageonnpmcallback.js'; import publishPackages from '../../lib/tasks/publishpackages.js'; import checkVersionAvailability from '../../lib/utils/checkversionavailability.js'; +import findPathsToPackages from '../../lib/utils/findpathstopackages.js'; -vi.mock( 'glob' ); vi.mock( 'fs-extra' ); vi.mock( '../../lib/utils/assertnpmauthorization.js' ); vi.mock( '../../lib/utils/assertpackages.js' ); @@ -26,12 +24,13 @@ vi.mock( '../../lib/utils/assertfilestopublish.js' ); vi.mock( '../../lib/utils/executeinparallel.js' ); vi.mock( '../../lib/utils/publishpackageonnpmcallback.js' ); vi.mock( '../../lib/utils/checkversionavailability.js' ); +vi.mock( '../../lib/utils/findpathstopackages.js' ); describe( 'publishPackages()', () => { beforeEach( () => { vi.spyOn( process, 'cwd' ).mockReturnValue( '/work/project' ); - vi.mocked( glob ).mockResolvedValue( [] ); + vi.mocked( findPathsToPackages ).mockResolvedValue( [] ); vi.mocked( assertNpmAuthorization ).mockResolvedValue(); vi.mocked( assertPackages ).mockResolvedValue(); vi.mocked( assertNpmTag ).mockResolvedValue(); @@ -56,27 +55,17 @@ describe( 'publishPackages()', () => { npmOwner: 'pepe' } ); - expect( vi.mocked( glob ) ).toHaveBeenCalledWith( - '*/', - expect.objectContaining( { - cwd: upath.join( process.cwd(), 'packages' ), - absolute: true - } ) ); + expect( vi.mocked( findPathsToPackages ) ).toHaveBeenCalledWith( '/work/project', 'packages' ); } ); it( 'should read the package directory (custom `cwd`)', async () => { await publishPackages( { packagesDirectory: 'packages', npmOwner: 'pepe', - cwd: '/work/project' + cwd: '/work/custom-dir' } ); - expect( vi.mocked( glob ) ).toHaveBeenCalledWith( - '*/', - expect.objectContaining( { - cwd: '/work/project/packages', - absolute: true - } ) ); + expect( vi.mocked( findPathsToPackages ) ).toHaveBeenCalledWith( '/work/custom-dir', 'packages' ); } ); it( 'should assert npm authorization', async () => { @@ -100,7 +89,7 @@ describe( 'publishPackages()', () => { } ); it( 'should assert that each found directory is a package', async () => { - vi.mocked( glob ) + vi.mocked( findPathsToPackages ) .mockResolvedValueOnce( [ '/work/project/packages/ckeditor5-foo', '/work/project/packages/ckeditor5-bar' @@ -126,7 +115,7 @@ describe( 'publishPackages()', () => { // See: https://github.com/ckeditor/ckeditor5/issues/15127. it( 'should allow enabling the "package entry point" validator', async () => { - vi.mocked( glob ) + vi.mocked( findPathsToPackages ) .mockResolvedValueOnce( [ '/work/project/packages/ckeditor5-foo', '/work/project/packages/ckeditor5-bar' @@ -168,7 +157,7 @@ describe( 'publishPackages()', () => { } ); it( 'should assert that each required file exists in the package directory (no optional entries)', async () => { - vi.mocked( glob ) + vi.mocked( findPathsToPackages ) .mockResolvedValueOnce( [ '/work/project/packages/ckeditor5-foo', '/work/project/packages/ckeditor5-bar' @@ -190,7 +179,7 @@ describe( 'publishPackages()', () => { } ); it( 'should assert that each required file exists in the package directory (with optional entries)', async () => { - vi.mocked( glob ) + vi.mocked( findPathsToPackages ) .mockResolvedValueOnce( [ '/work/project/packages/ckeditor5-foo', '/work/project/packages/ckeditor5-bar' @@ -228,7 +217,7 @@ describe( 'publishPackages()', () => { } ); it( 'should assert that version tag matches the npm tag (default npm tag)', async () => { - vi.mocked( glob ) + vi.mocked( findPathsToPackages ) .mockResolvedValueOnce( [ '/work/project/packages/ckeditor5-foo', '/work/project/packages/ckeditor5-bar' @@ -250,7 +239,7 @@ describe( 'publishPackages()', () => { } ); it( 'should assert that version tag matches the npm tag (custom npm tag)', async () => { - vi.mocked( glob ) + vi.mocked( findPathsToPackages ) .mockResolvedValueOnce( [ '/work/project/packages/ckeditor5-foo', '/work/project/packages/ckeditor5-bar' @@ -388,7 +377,7 @@ describe( 'publishPackages()', () => { } ); it( 'should verify if given package can be published', async () => { - vi.mocked( glob ) + vi.mocked( findPathsToPackages ) .mockResolvedValueOnce( [ '/work/project/packages/ckeditor5-foo', '/work/project/packages/ckeditor5-bar' @@ -414,7 +403,7 @@ describe( 'publishPackages()', () => { } ); it( 'should remove a package if is already published', async () => { - vi.mocked( glob ) + vi.mocked( findPathsToPackages ) .mockResolvedValueOnce( [ '/work/project/packages/ckeditor5-foo', '/work/project/packages/ckeditor5-bar' @@ -448,7 +437,7 @@ describe( 'publishPackages()', () => { } ); it( 'should not execute the specified `confirmationCallback` when re-publishing packages', async () => { - vi.mocked( glob ) + vi.mocked( findPathsToPackages ) // First execution. .mockResolvedValueOnce( [ '/work/project/packages/ckeditor5-bar' @@ -480,7 +469,7 @@ describe( 'publishPackages()', () => { } ); it( 'should execute itself once again after a timeout passes if some packages could not be published', async () => { - vi.mocked( glob ) + vi.mocked( findPathsToPackages ) // First execution. .mockResolvedValueOnce( [ '/work/project/packages/ckeditor5-bar' @@ -514,7 +503,7 @@ describe( 'publishPackages()', () => { } ); it( 'should try to publish packages thrice before rejecting a promise', async () => { - vi.mocked( glob ) + vi.mocked( findPathsToPackages ) .mockResolvedValueOnce( [ '/work/project/packages/ckeditor5-bar' ] ) .mockResolvedValueOnce( [ '/work/project/packages/ckeditor5-bar' ] ) .mockResolvedValueOnce( [ '/work/project/packages/ckeditor5-bar' ] ) @@ -538,7 +527,7 @@ describe( 'publishPackages()', () => { } ); it( 'should execute itself and publish the non-published packages again (integration)', async () => { - vi.mocked( glob ) + vi.mocked( findPathsToPackages ) // First execution. .mockResolvedValueOnce( [ '/work/project/packages/ckeditor5-foo', @@ -587,11 +576,11 @@ describe( 'publishPackages()', () => { expect( vi.mocked( fs ).remove ).toHaveBeenCalledWith( '/work/project/packages/ckeditor5-foo' ); expect( vi.mocked( executeInParallel ) ).toHaveBeenCalledTimes( 2 ); - expect( vi.mocked( glob ) ).toHaveBeenCalledTimes( 4 ); + expect( vi.mocked( findPathsToPackages ) ).toHaveBeenCalledTimes( 4 ); } ); it( 'should reject a promise if cannot publish packages and there is no more attempting', async () => { - vi.mocked( glob ).mockResolvedValue( [ '/work/project/packages/ckeditor5-bar' ] ); + vi.mocked( findPathsToPackages ).mockResolvedValue( [ '/work/project/packages/ckeditor5-bar' ] ); vi.mocked( fs ).readJson.mockResolvedValue( { name: '@ckeditor/ckeditor5-bar', @@ -606,7 +595,7 @@ describe( 'publishPackages()', () => { } ); it( 'should reject a promise if cannot publish packages and there is no more attempting (a negative attempts value)', async () => { - vi.mocked( glob ).mockResolvedValue( [ '/work/project/packages/ckeditor5-bar' ] ); + vi.mocked( findPathsToPackages ).mockResolvedValue( [ '/work/project/packages/ckeditor5-bar' ] ); vi.mocked( fs ).readJson.mockResolvedValue( { name: '@ckeditor/ckeditor5-bar', diff --git a/packages/ckeditor5-dev-release-tools/tests/tasks/updatedependencies.js b/packages/ckeditor5-dev-release-tools/tests/tasks/updatedependencies.js index 9574d1762..dd24a023e 100644 --- a/packages/ckeditor5-dev-release-tools/tests/tasks/updatedependencies.js +++ b/packages/ckeditor5-dev-release-tools/tests/tasks/updatedependencies.js @@ -5,12 +5,11 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import fs from 'fs-extra'; -import { glob } from 'glob'; -import upath from 'upath'; import updateDependencies from '../../lib/tasks/updatedependencies.js'; +import findPathsToPackages from '../../lib/utils/findpathstopackages.js'; vi.mock( 'fs-extra' ); -vi.mock( 'glob' ); +vi.mock( '../../lib/utils/findpathstopackages.js' ); describe( 'updateDependencies()', () => { beforeEach( () => { @@ -19,7 +18,7 @@ describe( 'updateDependencies()', () => { describe( 'preparing options', () => { beforeEach( () => { - vi.mocked( glob ).mockResolvedValue( [] ); + vi.mocked( findPathsToPackages ).mockResolvedValue( [] ); } ); it( 'should use provided `cwd` to search for packages', async () => { @@ -29,43 +28,43 @@ describe( 'updateDependencies()', () => { await updateDependencies( options ); - expect( vi.mocked( glob ) ).toHaveBeenCalledExactlyOnceWith( - [ 'package.json' ], - expect.objectContaining( { - cwd: '/work/another/project' - } ) + expect( vi.mocked( findPathsToPackages ) ).toHaveBeenCalledExactlyOnceWith( + '/work/another/project', + null, + expect.any( Object ) ); } ); it( 'should use `process.cwd()` to search for packages if `cwd` option is not provided', async () => { await updateDependencies( {} ); - expect( vi.mocked( glob ) ).toHaveBeenCalledExactlyOnceWith( - [ 'package.json' ], - expect.objectContaining( { - cwd: '/work/project' - } ) + expect( vi.mocked( findPathsToPackages ) ).toHaveBeenCalledExactlyOnceWith( + '/work/project', + null, + expect.any( Object ) ); } ); - it( 'should match only files', async () => { + it( 'should search for "package.json" files', async () => { await updateDependencies( {} ); - expect( vi.mocked( glob ) ).toHaveBeenCalledExactlyOnceWith( - expect.any( Array ), + expect( vi.mocked( findPathsToPackages ) ).toHaveBeenCalledExactlyOnceWith( + expect.anything(), + null, expect.objectContaining( { - nodir: true + includePackageJson: true } ) ); } ); - it( 'should always receive absolute paths for matched files', async () => { + it( 'should include a package included in the "cwd"', async () => { await updateDependencies( {} ); - expect( vi.mocked( glob ) ).toHaveBeenCalledExactlyOnceWith( - expect.any( Array ), + expect( vi.mocked( findPathsToPackages ) ).toHaveBeenCalledExactlyOnceWith( + expect.anything(), + null, expect.objectContaining( { - absolute: true + includeCwd: true } ) ); } ); @@ -75,35 +74,55 @@ describe( 'updateDependencies()', () => { packagesDirectory: 'packages' } ); - expect( vi.mocked( glob ) ).toHaveBeenCalledExactlyOnceWith( - [ - 'package.json', - 'packages/*/package.json' - ], + expect( vi.mocked( findPathsToPackages ) ).toHaveBeenCalledExactlyOnceWith( + expect.anything(), + 'packages', expect.any( Object ) ); } ); - it( 'should not search for packages if the `packagesDirectory` option is not provided', async () => { - await updateDependencies( {} ); + it( 'should convert backslashes to slashes from the `packagesDirectory` (Windows-like paths)', async () => { + await updateDependencies( { + packagesDirectory: 'path\\to\\packages\\' + } ); - expect( vi.mocked( glob ) ).toHaveBeenCalledExactlyOnceWith( - [ 'package.json' ], + expect( vi.mocked( findPathsToPackages ) ).toHaveBeenCalledExactlyOnceWith( + expect.anything(), + 'path/to/packages', expect.any( Object ) ); } ); - it( 'should convert backslashes to slashes from the `packagesDirectory` (Windows-like paths)', async () => { + it( 'should pass `null` as `packagesDirectoryFilter` when searching for packages if not specified', async () => { await updateDependencies( { - packagesDirectory: 'path\\to\\packages\\' + version: '^38.0.0', + packagesDirectory: 'packages' } ); - expect( vi.mocked( glob ) ).toHaveBeenCalledExactlyOnceWith( - [ - 'package.json', - 'path/to/packages/*/package.json' - ], - expect.any( Object ) + expect( vi.mocked( findPathsToPackages ) ).toHaveBeenCalledExactlyOnceWith( + expect.anything(), + expect.anything(), + expect.objectContaining( { + packagesDirectoryFilter: null + } ) + ); + } ); + + it( 'should allow filtering out packages that do not pass the `packagesDirectoryFilter` callback', async () => { + const packagesDirectoryFilter = vi.fn(); + + await updateDependencies( { + version: '^38.0.0', + packagesDirectory: 'packages', + packagesDirectoryFilter + } ); + + expect( vi.mocked( findPathsToPackages ) ).toHaveBeenCalledExactlyOnceWith( + expect.anything(), + expect.anything(), + expect.objectContaining( { + packagesDirectoryFilter + } ) ); } ); } ); @@ -116,21 +135,11 @@ describe( 'updateDependencies()', () => { } ); it( 'should read and write `package.json` for each found package', async () => { - vi.mocked( glob ).mockImplementation( patterns => { - const paths = { - 'package.json': [ - '/work/project/package.json' - ], - 'packages/*/package.json': [ - '/work/project/packages/ckeditor5-foo/package.json', - '/work/project/packages/ckeditor5-bar/package.json' - ] - }; - - return Promise.resolve( - patterns.flatMap( pattern => paths[ pattern ] || [] ) - ); - } ); + vi.mocked( findPathsToPackages ).mockResolvedValue( [ + '/work/project/package.json', + '/work/project/packages/ckeditor5-foo/package.json', + '/work/project/packages/ckeditor5-bar/package.json' + ] ); vi.mocked( fs ).readJson.mockResolvedValue( {} ); @@ -161,58 +170,8 @@ describe( 'updateDependencies()', () => { ); } ); - it( 'should allow filtering out packages that do not pass the `packagesDirectoryFilter` callback', async () => { - vi.mocked( glob ).mockImplementation( patterns => { - const paths = { - 'package.json': [ - '/work/project/package.json' - ], - 'packages/*/package.json': [ - '/work/project/packages/ckeditor5-ignore-me/package.json', - '/work/project/packages/ckeditor5-bar/package.json' - ] - }; - - return Promise.resolve( - patterns.flatMap( pattern => paths[ pattern ] || [] ) - ); - } ); - - vi.mocked( fs ).readJson.mockResolvedValue( {} ); - - const directoriesToSkip = [ - 'ckeditor5-ignore-me' - ]; - - await updateDependencies( { - version: '^38.0.0', - packagesDirectory: 'packages', - packagesDirectoryFilter: packageJsonPath => { - return !directoriesToSkip.some( item => { - return upath.dirname( packageJsonPath ).endsWith( item ); - } ); - } - } ); - - expect( vi.mocked( fs ).readJson ).toHaveBeenCalledTimes( 2 ); - expect( vi.mocked( fs ).readJson ).toHaveBeenCalledWith( '/work/project/package.json' ); - expect( vi.mocked( fs ).readJson ).toHaveBeenCalledWith( '/work/project/packages/ckeditor5-bar/package.json' ); - - expect( vi.mocked( fs ).writeJson ).toHaveBeenCalledTimes( 2 ); - expect( vi.mocked( fs ).writeJson ).toHaveBeenCalledWith( - '/work/project/package.json', - expect.any( Object ), - expect.any( Object ) - ); - expect( vi.mocked( fs ).writeJson ).toHaveBeenCalledWith( - '/work/project/packages/ckeditor5-bar/package.json', - expect.any( Object ), - expect.any( Object ) - ); - } ); - it( 'should update eligible dependencies from the `dependencies` key', async () => { - vi.mocked( glob ).mockResolvedValue( [ '/work/project/package.json' ] ); + vi.mocked( findPathsToPackages ).mockResolvedValue( [ '/work/project/package.json' ] ); vi.mocked( fs ).readJson.mockResolvedValue( { dependencies: { @@ -250,7 +209,7 @@ describe( 'updateDependencies()', () => { } ); it( 'should update eligible dependencies from the `devDependencies` key', async () => { - vi.mocked( glob ).mockResolvedValue( [ '/work/project/package.json' ] ); + vi.mocked( findPathsToPackages ).mockResolvedValue( [ '/work/project/package.json' ] ); vi.mocked( fs ).readJson.mockResolvedValue( { devDependencies: { @@ -288,7 +247,7 @@ describe( 'updateDependencies()', () => { } ); it( 'should update eligible dependencies from the `peerDependencies` key', async () => { - vi.mocked( glob ).mockResolvedValue( [ '/work/project/package.json' ] ); + vi.mocked( findPathsToPackages ).mockResolvedValue( [ '/work/project/package.json' ] ); vi.mocked( fs ).readJson.mockResolvedValue( { peerDependencies: { @@ -326,7 +285,7 @@ describe( 'updateDependencies()', () => { } ); it( 'should not update any package if `shouldUpdateVersionCallback` callback resolves falsy value', async () => { - vi.mocked( glob ).mockResolvedValue( [ '/work/project/package.json' ] ); + vi.mocked( findPathsToPackages ).mockResolvedValue( [ '/work/project/package.json' ] ); vi.mocked( fs ).readJson.mockResolvedValue( { dependencies: { diff --git a/packages/ckeditor5-dev-release-tools/tests/tasks/updateversions.js b/packages/ckeditor5-dev-release-tools/tests/tasks/updateversions.js index a8cae3ccf..eaf1eec08 100644 --- a/packages/ckeditor5-dev-release-tools/tests/tasks/updateversions.js +++ b/packages/ckeditor5-dev-release-tools/tests/tasks/updateversions.js @@ -4,23 +4,22 @@ */ import { beforeEach, describe, expect, it, vi } from 'vitest'; -import { glob } from 'glob'; -import upath from 'upath'; import fs from 'fs-extra'; import updateVersions from '../../lib/tasks/updateversions.js'; +import findPathsToPackages from '../../lib/utils/findpathstopackages.js'; vi.mock( 'fs-extra' ); -vi.mock( 'glob' ); +vi.mock( '../../lib/utils/findpathstopackages.js' ); describe( 'updateVersions()', () => { beforeEach( () => { vi.mocked( fs ).readJson.mockResolvedValue( { version: '1.0.0' } ); - vi.mocked( glob ).mockResolvedValue( [ '/ckeditor5-dev' ] ); + vi.mocked( findPathsToPackages ).mockResolvedValue( [ '/ckeditor5-dev' ] ); vi.spyOn( process, 'cwd' ).mockReturnValue( '/ckeditor5-dev' ); } ); it( 'should update the version field in all found packages including the root package', async () => { - vi.mocked( glob ).mockResolvedValue( [ + vi.mocked( findPathsToPackages ).mockResolvedValue( [ '/ckeditor5-dev/packages/package1/package.json', '/ckeditor5-dev/packages/package2/package.json', '/ckeditor5-dev/packages/package3/package.json', @@ -29,9 +28,14 @@ describe( 'updateVersions()', () => { await updateVersions( { version: '1.0.1', packagesDirectory: 'packages' } ); - expect( vi.mocked( glob ) ).toHaveBeenCalledExactlyOnceWith( - [ 'package.json', 'packages/*/package.json' ], - expect.any( Object ) + expect( vi.mocked( findPathsToPackages ) ).toHaveBeenCalledExactlyOnceWith( + '/ckeditor5-dev', + 'packages', + { + includePackageJson: true, + includeCwd: true, + packagesDirectoryFilter: null + } ); expect( vi.mocked( fs ).writeJson ).toHaveBeenCalledTimes( 4 ); @@ -66,50 +70,38 @@ describe( 'updateVersions()', () => { } ); it( 'should allow filtering out packages that do not pass the `packagesDirectoryFilter` callback', async () => { - vi.mocked( glob ).mockResolvedValue( [ + vi.mocked( findPathsToPackages ).mockResolvedValue( [ '/ckeditor5-dev/packages/package1/package.json', '/ckeditor5-dev/packages/package-bar/package.json', '/ckeditor5-dev/packages/package-foo/package.json', - '/ckeditor5-dev/packages/package-number/package.json', '/ckeditor5-dev/package.json' ] ); - const directoriesToSkip = [ - 'package-number' - ]; + const packagesDirectoryFilter = vi.fn(); await updateVersions( { version: '1.0.1', packagesDirectory: 'packages', - packagesDirectoryFilter: packageJsonPath => { - return !directoriesToSkip.some( item => { - return upath.dirname( packageJsonPath ).endsWith( item ); - } ); - } + packagesDirectoryFilter } ); - expect( vi.mocked( glob ) ).toHaveBeenCalledExactlyOnceWith( - [ 'package.json', 'packages/*/package.json' ], - expect.any( Object ) - ); - - expect( vi.mocked( fs ).writeJson ).toHaveBeenCalled(); - expect( vi.mocked( fs ).writeJson ).not.toHaveBeenCalledWith( - '/ckeditor5-dev/packages/package-number/package.json', - { - version: '1.0.1' - }, - expect.any( Object ) + expect( vi.mocked( findPathsToPackages ) ).toHaveBeenCalledExactlyOnceWith( + expect.anything(), + expect.anything(), + expect.objectContaining( { + packagesDirectoryFilter + } ) ); } ); it( 'should update the version field in the root package when `packagesDirectory` is not provided', async () => { - vi.mocked( glob ).mockResolvedValue( [ '/ckeditor5-dev/package.json' ] ); + vi.mocked( findPathsToPackages ).mockResolvedValue( [ '/ckeditor5-dev/package.json' ] ); await updateVersions( { version: '1.0.1' } ); - expect( vi.mocked( glob ) ).toHaveBeenCalledExactlyOnceWith( - [ 'package.json' ], + expect( vi.mocked( findPathsToPackages ) ).toHaveBeenCalledExactlyOnceWith( + '/ckeditor5-dev', + null, expect.any( Object ) ); @@ -134,13 +126,12 @@ describe( 'updateVersions()', () => { } ); it( 'should be able to provide custom cwd', async () => { - await updateVersions( { version: '1.0.1', cwd: 'Users/username/ckeditor5-dev/custom-dir' } ); + await updateVersions( { version: '1.0.1', cwd: 'C:/Users/username/ckeditor5-dev/custom-dir' } ); - expect( vi.mocked( glob ) ).toHaveBeenCalledExactlyOnceWith( - expect.any( Array ), - expect.objectContaining( { - cwd: 'Users/username/ckeditor5-dev/custom-dir' - } ) + expect( vi.mocked( findPathsToPackages ) ).toHaveBeenCalledExactlyOnceWith( + 'C:/Users/username/ckeditor5-dev/custom-dir', + null, + expect.any( Object ) ); } ); } ); diff --git a/packages/ckeditor5-dev-release-tools/tests/utils/executeinparallel.js b/packages/ckeditor5-dev-release-tools/tests/utils/executeinparallel.js index 2b88c78cb..afcdcc0cc 100644 --- a/packages/ckeditor5-dev-release-tools/tests/utils/executeinparallel.js +++ b/packages/ckeditor5-dev-release-tools/tests/utils/executeinparallel.js @@ -4,10 +4,10 @@ */ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; -import { glob } from 'glob'; import fs from 'fs/promises'; import { registerAbortController, deregisterAbortController } from '../../lib/utils/abortcontroller.js'; import executeInParallel from '../../lib/utils/executeinparallel.js'; +import findPathsToPackages from '../../lib/utils/findpathstopackages.js'; import os from 'os'; const stubs = vi.hoisted( () => ( { @@ -45,7 +45,7 @@ vi.mock( 'crypto', () => ( { } } ) ); -vi.mock( 'glob' ); +vi.mock( '../../lib/utils/findpathstopackages.js' ); vi.mock( 'fs/promises' ); vi.mock( '../../lib/utils/abortcontroller.js' ); @@ -55,7 +55,7 @@ describe( 'executeInParallel()', () => { beforeEach( () => { vi.spyOn( process, 'cwd' ).mockReturnValue( '/home/ckeditor' ); - vi.mocked( glob ).mockResolvedValue( [ + vi.mocked( findPathsToPackages ).mockResolvedValue( [ '/home/ckeditor/my-packages/package-01', '/home/ckeditor/my-packages/package-02', '/home/ckeditor/my-packages/package-03', @@ -92,11 +92,11 @@ describe( 'executeInParallel()', () => { const [ firstWorker, secondWorker ] = stubs.WorkerMock.instances; - expect( glob ).toHaveBeenCalledTimes( 1 ); - expect( glob ).toHaveBeenCalledWith( 'my-packages/*/', expect.objectContaining( { - cwd: '/home/ckeditor', - absolute: true - } ) ); + expect( vi.mocked( findPathsToPackages ) ).toHaveBeenCalledExactlyOnceWith( + '/home/ckeditor', + 'my-packages', + expect.any( Object ) + ); expect( fs.writeFile ).toHaveBeenCalledTimes( 1 ); expect( fs.writeFile ).toHaveBeenCalledWith( @@ -120,11 +120,14 @@ describe( 'executeInParallel()', () => { } ); it( 'should execute the specified `taskToExecute` on packages found in the `packagesDirectory` that are not filtered', async () => { - const options = Object.assign( {}, defaultOptions, { - // Skip "package-02". - packagesDirectoryFilter: packageDirectory => !packageDirectory.endsWith( 'package-02' ) - } ); + vi.mocked( findPathsToPackages ).mockResolvedValue( [ + '/home/ckeditor/my-packages/package-01', + '/home/ckeditor/my-packages/package-03', + '/home/ckeditor/my-packages/package-04' + ] ); + const packagesDirectoryFilter = vi.fn(); + const options = Object.assign( {}, defaultOptions, { packagesDirectoryFilter } ); const promise = executeInParallel( options ); await delay( 0 ); @@ -133,6 +136,12 @@ describe( 'executeInParallel()', () => { const [ firstWorker, secondWorker ] = stubs.WorkerMock.instances; + expect( vi.mocked( findPathsToPackages ) ).toHaveBeenCalledExactlyOnceWith( + '/home/ckeditor', + 'my-packages', + expect.objectContaining( { packagesDirectoryFilter } ) + ); + expect( firstWorker.workerData.packages ).toEqual( [ '/home/ckeditor/my-packages/package-01', '/home/ckeditor/my-packages/package-04' @@ -157,32 +166,7 @@ describe( 'executeInParallel()', () => { const promise = executeInParallel( options ); await delay( 0 ); - expect( glob ).toHaveBeenCalledTimes( 1 ); - expect( glob ).toHaveBeenCalledWith( 'my-packages/*/', expect.objectContaining( { - cwd: '/custom/cwd', - absolute: true - } ) ); - - const [ firstWorker, secondWorker ] = stubs.WorkerMock.instances; - - // Workers did not emit an error. - getExitCallback( firstWorker )( 0 ); - getExitCallback( secondWorker )( 0 ); - - await promise; - } ); - - it( 'should normalize the current working directory to unix-style (default value, Windows path)', async () => { - process.cwd.mockReturnValue( 'C:\\Users\\ckeditor' ); - - const promise = executeInParallel( defaultOptions ); - await delay( 0 ); - - expect( glob ).toHaveBeenCalledTimes( 1 ); - expect( glob ).toHaveBeenCalledWith( 'my-packages/*/', expect.objectContaining( { - cwd: 'C:/Users/ckeditor', - absolute: true - } ) ); + expect( vi.mocked( findPathsToPackages ) ).toHaveBeenCalledExactlyOnceWith( '/custom/cwd', 'my-packages', expect.any( Object ) ); const [ firstWorker, secondWorker ] = stubs.WorkerMock.instances; @@ -193,62 +177,6 @@ describe( 'executeInParallel()', () => { await promise; } ); - it( 'should normalize the current working directory to unix-style (`options.cwd`, Windows path)', async () => { - const options = Object.assign( {}, defaultOptions, { - cwd: 'C:\\Users\\ckeditor' - } ); - - const promise = executeInParallel( options ); - await delay( 0 ); - - expect( glob ).toHaveBeenCalledTimes( 1 ); - expect( glob ).toHaveBeenCalledWith( 'my-packages/*/', expect.objectContaining( { - cwd: 'C:/Users/ckeditor', - absolute: true - } ) ); - - const [ firstWorker, secondWorker ] = stubs.WorkerMock.instances; - - // Workers did not emit an error. - getExitCallback( firstWorker )( 0 ); - getExitCallback( secondWorker )( 0 ); - - await promise; - } ); - - it( 'should work on normalized paths to packages', async () => { - vi.mocked( glob ).mockResolvedValue( [ - 'C:/Users/workspace/ckeditor/my-packages/package-01', - 'C:/Users/workspace/ckeditor/my-packages/package-02', - 'C:/Users/workspace/ckeditor/my-packages/package-03', - 'C:/Users/workspace/ckeditor/my-packages/package-04' - ] ); - - const promise = executeInParallel( defaultOptions ); - await delay( 0 ); - - // By default the helper uses a half of available CPUs. - expect( stubs.WorkerMock.instances ).toHaveLength( 2 ); - - const [ firstWorker, secondWorker ] = stubs.WorkerMock.instances; - - expect( firstWorker.workerData.packages ).toEqual( [ - 'C:/Users/workspace/ckeditor/my-packages/package-01', - 'C:/Users/workspace/ckeditor/my-packages/package-03' - ] ); - - expect( secondWorker.workerData.packages ).toEqual( [ - 'C:/Users/workspace/ckeditor/my-packages/package-02', - 'C:/Users/workspace/ckeditor/my-packages/package-04' - ] ); - - // Workers did not emit an error. - getExitCallback( firstWorker )( 0 ); - getExitCallback( secondWorker )( 0 ); - - await promise; - } ); - it( 'should pass task options to all workers', async () => { const taskOptions = { property: 'Example of the property.', diff --git a/packages/ckeditor5-dev-release-tools/tests/utils/findpathstopackages.js b/packages/ckeditor5-dev-release-tools/tests/utils/findpathstopackages.js new file mode 100644 index 000000000..c8c43a3ed --- /dev/null +++ b/packages/ckeditor5-dev-release-tools/tests/utils/findpathstopackages.js @@ -0,0 +1,164 @@ +/** + * @license Copyright (c) 2003-2024, CKSource Holding sp. z o.o. All rights reserved. + * For licensing, see LICENSE.md. + */ + +import { describe, expect, it, vi } from 'vitest'; +import { glob } from 'glob'; +import findPathsToPackages from '../../lib/utils/findpathstopackages.js'; + +vi.mock( 'glob' ); + +describe( 'findPathsToPackages()', () => { + it( 'should return an empty array if a package directory is not specified', async () => { + await expect( findPathsToPackages( '/home/ckeditor', null ) ) + .resolves.toEqual( [] ); + } ); + + it( 'should include a cwd directory when `includeCwd=true`', async () => { + await expect( findPathsToPackages( '/home/ckeditor', null, { includeCwd: true } ) ) + .resolves.toEqual( [ '/home/ckeditor' ] ); + } ); + + it( 'should point to a "package.json" file when `includePackageJson=true` (cwd check)', async () => { + await expect( findPathsToPackages( '/home/ckeditor', null, { includeCwd: true, includePackageJson: true } ) ) + .resolves.toEqual( [ '/home/ckeditor/package.json' ] ); + } ); + + it( 'should return an array of packages', async () => { + vi.mocked( glob ).mockResolvedValue( [ + '/home/ckeditor/packages/ckeditor5-c', + '/home/ckeditor/packages/ckeditor5-b', + '/home/ckeditor/packages/ckeditor5-a' + ] ); + + await expect( findPathsToPackages( '/home/ckeditor', 'packages' ) ) + .resolves.toEqual( [ + '/home/ckeditor/packages/ckeditor5-c', + '/home/ckeditor/packages/ckeditor5-b', + '/home/ckeditor/packages/ckeditor5-a' + ] ); + } ); + + it( 'should return an array of packages including a cwd directory when `includeCwd=true`', async () => { + vi.mocked( glob ).mockResolvedValue( [ + '/home/ckeditor/packages/ckeditor5-a' + ] ); + + await expect( findPathsToPackages( '/home/ckeditor', 'packages', { includeCwd: true } ) ) + .resolves.toEqual( [ + '/home/ckeditor/packages/ckeditor5-a', + '/home/ckeditor' + ] ); + } ); + + it( 'should return an array of "package.json" paths', async () => { + vi.mocked( glob ).mockResolvedValue( [ + '/home/ckeditor/packages/ckeditor5-c/package.json', + '/home/ckeditor/packages/ckeditor5-b/package.json', + '/home/ckeditor/packages/ckeditor5-a/package.json' + ] ); + + await expect( findPathsToPackages( '/home/ckeditor', 'packages', { includePackageJson: true } ) ) + .resolves.toEqual( [ + '/home/ckeditor/packages/ckeditor5-c/package.json', + '/home/ckeditor/packages/ckeditor5-b/package.json', + '/home/ckeditor/packages/ckeditor5-a/package.json' + ] ); + } ); + + it( 'should return an array of "package.json" paths including a cwd directory when `includeCwd=true`', async () => { + vi.mocked( glob ).mockResolvedValue( [ + '/home/ckeditor/packages/ckeditor5-a/package.json' + ] ); + + await expect( findPathsToPackages( '/home/ckeditor', 'packages', { includeCwd: true, includePackageJson: true } ) ) + .resolves.toEqual( [ + '/home/ckeditor/packages/ckeditor5-a/package.json', + '/home/ckeditor/package.json' + ] ); + } ); + + it( 'should filter filter packages when `packagesDirectoryFilter` is defined', async () => { + vi.mocked( glob ).mockResolvedValue( [ + '/home/ckeditor/packages/ckeditor5-2', + '/home/ckeditor/packages/ckeditor5-1', + '/home/ckeditor/packages/ckeditor5-a', + '/home/ckeditor/packages/ckeditor5-b' + ] ); + + const packagesDirectoryFilter = vi.fn( packageJsonPath => { + return packageJsonPath.endsWith( 'a' ) || packageJsonPath.endsWith( '1' ); + } ); + + await expect( findPathsToPackages( '/home/ckeditor', 'packages', { packagesDirectoryFilter } ) ) + .resolves.toEqual( [ + '/home/ckeditor/packages/ckeditor5-1', + '/home/ckeditor/packages/ckeditor5-a' + ] ); + + expect( packagesDirectoryFilter ).toHaveBeenCalledTimes( 4 ); + expect( packagesDirectoryFilter ).toHaveBeenCalledWith( '/home/ckeditor/packages/ckeditor5-2' ); + expect( packagesDirectoryFilter ).toHaveBeenCalledWith( '/home/ckeditor/packages/ckeditor5-1' ); + expect( packagesDirectoryFilter ).toHaveBeenCalledWith( '/home/ckeditor/packages/ckeditor5-b' ); + expect( packagesDirectoryFilter ).toHaveBeenCalledWith( '/home/ckeditor/packages/ckeditor5-a' ); + } ); + + it( 'should filter filter packages when `packagesDirectoryFilter` is defined (`includeCwd=true` check)', async () => { + vi.mocked( glob ).mockResolvedValue( [ + '/home/ckeditor/packages/ckeditor5-b', + '/home/ckeditor/packages/ckeditor5-a' + ] ); + + const packagesDirectoryFilter = vi.fn( packageJsonPath => { + return packageJsonPath.endsWith( 'ckeditor' ); + } ); + + await expect( findPathsToPackages( '/home/ckeditor', 'packages', { packagesDirectoryFilter, includeCwd: true } ) ) + .resolves.toEqual( [ + '/home/ckeditor' + ] ); + + expect( packagesDirectoryFilter ).toHaveBeenCalledTimes( 3 ); + expect( packagesDirectoryFilter ).toHaveBeenCalledWith( '/home/ckeditor/packages/ckeditor5-b' ); + expect( packagesDirectoryFilter ).toHaveBeenCalledWith( '/home/ckeditor/packages/ckeditor5-a' ); + expect( packagesDirectoryFilter ).toHaveBeenCalledWith( '/home/ckeditor' ); + } ); + + it( 'should search for packages in the specified directory', async () => { + vi.mocked( glob ).mockResolvedValue( [] ); + + await findPathsToPackages( '/home/ckeditor', 'packages' ); + + expect( vi.mocked( glob ) ).toHaveBeenCalledExactlyOnceWith( '*/', { + cwd: '/home/ckeditor/packages', + absolute: true + } ); + } ); + + it( 'should search for "package.json" files in the specified directory when `includePackageJson=true`', async () => { + vi.mocked( glob ).mockResolvedValue( [] ); + + await findPathsToPackages( '/home/ckeditor', 'packages', { includePackageJson: true } ); + + expect( vi.mocked( glob ) ).toHaveBeenCalledExactlyOnceWith( '*/package.json', expect.objectContaining( { + nodir: true + } ) ); + } ); + + it( 'should normalize Windows-like paths to Unix-like', async () => { + vi.mocked( glob ).mockResolvedValue( [ + 'C:\\home\\ckeditor\\nested\\packages\\ckeditor5-c\\package.json', + 'C:\\home\\ckeditor\\nested\\packages\\ckeditor5-b\\package.json', + 'C:\\home\\ckeditor\\nested\\packages\\ckeditor5-a\\package.json' + ] ); + + await expect( findPathsToPackages( 'C:\\home\\ckeditor', 'nested\\packages', { includeCwd: true, includePackageJson: true } ) ) + .resolves.toEqual( [ + 'C:/home/ckeditor/nested/packages/ckeditor5-c/package.json', + 'C:/home/ckeditor/nested/packages/ckeditor5-b/package.json', + 'C:/home/ckeditor/nested/packages/ckeditor5-a/package.json', + 'C:/home/ckeditor/package.json' + ] ); + } ); +} );