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

Command to start a local container #491

Merged
merged 19 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
260 changes: 256 additions & 4 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@shikijs/cli": "^1.24.0",
"awilix": "^12.0.2",
"chalk": "^5.3.0",
"dockerode": "^4.0.2",
"eslint": "^9.12.0",
"esprima": "^4.0.1",
"fauna": "^2.4.0",
Expand Down Expand Up @@ -79,7 +80,7 @@
"pretest:ci": "npm run build:app",
"test:ci": "mocha --forbid-only --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 --define:process.env.NODE_ENV=\\\"production\\\"",
"build:app": "esbuild --loader:.node=file --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\\\"",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tl;dr: What does this do?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some node modules like low level crypto libraries esbuild does not bundle by default. This instructs esbuild to do so by copying the files it needs into the bundle.

"build:sea": "node ./sea/build.cjs",
"format": "prettier -w --log-level silent .",
"format:check": "prettier -c .",
Expand Down
2 changes: 2 additions & 0 deletions src/cli.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import chalk from "chalk";
import yargs from "yargs";

import databaseCommand from "./commands/database/database.mjs";
import localCommand from "./commands/local.mjs";
import loginCommand from "./commands/login.mjs";
import queryCommand from "./commands/query.mjs";
import schemaCommand from "./commands/schema/schema.mjs";
Expand Down Expand Up @@ -108,6 +109,7 @@ function buildYargs(argvInput) {
.command(loginCommand)
.command(schemaCommand)
.command(databaseCommand)
.command(localCommand)
.demandCommand()
.strictCommands(true)
.options({
Expand Down
55 changes: 55 additions & 0 deletions src/commands/local.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { ensureContainerRunning } from "../lib/docker-containers.mjs";

/**
* Starts the local Fauna container
* @param {import('yargs').Arguments} argv The arguments from yargs
* @returns {Promise<void>} a promise that resolves when the container is ready.
* It will reject if the container is not ready after the maximum number of attempts.
*/
async function startLocal(argv) {
await ensureContainerRunning({
imageName: argv.image,
containerName: argv.name,
hostPort: argv.hostPort,
containerPort: argv.containerPort,
pull: argv.pull,
});
}

/**
* Builds the yargs command for the local command
* @param {import('yargs').Argv} yargs The yargs instance
* @returns {import('yargs').Argv} The yargs instance
*/
function buildLocalCommand(yargs) {
return yargs.options({
containerPort: {
describe: "The port inside the container Fauna listens on.",
type: "number",
default: "8443",
},
hostPort: {
describe:
"The port on the host machine mapped to the container's port. This is the port you'll connect to Fauna on.",
type: "number",
default: "8443",
},
name: {
describe: "The name to give the container",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
describe: "The name to give the container",
describe: "The name to give the container.",

type: "string",
default: "faunadb",
},
pull: {
describe: "Pull the latest image before starting the container.",
type: "boolean",
default: true,
},
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can do this in a future PR, but I really love to see FSL directory flag and a database name flag that this command creates and pushes into.

Copy link
Contributor Author

@cleve-fauna cleve-fauna Dec 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's good idea for starting up dev and test environments.

}

export default {
command: "local",
describe: "Start a local Fauna container",
builder: buildLocalCommand,
handler: startLocal,
};
9 changes: 9 additions & 0 deletions src/config/setup-container.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { exit } from "node:process";
import { confirm } from "@inquirer/prompts";
import * as awilix from "awilix";
import { Lifetime } from "awilix";
import Docker from "dockerode";
import fauna from "fauna";
import faunadb from "faunadb";
import open from "open";
Expand Down Expand Up @@ -74,6 +75,14 @@ export const injectables = {
// generic lib (homemade utilities)
parseYargs: awilix.asValue(parseYargs),
logger: awilix.asFunction(buildLogger, { lifetime: Lifetime.SINGLETON }),
docker: awilix.asFunction(
() => {
const dockerInstance = new Docker(); // Create instance
// If Docker requires additional async setup, perform it here and return a promise
return dockerInstance;
},
{ lifetime: Lifetime.SINGLETON },
),
oauthClient: awilix.asClass(OAuthClient, { lifetime: Lifetime.SCOPED }),
makeAccountRequest: awilix.asValue(makeAccountRequest),
makeFaunaRequest: awilix.asValue(makeRetryableFaunaRequest),
Expand Down
9 changes: 9 additions & 0 deletions src/config/setup-test-container.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ export function setupTestContainer() {
getSession: stub(),
})),
oauthClient: awilix.asFunction(stub()),
docker: awilix.asValue({
createContainer: stub(),
getContainer: stub(),
listContainers: stub(),
modem: {
followProgress: stub(),
},
pull: stub(),
}),
credentials: awilix.asClass(stub()).singleton(),
errorHandler: awilix.asValue((error, exitCode) => {
error.code = exitCode;
Expand Down
10 changes: 5 additions & 5 deletions src/lib/auth/oauth-client.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
import SuccessPage from "./successPage.mjs";

// Default to prod client id and secret
const clientId = process.env.FAUNA_CLIENT_ID ?? "Aq4_G0mOtm_F1fK3PuzE0k-i9F0";
const CLIENT_ID = process.env.FAUNA_CLIENT_ID ?? "Aq4_G0mOtm_F1fK3PuzE0k-i9F0";
// Native public clients are not confidential. The client secret is not used beyond
// client identification. https://datatracker.ietf.org/doc/html/rfc8252#section-8.5
const clientSecret =
const CLIENT_SECRET =
process.env.FAUNA_CLIENT_SECRET ??
"2W9eZYlyN5XwnpvaP3AwOfclrtAjTXncH6k-bdFq1ZV0hZMFPzRIfg";
const REDIRECT_URI = `http://127.0.0.1`;
Expand All @@ -28,7 +28,7 @@

getOAuthParams() {
return {
client_id: clientId, // eslint-disable-line camelcase
client_id: CLIENT_ID, // eslint-disable-line camelcase
redirect_uri: `${REDIRECT_URI}:${this.port}`, // eslint-disable-line camelcase
code_challenge: this.codeChallenge, // eslint-disable-line camelcase
code_challenge_method: "S256", // eslint-disable-line camelcase
Expand All @@ -40,8 +40,8 @@

getTokenParams() {
return {
clientId,
clientSecret,
clientId: CLIENT_ID,
clientSecret: CLIENT_SECRET,
authCode: this.authCode,
redirectURI: `${REDIRECT_URI}:${this.port}`,
codeVerifier: this.codeVerifier,
Expand All @@ -53,7 +53,7 @@
}

// req: IncomingMessage, res: ServerResponse
_handleRequest(req, res) {

Check warning on line 56 in src/lib/auth/oauth-client.mjs

View workflow job for this annotation

GitHub Actions / lint

Method '_handleRequest' has a complexity of 13. Maximum allowed is 10
const logger = container.resolve("logger");
const allowedOrigins = [
"http://localhost:3005",
Expand Down
Loading
Loading