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,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 "
|
|
4
|
-
import { LoggerHelpers } from "
|
|
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
|
-
|
|
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
|
package/src/constants.ts
ADDED
|
@@ -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;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { execCommand } from "../services/exec.js";
|
|
2
|
+
import { LoggerHelpers } from "../services/logger.js";
|
|
3
|
+
import { validateFlutterProject, validateFlutterSdk, validateIosProject, validateAndroidProject } from "../validators/validation.js";
|
|
4
|
+
|
|
5
|
+
export { executeBuild, BuildConfig };
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Build configuration
|
|
9
|
+
*/
|
|
10
|
+
interface BuildConfig {
|
|
11
|
+
/** Build type name (e.g., "APK", "Bundle", "iOS", "IPA") */
|
|
12
|
+
type: string;
|
|
13
|
+
/** Base Flutter command (e.g., "flutter build apk") */
|
|
14
|
+
command: string;
|
|
15
|
+
/** Additional build flags */
|
|
16
|
+
flags?: string[];
|
|
17
|
+
/** Whether to validate iOS project */
|
|
18
|
+
requireIos?: boolean;
|
|
19
|
+
/** Whether to validate Android project */
|
|
20
|
+
requireAndroid?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Executes a Flutter build with common validation and error handling
|
|
25
|
+
*
|
|
26
|
+
* @param config - Build configuration
|
|
27
|
+
* @param noFvm - Whether to disable FVM usage
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* await executeBuild({
|
|
31
|
+
* type: "APK",
|
|
32
|
+
* command: "flutter build apk",
|
|
33
|
+
* flags: ["--release", "--obfuscate", "--split-debug-info=build/app/outputs/symbols"],
|
|
34
|
+
* requireAndroid: true
|
|
35
|
+
* }, false);
|
|
36
|
+
*/
|
|
37
|
+
async function executeBuild(config: BuildConfig, noFvm: boolean): Promise<void> {
|
|
38
|
+
const { type, command, flags = [], requireIos = false, requireAndroid = false } = config;
|
|
39
|
+
|
|
40
|
+
LoggerHelpers.info(
|
|
41
|
+
noFvm
|
|
42
|
+
? `Building Flutter ${type} without FVM...`
|
|
43
|
+
: `Building Flutter ${type} with FVM...`
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// Pre-flight validation
|
|
47
|
+
if (!validateFlutterProject()) {
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!(await validateFlutterSdk(!noFvm))) {
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (requireIos && !validateIosProject()) {
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (requireAndroid && !validateAndroidProject()) {
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Build the full command
|
|
64
|
+
const baseCommand = noFvm ? command : command.replace(/^flutter\s/, "fvm flutter ");
|
|
65
|
+
const fullCommand = flags.length > 0
|
|
66
|
+
? `${baseCommand} ${flags.join(" ")}`
|
|
67
|
+
: baseCommand;
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
await execCommand(fullCommand);
|
|
71
|
+
LoggerHelpers.success(`Flutter ${type} build successful.`);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
if (error instanceof Error) {
|
|
74
|
+
LoggerHelpers.error(`Error during ${type} build: ${error.message}`);
|
|
75
|
+
} else {
|
|
76
|
+
LoggerHelpers.error(`Error during ${type} build: ${error}`);
|
|
77
|
+
}
|
|
78
|
+
process.exit(1);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { LoggerHelpers } from "../services/logger.js";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
|
|
4
|
+
export { DryRunManager, isDryRunMode, setDryRunMode };
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Global dry-run state
|
|
8
|
+
*/
|
|
9
|
+
let dryRunEnabled = false;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check if dry-run mode is enabled
|
|
13
|
+
*/
|
|
14
|
+
function isDryRunMode(): boolean {
|
|
15
|
+
return dryRunEnabled;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Set dry-run mode
|
|
20
|
+
*/
|
|
21
|
+
function setDryRunMode(enabled: boolean): void {
|
|
22
|
+
dryRunEnabled = enabled;
|
|
23
|
+
if (enabled) {
|
|
24
|
+
LoggerHelpers.info(chalk.yellow("🔍 DRY-RUN MODE ENABLED - No commands will be executed"));
|
|
25
|
+
console.log(chalk.gray("Commands will be displayed but not run\n"));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Dry-run manager for tracking and displaying operations
|
|
31
|
+
*/
|
|
32
|
+
class DryRunManager {
|
|
33
|
+
private operations: Array<{
|
|
34
|
+
type: string;
|
|
35
|
+
description: string;
|
|
36
|
+
command?: string;
|
|
37
|
+
details?: string;
|
|
38
|
+
}> = [];
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Log a command that would be executed
|
|
42
|
+
*/
|
|
43
|
+
logCommand(description: string, command: string, details?: string): void {
|
|
44
|
+
if (!isDryRunMode()) return;
|
|
45
|
+
|
|
46
|
+
this.operations.push({
|
|
47
|
+
type: "command",
|
|
48
|
+
description,
|
|
49
|
+
command,
|
|
50
|
+
details,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
console.log(chalk.cyan("→"), chalk.bold(description));
|
|
54
|
+
console.log(chalk.gray(" Command:"), chalk.white(command));
|
|
55
|
+
if (details) {
|
|
56
|
+
console.log(chalk.gray(" Details:"), details);
|
|
57
|
+
}
|
|
58
|
+
console.log();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Log a file operation that would be performed
|
|
63
|
+
*/
|
|
64
|
+
logFileOperation(operation: string, filePath: string, details?: string): void {
|
|
65
|
+
if (!isDryRunMode()) return;
|
|
66
|
+
|
|
67
|
+
this.operations.push({
|
|
68
|
+
type: "file",
|
|
69
|
+
description: `${operation}: ${filePath}`,
|
|
70
|
+
details,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
console.log(chalk.cyan("→"), chalk.bold(operation));
|
|
74
|
+
console.log(chalk.gray(" File:"), chalk.white(filePath));
|
|
75
|
+
if (details) {
|
|
76
|
+
console.log(chalk.gray(" Details:"), details);
|
|
77
|
+
}
|
|
78
|
+
console.log();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Log a validation check
|
|
83
|
+
*/
|
|
84
|
+
logValidation(check: string, result: boolean, message?: string): void {
|
|
85
|
+
if (!isDryRunMode()) return;
|
|
86
|
+
|
|
87
|
+
const icon = result ? chalk.green("✓") : chalk.red("✗");
|
|
88
|
+
const status = result ? chalk.green("PASS") : chalk.red("FAIL");
|
|
89
|
+
|
|
90
|
+
console.log(icon, chalk.bold(check), status);
|
|
91
|
+
if (message) {
|
|
92
|
+
console.log(chalk.gray(" "), message);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Display summary of dry-run operations
|
|
98
|
+
*/
|
|
99
|
+
displaySummary(): void {
|
|
100
|
+
if (!isDryRunMode() || this.operations.length === 0) return;
|
|
101
|
+
|
|
102
|
+
console.log(chalk.yellow("\n" + "=".repeat(60)));
|
|
103
|
+
console.log(chalk.yellow.bold("DRY-RUN SUMMARY"));
|
|
104
|
+
console.log(chalk.yellow("=".repeat(60)));
|
|
105
|
+
|
|
106
|
+
const commands = this.operations.filter((op) => op.type === "command");
|
|
107
|
+
const files = this.operations.filter((op) => op.type === "file");
|
|
108
|
+
|
|
109
|
+
console.log(chalk.white(`\nTotal operations: ${this.operations.length}`));
|
|
110
|
+
console.log(chalk.white(` Commands: ${commands.length}`));
|
|
111
|
+
console.log(chalk.white(` File operations: ${files.length}`));
|
|
112
|
+
|
|
113
|
+
console.log(chalk.yellow("\n" + "=".repeat(60)));
|
|
114
|
+
console.log(chalk.gray("No actual changes were made to your system."));
|
|
115
|
+
console.log(chalk.gray("Run without --dry-run to execute these operations.\n"));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Reset the operations log
|
|
120
|
+
*/
|
|
121
|
+
reset(): void {
|
|
122
|
+
this.operations = [];
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import { capitalize } from "./
|
|
3
|
+
import { capitalize } from "./string.js";
|
|
4
4
|
|
|
5
5
|
export { createDirectories, writeFile, getClassName };
|
|
6
6
|
|
|
@@ -18,7 +18,8 @@ function writeFile(filePath: string, content: string) {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
function getClassName(moduleName: string, type: string): string {
|
|
21
|
-
|
|
21
|
+
// Split module name by underscores and capitalize each part
|
|
22
|
+
const defineItems = moduleName.split("_").filter(item => item.length > 0);
|
|
22
23
|
let className = "";
|
|
23
24
|
defineItems.forEach((item) => {
|
|
24
25
|
className += capitalize(item);
|