optikit 1.2.0 → 1.2.2

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/CHANGELOG.md CHANGED
@@ -10,6 +10,22 @@ We follow Semantic Versioning (SemVer) to indicate the nature of changes:
10
10
 
11
11
  Each section lists the changes in chronological order, with the most recent release at the top. We also include links to relevant discussions or issues when appropriate.
12
12
 
13
+ ## [1.2.1]
14
+
15
+ ### Added
16
+
17
+ - **Open Build Output Commands:** New commands to quickly access build artifacts:
18
+ - `open-ipa` - Opens the IPA build output directory (`build/ios/ipa/`)
19
+ - `open-apk` - Opens the APK build output directory (`build/app/outputs/flutter-apk/`)
20
+ - `open-bundle` - Opens the Android Bundle output directory (`build/app/outputs/bundle/release/`)
21
+ - **Cross-Platform Support:** All open commands work seamlessly on macOS (Finder), Windows (Explorer), and Linux (default file manager)
22
+ - **Smart Validation:** Commands check if build output exists before attempting to open, with helpful error messages
23
+
24
+ ### Improved
25
+
26
+ - **Documentation:** Updated USAGE.md with comprehensive open build output commands section
27
+ - **User Experience:** No need for shell aliases to access build outputs - built directly into the CLI
28
+
13
29
  ## [1.2.0]
14
30
 
15
31
  ### Added
package/README.md CHANGED
@@ -46,7 +46,7 @@ optikit init
46
46
  optikit version
47
47
 
48
48
  # Bump patch version (1.0.0 → 1.0.1)
49
- optikit version bump patch
49
+ optikit bump patch
50
50
 
51
51
  # Build APK for release
52
52
  optikit flutter-build-apk
package/USAGE.md CHANGED
@@ -102,19 +102,19 @@ Version: 1.2.3+45
102
102
  Increment patch version (bug fixes):
103
103
 
104
104
  ```bash
105
- optikit version bump patch
105
+ optikit bump patch
106
106
  ```
107
107
 
108
108
  Increment minor version (new features):
109
109
 
110
110
  ```bash
111
- optikit version bump minor
111
+ optikit bump minor
112
112
  ```
113
113
 
114
114
  Increment major version (breaking changes):
115
115
 
116
116
  ```bash
117
- optikit version bump major
117
+ optikit bump major
118
118
  ```
119
119
 
120
120
  **What it does:**
@@ -129,7 +129,7 @@ optikit version bump major
129
129
 
130
130
  ```text
131
131
  Current: 1.0.2+45
132
- After `optikit version bump patch`:
132
+ After `optikit bump patch`:
133
133
  New: 1.0.3+46 (iOS build: 1)
134
134
  ```
135
135
 
@@ -140,7 +140,7 @@ New: 1.0.3+46 (iOS build: 1)
140
140
  Increment only iOS build number without changing version:
141
141
 
142
142
  ```bash
143
- optikit version bump-ios
143
+ optikit bump-ios
144
144
  ```
145
145
 
146
146
  **What it does:**
@@ -169,7 +169,7 @@ After:
169
169
  Increment only Android build number:
170
170
 
171
171
  ```bash
172
- optikit version bump-android
172
+ optikit bump-android
173
173
  ```
174
174
 
175
175
  **What it does:**
@@ -330,6 +330,45 @@ optikit open-ios
330
330
 
331
331
  ---
332
332
 
333
+ ### Open Build Output Directories
334
+
335
+ Quickly access your build artifacts in Finder (macOS), File Explorer (Windows), or your default file manager (Linux).
336
+
337
+ **Open IPA build output:**
338
+
339
+ ```bash
340
+ optikit open-ipa
341
+ ```
342
+
343
+ Opens: `build/ios/ipa/`
344
+
345
+ **Open APK build output:**
346
+
347
+ ```bash
348
+ optikit open-apk
349
+ ```
350
+
351
+ Opens: `build/app/outputs/flutter-apk/`
352
+
353
+ **Open Android Bundle build output:**
354
+
355
+ ```bash
356
+ optikit open-bundle
357
+ ```
358
+
359
+ Opens: `build/app/outputs/bundle/release/`
360
+
361
+ **What it does:**
362
+
363
+ - Validates that you're in a Flutter project
364
+ - Checks if the build output directory exists
365
+ - Opens the directory in your system's default file manager
366
+ - Works cross-platform (macOS, Windows, Linux)
367
+
368
+ **Note:** You must build the respective artifact first before opening its output directory.
369
+
370
+ ---
371
+
333
372
  ### VS Code Setup Command
