@rxap/plugin-angular 20.0.1-dev.10 → 20.0.1-dev.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/.eslintignore +6 -0
  2. package/.eslintrc.json +44 -0
  3. package/CHANGELOG.md +4 -0
  4. package/LICENSE +621 -0
  5. package/README.md.handlebars +115 -0
  6. package/jest.config.ts +11 -0
  7. package/package.json +2 -15
  8. package/project.json +68 -0
  9. package/schematics.yaml +6 -0
  10. package/src/application.ts +124 -0
  11. package/src/executors/check-ng-package/executor.ts +33 -0
  12. package/src/executors/config/executor.ts +74 -0
  13. package/src/executors/i18n/executor.ts +198 -0
  14. package/src/executors/tailwind/executor.ts +34 -0
  15. package/src/generators/convert-to-buildable-library/generator.ts +110 -0
  16. package/src/generators/fix-schematic/generator.ts +103 -0
  17. package/src/generators/fix-schematic/index.ts +5 -0
  18. package/src/generators/init/coerce-nx-json.ts +72 -0
  19. package/src/generators/init/generator.ts +28 -0
  20. package/src/generators/init/index.ts +5 -0
  21. package/src/generators/init/init-workspace.ts +84 -0
  22. package/src/generators/init-application/assert-main-statements.ts +74 -0
  23. package/src/generators/init-application/cleanup.ts +127 -0
  24. package/src/generators/init-application/coerce-app-config.ts +147 -0
  25. package/src/generators/init-application/coerce-environment-files.ts +105 -0
  26. package/src/generators/init-application/coerce-localazy-config-file.ts +26 -0
  27. package/src/generators/init-application/coerce-project.ts +128 -0
  28. package/src/generators/init-application/generate-authentication.ts +122 -0
  29. package/src/generators/init-application/generate-monolithic.spec.ts +45 -0
  30. package/src/generators/init-application/generate-monolithic.ts +57 -0
  31. package/src/generators/init-application/generator.ts +356 -0
  32. package/src/generators/init-application/index.ts +5 -0
  33. package/src/generators/init-application/link-mfe-remote-with-host.ts +60 -0
  34. package/src/generators/init-application/project-i18n-configuration.ts +4 -0
  35. package/src/generators/init-application/update-git-ignore.ts +22 -0
  36. package/src/generators/init-application/update-main-file.ts +118 -0
  37. package/src/generators/init-application/update-project-targets.ts +229 -0
  38. package/src/generators/init-application/update-tags.ts +30 -0
  39. package/src/generators/init-application/update-target-defaults.ts +43 -0
  40. package/src/generators/init-application/update-ts-config.ts +31 -0
  41. package/src/generators/init-component/generator.ts +147 -0
  42. package/src/generators/init-component/index.ts +5 -0
  43. package/src/generators/init-feature/generator.ts +77 -0
  44. package/src/generators/init-feature/index.ts +5 -0
  45. package/src/generators/init-feature-library/generator.ts +82 -0
  46. package/src/generators/init-feature-library/index.ts +5 -0
  47. package/src/generators/init-feature-library/init-project.ts +53 -0
  48. package/src/generators/init-feature-library/init-workspace.ts +10 -0
  49. package/src/generators/init-library/check-if-secondary-entrypoint-include-in-the-ts-config.ts +46 -0
  50. package/src/generators/init-library/cleanup.ts +22 -0
  51. package/src/generators/init-library/coerce-projects.ts +118 -0
  52. package/src/generators/init-library/coerce-tailwind-theme-scss.ts +27 -0
  53. package/src/generators/init-library/extend-angular-specific-eslint.ts +37 -0
  54. package/src/generators/init-library/generator.ts +87 -0
  55. package/src/generators/init-library/has-index-scss.ts +14 -0
  56. package/src/generators/init-library/has-tailwind-config.ts +9 -0
  57. package/src/generators/init-library/index.ts +5 -0
  58. package/src/generators/init-library/init-project.ts +46 -0
  59. package/src/generators/init-library/init-workspace.ts +26 -0
  60. package/src/generators/init-library/is-ng-packagr-project.ts +9 -0
  61. package/src/generators/init-library/ng-package-json.ts +23 -0
  62. package/src/generators/init-library/set-general-target-defaults.ts +36 -0
  63. package/src/generators/init-library/update-package-json.ts +26 -0
  64. package/src/generators/init-library/update-project-ng-package-configuration.ts +51 -0
  65. package/src/generators/init-library/update-ts-config.ts +24 -0
  66. package/src/generators/schematic/generator.ts +179 -0
  67. package/src/generators/schematic/index.ts +5 -0
  68. package/src/i18n.ts +130 -0
  69. package/src/index.ts +3 -0
  70. package/src/lib/angular-version.ts +1 -0
  71. package/src/lib/coerce-cypress-component-testing.ts +113 -0
  72. package/src/lib/coerce-test-setup.ts +60 -0
  73. package/src/library.ts +153 -0
  74. package/src/migrations/update-19-0-0/add-m2-prefix-to-material-scss-functions.ts +32 -0
  75. package/src/migrations/update-19-0-0/migration.ts +283 -0
  76. package/src/test-setup.ts +24 -0
  77. package/tsconfig.json +20 -0
  78. package/tsconfig.lib.json +10 -0
  79. package/tsconfig.spec.json +15 -0
  80. package/tsconfig.typedoc.json +5 -0
