optikit 1.1.1 → 1.2.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 (79) hide show
  1. package/CHANGELOG.md +23 -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 +225 -41
  9. package/VERSION_MANAGEMENT.md +438 -0
  10. package/dist/cli.js +116 -7
  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 +63 -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 +165 -7
  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/{openProject.ts → project/open.ts} +26 -5
  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/setupVSCode.ts +0 -44
  78. /package/src/utils/{stringHelpers.ts → helpers/string.ts} +0 -0
  79. /package/src/utils/{loggerHelpers.ts → services/logger.ts} +0 -0
@@ -0,0 +1,161 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { LoggerHelpers } from "../utils/loggerHelpers.js";
4
+ import { restoreBackup } from "../utils/backupHelpers.js";
5
+ import chalk from "chalk";
6
+ export { rollbackFiles };
7
+ /**
8
+ * Lists all available backups and allows restoration
9
+ */
10
+ async function rollbackFiles() {
11
+ try {
12
+ LoggerHelpers.info("Searching for OptiKit backups...");
13
+ const backups = findAllBackups(process.cwd());
14
+ if (backups.length === 0) {
15
+ LoggerHelpers.warning("No backups found in this project.");
16
+ LoggerHelpers.info("Backups are created automatically when files are modified.");
17
+ return;
18
+ }
19
+ console.log(chalk.bold(`\nFound ${backups.length} backup(s):\n`));
20
+ // Group backups by original file
21
+ const backupsByFile = new Map();
22
+ for (const backup of backups) {
23
+ const originalFile = getOriginalFilePath(backup.backupPath);
24
+ if (!backupsByFile.has(originalFile)) {
25
+ backupsByFile.set(originalFile, []);
26
+ }
27
+ backupsByFile.get(originalFile).push(backup);
28
+ }
29
+ // Display backups grouped by file
30
+ let index = 1;
31
+ const backupList = [];
32
+ for (const [originalFile, fileBackups] of backupsByFile) {
33
+ console.log(chalk.cyan.bold(`\n${originalFile}`));
34
+ // Sort by timestamp (newest first)
35
+ fileBackups.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
36
+ for (const backup of fileBackups) {
37
+ const timeAgo = getTimeAgo(backup.timestamp);
38
+ const sizeKB = (backup.size / 1024).toFixed(2);
39
+ console.log(chalk.gray(` [${index}]`), chalk.white(backup.timestamp.toLocaleString()), chalk.gray(`(${timeAgo}, ${sizeKB} KB)`));
40
+ backupList.push({
41
+ index,
42
+ originalPath: originalFile,
43
+ backupPath: backup.backupPath,
44
+ timestamp: backup.timestamp,
45
+ });
46
+ index++;
47
+ }
48
+ }
49
+ console.log(chalk.yellow("\n" + "=".repeat(60)));
50
+ console.log(chalk.gray("To restore a backup, run:"));
51
+ console.log(chalk.white(" optikit rollback --restore <number>"));
52
+ console.log(chalk.gray("\nExample:"));
53
+ console.log(chalk.white(" optikit rollback --restore 1"));
54
+ console.log(chalk.yellow("=".repeat(60) + "\n"));
55
+ }
56
+ catch (error) {
57
+ if (error instanceof Error) {
58
+ LoggerHelpers.error(`Error listing backups: ${error.message}`);
59
+ }
60
+ else {
61
+ LoggerHelpers.error(`Error listing backups: ${error}`);
62
+ }
63
+ process.exit(1);
64
+ }
65
+ }
66
+ /**
67
+ * Restores a specific backup by index
68
+ */
69
+ export async function rollbackRestore(index) {
70
+ try {
71
+ const backups = findAllBackups(process.cwd());
72
+ if (index < 1 || index > backups.length) {
73
+ LoggerHelpers.error(`Invalid backup index: ${index}`);
74
+ LoggerHelpers.info(`Please choose a number between 1 and ${backups.length}`);
75
+ process.exit(1);
76
+ }
77
+ const backup = backups[index - 1];
78
+ const originalPath = getOriginalFilePath(backup.backupPath);
79
+ LoggerHelpers.info(`Restoring: ${originalPath}`);
80
+ LoggerHelpers.info(`From backup: ${backup.timestamp.toLocaleString()}`);
81
+ const success = restoreBackup(originalPath, backup.backupPath);
82
+ if (success) {
83
+ LoggerHelpers.success("Backup restored successfully!");
84
+ }
85
+ else {
86
+ LoggerHelpers.error("Failed to restore backup.");
87
+ process.exit(1);
88
+ }
89
+ }
90
+ catch (error) {
91
+ if (error instanceof Error) {
92
+ LoggerHelpers.error(`Error restoring backup: ${error.message}`);
93
+ }
94
+ else {
95
+ LoggerHelpers.error(`Error restoring backup: ${error}`);
96
+ }
97
+ process.exit(1);
98
+ }
99
+ }
100
+ /**
101
+ * Recursively finds all backup files in a directory
102
+ */
103
+ function findAllBackups(dir) {
104
+ const backups = [];
105
+ function searchDirectory(currentDir) {
106
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
107
+ for (const entry of entries) {
108
+ const fullPath = path.join(currentDir, entry.name);
109
+ if (entry.isDirectory()) {
110
+ if (entry.name === ".optikit-backup") {
111
+ // Found a backup directory
112
+ const backupFiles = fs.readdirSync(fullPath);
113
+ for (const backupFile of backupFiles) {
114
+ const backupPath = path.join(fullPath, backupFile);
115
+ const stats = fs.statSync(backupPath);
116
+ backups.push({
117
+ backupPath,
118
+ timestamp: stats.mtime,
119
+ size: stats.size,
120
+ });
121
+ }
122
+ }
123
+ else if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
124
+ // Recursively search subdirectories
125
+ searchDirectory(fullPath);
126
+ }
127
+ }
128
+ }
129
+ }
130
+ searchDirectory(dir);
131
+ return backups;
132
+ }
133
+ /**
134
+ * Extracts the original file path from a backup path
135
+ */
136
+ function getOriginalFilePath(backupPath) {
137
+ const backupDir = path.dirname(backupPath);
138
+ const originalDir = path.dirname(backupDir);
139
+ const backupFileName = path.basename(backupPath);
140
+ // Remove timestamp from filename
141
+ // Format: filename_YYYY-MM-DDTHH-MM-SS-mmmZ.ext
142
+ const match = backupFileName.match(/^(.+)_\d{4}-\d{2}-\d{2}T[\d-]+Z(\.\w+)$/);
143
+ if (match) {
144
+ const [, baseName, extension] = match;
145
+ return path.join(originalDir, `${baseName}${extension}`);
146
+ }
147
+ return path.join(originalDir, backupFileName);
148
+ }
149
+ /**
150
+ * Gets human-readable time ago string
151
+ */
152
+ function getTimeAgo(date) {
153
+ const seconds = Math.floor((new Date().getTime() - date.getTime()) / 1000);
154
+ if (seconds < 60)
155
+ return `${seconds}s ago`;
156
+ if (seconds < 3600)
157
+ return `${Math.floor(seconds / 60)}m ago`;
158
+ if (seconds < 86400)
159
+ return `${Math.floor(seconds / 3600)}h ago`;
160
+ return `${Math.floor(seconds / 86400)}d ago`;
161
+ }
@@ -1,4 +1,5 @@
1
- import { execCommand } from "../utils/execHelpers.js";
1
+ import fs from "fs";
2
+ import path from "path";
2
3
  import { LoggerHelpers } from "../utils/loggerHelpers.js";
