optikit 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -2
- package/CLAUDE.md +239 -0
- package/CODE_QUALITY.md +398 -0
- package/ENHANCEMENTS.md +310 -0
- package/FEATURE_ENHANCEMENTS.md +435 -0
- package/README.md +46 -13
- package/SAFETY_FEATURES.md +396 -0
- package/USAGE.md +225 -41
- package/VERSION_MANAGEMENT.md +438 -0
- package/dist/cli.js +116 -7
- package/dist/commands/build/releases.js +57 -0
- package/dist/commands/buildReleases.js +48 -74
- package/dist/commands/clean/flutter.js +51 -0
- package/dist/commands/clean/ios.js +109 -0
- package/dist/commands/cleanProject.js +34 -4
- package/dist/commands/cleanProjectIos.js +17 -7
- package/dist/commands/config/init.js +54 -0
- package/dist/commands/config/rollback.js +161 -0
- package/dist/commands/generateModule.js +39 -11
- package/dist/commands/init.js +54 -0
- package/dist/commands/openProject.js +17 -0
- package/dist/commands/project/devices.js +188 -0
- package/dist/commands/project/generate.js +143 -0
- package/dist/commands/project/open.js +63 -0
- package/dist/commands/project/setup.js +46 -0
- package/dist/commands/rollback.js +161 -0
- package/dist/commands/setupVSCode.js +27 -21
- package/dist/commands/updateVersions.js +13 -1
- package/dist/commands/version/bump.js +161 -0
- package/dist/commands/version/update.js +91 -0
- package/dist/commands/version.js +161 -0
- package/dist/constants.js +131 -0
- package/dist/utils/backupHelpers.js +88 -0
- package/dist/utils/buildHelpers.js +55 -0
- package/dist/utils/commandHelpers.js +51 -0
- package/dist/utils/configHelpers.js +80 -0
- package/dist/utils/dryRunHelpers.js +103 -0
- package/dist/utils/fileHelpers.js +2 -1
- package/dist/utils/helpers/build.js +55 -0
- package/dist/utils/helpers/dryRun.js +103 -0
- package/dist/utils/helpers/file.js +24 -0
- package/dist/utils/helpers/string.js +3 -0
- package/dist/utils/helpers/version.js +80 -0
- package/dist/utils/services/backup.js +88 -0
- package/dist/utils/services/command.js +51 -0
- package/dist/utils/services/config.js +80 -0
- package/dist/utils/services/exec.js +132 -0
- package/dist/utils/services/logger.js +15 -0
- package/dist/utils/validationHelpers.js +101 -0
- package/dist/utils/validators/validation.js +101 -0
- package/dist/utils/versionHelpers.js +80 -0
- package/package.json +1 -1
- package/src/cli.ts +165 -7
- package/src/commands/build/releases.ts +79 -0
- package/src/commands/clean/flutter.ts +58 -0
- package/src/commands/{cleanProjectIos.ts → clean/ios.ts} +19 -10
- package/src/commands/config/init.ts +63 -0
- package/src/commands/config/rollback.ts +200 -0
- package/src/commands/project/devices.ts +246 -0
- package/src/commands/{generateModule.ts → project/generate.ts} +47 -17
- package/src/commands/{openProject.ts → project/open.ts} +26 -5
- package/src/commands/project/setup.ts +50 -0
- package/src/commands/version/bump.ts +202 -0
- package/src/commands/{updateVersions.ts → version/update.ts} +22 -6
- package/src/constants.ts +144 -0
- package/src/utils/helpers/build.ts +80 -0
- package/src/utils/helpers/dryRun.ts +124 -0
- package/src/utils/{fileHelpers.ts → helpers/file.ts} +3 -2
- package/src/utils/helpers/version.ts +109 -0
- package/src/utils/services/backup.ts +109 -0
- package/src/utils/services/command.ts +76 -0
- package/src/utils/services/config.ts +106 -0
- package/src/utils/{execHelpers.ts → services/exec.ts} +1 -1
- package/src/utils/validators/validation.ts +122 -0
- package/src/commands/buildReleases.ts +0 -102
- package/src/commands/cleanProject.ts +0 -25
- package/src/commands/setupVSCode.ts +0 -44
- /package/src/utils/{stringHelpers.ts → helpers/string.ts} +0 -0
- /package/src/utils/{loggerHelpers.ts → services/logger.ts} +0 -0
|
@@ -0,0 +1,63 @@
|
|
|
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
|
+
|
|
6
|
+
export { initializeProject };
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Initializes OptiKit in the current project
|
|
10
|
+
* Creates .optikitrc configuration file with defaults
|
|
11
|
+
*/
|
|
12
|
+
async function initializeProject(): Promise<void> {
|
|
13
|
+
try {
|
|
14
|
+
LoggerHelpers.info("Initializing OptiKit in this project...");
|
|
15
|
+
|
|
16
|
+
const configPath = path.join(process.cwd(), ".optikitrc.json");
|
|
17
|
+
|
|
18
|
+
// Check if config already exists
|
|
19
|
+
if (fs.existsSync(configPath)) {
|
|
20
|
+
LoggerHelpers.warning("OptiKit configuration already exists.");
|
|
21
|
+
LoggerHelpers.info("To reconfigure, delete .optikitrc.json and run init again.");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Create default configuration
|
|
26
|
+
const defaultConfig = {
|
|
27
|
+
backupRetentionCount: 5,
|
|
28
|
+
useFvmByDefault: false,
|
|
29
|
+
autoBackup: true,
|
|
30
|
+
verbose: false,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Save configuration
|
|
34
|
+
const success = saveConfig(defaultConfig);
|
|
35
|
+
|
|
36
|
+
if (success) {
|
|
37
|
+
LoggerHelpers.success("OptiKit initialized successfully!");
|
|
38
|
+
console.log("\nDefault configuration:");
|
|
39
|
+
console.log(JSON.stringify(defaultConfig, null, 2));
|
|
40
|
+
console.log("\nYou can modify .optikitrc.json to customize these settings.\n");
|
|
41
|
+
|
|
42
|
+
// Create .gitignore entry for backups if .gitignore exists
|
|
43
|
+
const gitignorePath = path.join(process.cwd(), ".gitignore");
|
|
44
|
+
if (fs.existsSync(gitignorePath)) {
|
|
45
|
+
const gitignoreContent = fs.readFileSync(gitignorePath, "utf8");
|
|
46
|
+
if (!gitignoreContent.includes(".optikit-backup")) {
|
|
47
|
+
fs.appendFileSync(
|
|
48
|
+
gitignorePath,
|
|
49
|
+
"\n# OptiKit backup files\n.optikit-backup/\n"
|
|
50
|
+
);
|
|
51
|
+
LoggerHelpers.success("Added .optikit-backup/ to .gitignore");
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if (error instanceof Error) {
|
|
57
|
+
LoggerHelpers.error(`Error initializing project: ${error.message}`);
|
|
58
|
+
} else {
|
|
59
|
+
LoggerHelpers.error(`Error initializing project: ${error}`);
|
|
60
|
+
}
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
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
|
+
|
|
7
|
+
export { rollbackFiles };
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Lists all available backups and allows restoration
|
|
11
|
+
*/
|
|
12
|
+
async function rollbackFiles(): Promise<void> {
|
|
13
|
+
try {
|
|
14
|
+
LoggerHelpers.info("Searching for OptiKit backups...");
|
|
15
|
+
|
|
16
|
+
const backups = findAllBackups(process.cwd());
|
|
17
|
+
|
|
18
|
+
if (backups.length === 0) {
|
|
19
|
+
LoggerHelpers.warning("No backups found in this project.");
|
|
20
|
+
LoggerHelpers.info("Backups are created automatically when files are modified.");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log(chalk.bold(`\nFound ${backups.length} backup(s):\n`));
|
|
25
|
+
|
|
26
|
+
// Group backups by original file
|
|
27
|
+
const backupsByFile = new Map<string, Array<{
|
|
28
|
+
backupPath: string;
|
|
29
|
+
timestamp: Date;
|
|
30
|
+
size: number;
|
|
31
|
+
}>>();
|
|
32
|
+
|
|
33
|
+
for (const backup of backups) {
|
|
34
|
+
const originalFile = getOriginalFilePath(backup.backupPath);
|
|
35
|
+
if (!backupsByFile.has(originalFile)) {
|
|
36
|
+
backupsByFile.set(originalFile, []);
|
|
37
|
+
}
|
|
38
|
+
backupsByFile.get(originalFile)!.push(backup);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Display backups grouped by file
|
|
42
|
+
let index = 1;
|
|
43
|
+
const backupList: Array<{
|
|
44
|
+
index: number;
|
|
45
|
+
originalPath: string;
|
|
46
|
+
backupPath: string;
|
|
47
|
+
timestamp: Date;
|
|
48
|
+
}> = [];
|
|
49
|
+
|
|
50
|
+
for (const [originalFile, fileBackups] of backupsByFile) {
|
|
51
|
+
console.log(chalk.cyan.bold(`\n${originalFile}`));
|
|
52
|
+
|
|
53
|
+
// Sort by timestamp (newest first)
|
|
54
|
+
fileBackups.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
|
|
55
|
+
|
|
56
|
+
for (const backup of fileBackups) {
|
|
57
|
+
const timeAgo = getTimeAgo(backup.timestamp);
|
|
58
|
+
const sizeKB = (backup.size / 1024).toFixed(2);
|
|
59
|
+
|
|
60
|
+
console.log(
|
|
61
|
+
chalk.gray(` [${index}]`),
|
|
62
|
+
chalk.white(backup.timestamp.toLocaleString()),
|
|
63
|
+
chalk.gray(`(${timeAgo}, ${sizeKB} KB)`)
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
backupList.push({
|
|
67
|
+
index,
|
|
68
|
+
originalPath: originalFile,
|
|
69
|
+
backupPath: backup.backupPath,
|
|
70
|
+
timestamp: backup.timestamp,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
index++;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
console.log(chalk.yellow("\n" + "=".repeat(60)));
|
|
78
|
+
console.log(chalk.gray("To restore a backup, run:"));
|
|
79
|
+
console.log(chalk.white(" optikit rollback --restore <number>"));
|
|
80
|
+
console.log(chalk.gray("\nExample:"));
|
|
81
|
+
console.log(chalk.white(" optikit rollback --restore 1"));
|
|
82
|
+
console.log(chalk.yellow("=".repeat(60) + "\n"));
|
|
83
|
+
|
|
84
|
+
} catch (error) {
|
|
85
|
+
if (error instanceof Error) {
|
|
86
|
+
LoggerHelpers.error(`Error listing backups: ${error.message}`);
|
|
87
|
+
} else {
|
|
88
|
+
LoggerHelpers.error(`Error listing backups: ${error}`);
|
|
89
|
+
}
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Restores a specific backup by index
|
|
96
|
+
*/
|
|
97
|
+
export async function rollbackRestore(index: number): Promise<void> {
|
|
98
|
+
try {
|
|
99
|
+
const backups = findAllBackups(process.cwd());
|
|
100
|
+
|
|
101
|
+
if (index < 1 || index > backups.length) {
|
|
102
|
+
LoggerHelpers.error(`Invalid backup index: ${index}`);
|
|
103
|
+
LoggerHelpers.info(`Please choose a number between 1 and ${backups.length}`);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const backup = backups[index - 1];
|
|
108
|
+
const originalPath = getOriginalFilePath(backup.backupPath);
|
|
109
|
+
|
|
110
|
+
LoggerHelpers.info(`Restoring: ${originalPath}`);
|
|
111
|
+
LoggerHelpers.info(`From backup: ${backup.timestamp.toLocaleString()}`);
|
|
112
|
+
|
|
113
|
+
const success = restoreBackup(originalPath, backup.backupPath);
|
|
114
|
+
|
|
115
|
+
if (success) {
|
|
116
|
+
LoggerHelpers.success("Backup restored successfully!");
|
|
117
|
+
} else {
|
|
118
|
+
LoggerHelpers.error("Failed to restore backup.");
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (error instanceof Error) {
|
|
123
|
+
LoggerHelpers.error(`Error restoring backup: ${error.message}`);
|
|
124
|
+
} else {
|
|
125
|
+
LoggerHelpers.error(`Error restoring backup: ${error}`);
|
|
126
|
+
}
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Recursively finds all backup files in a directory
|
|
133
|
+
*/
|
|
134
|
+
function findAllBackups(
|
|
135
|
+
dir: string
|
|
136
|
+
): Array<{ backupPath: string; timestamp: Date; size: number }> {
|
|
137
|
+
const backups: Array<{ backupPath: string; timestamp: Date; size: number }> = [];
|
|
138
|
+
|
|
139
|
+
function searchDirectory(currentDir: string): void {
|
|
140
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
141
|
+
|
|
142
|
+
for (const entry of entries) {
|
|
143
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
144
|
+
|
|
145
|
+
if (entry.isDirectory()) {
|
|
146
|
+
if (entry.name === ".optikit-backup") {
|
|
147
|
+
// Found a backup directory
|
|
148
|
+
const backupFiles = fs.readdirSync(fullPath);
|
|
149
|
+
for (const backupFile of backupFiles) {
|
|
150
|
+
const backupPath = path.join(fullPath, backupFile);
|
|
151
|
+
const stats = fs.statSync(backupPath);
|
|
152
|
+
backups.push({
|
|
153
|
+
backupPath,
|
|
154
|
+
timestamp: stats.mtime,
|
|
155
|
+
size: stats.size,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
} else if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
159
|
+
// Recursively search subdirectories
|
|
160
|
+
searchDirectory(fullPath);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
searchDirectory(dir);
|
|
167
|
+
return backups;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Extracts the original file path from a backup path
|
|
172
|
+
*/
|
|
173
|
+
function getOriginalFilePath(backupPath: string): string {
|
|
174
|
+
const backupDir = path.dirname(backupPath);
|
|
175
|
+
const originalDir = path.dirname(backupDir);
|
|
176
|
+
const backupFileName = path.basename(backupPath);
|
|
177
|
+
|
|
178
|
+
// Remove timestamp from filename
|
|
179
|
+
// Format: filename_YYYY-MM-DDTHH-MM-SS-mmmZ.ext
|
|
180
|
+
const match = backupFileName.match(/^(.+)_\d{4}-\d{2}-\d{2}T[\d-]+Z(\.\w+)$/);
|
|
181
|
+
|
|
182
|
+
if (match) {
|
|
183
|
+
const [, baseName, extension] = match;
|
|
184
|
+
return path.join(originalDir, `${baseName}${extension}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return path.join(originalDir, backupFileName);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Gets human-readable time ago string
|
|
192
|
+
*/
|
|
193
|
+
function getTimeAgo(date: Date): string {
|
|
194
|
+
const seconds = Math.floor((new Date().getTime() - date.getTime()) / 1000);
|
|
195
|
+
|
|
196
|
+
if (seconds < 60) return `${seconds}s ago`;
|
|
197
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`;
|
|
198
|
+
if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`;
|
|
199
|
+
return `${Math.floor(seconds / 86400)}d ago`;
|
|
200
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { exec } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
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 chalk from "chalk";
|
|
7
|
+
|
|
8
|
+
const execAsync = promisify(exec);
|
|
9
|
+
|
|
10
|
+
export { listDevices, runApp, getDevicesList, runAppInteractive };
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Device information structure
|
|
14
|
+
*/
|
|
15
|
+
interface DeviceInfo {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
platform: string;
|
|
19
|
+
isEmulator: boolean;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Gets a parsed list of devices
|
|
24
|
+
*/
|
|
25
|
+
async function getDevicesList(useFvm: boolean = false): Promise<DeviceInfo[]> {
|
|
26
|
+
const flutterCommand = useFvm ? "fvm flutter" : "flutter";
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const { stdout } = await execAsync(`${flutterCommand} devices --machine`);
|
|
30
|
+
const devices = JSON.parse(stdout) as Array<{
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
platform: string;
|
|
34
|
+
emulator: boolean;
|
|
35
|
+
}>;
|
|
36
|
+
|
|
37
|
+
return devices.map(device => ({
|
|
38
|
+
id: device.id,
|
|
39
|
+
name: device.name,
|
|
40
|
+
platform: device.platform,
|
|
41
|
+
isEmulator: device.emulator
|
|
42
|
+
}));
|
|
43
|
+
} catch (error) {
|
|
44
|
+
// Fallback to empty array if parsing fails
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Lists all connected devices with numbered list
|
|
51
|
+
*/
|
|
52
|
+
async function listDevices(useFvm: boolean = false): Promise<void> {
|
|
53
|
+
// Pre-flight validation
|
|
54
|
+
if (!validateFlutterProject()) {
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (!(await validateFlutterSdk(!useFvm))) {
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
LoggerHelpers.info("Fetching connected devices...\n");
|
|
64
|
+
|
|
65
|
+
const devices = await getDevicesList(useFvm);
|
|
66
|
+
|
|
67
|
+
if (devices.length === 0) {
|
|
68
|
+
LoggerHelpers.warning("No devices found.");
|
|
69
|
+
console.log(chalk.gray("\nMake sure you have:"));
|
|
70
|
+
console.log(chalk.gray(" - A device connected via USB"));
|
|
71
|
+
console.log(chalk.gray(" - An emulator/simulator running"));
|
|
72
|
+
console.log(chalk.gray(" - Chrome browser for web development\n"));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log(chalk.bold("\n📱 Connected Devices:\n"));
|
|
77
|
+
|
|
78
|
+
devices.forEach((device, index) => {
|
|
79
|
+
const number = chalk.cyan(`[${index + 1}]`);
|
|
80
|
+
const name = chalk.white.bold(device.name);
|
|
81
|
+
const platform = chalk.gray(`(${device.platform})`);
|
|
82
|
+
const type = device.isEmulator ? chalk.yellow(" [Emulator]") : chalk.green(" [Physical]");
|
|
83
|
+
const id = chalk.gray(`ID: ${device.id}`);
|
|
84
|
+
|
|
85
|
+
console.log(`${number} ${name} ${platform}${type}`);
|
|
86
|
+
console.log(` ${id}`);
|
|
87
|
+
console.log();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
console.log(chalk.gray("═".repeat(60)));
|
|
91
|
+
console.log(chalk.gray("To run on a specific device:"));
|
|
92
|
+
console.log(chalk.white(" optikit run --device <device-id>"));
|
|
93
|
+
console.log(chalk.gray("\nOr use interactive selection:"));
|
|
94
|
+
console.log(chalk.white(" optikit run-select"));
|
|
95
|
+
console.log(chalk.gray("═".repeat(60) + "\n"));
|
|
96
|
+
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (error instanceof Error) {
|
|
99
|
+
LoggerHelpers.error(`Error listing devices: ${error.message}`);
|
|
100
|
+
} else {
|
|
101
|
+
LoggerHelpers.error(`Error listing devices: ${error}`);
|
|
102
|
+
}
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Configuration for running the Flutter app
|
|
109
|
+
*/
|
|
110
|
+
interface RunConfig {
|
|
111
|
+
device?: string;
|
|
112
|
+
release?: boolean;
|
|
113
|
+
flavor?: string;
|
|
114
|
+
useFvm?: boolean;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Runs the Flutter app on a connected device
|
|
119
|
+
*/
|
|
120
|
+
async function runApp(config: RunConfig): Promise<void> {
|
|
121
|
+
// Pre-flight validation
|
|
122
|
+
if (!validateFlutterProject()) {
|
|
123
|
+
process.exit(1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!(await validateFlutterSdk(!config.useFvm))) {
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const flutterCommand = config.useFvm ? "fvm flutter" : "flutter";
|
|
132
|
+
|
|
133
|
+
// Build the run command
|
|
134
|
+
let command = `${flutterCommand} run`;
|
|
135
|
+
|
|
136
|
+
// Add device flag if specified
|
|
137
|
+
if (config.device) {
|
|
138
|
+
command += ` --device-id ${config.device}`;
|
|
139
|
+
LoggerHelpers.info(`Running on device: ${config.device}`);
|
|
140
|
+
} else {
|
|
141
|
+
LoggerHelpers.info("Running on default device...");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Add release flag if specified
|
|
145
|
+
if (config.release) {
|
|
146
|
+
command += " --release";
|
|
147
|
+
LoggerHelpers.info("Running in release mode");
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Add flavor flag if specified
|
|
151
|
+
if (config.flavor) {
|
|
152
|
+
command += ` --flavor ${config.flavor}`;
|
|
153
|
+
LoggerHelpers.info(`Running with flavor: ${config.flavor}`);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
console.log(chalk.cyan("\nStarting Flutter app..."));
|
|
157
|
+
console.log(chalk.gray(`Command: ${command}\n`));
|
|
158
|
+
|
|
159
|
+
await execCommand(command);
|
|
160
|
+
|
|
161
|
+
} catch (error) {
|
|
162
|
+
if (error instanceof Error) {
|
|
163
|
+
LoggerHelpers.error(`Error running app: ${error.message}`);
|
|
164
|
+
} else {
|
|
165
|
+
LoggerHelpers.error(`Error running app: ${error}`);
|
|
166
|
+
}
|
|
167
|
+
process.exit(1);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Interactive device selection and run
|
|
173
|
+
*/
|
|
174
|
+
async function runAppInteractive(config: Omit<RunConfig, 'device'>): Promise<void> {
|
|
175
|
+
// Pre-flight validation
|
|
176
|
+
if (!validateFlutterProject()) {
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (!(await validateFlutterSdk(!config.useFvm))) {
|
|
181
|
+
process.exit(1);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
LoggerHelpers.info("Fetching connected devices...\n");
|
|
186
|
+
|
|
187
|
+
const devices = await getDevicesList(config.useFvm);
|
|
188
|
+
|
|
189
|
+
if (devices.length === 0) {
|
|
190
|
+
LoggerHelpers.error("No devices found. Please connect a device or start an emulator.");
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Show devices
|
|
195
|
+
console.log(chalk.bold("\n📱 Connected Devices:\n"));
|
|
196
|
+
|
|
197
|
+
devices.forEach((device, index) => {
|
|
198
|
+
const number = chalk.cyan.bold(`[${index + 1}]`);
|
|
199
|
+
const name = chalk.white.bold(device.name);
|
|
200
|
+
const platform = chalk.gray(`(${device.platform})`);
|
|
201
|
+
const type = device.isEmulator ? chalk.yellow(" [Emulator]") : chalk.green(" [Physical]");
|
|
202
|
+
|
|
203
|
+
console.log(`${number} ${name} ${platform}${type}`);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
console.log(chalk.gray("\n" + "═".repeat(60)));
|
|
207
|
+
console.log(chalk.cyan("Enter device number to run on:"));
|
|
208
|
+
console.log(chalk.gray("═".repeat(60) + "\n"));
|
|
209
|
+
|
|
210
|
+
// Use readline for user input
|
|
211
|
+
const readline = require('readline');
|
|
212
|
+
const rl = readline.createInterface({
|
|
213
|
+
input: process.stdin,
|
|
214
|
+
output: process.stdout
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
rl.question(chalk.yellow("Device number: "), async (answer: string) => {
|
|
218
|
+
rl.close();
|
|
219
|
+
|
|
220
|
+
const deviceIndex = parseInt(answer) - 1;
|
|
221
|
+
|
|
222
|
+
if (isNaN(deviceIndex) || deviceIndex < 0 || deviceIndex >= devices.length) {
|
|
223
|
+
LoggerHelpers.error(`Invalid device number. Please choose between 1 and ${devices.length}`);
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const selectedDevice = devices[deviceIndex];
|
|
228
|
+
|
|
229
|
+
console.log(chalk.green(`\n✓ Selected: ${selectedDevice.name}\n`));
|
|
230
|
+
|
|
231
|
+
// Run on selected device
|
|
232
|
+
await runApp({
|
|
233
|
+
...config,
|
|
234
|
+
device: selectedDevice.id
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
} catch (error) {
|
|
239
|
+
if (error instanceof Error) {
|
|
240
|
+
LoggerHelpers.error(`Error: ${error.message}`);
|
|
241
|
+
} else {
|
|
242
|
+
LoggerHelpers.error(`Error: ${error}`);
|
|
243
|
+
}
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
@@ -4,27 +4,57 @@ import {
|
|
|
4
4
|
createDirectories,
|
|
5
5
|
writeFile,
|
|
6
6
|
getClassName,
|
|
7
|
-
} from "
|
|
8
|
-
import { LoggerHelpers } from "
|
|
7
|
+
} from "../../utils/helpers/file.js";
|
|
8
|
+
import { LoggerHelpers } from "../../utils/services/logger.js";
|
|
9
9
|
|
|
10
10
|
export { generateModule };
|
|
11
11
|
|
|
12
12
|
function generateModule(moduleName: string) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
13
|
+
try {
|
|
14
|
+
// Validate module name
|
|
15
|
+
if (!moduleName || moduleName.trim().length === 0) {
|
|
16
|
+
LoggerHelpers.error("Module name cannot be empty.");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Validate module name format (only lowercase letters, numbers, and underscores)
|
|
21
|
+
const validNamePattern = /^[a-z0-9_]+$/;
|
|
22
|
+
if (!validNamePattern.test(moduleName)) {
|
|
23
|
+
LoggerHelpers.error(
|
|
24
|
+
"Module name must contain only lowercase letters, numbers, and underscores."
|
|
25
|
+
);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const modulePath = path.join("lib", "module", moduleName);
|
|
30
|
+
const directories = ["bloc", "event", "state", "screen", "import", "factory"];
|
|
31
|
+
|
|
32
|
+
// Check if module already exists
|
|
33
|
+
if (fs.existsSync(modulePath)) {
|
|
34
|
+
LoggerHelpers.warning(`Module ${moduleName} already exists at ${modulePath}`);
|
|
35
|
+
LoggerHelpers.info("Files will be overwritten...");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
createDirectories(modulePath, directories);
|
|
39
|
+
|
|
40
|
+
LoggerHelpers.info(`Creating module structure for ${moduleName}...`);
|
|
41
|
+
|
|
42
|
+
generateBloc(moduleName, path.join(modulePath, "bloc"));
|
|
43
|
+
generateEvent(moduleName, path.join(modulePath, "event"));
|
|
44
|
+
generateState(moduleName, path.join(modulePath, "state"));
|
|
45
|
+
generateScreen(moduleName, path.join(modulePath, "screen"));
|
|
46
|
+
generateImport(moduleName, path.join(modulePath, "import"));
|
|
47
|
+
generateStateFactory(moduleName, path.join(modulePath, "factory"));
|
|
48
|
+
|
|
49
|
+
LoggerHelpers.success(`Module ${moduleName} created with full structure.`);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
if (error instanceof Error) {
|
|
52
|
+
LoggerHelpers.error(`Error generating module: ${error.message}`);
|
|
53
|
+
} else {
|
|
54
|
+
LoggerHelpers.error(`Error generating module: ${error}`);
|
|
55
|
+
}
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
28
58
|
}
|
|
29
59
|
|
|
30
60
|
function generateBloc(moduleName: string, blocPath: string) {
|
|
@@ -1,12 +1,22 @@
|
|
|
1
|
-
import { LoggerHelpers } from "
|
|
2
|
-
import { execCommand } from "
|
|
3
|
-
import { platform } from "os";
|
|
1
|
+
import { LoggerHelpers } from "../../utils/services/logger.js";
|
|
2
|
+
import { execCommand } from "../../utils/services/exec.js";
|
|
3
|
+
import { platform } from "os";
|
|
4
|
+
import { validateFlutterProject, validateIosProject, validateAndroidProject } from "../../utils/validators/validation.js";
|
|
4
5
|
|
|
5
6
|
export { openIos, openAndroid };
|
|
6
7
|
|
|
7
8
|
async function openIos() {
|
|
8
9
|
LoggerHelpers.info("Opening the iOS project in Xcode...");
|
|
9
10
|
|
|
11
|
+
// Pre-flight validation
|
|
12
|
+
if (!validateFlutterProject()) {
|
|
13
|
+
process.exit(1);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (!validateIosProject()) {
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
|
|
10
20
|
const command = "open ios/Runner.xcworkspace";
|
|
11
21
|
|
|
12
22
|
try {
|
|
@@ -18,12 +28,22 @@ async function openIos() {
|
|
|
18
28
|
} else {
|
|
19
29
|
LoggerHelpers.error(`Error while opening Xcode: ${error}`);
|
|
20
30
|
}
|
|
31
|
+
process.exit(1);
|
|
21
32
|
}
|
|
22
33
|
}
|
|
23
34
|
|
|
24
35
|
async function openAndroid() {
|
|
25
36
|
LoggerHelpers.info("Opening the Android project in Android Studio...");
|
|
26
37
|
|
|
38
|
+
// Pre-flight validation
|
|
39
|
+
if (!validateFlutterProject()) {
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!validateAndroidProject()) {
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
27
47
|
const osPlatform = platform();
|
|
28
48
|
let command;
|
|
29
49
|
|
|
@@ -33,9 +53,9 @@ async function openAndroid() {
|
|
|
33
53
|
} else if (osPlatform === "darwin") {
|
|
34
54
|
command = "open -a 'Android Studio' android";
|
|
35
55
|
} else {
|
|
36
|
-
command = "xdg-open android";
|
|
56
|
+
command = "xdg-open android";
|
|
37
57
|
}
|
|
38
|
-
|
|
58
|
+
|
|
39
59
|
try {
|
|
40
60
|
await execCommand(command);
|
|
41
61
|
LoggerHelpers.success("Android Studio opened successfully.");
|
|
@@ -47,5 +67,6 @@ async function openAndroid() {
|
|
|
47
67
|
} else {
|
|
48
68
|
LoggerHelpers.error(`Error while opening Android Studio: ${error}`);
|
|
49
69
|
}
|
|
70
|
+
process.exit(1);
|
|
50
71
|
}
|
|
51
72
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { LoggerHelpers } from "../../utils/services/logger.js";
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
createVscodeSettings,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Creates a .vscode directory (if it doesn't exist) and writes a settings.json file
|
|
11
|
+
* with recommended Flutter configuration. The Flutter SDK path is set to ".fvm/flutter_sdk".
|
|
12
|
+
*/
|
|
13
|
+
async function createVscodeSettings(){
|
|
14
|
+
try {
|
|
15
|
+
const vscodeDir = path.join(process.cwd(), ".vscode");
|
|
16
|
+
const settingsPath = path.join(vscodeDir, "settings.json");
|
|
17
|
+
|
|
18
|
+
// Create the .vscode folder using Node.js fs (cross-platform)
|
|
19
|
+
if (!fs.existsSync(vscodeDir)) {
|
|
20
|
+
fs.mkdirSync(vscodeDir, { recursive: true });
|
|
21
|
+
LoggerHelpers.success("Created .vscode directory.");
|
|
22
|
+
} else {
|
|
23
|
+
LoggerHelpers.info(".vscode directory already exists.");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Define the settings object
|
|
27
|
+
const settings = {
|
|
28
|
+
"dart.flutterSdkPath": ".fvm/flutter_sdk",
|
|
29
|
+
"editor.formatOnSave": true,
|
|
30
|
+
"dart.previewFlutterUiGuides": true,
|
|
31
|
+
"files.exclude": {
|
|
32
|
+
"**/.git": true,
|
|
33
|
+
"**/.DS_Store": true,
|
|
34
|
+
"**/node_modules": true,
|
|
35
|
+
"**/build": true
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Write settings.json using Node.js fs
|
|
40
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf8");
|
|
41
|
+
LoggerHelpers.success("Created .vscode/settings.json with Flutter configuration.");
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (error instanceof Error) {
|
|
44
|
+
LoggerHelpers.error(`Error while creating VSCode settings: ${error.message}`);
|
|
45
|
+
} else {
|
|
46
|
+
LoggerHelpers.error(`Error while creating VSCode settings: ${error}`);
|
|
47
|
+
}
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
}
|