🌳
pt0/deployF/eptMjsRunnerF.mts
1import { ptDir } from '../serverF/ptDirF.mts'
2import { setEnvConf, type envConfType } from '../serverF/envConfF.mts'
5import { klusterCtx, type KlusterCfg } from './k8sF/ctxF/klusterCtxF.mts'
10import { appCfgCtx, type AppCfg } from './k8sF/ctxF/appCfgCtxF.mts'
12import * as _ from 'lodash-es'
13import path from 'path'
18import { cliHelp } from './cliF/cliHelpF.mts'
41import { sleep } from '../sharedF/sleepF.mts'
46import type { absFileDirPath } from '../ptDirF.mts'
47import type { DockFile } from './dockerF/dockFileF.mts'
50export { ciBasePrefix }
52export const eptMjsRunner = async (appConfig: AppCfg & Record<string, unknown>) => {
53 let {
54 extraDockerSetupLine, fromImgName,
55 inclPtPathA = [], extraActions,
56 } = appConfig as Record<string, any>
57 const {importMetaUrl, nameNoCiBase} = appConfig as Record<string, any>
58 const action = appConfig.action || getAction()
60 appConfig = defaultAppCfg({inclServer: false, ...appConfig})
62 if (isDeployAction(action!)) deriveDbNodePorts(appConfig)
64 appConfig.jobType ||= {kind: 'Deployment'}
66 assertDefined(importMetaUrl)
68 const {dockreg_host, cluster_name, jobType, aptPkgNames, secretNames, appPath} = appConfig as Record<string, any>
69 appCfgCtx.enterWith(appConfig); klusterCtx.enterWith(appConfig as KlusterCfg)
70 ptDeployActionsCtx.enterWith({...dockerActions, zipsecrets, exceptions, ...extraActions})
71 if (extraActions) {
72 availActionsCtx.enterWith({...availActionsCtx.getStore(), ...extraActions})
73 }
75 const jobName = nameNoCiBase || `${ciBasePrefix}${appConfig.name}`
77 const actionsH = {...resourcesActionsH(), ...getDeployActions()} as Record<string, any>
78 const actionNames = Object.keys(actionsH)
79 const wantsHelp = cliFlag('--help', '-h')
80 if (!actionNames.includes(action!) || wantsHelp) {
81 cliHelp({action, actionsH})
82 return
83 }
85 if (action === 'apply' && cliFlag('--history')) {
86 await applyHistoryGuard(importMetaUrl)
87 return
88 }
90 const runFileAbsPath = (appConfig as Record<string, any>).runFilePath
91 if (!runFileAbsPath) throPtErr('!runFilePath', {importMetaUrl})
92 if (!await fileExists(runFileAbsPath as absFileDirPath)) {
93 throPtErr('!fileExists', {runFileAbsPath, importMetaUrl})
94 }
96 const epPtPathsA = [absPathToPtPath(runFileAbsPath as absFileDirPath), ...inclPtPathA as string[]]
97 const nextConfigPtPath = `${appPath}/next.config.mjs`
98 if (await fileExists(`${ptDir}/${nextConfigPtPath}` as absFileDirPath)) {
99 epPtPathsA.push(nextConfigPtPath)
100 }
101 Object.assign(appConfig, {epPtPathsA, usesExplicitEpPtPaths: true})
103 const git_sha = await getHeadGitSha()
105 Object.assign(appConfig, {git_sha})
106 appCfgCtx.enterWith(appConfig)
108 const dockerEnv = {...getDefaultEnv(), NODE_ENV: (appConfig.envConf as Record<string, any>)?.nextIsDev ? 'development' : 'production'}
110 if (deployActionGensDf({action})) {
111 const bundle = !!appConfig.bundle
112 const nativeInstallPackages = appConfig.nativeInstallPackages || []
113 const afterYarnInstallDockPathsA = bundle ? [] : await epsToPathsGitCached({epPtPathsA})
114 const dockFile = await getDockerBaseStr({
115 afterYarnInstallDockPathsA,
116 appPath, fromImgName,
117 extraDockerSetupLine, aptPkgNames,
118 ...(bundle && {bundle, entrypointAbsPath: runFileAbsPath as string, bundleName: appConfig.name, nativeInstallPackages}),
119 })
120 dockFile.pushLine(getEnvLines({dockerEnv}))
122 Object.assign(appConfig, {dockerFilesA: dockFile})
123 }
125 setEnvConf(appConfig.envConf as envConfType)
126 if (isDeployAction(action!)) await guardPendingMigrations({appCfg: appConfig as Record<string, any>})
128 if (isApplyishAction(action)) {
130 printActionEta(importMetaUrl, action)
131 const {dockerFilesA} = appConfig
132 await chainPromiseCalls(_.castArray(dockerFilesA) as DockFile[], async (dockFile) => {
133 await dockerBuildAndRet({dfToBuild: dockFile, dfLabel: nameNoCiBase || 'mjsrunner'})
134 })
135 }
137 let {envConf} = appConfig
138 envConf = buildEnvConf({envConf: envConf as Record<string, string>, action: action!, cluster_name: cluster_name as string, name: appConfig.name!, importMetaUrl: importMetaUrl as string})
139 Object.assign(appConfig, {envConf})
141 const appdir = `${dockerApproot}/${appConfig.appPath}`
142 const taskCmd = appConfig.bundle
143 ? `date && cd ${dockerApproot} && node bundle.mjs runn`
144 : `date && cd ${appdir} && node ${
145 path.relative(appdir, replaceStrict(runFileAbsPath as string, ptDir, dockerApproot))
146 } runn`
148 const {image} = await imageFromAppCfg(appConfig)
149 const {getVolMntsForPvc} = appConfig as Record<string, any>
150 const resourceTmpl = await mkJobDeployment({
151 appConfig, envLocal: {...dockerEnv, ...((appConfig as Record<string, any>).downEnv || {})},
152 name: jobName, cluster_name: cluster_name as string, image: image!,
153 envConf: envConf as Record<string, string>, dockreg_host, git_sha, taskCmd, jobType,
154 secretNames, action,
155 getVolMntsForPvc,
156 })
158 Object.assign(appConfig, {resourceTmpl})
160 appConfig = await decAppCfgResGitSha({appConfig})
162 appCfgCtx.enterWith(appConfig)
163 const startTime = Date.now()
164 if (action !== 'testdeploy') await doPtDeployAction()
165 if (action === 'apply') {
166 recordApplyTiming({ep: importMetaUrl, startTime, gitSha: appConfig.git_sha!})
167 }
168 if (action === 'testdeploy') {
169 const applySec = elapsedSec(startTime)
170 console.log('waiting 30s for pod stability...')
171 const t2 = Date.now()
172 await sleep(30_000)
173 const health = await checkPodsHealthy({name: jobName, cluster_name: cluster_name as string})
174 if (!health.healthy) {
175 console.error(chalkRed(`pod health check failed: ${health.message}`))
176 process.exit(1)
177 }
178 const phases = {apply: applySec, healthCheck: elapsedSec(t2)}
179 recordActionResult({ep: toPtRelPath(importMetaUrl), action: 'testdeploy', ts: new Date().toISOString(), durationSec: elapsedSec(startTime), success: true, gitSha: appConfig.git_sha!, phases, ...(currentBuilder() !== 'docker' && {builder: currentBuilder()})})
180 console.log(chalkGreen('testdeploy complete — pod healthy'))
181 }