@@ -0,0 +1,24 @@
1
+ import { Tree } from '@nx/devkit';
2
+ import {
3
+ GetProjectRoot,
4
+ UpdateTsConfigJson,
5
+ } from '@rxap/workspace-utilities';
6
+
7
+ export function updateTsConfig(tree: Tree, projectName: string) {
8
+
9
+ const projectRoot = GetProjectRoot(tree, projectName);
10
+
11
+ for (const infix of [ 'lib', 'spec' ]) {
12
+ UpdateTsConfigJson(tree, tsConfig => {
13
+ tsConfig.compilerOptions ??= {};
14
+ tsConfig.compilerOptions.types ??= [];
15
+ if (!tsConfig.compilerOptions.types.includes('@angular/localize')) {
16
+ tsConfig.compilerOptions.types.push('@angular/localize');
17
+ }
18
+ }, {
19
+ basePath: projectRoot,
20
+ infix,
21
+ });
22
+ }
23
+
24
+ }
@@ -0,0 +1,179 @@
1
+ import {
2
+ formatFiles,
3
+ generateFiles,
4
+ GeneratorsJson,
5
+ joinPathFragments,
6
+ names,
7
+ readJson,
8
+ readProjectConfiguration,
9
+ Tree,
10
+ updateJson,
11
+ updateProjectConfiguration,
12
+ writeJson,
13
+ } from '@nx/devkit';
14
+ import {
15
+ CoerceAssets,
16
+ GetWorkspaceScope,
17
+ PackageJson,
18
+ } from '@rxap/workspace-utilities';
19
+ import * as path from 'path';
20
+ import { SchematicGeneratorSchema } from './schema';
21
+
22
+ type NormalizedSchema = SchematicGeneratorSchema & ReturnType<typeof names> & {
23
+ fileName: string;
24
+ className: string;
25
+ projectRoot: string;
26
+ projectSourceRoot: string;
27
+ npmScope: string;
28
+ npmPackageName: string;
29
+ };
30
+
31
+ function normalizeOptions(host: Tree, options: SchematicGeneratorSchema): NormalizedSchema {
32
+ const npmScope = GetWorkspaceScope(host);
33
+
34
+ const {
35
+ root: projectRoot,
36
+ sourceRoot: projectSourceRoot,
37
+ } = readProjectConfiguration(host, options.project);
38
+
39
+ const npmPackageName = readJson<{ name: string }>(host, path.join(projectRoot, 'package.json')).name;
40
+
41
+ let description: string;
42
+ if (options.description) {
43
+ description = options.description;
44
+ } else {
45
+ description = `${ options.name } schematic`;
46
+ }
47
+
48
+ if (!projectSourceRoot) {
49
+ throw new Error(`Project source root not found for project ${ options.project }`);
50
+ }
51
+
52
+ return {
53
+ ...options, ...names(options.name),
54
+ description,
55
+ projectRoot,
56
+ projectSourceRoot,
57
+ npmScope,
58
+ npmPackageName,
59
+ };
60
+ }
61
+
62
+ function addFiles(host: Tree, options: NormalizedSchema) {
63
+ generateFiles(host, path.join(__dirname, './files/schematic'), `${ options.projectSourceRoot }/schematics`, {
64
+ ...options,
65
+ schematicFnName: `${ options.propertyName }Schematic`,
66
+ schemaInterfaceName: `${ options.className }SchematicSchema`,
67
+ });
68
+ }
69
+
70
+ export async function createCollectionJson(host: Tree, projectRoot: string) {
71
+ updateJson<PackageJson>(host, joinPathFragments(projectRoot, 'package.json'), (json) => {
72
+ json.schematics ??= './collection.json';
73
+ return json;
74
+ });
75
+ writeJson<GeneratorsJson>(host, joinPathFragments(projectRoot, 'collection.json'), {
76
+ schematics: {},
77
+ });
78
+ }
79
+
80
+ async function updateCollectionJson(host: Tree, options: NormalizedSchema) {
81
+ const packageJson = readJson<PackageJson>(host, joinPathFragments(options.projectRoot, 'package.json'));
82
+ const packageJsonSchematics = packageJson.schematics;
83
+ let schematicsPath = packageJsonSchematics ? joinPathFragments(options.projectRoot, packageJsonSchematics) : null;
84
+
85
+ if (!schematicsPath) {
86
+ schematicsPath = joinPathFragments(options.projectRoot, 'collection.json');
87
+ }
88
+ if (!host.exists(schematicsPath)) {
89
+ await createCollectionJson(host, options.projectRoot);
90
+ }
91
+
92
+ updateJson<GeneratorsJson>(host, schematicsPath, (json) => {
93
+ let schematics = json.schematics;
94
+ schematics ??= {};
95
+ schematics[options.name] = {
96
+ factory: `./src/schematics/${ options.fileName }/index`,
97
+ schema: `./src/schematics/${ options.fileName }/schema.json`,
98
+ description: options.description,
99
+ };
100
+ json.schematics = schematics;
101
+ return json;
102
+ });
103
+ }
104
+
105
+ export function hasSchematic(tree: Tree, projectName: string, schematicName: string): boolean {
106
+ const project = readProjectConfiguration(tree, projectName);
107
+ const packageJson = readJson<PackageJson>(tree, joinPathFragments(project.root, 'package.json'));
108
+ if (!packageJson.schematics) {
109
+ return false;
110
+ }
111
+ const schematicsPath = joinPathFragments(project.root, packageJson.schematics);
112
+ const collectionJson = readJson<GeneratorsJson>(tree, schematicsPath);
113
+ return (
114
+ collectionJson.schematics?.[schematicName] !== undefined
115
+ );
116
+ }
117
+
118
+ function coerceBuildTarget(
119
+ tree: Tree,
120
+ {
121
+ projectRoot,
122
+ project,
123
+ }: NormalizedSchema,
124
+ ) {
125
+
126
+ const projectConfiguration = readProjectConfiguration(tree, project);
127
+
128
+ projectConfiguration.targets ??= {};
129
+ projectConfiguration.targets['build'] ??= {};
130
+ const buildTarget = projectConfiguration.targets['build'];
131
+
132
+ buildTarget.executor = '@nx/js:tsc';
133
+ buildTarget.outputs ??= [ '{options.outputPath}' ];
134
+ buildTarget.options ??= {};
135
+ buildTarget.options.outputPath = `dist/${ projectRoot }`;
136
+ buildTarget.options.main = `${ projectRoot }/src/index.ts`;
137
+ buildTarget.options.tsConfig = `${ projectRoot }/tsconfig.lib.json`;
138
+ buildTarget.options.assets ??= [];
139
+
140
+ const assets = [
141
+ `${ projectRoot }/*.md`, {
142
+ 'input': `./${ projectRoot }/src`,
143
+ 'glob': '**/!(*.ts)',
144
+ 'output': './src',
145
+ }, {
146
+ 'input': `./${ projectRoot }/src`,
147
+ 'glob': '**/*.d.ts',
148
+ 'output': './src',
149
+ }, {
150
+ 'input': `./${ projectRoot }`,
151
+ 'glob': 'collection.json',
152
+ 'output': '.',
153
+ },
154
+ ];
155
+
156
+ CoerceAssets(buildTarget.options.assets, assets);
157
+
158
+ updateProjectConfiguration(tree, project, projectConfiguration);
159
+ }
160
+
161
+ export async function schematicGenerator(host: Tree, _options: SchematicGeneratorSchema) {
162
+ const options = normalizeOptions(host, _options);
163
+ if (hasSchematic(host, options.project, options.name)) {
164
+ throw new Error(`Generator ${ options.name } already exists.`);
165
+ }
166
+
167
+ addFiles(host, options);
168
+
169
+ await updateCollectionJson(host, options);
170
+
171
+ if (!options.skipFormat) {
172
+ await formatFiles(host);
173
+ }
174
+
175
+ coerceBuildTarget(host, options);
176
+
177
+ }
178
+
179
+ export default schematicGenerator;
@@ -0,0 +1,5 @@
1
+ import { convertNxGenerator } from '@nx/devkit';
2
+ import generator from './generator';
3
+
4
+ const schematic = convertNxGenerator(generator);
5
+ export default schematic;
package/src/i18n.ts ADDED
@@ -0,0 +1,130 @@
1
+ import {
2
+ CreateNodesContextV2,
3
+ CreateNodesV2,
4
+ ProjectConfiguration,
5
+ TargetConfiguration,
6
+ } from '@nx/devkit';
7
+ import {
8
+ FindProjectByPath,
9
+ FsTree,
10
+ } from '@rxap/workspace-utilities';
11
+ import { Optional } from 'nx/src/project-graph/plugins';
12
+ import { combineGlobPatterns } from 'nx/src/utils/globs';
13
+ import { dirname } from 'path';
14
+ import 'colors';
15
+
16
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
17
+ export interface PluginOptions {}
18
+
19
+ export function normalizeOptions(
20
+ options: PluginOptions | undefined,
21
+ ): PluginOptions {
22
+ return options ?? {};
23
+ }
24
+
25
+ export const createNodesV2: CreateNodesV2<PluginOptions> = [
26
+ combineGlobPatterns([
27
+ '**/tsconfig.app.json',
28
+ '**/src/main.ts',
29
+ '**/src/app/app.module.ts',
30
+ '**/src/app/app.component.ts',
31
+ '**/src/index.html',
32
+ '**/src/i18n.index.html.hbs'
33
+ ]),
34
+ async (configFilePaths, options, context) => {
35
+ const normalizedOptions = normalizeOptions(options);
36
+
37
+ const includedConfigFilePaths = await Promise.all(
38
+ configFilePaths.map(async (configFilePath) => {
39
+ if (
40
+ await shouldHaveProjectConfiguration(
41
+ configFilePath,
42
+ normalizedOptions,
43
+ context,
44
+ )
45
+ ) {
46
+ return configFilePath;
47
+ }
48
+ return undefined;
49
+ }),
50
+ ).then((configFilePathOrUndefinedList) =>
51
+ configFilePathOrUndefinedList.filter((value) => value !== undefined),
52
+ );
53
+
54
+ const results = await Promise.all(
55
+ includedConfigFilePaths.map(async (configFilePath) => {
56
+ const [ projectPath, projectConfiguration ] =
57
+ await createProjectConfiguration(
58
+ configFilePath,
59
+ normalizedOptions,
60
+ context,
61
+ );
62
+ return [ configFilePath, projectPath, projectConfiguration ] as [
63
+ string,
64
+ string,
65
+ Optional<ProjectConfiguration, 'root'>
66
+ ];
67
+ }),
68
+ );
69
+
70
+ return results.map(
71
+ ([ configFilePath, projectPath, projectConfiguration ]) => [
72
+ configFilePath,
73
+ {
74
+ projects: {
75
+ [projectPath]: projectConfiguration,
76
+ },
77
+ },
78
+ ],
79
+ );
80
+ },
81
+ ];
82
+
83
+ async function shouldHaveProjectConfiguration(
84
+ configFilePath: string,
85
+ options: PluginOptions,
86
+ context: CreateNodesContextV2,
87
+ ): Promise<boolean> {
88
+ const projectPath = dirname(configFilePath);
89
+ const tree = new FsTree(context.workspaceRoot);
90
+ if (!FindProjectByPath(tree, projectPath)) {
91
+ // console.log(`The folder of the file '${ configFilePath }' is not the root of a project. Skipping`.yellow);
92
+ return false;
93
+ }
94
+ return true;
95
+ }
96
+
97
+ async function createProjectConfiguration(
98
+ configFilePath: string,
99
+ options: PluginOptions,
100
+ context: CreateNodesContextV2,
101
+ ): Promise<[ string, Optional<ProjectConfiguration, 'root'> ]> {
102
+ const projectPath = dirname(configFilePath);
103
+ const targets: Record<string, TargetConfiguration> = {};
104
+ const tree = new FsTree(context.workspaceRoot);
105
+ const projectConfiguration = FindProjectByPath(tree, projectPath);
106
+
107
+ if (!projectConfiguration) {
108
+ throw new Error(`Could not find project in '${ projectPath }'`);
109
+ }
110
+
111
+ targets['i18n-index-html'] = createI18nIndexHtmlTarget();
112
+
113
+ return [
114
+ projectPath, {
115
+ targets,
116
+ },
117
+ ];
118
+ }
119
+
120
+ function createI18nIndexHtmlTarget(): TargetConfiguration {
121
+ return {
122
+ dependsOn: [ "build"],
123
+ executor: "@rxap/plugin-application:i18n",
124
+ outputs: [ "{workspaceRoot}/dist/{projectRoot}/index.html"],
125
+ inputs: [
126
+ "{projectRoot}/i18n.index.html.hbs"
127
+ ],
128
+ cache: true
129
+ };
130
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { initGenerator as AngularInitGenerator } from './generators/init/generator';
2
+ export { initLibraryGenerator as AngularInitLibraryGenerator } from './generators/init-library/generator';
3
+ export { initApplicationGenerator as AngularInitApplicationGenerator } from './generators/init-application/generator';
@@ -0,0 +1 @@
1
+ export const ANGULAR_VERSION = '~19.1.0';
@@ -0,0 +1,113 @@
1
+ import { cypressComponentConfiguration } from '@nx/angular/generators';
2
+ import {
3
+ getProjects,
4
+ ProjectConfiguration,
5
+ readProjectConfiguration,
6
+ Tree,
7
+ updateProjectConfiguration,
8
+ } from '@nx/devkit';
9
+ import {
10
+ CoerceFile,
11
+ IsAngularProject,
12
+ IsApplicationProject,
13
+ IsRxapRepository,
14
+ } from '@rxap/workspace-utilities';
15
+ import { join } from 'path';
16
+
17
+ function determineCypressBuildTargetProject(tree: Tree, project: ProjectConfiguration, projectName: string): string {
18
+
19
+ if (IsApplicationProject(project)) {
20
+ return projectName;
21
+ }
22
+
23
+ const projects = getProjects(tree);
24
+
25
+ const angularApplicationProjects = Array.from(projects.entries())
26
+ .filter(([ , project ]) => IsApplicationProject(project) && IsAngularProject(project));
27
+
28
+ const cypressProject = angularApplicationProjects.find(([ projectName ]) => projectName.endsWith('cypress'));
29
+
30
+ if (cypressProject) {
31
+ return cypressProject[0];
32
+ }
33
+
34
+ if (angularApplicationProjects.length) {
35
+ return angularApplicationProjects[0][0];
36
+ } else {
37
+ throw new Error('No angular application project found. Can not determine the cypress build target project.');
38
+ }
39
+
40
+ }
41
+
42
+ export async function CoerceCypressComponentTesting(tree: Tree, project: ProjectConfiguration, projectName: string) {
43
+
44
+ project.targets ??= {};
45
+
46
+ if (!project.targets['component-test']) {
47
+ const buildTargetProjectName = determineCypressBuildTargetProject(tree, project, projectName);
48
+ await cypressComponentConfiguration(tree, {
49
+ project: projectName,
50
+ generateTests: true,
51
+ skipFormat: false,
52
+ buildTarget: `${ buildTargetProjectName }:build:development`,
53
+ });
54
+ if (buildTargetProjectName !== projectName) {
55
+ const buildTargetProject = readProjectConfiguration(tree, buildTargetProjectName);
56
+ buildTargetProject.implicitDependencies ??= [];
57
+ if (!buildTargetProject.implicitDependencies.includes(projectName)) {
58
+ buildTargetProject.implicitDependencies.push(projectName);
59
+ updateProjectConfiguration(
60
+ tree,
61
+ buildTargetProjectName,
62
+ buildTargetProject,
63
+ );
64
+ }
65
+ }
66
+ const _project = readProjectConfiguration(tree, projectName);
67
+ _project.targets ??= {};
68
+ _project.targets['component-test'].configurations ??= {};
69
+ _project.targets['component-test'].configurations['open'] ??= {};
70
+ _project.targets['component-test'].configurations['open'].watch = true;
71
+ updateProjectConfiguration(
72
+ tree,
73
+ projectName,
74
+ _project,
75
+ );
76
+ if (IsRxapRepository(tree)) {
77
+ CoerceFile(tree, join(
78
+ project.root,
79
+ 'cypress.config.ts',
80
+ ),
81
+ `import { componentTestingPreset } from 'workspace';
82
+ import { defineConfig } from 'cypress';
83
+
84
+ export default defineConfig({
85
+ component: componentTestingPreset(__filename),
86
+ });`, true);
87
+ }
88
+ }
89
+
90
+ const _project = readProjectConfiguration(
91
+ tree,
92
+ projectName,
93
+ );
94
+ _project.targets ??= {};
95
+ if (_project.targets['component-test'].options.devServerTarget) {
96
+ const cypressProjectName = _project.targets['component-test'].options.devServerTarget.split(':').shift();
97
+ if (cypressProjectName !== projectName) {
98
+ const cypressProject = readProjectConfiguration(
99
+ tree,
100
+ cypressProjectName,
101
+ );
102
+ cypressProject.implicitDependencies ??= [];
103
+ if (!cypressProject.implicitDependencies.includes(projectName)) {
104
+ cypressProject.implicitDependencies.push(projectName);
105
+ updateProjectConfiguration(
106
+ tree,
107
+ cypressProjectName,
108
+ cypressProject,
109
+ );
110
+ }
111
+ }
112
+ }
113
+ }
@@ -0,0 +1,60 @@
1
+ import { Tree } from '@nx/devkit';
2
+ import { CoerceImports } from '@rxap/ts-morph';
3
+ import { TsMorphAngularProjectTransform } from '@rxap/workspace-ts-morph';
4
+ import {
5
+ CoerceFile,
6
+ GetProjectSourceRoot,
7
+ } from '@rxap/workspace-utilities';
8
+
9
+ export function coerceTestSetup(tree: Tree, projectName: string) {
10
+ const projectSourceRoot = GetProjectSourceRoot(tree, projectName);
11
+
12
+ const testSetupPath = `${projectSourceRoot}/test-setup.ts`;
13
+ CoerceFile(tree, testSetupPath, `// @ts-expect-error https://thymikee.github.io/jest-preset-angular/docs/getting-started/test-environment
14
+ globalThis.ngJest = {
15
+ testEnvironmentOptions: {
16
+ errorOnUnknownElements: true,
17
+ errorOnUnknownProperties: true,
18
+ },
19
+ };
20
+ import 'jest-preset-angular/setup-jest';
21
+ `);
22
+
23
+ TsMorphAngularProjectTransform(tree, { project: projectName, basePath: projectSourceRoot }, (_, [ sourceFile ]) => {
24
+
25
+ CoerceImports(sourceFile, [
26
+ {
27
+ moduleSpecifier: 'util',
28
+ namedImports: [ 'TextDecoder', 'TextEncoder' ],
29
+ },
30
+ {
31
+ moduleSpecifier: '@angular/localize/init',
32
+ },
33
+ ]);
34
+
35
+ const hasTextEncoderStatement = !!sourceFile.getStatement(
36
+ statement => statement.getText().includes('global.TextEncoder ??= TextEncoder as any;'));
37
+ const hasTextDecoderStatement = !!sourceFile.getStatement(
38
+ statement => statement.getText().includes('global.TextDecoder ??= TextDecoder as any;'));
39
+ const hasLocalizeInitStatement = !!sourceFile.getStatement(
40
+ statement => statement.getText().includes('jest.spyOn(global as any, \'$localize\')'));
41
+
42
+ if (!hasTextEncoderStatement) {
43
+ sourceFile.addStatements(`global.TextEncoder ??= TextEncoder as any;`);
44
+ }
45
+
46
+ if (!hasTextDecoderStatement) {
47
+ sourceFile.addStatements(`global.TextDecoder ??= TextDecoder as any;`);
48
+ }
49
+
50
+ if (!hasLocalizeInitStatement) {
51
+ sourceFile.addStatements(`jest.spyOn(global as any, '$localize').mockImplementation((...args: any[]) => {
52
+ // This template tag function just returns the first argument with no transformations.
53
+ // Change this to fit your unit test needs.
54
+ return args[0];
55
+ });`);
56
+ }
57
+
58
+ }, [ 'test-setup.ts' ]);
59
+
60
+ }
package/src/library.ts ADDED
@@ -0,0 +1,153 @@
1
+ import {
2
+ CreateNodesContextV2,
3
+ CreateNodesV2,
4
+ ProjectConfiguration,
5
+ TargetConfiguration,
6
+ } from '@nx/devkit';
7
+ import {
8
+ FindProjectByPath,
9
+ FsTree,
10
+ IsRxapRepository,
11
+ } from '@rxap/workspace-utilities';
12
+ import { existsSync } from 'fs';
13
+ import { Optional } from 'nx/src/project-graph/plugins';
14
+ import {
15
+ dirname,
16
+ join,
17
+ } from 'path';
18
+ import 'colors';
19
+
20
+ // eslint-disable-next-line @typescript-eslint/no-empty-interface
21
+ export interface PluginOptions {}
22
+
23
+ export function normalizeOptions(
24
+ options: PluginOptions | undefined,
25
+ ): PluginOptions {
26
+ return options ?? {};
27
+ }
28
+
29
+ export const createNodesV2: CreateNodesV2<PluginOptions> = [
30
+ '**/ng-package.json',
31
+ async (configFilePaths, options, context) => {
32
+ const normalizedOptions = normalizeOptions(options);
33
+
34
+ const includedConfigFilePaths = await Promise.all(
35
+ configFilePaths.map(async (configFilePath) => {
36
+ if (
37
+ await shouldHaveProjectConfiguration(
38
+ configFilePath,
39
+ normalizedOptions,
40
+ context,
41
+ )
42
+ ) {
43
+ return configFilePath;
44
+ }
45
+ return undefined;
46
+ }),
47
+ ).then((configFilePathOrUndefinedList) =>
48
+ configFilePathOrUndefinedList.filter((value) => value !== undefined),
49
+ );
50
+
51
+ const results = await Promise.all(
52
+ includedConfigFilePaths.map(async (configFilePath) => {
53
+ const [ projectPath, projectConfiguration ] =
54
+ await createProjectConfiguration(
55
+ configFilePath,
56
+ normalizedOptions,
57
+ context,
58
+ );
59
+ return [ configFilePath, projectPath, projectConfiguration ] as [
60
+ string,
61
+ string,
62
+ Optional<ProjectConfiguration, 'root'>
63
+ ];
64
+ }),
65
+ );
66
+
67
+ return results.map(
68
+ ([ configFilePath, projectPath, projectConfiguration ]) => [
69
+ configFilePath,
70
+ {
71
+ projects: {
72
+ [projectPath]: projectConfiguration,
73
+ },
74
+ },
75
+ ],
76
+ );
77
+ },
78
+ ];
79
+
80
+ async function shouldHaveProjectConfiguration(
81
+ configFilePath: string,
82
+ options: PluginOptions,
83
+ context: CreateNodesContextV2,
84
+ ): Promise<boolean> {
85
+ const projectPath = dirname(configFilePath);
86
+ const tree = new FsTree(context.workspaceRoot);
87
+ if (!FindProjectByPath(tree, projectPath)) {
88
+ // console.log(`The folder of the file '${ configFilePath }' is not the root of a project. Skipping`.yellow);
89
+ return false;
90
+ }
91
+ return true;
92
+ }
93
+
94
+ async function createProjectConfiguration(
95
+ configFilePath: string,
96
+ options: PluginOptions,
97
+ context: CreateNodesContextV2,
98
+ ): Promise<[ string, Optional<ProjectConfiguration, 'root'> ]> {
99
+ const projectPath = dirname(configFilePath);
100
+ const targets: Record<string, TargetConfiguration> = {};
101
+ const tree = new FsTree(context.workspaceRoot);
102
+ const projectConfiguration = FindProjectByPath(tree, projectPath);
103
+
104
+ if (!projectConfiguration) {
105
+ throw new Error(`Could not find project in '${ projectPath }'`);
106
+ }
107
+
108
+ targets['check-ng-package'] = createCheckNgPackageTarget();
109
+
110
+ if (existsSync(join(projectPath, 'tailwind.config.js'))) {
111
+ targets['build-tailwind'] = createBuildTailwindTarget(IsRxapRepository(context.workspaceRoot));
112
+ }
113
+
114
+ return [
115
+ projectPath, {
116
+ targets,
117
+ },
118
+ ];
119
+ }
120
+
121
+ function createBuildTailwindTarget(isRxapRepository: boolean): TargetConfiguration {
122
+ const dependsOn: TargetConfiguration['dependsOn'] = [];
123
+ if (isRxapRepository) {
124
+ dependsOn.push({
125
+ target: 'build',
126
+ projects: [ 'browser-tailwind' ],
127
+ });
128
+ }
129
+ return {
130
+ executor: '@rxap/plugin-angular:tailwind',
131
+ configurations: {
132
+ production: {
133
+ minify: true,
134
+ },
135
+ },
136
+ dependsOn,
137
+ inputs: [
138
+ '{projectRoot}/**/*.html',
139
+ '{projectRoot}/**/*.scss',
140
+ '{projectRoot}/**/*.css',
141
+ ],
142
+ outputs: [ '{projectRoot}/theme.css' ],
143
+ cache: true,
144
+ };
145
+ }
146
+
147
+ function createCheckNgPackageTarget(): TargetConfiguration {
148
+ return {
149
+ executor: '@rxap/plugin-angular:check-ng-package',
150
+ inputs: [ '{projectRoot}/ng-package.json', '{projectRoot}/package.json' ],
151
+ cache: true,
152
+ };
153
+ }