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
@@ -1,83 +1,57 @@
1
- import { execCommand } from "../utils/execHelpers.js";
2
- import { LoggerHelpers } from "../utils/loggerHelpers.js";
1
+ import { executeBuild } from "../utils/buildHelpers.js";
2
+ import { BUILD_CONFIGS } from "../constants.js";
3
3
  export { buildFlutterApk, buildFlutterBundle, buildFlutterIos, buildFlutterIpa, };
4
+ /**
5
+ * Builds Flutter APK with release configuration, obfuscation, and split debug info
6
+ * @param noFvm - Whether to disable FVM usage
7
+ */
4
8
  async function buildFlutterApk(noFvm) {
5
- LoggerHelpers.info(noFvm
6
- ? "Building Flutter APK without FVM..."
7
- : "Building Flutter APK with FVM...");
8
- const command = noFvm
9
- ? "flutter build apk --release --obfuscate --split-debug-info=build/app/outputs/symbols"
10
- : "fvm flutter build apk --release --obfuscate --split-debug-info=build/app/outputs/symbols";
11
- try {
12
- await execCommand(command);
13
- LoggerHelpers.success("Flutter APK build successful.");
14
- }
15
- catch (error) {
16
- if (error instanceof Error) {
17
- LoggerHelpers.error(`Error during APK build: ${error.message}`);
18
- }
19
- else {
20
- LoggerHelpers.error(`Error during APK build: ${error}`);
21
- }
22
- }
9
+ await executeBuild({
10
+ type: "APK",
11
+ command: "flutter build apk",
12
+ flags: [
13
+ ...BUILD_CONFIGS.APK.flags,
14
+ `--split-debug-info=${BUILD_CONFIGS.APK.outputPath}`,
15
+ ],
16
+ requireAndroid: true,
17
+ }, noFvm);
23
18
  }
19
+ /**
20
+ * Builds Flutter App Bundle with release configuration, obfuscation, and split debug info
21
+ * @param noFvm - Whether to disable FVM usage
22
+ */
24
23
  async function buildFlutterBundle(noFvm) {
25
- LoggerHelpers.info(noFvm
26
- ? "Building Flutter Bundle without FVM..."
27
- : "Building Flutter Bundle with FVM...");
28
- const command = noFvm
29
- ? "flutter build appbundle --release --obfuscate --split-debug-info=build/app/outputs/symbols"
30
- : "fvm flutter build appbundle --release --obfuscate --split-debug-info=build/app/outputs/symbols";
31
- try {
32
- await execCommand(command);
33
- LoggerHelpers.success("Flutter Bundle build successful.");
34
- }
35
- catch (error) {
36
- if (error instanceof Error) {
37
- LoggerHelpers.error(`Error during Bundle build: ${error.message}`);
38
- }
39
- else {
40
- LoggerHelpers.error(`Error during Bundle build: ${error}`);
41
- }
42
- }
24
+ await executeBuild({
25
+ type: "Bundle",
26
+ command: "flutter build appbundle",
27
+ flags: [
28
+ ...BUILD_CONFIGS.BUNDLE.flags,
29
+ `--split-debug-info=${BUILD_CONFIGS.BUNDLE.outputPath}`,
30
+ ],
31
+ requireAndroid: true,
32
+ }, noFvm);
43
33
  }
34
+ /**
35
+ * Builds Flutter iOS app with release configuration
36
+ * @param noFvm - Whether to disable FVM usage
37
+ */
44
38
  async function buildFlutterIos(noFvm) {
45
- LoggerHelpers.info(noFvm
46
- ? "Building Flutter iOS app without FVM..."
47
- : "Building Flutter iOS app with FVM...");
48
- const command = noFvm
49
- ? "flutter build ios --release"
50
- : "fvm flutter build ios --release";
51
- try {
52
- await execCommand(command);
53
- LoggerHelpers.success("Flutter iOS app build successful.");
54
- }
55
- catch (error) {
56
- if (error instanceof Error) {
57
- LoggerHelpers.error(`Error during iOS build: ${error.message}`);
58
- }
59
- else {
60
- LoggerHelpers.error(`Error during iOS build: ${error}`);
61
- }
62
- }
39
+ await executeBuild({
40
+ type: "iOS app",
41
+ command: "flutter build ios",
42
+ flags: [...BUILD_CONFIGS.IOS.flags],
43
+ requireIos: true,
44
+ }, noFvm);
63
45
  }
