@simplysm/sd-cli 7.0.263 → 7.0.266

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 (78) hide show
  1. package/README.md +69 -22
  2. package/{src/entry-points/file/assets → assets/client-files}/assets/icons/icon-128x128.png +0 -0
  3. package/{src/entry-points/file/assets → assets/client-files}/assets/icons/icon-144x144.png +0 -0
  4. package/{src/entry-points/file/assets → assets/client-files}/assets/icons/icon-152x152.png +0 -0
  5. package/{src/entry-points/file/assets → assets/client-files}/assets/icons/icon-192x192.png +0 -0
  6. package/{src/entry-points/file/assets → assets/client-files}/assets/icons/icon-384x384.png +0 -0
  7. package/{src/entry-points/file/assets → assets/client-files}/assets/icons/icon-512x512.png +0 -0
  8. package/{src/entry-points/file/assets → assets/client-files}/assets/icons/icon-72x72.png +0 -0
  9. package/{src/entry-points/file/assets → assets/client-files}/assets/icons/icon-96x96.png +0 -0
  10. package/{src/entry-points/file/assets → assets/client-files}/favicon.ico +0 -0
  11. package/dist/bin/sd-cli.mjs +44 -5
  12. package/dist/build-tool/SdCliCordova.mjs +3 -3
  13. package/dist/build-tool/SdCliIndexFileGenerator.mjs +2 -3
  14. package/dist/builder/SdCliClientBuilder.d.ts +2 -0
  15. package/dist/builder/SdCliClientBuilder.mjs +45 -12
  16. package/dist/builder/SdCliServerBuilder.d.ts +2 -0
  17. package/dist/builder/SdCliServerBuilder.mjs +27 -13
  18. package/dist/entry-points/SdCliPrepare.mjs +34 -34
  19. package/dist/entry-points/SdCliProjectGenerator.d.ts +7 -0
  20. package/dist/entry-points/SdCliProjectGenerator.mjs +65 -33
  21. package/dist/entry-points/file/base/fc_package_eslintrc.mjs +11 -11
  22. package/dist/entry-points/file/base/fc_package_tsconfig.d.ts +1 -0
  23. package/dist/entry-points/file/base/fc_package_tsconfig.mjs +3 -2
  24. package/dist/entry-points/file/client/fc_package_AppModule.mjs +1 -1
  25. package/dist/entry-points/file/client/fc_package_AppPage.mjs +2 -22
  26. package/dist/entry-points/file/client/fc_package_Page.d.ts +4 -0
  27. package/dist/entry-points/file/client/fc_package_Page.mjs +19 -0
  28. package/dist/entry-points/file/project/fc_project_gitignore.mjs +1 -1
  29. package/dist/entry-points/file/project/fc_project_readme.mjs +3 -2
  30. package/dist/index.d.ts +1 -1
  31. package/dist/index.mjs +2 -2
  32. package/dist/ng-tools/SdCliNgModuleGenerator.mjs +35 -36
  33. package/dist/packages/SdCliPackage.mjs +2 -2
  34. package/docs/client-add-page.md +8 -0
  35. package/docs/client-router.md +85 -0
  36. package/docs/client-sw.md +101 -0
  37. package/docs/conf-orm.md +81 -0
  38. package/docs/conf-usage.md +5 -0
  39. package/docs/lib-local-update.md +50 -0
  40. package/docs/lib-ts-paths.md +25 -0
  41. package/package.json +6 -6
  42. package/src/SdCliBuildResultError.ts +9 -9
  43. package/src/bin/sd-cli.ts +48 -5
  44. package/src/build-tool/SdCliCacheCompilerHost.ts +77 -77
  45. package/src/build-tool/SdCliCordova.ts +2 -2
  46. package/src/build-tool/SdCliGithubApi.ts +110 -110
  47. package/src/build-tool/SdCliIndexFileGenerator.ts +79 -80
  48. package/src/build-tool/SdCliNgCacheCompilerHost.ts +58 -58
  49. package/src/build-tool/SdCliPackageLinter.ts +74 -74
  50. package/src/builder/SdCliClientBuilder.ts +58 -13
  51. package/src/builder/SdCliServerBuilder.ts +34 -14
  52. package/src/entry-points/SdCliFileCrypto.ts +87 -87
  53. package/src/entry-points/SdCliPrepare.ts +55 -55
  54. package/src/entry-points/SdCliProjectGenerator.ts +80 -38
  55. package/src/entry-points/file/base/fc_package_eslintrc.ts +10 -10
  56. package/src/entry-points/file/base/fc_package_tsconfig.ts +3 -2
  57. package/src/entry-points/file/client/fc_package_AppModule.ts +1 -1
  58. package/src/entry-points/file/client/fc_package_AppPage.ts +1 -21
  59. package/src/entry-points/file/client/fc_package_Page.ts +19 -0
  60. package/src/entry-points/file/project/fc_project_gitignore.ts +1 -1
  61. package/src/entry-points/file/project/fc_project_readme.ts +2 -1
  62. package/src/index.ts +55 -55
  63. package/src/ng-tools/SdCliNgModuleGenerator.ts +639 -640
  64. package/src/ng-tools/babel/SdCliBbFileMetadata.ts +568 -568
  65. package/src/ng-tools/babel/SdCliBbRootMetadata.ts +239 -239
  66. package/src/ng-tools/babel/SdCliBbUtil.ts +15 -15
  67. package/src/ng-tools/babel/TSdCliBbNgMetadata.ts +234 -234
  68. package/src/ng-tools/babel/TSdCliBbTypeMetadata.ts +177 -177
  69. package/src/ng-tools/commons.ts +3 -3
  70. package/src/ng-tools/typescript/SdCliTsFileMetadata.ts +384 -384
  71. package/src/ng-tools/typescript/SdCliTsRootMetadata.ts +32 -32
  72. package/src/ng-tools/typescript/SdCliTsUtil.ts +29 -29
  73. package/src/packages/SdCliPackage.ts +1 -1
  74. package/src/utils/SdCliConfigUtil.ts +94 -94
  75. package/src/utils/SdCliNpmConfigUtil.ts +16 -16
  76. package/dist/entry-points/file/client/fc_package_appicons.d.ts +0 -1
  77. package/dist/entry-points/file/client/fc_package_appicons.mjs +0 -6
  78. package/src/entry-points/file/client/fc_package_appicons.ts +0 -5
