@simplysm/sd-cli 7.0.66 → 7.0.74
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/dist/bin/sd-cli.mjs +22 -4
- package/dist/build-tool/SdCliCordova.d.ts +1 -1
- package/dist/build-tool/SdCliCordova.mjs +16 -11
- package/dist/builder/SdCliClientBuilder.mjs +2 -1
- package/dist/builder/SdCliServerBuilder.mjs +27 -16
- package/dist/builder/SdCliTsLibBuilder.mjs +2 -2
- package/dist/commons.d.ts +16 -0
- package/dist/entry-points/SdCliPrepare.mjs +2 -2
- package/dist/entry-points/SdCliWorkspace.d.ts +3 -0
- package/dist/entry-points/SdCliWorkspace.mjs +57 -37
- package/dist/ng-tools/SdCliNgModuleGenerator.d.ts +2 -0
- package/dist/ng-tools/SdCliNgModuleGenerator.mjs +98 -16
- package/dist/packages/SdCliPackage.mjs +52 -5
- package/package.json +6 -6
- package/src/bin/sd-cli.ts +21 -3
- package/src/build-tool/SdCliCordova.ts +15 -10
- package/src/builder/SdCliClientBuilder.ts +1 -0
- package/src/builder/SdCliServerBuilder.ts +28 -15
- package/src/builder/SdCliTsLibBuilder.ts +1 -1
- package/src/commons.ts +19 -0
- package/src/entry-points/SdCliPrepare.ts +1 -1
- package/src/entry-points/SdCliWorkspace.ts +59 -41
- package/src/ng-tools/SdCliNgModuleGenerator.ts +120 -15
- package/src/packages/SdCliPackage.ts +55 -4
- package/tsconfig.json +3 -0
|
@@ -13,8 +13,8 @@ import { ObjectUtil, StringUtil } from "@simplysm/sd-core-common";
|
|
|
13
13
|
import ESLintWebpackPlugin from "eslint-webpack-plugin";
|
|
14
14
|
import CopyWebpackPlugin from "copy-webpack-plugin";
|
|
15
15
|
import { LicenseWebpackPlugin } from "license-webpack-plugin";
|
|
16
|
-
import { createHash } from "crypto";
|
|
17
16
|
import { SdCliNpmConfigUtil } from "../utils/SdCliNpmConfigUtil";
|
|
17
|
+
import { createHash } from "crypto";
|
|
18
18
|
import LintResult = ESLint.LintResult;
|
|
19
19
|
|
|
20
20
|
export class SdCliServerBuilder extends EventEmitter {
|
|
@@ -47,7 +47,7 @@ export class SdCliServerBuilder extends EventEmitter {
|
|
|
47
47
|
|
|
48
48
|
// 빌드 준비
|
|
49
49
|
const extModules = this._getExternalModules();
|
|
50
|
-
const webpackConfig = this._getWebpackConfig(true, extModules
|
|
50
|
+
const webpackConfig = this._getWebpackConfig(true, extModules);
|
|
51
51
|
const compiler = webpack(webpackConfig);
|
|
52
52
|
await new Promise<void>((resolve, reject) => {
|
|
53
53
|
compiler.hooks.watchRun.tapAsync(this.constructor.name, (args, callback) => {
|
|
@@ -81,7 +81,7 @@ export class SdCliServerBuilder extends EventEmitter {
|
|
|
81
81
|
// 빌드
|
|
82
82
|
this._logger.debug("Webpack 빌드 수행...");
|
|
83
83
|
const extModules = this._getExternalModules();
|
|
84
|
-
const webpackConfig = this._getWebpackConfig(false, extModules
|
|
84
|
+
const webpackConfig = this._getWebpackConfig(false, extModules);
|
|
85
85
|
const compiler = webpack(webpackConfig);
|
|
86
86
|
const buildResults = await new Promise<ISdCliPackageBuildResult[]>((resolve, reject) => {
|
|
87
87
|
compiler.run((err, stats) => {
|
|
@@ -130,6 +130,7 @@ export class SdCliServerBuilder extends EventEmitter {
|
|
|
130
130
|
{
|
|
131
131
|
"name": npmConfig.name.replace(/@/g, "").replace(/\//g, "-"),
|
|
132
132
|
"script": path.basename(path.resolve(this._parsedTsconfig.options.outDir!, "main.mjs")),
|
|
133
|
+
"node_args": "--experimental-specifier-resolution=node --experimental-import-meta-resolve",
|
|
133
134
|
"watch": true,
|
|
134
135
|
"watch_delay": 2000,
|
|
135
136
|
"ignore_watch": [
|
|
@@ -211,7 +212,7 @@ export class SdCliServerBuilder extends EventEmitter {
|
|
|
211
212
|
].map((p) => path.dirname(p));
|
|
212
213
|
}
|
|
213
214
|
|
|
214
|
-
private _getWebpackConfig(watch: boolean,
|
|
215
|
+
private _getWebpackConfig(watch: boolean, extModules: { name: string; exists: boolean }[]): webpack.Configuration {
|
|
215
216
|
const workspaceNpmConfig = this._getNpmConfig(this._workspaceRootPath)!;
|
|
216
217
|
const workspaceName = workspaceNpmConfig.name;
|
|
217
218
|
|
|
@@ -227,15 +228,15 @@ export class SdCliServerBuilder extends EventEmitter {
|
|
|
227
228
|
return {
|
|
228
229
|
mode: watch ? "development" : "production",
|
|
229
230
|
devtool: false,
|
|
230
|
-
target: ["node", "
|
|
231
|
+
target: ["node", "es2020"],
|
|
231
232
|
profile: false,
|
|
232
233
|
resolve: {
|
|
233
234
|
roots: [this._rootPath],
|
|
234
235
|
extensions: [".ts", ".js", ".mjs", ".cjs"],
|
|
235
236
|
symlinks: true,
|
|
236
237
|
modules: [this._workspaceRootPath, "node_modules"],
|
|
237
|
-
mainFields: ["
|
|
238
|
-
conditionNames: ["
|
|
238
|
+
mainFields: ["es2020", "default", "module", "main"],
|
|
239
|
+
conditionNames: ["es2020", "..."]
|
|
239
240
|
},
|
|
240
241
|
resolveLoader: {
|
|
241
242
|
symlinks: true
|
|
@@ -251,17 +252,23 @@ export class SdCliServerBuilder extends EventEmitter {
|
|
|
251
252
|
hashFunction: "xxhash64",
|
|
252
253
|
clean: true,
|
|
253
254
|
path: this._parsedTsconfig.options.outDir,
|
|
254
|
-
filename: "[name].
|
|
255
|
-
chunkFilename: "[name].
|
|
255
|
+
filename: "[name].mjs",
|
|
256
|
+
chunkFilename: "[name].mjs",
|
|
256
257
|
assetModuleFilename: "res/[name][ext][query]",
|
|
257
|
-
|
|
258
|
+
library: {
|
|
259
|
+
type: "module"
|
|
260
|
+
},
|
|
261
|
+
module: true
|
|
262
|
+
},
|
|
263
|
+
experiments: {
|
|
264
|
+
outputModule: true
|
|
258
265
|
},
|
|
259
266
|
watch: false,
|
|
260
267
|
watchOptions: { poll: undefined, ignored: undefined },
|
|
261
268
|
performance: { hints: false },
|
|
262
269
|
infrastructureLogging: { level: "error" },
|
|
263
270
|
stats: "errors-warnings",
|
|
264
|
-
externals:
|
|
271
|
+
externals: extModules.toObject((item) => item.name, (item) => "node-commonjs " + item.name),
|
|
265
272
|
cache: {
|
|
266
273
|
type: "filesystem",
|
|
267
274
|
profile: watch ? undefined : false,
|
|
@@ -276,6 +283,7 @@ export class SdCliServerBuilder extends EventEmitter {
|
|
|
276
283
|
.update(watch.toString())
|
|
277
284
|
.digest("hex")
|
|
278
285
|
},
|
|
286
|
+
// cache: { type: "memory", maxGenerations: 1 },
|
|
279
287
|
...watch ? {
|
|
280
288
|
snapshot: {
|
|
281
289
|
immutablePaths: internalModuleCachePaths,
|
|
@@ -289,7 +297,7 @@ export class SdCliServerBuilder extends EventEmitter {
|
|
|
289
297
|
extractComments: false,
|
|
290
298
|
terserOptions: {
|
|
291
299
|
compress: true,
|
|
292
|
-
ecma:
|
|
300
|
+
ecma: 2020,
|
|
293
301
|
sourceMap: false,
|
|
294
302
|
keep_classnames: true,
|
|
295
303
|
keep_fnames: true,
|
|
@@ -318,9 +326,6 @@ export class SdCliServerBuilder extends EventEmitter {
|
|
|
318
326
|
test: /\.[cm]?[tj]sx?$/,
|
|
319
327
|
resolve: {
|
|
320
328
|
fullySpecified: false
|
|
321
|
-
},
|
|
322
|
-
use: {
|
|
323
|
-
loader: "babel-loader"
|
|
324
329
|
}
|
|
325
330
|
},
|
|
326
331
|
...watch ? [
|
|
@@ -455,6 +460,10 @@ export class SdCliServerBuilder extends EventEmitter {
|
|
|
455
460
|
results.push({ name: moduleName, exists: true });
|
|
456
461
|
}
|
|
457
462
|
|
|
463
|
+
if (this._config.externalNodeModules?.includes(moduleName)) {
|
|
464
|
+
results.push({ name: moduleName, exists: true });
|
|
465
|
+
}
|
|
466
|
+
|
|
458
467
|
fn(modulePath);
|
|
459
468
|
}
|
|
460
469
|
|
|
@@ -472,6 +481,10 @@ export class SdCliServerBuilder extends EventEmitter {
|
|
|
472
481
|
results.push({ name: optModuleName, exists: true });
|
|
473
482
|
}
|
|
474
483
|
|
|
484
|
+
if (this._config.externalNodeModules?.includes(optModuleName)) {
|
|
485
|
+
results.push({ name: optModuleName, exists: true });
|
|
486
|
+
}
|
|
487
|
+
|
|
475
488
|
fn(optModulePath);
|
|
476
489
|
}
|
|
477
490
|
};
|
package/src/commons.ts
CHANGED
|
@@ -57,6 +57,8 @@ export interface ISdCliServerPackageConfig {
|
|
|
57
57
|
configs?: Record<string, any>;
|
|
58
58
|
pm2?: Record<string, any> | boolean;
|
|
59
59
|
iis?: { serverExeFilePath?: string } | boolean;
|
|
60
|
+
externalNodeModules?: string[];
|
|
61
|
+
publish?: TSdCliPublishConfig;
|
|
60
62
|
}
|
|
61
63
|
|
|
62
64
|
export interface ISdCliClientPackageConfig {
|
|
@@ -65,6 +67,23 @@ export interface ISdCliClientPackageConfig {
|
|
|
65
67
|
server: string;
|
|
66
68
|
env?: Record<string, string>;
|
|
67
69
|
configs?: Record<string, any>;
|
|
70
|
+
publish?: TSdCliPublishConfig;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export type TSdCliPublishConfig = ISdCliFtpPublishConfig | ISdCliLocalDirectoryPublishConfig;
|
|
74
|
+
|
|
75
|
+
export interface ISdCliFtpPublishConfig {
|
|
76
|
+
type: "ftp" | "ftps" | "sftp";
|
|
77
|
+
host: string;
|
|
78
|
+
port?: number;
|
|
79
|
+
path: string;
|
|
80
|
+
user: string;
|
|
81
|
+
pass: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface ISdCliLocalDirectoryPublishConfig {
|
|
85
|
+
type: "local-directory";
|
|
86
|
+
path: string;
|
|
68
87
|
}
|
|
69
88
|
|
|
70
89
|
export interface ISdCliClientPackageCordovaConfig {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FsUtil, Logger, SdProcess } from "@simplysm/sd-core-node";
|
|
1
|
+
import { FsUtil, Logger, PathUtil, SdProcess } from "@simplysm/sd-core-node";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { INpmConfig, ISdCliConfig, ISdCliPackageBuildResult } from "../commons";
|
|
4
4
|
import { SdCliPackage } from "../packages/SdCliPackage";
|
|
@@ -23,7 +23,7 @@ export class SdCliWorkspace {
|
|
|
23
23
|
this._npmConfig = FsUtil.readJson(npmConfigFilePath);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
public async watchAsync(opt: { confFileRelPath: string; optNames: string[] }): Promise<void> {
|
|
26
|
+
public async watchAsync(opt: { confFileRelPath: string; optNames: string[]; pkgs: string[] }): Promise<void> {
|
|
27
27
|
this._logger.debug("프로젝트 설정 가져오기...");
|
|
28
28
|
const config = await SdCliConfigUtil.loadConfigAsync(path.resolve(this._rootPath, opt.confFileRelPath), true, opt.optNames);
|
|
29
29
|
|
|
@@ -33,7 +33,7 @@ export class SdCliWorkspace {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
this._logger.debug("패키지 목록 구성...");
|
|
36
|
-
const pkgs = await this._getPackagesAsync(config);
|
|
36
|
+
const pkgs = await this._getPackagesAsync(config, opt.pkgs);
|
|
37
37
|
|
|
38
38
|
this._logger.debug("패키지 이벤트 설정...");
|
|
39
39
|
let changeCount = 0;
|
|
@@ -72,22 +72,29 @@ export class SdCliWorkspace {
|
|
|
72
72
|
}
|
|
73
73
|
changeCount++;
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
await
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
75
|
+
try {
|
|
76
|
+
const pkgNames = pkgs.map((item) => item.name);
|
|
77
|
+
const buildCompletedPackageNames: string[] = [];
|
|
78
|
+
await pkgs.parallelAsync(async (pkg) => {
|
|
79
|
+
await Wait.until(() => !pkg.allDependencies.some((dep) => pkgNames.includes(dep) && !buildCompletedPackageNames.includes(dep)));
|
|
80
|
+
if (pkg.config.type === "client") {
|
|
81
|
+
const middlewares = (await pkg.watchAsync()) as NextHandleFunction[];
|
|
82
|
+
|
|
83
|
+
const serverInfo = this._serverInfoMap.getOrCreate(pkg.config.server, { middlewares: [], clientInfos: [] });
|
|
84
|
+
serverInfo.middlewares.push(...middlewares);
|
|
85
|
+
serverInfo.clientInfos.push({ pkgKey: pkg.name.split("/").last()! });
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
await pkg.watchAsync();
|
|
89
|
+
}
|
|
90
|
+
buildCompletedPackageNames.push(pkg.name);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
this._loggingResults(totalResultMap);
|
|
95
|
+
this._loggingOpenClientHrefs();
|
|
96
|
+
throw err;
|
|
97
|
+
}
|
|
91
98
|
|
|
92
99
|
changeCount--;
|
|
93
100
|
if (changeCount === 0) {
|
|
@@ -111,18 +118,22 @@ export class SdCliWorkspace {
|
|
|
111
118
|
}
|
|
112
119
|
const entryFilePath = path.resolve(pkg.rootPath, entryFileRelPath);
|
|
113
120
|
|
|
114
|
-
this._logger.log(`서버(${pkg.name}) 재시작...`);
|
|
115
121
|
try {
|
|
116
122
|
const serverInfo = this._serverInfoMap.getOrCreate(path.basename(pkg.rootPath), {
|
|
117
123
|
middlewares: [],
|
|
118
124
|
clientInfos: []
|
|
119
125
|
});
|
|
120
126
|
if (serverInfo.server) {
|
|
127
|
+
this._logger.log(`[${pkg.name}] 기존 서버 중지...`);
|
|
121
128
|
await serverInfo.server.closeAsync();
|
|
122
129
|
delete serverInfo.server;
|
|
123
130
|
}
|
|
124
131
|
|
|
125
|
-
|
|
132
|
+
this._logger.log(`[${pkg.name}] 서버 시작중...`);
|
|
133
|
+
|
|
134
|
+
const serverMainPath = "file:///" + PathUtil.posix(entryFilePath) + "?update=" + Uuid.new().toString().replace(/-/g, "");
|
|
135
|
+
const serverModule = await import(serverMainPath);
|
|
136
|
+
serverInfo.server = serverModule.default as SdServiceServer | undefined;
|
|
126
137
|
if (!serverInfo.server) {
|
|
127
138
|
this._logger.error(`${entryFilePath}(0, 0): 'SdServiceServer'를 'export'해야 합니다.`);
|
|
128
139
|
this._isServerRestarting = false;
|
|
@@ -131,7 +142,7 @@ export class SdCliWorkspace {
|
|
|
131
142
|
serverInfo.server.devMiddlewares = serverInfo.middlewares;
|
|
132
143
|
await Wait.until(() => serverInfo.server!.isOpen);
|
|
133
144
|
|
|
134
|
-
this._logger.log(
|
|
145
|
+
this._logger.log(`[${pkg.name}] 서버가 시작되었습니다.`);
|
|
135
146
|
}
|
|
136
147
|
catch (err) {
|
|
137
148
|
this._logger.error(`서버(${pkg.name}) 재시작중 오류 발생`, err);
|
|
@@ -140,12 +151,12 @@ export class SdCliWorkspace {
|
|
|
140
151
|
this._isServerRestarting = false;
|
|
141
152
|
}
|
|
142
153
|
|
|
143
|
-
public async buildAsync(opt: { confFileRelPath: string; optNames: string[] }): Promise<void> {
|
|
154
|
+
public async buildAsync(opt: { confFileRelPath: string; optNames: string[]; pkgs: string[] }): Promise<void> {
|
|
144
155
|
this._logger.debug("프로젝트 설정 가져오기...");
|
|
145
156
|
const config = await SdCliConfigUtil.loadConfigAsync(path.resolve(this._rootPath, opt.confFileRelPath), false, opt.optNames);
|
|
146
157
|
|
|
147
158
|
this._logger.debug("패키지 목록 구성...");
|
|
148
|
-
const pkgs = await this._getPackagesAsync(config);
|
|
159
|
+
const pkgs = await this._getPackagesAsync(config, opt.pkgs);
|
|
149
160
|
|
|
150
161
|
this._logger.debug("프로젝트 및 패키지 버전 설정...");
|
|
151
162
|
await this._upgradeVersionAsync(pkgs);
|
|
@@ -159,26 +170,32 @@ export class SdCliWorkspace {
|
|
|
159
170
|
const pkgNames = pkgs.map((item) => item.name);
|
|
160
171
|
const buildCompletedPackageNames: string[] = [];
|
|
161
172
|
const totalResultMap = new Map<string, ISdCliPackageBuildResult[]>();
|
|
162
|
-
|
|
163
|
-
await
|
|
173
|
+
try {
|
|
174
|
+
await pkgs.parallelAsync(async (pkg) => {
|
|
175
|
+
await Wait.until(() => !pkg.allDependencies.some((dep) => pkgNames.includes(dep) && !buildCompletedPackageNames.includes(dep)));
|
|
164
176
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
177
|
+
this._logger.debug(`[${pkg.name}] 빌드를 시작합니다...`);
|
|
178
|
+
totalResultMap.set(pkg.name, await pkg.buildAsync());
|
|
179
|
+
this._logger.debug(`[${pkg.name}] 빌드가 완료되었습니다.`);
|
|
168
180
|
|
|
169
|
-
|
|
170
|
-
|
|
181
|
+
buildCompletedPackageNames.push(pkg.name);
|
|
182
|
+
});
|
|
171
183
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
184
|
+
this._loggingResults(totalResultMap);
|
|
185
|
+
if (Array.from(totalResultMap.values()).mapMany().some((item) => item.severity === "error")) {
|
|
186
|
+
throw new Error("빌드중 오류가 발생하였습니다.");
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
this._logger.info("모든 빌드가 완료되었습니다.");
|
|
190
|
+
}
|
|
175
191
|
}
|
|
176
|
-
|
|
177
|
-
this.
|
|
192
|
+
catch (err) {
|
|
193
|
+
this._loggingResults(totalResultMap);
|
|
194
|
+
throw new err;
|
|
178
195
|
}
|
|
179
196
|
}
|
|
180
197
|
|
|
181
|
-
public async publishAsync(opt: { noBuild: boolean; confFileRelPath: string; optNames: string[] }): Promise<void> {
|
|
198
|
+
public async publishAsync(opt: { noBuild: boolean; confFileRelPath: string; optNames: string[]; pkgs: string[] }): Promise<void> {
|
|
182
199
|
this._logger.debug("프로젝트 설정 가져오기...");
|
|
183
200
|
const config = await SdCliConfigUtil.loadConfigAsync(path.resolve(this._rootPath, opt.confFileRelPath), false, opt.optNames);
|
|
184
201
|
|
|
@@ -197,7 +214,7 @@ export class SdCliWorkspace {
|
|
|
197
214
|
}
|
|
198
215
|
|
|
199
216
|
this._logger.debug("패키지 목록 구성...");
|
|
200
|
-
const pkgs = await this._getPackagesAsync(config);
|
|
217
|
+
const pkgs = await this._getPackagesAsync(config, opt.pkgs);
|
|
201
218
|
|
|
202
219
|
this._logger.debug("프로젝트 및 패키지 버전 설정...");
|
|
203
220
|
await this._upgradeVersionAsync(pkgs);
|
|
@@ -270,16 +287,17 @@ export class SdCliWorkspace {
|
|
|
270
287
|
process.stdout.clearLine(0);
|
|
271
288
|
}
|
|
272
289
|
|
|
273
|
-
private async _getPackagesAsync(conf: ISdCliConfig): Promise<SdCliPackage[]> {
|
|
290
|
+
private async _getPackagesAsync(conf: ISdCliConfig, pkgs: string[]): Promise<SdCliPackage[]> {
|
|
274
291
|
const pkgRootPaths = await this._npmConfig.workspaces?.mapManyAsync(async (item) => await FsUtil.globAsync(item));
|
|
275
292
|
if (!pkgRootPaths) {
|
|
276
293
|
throw new Error("최상위 'package.json'에서 'workspaces'를 찾을 수 없습니다.");
|
|
277
294
|
}
|
|
278
295
|
|
|
279
|
-
|
|
280
296
|
return pkgRootPaths.map((pkgRootPath) => {
|
|
297
|
+
if (pkgs.length > 0 && !pkgs.includes(path.basename(pkgRootPath))) return undefined;
|
|
281
298
|
const pkgConfig = conf.packages[path.basename(pkgRootPath)];
|
|
282
|
-
|
|
299
|
+
if (!pkgConfig) return undefined;
|
|
300
|
+
return new SdCliPackage(this._rootPath, pkgRootPath, pkgConfig);
|
|
283
301
|
}).filterExists();
|
|
284
302
|
}
|
|
285
303
|
|
|
@@ -7,17 +7,21 @@ import {
|
|
|
7
7
|
SdCliBbNgModuleMetadata,
|
|
8
8
|
SdCliBbNgPipeMetadata
|
|
9
9
|
} from "./babel/TSdCliBbNgMetadata";
|
|
10
|
-
import { NeverEntryError, StringUtil } from "@simplysm/sd-core-common";
|
|
10
|
+
import { NeverEntryError, ObjectUtil, StringUtil } from "@simplysm/sd-core-common";
|
|
11
11
|
import { TSdCliMetaRef } from "./commons";
|
|
12
12
|
import { SdCliTsNgInjectableMetadata } from "./typescript/SdCliTsFileMetadata";
|
|
13
13
|
import path from "path";
|
|
14
14
|
import { JSDOM } from "jsdom";
|
|
15
|
-
import { FsUtil, PathUtil } from "@simplysm/sd-core-node";
|
|
15
|
+
import { FsUtil, Logger, PathUtil } from "@simplysm/sd-core-node";
|
|
16
16
|
import os from "os";
|
|
17
|
+
import { INpmConfig } from "../commons";
|
|
17
18
|
|
|
18
19
|
// TODO: 에러 메시지 처리
|
|
19
20
|
// TODO: forRoot에 있는 Provider 처리??
|
|
21
|
+
// TODO: CircularDependency
|
|
20
22
|
export class SdCliNgModuleGenerator {
|
|
23
|
+
private readonly _logger: Logger;
|
|
24
|
+
|
|
21
25
|
private readonly _bbMeta: SdCliBbRootMetadata;
|
|
22
26
|
private readonly _tsMeta: SdCliTsRootMetadata;
|
|
23
27
|
|
|
@@ -44,6 +48,9 @@ export class SdCliNgModuleGenerator {
|
|
|
44
48
|
const content = FsUtil.readFile(currFilePath);
|
|
45
49
|
this._fileWriterCache.set(currFilePath, content);
|
|
46
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]);
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
public removeCaches(changedFilePaths: string[]): void {
|
|
@@ -57,20 +64,118 @@ export class SdCliNgModuleGenerator {
|
|
|
57
64
|
}
|
|
58
65
|
|
|
59
66
|
public async runAsync(): Promise<void> {
|
|
67
|
+
this._logger.debug("Babel NgModule 정의 가져오기...");
|
|
60
68
|
const bbNgModules = this._getBbNgModuleDefs();
|
|
69
|
+
|
|
70
|
+
this._logger.debug("NgModule Preset 구성...");
|
|
61
71
|
const tsPreset = this._getTsGenNgModulePreset();
|
|
62
72
|
|
|
73
|
+
this._logger.debug("RoutingModule 정의 구성...");
|
|
63
74
|
const genNgRoutingModules = await this._getGenNgRoutingModuleDefsAsync();
|
|
75
|
+
|
|
76
|
+
this._logger.debug("NgModule 정의 구성...");
|
|
64
77
|
const genNgModules = this._getGenNgModuleDefs(bbNgModules, tsPreset.modules, tsPreset.sources);
|
|
65
78
|
|
|
79
|
+
this._logger.debug("NgModule 순환참조 병합...");
|
|
80
|
+
const mergedGenNgModules = this._mergeNgModuleCircularDependency(genNgModules);
|
|
81
|
+
|
|
82
|
+
this._logger.debug("생성할 파일 내용 구성...");
|
|
66
83
|
const files = [
|
|
67
84
|
...this._getGenNgRoutingModuleFiles(genNgRoutingModules),
|
|
68
|
-
...this._getGenNgModuleFiles(
|
|
85
|
+
...this._getGenNgModuleFiles(mergedGenNgModules)
|
|
69
86
|
];
|
|
70
87
|
|
|
88
|
+
this._logger.debug("파일 쓰기...");
|
|
71
89
|
await this._writeFilesAsync(files);
|
|
72
90
|
}
|
|
73
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
|
+
|
|
74
179
|
private _findBbMetasFromMeta(srcMeta: TSdCliBbMetadata): TSdCliBbMetadata[] {
|
|
75
180
|
const metaTmp = typeof srcMeta !== "string" && "__TSdCliMetaRef__" in srcMeta ? this._bbMeta.findMeta(srcMeta) : srcMeta;
|
|
76
181
|
return metaTmp instanceof Array ? metaTmp : [metaTmp];
|
|
@@ -187,7 +292,7 @@ export class SdCliNgModuleGenerator {
|
|
|
187
292
|
const moduleFilePath = path.resolve(this._modulesPath, path.relative(this._srcPath, path.dirname(fileMeta.filePath)), moduleClassName);
|
|
188
293
|
|
|
189
294
|
result.sources.push({
|
|
190
|
-
filePath: fileMeta.filePath,
|
|
295
|
+
filePath: fileMeta.filePath.replace(/\.[^.]*$/, ""),
|
|
191
296
|
name: cls.exportedName,
|
|
192
297
|
module: {
|
|
193
298
|
filePath: moduleFilePath,
|
|
@@ -202,7 +307,7 @@ export class SdCliNgModuleGenerator {
|
|
|
202
307
|
filePath: moduleFilePath,
|
|
203
308
|
name: moduleClassName,
|
|
204
309
|
source: {
|
|
205
|
-
filePath: fileMeta.filePath,
|
|
310
|
+
filePath: fileMeta.filePath.replace(/\.[^.]*$/, ""),
|
|
206
311
|
name: cls.exportedName
|
|
207
312
|
},
|
|
208
313
|
selector: "selector" in cls.target.ngDecl ? cls.target.ngDecl.selector : undefined,
|
|
@@ -218,9 +323,9 @@ export class SdCliNgModuleGenerator {
|
|
|
218
323
|
private _getGenNgModuleDefs(bbModules: IBbNgModuleDef[], presetModules: ITsNgPresetModuleDef[], presetSources: ITsNgPresetSourceDef[]): ITsGenNgModuleDef[] {
|
|
219
324
|
const result: ITsGenNgModuleDef[] = [];
|
|
220
325
|
|
|
221
|
-
for (const
|
|
326
|
+
for (const presetSource of presetSources) {
|
|
222
327
|
const imports: TSdCliMetaRef[] = [];
|
|
223
|
-
for (const imp of
|
|
328
|
+
for (const imp of presetSource.imports) {
|
|
224
329
|
if ("moduleName" in imp) {
|
|
225
330
|
for (const mod of bbModules) {
|
|
226
331
|
if (mod.exports.some((item) => item.moduleName === imp.moduleName && item.name === imp.name)) {
|
|
@@ -240,8 +345,8 @@ export class SdCliNgModuleGenerator {
|
|
|
240
345
|
}
|
|
241
346
|
}
|
|
242
347
|
|
|
243
|
-
if (
|
|
244
|
-
const templateDOM = new JSDOM(
|
|
348
|
+
if (presetSource.template !== undefined) {
|
|
349
|
+
const templateDOM = new JSDOM(presetSource.template);
|
|
245
350
|
|
|
246
351
|
for (const mod of bbModules) {
|
|
247
352
|
for (const selector of mod.selectors) {
|
|
@@ -257,7 +362,7 @@ export class SdCliNgModuleGenerator {
|
|
|
257
362
|
}
|
|
258
363
|
|
|
259
364
|
for (const pipeName of mod.pipeNames) {
|
|
260
|
-
if (new RegExp("| *" + pipeName + "[^\\w]").test(
|
|
365
|
+
if (new RegExp("| *" + pipeName + "[^\\w]").test(presetSource.template)) {
|
|
261
366
|
imports.push({ moduleName: mod.moduleName, name: mod.name, __TSdCliMetaRef__: "__TSdCliMetaRef__" });
|
|
262
367
|
}
|
|
263
368
|
}
|
|
@@ -277,7 +382,7 @@ export class SdCliNgModuleGenerator {
|
|
|
277
382
|
}
|
|
278
383
|
|
|
279
384
|
if (mod.pipeName !== undefined) {
|
|
280
|
-
if (new RegExp("| *" + mod.pipeName + "[^\\w]").test(
|
|
385
|
+
if (new RegExp("| *" + mod.pipeName + "[^\\w]").test(presetSource.template)) {
|
|
281
386
|
imports.push({ filePath: mod.filePath, name: mod.name, __TSdCliMetaRef__: "__TSdCliMetaRef__" });
|
|
282
387
|
}
|
|
283
388
|
}
|
|
@@ -285,11 +390,11 @@ export class SdCliNgModuleGenerator {
|
|
|
285
390
|
}
|
|
286
391
|
|
|
287
392
|
result.push({
|
|
288
|
-
filePath:
|
|
289
|
-
name:
|
|
393
|
+
filePath: presetSource.module.filePath,
|
|
394
|
+
name: presetSource.module.name,
|
|
290
395
|
imports: imports.distinct(),
|
|
291
|
-
exports:
|
|
292
|
-
providers:
|
|
396
|
+
exports: presetSource.isProvider ? [] : [{ filePath: presetSource.filePath, name: presetSource.name }],
|
|
397
|
+
providers: presetSource.isProvider ? [{ filePath: presetSource.filePath, name: presetSource.name }] : []
|
|
293
398
|
});
|
|
294
399
|
}
|
|
295
400
|
|