optikit 1.1.1 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/CHANGELOG.md +23 -2
  2. package/CLAUDE.md +239 -0
  3. package/CODE_QUALITY.md +398 -0
  4. package/ENHANCEMENTS.md +310 -0
  5. package/FEATURE_ENHANCEMENTS.md +435 -0
  6. package/README.md +46 -13
  7. package/SAFETY_FEATURES.md +396 -0
  8. package/USAGE.md +225 -41
  9. package/VERSION_MANAGEMENT.md +438 -0
  10. package/dist/cli.js +116 -7
  11. package/dist/commands/build/releases.js +57 -0
  12. package/dist/commands/buildReleases.js +48 -74
  13. package/dist/commands/clean/flutter.js +51 -0
  14. package/dist/commands/clean/ios.js +109 -0
  15. package/dist/commands/cleanProject.js +34 -4
  16. package/dist/commands/cleanProjectIos.js +17 -7
  17. package/dist/commands/config/init.js +54 -0
  18. package/dist/commands/config/rollback.js +161 -0
  19. package/dist/commands/generateModule.js +39 -11
  20. package/dist/commands/init.js +54 -0
  21. package/dist/commands/openProject.js +17 -0
  22. package/dist/commands/project/devices.js +188 -0
  23. package/dist/commands/project/generate.js +143 -0
  24. package/dist/commands/project/open.js +63 -0
  25. package/dist/commands/project/setup.js +46 -0
  26. package/dist/commands/rollback.js +161 -0
  27. package/dist/commands/setupVSCode.js +27 -21
  28. package/dist/commands/updateVersions.js +13 -1
  29. package/dist/commands/version/bump.js +161 -0
  30. package/dist/commands/version/update.js +91 -0
  31. package/dist/commands/version.js +161 -0
  32. package/dist/constants.js +131 -0
  33. package/dist/utils/backupHelpers.js +88 -0
  34. package/dist/utils/buildHelpers.js +55 -0
  35. package/dist/utils/commandHelpers.js +51 -0
  36. package/dist/utils/configHelpers.js +80 -0
  37. package/dist/utils/dryRunHelpers.js +103 -0
  38. package/dist/utils/fileHelpers.js +2 -1
  39. package/dist/utils/helpers/build.js +55 -0
  40. package/dist/utils/helpers/dryRun.js +103 -0
  41. package/dist/utils/helpers/file.js +24 -0
  42. package/dist/utils/helpers/string.js +3 -0
  43. package/dist/utils/helpers/version.js +80 -0
  44. package/dist/utils/services/backup.js +88 -0
  45. package/dist/utils/services/command.js +51 -0
  46. package/dist/utils/services/config.js +80 -0
  47. package/dist/utils/services/exec.js +132 -0
  48. package/dist/utils/services/logger.js +15 -0
  49. package/dist/utils/validationHelpers.js +101 -0
  50. package/dist/utils/validators/validation.js +101 -0
  51. package/dist/utils/versionHelpers.js +80 -0
  52. package/package.json +1 -1
  53. package/src/cli.ts +165 -7
  54. package/src/commands/build/releases.ts +79 -0
  55. package/src/commands/clean/flutter.ts +58 -0
  56. package/src/commands/{cleanProjectIos.ts → clean/ios.ts} +19 -10
  57. package/src/commands/config/init.ts +63 -0
  58. package/src/commands/config/rollback.ts +200 -0
  59. package/src/commands/project/devices.ts +246 -0
  60. package/src/commands/{generateModule.ts → project/generate.ts} +47 -17
  61. package/src/commands/{openProject.ts → project/open.ts} +26 -5
  62. package/src/commands/project/setup.ts +50 -0
  63. package/src/commands/version/bump.ts +202 -0
  64. package/src/commands/{updateVersions.ts → version/update.ts} +22 -6
  65. package/src/constants.ts +144 -0
  66. package/src/utils/helpers/build.ts +80 -0
  67. package/src/utils/helpers/dryRun.ts +124 -0
  68. package/src/utils/{fileHelpers.ts → helpers/file.ts} +3 -2
  69. package/src/utils/helpers/version.ts +109 -0
  70. package/src/utils/services/backup.ts +109 -0
  71. package/src/utils/services/command.ts +76 -0
  72. package/src/utils/services/config.ts +106 -0
  73. package/src/utils/{execHelpers.ts → services/exec.ts} +1 -1
  74. package/src/utils/validators/validation.ts +122 -0
  75. package/src/commands/buildReleases.ts +0 -102
  76. package/src/commands/cleanProject.ts +0 -25
  77. package/src/commands/setupVSCode.ts +0 -44
  78. /package/src/utils/{stringHelpers.ts → helpers/string.ts} +0 -0
  79. /package/src/utils/{loggerHelpers.ts → services/logger.ts} +0 -0
