├── .cargo └── config.toml ├── .changeset ├── .markdownlint.json ├── README.md └── config.json ├── .editorconfig ├── .eslintrc.yml ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ └── feature_request.yml ├── renovate.json └── workflows │ ├── cspell.yml │ ├── lint-pr-title.yml │ ├── lint.yml │ ├── release.yml │ └── test.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .prettierignore ├── .taplo.toml ├── .yarn └── releases │ └── yarn-4.6.0.cjs ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── __test__ ├── exclusive.spec.ts ├── global.spec.ts ├── index.spec.ts ├── num_args.spec.ts ├── options.spec.ts ├── package.json ├── subcommand.spec.ts └── tsconfig.json ├── babel.config.json ├── benchmark ├── bench.ts ├── package.json └── tsconfig.json ├── build.rs ├── cspell.json ├── examples ├── .gitignore ├── exclusive.cts ├── global.cts ├── inquire.cts ├── no_version.cts ├── num_args.cts ├── package.json ├── positional_required.cts ├── progressbar.cts ├── simple.cts └── tsconfig.json ├── index.d.ts ├── index.js ├── jest.config.ts ├── npm ├── android-arm-eabi │ ├── README.md │ └── package.json ├── android-arm64 │ ├── README.md │ └── package.json ├── darwin-arm64 │ ├── README.md │ └── package.json ├── darwin-x64 │ ├── README.md │ └── package.json ├── freebsd-x64 │ ├── README.md │ └── package.json ├── linux-arm-gnueabihf │ ├── README.md │ └── package.json ├── linux-arm64-gnu │ ├── README.md │ └── package.json ├── linux-arm64-musl │ ├── README.md │ └── package.json ├── linux-x64-gnu │ ├── README.md │ └── package.json ├── linux-x64-musl │ ├── README.md │ └── package.json ├── win32-arm64-msvc │ ├── README.md │ └── package.json ├── win32-ia32-msvc │ ├── README.md │ └── package.json └── win32-x64-msvc │ ├── README.md │ └── package.json ├── package.json ├── pnpm-lock.yaml ├── rustfmt.toml ├── scripts ├── bump.mjs └── watch.mjs ├── src ├── command.rs ├── inquire.rs ├── lib.rs ├── progressbar.rs ├── resolver.rs ├── types.rs └── utils.rs └── tsconfig.json /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.aarch64-unknown-linux-musl] 2 | linker = "aarch64-linux-musl-gcc" 3 | rustflags = ["-C", "target-feature=-crt-static"] 4 | -------------------------------------------------------------------------------- /.changeset/.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "MD041": false 3 | } -------------------------------------------------------------------------------- /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.2/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "public", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors or IDEs 3 | # http://editorconfig.org 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | charset = utf-8 11 | trim_trailing_whitespace = true 12 | insert_final_newline = true 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | parser: '@typescript-eslint/parser' 2 | 3 | parserOptions: 4 | ecmaFeatures: 5 | jsx: true 6 | ecmaVersion: latest 7 | sourceType: module 8 | project: ./tsconfig.json 9 | 10 | env: 11 | browser: true 12 | es6: true 13 | node: true 14 | jest: true 15 | 16 | ignorePatterns: ['index.js'] 17 | 18 | plugins: 19 | - import 20 | - '@typescript-eslint' 21 | 22 | extends: 23 | - eslint:recommended 24 | - plugin:prettier/recommended 25 | 26 | rules: 27 | # 0 = off, 1 = warn, 2 = error 28 | 'space-before-function-paren': 0 29 | 'no-useless-constructor': 0 30 | 'no-undef': 2 31 | 'no-console': [2, { allow: ['error', 'warn', 'info', 'assert'] }] 32 | 'comma-dangle': ['error', 'only-multiline'] 33 | 'no-unused-vars': 0 34 | 'no-var': 2 35 | 'one-var-declaration-per-line': 2 36 | 'prefer-const': 2 37 | 'no-const-assign': 2 38 | 'no-duplicate-imports': 2 39 | 'no-use-before-define': [2, { 'functions': false, 'classes': false }] 40 | 'eqeqeq': [2, 'always', { 'null': 'ignore' }] 41 | 'no-case-declarations': 0 42 | 'no-restricted-syntax': 43 | [ 44 | 2, 45 | { 46 | 'selector': 'BinaryExpression[operator=/(==|===|!=|!==)/][left.raw=true], BinaryExpression[operator=/(==|===|!=|!==)/][right.raw=true]', 47 | 'message': Don't compare for equality against boolean literals, 48 | }, 49 | ] 50 | 51 | # https://github.com/benmosher/eslint-plugin-import/pull/334 52 | 'import/no-duplicates': 2 53 | 'import/first': 2 54 | 'import/newline-after-import': 2 55 | 'import/order': 56 | [ 57 | 2, 58 | { 59 | 'newlines-between': 'always', 60 | 'alphabetize': { 'order': 'asc' }, 61 | 'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'], 62 | }, 63 | ] 64 | 65 | overrides: 66 | - files: 67 | - ./**/*{.ts,.tsx} 68 | rules: 69 | 'no-unused-vars': [2, { varsIgnorePattern: '^_', argsIgnorePattern: '^_', ignoreRestSiblings: true }] 70 | 'no-undef': 0 71 | # TypeScript declare merge 72 | 'no-redeclare': 0 73 | 'no-useless-constructor': 0 74 | 'no-dupe-class-members': 0 75 | 'no-case-declarations': 0 76 | 'no-duplicate-imports': 0 77 | # TypeScript Interface and Type 78 | 'no-use-before-define': 0 79 | 80 | '@typescript-eslint/adjacent-overload-signatures': 2 81 | '@typescript-eslint/await-thenable': 2 82 | '@typescript-eslint/consistent-type-assertions': 2 83 | '@typescript-eslint/ban-types': 84 | [ 85 | 'error', 86 | { 87 | 'types': 88 | { 89 | 'String': { 'message': 'Use string instead', 'fixWith': 'string' }, 90 | 'Number': { 'message': 'Use number instead', 'fixWith': 'number' }, 91 | 'Boolean': { 'message': 'Use boolean instead', 'fixWith': 'boolean' }, 92 | 'Function': { 'message': 'Use explicit type instead' }, 93 | }, 94 | }, 95 | ] 96 | '@typescript-eslint/explicit-member-accessibility': 97 | [ 98 | 'error', 99 | { 100 | accessibility: 'explicit', 101 | overrides: 102 | { 103 | accessors: 'no-public', 104 | constructors: 'no-public', 105 | methods: 'no-public', 106 | properties: 'no-public', 107 | parameterProperties: 'explicit', 108 | }, 109 | }, 110 | ] 111 | '@typescript-eslint/method-signature-style': 2 112 | '@typescript-eslint/no-floating-promises': 2 113 | '@typescript-eslint/no-implied-eval': 2 114 | '@typescript-eslint/no-for-in-array': 2 115 | '@typescript-eslint/no-inferrable-types': 2 116 | '@typescript-eslint/no-invalid-void-type': 2 117 | '@typescript-eslint/no-misused-new': 2 118 | '@typescript-eslint/no-misused-promises': 2 119 | '@typescript-eslint/no-namespace': 2 120 | '@typescript-eslint/no-non-null-asserted-optional-chain': 2 121 | '@typescript-eslint/no-throw-literal': 2 122 | '@typescript-eslint/no-unnecessary-boolean-literal-compare': 2 123 | '@typescript-eslint/prefer-for-of': 2 124 | '@typescript-eslint/prefer-nullish-coalescing': 2 125 | '@typescript-eslint/switch-exhaustiveness-check': 2 126 | '@typescript-eslint/prefer-optional-chain': 2 127 | '@typescript-eslint/prefer-readonly': 2 128 | '@typescript-eslint/prefer-string-starts-ends-with': 0 129 | '@typescript-eslint/no-array-constructor': 2 130 | '@typescript-eslint/require-await': 2 131 | '@typescript-eslint/return-await': 2 132 | '@typescript-eslint/ban-ts-comment': 133 | [2, { 'ts-expect-error': false, 'ts-ignore': true, 'ts-nocheck': true, 'ts-check': false }] 134 | '@typescript-eslint/naming-convention': 135 | [ 136 | 2, 137 | { 138 | selector: 'memberLike', 139 | format: ['camelCase', 'PascalCase'], 140 | modifiers: ['private'], 141 | leadingUnderscore: 'forbid', 142 | }, 143 | ] 144 | '@typescript-eslint/no-unused-vars': 145 | [2, { varsIgnorePattern: '^_', argsIgnorePattern: '^_', ignoreRestSiblings: true }] 146 | '@typescript-eslint/member-ordering': 147 | [ 148 | 2, 149 | { 150 | default: 151 | [ 152 | 'public-static-field', 153 | 'protected-static-field', 154 | 'private-static-field', 155 | 'public-static-method', 156 | 'protected-static-method', 157 | 'private-static-method', 158 | 'public-instance-field', 159 | 'protected-instance-field', 160 | 'private-instance-field', 161 | 'public-constructor', 162 | 'protected-constructor', 163 | 'private-constructor', 164 | 'public-instance-method', 165 | 'protected-instance-method', 166 | 'private-instance-method', 167 | ], 168 | }, 169 | ] 170 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | 5 | *.ts text eol=lf merge=union 6 | *.tsx text eol=lf merge=union 7 | *.rs text eol=lf merge=union 8 | *.js text eol=lf merge=union 9 | *.json text eol=lf merge=union 10 | *.debug text eol=lf merge=union 11 | 12 | # Generated codes 13 | index.js linguist-detectable=false 14 | index.d.ts linguist-detectable=false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: "Bug Report" 2 | description: Report a bug or issue with archons. 3 | title: "[Bug Report]: " 4 | labels: ["bug"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | **Before You Start...** 10 | 11 | This form is only for submitting bug reports. If you have a usage question 12 | or are unsure if this is really a bug, please use the [Discussions](https://github.com/noctisynth/Grassator/discussions) section instead. 13 | 14 | Also try to search for your issue - it may have already been answered or even fixed in the development branch. 15 | However, if you find that an old, closed issue still persists in the latest version, 16 | you should open a new issue using the form below instead of commenting on the old issue. 17 | - type: textarea 18 | id: steps-to-reproduce 19 | attributes: 20 | label: Steps to reproduce 21 | description: | 22 | What do we need to do after opening your repro in order to make the bug happen? 23 | Clear and concise reproduction instructions are important for us to be able to triage your issue in a timely manner. 24 | Note that you can use [Markdown](https://guides.github.com/features/mastering-markdown/) to format lists and code. 25 | placeholder: | 26 | Please provide a step-by-step guide to reproduce the issue. If possible, 27 | provide screenshots or files like videos that show the issue. 28 | validations: 29 | required: true 30 | - type: input 31 | id: repro 32 | attributes: 33 | label: Reproduce link 34 | description: "Please provide a simplest reproduction of the problem, provide the URL of the project:" 35 | placeholder: Paste link here 36 | - type: textarea 37 | id: actually-happening 38 | attributes: 39 | label: What is actually happening? 40 | placeholder: | 41 | Describe what happened in detail. 42 | validations: 43 | required: true 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: "Feature Request" 2 | description: Submit a feature request or enhancement for archons. 3 | title: "[Feature Request]: " 4 | labels: ["pending triage"] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | **Before You Start...** 10 | 11 | This form is only for submitting feature requests. If you have a usage question 12 | or are unsure if this is really a bug, make sure to: 13 | Also try to search for your issue - another user may have already requested something similar! 14 | 15 | - type: textarea 16 | id: problem-description 17 | attributes: 18 | label: What problem does this feature solve? 19 | description: | 20 | Explain your use case, context, and rationale behind this feature request. More importantly, 21 | what is the **end user experience** you are trying to build that led to the need for this feature? 22 | placeholder: Problem description 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: proposed-API 27 | attributes: 28 | label: What does the proposed API look like? 29 | description: | 30 | Describe how you propose to solve the problem and provide code samples of how the API would work once implemented. 31 | Note that you can use [Markdown](https://guides.github.com/features/mastering-markdown/) to format your code blocks. 32 | placeholder: Proposed API 33 | validations: 34 | required: true 35 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:base", "group:allNonMajor", ":preserveSemverRanges", ":disablePeerDependencies"], 4 | "labels": ["dependencies"], 5 | "packageRules": [ 6 | { 7 | "matchPackageNames": ["@napi/cli", "napi", "napi-build", "napi-derive"], 8 | "addLabels": ["napi-rs"], 9 | "groupName": "napi-rs" 10 | }, 11 | { 12 | "matchPackagePatterns": ["^eslint", "^@typescript-eslint"], 13 | "groupName": "linter" 14 | } 15 | ], 16 | "commitMessagePrefix": "chore: ", 17 | "commitMessageAction": "bump up", 18 | "commitMessageTopic": "{{depName}} version", 19 | "ignoreDeps": [] 20 | } 21 | -------------------------------------------------------------------------------- /.github/workflows/cspell.yml: -------------------------------------------------------------------------------- 1 | name: CSpell 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | pull_request: 7 | branches: ["main"] 8 | 9 | jobs: 10 | lint-cspell: 11 | name: Lint CSpell 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | with: 16 | submodules: recursive 17 | - uses: streetsidesoftware/cspell-action@v6 18 | with: 19 | files: "**" 20 | config: cspell.json 21 | strict: true 22 | verbose: true 23 | -------------------------------------------------------------------------------- /.github/workflows/lint-pr-title.yml: -------------------------------------------------------------------------------- 1 | name: Lint Pr Title 2 | 3 | on: 4 | pull_request_target: 5 | types: 6 | - opened 7 | - edited 8 | - synchronize 9 | - reopened 10 | 11 | permissions: 12 | pull-requests: read 13 | 14 | jobs: 15 | lint-pr-title: 16 | name: Validating PR title 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: amannn/action-semantic-pull-request@v5 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | with: 23 | types: | 24 | fix 25 | feat 26 | docs 27 | style 28 | refactor 29 | perf 30 | test 31 | build 32 | ci 33 | chore 34 | revert 35 | release 36 | requireScope: false 37 | ignoreLabels: | 38 | bot 39 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags-ignore: 8 | - '**' 9 | pull_request: 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | jobs: 14 | lint: 15 | name: Lint 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: pnpm/action-setup@v4 20 | name: Install pnpm 21 | with: 22 | version: 10 23 | run_install: false 24 | 25 | - name: Setup node 26 | uses: actions/setup-node@v4 27 | with: 28 | node-version: 20 29 | cache: 'pnpm' 30 | 31 | - name: Install 32 | uses: dtolnay/rust-toolchain@stable 33 | with: 34 | components: clippy, rustfmt 35 | 36 | - name: Install dependencies 37 | run: pnpm install 38 | 39 | - name: ESLint 40 | run: pnpm lint 41 | 42 | - name: Cargo fmt 43 | run: cargo fmt -- --check 44 | 45 | - name: Clippy 46 | run: cargo clippy 47 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - '**/*.md' 9 | - LICENSE 10 | - '**/*.gitignore' 11 | - .editorconfig 12 | - docs/** 13 | 14 | concurrency: 'release ${{ github.workflow }}-${{ github.ref }}' 15 | 16 | permissions: write-all 17 | 18 | jobs: 19 | build-and-test: 20 | name: Build and Test 21 | uses: ./.github/workflows/test.yml 22 | 23 | release: 24 | name: Release 25 | needs: [build-and-test] 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Checkout Repository 29 | uses: actions/checkout@v4 30 | - uses: pnpm/action-setup@v4 31 | name: Install pnpm 32 | with: 33 | version: 10 34 | run_install: false 35 | - name: Setup node 36 | uses: actions/setup-node@v4 37 | with: 38 | node-version: 20 39 | cache: pnpm 40 | - name: Install dependencies 41 | run: pnpm install 42 | - name: Download all artifacts 43 | uses: actions/download-artifact@v4 44 | with: 45 | path: artifacts 46 | - name: Move artifacts 47 | run: pnpm artifacts 48 | - name: List packages 49 | run: ls -R ./npm 50 | shell: bash 51 | - name: Set NPM Provenance 52 | run: npm config set provenance true 53 | - name: Create Release Pull Request or Publish to npm 54 | id: changesets 55 | uses: changesets/action@v1 56 | with: 57 | title: 'chore(release): version packages' 58 | commit: 'chore(release): version packages' 59 | version: pnpm bump 60 | publish: npx changeset publish 61 | env: 62 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 63 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 64 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | env: 3 | DEBUG: napi:* 4 | APP_NAME: archons-napi 5 | MACOSX_DEPLOYMENT_TARGET: '10.13' 6 | CARGO_INCREMENTAL: '1' 7 | permissions: 8 | contents: write 9 | id-token: write 10 | on: 11 | pull_request: 12 | branches: 13 | - main 14 | paths-ignore: 15 | - '**/*.md' 16 | - LICENSE 17 | - '**/*.gitignore' 18 | - .editorconfig 19 | - docs/** 20 | - cspell.json 21 | workflow_call: null 22 | concurrency: 23 | group: '${{ github.workflow }}-${{ github.ref }}' 24 | cancel-in-progress: true 25 | jobs: 26 | build: 27 | strategy: 28 | fail-fast: false 29 | matrix: 30 | settings: 31 | - host: macos-latest 32 | target: x86_64-apple-darwin 33 | build: pnpm build --target x86_64-apple-darwin 34 | - host: windows-latest 35 | build: |- 36 | corepack disable || true && 37 | npm install -g pnpm && 38 | export PNPM_HOME="/root/.local/share/pnpm" && 39 | export PATH="$PNPM_HOME:$PATH" && 40 | pnpm build --target x86_64-pc-windows-msvc 41 | target: x86_64-pc-windows-msvc 42 | - host: windows-latest 43 | build: pnpm build --target i686-pc-windows-msvc 44 | target: i686-pc-windows-msvc 45 | - host: ubuntu-latest 46 | target: x86_64-unknown-linux-gnu 47 | docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian 48 | build: |- 49 | corepack disable || true && 50 | npm install -g pnpm && 51 | export PNPM_HOME="/root/.local/share/pnpm" && 52 | export PATH="$PNPM_HOME:$PATH" && 53 | pnpm build --target x86_64-unknown-linux-gnu 54 | - host: ubuntu-latest 55 | target: x86_64-unknown-linux-musl 56 | docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine 57 | build: pnpm build --target x86_64-unknown-linux-musl 58 | - host: macos-latest 59 | target: aarch64-apple-darwin 60 | build: pnpm build --target aarch64-apple-darwin 61 | - host: ubuntu-latest 62 | target: aarch64-unknown-linux-gnu 63 | docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-debian-aarch64 64 | build: |- 65 | corepack disable || true && 66 | npm install -g pnpm && 67 | export PNPM_HOME="/root/.local/share/pnpm" && 68 | export PATH="$PNPM_HOME:$PATH" && 69 | pnpm build --target aarch64-unknown-linux-gnu 70 | - host: ubuntu-latest 71 | target: armv7-unknown-linux-gnueabihf 72 | setup: | 73 | sudo apt-get update 74 | sudo apt-get install gcc-arm-linux-gnueabihf -y 75 | build: | 76 | pnpm build --target armv7-unknown-linux-gnueabihf 77 | - host: ubuntu-latest 78 | target: aarch64-linux-android 79 | build: pnpm build --target aarch64-linux-android 80 | - host: ubuntu-latest 81 | target: armv7-linux-androideabi 82 | build: pnpm build --target armv7-linux-androideabi 83 | - host: ubuntu-latest 84 | target: aarch64-unknown-linux-musl 85 | docker: ghcr.io/napi-rs/napi-rs/nodejs-rust:lts-alpine 86 | build: |- 87 | set -e && 88 | rustup target add aarch64-unknown-linux-musl && 89 | pnpm build --target aarch64-unknown-linux-musl 90 | - host: windows-latest 91 | target: aarch64-pc-windows-msvc 92 | build: pnpm build --target aarch64-pc-windows-msvc 93 | name: Build on ${{ matrix.settings.target }} - node@18 94 | runs-on: ${{ matrix.settings.host }} 95 | steps: 96 | - uses: actions/checkout@v4 97 | - uses: pnpm/action-setup@v4 98 | name: Install pnpm 99 | with: 100 | version: 10 101 | run_install: false 102 | - name: Setup node 103 | uses: actions/setup-node@v4 104 | if: ${{ !matrix.settings.docker }} 105 | with: 106 | node-version: 20 107 | cache: pnpm 108 | - name: Install 109 | uses: dtolnay/rust-toolchain@stable 110 | if: ${{ !matrix.settings.docker }} 111 | with: 112 | toolchain: stable 113 | targets: ${{ matrix.settings.target }} 114 | - name: Cache cargo 115 | uses: actions/cache@v4 116 | with: 117 | path: | 118 | ~/.cargo/registry/index/ 119 | ~/.cargo/registry/cache/ 120 | ~/.cargo/git/db/ 121 | .cargo-cache 122 | target/ 123 | key: ${{ matrix.settings.target }}-cargo-${{ matrix.settings.host }} 124 | - uses: goto-bus-stop/setup-zig@v2 125 | if: ${{ matrix.settings.target == 'armv7-unknown-linux-gnueabihf' }} 126 | with: 127 | version: 0.13.0 128 | - name: Setup toolchain 129 | run: ${{ matrix.settings.setup }} 130 | if: ${{ matrix.settings.setup }} 131 | shell: bash 132 | - name: Install dependencies 133 | run: pnpm install 134 | - name: Build in docker 135 | uses: addnab/docker-run-action@v3 136 | if: ${{ matrix.settings.docker }} 137 | with: 138 | image: ${{ matrix.settings.docker }} 139 | options: '--user 0:0 -v ${{ github.workspace }}/.cargo-cache/git/db:/usr/local/cargo/git/db -v ${{ github.workspace }}/.cargo/registry/cache:/usr/local/cargo/registry/cache -v ${{ github.workspace }}/.cargo/registry/index:/usr/local/cargo/registry/index -v ${{ github.workspace }}:/build -w /build' 140 | run: ${{ matrix.settings.build }} 141 | - name: Build 142 | run: ${{ matrix.settings.build }} 143 | if: ${{ !matrix.settings.docker }} 144 | shell: bash 145 | - name: Upload artifact 146 | uses: actions/upload-artifact@v4 147 | with: 148 | name: bindings-${{ matrix.settings.target }} 149 | path: ${{ env.APP_NAME }}.*.node 150 | if-no-files-found: error 151 | build-freebsd: 152 | runs-on: ubuntu-latest 153 | name: Build FreeBSD 154 | steps: 155 | - uses: actions/checkout@v4 156 | - name: Build 157 | id: build 158 | uses: cross-platform-actions/action@v0.27.0 159 | env: 160 | DEBUG: napi:* 161 | RUSTUP_IO_THREADS: 1 162 | with: 163 | operating_system: freebsd 164 | version: '14.0' 165 | memory: 8G 166 | cpu_count: 3 167 | environment_variables: DEBUG RUSTUP_IO_THREADS 168 | shell: bash 169 | run: | 170 | sudo pkg install -y -f curl node libnghttp2 npm 171 | sudo npm install -g pnpm --ignore-scripts 172 | curl https://sh.rustup.rs -sSf --output rustup.sh 173 | sh rustup.sh -y --profile minimal --default-toolchain beta 174 | source "$HOME/.cargo/env" 175 | echo "~~~~ rustc --version ~~~~" 176 | rustc --version 177 | echo "~~~~ node -v ~~~~" 178 | node -v 179 | echo "~~~~ pnpm --version ~~~~" 180 | pnpm --version 181 | pwd 182 | ls -lah 183 | whoami 184 | env 185 | freebsd-version 186 | pnpm install 187 | pnpm build 188 | rm -rf node_modules 189 | rm -rf target 190 | rm -rf .pnpm/cache 191 | - name: Upload artifact 192 | uses: actions/upload-artifact@v4 193 | with: 194 | name: bindings-freebsd 195 | path: ${{ env.APP_NAME }}.*.node 196 | if-no-files-found: error 197 | test-macOS-windows-binding: 198 | name: Test bindings on ${{ matrix.settings.target }} - node@${{ matrix.node }} 199 | needs: 200 | - build 201 | strategy: 202 | fail-fast: false 203 | matrix: 204 | settings: 205 | - host: windows-latest 206 | target: x86_64-pc-windows-msvc 207 | architecture: x64 208 | - host: macos-latest 209 | target: aarch64-apple-darwin 210 | architecture: arm64 211 | - host: macos-latest 212 | target: x86_64-apple-darwin 213 | architecture: x64 214 | node: 215 | - '18' 216 | - '20' 217 | runs-on: ${{ matrix.settings.host }} 218 | steps: 219 | - uses: actions/checkout@v4 220 | - uses: pnpm/action-setup@v4 221 | name: Install pnpm 222 | with: 223 | version: 10 224 | run_install: false 225 | - name: Setup node 226 | uses: actions/setup-node@v4 227 | with: 228 | node-version: ${{ matrix.node }} 229 | cache: pnpm 230 | architecture: ${{ matrix.settings.architecture }} 231 | - name: Install dependencies 232 | run: pnpm install 233 | - name: Download artifacts 234 | uses: actions/download-artifact@v4 235 | with: 236 | name: bindings-${{ matrix.settings.target }} 237 | path: . 238 | - name: Build Examples 239 | run: pnpm build:examples 240 | - name: List packages 241 | run: ls -R . 242 | shell: bash 243 | - name: Test bindings 244 | run: pnpm test 245 | test-linux-x64-gnu-binding: 246 | name: Test bindings on Linux-x64-gnu - node@${{ matrix.node }} 247 | needs: 248 | - build 249 | strategy: 250 | fail-fast: false 251 | matrix: 252 | node: 253 | - '18' 254 | - '20' 255 | runs-on: ubuntu-latest 256 | steps: 257 | - uses: actions/checkout@v4 258 | - uses: pnpm/action-setup@v4 259 | name: Install pnpm 260 | with: 261 | version: 10 262 | run_install: false 263 | - name: Setup node 264 | uses: actions/setup-node@v4 265 | with: 266 | node-version: ${{ matrix.node }} 267 | cache: pnpm 268 | - name: Install dependencies 269 | run: pnpm install 270 | - name: Download artifacts 271 | uses: actions/download-artifact@v4 272 | with: 273 | name: bindings-x86_64-unknown-linux-gnu 274 | path: . 275 | - name: Build Examples 276 | run: pnpm build:examples 277 | - name: List packages 278 | run: ls -R . 279 | shell: bash 280 | - name: Setup and run tests 281 | uses: addnab/docker-run-action@v3 282 | with: 283 | image: node:${{ matrix.node }}-slim 284 | options: '-v ${{ github.workspace }}:/build -w /build' 285 | run: | 286 | set -e 287 | npm install -g pnpm 288 | pnpm test 289 | test-linux-x64-musl-binding: 290 | name: Test bindings on x86_64-unknown-linux-musl - node@${{ matrix.node }} 291 | needs: 292 | - build 293 | strategy: 294 | fail-fast: false 295 | matrix: 296 | node: 297 | - '18' 298 | - '20' 299 | runs-on: ubuntu-latest 300 | steps: 301 | - uses: actions/checkout@v4 302 | - uses: pnpm/action-setup@v4 303 | name: Install pnpm 304 | with: 305 | version: 10 306 | run_install: false 307 | - name: Setup node 308 | uses: actions/setup-node@v4 309 | with: 310 | node-version: ${{ matrix.node }} 311 | cache: pnpm 312 | - name: Install dependencies 313 | run: | 314 | pnpm config set supportedArchitectures.libc "musl" 315 | pnpm install 316 | - name: Download artifacts 317 | uses: actions/download-artifact@v4 318 | with: 319 | name: bindings-x86_64-unknown-linux-musl 320 | path: . 321 | - name: Build Examples 322 | run: pnpm build:examples 323 | - name: List packages 324 | run: ls -R . 325 | shell: bash 326 | - name: Setup and run tests 327 | uses: addnab/docker-run-action@v3 328 | with: 329 | image: node:${{ matrix.node }}-alpine 330 | options: '-v ${{ github.workspace }}:/build -w /build' 331 | run: | 332 | set -e 333 | npm install -g pnpm 334 | pnpm test 335 | test-linux-aarch64-gnu-binding: 336 | name: Test bindings on aarch64-unknown-linux-gnu - node@${{ matrix.node }} 337 | needs: 338 | - build 339 | strategy: 340 | fail-fast: false 341 | matrix: 342 | node: 343 | - '18' 344 | - '20' 345 | runs-on: ubuntu-latest 346 | steps: 347 | - uses: actions/checkout@v4 348 | - uses: pnpm/action-setup@v4 349 | name: Install pnpm 350 | with: 351 | version: 10 352 | run_install: false 353 | - name: Download artifacts 354 | uses: actions/download-artifact@v4 355 | with: 356 | name: bindings-aarch64-unknown-linux-gnu 357 | path: . 358 | - name: List packages 359 | run: ls -R . 360 | shell: bash 361 | - name: Install dependencies 362 | run: | 363 | pnpm config set supportedArchitectures.cpu "arm64" 364 | pnpm config set supportedArchitectures.libc "glibc" 365 | pnpm install 366 | - name: Build Examples 367 | run: pnpm build:examples 368 | - name: Set up QEMU 369 | uses: docker/setup-qemu-action@v3 370 | with: 371 | platforms: arm64 372 | - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 373 | - name: Setup and run tests 374 | uses: addnab/docker-run-action@v3 375 | with: 376 | image: node:${{ matrix.node }}-slim 377 | options: '--platform linux/arm64 -v ${{ github.workspace }}:/build -w /build' 378 | run: | 379 | set -e 380 | npm install -g pnpm 381 | pnpm test 382 | ls -la 383 | test-linux-aarch64-musl-binding: 384 | name: Test bindings on aarch64-unknown-linux-musl - node@lts 385 | needs: 386 | - build 387 | runs-on: ubuntu-latest 388 | steps: 389 | - uses: actions/checkout@v4 390 | - uses: pnpm/action-setup@v4 391 | name: Install pnpm 392 | with: 393 | version: 10 394 | run_install: false 395 | - name: Download artifacts 396 | uses: actions/download-artifact@v4 397 | with: 398 | name: bindings-aarch64-unknown-linux-musl 399 | path: . 400 | - name: List packages 401 | run: ls -R . 402 | shell: bash 403 | - name: Install dependencies 404 | run: | 405 | pnpm config set supportedArchitectures.cpu "arm64" 406 | pnpm config set supportedArchitectures.libc "musl" 407 | pnpm install 408 | - name: Build Examples 409 | run: pnpm build:examples 410 | - name: Set up QEMU 411 | uses: docker/setup-qemu-action@v3 412 | with: 413 | platforms: arm64 414 | - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 415 | - name: Setup and run tests 416 | uses: addnab/docker-run-action@v3 417 | with: 418 | image: node:lts-alpine 419 | options: '--platform linux/arm64 -v ${{ github.workspace }}:/build -w /build' 420 | run: | 421 | set -e 422 | npm install -g pnpm 423 | pnpm test 424 | test-linux-arm-gnueabihf-binding: 425 | name: Test bindings on armv7-unknown-linux-gnueabihf - node@${{ matrix.node }} 426 | needs: 427 | - build 428 | strategy: 429 | fail-fast: false 430 | matrix: 431 | node: 432 | - '18' 433 | - '20' 434 | runs-on: ubuntu-latest 435 | steps: 436 | - uses: actions/checkout@v4 437 | - uses: pnpm/action-setup@v4 438 | name: Install pnpm 439 | with: 440 | version: 10 441 | run_install: false 442 | - name: Download artifacts 443 | uses: actions/download-artifact@v4 444 | with: 445 | name: bindings-armv7-unknown-linux-gnueabihf 446 | path: . 447 | - name: List packages 448 | run: ls -R . 449 | shell: bash 450 | - name: Install dependencies 451 | run: | 452 | pnpm config set supportedArchitectures.cpu "arm" 453 | pnpm install 454 | - name: Build Examples 455 | run: pnpm build:examples 456 | - name: Set up QEMU 457 | uses: docker/setup-qemu-action@v3 458 | with: 459 | platforms: arm 460 | - run: docker run --rm --privileged multiarch/qemu-user-static --reset -p yes 461 | - name: Setup and run tests 462 | uses: addnab/docker-run-action@v3 463 | with: 464 | image: node:${{ matrix.node }}-bullseye-slim 465 | options: '--platform linux/arm/v7 -v ${{ github.workspace }}:/build -w /build' 466 | run: | 467 | set -e 468 | npm install -g pnpm 469 | pnpm test 470 | ls -la 471 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/node 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=node 4 | 5 | ### Node ### 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | lerna-debug.log* 13 | 14 | # Diagnostic reports (https://nodejs.org/api/report.html) 15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 16 | 17 | # Runtime data 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | 23 | # Directory for instrumented libs generated by jscoverage/JSCover 24 | lib-cov 25 | 26 | # Coverage directory used by tools like istanbul 27 | coverage 28 | *.lcov 29 | 30 | # nyc test coverage 31 | .nyc_output 32 | 33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 34 | .grunt 35 | 36 | # Bower dependency directory (https://bower.io/) 37 | bower_components 38 | 39 | # node-waf configuration 40 | .lock-wscript 41 | 42 | # Compiled binary addons (https://nodejs.org/api/addons.html) 43 | build/Release 44 | 45 | # Dependency directories 46 | node_modules/ 47 | jspm_packages/ 48 | 49 | # TypeScript v1 declaration files 50 | typings/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Microbundle cache 62 | .rpt2_cache/ 63 | .rts2_cache_cjs/ 64 | .rts2_cache_es/ 65 | .rts2_cache_umd/ 66 | 67 | # Optional REPL history 68 | .node_repl_history 69 | 70 | # Output of 'npm pack' 71 | *.tgz 72 | 73 | # Yarn Integrity file 74 | .yarn-integrity 75 | 76 | # dotenv environment variables file 77 | .env 78 | .env.test 79 | 80 | # parcel-bundler cache (https://parceljs.org/) 81 | .cache 82 | 83 | # Next.js build output 84 | .next 85 | 86 | # Nuxt.js build / generate output 87 | .nuxt 88 | dist 89 | 90 | # Gatsby files 91 | .cache/ 92 | # Comment in the public line in if your project uses Gatsby and not Next.js 93 | # https://nextjs.org/blog/next-9-1#public-directory-support 94 | # public 95 | 96 | # vuepress build output 97 | .vuepress/dist 98 | 99 | # Serverless directories 100 | .serverless/ 101 | 102 | # FuseBox cache 103 | .fusebox/ 104 | 105 | # DynamoDB Local files 106 | .dynamodb/ 107 | 108 | # TernJS port file 109 | .tern-port 110 | 111 | # Stores VSCode versions used for testing VSCode extensions 112 | .vscode-test 113 | 114 | # End of https://www.toptal.com/developers/gitignore/api/node 115 | 116 | 117 | # Added by cargo 118 | 119 | /target 120 | 121 | *.node 122 | .pnp.* 123 | .yarn/* 124 | !.yarn/patches 125 | !.yarn/plugins 126 | !.yarn/releases 127 | !.yarn/sdks 128 | !.yarn/versions 129 | 130 | # Napi-rs default CI 131 | .github/workflows/CI.yml 132 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | pnpm lint-staged 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | target 2 | -------------------------------------------------------------------------------- /.taplo.toml: -------------------------------------------------------------------------------- 1 | exclude = ["node_modules/**/*.toml"] 2 | 3 | # https://taplo.tamasfe.dev/configuration/formatter-options.html 4 | [formatting] 5 | align_entries = true 6 | indent_tables = true 7 | reorder_keys = true 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # archons 2 | 3 | ## 0.2.11 4 | 5 | ### Patch Changes 6 | 7 | - 7a1a423: Update dependencies 8 | - dc4ed52: Optimize action resolver, auto set action to `SetTrue` when parser is `"boolean"` 9 | 10 | ## 0.2.10 11 | 12 | ### Patch Changes 13 | 14 | - 7129402: Fix some compatibilities in inquire and use macros to minify codes 15 | 16 | ## 0.2.9 17 | 18 | ### Patch Changes 19 | 20 | - 8a0113c: Use macro apply for resolvers 21 | 22 | ## 0.2.8 23 | 24 | ### Patch Changes 25 | 26 | - 12b8866: Add support for Deno.js 27 | - 12b8866: Add support for inquire feature 28 | - 12b8866: Fix clippy warnings 29 | - 12b8866: Optimize performance of the parser and resolver 30 | 31 | ## 0.2.7 32 | 33 | ### Patch Changes 34 | 35 | - 7ef1753: Refactor `Context` into a js class 36 | - 4aa02ff: Support set customized tick strings and progress chars 37 | - fa31f1f: Add progressbar feature for `Context` 38 | 39 | ## 0.2.6 40 | 41 | ### Patch Changes 42 | 43 | - 87b3e8a: chore: bump up all non-major dependencies 44 | - 8ae38a4: Use faster hash algorithm instead of safe hasher 45 | 46 | ## 0.2.5 47 | 48 | ### Patch Changes 49 | 50 | - 2018e1b: chore: bump up oxlint version to ^0.9.0 51 | 52 | ## 0.2.4 53 | 54 | ### Patch Changes 55 | 56 | - dc0eac1: Improve doc string of `run` function 57 | - fcccf4e: Add `num_args` range resolver 58 | - fcccf4e: Support `required_equals` option field 59 | 60 | ## 0.2.3 61 | 62 | ### Patch Changes 63 | 64 | - d9a1ad6: Lock rust dependencies version 65 | 66 | ## 0.2.2 67 | 68 | ### Patch Changes 69 | 70 | - 4133af7: Support `exclusive` field for arguments 71 | - 4133af7: Support set `value_hint` for arguments 72 | 73 | ## 0.2.1 74 | 75 | ### Patch Changes 76 | 77 | - 2c3ce3f: Wrap `parse_arguments` to inner function 78 | - 2c3ce3f: Fix global option can't be downcast 79 | 80 | ## 0.2.0 81 | 82 | ### Minor Changes 83 | 84 | - bbfb42c: Refactor arguments parsing policy and support global options 85 | 86 | ### Patch Changes 87 | 88 | - bbfb42c: Fix context args annotations to `Record` 89 | - bbfb42c: Refactor merge arguments matches policy 90 | - bbfb42c: Remove `{ length: 1}` annotation 91 | - bbfb42c: Refactor `Vec` to `Vec<&str>` 92 | - 1b5d6b7: Fix args resolver in musl systems 93 | - bbfb42c: Improve parser resolver and determine default parser by action 94 | 95 | ## 0.1.7 96 | 97 | ### Patch Changes 98 | 99 | - fa1b5c6: Fix prepublish package names 100 | - be34ebc: Refactor codes to extract resolvers to single module 101 | - f4e7ff9: Support more options for command option 102 | - f4e7ff9: Support action option and resolve by str 103 | - f4e7ff9: Support `conflicts_with` in command option definition 104 | - f4e7ff9: Improvements of type annotations for command option 105 | - f4e7ff9: Add `default_missing` arg for option 106 | 107 | ## 0.1.6 108 | 109 | ### Patch Changes 110 | 111 | - d565ad4: Release v0.1.6 112 | - df6f8bf: Remove `VERSION` const from `Cargo.toml` 113 | 114 | ## 0.1.5 115 | 116 | ### Patch Changes 117 | 118 | - 3382c3a: Add `--js-package-name` option in build script 119 | 120 | ## 0.1.4 121 | 122 | ### Patch Changes 123 | 124 | - 8f34ec5: Support pass help option for args 125 | 126 | ## 0.1.3 127 | 128 | ### Patch Changes 129 | 130 | - 0968fe3: Fix napi package failed to release 131 | 132 | ## 0.1.2 133 | 134 | ### Patch Changes 135 | 136 | - bcee4ef: Support styled option for command meta 137 | 138 | ## 0.1.1 139 | 140 | ### Patch Changes 141 | 142 | - c3e676e: Set color choice to always 143 | - c3e676e: Implement utils functions to create clap command instance 144 | - c3e676e: Call callback functions with context object after merged parsed args 145 | - c3e676e: Merge parsed args by `clap-rs` to context args object 146 | - c3e676e: Remove features for clap-rs 147 | - c3e676e: Add `defineCommand` interface 148 | - c3e676e: Implement command definition types 149 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anstream" 16 | version = "0.6.18" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 19 | dependencies = [ 20 | "anstyle", 21 | "anstyle-parse", 22 | "anstyle-query", 23 | "anstyle-wincon", 24 | "colorchoice", 25 | "is_terminal_polyfill", 26 | "utf8parse", 27 | ] 28 | 29 | [[package]] 30 | name = "anstyle" 31 | version = "1.0.10" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 34 | 35 | [[package]] 36 | name = "anstyle-parse" 37 | version = "0.2.6" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 40 | dependencies = [ 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-query" 46 | version = "1.1.2" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 49 | dependencies = [ 50 | "windows-sys 0.59.0", 51 | ] 52 | 53 | [[package]] 54 | name = "anstyle-wincon" 55 | version = "3.0.7" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" 58 | dependencies = [ 59 | "anstyle", 60 | "once_cell", 61 | "windows-sys 0.59.0", 62 | ] 63 | 64 | [[package]] 65 | name = "archons" 66 | version = "0.1.0" 67 | dependencies = [ 68 | "clap", 69 | "indicatif", 70 | "inquire", 71 | "napi", 72 | "napi-build", 73 | "napi-derive", 74 | "rustc-hash", 75 | "thiserror", 76 | ] 77 | 78 | [[package]] 79 | name = "autocfg" 80 | version = "1.4.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 83 | 84 | [[package]] 85 | name = "bitflags" 86 | version = "1.3.2" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 89 | 90 | [[package]] 91 | name = "bitflags" 92 | version = "2.9.0" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" 95 | 96 | [[package]] 97 | name = "bumpalo" 98 | version = "3.17.0" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" 101 | 102 | [[package]] 103 | name = "byteorder" 104 | version = "1.5.0" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 107 | 108 | [[package]] 109 | name = "cfg-if" 110 | version = "1.0.0" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 113 | 114 | [[package]] 115 | name = "clap" 116 | version = "4.5.37" 117 | source = "registry+https://github.com/rust-lang/crates.io-index" 118 | checksum = "eccb054f56cbd38340b380d4a8e69ef1f02f1af43db2f0cc817a4774d80ae071" 119 | dependencies = [ 120 | "clap_builder", 121 | ] 122 | 123 | [[package]] 124 | name = "clap_builder" 125 | version = "4.5.37" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "efd9466fac8543255d3b1fcad4762c5e116ffe808c8a3043d4263cd4fd4862a2" 128 | dependencies = [ 129 | "anstream", 130 | "anstyle", 131 | "clap_lex", 132 | "strsim", 133 | ] 134 | 135 | [[package]] 136 | name = "clap_lex" 137 | version = "0.7.4" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 140 | 141 | [[package]] 142 | name = "colorchoice" 143 | version = "1.0.3" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 146 | 147 | [[package]] 148 | name = "console" 149 | version = "0.15.11" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "054ccb5b10f9f2cbf51eb355ca1d05c2d279ce1804688d0db74b4733a5aeafd8" 152 | dependencies = [ 153 | "encode_unicode", 154 | "libc", 155 | "once_cell", 156 | "unicode-width 0.2.0", 157 | "windows-sys 0.59.0", 158 | ] 159 | 160 | [[package]] 161 | name = "convert_case" 162 | version = "0.6.0" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" 165 | dependencies = [ 166 | "unicode-segmentation", 167 | ] 168 | 169 | [[package]] 170 | name = "crossterm" 171 | version = "0.25.0" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" 174 | dependencies = [ 175 | "bitflags 1.3.2", 176 | "crossterm_winapi", 177 | "libc", 178 | "mio", 179 | "parking_lot", 180 | "signal-hook", 181 | "signal-hook-mio", 182 | "winapi", 183 | ] 184 | 185 | [[package]] 186 | name = "crossterm_winapi" 187 | version = "0.9.1" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" 190 | dependencies = [ 191 | "winapi", 192 | ] 193 | 194 | [[package]] 195 | name = "ctor" 196 | version = "0.2.9" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" 199 | dependencies = [ 200 | "quote", 201 | "syn", 202 | ] 203 | 204 | [[package]] 205 | name = "dyn-clone" 206 | version = "1.0.19" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" 209 | 210 | [[package]] 211 | name = "encode_unicode" 212 | version = "1.0.0" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" 215 | 216 | [[package]] 217 | name = "fuzzy-matcher" 218 | version = "0.3.7" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" 221 | dependencies = [ 222 | "thread_local", 223 | ] 224 | 225 | [[package]] 226 | name = "fxhash" 227 | version = "0.2.1" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" 230 | dependencies = [ 231 | "byteorder", 232 | ] 233 | 234 | [[package]] 235 | name = "indicatif" 236 | version = "0.17.11" 237 | source = "registry+https://github.com/rust-lang/crates.io-index" 238 | checksum = "183b3088984b400f4cfac3620d5e076c84da5364016b4f49473de574b2586235" 239 | dependencies = [ 240 | "console", 241 | "number_prefix", 242 | "portable-atomic", 243 | "unicode-width 0.2.0", 244 | "web-time", 245 | ] 246 | 247 | [[package]] 248 | name = "inquire" 249 | version = "0.7.5" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a" 252 | dependencies = [ 253 | "bitflags 2.9.0", 254 | "crossterm", 255 | "dyn-clone", 256 | "fuzzy-matcher", 257 | "fxhash", 258 | "newline-converter", 259 | "once_cell", 260 | "unicode-segmentation", 261 | "unicode-width 0.1.14", 262 | ] 263 | 264 | [[package]] 265 | name = "is_terminal_polyfill" 266 | version = "1.70.1" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 269 | 270 | [[package]] 271 | name = "js-sys" 272 | version = "0.3.77" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" 275 | dependencies = [ 276 | "once_cell", 277 | "wasm-bindgen", 278 | ] 279 | 280 | [[package]] 281 | name = "libc" 282 | version = "0.2.172" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" 285 | 286 | [[package]] 287 | name = "libloading" 288 | version = "0.8.6" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" 291 | dependencies = [ 292 | "cfg-if", 293 | "windows-targets 0.52.6", 294 | ] 295 | 296 | [[package]] 297 | name = "lock_api" 298 | version = "0.4.12" 299 | source = "registry+https://github.com/rust-lang/crates.io-index" 300 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 301 | dependencies = [ 302 | "autocfg", 303 | "scopeguard", 304 | ] 305 | 306 | [[package]] 307 | name = "log" 308 | version = "0.4.27" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" 311 | 312 | [[package]] 313 | name = "memchr" 314 | version = "2.7.4" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 317 | 318 | [[package]] 319 | name = "mio" 320 | version = "0.8.11" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" 323 | dependencies = [ 324 | "libc", 325 | "log", 326 | "wasi", 327 | "windows-sys 0.48.0", 328 | ] 329 | 330 | [[package]] 331 | name = "napi" 332 | version = "2.16.17" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "55740c4ae1d8696773c78fdafd5d0e5fe9bc9f1b071c7ba493ba5c413a9184f3" 335 | dependencies = [ 336 | "bitflags 2.9.0", 337 | "ctor", 338 | "napi-derive", 339 | "napi-sys", 340 | "once_cell", 341 | ] 342 | 343 | [[package]] 344 | name = "napi-build" 345 | version = "2.1.6" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "e28acfa557c083f6e254a786e01ba253fc56f18ee000afcd4f79af735f73a6da" 348 | 349 | [[package]] 350 | name = "napi-derive" 351 | version = "2.16.13" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "7cbe2585d8ac223f7d34f13701434b9d5f4eb9c332cccce8dee57ea18ab8ab0c" 354 | dependencies = [ 355 | "cfg-if", 356 | "convert_case", 357 | "napi-derive-backend", 358 | "proc-macro2", 359 | "quote", 360 | "syn", 361 | ] 362 | 363 | [[package]] 364 | name = "napi-derive-backend" 365 | version = "1.0.75" 366 | source = "registry+https://github.com/rust-lang/crates.io-index" 367 | checksum = "1639aaa9eeb76e91c6ae66da8ce3e89e921cd3885e99ec85f4abacae72fc91bf" 368 | dependencies = [ 369 | "convert_case", 370 | "once_cell", 371 | "proc-macro2", 372 | "quote", 373 | "regex", 374 | "semver", 375 | "syn", 376 | ] 377 | 378 | [[package]] 379 | name = "napi-sys" 380 | version = "2.4.0" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "427802e8ec3a734331fec1035594a210ce1ff4dc5bc1950530920ab717964ea3" 383 | dependencies = [ 384 | "libloading", 385 | ] 386 | 387 | [[package]] 388 | name = "newline-converter" 389 | version = "0.3.0" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f" 392 | dependencies = [ 393 | "unicode-segmentation", 394 | ] 395 | 396 | [[package]] 397 | name = "number_prefix" 398 | version = "0.4.0" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" 401 | 402 | [[package]] 403 | name = "once_cell" 404 | version = "1.21.3" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" 407 | 408 | [[package]] 409 | name = "parking_lot" 410 | version = "0.12.3" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 413 | dependencies = [ 414 | "lock_api", 415 | "parking_lot_core", 416 | ] 417 | 418 | [[package]] 419 | name = "parking_lot_core" 420 | version = "0.9.10" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 423 | dependencies = [ 424 | "cfg-if", 425 | "libc", 426 | "redox_syscall", 427 | "smallvec", 428 | "windows-targets 0.52.6", 429 | ] 430 | 431 | [[package]] 432 | name = "portable-atomic" 433 | version = "1.11.0" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" 436 | 437 | [[package]] 438 | name = "proc-macro2" 439 | version = "1.0.95" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" 442 | dependencies = [ 443 | "unicode-ident", 444 | ] 445 | 446 | [[package]] 447 | name = "quote" 448 | version = "1.0.40" 449 | source = "registry+https://github.com/rust-lang/crates.io-index" 450 | checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 451 | dependencies = [ 452 | "proc-macro2", 453 | ] 454 | 455 | [[package]] 456 | name = "redox_syscall" 457 | version = "0.5.11" 458 | source = "registry+https://github.com/rust-lang/crates.io-index" 459 | checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" 460 | dependencies = [ 461 | "bitflags 2.9.0", 462 | ] 463 | 464 | [[package]] 465 | name = "regex" 466 | version = "1.11.1" 467 | source = "registry+https://github.com/rust-lang/crates.io-index" 468 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 469 | dependencies = [ 470 | "aho-corasick", 471 | "memchr", 472 | "regex-automata", 473 | "regex-syntax", 474 | ] 475 | 476 | [[package]] 477 | name = "regex-automata" 478 | version = "0.4.9" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 481 | dependencies = [ 482 | "aho-corasick", 483 | "memchr", 484 | "regex-syntax", 485 | ] 486 | 487 | [[package]] 488 | name = "regex-syntax" 489 | version = "0.8.5" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 492 | 493 | [[package]] 494 | name = "rustc-hash" 495 | version = "2.1.1" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" 498 | 499 | [[package]] 500 | name = "scopeguard" 501 | version = "1.2.0" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 504 | 505 | [[package]] 506 | name = "semver" 507 | version = "1.0.26" 508 | source = "registry+https://github.com/rust-lang/crates.io-index" 509 | checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" 510 | 511 | [[package]] 512 | name = "signal-hook" 513 | version = "0.3.17" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" 516 | dependencies = [ 517 | "libc", 518 | "signal-hook-registry", 519 | ] 520 | 521 | [[package]] 522 | name = "signal-hook-mio" 523 | version = "0.2.4" 524 | source = "registry+https://github.com/rust-lang/crates.io-index" 525 | checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" 526 | dependencies = [ 527 | "libc", 528 | "mio", 529 | "signal-hook", 530 | ] 531 | 532 | [[package]] 533 | name = "signal-hook-registry" 534 | version = "1.4.5" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" 537 | dependencies = [ 538 | "libc", 539 | ] 540 | 541 | [[package]] 542 | name = "smallvec" 543 | version = "1.15.0" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" 546 | 547 | [[package]] 548 | name = "strsim" 549 | version = "0.11.1" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 552 | 553 | [[package]] 554 | name = "syn" 555 | version = "2.0.100" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" 558 | dependencies = [ 559 | "proc-macro2", 560 | "quote", 561 | "unicode-ident", 562 | ] 563 | 564 | [[package]] 565 | name = "thiserror" 566 | version = "2.0.12" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" 569 | dependencies = [ 570 | "thiserror-impl", 571 | ] 572 | 573 | [[package]] 574 | name = "thiserror-impl" 575 | version = "2.0.12" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" 578 | dependencies = [ 579 | "proc-macro2", 580 | "quote", 581 | "syn", 582 | ] 583 | 584 | [[package]] 585 | name = "thread_local" 586 | version = "1.1.8" 587 | source = "registry+https://github.com/rust-lang/crates.io-index" 588 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 589 | dependencies = [ 590 | "cfg-if", 591 | "once_cell", 592 | ] 593 | 594 | [[package]] 595 | name = "unicode-ident" 596 | version = "1.0.18" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" 599 | 600 | [[package]] 601 | name = "unicode-segmentation" 602 | version = "1.12.0" 603 | source = "registry+https://github.com/rust-lang/crates.io-index" 604 | checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" 605 | 606 | [[package]] 607 | name = "unicode-width" 608 | version = "0.1.14" 609 | source = "registry+https://github.com/rust-lang/crates.io-index" 610 | checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" 611 | 612 | [[package]] 613 | name = "unicode-width" 614 | version = "0.2.0" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" 617 | 618 | [[package]] 619 | name = "utf8parse" 620 | version = "0.2.2" 621 | source = "registry+https://github.com/rust-lang/crates.io-index" 622 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 623 | 624 | [[package]] 625 | name = "wasi" 626 | version = "0.11.0+wasi-snapshot-preview1" 627 | source = "registry+https://github.com/rust-lang/crates.io-index" 628 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 629 | 630 | [[package]] 631 | name = "wasm-bindgen" 632 | version = "0.2.100" 633 | source = "registry+https://github.com/rust-lang/crates.io-index" 634 | checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" 635 | dependencies = [ 636 | "cfg-if", 637 | "once_cell", 638 | "wasm-bindgen-macro", 639 | ] 640 | 641 | [[package]] 642 | name = "wasm-bindgen-backend" 643 | version = "0.2.100" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" 646 | dependencies = [ 647 | "bumpalo", 648 | "log", 649 | "proc-macro2", 650 | "quote", 651 | "syn", 652 | "wasm-bindgen-shared", 653 | ] 654 | 655 | [[package]] 656 | name = "wasm-bindgen-macro" 657 | version = "0.2.100" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" 660 | dependencies = [ 661 | "quote", 662 | "wasm-bindgen-macro-support", 663 | ] 664 | 665 | [[package]] 666 | name = "wasm-bindgen-macro-support" 667 | version = "0.2.100" 668 | source = "registry+https://github.com/rust-lang/crates.io-index" 669 | checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" 670 | dependencies = [ 671 | "proc-macro2", 672 | "quote", 673 | "syn", 674 | "wasm-bindgen-backend", 675 | "wasm-bindgen-shared", 676 | ] 677 | 678 | [[package]] 679 | name = "wasm-bindgen-shared" 680 | version = "0.2.100" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" 683 | dependencies = [ 684 | "unicode-ident", 685 | ] 686 | 687 | [[package]] 688 | name = "web-time" 689 | version = "1.1.0" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" 692 | dependencies = [ 693 | "js-sys", 694 | "wasm-bindgen", 695 | ] 696 | 697 | [[package]] 698 | name = "winapi" 699 | version = "0.3.9" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 702 | dependencies = [ 703 | "winapi-i686-pc-windows-gnu", 704 | "winapi-x86_64-pc-windows-gnu", 705 | ] 706 | 707 | [[package]] 708 | name = "winapi-i686-pc-windows-gnu" 709 | version = "0.4.0" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 712 | 713 | [[package]] 714 | name = "winapi-x86_64-pc-windows-gnu" 715 | version = "0.4.0" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 718 | 719 | [[package]] 720 | name = "windows-sys" 721 | version = "0.48.0" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 724 | dependencies = [ 725 | "windows-targets 0.48.5", 726 | ] 727 | 728 | [[package]] 729 | name = "windows-sys" 730 | version = "0.59.0" 731 | source = "registry+https://github.com/rust-lang/crates.io-index" 732 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 733 | dependencies = [ 734 | "windows-targets 0.52.6", 735 | ] 736 | 737 | [[package]] 738 | name = "windows-targets" 739 | version = "0.48.5" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 742 | dependencies = [ 743 | "windows_aarch64_gnullvm 0.48.5", 744 | "windows_aarch64_msvc 0.48.5", 745 | "windows_i686_gnu 0.48.5", 746 | "windows_i686_msvc 0.48.5", 747 | "windows_x86_64_gnu 0.48.5", 748 | "windows_x86_64_gnullvm 0.48.5", 749 | "windows_x86_64_msvc 0.48.5", 750 | ] 751 | 752 | [[package]] 753 | name = "windows-targets" 754 | version = "0.52.6" 755 | source = "registry+https://github.com/rust-lang/crates.io-index" 756 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 757 | dependencies = [ 758 | "windows_aarch64_gnullvm 0.52.6", 759 | "windows_aarch64_msvc 0.52.6", 760 | "windows_i686_gnu 0.52.6", 761 | "windows_i686_gnullvm", 762 | "windows_i686_msvc 0.52.6", 763 | "windows_x86_64_gnu 0.52.6", 764 | "windows_x86_64_gnullvm 0.52.6", 765 | "windows_x86_64_msvc 0.52.6", 766 | ] 767 | 768 | [[package]] 769 | name = "windows_aarch64_gnullvm" 770 | version = "0.48.5" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 773 | 774 | [[package]] 775 | name = "windows_aarch64_gnullvm" 776 | version = "0.52.6" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 779 | 780 | [[package]] 781 | name = "windows_aarch64_msvc" 782 | version = "0.48.5" 783 | source = "registry+https://github.com/rust-lang/crates.io-index" 784 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 785 | 786 | [[package]] 787 | name = "windows_aarch64_msvc" 788 | version = "0.52.6" 789 | source = "registry+https://github.com/rust-lang/crates.io-index" 790 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 791 | 792 | [[package]] 793 | name = "windows_i686_gnu" 794 | version = "0.48.5" 795 | source = "registry+https://github.com/rust-lang/crates.io-index" 796 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 797 | 798 | [[package]] 799 | name = "windows_i686_gnu" 800 | version = "0.52.6" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 803 | 804 | [[package]] 805 | name = "windows_i686_gnullvm" 806 | version = "0.52.6" 807 | source = "registry+https://github.com/rust-lang/crates.io-index" 808 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 809 | 810 | [[package]] 811 | name = "windows_i686_msvc" 812 | version = "0.48.5" 813 | source = "registry+https://github.com/rust-lang/crates.io-index" 814 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 815 | 816 | [[package]] 817 | name = "windows_i686_msvc" 818 | version = "0.52.6" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 821 | 822 | [[package]] 823 | name = "windows_x86_64_gnu" 824 | version = "0.48.5" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 827 | 828 | [[package]] 829 | name = "windows_x86_64_gnu" 830 | version = "0.52.6" 831 | source = "registry+https://github.com/rust-lang/crates.io-index" 832 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 833 | 834 | [[package]] 835 | name = "windows_x86_64_gnullvm" 836 | version = "0.48.5" 837 | source = "registry+https://github.com/rust-lang/crates.io-index" 838 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 839 | 840 | [[package]] 841 | name = "windows_x86_64_gnullvm" 842 | version = "0.52.6" 843 | source = "registry+https://github.com/rust-lang/crates.io-index" 844 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 845 | 846 | [[package]] 847 | name = "windows_x86_64_msvc" 848 | version = "0.48.5" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 851 | 852 | [[package]] 853 | name = "windows_x86_64_msvc" 854 | version = "0.52.6" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 857 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["苏向夜 "] 3 | edition = "2021" 4 | name = "archons" 5 | version = "0.1.0" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [lib] 10 | crate-type = ["cdylib"] 11 | 12 | [dependencies] 13 | clap = "4.5.24" 14 | indicatif = "0.17.9" 15 | inquire = "0.7.5" 16 | napi = "2" 17 | napi-derive = "2" 18 | rustc-hash = "2.1.0" 19 | thiserror = "2.0.10" 20 | 21 | [build-dependencies] 22 | napi-build = "2" 23 | 24 | [profile.release] 25 | codegen-units = 1 26 | debug-assertions = false 27 | lto = true 28 | opt-level = 3 29 | panic = "abort" 30 | strip = true 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Noctisynth, org. 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 | # archons 2 | 3 | Fast, powerful and elegant CLI build tool based on Rust 4 | 5 | ## Installation 6 | 7 | 1. Using npm 8 | 9 | ```bash 10 | npm install --save-dev archons 11 | ``` 12 | 13 | 2. Using pnpm 14 | 15 | ```bash 16 | pnpm add -D archons 17 | ``` 18 | 19 | 3. Using Deno.js 20 | 21 | ```bash 22 | deno add -D archons 23 | ``` 24 | 25 | 4. Using yarn 26 | 27 | ```bash 28 | yarn add -D archons 29 | ``` 30 | 31 | 5. Using bun 32 | 33 | ```bash 34 | bun add -d archons 35 | ``` 36 | 37 | ## Usage 38 | 39 | See [Examples](./examples) for examples. 40 | 41 | See [clap-rs](https://github.com/clap-rs/clap) for more information. 42 | 43 | ## Inspiration 44 | 45 | - [citty](https://github.com/unjs/citty): An elegant CLI Builder created with nodejs, which inspired the API style of this project. 46 | 47 | - [clap-rs](https://github.com/clap-rs/clap-rs): A full featured, fast Command Line Argument Parser for Rust, which is the core command-line parser of this project. 48 | 49 | - [indicatif](https://github.com/console-rs/indicatif): A command line progress reporting library for Rust, which is used to display progress bars in this project. 50 | 51 | - [inquire](https://github.com/mikaelmello/inquire): A Rust library for building interactive prompts, which is used to display prompts in this project. 52 | 53 | ## License 54 | 55 | Released under [MIT](./LICENSE) license. 56 | -------------------------------------------------------------------------------- /__test__/exclusive.spec.ts: -------------------------------------------------------------------------------- 1 | import { spawnSync } from 'node:child_process' 2 | 3 | test('exclusive', () => { 4 | const exclusive = spawnSync('node', ['examples/exclusive.cjs', '--config', 'config.json']) 5 | const should_fail = spawnSync('node', ['examples/exclusive.cjs', '--config', 'config.json', '--other', 'other']) 6 | expect(exclusive.error).toBe(undefined) 7 | expect(exclusive.stderr.length).toBe(0) 8 | expect(exclusive.status ?? 0).toEqual(0) 9 | expect(should_fail.stderr.length).not.toBe(0) 10 | }) 11 | -------------------------------------------------------------------------------- /__test__/global.spec.ts: -------------------------------------------------------------------------------- 1 | import { type Context, defineCommand, run } from 'archons' 2 | 3 | test('global option', () => { 4 | const dev = defineCommand({ 5 | meta: { 6 | name: 'dev', 7 | }, 8 | options: {}, 9 | callback: (ctx: Context) => { 10 | expect(ctx.args.config).toBe('config.json') 11 | }, 12 | }) 13 | const main = defineCommand({ 14 | meta: { 15 | name: 'test', 16 | }, 17 | options: { 18 | config: { 19 | type: 'option', 20 | global: true, 21 | }, 22 | }, 23 | subcommands: { 24 | dev, 25 | }, 26 | }) 27 | expect(() => { 28 | run(main, ['node', 'test.js', 'dev', '--config', 'config.json']) 29 | }).not.toThrow() 30 | }) 31 | -------------------------------------------------------------------------------- /__test__/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { spawnSync } from 'node:child_process' 2 | 3 | import { type Context, type Command, defineCommand, run } from 'archons' 4 | 5 | const cmd: Command = { 6 | meta: { 7 | name: 'test', 8 | version: '1.0.0', 9 | about: 'test command', 10 | }, 11 | options: { 12 | foo: { 13 | type: 'positional', 14 | action: 'set', 15 | }, 16 | }, 17 | callback: (_: Context) => {}, 18 | } 19 | 20 | const main = defineCommand(cmd) 21 | 22 | test('define command', () => { 23 | expect(defineCommand(cmd)).toEqual(cmd) 24 | }) 25 | 26 | test('run command', () => { 27 | expect(() => { 28 | run(main, ['node', 'test.js']) 29 | }).not.toThrow() 30 | }) 31 | 32 | test('run help', () => { 33 | const result = spawnSync('node', ['examples/simple.cjs', '--help']) 34 | expect(result.error).toBe(undefined) 35 | expect(result.stderr.length).toBe(0) 36 | expect(result.status ?? 0).toEqual(0) 37 | }) 38 | 39 | test('run version', () => { 40 | const version = spawnSync('node', ['examples/simple.cjs', '--version']) 41 | const no_version = spawnSync('node', ['examples/no_version.cjs', '--version']) 42 | expect(version.error).toBe(undefined) 43 | expect(version.stderr.length).toBe(0) 44 | expect(version.status ?? 0).toEqual(0) 45 | expect(no_version.stderr.length).not.toBe(0) 46 | }) 47 | -------------------------------------------------------------------------------- /__test__/num_args.spec.ts: -------------------------------------------------------------------------------- 1 | import { spawnSync } from 'node:child_process' 2 | 3 | test('num_args', () => { 4 | const result = spawnSync('node', ['examples/num_args.cjs', '--foo', '1', '2', '3']) 5 | const more_than = spawnSync('node', ['examples/num_args.cjs', '--foo', '1', '2', '3', '4']) 6 | const less_than = spawnSync('node', ['examples/num_args.cjs', '--foo', '1']) 7 | expect(result.error).toBe(undefined) 8 | expect(result.stderr.length).toBe(0) 9 | expect(result.status ?? 0).toEqual(0) 10 | expect(more_than.stderr.length).not.toBe(0) 11 | expect(less_than.stderr.length).not.toBe(0) 12 | }) 13 | -------------------------------------------------------------------------------- /__test__/options.spec.ts: -------------------------------------------------------------------------------- 1 | import { spawnSync } from 'node:child_process' 2 | 3 | import { type Context, defineCommand, run } from 'archons' 4 | 5 | test('positional option', () => { 6 | const main = defineCommand({ 7 | meta: { 8 | name: 'test', 9 | }, 10 | options: { 11 | foo: { 12 | type: 'positional', 13 | }, 14 | }, 15 | callback: (ctx: Context) => { 16 | expect(ctx.args.foo).toBe('foo') 17 | }, 18 | }) 19 | expect(() => { 20 | run(main, ['node', 'test.js', 'foo']) 21 | }).not.toThrow() 22 | }) 23 | 24 | test('required positional option', () => { 25 | const result = spawnSync('node', ['examples/positional_required.cjs', 'foo']) 26 | const should_fail = spawnSync('node', ['examples/positional_required.cjs']) 27 | expect(result.error).toBe(undefined) 28 | expect(result.stderr.length).toBe(0) 29 | expect(result.status ?? 0).toEqual(0) 30 | expect(should_fail.stderr.length).not.toBe(0) 31 | }) 32 | 33 | test('boolean flag', () => { 34 | const main = defineCommand({ 35 | meta: { 36 | name: 'test', 37 | }, 38 | options: { 39 | verbose: { 40 | type: 'option', 41 | action: 'store', 42 | }, 43 | eq: { 44 | type: 'option', 45 | action: 'store', 46 | alias: ['e'], 47 | }, 48 | }, 49 | callback: (ctx: Context) => { 50 | expect(ctx.args.verbose).toBe(ctx.args.eq) 51 | }, 52 | }) 53 | expect(() => { 54 | run(main, ['node', 'test.js', '--verbose', '-e']) 55 | }).not.toThrow() 56 | expect(() => { 57 | run(main, ['node', 'test.js']) 58 | }).not.toThrow() 59 | }) 60 | -------------------------------------------------------------------------------- /__test__/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } 4 | -------------------------------------------------------------------------------- /__test__/subcommand.spec.ts: -------------------------------------------------------------------------------- 1 | import { type Context, defineCommand, run } from 'archons' 2 | 3 | test('sub command', () => { 4 | const cmd = defineCommand({ 5 | meta: {}, 6 | options: { 7 | foo: { 8 | type: 'positional', 9 | }, 10 | }, 11 | callback: (ctx: Context) => { 12 | expect(ctx.args).toEqual({ foo: 'foo' }) 13 | }, 14 | }) 15 | const main = defineCommand({ 16 | meta: { 17 | name: 'test', 18 | }, 19 | options: {}, 20 | subcommands: { 21 | cmd, 22 | }, 23 | }) 24 | expect(() => { 25 | run(main, ['node.exe', 'test.js', 'cmd', 'foo']) 26 | }).not.toThrow() 27 | }) 28 | -------------------------------------------------------------------------------- /__test__/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "module": "ESNext", 5 | "moduleResolution": "Bundler", 6 | "outDir": "lib", 7 | "rootDir": "." 8 | }, 9 | "include": ["*.ts"], 10 | "exclude": ["lib"] 11 | } 12 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [["@babel/preset-env", { "targets": { "node": "current" } }], "@babel/preset-typescript"], 3 | "plugins": [ 4 | [ 5 | "@babel/plugin-transform-typescript", 6 | { 7 | "allowDeclareFields": true, 8 | "onlyRemoveTypeImports": true 9 | } 10 | ] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /benchmark/bench.ts: -------------------------------------------------------------------------------- 1 | import { Bench } from 'tinybench' 2 | 3 | const b = new Bench() 4 | 5 | b.add('Native a + 100', () => { 6 | 7 | }) 8 | 9 | b.add('JavaScript a + 100', () => { 10 | 11 | }) 12 | 13 | await b.run() 14 | 15 | console.table(b.table()) 16 | -------------------------------------------------------------------------------- /benchmark/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module" 3 | } 4 | -------------------------------------------------------------------------------- /benchmark/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "moduleResolution": "NodeNext", 5 | "module": "NodeNext", 6 | "outDir": "lib" 7 | }, 8 | "include": ["."], 9 | "exclude": ["lib"] 10 | } 11 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | extern crate napi_build; 2 | 3 | fn main() { 4 | napi_build::setup(); 5 | } 6 | -------------------------------------------------------------------------------- /cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json", 3 | "words": [ 4 | "aarch", 5 | "androideabi", 6 | "bindgen", 7 | "cdylib", 8 | "codegen", 9 | "gnueabihf", 10 | "indicatif", 11 | "msvc", 12 | "napi", 13 | "noctisynth", 14 | "oxlint", 15 | "parens", 16 | "prebuild", 17 | "rustc", 18 | "rustflags", 19 | "taplo", 20 | "thiserror", 21 | "tinybench" 22 | ], 23 | "ignoreWords": ["armv"], 24 | "ignorePaths": [ 25 | "**/node_modules/**", 26 | "**/target/**", 27 | "./npm/*", 28 | "index.js", 29 | "README.md", 30 | "pnpm-lock.yaml", 31 | "Cargo.toml" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | *.cjs 2 | -------------------------------------------------------------------------------- /examples/exclusive.cts: -------------------------------------------------------------------------------- 1 | import { type Context, defineCommand, run } from 'archons' 2 | 3 | const main = defineCommand({ 4 | meta: { 5 | name: 'test', 6 | }, 7 | options: { 8 | config: { 9 | type: 'option', 10 | exclusive: true, 11 | }, 12 | other: { 13 | type: 'option', 14 | }, 15 | }, 16 | callback: async (ctx: Context) => { 17 | console.log(ctx) 18 | }, 19 | }) 20 | 21 | run(main) 22 | -------------------------------------------------------------------------------- /examples/global.cts: -------------------------------------------------------------------------------- 1 | import { type Context, defineCommand, run } from 'archons' 2 | 3 | const dev = defineCommand({ 4 | meta: { 5 | name: 'dev', 6 | }, 7 | options: {}, 8 | callback: (ctx: Context) => { 9 | console.log(ctx.args.config) 10 | }, 11 | }) 12 | 13 | const main = defineCommand({ 14 | meta: { 15 | name: 'test', 16 | }, 17 | options: { 18 | config: { 19 | type: 'option', 20 | global: true, 21 | }, 22 | }, 23 | subcommands: { 24 | dev, 25 | }, 26 | callback: (ctx: Context) => { 27 | console.log(ctx.get('config')) 28 | }, 29 | }) 30 | 31 | run(main) 32 | -------------------------------------------------------------------------------- /examples/inquire.cts: -------------------------------------------------------------------------------- 1 | import { 2 | checkbox, 3 | defineCommand, 4 | password, 5 | run, 6 | select, 7 | type Context, 8 | } from "archons"; 9 | 10 | const main = defineCommand({ 11 | meta: { 12 | name: "inquire", 13 | styled: true, 14 | }, 15 | options: {}, 16 | callback: (ctx: Context) => { 17 | const color = select("What is your favorite color?", [ 18 | "Red", 19 | "Green", 20 | "Blue", 21 | ]); 22 | console.log(`Your favorite color is ${color}`); 23 | const foods = checkbox("What are your favorite foods?", [ 24 | "Pizza", 25 | "Burger", 26 | "Ice Cream", 27 | ]); 28 | console.log(`Your favorite foods are ${foods.join(", ")}`); 29 | const name = ctx.ask("What is your name?"); 30 | console.log(`Your name is ${name}`); 31 | const confirm = ctx.confirm("Are you sure? [y/n]"); 32 | console.log(`You answered ${confirm}`); 33 | const pwd = password("Enter your password"); 34 | console.log(`Your password is ${pwd}`); 35 | }, 36 | }); 37 | 38 | run(main); 39 | -------------------------------------------------------------------------------- /examples/no_version.cts: -------------------------------------------------------------------------------- 1 | import { defineCommand, run, type Context } from 'archons' 2 | 3 | const main = defineCommand({ 4 | meta: { 5 | name: 'simple', 6 | }, 7 | options: {}, 8 | callback: (_: Context) => {}, 9 | }) 10 | 11 | run(main) 12 | -------------------------------------------------------------------------------- /examples/num_args.cts: -------------------------------------------------------------------------------- 1 | import { type Context, defineCommand, run } from "archons"; 2 | 3 | const main = defineCommand({ 4 | meta: { 5 | name: "test", 6 | }, 7 | options: { 8 | foo: { 9 | type: "option", 10 | numArgs: "2..=3", 11 | required: true, 12 | }, 13 | }, 14 | callback: (_: Context) => {}, 15 | }); 16 | 17 | run(main); 18 | -------------------------------------------------------------------------------- /examples/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "commonjs", 3 | "dependencies": { 4 | "archons": "link:.." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /examples/positional_required.cts: -------------------------------------------------------------------------------- 1 | import { type Context, defineCommand, run } from 'archons' 2 | 3 | const main = defineCommand({ 4 | meta: { 5 | name: 'test', 6 | }, 7 | options: { 8 | foo: { 9 | type: 'positional', 10 | required: true, 11 | }, 12 | }, 13 | callback: (_: Context) => {}, 14 | }) 15 | 16 | run(main) 17 | -------------------------------------------------------------------------------- /examples/progressbar.cts: -------------------------------------------------------------------------------- 1 | import { type Context, defineCommand, run } from "archons"; 2 | 3 | const spinner = defineCommand({ 4 | meta: { 5 | name: "spinner", 6 | }, 7 | options: { 8 | "enable-steady-tick": { 9 | type: "option", 10 | action: "store", 11 | }, 12 | }, 13 | callback: async (ctx: Context) => { 14 | const spinner = ctx.createSpinner(); 15 | spinner.setMessage("loading"); 16 | spinner.tick(); 17 | let i = 100; 18 | const interval = ctx.args.interval as number; 19 | if (ctx.args["enable-steady-tick"]) { 20 | spinner.println("Enabled steady tick"); 21 | spinner.enableSteadyTick(interval); 22 | while (i--) { 23 | if (i < 30) { 24 | spinner.setMessage("Disabled steady tick for now"); 25 | spinner.disableSteadyTick(); 26 | } 27 | await new Promise((resolve) => setTimeout(resolve, interval)); 28 | } 29 | } else { 30 | spinner.println("Disabled steady tick"); 31 | while (i--) { 32 | spinner.tick(); 33 | await new Promise((resolve) => setTimeout(resolve, interval)); 34 | } 35 | } 36 | spinner.finishWithMessage("✨ finished"); 37 | }, 38 | }); 39 | 40 | const bar = defineCommand({ 41 | meta: { 42 | name: "bar", 43 | }, 44 | options: { 45 | clear: { 46 | type: "option", 47 | action: "store", 48 | help: "Clear the progress bar", 49 | parser: "boolean", 50 | }, 51 | }, 52 | callback: async (ctx: Context) => { 53 | const bar = ctx.createProgressBar(ctx.args.total as number); 54 | bar.setTemplate( 55 | "{spinner:.green} [{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos:>5}/{len:5} {msg}", 56 | ); 57 | bar.setProgressChars("=>-"); 58 | let i = 100; 59 | const interval = ctx.args.interval as number; 60 | while (i--) { 61 | bar.inc(1); 62 | await new Promise((resolve) => setTimeout(resolve, interval)); 63 | } 64 | if (ctx.args.clear) { 65 | bar.finishAndClear(); 66 | } else { 67 | bar.finish(); 68 | } 69 | console.log("✨ finished"); 70 | }, 71 | }); 72 | 73 | const main = defineCommand({ 74 | meta: { 75 | name: "progressbar", 76 | styled: true, 77 | subcommandRequired: true, 78 | }, 79 | options: { 80 | interval: { 81 | type: "option", 82 | numArgs: "1", 83 | default: "100", 84 | global: true, 85 | help: "Interval of spinner", 86 | parser: "number", 87 | }, 88 | total: { 89 | type: "option", 90 | numArgs: "1", 91 | default: "100", 92 | global: true, 93 | help: "Total of progress bar", 94 | parser: "number", 95 | }, 96 | }, 97 | subcommands: { 98 | spinner, 99 | bar, 100 | }, 101 | }); 102 | 103 | run(main); 104 | -------------------------------------------------------------------------------- /examples/simple.cts: -------------------------------------------------------------------------------- 1 | import { defineCommand, run, type Context } from 'archons'; 2 | 3 | const dev = defineCommand({ 4 | meta: { 5 | name: 'dev', 6 | about: 'Run development server', 7 | }, 8 | options: { 9 | port: { 10 | type: 'option', 11 | parser: 'number', 12 | default: '3000', 13 | }, 14 | }, 15 | callback: (ctx: Context) => { 16 | console.log(ctx); 17 | } 18 | }) 19 | 20 | const main = defineCommand({ 21 | meta: { 22 | name: 'simple', 23 | version: '0.0.1', 24 | about: 'A simple command line tool', 25 | styled: true, 26 | }, 27 | options: { 28 | name: { 29 | type: 'positional', 30 | required: true, 31 | help: 'Name of the person to greet', 32 | }, 33 | verbose: { 34 | type: 'option', 35 | parser: 'boolean', 36 | action: 'store', 37 | help: 'Enable verbose output', 38 | global: true 39 | }, 40 | }, 41 | subcommands: { 42 | dev, 43 | }, 44 | callback: (ctx: Context) => { 45 | console.log(ctx); 46 | } 47 | }) 48 | 49 | run(main) 50 | -------------------------------------------------------------------------------- /examples/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "moduleResolution": "NodeNext", 5 | "module": "NodeNext", 6 | "outDir": "." 7 | }, 8 | "include": ["."], 9 | "exclude": ["*.cjs"] 10 | } 11 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | 4 | /* auto-generated by NAPI-RS */ 5 | 6 | /** 7 | * Define a command functionally 8 | * 9 | * @param options Command options 10 | * @returns {Command} 11 | */ 12 | export declare function defineCommand(options: Command): Command 13 | /** 14 | * Run command 15 | * 16 | * **NOTE**: If the given `args` is empty, it will use `process.argv` 17 | * (or `Deno.args` in Deno.js environment) instead. 18 | * 19 | * **NOTE**: The given `args` should include the nodejs executable and script name. 20 | * For example, if you are running a script `index.js` in the current directory with 21 | * a flag `--foo`, you should pass `["node", "index.js", "--foo"]` as `args`. 22 | * 23 | * @param cmd Command object 24 | * @param args Run with given arguments 25 | * @returns {void} 26 | */ 27 | export declare function run(cmd: Command, args?: string[]): void 28 | export interface SelectConfig { 29 | helpMessage?: string 30 | pageSize?: number 31 | resetCursor?: boolean 32 | startingCursor?: number 33 | startingFilterInput?: string 34 | vimMode?: boolean 35 | filtering?: boolean 36 | helpMessageDisabled?: boolean 37 | } 38 | export declare function select(prompt: string, choices: Array, config?: SelectConfig | undefined | null): string 39 | export interface CheckboxConfig { 40 | allSelectedByDefault?: boolean 41 | default?: Array 42 | helpMessage?: string 43 | keepFilter?: boolean 44 | pageSize?: number 45 | resetCursor?: boolean 46 | startingCursor?: number 47 | startingFilterInput?: string 48 | vimMode?: boolean 49 | filtering?: boolean 50 | helpMessageDisabled?: boolean 51 | } 52 | export declare function checkbox( 53 | prompt: string, 54 | choices: Array, 55 | config?: CheckboxConfig | undefined | null, 56 | ): Array 57 | export interface InputConfig { 58 | default?: string 59 | formatter?: (value: string) => string 60 | helpMessage?: string 61 | initialValue?: string 62 | pageSize?: number 63 | placeholder?: string 64 | validators?: ((text: string) => StringValidatorResult)[] 65 | } 66 | export declare function input(prompt: string, config?: InputConfig | undefined | null): string 67 | export interface ConfirmConfig { 68 | default?: boolean 69 | defaultValueFormatter?: (value: boolean) => string 70 | errorMessage?: string 71 | formatter?: (value: boolean) => string 72 | helpMessage?: string 73 | parser?: (value: boolean) => boolean 74 | placeholder?: string 75 | startingInput?: string 76 | } 77 | export declare function confirm(prompt: string, config?: ConfirmConfig | undefined | null): boolean 78 | export interface PasswordConfig { 79 | customConfirmationErrorMessage?: string 80 | customConfirmationMessage?: string 81 | displayMode?: 'hidden' | 'masked' | 'full' 82 | displayToggle?: boolean 83 | helpMessage?: string 84 | formatter?: (text: string) => string 85 | validators?: ((text: string) => StringValidatorResult)[] 86 | confirmation?: boolean 87 | } 88 | export interface StringValidatorResult { 89 | validation: 'valid' | 'invalid' 90 | errMsg?: string 91 | } 92 | export declare function password(prompt: string, config?: PasswordConfig | undefined | null): string 93 | /** 94 | * Creates a new progress bar with the specified total number of steps. 95 | * 96 | * # Arguments 97 | * 98 | * * `total` - The total number of steps for the progress bar. 99 | * 100 | * # Returns 101 | * 102 | * A new `ProgressBar` instance. 103 | */ 104 | export declare function createProgressBar(total: number): ProgressBar 105 | /** 106 | * Creates a new spinner progress bar. 107 | * 108 | * # Returns 109 | * 110 | * A new `ProgressBar` instance with a spinner style. 111 | */ 112 | export declare function createSpinner(): ProgressBar 113 | /** Command metadata */ 114 | export interface CommandMeta { 115 | /** 116 | * Command name 117 | * 118 | * This is the name of the command that will be used to call it from the CLI. 119 | * If the command is the main command, the name will be the name of the binary. 120 | * If the command is a subcommand, the name will be the name of the subcommand. 121 | */ 122 | name?: string 123 | /** 124 | * CLI version 125 | * 126 | * This is optional and can be used to display the version of the CLI 127 | * when the command is called with the `--version` flag or `-V` option. 128 | * 129 | * If not provided, the CLI will not display the version and you can't 130 | * call the command with the `--version` flag or `-V` option. 131 | */ 132 | version?: string 133 | /** 134 | * Command description 135 | * 136 | * Command description will be displayed in the help output. 137 | */ 138 | about?: string 139 | /** 140 | * Enable styled mode 141 | * 142 | * Determines whether the CLI output should be displayed in the styled format. 143 | */ 144 | styled?: boolean 145 | /** 146 | * Subcommand required 147 | * 148 | * If true, the command will fail if no subcommand is provided. 149 | */ 150 | subcommandRequired?: boolean 151 | } 152 | export interface CommandOption { 153 | /** 154 | * Option type for argument 155 | * 156 | * `type` option and `action` option are used to specify how to parse the argument. 157 | * 158 | * - `option` and (`store` or `store_false`): Boolean flag 159 | * - `option` and `count`: Counter flag 160 | * - `option` and `set`: Option flag 161 | * - `option` and `append`: Multiple option flag 162 | * - `positional` and `set`: Positional argument 163 | * - `positional` and `append`: Multiple positional argument 164 | * 165 | * Defaults to `option` if not specified. 166 | */ 167 | type?: 'positional' | 'option' 168 | /** Specify the value type for the argument. */ 169 | parser?: 'string' | 'number' | 'boolean' 170 | /** 171 | * Specify how to react to an argument when parsing it. 172 | * 173 | * - `set`: Overwrite previous values with new ones 174 | * - `append`: Append new values to all previous ones 175 | * - `count`: Count how many times a flag occurs 176 | * - `store`: Store the value as a boolean flag 177 | * - `store_false`: Store the value as a boolean flag with opposite meaning 178 | * 179 | * Defaults to `set` if not specified. 180 | */ 181 | action?: 'set' | 'append' | 'count' | 'store' | 'store_false' 182 | /** 183 | * Short option name 184 | * 185 | * This is a single character that can be used to represent the option 186 | * in the command line. For example, `-v` for the `--verbose` option. 187 | * Panics if the length of the string is empty. If the size of string 188 | * is greater than 1, the first character will be used as the short option. 189 | * 190 | * This option will be ignored if option `type` is not `option`. 191 | * 192 | * Defaults to the first character of the long option name. 193 | */ 194 | short?: string 195 | /** 196 | * Long option name 197 | * 198 | * This is the name of the option that will be used to represent the option, 199 | * preceded by two dashes. For example, `--verbose` option. 200 | * 201 | * This option will be ignored if option `type` is not `option`. 202 | * 203 | * Defaults to the name of the argument. 204 | */ 205 | long?: string 206 | /** Option aliases */ 207 | alias?: Array 208 | /** Hidden option aliases */ 209 | hiddenAlias?: Array 210 | /** Short option aliases */ 211 | shortAlias?: Array 212 | /** Hidden short option aliases */ 213 | hiddenShortAlias?: Array 214 | /** 215 | * Value hint for shell completion 216 | * 217 | * Provide the shell a hint about how to complete this argument. 218 | * 219 | * **Warning**: this will implicitly set `action` to `set`. 220 | */ 221 | valueHint?: 222 | | 'any_path' 223 | | 'file' 224 | | 'dir' 225 | | 'executable' 226 | | 'cmd_name' 227 | | 'cmd' 228 | | 'cmd_with_args' 229 | | 'url' 230 | | 'username' 231 | | 'hostname' 232 | | 'email' 233 | /** Option description */ 234 | help?: string 235 | /** 236 | * Required argument 237 | * 238 | * If true, the argument is required and the command will fail without it. 239 | */ 240 | required?: boolean 241 | /** Value for the argument when not present */ 242 | default?: string 243 | /** 244 | * Value for the argument when the flag is present but no value is specified. 245 | * 246 | * This configuration option is often used to give the user a shortcut and 247 | * allow them to efficiently specify an option argument without requiring an 248 | * explicitly value. The `--color` argument is a common example. By supplying 249 | * a default, such as `default_missing_value("always")`, the user can quickly 250 | * just add `--color` to the command line to produce the desired color output. 251 | */ 252 | defaultMissing?: string 253 | /** 254 | * Limit the count of values for the argument 255 | * 256 | * If the expected number of parameters required is a fixed value, pass in the 257 | * number directly. If you want to limit the number of values to a range, for 258 | * example, pass `1..5` or `1..=4` to specify a range from 1 to 4 inclusive. 259 | */ 260 | numArgs?: string 261 | /** Requires that options use the `--option=val` syntax */ 262 | requiredEquals?: boolean 263 | /** 264 | * Hide argument in help output 265 | * 266 | * Do not display the argument in the help message. 267 | */ 268 | hidden?: boolean 269 | /** 270 | * Global argument 271 | * 272 | * Specifies that an argument can be matched to all child subcommands 273 | */ 274 | global?: boolean 275 | /** 276 | * Options that conflict with this argument 277 | * 278 | * This argument is mutually exclusive with the specified arguments. 279 | */ 280 | conflictsWith?: Array 281 | /** 282 | * Exclusive argument 283 | * 284 | * This argument must be passed alone; it conflicts with all other arguments. 285 | */ 286 | exclusive?: boolean 287 | /** 288 | * Hide default value in help output 289 | * 290 | * Do not display the default value of the argument in the help message. 291 | * 292 | * This is useful when default behavior of an arg is explained elsewhere 293 | * in the help text. 294 | */ 295 | hideDefaultValue?: boolean 296 | } 297 | /** 298 | * Command definition 299 | * 300 | * This is the object that defines a command. 301 | * It contains the metadata, options, and callback function. 302 | */ 303 | export interface Command { 304 | meta: CommandMeta 305 | options: Record 306 | callback?: (ctx: Context) => void 307 | subcommands?: Record 308 | } 309 | export declare class ProgressBar { 310 | finish(): void 311 | finishAndClear(): void 312 | finishUsingStyle(): void 313 | finishWithMessage(msg: string): void 314 | setPosition(pos: number): void 315 | setLength(len: number): void 316 | setMessage(msg: string): void 317 | setPrefix(prefix: string): void 318 | setTabWidth(width: number): void 319 | setTemplate(template: string): void 320 | setTickStrings(s: Array): void 321 | setProgressChars(s: string): void 322 | tick(): void 323 | abandon(): void 324 | abandonWithMessage(msg: string): void 325 | inc(delta: number): void 326 | incLength(delta: number): void 327 | reset(): void 328 | println(msg: string): void 329 | suspend(f: () => void): void 330 | enableSteadyTick(ms: number): void 331 | disableSteadyTick(): void 332 | } 333 | /** 334 | * Command context 335 | * 336 | * This is the context object that is passed to the command callback. 337 | */ 338 | export declare class Context { 339 | /** 340 | * Raw arguments 341 | * 342 | * The raw arguments parsed by command line or manually given. 343 | */ 344 | rawArgs: string[] 345 | ask(prompt: string, config?: InputConfig | undefined | null): string 346 | confirm(prompt: string, config?: ConfirmConfig | undefined | null): boolean 347 | createProgressBar(total: number): ProgressBar 348 | createSpinner(): ProgressBar 349 | /** 350 | * Create a new command context 351 | * 352 | * This method is used to create a new command context, 353 | * and is not intended to be used directly. 354 | * 355 | * @param args - Parsed arguments 356 | * @param raw_args - Raw arguments 357 | */ 358 | constructor(args: Record, raw_args: string[]) 359 | /** Get the parsed arguments */ 360 | get args(): Record 361 | /** Get the raw arguments */ 362 | getRawArgs(): string[] 363 | /** Get the argument value by specified key */ 364 | get(key: string): any 365 | } 366 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /* tslint:disable */ 2 | /* eslint-disable */ 3 | /* prettier-ignore */ 4 | 5 | /* auto-generated by NAPI-RS */ 6 | 7 | const { existsSync, readFileSync } = require('fs') 8 | const { join } = require('path') 9 | 10 | const { platform, arch } = process 11 | 12 | let nativeBinding = null 13 | let localFileExisted = false 14 | let loadError = null 15 | 16 | function isMusl() { 17 | // For Node 10 18 | if (!process.report || typeof process.report.getReport !== 'function') { 19 | try { 20 | const lddPath = require('child_process').execSync('which ldd').toString().trim() 21 | return readFileSync(lddPath, 'utf8').includes('musl') 22 | } catch (e) { 23 | return true 24 | } 25 | } else { 26 | const { glibcVersionRuntime } = process.report.getReport().header 27 | return !glibcVersionRuntime 28 | } 29 | } 30 | 31 | switch (platform) { 32 | case 'android': 33 | switch (arch) { 34 | case 'arm64': 35 | localFileExisted = existsSync(join(__dirname, 'archons-napi.android-arm64.node')) 36 | try { 37 | if (localFileExisted) { 38 | nativeBinding = require('./archons-napi.android-arm64.node') 39 | } else { 40 | nativeBinding = require('archons-napi-android-arm64') 41 | } 42 | } catch (e) { 43 | loadError = e 44 | } 45 | break 46 | case 'arm': 47 | localFileExisted = existsSync(join(__dirname, 'archons-napi.android-arm-eabi.node')) 48 | try { 49 | if (localFileExisted) { 50 | nativeBinding = require('./archons-napi.android-arm-eabi.node') 51 | } else { 52 | nativeBinding = require('archons-napi-android-arm-eabi') 53 | } 54 | } catch (e) { 55 | loadError = e 56 | } 57 | break 58 | default: 59 | throw new Error(`Unsupported architecture on Android ${arch}`) 60 | } 61 | break 62 | case 'win32': 63 | switch (arch) { 64 | case 'x64': 65 | localFileExisted = existsSync(join(__dirname, 'archons-napi.win32-x64-msvc.node')) 66 | try { 67 | if (localFileExisted) { 68 | nativeBinding = require('./archons-napi.win32-x64-msvc.node') 69 | } else { 70 | nativeBinding = require('archons-napi-win32-x64-msvc') 71 | } 72 | } catch (e) { 73 | loadError = e 74 | } 75 | break 76 | case 'ia32': 77 | localFileExisted = existsSync(join(__dirname, 'archons-napi.win32-ia32-msvc.node')) 78 | try { 79 | if (localFileExisted) { 80 | nativeBinding = require('./archons-napi.win32-ia32-msvc.node') 81 | } else { 82 | nativeBinding = require('archons-napi-win32-ia32-msvc') 83 | } 84 | } catch (e) { 85 | loadError = e 86 | } 87 | break 88 | case 'arm64': 89 | localFileExisted = existsSync(join(__dirname, 'archons-napi.win32-arm64-msvc.node')) 90 | try { 91 | if (localFileExisted) { 92 | nativeBinding = require('./archons-napi.win32-arm64-msvc.node') 93 | } else { 94 | nativeBinding = require('archons-napi-win32-arm64-msvc') 95 | } 96 | } catch (e) { 97 | loadError = e 98 | } 99 | break 100 | default: 101 | throw new Error(`Unsupported architecture on Windows: ${arch}`) 102 | } 103 | break 104 | case 'darwin': 105 | localFileExisted = existsSync(join(__dirname, 'archons-napi.darwin-universal.node')) 106 | try { 107 | if (localFileExisted) { 108 | nativeBinding = require('./archons-napi.darwin-universal.node') 109 | } else { 110 | nativeBinding = require('archons-napi-darwin-universal') 111 | } 112 | break 113 | } catch {} 114 | switch (arch) { 115 | case 'x64': 116 | localFileExisted = existsSync(join(__dirname, 'archons-napi.darwin-x64.node')) 117 | try { 118 | if (localFileExisted) { 119 | nativeBinding = require('./archons-napi.darwin-x64.node') 120 | } else { 121 | nativeBinding = require('archons-napi-darwin-x64') 122 | } 123 | } catch (e) { 124 | loadError = e 125 | } 126 | break 127 | case 'arm64': 128 | localFileExisted = existsSync(join(__dirname, 'archons-napi.darwin-arm64.node')) 129 | try { 130 | if (localFileExisted) { 131 | nativeBinding = require('./archons-napi.darwin-arm64.node') 132 | } else { 133 | nativeBinding = require('archons-napi-darwin-arm64') 134 | } 135 | } catch (e) { 136 | loadError = e 137 | } 138 | break 139 | default: 140 | throw new Error(`Unsupported architecture on macOS: ${arch}`) 141 | } 142 | break 143 | case 'freebsd': 144 | if (arch !== 'x64') { 145 | throw new Error(`Unsupported architecture on FreeBSD: ${arch}`) 146 | } 147 | localFileExisted = existsSync(join(__dirname, 'archons-napi.freebsd-x64.node')) 148 | try { 149 | if (localFileExisted) { 150 | nativeBinding = require('./archons-napi.freebsd-x64.node') 151 | } else { 152 | nativeBinding = require('archons-napi-freebsd-x64') 153 | } 154 | } catch (e) { 155 | loadError = e 156 | } 157 | break 158 | case 'linux': 159 | switch (arch) { 160 | case 'x64': 161 | if (isMusl()) { 162 | localFileExisted = existsSync(join(__dirname, 'archons-napi.linux-x64-musl.node')) 163 | try { 164 | if (localFileExisted) { 165 | nativeBinding = require('./archons-napi.linux-x64-musl.node') 166 | } else { 167 | nativeBinding = require('archons-napi-linux-x64-musl') 168 | } 169 | } catch (e) { 170 | loadError = e 171 | } 172 | } else { 173 | localFileExisted = existsSync(join(__dirname, 'archons-napi.linux-x64-gnu.node')) 174 | try { 175 | if (localFileExisted) { 176 | nativeBinding = require('./archons-napi.linux-x64-gnu.node') 177 | } else { 178 | nativeBinding = require('archons-napi-linux-x64-gnu') 179 | } 180 | } catch (e) { 181 | loadError = e 182 | } 183 | } 184 | break 185 | case 'arm64': 186 | if (isMusl()) { 187 | localFileExisted = existsSync(join(__dirname, 'archons-napi.linux-arm64-musl.node')) 188 | try { 189 | if (localFileExisted) { 190 | nativeBinding = require('./archons-napi.linux-arm64-musl.node') 191 | } else { 192 | nativeBinding = require('archons-napi-linux-arm64-musl') 193 | } 194 | } catch (e) { 195 | loadError = e 196 | } 197 | } else { 198 | localFileExisted = existsSync(join(__dirname, 'archons-napi.linux-arm64-gnu.node')) 199 | try { 200 | if (localFileExisted) { 201 | nativeBinding = require('./archons-napi.linux-arm64-gnu.node') 202 | } else { 203 | nativeBinding = require('archons-napi-linux-arm64-gnu') 204 | } 205 | } catch (e) { 206 | loadError = e 207 | } 208 | } 209 | break 210 | case 'arm': 211 | if (isMusl()) { 212 | localFileExisted = existsSync(join(__dirname, 'archons-napi.linux-arm-musleabihf.node')) 213 | try { 214 | if (localFileExisted) { 215 | nativeBinding = require('./archons-napi.linux-arm-musleabihf.node') 216 | } else { 217 | nativeBinding = require('archons-napi-linux-arm-musleabihf') 218 | } 219 | } catch (e) { 220 | loadError = e 221 | } 222 | } else { 223 | localFileExisted = existsSync(join(__dirname, 'archons-napi.linux-arm-gnueabihf.node')) 224 | try { 225 | if (localFileExisted) { 226 | nativeBinding = require('./archons-napi.linux-arm-gnueabihf.node') 227 | } else { 228 | nativeBinding = require('archons-napi-linux-arm-gnueabihf') 229 | } 230 | } catch (e) { 231 | loadError = e 232 | } 233 | } 234 | break 235 | case 'riscv64': 236 | if (isMusl()) { 237 | localFileExisted = existsSync(join(__dirname, 'archons-napi.linux-riscv64-musl.node')) 238 | try { 239 | if (localFileExisted) { 240 | nativeBinding = require('./archons-napi.linux-riscv64-musl.node') 241 | } else { 242 | nativeBinding = require('archons-napi-linux-riscv64-musl') 243 | } 244 | } catch (e) { 245 | loadError = e 246 | } 247 | } else { 248 | localFileExisted = existsSync(join(__dirname, 'archons-napi.linux-riscv64-gnu.node')) 249 | try { 250 | if (localFileExisted) { 251 | nativeBinding = require('./archons-napi.linux-riscv64-gnu.node') 252 | } else { 253 | nativeBinding = require('archons-napi-linux-riscv64-gnu') 254 | } 255 | } catch (e) { 256 | loadError = e 257 | } 258 | } 259 | break 260 | case 's390x': 261 | localFileExisted = existsSync(join(__dirname, 'archons-napi.linux-s390x-gnu.node')) 262 | try { 263 | if (localFileExisted) { 264 | nativeBinding = require('./archons-napi.linux-s390x-gnu.node') 265 | } else { 266 | nativeBinding = require('archons-napi-linux-s390x-gnu') 267 | } 268 | } catch (e) { 269 | loadError = e 270 | } 271 | break 272 | default: 273 | throw new Error(`Unsupported architecture on Linux: ${arch}`) 274 | } 275 | break 276 | default: 277 | throw new Error(`Unsupported OS: ${platform}, architecture: ${arch}`) 278 | } 279 | 280 | if (!nativeBinding) { 281 | if (loadError) { 282 | throw loadError 283 | } 284 | throw new Error(`Failed to load native binding`) 285 | } 286 | 287 | const { 288 | defineCommand, 289 | run, 290 | select, 291 | checkbox, 292 | input, 293 | confirm, 294 | password, 295 | ProgressBar, 296 | createProgressBar, 297 | createSpinner, 298 | Context, 299 | } = nativeBinding 300 | 301 | module.exports.defineCommand = defineCommand 302 | module.exports.run = run 303 | module.exports.select = select 304 | module.exports.checkbox = checkbox 305 | module.exports.input = input 306 | module.exports.confirm = confirm 307 | module.exports.password = password 308 | module.exports.ProgressBar = ProgressBar 309 | module.exports.createProgressBar = createProgressBar 310 | module.exports.createSpinner = createSpinner 311 | module.exports.Context = Context 312 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * For a detailed explanation regarding each configuration property, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | import type { Config } from 'jest' 7 | 8 | const config: Config = { 9 | // All imported modules in your tests should be mocked automatically 10 | // automock: false, 11 | 12 | // Stop running tests after `n` failures 13 | // bail: 0, 14 | 15 | // The directory where Jest should store its cached dependency information 16 | // cacheDirectory: "/tmp/jest_rs", 17 | 18 | // Automatically clear mock calls, instances, contexts and results before every test 19 | clearMocks: true, 20 | 21 | // Indicates whether the coverage information should be collected while executing the test 22 | // collectCoverage: false, 23 | 24 | // An array of glob patterns indicating a set of files for which coverage information should be collected 25 | // collectCoverageFrom: undefined, 26 | 27 | // The directory where Jest should output its coverage files 28 | // coverageDirectory: undefined, 29 | 30 | // An array of regexp pattern strings used to skip coverage collection 31 | // coveragePathIgnorePatterns: [ 32 | // "/node_modules/" 33 | // ], 34 | 35 | // Indicates which provider should be used to instrument code for coverage 36 | coverageProvider: 'v8', 37 | 38 | // A list of reporter names that Jest uses when writing coverage reports 39 | // coverageReporters: [ 40 | // "json", 41 | // "text", 42 | // "lcov", 43 | // "clover" 44 | // ], 45 | 46 | // An object that configures minimum threshold enforcement for coverage results 47 | // coverageThreshold: undefined, 48 | 49 | // A path to a custom dependency extractor 50 | // dependencyExtractor: undefined, 51 | 52 | // Make calling deprecated APIs throw helpful error messages 53 | // errorOnDeprecated: false, 54 | 55 | // The default configuration for fake timers 56 | // fakeTimers: { 57 | // "enableGlobally": false 58 | // }, 59 | 60 | // Force coverage collection from ignored files using an array of glob patterns 61 | // forceCoverageMatch: [], 62 | 63 | // A path to a module which exports an async function that is triggered once before all test suites 64 | // globalSetup: undefined, 65 | 66 | // A path to a module which exports an async function that is triggered once after all test suites 67 | // globalTeardown: undefined, 68 | 69 | // A set of global variables that need to be available in all test environments 70 | // globals: {}, 71 | 72 | // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. 73 | // maxWorkers: "50%", 74 | 75 | // An array of directory names to be searched recursively up from the requiring module's location 76 | // moduleDirectories: [ 77 | // "node_modules" 78 | // ], 79 | 80 | // An array of file extensions your modules use 81 | // moduleFileExtensions: [ 82 | // "js", 83 | // "mjs", 84 | // "cjs", 85 | // "jsx", 86 | // "ts", 87 | // "tsx", 88 | // "json", 89 | // "node" 90 | // ], 91 | 92 | // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module 93 | // moduleNameMapper: {}, 94 | 95 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 96 | // modulePathIgnorePatterns: [], 97 | 98 | // Activates notifications for test results 99 | // notify: false, 100 | 101 | // An enum that specifies notification mode. Requires { notify: true } 102 | // notifyMode: "failure-change", 103 | 104 | // A preset that is used as a base for Jest's configuration 105 | // preset: undefined, 106 | 107 | // Run tests from one or more projects 108 | // projects: undefined, 109 | 110 | // Use this configuration option to add custom reporters to Jest 111 | // reporters: undefined, 112 | 113 | // Automatically reset mock state before every test 114 | // resetMocks: false, 115 | 116 | // Reset the module registry before running each individual test 117 | // resetModules: false, 118 | 119 | // A path to a custom resolver 120 | // resolver: undefined, 121 | 122 | // Automatically restore mock state and implementation before every test 123 | // restoreMocks: false, 124 | 125 | // The root directory that Jest should scan for tests and modules within 126 | // rootDir: undefined, 127 | 128 | // A list of paths to directories that Jest should use to search for files in 129 | // roots: [ 130 | // "" 131 | // ], 132 | 133 | // Allows you to use a custom runner instead of Jest's default test runner 134 | // runner: "jest-runner", 135 | 136 | // The paths to modules that run some code to configure or set up the testing environment before each test 137 | // setupFiles: [], 138 | 139 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 140 | // setupFilesAfterEnv: [], 141 | 142 | // The number of seconds after which a test is considered as slow and reported as such in the results. 143 | // slowTestThreshold: 5, 144 | 145 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 146 | // snapshotSerializers: [], 147 | 148 | // The test environment that will be used for testing 149 | // testEnvironment: "jest-environment-node", 150 | 151 | // Options that will be passed to the testEnvironment 152 | // testEnvironmentOptions: {}, 153 | 154 | // Adds a location field to test results 155 | // testLocationInResults: false, 156 | 157 | // The glob patterns Jest uses to detect test files 158 | // testMatch: [ 159 | // "**/__tests__/**/*.[jt]s?(x)", 160 | // "**/?(*.)+(spec|test).[tj]s?(x)" 161 | // ], 162 | 163 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 164 | // testPathIgnorePatterns: [ 165 | // "/node_modules/" 166 | // ], 167 | 168 | // The regexp pattern or array of patterns that Jest uses to detect test files 169 | // testRegex: [], 170 | 171 | // This option allows the use of a custom results processor 172 | // testResultsProcessor: undefined, 173 | 174 | // This option allows use of a custom test runner 175 | // testRunner: "jest-circus/runner", 176 | 177 | // A map from regular expressions to paths to transformers 178 | // transform: undefined, 179 | 180 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 181 | // transformIgnorePatterns: [ 182 | // "/node_modules/", 183 | // "\\.pnp\\.[^\\/]+$" 184 | // ], 185 | 186 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 187 | // unmockedModulePathPatterns: undefined, 188 | 189 | // Indicates whether each individual test should be reported during the run 190 | // verbose: undefined, 191 | 192 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 193 | // watchPathIgnorePatterns: [], 194 | 195 | // Whether to use watchman for file crawling 196 | // watchman: true, 197 | } 198 | 199 | export default config 200 | -------------------------------------------------------------------------------- /npm/android-arm-eabi/README.md: -------------------------------------------------------------------------------- 1 | # `archons-napi-android-arm-eabi` 2 | 3 | This is the **armv7-linux-androideabi** binary for `archons-napi` 4 | -------------------------------------------------------------------------------- /npm/android-arm-eabi/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "archons-napi-android-arm-eabi", 3 | "version": "0.2.11", 4 | "os": [ 5 | "android" 6 | ], 7 | "cpu": [ 8 | "arm" 9 | ], 10 | "main": "archons-napi.android-arm-eabi.node", 11 | "files": [ 12 | "archons-napi.android-arm-eabi.node" 13 | ], 14 | "description": "Fast, powerful and elegant CLI build tool based on clap-rs", 15 | "keywords": [ 16 | "cli-tool", 17 | "rust" 18 | ], 19 | "author": { 20 | "name": "苏向夜", 21 | "email": "fu050409@163.com", 22 | "url": "https://github.com/fu050409" 23 | }, 24 | "license": "MIT", 25 | "engines": { 26 | "node": ">= 10" 27 | }, 28 | "publishConfig": { 29 | "registry": "https://registry.npmjs.org/", 30 | "access": "public" 31 | }, 32 | "repository": "https://github.com/noctisynth/archons.git" 33 | } 34 | -------------------------------------------------------------------------------- /npm/android-arm64/README.md: -------------------------------------------------------------------------------- 1 | # `archons-napi-android-arm64` 2 | 3 | This is the **aarch64-linux-android** binary for `archons-napi` 4 | -------------------------------------------------------------------------------- /npm/android-arm64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "archons-napi-android-arm64", 3 | "version": "0.2.11", 4 | "os": [ 5 | "android" 6 | ], 7 | "cpu": [ 8 | "arm64" 9 | ], 10 | "main": "archons-napi.android-arm64.node", 11 | "files": [ 12 | "archons-napi.android-arm64.node" 13 | ], 14 | "description": "Fast, powerful and elegant CLI build tool based on clap-rs", 15 | "keywords": [ 16 | "cli-tool", 17 | "rust" 18 | ], 19 | "author": { 20 | "name": "苏向夜", 21 | "email": "fu050409@163.com", 22 | "url": "https://github.com/fu050409" 23 | }, 24 | "license": "MIT", 25 | "engines": { 26 | "node": ">= 10" 27 | }, 28 | "publishConfig": { 29 | "registry": "https://registry.npmjs.org/", 30 | "access": "public" 31 | }, 32 | "repository": "https://github.com/noctisynth/archons.git" 33 | } 34 | -------------------------------------------------------------------------------- /npm/darwin-arm64/README.md: -------------------------------------------------------------------------------- 1 | # `archons-napi-darwin-arm64` 2 | 3 | This is the **aarch64-apple-darwin** binary for `archons-napi` 4 | -------------------------------------------------------------------------------- /npm/darwin-arm64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "archons-napi-darwin-arm64", 3 | "version": "0.2.11", 4 | "os": [ 5 | "darwin" 6 | ], 7 | "cpu": [ 8 | "arm64" 9 | ], 10 | "main": "archons-napi.darwin-arm64.node", 11 | "files": [ 12 | "archons-napi.darwin-arm64.node" 13 | ], 14 | "description": "Fast, powerful and elegant CLI build tool based on clap-rs", 15 | "keywords": [ 16 | "cli-tool", 17 | "rust" 18 | ], 19 | "author": { 20 | "name": "苏向夜", 21 | "email": "fu050409@163.com", 22 | "url": "https://github.com/fu050409" 23 | }, 24 | "license": "MIT", 25 | "engines": { 26 | "node": ">= 10" 27 | }, 28 | "publishConfig": { 29 | "registry": "https://registry.npmjs.org/", 30 | "access": "public" 31 | }, 32 | "repository": "https://github.com/noctisynth/archons.git" 33 | } 34 | -------------------------------------------------------------------------------- /npm/darwin-x64/README.md: -------------------------------------------------------------------------------- 1 | # `archons-napi-darwin-x64` 2 | 3 | This is the **x86_64-apple-darwin** binary for `archons-napi` 4 | -------------------------------------------------------------------------------- /npm/darwin-x64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "archons-napi-darwin-x64", 3 | "version": "0.2.11", 4 | "os": [ 5 | "darwin" 6 | ], 7 | "cpu": [ 8 | "x64" 9 | ], 10 | "main": "archons-napi.darwin-x64.node", 11 | "files": [ 12 | "archons-napi.darwin-x64.node" 13 | ], 14 | "description": "Fast, powerful and elegant CLI build tool based on clap-rs", 15 | "keywords": [ 16 | "cli-tool", 17 | "rust" 18 | ], 19 | "author": { 20 | "name": "苏向夜", 21 | "email": "fu050409@163.com", 22 | "url": "https://github.com/fu050409" 23 | }, 24 | "license": "MIT", 25 | "engines": { 26 | "node": ">= 10" 27 | }, 28 | "publishConfig": { 29 | "registry": "https://registry.npmjs.org/", 30 | "access": "public" 31 | }, 32 | "repository": "https://github.com/noctisynth/archons.git" 33 | } 34 | -------------------------------------------------------------------------------- /npm/freebsd-x64/README.md: -------------------------------------------------------------------------------- 1 | # `archons-napi-freebsd-x64` 2 | 3 | This is the **x86_64-unknown-freebsd** binary for `archons-napi` 4 | -------------------------------------------------------------------------------- /npm/freebsd-x64/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "archons-napi-freebsd-x64", 3 | "version": "0.2.11", 4 | "os": [ 5 | "freebsd" 6 | ], 7 | "cpu": [ 8 | "x64" 9 | ], 10 | "main": "archons-napi.freebsd-x64.node", 11 | "files": [ 12 | "archons-napi.freebsd-x64.node" 13 | ], 14 | "description": "Fast, powerful and elegant CLI build tool based on clap-rs", 15 | "keywords": [ 16 | "cli-tool", 17 | "rust" 18 | ], 19 | "author": { 20 | "name": "苏向夜", 21 | "email": "fu050409@163.com", 22 | "url": "https://github.com/fu050409" 23 | }, 24 | "license": "MIT", 25 | "engines": { 26 | "node": ">= 10" 27 | }, 28 | "publishConfig": { 29 | "registry": "https://registry.npmjs.org/", 30 | "access": "public" 31 | }, 32 | "repository": "https://github.com/noctisynth/archons.git" 33 | } 34 | -------------------------------------------------------------------------------- /npm/linux-arm-gnueabihf/README.md: -------------------------------------------------------------------------------- 1 | # `archons-napi-linux-arm-gnueabihf` 2 | 3 | This is the **armv7-unknown-linux-gnueabihf** binary for `archons-napi` 4 | -------------------------------------------------------------------------------- /npm/linux-arm-gnueabihf/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "archons-napi-linux-arm-gnueabihf", 3 | "version": "0.2.11", 4 | "os": [ 5 | "linux" 6 | ], 7 | "cpu": [ 8 | "arm" 9 | ], 10 | "main": "archons-napi.linux-arm-gnueabihf.node", 11 | "files": [ 12 | "archons-napi.linux-arm-gnueabihf.node" 13 | ], 14 | "description": "Fast, powerful and elegant CLI build tool based on clap-rs", 15 | "keywords": [ 16 | "cli-tool", 17 | "rust" 18 | ], 19 | "author": { 20 | "name": "苏向夜", 21 | "email": "fu050409@163.com", 22 | "url": "https://github.com/fu050409" 23 | }, 24 | "license": "MIT", 25 | "engines": { 26 | "node": ">= 10" 27 | }, 28 | "publishConfig": { 29 | "registry": "https://registry.npmjs.org/", 30 | "access": "public" 31 | }, 32 | "repository": "https://github.com/noctisynth/archons.git" 33 | } 34 | -------------------------------------------------------------------------------- /npm/linux-arm64-gnu/README.md: -------------------------------------------------------------------------------- 1 | # `archons-napi-linux-arm64-gnu` 2 | 3 | This is the **aarch64-unknown-linux-gnu** binary for `archons-napi` 4 | -------------------------------------------------------------------------------- /npm/linux-arm64-gnu/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "archons-napi-linux-arm64-gnu", 3 | "version": "0.2.11", 4 | "os": [ 5 | "linux" 6 | ], 7 | "cpu": [ 8 | "arm64" 9 | ], 10 | "main": "archons-napi.linux-arm64-gnu.node", 11 | "files": [ 12 | "archons-napi.linux-arm64-gnu.node" 13 | ], 14 | "description": "Fast, powerful and elegant CLI build tool based on clap-rs", 15 | "keywords": [ 16 | "cli-tool", 17 | "rust" 18 | ], 19 | "author": { 20 | "name": "苏向夜", 21 | "email": "fu050409@163.com", 22 | "url": "https://github.com/fu050409" 23 | }, 24 | "license": "MIT", 25 | "engines": { 26 | "node": ">= 10" 27 | }, 28 | "publishConfig": { 29 | "registry": "https://registry.npmjs.org/", 30 | "access": "public" 31 | }, 32 | "repository": "https://github.com/noctisynth/archons.git", 33 | "libc": [ 34 | "glibc" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /npm/linux-arm64-musl/README.md: -------------------------------------------------------------------------------- 1 | # `archons-napi-linux-arm64-musl` 2 | 3 | This is the **aarch64-unknown-linux-musl** binary for `archons-napi` 4 | -------------------------------------------------------------------------------- /npm/linux-arm64-musl/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "archons-napi-linux-arm64-musl", 3 | "version": "0.2.11", 4 | "os": [ 5 | "linux" 6 | ], 7 | "cpu": [ 8 | "arm64" 9 | ], 10 | "main": "archons-napi.linux-arm64-musl.node", 11 | "files": [ 12 | "archons-napi.linux-arm64-musl.node" 13 | ], 14 | "description": "Fast, powerful and elegant CLI build tool based on clap-rs", 15 | "keywords": [ 16 | "cli-tool", 17 | "rust" 18 | ], 19 | "author": { 20 | "name": "苏向夜", 21 | "email": "fu050409@163.com", 22 | "url": "https://github.com/fu050409" 23 | }, 24 | "license": "MIT", 25 | "engines": { 26 | "node": ">= 10" 27 | }, 28 | "publishConfig": { 29 | "registry": "https://registry.npmjs.org/", 30 | "access": "public" 31 | }, 32 | "repository": "https://github.com/noctisynth/archons.git", 33 | "libc": [ 34 | "musl" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /npm/linux-x64-gnu/README.md: -------------------------------------------------------------------------------- 1 | # `archons-napi-linux-x64-gnu` 2 | 3 | This is the **x86_64-unknown-linux-gnu** binary for `archons-napi` 4 | -------------------------------------------------------------------------------- /npm/linux-x64-gnu/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "archons-napi-linux-x64-gnu", 3 | "version": "0.2.11", 4 | "os": [ 5 | "linux" 6 | ], 7 | "cpu": [ 8 | "x64" 9 | ], 10 | "main": "archons-napi.linux-x64-gnu.node", 11 | "files": [ 12 | "archons-napi.linux-x64-gnu.node" 13 | ], 14 | "description": "Fast, powerful and elegant CLI build tool based on clap-rs", 15 | "keywords": [ 16 | "cli-tool", 17 | "rust" 18 | ], 19 | "author": { 20 | "name": "苏向夜", 21 | "email": "fu050409@163.com", 22 | "url": "https://github.com/fu050409" 23 | }, 24 | "license": "MIT", 25 | "engines": { 26 | "node": ">= 10" 27 | }, 28 | "publishConfig": { 29 | "registry": "https://registry.npmjs.org/", 30 | "access": "public" 31 | }, 32 | "repository": "https://github.com/noctisynth/archons.git", 33 | "libc": [ 34 | "glibc" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /npm/linux-x64-musl/README.md: -------------------------------------------------------------------------------- 1 | # `archons-napi-linux-x64-musl` 2 | 3 | This is the **x86_64-unknown-linux-musl** binary for `archons-napi` 4 | -------------------------------------------------------------------------------- /npm/linux-x64-musl/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "archons-napi-linux-x64-musl", 3 | "version": "0.2.11", 4 | "os": [ 5 | "linux" 6 | ], 7 | "cpu": [ 8 | "x64" 9 | ], 10 | "main": "archons-napi.linux-x64-musl.node", 11 | "files": [ 12 | "archons-napi.linux-x64-musl.node" 13 | ], 14 | "description": "Fast, powerful and elegant CLI build tool based on clap-rs", 15 | "keywords": [ 16 | "cli-tool", 17 | "rust" 18 | ], 19 | "author": { 20 | "name": "苏向夜", 21 | "email": "fu050409@163.com", 22 | "url": "https://github.com/fu050409" 23 | }, 24 | "license": "MIT", 25 | "engines": { 26 | "node": ">= 10" 27 | }, 28 | "publishConfig": { 29 | "registry": "https://registry.npmjs.org/", 30 | "access": "public" 31 | }, 32 | "repository": "https://github.com/noctisynth/archons.git", 33 | "libc": [ 34 | "musl" 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /npm/win32-arm64-msvc/README.md: -------------------------------------------------------------------------------- 1 | # `archons-napi-win32-arm64-msvc` 2 | 3 | This is the **aarch64-pc-windows-msvc** binary for `archons-napi` 4 | -------------------------------------------------------------------------------- /npm/win32-arm64-msvc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "archons-napi-win32-arm64-msvc", 3 | "version": "0.2.11", 4 | "os": [ 5 | "win32" 6 | ], 7 | "cpu": [ 8 | "arm64" 9 | ], 10 | "main": "archons-napi.win32-arm64-msvc.node", 11 | "files": [ 12 | "archons-napi.win32-arm64-msvc.node" 13 | ], 14 | "description": "Fast, powerful and elegant CLI build tool based on clap-rs", 15 | "keywords": [ 16 | "cli-tool", 17 | "rust" 18 | ], 19 | "author": { 20 | "name": "苏向夜", 21 | "email": "fu050409@163.com", 22 | "url": "https://github.com/fu050409" 23 | }, 24 | "license": "MIT", 25 | "engines": { 26 | "node": ">= 10" 27 | }, 28 | "publishConfig": { 29 | "registry": "https://registry.npmjs.org/", 30 | "access": "public" 31 | }, 32 | "repository": "https://github.com/noctisynth/archons.git" 33 | } 34 | -------------------------------------------------------------------------------- /npm/win32-ia32-msvc/README.md: -------------------------------------------------------------------------------- 1 | # `archons-napi-win32-ia32-msvc` 2 | 3 | This is the **i686-pc-windows-msvc** binary for `archons-napi` 4 | -------------------------------------------------------------------------------- /npm/win32-ia32-msvc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "archons-napi-win32-ia32-msvc", 3 | "version": "0.2.11", 4 | "os": [ 5 | "win32" 6 | ], 7 | "cpu": [ 8 | "ia32" 9 | ], 10 | "main": "archons-napi.win32-ia32-msvc.node", 11 | "files": [ 12 | "archons-napi.win32-ia32-msvc.node" 13 | ], 14 | "description": "Fast, powerful and elegant CLI build tool based on clap-rs", 15 | "keywords": [ 16 | "cli-tool", 17 | "rust" 18 | ], 19 | "author": { 20 | "name": "苏向夜", 21 | "email": "fu050409@163.com", 22 | "url": "https://github.com/fu050409" 23 | }, 24 | "license": "MIT", 25 | "engines": { 26 | "node": ">= 10" 27 | }, 28 | "publishConfig": { 29 | "registry": "https://registry.npmjs.org/", 30 | "access": "public" 31 | }, 32 | "repository": "https://github.com/noctisynth/archons.git" 33 | } 34 | -------------------------------------------------------------------------------- /npm/win32-x64-msvc/README.md: -------------------------------------------------------------------------------- 1 | # `archons-napi-win32-x64-msvc` 2 | 3 | This is the **x86_64-pc-windows-msvc** binary for `archons-napi` 4 | -------------------------------------------------------------------------------- /npm/win32-x64-msvc/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "archons-napi-win32-x64-msvc", 3 | "version": "0.2.11", 4 | "os": [ 5 | "win32" 6 | ], 7 | "cpu": [ 8 | "x64" 9 | ], 10 | "main": "archons-napi.win32-x64-msvc.node", 11 | "files": [ 12 | "archons-napi.win32-x64-msvc.node" 13 | ], 14 | "description": "Fast, powerful and elegant CLI build tool based on clap-rs", 15 | "keywords": [ 16 | "cli-tool", 17 | "rust" 18 | ], 19 | "author": { 20 | "name": "苏向夜", 21 | "email": "fu050409@163.com", 22 | "url": "https://github.com/fu050409" 23 | }, 24 | "license": "MIT", 25 | "engines": { 26 | "node": ">= 10" 27 | }, 28 | "publishConfig": { 29 | "registry": "https://registry.npmjs.org/", 30 | "access": "public" 31 | }, 32 | "repository": "https://github.com/noctisynth/archons.git" 33 | } 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "archons", 3 | "version": "0.2.11", 4 | "description": "Fast, powerful and elegant CLI build tool based on clap-rs", 5 | "author": { 6 | "name": "苏向夜", 7 | "email": "fu050409@163.com", 8 | "url": "https://github.com/fu050409" 9 | }, 10 | "main": "index.js", 11 | "exports": { 12 | ".": { 13 | "import": "./index.js", 14 | "require": "./index.js" 15 | } 16 | }, 17 | "type": "commonjs", 18 | "repository": "https://github.com/noctisynth/archons.git", 19 | "license": "MIT", 20 | "keywords": [ 21 | "cli-tool", 22 | "rust" 23 | ], 24 | "files": [ 25 | "index.d.ts", 26 | "index.js" 27 | ], 28 | "napi": { 29 | "name": "archons-napi", 30 | "triples": { 31 | "defaults": true, 32 | "additional": [ 33 | "x86_64-unknown-linux-musl", 34 | "aarch64-unknown-linux-gnu", 35 | "i686-pc-windows-msvc", 36 | "armv7-unknown-linux-gnueabihf", 37 | "aarch64-apple-darwin", 38 | "aarch64-linux-android", 39 | "x86_64-unknown-freebsd", 40 | "aarch64-unknown-linux-musl", 41 | "aarch64-pc-windows-msvc", 42 | "armv7-linux-androideabi" 43 | ] 44 | }, 45 | "package": { 46 | "name": "archons-napi" 47 | } 48 | }, 49 | "engines": { 50 | "node": ">= 10" 51 | }, 52 | "publishConfig": { 53 | "registry": "https://registry.npmjs.org/", 54 | "access": "public" 55 | }, 56 | "scripts": { 57 | "watch": "node scripts/watch.mjs", 58 | "artifacts": "napi artifacts", 59 | "bench": "node --import @swc-node/register/esm-register benchmark/bench.ts", 60 | "build": "napi build --platform --release --pipe \"prettier -w\"", 61 | "build:examples": "tsc -p examples", 62 | "build:debug": "napi build --platform --pipe \"prettier -w\"", 63 | "format": "run-p format:prettier format:rs format:toml", 64 | "format:prettier": "prettier . -w", 65 | "format:toml": "taplo format", 66 | "format:rs": "cargo fmt", 67 | "lint": "oxlint .", 68 | "prepublishOnly": "napi prepublish -t npm --skip-gh-release", 69 | "test": "jest", 70 | "bump": "node scripts/bump.mjs", 71 | "prepare": "husky" 72 | }, 73 | "devDependencies": { 74 | "@babel/plugin-transform-typescript": "^7.27.0", 75 | "@babel/preset-env": "^7.26.9", 76 | "@babel/preset-typescript": "^7.27.0", 77 | "@changesets/cli": "^2.29.2", 78 | "@napi-rs/cli": "^2.18.4", 79 | "@taplo/cli": "^0.7.0", 80 | "@types/jest": "^29.5.14", 81 | "@types/node": "^22.14.1", 82 | "archons": "link:", 83 | "chalk": "^5.4.1", 84 | "husky": "^9.1.7", 85 | "jest": "^29.7.0", 86 | "lint-staged": "^15.5.1", 87 | "npm-run-all2": "^7.0.2", 88 | "oxlint": "^0.16.7", 89 | "prettier": "^3.5.3", 90 | "tinybench": "^4.0.1", 91 | "ts-node": "^10.9.2", 92 | "typescript": "^5.8.3" 93 | }, 94 | "lint-staged": { 95 | "*.@(js|ts|tsx)": [ 96 | "oxlint --fix" 97 | ], 98 | "*.@(js|ts|tsx|yml|yaml|md|json)": [ 99 | "prettier --write" 100 | ], 101 | "*.toml": [ 102 | "taplo format" 103 | ], 104 | "*.rs": [ 105 | "cargo fmt -- " 106 | ] 107 | }, 108 | "prettier": { 109 | "printWidth": 120, 110 | "semi": false, 111 | "trailingComma": "all", 112 | "singleQuote": true, 113 | "arrowParens": "always" 114 | }, 115 | "husky": { 116 | "hooks": { 117 | "pre-commit": "pnpm lint-staged" 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | tab_spaces = 2 2 | -------------------------------------------------------------------------------- /scripts/bump.mjs: -------------------------------------------------------------------------------- 1 | import { execSync } from 'node:child_process'; 2 | 3 | execSync('npx changeset version', { stdio: 'inherit' }); 4 | execSync('npx napi version', { stdio: 'inherit' }); 5 | execSync('pnpm install', { stdio: 'inherit' }); 6 | -------------------------------------------------------------------------------- /scripts/watch.mjs: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import { execSync } from 'node:child_process' 3 | import { fileURLToPath } from 'node:url' 4 | import path from 'node:path' 5 | import fs from 'node:fs' 6 | import { createSpinner } from '../index.js' 7 | import chalk from 'chalk' 8 | 9 | /** 10 | * @param {string} filename 11 | */ 12 | function isIgnored(filename) { 13 | for (const pattern of ignored) { 14 | if (filename.includes(pattern)) { 15 | return true 16 | } 17 | } 18 | return false 19 | } 20 | 21 | /** 22 | * @param {string} cmd 23 | * @param {string} msg 24 | * @param {import("../index.d.ts").ProgressBar} spinner 25 | */ 26 | function tryBuild(cmd, msg, spinner) { 27 | try { 28 | spinner.setMessage(`${msg}`) 29 | execSync(cmd, { stdio: 'inherit' }) 30 | } catch (e) { 31 | console.log(e) 32 | spinner.println('[Archons] Build failed.') 33 | } 34 | } 35 | 36 | function createArchonsSpinner() { 37 | const spinner = createSpinner() 38 | spinner.setTemplate('{prefix:.green} {spinner:.yellow} {msg}') 39 | spinner.setPrefix('[Archons]') 40 | return spinner 41 | } 42 | 43 | const dirPath = path.join(path.dirname(fileURLToPath(import.meta.url)), '..') 44 | const ignored = ['node_modules', '.git', '.github', '.vscode', 'dist', 'build', 'bin', '.md', '.d.ts', 'target', '.cjs'] 45 | const greenPrefix = chalk.green('[Archons]') 46 | 47 | const spinner = createArchonsSpinner(); 48 | spinner.enableSteadyTick(100) 49 | spinner.setMessage('Initial building...') 50 | tryBuild('pnpm build:debug', 'Building module...', spinner) 51 | tryBuild('pnpm build:examples', 'Building examples...', spinner) 52 | spinner.finishAndClear() 53 | console.clear() 54 | console.log(`${greenPrefix} Build complete.\n`) 55 | console.log(`${greenPrefix} Watching on ${chalk.cyan(dirPath)} for changes...`) 56 | 57 | let lastBuild = Date.now() 58 | fs.watch(dirPath, { recursive: true }, (eventType, filename) => { 59 | // debounce 60 | if (Date.now() - lastBuild < 1000) { 61 | return 62 | } 63 | if (filename && !isIgnored(filename)) { 64 | const spinner = createArchonsSpinner() 65 | spinner.println(`${greenPrefix} File ${chalk.cyan(filename)} was ${eventType}d, rebuilding...`) 66 | spinner.enableSteadyTick(100) 67 | if (filename.endsWith('.rs') || filename.endsWith('.toml')) { 68 | tryBuild('pnpm build:debug', 'Rebuilding module...', spinner) 69 | } 70 | tryBuild('pnpm build:examples', 'Rebuilding examples...', spinner) 71 | console.clear() 72 | spinner.println(`${greenPrefix} Build complete.\n\n`) 73 | spinner.println(`${greenPrefix} Watching for changes...`) 74 | spinner.finishAndClear() 75 | lastBuild = Date.now() 76 | } 77 | }) 78 | -------------------------------------------------------------------------------- /src/command.rs: -------------------------------------------------------------------------------- 1 | use napi::{Env, Result}; 2 | use napi_derive::napi; 3 | 4 | use crate::resolver::{resolve_command, resolve_option_args}; 5 | use crate::types::Command; 6 | use crate::utils::parse_arguments; 7 | 8 | /// Define a command functionally 9 | /// 10 | /// @param options Command options 11 | /// @returns {Command} 12 | #[napi] 13 | pub fn define_command(options: Command) -> Command { 14 | options 15 | } 16 | 17 | /// Run command 18 | /// 19 | /// **NOTE**: If the given `args` is empty, it will use `process.argv` 20 | /// (or `Deno.args` in Deno.js environment) instead. 21 | /// 22 | /// **NOTE**: The given `args` should include the nodejs executable and script name. 23 | /// For example, if you are running a script `index.js` in the current directory with 24 | /// a flag `--foo`, you should pass `["node", "index.js", "--foo"]` as `args`. 25 | /// 26 | /// @param cmd Command object 27 | /// @param args Run with given arguments 28 | /// @returns {void} 29 | #[napi(ts_args_type = "cmd: Command, args?: string[]")] 30 | pub fn run(env: Env, cmd: Command, argv: Option>) -> Result<()> { 31 | let raw_args = resolve_option_args(env, argv)?; 32 | let clap = resolve_command(clap::Command::default(), Default::default(), &cmd); 33 | let matches = clap.clone().get_matches_from(&raw_args); 34 | 35 | parse_arguments(env, &clap, cmd, &matches, raw_args) 36 | } 37 | -------------------------------------------------------------------------------- /src/inquire.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use inquire::validator::{ErrorMessage, StringValidator, Validation}; 4 | use napi::JsFunction; 5 | use napi_derive::napi; 6 | 7 | use crate::{ 8 | apply_opt, 9 | types::{Context, Error}, 10 | utils::{as_usize, leak_str, wrap_bool_formatter, wrap_bool_parser, wrap_string_formatter}, 11 | }; 12 | 13 | #[napi(object)] 14 | #[derive(Default)] 15 | pub struct SelectConfig { 16 | pub help_message: Option, 17 | pub page_size: Option, 18 | pub reset_cursor: Option, 19 | pub starting_cursor: Option, 20 | pub starting_filter_input: Option, 21 | pub vim_mode: Option, 22 | pub filtering: Option, 23 | pub help_message_disabled: Option, 24 | } 25 | 26 | #[napi] 27 | pub fn select( 28 | prompt: String, 29 | choices: Vec, 30 | config: Option, 31 | ) -> napi::Result { 32 | let mut inquire = inquire::Select::new(&prompt, choices); 33 | let config = config.unwrap_or_default(); 34 | 35 | apply_opt!(inquire, config, leak_str(help_message) => with_help_message); 36 | apply_opt!(inquire, config, as_usize(page_size) => with_page_size); 37 | apply_opt!(inquire, config, reset_cursor => with_reset_cursor); 38 | apply_opt!(inquire, config, as_usize(starting_cursor) => with_starting_cursor); 39 | apply_opt!(inquire, config, leak_str(starting_filter_input) => with_starting_filter_input); 40 | apply_opt!(inquire, config, vim_mode => with_vim_mode); 41 | 42 | if let Some(false) = config.filtering { 43 | inquire = inquire.without_filtering(); 44 | } 45 | if let Some(true) = config.help_message_disabled { 46 | inquire = inquire.without_help_message(); 47 | } 48 | 49 | Ok(inquire.prompt().map_err(Error::InquireError)?) 50 | } 51 | 52 | #[napi(object)] 53 | #[derive(Default)] 54 | pub struct CheckboxConfig { 55 | pub all_selected_by_default: Option, 56 | pub default: Option>, 57 | pub help_message: Option, 58 | pub keep_filter: Option, 59 | pub page_size: Option, 60 | pub reset_cursor: Option, 61 | pub starting_cursor: Option, 62 | pub starting_filter_input: Option, 63 | pub vim_mode: Option, 64 | pub filtering: Option, 65 | pub help_message_disabled: Option, 66 | } 67 | 68 | #[napi] 69 | pub fn checkbox( 70 | prompt: String, 71 | choices: Vec, 72 | config: Option, 73 | ) -> napi::Result> { 74 | let mut inquire = inquire::MultiSelect::new(&prompt, choices); 75 | let config = config.unwrap_or_default(); 76 | if let Some(true) = config.all_selected_by_default { 77 | inquire = inquire.with_all_selected_by_default(); 78 | } 79 | if let Some(default) = config.default { 80 | let default = Box::new( 81 | default 82 | .into_iter() 83 | .map(|i| i as usize) 84 | .collect::>(), 85 | ) 86 | .leak(); 87 | inquire = inquire.with_default(default); 88 | } 89 | // TODO: formatter 90 | // if let Some(formatter) = config.formatter { 91 | // } 92 | apply_opt!(inquire, config, leak_str(help_message) => with_help_message); 93 | apply_opt!(inquire, config, keep_filter => with_keep_filter); 94 | apply_opt!(inquire, config, as_usize(page_size) => with_page_size); 95 | apply_opt!(inquire, config, reset_cursor => with_reset_cursor); 96 | // TODO: scorer 97 | // if let Some(scorer) = config.scorer { 98 | // } 99 | apply_opt!(inquire, config, as_usize(starting_cursor) => with_starting_cursor); 100 | apply_opt!(inquire, config, leak_str(starting_filter_input) => with_starting_filter_input); 101 | // TODO: validator 102 | // if let Some(validator) = config.validator { 103 | // } 104 | apply_opt!(inquire, config, vim_mode => with_vim_mode); 105 | 106 | if let Some(false) = config.filtering { 107 | inquire = inquire.without_filtering(); 108 | } 109 | if let Some(true) = config.help_message_disabled { 110 | inquire = inquire.without_help_message(); 111 | } 112 | 113 | Ok(inquire.prompt().map_err(Error::InquireError)?) 114 | } 115 | 116 | #[napi(object)] 117 | #[derive(Default)] 118 | pub struct InputConfig { 119 | pub default: Option, 120 | #[napi(ts_type = "(value: string) => string")] 121 | pub formatter: Option, 122 | pub help_message: Option, 123 | pub initial_value: Option, 124 | pub page_size: Option, 125 | pub placeholder: Option, 126 | #[napi(ts_type = "((text: string) => StringValidatorResult)[]")] 127 | pub validators: Option>, 128 | } 129 | 130 | #[napi] 131 | pub fn input(prompt: String, config: Option) -> napi::Result { 132 | let mut inquire = inquire::Text::new(&prompt); 133 | let config = config.unwrap_or_default(); 134 | apply_opt!(inquire, config, leak_str(default) => with_default); 135 | apply_opt!(inquire, config, wrap_string_formatter(formatter) => with_formatter); 136 | apply_opt!(inquire, config, leak_str(help_message) => with_help_message); 137 | apply_opt!(inquire, config, leak_str(initial_value) => with_initial_value); 138 | apply_opt!(inquire, config, as_usize(page_size) => with_page_size); 139 | apply_opt!(inquire, config, leak_str(placeholder) => with_placeholder); 140 | if let Some(validators) = config.validators { 141 | let validators: Vec> = validators 142 | .into_iter() 143 | .map(|f| validator(Rc::new(f))) 144 | .collect(); 145 | inquire = inquire.with_validators(&validators); 146 | } 147 | Ok(inquire.prompt().map_err(Error::InquireError)?) 148 | } 149 | 150 | #[napi(object)] 151 | #[derive(Default)] 152 | pub struct ConfirmConfig { 153 | pub default: Option, 154 | #[napi(ts_type = "(value: boolean) => string")] 155 | pub default_value_formatter: Option, 156 | pub error_message: Option, 157 | #[napi(ts_type = "(value: boolean) => string")] 158 | pub formatter: Option, 159 | pub help_message: Option, 160 | #[napi(ts_type = "(value: boolean) => boolean")] 161 | pub parser: Option, 162 | pub placeholder: Option, 163 | pub starting_input: Option, 164 | } 165 | 166 | #[napi] 167 | pub fn confirm(prompt: String, config: Option) -> napi::Result { 168 | let mut inquire = inquire::Confirm::new(&prompt); 169 | let config = config.unwrap_or_default(); 170 | apply_opt!(inquire, config, default => with_default); 171 | apply_opt!(inquire, config, wrap_bool_formatter(default_value_formatter) => with_default_value_formatter); 172 | apply_opt!(inquire, config, leak_str(error_message) => with_error_message); 173 | apply_opt!(inquire, config, wrap_bool_formatter(formatter) => with_formatter); 174 | apply_opt!(inquire, config, leak_str(help_message) => with_help_message); 175 | apply_opt!(inquire, config, wrap_bool_parser(parser) => with_parser); 176 | apply_opt!(inquire, config, leak_str(placeholder) => with_placeholder); 177 | apply_opt!(inquire, config, leak_str(starting_input) => with_starting_input); 178 | Ok(inquire.prompt().map_err(Error::InquireError)?) 179 | } 180 | 181 | #[napi(object)] 182 | #[derive(Default)] 183 | pub struct PasswordConfig { 184 | pub custom_confirmation_error_message: Option, 185 | pub custom_confirmation_message: Option, 186 | #[napi(ts_type = "'hidden' | 'masked' | 'full'")] 187 | pub display_mode: Option, 188 | pub display_toggle: Option, 189 | pub help_message: Option, 190 | #[napi(ts_type = "(text: string) => string")] 191 | pub formatter: Option, 192 | #[napi(ts_type = "((text: string) => StringValidatorResult)[]")] 193 | pub validators: Option>, 194 | pub confirmation: Option, 195 | } 196 | 197 | #[napi(object)] 198 | pub struct StringValidatorResult { 199 | #[napi(ts_type = "'valid' | 'invalid'")] 200 | pub validation: String, 201 | pub err_msg: Option, 202 | } 203 | 204 | fn validator(f: Rc) -> Box { 205 | Box::new(move |text: &str| { 206 | let res: StringValidatorResult = f.call1(text)?; 207 | Ok(match res.validation.as_str() { 208 | "valid" => Validation::Valid, 209 | "invalid" => Validation::Invalid(ErrorMessage::Custom( 210 | res.err_msg.unwrap_or("invalid".to_string()), 211 | )), 212 | _ => panic!("invalid validation result"), 213 | }) 214 | }) 215 | } 216 | 217 | #[napi] 218 | pub fn password(prompt: String, config: Option) -> napi::Result { 219 | let mut inquire = inquire::Password::new(&prompt); 220 | let config = config.unwrap_or_default(); 221 | apply_opt!(inquire, config, leak_str(custom_confirmation_error_message) => with_custom_confirmation_error_message); 222 | apply_opt!(inquire, config, leak_str(custom_confirmation_message) => with_custom_confirmation_message); 223 | if let Some(display_mode) = config.display_mode { 224 | inquire = inquire.with_display_mode(match display_mode.as_str() { 225 | "hidden" => inquire::PasswordDisplayMode::Full, 226 | "masked" => inquire::PasswordDisplayMode::Masked, 227 | "full" => inquire::PasswordDisplayMode::Hidden, 228 | _ => { 229 | return Err( 230 | Error::InquireError(inquire::InquireError::InvalidConfiguration( 231 | "invalid password display mode".to_string(), 232 | )) 233 | .into(), 234 | ); 235 | } 236 | }); 237 | } 238 | if let Some(true) = config.display_toggle { 239 | inquire = inquire.with_display_toggle_enabled(); 240 | } 241 | apply_opt!(inquire, config, leak_str(help_message) => with_help_message); 242 | apply_opt!(inquire, config, wrap_string_formatter(formatter) => with_formatter); 243 | if let Some(false) = config.confirmation { 244 | inquire = inquire.without_confirmation(); 245 | } 246 | if let Some(validators) = config.validators { 247 | let validators: Vec> = validators 248 | .into_iter() 249 | .map(|f| validator(Rc::new(f))) 250 | .collect(); 251 | inquire = inquire.with_validators(&validators); 252 | } 253 | Ok(inquire.prompt().map_err(Error::InquireError)?) 254 | } 255 | 256 | #[napi] 257 | impl Context { 258 | #[napi] 259 | pub fn ask(&self, prompt: String, config: Option) -> napi::Result { 260 | input(prompt, config) 261 | } 262 | 263 | #[napi] 264 | pub fn confirm(&self, prompt: String, config: Option) -> napi::Result { 265 | confirm(prompt, config) 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod command; 2 | pub mod inquire; 3 | pub mod progressbar; 4 | pub mod resolver; 5 | pub mod types; 6 | pub mod utils; 7 | 8 | pub type HashMap = rustc_hash::FxHashMap; 9 | 10 | #[macro_export] 11 | macro_rules! apply_opt { 12 | ($arg:ident, $opt:ident, $field:ident => $method:ident) => { 13 | if let Some(val) = $opt.$field { 14 | $arg = $arg.$method(val); 15 | } 16 | }; 17 | ($arg:ident, $opt:ident, &$field:ident => $method:ident) => { 18 | if let Some(val) = &$opt.$field { 19 | $arg = $arg.$method(val); 20 | } 21 | }; 22 | ($arg:ident, $opt:ident, $wrapper:ident($field:ident) => $method:ident) => { 23 | if let Some(val) = $opt.$field { 24 | $arg = $arg.$method($wrapper(val)); 25 | } 26 | }; 27 | ($arg:ident, $opt:ident, $wrapper:ident(&$field:ident) => $method:ident) => { 28 | if let Some(val) = &$opt.$field { 29 | $arg = $arg.$method($wrapper(val)); 30 | } 31 | }; 32 | ($arg:ident, $opt:ident, $wrapper:ident!($field:ident) => $method:ident) => { 33 | if let Some(val) = $opt.$field { 34 | $arg = $arg.$method($wrapper!(val)); 35 | } 36 | }; 37 | ($arg:ident, $opt:ident, $wrapper:ident!(&$field:ident) => $method:ident) => { 38 | if let Some(val) = &$opt.$field { 39 | $arg = $arg.$method($wrapper!(val)); 40 | } 41 | }; 42 | ($arg:ident, $opt:ident, $field:ident) => { 43 | apply_opt!($arg, $opt, $field => $field); 44 | }; 45 | ($arg:ident, $opt:ident, &$field:ident) => { 46 | apply_opt!($arg, $opt, &$field => $field); 47 | }; 48 | } 49 | 50 | #[macro_export] 51 | macro_rules! to_char_vec { 52 | ($vec:ident) => { 53 | $vec 54 | .into_iter() 55 | .map(|c| c.chars().next().unwrap()) 56 | .collect::>() 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /src/progressbar.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use napi_derive::napi; 4 | 5 | use crate::types::{Context, Error}; 6 | 7 | #[napi] 8 | pub struct ProgressBar { 9 | bar: indicatif::ProgressBar, 10 | } 11 | 12 | #[napi] 13 | impl ProgressBar { 14 | #[napi] 15 | pub fn finish(&self) { 16 | self.bar.finish(); 17 | } 18 | 19 | #[napi] 20 | pub fn finish_and_clear(&self) { 21 | self.bar.finish_and_clear(); 22 | } 23 | 24 | #[napi] 25 | pub fn finish_using_style(&self) { 26 | self.bar.finish_using_style(); 27 | } 28 | 29 | #[napi] 30 | pub fn finish_with_message(&self, msg: String) { 31 | self.bar.finish_with_message(msg); 32 | } 33 | 34 | #[napi] 35 | pub fn set_position(&self, pos: u32) { 36 | self.bar.set_position(pos as u64); 37 | } 38 | 39 | #[napi] 40 | pub fn set_length(&self, len: u32) { 41 | self.bar.set_length(len as u64); 42 | } 43 | 44 | #[napi] 45 | pub fn set_message(&self, msg: String) { 46 | self.bar.set_message(msg); 47 | } 48 | 49 | #[napi] 50 | pub fn set_prefix(&self, prefix: String) { 51 | self.bar.set_prefix(prefix); 52 | } 53 | 54 | #[napi] 55 | pub fn set_tab_width(&mut self, width: u32) { 56 | self.bar.set_tab_width(width as usize); 57 | } 58 | 59 | #[napi] 60 | pub fn set_template(&mut self, template: String) -> napi::Result<()> { 61 | self.bar.set_style( 62 | self 63 | .bar 64 | .style() 65 | .template(&template) 66 | .map_err(Error::IndicatifTemplateError)?, 67 | ); 68 | Ok(()) 69 | } 70 | 71 | #[napi] 72 | pub fn set_tick_strings(&self, s: Vec) { 73 | self.bar.set_style( 74 | self 75 | .bar 76 | .style() 77 | .tick_strings(&s.iter().map(|s| s.as_str()).collect::>()), 78 | ); 79 | } 80 | 81 | #[napi] 82 | pub fn set_progress_chars(&self, s: String) { 83 | self 84 | .bar 85 | .set_style(self.bar.style().progress_chars(s.as_str())); 86 | } 87 | 88 | #[napi] 89 | pub fn tick(&self) { 90 | self.bar.tick(); 91 | } 92 | 93 | #[napi] 94 | pub fn abandon(&self) { 95 | self.bar.abandon(); 96 | } 97 | 98 | #[napi] 99 | pub fn abandon_with_message(&self, msg: String) { 100 | self.bar.abandon_with_message(msg); 101 | } 102 | 103 | #[napi] 104 | pub fn inc(&self, delta: u32) { 105 | self.bar.inc(delta as u64); 106 | } 107 | 108 | #[napi] 109 | pub fn inc_length(&self, delta: u32) { 110 | self.bar.inc_length(delta as u64); 111 | } 112 | 113 | #[napi] 114 | pub fn reset(&self) { 115 | self.bar.reset(); 116 | } 117 | 118 | #[napi] 119 | pub fn println(&self, msg: String) { 120 | self.bar.println(msg); 121 | } 122 | 123 | #[napi] 124 | pub fn suspend napi::Result<()>>(&self, f: F) -> napi::Result<()> { 125 | self.bar.suspend(f) 126 | } 127 | 128 | #[napi] 129 | pub fn enable_steady_tick(&self, ms: u32) { 130 | self 131 | .bar 132 | .enable_steady_tick(Duration::from_millis(ms as u64)); 133 | } 134 | 135 | #[napi] 136 | pub fn disable_steady_tick(&self) { 137 | self.bar.disable_steady_tick(); 138 | } 139 | } 140 | 141 | #[napi] 142 | impl Context { 143 | #[napi] 144 | pub fn create_progress_bar(&self, total: u32) -> ProgressBar { 145 | let bar = indicatif::ProgressBar::new(total as u64); 146 | ProgressBar { bar } 147 | } 148 | 149 | #[napi] 150 | pub fn create_spinner(&self) -> ProgressBar { 151 | let bar = indicatif::ProgressBar::new_spinner(); 152 | ProgressBar { bar } 153 | } 154 | } 155 | 156 | /// Creates a new progress bar with the specified total number of steps. 157 | /// 158 | /// # Arguments 159 | /// 160 | /// * `total` - The total number of steps for the progress bar. 161 | /// 162 | /// # Returns 163 | /// 164 | /// A new `ProgressBar` instance. 165 | #[napi] 166 | pub fn create_progress_bar(total: u32) -> ProgressBar { 167 | let bar = indicatif::ProgressBar::new(total as u64); 168 | ProgressBar { bar } 169 | } 170 | 171 | /// Creates a new spinner progress bar. 172 | /// 173 | /// # Returns 174 | /// 175 | /// A new `ProgressBar` instance with a spinner style. 176 | #[napi] 177 | pub fn create_spinner() -> ProgressBar { 178 | let bar = indicatif::ProgressBar::new_spinner(); 179 | ProgressBar { bar } 180 | } 181 | -------------------------------------------------------------------------------- /src/resolver.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | apply_opt, to_char_vec, 3 | types::{Command, CommandMeta, CommandOption}, 4 | utils::{leak_borrowed_str, leak_borrowed_str_or_default, leak_str}, 5 | HashMap, 6 | }; 7 | 8 | pub(crate) fn resolve_option_args( 9 | env: napi::Env, 10 | args: Option>, 11 | ) -> napi::Result> { 12 | if let Some(mut args) = args { 13 | args.remove(0); 14 | return Ok(args); 15 | } 16 | 17 | let global = env.get_global()?; 18 | if let Ok(deno) = global.get_named_property::("Deno") { 19 | let mut args = vec!["deno".to_string()]; 20 | args.extend(deno.get_named_property::>("args")?); 21 | return Ok(args); 22 | } 23 | 24 | let mut args = global 25 | .get_named_property::("process")? 26 | .get_named_property::>("argv")?; 27 | args.remove(0); 28 | Ok(args) 29 | } 30 | 31 | pub(crate) fn resolve_command_meta( 32 | mut clap: clap::Command, 33 | bin_name: Option, 34 | meta: &CommandMeta, 35 | ) -> clap::Command { 36 | let name: &'static str = meta.name.as_ref().map_or_else( 37 | || leak_str(bin_name.expect("bin_name must be provided")), 38 | |name| leak_borrowed_str(name), 39 | ); 40 | clap = clap.name(name); 41 | 42 | apply_opt!(clap, meta, leak_borrowed_str(&version) => version); 43 | apply_opt!(clap, meta, leak_borrowed_str(&about) => about); 44 | apply_opt!(clap, meta, subcommand_required); 45 | 46 | if meta.styled.unwrap_or(false) { 47 | use clap::builder::styling; 48 | let styles = styling::Styles::styled() 49 | .header(styling::AnsiColor::Green.on_default() | styling::Effects::BOLD) 50 | .usage(styling::AnsiColor::Green.on_default() | styling::Effects::BOLD) 51 | .literal(styling::AnsiColor::Cyan.on_default() | styling::Effects::BOLD) 52 | .placeholder(styling::AnsiColor::Cyan.on_default()); 53 | clap = clap.styles(styles); 54 | } 55 | 56 | clap 57 | } 58 | 59 | pub(crate) fn resolve_action( 60 | action: &Option, 61 | r#type: &Option, 62 | parser: &Option, 63 | ) -> clap::ArgAction { 64 | let r#type = r#type.as_deref().unwrap_or("option"); 65 | match action.as_deref() { 66 | Some("set") => clap::ArgAction::Set, 67 | Some("append") => clap::ArgAction::Append, 68 | Some("count") => clap::ArgAction::Count, 69 | Some("store") => clap::ArgAction::SetTrue, 70 | Some("store_false") => clap::ArgAction::SetFalse, 71 | None => match r#type { 72 | "option" | "positional" => match parser.as_deref() { 73 | Some("boolean") => clap::ArgAction::SetTrue, 74 | Some("number") | Some("string") | None => clap::ArgAction::Set, 75 | _ => panic!("Unsupported parser: {:?}", parser), 76 | }, 77 | _ => panic!("Unsupported type: {:?}", r#type), 78 | }, 79 | _ => panic!("Unsupported action: {:?}", action), 80 | } 81 | } 82 | 83 | pub(crate) fn resolve_parser( 84 | parser: Option<&str>, 85 | action: Option<&str>, 86 | ) -> clap::builder::ValueParser { 87 | match parser { 88 | Some("string") => clap::builder::ValueParser::string(), 89 | Some("number") => clap::value_parser!(i64).into(), 90 | Some("boolean") => clap::builder::ValueParser::bool(), 91 | None => match action { 92 | Some("store") | Some("store_false") => clap::builder::ValueParser::bool(), 93 | Some("count") => clap::value_parser!(u64).into(), 94 | Some("append") => clap::builder::ValueParser::string(), 95 | Some("set") => clap::builder::ValueParser::string(), 96 | None => clap::builder::ValueParser::string(), 97 | _ => panic!("Unsupported action: {:?}", action), 98 | }, 99 | _ => panic!("Unsupported parser: {:?}", parser), 100 | } 101 | } 102 | 103 | pub(crate) fn resolve_value_hint(value_hint: &str) -> clap::builder::ValueHint { 104 | match value_hint { 105 | "any_path" => clap::builder::ValueHint::AnyPath, 106 | "file" => clap::builder::ValueHint::FilePath, 107 | "dir" => clap::builder::ValueHint::DirPath, 108 | "executable" => clap::builder::ValueHint::ExecutablePath, 109 | "cmd_name" => clap::builder::ValueHint::CommandName, 110 | "cmd" => clap::builder::ValueHint::CommandString, 111 | "cmd_with_args" => clap::builder::ValueHint::CommandWithArguments, 112 | "url" => clap::builder::ValueHint::Url, 113 | "username" => clap::builder::ValueHint::Username, 114 | "hostname" => clap::builder::ValueHint::Hostname, 115 | "email" => clap::builder::ValueHint::EmailAddress, 116 | _ => panic!("Unsupported value_hint: {:?}", value_hint), 117 | } 118 | } 119 | 120 | pub(crate) fn resolve_num_args(num_args: &str) -> clap::builder::ValueRange { 121 | if let Ok(n) = num_args.parse::() { 122 | return n.into(); 123 | } 124 | let (start, end) = num_args.split_once("..").expect("Invalid num_args"); 125 | match (start, end) { 126 | ("", "") => (..).into(), 127 | ("", end) => { 128 | if end.starts_with("=") { 129 | let end: usize = end 130 | .strip_prefix("=") 131 | .unwrap() 132 | .parse() 133 | .expect("Invalid end of range"); 134 | (..=end).into() 135 | } else { 136 | let end: usize = end.parse().expect("Invalid end of range"); 137 | (..end).into() 138 | } 139 | } 140 | (start, "") => { 141 | let start: usize = start.parse().expect("Invalid start of range"); 142 | (start..).into() 143 | } 144 | (start, end) => { 145 | let start: usize = start.parse().expect("Invalid start of range"); 146 | if end.starts_with("=") { 147 | let end: usize = end 148 | .strip_prefix("=") 149 | .unwrap() 150 | .parse() 151 | .expect("Invalid end of range"); 152 | (start..=end).into() 153 | } else { 154 | let end: usize = end.parse().expect("Invalid end of range"); 155 | (start..end).into() 156 | } 157 | } 158 | } 159 | } 160 | 161 | pub(crate) fn resolve_command_options( 162 | clap: clap::Command, 163 | meta: &HashMap, 164 | ) -> clap::Command { 165 | clap.args( 166 | meta 167 | .iter() 168 | .map(|(name, opt)| { 169 | let mut arg = clap::Arg::new(leak_borrowed_str(name)); 170 | arg = arg.action(resolve_action(&opt.action, &opt.r#type, &opt.parser)); 171 | if opt.r#type.as_deref() != Some("positional") { 172 | let long = leak_borrowed_str_or_default(opt.long.as_ref(), name); 173 | arg = arg.long(long).short( 174 | leak_borrowed_str_or_default(opt.short.as_ref(), long) 175 | .chars() 176 | .next(), 177 | ); 178 | } 179 | arg = arg.value_parser(resolve_parser(opt.parser.as_deref(), opt.action.as_deref())); 180 | apply_opt!(arg, opt, &alias => visible_aliases); 181 | apply_opt!(arg, opt, &hidden_alias => aliases); 182 | apply_opt!(arg, opt, to_char_vec!(&short_alias) => short_aliases); 183 | apply_opt!(arg, opt, to_char_vec!(&hidden_short_alias) => short_aliases); 184 | apply_opt!(arg, opt, resolve_value_hint(&value_hint) => value_hint); 185 | apply_opt!(arg, opt, &help); 186 | apply_opt!(arg, opt, required); 187 | apply_opt!(arg, opt, default => default_value); 188 | apply_opt!(arg, opt, default_missing => default_missing_value); 189 | apply_opt!(arg, opt, resolve_num_args(num_args) => num_args); 190 | apply_opt!(arg, opt, required_equals => require_equals); 191 | apply_opt!(arg, opt, hidden => hide); 192 | apply_opt!(arg, opt, global); 193 | apply_opt!(arg, opt, exclusive); 194 | apply_opt!(arg, opt, &conflicts_with => conflicts_with_all); 195 | apply_opt!(arg, opt, hide_default_value); 196 | arg 197 | }) 198 | .collect::>(), 199 | ) 200 | } 201 | 202 | pub(crate) fn resolve_command( 203 | mut clap: clap::Command, 204 | name: String, 205 | cmd: &Command, 206 | ) -> clap::Command { 207 | clap = resolve_command_meta(clap, Some(name), &cmd.meta); 208 | clap = resolve_command_options(clap, &cmd.options); 209 | if let Some(subcommands) = &cmd.subcommands { 210 | clap = clap.subcommands( 211 | subcommands 212 | .iter() 213 | .map(|(name, sub_cmd)| resolve_command(clap::Command::default(), name.clone(), sub_cmd)) 214 | .collect::>(), 215 | ); 216 | } 217 | clap 218 | } 219 | -------------------------------------------------------------------------------- /src/types.rs: -------------------------------------------------------------------------------- 1 | use napi::{JsFunction, JsObject}; 2 | use napi_derive::napi; 3 | use thiserror::Error; 4 | 5 | use crate::HashMap; 6 | 7 | /// Command context 8 | /// 9 | /// This is the context object that is passed to the command callback. 10 | #[napi] 11 | pub struct Context { 12 | /// Parsed arguments 13 | /// 14 | /// This is a js object that contains the parsed arguments. 15 | /// The keys of the object are the names of the arguments and 16 | /// the values are the parsed values. 17 | args: JsObject, 18 | /// Raw arguments 19 | /// 20 | /// The raw arguments parsed by command line or manually given. 21 | #[napi(ts_type = "string[]")] 22 | pub raw_args: Vec, 23 | } 24 | 25 | #[napi] 26 | impl Context { 27 | /// Create a new command context 28 | /// 29 | /// This method is used to create a new command context, 30 | /// and is not intended to be used directly. 31 | /// 32 | /// @param args - Parsed arguments 33 | /// @param raw_args - Raw arguments 34 | #[napi( 35 | constructor, 36 | ts_args_type = "args: Record, raw_args: string[]" 37 | )] 38 | pub fn new(args: JsObject, raw_args: Vec) -> Self { 39 | Self { args, raw_args } 40 | } 41 | 42 | /// Get the parsed arguments 43 | #[napi(getter, ts_return_type = "Record")] 44 | pub fn args(&self) -> &JsObject { 45 | &self.args 46 | } 47 | 48 | /// Get the raw arguments 49 | #[napi(ts_return_type = "string[]")] 50 | pub fn get_raw_args(&self) -> &Vec { 51 | &self.raw_args 52 | } 53 | 54 | /// Get the argument value by specified key 55 | #[napi(ts_return_type = "any")] 56 | pub fn get(&self, key: String) -> napi::Result { 57 | self.args.get_named_property(&key) 58 | } 59 | } 60 | 61 | /// Command metadata 62 | #[napi(object)] 63 | #[derive(Clone)] 64 | pub struct CommandMeta { 65 | /// Command name 66 | /// 67 | /// This is the name of the command that will be used to call it from the CLI. 68 | /// If the command is the main command, the name will be the name of the binary. 69 | /// If the command is a subcommand, the name will be the name of the subcommand. 70 | pub name: Option, 71 | /// CLI version 72 | /// 73 | /// This is optional and can be used to display the version of the CLI 74 | /// when the command is called with the `--version` flag or `-V` option. 75 | /// 76 | /// If not provided, the CLI will not display the version and you can't 77 | /// call the command with the `--version` flag or `-V` option. 78 | pub version: Option, 79 | /// Command description 80 | /// 81 | /// Command description will be displayed in the help output. 82 | pub about: Option, 83 | /// Enable styled mode 84 | /// 85 | /// Determines whether the CLI output should be displayed in the styled format. 86 | pub styled: Option, 87 | /// Subcommand required 88 | /// 89 | /// If true, the command will fail if no subcommand is provided. 90 | pub subcommand_required: Option, 91 | } 92 | 93 | #[napi(object)] 94 | #[derive(Clone)] 95 | pub struct CommandOption { 96 | /// Option type for argument 97 | /// 98 | /// `type` option and `action` option are used to specify how to parse the argument. 99 | /// 100 | /// - `option` and (`store` or `store_false`): Boolean flag 101 | /// - `option` and `count`: Counter flag 102 | /// - `option` and `set`: Option flag 103 | /// - `option` and `append`: Multiple option flag 104 | /// - `positional` and `set`: Positional argument 105 | /// - `positional` and `append`: Multiple positional argument 106 | /// 107 | /// Defaults to `option` if not specified. 108 | #[napi(ts_type = "'positional' | 'option'")] 109 | pub r#type: Option, 110 | /// Specify the value type for the argument. 111 | #[napi(ts_type = "'string' | 'number' | 'boolean'")] 112 | pub parser: Option, 113 | /// Specify how to react to an argument when parsing it. 114 | /// 115 | /// - `set`: Overwrite previous values with new ones 116 | /// - `append`: Append new values to all previous ones 117 | /// - `count`: Count how many times a flag occurs 118 | /// - `store`: Store the value as a boolean flag 119 | /// - `store_false`: Store the value as a boolean flag with opposite meaning 120 | /// 121 | /// Defaults to `set` if not specified. 122 | #[napi(ts_type = "'set' | 'append' | 'count' | 'store' | 'store_false'")] 123 | pub action: Option, 124 | /// Short option name 125 | /// 126 | /// This is a single character that can be used to represent the option 127 | /// in the command line. For example, `-v` for the `--verbose` option. 128 | /// Panics if the length of the string is empty. If the size of string 129 | /// is greater than 1, the first character will be used as the short option. 130 | /// 131 | /// This option will be ignored if option `type` is not `option`. 132 | /// 133 | /// Defaults to the first character of the long option name. 134 | pub short: Option, 135 | /// Long option name 136 | /// 137 | /// This is the name of the option that will be used to represent the option, 138 | /// preceded by two dashes. For example, `--verbose` option. 139 | /// 140 | /// This option will be ignored if option `type` is not `option`. 141 | /// 142 | /// Defaults to the name of the argument. 143 | pub long: Option, 144 | /// Option aliases 145 | pub alias: Option>, 146 | /// Hidden option aliases 147 | pub hidden_alias: Option>, 148 | /// Short option aliases 149 | pub short_alias: Option>, 150 | /// Hidden short option aliases 151 | pub hidden_short_alias: Option>, 152 | /// Value hint for shell completion 153 | /// 154 | /// Provide the shell a hint about how to complete this argument. 155 | /// 156 | /// **Warning**: this will implicitly set `action` to `set`. 157 | #[napi(ts_type = r#" 158 | | 'any_path' 159 | | 'file' 160 | | 'dir' 161 | | 'executable' 162 | | 'cmd_name' 163 | | 'cmd' 164 | | 'cmd_with_args' 165 | | 'url' 166 | | 'username' 167 | | 'hostname' 168 | | 'email'"#)] 169 | pub value_hint: Option, 170 | /// Option description 171 | pub help: Option<&'static str>, 172 | /// Required argument 173 | /// 174 | /// If true, the argument is required and the command will fail without it. 175 | pub required: Option, 176 | /// Value for the argument when not present 177 | pub default: Option<&'static str>, 178 | /// Value for the argument when the flag is present but no value is specified. 179 | /// 180 | /// This configuration option is often used to give the user a shortcut and 181 | /// allow them to efficiently specify an option argument without requiring an 182 | /// explicitly value. The `--color` argument is a common example. By supplying 183 | /// a default, such as `default_missing_value("always")`, the user can quickly 184 | /// just add `--color` to the command line to produce the desired color output. 185 | pub default_missing: Option<&'static str>, 186 | /// Limit the count of values for the argument 187 | /// 188 | /// If the expected number of parameters required is a fixed value, pass in the 189 | /// number directly. If you want to limit the number of values to a range, for 190 | /// example, pass `1..5` or `1..=4` to specify a range from 1 to 4 inclusive. 191 | pub num_args: Option<&'static str>, 192 | /// Requires that options use the `--option=val` syntax 193 | pub required_equals: Option, 194 | /// Hide argument in help output 195 | /// 196 | /// Do not display the argument in the help message. 197 | pub hidden: Option, 198 | /// Global argument 199 | /// 200 | /// Specifies that an argument can be matched to all child subcommands 201 | pub global: Option, 202 | /// Options that conflict with this argument 203 | /// 204 | /// This argument is mutually exclusive with the specified arguments. 205 | pub conflicts_with: Option>, 206 | /// Exclusive argument 207 | /// 208 | /// This argument must be passed alone; it conflicts with all other arguments. 209 | pub exclusive: Option, 210 | /// Hide default value in help output 211 | /// 212 | /// Do not display the default value of the argument in the help message. 213 | /// 214 | /// This is useful when default behavior of an arg is explained elsewhere 215 | /// in the help text. 216 | pub hide_default_value: Option, 217 | } 218 | 219 | /// Command definition 220 | /// 221 | /// This is the object that defines a command. 222 | /// It contains the metadata, options, and callback function. 223 | #[napi(object)] 224 | pub struct Command { 225 | pub meta: CommandMeta, 226 | pub options: HashMap, 227 | #[napi(ts_type = "(ctx: Context) => void")] 228 | pub callback: Option, 229 | pub subcommands: Option>, 230 | } 231 | 232 | /// Errors 233 | /// 234 | /// This is the error type that is thrown to Node.js. 235 | #[derive(Error, Debug)] 236 | pub enum Error { 237 | #[error("Indicatif template error: {0}")] 238 | IndicatifTemplateError(#[from] indicatif::style::TemplateError), 239 | #[error("Inquire error: {0}")] 240 | InquireError(#[from] inquire::InquireError), 241 | } 242 | 243 | impl From for napi::Error { 244 | fn from(err: Error) -> napi::Error { 245 | napi::Error::from_reason(err.to_string()) 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /src/utils.rs: -------------------------------------------------------------------------------- 1 | use napi::{Env, JsNull, JsObject}; 2 | 3 | use crate::types::{Command, Context}; 4 | use crate::HashMap; 5 | 6 | const ISSUE_LINK: &str = "https://github.com/noctisynth/archons/issues"; 7 | 8 | #[inline] 9 | pub(crate) fn leak_str<'a>(s: String) -> &'a str { 10 | s.leak() 11 | } 12 | 13 | #[inline] 14 | pub(crate) fn leak_borrowed_str<'a>(s: &str) -> &'a str { 15 | unsafe { std::mem::transmute(s) } 16 | } 17 | 18 | #[inline] 19 | pub(crate) fn leak_borrowed_str_or_default<'a>(s: Option<&String>, default: &str) -> &'a str { 20 | s.map_or_else(|| leak_borrowed_str(default), |s| leak_borrowed_str(s)) 21 | } 22 | 23 | #[inline(always)] 24 | pub fn as_usize(num: u32) -> usize { 25 | num as usize 26 | } 27 | 28 | #[inline] 29 | pub fn wrap_string_formatter<'a>(formatter: napi::JsFunction) -> &'a (dyn Fn(&str) -> String + 'a) { 30 | let formatter = Box::new(move |value: &str| { 31 | let res: String = formatter.call1(value).unwrap_or(value.to_string()); 32 | res 33 | }); 34 | Box::leak(formatter) 35 | } 36 | 37 | #[inline] 38 | pub fn wrap_bool_formatter<'a>(formatter: napi::JsFunction) -> &'a (dyn Fn(bool) -> String + 'a) { 39 | let formatter = Box::new(move |value: bool| { 40 | let res: String = formatter.call1(value).unwrap_or(value.to_string()); 41 | res 42 | }); 43 | Box::leak(formatter) 44 | } 45 | 46 | #[inline] 47 | pub fn wrap_bool_parser<'a>( 48 | parser: napi::JsFunction, 49 | ) -> &'a (dyn Fn(&str) -> Result + 'a) { 50 | let parser = Box::new(move |value: &str| { 51 | let res: bool = parser.call1(value).unwrap_or(false); 52 | Ok(res) 53 | }); 54 | Box::leak(parser) 55 | } 56 | 57 | pub(crate) fn merge_args_matches( 58 | parsed_args: &mut JsObject, 59 | args: &[&clap::Arg], 60 | options: &HashMap, 61 | matches: &clap::ArgMatches, 62 | ) -> napi::Result<()> { 63 | for id in matches.ids() { 64 | let action = args 65 | .iter() 66 | .find(|arg| arg.get_id() == id) 67 | .map(|arg| arg.get_action()) 68 | .unwrap_or_else(|| { 69 | panic!( 70 | "Argument not found when merging matches, this is likely a internal bug.\n 71 | If you convinced this is a bug, report it at: {}", 72 | ISSUE_LINK 73 | ) 74 | }); 75 | let option: &str = options.get(id.as_str()).unwrap(); 76 | match action { 77 | clap::ArgAction::Set => match option { 78 | "string" => { 79 | parsed_args.set(id, matches.get_one::(id.as_str()).unwrap())?; 80 | } 81 | "number" => { 82 | parsed_args.set(id, *matches.get_one::(id.as_str()).unwrap())?; 83 | } 84 | _ => panic!("Invalid option type: {}", option), 85 | }, 86 | clap::ArgAction::SetTrue | clap::ArgAction::SetFalse => { 87 | parsed_args.set(id, matches.get_flag(id.as_str()))?; 88 | } 89 | clap::ArgAction::Count => { 90 | parsed_args.set(id, matches.get_count(id.as_str()))?; 91 | } 92 | clap::ArgAction::Append => match option { 93 | "string" => { 94 | parsed_args.set( 95 | id, 96 | matches 97 | .get_many::(id.as_str()) 98 | .unwrap_or_default() 99 | .map(|v| v.as_str()) 100 | .collect::>(), 101 | )?; 102 | } 103 | "number" => { 104 | parsed_args.set( 105 | id, 106 | matches 107 | .get_many::(id.as_str()) 108 | .unwrap_or_default() 109 | .copied() 110 | .collect::>(), 111 | )?; 112 | } 113 | _ => panic!("Invalid option type: {}", option), 114 | }, 115 | _ => panic!("Unsupported argument action: {:?}", action), 116 | } 117 | } 118 | Ok(()) 119 | } 120 | 121 | #[allow(clippy::too_many_arguments)] 122 | pub(crate) fn parse_arguments_inner<'arg>( 123 | env: Env, 124 | mut parsed_args: JsObject, 125 | clap: &'arg clap::Command, 126 | cmd: Command, 127 | matches: &clap::ArgMatches, 128 | raw_args: Vec, 129 | mut global_options: HashMap, 130 | mut global_args: Vec<&'arg clap::Arg>, 131 | ) -> napi::Result<()> { 132 | let mut options: HashMap = HashMap::default(); 133 | options.extend(global_options.clone()); 134 | 135 | for (name, option) in &cmd.options { 136 | let parser = leak_borrowed_str_or_default(option.parser.as_ref(), "string"); 137 | options.entry(name.to_string()).or_insert(parser); 138 | if option.global.unwrap_or(false) { 139 | global_options.entry(name.to_string()).or_insert(parser); 140 | } 141 | } 142 | 143 | let mut args = clap.get_arguments().collect::>(); 144 | args.extend(global_args.clone()); 145 | 146 | let global_args_this = clap 147 | .get_arguments() 148 | .filter(|arg| arg.is_global_set()) 149 | .collect::>(); 150 | global_args.extend(global_args_this); 151 | 152 | merge_args_matches(&mut parsed_args, &args, &options, matches)?; 153 | 154 | if let Some((sub_command_name, sub_matches)) = matches.subcommand() { 155 | let mut sub_commands = cmd.subcommands.unwrap_or_default(); 156 | let sub_command_def = sub_commands.remove(sub_command_name).unwrap(); 157 | 158 | let sub_command = clap 159 | .get_subcommands() 160 | .find(|&sub_command| sub_command.get_name() == sub_command_name) 161 | .unwrap(); 162 | 163 | parse_arguments_inner( 164 | env, 165 | parsed_args, 166 | sub_command, 167 | sub_command_def, 168 | sub_matches, 169 | raw_args, 170 | global_options, 171 | global_args, 172 | )?; 173 | } else { 174 | let context = Context::new(parsed_args, raw_args); 175 | if let Some(cb) = cmd.callback.as_ref() { 176 | cb.call1::(context)?; 177 | } else { 178 | env.throw_error( 179 | "No callback function found for main command and no subcommand was provided.", 180 | Some("E_NO_CALLBACK"), 181 | )?; 182 | }; 183 | }; 184 | Ok(()) 185 | } 186 | 187 | pub(crate) fn parse_arguments( 188 | env: Env, 189 | clap: &clap::Command, 190 | cmd: Command, 191 | matches: &clap::ArgMatches, 192 | raw_args: Vec, 193 | ) -> napi::Result<()> { 194 | let parsed_args = env.create_object()?; 195 | 196 | parse_arguments_inner( 197 | env, 198 | parsed_args, 199 | clap, 200 | cmd, 201 | matches, 202 | raw_args, 203 | HashMap::default(), 204 | Vec::new(), 205 | ) 206 | } 207 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "strict": true, 5 | "moduleResolution": "node", 6 | "module": "CommonJS", 7 | "noUnusedLocals": true, 8 | "noUnusedParameters": true, 9 | "esModuleInterop": true, 10 | "allowSyntheticDefaultImports": true 11 | }, 12 | "include": ["."], 13 | "exclude": ["node_modules", "bench", "__test__", "scripts", "examples"] 14 | } 15 | --------------------------------------------------------------------------------