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

feat(cli): support multiple projects #1664

Merged
merged 6 commits into from
Nov 9, 2023
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
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
Loading