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.
- package/CHANGELOG.md +23 -2
- package/CLAUDE.md +239 -0
- package/CODE_QUALITY.md +398 -0
- package/ENHANCEMENTS.md +310 -0
- package/FEATURE_ENHANCEMENTS.md +435 -0
- package/README.md +46 -13
- package/SAFETY_FEATURES.md +396 -0
- package/USAGE.md +225 -41
- package/VERSION_MANAGEMENT.md +438 -0
- package/dist/cli.js +116 -7
- package/dist/commands/build/releases.js +57 -0
- package/dist/commands/buildReleases.js +48 -74
- package/dist/commands/clean/flutter.js +51 -0
- package/dist/commands/clean/ios.js +109 -0
- package/dist/commands/cleanProject.js +34 -4
- package/dist/commands/cleanProjectIos.js +17 -7
- package/dist/commands/config/init.js +54 -0
- package/dist/commands/config/rollback.js +161 -0
- package/dist/commands/generateModule.js +39 -11
- package/dist/commands/init.js +54 -0
- package/dist/commands/openProject.js +17 -0
- package/dist/commands/project/devices.js +188 -0
- package/dist/commands/project/generate.js +143 -0
- package/dist/commands/project/open.js +63 -0
- package/dist/commands/project/setup.js +46 -0
- package/dist/commands/rollback.js +161 -0
- package/dist/commands/setupVSCode.js +27 -21
- package/dist/commands/updateVersions.js +13 -1
- package/dist/commands/version/bump.js +161 -0
- package/dist/commands/version/update.js +91 -0
- package/dist/commands/version.js +161 -0
- package/dist/constants.js +131 -0
- package/dist/utils/backupHelpers.js +88 -0
- package/dist/utils/buildHelpers.js +55 -0
- package/dist/utils/commandHelpers.js +51 -0
- package/dist/utils/configHelpers.js +80 -0
- package/dist/utils/dryRunHelpers.js +103 -0
- package/dist/utils/fileHelpers.js +2 -1
- package/dist/utils/helpers/build.js +55 -0
- package/dist/utils/helpers/dryRun.js +103 -0
- package/dist/utils/helpers/file.js +24 -0
- package/dist/utils/helpers/string.js +3 -0
- package/dist/utils/helpers/version.js +80 -0
- package/dist/utils/services/backup.js +88 -0
- package/dist/utils/services/command.js +51 -0
- package/dist/utils/services/config.js +80 -0
- package/dist/utils/services/exec.js +132 -0
- package/dist/utils/services/logger.js +15 -0
- package/dist/utils/validationHelpers.js +101 -0
- package/dist/utils/validators/validation.js +101 -0
- package/dist/utils/versionHelpers.js +80 -0
- package/package.json +1 -1
- package/src/cli.ts +165 -7
- package/src/commands/build/releases.ts +79 -0
- package/src/commands/clean/flutter.ts +58 -0
- package/src/commands/{cleanProjectIos.ts → clean/ios.ts} +19 -10
- package/src/commands/config/init.ts +63 -0
- package/src/commands/config/rollback.ts +200 -0
- package/src/commands/project/devices.ts +246 -0
- package/src/commands/{generateModule.ts → project/generate.ts} +47 -17
- package/src/commands/{openProject.ts → project/open.ts} +26 -5
- package/src/commands/project/setup.ts +50 -0
- package/src/commands/version/bump.ts +202 -0
- package/src/commands/{updateVersions.ts → version/update.ts} +22 -6
- package/src/constants.ts +144 -0
- package/src/utils/helpers/build.ts +80 -0
- package/src/utils/helpers/dryRun.ts +124 -0
- package/src/utils/{fileHelpers.ts → helpers/file.ts} +3 -2
- package/src/utils/helpers/version.ts +109 -0
- package/src/utils/services/backup.ts +109 -0
- package/src/utils/services/command.ts +76 -0
- package/src/utils/services/config.ts +106 -0
- package/src/utils/{execHelpers.ts → services/exec.ts} +1 -1
- package/src/utils/validators/validation.ts +122 -0
- package/src/commands/buildReleases.ts +0 -102
- package/src/commands/cleanProject.ts +0 -25
- package/src/commands/setupVSCode.ts +0 -44
- /package/src/utils/{stringHelpers.ts → helpers/string.ts} +0 -0
- /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
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
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
|
+
}
|