Skip to content

Commit

Permalink
feat(cli): support multiple projects (#1664)
Browse files Browse the repository at this point in the history
  • Loading branch information
cipchk authored Nov 9, 2023
1 parent 8ab0e82 commit e5476e2
Show file tree
Hide file tree
Showing 10 changed files with 232 additions and 100 deletions.
12 changes: 12 additions & 0 deletions docs/getting-started.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,18 @@ npm start
# Or use HMR mode by: npm run hmr
```

**Multiple projects**

```bash
yarn global add @angular/cli
ng new my-workspace --no-create-application --package-manager yarn
cd my-workspace
ng g application mgr --style less --routing
ng add ng-alain
yarn mgr:start
# Or use HMR mode by: yarn run mgr:hmr
```

> Please refer to [Schematics](/cli) for more details.
### Clone the Git Repository
Expand Down
8 changes: 7 additions & 1 deletion docs/getting-started.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,23 @@ NG-ALAIN 必须先创建一个全新的 Angular 项目,可以通过终端窗

```bash
ng new my-project --style less --routing
cd my-project
# 或多重项目
ng new my-workspace --no-create-application
cd my-workspace
ng g application mgr --style less --routing
```

> 如果你想了解 `--style``--routing` 参数,请参考 [ng new](https://angular.io/cli/new#options) 文档。
接下来只需要将 NG-ALAIN 添加到 `my-project` 项目中即可,在 `my-project` 目录下通过终端窗口中运行:

```bash
cd my-project
ng add ng-alain
```

> 若多重项目时,需要提供具体的项目名称。
NG-ALAIN 会询问是否需要一些额外的插件,一开始完全可以一路回车,这些插件都是可插拔,后期可以自行添加与移除。

> 以上只会生成干净的项目,可以直接用于生产环境中。你可能在[预览](https://ng-alain.gitee.io/)上看到许多示例页,它们全都可以在 [Github](https://github.com/ng-alain/ng-alain) 查看到源代码,当然也可以通过 Git 克隆代码的形式获得:
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,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`);
}
});
});
});
120 changes: 70 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,25 +27,29 @@ 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 { addESLintRule, UpgradeMainVersions } from '../utils/versions';

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

/** Remove files to be overwrite */
function removeOrginalFiles(): Rule {
Expand All @@ -69,41 +73,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 @@ -121,7 +109,7 @@ function fixBrowserBuilderBudgets(options: ApplicationOptions): Rule {
};
}

function addDependenciesToPackageJson(options: ApplicationOptions): Rule {
function addDependenciesToPackageJson(): Rule {
return (tree: Tree) => {
UpgradeMainVersions(tree);
// 3rd
Expand All @@ -134,34 +122,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/*'];
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, 'tsconfig.json', json);
writeJSON(tree, tsconfigPath, json);
return tree;
};
}
Expand Down Expand Up @@ -240,9 +240,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 @@ -369,33 +373,49 @@ 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),
// files
removeOrginalFiles(),
addFilesToRoot(options),
forceLess(),
addStyle(),
fixLang(options),
fixVsCode(),
fixAngularJson(options),
fixBrowserBuilderBudgets(options)
fixAngularJson(),
fixBrowserBuilderBudgets(),
fixNgAlainJson()
]);
};
}
Loading

0 comments on commit e5476e2

Please sign in to comment.