optikit 1.1.1 → 1.2.1

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 (80) hide show
  1. package/CHANGELOG.md +39 -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 +264 -41
  9. package/VERSION_MANAGEMENT.md +438 -0
  10. package/dist/cli.js +127 -8
  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 +163 -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 +191 -8
  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/project/open.ts +193 -0
  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/openProject.ts +0 -51
  78. package/src/commands/setupVSCode.ts +0 -44
  79. /package/src/utils/{stringHelpers.ts → helpers/string.ts} +0 -0
  80. /package/src/utils/{loggerHelpers.ts → services/logger.ts} +0 -0
@@ -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 "./stringHelpers.js";
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
- const defineItems = moduleName.replace(type, "").split("_");
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);
@@ -0,0 +1,109 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { LoggerHelpers } from "../services/logger.js";
4
+
5
+ export { parseVersion, incrementVersion, getCurrentVersion, VersionInfo };
6
+
7
+ /**
8
+ * Version information structure
9
+ */
10
+ interface VersionInfo {
11
+ major: number;
12
+ minor: number;
13
+ patch: number;
14
+ buildNumber: number;
15
+ }
16
+
17
+ /**
18
+ * Parses a version string in format "X.Y.Z+B"
19
+ * @param versionString - Version string (e.g., "1.2.3+45")
20
+ * @returns Parsed version info
21
+ */
22
+ function parseVersion(versionString: string): VersionInfo {
23
+ const match = versionString.match(/^(\d+)\.(\d+)\.(\d+)\+(\d+)$/);
24
+
25
+ if (!match) {
26
+ throw new Error(`Invalid version format: ${versionString}. Expected format: X.Y.Z+B (e.g., 1.2.3+45)`);
27
+ }
28
+
29
+ return {
30
+ major: parseInt(match[1], 10),
31
+ minor: parseInt(match[2], 10),
32
+ patch: parseInt(match[3], 10),
33
+ buildNumber: parseInt(match[4], 10),
34
+ };
35
+ }
36
+
37
+ /**
38
+ * Increments version based on type
39
+ * @param current - Current version info
40
+ * @param type - Type of increment (major, minor, patch)
41
+ * @param resetIosBuildNumber - Whether to reset iOS build number to 1
42
+ * @returns New version info
43
+ */
44
+ function incrementVersion(
45
+ current: VersionInfo,
46
+ type: 'major' | 'minor' | 'patch',
47
+ resetIosBuildNumber: boolean = false
48
+ ): VersionInfo {
49
+ const newVersion = { ...current };
50
+
51
+ switch (type) {
52
+ case 'major':
53
+ newVersion.major += 1;
54
+ newVersion.minor = 0;
55
+ newVersion.patch = 0;
56
+ newVersion.buildNumber += 1;
57
+ break;
58
+ case 'minor':
59
+ newVersion.minor += 1;
60
+ newVersion.patch = 0;
61
+ newVersion.buildNumber += 1;
62
+ break;
63
+ case 'patch':
64
+ newVersion.patch += 1;
65
+ newVersion.buildNumber += 1;
66
+ break;
67
+ }
68
+
69
+ return newVersion;
70
+ }
71
+
72
+ /**
73
+ * Gets current version from pubspec.yaml
74
+ * @returns Current version info
75
+ */
76
+ function getCurrentVersion(): VersionInfo {
77
+ const pubspecPath = path.join(process.cwd(), "pubspec.yaml");
78
+
79
+ if (!fs.existsSync(pubspecPath)) {
80
+ throw new Error("pubspec.yaml not found. Are you in a Flutter project?");
81
+ }
82
+
83
+ const pubspecContent = fs.readFileSync(pubspecPath, "utf8");
84
+ const versionMatch = pubspecContent.match(/version:\s*(\d+\.\d+\.\d+\+\d+)/);
85
+
86
+ if (!versionMatch) {
87
+ throw new Error("No version found in pubspec.yaml");
88
+ }
89
+
90
+ return parseVersion(versionMatch[1]);
91
+ }
92
+
93
+ /**
94
+ * Formats version info to string
95
+ * @param version - Version info
96
+ * @returns Formatted version string (e.g., "1.2.3+45")
97
+ */
98
+ export function formatVersion(version: VersionInfo): string {
99
+ return `${version.major}.${version.minor}.${version.patch}+${version.buildNumber}`;
100
+ }
101
+
102
+ /**
103
+ * Gets the next build number from current version
104
+ * @returns Next build number
105
+ */
106
+ export function getNextBuildNumber(): number {
107
+ const current = getCurrentVersion();
108
+ return current.buildNumber + 1;
109
+ }
@@ -0,0 +1,109 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { LoggerHelpers } from "./logger.js";
4
+
5
+ export { createBackup, restoreBackup, cleanupBackups, getBackupPath };
6
+
7
+ /**
8
+ * Creates a backup of a file with timestamp
9
+ * Returns the backup path if successful, null otherwise
10
+ */
11
+ function createBackup(filePath: string): string | null {
12
+ try {
13
+ if (!fs.existsSync(filePath)) {
14
+ LoggerHelpers.warning(`File does not exist, skipping backup: ${filePath}`);
15
+ return null;
16
+ }
17
+
18
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
19
+ const parsedPath = path.parse(filePath);
20
+ const backupPath = path.join(
21
+ parsedPath.dir,
22
+ `.optikit-backup`,
23
+ `${parsedPath.name}_${timestamp}${parsedPath.ext}`
24
+ );
25
+
26
+ // Create backup directory if it doesn't exist
27
+ const backupDir = path.dirname(backupPath);
28
+ if (!fs.existsSync(backupDir)) {
29
+ fs.mkdirSync(backupDir, { recursive: true });
30
+ }
31
+
32
+ // Copy file to backup location
33
+ fs.copyFileSync(filePath, backupPath);
34
+ LoggerHelpers.info(`Backup created: ${backupPath}`);
35
+
36
+ return backupPath;
37
+ } catch (error) {
38
+ LoggerHelpers.error(
39
+ `Failed to create backup: ${error instanceof Error ? error.message : error}`
40
+ );
41
+ return null;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Restores a file from backup
47
+ */
48
+ function restoreBackup(originalPath: string, backupPath: string): boolean {
49
+ try {
50
+ if (!fs.existsSync(backupPath)) {
51
+ LoggerHelpers.error(`Backup file not found: ${backupPath}`);
52
+ return false;
53
+ }
54
+
55
+ fs.copyFileSync(backupPath, originalPath);
56
+ LoggerHelpers.success(`Restored from backup: ${originalPath}`);
57
+ return true;
58
+ } catch (error) {
59
+ LoggerHelpers.error(
60
+ `Failed to restore backup: ${error instanceof Error ? error.message : error}`
61
+ );
62
+ return false;
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Cleans up old backups (keeps only the most recent N backups)
68
+ */
69
+ function cleanupBackups(directory: string, keepCount: number = 5): void {
70
+ try {
71
+ const backupDir = path.join(directory, ".optikit-backup");
72
+
73
+ if (!fs.existsSync(backupDir)) {
74
+ return;
75
+ }
76
+
77
+ const files = fs.readdirSync(backupDir);
78
+
79
+ // Sort by modification time (newest first)
80
+ const sortedFiles = files
81
+ .map((file) => ({
82
+ name: file,
83
+ path: path.join(backupDir, file),
84
+ mtime: fs.statSync(path.join(backupDir, file)).mtime.getTime(),
85
+ }))
86
+ .sort((a, b) => b.mtime - a.mtime);
87
+
88
+ // Delete old backups beyond keepCount
89
+ if (sortedFiles.length > keepCount) {
90
+ const filesToDelete = sortedFiles.slice(keepCount);
91
+ filesToDelete.forEach((file) => {
92
+ fs.unlinkSync(file.path);
93
+ LoggerHelpers.info(`Cleaned up old backup: ${file.name}`);
94
+ });
95
+ }
96
+ } catch (error) {
97
+ LoggerHelpers.warning(
98
+ `Failed to cleanup backups: ${error instanceof Error ? error.message : error}`
99
+ );
100
+ }
101
+ }
102
+
103
+ /**
104
+ * Gets the backup directory path for a given file
105
+ */
106
+ function getBackupPath(filePath: string): string {
107
+ const parsedPath = path.parse(filePath);
108
+ return path.join(parsedPath.dir, ".optikit-backup");
109
+ }
@@ -0,0 +1,76 @@
1
+ import { validateFlutterProject, validateFlutterSdk, validateIosProject, validateAndroidProject } from "../validators/validation.js";
2
+
3
+ export { validateBuildEnvironment, getFlutterCommand };
4
+
5
+ /**
6
+ * Validation options for build environment
7
+ */
8
+ interface ValidationOptions {
9
+ requireFlutterProject?: boolean;
10
+ requireFlutterSdk?: boolean;
11
+ requireIosProject?: boolean;
12
+ requireAndroidProject?: boolean;
13
+ useFvm?: boolean;
14
+ }
15
+
16
+ /**
17
+ * Performs common validation checks for commands
18
+ * Exits with code 1 if any validation fails
19
+ *
20
+ * @param options - Validation options
21
+ * @returns true if all validations pass (won't return false, exits instead)
22
+ */
23
+ function validateBuildEnvironment(options: ValidationOptions): boolean {
24
+ const {
25
+ requireFlutterProject = true,
26
+ requireFlutterSdk = false,
27
+ requireIosProject = false,
28
+ requireAndroidProject = false,
29
+ useFvm = false,
30
+ } = options;
31
+
32
+ // Flutter project validation
33
+ if (requireFlutterProject && !validateFlutterProject()) {
34
+ process.exit(1);
35
+ }
36
+
37
+ // Flutter SDK validation (async)
38
+ if (requireFlutterSdk) {
39
+ validateFlutterSdk(useFvm).then((isValid) => {
40
+ if (!isValid) {
41
+ process.exit(1);
42
+ }
43
+ });
44
+ }
45
+
46
+ // iOS project validation
47
+ if (requireIosProject && !validateIosProject()) {
48
+ process.exit(1);
49
+ }
50
+
51
+ // Android project validation
52
+ if (requireAndroidProject && !validateAndroidProject()) {
53
+ process.exit(1);
54
+ }
55
+
56
+ return true;
57
+ }
58
+
59
+ /**
60
+ * Gets the appropriate Flutter command based on FVM usage
61
+ *
62
+ * @param baseCommand - The base Flutter command (e.g., "flutter clean")
63
+ * @param useFvm - Whether to use FVM
64
+ * @returns The command string with or without FVM prefix
65
+ *
66
+ * @example
67
+ * getFlutterCommand("flutter clean", true) // "fvm flutter clean"
68
+ * getFlutterCommand("flutter pub get", false) // "flutter pub get"
69
+ */
70
+ function getFlutterCommand(baseCommand: string, useFvm: boolean): string {
71
+ if (useFvm) {
72
+ // Replace "flutter" with "fvm flutter"
73
+ return baseCommand.replace(/^flutter\s/, "fvm flutter ");
74
+ }
75
+ return baseCommand;
76
+ }
@@ -0,0 +1,106 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { LoggerHelpers } from "./logger.js";
4
+
5
+ export { loadConfig, saveConfig, getConfigPath, OptiKitConfig };
6
+
7
+ /**
8
+ * OptiKit configuration interface
9
+ */
10
+ interface OptiKitConfig {
11
+ /** Backup retention count */
12
+ backupRetentionCount?: number;
13
+ /** Default FVM usage */
14
+ useFvmByDefault?: boolean;
15
+ /** Auto-create backups before destructive operations */
16
+ autoBackup?: boolean;
17
+ /** Verbose logging */
18
+ verbose?: boolean;
19
+ }
20
+
21
+ /**
22
+ * Default configuration
23
+ */
24
+ const DEFAULT_CONFIG: OptiKitConfig = {
25
+ backupRetentionCount: 5,
26
+ useFvmByDefault: false,
27
+ autoBackup: true,
28
+ verbose: false,
29
+ };
30
+
31
+ /**
32
+ * Gets the config file path
33
+ * Checks multiple locations in priority order:
34
+ * 1. .optikitrc in current directory
35
+ * 2. .optikitrc in home directory
36
+ *
37
+ * @returns Config file path or null if not found
38
+ */
39
+ function getConfigPath(): string | null {
40
+ const cwd = process.cwd();
41
+ const home = process.env.HOME || process.env.USERPROFILE || "";
42
+
43
+ const possiblePaths = [
44
+ path.join(cwd, ".optikitrc"),
45
+ path.join(cwd, ".optikitrc.json"),
46
+ path.join(home, ".optikitrc"),
47
+ path.join(home, ".optikitrc.json"),
48
+ ];
49
+
50
+ for (const configPath of possiblePaths) {
51
+ if (fs.existsSync(configPath)) {
52
+ return configPath;
53
+ }
54
+ }
55
+
56
+ return null;
57
+ }
58
+
59
+ /**
60
+ * Loads configuration from .optikitrc file
61
+ * Falls back to default configuration if file doesn't exist
62
+ *
63
+ * @returns Merged configuration (defaults + user config)
64
+ */
65
+ function loadConfig(): OptiKitConfig {
66
+ const configPath = getConfigPath();
67
+
68
+ if (!configPath) {
69
+ return { ...DEFAULT_CONFIG };
70
+ }
71
+
72
+ try {
73
+ const fileContent = fs.readFileSync(configPath, "utf8");
74
+ const userConfig = JSON.parse(fileContent) as Partial<OptiKitConfig>;
75
+
76
+ // Merge with defaults
77
+ return {
78
+ ...DEFAULT_CONFIG,
79
+ ...userConfig,
80
+ };
81
+ } catch (error) {
82
+ LoggerHelpers.warning(`Failed to load config from ${configPath}, using defaults.`);
83
+ return { ...DEFAULT_CONFIG };
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Saves configuration to .optikitrc file in current directory
89
+ *
90
+ * @param config - Configuration to save
91
+ * @returns true if successful, false otherwise
92
+ */
93
+ function saveConfig(config: OptiKitConfig): boolean {
94
+ const configPath = path.join(process.cwd(), ".optikitrc.json");
95
+
96
+ try {
97
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), "utf8");
98
+ LoggerHelpers.success(`Configuration saved to ${configPath}`);
99
+ return true;
100
+ } catch (error) {
101
+ LoggerHelpers.error(
102
+ `Failed to save config: ${error instanceof Error ? error.message : error}`
103
+ );
104
+ return false;
105
+ }
106
+ }
@@ -1,7 +1,7 @@
1
1
  import { exec, spawn } from "child_process";
2
2
  import { promisify } from "util";
3
3
  import path from "path";
4
- import { LoggerHelpers } from "../utils/loggerHelpers.js";
4
+ import { LoggerHelpers } from "./logger.js";
5
5
 
6
6
  export const iosDirectory = path.join(process.cwd(), "ios");
7
7