import { importResourceGroupType, ResourceGroupType } from "@/resourceGroups"; import { dockerfileExists } from "../../docker"; import ejs from "ejs"; import fs from "fs/promises"; import { existsSync, readFileSync } from "fs"; import { BaseTemplate, parseInputSchema } from "@/templates"; import { ZodObject } from "zod"; import path from "path"; class NextJsFramework extends BaseTemplate { public data: any; constructor( public name: string, public directory: string, public resourceGroupType: ResourceGroupType, public inputSchema: ZodObject | undefined = undefined ) { const dirname = path.resolve(__dirname); super(name, directory, resourceGroupType, dirname); if ( !NextJsFramework.resourceGroupsSupported.includes(this.resourceGroupType) ) { throw new Error("Resource group type not supported"); } const tmpResourceGroup = importResourceGroupType( resourceGroupType, name, directory ); if (!tmpResourceGroup || !tmpResourceGroup.inputSchema) { throw new Error("Error importing resource group type"); } const templatePath = path.join(__dirname, "template.yaml"); if (existsSync(templatePath)) { this.inputSchema = parseInputSchema(readFileSync(templatePath, "utf-8"))!; this.inputSchema = this.inputSchema.merge(tmpResourceGroup.inputSchema); } else { this.inputSchema = tmpResourceGroup.inputSchema; } } public static resourceGroupsSupported: ResourceGroupType[] = [ "fullstack-aws", "staticsite-aws" ]; public async collectInputs(inputs: any) { if (typeof this.inputSchema === "undefined") { throw new Error("Error parsing input schema"); } this.data = this.inputSchema.parse(inputs); } public async generate() { const ensureDockerfile = async () => { if (this.resourceGroupType !== "fullstack-aws") { return; } if (await dockerfileExists()) { return; } try { const result = await ejs.renderFile( `${__dirname}/Dockerfile.ejs.t`, this.data ); await fs.writeFile("Dockerfile", result as string); } catch (error) { console.error("Unable to render Dockerfile", error); throw error; } }; const updateNextConfig = async () => { // get next.config.js or next.config.ts let nextConfigFile = "next.config.js"; try { await fs.access("next.config.ts"); nextConfigFile = "next.config.ts"; } catch (error) {} const nextConfig = await fs.readFile(nextConfigFile, "utf-8"); // update next.config.ts, detect const nextConfig: NextConfig = { const nextConfigMatch = nextConfig.match( /const nextConfig: NextConfig = {/ ); if (nextConfigMatch) { // find a section matching output: 'standalone' const outputMatch = nextConfig.match(/output: *('|")(.+)('|")/); const desiredOutputType = this.resourceGroupType === "fullstack-aws" ? "standalone" : "export"; if (!outputMatch) { const updatedNextConfig = nextConfig.replace( /const nextConfig: NextConfig = {/, `const nextConfig: NextConfig = {\n output: '${desiredOutputType}',\n` ); await fs.writeFile(nextConfigFile, updatedNextConfig); } else if (outputMatch[2] !== desiredOutputType) { throw new Error( `Your NextJS config 'output' property is not set to '${desiredOutputType}'.\n\n` + `Limo currently requires '${desiredOutputType}' to be set for ${this.resourceGroupType} compatibility.` ); } } }; const updateDockerignore = async () => { if (this.resourceGroupType !== "fullstack-aws") { return; } const contents = await fs.readFile(".dockerignore", "utf-8"); if (!contents.includes("node_modules")) { await fs.appendFile( ".dockerignore", ["node_modules", "npm-debug.log", ".next"].join("\n") ); } }; await Promise.all([ ensureDockerfile(), updateNextConfig(), updateDockerignore() ]); } public async destroy() { const updateNextConfig = async () => { // get next.config.js or next.config.ts let nextConfigFile = "next.config.js"; try { await fs.access("next.config.ts"); nextConfigFile = "next.config.ts"; } catch (error) {} const nextConfig = await fs.readFile(nextConfigFile, "utf-8"); // update next.config.ts, detect const nextConfig: NextConfig = { const nextConfigMatch = nextConfig.match( /const nextConfig: NextConfig = {/ ); if (nextConfigMatch) { // find a section matching output: 'standalone' const outputMatch = nextConfig.match(/output: *('|")(.+)('|")/); const desiredOutputType = this.resourceGroupType === "fullstack-aws" ? "standalone" : "export"; if (!outputMatch) { const updatedNextConfig = nextConfig.replace( /const nextConfig: NextConfig = {/, `const nextConfig: NextConfig = {\n output: '${desiredOutputType}',\n` ); await fs.writeFile(nextConfigFile, updatedNextConfig); } else if (outputMatch[2] !== desiredOutputType) { throw new Error( `Your NextJS config 'output' property is not set to '${desiredOutputType}'.\n\n` + `Limo currently requires '${desiredOutputType}' to be set for ${this.resourceGroupType} compatibility.` ); } } }; } } export default NextJsFramework;