Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(#2758): set exact-o3r-version as default for all schematics if provided when adding @o3r/core #2769

Merged
merged 1 commit into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions packages/@o3r/application/schematics/ng-add/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@
},
"exactO3rVersion": {
"type": "boolean",
"description": "Use a pinned version for otter packages",
"default": false
"description": "Use a pinned version for otter packages"
}
},
"additionalProperties": true,
Expand Down
1 change: 1 addition & 0 deletions packages/@o3r/core/schematics/ng-add/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ function ngAddFn(options: NgAddSchematicsSchema): Rule {

return chain([
setupSchematicsParamsForProject({ '*:ng-add': { registerDevtool: options.withDevtool } }, options.projectName),
options.exactO3rVersion ? setupSchematicsParamsForProject({ '*:*': { exactO3rVersion: true } }, options.projectName) : noop(),
options.projectName ? prepareProject(options, dependenciesSetupConfig) : noop(),
registerPackageCollectionSchematics(corePackageJsonContent),
async (t, c) => {
Expand Down
3 changes: 1 addition & 2 deletions packages/@o3r/core/schematics/ng-add/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,7 @@
},
"exactO3rVersion": {
"type": "boolean",
"description": "Use a pinned version for otter packages",
"default": false
"description": "Use a pinned version for otter packages"
}
},
"additionalProperties": true,
Expand Down
2 changes: 1 addition & 1 deletion packages/@o3r/create/src/index.it.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ describe('Create new otter project command', () => {
expect(() => packageManagerInstall(execInAppOptions)).not.toThrow();

const appName = 'test-application';
expect(() => packageManagerExec({ script: 'ng', args: ['g', 'application', appName, '--exact-o3r-version'] }, execInAppOptions)).not.toThrow();
expect(() => packageManagerExec({ script: 'ng', args: ['g', 'application', appName] }, execInAppOptions)).not.toThrow();
expect(existsSync(path.join(inProjectPath, 'project'))).toBe(false);
expect(() => packageManagerRunOnProject(appName, true, { script: 'build' }, execInAppOptions)).not.toThrow();

Expand Down
1 change: 1 addition & 0 deletions packages/@o3r/schematics/src/rule-factories/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export * from './dev-tools/index';
export * from './eslint-fix/index';
export * from './get-test-frameworks/index';
export * from './ng-add/index';
export * from './options/index';
export * from './remove-packages/index';
export * from './update-imports/index';
export * from './vscode-extensions/index';
118 changes: 118 additions & 0 deletions packages/@o3r/schematics/src/rule-factories/options/index.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import {
callRule,
Rule,
SchematicContext,
Tree,
} from '@angular-devkit/schematics';
import {
lastValueFrom,
} from 'rxjs';
import {
createSchematicWithOptionsFromWorkspace,
} from './index';

let context: SchematicContext;

describe('createSchematicWithOptionsFromWorkspaceIfInstalled', () => {
beforeEach(() => {
context = {
schematic: {
description: {
collection: {
name: 'MyCollection'
},
name: 'MySchematic'
}
},
interactive: false
} as any as SchematicContext;
});

it('should call the original schematic with the options', async () => {
const rule = jest.fn((tree: Tree) => tree);

const originalSchematic = jest.fn((_opts: any): Rule => rule);
const schematic = createSchematicWithOptionsFromWorkspace(originalSchematic);
const options = {
example: 'test'
};
await lastValueFrom(callRule(schematic(options), Tree.empty(), context));
expect(originalSchematic).toHaveBeenCalled();
expect(originalSchematic).toHaveBeenCalledWith(expect.objectContaining(options));
expect(rule).toHaveBeenCalled();
});

it('should call the original schematic with the merge of the options + the options from the angular.json', async () => {
const initialTree = Tree.empty();
initialTree.create('angular.json', JSON.stringify({
schematics: {
'*:*': {
commonWithValue: 'default',
workspace: 'workspace',
commonWithUndefined: 'definedValue'
}
}
}, null, 2));
const rule = jest.fn((tree: Tree) => tree);

const originalSchematic = jest.fn((_opts: any): Rule => rule);
const schematic = createSchematicWithOptionsFromWorkspace(originalSchematic);
const options: any = {
commonWithValue: 'test',
option: 'option',
commonWithUndefined: undefined
};
await lastValueFrom(callRule(schematic(options), initialTree, context));
expect(originalSchematic).toHaveBeenCalled();
expect(originalSchematic).toHaveBeenCalledWith(expect.objectContaining({
commonWithValue: 'test',
workspace: 'workspace',
option: 'option',
commonWithUndefined: 'definedValue'
}));
expect(rule).toHaveBeenCalled();
});

it('should works if we chain schematic wrapper', async () => {
const rule = jest.fn((tree: Tree) => tree);

const originalSchematic = jest.fn((_opts: any): Rule => rule);
const noopSchematicWrapper = (schematicFn: (_opts: any) => Rule) => (opts: any): Rule => schematicFn(opts);
const schematic = noopSchematicWrapper(createSchematicWithOptionsFromWorkspace(originalSchematic));
const options = {
example: 'test'
};
await lastValueFrom(callRule(schematic(options), Tree.empty(), context));
expect(originalSchematic).toHaveBeenCalled();
expect(originalSchematic).toHaveBeenCalledWith(expect.objectContaining(options));
expect(rule).toHaveBeenCalled();
});

it('should throw the original error', async () => {
const error = new Error('error example');
const rule = jest.fn(() => {
throw error;
});

const originalSchematic = jest.fn((_opts: any): Rule => rule);
const schematic = createSchematicWithOptionsFromWorkspace(originalSchematic);
const options = {
example: 'test'
};
await expect(lastValueFrom(callRule(schematic(options), Tree.empty(), context))).rejects.toThrow(error);
expect(originalSchematic).toHaveBeenCalled();
expect(originalSchematic).toHaveBeenCalledWith(expect.objectContaining(options));
expect(rule).toHaveBeenCalled();
});

it('should throw if the rule is a rejected Promise', async () => {
const rule = jest.fn(() => Promise.reject(new Error('rejected')));

const originalSchematic = jest.fn((_opts: any): Rule => rule);
const schematic = createSchematicWithOptionsFromWorkspace(originalSchematic);
const options = {
example: 'test'
};
await expect(lastValueFrom(callRule(schematic(options), Tree.empty(), context))).rejects.toThrow();
});
});
40 changes: 40 additions & 0 deletions packages/@o3r/schematics/src/rule-factories/options/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type {
Rule,
} from '@angular-devkit/schematics';
import {
getDefaultOptionsForSchematic,
getWorkspaceConfig,
} from '../../utility';

/**
* Factory of the schematic to wrap
* @param options Options of the factory
*/
type SchematicWrapperFn<S extends Record<string, any>> = (options: S) => Rule;

/**
* Wrapper method of a schematic to retrieve options from workspace and merge it with the one from the run of the schematic
* @param schematicFn
*/
export function createSchematicWithOptionsFromWorkspace<S extends Record<string, any>>(schematicFn: SchematicWrapperFn<S>): SchematicWrapperFn<S> {
return (options) => (tree, context) => {
const workspace = getWorkspaceConfig(tree);
const workspaceOptions = getDefaultOptionsForSchematic(
workspace,
context.schematic.description.collection.name,
context.schematic.description.name,
{ projectName: undefined, ...options }
);
const schematicOptionsWithoutUndefined = Object.entries(options).reduce((acc: Record<string, any>, [key, value]) => {
if (typeof value !== 'undefined') {
acc[key] = value;
}
return acc;
}, {}) as S;
Comment on lines +28 to +33
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const schematicOptionsWithoutUndefined = Object.entries(options).reduce((acc: Record<string, any>, [key, value]) => {
if (typeof value !== 'undefined') {
acc[key] = value;
}
return acc;
}, {}) as S;
const schematicOptionsWithoutUndefined = Object.fromEntries(
Object.entries(options)
.filter(([, value]) => typeof value !== 'undefined')
) as S;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed it in the team and half of them preferred the .reduce() because we are not used to Object.fromEntries()

const schematicOptions = {
...workspaceOptions,
...schematicOptionsWithoutUndefined
};
return schematicFn(schematicOptions satisfies S);
};
}
37 changes: 15 additions & 22 deletions packages/@o3r/schematics/src/utility/collection.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ import type {
WorkspaceSchema,
} from '../interfaces';
import {
getSchematicOptions,
getDefaultOptionsForSchematic,
} from './collection';

const angularJsonGenericNgAdd: WorkspaceSchema = {
projects: {},
version: 1,
schematics: { '@o3r/components:component': { path: '' },
schematics: {
'@o3r/components:component': { path: '' },
'@o3r/services:service': { path: '' },
'@o3r/store:store': { path: '' },
'@o3r/core:schematics': { path: '' },
Expand All @@ -20,7 +21,8 @@ const angularJsonGenericNgAdd: WorkspaceSchema = {
const angularJsonSpecificNgAdd: WorkspaceSchema = {
projects: {},
version: 1,
schematics: { '@o3r/components:component': { path: '' },
schematics: {
'@o3r/components:component': { path: '' },
'@o3r/services:service': { path: '' },
'@o3r/store:store': { path: '' },
'@o3r/core:schematics': { path: '' },
Expand All @@ -33,7 +35,8 @@ const angularJsonSpecificNgAdd: WorkspaceSchema = {
const angularJsonNoGeneric: WorkspaceSchema = {
projects: {},
version: 1,
schematics: { '@o3r/components:component': { path: '' },
schematics: {
'@o3r/components:component': { path: '' },
'@o3r/services:service': { path: '' },
'@o3r/store:store': { path: '' },
'@o3r/core:schematics': { path: '' },
Expand All @@ -42,32 +45,21 @@ const angularJsonNoGeneric: WorkspaceSchema = {
} as any
};

const createFakeContext = (collection: string, name: string): any => ({
schematic: {
description: {
collection: {
name: collection
},
name
}
}
});

describe('Get schematics options', () => {
it('should return the ng-add generic options followed by overall generic options', () => {
const options = getSchematicOptions(angularJsonGenericNgAdd, createFakeContext('@o3r/core', 'ng-add'));
const options = getDefaultOptionsForSchematic(angularJsonGenericNgAdd, '@o3r/core', 'ng-add');
expect(Object.keys(options)[0]).toBe('enableMetadataExtract');
expect(Object.keys(options)[1]).toBe('libsDir');
expect(Object.keys(options).length).toBe(3);
});

it('should return the generic options when no matches for schematics name', () => {
const options = getSchematicOptions(angularJsonGenericNgAdd, createFakeContext('@o3r/core', 'dummy'));
const options = getDefaultOptionsForSchematic(angularJsonGenericNgAdd, '@o3r/core', 'dummy');
expect(options).toEqual(angularJsonGenericNgAdd.schematics['*:*']);
});

it('should return the specific o3r/core ng add, followed by ng-add generic options, followed by overall generic options', () => {
const options = getSchematicOptions(angularJsonSpecificNgAdd, createFakeContext('@o3r/core', 'ng-add'));
const options = getDefaultOptionsForSchematic(angularJsonSpecificNgAdd, '@o3r/core', 'ng-add');
expect(Object.keys(options)[0]).toBe('projectName');
expect(Object.keys(options)[1]).toBe('enableMetadataExtract');
expect(Object.keys(options)[2]).toBe('libsDir');
Expand All @@ -78,14 +70,15 @@ describe('Get schematics options', () => {
});

it('should return closest matching when no generic options present', () => {
const options = getSchematicOptions(angularJsonNoGeneric, createFakeContext('@o3r/core', 'ng-add'));
const options = getDefaultOptionsForSchematic(angularJsonNoGeneric, '@o3r/core', 'ng-add');
expect(Object.keys(options)[0]).toBe('projectName');
expect(Object.keys(options)[1]).toBe('enableMetadataExtract');
expect(Object.keys(options).length).toBe(2);
});

it('should return undefined when no generic options present and no matching', () => {
const options = getSchematicOptions(angularJsonNoGeneric, createFakeContext('@o3r/core', 'dummy'));
expect(options).toBeUndefined();
it('should return empty object when no generic options present and no matching', () => {
const options = getDefaultOptionsForSchematic(angularJsonNoGeneric, '@o3r/core', 'dummy');
expect(options).toBeDefined();
expect(Object.keys(options).length).toBe(0);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ jest.mock('@o3r/schematics', () => ({
getPackagesBaseRootFolder: jest.fn().mockReturnValue('/projects'),
getWorkspaceConfig: jest.fn().mockReturnValue({ projects: {} }),
isNxContext: jest.fn().mockReturnValue(false),
createSchematicWithMetricsIfInstalled: jest.fn().mockImplementation((fn: any) => fn)
createSchematicWithMetricsIfInstalled: jest.fn().mockImplementation((fn: any) => fn),
createSchematicWithOptionsFromWorkspace: jest.fn().mockImplementation((fn: any) => fn)
}));

jest.mock('@angular-devkit/schematics', () => {
Expand Down
3 changes: 2 additions & 1 deletion packages/@o3r/workspace/schematics/application/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from '@angular-devkit/schematics';
import {
createSchematicWithMetricsIfInstalled,
createSchematicWithOptionsFromWorkspace,
type DependencyToAdd,
enforceTildeRange,
getPackagesBaseRootFolder,
Expand Down Expand Up @@ -133,4 +134,4 @@ function generateApplicationFn(options: NgGenerateApplicationSchema): Rule {
* Add an Otter application to a monorepo
* @param options Schematic options
*/
export const generateApplication = createSchematicWithMetricsIfInstalled(generateApplicationFn);
export const generateApplication = createSchematicWithOptionsFromWorkspace(createSchematicWithMetricsIfInstalled(generateApplicationFn));
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@
},
"exactO3rVersion": {
"type": "boolean",
"description": "Use a pinned version for otter packages",
"default": false
"description": "Use a pinned version for otter packages"
}
},
"required": ["name"],
Expand Down
3 changes: 2 additions & 1 deletion packages/@o3r/workspace/schematics/library/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
import {
applyEsLintFix,
createSchematicWithMetricsIfInstalled,
createSchematicWithOptionsFromWorkspace,
type DependencyToAdd,
getPackagesBaseRootFolder,
getWorkspaceConfig,
Expand Down Expand Up @@ -88,4 +89,4 @@ function generateModuleFn(options: NgGenerateModuleSchema): Rule {
* Add an Otter compatible module to a monorepo
* @param options Schematic options
*/
export const generateModule = createSchematicWithMetricsIfInstalled(generateModuleFn);
export const generateModule = createSchematicWithOptionsFromWorkspace(createSchematicWithMetricsIfInstalled(generateModuleFn));
3 changes: 2 additions & 1 deletion packages/@o3r/workspace/schematics/ng-add/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from '@angular-devkit/schematics/tasks';
import {
createSchematicWithMetricsIfInstalled,
createSchematicWithOptionsFromWorkspace,
getPackageManagerExecutor,
getWorkspaceConfig,
registerPackageCollectionSchematics,
Expand Down Expand Up @@ -67,4 +68,4 @@ function ngAddFn(options: NgAddSchematicsSchema): Rule {
* Add Otter library to an Angular Project
* @param options
*/
export const ngAdd = createSchematicWithMetricsIfInstalled(ngAddFn);
export const ngAdd = createSchematicWithOptionsFromWorkspace(createSchematicWithMetricsIfInstalled(ngAddFn));
3 changes: 1 addition & 2 deletions packages/@o3r/workspace/schematics/ng-add/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@
},
"exactO3rVersion": {
"type": "boolean",
"description": "Use a pinned version for otter packages",
"default": false
"description": "Use a pinned version for otter packages"
},
"monorepoManager": {
"description": "Which monorepo manager to use",
Expand Down
3 changes: 2 additions & 1 deletion packages/@o3r/workspace/schematics/sdk/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
} from '@angular-devkit/schematics/tasks';
import {
createSchematicWithMetricsIfInstalled,
createSchematicWithOptionsFromWorkspace,
getPackageManager,
getPackagesBaseRootFolder,
getWorkspaceConfig,
Expand Down Expand Up @@ -131,4 +132,4 @@ function generateSdkFn(options: NgGenerateSdkSchema): Rule {
* Add an Otter compatible SDK to a monorepo
* @param options Schematic options
*/
export const generateSdk = createSchematicWithMetricsIfInstalled(generateSdkFn);
export const generateSdk = createSchematicWithOptionsFromWorkspace(createSchematicWithMetricsIfInstalled(generateSdkFn));
Empty file modified tools/github-actions/release/packaged-action/index.cjs
100755 → 100644
Empty file.
Loading