Skip to content

Commit

Permalink
feat(cli): support multiple projects
Browse files Browse the repository at this point in the history
  • Loading branch information
cipchk committed Oct 20, 2023
1 parent 4e8061e commit 4f3cd74
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 97 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@
"less-bundle-promise": "^1.0.11",
"ng-alain-codelyzer": "^0.0.1",
"ng-alain-sts": "^0.0.2",
"ng-alain-plugin-theme": "^15.0.1",
"ng-alain-plugin-theme": "^16.0.0",
"tsconfig-paths": "^4.2.0",
"@nguniversal/builders": "^16.2.0",
"@types/express": "^4.17.17",
Expand Down
69 changes: 69 additions & 0 deletions schematics/application/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,73 @@ describe('NgAlainSchematic: application', () => {
expect(content).not.toContain(`json-schema`);
});
});

describe('#multiple-projects', () => {
let runner: SchematicTestRunner;
let tree: UnitTestTree;
let projectName = 'mgr';
beforeEach(async () => {
const baseRunner = createNgRunner();
const workspaceTree = await baseRunner.runSchematic('workspace', {
name: 'workspace',
newProjectRoot: 'projects',
version: '16.0.0'
});
await baseRunner.runSchematic(
'application',
{
name: 'h5',
inlineStyle: false,
inlineTemplate: false,
routing: false,
style: 'css',
skipTests: false,
skipPackageJson: false
},
workspaceTree
);
tree = await baseRunner.runSchematic(
'application',
{
name: projectName,
inlineStyle: false,
inlineTemplate: false,
routing: false,
style: 'css',
skipTests: false,
skipPackageJson: false
},
workspaceTree
);
runner = createAlainRunner();
});
it(`should be working`, async () => {
tree = await runner.runSchematic(
'ng-add',
{
skipPackageJson: false,
project: projectName
},
tree
);
const content = tree.readContent(`/projects/${projectName}/src/app/shared/index.ts`);
expect(content).toContain(`json-schema`);
expect(tree.exists(`/projects/h5/src/app/shared/index.ts`)).toBe(false);
});
it(`should be throw error when not found project name`, async () => {
try {
tree = await runner.runSchematic(
'ng-add',
{
skipPackageJson: false,
project: `${projectName}invalid`
},
tree
);
expect(true).toBe(false);
} catch (ex) {
expect(ex.message).toContain(`Not found under the projects node of angular.json`);
}
});
});
});
121 changes: 71 additions & 50 deletions schematics/application/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
Tree,
url
} from '@angular-devkit/schematics';
import { getWorkspace, updateWorkspace } from '@schematics/angular/utility/workspace';
import { updateWorkspace } from '@schematics/angular/utility/workspace';

import { Schema as ApplicationOptions } from './schema';
import { getLangData } from '../core/lang.config';
Expand All @@ -27,26 +27,30 @@ import {
addHtmlToBody,
addPackage,
addSchematicCollections,
addStylePreprocessorOptionsToAllProject,
addStylePreprocessorOptions,
BUILD_TARGET_BUILD,
BUILD_TARGET_SERVE,
DEFAULT_WORKSPACE_PATH,
getNgAlainJson,
getProject,
getProjectFromWorkspace,
getProjectName,
isMulitProject,
readContent,
readJSON,
readPackage,
VERSION,
writeFile,
writeJSON,
writeNgAlainJson,
writePackage,
ZORROVERSION
} from '../utils';
import { addImportNotation } from '../utils/less';
import { addESLintRule, UpgradeMainVersions } from '../utils/versions';

let project: ProjectDefinition;
let projectName: string;
let mulitProject = false;

/** Remove files to be overwrite */
function removeOrginalFiles(): Rule {
Expand All @@ -70,41 +74,25 @@ function removeOrginalFiles(): Rule {
};
}

