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
|
@@ -1,83 +1,57 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
: "
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
: "
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
: "
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
: "
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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`);
|