├── .github └── workflows │ ├── ci.yaml │ ├── codeql.yml │ ├── npm-publish-from-pr.yml │ └── osv-scanner.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── mod.ts ├── package-lock.json ├── package.json ├── src ├── main │ ├── ts │ │ ├── error.ts │ │ ├── index.ts │ │ ├── mixin │ │ │ ├── kill.ts │ │ │ ├── pipe.ts │ │ │ └── timeout.ts │ │ ├── spawn.ts │ │ ├── util.ts │ │ ├── x.ts │ │ └── zurk.ts │ └── typedoc │ │ └── typedoc.json ├── scripts │ ├── build-jsr.mjs │ ├── build.mjs │ ├── object.polyfill.cjs │ └── test.mjs └── test │ ├── fixtures │ └── foo.json │ ├── js │ ├── index.test.cjs │ └── index.test.mjs │ ├── lint │ └── .eslintrc.json │ ├── smoke │ ├── invoke.test.cjs │ └── invoke.test.mjs │ └── ts │ ├── error.test.ts │ ├── index.test.ts │ ├── spawn.test.ts │ ├── util.test.ts │ ├── x.test.ts │ └── zurk.test.ts ├── target ├── cjs │ ├── cjslib.cjs │ ├── error.cjs │ ├── index.cjs │ ├── spawn.cjs │ ├── util.cjs │ └── zurk.cjs ├── dts │ ├── error.d.ts │ ├── index.d.ts │ ├── mixin │ │ ├── kill.d.ts │ │ ├── pipe.d.ts │ │ └── timeout.d.ts │ ├── spawn.d.ts │ ├── util.d.ts │ ├── x.d.ts │ └── zurk.d.ts └── esm │ ├── error.mjs │ ├── index.mjs │ ├── spawn.mjs │ ├── util.mjs │ └── zurk.mjs └── tsconfig.json /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | # This is a Github Workflow that runs tests on any push or pull request. 2 | # If the tests pass and this is a push to the master branch it also runs Semantic Release. 3 | name: CI 4 | on: [push, pull_request] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - name: Use Node.js 20.x 11 | uses: actions/setup-node@v4 12 | with: 13 | node-version: 20.x 14 | cache: 'npm' 15 | - run: npm ci 16 | - run: npm run build 17 | - uses: actions/upload-artifact@v4 18 | with: 19 | name: build 20 | path: | 21 | target 22 | package.json 23 | jsr.json 24 | retention-days: 1 25 | 26 | push: 27 | name: Push ${{ github.ref }} 28 | needs: build 29 | if: github.event_name == 'push' 30 | runs-on: ubuntu-latest 31 | permissions: 32 | checks: read 33 | statuses: write 34 | contents: write 35 | packages: write 36 | id-token: write 37 | steps: 38 | - name: Checkout 39 | uses: actions/checkout@v4 40 | with: 41 | fetch-depth: ${{ github.ref == 'refs/heads/main' && '0' || '1' }} 42 | - name: Setup Node.js 43 | uses: actions/setup-node@v4 44 | with: 45 | node-version: 20 46 | cache: 'npm' 47 | - uses: actions/download-artifact@v4 48 | with: 49 | name: build 50 | - name: Fetch deps 51 | run: npm ci 52 | - name: Run tests 53 | run: npm run test 54 | 55 | # - name: Codeclimate 56 | # if: github.ref == 'refs/heads/main' 57 | # uses: paambaati/codeclimate-action@v4.0.0 58 | # env: 59 | # CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }} 60 | # with: 61 | # coverageLocations: | 62 | # ${{github.workspace}}/target/coverage/lcov.info:lcov 63 | 64 | - name: Semantic Release (npm) 65 | if: github.ref == 'refs/heads/main' 66 | env: 67 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 68 | NPM_PROVENANCE: true 69 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 70 | GIT_BRANCH: 'main' 71 | GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }} 72 | GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }} 73 | GIT_COMMITTER_NAME: ${{ secrets.GIT_COMMITTER_NAME }} 74 | GIT_COMMITTER_EMAIL: ${{ secrets.GIT_COMMITTER_EMAIL }} 75 | run: npx zx-semrel 76 | 77 | pr: 78 | if: github.event_name == 'pull_request' 79 | needs: build 80 | name: PR (Node v${{ matrix.node-version }}, OS ${{ matrix.os }}) 81 | strategy: 82 | matrix: 83 | os: [ ubuntu-latest, windows-latest ] 84 | node-version: [ 6, 12, 18, 20, 22, 23 ] 85 | 86 | runs-on: ${{ matrix.os }} 87 | steps: 88 | - name: Checkout 89 | uses: actions/checkout@v4 90 | - name: Setup Node.js 91 | uses: actions/setup-node@v4 92 | with: 93 | node-version: ${{ matrix.node-version }} 94 | cache: 'npm' 95 | - uses: actions/download-artifact@v4 96 | with: 97 | name: build 98 | 99 | - name: Run all tests 100 | if: matrix.os == 'ubuntu-latest' && matrix.node-version == 20 101 | run: | 102 | npm ci 103 | npm run test 104 | - name: Run spawn tests (win32) 105 | if: matrix.os == 'windows-latest' && matrix.node-version == 20 106 | run: | 107 | npm ci 108 | node --loader ts-node/esm --experimental-specifier-resolution=node ./src/test/ts/spawn.test.ts 109 | timeout-minutes: 5 110 | 111 | - name: Run smoke:esm tests 112 | if: matrix.node-version >= 12 113 | run: npm run test:smoke:esm 114 | 115 | - name: Run smoke:cjs tests 116 | run: npm run test:smoke:cjs 117 | 118 | smoke-bun: 119 | runs-on: ubuntu-latest 120 | name: Smoke Bun 121 | needs: build 122 | steps: 123 | - uses: actions/checkout@v4 124 | - name: Setup Bun 125 | uses: antongolub/action-setup-bun@v1 126 | - uses: actions/download-artifact@v4 127 | with: 128 | name: build 129 | - run: | 130 | bun ./src/test/smoke/invoke.test.cjs 131 | bun ./src/test/smoke/invoke.test.mjs 132 | 133 | smoke-deno: 134 | runs-on: ubuntu-latest 135 | name: Smoke Deno ${{ matrix.deno-version }} 136 | needs: build 137 | strategy: 138 | matrix: 139 | deno-version: [ v1.x, v2.x ] 140 | steps: 141 | - uses: actions/checkout@v4 142 | - name: Setup Deno 143 | uses: denoland/setup-deno@v2 144 | with: 145 | deno-version: ${{ matrix.deno-version }} 146 | - run: deno install npm:types/node 147 | - uses: actions/download-artifact@v4 148 | with: 149 | name: build 150 | - run: deno test --allow-read --allow-sys --allow-env --allow-run ./src/test/smoke/invoke.test.mjs 151 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL Advanced" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | branches: [ "main" ] 19 | schedule: 20 | - cron: '17 20 * * 0' 21 | 22 | jobs: 23 | analyze: 24 | name: Analyze (${{ matrix.language }}) 25 | # Runner size impacts CodeQL analysis time. To learn more, please see: 26 | # - https://gh.io/recommended-hardware-resources-for-running-codeql 27 | # - https://gh.io/supported-runners-and-hardware-resources 28 | # - https://gh.io/using-larger-runners (GitHub.com only) 29 | # Consider using larger runners or machines with greater resources for possible analysis time improvements. 30 | runs-on: 'ubuntu-latest' 31 | permissions: 32 | # required for all workflows 33 | security-events: write 34 | 35 | # required to fetch internal or private CodeQL packs 36 | packages: read 37 | 38 | # only required for workflows in private repositories 39 | actions: read 40 | contents: read 41 | 42 | strategy: 43 | fail-fast: false 44 | matrix: 45 | include: 46 | - language: javascript-typescript 47 | build-mode: none 48 | # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' 49 | # Use `c-cpp` to analyze code written in C, C++ or both 50 | # Use 'java-kotlin' to analyze code written in Java, Kotlin or both 51 | # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both 52 | # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, 53 | # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. 54 | # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how 55 | # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages 56 | steps: 57 | - name: Checkout repository 58 | uses: actions/checkout@v4 59 | 60 | # Initializes the CodeQL tools for scanning. 61 | - name: Initialize CodeQL 62 | uses: github/codeql-action/init@v3 63 | with: 64 | languages: ${{ matrix.language }} 65 | build-mode: ${{ matrix.build-mode }} 66 | # If you wish to specify custom queries, you can do so here or in a config file. 67 | # By default, queries listed here will override any specified in a config file. 68 | # Prefix the list here with "+" to use these queries and those in the config file. 69 | 70 | # For more 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 71 | # queries: security-extended,security-and-quality 72 | 73 | # If the analyze step fails for one of the languages you are analyzing with 74 | # "We were unable to automatically build your code", modify the matrix above 75 | # to set the build mode to "manual" for that language. Then modify this step 76 | # to build your code. 77 | # ℹ️ Command-line programs to run using the OS shell. 78 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 79 | - if: matrix.build-mode == 'manual' 80 | shell: bash 81 | run: | 82 | echo 'If you are using a "manual" build mode for one or more of the' \ 83 | 'languages you are analyzing, replace this with the commands to build' \ 84 | 'your code, for example:' 85 | echo ' make bootstrap' 86 | echo ' make release' 87 | exit 1 88 | 89 | - name: Perform CodeQL Analysis 90 | uses: github/codeql-action/analyze@v3 91 | with: 92 | category: "/language:${{matrix.language}}" 93 | -------------------------------------------------------------------------------- /.github/workflows/npm-publish-from-pr.yml: -------------------------------------------------------------------------------- 1 | name: npm publish from PR 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | repo: 7 | description: 'source gh repo, like `google/zx`' 8 | required: true 9 | commit: 10 | description: 'commit id' 11 | required: true 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Use Node.js 22 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: 22 21 | 22 | - name: Checkout 23 | uses: actions/checkout@v4 24 | with: 25 | repository: ${{ github.event.inputs.repo }} 26 | ref: ${{ github.event.inputs.commit }} 27 | 28 | - name: Build 29 | run: | 30 | npm ci 31 | npm run build 32 | 33 | - uses: actions/upload-artifact@v4 34 | with: 35 | name: build-pr-${{ github.run_id }} 36 | path: | 37 | target 38 | package.json 39 | README.md 40 | buildstamp.json 41 | retention-days: 1 42 | 43 | publish: 44 | needs: build 45 | runs-on: ubuntu-latest 46 | permissions: 47 | checks: read 48 | statuses: write 49 | contents: write 50 | packages: write 51 | id-token: write 52 | steps: 53 | - uses: actions/setup-node@v4 54 | with: 55 | node-version: 22 56 | - uses: actions/download-artifact@v4 57 | with: 58 | name: build-pr-${{ github.run_id }} 59 | 60 | - run: echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" >> .npmrc 61 | env: 62 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 63 | 64 | - name: Notice 65 | if: github.event.inputs.repo != github.repository 66 | run: node -e "const fs = require('fs'); fs.writeFileSync('./README.md', process.env.NOTICE + fs.readFileSync('./README.md'))" 67 | env: 68 | NOTICE: "> ⚠ **This snapshot was built from the external source** \n> repo: ${{ github.event.inputs.repo }} \n> commit: ${{ github.event.inputs.commit }}\n" 69 | 70 | - run: | 71 | npm version $(node --eval="process.stdout.write(require('./package.json').version)")-pr.${{ github.run_id }} --no-git-tag-version 72 | npm publish --provenance --access=public --no-git-tag-version --tag pr 73 | -------------------------------------------------------------------------------- /.github/workflows/osv-scanner.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | # A sample workflow which sets up periodic OSV-Scanner scanning for vulnerabilities, 7 | # in addition to a PR check which fails if new vulnerabilities are introduced. 8 | # 9 | # For more examples and options, including how to ignore specific vulnerabilities, 10 | # see https://google.github.io/osv-scanner/github-action/ 11 | 12 | name: OSV-Scanner 13 | 14 | on: 15 | pull_request: 16 | branches: [ "main" ] 17 | merge_group: 18 | branches: [ "main" ] 19 | schedule: 20 | - cron: '29 14 * * 1' 21 | push: 22 | branches: [ "main" ] 23 | 24 | permissions: 25 | # Require writing security events to upload SARIF file to security tab 26 | security-events: write 27 | # Read commit contents 28 | contents: read 29 | 30 | jobs: 31 | scan-scheduled: 32 | if: ${{ github.event_name == 'push' || github.event_name == 'schedule' }} 33 | uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable.yml@1f1242919d8a60496dd1874b24b62b2370ed4c78" # v1.7.1 34 | with: 35 | # Example of specifying custom arguments 36 | scan-args: |- 37 | -r 38 | --skip-git 39 | ./ 40 | scan-pr: 41 | if: ${{ github.event_name == 'pull_request' || github.event_name == 'merge_group' }} 42 | uses: "google/osv-scanner-action/.github/workflows/osv-scanner-reusable-pr.yml@1f1242919d8a60496dd1874b24b62b2370ed4c78" # v1.7.1 43 | with: 44 | # Example of specifying custom arguments 45 | scan-args: |- 46 | -r 47 | --skip-git 48 | ./ 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/* 2 | !target/cjs/ 3 | !target/esm/ 4 | !target/dts/ 5 | jsr.json 6 | node_modules/ 7 | buildstamp.json 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.11.2](https://github.com/webpod/zurk/compare/v0.11.1...v0.11.2) (2025-04-01) 2 | 3 | ### Fixes & improvements 4 | * fix: export `VoidStream` ([626d865](https://github.com/webpod/zurk/commit/626d86561774f1bafda21eb750ee42ed73c39ee1)) 5 | 6 | ## [0.11.1](https://github.com/webpod/zurk/compare/v0.11.0...v0.11.1) (2025-03-31) 7 | 8 | ### Fixes & improvements 9 | * refactor: use .ts ext in sources ([2504eab](https://github.com/webpod/zurk/commit/2504eabe20310f8287efcdfeb4a03f0ea4fe2b00)) 10 | 11 | ## [0.11.0](https://github.com/webpod/zurk/compare/v0.10.3...v0.11.0) (2025-03-27) 12 | 13 | ### Features 14 | * feat: expose `quote` and `quotePwsh` ([f5a1c61](https://github.com/webpod/zurk/commit/f5a1c6111e47bc0adb7aac093c00a1cc9fa0a1bb)) 15 | 16 | ## [0.10.3](https://github.com/webpod/zurk/compare/v0.10.2...v0.10.3) (2025-03-27) 17 | 18 | ### Fixes & improvements 19 | * fix: strengthen `spawn.input` type ([bef4919](https://github.com/webpod/zurk/commit/bef49194c234aa26e5f2a053b8c4dc5634080e4b)) 20 | 21 | ## [0.10.2](https://github.com/webpod/zurk/compare/v0.10.1...v0.10.2) (2025-01-12) 22 | 23 | ### Fixes & improvements 24 | * docs: add jsdocs ([538b43a](https://github.com/webpod/zurk/commit/538b43aec0cb15cab22bf67a2a6e0aa3ff804670)) 25 | 26 | ## [0.10.1](https://github.com/webpod/zurk/compare/v0.10.0...v0.10.1) (2025-01-10) 27 | 28 | ### Fixes & improvements 29 | * fix: make `spawnChunkStore` reducible ([c04f309](https://github.com/webpod/zurk/commit/c04f3099036bfd10962bba6194b0eae1e9857161)) 30 | 31 | ## [0.10.0](https://github.com/webpod/zurk/compare/v0.9.3...v0.10.0) (2024-12-25) 32 | 33 | ### Features 34 | * feat: introduce `stdall` event ([13d8abd](https://github.com/webpod/zurk/commit/13d8abd26f68a99117a88e8e546ce00d8b0a0e12)) 35 | 36 | ## [0.9.3](https://github.com/webpod/zurk/compare/v0.9.2...v0.9.3) (2024-12-17) 37 | 38 | ### Fixes & improvements 39 | * fix: apply forced stdio destroy for abort flow only ([bbac4fe](https://github.com/webpod/zurk/commit/bbac4fe9577eb85b52396b3e92028909094768b3)) 40 | * fix: force stdio destroy on `abort/child.kill()` ([cd32992](https://github.com/webpod/zurk/commit/cd3299235bb2c0b31c59d4533510b0c751b55b66)) 41 | 42 | ## [0.9.2](https://github.com/webpod/zurk/compare/v0.9.1...v0.9.2) (2024-12-09) 43 | 44 | ### Fixes & improvements 45 | * docs: improve jsr docs refs ([770c846](https://github.com/webpod/zurk/commit/770c84686468686762e61f2a733c70dfe63efe62)) 46 | * fix: build error chunk separately ([556ef0f](https://github.com/webpod/zurk/commit/556ef0f58598c241407bdd8d1c83c83beba8dc77)) 47 | 48 | ## [0.9.1](https://github.com/webpod/zurk/compare/v0.9.0...v0.9.1) (2024-12-09) 49 | 50 | ### Fixes & improvements 51 | * fix: relax `isTemplateLiteral` check for tslib compat ([898f0d0](https://github.com/webpod/zurk/commit/898f0d03f92b8c60ed6858277f02cf3c8d340f58)) 52 | 53 | ## [0.9.0](https://github.com/webpod/zurk/compare/v0.8.0...v0.9.0) (2024-12-08) 54 | 55 | ### Features 56 | * feat: let `shell` be false ([5b4ebdb](https://github.com/webpod/zurk/commit/5b4ebdbb37105a4b2c91c920357be609a5b7e18b)) 57 | 58 | ## [0.8.0](https://github.com/webpod/zurk/compare/v0.7.5...v0.8.0) (2024-12-06) 59 | 60 | ### Features 61 | * feat: apply formatting to $ error messages ([6b1eb65](https://github.com/webpod/zurk/commit/6b1eb65af4de6b01ad316fa78ccc405c0bff4deb)) 62 | * feat: provide error utils ([0b6949b](https://github.com/webpod/zurk/commit/0b6949bd8b62ce57b128c919baf63fdaaeabd954)) 63 | 64 | ## [0.7.5](https://github.com/webpod/zurk/compare/v0.7.4...v0.7.5) (2024-12-03) 65 | 66 | ### Fixes & improvements 67 | * perf: tech release ([79b5456](https://github.com/webpod/zurk/commit/79b5456e288a7ff71f5a8992843a6d55a29406b2)) 68 | 69 | ## [0.7.4](https://github.com/webpod/zurk/compare/v0.7.3...v0.7.4) (2024-12-03) 70 | 71 | ### Fixes & improvements 72 | * perf: tech release ([07902df](https://github.com/webpod/zurk/commit/07902df6ff62fd1f6965c67642dd8ac0adc7c870)) 73 | 74 | ## [0.7.3](https://github.com/webpod/zurk/compare/v0.7.2...v0.7.3) (2024-12-03) 75 | 76 | ### Fixes & improvements 77 | * fix: move jsr publish to pkg postrelease script ([4c353c5](https://github.com/webpod/zurk/commit/4c353c541debc376925e6e933df02c378bc9ca2f)) 78 | 79 | ## [0.7.2](https://github.com/webpod/zurk/compare/v0.7.1...v0.7.2) (2024-12-03) 80 | 81 | ### Fixes & improvements 82 | * fix: fix jsr publish flow ([240386b](https://github.com/webpod/zurk/commit/240386b105c2a90afc92ffe17b0835475437753c)) 83 | 84 | ## [0.7.1](https://github.com/webpod/zurk/compare/v0.7.0...v0.7.1) (2024-12-03) 85 | 86 | ### Fixes & improvements 87 | * perf: enable publishing to jsr ([044ea18](https://github.com/webpod/zurk/commit/044ea184c7c1bafa121251c2f79f1d5cc825d0a2)) 88 | 89 | ## [0.7.0](https://github.com/webpod/zurk/compare/v0.6.3...v0.7.0) (2024-12-03) 90 | 91 | ### Features 92 | * feat: store target in git to support distribution via gh refs ([d3b3c20](https://github.com/webpod/zurk/commit/d3b3c2076a71834371e74bbadcbf48fd561a0f67)) 93 | 94 | ## [0.6.3](https://github.com/webpod/zurk/compare/v0.6.2...v0.6.3) (2024-11-10) 95 | 96 | ### Fixes & improvements 97 | * fix: unhandledRejection on proxified finally ([5877c68](https://github.com/webpod/zurk/commit/5877c68aa5ddc649815541e20b300830df6fe412)) 98 | 99 | ## [0.6.2](https://github.com/webpod/zurk/compare/v0.6.1...v0.6.2) (2024-11-10) 100 | 101 | ### Fixes & improvements 102 | * fix(spawn): handle nullable stdout/stderr ([030decd](https://github.com/webpod/zurk/commit/030decd3b96626fe4e01ead3ffd25daab889a578)) 103 | 104 | ## [0.6.1](https://github.com/webpod/zurk/compare/v0.6.0...v0.6.1) (2024-10-30) 105 | 106 | ### Fixes & improvements 107 | * fix: fix `TShellOptions` type ([baa38f7](https://github.com/webpod/zurk/commit/baa38f77e54f9d4aa68025953389f015dd2c18bc)) 108 | 109 | ## [0.6.0](https://github.com/webpod/zurk/compare/v0.5.0...v0.6.0) (2024-10-17) 110 | 111 | ### Features 112 | * feat: provide deno support ([ec3addd](https://github.com/webpod/zurk/commit/ec3addd86df51c1cc191a237c9c7064aaee9d835)) 113 | 114 | ## [0.5.0](https://github.com/webpod/zurk/compare/v0.4.4...v0.5.0) (2024-10-02) 115 | 116 | ### Fixes & improvements 117 | * docs: mention pipe split feature ([439669d](https://github.com/webpod/zurk/commit/439669d2a5d2146c69cbb9b7c8d236e333a0c069)) 118 | * perf: make pipe chunked ([4daae39](https://github.com/webpod/zurk/commit/4daae39f6315a54f1b323d106d43338aa8144569)) 119 | 120 | ### Features 121 | * feat: provide multipiping ([a4e0312](https://github.com/webpod/zurk/commit/a4e0312bbf1abaa88a9d23a90625ec4a2de2d5eb)) 122 | 123 | ## [0.4.4](https://github.com/webpod/zurk/compare/v0.4.3...v0.4.4) (2024-09-25) 124 | 125 | ### Fixes & improvements 126 | * fix: fill stdall store on sync spawn ([c63c58d](https://github.com/webpod/zurk/commit/c63c58deae9c9bd79c16d18b1431895db3cbdf42)) 127 | 128 | ## [0.4.3](https://github.com/webpod/zurk/compare/v0.4.2...v0.4.3) (2024-09-20) 129 | 130 | ### Fixes & improvements 131 | * fix: apply events detach on process end ([3d393be](https://github.com/webpod/zurk/commit/3d393be6ce17fd7695646735dfc1e197d3c964ac)) 132 | 133 | ## [0.4.2](https://github.com/webpod/zurk/compare/v0.4.1...v0.4.2) (2024-09-20) 134 | 135 | ### Fixes & improvements 136 | * perf: release ee handlers on process end ([49d89a9](https://github.com/webpod/zurk/commit/49d89a98fbe3d27fc0427e00d387e2f1fd50f8eb)) 137 | 138 | ## [0.4.1](https://github.com/webpod/zurk/compare/v0.4.0...v0.4.1) (2024-09-20) 139 | 140 | ### Fixes & improvements 141 | * refactor: enhance internal zurk objects detectors ([bcba34d](https://github.com/webpod/zurk/commit/bcba34deb0186d1d48c996f55d738168e22bc291)) 142 | 143 | ## [0.4.0](https://github.com/webpod/zurk/compare/v0.3.5...v0.4.0) (2024-09-20) 144 | 145 | ### Features 146 | * feat: export spawn ctx defaults ([d4466b0](https://github.com/webpod/zurk/commit/d4466b0d1ff5c7b8eb8b719729e993ffe5d824d7)) 147 | 148 | ## [0.3.5](https://github.com/webpod/zurk/compare/v0.3.4...v0.3.5) (2024-09-19) 149 | 150 | ### Fixes & improvements 151 | * fix: enhance `TemplateStringArray` detection ([10dc031](https://github.com/webpod/zurk/commit/10dc031690e338dd8b0a6fb763f5163c2f851c3d)) 152 | 153 | ## [0.3.4](https://github.com/webpod/zurk/compare/v0.3.3...v0.3.4) (2024-09-18) 154 | 155 | ### Fixes & improvements 156 | * fix(type): add index to `TSpawnChunks` type ([c61538a](https://github.com/webpod/zurk/commit/c61538ae58115b9044dbae9895c2f0e47825c996)) 157 | 158 | ## [0.3.3](https://github.com/webpod/zurk/compare/v0.3.2...v0.3.3) (2024-09-18) 159 | 160 | ### Fixes & improvements 161 | * fix(type): add length prop to `TSpawnChunks` type ([93feadf](https://github.com/webpod/zurk/commit/93feadf0df4ed7f09442c3eb721003e03f8f3c1a)) 162 | 163 | ## [0.3.2](https://github.com/webpod/zurk/compare/v0.3.1...v0.3.2) (2024-09-11) 164 | 165 | ### Fixes & improvements 166 | * fix: enhance abort handlers clean up (#13) ([cf0211a](https://github.com/webpod/zurk/commit/cf0211a2a8e991724bbc689f01df1b61e329d45f)) 167 | 168 | ## [0.3.1](https://github.com/webpod/zurk/compare/v0.3.0...v0.3.1) (2024-09-05) 169 | 170 | ### Fixes & improvements 171 | * fix: release on abort signal handler ([c575cd4](https://github.com/webpod/zurk/commit/c575cd4be9cabc3aa0821d15bf8f6fa08b7d299e)) 172 | 173 | ## [0.3.0](https://github.com/webpod/zurk/compare/v0.2.0...v0.3.0) (2024-06-12) 174 | 175 | ### Features 176 | * feat: provide chunks store customization ([165b020](https://github.com/webpod/zurk/commit/165b02001f0f46e8b46e521c2e8e800960a11241)) 177 | 178 | ## [0.2.0](https://github.com/webpod/zurk/compare/v0.1.4...v0.2.0) (2024-06-01) 179 | 180 | ### Features 181 | * feat: provide compat with nodejs 6+ (cjs) and nodejs 12+ (esm) ([1606b98](https://github.com/webpod/zurk/commit/1606b9812d7a997d84ede212150c7b86b4468abb)) 182 | 183 | ## [0.1.4](https://github.com/webpod/zurk/compare/v0.1.3...v0.1.4) (2024-04-30) 184 | 185 | ### Fixes & improvements 186 | * fix: replace `Object.hasOwn` with `Object.hasOwnPrototype` ([1fb5262](https://github.com/webpod/zurk/commit/1fb5262f752b478a39cffef399d744aa6f37c7a4)) 187 | 188 | ## [0.1.3](https://github.com/webpod/zurk/compare/v0.1.2...v0.1.3) (2024-04-27) 189 | 190 | ### Fixes & improvements 191 | * fix: let AbortController API be optional ([a21e1b9](https://github.com/webpod/zurk/commit/a21e1b94d1e7f15c1adc0c5bc7d951901cc87bca)) 192 | 193 | ## [0.1.2](https://github.com/webpod/zurk/compare/v0.1.1...v0.1.2) (2024-04-07) 194 | 195 | ### Fixes & improvements 196 | * fix: extend stdio opts ([546afc6](https://github.com/webpod/zurk/commit/546afc6da0d015926d3936793f2c69358fc2cb5f)) 197 | 198 | ## [0.1.1](https://github.com/webpod/zurk/compare/v0.1.0...v0.1.1) (2024-04-07) 199 | 200 | ### Fixes & improvements 201 | * fix: provide stdio customization ([cbfc232](https://github.com/webpod/zurk/commit/cbfc232011d79ae54e9bc99e6b41c59c0d6a47c0)) 202 | 203 | ## [0.1.0](https://github.com/webpod/zurk/compare/v0.0.32...v0.1.0) (2024-04-06) 204 | 205 | ### Features 206 | * feat: provide `signal` opt ([dc2b7ea](https://github.com/webpod/zurk/commit/dc2b7ea0a07ead8d7250290881f7c6422cb5b090)) 207 | 208 | ## [0.0.32](https://github.com/webpod/zurk/compare/v0.0.31...v0.0.32) (2024-03-26) 209 | 210 | ### Fixes & improvements 211 | * perf: migrate from yarn to npm ([6c455a2](https://github.com/webpod/zurk/commit/6c455a20ff2503ec1a439e36d58480025ba1e878)) 212 | 213 | ## [0.0.31](https://github.com/webpod/zurk/compare/v0.0.30...v0.0.31) (2024-03-17) 214 | 215 | ### Fixes & improvements 216 | * fix: apply undefined-filter to `assign` ([1463ca8](https://github.com/webpod/zurk/commit/1463ca8bba2d6ff6bdeb82a8598d306150481990)) 217 | 218 | ## [0.0.30](https://github.com/webpod/zurk/compare/undefined...v0.0.30) (2024-03-17) 219 | 220 | ### Fixes & improvements 221 | * fix: set detached `false` for win32 by default ([4d4e3c0](https://github.com/webpod/zurk/commit/4d4e3c0f9759448247fd60dbaad8c1e3fe10511c)) 222 | * docs: formatting ([c907044](https://github.com/webpod/zurk/commit/c907044dd76be3da591574fab66baf9d1bfcf9a1)) 223 | * docs: md formatting ([04a65a4](https://github.com/webpod/zurk/commit/04a65a436b0aeb0b3be0af9c94556794deb56a2f)) 224 | * refactor: move cmd builder to utils ([60123a5](https://github.com/webpod/zurk/commit/60123a5ac1d6f5f71302c639fb1a3b877f7bb2f2)) 225 | * docs: provide minimal usage example ([ae22952](https://github.com/webpod/zurk/commit/ae2295244c90e929ca8f12d261bd27cbebcc66d1)) 226 | * docs(zurk): describe main concepts ([8a9442b](https://github.com/webpod/zurk/commit/8a9442b20a1e87d222712229c9522adefcaa3372)) 227 | * refactor: move cmd builder to utils ([e73bb7a](https://github.com/webpod/zurk/commit/e73bb7a36a6a513bf93fcc6d6ad0b929fa6b7514)) 228 | * perf(spawn): compose `normalize` and `invoke` ([4e07348](https://github.com/webpod/zurk/commit/4e0734825df6f03c8569943ad8d07e4e6f83e638)) 229 | * refactor: move ee subscriber to `spawn` layer ([c91ff33](https://github.com/webpod/zurk/commit/c91ff3334de0e044cdd9a1b660fb9166b03f38b9)) 230 | * refactor: define `TZurkOn` handlers ifaces ([86d19dd](https://github.com/webpod/zurk/commit/86d19dd5e57bf754d1126334fed1320604494015)) 231 | * refactor(spawn): remove `onStdout` and `onStderr` handlers in favor or `on('stdout', () => {...})` ([7950a6c](https://github.com/webpod/zurk/commit/7950a6c142ed90b6da455b1042f7008e26af1480)) 232 | 233 | ### Features 234 | * feat: introduce listeners map, rename `_ctx` to `ctx` ([616b091](https://github.com/webpod/zurk/commit/616b0913117496f1aec4dd26353270fdfdd30f11)) 235 | * feat: pass reason arg to internal abortController ([76441c5](https://github.com/webpod/zurk/commit/76441c5a70af0716013a18818f184854625a5d5b)) 236 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Webpod 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 | # zurk 2 | 3 | 4 | – cute sounds but never friendly.
5 | – eats all kinds of materials. 6 |
7 | 8 | # 🔬🧫 9 | 10 | > This subproject is a kind of experiment, addressed to the [google/zx/issues/589](https://github.com/google/zx/issues/589). 11 | Just a testing ground for verifying ideas and approaches aimed at improve the [zx](https://github.com/google/zx) architecture. 12 | 13 | ## Concepts 14 | * **Layered** architecture: 15 | * `spawn` builds a configurable exec context around the `node:child_process` API. 16 | * `zurk` implements the API for sync and async executions. 17 | * `x` provides the basic template-string API. 18 | * **Granularity**: the package provides several entry points to help user to choose the right level of abstraction and/or to assist with tree-shaking. 19 | * **Extensibility**: 20 | * The context object at every layer is accessible fo modify. 21 | * Typings are mostly represented by interfaces, so it's easy to tweak up if necessary. 22 | 23 | ## Requirements 24 | * **OS** 25 | * Linux 26 | * MacOS 27 | * Windows 28 | * **Runtime** 29 | * Node.js >= 6 (CJS) 30 | * Node.js >= 12 (ESM) 31 | * Bun >= 1.0.0 32 | * Deno >= 1.7.0, 2.x 33 | 34 | ## Install 35 | ```bash 36 | yarn add zurk 37 | ``` 38 | 39 | ## API 40 | 41 | ```ts 42 | import {$, exec, zurk} from 'zurk' 43 | 44 | const r1 = exec({sync: true, cmd: 'echo foo'}) 45 | const r2 = await zurk({sync: false, cmd: 'echo foo'}) 46 | const r3 = await $`echo foo` 47 | ``` 48 | 49 | ## Proposals 50 | - [x] Promises in cmd literals 51 | ```ts 52 | const foo = $`echo foo` 53 | const foobarbaz = (await $`echo ${foo} ${$`echo bar`} ${await $`echo baz`}`) 54 | ``` 55 | 56 | - [x] Both sync and async executions 57 | ```ts 58 | const p1 = $`echo foo` 59 | const p2 = $({sync: true})`echo foo` 60 | 61 | const o1 = (await p1).toString() // foo 62 | const o2 = await p1.stdout // foo 63 | const o3 = p2.stdout // foo 64 | ``` 65 | 66 | - [x] Configurable input 67 | ```ts 68 | const input = '{"name": "foo"}' 69 | const name = await $({input})`jq -r .name` // foo 70 | 71 | const stdin = fs.createReadStream(path.join(fixtures, 'foo.json')) 72 | const data = await $({stdin})`jq -r .data` // foo 73 | 74 | const p = $`echo "5\\n3\\n1\\n4\\n2"` 75 | const sorted = $({input: p})`sort` // 1\n2\n3\n4\n5 76 | ``` 77 | 78 | - [x] Pipe literals 79 | ```ts 80 | const result = $`echo "5\\n3\\n1\\n4\\n2"` 81 | 82 | const piped0 = result.pipe`sort | cat` // '1\n2\n3\n4\n5' 83 | const piped1 = result.pipe`sort`.pipe`cat` // ... 84 | const piped2 = (await result).pipe`sort` 85 | const piped3 = result.pipe($`sort`) 86 | ``` 87 | 88 | - [x] Pipe splitting 89 | ```ts 90 | const result = $`echo 1; sleep 1; echo 2; sleep 1; echo 3` 91 | const piped1 = result.pipe`cat` 92 | let piped2: any 93 | 94 | setTimeout(() => { 95 | piped2 = result.pipe`cat` 96 | }, 1500) 97 | 98 | await piped1 99 | assert.equal((await piped1).toString(), '1\n2\n3') 100 | assert.equal((await piped2).toString(), '1\n2\n3') 101 | ``` 102 | 103 | - [x] Presets 104 | ```ts 105 | const $$ = $({sync: true, cmd: 'echo foo'}) 106 | const $$$ = $$({cmd: 'echo bar'}) 107 | 108 | const p1 = $$() // foo 109 | const p2 = $$$() // bar 110 | const p3 = $$`echo baz` // baz 111 | ``` 112 | 113 | - [x] AbortController 114 | ```ts 115 | const ac = new AbortController() 116 | const p = $({nothrow: true, ac})`sleep 10` 117 | setTimeout(() => { 118 | ac.signal.abort() // or just `p.abort()` 119 | }, 500) 120 | 121 | const { error } = await p 122 | error.message // 'The operation was aborted' 123 | ``` 124 | 125 | - [x] Stdout limit 126 | 127 | ```ts 128 | import {type TSpawnStore, $} from 'zurk' 129 | 130 | const getFixedSizeArray = (size: number) => { 131 | const arr: any[] = [] 132 | return new Proxy(arr, { 133 | get: (target: any, prop) => 134 | prop === 'push' && arr.length >= size 135 | ? () => {} 136 | : target[prop] 137 | }) 138 | } 139 | const store: TSpawnStore = { 140 | stdout: getFixedSizeArray(1), 141 | stderr: getFixedSizeArray(2), 142 | stdall: getFixedSizeArray(0) 143 | } 144 | 145 | const result = await $({store})`echo hello` 146 | result.stdout // 'hello\n' 147 | result.stdall // '' 148 | ``` 149 | 150 | - [x] Built-in quote for bash and powershell 151 | ```ts 152 | import {quote, quotePwsh} from 'zurk' 153 | 154 | const arg = 'foo bar' 155 | $({quote})`echo ${arg}` // "echo $'foo bar'" 156 | $({quote: quotePwsh})`echo ${arg}` // "echo 'foo bar'" 157 | ``` 158 | 159 | ## License 160 | [MIT](./LICENSE) 161 | -------------------------------------------------------------------------------- /mod.ts: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webpod/zurk/26f90ad66b5970ce6bd577966208ef6462fbf5a5/mod.ts -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zurk", 3 | "version": "0.11.2", 4 | "description": "A generic process spawner", 5 | "type": "module", 6 | "main": "target/cjs/index.cjs", 7 | "exports": { 8 | ".": { 9 | "types": "./target/dts/index.d.ts", 10 | "require": "./target/cjs/index.cjs", 11 | "import": "./target/esm/index.mjs", 12 | "default": "./target/esm/index.mjs" 13 | }, 14 | "./error": { 15 | "types": "./target/dts/error.d.ts", 16 | "require": "./target/cjs/error.cjs", 17 | "import": "./target/esm/error.mjs", 18 | "default": "./target/esm/error.mjs" 19 | }, 20 | "./spawn": { 21 | "types": "./target/dts/spawn.d.ts", 22 | "require": "./target/cjs/spawn.cjs", 23 | "import": "./target/esm/spawn.mjs", 24 | "default": "./target/esm/spawn.mjs" 25 | }, 26 | "./util": { 27 | "types": "./target/dts/util.d.ts", 28 | "require": "./target/cjs/util.cjs", 29 | "import": "./target/esm/util.mjs", 30 | "default": "./target/esm/util.mjs" 31 | }, 32 | "./zurk": { 33 | "types": "./target/dts/zurk.d.ts", 34 | "require": "./target/cjs/zurk.cjs", 35 | "import": "./target/esm/zurk.mjs", 36 | "default": "./target/esm/zurk.mjs" 37 | } 38 | }, 39 | "module": "target/esm/index.mjs", 40 | "types": "target/dts/index.d.ts", 41 | "files": [ 42 | "target/cjs", 43 | "target/esm", 44 | "target/dts" 45 | ], 46 | "scripts": { 47 | "build": "concurrently 'npm:build:*'", 48 | "build:js": "node ./src/scripts/build.mjs --entry='./src/main/ts/index.ts:./src/main/ts/util.ts:./src/main/ts/spawn.ts:./src/main/ts/zurk.ts:./src/main/ts/error.ts'", 49 | "build:dts": "tsc --emitDeclarationOnly --outDir target/dts", 50 | "build:docs": "typedoc --options src/main/typedoc", 51 | "build:stamp": "npx buildstamp", 52 | "build:jsr": "node ./src/scripts/build-jsr.mjs", 53 | "test": "concurrently 'npm:test:*'", 54 | "test:target": "git diff --exit-code --quiet || (echo 'Uncommitted changes' && exit 1)", 55 | "test:lint": "eslint -c src/test/lint/.eslintrc.json src", 56 | "test:unit": "c8 -r lcov -r text -o target/coverage -x src/scripts -x src/test -x target node --loader ts-node/esm --experimental-specifier-resolution=node src/scripts/test.mjs", 57 | "test:smoke:esm": "node ./src/test/smoke/invoke.test.mjs", 58 | "test:smoke:cjs": "node src/test/smoke/invoke.test.cjs", 59 | "test:jsr": "jsr publish --dry-run", 60 | "test:audit": "npm audit", 61 | "publish:draft": "npm run build && npm publish --no-git-tag-version", 62 | "postrelease": "node src/scripts/build-jsr.mjs && jsr publish --allow-dirty" 63 | }, 64 | "repository": { 65 | "type": "git", 66 | "url": "git+https://github.com/webpod/zurk.git" 67 | }, 68 | "author": "Anton Golub ", 69 | "license": "MIT", 70 | "bugs": { 71 | "url": "https://github.com/webpod/zurk/issues" 72 | }, 73 | "homepage": "https://github.com/webpod/zurk#readme", 74 | "devDependencies": { 75 | "@types/node": "^22.13.14", 76 | "c8": "^10.1.3", 77 | "concurrently": "^9.1.2", 78 | "esbuild": "^0.25.2", 79 | "esbuild-node-externals": "^1.18.0", 80 | "esbuild-plugin-entry-chunks": "^0.1.15", 81 | "esbuild-plugin-extract-helpers": "^0.0.6", 82 | "esbuild-plugin-transform-hook": "^0.2.0", 83 | "esbuild-plugin-utils": "^0.1.0", 84 | "eslint": "^8.57.0", 85 | "eslint-config-qiwi": "^2.1.6", 86 | "fast-glob": "^3.3.3", 87 | "jsr": "^0.13.4", 88 | "minimist": "^1.2.8", 89 | "ts-node": "^10.9.2", 90 | "tslib": "^2.8.1", 91 | "typedoc": "^0.28.1", 92 | "typescript": "^5.8.2" 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/main/ts/error.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module 3 | * 4 | * Zurk spawn error codes & handling utilities 5 | * 6 | * @example 7 | * ```ts 8 | * import {EXIT_CODES} from 'zurk/error' 9 | * 10 | * console.log(EXIT_CODES[2]) // 'Misuse of shell builtins' 11 | * ``` 12 | */ 13 | 14 | export const EXIT_CODES = { 15 | 2: 'Misuse of shell builtins', 16 | 126: 'Invoked command cannot execute', 17 | 127: 'Command not found', 18 | 128: 'Invalid exit argument', 19 | 129: 'Hangup', 20 | 130: 'Interrupt', 21 | 131: 'Quit and dump core', 22 | 132: 'Illegal instruction', 23 | 133: 'Trace/breakpoint trap', 24 | 134: 'Process aborted', 25 | 135: 'Bus error: "access to undefined portion of memory object"', 26 | 136: 'Floating point exception: "erroneous arithmetic operation"', 27 | 137: 'Kill (terminate immediately)', 28 | 138: 'User-defined 1', 29 | 139: 'Segmentation violation', 30 | 140: 'User-defined 2', 31 | 141: 'Write to pipe with no one reading', 32 | 142: 'Signal raised by alarm', 33 | 143: 'Termination (request to terminate)', 34 | 145: 'Child process terminated, stopped (or continued*)', 35 | 146: 'Continue if stopped', 36 | 147: 'Stop executing temporarily', 37 | 148: 'Terminal stop signal', 38 | 149: 'Background process attempting to read from tty ("in")', 39 | 150: 'Background process attempting to write to tty ("out")', 40 | 151: 'Urgent data available on socket', 41 | 152: 'CPU time limit exceeded', 42 | 153: 'File size limit exceeded', 43 | 154: 'Signal raised by timer counting virtual time: "virtual timer expired"', 44 | 155: 'Profiling timer expired', 45 | 157: 'Pollable event', 46 | 159: 'Bad syscall', 47 | } 48 | 49 | export const ERRNO_CODES = { 50 | 0: 'Success', 51 | 1: 'Not super-user', 52 | 2: 'No such file or directory', 53 | 3: 'No such process', 54 | 4: 'Interrupted system call', 55 | 5: 'I/O error', 56 | 6: 'No such device or address', 57 | 7: 'Arg list too long', 58 | 8: 'Exec format error', 59 | 9: 'Bad file number', 60 | 10: 'No children', 61 | 11: 'No more processes', 62 | 12: 'Not enough core', 63 | 13: 'Permission denied', 64 | 14: 'Bad address', 65 | 15: 'Block device required', 66 | 16: 'Mount device busy', 67 | 17: 'File exists', 68 | 18: 'Cross-device link', 69 | 19: 'No such device', 70 | 20: 'Not a directory', 71 | 21: 'Is a directory', 72 | 22: 'Invalid argument', 73 | 23: 'Too many open files in system', 74 | 24: 'Too many open files', 75 | 25: 'Not a typewriter', 76 | 26: 'Text file busy', 77 | 27: 'File too large', 78 | 28: 'No space left on device', 79 | 29: 'Illegal seek', 80 | 30: 'Read only file system', 81 | 31: 'Too many links', 82 | 32: 'Broken pipe', 83 | 33: 'Math arg out of domain of func', 84 | 34: 'Math result not representable', 85 | 35: 'File locking deadlock error', 86 | 36: 'File or path name too long', 87 | 37: 'No record locks available', 88 | 38: 'Function not implemented', 89 | 39: 'Directory not empty', 90 | 40: 'Too many symbolic links', 91 | 42: 'No message of desired type', 92 | 43: 'Identifier removed', 93 | 44: 'Channel number out of range', 94 | 45: 'Level 2 not synchronized', 95 | 46: 'Level 3 halted', 96 | 47: 'Level 3 reset', 97 | 48: 'Link number out of range', 98 | 49: 'Protocol driver not attached', 99 | 50: 'No CSI structure available', 100 | 51: 'Level 2 halted', 101 | 52: 'Invalid exchange', 102 | 53: 'Invalid request descriptor', 103 | 54: 'Exchange full', 104 | 55: 'No anode', 105 | 56: 'Invalid request code', 106 | 57: 'Invalid slot', 107 | 59: 'Bad font file fmt', 108 | 60: 'Device not a stream', 109 | 61: 'No data (for no delay io)', 110 | 62: 'Timer expired', 111 | 63: 'Out of streams resources', 112 | 64: 'Machine is not on the network', 113 | 65: 'Package not installed', 114 | 66: 'The object is remote', 115 | 67: 'The link has been severed', 116 | 68: 'Advertise error', 117 | 69: 'Srmount error', 118 | 70: 'Communication error on send', 119 | 71: 'Protocol error', 120 | 72: 'Multihop attempted', 121 | 73: 'Cross mount point (not really error)', 122 | 74: 'Trying to read unreadable message', 123 | 75: 'Value too large for defined data type', 124 | 76: 'Given log. name not unique', 125 | 77: 'f.d. invalid for this operation', 126 | 78: 'Remote address changed', 127 | 79: 'Can access a needed shared lib', 128 | 80: 'Accessing a corrupted shared lib', 129 | 81: '.lib section in a.out corrupted', 130 | 82: 'Attempting to link in too many libs', 131 | 83: 'Attempting to exec a shared library', 132 | 84: 'Illegal byte sequence', 133 | 86: 'Streams pipe error', 134 | 87: 'Too many users', 135 | 88: 'Socket operation on non-socket', 136 | 89: 'Destination address required', 137 | 90: 'Message too long', 138 | 91: 'Protocol wrong type for socket', 139 | 92: 'Protocol not available', 140 | 93: 'Unknown protocol', 141 | 94: 'Socket type not supported', 142 | 95: 'Not supported', 143 | 96: 'Protocol family not supported', 144 | 97: 'Address family not supported by protocol family', 145 | 98: 'Address already in use', 146 | 99: 'Address not available', 147 | 100: 'Network interface is not configured', 148 | 101: 'Network is unreachable', 149 | 102: 'Connection reset by network', 150 | 103: 'Connection aborted', 151 | 104: 'Connection reset by peer', 152 | 105: 'No buffer space available', 153 | 106: 'Socket is already connected', 154 | 107: 'Socket is not connected', 155 | 108: "Can't send after socket shutdown", 156 | 109: 'Too many references', 157 | 110: 'Connection timed out', 158 | 111: 'Connection refused', 159 | 112: 'Host is down', 160 | 113: 'Host is unreachable', 161 | 114: 'Socket already connected', 162 | 115: 'Connection already in progress', 163 | 116: 'Stale file handle', 164 | 122: 'Quota exceeded', 165 | 123: 'No medium (in tape drive)', 166 | 125: 'Operation canceled', 167 | 130: 'Previous owner died', 168 | 131: 'State not recoverable', 169 | } 170 | 171 | export function getErrnoMessage(errno?: number): string { 172 | return ( 173 | ERRNO_CODES[-(errno as number) as keyof typeof ERRNO_CODES] || 174 | 'Unknown error' 175 | ) 176 | } 177 | 178 | export function getExitCodeInfo(exitCode: number | null): string | undefined { 179 | return EXIT_CODES[exitCode as keyof typeof EXIT_CODES] 180 | } 181 | 182 | export const formatExitMessage = ( 183 | code: number | null, 184 | signal: NodeJS.Signals | null, 185 | stderr: string, 186 | from: string 187 | ) => { 188 | let message = `exit code: ${code}` 189 | if (code != 0 || signal != null) { 190 | message = `${stderr || '\n'} at ${from}` 191 | message += `\n exit code: ${code}${ 192 | getExitCodeInfo(code) ? ' (' + getExitCodeInfo(code) + ')' : '' 193 | }` 194 | if (signal != null) { 195 | message += `\n signal: ${signal}` 196 | } 197 | } 198 | 199 | return message 200 | } 201 | 202 | export const formatErrorMessage = (err: NodeJS.ErrnoException, from: string) => { 203 | return ( 204 | `${err.message}\n` + 205 | ` errno: ${err.errno} (${getErrnoMessage(err.errno)})\n` + 206 | ` code: ${err.code}\n` + 207 | ` at ${from}` 208 | ) 209 | } 210 | 211 | export function getCallerLocation(err = new Error('zurk error')) { 212 | return getCallerLocationFromString(err.stack) 213 | } 214 | 215 | export function getCallerLocationFromString(stackString = 'unknown') { 216 | return ( 217 | stackString 218 | .split(/^\s*(at\s)?/m) 219 | .filter((s) => s?.includes(':'))[2] 220 | ?.trim() || stackString 221 | ) 222 | } 223 | -------------------------------------------------------------------------------- /src/main/ts/index.ts: -------------------------------------------------------------------------------- 1 | export type * from './spawn.ts' 2 | export type * from './x.ts' 3 | export type * from './zurk.ts' 4 | 5 | export { invoke, exec, defaults, VoidStream, isStringLiteral, quote, quotePwsh, buildCmd } from './spawn.ts' 6 | export { $ } from './x.ts' 7 | export { zurk } from './zurk.ts' 8 | 9 | /** 10 | * @module 11 | * 12 | * A generic process spawner 13 | * 14 | * @example 15 | * ```ts 16 | * import {$, exec, zurk} from 'zurk' 17 | * 18 | * const r1 = exec({sync: true, cmd: 'echo foo'}) 19 | * const r2 = await zurk({sync: false, cmd: 'echo foo'}) 20 | * const r3 = await $`echo foo` 21 | * ``` 22 | */ 23 | -------------------------------------------------------------------------------- /src/main/ts/mixin/kill.ts: -------------------------------------------------------------------------------- 1 | import process from 'node:process' 2 | import { ChildProcess } from 'node:child_process' 3 | import { assign } from '../util.ts' 4 | import type { TMixin, TShell, TShellCtx } from '../x.ts' 5 | import { type TZurk, type TZurkPromise, isZurkAny } from '../zurk.ts' 6 | 7 | /** 8 | * @module 9 | * 10 | * Zurk $ pipe mixin 11 | */ 12 | 13 | // https://github.com/nodejs/node/issues/37518 14 | // https://github.com/nodejs/node/issues/46865 15 | const kill = (child?: ChildProcess, signal: null | string | number | NodeJS.Signals = 'SIGTERM') => new Promise((resolve, reject) => { 16 | if (child) { 17 | child.on('exit', (code, signal) => { 18 | resolve(signal) 19 | }) 20 | process.kill(-(child.pid as number), signal as NodeJS.Signals) 21 | } else { 22 | reject(new Error('No child process to kill')) 23 | } 24 | }) 25 | 26 | export const killMixin: TMixin = ($: TShell, result: T, ctx: TShellCtx) => 27 | isZurkAny(result) 28 | ? assign(result, { 29 | kill(signal?: null | string | number | NodeJS.Signals): Promise { 30 | return kill(ctx.child, signal) 31 | }, 32 | abort(reason?: any) { ctx.ac.abort(reason)}, 33 | }) 34 | : result 35 | -------------------------------------------------------------------------------- /src/main/ts/mixin/pipe.ts: -------------------------------------------------------------------------------- 1 | import { Writable } from 'node:stream' 2 | import { assign, isStringLiteral } from '../util.ts' 3 | import { VoidStream } from '../spawn.ts' 4 | import type { TShell, TMixin, TShellCtx } from '../x.ts' 5 | import { type TZurk, type TZurkPromise, isZurkAny } from '../zurk.ts' 6 | 7 | /** 8 | * @module 9 | * 10 | * Zurk $ pipe mixin 11 | */ 12 | 13 | // eslint-disable-next-line sonarjs/cognitive-complexity 14 | export const pipeMixin: TMixin = ($: TShell, result: T, ctx: TShellCtx) => 15 | isZurkAny(result) 16 | ? assign(result, { 17 | pipe(...args: any) { 18 | const [target, ...rest] = args 19 | const { fulfilled, store, ee } = ctx 20 | const from = new VoidStream() 21 | const sync = !('then' in result) 22 | const input = fulfilled ? fulfilled.stdout : from 23 | const fill = () => { 24 | for (const chunk of store.stdout) { 25 | from.write(chunk) 26 | } 27 | } 28 | let _result 29 | 30 | if (isZurkAny(target)) { 31 | target.ctx.input = input 32 | _result = target 33 | } else if (target instanceof Writable) { 34 | _result = from.pipe(target) 35 | } else if (isStringLiteral(target, ...rest)) { 36 | _result = $.apply({ input: input, sync}, args) 37 | } else { 38 | throw new Error('Unsupported pipe argument') 39 | } 40 | 41 | if (fulfilled) { 42 | fill() 43 | from.end() 44 | } else { 45 | const onStdout = (chunk: string | Buffer) => from.write(chunk) 46 | ee 47 | .once('stdout', () => { 48 | fill() 49 | ee.on('stdout', onStdout) 50 | }) 51 | .once('end', () => { 52 | ee.removeListener('stdout', onStdout) 53 | from.end() 54 | }) 55 | } 56 | 57 | return _result 58 | } 59 | }) 60 | : result 61 | -------------------------------------------------------------------------------- /src/main/ts/mixin/timeout.ts: -------------------------------------------------------------------------------- 1 | import process from 'node:process' 2 | import { assign, pFinally } from '../util.ts' 3 | import type { TMixin, TShell, TShellCtx } from '../x.ts' 4 | import { type TZurk, type TZurkPromise, isZurkPromise } from '../zurk.ts' 5 | 6 | /** 7 | * @module 8 | * 9 | * Zurk $ timeout mixin 10 | */ 11 | 12 | const attachTimeout = void }>( 13 | ctx: TShellCtx, 14 | result: T 15 | ) => { 16 | clearTimeout(ctx.timer) 17 | if (ctx.timeout === undefined) return 18 | 19 | const kill = () => { 20 | const { child, timeoutSignal = 'SIGTERM' } = ctx 21 | if (result.kill) return result.kill(timeoutSignal) 22 | if (child?.pid) process.kill(child.pid as number, timeoutSignal) 23 | } 24 | ctx.timer = setTimeout(kill, ctx.timeout) 25 | } 26 | 27 | export const timeoutMixin: TMixin = ($: TShell, result: T, ctx: TShellCtx) => { 28 | if (isZurkPromise(result)) { 29 | assign(result, { 30 | set timeoutSignal(timeoutSignal: NodeJS.Signals) { 31 | assign(ctx, { timeoutSignal }) 32 | }, 33 | set timeout(timeout: number) { 34 | assign(ctx, {timeout}) 35 | attachTimeout(ctx, result) 36 | } 37 | }) 38 | 39 | attachTimeout(ctx, result) 40 | pFinally(result,() => clearTimeout((ctx as any).timer)) 41 | } 42 | 43 | return result 44 | } 45 | -------------------------------------------------------------------------------- /src/main/ts/spawn.ts: -------------------------------------------------------------------------------- 1 | import * as cp from 'node:child_process' 2 | import process from 'node:process' 3 | import EventEmitter from 'node:events' 4 | import { Buffer } from 'node:buffer' 5 | import { Readable, Writable, Transform } from 'node:stream' 6 | import { assign, noop, randomId, g, immediate } from './util.ts' 7 | 8 | /** 9 | * @module 10 | * 11 | * Zurk internal child_process caller API 12 | * 13 | * @example 14 | * ```ts 15 | * import {invoke, normalizeCtx, TSpawnCtx} from 'zurk/spawn' 16 | * 17 | * const results: string[] = [] 18 | * const callback: TSpawnCtx['callback'] = (_err, result) => results.push(result.stdout) 19 | * 20 | * invoke(normalizeCtx({ 21 | * sync: true, 22 | * cmd: 'echo', 23 | * args: ['hello'], 24 | * callback, 25 | * })) 26 | * ``` 27 | */ 28 | 29 | export * from './util.ts' 30 | 31 | export type TSpawnError = any 32 | 33 | export type TPushable = { push(...args: T[]): number } 34 | 35 | export type TJoinable = { join(sep?: string): string } 36 | 37 | export type TReducible = { reduce(fn: (acc: U, cur: T, i: number, arr: T[]) => U, init: U): R } 38 | 39 | export type TArrayLike = Iterable & TPushable & TJoinable & TReducible &{ length: number, [i: number]: T | undefined } 40 | 41 | export type TSpawnStoreChunks = TArrayLike 42 | 43 | export type TSpawnStore = { 44 | stdout: TSpawnStoreChunks 45 | stderr: TSpawnStoreChunks 46 | stdall: TSpawnStoreChunks 47 | } 48 | 49 | export type TSpawnResult = { 50 | stderr: string 51 | stdout: string 52 | stdall: string, 53 | stdio: [Readable | Writable, Writable, Writable] 54 | status: number | null 55 | signal: NodeJS.Signals | null 56 | duration: number 57 | ctx: TSpawnCtxNormalized 58 | error?: TSpawnError, 59 | child?: TChild 60 | } 61 | 62 | export type TSpawnListeners = { 63 | start: (data: TChild, ctx: TSpawnCtxNormalized) => void 64 | stdout: (data: Buffer, ctx: TSpawnCtxNormalized) => void 65 | stderr: (data: Buffer, ctx: TSpawnCtxNormalized) => void 66 | stdall: (data: Buffer, ctx: TSpawnCtxNormalized) => void 67 | abort: (error: Event, ctx: TSpawnCtxNormalized) => void 68 | err: (error: Error, ctx: TSpawnCtxNormalized) => void 69 | end: (result: TSpawnResult, ctx: TSpawnCtxNormalized) => void 70 | } 71 | 72 | export type TSpawnCtx = Partial> 73 | 74 | export type TChild = ReturnType 75 | 76 | export type TInput = string | Buffer | Readable 77 | 78 | export interface TSpawnCtxNormalized { 79 | id: string, 80 | cwd: string 81 | cmd: string 82 | sync: boolean 83 | args: ReadonlyArray 84 | input: TInput | null 85 | stdio: cp.StdioOptions 86 | detached: boolean 87 | env: Record 88 | ee: EventEmitter 89 | on: Partial 90 | ac: AbortController 91 | signal: AbortController['signal'] 92 | shell: string | boolean | undefined 93 | spawn: typeof cp.spawn 94 | spawnSync: typeof cp.spawnSync 95 | spawnOpts: Record 96 | store: TSpawnStore 97 | callback: (err: TSpawnError, result: TSpawnResult) => void 98 | stdin: Readable 99 | stdout: Writable 100 | stderr: Writable 101 | child?: TChild 102 | fulfilled?: TSpawnResult 103 | error?: any 104 | run: (cb: () => void, ctx: TSpawnCtxNormalized) => void 105 | stack: string 106 | } 107 | 108 | /** 109 | * zurk default settings 110 | */ 111 | export const defaults: TSpawnCtxNormalized = { 112 | get id() { return randomId() }, 113 | cmd: '', 114 | get cwd() { return process.cwd() }, 115 | sync: false, 116 | args: [], 117 | input: null, 118 | env: process.env, 119 | get ee() { return new EventEmitter() }, 120 | get ac() { return g.AbortController && new AbortController() }, 121 | get signal() { return this.ac?.signal }, 122 | on: {}, 123 | detached: process.platform !== 'win32', 124 | shell: true, 125 | spawn: cp.spawn, 126 | spawnSync: cp.spawnSync, 127 | spawnOpts: {}, 128 | get store() { return createStore() }, 129 | callback: noop, 130 | get stdin() { return new VoidStream() }, 131 | get stdout(){ return new VoidStream() }, 132 | get stderr(){ return new VoidStream() }, 133 | stdio: ['pipe', 'pipe', 'pipe'], 134 | run: immediate, 135 | stack: '' 136 | } 137 | 138 | /** 139 | * Normalizes spawn context. 140 | * 141 | * @param ctxs Contexts to normalize 142 | * @returns 143 | */ 144 | export const normalizeCtx = (...ctxs: TSpawnCtx[]): TSpawnCtxNormalized => assign({ 145 | ...defaults, 146 | get signal() { return this.ac?.signal }}, 147 | ...ctxs) 148 | 149 | /** 150 | * Redirects input to child process stdin 151 | * @param child 152 | * @param input 153 | */ 154 | export const processInput = (child: TChild, input?: TInput | null): void => { 155 | if (input && child.stdin && !child.stdin.destroyed) { 156 | if (input instanceof Readable) { 157 | input.pipe(child.stdin) 158 | } else { 159 | child.stdin.write(input) 160 | child.stdin.end() 161 | } 162 | } 163 | } 164 | 165 | /** 166 | * Transformer that emits data but does not consume it. 167 | */ 168 | export class VoidStream extends Transform { 169 | _transform(chunk: any, _: string, cb: (err?: Error) => void) { 170 | this.emit('data', chunk) 171 | cb() 172 | } 173 | } 174 | 175 | /** 176 | * Builds spawn options 177 | * @param ctx 178 | * @returns spawn options 179 | */ 180 | export const buildSpawnOpts = ({spawnOpts, stdio, cwd, shell, input, env, detached, signal}: TSpawnCtxNormalized) => ({ 181 | ...spawnOpts, 182 | env, 183 | cwd, 184 | stdio, 185 | shell, 186 | input: input as string | Buffer, 187 | windowsHide: true, 188 | detached, 189 | signal 190 | }) 191 | 192 | /** 193 | * Toggles event listeners 194 | * @param pos 'on' | 'off' 195 | * @param ee EventEmitter 196 | * @param on listeners map 197 | */ 198 | export const toggleListeners = (pos: 'on' | 'off', ee: EventEmitter, on: Partial = {}): void => { 199 | for (const [name, listener] of Object.entries(on)) { 200 | ee[pos](name, listener as any) 201 | } 202 | if (pos === 'on') 203 | ee.once('end', () => toggleListeners('off', ee, on)) 204 | } 205 | 206 | /** 207 | * Creates a new spawn store 208 | */ 209 | export const createStore = (): TSpawnStore => ({ 210 | stdout: [], 211 | stderr: [], 212 | stdall: [], 213 | }) 214 | 215 | /** 216 | * Invokes a child process 217 | * @param c Normalized context. 218 | * @returns Normalized context. 219 | */ 220 | // eslint-disable-next-line sonarjs/cognitive-complexity 221 | export const invoke = (c: TSpawnCtxNormalized): TSpawnCtxNormalized => { 222 | const now = Date.now() 223 | const stdio: TSpawnResult['stdio'] = [c.stdin, c.stdout, c.stderr] 224 | const push = (kind: 'stdout' | 'stderr', data: Buffer) => { 225 | c.store[kind].push(data) 226 | c.store.stdall.push(data) 227 | c.ee.emit(kind, data, c) 228 | c.ee.emit('stdall', data, c) 229 | } 230 | 231 | try { 232 | if (c.sync) { 233 | toggleListeners('on', c.ee, c.on) 234 | const opts = buildSpawnOpts(c) 235 | const r = c.spawnSync(c.cmd, c.args, opts) 236 | c.ee.emit('start', r, c) 237 | if (r.stdout?.length > 0) { 238 | c.stdout.write(r.stdout) 239 | push('stdout', r.stdout) 240 | } 241 | if (r.stderr?.length > 0) { 242 | c.stderr.write(r.stderr) 243 | push('stderr', r.stderr) 244 | } 245 | c.callback(null, c.fulfilled = { 246 | ...r, 247 | get stdout() { return c.store.stdout.join('') }, 248 | get stderr() { return c.store.stderr.join('') }, 249 | get stdall() { return c.store.stdall.join('') }, 250 | stdio, 251 | duration: Date.now() - now, 252 | ctx: c 253 | }) 254 | c.ee.emit('end', c.fulfilled, c) 255 | 256 | } else { 257 | c.run(() => { 258 | toggleListeners('on', c.ee, c.on) 259 | 260 | let error: any = null 261 | let aborted = false 262 | const opts = buildSpawnOpts(c) 263 | const child = c.spawn(c.cmd, c.args, opts) 264 | const onAbort = (event: any) => { 265 | if (opts.detached && child.pid) { 266 | try { 267 | // https://github.com/nodejs/node/issues/51766 268 | process.kill(-child.pid) 269 | } catch { 270 | child.kill() 271 | } 272 | } 273 | aborted = true 274 | c.ee.emit('abort', event, c) 275 | } 276 | c.child = child 277 | c.ee.emit('start', child, c) 278 | 279 | opts.signal?.addEventListener('abort', onAbort) 280 | processInput(child, c.input || c.stdin) 281 | 282 | child.stdout?.on('data', d => { push('stdout', d) }).pipe(c.stdout) 283 | child.stderr?.on('data', d => { push('stderr', d) }).pipe(c.stderr) 284 | child 285 | .once('error', (e: any) => { 286 | error = e 287 | c.ee.emit('err', error, c) 288 | }) 289 | .once('exit', () => { 290 | if (aborted) { 291 | child.stdout?.destroy() 292 | child.stderr?.destroy() 293 | } 294 | }) 295 | .once('close', (status, signal) => { 296 | c.fulfilled = { 297 | error, 298 | status, 299 | signal, 300 | get stdout() { return c.store.stdout.join('') }, 301 | get stderr() { return c.store.stderr.join('') }, 302 | get stdall() { return c.store.stdall.join('') }, 303 | stdio, 304 | duration: Date.now() - now, 305 | ctx: c 306 | } 307 | opts.signal?.removeEventListener('abort', onAbort) 308 | c.callback(error, c.fulfilled) 309 | c.ee.emit('end', c.fulfilled, c) 310 | }) 311 | }, c) 312 | } 313 | } catch (error: unknown) { 314 | c.callback( 315 | error, 316 | c.fulfilled = { 317 | error, 318 | status: null, 319 | signal: null, 320 | stdout: '', 321 | stderr: '', 322 | stdall: '', 323 | stdio, 324 | duration: Date.now() - now, 325 | ctx: c 326 | } 327 | ) 328 | c.ee.emit('err', error, c) 329 | c.ee.emit('end', c.fulfilled, c) 330 | } 331 | 332 | return c 333 | } 334 | 335 | /** 336 | * Executes a child process 337 | * @param ctx TSpawnCtx 338 | * @returns TSpawnCtxNormalized 339 | */ 340 | export const exec = (ctx: TSpawnCtx): TSpawnCtxNormalized => invoke(normalizeCtx(ctx)) 341 | 342 | // https://2ality.com/2018/05/child-process-streams.html 343 | -------------------------------------------------------------------------------- /src/main/ts/util.ts: -------------------------------------------------------------------------------- 1 | import { Stream } from 'node:stream' 2 | import process from 'node:process' 3 | import { Buffer } from 'node:buffer' 4 | 5 | /** 6 | * @module 7 | * 8 | * Zurk utility functions 9 | * 10 | * @example 11 | * ```ts 12 | * import {randomId} from 'zurk/util' 13 | * 14 | * randomId() // 'kdrx9bngrb' 15 | * ``` 16 | */ 17 | 18 | export const g = (!process.versions.deno && global) || globalThis 19 | 20 | export const immediate = g.setImmediate || ((f: any): NodeJS.Timeout => g.setTimeout(f, 0)) 21 | 22 | export const noop = () => { /* noop */ } 23 | 24 | export const asyncVoidCall = (cb: TVoidCallback)=> async (): Promise => { await cb() } 25 | 26 | export const randomId = (): string => Math.random().toString(36).slice(2) 27 | 28 | export type PromiseResolve = (value: T | PromiseLike) => void 29 | 30 | export type TVoidCallback = (...any: any) => void 31 | 32 | // https://stackoverflow.com/questions/47423241/replace-fields-types-in-interfaces-to-promises 33 | export type Promisified = { 34 | [K in keyof T]: T[K] extends (...args: any) => infer R 35 | ? (...args: Parameters) => Promise 36 | : Promise 37 | } 38 | 39 | export const makeDeferred = (): { promise: Promise, resolve: PromiseResolve, reject: PromiseResolve } => { 40 | let resolve 41 | let reject 42 | const promise = new Promise((res, rej) => { resolve = res; reject = rej }) 43 | return { resolve, reject, promise } as any 44 | } 45 | 46 | export const isPromiseLike = (value: any): boolean => typeof value?.then === 'function' 47 | 48 | export const isStringLiteral = ( 49 | pieces: any, 50 | ...rest: any[] 51 | ): pieces is TemplateStringsArray => 52 | pieces?.length > 0 && 53 | pieces.raw?.length === pieces.length && 54 | // Object.isFrozen(pieces) && 55 | rest.length + 1 === pieces.length 56 | 57 | export const assign = (target: T, ...extras: E[]): T => 58 | Object.defineProperties(target, extras.reduce>((m: any, extra) => 59 | ({...m, ...Object.fromEntries(Object.entries(Object.getOwnPropertyDescriptors(extra)) 60 | .filter(([,v]) => !Object.prototype.hasOwnProperty.call(v, 'value') || v.value !== undefined))}), {})) 61 | 62 | export const quote = (arg: string): string => { 63 | if (arg === '') return `$''` 64 | if (/^[\w./:=@-]+$/.test(arg)) return arg 65 | 66 | return ( 67 | `$'` + 68 | arg 69 | .replace(/\\/g, '\\\\') 70 | .replace(/'/g, "\\'") 71 | .replace(/\f/g, '\\f') 72 | .replace(/\n/g, '\\n') 73 | .replace(/\r/g, '\\r') 74 | .replace(/\t/g, '\\t') 75 | .replace(/\v/g, '\\v') 76 | .replace(/\0/g, '\\0') + 77 | `'` 78 | ) 79 | } 80 | 81 | export function quotePwsh(arg: string): string { 82 | if (arg === '') return `''` 83 | if (/^[\w./-]+$/.test(arg)) return arg 84 | 85 | return `'` + arg.replace(/'/g, "''") + `'` 86 | } 87 | 88 | export type TQuote = (input: string) => string 89 | 90 | export const buildCmd = (quote: TQuote, pieces: TemplateStringsArray, args: any[], subs = substitute): string | Promise => { 91 | if (args.some(isPromiseLike)) 92 | return Promise.all(args).then((args) => buildCmd(quote, pieces, args)) 93 | 94 | let cmd = pieces[0], i = 0 95 | while (i < args.length) { 96 | const s = Array.isArray(args[i]) 97 | ? args[i].map((x: any) => quote(subs(x))).join(' ') 98 | : quote(subs(args[i])) 99 | 100 | cmd += s + pieces[++i] 101 | } 102 | 103 | return cmd 104 | } 105 | 106 | export type TSubstitute = (arg: any) => string 107 | 108 | export const substitute: TSubstitute = (arg: any) => 109 | (typeof arg?.stdout === 'string') 110 | ? arg.stdout.replace(/\n$/, '') 111 | : `${arg}` 112 | 113 | export const parseInput = (input: any): string | Buffer | Stream | null => { 114 | if (typeof input === 'string' || input instanceof Buffer || input instanceof Stream) return input 115 | 116 | if (typeof input?.stdout === 'string') return input.stdout 117 | 118 | if (input?.ctx) return parseInput(input.ctx.stdout) 119 | 120 | return null 121 | } 122 | 123 | export const pFinally = (p: Promise, cb: TVoidCallback): Promise => p.finally?.(asyncVoidCall(cb)) || p.then(asyncVoidCall(cb), asyncVoidCall(cb)) 124 | -------------------------------------------------------------------------------- /src/main/ts/x.ts: -------------------------------------------------------------------------------- 1 | import type { Readable, Writable } from 'node:stream' 2 | import { 3 | zurk, 4 | zurkifyPromise, 5 | isZurkAny, 6 | TZurk, 7 | TZurkPromise, 8 | TZurkOptions, 9 | TZurkCtx 10 | } from './zurk.ts' 11 | import { 12 | type Promisified, 13 | type TVoidCallback, 14 | type TQuote, 15 | isPromiseLike, 16 | isStringLiteral, 17 | assign, 18 | quote, 19 | buildCmd, 20 | parseInput, 21 | g, 22 | immediate 23 | } from './util.ts' 24 | import { getCallerLocation } from './error.ts' 25 | import { pipeMixin } from './mixin/pipe.ts' 26 | import { killMixin } from './mixin/kill.ts' 27 | import { timeoutMixin } from './mixin/timeout.ts' 28 | 29 | 30 | /** 31 | * @module 32 | * 33 | * Zurk $ API 34 | * 35 | * @example 36 | * ```ts 37 | * import {$} from 'zurk/x' 38 | * 39 | * const p = await $`echo foo`' 40 | * ``` 41 | */ 42 | 43 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 44 | export interface TShellCtxExtra { 45 | } 46 | 47 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 48 | export interface TShellExtra { 49 | } 50 | 51 | export interface TShellOptionsExtra { 52 | timeout?: number 53 | timeoutSignal?: NodeJS.Signals 54 | } 55 | 56 | export interface TShellResponseExtra { 57 | pipe(shell: T): T 58 | pipe(stream: Writable): Writable 59 | pipe(pieces: TemplateStringsArray, ...args: any[]): T 60 | kill(signal?: NodeJS.Signals | null): Promise 61 | abort(): void 62 | timeout?: number 63 | timeoutSignal?: NodeJS.Signals 64 | } 65 | 66 | // eslint-disable-next-line @typescript-eslint/no-empty-interface 67 | export interface TShellCtx extends TZurkCtx, TShellCtxExtra { 68 | timer?: number | NodeJS.Timeout 69 | timeout?: number 70 | timeoutSignal?: NodeJS.Signals 71 | } 72 | 73 | export type TShellOptions = Omit & { 74 | quote?: TQuote 75 | input?: TShellCtx['input'] | TShellResponse | TShellResponseSync | null 76 | } & TShellOptionsExtra 77 | 78 | export interface TShellResponse extends Omit, 'stdio' | 'ctx' | 'child'>, Promise>, TShellResponseExtra { 79 | child: TZurk['child'] 80 | stdio: [Readable | Writable, Writable, Writable] 81 | ctx: TShellCtx 82 | on: (event: string | symbol, listener: TVoidCallback) => TShellResponse 83 | } 84 | 85 | export interface TShellResponseSync extends TZurk, TShellResponseExtra { 86 | } 87 | 88 | export type TMixin = 89 | (($: TShell, target: TShellOptions) => TShellOptions | TZurk | TZurkPromise) | 90 | (($: TShell, target: TZurk, ctx: TShellCtx) => TZurk) | 91 | (($: TShell, target: Promise | TZurkPromise, ctx: TShellCtx) => TZurkPromise) 92 | 93 | export interface TShell extends TShellExtra { 94 | mixins: TMixin[] 95 | (this: O, pieces?: TemplateStringsArray, ...args: any[]): TShellResponse 96 | (this: O, pieces?: TemplateStringsArray, ...args: any[]): R 97 | (opts: O): R 98 | } 99 | 100 | export interface TShellSync { 101 | (this: O, pieces?: TemplateStringsArray, ...args: any[]): TShellResponseSync 102 | (opts: TShellOptions): TShellSync 103 | } 104 | 105 | /** 106 | * Zurk $ template API 107 | * 108 | * @param pieces 109 | * @param args 110 | */ 111 | export const $: TShell = function(this: any, pieces?: any, ...args: any): any { 112 | const self = (this !== g) && this 113 | const preset = self || {} 114 | preset.stack = (preset.stack || getCallerLocation()) 115 | 116 | if (pieces === undefined) return applyMixins($, preset) 117 | 118 | if (isStringLiteral(pieces, ...args)) return ignite(preset, pieces, ...args) 119 | 120 | return (...args: any) => $.apply(self ? assign(self, pieces) : pieces, args) 121 | } 122 | 123 | const ignite = (preset: any, pieces: TemplateStringsArray, ...args: any[]) => { 124 | const _quote = preset.quote || (preset.shell === false ? (arg: string) => arg : quote) 125 | const cmd = buildCmd(_quote, pieces as TemplateStringsArray, args) 126 | const input = parseInput(preset.input) 127 | const run = cmd instanceof Promise 128 | ? (cb: TVoidCallback, ctx: TShellCtx) => cmd.then((cmd) => { ctx.cmd = cmd; cb() }) 129 | : immediate 130 | const opts = assign(preset, { cmd, run, input }) 131 | 132 | return applyMixins($, opts) 133 | } 134 | 135 | const zurkMixin: TMixin = ($: TShell, target: TShellOptions | TZurk | TZurkPromise | Promise) => { 136 | if (isZurkAny(target)) return target 137 | 138 | const result: TZurk | TZurkPromise = zurk(target as TZurkOptions) 139 | return isPromiseLike(result) 140 | ? zurkifyPromise( 141 | (result as TZurkPromise).then((r: TZurk) => applyMixins($, r, result)) as Promise, 142 | result.ctx) 143 | : result as TZurk 144 | } 145 | 146 | $.mixins = [zurkMixin, killMixin, pipeMixin, timeoutMixin] 147 | 148 | /** 149 | * Applies mixins to the result. 150 | * @param $ 151 | * @param result 152 | * @param parent 153 | * @returns TZurk | TZurkPromise | TShellOptions 154 | */ 155 | export const applyMixins = ($: TShell, result: TZurk | TZurkPromise | TShellOptions, parent?: TZurk | TZurkPromise): TZurk | TZurkPromise | TShellOptions => { 156 | let ctx: TShellCtx = (parent as TZurkPromise | TZurk)?.ctx 157 | 158 | return $.mixins.reduce((r, m) => { 159 | ctx = ctx || (r as TZurkPromise | TZurk).ctx 160 | return m($, r as any, ctx) 161 | }, result) 162 | } 163 | -------------------------------------------------------------------------------- /src/main/ts/zurk.ts: -------------------------------------------------------------------------------- 1 | import { 2 | invoke, 3 | normalizeCtx, 4 | asyncVoidCall, 5 | type TSpawnCtxNormalized, 6 | type TSpawnResult, 7 | type TSpawnListeners, 8 | } from './spawn.ts' 9 | import { 10 | isPromiseLike, 11 | makeDeferred, 12 | type Promisified, 13 | type TVoidCallback 14 | } from './util.ts' 15 | import { 16 | formatErrorMessage, 17 | formatExitMessage 18 | } from './error.ts' 19 | 20 | 21 | /** 22 | * @module 23 | * 24 | * Zurk process spawner 25 | * 26 | * @example 27 | * ```ts 28 | * import {zurk} from 'zurk/zurk' 29 | * 30 | * const r1 = zurk({ sync: true, cmd: 'echo', args: ['foo']}) 31 | * const r2 = await zurk({ sync: false, cmd: 'echo', args: ['foo']}) 32 | * ``` 33 | */ 34 | 35 | export const ZURK = Symbol('Zurk') 36 | export const ZURKPROXY = Symbol('ZurkProxy') 37 | 38 | // TODO infer 39 | export interface TZurkOn { 40 | on(name: T, listener: L): R 41 | on(name: T, listener: L): R 42 | on(name: T, listener: L): R 43 | on(name: T, listener: L): R 44 | on(name: T, listener: L): R 45 | on(name: T, listener: L): R 46 | } 47 | 48 | export interface TZurk extends TSpawnResult, TZurkOn { 49 | ctx: TZurkCtx 50 | } 51 | 52 | export type TZurkCtx = TSpawnCtxNormalized & { nothrow?: boolean, nohandle?: boolean } 53 | 54 | export type TZurkOptions = Partial> 55 | 56 | export type TZurkPromise = Promise & Promisified & TZurkOn & { 57 | ctx: TZurkCtx 58 | stdio: TZurkCtx['stdio'] 59 | child: TZurkCtx['child'] 60 | } 61 | 62 | export const zurk = (opts: T): R => 63 | (opts.sync ? zurkSync(opts) : zurkAsync(opts)) as R 64 | 65 | export const zurkAsync = (opts: TZurkOptions): TZurkPromise => { 66 | const { promise, resolve, reject } = makeDeferred() 67 | const ctx: TZurkCtx = normalizeCtx(opts, { 68 | sync: false, 69 | callback(err, data) { 70 | ctx.error = ctx.nohandle ? err : getError(data) 71 | ctx.error && !ctx.nothrow ? reject(ctx.error) : resolve(zurkFactory(ctx)) 72 | } 73 | }) 74 | 75 | invoke(ctx) 76 | 77 | return zurkifyPromise(promise, ctx) 78 | } 79 | 80 | export const zurkSync = (opts: TZurkOptions): TZurk => { 81 | let response: TZurk 82 | const ctx: TZurkCtx = normalizeCtx(opts, { 83 | sync: true, 84 | callback(err, data) { 85 | ctx.error = ctx.nohandle ? err : getError(data) 86 | if (ctx.error && !ctx.nothrow) throw ctx.error 87 | response = zurkFactory(ctx) 88 | } 89 | }) 90 | 91 | invoke(ctx) 92 | 93 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 94 | // @ts-ignore 95 | return response as TZurk 96 | } 97 | 98 | // eslint-disable-next-line sonarjs/cognitive-complexity 99 | export const zurkifyPromise = (target: Promise | TZurkPromise, ctx: TSpawnCtxNormalized): TZurkPromise => { 100 | if (isZurkProxy(target) || !isPromiseLike(target)) 101 | return target as TZurkPromise 102 | 103 | const proxy = new Proxy(target, { 104 | get(target: Promise, p: string | symbol, receiver: any): any { 105 | if (p === ZURKPROXY || p === ZURK) return p 106 | if (p === 'then') return target.then.bind(target) 107 | if (p === 'catch') return target.catch.bind(target) 108 | if (p === 'finally') return (cb: TVoidCallback) => proxy.then(asyncVoidCall(cb), asyncVoidCall(cb)) 109 | if (p === 'stdio') return ctx.stdio 110 | if (p === 'ctx') return ctx 111 | if (p === 'child') return ctx.child 112 | if (p === 'on') return function (name: string, cb: VoidFunction){ ctx.ee.on(name, cb); return proxy } 113 | 114 | if (p in target) return Reflect.get(target, p, receiver) 115 | 116 | return target.then(v => Reflect.get(v, p, receiver)) 117 | } 118 | }) as TZurkPromise 119 | 120 | return proxy 121 | } 122 | 123 | export const getError = (spawnResult: TSpawnResult): Error | null => { 124 | if (spawnResult.error) 125 | return new Error(formatErrorMessage(spawnResult.error, spawnResult.ctx.stack)) 126 | if (spawnResult.status || spawnResult.signal) 127 | return new Error(formatExitMessage(spawnResult.status, spawnResult.signal, spawnResult.stderr, spawnResult.ctx.stack)) 128 | 129 | return null 130 | } 131 | 132 | export const isZurkAny = (o: any): o is TZurk | TZurkPromise => o?.[ZURK] === ZURK 133 | export const isZurk = (o: any): o is TZurk => isZurkAny(o) && !(o instanceof Promise) 134 | export const isZurkPromise = (o: any): o is TZurkPromise => isZurkAny(o) && o instanceof Promise 135 | export const isZurkProxy = (value: any): boolean => value?.[ZURKPROXY] === ZURKPROXY 136 | 137 | export const zurkFactory = (ctx: C): TZurk => new Zurk(ctx) 138 | 139 | class Zurk implements TZurk { 140 | [ZURK] = ZURK 141 | ctx: TZurkCtx 142 | constructor(ctx: TZurkCtx) { 143 | this.ctx = ctx 144 | } 145 | on(name: string, cb: TVoidCallback): this { this.ctx.ee.on(name, cb); return this } 146 | get child() { return this.ctx.child } 147 | get status() { return this.ctx.fulfilled?.status ?? null } 148 | get signal() { return this.ctx.fulfilled?.signal ?? null } 149 | get error() { return this.ctx.error } 150 | get stderr() { return this.ctx.fulfilled?.stderr || '' } 151 | get stdout() { return this.ctx.fulfilled?.stdout || '' } 152 | get stdall() { return this.ctx.fulfilled?.stdall || '' } 153 | get stdio(): TSpawnResult['stdio'] { return [ 154 | this.ctx.stdin, 155 | this.ctx.stdout, 156 | this.ctx.stderr 157 | ]} 158 | get duration() { return this.ctx.fulfilled?.duration ?? 0 } 159 | toString(){ return this.stdall.trim() } 160 | valueOf(){ return this.stdall.trim() } 161 | } 162 | -------------------------------------------------------------------------------- /src/main/typedoc/typedoc.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zurk", 3 | "out": "../../../target/docs", 4 | "entryPoints": ["../../../src/main/ts/"], 5 | "exclude": ["../../../src/test", "**/node_modules/**"], 6 | "externalPattern": ["**/node_modules/**"], 7 | "excludeExternals": true, 8 | "excludePrivate": true, 9 | "excludeProtected": true, 10 | "includeVersion": true, 11 | "hideGenerator": true, 12 | "readme": "../../../README.md", 13 | "tsconfig": "../../../tsconfig.json", 14 | "theme": "default" 15 | } 16 | -------------------------------------------------------------------------------- /src/scripts/build-jsr.mjs: -------------------------------------------------------------------------------- 1 | import fs from 'fs' 2 | import path from 'path' 3 | const cwd = process.cwd() 4 | const pkgJson = JSON.parse(fs.readFileSync(path.resolve(cwd, 'package.json'), 'utf-8')) 5 | 6 | fs.writeFileSync(path.resolve(cwd, 'jsr.json'), JSON.stringify({ 7 | name: '@webpod/zurk', 8 | version: pkgJson.version, 9 | exports: { 10 | '.': './src/main/ts/index.ts', 11 | './spawn': './src/main/ts/spawn.ts', 12 | './util': './src/main/ts/util.ts', 13 | './zurk': './src/main/ts/zurk.ts' 14 | }, 15 | publish: { 16 | include: [ 17 | 'src/main/ts', 18 | 'README.md' 19 | ] 20 | } 21 | }, null, 2)) 22 | -------------------------------------------------------------------------------- /src/scripts/build.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import process from 'node:process' 4 | import esbuild from 'esbuild' 5 | import { nodeExternalsPlugin } from 'esbuild-node-externals' 6 | import { entryChunksPlugin } from 'esbuild-plugin-entry-chunks' 7 | import { transformHookPlugin } from 'esbuild-plugin-transform-hook' 8 | import { extractHelpersPlugin } from 'esbuild-plugin-extract-helpers' 9 | import { injectFile } from 'esbuild-plugin-utils' 10 | import minimist from 'minimist' 11 | import glob from 'fast-glob' 12 | import path from "node:path"; 13 | 14 | const unwrapQuotes = str => str.replace(/^['"]|['"]$/g, '') 15 | const argv = minimist(process.argv.slice(2), { 16 | default: { 17 | entry: './src/main/ts/index.ts', 18 | external: 'node:*', 19 | bundle: 'src', // 'all' | 'none' 20 | license: 'eof', 21 | minify: false, 22 | sourcemap: false, 23 | format: 'cjs,esm', 24 | cwd: process.cwd() 25 | }, 26 | boolean: ['minify', 'sourcemap', 'banner'], 27 | string: ['entry', 'external', 'bundle', 'license', 'format', 'map', 'cwd'] 28 | }) 29 | const { entry, external, bundle, minify, sourcemap, license, format, cwd: _cwd } = argv 30 | const plugins = [] 31 | const cwd = Array.isArray(_cwd) ? _cwd[_cwd.length - 1] : _cwd 32 | const entryPoints = entry.includes('*') 33 | ? await glob(unwrapQuotes(entry).split(':'), { absolute: false, onlyFiles: true, cwd }) 34 | : unwrapQuotes(entry).split(':') 35 | 36 | const _bundle = bundle !== 'none' && !process.argv.includes('--no-bundle') 37 | const _external = _bundle 38 | ? external.split(',') 39 | : undefined // https://github.com/evanw/esbuild/issues/1466 40 | 41 | if (_bundle && entryPoints.length > 1) { 42 | plugins.push(entryChunksPlugin()) 43 | } 44 | 45 | if (bundle === 'src') { 46 | // https://github.com/evanw/esbuild/issues/619 47 | // https://github.com/pradel/esbuild-node-externals/pull/52 48 | plugins.push(nodeExternalsPlugin()) 49 | } 50 | 51 | const cjsPlugins = [ 52 | extractHelpersPlugin({ 53 | helper: 'cjslib.cjs', 54 | cwd: 'target/cjs', 55 | include: /\.cjs/, 56 | }), 57 | transformHookPlugin({ 58 | hooks: [ 59 | { 60 | on: 'end', 61 | pattern: /cjslib/, 62 | transform(contents) { 63 | return injectFile(contents.toString(), './src/scripts/object.polyfill.cjs') 64 | }, 65 | }, 66 | { 67 | on: 'end', 68 | pattern: entryPointsToRegexp(entryPoints), 69 | transform(contents, p) { 70 | return contents 71 | .toString() 72 | .replaceAll('"node:', '"') 73 | .replace( 74 | /0 && \(module\.exports =(.|\n)+/, 75 | ($0) => { 76 | if (!$0.includes('...')) return $0 77 | 78 | const lines = $0.split('\n').slice(1, -1) 79 | const vars = [] 80 | const reexports = [] 81 | lines.forEach((l) => { 82 | const e = /\s*\.{3}(require\(.+\))/.exec(l)?.[1] 83 | if (e) { 84 | reexports.push(e) 85 | } else { 86 | vars.push(l) 87 | } 88 | }) 89 | 90 | return `0 && (module.exports = Object.assign({ 91 | ${vars.join('\n')} 92 | }, ${reexports.join(',\n')}))` 93 | } 94 | ) 95 | }, 96 | },] 97 | }) 98 | ] 99 | 100 | const formats = format.split(',') 101 | 102 | const esmConfig = { 103 | absWorkingDir: cwd, 104 | entryPoints, 105 | outdir: './target/esm', 106 | bundle: _bundle, 107 | external: _external, 108 | minify, 109 | sourcemap, 110 | sourcesContent: false, 111 | platform: 'node', 112 | target: 'es2019', 113 | format: 'esm', 114 | outExtension: { 115 | '.js': '.mjs' 116 | }, 117 | plugins, 118 | legalComments: license, 119 | tsconfig: './tsconfig.json', 120 | 121 | } 122 | 123 | const cjsConfig = { 124 | ...esmConfig, 125 | outdir: './target/cjs', 126 | target: 'es2015', 127 | format: 'cjs', 128 | banner: {}, 129 | outExtension: { 130 | '.js': '.cjs' 131 | }, 132 | plugins: [ 133 | ...plugins, 134 | ...cjsPlugins 135 | ], 136 | } 137 | 138 | for (const format of formats) { 139 | const config = format === 'cjs' ? cjsConfig : esmConfig 140 | 141 | await esbuild 142 | .build(config) 143 | .catch((e) => { 144 | console.error(e) 145 | process.exit(1) 146 | }) 147 | } 148 | 149 | function entryPointsToRegexp(entryPoints) { 150 | return new RegExp( 151 | '(' + entryPoints.map((e) => escapeRegExp(path.parse(e).name)).join('|') + ')\\.cjs$' 152 | ) 153 | } 154 | 155 | function escapeRegExp(str) { 156 | return str.replace(/[/\-\\^$*+?.()|[\]{}]/g, '\\$&') 157 | } 158 | 159 | process.exit(0) 160 | -------------------------------------------------------------------------------- /src/scripts/object.polyfill.cjs: -------------------------------------------------------------------------------- 1 | Object.getOwnPropertyDescriptors = Object.getOwnPropertyDescriptors || function(obj) { 2 | if (obj === null || obj === void 0) throw new TypeError("Cannot convert undefined or null to object"); 3 | const protoPropDescriptor = Object.getOwnPropertyDescriptor(obj, "__proto__"); 4 | const descriptors = protoPropDescriptor ? { ["__proto__"]: protoPropDescriptor } : {}; 5 | for (const name of Object.getOwnPropertyNames(obj)) { 6 | descriptors[name] = Object.getOwnPropertyDescriptor(obj, name); 7 | } 8 | return descriptors; 9 | }; 10 | 11 | Object.entries = Object.entries || function (obj) { 12 | return Object.keys(obj).map((key) => [key, obj[key]]); 13 | }; 14 | 15 | Object.fromEntries = Object.fromEntries || function (entries) { 16 | return [...entries].reduce((obj, [key, val]) => { 17 | obj[key] = val; 18 | return obj; 19 | }, {}); 20 | }; 21 | -------------------------------------------------------------------------------- /src/scripts/test.mjs: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import glob from 'fast-glob' 4 | import { pathToFileURL } from 'node:url' 5 | import process from 'node:process' 6 | 7 | const focused = process.argv.slice(2) 8 | const suites = focused.length ? focused : await glob('src/test/**/*.test.{ts,cjs,mjs,js}', {cwd: process.cwd(), absolute: true, onlyFiles: true}) 9 | 10 | await Promise.all(suites.map(suite => import(pathToFileURL(suite)))) 11 | -------------------------------------------------------------------------------- /src/test/fixtures/foo.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": "foobar" 3 | } -------------------------------------------------------------------------------- /src/test/js/index.test.cjs: -------------------------------------------------------------------------------- 1 | const assert = require('node:assert') 2 | const { describe, it } = require('node:test') 3 | const { $ } = require('zurk') 4 | const { buildCmd } = require('zurk/util') 5 | 6 | describe('cjs entry', () => { 7 | it('$ is callable', () => { 8 | assert.equal(typeof $, 'function') 9 | }) 10 | it('buildCmd is callable', () => { 11 | assert.equal(typeof buildCmd, 'function') 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /src/test/js/index.test.mjs: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert' 2 | import { describe, it } from 'node:test' 3 | import { $ } from 'zurk' 4 | import { buildCmd } from 'zurk/util' 5 | 6 | describe('mjs entry', () => { 7 | it('$ is callable', () => { 8 | assert.equal(typeof $, 'function') 9 | }) 10 | it('buildCmd is callable', () => { 11 | assert.equal(typeof buildCmd, 'function') 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /src/test/lint/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint-config-qiwi"], 3 | "rules": {}, 4 | "overrides": [ 5 | { 6 | "files": ["src/scripts/build-from-remote.mts"], 7 | "rules": { 8 | "sonarjs/no-duplicate-string": "off" 9 | } 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /src/test/smoke/invoke.test.cjs: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const { zurk, $ } = require('../../../target/cjs/index.cjs') 3 | const keepAlive = setInterval(() => {}, 1000 * 60 * 60) 4 | 5 | const r1 = zurk({ sync: true, cmd: 'echo', args: ['foo'] }) 6 | assert.ok(/foo/.test(r1.stdout)) 7 | assert.equal(r1.status, 0) 8 | 9 | const r2 = $({sync: true})`echo bar` 10 | assert.ok(/bar/.test(r2.stdout)) 11 | assert.equal(r2.status, 0); 12 | 13 | $`echo baz` 14 | .then((r3) => { 15 | clearInterval(keepAlive) 16 | 17 | assert.ok(/baz/.test(r3.stdout)) 18 | assert.equal(r3.status, 0) 19 | }) 20 | 21 | console.log('nodejs:', process.versions.node) 22 | console.log('smoke cjs: ok') 23 | -------------------------------------------------------------------------------- /src/test/smoke/invoke.test.mjs: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert' 2 | import process from 'node:process' 3 | import { zurk, $ } from '../../../target/esm/index.mjs' 4 | 5 | const keepAlive = setInterval(() => {}, 1000 * 60 * 60) 6 | 7 | const r1 = zurk({ sync: true, cmd: 'echo', args: ['foo'] }) 8 | assert.ok(/foo/.test(r1.stdout)) 9 | assert.equal(r1.status, 0) 10 | 11 | const r2 = $({sync: true})`echo bar` 12 | assert.ok(/bar/.test(r2.stdout)) 13 | assert.equal(r2.status, 0); 14 | 15 | $`echo baz` 16 | .then((r3) => { 17 | clearInterval(keepAlive) 18 | assert.ok(/baz/.test(r3.stdout)) 19 | assert.equal(r3.status, 0) 20 | }) 21 | 22 | console.log('nodejs:', process.versions.node) 23 | console.log('smoke mjs: ok') 24 | -------------------------------------------------------------------------------- /src/test/ts/error.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'node:assert' 2 | import { describe, it } from 'node:test' 3 | import { 4 | getCallerLocation, 5 | getCallerLocationFromString, 6 | getExitCodeInfo, 7 | getErrnoMessage, 8 | formatErrorMessage, 9 | formatExitMessage, 10 | EXIT_CODES, 11 | ERRNO_CODES 12 | } from '../../main/ts/error.ts' 13 | 14 | import * as all from '../../main/ts/error.ts' 15 | 16 | describe('error', () => { 17 | it('has proper exports', () => { 18 | assert.equal(typeof getCallerLocation, 'function') 19 | assert.equal(typeof getCallerLocationFromString, 'function') 20 | assert.equal(typeof getExitCodeInfo, 'function') 21 | assert.equal(typeof getErrnoMessage, 'function') 22 | assert.equal(typeof formatErrorMessage, 'function') 23 | assert.equal(typeof formatExitMessage, 'function') 24 | assert.equal(typeof EXIT_CODES, 'object') 25 | assert.equal(typeof ERRNO_CODES, 'object') 26 | assert.equal(Object.keys(all).length, 8) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /src/test/ts/index.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'node:assert' 2 | import { describe, it } from 'node:test' 3 | import { invoke, zurk, $, buildCmd, exec, defaults, isStringLiteral, VoidStream } from '../../main/ts/index.ts' 4 | import * as all from '../../main/ts/index.ts' 5 | 6 | describe('index', () => { 7 | it('has proper exports', () => { 8 | assert.equal(typeof defaults, 'object') 9 | assert.equal(typeof $, 'function') 10 | assert.equal(typeof zurk, 'function') 11 | assert.equal(typeof exec, 'function') 12 | assert.equal(typeof invoke, 'function') 13 | assert.equal(typeof buildCmd, 'function') 14 | assert.equal(typeof VoidStream, 'function') 15 | assert.equal(typeof isStringLiteral, 'function') 16 | assert.equal(Object.keys(all).length, 10) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /src/test/ts/spawn.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'node:assert' 2 | import { describe, it } from 'node:test' 3 | import EventEmitter from 'node:events' 4 | import { 5 | exec, 6 | invoke, 7 | normalizeCtx, 8 | TSpawnCtx, 9 | TSpawnResult, 10 | TSpawnStore 11 | } from '../../main/ts/spawn.ts' 12 | import { makeDeferred } from '../../main/ts/util.ts' 13 | 14 | describe('invoke()', () => { 15 | it('calls a given cmd', async () => { 16 | const results: string[] = [] 17 | const callback: TSpawnCtx['callback'] = (_err, result) => results.push(result.stdout) 18 | const { promise, resolve, reject } = makeDeferred() 19 | 20 | invoke(normalizeCtx({ 21 | sync: true, 22 | cmd: 'echo', 23 | args: ['hello'], 24 | callback, 25 | })) 26 | 27 | invoke(normalizeCtx({ 28 | sync: false, 29 | cmd: 'echo', 30 | args: ['world'], 31 | callback(err, result) { 32 | err ? reject(err) : resolve(result) 33 | }, 34 | })) 35 | 36 | await promise.then((result) => callback(null, result)) 37 | 38 | console.log(results) 39 | }) 40 | 41 | it('supports stdin injection', async () => { 42 | const {promise, resolve, reject} = makeDeferred() 43 | const input = '{"name": "world"}' 44 | invoke(normalizeCtx({ 45 | sync: false, 46 | input, 47 | cmd: 'jq', 48 | args: ['-r', '.name'], 49 | callback(err, result) { 50 | err ? reject(err) : resolve(result.stdout) 51 | } 52 | })) 53 | 54 | const name = await promise 55 | assert.equal(name.trim(), 'world') 56 | }) 57 | }) 58 | 59 | describe('normalizeCtx()', () => { 60 | it('normalizes ctx', () => { 61 | const signal = new AbortController().signal 62 | const cwds = ['a', 'b', 'c'] 63 | const ctx = { 64 | cmd: 'foo', 65 | signal, 66 | get cwd () { 67 | return cwds.shift() || process.cwd() 68 | }, 69 | } 70 | const normalized = normalizeCtx(ctx) 71 | assert.equal(normalized.cwd, 'a') 72 | assert.equal(normalized.cwd, 'b') 73 | assert.equal(normalized.cwd, 'c') 74 | assert.equal(normalized.signal, signal) 75 | assert.ok(normalized.ee instanceof EventEmitter) 76 | assert.ok(normalized.ac instanceof AbortController) 77 | assert.deepEqual(normalized.store.stdout, []) 78 | assert.deepEqual(normalized.store.stderr, []) 79 | }) 80 | }) 81 | 82 | describe('exec()', () => { 83 | it('supports custom stores', async () => { 84 | // eslint-disable-next-line unicorn/consistent-function-scoping 85 | const getFixedSizeArray = (size: number) => { 86 | const arr: any[] = [] 87 | return new Proxy(arr, { 88 | get: (target: any, prop) => 89 | prop === 'push' && arr.length >= size 90 | ? () => { /* noop */ } 91 | : target[prop] 92 | }) 93 | } 94 | const { promise, resolve, reject } = makeDeferred() 95 | const callback: TSpawnCtx['callback'] = (err, result) => err ? reject(err) : resolve(result) 96 | const store: TSpawnStore = { 97 | stdout: getFixedSizeArray(1), 98 | stderr: getFixedSizeArray(2), 99 | stdall: getFixedSizeArray(0) 100 | } 101 | 102 | const ctx = exec({sync: false, callback, store, cmd: 'echo', args: ['hello']}) 103 | const result = await promise 104 | 105 | assert.equal(ctx.store.stdall.join(''), '') 106 | assert.equal(ctx.store.stdout.join('').trim(), 'hello') 107 | assert.equal([...ctx.store.stdout].length, 1) 108 | assert.equal(result.stdout.trim(), 'hello') 109 | assert.equal(result.stdall, '') 110 | }) 111 | }) 112 | -------------------------------------------------------------------------------- /src/test/ts/util.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'node:assert' 2 | import {describe, it, test} from 'node:test' 3 | import { assign, isStringLiteral, randomId, quote, quotePwsh } from '../../main/ts/util.ts' 4 | import tslib from 'tslib' 5 | 6 | describe('util', () => { 7 | it('assign()', () => { 8 | assert.deepEqual(assign({a: 1}, {b: 2}), {a: 1, b: 2}) 9 | assert.deepEqual(assign({a: 1}, {a: undefined}), {a: 1}) 10 | }) 11 | 12 | it('randomId()', () => { 13 | assert.match(randomId(), /^[\da-z]+$/) 14 | }) 15 | 16 | test('isStringLiteral()', () => { 17 | const bar = 'baz' 18 | assert.ok(isStringLiteral``) 19 | assert.ok(isStringLiteral`foo`) 20 | assert.ok(isStringLiteral`foo ${bar}`) 21 | assert.ok(isStringLiteral(tslib.__makeTemplateObject(["git pull --tags --force ", " ", ""], ["git pull --tags --force ", " ", ""]), 'foo', 'bar')) 22 | 23 | assert.ok(!isStringLiteral('')) 24 | assert.ok(!isStringLiteral('foo')) 25 | assert.ok(!isStringLiteral(['foo'])) 26 | }) 27 | 28 | test('quotePwsh()', () => { 29 | assert.equal(quotePwsh(''), "''") 30 | assert.equal(quotePwsh('--foo'), '--foo') 31 | assert.equal(quotePwsh('foo bar\r\nbaz'), "'foo bar\r\nbaz'") 32 | }) 33 | 34 | test('quote()', () => { 35 | assert.equal(quote(''), "$''") 36 | assert.equal(quote('foo bar\r\nbaz'), "$'foo bar\\r\\nbaz'") 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /src/test/ts/x.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'node:assert' 2 | import * as fs from 'node:fs' 3 | import * as os from 'node:os' 4 | import path from 'node:path' 5 | import { describe, it } from 'node:test' 6 | import { Stream } from 'node:stream' 7 | import { getEventListeners } from 'node:events' 8 | import { $ } from '../../main/ts/x.ts' 9 | import { quote, quotePwsh } from '../../main/ts/util.ts' 10 | 11 | const __dirname = new URL('.', import.meta.url).pathname 12 | const fixtures = path.resolve(__dirname, '../fixtures') 13 | const tempy = fs.mkdtempSync(path.join(os.tmpdir(), 'tempy-')) 14 | const onStreamFinish = (stream: Stream) => new Promise((resolve) => stream.on('finish', resolve)) 15 | const throwError = (err: any = new Error('should have thrown')) => { throw err } 16 | 17 | describe('$()', () => { 18 | it('supports async flow', async () => { 19 | const p = $`echo foo` 20 | 21 | assert.equal((await p).toString(), 'foo') 22 | assert.equal(await p.stdout, 'foo\n') 23 | assert.equal(await p.stderr, '') 24 | assert.equal(await p.status, 0) 25 | 26 | try { 27 | await $`exit 2` 28 | } catch (error: unknown) { 29 | console.error(error) 30 | assert.ok((error as Error).message.includes('exit code: 2 (Misuse of shell builtins)')) 31 | } 32 | 33 | const err = await $`exit 2`.catch((error) => error) 34 | assert.ok(err.message.includes('exit code: 2 (Misuse of shell builtins)')) 35 | }) 36 | 37 | it('supports sync flow', () => { 38 | const p = $({sync: true})`echo foo` 39 | 40 | assert.equal(p.toString(), 'foo') 41 | assert.equal(p.stdout, 'foo\n') 42 | assert.equal(p.stderr, '') 43 | assert.deepEqual(p.stdall, 'foo\n') 44 | 45 | try { 46 | $({sync: true})`exit 2` 47 | } catch (error: unknown) { 48 | assert.match((error as Error).message, /exit code: 2 \(Misuse of shell builtins\)/) 49 | } 50 | }) 51 | 52 | it('handles promises in cmd literal', async () => { 53 | const example = $`echo example` 54 | 55 | // eslint-disable-next-line sonarjs/no-nested-template-literals 56 | assert.equal((await $`echo ${example} ${$`echo and`} ${await example}`) 57 | .toString(), 'example and example') 58 | }) 59 | 60 | it('supports stdin', async () => { 61 | const input = '{"name": "foo"}' 62 | const name = await $({input})`jq -r .name` 63 | assert.equal(name.toString().trim(), 'foo') 64 | 65 | const stdin = fs.createReadStream(path.resolve(fixtures, 'foo.json')) 66 | const data = await $({stdin})`jq -r .data` 67 | assert.equal(data.toString().trim(), 'foobar') 68 | 69 | const p = $`echo "5\\n3\\n1\\n4\\n2"` 70 | const sorted = $({input: p})`sort` 71 | 72 | assert.equal((await sorted).toString(), '1\n2\n3\n4\n5') 73 | }) 74 | 75 | it('handles custom stdio', async () => { 76 | await $({stdio: ['inherit', 'inherit', 'inherit']})`ls` 77 | await $({stdio: 'ignore'})`ls` 78 | $({stdio: 'ignore', sync: true})`ls` 79 | }) 80 | 81 | it('works without shell', async () => { 82 | const o1 = await $({shell: true})`exit 2 | exit 0` 83 | const o2 = await $({shell: false, nothrow: true})`exit 1 | exit 0` 84 | 85 | assert.equal(o1.status, 0) 86 | assert.equal(o2.status, -2) 87 | }) 88 | 89 | it('supports presets', () => { 90 | const $$ = $({sync: true, cmd: 'echo foo'}) 91 | const $$$ = $$({cmd: 'echo bar'}) 92 | const p1 = $$() 93 | const p2 = $$$() 94 | const p3 = $$`echo baz` 95 | const p4 = $$$({cmd: 'echo qux'})() 96 | const o1 = p1.stdout 97 | const o2 = p2.stdout 98 | const o3 = p3.stdout 99 | const o4 = p4.stdout 100 | 101 | assert.equal(o1.trim(), 'foo') 102 | assert.equal(o2.trim(), 'bar') 103 | assert.equal(o3.trim(), 'baz') 104 | assert.equal(o4.trim(), 'qux') 105 | }) 106 | 107 | it('accepts custom quote', async () => { 108 | const arg = 'foo bar' 109 | const p1 = $({quote}) 110 | const p2 = $({quote: quotePwsh}) 111 | 112 | assert.equal(p1`echo ${arg}`.ctx.cmd, "echo $'foo bar'") 113 | assert.equal(p2`echo ${arg}`.ctx.cmd, "echo 'foo bar'") 114 | 115 | await p1 116 | await p2 117 | }) 118 | }) 119 | 120 | describe('mixins', () => { 121 | describe('kill', () => { 122 | it('handles `kill`', async () => { 123 | const p = $({nothrow: true})`sleep 10` 124 | let killed 125 | setTimeout(() => killed = p.kill(), 25) 126 | 127 | const { error } = await p 128 | const signal = await killed 129 | 130 | assert.equal(signal, 'SIGTERM') 131 | assert.ok(error.message.includes('signal: SIGTERM')) 132 | }) 133 | 134 | it('handles `abort`', async () => { 135 | const p = $({nothrow: true})`sleep 10` 136 | const events: any[] = [] 137 | let c = 0 138 | 139 | setTimeout(() => p.abort(), 25) 140 | setTimeout(() => c = getEventListeners(p.ctx.signal, 'abort').length, 10) 141 | p 142 | .on('abort', () => events.push('abort')) 143 | .on('end', () => events.push('end')) 144 | 145 | const { error } = await p 146 | assert.ok(getEventListeners(p.ctx.signal, 'abort').length < c) 147 | assert.ok(error.message.startsWith('The operation was aborted')) 148 | assert.match(error.message, /code: ABORT_ERR/) 149 | assert.deepEqual(events, ['abort', 'end']) 150 | }) 151 | }) 152 | 153 | describe('timeout', () => { 154 | it('handles `timeout` as option', async () => { 155 | const p = $({ timeout: 25, timeoutSignal: 'SIGALRM', nothrow: true })`sleep 10` 156 | 157 | const { error } = await p 158 | assert.ok(error.message.includes('signal: SIGALRM')) 159 | }) 160 | 161 | it('handles `timeout` as promise setter', async () => { 162 | const p = $`sleep 10` 163 | p.timeoutSignal = 'SIGALRM' 164 | p.timeout = 25 165 | p.ctx.nothrow = true 166 | 167 | const { error } = await p 168 | assert.ok(error.message.includes('signal: SIGALRM')) 169 | }) 170 | }) 171 | 172 | describe('pipe', () => { 173 | it('supports async flow', async () => { 174 | const result = $`echo "5\\n3\\n1\\n4\\n2"` 175 | const expected = '1\n2\n3\n4\n5' 176 | const writable1 = fs.createWriteStream(path.join(tempy, 'output1.txt')) 177 | const writable2 = fs.createWriteStream(path.join(tempy, 'output2.txt')) 178 | const w1 = onStreamFinish(writable1) 179 | const w2 = onStreamFinish(writable1) 180 | 181 | const piped0 = result.pipe`sort | cat` 182 | const piped1 = result.pipe`sort`.pipe`cat` 183 | const piped2 = result.pipe(writable2) 184 | const piped3 = result.pipe($`sort`) 185 | const piped4 = (await result).pipe`sort` 186 | const piped5 = result.pipe($`sort`) 187 | const piped6 = (await result.pipe`sort`).pipe(writable1) 188 | 189 | assert.equal(piped6, writable1) 190 | assert.equal(piped2, writable2) 191 | assert.equal((await piped0).toString(), expected) 192 | assert.equal((await piped1).toString(), expected) 193 | assert.equal((await piped3).toString(), expected) 194 | assert.equal((await piped4).toString(), expected) 195 | assert.equal((await piped5).toString(), expected) 196 | 197 | await w1 198 | await w2 199 | assert.equal(fs.readFileSync(path.join(tempy, 'output1.txt'), 'utf8').trim(), expected) 200 | assert.equal(fs.readFileSync(path.join(tempy, 'output2.txt'), 'utf8').trim(), '5\n3\n1\n4\n2') 201 | }) 202 | 203 | it('supports sync flow', async () => { 204 | const result = $({sync: true})`echo "5\\n3\\n1\\n4\\n2"` 205 | assert.equal(result.toString().trim(), '5\n3\n1\n4\n2') 206 | 207 | const expected = '1\n2\n3\n4\n5' 208 | const piped = result.pipe`sort` 209 | 210 | assert.equal(piped.toString(), expected) 211 | }) 212 | 213 | it('supports multipiping', async () => { 214 | const result = $`echo 1; sleep 1; echo 2; sleep 1; echo 3` 215 | const piped1 = result.pipe`cat` 216 | let piped2: any 217 | 218 | setTimeout(() => { 219 | piped2 = result.pipe`cat` 220 | }, 1500) 221 | 222 | await piped1 223 | assert.equal((await piped1).toString(), '1\n2\n3') 224 | assert.equal((await piped2).toString(), '1\n2\n3') 225 | }) 226 | }) 227 | }) 228 | -------------------------------------------------------------------------------- /src/test/ts/zurk.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'node:assert' 2 | import { describe, it } from 'node:test' 3 | import { zurk, isZurk, isZurkPromise } from '../../main/ts/zurk.ts' 4 | import type { TSpawnResult } from '../../main/ts/spawn.ts' 5 | 6 | describe('zurk()', () => { 7 | it('sync returns Zurk instance', async () => { 8 | const result = zurk({ sync: true, cmd: 'echo', args: ['foo']}) 9 | assert.equal(result.toString(), 'foo') 10 | assert.equal(result.stdout, 'foo\n') 11 | assert.equal(result.status, 0) 12 | assert.equal(result.signal, null) 13 | assert.ok(isZurk(result)) 14 | }) 15 | 16 | it('async returns ZurkPromise', async () => { 17 | const result = zurk({ sync: false, cmd: 'echo', args: ['foo']}) 18 | let _result: TSpawnResult 19 | 20 | result.on('end', data => _result = data) 21 | 22 | assert.equal(result.child, result.ctx.child) 23 | assert.equal((await result).toString(), 'foo') 24 | assert.equal((await result).stdout, 'foo\n') 25 | assert.equal(await result.stdout, 'foo\n') 26 | assert.equal(await result.status, 0) 27 | assert.equal(await result.signal, null) 28 | assert.ok(isZurkPromise(result)) 29 | assert.ok(isZurk(await result)) 30 | 31 | // @ts-expect-error should be resolved by now 32 | assert.equal(_result.stdout, 'foo\n') 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /target/cjs/cjslib.cjs: -------------------------------------------------------------------------------- 1 | Object.getOwnPropertyDescriptors = Object.getOwnPropertyDescriptors || function(obj) { 2 | if (obj === null || obj === void 0) throw new TypeError("Cannot convert undefined or null to object"); 3 | const protoPropDescriptor = Object.getOwnPropertyDescriptor(obj, "__proto__"); 4 | const descriptors = protoPropDescriptor ? { ["__proto__"]: protoPropDescriptor } : {}; 5 | for (const name of Object.getOwnPropertyNames(obj)) { 6 | descriptors[name] = Object.getOwnPropertyDescriptor(obj, name); 7 | } 8 | return descriptors; 9 | }; 10 | 11 | Object.entries = Object.entries || function (obj) { 12 | return Object.keys(obj).map((key) => [key, obj[key]]); 13 | }; 14 | 15 | Object.fromEntries = Object.fromEntries || function (entries) { 16 | return [...entries].reduce((obj, [key, val]) => { 17 | obj[key] = val; 18 | return obj; 19 | }, {}); 20 | }; 21 | 22 | 23 | var __defProp = Object.defineProperty; 24 | 25 | var __getOwnPropDesc = Object.getOwnPropertyDescriptor; 26 | 27 | var __getOwnPropNames = Object.getOwnPropertyNames; 28 | 29 | var __hasOwnProp = Object.prototype.hasOwnProperty; 30 | 31 | var __export = (target, all) => { 32 | for (var name in all) 33 | __defProp(target, name, { get: all[name], enumerable: true }); 34 | }; 35 | 36 | var __copyProps = (to, from, except, desc) => { 37 | if (from && typeof from === "object" || typeof from === "function") { 38 | for (let key of __getOwnPropNames(from)) 39 | if (!__hasOwnProp.call(to, key) && key !== except) 40 | __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); 41 | } 42 | return to; 43 | }; 44 | 45 | var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); 46 | 47 | var __create = Object.create; 48 | 49 | var __getProtoOf = Object.getPrototypeOf; 50 | 51 | var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( 52 | // If the importer is in node compatibility mode or this is not an ESM 53 | // file that has been converted to a CommonJS file using a Babel- 54 | // compatible transform (i.e. "__esModule" has not been set), then set 55 | // "default" to the CommonJS "module.exports" for node compatibility. 56 | isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, 57 | mod 58 | )); 59 | 60 | var __defProps = Object.defineProperties; 61 | 62 | var __getOwnPropDescs = Object.getOwnPropertyDescriptors; 63 | 64 | var __getOwnPropSymbols = Object.getOwnPropertySymbols; 65 | 66 | var __propIsEnum = Object.prototype.propertyIsEnumerable; 67 | 68 | var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; 69 | 70 | var __spreadValues = (a, b) => { 71 | for (var prop in b || (b = {})) 72 | if (__hasOwnProp.call(b, prop)) 73 | __defNormalProp(a, prop, b[prop]); 74 | if (__getOwnPropSymbols) 75 | for (var prop of __getOwnPropSymbols(b)) { 76 | if (__propIsEnum.call(b, prop)) 77 | __defNormalProp(a, prop, b[prop]); 78 | } 79 | return a; 80 | }; 81 | 82 | var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b)); 83 | 84 | var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default")); 85 | 86 | var __async = (__this, __arguments, generator) => { 87 | return new Promise((resolve, reject) => { 88 | var fulfilled = (value) => { 89 | try { 90 | step(generator.next(value)); 91 | } catch (e) { 92 | reject(e); 93 | } 94 | }; 95 | var rejected = (value) => { 96 | try { 97 | step(generator.throw(value)); 98 | } catch (e) { 99 | reject(e); 100 | } 101 | }; 102 | var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); 103 | step((generator = generator.apply(__this, __arguments)).next()); 104 | }); 105 | }; 106 | 107 | var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); 108 | 109 | module.exports = { 110 | __defProp, 111 | __getOwnPropDesc, 112 | __getOwnPropNames, 113 | __hasOwnProp, 114 | __export, 115 | __copyProps, 116 | __toCommonJS, 117 | __create, 118 | __getProtoOf, 119 | __toESM, 120 | __defProps, 121 | __getOwnPropDescs, 122 | __getOwnPropSymbols, 123 | __propIsEnum, 124 | __defNormalProp, 125 | __spreadValues, 126 | __spreadProps, 127 | __reExport, 128 | __async, 129 | __publicField 130 | }; 131 | -------------------------------------------------------------------------------- /target/cjs/error.cjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const { 3 | __export, 4 | __toCommonJS 5 | } = require('./cjslib.cjs'); 6 | 7 | 8 | // src/main/ts/error.ts 9 | var error_exports = {}; 10 | __export(error_exports, { 11 | ERRNO_CODES: () => ERRNO_CODES, 12 | EXIT_CODES: () => EXIT_CODES, 13 | formatErrorMessage: () => formatErrorMessage, 14 | formatExitMessage: () => formatExitMessage, 15 | getCallerLocation: () => getCallerLocation, 16 | getCallerLocationFromString: () => getCallerLocationFromString, 17 | getErrnoMessage: () => getErrnoMessage, 18 | getExitCodeInfo: () => getExitCodeInfo 19 | }); 20 | module.exports = __toCommonJS(error_exports); 21 | var EXIT_CODES = { 22 | 2: "Misuse of shell builtins", 23 | 126: "Invoked command cannot execute", 24 | 127: "Command not found", 25 | 128: "Invalid exit argument", 26 | 129: "Hangup", 27 | 130: "Interrupt", 28 | 131: "Quit and dump core", 29 | 132: "Illegal instruction", 30 | 133: "Trace/breakpoint trap", 31 | 134: "Process aborted", 32 | 135: 'Bus error: "access to undefined portion of memory object"', 33 | 136: 'Floating point exception: "erroneous arithmetic operation"', 34 | 137: "Kill (terminate immediately)", 35 | 138: "User-defined 1", 36 | 139: "Segmentation violation", 37 | 140: "User-defined 2", 38 | 141: "Write to pipe with no one reading", 39 | 142: "Signal raised by alarm", 40 | 143: "Termination (request to terminate)", 41 | 145: "Child process terminated, stopped (or continued*)", 42 | 146: "Continue if stopped", 43 | 147: "Stop executing temporarily", 44 | 148: "Terminal stop signal", 45 | 149: 'Background process attempting to read from tty ("in")', 46 | 150: 'Background process attempting to write to tty ("out")', 47 | 151: "Urgent data available on socket", 48 | 152: "CPU time limit exceeded", 49 | 153: "File size limit exceeded", 50 | 154: 'Signal raised by timer counting virtual time: "virtual timer expired"', 51 | 155: "Profiling timer expired", 52 | 157: "Pollable event", 53 | 159: "Bad syscall" 54 | }; 55 | var ERRNO_CODES = { 56 | 0: "Success", 57 | 1: "Not super-user", 58 | 2: "No such file or directory", 59 | 3: "No such process", 60 | 4: "Interrupted system call", 61 | 5: "I/O error", 62 | 6: "No such device or address", 63 | 7: "Arg list too long", 64 | 8: "Exec format error", 65 | 9: "Bad file number", 66 | 10: "No children", 67 | 11: "No more processes", 68 | 12: "Not enough core", 69 | 13: "Permission denied", 70 | 14: "Bad address", 71 | 15: "Block device required", 72 | 16: "Mount device busy", 73 | 17: "File exists", 74 | 18: "Cross-device link", 75 | 19: "No such device", 76 | 20: "Not a directory", 77 | 21: "Is a directory", 78 | 22: "Invalid argument", 79 | 23: "Too many open files in system", 80 | 24: "Too many open files", 81 | 25: "Not a typewriter", 82 | 26: "Text file busy", 83 | 27: "File too large", 84 | 28: "No space left on device", 85 | 29: "Illegal seek", 86 | 30: "Read only file system", 87 | 31: "Too many links", 88 | 32: "Broken pipe", 89 | 33: "Math arg out of domain of func", 90 | 34: "Math result not representable", 91 | 35: "File locking deadlock error", 92 | 36: "File or path name too long", 93 | 37: "No record locks available", 94 | 38: "Function not implemented", 95 | 39: "Directory not empty", 96 | 40: "Too many symbolic links", 97 | 42: "No message of desired type", 98 | 43: "Identifier removed", 99 | 44: "Channel number out of range", 100 | 45: "Level 2 not synchronized", 101 | 46: "Level 3 halted", 102 | 47: "Level 3 reset", 103 | 48: "Link number out of range", 104 | 49: "Protocol driver not attached", 105 | 50: "No CSI structure available", 106 | 51: "Level 2 halted", 107 | 52: "Invalid exchange", 108 | 53: "Invalid request descriptor", 109 | 54: "Exchange full", 110 | 55: "No anode", 111 | 56: "Invalid request code", 112 | 57: "Invalid slot", 113 | 59: "Bad font file fmt", 114 | 60: "Device not a stream", 115 | 61: "No data (for no delay io)", 116 | 62: "Timer expired", 117 | 63: "Out of streams resources", 118 | 64: "Machine is not on the network", 119 | 65: "Package not installed", 120 | 66: "The object is remote", 121 | 67: "The link has been severed", 122 | 68: "Advertise error", 123 | 69: "Srmount error", 124 | 70: "Communication error on send", 125 | 71: "Protocol error", 126 | 72: "Multihop attempted", 127 | 73: "Cross mount point (not really error)", 128 | 74: "Trying to read unreadable message", 129 | 75: "Value too large for defined data type", 130 | 76: "Given log. name not unique", 131 | 77: "f.d. invalid for this operation", 132 | 78: "Remote address changed", 133 | 79: "Can access a needed shared lib", 134 | 80: "Accessing a corrupted shared lib", 135 | 81: ".lib section in a.out corrupted", 136 | 82: "Attempting to link in too many libs", 137 | 83: "Attempting to exec a shared library", 138 | 84: "Illegal byte sequence", 139 | 86: "Streams pipe error", 140 | 87: "Too many users", 141 | 88: "Socket operation on non-socket", 142 | 89: "Destination address required", 143 | 90: "Message too long", 144 | 91: "Protocol wrong type for socket", 145 | 92: "Protocol not available", 146 | 93: "Unknown protocol", 147 | 94: "Socket type not supported", 148 | 95: "Not supported", 149 | 96: "Protocol family not supported", 150 | 97: "Address family not supported by protocol family", 151 | 98: "Address already in use", 152 | 99: "Address not available", 153 | 100: "Network interface is not configured", 154 | 101: "Network is unreachable", 155 | 102: "Connection reset by network", 156 | 103: "Connection aborted", 157 | 104: "Connection reset by peer", 158 | 105: "No buffer space available", 159 | 106: "Socket is already connected", 160 | 107: "Socket is not connected", 161 | 108: "Can't send after socket shutdown", 162 | 109: "Too many references", 163 | 110: "Connection timed out", 164 | 111: "Connection refused", 165 | 112: "Host is down", 166 | 113: "Host is unreachable", 167 | 114: "Socket already connected", 168 | 115: "Connection already in progress", 169 | 116: "Stale file handle", 170 | 122: "Quota exceeded", 171 | 123: "No medium (in tape drive)", 172 | 125: "Operation canceled", 173 | 130: "Previous owner died", 174 | 131: "State not recoverable" 175 | }; 176 | function getErrnoMessage(errno) { 177 | return ERRNO_CODES[-errno] || "Unknown error"; 178 | } 179 | function getExitCodeInfo(exitCode) { 180 | return EXIT_CODES[exitCode]; 181 | } 182 | var formatExitMessage = (code, signal, stderr, from) => { 183 | let message = `exit code: ${code}`; 184 | if (code != 0 || signal != null) { 185 | message = `${stderr || "\n"} at ${from}`; 186 | message += ` 187 | exit code: ${code}${getExitCodeInfo(code) ? " (" + getExitCodeInfo(code) + ")" : ""}`; 188 | if (signal != null) { 189 | message += ` 190 | signal: ${signal}`; 191 | } 192 | } 193 | return message; 194 | }; 195 | var formatErrorMessage = (err, from) => { 196 | return `${err.message} 197 | errno: ${err.errno} (${getErrnoMessage(err.errno)}) 198 | code: ${err.code} 199 | at ${from}`; 200 | }; 201 | function getCallerLocation(err = new Error("zurk error")) { 202 | return getCallerLocationFromString(err.stack); 203 | } 204 | function getCallerLocationFromString(stackString = "unknown") { 205 | var _a; 206 | return ((_a = stackString.split(/^\s*(at\s)?/m).filter((s) => s == null ? void 0 : s.includes(":"))[2]) == null ? void 0 : _a.trim()) || stackString; 207 | } 208 | // Annotate the CommonJS export names for ESM import in node: 209 | 0 && (module.exports = { 210 | ERRNO_CODES, 211 | EXIT_CODES, 212 | formatErrorMessage, 213 | formatExitMessage, 214 | getCallerLocation, 215 | getCallerLocationFromString, 216 | getErrnoMessage, 217 | getExitCodeInfo 218 | }); -------------------------------------------------------------------------------- /target/cjs/index.cjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const { 3 | __export, 4 | __toESM, 5 | __toCommonJS 6 | } = require('./cjslib.cjs'); 7 | 8 | 9 | // src/main/ts/index.ts 10 | var index_exports = {}; 11 | __export(index_exports, { 12 | $: () => $, 13 | VoidStream: () => import_spawn2.VoidStream, 14 | buildCmd: () => import_spawn2.buildCmd, 15 | defaults: () => import_spawn2.defaults, 16 | exec: () => import_spawn2.exec, 17 | invoke: () => import_spawn2.invoke, 18 | isStringLiteral: () => import_spawn2.isStringLiteral, 19 | quote: () => import_spawn2.quote, 20 | quotePwsh: () => import_spawn2.quotePwsh, 21 | zurk: () => import_zurk5.zurk 22 | }); 23 | module.exports = __toCommonJS(index_exports); 24 | var import_spawn2 = require("./spawn.cjs"); 25 | 26 | // src/main/ts/x.ts 27 | var import_zurk4 = require("./zurk.cjs"); 28 | var import_util4 = require("./util.cjs"); 29 | var import_error = require("./error.cjs"); 30 | 31 | // src/main/ts/mixin/pipe.ts 32 | var import_node_stream = require("stream"); 33 | var import_util = require("./util.cjs"); 34 | var import_spawn = require("./spawn.cjs"); 35 | var import_zurk = require("./zurk.cjs"); 36 | var pipeMixin = ($2, result, ctx) => (0, import_zurk.isZurkAny)(result) ? (0, import_util.assign)(result, { 37 | pipe(...args) { 38 | const [target, ...rest] = args; 39 | const { fulfilled, store, ee } = ctx; 40 | const from = new import_spawn.VoidStream(); 41 | const sync = !("then" in result); 42 | const input = fulfilled ? fulfilled.stdout : from; 43 | const fill = () => { 44 | for (const chunk of store.stdout) { 45 | from.write(chunk); 46 | } 47 | }; 48 | let _result; 49 | if ((0, import_zurk.isZurkAny)(target)) { 50 | target.ctx.input = input; 51 | _result = target; 52 | } else if (target instanceof import_node_stream.Writable) { 53 | _result = from.pipe(target); 54 | } else if ((0, import_util.isStringLiteral)(target, ...rest)) { 55 | _result = $2.apply({ input, sync }, args); 56 | } else { 57 | throw new Error("Unsupported pipe argument"); 58 | } 59 | if (fulfilled) { 60 | fill(); 61 | from.end(); 62 | } else { 63 | const onStdout = (chunk) => from.write(chunk); 64 | ee.once("stdout", () => { 65 | fill(); 66 | ee.on("stdout", onStdout); 67 | }).once("end", () => { 68 | ee.removeListener("stdout", onStdout); 69 | from.end(); 70 | }); 71 | } 72 | return _result; 73 | } 74 | }) : result; 75 | 76 | // src/main/ts/mixin/kill.ts 77 | var import_node_process = __toESM(require("process"), 1); 78 | var import_util2 = require("./util.cjs"); 79 | var import_zurk2 = require("./zurk.cjs"); 80 | var kill = (child, signal = "SIGTERM") => new Promise((resolve, reject) => { 81 | if (child) { 82 | child.on("exit", (code, signal2) => { 83 | resolve(signal2); 84 | }); 85 | import_node_process.default.kill(-child.pid, signal); 86 | } else { 87 | reject(new Error("No child process to kill")); 88 | } 89 | }); 90 | var killMixin = ($2, result, ctx) => (0, import_zurk2.isZurkAny)(result) ? (0, import_util2.assign)(result, { 91 | kill(signal) { 92 | return kill(ctx.child, signal); 93 | }, 94 | abort(reason) { 95 | ctx.ac.abort(reason); 96 | } 97 | }) : result; 98 | 99 | // src/main/ts/mixin/timeout.ts 100 | var import_node_process2 = __toESM(require("process"), 1); 101 | var import_util3 = require("./util.cjs"); 102 | var import_zurk3 = require("./zurk.cjs"); 103 | var attachTimeout = (ctx, result) => { 104 | clearTimeout(ctx.timer); 105 | if (ctx.timeout === void 0) return; 106 | const kill2 = () => { 107 | const { child, timeoutSignal = "SIGTERM" } = ctx; 108 | if (result.kill) return result.kill(timeoutSignal); 109 | if (child == null ? void 0 : child.pid) import_node_process2.default.kill(child.pid, timeoutSignal); 110 | }; 111 | ctx.timer = setTimeout(kill2, ctx.timeout); 112 | }; 113 | var timeoutMixin = ($2, result, ctx) => { 114 | if ((0, import_zurk3.isZurkPromise)(result)) { 115 | (0, import_util3.assign)(result, { 116 | set timeoutSignal(timeoutSignal) { 117 | (0, import_util3.assign)(ctx, { timeoutSignal }); 118 | }, 119 | set timeout(timeout) { 120 | (0, import_util3.assign)(ctx, { timeout }); 121 | attachTimeout(ctx, result); 122 | } 123 | }); 124 | attachTimeout(ctx, result); 125 | (0, import_util3.pFinally)(result, () => clearTimeout(ctx.timer)); 126 | } 127 | return result; 128 | }; 129 | 130 | // src/main/ts/x.ts 131 | var $ = function(pieces, ...args) { 132 | const self = this !== import_util4.g && this; 133 | const preset = self || {}; 134 | preset.stack = preset.stack || (0, import_error.getCallerLocation)(); 135 | if (pieces === void 0) return applyMixins($, preset); 136 | if ((0, import_util4.isStringLiteral)(pieces, ...args)) return ignite(preset, pieces, ...args); 137 | return (...args2) => $.apply(self ? (0, import_util4.assign)(self, pieces) : pieces, args2); 138 | }; 139 | var ignite = (preset, pieces, ...args) => { 140 | const _quote = preset.quote || (preset.shell === false ? (arg) => arg : import_util4.quote); 141 | const cmd = (0, import_util4.buildCmd)(_quote, pieces, args); 142 | const input = (0, import_util4.parseInput)(preset.input); 143 | const run = cmd instanceof Promise ? (cb, ctx) => cmd.then((cmd2) => { 144 | ctx.cmd = cmd2; 145 | cb(); 146 | }) : import_util4.immediate; 147 | const opts = (0, import_util4.assign)(preset, { cmd, run, input }); 148 | return applyMixins($, opts); 149 | }; 150 | var zurkMixin = ($2, target) => { 151 | if ((0, import_zurk4.isZurkAny)(target)) return target; 152 | const result = (0, import_zurk4.zurk)(target); 153 | return (0, import_util4.isPromiseLike)(result) ? (0, import_zurk4.zurkifyPromise)( 154 | result.then((r) => applyMixins($2, r, result)), 155 | result.ctx 156 | ) : result; 157 | }; 158 | $.mixins = [zurkMixin, killMixin, pipeMixin, timeoutMixin]; 159 | var applyMixins = ($2, result, parent) => { 160 | let ctx = parent == null ? void 0 : parent.ctx; 161 | return $2.mixins.reduce((r, m) => { 162 | ctx = ctx || r.ctx; 163 | return m($2, r, ctx); 164 | }, result); 165 | }; 166 | 167 | // src/main/ts/index.ts 168 | var import_zurk5 = require("./zurk.cjs"); 169 | // Annotate the CommonJS export names for ESM import in node: 170 | 0 && (module.exports = { 171 | $, 172 | VoidStream, 173 | buildCmd, 174 | defaults, 175 | exec, 176 | invoke, 177 | isStringLiteral, 178 | quote, 179 | quotePwsh, 180 | zurk 181 | }); -------------------------------------------------------------------------------- /target/cjs/spawn.cjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const { 3 | __spreadValues, 4 | __spreadProps, 5 | __export, 6 | __reExport, 7 | __toESM, 8 | __toCommonJS 9 | } = require('./cjslib.cjs'); 10 | 11 | 12 | // src/main/ts/spawn.ts 13 | var spawn_exports = {}; 14 | __export(spawn_exports, { 15 | VoidStream: () => VoidStream, 16 | buildSpawnOpts: () => buildSpawnOpts, 17 | createStore: () => createStore, 18 | defaults: () => defaults, 19 | exec: () => exec, 20 | invoke: () => invoke, 21 | normalizeCtx: () => normalizeCtx, 22 | processInput: () => processInput, 23 | toggleListeners: () => toggleListeners 24 | }); 25 | module.exports = __toCommonJS(spawn_exports); 26 | var cp = __toESM(require("child_process"), 1); 27 | var import_node_process = __toESM(require("process"), 1); 28 | var import_node_events = __toESM(require("events"), 1); 29 | var import_node_stream = require("stream"); 30 | var import_util = require("./util.cjs"); 31 | __reExport(spawn_exports, require("./util.cjs"), module.exports); 32 | var defaults = { 33 | get id() { 34 | return (0, import_util.randomId)(); 35 | }, 36 | cmd: "", 37 | get cwd() { 38 | return import_node_process.default.cwd(); 39 | }, 40 | sync: false, 41 | args: [], 42 | input: null, 43 | env: import_node_process.default.env, 44 | get ee() { 45 | return new import_node_events.default(); 46 | }, 47 | get ac() { 48 | return import_util.g.AbortController && new AbortController(); 49 | }, 50 | get signal() { 51 | var _a; 52 | return (_a = this.ac) == null ? void 0 : _a.signal; 53 | }, 54 | on: {}, 55 | detached: import_node_process.default.platform !== "win32", 56 | shell: true, 57 | spawn: cp.spawn, 58 | spawnSync: cp.spawnSync, 59 | spawnOpts: {}, 60 | get store() { 61 | return createStore(); 62 | }, 63 | callback: import_util.noop, 64 | get stdin() { 65 | return new VoidStream(); 66 | }, 67 | get stdout() { 68 | return new VoidStream(); 69 | }, 70 | get stderr() { 71 | return new VoidStream(); 72 | }, 73 | stdio: ["pipe", "pipe", "pipe"], 74 | run: import_util.immediate, 75 | stack: "" 76 | }; 77 | var normalizeCtx = (...ctxs) => (0, import_util.assign)( 78 | __spreadProps(__spreadValues({}, defaults), { 79 | get signal() { 80 | var _a; 81 | return (_a = this.ac) == null ? void 0 : _a.signal; 82 | } 83 | }), 84 | ...ctxs 85 | ); 86 | var processInput = (child, input) => { 87 | if (input && child.stdin && !child.stdin.destroyed) { 88 | if (input instanceof import_node_stream.Readable) { 89 | input.pipe(child.stdin); 90 | } else { 91 | child.stdin.write(input); 92 | child.stdin.end(); 93 | } 94 | } 95 | }; 96 | var VoidStream = class extends import_node_stream.Transform { 97 | _transform(chunk, _, cb) { 98 | this.emit("data", chunk); 99 | cb(); 100 | } 101 | }; 102 | var buildSpawnOpts = ({ spawnOpts, stdio, cwd, shell, input, env, detached, signal }) => __spreadProps(__spreadValues({}, spawnOpts), { 103 | env, 104 | cwd, 105 | stdio, 106 | shell, 107 | input, 108 | windowsHide: true, 109 | detached, 110 | signal 111 | }); 112 | var toggleListeners = (pos, ee, on = {}) => { 113 | for (const [name, listener] of Object.entries(on)) { 114 | ee[pos](name, listener); 115 | } 116 | if (pos === "on") 117 | ee.once("end", () => toggleListeners("off", ee, on)); 118 | }; 119 | var createStore = () => ({ 120 | stdout: [], 121 | stderr: [], 122 | stdall: [] 123 | }); 124 | var invoke = (c) => { 125 | var _a, _b; 126 | const now = Date.now(); 127 | const stdio = [c.stdin, c.stdout, c.stderr]; 128 | const push = (kind, data) => { 129 | c.store[kind].push(data); 130 | c.store.stdall.push(data); 131 | c.ee.emit(kind, data, c); 132 | c.ee.emit("stdall", data, c); 133 | }; 134 | try { 135 | if (c.sync) { 136 | toggleListeners("on", c.ee, c.on); 137 | const opts = buildSpawnOpts(c); 138 | const r = c.spawnSync(c.cmd, c.args, opts); 139 | c.ee.emit("start", r, c); 140 | if (((_a = r.stdout) == null ? void 0 : _a.length) > 0) { 141 | c.stdout.write(r.stdout); 142 | push("stdout", r.stdout); 143 | } 144 | if (((_b = r.stderr) == null ? void 0 : _b.length) > 0) { 145 | c.stderr.write(r.stderr); 146 | push("stderr", r.stderr); 147 | } 148 | c.callback(null, c.fulfilled = __spreadProps(__spreadValues({}, r), { 149 | get stdout() { 150 | return c.store.stdout.join(""); 151 | }, 152 | get stderr() { 153 | return c.store.stderr.join(""); 154 | }, 155 | get stdall() { 156 | return c.store.stdall.join(""); 157 | }, 158 | stdio, 159 | duration: Date.now() - now, 160 | ctx: c 161 | })); 162 | c.ee.emit("end", c.fulfilled, c); 163 | } else { 164 | c.run(() => { 165 | var _a2, _b2, _c; 166 | toggleListeners("on", c.ee, c.on); 167 | let error = null; 168 | let aborted = false; 169 | const opts = buildSpawnOpts(c); 170 | const child = c.spawn(c.cmd, c.args, opts); 171 | const onAbort = (event) => { 172 | if (opts.detached && child.pid) { 173 | try { 174 | import_node_process.default.kill(-child.pid); 175 | } catch (e) { 176 | child.kill(); 177 | } 178 | } 179 | aborted = true; 180 | c.ee.emit("abort", event, c); 181 | }; 182 | c.child = child; 183 | c.ee.emit("start", child, c); 184 | (_a2 = opts.signal) == null ? void 0 : _a2.addEventListener("abort", onAbort); 185 | processInput(child, c.input || c.stdin); 186 | (_b2 = child.stdout) == null ? void 0 : _b2.on("data", (d) => { 187 | push("stdout", d); 188 | }).pipe(c.stdout); 189 | (_c = child.stderr) == null ? void 0 : _c.on("data", (d) => { 190 | push("stderr", d); 191 | }).pipe(c.stderr); 192 | child.once("error", (e) => { 193 | error = e; 194 | c.ee.emit("err", error, c); 195 | }).once("exit", () => { 196 | var _a3, _b3; 197 | if (aborted) { 198 | (_a3 = child.stdout) == null ? void 0 : _a3.destroy(); 199 | (_b3 = child.stderr) == null ? void 0 : _b3.destroy(); 200 | } 201 | }).once("close", (status, signal) => { 202 | var _a3; 203 | c.fulfilled = { 204 | error, 205 | status, 206 | signal, 207 | get stdout() { 208 | return c.store.stdout.join(""); 209 | }, 210 | get stderr() { 211 | return c.store.stderr.join(""); 212 | }, 213 | get stdall() { 214 | return c.store.stdall.join(""); 215 | }, 216 | stdio, 217 | duration: Date.now() - now, 218 | ctx: c 219 | }; 220 | (_a3 = opts.signal) == null ? void 0 : _a3.removeEventListener("abort", onAbort); 221 | c.callback(error, c.fulfilled); 222 | c.ee.emit("end", c.fulfilled, c); 223 | }); 224 | }, c); 225 | } 226 | } catch (error) { 227 | c.callback( 228 | error, 229 | c.fulfilled = { 230 | error, 231 | status: null, 232 | signal: null, 233 | stdout: "", 234 | stderr: "", 235 | stdall: "", 236 | stdio, 237 | duration: Date.now() - now, 238 | ctx: c 239 | } 240 | ); 241 | c.ee.emit("err", error, c); 242 | c.ee.emit("end", c.fulfilled, c); 243 | } 244 | return c; 245 | }; 246 | var exec = (ctx) => invoke(normalizeCtx(ctx)); 247 | // Annotate the CommonJS export names for ESM import in node: 248 | 0 && (module.exports = Object.assign({ 249 | VoidStream, 250 | buildSpawnOpts, 251 | createStore, 252 | defaults, 253 | exec, 254 | invoke, 255 | normalizeCtx, 256 | processInput, 257 | toggleListeners, 258 | }, require("./util.cjs"))) -------------------------------------------------------------------------------- /target/cjs/util.cjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const { 3 | __spreadValues, 4 | __export, 5 | __toESM, 6 | __toCommonJS, 7 | __async 8 | } = require('./cjslib.cjs'); 9 | 10 | 11 | // src/main/ts/util.ts 12 | var util_exports = {}; 13 | __export(util_exports, { 14 | assign: () => assign, 15 | asyncVoidCall: () => asyncVoidCall, 16 | buildCmd: () => buildCmd, 17 | g: () => g, 18 | immediate: () => immediate, 19 | isPromiseLike: () => isPromiseLike, 20 | isStringLiteral: () => isStringLiteral, 21 | makeDeferred: () => makeDeferred, 22 | noop: () => noop, 23 | pFinally: () => pFinally, 24 | parseInput: () => parseInput, 25 | quote: () => quote, 26 | quotePwsh: () => quotePwsh, 27 | randomId: () => randomId, 28 | substitute: () => substitute 29 | }); 30 | module.exports = __toCommonJS(util_exports); 31 | var import_node_stream = require("stream"); 32 | var import_node_process = __toESM(require("process"), 1); 33 | var import_node_buffer = require("buffer"); 34 | var g = !import_node_process.default.versions.deno && global || globalThis; 35 | var immediate = g.setImmediate || ((f) => g.setTimeout(f, 0)); 36 | var noop = () => { 37 | }; 38 | var asyncVoidCall = (cb) => () => __async(void 0, null, function* () { 39 | yield cb(); 40 | }); 41 | var randomId = () => Math.random().toString(36).slice(2); 42 | var makeDeferred = () => { 43 | let resolve; 44 | let reject; 45 | const promise = new Promise((res, rej) => { 46 | resolve = res; 47 | reject = rej; 48 | }); 49 | return { resolve, reject, promise }; 50 | }; 51 | var isPromiseLike = (value) => typeof (value == null ? void 0 : value.then) === "function"; 52 | var isStringLiteral = (pieces, ...rest) => { 53 | var _a; 54 | return (pieces == null ? void 0 : pieces.length) > 0 && ((_a = pieces.raw) == null ? void 0 : _a.length) === pieces.length && // Object.isFrozen(pieces) && 55 | rest.length + 1 === pieces.length; 56 | }; 57 | var assign = (target, ...extras) => Object.defineProperties(target, extras.reduce((m, extra) => __spreadValues(__spreadValues({}, m), Object.fromEntries(Object.entries(Object.getOwnPropertyDescriptors(extra)).filter(([, v]) => !Object.prototype.hasOwnProperty.call(v, "value") || v.value !== void 0))), {})); 58 | var quote = (arg) => { 59 | if (arg === "") return `$''`; 60 | if (/^[\w./:=@-]+$/.test(arg)) return arg; 61 | return `$'` + arg.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\f/g, "\\f").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t").replace(/\v/g, "\\v").replace(/\0/g, "\\0") + `'`; 62 | }; 63 | function quotePwsh(arg) { 64 | if (arg === "") return `''`; 65 | if (/^[\w./-]+$/.test(arg)) return arg; 66 | return `'` + arg.replace(/'/g, "''") + `'`; 67 | } 68 | var buildCmd = (quote2, pieces, args, subs = substitute) => { 69 | if (args.some(isPromiseLike)) 70 | return Promise.all(args).then((args2) => buildCmd(quote2, pieces, args2)); 71 | let cmd = pieces[0], i = 0; 72 | while (i < args.length) { 73 | const s = Array.isArray(args[i]) ? args[i].map((x) => quote2(subs(x))).join(" ") : quote2(subs(args[i])); 74 | cmd += s + pieces[++i]; 75 | } 76 | return cmd; 77 | }; 78 | var substitute = (arg) => typeof (arg == null ? void 0 : arg.stdout) === "string" ? arg.stdout.replace(/\n$/, "") : `${arg}`; 79 | var parseInput = (input) => { 80 | if (typeof input === "string" || input instanceof import_node_buffer.Buffer || input instanceof import_node_stream.Stream) return input; 81 | if (typeof (input == null ? void 0 : input.stdout) === "string") return input.stdout; 82 | if (input == null ? void 0 : input.ctx) return parseInput(input.ctx.stdout); 83 | return null; 84 | }; 85 | var pFinally = (p, cb) => { 86 | var _a; 87 | return ((_a = p.finally) == null ? void 0 : _a.call(p, asyncVoidCall(cb))) || p.then(asyncVoidCall(cb), asyncVoidCall(cb)); 88 | }; 89 | // Annotate the CommonJS export names for ESM import in node: 90 | 0 && (module.exports = { 91 | assign, 92 | asyncVoidCall, 93 | buildCmd, 94 | g, 95 | immediate, 96 | isPromiseLike, 97 | isStringLiteral, 98 | makeDeferred, 99 | noop, 100 | pFinally, 101 | parseInput, 102 | quote, 103 | quotePwsh, 104 | randomId, 105 | substitute 106 | }); -------------------------------------------------------------------------------- /target/cjs/zurk.cjs: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const { 3 | __export, 4 | __toCommonJS, 5 | __publicField 6 | } = require('./cjslib.cjs'); 7 | 8 | 9 | // src/main/ts/zurk.ts 10 | var zurk_exports = {}; 11 | __export(zurk_exports, { 12 | ZURK: () => ZURK, 13 | ZURKPROXY: () => ZURKPROXY, 14 | getError: () => getError, 15 | isZurk: () => isZurk, 16 | isZurkAny: () => isZurkAny, 17 | isZurkPromise: () => isZurkPromise, 18 | isZurkProxy: () => isZurkProxy, 19 | zurk: () => zurk, 20 | zurkAsync: () => zurkAsync, 21 | zurkFactory: () => zurkFactory, 22 | zurkSync: () => zurkSync, 23 | zurkifyPromise: () => zurkifyPromise 24 | }); 25 | module.exports = __toCommonJS(zurk_exports); 26 | var import_spawn = require("./spawn.cjs"); 27 | var import_util = require("./util.cjs"); 28 | var import_error = require("./error.cjs"); 29 | var ZURK = Symbol("Zurk"); 30 | var ZURKPROXY = Symbol("ZurkProxy"); 31 | var zurk = (opts) => opts.sync ? zurkSync(opts) : zurkAsync(opts); 32 | var zurkAsync = (opts) => { 33 | const { promise, resolve, reject } = (0, import_util.makeDeferred)(); 34 | const ctx = (0, import_spawn.normalizeCtx)(opts, { 35 | sync: false, 36 | callback(err, data) { 37 | ctx.error = ctx.nohandle ? err : getError(data); 38 | ctx.error && !ctx.nothrow ? reject(ctx.error) : resolve(zurkFactory(ctx)); 39 | } 40 | }); 41 | (0, import_spawn.invoke)(ctx); 42 | return zurkifyPromise(promise, ctx); 43 | }; 44 | var zurkSync = (opts) => { 45 | let response; 46 | const ctx = (0, import_spawn.normalizeCtx)(opts, { 47 | sync: true, 48 | callback(err, data) { 49 | ctx.error = ctx.nohandle ? err : getError(data); 50 | if (ctx.error && !ctx.nothrow) throw ctx.error; 51 | response = zurkFactory(ctx); 52 | } 53 | }); 54 | (0, import_spawn.invoke)(ctx); 55 | return response; 56 | }; 57 | var zurkifyPromise = (target, ctx) => { 58 | if (isZurkProxy(target) || !(0, import_util.isPromiseLike)(target)) 59 | return target; 60 | const proxy = new Proxy(target, { 61 | get(target2, p, receiver) { 62 | if (p === ZURKPROXY || p === ZURK) return p; 63 | if (p === "then") return target2.then.bind(target2); 64 | if (p === "catch") return target2.catch.bind(target2); 65 | if (p === "finally") return (cb) => proxy.then((0, import_spawn.asyncVoidCall)(cb), (0, import_spawn.asyncVoidCall)(cb)); 66 | if (p === "stdio") return ctx.stdio; 67 | if (p === "ctx") return ctx; 68 | if (p === "child") return ctx.child; 69 | if (p === "on") return function(name, cb) { 70 | ctx.ee.on(name, cb); 71 | return proxy; 72 | }; 73 | if (p in target2) return Reflect.get(target2, p, receiver); 74 | return target2.then((v) => Reflect.get(v, p, receiver)); 75 | } 76 | }); 77 | return proxy; 78 | }; 79 | var getError = (spawnResult) => { 80 | if (spawnResult.error) 81 | return new Error((0, import_error.formatErrorMessage)(spawnResult.error, spawnResult.ctx.stack)); 82 | if (spawnResult.status || spawnResult.signal) 83 | return new Error((0, import_error.formatExitMessage)(spawnResult.status, spawnResult.signal, spawnResult.stderr, spawnResult.ctx.stack)); 84 | return null; 85 | }; 86 | var isZurkAny = (o) => (o == null ? void 0 : o[ZURK]) === ZURK; 87 | var isZurk = (o) => isZurkAny(o) && !(o instanceof Promise); 88 | var isZurkPromise = (o) => isZurkAny(o) && o instanceof Promise; 89 | var isZurkProxy = (value) => (value == null ? void 0 : value[ZURKPROXY]) === ZURKPROXY; 90 | var zurkFactory = (ctx) => new Zurk(ctx); 91 | var _a; 92 | _a = ZURK; 93 | var Zurk = class { 94 | constructor(ctx) { 95 | __publicField(this, _a, ZURK); 96 | __publicField(this, "ctx"); 97 | this.ctx = ctx; 98 | } 99 | on(name, cb) { 100 | this.ctx.ee.on(name, cb); 101 | return this; 102 | } 103 | get child() { 104 | return this.ctx.child; 105 | } 106 | get status() { 107 | var _a2, _b; 108 | return (_b = (_a2 = this.ctx.fulfilled) == null ? void 0 : _a2.status) != null ? _b : null; 109 | } 110 | get signal() { 111 | var _a2, _b; 112 | return (_b = (_a2 = this.ctx.fulfilled) == null ? void 0 : _a2.signal) != null ? _b : null; 113 | } 114 | get error() { 115 | return this.ctx.error; 116 | } 117 | get stderr() { 118 | var _a2; 119 | return ((_a2 = this.ctx.fulfilled) == null ? void 0 : _a2.stderr) || ""; 120 | } 121 | get stdout() { 122 | var _a2; 123 | return ((_a2 = this.ctx.fulfilled) == null ? void 0 : _a2.stdout) || ""; 124 | } 125 | get stdall() { 126 | var _a2; 127 | return ((_a2 = this.ctx.fulfilled) == null ? void 0 : _a2.stdall) || ""; 128 | } 129 | get stdio() { 130 | return [ 131 | this.ctx.stdin, 132 | this.ctx.stdout, 133 | this.ctx.stderr 134 | ]; 135 | } 136 | get duration() { 137 | var _a2, _b; 138 | return (_b = (_a2 = this.ctx.fulfilled) == null ? void 0 : _a2.duration) != null ? _b : 0; 139 | } 140 | toString() { 141 | return this.stdall.trim(); 142 | } 143 | valueOf() { 144 | return this.stdall.trim(); 145 | } 146 | }; 147 | // Annotate the CommonJS export names for ESM import in node: 148 | 0 && (module.exports = { 149 | ZURK, 150 | ZURKPROXY, 151 | getError, 152 | isZurk, 153 | isZurkAny, 154 | isZurkPromise, 155 | isZurkProxy, 156 | zurk, 157 | zurkAsync, 158 | zurkFactory, 159 | zurkSync, 160 | zurkifyPromise 161 | }); -------------------------------------------------------------------------------- /target/dts/error.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @module 3 | * 4 | * Zurk spawn error codes & handling utilities 5 | * 6 | * @example 7 | * ```ts 8 | * import {EXIT_CODES} from 'zurk/error' 9 | * 10 | * console.log(EXIT_CODES[2]) // 'Misuse of shell builtins' 11 | * ``` 12 | */ 13 | export declare const EXIT_CODES: { 14 | 2: string; 15 | 126: string; 16 | 127: string; 17 | 128: string; 18 | 129: string; 19 | 130: string; 20 | 131: string; 21 | 132: string; 22 | 133: string; 23 | 134: string; 24 | 135: string; 25 | 136: string; 26 | 137: string; 27 | 138: string; 28 | 139: string; 29 | 140: string; 30 | 141: string; 31 | 142: string; 32 | 143: string; 33 | 145: string; 34 | 146: string; 35 | 147: string; 36 | 148: string; 37 | 149: string; 38 | 150: string; 39 | 151: string; 40 | 152: string; 41 | 153: string; 42 | 154: string; 43 | 155: string; 44 | 157: string; 45 | 159: string; 46 | }; 47 | export declare const ERRNO_CODES: { 48 | 0: string; 49 | 1: string; 50 | 2: string; 51 | 3: string; 52 | 4: string; 53 | 5: string; 54 | 6: string; 55 | 7: string; 56 | 8: string; 57 | 9: string; 58 | 10: string; 59 | 11: string; 60 | 12: string; 61 | 13: string; 62 | 14: string; 63 | 15: string; 64 | 16: string; 65 | 17: string; 66 | 18: string; 67 | 19: string; 68 | 20: string; 69 | 21: string; 70 | 22: string; 71 | 23: string; 72 | 24: string; 73 | 25: string; 74 | 26: string; 75 | 27: string; 76 | 28: string; 77 | 29: string; 78 | 30: string; 79 | 31: string; 80 | 32: string; 81 | 33: string; 82 | 34: string; 83 | 35: string; 84 | 36: string; 85 | 37: string; 86 | 38: string; 87 | 39: string; 88 | 40: string; 89 | 42: string; 90 | 43: string; 91 | 44: string; 92 | 45: string; 93 | 46: string; 94 | 47: string; 95 | 48: string; 96 | 49: string; 97 | 50: string; 98 | 51: string; 99 | 52: string; 100 | 53: string; 101 | 54: string; 102 | 55: string; 103 | 56: string; 104 | 57: string; 105 | 59: string; 106 | 60: string; 107 | 61: string; 108 | 62: string; 109 | 63: string; 110 | 64: string; 111 | 65: string; 112 | 66: string; 113 | 67: string; 114 | 68: string; 115 | 69: string; 116 | 70: string; 117 | 71: string; 118 | 72: string; 119 | 73: string; 120 | 74: string; 121 | 75: string; 122 | 76: string; 123 | 77: string; 124 | 78: string; 125 | 79: string; 126 | 80: string; 127 | 81: string; 128 | 82: string; 129 | 83: string; 130 | 84: string; 131 | 86: string; 132 | 87: string; 133 | 88: string; 134 | 89: string; 135 | 90: string; 136 | 91: string; 137 | 92: string; 138 | 93: string; 139 | 94: string; 140 | 95: string; 141 | 96: string; 142 | 97: string; 143 | 98: string; 144 | 99: string; 145 | 100: string; 146 | 101: string; 147 | 102: string; 148 | 103: string; 149 | 104: string; 150 | 105: string; 151 | 106: string; 152 | 107: string; 153 | 108: string; 154 | 109: string; 155 | 110: string; 156 | 111: string; 157 | 112: string; 158 | 113: string; 159 | 114: string; 160 | 115: string; 161 | 116: string; 162 | 122: string; 163 | 123: string; 164 | 125: string; 165 | 130: string; 166 | 131: string; 167 | }; 168 | export declare function getErrnoMessage(errno?: number): string; 169 | export declare function getExitCodeInfo(exitCode: number | null): string | undefined; 170 | export declare const formatExitMessage: (code: number | null, signal: NodeJS.Signals | null, stderr: string, from: string) => string; 171 | export declare const formatErrorMessage: (err: NodeJS.ErrnoException, from: string) => string; 172 | export declare function getCallerLocation(err?: Error): string; 173 | export declare function getCallerLocationFromString(stackString?: string): string; 174 | -------------------------------------------------------------------------------- /target/dts/index.d.ts: -------------------------------------------------------------------------------- 1 | export type * from './spawn.ts'; 2 | export type * from './x.ts'; 3 | export type * from './zurk.ts'; 4 | export { invoke, exec, defaults, VoidStream, isStringLiteral, quote, quotePwsh, buildCmd } from './spawn.ts'; 5 | export { $ } from './x.ts'; 6 | export { zurk } from './zurk.ts'; 7 | /** 8 | * @module 9 | * 10 | * A generic process spawner 11 | * 12 | * @example 13 | * ```ts 14 | * import {$, exec, zurk} from 'zurk' 15 | * 16 | * const r1 = exec({sync: true, cmd: 'echo foo'}) 17 | * const r2 = await zurk({sync: false, cmd: 'echo foo'}) 18 | * const r3 = await $`echo foo` 19 | * ``` 20 | */ 21 | -------------------------------------------------------------------------------- /target/dts/mixin/kill.d.ts: -------------------------------------------------------------------------------- 1 | import type { TMixin } from '../x.ts'; 2 | export declare const killMixin: TMixin; 3 | -------------------------------------------------------------------------------- /target/dts/mixin/pipe.d.ts: -------------------------------------------------------------------------------- 1 | import type { TMixin } from '../x.ts'; 2 | /** 3 | * @module 4 | * 5 | * Zurk $ pipe mixin 6 | */ 7 | export declare const pipeMixin: TMixin; 8 | -------------------------------------------------------------------------------- /target/dts/mixin/timeout.d.ts: -------------------------------------------------------------------------------- 1 | import type { TMixin } from '../x.ts'; 2 | export declare const timeoutMixin: TMixin; 3 | -------------------------------------------------------------------------------- /target/dts/spawn.d.ts: -------------------------------------------------------------------------------- 1 | import * as cp from 'node:child_process'; 2 | import EventEmitter from 'node:events'; 3 | import { Buffer } from 'node:buffer'; 4 | import { Readable, Writable, Transform } from 'node:stream'; 5 | /** 6 | * @module 7 | * 8 | * Zurk internal child_process caller API 9 | * 10 | * @example 11 | * ```ts 12 | * import {invoke, normalizeCtx, TSpawnCtx} from 'zurk/spawn' 13 | * 14 | * const results: string[] = [] 15 | * const callback: TSpawnCtx['callback'] = (_err, result) => results.push(result.stdout) 16 | * 17 | * invoke(normalizeCtx({ 18 | * sync: true, 19 | * cmd: 'echo', 20 | * args: ['hello'], 21 | * callback, 22 | * })) 23 | * ``` 24 | */ 25 | export * from './util.ts'; 26 | export type TSpawnError = any; 27 | export type TPushable = { 28 | push(...args: T[]): number; 29 | }; 30 | export type TJoinable = { 31 | join(sep?: string): string; 32 | }; 33 | export type TReducible = { 34 | reduce(fn: (acc: U, cur: T, i: number, arr: T[]) => U, init: U): R; 35 | }; 36 | export type TArrayLike = Iterable & TPushable & TJoinable & TReducible & { 37 | length: number; 38 | [i: number]: T | undefined; 39 | }; 40 | export type TSpawnStoreChunks = TArrayLike; 41 | export type TSpawnStore = { 42 | stdout: TSpawnStoreChunks; 43 | stderr: TSpawnStoreChunks; 44 | stdall: TSpawnStoreChunks; 45 | }; 46 | export type TSpawnResult = { 47 | stderr: string; 48 | stdout: string; 49 | stdall: string; 50 | stdio: [Readable | Writable, Writable, Writable]; 51 | status: number | null; 52 | signal: NodeJS.Signals | null; 53 | duration: number; 54 | ctx: TSpawnCtxNormalized; 55 | error?: TSpawnError; 56 | child?: TChild; 57 | }; 58 | export type TSpawnListeners = { 59 | start: (data: TChild, ctx: TSpawnCtxNormalized) => void; 60 | stdout: (data: Buffer, ctx: TSpawnCtxNormalized) => void; 61 | stderr: (data: Buffer, ctx: TSpawnCtxNormalized) => void; 62 | stdall: (data: Buffer, ctx: TSpawnCtxNormalized) => void; 63 | abort: (error: Event, ctx: TSpawnCtxNormalized) => void; 64 | err: (error: Error, ctx: TSpawnCtxNormalized) => void; 65 | end: (result: TSpawnResult, ctx: TSpawnCtxNormalized) => void; 66 | }; 67 | export type TSpawnCtx = Partial>; 68 | export type TChild = ReturnType; 69 | export type TInput = string | Buffer | Readable; 70 | export interface TSpawnCtxNormalized { 71 | id: string; 72 | cwd: string; 73 | cmd: string; 74 | sync: boolean; 75 | args: ReadonlyArray; 76 | input: TInput | null; 77 | stdio: cp.StdioOptions; 78 | detached: boolean; 79 | env: Record; 80 | ee: EventEmitter; 81 | on: Partial; 82 | ac: AbortController; 83 | signal: AbortController['signal']; 84 | shell: string | boolean | undefined; 85 | spawn: typeof cp.spawn; 86 | spawnSync: typeof cp.spawnSync; 87 | spawnOpts: Record; 88 | store: TSpawnStore; 89 | callback: (err: TSpawnError, result: TSpawnResult) => void; 90 | stdin: Readable; 91 | stdout: Writable; 92 | stderr: Writable; 93 | child?: TChild; 94 | fulfilled?: TSpawnResult; 95 | error?: any; 96 | run: (cb: () => void, ctx: TSpawnCtxNormalized) => void; 97 | stack: string; 98 | } 99 | /** 100 | * zurk default settings 101 | */ 102 | export declare const defaults: TSpawnCtxNormalized; 103 | /** 104 | * Normalizes spawn context. 105 | * 106 | * @param ctxs Contexts to normalize 107 | * @returns 108 | */ 109 | export declare const normalizeCtx: (...ctxs: TSpawnCtx[]) => TSpawnCtxNormalized; 110 | /** 111 | * Redirects input to child process stdin 112 | * @param child 113 | * @param input 114 | */ 115 | export declare const processInput: (child: TChild, input?: TInput | null) => void; 116 | /** 117 | * Transformer that emits data but does not consume it. 118 | */ 119 | export declare class VoidStream extends Transform { 120 | _transform(chunk: any, _: string, cb: (err?: Error) => void): void; 121 | } 122 | /** 123 | * Builds spawn options 124 | * @param ctx 125 | * @returns spawn options 126 | */ 127 | export declare const buildSpawnOpts: ({ spawnOpts, stdio, cwd, shell, input, env, detached, signal }: TSpawnCtxNormalized) => { 128 | env: Record; 129 | cwd: string; 130 | stdio: cp.StdioOptions; 131 | shell: string | boolean | undefined; 132 | input: string | Buffer; 133 | windowsHide: boolean; 134 | detached: boolean; 135 | signal: AbortSignal; 136 | }; 137 | /** 138 | * Toggles event listeners 139 | * @param pos 'on' | 'off' 140 | * @param ee EventEmitter 141 | * @param on listeners map 142 | */ 143 | export declare const toggleListeners: (pos: "on" | "off", ee: EventEmitter, on?: Partial) => void; 144 | /** 145 | * Creates a new spawn store 146 | */ 147 | export declare const createStore: () => TSpawnStore; 148 | /** 149 | * Invokes a child process 150 | * @param c Normalized context. 151 | * @returns Normalized context. 152 | */ 153 | export declare const invoke: (c: TSpawnCtxNormalized) => TSpawnCtxNormalized; 154 | /** 155 | * Executes a child process 156 | * @param ctx TSpawnCtx 157 | * @returns TSpawnCtxNormalized 158 | */ 159 | export declare const exec: (ctx: TSpawnCtx) => TSpawnCtxNormalized; 160 | -------------------------------------------------------------------------------- /target/dts/util.d.ts: -------------------------------------------------------------------------------- 1 | import { Stream } from 'node:stream'; 2 | import { Buffer } from 'node:buffer'; 3 | /** 4 | * @module 5 | * 6 | * Zurk utility functions 7 | * 8 | * @example 9 | * ```ts 10 | * import {randomId} from 'zurk/util' 11 | * 12 | * randomId() // 'kdrx9bngrb' 13 | * ``` 14 | */ 15 | export declare const g: typeof globalThis; 16 | export declare const immediate: typeof setImmediate; 17 | export declare const noop: () => void; 18 | export declare const asyncVoidCall: (cb: TVoidCallback) => () => Promise; 19 | export declare const randomId: () => string; 20 | export type PromiseResolve = (value: T | PromiseLike) => void; 21 | export type TVoidCallback = (...any: any) => void; 22 | export type Promisified = { 23 | [K in keyof T]: T[K] extends (...args: any) => infer R ? (...args: Parameters) => Promise : Promise; 24 | }; 25 | export declare const makeDeferred: () => { 26 | promise: Promise; 27 | resolve: PromiseResolve; 28 | reject: PromiseResolve; 29 | }; 30 | export declare const isPromiseLike: (value: any) => boolean; 31 | export declare const isStringLiteral: (pieces: any, ...rest: any[]) => pieces is TemplateStringsArray; 32 | export declare const assign: (target: T, ...extras: E[]) => T; 33 | export declare const quote: (arg: string) => string; 34 | export declare function quotePwsh(arg: string): string; 35 | export type TQuote = (input: string) => string; 36 | export declare const buildCmd: (quote: TQuote, pieces: TemplateStringsArray, args: any[], subs?: TSubstitute) => string | Promise; 37 | export type TSubstitute = (arg: any) => string; 38 | export declare const substitute: TSubstitute; 39 | export declare const parseInput: (input: any) => string | Buffer | Stream | null; 40 | export declare const pFinally: (p: Promise, cb: TVoidCallback) => Promise; 41 | -------------------------------------------------------------------------------- /target/dts/x.d.ts: -------------------------------------------------------------------------------- 1 | import type { Readable, Writable } from 'node:stream'; 2 | import { TZurk, TZurkPromise, TZurkOptions, TZurkCtx } from './zurk.ts'; 3 | import { type Promisified, type TVoidCallback, type TQuote } from './util.ts'; 4 | /** 5 | * @module 6 | * 7 | * Zurk $ API 8 | * 9 | * @example 10 | * ```ts 11 | * import {$} from 'zurk/x' 12 | * 13 | * const p = await $`echo foo`' 14 | * ``` 15 | */ 16 | export interface TShellCtxExtra { 17 | } 18 | export interface TShellExtra { 19 | } 20 | export interface TShellOptionsExtra { 21 | timeout?: number; 22 | timeoutSignal?: NodeJS.Signals; 23 | } 24 | export interface TShellResponseExtra { 25 | pipe(shell: T): T; 26 | pipe(stream: Writable): Writable; 27 | pipe(pieces: TemplateStringsArray, ...args: any[]): T; 28 | kill(signal?: NodeJS.Signals | null): Promise; 29 | abort(): void; 30 | timeout?: number; 31 | timeoutSignal?: NodeJS.Signals; 32 | } 33 | export interface TShellCtx extends TZurkCtx, TShellCtxExtra { 34 | timer?: number | NodeJS.Timeout; 35 | timeout?: number; 36 | timeoutSignal?: NodeJS.Signals; 37 | } 38 | export type TShellOptions = Omit & { 39 | quote?: TQuote; 40 | input?: TShellCtx['input'] | TShellResponse | TShellResponseSync | null; 41 | } & TShellOptionsExtra; 42 | export interface TShellResponse extends Omit, 'stdio' | 'ctx' | 'child'>, Promise>, TShellResponseExtra { 43 | child: TZurk['child']; 44 | stdio: [Readable | Writable, Writable, Writable]; 45 | ctx: TShellCtx; 46 | on: (event: string | symbol, listener: TVoidCallback) => TShellResponse; 47 | } 48 | export interface TShellResponseSync extends TZurk, TShellResponseExtra { 49 | } 50 | export type TMixin = (($: TShell, target: TShellOptions) => TShellOptions | TZurk | TZurkPromise) | (($: TShell, target: TZurk, ctx: TShellCtx) => TZurk) | (($: TShell, target: Promise | TZurkPromise, ctx: TShellCtx) => TZurkPromise); 51 | export interface TShell extends TShellExtra { 52 | mixins: TMixin[]; 53 | (this: O, pieces?: TemplateStringsArray, ...args: any[]): TShellResponse; 54 | (this: O, pieces?: TemplateStringsArray, ...args: any[]): R; 57 | (opts: O): R; 60 | } 61 | export interface TShellSync { 62 | (this: O, pieces?: TemplateStringsArray, ...args: any[]): TShellResponseSync; 63 | (opts: TShellOptions): TShellSync; 64 | } 65 | /** 66 | * Zurk $ template API 67 | * 68 | * @param pieces 69 | * @param args 70 | */ 71 | export declare const $: TShell; 72 | /** 73 | * Applies mixins to the result. 74 | * @param $ 75 | * @param result 76 | * @param parent 77 | * @returns TZurk | TZurkPromise | TShellOptions 78 | */ 79 | export declare const applyMixins: ($: TShell, result: TZurk | TZurkPromise | TShellOptions, parent?: TZurk | TZurkPromise) => TZurk | TZurkPromise | TShellOptions; 80 | -------------------------------------------------------------------------------- /target/dts/zurk.d.ts: -------------------------------------------------------------------------------- 1 | import { type TSpawnCtxNormalized, type TSpawnResult, type TSpawnListeners } from './spawn.ts'; 2 | import { type Promisified } from './util.ts'; 3 | /** 4 | * @module 5 | * 6 | * Zurk process spawner 7 | * 8 | * @example 9 | * ```ts 10 | * import {zurk} from 'zurk/zurk' 11 | * 12 | * const r1 = zurk({ sync: true, cmd: 'echo', args: ['foo']}) 13 | * const r2 = await zurk({ sync: false, cmd: 'echo', args: ['foo']}) 14 | * ``` 15 | */ 16 | export declare const ZURK: unique symbol; 17 | export declare const ZURKPROXY: unique symbol; 18 | export interface TZurkOn { 19 | on(name: T, listener: L): R; 20 | on(name: T, listener: L): R; 21 | on(name: T, listener: L): R; 22 | on(name: T, listener: L): R; 23 | on(name: T, listener: L): R; 24 | on(name: T, listener: L): R; 25 | } 26 | export interface TZurk extends TSpawnResult, TZurkOn { 27 | ctx: TZurkCtx; 28 | } 29 | export type TZurkCtx = TSpawnCtxNormalized & { 30 | nothrow?: boolean; 31 | nohandle?: boolean; 32 | }; 33 | export type TZurkOptions = Partial>; 34 | export type TZurkPromise = Promise & Promisified & TZurkOn & { 35 | ctx: TZurkCtx; 36 | stdio: TZurkCtx['stdio']; 37 | child: TZurkCtx['child']; 38 | }; 39 | export declare const zurk: (opts: T) => R; 42 | export declare const zurkAsync: (opts: TZurkOptions) => TZurkPromise; 43 | export declare const zurkSync: (opts: TZurkOptions) => TZurk; 44 | export declare const zurkifyPromise: (target: Promise | TZurkPromise, ctx: TSpawnCtxNormalized) => TZurkPromise; 45 | export declare const getError: (spawnResult: TSpawnResult) => Error | null; 46 | export declare const isZurkAny: (o: any) => o is TZurk | TZurkPromise; 47 | export declare const isZurk: (o: any) => o is TZurk; 48 | export declare const isZurkPromise: (o: any) => o is TZurkPromise; 49 | export declare const isZurkProxy: (value: any) => boolean; 50 | export declare const zurkFactory: (ctx: C) => TZurk; 51 | -------------------------------------------------------------------------------- /target/esm/error.mjs: -------------------------------------------------------------------------------- 1 | // src/main/ts/error.ts 2 | var EXIT_CODES = { 3 | 2: "Misuse of shell builtins", 4 | 126: "Invoked command cannot execute", 5 | 127: "Command not found", 6 | 128: "Invalid exit argument", 7 | 129: "Hangup", 8 | 130: "Interrupt", 9 | 131: "Quit and dump core", 10 | 132: "Illegal instruction", 11 | 133: "Trace/breakpoint trap", 12 | 134: "Process aborted", 13 | 135: 'Bus error: "access to undefined portion of memory object"', 14 | 136: 'Floating point exception: "erroneous arithmetic operation"', 15 | 137: "Kill (terminate immediately)", 16 | 138: "User-defined 1", 17 | 139: "Segmentation violation", 18 | 140: "User-defined 2", 19 | 141: "Write to pipe with no one reading", 20 | 142: "Signal raised by alarm", 21 | 143: "Termination (request to terminate)", 22 | 145: "Child process terminated, stopped (or continued*)", 23 | 146: "Continue if stopped", 24 | 147: "Stop executing temporarily", 25 | 148: "Terminal stop signal", 26 | 149: 'Background process attempting to read from tty ("in")', 27 | 150: 'Background process attempting to write to tty ("out")', 28 | 151: "Urgent data available on socket", 29 | 152: "CPU time limit exceeded", 30 | 153: "File size limit exceeded", 31 | 154: 'Signal raised by timer counting virtual time: "virtual timer expired"', 32 | 155: "Profiling timer expired", 33 | 157: "Pollable event", 34 | 159: "Bad syscall" 35 | }; 36 | var ERRNO_CODES = { 37 | 0: "Success", 38 | 1: "Not super-user", 39 | 2: "No such file or directory", 40 | 3: "No such process", 41 | 4: "Interrupted system call", 42 | 5: "I/O error", 43 | 6: "No such device or address", 44 | 7: "Arg list too long", 45 | 8: "Exec format error", 46 | 9: "Bad file number", 47 | 10: "No children", 48 | 11: "No more processes", 49 | 12: "Not enough core", 50 | 13: "Permission denied", 51 | 14: "Bad address", 52 | 15: "Block device required", 53 | 16: "Mount device busy", 54 | 17: "File exists", 55 | 18: "Cross-device link", 56 | 19: "No such device", 57 | 20: "Not a directory", 58 | 21: "Is a directory", 59 | 22: "Invalid argument", 60 | 23: "Too many open files in system", 61 | 24: "Too many open files", 62 | 25: "Not a typewriter", 63 | 26: "Text file busy", 64 | 27: "File too large", 65 | 28: "No space left on device", 66 | 29: "Illegal seek", 67 | 30: "Read only file system", 68 | 31: "Too many links", 69 | 32: "Broken pipe", 70 | 33: "Math arg out of domain of func", 71 | 34: "Math result not representable", 72 | 35: "File locking deadlock error", 73 | 36: "File or path name too long", 74 | 37: "No record locks available", 75 | 38: "Function not implemented", 76 | 39: "Directory not empty", 77 | 40: "Too many symbolic links", 78 | 42: "No message of desired type", 79 | 43: "Identifier removed", 80 | 44: "Channel number out of range", 81 | 45: "Level 2 not synchronized", 82 | 46: "Level 3 halted", 83 | 47: "Level 3 reset", 84 | 48: "Link number out of range", 85 | 49: "Protocol driver not attached", 86 | 50: "No CSI structure available", 87 | 51: "Level 2 halted", 88 | 52: "Invalid exchange", 89 | 53: "Invalid request descriptor", 90 | 54: "Exchange full", 91 | 55: "No anode", 92 | 56: "Invalid request code", 93 | 57: "Invalid slot", 94 | 59: "Bad font file fmt", 95 | 60: "Device not a stream", 96 | 61: "No data (for no delay io)", 97 | 62: "Timer expired", 98 | 63: "Out of streams resources", 99 | 64: "Machine is not on the network", 100 | 65: "Package not installed", 101 | 66: "The object is remote", 102 | 67: "The link has been severed", 103 | 68: "Advertise error", 104 | 69: "Srmount error", 105 | 70: "Communication error on send", 106 | 71: "Protocol error", 107 | 72: "Multihop attempted", 108 | 73: "Cross mount point (not really error)", 109 | 74: "Trying to read unreadable message", 110 | 75: "Value too large for defined data type", 111 | 76: "Given log. name not unique", 112 | 77: "f.d. invalid for this operation", 113 | 78: "Remote address changed", 114 | 79: "Can access a needed shared lib", 115 | 80: "Accessing a corrupted shared lib", 116 | 81: ".lib section in a.out corrupted", 117 | 82: "Attempting to link in too many libs", 118 | 83: "Attempting to exec a shared library", 119 | 84: "Illegal byte sequence", 120 | 86: "Streams pipe error", 121 | 87: "Too many users", 122 | 88: "Socket operation on non-socket", 123 | 89: "Destination address required", 124 | 90: "Message too long", 125 | 91: "Protocol wrong type for socket", 126 | 92: "Protocol not available", 127 | 93: "Unknown protocol", 128 | 94: "Socket type not supported", 129 | 95: "Not supported", 130 | 96: "Protocol family not supported", 131 | 97: "Address family not supported by protocol family", 132 | 98: "Address already in use", 133 | 99: "Address not available", 134 | 100: "Network interface is not configured", 135 | 101: "Network is unreachable", 136 | 102: "Connection reset by network", 137 | 103: "Connection aborted", 138 | 104: "Connection reset by peer", 139 | 105: "No buffer space available", 140 | 106: "Socket is already connected", 141 | 107: "Socket is not connected", 142 | 108: "Can't send after socket shutdown", 143 | 109: "Too many references", 144 | 110: "Connection timed out", 145 | 111: "Connection refused", 146 | 112: "Host is down", 147 | 113: "Host is unreachable", 148 | 114: "Socket already connected", 149 | 115: "Connection already in progress", 150 | 116: "Stale file handle", 151 | 122: "Quota exceeded", 152 | 123: "No medium (in tape drive)", 153 | 125: "Operation canceled", 154 | 130: "Previous owner died", 155 | 131: "State not recoverable" 156 | }; 157 | function getErrnoMessage(errno) { 158 | return ERRNO_CODES[-errno] || "Unknown error"; 159 | } 160 | function getExitCodeInfo(exitCode) { 161 | return EXIT_CODES[exitCode]; 162 | } 163 | var formatExitMessage = (code, signal, stderr, from) => { 164 | let message = `exit code: ${code}`; 165 | if (code != 0 || signal != null) { 166 | message = `${stderr || "\n"} at ${from}`; 167 | message += ` 168 | exit code: ${code}${getExitCodeInfo(code) ? " (" + getExitCodeInfo(code) + ")" : ""}`; 169 | if (signal != null) { 170 | message += ` 171 | signal: ${signal}`; 172 | } 173 | } 174 | return message; 175 | }; 176 | var formatErrorMessage = (err, from) => { 177 | return `${err.message} 178 | errno: ${err.errno} (${getErrnoMessage(err.errno)}) 179 | code: ${err.code} 180 | at ${from}`; 181 | }; 182 | function getCallerLocation(err = new Error("zurk error")) { 183 | return getCallerLocationFromString(err.stack); 184 | } 185 | function getCallerLocationFromString(stackString = "unknown") { 186 | var _a; 187 | return ((_a = stackString.split(/^\s*(at\s)?/m).filter((s) => s == null ? void 0 : s.includes(":"))[2]) == null ? void 0 : _a.trim()) || stackString; 188 | } 189 | export { 190 | ERRNO_CODES, 191 | EXIT_CODES, 192 | formatErrorMessage, 193 | formatExitMessage, 194 | getCallerLocation, 195 | getCallerLocationFromString, 196 | getErrnoMessage, 197 | getExitCodeInfo 198 | }; 199 | -------------------------------------------------------------------------------- /target/esm/index.mjs: -------------------------------------------------------------------------------- 1 | // src/main/ts/index.ts 2 | import { invoke, exec, defaults, VoidStream as VoidStream2, isStringLiteral as isStringLiteral3, quote as quote2, quotePwsh, buildCmd as buildCmd2 } from "./spawn.mjs"; 3 | 4 | // src/main/ts/x.ts 5 | import { 6 | zurk, 7 | zurkifyPromise, 8 | isZurkAny as isZurkAny3 9 | } from "./zurk.mjs"; 10 | import { 11 | isPromiseLike, 12 | isStringLiteral as isStringLiteral2, 13 | assign as assign4, 14 | quote, 15 | buildCmd, 16 | parseInput, 17 | g, 18 | immediate 19 | } from "./util.mjs"; 20 | import { getCallerLocation } from "./error.mjs"; 21 | 22 | // src/main/ts/mixin/pipe.ts 23 | import { Writable } from "node:stream"; 24 | import { assign, isStringLiteral } from "./util.mjs"; 25 | import { VoidStream } from "./spawn.mjs"; 26 | import { isZurkAny } from "./zurk.mjs"; 27 | var pipeMixin = ($2, result, ctx) => isZurkAny(result) ? assign(result, { 28 | pipe(...args) { 29 | const [target, ...rest] = args; 30 | const { fulfilled, store, ee } = ctx; 31 | const from = new VoidStream(); 32 | const sync = !("then" in result); 33 | const input = fulfilled ? fulfilled.stdout : from; 34 | const fill = () => { 35 | for (const chunk of store.stdout) { 36 | from.write(chunk); 37 | } 38 | }; 39 | let _result; 40 | if (isZurkAny(target)) { 41 | target.ctx.input = input; 42 | _result = target; 43 | } else if (target instanceof Writable) { 44 | _result = from.pipe(target); 45 | } else if (isStringLiteral(target, ...rest)) { 46 | _result = $2.apply({ input, sync }, args); 47 | } else { 48 | throw new Error("Unsupported pipe argument"); 49 | } 50 | if (fulfilled) { 51 | fill(); 52 | from.end(); 53 | } else { 54 | const onStdout = (chunk) => from.write(chunk); 55 | ee.once("stdout", () => { 56 | fill(); 57 | ee.on("stdout", onStdout); 58 | }).once("end", () => { 59 | ee.removeListener("stdout", onStdout); 60 | from.end(); 61 | }); 62 | } 63 | return _result; 64 | } 65 | }) : result; 66 | 67 | // src/main/ts/mixin/kill.ts 68 | import process from "node:process"; 69 | import { assign as assign2 } from "./util.mjs"; 70 | import { isZurkAny as isZurkAny2 } from "./zurk.mjs"; 71 | var kill = (child, signal = "SIGTERM") => new Promise((resolve, reject) => { 72 | if (child) { 73 | child.on("exit", (code, signal2) => { 74 | resolve(signal2); 75 | }); 76 | process.kill(-child.pid, signal); 77 | } else { 78 | reject(new Error("No child process to kill")); 79 | } 80 | }); 81 | var killMixin = ($2, result, ctx) => isZurkAny2(result) ? assign2(result, { 82 | kill(signal) { 83 | return kill(ctx.child, signal); 84 | }, 85 | abort(reason) { 86 | ctx.ac.abort(reason); 87 | } 88 | }) : result; 89 | 90 | // src/main/ts/mixin/timeout.ts 91 | import process2 from "node:process"; 92 | import { assign as assign3, pFinally } from "./util.mjs"; 93 | import { isZurkPromise } from "./zurk.mjs"; 94 | var attachTimeout = (ctx, result) => { 95 | clearTimeout(ctx.timer); 96 | if (ctx.timeout === void 0) return; 97 | const kill2 = () => { 98 | const { child, timeoutSignal = "SIGTERM" } = ctx; 99 | if (result.kill) return result.kill(timeoutSignal); 100 | if (child == null ? void 0 : child.pid) process2.kill(child.pid, timeoutSignal); 101 | }; 102 | ctx.timer = setTimeout(kill2, ctx.timeout); 103 | }; 104 | var timeoutMixin = ($2, result, ctx) => { 105 | if (isZurkPromise(result)) { 106 | assign3(result, { 107 | set timeoutSignal(timeoutSignal) { 108 | assign3(ctx, { timeoutSignal }); 109 | }, 110 | set timeout(timeout) { 111 | assign3(ctx, { timeout }); 112 | attachTimeout(ctx, result); 113 | } 114 | }); 115 | attachTimeout(ctx, result); 116 | pFinally(result, () => clearTimeout(ctx.timer)); 117 | } 118 | return result; 119 | }; 120 | 121 | // src/main/ts/x.ts 122 | var $ = function(pieces, ...args) { 123 | const self = this !== g && this; 124 | const preset = self || {}; 125 | preset.stack = preset.stack || getCallerLocation(); 126 | if (pieces === void 0) return applyMixins($, preset); 127 | if (isStringLiteral2(pieces, ...args)) return ignite(preset, pieces, ...args); 128 | return (...args2) => $.apply(self ? assign4(self, pieces) : pieces, args2); 129 | }; 130 | var ignite = (preset, pieces, ...args) => { 131 | const _quote = preset.quote || (preset.shell === false ? (arg) => arg : quote); 132 | const cmd = buildCmd(_quote, pieces, args); 133 | const input = parseInput(preset.input); 134 | const run = cmd instanceof Promise ? (cb, ctx) => cmd.then((cmd2) => { 135 | ctx.cmd = cmd2; 136 | cb(); 137 | }) : immediate; 138 | const opts = assign4(preset, { cmd, run, input }); 139 | return applyMixins($, opts); 140 | }; 141 | var zurkMixin = ($2, target) => { 142 | if (isZurkAny3(target)) return target; 143 | const result = zurk(target); 144 | return isPromiseLike(result) ? zurkifyPromise( 145 | result.then((r) => applyMixins($2, r, result)), 146 | result.ctx 147 | ) : result; 148 | }; 149 | $.mixins = [zurkMixin, killMixin, pipeMixin, timeoutMixin]; 150 | var applyMixins = ($2, result, parent) => { 151 | let ctx = parent == null ? void 0 : parent.ctx; 152 | return $2.mixins.reduce((r, m) => { 153 | ctx = ctx || r.ctx; 154 | return m($2, r, ctx); 155 | }, result); 156 | }; 157 | 158 | // src/main/ts/index.ts 159 | import { zurk as zurk2 } from "./zurk.mjs"; 160 | export { 161 | $, 162 | VoidStream2 as VoidStream, 163 | buildCmd2 as buildCmd, 164 | defaults, 165 | exec, 166 | invoke, 167 | isStringLiteral3 as isStringLiteral, 168 | quote2 as quote, 169 | quotePwsh, 170 | zurk2 as zurk 171 | }; 172 | -------------------------------------------------------------------------------- /target/esm/spawn.mjs: -------------------------------------------------------------------------------- 1 | // src/main/ts/spawn.ts 2 | import * as cp from "node:child_process"; 3 | import process from "node:process"; 4 | import EventEmitter from "node:events"; 5 | import { Readable, Transform } from "node:stream"; 6 | import { assign, noop, randomId, g, immediate } from "./util.mjs"; 7 | export * from "./util.mjs"; 8 | var defaults = { 9 | get id() { 10 | return randomId(); 11 | }, 12 | cmd: "", 13 | get cwd() { 14 | return process.cwd(); 15 | }, 16 | sync: false, 17 | args: [], 18 | input: null, 19 | env: process.env, 20 | get ee() { 21 | return new EventEmitter(); 22 | }, 23 | get ac() { 24 | return g.AbortController && new AbortController(); 25 | }, 26 | get signal() { 27 | var _a; 28 | return (_a = this.ac) == null ? void 0 : _a.signal; 29 | }, 30 | on: {}, 31 | detached: process.platform !== "win32", 32 | shell: true, 33 | spawn: cp.spawn, 34 | spawnSync: cp.spawnSync, 35 | spawnOpts: {}, 36 | get store() { 37 | return createStore(); 38 | }, 39 | callback: noop, 40 | get stdin() { 41 | return new VoidStream(); 42 | }, 43 | get stdout() { 44 | return new VoidStream(); 45 | }, 46 | get stderr() { 47 | return new VoidStream(); 48 | }, 49 | stdio: ["pipe", "pipe", "pipe"], 50 | run: immediate, 51 | stack: "" 52 | }; 53 | var normalizeCtx = (...ctxs) => assign( 54 | { 55 | ...defaults, 56 | get signal() { 57 | var _a; 58 | return (_a = this.ac) == null ? void 0 : _a.signal; 59 | } 60 | }, 61 | ...ctxs 62 | ); 63 | var processInput = (child, input) => { 64 | if (input && child.stdin && !child.stdin.destroyed) { 65 | if (input instanceof Readable) { 66 | input.pipe(child.stdin); 67 | } else { 68 | child.stdin.write(input); 69 | child.stdin.end(); 70 | } 71 | } 72 | }; 73 | var VoidStream = class extends Transform { 74 | _transform(chunk, _, cb) { 75 | this.emit("data", chunk); 76 | cb(); 77 | } 78 | }; 79 | var buildSpawnOpts = ({ spawnOpts, stdio, cwd, shell, input, env, detached, signal }) => ({ 80 | ...spawnOpts, 81 | env, 82 | cwd, 83 | stdio, 84 | shell, 85 | input, 86 | windowsHide: true, 87 | detached, 88 | signal 89 | }); 90 | var toggleListeners = (pos, ee, on = {}) => { 91 | for (const [name, listener] of Object.entries(on)) { 92 | ee[pos](name, listener); 93 | } 94 | if (pos === "on") 95 | ee.once("end", () => toggleListeners("off", ee, on)); 96 | }; 97 | var createStore = () => ({ 98 | stdout: [], 99 | stderr: [], 100 | stdall: [] 101 | }); 102 | var invoke = (c) => { 103 | var _a, _b; 104 | const now = Date.now(); 105 | const stdio = [c.stdin, c.stdout, c.stderr]; 106 | const push = (kind, data) => { 107 | c.store[kind].push(data); 108 | c.store.stdall.push(data); 109 | c.ee.emit(kind, data, c); 110 | c.ee.emit("stdall", data, c); 111 | }; 112 | try { 113 | if (c.sync) { 114 | toggleListeners("on", c.ee, c.on); 115 | const opts = buildSpawnOpts(c); 116 | const r = c.spawnSync(c.cmd, c.args, opts); 117 | c.ee.emit("start", r, c); 118 | if (((_a = r.stdout) == null ? void 0 : _a.length) > 0) { 119 | c.stdout.write(r.stdout); 120 | push("stdout", r.stdout); 121 | } 122 | if (((_b = r.stderr) == null ? void 0 : _b.length) > 0) { 123 | c.stderr.write(r.stderr); 124 | push("stderr", r.stderr); 125 | } 126 | c.callback(null, c.fulfilled = { 127 | ...r, 128 | get stdout() { 129 | return c.store.stdout.join(""); 130 | }, 131 | get stderr() { 132 | return c.store.stderr.join(""); 133 | }, 134 | get stdall() { 135 | return c.store.stdall.join(""); 136 | }, 137 | stdio, 138 | duration: Date.now() - now, 139 | ctx: c 140 | }); 141 | c.ee.emit("end", c.fulfilled, c); 142 | } else { 143 | c.run(() => { 144 | var _a2, _b2, _c; 145 | toggleListeners("on", c.ee, c.on); 146 | let error = null; 147 | let aborted = false; 148 | const opts = buildSpawnOpts(c); 149 | const child = c.spawn(c.cmd, c.args, opts); 150 | const onAbort = (event) => { 151 | if (opts.detached && child.pid) { 152 | try { 153 | process.kill(-child.pid); 154 | } catch { 155 | child.kill(); 156 | } 157 | } 158 | aborted = true; 159 | c.ee.emit("abort", event, c); 160 | }; 161 | c.child = child; 162 | c.ee.emit("start", child, c); 163 | (_a2 = opts.signal) == null ? void 0 : _a2.addEventListener("abort", onAbort); 164 | processInput(child, c.input || c.stdin); 165 | (_b2 = child.stdout) == null ? void 0 : _b2.on("data", (d) => { 166 | push("stdout", d); 167 | }).pipe(c.stdout); 168 | (_c = child.stderr) == null ? void 0 : _c.on("data", (d) => { 169 | push("stderr", d); 170 | }).pipe(c.stderr); 171 | child.once("error", (e) => { 172 | error = e; 173 | c.ee.emit("err", error, c); 174 | }).once("exit", () => { 175 | var _a3, _b3; 176 | if (aborted) { 177 | (_a3 = child.stdout) == null ? void 0 : _a3.destroy(); 178 | (_b3 = child.stderr) == null ? void 0 : _b3.destroy(); 179 | } 180 | }).once("close", (status, signal) => { 181 | var _a3; 182 | c.fulfilled = { 183 | error, 184 | status, 185 | signal, 186 | get stdout() { 187 | return c.store.stdout.join(""); 188 | }, 189 | get stderr() { 190 | return c.store.stderr.join(""); 191 | }, 192 | get stdall() { 193 | return c.store.stdall.join(""); 194 | }, 195 | stdio, 196 | duration: Date.now() - now, 197 | ctx: c 198 | }; 199 | (_a3 = opts.signal) == null ? void 0 : _a3.removeEventListener("abort", onAbort); 200 | c.callback(error, c.fulfilled); 201 | c.ee.emit("end", c.fulfilled, c); 202 | }); 203 | }, c); 204 | } 205 | } catch (error) { 206 | c.callback( 207 | error, 208 | c.fulfilled = { 209 | error, 210 | status: null, 211 | signal: null, 212 | stdout: "", 213 | stderr: "", 214 | stdall: "", 215 | stdio, 216 | duration: Date.now() - now, 217 | ctx: c 218 | } 219 | ); 220 | c.ee.emit("err", error, c); 221 | c.ee.emit("end", c.fulfilled, c); 222 | } 223 | return c; 224 | }; 225 | var exec = (ctx) => invoke(normalizeCtx(ctx)); 226 | export { 227 | VoidStream, 228 | buildSpawnOpts, 229 | createStore, 230 | defaults, 231 | exec, 232 | invoke, 233 | normalizeCtx, 234 | processInput, 235 | toggleListeners 236 | }; 237 | -------------------------------------------------------------------------------- /target/esm/util.mjs: -------------------------------------------------------------------------------- 1 | // src/main/ts/util.ts 2 | import { Stream } from "node:stream"; 3 | import process from "node:process"; 4 | import { Buffer } from "node:buffer"; 5 | var g = !process.versions.deno && global || globalThis; 6 | var immediate = g.setImmediate || ((f) => g.setTimeout(f, 0)); 7 | var noop = () => { 8 | }; 9 | var asyncVoidCall = (cb) => async () => { 10 | await cb(); 11 | }; 12 | var randomId = () => Math.random().toString(36).slice(2); 13 | var makeDeferred = () => { 14 | let resolve; 15 | let reject; 16 | const promise = new Promise((res, rej) => { 17 | resolve = res; 18 | reject = rej; 19 | }); 20 | return { resolve, reject, promise }; 21 | }; 22 | var isPromiseLike = (value) => typeof (value == null ? void 0 : value.then) === "function"; 23 | var isStringLiteral = (pieces, ...rest) => { 24 | var _a; 25 | return (pieces == null ? void 0 : pieces.length) > 0 && ((_a = pieces.raw) == null ? void 0 : _a.length) === pieces.length && // Object.isFrozen(pieces) && 26 | rest.length + 1 === pieces.length; 27 | }; 28 | var assign = (target, ...extras) => Object.defineProperties(target, extras.reduce((m, extra) => ({ ...m, ...Object.fromEntries(Object.entries(Object.getOwnPropertyDescriptors(extra)).filter(([, v]) => !Object.prototype.hasOwnProperty.call(v, "value") || v.value !== void 0)) }), {})); 29 | var quote = (arg) => { 30 | if (arg === "") return `$''`; 31 | if (/^[\w./:=@-]+$/.test(arg)) return arg; 32 | return `$'` + arg.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/\f/g, "\\f").replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t").replace(/\v/g, "\\v").replace(/\0/g, "\\0") + `'`; 33 | }; 34 | function quotePwsh(arg) { 35 | if (arg === "") return `''`; 36 | if (/^[\w./-]+$/.test(arg)) return arg; 37 | return `'` + arg.replace(/'/g, "''") + `'`; 38 | } 39 | var buildCmd = (quote2, pieces, args, subs = substitute) => { 40 | if (args.some(isPromiseLike)) 41 | return Promise.all(args).then((args2) => buildCmd(quote2, pieces, args2)); 42 | let cmd = pieces[0], i = 0; 43 | while (i < args.length) { 44 | const s = Array.isArray(args[i]) ? args[i].map((x) => quote2(subs(x))).join(" ") : quote2(subs(args[i])); 45 | cmd += s + pieces[++i]; 46 | } 47 | return cmd; 48 | }; 49 | var substitute = (arg) => typeof (arg == null ? void 0 : arg.stdout) === "string" ? arg.stdout.replace(/\n$/, "") : `${arg}`; 50 | var parseInput = (input) => { 51 | if (typeof input === "string" || input instanceof Buffer || input instanceof Stream) return input; 52 | if (typeof (input == null ? void 0 : input.stdout) === "string") return input.stdout; 53 | if (input == null ? void 0 : input.ctx) return parseInput(input.ctx.stdout); 54 | return null; 55 | }; 56 | var pFinally = (p, cb) => { 57 | var _a; 58 | return ((_a = p.finally) == null ? void 0 : _a.call(p, asyncVoidCall(cb))) || p.then(asyncVoidCall(cb), asyncVoidCall(cb)); 59 | }; 60 | export { 61 | assign, 62 | asyncVoidCall, 63 | buildCmd, 64 | g, 65 | immediate, 66 | isPromiseLike, 67 | isStringLiteral, 68 | makeDeferred, 69 | noop, 70 | pFinally, 71 | parseInput, 72 | quote, 73 | quotePwsh, 74 | randomId, 75 | substitute 76 | }; 77 | -------------------------------------------------------------------------------- /target/esm/zurk.mjs: -------------------------------------------------------------------------------- 1 | var __defProp = Object.defineProperty; 2 | var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; 3 | var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); 4 | 5 | // src/main/ts/zurk.ts 6 | import { 7 | invoke, 8 | normalizeCtx, 9 | asyncVoidCall 10 | } from "./spawn.mjs"; 11 | import { 12 | isPromiseLike, 13 | makeDeferred 14 | } from "./util.mjs"; 15 | import { 16 | formatErrorMessage, 17 | formatExitMessage 18 | } from "./error.mjs"; 19 | var ZURK = Symbol("Zurk"); 20 | var ZURKPROXY = Symbol("ZurkProxy"); 21 | var zurk = (opts) => opts.sync ? zurkSync(opts) : zurkAsync(opts); 22 | var zurkAsync = (opts) => { 23 | const { promise, resolve, reject } = makeDeferred(); 24 | const ctx = normalizeCtx(opts, { 25 | sync: false, 26 | callback(err, data) { 27 | ctx.error = ctx.nohandle ? err : getError(data); 28 | ctx.error && !ctx.nothrow ? reject(ctx.error) : resolve(zurkFactory(ctx)); 29 | } 30 | }); 31 | invoke(ctx); 32 | return zurkifyPromise(promise, ctx); 33 | }; 34 | var zurkSync = (opts) => { 35 | let response; 36 | const ctx = normalizeCtx(opts, { 37 | sync: true, 38 | callback(err, data) { 39 | ctx.error = ctx.nohandle ? err : getError(data); 40 | if (ctx.error && !ctx.nothrow) throw ctx.error; 41 | response = zurkFactory(ctx); 42 | } 43 | }); 44 | invoke(ctx); 45 | return response; 46 | }; 47 | var zurkifyPromise = (target, ctx) => { 48 | if (isZurkProxy(target) || !isPromiseLike(target)) 49 | return target; 50 | const proxy = new Proxy(target, { 51 | get(target2, p, receiver) { 52 | if (p === ZURKPROXY || p === ZURK) return p; 53 | if (p === "then") return target2.then.bind(target2); 54 | if (p === "catch") return target2.catch.bind(target2); 55 | if (p === "finally") return (cb) => proxy.then(asyncVoidCall(cb), asyncVoidCall(cb)); 56 | if (p === "stdio") return ctx.stdio; 57 | if (p === "ctx") return ctx; 58 | if (p === "child") return ctx.child; 59 | if (p === "on") return function(name, cb) { 60 | ctx.ee.on(name, cb); 61 | return proxy; 62 | }; 63 | if (p in target2) return Reflect.get(target2, p, receiver); 64 | return target2.then((v) => Reflect.get(v, p, receiver)); 65 | } 66 | }); 67 | return proxy; 68 | }; 69 | var getError = (spawnResult) => { 70 | if (spawnResult.error) 71 | return new Error(formatErrorMessage(spawnResult.error, spawnResult.ctx.stack)); 72 | if (spawnResult.status || spawnResult.signal) 73 | return new Error(formatExitMessage(spawnResult.status, spawnResult.signal, spawnResult.stderr, spawnResult.ctx.stack)); 74 | return null; 75 | }; 76 | var isZurkAny = (o) => (o == null ? void 0 : o[ZURK]) === ZURK; 77 | var isZurk = (o) => isZurkAny(o) && !(o instanceof Promise); 78 | var isZurkPromise = (o) => isZurkAny(o) && o instanceof Promise; 79 | var isZurkProxy = (value) => (value == null ? void 0 : value[ZURKPROXY]) === ZURKPROXY; 80 | var zurkFactory = (ctx) => new Zurk(ctx); 81 | var _a; 82 | _a = ZURK; 83 | var Zurk = class { 84 | constructor(ctx) { 85 | __publicField(this, _a, ZURK); 86 | __publicField(this, "ctx"); 87 | this.ctx = ctx; 88 | } 89 | on(name, cb) { 90 | this.ctx.ee.on(name, cb); 91 | return this; 92 | } 93 | get child() { 94 | return this.ctx.child; 95 | } 96 | get status() { 97 | var _a2, _b; 98 | return (_b = (_a2 = this.ctx.fulfilled) == null ? void 0 : _a2.status) != null ? _b : null; 99 | } 100 | get signal() { 101 | var _a2, _b; 102 | return (_b = (_a2 = this.ctx.fulfilled) == null ? void 0 : _a2.signal) != null ? _b : null; 103 | } 104 | get error() { 105 | return this.ctx.error; 106 | } 107 | get stderr() { 108 | var _a2; 109 | return ((_a2 = this.ctx.fulfilled) == null ? void 0 : _a2.stderr) || ""; 110 | } 111 | get stdout() { 112 | var _a2; 113 | return ((_a2 = this.ctx.fulfilled) == null ? void 0 : _a2.stdout) || ""; 114 | } 115 | get stdall() { 116 | var _a2; 117 | return ((_a2 = this.ctx.fulfilled) == null ? void 0 : _a2.stdall) || ""; 118 | } 119 | get stdio() { 120 | return [ 121 | this.ctx.stdin, 122 | this.ctx.stdout, 123 | this.ctx.stderr 124 | ]; 125 | } 126 | get duration() { 127 | var _a2, _b; 128 | return (_b = (_a2 = this.ctx.fulfilled) == null ? void 0 : _a2.duration) != null ? _b : 0; 129 | } 130 | toString() { 131 | return this.stdall.trim(); 132 | } 133 | valueOf() { 134 | return this.stdall.trim(); 135 | } 136 | }; 137 | export { 138 | ZURK, 139 | ZURKPROXY, 140 | getError, 141 | isZurk, 142 | isZurkAny, 143 | isZurkPromise, 144 | isZurkProxy, 145 | zurk, 146 | zurkAsync, 147 | zurkFactory, 148 | zurkSync, 149 | zurkifyPromise 150 | }; 151 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "./src/main/ts/", 4 | "baseUrl": "./src/main/ts/", 5 | "esModuleInterop": true, 6 | "allowImportingTsExtensions": true, 7 | "moduleResolution": "nodenext", 8 | "module": "nodenext", 9 | "target": "esnext", 10 | "strict": true, 11 | "noImplicitAny": true, 12 | "declaration": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "allowJs": false 15 | }, 16 | "ts-node": { 17 | "transpileOnly": true, 18 | "files": true, 19 | }, 20 | "include": [ 21 | "src/main/ts/" 22 | ], 23 | "exclude": [ 24 | "node_modules", 25 | "src/test" 26 | ] 27 | } 28 | --------------------------------------------------------------------------------