@simplysm/sd-cli 12.16.8 → 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.
|
@@ -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,41 +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
|
-
|
|
80
|
-
}
|
|
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);
|
|
81
85
|
// www/index.html 생성
|
|
82
86
|
const wwwPath = path.resolve(capacitorPath, "www");
|
|
83
87
|
await FsUtils.writeFileAsync(path.resolve(wwwPath, "index.html"), "<!DOCTYPE html><html><head></head><body></body></html>");
|
|
88
|
+
return true;
|
|
84
89
|
}
|
|
85
90
|
// 버전 동기화
|
|
86
91
|
async _syncVersionAsync(capacitorPath) {
|
|
@@ -139,43 +144,55 @@ export class SdCliCapacitor {
|
|
|
139
144
|
await SdCliCapacitor._execAsync("npx", ["cap", "add", platform], capacitorPath);
|
|
140
145
|
}
|
|
141
146
|
}
|
|
142
|
-
// 4. 플러그인 관리
|
|
143
147
|
async _managePluginsAsync(capacitorPath) {
|
|
144
148
|
const pkgJsonPath = path.resolve(capacitorPath, "package.json");
|
|
145
149
|
const pkgJson = (await FsUtils.readJsonAsync(pkgJsonPath));
|
|
146
|
-
const currentDeps = Object.keys(pkgJson.dependencies ?? {});
|
|
147
|
-
const usePlugins = Object.keys(this._opt.config.plugins ?? {});
|
|
148
|
-
// 사용하지 않는 플러그인 제거
|
|
149
|
-
for (const dep of currentDeps) {
|
|
150
|
-
// @capacitor/core, @capacitor/android 등 기본 패키지는 제외
|
|
151
|
-
if (dep.startsWith("@capacitor/") &&
|
|
152
|
-
["core", "android", "ios"].some((p) => dep.endsWith(p))) {
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
// 플러그인 목록에 없는 패키지는 제거
|
|
156
|
-
if (!usePlugins.includes(dep)) {
|
|
157
|
-
// Capacitor 관련 플러그인만 제거
|
|
158
|
-
if (dep.startsWith("@capacitor/") || dep.includes("capacitor-plugin")) {
|
|
159
|
-
await SdCliCapacitor._execAsync("yarn", ["remove", dep], capacitorPath);
|
|
160
|
-
SdCliCapacitor._logger.debug(`플러그인 제거: ${dep}`);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
// 새 플러그인 설치
|
|
165
150
|
const mainDeps = {
|
|
166
151
|
...this._npmConfig.dependencies,
|
|
167
152
|
...this._npmConfig.devDependencies,
|
|
168
153
|
...this._npmConfig.peerDependencies,
|
|
169
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
|
+
// 새 플러그인 추가
|
|
170
167
|
for (const plugin of usePlugins) {
|
|
171
|
-
if (!
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
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}`);
|
|
177
173
|
}
|
|
178
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");
|
|
179
196
|
}
|
|
180
197
|
// 5. 안드로이드 서명 설정
|
|
181
198
|
async _setupAndroidSignAsync(capacitorPath) {
|
|
@@ -189,33 +206,45 @@ export class SdCliCapacitor {
|
|
|
189
206
|
}
|
|
190
207
|
// 6. 아이콘 및 스플래시 스크린 설정
|
|
191
208
|
async _setupIconAndSplashScreenAsync(capacitorPath) {
|
|
192
|
-
const
|
|
209
|
+
const resourcesDirPath = path.resolve(capacitorPath, this._ICON_DIR_PATH);
|
|
193
210
|
if (this._opt.config.icon != null) {
|
|
194
|
-
await FsUtils.mkdirsAsync(
|
|
211
|
+
await FsUtils.mkdirsAsync(resourcesDirPath);
|
|
195
212
|
const iconSource = path.resolve(this._opt.pkgPath, this._opt.config.icon);
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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);
|
|
201
223
|
try {
|
|
202
|
-
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);
|
|
203
233
|
}
|
|
204
234
|
catch {
|
|
205
|
-
SdCliCapacitor._logger.warn("아이콘
|
|
235
|
+
SdCliCapacitor._logger.warn("아이콘 생성 실패, 기본 아이콘 사용");
|
|
206
236
|
}
|
|
207
237
|
}
|
|
208
238
|
else {
|
|
209
|
-
await FsUtils.removeAsync(
|
|
239
|
+
await FsUtils.removeAsync(resourcesDirPath);
|
|
210
240
|
}
|
|
211
241
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const
|
|
215
|
-
const padding = Math.floor((outputSize -
|
|
216
|
-
// 원본 크기 상관없이 iconSize로 리사이즈 후 여백 추가
|
|
242
|
+
// 중앙에 로고를 배치한 이미지 생성
|
|
243
|
+
async _createCenteredImageAsync(sourcePath, outputPath, outputSize, logoRatio) {
|
|
244
|
+
const logoSize = Math.floor(outputSize * logoRatio);
|
|
245
|
+
const padding = Math.floor((outputSize - logoSize) / 2);
|
|
217
246
|
await sharp(sourcePath)
|
|
218
|
-
.resize(
|
|
247
|
+
.resize(logoSize, logoSize, {
|
|
219
248
|
fit: "contain",
|
|
220
249
|
background: { r: 0, g: 0, b: 0, alpha: 0 },
|
|
221
250
|
})
|
|
@@ -228,6 +257,19 @@ export class SdCliCapacitor {
|
|
|
228
257
|
})
|
|
229
258
|
.toFile(outputPath);
|
|
230
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
|
+
}
|
|
231
273
|
// 7. Android 네이티브 설정
|
|
232
274
|
async _configureAndroidNativeAsync(capacitorPath) {
|
|
233
275
|
const androidPath = path.resolve(capacitorPath, "android");
|
|
@@ -414,10 +456,12 @@ export class SdCliCapacitor {
|
|
|
414
456
|
async buildAsync(outPath) {
|
|
415
457
|
const capacitorPath = path.resolve(this._opt.pkgPath, this._CAPACITOR_DIR_NAME);
|
|
416
458
|
const buildType = this._opt.config.debug ? "debug" : "release";
|
|
417
|
-
// 웹 자산 동기화
|
|
418
|
-
await SdCliCapacitor._execAsync("npx", ["cap", "sync"], capacitorPath);
|
|
419
459
|
// 플랫폼별 빌드
|
|
420
|
-
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
|
+
}));
|
|
421
465
|
}
|
|
422
466
|
async _buildPlatformAsync(capacitorPath, outPath, platform, buildType) {
|
|
423
467
|
if (platform === "android") {
|
|
@@ -509,7 +553,7 @@ export class SdCliCapacitor {
|
|
|
509
553
|
}
|
|
510
554
|
}
|
|
511
555
|
// cap sync 후 run
|
|
512
|
-
await
|
|
556
|
+
await SdCliCapacitor._execAsync("npx", ["cap", "copy", opt.platform], capacitorPath);
|
|
513
557
|
try {
|
|
514
558
|
await this._execAsync("npx", ["cap", "run", opt.platform], capacitorPath);
|
|
515
559
|
}
|
|
@@ -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
|
}
|
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",
|
|
@@ -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,64 +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
|
-
"@capacitor/app": "^7.0.0",
|
|
86
|
-
},
|
|
87
|
-
devDependencies: {
|
|
88
|
-
"@capacitor/cli": "^7.0.0",
|
|
89
|
-
"@capacitor/assets": "^3.0.0",
|
|
90
|
-
...this._platforms.toObject(
|
|
91
|
-
(item) => `@capacitor/${item}`,
|
|
92
|
-
() => "^7.0.0",
|
|
93
|
-
),
|
|
94
|
-
},
|
|
95
|
-
};
|
|
96
|
-
await FsUtils.writeJsonAsync(path.resolve(capacitorPath, "package.json"), pkgJson, {
|
|
97
|
-
space: 2,
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
// .yarnrc.yml 작성
|
|
101
|
-
await FsUtils.writeFileAsync(
|
|
102
|
-
path.resolve(capacitorPath, ".yarnrc.yml"),
|
|
103
|
-
"nodeLinker: node-modules",
|
|
104
|
-
);
|
|
105
78
|
|
|
106
|
-
|
|
107
|
-
await FsUtils.writeFileAsync(path.resolve(capacitorPath, "yarn.lock"), "");
|
|
79
|
+
await FsUtils.mkdirsAsync(capacitorPath);
|
|
108
80
|
|
|
109
|
-
|
|
110
|
-
|
|
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
|
+
});
|
|
111
106
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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
|
+
);
|
|
119
125
|
|
|
120
126
|
// www/index.html 생성
|
|
121
127
|
const wwwPath = path.resolve(capacitorPath, "www");
|
|
@@ -123,6 +129,8 @@ export class SdCliCapacitor {
|
|
|
123
129
|
path.resolve(wwwPath, "index.html"),
|
|
124
130
|
"<!DOCTYPE html><html><head></head><body></body></html>",
|
|
125
131
|
);
|
|
132
|
+
|
|
133
|
+
return true;
|
|
126
134
|
}
|
|
127
135
|
|
|
128
136
|
// 버전 동기화
|
|
@@ -192,51 +200,62 @@ export class SdCliCapacitor {
|
|
|
192
200
|
}
|
|
193
201
|
}
|
|
194
202
|
|
|
195
|
-
|
|
196
|
-
private async _managePluginsAsync(capacitorPath: string): Promise<void> {
|
|
203
|
+
private async _managePluginsAsync(capacitorPath: string): Promise<boolean> {
|
|
197
204
|
const pkgJsonPath = path.resolve(capacitorPath, "package.json");
|
|
198
205
|
const pkgJson = (await FsUtils.readJsonAsync(pkgJsonPath)) as INpmConfig;
|
|
199
|
-
|
|
206
|
+
|
|
207
|
+
const mainDeps = {
|
|
208
|
+
...this._npmConfig.dependencies,
|
|
209
|
+
...this._npmConfig.devDependencies,
|
|
210
|
+
...this._npmConfig.peerDependencies,
|
|
211
|
+
};
|
|
200
212
|
|
|
201
213
|
const usePlugins = Object.keys(this._opt.config.plugins ?? {});
|
|
214
|
+
const currentDeps = pkgJson.dependencies ?? {};
|
|
215
|
+
|
|
216
|
+
let changed = false;
|
|
202
217
|
|
|
203
218
|
// 사용하지 않는 플러그인 제거
|
|
204
|
-
for (const dep of currentDeps) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
) {
|
|
210
|
-
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}`);
|
|
211
224
|
}
|
|
225
|
+
}
|
|
212
226
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
}
|
|
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}`);
|
|
220
234
|
}
|
|
221
235
|
}
|
|
222
236
|
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
+
}
|
|
229
247
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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;
|
|
235
257
|
|
|
236
|
-
|
|
237
|
-
SdCliCapacitor._logger.debug(`플러그인 설치: ${pluginWithVersion}`);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
258
|
+
return dep.startsWith("@capacitor/") || dep.includes("capacitor-plugin");
|
|
240
259
|
}
|
|
241
260
|
|
|
242
261
|
// 5. 안드로이드 서명 설정
|
|
@@ -255,42 +274,60 @@ export class SdCliCapacitor {
|
|
|
255
274
|
|
|
256
275
|
// 6. 아이콘 및 스플래시 스크린 설정
|
|
257
276
|
private async _setupIconAndSplashScreenAsync(capacitorPath: string): Promise<void> {
|
|
258
|
-
const
|
|
277
|
+
const resourcesDirPath = path.resolve(capacitorPath, this._ICON_DIR_PATH);
|
|
259
278
|
|
|
260
279
|
if (this._opt.config.icon != null) {
|
|
261
|
-
await FsUtils.mkdirsAsync(
|
|
280
|
+
await FsUtils.mkdirsAsync(resourcesDirPath);
|
|
262
281
|
|
|
263
282
|
const iconSource = path.resolve(this._opt.pkgPath, this._opt.config.icon);
|
|
264
283
|
|
|
265
|
-
//
|
|
266
|
-
|
|
267
|
-
|
|
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);
|
|
268
288
|
|
|
269
|
-
// splash
|
|
270
|
-
|
|
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);
|
|
271
296
|
|
|
272
297
|
try {
|
|
273
298
|
await SdCliCapacitor._execAsync(
|
|
274
299
|
"npx",
|
|
275
|
-
[
|
|
300
|
+
[
|
|
301
|
+
"@capacitor/assets",
|
|
302
|
+
"generate",
|
|
303
|
+
"--android",
|
|
304
|
+
"--iconBackgroundColor",
|
|
305
|
+
"#ffffff",
|
|
306
|
+
"--splashBackgroundColor",
|
|
307
|
+
"#ffffff",
|
|
308
|
+
],
|
|
276
309
|
capacitorPath,
|
|
277
310
|
);
|
|
278
311
|
} catch {
|
|
279
|
-
SdCliCapacitor._logger.warn("아이콘
|
|
312
|
+
SdCliCapacitor._logger.warn("아이콘 생성 실패, 기본 아이콘 사용");
|
|
280
313
|
}
|
|
281
314
|
} else {
|
|
282
|
-
await FsUtils.removeAsync(
|
|
315
|
+
await FsUtils.removeAsync(resourcesDirPath);
|
|
283
316
|
}
|
|
284
317
|
}
|
|
285
318
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
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);
|
|
290
328
|
|
|
291
|
-
// 원본 크기 상관없이 iconSize로 리사이즈 후 여백 추가
|
|
292
329
|
await sharp(sourcePath)
|
|
293
|
-
.resize(
|
|
330
|
+
.resize(logoSize, logoSize, {
|
|
294
331
|
fit: "contain",
|
|
295
332
|
background: { r: 0, g: 0, b: 0, alpha: 0 },
|
|
296
333
|
})
|
|
@@ -304,6 +341,21 @@ export class SdCliCapacitor {
|
|
|
304
341
|
.toFile(outputPath);
|
|
305
342
|
}
|
|
306
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
|
+
|
|
307
359
|
// 7. Android 네이티브 설정
|
|
308
360
|
private async _configureAndroidNativeAsync(capacitorPath: string) {
|
|
309
361
|
const androidPath = path.resolve(capacitorPath, "android");
|
|
@@ -572,14 +624,13 @@ export class SdCliCapacitor {
|
|
|
572
624
|
const capacitorPath = path.resolve(this._opt.pkgPath, this._CAPACITOR_DIR_NAME);
|
|
573
625
|
const buildType = this._opt.config.debug ? "debug" : "release";
|
|
574
626
|
|
|
575
|
-
// 웹 자산 동기화
|
|
576
|
-
await SdCliCapacitor._execAsync("npx", ["cap", "sync"], capacitorPath);
|
|
577
|
-
|
|
578
627
|
// 플랫폼별 빌드
|
|
579
628
|
await Promise.all(
|
|
580
|
-
this._platforms.map((platform) =>
|
|
581
|
-
|
|
582
|
-
|
|
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
|
+
}),
|
|
583
634
|
);
|
|
584
635
|
}
|
|
585
636
|
|
|
@@ -736,7 +787,7 @@ export class SdCliCapacitor {
|
|
|
736
787
|
}
|
|
737
788
|
|
|
738
789
|
// cap sync 후 run
|
|
739
|
-
await
|
|
790
|
+
await SdCliCapacitor._execAsync("npx", ["cap", "copy", opt.platform], capacitorPath);
|
|
740
791
|
|
|
741
792
|
try {
|
|
742
793
|
await this._execAsync("npx", ["cap", "run", opt.platform], capacitorPath);
|
|
@@ -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
|
}
|