334
373
 
335
374
  Automatically create a `.vscode` folder with a `settings.json` file preconfigured for Flutter projects using FVM. This command streamlines your project setup by setting the Flutter SDK path to `.fvm/flutter_sdk`.
@@ -46,7 +46,7 @@ Version: 1.2.3+45
46
46
 
47
47
  #### Bump Patch (1.2.3 → 1.2.4)
48
48
  ```bash
49
- optikit version bump patch
49
+ optikit bump patch
50
50
  ```
51
51
 
52
52
  **What it does:**
@@ -61,7 +61,7 @@ optikit version bump patch
61
61
 
62
62
  #### Bump Minor (1.2.4 → 1.3.0)
63
63
  ```bash
64
- optikit version bump minor
64
+ optikit bump minor
65
65
  ```
66
66
 
67
67
  **What it does:**
@@ -76,7 +76,7 @@ optikit version bump minor
76
76
 
77
77
  #### Bump Major (1.3.0 → 2.0.0)
78
78
  ```bash
79
- optikit version bump major
79
+ optikit bump major
80
80
  ```
81
81
 
82
82
  **What it does:**
@@ -95,7 +95,7 @@ optikit version bump major
95
95
  ### 3. Bump iOS Build Only (TestFlight)
96
96
 
97
97
  ```bash
98
- optikit version bump-ios
98
+ optikit bump-ios
99
99
  ```
100
100
 
101
101
  **What it does:**
@@ -126,7 +126,7 @@ After:
126
126
  ### 4. Bump Android Build Only
127
127
 
128
128
  ```bash
129
- optikit version bump-android
129
+ optikit bump-android
130
130
  ```
131
131
 
132
132
  **What it does:**
@@ -160,7 +160,7 @@ After:
160
160
 
161
161
  ```bash
162
162
  # Current: 1.0.2+5
163
- optikit version bump patch
163
+ optikit bump patch
164
164
 
165
165
  # Result:
166
166
  # Version: 1.0.3+6
@@ -183,15 +183,15 @@ optikit version bump patch
183
183
  # Current: 1.0.3+6 (iOS build: 1)
184
184
 
185
185
  # First TestFlight upload
186
- optikit version bump-ios
186
+ optikit bump-ios
187
187
  # Version: 1.0.3+6, iOS: 2
188
188
 
189
189
  # Found a bug, fix it, upload again
190
- optikit version bump-ios
190
+ optikit bump-ios
191
191
  # Version: 1.0.3+6, iOS: 3
192
192
 
193
193
  # Another fix
194
- optikit version bump-ios
194
+ optikit bump-ios
195
195
  # Version: 1.0.3+6, iOS: 4
196
196
  ```
197
197
 
@@ -210,7 +210,7 @@ optikit version bump-ios
210
210
  # Current: 1.0.3+6 (iOS: 4)
211
211
 
212
212
  # Increment Android build only
213
- optikit version bump-android
213
+ optikit bump-android
214
214
  # Version: 1.0.3+7, iOS: 4 (unchanged)
