1import * as _ from 'lodash-es' 9 push_repo_name_tag: string 10 dockerfileContent: string 11 dfDockPathsA: string[] 13 includeShaInTag?: boolean 14 getRnt: () => Promise<BakedDf> 17export type DockFile = { 19 pushLine: (line: string | BakedDf) => void 20 pushEnv: (key: string, value: string, opts?: {append?: boolean, allowOverwrite?: boolean}) => void 21 pushCpTar: (arg: {dfCpTarStr: string, dockerPathsA: string[], tarSpec?: object}) => void 22 getRnt: () => Promise<BakedDf> 25const unquoteEnvVal = (v: string) => v.trim().replace(/^(['"])((?:\\.|(?!\1).)*)\1$/, '$2') 27const renderEnvLine = (key: string, value: string) => 28 `ENV ${key}=${/\s/.test(value) ? `"${value}"` : value}` 30export const initDockFile = (initialLine?: string, {includeShaInTag}: {includeShaInTag?: boolean} = {}): DockFile => { 31 const linesA: string[] = initialLine ? [initialLine] : [] 33 let workDir: string | undefined 34 let stageEnvH: Record<string, string> = {} 35 const dfDockPathsA: string[] = [] 36 const tarSpecsA: object[] = [] 37 const pushDockPath = (path: string) => dfDockPathsA.push(path) 38 const pushTarSpec = (tarSpec: object) => tarSpecsA.push(tarSpec) 39 let frozenRez: BakedDf | undefined 41 const trackEnvLine = (line: string) => { 42 if (line.startsWith('FROM ')) { 46 const m = line.match(/^ENV ([A-Za-z_][A-Za-z0-9_]*)=(.*)$/) 48 const [, key, rawVal] = m as unknown as [string, string, string] 49 const val = unquoteEnvVal(rawVal) 50 const prev = stageEnvH[key] 51 throwIf(() => prev !== undefined && prev !== val && val !== '', {key, prevValue: prev, clobberLine: line}) 55 const pushLine = (lineStr: string | BakedDf) => { 58 if ((lineStr as BakedDf)?.isBakedDf) { 59 const df = lineStr as BakedDf 60 for (const l of df.dockerfileContent.split('\n')) trackEnvLine(l) 61 linesA.push(df.dockerfileContent) 62 for (const p of df.dfDockPathsA) pushDockPath(p) 63 for (const t of df.tarSpecsA) pushTarSpec(t) 67 const workdirMatch = lineStr.match(/WORKDIR (.+)/) 69 const [, newWorkdir] = workdirMatch 70 if (newWorkdir == workDir) return 73 for (const l of lineStr.split('\n')) trackEnvLine(l) 77 const pushEnv = (key: string, value: string, {append, allowOverwrite}: {append?: boolean, allowOverwrite?: boolean} = {}) => { 79 const prev = stageEnvH[key] 80 if (append && prev !== undefined) { 81 const flagsA = prev.split(/\s+/) 82 const newFlagsA = value.split(/\s+/).filter(f => f && !flagsA.includes(f)) 83 if (!newFlagsA.length) return 84 stageEnvH[key] = [...flagsA, ...newFlagsA].join(' ') 86 if (prev === value) return 87 throwIf(() => prev !== undefined && !allowOverwrite, {key, prevValue: prev, newValue: value}) 88 stageEnvH[key] = value 90 linesA.push(renderEnvLine(key, stageEnvH[key]!)) 92 const pushCpTar = ({dfCpTarStr, dockerPathsA, tarSpec}: {dfCpTarStr: string, dockerPathsA: string[], tarSpec?: object}) => { 94 for (const p of dockerPathsA) pushDockPath(p) 95 if (tarSpec) pushTarSpec(tarSpec) 98 const getRnt = async (): Promise<BakedDf> => { 99 if (frozenRez) return frozenRez 100 const dockerfileContent = _.chain(linesA).map(_.trim).compact().value().join("\n") 101 const {repo_name_tag, push_repo_name_tag} = await get2RepoNameTag({dockerfileContent, includeShaInTag}) 102 frozenRez = {repo_name_tag, push_repo_name_tag, dockerfileContent, dfDockPathsA, tarSpecsA, isBakedDf: true, includeShaInTag, getRnt} 106 return {pushLine, pushEnv, pushCpTar, isDockFile: true, getRnt}