ota-manager 1.0.6 → 1.0.13

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/lib/ota-deploy.js CHANGED
@@ -160,7 +160,7 @@ async function deployOTA() {
160
160
  execSync('npm run build', { stdio: 'inherit', cwd: rootDir });
161
161
 
162
162
  if (fs.existsSync(FLATTEN_SCRIPT)) {
163
- console.log('šŸš€ Running Tsar Bomba Path Cleanse...');
163
+ console.log('šŸš€ Running Absolute Path Normalization...');
164
164
  execSync(`node "${FLATTEN_SCRIPT}"`, { stdio: 'inherit', cwd: rootDir });
165
165
  }
166
166
 
@@ -192,7 +192,9 @@ async function deployOTA() {
192
192
  console.log('šŸ” Changes detected! Auto-incrementing from remote manifest...');
193
193
  const parts = previousVersion.split('.');
194
194
  const lastIdx = parts.length - 1;
195
- parts[lastIdx] = (parseInt(parts[lastIdx]) + 1).toString();
195
+ const oldPart = parts[lastIdx];
196
+ const oldLen = oldPart.length;
197
+ parts[lastIdx] = (parseInt(oldPart) + 1).toString().padStart(oldLen, '0');
196
198
  nextVersion = parts.join('.');
197
199
  console.log(`šŸ“ˆ Auto-increment: ${previousVersion} -> ${nextVersion}`);
198
200
  }
@@ -221,12 +223,90 @@ async function deployOTA() {
221
223
 
222
224
  console.log(`šŸ“ Updating .env to version ${nextVersion} (Android & iOS) and setting OTA target...`);
223
225
  fs.writeFileSync(ENV_PATH, updatedEnv);
226
+
227
+ const envLivePath = path.join(rootDir, '.env-live');
228
+ const envTrainingPath = path.join(rootDir, '.env-training');
229
+ if (argChannel === 'live' && fs.existsSync(envLivePath)) {
230
+ let liveEnv = fs.readFileSync(envLivePath, 'utf-8');
231
+ liveEnv = liveEnv.replace(/PUBLIC_APP_VERSION=.*/, `PUBLIC_APP_VERSION=${nextVersion}`);
232
+ if (liveEnv.includes('PUBLIC_OTA_UPDATE_URL=')) {
233
+ liveEnv = liveEnv.replace(/PUBLIC_OTA_UPDATE_URL=.*/, `PUBLIC_OTA_UPDATE_URL=${activeOtaUrl}`);
234
+ } else {
235
+ liveEnv += `\nPUBLIC_OTA_UPDATE_URL=${activeOtaUrl}`;
236
+ }
237
+ fs.writeFileSync(envLivePath, liveEnv);
238
+ console.log(`šŸ“ Updated .env-live to version ${nextVersion}`);
239
+ }
240
+ if (argChannel === 'training' && fs.existsSync(envTrainingPath)) {
241
+ let trainingEnv = fs.readFileSync(envTrainingPath, 'utf-8');
242
+ trainingEnv = trainingEnv.replace(/PUBLIC_APP_VERSION=.*/, `PUBLIC_APP_VERSION=${nextVersion}`);
243
+ if (trainingEnv.includes('PUBLIC_OTA_UPDATE_URL=')) {
244
+ trainingEnv = trainingEnv.replace(/PUBLIC_OTA_UPDATE_URL=.*/, `PUBLIC_OTA_UPDATE_URL=${activeOtaUrl}`);
245
+ } else {
246
+ trainingEnv += `\nPUBLIC_OTA_UPDATE_URL=${activeOtaUrl}`;
247
+ }
248
+ fs.writeFileSync(envTrainingPath, trainingEnv);
249
+ console.log(`šŸ“ Updated .env-training to version ${nextVersion}`);
250
+ }
224
251
 
252
+ // Sync Native Android & iOS Versions
253
+ try {
254
+ const partsStr = nextVersion.split('.');
255
+ let majorStr = partsStr[0] || '0';
256
+ let minorStr = partsStr[1] || '0';
257
+ let patchStr = partsStr[2] || '00';
258
+ if (patchStr.length === 1) patchStr = '0' + patchStr;
259
+ const versionCodeNum = parseInt(`${majorStr}${minorStr}${patchStr}`, 10) || 1;
260
+
261
+ const buildGradlePath = path.join(rootDir, 'android', 'app', 'build.gradle');
262
+ if (fs.existsSync(buildGradlePath)) {
263
+ let bgContent = fs.readFileSync(buildGradlePath, 'utf8');
264
+ bgContent = bgContent.replace(/versionCode\s+\d+/, `versionCode ${versionCodeNum}`);
265
+ bgContent = bgContent.replace(/versionName\s+".*?"/, `versionName "${nextVersion}"`);
266
+ fs.writeFileSync(buildGradlePath, bgContent);
267
+ }
268
+
269
+ const pbxprojPath = path.join(rootDir, 'ios', 'App', 'App.xcodeproj', 'project.pbxproj');
270
+ if (fs.existsSync(pbxprojPath)) {
271
+ let pbxContent = fs.readFileSync(pbxprojPath, 'utf8');
272
+ pbxContent = pbxContent.replace(/CURRENT_PROJECT_VERSION = \d+;/g, `CURRENT_PROJECT_VERSION = ${versionCodeNum};`);
273
+ pbxContent = pbxContent.replace(/MARKETING_VERSION = .*?;/g, `MARKETING_VERSION = ${nextVersion};`);
274
+ fs.writeFileSync(pbxprojPath, pbxContent);
275
+ }
276
+ console.log(`āœ… Native Android & iOS versions synchronized (versionCode: ${versionCodeNum}).`);
277
+ } catch (err) {
278
+ console.error('āš ļø Warning: Failed to sync native versions:', err.message);
279
+ }
280
+
225
281
  if (fs.existsSync(MAIN_MANIFEST_PATH)) {
226
282
  const manifest = JSON.parse(fs.readFileSync(MAIN_MANIFEST_PATH, 'utf8'));
227
283
  manifest.version = nextVersion;
228
284
  fs.writeFileSync(MAIN_MANIFEST_PATH, JSON.stringify(manifest, null, 2));
229
285
  }
286
+ const pkgJsonPath = path.join(rootDir, 'package.json');
287
+ if (fs.existsSync(pkgJsonPath)) {
288
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
289
+ pkgJson.version = nextVersion;
290
+ fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
291
+ }
292
+
293
+ console.log('šŸ—ļø Rebuilding Astro to bake new version string into JS bundle...');
294
+ hideApi();
295
+ try {
296
+ execSync('npm run build', { stdio: 'inherit', cwd: rootDir });
297
+
298
+ if (fs.existsSync(FLATTEN_SCRIPT)) {
299
+ console.log('šŸš€ Running Absolute Path Normalization...');
300
+ execSync(`node "${FLATTEN_SCRIPT}"`, { stdio: 'inherit', cwd: rootDir });
301
+ }
302
+
303
+ if (fs.existsSync(VERIFY_SCRIPT)) {
304
+ console.log('šŸ” Running Pre-Flight Verification...');
305
+ execSync(`node "${VERIFY_SCRIPT}"`, { stdio: 'inherit', cwd: rootDir });
306
+ }
307
+ } finally {
308
+ showApi();
309
+ }
230
310
 
231
311
  console.log('šŸ›”ļø Size Guardian: Checking dist/ folder size...');
232
312
  const distSizeBytes = getDirSize(DIST_DIR);
@@ -237,7 +317,7 @@ async function deployOTA() {
237
317
  throw new Error(`CRITICAL SIZE VIOLATION: Folder dist/ has bloated to ${distSizeMB.toFixed(2)} MB! Limit is ${MAX_OTA_SIZE_MB} MB. Packaging aborted to prevent ZIP BOMB!`);
238
318
  }
239
319
 
240
- const otaName = `v${nextVersion.replace(/\./g, '_')}.zip`;
320
+ const otaName = `${nextVersion.replace(/\./g, '_')}.zip`;
241
321
  const otaPath = path.join(rootDir, otaName);
242
322
 
243
323
  const isWindows = process.platform === 'win32';
@@ -306,7 +386,7 @@ async function deployToRemote(otaPath, otaName, version, channel, branch, buildH
306
386
  fs.writeFileSync(flatManifestPath, JSON.stringify(manifest[channel], null, 2));
307
387
 
308
388
  execSync(`git add .`, { cwd: OTA_RELEASES_DIR });
309
- execSync(`git commit -m "release: v${version} for ${channel}"`, { cwd: OTA_RELEASES_DIR });
389
+ execSync(`git commit -m "release: ${version} for ${channel}"`, { cwd: OTA_RELEASES_DIR });
310
390
  execSync(`git push origin ${branch}`, { cwd: OTA_RELEASES_DIR });
311
391
 
312
392
  if (fs.existsSync(otaPath)) fs.unlinkSync(otaPath);
package/lib/ota-main.js CHANGED
@@ -1,214 +1,243 @@
1
- import { execSync } from 'child_process';
2
- import path from 'path';
3
- import { fileURLToPath } from 'url';
4
- import fs from 'fs';
5
- import OTA_CONFIG from './ota-config.js';
6
- import { listConfigs, useConfig, registerConfig, testConnection } from './ota-manager.js';
7
-
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = path.dirname(__filename);
10
- const rootDir = process.cwd();
11
-
12
- const command = process.argv[2];
13
- const subArg = process.argv[3];
14
- const versionArg = process.argv[4];
15
-
16
- const TOOL_VERSION = '1.2.0';
17
-
18
- const scripts = {
19
- version: path.join(__dirname, 'ota-version.js'),
20
- deploy: path.join(__dirname, 'ota-deploy.js'),
21
- verify: path.join(__dirname, 'verify-dist.cjs'),
22
- security: path.join(__dirname, 'ota-security.js'),
23
- build: path.join(__dirname, 'ota-build.cjs'),
24
- };
25
-
26
- function showHelp() {
27
- console.log(`
28
- ╔════════════════════════════════════════════════════════════╗
29
- ā•‘ OTA MANAGER ā•‘
30
- ā•‘ Version ${TOOL_VERSION} ā•‘
31
- ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•
32
-
33
- Usage: npx ota-manager <command> [sub-command] [version]
34
-
35
- Management Commands:
36
- list : Show all registered infrastructures.
37
- use <id> : Set default infrastructure (e.g., use gitlab).
38
- register <id> : Register or update infrastructure (e.g., register s3).
39
- verify : Verify active infrastructure connectivity.
40
- audit : Audit public token for security leaks.
41
- test : Run E2E simulation (Push & Read).
42
-
43
- Operational Commands:
44
- status : Check local vs remote version.
45
- deploy training : Deploy update to TRAINING channel.
46
- deploy live : Deploy update to LIVE channel.
47
-
48
- Active Infrastructure:
49
- Strategy : ${OTA_CONFIG.strategy.toUpperCase()}
50
- Repo : ${OTA_CONFIG[OTA_CONFIG.strategy]?.repo || 'Not Configured'}
51
- ──────────────────────────────────────────────────────────────
52
- `);
53
- }
54
-
55
- import readline from 'readline';
56
-
57
- function confirm(message) {
58
- return new Promise((resolve) => {
59
- const rl = readline.createInterface({
60
- input: process.stdin,
61
- output: process.stdout
62
- });
63
- rl.question(`\nāš ļø ${message} [y/N]: `, (answer) => {
64
- rl.close();
65
- resolve(answer.toLowerCase() === 'y');
66
- });
67
- });
68
- }
69
-
70
- async function run() {
71
- try {
72
- switch (command) {
73
- case 'list':
74
- await listConfigs();
75
- process.exit(0);
76
- break;
77
-
78
- case 'use':
79
- if (!subArg) {
80
- console.log('āŒ Error: Please specify the infrastructure ID (e.g., use gitlab).');
81
- process.exit(1);
82
- }
83
- await useConfig(subArg);
84
- process.exit(0);
85
- break;
86
-
87
- case 'register':
88
- if (!subArg) {
89
- console.log('āŒ Error: Please specify the ID (e.g., register gitlab).');
90
- process.exit(1);
91
- }
92
- await registerConfig(subArg);
93
- process.exit(0);
94
- break;
95
-
96
- case 'help':
97
- case '-h':
98
- case '--help':
99
- showHelp();
100
- process.exit(0);
101
- break;
102
-
103
- case '-v':
104
- case '--version':
105
- console.log(`OTA Manager v${TOOL_VERSION}`);
106
- process.exit(0);
107
- break;
108
-
109
- case 'status':
110
- case 'version':
111
- execSync(`node "${scripts.version}"`, { stdio: 'inherit' });
112
- process.exit(0);
113
- break;
114
-
115
- case 'verify':
116
- execSync(`node "${scripts.verify}"`, { stdio: 'inherit' });
117
- process.exit(0);
118
- break;
119
-
120
- case 'test':
121
- await testConnection();
122
- process.exit(0);
123
- break;
124
-
125
- case 'build':
126
- execSync(`node "${scripts.build}" ${subArg || ''}`, { stdio: 'inherit' });
127
- process.exit(0);
128
- break;
129
-
130
- case 'audit':
131
- case 'security-check':
132
- case 'security':
133
- execSync(`node "${scripts.security}"`, { stdio: 'inherit' });
134
- process.exit(0);
135
- break;
136
-
137
- case 'training':
138
- execSync(`node "${scripts.version}"`, { stdio: 'inherit' });
139
- execSync(`node "${scripts.verify}"`, { stdio: 'inherit' });
140
-
141
- console.log(`\nšŸ“‹ DEPLOYMENT PLAN [TRAINING]`);
142
- console.log(`šŸ”¹ Infra : ${OTA_CONFIG.strategy.toUpperCase()}`);
143
- console.log(`šŸ”¹ Action : Build & Push to Training Channel`);
144
-
145
- if (await confirm('Proceed with TRAINING deployment?')) {
146
- execSync(`node "${scripts.deploy}" training ${subArg || ''}`, { stdio: 'inherit' });
147
- } else {
148
- console.log('āŒ Deployment cancelled.');
149
- }
150
- process.exit(0);
151
- break;
152
-
153
- case 'live':
154
- execSync(`node "${scripts.version}"`, { stdio: 'inherit' });
155
- execSync(`node "${scripts.verify}"`, { stdio: 'inherit' });
156
-
157
- console.log(`\n🚨 WARNING: DEPLOYMENT KE LIVE [PRODUCTION]`);
158
- console.log(`šŸ”¹ Infra : ${OTA_CONFIG.strategy.toUpperCase()}`);
159
- console.log(`šŸ”¹ Action : Build & Push to LIVE Channel`);
160
-
161
- if (await confirm('ARE YOU SURE you want to deploy to LIVE?')) {
162
- execSync(`node "${scripts.deploy}" live ${subArg || ''}`, { stdio: 'inherit' });
163
- } else {
164
- console.log('āŒ LIVE Deployment cancelled.');
165
- }
166
- process.exit(0);
167
- break;
168
-
169
- case '-d':
170
- case 'deploy':
171
- if (!subArg) {
172
- console.log('āŒ Error: Mohon tentukan channel (e.g., deploy training atau deploy live).');
173
- process.exit(1);
174
- }
175
- const channel = subArg.toLowerCase();
176
- if (channel !== 'training' && channel !== 'live') {
177
- console.log(`āŒ Error: Channel "${channel}" tidak dikenal. Gunakan 'training' atau 'live'.`);
178
- process.exit(1);
179
- }
180
-
181
- execSync(`node "${scripts.version}"`, { stdio: 'inherit' });
182
- execSync(`node "${scripts.verify}"`, { stdio: 'inherit' });
183
-
184
- if (channel === 'training') {
185
- console.log(`\nšŸ“‹ DEPLOYMENT PLAN [TRAINING]`);
186
- console.log(`šŸ”¹ Infra : ${OTA_CONFIG.strategy.toUpperCase()}`);
187
- console.log(`šŸ”¹ Action : Build & Push to Training Channel`);
188
- } else {
189
- console.log(`\n🚨 WARNING: DEPLOYMENT KE LIVE [PRODUCTION]`);
190
- console.log(`šŸ”¹ Infra : ${OTA_CONFIG.strategy.toUpperCase()}`);
191
- console.log(`šŸ”¹ Action : Build & Push to LIVE Channel`);
192
- }
193
-
194
- if (await confirm(`Proceed with ${channel.toUpperCase()} deployment?`)) {
195
- execSync(`node "${scripts.deploy}" ${channel} ${versionArg || ''}`, { stdio: 'inherit' });
196
- } else {
197
- console.log('āŒ Deployment cancelled.');
198
- }
199
- process.exit(0);
200
- break;
201
-
202
- default:
203
- console.log(`\nā“ Unknown command: "${command || ''}"`);
204
- showHelp();
205
- process.exit(0);
206
- break;
207
- }
208
- } catch (error) {
209
- console.log(`\nāŒ Process stopped due to error or cancellation.`);
210
- process.exit(1);
211
- }
212
- }
213
-
214
- run();
1
+ import { execSync } from 'child_process';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import fs from 'fs';
5
+ import OTA_CONFIG from './ota-config.js';
6
+ import { listConfigs, useConfig, registerConfig, testConnection } from './ota-manager.js';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+ const rootDir = process.cwd();
11
+
12
+ const command = process.argv[2];
13
+ const subArg = process.argv[3];
14
+ const versionArg = process.argv[4];
15
+
16
+ const packageJsonPath = path.join(__dirname, '..', 'package.json');
17
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
18
+ const TOOL_VERSION = pkg.version;
19
+
20
+ const scripts = {
21
+ version: path.join(__dirname, 'ota-version.js'),
22
+ deploy: path.join(__dirname, 'ota-deploy.js'),
23
+ verify: path.join(__dirname, 'verify-dist.cjs'),
24
+ security: path.join(__dirname, 'ota-security.js'),
25
+ build: path.join(__dirname, 'ota-build.cjs'),
26
+ rollback: path.join(__dirname, 'ota-rollback.js'),
27
+ };
28
+
29
+ function showHelp() {
30
+ console.log(`
31
+ ╔════════════════════════════════════════════════════════════╗
32
+ ā•‘ OTA MANAGER ā•‘
33
+ ā•‘ Version ${TOOL_VERSION} ā•‘
34
+ ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•
35
+
36
+ Usage: npx ota-manager <command> [sub-command] [version]
37
+
38
+ Management Commands:
39
+ list : Show all registered infrastructures.
40
+ use <id> : Set default infrastructure (e.g., use gitlab).
41
+ register <id> : Register or update infrastructure (e.g., register s3).
42
+ verify : Verify active infrastructure connectivity.
43
+ audit : Audit public token for security leaks.
44
+ test : Run E2E simulation (Push & Read).
45
+
46
+ Operational Commands:
47
+ status : Check local vs remote version.
48
+ deploy training : Deploy update to TRAINING channel.
49
+ deploy live : Deploy update to LIVE channel.
50
+ rollback <v> [ch] : Rollback / Downgrade ke versi lama (e.g., rollback 2.0.00 training).
51
+
52
+ Native Build Commands:
53
+ build android : Build Android APK Debug (default).
54
+ build android release : Build Android APK Release (assembleRelease).
55
+ build android bundle : Build Android App Bundle (.aab) siap rilis Play Store.
56
+ build ios : Trigger iOS GitHub Actions build & sync branding (Debug/Sideloading).
57
+ build ios release : Trigger iOS App Store Release signed build & sync branding.
58
+ * Gunakan flag 'no', 'norev', 'nover', '-no', atau '-nv' di akhir perintah untuk mencegah kenaikan versi.
59
+
60
+ Active Infrastructure:
61
+ Strategy : ${OTA_CONFIG.strategy.toUpperCase()}
62
+ Repo : ${OTA_CONFIG[OTA_CONFIG.strategy]?.repo || 'Not Configured'}
63
+ ──────────────────────────────────────────────────────────────
64
+ `);
65
+ }
66
+
67
+ import readline from 'readline';
68
+
69
+ function confirm(message) {
70
+ return new Promise((resolve) => {
71
+ const rl = readline.createInterface({
72
+ input: process.stdin,
73
+ output: process.stdout
74
+ });
75
+ rl.question(`\nāš ļø ${message} [y/N]: `, (answer) => {
76
+ rl.close();
77
+ resolve(answer.toLowerCase() === 'y');
78
+ });
79
+ });
80
+ }
81
+
82
+ async function run() {
83
+ try {
84
+ switch (command) {
85
+ case 'list':
86
+ await listConfigs();
87
+ process.exit(0);
88
+ break;
89
+
90
+ case 'use':
91
+ if (!subArg) {
92
+ console.log('āŒ Error: Please specify the infrastructure ID (e.g., use gitlab).');
93
+ process.exit(1);
94
+ }
95
+ await useConfig(subArg);
96
+ process.exit(0);
97
+ break;
98
+
99
+ case 'register':
100
+ if (!subArg) {
101
+ console.log('āŒ Error: Please specify the ID (e.g., register gitlab).');
102
+ process.exit(1);
103
+ }
104
+ await registerConfig(subArg);
105
+ process.exit(0);
106
+ break;
107
+
108
+ case 'help':
109
+ case '-h':
110
+ case '--help':
111
+ showHelp();
112
+ process.exit(0);
113
+ break;
114
+
115
+ case '-v':
116
+ case '--version':
117
+ console.log(`OTA Manager v${TOOL_VERSION}`);
118
+ process.exit(0);
119
+ break;
120
+
121
+ case 'status':
122
+ case 'version':
123
+ execSync(`node "${scripts.version}"`, { stdio: 'inherit' });
124
+ process.exit(0);
125
+ break;
126
+
127
+ case 'verify':
128
+ execSync(`node "${scripts.verify}"`, { stdio: 'inherit' });
129
+ process.exit(0);
130
+ break;
131
+
132
+ case 'test':
133
+ await testConnection();
134
+ process.exit(0);
135
+ break;
136
+
137
+ case 'build':
138
+ const allArgs = process.argv.slice(3).join(' ');
139
+ execSync(`node "${scripts.build}" ${allArgs}`, { stdio: 'inherit' });
140
+ process.exit(0);
141
+ break;
142
+
143
+ case 'audit':
144
+ case 'security-check':
145
+ case 'security':
146
+ execSync(`node "${scripts.security}"`, { stdio: 'inherit' });
147
+ process.exit(0);
148
+ break;
149
+
150
+ case 'rollback':
151
+ case 'downgrade':
152
+ case 'set-version':
153
+ if (!subArg) {
154
+ console.log('āŒ Error: Mohon tentukan versi tujuan (e.g., npx ota-manager rollback 0.2.0.11 training).');
155
+ process.exit(1);
156
+ }
157
+ const rbChannel = versionArg || 'training';
158
+ if (await confirm(`ARE YOU SURE you want to rollback ${rbChannel.toUpperCase()} to version ${subArg}?`)) {
159
+ execSync(`node "${scripts.rollback}" ${subArg} ${rbChannel}`, { stdio: 'inherit' });
160
+ } else {
161
+ console.log('āŒ Rollback cancelled.');
162
+ }
163
+ process.exit(0);
164
+ break;
165
+
166
+ case 'training':
167
+ execSync(`node "${scripts.version}"`, { stdio: 'inherit' });
168
+ execSync(`node "${scripts.verify}"`, { stdio: 'inherit' });
169
+
170
+ console.log(`\nšŸ“‹ DEPLOYMENT PLAN [TRAINING]`);
171
+ console.log(`šŸ”¹ Infra : ${OTA_CONFIG.strategy.toUpperCase()}`);
172
+ console.log(`šŸ”¹ Action : Build & Push to Training Channel`);
173
+
174
+ if (await confirm('Proceed with TRAINING deployment?')) {
175
+ execSync(`node "${scripts.deploy}" training ${subArg || ''}`, { stdio: 'inherit' });
176
+ } else {
177
+ console.log('āŒ Deployment cancelled.');
178
+ }
179
+ process.exit(0);
180
+ break;
181
+
182
+ case 'live':
183
+ execSync(`node "${scripts.version}"`, { stdio: 'inherit' });
184
+ execSync(`node "${scripts.verify}"`, { stdio: 'inherit' });
185
+
186
+ console.log(`\n🚨 WARNING: DEPLOYMENT KE LIVE [PRODUCTION]`);
187
+ console.log(`šŸ”¹ Infra : ${OTA_CONFIG.strategy.toUpperCase()}`);
188
+ console.log(`šŸ”¹ Action : Build & Push to LIVE Channel`);
189
+
190
+ if (await confirm('ARE YOU SURE you want to deploy to LIVE?')) {
191
+ execSync(`node "${scripts.deploy}" live ${subArg || ''}`, { stdio: 'inherit' });
192
+ } else {
193
+ console.log('āŒ LIVE Deployment cancelled.');
194
+ }
195
+ process.exit(0);
196
+ break;
197
+
198
+ case '-d':
199
+ case 'deploy':
200
+ if (!subArg) {
201
+ console.log('āŒ Error: Mohon tentukan channel (e.g., deploy training atau deploy live).');
202
+ process.exit(1);
203
+ }
204
+ const channel = subArg.toLowerCase();
205
+ if (channel !== 'training' && channel !== 'live') {
206
+ console.log(`āŒ Error: Channel "${channel}" tidak dikenal. Gunakan 'training' atau 'live'.`);
207
+ process.exit(1);
208
+ }
209
+
210
+ execSync(`node "${scripts.version}"`, { stdio: 'inherit' });
211
+ execSync(`node "${scripts.verify}"`, { stdio: 'inherit' });
212
+
213
+ if (channel === 'training') {
214
+ console.log(`\nšŸ“‹ DEPLOYMENT PLAN [TRAINING]`);
215
+ console.log(`šŸ”¹ Infra : ${OTA_CONFIG.strategy.toUpperCase()}`);
216
+ console.log(`šŸ”¹ Action : Build & Push to Training Channel`);
217
+ } else {
218
+ console.log(`\n🚨 WARNING: DEPLOYMENT KE LIVE [PRODUCTION]`);
219
+ console.log(`šŸ”¹ Infra : ${OTA_CONFIG.strategy.toUpperCase()}`);
220
+ console.log(`šŸ”¹ Action : Build & Push to LIVE Channel`);
221
+ }
222
+
223
+ if (await confirm(`Proceed with ${channel.toUpperCase()} deployment?`)) {
224
+ execSync(`node "${scripts.deploy}" ${channel} ${versionArg || ''}`, { stdio: 'inherit' });
225
+ } else {
226
+ console.log('āŒ Deployment cancelled.');
227
+ }
228
+ process.exit(0);
229
+ break;
230
+
231
+ default:
232
+ console.log(`\nā“ Unknown command: "${command || ''}"`);
233
+ showHelp();
234
+ process.exit(0);
235
+ break;
236
+ }
237
+ } catch (error) {
238
+ console.log(`\nāŒ Process stopped due to error or cancellation.`);
239
+ process.exit(1);
240
+ }
241
+ }
242
+
243
+ run();