@@ -0,0 +1,101 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { execCommand } from "./execHelpers.js";
4
+ import { LoggerHelpers } from "./loggerHelpers.js";
5
+ export { validateFlutterProject, validateFlutterSdk, validateIosProject, validateAndroidProject, checkFileExists, };
6
+ /**
7
+ * Validates that the current directory is a Flutter project
8
+ * by checking for pubspec.yaml
9
+ */
10
+ function validateFlutterProject() {
11
+ const pubspecPath = path.join(process.cwd(), "pubspec.yaml");
12
+ if (!fs.existsSync(pubspecPath)) {
13
+ LoggerHelpers.error("Not a Flutter project: pubspec.yaml not found.");
14
+ LoggerHelpers.info("Please run this command from the root of a Flutter project.");
15
+ return false;
16
+ }
17
+ // Check if pubspec.yaml contains Flutter SDK
18
+ const pubspecContent = fs.readFileSync(pubspecPath, "utf8");
19
+ if (!pubspecContent.includes("flutter:")) {
20
+ LoggerHelpers.error("Not a Flutter project: pubspec.yaml does not reference Flutter SDK.");
21
+ return false;
22
+ }
23
+ return true;
24
+ }
25
+ /**
26
+ * Validates that Flutter SDK is available (either via FVM or globally)
27
+ */
28
+ async function validateFlutterSdk(useFvm = false) {
29
+ try {
30
+ if (useFvm) {
31
+ // Check if FVM directory exists
32
+ const fvmPath = path.join(process.cwd(), ".fvm", "flutter_sdk");
33
+ if (!fs.existsSync(fvmPath)) {
34
+ LoggerHelpers.error("FVM Flutter SDK not found at .fvm/flutter_sdk");
35
+ LoggerHelpers.info("Run 'fvm install' or use --disable-fvm flag.");
36
+ return false;
37
+ }
38
+ // Check if fvm command is available
39
+ await execCommand("fvm --version");
40
+ return true;
41
+ }
42
+ else {
43
+ // Check if global Flutter is available
44
+ await execCommand("flutter --version");
45
+ return true;
46
+ }
47
+ }
48
+ catch (error) {
49
+ if (useFvm) {
50
+ LoggerHelpers.error("FVM not found. Please install FVM or use --disable-fvm flag.");
51
+ LoggerHelpers.info("Install FVM: https://fvm.app/docs/getting_started/installation");
52
+ }
53
+ else {
54
+ LoggerHelpers.error("Flutter SDK not found.");
55
+ LoggerHelpers.info("Install Flutter: https://flutter.dev/docs/get-started/install");
56
+ }
57
+ return false;
58
+ }
59
+ }
60
+ /**
61
+ * Validates that the iOS project exists
62
+ */
63
+ function validateIosProject() {
64
+ const iosPath = path.join(process.cwd(), "ios");
65
+ if (!fs.existsSync(iosPath)) {
66
+ LoggerHelpers.error("iOS project directory not found.");
67
+ LoggerHelpers.info("Run 'flutter create .' to add iOS support.");
68
+ return false;
69
+ }
70
+ const xcodeProjPath = path.join(iosPath, "Runner.xcodeproj");
71
+ const xcworkspacePath = path.join(iosPath, "Runner.xcworkspace");
72
+ if (!fs.existsSync(xcodeProjPath) && !fs.existsSync(xcworkspacePath)) {
73
+ LoggerHelpers.error("No Xcode project or workspace found in ios/ directory.");
74
+ return false;
75
+ }
76
+ return true;
77
+ }
78
+ /**
79
+ * Validates that the Android project exists
80
+ */
81
+ function validateAndroidProject() {
82
+ const androidPath = path.join(process.cwd(), "android");
83
+ if (!fs.existsSync(androidPath)) {
84
+ LoggerHelpers.error("Android project directory not found.");
85
+ LoggerHelpers.info("Run 'flutter create .' to add Android support.");
86
+ return false;
87
+ }
88
+ const buildGradlePath = path.join(androidPath, "build.gradle");
89
+ const buildGradleKtsPath = path.join(androidPath, "build.gradle.kts");
90
+ if (!fs.existsSync(buildGradlePath) && !fs.existsSync(buildGradleKtsPath)) {
91
+ LoggerHelpers.error("No build.gradle found in android/ directory.");
92
+ return false;
93
+ }
94
+ return true;
95
+ }
96
+ /**
97
+ * Checks if a file exists at the given path
98
+ */
99
+ function checkFileExists(filePath) {
100
+ return fs.existsSync(filePath);
101
+ }
@@ -0,0 +1,101 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { execCommand } from "../services/exec.js";
4
+ import { LoggerHelpers } from "../services/logger.js";
5
+ export { validateFlutterProject, validateFlutterSdk, validateIosProject, validateAndroidProject, checkFileExists, };
6
+ /**
7
+ * Validates that the current directory is a Flutter project
8
+ * by checking for pubspec.yaml
9
+ */
10
+ function validateFlutterProject() {
11
+ const pubspecPath = path.join(process.cwd(), "pubspec.yaml");
12
+ if (!fs.existsSync(pubspecPath)) {
13
+ LoggerHelpers.error("Not a Flutter project: pubspec.yaml not found.");
14
+ LoggerHelpers.info("Please run this command from the root of a Flutter project.");
15
+ return false;
16
+ }
17
+ // Check if pubspec.yaml contains Flutter SDK
18
+ const pubspecContent = fs.readFileSync(pubspecPath, "utf8");
19
+ if (!pubspecContent.includes("flutter:")) {
20
+ LoggerHelpers.error("Not a Flutter project: pubspec.yaml does not reference Flutter SDK.");
21
+ return false;
22
+ }
23
+ return true;
24
+ }
25
+ /**
26
+ * Validates that Flutter SDK is available (either via FVM or globally)
27
+ */
28
+ async function validateFlutterSdk(useFvm = false) {
29
+ try {
30
+ if (useFvm) {
31
+ // Check if FVM directory exists
32
+ const fvmPath = path.join(process.cwd(), ".fvm", "flutter_sdk");
33
+ if (!fs.existsSync(fvmPath)) {
34
+ LoggerHelpers.error("FVM Flutter SDK not found at .fvm/flutter_sdk");
35
+ LoggerHelpers.info("Run 'fvm install' or use --disable-fvm flag.");
36
+ return false;
37
+ }
38
+ // Check if fvm command is available
39
+ await execCommand("fvm --version");
40
+ return true;
41
+ }
42
+ else {
43
+ // Check if global Flutter is available
44
+ await execCommand("flutter --version");
45
+ return true;
46
+ }
47
+ }
48
+ catch (error) {
49
+ if (useFvm) {
50
+ LoggerHelpers.error("FVM not found. Please install FVM or use --disable-fvm flag.");
51
+ LoggerHelpers.info("Install FVM: https://fvm.app/docs/getting_started/installation");
52
+ }
53
+ else {
54
+ LoggerHelpers.error("Flutter SDK not found.");
55
+ LoggerHelpers.info("Install Flutter: https://flutter.dev/docs/get-started/install");
56
+ }
57
+ return false;
58
+ }
59
+ }
60
+ /**
61
+ * Validates that the iOS project exists
62
+ */
63
+ function validateIosProject() {
64
+ const iosPath = path.join(process.cwd(), "ios");
65
+ if (!fs.existsSync(iosPath)) {
66
+ LoggerHelpers.error("iOS project directory not found.");
67
+ LoggerHelpers.info("Run 'flutter create .' to add iOS support.");
68
+ return false;
69
+ }
70
+ const xcodeProjPath = path.join(iosPath, "Runner.xcodeproj");
71
+ const xcworkspacePath = path.join(iosPath, "Runner.xcworkspace");
72
+ if (!fs.existsSync(xcodeProjPath) && !fs.existsSync(xcworkspacePath)) {
73
+ LoggerHelpers.error("No Xcode project or workspace found in ios/ directory.");
74
+ return false;
75
+ }
76
+ return true;
77
+ }
78
+ /**
79
+ * Validates that the Android project exists
80
+ */
81
+ function validateAndroidProject() {
82
+ const androidPath = path.join(process.cwd(), "android");
83
+ if (!fs.existsSync(androidPath)) {
84
+ LoggerHelpers.error("Android project directory not found.");
85
+ LoggerHelpers.info("Run 'flutter create .' to add Android support.");
86
+ return false;
87
+ }
88
+ const buildGradlePath = path.join(androidPath, "build.gradle");
89
+ const buildGradleKtsPath = path.join(androidPath, "build.gradle.kts");
90
+ if (!fs.existsSync(buildGradlePath) && !fs.existsSync(buildGradleKtsPath)) {
91
+ LoggerHelpers.error("No build.gradle found in android/ directory.");
92
+ return false;
93
+ }
94
+ return true;
95
+ }
96
+ /**
97
+ * Checks if a file exists at the given path
98
+ */
99
+ function checkFileExists(filePath) {
100
+ return fs.existsSync(filePath);
101
+ }
@@ -0,0 +1,80 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ export { parseVersion, incrementVersion, getCurrentVersion };
4
+ /**
5
+ * Parses a version string in format "X.Y.Z+B"
6
+ * @param versionString - Version string (e.g., "1.2.3+45")
7
+ * @returns Parsed version info
8
+ */
9
+ function parseVersion(versionString) {
10
+ const match = versionString.match(/^(\d+)\.(\d+)\.(\d+)\+(\d+)$/);
11
+ if (!match) {
12
+ throw new Error(`Invalid version format: ${versionString}. Expected format: X.Y.Z+B (e.g., 1.2.3+45)`);
13
+ }
14
+ return {
15
+ major: parseInt(match[1], 10),
16
+ minor: parseInt(match[2], 10),
17
+ patch: parseInt(match[3], 10),
18
+ buildNumber: parseInt(match[4], 10),
19
+ };
20
+ }
21
+ /**
22
+ * Increments version based on type
23
+ * @param current - Current version info
24
+ * @param type - Type of increment (major, minor, patch)
25
+ * @param resetIosBuildNumber - Whether to reset iOS build number to 1
26
+ * @returns New version info
27
+ */
28
+ function incrementVersion(current, type, resetIosBuildNumber = false) {
29
+ const newVersion = { ...current };
30
+ switch (type) {
31
+ case 'major':
32
+ newVersion.major += 1;
33
+ newVersion.minor = 0;
34
+ newVersion.patch = 0;
35
+ newVersion.buildNumber += 1;
36
+ break;
37
+ case 'minor':
38
+ newVersion.minor += 1;
39
+ newVersion.patch = 0;
40
+ newVersion.buildNumber += 1;
41
+ break;
42
+ case 'patch':
43
+ newVersion.patch += 1;
44
+ newVersion.buildNumber += 1;
45
+ break;
46
+ }
47
+ return newVersion;
48
+ }
49
+ /**
50
+ * Gets current version from pubspec.yaml
51
+ * @returns Current version info
52
+ */
53
+ function getCurrentVersion() {
54
+ const pubspecPath = path.join(process.cwd(), "pubspec.yaml");
55
+ if (!fs.existsSync(pubspecPath)) {
56
+ throw new Error("pubspec.yaml not found. Are you in a Flutter project?");
57
+ }
58
+ const pubspecContent = fs.readFileSync(pubspecPath, "utf8");
59
+ const versionMatch = pubspecContent.match(/version:\s*(\d+\.\d+\.\d+\+\d+)/);
60
+ if (!versionMatch) {
61
+ throw new Error("No version found in pubspec.yaml");
62
+ }
63
+ return parseVersion(versionMatch[1]);
64
+ }
65
+ /**
66
+ * Formats version info to string
67
+ * @param version - Version info
68
+ * @returns Formatted version string (e.g., "1.2.3+45")
69
+ */
70
+ export function formatVersion(version) {
71
+ return `${version.major}.${version.minor}.${version.patch}+${version.buildNumber}`;
72
+ }
73
+ /**
74
+ * Gets the next build number from current version
75
+ * @returns Next build number
76
+ */
77
+ export function getNextBuildNumber() {
78
+ const current = getCurrentVersion();
79
+ return current.buildNumber + 1;
80
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "optikit",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "OptiKit CLI",
5
5
  "main": "src/cli.ts",
6
6
  "type": "module",
package/src/cli.ts CHANGED
@@ -4,20 +4,29 @@ import chalk from "chalk";
4
4
  import boxen from "boxen";
5
5
  import yargs from "yargs/yargs";
6
6
  import { hideBin } from "yargs/helpers";
7
- import { generateModule } from "./commands/generateModule.js";
8
- import { cleanProject } from "./commands/cleanProject.js";
9
- import { cleanIosProject } from "./commands/cleanProjectIos.js";
10
- import { updateFlutterVersion } from "./commands/updateVersions.js";
7
+ import { generateModule } from "./commands/project/generate.js";
8
+ import { cleanProject } from "./commands/clean/flutter.js";
9
+ import { cleanIosProject } from "./commands/clean/ios.js";
10
+ import { updateFlutterVersion } from "./commands/version/update.js";
11
11
  import {
12
12
  buildFlutterApk,
13
13
  buildFlutterBundle,
14
14
  buildFlutterIos,
15
15
  buildFlutterIpa,
16
- } from "./commands/buildReleases.js";
16
+ } from "./commands/build/releases.js";
17
17
  import { boxenOptions } from "./styles.js";
