@simplysm/sd-cli 12.15.68 → 12.15.69

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 (40) hide show
  1. package/dist/entry/SdCliCapacitor.d.ts +37 -0
  2. package/dist/entry/SdCliCapacitor.js +390 -0
  3. package/dist/pkg-builders/client/SdClientBuildRunner.d.ts +1 -0
  4. package/dist/pkg-builders/client/SdClientBuildRunner.js +14 -0
  5. package/dist/pkg-builders/client/SdNgBundler.js +3 -1
  6. package/dist/sd-cli-entry.js +18 -44
  7. package/dist/types/config/ISdProjectConfig.d.ts +31 -0
  8. package/package.json +5 -5
  9. package/src/entry/SdCliCapacitor.ts +560 -0
  10. package/src/pkg-builders/client/SdClientBuildRunner.ts +17 -0
  11. package/src/pkg-builders/client/SdNgBundler.ts +3 -1
  12. package/src/sd-cli-entry.ts +26 -56
  13. package/src/types/config/ISdProjectConfig.ts +34 -0
  14. package/dist/fix/convertPrivateToHash.d.ts +0 -1
  15. package/dist/fix/convertPrivateToHash.js +0 -58
  16. package/dist/fix/convertSdAngularSymbolNames.d.ts +0 -1
  17. package/dist/fix/convertSdAngularSymbolNames.js +0 -22
  18. package/dist/fix/core/convertSymbols.d.ts +0 -1
  19. package/dist/fix/core/convertSymbols.js +0 -101
  20. package/dist/fix/core/getTsMortphSourceFiles.d.ts +0 -1
  21. package/dist/fix/core/getTsMortphSourceFiles.js +0 -7
  22. package/dist/fix/core/removeSymbols.d.ts +0 -1
  23. package/dist/fix/core/removeSymbols.js +0 -76
  24. package/dist/fix/removeSdAngularSymbolNames.d.ts +0 -1
  25. package/dist/fix/removeSdAngularSymbolNames.js +0 -6
  26. package/dist/fix/removeUnusedImports.d.ts +0 -1
  27. package/dist/fix/removeUnusedImports.js +0 -41
  28. package/dist/fix/removeUnusedInjects.d.ts +0 -1
  29. package/dist/fix/removeUnusedInjects.js +0 -37
  30. package/dist/fix/removeUnusedProtectedReadonly.d.ts +0 -1
  31. package/dist/fix/removeUnusedProtectedReadonly.js +0 -57
  32. package/src/fix/convertPrivateToHash.ts +0 -74
  33. package/src/fix/convertSdAngularSymbolNames.ts +0 -27
  34. package/src/fix/core/convertSymbols.ts +0 -135
  35. package/src/fix/core/getTsMortphSourceFiles.ts +0 -9
  36. package/src/fix/core/removeSymbols.ts +0 -102
  37. package/src/fix/removeSdAngularSymbolNames.ts +0 -9
  38. package/src/fix/removeUnusedImports.ts +0 -50
  39. package/src/fix/removeUnusedInjects.ts +0 -50
  40. package/src/fix/removeUnusedProtectedReadonly.ts +0 -69
