feat: add godaddy api command for direct API access#2
feat: add godaddy api command for direct API access#2wcole1-godaddy wants to merge 2 commits intomainfrom
godaddy api command for direct API access#2Conversation
Add a new command that allows direct, authenticated requests to any GoDaddy API endpoint, similar to `gh api` and `vercel api`. Features: - Support for all HTTP methods (GET, POST, PUT, PATCH, DELETE) - Field arguments (-f key=value) and JSON file body (-F path) - Custom headers (-H "Key: Value") - JSON path queries for filtering output (-q .path.to.value) - Response header display (-i, --include) - Debug mode shows request/response details (--debug) - Proactive token expiry checking with helpful messages - Specific handling for 401/403 responses Also includes: - Comprehensive documentation in README - Unit tests for API module and command - Improved test reliability for CI environments Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
| encoding: "utf-8", | ||
| }); | ||
| it.skipIf(!canRunCli)("should display help and exit with code 0", () => { | ||
| const result = execSync(`node ${CLI_PATH} --help`, { |
Check warning
Code scanning / CodeQL
Shell command built from environment values Medium test
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 days ago
To fix the problem, avoid building a single shell command string that embeds CLI_PATH. Instead, invoke node with arguments passed as an array so the path is not parsed by a shell. This removes any risk from spaces or metacharacters in process.cwd(); node receives the path as a literal argument.
The minimal, behavior-preserving change within tests/integration/cli-smoke.test.ts is:
- Keep
execSyncbut stop using a shell-style command string. - Call
execSyncwith an array of arguments, and ensureshellis disabled so the command is executed directly. - For each current use:
execSync(`node ${CLI_PATH} --help`, …)→execSync("node", [CLI_PATH, "--help"], { encoding: "utf-8", shell: false })- Similarly for
application --help,--version,--env invalid-env env get, andnonexistent-command.
- Optionally, for
pnpm run build, you can also avoid the shell; however, the reported issue concernsCLI_PATH, so we will leave that call unchanged to minimize scope, unless the broader project expects shell features there.
No new imports are needed; we are already importing execSync from node:child_process. All changes occur inside tests/integration/cli-smoke.test.ts on the lines where execSync is currently passed a template string involving CLI_PATH.
| encoding: "utf-8", | ||
| }); | ||
| it.skipIf(!canRunCli)("should display version and exit with code 0", () => { | ||
| const result = execSync(`node ${CLI_PATH} --version`, { |
Check warning
Code scanning / CodeQL
Shell command built from environment values Medium test
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 days ago
In general, to fix this issue you should avoid constructing a full shell command string that includes tainted or environment-derived paths and instead pass the executable and its arguments separately to an API that does not go through a shell (for Node’s child_process, that is execFileSync or spawnSync with an argument array). This prevents shell interpretation of spaces and special characters in file paths or arguments.
For this file, the best minimal-change fix is:
- Keep using
execSyncwhere it is only given a fixed string literal command (e.g.,"pnpm run build"), because there is no tainted interpolation there. - For every place where
CLI_PATHis interpolated into a template string passed toexecSync, switch tospawnSync(orexecFileSync) and pass:- the command
"node"as the executable, - an array of arguments containing
CLI_PATHand the rest of the CLI arguments separately, - the encoding/stdio options as before.
- the command
- Update imports to include
spawnSyncfromnode:child_process. - Adapt expectations:
- Where the code previously captured
execSync’s stdout as the return value, now readresult.stdoutfromspawnSync. - Where the code previously wrapped
execSyncinexpect(() => { ... }).toThrow(), now performspawnSyncand assert thatstatusis non-zero (an error exit), which preserves the tested behavior without relying on thrown exceptions.
- Where the code previously captured
Specifically:
- In
tests/integration/cli-smoke.test.ts:- Add
spawnSyncto thenode:child_processimport. - Replace the four
execSynccalls on lines 39, 50, 62, 75, and 87 that interpolateCLI_PATHwithspawnSync("node", [CLI_PATH, ...args], { encoding: "utf-8", ... }), adjusting to capturestdoutor assert onstatusas appropriate.
- Add
| "should exit with error for invalid --env value", | ||
| () => { | ||
| expect(() => { | ||
| execSync(`node ${CLI_PATH} --env invalid-env env get`, { |
Check warning
Code scanning / CodeQL
Shell command built from environment values Medium test
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 4 days ago
In general, the problem is that we’re building a single shell command string (node ${CLI_PATH} ...) that includes a path derived from process.cwd(), and passing it to execSync, which invokes a shell. To fix this, we should avoid the shell and pass the command and its arguments separately so that the path is not reinterpreted by the shell.
The best fix here is to switch all execSync calls that run node ${CLI_PATH} ... to use execFileSync (from node:child_process) with an argument array: execFileSync("node", [CLI_PATH, "--help"]), etc. This preserves existing behavior (we’re still running the same Node script with the same arguments) but removes any shell interpretation of CLI_PATH or other arguments. We will need to import execFileSync alongside execSync. For the pnpm run build invocation, the taint source is not involved and the command is static, so it can remain as-is; only the calls that interpolate CLI_PATH need changing.
Concretely in tests/integration/cli-smoke.test.ts:
- Update the import on line 1 to include
execFileSync. - Replace the
execSynccalls that construct commands using template literals withexecFileSync("node", [...args]):- Help test:
execSync(\node ${CLI_PATH} --help`, ...)→execFileSync("node", [CLI_PATH, "--help"], ...)`. - Subcommand help:
execSync(\node ${CLI_PATH} application --help`, ...)→execFileSync("node", [CLI_PATH, "application", "--help"], ...)`. - Version:
execSync(\node ${CLI_PATH} --version`, ...)→execFileSync("node", [CLI_PATH, "--version"], ...)`. - Invalid env:
execSync(\node ${CLI_PATH} --env invalid-env env get`, ...)→execFileSync("node", [CLI_PATH, "--env", "invalid-env", "env", "get"], ...)`. - Unknown command:
execSync(\node ${CLI_PATH} nonexistent-command`, ...)→execFileSync("node", [CLI_PATH, "nonexistent-command"], ...)`.
- Help test:
No other behavioral changes are needed.
| @@ -1,4 +1,4 @@ | ||
| import { execSync } from "node:child_process"; | ||
| import { execSync, execFileSync } from "node:child_process"; | ||
| import { existsSync } from "node:fs"; | ||
| import { join } from "node:path"; | ||
| import { describe, expect, it } from "vitest"; | ||
| @@ -36,7 +36,7 @@ | ||
| describe("CLI Smoke Tests", () => { | ||
| describe("--help", () => { | ||
| it.skipIf(!canRunCli)("should display help and exit with code 0", () => { | ||
| const result = execSync(`node ${CLI_PATH} --help`, { | ||
| const result = execFileSync("node", [CLI_PATH, "--help"], { | ||
| encoding: "utf-8", | ||
| }); | ||
|
|
||
| @@ -47,7 +47,7 @@ | ||
| }); | ||
|
|
||
| it.skipIf(!canRunCli)("should display subcommand help", () => { | ||
| const result = execSync(`node ${CLI_PATH} application --help`, { | ||
| const result = execFileSync("node", [CLI_PATH, "application", "--help"], { | ||
| encoding: "utf-8", | ||
| }); | ||
|
|
||
| @@ -59,7 +59,7 @@ | ||
|
|
||
| describe("--version", () => { | ||
| it.skipIf(!canRunCli)("should display version and exit with code 0", () => { | ||
| const result = execSync(`node ${CLI_PATH} --version`, { | ||
| const result = execFileSync("node", [CLI_PATH, "--version"], { | ||
| encoding: "utf-8", | ||
| }); | ||
|
|
||
| @@ -72,7 +72,7 @@ | ||
| "should exit with error for invalid --env value", | ||
| () => { | ||
| expect(() => { | ||
| execSync(`node ${CLI_PATH} --env invalid-env env get`, { | ||
| execFileSync("node", [CLI_PATH, "--env", "invalid-env", "env", "get"], { | ||
| encoding: "utf-8", | ||
| stdio: "pipe", | ||
| }); | ||
| @@ -84,7 +84,7 @@ | ||
| describe("unknown command", () => { | ||
| it.skipIf(!canRunCli)("should show error for unknown command", () => { | ||
| expect(() => { | ||
| execSync(`node ${CLI_PATH} nonexistent-command`, { | ||
| execFileSync("node", [CLI_PATH, "nonexistent-command"], { | ||
| encoding: "utf-8", | ||
| stdio: "pipe", | ||
| }); |
Add a new command that allows direct, authenticated requests to any GoDaddy API endpoint, similar to
gh apiandvercel api. Features:Also includes: