Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FE-6024 shell history #431

Closed
wants to merge 13 commits into from
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ experiments
coverage
test-results.xml
.history
/test/test-homedir

# default fauna config file names
fauna.config.yaml,
Expand Down
42 changes: 42 additions & 0 deletions src/commands/shell.mjs
Original file line number Diff line number Diff line change
@@ -1,21 +1,35 @@
//@ts-check

import path from "node:path";
import repl from "node:repl";

import { container } from "../cli.mjs";
import {
// ensureDbScopeClient,
commonConfigurableQueryOptions,
} from "../lib/command-helpers.mjs";
import { dirExists, fileExists } from "../lib/file-util.mjs";
import { performQuery } from "./eval.mjs";

async function doShell(argv) {
const fs = container.resolve("fs");
const logger = container.resolve("logger");
let completionPromise;

if (argv.dbPath) logger.stdout(`Starting shell for database ${argv.dbPath}`);
logger.stdout("Type Ctrl+D or .exit to exit the shell");

// Setup history file
const homedir = container.resolve("homedir");
const historyDir = path.join(homedir.toString(), ".fauna");
if (!dirExists(historyDir)) {
fs.mkdirSync(historyDir, { recursive: true });
}
const historyFile = path.join(historyDir, "history");
if (!fileExists(historyFile)) {
fs.writeFileSync(historyFile, "");
}

/** @type {import('node:repl').ReplOptions} */
const replArgs = {
prompt: `${argv.db_path || ""}> `,
Expand All @@ -27,9 +41,18 @@ async function doShell(argv) {
input: container.resolve("stdinStream"),
eval: await buildCustomEval(argv),
terminal: true,
historySize: 1000
};

const shell = repl.start(replArgs);

// Setup history
shell.setupHistory(historyFile, (err) => {
if (err) {
logger.stderr(`Error setting up history: ${err.message}`);
}
});

// eslint-disable-next-line no-console
shell.on("error", console.error);

Expand All @@ -55,6 +78,25 @@ async function doShell(argv) {
shell.prompt();
},
},
{
cmd: "clearhistory",
help: "Clear command history",
action: () => {
try {
fs.writeFileSync(historyFile, '');
logger.stdout('History cleared');
// Reinitialize history
shell.setupHistory(historyFile, (err) => {
if (err) {
logger.stderr(`Error reinitializing history: ${err.message}`);
}
});
} catch (err) {
logger.stderr(`Error clearing history: ${err.message}`);
}
shell.prompt();
},
}
].forEach(({ cmd, ...cmdOptions }) => shell.defineCommand(cmd, cmdOptions));

return completionPromise;
Expand Down
2 changes: 1 addition & 1 deletion src/config/setup-container.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { makeAccountRequest } from "../lib/account.mjs";
import OAuthClient from "../lib/auth/oauth-client.mjs";
import { getSimpleClient } from "../lib/command-helpers.mjs";
import { makeFaunaRequest } from "../lib/db.mjs";
import { getV10Client,runV10Query } from "../lib/fauna.mjs";
import { getV10Client, runV10Query } from "../lib/fauna.mjs";
import { FaunaAccountClient } from "../lib/fauna-account-client.mjs";
import fetchWrapper from "../lib/fetch-wrapper.mjs";
import { AccountKey, SecretKey } from "../lib/file-util.mjs";
Expand Down
13 changes: 10 additions & 3 deletions src/config/setup-test-container.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import fs from "node:fs";
import { normalize } from "node:path";
import path from "node:path";
import { PassThrough } from "node:stream";

import * as awilix from "awilix";
Expand Down Expand Up @@ -38,10 +38,12 @@ function confirmManualMocks(manualMocks, thingsToManuallyMock) {
}