215
215
  ```
216
216
 
@@ -222,23 +222,23 @@ optikit version bump-android
222
222
  # 1. Start: 1.0.2+10 (iOS: 5)
223
223
 
224
224
  # 2. New feature development
225
- optikit version bump minor
225
+ optikit bump minor
226
226
  # → 1.1.0+11 (iOS: 1, Android: 11)
227
227
 
228
228
  # 3. TestFlight testing (3 iterations)
229
- optikit version bump-ios # iOS: 2
230
- optikit version bump-ios # iOS: 3
231
- optikit version bump-ios # iOS: 4
229
+ optikit bump-ios # iOS: 2
230
+ optikit bump-ios # iOS: 3
231
+ optikit bump-ios # iOS: 4
232
232
 
233
233
  # 4. Android beta testing
234
- optikit version bump-android # Android: 12
234
+ optikit bump-android # Android: 12
235
235
 
236
236
  # 5. Fix found during testing
237
- optikit version bump patch
237
+ optikit bump patch
238
238
  # → 1.1.1+13 (iOS: 1, Android: 13)
239
239
 
240
240
  # 6. Final TestFlight
241
- optikit version bump-ios # iOS: 2
241
+ optikit bump-ios # iOS: 2
242
242
 
243
243
  # 7. Release to stores!
244
244
  # iOS: 1.1.1 (build 2)
@@ -261,7 +261,7 @@ BEFORE: AFTER:
261
261
  │ iOS: 3 │ ───> │ iOS: 1 │ ✅ Reset to 1
262
262
  └─────────────┘ └─────────────┘
263
263
 
264
- Command: optikit version bump patch
264
+ Command: optikit bump patch
265
265
  ```
266
266
 
267
267
  ### Example 2: iOS-Only Bump
@@ -276,7 +276,7 @@ BEFORE: AFTER:
276
276
  │ iOS: 1 │ ───> │ iOS: 2 │ ✅ Build +1
277
277
  └─────────────┘ └─────────────┘
278
278
 
279
- Command: optikit version bump-ios
279
+ Command: optikit bump-ios
280
280
  ```
281
281
 
282
282
  ---
@@ -328,24 +328,24 @@ version: 1.2.3+45
328
328
  ### 1. Always Use Semantic Versioning
329
329
  ```bash
330
330
  # Bug fix
331
- optikit version bump patch
331
+ optikit bump patch
332
332
 
333
333
  # New feature
334
- optikit version bump minor
334
+ optikit bump minor
335
335
 
336
336
  # Breaking change
337
- optikit version bump major
337
+ optikit bump major
338
338
  ```
339
339
 
340
340
  ### 2. TestFlight Workflow
341
341
  ```bash
342
342
  # Create new version
343
- optikit version bump minor
343
+ optikit bump minor
344
344
 
345
345
  # Multiple TestFlight uploads
346
- optikit version bump-ios # Upload 1
347
- optikit version bump-ios # Upload 2
348
- optikit version bump-ios # Upload 3
346
+ optikit bump-ios # Upload 1
347
+ optikit bump-ios # Upload 2
348
+ optikit bump-ios # Upload 3
349
349
 
350
350
  # When satisfied, release to App Store
351
351
  # (No additional command needed)
@@ -354,11 +354,11 @@ optikit version bump-ios # Upload 3
354
354
  ### 3. Hotfix Workflow
355
355
  ```bash
356
356
  # iOS hotfix
357
- optikit version bump patch
357
+ optikit bump patch
358
358
  optikit flutter-build-ipa
359
359
 
360
360
  # Android hotfix
361
- optikit version bump patch
361
+ optikit bump patch
362
362
  optikit flutter-build-bundle
363
363
  ```
364
364
 
@@ -368,7 +368,7 @@ optikit flutter-build-bundle
368
368
  optikit version
369
369
 
370
370
  # Then bump
371
- optikit version bump patch
371
+ optikit bump patch
372
372
  ```
373
373
 
374
374
  ---
@@ -411,15 +411,15 @@ App Store Connect:
411
411
  optikit version
412
412
 
413
413
  # Release new version
414
- optikit version bump patch # 1.0.0 → 1.0.1
415
- optikit version bump minor # 1.0.1 → 1.1.0
416
- optikit version bump major # 1.1.0 → 2.0.0
414
+ optikit bump patch # 1.0.0 → 1.0.1
415
+ optikit bump minor # 1.0.1 → 1.1.0
416
+ optikit bump major # 1.1.0 → 2.0.0
417
417
 
418
418
  # TestFlight only
419
- optikit version bump-ios
419
+ optikit bump-ios
420
420
 
421
421
  # Google Play only
422
- optikit version bump-android
422
+ optikit bump-android
423
423
  ```
