From 3892e149be14a9c338bfbf2f8a247531eb2906f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Hamal=20Dvo=C5=99=C3=A1k?= Date: Tue, 26 Aug 2025 12:39:50 +0200 Subject: [PATCH] Improve command-line handling --- package-lock.json | 73 ++++++++++++++++++++++++++++++++++++++++++ package.json | 8 +++-- src/index.ts | 80 ++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 150 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index edd7b04..1fdd0b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "ISC", "dependencies": { + "command-line-args": "^6.0.1", "find-up": "^7.0.0", "oracledb": "^6.8.0" }, @@ -16,6 +17,7 @@ "node-oracledb-cli": "dist/index.js" }, "devDependencies": { + "@types/command-line-args": "^5.2.3", "@types/oracledb": "^6.9.1", "tsx": "^4.20.4", "typescript": "^5.9.2" @@ -463,6 +465,13 @@ "node": ">=18" } }, + "node_modules/@types/command-line-args": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/command-line-args/-/command-line-args-5.2.3.tgz", + "integrity": "sha512-uv0aG6R0Y8WHZLTamZwtfsDLVRnOa+n+n5rEvFWL5Na5gZ8V2Teab/duDPFzIIIhs9qizDpcavCusCLJZu62Kw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.3.0", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", @@ -483,6 +492,38 @@ "@types/node": "*" } }, + "node_modules/array-back": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", + "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", + "license": "MIT", + "engines": { + "node": ">=12.17" + } + }, + "node_modules/command-line-args": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-6.0.1.tgz", + "integrity": "sha512-Jr3eByUjqyK0qd8W0SGFW1nZwqCaNCtbXjRo2cRJC1OYxWl3MZ5t1US3jq+cO4sPavqgw4l9BMGX0CBe+trepg==", + "license": "MIT", + "dependencies": { + "array-back": "^6.2.2", + "find-replace": "^5.0.2", + "lodash.camelcase": "^4.3.0", + "typical": "^7.2.0" + }, + "engines": { + "node": ">=12.20" + }, + "peerDependencies": { + "@75lb/nature": "latest" + }, + "peerDependenciesMeta": { + "@75lb/nature": { + "optional": true + } + } + }, "node_modules/esbuild": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", @@ -525,6 +566,23 @@ "@esbuild/win32-x64": "0.25.9" } }, + "node_modules/find-replace": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-5.0.2.tgz", + "integrity": "sha512-Y45BAiE3mz2QsrN2fb5QEtO4qb44NcS7en/0y9PEVsg351HsLeVclP8QPMH79Le9sH3rs5RSwJu99W0WPZO43Q==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@75lb/nature": "latest" + }, + "peerDependenciesMeta": { + "@75lb/nature": { + "optional": true + } + } + }, "node_modules/find-up": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-7.0.0.tgz", @@ -585,6 +643,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, "node_modules/oracledb": { "version": "6.9.0", "resolved": "https://registry.npmjs.org/oracledb/-/oracledb-6.9.0.tgz", @@ -678,6 +742,15 @@ "node": ">=14.17" } }, + "node_modules/typical": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-7.3.0.tgz", + "integrity": "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==", + "license": "MIT", + "engines": { + "node": ">=12.17" + } + }, "node_modules/undici-types": { "version": "7.10.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", diff --git a/package.json b/package.json index 19053c0..a45df23 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,9 @@ { "name": "node-oracledb-cli", "version": "1.0.0", - "description": "", + "author": "Jan Hamal Dvořák ", + "license": "MIT", + "description": "Command-line Oracle Client", "scripts": { "build": "tsc", "start": "node dist/index.js", @@ -10,14 +12,14 @@ "bin": { "node-oracledb-cli": "dist/index.js" }, - "author": "", - "license": "ISC", "devDependencies": { + "@types/command-line-args": "^5.2.3", "@types/oracledb": "^6.9.1", "tsx": "^4.20.4", "typescript": "^5.9.2" }, "dependencies": { + "command-line-args": "^6.0.1", "find-up": "^7.0.0", "oracledb": "^6.8.0" } diff --git a/src/index.ts b/src/index.ts index 05e4dc5..66290e3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,11 @@ #!/usr/bin/env node import fs from 'node:fs' +import os from 'node:os' import readline from 'node:readline' import oracledb from 'oracledb' import { findUpSync } from 'find-up' +import commandLineArgs from 'command-line-args' interface OraTable { OWNER: string @@ -87,15 +89,73 @@ function getStringParts(str: string, offset: number) { return { init, word, rest } } -async function main() { - const args = process.argv.slice(2) +interface Options { + host: string + port: number + dbname: string + username: string + help: boolean +} - if (args.length < 3) { - console.log('Usage: node oracle-client.js ') - process.exit(1) +async function main() { + const { username } = os.userInfo() + + const options = commandLineArgs([ + { name: 'host', alias: 'h', type: String, defaultValue: 'localhost' }, + { name: 'port', alias: 'p', type: parseInt, defaultValue: 1521 }, + { name: 'dbname', alias: 'd', type: String, defaultValue: 'hr', defaultOption: true }, + { name: 'username', alias: 'U', type: String, defaultValue: username }, + { name: 'help', type: Boolean, defaultValue: false }, + ]) as Options + + if (options.help) { + console.log('Usage: node-oracledb-cli -h dbhost -U username dbname') + console.log('Command line client for Oracle SQL databases\n') + console.log('Options:') + console.log(' --help Show this help') + console.log(' --host, -h HOST Address of the host to connect to') + console.log(' --port, -p PORT Port of the host to connect to') + console.log(' --dbname, -d NAME Name of the database/service to connect to') + console.log(' --username, -u NAME Name of the user for login') + console.log('\nAuthentication:') + console.log(' You need to create .orapass file in this or a parent directory.') + console.log(' The file shall contain "host:port:dbname:username:password" lines.') + process.exit(0) } - const [user, password, connectString] = args + const passFile = findUpSync([ + '.config/node-oracledb-cli/passwords', + '.oracle_passwords', + '.orapass', + ]) + const passLines = passFile ? fs.readFileSync(passFile, 'utf8') : '' + const passwords = passLines + .split('\n') + .map((line) => line.trim().split(/:/g)) + .filter(([h, p, d, u]) => { + if ( + h === options.host && + parseInt(p) === options.port && + d === options.dbname && + u === options.username + ) { + return true + } + }) + .map((row) => row[4]) + + if (process.env.ORACLE_PASSWORD !== undefined) { + passwords.unshift(process.env.ORACLE_PASSWORD) + } + + if (!passwords.length) { + console.error('Password not found. Please create .orapass entry like this:\n') + console.error( + `${options.host}:${options.port}:${options.dbname}:${options.username}:Passw0rd123\n`, + ) + console.error('Or set ORACLE_PASSWORD environment variable and try again.') + process.exit(1) + } const historyFile = findUpSync(['.config/node-oracledb-cli/history', '.oracle_history']) const historyLines = historyFile ? fs.readFileSync(historyFile, 'utf8') : '' @@ -107,7 +167,7 @@ async function main() { const rl = readline.createInterface({ input: process.stdin, output: process.stdout, - prompt: `${user}@${connectString}> `, + prompt: `${options.username}@${options.dbname}> `, removeHistoryDuplicates: true, history, completer(linePartial: string) { @@ -160,7 +220,11 @@ async function main() { }, }) - const connection = await oracledb.getConnection({ user, password, connectString }) + const connection = await oracledb.getConnection({ + user: options.username, + password: passwords[0], + connectString: `${options.host}:${options.port}/${options.dbname}`, + }) function execute(query: string, args: any[] = [], maxRows?: number) { return connection.execute(query, args, {