fix: skip Docker Compose $$ escaped variables in Anatomy parser (#1450)

The Anatomy view's interpolation regex did not skip Compose's $$ escape
syntax, so $${VAR} (a literal, not a reference) was incorrectly flagged as
a missing variable. Add the (?<!\$) negative lookbehind that the backend
parser already uses, matching its behaviour.
This commit is contained in:
Anso
2026-06-25 13:48:41 -04:00
committed by GitHub
parent 7982251dc6
commit ba1be3cc4e
2 changed files with 25 additions and 1 deletions
+23
View File
@@ -56,6 +56,17 @@ describe('parseAnatomy', () => {
expect(a.referencedVars).not.toContain('HAS');
});
it('skips the $${ESCAPED} literal (Compose escape)', () => {
const a = parseAnatomy('services:\n app:\n image: x\n labels:\n - template=$${ESCAPED}\n')!;
expect(a.referencedVars).not.toContain('ESCAPED');
});
it('skips $$ escaped vars but still catches real interpolations', () => {
const a = parseAnatomy('services:\n app:\n image: x\n environment:\n - A=${REAL}\n - B=$${ESCAPED}\n')!;
expect(a.referencedVars).toContain('REAL');
expect(a.referencedVars).not.toContain('ESCAPED');
});
it('marks container-only short-form ports as not published', () => {
const a = parseAnatomy('services:\n web:\n image: x\n ports:\n - "80"\n')!;
expect(a.ports.web).toEqual([{ host: '80', container: '80', proto: 'tcp', published: false }]);
@@ -143,6 +154,18 @@ describe('assembleAnatomyInput', () => {
expect(input.gitSource).toBe('github.com/acme/plex#main');
});
it('excludes $$ escaped vars from missingVars while real refs still appear', () => {
const input = assembleAnatomyInput({
stackName: 'test',
content: 'services:\n app:\n image: x\n labels:\n - template=$${ESCAPED}\n environment:\n - TOKEN=${REAL}\n',
envContent: '',
selectedEnvFile: null,
gitSource: null,
})!;
expect(input.missingVars).toContain('REAL');
expect(input.missingVars).not.toContain('ESCAPED');
});
it('returns null when compose cannot be parsed', () => {
expect(assembleAnatomyInput({
stackName: 's', content: '', envContent: '', selectedEnvFile: null, gitSource: null,
+2 -1
View File
@@ -28,8 +28,9 @@ export interface Anatomy {
}
// Matches ${VAR}, ${VAR:-default}, ${VAR-default}, ${VAR:?err}, ${VAR?err}.
// The leading (?<!\$) skips Compose's `$${VAR}` escape (a literal, not a ref).
// Capture group 1 is the variable name, group 2 (optional) is the modifier form.
const INTERPOLATION_REGEX = /\$\{([A-Za-z_][A-Za-z0-9_]*)(?:(:?[-?])[^}]*)?\}/g;
const INTERPOLATION_REGEX = /(?<!\$)\$\{([A-Za-z_][A-Za-z0-9_]*)(?:(:?[-?])[^}]*)?\}/g;
function parsePortMapping(raw: unknown): PortRow | null {
if (typeof raw === 'string') {