@@ -1,640 +1,639 @@
1
- import { SdCliTsRootMetadata } from "./typescript/SdCliTsRootMetadata";
2
- import { SdCliBbRootMetadata, TSdCliBbMetadata } from "./babel/SdCliBbRootMetadata";
3
- import { SdCliBbClassMetadata, SdCliBbObjectMetadata } from "./babel/TSdCliBbTypeMetadata";
4
- import {
5
- SdCliBbNgComponentMetadata,
6
- SdCliBbNgDirectiveMetadata,
7
- SdCliBbNgModuleMetadata,
8
- SdCliBbNgPipeMetadata
9
- } from "./babel/TSdCliBbNgMetadata";
10
- import { NeverEntryError, ObjectUtil, StringUtil } from "@simplysm/sd-core-common";
11
- import { TSdCliMetaRef } from "./commons";
12
- import { SdCliTsNgInjectableMetadata } from "./typescript/SdCliTsFileMetadata";
13
- import path from "path";
14
- import { JSDOM } from "jsdom";
15
- import { FsUtil, Logger, PathUtil } from "@simplysm/sd-core-node";
16
- import os from "os";
17
- import { INpmConfig } from "../commons";
18
-
19
- export class SdCliNgModuleGenerator {
20
- private readonly _logger: Logger;
21
-
22
- private readonly _bbMeta: SdCliBbRootMetadata;
23
- private readonly _tsMeta: SdCliTsRootMetadata;
24
-
25
- private readonly _srcPath: string;
26
- private readonly _modulesPath: string;
27
-
28
- private readonly _fileWriterCache = new Map<string, string>();
29
-
30
- public constructor(packagePath: string,
31
- private readonly _srcDirRelPaths: string[],
32
- private readonly _routeOpts: {
33
- glob: string;
34
- fileEndsWith: string;
35
- rootClassName: string;
36
- } | undefined) {
37
- this._srcPath = path.resolve(packagePath, "src");
38
- this._modulesPath = path.resolve(packagePath, "src", "_modules");
39
-
40
- this._bbMeta = new SdCliBbRootMetadata(packagePath);
41
- this._tsMeta = new SdCliTsRootMetadata(packagePath);
42
-
43
- const glob = FsUtil.glob(path.resolve(this._modulesPath, "**", "*.ts"));
44
- for (const currFilePath of glob) {
45
- const content = FsUtil.readFile(currFilePath);
46
- this._fileWriterCache.set(currFilePath, content);
47
- }
48
-
49
- const npmConfig: INpmConfig = FsUtil.readJson(path.resolve(packagePath, "package.json"));
50
- this._logger = Logger.get(["simplysm", "sd-cli", this.constructor.name, npmConfig.name]);
51
- }
52
-
53
- public removeCaches(changedFilePaths: string[]): void {
54
- this._bbMeta.removeCaches(changedFilePaths);
55
- this._tsMeta.removeCaches(changedFilePaths);
56
- for (const changedFilePath of changedFilePaths) {
57
- if (this._fileWriterCache.has(changedFilePath) && !FsUtil.exists(changedFilePath)) {
58
- this._fileWriterCache.delete(changedFilePath);
59
- }
60
- }
61
- }
62
-
63
- public async runAsync(): Promise<void> {
64
- this._logger.debug("Babel NgModule 정의 가져오기...");
65
- const bbNgModules = this._getBbNgModuleDefs();
66
-
67
- this._logger.debug("NgModule Preset 구성...");
68
- const tsPreset = this._getTsGenNgModulePreset();
69
-
70
- this._logger.debug("RoutingModule 정의 구성...");
71
- const genNgRoutingModules = await this._getGenNgRoutingModuleDefsAsync();
72
-
73
- this._logger.debug("NgModule 정의 구성...");
74
- const genNgModules = this._getGenNgModuleDefs(bbNgModules, tsPreset.modules, tsPreset.sources);
75
-
76
- this._logger.debug("NgModule 순환참조 병합...");
77
- const mergedGenNgModules = this._mergeNgModuleCircularDependency(genNgModules);
78
-
79
- this._logger.debug("생성할 파일 내용 구성...");
80
- const files = [
81
- ...this._getGenNgRoutingModuleFiles(genNgRoutingModules),
82
- ...this._getGenNgModuleFiles(mergedGenNgModules)
83
- ];
84
-
85
- this._logger.debug("파일 쓰기...");
86
- await this._writeFilesAsync(files);
87
- }
88
-
89
- private _mergeNgModuleCircularDependency(mods: ITsGenNgModuleDef[]): ITsGenNgModuleDef[] {
90
- // 중복 'check'목록 구성
91
- const check = (mod: ITsGenNgModuleDef, ref: { filePath: string; name: string }, chain: ITsGenNgModuleDef[]): ITsGenNgModuleDef[][] => {
92
- const result: ITsGenNgModuleDef[][] = [];
93
- for (const imp of mod.imports) {
94
- if ("filePath" in imp) {
95
- if (imp.filePath === ref.filePath || imp.name === ref.name) {
96
- result.push(chain.concat([mod]));
97
- }
98
- else {
99
- const childMod = mods.single((item) => item.filePath === imp.filePath && item.name === imp.name);
100
- if (childMod) {
101
- if (chain.some((item) => item.filePath === childMod.filePath && item.name === childMod.name)) {
102
- continue;
103
- }
104
-
105
- result.push(
106
- ...check(childMod, ref, chain.concat([mod]))
107
- );
108
- }
109
- }
110
- }
111
- }
112
-
113
- return result;
114
- };
115
-
116
- const checkedList: ITsGenNgModuleDef[][] = [];
117
- for (const mod of mods) {
118
- const circularDepModules = check(mod, { filePath: mod.filePath, name: mod.name }, []);
119
- if (circularDepModules.length > 0) {
120
- checkedList.push(...circularDepModules);
121
- }
122
- }
123
- const mergeModPacks = checkedList.map((item) => item.orderBy((item1) => item1.filePath)).distinct();
124
-
125
- // 'merge'목록 구성
126
- const currMergedList: ITsGenNgModuleDef[] = [];
127
- for (const mergeModPack of mergeModPacks) {
128
- let currMerged = mergeModPack[0];
129
- for (const mergeMod of mergeModPack.slice(1)) {
130
- currMerged = {
131
- filePath: currMerged.filePath,
132
- name: currMerged.name,
133
- imports: currMerged.imports.concat(mergeMod.imports).distinct(),
134
- exports: currMerged.exports.concat(mergeMod.exports).distinct(),
135
- providers: currMerged.providers.concat(mergeMod.providers).distinct()
136
- };
137
- }
138
- currMerged.imports.remove((imp) => (
139
- "filePath" in imp &&
140
- mergeModPack.some((mergeMod) => (
141
- imp.filePath === mergeMod.filePath &&
142
- imp.name === mergeMod.name
143
- ))
144
- ));
145
-
146
- currMergedList.push(currMerged);
147
- }
148
-
149
- // 기존 'check'된 것들 모두 삭제
150
- const result: ITsGenNgModuleDef[] = ObjectUtil.clone(mods);
151
- for (const delMod of mergeModPacks.mapMany()) {
152
- result.remove((item) => item.filePath === delMod.filePath && item.name === delMod.name);
153
- }
154
-
155
- // 'merge'된 것들 등록
156
- result.push(...currMergedList);
157
-
158
- // 모든 결과물에서 'import'에 'merge'된것들이 있으면 'merge'결과물 파일로 변경후, distinct
159
- for (const resultItem of result) {
160
- for (const imp of resultItem.imports) {
161
- if (!("filePath" in imp)) continue;
162
-
163
- for (const mergeModPack of mergeModPacks) {
164
- if (mergeModPack.some((mergeModPackItem) => mergeModPackItem.filePath === imp.filePath && mergeModPackItem.name === imp.name)) {
165
- imp.filePath = mergeModPack[0].filePath;
166
- imp.name = mergeModPack[0].name;
167
- }
168
- }
169
- }
170
- resultItem.imports = resultItem.imports.distinct();
171
- }
172
-
173
- return result;
174
- }
175
-
176
- private _findBbMetasFromMeta(srcMeta: TSdCliBbMetadata): TSdCliBbMetadata[] {
177
- const metaTmp = typeof srcMeta !== "string" && "__TSdCliMetaRef__" in srcMeta ? this._bbMeta.findMeta(srcMeta) : srcMeta;
178
- return metaTmp instanceof Array ? metaTmp : [metaTmp];
179
- }
180
-
181
- private _getBbNgModuleDefs(): IBbNgModuleDef[] {
182
- const entryRecord = this._bbMeta.getEntryFileMetaRecord();
183
- const result: IBbNgModuleDef[] = [];
184
-
185
- for (const moduleName of Object.keys(entryRecord)) {
186
- const fileMeta = entryRecord[moduleName];
187
- for (const exp of fileMeta.exports) {
188
- const metas = this._findBbMetasFromMeta(exp.target);
189
- for (const meta of metas) {
190
- if (meta instanceof SdCliBbClassMetadata) {
191
- if (meta.ngDecl instanceof SdCliBbNgModuleMetadata) {
192
- const ngDecl = meta.ngDecl;
193
-
194
- const resultItem: IBbNgModuleDef = {
195
- moduleName,
196
- name: exp.exportedName === "*" ? meta.name : exp.exportedName,
197
- providers: [],
198
- exports: [],
199
- selectors: [],
200
- pipeNames: []
201
- };
202
-
203
- for (const modProv of ngDecl.def.providers) {
204
- const modProvMetas = this._findBbMetasFromMeta(modProv);
205
- if (modProvMetas.length === 0) {
206
- throw new NeverEntryError();
207
- }
208
-
209
- for (const modProvMeta of modProvMetas) {
210
- if (modProvMeta instanceof SdCliBbClassMetadata) {
211
- const ref = this._bbMeta.findExportRef({
212
- filePath: modProvMeta.filePath,
213
- name: modProvMeta.name
214
- });
215
- if (ref) {
216
- resultItem.providers.push(ref);
217
- }
218
- }
219
- else if (modProvMeta instanceof SdCliBbObjectMetadata) {
220
- // 무시
221
- }
222
- else {
223
- throw new NeverEntryError();
224
- }
225
- }
226
- }
227
-
228
- for (const modExp of ngDecl.def.exports) {
229
- const modExpMetas = this._findBbMetasFromMeta(modExp);
230
- if (modExpMetas.length === 0) {
231
- throw new NeverEntryError();
232
- }
233
-
234
- for (const modExpMeta of modExpMetas) {
235
- if (modExpMeta instanceof SdCliBbClassMetadata) {
236
- const ref = this._bbMeta.findExportRef({
237
- filePath: modExpMeta.filePath,
238
- name: modExpMeta.name
239
- });
240
- if (ref) {
241
- resultItem.exports.push(ref);
242
- }
243
-
244
- if (modExpMeta.ngDecl instanceof SdCliBbNgDirectiveMetadata) {
245
- resultItem.selectors.push(modExpMeta.ngDecl.selector);
246
- }
247
- else if (modExpMeta.ngDecl instanceof SdCliBbNgComponentMetadata) {
248
- resultItem.selectors.push(modExpMeta.ngDecl.selector);
249
- }
250
- else if (modExpMeta.ngDecl instanceof SdCliBbNgPipeMetadata) {
251
- resultItem.pipeNames.push(modExpMeta.ngDecl.pipeName);
252
- }
253
- }
254
- else {
255
- throw new NeverEntryError();
256
- }
257
- }
258
- }
259
-
260
- result.push(resultItem);
261
- }
262
- }
263
- }
264
- }
265
- }
266
-
267
- return result;
268
- }
269
-
270
- private _getTsGenNgModulePreset(): { modules: ITsNgPresetModuleDef[]; sources: ITsNgPresetSourceDef[] } {
271
- const fileMetas = this._tsMeta.getFileMetas();
272
-
273
- const result: { modules: ITsNgPresetModuleDef[]; sources: ITsNgPresetSourceDef[] } = { modules: [], sources: [] };
274
-
275
- for (const fileMeta of fileMetas) {
276
- if (!this._srcDirRelPaths.some((item) => fileMeta.filePath.startsWith(path.resolve(this._srcPath, item)))) {
277
- continue;
278
- }
279
-
280
- for (const cls of fileMeta.directExportClasses) {
281
- if (cls.target.ngDecl) {
282
- if (cls.target.ngDecl instanceof SdCliTsNgInjectableMetadata) {
283
- if (cls.target.ngDecl.providedIn === "root") {
284
- continue;
285
- }
286
- }
287
-
288
- const moduleClassName = cls.exportedName + "Module";
289
- const moduleFilePath = path.resolve(this._modulesPath, path.relative(this._srcPath, path.dirname(fileMeta.filePath)), moduleClassName);
290
-
291
- result.sources.push({
292
- filePath: fileMeta.filePath.replace(/\.[^.]*$/, ""),
293
- name: cls.exportedName,
294
- module: {
295
- filePath: moduleFilePath,
296
- name: moduleClassName
297
- },
298
- imports: fileMeta.imports,
299
- template: "template" in cls.target.ngDecl ? cls.target.ngDecl.template : undefined,
300
- isProvider: cls.target.ngDecl instanceof SdCliTsNgInjectableMetadata
301
- });
302
-
303
- result.modules.push({
304
- filePath: moduleFilePath,
305
- name: moduleClassName,
306
- source: {
307
- filePath: fileMeta.filePath.replace(/\.[^.]*$/, ""),
308
- name: cls.exportedName
309
- },
310
- selector: "selector" in cls.target.ngDecl ? cls.target.ngDecl.selector : undefined,
311
- pipeName: "pipeName" in cls.target.ngDecl ? cls.target.ngDecl.pipeName : undefined
312
- });
313
- }
314
- }
315
- }
316
-
317
- return result;
318
- }
319
-
320
- private _getGenNgModuleDefs(bbModules: IBbNgModuleDef[], presetModules: ITsNgPresetModuleDef[], presetSources: ITsNgPresetSourceDef[]): ITsGenNgModuleDef[] {
321
- const result: ITsGenNgModuleDef[] = [];
322
-
323
- for (const presetSource of presetSources) {
324
- const imports: TSdCliMetaRef[] = [];
325
- for (const imp of presetSource.imports) {
326
- if ("moduleName" in imp) {
327
- for (const mod of bbModules) {
328
- if (mod.exports.some((item) => item.moduleName === imp.moduleName && item.name === imp.name)) {
329
- imports.push({ moduleName: mod.moduleName, name: mod.name, __TSdCliMetaRef__: "__TSdCliMetaRef__" });
330
- }
331
- if (mod.providers.some((item) => item.moduleName === imp.moduleName && item.name === imp.name)) {
332
- imports.push({ moduleName: mod.moduleName, name: mod.name, __TSdCliMetaRef__: "__TSdCliMetaRef__" });
333
- }
334
- }
335
- }
336
- else {
337
- for (const mod of presetModules) {
338
- if (mod.source && mod.source.filePath === imp.filePath && mod.source.name === imp.name) {
339
- imports.push({ filePath: mod.filePath, name: mod.name, __TSdCliMetaRef__: "__TSdCliMetaRef__" });
340
- }
341
- }
342
- }
343
- }
344
-
345
- if (presetSource.template !== undefined) {
346
- const templateDOM = new JSDOM(presetSource.template);
347
-
348
- for (const mod of bbModules) {
349
- for (const selector of mod.selectors) {
350
- if (
351
- templateDOM.window.document.querySelector([
352
- selector,
353
- selector.replace(/\[/g, "[\\[").replace(/]/g, "\\]]"),
354
- selector.replace(/\[/g, "[\\(").replace(/]/g, "\\)]")
355
- ].join(", ")) != null
356
- ) {
357
- imports.push({ moduleName: mod.moduleName, name: mod.name, __TSdCliMetaRef__: "__TSdCliMetaRef__" });
358
- }
359
- }
360
-
361
- for (const pipeName of mod.pipeNames) {
362
- if (new RegExp("| *" + pipeName + "[^\\w]").test(presetSource.template)) {
363
- imports.push({ moduleName: mod.moduleName, name: mod.name, __TSdCliMetaRef__: "__TSdCliMetaRef__" });
364
- }
365
- }
366
- }
367
-
368
- for (const mod of presetModules) {
369
- if (mod.selector !== undefined) {
370
- if (
371
- templateDOM.window.document.querySelector([
372
- mod.selector,
373
- mod.selector.replace(/\[/g, "[\\[").replace(/]/g, "\\]]"),
374
- mod.selector.replace(/\[/g, "[\\(").replace(/]/g, "\\)]")
375
- ].join(", ")) != null
376
- ) {
377
- imports.push({ filePath: mod.filePath, name: mod.name, __TSdCliMetaRef__: "__TSdCliMetaRef__" });
378
- }
379
- }
380
-
381
- if (mod.pipeName !== undefined) {
382
- if (new RegExp("| *" + mod.pipeName + "[^\\w]").test(presetSource.template)) {
383
- imports.push({ filePath: mod.filePath, name: mod.name, __TSdCliMetaRef__: "__TSdCliMetaRef__" });
384
- }
385
- }
386
- }
387
- }
388
-
389
- result.push({
390
- filePath: presetSource.module.filePath,
391
- name: presetSource.module.name,
392
- imports: imports.distinct(),
393
- exports: presetSource.isProvider ? [] : [{ filePath: presetSource.filePath, name: presetSource.name }],
394
- providers: presetSource.isProvider ? [{ filePath: presetSource.filePath, name: presetSource.name }] : []
395
- });
396
- }
397
-
398
- return result;
399
- }
400
-
401
- private async _getGenNgRoutingModuleDefsAsync(): Promise<TTsGenNgRoutingModuleDef[]> {
402
- if (this._routeOpts === undefined) return [];
403
-
404
- const result: TTsGenNgRoutingModuleDef[] = [];
405
-
406
- const filePaths = await FsUtil.globAsync(path.resolve(this._srcPath, this._routeOpts.glob));
407
- for (const filePath of filePaths) {
408
- const sourceClassName = path.basename(filePath, path.extname(filePath));
409
- if (sourceClassName === this._routeOpts.rootClassName) {
410
- const childDirName = StringUtil.toKebabCase(sourceClassName.slice(0, -this._routeOpts.fileEndsWith.length));
411
- const childDirPath = path.resolve(path.dirname(filePath), childDirName);
412
-
413
- result.push({
414
- filePath: path.resolve(this._srcPath, "_routes"),
415
- children: await this._getGenNgRoutingModuleDefChildrenAsync(childDirPath)
416
- });
417
- }
418
- else {
419
- const routingModuleClassName = sourceClassName + "RoutingModule";
420
- const routingModuleFilePath = path.resolve(this._modulesPath, path.relative(this._srcPath, path.dirname(filePath)), routingModuleClassName);
421
- const childDirName = StringUtil.toKebabCase(sourceClassName.slice(0, -this._routeOpts.fileEndsWith.length));
422
- const childDirPath = path.resolve(path.dirname(filePath), childDirName);
423
-
424
- result.push({
425
- filePath: routingModuleFilePath,
426
- name: routingModuleClassName,
427
- component: { filePath: filePath.replace(/\.ts$/, ""), name: sourceClassName },
428
- children: FsUtil.exists(childDirPath) ? await this._getGenNgRoutingModuleDefChildrenAsync(childDirPath) : undefined
429
- });
430
- }
431
- }
432
-
433
- return result;
434
- }
435
-
436
- private async _getGenNgRoutingModuleDefChildrenAsync(rootPath: string): Promise<TTsGenNgRoutingModuleDefChild[]> {
437
- const result: TTsGenNgRoutingModuleDefChild[] = [];
438
-
439
- const childNames = await FsUtil.readdirAsync(rootPath);
440
- for (const childName of childNames) {
441
- if (!FsUtil.stat(path.resolve(rootPath, childName)).isDirectory()) { // 파일
442
- const sourceFilePath = path.resolve(rootPath, childName);
443
- const sourceClassName = path.basename(sourceFilePath, path.extname(childName));
444
- const moduleClassName = sourceClassName + "Module";
445
- const moduleFilePath = path.resolve(this._modulesPath, path.relative(this._srcPath, path.dirname(sourceFilePath)), moduleClassName);
446
-
447
- result.push({
448
- path: StringUtil.toKebabCase(childName.substring(0, childName.length - 7)),
449
- target: { filePath: moduleFilePath, name: moduleClassName }
450
- });
451
- }
452
- else if (!FsUtil.exists(path.resolve(rootPath, StringUtil.toPascalCase(childName) + this._routeOpts!.fileEndsWith + ".ts"))) { // 파일없는 디렉토리
453
- result.push({
454
- path: childName,
455
- children: await this._getGenNgRoutingModuleDefChildrenAsync(path.resolve(rootPath, childName))
456
- });
457
- }
458
- }
459
-
460
- return result;
461
- }
462
-
463
- private _getGenNgRoutingModuleFiles(defs: TTsGenNgRoutingModuleDef[]): { filePath: string; content: string }[] {
464
- const result: { filePath: string; content: string }[] = [];
465
-
466
- for (const def of defs) {
467
- const fn = (children: TTsGenNgRoutingModuleDefChild[]): string => {
468
- let fnResult = "[\n";
469
- for (const child of children) {
470
- fnResult += " {\n";
471
- fnResult += ` path: "${child.path}",\n`;
472
- if ("target" in child) {
473
- fnResult += ` loadChildren: async () => await import("${this._getImportModuleName(def.filePath, child.target.filePath)}").then((m) => m.${child.target.name})\n`;
474
- }
475
- else {
476
- fnResult += ` children: ${fn(child.children).replace(/\n/g, "\n ")}\n`;
477
- }
478
- fnResult += " },\n";
479
- }
480
- fnResult = fnResult.slice(0, -2) + "\n";
481
- fnResult += "]";
482
-
483
- return fnResult;
484
- };
485
-
486
- const content = "component" in def ? `
487
- import { NgModule } from "@angular/core";
488
- import { RouterModule } from "@angular/router";
489
- import { SdCanDeactivateGuard } from "@simplysm/sd-angular";
490
- import { ${def.component.name} } from "${this._getImportModuleName(def.filePath, def.component.filePath)}";
491
-
492
- @NgModule({
493
- imports: [
494
- RouterModule.forChild([
495
- {
496
- path: "",
497
- component: ${def.component.name},
498
- canDeactivate: [SdCanDeactivateGuard]${def.children ? `,
499
- children: ${fn(def.children).replace(/\n/g, "\n ")}` : ""}
500
- }
501
- ])
502
- ],
503
- exports: [RouterModule]
504
- })
505
- export class ${def.name} {
506
- }`.trim() : `
507
- import { Routes } from "@angular/router";
508
-
509
- export const routes: Routes = ${fn(def.children)};
510
- `.trim();
511
-
512
- result.push({ filePath: def.filePath + ".ts", content });
513
- }
514
-
515
- return result;
516
- }
517
-
518
- private _getGenNgModuleFiles(mods: ITsGenNgModuleDef[]): { filePath: string; content: string }[] {
519
- const result: { filePath: string; content: string }[] = [];
520
-
521
- for (const mod of mods) {
522
- const importMap = [...mod.imports, ...mod.exports, ...mod.providers]
523
- .map((item) => ({
524
- moduleName: "moduleName" in item ? item.moduleName : this._getImportModuleName(mod.filePath, item.filePath),
525
- name: item.name
526
- }))
527
- .groupBy((item) => item.moduleName)
528
- .toMap((item) => item.key, (item) => item.values.map((v) => v.name));
529
- importMap.getOrCreate("@angular/core", []).push("NgModule");
530
-
531
- const importTexts: string[] = [];
532
- for (const moduleName of Array.from(importMap.keys()).orderBy()) {
533
- const targetNames = importMap.get(moduleName)!.distinct();
534
- if (targetNames.length === 0) continue;
535
- importTexts.push(`import { ${targetNames.join(", ")} } from "${moduleName}";`);
536
- }
537
-
538
- const moduleImportNames = mod.imports.map((item) => item.name);
539
- if (
540
- this._routeOpts !== undefined
541
- && mod.filePath.endsWith(this._routeOpts.fileEndsWith + "Module")
542
- && mod.name !== this._routeOpts.rootClassName + "Module"
543
- ) {
544
- const routingModuleName = mod.name.replace(/Module$/, "RoutingModule");
545
- importTexts.push(`import { ${routingModuleName} } from "./${routingModuleName}";`);
546
- moduleImportNames.push(routingModuleName);
547
- }
548
-
549
- const content = `
550
- ${importTexts.join(os.EOL)}
551
-
552
- @NgModule({
553
- imports: [${moduleImportNames.orderBy().join(", ")}],
554
- declarations: [${mod.exports.map((item) => item.name).distinct().orderBy().join(", ")}],
555
- exports: [${mod.exports.map((item) => item.name).distinct().orderBy().join(", ")}],
556
- providers: [${mod.providers.map((item) => item.name).distinct().orderBy().join(", ")}]
557
- })
558
- export class ${mod.name} {
559
- }`.trim();
560
-
561
- result.push({ filePath: mod.filePath + ".ts", content });
562
- }
563
-
564
- return result;
565
- }
566
-
567
- private async _writeFilesAsync(files: { filePath: string; content: string }[]): Promise<void> {
568
- for (const file of files) {
569
- if (this._fileWriterCache.get(file.filePath) !== file.content) {
570
- await FsUtil.writeFileAsync(file.filePath, file.content);
571
- this._fileWriterCache.set(file.filePath, file.content);
572
- }
573
- }
574
-
575
- const glob = await FsUtil.globAsync(path.resolve(this._modulesPath, "**", "*"));
576
- for (const currFilePath of glob) {
577
- if (!files.some((item) => item.filePath.startsWith(currFilePath))) {
578
- await FsUtil.removeAsync(currFilePath);
579
- this._fileWriterCache.delete(currFilePath);
580
- }
581
- }
582
- }
583
-
584
- private _getImportModuleName(mainFilePath: string, importFilePath: string): string {
585
- const filePath = PathUtil.posix(path.relative(path.dirname(mainFilePath), importFilePath).replace(/\.ts$/, ""));
586
- return filePath.startsWith(".") ? filePath : "./" + filePath;
587
- }
588
- }
589
-
590
- interface IBbNgModuleDef {
591
- moduleName: string;
592
- name: string;
593
- providers: { moduleName: string; name: string }[];
594
- exports: { moduleName: string; name: string }[];
595
- selectors: string[];
596
- pipeNames: string[];
597
- }
598
-
599
- interface ITsNgPresetModuleDef {
600
- filePath: string;
601
- name: string;
602
- source?: { filePath: string; name: string };
603
- selector?: string;
604
- pipeName?: string;
605
- }
606
-
607
- interface ITsNgPresetSourceDef {
608
- filePath: string;
609
- name: string;
610
- module: { filePath: string; name: string };
611
- imports: TSdCliMetaRef[];
612
- template?: string;
613
- isProvider: boolean;
614
- }
615
-
616
- interface ITsGenNgModuleDef {
617
- filePath: string;
618
- name: string;
619
- imports: TSdCliMetaRef[];
620
- exports: { filePath: string; name: string }[];
621
- providers: { filePath: string; name: string }[];
622
- }
623
-
624
- type TTsGenNgRoutingModuleDef = {
625
- filePath: string;
626
- name: string;
627
- component: { filePath: string; name: string };
628
- children?: TTsGenNgRoutingModuleDefChild[];
629
- } | {
630
- filePath: string;
631
- children: TTsGenNgRoutingModuleDefChild[];
632
- };
633
-
634
- type TTsGenNgRoutingModuleDefChild = {
635
- path: string;
636
- target: { filePath: string; name: string };
637
- } | {
638
- path: string;
639
- children: TTsGenNgRoutingModuleDefChild[];
640
- };
1
+ import { SdCliTsRootMetadata } from "./typescript/SdCliTsRootMetadata";
2
+ import { SdCliBbRootMetadata, TSdCliBbMetadata } from "./babel/SdCliBbRootMetadata";
3
+ import { SdCliBbClassMetadata, SdCliBbObjectMetadata } from "./babel/TSdCliBbTypeMetadata";
4
+ import {
5
+ SdCliBbNgComponentMetadata,
6
+ SdCliBbNgDirectiveMetadata,
7
+ SdCliBbNgModuleMetadata,
8
+ SdCliBbNgPipeMetadata
9
+ } from "./babel/TSdCliBbNgMetadata";
10
+ import { NeverEntryError, ObjectUtil, StringUtil } from "@simplysm/sd-core-common";
11
+ import { TSdCliMetaRef } from "./commons";
12
+ import { SdCliTsNgInjectableMetadata } from "./typescript/SdCliTsFileMetadata";
13
+ import path from "path";
14
+ import { JSDOM } from "jsdom";
15
+ import { FsUtil, Logger, PathUtil } from "@simplysm/sd-core-node";
16
+ import { INpmConfig } from "../commons";
17
+
18
+ export class SdCliNgModuleGenerator {
19
+ private readonly _logger: Logger;
20
+
21
+ private readonly _bbMeta: SdCliBbRootMetadata;
22
+ private readonly _tsMeta: SdCliTsRootMetadata;
23
+
24
+ private readonly _srcPath: string;
25
+ private readonly _modulesPath: string;
26
+
27
+ private readonly _fileWriterCache = new Map<string, string>();
28
+
29
+ public constructor(packagePath: string,
30
+ private readonly _srcDirRelPaths: string[],
31
+ private readonly _routeOpts: {
32
+ glob: string;
33
+ fileEndsWith: string;
34
+ rootClassName: string;
35
+ } | undefined) {
36
+ this._srcPath = path.resolve(packagePath, "src");
37
+ this._modulesPath = path.resolve(packagePath, "src", "_modules");
38
+
39
+ this._bbMeta = new SdCliBbRootMetadata(packagePath);
40
+ this._tsMeta = new SdCliTsRootMetadata(packagePath);
41
+
42
+ const glob = FsUtil.glob(path.resolve(this._modulesPath, "**", "*.ts"));
43
+ for (const currFilePath of glob) {
44
+ const content = FsUtil.readFile(currFilePath);
45
+ this._fileWriterCache.set(currFilePath, content);
46
+ }
47
+
48
+ const npmConfig: INpmConfig = FsUtil.readJson(path.resolve(packagePath, "package.json"));
49
+ this._logger = Logger.get(["simplysm", "sd-cli", this.constructor.name, npmConfig.name]);
50
+ }
51
+
52
+ public removeCaches(changedFilePaths: string[]): void {
53
+ this._bbMeta.removeCaches(changedFilePaths);
54
+ this._tsMeta.removeCaches(changedFilePaths);
55
+ for (const changedFilePath of changedFilePaths) {
56
+ if (this._fileWriterCache.has(changedFilePath) && !FsUtil.exists(changedFilePath)) {
57
+ this._fileWriterCache.delete(changedFilePath);
58
+ }
59
+ }
60
+ }
61
+
62
+ public async runAsync(): Promise<void> {
63
+ this._logger.debug("Babel NgModule 정의 가져오기...");
64
+ const bbNgModules = this._getBbNgModuleDefs();
65
+
66
+ this._logger.debug("NgModule Preset 구성...");
67
+ const tsPreset = this._getTsGenNgModulePreset();
68
+
69
+ this._logger.debug("RoutingModule 정의 구성...");
70
+ const genNgRoutingModules = await this._getGenNgRoutingModuleDefsAsync();
71
+
72
+ this._logger.debug("NgModule 정의 구성...");
73
+ const genNgModules = this._getGenNgModuleDefs(bbNgModules, tsPreset.modules, tsPreset.sources);
74
+
75
+ this._logger.debug("NgModule 순환참조 병합...");
76
+ const mergedGenNgModules = this._mergeNgModuleCircularDependency(genNgModules);
77
+
78
+ this._logger.debug("생성할 파일 내용 구성...");
79
+ const files = [
80
+ ...this._getGenNgRoutingModuleFiles(genNgRoutingModules),
81
+ ...this._getGenNgModuleFiles(mergedGenNgModules)
82
+ ];
83
+
84
+ this._logger.debug("파일 쓰기...");
85
+ await this._writeFilesAsync(files);
86
+ }
87
+
88
+ private _mergeNgModuleCircularDependency(mods: ITsGenNgModuleDef[]): ITsGenNgModuleDef[] {
89
+ // 중복 'check'목록 구성
90
+ const check = (mod: ITsGenNgModuleDef, ref: { filePath: string; name: string }, chain: ITsGenNgModuleDef[]): ITsGenNgModuleDef[][] => {
91
+ const result: ITsGenNgModuleDef[][] = [];
92
+ for (const imp of mod.imports) {
93
+ if ("filePath" in imp) {
94
+ if (imp.filePath === ref.filePath || imp.name === ref.name) {
95
+ result.push(chain.concat([mod]));
96
+ }
97
+ else {
98
+ const childMod = mods.single((item) => item.filePath === imp.filePath && item.name === imp.name);
99
+ if (childMod) {
100
+ if (chain.some((item) => item.filePath === childMod.filePath && item.name === childMod.name)) {
101
+ continue;
102
+ }
103
+
104
+ result.push(
105
+ ...check(childMod, ref, chain.concat([mod]))
106
+ );
107
+ }
108
+ }
109
+ }
110
+ }
111
+
112
+ return result;
113
+ };
114
+
115
+ const checkedList: ITsGenNgModuleDef[][] = [];
116
+ for (const mod of mods) {
117
+ const circularDepModules = check(mod, { filePath: mod.filePath, name: mod.name }, []);
118
+ if (circularDepModules.length > 0) {
119
+ checkedList.push(...circularDepModules);
120
+ }
121
+ }
122
+ const mergeModPacks = checkedList.map((item) => item.orderBy((item1) => item1.filePath)).distinct();
123
+
124
+ // 'merge'목록 구성
125
+ const currMergedList: ITsGenNgModuleDef[] = [];
126
+ for (const mergeModPack of mergeModPacks) {
127
+ let currMerged = mergeModPack[0];
128
+ for (const mergeMod of mergeModPack.slice(1)) {
129
+ currMerged = {
130
+ filePath: currMerged.filePath,
131
+ name: currMerged.name,
132
+ imports: currMerged.imports.concat(mergeMod.imports).distinct(),
133
+ exports: currMerged.exports.concat(mergeMod.exports).distinct(),
134
+ providers: currMerged.providers.concat(mergeMod.providers).distinct()
135
+ };
136
+ }
137
+ currMerged.imports.remove((imp) => (
138
+ "filePath" in imp &&
139
+ mergeModPack.some((mergeMod) => (
140
+ imp.filePath === mergeMod.filePath &&
141
+ imp.name === mergeMod.name
142
+ ))
143
+ ));
144
+
145
+ currMergedList.push(currMerged);
146
+ }
147
+
148
+ // 기존 'check'된 것들 모두 삭제
149
+ const result: ITsGenNgModuleDef[] = ObjectUtil.clone(mods);
150
+ for (const delMod of mergeModPacks.mapMany()) {
151
+ result.remove((item) => item.filePath === delMod.filePath && item.name === delMod.name);
152
+ }
153
+
154
+ // 'merge'된 것들 등록
155
+ result.push(...currMergedList);
156
+
157
+ // 모든 결과물에서 'import'에 'merge'된것들이 있으면 'merge'결과물 파일로 변경후, distinct
158
+ for (const resultItem of result) {
159
+ for (const imp of resultItem.imports) {
160
+ if (!("filePath" in imp)) continue;
161
+
162
+ for (const mergeModPack of mergeModPacks) {
163
+ if (mergeModPack.some((mergeModPackItem) => mergeModPackItem.filePath === imp.filePath && mergeModPackItem.name === imp.name)) {
164
+ imp.filePath = mergeModPack[0].filePath;
165
+ imp.name = mergeModPack[0].name;
166
+ }
167
+ }
168
+ }
169
+ resultItem.imports = resultItem.imports.distinct();
170
+ }
171
+
172
+ return result;
173
+ }
174
+
175
+ private _findBbMetasFromMeta(srcMeta: TSdCliBbMetadata): TSdCliBbMetadata[] {
176
+ const metaTmp = typeof srcMeta !== "string" && "__TSdCliMetaRef__" in srcMeta ? this._bbMeta.findMeta(srcMeta) : srcMeta;
177
+ return metaTmp instanceof Array ? metaTmp : [metaTmp];
178
+ }
179
+
180
+ private _getBbNgModuleDefs(): IBbNgModuleDef[] {
181
+ const entryRecord = this._bbMeta.getEntryFileMetaRecord();
182
+ const result: IBbNgModuleDef[] = [];
183
+
184
+ for (const moduleName of Object.keys(entryRecord)) {
185
+ const fileMeta = entryRecord[moduleName];
186
+ for (const exp of fileMeta.exports) {
187
+ const metas = this._findBbMetasFromMeta(exp.target);
188
+ for (const meta of metas) {
189
+ if (meta instanceof SdCliBbClassMetadata) {
190
+ if (meta.ngDecl instanceof SdCliBbNgModuleMetadata) {
191
+ const ngDecl = meta.ngDecl;
192
+
193
+ const resultItem: IBbNgModuleDef = {
194
+ moduleName,
195
+ name: exp.exportedName === "*" ? meta.name : exp.exportedName,
196
+ providers: [],
197
+ exports: [],
198
+ selectors: [],
199
+ pipeNames: []
200
+ };
201
+
202
+ for (const modProv of ngDecl.def.providers) {
203
+ const modProvMetas = this._findBbMetasFromMeta(modProv);
204
+ if (modProvMetas.length === 0) {
205
+ throw new NeverEntryError();
206
+ }
207
+
208
+ for (const modProvMeta of modProvMetas) {
209
+ if (modProvMeta instanceof SdCliBbClassMetadata) {
210
+ const ref = this._bbMeta.findExportRef({
211
+ filePath: modProvMeta.filePath,
212
+ name: modProvMeta.name
213
+ });
214
+ if (ref) {
215
+ resultItem.providers.push(ref);
216
+ }
217
+ }
218
+ else if (modProvMeta instanceof SdCliBbObjectMetadata) {
219
+ // 무시
220
+ }
221
+ else {
222
+ throw new NeverEntryError();
223
+ }
224
+ }
225
+ }
226
+
227
+ for (const modExp of ngDecl.def.exports) {
228
+ const modExpMetas = this._findBbMetasFromMeta(modExp);
229
+ if (modExpMetas.length === 0) {
230
+ throw new NeverEntryError();
231
+ }
232
+
233
+ for (const modExpMeta of modExpMetas) {
234
+ if (modExpMeta instanceof SdCliBbClassMetadata) {
235
+ const ref = this._bbMeta.findExportRef({
236
+ filePath: modExpMeta.filePath,
237
+ name: modExpMeta.name
238
+ });
239
+ if (ref) {
240
+ resultItem.exports.push(ref);
241
+ }
242
+
243
+ if (modExpMeta.ngDecl instanceof SdCliBbNgDirectiveMetadata) {
244
+ resultItem.selectors.push(modExpMeta.ngDecl.selector);
245
+ }
246
+ else if (modExpMeta.ngDecl instanceof SdCliBbNgComponentMetadata) {
247
+ resultItem.selectors.push(modExpMeta.ngDecl.selector);
248
+ }
249
+ else if (modExpMeta.ngDecl instanceof SdCliBbNgPipeMetadata) {
250
+ resultItem.pipeNames.push(modExpMeta.ngDecl.pipeName);
251
+ }
252
+ }
253
+ else {
254
+ throw new NeverEntryError();
255
+ }
256
+ }
257
+ }
258
+
259
+ result.push(resultItem);
260
+ }
261
+ }
262
+ }
263
+ }
264
+ }
265
+
266
+ return result;
267
+ }
268
+
269
+ private _getTsGenNgModulePreset(): { modules: ITsNgPresetModuleDef[]; sources: ITsNgPresetSourceDef[] } {
270
+ const fileMetas = this._tsMeta.getFileMetas();
271
+
272
+ const result: { modules: ITsNgPresetModuleDef[]; sources: ITsNgPresetSourceDef[] } = { modules: [], sources: [] };
273
+
274
+ for (const fileMeta of fileMetas) {
275
+ if (!this._srcDirRelPaths.some((item) => fileMeta.filePath.startsWith(path.resolve(this._srcPath, item)))) {
276
+ continue;
277
+ }
278
+
279
+ for (const cls of fileMeta.directExportClasses) {
280
+ if (cls.target.ngDecl) {
281
+ if (cls.target.ngDecl instanceof SdCliTsNgInjectableMetadata) {
282
+ if (cls.target.ngDecl.providedIn === "root") {
283
+ continue;
284
+ }
285
+ }
286
+
287
+ const moduleClassName = cls.exportedName + "Module";
288
+ const moduleFilePath = path.resolve(this._modulesPath, path.relative(this._srcPath, path.dirname(fileMeta.filePath)), moduleClassName);
289
+
290
+ result.sources.push({
291
+ filePath: fileMeta.filePath.replace(/\.[^.]*$/, ""),
292
+ name: cls.exportedName,
293
+ module: {
294
+ filePath: moduleFilePath,
295
+ name: moduleClassName
296
+ },
297
+ imports: fileMeta.imports,
298
+ template: "template" in cls.target.ngDecl ? cls.target.ngDecl.template : undefined,
299
+ isProvider: cls.target.ngDecl instanceof SdCliTsNgInjectableMetadata
300
+ });
301
+
302
+ result.modules.push({
303
+ filePath: moduleFilePath,
304
+ name: moduleClassName,
305
+ source: {
306
+ filePath: fileMeta.filePath.replace(/\.[^.]*$/, ""),
307
+ name: cls.exportedName
308
+ },
309
+ selector: "selector" in cls.target.ngDecl ? cls.target.ngDecl.selector : undefined,
310
+ pipeName: "pipeName" in cls.target.ngDecl ? cls.target.ngDecl.pipeName : undefined
311
+ });
312
+ }
313
+ }
314
+ }
315
+
316
+ return result;
317
+ }
318
+
319
+ private _getGenNgModuleDefs(bbModules: IBbNgModuleDef[], presetModules: ITsNgPresetModuleDef[], presetSources: ITsNgPresetSourceDef[]): ITsGenNgModuleDef[] {
320
+ const result: ITsGenNgModuleDef[] = [];
321
+
322
+ for (const presetSource of presetSources) {
323
+ const imports: TSdCliMetaRef[] = [];
324
+ for (const imp of presetSource.imports) {
325
+ if ("moduleName" in imp) {
326
+ for (const mod of bbModules) {
327
+ if (mod.exports.some((item) => item.moduleName === imp.moduleName && item.name === imp.name)) {
328
+ imports.push({ moduleName: mod.moduleName, name: mod.name, __TSdCliMetaRef__: "__TSdCliMetaRef__" });
329
+ }
330
+ if (mod.providers.some((item) => item.moduleName === imp.moduleName && item.name === imp.name)) {
331
+ imports.push({ moduleName: mod.moduleName, name: mod.name, __TSdCliMetaRef__: "__TSdCliMetaRef__" });
332
+ }
333
+ }
334
+ }
335
+ else {
336
+ for (const mod of presetModules) {
337
+ if (mod.source && mod.source.filePath === imp.filePath && mod.source.name === imp.name) {
338
+ imports.push({ filePath: mod.filePath, name: mod.name, __TSdCliMetaRef__: "__TSdCliMetaRef__" });
339
+ }
340
+ }
341
+ }
342
+ }
343
+
344
+ if (presetSource.template !== undefined) {
345
+ const templateDOM = new JSDOM(presetSource.template);
346
+
347
+ for (const mod of bbModules) {
348
+ for (const selector of mod.selectors) {
349
+ if (
350
+ templateDOM.window.document.querySelector([
351
+ selector,
352
+ selector.replace(/\[/g, "[\\[").replace(/]/g, "\\]]"),
353
+ selector.replace(/\[/g, "[\\(").replace(/]/g, "\\)]")
354
+ ].join(", ")) != null
355
+ ) {
356
+ imports.push({ moduleName: mod.moduleName, name: mod.name, __TSdCliMetaRef__: "__TSdCliMetaRef__" });
357
+ }
358
+ }
359
+
360
+ for (const pipeName of mod.pipeNames) {
361
+ if (new RegExp("| *" + pipeName + "[^\\w]").test(presetSource.template)) {
362
+ imports.push({ moduleName: mod.moduleName, name: mod.name, __TSdCliMetaRef__: "__TSdCliMetaRef__" });
363
+ }
364
+ }
365
+ }
366
+
367
+ for (const mod of presetModules) {
368
+ if (mod.selector !== undefined) {
369
+ if (
370
+ templateDOM.window.document.querySelector([
371
+ mod.selector,
372
+ mod.selector.replace(/\[/g, "[\\[").replace(/]/g, "\\]]"),
373
+ mod.selector.replace(/\[/g, "[\\(").replace(/]/g, "\\)]")
374
+ ].join(", ")) != null
375
+ ) {
376
+ imports.push({ filePath: mod.filePath, name: mod.name, __TSdCliMetaRef__: "__TSdCliMetaRef__" });
377
+ }
378
+ }
379
+
380
+ if (mod.pipeName !== undefined) {
381
+ if (new RegExp("| *" + mod.pipeName + "[^\\w]").test(presetSource.template)) {
382
+ imports.push({ filePath: mod.filePath, name: mod.name, __TSdCliMetaRef__: "__TSdCliMetaRef__" });
383
+ }
384
+ }
385
+ }
386
+ }
387
+
388
+ result.push({
389
+ filePath: presetSource.module.filePath,
390
+ name: presetSource.module.name,
391
+ imports: imports.distinct(),
392
+ exports: presetSource.isProvider ? [] : [{ filePath: presetSource.filePath, name: presetSource.name }],
393
+ providers: presetSource.isProvider ? [{ filePath: presetSource.filePath, name: presetSource.name }] : []
394
+ });
395
+ }
396
+
397
+ return result;
398
+ }
399
+
400
+ private async _getGenNgRoutingModuleDefsAsync(): Promise<TTsGenNgRoutingModuleDef[]> {
401
+ if (this._routeOpts === undefined) return [];
402
+
403
+ const result: TTsGenNgRoutingModuleDef[] = [];
404
+
405
+ const filePaths = await FsUtil.globAsync(path.resolve(this._srcPath, this._routeOpts.glob));
406
+ for (const filePath of filePaths) {
407
+ const sourceClassName = path.basename(filePath, path.extname(filePath));
408
+ if (sourceClassName === this._routeOpts.rootClassName) {
409
+ const childDirName = StringUtil.toKebabCase(sourceClassName.slice(0, -this._routeOpts.fileEndsWith.length));
410
+ const childDirPath = path.resolve(path.dirname(filePath), childDirName);
411
+
412
+ result.push({
413
+ filePath: path.resolve(this._srcPath, "_routes"),
414
+ children: await this._getGenNgRoutingModuleDefChildrenAsync(childDirPath)
415
+ });
416
+ }
417
+ else {
418
+ const routingModuleClassName = sourceClassName + "RoutingModule";
419
+ const routingModuleFilePath = path.resolve(this._modulesPath, path.relative(this._srcPath, path.dirname(filePath)), routingModuleClassName);
420
+ const childDirName = StringUtil.toKebabCase(sourceClassName.slice(0, -this._routeOpts.fileEndsWith.length));
421
+ const childDirPath = path.resolve(path.dirname(filePath), childDirName);
422
+
423
+ result.push({
424
+ filePath: routingModuleFilePath,
425
+ name: routingModuleClassName,
426
+ component: { filePath: filePath.replace(/\.ts$/, ""), name: sourceClassName },
427
+ children: FsUtil.exists(childDirPath) ? await this._getGenNgRoutingModuleDefChildrenAsync(childDirPath) : undefined
428
+ });
429
+ }
430
+ }
431
+
432
+ return result;
433
+ }
434
+
435
+ private async _getGenNgRoutingModuleDefChildrenAsync(rootPath: string): Promise<TTsGenNgRoutingModuleDefChild[]> {
436
+ const result: TTsGenNgRoutingModuleDefChild[] = [];
437
+
438
+ const childNames = await FsUtil.readdirAsync(rootPath);
439
+ for (const childName of childNames) {
440
+ if (!FsUtil.stat(path.resolve(rootPath, childName)).isDirectory()) { // 파일
441
+ const sourceFilePath = path.resolve(rootPath, childName);
442
+ const sourceClassName = path.basename(sourceFilePath, path.extname(childName));
443
+ const moduleClassName = sourceClassName + "Module";
444
+ const moduleFilePath = path.resolve(this._modulesPath, path.relative(this._srcPath, path.dirname(sourceFilePath)), moduleClassName);
445
+
446
+ result.push({
447
+ path: StringUtil.toKebabCase(childName.substring(0, childName.length - 7)),
448
+ target: { filePath: moduleFilePath, name: moduleClassName }
449
+ });
450
+ }
451
+ else if (!FsUtil.exists(path.resolve(rootPath, StringUtil.toPascalCase(childName) + this._routeOpts!.fileEndsWith + ".ts"))) { // 파일없는 디렉토리
452
+ result.push({
453
+ path: childName,
454
+ children: await this._getGenNgRoutingModuleDefChildrenAsync(path.resolve(rootPath, childName))
455
+ });
456
+ }
457
+ }
458
+
459
+ return result;
460
+ }
461
+
462
+ private _getGenNgRoutingModuleFiles(defs: TTsGenNgRoutingModuleDef[]): { filePath: string; content: string }[] {
463
+ const result: { filePath: string; content: string }[] = [];
464
+
465
+ for (const def of defs) {
466
+ const fn = (children: TTsGenNgRoutingModuleDefChild[]): string => {
467
+ let fnResult = "[\n";
468
+ for (const child of children) {
469
+ fnResult += " {\n";
470
+ fnResult += ` path: "${child.path}",\n`;
471
+ if ("target" in child) {
472
+ fnResult += ` loadChildren: async () => await import("${this._getImportModuleName(def.filePath, child.target.filePath)}").then((m) => m.${child.target.name})\n`;
473
+ }
474
+ else {
475
+ fnResult += ` children: ${fn(child.children).replace(/\n/g, "\n ")}\n`;
476
+ }
477
+ fnResult += " },\n";
478
+ }
479
+ fnResult = fnResult.slice(0, -2) + "\n";
480
+ fnResult += "]";
481
+
482
+ return fnResult;
483
+ };
484
+
485
+ const content = "component" in def ? `
486
+ import { NgModule } from "@angular/core";
487
+ import { RouterModule } from "@angular/router";
488
+ import { SdCanDeactivateGuard } from "@simplysm/sd-angular";
489
+ import { ${def.component.name} } from "${this._getImportModuleName(def.filePath, def.component.filePath)}";
490
+
491
+ @NgModule({
492
+ imports: [
493
+ RouterModule.forChild([
494
+ {
495
+ path: "",
496
+ component: ${def.component.name},
497
+ canDeactivate: [SdCanDeactivateGuard]${def.children ? `,
498
+ children: ${fn(def.children).replace(/\n/g, "\n ")}` : ""}
499
+ }
500
+ ])
501
+ ],
502
+ exports: [RouterModule]
503
+ })
504
+ export class ${def.name} {
505
+ }`.trim() : `
506
+ import { Routes } from "@angular/router";
507
+
508
+ export const routes: Routes = ${fn(def.children)};
509
+ `.trim();
510
+
511
+ result.push({ filePath: def.filePath + ".ts", content });
512
+ }
513
+
514
+ return result;
515
+ }
516
+
517
+ private _getGenNgModuleFiles(mods: ITsGenNgModuleDef[]): { filePath: string; content: string }[] {
518
+ const result: { filePath: string; content: string }[] = [];
519
+
520
+ for (const mod of mods) {
521
+ const importMap = [...mod.imports, ...mod.exports, ...mod.providers]
522
+ .map((item) => ({
523
+ moduleName: "moduleName" in item ? item.moduleName : this._getImportModuleName(mod.filePath, item.filePath),
524
+ name: item.name
525
+ }))
526
+ .groupBy((item) => item.moduleName)
527
+ .toMap((item) => item.key, (item) => item.values.map((v) => v.name));
528
+ importMap.getOrCreate("@angular/core", []).push("NgModule");
529
+
530
+ const importTexts: string[] = [];
531
+ for (const moduleName of Array.from(importMap.keys()).orderBy()) {
532
+ const targetNames = importMap.get(moduleName)!.distinct();
533
+ if (targetNames.length === 0) continue;
534
+ importTexts.push(`import { ${targetNames.join(", ")} } from "${moduleName}";`);
535
+ }
536
+
537
+ const moduleImportNames = mod.imports.map((item) => item.name);
538
+ if (
539
+ this._routeOpts !== undefined
540
+ && mod.filePath.endsWith(this._routeOpts.fileEndsWith + "Module")
541
+ && mod.name !== this._routeOpts.rootClassName + "Module"
542
+ ) {
543
+ const routingModuleName = mod.name.replace(/Module$/, "RoutingModule");
544
+ importTexts.push(`import { ${routingModuleName} } from "./${routingModuleName}";`);
545
+ moduleImportNames.push(routingModuleName);
546
+ }
547
+
548
+ const content = `
549
+ ${importTexts.join("\n")}
550
+
551
+ @NgModule({
552
+ imports: [${moduleImportNames.orderBy().join(", ")}],
553
+ declarations: [${mod.exports.map((item) => item.name).distinct().orderBy().join(", ")}],
554
+ exports: [${mod.exports.map((item) => item.name).distinct().orderBy().join(", ")}],
555
+ providers: [${mod.providers.map((item) => item.name).distinct().orderBy().join(", ")}]
556
+ })
557
+ export class ${mod.name} {
558
+ }`.trim();
559
+
560
+ result.push({ filePath: mod.filePath + ".ts", content });
561
+ }
562
+
563
+ return result;
564
+ }
565
+
566
+ private async _writeFilesAsync(files: { filePath: string; content: string }[]): Promise<void> {
567
+ for (const file of files) {
568
+ if (this._fileWriterCache.get(file.filePath) !== file.content) {
569
+ await FsUtil.writeFileAsync(file.filePath, file.content);
570
+ this._fileWriterCache.set(file.filePath, file.content);
571
+ }
572
+ }
573
+
574
+ const glob = await FsUtil.globAsync(path.resolve(this._modulesPath, "**", "*"));
575
+ for (const currFilePath of glob) {
576
+ if (!files.some((item) => item.filePath.startsWith(currFilePath))) {
577
+ await FsUtil.removeAsync(currFilePath);
578
+ this._fileWriterCache.delete(currFilePath);
579
+ }
580
+ }
581
+ }
582
+
583
+ private _getImportModuleName(mainFilePath: string, importFilePath: string): string {
584
+ const filePath = PathUtil.posix(path.relative(path.dirname(mainFilePath), importFilePath).replace(/\.ts$/, ""));
585
+ return filePath.startsWith(".") ? filePath : "./" + filePath;
586
+ }
587
+ }
588
+
589
+ interface IBbNgModuleDef {
590
+ moduleName: string;
591
+ name: string;
592
+ providers: { moduleName: string; name: string }[];
593
+ exports: { moduleName: string; name: string }[];
594
+ selectors: string[];
595
+ pipeNames: string[];
596
+ }
597
+
598
+ interface ITsNgPresetModuleDef {
599
+ filePath: string;
600
+ name: string;
601
+ source?: { filePath: string; name: string };
602
+ selector?: string;
603
+ pipeName?: string;
604
+ }
605
+
606
+ interface ITsNgPresetSourceDef {
607
+ filePath: string;
608
+ name: string;
609
+ module: { filePath: string; name: string };
610
+ imports: TSdCliMetaRef[];
611
+ template?: string;
612
+ isProvider: boolean;
613
+ }
614
+
615
+ interface ITsGenNgModuleDef {
616
+ filePath: string;
617
+ name: string;
618
+ imports: TSdCliMetaRef[];
619
+ exports: { filePath: string; name: string }[];
620
+ providers: { filePath: string; name: string }[];
621
+ }
622
+
623
+ type TTsGenNgRoutingModuleDef = {
624
+ filePath: string;
625
+ name: string;
626
+ component: { filePath: string; name: string };
627
+ children?: TTsGenNgRoutingModuleDefChild[];
628
+ } | {
629
+ filePath: string;
630
+ children: TTsGenNgRoutingModuleDefChild[];
631
+ };
632
+
633
+ type TTsGenNgRoutingModuleDefChild = {
634
+ path: string;
635
+ target: { filePath: string; name: string };
636
+ } | {
637
+ path: string;
638
+ children: TTsGenNgRoutingModuleDefChild[];
639
+ };