├── .github └── workflows │ └── build.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── npm-release ├── dprint.json ├── npm-release ├── package.json └── pnpm-lock.yaml ├── npm ├── esbuild-config-darwin-64 │ ├── README.md │ └── package.json ├── esbuild-config-linux-64 │ ├── README.md │ └── package.json ├── esbuild-config-linux-arm64 │ ├── README.md │ └── package.json ├── esbuild-config-windows-64 │ ├── README.md │ ├── bin │ │ └── esbuild-config │ └── package.json └── esbuild-config │ ├── README.md │ ├── bin │ └── esbuild-config │ ├── install.js │ └── package.json └── src ├── lib ├── args.rs ├── errors.rs ├── mod.rs └── paths.rs └── main.rs /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # Greatly inspired from 2 | # https://github.com/paskausks/rust-bin-github-workflows/blob/894a4f2debade42f8d7b5b95f493eaa33fdeb81b/.github/workflows/release.yml 3 | 4 | name: Create release 5 | 6 | on: 7 | push: 8 | tags: 9 | - 'v*' 10 | 11 | env: 12 | RELEASE_BIN: esbuild-config 13 | RELEASE_ADDS: README.md LICENSE 14 | CARGO_TERM_COLOR: always 15 | 16 | jobs: 17 | build: 18 | runs-on: ${{ matrix.os }} 19 | strategy: 20 | matrix: 21 | build: [linux, macos, windows] 22 | include: 23 | - build: linux 24 | os: ubuntu-latest 25 | rust: stable 26 | - build: macos 27 | os: macos-latest 28 | rust: stable 29 | - build: windows 30 | os: windows-latest 31 | rust: stable 32 | 33 | steps: 34 | - uses: actions/checkout@v2 35 | 36 | - name: Install Rust via rustup 37 | run: | 38 | rustup update ${{ matrix.rust }} --no-self-update 39 | rustup default ${{ matrix.rust }} 40 | 41 | - name: Install build dependencies (macOS) 42 | run: | 43 | rustup target add x86_64-apple-darwin 44 | rustup target add aarch64-apple-darwin 45 | if: matrix.os == 'macos-latest' 46 | 47 | - name: Build (macOS x86_64) 48 | run: SDKROOT=$(xcrun -sdk macosx --show-sdk-path) cargo build --target=x86_64-apple-darwin --verbose --release 49 | if: matrix.os == 'macos-latest' 50 | 51 | - name: Build (macOS arm64) 52 | run: SDKROOT=$(xcrun -sdk macosx --show-sdk-path) cargo build --target=aarch64-apple-darwin --verbose --release 53 | if: matrix.os == 'macos-latest' 54 | 55 | - name: Universal binary (macOS) 56 | run: | 57 | mkdir -p ./target/release 58 | lipo -create ./target/x86_64-apple-darwin/release/${{ env.RELEASE_BIN }} ./target/aarch64-apple-darwin/release/${{ env.RELEASE_BIN }} -output ./target/release/${{ env.RELEASE_BIN }} 59 | if: matrix.os == 'macos-latest' 60 | 61 | - name: Install build dependencies (Linux) 62 | run: | 63 | rustup target add x86_64-unknown-linux-gnu 64 | rustup target add aarch64-unknown-linux-gnu 65 | sudo apt-get install gcc-aarch64-linux-gnu 66 | if: matrix.os == 'ubuntu-latest' 67 | 68 | - name: Build (Linux x86_64) 69 | run: | 70 | cargo build --verbose --release --target=x86_64-unknown-linux-gnu 71 | mkdir -p ./target/release 72 | mv ./target/x86_64-unknown-linux-gnu/release/${{ env.RELEASE_BIN }} ./target/release/${{ env.RELEASE_BIN }} 73 | if: matrix.os == 'ubuntu-latest' 74 | 75 | - name: Build (Linux arm64) 76 | run: | 77 | CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++ cargo build --verbose --release --target=aarch64-unknown-linux-gnu 78 | mkdir -p ./target/release 79 | mv ./target/aarch64-unknown-linux-gnu/release/${{ env.RELEASE_BIN }} ./target/release/${{ env.RELEASE_BIN }}-arm64 80 | if: matrix.os == 'ubuntu-latest' 81 | 82 | - name: build (Windows) 83 | run: cargo build --verbose --release 84 | if: matrix.os == 'windows-latest' 85 | 86 | - name: Create artifact directory 87 | run: mkdir artifacts 88 | 89 | - name: Create archive (macOS) 90 | run: | 91 | # 7Zip not available on MacOS, install p7zip via homebrew. 92 | brew install p7zip 93 | 7z a -ttar -so -an ./target/release/${{ env.RELEASE_BIN }} ${{ env.RELEASE_ADDS }} | 7z a -si ./artifacts/${{ env.RELEASE_BIN }}-macos-64.tar.gz 94 | if: matrix.os == 'macos-latest' 95 | 96 | - name: Create archive (Linux) 97 | run: 7z a -ttar -so -an ./target/release/${{ env.RELEASE_BIN }} ./target/release/${{ env.RELEASE_BIN }}-arm64 ${{ env.RELEASE_ADDS }} | 7z a -si ./artifacts/${{ env.RELEASE_BIN }}-linux-64.tar.gz 98 | if: matrix.os == 'ubuntu-latest' 99 | 100 | - name: Create archive (Windows) 101 | run: | 102 | 7z a ./tmp/${{ env.RELEASE_BIN }}-windows-x86_64.tar ./target/release/${{ env.RELEASE_BIN }}.exe ${{ env.RELEASE_ADDS }} 103 | 7z a ./artifacts/${{ env.RELEASE_BIN }}-windows-x86_64.tar.gz ./tmp/${{ env.RELEASE_BIN }}-windows-x86_64.tar 104 | if: matrix.os == 'windows-latest' 105 | 106 | # This will double-zip 107 | # See - https://github.com/actions/upload-artifact/issues/39 108 | - uses: actions/upload-artifact@v1 109 | name: Upload archive 110 | with: 111 | name: ${{ runner.os }} 112 | path: artifacts/ 113 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | node_modules/ 4 | yarn.lock 5 | 6 | /npm/esbuild-config-linux-arm64/bin/esbuild-config 7 | /npm/esbuild-config-linux-64/bin/esbuild-config 8 | /npm/esbuild-config-darwin-64/bin/esbuild-config 9 | /npm/esbuild-config-windows-64/esbuild-config.exe 10 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "esbuild-config" 7 | version = "0.2.0" 8 | dependencies = [ 9 | "json", 10 | "snailquote", 11 | ] 12 | 13 | [[package]] 14 | name = "json" 15 | version = "0.12.4" 16 | source = "registry+https://github.com/rust-lang/crates.io-index" 17 | checksum = "078e285eafdfb6c4b434e0d31e8cfcb5115b651496faca5749b88fafd4f23bfd" 18 | 19 | [[package]] 20 | name = "proc-macro2" 21 | version = "1.0.19" 22 | source = "registry+https://github.com/rust-lang/crates.io-index" 23 | checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12" 24 | dependencies = [ 25 | "unicode-xid", 26 | ] 27 | 28 | [[package]] 29 | name = "quote" 30 | version = "1.0.7" 31 | source = "registry+https://github.com/rust-lang/crates.io-index" 32 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 33 | dependencies = [ 34 | "proc-macro2", 35 | ] 36 | 37 | [[package]] 38 | name = "snailquote" 39 | version = "0.3.0" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "f34b729d802f52194598858ac852c3fb3b33f6e026cd03195072ccb7bf3fc810" 42 | dependencies = [ 43 | "thiserror", 44 | "unicode_categories", 45 | ] 46 | 47 | [[package]] 48 | name = "syn" 49 | version = "1.0.38" 50 | source = "registry+https://github.com/rust-lang/crates.io-index" 51 | checksum = "e69abc24912995b3038597a7a593be5053eb0fb44f3cc5beec0deb421790c1f4" 52 | dependencies = [ 53 | "proc-macro2", 54 | "quote", 55 | "unicode-xid", 56 | ] 57 | 58 | [[package]] 59 | name = "thiserror" 60 | version = "1.0.20" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" 63 | dependencies = [ 64 | "thiserror-impl", 65 | ] 66 | 67 | [[package]] 68 | name = "thiserror-impl" 69 | version = "1.0.20" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" 72 | dependencies = [ 73 | "proc-macro2", 74 | "quote", 75 | "syn", 76 | ] 77 | 78 | [[package]] 79 | name = "unicode-xid" 80 | version = "0.2.1" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 83 | 84 | [[package]] 85 | name = "unicode_categories" 86 | version = "0.1.1" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" 89 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "esbuild-config" 3 | version = "1.0.1" 4 | edition = "2018" 5 | description = "A short description of my package" 6 | authors = ["Pierre Bertet "] 7 | repository = "https://github.com/bpierre/esbuild-config" 8 | license = "MIT" 9 | 10 | [dependencies] 11 | json = "0.12.4" 12 | snailquote = "0.3.0" 13 | 14 | [[bin]] 15 | name = "esbuild-config" 16 | path = "src/main.rs" 17 | test = false 18 | 19 | [lib] 20 | name = "esbuild_config_lib" 21 | path = "src/lib/mod.rs" 22 | doctest = false 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Pierre Bertet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # esbuild-config 2 | 3 | Config files for [esbuild](https://github.com/evanw/esbuild). 4 | 5 | ## Why? 6 | 7 | esbuild is an incredible tool, that is [using command line parameters](https://github.com/evanw/esbuild/issues/39) as a configuration syntax. This is fine, but some people might prefer using a configuration file. 8 | 9 | esbuild-config can transform a `esbuild.config.json` configuration file like this one: 10 | 11 | ```json 12 | { 13 | "entry": "./index.js", 14 | "outfile": "./bundle.js", 15 | "external": ["react", "react-dom"], 16 | "loader": { ".js": "jsx", ".png": "base64" }, 17 | "minify": true 18 | } 19 | ``` 20 | 21 | Into a set of parameters for `esbuild`: 22 | 23 | ```console 24 | --outfile=./bundle.js --minify --external:react --external:react-dom --loader:.js=jsx --loader:.png=base64 ./index.js 25 | ``` 26 | 27 | Which means that `esbuild` can read a static configuration by running it this way: 28 | 29 | ```console 30 | esbuild $(esbuild-config) 31 | ``` 32 | 33 | ## Usage 34 | 35 | The esbuild-config command outputs a list of parameters based on a `esbuild.config.json` file, that can get passed to esbuild directly: 36 | 37 | ```console 38 | esbuild $(esbuild-config) 39 | ``` 40 | 41 | It detects the presence of `esbuild.config.json` in the current directory or the project root (using the presence of a `package.json` file). The same configuration format can also get defined in the `package.json` file, using the `esbuild` field.json` file. 42 | 43 | A specific file path can also get passed as a parameter: 44 | 45 | ```console 46 | esbuild $(esbuild-config ./my-conf.json) 47 | ``` 48 | 49 | ## Syntax 50 | 51 | esbuild-config doesn’t do any validation on the configuration values: it only converts JSON types into arguments that are compatible with the format esbuild uses for its arguments. This makes it independent from esbuild versions, assuming the format doesn’t change. 52 | 53 | The only exception to this is the `entry` field, which gets converted into a list of file names (when an array is provided) or a single file name (when a string is provided). 54 | 55 | This is how JSON types get converted: 56 | 57 | ```json 58 | { 59 | "entry": "./index.js", 60 | "outfile": "./bundle.js", 61 | "external": ["react", "react-dom"], 62 | "loader": { ".js": "jsx", ".png": "base64" }, 63 | "minify": true 64 | } 65 | ``` 66 | 67 | Output: 68 | 69 | ```console 70 | --outfile=./bundle.js --minify --external:react --external:react-dom --loader:.js=jsx --loader:.png=base64 ./index.js 71 | ``` 72 | 73 | Notice how the entry, `./index.js`, has been moved to the end. esbuild-config also takes care of escaping the parameters as needed (e.g. by adding quotes). 74 | 75 | ## Install 76 | 77 | ### npm 78 | 79 | The easiest way to install esbuild-config is through npm. 80 | 81 | Install it globally using the following command: 82 | 83 | ```console 84 | npm install --global esbuild-config 85 | ``` 86 | 87 | Or add it to your project: 88 | 89 | ```console 90 | npm install --save-dev esbuild-config 91 | ``` 92 | 93 | See below for [alternative installation methods](#other-installation-methods). 94 | 95 | ### Binaries 96 | 97 | You can download the precompiled binaries [from the release page](https://github.com/bpierre/esbuild-config/releases). 98 | 99 | ### Cargo 100 | 101 | Install it with [Cargo](https://github.com/rust-lang/cargo) using the following command: 102 | 103 | ```console 104 | cargo install esbuild-config 105 | ``` 106 | 107 | ### From source 108 | 109 | To clone the repository and build esbuild-config, run these commands ([after having installed Rust](https://www.rust-lang.org/tools/install)): 110 | 111 | ```console 112 | git clone git@github.com:bpierre/esbuild-config.git 113 | cd esbuild-config 114 | cargo build --release 115 | ``` 116 | 117 | The compiled binary is at `target/release/esbuild-config`. 118 | 119 | ## Contribute 120 | 121 | ```console 122 | # Run the app 123 | cargo run 124 | 125 | # Run the tests 126 | cargo test 127 | 128 | # Generate the code coverage report (install cargo-tarpaulin first) 129 | cargo tarpaulin -o Html 130 | ``` 131 | 132 | ## FAQ 133 | 134 | ### Doesn’t esbuild already support config files? 135 | 136 | The recommended way to use a configuration file with esbuild is [through its Node.js API](https://github.com/evanw/esbuild/blob/1336fbcf9bcca2f2708f5f575770f13a8440bde3/docs/js-api.md), using a Node program as a configuration file: 137 | 138 | ```js 139 | const { build } = require('esbuild') 140 | 141 | build({ 142 | entryPoints: ['./index.js'], 143 | outfile: './bundle.js', 144 | external: ['react', 'react-dom'], 145 | loader: { '.js': 'jsx', '.png': 'base64' }, 146 | minify: true, 147 | }).catch((error) => { 148 | console.error(error) 149 | process.exit(1) 150 | }) 151 | ``` 152 | 153 | If it works for you, you don’t need esbuild-config: the esbuild module already comes bundled with this JS API. esbuild-config provides an alternative way to configure esbuild. Instead of using the esbuild API through Node.js, it converts a [configuration file](#syntax) into command line parameters, that can be passed directly to the esbuild binary. 154 | 155 | There are several reasons why you might want to use esbuild-config: 156 | 157 | - You prefer using JSON as a configuration language. 158 | - You prefer to have as much configuration as possible in the package.json. 159 | - You prefer to not launch Node at all in the process. 160 | 161 | ## Special thanks 162 | 163 | [esbuild](https://github.com/evanw/esbuild) and [its author](https://github.com/evanw) obviously, not only for esbuild itself but also for its approach to [install a platform-specific binary through npm](https://github.com/evanw/esbuild/blob/1336fbcf9bcca2f2708f5f575770f13a8440bde3/lib/install.ts), that esbuild-config is also using. 164 | 165 | ## License 166 | 167 | [MIT](./LICENSE) 168 | -------------------------------------------------------------------------------- /npm-release/dprint.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript": { 3 | "associations": [ 4 | "npm-release", 5 | ] 6 | }, 7 | "json": { 8 | }, 9 | "includes": ["**/*.{ts,tsx,js,jsx,cjs,mjs,json}"], 10 | "excludes": [ 11 | "**/node_modules", 12 | "**/*-lock.json" 13 | ], 14 | "plugins": [ 15 | "https://plugins.dprint.dev/typescript-0.73.1.wasm", 16 | "https://plugins.dprint.dev/json-0.15.6.wasm" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /npm-release/npm-release: -------------------------------------------------------------------------------- 1 | #!./node_modules/zx/build/cli.js 2 | 3 | const npmModulesPath = path.resolve(`${__dirname}/../npm`); 4 | 5 | const platforms = [ 6 | { 7 | zip: "Linux.zip", 8 | tar: "esbuild-config-linux-64.tar.gz", 9 | npm: { 10 | "esbuild-config-linux-64": ["esbuild-config", "bin/esbuild-config"], 11 | "esbuild-config-linux-arm64": ["esbuild-config-arm64", "bin/esbuild-config"], 12 | }, 13 | }, 14 | { 15 | zip: "macOS.zip", 16 | tar: "esbuild-config-macos-64.tar.gz", 17 | npm: { 18 | "esbuild-config-darwin-64": ["esbuild-config", "bin/esbuild-config"], 19 | }, 20 | }, 21 | { 22 | zip: "Windows.zip", 23 | tar: "esbuild-config-windows-x86_64.tar.gz", 24 | npm: { 25 | "esbuild-config-windows-64": ["esbuild-config.exe", "esbuild-config.exe"], 26 | }, 27 | }, 28 | ]; 29 | 30 | const msg = msg => console.log(`\n${msg}\n`); 31 | 32 | const [version] = argv._; 33 | 34 | if (!version) { 35 | msg(`Please specify a version.`); 36 | process.exit(1); 37 | } 38 | 39 | for (const platform of platforms) { 40 | if (!fs.existsSync(`${__dirname}/${platform.zip}`)) { 41 | msg(`${zip} is missing: please drop it in the npm-release directory.`); 42 | process.exit(1); 43 | } 44 | } 45 | 46 | msg("Prepare extraction…"); 47 | await $`rm -rf ${__dirname}/tmp`; 48 | await $`mkdir -p ${__dirname}/tmp`; 49 | await Promise.all(platforms.map(platform => $`cp ${__dirname}/${platform.zip} ${__dirname}/tmp/`)); 50 | cd(`${__dirname}/tmp`); 51 | 52 | msg("Extract zip archives…"); 53 | await Promise.all(platforms.map(platform => $`unzip -q ${platform.zip}`)); 54 | 55 | msg("Extract tar archives…"); 56 | await Promise.all(platforms.map(platform => $`tar xzf ${platform.tar} --one-top-level`)); 57 | 58 | msg("Copy binaries to their respective npm modules…"); 59 | await Promise.all( 60 | platforms.flatMap(platform => 61 | Object.entries(platform.npm).map(([moduleDir, [sourceBinary, destBinary]]) => { 62 | const sourcePath = `${path.basename(platform.tar, ".tar.gz")}/${sourceBinary}`; 63 | const destPath = `${npmModulesPath}/${moduleDir}/${destBinary}`; 64 | return $`cp ${sourcePath} ${destPath}`; 65 | }) 66 | ), 67 | ); 68 | 69 | // Run a command in every npm package (except the main one) 70 | const npmCmd = (command) => 71 | Promise.all( 72 | platforms.flatMap(platform => 73 | Object.keys(platform.npm).map((moduleDir) => { 74 | cd(`${npmModulesPath}/${moduleDir}`); 75 | return command(); 76 | }) 77 | ), 78 | ); 79 | 80 | msg("Update npm versions"); 81 | await npmCmd(() => $`npm version ${version} --allow-same-version`); 82 | 83 | const publishAnswer = await question("\nPublish packages on npm? [Y/n] "); 84 | const publish = ["", "y"].includes(publishAnswer.trim().toLowerCase()); 85 | 86 | if (publish) { 87 | msg("Publish npm packages"); 88 | await npmCmd(() => $`npm publish`); 89 | } 90 | 91 | msg(publish ? "Done. You can now publish npm/esbuild-config manually." : "Done."); 92 | -------------------------------------------------------------------------------- /npm-release/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npm-release", 3 | "private": true, 4 | "dependencies": { 5 | "zx": "^7.0.8" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /npm-release/pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: 5.4 2 | 3 | specifiers: 4 | zx: ^7.0.8 5 | 6 | dependencies: 7 | zx: 7.0.8 8 | 9 | packages: 10 | 11 | /@nodelib/fs.scandir/2.1.5: 12 | resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} 13 | engines: {node: '>= 8'} 14 | dependencies: 15 | '@nodelib/fs.stat': 2.0.5 16 | run-parallel: 1.2.0 17 | dev: false 18 | 19 | /@nodelib/fs.stat/2.0.5: 20 | resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} 21 | engines: {node: '>= 8'} 22 | dev: false 23 | 24 | /@nodelib/fs.walk/1.2.8: 25 | resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 26 | engines: {node: '>= 8'} 27 | dependencies: 28 | '@nodelib/fs.scandir': 2.1.5 29 | fastq: 1.13.0 30 | dev: false 31 | 32 | /@types/fs-extra/9.0.13: 33 | resolution: {integrity: sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==} 34 | dependencies: 35 | '@types/node': 18.7.18 36 | dev: false 37 | 38 | /@types/minimist/1.2.2: 39 | resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} 40 | dev: false 41 | 42 | /@types/node/18.7.18: 43 | resolution: {integrity: sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==} 44 | dev: false 45 | 46 | /@types/ps-tree/1.1.2: 47 | resolution: {integrity: sha512-ZREFYlpUmPQJ0esjxoG1fMvB2HNaD3z+mjqdSosZvd3RalncI9NEur73P8ZJz4YQdL64CmV1w0RuqoRUlhQRBw==} 48 | dev: false 49 | 50 | /@types/which/2.0.1: 51 | resolution: {integrity: sha512-Jjakcv8Roqtio6w1gr0D7y6twbhx6gGgFGF5BLwajPpnOIOxFkakFhCq+LmyyeAz7BX6ULrjBOxdKaCDy+4+dQ==} 52 | dev: false 53 | 54 | /braces/3.0.2: 55 | resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} 56 | engines: {node: '>=8'} 57 | dependencies: 58 | fill-range: 7.0.1 59 | dev: false 60 | 61 | /chalk/5.0.1: 62 | resolution: {integrity: sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==} 63 | engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} 64 | dev: false 65 | 66 | /data-uri-to-buffer/4.0.0: 67 | resolution: {integrity: sha512-Vr3mLBA8qWmcuschSLAOogKgQ/Jwxulv3RNE4FXnYWRGujzrRWQI4m12fQqRkwX06C0KanhLr4hK+GydchZsaA==} 68 | engines: {node: '>= 12'} 69 | dev: false 70 | 71 | /dir-glob/3.0.1: 72 | resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} 73 | engines: {node: '>=8'} 74 | dependencies: 75 | path-type: 4.0.0 76 | dev: false 77 | 78 | /duplexer/0.1.2: 79 | resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} 80 | dev: false 81 | 82 | /event-stream/3.3.4: 83 | resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==} 84 | dependencies: 85 | duplexer: 0.1.2 86 | from: 0.1.7 87 | map-stream: 0.1.0 88 | pause-stream: 0.0.11 89 | split: 0.3.3 90 | stream-combiner: 0.0.4 91 | through: 2.3.8 92 | dev: false 93 | 94 | /fast-glob/3.2.12: 95 | resolution: {integrity: sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==} 96 | engines: {node: '>=8.6.0'} 97 | dependencies: 98 | '@nodelib/fs.stat': 2.0.5 99 | '@nodelib/fs.walk': 1.2.8 100 | glob-parent: 5.1.2 101 | merge2: 1.4.1 102 | micromatch: 4.0.5 103 | dev: false 104 | 105 | /fastq/1.13.0: 106 | resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==} 107 | dependencies: 108 | reusify: 1.0.4 109 | dev: false 110 | 111 | /fetch-blob/3.2.0: 112 | resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} 113 | engines: {node: ^12.20 || >= 14.13} 114 | dependencies: 115 | node-domexception: 1.0.0 116 | web-streams-polyfill: 3.2.1 117 | dev: false 118 | 119 | /fill-range/7.0.1: 120 | resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} 121 | engines: {node: '>=8'} 122 | dependencies: 123 | to-regex-range: 5.0.1 124 | dev: false 125 | 126 | /formdata-polyfill/4.0.10: 127 | resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} 128 | engines: {node: '>=12.20.0'} 129 | dependencies: 130 | fetch-blob: 3.2.0 131 | dev: false 132 | 133 | /from/0.1.7: 134 | resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} 135 | dev: false 136 | 137 | /fs-extra/10.1.0: 138 | resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} 139 | engines: {node: '>=12'} 140 | dependencies: 141 | graceful-fs: 4.2.10 142 | jsonfile: 6.1.0 143 | universalify: 2.0.0 144 | dev: false 145 | 146 | /glob-parent/5.1.2: 147 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 148 | engines: {node: '>= 6'} 149 | dependencies: 150 | is-glob: 4.0.3 151 | dev: false 152 | 153 | /globby/13.1.2: 154 | resolution: {integrity: sha512-LKSDZXToac40u8Q1PQtZihbNdTYSNMuWe+K5l+oa6KgDzSvVrHXlJy40hUP522RjAIoNLJYBJi7ow+rbFpIhHQ==} 155 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 156 | dependencies: 157 | dir-glob: 3.0.1 158 | fast-glob: 3.2.12 159 | ignore: 5.2.0 160 | merge2: 1.4.1 161 | slash: 4.0.0 162 | dev: false 163 | 164 | /graceful-fs/4.2.10: 165 | resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==} 166 | dev: false 167 | 168 | /ignore/5.2.0: 169 | resolution: {integrity: sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==} 170 | engines: {node: '>= 4'} 171 | dev: false 172 | 173 | /is-extglob/2.1.1: 174 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 175 | engines: {node: '>=0.10.0'} 176 | dev: false 177 | 178 | /is-glob/4.0.3: 179 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 180 | engines: {node: '>=0.10.0'} 181 | dependencies: 182 | is-extglob: 2.1.1 183 | dev: false 184 | 185 | /is-number/7.0.0: 186 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 187 | engines: {node: '>=0.12.0'} 188 | dev: false 189 | 190 | /isexe/2.0.0: 191 | resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 192 | dev: false 193 | 194 | /jsonfile/6.1.0: 195 | resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} 196 | dependencies: 197 | universalify: 2.0.0 198 | optionalDependencies: 199 | graceful-fs: 4.2.10 200 | dev: false 201 | 202 | /map-stream/0.1.0: 203 | resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==} 204 | dev: false 205 | 206 | /merge2/1.4.1: 207 | resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 208 | engines: {node: '>= 8'} 209 | dev: false 210 | 211 | /micromatch/4.0.5: 212 | resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} 213 | engines: {node: '>=8.6'} 214 | dependencies: 215 | braces: 3.0.2 216 | picomatch: 2.3.1 217 | dev: false 218 | 219 | /minimist/1.2.6: 220 | resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} 221 | dev: false 222 | 223 | /node-domexception/1.0.0: 224 | resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} 225 | engines: {node: '>=10.5.0'} 226 | dev: false 227 | 228 | /node-fetch/3.2.8: 229 | resolution: {integrity: sha512-KtpD1YhGszhntMpBDyp5lyagk8KIMopC1LEb7cQUAh7zcosaX5uK8HnbNb2i3NTQK3sIawCItS0uFC3QzcLHdg==} 230 | engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 231 | dependencies: 232 | data-uri-to-buffer: 4.0.0 233 | fetch-blob: 3.2.0 234 | formdata-polyfill: 4.0.10 235 | dev: false 236 | 237 | /path-type/4.0.0: 238 | resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} 239 | engines: {node: '>=8'} 240 | dev: false 241 | 242 | /pause-stream/0.0.11: 243 | resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} 244 | dependencies: 245 | through: 2.3.8 246 | dev: false 247 | 248 | /picomatch/2.3.1: 249 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 250 | engines: {node: '>=8.6'} 251 | dev: false 252 | 253 | /ps-tree/1.2.0: 254 | resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} 255 | engines: {node: '>= 0.10'} 256 | hasBin: true 257 | dependencies: 258 | event-stream: 3.3.4 259 | dev: false 260 | 261 | /queue-microtask/1.2.3: 262 | resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 263 | dev: false 264 | 265 | /reusify/1.0.4: 266 | resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} 267 | engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 268 | dev: false 269 | 270 | /run-parallel/1.2.0: 271 | resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 272 | dependencies: 273 | queue-microtask: 1.2.3 274 | dev: false 275 | 276 | /slash/4.0.0: 277 | resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} 278 | engines: {node: '>=12'} 279 | dev: false 280 | 281 | /split/0.3.3: 282 | resolution: {integrity: sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==} 283 | dependencies: 284 | through: 2.3.8 285 | dev: false 286 | 287 | /stream-combiner/0.0.4: 288 | resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} 289 | dependencies: 290 | duplexer: 0.1.2 291 | dev: false 292 | 293 | /through/2.3.8: 294 | resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} 295 | dev: false 296 | 297 | /to-regex-range/5.0.1: 298 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 299 | engines: {node: '>=8.0'} 300 | dependencies: 301 | is-number: 7.0.0 302 | dev: false 303 | 304 | /universalify/2.0.0: 305 | resolution: {integrity: sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==} 306 | engines: {node: '>= 10.0.0'} 307 | dev: false 308 | 309 | /web-streams-polyfill/3.2.1: 310 | resolution: {integrity: sha512-e0MO3wdXWKrLbL0DgGnUV7WHVuw9OUvL4hjgnPkIeEvESk74gAITi5G606JtZPp39cd8HA9VQzCIvA49LpPN5Q==} 311 | engines: {node: '>= 8'} 312 | dev: false 313 | 314 | /which/2.0.2: 315 | resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 316 | engines: {node: '>= 8'} 317 | hasBin: true 318 | dependencies: 319 | isexe: 2.0.0 320 | dev: false 321 | 322 | /yaml/2.1.1: 323 | resolution: {integrity: sha512-o96x3OPo8GjWeSLF+wOAbrPfhFOGY0W00GNaxCDv+9hkcDJEnev1yh8S7pgHF0ik6zc8sQLuL8hjHjJULZp8bw==} 324 | engines: {node: '>= 14'} 325 | dev: false 326 | 327 | /zx/7.0.8: 328 | resolution: {integrity: sha512-sNjfDHzskqrSkWNj0TVhaowVK5AbpvuyuO1RBU4+LrFcgYI5u9CtyWWgUBRtRZl3bgGEF31zByszoBmwS47d1w==} 329 | engines: {node: '>= 16.0.0'} 330 | hasBin: true 331 | dependencies: 332 | '@types/fs-extra': 9.0.13 333 | '@types/minimist': 1.2.2 334 | '@types/node': 18.7.18 335 | '@types/ps-tree': 1.1.2 336 | '@types/which': 2.0.1 337 | chalk: 5.0.1 338 | fs-extra: 10.1.0 339 | globby: 13.1.2 340 | minimist: 1.2.6 341 | node-fetch: 3.2.8 342 | ps-tree: 1.2.0 343 | which: 2.0.2 344 | yaml: 2.1.1 345 | dev: false 346 | -------------------------------------------------------------------------------- /npm/esbuild-config-darwin-64/README.md: -------------------------------------------------------------------------------- 1 | # esbuild-config 2 | 3 | This is the macOS 64-bit binary for esbuild-config. See https://github.com/bpierre/esbuild-config for details. 4 | -------------------------------------------------------------------------------- /npm/esbuild-config-darwin-64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "esbuild-config-darwin-64", 3 | "description": "The macOS universal 64-bit binary for esbuild-config.", 4 | "version": "1.0.1", 5 | "repository": "https://github.com/bpierre/esbuild-config", 6 | "author": "Pierre Bertet ", 7 | "license": "MIT", 8 | "os": [ 9 | "darwin" 10 | ], 11 | "cpu": [ 12 | "x64", 13 | "arm64" 14 | ], 15 | "directories": { 16 | "bin": "bin" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /npm/esbuild-config-linux-64/README.md: -------------------------------------------------------------------------------- 1 | # esbuild-config 2 | 3 | This is the Linux 64-bit binary for esbuild-config. See https://github.com/bpierre/esbuild-config for details. 4 | -------------------------------------------------------------------------------- /npm/esbuild-config-linux-64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "esbuild-config-linux-64", 3 | "description": "The Linux 64-bit binary for esbuild-config.", 4 | "version": "1.0.1", 5 | "repository": "https://github.com/bpierre/esbuild-config", 6 | "author": "Pierre Bertet ", 7 | "license": "MIT", 8 | "os": [ 9 | "linux" 10 | ], 11 | "cpu": [ 12 | "x64" 13 | ], 14 | "directories": { 15 | "bin": "bin" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /npm/esbuild-config-linux-arm64/README.md: -------------------------------------------------------------------------------- 1 | # esbuild-config 2 | 3 | This is the Linux 64-bit binary for esbuild-config. See https://github.com/bpierre/esbuild-config for details. 4 | -------------------------------------------------------------------------------- /npm/esbuild-config-linux-arm64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "esbuild-config-linux-arm64", 3 | "description": "The Linux ARM 64-bit binary for esbuild-config.", 4 | "version": "1.0.1", 5 | "repository": "https://github.com/bpierre/esbuild-config", 6 | "author": "Pierre Bertet ", 7 | "license": "MIT", 8 | "os": [ 9 | "linux" 10 | ], 11 | "cpu": [ 12 | "arm64" 13 | ], 14 | "directories": { 15 | "bin": "bin" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /npm/esbuild-config-windows-64/README.md: -------------------------------------------------------------------------------- 1 | # esbuild-config 2 | 3 | This is the Windows 64-bit binary for esbuild-config. See https://github.com/bpierre/esbuild-config for details. 4 | -------------------------------------------------------------------------------- /npm/esbuild-config-windows-64/bin/esbuild-config: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | // From esbuild: 4 | // https://github.com/evanw/esbuild/blob/1336fbcf9bcca2f2708f5f575770f13a8440bde3/npm/esbuild-windows-64/bin/esbuild 5 | 6 | // Unfortunately even though npm shims "bin" commands on Windows with auto- 7 | // generated forwarding scripts, it doesn't strip the ".exe" from the file name 8 | // first. So it's possible to publish executables via npm on all platforms 9 | // except Windows. I consider this a npm bug. 10 | // 11 | // My workaround is to add this script as another layer of indirection. It'll 12 | // be slower because node has to boot up just to shell out to the actual exe, 13 | // but Windows is somewhat of a second-class platform to npm so it's the best 14 | // I can do I think. 15 | const esbuild_exe = require.resolve('esbuild-config-windows-64/esbuild-config.exe'); 16 | const child_process = require('child_process'); 17 | child_process.spawnSync(esbuild_exe, process.argv.slice(2), { stdio: 'inherit' }); 18 | -------------------------------------------------------------------------------- /npm/esbuild-config-windows-64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "esbuild-config-windows-64", 3 | "description": "The Windows 64-bit binary for esbuild-config.", 4 | "version": "1.0.1", 5 | "repository": "https://github.com/bpierre/esbuild-config", 6 | "author": "Pierre Bertet ", 7 | "license": "MIT", 8 | "os": [ 9 | "win32" 10 | ], 11 | "cpu": [ 12 | "x64" 13 | ], 14 | "directories": { 15 | "bin": "bin" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /npm/esbuild-config/README.md: -------------------------------------------------------------------------------- 1 | # esbuild-config 2 | 3 | Config files for [esbuild](https://github.com/evanw/esbuild). 4 | 5 | ## Why? 6 | 7 | esbuild is an incredible tool, that is [exclusively using command line parameters](https://github.com/evanw/esbuild/issues/39) as a configuration syntax. Some people prefer configuration files, so I thought it could be a good idea to provide a solution for this. It is also for me a pretext to use Rust while learning it :) 8 | 9 | ## Usage 10 | 11 | The esbuild-config command outputs a list of parameters based on a `esbuild.config.json` file, that can get passed to esbuild directly: 12 | 13 | ```console 14 | esbuild $(esbuild-config) 15 | ``` 16 | 17 | It detects the presence of `esbuild.config.json` in the current directory, or the project root (using the presence of a `package.json` file). Any file can also get provided as a parameter: 18 | 19 | ```console 20 | esbuild $(esbuild-config ./my-conf.json) 21 | ``` 22 | 23 | ## Install 24 | 25 | You have different options to install esbuild-config. 26 | 27 | ### npm 28 | 29 | Install globally with npm using the following command: 30 | 31 | ```console 32 | npm install --global esbuild-config 33 | ``` 34 | 35 | You can also add it to your project: 36 | 37 | ```console 38 | npm install --save-dev esbuild-config 39 | ``` 40 | 41 | ### Cargo 42 | 43 | Install it with [Cargo](https://github.com/rust-lang/cargo) using the following command: 44 | 45 | ```console 46 | cargo install esbuild-config 47 | ``` 48 | 49 | ### Binaries 50 | 51 | You can download the precompiled binaries [from the release page](https://github.com/bpierre/esbuild-config/releases). 52 | 53 | ### From source 54 | 55 | To clone the repository and build esbuild-config, run these commands ([after having installed Rust](https://www.rust-lang.org/tools/install)): 56 | 57 | ```console 58 | git clone git@github.com:bpierre/esbuild-config.git 59 | cd esbuild-config 60 | cargo build --release 61 | ``` 62 | 63 | The compiled binary is at `target/release/esbuild-config`. 64 | 65 | ## Syntax 66 | 67 | esbuild-config doesn’t do any validation on the configuration values: it only converts JSON types into arguments that are compatible with the format esbuild uses for its arguments. This makes it independent from esbuild versions, assuming the format doesn’t change. 68 | 69 | The only exception to this is the `entry` field, which gets converted into a list of file names (when an array is provided) or a single file name (when a string is provided). 70 | 71 | This is how JSON types get converted: 72 | 73 | ```json 74 | { 75 | "entry": "./index.js", 76 | "outfile": "./bundle.js", 77 | "external": ["react", "react-dom"], 78 | "loader": { ".js": "jsx", ".png": "base64" }, 79 | "minify": true 80 | } 81 | ``` 82 | 83 | Output: 84 | 85 | ```console 86 | --outfile=./bundle.js --minify --external:react --external:react-dom --loader:.js=jsx --loader:.png=base64 ./index.js 87 | ``` 88 | 89 | Notice how the entry, `./index.js`, has been moved to the end. esbuild-config also takes care of escaping the parameters as needed (e.g. by adding quotes). 90 | 91 | ## Contribute 92 | 93 | ```console 94 | # Run the app 95 | cargo run 96 | 97 | # Run the tests 98 | cargo test 99 | 100 | # Generate the code coverage report 101 | cargo tarpaulin -o Html 102 | ``` 103 | 104 | ## Special thanks 105 | 106 | [esbuild](https://github.com/evanw/esbuild) and [its author](https://github.com/evanw) obviously, not only for esbuild itself but also for its approach to [install a platform-specific binary through npm](https://github.com/evanw/esbuild/blob/1336fbcf9bcca2f2708f5f575770f13a8440bde3/lib/install.ts), that esbuild-config is also using. 107 | 108 | ## License 109 | 110 | [MIT](./LICENSE) 111 | -------------------------------------------------------------------------------- /npm/esbuild-config/bin/esbuild-config: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | throw new Error('esbuild-config: Failed to install correctly') 4 | -------------------------------------------------------------------------------- /npm/esbuild-config/install.js: -------------------------------------------------------------------------------- 1 | // This is a slightly modified version of the esbuild install script: 2 | // https://github.com/evanw/esbuild/blob/1336fbcf9bcca2f2708f5f575770f13a8440bde3/lib/install.ts 3 | 4 | const fs = require('fs') 5 | const os = require('os') 6 | const path = require('path') 7 | const zlib = require('zlib') 8 | const https = require('https') 9 | const child_process = require('child_process') 10 | 11 | const version = require('./package.json').version 12 | const binPath = path.join(__dirname, 'bin', 'esbuild-config') 13 | const stampPath = path.join(__dirname, 'stamp.txt') 14 | 15 | async function installBinaryFromPackage(name, fromPath, toPath) { 16 | // It turns out that some package managers (e.g. yarn) sometimes re-run the 17 | // postinstall script for this package after we have already been installed. 18 | // That means this script must be idempotent. Let's skip the install if it's 19 | // already happened. 20 | if (fs.existsSync(stampPath)) { 21 | return 22 | } 23 | 24 | // Try to install from the cache if possible 25 | const cachePath = getCachePath(name) 26 | try { 27 | // Copy from the cache 28 | fs.copyFileSync(cachePath, toPath) 29 | fs.chmodSync(toPath, 0o755) 30 | 31 | // Mark the cache entry as used for LRU 32 | const now = new Date() 33 | fs.utimesSync(cachePath, now, now) 34 | 35 | // Mark the operation as successful so this script is idempotent 36 | fs.writeFileSync(stampPath, '') 37 | return 38 | } catch {} 39 | 40 | // Next, try to install using npm. This should handle various tricky cases 41 | // such as environments where requests to npmjs.org will hang (in which case 42 | // there is probably a proxy and/or a custom registry configured instead). 43 | let buffer 44 | let didFail = false 45 | try { 46 | buffer = installUsingNPM(name, fromPath) 47 | } catch (err) { 48 | didFail = true 49 | console.error(`Trying to install "${name}" using npm`) 50 | console.error( 51 | `Failed to install "${name}" using npm: ${(err && err.message) || err}` 52 | ) 53 | } 54 | 55 | // If that fails, the user could have npm configured incorrectly or could not 56 | // have npm installed. Try downloading directly from npm as a last resort. 57 | if (!buffer) { 58 | const url = `https://registry.npmjs.org/${name}/-/${name}-${version}.tgz` 59 | console.error(`Trying to download ${JSON.stringify(url)}`) 60 | try { 61 | buffer = extractFileFromTarGzip(await fetch(url), fromPath) 62 | } catch (err) { 63 | console.error( 64 | `Failed to download ${JSON.stringify(url)}: ${ 65 | (err && err.message) || err 66 | }` 67 | ) 68 | } 69 | } 70 | 71 | // Give up if none of that worked 72 | if (!buffer) { 73 | console.error(`Install unsuccessful`) 74 | process.exit(1) 75 | } 76 | 77 | // Write out the binary executable that was extracted from the package 78 | fs.writeFileSync(toPath, buffer, { mode: 0o755 }) 79 | 80 | // Mark the operation as successful so this script is idempotent 81 | fs.writeFileSync(stampPath, '') 82 | 83 | // Also try to cache the file to speed up future installs 84 | try { 85 | fs.mkdirSync(path.dirname(cachePath), { recursive: true }) 86 | fs.copyFileSync(toPath, cachePath) 87 | cleanCacheLRU(cachePath) 88 | } catch {} 89 | 90 | if (didFail) console.error(`Install successful`) 91 | } 92 | 93 | function getCachePath(name) { 94 | const home = os.homedir() 95 | const common = ['esbuild-config', 'bin', `${name}@${version}`] 96 | if (process.platform === 'darwin') 97 | return path.join(home, 'Library', 'Caches', ...common) 98 | if (process.platform === 'win32') 99 | return path.join(home, 'AppData', 'Local', 'Cache', ...common) 100 | return path.join(home, '.cache', ...common) 101 | } 102 | 103 | function cleanCacheLRU(fileToKeep) { 104 | // Gather all entries in the cache 105 | const dir = path.dirname(fileToKeep) 106 | const entries = [] 107 | for (const entry of fs.readdirSync(dir)) { 108 | const entryPath = path.join(dir, entry) 109 | try { 110 | const stats = fs.statSync(entryPath) 111 | entries.push({ path: entryPath, mtime: stats.mtime }) 112 | } catch {} 113 | } 114 | 115 | // Only keep the most recent entries 116 | entries.sort((a, b) => +b.mtime - +a.mtime) 117 | for (const entry of entries.slice(5)) { 118 | try { 119 | fs.unlinkSync(entry.path) 120 | } catch {} 121 | } 122 | } 123 | 124 | function fetch(url) { 125 | return new Promise((resolve, reject) => { 126 | https 127 | .get(url, (res) => { 128 | if ( 129 | (res.statusCode === 301 || res.statusCode === 302) && 130 | res.headers.location 131 | ) 132 | return fetch(res.headers.location).then(resolve, reject) 133 | if (res.statusCode !== 200) 134 | return reject(new Error(`Server responded with ${res.statusCode}`)) 135 | let chunks = [] 136 | res.on('data', (chunk) => chunks.push(chunk)) 137 | res.on('end', () => resolve(Buffer.concat(chunks))) 138 | }) 139 | .on('error', reject) 140 | }) 141 | } 142 | 143 | function extractFileFromTarGzip(buffer, file) { 144 | try { 145 | buffer = zlib.unzipSync(buffer) 146 | } catch (err) { 147 | throw new Error( 148 | `Invalid gzip data in archive: ${(err && err.message) || err}` 149 | ) 150 | } 151 | let str = (i, n) => 152 | String.fromCharCode(...buffer.subarray(i, i + n)).replace(/\0.*$/, '') 153 | let offset = 0 154 | file = `package/${file}` 155 | while (offset < buffer.length) { 156 | let name = str(offset, 100) 157 | let size = parseInt(str(offset + 124, 12), 8) 158 | offset += 512 159 | if (!isNaN(size)) { 160 | if (name === file) return buffer.subarray(offset, offset + size) 161 | offset += (size + 511) & ~511 162 | } 163 | } 164 | throw new Error(`Could not find ${JSON.stringify(file)} in archive`) 165 | } 166 | 167 | function installUsingNPM(name, file) { 168 | const installDir = path.join(__dirname, '.install') 169 | fs.mkdirSync(installDir) 170 | fs.writeFileSync(path.join(installDir, 'package.json'), '{}') 171 | 172 | // Erase "npm_config_global" so that "npm install --global esbuild-config" 173 | // works. Otherwise this nested "npm install" will also be global, and the 174 | // install will deadlock waiting for the global installation lock. 175 | const env = { ...process.env, npm_config_global: undefined } 176 | 177 | child_process.execSync( 178 | `npm install --loglevel=error --prefer-offline --no-audit --progress=false ${name}@${version}`, 179 | { cwd: installDir, stdio: 'pipe', env } 180 | ) 181 | const buffer = fs.readFileSync( 182 | path.join(installDir, 'node_modules', name, file) 183 | ) 184 | removeRecursive(installDir) 185 | return buffer 186 | } 187 | 188 | function removeRecursive(dir) { 189 | for (const entry of fs.readdirSync(dir)) { 190 | const entryPath = path.join(dir, entry) 191 | let stats 192 | try { 193 | stats = fs.lstatSync(entryPath) 194 | } catch (e) { 195 | continue // Guard against https://github.com/nodejs/node/issues/4760 196 | } 197 | if (stats.isDirectory()) removeRecursive(entryPath) 198 | else fs.unlinkSync(entryPath) 199 | } 200 | fs.rmdirSync(dir) 201 | } 202 | 203 | function installOnUnix(name) { 204 | installBinaryFromPackage(name, 'bin/esbuild-config', binPath).catch((e) => 205 | setImmediate(() => { 206 | throw e 207 | }) 208 | ) 209 | } 210 | 211 | function installOnWindows(name) { 212 | fs.writeFileSync( 213 | binPath, 214 | `#!/usr/bin/env node 215 | const path = require('path'); 216 | const esbuild_config_exe = path.join(__dirname, '..', 'esbuild-config.exe'); 217 | const child_process = require('child_process'); 218 | child_process.spawnSync(esbuild_config_exe, process.argv.slice(2), { stdio: 'inherit' }); 219 | ` 220 | ) 221 | const exePath = path.join(__dirname, 'esbuild-config.exe') 222 | installBinaryFromPackage(name, 'esbuild-config.exe', exePath).catch((e) => 223 | setImmediate(() => { 224 | throw e 225 | }) 226 | ) 227 | } 228 | 229 | const key = `${process.platform} ${os.arch()} ${os.endianness()}` 230 | const knownWindowsPackages = { 231 | 'win32 x64 LE': 'esbuild-config-windows-64', 232 | } 233 | const knownUnixlikePackages = { 234 | 'darwin arm64 LE': 'esbuild-config-darwin-64', 235 | 'darwin x64 LE': 'esbuild-config-darwin-64', 236 | 'linux arm64 LE': 'esbuild-config-linux-arm64', 237 | 'linux x64 LE': 'esbuild-config-linux-64', 238 | } 239 | 240 | // Pick a package to install 241 | if (key in knownWindowsPackages) { 242 | installOnWindows(knownWindowsPackages[key]) 243 | } else if (key in knownUnixlikePackages) { 244 | installOnUnix(knownUnixlikePackages[key]) 245 | } else { 246 | console.error(`Unsupported platform: ${key}`) 247 | process.exit(1) 248 | } 249 | -------------------------------------------------------------------------------- /npm/esbuild-config/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "esbuild-config", 3 | "description": "Config files for esbuild.", 4 | "version": "1.0.1", 5 | "repository": "https://github.com/bpierre/esbuild-config", 6 | "author": "Pierre Bertet ", 7 | "license": "MIT", 8 | "bin": "bin/esbuild-config", 9 | "scripts": { 10 | "postinstall": "node install.js" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/lib/args.rs: -------------------------------------------------------------------------------- 1 | use super::errors; 2 | use json; 3 | use snailquote; 4 | 5 | // Get the esbuild arguments from the esbuild.config.json data structure 6 | pub fn args_from_config_json_value( 7 | json: json::JsonValue, 8 | ) -> Result { 9 | let mut args: Vec = vec![]; 10 | let mut entries: Vec = vec![]; 11 | let mut options: Vec = vec![]; 12 | 13 | if !json.is_object() { 14 | return Err(errors::ConfigParseError::JsonError(json::Error::WrongType( 15 | String::from("The JSON main type must be an object."), 16 | ))); 17 | } 18 | 19 | for (key, value) in json.entries() { 20 | if key == "entry" { 21 | match entries_from_config(value) { 22 | Some(result) => entries = result, 23 | None => (), 24 | } 25 | continue; 26 | } 27 | match option_from_config(key, value) { 28 | Some(param) => options.push(param), 29 | None => (), 30 | } 31 | } 32 | 33 | args.append(&mut options); 34 | args.append(&mut entries); 35 | Ok(args.join(" ")) 36 | } 37 | 38 | // Get the esbuild arguments from the data structure of the package.json esbuild field 39 | pub fn args_from_package_json_value( 40 | json: json::JsonValue, 41 | ) -> Result { 42 | if !json.is_object() { 43 | return Err(errors::ConfigParseError::JsonError(json::Error::WrongType( 44 | String::from("The package.json seems malformed."), 45 | ))); 46 | } 47 | args_from_config_json_value(json["esbuild"].clone()) 48 | } 49 | 50 | // Get the entries in the config file 51 | pub fn entries_from_config(value: &json::JsonValue) -> Option> { 52 | if value.is_string() { 53 | return Some(vec![quote_value(value.as_str().unwrap())]); 54 | } 55 | if value.is_array() { 56 | let entries: Vec = value 57 | .members() 58 | .filter_map(|entry| match entry.as_str() { 59 | Some(value) => Some(quote_value(value)), 60 | None => None, 61 | }) 62 | .collect(); 63 | return match entries.is_empty() { 64 | false => Some(entries), 65 | true => None, 66 | }; 67 | } 68 | None 69 | } 70 | 71 | // Parse a single config value from esbuild.config.json 72 | pub fn option_from_config(key: &str, value: &json::JsonValue) -> Option { 73 | if value.is_boolean() { 74 | return option_from_bool(key, value); 75 | } 76 | if value.is_string() { 77 | return option_from_string(key, value); 78 | } 79 | if value.is_array() { 80 | return option_from_array(key, value); 81 | } 82 | if value.is_object() { 83 | return option_from_object(key, value); 84 | } 85 | None 86 | } 87 | 88 | // Parse a bool config value 89 | pub fn option_from_bool(key: &str, value: &json::JsonValue) -> Option { 90 | match value.as_bool() { 91 | Some(value) => { 92 | if value { 93 | Some(["--", key].concat()) 94 | } else { 95 | None 96 | } 97 | } 98 | None => None, 99 | } 100 | } 101 | 102 | // Parse a string config value 103 | pub fn option_from_string(key: &str, value: &json::JsonValue) -> Option { 104 | match value.as_str() { 105 | Some(value) => Some(["--", key, "=", "e_value(value)].concat()), 106 | None => None, 107 | } 108 | } 109 | 110 | // Parse an object config value 111 | pub fn option_from_object(key: &str, value: &json::JsonValue) -> Option { 112 | let mut options: Vec = vec![]; 113 | 114 | for (k, v) in value.entries() { 115 | match v.as_str() { 116 | Some(value) => options.push(["--", key, ":", k, "=", "e_value(value)].concat()), 117 | None => (), 118 | } 119 | } 120 | 121 | if options.len() > 0 { 122 | Some(options.join(" ")) 123 | } else { 124 | None 125 | } 126 | } 127 | 128 | // Parse an array config value 129 | pub fn option_from_array(key: &str, value: &json::JsonValue) -> Option { 130 | let mut options: Vec = vec![]; 131 | 132 | for param_value in value.members() { 133 | match param_value.as_str() { 134 | Some(value) => options.push(["--", key, ":", "e_value(value)].concat()), 135 | None => (), 136 | } 137 | } 138 | 139 | if options.len() > 0 { 140 | Some(options.join(" ")) 141 | } else { 142 | None 143 | } 144 | } 145 | 146 | // Quote a value if it contains a space 147 | pub fn quote_value(value: &str) -> String { 148 | let value = snailquote::escape(&value).to_string(); 149 | if value == "" { 150 | String::from("''") 151 | } else { 152 | value 153 | } 154 | } 155 | 156 | #[cfg(test)] 157 | mod tests { 158 | use super::*; 159 | 160 | #[test] 161 | fn test_args_from_json_value() { 162 | let value = json::parse( 163 | r#"{ 164 | "entry": "index.js", 165 | "a": true, 166 | "b": "abc", 167 | "c": ["def", "ghi"], 168 | "d": { "e": "jkl", "f": "mno" } 169 | }"#, 170 | ) 171 | .unwrap(); 172 | assert_eq!( 173 | args_from_config_json_value(value).unwrap(), 174 | "--a --b=abc --c:def --c:ghi --d:e=jkl --d:f=mno index.js" 175 | ); 176 | } 177 | 178 | #[test] 179 | fn test_entries_from_config() { 180 | let value = json::parse("\"path/to/some/file.js\"").unwrap(); 181 | let entries = entries_from_config(&value).unwrap(); 182 | assert_eq!(entries[0], "path/to/some/file.js"); 183 | 184 | let value = json::parse("[\"path/to/some/file.js\"]").unwrap(); 185 | let entries = entries_from_config(&value).unwrap(); 186 | assert_eq!(entries[0], "path/to/some/file.js"); 187 | 188 | let value = json::parse( 189 | r#"[ 190 | "path/to/some/file.js", 191 | "./path with spaces.js", 192 | true 193 | ]"#, 194 | ) 195 | .unwrap(); 196 | let entries = entries_from_config(&value).unwrap(); 197 | assert_eq!(entries[0], "path/to/some/file.js"); 198 | assert_eq!(entries[1], "'./path with spaces.js'"); 199 | assert!(entries.get(2).is_none()); 200 | 201 | let value = json::parse("[]").unwrap(); 202 | assert!(entries_from_config(&value).is_none()); 203 | 204 | let value = json::parse("true").unwrap(); 205 | assert!(entries_from_config(&value).is_none()); 206 | } 207 | 208 | #[test] 209 | fn test_option_from_config() { 210 | let value = json::parse("true").unwrap(); 211 | assert!(!option_from_config("name", &value).is_none()); 212 | 213 | let value = json::parse("false").unwrap(); 214 | assert!(option_from_config("name", &value).is_none()); 215 | 216 | let value = json::parse("\"a\"").unwrap(); 217 | assert!(!option_from_config("name", &value).is_none()); 218 | 219 | let value = json::parse("[\"a\"]").unwrap(); 220 | assert!(!option_from_config("name", &value).is_none()); 221 | 222 | let value = json::parse("{\"a\": \"abc\"}").unwrap(); 223 | assert!(!option_from_config("name", &value).is_none()); 224 | 225 | let value = json::parse("null").unwrap(); 226 | assert!(option_from_config("name", &value).is_none()); 227 | } 228 | 229 | #[test] 230 | fn test_option_from_bool() { 231 | let value = json::parse("true").unwrap(); 232 | assert_eq!(option_from_bool("name", &value).unwrap(), "--name"); 233 | 234 | let value = json::parse("false").unwrap(); 235 | assert!(option_from_bool("name", &value).is_none()); 236 | 237 | // Wrong types get ignored 238 | let value = json::parse("1").unwrap(); 239 | assert!(option_from_bool("name", &value).is_none()); 240 | } 241 | 242 | #[test] 243 | fn test_option_from_string() { 244 | let value = json::parse("\"a\"").unwrap(); 245 | assert_eq!(option_from_string("name", &value).unwrap(), "--name=a"); 246 | 247 | // Wrong types get ignored 248 | let value = json::parse("1").unwrap(); 249 | assert!(option_from_string("name", &value).is_none()); 250 | 251 | // Empty value 252 | let value = json::parse("\"\"").unwrap(); 253 | assert_eq!(option_from_string("name", &value).unwrap(), "--name=''"); 254 | } 255 | 256 | #[test] 257 | fn test_option_from_object() { 258 | let value = json::parse("{ \"a\": \"abc\", \"b\": \"def\" }").unwrap(); 259 | assert_eq!( 260 | option_from_object("name", &value).unwrap(), 261 | "--name:a=abc --name:b=def" 262 | ); 263 | 264 | let value = json::parse("{}").unwrap(); 265 | assert!(option_from_object("name", &value).is_none()); 266 | 267 | // Wrong types in the object get ignored 268 | let value = json::parse("{ \"a\": \"abc\", \"b\": 123 }").unwrap(); 269 | assert_eq!(option_from_object("name", &value).unwrap(), "--name:a=abc"); 270 | } 271 | 272 | #[test] 273 | fn test_option_from_array() { 274 | let value = json::parse("[\"a\", \"b\", \"c\"]").unwrap(); 275 | assert_eq!( 276 | option_from_array("name", &value).unwrap(), 277 | "--name:a --name:b --name:c" 278 | ); 279 | 280 | // Empty arrays 281 | let value = json::parse("[]").unwrap(); 282 | assert!(option_from_array("name", &value).is_none()); 283 | 284 | // Wrong types in the array get ignored 285 | let value = json::parse("[\"a\", 1, \"b\"]").unwrap(); 286 | assert_eq!( 287 | option_from_array("name", &value).unwrap(), 288 | "--name:a --name:b" 289 | ); 290 | } 291 | 292 | #[test] 293 | fn test_quote_value() { 294 | assert_eq!(quote_value("value"), "value"); 295 | 296 | // Having a space should return the value with quotes 297 | assert_eq!(quote_value("with space"), "'with space'"); 298 | 299 | // Having a quote should return the value with quotes 300 | assert_eq!(quote_value("with\"quote"), "'with\"quote'"); 301 | assert_eq!(quote_value("with'quote"), "\"with'quote\""); 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /src/lib/errors.rs: -------------------------------------------------------------------------------- 1 | use std::{error, fmt, io}; 2 | 3 | #[derive(Debug)] 4 | pub enum EsbuildConfigError { 5 | ConfigParseError, 6 | ConfigPathError, 7 | Io(io::Error), 8 | } 9 | impl fmt::Display for EsbuildConfigError { 10 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 11 | write!(f, "An unknown error happened.") 12 | } 13 | } 14 | 15 | #[derive(Debug)] 16 | pub enum ConfigPathError { 17 | Io(io::Error), 18 | } 19 | impl fmt::Display for ConfigPathError { 20 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 21 | write!(f, "Couldn’t find or open the esbuild configuration file.") 22 | } 23 | } 24 | 25 | #[derive(Debug)] 26 | pub enum ConfigParseError { 27 | InvalidConfigError, 28 | JsonError(json::Error), 29 | } 30 | impl fmt::Display for ConfigParseError { 31 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 32 | write!(f, "Invalid esbuild configuration format.") 33 | } 34 | } 35 | 36 | #[derive(Debug)] 37 | pub struct InvalidConfigError; 38 | impl error::Error for InvalidConfigError {} 39 | impl fmt::Display for InvalidConfigError { 40 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 41 | write!(f, "Invalid esbuild configuration format.") 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/lib/mod.rs: -------------------------------------------------------------------------------- 1 | mod args; 2 | pub mod errors; 3 | mod paths; 4 | 5 | use json; 6 | use std::{fs, io, path}; 7 | 8 | pub enum ConfigFileType { 9 | ConfigJson, 10 | PackageJson, 11 | } 12 | 13 | pub fn esbuild_conf(args: Vec) -> Result { 14 | let (config_path, config_file_type) = 15 | paths::config_path(args.get(1)).map_err(|_| errors::EsbuildConfigError::ConfigPathError)?; 16 | 17 | let config_content = 18 | read_json_content(config_path).map_err(|_| errors::EsbuildConfigError::ConfigParseError)?; 19 | 20 | parse_esbuild_config(config_content, config_file_type) 21 | .map_err(|_| errors::EsbuildConfigError::ConfigParseError) 22 | } 23 | 24 | pub fn read_json_content(path: path::PathBuf) -> Result { 25 | match fs::read_to_string(&path) { 26 | Ok(content) => Ok(content), 27 | Err(_) => Err(errors::EsbuildConfigError::Io(io::Error::new( 28 | io::ErrorKind::Other, 29 | [ 30 | "Couldn’t read ", 31 | path.into_os_string() 32 | .into_string() 33 | .expect("The provided path couldn’t get read.") 34 | .as_str(), 35 | ] 36 | .concat(), 37 | ))), 38 | } 39 | } 40 | 41 | // Parse the entire esbuild.config.json 42 | pub fn parse_esbuild_config( 43 | content: String, 44 | config_file_type: ConfigFileType, 45 | ) -> Result { 46 | match json::parse(&content) { 47 | Ok(value) => match config_file_type { 48 | ConfigFileType::ConfigJson => args::args_from_config_json_value(value), 49 | ConfigFileType::PackageJson => args::args_from_package_json_value(value), 50 | } 51 | .map_err(|_| errors::ConfigParseError::InvalidConfigError), 52 | Err(_) => return Err(errors::ConfigParseError::InvalidConfigError), 53 | } 54 | } 55 | 56 | #[cfg(test)] 57 | mod tests { 58 | use super::*; 59 | 60 | #[test] 61 | fn test_parse_esbuild_config() { 62 | let config_json = r#" 63 | { 64 | "entry": "index.js", 65 | "a": true, 66 | "b": "abc", 67 | "c": ["def", "ghi"], 68 | "d": { "e": "jkl", "f": "mno" } 69 | } 70 | "#; 71 | assert_eq!( 72 | parse_esbuild_config(config_json.to_string(), ConfigFileType::ConfigJson).unwrap(), 73 | "--a --b=abc --c:def --c:ghi --d:e=jkl --d:f=mno index.js" 74 | ); 75 | assert!( 76 | match parse_esbuild_config("true".to_string(), ConfigFileType::ConfigJson) { 77 | Ok(_) => false, 78 | Err(_) => true, 79 | } 80 | ); 81 | 82 | let package_json = r#" 83 | { 84 | "esbuild": { 85 | "entry": "index.js", 86 | "a": true, 87 | "b": "abc", 88 | "c": ["def", "ghi"], 89 | "d": { "e": "jkl", "f": "mno" } 90 | } 91 | } 92 | "#; 93 | assert_eq!( 94 | parse_esbuild_config(package_json.to_string(), ConfigFileType::PackageJson).unwrap(), 95 | "--a --b=abc --c:def --c:ghi --d:e=jkl --d:f=mno index.js" 96 | ); 97 | assert!( 98 | match parse_esbuild_config("1".to_string(), ConfigFileType::PackageJson) { 99 | Ok(_) => false, 100 | Err(_) => true, 101 | } 102 | ); 103 | assert!( 104 | match parse_esbuild_config("{}".to_string(), ConfigFileType::PackageJson) { 105 | Ok(_) => false, 106 | Err(_) => true, 107 | } 108 | ); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/lib/paths.rs: -------------------------------------------------------------------------------- 1 | use super::errors; 2 | use super::ConfigFileType; 3 | use std::{env, io, path::PathBuf}; 4 | 5 | const CONFIG_FILE_NAME: &str = "esbuild.config.json"; 6 | 7 | // Return the path of the config file, based on the passed string or by detecting it. 8 | pub fn config_path( 9 | path: Option<&String>, 10 | ) -> Result<(PathBuf, ConfigFileType), errors::ConfigPathError> { 11 | match path { 12 | Some(path) => { 13 | let esbuild_json = PathBuf::from(path); 14 | if esbuild_json.exists() { 15 | Ok((esbuild_json, ConfigFileType::ConfigJson)) 16 | } else { 17 | Err(errors::ConfigPathError::Io(io::Error::new( 18 | io::ErrorKind::NotFound, 19 | "The provided file doesn’t seem to exist.", 20 | ))) 21 | } 22 | } 23 | None => Ok(detect_config_path()?), 24 | } 25 | } 26 | 27 | // Get the first ancestor directory containing a package.json 28 | pub fn pkg_root_path() -> Result { 29 | let cwd = env::current_dir().map_err(errors::ConfigPathError::Io)?; 30 | 31 | for dir in cwd.ancestors() { 32 | if dir.join("package.json").exists() { 33 | return Ok(dir.to_path_buf()); 34 | } 35 | } 36 | 37 | Err(errors::ConfigPathError::Io(io::Error::new( 38 | io::ErrorKind::NotFound, 39 | "No package.json found.", 40 | ))) 41 | } 42 | 43 | // Detect the path of the config file from the current directory. 44 | pub fn detect_config_path() -> Result<(PathBuf, ConfigFileType), errors::ConfigPathError> { 45 | let cwd = env::current_dir().map_err(errors::ConfigPathError::Io)?; 46 | let local_esbuild_json = cwd.join(CONFIG_FILE_NAME); 47 | 48 | // Local esbuild.config.json 49 | if local_esbuild_json.exists() { 50 | return Ok((local_esbuild_json, ConfigFileType::ConfigJson)); 51 | } 52 | 53 | // Project root esbuild.config.json 54 | let pkg_root = match pkg_root_path() { 55 | Ok(pkg_root) => pkg_root, 56 | Err(_) => { 57 | return Err(errors::ConfigPathError::Io(io::Error::new( 58 | io::ErrorKind::NotFound, 59 | [ 60 | "No ", 61 | CONFIG_FILE_NAME, 62 | " found in the current directory, and no project root found.", 63 | ] 64 | .concat(), 65 | ))) 66 | } 67 | }; 68 | 69 | let esbuild_json = pkg_root.join(CONFIG_FILE_NAME); 70 | let package_json = pkg_root.join("package.json"); 71 | 72 | if esbuild_json.exists() { 73 | Ok((esbuild_json, ConfigFileType::ConfigJson)) 74 | } else { 75 | Ok((package_json, ConfigFileType::PackageJson)) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod lib; 2 | 3 | use lib::errors::EsbuildConfigError; 4 | use std::env; 5 | 6 | fn main() { 7 | match lib::esbuild_conf(env::args().collect()) { 8 | Ok(value) => println!("{}", value), 9 | Err(err) => match err { 10 | EsbuildConfigError::ConfigParseError => { 11 | eprintln!("The configuration file or package.json is invalid."); 12 | } 13 | EsbuildConfigError::ConfigPathError => { 14 | eprintln!("Couldn’t find or open the esbuild configuration file."); 15 | } 16 | _ => { 17 | eprintln!("Error: {}", err); 18 | } 19 | }, 20 | } 21 | } 22 | --------------------------------------------------------------------------------