diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 0000000..190a4cd --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,27 @@ +name: Pull request checks +on: + pull_request: + types: + - opened + - reopened + branches: + - main + +jobs: + pull_request: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install + uses: pnpm/action-setup@v4 + with: + version: latest + run_install: true + + - name: TypeScript Compiler + run: tsc --noEmit + + - name: Lint + run: pnpx eslint . diff --git a/LICENSE b/LICENSE index 465a665..31895db 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 Simon +Copyright (c) 2024 Simon Kovtyk Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index d23f889..90227f6 100644 --- a/README.md +++ b/README.md @@ -1,147 +1,166 @@ -# esbuild plugin for copying files +
-![NPM Downloads](https://img.shields.io/npm/dw/esbuild-plugin-file-copy) ![NPM License](https://img.shields.io/npm/l/esbuild-plugin-file-copy) + -The plugin copies files to the esbuild out folder, ensuring that all required files are included alongside the built code. +

File Copy Plugin

-* Supports newest esbuild version -* Advanced with globs -* Uses raw filesystem calls -* Uses esbuild config to determine the out folder -* Type declarations (d.ts) included +

This esbuild plugin copies specified files to the output folder after the bundling process. It ensures static assets are included in the final build without interrupting or altering the main esbuild workflow.

-## How It Works +![NPM Downloads](https://img.shields.io/npm/dw/esbuild-plugin-file-copy) +![NPM License](https://img.shields.io/npm/l/esbuild-plugin-file-copy) +![GitHub package.json version](https://img.shields.io/npm/v/esbuild-plugin-file-copy) +![TypeScript types](https://img.shields.io/badge/TypeScript_types-included-blue) -1. Parses the file paths by globs -2. Creates folder for the file paths, if there are not exists. -3. Determines the out-folder by using the existing esbuild configuration. -4. Writes the files to their responsible out paths. +
-This plugin prefers an ``outdir`` over an ``outfile``, but if only an ``outfile`` is provided, the plugin will choose the directory of the ``outfile`` as base output directory for the files -instead.\ -The ``outbase`` is used as a prefix for the ``outdir`` or ``outfile`` and it can be left as empty, if it is not needed. +Add a ⭐ to this repository β€” *it motivates me a lot!* -## Options +
-### Input files to copy them +## ⚑️ Getting started -Files can be provided as glob.\ -[See here for more about the syntax of globs.](https://github.com/mrmlnc/fast-glob?tab=readme-ov-file#pattern-syntax) +Simply install this package with your package manager. -#### Simple including - -It is possible to provide globs only as ``string``: -````typescript -fileCopyPlugin({ - inputs: [ - "src/**/README.md" - ] -}) +````shell +npm install -D esbuild-plugin-file-copy ```` -In this case, the output directory of the matching files is the output directory of the esbuild configuration, because no output directory else was specified. +
+πŸ“¦ other package manager -If you want an option to handle the output directory of the files by yourself, you should follow the advanced including. +Here are examples for installing the package with other package manager. -#### Advanced including +> πŸ’Ύ **yarn** +> ````shell +> yarn add -D esbuild-plugin-file-copy +> ```` -It is also possible to provide globs as ``Object``. +> πŸ’Ύ **pnpm** +> ````shell +> pnpm install -D esbuild-plugin-file-copy +> ```` -````typescript -fileCopyPlugin({ - inputs: [ - { - glob: "src/**/README.md", - output: "somefolder/anyfolder" - } - ] -}) -```` +
+ +Looks good so far πŸ”₯ β€” now you have installed the latest version! -The ``Object`` expects a key "glob" and an optional key "output". +## πŸ’‘ Introduction +This esbuild plugin simply copies specified files to the output folder during the build process. It reads a list of file paths or patterns, then moves those files into the output directory after +esbuild finishes bundling. -If the key "output" is set, the output directory of the files will be the given output directory **without** the output directory of the esbuild configuration as base output directory. +The plugin ensures that static assets, such as images or configuration files, are included in the final build. It’s lightweight and works without altering the bundling +process itself. By automating file copying, it reduces manual steps and keeps the build process smooth and consistent. -If the key "output" is not set, the output directory will be the base output directory of the esbuild configuration. +## πŸ”§ Usage -### Base output directory +```typescript +fileCopyPlugin(options); +``` -This plugin will use the esbuild configuration to determine the base output directory for the files.\ -Sometimes it can be helpful to overwrite the base output directory. +This function needs to be called inside the esbuild configuration in order to use this plugin. It will provide the plugin inside the build process of esbuild. + +
+Show an example of the integration ````typescript -fileCopyPlugin( - overrideOutBase?: string | undefined, - overrideOutDir?: string | undefined, - overrideOutFile?: string | undefined -); +esbuild.build({ + // some esbuild configuration... + plugins: [ + fileCopyPlugin( + // configure it here... + ); + // more esbuild plugins here... + ] +}) ```` -Each overwrite will overwrite the specific esbuild configuration. +
+ +### Properties -[See here for more details about the out configuration of esbuild.](https://esbuild.github.io/api/#outbase) +#### ``inputs`` -### Lifecycle +> Default: ``undefined`` -You can configure at which lifecycle of esbuild the plugin will be called. +A ``Array`` of ``object`` with the following properties: ````typescript -fileCopyPlugin( - lifecycle: "onStart" | "onEnd" | "onDispose" | undefined -); +{ + from: string, + to: string +} ```` -[See here for more about the esbuild lifecycles.](https://esbuild.github.io/plugins/#concepts) +Any file or directory from the source path (``from`` key) will be copied to the target path (``to`` key). -## Usage +The file name will be kept while copying the file from source path to target path. -### Installation +
+Show an example -The plugin can be installed by any package manager. +````typescript +fileCopyPlugin({ + inputs: [{ + from: "my-lib/example.ts", // input path + to: "dist/my-lib" // copied to path + }] +}); +```` -
Show instructions +
-> npm \ -> ``npm install esbuild-plugin-file-copy`` +#### ``globs`` -> yarn \ -> ``yarn install esbuild-plugin-file-copy`` +> Default: ``undefined`` -> pnpm \ -> ``pnpm install esbuild-plugin-file-copy`` +A ``Array`` of ``object`` with the following properties: -
+````typescript +{ + from: string, + to: string +} +```` -### Integration +Any matching file or directory from the source path (``from`` key) will be copied to the target path (``to`` key). -The easy way to integrate this plugin in esbuild. +The file name will be kept while copying the file from source path to target path. -
Show instructions +This option enables the use of glob patterns. [See here](https://www.malikbrowne.com/blog/a-beginners-guide-glob-patterns/) for more about glob patterns. + +
+Show an example ````typescript -await esbuild.build({ - [...] - plugins: [ - fileCopyPlugin(...) - ] -}) +fileCopyPlugin({ + globs: [{ + from: "my-lib/**/*.env", // input path + to: "dist/my-lib" // copied to path + }] +}); ```` -[See here for more about the esbuild plugin integration.](https://esbuild.github.io/plugins/#using-plugins) -
+### Returns + +Type: ``Plugin`` + +An instance of this plugin, that will be used by esbuild automatically. + ## License -The MIT License (MIT) - Please have a look at the LICENSE file for more details. +The MIT License (MIT) - Please have a look at the [License](https://github.com/simonkovtyk/esbuild-plugin-file-copy/blob/main/LICENSE) file for more details. ## Contributing -Feel free to contribute to this project.\ -You can fork this project and create a new pull request for contributing. +Want to contribute to an open-source project on GitHub but unsure where to start? Check out this comprehensive step-by-step guide on how to contribute effectively! + +From forking the repository to creating pull requests, this guide walks you through every stage of the process, helping you make a successful contribution to this GitHub project. Start collaborating, +learn new skills, and make an impact on this project! -[Get to the repository at GitHub.](https://github.com/simonkovtyk/esbuild-plugin-file-copy) +[See here](https://github.com/simonkovtyk/esbuild-plugin-file-copy/blob/main/docs/guides/HOW_TO_CONTRIBUTE.md) for the contribute guide at GitHub.
diff --git a/docs/esbuild-favicon.svg b/docs/esbuild-favicon.svg new file mode 100644 index 0000000..234900f --- /dev/null +++ b/docs/esbuild-favicon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/docs/guides/HOW_TO_CONTRIBUTE.md b/docs/guides/HOW_TO_CONTRIBUTE.md new file mode 100644 index 0000000..a602423 --- /dev/null +++ b/docs/guides/HOW_TO_CONTRIBUTE.md @@ -0,0 +1,150 @@ +# βš™οΈ How to contribute + +How to Contribute to a GitHub Repository: A Step-by-Step Guide +Contributing to open-source projects on GitHub is a great way to collaborate with others, learn new skills, and improve software. Here's a step-by-step guide on how to contribute to this GitHub +repository. + +## 1. Fork the Repository + +Before making any changes, you'll need to fork the repository to your own GitHub account. + +1. Go to the [repository page](https://github.com/simonkovtyk/esbuild-plugin-file-copy/). +2. Click on the "Fork" button in the top-right corner. +3. GitHub will create a copy of the repository in your account. + +## 2. Clone Your Fork Locally + +Next, you need to clone your forked repository to your local machine. + +Open a terminal on your computer. + +Use the following command to clone the repository: + +````shell +git clone https://github.com/YOUR-USERNAME/esbuild-plugin-file-copy.git +```` + +Replace YOUR-USERNAME with your GitHub username. + +Navigate into the project directory: + +````shell +cd esbuild-plugin-file-copy +```` + +## 3. Set Up the Upstream Remote + +To ensure you can pull in updates from the original repository, add an "upstream" remote. + +In the terminal, run: + +````shell +git remote add upstream https://github.com/simonkovtyk/esbuild-plugin-file-copy.git +```` + +Confirm the upstream remote has been added: + +````shell +git remote -v +```` + +## 4. Create a New Branch + +To keep your changes organized and separate from the main codebase, create a new branch. + +Make sure you're in the main branch: + +````shell +git checkout main +```` + +Create and switch to a new branch: + +````shell +git checkout -b branch-name +```` + +Replace branch-name with a descriptive name for your branch (e.g., fix-bug, add-feature). + +## 5. Make Your Changes + +Now you're ready to make changes to the codebase. Open the project in your favorite code editor, modify the code, and save your changes. + +## 6. Commit Your Changes + +After making your changes, commit them to your branch. + +Check which files have changed: + +````shell +git status +```` + +Stage your changes: + +````shell +git add . +```` + +Commit your changes with a descriptive message: + +````shell +git commit -m "Brief description of the changes" +```` + +## 7. Push Your Branch to GitHub + +Push your changes to your fork on GitHub. + +````shell +git push origin branch-name +```` + +## 8. Open a Pull Request + +Now that your changes are pushed to your fork, it's time to open a pull request (PR) to the original repository. + +1. Go to the [original repository](https://github.com/simonkovtyk/esbuild-plugin-file-copy/) on GitHub. +2. Click the "Compare & pull request" button. +3. Review your changes and ensure they are correct. +4. Add a descriptive title and description for your PR. +5. Click "Create pull request." + +## 9. Respond to Review Feedback + +After opening your pull request, a code style check will run automatically and the maintainers of the repository might review your code and suggest changes. + +If changes are requested, update your branch locally, commit the new changes, and push them to your fork. +The PR will automatically update with your latest changes. + +## 10. Keep Your Fork Up-to-Date + +To ensure your fork remains up-to-date with the original repository, regularly sync it with the upstream repository. + +Fetch the latest updates from upstream: + +````shell +git fetch upstream +```` + +Merge the upstream changes into your local main branch: + +````shell +git checkout main +```` + +````shell +git merge upstream/main +```` + +Push the updated main branch to your fork: + +````shell +git push origin main +```` + +## 11. Celebrate Your Contribution! + +Once your pull request is merged, you've officially contributed to an open-source project! + +**πŸš€ Congratulations!** diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..0458e13 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,25 @@ +import eslint from "@eslint/js"; +import tseslint from "typescript-eslint"; +import stylistic from "@stylistic/eslint-plugin"; + +export default tseslint.config( + { + ignores: [ + ".github", + ".idea", + "dist", + "docs", + "node_modules" + ] + }, + eslint.configs.recommended, + ...tseslint.configs.strict, + ...tseslint.configs.stylistic, + stylistic.configs.customize({ + indent: 2, + quotes: "double", + semi: true, + commaDangle: "never", + jsx: false + }) +); diff --git a/package.json b/package.json index 12bb803..140d064 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "esbuild-plugin-file-copy", - "description": "The plugin copies assets to the esbuild out folder, ensuring that all required files are included alongside the built code.", + "description": "The plugin copies files to the esbuild out folder, ensuring that all required files are included alongside the built code.", "keywords": [ "frontend", "backend", @@ -27,18 +27,16 @@ "files copy" ], "license": "MIT", - "version": "1.0.3", + "version": "2.0.0-next.0", "bugs": "https://github.com/simonkovtyk/esbuild-plugin-file-copy/issues", "homepage": "https://github.com/simonkovtyk/esbuild-plugin-file-copy", "repository": { "type": "git", "url": "git+https://github.com/simonkovtyk/esbuild-plugin-file-copy.git" }, + "types": "./public-api.d.ts", "exports": { - ".": { - "default": "./public-api.js", - "types": "./public-api.d.ts" - } + "default": "./public-api.js" }, "author": { "name": "Simon Kovtyk", @@ -58,6 +56,12 @@ }, "devDependencies": { "typescript": "^5.5.4", - "@types/node": "^22.5.4" + "@types/node": "^22.5.4", + "eslint": "^9.10.0", + "typescript-eslint": "^8.6.0", + "@stylistic/eslint-plugin": "^2.8.0", + "@eslint/js": "^9.10.0", + "@types/eslint__js": "^8.42.3", + "husky": "^9.1.6" } } diff --git a/src/core/helpers/out.helper.ts b/src/core/helpers/out.helper.ts deleted file mode 100644 index 47d2be8..0000000 --- a/src/core/helpers/out.helper.ts +++ /dev/null @@ -1,32 +0,0 @@ -import path from "node:path"; -import process from "node:process"; -import { ResolvePathOptions } from "../types/options.type"; - -// Prefer out dir before out file -const resolveOutDir = (options: ResolvePathOptions): string => { - const explicitOutBase: string | undefined = options.overrideOutBase ?? options.outBase; - - const outBase = explicitOutBase - ? path.join(process.cwd(), explicitOutBase) - : process.cwd(); - - const explicitOutDir: string | undefined = options.overrideOutDir ?? options.outDir; - - if (explicitOutDir !== undefined) { - return path.join(outBase, explicitOutDir); - } - - const explicitOutFile: string | undefined = options.overrideOutFile ?? options.outFile; - - if (explicitOutFile !== undefined) { - const dirOfOutFile = path.parse(explicitOutFile).dir; - - return path.join(outBase, dirOfOutFile); - } - - return path.join(outBase, "dist") -} - -export { - resolveOutDir -} diff --git a/src/core/plugin.ts b/src/core/plugin.ts index ece5294..4e176dc 100644 --- a/src/core/plugin.ts +++ b/src/core/plugin.ts @@ -1,82 +1,70 @@ import path from "node:path"; -import { resolveOutDir } from "./helpers/out.helper"; -import { Input, Lifecycle, Options, ResolvePathOptions } from "./types/options.type"; +import { Glob, HandlerOptions, Input, Lifecycle, Options } from "./types/options.type"; import { PluginBuild, Plugin } from "esbuild"; -import fastGlob from "fast-glob" import fs from "node:fs"; +import fastGlob from "fast-glob"; -const handler = (inputs: string[] | Input[], options: ResolvePathOptions) => { - return async () => { - const resolvedOutDir: string = resolveOutDir(options); +const handler = (handlerOptions: HandlerOptions) => { + return async () => { + handlerOptions.inputs?.forEach((input: Input): void => { + const inputFilePath: string = path.join(process.cwd(), input.from); - inputs.forEach((input: string | Input): void => { - if (typeof input === "string") { - const globs: string[] = fastGlob.sync(input); + if (!fs.existsSync(inputFilePath)) + return; - if (globs.length === 0) - return; + const outputPath = path.join(process.cwd(), input.to); + const outputPathDir = path.parse(outputPath).dir; - if (! fs.existsSync(resolvedOutDir)) - fs.mkdirSync(resolvedOutDir, { recursive: true }); + if (!fs.existsSync(outputPathDir)) + fs.mkdirSync(outputPathDir, { recursive: true }); - globs.forEach((glob: string): void => { - const fileName: string = path.parse(glob).base; + fs.copyFileSync(inputFilePath, outputPath); + }); - fs.copyFileSync(glob, `${ resolvedOutDir }/${ fileName }`); - }); + handlerOptions.globs?.forEach((glob: Glob): void => { + const inputFilePaths: string[] = fastGlob.sync(glob.from); - return; - } + inputFilePaths.forEach(((inputFilePath: string): void => { + if (!fs.existsSync(inputFilePath)) + return; - const globs: string[] = fastGlob.sync(input.glob); + const inputFileName = path.parse(inputFilePath).base; - if (globs.length === 0) - return; + const outputFilePath = path.join(process.cwd(), glob.to, inputFileName); + const outputDirPath = path.parse(outputFilePath).dir; - globs.forEach((glob: string): void => { - const fileName: string = path.parse(glob).base; + if (!fs.existsSync(outputDirPath)) + fs.mkdirSync(outputDirPath, { recursive: true }); - const outDir: string = input.output ?? resolvedOutDir; + fs.copyFileSync(inputFilePath, outputFilePath); + })); + }); + }; +}; - if (! fs.existsSync(outDir)) - fs.mkdirSync(outDir, { recursive: true }); +const fileCopyPlugin = (options?: Options | undefined): Plugin => ({ + name: "esbuild-plugin-file-copy", + setup: (build: PluginBuild) => { + const lifecycle: Lifecycle = options?.lifecycle ?? "onEnd"; - fs.copyFileSync(glob, `${ outDir }/${ fileName }`); - }) - }); - } -} + const handlerOptions: HandlerOptions = { + inputs: options?.inputs, + globs: options?.globs + }; -const fileCopyPlugin = (options: Options): Plugin => ({ - name: "esbuild-plugin-file-copy", - setup: (build: PluginBuild) => { - const lifecycle: Lifecycle = options.lifecycle ?? "onEnd"; + const handlerRef = handler(handlerOptions); - const resolvePathOptions: ResolvePathOptions = { - outBase: build.initialOptions.outbase, - outDir: build.initialOptions.outdir, - outFile: build.initialOptions.outfile, - overrideOutBase: options.overrideOutBase, - overrideOutDir: options.overrideOutDir, - overrideOutFile: options.overrideOutFile - } - - const handlerRef = handler(options.inputs, resolvePathOptions); - - switch (lifecycle) { - case "onStart": - build.onStart(handlerRef); - break; - case "onEnd": - build.onEnd(handlerRef); - break; - case "onDispose": - build.onDispose(handlerRef); - break; - } - } -}) + switch (lifecycle) { + case "onStart": + build.onStart(handlerRef); + break; + case "onEnd": + build.onEnd(handlerRef); + break; + } + } +}); export { - fileCopyPlugin -} + fileCopyPlugin +}; diff --git a/src/core/types/options.type.ts b/src/core/types/options.type.ts index 2d6470d..0e63826 100644 --- a/src/core/types/options.type.ts +++ b/src/core/types/options.type.ts @@ -1,30 +1,28 @@ -type Lifecycle = "onStart" | "onEnd" | "onDispose"; +type Lifecycle = "onStart" | "onEnd"; -type Input = { - glob: string, - output?: string | undefined +interface Glob { + from: string; + to: string; } -type PathOverrides = { - overrideOutBase?: string | undefined, - overrideOutDir?: string | undefined, - overrideOutFile?: string | undefined +interface Input { + from: string; + to: string; } -type Options = { - lifecycle?: Lifecycle | undefined, - inputs: string[] | Input[] -} & PathOverrides +interface HandlerOptions { + inputs?: Input[] | undefined; + globs?: Glob[] | undefined; +} -type ResolvePathOptions = { - outDir?: string | undefined, - outFile?: string | undefined, - outBase?: string | undefined -} & PathOverrides +interface Options extends HandlerOptions { + lifecycle?: Lifecycle | undefined; +} export type { - ResolvePathOptions, - Lifecycle, - Options, - Input -} + HandlerOptions, + Lifecycle, + Options, + Glob, + Input +};