function fixAngularJson(options: ApplicationOptions): Rule {
function fixAngularJson(): Rule {
return updateWorkspace(async workspace => {
const p = getProjectFromWorkspace(workspace, options.project);
const p = getProjectFromWorkspace(workspace, projectName);
// Add proxy.conf.js
const serveTarget = p.targets?.get(BUILD_TARGET_SERVE);
if (serveTarget.options == null) serveTarget.options = {};
serveTarget.options.proxyConfig = 'proxy.conf.js';

// // 调整budgets, error in angular 15.1
// const budgets = (getProjectTarget(p, BUILD_TARGET_BUILD, 'configurations').production as JsonObject)
// .budgets as Array<{
// type: string;
// maximumWarning: string;
// maximumError: string;
// }>;
// if (budgets && budgets.length > 0) {
// const initial = budgets.find(w => w.type === 'initial');
// if (initial) {
// initial.maximumWarning = '2mb';
// initial.maximumError = '3mb';
// }
// }

addStylePreprocessorOptionsToAllProject(workspace);
addStylePreprocessorOptions(workspace, projectName);
addSchematicCollections(workspace);
addFileReplacements(workspace);
addFileReplacements(workspace, projectName);
});
}

/**
* Fix https://github.com/ng-alain/ng-alain/issues/2359
*/
function fixBrowserBuilderBudgets(options: ApplicationOptions): Rule {
function fixBrowserBuilderBudgets(): Rule {
return async (tree: Tree) => {
const projectName = getProjectName(await getWorkspace(tree), options.project);
const json = readJSON(tree, DEFAULT_WORKSPACE_PATH);
const budgets = json.projects[projectName].architect.build.configurations.production.budgets as Array<{
type: string;
Expand All @@ -122,7 +110,7 @@ function fixBrowserBuilderBudgets(options: ApplicationOptions): Rule {
};
}

function addDependenciesToPackageJson(options: ApplicationOptions): Rule {
function addDependenciesToPackageJson(): Rule {
return (tree: Tree) => {
UpgradeMainVersions(tree);
// 3rd
Expand All @@ -135,33 +123,46 @@ function addRunScriptToPackageJson(): Rule {
return (tree: Tree) => {
const json = readPackage(tree, 'scripts');
if (json == null) return tree;

const commandPrefix = mulitProject ? `${projectName}:` : '';
const commandFragment = mulitProject ? ` ${projectName}` : '';
json.scripts['ng-high-memory'] = `node --max_old_space_size=8000 ./node_modules/@angular/cli/bin/ng`;
json.scripts.start = `ng s -o`;
json.scripts.hmr = `ng s -o --hmr`;
json.scripts.build = `npm run ng-high-memory build`;
json.scripts.analyze = `npm run ng-high-memory build -- --source-map`;
json.scripts['analyze:view'] = `source-map-explorer dist/**/*.js`;
json.scripts['test-coverage'] = `ng test --code-coverage --watch=false`;
json.scripts['color-less'] = `ng-alain-plugin-theme -t=colorLess`;
json.scripts.theme = `ng-alain-plugin-theme -t=themeCss`;
json.scripts.icon = `ng g ng-alain:plugin icon`;
json.scripts[`${commandPrefix}start`] = `ng s${commandFragment} -o`;
json.scripts[`${commandPrefix}hmr`] = `ng s${commandFragment} -o --hmr`;
json.scripts[`${commandPrefix}build`] = `npm run ng-high-memory build${commandFragment}`;
json.scripts[`${commandPrefix}analyze`] = `npm run ng-high-memory build${commandFragment} -- --source-map`;
json.scripts[`${commandPrefix}analyze:view`] = `source-map-explorer dist/${
mulitProject ? `${projectName}/` : ''
}**/*.js`;
json.scripts[`${commandPrefix}test-coverage`] = `ng test${commandFragment} --code-coverage --watch=false`;
const themeCommand = mulitProject ? ` -n=${projectName}` : '';
json.scripts[`${commandPrefix}color-less`] = `ng-alain-plugin-theme -t=colorLess${themeCommand}`;
json.scripts[`${commandPrefix}theme`] = `ng-alain-plugin-theme -t=themeCss${themeCommand}`;
json.scripts[`${commandPrefix}icon`] = `ng g ng-alain:plugin icon${
mulitProject ? ` --project ${projectName}` : ''
}`;
json.scripts.prepare = `husky install`;
writePackage(tree, json);
return tree;
};
}

function addPathsToTsConfig(): Rule {
function addPathsToTsConfig(project: ProjectDefinition): Rule {
return (tree: Tree) => {
const json = readJSON(tree, 'tsconfig.json', 'compilerOptions');
if (project == null) return;
const tsconfigPath = project.targets?.get(BUILD_TARGET_BUILD)?.options?.tsConfig as string;
if (tsconfigPath == null) return;
const json = readJSON(tree, tsconfigPath);
if (json == null) return tree;
if (!json.compilerOptions) json.compilerOptions = {};
if (!json.compilerOptions.paths) json.compilerOptions.paths = {};
const paths = json.compilerOptions.paths;
paths['@shared'] = ['src/app/shared/index'];
paths['@core'] = ['src/app/core/index'];
paths['@env/*'] = ['src/environments/*'];
writeJSON(tree, 'tsconfig.json', json);
const commandPrefix = mulitProject ? `projects/${projectName}/` : '';
paths['@shared'] = [`${commandPrefix}src/app/shared/index`];
paths['@core'] = [`${commandPrefix}src/app/core/index`];
paths['@env/*'] = [`${commandPrefix}src/environments/*`];
paths['@_mock'] = ['_mock/index'];
writeJSON(tree, tsconfigPath, json);
return tree;
};
}
Expand Down Expand Up @@ -241,9 +242,13 @@ function addSchematics(options: ApplicationOptions): Rule {
}

function forceLess(): Rule {
return () => {
addAssetsToTarget([{ type: 'style', value: 'src/styles.less' }], 'add', [BUILD_TARGET_BUILD], null!, true);
};
return addAssetsToTarget(
[{ type: 'style', value: `${mulitProject ? `projects/${projectName}/` : ''}src/styles.less` }],
'add',
[BUILD_TARGET_BUILD],
projectName,
false
);
}

function addStyle(): Rule {
Expand Down Expand Up @@ -370,24 +375,39 @@ function fixVsCode(): Rule {
};
}

function fixNgAlainJson(): Rule {
return (tree: Tree) => {
const json = getNgAlainJson(tree);
if (json == null) return;

if (typeof json.projects !== 'object') json.projects = {};
if (!json.projects[projectName]) json.projects[projectName] = {};

writeNgAlainJson(tree, json);
};
}

export default function (options: ApplicationOptions): Rule {
return async (tree: Tree, context: SchematicContext) => {
project = (await getProject(tree, options.project)).project;
context.logger.info(`Generating NG-ALAIN scaffold...`);
const res = await getProject(tree, options.project);
mulitProject = isMulitProject(tree);
project = res.project;
projectName = res.name;
context.logger.info(`Generating NG-ALAIN scaffold to ${projectName} project...`);
return chain([
// @delon/* dependencies
addDependenciesToPackageJson(options),
addDependenciesToPackageJson(),
// Configuring CommonJS dependencies
// https://angular.io/guide/build#configuring-commonjs-dependencies
addAllowedCommonJsDependencies([]),
addAllowSyntheticDefaultImports(),
// ci
addRunScriptToPackageJson(),
addPathsToTsConfig(),
addPathsToTsConfig(project),
// code style
addCodeStylesToPackageJson(),
addSchematics(options),
addESLintRule(context, false),
addESLintRule(res.name),
addImportNotation(),
// files
removeOrginalFiles(),
Expand All @@ -396,8 +416,9 @@ export default function (options: ApplicationOptions): Rule {
addStyle(),
fixLang(options),
fixVsCode(),
fixAngularJson(options),
fixBrowserBuilderBudgets(options)
fixAngularJson(),
fixBrowserBuilderBudgets(),
fixNgAlainJson()
]);
};
}
23 changes: 20 additions & 3 deletions schematics/ng-add/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ function isYarn(tree: Tree): boolean {
return readJSON(tree, DEFAULT_WORKSPACE_PATH)?.cli?.packageManager === 'yarn';
}

function isValidProjectName(tree: Tree, name: string): boolean {
return Object.keys(readJSON(tree, DEFAULT_WORKSPACE_PATH)?.projects ?? {}).indexOf(name) !== -1;
}

function finished(): Rule {
return (_: Tree, context: SchematicContext) => {
context.addTask(new NodePackageInstallTask());
Expand All @@ -75,7 +79,9 @@ NG-ALAIN documentation site: https://ng-alain.com
export default function (options: NgAddOptions): Rule {
return (tree: Tree, context: SchematicContext) => {
if (!isYarn(tree)) {
context.logger.warn(`TIPS:: Please use yarn instead of NPM to install dependencies`);
context.logger.warn(
`TIPS:: Please use yarn instead of NPM to install dependencies, Pls refer to https://ng-alain.com/docs/getting-started/en#Installation`
);
}

const nodeVersion = getNodeMajorVersion();
Expand All @@ -89,8 +95,19 @@ export default function (options: NgAddOptions): Rule {

const pkg = readPackage(tree);

if (pkg.devDependencies['ng-alain']) {
throw new SchematicsException(`Already an NG-ALAIN project and can't be executed again: ng add ng-alain`);
if (options.project) {
if (!isValidProjectName(tree, options.project)) {
throw new SchematicsException(`Not found under the projects node of angular.json: ${options.project}`);
}
} else {
if (pkg.devDependencies['ng-alain']) {
throw new SchematicsException(`Already an NG-ALAIN project and can't be executed again: ng add ng-alain`);
}
if (!tree.exists('src/index.html')) {
throw new SchematicsException(
`NG-ALAIN must be attached to a new Angular project, so you need to use 'ng new projectName' to build a new Angular project, or specify the project name to be attached`
);
}
}

let ngCoreVersion = pkg.dependencies['@angular/core'] as string;
Expand Down
7 changes: 6 additions & 1 deletion schematics/ng-add/schema.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
{
"$schema": "http://json-schema.org/draft-07/schema",
"$id": "SchematicsNgAlainAdd",
"title": "NgAlain Add Options Schema",
"title": "NG-ALAIN Add Options Schema",
"type": "object",
"properties": {
"project": {
"type": "string",
"description": "The name of the project name.",
"x-prompt": "In which project do you want to create NG-ALAIN? (If you enter project name, it means using in multiple-projects)"
},
"defaultLanguage": {
"type": "string",
"description": "Specify default language [https://ng-alain.com/docs/i18n].",
Expand Down
1 change: 1 addition & 0 deletions schematics/ng-add/schema.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export interface Schema {
project?: string;
form?: boolean;
mock?: boolean;
defaultLanguage?: string;
Expand Down
Loading

0 comments on commit 4f3cd74

Please sign in to comment.