diff --git a/packages/@ama-sdk/schematics/README.md b/packages/@ama-sdk/schematics/README.md index 9a5c7b93e1..731216e0ef 100644 --- a/packages/@ama-sdk/schematics/README.md +++ b/packages/@ama-sdk/schematics/README.md @@ -242,6 +242,18 @@ yarn schematics @ama-sdk/schematics:typescript-core --generator-key example-sdk > The values provided by the parameter `--global-property` will actually be merged with the values of `globalProperty` from > `openapitools.json` (rather than override them like the other properties). +### Migration + +To help to apply changes on the Shell part of the SDK repository, a `migrate` schematic is exposed: + +```shell +yarn schematics @ama-sdk/schematics:migrate --from 10.0.0 [--to 11.0.0] +``` + +> [!NOTE] +> - The `--from` parameter is mandatory to provide the version of the original `@ama-sdk/schematics` package from which the rules should be run. +> - The *optional* `--to` parameter allows to indicate a version until which the rules should be run. The current installed version will be used if not provided. + ### Debug The OpenApi generator extracts an enhanced JSON data model from the specification YAML and uses this data model to feed the templates to generate the code. diff --git a/packages/@ama-sdk/schematics/collection.json b/packages/@ama-sdk/schematics/collection.json index 5b9a232603..17e0837db5 100644 --- a/packages/@ama-sdk/schematics/collection.json +++ b/packages/@ama-sdk/schematics/collection.json @@ -7,6 +7,11 @@ "schema": "./schematics/ng-add/schema.json", "aliases": ["install", "i"] }, + "migrate": { + "description": "Execute migration scripts between 2 versions.", + "factory": "./schematics/migrate/index#migrate", + "schema": "./schematics/migrate/schema.json" + }, "typescript-mock": { "description": "Generate an api mock into the project.", "factory": "./schematics/typescript/mock/index#ngGenerateMock", diff --git a/packages/@ama-sdk/schematics/schematics/helpers/is-typescript-project.ts b/packages/@ama-sdk/schematics/schematics/helpers/is-typescript-project.ts new file mode 100644 index 0000000000..ba4e16ca1b --- /dev/null +++ b/packages/@ama-sdk/schematics/schematics/helpers/is-typescript-project.ts @@ -0,0 +1,12 @@ +import type { Tree } from '@angular-devkit/schematics'; + +/** + * Determine if the targeted SDK is typescript based + * @param tree Schematic Tree + * @param pathInTree Path to the SDK in the tree + */ +export const isTypescriptSdk = (tree: Tree, pathInTree = '/') => { + return tree.getDir(pathInTree) + .subfiles + .some((filePath) => /tsconfig[^/\\]*\.json$/.test(filePath)); +}; diff --git a/packages/@ama-sdk/schematics/schematics/migrate/index.ts b/packages/@ama-sdk/schematics/schematics/migrate/index.ts new file mode 100644 index 0000000000..213f1d4318 --- /dev/null +++ b/packages/@ama-sdk/schematics/schematics/migrate/index.ts @@ -0,0 +1,39 @@ +import type { Rule } from '@angular-devkit/schematics'; +import { MigrateSchematicsSchemaOptions } from './schema'; +import { getMigrationRuleRunner, getWorkspaceConfig, type MigrationRulesMap } from '@o3r/schematics'; +import { resolve } from 'node:path'; +import { gt, minVersion } from 'semver'; +import { isTypescriptSdk } from '../helpers/is-typescript-project'; + +const tsMigrationMap: MigrationRulesMap = { + +}; +/** + * Facilitate the migration of a version to another by the run of migration rules + * @param options + */ +function migrateFn(options: MigrateSchematicsSchemaOptions): Rule { + + const currentVersion = JSON.parse(require(resolve(__dirname, '..', '..', 'package.json'))).version; + const to: string = options.to || currentVersion; + const minimumVersion = minVersion(to); + + return (tree, context) => { + if (minimumVersion && gt(minimumVersion, currentVersion)) { + context.logger.warn(`The specified range "${to}" has a minimum supported version higher than the current version of @ama-sdk/schematics (${currentVersion}).` + + ' The migration may not have any effect.'); + } + const workingDirectory = options?.projectName && getWorkspaceConfig(tree)?.projects[options.projectName]?.root || '/'; + const runMigrateSchematic = isTypescriptSdk(tree, workingDirectory) ? getMigrationRuleRunner(tsMigrationMap, { logger: context.logger }) : undefined; + return runMigrateSchematic?.({from: options.from, to}); + }; +} + +/** + * Facilitate the migration of a version to another by the run of migration rules + * @param options + */ +export const migrate = (options: MigrateSchematicsSchemaOptions) => async () => { + const { createSchematicWithMetricsIfInstalled } = await import('@o3r/schematics'); + return createSchematicWithMetricsIfInstalled(migrateFn)(options); +}; diff --git a/packages/@ama-sdk/schematics/schematics/migrate/schema.json b/packages/@ama-sdk/schematics/schematics/migrate/schema.json new file mode 100644 index 0000000000..7766e9a173 --- /dev/null +++ b/packages/@ama-sdk/schematics/schematics/migrate/schema.json @@ -0,0 +1,28 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "$id": "MigrateSchematicsSchema", + "title": "Execute migration scripts between 2 versions", + "description": "Schematics to migrate from a specific version to another one", + "properties": { + "from": { + "type": "string", + "description": "Starting version from which the migration scripts are executed", + "x-prompt": "What was the original version before migration?" + }, + "to": { + "type": "string", + "description": "Version of the package to migrate to (will use the current version if not specified)" + }, + "projectName": { + "type": "string", + "description": "Project name (in case it is applied to a module from an Angular Project)", + "$default": { + "$source": "projectName" + } + } + }, + "additionalProperties": true, + "required": [ + "from" + ] +} diff --git a/packages/@ama-sdk/schematics/schematics/migrate/schema.ts b/packages/@ama-sdk/schematics/schematics/migrate/schema.ts new file mode 100644 index 0000000000..a2ca0c5a64 --- /dev/null +++ b/packages/@ama-sdk/schematics/schematics/migrate/schema.ts @@ -0,0 +1,9 @@ +/** Schematic Option */ +export interface MigrateSchematicsSchemaOptions { + /** Starting version from which the migration scripts are executed */ + from: string; + /** Version of the package to migrate to (will use the current version if not specified) */ + to?: string; + /** Project name */ + projectName?: string | undefined; +} diff --git a/packages/@ama-sdk/schematics/schematics/ng-update/index.ts b/packages/@ama-sdk/schematics/schematics/ng-update/index.ts index f37932d9ef..634db42e0a 100644 --- a/packages/@ama-sdk/schematics/schematics/ng-update/index.ts +++ b/packages/@ama-sdk/schematics/schematics/ng-update/index.ts @@ -1,16 +1,9 @@ /* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable camelcase */ -import { Rule, Tree } from '@angular-devkit/schematics'; +import type { Rule } from '@angular-devkit/schematics'; import { updateV10_0 as tsUpdateV10_0 } from './typescript'; - -/** - * Determine if the script is run in a Typescript SDK - * @param tree - */ -const isTypescriptSdk = (tree: Tree) => { - return tree.exists('/tsconfig.json'); -}; +import { isTypescriptSdk } from '../helpers/is-typescript-project'; /** * update of Otter library V10.0 diff --git a/packages/@ama-sdk/schematics/schematics/typescript/shell/templates/base/package.json.template b/packages/@ama-sdk/schematics/schematics/typescript/shell/templates/base/package.json.template index 7b964c27f3..9165333bcb 100644 --- a/packages/@ama-sdk/schematics/schematics/typescript/shell/templates/base/package.json.template +++ b/packages/@ama-sdk/schematics/schematics/typescript/shell/templates/base/package.json.template @@ -29,6 +29,7 @@ } }, "scripts": { + "schematics": "schematics", "clean": "rimraf test/ test-dev/ dist/ dist-dev/ dist-test/ build/", "lint:ci": "eslint \"**/*[jt]s\" --quiet --format junit --output-file ./dist-lint/result.xml", "lint": "eslint \"**/*[jt]s\" --cache", diff --git a/packages/@o3r/schematics/src/utility/index.ts b/packages/@o3r/schematics/src/utility/index.ts index 96227516aa..fcc202b7e8 100644 --- a/packages/@o3r/schematics/src/utility/index.ts +++ b/packages/@o3r/schematics/src/utility/index.ts @@ -22,3 +22,4 @@ export * from './update-imports'; export * from './update-pipes'; export * from './builder'; export * from './wrapper'; +export * from './migration/migration'; diff --git a/packages/@o3r/schematics/src/utility/migration/migration.spec.ts b/packages/@o3r/schematics/src/utility/migration/migration.spec.ts new file mode 100644 index 0000000000..1c70f0a450 --- /dev/null +++ b/packages/@o3r/schematics/src/utility/migration/migration.spec.ts @@ -0,0 +1,50 @@ +import { callRule, Tree } from '@angular-devkit/schematics'; +import { getMigrationRuleRunner } from './migration'; +import { firstValueFrom } from 'rxjs'; + +describe('getMigrationRuleRunner', () => { + + it('should execute rule when in the range', async () => { + const spy = jest.fn(); + const runnner = getMigrationRuleRunner({ + // eslint-disable-next-line @typescript-eslint/naming-convention + '10.0.*': spy + }); + + const rules = runnner({ from: '9.0.0', to: '10.1.0' }); + + await firstValueFrom(callRule(rules, Tree.empty(), {} as any)); + + expect(spy).toHaveBeenCalled(); + }); + + it('should not execute rule when not in the range', async () => { + const spy = jest.fn(); + const spy2 = jest.fn(); + const runnner = getMigrationRuleRunner({ + // eslint-disable-next-line @typescript-eslint/naming-convention + '8.*': spy2, + // eslint-disable-next-line @typescript-eslint/naming-convention + '10.0.*': spy + }); + + const rules = runnner({ from: '8.0.0', to: '9.1.0' }); + await firstValueFrom(callRule(rules, Tree.empty(), {} as any)); + + expect(spy).not.toHaveBeenCalled(); + expect(spy2).toHaveBeenCalled(); + }); + + it('should execute rule when in the range without limit', async () => { + const spy = jest.fn(); + const runnner = getMigrationRuleRunner({ + // eslint-disable-next-line @typescript-eslint/naming-convention + '10.0.*': spy + }); + + const rules = runnner({ from: '9.0.0' }); + await firstValueFrom(callRule(rules, Tree.empty(), {} as any)); + + expect(spy).toHaveBeenCalled(); + }); +}); diff --git a/packages/@o3r/schematics/src/utility/migration/migration.ts b/packages/@o3r/schematics/src/utility/migration/migration.ts new file mode 100644 index 0000000000..07b9a298e2 --- /dev/null +++ b/packages/@o3r/schematics/src/utility/migration/migration.ts @@ -0,0 +1,60 @@ +import type { LoggerApi } from '@angular-devkit/core/src/logger'; +import { chain, type Rule } from '@angular-devkit/schematics'; +import { intersects, Range, validRange } from 'semver'; + +/** Create the migration */ +interface MigrateRuleRunnerOptions { + /** The original version from which to execute the rules */ + from: string; + + /** + * The last version for which to execute the rule. + * If not specified, all the versions from the "from" parameter will be considered. + */ + to?: string; +} + +/** Mapping of rules to apply to its specific range */ +export interface MigrationRulesMap { + /** Rules to apply to a specific semver range */ + [range: string]: Rule | Rule[]; +} + +/** + * Option for migration rule runner factory + */ +interface MigrationRuleRunnerOptions { + /** Logger */ + logger?: LoggerApi; +} + +/** + * Generate the Migration Rule Schematic runner to execute rules according to the range + * @param rulesMapping Mapping of rules to execute based on its semver range + * @param options Additional options + */ +export function getMigrationRuleRunner(rulesMapping: MigrationRulesMap, options?: MigrationRuleRunnerOptions) { + const rangeMapping = Object.entries(rulesMapping) + .reduce((acc, [range, rule]) => { + const checkedRange = validRange(range); + if (!checkedRange) { + options?.logger?.warn(`The range "${range}" is invalid and will be ignored in the Migration rule`); + } else { + acc.push([checkedRange, Array.isArray(rule) ? rule : [rule]]); + } + return acc; + }, [] as [string, Rule[]][]); + + /** + * Migration rule runner + * @param config Provide information regarding the range to match + */ + return (config: MigrateRuleRunnerOptions): Rule => { + const fromToRange = new Range(config.to ? `>${config.from} <=${config.to}` : `>${config.from}`); + return chain( + rangeMapping + .filter(([range]) => intersects(range, fromToRange)) + .map(([_, rules]) => chain(rules)) + ); + }; +} diff --git a/tools/renovate/tasks/sdk-regenerate.json b/tools/renovate/tasks/sdk-regenerate.json index e5f576de55..4834e8b16e 100644 --- a/tools/renovate/tasks/sdk-regenerate.json +++ b/tools/renovate/tasks/sdk-regenerate.json @@ -9,6 +9,7 @@ "postUpgradeTasks": { "commands": [ "{{arg0}} install", + "{{arg0}} run schematics @ama-sdk/schematics:migrate -- --from={{{currentVersion}}} --to={{{newVersion}}}", "{{arg0}} run spec:upgrade" ], "fileFilters": [