3
4
  export { createVscodeSettings, };
4
5
  /**
@@ -7,26 +8,30 @@ export { createVscodeSettings, };
7
8
  */
8
9
  async function createVscodeSettings() {
9
10
  try {
10
- // Create the .vscode folder (using -p ensures it won't error if it already exists)
11
- await execCommand('mkdir -p .vscode');
12
- LoggerHelpers.success("Created .vscode directory (if not already present).");
13
- // Use a heredoc to write the settings.json file
14
- const command = `
15
- cat << 'EOF' > .vscode/settings.json
16
- {
17
- "dart.flutterSdkPath": ".fvm/flutter_sdk",
18
- "editor.formatOnSave": true,
19
- "dart.previewFlutterUiGuides": true,
20
- "files.exclude": {
21
- "**/.git": true,
22
- "**/.DS_Store": true,
23
- "**/node_modules": true,
24
- "**/build": true
25
- }
26
- }
27
- EOF
28
- `;
29
- await execCommand(command);
11
+ const vscodeDir = path.join(process.cwd(), ".vscode");
12
+ const settingsPath = path.join(vscodeDir, "settings.json");
13
+ // Create the .vscode folder using Node.js fs (cross-platform)
14
+ if (!fs.existsSync(vscodeDir)) {
15
+ fs.mkdirSync(vscodeDir, { recursive: true });
16
+ LoggerHelpers.success("Created .vscode directory.");
17
+ }
18
+ else {
19
+ LoggerHelpers.info(".vscode directory already exists.");
20
+ }
21
+ // Define the settings object
22
+ const settings = {
23
+ "dart.flutterSdkPath": ".fvm/flutter_sdk",
24
+ "editor.formatOnSave": true,
25
+ "dart.previewFlutterUiGuides": true,
26
+ "files.exclude": {
27
+ "**/.git": true,
28
+ "**/.DS_Store": true,
29
+ "**/node_modules": true,
30
+ "**/build": true
31
+ }
32
+ };
33
+ // Write settings.json using Node.js fs
34
+ fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf8");
30
35
  LoggerHelpers.success("Created .vscode/settings.json with Flutter configuration.");
31
36
  }
32
37
  catch (error) {
@@ -36,5 +41,6 @@ EOF
36
41
  else {
37
42
  LoggerHelpers.error(`Error while creating VSCode settings: ${error}`);
38
43
  }
44
+ process.exit(1);
39
45
  }
40
46
  }
