genezio
Version:
Command line utility to interact with Genezio infrastructure.
201 lines (200 loc) • 10.1 kB
JavaScript
import { $ } from "execa";
import glob from "glob";
import git from "isomorphic-git";
import { UserError } from "../../../errors.js";
import { functionToCloudInput, getCloudAdapter } from "../genezio.js";
import { getCloudProvider } from "../../../requests/getCloudProvider.js";
import { FunctionType, Language } from "../../../projectConfiguration/yaml/models.js";
import { NODE_DEFAULT_PACKAGE_MANAGER, PackageManagerType, } from "../../../packageManagers/packageManager.js";
import { ProjectConfiguration } from "../../../models/projectConfiguration.js";
import { debugLogger, log } from "../../../utils/logging.js";
import { actionDetectedEnvFile, attemptToInstallDependencies, prepareServicesPostBackendDeployment, prepareServicesPreBackendDeployment, readOrAskConfig, createBackendEnvVarList, uploadUserCode, } from "../utils.js";
import path from "path";
import colors from "colors";
import { FrontendPresignedURLAppType, getFrontendPresignedURL, } from "../../../requests/getFrontendPresignedURL.js";
import { createTemporaryFolder, zipDirectoryToDestinationPath } from "../../../utils/file.js";
import fs from "fs";
import { uploadContentToS3 } from "../../../requests/uploadContentToS3.js";
import { createFrontendProjectV2, } from "../../../requests/createFrontendProject.js";
import { DeployType } from "../command.js";
import { DEFAULT_ARCHITECTURE, SSRFrameworkComponentType } from "../../../models/projectOptions.js";
import { addSSRComponentToConfig } from "../../analyze/utils.js";
import { DASHBOARD_URL } from "../../../constants.js";
import { warningMissingEnvironmentVariables } from "../../../utils/environmentVariables.js";
import { isCI } from "../../../utils/process.js";
export async function nuxtNitroDeploy(options, deployType) {
const genezioConfig = await readOrAskConfig(options.config);
const packageManagerType = genezioConfig.nuxt?.packageManager || NODE_DEFAULT_PACKAGE_MANAGER;
const cwd = process.cwd();
const NitroOrNuxtFlag = deployType === DeployType.Nitro
? SSRFrameworkComponentType.nitro
: SSRFrameworkComponentType.nuxt;
const componentPath = genezioConfig[NitroOrNuxtFlag]?.path
? path.resolve(cwd, genezioConfig[NitroOrNuxtFlag].path)
: cwd;
// Give the user another chance if he forgot to add `--env` flag
if (!isCI() && !options.env) {
options.env = await actionDetectedEnvFile(componentPath, genezioConfig.name, options.stage);
}
// Prepare services before deploying (database, authentication, etc)
await prepareServicesPreBackendDeployment(genezioConfig, genezioConfig.name, options.stage, options.env);
// Install dependencies
const installDependenciesCommand = await attemptToInstallDependencies([], componentPath, packageManagerType);
switch (deployType) {
case DeployType.Nuxt:
await $({
stdio: "inherit",
env: { NITRO_PRESET: "aws_lambda" },
cwd: componentPath,
}) `npx nuxi build --preset=aws_lambda`.catch(() => {
throw new UserError(`Failed to build the Nuxt project. Check the logs above.
Note: If your Nuxt project was not migrated to Nuxt 3, please visit https://v2.nuxt.com/lts for guidance on migrating your project. Genezio supports only Nuxt 3 projects.`);
});
break;
case DeployType.Nitro:
await $({
stdio: "inherit",
env: { NITRO_PRESET: "aws_lambda" },
cwd: componentPath,
}) `npx nitro build --preset=aws_lambda`.catch(() => {
throw new UserError("Failed to build the Nuxt project. Check the logs above.");
});
break;
default:
throw new Error(`Incorrect deployment type ${deployType}`);
}
// Add component in genezio config file
await addSSRComponentToConfig(options.config, {
path: componentPath,
packageManager: packageManagerType,
scripts: {
deploy: [`${installDependenciesCommand.command}`],
},
}, NitroOrNuxtFlag);
const environmentVariables = await createBackendEnvVarList(options.env, options.stage, genezioConfig, NitroOrNuxtFlag);
const [cloudResult, domain] = await Promise.all([
deployFunction(genezioConfig, options, componentPath, environmentVariables),
deployStaticAssets(genezioConfig, options.stage, componentPath),
]);
const [cdnUrl] = await Promise.all([
deployCDN(cloudResult.functions, domain, genezioConfig, options.stage, componentPath),
uploadUserCode(genezioConfig.name, genezioConfig.region, options.stage, componentPath),
]);
await warningMissingEnvironmentVariables(genezioConfig.nuxt?.path || "./", cloudResult.projectId, cloudResult.projectEnvId);
// Prepare services after deploying (authentication, etc)
await prepareServicesPostBackendDeployment(genezioConfig, genezioConfig.name, options.stage);
log.info(`The app is being deployed at ${colors.cyan(cdnUrl)}. It might take a few moments to be available worldwide.`);
log.info(`\nApp Dashboard URL: ${colors.cyan(`${DASHBOARD_URL}/project/${cloudResult.projectId}/${cloudResult.projectEnvId}`)}\n` +
`${colors.dim("Here you can monitor logs, set up a custom domain, and more.")}\n`);
}
async function deployFunction(config, options, cwd, environmentVariables) {
const cloudProvider = await getCloudProvider(config.name);
const cloudAdapter = getCloudAdapter(cloudProvider);
const cwdRelative = path.relative(process.cwd(), cwd) || ".";
const functions = [
{
path: path.join(cwdRelative, ".output"),
name: "nuxt-server",
entry: path.join("server", "index.mjs"),
handler: "handler",
type: config.nuxt?.type === FunctionType.persistent
? FunctionType.persistent
: FunctionType.aws,
timeout: config.nuxt?.timeout,
storageSize: config.nuxt?.storageSize,
instanceSize: config.nuxt?.instanceSize,
vcpuCount: config.nuxt?.vcpuCount,
memoryMb: config.nuxt?.memoryMb,
maxConcurrentRequestsPerInstance: config.nuxt?.maxConcurrentRequestsPerInstance,
maxConcurrentInstances: config.nuxt?.maxConcurrentInstances,
cooldownTime: config.nuxt?.cooldownTime,
},
];
const deployConfig = {
...config,
backend: {
path: cwdRelative,
language: {
name: Language.js,
architecture: DEFAULT_ARCHITECTURE,
packageManager: PackageManagerType.npm,
...(config.nuxt?.runtime !== undefined && { runtime: config.nuxt.runtime }),
},
functions,
},
};
const projectConfiguration = new ProjectConfiguration(deployConfig, await getCloudProvider(deployConfig.name), {
generatorResponses: [],
classesInfo: [],
});
const cloudInputs = await Promise.all(projectConfiguration.functions.map((f) => functionToCloudInput(f, ".")));
const projectGitRepositoryUrl = (await git.listRemotes({ fs, dir: process.cwd() })).find((r) => r.remote === "origin")?.url;
const result = await cloudAdapter.deploy(cloudInputs, projectConfiguration, { stage: options.stage }, ["nuxt"], projectGitRepositoryUrl, environmentVariables);
return result;
}
async function deployCDN(deployedFunctions, domainName, config, stage, cwd) {
const serverOrigin = {
domain: {
id: deployedFunctions[0].id,
type: "function",
},
path: undefined,
methods: ["GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"],
cachePolicy: "custom-function-cache",
};
const s3Origin = {
domain: {
id: "frontendHosting",
type: "s3",
},
path: undefined,
methods: ["GET", "HEAD", "OPTIONS"],
cachePolicy: "caching-optimized",
};
const paths = [...(await computeAssetsPaths(s3Origin, cwd))];
const { domain: distributionUrl } = await createFrontendProjectV2(domainName, config.name, config.region, stage, paths,
/* defaultPath= */ {
origin: serverOrigin,
}, ["nuxt"]);
if (!distributionUrl.startsWith("https://") && !distributionUrl.startsWith("http://")) {
return `https://${distributionUrl}`;
}
return distributionUrl;
}
async function deployStaticAssets(config, stage, cwd) {
const getFrontendPresignedURLPromise = getFrontendPresignedURL(
/* subdomain= */ config.nuxt?.subdomain,
/* projectName= */ config.name, stage,
/* type= */ FrontendPresignedURLAppType.AutoGenerateDomain);
const temporaryFolder = await createTemporaryFolder();
const archivePath = path.join(temporaryFolder, "nuxt-static.zip");
await fs.promises.mkdir(path.join(temporaryFolder, "nuxt-static"));
await fs.promises.cp(path.join(cwd, ".output", "public"), path.join(temporaryFolder, "nuxt-static"), { recursive: true });
const { presignedURL, userId, domain } = await getFrontendPresignedURLPromise;
debugLogger.debug(`Generated presigned URL for Next.js static files. Domain: ${domain}`);
await zipDirectoryToDestinationPath(path.join(temporaryFolder, "nuxt-static"), domain, archivePath);
await uploadContentToS3(presignedURL, archivePath, undefined, userId);
debugLogger.debug("Uploaded Nuxt static files to S3.");
return domain;
}
async function computeAssetsPaths(s3Origin, cwd) {
const folder = path.join(cwd, ".output", "public");
return new Promise((resolve, reject) => {
glob("*", {
dot: true,
cwd: folder,
}, (err, files) => {
if (err) {
reject(err);
return;
}
const paths = files.map((file) => ({
origin: s3Origin,
pattern: fs.lstatSync(path.join(folder, file)).isDirectory()
? `${file}/*`
: file,
}));
resolve(paths);
});
});
}