@simplysm/sd-cli 7.0.65 → 7.0.75

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