optikit 1.2.5 → 1.4.0

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.
Files changed (104) hide show
  1. package/.claude/commands/build.md +57 -0
  2. package/.claude/commands/clean.md +45 -0
  3. package/.claude/commands/generate.md +64 -0
  4. package/.claude/commands/rollback.md +67 -0
  5. package/.claude/commands/run.md +63 -0
  6. package/.claude/commands/setup.md +69 -0
  7. package/.claude/commands/sync-docs.md +148 -0
  8. package/.claude/commands/version.md +63 -0
  9. package/.claude/settings.local.json +31 -0
  10. package/.claude-plugin/marketplace.json +36 -0
  11. package/.claude-plugin/plugin.json +25 -0
  12. package/.history/README_20260325211923.md +268 -0
  13. package/.history/README_20260325212116.md +268 -0
  14. package/.history/README_20260325212234.md +266 -0
  15. package/.history/README_20260325221838.md +321 -0
  16. package/.history/README_20260325221920.md +327 -0
  17. package/.history/README_20260325222245.md +328 -0
  18. package/.history/README_20260325222247.md +328 -0
  19. package/.mcp.json +8 -0
  20. package/CHANGELOG.md +72 -95
  21. package/CLAUDE.md +57 -206
  22. package/OPTIKIT_AGENT.md +398 -0
  23. package/README.md +293 -60
  24. package/dist/cli.js +75 -244
  25. package/dist/commands/build/commands.js +146 -0
  26. package/dist/commands/build/testflight.js +14 -0
  27. package/dist/commands/clean/commands.js +41 -0
  28. package/dist/commands/clean/flutter.js +8 -14
  29. package/dist/commands/clean/ios.js +12 -15
  30. package/dist/commands/config/aliases.js +122 -0
  31. package/dist/commands/config/commands.js +49 -0
  32. package/dist/commands/config/initApp.js +191 -0
  33. package/dist/commands/config/rollback.js +15 -4
  34. package/dist/commands/config/upgrade.js +36 -0
  35. package/dist/commands/mcp/commands.js +21 -0
  36. package/dist/commands/mcp/server.js +27 -0
  37. package/dist/commands/mcp/setup.js +62 -0
  38. package/dist/commands/mcp/tools.js +359 -0
  39. package/dist/commands/project/commands.js +132 -0
  40. package/dist/commands/project/devices.js +10 -26
  41. package/dist/commands/project/doctor.js +58 -0
  42. package/dist/commands/project/generate.js +356 -30
  43. package/dist/commands/project/setup.js +10 -28
  44. package/dist/commands/project/status.js +65 -0
  45. package/dist/commands/version/bump.js +75 -101
  46. package/dist/commands/version/commands.js +63 -0
  47. package/dist/commands/version/update.js +36 -24
  48. package/dist/constants.js +6 -1
  49. package/dist/styles.js +42 -5
  50. package/dist/utils/helpers/error.js +14 -0
  51. package/dist/utils/helpers/file.js +1 -1
  52. package/dist/utils/helpers/version.js +2 -1
  53. package/dist/utils/services/backup.js +12 -1
  54. package/dist/utils/services/command.js +1 -34
  55. package/dist/utils/services/exec.js +76 -101
  56. package/dist/utils/services/logger.js +10 -4
  57. package/dist/utils/validators/validation.js +24 -12
  58. package/docs/INSTALLATION.md +72 -0
  59. package/docs/TROUBLESHOOT.md +140 -0
  60. package/docs/USAGE.md +185 -0
  61. package/docs/VERSION_MANAGEMENT.md +177 -0
  62. package/package.json +7 -11
  63. package/src/cli.ts +82 -371
  64. package/src/commands/build/commands.ts +169 -0
  65. package/src/commands/build/testflight.ts +18 -0
  66. package/src/commands/clean/commands.ts +43 -0
  67. package/src/commands/clean/flutter.ts +9 -13
  68. package/src/commands/clean/ios.ts +13 -13
  69. package/src/commands/config/aliases.ts +150 -0
  70. package/src/commands/config/commands.ts +50 -0
  71. package/src/commands/config/initApp.ts +213 -0
  72. package/src/commands/config/rollback.ts +16 -4
  73. package/src/commands/config/upgrade.ts +40 -0
  74. package/src/commands/mcp/commands.ts +23 -0
  75. package/src/commands/mcp/server.ts +35 -0
  76. package/src/commands/mcp/setup.ts +69 -0
  77. package/src/commands/mcp/tools.ts +365 -0
  78. package/src/commands/project/commands.ts +132 -0
  79. package/src/commands/project/devices.ts +11 -24
  80. package/src/commands/project/doctor.ts +81 -0
  81. package/src/commands/project/generate.ts +414 -32
  82. package/src/commands/project/setup.ts +13 -30
  83. package/src/commands/project/status.ts +72 -0
  84. package/src/commands/version/bump.ts +98 -110
  85. package/src/commands/version/commands.ts +76 -0
  86. package/src/commands/version/update.ts +86 -75
  87. package/src/constants.ts +7 -1
  88. package/src/styles.ts +49 -7
  89. package/src/utils/helpers/error.ts +16 -0
  90. package/src/utils/helpers/file.ts +1 -1
  91. package/src/utils/helpers/version.ts +2 -1
  92. package/src/utils/services/backup.ts +17 -1
  93. package/src/utils/services/command.ts +1 -58
  94. package/src/utils/services/exec.ts +92 -117
  95. package/src/utils/services/logger.ts +12 -4
  96. package/src/utils/validators/validation.ts +24 -12
  97. package/CODE_QUALITY.md +0 -398
  98. package/ENHANCEMENTS.md +0 -310
  99. package/FEATURE_ENHANCEMENTS.md +0 -435
  100. package/INSTALLATION.md +0 -118
  101. package/SAFETY_FEATURES.md +0 -396
  102. package/TROUBLESHOOT.md +0 -60
  103. package/USAGE.md +0 -412
  104. package/VERSION_MANAGEMENT.md +0 -438
