@simplysm/sd-cli 12.16.7 → 12.16.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.
- package/dist/entry/SdCliCapacitor.d.ts +3 -1
- package/dist/entry/SdCliCapacitor.js +127 -81
- package/dist/pkg-builders/lib/SdCliIndexFileGenerator.js +9 -7
- package/dist/pkg-builders/server/SdServerBuildRunner.js +1 -1
- package/dist/ts-compiler/SdTsCompiler.js +1 -1
- package/package.json +6 -6
- package/src/entry/SdCliCapacitor.ts +156 -103
- package/src/pkg-builders/lib/SdCliIndexFileGenerator.ts +10 -8
- package/src/pkg-builders/server/SdServerBuildRunner.ts +1 -1
- package/src/ts-compiler/SdTsCompiler.ts +1 -1
|
@@ -19,9 +19,11 @@ export declare class SdCliCapacitor {
|
|
|
19
19
|
private _createCapacitorConfigAsync;
|
|
20
20
|
private _managePlatformsAsync;
|
|
21
21
|
private _managePluginsAsync;
|
|
22
|
+
private _isCapacitorPlugin;
|
|
22
23
|
private _setupAndroidSignAsync;
|
|
23
24
|
private _setupIconAndSplashScreenAsync;
|
|
24
|
-
private
|
|
25
|
+
private _createCenteredImageAsync;
|
|
26
|
+
private _cleanupExistingIconsAsync;
|
|
25
27
|
private _configureAndroidNativeAsync;
|
|
26
28
|
private _configureAndroidStylesAsync;
|
|
27
29
|
private _configureAndroidGradlePropertiesAsync;
|
|
@@ -22,13 +22,13 @@ export class SdCliCapacitor {
|
|
|
22
22
|
async initializeAsync() {
|
|
23
23
|
const capacitorPath = path.resolve(this._opt.pkgPath, this._CAPACITOR_DIR_NAME);
|
|
24
24
|
// 1. Capacitor 프로젝트 초기화
|
|
25
|
-
await this._initializeCapacitorProjectAsync(capacitorPath);
|
|
25
|
+
const isNewProject = await this._initializeCapacitorProjectAsync(capacitorPath);
|
|
26
26
|
// 2. Capacitor 설정 파일 생성
|
|
27
27
|
await this._createCapacitorConfigAsync(capacitorPath);
|
|
28
28
|
// 3. 플랫폼 관리
|
|
29
29
|
await this._managePlatformsAsync(capacitorPath);
|
|
30
30
|
// 4. 플러그인 관리
|
|
31
|
-
await this._managePluginsAsync(capacitorPath);
|
|
31
|
+
const pluginsChanged = await this._managePluginsAsync(capacitorPath);
|
|
32
32
|
// 5. 안드로이드 서명 설정
|
|
33
33
|
await this._setupAndroidSignAsync(capacitorPath);
|
|
34
34
|
// 6. 아이콘 및 스플래시 스크린 설정
|
|
@@ -38,7 +38,12 @@ export class SdCliCapacitor {
|
|
|
38
38
|
await this._configureAndroidNativeAsync(capacitorPath);
|
|
39
39
|
}
|
|
40
40
|
// 8. 웹 자산 동기화
|
|
41
|
-
|
|
41
|
+
if (isNewProject || pluginsChanged) {
|
|
42
|
+
await SdCliCapacitor._execAsync("npx", ["cap", "sync"], capacitorPath);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
await SdCliCapacitor._execAsync("npx", ["cap", "copy"], capacitorPath);
|
|
46
|
+
}
|
|
42
47
|
}
|
|
43
48
|
// 1. Capacitor 프로젝트 초기화
|
|
44
49
|
async _initializeCapacitorProjectAsync(capacitorPath) {
|
|
@@ -46,40 +51,41 @@ export class SdCliCapacitor {
|
|
|
46
51
|
SdCliCapacitor._logger.log("이미 생성되어있는 '.capacitor'를 사용합니다.");
|
|
47
52
|
// 버전 동기화
|
|
48
53
|
await this._syncVersionAsync(capacitorPath);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
},
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
await FsUtils.mkdirsAsync(capacitorPath);
|
|
57
|
+
// package.json 생성
|
|
58
|
+
const projNpmConfig = await FsUtils.readJsonAsync(path.resolve(this._opt.pkgPath, "../../package.json"));
|
|
59
|
+
const pkgJson = {
|
|
60
|
+
name: this._opt.config.appId,
|
|
61
|
+
version: this._npmConfig.version,
|
|
62
|
+
private: true,
|
|
63
|
+
volta: projNpmConfig.volta,
|
|
64
|
+
dependencies: {
|
|
65
|
+
"@capacitor/core": "^7.0.0",
|
|
66
|
+
"@capacitor/app": "^7.0.0",
|
|
67
|
+
},
|
|
68
|
+
devDependencies: {
|
|
69
|
+
"@capacitor/cli": "^7.0.0",
|
|
70
|
+
"@capacitor/assets": "^3.0.0",
|
|
71
|
+
...this._platforms.toObject((item) => `@capacitor/${item}`, () => "^7.0.0"),
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
await FsUtils.writeJsonAsync(path.resolve(capacitorPath, "package.json"), pkgJson, {
|
|
75
|
+
space: 2,
|
|
76
|
+
});
|
|
77
|
+
// .yarnrc.yml 작성
|
|
78
|
+
await FsUtils.writeFileAsync(path.resolve(capacitorPath, ".yarnrc.yml"), "nodeLinker: node-modules");
|
|
79
|
+
// yarn.lock 작성
|
|
80
|
+
await FsUtils.writeFileAsync(path.resolve(capacitorPath, "yarn.lock"), "");
|
|
81
|
+
// yarn install
|
|
82
|
+
await SdCliCapacitor._execAsync("yarn", ["install"], capacitorPath);
|
|
83
|
+
// capacitor init
|
|
84
|
+
await SdCliCapacitor._execAsync("npx", ["cap", "init", this._opt.config.appName, this._opt.config.appId], capacitorPath);
|
|
80
85
|
// www/index.html 생성
|
|
81
86
|
const wwwPath = path.resolve(capacitorPath, "www");
|
|
82
87
|
await FsUtils.writeFileAsync(path.resolve(wwwPath, "index.html"), "<!DOCTYPE html><html><head></head><body></body></html>");
|
|
88
|
+
return true;
|
|
83
89
|
}
|
|
84
90
|
// 버전 동기화
|
|
85
91
|
async _syncVersionAsync(capacitorPath) {
|
|
@@ -138,43 +144,55 @@ export class SdCliCapacitor {
|
|
|
138
144
|
await SdCliCapacitor._execAsync("npx", ["cap", "add", platform], capacitorPath);
|
|
139
145
|
}
|
|
140
146
|
}
|
|
141
|
-
// 4. 플러그인 관리
|
|
142
147
|
async _managePluginsAsync(capacitorPath) {
|
|
143
148
|
const pkgJsonPath = path.resolve(capacitorPath, "package.json");
|
|
144
149
|
const pkgJson = (await FsUtils.readJsonAsync(pkgJsonPath));
|
|
145
|
-
const currentDeps = Object.keys(pkgJson.dependencies ?? {});
|
|
146
|
-
const usePlugins = Object.keys(this._opt.config.plugins ?? {});
|
|
147
|
-
// 사용하지 않는 플러그인 제거
|
|
148
|
-
for (const dep of currentDeps) {
|
|
149
|
-
// @capacitor/core, @capacitor/android 등 기본 패키지는 제외
|
|
150
|
-
if (dep.startsWith("@capacitor/") &&
|
|
151
|
-
["core", "android", "ios"].some((p) => dep.endsWith(p))) {
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
// 플러그인 목록에 없는 패키지는 제거
|
|
155
|
-
if (!usePlugins.includes(dep)) {
|
|
156
|
-
// Capacitor 관련 플러그인만 제거
|
|
157
|
-
if (dep.startsWith("@capacitor/") || dep.includes("capacitor-plugin")) {
|
|
158
|
-
await SdCliCapacitor._execAsync("yarn", ["remove", dep], capacitorPath);
|
|
159
|
-
SdCliCapacitor._logger.debug(`플러그인 제거: ${dep}`);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
// 새 플러그인 설치
|
|
164
150
|
const mainDeps = {
|
|
165
151
|
...this._npmConfig.dependencies,
|
|
166
152
|
...this._npmConfig.devDependencies,
|
|
167
153
|
...this._npmConfig.peerDependencies,
|
|
168
154
|
};
|
|
155
|
+
const usePlugins = Object.keys(this._opt.config.plugins ?? {});
|
|
156
|
+
const currentDeps = pkgJson.dependencies ?? {};
|
|
157
|
+
let changed = false;
|
|
158
|
+
// 사용하지 않는 플러그인 제거
|
|
159
|
+
for (const dep of Object.keys(currentDeps)) {
|
|
160
|
+
if (this._isCapacitorPlugin(dep) && !usePlugins.includes(dep)) {
|
|
161
|
+
delete currentDeps[dep];
|
|
162
|
+
changed = true;
|
|
163
|
+
SdCliCapacitor._logger.debug(`플러그인 제거: ${dep}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// 새 플러그인 추가
|
|
169
167
|
for (const plugin of usePlugins) {
|
|
170
|
-
if (!
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
SdCliCapacitor._logger.debug(`플러그인 설치: ${pluginWithVersion}`);
|
|
168
|
+
if (!(plugin in currentDeps)) {
|
|
169
|
+
const version = mainDeps[plugin] ?? "^7.0.0";
|
|
170
|
+
currentDeps[plugin] = version;
|
|
171
|
+
changed = true;
|
|
172
|
+
SdCliCapacitor._logger.debug(`플러그인 추가: ${plugin}@${version}`);
|
|
176
173
|
}
|
|
177
174
|
}
|
|
175
|
+
// 변경사항 있을 때만 저장 & install
|
|
176
|
+
if (changed) {
|
|
177
|
+
pkgJson.dependencies = currentDeps;
|
|
178
|
+
await FsUtils.writeJsonAsync(pkgJsonPath, pkgJson, { space: 2 });
|
|
179
|
+
await SdCliCapacitor._execAsync("yarn", ["install"], capacitorPath);
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
// 변경 없으면 아무것도 안 함 → 오프라인 OK
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
_isCapacitorPlugin(dep) {
|
|
186
|
+
// 기본 패키지 제외
|
|
187
|
+
const corePackages = [
|
|
188
|
+
"@capacitor/core",
|
|
189
|
+
"@capacitor/android",
|
|
190
|
+
"@capacitor/ios",
|
|
191
|
+
"@capacitor/app",
|
|
192
|
+
];
|
|
193
|
+
if (corePackages.includes(dep))
|
|
194
|
+
return false;
|
|
195
|
+
return dep.startsWith("@capacitor/") || dep.includes("capacitor-plugin");
|
|
178
196
|
}
|
|
179
197
|
// 5. 안드로이드 서명 설정
|
|
180
198
|
async _setupAndroidSignAsync(capacitorPath) {
|
|
@@ -188,33 +206,45 @@ export class SdCliCapacitor {
|
|
|
188
206
|
}
|
|
189
207
|
// 6. 아이콘 및 스플래시 스크린 설정
|
|
190
208
|
async _setupIconAndSplashScreenAsync(capacitorPath) {
|
|
191
|
-
const
|
|
209
|
+
const resourcesDirPath = path.resolve(capacitorPath, this._ICON_DIR_PATH);
|
|
192
210
|
if (this._opt.config.icon != null) {
|
|
193
|
-
await FsUtils.mkdirsAsync(
|
|
211
|
+
await FsUtils.mkdirsAsync(resourcesDirPath);
|
|
194
212
|
const iconSource = path.resolve(this._opt.pkgPath, this._opt.config.icon);
|
|
195
|
-
//
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
213
|
+
// logo.png: 1024x1024 (Easy Mode)
|
|
214
|
+
// 로고 크기 약 60% - safe zone(61%) 내에서 최대한 크게
|
|
215
|
+
const logoPath = path.resolve(resourcesDirPath, "logo.png");
|
|
216
|
+
await this._createCenteredImageAsync(iconSource, logoPath, 1024, 0.6);
|
|
217
|
+
// splash.png: 2732x2732
|
|
218
|
+
// 로고 크기 약 35% - 화면 중앙에 적당한 크기로
|
|
219
|
+
const splashPath = path.resolve(resourcesDirPath, "splash.png");
|
|
220
|
+
await this._createCenteredImageAsync(iconSource, splashPath, 2732, 0.35);
|
|
221
|
+
// 기존 아이콘 삭제 (겹침 방지)
|
|
222
|
+
await this._cleanupExistingIconsAsync(capacitorPath);
|
|
200
223
|
try {
|
|
201
|
-
await SdCliCapacitor._execAsync("npx", [
|
|
224
|
+
await SdCliCapacitor._execAsync("npx", [
|
|
225
|
+
"@capacitor/assets",
|
|
226
|
+
"generate",
|
|
227
|
+
"--android",
|
|
228
|
+
"--iconBackgroundColor",
|
|
229
|
+
"#ffffff",
|
|
230
|
+
"--splashBackgroundColor",
|
|
231
|
+
"#ffffff",
|
|
232
|
+
], capacitorPath);
|
|
202
233
|
}
|
|
203
234
|
catch {
|
|
204
|
-
SdCliCapacitor._logger.warn("아이콘
|
|
235
|
+
SdCliCapacitor._logger.warn("아이콘 생성 실패, 기본 아이콘 사용");
|
|
205
236
|
}
|
|
206
237
|
}
|
|
207
238
|
else {
|
|
208
|
-
await FsUtils.removeAsync(
|
|
239
|
+
await FsUtils.removeAsync(resourcesDirPath);
|
|
209
240
|
}
|
|
210
241
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
const padding = Math.floor((outputSize -
|
|
215
|
-
// 원본 크기 상관없이 iconSize로 리사이즈 후 여백 추가
|
|
242
|
+
// 중앙에 로고를 배치한 이미지 생성
|
|
243
|
+
async _createCenteredImageAsync(sourcePath, outputPath, outputSize, logoRatio) {
|
|
244
|
+
const logoSize = Math.floor(outputSize * logoRatio);
|
|
245
|
+
const padding = Math.floor((outputSize - logoSize) / 2);
|
|
216
246
|
await sharp(sourcePath)
|
|
217
|
-
.resize(
|
|
247
|
+
.resize(logoSize, logoSize, {
|
|
218
248
|
fit: "contain",
|
|
219
249
|
background: { r: 0, g: 0, b: 0, alpha: 0 },
|
|
220
250
|
})
|
|
@@ -227,6 +257,19 @@ export class SdCliCapacitor {
|
|
|
227
257
|
})
|
|
228
258
|
.toFile(outputPath);
|
|
229
259
|
}
|
|
260
|
+
// 기존 아이콘 파일 삭제
|
|
261
|
+
async _cleanupExistingIconsAsync(capacitorPath) {
|
|
262
|
+
const androidResPath = path.resolve(capacitorPath, "android/app/src/main/res");
|
|
263
|
+
if (!FsUtils.exists(androidResPath))
|
|
264
|
+
return;
|
|
265
|
+
const mipmapDirs = await FsUtils.globAsync(path.resolve(androidResPath, "mipmap-*"));
|
|
266
|
+
for (const dir of mipmapDirs) {
|
|
267
|
+
const iconFiles = await FsUtils.globAsync(path.resolve(dir, "ic_launcher*.png"));
|
|
268
|
+
for (const file of iconFiles) {
|
|
269
|
+
await FsUtils.removeAsync(file);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
230
273
|
// 7. Android 네이티브 설정
|
|
231
274
|
async _configureAndroidNativeAsync(capacitorPath) {
|
|
232
275
|
const androidPath = path.resolve(capacitorPath, "android");
|
|
@@ -413,10 +456,12 @@ export class SdCliCapacitor {
|
|
|
413
456
|
async buildAsync(outPath) {
|
|
414
457
|
const capacitorPath = path.resolve(this._opt.pkgPath, this._CAPACITOR_DIR_NAME);
|
|
415
458
|
const buildType = this._opt.config.debug ? "debug" : "release";
|
|
416
|
-
// 웹 자산 동기화
|
|
417
|
-
await SdCliCapacitor._execAsync("npx", ["cap", "sync"], capacitorPath);
|
|
418
459
|
// 플랫폼별 빌드
|
|
419
|
-
await Promise.all(this._platforms.map((platform) =>
|
|
460
|
+
await Promise.all(this._platforms.map(async (platform) => {
|
|
461
|
+
// 해당 플랫폼만 copy
|
|
462
|
+
await SdCliCapacitor._execAsync("npx", ["cap", "copy", platform], capacitorPath);
|
|
463
|
+
await this._buildPlatformAsync(capacitorPath, outPath, platform, buildType);
|
|
464
|
+
}));
|
|
420
465
|
}
|
|
421
466
|
async _buildPlatformAsync(capacitorPath, outPath, platform, buildType) {
|
|
422
467
|
if (platform === "android") {
|
|
@@ -508,12 +553,13 @@ export class SdCliCapacitor {
|
|
|
508
553
|
}
|
|
509
554
|
}
|
|
510
555
|
// cap sync 후 run
|
|
511
|
-
await
|
|
556
|
+
await SdCliCapacitor._execAsync("npx", ["cap", "copy", opt.platform], capacitorPath);
|
|
512
557
|
try {
|
|
513
558
|
await this._execAsync("npx", ["cap", "run", opt.platform], capacitorPath);
|
|
514
559
|
}
|
|
515
|
-
catch {
|
|
560
|
+
catch (err) {
|
|
516
561
|
await SdProcess.spawnAsync("adb", ["kill-server"]);
|
|
562
|
+
throw err;
|
|
517
563
|
}
|
|
518
564
|
}
|
|
519
565
|
}
|
|
@@ -64,14 +64,16 @@ export class SdCliIndexFileGenerator {
|
|
|
64
64
|
const indexFilePath = path.resolve(pkgPath, "src/index.ts");
|
|
65
65
|
const tsconfig = await FsUtils.readJsonAsync(path.resolve(pkgPath, "tsconfig.json"));
|
|
66
66
|
return [
|
|
67
|
-
...(tsconfig.excludes ?? []),
|
|
68
|
-
...(excludes ?? []),
|
|
69
67
|
indexFilePath,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
68
|
+
...[
|
|
69
|
+
...(tsconfig.excludes ?? []),
|
|
70
|
+
...(excludes ?? []),
|
|
71
|
+
"src/**/*.d.ts",
|
|
72
|
+
"src/index.ts",
|
|
73
|
+
"src/workers/**/*{.ts,.tsx}",
|
|
74
|
+
// TODO: index에 없는 파일은 watch가 안됨... 처리 필요함.
|
|
75
|
+
// "src/internal/**/*{.ts,.tsx}",
|
|
76
|
+
].map((item) => path.resolve(pkgPath, item)),
|
|
75
77
|
].map((item) => item.replace(/\\/g, "/"));
|
|
76
78
|
}
|
|
77
79
|
}
|
|
@@ -109,7 +109,7 @@ Options = UnsafeLegacyRenegotiation`.trim());
|
|
|
109
109
|
arrayProcess: "concat",
|
|
110
110
|
useDelTargetNull: true
|
|
111
111
|
};`
|
|
112
|
-
.
|
|
112
|
+
.replace(/\n {8}/g, "\n")
|
|
113
113
|
.trim();
|
|
114
114
|
FsUtils.writeFile(path.resolve(this._opt.pkgPath, "dist/pm2.config.cjs"), str);
|
|
115
115
|
}
|
|
@@ -334,7 +334,7 @@ export class SdTsCompiler {
|
|
|
334
334
|
(transformers.before ??= []).push(createWorkerTransformer((file, importer) => {
|
|
335
335
|
const fullPath = path.resolve(path.dirname(importer), file);
|
|
336
336
|
const relPath = path.relative(path.resolve(this._opt.pkgPath, "src"), fullPath);
|
|
337
|
-
return relPath.replace(/\.ts$/, "").
|
|
337
|
+
return relPath.replace(/\.ts$/, "").replace(/\\/, "/") + ".js";
|
|
338
338
|
}));
|
|
339
339
|
}
|
|
340
340
|
this._debug(`파일 출력 중...`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/sd-cli",
|
|
3
|
-
"version": "12.16.
|
|
3
|
+
"version": "12.16.9",
|
|
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.16.
|
|
21
|
-
"@simplysm/sd-core-node": "12.16.
|
|
22
|
-
"@simplysm/sd-service-server": "12.16.
|
|
23
|
-
"@simplysm/sd-storage": "12.16.
|
|
20
|
+
"@simplysm/sd-core-common": "12.16.9",
|
|
21
|
+
"@simplysm/sd-core-node": "12.16.9",
|
|
22
|
+
"@simplysm/sd-service-server": "12.16.9",
|
|
23
|
+
"@simplysm/sd-storage": "12.16.9",
|
|
24
24
|
"browserslist": "^4.28.1",
|
|
25
25
|
"cordova": "^13.0.0",
|
|
26
26
|
"electron": "^33.4.11",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"glob": "^13.0.0",
|
|
32
32
|
"node-stdlib-browser": "^1.3.1",
|
|
33
33
|
"rxjs": "^7.8.2",
|
|
34
|
-
"sass-embedded": "^1.
|
|
34
|
+
"sass-embedded": "^1.97.0",
|
|
35
35
|
"semver": "^7.7.3",
|
|
36
36
|
"sharp": "^0.34.5",
|
|
37
37
|
"specifier-resolution-node": "^1.1.4",
|
|
@@ -35,7 +35,7 @@ export class SdCliCapacitor {
|
|
|
35
35
|
const capacitorPath = path.resolve(this._opt.pkgPath, this._CAPACITOR_DIR_NAME);
|
|
36
36
|
|
|
37
37
|
// 1. Capacitor 프로젝트 초기화
|
|
38
|
-
await this._initializeCapacitorProjectAsync(capacitorPath);
|
|
38
|
+
const isNewProject = await this._initializeCapacitorProjectAsync(capacitorPath);
|
|
39
39
|
|
|
40
40
|
// 2. Capacitor 설정 파일 생성
|
|
41
41
|
await this._createCapacitorConfigAsync(capacitorPath);
|
|
@@ -44,7 +44,7 @@ export class SdCliCapacitor {
|
|
|
44
44
|
await this._managePlatformsAsync(capacitorPath);
|
|
45
45
|
|
|
46
46
|
// 4. 플러그인 관리
|
|
47
|
-
await this._managePluginsAsync(capacitorPath);
|
|
47
|
+
const pluginsChanged = await this._managePluginsAsync(capacitorPath);
|
|
48
48
|
|
|
49
49
|
// 5. 안드로이드 서명 설정
|
|
50
50
|
await this._setupAndroidSignAsync(capacitorPath);
|
|
@@ -58,63 +58,70 @@ export class SdCliCapacitor {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
// 8. 웹 자산 동기화
|
|
61
|
-
|
|
61
|
+
if (isNewProject || pluginsChanged) {
|
|
62
|
+
await SdCliCapacitor._execAsync("npx", ["cap", "sync"], capacitorPath);
|
|
63
|
+
} else {
|
|
64
|
+
await SdCliCapacitor._execAsync("npx", ["cap", "copy"], capacitorPath);
|
|
65
|
+
}
|
|
62
66
|
}
|
|
63
67
|
|
|
64
68
|
// 1. Capacitor 프로젝트 초기화
|
|
65
|
-
private async _initializeCapacitorProjectAsync(capacitorPath: string): Promise<
|
|
69
|
+
private async _initializeCapacitorProjectAsync(capacitorPath: string): Promise<boolean> {
|
|
66
70
|
if (FsUtils.exists(path.resolve(capacitorPath, "www"))) {
|
|
67
71
|
SdCliCapacitor._logger.log("이미 생성되어있는 '.capacitor'를 사용합니다.");
|
|
68
72
|
|
|
69
73
|
// 버전 동기화
|
|
70
74
|
await this._syncVersionAsync(capacitorPath);
|
|
71
|
-
|
|
72
|
-
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
73
77
|
|
|
74
|
-
// package.json 생성
|
|
75
|
-
const projNpmConfig = await FsUtils.readJsonAsync(
|
|
76
|
-
path.resolve(this._opt.pkgPath, "../../package.json"),
|
|
77
|
-
);
|
|
78
|
-
const pkgJson = {
|
|
79
|
-
name: this._opt.config.appId,
|
|
80
|
-
version: this._npmConfig.version,
|
|
81
|
-
private: true,
|
|
82
|
-
volta: projNpmConfig.volta,
|
|
83
|
-
dependencies: {
|
|
84
|
-
"@capacitor/core": "^7.0.0",
|
|
85
|
-
},
|
|
86
|
-
devDependencies: {
|
|
87
|
-
"@capacitor/cli": "^7.0.0",
|
|
88
|
-
"@capacitor/assets": "^3.0.0",
|
|
89
|
-
...this._platforms.toObject(
|
|
90
|
-
(item) => `@capacitor/${item}`,
|
|
91
|
-
() => "^7.0.0",
|
|
92
|
-
),
|
|
93
|
-
},
|
|
94
|
-
};
|
|
95
|
-
await FsUtils.writeJsonAsync(path.resolve(capacitorPath, "package.json"), pkgJson, {
|
|
96
|
-
space: 2,
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
// .yarnrc.yml 작성
|
|
100
|
-
await FsUtils.writeFileAsync(
|
|
101
|
-
path.resolve(capacitorPath, ".yarnrc.yml"),
|
|
102
|
-
"nodeLinker: node-modules",
|
|
103
|
-
);
|
|
104
78
|
|
|
105
|
-
|
|
106
|
-
await FsUtils.writeFileAsync(path.resolve(capacitorPath, "yarn.lock"), "");
|
|
79
|
+
await FsUtils.mkdirsAsync(capacitorPath);
|
|
107
80
|
|
|
108
|
-
|
|
109
|
-
|
|
81
|
+
// package.json 생성
|
|
82
|
+
const projNpmConfig = await FsUtils.readJsonAsync(
|
|
83
|
+
path.resolve(this._opt.pkgPath, "../../package.json"),
|
|
84
|
+
);
|
|
85
|
+
const pkgJson = {
|
|
86
|
+
name: this._opt.config.appId,
|
|
87
|
+
version: this._npmConfig.version,
|
|
88
|
+
private: true,
|
|
89
|
+
volta: projNpmConfig.volta,
|
|
90
|
+
dependencies: {
|
|
91
|
+
"@capacitor/core": "^7.0.0",
|
|
92
|
+
"@capacitor/app": "^7.0.0",
|
|
93
|
+
},
|
|
94
|
+
devDependencies: {
|
|
95
|
+
"@capacitor/cli": "^7.0.0",
|
|
96
|
+
"@capacitor/assets": "^3.0.0",
|
|
97
|
+
...this._platforms.toObject(
|
|
98
|
+
(item) => `@capacitor/${item}`,
|
|
99
|
+
() => "^7.0.0",
|
|
100
|
+
),
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
await FsUtils.writeJsonAsync(path.resolve(capacitorPath, "package.json"), pkgJson, {
|
|
104
|
+
space: 2,
|
|
105
|
+
});
|
|
110
106
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
107
|
+
// .yarnrc.yml 작성
|
|
108
|
+
await FsUtils.writeFileAsync(
|
|
109
|
+
path.resolve(capacitorPath, ".yarnrc.yml"),
|
|
110
|
+
"nodeLinker: node-modules",
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// yarn.lock 작성
|
|
114
|
+
await FsUtils.writeFileAsync(path.resolve(capacitorPath, "yarn.lock"), "");
|
|
115
|
+
|
|
116
|
+
// yarn install
|
|
117
|
+
await SdCliCapacitor._execAsync("yarn", ["install"], capacitorPath);
|
|
118
|
+
|
|
119
|
+
// capacitor init
|
|
120
|
+
await SdCliCapacitor._execAsync(
|
|
121
|
+
"npx",
|
|
122
|
+
["cap", "init", this._opt.config.appName, this._opt.config.appId],
|
|
123
|
+
capacitorPath,
|
|
124
|
+
);
|
|
118
125
|
|
|
119
126
|
// www/index.html 생성
|
|
120
127
|
const wwwPath = path.resolve(capacitorPath, "www");
|
|
@@ -122,6 +129,8 @@ export class SdCliCapacitor {
|
|
|
122
129
|
path.resolve(wwwPath, "index.html"),
|
|
123
130
|
"<!DOCTYPE html><html><head></head><body></body></html>",
|
|
124
131
|
);
|
|
132
|
+
|
|
133
|
+
return true;
|
|
125
134
|
}
|
|
126
135
|
|
|
127
136
|
// 버전 동기화
|
|
@@ -191,51 +200,62 @@ export class SdCliCapacitor {
|
|
|
191
200
|
}
|
|
192
201
|
}
|
|
193
202
|
|
|
194
|
-
|
|
195
|
-
private async _managePluginsAsync(capacitorPath: string): Promise<void> {
|
|
203
|
+
private async _managePluginsAsync(capacitorPath: string): Promise<boolean> {
|
|
196
204
|
const pkgJsonPath = path.resolve(capacitorPath, "package.json");
|
|
197
205
|
const pkgJson = (await FsUtils.readJsonAsync(pkgJsonPath)) as INpmConfig;
|
|
198
|
-
|
|
206
|
+
|
|
207
|
+
const mainDeps = {
|
|
208
|
+
...this._npmConfig.dependencies,
|
|
209
|
+
...this._npmConfig.devDependencies,
|
|
210
|
+
...this._npmConfig.peerDependencies,
|
|
211
|
+
};
|
|
199
212
|
|
|
200
213
|
const usePlugins = Object.keys(this._opt.config.plugins ?? {});
|
|
214
|
+
const currentDeps = pkgJson.dependencies ?? {};
|
|
215
|
+
|
|
216
|
+
let changed = false;
|
|
201
217
|
|
|
202
218
|
// 사용하지 않는 플러그인 제거
|
|
203
|
-
for (const dep of currentDeps) {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
) {
|
|
209
|
-
continue;
|
|
219
|
+
for (const dep of Object.keys(currentDeps)) {
|
|
220
|
+
if (this._isCapacitorPlugin(dep) && !usePlugins.includes(dep)) {
|
|
221
|
+
delete currentDeps[dep];
|
|
222
|
+
changed = true;
|
|
223
|
+
SdCliCapacitor._logger.debug(`플러그인 제거: ${dep}`);
|
|
210
224
|
}
|
|
225
|
+
}
|
|
211
226
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
227
|
+
// 새 플러그인 추가
|
|
228
|
+
for (const plugin of usePlugins) {
|
|
229
|
+
if (!(plugin in currentDeps)) {
|
|
230
|
+
const version = mainDeps[plugin] ?? "^7.0.0";
|
|
231
|
+
currentDeps[plugin] = version;
|
|
232
|
+
changed = true;
|
|
233
|
+
SdCliCapacitor._logger.debug(`플러그인 추가: ${plugin}@${version}`);
|
|
219
234
|
}
|
|
220
235
|
}
|
|
221
236
|
|
|
222
|
-
//
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
237
|
+
// 변경사항 있을 때만 저장 & install
|
|
238
|
+
if (changed) {
|
|
239
|
+
pkgJson.dependencies = currentDeps;
|
|
240
|
+
await FsUtils.writeJsonAsync(pkgJsonPath, pkgJson, { space: 2 });
|
|
241
|
+
await SdCliCapacitor._execAsync("yarn", ["install"], capacitorPath);
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
// 변경 없으면 아무것도 안 함 → 오프라인 OK
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
228
247
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
248
|
+
private _isCapacitorPlugin(dep: string): boolean {
|
|
249
|
+
// 기본 패키지 제외
|
|
250
|
+
const corePackages = [
|
|
251
|
+
"@capacitor/core",
|
|
252
|
+
"@capacitor/android",
|
|
253
|
+
"@capacitor/ios",
|
|
254
|
+
"@capacitor/app",
|
|
255
|
+
];
|
|
256
|
+
if (corePackages.includes(dep)) return false;
|
|
234
257
|
|
|
235
|
-
|
|
236
|
-
SdCliCapacitor._logger.debug(`플러그인 설치: ${pluginWithVersion}`);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
258
|
+
return dep.startsWith("@capacitor/") || dep.includes("capacitor-plugin");
|
|
239
259
|
}
|
|
240
260
|
|
|
241
261
|
// 5. 안드로이드 서명 설정
|
|
@@ -254,42 +274,60 @@ export class SdCliCapacitor {
|
|
|
254
274
|
|
|
255
275
|
// 6. 아이콘 및 스플래시 스크린 설정
|
|
256
276
|
private async _setupIconAndSplashScreenAsync(capacitorPath: string): Promise<void> {
|
|
257
|
-
const
|
|
277
|
+
const resourcesDirPath = path.resolve(capacitorPath, this._ICON_DIR_PATH);
|
|
258
278
|
|
|
259
279
|
if (this._opt.config.icon != null) {
|
|
260
|
-
await FsUtils.mkdirsAsync(
|
|
280
|
+
await FsUtils.mkdirsAsync(resourcesDirPath);
|
|
261
281
|
|
|
262
282
|
const iconSource = path.resolve(this._opt.pkgPath, this._opt.config.icon);
|
|
263
283
|
|
|
264
|
-
//
|
|
265
|
-
|
|
266
|
-
|
|
284
|
+
// logo.png: 1024x1024 (Easy Mode)
|
|
285
|
+
// 로고 크기 약 60% - safe zone(61%) 내에서 최대한 크게
|
|
286
|
+
const logoPath = path.resolve(resourcesDirPath, "logo.png");
|
|
287
|
+
await this._createCenteredImageAsync(iconSource, logoPath, 1024, 0.6);
|
|
267
288
|
|
|
268
|
-
// splash
|
|
269
|
-
|
|
289
|
+
// splash.png: 2732x2732
|
|
290
|
+
// 로고 크기 약 35% - 화면 중앙에 적당한 크기로
|
|
291
|
+
const splashPath = path.resolve(resourcesDirPath, "splash.png");
|
|
292
|
+
await this._createCenteredImageAsync(iconSource, splashPath, 2732, 0.35);
|
|
293
|
+
|
|
294
|
+
// 기존 아이콘 삭제 (겹침 방지)
|
|
295
|
+
await this._cleanupExistingIconsAsync(capacitorPath);
|
|
270
296
|
|
|
271
297
|
try {
|
|
272
298
|
await SdCliCapacitor._execAsync(
|
|
273
299
|
"npx",
|
|
274
|
-
[
|
|
300
|
+
[
|
|
301
|
+
"@capacitor/assets",
|
|
302
|
+
"generate",
|
|
303
|
+
"--android",
|
|
304
|
+
"--iconBackgroundColor",
|
|
305
|
+
"#ffffff",
|
|
306
|
+
"--splashBackgroundColor",
|
|
307
|
+
"#ffffff",
|
|
308
|
+
],
|
|
275
309
|
capacitorPath,
|
|
276
310
|
);
|
|
277
311
|
} catch {
|
|
278
|
-
SdCliCapacitor._logger.warn("아이콘
|
|
312
|
+
SdCliCapacitor._logger.warn("아이콘 생성 실패, 기본 아이콘 사용");
|
|
279
313
|
}
|
|
280
314
|
} else {
|
|
281
|
-
await FsUtils.removeAsync(
|
|
315
|
+
await FsUtils.removeAsync(resourcesDirPath);
|
|
282
316
|
}
|
|
283
317
|
}
|
|
284
318
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
319
|
+
// 중앙에 로고를 배치한 이미지 생성
|
|
320
|
+
private async _createCenteredImageAsync(
|
|
321
|
+
sourcePath: string,
|
|
322
|
+
outputPath: string,
|
|
323
|
+
outputSize: number,
|
|
324
|
+
logoRatio: number, // 0.0 ~ 1.0
|
|
325
|
+
): Promise<void> {
|
|
326
|
+
const logoSize = Math.floor(outputSize * logoRatio);
|
|
327
|
+
const padding = Math.floor((outputSize - logoSize) / 2);
|
|
289
328
|
|
|
290
|
-
// 원본 크기 상관없이 iconSize로 리사이즈 후 여백 추가
|
|
291
329
|
await sharp(sourcePath)
|
|
292
|
-
.resize(
|
|
330
|
+
.resize(logoSize, logoSize, {
|
|
293
331
|
fit: "contain",
|
|
294
332
|
background: { r: 0, g: 0, b: 0, alpha: 0 },
|
|
295
333
|
})
|
|
@@ -303,6 +341,21 @@ export class SdCliCapacitor {
|
|
|
303
341
|
.toFile(outputPath);
|
|
304
342
|
}
|
|
305
343
|
|
|
344
|
+
// 기존 아이콘 파일 삭제
|
|
345
|
+
private async _cleanupExistingIconsAsync(capacitorPath: string): Promise<void> {
|
|
346
|
+
const androidResPath = path.resolve(capacitorPath, "android/app/src/main/res");
|
|
347
|
+
|
|
348
|
+
if (!FsUtils.exists(androidResPath)) return;
|
|
349
|
+
|
|
350
|
+
const mipmapDirs = await FsUtils.globAsync(path.resolve(androidResPath, "mipmap-*"));
|
|
351
|
+
for (const dir of mipmapDirs) {
|
|
352
|
+
const iconFiles = await FsUtils.globAsync(path.resolve(dir, "ic_launcher*.png"));
|
|
353
|
+
for (const file of iconFiles) {
|
|
354
|
+
await FsUtils.removeAsync(file);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
306
359
|
// 7. Android 네이티브 설정
|
|
307
360
|
private async _configureAndroidNativeAsync(capacitorPath: string) {
|
|
308
361
|
const androidPath = path.resolve(capacitorPath, "android");
|
|
@@ -571,14 +624,13 @@ export class SdCliCapacitor {
|
|
|
571
624
|
const capacitorPath = path.resolve(this._opt.pkgPath, this._CAPACITOR_DIR_NAME);
|
|
572
625
|
const buildType = this._opt.config.debug ? "debug" : "release";
|
|
573
626
|
|
|
574
|
-
// 웹 자산 동기화
|
|
575
|
-
await SdCliCapacitor._execAsync("npx", ["cap", "sync"], capacitorPath);
|
|
576
|
-
|
|
577
627
|
// 플랫폼별 빌드
|
|
578
628
|
await Promise.all(
|
|
579
|
-
this._platforms.map((platform) =>
|
|
580
|
-
|
|
581
|
-
|
|
629
|
+
this._platforms.map(async (platform) => {
|
|
630
|
+
// 해당 플랫폼만 copy
|
|
631
|
+
await SdCliCapacitor._execAsync("npx", ["cap", "copy", platform], capacitorPath);
|
|
632
|
+
await this._buildPlatformAsync(capacitorPath, outPath, platform, buildType);
|
|
633
|
+
}),
|
|
582
634
|
);
|
|
583
635
|
}
|
|
584
636
|
|
|
@@ -735,12 +787,13 @@ export class SdCliCapacitor {
|
|
|
735
787
|
}
|
|
736
788
|
|
|
737
789
|
// cap sync 후 run
|
|
738
|
-
await
|
|
790
|
+
await SdCliCapacitor._execAsync("npx", ["cap", "copy", opt.platform], capacitorPath);
|
|
739
791
|
|
|
740
792
|
try {
|
|
741
793
|
await this._execAsync("npx", ["cap", "run", opt.platform], capacitorPath);
|
|
742
|
-
} catch {
|
|
794
|
+
} catch (err) {
|
|
743
795
|
await SdProcess.spawnAsync("adb", ["kill-server"]);
|
|
796
|
+
throw err;
|
|
744
797
|
}
|
|
745
798
|
}
|
|
746
799
|
}
|
|
@@ -78,15 +78,17 @@ export class SdCliIndexFileGenerator {
|
|
|
78
78
|
const tsconfig = await FsUtils.readJsonAsync(path.resolve(pkgPath, "tsconfig.json"));
|
|
79
79
|
|
|
80
80
|
return [
|
|
81
|
-
...(tsconfig.excludes ?? []),
|
|
82
|
-
...(excludes ?? []),
|
|
83
81
|
indexFilePath,
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
82
|
+
...[
|
|
83
|
+
...(tsconfig.excludes ?? []),
|
|
84
|
+
...(excludes ?? []),
|
|
85
|
+
"src/**/*.d.ts",
|
|
86
|
+
"src/index.ts",
|
|
87
|
+
"src/workers/**/*{.ts,.tsx}",
|
|
88
|
+
|
|
89
|
+
// TODO: index에 없는 파일은 watch가 안됨... 처리 필요함.
|
|
90
|
+
// "src/internal/**/*{.ts,.tsx}",
|
|
91
|
+
].map((item) => path.resolve(pkgPath, item)),
|
|
90
92
|
].map((item) => item.replace(/\\/g, "/"));
|
|
91
93
|
}
|
|
92
94
|
}
|
|
@@ -136,7 +136,7 @@ Options = UnsafeLegacyRenegotiation`.trim(),
|
|
|
136
136
|
arrayProcess: "concat",
|
|
137
137
|
useDelTargetNull: true
|
|
138
138
|
};`
|
|
139
|
-
.
|
|
139
|
+
.replace(/\n {8}/g, "\n")
|
|
140
140
|
.trim();
|
|
141
141
|
|
|
142
142
|
FsUtils.writeFile(path.resolve(this._opt.pkgPath, "dist/pm2.config.cjs"), str);
|
|
@@ -499,7 +499,7 @@ export class SdTsCompiler {
|
|
|
499
499
|
createWorkerTransformer((file, importer) => {
|
|
500
500
|
const fullPath = path.resolve(path.dirname(importer), file);
|
|
501
501
|
const relPath = path.relative(path.resolve(this._opt.pkgPath, "src"), fullPath);
|
|
502
|
-
return relPath.replace(/\.ts$/, "").
|
|
502
|
+
return relPath.replace(/\.ts$/, "").replace(/\\/, "/") + ".js";
|
|
503
503
|
}),
|
|
504
504
|
);
|
|
505
505
|
}
|