Skip to content

Commit 1fcd224

Browse files
Merge pull request #46 from DBB-Software/feat/PLATFORM-1652
feat: added next config wrapper utility
2 parents 4c4543d + 3988a44 commit 1fcd224

File tree

10 files changed

+172
-174
lines changed

10 files changed

+172
-174
lines changed

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@
44
"description": "",
55
"main": "./dist/index.js",
66
"bin": {
7-
"@dbbs/next-serverless-deployment": "./dist/index.js"
8-
},
9-
"exports": {
10-
"./*": "./dist/*"
7+
"@dbbs/next-serverless-deployment": "./dist/commands/index.js"
118
},
129
"types": "dist/types/index.d.ts",
1310
"files": [

src/build/next.ts

Lines changed: 23 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import childProcess from 'node:child_process'
22
import fs from 'fs/promises'
33
import path from 'node:path'
4-
import { type ProjectPackager, type ProjectSettings, loadFile } from '../common/project'
5-
import loadConfig from '../commands/helpers/loadConfig'
6-
import appRouterRevalidate from './cache/handlers/appRouterRevalidate'
4+
import { type ProjectPackager, type ProjectSettings } from '../common/project'
5+
import appRouterRevalidateTemplate from './cache/handlers/appRouterRevalidate'
76

87
interface BuildOptions {
98
packager: ProjectPackager
@@ -21,78 +20,52 @@ interface BuildAppOptions {
2120

2221
export const OUTPUT_FOLDER = 'serverless-next'
2322

24-
const setNextOptions = async (nextConfigPath: string, s3BucketName: string): Promise<() => Promise<void>> => {
25-
// set s3 bucket name for cache handler during build time
23+
const setNextEnvs = (s3BucketName: string) => {
2624
process.env.STATIC_BUCKET_NAME = s3BucketName
27-
28-
const cacheConfig = await loadConfig()
29-
const currentConfig = await loadFile(nextConfigPath)
30-
const updatedConfig = {
31-
...currentConfig,
32-
output: 'standalone',
33-
serverRuntimeConfig: {
34-
...currentConfig.serverRuntimeConfig,
35-
nextServerlessCacheConfig: cacheConfig
36-
},
37-
cacheHandler: require.resolve(path.join('..', 'cacheHandler', 'index.js'))
38-
}
39-
40-
const currentContent = await fs.readFile(nextConfigPath, 'utf-8')
41-
42-
let updatedContent = `module.exports = ${JSON.stringify(updatedConfig, null, 4)};\n`
43-
44-
// Check if the file has .mjs extension
45-
if (nextConfigPath.endsWith('.mjs')) {
46-
updatedContent = `export default ${JSON.stringify(updatedConfig, null, 4)};\n`
47-
}
48-
49-
await fs.writeFile(nextConfigPath, updatedContent, 'utf-8')
50-
51-
// Function to revert back to original content of file
52-
return async () => {
53-
fs.writeFile(nextConfigPath, currentContent, 'utf-8')
54-
}
25+
process.env.NEXT_SERVERLESS_DEPLOYING_PHASE = 'true'
5526
}
5627

5728
const appendRevalidateApi = async (projectPath: string, isAppDir: boolean): Promise<string> => {
5829
const routeFolderPath = path.join(projectPath, isAppDir ? 'src/app' : 'src', 'api', 'revalidate')
5930
const routePath = path.join(routeFolderPath, 'route.ts')
60-
if ((await fs.stat(routeFolderPath)).isDirectory()) {
61-
await fs.mkdir(routeFolderPath, { recursive: true })
62-
}
6331

64-
fs.writeFile(routePath, appRouterRevalidate, 'utf-8')
32+
await fs.mkdir(routeFolderPath, { recursive: true })
33+
await fs.writeFile(routePath, appRouterRevalidateTemplate, 'utf-8')
6534

6635
return routePath
6736
}
6837

6938
export const buildNext = async (options: BuildOptions): Promise<() => Promise<void>> => {
70-
const { packager, nextConfigPath, s3BucketName, projectPath, isAppDir } = options
39+
const { packager, projectPath, s3BucketName, isAppDir } = options
7140

41+
setNextEnvs(s3BucketName)
7242
const revalidateRoutePath = await appendRevalidateApi(projectPath, isAppDir)
73-
const clearNextConfig = await setNextOptions(nextConfigPath, s3BucketName)
7443
childProcess.execSync(packager.buildCommand, { stdio: 'inherit' })
7544

7645
// Reverts changes to the next project
7746
return async () => {
78-
await Promise.all([clearNextConfig(), fs.rm(revalidateRoutePath)])
47+
await fs.rm(revalidateRoutePath)
7948
}
8049
}
8150

82-
const copyAssets = async (outputPath: string, appPath: string) => {
51+
const copyAssets = async (outputPath: string, appPath: string, appRelativePath: string) => {
8352
// Copying static assets (like js, css, images, .etc)
84-
await Promise.all([
85-
fs.cp(path.join(appPath, '.next', 'static'), path.join(outputPath, '_next', 'static'), { recursive: true }),
86-
fs.cp(path.join(appPath, '.next', 'standalone'), path.join(outputPath, 'server'), {
53+
await fs.cp(path.join(appPath, '.next'), path.join(outputPath, '.next'), {
54+
recursive: true
55+
})
56+
await fs.cp(
57+
path.join(appPath, '.next', 'static'),
58+
path.join(outputPath, '.next', 'standalone', appRelativePath, '.next', 'static'),
59+
{
8760
recursive: true
88-
})
89-
])
61+
}
62+
)
9063
}
9164

9265
export const buildApp = async (options: BuildAppOptions) => {
9366
const { projectSettings, outputPath, s3BucketName } = options
9467

95-
const { packager, nextConfigPath, projectPath, isAppDir } = projectSettings
68+
const { packager, nextConfigPath, projectPath, isAppDir, root, isMonorepo } = projectSettings
9669

9770
const cleanNextApp = await buildNext({
9871
packager,
@@ -102,7 +75,9 @@ export const buildApp = async (options: BuildAppOptions) => {
10275
projectPath
10376
})
10477

105-
await copyAssets(outputPath, projectPath)
78+
const appRelativePath = isMonorepo ? path.relative(root, projectPath) : ''
79+
80+
await copyAssets(outputPath, projectPath, appRelativePath)
10681

10782
return cleanNextApp
10883
}

src/build/withNextDeploy.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import type { NextConfig } from 'next/dist/server/config-shared'
2+
import path from 'node:path'
3+
import loadConfig from '../commands/helpers/loadConfig'
4+
5+
export const withNextDeploy = async (nextConfig: NextConfig): Promise<NextConfig> => {
6+
if (process.env.NEXT_SERVERLESS_DEPLOYING_PHASE === 'true') {
7+
const cacheConfig = await loadConfig()
8+
return {
9+
...nextConfig,
10+
output: 'standalone',
11+
serverRuntimeConfig: {
12+
...nextConfig.serverRuntimeConfig,
13+
nextServerlessCacheConfig: cacheConfig,
14+
staticBucketName: process.env.STATIC_BUCKET_NAME
15+
},
16+
cacheHandler: require.resolve(path.join('..', 'cacheHandler', 'index.js'))
17+
}
18+
}
19+
20+
return nextConfig
21+
}

src/cacheHandler/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@ import { S3Cache } from './strategy/s3'
55

66
const { serverRuntimeConfig } = getConfig() || {}
77
const config: CacheConfig | undefined = serverRuntimeConfig?.nextServerlessCacheConfig
8+
const staticBucketName = serverRuntimeConfig?.staticBucketName || ''
89

910
Cache.setConfig({
1011
cacheCookies: config?.cacheCookies ?? [],
1112
cacheQueries: config?.cacheQueries ?? [],
1213
noCacheMatchers: config?.noCacheRoutes ?? [],
1314
enableDeviceSplit: config?.enableDeviceSplit,
14-
cache: new S3Cache(process.env.STATIC_BUCKET_NAME!)
15+
cache: new S3Cache(staticBucketName)
1516
})
1617

1718
export default Cache

src/cacheHandler/strategy/s3.spec.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ describe('S3Cache', () => {
8585
ContentType: 'application/json'
8686
})
8787

88-
const result = await s3Cache.get(cacheKey, cacheKey, mockCacheContext)
88+
const result = await s3Cache.get(cacheKey, cacheKey)
8989
expect(result).toEqual(mockCacheEntry.value.pageData)
9090
expect(s3Cache.client.getObject).toHaveBeenCalledTimes(1)
9191
expect(s3Cache.client.getObject).toHaveBeenCalledWith({
@@ -110,7 +110,7 @@ describe('S3Cache', () => {
110110
ContentType: 'text/x-component'
111111
})
112112

113-
const result = await s3Cache.get(cacheKey, cacheKey, mockCacheContext)
113+
const result = await s3Cache.get(cacheKey, cacheKey)
114114
expect(result).toEqual(mockCacheEntry.value.pageData)
115115
expect(s3Cache.client.getObject).toHaveBeenCalledTimes(1)
116116
expect(s3Cache.client.getObject).toHaveBeenCalledWith({
@@ -135,7 +135,7 @@ describe('S3Cache', () => {
135135
ContentType: 'application/json'
136136
})
137137

138-
const result = await s3Cache.get(cacheKey, cacheKey, mockCacheContext)
138+
const result = await s3Cache.get(cacheKey, cacheKey)
139139
expect(result).toEqual(mockCacheEntry.value.pageData)
140140
expect(s3Cache.client.getObject).toHaveBeenCalledTimes(1)
141141
expect(s3Cache.client.getObject).toHaveBeenCalledWith({
@@ -144,7 +144,7 @@ describe('S3Cache', () => {
144144
})
145145

146146
await s3Cache.delete(cacheKey, cacheKey)
147-
const updatedResult = await s3Cache.get(cacheKey, cacheKey, mockCacheContext)
147+
const updatedResult = await s3Cache.get(cacheKey, cacheKey)
148148
expect(updatedResult).toBeNull()
149149
expect(s3Cache.client.deleteObjects).toHaveBeenCalledTimes(1)
150150
expect(s3Cache.client.deleteObjects).toHaveBeenNthCalledWith(1, {
@@ -163,19 +163,19 @@ describe('S3Cache', () => {
163163
const mockCacheEntryWithTags = { ...mockCacheEntry, tags: [cacheKey] }
164164
await s3Cache.set(cacheKey, cacheKey, mockCacheEntryWithTags, mockCacheContext)
165165

166-
expect(await s3Cache.get(cacheKey, cacheKey, mockCacheContext)).toEqual(mockCacheEntryWithTags.value.pageData)
166+
expect(await s3Cache.get(cacheKey, cacheKey)).toEqual(mockCacheEntryWithTags.value.pageData)
167167

168168
await s3Cache.revalidateTag(cacheKey, [])
169169

170-
expect(await s3Cache.get(cacheKey, cacheKey, mockCacheContext)).toBeNull()
170+
expect(await s3Cache.get(cacheKey, cacheKey)).toBeNull()
171171
})
172172

173173
it('should revalidate cache by path', async () => {
174174
await s3Cache.set(cacheKey, cacheKey, mockCacheEntry, mockCacheContext)
175175

176-
expect(await s3Cache.get(cacheKey, cacheKey, mockCacheContext)).toEqual(mockCacheEntry.value.pageData)
176+
expect(await s3Cache.get(cacheKey, cacheKey)).toEqual(mockCacheEntry.value.pageData)
177177

178178
await s3Cache.deleteAllByKeyMatch(cacheKey, '')
179-
expect(await s3Cache.get(cacheKey, cacheKey, mockCacheContext)).toBeNull()
179+
expect(await s3Cache.get(cacheKey, cacheKey)).toBeNull()
180180
})
181181
})

src/commands/deploy.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -168,21 +168,24 @@ export const deploy = async (config: DeployConfig) => {
168168
const versionLabel = `${OUTPUT_FOLDER}-server-v${now}`
169169

170170
fs.writeFileSync(
171-
path.join(outputPath, 'server', 'Procfile'),
171+
path.join(outputPath, '.next', 'Procfile'),
172172
`web: node ${path.join(path.relative(projectSettings.root, projectSettings.projectPath), 'server.js')}`
173173
)
174174

175-
childProcess.execSync(`cd ${path.join(outputPath, 'server')} && zip -r ../${archivedFolderName} \\.* *`, {
176-
stdio: 'inherit'
177-
})
175+
childProcess.execSync(
176+
`cd ${path.join(outputPath, '.next', 'standalone')} && zip -r ../../${archivedFolderName} \\.* *`,
177+
{
178+
stdio: 'inherit'
179+
}
180+
)
178181

179182
// prune static bucket before upload
180183
await emptyBucket(s3Client, nextRenderServerStackOutput.StaticBucketName)
181184

182185
await uploadFolderToS3(s3Client, {
183186
Bucket: nextRenderServerStackOutput.StaticBucketName,
184-
Key: '_next',
185-
folderRootPath: outputPath
187+
Key: '_next/static',
188+
folderRootPath: path.join(outputPath, '.next', 'static')
186189
})
187190

188191
// upload code version to bucket.

src/commands/index.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
#!/usr/bin/env node
2+
import yargs from 'yargs'
3+
import { hideBin } from 'yargs/helpers'
4+
import { deploy } from './deploy'
5+
import { bootstrap } from './bootstrap'
6+
7+
interface CLIOptions {
8+
siteName: string
9+
stage?: string
10+
region?: string
11+
profile?: string
12+
nodejs?: string
13+
production?: boolean
14+
renderServerInstanceType?: string
15+
renderServerMinInstances?: number
16+
renderServerMaxInstances?: number
17+
}
18+
19+
const cli = yargs(hideBin(process.argv))
20+
.scriptName('@dbbs-next')
21+
.usage('$0 <command> [options]')
22+
.option('region', {
23+
type: 'string'
24+
})
25+
.option('profile', {
26+
type: 'string'
27+
})
28+
29+
cli.command<CLIOptions>(
30+
'bootstrap',
31+
'bootsrap CDK project',
32+
() => {},
33+
async (argv) => {
34+
const { profile, region } = argv
35+
await bootstrap({ profile, region })
36+
}
37+
)
38+
39+
cli
40+
.command<CLIOptions>(
41+
'deploy',
42+
'app deployment',
43+
() => {},
44+
async (argv) => {
45+
const {
46+
siteName,
47+
stage,
48+
region,
49+
profile,
50+
nodejs,
51+
production,
52+
renderServerInstanceType,
53+
renderServerMinInstances,
54+
renderServerMaxInstances
55+
} = argv
56+
57+
await deploy({
58+
siteName,
59+
stage,
60+
nodejs,
61+
isProduction: production,
62+
renderServerInstanceType,
63+
renderServerMinInstances,
64+
renderServerMaxInstances,
65+
aws: {
66+
region,
67+
profile
68+
}
69+
})
70+
}
71+
)
72+
.option('siteName', {
73+
type: 'string',
74+
requiresArg: true,
75+
describe: 'The name is used to create CDK stack and components.'
76+
})
77+
.option('stage', {
78+
type: 'string',
79+
describe: 'The stage of the app, defaults to production'
80+
})
81+
.option('nodejs', {
82+
type: 'string'
83+
})
84+
.option('production', {
85+
type: 'boolean',
86+
description: 'Creates production stack.',
87+
default: false
88+
})
89+
.option('renderServerInstanceType', {
90+
type: 'string',
91+
describe: 'Set instance type for render server. Default is t2.micro.'
92+
})
93+
.option('renderServerMinInstances', {
94+
type: 'number',
95+
describe: 'Set min render server instances. Default is 1.'
96+
})
97+
.option('renderServerMaxInstances', {
98+
type: 'number',
99+
describe: 'Set max render server instances. Default is 2.'
100+
})
101+
102+
cli.help()
103+
cli.parse()

src/common/aws.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,17 +69,17 @@ export const uploadFileToS3 = async (s3Client: S3, options: PutObjectCommandInpu
6969

7070
export const uploadFolderToS3 = async (s3Client: S3, options: S3UploadFolderOptions) => {
7171
const { folderRootPath, Key, ...s3UploadOptions } = options
72-
const files = fs.readdirSync(path.join(folderRootPath, Key))
72+
const files = fs.readdirSync(folderRootPath)
7373

7474
for (const file of files) {
75-
const filePath = path.join(folderRootPath, Key, file)
75+
const filePath = path.join(folderRootPath, file)
7676
const s3FilePath = path.join(Key, file)
7777

7878
if (fs.lstatSync(filePath).isDirectory()) {
7979
await uploadFolderToS3(s3Client, {
8080
...s3UploadOptions,
8181
Key: s3FilePath,
82-
folderRootPath
82+
folderRootPath: filePath
8383
})
8484
} else {
8585
await uploadFileToS3(s3Client, {

0 commit comments

Comments
 (0)