Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions packages/js-sdk/src/template/dockerfileParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,22 @@ function handleUserInstruction(
}
}

/**
* Strip surrounding double or single quotes from an ENV value.
* Docker's ENV instruction strips quotes from values like ENV KEY="value".
*/
function stripQuotes(value: string): string {
if (value.length >= 2) {
if (
(value[0] === '"' && value[value.length - 1] === '"') ||
(value[0] === "'" && value[value.length - 1] === "'")
) {
return value.slice(1, -1)
}
}
return value
}

function handleEnvInstruction(
instruction: DockerfileInstruction,
templateBuilder: DockerfileParserInterface
Expand All @@ -243,13 +259,13 @@ function handleEnvInstruction(
const equalIndex = envString.indexOf('=')
if (equalIndex > 0) {
const key = envString.substring(0, equalIndex)
const value = envString.substring(equalIndex + 1)
const value = stripQuotes(envString.substring(equalIndex + 1))
envVars[key] = value
}
}
} else {
// Traditional ENV key value format
envVars[firstArg] = secondArg
envVars[firstArg] = stripQuotes(secondArg)
}
} else if (argumentsData.length === 1) {
// ENV/ARG key=value format (single argument) or ARG key (without default)
Expand All @@ -259,7 +275,7 @@ function handleEnvInstruction(
const equalIndex = envString.indexOf('=')
if (equalIndex > 0) {
const key = envString.substring(0, equalIndex)
const value = envString.substring(equalIndex + 1)
const value = stripQuotes(envString.substring(equalIndex + 1))
envVars[key] = value
} else if (keyword === 'ARG' && envString.trim()) {
// ARG without default value - set as empty ENV
Expand All @@ -273,7 +289,7 @@ function handleEnvInstruction(
const equalIndex = envString.indexOf('=')
if (equalIndex > 0) {
const key = envString.substring(0, equalIndex)
const value = envString.substring(equalIndex + 1)
const value = stripQuotes(envString.substring(equalIndex + 1))
envVars[key] = value
} else if (keyword === 'ARG') {
// ARG without default value
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { describe, it, assert } from 'vitest'
import { Template } from '../../../src'
import { InstructionType } from '../../../src/template/types'

/**
* Helper to extract ENV instructions from a parsed Dockerfile.
* Returns an array of Record<string, string> — one per ENV instruction.
*/
function getEnvs(dockerfileContent: string): Record<string, string>[] {
const template = Template().fromDockerfile(dockerfileContent)
// @ts-expect-error - instructions is not a property of TemplateBuilder
const instructions = template.instructions as {
type: InstructionType
args: string[]
}[]
const envInstructions = instructions.filter(
(i) => i.type === InstructionType.ENV
)
return envInstructions.map((inst) => {
const result: Record<string, string> = {}
for (let i = 0; i < inst.args.length; i += 2) {
result[inst.args[i]] = inst.args[i + 1]
}
return result
})
}

describe('dockerfileParser ENV handling', () => {
describe('quote stripping', () => {
it('strips double quotes from ENV values', () => {
const envs = getEnvs('FROM node:24\nENV GOPATH="/go"')
assert.deepEqual(envs, [{ GOPATH: '/go' }])
})

it('strips single quotes from ENV values', () => {
const envs = getEnvs("FROM node:24\nENV GOPATH='/go'")
assert.deepEqual(envs, [{ GOPATH: '/go' }])
})

it('does not strip mismatched quotes', () => {
const envs = getEnvs('FROM node:24\nENV GOPATH="/go\'')
assert.deepEqual(envs, [{ GOPATH: '"/go\'' }])
})

it('handles unquoted values', () => {
const envs = getEnvs('FROM node:24\nENV GOPATH=/go')
assert.deepEqual(envs, [{ GOPATH: '/go' }])
})

it('preserves variable references as-is (expansion done by backend)', () => {
const envs = getEnvs(
'FROM node:24\nENV GOPATH=/go\nENV PATH="/usr/bin:${GOPATH}/bin"'
)
assert.deepEqual(envs, [
{ GOPATH: '/go' },
{ PATH: '/usr/bin:${GOPATH}/bin' },
])
})

it('strips quotes from multiple key=value pairs', () => {
const envs = getEnvs('FROM node:24\nENV A="hello" B="world"')
assert.deepEqual(envs, [{ A: 'hello', B: 'world' }])
})
})

describe('ARG handling', () => {
it('ARG with default value', () => {
const envs = getEnvs('FROM node:24\nARG MY_ARG="hello"')
assert.deepEqual(envs, [{ MY_ARG: 'hello' }])
})

it('ARG without default sets empty value', () => {
const envs = getEnvs('FROM node:24\nARG MY_ARG')
assert.deepEqual(envs, [{ MY_ARG: '' }])
})
})
})