Skip to content

Commit

Permalink
chore: substitude node dependency with docker container for prisma co…
Browse files Browse the repository at this point in the history
…mmands
  • Loading branch information
NathanFlurry committed Mar 7, 2024
1 parent 3955f35 commit 416e7e4
Show file tree
Hide file tree
Showing 4 changed files with 126 additions and 22 deletions.
4 changes: 2 additions & 2 deletions deno.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@
"cli:run": "deno task artifacts:build:all && deno run -A --check src/cli/main.ts --path tests/basic",

// Compiles the CLI to a binary
"cli:compile": "deno task artifacts:build:all && deno compile --check --allow-net --allow-read --allow-env --allow-run --allow-write --output _gen/cli src/cli/main.ts",
"cli:compile": "deno task artifacts:build:all && deno compile --check --allow-net --allow-read --allow-env --allow-run --allow-write --allow-sys --output _gen/cli src/cli/main.ts",

// Installs the CLI on the local machine
"cli:install": "deno task artifacts:build:all && deno install --allow-net --allow-read --allow-env --allow-run --allow-write --name opengb --force src/cli/main.ts",
"cli:install": "deno task artifacts:build:all && deno install --allow-net --allow-read --allow-env --allow-run --allow-write --allow-sys --name opengb --force src/cli/main.ts",

// Generates schema
"artifacts:build:all": "deno task artifacts:build:schema && deno task artifacts:build:runtime_archive",
Expand Down
12 changes: 10 additions & 2 deletions src/migrate/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//
// Wrapper around `prisma generate`

