@simplysm/sd-cli 7.0.79 → 7.0.100
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/bin/sd-cli.mjs +13 -3
- package/dist/build-tool/SdCliCordova.d.ts +4 -4
- package/dist/build-tool/SdCliCordova.mjs +105 -110
- package/dist/build-tool/SdCliElectron.d.ts +3 -0
- package/dist/build-tool/SdCliElectron.mjs +21 -0
- package/dist/builder/SdCliClientBuilder.mjs +123 -53
- package/dist/builder/SdCliJsLibBuilder.mjs +4 -4
- package/dist/builder/SdCliTsLibBuilder.mjs +4 -4
- package/dist/commons.d.ts +25 -10
- package/dist/entry-points/SdCliLocalUpdate.mjs +3 -3
- package/dist/entry-points/SdCliWorkspace.mjs +21 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +2 -1
- package/dist/ng-tools/babel/SdCliBbFileMetadata.mjs +2 -2
- package/dist/ng-tools/babel/SdCliBbRootMetadata.d.ts +1 -0
- package/dist/ng-tools/babel/SdCliBbRootMetadata.mjs +29 -3
- package/package.json +11 -8
- package/src/bin/sd-cli.ts +18 -4
- package/src/build-tool/SdCliCordova.ts +130 -146
- package/src/build-tool/SdCliElectron.ts +25 -0
- package/src/builder/SdCliClientBuilder.ts +136 -54
- package/src/builder/SdCliJsLibBuilder.ts +3 -3
- package/src/builder/SdCliTsLibBuilder.ts +3 -3
- package/src/commons.ts +28 -10
- package/src/entry-points/SdCliLocalUpdate.ts +2 -2
- package/src/entry-points/SdCliWorkspace.ts +19 -3
- package/src/index.ts +1 -0
- package/src/ng-tools/babel/SdCliBbFileMetadata.ts +1 -1
- package/src/ng-tools/babel/SdCliBbRootMetadata.ts +30 -2
- package/tsconfig.json +3 -0
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { INpmConfig,
|
|
1
|
+
import { INpmConfig, ISdCliClientBuilderCordovaConfig } from "../commons";
|
|
2
2
|
import * as path from "path";
|
|
3
3
|
import { FsUtil, Logger, SdProcess } from "@simplysm/sd-core-node";
|
|
4
|
-
import { NotImplementError } from "@simplysm/sd-core-common";
|
|
5
4
|
import JSZip from "jszip";
|
|
6
|
-
import
|
|
5
|
+
import xml2js from "xml2js";
|
|
7
6
|
|
|
8
7
|
export class SdCliCordova {
|
|
9
8
|
protected readonly _logger: Logger;
|
|
@@ -13,11 +12,14 @@ export class SdCliCordova {
|
|
|
13
12
|
public readonly cordovaPath = path.resolve(this._rootPath, ".cordova");
|
|
14
13
|
private readonly _binPath = path.resolve(process.cwd(), "node_modules/.bin/cordova.cmd");
|
|
15
14
|
|
|
16
|
-
public get platforms():
|
|
17
|
-
return
|
|
15
|
+
public get platforms(): ("browser" | "android")[] {
|
|
16
|
+
return [
|
|
17
|
+
...this._config.targets?.browser ? ["browser" as const] : [],
|
|
18
|
+
...this._config.targets?.android ? ["android" as const] : []
|
|
19
|
+
];
|
|
18
20
|
}
|
|
19
21
|
|
|
20
|
-
public constructor(private readonly _rootPath: string, private readonly _config:
|
|
22
|
+
public constructor(private readonly _rootPath: string, private readonly _config: ISdCliClientBuilderCordovaConfig) {
|
|
21
23
|
this._npmConfig = FsUtil.readJson(path.resolve(this._rootPath, "package.json"));
|
|
22
24
|
this._logger = Logger.get(["simplysm", "sd-cli", this.constructor.name, this._npmConfig.name]);
|
|
23
25
|
}
|
|
@@ -53,156 +55,156 @@ export class SdCliCordova {
|
|
|
53
55
|
}
|
|
54
56
|
}
|
|
55
57
|
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
: undefined;
|
|
61
|
-
const alreadyPlugins = pluginsFetch != undefined
|
|
62
|
-
? Object.values(pluginsFetch)
|
|
63
|
-
.map((item: any) => (item.source.id !== undefined ? item.source.id.replace(/@.*$/, "") : item.source.url))
|
|
64
|
-
: [];
|
|
65
|
-
|
|
66
|
-
for (const plugin of this._config.plugins.distinct()) {
|
|
67
|
-
if (!alreadyPlugins.includes(plugin)) {
|
|
68
|
-
await this._execAsync(`${this._binPath} plugin add ${plugin}`, this.cordovaPath);
|
|
69
|
-
}
|
|
58
|
+
// 설치 미빌드 플랫폼 삭제
|
|
59
|
+
for (const alreadyPlatform of alreadyPlatforms) {
|
|
60
|
+
if (this._config[alreadyPlatform] == null) {
|
|
61
|
+
await this._execAsync(`${this._binPath} platform remove ${alreadyPlatform}`, this.cordovaPath);
|
|
70
62
|
}
|
|
71
63
|
}
|
|
72
64
|
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
await FsUtil.
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (this._config.icon !== undefined && !configFileContent.includes("<icon")) {
|
|
87
|
-
configFileContent = configFileContent.replace("</widget>", " <icon src=\"res/icon/icon.png\" />\r\n</widget>");
|
|
65
|
+
// 미설치 플러그인들 설치
|
|
66
|
+
const pluginsFetch = FsUtil.exists(path.resolve(this.cordovaPath, "plugins/fetch.json"))
|
|
67
|
+
? await FsUtil.readJsonAsync(path.resolve(this.cordovaPath, "plugins/fetch.json"))
|
|
68
|
+
: undefined;
|
|
69
|
+
const alreadyPlugins = pluginsFetch != undefined
|
|
70
|
+
? Object.values(pluginsFetch)
|
|
71
|
+
.map((item: any) => (item.source.id !== undefined ? item.source.id.replace(/@.*$/, "") : item.source.url))
|
|
72
|
+
: [];
|
|
73
|
+
|
|
74
|
+
for (const plugin of this._config.plugins?.distinct() ?? []) {
|
|
75
|
+
if (!alreadyPlugins.includes(plugin)) {
|
|
76
|
+
await this._execAsync(`${this._binPath} plugin add ${plugin}`, this.cordovaPath);
|
|
77
|
+
}
|
|
88
78
|
}
|
|
89
79
|
|
|
90
|
-
//
|
|
91
|
-
|
|
92
|
-
if (!
|
|
93
|
-
|
|
94
|
-
"xmlns=\"http://www.w3.org/ns/widgets\"",
|
|
95
|
-
`xmlns="http://www.w3.org/ns/widgets" xmlns:android="http://schemas.android.com/apk/res/android"`
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
if (!configFileContent.includes("application android:usesCleartextTraffic=\"true\" />")) {
|
|
99
|
-
if (configFileContent.includes("<platform name=\"android\">")) {
|
|
100
|
-
configFileContent = configFileContent.replace("<platform name=\"android\">", `<platform name="android">
|
|
101
|
-
<edit-config file="app/src/main/AndroidManifest.xml" mode="merge" target="/manifest/application">
|
|
102
|
-
<application android:usesCleartextTraffic="true" />
|
|
103
|
-
</edit-config>`);
|
|
104
|
-
}
|
|
105
|
-
else {
|
|
106
|
-
configFileContent = configFileContent.replace("<content src=\"index.html\" />", `<content src="index.html" />
|
|
107
|
-
<platform name="android">
|
|
108
|
-
<edit-config file="app/src/main/AndroidManifest.xml" mode="merge" target="/manifest/application">
|
|
109
|
-
<application android:usesCleartextTraffic="true" />
|
|
110
|
-
</edit-config>
|
|
111
|
-
</platform>`);
|
|
112
|
-
}
|
|
80
|
+
// 설치된 미사용 플러그인 삭제
|
|
81
|
+
for (const alreadyPlugin of alreadyPlugins) {
|
|
82
|
+
if (!(this._config.plugins?.distinct() ?? []).includes(alreadyPlugin)) {
|
|
83
|
+
await this._execAsync(`${this._binPath} plugin remove ${alreadyPlugin}`, this.cordovaPath);
|
|
113
84
|
}
|
|
114
85
|
}
|
|
115
86
|
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
public async buildAsync(outPath: string): Promise<void> {
|
|
121
|
-
const packageType = this._config.buildOption?.bundle ? "bundle" : "apk";
|
|
122
|
-
|
|
123
|
-
// ANDROID 서명 처리
|
|
124
|
-
if (this._config.buildOption?.sign?.android) {
|
|
87
|
+
// ANDROID SIGN 파일 복사
|
|
88
|
+
if (this._config.targets?.android?.sign) {
|
|
125
89
|
await FsUtil.copyAsync(
|
|
126
|
-
path.resolve(this._rootPath, this._config.
|
|
127
|
-
path.resolve(this.cordovaPath,
|
|
90
|
+
path.resolve(this._rootPath, this._config.targets.android.sign.keystore),
|
|
91
|
+
path.resolve(this.cordovaPath, "android.keystore")
|
|
128
92
|
);
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
await FsUtil.removeAsync(path.resolve(this.cordovaPath, "android.keystore"));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 빌드 옵션 파일 생성
|
|
99
|
+
await FsUtil.writeJsonAsync(
|
|
100
|
+
path.resolve(this.cordovaPath, "build.json"),
|
|
101
|
+
{
|
|
102
|
+
...this._config.targets?.android ? {
|
|
132
103
|
android: {
|
|
133
104
|
release: {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
105
|
+
packageType: this._config.targets.android.bundle ? "bundle" : "apk",
|
|
106
|
+
...this._config.targets.android.sign ? {
|
|
107
|
+
keystore: path.resolve(this.cordovaPath, "android.keystore"),
|
|
108
|
+
storePassword: this._config.targets.android.sign.storePassword,
|
|
109
|
+
alias: this._config.targets.android.sign.alias,
|
|
110
|
+
password: this._config.targets.android.sign.password,
|
|
111
|
+
keystoreType: this._config.targets.android.sign.keystoreType
|
|
112
|
+
} : {}
|
|
140
113
|
}
|
|
141
114
|
}
|
|
142
|
-
}
|
|
143
|
-
|
|
115
|
+
} : {}
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// ICON 파일 복사
|
|
120
|
+
if (this._config.icon !== undefined) {
|
|
121
|
+
await FsUtil.copyAsync(path.resolve(this._rootPath, this._config.icon), path.resolve(this.cordovaPath, "res", "icon.png"));
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
await FsUtil.removeAsync(path.resolve(this.cordovaPath, "res", "icon.png"));
|
|
144
125
|
}
|
|
145
126
|
|
|
146
|
-
// CONFIG
|
|
127
|
+
// CONFIG: 초기값 백업
|
|
147
128
|
const configFilePath = path.resolve(this.cordovaPath, "config.xml");
|
|
148
|
-
|
|
129
|
+
const configBackFilePath = path.resolve(this.cordovaPath, "config.xml.bak");
|
|
130
|
+
if (!FsUtil.exists(configBackFilePath)) {
|
|
131
|
+
await FsUtil.copyAsync(configFilePath, configBackFilePath);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// CONFIG: 초기값 읽기
|
|
135
|
+
const configFileContent = await FsUtil.readFileAsync(configBackFilePath);
|
|
136
|
+
const configXml = await xml2js.parseStringPromise(configFileContent);
|
|
149
137
|
|
|
150
|
-
// CONFIG: 접근허용 일단 모두 지우기
|
|
151
|
-
configFileContent = configFileContent.replace(/ {4}<allow-navigation href="[^"]*" \/>\n/g, "");
|
|
152
138
|
|
|
153
|
-
// CONFIG:
|
|
154
|
-
|
|
139
|
+
// CONFIG: 버전 설정
|
|
140
|
+
configXml.widget.$.version = this._npmConfig.version;
|
|
141
|
+
|
|
142
|
+
// CONFIG: ICON 설정
|
|
143
|
+
if (this._config.icon !== undefined) {
|
|
144
|
+
configXml["widget"]["icon"] = [{ "$": { "src": "res/icon.png" } }];
|
|
145
|
+
}
|
|
155
146
|
|
|
156
|
-
// CONFIG:
|
|
157
|
-
|
|
147
|
+
// CONFIG: 접근허용 세팅
|
|
148
|
+
configXml["widget"]["allow-navigation"] = [{ "$": { "href": "*://*/*" } }];
|
|
158
149
|
|
|
150
|
+
// CONFIG: ANDROID usesCleartextTraffic 설정
|
|
151
|
+
if (this._config.targets?.android) {
|
|
152
|
+
configXml.widget.$["xmlns:android"] = "http://schemas.android.com/apk/res/android";
|
|
153
|
+
|
|
154
|
+
configXml["widget"]["platform"] = configXml["widget"]["platform"] ?? [];
|
|
155
|
+
configXml["widget"]["platform"].push({
|
|
156
|
+
"$": {
|
|
157
|
+
"name": "android"
|
|
158
|
+
},
|
|
159
|
+
"edit-config": [{
|
|
160
|
+
"$": {
|
|
161
|
+
"file": "app/src/main/AndroidManifest.xml",
|
|
162
|
+
"mode": "merge",
|
|
163
|
+
"target": "/manifest/application"
|
|
164
|
+
},
|
|
165
|
+
"application": [{
|
|
166
|
+
"android:usesCleartextTraffic": "true"
|
|
167
|
+
}]
|
|
168
|
+
}]
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// CONFIG: 파일 새로 쓰기
|
|
173
|
+
const configResultContent = new xml2js.Builder().buildObject(configXml);
|
|
174
|
+
await FsUtil.writeFileAsync(configFilePath, configResultContent);
|
|
175
|
+
|
|
176
|
+
// 각 플랫폼 www 준비
|
|
177
|
+
await this._execAsync(`${this._binPath} prepare`, this.cordovaPath);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
public async buildAsync(outPath: string): Promise<void> {
|
|
159
181
|
// 실행
|
|
160
|
-
const buildType = this._config.
|
|
182
|
+
const buildType = this._config.debug ? "debug" : "release";
|
|
161
183
|
for (const platform of this.platforms) {
|
|
162
|
-
await this._execAsync(`${this._binPath} build ${platform} --${buildType}
|
|
184
|
+
await this._execAsync(`${this._binPath} build ${platform} --${buildType}`, this.cordovaPath);
|
|
163
185
|
}
|
|
164
186
|
|
|
165
|
-
// 결과물
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
else if (platform === "electron") {
|
|
183
|
-
const targetOutPath = path.resolve(outPath, platform);
|
|
184
|
-
const outFileName = `${this._config.appName} Setup ${this._npmConfig.version}.exe`;
|
|
185
|
-
const distFileName = outFileName;
|
|
186
|
-
const latestDistFileName = `${this._config.appName} Setup latest.exe`;
|
|
187
|
-
await FsUtil.mkdirsAsync(targetOutPath);
|
|
188
|
-
await FsUtil.copyAsync(
|
|
189
|
-
path.resolve(this.cordovaPath, "platforms/electron/build/", buildType, outFileName),
|
|
190
|
-
path.resolve(targetOutPath, distFileName)
|
|
191
|
-
);
|
|
192
|
-
await FsUtil.copyAsync(
|
|
193
|
-
path.resolve(this.cordovaPath, "platforms/electron/build/", buildType, outFileName),
|
|
194
|
-
path.resolve(targetOutPath, latestDistFileName)
|
|
195
|
-
);
|
|
196
|
-
}
|
|
197
|
-
else {
|
|
198
|
-
throw new NotImplementError();
|
|
199
|
-
}
|
|
187
|
+
// 결과물 복사: ANDROID
|
|
188
|
+
if (this._config.targets?.android) {
|
|
189
|
+
const targetOutPath = path.resolve(outPath, "android");
|
|
190
|
+
const apkFileName = this._config.targets.android.sign ? `app-${buildType}.apk` : `app-${buildType}-unsigned.apk`;
|
|
191
|
+
const distApkFileName = path.basename(`${this._config.appName}${this._config.targets.android.sign ? "" : "-unsigned"}-v${this._npmConfig.version}.apk`);
|
|
192
|
+
const latestDistApkFileName = path.basename(`${this._config.appName}${this._config.targets.android.sign ? "" : "-unsigned"}-latest.apk`);
|
|
193
|
+
await FsUtil.mkdirsAsync(targetOutPath);
|
|
194
|
+
await FsUtil.copyAsync(
|
|
195
|
+
path.resolve(this.cordovaPath, "platforms/android/app/build/outputs/apk", buildType, apkFileName),
|
|
196
|
+
path.resolve(targetOutPath, distApkFileName)
|
|
197
|
+
);
|
|
198
|
+
await FsUtil.copyAsync(
|
|
199
|
+
path.resolve(this.cordovaPath, "platforms/android/app/build/outputs/apk", buildType, apkFileName),
|
|
200
|
+
path.resolve(targetOutPath, latestDistApkFileName)
|
|
201
|
+
);
|
|
200
202
|
}
|
|
201
203
|
|
|
202
|
-
if (this.
|
|
204
|
+
if (this._config.targets?.android) {
|
|
203
205
|
// 자동업데이트를 위한 zip 파일 쓰기
|
|
204
206
|
const zip = new JSZip();
|
|
205
|
-
const resultFiles = await FsUtil.globAsync(path.resolve(this.cordovaPath, "www", "**/*"), {
|
|
207
|
+
const resultFiles = await FsUtil.globAsync(path.resolve(this.cordovaPath, "platforms", "android", "app", "src", "main", "assets", "www", "**/*"), {
|
|
206
208
|
dot: true,
|
|
207
209
|
nodir: true
|
|
208
210
|
});
|
|
@@ -217,31 +219,13 @@ export class SdCliCordova {
|
|
|
217
219
|
|
|
218
220
|
await FsUtil.writeFileAsync(path.resolve(outPath, "updates/", zipFileName), resultBuffer);
|
|
219
221
|
}
|
|
220
|
-
|
|
221
|
-
if (this.platforms.includes("electron")) {
|
|
222
|
-
const distFileName = path.basename(`${this._config.appName}-v${this._npmConfig.version}.asar`);
|
|
223
|
-
await asar.createPackage(path.resolve(this.cordovaPath, "www"), path.resolve(outPath, "updates/", distFileName));
|
|
224
|
-
}
|
|
225
222
|
}
|
|
226
223
|
|
|
227
|
-
public static async runWebviewOnDeviceAsync(cordovaPath: string, platform:
|
|
224
|
+
public static async runWebviewOnDeviceAsync(cordovaPath: string, platform: "browser" | "android", url: string): Promise<void> {
|
|
228
225
|
await FsUtil.removeAsync(path.resolve(cordovaPath, "www"));
|
|
229
226
|
await FsUtil.mkdirsAsync(path.resolve(cordovaPath, "www"));
|
|
230
227
|
await FsUtil.writeFileAsync(path.resolve(cordovaPath, "www/index.html"), `'${url}'로 이동중... <script>setTimeout(function () {window.location.href = "${url}"}, 3000);</script>`.trim());
|
|
231
228
|
|
|
232
|
-
// CONFIG
|
|
233
|
-
const configFilePath = path.resolve(cordovaPath, "config.xml");
|
|
234
|
-
let configFileContent = await FsUtil.readFileAsync(configFilePath);
|
|
235
|
-
|
|
236
|
-
// CONFIG: 접근허용 일단 모두 지우기
|
|
237
|
-
configFileContent = configFileContent.replace(/ {4}<allow-navigation href="[^"]*" \/>\n/g, "");
|
|
238
|
-
|
|
239
|
-
// CONFIG: 접근허용 등록
|
|
240
|
-
configFileContent = configFileContent.replace("</widget>", ` <allow-navigation href="*://*/*" />\n</widget>`);
|
|
241
|
-
|
|
242
|
-
// CONFIG: 파일쓰기
|
|
243
|
-
await FsUtil.writeFileAsync(configFilePath, configFileContent);
|
|
244
|
-
|
|
245
229
|
const binPath = path.resolve(process.cwd(), "node_modules/.bin/cordova.cmd");
|
|
246
230
|
await SdProcess.spawnAsync(`${binPath} run ${platform} --device`, { cwd: cordovaPath }, true);
|
|
247
231
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { FsUtil, SdProcess } from "@simplysm/sd-core-node";
|
|
2
|
+
import { INpmConfig } from "../commons";
|
|
3
|
+
import path from "path";
|
|
4
|
+
|
|
5
|
+
export class SdCliElectron {
|
|
6
|
+
public static async runWebviewOnDeviceAsync(rootPath: string, pkgName: string): Promise<void> {
|
|
7
|
+
const electronPath = path.resolve(rootPath, `packages/${pkgName}/.electron`);
|
|
8
|
+
|
|
9
|
+
const npmConfig = (await FsUtil.readJsonAsync(path.resolve(rootPath, `packages/${pkgName}/package.json`))) as INpmConfig;
|
|
10
|
+
await FsUtil.writeJsonAsync(path.resolve(electronPath, `package.json`), {
|
|
11
|
+
name: npmConfig.name,
|
|
12
|
+
version: npmConfig.version,
|
|
13
|
+
description: npmConfig.description,
|
|
14
|
+
main: "electron.js",
|
|
15
|
+
author: npmConfig.author,
|
|
16
|
+
license: npmConfig.license
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
await FsUtil.copyAsync(path.resolve(rootPath, `packages/${pkgName}/src/electron.dev.js`), path.resolve(electronPath, "electron.js"));
|
|
20
|
+
|
|
21
|
+
await SdProcess.spawnAsync(`electron "${electronPath}"`, {
|
|
22
|
+
cwd: rootPath
|
|
23
|
+
}, true);
|
|
24
|
+
}
|
|
25
|
+
}
|