@akanjs/devkit 0.9.57 → 0.9.58-canary.1

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.
@@ -33,6 +33,7 @@ module.exports = __toCommonJS(capacitorApp_exports);
33
33
  var import_common = require("@akanjs/common");
34
34
  var import_project = require("@trapezedev/project");
35
35
  var import_fs = __toESM(require("fs"));
36
+ var import_fileEditor = require("./fileEditor");
36
37
  class CapacitorApp {
37
38
  constructor(app) {
38
39
  this.app = app;
@@ -66,69 +67,151 @@ class CapacitorApp {
66
67
  await this.app.spawn("npx", ["@capacitor/assets", "generate"]);
67
68
  } else
68
69
  this.app.verbose(`iOS already added, skip adding process`);
70
+ this.app.verbose(`syncing iOS`);
69
71
  await this.app.spawn("npx", ["cap", "sync", "ios"]);
72
+ this.app.verbose(`sync completed.`);
70
73
  }
71
74
  async buildIos() {
72
75
  await this.#prepareIos();
73
- await this.app.spawn("npx", ["cap", "run", "ios"]);
76
+ this.app.verbose(`build completed iOS.`);
77
+ return;
78
+ }
79
+ async syncIos() {
80
+ await this.app.spawn("npx", ["cap", "sync", "ios"]);
74
81
  }
75
82
  async openIos() {
76
83
  await this.app.spawn("npx", ["cap", "open", "ios"]);
77
84
  }
78
- async runIos({ operation, bundleId, version = "0.0.1", buildNum = 1 }) {
85
+ async runIos({ operation, appId, version = "0.0.1", buildNum = 1, host = "local" }) {
86
+ const defaultAppId = `com.${this.app.name}.app`;
79
87
  await this.#prepareIos();
80
- this.project.ios.setBundleId("App", "Debug", bundleId);
81
- this.project.ios.setBundleId("App", "Release", bundleId);
88
+ this.project.ios.setBundleId("App", "Debug", appId ?? defaultAppId);
89
+ this.project.ios.setBundleId("App", "Release", appId ?? defaultAppId);
82
90
  await this.project.ios.setVersion("App", "Debug", version);
83
91
  await this.project.ios.setVersion("App", "Release", version);
84
92
  await this.project.ios.setBuild("App", "Debug", buildNum);
85
93
  await this.project.ios.setBuild("App", "Release", buildNum);
86
94
  await this.project.commit();
87
- await this.app.spawn("npx", [
88
- "cross-env",
89
- `APP_OPERATION_MODE=${operation}`,
95
+ await this.app.spawn(
90
96
  "npx",
91
- "cap",
92
- "run",
93
- "ios",
94
- "--live-reload",
95
- "--port",
96
- "4201"
97
- ]);
97
+ [
98
+ "cross-env",
99
+ `APP_OPERATION_MODE=${operation}`,
100
+ `NEXT_PUBLIC_ENV=${host}`,
101
+ "npx",
102
+ "cap",
103
+ "run",
104
+ "ios",
105
+ "--live-reload",
106
+ operation === "release" ? "" : "--live-reload",
107
+ operation === "release" ? "" : "--port",
108
+ operation === "release" ? "" : "4201"
109
+ ],
110
+ {
111
+ stdio: "inherit"
112
+ }
113
+ );
98
114
  }
99
115
  async #prepareAndroid() {
100
116
  const isAdded = import_fs.default.existsSync(`${this.app.cwdPath}/android/app/build.gradle`);
101
117
  if (!isAdded) {
102
118
  await this.app.spawn("npx", ["cap", "add", "android"]);
103
- await this.app.spawn("npx", ["@capacitor/assets", "generate"]);
104
119
  } else
105
120
  this.app.verbose(`Android already added, skip adding process`);
121
+ await this.app.spawn("npx", ["@capacitor/assets", "generate"]);
106
122
  await this.app.spawn("npx", ["cap", "sync", "android"]);
107
123
  }
108
- async buildAndroid() {
124
+ #updateAndroidBuildTypes() {
125
+ const appGradle = new import_fileEditor.FileEditor(`${this.app.cwdPath}/android/app/build.gradle`);
126
+ const buildTypesBlock = `
127
+ debug {
128
+ applicationIdSuffix ".debug"
129
+ versionNameSuffix "-DEBUG"
130
+ debuggable true
131
+ minifyEnabled false
132
+ }
133
+ `;
134
+ const singinConfigBlock = `
135
+ signingConfigs {
136
+ debug {
137
+ storeFile file('debug.keystore')
138
+ storePassword 'android'
139
+ keyAlias 'androiddebugkey'
140
+ keyPassword 'android'
141
+ }
142
+ release {
143
+ if (project.hasProperty('MYAPP_RELEASE_STORE_FILE')) {
144
+ storeFile file(MYAPP_RELEASE_STORE_FILE)
145
+ storePassword MYAPP_RELEASE_STORE_PASSWORD
146
+ keyAlias MYAPP_RELEASE_KEY_ALIAS
147
+ keyPassword MYAPP_RELEASE_KEY_PASSWORD
148
+ }
149
+ }
150
+ }
151
+ `;
152
+ if (appGradle.find("signingConfigs {") === -1) {
153
+ appGradle.insertBefore("buildTypes {", singinConfigBlock);
154
+ }
155
+ if (appGradle.find(`applicationIdSuffix ".debug"`) === -1) {
156
+ appGradle.insertAfter("buildTypes {", buildTypesBlock);
157
+ }
158
+ appGradle.save();
159
+ }
160
+ async buildAndroid(assembleType) {
109
161
  await this.#prepareAndroid();
110
- await this.app.spawn("npx", ["cap", "build", "android"]);
162
+ this.#updateAndroidBuildTypes();
163
+ const isWindows = process.platform === "win32";
164
+ const gradleCommand = isWindows ? "gradlew.bat" : "./gradlew";
165
+ await this.app.spawn(gradleCommand, [assembleType === "apk" ? "assembleRelease" : "bundleRelease"], {
166
+ stdio: "inherit",
167
+ cwd: `${this.app.cwdPath}/android`
168
+ });
111
169
  }
112
170
  async openAndroid() {
113
171
  await this.app.spawn("npx", ["cap", "open", "android"]);
114
172
  }
115
- async runAndroid({ operation, appName, bundleId, version = "0.0.1", buildNum = 1 }) {
173
+ async syncAndroid() {
174
+ await this.#prepareAndroid();
175
+ this.app.log(`Sync Android Completed.`);
176
+ }
177
+ async runAndroid({ operation, appName, appId, version = "0.0.1", buildNum = 1, host = "local" }) {
178
+ const defaultAppId = `com.${this.app.name}.app`;
179
+ const defaultAppName = this.app.name;
116
180
  await this.project.android.setVersionName(version);
181
+ await this.project.android.setPackageName(appId ?? defaultAppId);
117
182
  await this.project.android.setVersionCode(buildNum);
118
- await this.project.android.setPackageName(bundleId);
119
- await this.project.android.setAppName(appName);
120
- await this.app.spawn("npx", [
121
- "cross-env",
122
- `APP_OPERATION_MODE=${operation}`,
183
+ const versionName = await this.project.android.getVersionName();
184
+ const versionCode = await this.project.android.getVersionCode();
185
+ await this.project.android.setAppName(appName ?? defaultAppName);
186
+ await this.project.commit();
187
+ await this.#prepareAndroid();
188
+ this.app.logger.info(`Running Android in ${operation} mode on ${host} host`);
189
+ await this.app.spawn(
123
190
  "npx",
124
- "cap",
125
- "run",
126
- "android",
127
- "--live-reload",
128
- "--port",
129
- "--list",
130
- "4201"
131
- ]);
191
+ [
192
+ "cross-env",
193
+ `NEXT_PUBLIC_ENV=${host}`,
194
+ `APP_OPERATION_MODE=${operation}`,
195
+ "npx",
196
+ "cap",
197
+ "run",
198
+ "android",
199
+ operation === "release" ? "" : "--live-reload",
200
+ operation === "release" ? "" : "--port",
201
+ operation === "release" ? "" : "4201"
202
+ ],
203
+ {
204
+ stdio: "inherit"
205
+ }
206
+ );
207
+ }
208
+ //? 릴리즈시 buildNum +1 version 파라미터 받아서 업데이트
209
+ async updateAndroidVersion(version, buildNum) {
210
+ await this.project.android.setVersionName(version);
211
+ await this.project.android.setVersionCode(buildNum);
212
+ const versionName = await this.project.android.getVersionName();
213
+ const versionCode = await this.project.android.getVersionCode();
214
+ await this.project.commit();
132
215
  }
133
216
  async releaseIos() {
134
217
  const isAdded = import_fs.default.existsSync(`${this.app.cwdPath}/ios/App/Podfile`);
@@ -114,13 +114,13 @@ class Executor {
114
114
  let stdout = "";
115
115
  let stderr = "";
116
116
  proc.stdout?.on("data", (data) => {
117
- stdout += data;
118
- this.logs.push(data);
117
+ stdout += data.toString();
118
+ this.logs.push(data.toString());
119
119
  this.#stdout(data);
120
120
  });
121
121
  proc.stderr?.on("data", (data) => {
122
- stderr += data;
123
- this.logs.push(data);
122
+ stderr += data.toString();
123
+ this.logs.push(data.toString());
124
124
  this.#stdout(data);
125
125
  });
126
126
  return new Promise((resolve, reject) => {
@@ -286,6 +286,10 @@ class Executor {
286
286
  this.logger.verbose(msg);
287
287
  return this;
288
288
  }
289
+ debug(msg) {
290
+ this.logger.debug(msg);
291
+ return this;
292
+ }
289
293
  spinning(msg, { prefix = `${this.emoji}${this.name}`, indent = 0, enableSpin = !Executor.verbose } = {}) {
290
294
  return new import_spinner.Spinner(msg, { prefix, indent, enableSpin }).start();
291
295
  }
@@ -836,10 +840,8 @@ class AppExecutor extends SysExecutor {
836
840
  await Promise.all([this.removeDir(projectPublicLibPath), this.removeDir(projectAssetsLibPath)]);
837
841
  const targetPublicDeps = libDeps.filter((dep) => this.exists(`${this.workspace.workspaceRoot}/libs/${dep}/public`));
838
842
  const targetAssetsDeps = libDeps.filter((dep) => this.exists(`${this.workspace.workspaceRoot}/libs/${dep}/assets`));
839
- await Promise.all([
840
- ...targetPublicDeps.map((dep) => this.mkdir(`${projectPublicLibPath}/${dep}`)),
841
- ...targetAssetsDeps.map((dep) => this.mkdir(`${projectAssetsLibPath}/${dep}`))
842
- ]);
843
+ targetPublicDeps.forEach((dep) => this.mkdir(`${projectPublicLibPath}/${dep}`));
844
+ targetAssetsDeps.forEach((dep) => this.mkdir(`${projectAssetsLibPath}/${dep}`));
843
845
  await Promise.all([
844
846
  ...targetPublicDeps.map(
845
847
  (dep) => this.cp(`${this.workspace.workspaceRoot}/libs/${dep}/public`, `${projectPublicLibPath}/${dep}`)
@@ -849,6 +851,12 @@ class AppExecutor extends SysExecutor {
849
851
  )
850
852
  ]);
851
853
  }
854
+ async increaseBuildNum() {
855
+ await (0, import_config.increaseBuildNum)(this.cwdPath, { ...this.workspace.getBaseDevEnv(), type: "app", name: this.name });
856
+ }
857
+ async decreaseBuildNum() {
858
+ await (0, import_config.decreaseBuildNum)(this.cwdPath, { ...this.workspace.getBaseDevEnv(), type: "app", name: this.name });
859
+ }
852
860
  }
853
861
  class LibExecutor extends SysExecutor {
854
862
  workspaceRoot;
@@ -0,0 +1,120 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+ var fileEditor_exports = {};
29
+ __export(fileEditor_exports, {
30
+ FileEditor: () => FileEditor
31
+ });
32
+ module.exports = __toCommonJS(fileEditor_exports);
33
+ var import_fs = __toESM(require("fs"));
34
+ class FileEditor {
35
+ filePath;
36
+ content;
37
+ constructor(filePath) {
38
+ this.filePath = filePath;
39
+ this.content = this.#readFile();
40
+ }
41
+ #readFile() {
42
+ try {
43
+ return import_fs.default.readFileSync(this.filePath, "utf-8");
44
+ } catch (error) {
45
+ throw new Error(`Failed to read file: ${this.filePath}`);
46
+ }
47
+ }
48
+ find(pattern) {
49
+ const lines = this.content.split("\n");
50
+ const regex = typeof pattern === "string" ? new RegExp(pattern) : pattern;
51
+ for (let i = 0; i < lines.length; i++) {
52
+ if (regex.test(lines[i])) {
53
+ return i;
54
+ }
55
+ }
56
+ return -1;
57
+ }
58
+ findAll(pattern) {
59
+ const lines = this.content.split("\n");
60
+ const regex = typeof pattern === "string" ? new RegExp(pattern) : pattern;
61
+ const matches = [];
62
+ for (let i = 0; i < lines.length; i++) {
63
+ if (regex.test(lines[i])) {
64
+ matches.push(i);
65
+ }
66
+ }
67
+ return matches;
68
+ }
69
+ insertAfter(pattern, data) {
70
+ const lineIndex = this.find(pattern);
71
+ if (lineIndex === -1) {
72
+ throw new Error(`Pattern not found: ${pattern}`);
73
+ }
74
+ const lines = this.content.split("\n");
75
+ lines.splice(lineIndex + 1, 0, data);
76
+ this.content = lines.join("\n");
77
+ return this;
78
+ }
79
+ insertBefore(pattern, data) {
80
+ const lineIndex = this.find(pattern);
81
+ if (lineIndex === -1) {
82
+ throw new Error(`Pattern not found: ${pattern}`);
83
+ }
84
+ const lines = this.content.split("\n");
85
+ lines.splice(lineIndex, 0, data);
86
+ this.content = lines.join("\n");
87
+ return this;
88
+ }
89
+ replace(pattern, replacement) {
90
+ const regex = typeof pattern === "string" ? new RegExp(pattern, "g") : pattern;
91
+ this.content = this.content.replace(regex, replacement);
92
+ return this;
93
+ }
94
+ append(data) {
95
+ this.content += "\n" + data;
96
+ return this;
97
+ }
98
+ prepend(data) {
99
+ this.content = data + "\n" + this.content;
100
+ return this;
101
+ }
102
+ save() {
103
+ try {
104
+ import_fs.default.writeFileSync(this.filePath, this.content, "utf-8");
105
+ } catch (error) {
106
+ throw new Error(`Failed to save file: ${this.filePath}`);
107
+ }
108
+ }
109
+ getContent() {
110
+ return this.content;
111
+ }
112
+ setContent(content) {
113
+ this.content = content;
114
+ return this;
115
+ }
116
+ }
117
+ // Annotate the CommonJS export names for ESM import in node:
118
+ 0 && (module.exports = {
119
+ FileEditor
120
+ });
@@ -44,6 +44,7 @@ const uploadRelease = async (appName, {
44
44
  environment,
45
45
  buildNum,
46
46
  platformVersion,
47
+ os,
47
48
  local
48
49
  }) => {
49
50
  const logger = new import_common.Logger("uploadRelease");
@@ -87,7 +88,7 @@ const uploadRelease = async (appName, {
87
88
  fetchingAppSpinner.succeed(`Fetching dev app information for ${appName}... done`);
88
89
  const pushingReleaseSpinner = spinning(`Pushing release to ${environment} environment...`);
89
90
  const release = (await import_axios.default.post(
90
- `${basePath}/release/pushRelease/${devApp.id}/${environment}/${major}/${minor}/${sourceFile.id}/${buildFile.id}/${appBuildFile.id}`
91
+ `${basePath}/release/pushRelease/${devApp.id}/${environment}/${major}/${minor}/${patch}/${sourceFile.id}/${buildFile.id}/${appBuildFile.id}${os ? `/${os}` : ""}`
91
92
  )).data;
92
93
  pushingReleaseSpinner.succeed(`Pushing release to ${environment} environment... done`);
93
94
  new import_spinner.Spinner(`Successfully pushed release to ${appName}-${environment} server. `, {
@@ -1,6 +1,7 @@
1
1
  import { capitalize } from "@akanjs/common";
2
2
  import { MobileProject } from "@trapezedev/project";
3
3
  import fs from "fs";
4
+ import { FileEditor } from "./fileEditor";
4
5
  class CapacitorApp {
5
6
  constructor(app) {
6
7
  this.app = app;
@@ -34,69 +35,151 @@ class CapacitorApp {
34
35
  await this.app.spawn("npx", ["@capacitor/assets", "generate"]);
35
36
  } else
36
37
  this.app.verbose(`iOS already added, skip adding process`);
38
+ this.app.verbose(`syncing iOS`);
37
39
  await this.app.spawn("npx", ["cap", "sync", "ios"]);
40
+ this.app.verbose(`sync completed.`);
38
41
  }
39
42
  async buildIos() {
40
43
  await this.#prepareIos();
41
- await this.app.spawn("npx", ["cap", "run", "ios"]);
44
+ this.app.verbose(`build completed iOS.`);
45
+ return;
46
+ }
47
+ async syncIos() {
48
+ await this.app.spawn("npx", ["cap", "sync", "ios"]);
42
49
  }
43
50
  async openIos() {
44
51
  await this.app.spawn("npx", ["cap", "open", "ios"]);
45
52
  }
46
- async runIos({ operation, bundleId, version = "0.0.1", buildNum = 1 }) {
53
+ async runIos({ operation, appId, version = "0.0.1", buildNum = 1, host = "local" }) {
54
+ const defaultAppId = `com.${this.app.name}.app`;
47
55
  await this.#prepareIos();
48
- this.project.ios.setBundleId("App", "Debug", bundleId);
49
- this.project.ios.setBundleId("App", "Release", bundleId);
56
+ this.project.ios.setBundleId("App", "Debug", appId ?? defaultAppId);
57
+ this.project.ios.setBundleId("App", "Release", appId ?? defaultAppId);
50
58
  await this.project.ios.setVersion("App", "Debug", version);
51
59
  await this.project.ios.setVersion("App", "Release", version);
52
60
  await this.project.ios.setBuild("App", "Debug", buildNum);
53
61
  await this.project.ios.setBuild("App", "Release", buildNum);
54
62
  await this.project.commit();
55
- await this.app.spawn("npx", [
56
- "cross-env",
57
- `APP_OPERATION_MODE=${operation}`,
63
+ await this.app.spawn(
58
64
  "npx",
59
- "cap",
60
- "run",
61
- "ios",
62
- "--live-reload",
63
- "--port",
64
- "4201"
65
- ]);
65
+ [
66
+ "cross-env",
67
+ `APP_OPERATION_MODE=${operation}`,
68
+ `NEXT_PUBLIC_ENV=${host}`,
69
+ "npx",
70
+ "cap",
71
+ "run",
72
+ "ios",
73
+ "--live-reload",
74
+ operation === "release" ? "" : "--live-reload",
75
+ operation === "release" ? "" : "--port",
76
+ operation === "release" ? "" : "4201"
77
+ ],
78
+ {
79
+ stdio: "inherit"
80
+ }
81
+ );
66
82
  }
67
83
  async #prepareAndroid() {
68
84
  const isAdded = fs.existsSync(`${this.app.cwdPath}/android/app/build.gradle`);
69
85
  if (!isAdded) {
70
86
  await this.app.spawn("npx", ["cap", "add", "android"]);
71
- await this.app.spawn("npx", ["@capacitor/assets", "generate"]);
72
87
  } else
73
88
  this.app.verbose(`Android already added, skip adding process`);
89
+ await this.app.spawn("npx", ["@capacitor/assets", "generate"]);
74
90
  await this.app.spawn("npx", ["cap", "sync", "android"]);
75
91
  }
76
- async buildAndroid() {
92
+ #updateAndroidBuildTypes() {
93
+ const appGradle = new FileEditor(`${this.app.cwdPath}/android/app/build.gradle`);
94
+ const buildTypesBlock = `
95
+ debug {
96
+ applicationIdSuffix ".debug"
97
+ versionNameSuffix "-DEBUG"
98
+ debuggable true
99
+ minifyEnabled false
100
+ }
101
+ `;
102
+ const singinConfigBlock = `
103
+ signingConfigs {
104
+ debug {
105
+ storeFile file('debug.keystore')
106
+ storePassword 'android'
107
+ keyAlias 'androiddebugkey'
108
+ keyPassword 'android'
109
+ }
110
+ release {
111
+ if (project.hasProperty('MYAPP_RELEASE_STORE_FILE')) {
112
+ storeFile file(MYAPP_RELEASE_STORE_FILE)
113
+ storePassword MYAPP_RELEASE_STORE_PASSWORD
114
+ keyAlias MYAPP_RELEASE_KEY_ALIAS
115
+ keyPassword MYAPP_RELEASE_KEY_PASSWORD
116
+ }
117
+ }
118
+ }
119
+ `;
120
+ if (appGradle.find("signingConfigs {") === -1) {
121
+ appGradle.insertBefore("buildTypes {", singinConfigBlock);
122
+ }
123
+ if (appGradle.find(`applicationIdSuffix ".debug"`) === -1) {
124
+ appGradle.insertAfter("buildTypes {", buildTypesBlock);
125
+ }
126
+ appGradle.save();
127
+ }
128
+ async buildAndroid(assembleType) {
77
129
  await this.#prepareAndroid();
78
- await this.app.spawn("npx", ["cap", "build", "android"]);
130
+ this.#updateAndroidBuildTypes();
131
+ const isWindows = process.platform === "win32";
132
+ const gradleCommand = isWindows ? "gradlew.bat" : "./gradlew";
133
+ await this.app.spawn(gradleCommand, [assembleType === "apk" ? "assembleRelease" : "bundleRelease"], {
134
+ stdio: "inherit",
135
+ cwd: `${this.app.cwdPath}/android`
136
+ });
79
137
  }
80
138
  async openAndroid() {
81
139
  await this.app.spawn("npx", ["cap", "open", "android"]);
82
140
  }
83
- async runAndroid({ operation, appName, bundleId, version = "0.0.1", buildNum = 1 }) {
141
+ async syncAndroid() {
142
+ await this.#prepareAndroid();
143
+ this.app.log(`Sync Android Completed.`);
144
+ }
145
+ async runAndroid({ operation, appName, appId, version = "0.0.1", buildNum = 1, host = "local" }) {
146
+ const defaultAppId = `com.${this.app.name}.app`;
147
+ const defaultAppName = this.app.name;
84
148
  await this.project.android.setVersionName(version);
149
+ await this.project.android.setPackageName(appId ?? defaultAppId);
85
150
  await this.project.android.setVersionCode(buildNum);
86
- await this.project.android.setPackageName(bundleId);
87
- await this.project.android.setAppName(appName);
88
- await this.app.spawn("npx", [
89
- "cross-env",
90
- `APP_OPERATION_MODE=${operation}`,
151
+ const versionName = await this.project.android.getVersionName();
152
+ const versionCode = await this.project.android.getVersionCode();
153
+ await this.project.android.setAppName(appName ?? defaultAppName);
154
+ await this.project.commit();
155
+ await this.#prepareAndroid();
156
+ this.app.logger.info(`Running Android in ${operation} mode on ${host} host`);
157
+ await this.app.spawn(
91
158
  "npx",
92
- "cap",
93
- "run",
94
- "android",
95
- "--live-reload",
96
- "--port",
97
- "--list",
98
- "4201"
99
- ]);
159
+ [
160
+ "cross-env",
161
+ `NEXT_PUBLIC_ENV=${host}`,
162
+ `APP_OPERATION_MODE=${operation}`,
163
+ "npx",
164
+ "cap",
165
+ "run",
166
+ "android",
167
+ operation === "release" ? "" : "--live-reload",
168
+ operation === "release" ? "" : "--port",
169
+ operation === "release" ? "" : "4201"
170
+ ],
171
+ {
172
+ stdio: "inherit"
173
+ }
174
+ );
175
+ }
176
+ //? 릴리즈시 buildNum +1 version 파라미터 받아서 업데이트
177
+ async updateAndroidVersion(version, buildNum) {
178
+ await this.project.android.setVersionName(version);
179
+ await this.project.android.setVersionCode(buildNum);
180
+ const versionName = await this.project.android.getVersionName();
181
+ const versionCode = await this.project.android.getVersionCode();
182
+ await this.project.commit();
100
183
  }
101
184
  async releaseIos() {
102
185
  const isAdded = fs.existsSync(`${this.app.cwdPath}/ios/App/Podfile`);
@@ -1,5 +1,10 @@
1
1
  import { capitalize, Logger } from "@akanjs/common";
2
- import { getAppConfig, getLibConfig } from "@akanjs/config";
2
+ import {
3
+ decreaseBuildNum,
4
+ getAppConfig,
5
+ getLibConfig,
6
+ increaseBuildNum
7
+ } from "@akanjs/config";
3
8
  import chalk from "chalk";
4
9
  import { exec, fork, spawn } from "child_process";
5
10
  import dotenv from "dotenv";
@@ -74,13 +79,13 @@ class Executor {
74
79
  let stdout = "";
75
80
  let stderr = "";
76
81
  proc.stdout?.on("data", (data) => {
77
- stdout += data;
78
- this.logs.push(data);
82
+ stdout += data.toString();
83
+ this.logs.push(data.toString());
79
84
  this.#stdout(data);
80
85
  });
81
86
  proc.stderr?.on("data", (data) => {
82
- stderr += data;
83
- this.logs.push(data);
87
+ stderr += data.toString();
88
+ this.logs.push(data.toString());
84
89
  this.#stdout(data);
85
90
  });
86
91
  return new Promise((resolve, reject) => {
@@ -246,6 +251,10 @@ class Executor {
246
251
  this.logger.verbose(msg);
247
252
  return this;
248
253
  }
254
+ debug(msg) {
255
+ this.logger.debug(msg);
256
+ return this;
257
+ }
249
258
  spinning(msg, { prefix = `${this.emoji}${this.name}`, indent = 0, enableSpin = !Executor.verbose } = {}) {
250
259
  return new Spinner(msg, { prefix, indent, enableSpin }).start();
251
260
  }
@@ -796,10 +805,8 @@ class AppExecutor extends SysExecutor {
796
805
  await Promise.all([this.removeDir(projectPublicLibPath), this.removeDir(projectAssetsLibPath)]);
797
806
  const targetPublicDeps = libDeps.filter((dep) => this.exists(`${this.workspace.workspaceRoot}/libs/${dep}/public`));
798
807
  const targetAssetsDeps = libDeps.filter((dep) => this.exists(`${this.workspace.workspaceRoot}/libs/${dep}/assets`));
799
- await Promise.all([
800
- ...targetPublicDeps.map((dep) => this.mkdir(`${projectPublicLibPath}/${dep}`)),
801
- ...targetAssetsDeps.map((dep) => this.mkdir(`${projectAssetsLibPath}/${dep}`))
802
- ]);
808
+ targetPublicDeps.forEach((dep) => this.mkdir(`${projectPublicLibPath}/${dep}`));
809
+ targetAssetsDeps.forEach((dep) => this.mkdir(`${projectAssetsLibPath}/${dep}`));
803
810
  await Promise.all([
804
811
  ...targetPublicDeps.map(
805
812
  (dep) => this.cp(`${this.workspace.workspaceRoot}/libs/${dep}/public`, `${projectPublicLibPath}/${dep}`)
@@ -809,6 +816,12 @@ class AppExecutor extends SysExecutor {
809
816
  )
810
817
  ]);
811
818
  }
819
+ async increaseBuildNum() {
820
+ await increaseBuildNum(this.cwdPath, { ...this.workspace.getBaseDevEnv(), type: "app", name: this.name });
821
+ }
822
+ async decreaseBuildNum() {
823
+ await decreaseBuildNum(this.cwdPath, { ...this.workspace.getBaseDevEnv(), type: "app", name: this.name });
824
+ }
812
825
  }
813
826
  class LibExecutor extends SysExecutor {
814
827
  workspaceRoot;
@@ -0,0 +1,87 @@
1
+ import fs from "fs";
2
+ class FileEditor {
3
+ filePath;
4
+ content;
5
+ constructor(filePath) {
6
+ this.filePath = filePath;
7
+ this.content = this.#readFile();
8
+ }
9
+ #readFile() {
10
+ try {
11
+ return fs.readFileSync(this.filePath, "utf-8");
12
+ } catch (error) {
13
+ throw new Error(`Failed to read file: ${this.filePath}`);
14
+ }
15
+ }
16
+ find(pattern) {
17
+ const lines = this.content.split("\n");
18
+ const regex = typeof pattern === "string" ? new RegExp(pattern) : pattern;
19
+ for (let i = 0; i < lines.length; i++) {
20
+ if (regex.test(lines[i])) {
21
+ return i;
22
+ }
23
+ }
24
+ return -1;
25
+ }
26
+ findAll(pattern) {
27
+ const lines = this.content.split("\n");
28
+ const regex = typeof pattern === "string" ? new RegExp(pattern) : pattern;
29
+ const matches = [];
30
+ for (let i = 0; i < lines.length; i++) {
31
+ if (regex.test(lines[i])) {
32
+ matches.push(i);
33
+ }
34
+ }
35
+ return matches;
36
+ }
37
+ insertAfter(pattern, data) {
38
+ const lineIndex = this.find(pattern);
39
+ if (lineIndex === -1) {
40
+ throw new Error(`Pattern not found: ${pattern}`);
41
+ }
42
+ const lines = this.content.split("\n");
43
+ lines.splice(lineIndex + 1, 0, data);
44
+ this.content = lines.join("\n");
45
+ return this;
46
+ }
47
+ insertBefore(pattern, data) {
48
+ const lineIndex = this.find(pattern);
49
+ if (lineIndex === -1) {
50
+ throw new Error(`Pattern not found: ${pattern}`);
51
+ }
52
+ const lines = this.content.split("\n");
53
+ lines.splice(lineIndex, 0, data);
54
+ this.content = lines.join("\n");
55
+ return this;
56
+ }
57
+ replace(pattern, replacement) {
58
+ const regex = typeof pattern === "string" ? new RegExp(pattern, "g") : pattern;
59
+ this.content = this.content.replace(regex, replacement);
60
+ return this;
61
+ }
62
+ append(data) {
63
+ this.content += "\n" + data;
64
+ return this;
65
+ }
66
+ prepend(data) {
67
+ this.content = data + "\n" + this.content;
68
+ return this;
69
+ }
70
+ save() {
71
+ try {
72
+ fs.writeFileSync(this.filePath, this.content, "utf-8");
73
+ } catch (error) {
74
+ throw new Error(`Failed to save file: ${this.filePath}`);
75
+ }
76
+ }
77
+ getContent() {
78
+ return this.content;
79
+ }
80
+ setContent(content) {
81
+ this.content = content;
82
+ return this;
83
+ }
84
+ }
85
+ export {
86
+ FileEditor
87
+ };
@@ -12,6 +12,7 @@ const uploadRelease = async (appName, {
12
12
  environment,
13
13
  buildNum,
14
14
  platformVersion,
15
+ os,
15
16
  local
16
17
  }) => {
17
18
  const logger = new Logger("uploadRelease");
@@ -55,7 +56,7 @@ const uploadRelease = async (appName, {
55
56
  fetchingAppSpinner.succeed(`Fetching dev app information for ${appName}... done`);
56
57
  const pushingReleaseSpinner = spinning(`Pushing release to ${environment} environment...`);
57
58
  const release = (await axios.post(
58
- `${basePath}/release/pushRelease/${devApp.id}/${environment}/${major}/${minor}/${sourceFile.id}/${buildFile.id}/${appBuildFile.id}`
59
+ `${basePath}/release/pushRelease/${devApp.id}/${environment}/${major}/${minor}/${patch}/${sourceFile.id}/${buildFile.id}/${appBuildFile.id}${os ? `/${os}` : ""}`
59
60
  )).data;
60
61
  pushingReleaseSpinner.succeed(`Pushing release to ${environment} environment... done`);
61
62
  new Spinner(`Successfully pushed release to ${appName}-${environment} server. `, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/devkit",
3
- "version": "0.9.57",
3
+ "version": "0.9.58-canary.1",
4
4
  "sourceType": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -31,7 +31,7 @@
31
31
  "ink": "^6.1.0",
32
32
  "js-yaml": "^4.1.0",
33
33
  "ora": "^3.4.0",
34
- "react": "19.1.1",
34
+ "react": "19.2.0",
35
35
  "reflect-metadata": "^0.2.2",
36
36
  "tunnel-ssh": "^5.2.0",
37
37
  "typescript": "5.8.3"
@@ -1,13 +1,14 @@
1
1
  import type { AppExecutor } from "@akanjs/devkit";
2
+ import { CapacitorConfig } from "@capacitor/cli";
2
3
  import { MobileProject } from "@trapezedev/project";
3
4
  import type { AndroidProject } from "@trapezedev/project/dist/android/project";
4
5
  import type { IosProject } from "@trapezedev/project/dist/ios/project";
5
- interface RunConfig {
6
+ interface RunConfig extends CapacitorConfig {
6
7
  operation: "local" | "release";
7
- appName: string;
8
- bundleId: string;
9
- version?: string;
10
- buildNum?: number;
8
+ version: string;
9
+ buildNum: number;
10
+ appId?: string;
11
+ host?: "local" | "debug" | "develop" | "main";
11
12
  }
12
13
  export declare class CapacitorApp {
13
14
  #private;
@@ -21,11 +22,14 @@ export declare class CapacitorApp {
21
22
  init(): Promise<this>;
22
23
  save(): Promise<void>;
23
24
  buildIos(): Promise<void>;
25
+ syncIos(): Promise<void>;
24
26
  openIos(): Promise<void>;
25
- runIos({ operation, bundleId, version, buildNum }: RunConfig): Promise<void>;
26
- buildAndroid(): Promise<void>;
27
+ runIos({ operation, appId, version, buildNum, host }: RunConfig): Promise<void>;
28
+ buildAndroid(assembleType: "apk" | "aab"): Promise<void>;
27
29
  openAndroid(): Promise<void>;
28
- runAndroid({ operation, appName, bundleId, version, buildNum }: RunConfig): Promise<void>;
30
+ syncAndroid(): Promise<void>;
31
+ runAndroid({ operation, appName, appId, version, buildNum, host }: RunConfig): Promise<void>;
32
+ updateAndroidVersion(version: string, buildNum: number): Promise<void>;
29
33
  releaseIos(): Promise<void>;
30
34
  releaseAndroid(): Promise<void>;
31
35
  addCamera(): Promise<void>;
@@ -1,6 +1,7 @@
1
1
  import { Logger } from "@akanjs/common";
2
2
  import { type AppConfigResult, type LibConfigResult } from "@akanjs/config";
3
3
  import { ChildProcess, type ExecOptions, type ForkOptions, type SpawnOptions } from "child_process";
4
+ import { ESLint, Linter as ESLintLinter } from "eslint";
4
5
  import { Linter } from "./linter";
5
6
  import { AppInfo, LibInfo, PkgInfo, WorkspaceInfo } from "./scanInfo";
6
7
  import { Spinner } from "./spinner";
@@ -54,6 +55,7 @@ export declare class Executor {
54
55
  cp(srcPath: string, destPath: string): Promise<void>;
55
56
  log(msg: string): this;
56
57
  verbose(msg: string): this;
58
+ debug(msg: string): this;
57
59
  spinning(msg: string, { prefix, indent, enableSpin }?: {
58
60
  prefix?: string | undefined;
59
61
  indent?: number | undefined;
@@ -96,10 +98,10 @@ export declare class Executor {
96
98
  fix?: boolean;
97
99
  dryRun?: boolean;
98
100
  }): Promise<{
99
- results: import("eslint").ESLint.LintResult[];
101
+ results: ESLint.LintResult[];
100
102
  message: string;
101
- errors: import("eslint").Linter.LintMessage[];
102
- warnings: import("eslint").Linter.LintMessage[];
103
+ errors: ESLintLinter.LintMessage[];
104
+ warnings: ESLintLinter.LintMessage[];
103
105
  }>;
104
106
  }
105
107
  interface ExecutorOptions {
@@ -241,6 +243,8 @@ export declare class AppExecutor extends SysExecutor {
241
243
  refresh?: boolean;
242
244
  }): Promise<AppConfigResult>;
243
245
  syncAssets(libDeps: string[]): Promise<void>;
246
+ increaseBuildNum(): Promise<void>;
247
+ decreaseBuildNum(): Promise<void>;
244
248
  }
245
249
  interface LibExecutorOptions {
246
250
  workspace?: WorkspaceExecutor;
@@ -0,0 +1,16 @@
1
+ export declare class FileEditor {
2
+ #private;
3
+ private filePath;
4
+ private content;
5
+ constructor(filePath: string);
6
+ find(pattern: string | RegExp): number;
7
+ findAll(pattern: string | RegExp): number[];
8
+ insertAfter(pattern: string | RegExp, data: string): this;
9
+ insertBefore(pattern: string | RegExp, data: string): this;
10
+ replace(pattern: string | RegExp, replacement: string): this;
11
+ append(data: string): this;
12
+ prepend(data: string): this;
13
+ save(): void;
14
+ getContent(): string;
15
+ setContent(content: string): this;
16
+ }
package/src/types.d.ts CHANGED
@@ -13,6 +13,7 @@ export interface PackageJson {
13
13
  esbuild?: {
14
14
  platform?: "node" | "browser" | "neutral";
15
15
  };
16
+ [key: string]: any;
16
17
  }
17
18
  export interface TsConfigJson {
18
19
  extends?: string;
@@ -1,8 +1,9 @@
1
- export declare const uploadRelease: (appName: string, { workspaceRoot, environment, buildNum, platformVersion, local, }: {
1
+ export declare const uploadRelease: (appName: string, { workspaceRoot, environment, buildNum, platformVersion, os, local, }: {
2
2
  workspaceRoot: string;
3
3
  environment: string;
4
4
  buildNum: number;
5
5
  platformVersion?: string;
6
+ os?: "android" | "ios";
6
7
  local?: boolean;
7
8
  }) => Promise<{
8
9
  id: string;