46
+ /**
47
+ * Creates release IPA with updated build version number
48
+ * @param noFvm - Whether to disable FVM usage
49
+ */
64
50
  async function buildFlutterIpa(noFvm) {
65
- LoggerHelpers.info(noFvm
66
- ? "Creating release IPA without FVM..."
67
- : "Creating release IPA with FVM...");
68
- const command = noFvm
69
- ? "flutter build ipa --release"
70
- : "fvm flutter build ipa --release";
71
- try {
72
- await execCommand(command);
73
- LoggerHelpers.success("Release IPA creation successful.");
74
- }
75
- catch (error) {
76
- if (error instanceof Error) {
77
- LoggerHelpers.error(`Error during IPA creation: ${error.message}`);
78
- }
79
- else {
80
- LoggerHelpers.error(`Error during IPA creation: ${error}`);
81
- }
82
- }
51
+ await executeBuild({
52
+ type: "IPA",
53
+ command: "flutter build ipa",
54
+ flags: [...BUILD_CONFIGS.IPA.flags],
55
+ requireIos: true,
56
+ }, noFvm);
83
57
  }
@@ -0,0 +1,51 @@
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
+ export { cleanProject };
8
+ async function cleanProject(noFvm) {
9
+ LoggerHelpers.info(noFvm ? "Running clean without FVM..." : "Running clean with FVM...");
10
+ // Pre-flight validation
11
+ if (!validateFlutterProject()) {
12
+ process.exit(1);
13
+ }
14
+ if (!(await validateFlutterSdk(!noFvm))) {
15
+ process.exit(1);
16
+ }
17
+ try {
18
+ // Step 1: Run flutter clean
19
+ const flutterCommand = noFvm ? "flutter clean" : "fvm flutter clean";
20
+ LoggerHelpers.info("Running Flutter clean...");
21
+ await execCommand(flutterCommand);
22
+ LoggerHelpers.success("Flutter clean completed.");
23
+ // Step 2: Remove pubspec.lock using Node.js fs (cross-platform)
24
+ const pubspecLockPath = path.join(process.cwd(), "pubspec.lock");
25
+ if (fs.existsSync(pubspecLockPath)) {
26
+ LoggerHelpers.info("Removing pubspec.lock...");
27
+ // Create backup before deletion
28
+ createBackup(pubspecLockPath);
29
+ fs.unlinkSync(pubspecLockPath);
30
+ LoggerHelpers.success("pubspec.lock removed.");
31
+ }
32
+ else {
33
+ LoggerHelpers.info("pubspec.lock does not exist, skipping removal.");
34
+ }
35
+ // Step 3: Run flutter pub get
36
+ const pubGetCommand = noFvm ? "flutter pub get" : "fvm flutter pub get";
37
+ LoggerHelpers.info("Running Flutter pub get...");
38
+ await execCommand(pubGetCommand);
39
+ LoggerHelpers.success("Flutter pub get completed.");
40
+ LoggerHelpers.success("Project cleaned successfully.");
41
+ }
42
+ catch (error) {
43
+ if (error instanceof Error) {
44
+ LoggerHelpers.error(`Error during clean: ${error.message}`);
45
+ }
46
+ else {
47
+ LoggerHelpers.error(`Error during clean: ${error}`);
48
+ }
49
+ process.exit(1);
50
+ }
51
+ }
@@ -0,0 +1,109 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
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";
6
+ export { cleanIosProject };
7
+ async function getFlutterSdkPath() {
8
+ const fvmDir = path.join(process.cwd(), ".fvm", "flutter_sdk");
9
+ if (fs.existsSync(fvmDir)) {
10
+ LoggerHelpers.info(`Using FVM Flutter SDK...`);
11
+ return fvmDir;
12
+ }
13
+ const command = "which flutter";
14
+ try {
15
+ const globalFlutterPath = await execCommand(command);
16
+ LoggerHelpers.info("Using Flutter SDK...");
17
+ return path.dirname(globalFlutterPath.trim());
18
+ }
19
+ catch (error) {
20
+ LoggerHelpers.error("Flutter SDK not found. Please ensure Flutter is installed.");
21
+ throw error;
22
+ }
23
+ }
24
+ async function getFlutterXcframeworkPath() {
25
+ const flutterSdkPath = await getFlutterSdkPath();
26
+ return path.join(flutterSdkPath, "bin/cache/artifacts/engine/ios/Flutter.xcframework");
27
+ }
28
+ async function ensureFlutterArtifactsExist() {
29
+ const flutterXcframeworkPath = await getFlutterXcframeworkPath();
30
+ if (!fs.existsSync(flutterXcframeworkPath)) {
31
+ LoggerHelpers.warning("Flutter.xcframework not found.");
32
+ try {
33
+ LoggerHelpers.info("Downloading Flutter.xcframework...");
34
+ await execCommand("fvm flutter precache --ios");
35
+ LoggerHelpers.success("Flutter.xcframework has been downloaded successfully.");
36
+ }
37
+ catch (error) {
38
+ LoggerHelpers.error(`Failed to run precache: ${error instanceof Error ? error.message : error}`);
39
+ }
40
+ }
41
+ else {
42
+ LoggerHelpers.success("Flutter.xcframework exists. No need to run 'fvm flutter precache --ios'.");
43
+ }
44
+ }
45
+ async function cleanIosProject(cleanCache, repoUpdate) {
46
+ // Pre-flight validation
47
+ if (!validateFlutterProject()) {
48
+ process.exit(1);
49
+ }
50
+ if (!validateIosProject()) {
51
+ process.exit(1);
52
+ }
53
+ try {
54
+ LoggerHelpers.info("Running clean for iOS project...");
55
+ await ensureFlutterArtifactsExist();
56
+ LoggerHelpers.info("Removing Podfile.lock...");
57
+ const podfileLockPath = path.join(iosDirectory, "Podfile.lock");
58
+ if (fs.existsSync(podfileLockPath)) {
59
+ fs.unlinkSync(podfileLockPath);
60
+ LoggerHelpers.success("Removed Podfile.lock.");
61
+ }
62
+ else {
63
+ LoggerHelpers.info("Podfile.lock does not exist, skipping removal.");
64
+ }
65
+ LoggerHelpers.info("Deintegrating pods...");
66
+ await execInIos("pod deintegrate");
67
+ LoggerHelpers.success("Deintegrated pods.");
68
+ if (cleanCache) {
69
+ LoggerHelpers.info("Cleaning CocoaPods cache...");
70
+ await execInIos("pod cache clean --all");
71
+ LoggerHelpers.success("Cleaned CocoaPods cache.");
72
+ }
73
+ if (repoUpdate) {
74
+ LoggerHelpers.info("Updating CocoaPods repositories...");
75
+ try {
76
+ await execInIosWithRetry("pod repo update", 3, 10000);
77
+ LoggerHelpers.success("Updated CocoaPods repositories.");
78
+ }
79
+ catch (error) {
80
+ LoggerHelpers.error("Failed to update CocoaPods repositories. Retrying...");
81
+ }
82
+ LoggerHelpers.info("Installing pods with repo update...");
83
+ try {
84
+ await execInIosWithRetry("pod update", 3, 10000);
85
+ LoggerHelpers.success("Installed pods with repo update.");
86
+ }
87
+ catch (error) {
88
+ LoggerHelpers.error("Failed to update pods. Retrying...");
89
+ }
90
+ }
91
+ else {
92
+ LoggerHelpers.info("Installing pods without repo update...");
93
+ await execInIosWithRetry("pod install", 3, 10000);
94
+ LoggerHelpers.success("Installed pods without repo update.");
95
+ }
96
+ }
97
+ catch (error) {
98
+ if (error instanceof Error) {
99
+ LoggerHelpers.error(`Error: ${error.message}`);
100
+ }
101
+ else if (typeof error === "string") {
102
+ LoggerHelpers.error(`Error: ${error}`);
103
+ }
104
+ else {
105
+ LoggerHelpers.error(`Unknown error: ${JSON.stringify(error)}`);
106
+ }
107
+ process.exit(1);
108
+ }
109
+ }
@@ -1,13 +1,42 @@
1
+ import fs from "fs";
2
+ import path from "path";
1
3
  import { execCommand } from "../utils/execHelpers.js";
