diff --git a/.eslintrc b/.eslintrc index 41b8ef58..88010630 100644 --- a/.eslintrc +++ b/.eslintrc @@ -32,7 +32,8 @@ { "files": ["*.test.js", "*.spec.js", "*.test.ts"], "rules": { - "no-unused-expressions": "off" + "no-unused-expressions": "off", + "node/no-missing-require": "off" } }, { @@ -40,7 +41,8 @@ "parser": "@typescript-eslint/parser", "rules": { "lines-between-class-members": "off", - "no-use-before-define": "off" + "no-use-before-define": "off", + "no-undef-init": "off" } } ], diff --git a/package.json b/package.json index 93758b31..1e270a97 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,8 @@ "object-sizeof": "^1.6.1", "prettier": "^2.3.0", "rate-limiter-flexible": "^2.3.6", - "stream-json": "^1.7.3" + "stream-json": "^1.7.3", + "supports-color": "^8" }, "devDependencies": { "@babel/eslint-parser": "^7.17.0", @@ -39,6 +40,7 @@ "@types/mocha": "^10.0.1", "@types/node": "^20.6.0", "@types/sinon": "^17.0.3", + "@types/supports-color": "^8.1.3", "@typescript-eslint/parser": "6.7.3", "c8": "^8.0.1", "chai": "^4.2.0", diff --git a/src/commands/schema/abandon.ts b/src/commands/schema/abandon.ts index f585ced9..d9727cd5 100644 --- a/src/commands/schema/abandon.ts +++ b/src/commands/schema/abandon.ts @@ -1,6 +1,7 @@ import { confirm } from "@inquirer/prompts"; import SchemaCommand from "../../lib/schema-command"; import { Flags } from "@oclif/core"; +import { colorParam, hasColor } from "../../lib/color"; export default class CommitSchemaCommand extends SchemaCommand { static flags = { @@ -41,9 +42,12 @@ export default class CommitSchemaCommand extends SchemaCommand { this.log("Schema has been abandonded"); } else { // Show status to confirm. - const { url, secret } = await this.fetchsetup(); + const params = new URLSearchParams({ + ...(hasColor() ? { color: colorParam() } : {}), + diff: "true", + }); const res = await fetch( - new URL("/schema/1/staged/status?diff=true", url), + new URL(`/schema/1/staged/status?${params}`, url), { method: "GET", headers: { AUTHORIZATION: `Bearer ${secret}` }, diff --git a/src/commands/schema/commit.ts b/src/commands/schema/commit.ts index 4d344fcc..a7fbbaac 100644 --- a/src/commands/schema/commit.ts +++ b/src/commands/schema/commit.ts @@ -1,6 +1,7 @@ import { confirm } from "@inquirer/prompts"; import SchemaCommand from "../../lib/schema-command"; import { Flags } from "@oclif/core"; +import { colorParam, hasColor } from "../../lib/color"; export default class CommitSchemaCommand extends SchemaCommand { static flags = { @@ -41,9 +42,12 @@ export default class CommitSchemaCommand extends SchemaCommand { this.log("Schema has been committed"); } else { // Show status to confirm. - const { url, secret } = await this.fetchsetup(); + const params = new URLSearchParams({ + ...(hasColor() ? { color: colorParam() } : {}), + diff: "true", + }); const res = await fetch( - new URL("/schema/1/staged/status?diff=true", url), + new URL(`/schema/1/staged/status?${params}`, url), { method: "GET", headers: { AUTHORIZATION: `Bearer ${secret}` }, diff --git a/src/commands/schema/diff.ts b/src/commands/schema/diff.ts index a6eeabbc..0d5e3881 100644 --- a/src/commands/schema/diff.ts +++ b/src/commands/schema/diff.ts @@ -1,4 +1,5 @@ import SchemaCommand from "../../lib/schema-command"; +import { colorParam, hasColor } from "../../lib/color"; export default class DiffSchemaCommand extends SchemaCommand { static flags = { @@ -16,7 +17,11 @@ export default class DiffSchemaCommand extends SchemaCommand { const files = this.read(fps); try { const { url, secret } = await this.fetchsetup(); - const res = await fetch(new URL("/schema/1/validate?force=true", url), { + const params = new URLSearchParams({ + ...(hasColor() ? { color: colorParam() } : {}), + force: "true", + }); + const res = await fetch(new URL(`/schema/1/validate?${params}`, url), { method: "POST", headers: { AUTHORIZATION: `Bearer ${secret}` }, body: this.body(files), diff --git a/src/commands/schema/push.ts b/src/commands/schema/push.ts index 1eaf3062..83ab184a 100644 --- a/src/commands/schema/push.ts +++ b/src/commands/schema/push.ts @@ -1,6 +1,7 @@ import { confirm } from "@inquirer/prompts"; import SchemaCommand from "../../lib/schema-command"; import { Flags } from "@oclif/core"; +import { colorParam, hasColor } from "../../lib/color"; export default class PushSchemaCommand extends SchemaCommand { static flags = { @@ -55,6 +56,7 @@ export default class PushSchemaCommand extends SchemaCommand { // Confirm diff, then push it. `force` is set on `validate` so we don't // need to pass the last known schema version through. const params = new URLSearchParams({ + ...(hasColor() ? { color: colorParam() } : {}), force: "true", }); const path = new URL(`/schema/1/validate?${params}`, url); diff --git a/src/commands/schema/status.ts b/src/commands/schema/status.ts index 8803c243..6315db6d 100644 --- a/src/commands/schema/status.ts +++ b/src/commands/schema/status.ts @@ -1,4 +1,5 @@ import SchemaCommand from "../../lib/schema-command"; +import { colorParam, hasColor } from "../../lib/color"; export default class StatusSchemaCommand extends SchemaCommand { static flags = { @@ -11,8 +12,13 @@ export default class StatusSchemaCommand extends SchemaCommand { async run() { try { const { url, secret } = await this.fetchsetup(); + + const params = new URLSearchParams({ + ...(hasColor() ? { color: colorParam() } : {}), + diff: "true", + }); const res = await fetch( - new URL("/schema/1/staged/status?diff=true", url), + new URL(`/schema/1/staged/status?${params}`, url), { method: "GET", headers: { AUTHORIZATION: `Bearer ${secret}` }, diff --git a/src/lib/color.ts b/src/lib/color.ts new file mode 100644 index 00000000..941570ad --- /dev/null +++ b/src/lib/color.ts @@ -0,0 +1,28 @@ +import { stdout } from "supports-color"; + +var colorEnabled: boolean | undefined = undefined; +export const disableColor = () => { + colorEnabled = false; +}; + +// Enable or disable color. Passing undefined will enable colors if they are +// supported in the current environment. +export const setHasColor = (color: boolean | undefined) => { + if (colorEnabled === undefined) { + if (color === undefined) { + // NB: `supports-color` is going to parse command line arguments on its own. + colorEnabled = stdout !== false && stdout.hasBasic; + } else { + colorEnabled = color; + } + } +}; + +// The value for the `color` parameter to the `/schema/1` endpoints. +export const colorParam = (): string => { + return hasColor() ? "ansi" : ""; +}; + +export const hasColor = (): boolean => { + return colorEnabled ?? false; +}; diff --git a/src/lib/fauna-command.js b/src/lib/fauna-command.js index fc9e4571..4c87fb43 100644 --- a/src/lib/fauna-command.js +++ b/src/lib/fauna-command.js @@ -2,6 +2,7 @@ import { Command, Flags } from "@oclif/core"; import { green } from "chalk"; import { Client, errors, query as q } from "faunadb"; import { ShellConfig } from "./config"; +import { setHasColor } from "./color"; import FaunaClient from "./fauna-client"; /** @@ -37,6 +38,8 @@ class FaunaCommand extends Command { this.flags = f; this.args = a; this.shellConfig = ShellConfig.read(this.flags, this); + + setHasColor(this.flags.color); } success(msg) { @@ -299,6 +302,10 @@ FaunaCommand.flags = { environment: Flags.string({ description: "Environment to use, from a Fauna project", }), + color: Flags.boolean({ + description: "Force color output", + allowNo: true, + }), }; export default FaunaCommand; diff --git a/test/commands/schema.test.js b/test/commands/schema.test.js index 2bd2290d..73c7eb26 100644 --- a/test/commands/schema.test.js +++ b/test/commands/schema.test.js @@ -7,6 +7,7 @@ const fs = require("fs"); const path = require("path"); const { query: q } = require("faunadb"); const { withOpts, getEndpoint, matchFqlReq } = require("../helpers/utils.js"); +const { disableColor } = require("../../src/lib/color"); const main = { version: 0, @@ -40,6 +41,10 @@ const pullfiles = { const updated = { version: 1 }; describe("fauna schema diff test", () => { + before(() => { + disableColor(); + }); + it("runs schema diff", async () => { nock(getEndpoint(), { allowUnmocked: false }) .persist() diff --git a/yarn.lock b/yarn.lock index b5aa389d..36614e63 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1576,6 +1576,11 @@ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" integrity sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw== +"@types/supports-color@^8.1.3": + version "8.1.3" + resolved "https://registry.yarnpkg.com/@types/supports-color/-/supports-color-8.1.3.tgz#b769cdce1d1bb1a3fa794e35b62c62acdf93c139" + integrity sha512-Hy6UMpxhE3j1tLpl27exp1XqHD7n8chAiNPzWfz16LPZoMMoSc4dzLl6w9qijkEb/r5O1ozdu1CWGA2L83ZeZg== + "@types/vinyl@^2.0.4": version "2.0.12" resolved "https://registry.yarnpkg.com/@types/vinyl/-/vinyl-2.0.12.tgz#17642ca9a8ae10f3db018e9f885da4188db4c6e6" @@ -6468,7 +6473,16 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.2, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -6500,7 +6514,7 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -6514,6 +6528,13 @@ strip-ansi@^5.0.0: dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -7046,7 +7067,7 @@ workerpool@^6.5.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -7064,6 +7085,15 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"