diff --git a/lib/platform/azure/index.spec.ts b/lib/platform/azure/index.spec.ts index 592b6d769245e97d3a9d7475d6c0bea7dcc975d5..a2a2cf2f42aeaba879b82b2d4623cccdb9ca6734 100644 --- a/lib/platform/azure/index.spec.ts +++ b/lib/platform/azure/index.spec.ts @@ -1250,6 +1250,24 @@ describe('platform/azure/index', () => { const res = await azure.getJsonFile('file.json'); expect(res).toEqual(data); }); + it('returns file content in json5 format', async () => { + const json5Data = ` + { + // json5 comment + foo: 'bar' + } + `; + azureApi.gitApi.mockImplementationOnce( + () => + ({ + getItemContent: jest.fn(() => + Promise.resolve(Readable.from(json5Data)) + ), + } as any) + ); + const res = await azure.getJsonFile('file.json5'); + expect(res).toEqual({ foo: 'bar' }); + }); it('throws on malformed JSON', async () => { azureApi.gitApi.mockImplementationOnce( () => diff --git a/lib/platform/azure/index.ts b/lib/platform/azure/index.ts index f0bfd15f2004e1971f058370fd464514457710d0..079286f474d3685b5d3612c806e205bfcb0b7bb1 100644 --- a/lib/platform/azure/index.ts +++ b/lib/platform/azure/index.ts @@ -8,6 +8,7 @@ import { PullRequestStatus, } from 'azure-devops-node-api/interfaces/GitInterfaces'; import delay from 'delay'; +import JSON5 from 'json5'; import { PlatformId } from '../../constants'; import { REPOSITORY_EMPTY } from '../../constants/error-messages'; import { logger } from '../../logger'; @@ -135,6 +136,9 @@ export async function getJsonFile( repoName?: string ): Promise<any | null> { const raw = await getRawFile(fileName, repoName); + if (fileName.endsWith('.json5')) { + return JSON5.parse(raw); + } return JSON.parse(raw); } diff --git a/lib/platform/bitbucket-server/__snapshots__/index.spec.ts.snap b/lib/platform/bitbucket-server/__snapshots__/index.spec.ts.snap index 6f51fedf04120adfc1950ceac26b7b65a7dbf2e5..ac53786634db0848b3c8f3cbc2f79a8aef0ab708 100644 --- a/lib/platform/bitbucket-server/__snapshots__/index.spec.ts.snap +++ b/lib/platform/bitbucket-server/__snapshots__/index.spec.ts.snap @@ -2068,6 +2068,47 @@ Array [ ] `; +exports[`platform/bitbucket-server/index endpoint with no path getJsonFile() returns file content in json5 format 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "stash.renovatebot.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + "x-atlassian-token": "no-check", + }, + "method": "GET", + "url": "https://stash.renovatebot.com/rest/api/1.0/projects/SOME/repos/repo", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "stash.renovatebot.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + "x-atlassian-token": "no-check", + }, + "method": "GET", + "url": "https://stash.renovatebot.com/rest/api/1.0/projects/SOME/repos/repo/branches/default", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "stash.renovatebot.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + "x-atlassian-token": "no-check", + }, + "method": "GET", + "url": "https://stash.renovatebot.com/rest/api/1.0/projects/SOME/repos/repo/browse/file.json5?limit=20000", + }, +] +`; + exports[`platform/bitbucket-server/index endpoint with no path getJsonFile() throws on errors 1`] = ` Array [ Object { @@ -6443,6 +6484,47 @@ Array [ ] `; +exports[`platform/bitbucket-server/index endpoint with path getJsonFile() returns file content in json5 format 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "stash.renovatebot.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + "x-atlassian-token": "no-check", + }, + "method": "GET", + "url": "https://stash.renovatebot.com/vcs/rest/api/1.0/projects/SOME/repos/repo", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "stash.renovatebot.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + "x-atlassian-token": "no-check", + }, + "method": "GET", + "url": "https://stash.renovatebot.com/vcs/rest/api/1.0/projects/SOME/repos/repo/branches/default", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "stash.renovatebot.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + "x-atlassian-token": "no-check", + }, + "method": "GET", + "url": "https://stash.renovatebot.com/vcs/rest/api/1.0/projects/SOME/repos/repo/browse/file.json5?limit=20000", + }, +] +`; + exports[`platform/bitbucket-server/index endpoint with path getJsonFile() throws on errors 1`] = ` Array [ Object { diff --git a/lib/platform/bitbucket-server/index.spec.ts b/lib/platform/bitbucket-server/index.spec.ts index 8b0d036d23998bac0c543b24d6303145cf1fc94c..2f77e0e4d03f0158d1d7082153d0ffa9c2360920 100644 --- a/lib/platform/bitbucket-server/index.spec.ts +++ b/lib/platform/bitbucket-server/index.spec.ts @@ -2109,6 +2109,26 @@ Followed by some information. expect(res).toEqual(data); expect(httpMock.getTrace()).toMatchSnapshot(); }); + it('returns file content in json5 format', async () => { + const json5Data = ` + { + // json5 comment + foo: 'bar' + } + `; + const scope = await initRepo(); + scope + .get( + `${urlPath}/rest/api/1.0/projects/SOME/repos/repo/browse/file.json5?limit=20000` + ) + .reply(200, { + isLastPage: true, + lines: [{ text: json5Data }], + }); + const res = await bitbucket.getJsonFile('file.json5'); + expect(res).toEqual({ foo: 'bar' }); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); it('throws on malformed JSON', async () => { const scope = await initRepo(); scope diff --git a/lib/platform/bitbucket-server/index.ts b/lib/platform/bitbucket-server/index.ts index 2a13acb49925af1487db6dc46304d2bee3be1194..04b8b1450fe06b7c6b5ef0dd57c488cd8959c210 100644 --- a/lib/platform/bitbucket-server/index.ts +++ b/lib/platform/bitbucket-server/index.ts @@ -1,6 +1,7 @@ import url from 'url'; import is from '@sindresorhus/is'; import delay from 'delay'; +import JSON5 from 'json5'; import type { PartialDeep } from 'type-fest'; import { PlatformId } from '../../constants'; import { @@ -141,6 +142,9 @@ export async function getJsonFile( repo: string = config.repository ): Promise<any | null> { const raw = await getRawFile(fileName, repo); + if (fileName.endsWith('.json5')) { + return JSON5.parse(raw); + } return JSON.parse(raw); } diff --git a/lib/platform/bitbucket/__snapshots__/index.spec.ts.snap b/lib/platform/bitbucket/__snapshots__/index.spec.ts.snap index 8484e51d2693daf0f84e1a9f3803148d06167dd4..44df44b929326c7291030c749fca17c0dc7118d0 100644 --- a/lib/platform/bitbucket/__snapshots__/index.spec.ts.snap +++ b/lib/platform/bitbucket/__snapshots__/index.spec.ts.snap @@ -938,6 +938,32 @@ Array [ ] `; +exports[`platform/bitbucket/index getJsonFile() returns file content in json5 format 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "api.bitbucket.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://api.bitbucket.org/2.0/repositories/some/repo", + }, + Object { + "headers": Object { + "accept-encoding": "gzip, deflate, br", + "authorization": "Basic YWJjOjEyMw==", + "host": "api.bitbucket.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://api.bitbucket.org/2.0/repositories/some/repo/src/HEAD/file.json5", + }, +] +`; + exports[`platform/bitbucket/index getJsonFile() throws on errors 1`] = ` Array [ Object { diff --git a/lib/platform/bitbucket/index.spec.ts b/lib/platform/bitbucket/index.spec.ts index 2a59c36dedfc1dc84851990a3d70cabf81ba233c..3abe950b01aa589279db601f53d8f0e8233c7914 100644 --- a/lib/platform/bitbucket/index.spec.ts +++ b/lib/platform/bitbucket/index.spec.ts @@ -938,6 +938,21 @@ describe('platform/bitbucket/index', () => { expect(res).toEqual(data); expect(httpMock.getTrace()).toMatchSnapshot(); }); + it('returns file content in json5 format', async () => { + const json5Data = ` + { + // json5 comment + foo: 'bar' + } + `; + const scope = await initRepoMock(); + scope + .get('/2.0/repositories/some/repo/src/HEAD/file.json5') + .reply(200, json5Data); + const res = await bitbucket.getJsonFile('file.json5'); + expect(res).toEqual({ foo: 'bar' }); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); it('throws on malformed JSON', async () => { const scope = await initRepoMock(); scope diff --git a/lib/platform/bitbucket/index.ts b/lib/platform/bitbucket/index.ts index c2e4db583b4b14a3fbf2090246464ac48cf7006b..f203d77e29c7f0bcfa33d1ab440ba04838ced8e8 100644 --- a/lib/platform/bitbucket/index.ts +++ b/lib/platform/bitbucket/index.ts @@ -1,5 +1,6 @@ import URL from 'url'; import is from '@sindresorhus/is'; +import JSON5 from 'json5'; import parseDiff from 'parse-diff'; import { PlatformId } from '../../constants'; import { REPOSITORY_NOT_FOUND } from '../../constants/error-messages'; @@ -123,6 +124,9 @@ export async function getJsonFile( repo: string = config.repository ): Promise<any | null> { const raw = await getRawFile(fileName, repo); + if (fileName.endsWith('.json5')) { + return JSON5.parse(raw); + } return JSON.parse(raw); } diff --git a/lib/platform/gitea/index.spec.ts b/lib/platform/gitea/index.spec.ts index 05b9a3de361c3de1650f039e06b6fb55aaaebaa6..5b41b8fb95c59fefc5d2cc841a52abd49fe2e6f5 100644 --- a/lib/platform/gitea/index.spec.ts +++ b/lib/platform/gitea/index.spec.ts @@ -1527,6 +1527,20 @@ describe('platform/gitea/index', () => { const res = await gitea.getJsonFile('file.json'); expect(res).toEqual(data); }); + it('returns file content in json5 format', async () => { + const json5Data = ` + { + // json5 comment + foo: 'bar' + } + `; + helper.getRepoContents.mockResolvedValueOnce({ + contentString: json5Data, + } as never); + await initFakeRepo({ full_name: 'some/repo' }); + const res = await gitea.getJsonFile('file.json5'); + expect(res).toEqual({ foo: 'bar' }); + }); it('throws on malformed JSON', async () => { helper.getRepoContents.mockResolvedValueOnce({ contentString: '!@#', diff --git a/lib/platform/gitea/index.ts b/lib/platform/gitea/index.ts index fa055a6ce78e486600ca29a04c63b2e493bfb945..22b23eab39f69c03675fe0c85205151abfba9c8b 100644 --- a/lib/platform/gitea/index.ts +++ b/lib/platform/gitea/index.ts @@ -1,5 +1,6 @@ import URL from 'url'; import is from '@sindresorhus/is'; +import JSON5 from 'json5'; import { lt } from 'semver'; import { PlatformId } from '../../constants'; import { @@ -221,6 +222,9 @@ const platform: Platform = { repo: string = config.repository ): Promise<any | null> { const raw = await platform.getRawFile(fileName, repo); + if (fileName.endsWith('.json5')) { + return JSON5.parse(raw); + } return JSON.parse(raw); }, diff --git a/lib/platform/github/__snapshots__/index.spec.ts.snap b/lib/platform/github/__snapshots__/index.spec.ts.snap index d8291824b1bc112856f778d66e13e6ba7b810222..42a296e4f2f717d0226125a1211ce9be7b6eb200 100644 --- a/lib/platform/github/__snapshots__/index.spec.ts.snap +++ b/lib/platform/github/__snapshots__/index.spec.ts.snap @@ -4964,6 +4964,66 @@ Array [ ] `; +exports[`platform/github/index getJsonFile() returns file content in json5 format 1`] = ` +Array [ + Object { + "graphql": Object { + "query": Object { + "__vars": Object { + "$name": "String!", + "$owner": "String!", + }, + "repository": Object { + "__args": Object { + "name": "$name", + "owner": "$owner", + }, + "autoMergeAllowed": null, + "defaultBranchRef": Object { + "name": null, + "target": Object { + "oid": null, + }, + }, + "isArchived": null, + "isFork": null, + "mergeCommitAllowed": null, + "nameWithOwner": null, + "rebaseMergeAllowed": null, + "squashMergeAllowed": null, + }, + }, + "variables": Object { + "name": "repo", + "owner": "some", + }, + }, + "headers": Object { + "accept": "application/vnd.github.v3+json", + "accept-encoding": "gzip, deflate, br", + "authorization": "token 123test", + "content-length": "373", + "content-type": "application/json", + "host": "api.github.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "POST", + "url": "https://api.github.com/graphql", + }, + Object { + "headers": Object { + "accept": "application/vnd.github.v3+json", + "accept-encoding": "gzip, deflate, br", + "authorization": "token 123test", + "host": "api.github.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://api.github.com/repos/some/repo/contents/file.json5", + }, +] +`; + exports[`platform/github/index getJsonFile() throws on errors 1`] = ` Array [ Object { diff --git a/lib/platform/github/index.spec.ts b/lib/platform/github/index.spec.ts index b988bd51f1795059b9b03aee98341665d06a6948..850d02222110da743d66701d55fb149155028e12 100644 --- a/lib/platform/github/index.spec.ts +++ b/lib/platform/github/index.spec.ts @@ -2399,6 +2399,23 @@ describe('platform/github/index', () => { expect(res).toEqual(data); expect(httpMock.getTrace()).toMatchSnapshot(); }); + it('returns file content in json5 format', async () => { + const json5Data = ` + { + // json5 comment + foo: 'bar' + } + `; + const scope = httpMock.scope(githubApiHost); + initRepoMock(scope, 'some/repo'); + await github.initRepo({ repository: 'some/repo', token: 'token' } as any); + scope.get('/repos/some/repo/contents/file.json5').reply(200, { + content: Buffer.from(json5Data).toString('base64'), + }); + const res = await github.getJsonFile('file.json5'); + expect(res).toEqual({ foo: 'bar' }); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); it('throws on malformed JSON', async () => { const scope = httpMock.scope(githubApiHost); initRepoMock(scope, 'some/repo'); diff --git a/lib/platform/github/index.ts b/lib/platform/github/index.ts index 1ac125cb37c5cebea098f59b2f81595227f663e7..367dbf7456ebd3f0e75ea46629462bdc8f120300 100644 --- a/lib/platform/github/index.ts +++ b/lib/platform/github/index.ts @@ -1,6 +1,7 @@ import URL from 'url'; import is from '@sindresorhus/is'; import delay from 'delay'; +import JSON5 from 'json5'; import { DateTime } from 'luxon'; import { PlatformId } from '../../constants'; import { @@ -166,6 +167,9 @@ export async function getJsonFile( repo: string = config.repository ): Promise<any | null> { const raw = await getRawFile(fileName, repo); + if (fileName.endsWith('.json5')) { + return JSON5.parse(raw); + } return JSON.parse(raw); } diff --git a/lib/platform/gitlab/__snapshots__/index.spec.ts.snap b/lib/platform/gitlab/__snapshots__/index.spec.ts.snap index 01d80dece811bc4f02b826583014784d4a1dbcf7..f12f50e8788ef34315f9903f11456037d920101c 100644 --- a/lib/platform/gitlab/__snapshots__/index.spec.ts.snap +++ b/lib/platform/gitlab/__snapshots__/index.spec.ts.snap @@ -2214,6 +2214,33 @@ Array [ ] `; +exports[`platform/gitlab/index getJsonFile() returns file content in json5 format 1`] = ` +Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Bearer 123test", + "host": "gitlab.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://gitlab.com/api/v4/projects/some%2Frepo", + }, + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Bearer 123test", + "host": "gitlab.com", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://gitlab.com/api/v4/projects/some%2Frepo/repository/files/dir%2Ffile.json5?ref=HEAD", + }, +] +`; + exports[`platform/gitlab/index getJsonFile() throws on errors 1`] = ` Array [ Object { diff --git a/lib/platform/gitlab/index.spec.ts b/lib/platform/gitlab/index.spec.ts index 0c553faf91dc280a80567d8a34a26768f3a5cc40..4f3107759acb3f254034400d600ff6a3511c86bd 100644 --- a/lib/platform/gitlab/index.spec.ts +++ b/lib/platform/gitlab/index.spec.ts @@ -1917,6 +1917,25 @@ These updates have all been created already. Click a checkbox below to force a r expect(res).toEqual(data); expect(httpMock.getTrace()).toMatchSnapshot(); }); + it('returns file content in json5 format', async () => { + const json5Data = ` + { + // json5 comment + foo: 'bar' + } + `; + const scope = await initRepo(); + scope + .get( + '/api/v4/projects/some%2Frepo/repository/files/dir%2Ffile.json5?ref=HEAD' + ) + .reply(200, { + content: Buffer.from(json5Data).toString('base64'), + }); + const res = await gitlab.getJsonFile('dir/file.json5'); + expect(res).toEqual({ foo: 'bar' }); + expect(httpMock.getTrace()).toMatchSnapshot(); + }); it('throws on malformed JSON', async () => { const scope = await initRepo(); scope diff --git a/lib/platform/gitlab/index.ts b/lib/platform/gitlab/index.ts index 348372468efb032a73dbde54a4555f94602e2302..f9aabbdfe3a5dfdc61714177924096d3c996b6b3 100644 --- a/lib/platform/gitlab/index.ts +++ b/lib/platform/gitlab/index.ts @@ -1,6 +1,7 @@ import URL from 'url'; import is from '@sindresorhus/is'; import delay from 'delay'; +import JSON5 from 'json5'; import pAll from 'p-all'; import { lt } from 'semver'; import { PlatformId } from '../../constants'; @@ -171,6 +172,9 @@ export async function getJsonFile( repo: string = config.repository ): Promise<any | null> { const raw = await getRawFile(fileName, repo); + if (fileName.endsWith('.json5')) { + return JSON5.parse(raw); + } return JSON.parse(raw); }