From bc9694b095417faec860f6bbfd405bf851df388c Mon Sep 17 00:00:00 2001 From: Marcelo Luiz Onhate Date: Thu, 4 Jan 2024 13:29:09 -0300 Subject: [PATCH] fix: destroy with no bundling (#193) * fix: destroy with no bundling * updated docs * yarn build --- docs/code-deployment-flow.md | 18 +++++++++++++ src/NextjsBuild.ts | 52 ++++++++++++++++++++++++++++++++---- 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/docs/code-deployment-flow.md b/docs/code-deployment-flow.md index 51dececc..1cbfafe6 100644 --- a/docs/code-deployment-flow.md +++ b/docs/code-deployment-flow.md @@ -14,6 +14,24 @@ Deep dive into `Nextjs` constructs code deployment flow - how your Next.js code _Only applicable for PNPM Monorepos_ PNPM Monorepos use symlinks between workspace node_modules and the top level node_modules. CDK Assets do not support symlinks despite the configuration options available. Therefore, we must zip up the assets ourselves. Also, `nextjs-bucket-deployment.ts` handles symlinks to unzip and zip symlinks within Lambda Custom Resources (for ServerFnBucketDeployment). + +## Conditional Build Logic + +`NextjjsBuild` will use the following logic to determine if a build is required or not to proceed. + +``` +| bundlingRequired | skipBuild | Scenario | Action | +|------------------|-----------|---------------------------------|---------------------------------------------------| +| true | true | deploy/synth with reused bundle | no build, check .open-next exists, fail if not | +| true | false | regular deploy/synth | build, .open-next will exist | +| false | false | destroy | no build, check if .open-next exists, if not mock | +| false | true | destroy with reused bundle | no build, check if .open-next exists, if not mock | +``` + +*bundlingRequired* = `Stack.of(this).bundlingRequired` [see](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.Stack.html#bundlingrequired) +*skipBuild* = `NextjsProps.skipBuild` + + Relevant GitHub Issues: - https://github.com/aws/aws-cdk/issues/9251 - https://github.com/Stuk/jszip/issues/386#issuecomment-634773343 diff --git a/src/NextjsBuild.ts b/src/NextjsBuild.ts index 85c5491e..10e7cbf3 100644 --- a/src/NextjsBuild.ts +++ b/src/NextjsBuild.ts @@ -5,12 +5,12 @@ import { Stack, Token } from 'aws-cdk-lib'; import { Construct } from 'constructs'; import { NEXTJS_BUILD_DIR, + NEXTJS_BUILD_DYNAMODB_PROVIDER_FN_DIR, NEXTJS_BUILD_IMAGE_FN_DIR, NEXTJS_BUILD_REVALIDATE_FN_DIR, NEXTJS_BUILD_SERVER_FN_DIR, - NEXTJS_STATIC_DIR, NEXTJS_CACHE_DIR, - NEXTJS_BUILD_DYNAMODB_PROVIDER_FN_DIR, + NEXTJS_STATIC_DIR, } from './constants'; import type { NextjsProps } from './Nextjs'; import { NextjsBucketDeployment } from './NextjsBucketDeployment'; @@ -103,9 +103,21 @@ export class NextjsBuild extends Construct { super(scope, id); this.props = props; this.validatePaths(); - // when `cdk deploy "NonNextjsStack" --exclusively` is run, don't run build - if (Stack.of(this).bundlingRequired && !this.props.skipBuild) { - this.build(); + + const bundlingRequired = Stack.of(this).bundlingRequired; + const skipBuild = this.props.skipBuild; + + // for more info see docs/code-deployment-flow.md Conditional Build Logic section + if (bundlingRequired) { + // deploy/synth + if (skipBuild) { + this.assertBuildDirExists(true); + } else { + this.build(); + } + } else { + // destroy + this.mockNextBuildDir(); } } @@ -171,6 +183,17 @@ export class NextjsBuild extends Construct { return listDirectory(this.nextStaticDir).map((file) => path.join('/', path.relative(this.nextStaticDir, file))); } + private assertBuildDirExists(throwIfMissing = true) { + const dir = this.getNextBuildDir(); + if (!fs.existsSync(dir)) { + if (throwIfMissing) { + throw new Error(`Build directory "${dir}" does not exist. Try removing skipBuild: true option.`); + } + return false; + } + return true; + } + private getNextBuildDir(): string { const dir = path.resolve(this.props.nextjsPath, NEXTJS_BUILD_DIR); this.warnIfMissing(dir); @@ -182,4 +205,23 @@ export class NextjsBuild extends Construct { console.warn(`Warning: ${dir} does not exist.`); } } + + private mockNextBuildDir() { + function createMockDirAndFile(dir: string) { + fs.mkdirSync(dir, { recursive: true }); + fs.writeFileSync(path.join(dir, 'package.json'), '{}', 'utf8'); + } + + const buildDirExists = this.assertBuildDirExists(false); + if (!buildDirExists) { + // mock .open-next + createMockDirAndFile(this.getNextBuildDir()); + createMockDirAndFile(this.nextServerFnDir); + createMockDirAndFile(this.nextImageFnDir); + createMockDirAndFile(this.nextRevalidateFnDir); + createMockDirAndFile(this.nextRevalidateDynamoDBProviderFnDir); + createMockDirAndFile(this.nextStaticDir); + createMockDirAndFile(this.nextCacheDir); + } + } }