18
- import { openIos, openAndroid } from "./commands/openProject.js";
18
+ import { openIos, openAndroid } from "./commands/project/open.js";
19
19
  import { createRequire } from "module";
20
- import { createVscodeSettings } from "./commands/setupVSCode.js";
20
+ import { createVscodeSettings } from "./commands/project/setup.js";
21
+ import { initializeProject } from "./commands/config/init.js";
22
+ import { rollbackFiles, rollbackRestore } from "./commands/config/rollback.js";
23
+ import {
24
+ bumpVersion,
25
+ bumpIosBuildOnly,
26
+ bumpAndroidBuildOnly,
27
+ showCurrentVersion
28
+ } from "./commands/version/bump.js";
29
+ import { listDevices, runApp, runAppInteractive } from "./commands/project/devices.js";
21
30
  const require = createRequire(import.meta.url);
22
31
  const packageInfo: { version: string } = require("../package.json");
23
32
 
@@ -191,5 +200,154 @@ const options = yargs(hideBin(process.argv))
191
200
  await createVscodeSettings();
192
201
  }
193
202
  )
203
+ .command(
204
+ "init",
205
+ "Initialize OptiKit configuration in the current project",
206
+ () => {},
207
+ async () => {
208
+ await initializeProject();
209
+ }
210
+ )
211
+ .command(
212
+ "rollback",
213
+ "List and restore files from OptiKit backups",
214
+ (yargs) => {
215
+ return yargs.option("restore", {
216
+ type: "number",
217
+ description: "Restore backup by index number",
218
+ demandOption: false,
219
+ });
220
+ },
221
+ async (argv) => {
222
+ const restoreIndex = argv.restore as number | undefined;
223
+ if (restoreIndex !== undefined) {
224
+ await rollbackRestore(restoreIndex);
225
+ } else {
226
+ await rollbackFiles();
227
+ }
228
+ }
229
+ )
230
+ .command(
231
+ "version",
232
+ "Show current version information",
233
+ () => {},
234
+ async () => {
235
+ await showCurrentVersion();
236
+ }
237
+ )
238
+ .command(
239
+ "version bump <type>",
240
+ "Bump version (major, minor, or patch)",
241
+ (yargs) => {
242
+ return yargs.positional("type", {
243
+ describe: "Version bump type (major, minor, patch)",
244
+ type: "string",
245
+ choices: ["major", "minor", "patch"],
246
+ });
247
+ },
248
+ async (argv) => {
249
+ const type = argv.type as 'major' | 'minor' | 'patch';
250
+ await bumpVersion(type);
251
+ }
252
+ )
253
+ .command(
254
+ "version bump-ios",
255
+ "Increment iOS build number only (for TestFlight)",
256
+ () => {},
257
+ async () => {
258
+ await bumpIosBuildOnly();
259
+ }
260
+ )
261
+ .command(
262
+ "version bump-android",
263
+ "Increment Android build number only",
264
+ () => {},
265
+ async () => {
266
+ await bumpAndroidBuildOnly();
267
+ }
268
+ )
269
+ .command(
270
+ "devices",
271
+ "List all connected devices",
272
+ (yargs) => {
273
+ return yargs.option("disable-fvm", {
274
+ type: "boolean",
275
+ default: false,
276
+ description: "Run without FVM (use --disable-fvm to enable)",
277
+ });
278
+ },
279
+ async (argv) => {
280
+ const useFvm = !argv.disableFvm;
281
+ await listDevices(useFvm);
282
+ }
283
+ )
284
+ .command(
285
+ "run",
286
+ "Run Flutter app on connected device",
287
+ (yargs) => {
288
+ return yargs
289
+ .option("device", {
290
+ alias: "d",
291
+ type: "string",
292
+ description: "Specific device ID to run on",
293
+ })
294
+ .option("release", {
295
+ alias: "r",
296
+ type: "boolean",
297
+ default: false,
298
+ description: "Run in release mode",
299
+ })
300
+ .option("flavor", {
301
+ alias: "f",
302
+ type: "string",
303
+ description: "Build flavor to use",
304
+ })
305
+ .option("disable-fvm", {
306
+ type: "boolean",
307
+ default: false,
308
+ description: "Run without FVM (use --disable-fvm to enable)",
309
+ });
310
+ },
311
+ async (argv) => {
312
+ const useFvm = !argv.disableFvm;
313
+ await runApp({
314
+ device: argv.device as string | undefined,
315
+ release: argv.release as boolean,
316
+ flavor: argv.flavor as string | undefined,
317
+ useFvm,
318
+ });
319
+ }
320
+ )
321
+ .command(
322
+ "run-select",
323
+ "Interactive device selection and run",
324
+ (yargs) => {
325
+ return yargs
326
+ .option("release", {
327
+ alias: "r",
328
+ type: "boolean",
329
+ default: false,
330
+ description: "Run in release mode",
331
+ })
332
+ .option("flavor", {
333
+ alias: "f",
334
+ type: "string",
335
+ description: "Build flavor to use",
336
+ })
337
+ .option("disable-fvm", {
338
+ type: "boolean",
339
+ default: false,
340
+ description: "Run without FVM (use --disable-fvm to enable)",
341
+ });
342
+ },
343
+ async (argv) => {
344
+ const useFvm = !argv.disableFvm;
345
+ await runAppInteractive({
346
+ release: argv.release as boolean,
347
+ flavor: argv.flavor as string | undefined,
348
+ useFvm,
349
+ });
350
+ }
351
+ )
194
352
  .version(version)
