diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml index c6b956d2..ba758051 100644 --- a/.github/workflows/build-binaries.yml +++ b/.github/workflows/build-binaries.yml @@ -6,6 +6,7 @@ on: jobs: build-binaries: name: Build binaries + timeout-minutes: 5 strategy: matrix: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8be99273..8e1b3d74 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,6 +13,7 @@ on: jobs: build: runs-on: ubuntu-latest + timeout-minutes: 2 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cbd2d28b..1c8095d9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -13,6 +13,7 @@ on: jobs: build: runs-on: ubuntu-latest + timeout-minutes: 3 strategy: matrix: @@ -27,7 +28,7 @@ jobs: node-version: ${{ matrix.node-version }} cache: "npm" - run: npm ci - - run: npm test + - run: npm run test:ci env: TERM: xterm-256color # Set to the correct color level; 2 is 256 colors diff --git a/package-lock.json b/package-lock.json index 71a6c8b3..7d03dff7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -39,6 +39,7 @@ "husky": "^9.1.6", "mocha": "^10.7.3", "mocha-junit-reporter": "^2.2.1", + "mocha-multi-reporters": "^1.5.1", "postject": "^1.0.0-alpha.6", "prettier": "^3.3.3", "sinon": "^19.0.2", @@ -186,7 +187,7 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.3", + "version": "0.2.0", "license": "Apache-2.0", "dependencies": { "levn": "^0.4.1" @@ -709,7 +710,6 @@ }, "node_modules/ansi-colors": { "version": "4.1.3", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -760,7 +760,6 @@ }, "node_modules/anymatch": { "version": "3.1.3", - "dev": true, "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", @@ -824,7 +823,6 @@ }, "node_modules/binary-extensions": { "version": "2.3.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -897,7 +895,6 @@ }, "node_modules/browser-stdout": { "version": "1.3.1", - "dev": true, "license": "ISC" }, "node_modules/btoa-lite": { @@ -989,7 +986,6 @@ }, "node_modules/chokidar": { "version": "3.6.0", - "dev": true, "license": "MIT", "dependencies": { "anymatch": "~3.1.2", @@ -1108,7 +1104,7 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.6", + "version": "7.0.3", "license": "MIT", "dependencies": { "path-key": "^3.1.0", @@ -1144,7 +1140,6 @@ }, "node_modules/decamelize": { "version": "4.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -1208,7 +1203,6 @@ }, "node_modules/diff": { "version": "5.2.0", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" @@ -1597,7 +1591,6 @@ }, "node_modules/flat": { "version": "5.0.2", - "dev": true, "license": "BSD-3-Clause", "bin": { "flat": "cli.js" @@ -1627,12 +1620,10 @@ }, "node_modules/fs.realpath": { "version": "1.0.0", - "dev": true, "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.3", - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1661,7 +1652,6 @@ }, "node_modules/glob": { "version": "8.1.0", - "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -1689,7 +1679,6 @@ }, "node_modules/glob/node_modules/brace-expansion": { "version": "2.0.1", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -1697,7 +1686,6 @@ }, "node_modules/glob/node_modules/minimatch": { "version": "5.1.6", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -1750,7 +1738,6 @@ }, "node_modules/he": { "version": "1.2.0", - "dev": true, "license": "MIT", "bin": { "he": "bin/he" @@ -1810,7 +1797,6 @@ }, "node_modules/inflight": { "version": "1.0.6", - "dev": true, "license": "ISC", "dependencies": { "once": "^1.3.0", @@ -1819,7 +1805,6 @@ }, "node_modules/inherits": { "version": "2.0.4", - "dev": true, "license": "ISC" }, "node_modules/ini": { @@ -1844,7 +1829,6 @@ }, "node_modules/is-binary-path": { "version": "2.1.0", - "dev": true, "license": "MIT", "dependencies": { "binary-extensions": "^2.0.0" @@ -1967,7 +1951,6 @@ }, "node_modules/is-plain-obj": { "version": "2.1.0", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -1975,7 +1958,6 @@ }, "node_modules/is-unicode-supported": { "version": "0.1.0", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -2116,6 +2098,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, "node_modules/lodash.get": { "version": "4.4.2", "dev": true, @@ -2127,7 +2115,6 @@ }, "node_modules/log-symbols": { "version": "4.1.0", - "dev": true, "license": "MIT", "dependencies": { "chalk": "^4.1.0", @@ -2142,7 +2129,6 @@ }, "node_modules/log-symbols/node_modules/chalk": { "version": "4.1.2", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -2241,7 +2227,6 @@ }, "node_modules/mocha": { "version": "10.7.3", - "dev": true, "license": "MIT", "dependencies": { "ansi-colors": "^4.1.3", @@ -2288,9 +2273,24 @@ "mocha": ">=2.2.5" } }, + "node_modules/mocha-multi-reporters": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/mocha-multi-reporters/-/mocha-multi-reporters-1.5.1.tgz", + "integrity": "sha512-Yb4QJOaGLIcmB0VY7Wif5AjvLMUFAdV57D2TWEva1Y0kU/3LjKpeRVmlMIfuO1SVbauve459kgtIizADqxMWPg==", + "license": "MIT", + "dependencies": { + "debug": "^4.1.1", + "lodash": "^4.17.15" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "mocha": ">=3.1.2" + } + }, "node_modules/mocha/node_modules/brace-expansion": { "version": "2.0.1", - "dev": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0" @@ -2298,7 +2298,6 @@ }, "node_modules/mocha/node_modules/cliui": { "version": "7.0.4", - "dev": true, "license": "ISC", "dependencies": { "string-width": "^4.2.0", @@ -2308,7 +2307,6 @@ }, "node_modules/mocha/node_modules/minimatch": { "version": "5.1.6", - "dev": true, "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" @@ -2319,7 +2317,6 @@ }, "node_modules/mocha/node_modules/supports-color": { "version": "8.1.1", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -2333,7 +2330,6 @@ }, "node_modules/mocha/node_modules/yargs": { "version": "16.2.0", - "dev": true, "license": "MIT", "dependencies": { "cliui": "^7.0.2", @@ -2407,7 +2403,6 @@ }, "node_modules/normalize-path": { "version": "3.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -2422,7 +2417,6 @@ }, "node_modules/once": { "version": "1.4.0", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -2671,7 +2665,6 @@ }, "node_modules/randombytes": { "version": "2.1.0", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" @@ -2704,7 +2697,6 @@ }, "node_modules/readdirp": { "version": "3.6.0", - "dev": true, "license": "MIT", "dependencies": { "picomatch": "^2.2.1" @@ -2805,7 +2797,6 @@ }, "node_modules/safe-buffer": { "version": "5.2.1", - "dev": true, "funding": [ { "type": "github", @@ -2838,7 +2829,6 @@ }, "node_modules/serialize-javascript": { "version": "6.0.2", - "dev": true, "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" @@ -3245,7 +3235,6 @@ }, "node_modules/workerpool": { "version": "6.5.1", - "dev": true, "license": "Apache-2.0" }, "node_modules/wrap-ansi": { @@ -3265,7 +3254,6 @@ }, "node_modules/wrappy": { "version": "1.0.2", - "dev": true, "license": "ISC" }, "node_modules/xdg-basedir": { @@ -3308,7 +3296,6 @@ }, "node_modules/yargs-parser": { "version": "20.2.9", - "dev": true, "license": "ISC", "engines": { "node": ">=10" @@ -3316,7 +3303,6 @@ }, "node_modules/yargs-unparser": { "version": "2.0.0", - "dev": true, "license": "MIT", "dependencies": { "camelcase": "^6.0.0", diff --git a/package.json b/package.json index a8efb445..df409113 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "author": "Fauna", "type": "module", "bin": { - "fauna": "./src/user-entrypoint.mjs" + "fauna": "./dist/cli.cjs" }, "bugs": "https://github.com/fauna/fauna-shell/issues", "dependencies": { @@ -36,6 +36,7 @@ "husky": "^9.1.6", "mocha": "^10.7.3", "mocha-junit-reporter": "^2.2.1", + "mocha-multi-reporters": "^1.5.1", "postject": "^1.0.0-alpha.6", "prettier": "^3.3.3", "sinon": "^19.0.2", @@ -48,7 +49,7 @@ "node": ">=20.0.0" }, "files": [ - "/src" + "/dist" ], "homepage": "https://github.com/fauna/fauna-shell", "keywords": [ @@ -64,10 +65,12 @@ "repository": "fauna/fauna-shell", "scripts": { "lint": "eslint . --fix", - "test": "mocha --recursive ./test --require ./test/mocha-root-hooks.mjs --reporter spec --reporter mocha-junit-reporter", - "test:local": "mocha --recursive ./test --require ./test/mocha-root-hooks.mjs", + "test": "npm run test:local", + "test:local": "mocha --recursive ./test --require ./test/mocha-root-hooks.mjs --reporter mocha-multi-reporters --reporter-options configFile=./test/config/reporter.json", + "pretest:ci": "npm run build:app", + "test:ci": "mocha --recursive ./test --require ./test/mocha-root-hooks.mjs --reporter mocha-multi-reporters --reporter-options configFile=./test/config/reporter.json", "build": "npm run build:app && npm run build:sea", - "build:app": "esbuild --bundle ./src/user-entrypoint.mjs --platform=node --outfile=./dist/cli.cjs --format=cjs --inject:./sea/import-meta-url.js --define:import.meta.url=importMetaUrl", + "build:app": "esbuild --bundle ./src/user-entrypoint.mjs --platform=node --outfile=./dist/cli.cjs --format=cjs --inject:./sea/import-meta-url.js --define:import.meta.url=importMetaUrl --define:process.env.NODE_ENV=\\\"production\\\"", "build:sea": "node ./sea/build.cjs", "format": "prettier -w ." }, diff --git a/src/cli.mjs b/src/cli.mjs index 424735e7..b15faa7c 100644 --- a/src/cli.mjs +++ b/src/cli.mjs @@ -27,6 +27,9 @@ export async function run(argvInput, _container) { container = _container; const logger = container.resolve("logger"); const parseYargs = container.resolve("parseYargs"); + if (process.env.NODE_ENV === "production") { + process.removeAllListeners("warning"); + } try { builtYargs = buildYargs(argvInput); @@ -61,6 +64,32 @@ function buildYargs(argvInput) { // https://github.com/yargs/yargs/blob/main/docs/typescript.md?plain=1#L124 const yargsInstance = yargs(argvInput); + // these debug commands are used by the tests in environments where they can't mock out the command handler + if ( + process.env.NODE_ENV !== "production" || + process.env.DEBUG_COMMANDS === "true" + ) { + yargsInstance + .command("throw", false, { + handler: () => { + throw new Error("this is a test error"); + }, + builder: {}, + }) + .command("reject", false, { + handler: async () => { + throw new Error("this is a rejected promise"); + }, + builder: {}, + }) + .command("warn", false, { + handler: async () => { + process.emitWarning("this is a warning emited on the node process"); + }, + builder: {}, + }); + } + return yargsInstance .scriptName("fauna") .middleware([checkForUpdates, logArgv], true) @@ -71,18 +100,6 @@ function buildYargs(argvInput) { .command(keyCommand) .command(schemaCommand) .command(databaseCommand) - .command("throw", false, { - handler: () => { - throw new Error("this is a test error"); - }, - builder: {}, - }) - .command("reject", false, { - handler: async () => { - throw new Error("this is a rejected promise"); - }, - builder: {}, - }) .demandCommand() .strict(true) .options({ diff --git a/test/config/reporter.json b/test/config/reporter.json new file mode 100644 index 00000000..d835720a --- /dev/null +++ b/test/config/reporter.json @@ -0,0 +1,4 @@ +{ + "reporterEnabled": "spec, mocha-junit-reporter", + "mochaJunitReporterOptions": {} +} diff --git a/test/general-cli.mjs b/test/general-cli.mjs index 8d7c3013..bc70ef08 100644 --- a/test/general-cli.mjs +++ b/test/general-cli.mjs @@ -1,5 +1,6 @@ //@ts-check +import { spawnSync } from "node:child_process"; import * as fs from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; @@ -13,6 +14,8 @@ import { builtYargs, run } from "../src/cli.mjs"; import { setupTestContainer as setupContainer } from "../src/config/setup-test-container.mjs"; import { f } from "./helpers.mjs"; +const __dirname = import.meta.dirname; + describe("cli operations", function () { let container; @@ -124,6 +127,48 @@ describe("cli operations", function () { expect(notify).to.have.been.called; }); + it("does not expose debug commands in production", async function () { + const cliPath = path.resolve(__dirname, "../dist/cli.cjs"); + const { stderr } = spawnSync(cliPath, ["throw"], { + encoding: "utf8", + timeout: 5000, + }); + + expect(stderr).to.include("Unknown argument: throw"); + }); + + it("enables nodeJS warnings from the dev entrypoint", async function () { + const cliPath = path.resolve(__dirname, "../src/user-entrypoint.mjs"); + let cli = spawnSync(cliPath, ["warn"], { + encoding: "utf8", + timeout: 5000, + }); + if (cli.error) throw cli.error; + let stderr = cli.stderr; + + // the dev script should emit warnings + expect(stderr).to.include( + "Warning: this is a warning emited on the node process", + ); + }); + + it("suppresses nodeJS warnings from the prod entrypoint", async function () { + const cliPath = path.resolve(__dirname, "../dist/cli.cjs"); + let cli = spawnSync(cliPath, ["warn"], { + encoding: "utf8", + timeout: 5000, + env: { + ...process.env, + DEBUG_COMMANDS: "true", + }, + }); + if (cli.error) throw cli.error; + + let stderr = cli.stderr; + // the prod one should not + expect(stderr).to.equal(""); + }); + it.skip("should detect color support if the user does not specify", async function () { // i can't find a way to mock this that doesn't involve setting a flag // and setting a flag defeats the purpose of testing if it's _detected_ automatically diff --git a/test/shell.mjs b/test/shell.mjs index 10519da0..9e9c15ab 100644 --- a/test/shell.mjs +++ b/test/shell.mjs @@ -148,6 +148,16 @@ describe("shell", function () { sinon.match({ version: "10", typecheck: true }), ); }); + + describe("error handling", function () { + it.skip("can handle a client-side query syntax error", async function () {}); + it.skip("can handle a server-side query syntax error", async function () {}); + it.skip("can handle a UDF abort", async function () {}); + it.skip("can handle a query limit exceeded error", async function () {}); + it.skip("can handle a query rate limit error", async function () {}); + it.skip("can handle a server-side query timeout", async function () {}); + it.skip("can handle a client-side query timeout", async function () {}); + }); }); describe("v4", function () { @@ -184,5 +194,15 @@ describe("shell", function () { return runPromise; }); + + describe("error handling", function () { + it.skip("can handle a client-side query syntax error", async function () {}); + it.skip("can handle a server-side query syntax error", async function () {}); + it.skip("can handle a UDF abort", async function () {}); + it.skip("can handle a query limit exceeded error", async function () {}); + it.skip("can handle a query rate limit error", async function () {}); + it.skip("can handle a server-side query timeout", async function () {}); + it.skip("can handle a client-side query timeout", async function () {}); + }); }); });