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

Self integrate nitro #1307

Merged
merged 13 commits into from
Aug 22, 2024
19 changes: 19 additions & 0 deletions src/commands/deploy/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { GenezioDeployOptions } from "../../models/commandOptions.js";
import { interruptLocalProcesses } from "../../utils/localInterrupt.js";
import { debugLogger } from "../../utils/logging.js";
import { genezioDeploy } from "./genezio.js";
import { nitroDeploy } from "./nitro/deploy.js";
import fs from "fs";
import { nextJsDeploy } from "./nextjs/deploy.js";
import path from "path";
Expand All @@ -19,12 +20,17 @@ export async function deployCommand(options: GenezioDeployOptions) {
debugLogger.debug("Deploying Next.js app");
await nextJsDeploy(options);
break;
case DeployType.Nitro:
debugLogger.debug("Deploying Nitro app");
await nitroDeploy(options);
break;
}
}

enum DeployType {
Classic,
NextJS,
Nitro,
}

function decideDeployType(): DeployType {
Expand All @@ -40,12 +46,25 @@ function decideDeployType(): DeployType {
return DeployType.NextJS;
}

// Check if nitro.config.js exists
if (
fs.existsSync(path.join(cwd, "nitro.config.js")) ||
fs.existsSync(path.join(cwd, "nitro.config.mjs")) ||
fs.existsSync(path.join(cwd, "nitro.config.cjs")) ||
fs.existsSync(path.join(cwd, "nitro.config.ts"))
) {
return DeployType.Nitro;
}

// Check if "next" package is present in the project dependencies
if (fs.existsSync(path.join(cwd, "package.json"))) {
const packageJson = JSON.parse(fs.readFileSync(path.join(cwd, "package.json"), "utf-8"));
if (packageJson.dependencies?.next) {
return DeployType.NextJS;
}
if (packageJson.devDependencies?.nitropack) {
return DeployType.Nitro;
}
}

return DeployType.Classic;
Expand Down
94 changes: 2 additions & 92 deletions src/commands/deploy/nextjs/deploy.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import fs, { existsSync, readFileSync } from "fs";
import fs, { existsSync } from "fs";
import { GenezioDeployOptions } from "../../../models/commandOptions.js";
import git from "isomorphic-git";
import { YamlConfigurationIOController } from "../../../projectConfiguration/yaml/v2.js";
import { YamlProjectConfiguration } from "../../../projectConfiguration/yaml/v2.js";
import inquirer from "inquirer";
import path from "path";
import { regions } from "../../../utils/configs.js";
import { checkProjectName } from "../../create/create.js";
import { debugLogger, log } from "../../../utils/logging.js";
import { $ } from "execa";
import { UserError } from "../../../errors.js";
Expand Down Expand Up @@ -36,21 +32,14 @@ import {
NEXT_JS_GET_ACCESS_KEY,
NEXT_JS_GET_SECRET_ACCESS_KEY,
} from "../../../constants.js";
import getProjectInfoByName from "../../../requests/getProjectInfoByName.js";
import colors from "colors";
import {
uniqueNamesGenerator,
adjectives,
colors as ungColors,
animals,
} from "unique-names-generator";
import { getPresignedURLForProjectCodePush } from "../../../requests/getPresignedURLForProjectCodePush.js";
import { computeAssetsPaths } from "./assets.js";
import * as Sentry from "@sentry/node";
import { randomUUID } from "crypto";
import { EdgeFunction, getEdgeFunctions } from "./edge.js";
import { uploadEnvVarsFromFile } from "../utils.js";
import { isCI } from "../../../utils/process.js";
import { readOrAskConfig } from "../utils.js";

export async function nextJsDeploy(options: GenezioDeployOptions) {
// Check if node_modules exists
Expand Down Expand Up @@ -513,82 +502,3 @@ async function writeOpenNextConfig(region: string, edgeFunctionPaths: EdgeFuncti
const tag = ENVIRONMENT === "prod" ? "latest" : "dev";
await getPackageManager().install([`@genezio/nextjs-isr-${region}@${tag}`]);
}

async function readOrAskConfig(configPath: string): Promise<YamlProjectConfiguration> {
const configIOController = new YamlConfigurationIOController(configPath);
if (!existsSync(configPath)) {
const name = await readOrAskProjectName();

let region = regions[0].value;
if (!isCI()) {
({ region } = await inquirer.prompt([
{
type: "list",
name: "region",
message: "Select the Genezio project region:",
choices: regions,
},
]));
} else {
log.info(
"Using the default region for the project because no `genezio.yaml` file was found.",
);
}

await configIOController.write({ name, region, yamlVersion: 2 });
}

return await configIOController.read();
}

async function readOrAskProjectName(): Promise<string> {
if (existsSync("package.json")) {
// Read package.json content
const packageJson = readFileSync("package.json", "utf-8");
const packageJsonName = JSON.parse(packageJson)["name"];

const validProjectName: boolean = await (async () => checkProjectName(packageJsonName))()
.then(() => true)
.catch(() => false);

const projectExists = await getProjectInfoByName(packageJsonName)
.then(() => true)
.catch(() => false);

// We don't want to automatically use the package.json name if the project
// exists, because it could overwrite the existing project by accident.
if (packageJsonName !== undefined && validProjectName && !projectExists)
return packageJsonName;
}

let name = uniqueNamesGenerator({
dictionaries: [ungColors, adjectives, animals],
separator: "-",
style: "lowerCase",
length: 3,
});
if (!isCI()) {
// Ask for project name
({ name } = await inquirer.prompt([
{
type: "input",
name: "name",
message: "Enter the Genezio project name:",
default: path.basename(process.cwd()),
validate: (input: string) => {
try {
checkProjectName(input);
return true;
} catch (error) {
if (error instanceof Error) return colors.red(error.message);
return colors.red("Unavailable project name");
}
},
},
]));
} else {
log.info("Using a random name for the project because no `genezio.yaml` file was found.");
}

return name;
}
75 changes: 75 additions & 0 deletions src/commands/deploy/nitro/deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { $ } from "execa";
import { GenezioDeployOptions } from "../../../models/commandOptions.js";
import { UserError } from "../../../errors.js";
import { YamlProjectConfiguration } from "../../../projectConfiguration/yaml/v2.js";
import { functionToCloudInput, getCloudAdapter } from "../genezio.js";
import { getCloudProvider } from "../../../requests/getCloudProvider.js";
import { FunctionType, Language } from "../../../projectConfiguration/yaml/models.js";
import { PackageManagerType } from "../../../packageManagers/packageManager.js";
import { ProjectConfiguration } from "../../../models/projectConfiguration.js";
import { debugLogger } from "../../../utils/logging.js";
import { readOrAskConfig } from "../utils.js";
import { existsSync } from "fs";
import { getPackageManager } from "../../../packageManagers/packageManager.js";
import path from "path";
export async function nitroDeploy(options: GenezioDeployOptions) {
// Check if node_modules exists
if (!existsSync("node_modules")) {
throw new UserError(
`Please run \`${getPackageManager().command} install\` before deploying your Nitro project. This will install the necessary dependencies.`,
);
}
await $({ stdio: "inherit" })`npx nitro build --preset aws_lambda`.catch(() => {
throw new UserError("Failed to build the Nitro project. Check the logs above.");
});
const genezioConfig = await readOrAskConfig(options.config);
await deployFunctions(genezioConfig, options.stage);
}

async function deployFunctions(config: YamlProjectConfiguration, stage?: string) {
const cloudProvider = await getCloudProvider(config.name);
const cloudAdapter = getCloudAdapter(cloudProvider);

const functions = [
{
path: path.join(process.cwd(), ".output", "server"),
name: "nitro-server",
entry: "index.mjs",
handler: "handler",
type: FunctionType.aws,
},
];

const deployConfig: YamlProjectConfiguration = {
...config,
backend: {
path: "",
language: {
name: Language.js,
runtime: "nodejs20.x",
architecture: "x86_64",
packageManager: PackageManagerType.npm,
},
functions,
},
};

const projectConfiguration = new ProjectConfiguration(
deployConfig,
await getCloudProvider(deployConfig.name),
{
generatorResponses: [],
classesInfo: [],
},
);
const cloudInputs = await Promise.all(
projectConfiguration.functions.map((f) => functionToCloudInput(f, ".")),
);

const result = await cloudAdapter.deploy(cloudInputs, projectConfiguration, { stage }, [
"nitro",
]);
debugLogger.debug(`Deployed functions: ${JSON.stringify(result.functions)}`);

return result;
}
90 changes: 90 additions & 0 deletions src/commands/deploy/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { CloudProviderIdentifier } from "../../models/cloudProviderIdentifier.js
import { CreateDatabaseRequest, GetDatabaseResponse } from "../../models/requests.js";
import { DatabaseType } from "../../projectConfiguration/yaml/models.js";
import { YamlProjectConfiguration } from "../../projectConfiguration/yaml/v2.js";
import { YamlConfigurationIOController } from "../../projectConfiguration/yaml/v2.js";
import {
createDatabase,
findLinkedDatabase,
Expand All @@ -29,6 +30,16 @@ import {
promptToConfirmSettingEnvironmentVariables,
resolveEnvironmentVariable,
} from "../../utils/environmentVariables.js";
import inquirer from "inquirer";
import { existsSync, readFileSync } from "fs";
import { checkProjectName } from "../create/create.js";
import {
uniqueNamesGenerator,
adjectives,
colors as ungColors,
animals,
} from "unique-names-generator";
import { regions } from "../../utils/configs.js";
import { EnvironmentVariable } from "../../models/environmentVariables.js";
import { isCI } from "../../utils/process.js";

Expand Down Expand Up @@ -71,6 +82,85 @@ export async function getOrCreateEmptyProject(
return { projectId: project.id, projectEnvId: projectEnv.id };
}

export async function readOrAskConfig(configPath: string): Promise<YamlProjectConfiguration> {
const configIOController = new YamlConfigurationIOController(configPath);
if (!existsSync(configPath)) {
const name = await readOrAskProjectName();

let region = regions[0].value;
if (!isCI()) {
({ region } = await inquirer.prompt([
{
type: "list",
name: "region",
message: "Select the Genezio project region:",
choices: regions,
},
]));
} else {
log.info(
"Using the default region for the project because no `genezio.yaml` file was found.",
);
}

await configIOController.write({ name, region, yamlVersion: 2 });
}

return await configIOController.read();
}

export async function readOrAskProjectName(): Promise<string> {
if (existsSync("package.json")) {
// Read package.json content
const packageJson = readFileSync("package.json", "utf-8");
const packageJsonName = JSON.parse(packageJson)["name"];

const validProjectName: boolean = await (async () => checkProjectName(packageJsonName))()
.then(() => true)
.catch(() => false);

const projectExists = await getProjectInfoByName(packageJsonName)
.then(() => true)
.catch(() => false);

// We don't want to automatically use the package.json name if the project
// exists, because it could overwrite the existing project by accident.
if (packageJsonName !== undefined && validProjectName && !projectExists)
return packageJsonName;
}

let name = uniqueNamesGenerator({
dictionaries: [ungColors, adjectives, animals],
separator: "-",
style: "lowerCase",
length: 3,
});
if (!isCI()) {
// Ask for project name
({ name } = await inquirer.prompt([
{
type: "input",
name: "name",
message: "Enter the Genezio project name:",
default: path.basename(process.cwd()),
validate: (input: string) => {
try {
checkProjectName(input);
return true;
} catch (error) {
if (error instanceof Error) return colors.red(error.message);
return colors.red("Unavailable project name");
}
},
},
]));
} else {
log.info("Using a random name for the project because no `genezio.yaml` file was found.");
}

return name;
}

export async function getOrCreateDatabase(
createDatabaseReq: CreateDatabaseRequest,
stage: string,
Expand Down
Loading