optikit 1.2.5 → 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.
- package/.claude/commands/build.md +57 -0
- package/.claude/commands/clean.md +45 -0
- package/.claude/commands/generate.md +64 -0
- package/.claude/commands/rollback.md +67 -0
- package/.claude/commands/run.md +63 -0
- package/.claude/commands/setup.md +69 -0
- package/.claude/commands/sync-docs.md +148 -0
- package/.claude/commands/version.md +63 -0
- package/.claude/settings.local.json +28 -0
- package/.claude-plugin/marketplace.json +36 -0
- package/.claude-plugin/plugin.json +25 -0
- package/.history/README_20260325211923.md +268 -0
- package/.history/README_20260325212116.md +268 -0
- package/.history/README_20260325212234.md +266 -0
- package/.history/README_20260325221838.md +321 -0
- package/.history/README_20260325221920.md +327 -0
- package/.history/README_20260325222245.md +328 -0
- package/.history/README_20260325222247.md +328 -0
- package/.mcp.json +8 -0
- package/CHANGELOG.md +67 -98
- package/CLAUDE.md +57 -206
- package/OPTIKIT_AGENT.md +398 -0
- package/README.md +293 -60
- package/dist/cli.js +75 -244
- package/dist/commands/build/commands.js +146 -0
- package/dist/commands/build/testflight.js +14 -0
- package/dist/commands/clean/commands.js +41 -0
- package/dist/commands/clean/flutter.js +8 -14
- package/dist/commands/clean/ios.js +12 -15
- package/dist/commands/config/aliases.js +122 -0
- package/dist/commands/config/commands.js +49 -0
- package/dist/commands/config/initApp.js +191 -0
- package/dist/commands/config/rollback.js +15 -4
- package/dist/commands/config/upgrade.js +36 -0
- package/dist/commands/mcp/commands.js +21 -0
- package/dist/commands/mcp/server.js +27 -0
- package/dist/commands/mcp/setup.js +62 -0
- package/dist/commands/mcp/tools.js +359 -0
- package/dist/commands/project/commands.js +132 -0
- package/dist/commands/project/devices.js +10 -26
- package/dist/commands/project/doctor.js +58 -0
- package/dist/commands/project/generate.js +183 -30
- package/dist/commands/project/setup.js +10 -28
- package/dist/commands/project/status.js +65 -0
- package/dist/commands/version/bump.js +75 -101
- package/dist/commands/version/commands.js +63 -0
- package/dist/commands/version/update.js +36 -24
- package/dist/constants.js +6 -1
- package/dist/styles.js +42 -5
- package/dist/utils/helpers/error.js +14 -0
- package/dist/utils/helpers/file.js +1 -1
- package/dist/utils/helpers/version.js +2 -1
- package/dist/utils/services/backup.js +12 -1
- package/dist/utils/services/command.js +1 -34
- package/dist/utils/services/exec.js +76 -101
- package/dist/utils/services/logger.js +10 -4
- package/dist/utils/validators/validation.js +24 -12
- package/docs/INSTALLATION.md +72 -0
- package/docs/TROUBLESHOOT.md +140 -0
- package/docs/USAGE.md +185 -0
- package/docs/VERSION_MANAGEMENT.md +177 -0
- package/package.json +7 -11
- package/src/cli.ts +82 -371
- package/src/commands/build/commands.ts +169 -0
- package/src/commands/build/testflight.ts +18 -0
- package/src/commands/clean/commands.ts +43 -0
- package/src/commands/clean/flutter.ts +9 -13
- package/src/commands/clean/ios.ts +13 -13
- package/src/commands/config/aliases.ts +150 -0
- package/src/commands/config/commands.ts +50 -0
- package/src/commands/config/initApp.ts +213 -0
- package/src/commands/config/rollback.ts +16 -4
- package/src/commands/config/upgrade.ts +40 -0
- package/src/commands/mcp/commands.ts +23 -0
- package/src/commands/mcp/server.ts +35 -0
- package/src/commands/mcp/setup.ts +69 -0
- package/src/commands/mcp/tools.ts +365 -0
- package/src/commands/project/commands.ts +132 -0
- package/src/commands/project/devices.ts +11 -24
- package/src/commands/project/doctor.ts +81 -0
- package/src/commands/project/generate.ts +211 -32
- package/src/commands/project/setup.ts +13 -30
- package/src/commands/project/status.ts +72 -0
- package/src/commands/version/bump.ts +98 -110
- package/src/commands/version/commands.ts +76 -0
- package/src/commands/version/update.ts +86 -75
- package/src/constants.ts +7 -1
- package/src/styles.ts +49 -7
- package/src/utils/helpers/error.ts +16 -0
- package/src/utils/helpers/file.ts +1 -1
- package/src/utils/helpers/version.ts +2 -1
- package/src/utils/services/backup.ts +17 -1
- package/src/utils/services/command.ts +1 -58
- package/src/utils/services/exec.ts +92 -117
- package/src/utils/services/logger.ts +12 -4
- package/src/utils/validators/validation.ts +24 -12
- package/CODE_QUALITY.md +0 -398
- package/ENHANCEMENTS.md +0 -310
- package/FEATURE_ENHANCEMENTS.md +0 -435
- package/INSTALLATION.md +0 -118
- package/SAFETY_FEATURES.md +0 -396
- package/TROUBLESHOOT.md +0 -60
- package/USAGE.md +0 -412
- package/VERSION_MANAGEMENT.md +0 -438
|
@@ -1,24 +1,55 @@
|
|
|
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 { getCurrentVersion, incrementVersion, formatVersion, getCurrentIosBuildNumber } from "../../utils/helpers/version.js";
|
|
5
|
+
import { handleCommandError } from "../../utils/helpers/error.js";
|
|
4
6
|
import { updateFlutterVersion } from "./update.js";
|
|
5
7
|
import chalk from "chalk";
|
|
6
8
|
export { bumpVersion, bumpIosBuildOnly, bumpAndroidBuildOnly, bumpBothBuilds, showCurrentVersion };
|
|
7
9
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* iOS build number resets to 1 on version change
|
|
11
|
-
*
|
|
12
|
-
* @param type - Type of version bump (major, minor, patch)
|
|
10
|
+
* Prompts user for confirmation. Returns true if confirmed.
|
|
11
|
+
* Skips prompt and returns true if skipConfirm is true (--yes flag).
|
|
13
12
|
*/
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
function confirmAction(message, skipConfirm) {
|
|
14
|
+
if (skipConfirm)
|
|
15
|
+
return Promise.resolve(true);
|
|
16
|
+
return new Promise((resolve) => {
|
|
17
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
18
|
+
rl.question(chalk.yellow(`${message} (y/n): `), (answer) => {
|
|
19
|
+
rl.close();
|
|
20
|
+
resolve(answer.toLowerCase() === "y");
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Shows a colored diff preview of what will change.
|
|
26
|
+
*/
|
|
27
|
+
function showDiffPreview(oldVersion, newVersion, oldAndroidBuild, newAndroidBuild, oldIosBuild, newIosBuild) {
|
|
28
|
+
console.log(chalk.cyan("\nFile changes preview:"));
|
|
29
|
+
if (newAndroidBuild !== "") {
|
|
30
|
+
console.log(chalk.gray("\n pubspec.yaml:"));
|
|
31
|
+
console.log(chalk.red(` - version: ${oldVersion}+${oldAndroidBuild}`));
|
|
32
|
+
console.log(chalk.green(` + version: ${newVersion}+${newAndroidBuild}`));
|
|
33
|
+
}
|
|
34
|
+
if (newIosBuild !== "") {
|
|
35
|
+
console.log(chalk.gray("\n project.pbxproj:"));
|
|
36
|
+
console.log(chalk.red(` - MARKETING_VERSION = ${oldVersion};`));
|
|
37
|
+
console.log(chalk.green(` + MARKETING_VERSION = ${newVersion};`));
|
|
38
|
+
console.log(chalk.red(` - CURRENT_PROJECT_VERSION = ${oldIosBuild};`));
|
|
39
|
+
console.log(chalk.green(` + CURRENT_PROJECT_VERSION = ${newIosBuild};`));
|
|
40
|
+
}
|
|
41
|
+
console.log();
|
|
42
|
+
}
|
|
43
|
+
async function bumpVersion(type, skipConfirm = false) {
|
|
16
44
|
if (!validateFlutterProject()) {
|
|
17
45
|
process.exit(1);
|
|
18
46
|
}
|
|
19
47
|
try {
|
|
20
48
|
const current = getCurrentVersion();
|
|
49
|
+
const currentIosBuild = getCurrentIosBuildNumber();
|
|
21
50
|
const newVersion = incrementVersion(current, type);
|
|
51
|
+
const currentVersionString = `${current.major}.${current.minor}.${current.patch}`;
|
|
52
|
+
const newVersionString = `${newVersion.major}.${newVersion.minor}.${newVersion.patch}`;
|
|
22
53
|
LoggerHelpers.info(`Current version: ${formatVersion(current)}`);
|
|
23
54
|
LoggerHelpers.info(`Bumping ${type} version...`);
|
|
24
55
|
console.log(chalk.cyan("\nVersion changes:"));
|
|
@@ -26,43 +57,29 @@ async function bumpVersion(type) {
|
|
|
26
57
|
console.log(chalk.gray(" New:"), chalk.green.bold(formatVersion(newVersion)));
|
|
27
58
|
console.log(chalk.cyan("\nBuild number strategy:"));
|
|
28
59
|
console.log(chalk.gray(" Android:"), chalk.white(`${current.buildNumber} → ${newVersion.buildNumber} (incremented)`));
|
|
29
|
-
console.log(chalk.gray(" iOS:"), chalk.white(`${
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
60
|
+
console.log(chalk.gray(" iOS:"), chalk.white(`${currentIosBuild} → 1 (reset for new version)`));
|
|
61
|
+
showDiffPreview(currentVersionString, newVersionString, current.buildNumber, newVersion.buildNumber, currentIosBuild, 1);
|
|
62
|
+
const confirmed = await confirmAction("Proceed with version bump?", skipConfirm);
|
|
63
|
+
if (!confirmed) {
|
|
64
|
+
LoggerHelpers.info("Bump cancelled.");
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
await updateFlutterVersion(newVersionString, newVersion.buildNumber.toString(), "1");
|
|
36
68
|
LoggerHelpers.success(`Version bumped to ${formatVersion(newVersion)}`);
|
|
37
69
|
console.log(chalk.gray("\nAndroid build:"), chalk.white(newVersion.buildNumber));
|
|
38
70
|
console.log(chalk.gray("iOS build:"), chalk.white("1"));
|
|
39
71
|
}
|
|
40
72
|
catch (error) {
|
|
41
|
-
|
|
42
|
-
LoggerHelpers.error(`Error bumping version: ${error.message}`);
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
LoggerHelpers.error(`Error bumping version: ${error}`);
|
|
46
|
-
}
|
|
47
|
-
process.exit(1);
|
|
73
|
+
handleCommandError(error, "Error bumping version");
|
|
48
74
|
}
|
|
49
75
|
}
|
|
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
|
|
76
|
+
async function bumpIosBuildOnly(skipConfirm = false) {
|
|
59
77
|
if (!validateFlutterProject()) {
|
|
60
78
|
process.exit(1);
|
|
61
79
|
}
|
|
62
80
|
try {
|
|
63
81
|
const current = getCurrentVersion();
|
|
64
82
|
const currentVersionString = `${current.major}.${current.minor}.${current.patch}`;
|
|
65
|
-
// Read actual iOS build number from project.pbxproj
|
|
66
83
|
const currentIosBuild = getCurrentIosBuildNumber();
|
|
67
84
|
const nextIosBuild = currentIosBuild + 1;
|
|
68
85
|
LoggerHelpers.info(`Current version: ${formatVersion(current)}`);
|
|
@@ -71,36 +88,27 @@ async function bumpIosBuildOnly() {
|
|
|
71
88
|
console.log(chalk.gray(" Version:"), chalk.white(`${currentVersionString} (unchanged)`));
|
|
72
89
|
console.log(chalk.gray(" Android:"), chalk.white(`${current.buildNumber} (unchanged)`));
|
|
73
90
|
console.log(chalk.gray(" iOS:"), chalk.white(`${currentIosBuild} → ${nextIosBuild}`), chalk.green("(incremented)"));
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
91
|
+
showDiffPreview(currentVersionString, currentVersionString, current.buildNumber, "", currentIosBuild, nextIosBuild);
|
|
92
|
+
const confirmed = await confirmAction("Proceed?", skipConfirm);
|
|
93
|
+
if (!confirmed) {
|
|
94
|
+
LoggerHelpers.info("Bump cancelled.");
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
await updateFlutterVersion(currentVersionString, "", nextIosBuild.toString());
|
|
78
98
|
LoggerHelpers.success(`iOS build number incremented to ${nextIosBuild}`);
|
|
79
|
-
console.log(chalk.gray("\nResult:"), chalk.white(`${currentVersionString}+${current.buildNumber} (iOS: ${nextIosBuild})`));
|
|
80
|
-
console.log(chalk.gray("Use this for:"), chalk.white("TestFlight uploads without version changes"));
|
|
81
99
|
}
|
|
82
100
|
catch (error) {
|
|
83
|
-
|
|
84
|
-
LoggerHelpers.error(`Error incrementing iOS build: ${error.message}`);
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
LoggerHelpers.error(`Error incrementing iOS build: ${error}`);
|
|
88
|
-
}
|
|
89
|
-
process.exit(1);
|
|
101
|
+
handleCommandError(error, "Error incrementing iOS build");
|
|
90
102
|
}
|
|
91
103
|
}
|
|
92
|
-
|
|
93
|
-
* Increments ONLY Android build number
|
|
94
|
-
* Keeps version and iOS build number unchanged
|
|
95
|
-
*/
|
|
96
|
-
async function bumpAndroidBuildOnly() {
|
|
97
|
-
// Pre-flight validation
|
|
104
|
+
async function bumpAndroidBuildOnly(skipConfirm = false) {
|
|
98
105
|
if (!validateFlutterProject()) {
|
|
99
106
|
process.exit(1);
|
|
100
107
|
}
|
|
101
108
|
try {
|
|
102
109
|
const current = getCurrentVersion();
|
|
103
110
|
const currentVersionString = `${current.major}.${current.minor}.${current.patch}`;
|
|
111
|
+
const currentIosBuild = getCurrentIosBuildNumber();
|
|
104
112
|
const nextAndroidBuild = current.buildNumber + 1;
|
|
105
113
|
LoggerHelpers.info(`Current version: ${formatVersion(current)}`);
|
|
106
114
|
LoggerHelpers.info("Incrementing Android build number only...");
|
|
@@ -108,36 +116,26 @@ async function bumpAndroidBuildOnly() {
|
|
|
108
116
|
console.log(chalk.gray(" Version:"), chalk.white(`${currentVersionString} (unchanged)`));
|
|
109
117
|
console.log(chalk.gray(" Android:"), chalk.white(`${current.buildNumber} → ${nextAndroidBuild}`), chalk.green("(incremented)"));
|
|
110
118
|
console.log(chalk.gray(" iOS:"), chalk.white("(unchanged)"));
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
119
|
+
showDiffPreview(currentVersionString, currentVersionString, current.buildNumber, nextAndroidBuild, currentIosBuild, "");
|
|
120
|
+
const confirmed = await confirmAction("Proceed?", skipConfirm);
|
|
121
|
+
if (!confirmed) {
|
|
122
|
+
LoggerHelpers.info("Bump cancelled.");
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
await updateFlutterVersion(currentVersionString, nextAndroidBuild.toString(), "");
|
|
115
126
|
LoggerHelpers.success(`Android build number incremented to ${nextAndroidBuild}`);
|
|
116
127
|
}
|
|
117
128
|
catch (error) {
|
|
118
|
-
|
|
119
|
-
LoggerHelpers.error(`Error incrementing Android build: ${error.message}`);
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
LoggerHelpers.error(`Error incrementing Android build: ${error}`);
|
|
123
|
-
}
|
|
124
|
-
process.exit(1);
|
|
129
|
+
handleCommandError(error, "Error incrementing Android build");
|
|
125
130
|
}
|
|
126
131
|
}
|
|
127
|
-
|
|
128
|
-
* Increments BOTH Android and iOS build numbers
|
|
129
|
-
* Keeps version unchanged
|
|
130
|
-
* Perfect for updating both platforms simultaneously
|
|
131
|
-
*/
|
|
132
|
-
async function bumpBothBuilds() {
|
|
133
|
-
// Pre-flight validation
|
|
132
|
+
async function bumpBothBuilds(skipConfirm = false) {
|
|
134
133
|
if (!validateFlutterProject()) {
|
|
135
134
|
process.exit(1);
|
|
136
135
|
}
|
|
137
136
|
try {
|
|
138
137
|
const current = getCurrentVersion();
|
|
139
138
|
const currentVersionString = `${current.major}.${current.minor}.${current.patch}`;
|
|
140
|
-
// Read actual iOS build number from project.pbxproj
|
|
141
139
|
const currentIosBuild = getCurrentIosBuildNumber();
|
|
142
140
|
const nextAndroidBuild = current.buildNumber + 1;
|
|
143
141
|
const nextIosBuild = currentIosBuild + 1;
|
|
@@ -147,26 +145,19 @@ async function bumpBothBuilds() {
|
|
|
147
145
|
console.log(chalk.gray(" Version:"), chalk.white(`${currentVersionString} (unchanged)`));
|
|
148
146
|
console.log(chalk.gray(" Android:"), chalk.white(`${current.buildNumber} → ${nextAndroidBuild}`), chalk.green("(incremented)"));
|
|
149
147
|
console.log(chalk.gray(" iOS:"), chalk.white(`${currentIosBuild} → ${nextIosBuild}`), chalk.green("(incremented)"));
|
|
150
|
-
|
|
151
|
-
|
|
148
|
+
showDiffPreview(currentVersionString, currentVersionString, current.buildNumber, nextAndroidBuild, currentIosBuild, nextIosBuild);
|
|
149
|
+
const confirmed = await confirmAction("Proceed?", skipConfirm);
|
|
150
|
+
if (!confirmed) {
|
|
151
|
+
LoggerHelpers.info("Bump cancelled.");
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
152
154
|
await updateFlutterVersion(currentVersionString, nextAndroidBuild.toString(), nextIosBuild.toString());
|
|
153
155
|
LoggerHelpers.success(`Both build numbers incremented successfully`);
|
|
154
|
-
console.log(chalk.gray("\nResult:"), chalk.white(`${currentVersionString}+${nextAndroidBuild} (iOS: ${nextIosBuild})`));
|
|
155
|
-
console.log(chalk.gray("Use this for:"), chalk.white("Simultaneous Android and iOS releases"));
|
|
156
156
|
}
|
|
157
157
|
catch (error) {
|
|
158
|
-
|
|
159
|
-
LoggerHelpers.error(`Error incrementing build numbers: ${error.message}`);
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
LoggerHelpers.error(`Error incrementing build numbers: ${error}`);
|
|
163
|
-
}
|
|
164
|
-
process.exit(1);
|
|
158
|
+
handleCommandError(error, "Error incrementing build numbers");
|
|
165
159
|
}
|
|
166
160
|
}
|
|
167
|
-
/**
|
|
168
|
-
* Shows current version information
|
|
169
|
-
*/
|
|
170
161
|
async function showCurrentVersion() {
|
|
171
162
|
try {
|
|
172
163
|
const current = getCurrentVersion();
|
|
@@ -182,23 +173,6 @@ async function showCurrentVersion() {
|
|
|
182
173
|
console.log();
|
|
183
174
|
}
|
|
184
175
|
catch (error) {
|
|
185
|
-
|
|
186
|
-
LoggerHelpers.error(`Error reading version: ${error.message}`);
|
|
187
|
-
}
|
|
188
|
-
else {
|
|
189
|
-
LoggerHelpers.error(`Error reading version: ${error}`);
|
|
190
|
-
}
|
|
191
|
-
process.exit(1);
|
|
176
|
+
handleCommandError(error, "Error reading version");
|
|
192
177
|
}
|
|
193
178
|
}
|
|
194
|
-
/**
|
|
195
|
-
* Helper to get next iOS build number
|
|
196
|
-
* In the future, this could read from Info.plist or project.pbxproj
|
|
197
|
-
* For now, we'll use a simple increment
|
|
198
|
-
*/
|
|
199
|
-
async function getNextIosBuildNumber() {
|
|
200
|
-
// TODO: Read actual iOS build number from Info.plist or project.pbxproj
|
|
201
|
-
// For now, we'll just increment based on timestamp or simple counter
|
|
202
|
-
const current = getCurrentVersion();
|
|
203
|
-
return current.buildNumber + 1;
|
|
204
|
-
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { bumpVersion, bumpIosBuildOnly, bumpAndroidBuildOnly, bumpBothBuilds, showCurrentVersion, } from "./bump.js";
|
|
2
|
+
import { updateFlutterVersion } from "./update.js";
|
|
3
|
+
const yesOption = {
|
|
4
|
+
yes: { alias: "y", type: "boolean", default: false, description: "Skip confirmation prompt" },
|
|
5
|
+
};
|
|
6
|
+
export const versionCommands = [
|
|
7
|
+
{
|
|
8
|
+
command: "bump <type>",
|
|
9
|
+
describe: "Bump version (major, minor, or patch)",
|
|
10
|
+
builder: (yargs) => {
|
|
11
|
+
return yargs
|
|
12
|
+
.positional("type", {
|
|
13
|
+
describe: "Version bump type (major, minor, patch)",
|
|
14
|
+
type: "string",
|
|
15
|
+
choices: ["major", "minor", "patch"],
|
|
16
|
+
})
|
|
17
|
+
.option("yes", yesOption.yes);
|
|
18
|
+
},
|
|
19
|
+
handler: async (argv) => {
|
|
20
|
+
await bumpVersion(argv.type, argv.yes);
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
command: "bump-ios",
|
|
25
|
+
aliases: ["bi"],
|
|
26
|
+
describe: "Increment iOS build number only (for TestFlight)",
|
|
27
|
+
builder: yesOption,
|
|
28
|
+
handler: async (argv) => { await bumpIosBuildOnly(argv.yes); },
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
command: "bump-android",
|
|
32
|
+
aliases: ["ba"],
|
|
33
|
+
describe: "Increment Android build number only",
|
|
34
|
+
builder: yesOption,
|
|
35
|
+
handler: async (argv) => { await bumpAndroidBuildOnly(argv.yes); },
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
command: "bump-build",
|
|
39
|
+
aliases: ["bb"],
|
|
40
|
+
describe: "Increment both Android and iOS build numbers",
|
|
41
|
+
builder: yesOption,
|
|
42
|
+
handler: async (argv) => { await bumpBothBuilds(argv.yes); },
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
command: "version",
|
|
46
|
+
aliases: ["v"],
|
|
47
|
+
describe: "Show current version information",
|
|
48
|
+
handler: async () => { await showCurrentVersion(); },
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
command: "flutter-update-version",
|
|
52
|
+
aliases: ["vset"],
|
|
53
|
+
describe: "Update version and build numbers for both Android and iOS",
|
|
54
|
+
builder: {
|
|
55
|
+
"app-version": { type: "string", description: "The version number to set", default: "" },
|
|
56
|
+
"android-build": { type: "string", description: "The Android build number", default: "" },
|
|
57
|
+
"ios-build": { type: "string", description: "The iOS build number", default: "" },
|
|
58
|
+
},
|
|
59
|
+
handler: async (argv) => {
|
|
60
|
+
await updateFlutterVersion(argv["app-version"], argv["android-build"], argv["ios-build"]);
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
];
|
|
@@ -2,85 +2,97 @@ import fs from "fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { LoggerHelpers } from "../../utils/services/logger.js";
|
|
4
4
|
import { validateFlutterProject } from "../../utils/validators/validation.js";
|
|
5
|
-
import {
|
|
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";
|
|
6
9
|
const currentDir = process.cwd();
|
|
7
10
|
export { updateFlutterVersion };
|
|
8
11
|
async function updateFlutterVersion(version, build, iosBuild) {
|
|
9
|
-
// Pre-flight validation
|
|
10
12
|
if (!validateFlutterProject()) {
|
|
11
13
|
process.exit(1);
|
|
12
14
|
}
|
|
13
15
|
try {
|
|
14
|
-
// Always update the version since it is required.
|
|
15
16
|
LoggerHelpers.info(`Starting update for version ${version}...`);
|
|
16
|
-
//
|
|
17
|
+
// Single dry-run manager for all operations
|
|
18
|
+
const dryRun = isDryRunMode() ? new DryRunManager() : null;
|
|
17
19
|
if (build.trim().length > 0) {
|
|
18
20
|
LoggerHelpers.info("Updating build number in pubspec.yaml...");
|
|
19
|
-
await updatePubspecVersionAndBuild(version, build);
|
|
21
|
+
await updatePubspecVersionAndBuild(version, build, dryRun);
|
|
20
22
|
}
|
|
21
23
|
else {
|
|
22
24
|
LoggerHelpers.info("Android build number not provided. Skipping pubspec.yaml update.");
|
|
23
25
|
}
|
|
24
|
-
// Update iOS settings only if an iOS build number is provided.
|
|
25
26
|
if (iosBuild.trim().length > 0) {
|
|
26
27
|
LoggerHelpers.info("Updating iOS version and build number...");
|
|
27
|
-
await updateIosVersionAndBuild(version, iosBuild);
|
|
28
|
+
await updateIosVersionAndBuild(version, iosBuild, dryRun);
|
|
28
29
|
}
|
|
29
30
|
else {
|
|
30
31
|
LoggerHelpers.info("iOS build number not provided. Skipping iOS update.");
|
|
31
32
|
}
|
|
33
|
+
// Show dry-run summary once at the end
|
|
34
|
+
if (dryRun) {
|
|
35
|
+
dryRun.displaySummary();
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
32
38
|
LoggerHelpers.success(`Update complete. Version set to ${version}` +
|
|
33
39
|
(build.trim().length > 0 ? `, Android build set to ${build}` : "") +
|
|
34
40
|
(iosBuild.trim().length > 0 ? `, and iOS build set to ${iosBuild}` : ""));
|
|
35
41
|
}
|
|
36
42
|
catch (error) {
|
|
37
|
-
|
|
38
|
-
process.exit(1);
|
|
43
|
+
handleCommandError(error, "Error while updating Flutter version and build");
|
|
39
44
|
}
|
|
40
45
|
}
|
|
41
|
-
async function updatePubspecVersionAndBuild(version, build) {
|
|
46
|
+
async function updatePubspecVersionAndBuild(version, build, dryRun) {
|
|
42
47
|
try {
|
|
43
|
-
const pubspecPath = path.join(currentDir,
|
|
48
|
+
const pubspecPath = path.join(currentDir, PROJECT_PATHS.PUBSPEC);
|
|
44
49
|
if (!fs.existsSync(pubspecPath)) {
|
|
45
50
|
throw new Error(`pubspec.yaml not found at ${pubspecPath}`);
|
|
46
51
|
}
|
|
47
|
-
|
|
48
|
-
|
|
52
|
+
if (dryRun) {
|
|
53
|
+
dryRun.logFileOperation("Update pubspec.yaml", pubspecPath, `version: ${version}+${build}`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
createBackupWithCleanup(pubspecPath);
|
|
49
57
|
const pubspecContent = fs.readFileSync(pubspecPath, "utf8");
|
|
50
58
|
const updatedPubspec = pubspecContent.replace(/version: \d+\.\d+\.\d+\+\d+/g, `version: ${version}+${build}`);
|
|
51
59
|
fs.writeFileSync(pubspecPath, updatedPubspec);
|
|
52
60
|
LoggerHelpers.success(`Updated pubspec.yaml with version ${version} and build ${build}`);
|
|
53
61
|
}
|
|
54
62
|
catch (error) {
|
|
55
|
-
LoggerHelpers.error(`Error while updating pubspec.yaml
|
|
63
|
+
LoggerHelpers.error(`Error while updating pubspec.yaml: ${error instanceof Error ? error.message : String(error)}`);
|
|
56
64
|
throw error;
|
|
57
65
|
}
|
|
58
66
|
}
|
|
59
|
-
async function updateIosVersionAndBuild(version, iosBuild) {
|
|
67
|
+
async function updateIosVersionAndBuild(version, iosBuild, dryRun) {
|
|
60
68
|
try {
|
|
61
|
-
const
|
|
62
|
-
const
|
|
69
|
+
const projectPbxProjPath = path.join(currentDir, PROJECT_PATHS.IOS_PROJECT_PBXPROJ);
|
|
70
|
+
const infoPlistPath = path.join(currentDir, PROJECT_PATHS.IOS_INFO_PLIST);
|
|
63
71
|
if (!fs.existsSync(projectPbxProjPath)) {
|
|
64
72
|
throw new Error(`project.pbxproj not found at ${projectPbxProjPath}`);
|
|
65
73
|
}
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
if (dryRun) {
|
|
75
|
+
dryRun.logFileOperation("Update project.pbxproj", projectPbxProjPath, `MARKETING_VERSION = ${version}, CURRENT_PROJECT_VERSION = ${iosBuild}`);
|
|
76
|
+
dryRun.logFileOperation("Update Info.plist", infoPlistPath, `CFBundleShortVersionString = ${version}, CFBundleVersion = ${iosBuild}`);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
// Update project.pbxproj
|
|
80
|
+
createBackupWithCleanup(projectPbxProjPath);
|
|
68
81
|
let projectContent = fs.readFileSync(projectPbxProjPath, 'utf8');
|
|
69
82
|
projectContent = projectContent
|
|
70
83
|
.replace(/MARKETING_VERSION\s*=\s*[^;]+;/g, `MARKETING_VERSION = ${version};`)
|
|
71
84
|
.replace(/CURRENT_PROJECT_VERSION\s*=\s*[^;]+;/g, `CURRENT_PROJECT_VERSION = ${iosBuild};`);
|
|
72
85
|
fs.writeFileSync(projectPbxProjPath, projectContent);
|
|
73
86
|
LoggerHelpers.success(`Updated MARKETING_VERSION to ${version} and CURRENT_PROJECT_VERSION to ${iosBuild}`);
|
|
74
|
-
|
|
87
|
+
// Update Info.plist
|
|
75
88
|
if (!fs.existsSync(infoPlistPath)) {
|
|
76
89
|
throw new Error(`Info.plist not found at ${infoPlistPath}`);
|
|
77
90
|
}
|
|
78
|
-
|
|
79
|
-
createBackup(infoPlistPath);
|
|
91
|
+
createBackupWithCleanup(infoPlistPath);
|
|
80
92
|
const infoPlistContent = fs.readFileSync(infoPlistPath, "utf8");
|
|
81
93
|
const updatedPlist = infoPlistContent
|
|
82
|
-
.replace(/<key>CFBundleShortVersionString<\/key>\s*<string
|
|
83
|
-
.replace(/<key>CFBundleVersion<\/key>\s*<string
|
|
94
|
+
.replace(/<key>CFBundleShortVersionString<\/key>\s*<string>[^<]+<\/string>/, `<key>CFBundleShortVersionString</key>\n\t<string>${version}</string>`)
|
|
95
|
+
.replace(/<key>CFBundleVersion<\/key>\s*<string>[^<]+<\/string>/, `<key>CFBundleVersion</key>\n\t<string>${iosBuild}</string>`);
|
|
84
96
|
fs.writeFileSync(infoPlistPath, updatedPlist);
|
|
85
97
|
LoggerHelpers.success("Updated Info.plist with the new version and iOS build.");
|
|
86
98
|
}
|
package/dist/constants.js
CHANGED
|
@@ -44,7 +44,7 @@ export const BACKUP_CONFIG = {
|
|
|
44
44
|
};
|
|
45
45
|
// Module generation
|
|
46
46
|
export const MODULE_STRUCTURE = {
|
|
47
|
-
DIRECTORIES: ["bloc", "event", "state", "screen", "import", "factory"],
|
|
47
|
+
DIRECTORIES: ["bloc", "event", "state", "screen", "import", "factory", "repo"],
|
|
48
48
|
NAME_PATTERN: /^[a-z0-9_]+$/,
|
|
49
49
|
};
|
|
50
50
|
// Flutter commands
|
|
@@ -122,6 +122,11 @@ export const ERROR_MESSAGES = {
|
|
|
122
122
|
MODULE_NAME_EMPTY: "Module name cannot be empty.",
|
|
123
123
|
MODULE_NAME_INVALID: "Module name must contain only lowercase letters, numbers, and underscores.",
|
|
124
124
|
};
|
|
125
|
+
// MCP server configuration
|
|
126
|
+
export const MCP_CONFIG = {
|
|
127
|
+
SERVER_NAME: "optikit",
|
|
128
|
+
CLAUDE_SETTINGS_FILE: ".claude.json",
|
|
129
|
+
};
|
|
125
130
|
// Info messages
|
|
126
131
|
export const INFO_MESSAGES = {
|
|
127
132
|
RUN_FROM_PROJECT_ROOT: "Please run this command from the root of a Flutter project.",
|
package/dist/styles.js
CHANGED
|
@@ -1,7 +1,44 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
1
2
|
export const boxenOptions = {
|
|
2
|
-
padding: 1,
|
|
3
|
-
margin: 1,
|
|
4
|
-
borderStyle: 'round',
|
|
5
|
-
borderColor: '
|
|
6
|
-
backgroundColor: '#555555',
|
|
3
|
+
padding: { top: 1, bottom: 1, left: 3, right: 3 },
|
|
4
|
+
margin: { top: 1, bottom: 0, left: 1, right: 1 },
|
|
5
|
+
borderStyle: 'round',
|
|
6
|
+
borderColor: 'cyan',
|
|
7
7
|
};
|
|
8
|
+
export function createBanner(version) {
|
|
9
|
+
const greeting = chalk.white.bold('Hello ^_^');
|
|
10
|
+
const logo = chalk.cyan.bold('OptiKit CLI');
|
|
11
|
+
const ver = chalk.gray(` v${version}`);
|
|
12
|
+
const tagline = chalk.white('Flutter & Opticore Toolkit');
|
|
13
|
+
const divider = chalk.gray('─────────────────────────');
|
|
14
|
+
return `${greeting}\n${divider}\n${logo}${ver}\n${tagline}`;
|
|
15
|
+
}
|
|
16
|
+
export const bannerBoxOptions = {
|
|
17
|
+
padding: { top: 1, bottom: 1, left: 3, right: 3 },
|
|
18
|
+
margin: { top: 1, bottom: 0, left: 1, right: 1 },
|
|
19
|
+
borderStyle: 'round',
|
|
20
|
+
borderColor: 'cyan',
|
|
21
|
+
textAlignment: 'center',
|
|
22
|
+
};
|
|
23
|
+
export const icons = {
|
|
24
|
+
success: chalk.green('✔'),
|
|
25
|
+
error: chalk.red('✖'),
|
|
26
|
+
warning: chalk.yellow('⚠'),
|
|
27
|
+
info: chalk.cyan('ℹ'),
|
|
28
|
+
arrow: chalk.cyan('→'),
|
|
29
|
+
dot: chalk.gray('·'),
|
|
30
|
+
star: chalk.yellow('★'),
|
|
31
|
+
rocket: '🚀',
|
|
32
|
+
package: '📦',
|
|
33
|
+
phone: '📱',
|
|
34
|
+
wrench: '🔧',
|
|
35
|
+
check: '✅',
|
|
36
|
+
cross: '❌',
|
|
37
|
+
clock: '⏱',
|
|
38
|
+
};
|
|
39
|
+
export const divider = chalk.gray('─'.repeat(50));
|
|
40
|
+
export function sectionHeader(title) {
|
|
41
|
+
console.log();
|
|
42
|
+
console.log(chalk.cyan.bold(` ${title}`));
|
|
43
|
+
console.log(chalk.gray(` ${'─'.repeat(title.length + 4)}`));
|
|
44
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { LoggerHelpers } from "../services/logger.js";
|
|
2
|
+
export { handleCommandError };
|
|
3
|
+
/**
|
|
4
|
+
* Handles command errors with consistent formatting and exits the process.
|
|
5
|
+
* Replaces the repeated try-catch pattern across all command modules.
|
|
6
|
+
*
|
|
7
|
+
* @param error - The caught error (unknown type)
|
|
8
|
+
* @param context - Description of what was happening (e.g., "Error bumping version")
|
|
9
|
+
*/
|
|
10
|
+
function handleCommandError(error, context) {
|
|
11
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
12
|
+
LoggerHelpers.error(`${context}: ${message}`);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
@@ -13,7 +13,7 @@ function createDirectories(modulePath, directories) {
|
|
|
13
13
|
function writeFile(filePath, content) {
|
|
14
14
|
fs.writeFileSync(filePath, content);
|
|
15
15
|
}
|
|
16
|
-
function getClassName(moduleName
|
|
16
|
+
function getClassName(moduleName) {
|
|
17
17
|
// Split module name by underscores and capitalize each part
|
|
18
18
|
const defineItems = moduleName.split("_").filter(item => item.length > 0);
|
|
19
19
|
let className = "";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { LoggerHelpers } from "../services/logger.js";
|
|
4
|
+
import { PROJECT_PATHS } from "../../constants.js";
|
|
4
5
|
export { parseVersion, incrementVersion, getCurrentVersion, getCurrentIosBuildNumber };
|
|
5
6
|
/**
|
|
6
7
|
* Parses a version string in format "X.Y.Z+B"
|
|
@@ -85,7 +86,7 @@ export function getNextBuildNumber() {
|
|
|
85
86
|
*/
|
|
86
87
|
function getCurrentIosBuildNumber() {
|
|
87
88
|
try {
|
|
88
|
-
const projectPbxProjPath = path.join(process.cwd(),
|
|
89
|
+
const projectPbxProjPath = path.join(process.cwd(), PROJECT_PATHS.IOS_PROJECT_PBXPROJ);
|
|
89
90
|
if (!fs.existsSync(projectPbxProjPath)) {
|
|
90
91
|
LoggerHelpers.warning("iOS project.pbxproj not found. Defaulting to build number 1.");
|
|
91
92
|
return 1;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { LoggerHelpers } from "./logger.js";
|
|
4
|
-
|
|
4
|
+
import { BACKUP_CONFIG } from "../../constants.js";
|
|
5
|
+
export { createBackup, createBackupWithCleanup, restoreBackup, cleanupBackups, getBackupPath };
|
|
5
6
|
/**
|
|
6
7
|
* Creates a backup of a file with timestamp
|
|
7
8
|
* Returns the backup path if successful, null otherwise
|
|
@@ -30,6 +31,16 @@ function createBackup(filePath) {
|
|
|
30
31
|
return null;
|
|
31
32
|
}
|
|
32
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Creates a backup and enforces retention cleanup in one call.
|
|
36
|
+
*/
|
|
37
|
+
function createBackupWithCleanup(filePath, retentionCount = BACKUP_CONFIG.RETENTION_COUNT) {
|
|
38
|
+
const backupPath = createBackup(filePath);
|
|
39
|
+
if (backupPath) {
|
|
40
|
+
cleanupBackups(path.dirname(filePath), retentionCount);
|
|
41
|
+
}
|
|
42
|
+
return backupPath;
|
|
43
|
+
}
|
|
33
44
|
/**
|
|
34
45
|
* Restores a file from backup
|
|
35
46
|
*/
|
|
@@ -1,36 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
export { validateBuildEnvironment, getFlutterCommand };
|
|
3
|
-
/**
|
|
4
|
-
* Performs common validation checks for commands
|
|
5
|
-
* Exits with code 1 if any validation fails
|
|
6
|
-
*
|
|
7
|
-
* @param options - Validation options
|
|
8
|
-
* @returns true if all validations pass (won't return false, exits instead)
|
|
9
|
-
*/
|
|
10
|
-
function validateBuildEnvironment(options) {
|
|
11
|
-
const { requireFlutterProject = true, requireFlutterSdk = false, requireIosProject = false, requireAndroidProject = false, useFvm = false, } = options;
|
|
12
|
-
// Flutter project validation
|
|
13
|
-
if (requireFlutterProject && !validateFlutterProject()) {
|
|
14
|
-
process.exit(1);
|
|
15
|
-
}
|
|
16
|
-
// Flutter SDK validation (async)
|
|
17
|
-
if (requireFlutterSdk) {
|
|
18
|
-
validateFlutterSdk(useFvm).then((isValid) => {
|
|
19
|
-
if (!isValid) {
|
|
20
|
-
process.exit(1);
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
}
|
|
24
|
-
// iOS project validation
|
|
25
|
-
if (requireIosProject && !validateIosProject()) {
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
28
|
-
// Android project validation
|
|
29
|
-
if (requireAndroidProject && !validateAndroidProject()) {
|
|
30
|
-
process.exit(1);
|
|
31
|
-
}
|
|
32
|
-
return true;
|
|
33
|
-
}
|
|
1
|
+
export { getFlutterCommand };
|
|
34
2
|
/**
|
|
35
3
|
* Gets the appropriate Flutter command based on FVM usage
|
|
36
4
|
*
|
|
@@ -44,7 +12,6 @@ function validateBuildEnvironment(options) {
|
|
|
44
12
|
*/
|
|
45
13
|
function getFlutterCommand(baseCommand, useFvm) {
|
|
46
14
|
if (useFvm) {
|
|
47
|
-
// Replace "flutter" with "fvm flutter"
|
|
48
15
|
return baseCommand.replace(/^flutter\s/, "fvm flutter ");
|
|
49
16
|
}
|
|
50
17
|
return baseCommand;
|