export function setupTestContainer() {
const __dirname = import.meta.dirname;

const container = setupCommonContainer();

const thingsToManuallyMock = automock(container);
const customfs = stub(fs);
const customfs = stub({ ...fs });
// this is a mock used by the default profile behavior
customfs.readdirSync.withArgs(process.cwd()).returns([]);

Expand All @@ -51,6 +53,11 @@ export function setupTestContainer() {
stdoutStream: awilix.asClass(InMemoryWritableStream).singleton(),
stderrStream: awilix.asClass(InMemoryWritableStream).singleton(),

// test home directory
homedir: awilix
.asFunction(() => path.join(__dirname, "../../test/test-homedir"))
.scoped(),

// wrap it in a spy so we can record calls, but use the
// real implementation
parseYargs: awilix.asValue(spy(parseYargs)),
Expand All @@ -73,7 +80,7 @@ export function setupTestContainer() {
error.code = exitCode;
throw error;
}),
normalize: awilix.asValue(spy(normalize)),
normalize: awilix.asValue(spy(path.normalize)),
fetch: awilix.asValue(stub().resolves(f({}))),
gatherFSL: awilix.asValue(stub().resolves([])),
makeFaunaRequest: awilix.asValue(spy(makeFaunaRequest)),
Expand Down
18 changes: 14 additions & 4 deletions src/lib/file-util.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
//@ts-check

import fs from "node:fs";
import path from "node:path";

import { container } from "../cli.mjs";
Expand All @@ -22,6 +20,7 @@ export function fixPath(path) {
* @returns {boolean}
*/
export function dirExists(path) {
const fs = container.resolve("fs");
const stat = fs.statSync(fixPath(path), {
// returns undefined instead of throwing if the file doesn't exist
throwIfNoEntry: false,
Expand All @@ -39,6 +38,7 @@ export function dirExists(path) {
* @returns {boolean}
*/
export function dirIsWriteable(path) {
const fs = container.resolve("fs");
try {
fs.accessSync(fixPath(path), fs.constants.W_OK);
} catch (e) {
Expand All @@ -54,7 +54,8 @@ export function dirIsWriteable(path) {
* @param {string} path - The path to the file.
* @returns {boolean} - Returns true if the file exists, otherwise false.
*/
function fileExists(path) {
export function fileExists(path) {
const fs = container.resolve("fs");
try {
fs.readFileSync(fixPath(path));
return true;
Expand All @@ -69,6 +70,7 @@ function fileExists(path) {
* @returns {Object.<string, any>} - The parsed JSON content of the file.
*/
function getJSONFileContents(path) {
const fs = container.resolve("fs");
// Open file for reading and writing without truncating
const fileContent = fs.readFileSync(path, { flag: "r+" }).toString();
if (!fileContent) {
Expand Down Expand Up @@ -103,11 +105,13 @@ export class Credentials {
* @param {string} [filename=""] - The name of the credentials file.
*/
constructor(filename = "") {
const fs = container.resolve("fs");

this.logger = container.resolve("logger");
this.filename = filename;

const homedir = container.resolve("homedir");
this.credsDir = path.join(homedir.toString(),".fauna/credentials");
this.credsDir = path.join(homedir.toString(), ".fauna/credentials");

if (!dirExists(this.credsDir)) {
fs.mkdirSync(this.credsDir, { recursive: true });
Expand Down Expand Up @@ -146,6 +150,8 @@ export class Credentials {
* @param {string} params.key - The key to index the creds under
*/
save({ creds, overwrite = false, key }) {
const fs = container.resolve("fs");

try {
const existingContent = overwrite ? {} : this.get();
const newContent = {
Expand All @@ -159,6 +165,8 @@ export class Credentials {
}

delete(key) {
const fs = container.resolve("fs");

try {
const existingContent = this.get();
delete existingContent[key];
Expand Down Expand Up @@ -193,6 +201,8 @@ export class SecretKey extends Credentials {
* @param {string} opts.creds.role - The role to save the secret
*/
this.save = ({ creds, overwrite = false, key }) => {
const fs = container.resolve("fs");

try {
const existingContent = overwrite ? {} : this.get();
const existingAccountSecrets = existingContent[key] || {};
Expand Down
5 changes: 0 additions & 5 deletions test/authNZ.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//@ts-check
import path from "node:path";

import * as awilix from "awilix";
import { expect } from "chai";
Expand Down Expand Up @@ -31,13 +30,9 @@ describe.skip("authNZMiddleware", function () {
};

beforeEach(() => {
const __dirname = import.meta.dirname;
const homedir = path.join(__dirname, "./test-homedir");

container = setupContainer();
container.register({
accountClient: awilix.asFunction(mockAccountClient).scoped(),
homedir: awilix.asFunction(() => homedir).scoped(),
});
fetch = container.resolve("fetch");
});
Expand Down
2 changes: 1 addition & 1 deletion test/config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import path from "node:path";

import * as awilix from "awilix";
import { expect } from "chai";
import chalk from "chalk";
import notAllowed from "not-allowed";
import sinon from "sinon";

import { builtYargs, run } from "../src/cli.mjs";
import { performQuery, performV10Query } from "../src/commands/eval.mjs";
import { setupTestContainer as setupContainer } from "../src/config/setup-test-container.mjs";
import chalk from "chalk";
import { validDefaultConfigNames } from "../src/lib/config/config.mjs";

const __dirname = import.meta.dirname;
Expand Down
5 changes: 0 additions & 5 deletions test/login.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//@ts-check
import path from "node:path";

import * as awilix from "awilix";
import { expect } from "chai";
Expand Down Expand Up @@ -63,15 +62,11 @@ describe("login", function () {
};

beforeEach(() => {
const __dirname = import.meta.dirname;
const homedir = path.join(__dirname, "./test-homedir");

container = setupContainer();
container.register({
oauthClient: awilix.asFunction(mockOAuth).scoped(),
accountClient: awilix.asFunction(mockAccountClient).scoped(),
accountCreds: awilix.asClass(AccountKey).scoped(),
homedir: awilix.asFunction(() => homedir).scoped(),
});
fs = container.resolve("fs");
});
Expand Down
3 changes: 1 addition & 2 deletions test/schema/diff.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,10 @@ describe("schema diff", function () {

it("can parse home directory paths", async function () {
const homedir = container.resolve("homedir");
homedir.returns("/Users/test-user");

await run(`schema diff --secret "secret" --dir ~`, container);

expect(gatherFSL).to.have.been.calledWith("/Users/test-user");
expect(gatherFSL).to.have.been.calledWith(homedir);
});

it.skip("errors if user provides both --staged and --active flags");
Expand Down
Loading