@simplysm/sd-cli 11.0.6 → 11.0.9

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 (38) hide show
  1. package/dist/build-cluster.js +2 -2
  2. package/dist/build-cluster.js.map +1 -1
  3. package/dist/build-tools/SdCliCordova.d.ts +21 -0
  4. package/dist/build-tools/SdCliCordova.js +182 -0
  5. package/dist/build-tools/SdCliCordova.js.map +1 -0
  6. package/dist/build-tools/SdNgBundler.d.ts +5 -4
  7. package/dist/build-tools/SdNgBundler.js +49 -19
  8. package/dist/build-tools/SdNgBundler.js.map +1 -1
  9. package/dist/build-tools/SdTsBundler.d.ts +0 -1
  10. package/dist/build-tools/SdTsBundler.js +5 -4
  11. package/dist/build-tools/SdTsBundler.js.map +1 -1
  12. package/dist/builders/SdCliClientBuilder.d.ts +1 -1
  13. package/dist/builders/SdCliClientBuilder.js +22 -2
  14. package/dist/builders/SdCliClientBuilder.js.map +1 -1
  15. package/dist/builders/SdCliServerBuilder.d.ts +0 -1
  16. package/dist/builders/SdCliServerBuilder.js +98 -79
  17. package/dist/builders/SdCliServerBuilder.js.map +1 -1
  18. package/dist/builders/SdCliTsLibBuilder.d.ts +2 -3
  19. package/dist/builders/SdCliTsLibBuilder.js +1 -2
  20. package/dist/builders/SdCliTsLibBuilder.js.map +1 -1
  21. package/dist/commons.d.ts +22 -0
  22. package/dist/index.d.ts +1 -0
  23. package/dist/index.js +1 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/sd-cli.js +24 -0
  26. package/dist/sd-cli.js.map +1 -1
  27. package/lib/cordova-entry.js +22 -0
  28. package/package.json +14 -11
  29. package/src/build-cluster.ts +2 -2
  30. package/src/build-tools/SdCliCordova.ts +240 -0
  31. package/src/build-tools/SdNgBundler.ts +64 -20
  32. package/src/build-tools/SdTsBundler.ts +6 -5
  33. package/src/builders/SdCliClientBuilder.ts +25 -4
  34. package/src/builders/SdCliServerBuilder.ts +16 -16
  35. package/src/builders/SdCliTsLibBuilder.ts +2 -3
  36. package/src/commons.ts +23 -0
  37. package/src/index.ts +1 -0
  38. package/src/sd-cli.ts +28 -0
