Compare commits

...

6 Commits

Author SHA1 Message Date
e905af4b21 4.0.15
Some checks failed
Default (tags) / security (push) Failing after 22s
Default (tags) / test (push) Failing after 14s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-10-28 13:53:34 +00:00
2e0b7d5053 fix(smartcli.helpers): Add robust getUserArgs helper and refactor Smartcli to use it; add MIT license and update documentation 2025-10-28 13:53:34 +00:00
270f75e8e0 4.0.14
Some checks failed
Default (tags) / security (push) Failing after 21s
Default (tags) / test (push) Failing after 12s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-10-28 09:32:21 +00:00
b9ec1e2be6 fix(license): Add MIT license file 2025-10-28 09:32:21 +00:00
86d62407e7 4.0.13
Some checks failed
Default (tags) / security (push) Failing after 23s
Default (tags) / test (push) Failing after 13s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-10-28 06:27:29 +00:00
6efd6232d1 fix(smartcli): Improve CLI argument parsing, update deps and tests 2025-10-28 06:27:29 +00:00
10 changed files with 12368 additions and 3235 deletions

View File

@@ -1,5 +1,29 @@
# Changelog # Changelog
## 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) ## 2025-04-01 - 4.0.12 - fix(docs)
Update documentation with comprehensive usage examples, improved command alias descriptions, and detailed configuration instructions Update documentation with comprehensive usage examples, improved command alias descriptions, and detailed configuration instructions

7233
deno.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,13 +1,13 @@
{ {
"name": "@push.rocks/smartcli", "name": "@push.rocks/smartcli",
"private": false, "private": false,
"version": "4.0.12", "version": "4.0.15",
"description": "A library that simplifies building reactive command-line applications using observables, with robust support for commands, arguments, options, aliases, and asynchronous operation management.", "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", "main": "dist_ts/index.js",
"typings": "dist_ts/index.d.ts", "typings": "dist_ts/index.d.ts",
"type": "module", "type": "module",
"scripts": { "scripts": {
"test": "(tstest test/ --web)", "test": "(tstest test/ --verbose)",
"build": "(tsbuild --web --allowimplicitany)", "build": "(tsbuild --web --allowimplicitany)",
"buildDocs": "tsdoc" "buildDocs": "tsdoc"
}, },
@@ -36,19 +36,18 @@
}, },
"homepage": "https://code.foss.global/push.rocks/smartcli", "homepage": "https://code.foss.global/push.rocks/smartcli",
"dependencies": { "dependencies": {
"@push.rocks/lik": "^6.0.15", "@push.rocks/lik": "^6.2.2",
"@push.rocks/smartlog": "^3.0.6", "@push.rocks/smartlog": "^3.1.10",
"@push.rocks/smartobject": "^1.0.12", "@push.rocks/smartobject": "^1.0.12",
"@push.rocks/smartpromise": "^4.0.3", "@push.rocks/smartpromise": "^4.2.3",
"@push.rocks/smartrx": "^3.0.3", "@push.rocks/smartrx": "^3.0.10",
"yargs-parser": "21.1.1" "yargs-parser": "22.0.0"
}, },
"devDependencies": { "devDependencies": {
"@git.zone/tsbuild": "^2.1.80", "@git.zone/tsbuild": "^2.6.8",
"@git.zone/tsrun": "^1.2.42", "@git.zone/tsrun": "^1.6.2",
"@git.zone/tstest": "^1.0.90", "@git.zone/tstest": "^2.7.0",
"@push.rocks/tapbundle": "^5.0.23", "@types/node": "^24.9.1"
"@types/node": "^20.12.12"
}, },
"files": [ "files": [
"ts/**/*", "ts/**/*",
@@ -64,5 +63,6 @@
], ],
"browserslist": [ "browserslist": [
"last 1 chrome versions" "last 1 chrome versions"
] ],
"packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34"
} }

8179
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1,28 @@
No specific hints. ## Cross-Runtime Compatibility
### CLI Argument Parsing
The module uses a robust cross-runtime approach for parsing command-line arguments:
**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 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]`
**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)
This approach works correctly with:
- Standard runtime execution
- Compiled executables (Deno compile, Node pkg, etc.)
- Custom-named executables
- Test environments with unusual argv setups

View File

@@ -1,4 +1,4 @@
import { tap, expect } from '@push.rocks/tapbundle'; import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as smartrx from '@push.rocks/smartrx'; import * as smartrx from '@push.rocks/smartrx';
import * as smartcli from '../ts/index.js'; import * as smartcli from '../ts/index.js';

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@push.rocks/smartcli', name: '@push.rocks/smartcli',
version: '4.0.12', version: '4.0.15',
description: 'A library that simplifies building reactive command-line applications using observables, with robust support for commands, arguments, options, aliases, and asynchronous operation management.' description: 'A library that simplifies building reactive command-line applications using observables, with robust support for commands, arguments, options, aliases, and asynchronous operation management.'
} }

View File

@@ -1,4 +1,5 @@
import * as plugins from './smartcli.plugins.js'; import * as plugins from './smartcli.plugins.js';
import { getUserArgs } from './smartcli.helpers.js';
// interfaces // interfaces
export interface ICommandObservableObject { export interface ICommandObservableObject {
@@ -91,7 +92,8 @@ export class Smartcli {
* getOption * getOption
*/ */
public getOption(optionNameArg: string) { public getOption(optionNameArg: string) {
const parsedYargs = plugins.yargsParser(process.argv); const userArgs = getUserArgs(process.argv);
const parsedYargs = plugins.yargsParser(userArgs);
return parsedYargs[optionNameArg]; return parsedYargs[optionNameArg];
} }
@@ -123,24 +125,10 @@ export class Smartcli {
* start the process of evaluating commands * start the process of evaluating commands
*/ */
public startParse(): void { public startParse(): void {
const parsedYArgs = plugins.yargsParser(process.argv); // Get user arguments, properly handling Node.js, Deno (run/compiled), and Bun
// Pass process.argv explicitly to handle test scenarios where it's modified
// lets handle commands const userArgs = getUserArgs(process.argv);
let counter = 0; const parsedYArgs = plugins.yargsParser(userArgs);
let foundCommand = false;
parsedYArgs._ = parsedYArgs._.filter((commandPartArg) => {
counter++;
if (typeof commandPartArg === 'number') {
return true;
}
if (counter <= 2 && !foundCommand) {
const isPath = commandPartArg.startsWith('/');
foundCommand = !isPath;
return foundCommand;
} else {
return true;
}
});
const wantedCommand = parsedYArgs._[0]; const wantedCommand = parsedYArgs._[0];
// lets handle some standards // lets handle some standards

80
ts/smartcli.helpers.ts Normal file
View File

@@ -0,0 +1,80 @@
/**
* 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-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);
}
// 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.
return a.slice(offset);
}
function basename(p: string): string {
if (!p) return '';
const parts = p.split(/[/\\]/);
return parts[parts.length - 1] || '';
}

View File

@@ -1,7 +1,7 @@
// @pushrocks scope // @pushrocks scope
import * as smartlog from '@push.rocks/smartlog'; import * as smartlog from '@push.rocks/smartlog';
import * as lik from '@push.rocks/lik'; import * as lik from '@push.rocks/lik';
import * as path from 'path'; import * as path from 'node:path';
import * as smartparam from '@push.rocks/smartobject'; import * as smartparam from '@push.rocks/smartobject';
import * as smartpromise from '@push.rocks/smartpromise'; import * as smartpromise from '@push.rocks/smartpromise';
import * as smartrx from '@push.rocks/smartrx'; import * as smartrx from '@push.rocks/smartrx';