2
4
  import { LoggerHelpers } from "../utils/loggerHelpers.js";
5
+ import { validateFlutterProject, validateFlutterSdk } from "../utils/validationHelpers.js";
6
+ import { createBackup } from "../utils/backupHelpers.js";
3
7
  export { cleanProject };
4
8
  async function cleanProject(noFvm) {
5
9
  LoggerHelpers.info(noFvm ? "Running clean without FVM..." : "Running clean with FVM...");
6
- const command = noFvm
7
- ? "flutter clean && rm -rf pubspec.lock && flutter pub get"
8
- : "fvm flutter clean && rm -rf pubspec.lock && fvm flutter pub get";
10
+ // Pre-flight validation
11
+ if (!validateFlutterProject()) {
12
+ process.exit(1);
13
+ }
14
+ if (!(await validateFlutterSdk(!noFvm))) {
15
+ process.exit(1);
16
+ }
9
17
  try {
10
- await execCommand(command);
18
+ // Step 1: Run flutter clean
19
+ const flutterCommand = noFvm ? "flutter clean" : "fvm flutter clean";
20
+ LoggerHelpers.info("Running Flutter clean...");
21
+ await execCommand(flutterCommand);
22
+ LoggerHelpers.success("Flutter clean completed.");
23
+ // Step 2: Remove pubspec.lock using Node.js fs (cross-platform)
24
+ const pubspecLockPath = path.join(process.cwd(), "pubspec.lock");
25
+ if (fs.existsSync(pubspecLockPath)) {
26
+ LoggerHelpers.info("Removing pubspec.lock...");
27
+ // Create backup before deletion
28
+ createBackup(pubspecLockPath);
29
+ fs.unlinkSync(pubspecLockPath);
30
+ LoggerHelpers.success("pubspec.lock removed.");
31
+ }
32
+ else {
33
+ LoggerHelpers.info("pubspec.lock does not exist, skipping removal.");
34
+ }
35
+ // Step 3: Run flutter pub get
36
+ const pubGetCommand = noFvm ? "flutter pub get" : "fvm flutter pub get";
37
+ LoggerHelpers.info("Running Flutter pub get...");
38
+ await execCommand(pubGetCommand);
39
+ LoggerHelpers.success("Flutter pub get completed.");
11
40
  LoggerHelpers.success("Project cleaned successfully.");
12
41
  }
13
42
  catch (error) {
@@ -17,5 +46,6 @@ async function cleanProject(noFvm) {
17
46
  else {
18
47
  LoggerHelpers.error(`Error during clean: ${error}`);
19
48
  }
49
+ process.exit(1);
20
50
  }
21
51
  }
@@ -2,6 +2,7 @@ import * as fs from "fs";
2
2
  import * as path from "path";
3
3
  import { LoggerHelpers } from "../utils/loggerHelpers.js";
4
4
  import { execInIos, execCommand, iosDirectory, execInIosWithRetry } from "../utils/execHelpers.js";
5
+ import { validateFlutterProject, validateIosProject } from "../utils/validationHelpers.js";
5
6
  export { cleanIosProject };
6
7
  async function getFlutterSdkPath() {
7
8
  const fvmDir = path.join(process.cwd(), ".fvm", "flutter_sdk");
@@ -42,17 +43,25 @@ async function ensureFlutterArtifactsExist() {
42
43
  }
43
44
  }
44
45
  async function cleanIosProject(cleanCache, repoUpdate) {
46
+ // Pre-flight validation
47
+ if (!validateFlutterProject()) {
48
+ process.exit(1);
49
+ }
50
+ if (!validateIosProject()) {
51
+ process.exit(1);
52
+ }
45
53
  try {
46
- const xcodeProjPath = path.join(iosDirectory, "Runner.xcodeproj");
47
- if (!fs.existsSync(xcodeProjPath)) {
48
- LoggerHelpers.error("No Xcode project found in the ios directory.");
49
- return;
50
- }
51
54
  LoggerHelpers.info("Running clean for iOS project...");
52
55
  await ensureFlutterArtifactsExist();
53
56
  LoggerHelpers.info("Removing Podfile.lock...");
54
- await execInIos("rm -rf Podfile.lock");
55
- LoggerHelpers.success("Removed Podfile.lock.");
57
+ const podfileLockPath = path.join(iosDirectory, "Podfile.lock");
58
+ if (fs.existsSync(podfileLockPath)) {
59
+ fs.unlinkSync(podfileLockPath);
60
+ LoggerHelpers.success("Removed Podfile.lock.");
61
+ }
62
+ else {
63
+ LoggerHelpers.info("Podfile.lock does not exist, skipping removal.");
64
+ }
56
65
  LoggerHelpers.info("Deintegrating pods...");
57
66
  await execInIos("pod deintegrate");
58
67
  LoggerHelpers.success("Deintegrated pods.");
@@ -95,5 +104,6 @@ async function cleanIosProject(cleanCache, repoUpdate) {
95
104
  else {
96
105
  LoggerHelpers.error(`Unknown error: ${JSON.stringify(error)}`);
97
106
  }
107
+ process.exit(1);
98
108
  }
99
109
  }
@@ -0,0 +1,54 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { LoggerHelpers } from "../../utils/services/logger.js";
4
+ import { saveConfig } from "../../utils/services/config.js";
5
+ export { initializeProject };
6
+ /**
7
+ * Initializes OptiKit in the current project
8
+ * Creates .optikitrc configuration file with defaults
9
+ */
10
+ async function initializeProject() {
11
+ try {
12
+ LoggerHelpers.info("Initializing OptiKit in this project...");
13
+ const configPath = path.join(process.cwd(), ".optikitrc.json");
14
+ // Check if config already exists
15
+ if (fs.existsSync(configPath)) {
16
+ LoggerHelpers.warning("OptiKit configuration already exists.");
17
+ LoggerHelpers.info("To reconfigure, delete .optikitrc.json and run init again.");
18
+ return;
19
+ }
20
+ // Create default configuration
21
+ const defaultConfig = {
22
+ backupRetentionCount: 5,
23
+ useFvmByDefault: false,
24
+ autoBackup: true,
25
+ verbose: false,
26
+ };
27
+ // Save configuration
28
+ const success = saveConfig(defaultConfig);
29
+ if (success) {
30
+ LoggerHelpers.success("OptiKit initialized successfully!");
31
+ console.log("\nDefault configuration:");
32
+ console.log(JSON.stringify(defaultConfig, null, 2));
33
+ console.log("\nYou can modify .optikitrc.json to customize these settings.\n");
34
+ // Create .gitignore entry for backups if .gitignore exists
35
+ const gitignorePath = path.join(process.cwd(), ".gitignore");
36
+ if (fs.existsSync(gitignorePath)) {
37
+ const gitignoreContent = fs.readFileSync(gitignorePath, "utf8");
38
+ if (!gitignoreContent.includes(".optikit-backup")) {
39
+ fs.appendFileSync(gitignorePath, "\n# OptiKit backup files\n.optikit-backup/\n");
40
+ LoggerHelpers.success("Added .optikit-backup/ to .gitignore");
41
+ }
42
+ }
43
+ }
44
+ }
45
+ catch (error) {
46
+ if (error instanceof Error) {
47
+ LoggerHelpers.error(`Error initializing project: ${error.message}`);
48
+ }
49
+ else {
50
+ LoggerHelpers.error(`Error initializing project: ${error}`);
51
+ }
52
+ process.exit(1);
53
+ }
54
+ }
@@ -0,0 +1,161 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import { LoggerHelpers } from "../../utils/services/logger.js";
4
+ import { restoreBackup } from "../../utils/services/backup.js";
5
+ import chalk from "chalk";
6
+ export { rollbackFiles };
7
+ /**
8
+ * Lists all available backups and allows restoration
9
+ */
10
+ async function rollbackFiles() {
11
+ try {
12
+ LoggerHelpers.info("Searching for OptiKit backups...");
13
+ const backups = findAllBackups(process.cwd());
14
+ if (backups.length === 0) {
15
+ LoggerHelpers.warning("No backups found in this project.");
16
+ LoggerHelpers.info("Backups are created automatically when files are modified.");
17
+ return;
18
+ }
19
+ console.log(chalk.bold(`\nFound ${backups.length} backup(s):\n`));
20
+ // Group backups by original file
21
+ const backupsByFile = new Map();
22
+ for (const backup of backups) {
23
+ const originalFile = getOriginalFilePath(backup.backupPath);
24
+ if (!backupsByFile.has(originalFile)) {
25
+ backupsByFile.set(originalFile, []);
26
+ }
27
+ backupsByFile.get(originalFile).push(backup);
28
+ }
29
+ // Display backups grouped by file
30
+ let index = 1;
31
+ const backupList = [];
32
+ for (const [originalFile, fileBackups] of backupsByFile) {
33
+ console.log(chalk.cyan.bold(`\n${originalFile}`));
34
+ // Sort by timestamp (newest first)
35
+ fileBackups.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
36
+ for (const backup of fileBackups) {
37
+ const timeAgo = getTimeAgo(backup.timestamp);
38
+ const sizeKB = (backup.size / 1024).toFixed(2);
39
+ console.log(chalk.gray(` [${index}]`), chalk.white(backup.timestamp.toLocaleString()), chalk.gray(`(${timeAgo}, ${sizeKB} KB)`));
40
+ backupList.push({
41
+ index,
42
+ originalPath: originalFile,
43
+ backupPath: backup.backupPath,
44
+ timestamp: backup.timestamp,
45
+ });
46
+ index++;
47
+ }
48
+ }
49
+ console.log(chalk.yellow("\n" + "=".repeat(60)));
50
+ console.log(chalk.gray("To restore a backup, run:"));
51
+ console.log(chalk.white(" optikit rollback --restore <number>"));
52
+ console.log(chalk.gray("\nExample:"));
53
+ console.log(chalk.white(" optikit rollback --restore 1"));
54
+ console.log(chalk.yellow("=".repeat(60) + "\n"));
55
+ }
56
+ catch (error) {
57
+ if (error instanceof Error) {
58
+ LoggerHelpers.error(`Error listing backups: ${error.message}`);
59
+ }
60
+ else {
61
+ LoggerHelpers.error(`Error listing backups: ${error}`);
62
+ }
63
+ process.exit(1);
64
+ }
65
+ }
66
+ /**
67
+ * Restores a specific backup by index
68
+ */
69
+ export async function rollbackRestore(index) {
70
+ try {
71
+ const backups = findAllBackups(process.cwd());
72
+ if (index < 1 || index > backups.length) {
73
+ LoggerHelpers.error(`Invalid backup index: ${index}`);
74
+ LoggerHelpers.info(`Please choose a number between 1 and ${backups.length}`);
75
+ process.exit(1);
76
+ }
77
+ const backup = backups[index - 1];
78
+ const originalPath = getOriginalFilePath(backup.backupPath);
79
+ LoggerHelpers.info(`Restoring: ${originalPath}`);
80
+ LoggerHelpers.info(`From backup: ${backup.timestamp.toLocaleString()}`);
81
+ const success = restoreBackup(originalPath, backup.backupPath);
82
+ if (success) {
83
+ LoggerHelpers.success("Backup restored successfully!");
84
+ }
85
+ else {
86
+ LoggerHelpers.error("Failed to restore backup.");
87
+ process.exit(1);
88
+ }
89
+ }
90
+ catch (error) {
91
+ if (error instanceof Error) {
92
+ LoggerHelpers.error(`Error restoring backup: ${error.message}`);
93
+ }
94
+ else {
95
+ LoggerHelpers.error(`Error restoring backup: ${error}`);
96
+ }
97
+ process.exit(1);
98
+ }
99
+ }
100
+ /**
101
+ * Recursively finds all backup files in a directory
102
+ */
103
+ function findAllBackups(dir) {
104
+ const backups = [];
105
+ function searchDirectory(currentDir) {
106
+ const entries = fs.readdirSync(currentDir, { withFileTypes: true });
107
+ for (const entry of entries) {
108
+ const fullPath = path.join(currentDir, entry.name);
109
+ if (entry.isDirectory()) {
110
+ if (entry.name === ".optikit-backup") {
111
+ // Found a backup directory
112
+ const backupFiles = fs.readdirSync(fullPath);
113
+ for (const backupFile of backupFiles) {
114
+ const backupPath = path.join(fullPath, backupFile);
115
+ const stats = fs.statSync(backupPath);
116
+ backups.push({
117
+ backupPath,
118
+ timestamp: stats.mtime,
119
+ size: stats.size,
120
+ });
121
+ }
122
+ }
123
+ else if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
124
+ // Recursively search subdirectories
125
+ searchDirectory(fullPath);
126
+ }
127
+ }
128
+ }
129
+ }
130
+ searchDirectory(dir);
131
+ return backups;
132
+ }
133
+ /**
134
+ * Extracts the original file path from a backup path
135
+ */
136
+ function getOriginalFilePath(backupPath) {
137
+ const backupDir = path.dirname(backupPath);
138
+ const originalDir = path.dirname(backupDir);
139
+ const backupFileName = path.basename(backupPath);
140
+ // Remove timestamp from filename
141
+ // Format: filename_YYYY-MM-DDTHH-MM-SS-mmmZ.ext
142
+ const match = backupFileName.match(/^(.+)_\d{4}-\d{2}-\d{2}T[\d-]+Z(\.\w+)$/);
143
+ if (match) {
144
+ const [, baseName, extension] = match;
145
+ return path.join(originalDir, `${baseName}${extension}`);
146
+ }
147
+ return path.join(originalDir, backupFileName);
148
+ }
149
+ /**
150
+ * Gets human-readable time ago string
151
+ */
152
+ function getTimeAgo(date) {
153
+ const seconds = Math.floor((new Date().getTime() - date.getTime()) / 1000);
154
+ if (seconds < 60)
155
+ return `${seconds}s ago`;
156
+ if (seconds < 3600)
157
+ return `${Math.floor(seconds / 60)}m ago`;
158
+ if (seconds < 86400)
159
+ return `${Math.floor(seconds / 3600)}h ago`;
160
+ return `${Math.floor(seconds / 86400)}d ago`;
161
+ }
@@ -1,19 +1,47 @@
1
+ import fs from "fs";
1
2
  import path from "path";
2
3
  import { createDirectories, writeFile, getClassName, } from "../utils/fileHelpers.js";
3
4
  import { LoggerHelpers } from "../utils/loggerHelpers.js";
4
5
  export { generateModule };
5
6
  function generateModule(moduleName) {
6
- const modulePath = path.join("lib", "module", moduleName);
7
- const directories = ["bloc", "event", "state", "screen", "import", "factory"];
8
- createDirectories(modulePath, directories);
9
- LoggerHelpers.info(`Creating module structure for ${moduleName}...`);
10
- generateBloc(moduleName, path.join(modulePath, "bloc"));
11
- generateEvent(moduleName, path.join(modulePath, "event"));
12
- generateState(moduleName, path.join(modulePath, "state"));
13
- generateScreen(moduleName, path.join(modulePath, "screen"));
14
- generateImport(moduleName, path.join(modulePath, "import"));
15
- generateStateFactory(moduleName, path.join(modulePath, "factory"));
16
- LoggerHelpers.success(`Module ${moduleName} created with full structure.`);
7
+ try {
8
+ // Validate module name
9
+ if (!moduleName || moduleName.trim().length === 0) {
10
+ LoggerHelpers.error("Module name cannot be empty.");
11
+ process.exit(1);
12
+ }
13
+ // Validate module name format (only lowercase letters, numbers, and underscores)
14
+ const validNamePattern = /^[a-z0-9_]+$/;
15
+ if (!validNamePattern.test(moduleName)) {
16
+ LoggerHelpers.error("Module name must contain only lowercase letters, numbers, and underscores.");
17
+ process.exit(1);
18
+ }
19
+ const modulePath = path.join("lib", "module", moduleName);
20
+ const directories = ["bloc", "event", "state", "screen", "import", "factory"];
21
+ // Check if module already exists
22
+ if (fs.existsSync(modulePath)) {
23
+ LoggerHelpers.warning(`Module ${moduleName} already exists at ${modulePath}`);
24
+ LoggerHelpers.info("Files will be overwritten...");
25
+ }
26
+ createDirectories(modulePath, directories);
27
+ LoggerHelpers.info(`Creating module structure for ${moduleName}...`);
28
+ generateBloc(moduleName, path.join(modulePath, "bloc"));
29
+ generateEvent(moduleName, path.join(modulePath, "event"));
30
+ generateState(moduleName, path.join(modulePath, "state"));
31
+ generateScreen(moduleName, path.join(modulePath, "screen"));
32
+ generateImport(moduleName, path.join(modulePath, "import"));
33
+ generateStateFactory(moduleName, path.join(modulePath, "factory"));
34
+ LoggerHelpers.success(`Module ${moduleName} created with full structure.`);
35
+ }
36
+ catch (error) {
37
+ if (error instanceof Error) {
38
+ LoggerHelpers.error(`Error generating module: ${error.message}`);
39
+ }
40
+ else {
41
+ LoggerHelpers.error(`Error generating module: ${error}`);
42
+ }
43
+ process.exit(1);
44
+ }
17
45
  }
18
46
  function generateBloc(moduleName, blocPath) {
19
47
  const blocFilePath = path.join(blocPath, `${moduleName}_bloc.dart`);