diff --git a/.vscode/launch.json b/.vscode/launch.json index 6989fa435d125df6aaded215238f0fbd415217db..bc9f371db6267af7450780132382511cb52263f1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -60,6 +60,25 @@ "protocol": "inspector", "skipFiles": ["<node_internals>/**/*.js"] }, + { + "type": "node", + "request": "launch", + "name": "Jest Current Folder", + "program": "${workspaceFolder}/node_modules/.bin/jest", + "args": [ + "--runInBand", + "--collectCoverage=false", + "--testTimeout=100000000", + "--roots=${workspaceFolder}/${relativeFileDirname}" + ], + "console": "integratedTerminal", + "windows": { + "program": "${workspaceFolder}/node_modules/jest/bin/jest" + }, + "runtimeArgs": ["--preserve-symlinks"], + "protocol": "inspector", + "skipFiles": ["<node_internals>/**/*.js"] + }, { "type": "node", "name": "vscode-jest-tests", diff --git a/jest.config.ts b/jest.config.ts index eed6d79eca0472eb299556b797b827e73dff13bc..60490b835e4a8069a8649ba89bca1b74a024b511 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -30,6 +30,7 @@ const config: InitialOptionsTsJest = { 'jest-extended/all', 'expect-more-jest', '<rootDir>/test/setup.ts', + '<rootDir>/test/to-migrate.ts', ], snapshotSerializers: ['<rootDir>/test/newline-snapshot-serializer.ts'], testEnvironment: 'node', diff --git a/lib/config/migration.ts b/lib/config/migration.ts index 89fc7a49b9c2c71caa4a4aa36f33015d0b857371..b0af7ad93f3b01381a52458693a1b2897c6b5543 100644 --- a/lib/config/migration.ts +++ b/lib/config/migration.ts @@ -35,7 +35,7 @@ export function migrateConfig( optionTypes[option.name] = option.type; }); } - const newConfig = MigrationsService.run(config).migratedConfig; + const newConfig = MigrationsService.run(config); const migratedConfig = clone(newConfig) as MigratedRenovateConfig; const depTypes = [ 'dependencies', diff --git a/lib/config/migrations/base/abstract-migration.ts b/lib/config/migrations/base/abstract-migration.ts index 9d47be887bd54ec967f78df9b3917f048557b358..258466e7f2e6225f4c3e6123cb177a82c26cd81a 100644 --- a/lib/config/migrations/base/abstract-migration.ts +++ b/lib/config/migrations/base/abstract-migration.ts @@ -3,10 +3,9 @@ import type { RenovateConfig } from '../../types'; import type { Migration } from '../types'; export abstract class AbstractMigration implements Migration { + readonly deprecated: boolean = false; abstract readonly propertyName: string; - private readonly originalConfig: RenovateConfig; - private readonly migratedConfig: RenovateConfig; constructor(originalConfig: RenovateConfig, migratedConfig: RenovateConfig) { diff --git a/lib/config/migrations/base/rename-property-migration.ts b/lib/config/migrations/base/rename-property-migration.ts index d883dd5069ee3ea9c959b86ecdbc55e0ea08b841..5a93a9c4dc392fa694c6f14eac23169a52391654 100644 --- a/lib/config/migrations/base/rename-property-migration.ts +++ b/lib/config/migrations/base/rename-property-migration.ts @@ -2,6 +2,7 @@ import type { RenovateConfig } from '../../types'; import { AbstractMigration } from './abstract-migration'; export class RenamePropertyMigration extends AbstractMigration { + override readonly deprecated = true; readonly propertyName: string; private readonly newPropertyName: string; @@ -18,8 +19,6 @@ export class RenamePropertyMigration extends AbstractMigration { } override run(value): void { - this.delete(this.propertyName); - this.setSafely(this.newPropertyName, value); } } diff --git a/lib/config/migrations/custom/binary-source-migration.spec.ts b/lib/config/migrations/custom/binary-source-migration.spec.ts index 5471ca20d0ae62623bbce2ba9a97d5cf127aa609..8e8ca4dd2b3f6b28be10f3122d9fc2fadc5c4cd2 100644 --- a/lib/config/migrations/custom/binary-source-migration.spec.ts +++ b/lib/config/migrations/custom/binary-source-migration.spec.ts @@ -1,14 +1,14 @@ -import { MigrationsService } from '../migrations-service'; +import { BinarySourceMigration } from './binary-source-migration'; describe('config/migrations/custom/binary-source-migration', () => { it('should migrate "auto" to "global"', () => { - const { isMigrated, migratedConfig } = MigrationsService.run({ - binarySource: 'auto', - }); - - expect(isMigrated).toBeTrue(); - expect(migratedConfig).toEqual({ - binarySource: 'global', - }); + expect(BinarySourceMigration).toMigrate( + { + binarySource: 'auto', + }, + { + binarySource: 'global', + } + ); }); }); diff --git a/lib/config/migrations/custom/go-mod-tidy-migration.spec.ts b/lib/config/migrations/custom/go-mod-tidy-migration.spec.ts index 4f342710dcf25c398287f7972a29690bc52d8f06..747a7b5fe04ae6c36c6d039c625a93026722a514 100644 --- a/lib/config/migrations/custom/go-mod-tidy-migration.spec.ts +++ b/lib/config/migrations/custom/go-mod-tidy-migration.spec.ts @@ -1,35 +1,35 @@ -import { MigrationsService } from '../migrations-service'; +import { GoModTidyMigration } from './go-mod-tidy-migration'; describe('config/migrations/custom/go-mod-tidy-migration', () => { it('should add postUpdateOptions option when true', () => { - const { isMigrated, migratedConfig } = MigrationsService.run({ - gomodTidy: true, - postUpdateOptions: ['test'], - }); - - expect(isMigrated).toBeTrue(); - expect(migratedConfig).toEqual({ - postUpdateOptions: ['test', 'gomodTidy'], - }); + expect(GoModTidyMigration).toMigrate( + { + gomodTidy: true, + postUpdateOptions: ['test'], + }, + { + postUpdateOptions: ['test', 'gomodTidy'], + } + ); }); it('should handle case when postUpdateOptions is not defined ', () => { - const { isMigrated, migratedConfig } = MigrationsService.run({ - gomodTidy: true, - }); - - expect(isMigrated).toBeTrue(); - expect(migratedConfig).toEqual({ - postUpdateOptions: ['gomodTidy'], - }); + expect(GoModTidyMigration).toMigrate( + { + gomodTidy: true, + }, + { + postUpdateOptions: ['gomodTidy'], + } + ); }); it('should only remove when false', () => { - const { isMigrated, migratedConfig } = MigrationsService.run({ - gomodTidy: false, - }); - - expect(isMigrated).toBeTrue(); - expect(migratedConfig).toEqual({}); + expect(GoModTidyMigration).toMigrate( + { + gomodTidy: false, + }, + {} + ); }); }); diff --git a/lib/config/migrations/custom/go-mod-tidy-migration.ts b/lib/config/migrations/custom/go-mod-tidy-migration.ts index 9f3c984e363c864df4d24951bd24f07b9114eada..455a21bca41127750148b91f815029b128c05ea9 100644 --- a/lib/config/migrations/custom/go-mod-tidy-migration.ts +++ b/lib/config/migrations/custom/go-mod-tidy-migration.ts @@ -1,13 +1,12 @@ import { AbstractMigration } from '../base/abstract-migration'; export class GoModTidyMigration extends AbstractMigration { + override readonly deprecated = true; readonly propertyName = 'gomodTidy'; override run(value): void { const postUpdateOptions = this.get('postUpdateOptions'); - this.delete(this.propertyName); - if (value) { const newPostUpdateOptions = Array.isArray(postUpdateOptions) ? postUpdateOptions.concat(['gomodTidy']) diff --git a/lib/config/migrations/custom/ignore-node-modules-migration.spec.ts b/lib/config/migrations/custom/ignore-node-modules-migration.spec.ts index 2be6ae247a70b6a88fe38f6c00e7a74e527af119..d90978b56f177eb797d0de5458dfc1527c13b94a 100644 --- a/lib/config/migrations/custom/ignore-node-modules-migration.spec.ts +++ b/lib/config/migrations/custom/ignore-node-modules-migration.spec.ts @@ -1,12 +1,12 @@ -import { MigrationsService } from '../migrations-service'; +import { IgnoreNodeModulesMigration } from './ignore-node-modules-migration'; describe('config/migrations/custom/ignore-node-modules-migration', () => { it('should migrate to ignorePaths', () => { - const { isMigrated, migratedConfig } = MigrationsService.run({ - ignoreNodeModules: true, - }); - - expect(isMigrated).toBeTrue(); - expect(migratedConfig).toEqual({ ignorePaths: ['node_modules/'] }); + expect(IgnoreNodeModulesMigration).toMigrate( + { + ignoreNodeModules: true, + }, + { ignorePaths: ['node_modules/'] } + ); }); }); diff --git a/lib/config/migrations/custom/ignore-node-modules-migration.ts b/lib/config/migrations/custom/ignore-node-modules-migration.ts index 79a1c77e7509402101d566842674c812c5edc06a..a579603f22f21e757d8c9b32808a0729a920344b 100644 --- a/lib/config/migrations/custom/ignore-node-modules-migration.ts +++ b/lib/config/migrations/custom/ignore-node-modules-migration.ts @@ -1,11 +1,10 @@ import { AbstractMigration } from '../base/abstract-migration'; export class IgnoreNodeModulesMigration extends AbstractMigration { + override readonly deprecated = true; readonly propertyName = 'ignoreNodeModules'; override run(value): void { - this.delete(this.propertyName); - this.setSafely('ignorePaths', value ? ['node_modules/'] : []); } } diff --git a/lib/config/migrations/custom/required-status-checks-migration.spec.ts b/lib/config/migrations/custom/required-status-checks-migration.spec.ts index 5aba3b883ceffd37849ade93106c9482a9aecba9..e664433ab3c5a202ceccbd8bea30ad6af7c550fc 100644 --- a/lib/config/migrations/custom/required-status-checks-migration.spec.ts +++ b/lib/config/migrations/custom/required-status-checks-migration.spec.ts @@ -1,14 +1,14 @@ -import { MigrationsService } from '../migrations-service'; +import { RequiredStatusChecksMigration } from './required-status-checks-migration'; describe('config/migrations/custom/required-status-checks-migration', () => { it('should migrate requiredStatusChecks=null to ignoreTests=true', () => { - const { isMigrated, migratedConfig } = MigrationsService.run({ - requiredStatusChecks: null, - }); - - expect(isMigrated).toBeTrue(); - expect(migratedConfig).toEqual({ - ignoreTests: true, - }); + expect(RequiredStatusChecksMigration).toMigrate( + { + requiredStatusChecks: null, + }, + { + ignoreTests: true, + } + ); }); }); diff --git a/lib/config/migrations/custom/required-status-checks-migration.ts b/lib/config/migrations/custom/required-status-checks-migration.ts index a6fb5c303432e35afba5dacf68641fcb3698ff9f..db4bd86fb3c29024aac412103b4214f0fd7ccb6a 100644 --- a/lib/config/migrations/custom/required-status-checks-migration.ts +++ b/lib/config/migrations/custom/required-status-checks-migration.ts @@ -1,11 +1,10 @@ import { AbstractMigration } from '../base/abstract-migration'; export class RequiredStatusChecksMigration extends AbstractMigration { + override readonly deprecated = true; readonly propertyName = 'requiredStatusChecks'; override run(value): void { - this.delete(this.propertyName); - if (value === null) { this.setSafely('ignoreTests', true); } diff --git a/lib/config/migrations/custom/trust-level-migration.spec.ts b/lib/config/migrations/custom/trust-level-migration.spec.ts index 51ac5e070e7a0c17f08ea43553ce033d237bd42c..4ecb187dddc85c79bc31c08fc3af5b6547b41608 100644 --- a/lib/config/migrations/custom/trust-level-migration.spec.ts +++ b/lib/config/migrations/custom/trust-level-migration.spec.ts @@ -1,32 +1,32 @@ -import { MigrationsService } from '../migrations-service'; +import { TrustLevelMigration } from './trust-level-migration'; describe('config/migrations/custom/trust-level-migration', () => { it('should handle hight level', () => { - const { isMigrated, migratedConfig } = MigrationsService.run({ - trustLevel: 'high', - }); - - expect(isMigrated).toBeTrue(); - expect(migratedConfig).toEqual({ - allowCustomCrateRegistries: true, - allowScripts: true, - exposeAllEnv: true, - }); + expect(TrustLevelMigration).toMigrate( + { + trustLevel: 'high', + }, + { + allowCustomCrateRegistries: true, + allowScripts: true, + exposeAllEnv: true, + } + ); }); it('should not rewrite provided properties', () => { - const { isMigrated, migratedConfig } = MigrationsService.run({ - allowCustomCrateRegistries: false, - allowScripts: false, - exposeAllEnv: false, - trustLevel: 'high', - }); - - expect(isMigrated).toBeTrue(); - expect(migratedConfig).toEqual({ - allowCustomCrateRegistries: false, - allowScripts: false, - exposeAllEnv: false, - }); + expect(TrustLevelMigration).toMigrate( + { + allowCustomCrateRegistries: false, + allowScripts: false, + exposeAllEnv: false, + trustLevel: 'high', + }, + { + allowCustomCrateRegistries: false, + allowScripts: false, + exposeAllEnv: false, + } + ); }); }); diff --git a/lib/config/migrations/custom/trust-level-migration.ts b/lib/config/migrations/custom/trust-level-migration.ts index 95948940cf88d16d1090dfc78f98799d270661a7..b7ec2cab07e684aebe9010d42ea5dd8758e9cd33 100644 --- a/lib/config/migrations/custom/trust-level-migration.ts +++ b/lib/config/migrations/custom/trust-level-migration.ts @@ -1,11 +1,10 @@ import { AbstractMigration } from '../base/abstract-migration'; export class TrustLevelMigration extends AbstractMigration { + override readonly deprecated = true; readonly propertyName = 'trustLevel'; override run(value): void { - this.delete(this.propertyName); - if (value === 'high') { this.setSafely('allowCustomCrateRegistries', true); this.setSafely('allowScripts', true); diff --git a/lib/config/migrations/migrations-service.spec.ts b/lib/config/migrations/migrations-service.spec.ts index ae7ba803c0dbcc49cafd3c1f70565fec4b19e215..fdb47ac60d0c52384406dc1d129214a908c0a9c1 100644 --- a/lib/config/migrations/migrations-service.spec.ts +++ b/lib/config/migrations/migrations-service.spec.ts @@ -8,9 +8,10 @@ describe('config/migrations/migrations-service', () => { [property]: 'test', }; - const { isMigrated, migratedConfig } = - MigrationsService.run(originalConfig); - expect(isMigrated).toBeTrue(); + const migratedConfig = MigrationsService.run(originalConfig); + expect( + MigrationsService.isMigrated(originalConfig, migratedConfig) + ).toBeTrue(); expect(migratedConfig).toEqual({}); } }); @@ -24,9 +25,10 @@ describe('config/migrations/migrations-service', () => { [oldPropertyName]: 'test', }; - const { isMigrated, migratedConfig } = - MigrationsService.run(originalConfig); - expect(isMigrated).toBeTrue(); + const migratedConfig = MigrationsService.run(originalConfig); + expect( + MigrationsService.isMigrated(originalConfig, migratedConfig) + ).toBeTrue(); expect(migratedConfig).toEqual({ [newPropertyName]: 'test', }); @@ -39,14 +41,15 @@ describe('config/migrations/migrations-service', () => { versionScheme: 'test', excludedPackageNames: ['test'], }; - const { isMigrated, migratedConfig } = - MigrationsService.run(originalConfig); + const migratedConfig = MigrationsService.run(originalConfig); const mappedProperties = Object.keys(originalConfig).map((property) => MigrationsService.renamedProperties.get(property) ); - expect(isMigrated).toBeTrue(); + expect( + MigrationsService.isMigrated(originalConfig, migratedConfig) + ).toBeTrue(); expect(mappedProperties).toEqual(Object.keys(migratedConfig)); }); }); diff --git a/lib/config/migrations/migrations-service.ts b/lib/config/migrations/migrations-service.ts index a0d67ed42daea5a60ed5951e05888d801f34232e..f4abee1004966931822410e6c54f1b0753ff0e10 100644 --- a/lib/config/migrations/migrations-service.ts +++ b/lib/config/migrations/migrations-service.ts @@ -1,5 +1,5 @@ import { dequal } from 'dequal'; -import type { MigratedConfig, RenovateConfig } from '../types'; +import type { RenovateConfig } from '../types'; import { RemovePropertyMigration } from './base/remove-property-migration'; import { RenamePropertyMigration } from './base/rename-property-migration'; import { BinarySourceMigration } from './custom/binary-source-migration'; @@ -43,26 +43,34 @@ export class MigrationsService { TrustLevelMigration, ]; - static run(originalConfig: RenovateConfig): MigratedConfig { + static run(originalConfig: RenovateConfig): RenovateConfig { const migratedConfig: RenovateConfig = {}; - const migrations = MigrationsService.getMigrations( - originalConfig, - migratedConfig - ); + const migrations = this.getMigrations(originalConfig, migratedConfig); for (const [key, value] of Object.entries(originalConfig)) { migratedConfig[key] ??= value; const migration = migrations.find((item) => item.propertyName === key); - migration?.run(value); + + if (migration) { + migration.run(value); + + if (migration.deprecated) { + delete migratedConfig[key]; + } + } } - return { - isMigrated: !dequal(originalConfig, migratedConfig), - migratedConfig, - }; + return migratedConfig; + } + + static isMigrated( + originalConfig: RenovateConfig, + migratedConfig: RenovateConfig + ): boolean { + return !dequal(originalConfig, migratedConfig); } - private static getMigrations( + protected static getMigrations( originalConfig: RenovateConfig, migratedConfig: RenovateConfig ): ReadonlyArray<Migration> { diff --git a/lib/config/migrations/types.ts b/lib/config/migrations/types.ts index 206b294863df1e6deff3f0efd8decc456be15de7..429cbd069f65070e7dba799784a0fa15ec4dce73 100644 --- a/lib/config/migrations/types.ts +++ b/lib/config/migrations/types.ts @@ -7,6 +7,7 @@ export interface MigrationConstructor { } export interface Migration { + readonly deprecated: boolean; readonly propertyName: string; run(value: unknown): void; } diff --git a/test/to-migrate.ts b/test/to-migrate.ts new file mode 100644 index 0000000000000000000000000000000000000000..38b7dea42d7c4bdff09c77b3145667f215e3fd14 --- /dev/null +++ b/test/to-migrate.ts @@ -0,0 +1,62 @@ +import { expect } from '@jest/globals'; +import type { + Migration, + MigrationConstructor, +} from '../lib/config/migrations/types'; +import type { RenovateConfig } from '../lib/config/types'; +import { MigrationsService } from './../lib/config/migrations/migrations-service'; + +declare global { + // eslint-disable-next-line @typescript-eslint/no-namespace + namespace jest { + interface Matchers<R> { + toMigrate( + originalConfig: RenovateConfig, + expectedConfig: RenovateConfig, + isMigrated?: boolean + ): R; + } + } +} + +expect.extend({ + toMigrate( + CustomMigration: MigrationConstructor, + originalConfig: RenovateConfig, + expectedConfig: RenovateConfig, + isMigrated = true + ) { + class CustomMigrationsService extends MigrationsService { + protected static override getMigrations( + original: RenovateConfig, + migrated: RenovateConfig + ): ReadonlyArray<Migration> { + return [new CustomMigration(original, migrated)]; + } + } + + const migratedConfig = CustomMigrationsService.run(originalConfig); + + if ( + MigrationsService.isMigrated(migratedConfig, originalConfig) !== + isMigrated + ) { + return { + message: (): string => `isMigrated should be ${isMigrated}`, + pass: false, + }; + } + + if (!this.equals(migratedConfig, expectedConfig)) { + return { + message: (): string => 'Migration failed', + pass: false, + }; + } + + return { + message: (): string => 'Migration passed successfully', + pass: true, + }; + }, +});