424
424
 
425
425
  ---
package/dist/cli.js CHANGED
@@ -9,7 +9,7 @@ import { cleanIosProject } from "./commands/clean/ios.js";
9
9
  import { updateFlutterVersion } from "./commands/version/update.js";
10
10
  import { buildFlutterApk, buildFlutterBundle, buildFlutterIos, buildFlutterIpa, } from "./commands/build/releases.js";
11
11
  import { boxenOptions } from "./styles.js";
12
- import { openIos, openAndroid } from "./commands/project/open.js";
12
+ import { openIos, openAndroid, openIpaOutput, openBundleOutput, openApkOutput } from "./commands/project/open.js";
13
13
  import { createRequire } from "module";
14
14
  import { createVscodeSettings } from "./commands/project/setup.js";
15
15
  import { initializeProject } from "./commands/config/init.js";
@@ -129,7 +129,17 @@ const options = yargs(hideBin(process.argv))
129
129
  })
130
130
  .command("open-android", "Open the Android project in Android Studio", {}, async () => {
131
131
  await openAndroid();
132
- }).command("setup-vscode", "Create a .vscode folder with recommended Flutter settings", () => { }, async () => {
132
+ })
133
+ .command("open-ipa", "Open the IPA build output directory", {}, async () => {
134
+ await openIpaOutput();
135
+ })
136
+ .command("open-apk", "Open the APK build output directory", {}, async () => {
137
+ await openApkOutput();
138
+ })
139
+ .command("open-bundle", "Open the Android Bundle build output directory", {}, async () => {
140
+ await openBundleOutput();
141
+ })
142
+ .command("setup-vscode", "Create a .vscode folder with recommended Flutter settings", () => { }, async () => {
133
143
  await createVscodeSettings();
134
144
  })
135
145
  .command("init", "Initialize OptiKit configuration in the current project", () => { }, async () => {
@@ -150,10 +160,7 @@ const options = yargs(hideBin(process.argv))
150
160
  await rollbackFiles();
151
161
  }
152
162
  })
