Skip to content

Commit

Permalink
feat: migration schematics
Browse files Browse the repository at this point in the history
  • Loading branch information
kpanot committed May 2, 2024
1 parent 8e8555b commit 6a9b544
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 0 deletions.
11 changes: 11 additions & 0 deletions packages/@ama-sdk/schematics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,17 @@ 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
```

> [!NOTE]
> The parameters `--from` is mandatory to provide the version of the `@ama-sdk/schematics` package original from which the rules should be run.
### 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.
Expand Down
5 changes: 5 additions & 0 deletions packages/@ama-sdk/schematics/collection.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
35 changes: 35 additions & 0 deletions packages/@ama-sdk/schematics/schematics/migrate/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Rule, type Tree } from '@angular-devkit/schematics';
import { MigrateSchematicsSchemaOptions } from './schema';
import { getMigrationRuleRunner, type MigrationRulesMap } from '@o3r/schematics';
import { resolve } from 'node:path';

const tsMigrationMap: MigrationRulesMap = {

};

const isTypescriptSdk = (tree: Tree) => {
return tree.exists('/tsconfig.json');
};

/**
* Generate a Extension of a API core definition
* @param options
*/
function migrateFn(options: MigrateSchematicsSchemaOptions): Rule {

const to: string = options.to || JSON.parse(require(resolve(__dirname, '..', '..', 'package.json'))).version;

return (tree, context) => {
const runMigrateSchematic = isTypescriptSdk(tree) ? getMigrationRuleRunner(tsMigrationMap, { logger: context.logger }) : undefined;
return runMigrateSchematic?.({from: options.from, to});
};
}

/**
* Generate a Extension of a API core definition
* @param options
*/
export const migrate = (options: MigrateSchematicsSchemaOptions) => async () => {
const { createSchematicWithMetricsIfInstalled } = await import('@o3r/schematics');
return createSchematicWithMetricsIfInstalled(migrateFn)(options);
};
21 changes: 21 additions & 0 deletions packages/@ama-sdk/schematics/schematics/migrate/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$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 executing the migration scripts",
"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)"
}
},
"additionalProperties": true,
"required": [
"from"
]
}
7 changes: 7 additions & 0 deletions packages/@ama-sdk/schematics/schematics/migrate/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** Schematic Option */
export interface MigrateSchematicsSchemaOptions {
/** Starting version from which executing the migration scripts */
from: string;
/** Version of the package to migrate to (will use the current version if not specified) */
to?: string;
}
1 change: 1 addition & 0 deletions packages/@o3r/schematics/src/utility/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@ export * from './update-imports';
export * from './update-pipes';
export * from './builder';
export * from './wrapper';
export * from './migration/migration';
50 changes: 50 additions & 0 deletions packages/@o3r/schematics/src/utility/migration/migration.spec.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});
60 changes: 60 additions & 0 deletions packages/@o3r/schematics/src/utility/migration/migration.ts
Original file line number Diff line number Diff line change
@@ -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 it's 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))
);
};
}

0 comments on commit 6a9b544

Please sign in to comment.