@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.
- package/.eslintignore +6 -0
- package/.eslintrc.json +44 -0
- package/CHANGELOG.md +4 -0
- package/LICENSE +621 -0
- package/README.md.handlebars +115 -0
- package/jest.config.ts +11 -0
- package/package.json +2 -15
- package/project.json +68 -0
- package/schematics.yaml +6 -0
- package/src/application.ts +124 -0
- package/src/executors/check-ng-package/executor.ts +33 -0
- package/src/executors/config/executor.ts +74 -0
- package/src/executors/i18n/executor.ts +198 -0
- package/src/executors/tailwind/executor.ts +34 -0
- package/src/generators/convert-to-buildable-library/generator.ts +110 -0
- package/src/generators/fix-schematic/generator.ts +103 -0
- package/src/generators/fix-schematic/index.ts +5 -0
- package/src/generators/init/coerce-nx-json.ts +72 -0
- package/src/generators/init/generator.ts +28 -0
- package/src/generators/init/index.ts +5 -0
- package/src/generators/init/init-workspace.ts +84 -0
- package/src/generators/init-application/assert-main-statements.ts +74 -0
- package/src/generators/init-application/cleanup.ts +127 -0
- package/src/generators/init-application/coerce-app-config.ts +147 -0
- package/src/generators/init-application/coerce-environment-files.ts +105 -0
- package/src/generators/init-application/coerce-localazy-config-file.ts +26 -0
- package/src/generators/init-application/coerce-project.ts +128 -0
- package/src/generators/init-application/generate-authentication.ts +122 -0
- package/src/generators/init-application/generate-monolithic.spec.ts +45 -0
- package/src/generators/init-application/generate-monolithic.ts +57 -0
- package/src/generators/init-application/generator.ts +356 -0
- package/src/generators/init-application/index.ts +5 -0
- package/src/generators/init-application/link-mfe-remote-with-host.ts +60 -0
- package/src/generators/init-application/project-i18n-configuration.ts +4 -0
- package/src/generators/init-application/update-git-ignore.ts +22 -0
- package/src/generators/init-application/update-main-file.ts +118 -0
- package/src/generators/init-application/update-project-targets.ts +229 -0
- package/src/generators/init-application/update-tags.ts +30 -0
- package/src/generators/init-application/update-target-defaults.ts +43 -0
- package/src/generators/init-application/update-ts-config.ts +31 -0
- package/src/generators/init-component/generator.ts +147 -0
- package/src/generators/init-component/index.ts +5 -0
- package/src/generators/init-feature/generator.ts +77 -0
- package/src/generators/init-feature/index.ts +5 -0
- package/src/generators/init-feature-library/generator.ts +82 -0
- package/src/generators/init-feature-library/index.ts +5 -0
- package/src/generators/init-feature-library/init-project.ts +53 -0
- package/src/generators/init-feature-library/init-workspace.ts +10 -0
- package/src/generators/init-library/check-if-secondary-entrypoint-include-in-the-ts-config.ts +46 -0
- package/src/generators/init-library/cleanup.ts +22 -0
- package/src/generators/init-library/coerce-projects.ts +118 -0
- package/src/generators/init-library/coerce-tailwind-theme-scss.ts +27 -0
- package/src/generators/init-library/extend-angular-specific-eslint.ts +37 -0
- package/src/generators/init-library/generator.ts +87 -0
- package/src/generators/init-library/has-index-scss.ts +14 -0
- package/src/generators/init-library/has-tailwind-config.ts +9 -0
- package/src/generators/init-library/index.ts +5 -0
- package/src/generators/init-library/init-project.ts +46 -0
- package/src/generators/init-library/init-workspace.ts +26 -0
- package/src/generators/init-library/is-ng-packagr-project.ts +9 -0
- package/src/generators/init-library/ng-package-json.ts +23 -0
- package/src/generators/init-library/set-general-target-defaults.ts +36 -0
- package/src/generators/init-library/update-package-json.ts +26 -0
- package/src/generators/init-library/update-project-ng-package-configuration.ts +51 -0
- package/src/generators/init-library/update-ts-config.ts +24 -0
- package/src/generators/schematic/generator.ts +179 -0
- package/src/generators/schematic/index.ts +5 -0
- package/src/i18n.ts +130 -0
- package/src/index.ts +3 -0
- package/src/lib/angular-version.ts +1 -0
- package/src/lib/coerce-cypress-component-testing.ts +113 -0
- package/src/lib/coerce-test-setup.ts +60 -0
- package/src/library.ts +153 -0
- package/src/migrations/update-19-0-0/add-m2-prefix-to-material-scss-functions.ts +32 -0
- package/src/migrations/update-19-0-0/migration.ts +283 -0
- package/src/test-setup.ts +24 -0
- package/tsconfig.json +20 -0
- package/tsconfig.lib.json +10 -0
- package/tsconfig.spec.json +15 -0
- 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;
|
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
|
+
}
|