Skip to content

Commit

Permalink
Merge branch 'v3' into gussy-up-status
Browse files Browse the repository at this point in the history
  • Loading branch information
henryfauna authored Dec 16, 2024
2 parents 21dbf9e + aa22fac commit 60c7ed1
Show file tree
Hide file tree
Showing 33 changed files with 1,245 additions and 455 deletions.
53 changes: 47 additions & 6 deletions src/cli.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import queryCommand from "./commands/query.mjs";
import schemaCommand from "./commands/schema/schema.mjs";
import shellCommand from "./commands/shell.mjs";
import { buildCredentials } from "./lib/auth/credentials.mjs";
import { getDbCompletions, getProfileCompletions } from "./lib/completions.mjs";
import { configParser } from "./lib/config/config.mjs";
import { handleParseYargsError } from "./lib/errors.mjs";
import {
Expand Down Expand Up @@ -112,6 +113,50 @@ function buildYargs(argvInput) {
.command(localCommand)
.demandCommand()
.strictCommands(true)
.completion(
"completion",
"Output bash/zsh script to enable shell completions. See command output for installation instructions.",
)
.completion(
"completion",
async function (currentWord, argv, defaultCompletions, done) {
// this is pretty hard to debug - if you need to, run
// `fauna --get-yargs-completions <command> <flag> <string to match>`
// for example: `fauna --get-yargs-completions --profile he`
// note that you need to have empty quotes to get all matches:
// `fauna --get-yargs-completions --profile ""`

// then, call the done callback with an array of strings for debugging, like:
// done(
// [
// `currentWord: ${currentWord}, currentWordFlag: ${currentWordFlag}, argv: ${JSON.stringify(argv)}`,
// ],
// );
const previousWord = process.argv.slice(-2, -1)[0].replace(/-/g, "");
const currentWordFlag = Object.keys(argv)
.filter((key) => previousWord === key)
.pop();

// TODO: this doesn't handle aliasing, and it needs to

Check warning on line 140 in src/cli.mjs

View workflow job for this annotation

GitHub Actions / lint

Unexpected 'todo' comment: 'TODO: this doesn't handle aliasing, and...'
if (
currentWord === "--profile" ||
currentWordFlag === "profile" ||
currentWord === "-p" ||
currentWordFlag === "p"
) {
done(getProfileCompletions(currentWord, argv));
} else if (
currentWord === "--database" ||
currentWordFlag === "database" ||
currentWord === "-d" ||
currentWordFlag === "d"
) {
done(await getDbCompletions(currentWord, argv));
} else {
defaultCompletions();
}
},
)
.options({
color: {
description:
Expand Down Expand Up @@ -153,7 +198,7 @@ function buildYargs(argvInput) {
"Components to emit diagnostic logs for. Takes precedence over the `--verbosity` flag. Pass components as a space-separated list, such as `--verboseComponent fetch error`, or as separate flags, such as `--verboseComponent fetch --verboseComponent error`.",
type: "array",
default: [],
choices: ["fetch", "error", "config", "argv", "creds"],
choices: ["fetch", "error", "config", "argv", "creds", "completion"],
group: "Debug:",
},
verbosity: {
Expand All @@ -169,9 +214,5 @@ function buildYargs(argvInput) {
.alias("help", "h")
.fail(false)
.exitProcess(false)
.version()
.completion(
"completion",
"Output bash/zsh script to enable shell completions. See command output for installation instructions.",
);
.version();
}
45 changes: 24 additions & 21 deletions src/commands/database/create.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//@ts-check

import { FaunaError } from "fauna";
import { ServiceError } from "fauna";

import { container } from "../../cli.mjs";
import { validateDatabaseOrSecret } from "../../lib/command-helpers.mjs";
import { throwForError } from "../../lib/fauna.mjs";
import { CommandError } from "../../lib/errors.mjs";
import { faunaToCommandError } from "../../lib/fauna.mjs";
import { getSecret, retryInvalidCredsOnce } from "../../lib/fauna-client.mjs";
import { colorize, Format } from "../../lib/formatting/colorize.mjs";

Expand Down Expand Up @@ -44,26 +44,29 @@ async function createDatabase(argv) {
logger.stdout(argv.name);
}
} catch (e) {
if (e instanceof FaunaError) {
throwForError(e, {
onConstraintFailure: (err) => {
const cf = err.constraint_failures;
if (cf && cf.length > 0) {
const nameIsInvalidIdentifier = cf.some(
(failure) =>
failure?.paths?.length === 1 &&
failure?.paths?.[0]?.[0] === "name" &&
failure?.message === "Invalid identifier.",
faunaToCommandError(e, (err) => {
if (err instanceof ServiceError && err.code === "constraint_failure") {
const cf = err.constraint_failures;
if (cf && cf.length > 0) {
const nameIsInvalidIdentifier = cf.some(
(failure) =>
failure?.paths?.length === 1 &&
failure?.paths?.[0]?.[0] === "name" &&
failure?.message === "Invalid identifier.",
);
if (nameIsInvalidIdentifier) {
throw new CommandError(
`The database name '${argv.name}' is invalid. Database names must begin with letters and include only letters, numbers, and underscores.`,
{ cause: err },
);
if (nameIsInvalidIdentifier) {
return `Constraint failure: The database name '${argv.name}' is invalid. Database names must begin with letters and include only letters, numbers, and underscores.`;
}
}
return `Constraint failure: The database '${argv.name}' already exists or one of the provided options is invalid.`;
},
});
}
throw e;
}
throw new CommandError(
`The database '${argv.name}' already exists or one of the provided options is invalid.`,
{ cause: err },
);
}
});
}
}

Expand Down
19 changes: 10 additions & 9 deletions src/commands/database/delete.mjs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
//@ts-check

import { FaunaError } from "fauna";
import { ServiceError } from "fauna";

import { container } from "../../cli.mjs";
import { validateDatabaseOrSecret } from "../../lib/command-helpers.mjs";
import { throwForError } from "../../lib/fauna.mjs";
import { CommandError } from "../../lib/errors.mjs";
import { faunaToCommandError } from "../../lib/fauna.mjs";
import { getSecret, retryInvalidCredsOnce } from "../../lib/fauna-client.mjs";

async function runDeleteQuery(secret, argv) {
Expand All @@ -29,13 +30,13 @@ async function deleteDatabase(argv) {
// We use stderr for messaging and there's no stdout output for a deleted database
logger.stderr(`Database '${argv.name}' was successfully deleted.`);
} catch (e) {
if (e instanceof FaunaError) {
throwForError(e, {
onDocumentNotFound: () =>
`Not found: Database '${argv.name}' not found. Please check the database name and try again.`,
});
}
throw e;
faunaToCommandError(e, (err) => {
if (err instanceof ServiceError && err.code === "document_not_found") {
throw new CommandError(
`Database '${argv.name}' not found. Please check the database name and try again.`,
);
}
});
}
}

Expand Down
51 changes: 24 additions & 27 deletions src/commands/database/list.mjs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
//@ts-check

import { FaunaError } from "fauna";

import { container } from "../../cli.mjs";
import { throwForError } from "../../lib/fauna.mjs";
import { faunaToCommandError } from "../../lib/fauna.mjs";
import { FaunaAccountClient } from "../../lib/fauna-account-client.mjs";
import { colorize, Format } from "../../lib/formatting/colorize.mjs";

Expand All @@ -28,51 +26,50 @@ function pickOutputFields(databases, argv) {
}

async function listDatabasesWithAccountAPI(argv) {
const { pageSize, database, color } = argv;
const { pageSize, database } = argv;
const accountClient = new FaunaAccountClient();
const response = await accountClient.listDatabases({
pageSize,
path: database,
});
const output = pickOutputFields(response.results, argv);

container.resolve("logger").stdout(
colorize(output, {
format: Format.JSON,
color: color,
}),
);
return pickOutputFields(response.results, argv);
}

async function listDatabasesWithSecret(argv) {
const { url, secret, pageSize, color } = argv;
const { runQueryFromString, formatQueryResponse } =
container.resolve("faunaClientV10");
const { url, secret, pageSize } = argv;
const { runQueryFromString } = container.resolve("faunaClientV10");

try {
const result = await runQueryFromString({
return await runQueryFromString({
url,
secret,
// This gives us back an array of database names. If we want to
// provide the after token at some point this query will need to be updated.
expression: `Database.all().paginate(${pageSize}).data { ${getOutputFields(argv)} }`,
});
container
.resolve("logger")
.stdout(formatQueryResponse(result, { format: Format.JSON, color }));
} catch (e) {
if (e instanceof FaunaError) {
throwForError(e);
}
throw e;
return faunaToCommandError(e);
}
}

export async function listDatabases(argv) {
let databases;
if (argv.secret) {
databases = await listDatabasesWithSecret(argv);
} else {
databases = await listDatabasesWithAccountAPI(argv);
}
return databases;
}

async function listDatabases(argv) {
async function doListDatabases(argv) {
const logger = container.resolve("logger");
const { formatQueryResponse } = container.resolve("faunaClientV10");
const res = await listDatabases(argv);
if (argv.secret) {
return listDatabasesWithSecret(argv);
logger.stdout(formatQueryResponse(res, argv));
} else {
return listDatabasesWithAccountAPI(argv);
logger.stdout(colorize(res, { format: Format.JSON, color: argv.color }));
}
}

Expand Down Expand Up @@ -110,5 +107,5 @@ export default {
command: "list",
description: "List databases.",
builder: buildListCommand,
handler: listDatabases,
handler: doListDatabases,
};
Loading

0 comments on commit 60c7ed1

Please sign in to comment.