@@ -1,21 +1,23 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
3
  import { LoggerHelpers } from "../../utils/services/logger.js";
4
+ import { validateFlutterProject } from "../../utils/validators/validation.js";
5
+ import { handleCommandError } from "../../utils/helpers/error.js";
6
+ import { VSCODE_SETTINGS_TEMPLATE, PROJECT_PATHS } from "../../constants.js";
4
7
 
5
8
  export {
6
9
  createVscodeSettings,
7
10
  };
8
11
 
9
- /**
10
- * Creates a .vscode directory (if it doesn't exist) and writes a settings.json file
11
- * with recommended Flutter configuration. The Flutter SDK path is set to ".fvm/flutter_sdk".
12
- */
13
- async function createVscodeSettings(){
12
+ async function createVscodeSettings() {
13
+ if (!validateFlutterProject()) {
14
+ process.exit(1);
15
+ }
16
+
14
17
  try {
15
- const vscodeDir = path.join(process.cwd(), ".vscode");
16
- const settingsPath = path.join(vscodeDir, "settings.json");
18
+ const vscodeDir = path.join(process.cwd(), PROJECT_PATHS.VSCODE_DIR);
19
+ const settingsPath = path.join(process.cwd(), PROJECT_PATHS.VSCODE_SETTINGS);
17
20
 
18
- // Create the .vscode folder using Node.js fs (cross-platform)
19
21
  if (!fs.existsSync(vscodeDir)) {
20
22
  fs.mkdirSync(vscodeDir, { recursive: true });
21
23
  LoggerHelpers.success("Created .vscode directory.");
@@ -23,28 +25,9 @@ async function createVscodeSettings(){
23
25
  LoggerHelpers.info(".vscode directory already exists.");
24
26
  }
25
27
 
26
- // Define the settings object
27
- const settings = {
28
- "dart.flutterSdkPath": ".fvm/flutter_sdk",
29
- "editor.formatOnSave": true,
30
- "dart.previewFlutterUiGuides": true,
31
- "files.exclude": {
32
- "**/.git": true,
33
- "**/.DS_Store": true,
34
- "**/node_modules": true,
35
- "**/build": true
36
- }
37
- };
38
-
39
- // Write settings.json using Node.js fs
40
- fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf8");
28
+ fs.writeFileSync(settingsPath, JSON.stringify(VSCODE_SETTINGS_TEMPLATE, null, 2), "utf8");
41
29
  LoggerHelpers.success("Created .vscode/settings.json with Flutter configuration.");
42
30
  } catch (error) {
43
- if (error instanceof Error) {
44
- LoggerHelpers.error(`Error while creating VSCode settings: ${error.message}`);
45
- } else {
46
- LoggerHelpers.error(`Error while creating VSCode settings: ${error}`);
47
- }
48
- process.exit(1);
31
+ handleCommandError(error, "Error creating VSCode settings");
49
32
  }
50
- }
33
+ }
@@ -0,0 +1,72 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import chalk from "chalk";
4
+ import { getCurrentVersion, formatVersion, getCurrentIosBuildNumber } from "../../utils/helpers/version.js";
5
+ import { getConfigPath } from "../../utils/services/config.js";
6
+ import { validateFlutterProject } from "../../utils/validators/validation.js";
7
+ import { PROJECT_PATHS, BACKUP_CONFIG } from "../../constants.js";
8
+
9
+ export { showStatus };
10
+
11
+ async function showStatus(): Promise<void> {
12
+ console.log(chalk.bold("\nOptiKit Status"));
13
+ console.log(chalk.gray("─".repeat(45)));
14
+
15
+ // Version info
16
+ if (validateFlutterProject(true)) {
17
+ try {
18
+ const current = getCurrentVersion();
19
+ const iosBuild = getCurrentIosBuildNumber();
20
+ console.log(chalk.cyan("\n Version:"), chalk.white.bold(formatVersion(current)));
21
+ console.log(chalk.gray(" Android Build:"), chalk.white(current.buildNumber));
22
+ console.log(chalk.gray(" iOS Build:"), chalk.white(iosBuild));
23
+ } catch {
24
+ console.log(chalk.yellow("\n Version:"), chalk.gray("Unable to read"));
25
+ }
26
+ } else {
27
+ console.log(chalk.yellow("\n Version:"), chalk.gray("Not a Flutter project"));
28
+ }
29
+
30
+ // FVM status
31
+ const fvmPath = path.join(process.cwd(), PROJECT_PATHS.FVM_FLUTTER_SDK);
32
+ const useFvm = fs.existsSync(fvmPath);
33
+ console.log(chalk.cyan(" Flutter:"), chalk.white(useFvm ? "FVM" : "Global SDK"));
34
+
35
+ // Backup count
36
+ const backupCount = countBackups(process.cwd());
37
+ console.log(chalk.cyan(" Backups:"), chalk.white(backupCount > 0 ? `${backupCount} file(s)` : "None"));
38
+
39
+ // Config
40
+ const configPath = getConfigPath();
41
+ console.log(chalk.cyan(" Config:"), chalk.white(configPath ?? "Not initialized (run: optikit init)"));
42
+
43
+ // Project structure
44
+ const hasIos = fs.existsSync(path.join(process.cwd(), PROJECT_PATHS.IOS_DIR));
45
+ const hasAndroid = fs.existsSync(path.join(process.cwd(), PROJECT_PATHS.ANDROID_DIR));
46
+ const platforms = [hasIos ? "iOS" : null, hasAndroid ? "Android" : null].filter(Boolean).join(", ");
47
+ console.log(chalk.cyan(" Platforms:"), chalk.white(platforms || "None detected"));
48
+
49
+ console.log(chalk.gray("\n" + "─".repeat(45) + "\n"));
50
+ }
51
+
52
+ function countBackups(dir: string): number {
53
+ let count = 0;
54
+
55
+ function search(currentDir: string): void {
56
+ try {
57
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
58
+ for (const entry of entries) {
59
+ if (entry.isDirectory()) {
60
+ if (entry.name === BACKUP_CONFIG.DIR_NAME) {
61
+ count += fs.readdirSync(path.join(currentDir, entry.name)).length;
62
+ } else if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
63
+ search(path.join(currentDir, entry.name));
64
+ }
65
+ }
66
+ }
67
+ } catch { /* skip inaccessible dirs */ }
68
+ }
69
+
70
+ search(dir);
71
+ return count;
72
+ }
@@ -1,3 +1,4 @@
1
+ import { createInterface } from "readline";
1
2
  import { validateFlutterProject } from "../../utils/validators/validation.js";
