Compare commits
160 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e2eb4eb040 | |||
| 502cca375f | |||
| c07b2969b8 | |||
| a5ec2717c5 | |||
| 8852bd5c86 | |||
| 6279f2cbad | |||
| e3f5616320 | |||
| 40c0dfb3df | |||
| 4f243289b8 | |||
| 2d28939986 | |||
| 01623eab2a | |||
| 5c65c43589 | |||
| 72109e478f | |||
| 53d9956735 | |||
| 913f8556d0 | |||
| e905af4b21 | |||
| 2e0b7d5053 | |||
| 270f75e8e0 | |||
| b9ec1e2be6 | |||
| 86d62407e7 | |||
| 6efd6232d1 | |||
| 44296dc57a | |||
| a08627f058 | |||
| e525e04b07 | |||
| 5e2225669d | |||
| a4a3343b1d | |||
| e10e8cf90d | |||
| 19900d088e | |||
| 3e5793f842 | |||
| 4fd13e65c4 | |||
| e5a8bbf4a3 | |||
| f28589603b | |||
| fed411a30f | |||
| 2328801f03 | |||
| 60883fed6d | |||
| e9fc5b98f2 | |||
| fef954c423 | |||
| 39408d9832 | |||
| 26d9b3e9cc | |||
| 5f027430bf | |||
| d3385782ed | |||
| 7384b54e09 | |||
| 0eac72e15d | |||
| b7957b0ab6 | |||
| 99a0a9ca81 | |||
| bd66903419 | |||
| 740d8dac35 | |||
| 488e7410fe | |||
| 04deb8960c | |||
| 19f0a9563f | |||
| db1e866fe1 | |||
| f7c24a0bd2 | |||
| fa59d2da40 | |||
| 311232aeea | |||
| 4cd0844bc3 | |||
| 17c1a687c8 | |||
| 1d1264c2b3 | |||
| b036e609ce | |||
| c2ec0df907 | |||
| 167b4d29df | |||
| 02fec216db | |||
| 4e9d2f3e8c | |||
| 65d8a8b6f5 | |||
| 8e04bd6a62 | |||
| 687a5f7c4e | |||
| 17983b1da9 | |||
| 5fcdf1ff8f | |||
| ef7ee7fc73 | |||
| c48e85897e | |||
| 9466b3e473 | |||
| ab3127b8a6 | |||
| 1e62e27980 | |||
| 4b87004478 | |||
| 7750f1fbf5 | |||
| c4e5ba6587 | |||
| 9d1f0f22ba | |||
| 1ce9e32116 | |||
| adfda70522 | |||
| c701e3e04c | |||
| 7b1de5b31d | |||
| 7908fd8cfd | |||
| 21bd0c9279 | |||
| 9d1108e40d | |||
| 390e0cb491 | |||
| 032fd0c2fd | |||
| 440881c3d8 | |||
| f208121e2c | |||
| 7c4ae84871 | |||
| 668f6c3e16 | |||
| b1e08aad1f | |||
| f1ab614cdf | |||
| 995c808512 | |||
| 28acb867a0 | |||
| 3148a50d43 | |||
| 41c99de4d8 | |||
| a91f56dacf | |||
| f60f17f91e | |||
| d154cf0d0f | |||
| a6e0fa65e0 | |||
| c7e940f597 | |||
| 45d3ce8ffc | |||
| ce121b8b7f | |||
| ce65b8d7c9 | |||
| 9acdfca460 | |||
| 59bcd8dadf | |||
| b6375fd8fa | |||
| 8183417c90 | |||
| 5e66d35125 | |||
| 3ff4c3ff2f | |||
| 6508b29bfc | |||
| 66fd7138ab | |||
| f3ce1c1408 | |||
| d2b84acc55 | |||
| ce008da9ad | |||
| f0f1f9b86f | |||
| 089787454a | |||
| f8a122b777 | |||
| c6db092062 | |||
| 857d31dcb2 | |||
| e257a38688 | |||
| 19a5082381 | |||
| 00f5539e6b | |||
| cacb0221f1 | |||
| b98b90163d | |||
| daa6312aea | |||
| 7f2dab091f | |||
| dd293875c4 | |||
| 120eca42ac | |||
| fc289616f6 | |||
| e7c1c1c45b | |||
| f33c759fa8 | |||
| 1185df362b | |||
| 36de8e11f0 | |||
| 74ffb3aa87 | |||
| 96a6d01720 | |||
| 7833bd0be8 | |||
| 7ca18c4a46 | |||
| b98e2a1a62 | |||
| e102203422 | |||
| 92a37cf29b | |||
| 8408d1f3b5 | |||
| d88e05466b | |||
| 3a789d052c | |||
| d76d829e46 | |||
| f8157ab8aa | |||
| 5c67015779 | |||
| b67e3c0de1 | |||
| c7a647d4ef | |||
| 9e5fb7a13a | |||
| c115d222c0 | |||
| 643514c64a | |||
| 19498f7b11 | |||
| 3afadd9045 | |||
| 0889423a24 | |||
| 86b4cf4cc3 | |||
| 4cb4eabef4 | |||
| bad0971f1c | |||
| 016f03bd8a | |||
| 63a9da11be | |||
| ab18c75b28 |
@@ -0,0 +1,66 @@
|
|||||||
|
name: Default (not tags)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags-ignore:
|
||||||
|
- '**'
|
||||||
|
|
||||||
|
env:
|
||||||
|
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||||
|
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
|
||||||
|
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||||
|
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||||
|
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||||
|
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
security:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
continue-on-error: true
|
||||||
|
container:
|
||||||
|
image: ${{ env.IMAGE }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install pnpm and npmci
|
||||||
|
run: |
|
||||||
|
pnpm install -g pnpm
|
||||||
|
pnpm install -g @shipzone/npmci
|
||||||
|
|
||||||
|
- name: Run npm prepare
|
||||||
|
run: npmci npm prepare
|
||||||
|
|
||||||
|
- name: Audit production dependencies
|
||||||
|
run: |
|
||||||
|
npmci command npm config set registry https://registry.npmjs.org
|
||||||
|
npmci command pnpm audit --audit-level=high --prod
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Audit development dependencies
|
||||||
|
run: |
|
||||||
|
npmci command npm config set registry https://registry.npmjs.org
|
||||||
|
npmci command pnpm audit --audit-level=high --dev
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
test:
|
||||||
|
if: ${{ always() }}
|
||||||
|
needs: security
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: ${{ env.IMAGE }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Test stable
|
||||||
|
run: |
|
||||||
|
npmci node install stable
|
||||||
|
npmci npm install
|
||||||
|
npmci npm test
|
||||||
|
|
||||||
|
- name: Test build
|
||||||
|
run: |
|
||||||
|
npmci node install stable
|
||||||
|
npmci npm install
|
||||||
|
npmci npm build
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
name: Default (tags)
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- '*'
|
||||||
|
|
||||||
|
env:
|
||||||
|
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||||
|
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
|
||||||
|
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||||
|
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||||
|
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||||
|
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
security:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
continue-on-error: true
|
||||||
|
container:
|
||||||
|
image: ${{ env.IMAGE }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Prepare
|
||||||
|
run: |
|
||||||
|
pnpm install -g pnpm
|
||||||
|
pnpm install -g @shipzone/npmci
|
||||||
|
npmci npm prepare
|
||||||
|
|
||||||
|
- name: Audit production dependencies
|
||||||
|
run: |
|
||||||
|
npmci command npm config set registry https://registry.npmjs.org
|
||||||
|
npmci command pnpm audit --audit-level=high --prod
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
- name: Audit development dependencies
|
||||||
|
run: |
|
||||||
|
npmci command npm config set registry https://registry.npmjs.org
|
||||||
|
npmci command pnpm audit --audit-level=high --dev
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
test:
|
||||||
|
if: ${{ always() }}
|
||||||
|
needs: security
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: ${{ env.IMAGE }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Prepare
|
||||||
|
run: |
|
||||||
|
pnpm install -g pnpm
|
||||||
|
pnpm install -g @shipzone/npmci
|
||||||
|
npmci npm prepare
|
||||||
|
|
||||||
|
- name: Test stable
|
||||||
|
run: |
|
||||||
|
npmci node install stable
|
||||||
|
npmci npm install
|
||||||
|
npmci npm test
|
||||||
|
|
||||||
|
- name: Test build
|
||||||
|
run: |
|
||||||
|
npmci node install stable
|
||||||
|
npmci npm install
|
||||||
|
npmci npm build
|
||||||
|
|
||||||
|
release:
|
||||||
|
needs: test
|
||||||
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: ${{ env.IMAGE }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Prepare
|
||||||
|
run: |
|
||||||
|
pnpm install -g pnpm
|
||||||
|
pnpm install -g @shipzone/npmci
|
||||||
|
npmci npm prepare
|
||||||
|
|
||||||
|
- name: Release
|
||||||
|
run: |
|
||||||
|
npmci node install stable
|
||||||
|
npmci npm publish
|
||||||
|
|
||||||
|
metadata:
|
||||||
|
needs: test
|
||||||
|
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: ${{ env.IMAGE }}
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Prepare
|
||||||
|
run: |
|
||||||
|
pnpm install -g pnpm
|
||||||
|
pnpm install -g @shipzone/npmci
|
||||||
|
npmci npm prepare
|
||||||
|
|
||||||
|
- name: Code quality
|
||||||
|
run: |
|
||||||
|
npmci command npm install -g typescript
|
||||||
|
npmci npm install
|
||||||
|
|
||||||
|
- name: Trigger
|
||||||
|
run: npmci trigger
|
||||||
|
|
||||||
|
- name: Build docs and upload artifacts
|
||||||
|
run: |
|
||||||
|
npmci node install stable
|
||||||
|
npmci npm install
|
||||||
|
pnpm install -g @git.zone/tsdoc
|
||||||
|
npmci command tsdoc
|
||||||
|
continue-on-error: true
|
||||||
+16
-7
@@ -1,11 +1,20 @@
|
|||||||
node_modules/
|
.nogit/
|
||||||
.settings/
|
|
||||||
.idea/
|
# artifacts
|
||||||
coverage/
|
coverage/
|
||||||
docs/
|
public/
|
||||||
|
pages/
|
||||||
|
|
||||||
|
# installs
|
||||||
|
node_modules/
|
||||||
|
|
||||||
ts/*.js
|
# caches
|
||||||
ts/*.js.map
|
.yarn/
|
||||||
ts/typings/
|
.cache/
|
||||||
|
.rpt2_cache
|
||||||
|
|
||||||
|
# builds
|
||||||
|
dist/
|
||||||
|
dist_*/
|
||||||
|
|
||||||
|
# custom
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
image: hosttoday/ht-docker-node:npmci
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- test
|
|
||||||
- release
|
|
||||||
|
|
||||||
testLEGACY:
|
|
||||||
stage: test
|
|
||||||
script:
|
|
||||||
- npmci test legacy
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
allow_failure: true
|
|
||||||
|
|
||||||
testLTS:
|
|
||||||
stage: test
|
|
||||||
script:
|
|
||||||
- npmci test lts
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
|
|
||||||
testSTABLE:
|
|
||||||
stage: test
|
|
||||||
script:
|
|
||||||
- npmci test stable
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
|
|
||||||
release:
|
|
||||||
stage: release
|
|
||||||
script:
|
|
||||||
- npmci publish
|
|
||||||
only:
|
|
||||||
- tags
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
{
|
||||||
|
"@git.zone/cli": {
|
||||||
|
"projectType": "npm",
|
||||||
|
"module": {
|
||||||
|
"githost": "code.foss.global",
|
||||||
|
"gitscope": "push.rocks",
|
||||||
|
"gitrepo": "smartcli",
|
||||||
|
"description": "A library that simplifies building reactive command-line applications using observables, with robust support for commands, arguments, options, aliases, and asynchronous operation management.",
|
||||||
|
"npmPackagename": "@push.rocks/smartcli",
|
||||||
|
"license": "MIT",
|
||||||
|
"projectDomain": "push.rocks"
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"targets": {
|
||||||
|
"npm": {
|
||||||
|
"registries": [
|
||||||
|
"https://verdaccio.lossless.digital",
|
||||||
|
"https://registry.npmjs.org"
|
||||||
|
],
|
||||||
|
"accessLevel": "public"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schemaVersion": 2
|
||||||
|
},
|
||||||
|
"@git.zone/tsdoc": {
|
||||||
|
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [LICENSE](LICENSE) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
||||||
|
},
|
||||||
|
"@ship.zone/szci": {
|
||||||
|
"npmGlobalTools": []
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+11
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"command": "npm test",
|
||||||
|
"name": "Run npm test",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "node-terminal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2015 Push.Rocks
|
Copyright (c) 2015 Task Venture Capital GmbH
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
# smartcli
|
|
||||||
nodejs wrapper for CLI related tasks. TypeScript ready.
|
|
||||||
|
|
||||||
## Status
|
|
||||||
[](https://travis-ci.org/pushrocks/smartcli)
|
|
||||||
[](https://david-dm.org/pushrocks/smartcli)
|
|
||||||
[](https://www.bithound.io/github/pushrocks/smartcli)
|
|
||||||
[](https://www.bithound.io/github/pushrocks/smartcli)
|
|
||||||
|
|
||||||
## Install the package
|
|
||||||
npm install smartcli --save
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
this plugin tries to establish some logic in which CLI tools work.
|
|
||||||
|
|
||||||
take the following commandline input:
|
|
||||||
|
|
||||||
```
|
|
||||||
mytool function argument1 argument2 --option1 option1Value --option2 option2Value
|
|
||||||
```
|
|
||||||
|
|
||||||
* 'mytool' obviously is the tool (like git)
|
|
||||||
* function is the main thing the tool shall do (like commit)
|
|
||||||
* option is an option you can add (like -m for message)
|
|
||||||
* optionValue is the referenced option value (like a commit message)
|
|
||||||
|
|
||||||
|
|
||||||
### The inner organization of smartcli
|
|
||||||
**smartcli** exposes three major groups of functions:
|
|
||||||
|
|
||||||
* check functions
|
|
||||||
* are grouped in **smartcli.checks** object
|
|
||||||
* get functions
|
|
||||||
* are grouped in **smartcli.get** object
|
|
||||||
* async interaction functions
|
|
||||||
* are grouped in **smartcli.interaction** object
|
|
||||||
|
|
||||||
|
|
||||||
### Methods
|
|
||||||
The examples are written in TypeScript
|
|
||||||
```typescript
|
|
||||||
import * as smartcli from "smartcli"
|
|
||||||
|
|
||||||
/* -------------- Check Functions -------------------*/
|
|
||||||
smartcli.check.command("jazz"); // check for a special command.
|
|
||||||
smartcli.check.commandPresence() // check if any command is specified
|
|
||||||
smartcli.check.commandArguemnt("myargument",1) // checks if a special argument is given, second argument is level
|
|
||||||
smartcli.check.commandArguemntPresence // checks of any Argument is present
|
|
||||||
smartcli.check.option("someoption") // checks for a specific option
|
|
||||||
smartcli.check.optionPresence() // checks if any option is specified
|
|
||||||
smartcli.get.option('myoption'); //
|
|
||||||
```
|
|
||||||
|
|
||||||
+434
@@ -0,0 +1,434 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## Pending
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## 2026-05-13 - 4.2.0
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- enhance terminal task rendering with progress, lifecycle helpers, and configurable output modes (terminal)
|
||||||
|
- add task progress reporting and task.run() helpers for automatic completion and failure handling
|
||||||
|
- support configurable unicode or ascii symbols, cleanup behavior, and throttled non-interactive lifecycle logs
|
||||||
|
- export new terminal task run and symbol mode types and document the updated terminal API
|
||||||
|
|
||||||
|
## 2026-05-13 - 4.1.0
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- add live terminal task rendering with interactive and non-interactive output modes (terminal)
|
||||||
|
- introduces SmartcliTerminal and SmartcliTerminalTask exports for fixed-row task rendering
|
||||||
|
- supports task updates, completion, failure handling, and persistent error output
|
||||||
|
- detects interactive terminals and falls back to append-only logs in CI or non-TTY environments
|
||||||
|
- adds tests covering interactive rendering, non-interactive logging, and error attachment behavior
|
||||||
|
|
||||||
|
## 2026-04-30 - 4.0.21 - fix(smartcli)
|
||||||
|
tighten command parsing and error handling while updating build and package configuration
|
||||||
|
|
||||||
|
- throw an explicit error when triggering an unregistered command instead of failing on an undefined subject
|
||||||
|
- make the cli version property optional to align with current typing expectations
|
||||||
|
- update tests to use explicit argv input and export the tap startup call for runtime compatibility
|
||||||
|
- enable stricter TypeScript configuration and refresh build, dependency, and package metadata files
|
||||||
|
|
||||||
|
## 2025-10-28 - 4.0.19 - fix(license)
|
||||||
|
Update license files and add local tool settings
|
||||||
|
|
||||||
|
- Update LICENSE header to reference Task Venture Capital GmbH as copyright holder
|
||||||
|
- Add a new license file containing the full MIT license text
|
||||||
|
- Add .claude/settings.local.json to store local tool permission settings
|
||||||
|
|
||||||
|
## 2025-10-28 - 4.0.18 - fix(smartcli)
|
||||||
|
Allow passing argv to startParse and improve getUserArgs Deno/runtime handling; update tests and add license
|
||||||
|
|
||||||
|
- Smartcli.startParse now accepts an optional testArgv parameter to bypass automatic runtime detection (makes testing deterministic).
|
||||||
|
- getUserArgs logic refined: always prefer Deno.args when available (handles Deno run and compiled executables reliably) and improve execPath fallback and slicing behavior for Node/Bun/other launchers.
|
||||||
|
- Tests updated: test/test.node+deno+bun.ts now passes process.argv explicitly to startParse to avoid Deno.args interference in test environments.
|
||||||
|
- Added MIT LICENSE file and a local .claude/settings.local.json for environment/permission settings.
|
||||||
|
|
||||||
|
## 2025-10-28 - 4.0.17 - fix(license)
|
||||||
|
Add MIT license and local Claude settings
|
||||||
|
|
||||||
|
- Add LICENSE file (MIT) to repository
|
||||||
|
- Add .claude/settings.local.json with local permissions for tooling
|
||||||
|
|
||||||
|
## 2025-10-28 - 4.0.16 - fix(smartcli.helpers)
|
||||||
|
Improve CLI argument parsing and Deno runtime detection; use getUserArgs consistently
|
||||||
|
|
||||||
|
- Enhance getUserArgs() to prefer Deno.args but detect when process.argv was manipulated (e.g. in tests) and fallback to manual parsing
|
||||||
|
- Add robust handling of process.execPath / execPath basename and compute correct argv offset for known launchers vs. compiled executables
|
||||||
|
- Call getUserArgs() (no explicit process.argv) from Smartcli.getOption and Smartcli.startParse to ensure consistent cross-runtime behavior
|
||||||
|
- Expand readme.hints.md with detailed cross-runtime examples and explanation of Deno.args vs process.argv for compiled executables
|
||||||
|
- Add local claude settings file for tooling configuration
|
||||||
|
|
||||||
|
## 2025-10-28 - 4.0.15 - fix(smartcli.helpers)
|
||||||
|
Add robust getUserArgs helper and refactor Smartcli to use it; add MIT license and update documentation
|
||||||
|
|
||||||
|
- Add ts/smartcli.helpers.ts: getUserArgs to normalize user arguments across Node.js, Deno (run/compiled), and Bun, with safety checks for test environments
|
||||||
|
- Refactor Smartcli (ts/smartcli.classes.smartcli.ts) to use getUserArgs in startParse and getOption for correct argument parsing and improved test compatibility
|
||||||
|
- Update readme.hints.md with detailed cross-runtime CLI argument parsing guidance
|
||||||
|
- Add LICENSE (MIT) file
|
||||||
|
- Add .claude/settings.local.json (local settings)
|
||||||
|
|
||||||
|
## 2025-10-28 - 4.0.14 - fix(license)
|
||||||
|
Add MIT license file
|
||||||
|
|
||||||
|
- Add MIT License file to repository to clarify licensing and copyright (Push.Rocks 2015).
|
||||||
|
|
||||||
|
## 2025-10-27 - 4.0.13 - fix(smartcli)
|
||||||
|
Improve CLI argument parsing, update deps and tests
|
||||||
|
|
||||||
|
- Enhance startParse() to filter runtime executables and script paths (node, deno, bun, tsx, ts-node) so commands are detected correctly across runtimes.
|
||||||
|
- Switch path import to node:path in plugins for ESM compatibility.
|
||||||
|
- Bump various dependencies and devDependencies (including @push.rocks/lik, @push.rocks/smartlog, @push.rocks/smartpromise, @push.rocks/smartrx, yargs-parser, @git.zone tooling) and add packageManager field.
|
||||||
|
- Replace / reorganize tests: add/modify test/test.node+deno+bun.ts, adjust test script to use --verbose.
|
||||||
|
- Add deno.lock and include many resolved npm dependencies.
|
||||||
|
- Add LICENSE file (MIT).
|
||||||
|
|
||||||
|
## 2025-04-01 - 4.0.12 - fix(docs)
|
||||||
|
Update documentation with comprehensive usage examples, improved command alias descriptions, and detailed configuration instructions
|
||||||
|
|
||||||
|
- Revised readme.md with in-depth examples covering multiple CLI scenarios and RxJS integration
|
||||||
|
- Updated package.json and npmextra.json descriptions and keywords to reflect enhanced functionality
|
||||||
|
- Expanded usage guide with additional commands, error handling strategies, and testing guidelines
|
||||||
|
|
||||||
|
## 2024-05-29 - 4.0.11 - general
|
||||||
|
update description
|
||||||
|
|
||||||
|
- Updated the project description
|
||||||
|
|
||||||
|
## 2024-05-28 - 4.0.10 - core
|
||||||
|
fix(core): update
|
||||||
|
|
||||||
|
- Fixed core functionality
|
||||||
|
|
||||||
|
## 2024-04-13 - 4.0.9 - core
|
||||||
|
fix(core): update
|
||||||
|
|
||||||
|
- Improved core update handling
|
||||||
|
|
||||||
|
## 2024-04-12 - 4.0.8 - core / npmextra
|
||||||
|
- fix(core): update
|
||||||
|
- update npmextra.json: githost (this change was applied multiple times)
|
||||||
|
|
||||||
|
## 2023-08-19 - 4.0.7 - core
|
||||||
|
fix(core): update
|
||||||
|
|
||||||
|
- Fixed core issues
|
||||||
|
|
||||||
|
## 2023-07-12 - 4.0.6 - core / org
|
||||||
|
- fix(core): update
|
||||||
|
- switch to new org scheme (applied twice)
|
||||||
|
|
||||||
|
## 2022-08-07 - 4.0.5 - core
|
||||||
|
fix(core): update
|
||||||
|
|
||||||
|
- Fixed core functionality
|
||||||
|
|
||||||
|
## 2022-08-04 - 4.0.4 - core
|
||||||
|
fix(core): update
|
||||||
|
|
||||||
|
- Improved core update
|
||||||
|
|
||||||
|
## 2022-08-03 - 4.0.3 - core
|
||||||
|
fix(core): update
|
||||||
|
|
||||||
|
- Fixed core issues
|
||||||
|
|
||||||
|
## 2022-08-03 - 4.0.2 - core
|
||||||
|
fix(core): update
|
||||||
|
|
||||||
|
- Updated core handling
|
||||||
|
|
||||||
|
## 2022-08-03 - 4.0.1 - core
|
||||||
|
fix(core): update
|
||||||
|
|
||||||
|
- Fixed core functionality
|
||||||
|
|
||||||
|
## 2022-08-03 - 4.0.0 - core
|
||||||
|
fix(core): update
|
||||||
|
|
||||||
|
- Improved core update handling
|
||||||
|
|
||||||
|
## 2022-08-03 - 3.0.14 - core
|
||||||
|
BREAKING CHANGE(core): switch to esm
|
||||||
|
|
||||||
|
- Switched the project to use ECMAScript modules
|
||||||
|
|
||||||
|
## 2021-04-07 - 3.0.13 - core
|
||||||
|
fix(core): update
|
||||||
|
|
||||||
|
- Fixed core update issues
|
||||||
|
|
||||||
|
## 2021-04-07 - 3.0.12 - core
|
||||||
|
fix(core): update
|
||||||
|
|
||||||
|
- Updated core functionality
|
||||||
|
|
||||||
|
## 2020-05-29 - 3.0.11 - core
|
||||||
|
fix(core): update
|
||||||
|
|
||||||
|
- Fixed core functionality
|
||||||
|
|
||||||
|
## 2020-04-13 - 3.0.10 - core
|
||||||
|
fix(core): more consistent handling of process.enc.CLI_CALL
|
||||||
|
|
||||||
|
- Made process.enc.CLI_CALL handling more consistent
|
||||||
|
|
||||||
|
## 2020-04-13 - 3.0.9 - core
|
||||||
|
fix(core): now works better with tapbundle tests
|
||||||
|
|
||||||
|
- Improved compatibility with tapbundle tests
|
||||||
|
|
||||||
|
## 2020-03-11 - 3.0.8 - core
|
||||||
|
fix(core): update
|
||||||
|
|
||||||
|
- Updated core functionality
|
||||||
|
|
||||||
|
## 2020-03-11 - 3.0.7 - core
|
||||||
|
fix(core): update
|
||||||
|
|
||||||
|
- Fixed core update issues
|
||||||
|
|
||||||
|
## 2018-12-11 - 3.0.6 - core
|
||||||
|
fix(core): update
|
||||||
|
|
||||||
|
- Updated core functionality
|
||||||
|
|
||||||
|
## 2018-09-30 - 3.0.5 - ci
|
||||||
|
fix(ci): remove obsolete dependencies
|
||||||
|
|
||||||
|
- Removed obsolete dependencies from CI configuration
|
||||||
|
|
||||||
|
## 2018-09-30 - 3.0.4 - core
|
||||||
|
fix(core): update
|
||||||
|
|
||||||
|
- Updated core functionality
|
||||||
|
|
||||||
|
## 2018-08-30 - 3.0.3 - structure
|
||||||
|
fix(structure): remove dist/ dir from git repo
|
||||||
|
|
||||||
|
- Removed the dist/ directory from the repository
|
||||||
|
|
||||||
|
## 2018-08-30 - 3.0.2 - dependencies
|
||||||
|
fix(dependencies): update to latest versions
|
||||||
|
|
||||||
|
- Bumped dependency versions to the latest
|
||||||
|
|
||||||
|
## 2018-06-28 - 3.0.1 - core
|
||||||
|
fix(core): slim down dependencies
|
||||||
|
|
||||||
|
- Slimmed down core dependencies
|
||||||
|
|
||||||
|
## 2018-05-03 - 3.0.0 - general
|
||||||
|
update
|
||||||
|
|
||||||
|
- General update
|
||||||
|
|
||||||
|
## 2018-05-03 - 2.0.12 - core / architecture
|
||||||
|
- change to an all rxjs Subject architecture
|
||||||
|
- system change
|
||||||
|
- fix(core): cleanup
|
||||||
|
- remove package-lock since using yarn
|
||||||
|
|
||||||
|
## 2018-01-27 - 2.0.11 - security
|
||||||
|
fix(improve security CI step):
|
||||||
|
|
||||||
|
- Improved security in the CI step
|
||||||
|
|
||||||
|
## 2018-01-27 - 2.0.10 - core / ci
|
||||||
|
- fix(core): remove vulnerable paths
|
||||||
|
- update ci (applied multiple times)
|
||||||
|
|
||||||
|
## 2018-01-27 - 2.0.09 - CI
|
||||||
|
add security step to CI
|
||||||
|
|
||||||
|
- Added an extra security step in the CI process
|
||||||
|
|
||||||
|
## 2017-10-12 - 2.0.08 - compatibility
|
||||||
|
ensure compatibility with code assertion library
|
||||||
|
|
||||||
|
- Ensured compatibility with the code assertion library
|
||||||
|
|
||||||
|
## 2017-10-12 - 2.0.07 - tests / cli
|
||||||
|
- fix tests and add .triggerOnlyOnProcessEnvCliCall()
|
||||||
|
- fix linting issues
|
||||||
|
|
||||||
|
## 2017-05-07 - 2.0.06 - tasks
|
||||||
|
fix promise rejection on standard task
|
||||||
|
|
||||||
|
- Fixed a promise rejection issue on standard tasks
|
||||||
|
|
||||||
|
## 2017-04-23 - 2.0.05 - tapbundle
|
||||||
|
use new tapbundle
|
||||||
|
|
||||||
|
- Switched to the new tapbundle for testing
|
||||||
|
|
||||||
|
## 2017-04-22 - 2.0.04 - tests
|
||||||
|
- comment out one test that makes problems due to tap
|
||||||
|
- update tests
|
||||||
|
|
||||||
|
## 2017-04-22 - 2.0.03 - npmextra
|
||||||
|
add npmextra.json
|
||||||
|
|
||||||
|
- Added npmextra.json for extra configuration
|
||||||
|
|
||||||
|
## 2017-04-22 - 2.0.02 - ci
|
||||||
|
update ci
|
||||||
|
|
||||||
|
- Updated CI configuration
|
||||||
|
|
||||||
|
## 2017-04-22 - 2.0.01 - misc
|
||||||
|
- update .gitignore
|
||||||
|
- update to latest standards
|
||||||
|
|
||||||
|
## 2016-12-18 - 2.0.00 - core
|
||||||
|
fix argvArg for observables
|
||||||
|
|
||||||
|
- Fixed the argvArg handling for observables
|
||||||
|
|
||||||
|
## 2016-12-18 - 1.0.16 - triggers
|
||||||
|
introduce triggers
|
||||||
|
|
||||||
|
- Introduced triggers
|
||||||
|
|
||||||
|
## 2016-11-19 - 1.0.15 - triggers / docs
|
||||||
|
- added .triggerCommandByName
|
||||||
|
- improve README
|
||||||
|
|
||||||
|
## 2016-11-19 - 1.0.14 - metadata
|
||||||
|
Update Metadata
|
||||||
|
|
||||||
|
- Updated project metadata
|
||||||
|
|
||||||
|
## 2016-11-19 - 1.0.13 - docs
|
||||||
|
improve README
|
||||||
|
|
||||||
|
- Improved the README documentation
|
||||||
|
|
||||||
|
## 2016-11-19 - 1.0.11 - core / tests / docs
|
||||||
|
- cleanup
|
||||||
|
- improve README
|
||||||
|
- update test file
|
||||||
|
|
||||||
|
## 2016-10-14 - 1.0.10 - deps
|
||||||
|
update deps
|
||||||
|
|
||||||
|
- Updated dependencies
|
||||||
|
|
||||||
|
## 2016-10-14 - 1.0.09 - standardJS
|
||||||
|
implement standardJS
|
||||||
|
|
||||||
|
- Implemented standardJS support
|
||||||
|
|
||||||
|
## 2016-09-04 - 1.0.08 - typings
|
||||||
|
improve typings
|
||||||
|
|
||||||
|
- Improved TypeScript typings
|
||||||
|
|
||||||
|
## 2016-09-04 - 1.0.07 - ci
|
||||||
|
fix ci
|
||||||
|
|
||||||
|
- Fixed CI configuration
|
||||||
|
|
||||||
|
## 2016-09-04 - 1.0.06 - base
|
||||||
|
fix base image
|
||||||
|
|
||||||
|
- Fixed the base image used for builds
|
||||||
|
|
||||||
|
## 2016-09-04 - 1.0.05 - interaction
|
||||||
|
- add page stage
|
||||||
|
- improve typings and docs
|
||||||
|
- update smartcli
|
||||||
|
- Add new file
|
||||||
|
- start interaction module
|
||||||
|
|
||||||
|
## 2016-08-26 - 1.0.04 - intellisense
|
||||||
|
improve intellisense
|
||||||
|
|
||||||
|
- Improved editor intellisense
|
||||||
|
|
||||||
|
## 2016-06-22 - 1.0.03 - compile
|
||||||
|
compile fix
|
||||||
|
|
||||||
|
- Fixed compilation issues
|
||||||
|
|
||||||
|
## 2016-06-22 - 1.0.02 - updates
|
||||||
|
- fix
|
||||||
|
- add getCommandPromise
|
||||||
|
- update deps and transition from npmts to npmts-g
|
||||||
|
|
||||||
|
## 2016-06-16 - 1.0.01 - tasks
|
||||||
|
- standard tasks now returns argv
|
||||||
|
- some cosmetics
|
||||||
|
- introduce new classes
|
||||||
|
|
||||||
|
## 2016-06-10 - 1.0.00 - version
|
||||||
|
- fix version return
|
||||||
|
- return argv to command
|
||||||
|
|
||||||
|
## 2016-06-10 - 0.0.13 - smartcli
|
||||||
|
- first version with basic funtionality
|
||||||
|
- remove bulk and add some features to Smartcli class
|
||||||
|
- start restructuring to use a smarter Smartcli class that handles command evaluation for you
|
||||||
|
- update dependencies
|
||||||
|
- compile
|
||||||
|
- add gitlab ci
|
||||||
|
- start smartcli class
|
||||||
|
- add class smartcli
|
||||||
|
- Update README and include commander
|
||||||
|
- fixed type issue
|
||||||
|
- fixed test issue
|
||||||
|
- update deps
|
||||||
|
|
||||||
|
## 2016-04-04 - 0.0.12 - deps
|
||||||
|
updated deps
|
||||||
|
|
||||||
|
- Updated dependencies
|
||||||
|
|
||||||
|
## 2016-04-04 - 0.0.11 - interface
|
||||||
|
- updated deps
|
||||||
|
- work in progress (noted twice)
|
||||||
|
- small interface fix
|
||||||
|
|
||||||
|
## 2015-11-09 - 0.0.10 - travis
|
||||||
|
improve travis process
|
||||||
|
|
||||||
|
- Improved the Travis process
|
||||||
|
|
||||||
|
## 2015-11-09 - 0.0.09 - tests / CLI
|
||||||
|
- add tests and fix some errors
|
||||||
|
- add Tests and improve TypeScript organization
|
||||||
|
- start smarter CLI logic
|
||||||
|
- fix small comment error (applied twice)
|
||||||
|
|
||||||
|
## 2015-10-14 - 0.0.08 - readme
|
||||||
|
- improved readme
|
||||||
|
- updated readme
|
||||||
|
- added devStatus badge
|
||||||
|
|
||||||
|
## 2015-10-12 - 0.0.07 - various
|
||||||
|
- improved return objects
|
||||||
|
- (Minor dependency updates and CI tweaks for beautylog and travis were also applied in this version)
|
||||||
|
|
||||||
|
## 2015-10-06 - 0.0.05 - CLI
|
||||||
|
- small update
|
||||||
|
- now handling CLI options
|
||||||
|
|
||||||
|
## 2015-10-05 - 0.0.4 - tests
|
||||||
|
modified test
|
||||||
|
|
||||||
|
## 2015-10-05 - 0.0.02 - travis / tests
|
||||||
|
- added travis + tests
|
||||||
|
- package.json update
|
||||||
|
|
||||||
|
## 2015-10-04 - 0.0.01 - initial
|
||||||
|
added initial structure
|
||||||
|
|
||||||
|
## 2015-10-04 - unknown - initial
|
||||||
|
Initial commit
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary of Omitted Versions
|
||||||
|
The following versions contained no additional user‐facing changes beyond version bumps and are summarized here: 1.0.12, 0.0.6, and 0.0.3.
|
||||||
Vendored
-2
@@ -1,2 +0,0 @@
|
|||||||
import "typings-global";
|
|
||||||
export { Smartcli } from "./smartcli.classes.smartcli";
|
|
||||||
Vendored
-6
@@ -1,6 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
require("typings-global");
|
|
||||||
var smartcli_classes_smartcli_1 = require("./smartcli.classes.smartcli");
|
|
||||||
exports.Smartcli = smartcli_classes_smartcli_1.Smartcli;
|
|
||||||
|
|
||||||
//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbImluZGV4LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSxRQUFPLGdCQUFnQixDQUFDLENBQUE7QUFHeEIsMENBQXVCLDZCQUE2QixDQUFDO0FBQTdDLHdEQUE2QyIsImZpbGUiOiJpbmRleC5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBcInR5cGluZ3MtZ2xvYmFsXCI7XG5cbmltcG9ydCB7U21hcnRjbGl9IGZyb20gXCIuL3NtYXJ0Y2xpLmNsYXNzZXMuc21hcnRjbGlcIjtcbmV4cG9ydCB7U21hcnRjbGl9IGZyb20gXCIuL3NtYXJ0Y2xpLmNsYXNzZXMuc21hcnRjbGlcIjtcbiJdfQ==
|
|
||||||
Vendored
-21
@@ -1,21 +0,0 @@
|
|||||||
import "typings-global";
|
|
||||||
export declare class Smartcli {
|
|
||||||
argv: any;
|
|
||||||
questionsDone: any;
|
|
||||||
parseStarted: any;
|
|
||||||
commands: any;
|
|
||||||
questions: any;
|
|
||||||
version: string;
|
|
||||||
constructor();
|
|
||||||
addAlias(keyArg: any, aliasArg: any): void;
|
|
||||||
addCommand(definitionArg: {
|
|
||||||
commandName: string;
|
|
||||||
}): any;
|
|
||||||
addQuestion(definitionArg: {
|
|
||||||
questionString: string;
|
|
||||||
questionType: string;
|
|
||||||
}): void;
|
|
||||||
addVersion(versionArg: string): void;
|
|
||||||
standardTask(): any;
|
|
||||||
startParse(): void;
|
|
||||||
}
|
|
||||||
Vendored
-66
File diff suppressed because one or more lines are too long
Vendored
-17
@@ -1,17 +0,0 @@
|
|||||||
import "typings-global";
|
|
||||||
import "./smartcli.interfaces";
|
|
||||||
/**
|
|
||||||
* executes callback with answer to question as argument
|
|
||||||
* @param questionString the question you want to ask the user
|
|
||||||
* @param cb the function to execute with answer as param
|
|
||||||
* @returns {null}
|
|
||||||
*/
|
|
||||||
export declare let getAnswer: (questionString: string, cb: any) => any;
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param questionString
|
|
||||||
* @param choiceOptions
|
|
||||||
* @param cb
|
|
||||||
* @returns {null}
|
|
||||||
*/
|
|
||||||
export declare let getChoice: (questionString: string, choiceOptions: string[], cb: any) => any;
|
|
||||||
Vendored
-55
@@ -1,55 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
require("typings-global");
|
|
||||||
require("./smartcli.interfaces");
|
|
||||||
var plugins = require("./smartcli.plugins");
|
|
||||||
/**
|
|
||||||
* executes callback with answer to question as argument
|
|
||||||
* @param questionString the question you want to ask the user
|
|
||||||
* @param cb the function to execute with answer as param
|
|
||||||
* @returns {null}
|
|
||||||
*/
|
|
||||||
exports.getAnswer = function (questionString, cb) {
|
|
||||||
if (typeof questionString != 'string') {
|
|
||||||
plugins.beautylog.error('no question specified');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
//make inquirer compatible question object
|
|
||||||
var question = {
|
|
||||||
type: "input",
|
|
||||||
name: "userFeedback",
|
|
||||||
message: questionString,
|
|
||||||
validate: function (value) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
plugins.inquirer.prompt([question], function (answers) {
|
|
||||||
var answer = answers.userFeedback;
|
|
||||||
cb(answer);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param questionString
|
|
||||||
* @param choiceOptions
|
|
||||||
* @param cb
|
|
||||||
* @returns {null}
|
|
||||||
*/
|
|
||||||
exports.getChoice = function (questionString, choiceOptions, cb) {
|
|
||||||
if (!Array.isArray(choiceOptions)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
//make inquirer compatible question object
|
|
||||||
var question = {
|
|
||||||
type: "list",
|
|
||||||
name: "userFeedback",
|
|
||||||
message: questionString,
|
|
||||||
choices: choiceOptions,
|
|
||||||
filter: function (val) { return val.toLowerCase(); }
|
|
||||||
};
|
|
||||||
plugins.inquirer.prompt(question, function (answers) {
|
|
||||||
var answer = answers.userFeedback;
|
|
||||||
cb(answer);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInNtYXJ0Y2xpLmludGVyYWN0aW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSxRQUFPLGdCQUFnQixDQUFDLENBQUE7QUFFeEIsUUFBTyx1QkFBdUIsQ0FBQyxDQUFBO0FBQy9CLElBQU8sT0FBTyxXQUFXLG9CQUFvQixDQUFDLENBQUM7QUFFL0M7Ozs7O0dBS0c7QUFDUSxpQkFBUyxHQUFHLFVBQVMsY0FBcUIsRUFBRSxFQUFFO0lBQ3JELEVBQUUsQ0FBQyxDQUFDLE9BQU8sY0FBYyxJQUFJLFFBQVEsQ0FBQyxDQUFDLENBQUM7UUFDcEMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsdUJBQXVCLENBQUMsQ0FBQztRQUNqRCxNQUFNLENBQUMsSUFBSSxDQUFDO0lBQ2hCLENBQUM7SUFDRCwwQ0FBMEM7SUFDMUMsSUFBSSxRQUFRLEdBQUc7UUFDWCxJQUFJLEVBQUUsT0FBTztRQUNiLElBQUksRUFBRSxjQUFjO1FBQ3BCLE9BQU8sRUFBRSxjQUFjO1FBQ3ZCLFFBQVEsRUFBRSxVQUFVLEtBQUs7WUFDckIsTUFBTSxDQUFDLElBQUksQ0FBQztRQUNoQixDQUFDO0tBQ0osQ0FBQztJQUVGLE9BQU8sQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsUUFBUSxDQUFDLEVBQUMsVUFBUyxPQUFPO1FBQy9DLElBQUksTUFBTSxHQUFHLE9BQU8sQ0FBQyxZQUFZLENBQUM7UUFDbEMsRUFBRSxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQ2YsQ0FBQyxDQUFDLENBQUM7QUFDUCxDQUFDLENBQUM7QUFFRjs7Ozs7O0dBTUc7QUFDUSxpQkFBUyxHQUFHLFVBQVMsY0FBcUIsRUFBRSxhQUFzQixFQUFFLEVBQUU7SUFDN0UsRUFBRSxDQUFBLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUMvQixNQUFNLENBQUMsSUFBSSxDQUFDO0lBQ2hCLENBQUM7SUFFRCwwQ0FBMEM7SUFDMUMsSUFBSSxRQUFRLEdBQUc7UUFDWCxJQUFJLEVBQUUsTUFBTTtRQUNaLElBQUksRUFBRSxjQUFjO1FBQ3BCLE9BQU8sRUFBRSxjQUFjO1FBQ3ZCLE9BQU8sRUFBRSxhQUFhO1FBQ3RCLE1BQU0sRUFBRSxVQUFVLEdBQUcsSUFBSyxNQUFNLENBQUMsR0FBRyxDQUFDLFdBQVcsRUFBRSxDQUFDLENBQUMsQ0FBQztLQUN4RCxDQUFDO0lBRUYsT0FBTyxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsUUFBUSxFQUFDLFVBQVMsT0FBTztRQUM3QyxJQUFJLE1BQU0sR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFDO1FBQ2xDLEVBQUUsQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUNmLENBQUMsQ0FBQyxDQUFDO0FBRVAsQ0FBQyxDQUFDIiwiZmlsZSI6InNtYXJ0Y2xpLmludGVyYWN0aW9uLmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IFwidHlwaW5ncy1nbG9iYWxcIjtcblxuaW1wb3J0IFwiLi9zbWFydGNsaS5pbnRlcmZhY2VzXCI7XG5pbXBvcnQgcGx1Z2lucyA9IHJlcXVpcmUoXCIuL3NtYXJ0Y2xpLnBsdWdpbnNcIik7XG5cbi8qKlxuICogZXhlY3V0ZXMgY2FsbGJhY2sgd2l0aCBhbnN3ZXIgdG8gcXVlc3Rpb24gYXMgYXJndW1lbnRcbiAqIEBwYXJhbSBxdWVzdGlvblN0cmluZyB0aGUgcXVlc3Rpb24geW91IHdhbnQgdG8gYXNrIHRoZSB1c2VyXG4gKiBAcGFyYW0gY2IgdGhlIGZ1bmN0aW9uIHRvIGV4ZWN1dGUgd2l0aCBhbnN3ZXIgYXMgcGFyYW1cbiAqIEByZXR1cm5zIHtudWxsfVxuICovXG5leHBvcnQgbGV0IGdldEFuc3dlciA9IGZ1bmN0aW9uKHF1ZXN0aW9uU3RyaW5nOnN0cmluZywgY2IpIHtcbiAgICBpZiAodHlwZW9mIHF1ZXN0aW9uU3RyaW5nICE9ICdzdHJpbmcnKSB7XG4gICAgICAgIHBsdWdpbnMuYmVhdXR5bG9nLmVycm9yKCdubyBxdWVzdGlvbiBzcGVjaWZpZWQnKTtcbiAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuICAgIC8vbWFrZSBpbnF1aXJlciBjb21wYXRpYmxlIHF1ZXN0aW9uIG9iamVjdFxuICAgIGxldCBxdWVzdGlvbiA9IHtcbiAgICAgICAgdHlwZTogXCJpbnB1dFwiLFxuICAgICAgICBuYW1lOiBcInVzZXJGZWVkYmFja1wiLFxuICAgICAgICBtZXNzYWdlOiBxdWVzdGlvblN0cmluZyxcbiAgICAgICAgdmFsaWRhdGU6IGZ1bmN0aW9uKCB2YWx1ZSApIHtcbiAgICAgICAgICAgIHJldHVybiB0cnVlO1xuICAgICAgICB9XG4gICAgfTtcblxuICAgIHBsdWdpbnMuaW5xdWlyZXIucHJvbXB0KFtxdWVzdGlvbl0sZnVuY3Rpb24oYW5zd2Vycyl7XG4gICAgICAgIGxldCBhbnN3ZXIgPSBhbnN3ZXJzLnVzZXJGZWVkYmFjaztcbiAgICAgICAgY2IoYW5zd2VyKTtcbiAgICB9KTtcbn07XG5cbi8qKlxuICpcbiAqIEBwYXJhbSBxdWVzdGlvblN0cmluZ1xuICogQHBhcmFtIGNob2ljZU9wdGlvbnNcbiAqIEBwYXJhbSBjYlxuICogQHJldHVybnMge251bGx9XG4gKi9cbmV4cG9ydCBsZXQgZ2V0Q2hvaWNlID0gZnVuY3Rpb24ocXVlc3Rpb25TdHJpbmc6c3RyaW5nLCBjaG9pY2VPcHRpb25zOnN0cmluZ1tdLCBjYikge1xuICAgIGlmKCFBcnJheS5pc0FycmF5KGNob2ljZU9wdGlvbnMpKSB7XG4gICAgICAgIHJldHVybiBudWxsO1xuICAgIH1cblxuICAgIC8vbWFrZSBpbnF1aXJlciBjb21wYXRpYmxlIHF1ZXN0aW9uIG9iamVjdFxuICAgIGxldCBxdWVzdGlvbiA9IHtcbiAgICAgICAgdHlwZTogXCJsaXN0XCIsXG4gICAgICAgIG5hbWU6IFwidXNlckZlZWRiYWNrXCIsXG4gICAgICAgIG1lc3NhZ2U6IHF1ZXN0aW9uU3RyaW5nLFxuICAgICAgICBjaG9pY2VzOiBjaG9pY2VPcHRpb25zLFxuICAgICAgICBmaWx0ZXI6IGZ1bmN0aW9uKCB2YWwgKSB7IHJldHVybiB2YWwudG9Mb3dlckNhc2UoKTsgfVxuICAgIH07XG5cbiAgICBwbHVnaW5zLmlucXVpcmVyLnByb21wdChxdWVzdGlvbixmdW5jdGlvbihhbnN3ZXJzKXtcbiAgICAgICAgbGV0IGFuc3dlciA9IGFuc3dlcnMudXNlckZlZWRiYWNrO1xuICAgICAgICBjYihhbnN3ZXIpO1xuICAgIH0pO1xuXG59OyJdfQ==
|
|
||||||
Vendored
-8
@@ -1,8 +0,0 @@
|
|||||||
import "typings-global";
|
|
||||||
export declare let argv: any;
|
|
||||||
export declare let beautylog: any;
|
|
||||||
export declare let cliff: any;
|
|
||||||
export declare let inquirer: any;
|
|
||||||
export declare let path: any;
|
|
||||||
export declare let q: any;
|
|
||||||
export declare let smartparam: any;
|
|
||||||
Vendored
-11
@@ -1,11 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
require("typings-global");
|
|
||||||
exports.argv = require('yargs');
|
|
||||||
exports.beautylog = require("beautylog");
|
|
||||||
exports.cliff = require("cliff");
|
|
||||||
exports.inquirer = require("inquirer");
|
|
||||||
exports.path = require("path");
|
|
||||||
exports.q = require("q");
|
|
||||||
exports.smartparam = require("smartparam");
|
|
||||||
|
|
||||||
//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInNtYXJ0Y2xpLnBsdWdpbnMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLFFBQU8sZ0JBQWdCLENBQUMsQ0FBQTtBQUViLFlBQUksR0FBRyxPQUFPLENBQUMsT0FBTyxDQUFDLENBQUM7QUFDeEIsaUJBQVMsR0FBRyxPQUFPLENBQUMsV0FBVyxDQUFDLENBQUM7QUFDakMsYUFBSyxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsQ0FBQztBQUN6QixnQkFBUSxHQUFHLE9BQU8sQ0FBQyxVQUFVLENBQUMsQ0FBQztBQUMvQixZQUFJLEdBQUcsT0FBTyxDQUFDLE1BQU0sQ0FBQyxDQUFDO0FBQ3ZCLFNBQUMsR0FBRyxPQUFPLENBQUMsR0FBRyxDQUFDLENBQUM7QUFDakIsa0JBQVUsR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFDLENBQUMiLCJmaWxlIjoic21hcnRjbGkucGx1Z2lucy5qcyIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCBcInR5cGluZ3MtZ2xvYmFsXCI7XG5cbmV4cG9ydCBsZXQgYXJndiA9IHJlcXVpcmUoJ3lhcmdzJyk7XG5leHBvcnQgbGV0IGJlYXV0eWxvZyA9IHJlcXVpcmUoXCJiZWF1dHlsb2dcIik7XG5leHBvcnQgbGV0IGNsaWZmID0gcmVxdWlyZShcImNsaWZmXCIpO1xuZXhwb3J0IGxldCBpbnF1aXJlciA9IHJlcXVpcmUoXCJpbnF1aXJlclwiKTtcbmV4cG9ydCBsZXQgcGF0aCA9IHJlcXVpcmUoXCJwYXRoXCIpO1xuZXhwb3J0IGxldCBxID0gcmVxdWlyZShcInFcIik7XG5leHBvcnQgbGV0IHNtYXJ0cGFyYW0gPSByZXF1aXJlKFwic21hcnRwYXJhbVwiKTsiXX0=
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
# smartcli
|
||||||
|
|
||||||
|
nodejs wrapper for CLI related tasks
|
||||||
|
|
||||||
|
## Availabililty
|
||||||
|
|
||||||
|
[](https://www.npmjs.com/package/smartcli)
|
||||||
|
[](https://GitLab.com/pushrocks/smartcli)
|
||||||
|
[](https://github.com/pushrocks/smartcli)
|
||||||
|
[](https://pushrocks.gitlab.io/smartcli/)
|
||||||
|
|
||||||
|
## Status for master
|
||||||
|
|
||||||
|
[](https://GitLab.com/pushrocks/smartcli/commits/master)
|
||||||
|
[](https://GitLab.com/pushrocks/smartcli/commits/master)
|
||||||
|
[](https://www.npmjs.com/package/smartcli)
|
||||||
|
[](https://david-dm.org/pushrocks/smartcli)
|
||||||
|
[](https://www.bithound.io/github/pushrocks/smartcli/master/dependencies/npm)
|
||||||
|
[](https://www.bithound.io/github/pushrocks/smartcli)
|
||||||
|
[](https://nodejs.org/dist/latest-v6.x/docs/api/)
|
||||||
|
[](https://nodejs.org/dist/latest-v6.x/docs/api/)
|
||||||
|
[](http://standardjs.com/)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
For further information read the linked docs at the top of this README.
|
||||||
|
|
||||||
|
> MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh)
|
||||||
|
> | By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy.html)
|
||||||
|
|
||||||
|
[](https://push.rocks)
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"mode":"default"
|
|
||||||
}
|
|
||||||
+55
-29
@@ -1,44 +1,70 @@
|
|||||||
{
|
{
|
||||||
"name": "smartcli",
|
"name": "@push.rocks/smartcli",
|
||||||
"version": "1.0.1",
|
"private": false,
|
||||||
"description": "nodejs wrapper for CLI related tasks",
|
"version": "4.2.0",
|
||||||
"main": "dist/index.js",
|
"description": "A library that simplifies building reactive command-line applications using observables, with robust support for commands, arguments, options, aliases, and asynchronous operation management.",
|
||||||
"typings": "dist/index.d.ts",
|
"main": "dist_ts/index.js",
|
||||||
|
"typings": "dist_ts/index.d.ts",
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "(npmts)",
|
"test": "(tstest test/ --verbose)",
|
||||||
"testm": "(cd ts/compile && gulp) && (node test.js jazz jam --awesome)",
|
"build": "tsbuild --web",
|
||||||
"devTest": "(npm test) && (node test.js --test true)",
|
"buildDocs": "tsdoc"
|
||||||
"reinstall": "(rm -r node_modules && npm install)",
|
|
||||||
"release": "(git pull origin master && npm version patch && git push origin master && git checkout release && git merge master && git push origin release && git checkout master)",
|
|
||||||
"startdev": "(git checkout master && git pull origin master)"
|
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://gitlab.com/pushrocks/smartcli.git"
|
"url": "https://code.foss.global/push.rocks/smartcli.git"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"json",
|
"CLI",
|
||||||
"jade",
|
"command line",
|
||||||
"template"
|
"observable",
|
||||||
|
"reactive",
|
||||||
|
"asynchronous",
|
||||||
|
"commands",
|
||||||
|
"arguments",
|
||||||
|
"options",
|
||||||
|
"alias",
|
||||||
|
"typescript",
|
||||||
|
"node.js",
|
||||||
|
"development tool"
|
||||||
],
|
],
|
||||||
"author": "Smart Coordination GmbH <office@push.rocks> (https://push.rocks)",
|
"author": "Lossless GmbH <office@lossless.com> (https://lossless.com)",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://gitlab.com/pushrocks/smartcli/issues"
|
"url": "https://code.foss.global/push.rocks/smartcli/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://gitlab.com/pushrocks/smartcli",
|
"homepage": "https://code.foss.global/push.rocks/smartcli",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"beautylog": "^5.0.8",
|
"@push.rocks/lik": "^6.4.1",
|
||||||
"cliff": "^0.1.10",
|
"@push.rocks/smartlog": "^3.2.2",
|
||||||
"inquirer": "^1.0.3",
|
"@push.rocks/smartobject": "^1.0.12",
|
||||||
"q": "^1.4.1",
|
"@push.rocks/smartpromise": "^4.2.4",
|
||||||
"smartparam": "0.0.7",
|
"@push.rocks/smartrx": "^3.0.10",
|
||||||
"typings-global": "^1.0.3",
|
"yargs-parser": "22.0.0"
|
||||||
"yargs": "^4.7.1"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"npmts": "^5.2.5",
|
"@git.zone/tsbuild": "^4.4.1",
|
||||||
"should": "^9.0.1",
|
"@git.zone/tsrun": "^2.0.4",
|
||||||
"typings-test": "^1.0.1"
|
"@git.zone/tstest": "^3.6.6",
|
||||||
}
|
"@types/node": "^25.7.0",
|
||||||
|
"@types/yargs-parser": "^21.0.3"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"ts/**/*",
|
||||||
|
"ts_web/**/*",
|
||||||
|
"dist/**/*",
|
||||||
|
"dist_*/**/*",
|
||||||
|
"dist_ts/**/*",
|
||||||
|
"dist_ts_web/**/*",
|
||||||
|
"assets/**/*",
|
||||||
|
"cli.js",
|
||||||
|
".smartconfig.json",
|
||||||
|
"LICENSE",
|
||||||
|
"readme.md"
|
||||||
|
],
|
||||||
|
"browserslist": [
|
||||||
|
"last 1 chrome versions"
|
||||||
|
],
|
||||||
|
"packageManager": "pnpm@10.28.2"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+7665
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,42 @@
|
|||||||
|
## Cross-Runtime Compatibility
|
||||||
|
|
||||||
|
### CLI Argument Parsing
|
||||||
|
The module uses a robust cross-runtime approach for parsing command-line arguments through the `getUserArgs()` utility in `ts/smartcli.helpers.ts`.
|
||||||
|
|
||||||
|
**Runtime-Specific Implementations:**
|
||||||
|
|
||||||
|
| Runtime | process.argv Structure | Preferred API | Reason |
|
||||||
|
|---------|------------------------|---------------|---------|
|
||||||
|
| **Node.js** | `["/path/to/node", "/path/to/script.js", ...userArgs]` | Manual parsing | No native user-args API |
|
||||||
|
| **Deno run** | `["deno", "/path/to/script.ts", ...userArgs]` | `Deno.args` ✅ | Pre-filtered by runtime |
|
||||||
|
| **Deno compiled** | `["/path/to/binary", "/tmp/deno-compile-.../mod.ts", ...userArgs]` | `Deno.args` ✅ | Filters internal bundle path |
|
||||||
|
| **Bun** | `["/path/to/bun", "/path/to/script.ts", ...userArgs]` | Manual parsing | Bun.argv not pre-filtered |
|
||||||
|
|
||||||
|
**Why Deno.args is Critical for Compiled Executables:**
|
||||||
|
|
||||||
|
Deno compiled executables insert an internal bundle path at `argv[1]`:
|
||||||
|
```javascript
|
||||||
|
process.argv = [
|
||||||
|
"/usr/local/bin/moxytool", // argv[0] - executable
|
||||||
|
"/tmp/deno-compile-moxytool/mod.ts", // argv[1] - INTERNAL bundle path
|
||||||
|
"scripts", // argv[2] - actual user command
|
||||||
|
"--option" // argv[3+] - user args
|
||||||
|
]
|
||||||
|
|
||||||
|
Deno.args = ["scripts", "--option"] // ✓ Correctly filtered by Deno runtime
|
||||||
|
```
|
||||||
|
|
||||||
|
**getUserArgs() Logic:**
|
||||||
|
|
||||||
|
1. **Prefer Deno.args** when available (unless process.argv appears manipulated for testing)
|
||||||
|
2. **Fallback to manual parsing** for Node.js and Bun:
|
||||||
|
- Check `process.execPath` basename
|
||||||
|
- Known launchers (node, deno, bun, tsx, ts-node) → skip 2 args
|
||||||
|
- Unknown (compiled executables) → skip 1 arg
|
||||||
|
3. **Test detection**: If `process.argv.length > 2` in Deno, use manual parsing (handles test manipulation)
|
||||||
|
|
||||||
|
**Key Benefits:**
|
||||||
|
- ✅ Works with custom-named compiled executables
|
||||||
|
- ✅ Handles Deno's internal bundle path automatically
|
||||||
|
- ✅ Compatible with test environments
|
||||||
|
- ✅ No heuristics needed for Deno (runtime does the work)
|
||||||
@@ -0,0 +1,458 @@
|
|||||||
|
# @push.rocks/smartcli
|
||||||
|
|
||||||
|
Build small, reactive command-line tools in TypeScript without hand-rolling argument dispatch. `@push.rocks/smartcli` parses the current runtime's user arguments, dispatches the first positional command into an RxJS `Subject`, exposes the parsed `yargs-parser` result to your handler, and gives you a clean fallback path when no command is provided.
|
||||||
|
|
||||||
|
It is ESM-first, ships TypeScript declarations, and handles user argument slicing across Node.js, Deno, Deno-compiled executables, Bun, `tsx`, and `ts-node`.
|
||||||
|
|
||||||
|
## Issue Reporting and Security
|
||||||
|
|
||||||
|
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm add @push.rocks/smartcli
|
||||||
|
```
|
||||||
|
|
||||||
|
## Why Use Smartcli
|
||||||
|
|
||||||
|
- Reactive command handlers: every command is an RxJS `Subject`, so command execution fits naturally into observable-based code.
|
||||||
|
- Tiny API surface: register commands, subscribe handlers, and call `startParse()`.
|
||||||
|
- Runtime-aware argument handling: Node.js, Deno, Deno-compiled binaries, Bun, `tsx`, and `ts-node` get user-only arguments consistently.
|
||||||
|
- `yargs-parser` integration: flags and positional arguments arrive as a familiar parsed object.
|
||||||
|
- Built-in standard command, help command, version output, manual triggering, and parse completion hooks.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
Create a CLI entrypoint, for example `cli.ts`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
#!/usr/bin/env node
|
||||||
|
import { Smartcli } from '@push.rocks/smartcli';
|
||||||
|
|
||||||
|
const cli = new Smartcli();
|
||||||
|
|
||||||
|
cli.addVersion('1.0.0');
|
||||||
|
|
||||||
|
cli.addHelp({
|
||||||
|
helpText: `
|
||||||
|
Usage:
|
||||||
|
demo greet --name Ada
|
||||||
|
demo --version
|
||||||
|
demo help
|
||||||
|
|
||||||
|
Commands:
|
||||||
|
greet Print a greeting
|
||||||
|
help Print this help text
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
cli.addCommand('greet').subscribe((argv) => {
|
||||||
|
const name = argv.name || 'World';
|
||||||
|
console.log(`Hello, ${name}!`);
|
||||||
|
});
|
||||||
|
|
||||||
|
cli.standardCommand().subscribe(() => {
|
||||||
|
console.log('Usage: demo greet --name Ada');
|
||||||
|
console.log('Run "demo help" for details.');
|
||||||
|
});
|
||||||
|
|
||||||
|
cli.startParse();
|
||||||
|
```
|
||||||
|
|
||||||
|
When exposed as a package `bin`, the CLI behaves like this:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
demo greet --name Ada
|
||||||
|
# Hello, Ada!
|
||||||
|
|
||||||
|
demo --version
|
||||||
|
# 1.0.0
|
||||||
|
|
||||||
|
demo help
|
||||||
|
# prints the configured help text
|
||||||
|
```
|
||||||
|
|
||||||
|
## Terminal Task Rendering
|
||||||
|
|
||||||
|
Use `SmartcliTerminal` for long-running jobs that should render cleanly in both interactive and non-interactive environments. In a TTY, active tasks render below each other with a fixed row count, colored status symbols, and elapsed time. In CI, pipes, Docker logs, or `TERM=dumb`, the same calls become throttled append-only lifecycle logs.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { SmartcliTerminal } from '@push.rocks/smartcli';
|
||||||
|
|
||||||
|
const terminal = new SmartcliTerminal();
|
||||||
|
|
||||||
|
const buildTask = terminal.task('Build package', {
|
||||||
|
rows: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
buildTask.update('Installing dependencies');
|
||||||
|
buildTask.setProgress(1, 2, 'Running tsbuild');
|
||||||
|
buildTask.complete('Build finished');
|
||||||
|
|
||||||
|
const publishTask = terminal.createProcess({
|
||||||
|
job: 'Publish package',
|
||||||
|
rows: 4,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await publishPackage();
|
||||||
|
publishTask.complete('Published');
|
||||||
|
} catch (error) {
|
||||||
|
publishTask.attachError(error);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Completed tasks collapse into one permanent success line. Failed tasks collapse into one permanent failure line with error details. If an error should remain visible inside the live task area, use `attachError(error, { keepOpen: true })`.
|
||||||
|
|
||||||
|
For scoped work, `task.run()` completes or fails automatically:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
await terminal.task('Generate assets').run(async (task) => {
|
||||||
|
task.setProgress(1, 3, 'Reading source files');
|
||||||
|
await readSourceFiles();
|
||||||
|
task.setProgress(2, 3, 'Rendering assets');
|
||||||
|
await renderAssets();
|
||||||
|
task.setProgress(3, 3, 'Writing output');
|
||||||
|
}, { successMessage: 'Assets generated' });
|
||||||
|
```
|
||||||
|
|
||||||
|
## Execution Model
|
||||||
|
|
||||||
|
1. Create a `Smartcli` instance.
|
||||||
|
2. Register all commands before parsing.
|
||||||
|
3. Subscribe handlers to the returned command subjects.
|
||||||
|
4. Call `startParse()` once to parse the current runtime arguments.
|
||||||
|
5. `startParse()` parses user args with `yargs-parser`.
|
||||||
|
6. The first positional argument, `argv._[0]`, is treated as the command name.
|
||||||
|
7. A matching command subject receives the parsed `argv` object through `.next(argv)`.
|
||||||
|
8. If no command is provided, the subject from `standardCommand()` is triggered when it exists.
|
||||||
|
9. After normal dispatch, `parseCompleted.promise` resolves with the parsed argument object.
|
||||||
|
|
||||||
|
`startParse()` dispatches synchronously. If a handler starts asynchronous work, manage that lifecycle in your application code.
|
||||||
|
|
||||||
|
## Commands and Options
|
||||||
|
|
||||||
|
Options are available directly on the parsed object handed to your subscription:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Smartcli } from '@push.rocks/smartcli';
|
||||||
|
|
||||||
|
const cli = new Smartcli();
|
||||||
|
|
||||||
|
cli.addCommand('deploy').subscribe((argv) => {
|
||||||
|
const environment = argv.env || 'development';
|
||||||
|
const force = Boolean(argv.force);
|
||||||
|
|
||||||
|
console.log(`Deploying to ${environment}`);
|
||||||
|
|
||||||
|
if (force) {
|
||||||
|
console.log('Force mode enabled');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cli.standardCommand().subscribe(() => {
|
||||||
|
console.log('Usage: deployer deploy --env production [--force]');
|
||||||
|
});
|
||||||
|
|
||||||
|
cli.startParse();
|
||||||
|
```
|
||||||
|
|
||||||
|
Run it with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
deployer deploy --env production --force
|
||||||
|
```
|
||||||
|
|
||||||
|
The handler receives a `yargs-parser` result similar to:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
{
|
||||||
|
_: ['deploy'],
|
||||||
|
env: 'production',
|
||||||
|
force: true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Positional Arguments
|
||||||
|
|
||||||
|
The first positional argument selects the command. Additional positional values remain in `argv._`:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Smartcli } from '@push.rocks/smartcli';
|
||||||
|
|
||||||
|
const cli = new Smartcli();
|
||||||
|
|
||||||
|
cli.addCommand('run').subscribe((argv) => {
|
||||||
|
const taskName = argv._[1];
|
||||||
|
|
||||||
|
if (!taskName) {
|
||||||
|
console.log('Usage: tool run <taskName>');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Running task: ${taskName}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
cli.startParse();
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
tool run build
|
||||||
|
# Running task: build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Standard Command
|
||||||
|
|
||||||
|
Use `standardCommand()` for the no-command case:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Smartcli } from '@push.rocks/smartcli';
|
||||||
|
|
||||||
|
const cli = new Smartcli();
|
||||||
|
|
||||||
|
cli.addCommand('status').subscribe(() => {
|
||||||
|
console.log('Everything is operational.');
|
||||||
|
});
|
||||||
|
|
||||||
|
cli.standardCommand().subscribe(() => {
|
||||||
|
console.log('Available commands: status');
|
||||||
|
});
|
||||||
|
|
||||||
|
cli.startParse();
|
||||||
|
```
|
||||||
|
|
||||||
|
If no standard command is registered and the user runs the CLI without a command, smartcli prints `no smartcli standard task was created or assigned.`.
|
||||||
|
|
||||||
|
## Help and Version Output
|
||||||
|
|
||||||
|
`addVersion()` enables `-v` and `--version` when no command is provided:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
cli.addVersion('2.3.0');
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
tool --version
|
||||||
|
# 2.3.0
|
||||||
|
```
|
||||||
|
|
||||||
|
`addHelp()` registers a `help` command. It does not create a `--help` flag:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
cli.addHelp({
|
||||||
|
helpText: `
|
||||||
|
Usage:
|
||||||
|
tool build --target app
|
||||||
|
tool status
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
```sh
|
||||||
|
tool help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Programmatic Dispatch
|
||||||
|
|
||||||
|
Use `triggerCommand()` when you want to invoke a registered command yourself, for example in orchestration code or focused tests:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Smartcli } from '@push.rocks/smartcli';
|
||||||
|
|
||||||
|
const cli = new Smartcli();
|
||||||
|
|
||||||
|
cli.addCommand('build').subscribe((argv) => {
|
||||||
|
console.log(`Building ${argv.target}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
cli.triggerCommand('build', {
|
||||||
|
_: ['build'],
|
||||||
|
target: 'docs',
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
`triggerCommand()` throws if the command has not been registered.
|
||||||
|
|
||||||
|
## Testing CLIs
|
||||||
|
|
||||||
|
`startParse()` accepts an optional `argv` override. Pass a full runtime-style argument array for deterministic tests:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Smartcli } from '@push.rocks/smartcli';
|
||||||
|
|
||||||
|
const cli = new Smartcli();
|
||||||
|
const parsedPromise = cli.parseCompleted.promise;
|
||||||
|
|
||||||
|
cli.addCommand('publish').subscribe((argv) => {
|
||||||
|
console.log(`Publishing with tag ${argv.tag}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
cli.startParse(['node', 'test-cli.js', 'publish', '--tag', 'next']);
|
||||||
|
|
||||||
|
const parsed = await parsedPromise;
|
||||||
|
console.log(parsed.tag);
|
||||||
|
// next
|
||||||
|
```
|
||||||
|
|
||||||
|
Create a fresh `Smartcli` instance per parse. `parseCompleted` is a single deferred value on the instance and resolves once.
|
||||||
|
|
||||||
|
## Cross-Runtime Argument Handling
|
||||||
|
|
||||||
|
smartcli uses `getUserArgs()` internally to remove runtime-specific executable and script entries before parsing:
|
||||||
|
|
||||||
|
| Runtime mode | Argument strategy |
|
||||||
|
| --- | --- |
|
||||||
|
| Deno without an explicit test argv | Uses `Deno.args`, which is already user-only. |
|
||||||
|
| Node.js | Uses `process.argv` and skips executable plus script path for known launchers. |
|
||||||
|
| Bun | Uses `process.argv` and skips executable plus script path for known launchers. |
|
||||||
|
| `tsx` and `ts-node` | Treated as known launchers and parsed like Node.js. |
|
||||||
|
| Unknown compiled executable | Skips only the executable path, keeping the first user argument intact. |
|
||||||
|
|
||||||
|
This matters for compiled CLIs where `process.argv[0]` is the binary itself and the first user argument should not be mistaken for a script path.
|
||||||
|
|
||||||
|
## Aliases
|
||||||
|
|
||||||
|
The class exposes `addCommandAlias(original, alias)` and the public `aliasObject` for keeping alias metadata:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
cli.addCommandAlias('deploy', 'd');
|
||||||
|
console.log(cli.aliasObject.deploy);
|
||||||
|
// ['d']
|
||||||
|
```
|
||||||
|
|
||||||
|
Current command dispatch is exact-match based on `argv._[0]`. If you want executable aliases, register the alias as a command and subscribe it to the same handler:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { Smartcli } from '@push.rocks/smartcli';
|
||||||
|
|
||||||
|
const cli = new Smartcli();
|
||||||
|
|
||||||
|
const deploy = (argv: any) => {
|
||||||
|
console.log(`Deploying ${argv.target || 'default target'}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
cli.addCommand('deploy').subscribe(deploy);
|
||||||
|
cli.addCommand('d').subscribe(deploy);
|
||||||
|
cli.addCommandAlias('deploy', 'd');
|
||||||
|
|
||||||
|
cli.startParse();
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
| API | Purpose |
|
||||||
|
| --- | --- |
|
||||||
|
| `new Smartcli()` | Creates an isolated CLI parser and command registry. |
|
||||||
|
| `addCommand(commandName)` | Registers a command and returns its RxJS `Subject`. If the command already exists, the existing subject is reused. |
|
||||||
|
| `standardCommand()` | Registers and returns the fallback subject for runs without a command. |
|
||||||
|
| `startParse(testArgv?)` | Parses current user args, or the provided full argv array, and dispatches to the matching command. |
|
||||||
|
| `triggerCommand(commandName, argvObject)` | Manually emits an argv object into a registered command subject. |
|
||||||
|
| `getCommandSubject(commandName)` | Returns the subject for a registered command or `null`. |
|
||||||
|
| `getOption(optionName)` | Parses current runtime args and returns one option by name. Inside command handlers, prefer the provided `argv` object. |
|
||||||
|
| `addHelp({ helpText })` | Registers a `help` command that logs the provided help text. |
|
||||||
|
| `addVersion(version)` | Stores the version printed by `-v` or `--version` when no command is selected. |
|
||||||
|
| `addCommandAlias(original, alias)` | Stores alias metadata in `aliasObject`. Register alias command names separately for direct dispatch. |
|
||||||
|
| `parseCompleted.promise` | Resolves with the parsed argv object after normal `startParse()` dispatch. |
|
||||||
|
|
||||||
|
## Practical Example
|
||||||
|
|
||||||
|
This example wires a tiny task runner with commands, options, help, version output, and a standard fallback:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
#!/usr/bin/env node
|
||||||
|
import { Smartcli } from '@push.rocks/smartcli';
|
||||||
|
|
||||||
|
const cli = new Smartcli();
|
||||||
|
|
||||||
|
const tasks = new Map<string, () => Promise<void> | void>([
|
||||||
|
['build', () => console.log('Building project...')],
|
||||||
|
['test', () => console.log('Running tests...')],
|
||||||
|
['release', () => console.log('Preparing release...')],
|
||||||
|
]);
|
||||||
|
|
||||||
|
cli.addVersion('1.4.0');
|
||||||
|
|
||||||
|
cli.addHelp({
|
||||||
|
helpText: `
|
||||||
|
Task Runner
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
tasker list
|
||||||
|
tasker run <taskName> [--dry]
|
||||||
|
tasker --version
|
||||||
|
|
||||||
|
Available tasks:
|
||||||
|
build
|
||||||
|
test
|
||||||
|
release
|
||||||
|
`,
|
||||||
|
});
|
||||||
|
|
||||||
|
cli.addCommand('list').subscribe(() => {
|
||||||
|
for (const taskName of tasks.keys()) {
|
||||||
|
console.log(taskName);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cli.addCommand('run').subscribe(async (argv) => {
|
||||||
|
const taskName = argv._[1];
|
||||||
|
const dryRun = Boolean(argv.dry);
|
||||||
|
|
||||||
|
if (!taskName) {
|
||||||
|
console.log('Usage: tasker run <taskName> [--dry]');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const task = tasks.get(taskName);
|
||||||
|
|
||||||
|
if (!task) {
|
||||||
|
console.log(`Unknown task: ${taskName}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dryRun) {
|
||||||
|
console.log(`Would run task: ${taskName}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await task();
|
||||||
|
});
|
||||||
|
|
||||||
|
cli.standardCommand().subscribe(() => {
|
||||||
|
console.log('Usage: tasker <list|run|help>');
|
||||||
|
});
|
||||||
|
|
||||||
|
cli.startParse();
|
||||||
|
```
|
||||||
|
|
||||||
|
## Sharp Edges Worth Knowing
|
||||||
|
|
||||||
|
- Register commands before calling `startParse()`.
|
||||||
|
- `startParse()` dispatches once; create a new `Smartcli` instance for a second parse.
|
||||||
|
- `addHelp()` registers the `help` command, not a `--help` option.
|
||||||
|
- `addVersion()` prints only when no positional command is present.
|
||||||
|
- `getOption()` reads the current runtime args. In command subscriptions, use the `argv` object you receive for the most testable code.
|
||||||
|
- Alias metadata is stored, but aliases are not automatically dispatched unless you register the alias command name too.
|
||||||
|
- smartcli does not validate option schemas. Add validation in your own command handlers when inputs matter.
|
||||||
|
|
||||||
|
## License and Legal Information
|
||||||
|
|
||||||
|
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
|
||||||
|
|
||||||
|
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
### Trademarks
|
||||||
|
|
||||||
|
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
|
||||||
|
|
||||||
|
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
|
||||||
|
|
||||||
|
### Company Information
|
||||||
|
|
||||||
|
Task Venture Capital GmbH
|
||||||
|
Registered at District Court Bremen HRB 35230 HB, Germany
|
||||||
|
|
||||||
|
For any legal inquiries or further information, please contact us via email at hello@task.vc.
|
||||||
|
|
||||||
|
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.
|
||||||
Vendored
-1
@@ -1 +0,0 @@
|
|||||||
import "typings-test";
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
require("typings-test");
|
|
||||||
var smartcli = require("../dist/index");
|
|
||||||
var beautylog = require("beautylog");
|
|
||||||
var should = require("should");
|
|
||||||
describe("smartcli.Smartcli class", function () {
|
|
||||||
var smartCliTestObject;
|
|
||||||
describe("new Smartcli()", function () {
|
|
||||||
it("should create a new Smartcli", function () {
|
|
||||||
smartCliTestObject = new smartcli.Smartcli();
|
|
||||||
smartCliTestObject.should.be.instanceof(smartcli.Smartcli);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe(".addCommand", function () {
|
|
||||||
it("should add an command", function () {
|
|
||||||
smartCliTestObject.addCommand({
|
|
||||||
commandName: "awesome"
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe(".standardTask", function () {
|
|
||||||
it("should start parsing a standardTask", function (done) {
|
|
||||||
smartCliTestObject.standardTask()
|
|
||||||
.then(function () {
|
|
||||||
console.log("this is the standard Task!");
|
|
||||||
});
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe(".startParse", function () {
|
|
||||||
it("should start parsing the CLI input", function () {
|
|
||||||
smartCliTestObject.startParse();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
//# sourceMappingURL=data:application/json;charset=utf8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLFFBQU8sY0FBYyxDQUFDLENBQUE7QUFFdEIsSUFBTyxRQUFRLFdBQVcsZUFBZSxDQUFDLENBQUM7QUFDM0MsSUFBSSxTQUFTLEdBQUcsT0FBTyxDQUFDLFdBQVcsQ0FBQyxDQUFDO0FBQ3JDLElBQUksTUFBTSxHQUFHLE9BQU8sQ0FBQyxRQUFRLENBQUMsQ0FBQztBQUUvQixRQUFRLENBQUMseUJBQXlCLEVBQUM7SUFDL0IsSUFBSSxrQkFBb0MsQ0FBQztJQUN6QyxRQUFRLENBQUMsZ0JBQWdCLEVBQUM7UUFDdEIsRUFBRSxDQUFDLDhCQUE4QixFQUFDO1lBQzlCLGtCQUFrQixHQUFHLElBQUksUUFBUSxDQUFDLFFBQVEsRUFBRSxDQUFDO1lBQzdDLGtCQUFrQixDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsVUFBVSxDQUFDLFFBQVEsQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUMvRCxDQUFDLENBQUMsQ0FBQztJQUNQLENBQUMsQ0FBQyxDQUFDO0lBQ0gsUUFBUSxDQUFDLGFBQWEsRUFBQztRQUNuQixFQUFFLENBQUMsdUJBQXVCLEVBQUM7WUFDdkIsa0JBQWtCLENBQUMsVUFBVSxDQUFDO2dCQUMxQixXQUFXLEVBQUMsU0FBUzthQUN4QixDQUFDLENBQUM7UUFFUCxDQUFDLENBQUMsQ0FBQztJQUNQLENBQUMsQ0FBQyxDQUFDO0lBQ0gsUUFBUSxDQUFDLGVBQWUsRUFBQztRQUNyQixFQUFFLENBQUMscUNBQXFDLEVBQUMsVUFBUyxJQUFJO1lBQ2xELGtCQUFrQixDQUFDLFlBQVksRUFBRTtpQkFDNUIsSUFBSSxDQUFDO2dCQUNGLE9BQU8sQ0FBQyxHQUFHLENBQUMsNEJBQTRCLENBQUMsQ0FBQztZQUM5QyxDQUFDLENBQUMsQ0FBQztZQUNQLElBQUksRUFBRSxDQUFDO1FBQ1gsQ0FBQyxDQUFDLENBQUE7SUFDTixDQUFDLENBQUMsQ0FBQTtJQUNGLFFBQVEsQ0FBQyxhQUFhLEVBQUM7UUFDbkIsRUFBRSxDQUFDLG9DQUFvQyxFQUFDO1lBQ3BDLGtCQUFrQixDQUFDLFVBQVUsRUFBRSxDQUFDO1FBQ3BDLENBQUMsQ0FBQyxDQUFBO0lBQ04sQ0FBQyxDQUFDLENBQUE7QUFDTixDQUFDLENBQUMsQ0FBQyIsImZpbGUiOiJ0ZXN0LmpzIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IFwidHlwaW5ncy10ZXN0XCI7XG5cbmltcG9ydCBzbWFydGNsaSA9IHJlcXVpcmUoXCIuLi9kaXN0L2luZGV4XCIpO1xubGV0IGJlYXV0eWxvZyA9IHJlcXVpcmUoXCJiZWF1dHlsb2dcIik7XG5sZXQgc2hvdWxkID0gcmVxdWlyZShcInNob3VsZFwiKTtcblxuZGVzY3JpYmUoXCJzbWFydGNsaS5TbWFydGNsaSBjbGFzc1wiLGZ1bmN0aW9uKCl7XG4gICAgbGV0IHNtYXJ0Q2xpVGVzdE9iamVjdDpzbWFydGNsaS5TbWFydGNsaTtcbiAgICBkZXNjcmliZShcIm5ldyBTbWFydGNsaSgpXCIsZnVuY3Rpb24oKXtcbiAgICAgICAgaXQoXCJzaG91bGQgY3JlYXRlIGEgbmV3IFNtYXJ0Y2xpXCIsZnVuY3Rpb24oKXtcbiAgICAgICAgICAgIHNtYXJ0Q2xpVGVzdE9iamVjdCA9IG5ldyBzbWFydGNsaS5TbWFydGNsaSgpO1xuICAgICAgICAgICAgc21hcnRDbGlUZXN0T2JqZWN0LnNob3VsZC5iZS5pbnN0YW5jZW9mKHNtYXJ0Y2xpLlNtYXJ0Y2xpKTtcbiAgICAgICAgfSk7XG4gICAgfSk7XG4gICAgZGVzY3JpYmUoXCIuYWRkQ29tbWFuZFwiLGZ1bmN0aW9uKCl7XG4gICAgICAgIGl0KFwic2hvdWxkIGFkZCBhbiBjb21tYW5kXCIsZnVuY3Rpb24oKXtcbiAgICAgICAgICAgIHNtYXJ0Q2xpVGVzdE9iamVjdC5hZGRDb21tYW5kKHtcbiAgICAgICAgICAgICAgICBjb21tYW5kTmFtZTpcImF3ZXNvbWVcIlxuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICBcbiAgICAgICAgfSk7XG4gICAgfSk7XG4gICAgZGVzY3JpYmUoXCIuc3RhbmRhcmRUYXNrXCIsZnVuY3Rpb24oKXtcbiAgICAgICAgaXQoXCJzaG91bGQgc3RhcnQgcGFyc2luZyBhIHN0YW5kYXJkVGFza1wiLGZ1bmN0aW9uKGRvbmUpe1xuICAgICAgICAgICAgc21hcnRDbGlUZXN0T2JqZWN0LnN0YW5kYXJkVGFzaygpXG4gICAgICAgICAgICAgICAgLnRoZW4oKCkgPT4ge1xuICAgICAgICAgICAgICAgICAgICBjb25zb2xlLmxvZyhcInRoaXMgaXMgdGhlIHN0YW5kYXJkIFRhc2shXCIpO1xuICAgICAgICAgICAgICAgIH0pO1xuICAgICAgICAgICAgZG9uZSgpO1xuICAgICAgICB9KVxuICAgIH0pXG4gICAgZGVzY3JpYmUoXCIuc3RhcnRQYXJzZVwiLGZ1bmN0aW9uKCl7XG4gICAgICAgIGl0KFwic2hvdWxkIHN0YXJ0IHBhcnNpbmcgdGhlIENMSSBpbnB1dFwiLGZ1bmN0aW9uKCl7XG4gICAgICAgICAgICBzbWFydENsaVRlc3RPYmplY3Quc3RhcnRQYXJzZSgpO1xuICAgICAgICB9KVxuICAgIH0pXG59KTtcbiJdfQ==
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"test.js","sourceRoot":"","sources":["test.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,IAAI,QAAQ,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAAC;AAC3C,IAAI,MAAM,GAAG,GAAG,CAAA;AAEhB;;wEAEwE;AAExE,QAAQ,CAAC,UAAU,EAAC;IAChB,QAAQ,CAAC,QAAQ,EAAC;QACd,QAAQ,CAAC,UAAU,EAAC;YAChB,EAAE,CAAC,oDAAoD,EAAC;gBACpD,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;YACpD,CAAC,CAAC,CAAC;YACH,EAAE,CAAC,yDAAyD,EAAC;gBACzD,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;YACrD,CAAC,CAAC,CAAC;QACP,CAAC,CAAC,CAAC;QACH,QAAQ,CAAC,kBAAkB,EAAC;QAE5B,CAAC,CAAC,CAAC;IACP,CAAC,CAAC,CAAC;AACP,CAAC,CAAC,CAAC;AAKH,IAAI,wBAAwB,GAAG;IAC3B,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,eAAe,CAAC,KAAK,EAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC1C,SAAS,CAAC,OAAO,CAAC,uCAAuC,CAAC,CAAC;IAC/D,CAAC;IAAC,IAAI,CAAC,CAAC;QACJ,SAAS,CAAC,KAAK,CAAC,wDAAwD,CAAC,CAAC;QAC1E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IACD,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,eAAe,CAAC,SAAS,EAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/C,SAAS,CAAC,OAAO,CAAC,mEAAmE,CAAC,CAAC;IAC3F,CAAC;IAAC,IAAI,CAAC,CAAC;QACJ,SAAS,CAAC,KAAK,CAAC,gFAAgF,CAAC,CAAC;QAClG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC,CAAC;AACF,wBAAwB,EAAE,CAAC;AAE3B,IAAI,gCAAgC,GAAG;IACnC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC5C,SAAS,CAAC,OAAO,CAAC,uCAAuC,CAAC,CAAC;IAC/D,CAAC;IAAC,IAAI,CAAC,CAAC;QACJ,SAAS,CAAC,KAAK,CAAC,wDAAwD,CAAC,CAAC;QAC1E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IACD,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7C,SAAS,CAAC,OAAO,CAAC,wCAAwC,CAAC,CAAC;IAChE,CAAC;IAAC,IAAI,CAAC,CAAC;QACJ,SAAS,CAAC,KAAK,CAAC,uDAAuD,CAAC,CAAC;QACzE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC,CAAC;AACF,gCAAgC,EAAE,CAAC;AAEnC;;wEAEwE;AACxE,IAAI,cAAc,GAAG;IACjB,IAAI,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;IACxC,EAAE,CAAA,CAAC,UAAU,CAAC,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC;QAC3B,SAAS,CAAC,OAAO,CAAC,gDAAgD,CAAC,CAAC;IACxE,CAAC;IAAC,IAAI,CAAC,CAAC;QACJ,SAAS,CAAC,KAAK,CAAC,+DAA+D,CAAC,CAAC;QACjF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AAEL,CAAC,CAAC;AACF,cAAc,EAAE,CAAC;AAEjB,IAAI,sBAAsB,GAAG;IACzB,IAAI,WAAW,GAAG,QAAQ,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IAClD,IAAI,YAAY,GAAG,QAAQ,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IACnD,EAAE,CAAA,CAAC,WAAW,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC;QAC3B,SAAS,CAAC,OAAO,CAAC,8DAA8D,CAAC,CAAC;IACtF,CAAC;IAAC,IAAI,CAAC,CAAC;QACJ,SAAS,CAAC,KAAK,CAAC,6EAA6E,CAAC,CAAC;QAC/F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,EAAE,CAAA,CAAC,YAAY,CAAC,IAAI,IAAI,WAAW,CAAC,CAAC,CAAC;QAClC,SAAS,CAAC,OAAO,CAAC,qEAAqE,CAAC,CAAC;IAC7F,CAAC;IAAC,IAAI,CAAC,CAAC;QACJ,SAAS,CAAC,KAAK,CAAC,oFAAoF,CAAC,CAAC;QACtG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC,CAAC;AACF,sBAAsB,EAAE,CAAC;AAEzB,IAAI,kBAAkB,GAAG;IACrB,IAAI,WAAW,GAAG,QAAQ,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;IAC7C,EAAE,CAAA,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC;QAC9B,SAAS,CAAC,OAAO,CAAC,2CAA2C,CAAC,CAAC;IACnE,CAAC;IAAC,IAAI,CAAC,CAAC;QACJ,SAAS,CAAC,KAAK,CAAC,0DAA0D,CAAC,CAAC;QAC5E,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACjC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC,CAAC;AACF,kBAAkB,EAAE,CAAC;AAErB,IAAI,aAAa,GAAG;IAChB,IAAI,SAAS,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IAC/C,IAAI,UAAU,GAAG,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IACjD,EAAE,CAAA,CAAC,SAAS,CAAC,SAAS,CAAC,CAAA,CAAC;QACpB,SAAS,CAAC,OAAO,CAAC,gCAAgC,CAAC,CAAA;IACvD,CAAC;IAAC,IAAI,CAAC,CAAC;QACJ,SAAS,CAAC,KAAK,CAAC,+CAA+C,CAAC,CAAC;QACjE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;IACD,EAAE,CAAA,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,CAAA,CAAC;QACtB,SAAS,CAAC,OAAO,CAAC,qCAAqC,CAAC,CAAA;IAC5D,CAAC;IAAC,IAAI,CAAC,CAAC;QACJ,SAAS,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC;QAC9D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC;AACL,CAAC,CAAC;AACF,aAAa,EAAE,CAAC;AAEhB,IAAI,UAAU,GAAG;IACb,SAAS,CAAC,IAAI,CAAC,4BAA4B,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;AAC3E,CAAC,CAAC;AACF,UAAU,EAAE,CAAC;AAGb;;wEAEwE;AAGxE,IAAI,wBAAwB,GAAG;IAC3B,QAAQ,CAAC,WAAW,CAAC,SAAS,CAAC,kBAAkB,EAAC,UAAS,MAAM;QAC7D,OAAO,CAAC,GAAG,CAAC,iBAAiB,GAAG,MAAM,CAAC,CAAC;QACxC,wBAAwB,EAAE,CAAC;IAC/B,CAAC,CAAC,CAAC;AACP,CAAC,CAAC;AAIF,IAAI,wBAAwB,GAAG;IAC3B,QAAQ,CAAC,WAAW,CAAC,SAAS,CAAC,iCAAiC,EAAC,CAAC,MAAM,EAAC,OAAO,EAAC,WAAW,CAAC,EAAC,UAAS,MAAM;QACzG,OAAO,CAAC,GAAG,CAAC,iBAAiB,GAAG,MAAM,CAAC,CAAC;QACxC,QAAQ,EAAE,CAAC;IACf,CAAC,CAAC,CAAC;AACP,CAAC,CAAC;AAEF,IAAI,QAAQ,GAAG;IACX,SAAS,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC;IAC/B,SAAS,CAAC,OAAO,CAAC,+BAA+B,CAAC,CAAC;AACvD,CAAC,CAAC;AAEF,EAAE,CAAA,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAA,CAAC;IACjC,wBAAwB,EAAE,CAAC;AAC/B,CAAC;AAAC,IAAI,CAAC,CAAC;IACJ,SAAS,CAAC,IAAI,CAAC,0EAA0E,CAAC,CAAC;IAC3F,QAAQ,EAAE,CAAC;AACf,CAAC;AAAA,CAAC"}
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
import * as smartrx from '@push.rocks/smartrx';
|
||||||
|
|
||||||
|
import * as smartcli from '../ts/index.js';
|
||||||
|
|
||||||
|
tap.test('should create a new Smartcli', async () => {
|
||||||
|
const smartCliTestObject = new smartcli.Smartcli();
|
||||||
|
expect(smartCliTestObject).toBeInstanceOf(smartcli.Smartcli);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should add an command', async (toolsArg) => {
|
||||||
|
const done = toolsArg.defer();
|
||||||
|
const smartCliTestObject = new smartcli.Smartcli();
|
||||||
|
const awesomeCommandSubject = smartCliTestObject.addCommand('awesome');
|
||||||
|
expect(awesomeCommandSubject).toBeInstanceOf(smartrx.rxjs.Subject);
|
||||||
|
awesomeCommandSubject.subscribe(() => {
|
||||||
|
done.resolve();
|
||||||
|
});
|
||||||
|
smartCliTestObject.startParse(['node', 'test.js', 'awesome']);
|
||||||
|
await done.promise;
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should start parsing a standardTask', async () => {
|
||||||
|
const smartCliTestObject = new smartcli.Smartcli();
|
||||||
|
expect(smartCliTestObject.standardCommand()).toBeInstanceOf(smartrx.rxjs.Subject);
|
||||||
|
});
|
||||||
|
|
||||||
|
let hasExecuted: boolean = false;
|
||||||
|
|
||||||
|
tap.test('should accept a command', async () => {
|
||||||
|
const smartCliTestObject = new smartcli.Smartcli();
|
||||||
|
smartCliTestObject.addCommand('triggerme').subscribe(() => {
|
||||||
|
hasExecuted = true;
|
||||||
|
});
|
||||||
|
smartCliTestObject.triggerCommand('triggerme', {});
|
||||||
|
expect(hasExecuted).toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
||||||
@@ -0,0 +1,190 @@
|
|||||||
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
|
||||||
|
import * as smartcli from '../ts/index.js';
|
||||||
|
|
||||||
|
class TestWritable implements smartcli.ISmartcliWritable {
|
||||||
|
public chunks: string[] = [];
|
||||||
|
public isTTY: boolean;
|
||||||
|
public columns = 80;
|
||||||
|
|
||||||
|
constructor(isTTYArg: boolean) {
|
||||||
|
this.isTTY = isTTYArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public write(chunkArg: string): boolean {
|
||||||
|
this.chunks.push(chunkArg);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public toString(): string {
|
||||||
|
return this.chunks.join('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tap.test('should render terminal tasks in non-interactive mode', async () => {
|
||||||
|
const stream = new TestWritable(false);
|
||||||
|
const terminal = new smartcli.SmartcliTerminal({
|
||||||
|
stream,
|
||||||
|
interactive: false,
|
||||||
|
colors: false,
|
||||||
|
symbols: 'ascii',
|
||||||
|
nonInteractiveThrottleMs: 0,
|
||||||
|
});
|
||||||
|
const task = terminal.createTask({ job: 'build package', rows: 2 });
|
||||||
|
|
||||||
|
task.update('running tsbuild');
|
||||||
|
task.complete('done');
|
||||||
|
|
||||||
|
const output = stream.toString();
|
||||||
|
expect(output).toInclude('start build package');
|
||||||
|
expect(output).toInclude('update build package: running tsbuild');
|
||||||
|
expect(output).toInclude('done build package in');
|
||||||
|
expect(output).toInclude(': done');
|
||||||
|
expect(terminal.getTasks()).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should render fixed rows in interactive mode', async () => {
|
||||||
|
const stream = new TestWritable(true);
|
||||||
|
const terminal = new smartcli.SmartcliTerminal({
|
||||||
|
stream,
|
||||||
|
interactive: true,
|
||||||
|
colors: false,
|
||||||
|
symbols: 'ascii',
|
||||||
|
cleanup: false,
|
||||||
|
});
|
||||||
|
const task = terminal.createTask({ job: 'install dependencies', rows: 2 });
|
||||||
|
|
||||||
|
task.update('fetching packages');
|
||||||
|
|
||||||
|
const renderedRows = task.renderPlainRows(80);
|
||||||
|
expect(renderedRows).toHaveLength(2);
|
||||||
|
expect(renderedRows[0]).toInclude('* install dependencies');
|
||||||
|
expect(stream.toString()).toInclude('\u001B[?25l');
|
||||||
|
expect(stream.toString()).toInclude('\u001B[2K');
|
||||||
|
|
||||||
|
task.complete('installed');
|
||||||
|
|
||||||
|
expect(stream.toString()).toInclude('OK install dependencies');
|
||||||
|
expect(stream.toString()).toInclude('installed');
|
||||||
|
expect(stream.toString()).toInclude('\u001B[?25h');
|
||||||
|
expect(terminal.getTasks()).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should attach persistent terminal task errors', async () => {
|
||||||
|
const stream = new TestWritable(true);
|
||||||
|
const terminal = new smartcli.SmartcliTerminal({
|
||||||
|
stream,
|
||||||
|
interactive: true,
|
||||||
|
colors: false,
|
||||||
|
symbols: 'ascii',
|
||||||
|
cleanup: false,
|
||||||
|
});
|
||||||
|
const task = terminal.createTask({ job: 'deploy release', rows: 3 });
|
||||||
|
|
||||||
|
task.attachError('deployment failed', { keepOpen: true });
|
||||||
|
|
||||||
|
expect(task.status).toEqual('failed');
|
||||||
|
expect(task.getErrorLines()).toContain('deployment failed');
|
||||||
|
expect(task.renderPlainRows(80)[0]).toInclude('X deploy release');
|
||||||
|
expect(terminal.getTasks()).toHaveLength(1);
|
||||||
|
|
||||||
|
terminal.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should collapse failed terminal tasks into permanent output', async () => {
|
||||||
|
const stream = new TestWritable(false);
|
||||||
|
const terminal = new smartcli.SmartcliTerminal({
|
||||||
|
stream,
|
||||||
|
interactive: false,
|
||||||
|
colors: false,
|
||||||
|
symbols: 'ascii',
|
||||||
|
});
|
||||||
|
const task = terminal.createTask({ job: 'publish package' });
|
||||||
|
|
||||||
|
task.attachError('registry rejected package');
|
||||||
|
|
||||||
|
const output = stream.toString();
|
||||||
|
expect(output).toInclude('fail publish package in');
|
||||||
|
expect(output).toInclude(': registry rejected package');
|
||||||
|
expect(terminal.getTasks()).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should set task progress', async () => {
|
||||||
|
const stream = new TestWritable(true);
|
||||||
|
const terminal = new smartcli.SmartcliTerminal({
|
||||||
|
stream,
|
||||||
|
interactive: true,
|
||||||
|
colors: false,
|
||||||
|
symbols: 'ascii',
|
||||||
|
cleanup: false,
|
||||||
|
});
|
||||||
|
const task = terminal.task('process files', { rows: 2 });
|
||||||
|
|
||||||
|
task.setProgress(2, 5, 'processed files');
|
||||||
|
|
||||||
|
expect(task.getLastLogLine()).toInclude('40% (2/5)');
|
||||||
|
expect(task.renderPlainRows(80)[0]).toInclude('40% (2/5)');
|
||||||
|
terminal.clear();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should auto-complete task.run', async () => {
|
||||||
|
const stream = new TestWritable(false);
|
||||||
|
const terminal = new smartcli.SmartcliTerminal({
|
||||||
|
stream,
|
||||||
|
interactive: false,
|
||||||
|
colors: false,
|
||||||
|
nonInteractiveThrottleMs: 0,
|
||||||
|
});
|
||||||
|
const task = terminal.task('run operation');
|
||||||
|
|
||||||
|
const result = await task.run(async (taskArg) => {
|
||||||
|
taskArg.update('inside operation');
|
||||||
|
return 'result';
|
||||||
|
}, { successMessage: 'operation finished' });
|
||||||
|
|
||||||
|
expect(result).toEqual('result');
|
||||||
|
expect(task.status).toEqual('completed');
|
||||||
|
expect(stream.toString()).toInclude('done run operation in');
|
||||||
|
expect(stream.toString()).toInclude('operation finished');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should auto-fail task.run', async () => {
|
||||||
|
const stream = new TestWritable(false);
|
||||||
|
const terminal = new smartcli.SmartcliTerminal({ stream, interactive: false, colors: false });
|
||||||
|
const task = terminal.task('failing operation');
|
||||||
|
|
||||||
|
let caughtError: Error | undefined;
|
||||||
|
try {
|
||||||
|
await task.run(async () => {
|
||||||
|
throw new Error('operation failed');
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
caughtError = error as Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(caughtError?.message).toEqual('operation failed');
|
||||||
|
expect(task.status).toEqual('failed');
|
||||||
|
expect(stream.toString()).toInclude('fail failing operation in');
|
||||||
|
expect(stream.toString()).toInclude('operation failed');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should throttle duplicate non-interactive updates', async () => {
|
||||||
|
const stream = new TestWritable(false);
|
||||||
|
const terminal = new smartcli.SmartcliTerminal({
|
||||||
|
stream,
|
||||||
|
interactive: false,
|
||||||
|
colors: false,
|
||||||
|
nonInteractiveThrottleMs: 10000,
|
||||||
|
});
|
||||||
|
const task = terminal.task('quiet task');
|
||||||
|
|
||||||
|
task.update('same update');
|
||||||
|
task.update('same update');
|
||||||
|
task.update('different but throttled');
|
||||||
|
|
||||||
|
const output = stream.toString();
|
||||||
|
expect(output).toInclude('update quiet task: same update');
|
||||||
|
expect(output).not.toInclude('different but throttled');
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import "typings-test";
|
|
||||||
|
|
||||||
import smartcli = require("../dist/index");
|
|
||||||
let beautylog = require("beautylog");
|
|
||||||
let should = require("should");
|
|
||||||
|
|
||||||
describe("smartcli.Smartcli class",function(){
|
|
||||||
let smartCliTestObject:smartcli.Smartcli;
|
|
||||||
describe("new Smartcli()",function(){
|
|
||||||
it("should create a new Smartcli",function(){
|
|
||||||
smartCliTestObject = new smartcli.Smartcli();
|
|
||||||
smartCliTestObject.should.be.instanceof(smartcli.Smartcli);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe(".addCommand",function(){
|
|
||||||
it("should add an command",function(){
|
|
||||||
smartCliTestObject.addCommand({
|
|
||||||
commandName:"awesome"
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
describe(".standardTask",function(){
|
|
||||||
it("should start parsing a standardTask",function(done){
|
|
||||||
smartCliTestObject.standardTask()
|
|
||||||
.then(() => {
|
|
||||||
console.log("this is the standard Task!");
|
|
||||||
});
|
|
||||||
done();
|
|
||||||
})
|
|
||||||
})
|
|
||||||
describe(".startParse",function(){
|
|
||||||
it("should start parsing the CLI input",function(){
|
|
||||||
smartCliTestObject.startParse();
|
|
||||||
})
|
|
||||||
})
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* autocreated commitinfo by @push.rocks/commitinfo
|
||||||
|
*/
|
||||||
|
export const commitinfo = {
|
||||||
|
name: '@push.rocks/smartcli',
|
||||||
|
version: '4.2.0',
|
||||||
|
description: 'A library that simplifies building reactive command-line applications using observables, with robust support for commands, arguments, options, aliases, and asynchronous operation management.'
|
||||||
|
}
|
||||||
+11
-4
@@ -1,4 +1,11 @@
|
|||||||
import "typings-global";
|
export { Smartcli } from './smartcli.classes.smartcli.js';
|
||||||
|
export { SmartcliTerminal, SmartcliTerminalTask } from './smartcli.classes.terminal.js';
|
||||||
import {Smartcli} from "./smartcli.classes.smartcli";
|
export type {
|
||||||
export {Smartcli} from "./smartcli.classes.smartcli";
|
ISmartcliTerminalAttachErrorOptions,
|
||||||
|
ISmartcliTerminalOptions,
|
||||||
|
ISmartcliTerminalTaskRunOptions,
|
||||||
|
ISmartcliTerminalTaskOptions,
|
||||||
|
ISmartcliWritable,
|
||||||
|
TSmartcliTerminalSymbolMode,
|
||||||
|
TSmartcliTerminalTaskStatus,
|
||||||
|
} from './smartcli.classes.terminal.js';
|
||||||
|
|||||||
+156
-55
@@ -1,63 +1,164 @@
|
|||||||
import "typings-global";
|
import * as plugins from './smartcli.plugins.js';
|
||||||
|
import { getUserArgs } from './smartcli.helpers.js';
|
||||||
|
|
||||||
import * as plugins from "./smartcli.plugins"
|
// interfaces
|
||||||
import * as SmartcliInteractions from "./smartcli.interaction";
|
export interface ICommandObservableObject {
|
||||||
|
commandName: string;
|
||||||
|
subject: plugins.smartrx.rxjs.Subject<any>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const logger = new plugins.smartlog.ConsoleLog();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* class to create a new instance of Smartcli. Handles parsing of command line arguments.
|
||||||
|
*/
|
||||||
export class Smartcli {
|
export class Smartcli {
|
||||||
argv;
|
/**
|
||||||
questionsDone;
|
* this Deferred contains the parsed result in the end
|
||||||
parseStarted;
|
*/
|
||||||
commands;
|
public parseCompleted = plugins.smartpromise.defer<any>();
|
||||||
questions;
|
|
||||||
version:string;
|
|
||||||
constructor(){
|
|
||||||
this.argv = plugins.argv;
|
|
||||||
this.questionsDone = plugins.q.defer();
|
|
||||||
this.parseStarted = plugins.q.defer();
|
|
||||||
}
|
|
||||||
addAlias(keyArg,aliasArg){
|
|
||||||
this.argv = this.argv.alias(keyArg,aliasArg);
|
|
||||||
};
|
|
||||||
addCommand(definitionArg:{commandName:string}){
|
|
||||||
let done = plugins.q.defer();
|
|
||||||
this.parseStarted.promise
|
|
||||||
.then(() => {
|
|
||||||
if (this.argv._.indexOf(definitionArg.commandName) == 0) {
|
|
||||||
done.resolve(this.argv);
|
|
||||||
} else {
|
|
||||||
return done.reject();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return done.promsise;
|
|
||||||
};
|
|
||||||
addQuestion(definitionArg:{questionString:string,questionType:string}){
|
|
||||||
|
|
||||||
};
|
public version?: string;
|
||||||
addVersion(versionArg:string){
|
|
||||||
|
/**
|
||||||
|
* map of all Trigger/Observable objects to keep track
|
||||||
|
*/
|
||||||
|
private commandObservableMap = new plugins.lik.ObjectMap<ICommandObservableObject>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* maps alias
|
||||||
|
*/
|
||||||
|
public aliasObject: { [key: string]: string[] } = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The constructor of Smartcli
|
||||||
|
*/
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* adds an alias, meaning one equals the other in terms of command execution.
|
||||||
|
*/
|
||||||
|
public addCommandAlias(originalArg: string, aliasArg: string): void {
|
||||||
|
this.aliasObject[originalArg] = this.aliasObject[originalArg] || [];
|
||||||
|
this.aliasObject[originalArg].push(aliasArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* adds a Command by returning a Promise that reacts to the specific commandString given.
|
||||||
|
* Note: in e.g. "npm install something" the "install" is considered the command.
|
||||||
|
*/
|
||||||
|
public addCommand(commandNameArg: string): plugins.smartrx.rxjs.Subject<any> {
|
||||||
|
let commandSubject: plugins.smartrx.rxjs.Subject<any>;
|
||||||
|
const existingCommandSubject = this.getCommandSubject(commandNameArg);
|
||||||
|
commandSubject = existingCommandSubject || new plugins.smartrx.rxjs.Subject<any>();
|
||||||
|
|
||||||
|
this.commandObservableMap.add({
|
||||||
|
commandName: commandNameArg,
|
||||||
|
subject: commandSubject,
|
||||||
|
});
|
||||||
|
return commandSubject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* execute trigger by name
|
||||||
|
* @param commandNameArg - the name of the command to trigger
|
||||||
|
*/
|
||||||
|
public triggerCommand(commandNameArg: string, argvObject: any) {
|
||||||
|
const triggerSubject = this.getCommandSubject(commandNameArg);
|
||||||
|
if (!triggerSubject) {
|
||||||
|
throw new Error(`No smartcli command registered for ${commandNameArg}`);
|
||||||
|
}
|
||||||
|
triggerSubject.next(argvObject);
|
||||||
|
return triggerSubject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gets the command subject for the specified name.
|
||||||
|
* call this before calling .parse()
|
||||||
|
* @param commandNameArg
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public getCommandSubject(commandNameArg: string) {
|
||||||
|
const triggerObservableObject = this.commandObservableMap.findSync(
|
||||||
|
(triggerObservableObjectArg) => {
|
||||||
|
return triggerObservableObjectArg.commandName === commandNameArg;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
if (triggerObservableObject) {
|
||||||
|
return triggerObservableObject.subject;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* getOption
|
||||||
|
*/
|
||||||
|
public getOption(optionNameArg: string) {
|
||||||
|
const userArgs = getUserArgs();
|
||||||
|
const parsedYargs = plugins.yargsParser(userArgs);
|
||||||
|
return parsedYargs[optionNameArg];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* allows to specify help text to be printed above the rest of the help text
|
||||||
|
*/
|
||||||
|
public addHelp(optionsArg: { helpText: string }) {
|
||||||
|
this.addCommand('help').subscribe((argvArg) => {
|
||||||
|
logger.log('info', optionsArg.helpText);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* specify version to be printed for -v --version
|
||||||
|
*/
|
||||||
|
public addVersion(versionArg: string) {
|
||||||
this.version = versionArg;
|
this.version = versionArg;
|
||||||
this.addAlias("v","version");
|
|
||||||
this.parseStarted.promise
|
|
||||||
.then(() => {
|
|
||||||
if(this.argv.v){
|
|
||||||
console.log(this.version);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
standardTask(){
|
|
||||||
let done = plugins.q.defer();
|
|
||||||
this.parseStarted.promise
|
|
||||||
.then(() => {
|
|
||||||
if(this.argv._.length == 0 && !this.argv.v){
|
|
||||||
done.resolve();
|
|
||||||
} else {
|
|
||||||
done.reject();
|
|
||||||
};
|
|
||||||
});
|
|
||||||
return done.promise;
|
|
||||||
}
|
|
||||||
startParse(){
|
|
||||||
this.argv = this.argv.argv;
|
|
||||||
this.parseStarted.resolve();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* adds a trigger that is called when no command is specified
|
||||||
|
*/
|
||||||
|
public standardCommand(): plugins.smartrx.rxjs.Subject<any> {
|
||||||
|
const standardSubject = this.addCommand('standardCommand');
|
||||||
|
return standardSubject;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* start the process of evaluating commands
|
||||||
|
* @param testArgv - Optional argv override for testing (bypasses automatic runtime detection)
|
||||||
|
*/
|
||||||
|
public startParse(testArgv?: string[]): void {
|
||||||
|
// Get user arguments, properly handling Node.js, Deno (run/compiled), and Bun
|
||||||
|
const userArgs = testArgv ? getUserArgs(testArgv) : getUserArgs();
|
||||||
|
const parsedYArgs = plugins.yargsParser(userArgs);
|
||||||
|
const wantedCommand = parsedYArgs._[0];
|
||||||
|
|
||||||
|
// lets handle some standards
|
||||||
|
if (!wantedCommand && (parsedYArgs.v || parsedYArgs.version)) {
|
||||||
|
console.log(this.version || 'unknown version');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const command of this.commandObservableMap.getArray()) {
|
||||||
|
if (!wantedCommand) {
|
||||||
|
const standardCommand = this.commandObservableMap.findSync((commandArg) => {
|
||||||
|
return commandArg.commandName === 'standardCommand';
|
||||||
|
});
|
||||||
|
if (standardCommand) {
|
||||||
|
standardCommand.subject.next(parsedYArgs);
|
||||||
|
} else {
|
||||||
|
console.log('no smartcli standard task was created or assigned.');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (command.commandName === parsedYArgs._[0]) {
|
||||||
|
command.subject.next(parsedYArgs);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (this.aliasObject[parsedYArgs[0]]) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.parseCompleted.resolve(parsedYArgs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,636 @@
|
|||||||
|
export type TSmartcliTerminalTaskStatus = 'running' | 'completed' | 'failed';
|
||||||
|
export type TSmartcliTerminalSymbolMode = 'auto' | 'unicode' | 'ascii';
|
||||||
|
|
||||||
|
export interface ISmartcliWritable {
|
||||||
|
isTTY?: boolean;
|
||||||
|
columns?: number;
|
||||||
|
write(chunk: string): void | boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISmartcliTerminalOptions {
|
||||||
|
stream?: ISmartcliWritable;
|
||||||
|
interactive?: boolean;
|
||||||
|
colors?: boolean;
|
||||||
|
symbols?: TSmartcliTerminalSymbolMode;
|
||||||
|
cleanup?: boolean;
|
||||||
|
nonInteractiveThrottleMs?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISmartcliTerminalTaskOptions {
|
||||||
|
job: string;
|
||||||
|
rows?: number;
|
||||||
|
logLimit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISmartcliTerminalAttachErrorOptions {
|
||||||
|
keepOpen?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISmartcliTerminalTaskRunOptions {
|
||||||
|
successMessage?: string;
|
||||||
|
errorKeepOpen?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface INonInteractiveLogState {
|
||||||
|
message: string;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ansiCodes = {
|
||||||
|
reset: '\u001B[0m',
|
||||||
|
red: '\u001B[31m',
|
||||||
|
green: '\u001B[32m',
|
||||||
|
cyan: '\u001B[36m',
|
||||||
|
gray: '\u001B[90m',
|
||||||
|
};
|
||||||
|
|
||||||
|
const unicodeSymbols = {
|
||||||
|
running: '●',
|
||||||
|
completed: '✓',
|
||||||
|
failed: '✕',
|
||||||
|
};
|
||||||
|
|
||||||
|
const asciiSymbols = {
|
||||||
|
running: '*',
|
||||||
|
completed: 'OK',
|
||||||
|
failed: 'X',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A live terminal renderer for multiple fixed-row tasks.
|
||||||
|
* It automatically falls back to append-only logs in non-interactive environments.
|
||||||
|
*/
|
||||||
|
export class SmartcliTerminal {
|
||||||
|
private stream: ISmartcliWritable;
|
||||||
|
private interactive: boolean;
|
||||||
|
private colors: boolean;
|
||||||
|
private useUnicodeSymbols: boolean;
|
||||||
|
private cleanupEnabled: boolean;
|
||||||
|
private nonInteractiveThrottleMs: number;
|
||||||
|
private tasks: SmartcliTerminalTask[] = [];
|
||||||
|
private renderedLineCount = 0;
|
||||||
|
private lastRenderedOutput = '';
|
||||||
|
private cursorHidden = false;
|
||||||
|
private cleanupRegistered = false;
|
||||||
|
private nonInteractiveLogState = new Map<SmartcliTerminalTask, INonInteractiveLogState>();
|
||||||
|
private cleanupHandlers: Array<() => void> = [];
|
||||||
|
|
||||||
|
constructor(optionsArg: ISmartcliTerminalOptions = {}) {
|
||||||
|
this.stream = optionsArg.stream || getDefaultStream();
|
||||||
|
this.interactive = getInteractiveState(this.stream, optionsArg.interactive);
|
||||||
|
this.colors = optionsArg.colors ?? (this.interactive && !hasEnvFlag('NO_COLOR'));
|
||||||
|
this.useUnicodeSymbols = getUnicodeSymbolState(this.stream, optionsArg.symbols);
|
||||||
|
this.cleanupEnabled = optionsArg.cleanup ?? true;
|
||||||
|
this.nonInteractiveThrottleMs = Math.max(0, optionsArg.nonInteractiveThrottleMs ?? 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
public createTask(optionsArg: ISmartcliTerminalTaskOptions): SmartcliTerminalTask {
|
||||||
|
const task = new SmartcliTerminalTask(this, optionsArg);
|
||||||
|
this.tasks.push(task);
|
||||||
|
|
||||||
|
if (this.interactive) {
|
||||||
|
this.ensureInteractiveSession();
|
||||||
|
this.render();
|
||||||
|
} else {
|
||||||
|
this.writePermanentLine(`start ${task.job}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return task;
|
||||||
|
}
|
||||||
|
|
||||||
|
public createProcess(optionsArg: ISmartcliTerminalTaskOptions): SmartcliTerminalTask {
|
||||||
|
return this.createTask(optionsArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public task(jobArg: string, optionsArg: Omit<ISmartcliTerminalTaskOptions, 'job'> = {}) {
|
||||||
|
return this.createTask({
|
||||||
|
...optionsArg,
|
||||||
|
job: jobArg,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public isInteractive(): boolean {
|
||||||
|
return this.interactive;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTasks(): SmartcliTerminalTask[] {
|
||||||
|
return [...this.tasks];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
public updateTask(taskArg: SmartcliTerminalTask, messageArg?: string): void {
|
||||||
|
if (!this.tasks.includes(taskArg)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.interactive) {
|
||||||
|
this.render();
|
||||||
|
} else if (messageArg && this.shouldWriteNonInteractiveUpdate(taskArg, messageArg)) {
|
||||||
|
this.writePermanentLine(`update ${taskArg.job}: ${messageArg}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
public completeTask(taskArg: SmartcliTerminalTask, messageArg?: string): void {
|
||||||
|
const message = messageArg || taskArg.getLastLogLine();
|
||||||
|
const summary = this.interactive
|
||||||
|
? `${this.getStatusSymbol('completed')} ${taskArg.job} ${taskArg.getElapsedText()}${message ? ` - ${message}` : ''}`
|
||||||
|
: `done ${taskArg.job} in ${taskArg.getElapsedText()}${message ? `: ${message}` : ''}`;
|
||||||
|
this.finalizeTask(taskArg, [summary], 'completed');
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
public failTask(taskArg: SmartcliTerminalTask, errorLinesArg: string[]): void {
|
||||||
|
const summary = this.interactive
|
||||||
|
? `${this.getStatusSymbol('failed')} ${taskArg.job} ${taskArg.getElapsedText()}${errorLinesArg[0] ? ` - ${errorLinesArg[0]}` : ''}`
|
||||||
|
: `fail ${taskArg.job} in ${taskArg.getElapsedText()}${errorLinesArg[0] ? `: ${errorLinesArg[0]}` : ''}`;
|
||||||
|
const detailLines = errorLinesArg.slice(1).map((lineArg) => ` ${lineArg}`);
|
||||||
|
this.finalizeTask(taskArg, [summary, ...detailLines], 'failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
public clear(): void {
|
||||||
|
if (this.interactive) {
|
||||||
|
this.clearRenderedBlock();
|
||||||
|
this.restoreCursor();
|
||||||
|
}
|
||||||
|
this.tasks = [];
|
||||||
|
this.nonInteractiveLogState.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
public getStatusSymbol(statusArg: TSmartcliTerminalTaskStatus): string {
|
||||||
|
const symbols = this.useUnicodeSymbols ? unicodeSymbols : asciiSymbols;
|
||||||
|
return symbols[statusArg];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
public colorizeLine(lineArg: string, statusArg?: TSmartcliTerminalTaskStatus): string {
|
||||||
|
if (!this.colors) {
|
||||||
|
return lineArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statusArg === 'completed') {
|
||||||
|
return `${ansiCodes.green}${lineArg}${ansiCodes.reset}`;
|
||||||
|
}
|
||||||
|
if (statusArg === 'failed') {
|
||||||
|
return `${ansiCodes.red}${lineArg}${ansiCodes.reset}`;
|
||||||
|
}
|
||||||
|
if (statusArg === 'running') {
|
||||||
|
return `${ansiCodes.cyan}${lineArg}${ansiCodes.reset}`;
|
||||||
|
}
|
||||||
|
if (lineArg.startsWith(' ')) {
|
||||||
|
return `${ansiCodes.gray}${lineArg}${ansiCodes.reset}`;
|
||||||
|
}
|
||||||
|
return lineArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ensureInteractiveSession(): void {
|
||||||
|
if (!this.cursorHidden) {
|
||||||
|
this.stream.write('\u001B[?25l');
|
||||||
|
this.cursorHidden = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.cleanupEnabled && !this.cleanupRegistered) {
|
||||||
|
this.registerProcessCleanup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private finalizeTask(
|
||||||
|
taskArg: SmartcliTerminalTask,
|
||||||
|
linesArg: string[],
|
||||||
|
statusArg: TSmartcliTerminalTaskStatus
|
||||||
|
): void {
|
||||||
|
this.tasks = this.tasks.filter((task) => task !== taskArg);
|
||||||
|
this.nonInteractiveLogState.delete(taskArg);
|
||||||
|
|
||||||
|
if (this.interactive) {
|
||||||
|
this.clearRenderedBlock();
|
||||||
|
this.writePermanentLines(linesArg.map((lineArg) => this.colorizeLine(lineArg, statusArg)));
|
||||||
|
this.render();
|
||||||
|
if (this.tasks.length === 0) {
|
||||||
|
this.restoreCursor();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.writePermanentLines(linesArg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private render(): void {
|
||||||
|
if (!this.interactive) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const width = this.getLineWidth();
|
||||||
|
const lines = this.tasks.flatMap((taskArg) => taskArg.renderPlainRows(width));
|
||||||
|
const coloredLines = lines.map((lineArg) => {
|
||||||
|
const status = lineArg.startsWith(' ') ? undefined : getStatusFromRenderedLine(lineArg, this);
|
||||||
|
return this.colorizeLine(lineArg, status);
|
||||||
|
});
|
||||||
|
const renderedOutput = coloredLines.join('\n');
|
||||||
|
|
||||||
|
if (renderedOutput === this.lastRenderedOutput) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.renderedLineCount > 0) {
|
||||||
|
this.stream.write(`\u001B[${this.renderedLineCount}F`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const lineCount = Math.max(this.renderedLineCount, coloredLines.length);
|
||||||
|
for (let index = 0; index < lineCount; index++) {
|
||||||
|
this.stream.write('\u001B[2K\r');
|
||||||
|
if (index < coloredLines.length) {
|
||||||
|
this.stream.write(coloredLines[index]);
|
||||||
|
}
|
||||||
|
this.stream.write('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
this.renderedLineCount = coloredLines.length;
|
||||||
|
this.lastRenderedOutput = renderedOutput;
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearRenderedBlock(): void {
|
||||||
|
if (this.renderedLineCount === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.stream.write(`\u001B[${this.renderedLineCount}F`);
|
||||||
|
this.stream.write(`\u001B[${this.renderedLineCount}M`);
|
||||||
|
this.renderedLineCount = 0;
|
||||||
|
this.lastRenderedOutput = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
private writePermanentLines(linesArg: string[]): void {
|
||||||
|
for (const line of linesArg) {
|
||||||
|
this.writePermanentLine(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private writePermanentLine(lineArg: string): void {
|
||||||
|
this.stream.write(`${lineArg}\n`);
|
||||||
|
}
|
||||||
|
|
||||||
|
private shouldWriteNonInteractiveUpdate(
|
||||||
|
taskArg: SmartcliTerminalTask,
|
||||||
|
messageArg: string
|
||||||
|
): boolean {
|
||||||
|
const now = Date.now();
|
||||||
|
const previousState = this.nonInteractiveLogState.get(taskArg);
|
||||||
|
|
||||||
|
if (previousState?.message === messageArg) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (previousState && now - previousState.timestamp < this.nonInteractiveThrottleMs) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.nonInteractiveLogState.set(taskArg, {
|
||||||
|
message: messageArg,
|
||||||
|
timestamp: now,
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private restoreCursor(): void {
|
||||||
|
if (!this.cursorHidden) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.stream.write('\u001B[?25h');
|
||||||
|
this.cursorHidden = false;
|
||||||
|
this.unregisterProcessCleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerProcessCleanup(): void {
|
||||||
|
const processObject = getProcessObject();
|
||||||
|
if (!processObject?.once) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const restoreOnly = () => {
|
||||||
|
this.clearRenderedBlock();
|
||||||
|
if (this.cursorHidden) {
|
||||||
|
this.stream.write('\u001B[?25h');
|
||||||
|
this.cursorHidden = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const exitWithSignal = (codeArg: number) => {
|
||||||
|
restoreOnly();
|
||||||
|
processObject.exit?.(codeArg);
|
||||||
|
};
|
||||||
|
const throwAfterRestore = (errorArg: unknown) => {
|
||||||
|
restoreOnly();
|
||||||
|
throw errorArg;
|
||||||
|
};
|
||||||
|
const sigintHandler = () => exitWithSignal(130);
|
||||||
|
const sigtermHandler = () => exitWithSignal(143);
|
||||||
|
|
||||||
|
processObject.once('exit', restoreOnly);
|
||||||
|
processObject.once('SIGINT', sigintHandler);
|
||||||
|
processObject.once('SIGTERM', sigtermHandler);
|
||||||
|
processObject.once('uncaughtException', throwAfterRestore);
|
||||||
|
this.cleanupHandlers = [
|
||||||
|
() => processObject.off?.('exit', restoreOnly),
|
||||||
|
() => processObject.off?.('SIGINT', sigintHandler),
|
||||||
|
() => processObject.off?.('SIGTERM', sigtermHandler),
|
||||||
|
() => processObject.off?.('uncaughtException', throwAfterRestore),
|
||||||
|
];
|
||||||
|
this.cleanupRegistered = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private unregisterProcessCleanup(): void {
|
||||||
|
for (const cleanupHandler of this.cleanupHandlers) {
|
||||||
|
cleanupHandler();
|
||||||
|
}
|
||||||
|
this.cleanupHandlers = [];
|
||||||
|
this.cleanupRegistered = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getLineWidth(): number {
|
||||||
|
return Math.max(20, (this.stream.columns || 80) - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SmartcliTerminalTask {
|
||||||
|
public readonly job: string;
|
||||||
|
public readonly rows: number;
|
||||||
|
public readonly startTime = Date.now();
|
||||||
|
public status: TSmartcliTerminalTaskStatus = 'running';
|
||||||
|
private terminal: SmartcliTerminal;
|
||||||
|
private logLimit: number;
|
||||||
|
private logLines: string[] = [];
|
||||||
|
private errorLines: string[] = [];
|
||||||
|
private progressCurrent?: number;
|
||||||
|
private progressTotal?: number;
|
||||||
|
|
||||||
|
constructor(terminalArg: SmartcliTerminal, optionsArg: ISmartcliTerminalTaskOptions) {
|
||||||
|
this.terminal = terminalArg;
|
||||||
|
this.job = optionsArg.job;
|
||||||
|
this.rows = Math.max(1, Math.floor(optionsArg.rows || 3));
|
||||||
|
this.logLimit = Math.max(this.rows, Math.floor(optionsArg.logLimit || 100));
|
||||||
|
}
|
||||||
|
|
||||||
|
public log(messageArg: string): this {
|
||||||
|
if (this.status !== 'running') {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newLines = normalizeLines(messageArg);
|
||||||
|
this.logLines.push(...newLines);
|
||||||
|
if (this.logLines.length > this.logLimit) {
|
||||||
|
this.logLines.splice(0, this.logLines.length - this.logLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.terminal.updateTask(this, newLines.join('\n'));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public update(messageArg: string): this {
|
||||||
|
return this.log(messageArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public setProgress(currentArg: number, totalArg: number, messageArg?: string): this {
|
||||||
|
if (this.status !== 'running') {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.progressCurrent = Math.max(0, currentArg);
|
||||||
|
this.progressTotal = Math.max(0, totalArg);
|
||||||
|
const progressText = this.getProgressText();
|
||||||
|
this.log(messageArg ? `${messageArg} ${progressText}` : progressText);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async run<T>(
|
||||||
|
operationArg: (taskArg: this) => T | Promise<T>,
|
||||||
|
optionsArg: ISmartcliTerminalTaskRunOptions = {}
|
||||||
|
): Promise<T> {
|
||||||
|
try {
|
||||||
|
const result = await operationArg(this);
|
||||||
|
this.complete(optionsArg.successMessage);
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
this.attachError(error, { keepOpen: optionsArg.errorKeepOpen });
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public complete(messageArg?: string): this {
|
||||||
|
if (this.status !== 'running') {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.status = 'completed';
|
||||||
|
this.terminal.completeTask(this, messageArg);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public attachError(
|
||||||
|
errorArg: unknown,
|
||||||
|
optionsArg: ISmartcliTerminalAttachErrorOptions = {}
|
||||||
|
): this {
|
||||||
|
if (this.status !== 'running') {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.status = 'failed';
|
||||||
|
this.errorLines = formatError(errorArg);
|
||||||
|
|
||||||
|
if (optionsArg.keepOpen) {
|
||||||
|
this.terminal.updateTask(this, this.errorLines.join('\n'));
|
||||||
|
} else {
|
||||||
|
this.terminal.failTask(this, this.errorLines);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public fail(errorArg: unknown): this {
|
||||||
|
return this.attachError(errorArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getElapsedText(): string {
|
||||||
|
return formatDuration(Date.now() - this.startTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLastLogLine(): string | undefined {
|
||||||
|
return this.logLines[this.logLines.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getLogLines(): string[] {
|
||||||
|
return [...this.logLines];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getErrorLines(): string[] {
|
||||||
|
return [...this.errorLines];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @internal */
|
||||||
|
public renderPlainRows(widthArg: number): string[] {
|
||||||
|
const lines: string[] = [];
|
||||||
|
const detailLines = this.errorLines.length > 0 ? this.errorLines : this.logLines;
|
||||||
|
const header = this.getHeaderLine();
|
||||||
|
|
||||||
|
if (this.rows === 1) {
|
||||||
|
const lastDetail = detailLines[detailLines.length - 1];
|
||||||
|
lines.push(`${header}${lastDetail ? ` - ${lastDetail}` : ''}`);
|
||||||
|
return lines.map((lineArg) => truncateLine(lineArg, widthArg));
|
||||||
|
}
|
||||||
|
|
||||||
|
lines.push(header);
|
||||||
|
const visibleDetailLineCount = this.rows - 1;
|
||||||
|
const visibleDetailLines = detailLines.slice(-visibleDetailLineCount);
|
||||||
|
for (const line of visibleDetailLines) {
|
||||||
|
lines.push(` ${line}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (lines.length < this.rows) {
|
||||||
|
lines.push('');
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.map((lineArg) => truncateLine(lineArg, widthArg));
|
||||||
|
}
|
||||||
|
|
||||||
|
private getHeaderLine(): string {
|
||||||
|
const progressText = this.progressTotal ? ` ${this.getProgressText()}` : '';
|
||||||
|
return `${this.terminal.getStatusSymbol(this.status)} ${this.job}${progressText} ${this.getElapsedText()}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getProgressText(): string {
|
||||||
|
if (!this.progressTotal) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const percent = Math.floor((this.progressCurrent || 0) / this.progressTotal * 100);
|
||||||
|
return `${Math.min(100, percent)}% (${this.progressCurrent}/${this.progressTotal})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDefaultStream(): ISmartcliWritable {
|
||||||
|
const processObject = getProcessObject();
|
||||||
|
if (processObject?.stdout?.write) {
|
||||||
|
return processObject.stdout;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
isTTY: false,
|
||||||
|
write: (chunkArg: string) => {
|
||||||
|
if (typeof console !== 'undefined') {
|
||||||
|
console.log(chunkArg.replace(/\n$/, ''));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInteractiveState(streamArg: ISmartcliWritable, overrideArg?: boolean): boolean {
|
||||||
|
if (typeof overrideArg === 'boolean') {
|
||||||
|
return overrideArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!streamArg.isTTY) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return !(
|
||||||
|
hasEnvFlag('CI') ||
|
||||||
|
hasEnvFlag('GITHUB_ACTIONS') ||
|
||||||
|
hasEnvFlag('JENKINS_URL') ||
|
||||||
|
hasEnvFlag('GITLAB_CI') ||
|
||||||
|
hasEnvFlag('TRAVIS') ||
|
||||||
|
hasEnvFlag('CIRCLECI') ||
|
||||||
|
getEnvValue('TERM') === 'dumb'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUnicodeSymbolState(
|
||||||
|
streamArg: ISmartcliWritable,
|
||||||
|
modeArg: TSmartcliTerminalSymbolMode = 'auto'
|
||||||
|
): boolean {
|
||||||
|
if (modeArg === 'unicode') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (modeArg === 'ascii') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const processObject = getProcessObject();
|
||||||
|
if (processObject?.platform === 'win32' && !getEnvValue('WT_SESSION')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return streamArg.isTTY !== false && getEnvValue('TERM') !== 'dumb';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStatusFromRenderedLine(
|
||||||
|
lineArg: string,
|
||||||
|
terminalArg: SmartcliTerminal
|
||||||
|
): TSmartcliTerminalTaskStatus | undefined {
|
||||||
|
if (lineArg.startsWith(terminalArg.getStatusSymbol('completed'))) {
|
||||||
|
return 'completed';
|
||||||
|
}
|
||||||
|
if (lineArg.startsWith(terminalArg.getStatusSymbol('failed'))) {
|
||||||
|
return 'failed';
|
||||||
|
}
|
||||||
|
if (lineArg.startsWith(terminalArg.getStatusSymbol('running'))) {
|
||||||
|
return 'running';
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasEnvFlag(nameArg: string): boolean {
|
||||||
|
const value = getEnvValue(nameArg);
|
||||||
|
return Boolean(value && value !== '0' && value.toLowerCase() !== 'false');
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEnvValue(nameArg: string): string | undefined {
|
||||||
|
return getProcessObject()?.env?.[nameArg];
|
||||||
|
}
|
||||||
|
|
||||||
|
function getProcessObject(): any {
|
||||||
|
const globalObject: any = globalThis as any;
|
||||||
|
return globalObject.process;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLines(messageArg: string): string[] {
|
||||||
|
return String(messageArg)
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map((lineArg) => lineArg.trimEnd())
|
||||||
|
.filter((lineArg) => lineArg.length > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatError(errorArg: unknown): string[] {
|
||||||
|
if (errorArg instanceof Error) {
|
||||||
|
return normalizeLines(errorArg.stack || errorArg.message || errorArg.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof errorArg === 'string') {
|
||||||
|
return normalizeLines(errorArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const jsonString = JSON.stringify(errorArg);
|
||||||
|
return normalizeLines(jsonString === undefined ? String(errorArg) : jsonString);
|
||||||
|
} catch {
|
||||||
|
return [String(errorArg)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatDuration(millisecondsArg: number): string {
|
||||||
|
if (millisecondsArg < 1000) {
|
||||||
|
return `${millisecondsArg}ms`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${(millisecondsArg / 1000).toFixed(1)}s`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function truncateLine(lineArg: string, widthArg: number): string {
|
||||||
|
if (lineArg.length <= widthArg) {
|
||||||
|
return lineArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widthArg <= 3) {
|
||||||
|
return lineArg.slice(0, widthArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${lineArg.slice(0, widthArg - 3)}...`;
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
/**
|
||||||
|
* Return only the user arguments (excluding runtime executable and script path),
|
||||||
|
* across Node.js, Deno (run/compiled), and Bun.
|
||||||
|
*
|
||||||
|
* - Deno: uses Deno.args directly (already user-only in both run and compile).
|
||||||
|
* - Node/Bun: uses process.execPath's basename to decide if there is a script arg.
|
||||||
|
* If execPath basename is a known launcher (node/nodejs/bun/deno), skip 2; else skip 1.
|
||||||
|
*/
|
||||||
|
export function getUserArgs(argv?: string[]): string[] {
|
||||||
|
// If argv is explicitly provided, use it instead of Deno.args
|
||||||
|
// This handles test scenarios where process.argv is manually modified
|
||||||
|
const useProvidedArgv = argv !== undefined;
|
||||||
|
|
||||||
|
// Prefer Deno.args when available and no custom argv provided;
|
||||||
|
// it's the most reliable for Deno run and compiled.
|
||||||
|
// Deno.args is ALWAYS correct in Deno environments - it handles the internal bundle path automatically.
|
||||||
|
// deno-lint-ignore no-explicit-any
|
||||||
|
const g: any = typeof globalThis !== 'undefined' ? globalThis : {};
|
||||||
|
|
||||||
|
if (!useProvidedArgv && g.Deno && g.Deno.args && Array.isArray(g.Deno.args)) {
|
||||||
|
return g.Deno.args.slice();
|
||||||
|
}
|
||||||
|
|
||||||
|
const a = argv ?? (typeof process !== 'undefined' && Array.isArray(process.argv) ? process.argv : []);
|
||||||
|
|
||||||
|
if (!Array.isArray(a) || a.length === 0) return [];
|
||||||
|
|
||||||
|
// Determine execPath in Node/Bun (or compat shims)
|
||||||
|
let execPath = '';
|
||||||
|
if (typeof process !== 'undefined' && typeof process.execPath === 'string') {
|
||||||
|
execPath = process.execPath;
|
||||||
|
} else if (g.Deno && typeof g.Deno.execPath === 'function') {
|
||||||
|
// Fallback for unusual shims: try Deno.execPath() if present.
|
||||||
|
try {
|
||||||
|
execPath = g.Deno.execPath();
|
||||||
|
} catch {
|
||||||
|
/* ignore */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const base = basename(execPath).toLowerCase();
|
||||||
|
const knownLaunchers = new Set([
|
||||||
|
'node',
|
||||||
|
'node.exe',
|
||||||
|
'nodejs',
|
||||||
|
'nodejs.exe',
|
||||||
|
'bun',
|
||||||
|
'bun.exe',
|
||||||
|
'deno',
|
||||||
|
'deno.exe',
|
||||||
|
'tsx',
|
||||||
|
'tsx.exe',
|
||||||
|
'ts-node',
|
||||||
|
'ts-node.exe',
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Always skip the executable (argv[0]).
|
||||||
|
let offset = Math.min(1, a.length);
|
||||||
|
|
||||||
|
// If the executable is a known runtime launcher, there's almost always a script path in argv[1].
|
||||||
|
// This handles Node, Bun, and "deno run" (but NOT "deno compile" which won't match 'deno').
|
||||||
|
if (knownLaunchers.has(base)) {
|
||||||
|
offset = Math.min(2, a.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: we intentionally avoid path/URL heuristics on argv[1] so we don't
|
||||||
|
// accidentally drop the first user arg when it's a path-like value in compiled mode.
|
||||||
|
// When offset >= a.length, this correctly returns an empty array (no user args).
|
||||||
|
return a.slice(offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
function basename(p: string): string {
|
||||||
|
if (!p) return '';
|
||||||
|
const parts = p.split(/[/\\]/);
|
||||||
|
return parts[parts.length - 1] || '';
|
||||||
|
}
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
import "typings-global";
|
|
||||||
|
|
||||||
import "./smartcli.interfaces";
|
|
||||||
import plugins = require("./smartcli.plugins");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* executes callback with answer to question as argument
|
|
||||||
* @param questionString the question you want to ask the user
|
|
||||||
* @param cb the function to execute with answer as param
|
|
||||||
* @returns {null}
|
|
||||||
*/
|
|
||||||
export let getAnswer = function(questionString:string, cb) {
|
|
||||||
if (typeof questionString != 'string') {
|
|
||||||
plugins.beautylog.error('no question specified');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
//make inquirer compatible question object
|
|
||||||
let question = {
|
|
||||||
type: "input",
|
|
||||||
name: "userFeedback",
|
|
||||||
message: questionString,
|
|
||||||
validate: function( value ) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
plugins.inquirer.prompt([question],function(answers){
|
|
||||||
let answer = answers.userFeedback;
|
|
||||||
cb(answer);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param questionString
|
|
||||||
* @param choiceOptions
|
|
||||||
* @param cb
|
|
||||||
* @returns {null}
|
|
||||||
*/
|
|
||||||
export let getChoice = function(questionString:string, choiceOptions:string[], cb) {
|
|
||||||
if(!Array.isArray(choiceOptions)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
//make inquirer compatible question object
|
|
||||||
let question = {
|
|
||||||
type: "list",
|
|
||||||
name: "userFeedback",
|
|
||||||
message: questionString,
|
|
||||||
choices: choiceOptions,
|
|
||||||
filter: function( val ) { return val.toLowerCase(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
plugins.inquirer.prompt(question,function(answers){
|
|
||||||
let answer = answers.userFeedback;
|
|
||||||
cb(answer);
|
|
||||||
});
|
|
||||||
|
|
||||||
};
|
|
||||||
+13
-8
@@ -1,9 +1,14 @@
|
|||||||
import "typings-global";
|
// @pushrocks scope
|
||||||
|
import * as smartlog from '@push.rocks/smartlog';
|
||||||
|
import * as lik from '@push.rocks/lik';
|
||||||
|
import * as path from 'node:path';
|
||||||
|
import * as smartparam from '@push.rocks/smartobject';
|
||||||
|
import * as smartpromise from '@push.rocks/smartpromise';
|
||||||
|
import * as smartrx from '@push.rocks/smartrx';
|
||||||
|
|
||||||
export let argv = require('yargs');
|
export { smartlog, lik, path, smartparam, smartpromise, smartrx };
|
||||||
export let beautylog = require("beautylog");
|
|
||||||
export let cliff = require("cliff");
|
// thirdparty scope
|
||||||
export let inquirer = require("inquirer");
|
import yargsParser from 'yargs-parser';
|
||||||
export let path = require("path");
|
|
||||||
export let q = require("q");
|
export { yargsParser };
|
||||||
export let smartparam = require("smartparam");
|
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"useDefineForClassFields": false,
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"types": ["node"]
|
||||||
|
},
|
||||||
|
"exclude": ["dist_*/**/*.d.ts"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user