import { resolve } from "../deps.ts";
import { copy, emptyDir, resolve } from "../deps.ts";
import { buildPrismaPackage } from "./build_prisma_esm.ts";
import { Module, Project } from "../project/mod.ts";
import { forEachPrismaSchema, runPrismaCommand } from "./mod.ts";
Expand All @@ -14,7 +14,10 @@ export async function generateClient(
await forEachPrismaSchema(
project,
modules,
async ({ databaseUrl, generatedClientDir }) => {
async ({ module, databaseUrl, generatedClientDir }) => {
// Clear generated dir
await emptyDir(generatedClientDir);

// Generate client
await runPrismaCommand(project, {
args: ["generate"],
Expand Down Expand Up @@ -55,6 +58,11 @@ export async function generateClient(
generatedClientDir,
resolve(generatedClientDir, "esm.js"),
);

// Copy to module
const dstDir = resolve(module.path, "_gen", "prisma");
await emptyDir(dstDir);
await copy(generatedClientDir, dstDir, { overwrite: true });
},
);
}
121 changes: 103 additions & 18 deletions src/migrate/mod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { Module, ModuleDatabase, Project } from "../project/mod.ts";
import { assertValidString } from "./validate.ts";
import { emptyDir } from "../deps.ts";

const NODE_IMAGE = "node:21-alpine";
const NODE_CONTAINER_NAME = "opengb-node";
const PRISMA_VERSION = "5.9.1";

export type ForEachDatabaseCallback = (
Expand Down Expand Up @@ -65,14 +67,15 @@ export async function forEachPrismaSchema(
project,
modules,
async ({ databaseUrl, module, db }) => {
const prismaDir = await preparePrismaWorkspace(project);
const prismaDir = await ensurePrismaWorkspace(project);

const srcDbDir = resolve(module.path, "db");
const dstDbDir = resolve(prismaDir, "db");

const generatedClientDir = resolve(
module.path,
"_gen",
"prisma",
prismaDir,
"client",
"js",
);

// Copy db directory
Expand All @@ -83,9 +86,11 @@ export async function forEachPrismaSchema(
const schemaPath = resolve(dstDbDir, "schema.prisma");
let schema = await Deno.readTextFile(schemaPath);
schema += dedent`
// Generated by Open Game Backend
generator client {
provider = "prisma-client-js"
output = "${generatedClientDir}"
output = "/prisma/client/js"
previewFeatures = ["driverAdapters"]
}
`;
Expand All @@ -111,20 +116,69 @@ async function createDatabases(client: PostgresClient, db: ModuleDatabase) {
}

/**
* Context about the Prisma workspace for the current command.
* Context about the Prisma workspace for the current process.
*/
const PRISMA_WORKSPACE_STATE = {
didRunInstall: false,
didStartContainer: false,
};

/**
* Installs a copy of Prisma in to a directory that can be reused for any
* Prisma-related commands.
*
* All commands are ran inside a Node Docker container in order to ensure we
* don't need Node installed on the host.
*/
async function preparePrismaWorkspace(project: Project): Promise<string> {
async function ensurePrismaWorkspace(project: Project): Promise<string> {
const prismaDir = resolve(project.path, "_gen", "prisma_workspace");
await Deno.mkdir(prismaDir, { recursive: true });

// Start Node container to run Prisma commands
if (!PRISMA_WORKSPACE_STATE.didStartContainer) {
// Remove the existing container if exists. This forces the new
// configuration for a new container.
const rmOutput = await new Deno.Command("docker", {
args: ["rm", "-f", NODE_CONTAINER_NAME],
}).output();
if (!rmOutput.success) {
throw new Error("Failed to remove the existing container.");
}

// Start the container
const runOutput = await new Deno.Command("docker", {
args: [
"run",
"-d",
"--rm",
`--name=${NODE_CONTAINER_NAME}`,
"--add-host=host.docker.internal:host-gateway",
`--volume=${prismaDir}:/prisma`,
"--entrypoint=sh",
NODE_IMAGE,
// ===
"-c",
`sleep infinity`,
// `
// addgroup -g ${Deno.gid()} node
// adduser -u ${Deno.uid()} -G node node
// rm -rf /.npm
// mkdir /.npm
// chown -R node:node /.npm
// sleep infinity
// `
],
}).output();
if (!runOutput.success) {
throw new Error("Failed to start the container:\n" + new TextDecoder().decode(runOutput.stderr));
}

await new Promise((resolve) => setTimeout(resolve, 1000));

PRISMA_WORKSPACE_STATE.didStartContainer = true;
}

// Install Prisma
if (!PRISMA_WORKSPACE_STATE.didRunInstall) {
// Write package.json
const packageJson = JSON.stringify({
Expand All @@ -134,16 +188,25 @@ async function preparePrismaWorkspace(project: Project): Promise<string> {
"dependencies": {
"@prisma/client": `^${PRISMA_VERSION}`,
},
"node": {
"target": "20.11.1",
},
});
await Deno.writeTextFile(resolve(prismaDir, "package.json"), packageJson);

// Install dependencies
const installOutput = await new Deno.Command("npm", {
args: ["install", "--prefer-offline", "--no-audit", "--no-fund", "--progress=false"],
cwd: prismaDir,
const installOutput = await new Deno.Command("docker", {
args: [
"exec",
"--workdir=/prisma",
// `--user=${Deno.uid()}:${Deno.gid()}`,
`--user=node`,
NODE_CONTAINER_NAME,
// ===
"npm",
"install",
"--prefer-offline",
"--no-audit",
"--no-fund",
"--progress=false",
],
stdout: "inherit",
stderr: "inherit",
}).output();
Expand All @@ -161,7 +224,8 @@ export interface RunPrismaCommandOpts {
}

/**
* Run a Prisma command in the Prisma workspace.
* Run a Prisma command in the Prisma workspace inside the Docker container. The
* CWD is set to the `db` directory.
*
* We don't use `deno run npm:prisma` because:
*
Expand All @@ -172,10 +236,31 @@ export async function runPrismaCommand(
project: Project,
opts: RunPrismaCommandOpts & Deno.CommandOptions,
) {
const prismaDir = resolve(project.path, "_gen", "prisma_workspace");
const status = await new Deno.Command("npx", {
args: ["prisma", ...opts.args],
cwd: resolve(prismaDir, "db"),
await ensurePrismaWorkspace(project);

// HACK: Replace the host with the Docker gateway. This isn't a failsave solution.
if (opts.env.DATABASE_URL) {
opts.env.DATABASE_URL = opts.env.DATABASE_URL
.replace("localhost", "host.docker.internal")
.replace("127.0.0.1", "host.docker.internal")
.replace("0.0.0.0", "host.docker.internal");
}

const envFlags = Object.entries(opts.env).map(([key, value]) => `--env=${key}=${value}`);

const status = await new Deno.Command("docker", {
args: [
"exec",
"-i",
"--workdir=/prisma/db",
// `--user=${Deno.uid()}:${Deno.gid()}`,
`--user=node`,
...envFlags,
NODE_CONTAINER_NAME,
"npx",
"prisma",
...opts.args,
],
stdin: "inherit",
stdout: "inherit",
stderr: "inherit",
Expand Down
11 changes: 11 additions & 0 deletions src/utils/postgres_daemon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@ import { Project } from "../project/mod.ts";
const CONTAINER_NAME = "opengb-postgres";
const VOLUME_NAME = "opengb-postgres-data";

/**
* Context about the Postgres server for the current process.
*/
const POSTGRES_STATE = {
running: false,
};

export async function ensurePostgresRunning(_project: Project) {
if (POSTGRES_STATE.running) return;

// Validate Docker is installed
const versionOutput = await new Deno.Command("docker", {
args: ["version"],
Expand Down Expand Up @@ -69,4 +78,6 @@ export async function ensurePostgresRunning(_project: Project) {
if (checkOutput.success) break;
await new Promise((r) => setTimeout(r, 500));
}

POSTGRES_STATE.running = true;
}

0 comments on commit 416e7e4

Please sign in to comment.