1import { ptDir } from '../../serverF/ptDirF.mts'2import { calcReqHash } from '../../serverF/calcHashF.mts'3import { throPtErr } from '../../sharedF/throwErrorF/sharedErrF.mts'4import { allPromCalls } from '../../sharedF/moDashF/promF/allPromCallsF.mts'5import { write1File } from '../../serverF/libFs/write1FileF.mts'6import { fileExists } from '../../serverF/libFs/mkdirpF.mts'7import * as _ from 'lodash-es'8import { exec1FileThrow } from '../../serverF/childProcF/spawnF.mts'9import { mkdirParentP } from '../../serverF/libFs/mkdirpF.mts'10import { humanBytes } from '../../sharedF/strF/humanBytesF.mts'11import { chalkGreen, chalkYellow } from '../../serverF/libChalkF.mts'12import { filterDevOnlyPaths } from './filterDevOnlyPathsF.mts'13import { fileSizeBytes } from '../../serverF/libFs/mkdirpF.mts'14import { getDeployWpReplaceTokens } from './dockerUtilsF.mts'15import { betLog } from '../../sharedF/betterConsLogF.mts'16import { prodPatchedInfix } from '../../sharedF/isPtCodeFileExtF.mts'17import { isVeryVerbose } from '../../sharedF/isDevPcF.mts'18import { assertDefined } from '../../sharedF/assertsF/assertDefinedF.mts'19import { assertEmpty } from '../../sharedF/assertsF/assertEmptyF.mts'20import { getHeadGitSha } from '../libF/gitF/gitRepoF.mts'21import { getUncommittedInPathSet, getGitignoredInPathSet } from '../libF/gitF/gitDirtyPathsAI.mts'22import { appCfgCtx, getAppCfg} from '../k8sF/ctxF/appCfgCtxF.mts'23import { getAction } from '../ctxF/actionCtxF.mts'24import { isDeployAction } from '../testsF/actionResultsAI.mts'25import { getTarCmd } from './getTarCmdF.mts'26import { formatFullImportTree } from '../ptDeployActions/buildImportTreeAI.mts'27import path from 'path'28import { getImportMetaUrlPath } from '../../serverF/isDirectlyRunF.mts'29import { absPathToPtPath } from '../../serverF/pathF/absPathToPtPathF.mts'30import { tsAbsPath } from '../../ptDirF.mts'32export const dockerTarPaths = async ({dockerPathsA}: {dockerPathsA: string[]}) => {34 if (isDeployAction(action)) {35 const uncommittedInTar = getUncommittedInPathSet(dockerPathsA)36 assertEmpty(uncommittedInTar, {uncommittedInTar})37 }38 const wpManReplaceTokH = getDeployWpReplaceTokens() || {}40 if (isVeryVerbose) {42 }44 const patchExtRe = /(\.\w+)$/46 // TODO: uncertain if regex escaping is needed for GNU tar --transform patterns47 // Worked fine without escaping before 8629639d9b, defaulting to false48 // If deploys fail on Linux (codeserv), try setting this to true49 const escapePatternForGnuTar = false50 const maybeEscape = (str: string) => escapePatternForGnuTar 51 ? str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') 52 : str54 const getCmdA = (tarFilePtPath: string) => {55 assertDefined(tarFilePtPath)56 return [59 '--transform', `s#${maybeEscape(prodPatchedInfix)}##`,60 ..._.chain(wpManReplaceTokH).map((toStr, fromStr) => {61 return ['--transform', `s#${maybeEscape(fromStr)}#${toStr}#`]62 }).flatten().value(),63 ]64 }66 const startAt = _.now()68 const pathsGitSha = await getHeadGitSha({filterPathList: dockerPathsA})69 const gitSliceSha = calcReqHash([70 pathsGitSha,71 ...getCmdA('calcHash'),72 ..._.sortBy(dockerPathsA),73 '1',74 ])76 const gitignoredPaths = getGitignoredInPathSet(dockerPathsA)77 if (gitignoredPaths.length) {78 const gitignoredSet = new Set(gitignoredPaths)79 dockerPathsA = dockerPathsA.filter(p => !gitignoredSet.has(p))80 }82 const {pathsThatDontImportDevA, pathsThatImportDevDecA} = await filterDevOnlyPaths(dockerPathsA as import('../../ptDirF.mts').absFileDirPath[])83 const patchedFilesH: Record<string, string> = {}84 const patchPathsA = await allPromCalls(pathsThatImportDevDecA, async ({path, newContents}: {path: string, newContents: string}) => {85 const newPath = path.replace(patchExtRe, prodPatchedInfix + '$1')86 patchedFilesH[newPath] = newContents87 await write1File(tsAbsPath(newPath), newContents)88 return newPath89 })90 dockerPathsA = [...pathsThatDontImportDevA, ...patchPathsA]92 // Merge caller-provided generated file content (for kaniko prebuild)94 if (inclContentH) {95 for (const [ptPath, content] of Object.entries(inclContentH as Record<string, string>)) {96 patchedFilesH[ptPath] = content97 await write1File(tsAbsPath(ptPath), content)98 if (!dockerPathsA.includes(ptPath)) dockerPathsA.push(ptPath)99 }100 }102 const tarFileFileName = `ptdeploy2slice-${dockerPathsA.length}cnt-${gitSliceSha}.tar.gz`103 const tarFilePtPath = `tmpdockertars/${tarFileFileName}`105 const msStr = `${_.now() - startAt}ms`106 if (await fileExists(tsAbsPath(tarFilePtPath))) {107 if (isVeryVerbose) {108 console.log(chalkGreen('cacheHit docker tar!'), msStr, tarFilePtPath)109 }110 } else {111 mkdirParentP(tarFilePtPath)112 const cmdA = getCmdA(tarFilePtPath)113 if (isVeryVerbose) {114 console.log(chalkYellow('writing docker tar..'), msStr, tarFilePtPath)115 }116 await exec1FileThrow([117 ...cmdA,118 ...dockerPathsA,119 ], {env: {120 ...process.env,121 GZIP: '-n',122 }})123 }125 // Build tree-structured comments showing import relationships127 const epLabel = deployEp || (importMetaUrl && path.basename(absPathToPtPath(tsAbsPath(getImportMetaUrlPath(importMetaUrl))))) || 'unknown'129 const commentLinesA = (madgeH && madgeEpPtPathsA)130 ? formatFullImportTree({madgeH, epA: madgeEpPtPathsA, epLabel, pathsA: dockerPathsA, maxLines: dockerPathsA.length + 5})131 : _.map(dockerPathsA, (p) => `# ${p}`)133 const dfCpTarStr = [134 ..._.map(commentLinesA, line => line.startsWith('#') ? line : `# ${line}`),135 `ADD ${tarFilePtPath} .`,136 `#^ ${humanBytes(await fileSizeBytes(tsAbsPath(tarFilePtPath)))}`,137 ].join("\n") + "\n"139 // tarSpec for kaniko prebuild - contains everything needed to regenerate the tar140 const tarSpec = {141 tarPath: tarFilePtPath,142 filesA: dockerPathsA,143 patchedFilesH: _.isEmpty(patchedFilesH) ? undefined : patchedFilesH,144 wpManReplaceTokH: _.isEmpty(wpManReplaceTokH) ? undefined : wpManReplaceTokH,145 }147 return {dfCpTarStr, dockerPathsA, tarSpec}148}