optikit 1.1.1 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/CHANGELOG.md +39 -2
  2. package/CLAUDE.md +239 -0
  3. package/CODE_QUALITY.md +398 -0
  4. package/ENHANCEMENTS.md +310 -0
  5. package/FEATURE_ENHANCEMENTS.md +435 -0
  6. package/README.md +46 -13
  7. package/SAFETY_FEATURES.md +396 -0
  8. package/USAGE.md +264 -41
  9. package/VERSION_MANAGEMENT.md +438 -0
  10. package/dist/cli.js +127 -8
  11. package/dist/commands/build/releases.js +57 -0
  12. package/dist/commands/buildReleases.js +48 -74
  13. package/dist/commands/clean/flutter.js +51 -0
  14. package/dist/commands/clean/ios.js +109 -0
  15. package/dist/commands/cleanProject.js +34 -4
  16. package/dist/commands/cleanProjectIos.js +17 -7
  17. package/dist/commands/config/init.js +54 -0
  18. package/dist/commands/config/rollback.js +161 -0
  19. package/dist/commands/generateModule.js +39 -11
  20. package/dist/commands/init.js +54 -0
  21. package/dist/commands/openProject.js +17 -0
  22. package/dist/commands/project/devices.js +188 -0
  23. package/dist/commands/project/generate.js +143 -0
  24. package/dist/commands/project/open.js +163 -0
  25. package/dist/commands/project/setup.js +46 -0
  26. package/dist/commands/rollback.js +161 -0
  27. package/dist/commands/setupVSCode.js +27 -21
  28. package/dist/commands/updateVersions.js +13 -1
  29. package/dist/commands/version/bump.js +161 -0
  30. package/dist/commands/version/update.js +91 -0
  31. package/dist/commands/version.js +161 -0
  32. package/dist/constants.js +131 -0
  33. package/dist/utils/backupHelpers.js +88 -0
  34. package/dist/utils/buildHelpers.js +55 -0
  35. package/dist/utils/commandHelpers.js +51 -0
  36. package/dist/utils/configHelpers.js +80 -0
  37. package/dist/utils/dryRunHelpers.js +103 -0
  38. package/dist/utils/fileHelpers.js +2 -1
  39. package/dist/utils/helpers/build.js +55 -0
  40. package/dist/utils/helpers/dryRun.js +103 -0
  41. package/dist/utils/helpers/file.js +24 -0
  42. package/dist/utils/helpers/string.js +3 -0
  43. package/dist/utils/helpers/version.js +80 -0
  44. package/dist/utils/services/backup.js +88 -0
  45. package/dist/utils/services/command.js +51 -0
  46. package/dist/utils/services/config.js +80 -0
  47. package/dist/utils/services/exec.js +132 -0
  48. package/dist/utils/services/logger.js +15 -0
  49. package/dist/utils/validationHelpers.js +101 -0
  50. package/dist/utils/validators/validation.js +101 -0
  51. package/dist/utils/versionHelpers.js +80 -0
  52. package/package.json +1 -1
  53. package/src/cli.ts +191 -8
  54. package/src/commands/build/releases.ts +79 -0
  55. package/src/commands/clean/flutter.ts +58 -0
  56. package/src/commands/{cleanProjectIos.ts → clean/ios.ts} +19 -10
  57. package/src/commands/config/init.ts +63 -0
  58. package/src/commands/config/rollback.ts +200 -0
  59. package/src/commands/project/devices.ts +246 -0
  60. package/src/commands/{generateModule.ts → project/generate.ts} +47 -17
  61. package/src/commands/project/open.ts +193 -0
  62. package/src/commands/project/setup.ts +50 -0
  63. package/src/commands/version/bump.ts +202 -0
  64. package/src/commands/{updateVersions.ts → version/update.ts} +22 -6
  65. package/src/constants.ts +144 -0
  66. package/src/utils/helpers/build.ts +80 -0
  67. package/src/utils/helpers/dryRun.ts +124 -0
  68. package/src/utils/{fileHelpers.ts → helpers/file.ts} +3 -2
  69. package/src/utils/helpers/version.ts +109 -0
  70. package/src/utils/services/backup.ts +109 -0
  71. package/src/utils/services/command.ts +76 -0
  72. package/src/utils/services/config.ts +106 -0
  73. package/src/utils/{execHelpers.ts → services/exec.ts} +1 -1
  74. package/src/utils/validators/validation.ts +122 -0
  75. package/src/commands/buildReleases.ts +0 -102
  76. package/src/commands/cleanProject.ts +0 -25
  77. package/src/commands/openProject.ts +0 -51
  78. package/src/commands/setupVSCode.ts +0 -44
  79. /package/src/utils/{stringHelpers.ts → helpers/string.ts} +0 -0
  80. /package/src/utils/{loggerHelpers.ts → services/logger.ts} +0 -0