@@ -0,0 +1,37 @@
1
+ import { ISdClientBuilderCapacitorConfig } from "../types/config/ISdProjectConfig";
2
+ export declare class SdCliCapacitor {
3
+ private readonly _opt;
4
+ private readonly _CAPACITOR_DIR_NAME;
5
+ private readonly _CONFIG_FILE_NAME;
6
+ private readonly _KEYSTORE_FILE_NAME;
7
+ private readonly _ICON_DIR_PATH;
8
+ private readonly _platforms;
9
+ private readonly _npmConfig;
10
+ constructor(_opt: {
11
+ pkgPath: string;
12
+ config: ISdClientBuilderCapacitorConfig;
13
+ });
14
+ private static readonly _logger;
15
+ private static _execAsync;
16
+ initializeAsync(): Promise<void>;
17
+ private _initializeCapacitorProjectAsync;
18
+ private _syncVersion;
19
+ private _createCapacitorConfig;
20
+ private _managePlatformsAsync;
21
+ private _managePluginsAsync;
22
+ private _setupAndroidSign;
23
+ private _setupIconAndSplashScreenAsync;
24
+ private _configureAndroidNative;
25
+ private _configureAndroidManifest;
26
+ private _configureAndroidBuildGradle;
27
+ private _configureAndroidStrings;
28
+ buildAsync(outPath: string): Promise<void>;
29
+ private _buildPlatformAsync;
30
+ private _buildAndroidAsync;
31
+ private _copyAndroidBuildOutput;
32
+ static runWebviewOnDeviceAsync(opt: {
33
+ platform: string;
34
+ package: string;
35
+ url?: string;
36
+ }): Promise<void>;
37
+ }
@@ -0,0 +1,390 @@
1
+ import * as path from "path";
2
+ import { FsUtils, PathUtils, SdLogger, SdProcess } from "@simplysm/sd-core-node";
3
+ import { StringUtils, typescript } from "@simplysm/sd-core-common";
4
+ export class SdCliCapacitor {
5
+ constructor(_opt) {
6
+ this._opt = _opt;
7
+ // 상수 정의
8
+ this._CAPACITOR_DIR_NAME = ".capacitor";
9
+ this._CONFIG_FILE_NAME = "capacitor.config.ts";
10
+ this._KEYSTORE_FILE_NAME = "android.keystore";
11
+ this._ICON_DIR_PATH = "resources";
12
+ this._platforms = Object.keys(this._opt.config.platform ?? { android: {} });
13
+ this._npmConfig = FsUtils.readJson(path.resolve(this._opt.pkgPath, "package.json"));
14
+ }
15
+ static { this._logger = SdLogger.get(["simplysm", "sd-cli", "SdCliCapacitor"]); }
16
+ static async _execAsync(cmd, args, cwd) {
17
+ this._logger.debug(`실행 명령: ${cmd + " " + args.join(" ")}`);
18
+ const msg = await SdProcess.spawnAsync(cmd, args, { cwd });
19
+ this._logger.debug(`실행 결과: ${msg}`);
20
+ }
21
+ async initializeAsync() {
22
+ const capacitorPath = path.resolve(this._opt.pkgPath, this._CAPACITOR_DIR_NAME);
23
+ // 1. Capacitor 프로젝트 초기화
24
+ await this._initializeCapacitorProjectAsync(capacitorPath);
25
+ // 2. Capacitor 설정 파일 생성
26
+ this._createCapacitorConfig(capacitorPath);
27
+ // 3. 플랫폼 관리
28
+ await this._managePlatformsAsync(capacitorPath);
29
+ // 4. 플러그인 관리
30
+ await this._managePluginsAsync(capacitorPath);
31
+ // 5. 안드로이드 서명 설정
32
+ this._setupAndroidSign(capacitorPath);
33
+ // 6. 아이콘 및 스플래시 스크린 설정
34
+ await this._setupIconAndSplashScreenAsync(capacitorPath);
35
+ // 7. Android 네이티브 설정 (AndroidManifest.xml, build.gradle 등)
36
+ if (this._platforms.includes("android")) {
37
+ this._configureAndroidNative(capacitorPath);
38
+ }
39
+ // 8. 웹 자산 동기화
40
+ await SdCliCapacitor._execAsync("npx", ["cap", "sync"], capacitorPath);
41
+ }
42
+ // 1. Capacitor 프로젝트 초기화
43
+ async _initializeCapacitorProjectAsync(capacitorPath) {
44
+ if (FsUtils.exists(capacitorPath)) {
45
+ SdCliCapacitor._logger.log("이미 생성되어있는 '.capacitor'를 사용합니다.");
46
+ // 버전 동기화
47
+ this._syncVersion(capacitorPath);
48
+ }
49
+ else {
50
+ FsUtils.mkdirs(capacitorPath);
51
+ // package.json 생성
52
+ const pkgJson = {
53
+ name: this._opt.config.appId,
54
+ version: this._npmConfig.version,
55
+ private: true,
56
+ dependencies: {
57
+ "@capacitor/core": "^7.0.0",
58
+ },
59
+ devDependencies: {
60
+ "@capacitor/cli": "^7.0.0",
61
+ "@capacitor/assets": "^3.0.0",
62
+ },
63
+ };
64
+ FsUtils.writeJson(path.resolve(capacitorPath, "package.json"), pkgJson, { space: 2 });
65
+ // yarn install
66
+ await SdCliCapacitor._execAsync("yarn", ["install"], capacitorPath);
67
+ // capacitor init
68
+ await SdCliCapacitor._execAsync("npx", ["cap", "init", this._opt.config.appName, this._opt.config.appId], capacitorPath);
69
+ }
70
+ }
71
+ // 버전 동기화
72
+ _syncVersion(capacitorPath) {
73
+ const pkgJsonPath = path.resolve(capacitorPath, "package.json");
74
+ if (FsUtils.exists(pkgJsonPath)) {
75
+ const pkgJson = FsUtils.readJson(pkgJsonPath);
76
+ if (pkgJson.version !== this._npmConfig.version) {
77
+ pkgJson.version = this._npmConfig.version;
78
+ FsUtils.writeJson(pkgJsonPath, pkgJson, { space: 2 });
79
+ SdCliCapacitor._logger.log(`버전 동기화: ${this._npmConfig.version}`);
80
+ }
81
+ }
82
+ }
83
+ // 2. Capacitor 설정 파일 생성
84
+ _createCapacitorConfig(capacitorPath) {
85
+ const configFilePath = path.resolve(capacitorPath, this._CONFIG_FILE_NAME);
86
+ // 플러그인 옵션 생성
87
+ const pluginOptions = {};
88
+ for (const [pluginName, options] of Object.entries(this._opt.config.plugins ?? {})) {
89
+ if (options !== true) {
90
+ // @capacitor/splash-screen → SplashScreen 형태로 변환
91
+ const configKey = StringUtils.toPascalCase(pluginName.split("/").last());
92
+ pluginOptions[configKey] = options;
93
+ }
94
+ }
95
+ const pluginsConfigStr = Object.keys(pluginOptions).length > 0
96
+ ? JSON.stringify(pluginOptions, null, 4).replace(/^/gm, " ").trim()
97
+ : "{}";
98
+ const configContent = typescript `
99
+ import type { CapacitorConfig } from "@capacitor/cli";
100
+
101
+ const config: CapacitorConfig = {
102
+ appId: "${this._opt.config.appId}",
103
+ appName: "${this._opt.config.appName}",
104
+ server: {
105
+ androidScheme: "http",
106
+ cleartext: true,
107
+ allowNavigation: ["*"],
108
+ },
109
+ android: {
110
+ allowMixedContent: true,
111
+ },
112
+ plugins: ${pluginsConfigStr},
113
+ };
114
+
115
+ export default config;
116
+ `;
117
+ FsUtils.writeFile(configFilePath, configContent);
118
+ }
119
+ // 3. 플랫폼 관리
120
+ async _managePlatformsAsync(capacitorPath) {
121
+ for (const platform of this._platforms) {
122
+ if (FsUtils.exists(path.resolve(capacitorPath, platform)))
123
+ continue;
124
+ await SdCliCapacitor._execAsync("npx", ["cap", "add", platform], capacitorPath);
125
+ }
126
+ }
127
+ // 4. 플러그인 관리
128
+ async _managePluginsAsync(capacitorPath) {
129
+ const pkgJsonPath = path.resolve(capacitorPath, "package.json");
130
+ const pkgJson = FsUtils.readJson(pkgJsonPath);
131
+ const currentDeps = Object.keys(pkgJson.dependencies ?? {});
132
+ const usePlugins = Object.keys(this._opt.config.plugins ?? {});
133
+ // 사용하지 않는 플러그인 제거
134
+ for (const dep of currentDeps) {
135
+ // @capacitor/core, @capacitor/android 등 기본 패키지는 제외
136
+ if (dep.startsWith("@capacitor/") &&
137
+ ["core", "android", "ios"].some((p) => dep.endsWith(p))) {
138
+ continue;
139
+ }
140
+ // 플러그인 목록에 없는 패키지는 제거
141
+ if (!usePlugins.includes(dep)) {
142
+ // Capacitor 관련 플러그인만 제거
143
+ if (dep.startsWith("@capacitor/") || dep.includes("capacitor-plugin")) {
144
+ try {
145
+ await SdCliCapacitor._execAsync("yarn", ["remove", dep], capacitorPath);
146
+ SdCliCapacitor._logger.log(`플러그인 제거: ${dep}`);
147
+ }
148
+ catch {
149
+ SdCliCapacitor._logger.warn(`플러그인 제거 실패: ${dep}`);
150
+ }
151
+ }
152
+ }
153
+ }
154
+ // 새 플러그인 설치
155
+ for (const plugin of usePlugins) {
156
+ if (!currentDeps.includes(plugin)) {
157
+ try {
158
+ await SdCliCapacitor._execAsync("yarn", ["add", plugin], capacitorPath);
159
+ SdCliCapacitor._logger.log(`플러그인 설치: ${plugin}`);
160
+ }
161
+ catch {
162
+ SdCliCapacitor._logger.warn(`플러그인 설치 실패: ${plugin}`);
163
+ }
164
+ }
165
+ }
166
+ }
167
+ // 5. 안드로이드 서명 설정
168
+ _setupAndroidSign(capacitorPath) {
169
+ const keystorePath = path.resolve(capacitorPath, this._KEYSTORE_FILE_NAME);
170
+ if (this._opt.config.platform?.android?.sign) {
171
+ FsUtils.copy(path.resolve(this._opt.pkgPath, this._opt.config.platform.android.sign.keystore), keystorePath);
172
+ }
173
+ else {
174
+ FsUtils.remove(keystorePath);
175
+ }
176
+ }
177
+ // 6. 아이콘 및 스플래시 스크린 설정
178
+ async _setupIconAndSplashScreenAsync(capacitorPath) {
179
+ const iconDirPath = path.resolve(capacitorPath, this._ICON_DIR_PATH);
180
+ // ICON 파일 복사
181
+ if (this._opt.config.icon != null) {
182
+ FsUtils.mkdirs(iconDirPath);
183
+ const iconSource = path.resolve(this._opt.pkgPath, this._opt.config.icon);
184
+ // icon.png, splash.png 둘 다 같은 파일 사용
185
+ FsUtils.copy(iconSource, path.resolve(iconDirPath, "icon.png"));
186
+ FsUtils.copy(iconSource, path.resolve(iconDirPath, "splash.png"));
187
+ // @capacitor/assets로 아이콘/스플래시 리사이징
188
+ try {
189
+ await SdCliCapacitor._execAsync("npx", ["@capacitor/assets", "generate", "--android"], capacitorPath);
190
+ }
191
+ catch {
192
+ SdCliCapacitor._logger.warn("아이콘 리사이징 실패, 기본 아이콘 사용");
193
+ }
194
+ }
195
+ else {
196
+ FsUtils.remove(iconDirPath);
197
+ }
198
+ }
199
+ // 7. Android 네이티브 설정
200
+ _configureAndroidNative(capacitorPath) {
201
+ const androidPath = path.resolve(capacitorPath, "android");
202
+ if (!FsUtils.exists(androidPath)) {
203
+ return;
204
+ }
205
+ // AndroidManifest.xml 수정
206
+ this._configureAndroidManifest(androidPath);
207
+ // build.gradle 수정 (필요시)
208
+ this._configureAndroidBuildGradle(androidPath);
209
+ // strings.xml 앱 이름 수정
210
+ this._configureAndroidStrings(androidPath);
211
+ }
212
+ _configureAndroidManifest(androidPath) {
213
+ const manifestPath = path.resolve(androidPath, "app/src/main/AndroidManifest.xml");
214
+ if (!FsUtils.exists(manifestPath)) {
215
+ return;
216
+ }
217
+ let manifestContent = FsUtils.readFile(manifestPath);
218
+ // usesCleartextTraffic 설정
219
+ if (!manifestContent.includes("android:usesCleartextTraffic")) {
220
+ manifestContent = manifestContent.replace("<application", '<application android:usesCleartextTraffic="true"');
221
+ }
222
+ // 추가 권한 설정
223
+ const permissions = this._opt.config.platform?.android?.permissions ?? [];
224
+ for (const perm of permissions) {
225
+ const permTag = `<uses-permission android:name="android.permission.${perm.name}"`;
226
+ if (!manifestContent.includes(permTag)) {
227
+ const maxSdkAttr = perm.maxSdkVersion != null ? ` android:maxSdkVersion="${perm.maxSdkVersion}"` : "";
228
+ const ignoreAttr = perm.ignore != null ? ` tools:ignore="${perm.ignore}"` : "";
229
+ const permLine = ` ${permTag}${maxSdkAttr}${ignoreAttr} />\n`;
230
+ // tools 네임스페이스 추가
231
+ if (perm.ignore != null && !manifestContent.includes("xmlns:tools=")) {
232
+ manifestContent = manifestContent.replace("<manifest xmlns:android", '<manifest xmlns:tools="http://schemas.android.com/tools" xmlns:android');
233
+ }
234
+ manifestContent = manifestContent.replace("</manifest>", `${permLine}</manifest>`);
235
+ }
236
+ }
237
+ // 추가 application 설정
238
+ const appConfig = this._opt.config.platform?.android?.config;
239
+ if (appConfig) {
240
+ for (const [key, value] of Object.entries(appConfig)) {
241
+ const attr = `android:${key}="${value}"`;
242
+ if (!manifestContent.includes(`android:${key}=`)) {
243
+ manifestContent = manifestContent.replace("<application", `<application ${attr}`);
244
+ }
245
+ }
246
+ }
247
+ FsUtils.writeFile(manifestPath, manifestContent);
248
+ }
249
+ _configureAndroidBuildGradle(androidPath) {
250
+ const buildGradlePath = path.resolve(androidPath, "app/build.gradle");
251
+ if (!FsUtils.exists(buildGradlePath)) {
252
+ return;
253
+ }
254
+ let gradleContent = FsUtils.readFile(buildGradlePath);
255
+ // versionName, versionCode 설정
256
+ const version = this._npmConfig.version;
257
+ const versionParts = version.split(".");
258
+ const versionCode = parseInt(versionParts[0] ?? "0") * 10000 +
259
+ parseInt(versionParts[1] ?? "0") * 100 +
260
+ parseInt(versionParts[2] ?? "0");
261
+ gradleContent = gradleContent.replace(/versionCode \d+/, `versionCode ${versionCode}`);
262
+ gradleContent = gradleContent.replace(/versionName "[^"]+"/, `versionName "${version}"`);
263
+ // SDK 버전 설정
264
+ if (this._opt.config.platform?.android?.sdkVersion != null) {
265
+ const sdkVersion = this._opt.config.platform.android.sdkVersion;
266
+ gradleContent = gradleContent.replace(/minSdkVersion \d+/, `minSdkVersion ${sdkVersion}`);
267
+ gradleContent = gradleContent.replace(/targetSdkVersion \d+/, `targetSdkVersion ${sdkVersion}`);
268
+ }
269
+ // Signing 설정
270
+ const signConfig = this._opt.config.platform?.android?.sign;
271
+ if (signConfig) {
272
+ const keystoreRelativePath = `../${this._KEYSTORE_FILE_NAME}`;
273
+ const keystoreType = signConfig.keystoreType ?? "jks";
274
+ // signingConfigs 블록 추가
275
+ if (!gradleContent.includes("signingConfigs")) {
276
+ const signingConfigsBlock = `
277
+ signingConfigs {
278
+ release {
279
+ storeFile file("${keystoreRelativePath}")
280
+ storePassword "${signConfig.storePassword}"
281
+ keyAlias "${signConfig.alias}"
282
+ keyPassword "${signConfig.password}"
283
+ storeType "${keystoreType}"
284
+ }
285
+ }
286
+ `;
287
+ // android { 블록 내부에 추가
288
+ gradleContent = gradleContent.replace(/(android\s*\{)/, `$1${signingConfigsBlock}`);
289
+ }
290
+ // buildTypes.release에 signingConfig 추가
291
+ if (!gradleContent.includes("signingConfig signingConfigs.release")) {
292
+ gradleContent = gradleContent.replace(/(buildTypes\s*\{[\s\S]*?release\s*\{)/, `$1\n signingConfig signingConfigs.release`);
293
+ }
294
+ }
295
+ FsUtils.writeFile(buildGradlePath, gradleContent);
296
+ }
297
+ _configureAndroidStrings(androidPath) {
298
+ const stringsPath = path.resolve(androidPath, "app/src/main/res/values/strings.xml");
299
+ if (!FsUtils.exists(stringsPath)) {
300
+ return;
301
+ }
302
+ let stringsContent = FsUtils.readFile(stringsPath);
303
+ stringsContent = stringsContent.replace(/<string name="app_name">[^<]+<\/string>/, `<string name="app_name">${this._opt.config.appName}</string>`);
304
+ stringsContent = stringsContent.replace(/<string name="title_activity_main">[^<]+<\/string>/, `<string name="title_activity_main">${this._opt.config.appName}</string>`);
305
+ stringsContent = stringsContent.replace(/<string name="package_name">[^<]+<\/string>/, `<string name="package_name">${this._opt.config.appId}</string>`);
306
+ stringsContent = stringsContent.replace(/<string name="custom_url_scheme">[^<]+<\/string>/, `<string name="custom_url_scheme">${this._opt.config.appId}</string>`);
307
+ FsUtils.writeFile(stringsPath, stringsContent);
308
+ }
309
+ async buildAsync(outPath) {
310
+ const capacitorPath = path.resolve(this._opt.pkgPath, this._CAPACITOR_DIR_NAME);
311
+ const buildType = this._opt.config.debug ? "debug" : "release";
312
+ // 웹 자산 동기화
313
+ await SdCliCapacitor._execAsync("npx", ["cap", "sync"], capacitorPath);
314
+ // 플랫폼별 빌드
315
+ await Promise.all(this._platforms.map((platform) => this._buildPlatformAsync(capacitorPath, outPath, platform, buildType)));
316
+ }
317
+ async _buildPlatformAsync(capacitorPath, outPath, platform, buildType) {
318
+ if (platform === "android") {
319
+ await this._buildAndroidAsync(capacitorPath, outPath, buildType);
320
+ }
321
+ // iOS 지원 시 추가
322
+ }
323
+ async _buildAndroidAsync(capacitorPath, outPath, buildType) {
324
+ const androidPath = path.resolve(capacitorPath, "android");
325
+ const targetOutPath = path.resolve(outPath, "android");
326
+ // Gradle wrapper로 빌드
327
+ const isBundle = this._opt.config.platform?.android?.bundle;
328
+ const gradleTask = buildType === "release" ? (isBundle ? "bundleRelease" : "assembleRelease") : "assembleDebug";
329
+ // gradlew 실행 권한 부여 (Linux/Mac)
330
+ const gradlewPath = path.resolve(androidPath, "gradlew");
331
+ if (FsUtils.exists(gradlewPath)) {
332
+ try {
333
+ await SdCliCapacitor._execAsync("chmod", ["+x", "gradlew"], androidPath);
334
+ }
335
+ catch {
336
+ // Windows에서는 무시
337
+ }
338
+ }
339
+ // Gradle 빌드 실행
340
+ const gradleCmd = process.platform === "win32" ? "gradlew.bat" : "./gradlew";
341
+ await SdCliCapacitor._execAsync(gradleCmd, [gradleTask, "--no-daemon"], androidPath);
342
+ // 빌드 결과물 복사
343
+ this._copyAndroidBuildOutput(androidPath, targetOutPath, buildType);
344
+ }
345
+ _copyAndroidBuildOutput(androidPath, targetOutPath, buildType) {
346
+ const isBundle = this._opt.config.platform?.android?.bundle;
347
+ const isSigned = !!this._opt.config.platform?.android?.sign;
348
+ const ext = isBundle ? "aab" : "apk";
349
+ const outputType = isBundle ? "bundle" : "apk";
350
+ const fileName = isSigned ? `app-${buildType}.${ext}` : `app-${buildType}-unsigned.${ext}`;
351
+ const sourcePath = path.resolve(androidPath, "app/build/outputs", outputType, buildType, fileName);
352
+ const actualPath = FsUtils.exists(sourcePath)
353
+ ? sourcePath
354
+ : path.resolve(androidPath, "app/build/outputs", outputType, buildType, `app-${buildType}.${ext}`);
355
+ if (!FsUtils.exists(actualPath)) {
356
+ SdCliCapacitor._logger.warn(`빌드 결과물을 찾을 수 없습니다: ${actualPath}`);
357
+ return;
358
+ }
359
+ const outputFileName = `${this._opt.config.appName}${isSigned ? "" : "-unsigned"}-latest.${ext}`;
360
+ FsUtils.mkdirs(targetOutPath);
361
+ FsUtils.copy(actualPath, path.resolve(targetOutPath, outputFileName));
362
+ const updatesPath = path.resolve(targetOutPath, "updates");
363
+ FsUtils.mkdirs(updatesPath);
364
+ FsUtils.copy(actualPath, path.resolve(updatesPath, `${this._npmConfig.version}.${ext}`));
365
+ }
366
+ static async runWebviewOnDeviceAsync(opt) {
367
+ const projNpmConf = FsUtils.readJson(path.resolve(process.cwd(), "package.json"));
368
+ const allPkgPaths = projNpmConf.workspaces.mapMany((item) => FsUtils.glob(PathUtils.posix(process.cwd(), item)));
369
+ const capacitorPath = path.resolve(allPkgPaths.single((item) => item.endsWith(opt.package)), ".capacitor");
370
+ if (opt.url !== undefined) {
371
+ // capacitor.config.ts의 server.url 설정 업데이트
372
+ const configPath = path.resolve(capacitorPath, "capacitor.config.ts");
373
+ if (FsUtils.exists(configPath)) {
374
+ let configContent = FsUtils.readFile(configPath);
375
+ const serverUrl = `${opt.url.replace(/\/$/, "")}/${opt.package}/capacitor/`;
376
+ // 기존 url 설정이 있으면 교체, 없으면 server 블록 첫 줄에 추가
377
+ if (configContent.includes("url:")) {
378
+ configContent = configContent.replace(/url:\s*"[^"]*"/, `url: "${serverUrl}"`);
379
+ }
380
+ else if (configContent.includes("server:")) {
381
+ configContent = configContent.replace(/server:\s*\{/, `server: {\n url: "${serverUrl}",`);
382
+ }
383
+ FsUtils.writeFile(configPath, configContent);
384
+ }
385
+ }
386
+ // cap sync 후 run
387
+ await this._execAsync("npx", ["cap", "sync", opt.platform], capacitorPath);
388
+ await this._execAsync("npx", ["cap", "run", opt.platform, "--target", "device"], capacitorPath);
389
+ }
390
+ }
@@ -5,5 +5,6 @@ export declare class SdClientBuildRunner extends SdBuildRunnerBase<"client"> {
5
5
  protected _logger: SdLogger;
6
6
  private _ngBundlers?;
7
7
  private _cordova?;
8
+ private _capacitor?;
8
9
  protected _runAsync(modifiedFileSet?: Set<TNormPath>): Promise<ISdBuildResult>;
9
10
  }
@@ -5,6 +5,7 @@ import { SdNgBundler } from "./SdNgBundler";
5
5
  import { SdCliCordova } from "../../entry/SdCliCordova";
6
6
  import { SdCliNgRoutesFileGenerator } from "./SdCliNgRoutesFileGenerator";
7
7
  import { SdCliElectron } from "../../entry/SdCliElectron";
8
+ import { SdCliCapacitor } from "../../entry/SdCliCapacitor";
8
9
  export class SdClientBuildRunner extends SdBuildRunnerBase {
9
10
  constructor() {
10
11
  super(...arguments);
@@ -27,6 +28,15 @@ export class SdClientBuildRunner extends SdBuildRunnerBase {
27
28
  });
28
29
  await this._cordova.initializeAsync();
29
30
  }
31
+ // capacitor
32
+ if (this._pkgConf.builder?.capacitor) {
33
+ this._debug("Preparing Capacitor...");
34
+ this._capacitor = new SdCliCapacitor({
35
+ pkgPath: this._opt.pkgPath,
36
+ config: this._pkgConf.builder.capacitor,
37
+ });
38
+ await this._capacitor.initializeAsync();
39
+ }
30
40
  // routes
31
41
  const npmConf = (await FsUtils.readJsonAsync(path.resolve(this._opt.pkgPath, "package.json")));
32
42
  if ("@angular/router" in (npmConf.dependencies ?? {})) {
@@ -80,6 +90,10 @@ export class SdClientBuildRunner extends SdBuildRunnerBase {
80
90
  this._debug("Building Cordova...");
81
91
  await this._cordova.buildAsync(path.resolve(this._opt.pkgPath, "dist"));
82
92
  }
93
+ if (!this._opt.watch?.dev && this._capacitor) {
94
+ this._debug("Building Capacitor...");
95
+ await this._capacitor.buildAsync(path.resolve(this._opt.pkgPath, "dist"));
96
+ }
83
97
  if (!this._opt.watch?.dev && this._pkgConf.builder?.electron) {
84
98
  this._debug("Bulding Electron...");
85
99
  await SdCliElectron.buildAsync({
@@ -54,7 +54,9 @@ export class SdNgBundler {
54
54
  ? PathUtils.norm(this._opt.pkgPath, ".electron/src")
55
55
  : this._conf.builderType === "cordova" && !this._opt.watch?.dev
56
56
  ? PathUtils.norm(this._opt.pkgPath, ".cordova/www")
57
- : PathUtils.norm(this._opt.pkgPath, "dist", this._conf.builderType);
57
+ : this._conf.builderType === "capacitor" && !this._opt.watch?.dev
58
+ ? PathUtils.norm(this._opt.pkgPath, ".capacitor/www")
59
+ : PathUtils.norm(this._opt.pkgPath, "dist", this._conf.builderType);
58
60
  }
59
61
  markForChanges(filePaths) {
60
62
  for (const filePath of filePaths) {
@@ -10,12 +10,7 @@ import { SdCliElectron } from "./entry/SdCliElectron";
10
10
  import { SdCliLocalUpdate } from "./entry/SdCliLocalUpdate";
11
11
  import { SdCliPostInstall } from "./entry/SdCliPostInstall";
12
12
  import { SdCliProject } from "./entry/SdCliProject";
13
- import convertPrivateToHash from "./fix/convertPrivateToHash";
14
- import removeSdAngularSymbolNames from "./fix/removeSdAngularSymbolNames";
15
- import convertSdAngularSymbolNames from "./fix/convertSdAngularSymbolNames";
16
- import { removeUnusedInjects } from "./fix/removeUnusedInjects";
17
- import removeUnusedProtectedReadonly from "./fix/removeUnusedProtectedReadonly";
18
- import { removeUnusedImports } from "./fix/removeUnusedImports";
13
+ import { SdCliCapacitor } from "./entry/SdCliCapacitor";
19
14
  Error.stackTraceLimit = Infinity;
20
15
  EventEmitter.defaultMaxListeners = 0;
21
16
  await yargs(hideBin(process.argv))
@@ -199,48 +194,27 @@ await yargs(hideBin(process.argv))
199
194
  describe: "Webview로 오픈할 URL",
200
195
  demandOption: true,
201
196
  }), async (argv) => await SdCliCordova.runWebviewOnDeviceAsync(argv))
202
- .command("commit", "AI를 통해 변경사항에 대한 커밋 메시지를 작성하여, 커밋 및 푸쉬를 수행합니다.", (cmd) => cmd.version(false).hide("help").hide("debug"), async () => await SdCliAiCommand.commitAsync())
203
- .command("postinstall", "설치후 자동실행할 작업", (cmd) => cmd.version(false).hide("help").hide("debug"), () => SdCliPostInstall.run())
204
- .command("fix", "가능한 내용 자동 수정", (cmd) => cmd
197
+ .command("run-capacitor <platform> <package> [url]", "변경감지중인 플랫폼을 Capacitor 디바이스에 형태로 띄웁니다.", (cmd) => cmd
205
198
  .version(false)
206
199
  .hide("help")
207
200
  .hide("debug")
208
- .options({
209
- library: {
210
- type: "boolean",
211
- describe: "simplysm 라이브러리 픽스",
212
- default: false,
213
- },
214
- }), (argv) => {
215
- // GIT 사용중일 경우, 커밋되지 않은 수정사항이 있는지 확인
216
- /*if (FsUtils.exists(path.resolve(process.cwd(), ".git"))) {
217
- const gitStatusResult = await SdProcess.spawnAsync("git status");
218
- if (gitStatusResult.includes("Changes") || gitStatusResult.includes("Untracked")) {
219
- throw new Error("커밋되지 않은 정보가 있습니다. FIX오류시 롤백이 불가능하므로, 미리 커밋을 해놔야 합니다.\n" + gitStatusResult);
220
- }
221
- }*/
222
- convertPrivateToHash();
223
- //-- 심볼정리
224
- removeSdAngularSymbolNames();
225
- convertSdAngularSymbolNames();
226
- //-- inject/import 정리
227
- removeUnusedInjects();
228
- removeUnusedProtectedReadonly();
229
- removeUnusedImports();
230
- if (argv.library)
231
- return;
232
- // convertSdSheetBindingsSafely();
233
- // convertSetupCumulateSelectedKeysToObjectParam();
234
- // convertExtendsSdModalBaseToInterface();
235
- // convertModalShowParams();
236
- // convertExtendsSdPrintTemplateBaseToInterface();
237
- // convertPrintParams();
238
- // convertToUsePermsSignal();
239
- // convertGetMenusToUsableMenus();
240
- // convertFlatPagesToUsableFlatMenus();
241
- // convertSdIconToFaIcon();
242
- // convertSelectModalButtonToSelectButton();
201
+ .positional("platform", {
202
+ type: "string",
203
+ describe: "빌드 플랫폼(android,...)",
204
+ demandOption: true,
243
205
  })
206
+ .positional("package", {
207
+ type: "string",
208
+ describe: "패키지명",
209
+ demandOption: true,
210
+ })
211
+ .positional("url", {
212
+ type: "string",
213
+ describe: "Webview로 오픈할 URL",
214
+ demandOption: true,
215
+ }), async (argv) => await SdCliCapacitor.runWebviewOnDeviceAsync(argv))
216
+ .command("commit", "AI를 통해 변경사항에 대한 커밋 메시지를 작성하여, 커밋 및 푸쉬를 수행합니다.", (cmd) => cmd.version(false).hide("help").hide("debug"), async () => await SdCliAiCommand.commitAsync())
217
+ .command("postinstall", "설치후 자동실행할 작업", (cmd) => cmd.version(false).hide("help").hide("debug"), () => SdCliPostInstall.run())
244
218
  .strict()
245
219
  .recommendCommands()
246
220
  .fail((msg, err, cmd) => {
@@ -45,6 +45,8 @@ export interface ISdClientPackageConfig {
45
45
  builder?: {
46
46
  web?: ISdClientBuilderWebConfig;
47
47
  electron?: ISdClientBuilderElectronConfig;
48
+ capacitor?: ISdClientBuilderCapacitorConfig;
49
+ /** @deprecated */
48
50
  cordova?: ISdClientBuilderCordovaConfig;
49
51
  };
50
52
  }
@@ -72,6 +74,7 @@ export interface ISdClientBuilderElectronConfig {
72
74
  export interface ISdClientBuilderWebConfig {
73
75
  env?: Record<string, string>;
74
76
  }
77
+ /** @deprecated */
75
78
  export interface ISdClientBuilderCordovaConfig {
76
79
  appId: string;
77
80
  appName: string;
@@ -101,6 +104,34 @@ export interface ISdClientBuilderCordovaConfig {
101
104
  env?: Record<string, string>;
102
105
  browserslist?: string[];
103
106
  }
107
+ export interface ISdClientBuilderCapacitorConfig {
108
+ appId: string;
109
+ appName: string;
110
+ plugins?: Record<string, Record<string, unknown> | true>;
111
+ icon?: string;
112
+ debug?: boolean;
113
+ platform?: {
114
+ android?: {
115
+ config?: Record<string, string>;
116
+ bundle?: boolean;
117
+ sign?: {
118
+ keystore: string;
119
+ storePassword: string;
120
+ alias: string;
121
+ password: string;
122
+ keystoreType?: string;
123
+ };
124
+ sdkVersion?: number;
125
+ permissions?: {
126
+ name: string;
127
+ maxSdkVersion?: number;
128
+ ignore?: string;
129
+ }[];
130
+ };
131
+ };
132
+ env?: Record<string, string>;
133
+ browserslist?: string[];
134
+ }
104
135
  export type TSdPostPublishConfig = ISdPostPublishScriptConfig;
105
136
  export interface ISdPostPublishScriptConfig {
106
137
  type: "script";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplysm/sd-cli",
3
- "version": "12.15.68",
3
+ "version": "12.15.69",
4
4
  "description": "심플리즘 패키지 - CLI",
5
5
  "author": "김석래",
6
6
  "repository": {
@@ -17,10 +17,10 @@
17
17
  "@angular/compiler-cli": "^20.3.15",
18
18
  "@anthropic-ai/sdk": "^0.71.2",
19
19
  "@electron/rebuild": "^4.0.2",
20
- "@simplysm/sd-core-common": "12.15.68",
21
- "@simplysm/sd-core-node": "12.15.68",
22
- "@simplysm/sd-service-server": "12.15.68",
23
- "@simplysm/sd-storage": "12.15.68",
20
+ "@simplysm/sd-core-common": "12.15.69",
21
+ "@simplysm/sd-core-node": "12.15.69",
22
+ "@simplysm/sd-service-server": "12.15.69",
23
+ "@simplysm/sd-storage": "12.15.69",
24
24
  "browserslist": "^4.28.1",
25
25
  "cordova": "^13.0.0",
26
26
  "electron": "^33.4.11",