@@ -1,9 +1,15 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
3
  import { LoggerHelpers } from "../utils/loggerHelpers.js";
4
+ import { validateFlutterProject } from "../utils/validationHelpers.js";
5
+ import { createBackup } from "../utils/backupHelpers.js";
4
6
  const currentDir = process.cwd();
5
7
  export { updateFlutterVersion };
6
8
  async function updateFlutterVersion(version, build, iosBuild) {
9
+ // Pre-flight validation
10
+ if (!validateFlutterProject()) {
11
+ process.exit(1);
12
+ }
7
13
  try {
8
14
  // Always update the version since it is required.
9
15
  LoggerHelpers.info(`Starting update for version ${version}...`);
@@ -29,7 +35,7 @@ async function updateFlutterVersion(version, build, iosBuild) {
29
35
  }
30
36
  catch (error) {
31
37
  LoggerHelpers.error(`Error while updating Flutter version and build: ${error instanceof Error ? error.message : String(error)}`);
32
- throw error;
38
+ process.exit(1);
33
39
  }
34
40
  }
35
41
  async function updatePubspecVersionAndBuild(version, build) {
@@ -38,6 +44,8 @@ async function updatePubspecVersionAndBuild(version, build) {
38
44
  if (!fs.existsSync(pubspecPath)) {
39
45
  throw new Error(`pubspec.yaml not found at ${pubspecPath}`);
40
46
  }
47
+ // Create backup before modification
48
+ createBackup(pubspecPath);
41
49
  const pubspecContent = fs.readFileSync(pubspecPath, "utf8");
42
50
  const updatedPubspec = pubspecContent.replace(/version: \d+\.\d+\.\d+\+\d+/g, `version: ${version}+${build}`);
43
51
  fs.writeFileSync(pubspecPath, updatedPubspec);
@@ -55,6 +63,8 @@ async function updateIosVersionAndBuild(version, iosBuild) {
55
63
  if (!fs.existsSync(projectPbxProjPath)) {
56
64
  throw new Error(`project.pbxproj not found at ${projectPbxProjPath}`);
57
65
  }
66
+ // Create backup before modification
67
+ createBackup(projectPbxProjPath);
58
68
  let projectContent = fs.readFileSync(projectPbxProjPath, 'utf8');
59
69
  projectContent = projectContent
60
70
  .replace(/MARKETING_VERSION\s*=\s*[^;]+;/g, `MARKETING_VERSION = ${version};`)
@@ -65,6 +75,8 @@ async function updateIosVersionAndBuild(version, iosBuild) {
65
75
  if (!fs.existsSync(infoPlistPath)) {
66
76
  throw new Error(`Info.plist not found at ${infoPlistPath}`);
67
77
  }
78
+ // Create backup before modification
79
+ createBackup(infoPlistPath);
68
80
  const infoPlistContent = fs.readFileSync(infoPlistPath, "utf8");
69
81
  const updatedPlist = infoPlistContent
70
82
  .replace(/<key>CFBundleShortVersionString<\/key>\s*<string>\$\{MARKETING_VERSION\}<\/string>/, `<key>CFBundleShortVersionString</key><string>${version}</string>`)
@@ -0,0 +1,161 @@
1
+ import { validateFlutterProject } from "../../utils/validators/validation.js";
2
+ import { LoggerHelpers } from "../../utils/services/logger.js";
3
+ import { getCurrentVersion, incrementVersion, formatVersion } from "../../utils/helpers/version.js";
4
+ import { updateFlutterVersion } from "./update.js";
5
+ import chalk from "chalk";
6
+ export { bumpVersion, bumpIosBuildOnly, bumpAndroidBuildOnly, showCurrentVersion };
7
+ /**
8
+ * Bumps version with semantic versioning (major, minor, patch)
9
+ * Android build number increments with version
10
+ * iOS build number resets to 1 on version change
11
+ *
12
+ * @param type - Type of version bump (major, minor, patch)
13
+ */
14
+ async function bumpVersion(type) {
15
+ // Pre-flight validation
16
+ if (!validateFlutterProject()) {
17
+ process.exit(1);
18
+ }
19
+ try {
20
+ const current = getCurrentVersion();
21
+ const newVersion = incrementVersion(current, type);
22
+ LoggerHelpers.info(`Current version: ${formatVersion(current)}`);
23
+ LoggerHelpers.info(`Bumping ${type} version...`);
24
+ console.log(chalk.cyan("\nVersion changes:"));
25
+ console.log(chalk.gray(" Old:"), chalk.white(formatVersion(current)));
26
+ console.log(chalk.gray(" New:"), chalk.green.bold(formatVersion(newVersion)));
27
+ console.log(chalk.cyan("\nBuild number strategy:"));
28
+ console.log(chalk.gray(" Android:"), chalk.white(`${current.buildNumber} → ${newVersion.buildNumber} (incremented)`));
29
+ console.log(chalk.gray(" iOS:"), chalk.white(`${current.buildNumber} → 1 (reset for new version)`));
30
+ console.log();
31
+ // Update with new version
32
+ // Android uses the incremented build number from version
33
+ // iOS gets reset to 1 for new version releases
34
+ await updateFlutterVersion(`${newVersion.major}.${newVersion.minor}.${newVersion.patch}`, newVersion.buildNumber.toString(), "1" // iOS always starts at 1 for new versions
35
+ );
36
+ LoggerHelpers.success(`Version bumped to ${formatVersion(newVersion)}`);
37
+ console.log(chalk.gray("\nAndroid build:"), chalk.white(newVersion.buildNumber));
38
+ console.log(chalk.gray("iOS build:"), chalk.white("1"));
39
+ }
40
+ catch (error) {
41
+ if (error instanceof Error) {
42
+ LoggerHelpers.error(`Error bumping version: ${error.message}`);
43
+ }
44
+ else {
45
+ LoggerHelpers.error(`Error bumping version: ${error}`);
46
+ }
47
+ process.exit(1);
48
+ }
49
+ }
50
+ /**
51
+ * Increments ONLY iOS build number (for TestFlight builds)
52
+ * Keeps version and Android build number unchanged
53
+ * Perfect for uploading new iOS builds without changing app version
54
+ *
55
+ * Example: 1.0.2+45 (iOS: 45) → 1.0.2+45 (iOS: 46)
56
+ */
57
+ async function bumpIosBuildOnly() {
58
+ // Pre-flight validation
59
+ if (!validateFlutterProject()) {
60
+ process.exit(1);
61
+ }
62
+ try {
63
+ const current = getCurrentVersion();
64
+ const currentVersionString = `${current.major}.${current.minor}.${current.patch}`;
65
+ // iOS build number increments from current Android build number
66
+ const nextIosBuild = current.buildNumber + 1;
67
+ LoggerHelpers.info(`Current version: ${formatVersion(current)}`);
68
+ LoggerHelpers.info("Incrementing iOS build number only (for TestFlight)...");
69
+ console.log(chalk.cyan("\nBuild number changes:"));
70
+ console.log(chalk.gray(" Version:"), chalk.white(`${currentVersionString} (unchanged)`));
71
+ console.log(chalk.gray(" Android:"), chalk.white(`${current.buildNumber} (unchanged)`));
72
+ console.log(chalk.gray(" iOS:"), chalk.white(`${current.buildNumber} → ${nextIosBuild}`), chalk.green("(incremented)"));
73
+ console.log();
74
+ // Update only iOS build number
75
+ await updateFlutterVersion(currentVersionString, "", // Empty string means don't update Android
76
+ nextIosBuild.toString());
77
+ LoggerHelpers.success(`iOS build number incremented to ${nextIosBuild}`);
78
+ console.log(chalk.gray("\nResult:"), chalk.white(`${currentVersionString}+${current.buildNumber} (iOS: ${nextIosBuild})`));
79
+ console.log(chalk.gray("Use this for:"), chalk.white("TestFlight uploads without version changes"));
80
+ }
81
+ catch (error) {
82
+ if (error instanceof Error) {
83
+ LoggerHelpers.error(`Error incrementing iOS build: ${error.message}`);
84
+ }
85
+ else {
86
+ LoggerHelpers.error(`Error incrementing iOS build: ${error}`);
87
+ }
88
+ process.exit(1);
89
+ }
90
+ }
91
+ /**
92
+ * Increments ONLY Android build number
93
+ * Keeps version and iOS build number unchanged
94
+ */
95
+ async function bumpAndroidBuildOnly() {
96
+ // Pre-flight validation
97
+ if (!validateFlutterProject()) {
98
+ process.exit(1);
99
+ }
100
+ try {
101
+ const current = getCurrentVersion();
102
+ const currentVersionString = `${current.major}.${current.minor}.${current.patch}`;
103
+ const nextAndroidBuild = current.buildNumber + 1;
104
+ LoggerHelpers.info(`Current version: ${formatVersion(current)}`);
105
+ LoggerHelpers.info("Incrementing Android build number only...");
106
+ console.log(chalk.cyan("\nBuild number changes:"));
107
+ console.log(chalk.gray(" Version:"), chalk.white(`${currentVersionString} (unchanged)`));
108
+ console.log(chalk.gray(" Android:"), chalk.white(`${current.buildNumber} → ${nextAndroidBuild}`), chalk.green("(incremented)"));
109
+ console.log(chalk.gray(" iOS:"), chalk.white("(unchanged)"));
110
+ console.log();
111
+ // Update only Android build number
112
+ await updateFlutterVersion(currentVersionString, nextAndroidBuild.toString(), "" // Empty string means don't update iOS
113
+ );
114
+ LoggerHelpers.success(`Android build number incremented to ${nextAndroidBuild}`);
115
+ }
116
+ catch (error) {
117
+ if (error instanceof Error) {
118
+ LoggerHelpers.error(`Error incrementing Android build: ${error.message}`);
119
+ }
120
+ else {
121
+ LoggerHelpers.error(`Error incrementing Android build: ${error}`);
122
+ }
123
+ process.exit(1);
124
+ }
125
+ }
126
+ /**
127
+ * Shows current version information
128
+ */
129
+ async function showCurrentVersion() {
130
+ try {
131
+ const current = getCurrentVersion();
132
+ const versionString = formatVersion(current);
133
+ console.log(chalk.bold("\n📱 Current Version Information\n"));
134
+ console.log(chalk.cyan("Version:"), chalk.white.bold(versionString));
135
+ console.log(chalk.gray(" Major:"), chalk.white(current.major));
136
+ console.log(chalk.gray(" Minor:"), chalk.white(current.minor));
137
+ console.log(chalk.gray(" Patch:"), chalk.white(current.patch));
138
+ console.log(chalk.gray(" Build:"), chalk.white(current.buildNumber));
139
+ console.log();
140
+ }
141
+ catch (error) {
142
+ if (error instanceof Error) {
143
+ LoggerHelpers.error(`Error reading version: ${error.message}`);
144
+ }
145
+ else {
146
+ LoggerHelpers.error(`Error reading version: ${error}`);
147
+ }
148
+ process.exit(1);
149
+ }
150
+ }
151
+ /**
152
+ * Helper to get next iOS build number
153
+ * In the future, this could read from Info.plist or project.pbxproj
154
+ * For now, we'll use a simple increment
155
+ */
156
+ async function getNextIosBuildNumber() {
157
+ // TODO: Read actual iOS build number from Info.plist or project.pbxproj
158
+ // For now, we'll just increment based on timestamp or simple counter
159
+ const current = getCurrentVersion();
160
+ return current.buildNumber + 1;
161
+ }
@@ -0,0 +1,91 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { LoggerHelpers } from "../../utils/services/logger.js";
4
+ import { validateFlutterProject } from "../../utils/validators/validation.js";
5
+ import { createBackup } from "../../utils/services/backup.js";
6
+ const currentDir = process.cwd();
7
+ export { updateFlutterVersion };
8
+ async function updateFlutterVersion(version, build, iosBuild) {
9
+ // Pre-flight validation
10
+ if (!validateFlutterProject()) {
11
+ process.exit(1);
12
+ }
13
+ try {
14
+ // Always update the version since it is required.
15
+ LoggerHelpers.info(`Starting update for version ${version}...`);
16
+ // Update pubspec.yaml only if an Android build number is provided.
17
+ if (build.trim().length > 0) {
18
+ LoggerHelpers.info("Updating build number in pubspec.yaml...");
19
+ await updatePubspecVersionAndBuild(version, build);
20
+ }
21
+ else {
22
+ LoggerHelpers.info("Android build number not provided. Skipping pubspec.yaml update.");
23
+ }
24
+ // Update iOS settings only if an iOS build number is provided.
25
+ if (iosBuild.trim().length > 0) {
26
+ LoggerHelpers.info("Updating iOS version and build number...");
27
+ await updateIosVersionAndBuild(version, iosBuild);
28
+ }
29
+ else {
30
+ LoggerHelpers.info("iOS build number not provided. Skipping iOS update.");
31
+ }
32
+ LoggerHelpers.success(`Update complete. Version set to ${version}` +
33
+ (build.trim().length > 0 ? `, Android build set to ${build}` : "") +
34
+ (iosBuild.trim().length > 0 ? `, and iOS build set to ${iosBuild}` : ""));
35
+ }
36
+ catch (error) {
37
+ LoggerHelpers.error(`Error while updating Flutter version and build: ${error instanceof Error ? error.message : String(error)}`);
38
+ process.exit(1);
39
+ }
40
+ }
41
+ async function updatePubspecVersionAndBuild(version, build) {
42
+ try {
43
+ const pubspecPath = path.join(currentDir, "pubspec.yaml");
44
+ if (!fs.existsSync(pubspecPath)) {
45
+ throw new Error(`pubspec.yaml not found at ${pubspecPath}`);
46
+ }
47
+ // Create backup before modification
48
+ createBackup(pubspecPath);
49
+ const pubspecContent = fs.readFileSync(pubspecPath, "utf8");
50
+ const updatedPubspec = pubspecContent.replace(/version: \d+\.\d+\.\d+\+\d+/g, `version: ${version}+${build}`);
51
+ fs.writeFileSync(pubspecPath, updatedPubspec);
52
+ LoggerHelpers.success(`Updated pubspec.yaml with version ${version} and build ${build}`);
53
+ }
54
+ catch (error) {
55
+ LoggerHelpers.error(`Error while updating pubspec.yaml version and build: ${error instanceof Error ? error.message : String(error)}`);
56
+ throw error;
57
+ }
58
+ }
59
+ async function updateIosVersionAndBuild(version, iosBuild) {
60
+ try {
61
+ const currentDir = process.cwd();
62
+ const projectPbxProjPath = path.join(currentDir, "ios/Runner.xcodeproj/project.pbxproj");
63
+ if (!fs.existsSync(projectPbxProjPath)) {
64
+ throw new Error(`project.pbxproj not found at ${projectPbxProjPath}`);
65
+ }
66
+ // Create backup before modification
67
+ createBackup(projectPbxProjPath);
68
+ let projectContent = fs.readFileSync(projectPbxProjPath, 'utf8');
69
+ projectContent = projectContent
70
+ .replace(/MARKETING_VERSION\s*=\s*[^;]+;/g, `MARKETING_VERSION = ${version};`)
71
+ .replace(/CURRENT_PROJECT_VERSION\s*=\s*[^;]+;/g, `CURRENT_PROJECT_VERSION = ${iosBuild};`);
72
+ fs.writeFileSync(projectPbxProjPath, projectContent);
73
+ LoggerHelpers.success(`Updated MARKETING_VERSION to ${version} and CURRENT_PROJECT_VERSION to ${iosBuild}`);
74
+ const infoPlistPath = path.join(currentDir, "ios/Runner/Info.plist");
75
+ if (!fs.existsSync(infoPlistPath)) {
76
+ throw new Error(`Info.plist not found at ${infoPlistPath}`);
77
+ }
78
+ // Create backup before modification
79
+ createBackup(infoPlistPath);
80
+ const infoPlistContent = fs.readFileSync(infoPlistPath, "utf8");
81
+ const updatedPlist = infoPlistContent
82
+ .replace(/<key>CFBundleShortVersionString<\/key>\s*<string>\$\{MARKETING_VERSION\}<\/string>/, `<key>CFBundleShortVersionString</key><string>${version}</string>`)
83
+ .replace(/<key>CFBundleVersion<\/key>\s*<string>\$\{CURRENT_PROJECT_VERSION\}<\/string>/, `<key>CFBundleVersion</key><string>${iosBuild}</string>`);
84
+ fs.writeFileSync(infoPlistPath, updatedPlist);
85
+ LoggerHelpers.success("Updated Info.plist with the new version and iOS build.");
86
+ }
87
+ catch (error) {
88
+ LoggerHelpers.error(`Error while updating iOS version and build: ${error instanceof Error ? error.message : String(error)}`);
89
+ throw error;
90
+ }
91
+ }