@simplysm/sd-cli 7.0.23 → 7.0.37
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.d.ts +1 -1
- package/dist/bin/sd-cli.mjs +15 -3
- package/dist/builder/SdCliClientBuilder.d.ts +20 -0
- package/dist/builder/SdCliClientBuilder.mjs +392 -0
- package/dist/builder/SdCliServerBuilder.d.ts +24 -0
- package/dist/builder/SdCliServerBuilder.mjs +399 -0
- package/dist/builder/SdCliTsLibBuilder.d.ts +5 -3
- package/dist/builder/SdCliTsLibBuilder.mjs +17 -18
- package/dist/commons.d.ts +10 -4
- package/dist/entry-points/SdCliNpm.d.ts +6 -0
- package/dist/entry-points/SdCliNpm.mjs +22 -0
- package/dist/entry-points/SdCliPrepare.d.ts +5 -0
- package/dist/entry-points/SdCliPrepare.mjs +54 -0
- package/dist/entry-points/SdCliWorkspace.d.ts +1 -0
- package/dist/entry-points/SdCliWorkspace.mjs +43 -36
- package/dist/packages/SdCliPackage.d.ts +1 -0
- package/dist/packages/SdCliPackage.mjs +20 -19
- package/dist/utils/SdCliBuildResultUtil.d.ts +4 -1
- package/dist/utils/SdCliBuildResultUtil.mjs +24 -2
- package/dist/utils/SdCliNpmConfigUtil.d.ts +7 -0
- package/dist/utils/SdCliNpmConfigUtil.mjs +15 -0
- package/package.json +22 -7
- package/src/bin/sd-cli.ts +22 -2
- package/src/builder/SdCliClientBuilder.ts +426 -0
- package/src/builder/SdCliServerBuilder.ts +457 -0
- package/src/builder/SdCliTsLibBuilder.ts +26 -21
- package/src/commons.ts +6 -5
- package/src/entry-points/SdCliNpm.ts +26 -0
- package/src/entry-points/SdCliPrepare.ts +54 -0
- package/src/entry-points/SdCliWorkspace.ts +51 -42
- package/src/packages/SdCliPackage.ts +21 -23
- package/src/utils/SdCliBuildResultUtil.ts +30 -1
- package/src/utils/SdCliNpmConfigUtil.ts +16 -0
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import { INpmConfig, ISdCliPackageBuildResult, ISdCliServerPackageConfig } from "../commons";
|
|
2
|
+
import { EventEmitter } from "events";
|
|
3
|
+
import { FsUtil, Logger, PathUtil } from "@simplysm/sd-core-node";
|
|
4
|
+
import webpack from "webpack";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import ts from "typescript";
|
|
7
|
+
import { SdCliBuildResultUtil } from "../utils/SdCliBuildResultUtil";
|
|
8
|
+
import { ErrorInfo } from "ts-loader/dist/interfaces";
|
|
9
|
+
import os from "os";
|
|
10
|
+
import { ESLint } from "eslint";
|
|
11
|
+
import TerserPlugin from "terser-webpack-plugin";
|
|
12
|
+
import { ObjectUtil, StringUtil } from "@simplysm/sd-core-common";
|
|
13
|
+
import ESLintWebpackPlugin from "eslint-webpack-plugin";
|
|
14
|
+
import CopyWebpackPlugin from "copy-webpack-plugin";
|
|
15
|
+
import { LicenseWebpackPlugin } from "license-webpack-plugin";
|
|
16
|
+
import LintResult = ESLint.LintResult;
|
|
17
|
+
|
|
18
|
+
export class SdCliServerBuilder extends EventEmitter {
|
|
19
|
+
private readonly _logger = Logger.get(["simplysm", "sd-cli", this.constructor.name]);
|
|
20
|
+
|
|
21
|
+
private readonly _tsconfigFilePath: string;
|
|
22
|
+
private readonly _parsedTsconfig: ts.ParsedCommandLine;
|
|
23
|
+
private readonly _npmConfigMap = new Map<string, INpmConfig>();
|
|
24
|
+
|
|
25
|
+
public constructor(private readonly _rootPath: string,
|
|
26
|
+
private readonly _config: ISdCliServerPackageConfig,
|
|
27
|
+
private readonly _workspaceRootPath: string) {
|
|
28
|
+
super();
|
|
29
|
+
|
|
30
|
+
// tsconfig
|
|
31
|
+
this._tsconfigFilePath = path.resolve(this._rootPath, "tsconfig-build.json");
|
|
32
|
+
const tsconfig = FsUtil.readJson(this._tsconfigFilePath);
|
|
33
|
+
this._parsedTsconfig = ts.parseJsonConfigFileContent(tsconfig, ts.sys, this._rootPath);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public override on(event: "change", listener: () => void): this;
|
|
37
|
+
public override on(event: "complete", listener: (results: ISdCliPackageBuildResult[]) => void): this;
|
|
38
|
+
public override on(event: string | symbol, listener: (...args: any[]) => void): this {
|
|
39
|
+
return super.on(event, listener);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public async watchAsync(): Promise<void> {
|
|
43
|
+
// DIST 비우기
|
|
44
|
+
await FsUtil.removeAsync(this._parsedTsconfig.options.outDir!);
|
|
45
|
+
|
|
46
|
+
// 빌드 준비
|
|
47
|
+
const extModuleNames = this._getExternalModuleNames();
|
|
48
|
+
const webpackConfig = this._getWebpackConfig(true, extModuleNames);
|
|
49
|
+
const compiler = webpack(webpackConfig);
|
|
50
|
+
await new Promise<void>((resolve, reject) => {
|
|
51
|
+
compiler.hooks.watchRun.tapAsync(this.constructor.name, (args, callback) => {
|
|
52
|
+
this.emit("change");
|
|
53
|
+
callback();
|
|
54
|
+
|
|
55
|
+
this._logger.debug("Webpack 빌드 수행...");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
compiler.watch({}, async (err, stats) => {
|
|
59
|
+
if (err != null || stats == null) {
|
|
60
|
+
reject(err);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// .config.json 파일 쓰기
|
|
65
|
+
await this._writeDistConfigFileAsync();
|
|
66
|
+
|
|
67
|
+
// 결과 반환
|
|
68
|
+
const results = SdCliBuildResultUtil.convertFromWebpackStats(stats);
|
|
69
|
+
this.emit("complete", results);
|
|
70
|
+
|
|
71
|
+
// 마무리
|
|
72
|
+
this._logger.debug("Webpack 빌드 완료");
|
|
73
|
+
resolve();
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public async buildAsync(): Promise<ISdCliPackageBuildResult[]> {
|
|
79
|
+
// DIST 비우기
|
|
80
|
+
await FsUtil.removeAsync(this._parsedTsconfig.options.outDir!);
|
|
81
|
+
|
|
82
|
+
// 빌드
|
|
83
|
+
this._logger.debug("Webpack 빌드 수행...");
|
|
84
|
+
const extModuleNames = this._getExternalModuleNames();
|
|
85
|
+
const webpackConfig = this._getWebpackConfig(false, extModuleNames);
|
|
86
|
+
const compiler = webpack(webpackConfig);
|
|
87
|
+
const buildResults = await new Promise<ISdCliPackageBuildResult[]>((resolve, reject) => {
|
|
88
|
+
compiler.run((err, stats) => {
|
|
89
|
+
if (err != null || stats == null) {
|
|
90
|
+
reject(err);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 결과 반환
|
|
95
|
+
const results = SdCliBuildResultUtil.convertFromWebpackStats(stats);
|
|
96
|
+
resolve(results);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// .config.json 파일 쓰기
|
|
101
|
+
await this._writeDistConfigFileAsync();
|
|
102
|
+
|
|
103
|
+
// pm2.json 파일 쓰기
|
|
104
|
+
await this._writeDistPm2ConfigFileAsync();
|
|
105
|
+
|
|
106
|
+
// iis 파일 쓰기
|
|
107
|
+
await this._writeDistIisConfigFileAsync();
|
|
108
|
+
|
|
109
|
+
// 배포용 package.json 파일 생성
|
|
110
|
+
await this._writeDistNpmConfigFileAsync(extModuleNames);
|
|
111
|
+
|
|
112
|
+
// 마무리
|
|
113
|
+
this._logger.debug("Webpack 빌드 완료");
|
|
114
|
+
return buildResults;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private async _writeDistConfigFileAsync(): Promise<void> {
|
|
118
|
+
const configDistPath = path.resolve(this._parsedTsconfig.options.outDir!, ".config.json");
|
|
119
|
+
await FsUtil.writeFileAsync(configDistPath, JSON.stringify(this._config.configs ?? {}, undefined, 2));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private async _writeDistPm2ConfigFileAsync(): Promise<void> {
|
|
123
|
+
if (this._config.pm2 === undefined || this._config.pm2 === false) return;
|
|
124
|
+
|
|
125
|
+
const npmConfig = this._getNpmConfig(this._rootPath)!;
|
|
126
|
+
const pm2DistPath = path.resolve(this._parsedTsconfig.options.outDir!, "pm2.json");
|
|
127
|
+
await FsUtil.writeFileAsync(
|
|
128
|
+
pm2DistPath,
|
|
129
|
+
JSON.stringify(
|
|
130
|
+
ObjectUtil.merge(
|
|
131
|
+
{
|
|
132
|
+
"name": npmConfig.name.replace(/@/g, "").replace(/\//g, "-"),
|
|
133
|
+
"script": path.basename(path.resolve(this._parsedTsconfig.options.outDir!, "main.mjs")),
|
|
134
|
+
"watch": true,
|
|
135
|
+
"watch_delay": 2000,
|
|
136
|
+
"ignore_watch": [
|
|
137
|
+
"node_modules",
|
|
138
|
+
"www"
|
|
139
|
+
].distinct(),
|
|
140
|
+
"interpreter": "node@" + process.versions.node,
|
|
141
|
+
"env": {
|
|
142
|
+
NODE_ENV: "production",
|
|
143
|
+
SD_VERSION: npmConfig.version,
|
|
144
|
+
TZ: "Asia/Seoul",
|
|
145
|
+
...this._config.env ? this._config.env : {}
|
|
146
|
+
}
|
|
147
|
+
},
|
|
148
|
+
(typeof this._config.pm2 !== "boolean") ? this._config.pm2 : {},
|
|
149
|
+
{
|
|
150
|
+
arrayProcess: "concat"
|
|
151
|
+
}),
|
|
152
|
+
undefined,
|
|
153
|
+
2
|
|
154
|
+
)
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private async _writeDistIisConfigFileAsync(): Promise<void> {
|
|
159
|
+
if (this._config.iis === undefined || this._config.iis === false) return;
|
|
160
|
+
|
|
161
|
+
const iisDistPath = path.resolve(this._parsedTsconfig.options.outDir!, "web.config");
|
|
162
|
+
const serverExeFilePath = (this._config.iis !== true && "serverExeFilePath" in this._config.iis)
|
|
163
|
+
? (this._config.iis.serverExeFilePath ?? "C:\\Program Files\\nodejs\\node.exe")
|
|
164
|
+
: "C:\\Program Files\\nodejs\\node.exe";
|
|
165
|
+
await FsUtil.writeFileAsync(iisDistPath, `
|
|
166
|
+
<configuration>
|
|
167
|
+
<system.webServer>
|
|
168
|
+
<webSocket enabled="false" />
|
|
169
|
+
<handlers>
|
|
170
|
+
<add name="iisnode" path="main.js" verb="*" modules="iisnode" />
|
|
171
|
+
</handlers>
|
|
172
|
+
<iisnode nodeProcessCommandLine="${serverExeFilePath}"
|
|
173
|
+
watchedFiles="web.config;*.js"
|
|
174
|
+
loggingEnabled="true"
|
|
175
|
+
devErrorsEnabled="true" />
|
|
176
|
+
<rewrite>
|
|
177
|
+
<rules>
|
|
178
|
+
<rule name="main">
|
|
179
|
+
<action type="Rewrite" url="main.mjs" />
|
|
180
|
+
</rule>
|
|
181
|
+
</rules>
|
|
182
|
+
</rewrite>
|
|
183
|
+
<httpErrors errorMode="Detailed" />
|
|
184
|
+
</system.webServer>
|
|
185
|
+
</configuration>`.trim());
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private async _writeDistNpmConfigFileAsync(deps: string[]): Promise<void> {
|
|
189
|
+
const distNpmConfig = ObjectUtil.clone(this._getNpmConfig(this._rootPath))!;
|
|
190
|
+
distNpmConfig.dependencies = {};
|
|
191
|
+
for (const dep of deps) {
|
|
192
|
+
distNpmConfig.dependencies[dep] = "*";
|
|
193
|
+
}
|
|
194
|
+
delete distNpmConfig.optionalDependencies;
|
|
195
|
+
delete distNpmConfig.devDependencies;
|
|
196
|
+
delete distNpmConfig.peerDependencies;
|
|
197
|
+
|
|
198
|
+
if (this._config.pm2 !== undefined) {
|
|
199
|
+
distNpmConfig.scripts = { "start": "pm2 start pm2.json" };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
await FsUtil.writeFileAsync(
|
|
203
|
+
path.resolve(this._parsedTsconfig.options.outDir!, "package.json"),
|
|
204
|
+
JSON.stringify(distNpmConfig, undefined, 2)
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private _getWebpackConfig(watch: boolean, extModuleNames: string[]): webpack.Configuration {
|
|
209
|
+
const internalModuleCachePaths = watch
|
|
210
|
+
? FsUtil.findAllParentChildDirPaths("node_modules/!(@simplysm)", this._rootPath, this._workspaceRootPath)
|
|
211
|
+
: undefined;
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
mode: watch ? "development" : "production",
|
|
215
|
+
devtool: false,
|
|
216
|
+
target: ["node", "es2020"],
|
|
217
|
+
profile: false,
|
|
218
|
+
resolve: {
|
|
219
|
+
roots: [this._rootPath],
|
|
220
|
+
extensions: [".ts", ".js", ".mjs", ".cjs"],
|
|
221
|
+
symlinks: true,
|
|
222
|
+
mainFields: ["es2020", "default", "module", "main"]
|
|
223
|
+
},
|
|
224
|
+
context: this._workspaceRootPath,
|
|
225
|
+
entry: {
|
|
226
|
+
main: [
|
|
227
|
+
path.resolve(this._rootPath, "src/main.ts")
|
|
228
|
+
]
|
|
229
|
+
},
|
|
230
|
+
output: {
|
|
231
|
+
clean: true,
|
|
232
|
+
path: this._parsedTsconfig.options.outDir,
|
|
233
|
+
filename: "[name].mjs",
|
|
234
|
+
chunkFilename: "[name].mjs",
|
|
235
|
+
assetModuleFilename: "res/[name][ext][query]",
|
|
236
|
+
libraryTarget: "module"
|
|
237
|
+
},
|
|
238
|
+
experiments: {
|
|
239
|
+
outputModule: true
|
|
240
|
+
},
|
|
241
|
+
performance: { hints: false },
|
|
242
|
+
node: false,
|
|
243
|
+
stats: "errors-warnings",
|
|
244
|
+
externals: extModuleNames,
|
|
245
|
+
...watch ? {
|
|
246
|
+
cache: { type: "memory", maxGenerations: 1 },
|
|
247
|
+
snapshot: {
|
|
248
|
+
immutablePaths: internalModuleCachePaths,
|
|
249
|
+
managedPaths: internalModuleCachePaths
|
|
250
|
+
}
|
|
251
|
+
} : {
|
|
252
|
+
cache: false
|
|
253
|
+
},
|
|
254
|
+
optimization: {
|
|
255
|
+
...watch ? {} : {
|
|
256
|
+
minimize: true,
|
|
257
|
+
minimizer: [
|
|
258
|
+
new TerserPlugin({
|
|
259
|
+
extractComments: false,
|
|
260
|
+
terserOptions: {
|
|
261
|
+
compress: true,
|
|
262
|
+
ecma: 2020,
|
|
263
|
+
sourceMap: false,
|
|
264
|
+
keep_classnames: true,
|
|
265
|
+
keep_fnames: true,
|
|
266
|
+
ie8: false,
|
|
267
|
+
safari10: false,
|
|
268
|
+
module: true,
|
|
269
|
+
format: {
|
|
270
|
+
comments: false
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
})
|
|
274
|
+
]
|
|
275
|
+
},
|
|
276
|
+
moduleIds: "deterministic",
|
|
277
|
+
chunkIds: watch ? "named" : "deterministic",
|
|
278
|
+
emitOnErrors: watch
|
|
279
|
+
},
|
|
280
|
+
module: {
|
|
281
|
+
strictExportPresence: true,
|
|
282
|
+
rules: [
|
|
283
|
+
{
|
|
284
|
+
test: /\.m?js/,
|
|
285
|
+
resolve: {
|
|
286
|
+
fullySpecified: false
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
...watch ? [
|
|
290
|
+
{
|
|
291
|
+
test: /\.m?js$/,
|
|
292
|
+
enforce: "pre" as const,
|
|
293
|
+
loader: "source-map-loader",
|
|
294
|
+
options: {
|
|
295
|
+
filterSourceMappingUrl: (mapUri: string, resourcePath: string) => {
|
|
296
|
+
return !resourcePath.includes("node_modules") || (/@simplysm[\\/]sd/).test(resourcePath);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
] : [],
|
|
301
|
+
{
|
|
302
|
+
test: /\.ts$/,
|
|
303
|
+
exclude: /node_modules/,
|
|
304
|
+
loader: "ts-loader",
|
|
305
|
+
options: {
|
|
306
|
+
configFile: this._tsconfigFilePath,
|
|
307
|
+
errorFormatter: (msg: ErrorInfo) => {
|
|
308
|
+
return SdCliBuildResultUtil.getMessage({
|
|
309
|
+
filePath: msg.file,
|
|
310
|
+
line: msg.line,
|
|
311
|
+
char: msg.character,
|
|
312
|
+
code: "TS" + msg.code.toString(),
|
|
313
|
+
severity: msg.severity,
|
|
314
|
+
message: msg.content
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico|otf|xlsx?|pptx?|docx?|zip|pfx|pkl)$/,
|
|
321
|
+
type: "asset/resource"
|
|
322
|
+
}
|
|
323
|
+
]
|
|
324
|
+
},
|
|
325
|
+
plugins: [
|
|
326
|
+
...watch ? [] : [
|
|
327
|
+
new LicenseWebpackPlugin({
|
|
328
|
+
stats: { warnings: false, errors: false },
|
|
329
|
+
perChunkOutput: false,
|
|
330
|
+
outputFilename: "3rd_party_licenses.txt",
|
|
331
|
+
skipChildCompilers: true
|
|
332
|
+
}) as any
|
|
333
|
+
],
|
|
334
|
+
new CopyWebpackPlugin({
|
|
335
|
+
patterns: ["assets/"].map((item) => ({
|
|
336
|
+
context: this._rootPath,
|
|
337
|
+
to: item,
|
|
338
|
+
from: `src/${item}`,
|
|
339
|
+
noErrorOnMissing: true,
|
|
340
|
+
force: true,
|
|
341
|
+
globOptions: {
|
|
342
|
+
dot: true,
|
|
343
|
+
followSymbolicLinks: false,
|
|
344
|
+
ignore: [
|
|
345
|
+
".gitkeep",
|
|
346
|
+
"**/.DS_Store",
|
|
347
|
+
"**/Thumbs.db"
|
|
348
|
+
].map((i) => PathUtil.posix(this._rootPath, i))
|
|
349
|
+
},
|
|
350
|
+
priority: 0
|
|
351
|
+
}))
|
|
352
|
+
}),
|
|
353
|
+
new webpack.EnvironmentPlugin({
|
|
354
|
+
SD_VERSION: this._getNpmConfig(this._rootPath)!.version,
|
|
355
|
+
...this._config.env
|
|
356
|
+
}),
|
|
357
|
+
new ESLintWebpackPlugin({
|
|
358
|
+
context: this._rootPath,
|
|
359
|
+
eslintPath: path.resolve(this._workspaceRootPath, "node_modules", "eslint"),
|
|
360
|
+
exclude: ["node_modules"],
|
|
361
|
+
extensions: ["ts", "js", "mjs", "cjs"],
|
|
362
|
+
fix: false,
|
|
363
|
+
threads: false,
|
|
364
|
+
formatter: (results: LintResult[]) => {
|
|
365
|
+
const resultMessages: string[] = [];
|
|
366
|
+
for (const result of results) {
|
|
367
|
+
for (const msg of result.messages) {
|
|
368
|
+
const severity = msg.severity === 1 ? "warning" : msg.severity === 2 ? "error" : undefined;
|
|
369
|
+
if (severity === undefined) continue;
|
|
370
|
+
|
|
371
|
+
resultMessages.push(SdCliBuildResultUtil.getMessage({
|
|
372
|
+
filePath: result.filePath,
|
|
373
|
+
line: msg.line,
|
|
374
|
+
char: msg.column,
|
|
375
|
+
code: msg.ruleId?.toString(),
|
|
376
|
+
severity,
|
|
377
|
+
message: msg.message
|
|
378
|
+
}));
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return resultMessages.join(os.EOL);
|
|
382
|
+
}
|
|
383
|
+
})/*,
|
|
384
|
+
new webpack.ProgressPlugin({
|
|
385
|
+
handler: (per: number, msg: string, ...args: string[]) => {
|
|
386
|
+
const phaseText = msg ? ` - phase: ${msg}` : "";
|
|
387
|
+
const argsText = args.length > 0 ? ` - args: [${args.join(", ")}]` : "";
|
|
388
|
+
this._logger.debug(`Webpack 빌드 수행중...(${Math.round(per * 100)}%)${phaseText}${argsText}`);
|
|
389
|
+
}
|
|
390
|
+
})*/
|
|
391
|
+
]
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
private _getExternalModuleNames(): string[] {
|
|
396
|
+
const loadedModuleNames: string[] = [];
|
|
397
|
+
const resultSet = new Set<string>();
|
|
398
|
+
|
|
399
|
+
const fn = (currPath: string): void => {
|
|
400
|
+
const npmConfig = this._getNpmConfig(currPath);
|
|
401
|
+
if (!npmConfig) return;
|
|
402
|
+
|
|
403
|
+
const moduleNames = [
|
|
404
|
+
...Object.keys(npmConfig.dependencies ?? {}),
|
|
405
|
+
...Object.keys(npmConfig.peerDependencies ?? {}).filter((item) => !npmConfig.peerDependenciesMeta?.[item].optional)
|
|
406
|
+
];
|
|
407
|
+
const optModuleNames = [
|
|
408
|
+
...Object.keys(npmConfig.optionalDependencies ?? {}),
|
|
409
|
+
...Object.keys(npmConfig.peerDependencies ?? {}).filter((item) => npmConfig.peerDependenciesMeta?.[item].optional)
|
|
410
|
+
].distinct();
|
|
411
|
+
|
|
412
|
+
for (const moduleName of moduleNames) {
|
|
413
|
+
if (loadedModuleNames.includes(moduleName)) continue;
|
|
414
|
+
loadedModuleNames.push(moduleName);
|
|
415
|
+
|
|
416
|
+
const modulePath = FsUtil.findAllParentChildDirPaths("node_modules/" + moduleName, currPath, this._workspaceRootPath).first();
|
|
417
|
+
if (StringUtil.isNullOrEmpty(modulePath)) {
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
if (FsUtil.exists(path.resolve(modulePath, "binding.gyp"))) {
|
|
422
|
+
resultSet.add(moduleName);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
fn(modulePath);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
for (const optModuleName of optModuleNames) {
|
|
429
|
+
if (loadedModuleNames.includes(optModuleName)) continue;
|
|
430
|
+
loadedModuleNames.push(optModuleName);
|
|
431
|
+
|
|
432
|
+
const optModulePath = FsUtil.findAllParentChildDirPaths("node_modules/" + optModuleName, currPath, this._workspaceRootPath).first();
|
|
433
|
+
if (StringUtil.isNullOrEmpty(optModulePath)) {
|
|
434
|
+
resultSet.add(optModuleName);
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (FsUtil.exists(path.resolve(optModulePath, "binding.gyp"))) {
|
|
439
|
+
resultSet.add(optModuleName);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
fn(optModulePath);
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
fn(this._rootPath);
|
|
447
|
+
|
|
448
|
+
return Array.from(resultSet.values());
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
private _getNpmConfig(pkgPath: string): INpmConfig | undefined {
|
|
452
|
+
if (!this._npmConfigMap.has(pkgPath)) {
|
|
453
|
+
this._npmConfigMap.set(pkgPath, FsUtil.readJson(path.resolve(pkgPath, "package.json")));
|
|
454
|
+
}
|
|
455
|
+
return this._npmConfigMap.get(pkgPath);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ISdCliPackageBuildResult } from "../commons";
|
|
1
|
+
import { INpmConfig, ISdCliPackageBuildResult } from "../commons";
|
|
2
2
|
import { EventEmitter } from "events";
|
|
3
3
|
import ts from "typescript";
|
|
4
4
|
import { FsUtil, Logger, PathUtil, SdFsWatcher } from "@simplysm/sd-core-node";
|
|
@@ -11,6 +11,7 @@ import { SdCliPackageLinter } from "../build-tool/SdCliPackageLinter";
|
|
|
11
11
|
import { SdCliCacheCompilerHost } from "../build-tool/SdCliCacheCompilerHost";
|
|
12
12
|
import { SdCliNgCacheCompilerHost } from "../build-tool/SdCliNgCacheCompilerHost";
|
|
13
13
|
import { NgCompiler } from "@angular/compiler-cli/src/ngtsc/core";
|
|
14
|
+
import { SdCliNpmConfigUtil } from "../utils/SdCliNpmConfigUtil";
|
|
14
15
|
|
|
15
16
|
export class SdCliTsLibBuilder extends EventEmitter {
|
|
16
17
|
private readonly _logger = Logger.get(["simplysm", "sd-cli", this.constructor.name]);
|
|
@@ -26,10 +27,26 @@ export class SdCliTsLibBuilder extends EventEmitter {
|
|
|
26
27
|
private _ngProgram?: NgtscProgram;
|
|
27
28
|
private _builder?: ts.EmitAndSemanticDiagnosticsBuilderProgram;
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
private readonly _tsconfigFilePath: string;
|
|
31
|
+
private readonly _parsedTsconfig: ts.ParsedCommandLine;
|
|
32
|
+
private readonly _npmConfig: INpmConfig;
|
|
33
|
+
|
|
34
|
+
private readonly _isAngular: boolean;
|
|
35
|
+
|
|
36
|
+
public constructor(private readonly _rootPath) {
|
|
31
37
|
super();
|
|
32
38
|
this._linter = new SdCliPackageLinter(this._rootPath);
|
|
39
|
+
|
|
40
|
+
// tsconfig
|
|
41
|
+
this._tsconfigFilePath = path.resolve(this._rootPath, "tsconfig-build.json");
|
|
42
|
+
const tsconfig = FsUtil.readJson(this._tsconfigFilePath);
|
|
43
|
+
this._parsedTsconfig = ts.parseJsonConfigFileContent(tsconfig, ts.sys, this._rootPath);
|
|
44
|
+
|
|
45
|
+
// package.json
|
|
46
|
+
this._npmConfig = FsUtil.readJson(path.resolve(this._rootPath, "package.json"));
|
|
47
|
+
|
|
48
|
+
// else
|
|
49
|
+
this._isAngular = SdCliNpmConfigUtil.getDependencies(this._npmConfig).defaults.includes("@angular/core");
|
|
33
50
|
}
|
|
34
51
|
|
|
35
52
|
public override on(event: "change", listener: () => void): this;
|
|
@@ -41,14 +58,11 @@ export class SdCliTsLibBuilder extends EventEmitter {
|
|
|
41
58
|
public async watchAsync(): Promise<void> {
|
|
42
59
|
this.emit("change");
|
|
43
60
|
|
|
44
|
-
// TSCONFIG 읽기
|
|
45
|
-
const parsedTsconfig = await this._getParsedTsconfigAsync();
|
|
46
|
-
|
|
47
61
|
// DIST 비우기
|
|
48
|
-
await FsUtil.removeAsync(
|
|
62
|
+
await FsUtil.removeAsync(this._parsedTsconfig.options.outDir!);
|
|
49
63
|
|
|
50
64
|
// 프로그램 리로드
|
|
51
|
-
const buildPack = this._createSdBuildPack(
|
|
65
|
+
const buildPack = this._createSdBuildPack(this._parsedTsconfig);
|
|
52
66
|
|
|
53
67
|
const relatedPaths = await this.getAllRelatedPathsAsync();
|
|
54
68
|
const watcher = SdFsWatcher.watch(relatedPaths);
|
|
@@ -65,7 +79,7 @@ export class SdCliTsLibBuilder extends EventEmitter {
|
|
|
65
79
|
}
|
|
66
80
|
|
|
67
81
|
// 빌드
|
|
68
|
-
const watchBuildPack = this._createSdBuildPack(
|
|
82
|
+
const watchBuildPack = this._createSdBuildPack(this._parsedTsconfig);
|
|
69
83
|
|
|
70
84
|
// 린트
|
|
71
85
|
const watchBuildResults = await this._runBuilderAsync(watchBuildPack.builder, watchBuildPack.ngCompiler);
|
|
@@ -90,14 +104,11 @@ export class SdCliTsLibBuilder extends EventEmitter {
|
|
|
90
104
|
}
|
|
91
105
|
|
|
92
106
|
public async buildAsync(): Promise<ISdCliPackageBuildResult[]> {
|
|
93
|
-
// TSCONFIG 읽기
|
|
94
|
-
const parsedTsconfig = await this._getParsedTsconfigAsync();
|
|
95
|
-
|
|
96
107
|
// DIST 비우기
|
|
97
|
-
await FsUtil.removeAsync(
|
|
108
|
+
await FsUtil.removeAsync(this._parsedTsconfig.options.outDir!);
|
|
98
109
|
|
|
99
110
|
// 프로그램 리로드
|
|
100
|
-
const buildPack = this._createSdBuildPack(
|
|
111
|
+
const buildPack = this._createSdBuildPack(this._parsedTsconfig);
|
|
101
112
|
|
|
102
113
|
// 빌드
|
|
103
114
|
const buildResults = await this._runBuilderAsync(buildPack.builder, buildPack.ngCompiler);
|
|
@@ -165,7 +176,7 @@ export class SdCliTsLibBuilder extends EventEmitter {
|
|
|
165
176
|
results.push(
|
|
166
177
|
...diagnostics
|
|
167
178
|
.filter((item) => [ts.DiagnosticCategory.Error, ts.DiagnosticCategory.Warning].includes(item.category))
|
|
168
|
-
.map((item) => SdCliBuildResultUtil.
|
|
179
|
+
.map((item) => SdCliBuildResultUtil.convertFromTsDiag(item))
|
|
169
180
|
.filterExists()
|
|
170
181
|
);
|
|
171
182
|
|
|
@@ -312,12 +323,6 @@ export class SdCliTsLibBuilder extends EventEmitter {
|
|
|
312
323
|
return compilerHost;
|
|
313
324
|
}
|
|
314
325
|
}
|
|
315
|
-
|
|
316
|
-
private async _getParsedTsconfigAsync(): Promise<ts.ParsedCommandLine> {
|
|
317
|
-
const tsconfigFilePath = path.resolve(this._rootPath, "tsconfig-build.json");
|
|
318
|
-
const tsconfig = await FsUtil.readJsonAsync(tsconfigFilePath);
|
|
319
|
-
return ts.parseJsonConfigFileContent(tsconfig, ts.sys, this._rootPath);
|
|
320
|
-
}
|
|
321
326
|
}
|
|
322
327
|
|
|
323
328
|
interface IFileCache {
|
package/src/commons.ts
CHANGED
|
@@ -15,6 +15,7 @@ export interface INpmConfig {
|
|
|
15
15
|
optionalDependencies?: Record<string, string>;
|
|
16
16
|
devDependencies?: Record<string, string>;
|
|
17
17
|
peerDependencies?: Record<string, string>;
|
|
18
|
+
peerDependenciesMeta?: Record<string, { optional?: boolean }>;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
export interface ITsconfig {
|
|
@@ -39,17 +40,17 @@ export interface ISdCliConfig {
|
|
|
39
40
|
localUpdates?: Record<string, string>;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
export type TSdCliPackageConfig = ISdCliLibPackageConfig |
|
|
43
|
+
export type TSdCliPackageConfig = ISdCliLibPackageConfig | ISdCliServerPackageConfig;
|
|
43
44
|
|
|
44
45
|
export interface ISdCliLibPackageConfig {
|
|
45
46
|
type: "library";
|
|
46
47
|
publish?: "npm";
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
export interface ISdCliNgLibPackageConfig {
|
|
50
|
-
type: "angular";
|
|
51
|
-
}
|
|
52
|
-
|
|
53
50
|
export interface ISdCliServerPackageConfig {
|
|
54
51
|
type: "server";
|
|
52
|
+
env?: Record<string, string>;
|
|
53
|
+
configs?: Record<string, any>;
|
|
54
|
+
pm2?: Record<string, any> | boolean;
|
|
55
|
+
iis?: { serverExeFilePath?: string } | boolean;
|
|
55
56
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Logger, SdProcess } from "@simplysm/sd-core-node";
|
|
2
|
+
import { SdCliPrepare } from "./SdCliPrepare";
|
|
3
|
+
|
|
4
|
+
export class SdCliNpm {
|
|
5
|
+
private readonly _logger = Logger.get(["simplysm", "sd-cli", this.constructor.name]);
|
|
6
|
+
|
|
7
|
+
public constructor(private readonly _rootPath: string) {
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public async updateAsync(): Promise<void> {
|
|
11
|
+
try {
|
|
12
|
+
this._logger.debug("업데이트할 패키지 확인...");
|
|
13
|
+
await SdProcess.spawnAsync("npm outdated", { cwd: this._rootPath });
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
this._logger.debug("업데이트 시작...");
|
|
19
|
+
await SdProcess.spawnAsync("npm update", { cwd: this._rootPath });
|
|
20
|
+
|
|
21
|
+
this._logger.debug("sd-cli 준비...");
|
|
22
|
+
await new SdCliPrepare().prepareAsync();
|
|
23
|
+
|
|
24
|
+
this._logger.info("노드 패키지 업데이트 완료");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { FsUtil, Logger } from "@simplysm/sd-core-node";
|
|
2
|
+
|
|
3
|
+
export class SdCliPrepare {
|
|
4
|
+
private readonly _logger = Logger.get(["simplysm", "sd-cli", this.constructor.name]);
|
|
5
|
+
|
|
6
|
+
public async prepareAsync(): Promise<void> {
|
|
7
|
+
// 타입체크 속도 지연 메시지 추가
|
|
8
|
+
const r1 = await this._modifyTypescriptCodeForTypeCheckPerformanceWarning();
|
|
9
|
+
this._logger.log(`[모듈수정] 타입체크 속도 지연 메시지 추가 (${r1 ? "O" : "X"})`);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
private async _modifyTypescriptCodeForTypeCheckPerformanceWarning(): Promise<boolean> {
|
|
13
|
+
const fileUrl = await import.meta.resolve!("typescript");
|
|
14
|
+
const filePath = fileUrl.replace(/^file:\/\/\//, "");
|
|
15
|
+
const fileContent = await FsUtil.readFileAsync(filePath);
|
|
16
|
+
const modifiedFileContent = fileContent.replace(`
|
|
17
|
+
function checkSourceElement(node) {
|
|
18
|
+
if (node) {
|
|
19
|
+
var saveCurrentNode = currentNode;
|
|
20
|
+
currentNode = node;
|
|
21
|
+
instantiationCount = 0;
|
|
22
|
+
checkSourceElementWorker(node);
|
|
23
|
+
currentNode = saveCurrentNode;
|
|
24
|
+
}
|
|
25
|
+
}`, `
|
|
26
|
+
function checkSourceElement(node) {
|
|
27
|
+
if (node) {
|
|
28
|
+
const prevUsage = process.cpuUsage();
|
|
29
|
+
|
|
30
|
+
var saveCurrentNode = currentNode;
|
|
31
|
+
currentNode = node;
|
|
32
|
+
instantiationCount = 0;
|
|
33
|
+
checkSourceElementWorker(node);
|
|
34
|
+
currentNode = saveCurrentNode;
|
|
35
|
+
|
|
36
|
+
const usage = process.cpuUsage(prevUsage);
|
|
37
|
+
if (node.hasChildPerformanceWarning) {
|
|
38
|
+
node.parent.hasChildPerformanceWarning = true;
|
|
39
|
+
}
|
|
40
|
+
else if (usage.user + usage.system > 2000 * 1000 && node.kind !== 253) {
|
|
41
|
+
error(node, {
|
|
42
|
+
code: 9000,
|
|
43
|
+
category: ts.DiagnosticCategory.Warning,
|
|
44
|
+
key: "simplysm_check_source_element_performance_slow",
|
|
45
|
+
message: "소스코드 타입분석에 너무 오랜 시간이 소요됩니다. [" + Math.round((usage.user + usage.system) / 1000) + "ms/cpu, KIND: " + node.kind + "]"
|
|
46
|
+
});
|
|
47
|
+
node.parent.hasChildPerformanceWarning = true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}`);
|
|
51
|
+
await FsUtil.writeFileAsync(filePath, modifiedFileContent);
|
|
52
|
+
return fileContent !== modifiedFileContent;
|
|
53
|
+
}
|
|
54
|
+
}
|