@@ -0,0 +1,240 @@
1
+ import {INpmConfig, ISdCliClientBuilderCordovaConfig} from "../commons";
2
+ import * as path from "path";
3
+ import {FsUtil, Logger, SdProcess} from "@simplysm/sd-core-node";
4
+ import xml2js from "xml2js";
5
+
6
+ const BIN_PATH = path.resolve(process.cwd(), "node_modules/.bin/cordova.cmd");
7
+
8
+ export class SdCliCordova {
9
+ protected readonly _logger = Logger.get(["simplysm", "sd-cli", "SdCliCordova"]);
10
+
11
+ private readonly _platforms: string[];
12
+ private readonly _npmConfig: INpmConfig;
13
+
14
+ public constructor(private readonly _opt: {
15
+ pkgPath: string;
16
+ config: ISdCliClientBuilderCordovaConfig;
17
+ cordovaPath: string;
18
+ }) {
19
+ this._platforms = Object.keys(this._opt.config.platform ?? {browser: {}});
20
+ this._npmConfig = FsUtil.readJson(path.resolve(this._opt.pkgPath, "package.json"));
21
+ // this._logger = Logger.get(["simplysm", "sd-cli", this.constructor.name, this._npmConfig.name]);
22
+ }
23
+
24
+ private async _execAsync(cmd: string, cwd: string): Promise<void> {
25
+ this._logger.debug(cmd);
26
+ const msg = await SdProcess.spawnAsync(cmd, {cwd});
27
+ this._logger.debug(msg);
28
+ }
29
+
30
+ public async initializeAsync(): Promise<void> {
31
+ if (FsUtil.exists(this._opt.cordovaPath)) {
32
+ this._logger.log("이미 생성되어있는 '.cordova'를 사용합니다.");
33
+ }
34
+ else {
35
+ await this._execAsync(`${BIN_PATH} telemetry on`, this._opt.pkgPath);
36
+
37
+ // 프로젝트 생성
38
+ await this._execAsync(`${BIN_PATH} create "${this._opt.cordovaPath}" "${this._opt.config.appId}" "${this._opt.config.appName}"`, process.cwd());
39
+ }
40
+
41
+ // platforms 폴더 혹시 없으면 생성
42
+ await FsUtil.mkdirsAsync(path.resolve(this._opt.cordovaPath, "platforms"));
43
+
44
+ // www 폴더 혹시 없으면 생성
45
+ await FsUtil.mkdirsAsync(path.resolve(this._opt.cordovaPath, "www"));
46
+
47
+ // 미설치 빌드 플랫폼 신규 생성
48
+ const alreadyPlatforms = await FsUtil.readdirAsync(path.resolve(this._opt.cordovaPath, "platforms"));
49
+ for (const platform of this._platforms) {
50
+ if (!alreadyPlatforms.includes(platform)) {
51
+ await this._execAsync(`${BIN_PATH} platform add ${platform}`, this._opt.cordovaPath);
52
+ }
53
+ }
54
+
55
+ // 설치 미빌드 플랫폼 삭제
56
+ for (const alreadyPlatform of alreadyPlatforms) {
57
+ if (!this._platforms.includes(alreadyPlatform)) {
58
+ await this._execAsync(`${BIN_PATH} platform remove ${alreadyPlatform}`, this._opt.cordovaPath);
59
+ }
60
+ }
61
+
62
+ // 설치된 미사용 플러그인 삭제
63
+ const pluginsFetch = FsUtil.exists(path.resolve(this._opt.cordovaPath, "plugins/fetch.json"))
64
+ ? await FsUtil.readJsonAsync(path.resolve(this._opt.cordovaPath, "plugins/fetch.json"))
65
+ : undefined;
66
+ const alreadyPluginIds = pluginsFetch != undefined
67
+ ? Object.values(pluginsFetch)
68
+ .map((item: any) => (item.source.id !== undefined ? item.source.id : item.source.url))
69
+ : [];
70
+ const usePlugins = ["cordova-plugin-ionic-webview", ...this._opt.config.plugins ?? []].distinct();
71
+
72
+ for (const alreadyPluginId of alreadyPluginIds) {
73
+ let hasPlugin = false;
74
+ for (const usePlugin of usePlugins) {
75
+ if (
76
+ (usePlugin.includes("@") && alreadyPluginId === usePlugin) ||
77
+ (!usePlugin.includes("@") && alreadyPluginId.replace(/@.*$/, "") === usePlugin)
78
+ ) {
79
+ hasPlugin = true;
80
+ break;
81
+ }
82
+ }
83
+
84
+ if (!hasPlugin) {
85
+ await this._execAsync(`${BIN_PATH} plugin remove ${alreadyPluginId.replace(/@.*$/, "")}`, this._opt.cordovaPath);
86
+ }
87
+ }
88
+
89
+ // 미설치 플러그인들 설치
90
+ for (const usePlugin of usePlugins) {
91
+ if (
92
+ (usePlugin.includes("@") && !alreadyPluginIds.includes(usePlugin)) ||
93
+ (!usePlugin.includes("@") && !alreadyPluginIds.map((alreadyPluginId) => alreadyPluginId.replace(/@.*$/, "")).includes(usePlugin))
94
+ ) {
95
+ await this._execAsync(`${BIN_PATH} plugin add ${usePlugin}`, this._opt.cordovaPath);
96
+ }
97
+ }
98
+
99
+ // ANDROID SIGN 파일 복사
100
+ if (this._opt.config.platform?.android?.sign) {
101
+ await FsUtil.copyAsync(
102
+ path.resolve(this._opt.pkgPath, this._opt.config.platform.android.sign.keystore),
103
+ path.resolve(this._opt.cordovaPath, "android.keystore")
104
+ );
105
+ }
106
+ else {
107
+ await FsUtil.removeAsync(path.resolve(this._opt.cordovaPath, "android.keystore"));
108
+ // SIGN을 안쓸경우 아래 파일이 생성되어 있으면 오류남
109
+ await FsUtil.removeAsync(path.resolve(this._opt.cordovaPath, "platforms/android/release-signing.properties"));
110
+ }
111
+
112
+ // 빌드 옵션 파일 생성
113
+ await FsUtil.writeJsonAsync(
114
+ path.resolve(this._opt.cordovaPath, "build.json"),
115
+ {
116
+ ...this._opt.config.platform?.android ? {
117
+ android: {
118
+ release: {
119
+ packageType: this._opt.config.platform.android.bundle ? "bundle" : "apk",
120
+ ...this._opt.config.platform.android.sign ? {
121
+ keystore: path.resolve(this._opt.cordovaPath, "android.keystore"),
122
+ storePassword: this._opt.config.platform.android.sign.storePassword,
123
+ alias: this._opt.config.platform.android.sign.alias,
124
+ password: this._opt.config.platform.android.sign.password,
125
+ keystoreType: this._opt.config.platform.android.sign.keystoreType
126
+ } : {}
127
+ }
128
+ }
129
+ } : {}
130
+ }
131
+ );
132
+
133
+ // ICON 파일 복사
134
+ if (this._opt.config.icon !== undefined) {
135
+ await FsUtil.copyAsync(path.resolve(this._opt.cordovaPath, this._opt.config.icon), path.resolve(this._opt.cordovaPath, "res", "icon.png"));
136
+ }
137
+ else {
138
+ await FsUtil.removeAsync(path.resolve(this._opt.cordovaPath, "res", "icon.png"));
139
+ }
140
+
141
+ // CONFIG: 초기값 백업
142
+ const configFilePath = path.resolve(this._opt.cordovaPath, "config.xml");
143
+ const configBackFilePath = path.resolve(this._opt.cordovaPath, "config.xml.bak");
144
+ if (!FsUtil.exists(configBackFilePath)) {
145
+ await FsUtil.copyAsync(configFilePath, configBackFilePath);
146
+ }
147
+
148
+ // CONFIG: 초기값 읽기
149
+ const configFileContent = await FsUtil.readFileAsync(configBackFilePath);
150
+ const configXml = await xml2js.parseStringPromise(configFileContent);
151
+
152
+
153
+ // CONFIG: 버전 설정
154
+ configXml.widget.$.version = this._npmConfig.version;
155
+
156
+ // CONFIG: ICON 설정
157
+ if (this._opt.config.icon !== undefined) {
158
+ configXml["widget"]["icon"] = [{"$": {"src": "res/icon.png"}}];
159
+ }
160
+
161
+ // CONFIG: 접근허용 세팅
162
+ configXml["widget"]["access"] = [{"$": {"origin": "*"}}];
163
+ configXml["widget"]["allow-navigation"] = [{"$": {"href": "*"}}];
164
+ configXml["widget"]["allow-intent"] = [{"$": {"href": "*"}}];
165
+ configXml["widget"]["preference"] = [{"$": {"name": "MixedContentMode", "value": "0"}}];
166
+
167
+ // CONFIG: ANDROID usesCleartextTraffic 설정
168
+ if (this._opt.config.platform?.android) {
169
+ configXml.widget.$["xmlns:android"] = "http://schemas.android.com/apk/res/android";
170
+
171
+ configXml["widget"]["platform"] = configXml["widget"]["platform"] ?? [];
172
+ configXml["widget"]["platform"].push({
173
+ "$": {
174
+ "name": "android"
175
+ },
176
+ "edit-config": [{
177
+ "$": {
178
+ "file": "app/src/main/AndroidManifest.xml",
179
+ "mode": "merge",
180
+ "target": "/manifest/application"
181
+ },
182
+ "application": [{
183
+ "$": {
184
+ "android:usesCleartextTraffic": "true"
185
+ }
186
+ }]
187
+ }]
188
+ });
189
+ }
190
+
191
+ // CONFIG: 파일 새로 쓰기
192
+ const configResultContent = new xml2js.Builder().buildObject(configXml);
193
+ await FsUtil.writeFileAsync(configFilePath, configResultContent);
194
+
195
+ // 각 플랫폼 www 준비
196
+ await this._execAsync(`${BIN_PATH} prepare`, this._opt.cordovaPath);
197
+ }
198
+
199
+ public async buildAsync(outPath: string): Promise<void> {
200
+ // 실행
201
+ const buildType = this._opt.config.debug ? "debug" : "release";
202
+ for (const platform of this._platforms) {
203
+ await this._execAsync(`${BIN_PATH} build ${platform} --${buildType}`, this._opt.cordovaPath);
204
+ }
205
+
206
+ // 결과물 복사: ANDROID
207
+ if (this._opt.config.platform?.android) {
208
+ const targetOutPath = path.resolve(outPath, "android");
209
+ const apkFileName = this._opt.config.platform.android.sign ? `app-${buildType}.apk` : `app-${buildType}-unsigned.apk`;
210
+ const latestDistApkFileName = path.basename(`${this._opt.config.appName}${this._opt.config.platform.android.sign ? "" : "-unsigned"}-latest.apk`);
211
+ await FsUtil.mkdirsAsync(targetOutPath);
212
+ await FsUtil.copyAsync(
213
+ path.resolve(this._opt.cordovaPath, "platforms/android/app/build/outputs/apk", buildType, apkFileName),
214
+ path.resolve(targetOutPath, latestDistApkFileName)
215
+ );
216
+ // 자동업데이트를 위한 파일 쓰기
217
+ await FsUtil.copyAsync(
218
+ path.resolve(this._opt.cordovaPath, "platforms/android/app/build/outputs/apk", buildType, apkFileName),
219
+ path.resolve(path.resolve(targetOutPath, "updates"), this._npmConfig.version + ".apk")
220
+ );
221
+ }
222
+ }
223
+
224
+ public static async runWebviewOnDeviceAsync(opt: {
225
+ platform: string,
226
+ pkgName: string,
227
+ url?: string
228
+ }): Promise<void> {
229
+ const cordovaPath = path.resolve(process.cwd(), `packages/${opt.pkgName}/.cache/cordova/`);
230
+
231
+ if (opt.url !== undefined) {
232
+ await FsUtil.removeAsync(path.resolve(cordovaPath, "www"));
233
+ await FsUtil.mkdirsAsync(path.resolve(cordovaPath, "www"));
234
+ await FsUtil.writeFileAsync(path.resolve(cordovaPath, "www/index.html"), `'${opt.url}'로 이동중... <script>setTimeout(function () {window.location.href = "${opt.url.replace(/\/$/, "")}/${opt.pkgName}/cordova/"}, 3000);</script>`.trim());
235
+ }
236
+
237
+ const binPath = path.resolve(process.cwd(), "node_modules/.bin/cordova.cmd");
238
+ await SdProcess.spawnAsync(`${binPath} run ${opt.platform} --device`, {cwd: cordovaPath}, true);
239
+ }
240
+ }
@@ -17,12 +17,13 @@ import {
17
17
  import nodeStdLibBrowser from "node-stdlib-browser";
18
18
  import nodeStdLibBrowserPlugin from "node-stdlib-browser/helpers/esbuild/plugin";
19
19
  import {ExecutionResult} from "@angular-devkit/build-angular/src/tools/esbuild/bundler-execution-result";
20
- import {INpmConfig, ISdCliPackageBuildResult} from "../commons";
20
+ import {INpmConfig, ISdCliClientPackageConfig, ISdCliPackageBuildResult} from "../commons";
21
21
  import {generateIndexHtml} from "@angular-devkit/build-angular/src/tools/esbuild/index-html-generator";
22
22
  import {copyAssets} from "@angular-devkit/build-angular/src/utils/copy-assets";
23
23
  import {extractLicenses} from "@angular-devkit/build-angular/src/tools/esbuild/license-extractor";
24
24
  import {augmentAppWithServiceWorkerEsbuild} from "@angular-devkit/build-angular/src/utils/service-worker";
25
25
  import {createGlobalStylesBundleOptions} from "@angular-devkit/build-angular/src/tools/esbuild/global-styles";
26
+ import {Entrypoint} from "@angular-devkit/build-angular/src/utils/index-file/augment-index-html";
26
27
 
27
28
  export class SdNgBundler {
28
29
  private readonly _logger = Logger.get(["simplysm", "sd-cli", "SdNgEsbuildBuilder"]);
@@ -34,8 +35,14 @@ export class SdNgBundler {
34
35
 
35
36
  private readonly _outputCache = new Map<string, string | number>();
36
37
 
37
- public constructor(private readonly _opt: { dev: boolean, builderType: string, pkgPath: string }) {
38
- this._options = this._getOptions(_opt);
38
+ public constructor(opt: {
39
+ dev: boolean,
40
+ builderType: keyof ISdCliClientPackageConfig["builder"],
41
+ pkgPath: string,
42
+ cordovaPlatforms: string[] | undefined,
43
+ outputPath: string
44
+ }) {
45
+ this._options = this._getOptions(opt);
39
46
  }
40
47
 
41
48
  public removeCache(filePaths: string[]): void {
@@ -58,7 +65,7 @@ export class SdNgBundler {
58
65
 
59
66
  const results = [
60
67
  ...bundlingResult.warnings.map((warn) => ({
61
- filePath: warn.location?.file !== undefined ? path.resolve(this._opt.pkgPath, warn.location.file) : undefined,
68
+ filePath: warn.location?.file !== undefined ? path.resolve(this._options.workspaceRoot, warn.location.file) : undefined,
62
69
  line: warn.location?.line,
63
70
  char: warn.location?.column,
64
71
  code: undefined,
@@ -67,7 +74,7 @@ export class SdNgBundler {
67
74
  type: "build" as const
68
75
  })),
69
76
  ...bundlingResult.errors?.map((err) => ({
70
- filePath: err.location?.file !== undefined ? path.resolve(this._opt.pkgPath, err.location.file) : undefined,
77
+ filePath: err.location?.file !== undefined ? path.resolve(this._options.workspaceRoot, err.location.file) : undefined,
71
78
  line: err.location?.line,
72
79
  char: err.location?.column !== undefined ? err.location.column + 1 : undefined,
73
80
  code: undefined,
@@ -77,10 +84,10 @@ export class SdNgBundler {
77
84
  })) ?? []
78
85
  ];
79
86
  const watchFilePaths = [
80
- ...this._sourceFileCache.typeScriptFileCache.keys(),
87
+ ...this._sourceFileCache.keys(),
81
88
  ...this._sourceFileCache.babelFileCache.keys()
82
89
  ].map((item) => path.resolve(item));
83
- let affectedSourceFilePaths = watchFilePaths.filter((item) => PathUtil.isChildPath(item, this._opt.pkgPath));
90
+ let affectedSourceFilePaths = watchFilePaths.filter((item) => PathUtil.isChildPath(item, this._options.workspaceRoot));
84
91
  if (bundlingResult.errors) {
85
92
  return {
86
93
  filePaths: watchFilePaths,
@@ -92,8 +99,8 @@ export class SdNgBundler {
92
99
  const depsMap = new Map<string, Set<string>>();
93
100
  for (const entry of Object.entries(bundlingResult.metafile.inputs)) {
94
101
  for (const imp of entry[1].imports) {
95
- const deps = depsMap.getOrCreate(path.resolve(this._opt.pkgPath, imp.path), new Set<string>());
96
- deps.add(path.resolve(this._opt.pkgPath, entry[0]));
102
+ const deps = depsMap.getOrCreate(path.resolve(this._options.workspaceRoot, imp.path), new Set<string>());
103
+ deps.add(path.resolve(this._options.workspaceRoot, entry[0]));
97
104
  }
98
105
  }
99
106
 
@@ -115,9 +122,10 @@ export class SdNgBundler {
115
122
  if (this._sourceFileCache.modifiedFiles.size > 0) {
116
123
  const affectedFilePathSet = new Set<string>();
117
124
  for (const modFile of this._sourceFileCache.modifiedFiles) {
125
+ affectedFilePathSet.add(path.resolve(modFile));
118
126
  affectedFilePathSet.adds(...searchAffectedFiles(path.resolve(modFile)));
119
127
  }
120
- affectedSourceFilePaths = Array.from(affectedFilePathSet.values()).filter((item) => PathUtil.isChildPath(item, this._opt.pkgPath));
128
+ affectedSourceFilePaths = Array.from(affectedFilePathSet.values()).filter((item) => PathUtil.isChildPath(item, this._options.workspaceRoot));
121
129
  }
122
130
 
123
131
  const executionResult = new ExecutionResult(this._contexts, this._sourceFileCache);
@@ -176,9 +184,8 @@ export class SdNgBundler {
176
184
 
177
185
  //-- write
178
186
 
179
- const distPath = path.resolve(this._options.outputPath, "dist", ...(this._opt.builderType === "web" ? [] : [this._opt.builderType]));
180
187
  for (const outputFile of executionResult.outputFiles) {
181
- const distFilePath = path.resolve(distPath, outputFile.path);
188
+ const distFilePath = path.resolve(this._options.outputPath, outputFile.path);
182
189
 
183
190
  const prev = this._outputCache.get(distFilePath);
184
191
  if (prev !== Buffer.from(outputFile.contents).toString("base64")) {
@@ -190,7 +197,7 @@ export class SdNgBundler {
190
197
  const prev = this._outputCache.get(assetFile.source);
191
198
  const curr = FsUtil.lstat(assetFile.source).mtime.getTime();
192
199
  if (prev !== curr) {
193
- await FsUtil.copyAsync(assetFile.source, path.resolve(distPath, assetFile.destination));
200
+ await FsUtil.copyAsync(assetFile.source, path.resolve(this._options.outputPath, assetFile.destination));
194
201
  this._outputCache.set(assetFile.source, curr);
195
202
  }
196
203
  }
@@ -276,7 +283,7 @@ export class SdNgBundler {
276
283
  minifySyntax: !this._options.watch,
277
284
  minifyWhitespace: !this._options.watch,
278
285
  pure: ['forwardRef'],
279
- outdir: this._options.outputPath,
286
+ outdir: this._options.workspaceRoot,
280
287
  outExtension: undefined,
281
288
  sourcemap: this._options.watch,
282
289
  splitting: true,
@@ -292,6 +299,7 @@ export class SdNgBundler {
292
299
  process: 'process',
293
300
  Buffer: 'Buffer',
294
301
  'process.env.SD_VERSION': JSON.stringify(pkgNpmConf.version),
302
+ "process.env.NODE_ENV": JSON.stringify(this._options.watch ? "development" : "production")
295
303
  },
296
304
  platform: 'browser',
297
305
  mainFields: ['es2020', 'es2015', 'browser', 'module', 'main'],
@@ -358,7 +366,13 @@ export class SdNgBundler {
358
366
  };
359
367
  }
360
368
 
361
- private _getOptions(opt: { dev: boolean, builderType: string, pkgPath: string }): NormalizedApplicationBuildOptions {
369
+ private _getOptions(opt: {
370
+ dev: boolean,
371
+ builderType: keyof ISdCliClientPackageConfig["builder"],
372
+ pkgPath: string,
373
+ cordovaPlatforms: string[] | undefined,
374
+ outputPath: string,
375
+ }): NormalizedApplicationBuildOptions {
362
376
  const mainFilePath = path.resolve(opt.pkgPath, "src/main.ts");
363
377
  const tsconfigFilePath = path.resolve(opt.pkgPath, "tsconfig.json");
364
378
  const indexHtmlFilePath = path.resolve(opt.pkgPath, "src/index.html");
@@ -398,20 +412,47 @@ export class SdNgBundler {
398
412
  verbose: false,
399
413
  watch: opt.dev,
400
414
  workspaceRoot: opt.pkgPath,
401
- entryPoints: {main: mainFilePath},
415
+ entryPoints: {
416
+ main: mainFilePath,
417
+ ...opt.builderType === "cordova" ? {
418
+ "cordova-entry": path.resolve(path.dirname(fileURLToPath(import.meta.url)), `../../lib/cordova-entry.js`)
419
+ } : {}
420
+ },
402
421
  optimizationOptions: {
403
422
  scripts: !opt.dev,
404
423
  styles: {minify: !opt.dev, inlineCritical: !opt.dev},
405
424
  fonts: {inline: !opt.dev}
406
425
  },
407
- outputPath: opt.pkgPath,
426
+ outputPath: opt.outputPath,
408
427
  outExtension: undefined,
409
428
  sourcemapOptions: {vendor: false, hidden: false, scripts: opt.dev, styles: opt.dev},
410
429
  tsconfig: tsconfigFilePath,
411
430
  projectRoot: opt.pkgPath,
412
431
  assets: [
413
- {glob: 'favicon.ico', input: 'src', output: ''},
414
- {glob: '**/*', input: 'src\\assets', output: 'assets'}
432
+ {input: 'src', glob: 'favicon.ico', output: ''},
433
+ {input: 'src\\assets', glob: '**/*', output: 'assets'},
434
+ ...opt.dev && opt.cordovaPlatforms ? opt.cordovaPlatforms.mapMany((platform) => [
435
+ {
436
+ input: `.cache\\cordova\\platforms\\${platform}\\platform_www\\plugins`,
437
+ glob: '**/*',
438
+ output: `cordova-${platform}/plugins`
439
+ },
440
+ {
441
+ input: `.cache\\cordova\\platforms\\${platform}\\platform_www`,
442
+ glob: 'cordova.js',
443
+ output: `cordova-${platform}`
444
+ },
445
+ {
446
+ input: `.cache\\cordova\\platforms\\${platform}\\platform_www`,
447
+ glob: 'cordova_plugins.js',
448
+ output: `cordova-${platform}`
449
+ },
450
+ {
451
+ input: `.cache\\cordova\\platforms\\${platform}\\www`,
452
+ glob: 'config.xml',
453
+ output: `cordova-${platform}`
454
+ },
455
+ ]) : []
415
456
  ],
416
457
  outputNames: {bundles: '[name]', media: 'media/[name]'},
417
458
  fileReplacements: undefined,
@@ -426,7 +467,10 @@ export class SdNgBundler {
426
467
  ['polyfills', true],
427
468
  ['styles', false],
428
469
  ['vendor', true],
429
- ['main', true]
470
+ ['main', true],
471
+ ...opt.builderType === "cordova" ? [
472
+ ["cordova-entry", true] as Entrypoint
473
+ ] : []
430
474
  ]
431
475
  },
432
476
  tailwindConfiguration: undefined
@@ -10,7 +10,7 @@ export class SdTsBundler {
10
10
  dev: boolean;
11
11
  pkgPath: string;
12
12
  entryPoints: string[];
13
- external?: string[];
13
+ // external?: string[];
14
14
  }) {
15
15
  }
16
16
 
@@ -64,14 +64,15 @@ export class SdTsBundler {
64
64
  },
65
65
  platform: "node",
66
66
  logLevel: "silent",
67
- external: this._opt.external,
67
+ packages: "external",
68
+ // external: this._opt.external,
68
69
  banner: {
69
70
  js: `
70
71
  import __path__ from 'path';
71
72
  import { fileURLToPath as __fileURLToPath__ } from 'url';
72
- import { createRequire as __createRequire__ } from 'module';
73
-
74
- const require = __createRequire__(import.meta.url);
73
+ // import { createRequire as __createRequire__ } from 'module';
74
+ //
75
+ // const require = __createRequire__(import.meta.url);
75
76
  const __filename = __fileURLToPath__(import.meta.url);
76
77
  const __dirname = __path__.dirname(__filename);`.trim()
77
78
  },
@@ -3,15 +3,16 @@ import {FsUtil, Logger, PathUtil, SdFsWatcher} from "@simplysm/sd-core-node";
3
3
  import {ISdCliBuilderResult, ISdCliClientPackageConfig, ISdCliConfig, ISdCliPackageBuildResult} from "../commons";
4
4
  import {FunctionQueue, Wait} from "@simplysm/sd-core-common";
5
5
  import path from "path";
6
- import {SdTsCompiler} from "../build-tools/SdTsCompiler";
7
6
  import {SdNgBundler} from "../build-tools/SdNgBundler";
8
7
  import {SdLinter} from "../build-tools/SdLinter";
8
+ import {SdCliCordova} from "../build-tools/SdCliCordova";
9
9
 
10
+ // TODO: ROUTER 자동생성
10
11
  export class SdCliClientBuilder extends EventEmitter {
11
12
  private readonly _logger = Logger.get(["simplysm", "sd-cli", "SdCliClientBuilder"]);
12
13
  private readonly _pkgConf: ISdCliClientPackageConfig;
13
14
  private _builders?: SdNgBundler[];
14
- private _checker?: SdTsCompiler;
15
+ private _cordova?: SdCliCordova;
15
16
 
16
17
  public constructor(private readonly _projConf: ISdCliConfig,
17
18
  private readonly _pkgPath: string) {
@@ -73,12 +74,27 @@ export class SdCliClientBuilder extends EventEmitter {
73
74
  affectedFilePaths: string[];
74
75
  buildResults: ISdCliPackageBuildResult[];
75
76
  }> {
77
+ const builderTypes = (Object.keys(this._pkgConf.builder ?? {web: {}}) as (keyof ISdCliClientPackageConfig["builder"])[]);
78
+ if (this._pkgConf.builder?.cordova && !this._cordova) {
79
+ this._debug("CORDOVA 준비...");
80
+ this._cordova = new SdCliCordova({
81
+ pkgPath: this._pkgPath,
82
+ config: this._pkgConf.builder.cordova,
83
+ cordovaPath: path.resolve(this._pkgPath, ".cache/cordova")
84
+ });
85
+ await this._cordova.initializeAsync();
86
+ }
87
+
76
88
  this._debug(`BUILD 준비...`);
77
- const builderTypes = (Object.keys(this._pkgConf.builder ?? {web: {}}) as ("web")[]);
78
89
  this._builders = this._builders ?? builderTypes.map((builderType) => new SdNgBundler({
79
90
  dev: opt.dev,
80
91
  builderType: builderType,
81
- pkgPath: this._pkgPath
92
+ pkgPath: this._pkgPath,
93
+ cordovaPlatforms: builderType === "cordova" ? Object.keys(this._pkgConf.builder!.cordova!.platform ?? {browser: {}}) : undefined,
94
+ outputPath: builderType === "web" ? path.resolve(this._pkgPath, "dist")
95
+ : builderType === "electron" ? path.resolve(this._pkgPath, ".cache/electron/src")
96
+ : builderType === "cordova" && !opt.dev ? path.resolve(this._pkgPath, ".cache/cordova/www")
97
+ : path.resolve(this._pkgPath, "dist", builderType)
82
98
  }));
83
99
 
84
100
  this._debug(`BUILD & CHECK...`);
@@ -102,6 +118,11 @@ export class SdCliClientBuilder extends EventEmitter {
102
118
  }
103
119
  }
104
120
 
121
+ if (!opt.dev && this._cordova) {
122
+ this._debug("CORDOVA BUILD...");
123
+ await this._cordova.buildAsync(path.resolve(this._pkgPath, ".cache/cordova"));
124
+ }
125
+
105
126
  const localUpdatePaths = Object.keys(this._projConf.localUpdates ?? {})
106
127
  .mapMany((key) => FsUtil.glob(path.resolve(this._pkgPath, "../../node_modules", key)));
107
128
  const watchFilePaths = filePaths
@@ -4,7 +4,7 @@ import {INpmConfig, ISdCliBuilderResult, ISdCliConfig, ISdCliServerPackageConfig
4
4
  import path from "path";
5
5
  import {SdTsCompiler} from "../build-tools/SdTsCompiler";
6
6
  import {SdLinter} from "../build-tools/SdLinter";
7
- import {FunctionQueue, ObjectUtil, StringUtil} from "@simplysm/sd-core-common";
7
+ import {FunctionQueue, ObjectUtil} from "@simplysm/sd-core-common";
8
8
  import {SdTsBundler} from "../build-tools/SdTsBundler";
9
9
 
10
10
  export class SdCliServerBuilder extends EventEmitter {
@@ -26,7 +26,7 @@ export class SdCliServerBuilder extends EventEmitter {
26
26
  }
27
27
 
28
28
  public async watchAsync(): Promise<void> {
29
- const extModules = await this._getExternalModulesAsync();
29
+ // const extModules = await this._getExternalModulesAsync();
30
30
 
31
31
  this._debug("dist 초기화...");
32
32
  await FsUtil.removeAsync(path.resolve(this._pkgPath, "dist"));
@@ -42,7 +42,7 @@ export class SdCliServerBuilder extends EventEmitter {
42
42
  entryPoints: [
43
43
  path.resolve(this._pkgPath, "src/main.ts")
44
44
  ],
45
- external: extModules.map((item) => item.name)
45
+ // external: extModules.map((item) => item.name)
46
46
  });
47
47
 
48
48
  const checker = new SdTsCompiler({
@@ -73,7 +73,7 @@ export class SdCliServerBuilder extends EventEmitter {
73
73
  const checkResult = await checker.buildAsync();
74
74
 
75
75
  this._debug(`LINT...`);
76
- const lintResults = await SdLinter.lintAsync(checkResult.affectedFilePaths, checker.program);
76
+ const lintResults = !this._withLint ? [] : await SdLinter.lintAsync(checkResult.affectedFilePaths, checker.program);
77
77
 
78
78
  this._debug(`빌드 완료`);
79
79
  this.emit("complete", {
@@ -86,7 +86,7 @@ export class SdCliServerBuilder extends EventEmitter {
86
86
 
87
87
  public async buildAsync(): Promise<ISdCliBuilderResult> {
88
88
  const npmConfig = (await FsUtil.readJsonAsync(path.resolve(this._pkgPath, "package.json"))) as INpmConfig;
89
- const extModules = await this._getExternalModulesAsync();
89
+ // const extModules = await this._getExternalModulesAsync();
90
90
 
91
91
  this._debug("dist 초기화...");
92
92
  await FsUtil.removeAsync(path.resolve(this._pkgPath, "dist"));
@@ -97,13 +97,13 @@ export class SdCliServerBuilder extends EventEmitter {
97
97
 
98
98
  this._debug("GEN package.json...");
99
99
  {
100
- const deps = extModules.filter((item) => item.exists).map((item) => item.name);
100
+ // const deps = extModules.filter((item) => item.exists).map((item) => item.name);
101
101
 
102
102
  const distNpmConfig = ObjectUtil.clone(npmConfig);
103
- distNpmConfig.dependencies = {};
104
- for (const dep of deps) {
105
- distNpmConfig.dependencies[dep] = "*";
106
- }
103
+ // distNpmConfig.dependencies = {};
104
+ // for (const dep of deps) {
105
+ // distNpmConfig.dependencies[dep] = "*";
106
+ // }
107
107
  delete distNpmConfig.optionalDependencies;
108
108
  delete distNpmConfig.devDependencies;
109
109
  delete distNpmConfig.peerDependencies;
@@ -184,7 +184,7 @@ export class SdCliServerBuilder extends EventEmitter {
184
184
  entryPoints: [
185
185
  path.resolve(this._pkgPath, "src/main.ts")
186
186
  ],
187
- external: extModules.map((item) => item.name)
187
+ // external: extModules.map((item) => item.name)
188
188
  });
189
189
 
190
190
  const checker = new SdTsCompiler({
@@ -201,7 +201,7 @@ export class SdCliServerBuilder extends EventEmitter {
201
201
  const checkResult = await checker.buildAsync();
202
202
 
203
203
  this._debug(`LINT...`);
204
- const lintResults = await SdLinter.lintAsync(checkResult.affectedFilePaths, checker.program);
204
+ const lintResults = !this._withLint ? [] : await SdLinter.lintAsync(checkResult.affectedFilePaths, checker.program);
205
205
 
206
206
  this._debug(`빌드 완료`);
207
207
  return {
@@ -210,7 +210,7 @@ export class SdCliServerBuilder extends EventEmitter {
210
210
  };
211
211
  }
212
212
 
213
- private async _getExternalModulesAsync(): Promise<{
213
+ /*private async _getExternalModulesAsync(): Promise<{
214
214
  name: string;
215
215
  exists: boolean
216
216
  }[]> {
@@ -245,7 +245,7 @@ export class SdCliServerBuilder extends EventEmitter {
245
245
  continue;
246
246
  }
247
247
 
248
- if (FsUtil.glob(path.resolve(modulePath, "**/binding.gyp")).length > 0) {
248
+ if (FsUtil.glob(path.resolve(modulePath, "**!/binding.gyp")).length > 0) {
249
249
  results.push({
250
250
  name: moduleName,
251
251
  exists: true
@@ -275,7 +275,7 @@ export class SdCliServerBuilder extends EventEmitter {
275
275
  continue;
276
276
  }
277
277
 
278
- if (FsUtil.glob(path.resolve(optModulePath, "**/binding.gyp")).length > 0) {
278
+ if (FsUtil.glob(path.resolve(optModulePath, "**!/binding.gyp")).length > 0) {
279
279
  results.push({
280
280
  name: optModuleName,
281
281
  exists: true
@@ -296,7 +296,7 @@ export class SdCliServerBuilder extends EventEmitter {
296
296
  await fn(this._pkgPath);
297
297
 
298
298
  return results;
299
- }
299
+ }*/
300
300
 
301
301
  private _debug(msg: string): void {
302
302
  this._logger.debug(`[${path.basename(this._pkgPath)}] ${msg}`);
@@ -1,6 +1,6 @@
1
1
  import {FsUtil, Logger, SdFsWatcher} from "@simplysm/sd-core-node";
2
2
  import path from "path";
3
- import {ISdCliBuilderResult, ISdCliConfig} from "../commons";
3
+ import {ISdCliBuilderResult} from "../commons";
4
4
  import {EventEmitter} from "events";
5
5
  import {SdTsCompiler} from "../build-tools/SdTsCompiler";
6
6
  import {SdLinter} from "../build-tools/SdLinter";
@@ -9,8 +9,7 @@ import {FunctionQueue} from "@simplysm/sd-core-common";
9
9
  export class SdCliTsLibBuilder extends EventEmitter {
10
10
  private readonly _logger = Logger.get(["simplysm", "sd-cli", "SdCliTsLibBuilder"]);
11
11
 
12
- public constructor(private readonly _projConf: ISdCliConfig,
13
- private readonly _pkgPath: string,
12
+ public constructor(private readonly _pkgPath: string,
14
13
  private readonly _withLint: boolean) {
15
14
  super();
16
15
  }