optikit 1.2.4 → 1.3.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 +28 -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 +79 -46
  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 -241
  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 +183 -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 +96 -82
  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 -362
  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 +211 -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 +124 -85
  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 -388
  104. package/VERSION_MANAGEMENT.md +0 -438
@@ -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
 
@@ -14,27 +16,70 @@ export {
14
16
  bumpVersion,
15
17
  bumpIosBuildOnly,
16
18
  bumpAndroidBuildOnly,
19
+ bumpBothBuilds,
17
20
  showCurrentVersion
18
21
  };
19
22
 
20
23
  /**
21
- * Bumps version with semantic versioning (major, minor, patch)
22
- * Android build number increments with version
23
- * iOS build number resets to 1 on version change
24
- *
25
- * @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).
26
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
+
27
69
  async function bumpVersion(
28
- type: 'major' | 'minor' | 'patch'
70
+ type: 'major' | 'minor' | 'patch',
71
+ skipConfirm: boolean = false
29
72
  ): Promise<void> {
30
- // Pre-flight validation
31
73
  if (!validateFlutterProject()) {
32
74
  process.exit(1);
33
75
  }
34
76
 
35
77
  try {
36
78
  const current = getCurrentVersion();
79
+ const currentIosBuild = getCurrentIosBuildNumber();
37
80
  const newVersion = incrementVersion(current, type);
81
+ const currentVersionString = `${current.major}.${current.minor}.${current.patch}`;
82
+ const newVersionString = `${newVersion.major}.${newVersion.minor}.${newVersion.patch}`;
38
83
 
39
84
  LoggerHelpers.info(`Current version: ${formatVersion(current)}`);
40
85
  LoggerHelpers.info(`Bumping ${type} version...`);
@@ -45,16 +90,20 @@ async function bumpVersion(
45
90
 
46
91
  console.log(chalk.cyan("\nBuild number strategy:"));
47
92
  console.log(chalk.gray(" Android:"), chalk.white(`${current.buildNumber} → ${newVersion.buildNumber} (incremented)`));
48
- console.log(chalk.gray(" iOS:"), chalk.white(`${current.buildNumber} → 1 (reset for new version)`));
49
- 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
+ }
50
102
 
51
- // Update with new version
52
- // Android uses the incremented build number from version
53
- // iOS gets reset to 1 for new version releases
54
103
  await updateFlutterVersion(
55
- `${newVersion.major}.${newVersion.minor}.${newVersion.patch}`,
104
+ newVersionString,
56
105
  newVersion.buildNumber.toString(),
57
- "1" // iOS always starts at 1 for new versions
106
+ "1"
58
107
  );
59
108
 
60
109
  LoggerHelpers.success(`Version bumped to ${formatVersion(newVersion)}`);
@@ -62,24 +111,11 @@ async function bumpVersion(
62
111
  console.log(chalk.gray("iOS build:"), chalk.white("1"));
63
112
 
64
113
  } catch (error) {
65
- if (error instanceof Error) {
66
- LoggerHelpers.error(`Error bumping version: ${error.message}`);
67
- } else {
68
- LoggerHelpers.error(`Error bumping version: ${error}`);
69
- }
70
- process.exit(1);
114
+ handleCommandError(error, "Error bumping version");
71
115
  }
72
116
  }
73
117
 
74
- /**
75
- * Increments ONLY iOS build number (for TestFlight builds)
76
- * Keeps version and Android build number unchanged
77
- * Perfect for uploading new iOS builds without changing app version
78
- *
79
- * Example: 1.0.2+45 (iOS: 45) → 1.0.2+45 (iOS: 46)
80
- */
81
- async function bumpIosBuildOnly(): Promise<void> {
82
- // Pre-flight validation
118
+ async function bumpIosBuildOnly(skipConfirm: boolean = false): Promise<void> {
83
119
  if (!validateFlutterProject()) {
84
120
  process.exit(1);
85
121
  }
@@ -87,8 +123,6 @@ async function bumpIosBuildOnly(): Promise<void> {
87
123
  try {
88
124
  const current = getCurrentVersion();
89
125
  const currentVersionString = `${current.major}.${current.minor}.${current.patch}`;
90
-
91
- // Read actual iOS build number from project.pbxproj
92
126
  const currentIosBuild = getCurrentIosBuildNumber();
93
127
  const nextIosBuild = currentIosBuild + 1;
94
128
 
@@ -99,35 +133,25 @@ async function bumpIosBuildOnly(): Promise<void> {
99
133
  console.log(chalk.gray(" Version:"), chalk.white(`${currentVersionString} (unchanged)`));
100
134
  console.log(chalk.gray(" Android:"), chalk.white(`${current.buildNumber} (unchanged)`));
101
135
  console.log(chalk.gray(" iOS:"), chalk.white(`${currentIosBuild} → ${nextIosBuild}`), chalk.green("(incremented)"));
102
- console.log();
103
136
 
104
- // Update only iOS build number
105
- await updateFlutterVersion(
106
- currentVersionString,
107
- "", // Empty string means don't update Android
108
- nextIosBuild.toString()
109
- );
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());
110
146
 
111
147
  LoggerHelpers.success(`iOS build number incremented to ${nextIosBuild}`);
112
- console.log(chalk.gray("\nResult:"), chalk.white(`${currentVersionString}+${current.buildNumber} (iOS: ${nextIosBuild})`));
113
- console.log(chalk.gray("Use this for:"), chalk.white("TestFlight uploads without version changes"));
114
148
 
115
149
  } catch (error) {
116
- if (error instanceof Error) {
117
- LoggerHelpers.error(`Error incrementing iOS build: ${error.message}`);
118
- } else {
119
- LoggerHelpers.error(`Error incrementing iOS build: ${error}`);
120
- }
121
- process.exit(1);
150
+ handleCommandError(error, "Error incrementing iOS build");
122
151
  }
123
152
  }
124
153
 
125
- /**
126
- * Increments ONLY Android build number
127
- * Keeps version and iOS build number unchanged
128
- */
129
- async function bumpAndroidBuildOnly(): Promise<void> {
130
- // Pre-flight validation
154
+ async function bumpAndroidBuildOnly(skipConfirm: boolean = false): Promise<void> {
131
155
  if (!validateFlutterProject()) {
132
156
  process.exit(1);
133
157
  }
@@ -135,6 +159,7 @@ async function bumpAndroidBuildOnly(): Promise<void> {
135
159
  try {
136
160
  const current = getCurrentVersion();
137
161
  const currentVersionString = `${current.major}.${current.minor}.${current.patch}`;
162
+ const currentIosBuild = getCurrentIosBuildNumber();
138
163
  const nextAndroidBuild = current.buildNumber + 1;
139
164
 
140
165
  LoggerHelpers.info(`Current version: ${formatVersion(current)}`);
@@ -144,30 +169,61 @@ async function bumpAndroidBuildOnly(): Promise<void> {
144
169
  console.log(chalk.gray(" Version:"), chalk.white(`${currentVersionString} (unchanged)`));
145
170
  console.log(chalk.gray(" Android:"), chalk.white(`${current.buildNumber} → ${nextAndroidBuild}`), chalk.green("(incremented)"));
146
171
  console.log(chalk.gray(" iOS:"), chalk.white("(unchanged)"));
147
- console.log();
148
172
 
149
- // Update only Android build number
150
- await updateFlutterVersion(
151
- currentVersionString,
152
- nextAndroidBuild.toString(),
153
- "" // Empty string means don't update iOS
154
- );
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(), "");
155
182
 
156
183
  LoggerHelpers.success(`Android build number incremented to ${nextAndroidBuild}`);
157
184
 
158
185
  } catch (error) {
159
- if (error instanceof Error) {
160
- LoggerHelpers.error(`Error incrementing Android build: ${error.message}`);
161
- } else {
162
- LoggerHelpers.error(`Error incrementing Android build: ${error}`);
163
- }
186
+ handleCommandError(error, "Error incrementing Android build");
187
+ }
188
+ }
189
+
190
+ async function bumpBothBuilds(skipConfirm: boolean = false): Promise<void> {
191
+ if (!validateFlutterProject()) {
164
192
  process.exit(1);
165
193
  }
194
+
195
+ try {
196
+ const current = getCurrentVersion();
197
+ const currentVersionString = `${current.major}.${current.minor}.${current.patch}`;
198
+ const currentIosBuild = getCurrentIosBuildNumber();
199
+ const nextAndroidBuild = current.buildNumber + 1;
200
+ const nextIosBuild = currentIosBuild + 1;
201
+
202
+ LoggerHelpers.info(`Current version: ${formatVersion(current)}`);
203
+ LoggerHelpers.info("Incrementing both Android and iOS build numbers...");
204
+
205
+ console.log(chalk.cyan("\nBuild number changes:"));
206
+ console.log(chalk.gray(" Version:"), chalk.white(`${currentVersionString} (unchanged)`));
207
+ console.log(chalk.gray(" Android:"), chalk.white(`${current.buildNumber} → ${nextAndroidBuild}`), chalk.green("(incremented)"));
208
+ console.log(chalk.gray(" iOS:"), chalk.white(`${currentIosBuild} → ${nextIosBuild}`), chalk.green("(incremented)"));
209
+
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());
219
+
220
+ LoggerHelpers.success(`Both build numbers incremented successfully`);
221
+
222
+ } catch (error) {
223
+ handleCommandError(error, "Error incrementing build numbers");
224
+ }
166
225
  }
167
226
 
168
- /**
169
- * Shows current version information
170
- */
171
227
  async function showCurrentVersion(): Promise<void> {
172
228
  try {
173
229
  const current = getCurrentVersion();
@@ -184,23 +240,6 @@ async function showCurrentVersion(): Promise<void> {
184
240
  console.log();
185
241
 
186
242
  } catch (error) {
187
- if (error instanceof Error) {
188
- LoggerHelpers.error(`Error reading version: ${error.message}`);
189
- } else {
190
- LoggerHelpers.error(`Error reading version: ${error}`);
191
- }
192
- process.exit(1);
243
+ handleCommandError(error, "Error reading version");
193
244
  }
194
245
  }
195
-
196
- /**
197
- * Helper to get next iOS build number
198
- * In the future, this could read from Info.plist or project.pbxproj
199
- * For now, we'll use a simple increment
200
- */
201
- async function getNextIosBuildNumber(): Promise<number> {
202
- // TODO: Read actual iOS build number from Info.plist or project.pbxproj
203
- // For now, we'll just increment based on timestamp or simple counter
204
- const current = getCurrentVersion();
205
- return current.buildNumber + 1;
206
- }
@@ -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
+ ];
@@ -1,9 +1,11 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
- import { execInIos } from "../../utils/services/exec.js";
4
3
  import { LoggerHelpers } from "../../utils/services/logger.js";
5
4
  import { validateFlutterProject } from "../../utils/validators/validation.js";
6
- import { createBackup } from "../../utils/services/backup.js";
5
+ import { createBackupWithCleanup } from "../../utils/services/backup.js";
6
+ import { handleCommandError } from "../../utils/helpers/error.js";
7
+ import { isDryRunMode, DryRunManager } from "../../utils/helpers/dryRun.js";
8
+ import { PROJECT_PATHS } from "../../constants.js";
7
9
 
8
10
  const currentDir = process.cwd();
9
11
 
@@ -14,59 +16,66 @@ async function updateFlutterVersion(
14
16
  build: string,
15
17
  iosBuild: string
16
18
  ) {
17
- // Pre-flight validation
18
19
  if (!validateFlutterProject()) {
19
20
  process.exit(1);
20
21
  }
21
22
 
22
23
  try {
23
- // Always update the version since it is required.
24
24
  LoggerHelpers.info(`Starting update for version ${version}...`);
25
25
 
26
- // Update pubspec.yaml only if an Android build number is provided.
26
+ // Single dry-run manager for all operations
27
+ const dryRun = isDryRunMode() ? new DryRunManager() : null;
28
+
27
29
  if (build.trim().length > 0) {
28
30
  LoggerHelpers.info("Updating build number in pubspec.yaml...");
29
- await updatePubspecVersionAndBuild(version, build);
31
+ await updatePubspecVersionAndBuild(version, build, dryRun);
30
32
  } else {
31
33
  LoggerHelpers.info("Android build number not provided. Skipping pubspec.yaml update.");
32
34
  }
33
35
 
34
- // Update iOS settings only if an iOS build number is provided.
35
36
  if (iosBuild.trim().length > 0) {
36
37
  LoggerHelpers.info("Updating iOS version and build number...");
37
- await updateIosVersionAndBuild(version, iosBuild);
38
+ await updateIosVersionAndBuild(version, iosBuild, dryRun);
38
39
  } else {
39
40
  LoggerHelpers.info("iOS build number not provided. Skipping iOS update.");
40
41
  }
41
42
 
43
+ // Show dry-run summary once at the end
44
+ if (dryRun) {
45
+ dryRun.displaySummary();
46
+ return;
47
+ }
48
+
42
49
  LoggerHelpers.success(
43
50
  `Update complete. Version set to ${version}` +
44
51
  (build.trim().length > 0 ? `, Android build set to ${build}` : "") +
45
52
  (iosBuild.trim().length > 0 ? `, and iOS build set to ${iosBuild}` : "")
46
53
  );
47
54
  } catch (error: unknown) {
48
- LoggerHelpers.error(
49
- `Error while updating Flutter version and build: ${
50
- error instanceof Error ? error.message : String(error)
51
- }`
52
- );
53
- process.exit(1);
55
+ handleCommandError(error, "Error while updating Flutter version and build");
54
56
  }
55
57
  }
56
58
 
57
- async function updatePubspecVersionAndBuild(version: string, build: string) {
59
+ async function updatePubspecVersionAndBuild(
60
+ version: string,
61
+ build: string,
62
+ dryRun: DryRunManager | null
63
+ ) {
58
64
  try {
59
- const pubspecPath = path.join(currentDir, "pubspec.yaml");
65
+ const pubspecPath = path.join(currentDir, PROJECT_PATHS.PUBSPEC);
60
66
 
61
67
  if (!fs.existsSync(pubspecPath)) {
62
68
  throw new Error(`pubspec.yaml not found at ${pubspecPath}`);
63
69
  }
64
70
 
65
- // Create backup before modification
66
- createBackup(pubspecPath);
71
+ if (dryRun) {
72
+ dryRun.logFileOperation("Update pubspec.yaml", pubspecPath, `version: ${version}+${build}`);
73
+ return;
74
+ }
67
75
 
68
- const pubspecContent = fs.readFileSync(pubspecPath, "utf8");
76
+ createBackupWithCleanup(pubspecPath);
69
77
 
78
+ const pubspecContent = fs.readFileSync(pubspecPath, "utf8");
70
79
  const updatedPubspec = pubspecContent.replace(
71
80
  /version: \d+\.\d+\.\d+\+\d+/g,
72
81
  `version: ${version}+${build}`
@@ -78,74 +87,76 @@ async function updatePubspecVersionAndBuild(version: string, build: string) {
78
87
  );
79
88
  } catch (error) {
80
89
  LoggerHelpers.error(
81
- `Error while updating pubspec.yaml version and build: ${
90
+ `Error while updating pubspec.yaml: ${
82
91
  error instanceof Error ? error.message : String(error)
83
92
  }`
84
93
  );
85
-
86
94
  throw error;
87
95
  }
88
96
  }
89
97
 
90
- async function updateIosVersionAndBuild(version: string, iosBuild: string) {
91
- try {
92
- const currentDir = process.cwd();
93
-
94
- const projectPbxProjPath = path.join(currentDir, "ios/Runner.xcodeproj/project.pbxproj");
95
-
96
- if (!fs.existsSync(projectPbxProjPath)) {
97
- throw new Error(`project.pbxproj not found at ${projectPbxProjPath}`);
98
- }
99
-
100
- // Create backup before modification
101
- createBackup(projectPbxProjPath);
102
-
103
- let projectContent = fs.readFileSync(projectPbxProjPath, 'utf8');
104
-
105
- projectContent = projectContent
106
- .replace(
107
- /MARKETING_VERSION\s*=\s*[^;]+;/g,
108
- `MARKETING_VERSION = ${version};`
109
- )
110
- .replace(
111
- /CURRENT_PROJECT_VERSION\s*=\s*[^;]+;/g,
112
- `CURRENT_PROJECT_VERSION = ${iosBuild};`
113
- );
114
-
115
- fs.writeFileSync(projectPbxProjPath, projectContent);
116
-
117
- LoggerHelpers.success(`Updated MARKETING_VERSION to ${version} and CURRENT_PROJECT_VERSION to ${iosBuild}`);
118
-
119
- const infoPlistPath = path.join(currentDir, "ios/Runner/Info.plist");
98
+ async function updateIosVersionAndBuild(
99
+ version: string,
100
+ iosBuild: string,
101
+ dryRun: DryRunManager | null
102
+ ) {
103
+ try {
104
+ const projectPbxProjPath = path.join(currentDir, PROJECT_PATHS.IOS_PROJECT_PBXPROJ);
105
+ const infoPlistPath = path.join(currentDir, PROJECT_PATHS.IOS_INFO_PLIST);
120
106
 
121
- if (!fs.existsSync(infoPlistPath)) {
122
- throw new Error(`Info.plist not found at ${infoPlistPath}`);
123
- }
107
+ if (!fs.existsSync(projectPbxProjPath)) {
108
+ throw new Error(`project.pbxproj not found at ${projectPbxProjPath}`);
109
+ }
124
110
 
125
- // Create backup before modification
126
- createBackup(infoPlistPath);
111
+ if (dryRun) {
112
+ dryRun.logFileOperation("Update project.pbxproj", projectPbxProjPath, `MARKETING_VERSION = ${version}, CURRENT_PROJECT_VERSION = ${iosBuild}`);
113
+ dryRun.logFileOperation("Update Info.plist", infoPlistPath, `CFBundleShortVersionString = ${version}, CFBundleVersion = ${iosBuild}`);
114
+ return;
115
+ }
127
116
 
128
- const infoPlistContent = fs.readFileSync(infoPlistPath, "utf8");
117
+ // Update project.pbxproj
118
+ createBackupWithCleanup(projectPbxProjPath);
119
+
120
+ let projectContent = fs.readFileSync(projectPbxProjPath, 'utf8');
121
+ projectContent = projectContent
122
+ .replace(
123
+ /MARKETING_VERSION\s*=\s*[^;]+;/g,
124
+ `MARKETING_VERSION = ${version};`
125
+ )
126
+ .replace(
127
+ /CURRENT_PROJECT_VERSION\s*=\s*[^;]+;/g,
128
+ `CURRENT_PROJECT_VERSION = ${iosBuild};`
129
+ );
129
130
 
130
- const updatedPlist = infoPlistContent
131
- .replace(
132
- /<key>CFBundleShortVersionString<\/key>\s*<string>\$\{MARKETING_VERSION\}<\/string>/,
133
- `<key>CFBundleShortVersionString</key><string>${version}</string>`
134
- )
135
- .replace(
136
- /<key>CFBundleVersion<\/key>\s*<string>\$\{CURRENT_PROJECT_VERSION\}<\/string>/,
137
- `<key>CFBundleVersion</key><string>${iosBuild}</string>`
138
- );
131
+ fs.writeFileSync(projectPbxProjPath, projectContent);
132
+ LoggerHelpers.success(`Updated MARKETING_VERSION to ${version} and CURRENT_PROJECT_VERSION to ${iosBuild}`);
139
133
 
140
- fs.writeFileSync(infoPlistPath, updatedPlist);
141
- LoggerHelpers.success("Updated Info.plist with the new version and iOS build.");
134
+ // Update Info.plist
135
+ if (!fs.existsSync(infoPlistPath)) {
136
+ throw new Error(`Info.plist not found at ${infoPlistPath}`);
137
+ }
142
138
 
143
- } catch (error) {
144
- LoggerHelpers.error(
145
- `Error while updating iOS version and build: ${
146
- error instanceof Error ? error.message : String(error)
147
- }`
139
+ createBackupWithCleanup(infoPlistPath);
140
+
141
+ const infoPlistContent = fs.readFileSync(infoPlistPath, "utf8");
142
+ const updatedPlist = infoPlistContent
143
+ .replace(
144
+ /<key>CFBundleShortVersionString<\/key>\s*<string>[^<]+<\/string>/,
145
+ `<key>CFBundleShortVersionString</key>\n\t<string>${version}</string>`
146
+ )
147
+ .replace(
148
+ /<key>CFBundleVersion<\/key>\s*<string>[^<]+<\/string>/,
149
+ `<key>CFBundleVersion</key>\n\t<string>${iosBuild}</string>`
148
150
  );
149
- throw error;
150
- }
151
+
152
+ fs.writeFileSync(infoPlistPath, updatedPlist);
153
+ LoggerHelpers.success("Updated Info.plist with the new version and iOS build.");
154
+ } catch (error) {
155
+ LoggerHelpers.error(
156
+ `Error while updating iOS version and build: ${
157
+ error instanceof Error ? error.message : String(error)
158
+ }`
159
+ );
160
+ throw error;
151
161
  }
162
+ }
package/src/constants.ts CHANGED
@@ -48,7 +48,7 @@ export const BACKUP_CONFIG = {
48
48
 
49
49
  // Module generation
50
50
  export const MODULE_STRUCTURE = {
51
- DIRECTORIES: ["bloc", "event", "state", "screen", "import", "factory"],
51
+ DIRECTORIES: ["bloc", "event", "state", "screen", "import", "factory", "repo"],
52
52
  NAME_PATTERN: /^[a-z0-9_]+$/,
53
53
  } as const;
54
54
 
@@ -135,6 +135,12 @@ export const ERROR_MESSAGES = {
135
135
  MODULE_NAME_INVALID: "Module name must contain only lowercase letters, numbers, and underscores.",
136
136
  } as const;
137
137
 
138
+ // MCP server configuration
139
+ export const MCP_CONFIG = {
140
+ SERVER_NAME: "optikit",
141
+ CLAUDE_SETTINGS_FILE: ".claude.json",
142
+ } as const;
143
+
138
144
  // Info messages
139
145
  export const INFO_MESSAGES = {
140
146
  RUN_FROM_PROJECT_ROOT: "Please run this command from the root of a Flutter project.",