@simplysm/sd-cli 7.0.210 → 7.0.220

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.
@@ -16,10 +16,6 @@ import MiniCssExtractPlugin from "mini-css-extract-plugin";
16
16
  import { AngularWebpackPlugin } from "@ngtools/webpack";
17
17
  import { IndexHtmlWebpackPlugin } from "@angular-devkit/build-angular/src/webpack/plugins/index-html-webpack-plugin";
18
18
  import { SassWorkerImplementation } from "@angular-devkit/build-angular/src/sass/sass-service";
19
- import { HmrLoader } from "@angular-devkit/build-angular/src/webpack/plugins/hmr/hmr-loader";
20
- import wdm from "webpack-dev-middleware";
21
- import whm from "webpack-hot-middleware";
22
- import { NextHandleFunction } from "connect";
23
19
  import { LicenseWebpackPlugin } from "license-webpack-plugin";
24
20
  import NodePolyfillPlugin from "node-polyfill-webpack-plugin";
25
21
  import { createHash } from "crypto";
@@ -94,7 +90,7 @@ export class SdCliClientBuilder extends EventEmitter {
94
90
  return super.on(event, listener);
95
91
  }
96
92
 
97
- public async watchAsync(): Promise<NextHandleFunction[]> {
93
+ public async watchAsync(): Promise<void> {
98
94
  // DIST 비우기
99
95
  await FsUtil.removeAsync(path.resolve(this._rootPath, ".electron"));
100
96
  await FsUtil.removeAsync(this._parsedTsconfig.options.outDir!);
@@ -102,7 +98,7 @@ export class SdCliClientBuilder extends EventEmitter {
102
98
  // NgModule 생성
103
99
  await this._ngModuleGenerator.runAsync();
104
100
 
105
- // CORDOVA
101
+ // CORDOVA 초기화
106
102
  if (this._cordova) {
107
103
  this._logger.debug("CORDOVA 구성...");
108
104
  await this._cordova.initializeAsync();
@@ -112,7 +108,7 @@ export class SdCliClientBuilder extends EventEmitter {
112
108
  const webpackConfigs = (Object.keys(this._config.builder ?? { web: {} }) as ("web" | "cordova" | "electron")[])
113
109
  .map((builderType) => this._getWebpackConfig(true, builderType));
114
110
  const multiCompiler = webpack(webpackConfigs);
115
- return await new Promise<NextHandleFunction[]>((resolve, reject) => {
111
+ await new Promise<void>((resolve, reject) => {
116
112
  multiCompiler.hooks.invalid.tap(this.constructor.name, (fileName) => {
117
113
  if (fileName != null) {
118
114
  this._logger.debug("파일변경 감지", fileName);
@@ -132,22 +128,20 @@ export class SdCliClientBuilder extends EventEmitter {
132
128
  this._logger.debug("Webpack 빌드 수행...");
133
129
  });
134
130
 
135
- for (const compiler of multiCompiler.compilers) {
136
- compiler.hooks.failed.tap(this.constructor.name, (err) => {
131
+ multiCompiler.watch({}, async (err, multiStats) => {
132
+ if (err != null || multiStats == null) {
137
133
  this.emit("complete", [{
138
134
  filePath: undefined,
139
135
  line: undefined,
140
136
  char: undefined,
141
137
  code: undefined,
142
138
  severity: "error",
143
- message: err.stack
139
+ message: err?.stack ?? "알 수 없는 오류 (multiStats=null)"
144
140
  }]);
145
141
  reject(err);
146
142
  return;
147
- });
148
- }
143
+ }
149
144
 
150
- multiCompiler.hooks.done.tap(this.constructor.name, async (multiStats) => {
151
145
  // 결과 반환
152
146
  const results = multiStats.stats.mapMany((stats) => SdCliBuildResultUtil.convertFromWebpackStats(stats));
153
147
 
@@ -162,22 +156,10 @@ export class SdCliClientBuilder extends EventEmitter {
162
156
 
163
157
  // 마무리
164
158
  this._logger.debug("Webpack 빌드 완료");
165
- resolve(middlewares);
159
+ resolve();
166
160
 
167
161
  this.emit("complete", results);
168
162
  });
169
-
170
- const middlewares = multiCompiler.compilers.mapMany((compiler) => [
171
- wdm(compiler, {
172
- publicPath: compiler.options.output.publicPath,
173
- index: "index.html",
174
- stats: false
175
- }),
176
- whm(compiler, {
177
- path: `${compiler.options.output.publicPath}__webpack_hmr`,
178
- log: false
179
- })
180
- ]);
181
163
  });
182
164
  }
183
165
 
@@ -189,6 +171,12 @@ export class SdCliClientBuilder extends EventEmitter {
189
171
  // NgModule 생성
190
172
  await this._ngModuleGenerator.runAsync();
191
173
 
174
+ // CORDOVA 초기화
175
+ if (this._cordova) {
176
+ this._logger.debug("CORDOVA 구성...");
177
+ await this._cordova.initializeAsync();
178
+ }
179
+
192
180
  // 빌드
193
181
  this._logger.debug("Webpack 빌드 수행...");
194
182
  const builderTypes = (Object.keys(this._config.builder ?? { web: {} }) as ("web" | "cordova" | "electron")[]);
@@ -222,11 +210,8 @@ export class SdCliClientBuilder extends EventEmitter {
222
210
  );
223
211
  }
224
212
 
225
- // CORDOVA
213
+ // CORDOVA 빌드
226
214
  if (this._cordova) {
227
- this._logger.debug("CORDOVA 구성...");
228
- await this._cordova.initializeAsync();
229
-
230
215
  this._logger.debug("CORDOVA 빌드...");
231
216
  await this._cordova.buildAsync(path.resolve(this._parsedTsconfig.options.outDir!, "cordova"));
232
217
  }
@@ -245,6 +230,8 @@ export class SdCliClientBuilder extends EventEmitter {
245
230
  throw new Error("ELECTRON 빌드 패키지의 'dependencies'에는 'dotenv'가 반드시 포함되어야 합니다.");
246
231
  }
247
232
 
233
+ const remoteVersion = npmConfig.dependencies?.["@electron/remote"];
234
+
248
235
  const electronSrcPath = path.resolve(this._rootPath, `.electron/src`);
249
236
  const electronDistPath = path.resolve(this._rootPath, `.electron/dist`);
250
237
 
@@ -259,7 +246,10 @@ export class SdCliClientBuilder extends EventEmitter {
259
246
  "electron": electronVersion.replace("^", "")
260
247
  },
261
248
  dependencies: {
262
- "dotenv": dotenvVersion
249
+ "dotenv": dotenvVersion,
250
+ ...remoteVersion !== undefined ? {
251
+ "@electron/remote": remoteVersion
252
+ } : {}
263
253
  }
264
254
  });
265
255
 
@@ -325,8 +315,6 @@ export class SdCliClientBuilder extends EventEmitter {
325
315
  const internalModuleCachePaths = watch ? this._getInternalModuleCachePaths(workspaceName) : undefined;
326
316
 
327
317
  const npmConfig = this._getNpmConfig(this._rootPath)!;
328
- // const pkgVersion = npmConfig.version;
329
- // const ngVersion = this._getNpmConfig(FsUtil.findAllParentChildDirPaths("node_modules/@angular/core", this._rootPath, this._workspaceRootPath)[0])!.version;
330
318
 
331
319
  const workspacePkgLockContent = FsUtil.readFile(path.resolve(this._workspaceRootPath, "package-lock.json"));
332
320
 
@@ -334,7 +322,6 @@ export class SdCliClientBuilder extends EventEmitter {
334
322
  const publicPath = builderType === "web" ? `/${pkgKey}/` : watch ? `/${pkgKey}/${builderType}/` : ``;
335
323
 
336
324
  const cacheBasePath = path.resolve(this._rootPath, ".cache");
337
- // const cachePath = path.resolve(cacheBasePath, pkgVersion);
338
325
 
339
326
  const distPath = (builderType === "cordova" && !watch) ? path.resolve(this._cordova!.cordovaPath, "www")
340
327
  : (builderType === "electron" && !watch) ? path.resolve(this._rootPath, ".electron/src")
@@ -355,7 +342,7 @@ export class SdCliClientBuilder extends EventEmitter {
355
342
  profile: false,
356
343
  resolve: {
357
344
  roots: [this._rootPath],
358
- extensions: [".ts", ".tsx", ".mjs", ".cjs", ".js"],
345
+ extensions: [".ts", ".tsx", ".mjs", ".cjs", ".js", ".jsx"],
359
346
  symlinks: true,
360
347
  modules: [this._workspaceRootPath, "node_modules"],
361
348
  mainFields: ["es2015", "browser", "module", "main"],
@@ -366,14 +353,9 @@ export class SdCliClientBuilder extends EventEmitter {
366
353
  },
367
354
  context: this._workspaceRootPath,
368
355
  entry: {
369
- main: [
370
- ...watch ? [
371
- `webpack-hot-middleware/client?path=${publicPath}__webpack_hmr&timeout=20000&reload=true&overlay=true`
372
- ] : [],
373
- mainFilePath
374
- ],
375
- ...FsUtil.exists(polyfillsFilePath) ? { polyfills: polyfillsFilePath } : {},
376
- ...FsUtil.exists(stylesFilePath) ? { styles: stylesFilePath } : {}
356
+ main: [mainFilePath],
357
+ ...FsUtil.exists(polyfillsFilePath) ? { polyfills: [polyfillsFilePath] } : {},
358
+ ...FsUtil.exists(stylesFilePath) ? { styles: [stylesFilePath] } : {}
377
359
  },
378
360
  output: {
379
361
  uniqueName: pkgKey,
@@ -389,7 +371,10 @@ export class SdCliClientBuilder extends EventEmitter {
389
371
  scriptType: "module"
390
372
  },
391
373
  watch: false,
392
- watchOptions: { poll: undefined, ignored: undefined },
374
+ watchOptions: {
375
+ poll: undefined,
376
+ ignored: undefined
377
+ },
393
378
  performance: { hints: false },
394
379
  ignoreWarnings: [
395
380
  /Failed to parse source map from/,
@@ -411,7 +396,6 @@ export class SdCliClientBuilder extends EventEmitter {
411
396
  .update(watch.toString())
412
397
  .digest("hex")
413
398
  },
414
- // cache: { type: "memory", maxGenerations: 1 },
415
399
  ...watch ? {
416
400
  snapshot: {
417
401
  immutablePaths: internalModuleCachePaths,
@@ -484,27 +468,6 @@ export class SdCliClientBuilder extends EventEmitter {
484
468
  test: /[/\\]rxjs[/\\]add[/\\].+\.js$/,
485
469
  sideEffects: true
486
470
  },
487
- ...watch ? [
488
- {
489
- loader: HmrLoader,
490
- include: [mainFilePath]
491
- }
492
- ] : [],
493
- ...watch ? [
494
- {
495
- test: /\.[cm]?jsx?$/,
496
- enforce: "pre" as const,
497
- loader: "source-map-loader",
498
- options: {
499
- filterSourceMappingUrl: (mapUri: string, resourcePath: string) => {
500
- const workspaceRegex = new RegExp(`node_modules[\\\\/]@${workspaceName}[\\\\/]`);
501
- return !resourcePath.includes("node_modules")
502
- || (/node_modules[\\/]@simplysm[\\/]/).test(resourcePath)
503
- || workspaceRegex.test(resourcePath);
504
- }
505
- }
506
- }
507
- ] : [],
508
471
  {
509
472
  test: /\.[cm]?[tj]sx?$/,
510
473
  resolve: { fullySpecified: false },
@@ -522,10 +485,25 @@ export class SdCliClientBuilder extends EventEmitter {
522
485
  }
523
486
  ]
524
487
  },
488
+ ...watch ? [
489
+ {
490
+ test: /\.[cm]?jsx?$/,
491
+ enforce: "pre" as const,
492
+ loader: "source-map-loader",
493
+ options: {
494
+ filterSourceMappingUrl: (mapUri: string, resourcePath: string) => {
495
+ const workspaceRegex = new RegExp(`node_modules[\\\\/]@${workspaceName}[\\\\/]`);
496
+ return !resourcePath.includes("node_modules")
497
+ || (/node_modules[\\/]@simplysm[\\/]/).test(resourcePath)
498
+ || workspaceRegex.test(resourcePath);
499
+ }
500
+ }
501
+ }
502
+ ] : [],
525
503
  {
526
504
  test: /\.[cm]?tsx?$/,
527
505
  loader: "@ngtools/webpack",
528
- exclude: [/[/\\](?:css-loader|mini-css-extract-plugin|webpack-dev-server|webpack)[/\\]/]
506
+ exclude: [/[/\\](?:css-loader|mini-css-extract-plugin|webpack)[/\\]/]
529
507
  },
530
508
  {
531
509
  test: /\.css$/i,
@@ -678,9 +656,6 @@ export class SdCliClientBuilder extends EventEmitter {
678
656
  emitNgModuleScope: watch,
679
657
  inlineStyleFileExtension: "scss"
680
658
  }),
681
- // new AnyComponentStyleBudgetChecker(watch ? [] : [
682
- // { type: Type.AnyComponentStyle, maximumWarning: "2kb", maximumError: "4kb" }
683
- // ]),
684
659
  {
685
660
  apply: (compiler: webpack.Compiler) => {
686
661
  compiler.hooks.shutdown.tap("sass-worker", () => {
@@ -690,13 +665,12 @@ export class SdCliClientBuilder extends EventEmitter {
690
665
  },
691
666
  new MiniCssExtractPlugin({ filename: "[name].css" }),
692
667
  new SuppressExtractedTextChunksWebpackPlugin(),
693
- // NgBuildAnalyticsPlugin,
694
668
  new IndexHtmlWebpackPlugin({
695
669
  indexPath: path.resolve(this._rootPath, "src/index.html"),
696
670
  outputPath: "index.html",
697
671
  baseHref: publicPath,
698
672
  entrypoints: [
699
- ["runtime", true],
673
+ ["runtime", !watch],
700
674
  ["polyfills", true],
701
675
  ["styles", false],
702
676
  ["vendor", true],
@@ -718,9 +692,6 @@ export class SdCliClientBuilder extends EventEmitter {
718
692
  crossOrigin: "none",
719
693
  lang: undefined
720
694
  }),
721
- ...watch ? [
722
- new webpack.HotModuleReplacementPlugin()
723
- ] : [],
724
695
  new webpack.EnvironmentPlugin({
725
696
  SD_VERSION: this._getNpmConfig(this._rootPath)!.version,
726
697
  ...this._config.env
@@ -58,6 +58,14 @@ export class SdCliServerBuilder extends EventEmitter {
58
58
 
59
59
  compiler.watch({}, async (err, stats) => {
60
60
  if (err != null || stats == null) {
61
+ this.emit("complete", [{
62
+ filePath: undefined,
63
+ line: undefined,
64
+ char: undefined,
65
+ code: undefined,
66
+ severity: "error",
67
+ message: err?.stack ?? "알 수 없는 오류 (stats=null)"
68
+ }]);
61
69
  reject(err);
62
70
  return;
63
71
  }
@@ -8,8 +8,9 @@ import { SdCliBuildResultUtil } from "../utils/SdCliBuildResultUtil";
8
8
  import semver from "semver/preload";
9
9
  import { SdCliConfigUtil } from "../utils/SdCliConfigUtil";
10
10
  import { SdServiceServer } from "@simplysm/sd-service-server";
11
- import { SdCliNpm } from "./SdCliNpm";
12
11
  import { SdCliLocalUpdate } from "./SdCliLocalUpdate";
12
+ import url from "url";
13
+ import mime from "mime";
13
14
  import { NextHandleFunction } from "connect";
14
15
 
15
16
  export class SdCliWorkspace {
@@ -37,6 +38,7 @@ export class SdCliWorkspace {
37
38
 
38
39
  this._logger.debug("패키지 이벤트 설정...");
39
40
  let changeCount = 0;
41
+ let changePkgs: SdCliPackage[] = [];
40
42
  const totalResultMap = new Map<string, ISdCliPackageBuildResult[]>();
41
43
  for (const pkg of pkgs) {
42
44
  pkg
@@ -47,17 +49,35 @@ export class SdCliWorkspace {
47
49
  changeCount++;
48
50
  this._logger.debug(`[${pkg.name}] 빌드를 시작합니다...`);
49
51
  })
50
- .on("complete", async (results) => {
51
- if (pkg.config.type === "server" && !results.some((item) => item.severity === "error")) {
52
- await this._restartServerAsync(pkg);
53
- }
52
+ .on("complete", (results) => {
53
+ changePkgs.push(pkg);
54
54
 
55
55
  this._logger.debug(`[${pkg.name}] 빌드가 완료되었습니다.`);
56
56
  totalResultMap.set(pkg.name, results);
57
57
 
58
- setTimeout(() => {
58
+ setTimeout(async () => {
59
59
  changeCount--;
60
60
  if (changeCount === 0) {
61
+ const currChangePkgs = [...changePkgs];
62
+ changePkgs = [];
63
+
64
+ for (const changePkg of currChangePkgs) {
65
+ if (changePkg.config.type === "server" && !results.some((item) => item.severity === "error")) {
66
+ await this._restartServerAsync(changePkg);
67
+ }
68
+
69
+ if (changePkg.config.type === "client") {
70
+ if (typeof changePkg.config.server === "string") {
71
+ const serverInfo = this._serverInfoMap.get(changePkg.config.server);
72
+ serverInfo?.server?.broadcastReload();
73
+ }
74
+ else {
75
+ const serverInfo = this._serverInfoMap.get("PORT:" + changePkg.config.server.port);
76
+ serverInfo?.server?.broadcastReload();
77
+ }
78
+ }
79
+ }
80
+
61
81
  this._loggingResults(totalResultMap);
62
82
  this._loggingOpenClientHrefs();
63
83
  this._logger.info("모든 빌드가 완료되었습니다.");
@@ -78,11 +98,37 @@ export class SdCliWorkspace {
78
98
  await pkgs.parallelAsync(async (pkg) => {
79
99
  await Wait.until(() => !pkg.allDependencies.some((dep) => pkgNames.includes(dep) && !buildCompletedPackageNames.includes(dep)));
80
100
  if (pkg.config.type === "client") {
81
- const middlewares = (await pkg.watchAsync()) as NextHandleFunction[];
101
+ await pkg.watchAsync();
102
+
103
+ const pkgMiddleware: NextHandleFunction = (req, res, next) => {
104
+ if (req.method === "GET") {
105
+ const urlObj = url.parse(req.url!, true, false);
106
+ const urlPathChain = decodeURI(urlObj.pathname!.slice(1)).split("/");
107
+ if (urlPathChain[0] === pkg.name.split("/").last()!) {
108
+ let targetFilePath = path.resolve(pkg.rootPath, "dist", ...urlPathChain.slice(1));
109
+ targetFilePath = FsUtil.exists(targetFilePath) && FsUtil.stat(targetFilePath).isDirectory() ? path.resolve(targetFilePath, "index.html") : targetFilePath;
110
+
111
+ if (FsUtil.exists(targetFilePath) && !path.basename(targetFilePath).startsWith(".")) {
112
+ const fileStream = FsUtil.createReadStream(targetFilePath);
113
+ const targetFileSize = FsUtil.lstat(targetFilePath).size;
114
+
115
+ fileStream.on("open", () => {
116
+ res.setHeader("Content-Length", targetFileSize);
117
+ res.setHeader("Content-Type", mime.getType(targetFilePath)!);
118
+ res.writeHead(200);
119
+ });
120
+ fileStream.pipe(res);
121
+ return;
122
+ }
123
+ }
124
+ }
125
+
126
+ next();
127
+ };
82
128
 
83
129
  if (typeof pkg.config.server === "string") {
84
130
  const serverInfo = this._serverInfoMap.getOrCreate(pkg.config.server, { middlewares: [], clientInfos: [] });
85
- serverInfo.middlewares.push(...middlewares);
131
+ serverInfo.middlewares.push(pkgMiddleware);
86
132
  serverInfo.clientInfos.push({
87
133
  pkgKey: pkg.name.split("/").last()!,
88
134
  platforms: pkg.config.builder ? Object.keys(pkg.config.builder) : ["web"],
@@ -90,7 +136,10 @@ export class SdCliWorkspace {
90
136
  });
91
137
  }
92
138
  else { // DEV SERVER
93
- const serverInfo = this._serverInfoMap.getOrCreate("_", { middlewares: [], clientInfos: [] });
139
+ const serverInfo = this._serverInfoMap.getOrCreate("PORT:" + pkg.config.server.port, {
140
+ middlewares: [],
141
+ clientInfos: []
142
+ });
94
143
  if (serverInfo.server === undefined) {
95
144
  const server = new SdServiceServer({
96
145
  rootPath: process.cwd(),
@@ -99,7 +148,7 @@ export class SdCliWorkspace {
99
148
  });
100
149
  await server.listenAsync();
101
150
  serverInfo.server = server;
102
- serverInfo.server.devMiddlewares = middlewares;
151
+ serverInfo.server.devMiddlewares = [pkgMiddleware];
103
152
  serverInfo.clientInfos.push({
104
153
  pkgKey: pkg.name.split("/").last()!,
105
154
  platforms: pkg.config.builder ? Object.keys(pkg.config.builder) : ["web"],
@@ -245,8 +294,8 @@ export class SdCliWorkspace {
245
294
 
246
295
  // 빌드
247
296
  if (!opt.noBuild) {
248
- this._logger.debug("노드패키지 업데이트...");
249
- await new SdCliNpm(this._rootPath).updateAsync();
297
+ // this._logger.debug("노드패키지 업데이트...");
298
+ // await new SdCliNpm(this._rootPath).updateAsync();
250
299
 
251
300
  this._logger.debug("빌드를 시작합니다...");
252
301
  await this._buildPkgsAsync(pkgs);
@@ -2,16 +2,13 @@ import { INpmConfig, ISdCliPackageBuildResult, ITsconfig, TSdCliPackageConfig }
2
2
  import path from "path";
3
3
  import { FsUtil, PathUtil, SdProcess } from "@simplysm/sd-core-node";
4
4
  import { EventEmitter } from "events";
5
- import { NeverEntryError, ObjectUtil, StringUtil } from "@simplysm/sd-core-common";
6
- import { SdCliTsLibBuilder } from "../builder/SdCliTsLibBuilder";
7
- import { SdCliJsLibBuilder } from "../builder/SdCliJsLibBuilder";
8
- import { SdCliServerBuilder } from "../builder/SdCliServerBuilder";
5
+ import { JsonConvert, NeverEntryError, ObjectUtil, StringUtil } from "@simplysm/sd-core-common";
9
6
  import { SdCliNpmConfigUtil } from "../utils/SdCliNpmConfigUtil";
10
- import { SdCliClientBuilder } from "../builder/SdCliClientBuilder";
11
- import { NextHandleFunction } from "connect";
12
7
  import { SdStorage } from "@simplysm/sd-storage";
13
8
  import ts from "typescript";
14
9
  import { SdCliGithubApi } from "../build-tool/SdCliGithubApi";
10
+ import cp from "child_process";
11
+ import { fileURLToPath } from "url";
15
12
 
16
13
  export class SdCliPackage extends EventEmitter {
17
14
  private readonly _npmConfig: INpmConfig;
@@ -70,19 +67,91 @@ export class SdCliPackage extends EventEmitter {
70
67
  await FsUtil.writeJsonAsync(npmConfigFilePath, this._npmConfig, { space: 2 });
71
68
  }
72
69
 
73
- public async watchAsync(): Promise<NextHandleFunction[] | void> {
74
- return await (await this._createBuilderAsync())
75
- .on("change", () => {
76
- this.emit("change");
77
- })
78
- .on("complete", (results) => {
79
- this.emit("complete", results);
80
- })
81
- .watchAsync();
70
+ public async watchAsync(): Promise<void> {
71
+ const isTs = FsUtil.exists(path.resolve(this.rootPath, "tsconfig.json"));
72
+
73
+ if (isTs) {
74
+ await this._genBuildTsconfigAsync();
75
+ }
76
+
77
+ const workerPath = fileURLToPath(await import.meta.resolve!("../worker/build-worker"));
78
+ await new Promise<void>((resolve, reject) => {
79
+ const worker = cp.fork(workerPath, [
80
+ "watch",
81
+ this.rootPath,
82
+ JsonConvert.stringify(this.config),
83
+ this._workspaceRootPath
84
+ ], {
85
+ stdio: ["pipe", "pipe", "pipe", "ipc"],
86
+ env: process.env
87
+ });
88
+
89
+ worker.on("error", (err) => {
90
+ reject(err);
91
+ });
92
+
93
+ worker.stdout!.pipe(process.stdout);
94
+ worker.stderr!.pipe(process.stderr);
95
+
96
+ worker.on("message", (json: string) => {
97
+ const msg = JsonConvert.parse(json);
98
+ if (msg.event === "ready") {
99
+ resolve();
100
+ }
101
+ else if (msg.event === "change") {
102
+ this.emit("change");
103
+ }
104
+ else if (msg.event === "complete") {
105
+ this.emit("complete", msg.body);
106
+ }
107
+ else {
108
+ throw new NeverEntryError();
109
+ }
110
+ });
111
+ });
82
112
  }
83
113
 
84
114
  public async buildAsync(): Promise<ISdCliPackageBuildResult[]> {
85
- return await (await this._createBuilderAsync()).buildAsync();
115
+ const isTs = FsUtil.exists(path.resolve(this.rootPath, "tsconfig.json"));
116
+
117
+ if (isTs) {
118
+ await this._genBuildTsconfigAsync();
119
+ }
120
+
121
+ const workerPath = fileURLToPath(await import.meta.resolve!("../worker/build-worker"));
122
+ return await new Promise<ISdCliPackageBuildResult[]>((resolve, reject) => {
123
+ const worker = cp.fork(workerPath, [
124
+ "build",
125
+ this.rootPath,
126
+ JsonConvert.stringify(this.config),
127
+ this._workspaceRootPath
128
+ ], {
129
+ stdio: ["pipe", "pipe", "pipe", "ipc"],
130
+ env: process.env
131
+ });
132
+
133
+ worker.on("error", (err) => {
134
+ reject(err);
135
+ });
136
+
137
+ worker.stdout!.pipe(process.stdout);
138
+ worker.stderr!.pipe(process.stderr);
139
+
140
+ let result: ISdCliPackageBuildResult[] = [];
141
+
142
+ worker.on("message", (json: string) => {
143
+ result = JsonConvert.parse(json);
144
+ });
145
+
146
+ worker.on("exit", (code) => {
147
+ if (code !== 0) {
148
+ reject(new Error(`오류와 함께 닫힘 (${code})`));
149
+ return;
150
+ }
151
+
152
+ resolve(result);
153
+ });
154
+ });
86
155
  }
87
156
 
88
157
  public async publishAsync(): Promise<void> {
@@ -156,24 +225,6 @@ export class SdCliPackage extends EventEmitter {
156
225
  }
157
226
  }
158
227
 
159
- private async _createBuilderAsync(): Promise<SdCliJsLibBuilder | SdCliTsLibBuilder | SdCliServerBuilder | SdCliClientBuilder> {
160
- const isTs = FsUtil.exists(path.resolve(this.rootPath, "tsconfig.json"));
161
-
162
- if (isTs) {
163
- await this._genBuildTsconfigAsync();
164
- }
165
-
166
- if (this.config.type === "library") {
167
- return isTs ? new SdCliTsLibBuilder(this.rootPath, this.config, this._workspaceRootPath) : new SdCliJsLibBuilder(this.rootPath);
168
- }
169
- else if (this.config.type === "server") {
170
- return new SdCliServerBuilder(this.rootPath, this.config, this._workspaceRootPath);
171
- }
172
- else {
173
- return new SdCliClientBuilder(this.rootPath, this.config, this._workspaceRootPath);
174
- }
175
- }
176
-
177
228
  private async _genBuildTsconfigAsync(): Promise<void> {
178
229
  const baseTsconfigFilePath = path.resolve(this.rootPath, "tsconfig.json");
179
230
  const baseTsconfig: ITsconfig = await FsUtil.readJsonAsync(baseTsconfigFilePath);
@@ -74,10 +74,10 @@ export class SdCliConfigUtil {
74
74
  return pkgConf;
75
75
  }
76
76
 
77
- private static _mergeObj<A, B>(org: A, target: B): A & B {
78
- return ObjectUtil.merge(org, target, {
77
+ private static _mergeObj<A, B>(orig: A, target: B): A & B {
78
+ return ObjectUtil.merge(orig, target, {
79
79
  arrayProcess: "replace",
80
- useDelTargetUndefined: true
80
+ useDelTargetNull: true
81
81
  });
82
82
  }
83
83
  }
@@ -0,0 +1,74 @@
1
+ import { SdCliTsLibBuilder } from "../builder/SdCliTsLibBuilder";
2
+ import { SdCliJsLibBuilder } from "../builder/SdCliJsLibBuilder";
3
+ import { SdCliServerBuilder } from "../builder/SdCliServerBuilder";
4
+ import { SdCliClientBuilder } from "../builder/SdCliClientBuilder";
5
+ import { FsUtil, Logger, LoggerSeverity } from "@simplysm/sd-core-node";
6
+ import path from "path";
7
+ import { JsonConvert } from "@simplysm/sd-core-common";
8
+ import { TSdCliPackageConfig } from "../commons";
9
+
10
+
11
+ if (process.env["SD_CLI_LOGGER_SEVERITY"] === "DEBUG") {
12
+ Logger.setConfig({
13
+ console: {
14
+ level: LoggerSeverity.debug
15
+ }
16
+ });
17
+ }
18
+ else {
19
+ Logger.setConfig({
20
+ dot: true
21
+ });
22
+ }
23
+
24
+ const type = process.argv[2] as ("build" | "watch");
25
+ const rootPath = process.argv[3];
26
+ const config = JsonConvert.parse(process.argv[4]) as TSdCliPackageConfig;
27
+ const workspaceRootPath = process.argv[5];
28
+
29
+ function createBuilder(): SdCliJsLibBuilder | SdCliTsLibBuilder | SdCliServerBuilder | SdCliClientBuilder {
30
+ const isTs = FsUtil.exists(path.resolve(rootPath, "tsconfig.json"));
31
+
32
+ if (config.type === "library") {
33
+ return isTs ? new SdCliTsLibBuilder(rootPath, config, workspaceRootPath) : new SdCliJsLibBuilder(rootPath);
34
+ }
35
+ else if (config.type === "server") {
36
+ return new SdCliServerBuilder(rootPath, config, workspaceRootPath);
37
+ }
38
+ else {
39
+ return new SdCliClientBuilder(rootPath, config, workspaceRootPath);
40
+ }
41
+ }
42
+
43
+ const builder = createBuilder();
44
+
45
+ if (type === "build") {
46
+ builder.buildAsync()
47
+ .then((result) => {
48
+ process.send!(JsonConvert.stringify(result));
49
+ process.exit(0);
50
+ })
51
+ .catch((err) => {
52
+ // eslint-disable-next-line no-console
53
+ console.error(err);
54
+ process.exit(1);
55
+ });
56
+ }
57
+ else {
58
+ builder
59
+ .on("change", () => {
60
+ process.send!(JsonConvert.stringify({ event: "change" }));
61
+ })
62
+ .on("complete", (results) => {
63
+ process.send!(JsonConvert.stringify({ event: "complete", body: results }));
64
+ })
65
+ .watchAsync()
66
+ .then(() => {
67
+ process.send!(JsonConvert.stringify({ event: "ready" }));
68
+ })
69
+ .catch((err) => {
70
+ // eslint-disable-next-line no-console
71
+ console.error(err);
72
+ process.exit(1);
73
+ });
74
+ }
@@ -9,6 +9,7 @@
9
9
  },
10
10
  "files": [
11
11
  "src/bin/sd-cli.ts",
12
- "src/index.ts"
12
+ "src/index.ts",
13
+ "src/worker/build-worker.ts"
13
14
  ]
14
15
  }