2
3
  import { LoggerHelpers } from "../../utils/services/logger.js";
3
4
  import {
@@ -7,6 +8,7 @@ import {
7
8
  getNextBuildNumber,
8
9
  getCurrentIosBuildNumber
9
10
  } from "../../utils/helpers/version.js";
11
+ import { handleCommandError } from "../../utils/helpers/error.js";
10
12
  import { updateFlutterVersion } from "./update.js";
11
13
  import chalk from "chalk";
12
14
 
@@ -19,23 +21,65 @@ export {
19
21
  };
20
22
 
21
23
  /**
22
- * Bumps version with semantic versioning (major, minor, patch)
23
- * Android build number increments with version
24
- * iOS build number resets to 1 on version change
25
- *
26
- * @param type - Type of version bump (major, minor, patch)
24
+ * Prompts user for confirmation. Returns true if confirmed.
25
+ * Skips prompt and returns true if skipConfirm is true (--yes flag).
27
26
  */
27
+ function confirmAction(message: string, skipConfirm: boolean): Promise<boolean> {
28
+ if (skipConfirm) return Promise.resolve(true);
29
+
30
+ return new Promise((resolve) => {
31
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
32
+ rl.question(chalk.yellow(`${message} (y/n): `), (answer) => {
33
+ rl.close();
34
+ resolve(answer.toLowerCase() === "y");
35
+ });
36
+ });
37
+ }
38
+
39
+ /**
40
+ * Shows a colored diff preview of what will change.
41
+ */
42
+ function showDiffPreview(
43
+ oldVersion: string,
44
+ newVersion: string,
45
+ oldAndroidBuild: number,
46
+ newAndroidBuild: number | string,
47
+ oldIosBuild: number,
48
+ newIosBuild: number | string
49
+ ) {
50
+ console.log(chalk.cyan("\nFile changes preview:"));
51
+
52
+ if (newAndroidBuild !== "") {
53
+ console.log(chalk.gray("\n pubspec.yaml:"));
54
+ console.log(chalk.red(` - version: ${oldVersion}+${oldAndroidBuild}`));
55
+ console.log(chalk.green(` + version: ${newVersion}+${newAndroidBuild}`));
56
+ }
57
+
58
+ if (newIosBuild !== "") {
59
+ console.log(chalk.gray("\n project.pbxproj:"));
60
+ console.log(chalk.red(` - MARKETING_VERSION = ${oldVersion};`));
61
+ console.log(chalk.green(` + MARKETING_VERSION = ${newVersion};`));
62
+ console.log(chalk.red(` - CURRENT_PROJECT_VERSION = ${oldIosBuild};`));
63
+ console.log(chalk.green(` + CURRENT_PROJECT_VERSION = ${newIosBuild};`));
64
+ }
65
+
66
+ console.log();
67
+ }
68
+
28
69
  async function bumpVersion(
29
- type: 'major' | 'minor' | 'patch'
70
+ type: 'major' | 'minor' | 'patch',
71
+ skipConfirm: boolean = false
30
72
  ): Promise<void> {
31
- // Pre-flight validation
32
73
  if (!validateFlutterProject()) {
33
74
  process.exit(1);
34
75
  }
35
76
 
36
77
  try {
37
78
  const current = getCurrentVersion();
79
+ const currentIosBuild = getCurrentIosBuildNumber();
38
80
  const newVersion = incrementVersion(current, type);
81
+ const currentVersionString = `${current.major}.${current.minor}.${current.patch}`;
82
+ const newVersionString = `${newVersion.major}.${newVersion.minor}.${newVersion.patch}`;
39
83
 
40
84
  LoggerHelpers.info(`Current version: ${formatVersion(current)}`);
41
85
  LoggerHelpers.info(`Bumping ${type} version...`);
@@ -46,16 +90,20 @@ async function bumpVersion(
46
90
 
47
91
  console.log(chalk.cyan("\nBuild number strategy:"));
48
92
  console.log(chalk.gray(" Android:"), chalk.white(`${current.buildNumber} → ${newVersion.buildNumber} (incremented)`));
49
- console.log(chalk.gray(" iOS:"), chalk.white(`${current.buildNumber} → 1 (reset for new version)`));
50
- console.log();
93
+ console.log(chalk.gray(" iOS:"), chalk.white(`${currentIosBuild} → 1 (reset for new version)`));
94
+
95
+ showDiffPreview(currentVersionString, newVersionString, current.buildNumber, newVersion.buildNumber, currentIosBuild, 1);
96
+
97
+ const confirmed = await confirmAction("Proceed with version bump?", skipConfirm);
98
+ if (!confirmed) {
99
+ LoggerHelpers.info("Bump cancelled.");
100
+ return;
101
+ }
51
102
 
52
- // Update with new version
53
- // Android uses the incremented build number from version
54
- // iOS gets reset to 1 for new version releases
55
103
  await updateFlutterVersion(
56
- `${newVersion.major}.${newVersion.minor}.${newVersion.patch}`,
104
+ newVersionString,
57
105
  newVersion.buildNumber.toString(),
58
- "1" // iOS always starts at 1 for new versions
106
+ "1"
59
107
  );
60
108
 
61
109
  LoggerHelpers.success(`Version bumped to ${formatVersion(newVersion)}`);
@@ -63,24 +111,11 @@ async function bumpVersion(
63
111
  console.log(chalk.gray("iOS build:"), chalk.white("1"));
64
112
 
65
113
  } catch (error) {
66
- if (error instanceof Error) {
67
- LoggerHelpers.error(`Error bumping version: ${error.message}`);
68
- } else {
69
- LoggerHelpers.error(`Error bumping version: ${error}`);
70
- }
71
- process.exit(1);
114
+ handleCommandError(error, "Error bumping version");
72
115
  }
73
116
  }
74
117
 
75
- /**
76
- * Increments ONLY iOS build number (for TestFlight builds)
77
- * Keeps version and Android build number unchanged
78
- * Perfect for uploading new iOS builds without changing app version
79
- *
80
- * Example: 1.0.2+45 (iOS: 45) → 1.0.2+45 (iOS: 46)
81
- */
82
- async function bumpIosBuildOnly(): Promise<void> {
83
- // Pre-flight validation
118
+ async function bumpIosBuildOnly(skipConfirm: boolean = false): Promise<void> {
84
119
  if (!validateFlutterProject()) {
85
120
  process.exit(1);
86
121
  }
@@ -88,8 +123,6 @@ async function bumpIosBuildOnly(): Promise<void> {
88
123
  try {
89
124
  const current = getCurrentVersion();
90
125
  const currentVersionString = `${current.major}.${current.minor}.${current.patch}`;
91
-
92
- // Read actual iOS build number from project.pbxproj
93
126
  const currentIosBuild = getCurrentIosBuildNumber();
94
127
  const nextIosBuild = currentIosBuild + 1;
95
128
 
@@ -100,35 +133,25 @@ async function bumpIosBuildOnly(): Promise<void> {
100
133
  console.log(chalk.gray(" Version:"), chalk.white(`${currentVersionString} (unchanged)`));
101
134
  console.log(chalk.gray(" Android:"), chalk.white(`${current.buildNumber} (unchanged)`));
102
135
  console.log(chalk.gray(" iOS:"), chalk.white(`${currentIosBuild} → ${nextIosBuild}`), chalk.green("(incremented)"));
103
- console.log();
104
136
 
105
- // Update only iOS build number
106
- await updateFlutterVersion(
107
- currentVersionString,
108
- "", // Empty string means don't update Android
109
- nextIosBuild.toString()
110
- );
137
+ showDiffPreview(currentVersionString, currentVersionString, current.buildNumber, "", currentIosBuild, nextIosBuild);
138
+
139
+ const confirmed = await confirmAction("Proceed?", skipConfirm);
140
+ if (!confirmed) {
141
+ LoggerHelpers.info("Bump cancelled.");
142
+ return;
143
+ }
144
+
145
+ await updateFlutterVersion(currentVersionString, "", nextIosBuild.toString());
111
146
 
112
147
  LoggerHelpers.success(`iOS build number incremented to ${nextIosBuild}`);
113
- console.log(chalk.gray("\nResult:"), chalk.white(`${currentVersionString}+${current.buildNumber} (iOS: ${nextIosBuild})`));
114
- console.log(chalk.gray("Use this for:"), chalk.white("TestFlight uploads without version changes"));
115
148
 
116
149
  } catch (error) {
117
- if (error instanceof Error) {
118
- LoggerHelpers.error(`Error incrementing iOS build: ${error.message}`);
119
- } else {
120
- LoggerHelpers.error(`Error incrementing iOS build: ${error}`);
121
- }
122
- process.exit(1);
150
+ handleCommandError(error, "Error incrementing iOS build");
123
151
  }
124
152
  }
125
153
 
126
- /**
127
- * Increments ONLY Android build number
128
- * Keeps version and iOS build number unchanged
129
- */
130
- async function bumpAndroidBuildOnly(): Promise<void> {
131
- // Pre-flight validation
154
+ async function bumpAndroidBuildOnly(skipConfirm: boolean = false): Promise<void> {
132
155
  if (!validateFlutterProject()) {
133
156
  process.exit(1);
134
157
  }
@@ -136,6 +159,7 @@ async function bumpAndroidBuildOnly(): Promise<void> {
136
159
  try {
137
160
  const current = getCurrentVersion();
138
161
  const currentVersionString = `${current.major}.${current.minor}.${current.patch}`;
162
+ const currentIosBuild = getCurrentIosBuildNumber();
139
163
  const nextAndroidBuild = current.buildNumber + 1;
140
164
 
141
165
  LoggerHelpers.info(`Current version: ${formatVersion(current)}`);
@@ -145,34 +169,25 @@ async function bumpAndroidBuildOnly(): Promise<void> {
145
169
  console.log(chalk.gray(" Version:"), chalk.white(`${currentVersionString} (unchanged)`));
146
170
  console.log(chalk.gray(" Android:"), chalk.white(`${current.buildNumber} → ${nextAndroidBuild}`), chalk.green("(incremented)"));
147
171
  console.log(chalk.gray(" iOS:"), chalk.white("(unchanged)"));
148
- console.log();
149
172
 
150
- // Update only Android build number
151
- await updateFlutterVersion(
152
- currentVersionString,
153
- nextAndroidBuild.toString(),
154
- "" // Empty string means don't update iOS
155
- );
173
+ showDiffPreview(currentVersionString, currentVersionString, current.buildNumber, nextAndroidBuild, currentIosBuild, "");
174
+
175
+ const confirmed = await confirmAction("Proceed?", skipConfirm);
176
+ if (!confirmed) {
177
+ LoggerHelpers.info("Bump cancelled.");
178
+ return;
179
+ }
180
+
181
+ await updateFlutterVersion(currentVersionString, nextAndroidBuild.toString(), "");
156
182
 
157
183
  LoggerHelpers.success(`Android build number incremented to ${nextAndroidBuild}`);
158
184
 
159
185
  } catch (error) {
160
- if (error instanceof Error) {
161
- LoggerHelpers.error(`Error incrementing Android build: ${error.message}`);
162
- } else {
163
- LoggerHelpers.error(`Error incrementing Android build: ${error}`);
164
- }
165
- process.exit(1);
186
+ handleCommandError(error, "Error incrementing Android build");
166
187
  }
167
188
  }
168
189
 
169
- /**
170
- * Increments BOTH Android and iOS build numbers
171
- * Keeps version unchanged
172
- * Perfect for updating both platforms simultaneously
173
- */
174
- async function bumpBothBuilds(): Promise<void> {
175
- // Pre-flight validation
190
+ async function bumpBothBuilds(skipConfirm: boolean = false): Promise<void> {
176
191
  if (!validateFlutterProject()) {
177
192
  process.exit(1);
178
193
  }
@@ -180,8 +195,6 @@ async function bumpBothBuilds(): Promise<void> {
180
195
  try {
181
196
  const current = getCurrentVersion();
182
197
  const currentVersionString = `${current.major}.${current.minor}.${current.patch}`;
183
-
184
- // Read actual iOS build number from project.pbxproj
185
198
  const currentIosBuild = getCurrentIosBuildNumber();
186
199
  const nextAndroidBuild = current.buildNumber + 1;
187
200
  const nextIosBuild = currentIosBuild + 1;
@@ -193,32 +206,24 @@ async function bumpBothBuilds(): Promise<void> {
193
206
  console.log(chalk.gray(" Version:"), chalk.white(`${currentVersionString} (unchanged)`));
194
207
  console.log(chalk.gray(" Android:"), chalk.white(`${current.buildNumber} → ${nextAndroidBuild}`), chalk.green("(incremented)"));
195
208
  console.log(chalk.gray(" iOS:"), chalk.white(`${currentIosBuild} → ${nextIosBuild}`), chalk.green("(incremented)"));
196
- console.log();
197
209
 
198
- // Update both Android and iOS build numbers
199
- await updateFlutterVersion(
200
- currentVersionString,
201
- nextAndroidBuild.toString(),
202
- nextIosBuild.toString()
203
- );
210
+ showDiffPreview(currentVersionString, currentVersionString, current.buildNumber, nextAndroidBuild, currentIosBuild, nextIosBuild);
211
+
212
+ const confirmed = await confirmAction("Proceed?", skipConfirm);
213
+ if (!confirmed) {
214
+ LoggerHelpers.info("Bump cancelled.");
215
+ return;
216
+ }
217
+
218
+ await updateFlutterVersion(currentVersionString, nextAndroidBuild.toString(), nextIosBuild.toString());
204
219
 
205
220
  LoggerHelpers.success(`Both build numbers incremented successfully`);
206
- console.log(chalk.gray("\nResult:"), chalk.white(`${currentVersionString}+${nextAndroidBuild} (iOS: ${nextIosBuild})`));
207
- console.log(chalk.gray("Use this for:"), chalk.white("Simultaneous Android and iOS releases"));
208
221
 
209
222
  } catch (error) {
210
- if (error instanceof Error) {
211
- LoggerHelpers.error(`Error incrementing build numbers: ${error.message}`);
212
- } else {
213
- LoggerHelpers.error(`Error incrementing build numbers: ${error}`);
214
- }
215
- process.exit(1);
223
+ handleCommandError(error, "Error incrementing build numbers");
216
224
  }
217
225
  }
218
226
 
219
- /**
220
- * Shows current version information
221
- */
222
227
  async function showCurrentVersion(): Promise<void> {
223
228
  try {
224
229
  const current = getCurrentVersion();
@@ -235,23 +240,6 @@ async function showCurrentVersion(): Promise<void> {
235
240
  console.log();
236
241
 
237
242
  } catch (error) {
238
- if (error instanceof Error) {
239
- LoggerHelpers.error(`Error reading version: ${error.message}`);
240
- } else {
241
- LoggerHelpers.error(`Error reading version: ${error}`);
242
- }
243
- process.exit(1);
243
+ handleCommandError(error, "Error reading version");
244
244
  }
245
245
  }
246
-
247
- /**
248
- * Helper to get next iOS build number
249
- * In the future, this could read from Info.plist or project.pbxproj
250
- * For now, we'll use a simple increment
251
- */
252
- async function getNextIosBuildNumber(): Promise<number> {
253
- // TODO: Read actual iOS build number from Info.plist or project.pbxproj
254
- // For now, we'll just increment based on timestamp or simple counter
255
- const current = getCurrentVersion();
256
- return current.buildNumber + 1;
257
- }
@@ -0,0 +1,76 @@
1
+ import type { CommandModule } from "yargs";
2
+ import {
3
+ bumpVersion,
4
+ bumpIosBuildOnly,
5
+ bumpAndroidBuildOnly,
6
+ bumpBothBuilds,
7
+ showCurrentVersion,
8
+ } from "./bump.js";
9
+ import { updateFlutterVersion } from "./update.js";
10
+
11
+ const yesOption = {
12
+ yes: { alias: "y", type: "boolean" as const, default: false, description: "Skip confirmation prompt" },
13
+ };
14
+
15
+ export const versionCommands: CommandModule[] = [
16
+ {
17
+ command: "bump <type>",
18
+ describe: "Bump version (major, minor, or patch)",
19
+ builder: (yargs) => {
20
+ return yargs
21
+ .positional("type", {
22
+ describe: "Version bump type (major, minor, patch)",
23
+ type: "string" as const,
24
+ choices: ["major", "minor", "patch"] as const,
25
+ })
26
+ .option("yes", yesOption.yes);
27
+ },
28
+ handler: async (argv) => {
29
+ await bumpVersion(argv.type as "major" | "minor" | "patch", argv.yes as boolean);
30
+ },
31
+ },
32
+ {
33
+ command: "bump-ios",
34
+ aliases: ["bi"],
35
+ describe: "Increment iOS build number only (for TestFlight)",
36
+ builder: yesOption,
37
+ handler: async (argv) => { await bumpIosBuildOnly(argv.yes as boolean); },
38
+ },
39
+ {
40
+ command: "bump-android",
41
+ aliases: ["ba"],
42
+ describe: "Increment Android build number only",
43
+ builder: yesOption,
44
+ handler: async (argv) => { await bumpAndroidBuildOnly(argv.yes as boolean); },
45
+ },
46
+ {
47
+ command: "bump-build",
48
+ aliases: ["bb"],
49
+ describe: "Increment both Android and iOS build numbers",
50
+ builder: yesOption,
51
+ handler: async (argv) => { await bumpBothBuilds(argv.yes as boolean); },
52
+ },
53
+ {
54
+ command: "version",
55
+ aliases: ["v"],
56
+ describe: "Show current version information",
57
+ handler: async () => { await showCurrentVersion(); },
58
+ },
59
+ {
60
+ command: "flutter-update-version",
61
+ aliases: ["vset"],
62
+ describe: "Update version and build numbers for both Android and iOS",
63
+ builder: {
64
+ "app-version": { type: "string", description: "The version number to set", default: "" },
65
+ "android-build": { type: "string", description: "The Android build number", default: "" },
66
+ "ios-build": { type: "string", description: "The iOS build number", default: "" },
67
+ },
68
+ handler: async (argv) => {
69
+ await updateFlutterVersion(
70
+ argv["app-version"] as string,
71
+ argv["android-build"] as string,
72
+ argv["ios-build"] as string
73
+ );
74
+ },
75
+ },
76
+ ];