153
- .command("version", "Show current version information", () => { }, async () => {
154
- await showCurrentVersion();
155
- })
156
- .command("version bump <type>", "Bump version (major, minor, or patch)", (yargs) => {
163
+ .command("bump <type>", "Bump version (major, minor, or patch)", (yargs) => {
157
164
  return yargs.positional("type", {
158
165
  describe: "Version bump type (major, minor, patch)",
159
166
  type: "string",
@@ -163,11 +170,14 @@ const options = yargs(hideBin(process.argv))
163
170
  const type = argv.type;
164
171
  await bumpVersion(type);
165
172
  })
166
- .command("version bump-ios", "Increment iOS build number only (for TestFlight)", () => { }, async () => {
173
+ .command("bump-ios", "Increment iOS build number only (for TestFlight)", () => { }, async () => {
167
174
  await bumpIosBuildOnly();
168
175
  })
169
- .command("version bump-android", "Increment Android build number only", () => { }, async () => {
176
+ .command("bump-android", "Increment Android build number only", () => { }, async () => {
170
177
  await bumpAndroidBuildOnly();
178
+ })
179
+ .command("version", "Show current version information", () => { }, async () => {
180
+ await showCurrentVersion();
171
181
  })
172
182
  .command("devices", "List all connected devices", (yargs) => {
173
183
  return yargs.option("disable-fvm", {
@@ -2,7 +2,9 @@ import { LoggerHelpers } from "../../utils/services/logger.js";
2
2
  import { execCommand } from "../../utils/services/exec.js";
3
3
  import { platform } from "os";
4
4
  import { validateFlutterProject, validateIosProject, validateAndroidProject } from "../../utils/validators/validation.js";
5
- export { openIos, openAndroid };
5
+ import * as fs from "fs";
6
+ import * as path from "path";
7
+ export { openIos, openAndroid, openIpaOutput, openBundleOutput, openApkOutput };
6
8
  async function openIos() {
7
9
  LoggerHelpers.info("Opening the iOS project in Xcode...");
8
10
  // Pre-flight validation
@@ -61,3 +63,101 @@ async function openAndroid() {
61
63
  process.exit(1);
62
64
  }
63
65
  }
66
+ /**
67
+ * Opens the IPA build output directory in Finder (macOS) or File Explorer (Windows/Linux)
68
+ */
69
+ async function openIpaOutput() {
70
+ if (!validateFlutterProject()) {
71
+ process.exit(1);
72
+ }
73
+ const ipaPath = path.join(process.cwd(), "build", "ios", "ipa");
74
+ if (!fs.existsSync(ipaPath)) {
75
+ LoggerHelpers.warning(`IPA output directory not found: ${ipaPath}`);
76
+ LoggerHelpers.info("Build an IPA first using: optikit flutter-build-ipa");
77
+ process.exit(1);
78
+ }
79
+ try {
80
+ const openCommand = getOpenCommand();
81
+ await execCommand(`${openCommand} "${ipaPath}"`);
82
+ LoggerHelpers.success(`Opened IPA output directory`);
83
+ }
84
+ catch (error) {
85
+ if (error instanceof Error) {
86
+ LoggerHelpers.error(`Error opening IPA directory: ${error.message}`);
87
+ }
88
+ else {
89
+ LoggerHelpers.error(`Error opening IPA directory: ${error}`);
90
+ }
91
+ process.exit(1);
92
+ }
93
+ }
94
+ /**
95
+ * Opens the Android App Bundle output directory in Finder (macOS) or File Explorer (Windows/Linux)
96
+ */
97
+ async function openBundleOutput() {
98
+ if (!validateFlutterProject()) {
99
+ process.exit(1);
100
+ }
101
+ const bundlePath = path.join(process.cwd(), "build", "app", "outputs", "bundle", "release");
102
+ if (!fs.existsSync(bundlePath)) {
103
+ LoggerHelpers.warning(`Bundle output directory not found: ${bundlePath}`);
104
+ LoggerHelpers.info("Build a bundle first using: optikit flutter-build-bundle");
105
+ process.exit(1);
106
+ }
107
+ try {
108
+ const openCommand = getOpenCommand();
109
+ await execCommand(`${openCommand} "${bundlePath}"`);
110
+ LoggerHelpers.success(`Opened Bundle output directory`);
111
+ }
112
+ catch (error) {
113
+ if (error instanceof Error) {
114
+ LoggerHelpers.error(`Error opening Bundle directory: ${error.message}`);
115
+ }
116
+ else {
117
+ LoggerHelpers.error(`Error opening Bundle directory: ${error}`);
118
+ }
119
+ process.exit(1);
120
+ }
121
+ }
122
+ /**
123
+ * Opens the APK build output directory in Finder (macOS) or File Explorer (Windows/Linux)
124
+ */
125
+ async function openApkOutput() {
126
+ if (!validateFlutterProject()) {
127
+ process.exit(1);
128
+ }
129
+ const apkPath = path.join(process.cwd(), "build", "app", "outputs", "flutter-apk");
130
+ if (!fs.existsSync(apkPath)) {
131
+ LoggerHelpers.warning(`APK output directory not found: ${apkPath}`);
132
+ LoggerHelpers.info("Build an APK first using: optikit flutter-build-apk");
133
+ process.exit(1);
134
+ }
135
+ try {
136
+ const openCommand = getOpenCommand();
137
+ await execCommand(`${openCommand} "${apkPath}"`);
138
+ LoggerHelpers.success(`Opened APK output directory`);
139
+ }
140
+ catch (error) {
141
+ if (error instanceof Error) {
142
+ LoggerHelpers.error(`Error opening APK directory: ${error.message}`);
143
+ }
144
+ else {
145
+ LoggerHelpers.error(`Error opening APK directory: ${error}`);
146
+ }
147
+ process.exit(1);
148
+ }
149
+ }
150
+ /**
151
+ * Returns the appropriate command to open a directory based on the platform
152
+ */
153
+ function getOpenCommand() {
154
+ const osPlatform = platform();
155
+ switch (osPlatform) {
156
+ case "darwin": // macOS
157
+ return "open";
158
+ case "win32": // Windows
159
+ return "start";
160
+ default: // Linux and others
161
+ return "xdg-open";
162
+ }
163
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "optikit",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "description": "OptiKit CLI",
5
5
  "main": "src/cli.ts",
6
6
  "type": "module",
package/src/cli.ts CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  buildFlutterIpa,
16
16
  } from "./commands/build/releases.js";
17
17
  import { boxenOptions } from "./styles.js";
18
- import { openIos, openAndroid } from "./commands/project/open.js";
18
+ import { openIos, openAndroid, openIpaOutput, openBundleOutput, openApkOutput } from "./commands/project/open.js";
19
19
  import { createRequire } from "module";
20
20
  import { createVscodeSettings } from "./commands/project/setup.js";
21
21
  import { initializeProject } from "./commands/config/init.js";
@@ -192,7 +192,32 @@ const options = yargs(hideBin(process.argv))
192
192
  async () => {
193
193
  await openAndroid();
194
194
  }
195
- ).command(
195
+ )
196
+ .command(
197
+ "open-ipa",
198
+ "Open the IPA build output directory",
199
+ {},
200
+ async () => {
201
+ await openIpaOutput();
202
+ }
203
+ )
204
+ .command(
205
+ "open-apk",
206
+ "Open the APK build output directory",
207
+ {},
208
+ async () => {
209
+ await openApkOutput();
210
+ }
211
+ )
212
+ .command(
213
+ "open-bundle",
214
+ "Open the Android Bundle build output directory",
215
+ {},
216
+ async () => {
217
+ await openBundleOutput();
218
+ }
219
+ )
220
+ .command(
196
221
  "setup-vscode",
197
222
  "Create a .vscode folder with recommended Flutter settings",
198
223
  () => {},
@@ -228,15 +253,7 @@ const options = yargs(hideBin(process.argv))
228
253
  }
229
254
  )
230
255
  .command(
231
- "version",
232
- "Show current version information",
233
- () => {},
234
- async () => {
235
- await showCurrentVersion();
236
- }
237
- )
238
- .command(
239
- "version bump <type>",
256
+ "bump <type>",
240
257
  "Bump version (major, minor, or patch)",
241
258
  (yargs) => {
242
259
  return yargs.positional("type", {
@@ -251,7 +268,7 @@ const options = yargs(hideBin(process.argv))
251
268
  }
252
269
  )
253
270
  .command(
254
- "version bump-ios",
271
+ "bump-ios",
255
272
  "Increment iOS build number only (for TestFlight)",
256
273
  () => {},
257
274
  async () => {
@@ -259,13 +276,21 @@ const options = yargs(hideBin(process.argv))
259
276
  }
260
277
  )
261
278
  .command(
262
- "version bump-android",
279
+ "bump-android",
263
280
  "Increment Android build number only",
264
281
  () => {},
265
282
  async () => {
266
283
  await bumpAndroidBuildOnly();
267
284
  }
268
285
  )
286
+ .command(
287
+ "version",
288
+ "Show current version information",
289
+ () => {},
290
+ async () => {
291
+ await showCurrentVersion();
292
+ }
293
+ )
269
294
  .command(
270
295
  "devices",
271
296
  "List all connected devices",
@@ -2,8 +2,10 @@ import { LoggerHelpers } from "../../utils/services/logger.js";
2
2
  import { execCommand } from "../../utils/services/exec.js";
3
3
  import { platform } from "os";
4
4
  import { validateFlutterProject, validateIosProject, validateAndroidProject } from "../../utils/validators/validation.js";
5
+ import * as fs from "fs";
6
+ import * as path from "path";
5
7
 
6
- export { openIos, openAndroid };
8
+ export { openIos, openAndroid, openIpaOutput, openBundleOutput, openApkOutput };
7
9
 
8
10
  async function openIos() {
9
11
  LoggerHelpers.info("Opening the iOS project in Xcode...");
@@ -70,3 +72,122 @@ async function openAndroid() {
70
72
  process.exit(1);
71
73
  }
72
74
  }
75
+
76
+ /**
77
+ * Opens the IPA build output directory in Finder (macOS) or File Explorer (Windows/Linux)
78
+ */
79
+ async function openIpaOutput(): Promise<void> {
80
+ if (!validateFlutterProject()) {
81
+ process.exit(1);
82
+ }
83
+
84
+ const ipaPath = path.join(process.cwd(), "build", "ios", "ipa");
85
+
86
+ if (!fs.existsSync(ipaPath)) {
87
+ LoggerHelpers.warning(`IPA output directory not found: ${ipaPath}`);
88
+ LoggerHelpers.info("Build an IPA first using: optikit flutter-build-ipa");
89
+ process.exit(1);
90
+ }
91
+
92
+ try {
93
+ const openCommand = getOpenCommand();
94
+ await execCommand(`${openCommand} "${ipaPath}"`);
95
+ LoggerHelpers.success(`Opened IPA output directory`);
96
+ } catch (error) {
97
+ if (error instanceof Error) {
98
+ LoggerHelpers.error(`Error opening IPA directory: ${error.message}`);
99
+ } else {
100
+ LoggerHelpers.error(`Error opening IPA directory: ${error}`);
101
+ }
102
+ process.exit(1);
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Opens the Android App Bundle output directory in Finder (macOS) or File Explorer (Windows/Linux)
108
+ */
109
+ async function openBundleOutput(): Promise<void> {
110
+ if (!validateFlutterProject()) {
111
+ process.exit(1);
112
+ }
113
+
114
+ const bundlePath = path.join(
115
+ process.cwd(),
116
+ "build",
117
+ "app",
118
+ "outputs",
119
+ "bundle",
120
+ "release"
121
+ );
122
+
123
+ if (!fs.existsSync(bundlePath)) {
124
+ LoggerHelpers.warning(`Bundle output directory not found: ${bundlePath}`);
125
+ LoggerHelpers.info("Build a bundle first using: optikit flutter-build-bundle");
126
+ process.exit(1);
127
+ }
128
+
129
+ try {
130
+ const openCommand = getOpenCommand();
131
+ await execCommand(`${openCommand} "${bundlePath}"`);
132
+ LoggerHelpers.success(`Opened Bundle output directory`);
133
+ } catch (error) {
134
+ if (error instanceof Error) {
135
+ LoggerHelpers.error(`Error opening Bundle directory: ${error.message}`);
136
+ } else {
137
+ LoggerHelpers.error(`Error opening Bundle directory: ${error}`);
138
+ }
139
+ process.exit(1);
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Opens the APK build output directory in Finder (macOS) or File Explorer (Windows/Linux)
145
+ */
146
+ async function openApkOutput(): Promise<void> {
147
+ if (!validateFlutterProject()) {
148
+ process.exit(1);
149
+ }
150
+
151
+ const apkPath = path.join(
152
+ process.cwd(),
153
+ "build",
154
+ "app",
155
+ "outputs",
156
+ "flutter-apk"
157
+ );
158
+
159
+ if (!fs.existsSync(apkPath)) {
160
+ LoggerHelpers.warning(`APK output directory not found: ${apkPath}`);
161
+ LoggerHelpers.info("Build an APK first using: optikit flutter-build-apk");
162
+ process.exit(1);
163
+ }
164
+
165
+ try {
166
+ const openCommand = getOpenCommand();
167
+ await execCommand(`${openCommand} "${apkPath}"`);
168
+ LoggerHelpers.success(`Opened APK output directory`);
169
+ } catch (error) {
170
+ if (error instanceof Error) {
171
+ LoggerHelpers.error(`Error opening APK directory: ${error.message}`);
172
+ } else {
173
+ LoggerHelpers.error(`Error opening APK directory: ${error}`);
174
+ }
175
+ process.exit(1);
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Returns the appropriate command to open a directory based on the platform
181
+ */
182
+ function getOpenCommand(): string {
183
+ const osPlatform = platform();
184
+
185
+ switch (osPlatform) {
186
+ case "darwin": // macOS
187
+ return "open";
188
+ case "win32": // Windows
189
+ return "start";
190
+ default: // Linux and others
191
+ return "xdg-open";
192
+ }
193
+ }