diff --git a/.editorconfig b/.editorconfig index 960773c..0b8fa87 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,12 +1,13 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file root = true +# Unix-style newlines with a newline ending every file [*] -indent_style = tab -end_of_line = lf charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.yml] -indent_size = 2 indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/.gitattributes b/.gitattributes index 391f0a4..8e2ef52 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ -* text=auto -*.js text eol=lf +* text=auto eol=lf + +*.exe -text binary diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..0070a8e --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +* @mimmi20 +* @kevva diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..a0d9405 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,73 @@ +# https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates + +# file-version: 1.0 + +version: 2 + +updates: + - package-ecosystem: "npm" + + directory: "/" + + schedule: + interval: "cron" + cronjob: "0 4 15 * *" + timezone: "Europe/Berlin" + + cooldown: + default-days: 7 + + groups: + production-dependencies: + dependency-type: "production" + development-dependencies: + dependency-type: "development" + + assignees: + - "mimmi20" + + labels: + - "dependencies" + + versioning-strategy: "increase" + + commit-message: + include: "scope" + prefix: "npm (master)" + + # Disable rebasing for all pull requests + rebase-strategy: "disabled" + + - package-ecosystem: "github-actions" + + directory: "/" + + schedule: + interval: "cron" + cronjob: "0 4 15 * *" + timezone: "Europe/Berlin" + + cooldown: + default-days: 7 + + groups: + production-dependencies: + dependency-type: "production" + development-dependencies: + dependency-type: "development" + + assignees: + - "mimmi20" + + labels: + - "dependencies" + + commit-message: + prefix: "github-actions (master)" + + ignore: + - dependency-name: "mimmi20/ci" + - dependency-name: "release-drafter/release-drafter" + + # Disable rebasing for all pull requests + rebase-strategy: "disabled" diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 0000000..57529a0 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,81 @@ +--- +# file-version: 1.0 + +template: | + ## Whatโ€™s Changed + + $CHANGES + + ## ๐Ÿ‘จ๐Ÿผโ€๐Ÿ’ป Contributors + + $CONTRIBUTORS + +change-template: "- $TITLE by @$AUTHOR (#$NUMBER)" +change-title-escapes: '\<*_&#@`' # You can add # and @ to disable mentions, and add ` to disable code blocks. +no-changes-template: "- No changes" +name-template: "$RESOLVED_VERSION" +tag-template: "$RESOLVED_VERSION" +version-template: "$MAJOR.$MINOR.$PATCH" + +exclude-labels: + - "duplicate" + - "question" + - "invalid" + - "wontfix" + +version-resolver: + major: + labels: + - "bc break" + - "removed" + minor: + labels: + - "deprecated" + - "security" + patch: + labels: + - "bug" + - "dependencies" + - "enhancement" + - "maintenance" + - "documentation" + default: patch + +autolabeler: + - label: "documentation" + files: + - "*.md" + +categories: + - title: "**๐Ÿ’ฅ Breaking:**" + labels: + - "bc break" + - title: "**๐Ÿฐ Enhancements:**" + labels: + - "enhancement" + - title: "**๐Ÿž Bugs:**" + labels: + - "bug" + - title: "**๐Ÿ’€ Deprecated:**" + labels: + - "deprecated" + - title: "**๐Ÿ—‘ Removed:**" + labels: + - "removed" + - title: "**๐Ÿ›ก Security:**" + labels: + - "security" + - title: "**๐Ÿ•ธ Dependencies:**" + labels: + - "dependencies" + - title: "**๐Ÿงน Maintenance:**" + labels: + - "maintenance" + - title: "**๐Ÿ“ฆ Documentation:**" + labels: + - "documentation" + +commitish: master +target_commitish: master +filter-by-commitish: true +include-pre-releases: true diff --git a/.github/workflows/cleanup-caches.yml b/.github/workflows/cleanup-caches.yml new file mode 100644 index 0000000..f4303eb --- /dev/null +++ b/.github/workflows/cleanup-caches.yml @@ -0,0 +1,43 @@ +# https://help.github.com/en/categories/automating-your-workflow-with-github-actions + +# file-version: 1.0 + +name: "Cleanup caches by a branch" + +on: + pull_request: + types: + - "closed" + workflow_dispatch: + +permissions: + contents: read + +jobs: + cleanup: + name: "Cleanup Caches" + uses: "mimmi20/ci/.github/workflows/cleanup-cache.yml@8.4" + with: + repository: ${{ github.repository }} + ref: ${{ github.ref }} + + # This is a meta job to avoid to have to constantly change the protection rules + # whenever we touch the matrix. + cleanup-status: + name: "Cleanup Status" + + runs-on: "ubuntu-latest" + + if: always() + + needs: + - "cleanup" + + steps: + - name: Successful run + if: ${{ !(contains(needs.*.result, 'failure')) }} + run: exit 0 + + - name: Failing run + if: ${{ contains(needs.*.result, 'failure') }} + run: exit 1 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..68906ae --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,91 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# + +# https://help.github.com/en/categories/automating-your-workflow-with-github-actions + +# file-version: 1.0 + +name: "CodeQL" + +on: + push: + branches: + - "master" + pull_request: + # The branches below must be a subset of the branches above + branches: + - "master" + schedule: + - cron: "28 0 * * 0" + +permissions: + contents: read + +jobs: + analyze: + name: "Analyze" + runs-on: "ubuntu-latest" + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: + - "javascript" + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + # Use only 'java' to analyze code written in Java, Kotlin or both + # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: "Checkout" + uses: "actions/checkout@v6" + with: + # Disabling shallow clone is recommended for improving relevancy of reporting + fetch-depth: 0 + lfs: false + persist-credentials: false + + # Initializes the CodeQL tools for scanning. + - name: "Initialize CodeQL" + uses: "github/codeql-action/init@v4" + with: + languages: "${{ matrix.language }}" + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: "Autobuild" + uses: "github/codeql-action/autobuild@v4" + + # โ„น๏ธ Command-line programs to run using the OS shell. + # ๐Ÿ“š See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: "Perform CodeQL Analysis" + uses: "github/codeql-action/analyze@v4" + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..adf0dcd --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,45 @@ +# Dependency Review Action +# +# This Action will scan dependency manifest files that change as part of a Pull Request, surfacing known-vulnerable versions of the packages declared or updated in the PR. Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable packages will be blocked from merging. +# +# Source repository: https://github.com/actions/dependency-review-action +# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement + +# https://help.github.com/en/categories/automating-your-workflow-with-github-actions + +# file-version: 1.0 + +name: "Dependency Review" + +on: + - pull_request + +permissions: + contents: read + pull-requests: write + +jobs: + dependency-review: + name: "Review" + uses: "mimmi20/ci/.github/workflows/review-dependency.yml@8.4" + + # This is a meta job to avoid to have to constantly change the protection rules + # whenever we touch the matrix. + review-status: + name: "Review Status" + + runs-on: "ubuntu-latest" + + if: always() + + needs: + - "dependency-review" + + steps: + - name: Successful run + if: ${{ !(contains(needs.*.result, 'failure')) }} + run: exit 0 + + - name: Failing run + if: ${{ contains(needs.*.result, 'failure') }} + run: exit 1 diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml new file mode 100644 index 0000000..dc9bed8 --- /dev/null +++ b/.github/workflows/labels.yml @@ -0,0 +1,46 @@ +# https://help.github.com/en/categories/automating-your-workflow-with-github-actions + +# file-version: 1.0 + +name: "Sync labels in the declarative way" + +on: + push: + branches: + - "master" + paths: + - ".github/labels.yml" + - ".github/workflows/labels.yml" + +permissions: + contents: read + issues: write + +jobs: + build: + name: "Sync labels" + uses: "mimmi20/ci/.github/workflows/sync-labels.yml@8.4" + with: + repository: ${{ github.repository }} + manifest: ".github/labels.yml" + + # This is a meta job to avoid to have to constantly change the protection rules + # whenever we touch the matrix. + label-status: + name: "Sync Label Status" + + runs-on: "ubuntu-latest" + + if: always() + + needs: + - "build" + + steps: + - name: Successful run + if: ${{ !(contains(needs.*.result, 'failure')) }} + run: exit 0 + + - name: Failing run + if: ${{ contains(needs.*.result, 'failure') }} + run: exit 1 diff --git a/.github/workflows/lint-workflow-files.yml b/.github/workflows/lint-workflow-files.yml new file mode 100644 index 0000000..62b07e1 --- /dev/null +++ b/.github/workflows/lint-workflow-files.yml @@ -0,0 +1,38 @@ +name: "Lint GitHub Actions workflows" + +on: + pull_request: + paths: + - ".github/**" + +permissions: + contents: read + +jobs: + actionlint: + uses: "mimmi20/ci/.github/workflows/lint-actions.yml@8.4" + with: + skip-nasm-install: false + skip-libimagequant-install: false + skip-librabbitmq-install: true + skip-libgif-install: true + + # This is a meta job to avoid to have to constantly change the protection rules + # whenever we touch the matrix. + lint-status: + name: "Lint Status" + + runs-on: "ubuntu-latest" + + if: always() + + needs: "actionlint" + + steps: + - name: Failing run + if: ${{ contains(needs.*.result, 'failure') }} + run: exit 1 + + - name: Successful run + if: ${{ !(contains(needs.*.result, 'failure')) }} + run: exit 0 diff --git a/.github/workflows/lock-closed-issues.yml b/.github/workflows/lock-closed-issues.yml new file mode 100644 index 0000000..4ee3ff5 --- /dev/null +++ b/.github/workflows/lock-closed-issues.yml @@ -0,0 +1,23 @@ +# https://help.github.com/en/categories/automating-your-workflow-with-github-actions + +# file-version: 1.0 + +name: "Lock closed issue" + +on: + issues: + types: + - "closed" + +permissions: + contents: read + +jobs: + lock: + name: "Lock" + runs-on: "ubuntu-latest" + permissions: + issues: write + steps: + - name: "Lock issue" + uses: "IOrlandoni/lock-issues@v1.2.1" diff --git a/.github/workflows/reactions.yml b/.github/workflows/reactions.yml new file mode 100644 index 0000000..99ef3ad --- /dev/null +++ b/.github/workflows/reactions.yml @@ -0,0 +1,38 @@ +# https://help.github.com/en/categories/automating-your-workflow-with-github-actions + +# file-version: 1.0 + +name: "Reaction Comments" + +on: + issue_comment: + types: + - "created" + - "edited" + pull_request_review_comment: + types: + - "created" + - "edited" + +permissions: + contents: read + actions: write + issues: write + pull-requests: write + +jobs: + action: + runs-on: "ubuntu-latest" + + steps: + - uses: "dessant/reaction-comments@v4" + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + issue-comment: > + :wave: @{comment-author}, would you like to leave + a reaction instead? + pr-comment: > + :wave: @{comment-author}, would you like to leave + a reaction instead? + process-only: '' + log-output: false diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 0000000..f4b2d5e --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,41 @@ +# https://help.github.com/en/categories/automating-your-workflow-with-github-actions + +# file-version: 1.0 + +name: "Release Drafter" + +on: + push: + # branches to consider in the event; optional, defaults to all + branches: + - "master" + # pull_request event is required only for autolabeler + pull_request: + # Only following types are handled by the action, but one can default to all as well + types: + - "opened" + - "reopened" + - "synchronize" + # pull_request_target event is required for autolabeler to support PRs from forks + # pull_request_target: + +permissions: + contents: read + +jobs: + update-release-draft: + permissions: + # write permission is required to create a GitHub release + contents: write + # write permission is required for autolabeler + # otherwise, read permission is required at least + pull-requests: write + + runs-on: "ubuntu-latest" + + steps: + # Drafts your next Release notes as Pull Requests are merged into "master" + - name: "Release Drafter" + uses: "release-drafter/release-drafter@v6.0.0" + env: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 0000000..e5267b4 --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,21 @@ +name: "OX Security Scan" +on: + push: + branches: + - "master" + pull_request: + types: ["opened", "reopened", "synchronize"] + branches: + - "master" + +permissions: + contents: read + +jobs: + security: + runs-on: "ubuntu-latest" + steps: + - name: "Run OX Security Scan to check for vulnerabilities" + with: + ox_api_key: ${{ secrets.OX_API_KEY }} + uses: "oxsecurity/ox-security-scan@1.0.0" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..80a38ed --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,28 @@ +name: test +on: + - push + - pull_request + +permissions: + contents: read + +jobs: + test: + name: Node.js ${{ matrix.node-version }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + node-version: + - 20 + - 22 + - 24 + os: + - ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-node@v6 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - run: npm test diff --git a/.gitignore b/.gitignore index 239ecff..e8726e1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,36 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Directories node_modules -yarn.lock +dist +dist-ssr +test +temp +coverage +.vite-inspect +other +package-lock.json + +# Files +todo +*.local +*.bak +*.tgz diff --git a/.npmrc b/.npmrc index 43c97e7..a1ad008 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,12 @@ -package-lock=false +color=true +engine-strict=true +format-package-lock=true +fund=false +lockfile-version=3 +package-lock=true +strict-peer-deps=false +legacy-peer-deps=true +save-exact=false + +# Sets the flags in NODE_OPTIONS when running `npm run myScript` +node-options='--unhandled-rejections=strict --trace-warnings --trace-uncaught' diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 57505cf..0000000 --- a/.travis.yml +++ /dev/null @@ -1,6 +0,0 @@ -sudo: false -language: node_js -node_js: - - '8' - - '6' - - '4' diff --git a/index.js b/index.js index 7ac74fb..7f5f93c 100644 --- a/index.js +++ b/index.js @@ -1,17 +1,28 @@ -'use strict'; -const fs = require('fs'); -const execa = require('execa'); -const pFinally = require('p-finally'); -const pify = require('pify'); -const rimraf = require('rimraf'); -const tempfile = require('tempfile'); - -const fsP = pify(fs); -const rmP = pify(rimraf); -const input = Symbol('inputPath'); -const output = Symbol('outputPath'); - -module.exports = opts => { +import { writeFile, readFile } from 'node:fs/promises'; +import { execa } from 'execa'; +import { rimraf } from 'rimraf' +import tempfile from 'tempfile'; + +export const input = Symbol('inputPath'); +export const output = Symbol('outputPath'); + +const pFinally = async function( + promise, + onFinally = (() => {}) +) { + let value; + try { + value = await promise; + } catch (error) { + await onFinally(); + throw error; + } + + await onFinally(); + return value; +}; + +const func = async opts => { opts = Object.assign({}, opts); if (!Buffer.isBuffer(opts.input)) { @@ -29,17 +40,18 @@ module.exports = opts => { const inputPath = opts.inputPath || tempfile(); const outputPath = opts.outputPath || tempfile(); - opts.args = opts.args.map(x => x === input ? inputPath : x === output ? outputPath : x); + opts.args = opts.args.map(function (x) { + return x === input ? inputPath : x === output ? outputPath : x; + }); - const promise = fsP.writeFile(inputPath, opts.input) + const promise = writeFile(inputPath, opts.input) .then(() => execa(opts.bin, opts.args)) - .then(() => fsP.readFile(outputPath)); + .then(() => readFile(outputPath)); return pFinally(promise, () => Promise.all([ - rmP(inputPath), - rmP(outputPath) + rimraf(inputPath), + rimraf(outputPath) ])); }; -module.exports.input = input; -module.exports.output = output; +export default func; diff --git a/package.json b/package.json index 2c3b6d9..a345eeb 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,10 @@ "url": "https://github.com/kevva" }, "engines": { - "node": ">=4" + "node": ">=20" }, "scripts": { - "test": "xo && ava" + "test": "ava" }, "files": [ "index.js" @@ -23,17 +23,16 @@ "exec" ], "dependencies": { - "execa": "^0.7.0", - "p-finally": "^1.0.0", - "pify": "^3.0.0", - "rimraf": "^2.5.4", - "tempfile": "^2.0.0" + "execa": "^9.6.1", + "rimraf": "^6.1.2", + "tempfile": "^5.0.0" }, "devDependencies": { - "ava": "*", - "gifsicle": "^3.0.4", - "is-gif": "^1.0.0", - "path-exists": "^3.0.0", - "xo": "*" - } + "ava": "^6.4.1", + "gifsicle": "^7.0.1", + "is-gif": "^4.0.1", + "path-exists": "^5.0.0" + }, + "type": "module", + "exports": "./index.js" } diff --git a/readme.md b/readme.md index da36e72..b57e7ee 100644 --- a/readme.md +++ b/readme.md @@ -2,13 +2,11 @@ > Run a Buffer through a child process - ## Install +```shell +npm install exec-buffer ``` -$ npm install exec-buffer -``` - ## Usage @@ -18,16 +16,15 @@ const execBuffer = require('exec-buffer'); const gifsicle = require('gifsicle').path; execBuffer({ - input: fs.readFileSync('test.gif'), - bin: gifsicle, - args: ['-o', execBuffer.output, execBuffer.input] + input: fs.readFileSync('test.gif'), + bin: gifsicle, + args: ['-o', execBuffer.output, execBuffer.input] }).then(data => { - console.log(data); - //=> + console.log(data); + //=> }); ``` - ## API ### execBuffer(options) @@ -76,7 +73,6 @@ Returns a temporary path to where the input file will be written. Returns a temporary path to where the output file will be written. - ## License MIT ยฉ [Kevin Mรฅrtensson](https://github.com/kevva) diff --git a/test.js b/test.js index 67c0b92..e16f6b4 100644 --- a/test.js +++ b/test.js @@ -1,24 +1,26 @@ -import fs from 'fs'; -import path from 'path'; +import { readFile } from 'node:fs/promises'; +import path from 'node:path'; import gifsicle from 'gifsicle'; import isGif from 'is-gif'; -import pathExists from 'path-exists'; -import pify from 'pify'; +import { pathExists } from 'path-exists' import test from 'ava'; -import m from './'; +import m, { input, output } from './index.js'; +import { fileURLToPath } from 'node:url'; -test('set temporary directories', t => { - const {input, output} = m; +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +test('set temporary directories', async t => { t.truthy(input); t.truthy(output); }); test('return a optimized buffer', async t => { - const buf = await pify(fs.readFile)(path.join(__dirname, 'fixture.gif')); + const buf = await readFile(path.join(__dirname, 'fixture.gif')); const data = await m({ input: buf, bin: gifsicle, - args: ['-o', m.output, m.input] + args: ['-o', output, input] }); t.true(data.length < buf.length); @@ -26,14 +28,27 @@ test('return a optimized buffer', async t => { }); test('remove temporary files', async t => { - const buf = await pify(fs.readFile)(path.join(__dirname, 'fixture.gif')); - const err = await t.throws(m({ - input: buf, - bin: 'foobarunicorn', - args: [m.output, m.input] - })); + // Skip the test on Windows + if (process.platform === 'win32') { + t.pass(); + return; + } + + const buf = await readFile(path.join(__dirname, 'fixture.gif')); + + try { + await m({ + input: buf, + bin: 'foobarunicorn', + args: [output, input] + }); + t.pass(); + } catch (err) { + t.is(err.code, 'ENOENT'); - t.is(err.code, 'ENOENT'); - t.false(await pathExists(err.spawnargs[0])); - t.false(await pathExists(err.spawnargs[1])); + if (err.cause instanceof Error) { + t.false(await pathExists(err.cause.spawnargs[0])); + t.false(await pathExists(err.cause.spawnargs[1])); + } + } });