Skip to content

Commit 39c005a

Browse files
committed
Fix JSON format to send raw changeset content with variable injection
1 parent 729bd4d commit 39c005a

3 files changed

Lines changed: 137 additions & 32 deletions

File tree

dist/index.js

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29848,6 +29848,27 @@ function injectYamlVariables(content, changesetVariables) {
2984829848
return yaml.dump(doc, { lineWidth: -1, noRefs: true });
2984929849
}
2985029850

29851+
function injectJsonVariables(content, changesetVariables) {
29852+
const doc = JSON.parse(content);
29853+
29854+
// Build new variable entries from the provided changeset variables
29855+
const injectedVars = Object.entries(changesetVariables).map(([name, value]) => ({
29856+
environment: null,
29857+
mask_value: true,
29858+
name,
29859+
value,
29860+
}));
29861+
29862+
// Remove existing entries for variables we're overriding
29863+
const overrideNames = new Set(Object.keys(changesetVariables));
29864+
const existingVars = Array.isArray(doc.variable) ? doc.variable : [];
29865+
const keptVars = existingVars.filter(v => !overrideNames.has(v.name));
29866+
29867+
doc.variable = [...keptVars, ...injectedVars];
29868+
29869+
return JSON.stringify(doc, null, 2);
29870+
}
29871+
2985129872
function getFileFormat(filePath) {
2985229873
const ext = path.extname(filePath).toLowerCase();
2985329874
if (ext === '.json') return 'json';
@@ -29901,11 +29922,7 @@ async function validateFile(filePath, options) {
2990129922
let body, contentType;
2990229923
if (format === 'json') {
2990329924
contentType = 'application/json';
29904-
const requestPayload = {
29905-
changeset: content,
29906-
...(changesetVariables && { variables: changesetVariables })
29907-
};
29908-
body = JSON.stringify(requestPayload);
29925+
body = changesetVariables ? injectJsonVariables(content, changesetVariables) : content;
2990929926
} else {
2991029927
contentType = 'application/yaml';
2991129928
body = changesetVariables ? injectYamlVariables(content, changesetVariables) : content;
@@ -29998,11 +30015,7 @@ async function executeFile(filePath, options) {
2999830015
let body, contentType;
2999930016
if (format === 'json') {
3000030017
contentType = 'application/json';
30001-
const requestPayload = {
30002-
changeset: content,
30003-
...(changesetVariables && { variables: changesetVariables })
30004-
};
30005-
body = JSON.stringify(requestPayload);
30018+
body = changesetVariables ? injectJsonVariables(content, changesetVariables) : content;
3000630019
} else {
3000730020
contentType = 'application/yaml';
3000830021
body = changesetVariables ? injectYamlVariables(content, changesetVariables) : content;
@@ -30316,7 +30329,7 @@ async function run() {
3031630329
}
3031730330
}
3031830331

30319-
module.exports = { run, pollTask, buildUrl, isGlobPattern, resolveFiles, worstStatus, getFileFormat, injectYamlVariables };
30332+
module.exports = { run, pollTask, buildUrl, isGlobPattern, resolveFiles, worstStatus, getFileFormat, injectYamlVariables, injectJsonVariables };
3032030333

3032130334
/* istanbul ignore next */
3032230335
if (require.main === require.cache[eval('__filename')]) {

src/index.js

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,27 @@ function injectYamlVariables(content, changesetVariables) {
8686
return yaml.dump(doc, { lineWidth: -1, noRefs: true });
8787
}
8888

89+
function injectJsonVariables(content, changesetVariables) {
90+
const doc = JSON.parse(content);
91+
92+
// Build new variable entries from the provided changeset variables
93+
const injectedVars = Object.entries(changesetVariables).map(([name, value]) => ({
94+
environment: null,
95+
mask_value: true,
96+
name,
97+
value,
98+
}));
99+
100+
// Remove existing entries for variables we're overriding
101+
const overrideNames = new Set(Object.keys(changesetVariables));
102+
const existingVars = Array.isArray(doc.variable) ? doc.variable : [];
103+
const keptVars = existingVars.filter(v => !overrideNames.has(v.name));
104+
105+
doc.variable = [...keptVars, ...injectedVars];
106+
107+
return JSON.stringify(doc, null, 2);
108+
}
109+
89110
function getFileFormat(filePath) {
90111
const ext = path.extname(filePath).toLowerCase();
91112
if (ext === '.json') return 'json';
@@ -139,11 +160,7 @@ async function validateFile(filePath, options) {
139160
let body, contentType;
140161
if (format === 'json') {
141162
contentType = 'application/json';
142-
const requestPayload = {
143-
changeset: content,
144-
...(changesetVariables && { variables: changesetVariables })
145-
};
146-
body = JSON.stringify(requestPayload);
163+
body = changesetVariables ? injectJsonVariables(content, changesetVariables) : content;
147164
} else {
148165
contentType = 'application/yaml';
149166
body = changesetVariables ? injectYamlVariables(content, changesetVariables) : content;
@@ -236,11 +253,7 @@ async function executeFile(filePath, options) {
236253
let body, contentType;
237254
if (format === 'json') {
238255
contentType = 'application/json';
239-
const requestPayload = {
240-
changeset: content,
241-
...(changesetVariables && { variables: changesetVariables })
242-
};
243-
body = JSON.stringify(requestPayload);
256+
body = changesetVariables ? injectJsonVariables(content, changesetVariables) : content;
244257
} else {
245258
contentType = 'application/yaml';
246259
body = changesetVariables ? injectYamlVariables(content, changesetVariables) : content;
@@ -554,7 +567,7 @@ async function run() {
554567
}
555568
}
556569

557-
module.exports = { run, pollTask, buildUrl, isGlobPattern, resolveFiles, worstStatus, getFileFormat, injectYamlVariables };
570+
module.exports = { run, pollTask, buildUrl, isGlobPattern, resolveFiles, worstStatus, getFileFormat, injectYamlVariables, injectJsonVariables };
558571

559572
/* istanbul ignore next */
560573
if (require.main === module) {

src/index.test.js

Lines changed: 89 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jest.mock('@actions/core', () => mockCore);
2121
const mockFetch = jest.fn();
2222
global.fetch = mockFetch;
2323

24-
const { run, pollTask, buildUrl, isGlobPattern, resolveFiles, worstStatus, getFileFormat, injectYamlVariables } = require('./index');
24+
const { run, pollTask, buildUrl, isGlobPattern, resolveFiles, worstStatus, getFileFormat, injectYamlVariables, injectJsonVariables } = require('./index');
2525

2626
// Helpers
2727
function mockInputs(inputs) {
@@ -1685,11 +1685,16 @@ variable:
16851685
// ─── run() — JSON file format ──────────────────────────────────────────────
16861686

16871687
describe('run — JSON file format', () => {
1688-
const SAMPLE_JSON_CHANGESET = JSON.stringify({
1688+
const SAMPLE_JSON_CHANGESET_OBJ = {
16891689
name: 'Test Queue',
16901690
environment: 'Development',
1691+
variable: [
1692+
{ environment: null, mask_value: false, name: 'DogsName', value: 'global value' },
1693+
{ environment: 1, mask_value: false, name: 'DogsName', value: 'dev value' },
1694+
],
16911695
action: [{ action: 'gencloud-create', object_type: 'RoutingQueue', data: { name: 'Test Queue' } }]
1692-
});
1696+
};
1697+
const SAMPLE_JSON_CHANGESET = JSON.stringify(SAMPLE_JSON_CHANGESET_OBJ, null, 2);
16931698
const SAMPLE_JSON_FILE = path.join(__dirname, '__test_changeset_sample__.json');
16941699

16951700
beforeAll(() => {
@@ -1724,9 +1729,9 @@ describe('run — JSON file format', () => {
17241729
headers: expect.objectContaining({ 'Content-Type': 'application/json' }),
17251730
})
17261731
);
1727-
// Body should be JSON with changeset field
1728-
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
1729-
expect(body.changeset).toBe(SAMPLE_JSON_CHANGESET);
1732+
// Body should be raw JSON changeset content (not wrapped)
1733+
const body = mockFetch.mock.calls[0][1].body;
1734+
expect(body).toBe(SAMPLE_JSON_CHANGESET);
17301735
expect(mockCore.setFailed).not.toHaveBeenCalled();
17311736
});
17321737

@@ -1754,16 +1759,19 @@ describe('run — JSON file format', () => {
17541759
headers: expect.objectContaining({ 'Content-Type': 'application/json' }),
17551760
})
17561761
);
1762+
// Body should be raw JSON changeset content (not wrapped)
1763+
const body = mockFetch.mock.calls[0][1].body;
1764+
expect(body).toBe(SAMPLE_JSON_CHANGESET);
17571765
expect(mockCore.setFailed).not.toHaveBeenCalled();
17581766
});
17591767

1760-
test('includes variables in JSON payload for .json files', async () => {
1768+
test('injects variables into JSON changeset body for .json files', async () => {
17611769
mockInputs({
17621770
api_key: 'key',
17631771
base_url: 'https://test.inprod.io',
17641772
changeset_file: SAMPLE_JSON_FILE,
17651773
validate_before_execute: 'false',
1766-
changeset_variables: 'DB_PASSWORD=secret\nAPI_KEY=key123',
1774+
changeset_variables: 'DogsName=overridden\nNewVar=newval',
17671775
});
17681776

17691777
const execResult = { run_id: 42, changeset_name: 'Test', environment: { id: 1, name: 'Dev' } };
@@ -1776,8 +1784,79 @@ describe('run — JSON file format', () => {
17761784
await promise;
17771785

17781786
const body = JSON.parse(mockFetch.mock.calls[0][1].body);
1779-
expect(body.changeset).toBe(SAMPLE_JSON_CHANGESET);
1780-
expect(body.variables).toEqual({ DB_PASSWORD: 'secret', API_KEY: 'key123' });
1787+
// Should NOT have a wrapper changeset/variables field
1788+
expect(body.changeset).toBeUndefined();
1789+
expect(body.variables).toBeUndefined();
1790+
// All existing DogsName entries should be removed, replaced with one injected entry
1791+
const dogsEntries = body.variable.filter(v => v.name === 'DogsName');
1792+
expect(dogsEntries).toHaveLength(1);
1793+
expect(dogsEntries[0]).toEqual({ environment: null, mask_value: true, name: 'DogsName', value: 'overridden' });
1794+
// New variable should be added
1795+
const newVarEntry = body.variable.find(v => v.name === 'NewVar');
1796+
expect(newVarEntry).toEqual({ environment: null, mask_value: true, name: 'NewVar', value: 'newval' });
17811797
expect(mockCore.setFailed).not.toHaveBeenCalled();
17821798
});
1799+
});
1800+
1801+
// ─── injectJsonVariables ────────────────────────────────────────────────────
1802+
1803+
describe('injectJsonVariables', () => {
1804+
const baseJson = JSON.stringify({
1805+
name: 'Test Queue',
1806+
variable: [
1807+
{ environment: null, mask_value: false, name: 'existing_var', value: 'old_value' },
1808+
{ environment: 1, mask_value: false, name: 'existing_var', value: 'dev_value' },
1809+
{ environment: null, mask_value: true, name: 'keep_var', value: 'kept' },
1810+
],
1811+
action: [{ action: 'gencloud-create' }]
1812+
}, null, 2);
1813+
1814+
test('injects new variables with mask_value: true', () => {
1815+
const result = JSON.parse(injectJsonVariables(baseJson, { NEW_VAR: 'new_value' }));
1816+
const injected = result.variable.find(v => v.name === 'NEW_VAR');
1817+
expect(injected).toEqual({ environment: null, mask_value: true, name: 'NEW_VAR', value: 'new_value' });
1818+
});
1819+
1820+
test('replaces all entries for an existing variable name', () => {
1821+
const result = JSON.parse(injectJsonVariables(baseJson, { existing_var: 'replaced_value' }));
1822+
const matches = result.variable.filter(v => v.name === 'existing_var');
1823+
expect(matches).toHaveLength(1);
1824+
expect(matches[0].value).toBe('replaced_value');
1825+
expect(matches[0].mask_value).toBe(true);
1826+
expect(matches[0].environment).toBeNull();
1827+
});
1828+
1829+
test('preserves existing variables that are not overridden', () => {
1830+
const result = JSON.parse(injectJsonVariables(baseJson, { existing_var: 'new' }));
1831+
const kept = result.variable.find(v => v.name === 'keep_var');
1832+
expect(kept).toEqual({ environment: null, mask_value: true, name: 'keep_var', value: 'kept' });
1833+
});
1834+
1835+
test('handles JSON with empty variable array', () => {
1836+
const jsonEmpty = JSON.stringify({ name: 'Test', variable: [] });
1837+
const result = JSON.parse(injectJsonVariables(jsonEmpty, { NEW_VAR: 'value' }));
1838+
expect(result.variable).toHaveLength(1);
1839+
expect(result.variable[0].name).toBe('NEW_VAR');
1840+
});
1841+
1842+
test('handles JSON with no variable field', () => {
1843+
const jsonNoVar = JSON.stringify({ name: 'Test', environment: 'Dev' });
1844+
const result = JSON.parse(injectJsonVariables(jsonNoVar, { NEW_VAR: 'value' }));
1845+
expect(result.variable).toHaveLength(1);
1846+
expect(result.variable[0].name).toBe('NEW_VAR');
1847+
});
1848+
1849+
test('injects multiple variables at once', () => {
1850+
const result = JSON.parse(injectJsonVariables(baseJson, { VAR_A: 'aaa', VAR_B: 'bbb' }));
1851+
const varA = result.variable.find(v => v.name === 'VAR_A');
1852+
const varB = result.variable.find(v => v.name === 'VAR_B');
1853+
expect(varA).toEqual({ environment: null, mask_value: true, name: 'VAR_A', value: 'aaa' });
1854+
expect(varB).toEqual({ environment: null, mask_value: true, name: 'VAR_B', value: 'bbb' });
1855+
});
1856+
1857+
test('preserves other JSON fields', () => {
1858+
const result = JSON.parse(injectJsonVariables(baseJson, { NEW_VAR: 'val' }));
1859+
expect(result.name).toBe('Test Queue');
1860+
expect(result.action).toEqual([{ action: 'gencloud-create' }]);
1861+
});
17831862
});

0 commit comments

Comments
 (0)