diff --git a/.codeclimate.yml b/.codeclimate.yml new file mode 100644 index 0000000..25d8ed0 --- /dev/null +++ b/.codeclimate.yml @@ -0,0 +1,29 @@ +version: "2" +checks: + argument-count: + enabled: false + complex-logic: + enabled: false + file-lines: + enabled: false + method-complexity: + enabled: false + method-count: + enabled: false + method-lines: + enabled: false + nested-control-flow: + enabled: true + return-statements: + enabled: false + similar-code: + enabled: false + identical-code: + enabled: false +plugins: + phpmd: + enabled: false + phpcodesniffer: + enabled: false + sonar-php: + enabled: false diff --git a/.editorconfig b/.editorconfig index 960773c..018fa9b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,12 +1,16 @@ +; This file is for unifying the coding style for different editors and IDEs. +; More information at http://editorconfig.org + 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 +indent_style = space +indent_size = 2 +end_of_line = lf insert_final_newline = true +trim_trailing_whitespace = true -[*.yml] -indent_size = 2 -indent_style = space +[*.md] +trim_trailing_whitespace = false 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..bda4837 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,90 @@ +# 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)" + + ignore: + - dependency-name: "@eslint/js" + versions: [ ">=10.0.0" ] + - dependency-name: "eslint" + versions: [ ">=10.0.0" ] + - dependency-name: "ava" + versions: [ ">=7.0.0" ] + + # 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/.github/workflows/validate-all.yml" + - dependency-name: "mimmi20/ci/.github/workflows/cleanup-cache.yml" + - dependency-name: "mimmi20/ci/.github/workflows/install-node.yml" + - dependency-name: "mimmi20/ci/.github/workflows/analytics-node.yml" + - dependency-name: "mimmi20/ci/.github/workflows/test-node.yml" + - dependency-name: "mimmi20/ci/.github/workflows/review-dependency.yml" + - dependency-name: "mimmi20/ci/.github/workflows/lint-actions.yml" + - dependency-name: "mimmi20/ci/.github/workflows/sync-labels.yml" + - dependency-name: "mimmi20/ci/.github/workflows/lock-issues.yml" + - dependency-name: "mimmi20/ci/.github/workflows/reaction-comments.yml" + - dependency-name: "mimmi20/ci/.github/workflows/draft-release.yml" + + # 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..043dda6 --- /dev/null +++ b/.github/workflows/cleanup-caches.yml @@ -0,0 +1,44 @@ +# 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 + actions: write + +jobs: + cleanup: + name: "Cleanup Caches" + uses: "mimmi20/ci/.github/workflows/cleanup-cache.yml@master" + with: + repository: ${{ github.repository }} + ref: ${{ github.event_name == 'pull_request' && format('refs/pull/{0}/merge', github.event.pull_request.number) || 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: Failing run + if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled') }} + run: exit 1 + + - name: Successful run + if: ${{ !(contains(needs.*.result, 'failure')) && !(contains(needs.*.result, 'skipped')) && !(contains(needs.*.result, 'cancelled')) }} + run: exit 0 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..e21b739 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,89 @@ +# 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" + +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/continuous-integration.yml b/.github/workflows/continuous-integration.yml new file mode 100644 index 0000000..e8b86ca --- /dev/null +++ b/.github/workflows/continuous-integration.yml @@ -0,0 +1,171 @@ +# https://help.github.com/en/categories/automating-your-workflow-with-github-actions + +# file-version: 1.0 + +name: "Continuous Integration" + +on: + - pull_request + - push + +permissions: + contents: read + security-events: read + issues: read + pull-requests: read + checks: read + +concurrency: + group: "${{ github.event_name }}-${{ github.workflow }}-${{ github.head_ref || github.run_id }}-${{ github.ref }}" + cancel-in-progress: true + +jobs: + validate-all: + name: "Validate Project" + + uses: "mimmi20/ci/.github/workflows/validate-all.yml@master" + with: + skip-validate-yaml: false + skip-validate-md: false + + install: + name: "Install Project" + + needs: + - "validate-all" + + uses: "mimmi20/ci/.github/workflows/install-node.yml@master" + with: + skip-nasm-install: true + skip-libimagequant-install: true + skip-libpng-install: true + skip-librabbitmq-install: true + skip-libgif-install: true + + analytics: + name: "Project Analysis" + + permissions: + security-events: write + id-token: write + contents: read + issues: read + pull-requests: read + checks: read + + needs: "install" + + uses: "mimmi20/ci/.github/workflows/analytics-node.yml@master" + with: + skip-eslint: false + skip-stylelint: true + skip-prettier: false + skip-nasm-install: true + skip-libimagequant-install: true + skip-libpng-install: true + skip-librabbitmq-install: true + skip-libgif-install: true + skip-oxlint: true + + tests: + name: "Node.js ${{ matrix.node-version }} on ${{ matrix.operating-system }}" + + needs: + - "analytics" + + runs-on: "${{ matrix.operating-system }}" + + strategy: + fail-fast: false + matrix: + node-version: + - "22" + - "24" + - "25" + operating-system: + - "ubuntu-latest" + + 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 + + - name: "Use Node.js ${{ matrix.node-version }}" + uses: "actions/setup-node@v6" + with: + node-version: "${{ matrix.node-version }}" + + - name: "install dependencies" + run: "npm install" + + - name: "run tests" + run: "npm test" + +# build-with-vite: +# name: "Build with vite" +# +# needs: "tests" +# +# runs-on: "${{ matrix.operating-system }}" +# +# timeout-minutes: 10 +# +# continue-on-error: false +# +# strategy: +# fail-fast: false +# +# matrix: +# operating-system: +# - "ubuntu-latest" +# +# node-version: +# - "22" +# +# steps: +# - name: "Checkout" +# uses: "actions/checkout@v4" +# with: +# # Disabling shallow clone is recommended for improving relevancy of reporting +# fetch-depth: 0 +# lfs: false +# persist-credentials: false +# +# - name: "install libimagequant-dev" +# run: "sudo apt-get install libimagequant-dev -y" +# +# - name: "Use Node.js ${{ matrix.node-version }}" +# uses: "actions/setup-node@v4" +# with: +# node-version: "${{ matrix.node-version }}" +# +# - name: "install dependencies" +# run: "npm install" +# +# - name: "build" +# run: "npm run build" + + # This is a meta job to avoid to have to constantly change the protection rules + # whenever we touch the matrix. + tests-status: + name: "CI Status" + + runs-on: "ubuntu-latest" + + if: always() + + needs: + - "tests" + + steps: + - name: Failing run + if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled') }} + run: exit 1 + + - name: Successful run + if: ${{ !(contains(needs.*.result, 'failure')) && !(contains(needs.*.result, 'skipped')) && !(contains(needs.*.result, 'cancelled')) }} + run: exit 0 diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..605aa12 --- /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@master" + + # 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: Failing run + if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled') }} + run: exit 1 + + - name: Successful run + if: ${{ !(contains(needs.*.result, 'failure')) && !(contains(needs.*.result, 'skipped')) && !(contains(needs.*.result, 'cancelled')) }} + run: exit 0 diff --git a/.github/workflows/labels.yml b/.github/workflows/labels.yml new file mode 100644 index 0000000..83b934f --- /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@master" + 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: Failing run + if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled') }} + run: exit 1 + + - name: Successful run + if: ${{ !(contains(needs.*.result, 'failure')) && !(contains(needs.*.result, 'skipped')) && !(contains(needs.*.result, 'cancelled')) }} + run: exit 0 diff --git a/.github/workflows/lint-workflow-files.yml b/.github/workflows/lint-workflow-files.yml new file mode 100644 index 0000000..c9fa1b1 --- /dev/null +++ b/.github/workflows/lint-workflow-files.yml @@ -0,0 +1,39 @@ +name: "Lint GitHub Actions workflows" + +on: + pull_request: + paths: + - ".github/**" + +permissions: + contents: read + +jobs: + actionlint: + uses: "mimmi20/ci/.github/workflows/lint-actions.yml@master" + with: + skip-nasm-install: true + skip-libimagequant-install: true + skip-libpng-install: true + 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') || contains(needs.*.result, 'skipped') || contains(needs.*.result, 'cancelled') }} + run: exit 1 + + - name: Successful run + if: ${{ !(contains(needs.*.result, 'failure')) && !(contains(needs.*.result, 'skipped')) && !(contains(needs.*.result, 'cancelled')) }} + 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..d17d851 --- /dev/null +++ b/.github/workflows/lock-closed-issues.yml @@ -0,0 +1,20 @@ +# 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 Issues" + uses: "mimmi20/ci/.github/workflows/lock-issues.yml@master" + permissions: + issues: write diff --git a/.github/workflows/reactions.yml b/.github/workflows/reactions.yml new file mode 100644 index 0000000..b6f1aed --- /dev/null +++ b/.github/workflows/reactions.yml @@ -0,0 +1,26 @@ +# 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: + name: "Comments" + uses: "mimmi20/ci/.github/workflows/reaction-comments.yml@master" diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 0000000..ccfc38e --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,34 @@ +# 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 + + uses: "mimmi20/ci/.github/workflows/draft-release.yml@master" 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/.markdown-link-check.json b/.markdown-link-check.json new file mode 100644 index 0000000..084971c --- /dev/null +++ b/.markdown-link-check.json @@ -0,0 +1,7 @@ +{ + "timeout": "30s", + "retryOn429": true, + "retryCount": 5, + "fallbackRetryDelay": "30s", + "aliveStatusCodes": [200, 206] +} diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 0000000..6160658 --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,236 @@ +# Example markdownlint configuration with all properties set to their default value + +# Default state for all rules +default: true + +# Path to configuration file to extend +extends: null + +# MD001/heading-increment : Heading levels should only increment by one level at a time : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md001.md +MD001: true + +# MD003/heading-style : Heading style : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md003.md +MD003: + # Heading style + style: "consistent" + +# MD004/ul-style : Unordered list style : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md004.md +MD004: + # List style + style: "consistent" + +# MD005/list-indent : Inconsistent indentation for list items at the same level : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md005.md +MD005: true + +# MD007/ul-indent : Unordered list indentation : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md007.md +MD007: + # Spaces for indent + indent: 2 + # Whether to indent the first level of the list + start_indented: false + # Spaces for first level indent (when start_indented is set) + start_indent: 2 + +# MD009/no-trailing-spaces : Trailing spaces : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md009.md +MD009: false + +# MD010/no-hard-tabs : Hard tabs : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md010.md +MD010: + # Include code blocks + code_blocks: true + # Fenced code languages to ignore + ignore_code_languages: [] + # Number of spaces for each hard tab + spaces_per_tab: 4 + +# MD011/no-reversed-links : Reversed link syntax : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md011.md +MD011: true + +# MD012/no-multiple-blanks : Multiple consecutive blank lines : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md012.md +MD012: + # Consecutive blank lines + maximum: 1 + +# MD013/line-length : Line length : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md013.md +MD013: false + +# MD014/commands-show-output : Dollar signs used before commands without showing output : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md014.md +MD014: true + +# MD018/no-missing-space-atx : No space after hash on atx style heading : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md018.md +MD018: true + +# MD019/no-multiple-space-atx : Multiple spaces after hash on atx style heading : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md019.md +MD019: true + +# MD020/no-missing-space-closed-atx : No space inside hashes on closed atx style heading : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md020.md +MD020: true + +# MD021/no-multiple-space-closed-atx : Multiple spaces inside hashes on closed atx style heading : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md021.md +MD021: true + +# MD022/blanks-around-headings : Headings should be surrounded by blank lines : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md022.md +MD022: + # Blank lines above heading + lines_above: 1 + # Blank lines below heading + lines_below: 1 + +# MD023/heading-start-left : Headings must start at the beginning of the line : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md023.md +MD023: true + +# MD024/no-duplicate-heading : Multiple headings with the same content : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md024.md +MD024: false + +# MD025/single-title/single-h1 : Multiple top-level headings in the same document : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md025.md +MD025: + # Heading level + level: 1 + # RegExp for matching title in front matter + front_matter_title: "^\\s*title\\s*[:=]" + +# MD026/no-trailing-punctuation : Trailing punctuation in heading : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md026.md +MD026: + # Punctuation characters + punctuation: ".,;:!。,;:!" + +# MD027/no-multiple-space-blockquote : Multiple spaces after blockquote symbol : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md027.md +MD027: true + +# MD028/no-blanks-blockquote : Blank line inside blockquote : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md028.md +MD028: true + +# MD029/ol-prefix : Ordered list item prefix : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md029.md +MD029: + # List style + style: "one_or_ordered" + +# MD030/list-marker-space : Spaces after list markers : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md030.md +MD030: + # Spaces for single-line unordered list items + ul_single: 1 + # Spaces for single-line ordered list items + ol_single: 1 + # Spaces for multi-line unordered list items + ul_multi: 1 + # Spaces for multi-line ordered list items + ol_multi: 1 + +# MD031/blanks-around-fences : Fenced code blocks should be surrounded by blank lines : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md031.md +MD031: + # Include list items + list_items: true + +# MD032/blanks-around-lists : Lists should be surrounded by blank lines : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md032.md +MD032: true + +# MD033/no-inline-html : Inline HTML : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md033.md +MD033: + # Allowed elements + allowed_elements: [] + +# MD034/no-bare-urls : Bare URL used : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md034.md +MD034: true + +# MD035/hr-style : Horizontal rule style : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md035.md +MD035: + # Horizontal rule style + style: "consistent" + +# MD036/no-emphasis-as-heading : Emphasis used instead of a heading : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md036.md +MD036: + # Punctuation characters + punctuation: ".,;:!?。,;:!?" + +# MD037/no-space-in-emphasis : Spaces inside emphasis markers : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md037.md +MD037: true + +# MD038/no-space-in-code : Spaces inside code span elements : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md038.md +MD038: true + +# MD039/no-space-in-links : Spaces inside link text : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md039.md +MD039: true + +# MD040/fenced-code-language : Fenced code blocks should have a language specified : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md040.md +MD040: + # List of languages + allowed_languages: [] + # Require language only + language_only: false + +# MD041/first-line-heading/first-line-h1 : First line in a file should be a top-level heading : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md041.md +MD041: + # Heading level + level: 1 + # RegExp for matching title in front matter + front_matter_title: "^\\s*title\\s*[:=]" + +# MD042/no-empty-links : No empty links : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md042.md +MD042: true + +# MD043/required-headings : Required heading structure : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md043.md +MD043: false + +# MD044/proper-names : Proper names should have the correct capitalization : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md044.md +MD044: + # List of proper names + names: [] + # Include code blocks + code_blocks: true + # Include HTML elements + html_elements: true + +# MD045/no-alt-text : Images should have alternate text (alt text) : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md045.md +MD045: true + +# MD046/code-block-style : Code block style : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md046.md +MD046: + # Block style + style: "consistent" + +# MD047/single-trailing-newline : Files should end with a single newline character : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md047.md +MD047: true + +# MD048/code-fence-style : Code fence style : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md048.md +MD048: + # Code fence style + style: "consistent" + +# MD049/emphasis-style : Emphasis style : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md049.md +MD049: + # Emphasis style + style: "consistent" + +# MD050/strong-style : Strong style : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md050.md +MD050: + # Strong style + style: "consistent" + +# MD051/link-fragments : Link fragments should be valid : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md051.md +MD051: true + +# MD052/reference-links-images : Reference links and images should use a label that is defined : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md052.md +MD052: + # Include shortcut syntax + shortcut_syntax: false + +# MD053/link-image-reference-definitions : Link and image reference definitions should be needed : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md053.md +MD053: + # Ignored definitions + ignored_definitions: + - "//" + +# MD054/link-image-style : Link and image style : https://github.com/DavidAnson/markdownlint/blob/v0.32.1/doc/md054.md +MD054: + # Allow autolinks + autolink: true + # Allow inline links and images + inline: true + # Allow full reference links and images + full: true + # Allow collapsed reference links and images + collapsed: true + # Allow shortcut reference links and images + shortcut: true + # Allow URLs as inline links + url_inline: true diff --git a/.mega-linter.yml b/.mega-linter.yml new file mode 100644 index 0000000..38de616 --- /dev/null +++ b/.mega-linter.yml @@ -0,0 +1,27 @@ +VALIDATE_ALL_CODEBASE: true +LOG_LEVEL: INFO +PRINT_ALPACA: false +SHOW_ELAPSED_TIME: true +FILTER_REGEX_EXCLUDE: (vendor/) +ENABLE: + - EDITORCONFIG + - MARKDOWN + - YAML +DISABLE: + - SPELL + #- COPYPASTE +DISABLE_LINTERS: + - MARKDOWN_MARKDOWN_TABLE_FORMATTER + - MARKDOWN_MARKDOWN_LINK_CHECK + - EDITORCONFIG_EDITORCONFIG_CHECKER + - YAML_V8R +EDITORCONFIG_EDITORCONFIG_CHECKER_FILTER_REGEX_EXCLUDE: "(\\.idea|\\.editorconfig|\\.github|\\.phpunit\\.result\\.cache|LICENSE)" +JSON_PRETTIER_FILTER_REGEX_EXCLUDE: "(composer\\.json)" +MARKDOWN_MARKDOWNLINT_FILTER_REGEX_EXCLUDE: "(\\.github)" +MARKDOWN_MARKDOWN_LINK_CHECK_FILTER_REGEX_EXCLUDE: "(\\.github)" +PHP_PHPCS_CONFIG_FILE: "phpcs.xml" +PHP_PHPCS_ARGUMENTS: "-n" +PHP_PHPSTAN_CONFIG_FILE: "phpstan.neon" +PHP_PHPSTAN_FILTER_REGEX_INCLUDE: "(src)" +YAML_YAMLLINT_FILTER_REGEX_EXCLUDE: "(\\.github)" +YAML_PRETTIER_FILTER_REGEX_EXCLUDE: "(\\.github)" 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/.yamllint.yml b/.yamllint.yml new file mode 100644 index 0000000..c167ccd --- /dev/null +++ b/.yamllint.yml @@ -0,0 +1,23 @@ +--- + +yaml-files: + - '*.yaml' + - '*.yml' + - '.yamllint' + +rules: + brackets: + forbid: false + min-spaces-inside: 0 + max-spaces-inside: 1 + min-spaces-inside-empty: 0 + max-spaces-inside-empty: 1 + + document-start: disable + + empty-lines: + max: 1 + + line-length: disable + + new-line-at-end-of-file: enable diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..9c924a4 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,24 @@ +codecov: + require_ci_to_pass: false + notify: + wait_for_ci: false + +coverage: + status: + project: + default: # default is the status check's name, not default settings + target: 100% + threshold: 1% + if_ci_failed: success #success, failure, error, ignore + informational: true + only_pulls: false + patch: + default: + informational: true + +comment: + layout: "reach, diff, flags, files" + behavior: default + require_changes: false # if true: only post the comment if coverage changes + require_base: false # [yes :: must have a base report to post] + require_head: false # [yes :: must have a head report to post] diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..690f954 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,440 @@ +import eslint from '@eslint/js'; +import tseslint from 'typescript-eslint'; +import prettierConfig from 'eslint-config-prettier'; +import prettier from 'eslint-plugin-prettier'; +import globals from 'globals'; +import pluginPromise from 'eslint-plugin-promise'; +import depend from 'eslint-plugin-depend'; +import { importX } from 'eslint-plugin-import-x'; + +export default tseslint.config( + eslint.configs.recommended, + prettierConfig, + ...tseslint.configs.recommended, + pluginPromise.configs['flat/recommended'], + importX.flatConfigs.recommended, + importX.flatConfigs.typescript, + { + rules: { + 'for-direction': 'error', + 'getter-return': 'error', + 'no-async-promise-executor': 'error', + 'no-await-in-loop': 'error', + 'no-compare-neg-zero': 'error', + 'no-cond-assign': 'error', + 'no-constant-condition': 'error', + 'no-control-regex': 'error', + 'no-debugger': 'error', + 'no-dupe-args': 'error', + 'no-dupe-else-if': 'error', + 'no-dupe-keys': 'error', + 'no-duplicate-case': 'error', + 'no-empty-character-class': 'error', + 'no-empty': [ + 'error', + { + allowEmptyCatch: true, + }, + ], + 'no-empty-static-block': 'error', + 'no-ex-assign': 'error', + 'no-extra-boolean-cast': 'error', + 'no-func-assign': 'error', + 'no-import-assign': 'error', + 'no-inner-declarations': 'error', + 'no-invalid-regexp': 'error', + 'no-loss-of-precision': 'error', + 'no-misleading-character-class': 'error', + 'no-obj-calls': 'error', + 'no-promise-executor-return': 'error', + 'no-prototype-builtins': 'error', + 'no-regex-spaces': 'error', + 'no-setter-return': 'error', + 'no-sparse-arrays': 'error', + 'no-template-curly-in-string': 'error', + 'no-unreachable': 'error', + 'no-unreachable-loop': 'error', + 'no-unsafe-finally': 'error', + 'no-unsafe-negation': [ + 'error', + { + enforceForOrderingRelations: true, + }, + ], + 'no-unsafe-optional-chaining': [ + 'error', + { + disallowArithmeticOperators: true, + }, + ], + 'no-useless-backreference': 'error', + 'use-isnan': 'error', + 'valid-typeof': [ + 'error', + { + requireStringLiterals: false, + }, + ], + 'no-unexpected-multiline': 'error', + 'accessor-pairs': [ + 'error', + { + enforceForClassMembers: true, + }, + ], + 'array-callback-return': [ + 'error', + { + allowImplicit: true, + }, + ], + 'block-scoped-var': 'error', + complexity: ['warn', { max: 24, variant: 'modified' }], + curly: 'error', + 'default-case': 'error', + 'default-case-last': 'error', + eqeqeq: 'error', + 'grouped-accessor-pairs': ['error', 'getBeforeSet'], + 'guard-for-in': 'error', + 'no-alert': 'error', + 'no-caller': 'error', + 'no-case-declarations': 'error', + 'no-constructor-return': 'error', + 'no-else-return': [ + 'error', + { + allowElseIf: false, + }, + ], + 'no-empty-pattern': 'error', + 'no-eq-null': 'error', + 'no-eval': 'error', + 'no-extend-native': 'error', + 'no-extra-bind': 'error', + 'no-extra-label': 'error', + 'no-fallthrough': 'error', + 'no-global-assign': 'error', + 'no-implicit-coercion': 'error', + 'no-implicit-globals': 'error', + 'no-implied-eval': 'error', + 'no-iterator': 'error', + 'no-labels': 'error', + 'no-lone-blocks': 'error', + 'no-multi-str': 'error', + 'no-new-func': 'error', + 'no-new-wrappers': 'error', + 'no-nonoctal-decimal-escape': 'error', + 'no-object-constructor': 'error', + 'no-new': 'error', + 'no-octal-escape': 'error', + 'no-octal': 'error', + 'no-proto': 'error', + 'no-return-assign': ['error', 'always'], + 'no-return-await': 'error', + 'no-script-url': 'error', + 'no-self-assign': [ + 'error', + { + props: true, + }, + ], + 'no-self-compare': 'error', + 'no-sequences': 'error', + 'no-throw-literal': 'error', + 'no-unmodified-loop-condition': 'error', + 'no-unused-expressions': [ + 'error', + { + enforceForJSX: true, + }, + ], + 'no-unused-labels': 'error', + 'no-useless-call': 'error', + 'no-useless-catch': 'error', + 'no-useless-concat': 'error', + 'no-useless-escape': 'error', + 'no-useless-return': 'error', + 'no-void': 'error', + 'no-with': 'error', + 'prefer-promise-reject-errors': [ + 'error', + { + allowEmptyReject: true, + }, + ], + 'prefer-regex-literals': [ + 'error', + { + disallowRedundantWrapping: true, + }, + ], + radix: 'error', + yoda: 'error', + 'no-delete-var': 'error', + 'no-label-var': 'error', + 'no-restricted-globals': [ + 'error', + 'event', + // TODO: Enable this in 2025. + // { + // name: 'Buffer', + // message: 'Use Uint8Array instead. See: https://sindresorhus.com/blog/goodbye-nodejs-buffer', + // }, + { + name: 'atob', + message: 'This API is deprecated. Use https://github.com/sindresorhus/uint8array-extras instead.', + }, + { + name: 'btoa', + message: 'This API is deprecated. Use https://github.com/sindresorhus/uint8array-extras instead.', + }, + ], + 'no-shadow-restricted-names': 'error', + 'no-undef-init': 'error', + 'no-unused-vars': [ + 'error', + { + vars: 'all', + varsIgnorePattern: /^_/.source, + args: 'after-used', + ignoreRestSiblings: true, + argsIgnorePattern: /^_/.source, + caughtErrors: 'all', + caughtErrorsIgnorePattern: /^_$/.source, + }, + ], + 'no-buffer-constructor': 'error', + 'no-restricted-imports': [ + 'error', + 'domain', + 'freelist', + 'smalloc', + 'punycode', + 'sys', + 'querystring', + 'colors', + // TODO: Enable this in 2025. + // { + // name: 'buffer', + // message: 'Use Uint8Array instead. See: https://sindresorhus.com/blog/goodbye-nodejs-buffer', + // }, + // { + // name: 'node:buffer', + // message: 'Use Uint8Array instead. See: https://sindresorhus.com/blog/goodbye-nodejs-buffer', + // }, + ], + camelcase: [ + 'warn', + { + properties: 'always', + }, + ], + 'func-name-matching': [ + 'error', + { + considerPropertyDescriptor: true, + }, + ], + 'func-names': ['error', 'never'], + 'max-depth': 'warn', + 'max-lines': [ + 'warn', + { + max: 1500, + skipComments: true, + }, + ], + 'max-nested-callbacks': ['warn', 4], + 'no-array-constructor': 'error', + 'no-bitwise': 'error', + 'no-lonely-if': 'error', + 'no-multi-assign': 'error', + 'no-negated-condition': 'error', + 'no-unneeded-ternary': 'error', + 'one-var': ['error', 'never'], + 'prefer-exponentiation-operator': 'error', + 'prefer-object-spread': 'error', + 'unicode-bom': ['error', 'never'], + 'arrow-body-style': 'error', + 'constructor-super': 'error', + 'no-class-assign': 'error', + 'no-const-assign': 'error', + 'no-constant-binary-expression': 'error', + 'no-dupe-class-members': 'error', + 'no-new-native-nonconstructor': 'error', + 'no-this-before-super': 'error', + 'no-unassigned-vars': 'error', + 'no-useless-computed-key': [ + 'error', + { + enforceForClassMembers: true, + }, + ], + 'no-useless-constructor': 'error', + 'no-useless-rename': 'error', + 'no-var': 'error', + 'prefer-arrow-callback': [ + 'error', + { + allowNamedFunctions: true, + }, + ], + 'prefer-const': [ + 'error', + { + destructuring: 'all', + }, + ], + 'prefer-destructuring': [ + 'error', + { + // `array` is disabled because it forces destructuring on + // stupid stuff like `foo.bar = process.argv[2];` + VariableDeclarator: { + array: false, + object: true, + }, + AssignmentExpression: { + array: false, + + // Disabled because object assignment destructuring requires parens wrapping: + // `let foo; ({foo} = object);` + object: false, + }, + }, + { + enforceForRenamedProperties: false, + }, + ], + 'prefer-numeric-literals': 'error', + 'prefer-rest-params': 'error', + 'prefer-spread': 'error', + 'require-yield': 'error', + 'symbol-description': 'error', + 'import-x/no-dynamic-require': 'warn', + 'import-x/no-nodejs-modules': 'warn', + }, + languageOptions: { + globals: { + ...globals.es2021, + ...globals.nodeBuiltin, + }, + ecmaVersion: 'latest', + parserOptions: { + ecmaFeatures: { + jsx: false, + }, + }, + }, + linterOptions: { + reportUnusedDisableDirectives: 'error', + reportUnusedInlineConfigs: 'error', + }, + }, + { + plugins: { + prettier, + }, + rules: { + 'prettier/prettier': 'error', + 'array-callback-return': 'error', + 'no-empty': [ + 'error', + { + allowEmptyCatch: true, + }, + ], + 'no-lonely-if': 'error', + 'no-var': 'error', + 'prefer-const': [ + 'error', + { + destructuring: 'all', + ignoreReadBeforeAssign: false, + }, + ], + 'prefer-destructuring': [ + 'error', + { + object: true, + array: false, + }, + ], + 'prefer-spread': 'error', + radix: 'error', + strict: 'error', + quotes: ['error', 'single'], + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-unsafe-function-type': 'off', + }, + languageOptions: { + globals: { + ...globals.es2021, + ...globals.nodeBuiltin, + }, + ecmaVersion: 'latest', + }, + linterOptions: { + reportUnusedDisableDirectives: 'error', + reportUnusedInlineConfigs: 'error', + }, + }, + { + plugins: { + depend, + }, + rules: { + 'depend/ban-dependencies': [ + 'error', + { + presets: ['native', 'microutilities', 'preferred'], + }, + ], + }, + }, + { + files: ['public/**/*.{mjs,ts}', 'eslint.config.mjs', 'postcss.config.mjs', 'prettier.config.mjs', 'stylelint.config.mjs', 'vite.config.ts', 'cypress.config.ts'], + languageOptions: { + sourceType: 'module', + globals: { + ...globals.es2021, + ...globals.nodeBuiltin, + }, + ecmaVersion: 'latest', + }, + linterOptions: { + reportUnusedDisableDirectives: 'error', + reportUnusedInlineConfigs: 'error', + }, + rules: { + 'object-shorthand': [ + 'error', + 'always', + { + avoidExplicitReturnArrows: true, + }, + ], + 'import/no-unresolved': 'off', + }, + }, + { + ignores: [ + 'public/**/*.d.ts', + 'public/js/vendor/*.{js,mjs,cjs,ts}', + 'public/js/vendor/**/*.{js,mjs,cjs,ts}', + 'public/dist/**/*.{js,mjs,cjs,ts}', + 'public/js/**/*.js', + 'node_modules/**/*.{js,mjs,cjs,ts}', + 'vendor-bin/**/*.{js,mjs,cjs,ts}', + 'vendor/**/*.{js,mjs,cjs,ts}', + '.build/**/*.{js,mjs,cjs,ts}', + '.reports/**/*.{js,mjs,cjs,ts}', + 'cypress/**/*.js', + 'cypress/**/*.d.ts', + 'vendor/**/*.*', + 'infection.json5', + 'public/dist/**/*.*', + '.github/**/*.*', + ], + } +); diff --git a/index.js b/index.js index 7ac74fb..29ffa3f 100644 --- a/index.js +++ b/index.js @@ -1,45 +1,48 @@ -'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 => { - opts = Object.assign({}, opts); - - if (!Buffer.isBuffer(opts.input)) { - return Promise.reject(new Error('Input is required')); - } - - if (typeof opts.bin !== 'string') { - return Promise.reject(new Error('Binary is required')); - } - - if (!Array.isArray(opts.args)) { - return Promise.reject(new Error('Arguments are required')); - } - - const inputPath = opts.inputPath || tempfile(); - const outputPath = opts.outputPath || tempfile(); - - opts.args = opts.args.map(x => x === input ? inputPath : x === output ? outputPath : x); - - const promise = fsP.writeFile(inputPath, opts.input) - .then(() => execa(opts.bin, opts.args)) - .then(() => fsP.readFile(outputPath)); - - return pFinally(promise, () => Promise.all([ - rmP(inputPath), - rmP(outputPath) - ])); +import { writeFile, readFile, rm } from 'node:fs/promises'; +import tempfile from 'tempfile'; +import exec from 'nanoexec'; + +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; }; -module.exports.input = input; -module.exports.output = output; +const func = async (opts) => { + opts = { ...opts }; + + if (!Buffer.isBuffer(opts.input)) { + return Promise.reject(new Error('Input is required')); + } + + if (typeof opts.bin !== 'string') { + return Promise.reject(new Error('Binary is required')); + } + + if (!Array.isArray(opts.args)) { + return Promise.reject(new Error('Arguments are required')); + } + + const inputPath = opts.inputPath || tempfile(); + const outputPath = opts.outputPath || tempfile(); + + opts.args = opts.args.map((x) => (x === input ? inputPath : x === output ? outputPath : x)); + + const promise = writeFile(inputPath, opts.input) + .then(() => exec(opts.bin, opts.args)) + .then(() => readFile(outputPath)); + + return pFinally(promise, () => Promise.all([rm(inputPath, { recursive: true, force: true }), rm(outputPath, { recursive: true, force: true })])); +}; + +export default func; diff --git a/package.json b/package.json index 2c3b6d9..9471459 100644 --- a/package.json +++ b/package.json @@ -1,39 +1,51 @@ { - "name": "exec-buffer", - "version": "3.2.0", - "description": "Run a buffer through a child process", - "license": "MIT", - "repository": "kevva/exec-buffer", - "author": { - "name": "Kevin Mårtensson", - "email": "kevinmartensson@gmail.com", - "url": "https://github.com/kevva" - }, - "engines": { - "node": ">=4" - }, - "scripts": { - "test": "xo && ava" - }, - "files": [ - "index.js" - ], - "keywords": [ - "buffer", - "exec" - ], - "dependencies": { - "execa": "^0.7.0", - "p-finally": "^1.0.0", - "pify": "^3.0.0", - "rimraf": "^2.5.4", - "tempfile": "^2.0.0" - }, - "devDependencies": { - "ava": "*", - "gifsicle": "^3.0.4", - "is-gif": "^1.0.0", - "path-exists": "^3.0.0", - "xo": "*" - } + "name": "exec-buffer", + "version": "3.2.0", + "description": "Run a buffer through a child process", + "license": "MIT", + "repository": "kevva/exec-buffer", + "author": { + "name": "Kevin Mårtensson", + "email": "kevinmartensson@gmail.com", + "url": "https://github.com/kevva" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >= 24.0.0" + }, + "scripts": { + "prettier": "prettier --config prettier.config.mjs --check index.js test.js eslint.config.mjs prettier.config.mjs", + "prettier-fix": "prettier --config prettier.config.mjs --write index.js test.js eslint.config.mjs prettier.config.mjs", + "eslint": "eslint . --config eslint.config.mjs", + "eslint-fix": "eslint . --config eslint.config.mjs --fix", + "test": "ava" + }, + "files": [ + "index.js" + ], + "keywords": [ + "buffer", + "exec" + ], + "dependencies": { + "nanoexec": "^1.1.0", + "tempfile": "^6.0.1" + }, + "devDependencies": { + "@eslint/js": "^9.39.4", + "ava": "^6.4.1", + "eslint": "^9.39.4", + "eslint-config-prettier": "^10.1.8", + "eslint-import-resolver-typescript": "^4.4.5", + "eslint-plugin-depend": "^1.5.0", + "eslint-plugin-import-x": "^4.16.2", + "eslint-plugin-prettier": "^5.5.6", + "eslint-plugin-promise": "^7.3.0", + "gifsicle": "^7.0.1", + "is-gif": "^4.0.1", + "prettier": "^3.8.3", + "typescript": "^6.0.3", + "typescript-eslint": "^8.60.1" + }, + "type": "module", + "exports": "./index.js" } diff --git a/prettier.config.mjs b/prettier.config.mjs new file mode 100644 index 0000000..08f05f0 --- /dev/null +++ b/prettier.config.mjs @@ -0,0 +1,15 @@ +/** @type {import('prettier').Config} */ +const config = { + arrowParens: 'always', + bracketSpacing: true, + printWidth: 200, + tabWidth: 2, + trailingComma: 'es5', + useTabs: false, + singleAttributePerLine: true, + singleQuote: true, + endOfLine: 'lf', + semi: true, +}; + +export default config; diff --git a/readme.md b/readme.md index da36e72..8738eb6 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) @@ -56,14 +53,16 @@ Arguments to run the binary with. ##### inputPath -Type: `string`
+Type: `string` + Default: `tempfile()` Where `input` will be written to. In most cases you don't need to set this. ##### outputPath -Type: `string`
+Type: `string` + Default: `tempfile()` Where output file will be written to. In most cases you don't need to set this. @@ -76,7 +75,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..a232f98 100644 --- a/test.js +++ b/test.js @@ -1,39 +1,63 @@ -import fs from 'fs'; -import path from 'path'; +import { readFile, access } 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 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; - t.truthy(input); - t.truthy(output); +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 data = await m({ - input: buf, - bin: gifsicle, - args: ['-o', m.output, m.input] - }); +test('return a optimized buffer', async (t) => { + const buf = await readFile(path.join(__dirname, 'fixture.gif')); + const data = await m({ + input: buf, + bin: gifsicle, + args: ['-o', output, input], + }); - t.true(data.length < buf.length); - t.true(isGif(data)); + t.true(data.length < buf.length); + t.true(isGif(data)); }); -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] - })); - - t.is(err.code, 'ENOENT'); - t.false(await pathExists(err.spawnargs[0])); - t.false(await pathExists(err.spawnargs[1])); +test('remove temporary files', async (t) => { + // 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'); + + if (err.cause instanceof Error) { + t.false( + await access(err.cause.spawnargs[0]).then( + () => true, + () => false + ) + ); + t.false( + await access(err.cause.spawnargs[1]).then( + () => true, + () => false + ) + ); + } + } });