Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8852bd5c86 | |||
| 6279f2cbad | |||
| e3f5616320 | |||
| 40c0dfb3df | |||
| 4f243289b8 | |||
| 2d28939986 | |||
| 01623eab2a | |||
| 5c65c43589 | |||
| 72109e478f | |||
| 53d9956735 | |||
| 913f8556d0 |
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"@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": {
|
||||
"registries": [
|
||||
"https://verdaccio.lossless.digital",
|
||||
"https://registry.npmjs.org"
|
||||
],
|
||||
"accessLevel": "public"
|
||||
}
|
||||
},
|
||||
"@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": [],
|
||||
"npmRegistryUrl": "registry.npmjs.org"
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
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
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
@@ -1,5 +1,43 @@
|
||||
# Changelog
|
||||
|
||||
## 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
|
||||
|
||||
|
||||
+12
-6
@@ -1,9 +1,5 @@
|
||||
{
|
||||
"npmci": {
|
||||
"npmGlobalTools": [],
|
||||
"npmAccesslevel": "public"
|
||||
},
|
||||
"gitzone": {
|
||||
"@git.zone/cli": {
|
||||
"projectType": "npm",
|
||||
"module": {
|
||||
"githost": "code.foss.global",
|
||||
@@ -26,9 +22,19 @@
|
||||
"node.js",
|
||||
"development tool"
|
||||
]
|
||||
},
|
||||
"release": {
|
||||
"registries": [
|
||||
"https://verdaccio.lossless.digital",
|
||||
"https://registry.npmjs.org"
|
||||
],
|
||||
"accessLevel": "public"
|
||||
}
|
||||
},
|
||||
"tsdoc": {
|
||||
"@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": []
|
||||
}
|
||||
}
|
||||
+13
-10
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "@push.rocks/smartcli",
|
||||
"private": false,
|
||||
"version": "4.0.15",
|
||||
"version": "4.0.21",
|
||||
"description": "A library that simplifies building reactive command-line applications using observables, with robust support for commands, arguments, options, aliases, and asynchronous operation management.",
|
||||
"main": "dist_ts/index.js",
|
||||
"typings": "dist_ts/index.d.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "(tstest test/ --verbose)",
|
||||
"build": "(tsbuild --web --allowimplicitany)",
|
||||
"build": "tsbuild --web",
|
||||
"buildDocs": "tsdoc"
|
||||
},
|
||||
"repository": {
|
||||
@@ -32,22 +32,23 @@
|
||||
"author": "Lossless GmbH <office@lossless.com> (https://lossless.com)",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://gitlab.com/pushrocks/smartcli/issues"
|
||||
"url": "https://code.foss.global/push.rocks/smartcli/issues"
|
||||
},
|
||||
"homepage": "https://code.foss.global/push.rocks/smartcli",
|
||||
"dependencies": {
|
||||
"@push.rocks/lik": "^6.2.2",
|
||||
"@push.rocks/smartlog": "^3.1.10",
|
||||
"@push.rocks/lik": "^6.4.1",
|
||||
"@push.rocks/smartlog": "^3.2.2",
|
||||
"@push.rocks/smartobject": "^1.0.12",
|
||||
"@push.rocks/smartpromise": "^4.2.3",
|
||||
"@push.rocks/smartrx": "^3.0.10",
|
||||
"yargs-parser": "22.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@git.zone/tsbuild": "^2.6.8",
|
||||
"@git.zone/tsrun": "^1.6.2",
|
||||
"@git.zone/tstest": "^2.7.0",
|
||||
"@types/node": "^24.9.1"
|
||||
"@git.zone/tsbuild": "^4.4.0",
|
||||
"@git.zone/tsrun": "^2.0.3",
|
||||
"@git.zone/tstest": "^3.6.3",
|
||||
"@types/node": "^25.6.0",
|
||||
"@types/yargs-parser": "^21.0.3"
|
||||
},
|
||||
"files": [
|
||||
"ts/**/*",
|
||||
@@ -58,11 +59,13 @@
|
||||
"dist_ts_web/**/*",
|
||||
"assets/**/*",
|
||||
"cli.js",
|
||||
".smartconfig.json",
|
||||
"LICENSE",
|
||||
"npmextra.json",
|
||||
"readme.md"
|
||||
],
|
||||
"browserslist": [
|
||||
"last 1 chrome versions"
|
||||
],
|
||||
"packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34"
|
||||
"packageManager": "pnpm@10.28.2"
|
||||
}
|
||||
|
||||
Generated
+2137
-2403
File diff suppressed because it is too large
Load Diff
+35
-21
@@ -1,28 +1,42 @@
|
||||
## Cross-Runtime Compatibility
|
||||
|
||||
### CLI Argument Parsing
|
||||
The module uses a robust cross-runtime approach for parsing command-line arguments:
|
||||
The module uses a robust cross-runtime approach for parsing command-line arguments through the `getUserArgs()` utility in `ts/smartcli.helpers.ts`.
|
||||
|
||||
**Key Implementation:**
|
||||
- `getUserArgs()` utility (in `ts/smartcli.helpers.ts`) handles process.argv differences across Node.js, Deno, and Bun
|
||||
- Uses `process.execPath` basename detection instead of content-based heuristics
|
||||
- Prefers `Deno.args` when available (for Deno run/compiled), unless argv is explicitly provided
|
||||
**Runtime-Specific Implementations:**
|
||||
|
||||
**Runtime Differences:**
|
||||
- **Node.js**: `process.argv = ["/path/to/node", "/path/to/script.js", ...userArgs]`
|
||||
- **Deno (run)**: `process.argv = ["deno", "/path/to/script.ts", ...userArgs]` (but `Deno.args` is preferred)
|
||||
- **Deno (compiled)**: `process.argv = ["/path/to/executable", ...userArgs]` (custom executable name)
|
||||
- **Bun**: `process.argv = ["/path/to/bun", "/path/to/script.ts", ...userArgs]`
|
||||
| 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 |
|
||||
|
||||
**How it works:**
|
||||
1. If `Deno.args` exists and no custom argv provided, use it directly
|
||||
2. Otherwise, detect runtime by checking `process.execPath` basename
|
||||
3. If basename is a known launcher (node, deno, bun, tsx, ts-node), skip 2 args
|
||||
4. If basename is unknown (compiled executable), skip only 1 arg
|
||||
5. Safety check: if offset would skip everything, don't skip anything (handles test edge cases)
|
||||
**Why Deno.args is Critical for Compiled Executables:**
|
||||
|
||||
This approach works correctly with:
|
||||
- Standard runtime execution
|
||||
- Compiled executables (Deno compile, Node pkg, etc.)
|
||||
- Custom-named executables
|
||||
- Test environments with unusual argv setups
|
||||
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)
|
||||
@@ -16,10 +16,7 @@ tap.test('should add an command', async (toolsArg) => {
|
||||
awesomeCommandSubject.subscribe(() => {
|
||||
done.resolve();
|
||||
});
|
||||
console.log(process.argv);
|
||||
process.argv.splice(2, 0, 'awesome');
|
||||
console.log(process.argv);
|
||||
smartCliTestObject.startParse();
|
||||
smartCliTestObject.startParse(['node', 'test.js', 'awesome']);
|
||||
await done.promise;
|
||||
});
|
||||
|
||||
@@ -39,4 +36,4 @@ tap.test('should accept a command', async () => {
|
||||
expect(hasExecuted).toBeTrue();
|
||||
});
|
||||
|
||||
tap.start();
|
||||
export default tap.start();
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@push.rocks/smartcli',
|
||||
version: '4.0.15',
|
||||
version: '4.0.21',
|
||||
description: 'A library that simplifies building reactive command-line applications using observables, with robust support for commands, arguments, options, aliases, and asynchronous operation management.'
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export class Smartcli {
|
||||
*/
|
||||
public parseCompleted = plugins.smartpromise.defer<any>();
|
||||
|
||||
public version: string;
|
||||
public version?: string;
|
||||
|
||||
/**
|
||||
* map of all Trigger/Observable objects to keep track
|
||||
@@ -65,6 +65,9 @@ export class Smartcli {
|
||||
*/
|
||||
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;
|
||||
}
|
||||
@@ -92,7 +95,7 @@ export class Smartcli {
|
||||
* getOption
|
||||
*/
|
||||
public getOption(optionNameArg: string) {
|
||||
const userArgs = getUserArgs(process.argv);
|
||||
const userArgs = getUserArgs();
|
||||
const parsedYargs = plugins.yargsParser(userArgs);
|
||||
return parsedYargs[optionNameArg];
|
||||
}
|
||||
@@ -123,11 +126,11 @@ export class Smartcli {
|
||||
|
||||
/**
|
||||
* start the process of evaluating commands
|
||||
* @param testArgv - Optional argv override for testing (bypasses automatic runtime detection)
|
||||
*/
|
||||
public startParse(): void {
|
||||
public startParse(testArgv?: string[]): void {
|
||||
// Get user arguments, properly handling Node.js, Deno (run/compiled), and Bun
|
||||
// Pass process.argv explicitly to handle test scenarios where it's modified
|
||||
const userArgs = getUserArgs(process.argv);
|
||||
const userArgs = testArgv ? getUserArgs(testArgv) : getUserArgs();
|
||||
const parsedYArgs = plugins.yargsParser(userArgs);
|
||||
const wantedCommand = parsedYArgs._[0];
|
||||
|
||||
@@ -136,7 +139,6 @@ export class Smartcli {
|
||||
console.log(this.version || 'unknown version');
|
||||
return;
|
||||
}
|
||||
console.log(`Wanted command: ${wantedCommand}`);
|
||||
for (const command of this.commandObservableMap.getArray()) {
|
||||
if (!wantedCommand) {
|
||||
const standardCommand = this.commandObservableMap.findSync((commandArg) => {
|
||||
|
||||
@@ -13,14 +13,15 @@ export function getUserArgs(argv?: string[]): string[] {
|
||||
|
||||
// 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 : []);
|
||||
const a = argv ?? (typeof process !== 'undefined' && Array.isArray(process.argv) ? process.argv : []);
|
||||
|
||||
if (!Array.isArray(a) || a.length === 0) return [];
|
||||
|
||||
@@ -62,14 +63,9 @@ export function getUserArgs(argv?: string[]): string[] {
|
||||
offset = Math.min(2, a.length);
|
||||
}
|
||||
|
||||
// Safety: if offset would skip all elements and array is not empty, don't skip anything
|
||||
// This handles edge cases like test environments with unusual argv setups
|
||||
if (offset >= a.length && a.length > 0) {
|
||||
offset = 0;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
||||
+4
-4
@@ -5,10 +5,10 @@
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"noImplicitAny": true,
|
||||
"esModuleInterop": true,
|
||||
"verbatimModuleSyntax": true
|
||||
"verbatimModuleSyntax": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"exclude": [
|
||||
"dist_*/**/*.d.ts"
|
||||
]
|
||||
"exclude": ["dist_*/**/*.d.ts"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user