195
353
  .help(true).argv;
@@ -0,0 +1,79 @@
1
+ import { executeBuild } from "../../utils/helpers/build.js";
2
+ import { BUILD_CONFIGS } from "../../constants.js";
3
+
4
+ export {
5
+ buildFlutterApk,
6
+ buildFlutterBundle,
7
+ buildFlutterIos,
8
+ buildFlutterIpa,
9
+ };
10
+
11
+ /**
12
+ * Builds Flutter APK with release configuration, obfuscation, and split debug info
13
+ * @param noFvm - Whether to disable FVM usage
14
+ */
15
+ async function buildFlutterApk(noFvm: boolean) {
16
+ await executeBuild(
17
+ {
18
+ type: "APK",
19
+ command: "flutter build apk",
20
+ flags: [
21
+ ...BUILD_CONFIGS.APK.flags,
22
+ `--split-debug-info=${BUILD_CONFIGS.APK.outputPath}`,
23
+ ],
24
+ requireAndroid: true,
25
+ },
26
+ noFvm
27
+ );
28
+ }
29
+
30
+ /**
31
+ * Builds Flutter App Bundle with release configuration, obfuscation, and split debug info
32
+ * @param noFvm - Whether to disable FVM usage
33
+ */
34
+ async function buildFlutterBundle(noFvm: boolean) {
35
+ await executeBuild(
36
+ {
37
+ type: "Bundle",
38
+ command: "flutter build appbundle",
39
+ flags: [
40
+ ...BUILD_CONFIGS.BUNDLE.flags,
41
+ `--split-debug-info=${BUILD_CONFIGS.BUNDLE.outputPath}`,
42
+ ],
43
+ requireAndroid: true,
44
+ },
45
+ noFvm
46
+ );
47
+ }
48
+
49
+ /**
50
+ * Builds Flutter iOS app with release configuration
51
+ * @param noFvm - Whether to disable FVM usage
52
+ */
53
+ async function buildFlutterIos(noFvm: boolean) {
54
+ await executeBuild(
55
+ {
56
+ type: "iOS app",
57
+ command: "flutter build ios",
58
+ flags: [...BUILD_CONFIGS.IOS.flags],
59
+ requireIos: true,
60
+ },
61
+ noFvm
62
+ );
63
+ }
64
+
65
+ /**
66
+ * Creates release IPA with updated build version number
67
+ * @param noFvm - Whether to disable FVM usage
68
+ */
69
+ async function buildFlutterIpa(noFvm: boolean) {
70
+ await executeBuild(
71
+ {
72
+ type: "IPA",
73
+ command: "flutter build ipa",
74
+ flags: [...BUILD_CONFIGS.IPA.flags],
75
+ requireIos: true,
76
+ },
77
+ noFvm
78
+ );
79
+ }
@@ -0,0 +1,58 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { execCommand } from "../../utils/services/exec.js";
4
+ import { LoggerHelpers } from "../../utils/services/logger.js";
5
+ import { validateFlutterProject, validateFlutterSdk } from "../../utils/validators/validation.js";
6
+ import { createBackup } from "../../utils/services/backup.js";
7
+
8
+ export { cleanProject };
9
+
10
+ async function cleanProject(noFvm: boolean) {
11
+ LoggerHelpers.info(
12
+ noFvm ? "Running clean without FVM..." : "Running clean with FVM..."
13
+ );
14
+
15
+ // Pre-flight validation
16
+ if (!validateFlutterProject()) {
17
+ process.exit(1);
18
+ }
19
+
20
+ if (!(await validateFlutterSdk(!noFvm))) {
21
+ process.exit(1);
22
+ }
23
+
24
+ try {
25
+ // Step 1: Run flutter clean
26
+ const flutterCommand = noFvm ? "flutter clean" : "fvm flutter clean";
27
+ LoggerHelpers.info("Running Flutter clean...");
28
+ await execCommand(flutterCommand);
29
+ LoggerHelpers.success("Flutter clean completed.");
30
+
31
+ // Step 2: Remove pubspec.lock using Node.js fs (cross-platform)
32
+ const pubspecLockPath = path.join(process.cwd(), "pubspec.lock");
33
+ if (fs.existsSync(pubspecLockPath)) {
34
+ LoggerHelpers.info("Removing pubspec.lock...");
35
+ // Create backup before deletion
36
+ createBackup(pubspecLockPath);
37
+ fs.unlinkSync(pubspecLockPath);
38
+ LoggerHelpers.success("pubspec.lock removed.");
39
+ } else {
40
+ LoggerHelpers.info("pubspec.lock does not exist, skipping removal.");
41
+ }
42
+
43
+ // Step 3: Run flutter pub get
44
+ const pubGetCommand = noFvm ? "flutter pub get" : "fvm flutter pub get";
45
+ LoggerHelpers.info("Running Flutter pub get...");
46
+ await execCommand(pubGetCommand);
47
+ LoggerHelpers.success("Flutter pub get completed.");
48
+
49
+ LoggerHelpers.success("Project cleaned successfully.");
50
+ } catch (error) {
51
+ if (error instanceof Error) {
52
+ LoggerHelpers.error(`Error during clean: ${error.message}`);
53
+ } else {
54
+ LoggerHelpers.error(`Error during clean: ${error}`);
55
+ }
56
+ process.exit(1);
57
+ }
58
+ }
@@ -1,7 +1,8 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
- import { LoggerHelpers } from "../utils/loggerHelpers.js";
4
- import { execInIos, execCommand, iosDirectory, execInIosWithRetry } from "../utils/execHelpers.js";
3
+ import { LoggerHelpers } from "../../utils/services/logger.js";
4
+ import { execInIos, execCommand, iosDirectory, execInIosWithRetry } from "../../utils/services/exec.js";
5
+ import { validateFlutterProject, validateIosProject } from "../../utils/validators/validation.js";
5
6
 
