🌳
pt0/nextF/deployF/syncBuildDeployNextAppF.mts
7import { tsAbsPath, type absFileDirPath } from '../../ptDirF.mts'
8import * as _ from 'lodash-es'
15import fs from 'fs'
43import * as devActions from './devActionsF.mts'
58const doSetEnvLocal = ({envLocal, envConf}: {envLocal: Record<string, string>, envConf: Record<string, unknown>}) => {
59 const envLocalPath = [process.cwd(), '.env.local'].join('/')
61 const envLocalS = toEnvLocal(envLocal)
63 console.log({envLocal, envConf})
65 fs.writeFileSync(envLocalPath, envLocalS)
68// IMPORTANT: All appConfig mutations must happen BEFORE nextDeployCtx.enterWith
69// because enterWith spreads a snapshot. Later mutations to appConfig won't
70// be visible to code reading from getReqNextDeployCtx().
71export const syncBuildDeployNextApp = async ({...appConfig}) => {
72 appConfig.action ??= getAction() // backward compat: inject action if not in config
73 if (isDeployAction(appConfig.action)) deriveDbNodePorts(appConfig)
75 // Derive cfProxied before entering context (needed for docker build env)
76 if (_.isUndefined(appConfig.cfProxied)) {
77 const hostname = appConfig.nonWcHostname || appConfig.kube_extHostname
78 if (hostname) appConfig.cfProxied = getCfProxied(hostname)
79 }
81 nextDeployCtx.enterWith({...appConfig})
83 let {
84 appPath, name, action, cluster_name,
85 envConf={}, envLocal, dockerEnv, prebuildEnvConfH,
86 cfProxied, dbQsCfg,
87 } = appConfig
89 if (action === 'apply') {
90 const cli = parseCli(applyCli)
91 if (cli.history) {
92 const statsRows = appConfig.deployHistoryStatsF ? await appConfig.deployHistoryStatsF({ep: appConfig.importMetaUrl, appConfig}) : undefined
93 await applyHistoryGuard(appConfig.importMetaUrl, statsRows, appConfig.deployHistoryStatsLabel)
94 return appConfig
95 }
97 }
98 const isDevAction = getIsDevAction({action})
99 if ((!isDevAction || isOcWeb) && name && cluster_name) {
100 envConf = buildEnvConf({envConf, action, cluster_name, name, importMetaUrl: appConfig.importMetaUrl})
101 }
103 const {gpIsProd} = dbQsCfg || {}
104 if (gpIsProd) await guardProdAction({gpIsProd, action, name})
105 let git_sha = 'devvv'
106 if (!isDevAction && appPath) {
107 git_sha = await getHeadGitSha()
108 }
109 mutateAppCfg(appConfig, {git_sha, getVirtualPathsFnc: () => nextAppPaths({inclServer: true, inclClient: true})})
111 const nextIsDev = _.includes(['setprocenv', 'devserver'], action)
112 if (nextIsDev) {
113 prebuildEnvConfH.nextCfgH ||= {}
114 }
116 if (appConfig.extraActions) {
117 ptDeployActionsCtx.enterWith({...ptDeployActionsCtx.getStore(), ...appConfig.extraActions})
118 availActionsCtx.enterWith({...availActionsCtx.getStore(), ...appConfig.extraActions})
119 }
120 appCfgCtx.enterWith(appConfig)
121 // Handle extraActions (rotatewchn, etc) early before docker setup
122 // Only dispatch if action came from argv (not overridden by caller like setDevPcEnv)
123 if (action === getAction() && await tryDispatchAction()) return appConfig
124 const defaultEnv = getDefaultEnv()
125 Object.assign(defaultEnv, {
126 NODE_ENV: nextIsDev ? 'development' : 'production' // necessary fix for next.js, not .mjs?
127 })
129 if (cfProxied) envConf = {...envConf, cfProxied}
130 const {svcPortNo=3000} = appConfig
131 envConf = {...envConf, nextSvcPortNo: svcPortNo, nextIsDev}
133 dockerEnv = _.merge(defaultEnv, dockerEnv)
134 envLocal = _.merge(defaultEnv, envLocal)
136 doSetProcessEnv({envLocal, envConf})
137 mutateAppCfg(appConfig, {envConf, envLocal})
139 if (isDeployAction(action)) await guardPendingMigrations({appCfg: appConfig})
141 const needsDocker = deployActionGensDf({action})
142 const needsLazyDocker = deployActionNeedsLazyDocker({action})
144 if (needsDocker || needsLazyDocker) {
146 const needsSwBuild = deployActionBuildsDf({action})
147 if (needsSwBuild) {
148 const {builtSwFilename, builtWorkerFilename} = getPubEnv()
149 if (builtSwFilename) {
150 await doBuildSw()
151 }
152 if (builtWorkerFilename) {
154 const workerPtPath = `${appPath}/public/${builtWorkerFilename}`
155 const workerAbsPath = `${ptDir}/${workerPtPath}`
156 if (fs.existsSync(workerAbsPath)) {
157 const workerContent = fs.readFileSync(workerAbsPath, 'utf8')
158 appConfig.inclContentH ??= {}
159 appConfig.inclContentH[workerPtPath] = workerContent
160 }
161 }
162 }
164 const epPtPathsA = await nextAppPaths()
166 mutateAppCfg(appConfig, {epPtPathsA})
167 const afterYarnInstallDockPathsA = await epsToPathsGitCached({epPtPathsA})
168 const buildSha = isDevAction ? 'devvv' : await getHeadGitSha({filterPathList: afterYarnInstallDockPathsA})
169 mutateAppCfg(appConfig, {buildSha})
171 const {getBoundaryDir} = getDeployNextCtx()
172 const clientDockPaths = _.filter(afterYarnInstallDockPathsA, (path) => ['componentsF', 'sharedF'].includes(getBoundaryDir(path)))
174 // pubEnvKeyA computation - needed for apply action
175 const computePubEnvKeyA = async () => {
176 const {transTokensHToPlain, transTokenFromPlain} = getDeployNextCtx()
177 const {envConf: {gpMaybePubEnvH}} = appConfig
178 const maybePubEnvH = transTokensHToPlain(gpMaybePubEnvH)
179 const seenTokensByPathH = _.chain(await allPromCalls(clientDockPaths, async (path: string) => {
180 const contents = await read1File(tsAbsPath(ptDir + '/' + path))
181 const foundPubEnvKeysA = _.chain(maybePubEnvH).keys().filter((pubEnvKey) => {
182 return _.includes(contents, pubEnvKey)
183 }).value()
184 if (foundPubEnvKeysA.length == 0) return
185 return [path, foundPubEnvKeysA]
186 })).compact().fromPairs().value()
187 const pubEnvKeyA = _.chain(seenTokensByPathH).values().flatten().uniq().map((orig) => {
188 return transTokenFromPlain({orig, prebuildEnvConfH})
189 }).value()
190 Object.assign(appConfig.envConf, {pubEnvKeyA})
191 }
193 if (needsDocker && !needsLazyDocker) {
194 // immediate build
195 mutateAppCfg(appConfig, {dockerFilesA: await buildNextDf({action, dockerEnv, git_sha, buildSha, afterYarnInstallDockPathsA: afterYarnInstallDockPathsA as absFileDirPath[]})})
196 } else {
197 // lazy build - store builder function, invoked by imageFromAppCfg when needed
198 const dockerBuilder = async () => {
199 return await buildNextDf({action: 'apply', dockerEnv, git_sha, buildSha, afterYarnInstallDockPathsA: afterYarnInstallDockPathsA as absFileDirPath[]})
200 }
201 mutateAppCfg(appConfig, {dockerBuilder})
202 }
203 if (!isDevAction) await computePubEnvKeyA()
204 }
206 if (action == 'setprocenv') {
207 return appConfig
208 }
210 if (action == 'setenv') {
211 doSetEnvLocal({envLocal, envConf})
212 return appConfig
213 }
215 mutateAppCfg(appConfig, {resourceTmpl: generic3DeploymentTmpl({name})})
216 appConfig = await decAppCfgResGitSha({appConfig})
218 nextContCtx.enterWith({...nextContCtx.getStore(),
219 name: 'nextcont',
220 livenessProbe: {
221 initialDelaySeconds: 3,
222 periodSeconds: 3,
223 timeoutSeconds: 5,
224 failureThreshold: 5,
225 httpGet: {
226 path: '/health/liveness',
228 }
229 },
230 })
231 // composed action (extraActions) owns ordering: its testlocal phase must run before any docker build
232 if (action === getAction() && getDeployActions()[action] && await doPtDeployAction()) return appConfig
233 await applyAndWaitRollout(appConfig)
235 if (resourceActionWins(action)) {
236 return appConfig
237 }
239 appCfgCtx.enterWith(appConfig)
240 if (await doPtDeployAction()) return appConfig
242 if (getIsDevAction({action})) {
244 return appConfig
245 }
247 const actionsH = {...resourcesActionsH(), ...module1ToObj(getDeployActions()), ...devActions}
248 const actionNames = [
250 ...devActionNames,
251 'help',
253 ]
254 k8sCli({actionNames: reorderActionNames(actionNames, actionsH as any), actionsH: actionsH as any})
256 return appConfig