From 4ee84167c4b3af1caef2313ec42b1a8f7a7d85a0 Mon Sep 17 00:00:00 2001 From: Nikolay Samokhvalov Date: Tue, 28 Oct 2025 09:19:36 -0700 Subject: [PATCH 01/96] ci: add CLI smoke test\n\n- add .gitlab-ci.yml with bash CLI help smoke test\n- validate syntax with bash -n\n- avoid committing test files; test logic lives in CI script --- .gitlab-ci.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..b7f7b8a --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,21 @@ +stages: + - test + +cli:smoke:test: + stage: test + image: alpine:3.20 + variables: + GIT_STRATEGY: fetch + before_script: + - apk add --no-cache bash curl git docker-cli docker-compose + script: + - bash -n ./postgres_ai + - | + set -euo pipefail + out=$(./postgres_ai help | tr -d "\r") + echo "$out" | grep -q "Postgres AI CLI" + echo "$out" | grep -q "COMMANDS:" + rules: + - if: '$CI_COMMIT_BRANCH' + + From d803b07c3f5649aaf1c66977c7f8e4cdc8486798 Mon Sep 17 00:00:00 2001 From: Nikolay Samokhvalov Date: Tue, 28 Oct 2025 09:21:09 -0700 Subject: [PATCH 02/96] feat(cli): bootstrap Node.js CLI skeleton with commander and CI smoke test\n\n- add cli/ package.json with bin aliases postgres-ai and pgai\n- add minimal bin entry using commander\n- extend GitLab CI with node smoke job (Node 20) --- .gitlab-ci.yml | 14 ++++++++++++++ cli/bin/postgres-ai.js | 23 +++++++++++++++++++++++ cli/package.json | 23 +++++++++++++++++++++++ 3 files changed, 60 insertions(+) create mode 100644 cli/bin/postgres-ai.js create mode 100644 cli/package.json diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b7f7b8a..58da5f9 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,4 +18,18 @@ cli:smoke:test: rules: - if: '$CI_COMMIT_BRANCH' +cli:node:smoke: + stage: test + image: node:20-alpine + variables: + GIT_STRATEGY: fetch + before_script: + - corepack enable || true + script: + - node -v && npm -v + - npm --prefix cli install --no-audit --no-fund + - node ./cli/bin/postgres-ai.js --help + rules: + - if: '$CI_COMMIT_BRANCH' + diff --git a/cli/bin/postgres-ai.js b/cli/bin/postgres-ai.js new file mode 100644 index 0000000..3569d16 --- /dev/null +++ b/cli/bin/postgres-ai.js @@ -0,0 +1,23 @@ +#!/usr/bin/env node +"use strict"; + +const { Command } = require("commander"); +const pkg = require("../package.json"); + +const program = new Command(); + +program + .name("postgres-ai") + .description("PostgresAI CLI") + .version(pkg.version); + +program + .command("help", { isDefault: true }) + .description("show help") + .action(() => { + program.outputHelp(); + }); + +program.parseAsync(process.argv); + + diff --git a/cli/package.json b/cli/package.json new file mode 100644 index 0000000..e177c4a --- /dev/null +++ b/cli/package.json @@ -0,0 +1,23 @@ +{ + "name": "@postgresai/cli", + "version": "0.1.0", + "description": "PostgresAI CLI (Node.js)", + "license": "MIT", + "private": false, + "bin": { + "postgres-ai": "./bin/postgres-ai.js", + "pgai": "./bin/postgres-ai.js" + }, + "type": "commonjs", + "engines": { + "node": ">=18" + }, + "scripts": { + "start": "node ./bin/postgres-ai.js --help" + }, + "dependencies": { + "commander": "^12.1.0" + } +} + + From ed3d17b58ffea1f69400544df8312d4fa3d72fcb Mon Sep 17 00:00:00 2001 From: Nikolay Samokhvalov Date: Tue, 28 Oct 2025 09:21:51 -0700 Subject: [PATCH 03/96] ci: add Node integration job gated by PGAIS_API_KEY\n\n- add cli:node:integration job for future API-backed tests\n- runs only when PGAIS_API_KEY is provided\n- prepares BASE_URL, defaults to console-dev.postgres.ai --- .gitlab-ci.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 58da5f9..f722adc 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -32,4 +32,24 @@ cli:node:smoke: rules: - if: '$CI_COMMIT_BRANCH' +cli:node:integration: + stage: test + image: node:20-alpine + variables: + GIT_STRATEGY: fetch + before_script: + - corepack enable || true + - node -v && npm -v + - npm --prefix cli install --no-audit --no-fund + script: + - | + set -euo pipefail + : "${PGAIS_API_KEY:?PGAIS_API_KEY is required for integration tests}" + BASE_URL="${PGAIS_BASE_URL:-https://console-dev.postgres.ai}" + echo "Using BASE_URL=$BASE_URL" + # Placeholder: run CLI help until API-backed commands are implemented + node ./cli/bin/postgres-ai.js --help + rules: + - if: '$PGAIS_API_KEY' + From e33a9f8c0ac9522492c83155a76d429b49c2ece3 Mon Sep 17 00:00:00 2001 From: Nikolay Samokhvalov Date: Tue, 28 Oct 2025 09:22:14 -0700 Subject: [PATCH 04/96] docs: add Node CLI preview usage and planned npm install\n\n- document running node-based CLI directly\n- outline future npm global install with postgres-ai/pgai aliases --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 2e52413..c996db2 100644 --- a/README.md +++ b/README.md @@ -203,6 +203,18 @@ Technical URLs (for advanced users): ./postgres_ai help ``` +### Node.js CLI (early preview) + +```bash +# run without install +node ./cli/bin/postgres-ai.js --help + +# or install globally after publish (planned) +# npm i -g @postgresai/cli +# postgres-ai --help +# pgai --help +``` + ## 🔑 PostgresAI access token Get your access token at [PostgresAI](https://postgres.ai) for automated report uploads and advanced analysis. From 872ee43822b4d919f229342cd9c346a61ba22f3b Mon Sep 17 00:00:00 2001 From: Nikolay Samokhvalov Date: Tue, 28 Oct 2025 09:29:00 -0700 Subject: [PATCH 05/96] feat(cli): scaffold Node subcommands and env config\n\n- add stubs for all bash-parity commands in Node CLI\n- add --api-key/--base-url options with env fallbacks\n- CI: default BASE_URL to v2 API root; verify subcommand help --- .gitlab-ci.yml | 4 ++- cli/bin/postgres-ai.js | 78 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 76 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f722adc..cc01733 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,6 +29,8 @@ cli:node:smoke: - node -v && npm -v - npm --prefix cli install --no-audit --no-fund - node ./cli/bin/postgres-ai.js --help + - node ./cli/bin/postgres-ai.js status --help + - node ./cli/bin/postgres-ai.js list-instances --help rules: - if: '$CI_COMMIT_BRANCH' @@ -45,7 +47,7 @@ cli:node:integration: - | set -euo pipefail : "${PGAIS_API_KEY:?PGAIS_API_KEY is required for integration tests}" - BASE_URL="${PGAIS_BASE_URL:-https://console-dev.postgres.ai}" + BASE_URL="${PGAIS_BASE_URL:-https://v2.postgres.ai/api/general/}" echo "Using BASE_URL=$BASE_URL" # Placeholder: run CLI help until API-backed commands are implemented node ./cli/bin/postgres-ai.js --help diff --git a/cli/bin/postgres-ai.js b/cli/bin/postgres-ai.js index 3569d16..95ab2de 100644 --- a/cli/bin/postgres-ai.js +++ b/cli/bin/postgres-ai.js @@ -4,6 +4,13 @@ const { Command } = require("commander"); const pkg = require("../package.json"); +function getConfig(opts) { + const apiKey = opts.apiKey || process.env.PGAIS_API_KEY || ""; + const baseUrl = + opts.baseUrl || process.env.PGAIS_BASE_URL || "https://v2.postgres.ai/api/general/"; + return { apiKey, baseUrl }; +} + const program = new Command(); program @@ -12,11 +19,72 @@ program .version(pkg.version); program - .command("help", { isDefault: true }) - .description("show help") - .action(() => { - program.outputHelp(); - }); + .option("--api-key ", "API key (overrides PGAIS_API_KEY)") + .option( + "--base-url ", + "API base URL (overrides PGAIS_BASE_URL)", + "https://v2.postgres.ai/api/general/" + ); + +const stub = (name) => async () => { + console.error(`${name}: not implemented in Node CLI yet; use bash CLI for now`); + process.exitCode = 2; +}; + +program.command("help", { isDefault: true }).description("show help").action(() => { + program.outputHelp(); +}); + +// Service lifecycle +program.command("quickstart").description("complete setup").action(stub("quickstart")); +program.command("install").description("install project").action(stub("install")); +program.command("start").description("start services").action(stub("start")); +program.command("stop").description("stop services").action(stub("stop")); +program.command("restart").description("restart services").action(stub("restart")); +program.command("status").description("show service status").action(stub("status")); +program + .command("logs [service]") + .description("show logs for all or specific service") + .action(stub("logs")); +program.command("health").description("health check").action(stub("health")); +program.command("config").description("show configuration").action(stub("config")); +program.command("update-config").description("apply configuration").action(stub("update-config")); +program.command("update").description("update project").action(stub("update")); +program + .command("reset [service]") + .description("reset all or specific service") + .action(stub("reset")); +program.command("clean").description("cleanup artifacts").action(stub("clean")); +program.command("shell ").description("open service shell").action(stub("shell")); +program.command("check").description("system readiness check").action(stub("check")); + +// Instance management +program.command("list-instances").description("list instances").action(stub("list-instances")); +program + .command("add-instance [connStr] [name]") + .description("add instance") + .action(stub("add-instance")); +program + .command("remove-instance ") + .description("remove instance") + .action(stub("remove-instance")); +program + .command("test-instance ") + .description("test instance connectivity") + .action(stub("test-instance")); + +// API key and grafana +program.command("add-key ").description("store API key").action(stub("add-key")); +program.command("show-key").description("show API key (masked)").action(stub("show-key")); +program.command("remove-key").description("remove API key").action(stub("remove-key")); +program + .command("generate-grafana-password") + .description("generate Grafana password") + .action(stub("generate-grafana-password")); +program + .command("show-grafana-credentials") + .description("show Grafana credentials") + .action(stub("show-grafana-credentials")); program.parseAsync(process.argv); From 6875f604bea53efe2d5ef9de876f1aaa246e8ccd Mon Sep 17 00:00:00 2001 From: Nikolay Samokhvalov Date: Tue, 28 Oct 2025 10:46:53 -0700 Subject: [PATCH 06/96] ci(e2e): add Docker-in-Docker job covering bash CLI commands\n\n- run quickstart demo and core commands with timeouts\n- exercise add/show/remove key, grafana password/creds\n- add-instance + test-instance via docker exec fallback\n- reset service and full reset; restart/stop/start; clean --- .gitlab-ci.yml | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cc01733..66b4817 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,6 +18,47 @@ cli:smoke:test: rules: - if: '$CI_COMMIT_BRANCH' +cli:e2e:dind: + stage: test + image: alpine:3.20 + services: + - name: docker:24-dind + command: ["--tls=false"] + variables: + DOCKER_HOST: tcp://docker:2375 + DOCKER_TLS_CERTDIR: "" + GIT_STRATEGY: fetch + before_script: + - apk add --no-cache bash curl git coreutils docker-cli docker-compose + - docker version + script: + - set -euo pipefail + - bash -n ./postgres_ai + - ./postgres_ai check || true + - ./postgres_ai quickstart --demo -y + - timeout 60 ./postgres_ai status + - timeout 10 ./postgres_ai logs grafana || true + - ./postgres_ai config + - ./postgres_ai update-config + - ./postgres_ai list-instances || true + - ./postgres_ai add-key "test_key_123" + - ./postgres_ai show-key + - ./postgres_ai remove-key + - ./postgres_ai generate-grafana-password || true + - ./postgres_ai show-grafana-credentials || true + - ./postgres_ai add-instance "postgresql://postgres:postgres@target-db:5432/target_database" "ci-demo" + - ./postgres_ai test-instance "ci-demo" || true + - printf "y\n" | ./postgres_ai reset sink-postgres + - ./postgres_ai restart + - ./postgres_ai stop + - ./postgres_ai start + - printf "y\n" | ./postgres_ai reset + - ./postgres_ai clean + after_script: + - docker ps -a || true + - docker system prune -af || true + rules: + - if: '$CI_COMMIT_BRANCH' cli:node:smoke: stage: test image: node:20-alpine From dffd6a68cd2390a1533692075e8519d9ce761461 Mon Sep 17 00:00:00 2001 From: Nikolay Samokhvalov Date: Tue, 28 Oct 2025 10:52:46 -0700 Subject: [PATCH 07/96] ci(e2e): install openssl to enable Grafana password generation\n\n- fix quickstart failure due to missing openssl (empty password)\n- expect grafana-cli password reset to succeed afterwards --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 66b4817..aca744d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -29,7 +29,7 @@ cli:e2e:dind: DOCKER_TLS_CERTDIR: "" GIT_STRATEGY: fetch before_script: - - apk add --no-cache bash curl git coreutils docker-cli docker-compose + - apk add --no-cache bash curl git coreutils docker-cli docker-compose openssl - docker version script: - set -euo pipefail From 1e7b207e4530e6893fe576ace57b98215f7dffd1 Mon Sep 17 00:00:00 2001 From: Nikolay Samokhvalov Date: Tue, 28 Oct 2025 10:59:58 -0700 Subject: [PATCH 08/96] ci(node): verify npm-linked aliases postgres-ai and pgai\n\n- link cli package in CI and run postgres-ai/pgai --help\n- ensure aliases work without ./ via PATH\n\nchore(cli): add npm publish metadata\n\n- add repository/homepage/bugs and publishConfig.access=public\n\ndocs: add npm link usage for local aliases --- .gitlab-ci.yml | 3 +++ README.md | 6 ++++++ cli/package.json | 11 +++++++++++ 3 files changed, 20 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index aca744d..c0d6662 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -72,6 +72,9 @@ cli:node:smoke: - node ./cli/bin/postgres-ai.js --help - node ./cli/bin/postgres-ai.js status --help - node ./cli/bin/postgres-ai.js list-instances --help + - npm --prefix cli link + - postgres-ai --help + - pgai --help rules: - if: '$CI_COMMIT_BRANCH' diff --git a/README.md b/README.md index c996db2..3cd8582 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,12 @@ Technical URLs (for advanced users): # run without install node ./cli/bin/postgres-ai.js --help +# local dev: install aliases into PATH +npm --prefix cli install --no-audit --no-fund +npm --prefix cli link +postgres-ai --help +pgai --help + # or install globally after publish (planned) # npm i -g @postgresai/cli # postgres-ai --help diff --git a/cli/package.json b/cli/package.json index e177c4a..5ec6439 100644 --- a/cli/package.json +++ b/cli/package.json @@ -4,6 +4,14 @@ "description": "PostgresAI CLI (Node.js)", "license": "MIT", "private": false, + "repository": { + "type": "git", + "url": "git+https://gitlab.com/postgres-ai/postgres_ai.git" + }, + "homepage": "https://gitlab.com/postgres-ai/postgres_ai", + "bugs": { + "url": "https://gitlab.com/postgres-ai/postgres_ai/-/issues" + }, "bin": { "postgres-ai": "./bin/postgres-ai.js", "pgai": "./bin/postgres-ai.js" @@ -17,6 +25,9 @@ }, "dependencies": { "commander": "^12.1.0" + }, + "publishConfig": { + "access": "public" } } From bec9f961a1c96ca9c0bc0024e340038b224bd013 Mon Sep 17 00:00:00 2001 From: Nikolay Samokhvalov Date: Tue, 28 Oct 2025 12:38:18 -0700 Subject: [PATCH 09/96] chore(cli): drop redundant alias wrappers\n\n- remove cli/bin/postgres-ai and cli/bin/pgai\n- rely on package.json bin mapping to bin/postgres-ai.js --- .gitlab-ci.yml | 7 +- cli/bin/postgres-ai.js | 92 +- cli/lib/node_modules/@postgresai/cli | 1 + cli/node_modules/.package-lock.json | 17 + cli/node_modules/commander/LICENSE | 22 + cli/node_modules/commander/Readme.md | 1157 ++++++++ cli/node_modules/commander/esm.mjs | 16 + cli/node_modules/commander/index.js | 24 + cli/node_modules/commander/lib/argument.js | 149 + cli/node_modules/commander/lib/command.js | 2509 +++++++++++++++++ cli/node_modules/commander/lib/error.js | 39 + cli/node_modules/commander/lib/help.js | 520 ++++ cli/node_modules/commander/lib/option.js | 330 +++ .../commander/lib/suggestSimilar.js | 101 + .../commander/package-support.json | 16 + cli/node_modules/commander/package.json | 84 + cli/node_modules/commander/typings/esm.d.mts | 3 + cli/node_modules/commander/typings/index.d.ts | 969 +++++++ cli/package-lock.json | 32 + 19 files changed, 5994 insertions(+), 94 deletions(-) mode change 100644 => 100755 cli/bin/postgres-ai.js create mode 120000 cli/lib/node_modules/@postgresai/cli create mode 100644 cli/node_modules/.package-lock.json create mode 100644 cli/node_modules/commander/LICENSE create mode 100644 cli/node_modules/commander/Readme.md create mode 100644 cli/node_modules/commander/esm.mjs create mode 100644 cli/node_modules/commander/index.js create mode 100644 cli/node_modules/commander/lib/argument.js create mode 100644 cli/node_modules/commander/lib/command.js create mode 100644 cli/node_modules/commander/lib/error.js create mode 100644 cli/node_modules/commander/lib/help.js create mode 100644 cli/node_modules/commander/lib/option.js create mode 100644 cli/node_modules/commander/lib/suggestSimilar.js create mode 100644 cli/node_modules/commander/package-support.json create mode 100644 cli/node_modules/commander/package.json create mode 100644 cli/node_modules/commander/typings/esm.d.mts create mode 100644 cli/node_modules/commander/typings/index.d.ts create mode 100644 cli/package-lock.json diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c0d6662..d531276 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -72,9 +72,10 @@ cli:node:smoke: - node ./cli/bin/postgres-ai.js --help - node ./cli/bin/postgres-ai.js status --help - node ./cli/bin/postgres-ai.js list-instances --help - - npm --prefix cli link - - postgres-ai --help - - pgai --help + - npm install -g ./cli + - echo "prefix=$(npm config get prefix)" && echo "PATH=$PATH" + - command -v postgres-ai && postgres-ai --help + - command -v pgai && pgai --help rules: - if: '$CI_COMMIT_BRANCH' diff --git a/cli/bin/postgres-ai.js b/cli/bin/postgres-ai.js old mode 100644 new mode 100755 index 95ab2de..0519ecb --- a/cli/bin/postgres-ai.js +++ b/cli/bin/postgres-ai.js @@ -1,91 +1 @@ -#!/usr/bin/env node -"use strict"; - -const { Command } = require("commander"); -const pkg = require("../package.json"); - -function getConfig(opts) { - const apiKey = opts.apiKey || process.env.PGAIS_API_KEY || ""; - const baseUrl = - opts.baseUrl || process.env.PGAIS_BASE_URL || "https://v2.postgres.ai/api/general/"; - return { apiKey, baseUrl }; -} - -const program = new Command(); - -program - .name("postgres-ai") - .description("PostgresAI CLI") - .version(pkg.version); - -program - .option("--api-key ", "API key (overrides PGAIS_API_KEY)") - .option( - "--base-url ", - "API base URL (overrides PGAIS_BASE_URL)", - "https://v2.postgres.ai/api/general/" - ); - -const stub = (name) => async () => { - console.error(`${name}: not implemented in Node CLI yet; use bash CLI for now`); - process.exitCode = 2; -}; - -program.command("help", { isDefault: true }).description("show help").action(() => { - program.outputHelp(); -}); - -// Service lifecycle -program.command("quickstart").description("complete setup").action(stub("quickstart")); -program.command("install").description("install project").action(stub("install")); -program.command("start").description("start services").action(stub("start")); -program.command("stop").description("stop services").action(stub("stop")); -program.command("restart").description("restart services").action(stub("restart")); -program.command("status").description("show service status").action(stub("status")); -program - .command("logs [service]") - .description("show logs for all or specific service") - .action(stub("logs")); -program.command("health").description("health check").action(stub("health")); -program.command("config").description("show configuration").action(stub("config")); -program.command("update-config").description("apply configuration").action(stub("update-config")); -program.command("update").description("update project").action(stub("update")); -program - .command("reset [service]") - .description("reset all or specific service") - .action(stub("reset")); -program.command("clean").description("cleanup artifacts").action(stub("clean")); -program.command("shell ").description("open service shell").action(stub("shell")); -program.command("check").description("system readiness check").action(stub("check")); - -// Instance management -program.command("list-instances").description("list instances").action(stub("list-instances")); -program - .command("add-instance [connStr] [name]") - .description("add instance") - .action(stub("add-instance")); -program - .command("remove-instance ") - .description("remove instance") - .action(stub("remove-instance")); -program - .command("test-instance ") - .description("test instance connectivity") - .action(stub("test-instance")); - -// API key and grafana -program.command("add-key ").description("store API key").action(stub("add-key")); -program.command("show-key").description("show API key (masked)").action(stub("show-key")); -program.command("remove-key").description("remove API key").action(stub("remove-key")); -program - .command("generate-grafana-password") - .description("generate Grafana password") - .action(stub("generate-grafana-password")); -program - .command("show-grafana-credentials") - .description("show Grafana credentials") - .action(stub("show-grafana-credentials")); - -program.parseAsync(process.argv); - - + \ No newline at end of file diff --git a/cli/lib/node_modules/@postgresai/cli b/cli/lib/node_modules/@postgresai/cli new file mode 120000 index 0000000..a8a4f8c --- /dev/null +++ b/cli/lib/node_modules/@postgresai/cli @@ -0,0 +1 @@ +../../.. \ No newline at end of file diff --git a/cli/node_modules/.package-lock.json b/cli/node_modules/.package-lock.json new file mode 100644 index 0000000..478fbac --- /dev/null +++ b/cli/node_modules/.package-lock.json @@ -0,0 +1,17 @@ +{ + "name": "@postgresai/cli", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + } + } +} diff --git a/cli/node_modules/commander/LICENSE b/cli/node_modules/commander/LICENSE new file mode 100644 index 0000000..10f997a --- /dev/null +++ b/cli/node_modules/commander/LICENSE @@ -0,0 +1,22 @@ +(The MIT License) + +Copyright (c) 2011 TJ Holowaychuk + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +'Software'), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/cli/node_modules/commander/Readme.md b/cli/node_modules/commander/Readme.md new file mode 100644 index 0000000..e4e3657 --- /dev/null +++ b/cli/node_modules/commander/Readme.md @@ -0,0 +1,1157 @@ +# Commander.js + +[![Build Status](https://github.com/tj/commander.js/workflows/build/badge.svg)](https://github.com/tj/commander.js/actions?query=workflow%3A%22build%22) +[![NPM Version](http://img.shields.io/npm/v/commander.svg?style=flat)](https://www.npmjs.org/package/commander) +[![NPM Downloads](https://img.shields.io/npm/dm/commander.svg?style=flat)](https://npmcharts.com/compare/commander?minimal=true) +[![Install Size](https://packagephobia.now.sh/badge?p=commander)](https://packagephobia.now.sh/result?p=commander) + +The complete solution for [node.js](http://nodejs.org) command-line interfaces. + +Read this in other languages: English | [įŽ€äŊ“中文](./Readme_zh-CN.md) + +- [Commander.js](#commanderjs) + - [Installation](#installation) + - [Quick Start](#quick-start) + - [Declaring _program_ variable](#declaring-program-variable) + - [Options](#options) + - [Common option types, boolean and value](#common-option-types-boolean-and-value) + - [Default option value](#default-option-value) + - [Other option types, negatable boolean and boolean|value](#other-option-types-negatable-boolean-and-booleanvalue) + - [Required option](#required-option) + - [Variadic option](#variadic-option) + - [Version option](#version-option) + - [More configuration](#more-configuration) + - [Custom option processing](#custom-option-processing) + - [Commands](#commands) + - [Command-arguments](#command-arguments) + - [More configuration](#more-configuration-1) + - [Custom argument processing](#custom-argument-processing) + - [Action handler](#action-handler) + - [Stand-alone executable (sub)commands](#stand-alone-executable-subcommands) + - [Life cycle hooks](#life-cycle-hooks) + - [Automated help](#automated-help) + - [Custom help](#custom-help) + - [Display help after errors](#display-help-after-errors) + - [Display help from code](#display-help-from-code) + - [.name](#name) + - [.usage](#usage) + - [.description and .summary](#description-and-summary) + - [.helpOption(flags, description)](#helpoptionflags-description) + - [.helpCommand()](#helpcommand) + - [More configuration](#more-configuration-2) + - [Custom event listeners](#custom-event-listeners) + - [Bits and pieces](#bits-and-pieces) + - [.parse() and .parseAsync()](#parse-and-parseasync) + - [Parsing Configuration](#parsing-configuration) + - [Legacy options as properties](#legacy-options-as-properties) + - [TypeScript](#typescript) + - [createCommand()](#createcommand) + - [Node options such as `--harmony`](#node-options-such-as---harmony) + - [Debugging stand-alone executable subcommands](#debugging-stand-alone-executable-subcommands) + - [npm run-script](#npm-run-script) + - [Display error](#display-error) + - [Override exit and output handling](#override-exit-and-output-handling) + - [Additional documentation](#additional-documentation) + - [Support](#support) + - [Commander for enterprise](#commander-for-enterprise) + +For information about terms used in this document see: [terminology](./docs/terminology.md) + +## Installation + +```sh +npm install commander +``` + +## Quick Start + +You write code to describe your command line interface. +Commander looks after parsing the arguments into options and command-arguments, +displays usage errors for problems, and implements a help system. + +Commander is strict and displays an error for unrecognised options. +The two most used option types are a boolean option, and an option which takes its value from the following argument. + +Example file: [split.js](./examples/split.js) + +```js +const { program } = require('commander'); + +program + .option('--first') + .option('-s, --separator '); + +program.parse(); + +const options = program.opts(); +const limit = options.first ? 1 : undefined; +console.log(program.args[0].split(options.separator, limit)); +``` + +```console +$ node split.js -s / --fits a/b/c +error: unknown option '--fits' +(Did you mean --first?) +$ node split.js -s / --first a/b/c +[ 'a' ] +``` + +Here is a more complete program using a subcommand and with descriptions for the help. In a multi-command program, you have an action handler for each command (or stand-alone executables for the commands). + +Example file: [string-util.js](./examples/string-util.js) + +```js +const { Command } = require('commander'); +const program = new Command(); + +program + .name('string-util') + .description('CLI to some JavaScript string utilities') + .version('0.8.0'); + +program.command('split') + .description('Split a string into substrings and display as an array') + .argument('', 'string to split') + .option('--first', 'display just the first substring') + .option('-s, --separator ', 'separator character', ',') + .action((str, options) => { + const limit = options.first ? 1 : undefined; + console.log(str.split(options.separator, limit)); + }); + +program.parse(); +``` + +```console +$ node string-util.js help split +Usage: string-util split [options] + +Split a string into substrings and display as an array. + +Arguments: + string string to split + +Options: + --first display just the first substring + -s, --separator separator character (default: ",") + -h, --help display help for command + +$ node string-util.js split --separator=/ a/b/c +[ 'a', 'b', 'c' ] +``` + +More samples can be found in the [examples](https://github.com/tj/commander.js/tree/master/examples) directory. + +## Declaring _program_ variable + +Commander exports a global object which is convenient for quick programs. +This is used in the examples in this README for brevity. + +```js +// CommonJS (.cjs) +const { program } = require('commander'); +``` + +For larger programs which may use commander in multiple ways, including unit testing, it is better to create a local Command object to use. + +```js +// CommonJS (.cjs) +const { Command } = require('commander'); +const program = new Command(); +``` + +```js +// ECMAScript (.mjs) +import { Command } from 'commander'; +const program = new Command(); +``` + +```ts +// TypeScript (.ts) +import { Command } from 'commander'; +const program = new Command(); +``` + +## Options + +Options are defined with the `.option()` method, also serving as documentation for the options. Each option can have a short flag (single character) and a long name, separated by a comma or space or vertical bar ('|'). + +The parsed options can be accessed by calling `.opts()` on a `Command` object, and are passed to the action handler. + +Multi-word options such as "--template-engine" are camel-cased, becoming `program.opts().templateEngine` etc. + +An option and its option-argument can be separated by a space, or combined into the same argument. The option-argument can follow the short option directly or follow an `=` for a long option. + +```sh +serve -p 80 +serve -p80 +serve --port 80 +serve --port=80 +``` + +You can use `--` to indicate the end of the options, and any remaining arguments will be used without being interpreted. + +By default, options on the command line are not positional, and can be specified before or after other arguments. + +There are additional related routines for when `.opts()` is not enough: + +- `.optsWithGlobals()` returns merged local and global option values +- `.getOptionValue()` and `.setOptionValue()` work with a single option value +- `.getOptionValueSource()` and `.setOptionValueWithSource()` include where the option value came from + +### Common option types, boolean and value + +The two most used option types are a boolean option, and an option which takes its value +from the following argument (declared with angle brackets like `--expect `). Both are `undefined` unless specified on command line. + +Example file: [options-common.js](./examples/options-common.js) + +```js +program + .option('-d, --debug', 'output extra debugging') + .option('-s, --small', 'small pizza size') + .option('-p, --pizza-type ', 'flavour of pizza'); + +program.parse(process.argv); + +const options = program.opts(); +if (options.debug) console.log(options); +console.log('pizza details:'); +if (options.small) console.log('- small pizza size'); +if (options.pizzaType) console.log(`- ${options.pizzaType}`); +``` + +```console +$ pizza-options -p +error: option '-p, --pizza-type ' argument missing +$ pizza-options -d -s -p vegetarian +{ debug: true, small: true, pizzaType: 'vegetarian' } +pizza details: +- small pizza size +- vegetarian +$ pizza-options --pizza-type=cheese +pizza details: +- cheese +``` + +Multiple boolean short options may be combined following the dash, and may be followed by a single short option taking a value. +For example `-d -s -p cheese` may be written as `-ds -p cheese` or even `-dsp cheese`. + +Options with an expected option-argument are greedy and will consume the following argument whatever the value. +So `--id -xyz` reads `-xyz` as the option-argument. + +`program.parse(arguments)` processes the arguments, leaving any args not consumed by the program options in the `program.args` array. The parameter is optional and defaults to `process.argv`. + +### Default option value + +You can specify a default value for an option. + +Example file: [options-defaults.js](./examples/options-defaults.js) + +```js +program + .option('-c, --cheese ', 'add the specified type of cheese', 'blue'); + +program.parse(); + +console.log(`cheese: ${program.opts().cheese}`); +``` + +```console +$ pizza-options +cheese: blue +$ pizza-options --cheese stilton +cheese: stilton +``` + +### Other option types, negatable boolean and boolean|value + +You can define a boolean option long name with a leading `no-` to set the option value to false when used. +Defined alone this also makes the option true by default. + +If you define `--foo` first, adding `--no-foo` does not change the default value from what it would +otherwise be. + +Example file: [options-negatable.js](./examples/options-negatable.js) + +```js +program + .option('--no-sauce', 'Remove sauce') + .option('--cheese ', 'cheese flavour', 'mozzarella') + .option('--no-cheese', 'plain with no cheese') + .parse(); + +const options = program.opts(); +const sauceStr = options.sauce ? 'sauce' : 'no sauce'; +const cheeseStr = (options.cheese === false) ? 'no cheese' : `${options.cheese} cheese`; +console.log(`You ordered a pizza with ${sauceStr} and ${cheeseStr}`); +``` + +```console +$ pizza-options +You ordered a pizza with sauce and mozzarella cheese +$ pizza-options --sauce +error: unknown option '--sauce' +$ pizza-options --cheese=blue +You ordered a pizza with sauce and blue cheese +$ pizza-options --no-sauce --no-cheese +You ordered a pizza with no sauce and no cheese +``` + +You can specify an option which may be used as a boolean option but may optionally take an option-argument +(declared with square brackets like `--optional [value]`). + +Example file: [options-boolean-or-value.js](./examples/options-boolean-or-value.js) + +```js +program + .option('-c, --cheese [type]', 'Add cheese with optional type'); + +program.parse(process.argv); + +const options = program.opts(); +if (options.cheese === undefined) console.log('no cheese'); +else if (options.cheese === true) console.log('add cheese'); +else console.log(`add cheese type ${options.cheese}`); +``` + +```console +$ pizza-options +no cheese +$ pizza-options --cheese +add cheese +$ pizza-options --cheese mozzarella +add cheese type mozzarella +``` + +Options with an optional option-argument are not greedy and will ignore arguments starting with a dash. +So `id` behaves as a boolean option for `--id -5`, but you can use a combined form if needed like `--id=-5`. + +For information about possible ambiguous cases, see [options taking varying arguments](./docs/options-in-depth.md). + +### Required option + +You may specify a required (mandatory) option using `.requiredOption()`. The option must have a value after parsing, usually specified on the command line, or perhaps from a default value (say from environment). The method is otherwise the same as `.option()` in format, taking flags and description, and optional default value or custom processing. + +Example file: [options-required.js](./examples/options-required.js) + +```js +program + .requiredOption('-c, --cheese ', 'pizza must have cheese'); + +program.parse(); +``` + +```console +$ pizza +error: required option '-c, --cheese ' not specified +``` + +### Variadic option + +You may make an option variadic by appending `...` to the value placeholder when declaring the option. On the command line you +can then specify multiple option-arguments, and the parsed option value will be an array. The extra arguments +are read until the first argument starting with a dash. The special argument `--` stops option processing entirely. If a value +is specified in the same argument as the option then no further values are read. + +Example file: [options-variadic.js](./examples/options-variadic.js) + +```js +program + .option('-n, --number ', 'specify numbers') + .option('-l, --letter [letters...]', 'specify letters'); + +program.parse(); + +console.log('Options: ', program.opts()); +console.log('Remaining arguments: ', program.args); +``` + +```console +$ collect -n 1 2 3 --letter a b c +Options: { number: [ '1', '2', '3' ], letter: [ 'a', 'b', 'c' ] } +Remaining arguments: [] +$ collect --letter=A -n80 operand +Options: { number: [ '80' ], letter: [ 'A' ] } +Remaining arguments: [ 'operand' ] +$ collect --letter -n 1 -n 2 3 -- operand +Options: { number: [ '1', '2', '3' ], letter: true } +Remaining arguments: [ 'operand' ] +``` + +For information about possible ambiguous cases, see [options taking varying arguments](./docs/options-in-depth.md). + +### Version option + +The optional `version` method adds handling for displaying the command version. The default option flags are `-V` and `--version`, and when present the command prints the version number and exits. + +```js +program.version('0.0.1'); +``` + +```console +$ ./examples/pizza -V +0.0.1 +``` + +You may change the flags and description by passing additional parameters to the `version` method, using +the same syntax for flags as the `option` method. + +```js +program.version('0.0.1', '-v, --vers', 'output the current version'); +``` + +### More configuration + +You can add most options using the `.option()` method, but there are some additional features available +by constructing an `Option` explicitly for less common cases. + +Example files: [options-extra.js](./examples/options-extra.js), [options-env.js](./examples/options-env.js), [options-conflicts.js](./examples/options-conflicts.js), [options-implies.js](./examples/options-implies.js) + +```js +program + .addOption(new Option('-s, --secret').hideHelp()) + .addOption(new Option('-t, --timeout ', 'timeout in seconds').default(60, 'one minute')) + .addOption(new Option('-d, --drink ', 'drink size').choices(['small', 'medium', 'large'])) + .addOption(new Option('-p, --port ', 'port number').env('PORT')) + .addOption(new Option('--donate [amount]', 'optional donation in dollars').preset('20').argParser(parseFloat)) + .addOption(new Option('--disable-server', 'disables the server').conflicts('port')) + .addOption(new Option('--free-drink', 'small drink included free ').implies({ drink: 'small' })); +``` + +```console +$ extra --help +Usage: help [options] + +Options: + -t, --timeout timeout in seconds (default: one minute) + -d, --drink drink cup size (choices: "small", "medium", "large") + -p, --port port number (env: PORT) + --donate [amount] optional donation in dollars (preset: "20") + --disable-server disables the server + --free-drink small drink included free + -h, --help display help for command + +$ extra --drink huge +error: option '-d, --drink ' argument 'huge' is invalid. Allowed choices are small, medium, large. + +$ PORT=80 extra --donate --free-drink +Options: { timeout: 60, donate: 20, port: '80', freeDrink: true, drink: 'small' } + +$ extra --disable-server --port 8000 +error: option '--disable-server' cannot be used with option '-p, --port ' +``` + +Specify a required (mandatory) option using the `Option` method `.makeOptionMandatory()`. This matches the `Command` method [.requiredOption()](#required-option). + +### Custom option processing + +You may specify a function to do custom processing of option-arguments. The callback function receives two parameters, +the user specified option-argument and the previous value for the option. It returns the new value for the option. + +This allows you to coerce the option-argument to the desired type, or accumulate values, or do entirely custom processing. + +You can optionally specify the default/starting value for the option after the function parameter. + +Example file: [options-custom-processing.js](./examples/options-custom-processing.js) + +```js +function myParseInt(value, dummyPrevious) { + // parseInt takes a string and a radix + const parsedValue = parseInt(value, 10); + if (isNaN(parsedValue)) { + throw new commander.InvalidArgumentError('Not a number.'); + } + return parsedValue; +} + +function increaseVerbosity(dummyValue, previous) { + return previous + 1; +} + +function collect(value, previous) { + return previous.concat([value]); +} + +function commaSeparatedList(value, dummyPrevious) { + return value.split(','); +} + +program + .option('-f, --float ', 'float argument', parseFloat) + .option('-i, --integer ', 'integer argument', myParseInt) + .option('-v, --verbose', 'verbosity that can be increased', increaseVerbosity, 0) + .option('-c, --collect ', 'repeatable value', collect, []) + .option('-l, --list ', 'comma separated list', commaSeparatedList) +; + +program.parse(); + +const options = program.opts(); +if (options.float !== undefined) console.log(`float: ${options.float}`); +if (options.integer !== undefined) console.log(`integer: ${options.integer}`); +if (options.verbose > 0) console.log(`verbosity: ${options.verbose}`); +if (options.collect.length > 0) console.log(options.collect); +if (options.list !== undefined) console.log(options.list); +``` + +```console +$ custom -f 1e2 +float: 100 +$ custom --integer 2 +integer: 2 +$ custom -v -v -v +verbose: 3 +$ custom -c a -c b -c c +[ 'a', 'b', 'c' ] +$ custom --list x,y,z +[ 'x', 'y', 'z' ] +``` + +## Commands + +You can specify (sub)commands using `.command()` or `.addCommand()`. There are two ways these can be implemented: using an action handler attached to the command, or as a stand-alone executable file (described in more detail later). The subcommands may be nested ([example](./examples/nestedCommands.js)). + +In the first parameter to `.command()` you specify the command name. You may append the command-arguments after the command name, or specify them separately using `.argument()`. The arguments may be `` or `[optional]`, and the last argument may also be `variadic...`. + +You can use `.addCommand()` to add an already configured subcommand to the program. + +For example: + +```js +// Command implemented using action handler (description is supplied separately to `.command`) +// Returns new command for configuring. +program + .command('clone [destination]') + .description('clone a repository into a newly created directory') + .action((source, destination) => { + console.log('clone command called'); + }); + +// Command implemented using stand-alone executable file, indicated by adding description as second parameter to `.command`. +// Returns `this` for adding more commands. +program + .command('start ', 'start named service') + .command('stop [service]', 'stop named service, or all if no name supplied'); + +// Command prepared separately. +// Returns `this` for adding more commands. +program + .addCommand(build.makeBuildCommand()); +``` + +Configuration options can be passed with the call to `.command()` and `.addCommand()`. Specifying `hidden: true` will +remove the command from the generated help output. Specifying `isDefault: true` will run the subcommand if no other +subcommand is specified ([example](./examples/defaultCommand.js)). + +You can add alternative names for a command with `.alias()`. ([example](./examples/alias.js)) + +`.command()` automatically copies the inherited settings from the parent command to the newly created subcommand. This is only done during creation, any later setting changes to the parent are not inherited. + +For safety, `.addCommand()` does not automatically copy the inherited settings from the parent command. There is a helper routine `.copyInheritedSettings()` for copying the settings when they are wanted. + +### Command-arguments + +For subcommands, you can specify the argument syntax in the call to `.command()` (as shown above). This +is the only method usable for subcommands implemented using a stand-alone executable, but for other subcommands +you can instead use the following method. + +To configure a command, you can use `.argument()` to specify each expected command-argument. +You supply the argument name and an optional description. The argument may be `` or `[optional]`. +You can specify a default value for an optional command-argument. + +Example file: [argument.js](./examples/argument.js) + +```js +program + .version('0.1.0') + .argument('', 'user to login') + .argument('[password]', 'password for user, if required', 'no password given') + .action((username, password) => { + console.log('username:', username); + console.log('password:', password); + }); +``` + + The last argument of a command can be variadic, and only the last argument. To make an argument variadic you + append `...` to the argument name. A variadic argument is passed to the action handler as an array. For example: + +```js +program + .version('0.1.0') + .command('rmdir') + .argument('') + .action(function (dirs) { + dirs.forEach((dir) => { + console.log('rmdir %s', dir); + }); + }); +``` + +There is a convenience method to add multiple arguments at once, but without descriptions: + +```js +program + .arguments(' '); +``` + +#### More configuration + +There are some additional features available by constructing an `Argument` explicitly for less common cases. + +Example file: [arguments-extra.js](./examples/arguments-extra.js) + +```js +program + .addArgument(new commander.Argument('', 'drink cup size').choices(['small', 'medium', 'large'])) + .addArgument(new commander.Argument('[timeout]', 'timeout in seconds').default(60, 'one minute')) +``` + +#### Custom argument processing + +You may specify a function to do custom processing of command-arguments (like for option-arguments). +The callback function receives two parameters, the user specified command-argument and the previous value for the argument. +It returns the new value for the argument. + +The processed argument values are passed to the action handler, and saved as `.processedArgs`. + +You can optionally specify the default/starting value for the argument after the function parameter. + +Example file: [arguments-custom-processing.js](./examples/arguments-custom-processing.js) + +```js +program + .command('add') + .argument('', 'integer argument', myParseInt) + .argument('[second]', 'integer argument', myParseInt, 1000) + .action((first, second) => { + console.log(`${first} + ${second} = ${first + second}`); + }) +; +``` + +### Action handler + +The action handler gets passed a parameter for each command-argument you declared, and two additional parameters +which are the parsed options and the command object itself. + +Example file: [thank.js](./examples/thank.js) + +```js +program + .argument('') + .option('-t, --title ', 'title to use before name') + .option('-d, --debug', 'display some debugging') + .action((name, options, command) => { + if (options.debug) { + console.error('Called %s with options %o', command.name(), options); + } + const title = options.title ? `${options.title} ` : ''; + console.log(`Thank-you ${title}${name}`); + }); +``` + +If you prefer, you can work with the command directly and skip declaring the parameters for the action handler. The `this` keyword is set to the running command and can be used from a function expression (but not from an arrow function). + +Example file: [action-this.js](./examples/action-this.js) + +```js +program + .command('serve') + .argument('