diff --git a/lib/workers/repository/config-migration/branch/__fixtures__/migrated-data-formatted.json b/lib/workers/repository/config-migration/branch/__fixtures__/migrated-data-formatted.json new file mode 100644 index 0000000000000000000000000000000000000000..dd59ce68ff3f74e4e598fb4504413eb13215e01b --- /dev/null +++ b/lib/workers/repository/config-migration/branch/__fixtures__/migrated-data-formatted.json @@ -0,0 +1,4 @@ +{ + "filename": "renovate.json", + "content": "{\n \"extends\": [\n \":separateMajorReleases\",\n \":prImmediately\",\n \":renovatePrefix\",\n \":semanticPrefixFixDepsChoreOthers\",\n \":updateNotScheduled\",\n \":automergeDisabled\",\n \":maintainLockFilesDisabled\",\n \":autodetectPinVersions\",\n \"group:monorepos\"\n ],\n \"onboarding\": false,\n \"rangeStrategy\": \"replace\",\n \"semanticCommits\": \"enabled\",\n \"timezone\": \"US/Central\",\n \"baseBranches\": [\"main\"]\n}\n" +} diff --git a/lib/workers/repository/config-migration/branch/migrated-data.spec.ts b/lib/workers/repository/config-migration/branch/migrated-data.spec.ts index 0e4074c741a0c761769294ee6fb46b09f0a6b22a..49978519f210aacda3bfe339916aaf8cd24c6723 100644 --- a/lib/workers/repository/config-migration/branch/migrated-data.spec.ts +++ b/lib/workers/repository/config-migration/branch/migrated-data.spec.ts @@ -4,10 +4,12 @@ import { mockedFunction } from '../../../../../test/util'; import { migrateConfig } from '../../../../config/migration'; import { readLocalFile } from '../../../../util/fs'; +import { getFileList } from '../../../../util/git'; import { detectRepoFileConfig } from '../../init/merge'; -import { MigratedDataFactory } from './migrated-data'; +import { MigratedDataFactory, applyPrettierFormatting } from './migrated-data'; jest.mock('../../../../config/migration'); +jest.mock('../../../../util/git'); jest.mock('../../../../util/fs'); jest.mock('../../init/merge'); jest.mock('detect-indent'); @@ -17,6 +19,9 @@ const rawNonMigratedJson5 = Fixtures.get('./renovate.json5'); const migratedData = Fixtures.getJson('./migrated-data.json'); const migratedDataJson5 = Fixtures.getJson('./migrated-data.json5'); const migratedConfigObj = Fixtures.getJson('./migrated.json'); +const formattedMigratedData = Fixtures.getJson( + './migrated-data-formatted.json' +); describe('workers/repository/config-migration/branch/migrated-data', () => { describe('MigratedDataFactory.getAsync', () => { @@ -35,6 +40,7 @@ describe('workers/repository/config-migration/branch/migrated-data', () => { isMigrated: true, migratedConfig: migratedConfigObj, }); + mockedFunction(getFileList).mockResolvedValue([]); }); it('Calls getAsync a first when migration not needed', async () => { @@ -110,5 +116,38 @@ describe('workers/repository/config-migration/branch/migrated-data', () => { MigratedDataFactory.reset(); await expect(MigratedDataFactory.getAsync()).resolves.toBeNull(); }); + + it('format and migrate a JSON config file', async () => { + mockedFunction(detectRepoFileConfig).mockResolvedValueOnce({ + configFileName: 'renovate.json', + }); + mockedFunction(readLocalFile).mockResolvedValueOnce(rawNonMigrated); + mockedFunction(getFileList).mockResolvedValue(['.prettierrc']); + MigratedDataFactory.reset(); + await expect(MigratedDataFactory.getAsync()).resolves.toEqual( + formattedMigratedData + ); + }); + + it('should not stop run for invalid package.json', async () => { + mockedFunction(detectRepoFileConfig).mockResolvedValueOnce({ + configFileName: 'renovate.json', + }); + mockedFunction(readLocalFile).mockResolvedValueOnce(rawNonMigrated); + mockedFunction(readLocalFile).mockResolvedValue('abci'); + MigratedDataFactory.reset(); + await expect(MigratedDataFactory.getAsync()).resolves.toEqual( + migratedData + ); + }); + + it('return original content if its invalid', async () => { + await expect( + applyPrettierFormatting(`{"name":"Rahul"`, 'json', { + indent: ' ', + amount: 2, + }) + ).resolves.toBe(`{"name":"Rahul"`); + }); }); }); diff --git a/lib/workers/repository/config-migration/branch/migrated-data.ts b/lib/workers/repository/config-migration/branch/migrated-data.ts index 0812fbc9c86bfa7c00cad2287a9fb75df86d9ea9..da22bb5f4b0daf5beacf343ee2ff83dd26d20a23 100644 --- a/lib/workers/repository/config-migration/branch/migrated-data.ts +++ b/lib/workers/repository/config-migration/branch/migrated-data.ts @@ -1,14 +1,65 @@ import detectIndent from 'detect-indent'; import JSON5 from 'json5'; +import prettier from 'prettier'; import { migrateConfig } from '../../../../config/migration'; import { logger } from '../../../../logger'; import { readLocalFile } from '../../../../util/fs'; +import { getFileList } from '../../../../util/git'; import { detectRepoFileConfig } from '../../init/merge'; export interface MigratedData { content: string; filename: string; } +interface Indent { + amount: number; + indent: string; + type?: string; +} + +const prettierConfigFilenames = new Set([ + '.prettierrc', + '.prettierrc.json', + '.prettierrc.yml', + '.prettierrc.yaml', + '.prettierrc.json5', + '.prettierrc.js', + '.prettierrc.cjs', + 'prettier.config.js', + 'prettier.config.cjs', + '.prettierrc.toml', +]); + +export async function applyPrettierFormatting( + content: string, + parser: string, + indent: Indent +): Promise<string> { + const fileList = await getFileList(); + let prettierExists = fileList.some((file) => + prettierConfigFilenames.has(file) + ); + if (!prettierExists) { + try { + const packageJsonContent = await readLocalFile('package.json', 'utf8'); + prettierExists = + packageJsonContent && JSON.parse(packageJsonContent).prettier; + } catch { + logger.warn('Invalid JSON found in package.json'); + } + } + + if (!prettierExists) { + return content; + } + const options = { + parser, + tabWidth: indent.amount === 0 ? 2 : indent.amount, + useTabs: indent.type === 'tab', + }; + + return prettier.format(content, options); +} export class MigratedDataFactory { // singleton @@ -52,15 +103,22 @@ export class MigratedDataFactory { // indent defaults to 2 spaces // TODO #7154 - const indent = detectIndent(raw!).indent ?? ' '; + const indent = detectIndent(raw!); + const indentSpace = indent.indent ?? ' '; let content: string; if (filename.endsWith('.json5')) { - content = JSON5.stringify(migratedConfig, undefined, indent); + content = JSON5.stringify(migratedConfig, undefined, indentSpace); } else { - content = JSON.stringify(migratedConfig, undefined, indent); + content = JSON.stringify(migratedConfig, undefined, indentSpace); } + // format if prettier is found in the user's repo + content = await applyPrettierFormatting( + content, + filename.endsWith('.json5') ? 'json5' : 'json', + indent + ); if (!content.endsWith('\n')) { content += '\n'; } diff --git a/package.json b/package.json index 89778517e974044e4354b6a5a43980bf8da84a55..8a355b52d5a85f3851f7636e0dc178541aa7d84f 100644 --- a/package.json +++ b/package.json @@ -197,6 +197,7 @@ "p-map": "4.0.0", "p-queue": "6.6.2", "parse-link-header": "2.0.0", + "prettier": "2.7.1", "redis": "4.2.0", "remark": "13.0.0", "remark-github": "10.1.0", @@ -291,7 +292,6 @@ "mockdate": "3.0.5", "nock": "13.2.8", "npm-run-all": "4.1.5", - "prettier": "2.7.1", "pretty-quick": "3.1.3", "rimraf": "3.0.2", "semantic-release": "19.0.3",