@@ -0,0 +1,193 @@
1
+ import { LoggerHelpers } from "../../utils/services/logger.js";
2
+ import { execCommand } from "../../utils/services/exec.js";
3
+ import { platform } from "os";
4
+ import { validateFlutterProject, validateIosProject, validateAndroidProject } from "../../utils/validators/validation.js";
5
+ import * as fs from "fs";
6
+ import * as path from "path";
7
+
8
+ export { openIos, openAndroid, openIpaOutput, openBundleOutput, openApkOutput };
9
+
10
+ async function openIos() {
11
+ LoggerHelpers.info("Opening the iOS project in Xcode...");
12
+
13
+ // Pre-flight validation
14
+ if (!validateFlutterProject()) {
15
+ process.exit(1);
16
+ }
17
+
18
+ if (!validateIosProject()) {
19
+ process.exit(1);
20
+ }
21
+
22
+ const command = "open ios/Runner.xcworkspace";
23
+
24
+ try {
25
+ await execCommand(command);
26
+ LoggerHelpers.success("Xcode opened successfully.");
27
+ } catch (error) {
28
+ if (error instanceof Error) {
29
+ LoggerHelpers.error(`Error while opening Xcode: ${error.message}`);
30
+ } else {
31
+ LoggerHelpers.error(`Error while opening Xcode: ${error}`);
32
+ }
33
+ process.exit(1);
34
+ }
35
+ }
36
+
37
+ async function openAndroid() {
38
+ LoggerHelpers.info("Opening the Android project in Android Studio...");
39
+
40
+ // Pre-flight validation
41
+ if (!validateFlutterProject()) {
42
+ process.exit(1);
43
+ }
44
+
45
+ if (!validateAndroidProject()) {
46
+ process.exit(1);
47
+ }
48
+
49
+ const osPlatform = platform();
50
+ let command;
51
+
52
+
53
+ if (osPlatform === "win32") {
54
+ command = "start android";
55
+ } else if (osPlatform === "darwin") {
56
+ command = "open -a 'Android Studio' android";
57
+ } else {
58
+ command = "xdg-open android";
59
+ }
60
+
61
+ try {
62
+ await execCommand(command);
63
+ LoggerHelpers.success("Android Studio opened successfully.");
64
+ } catch (error) {
65
+ if (error instanceof Error) {
66
+ LoggerHelpers.error(
67
+ `Error while opening Android Studio: ${error.message}`
68
+ );
69
+ } else {
70
+ LoggerHelpers.error(`Error while opening Android Studio: ${error}`);
71
+ }
72
+ process.exit(1);
73
+ }
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
+ }
@@ -0,0 +1,50 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { LoggerHelpers } from "../../utils/services/logger.js";
4
+
5
+ export {
6
+ createVscodeSettings,
7
+ };
8
+
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(){
14
+ try {
15
+ const vscodeDir = path.join(process.cwd(), ".vscode");
16
+ const settingsPath = path.join(vscodeDir, "settings.json");
17
+
18
+ // Create the .vscode folder using Node.js fs (cross-platform)
19
+ if (!fs.existsSync(vscodeDir)) {
20
+ fs.mkdirSync(vscodeDir, { recursive: true });
21
+ LoggerHelpers.success("Created .vscode directory.");
22
+ } else {
23
+ LoggerHelpers.info(".vscode directory already exists.");
24
+ }
25
+
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");
41
+ LoggerHelpers.success("Created .vscode/settings.json with Flutter configuration.");
42
+ } 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);
49
+ }
50
+ }
@@ -0,0 +1,202 @@
1
+ import { validateFlutterProject } from "../../utils/validators/validation.js";
2
+ import { LoggerHelpers } from "../../utils/services/logger.js";
3
+ import {
4
+ getCurrentVersion,
5
+ incrementVersion,
6
+ formatVersion,
7
+ getNextBuildNumber
8
+ } from "../../utils/helpers/version.js";
9
+ import { updateFlutterVersion } from "./update.js";
10
+ import chalk from "chalk";
11
+
12
+ export {
13
+ bumpVersion,
14
+ bumpIosBuildOnly,
15
+ bumpAndroidBuildOnly,
16
+ showCurrentVersion
17
+ };
18
+
19
+ /**
20
+ * Bumps version with semantic versioning (major, minor, patch)
21
+ * Android build number increments with version
22
+ * iOS build number resets to 1 on version change
23
+ *
24
+ * @param type - Type of version bump (major, minor, patch)
25
+ */
26
+ async function bumpVersion(
27
+ type: 'major' | 'minor' | 'patch'
28
+ ): Promise<void> {
29
+ // Pre-flight validation
30
+ if (!validateFlutterProject()) {
31
+ process.exit(1);
32
+ }
33
+
34
+ try {
35
+ const current = getCurrentVersion();
36
+ const newVersion = incrementVersion(current, type);
37
+
38
+ LoggerHelpers.info(`Current version: ${formatVersion(current)}`);
39
+ LoggerHelpers.info(`Bumping ${type} version...`);
40
+
41
+ console.log(chalk.cyan("\nVersion changes:"));
42
+ console.log(chalk.gray(" Old:"), chalk.white(formatVersion(current)));
43
+ console.log(chalk.gray(" New:"), chalk.green.bold(formatVersion(newVersion)));
44
+
45
+ console.log(chalk.cyan("\nBuild number strategy:"));
46
+ console.log(chalk.gray(" Android:"), chalk.white(`${current.buildNumber} → ${newVersion.buildNumber} (incremented)`));
47
+ console.log(chalk.gray(" iOS:"), chalk.white(`${current.buildNumber} → 1 (reset for new version)`));
48
+ console.log();
49
+
50
+ // Update with new version
51
+ // Android uses the incremented build number from version
52
+ // iOS gets reset to 1 for new version releases
53
+ await updateFlutterVersion(
54
+ `${newVersion.major}.${newVersion.minor}.${newVersion.patch}`,
55
+ newVersion.buildNumber.toString(),
56
+ "1" // iOS always starts at 1 for new versions
57
+ );
58
+
59
+ LoggerHelpers.success(`Version bumped to ${formatVersion(newVersion)}`);
60
+ console.log(chalk.gray("\nAndroid build:"), chalk.white(newVersion.buildNumber));
61
+ console.log(chalk.gray("iOS build:"), chalk.white("1"));
62
+
63
+ } catch (error) {
64
+ if (error instanceof Error) {
65
+ LoggerHelpers.error(`Error bumping version: ${error.message}`);
66
+ } else {
67
+ LoggerHelpers.error(`Error bumping version: ${error}`);
68
+ }
69
+ process.exit(1);
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Increments ONLY iOS build number (for TestFlight builds)
75
+ * Keeps version and Android build number unchanged
76
+ * Perfect for uploading new iOS builds without changing app version
77
+ *
78
+ * Example: 1.0.2+45 (iOS: 45) → 1.0.2+45 (iOS: 46)
79
+ */
80
+ async function bumpIosBuildOnly(): Promise<void> {
81
+ // Pre-flight validation
82
+ if (!validateFlutterProject()) {
83
+ process.exit(1);
84
+ }
85
+
86
+ try {
87
+ const current = getCurrentVersion();
88
+ const currentVersionString = `${current.major}.${current.minor}.${current.patch}`;
89
+
90
+ // iOS build number increments from current Android build number
91
+ const nextIosBuild = current.buildNumber + 1;
92
+
93
+ LoggerHelpers.info(`Current version: ${formatVersion(current)}`);
94
+ LoggerHelpers.info("Incrementing iOS build number only (for TestFlight)...");
95
+
96
+ console.log(chalk.cyan("\nBuild number changes:"));
97
+ console.log(chalk.gray(" Version:"), chalk.white(`${currentVersionString} (unchanged)`));
98
+ console.log(chalk.gray(" Android:"), chalk.white(`${current.buildNumber} (unchanged)`));
99
+ console.log(chalk.gray(" iOS:"), chalk.white(`${current.buildNumber} → ${nextIosBuild}`), chalk.green("(incremented)"));
100
+ console.log();
101
+
102
+ // Update only iOS build number
103
+ await updateFlutterVersion(
104
+ currentVersionString,
105
+ "", // Empty string means don't update Android
106
+ nextIosBuild.toString()
107
+ );
108
+
109
+ LoggerHelpers.success(`iOS build number incremented to ${nextIosBuild}`);
110
+ console.log(chalk.gray("\nResult:"), chalk.white(`${currentVersionString}+${current.buildNumber} (iOS: ${nextIosBuild})`));
111
+ console.log(chalk.gray("Use this for:"), chalk.white("TestFlight uploads without version changes"));
112
+
113
+ } catch (error) {
114
+ if (error instanceof Error) {
115
+ LoggerHelpers.error(`Error incrementing iOS build: ${error.message}`);
116
+ } else {
117
+ LoggerHelpers.error(`Error incrementing iOS build: ${error}`);
118
+ }
119
+ process.exit(1);
120
+ }
121
+ }
122
+
123
+ /**
124
+ * Increments ONLY Android build number
125
+ * Keeps version and iOS build number unchanged
126
+ */
127
+ async function bumpAndroidBuildOnly(): Promise<void> {
128
+ // Pre-flight validation
129
+ if (!validateFlutterProject()) {
130
+ process.exit(1);
131
+ }
132
+
133
+ try {
134
+ const current = getCurrentVersion();
135
+ const currentVersionString = `${current.major}.${current.minor}.${current.patch}`;
136
+ const nextAndroidBuild = current.buildNumber + 1;
137
+
138
+ LoggerHelpers.info(`Current version: ${formatVersion(current)}`);
139
+ LoggerHelpers.info("Incrementing Android build number only...");
140
+
141
+ console.log(chalk.cyan("\nBuild number changes:"));
142
+ console.log(chalk.gray(" Version:"), chalk.white(`${currentVersionString} (unchanged)`));
143
+ console.log(chalk.gray(" Android:"), chalk.white(`${current.buildNumber} → ${nextAndroidBuild}`), chalk.green("(incremented)"));
144
+ console.log(chalk.gray(" iOS:"), chalk.white("(unchanged)"));
145
+ console.log();
146
+
147
+ // Update only Android build number
148
+ await updateFlutterVersion(
149
+ currentVersionString,
150
+ nextAndroidBuild.toString(),
151
+ "" // Empty string means don't update iOS
152
+ );
153
+
154
+ LoggerHelpers.success(`Android build number incremented to ${nextAndroidBuild}`);
155
+
156
+ } catch (error) {
157
+ if (error instanceof Error) {
158
+ LoggerHelpers.error(`Error incrementing Android build: ${error.message}`);
159
+ } else {
160
+ LoggerHelpers.error(`Error incrementing Android build: ${error}`);
161
+ }
162
+ process.exit(1);
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Shows current version information
168
+ */
169
+ async function showCurrentVersion(): Promise<void> {
170
+ try {
171
+ const current = getCurrentVersion();
172
+ const versionString = formatVersion(current);
173
+
174
+ console.log(chalk.bold("\n📱 Current Version Information\n"));
175
+ console.log(chalk.cyan("Version:"), chalk.white.bold(versionString));
176
+ console.log(chalk.gray(" Major:"), chalk.white(current.major));
177
+ console.log(chalk.gray(" Minor:"), chalk.white(current.minor));
178
+ console.log(chalk.gray(" Patch:"), chalk.white(current.patch));
179
+ console.log(chalk.gray(" Build:"), chalk.white(current.buildNumber));
180
+ console.log();
181
+
182
+ } catch (error) {
183
+ if (error instanceof Error) {
184
+ LoggerHelpers.error(`Error reading version: ${error.message}`);
185
+ } else {
186
+ LoggerHelpers.error(`Error reading version: ${error}`);
187
+ }
188
+ process.exit(1);
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Helper to get next iOS build number
194
+ * In the future, this could read from Info.plist or project.pbxproj
195
+ * For now, we'll use a simple increment
196
+ */
197
+ async function getNextIosBuildNumber(): Promise<number> {
198
+ // TODO: Read actual iOS build number from Info.plist or project.pbxproj
199
+ // For now, we'll just increment based on timestamp or simple counter
200
+ const current = getCurrentVersion();
201
+ return current.buildNumber + 1;
202
+ }
@@ -1,7 +1,9 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
- import { execInIos } from "../utils/execHelpers.js";
4
- import { LoggerHelpers } from "../utils/loggerHelpers.js";
3
+ import { execInIos } from "../../utils/services/exec.js";
4
+ import { LoggerHelpers } from "../../utils/services/logger.js";
5
+ import { validateFlutterProject } from "../../utils/validators/validation.js";
6
+ import { createBackup } from "../../utils/services/backup.js";
5
7
 
6
8
  const currentDir = process.cwd();
7
9
 
@@ -12,6 +14,11 @@ async function updateFlutterVersion(
12
14
  build: string,
13
15
  iosBuild: string
14
16
  ) {
17
+ // Pre-flight validation
18
+ if (!validateFlutterProject()) {
19
+ process.exit(1);
20
+ }
21
+
15
22
  try {
16
23
  // Always update the version since it is required.
17
24
  LoggerHelpers.info(`Starting update for version ${version}...`);
@@ -43,7 +50,7 @@ async function updateFlutterVersion(
43
50
  error instanceof Error ? error.message : String(error)
44
51
  }`
45
52
  );
46
- throw error;
53
+ process.exit(1);
47
54
  }
48
55
  }
49
56
 
@@ -55,6 +62,9 @@ async function updatePubspecVersionAndBuild(version: string, build: string) {
55
62
  throw new Error(`pubspec.yaml not found at ${pubspecPath}`);
56
63
  }
57
64
 
65
+ // Create backup before modification
66
+ createBackup(pubspecPath);
67
+
58
68
  const pubspecContent = fs.readFileSync(pubspecPath, "utf8");
59
69
 
60
70
  const updatedPubspec = pubspecContent.replace(
@@ -80,13 +90,16 @@ async function updatePubspecVersionAndBuild(version: string, build: string) {
80
90
  async function updateIosVersionAndBuild(version: string, iosBuild: string) {
81
91
  try {
82
92
  const currentDir = process.cwd();
83
-
93
+
84
94
  const projectPbxProjPath = path.join(currentDir, "ios/Runner.xcodeproj/project.pbxproj");
85
-
95
+
86
96
  if (!fs.existsSync(projectPbxProjPath)) {
87
97
  throw new Error(`project.pbxproj not found at ${projectPbxProjPath}`);
88
98
  }
89
-
99
+
100
+ // Create backup before modification
101
+ createBackup(projectPbxProjPath);
102
+
90
103
  let projectContent = fs.readFileSync(projectPbxProjPath, 'utf8');
91
104
 
92
105
  projectContent = projectContent
@@ -109,6 +122,9 @@ async function updateIosVersionAndBuild(version: string, iosBuild: string) {
109
122
  throw new Error(`Info.plist not found at ${infoPlistPath}`);
110
123
  }
111
124
 
125
+ // Create backup before modification
126
+ createBackup(infoPlistPath);
127
+
112
128
  const infoPlistContent = fs.readFileSync(infoPlistPath, "utf8");
113
129
 
114
130
  const updatedPlist = infoPlistContent
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Application-wide constants
3
+ */
4
+
5
+ // Build configurations
6
+ export const BUILD_CONFIGS = {
7
+ APK: {
8
+ outputPath: "build/app/outputs/symbols",
9
+ flags: ["--release", "--obfuscate"],
10
+ },
11
+ BUNDLE: {
12
+ outputPath: "build/app/outputs/symbols",
13
+ flags: ["--release", "--obfuscate"],
14
+ },
15
+ IOS: {
16
+ flags: ["--release"],
17
+ },
18
+ IPA: {
19
+ flags: ["--release"],
20
+ },
21
+ } as const;
22
+
23
+ // Project structure
24
+ export const PROJECT_PATHS = {
25
+ PUBSPEC: "pubspec.yaml",
26
+ PUBSPEC_LOCK: "pubspec.lock",
27
+ IOS_DIR: "ios",
28
+ ANDROID_DIR: "android",
29
+ LIB_DIR: "lib",
30
+ MODULE_DIR: "lib/module",
31
+ IOS_RUNNER_PROJ: "ios/Runner.xcodeproj",
32
+ IOS_RUNNER_WORKSPACE: "ios/Runner.xcworkspace",
33
+ IOS_PROJECT_PBXPROJ: "ios/Runner.xcodeproj/project.pbxproj",
34
+ IOS_INFO_PLIST: "ios/Runner/Info.plist",
35
+ IOS_PODFILE_LOCK: "ios/Podfile.lock",
36
+ ANDROID_BUILD_GRADLE: "android/build.gradle",
37
+ ANDROID_BUILD_GRADLE_KTS: "android/build.gradle.kts",
38
+ FVM_FLUTTER_SDK: ".fvm/flutter_sdk",
39
+ VSCODE_DIR: ".vscode",
40
+ VSCODE_SETTINGS: ".vscode/settings.json",
41
+ } as const;
42
+
43
+ // Backup configuration
44
+ export const BACKUP_CONFIG = {
45
+ DIR_NAME: ".optikit-backup",
46
+ RETENTION_COUNT: 5,
47
+ } as const;
48
+
49
+ // Module generation
50
+ export const MODULE_STRUCTURE = {
51
+ DIRECTORIES: ["bloc", "event", "state", "screen", "import", "factory"],
52
+ NAME_PATTERN: /^[a-z0-9_]+$/,
53
+ } as const;
54
+
55
+ // Flutter commands
56
+ export const FLUTTER_COMMANDS = {
57
+ CLEAN: "flutter clean",
58
+ PUB_GET: "flutter pub get",
59
+ BUILD_APK: "flutter build apk",
60
+ BUILD_BUNDLE: "flutter build appbundle",
61
+ BUILD_IOS: "flutter build ios",
62
+ BUILD_IPA: "flutter build ipa",
63
+ PRECACHE_IOS: "flutter precache --ios",
64
+ VERSION: "flutter --version",
65
+ } as const;
66
+
67
+ // FVM commands
68
+ export const FVM_COMMANDS = {
69
+ CLEAN: "fvm flutter clean",
70
+ PUB_GET: "fvm flutter pub get",
71
+ BUILD_APK: "fvm flutter build apk",
72
+ BUILD_BUNDLE: "fvm flutter build appbundle",
73
+ BUILD_IOS: "fvm flutter build ios",
74
+ BUILD_IPA: "fvm flutter build ipa",
75
+ PRECACHE_IOS: "fvm flutter precache --ios",
76
+ VERSION: "fvm --version",
77
+ } as const;
78
+
79
+ // iOS commands
80
+ export const IOS_COMMANDS = {
81
+ POD_DEINTEGRATE: "pod deintegrate",
82
+ POD_INSTALL: "pod install",
83
+ POD_UPDATE: "pod update",
84
+ POD_REPO_UPDATE: "pod repo update",
85
+ POD_CACHE_CLEAN: "pod cache clean --all",
86
+ } as const;
87
+
88
+ // IDE commands
89
+ export const IDE_COMMANDS = {
90
+ XCODE: "open ios/Runner.xcworkspace",
91
+ ANDROID_STUDIO: {
92
+ DARWIN: "open -a 'Android Studio' android",
93
+ WIN32: "start android",
94
+ LINUX: "xdg-open android",
95
+ },
96
+ } as const;
97
+
98
+ // VSCode settings template
99
+ export const VSCODE_SETTINGS_TEMPLATE = {
100
+ "dart.flutterSdkPath": ".fvm/flutter_sdk",
101
+ "editor.formatOnSave": true,
102
+ "dart.previewFlutterUiGuides": true,
103
+ "files.exclude": {
104
+ "**/.git": true,
105
+ "**/.DS_Store": true,
106
+ "**/node_modules": true,
107
+ "**/build": true,
108
+ },
109
+ } as const;
110
+
111
+ // Retry configuration
112
+ export const RETRY_CONFIG = {
113
+ DEFAULT_ATTEMPTS: 3,
114
+ DEFAULT_DELAY_MS: 10000,
115
+ IOS_TIMEOUT_MS: 600000,
116
+ } as const;
117
+
118
+ // Help URLs
119
+ export const HELP_URLS = {
120
+ FLUTTER_INSTALL: "https://flutter.dev/docs/get-started/install",
121
+ FVM_INSTALL: "https://fvm.app/docs/getting_started/installation",
122
+ } as const;
123
+
124
+ // Error messages
125
+ export const ERROR_MESSAGES = {
126
+ NOT_FLUTTER_PROJECT: "Not a Flutter project: pubspec.yaml not found.",
127
+ NO_FLUTTER_REFERENCE: "Not a Flutter project: pubspec.yaml does not reference Flutter SDK.",
128
+ FVM_NOT_FOUND: "FVM Flutter SDK not found at .fvm/flutter_sdk",
129
+ FLUTTER_NOT_FOUND: "Flutter SDK not found.",
130
+ IOS_PROJECT_NOT_FOUND: "iOS project directory not found.",
131
+ ANDROID_PROJECT_NOT_FOUND: "Android project directory not found.",
132
+ NO_XCODE_PROJECT: "No Xcode project or workspace found in ios/ directory.",
133
+ NO_BUILD_GRADLE: "No build.gradle found in android/ directory.",
134
+ MODULE_NAME_EMPTY: "Module name cannot be empty.",
135
+ MODULE_NAME_INVALID: "Module name must contain only lowercase letters, numbers, and underscores.",
136
+ } as const;
137
+
138
+ // Info messages
139
+ export const INFO_MESSAGES = {
140
+ RUN_FROM_PROJECT_ROOT: "Please run this command from the root of a Flutter project.",
141
+ ADD_IOS_SUPPORT: "Run 'flutter create .' to add iOS support.",
142
+ ADD_ANDROID_SUPPORT: "Run 'flutter create .' to add Android support.",
143
+ INSTALL_FVM_OR_DISABLE: "Run 'fvm install' or use --disable-fvm flag.",
144
+ } as const;