Skip to content

Commit

Permalink
fix: enforce basic preset (#1204)
Browse files Browse the repository at this point in the history
## Proposed change

Enforce Basic preset installation to avoid full installation at
workspace level
  • Loading branch information
kpanot authored Jan 15, 2024
2 parents 1808a9f + 3a376a7 commit f15302a
Show file tree
Hide file tree
Showing 12 changed files with 105 additions and 58 deletions.
4 changes: 3 additions & 1 deletion packages/@ama-sdk/core/schematics/ng-add/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ describe('Ng add @ama-sdk/core', () => {
const initialTree = Tree.empty();
initialTree.create('angular.json', readFileSync(join(__dirname, 'mocks', 'angular.mocks.json')));
initialTree.create('src/example.ts', readFileSync(join(__dirname, 'mocks', 'example.ts.mock')));
const tree = await firstValueFrom(callRule(ngAdd, initialTree, undefined as any));
const context: any = { addTask: jest.fn() };
const tree = await firstValueFrom(callRule(ngAdd, initialTree, context));
const newContent = tree.readText('src/example.ts');
expect(newContent).not.toContain('@dapi/sdk-core');
expect(newContent).toContain('from \'@ama-sdk/core\'');
expect(newContent).toContain('@whatever/package');
expect(context.addTask).toHaveBeenCalled();
});
});
94 changes: 57 additions & 37 deletions packages/@ama-sdk/core/schematics/ng-add/index.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import {chain, Rule, SchematicContext, Tree} from '@angular-devkit/schematics';
import {chain, type Rule} from '@angular-devkit/schematics';
import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';
import * as ts from 'typescript';
import * as path from 'node:path';

