diff --git a/.changeset/thirty-meals-pay.md b/.changeset/thirty-meals-pay.md new file mode 100644 index 00000000..c5395ba2 --- /dev/null +++ b/.changeset/thirty-meals-pay.md @@ -0,0 +1,5 @@ +--- +"@codemod-utils/files": minor +--- + +Added JSDoc diff --git a/packages/files/README.md b/packages/files/README.md index e88e1d39..37491547 100644 --- a/packages/files/README.md +++ b/packages/files/README.md @@ -12,31 +12,29 @@ _Utilities for handling files_ ## API -Many of the methods make use of the **file path**, a string that represents the location of a file. Therefore, I recommend learning [`findFiles`](#findfiles-unionize) first, as it returns the paths of all files that match the search criteria. +Many of the methods make use of the **file path**, a string that represents the location of a file. Therefore, I recommend learning [`findFiles`](#findfiles) first, as it returns the paths of all files that match the search criteria. ### copyFiles -Copy files from one directory to another. +Copies files from one directory (source) to another (destination). Creates the destination directory if it doesn't exist.
Example -In [`ember-codemod-v1-to-v2`](https://github.com/ijlee2/ember-codemod-v1-to-v2/), we want to copy some files from the project root to the addon package. +Copy `LICENSE.md` and `README.md` from the project root to the folder `ember-container-query`. -```js -import { copyFiles, mapFilePaths } from '@codemod-utils/files'; +```ts +import { copyFiles } from '@codemod-utils/files'; -const filePaths = [/* ... */]; - -const filePathMap = mapFilePaths(filePaths, { - from: '', - to: '__addonLocation__', -}); +const filePathMap = new Map([ + ['LICENSE.md', 'ember-container-query/LICENSE.md'], + ['README.md', 'ember-container-query/README.md'], +]); copyFiles(filePathMap, { - projectRoot: '__projectRoot__', + projectRoot, }); ``` @@ -45,20 +43,39 @@ copyFiles(filePathMap, { ### createDirectory -Ensure that, given a file path, the directories exist. +Creates the directories specified in the file path, if they don't exist yet. + +⚠️ Likely, you won't need this method but [`createFiles`](#createfiles) instead. + +
+ +Example + +Create the folder `ember-container-query` if it doesn't exist. + +```ts +import { createDirectory } from '@codemod-utils/files'; -⚠️ Likely, you won't need this method. Call [`createFiles`](#createfiles) instead. +const newFilePath = 'ember-container-query/LICENSE.md'; +const destination = join(projectRoot, newFilePath); + +createDirectory(destination); +``` + +
### createFiles -Create files in bulk. You will need to provide a mapping between file paths and file contents. +Create files. Creates the destination directory if it doesn't exist.
Example -```js +Create `LICENSE.md` and `README.md` in the project root. + +```ts import { createFiles } from '@codemod-utils/files'; const fileMap = new Map([ @@ -67,14 +84,14 @@ const fileMap = new Map([ ]); createFiles(fileMap, { - projectRoot: '__projectRoot__', + projectRoot, }); ```
-### findFiles, unionize +### findFiles Often, you will want a codemod step to apply to select files. `findFiles` provides the paths of all files that match your search criteria (i.e. [glob pattern](https://github.com/isaacs/node-glob#glob-primer), ignore list, and project root). The paths are sorted in alphabetical order. @@ -82,13 +99,13 @@ Often, you will want a codemod step to apply to select files. `findFiles` provid Example -In [`ember-codemod-v1-to-v2`](https://github.com/ijlee2/ember-codemod-v1-to-v2/), we want to move the `tests/dummy` folder to the test-app package. +Find all component templates in an Ember app. -```js +```ts import { findfiles } from '@codemod-utils/files'; -const filePaths = findFiles('tests/dummy/**/*', { - projectRoot: '__projectRoot__', +const filePaths = findFiles('app/components/**/*.hbs', { + projectRoot, }); ``` @@ -100,32 +117,44 @@ You can provide `ignoreList`, an array of file paths or glob patterns, to exclud Example -In [`ember-codemod-v1-to-v2`](https://github.com/ijlee2/ember-codemod-v1-to-v2/), we want to move some files in `tests` to the test-app package's `tests` folder. +Find all component classes in an Ember app. -```js +```ts import { findfiles } from '@codemod-utils/files'; -const filePaths = findFiles('tests/**/*', { - ignoreList: ['tests/dummy/**/*'], - projectRoot: '__projectRoot__', +const filePaths = findFiles('app/components/**/*.{js,ts}', { + ignoreList: ['**/*.d.ts'], + projectRoot, }); ```
-Lastly, you can use `unionize` to look for multiple files ("possibly file A or file B or ..."): +To look for multiple types of files, you can pass an array of glob patterns (pattern A or pattern B or ...).
-Example +Examples + +```ts +import { findfiles } from '@codemod-utils/files'; -```js -import { findfiles, unionize } from '@codemod-utils/files'; +const filePaths = findFiles([ + 'LICENSE.md', + 'README.md', +], { + projectRoot, +}); +``` -const files = ['LICENSE.md', 'README.md']; +```ts +import { findfiles } from '@codemod-utils/files'; -const filePaths = findFiles(unionize(files), { - projectRoot: '__projectRoot__', +const filePaths = findFiles([ + 'app/components/**/*.hbs', + 'tests/integration/components/**/*-test.{js,ts}', +], { + projectRoot, }); ``` @@ -134,31 +163,48 @@ const filePaths = findFiles(unionize(files), { ### mapFilePaths -Create a mapping of file paths, which can be passed to [`createFiles`](#createfiles) or [`moveFiles`](#movefiles). - - -### moveFiles - -Move files from one directory to another. +Creates a mapping of file paths, which can then be passed to [`copyFiles`](#copyfiles) or [`moveFiles`](#movefiles).
Example -In [`ember-codemod-v1-to-v2`](https://github.com/ijlee2/ember-codemod-v1-to-v2/), we want to move some files from the project root to the test-app package. +Map `LICENSE.md` to `ember-container-query/LICENSE.md` (and similarly for `README.md`). -```js -import { mapFilePaths, moveFiles } from '@codemod-utils/files'; +```ts +import { mapFilePaths } from '@codemod-utils/files'; -const filePaths = [/* ... */]; +const filePaths = ['LICENSE.md', 'README.md']; const filePathMap = mapFilePaths(filePaths, { from: '', - to: '__testAppLocation__', + to: 'ember-container-query', }); +``` + +
+ + +### moveFiles + +Moves files from one directory (source) to another (destination). Creates the destination directory if it doesn't exist. Removes the source directory if it is empty. + +
+ +Example + +Move `LICENSE.md` and `README.md` from the project root to a folder named `ember-container-query`. + +```ts +import { moveFiles } from '@codemod-utils/files'; + +const filePathMap = new Map([ + ['LICENSE.md', 'ember-container-query/LICENSE.md'], + ['README.md', 'ember-container-query/README.md'], +]); moveFiles(filePathMap, { - projectRoot: '__projectRoot__', + projectRoot, }); ``` @@ -167,24 +213,22 @@ moveFiles(filePathMap, { ### parseFilePath -Parse a file path, just like you can with `parse` from `node:path`. `parseFilePath` can handle file extensions with more than one `.` (e.g. `.d.ts`, `.css.d.ts`). +Parses a file path, similarly to `parse()` from `node:path`, but correctly handles file extensions with more than one `.`, e.g. `.d.ts` and `.css.d.ts`.
Example -```js +```ts import { parseFilePath } from '@codemod-utils/files'; const filePath = 'src/components/navigation-menu.d.ts'; const { base, dir, ext, name } = parseFilePath(filePath); -/* - base = 'navigation-menu.d.ts' - dir = 'src/components' - ext = '.d.ts' - name = 'navigation-menu' -*/ +// base -> 'navigation-menu.d.ts' +// dir -> 'src/components' +// ext -> '.d.ts' +// name -> 'navigation-menu' ```
@@ -192,26 +236,46 @@ const { base, dir, ext, name } = parseFilePath(filePath); ### removeDirectoryIfEmpty -Ensure that, after deleting a file, the directories in the file path are removed if empty. +Removes the directories specified in the file path, if they are empty. -⚠️ Likely, you won't need this method. Call [`removeFiles`](#removefiles) instead. +⚠️ Likely, you won't need this method but [`removeFiles`](#removefiles) instead. + +
+ +Example + +Remove the folder `ember-container-query` if it is empty. + +```ts +import { removeDirectoryIfEmpty } from '@codemod-utils/files'; + +const filePath = 'ember-container-query/LICENSE.md'; + +removeDirectoryIfEmpty(filePath, { + projectRoot, +}); +``` + +
### removeFiles -Remove files in bulk. +Removes files. Removes the source directory if it is empty.
Example -```js +Remove `LICENSE.md` and `README.md` from the project root. + +```ts import { removeFiles } from '@codemod-utils/files'; const filePaths = ['LICENSE.md', 'README.md']; removeFiles(filePaths, { - projectRoot: '__projectRoot__', + projectRoot, }); ``` @@ -220,33 +284,25 @@ removeFiles(filePaths, { ### renamePathByDirectory -Get a new file path by altering the path's directory. +Forms a new file path by altering the path's directory.
Example -In [`ember-codemod-v1-to-v2`](https://github.com/ijlee2/ember-codemod-v1-to-v2/), we want to compute `appReexports`. To do so, we find all files in the `app` folder, then remove the word `app` from each file path. +Prepare to move components from `addon` to `ember-container-query/src`. -```js +```ts import { findFiles, renamePathByDirectory } from '@codemod-utils/files'; -function getAppReexports(options) { - const { projectRoot } = options; - - const filePaths = findFiles('app/**/*.js', { - cwd: projectRoot, - }); - - return filePaths - .map((filePath) => { - return renameDirectory(filePath, { - from: 'app', - to: '', - }); - }) - .sort(); -} +const oldFilePath = 'addon/components/container-query.hbs'; + +const newFilePath = renamePathByDirectory(oldFilePath, { + from: 'addon', + to: 'ember-container-query/src', +}); + +// newFilePath -> 'ember-container-query/src/components/container-query.hbs' ```
@@ -254,38 +310,30 @@ function getAppReexports(options) { ### renamePathByFile -Get a new file path by altering the path's file name. +Forms a new file path by altering the path's file name.
Example -In [`ember-codemod-pod-to-octane`](https://github.com/ijlee2/ember-codemod-pod-to-octane/), we want to "un-pod" components. To do so, we find all files in the `app/components` folder, then adjust the file name. +Prepare to un-pod components. -```js +```ts import { findFiles, renamePathByFile } from '@codemod-utils/files'; -function migrationStrategyForComponentClasses(options) { - const { projectRoot } = options; - - const filePaths = findFiles('app/components/**/component.{d.ts,js,ts}', { - projectRoot, - }); - - return filePaths.map((oldFilePath) => { - const newFilePath = renamePathByFile(oldFilePath, { - find: { - directory: 'app/components', - file: 'component', - }, - replace: (key) => { - return `app/components/${key}`; - }, - }); - - return [oldFilePath, newFilePath]; - }); -} +const oldFilePath = 'app/components/navigation-menu/template.hbs'; + +const newFilePath = renamePathByFile(oldFilePath, { + find: { + directory: 'app/components', + file: 'template', + }, + replace: (key: string) => { + return `app/components/${key}`; + }, +}); + +// newFilePath -> 'app/components/navigation-menu.hbs' ```
diff --git a/packages/files/src/files/copy-files.ts b/packages/files/src/files/copy-files.ts index 5fd661f0..17e30e7e 100644 --- a/packages/files/src/files/copy-files.ts +++ b/packages/files/src/files/copy-files.ts @@ -4,7 +4,40 @@ import { join } from 'node:path'; import type { FilePathMap, Options } from '../types/index.js'; import { createDirectory } from './create-directory.js'; -export function copyFiles(filePathMap: FilePathMap, options: Options): void { +/** + * Copies files from one directory (source) to another (destination). + * Creates the destination directory if it doesn't exist. + * + * @param filePathMap + * + * A mapping from source to destination. + * + * @param options + * + * An object with `projectRoot`. + * + * @example + * + * Copy `LICENSE.md` and `README.md` from the project root to the + * folder `ember-container-query`. + * + * ```ts + * const filePathMap = new Map([ + * ['LICENSE.md', 'ember-container-query/LICENSE.md'], + * ['README.md', 'ember-container-query/README.md'], + * ]); + * + * copyFiles(filePathMap, { + * projectRoot, + * }); + * ``` + */ +export function copyFiles( + filePathMap: FilePathMap, + options: Options & { + projectRoot: string; + }, +): void { const { projectRoot } = options; filePathMap.forEach((newFilePath, oldFilePath) => { diff --git a/packages/files/src/files/create-directory.ts b/packages/files/src/files/create-directory.ts index f8e1b546..e8e30b1d 100644 --- a/packages/files/src/files/create-directory.ts +++ b/packages/files/src/files/create-directory.ts @@ -3,6 +3,27 @@ import { dirname } from 'node:path'; import type { FilePath } from '../types/index.js'; +/** + * Creates the directories specified in the file path, if they don't + * exist yet. + * + * ⚠️ Likely, you won't need this method but `createFiles()` instead. + * + * @param filePath + * + * A file path. + * + * @example + * + * Create the folder `ember-container-query` if it doesn't exist. + * + * ```ts + * const newFilePath = 'ember-container-query/LICENSE.md'; + * const destination = join(projectRoot, newFilePath); + * + * createDirectory(destination); + * ``` + */ export function createDirectory(filePath: FilePath): void { const directory = dirname(filePath); diff --git a/packages/files/src/files/create-files.ts b/packages/files/src/files/create-files.ts index 9d57aba8..d16869bd 100644 --- a/packages/files/src/files/create-files.ts +++ b/packages/files/src/files/create-files.ts @@ -4,7 +4,39 @@ import { join } from 'node:path'; import type { FileMap, Options } from '../types/index.js'; import { createDirectory } from './create-directory.js'; -export function createFiles(fileMap: FileMap, options: Options): void { +/** + * Creates files. Creates the destination directory if it doesn't + * exist. + * + * @param fileMap + * + * A mapping between the file path and the file content (UTF-8). + * + * @param options + * + * An object with `projectRoot`. + * + * @example + * + * Create `LICENSE.md` and `README.md` in the project root. + * + * ```ts + * const fileMap = new Map([ + * ['LICENSE.md', 'The MIT License (MIT)'], + * ['README.md', '# ember-container-query'], + * ]); + * + * createFiles(fileMap, { + * projectRoot, + * }); + * ``` + */ +export function createFiles( + fileMap: FileMap, + options: Options & { + projectRoot: string; + }, +): void { const { projectRoot } = options; fileMap.forEach((file, filePath) => { diff --git a/packages/files/src/files/find-files.ts b/packages/files/src/files/find-files.ts index 4ab2e8cd..85e5ecbd 100644 --- a/packages/files/src/files/find-files.ts +++ b/packages/files/src/files/find-files.ts @@ -2,9 +2,70 @@ import { globSync } from 'glob'; import type { FilePath, Options } from '../types/index.js'; +/** + * Returns the paths of all files that match your search criteria + * (i.e. {@link https://github.com/isaacs/node-glob#glob-primer | glob pattern}, ignore list, and project root). + * The paths are sorted in alphabetical order. + * + * @param pattern + * + * A glob pattern that describes which files you are looking for. + * + * @param options + * + * An object with `ignoreList` (an array of file paths or glob + * patterns) and `projectRoot`. + * + * @return + * + * Paths of all files that match your search criteria. + * + * @example + * + * Find all component templates in an Ember app. + * + * ```ts + * const filePaths = findFiles('app/components/**\/*.hbs', { + * projectRoot, + * }); + * ``` + * + * @example + * + * Find all component classes in an Ember app. + * + * ```ts + * const filePaths = findFiles('app/components/**\/*.{js,ts}', { + * ignoreList: ['**\/*.d.ts'], + * projectRoot, + * }); + * ``` + * + * @example + * + * Pass an array of glob patterns (pattern A or pattern B or ...). + * + * ```ts + * const filePaths = findFiles([ + * 'LICENSE.md', + * 'README.md', + * ], { + * projectRoot, + * }); + * ``` + * + * ```ts + * const filePaths = findFiles([ + * 'app/components/**\/*.hbs', + * 'tests/integration/components/**\/*-test.{js,ts}', + * ], { + * projectRoot, + * }); + * ``` + */ export function findFiles( pattern: string | string[], - options: Options & { ignoreList?: string[] }, + options: Options & { ignoreList?: string[]; projectRoot: string }, ): FilePath[] { const { ignoreList = [], projectRoot } = options; @@ -25,11 +86,3 @@ export function findFiles( return filePaths.sort(); } - -export function unionize(files: string[]): string { - if (files.length <= 1) { - return files.join(','); - } - - return `{${files.join(',')}}`; -} diff --git a/packages/files/src/files/map-file-paths.ts b/packages/files/src/files/map-file-paths.ts index 87974f0d..0a0825d6 100644 --- a/packages/files/src/files/map-file-paths.ts +++ b/packages/files/src/files/map-file-paths.ts @@ -1,12 +1,39 @@ import type { FilePath } from '../types/index.js'; import { renamePathByDirectory } from './rename-path-by-directory.js'; -type Options = { - from: string; - to: string; -}; - -export function mapFilePaths(filePaths: FilePath[], options: Options) { +/** + * Creates a mapping of file paths, which can then be passed to + * `copyFiles()` or `moveFiles()`. + * + * @param filePaths + * + * An array of file paths. The array may come from `findFiles()`. + * + * @param options + * + * An object with `from` and `to`. + * + * @example + * + * Map `LICENSE.md` to `ember-container-query/LICENSE.md` (and + * similarly for `README.md`). + * + * ```ts + * const filePaths = ['LICENSE.md', 'README.md']; + * + * const filePathMap = mapFilePaths(filePaths, { + * from: '', + * to: 'ember-container-query', + * }); + * ``` + */ +export function mapFilePaths( + filePaths: FilePath[], + options: { + from: string; + to: string; + }, +) { const { from, to } = options; return new Map( diff --git a/packages/files/src/files/move-files.ts b/packages/files/src/files/move-files.ts index 52abcbef..0ab59c81 100644 --- a/packages/files/src/files/move-files.ts +++ b/packages/files/src/files/move-files.ts @@ -5,7 +5,41 @@ import type { FilePathMap, Options } from '../types/index.js'; import { createDirectory } from './create-directory.js'; import { removeDirectoryIfEmpty } from './remove-directory-if-empty.js'; -export function moveFiles(filePathMap: FilePathMap, options: Options): void { +/** + * Moves files from one directory (source) to another (destination). + * Creates the destination directory if it doesn't exist. Removes + * the source directory if it is empty. + * + * @param filePathMap + * + * A mapping from source to destination. + * + * @param options + * + * An object with `projectRoot`. + * + * @example + * + * Move `LICENSE.md` and `README.md` from the project root to a + * folder named `ember-container-query`. + * + * ```ts + * const filePathMap = new Map([ + * ['LICENSE.md', 'ember-container-query/LICENSE.md'], + * ['README.md', 'ember-container-query/README.md'], + * ]); + * + * moveFiles(filePathMap, { + * projectRoot, + * }); + * ``` + */ +export function moveFiles( + filePathMap: FilePathMap, + options: Options & { + projectRoot: string; + }, +): void { const { projectRoot } = options; filePathMap.forEach((newFilePath, oldFilePath) => { diff --git a/packages/files/src/files/parse-file-path.ts b/packages/files/src/files/parse-file-path.ts index 1e54ad5a..c14a3a3d 100644 --- a/packages/files/src/files/parse-file-path.ts +++ b/packages/files/src/files/parse-file-path.ts @@ -2,6 +2,31 @@ import { parse } from 'node:path'; import type { FilePath, ParsedPath } from '../types/index.js'; +/** + * Parses a file path, similarly to `parse()` from `node:path`, + * but correctly handles file extensions with more than one `.`, + * e.g. `.d.ts` and `.css.d.ts`. + * + * @param filePath + * + * A file path. + * + * @return + * + * An object with `base`, `dir`, `ext`, and `name`. + * + * @example + * + * ```ts + * const filePath = 'src/components/navigation-menu.d.ts'; + * const { base, dir, ext, name } = parseFilePath(filePath); + * + * // base -> 'navigation-menu.d.ts' + * // dir -> 'src/components' + * // ext -> '.d.ts' + * // name -> 'navigation-menu' + * ``` + */ export function parseFilePath(filePath: FilePath): ParsedPath { // eslint-disable-next-line prefer-const let { base, dir, ext, name } = parse(filePath); diff --git a/packages/files/src/files/remove-directory-if-empty.ts b/packages/files/src/files/remove-directory-if-empty.ts index 81b5a630..4f7ebebd 100644 --- a/packages/files/src/files/remove-directory-if-empty.ts +++ b/packages/files/src/files/remove-directory-if-empty.ts @@ -3,9 +3,37 @@ import { dirname, join } from 'node:path'; import type { FilePath, Options } from '../types/index.js'; +/** + * Removes the directories specified in the file path, if they are + * empty. + * + * ⚠️ Likely, you won't need this method but `removeFiles()` instead. + * + * @param filePath + * + * A file path. + * + * @param options + * + * An object with `projectRoot`. + * + * @example + * + * Remove the folder `ember-container-query` if it is empty. + * + * ```ts + * const filePath = 'ember-container-query/LICENSE.md'; + * + * removeDirectoryIfEmpty(filePath, { + * projectRoot, + * }); + * ``` + */ export function removeDirectoryIfEmpty( filePath: FilePath, - options: Options, + options: Options & { + projectRoot: string; + }, ): void { const { projectRoot } = options; diff --git a/packages/files/src/files/remove-files.ts b/packages/files/src/files/remove-files.ts index 27d52b7b..a3aec8c4 100644 --- a/packages/files/src/files/remove-files.ts +++ b/packages/files/src/files/remove-files.ts @@ -4,7 +4,35 @@ import { join } from 'node:path'; import type { FilePath, Options } from '../types/index.js'; import { removeDirectoryIfEmpty } from './remove-directory-if-empty.js'; -export function removeFiles(filePaths: FilePath[], options: Options): void { +/** + * Removes files. Removes the source directory if it is empty. + * + * @param filePaths + * + * An array of file paths. + * + * @param options + * + * An object with `projectRoot`. + * + * @example + * + * Remove `LICENSE.md` and `README.md` from the project root. + * + * ```ts + * const filePaths = ['LICENSE.md', 'README.md']; + * + * removeFiles(filePaths, { + * projectRoot, + * }); + * ``` + */ +export function removeFiles( + filePaths: FilePath[], + options: Options & { + projectRoot: string; + }, +): void { const { projectRoot } = options; filePaths.forEach((filePath) => { diff --git a/packages/files/src/files/rename-path-by-directory.ts b/packages/files/src/files/rename-path-by-directory.ts index eac48cde..285ba260 100644 --- a/packages/files/src/files/rename-path-by-directory.ts +++ b/packages/files/src/files/rename-path-by-directory.ts @@ -2,14 +2,42 @@ import { join } from 'node:path'; import type { FilePath } from '../types/index.js'; -type Options = { - from: string; - to: string; -}; - +/** + * Forms a new file path by altering the path's directory. + * + * @param filePath + * + * A file path. + * + * @param options + * + * An object with `from` and `to`. + * + * @return + * + * A file path. + * + * @example + * + * Prepare to move components from `addon` to `ember-container-query/src`. + * + * ```ts + * const oldFilePath = 'addon/components/container-query.hbs'; + * + * const newFilePath = renamePathByDirectory(oldFilePath, { + * from: 'addon', + * to: 'ember-container-query/src', + * }); + * + * // newFilePath -> 'ember-container-query/src/components/container-query.hbs' + * ``` + */ export function renamePathByDirectory( filePath: FilePath, - options: Options, + options: { + from: string; + to: string; + }, ): FilePath { const { from, to } = options; diff --git a/packages/files/src/files/rename-path-by-file.ts b/packages/files/src/files/rename-path-by-file.ts index de2e27b3..42277821 100644 --- a/packages/files/src/files/rename-path-by-file.ts +++ b/packages/files/src/files/rename-path-by-file.ts @@ -1,17 +1,50 @@ import type { FilePath } from '../types/index.js'; import { parseFilePath } from './parse-file-path.js'; -type Options = { - find: { - directory: string; - file: string; - }; - replace: (key: string) => string; -}; - +/** + * Forms a new file path by altering the path's file name. + * + * @param filePath + * + * A file path. + * + * @param options + * + * An object with `find` and `replace`. + * + * @return + * + * A file path. + * + * @example + * + * Prepare to un-pod components. + * + * ```ts + * const oldFilePath = 'app/components/navigation-menu/template.hbs'; + * + * const newFilePath = renamePathByFile(oldFilePath, { + * find: { + * directory: 'app/components', + * file: 'template', + * }, + * replace: (key: string) => { + * return `app/components/${key}`; + * }, + * }); + * + * // newFilePath -> 'app/components/navigation-menu.hbs' + * ``` + */ export function renamePathByFile( filePath: FilePath, - options: Options, + options: { + find: { + directory: string; + file: string; + }; + replace: (key: string) => string; + }, ): FilePath { const { dir, ext, name } = parseFilePath(filePath); const { find, replace } = options; diff --git a/packages/files/src/files/unionize.ts b/packages/files/src/files/unionize.ts new file mode 100644 index 00000000..3a1a5a94 --- /dev/null +++ b/packages/files/src/files/unionize.ts @@ -0,0 +1,40 @@ +/** + * Returns the glob pattern that can search multiple files + * ("file A or file B or ..."). The glob pattern is to be + * passed to `findFiles()`. + * + * @deprecated + * + * Pass an array of glob patterns to `findFiles()` instead. + * + * @param files + * + * An array of file paths. + * + * @return + * + * A glob pattern that can be passed to `findFiles()`. + * + * @example + * + * Look for multiple files: + * + * ```ts + * const pattern = unionize([ + * 'package-lock.json', + * 'pnpm-lock.yaml', + * 'yarn.lock', + * ]); + * + * const filePaths = findFiles(pattern, { + * projectRoot, + * }); + * ``` + */ +export function unionize(files: string[]): string { + if (files.length <= 1) { + return files.join(','); + } + + return `{${files.join(',')}}`; +} diff --git a/packages/files/src/index.ts b/packages/files/src/index.ts index f38108d1..95d5b92c 100644 --- a/packages/files/src/index.ts +++ b/packages/files/src/index.ts @@ -9,4 +9,5 @@ export * from './files/remove-directory-if-empty.js'; export * from './files/remove-files.js'; export * from './files/rename-path-by-directory.js'; export * from './files/rename-path-by-file.js'; +export * from './files/unionize.js'; export * from './types/index.js'; diff --git a/packages/files/src/types/index.ts b/packages/files/src/types/index.ts index a223f746..43d9585a 100644 --- a/packages/files/src/types/index.ts +++ b/packages/files/src/types/index.ts @@ -6,10 +6,7 @@ type FilePath = string; type FilePathMap = Map; -type Options = { - [key: string]: unknown; - projectRoot: string; -}; +type Options = Record; type ParsedPath = { base: string; diff --git a/packages/files/tests/files/find-files/edge-case-projectRoot-does-not-exist.test.ts b/packages/files/tests/files/find-files/edge-case-projectRoot-does-not-exist.test.ts index 29aa165c..5b84b56a 100644 --- a/packages/files/tests/files/find-files/edge-case-projectRoot-does-not-exist.test.ts +++ b/packages/files/tests/files/find-files/edge-case-projectRoot-does-not-exist.test.ts @@ -35,7 +35,7 @@ test('files | find-files > edge case (projectRoot does not exist)', function () loadFixture(inputProject, codemodOptions); const filePaths = findFiles('tests/dummy/**/*.{js,ts}', { - projectRoot: 'foo', + projectRoot: 'path/to/somewhere/else', }); assert.deepStrictEqual(filePaths, []); diff --git a/packages/files/tests/files/find-files/multiple-patterns.test.ts b/packages/files/tests/files/find-files/multiple-patterns.test.ts new file mode 100644 index 00000000..ea4fbb27 --- /dev/null +++ b/packages/files/tests/files/find-files/multiple-patterns.test.ts @@ -0,0 +1,48 @@ +import { assert, loadFixture, test } from '@codemod-utils/tests'; + +import { findFiles } from '../../../src/index.js'; +import { codemodOptions, options } from '../../shared-test-setups/index.js'; + +test('files | find-files > multiple patterns', function () { + const inputProject = { + tests: { + dummy: { + app: { + '.eslintrc.js': '', + 'app.ts': '', + 'index.html': '', + 'router.ts': '', + }, + config: { + 'environment.js': '', + 'optional-features.json': '', + 'targets.js': '', + }, + }, + integration: { + components: { + 'container-query-test.ts': '', + }, + }, + 'index.html': '', + 'test-helper.ts': '', + }, + 'ember-cli-build.js': '', + 'index.js': '', + 'package.json': '', + }; + + loadFixture(inputProject, codemodOptions); + + const filePaths = findFiles(['**/*.json', '**/index.*'], { + projectRoot: options.projectRoot, + }); + + assert.deepStrictEqual(filePaths, [ + 'index.js', + 'package.json', + 'tests/dummy/app/index.html', + 'tests/dummy/config/optional-features.json', + 'tests/index.html', + ]); +}); diff --git a/packages/files/tests/files/map-file-paths/base-case.test.ts b/packages/files/tests/files/map-file-paths/base-case.test.ts index f801dbaf..dd0e8b78 100644 --- a/packages/files/tests/files/map-file-paths/base-case.test.ts +++ b/packages/files/tests/files/map-file-paths/base-case.test.ts @@ -3,21 +3,21 @@ import { assert, test } from '@codemod-utils/tests'; import { mapFilePaths } from '../../../src/index.js'; test('files | map-file-paths > base case', function () { - const filePaths = ['addon/some-folder/some-file.ts', 'addon/.gitkeep']; + const filePaths = ['addon/components/container-query.hbs', 'addon/.gitkeep']; const filePathMap = mapFilePaths(filePaths, { from: 'addon', - to: 'new-location/src', + to: 'ember-container-query/src', }); assert.deepStrictEqual( filePathMap, new Map([ [ - 'addon/some-folder/some-file.ts', - 'new-location/src/some-folder/some-file.ts', + 'addon/components/container-query.hbs', + 'ember-container-query/src/components/container-query.hbs', ], - ['addon/.gitkeep', 'new-location/src/.gitkeep'], + ['addon/.gitkeep', 'ember-container-query/src/.gitkeep'], ]), ); }); diff --git a/packages/files/tests/files/map-file-paths/edge-case-no-match.test.ts b/packages/files/tests/files/map-file-paths/edge-case-no-match.test.ts index 74460dc4..686068f2 100644 --- a/packages/files/tests/files/map-file-paths/edge-case-no-match.test.ts +++ b/packages/files/tests/files/map-file-paths/edge-case-no-match.test.ts @@ -7,7 +7,7 @@ test('files | map-file-paths > edge case (no match)', function () { const filePathMap = mapFilePaths(filePaths, { from: 'addon', - to: 'new-location/src', + to: 'ember-container-query/src', }); assert.deepStrictEqual( diff --git a/packages/files/tests/files/rename-path-by-directory/base-case.test.ts b/packages/files/tests/files/rename-path-by-directory/base-case.test.ts index b79f727e..db2162dd 100644 --- a/packages/files/tests/files/rename-path-by-directory/base-case.test.ts +++ b/packages/files/tests/files/rename-path-by-directory/base-case.test.ts @@ -3,12 +3,15 @@ import { assert, test } from '@codemod-utils/tests'; import { renamePathByDirectory } from '../../../src/index.js'; test('files | rename-path-by-directory > base case', function () { - const oldFilePath = 'addon/some-folder/some-file.ts'; + const oldFilePath = 'addon/components/container-query.hbs'; const newFilePath = renamePathByDirectory(oldFilePath, { from: 'addon', - to: 'new-location/src', + to: 'ember-container-query/src', }); - assert.strictEqual(newFilePath, 'new-location/src/some-folder/some-file.ts'); + assert.strictEqual( + newFilePath, + 'ember-container-query/src/components/container-query.hbs', + ); }); diff --git a/packages/files/tests/files/rename-path-by-directory/edge-case-from-is-empty.test.ts b/packages/files/tests/files/rename-path-by-directory/edge-case-from-is-empty.test.ts index 63d52110..e18a21f4 100644 --- a/packages/files/tests/files/rename-path-by-directory/edge-case-from-is-empty.test.ts +++ b/packages/files/tests/files/rename-path-by-directory/edge-case-from-is-empty.test.ts @@ -3,15 +3,15 @@ import { assert, test } from '@codemod-utils/tests'; import { renamePathByDirectory } from '../../../src/index.js'; test('files | rename-path-by-directory > edge case (from is empty)', function () { - const oldFilePath = 'addon/some-folder/some-file.ts'; + const oldFilePath = 'addon/components/container-query.hbs'; const newFilePath = renamePathByDirectory(oldFilePath, { from: '', - to: 'new-location/src', + to: 'ember-container-query/src', }); assert.strictEqual( newFilePath, - 'new-location/src/addon/some-folder/some-file.ts', + 'ember-container-query/src/addon/components/container-query.hbs', ); }); diff --git a/packages/files/tests/files/rename-path-by-directory/edge-case-no-match.test.ts b/packages/files/tests/files/rename-path-by-directory/edge-case-no-match.test.ts index 4ec06172..dc380c83 100644 --- a/packages/files/tests/files/rename-path-by-directory/edge-case-no-match.test.ts +++ b/packages/files/tests/files/rename-path-by-directory/edge-case-no-match.test.ts @@ -7,7 +7,7 @@ test('files | rename-path-by-directory > edge case (no match)', function () { const newFilePath = renamePathByDirectory(oldFilePath, { from: 'addon', - to: 'new-location/src', + to: 'ember-container-query/src', }); assert.strictEqual(newFilePath, 'addon'); diff --git a/packages/files/tests/files/rename-path-by-directory/edge-case-to-is-empty.test.ts b/packages/files/tests/files/rename-path-by-directory/edge-case-to-is-empty.test.ts index cb5be2ee..fd3e5f9e 100644 --- a/packages/files/tests/files/rename-path-by-directory/edge-case-to-is-empty.test.ts +++ b/packages/files/tests/files/rename-path-by-directory/edge-case-to-is-empty.test.ts @@ -3,12 +3,12 @@ import { assert, test } from '@codemod-utils/tests'; import { renamePathByDirectory } from '../../../src/index.js'; test('files | rename-path-by-directory > edge case (to is empty)', function () { - const oldFilePath = 'addon/some-folder/some-file.ts'; + const oldFilePath = 'addon/components/container-query.hbs'; const newFilePath = renamePathByDirectory(oldFilePath, { from: 'addon', to: '', }); - assert.strictEqual(newFilePath, 'some-folder/some-file.ts'); + assert.strictEqual(newFilePath, 'components/container-query.hbs'); }); diff --git a/packages/files/tests/files/unionize/base-case.test.ts b/packages/files/tests/files/unionize/base-case.test.ts new file mode 100644 index 00000000..2fa7a8f7 --- /dev/null +++ b/packages/files/tests/files/unionize/base-case.test.ts @@ -0,0 +1,13 @@ +import { assert, test } from '@codemod-utils/tests'; + +import { unionize } from '../../../src/index.js'; + +test('files | unionize > base case', function () { + const pattern = unionize([ + 'package-lock.json', + 'pnpm-lock.yaml', + 'yarn.lock', + ]); + + assert.strictEqual(pattern, '{package-lock.json,pnpm-lock.yaml,yarn.lock}'); +}); diff --git a/packages/files/tests/files/unionize/edge-case-no-file-paths.test.ts b/packages/files/tests/files/unionize/edge-case-no-file-paths.test.ts new file mode 100644 index 00000000..8d2c0354 --- /dev/null +++ b/packages/files/tests/files/unionize/edge-case-no-file-paths.test.ts @@ -0,0 +1,9 @@ +import { assert, test } from '@codemod-utils/tests'; + +import { unionize } from '../../../src/index.js'; + +test('files | unionize > edge case (no file paths)', function () { + const pattern = unionize([]); + + assert.strictEqual(pattern, ''); +}); diff --git a/packages/files/tests/files/unionize/edge-case-one-file-path.test.ts b/packages/files/tests/files/unionize/edge-case-one-file-path.test.ts new file mode 100644 index 00000000..86a66b56 --- /dev/null +++ b/packages/files/tests/files/unionize/edge-case-one-file-path.test.ts @@ -0,0 +1,9 @@ +import { assert, test } from '@codemod-utils/tests'; + +import { unionize } from '../../../src/index.js'; + +test('files | unionize > edge case (1 file path)', function () { + const pattern = unionize(['README.md']); + + assert.strictEqual(pattern, 'README.md'); +});