6
7
  export { cleanIosProject };
7
8
 
@@ -61,21 +62,28 @@ async function ensureFlutterArtifactsExist() {
61
62
  }
62
63
 
63
64
  async function cleanIosProject(cleanCache: boolean, repoUpdate: boolean) {
64
- try {
65
- const xcodeProjPath = path.join(iosDirectory, "Runner.xcodeproj");
65
+ // Pre-flight validation
66
+ if (!validateFlutterProject()) {
67
+ process.exit(1);
68
+ }
66
69
 
67
- if (!fs.existsSync(xcodeProjPath)) {
68
- LoggerHelpers.error("No Xcode project found in the ios directory.");
69
- return;
70
- }
70
+ if (!validateIosProject()) {
71
+ process.exit(1);
72
+ }
71
73
 
74
+ try {
72
75
  LoggerHelpers.info("Running clean for iOS project...");
73
76
 
74
77
  await ensureFlutterArtifactsExist();
75
78
 
76
79
  LoggerHelpers.info("Removing Podfile.lock...");
77
- await execInIos("rm -rf Podfile.lock");
78
- LoggerHelpers.success("Removed Podfile.lock.");
80
+ const podfileLockPath = path.join(iosDirectory, "Podfile.lock");
81
+ if (fs.existsSync(podfileLockPath)) {
82
+ fs.unlinkSync(podfileLockPath);
83
+ LoggerHelpers.success("Removed Podfile.lock.");
84
+ } else {
85
+ LoggerHelpers.info("Podfile.lock does not exist, skipping removal.");
86
+ }
79
87
 
80
88
  LoggerHelpers.info("Deintegrating pods...");
81
89
  await execInIos("pod deintegrate");
@@ -116,5 +124,6 @@ async function cleanIosProject(cleanCache: boolean, repoUpdate: boolean) {
116
124
  } else {
117
125
  LoggerHelpers.error(`Unknown error: ${JSON.stringify(error)}`);
118
126
  }
127
+ process.exit(1);
119
128
  }
120
129
  }