/**
* Rule to import all the necessary dependency to run an @ama-sdk based application
* Helps to migrate from previous versions with an import replacement
*/
export function ngAdd(): Rule {

const removeImports = async (_: Tree, context: SchematicContext) => {
const checkSchematicsDependency: Rule = async (_, context) => {
try {
const {removePackages} = await import('@o3r/schematics');
return removePackages(['@dapi/sdk-core']);
await import('@o3r/schematics');
} catch (e) {
// o3r extractors needs o3r/core as peer dep. o3r/core will install o3r/schematics
context.logger.error(`[ERROR]: Adding @ama-sdk/core has failed.
Expand All @@ -20,42 +21,61 @@ export function ngAdd(): Rule {
}
};

const removeImports: Rule = async () => {
const {removePackages} = await import('@o3r/schematics');
return removePackages(['@dapi/sdk-core']);
};

/* ng add rules */
const updateImports = async (tree: Tree, context: SchematicContext) => {
try {
const {getFilesInFolderFromWorkspaceProjectsInTree} = await import('@o3r/schematics');
const files = getFilesInFolderFromWorkspaceProjectsInTree(tree, '', 'ts');
files.forEach((file) => {
const sourceFile = ts.createSourceFile(
file,
tree.readText(file),
ts.ScriptTarget.ES2015,
true
);
const dapiSdkCodeImports = sourceFile.statements.filter((node): node is ts.ImportDeclaration =>
ts.isImportDeclaration(node)
&& !!node.moduleSpecifier.getText().match(/['"]@dapi\/sdk-core['"]/)
);
if (dapiSdkCodeImports.length) {
const recorder = tree.beginUpdate(file);
dapiSdkCodeImports.forEach((imp) => {
recorder.remove(imp.moduleSpecifier.getStart(), imp.moduleSpecifier.getWidth());
recorder.insertRight(imp.moduleSpecifier.getStart(), '\'@ama-sdk/core\'');
});
tree.commitUpdate(recorder);
}
return tree;
});
} catch (e) {
// o3r extractors needs o3r/core as peer dep. o3r/core will install o3r/schematics
context.logger.error(`[ERROR]: Adding @ama-sdk/core has failed.
If the error is related to missing @o3r dependencies you need to install '@o3r/schematics' to be able to use the @ama-sdk/core ng add. Please run 'ng add @o3r/schematics' .
Otherwise, use the error message as guidance.`);
throw (e);
}
const updateImports: Rule = async (tree) => {
const {getFilesInFolderFromWorkspaceProjectsInTree} = await import('@o3r/schematics');
const files = getFilesInFolderFromWorkspaceProjectsInTree(tree, '', 'ts');
files.forEach((file) => {
const sourceFile = ts.createSourceFile(
file,
tree.readText(file),
ts.ScriptTarget.ES2015,
true
);
const dapiSdkCodeImports = sourceFile.statements.filter((node): node is ts.ImportDeclaration =>
ts.isImportDeclaration(node)
&& !!node.moduleSpecifier.getText().match(/['"]@dapi\/sdk-core['"]/)
);
if (dapiSdkCodeImports.length) {
const recorder = tree.beginUpdate(file);
dapiSdkCodeImports.forEach((imp) => {
recorder.remove(imp.moduleSpecifier.getStart(), imp.moduleSpecifier.getWidth());
recorder.insertRight(imp.moduleSpecifier.getStart(), '\'@ama-sdk/core\'');
});
tree.commitUpdate(recorder);
}
return tree;
});
};


const addMandatoryPeerDeps: Rule = async (_, context) => {
const { getPeerDepWithPattern } = await import('@o3r/schematics');

const peerDepToInstall = getPeerDepWithPattern(path.resolve(__dirname, '..', '..', 'package.json'), [
'chokidar',
'cpy',
'minimist'
]);
context.addTask(new NodePackageInstallTask({
packageName: Object.entries(peerDepToInstall.matchingPackagesVersions)
// eslint-disable-next-line @typescript-eslint/no-base-to-string, @typescript-eslint/restrict-template-expressions
.map(([dependency, version]) => `${dependency}@${version || 'latest'}`)
.join(' '),
hideOutput: false,
quiet: false
}));
};

return chain([
checkSchematicsDependency,
removeImports,
updateImports
updateImports,
addMandatoryPeerDeps
]);
}
2 changes: 1 addition & 1 deletion packages/@ama-sdk/core/tsconfig.spec.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"target": "ES2020"
},
"include": [
"./src/**/*.spec.ts",
"./**/*.spec.ts",
"./schematics/ng-add/mocks/**/*.ts"
],
"exclude": [
Expand Down
4 changes: 2 additions & 2 deletions packages/@o3r/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@
"@angular/cli": {
"optional": true
},
"@ngrx/effects": {
"@ngrx/entity": {
"optional": true
},
"@ngrx/router-store": {
"@ngrx/store": {
"optional": true
},
"@nrwl/devkit": {
Expand Down
5 changes: 2 additions & 3 deletions packages/@o3r/core/schematics/rule-factories/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ const ngrxEntityDep = '@ngrx/entity';
const ngrxStoreDep = '@ngrx/store';
const ngrxRouterStore = '@ngrx/router-store';
const ngrxRouterStoreDevToolDep = '@ngrx/store-devtools';
// TODO Remove this explicit dependency when correctly brought by the ng-add of @o3r/store-sync
const fastDeepEqualDep = 'fast-deep-equal';

/**
* Add Redux Store support
*
* @param options @see RuleFactory.options
* @param rootPath @see RuleFactory.rootPath
* @param options.projectName
* @param options.workingDirectory
* @param projectType
*/
export function updateStore(options: { projectName?: string | undefined; workingDirectory?: string | undefined}, projectType?: WorkspaceProject['projectType']): Rule {
Expand Down Expand Up @@ -58,7 +59,6 @@ export function updateStore(options: { projectName?: string | undefined; working

/**
* Changed package.json start script to run localization generation
*
* @param tree
* @param context
*/
Expand Down Expand Up @@ -86,7 +86,6 @@ export function updateStore(options: { projectName?: string | undefined; working

/**
* Edit main module with the translation required configuration
*
* @param tree
* @param context
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/@o3r/core/src/store/async/async-entity.adapter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {EntityAdapter, EntityState, Update} from '@ngrx/entity';
import type {EntityAdapter, EntityState, Update} from '@ngrx/entity';
import {asyncStoreItemAdapter} from './async.adapter';
import {AsyncStoreItem, EntityWithoutAsyncStoreItem} from './async.interfaces';

Expand Down
2 changes: 1 addition & 1 deletion packages/@o3r/core/src/store/async/async.serializer.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EntityState } from '@ngrx/entity';
import type { EntityState } from '@ngrx/entity';
import { asyncStoreItemAdapter } from './async.adapter';
import { AsyncStoreItem, EntityStatus } from './async.interfaces';

Expand Down
6 changes: 6 additions & 0 deletions packages/@o3r/create/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,16 @@ const logo = `
const binPath = join(require.resolve('@angular/cli/package.json'), '../bin/ng.js');
const args = process.argv.slice(2).filter((a) => a !== '--create-application');

// Default styling to Sass
if (!args.some((a) => a.startsWith('--style'))) {
args.push('--style', 'scss');
}

// Default Preset to Basic
if (!args.some((a) => a.startsWith('--preset'))) {
args.push('--preset', 'basic');
}

const hasPackageManagerArg = args.some((a) => a.startsWith('--package-manager'));
if (!hasPackageManagerArg) {
const packageManager = process.env.npm_config_user_agent?.split('/')[0];
Expand Down
16 changes: 10 additions & 6 deletions packages/@o3r/schematics/src/utility/matching-peers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@ import { PackageJson } from 'type-fest';
* @param packageJsonPath
* @param pattern
*/
export function getPeerDepWithPattern(packageJsonPath: string, pattern = /^@(otter|o3r|ama-sdk)/) {
export function getPeerDepWithPattern(packageJsonPath: string, pattern: RegExp | string[] = /^@(otter|o3r|ama-sdk)/) {
const packageJsonContent: PackageJson = JSON.parse(fs.readFileSync(packageJsonPath, { encoding: 'utf-8' }));
const packageName = packageJsonContent.name;
const packageVersion = packageJsonContent.version;
const optionalPackages = Object.entries(packageJsonContent.peerDependenciesMeta || {})
.filter(([, dep]) => dep?.optional)
.map(([depName]) => depName);

const matchingPackages = Object.keys(packageJsonContent.peerDependencies || [])
.filter(peerDep => pattern.test(peerDep) && !optionalPackages.includes(peerDep));
const matchingPackagesVersions = Object.fromEntries(
Object.entries(packageJsonContent.peerDependencies || {})
.filter(([peerDep]) => (Array.isArray(pattern) ? pattern.includes(peerDep) : pattern.test(peerDep)) && !optionalPackages.includes(peerDep))
);
const matchingPackages = Object.keys(matchingPackagesVersions);

return { packageName, packageVersion, matchingPackages };
return { packageName, packageVersion, matchingPackages, matchingPackagesVersions };
}

const basicsPackageName = new Set([
Expand All @@ -32,12 +35,13 @@ const basicsPackageName = new Set([
* @param packageJsonPath The package json on which we search for o3r peer deps
* @param filterBasics If activated it will remove the basic peer deps (o3r/core, o3r/dev-tools, o3r/workspace and o3r/schematics) from the list of results
* @param packagePattern Pattern of the package name to look in the packages peer dependencies.
* @param versionRangePrefix Prefix to add to the package version to determine Semver Range
*/
export function getO3rPeerDeps(packageJsonPath: string, filterBasics = true, packagePattern = /^@(?:o3r|ama-sdk)/) {
export function getO3rPeerDeps(packageJsonPath: string, filterBasics = true, packagePattern = /^@(?:o3r|ama-sdk)/, versionRangePrefix = '^') {
const depsInfo = getPeerDepWithPattern(packageJsonPath, packagePattern);
return {
packageName: depsInfo.packageName,
packageVersion: depsInfo.packageVersion,
packageVersion: versionRangePrefix + depsInfo.packageVersion,
o3rPeerDeps: filterBasics ?
depsInfo.matchingPackages.filter((peerDep) => !basicsPackageName.has(peerDep))
: depsInfo.matchingPackages
Expand Down
18 changes: 16 additions & 2 deletions packages/@o3r/store-sync/schematics/ng-add/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { chain, noop, Rule } from '@angular-devkit/schematics';
import type { NgAddSchematicsSchema } from './schema';
import * as path from 'node:path';
import { NodePackageInstallTask } from '@angular-devkit/schematics/tasks';

/**
* Add Otter store-sync to an Otter Project
Expand All @@ -10,7 +11,7 @@ export function ngAdd(options: NgAddSchematicsSchema): Rule {
return async (tree, context) => {
try {
// use dynamic import to properly raise an exception if it is not an Otter project.
const { applyEsLintFix, install, ngAddPackages, getO3rPeerDeps, getWorkspaceConfig } = await import('@o3r/schematics');
const { applyEsLintFix, install, ngAddPackages, getO3rPeerDeps, getWorkspaceConfig, getPeerDepWithPattern } = await import('@o3r/schematics');
// retrieve dependencies following the /^@o3r\/.*/ pattern within the peerDependencies of the current module
const depsInfo = getO3rPeerDeps(path.resolve(__dirname, '..', '..', 'package.json'));
const workingDirectory = options?.projectName && getWorkspaceConfig(tree)?.projects[options.projectName]?.root || '.';
Expand All @@ -26,7 +27,20 @@ export function ngAdd(options: NgAddSchematicsSchema): Rule {
parentPackageInfo: `${depsInfo.packageName!} - setup`,
projectName: options.projectName,
workingDirectory
})
}),

// Add mandatory peerDependency
(_, ctx) => {
const peerDepToInstall = getPeerDepWithPattern(path.resolve(__dirname, '..', '..', 'package.json'), ['fast-deep-equal']);
ctx.addTask(new NodePackageInstallTask({
packageName: Object.entries(peerDepToInstall.matchingPackagesVersions)
// eslint-disable-next-line @typescript-eslint/no-base-to-string, @typescript-eslint/restrict-template-expressions
.map(([dependency, version]) => `${dependency}@${version || 'latest'}`)
.join(' '),
hideOutput: false,
quiet: false
}));
}
]);
} catch (e) {
// If the installation is initialized in a non-Otter application, mandatory packages will be missing. We need to notify the user
Expand Down
6 changes: 4 additions & 2 deletions packages/@o3r/testing/schematics/ng-add/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,11 @@ export function ngAdd(options: NgAddSchematicsSchema): Rule {
packageJsonFile.scripts ||= {};
packageJsonFile.scripts.test = 'jest';
tree.overwrite(`${workingDirectory}/package.json`, JSON.stringify(packageJsonFile, null, 2));
const rootRelativePath = path.posix.relative(workingDirectory, tree.root.path.replace(/^\//, './'));
const jestConfigFilesForProject = () => mergeWith(apply(url('./templates/project'), [
template({
...options,
rootRelativePath: path.posix.relative(workingDirectory, tree.root.path.replace(/^\//, './')),
rootRelativePath,
isAngularSetup: tree.exists('/angular.json')
}),
move(workingDirectory),
Expand All @@ -123,7 +124,8 @@ export function ngAdd(options: NgAddSchematicsSchema): Rule {

const jestConfigFilesForWorkspace = () => mergeWith(apply(url('./templates/workspace'), [
template({
...options
...options,
rootRelativePath
}),
move(tree.root.path),
renameTemplateFiles()
Expand Down
4 changes: 2 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -6921,9 +6921,9 @@ __metadata:
peerDependenciesMeta:
"@angular/cli":
optional: true
"@ngrx/effects":
"@ngrx/entity":
optional: true
"@ngrx/router-store":
"@ngrx/store":
optional: true
"@nrwl/devkit":
optional: true
Expand Down

0 comments on commit f15302a

Please sign in to comment.