optikit 1.2.5 → 1.3.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/.claude/commands/build.md +57 -0
- package/.claude/commands/clean.md +45 -0
- package/.claude/commands/generate.md +64 -0
- package/.claude/commands/rollback.md +67 -0
- package/.claude/commands/run.md +63 -0
- package/.claude/commands/setup.md +69 -0
- package/.claude/commands/sync-docs.md +148 -0
- package/.claude/commands/version.md +63 -0
- package/.claude/settings.local.json +28 -0
- package/.claude-plugin/marketplace.json +36 -0
- package/.claude-plugin/plugin.json +25 -0
- package/.history/README_20260325211923.md +268 -0
- package/.history/README_20260325212116.md +268 -0
- package/.history/README_20260325212234.md +266 -0
- package/.history/README_20260325221838.md +321 -0
- package/.history/README_20260325221920.md +327 -0
- package/.history/README_20260325222245.md +328 -0
- package/.history/README_20260325222247.md +328 -0
- package/.mcp.json +8 -0
- package/CHANGELOG.md +67 -98
- package/CLAUDE.md +57 -206
- package/OPTIKIT_AGENT.md +398 -0
- package/README.md +293 -60
- package/dist/cli.js +75 -244
- package/dist/commands/build/commands.js +146 -0
- package/dist/commands/build/testflight.js +14 -0
- package/dist/commands/clean/commands.js +41 -0
- package/dist/commands/clean/flutter.js +8 -14
- package/dist/commands/clean/ios.js +12 -15
- package/dist/commands/config/aliases.js +122 -0
- package/dist/commands/config/commands.js +49 -0
- package/dist/commands/config/initApp.js +191 -0
- package/dist/commands/config/rollback.js +15 -4
- package/dist/commands/config/upgrade.js +36 -0
- package/dist/commands/mcp/commands.js +21 -0
- package/dist/commands/mcp/server.js +27 -0
- package/dist/commands/mcp/setup.js +62 -0
- package/dist/commands/mcp/tools.js +359 -0
- package/dist/commands/project/commands.js +132 -0
- package/dist/commands/project/devices.js +10 -26
- package/dist/commands/project/doctor.js +58 -0
- package/dist/commands/project/generate.js +183 -30
- package/dist/commands/project/setup.js +10 -28
- package/dist/commands/project/status.js +65 -0
- package/dist/commands/version/bump.js +75 -101
- package/dist/commands/version/commands.js +63 -0
- package/dist/commands/version/update.js +36 -24
- package/dist/constants.js +6 -1
- package/dist/styles.js +42 -5
- package/dist/utils/helpers/error.js +14 -0
- package/dist/utils/helpers/file.js +1 -1
- package/dist/utils/helpers/version.js +2 -1
- package/dist/utils/services/backup.js +12 -1
- package/dist/utils/services/command.js +1 -34
- package/dist/utils/services/exec.js +76 -101
- package/dist/utils/services/logger.js +10 -4
- package/dist/utils/validators/validation.js +24 -12
- package/docs/INSTALLATION.md +72 -0
- package/docs/TROUBLESHOOT.md +140 -0
- package/docs/USAGE.md +185 -0
- package/docs/VERSION_MANAGEMENT.md +177 -0
- package/package.json +7 -11
- package/src/cli.ts +82 -371
- package/src/commands/build/commands.ts +169 -0
- package/src/commands/build/testflight.ts +18 -0
- package/src/commands/clean/commands.ts +43 -0
- package/src/commands/clean/flutter.ts +9 -13
- package/src/commands/clean/ios.ts +13 -13
- package/src/commands/config/aliases.ts +150 -0
- package/src/commands/config/commands.ts +50 -0
- package/src/commands/config/initApp.ts +213 -0
- package/src/commands/config/rollback.ts +16 -4
- package/src/commands/config/upgrade.ts +40 -0
- package/src/commands/mcp/commands.ts +23 -0
- package/src/commands/mcp/server.ts +35 -0
- package/src/commands/mcp/setup.ts +69 -0
- package/src/commands/mcp/tools.ts +365 -0
- package/src/commands/project/commands.ts +132 -0
- package/src/commands/project/devices.ts +11 -24
- package/src/commands/project/doctor.ts +81 -0
- package/src/commands/project/generate.ts +211 -32
- package/src/commands/project/setup.ts +13 -30
- package/src/commands/project/status.ts +72 -0
- package/src/commands/version/bump.ts +98 -110
- package/src/commands/version/commands.ts +76 -0
- package/src/commands/version/update.ts +86 -75
- package/src/constants.ts +7 -1
- package/src/styles.ts +49 -7
- package/src/utils/helpers/error.ts +16 -0
- package/src/utils/helpers/file.ts +1 -1
- package/src/utils/helpers/version.ts +2 -1
- package/src/utils/services/backup.ts +17 -1
- package/src/utils/services/command.ts +1 -58
- package/src/utils/services/exec.ts +92 -117
- package/src/utils/services/logger.ts +12 -4
- package/src/utils/validators/validation.ts +24 -12
- package/CODE_QUALITY.md +0 -398
- package/ENHANCEMENTS.md +0 -310
- package/FEATURE_ENHANCEMENTS.md +0 -435
- package/INSTALLATION.md +0 -118
- package/SAFETY_FEATURES.md +0 -396
- package/TROUBLESHOOT.md +0 -60
- package/USAGE.md +0 -412
- package/VERSION_MANAGEMENT.md +0 -438
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { promisify } from "util";
|
|
3
|
-
import { execCommand } from "../../utils/services/exec.js";
|
|
1
|
+
import { execCommand, execCommandSilent } from "../../utils/services/exec.js";
|
|
4
2
|
import { LoggerHelpers } from "../../utils/services/logger.js";
|
|
5
3
|
import { validateFlutterProject, validateFlutterSdk } from "../../utils/validators/validation.js";
|
|
4
|
+
import { handleCommandError } from "../../utils/helpers/error.js";
|
|
6
5
|
import chalk from "chalk";
|
|
7
6
|
|
|
8
|
-
const execAsync = promisify(exec);
|
|
9
|
-
|
|
10
7
|
export { listDevices, runApp, getDevicesList, runAppInteractive };
|
|
11
8
|
|
|
12
9
|
/**
|
|
@@ -26,7 +23,7 @@ async function getDevicesList(useFvm: boolean = false): Promise<DeviceInfo[]> {
|
|
|
26
23
|
const flutterCommand = useFvm ? "fvm flutter" : "flutter";
|
|
27
24
|
|
|
28
25
|
try {
|
|
29
|
-
const
|
|
26
|
+
const stdout = await execCommandSilent(`${flutterCommand} devices --machine`);
|
|
30
27
|
const devices = JSON.parse(stdout) as Array<{
|
|
31
28
|
id: string;
|
|
32
29
|
name: string;
|
|
@@ -95,12 +92,7 @@ async function listDevices(useFvm: boolean = false): Promise<void> {
|
|
|
95
92
|
console.log(chalk.gray("═".repeat(60) + "\n"));
|
|
96
93
|
|
|
97
94
|
} catch (error) {
|
|
98
|
-
|
|
99
|
-
LoggerHelpers.error(`Error listing devices: ${error.message}`);
|
|
100
|
-
} else {
|
|
101
|
-
LoggerHelpers.error(`Error listing devices: ${error}`);
|
|
102
|
-
}
|
|
103
|
-
process.exit(1);
|
|
95
|
+
handleCommandError(error, "Error listing devices");
|
|
104
96
|
}
|
|
105
97
|
}
|
|
106
98
|
|
|
@@ -159,12 +151,7 @@ async function runApp(config: RunConfig): Promise<void> {
|
|
|
159
151
|
await execCommand(command);
|
|
160
152
|
|
|
161
153
|
} catch (error) {
|
|
162
|
-
|
|
163
|
-
LoggerHelpers.error(`Error running app: ${error.message}`);
|
|
164
|
-
} else {
|
|
165
|
-
LoggerHelpers.error(`Error running app: ${error}`);
|
|
166
|
-
}
|
|
167
|
-
process.exit(1);
|
|
154
|
+
handleCommandError(error, "Error running app");
|
|
168
155
|
}
|
|
169
156
|
}
|
|
170
157
|
|
|
@@ -214,6 +201,11 @@ async function runAppInteractive(config: Omit<RunConfig, 'device'>): Promise<voi
|
|
|
214
201
|
output: process.stdout
|
|
215
202
|
});
|
|
216
203
|
|
|
204
|
+
rl.on('error', () => {
|
|
205
|
+
LoggerHelpers.error("Input error. Please try again.");
|
|
206
|
+
process.exit(1);
|
|
207
|
+
});
|
|
208
|
+
|
|
217
209
|
rl.question(chalk.yellow("Device number: "), async (answer: string) => {
|
|
218
210
|
rl.close();
|
|
219
211
|
|
|
@@ -236,11 +228,6 @@ async function runAppInteractive(config: Omit<RunConfig, 'device'>): Promise<voi
|
|
|
236
228
|
});
|
|
237
229
|
|
|
238
230
|
} catch (error) {
|
|
239
|
-
|
|
240
|
-
LoggerHelpers.error(`Error: ${error.message}`);
|
|
241
|
-
} else {
|
|
242
|
-
LoggerHelpers.error(`Error: ${error}`);
|
|
243
|
-
}
|
|
244
|
-
process.exit(1);
|
|
231
|
+
handleCommandError(error, "Error selecting device");
|
|
245
232
|
}
|
|
246
233
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { execCommandSilent } from "../../utils/services/exec.js";
|
|
3
|
+
import { validateFlutterProject, validateIosProject, validateAndroidProject } from "../../utils/validators/validation.js";
|
|
4
|
+
import { getConfigPath } from "../../utils/services/config.js";
|
|
5
|
+
import { HELP_URLS } from "../../constants.js";
|
|
6
|
+
import { sectionHeader } from "../../styles.js";
|
|
7
|
+
|
|
8
|
+
export { runDoctor };
|
|
9
|
+
|
|
10
|
+
interface CheckResult {
|
|
11
|
+
name: string;
|
|
12
|
+
passed: boolean;
|
|
13
|
+
version?: string;
|
|
14
|
+
fix?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function checkCommand(
|
|
18
|
+
name: string,
|
|
19
|
+
command: string,
|
|
20
|
+
fix: string
|
|
21
|
+
): Promise<CheckResult> {
|
|
22
|
+
try {
|
|
23
|
+
const output = await execCommandSilent(command);
|
|
24
|
+
const version = output.trim().split("\n")[0];
|
|
25
|
+
return { name, passed: true, version };
|
|
26
|
+
} catch {
|
|
27
|
+
return { name, passed: false, fix };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function runDoctor(): Promise<void> {
|
|
32
|
+
sectionHeader("OptiKit Doctor");
|
|
33
|
+
|
|
34
|
+
const results: CheckResult[] = [];
|
|
35
|
+
|
|
36
|
+
// Tool checks (run in parallel)
|
|
37
|
+
const toolChecks = await Promise.all([
|
|
38
|
+
checkCommand("Flutter SDK", "flutter --version", `Install Flutter: ${HELP_URLS.FLUTTER_INSTALL}`),
|
|
39
|
+
checkCommand("FVM", "fvm --version", `Install FVM: ${HELP_URLS.FVM_INSTALL} (optional)`),
|
|
40
|
+
checkCommand("CocoaPods", "pod --version", "Install: gem install cocoapods"),
|
|
41
|
+
checkCommand("Xcode CLI Tools", "xcode-select -p", "Install: xcode-select --install"),
|
|
42
|
+
]);
|
|
43
|
+
results.push(...toolChecks);
|
|
44
|
+
|
|
45
|
+
// Project checks
|
|
46
|
+
results.push(
|
|
47
|
+
{ name: "Flutter project", passed: validateFlutterProject(true), fix: "Run from Flutter project root" },
|
|
48
|
+
{ name: "iOS project", passed: validateIosProject(true), fix: "Run: flutter create ." },
|
|
49
|
+
{ name: "Android project", passed: validateAndroidProject(true), fix: "Run: flutter create ." },
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
const configPath = getConfigPath();
|
|
53
|
+
results.push({
|
|
54
|
+
name: "OptiKit config",
|
|
55
|
+
passed: configPath !== null,
|
|
56
|
+
version: configPath ?? undefined,
|
|
57
|
+
fix: "Run: optikit init",
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Display results
|
|
61
|
+
console.log();
|
|
62
|
+
let passCount = 0;
|
|
63
|
+
|
|
64
|
+
for (const result of results) {
|
|
65
|
+
if (result.passed) {
|
|
66
|
+
passCount++;
|
|
67
|
+
const ver = result.version ? chalk.gray(` ${result.version}`) : "";
|
|
68
|
+
console.log(chalk.green(" ✔"), chalk.white(result.name) + ver);
|
|
69
|
+
} else {
|
|
70
|
+
console.log(chalk.red(" ✖"), chalk.white(result.name));
|
|
71
|
+
if (result.fix) {
|
|
72
|
+
console.log(chalk.gray(" └─"), chalk.yellow(result.fix));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const total = results.length;
|
|
78
|
+
const color = passCount === total ? chalk.green : passCount >= total - 2 ? chalk.yellow : chalk.red;
|
|
79
|
+
console.log(chalk.gray("\n ─────────────────────────────────"));
|
|
80
|
+
console.log(color(` ${passCount}/${total} checks passed\n`));
|
|
81
|
+
}
|
|
@@ -6,31 +6,30 @@ import {
|
|
|
6
6
|
getClassName,
|
|
7
7
|
} from "../../utils/helpers/file.js";
|
|
8
8
|
import { LoggerHelpers } from "../../utils/services/logger.js";
|
|
9
|
+
import { handleCommandError } from "../../utils/helpers/error.js";
|
|
10
|
+
import { ERROR_MESSAGES, MODULE_STRUCTURE } from "../../constants.js";
|
|
9
11
|
|
|
10
|
-
export { generateModule };
|
|
12
|
+
export { generateModule, generateRepoModule, addRoute };
|
|
11
13
|
|
|
12
14
|
function generateModule(moduleName: string) {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
15
|
+
// Validate module name
|
|
16
|
+
if (!moduleName || moduleName.trim().length === 0) {
|
|
17
|
+
LoggerHelpers.error(ERROR_MESSAGES.MODULE_NAME_EMPTY);
|
|
18
|
+
process.exit(1);
|
|
19
|
+
}
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
"Module name must contain only lowercase letters, numbers, and underscores."
|
|
25
|
-
);
|
|
26
|
-
process.exit(1);
|
|
27
|
-
}
|
|
21
|
+
if (!MODULE_STRUCTURE.NAME_PATTERN.test(moduleName)) {
|
|
22
|
+
LoggerHelpers.error(ERROR_MESSAGES.MODULE_NAME_INVALID);
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
28
25
|
|
|
29
|
-
|
|
30
|
-
|
|
26
|
+
const modulePath = path.join("lib", "module", moduleName);
|
|
27
|
+
const isNew = !fs.existsSync(modulePath);
|
|
31
28
|
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
try {
|
|
30
|
+
const directories = [...MODULE_STRUCTURE.DIRECTORIES];
|
|
31
|
+
|
|
32
|
+
if (!isNew) {
|
|
34
33
|
LoggerHelpers.warning(`Module ${moduleName} already exists at ${modulePath}`);
|
|
35
34
|
LoggerHelpers.info("Files will be overwritten...");
|
|
36
35
|
}
|
|
@@ -45,29 +44,43 @@ function generateModule(moduleName: string) {
|
|
|
45
44
|
generateScreen(moduleName, path.join(modulePath, "screen"));
|
|
46
45
|
generateImport(moduleName, path.join(modulePath, "import"));
|
|
47
46
|
generateStateFactory(moduleName, path.join(modulePath, "factory"));
|
|
47
|
+
generateRepo(moduleName, path.join(modulePath, "repo"));
|
|
48
48
|
|
|
49
49
|
LoggerHelpers.success(`Module ${moduleName} created with full structure.`);
|
|
50
50
|
} catch (error) {
|
|
51
|
-
if
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
// Clean up only if we created a new module directory
|
|
52
|
+
if (isNew) {
|
|
53
|
+
if (fs.existsSync(modulePath)) {
|
|
54
|
+
try {
|
|
55
|
+
fs.rmSync(modulePath, { recursive: true });
|
|
56
|
+
LoggerHelpers.warning("Cleaned up partial module files.");
|
|
57
|
+
} catch { /* ignore cleanup errors */ }
|
|
58
|
+
}
|
|
55
59
|
}
|
|
56
|
-
|
|
60
|
+
handleCommandError(error, "Error generating module");
|
|
57
61
|
}
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
function generateBloc(moduleName: string, blocPath: string) {
|
|
61
65
|
const blocFilePath = path.join(blocPath, `${moduleName}_bloc.dart`);
|
|
62
|
-
const className = getClassName(moduleName
|
|
66
|
+
const className = getClassName(moduleName);
|
|
63
67
|
|
|
64
68
|
const template = `part of '../import/${moduleName}_import.dart';
|
|
65
69
|
|
|
66
70
|
class ${className}Bloc extends BaseBloc {
|
|
67
71
|
${className}Bloc() : super(
|
|
68
|
-
${className}Factory(),
|
|
72
|
+
${className}Factory(),
|
|
69
73
|
initialState: ${className}InitialState(),
|
|
70
|
-
) {
|
|
74
|
+
) {
|
|
75
|
+
on<${className}InitialEvent>(_onInitialEvent);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
Future<void> _onInitialEvent(
|
|
79
|
+
${className}InitialEvent event,
|
|
80
|
+
Emitter<BaseState> emit,
|
|
81
|
+
) async {
|
|
82
|
+
// Handle initial event
|
|
83
|
+
}
|
|
71
84
|
|
|
72
85
|
@override
|
|
73
86
|
void onDispose() {}
|
|
@@ -82,7 +95,7 @@ class ${className}Bloc extends BaseBloc {
|
|
|
82
95
|
|
|
83
96
|
function generateEvent(moduleName: string, eventPath: string) {
|
|
84
97
|
const eventFilePath = path.join(eventPath, `${moduleName}_event.dart`);
|
|
85
|
-
const className = getClassName(moduleName
|
|
98
|
+
const className = getClassName(moduleName);
|
|
86
99
|
|
|
87
100
|
const template = `part of '../import/${moduleName}_import.dart';
|
|
88
101
|
|
|
@@ -97,7 +110,7 @@ class ${className}InitialEvent extends BaseEvent {}
|
|
|
97
110
|
|
|
98
111
|
function generateState(moduleName: string, statePath: string) {
|
|
99
112
|
const stateFilePath = path.join(statePath, `${moduleName}_state.dart`);
|
|
100
|
-
const className = getClassName(moduleName
|
|
113
|
+
const className = getClassName(moduleName);
|
|
101
114
|
|
|
102
115
|
const template = `part of '../import/${moduleName}_import.dart';
|
|
103
116
|
|
|
@@ -114,7 +127,7 @@ class ${className}InitialState extends RenderDataState {
|
|
|
114
127
|
|
|
115
128
|
function generateScreen(moduleName: string, screenPath: string) {
|
|
116
129
|
const screenFilePath = path.join(screenPath, `${moduleName}_screen.dart`);
|
|
117
|
-
const className = getClassName(moduleName
|
|
130
|
+
const className = getClassName(moduleName);
|
|
118
131
|
|
|
119
132
|
const template = `part of '../import/${moduleName}_import.dart';
|
|
120
133
|
|
|
@@ -147,7 +160,7 @@ class _${className}ScreenState extends BaseScreen<${className}Bloc, ${className}
|
|
|
147
160
|
|
|
148
161
|
function generateImport(moduleName: string, importPath: string) {
|
|
149
162
|
const importFilePath = path.join(importPath, `${moduleName}_import.dart`);
|
|
150
|
-
const className = getClassName(moduleName
|
|
163
|
+
const className = getClassName(moduleName);
|
|
151
164
|
|
|
152
165
|
const template = `import 'package:flutter/material.dart';
|
|
153
166
|
import 'package:opticore/opticore.dart';
|
|
@@ -157,6 +170,7 @@ part '../event/${moduleName}_event.dart';
|
|
|
157
170
|
part '../screen/${moduleName}_screen.dart';
|
|
158
171
|
part '../state/${moduleName}_state.dart';
|
|
159
172
|
part '../factory/${moduleName}_factory.dart';
|
|
173
|
+
part '../repo/${moduleName}_repo.dart';
|
|
160
174
|
`;
|
|
161
175
|
|
|
162
176
|
writeFile(importFilePath, template);
|
|
@@ -170,7 +184,7 @@ function generateStateFactory(moduleName: string, factoryPath: string) {
|
|
|
170
184
|
factoryPath,
|
|
171
185
|
`${moduleName}_factory.dart`
|
|
172
186
|
);
|
|
173
|
-
const className = getClassName(moduleName
|
|
187
|
+
const className = getClassName(moduleName);
|
|
174
188
|
|
|
175
189
|
const template = `part of '../import/${moduleName}_import.dart';
|
|
176
190
|
|
|
@@ -187,3 +201,168 @@ class ${className}Factory extends BaseFactory {
|
|
|
187
201
|
`State Factory file ${moduleName}_factory.dart created in ${factoryPath}`
|
|
188
202
|
);
|
|
189
203
|
}
|
|
204
|
+
|
|
205
|
+
function generateRepo(moduleName: string, repoPath: string) {
|
|
206
|
+
const repoFilePath = path.join(repoPath, `${moduleName}_repo.dart`);
|
|
207
|
+
const className = getClassName(moduleName);
|
|
208
|
+
|
|
209
|
+
const template = `part of '../import/${moduleName}_import.dart';
|
|
210
|
+
|
|
211
|
+
class ${className}Repo extends BaseRepo {
|
|
212
|
+
|
|
213
|
+
Future<ApiResponse<Map<String, dynamic>?>> fetchData() async {
|
|
214
|
+
return await networkHelper.request<Map<String, dynamic>>(
|
|
215
|
+
'/${moduleName.replace(/_/g, '-')}',
|
|
216
|
+
(json) => json ?? {},
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
`;
|
|
221
|
+
|
|
222
|
+
writeFile(repoFilePath, template);
|
|
223
|
+
LoggerHelpers.success(
|
|
224
|
+
`Repo file ${moduleName}_repo.dart created in ${repoPath}`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Generates a repository for an existing module.
|
|
230
|
+
* Creates repo directory, generates repo file, and appends part directive to import file.
|
|
231
|
+
*/
|
|
232
|
+
function generateRepoModule(moduleName: string) {
|
|
233
|
+
try {
|
|
234
|
+
// Validate module name
|
|
235
|
+
if (!moduleName || moduleName.trim().length === 0) {
|
|
236
|
+
LoggerHelpers.error(ERROR_MESSAGES.MODULE_NAME_EMPTY);
|
|
237
|
+
process.exit(1);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (!MODULE_STRUCTURE.NAME_PATTERN.test(moduleName)) {
|
|
241
|
+
LoggerHelpers.error(ERROR_MESSAGES.MODULE_NAME_INVALID);
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const modulePath = path.join("lib", "module", moduleName);
|
|
246
|
+
|
|
247
|
+
// Verify module exists
|
|
248
|
+
if (!fs.existsSync(modulePath)) {
|
|
249
|
+
LoggerHelpers.error(`Module ${moduleName} does not exist at ${modulePath}. Create it first with: optikit generate module ${moduleName}`);
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Create repo directory
|
|
254
|
+
const repoPath = path.join(modulePath, "repo");
|
|
255
|
+
if (!fs.existsSync(repoPath)) {
|
|
256
|
+
fs.mkdirSync(repoPath, { recursive: true });
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
LoggerHelpers.info(`Adding repository to module ${moduleName}...`);
|
|
260
|
+
generateRepo(moduleName, repoPath);
|
|
261
|
+
|
|
262
|
+
// Append part directive to import file if not already present
|
|
263
|
+
const importFilePath = path.join(modulePath, "import", `${moduleName}_import.dart`);
|
|
264
|
+
if (fs.existsSync(importFilePath)) {
|
|
265
|
+
const importContent = fs.readFileSync(importFilePath, "utf8");
|
|
266
|
+
const repoPartLine = `part '../repo/${moduleName}_repo.dart';`;
|
|
267
|
+
|
|
268
|
+
if (!importContent.includes(repoPartLine)) {
|
|
269
|
+
const updatedContent = importContent.trimEnd() + `\n${repoPartLine}\n`;
|
|
270
|
+
writeFile(importFilePath, updatedContent);
|
|
271
|
+
LoggerHelpers.success(`Added repo part directive to ${moduleName}_import.dart`);
|
|
272
|
+
} else {
|
|
273
|
+
LoggerHelpers.info("Repo part directive already exists in import file.");
|
|
274
|
+
}
|
|
275
|
+
} else {
|
|
276
|
+
LoggerHelpers.warning(`Import file not found at ${importFilePath}. Add the part directive manually.`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
LoggerHelpers.success(`Repository added to module ${moduleName}.`);
|
|
280
|
+
} catch (error) {
|
|
281
|
+
handleCommandError(error, "Error generating repo");
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Adds a route entry for a module to the app_router.dart file.
|
|
287
|
+
* Looks for the OPTIKIT_ROUTE_ANCHOR comment and inserts above it.
|
|
288
|
+
*/
|
|
289
|
+
function addRoute(moduleName: string) {
|
|
290
|
+
try {
|
|
291
|
+
if (!moduleName || moduleName.trim().length === 0) {
|
|
292
|
+
LoggerHelpers.error(ERROR_MESSAGES.MODULE_NAME_EMPTY);
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (!MODULE_STRUCTURE.NAME_PATTERN.test(moduleName)) {
|
|
297
|
+
LoggerHelpers.error(ERROR_MESSAGES.MODULE_NAME_INVALID);
|
|
298
|
+
process.exit(1);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const routerPath = path.join("lib", "config", "app_router.dart");
|
|
302
|
+
|
|
303
|
+
if (!fs.existsSync(routerPath)) {
|
|
304
|
+
LoggerHelpers.error("app_router.dart not found at lib/config/app_router.dart");
|
|
305
|
+
LoggerHelpers.info("Run 'optikit init-app' first to scaffold the app structure.");
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const className = getClassName(moduleName);
|
|
310
|
+
const routeName = moduleName.replace(/_/g, '-');
|
|
311
|
+
const anchor = "// === OPTIKIT_ROUTE_ANCHOR === (do not remove this line)";
|
|
312
|
+
|
|
313
|
+
const routerContent = fs.readFileSync(routerPath, "utf8");
|
|
314
|
+
|
|
315
|
+
// Check if route already exists
|
|
316
|
+
if (routerContent.includes(`'/${routeName}'`)) {
|
|
317
|
+
LoggerHelpers.warning(`Route '/${routeName}' already exists in app_router.dart`);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (!routerContent.includes(anchor)) {
|
|
322
|
+
LoggerHelpers.error("Route anchor not found in app_router.dart. Cannot insert route.");
|
|
323
|
+
LoggerHelpers.info("Ensure the file contains: " + anchor);
|
|
324
|
+
process.exit(1);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Build the import path for the module
|
|
328
|
+
const importPath = `import '../module/${moduleName}/import/${moduleName}_import.dart';`;
|
|
329
|
+
|
|
330
|
+
// Build route case
|
|
331
|
+
const routeCase = ` case '/${routeName}':
|
|
332
|
+
return MaterialPageRoute(
|
|
333
|
+
builder: (_) => BlocProvider(
|
|
334
|
+
create: (_) => ${className}Bloc(),
|
|
335
|
+
child: ${className}Screen(bloc: ${className}Bloc()),
|
|
336
|
+
),
|
|
337
|
+
);`;
|
|
338
|
+
|
|
339
|
+
// Insert route above anchor
|
|
340
|
+
const updatedRouter = routerContent.replace(
|
|
341
|
+
anchor,
|
|
342
|
+
`${routeCase}\n ${anchor}`
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
// Add import if not already present
|
|
346
|
+
let finalContent = updatedRouter;
|
|
347
|
+
if (!finalContent.includes(importPath)) {
|
|
348
|
+
// Insert import after the last existing import line
|
|
349
|
+
const lastImportIndex = finalContent.lastIndexOf("import ");
|
|
350
|
+
if (lastImportIndex !== -1) {
|
|
351
|
+
const endOfImportLine = finalContent.indexOf("\n", lastImportIndex);
|
|
352
|
+
finalContent =
|
|
353
|
+
finalContent.slice(0, endOfImportLine + 1) +
|
|
354
|
+
importPath + "\n" +
|
|
355
|
+
finalContent.slice(endOfImportLine + 1);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
writeFile(routerPath, finalContent);
|
|
360
|
+
|
|
361
|
+
LoggerHelpers.success(`Route '/${routeName}' added for module ${moduleName}`);
|
|
362
|
+
LoggerHelpers.info(` Route: '/${routeName}'`);
|
|
363
|
+
LoggerHelpers.info(` Screen: ${className}Screen`);
|
|
364
|
+
LoggerHelpers.info(` BLoC: ${className}Bloc`);
|
|
365
|
+
} catch (error) {
|
|
366
|
+
handleCommandError(error, "Error adding route");
|
|
367
|
+
}
|
|
368
|
+
}
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { LoggerHelpers } from "../../utils/services/logger.js";
|
|
4
|
+
import { validateFlutterProject } from "../../utils/validators/validation.js";
|
|
5
|
+
import { handleCommandError } from "../../utils/helpers/error.js";
|
|
6
|
+
import { VSCODE_SETTINGS_TEMPLATE, PROJECT_PATHS } from "../../constants.js";
|
|
4
7
|
|
|
5
8
|
export {
|
|
6
9
|
createVscodeSettings,
|
|
7
10
|
};
|
|
8
11
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
async function createVscodeSettings() {
|
|
13
|
+
if (!validateFlutterProject()) {
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
14
17
|
try {
|
|
15
|
-
const vscodeDir = path.join(process.cwd(),
|
|
16
|
-
const settingsPath = path.join(
|
|
18
|
+
const vscodeDir = path.join(process.cwd(), PROJECT_PATHS.VSCODE_DIR);
|
|
19
|
+
const settingsPath = path.join(process.cwd(), PROJECT_PATHS.VSCODE_SETTINGS);
|
|
17
20
|
|
|
18
|
-
// Create the .vscode folder using Node.js fs (cross-platform)
|
|
19
21
|
if (!fs.existsSync(vscodeDir)) {
|
|
20
22
|
fs.mkdirSync(vscodeDir, { recursive: true });
|
|
21
23
|
LoggerHelpers.success("Created .vscode directory.");
|
|
@@ -23,28 +25,9 @@ async function createVscodeSettings(){
|
|
|
23
25
|
LoggerHelpers.info(".vscode directory already exists.");
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
|
|
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");
|
|
28
|
+
fs.writeFileSync(settingsPath, JSON.stringify(VSCODE_SETTINGS_TEMPLATE, null, 2), "utf8");
|
|
41
29
|
LoggerHelpers.success("Created .vscode/settings.json with Flutter configuration.");
|
|
42
30
|
} catch (error) {
|
|
43
|
-
|
|
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);
|
|
31
|
+
handleCommandError(error, "Error creating VSCode settings");
|
|
49
32
|
}
|
|
50
|
-
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { getCurrentVersion, formatVersion, getCurrentIosBuildNumber } from "../../utils/helpers/version.js";
|
|
5
|
+
import { getConfigPath } from "../../utils/services/config.js";
|
|
6
|
+
import { validateFlutterProject } from "../../utils/validators/validation.js";
|
|
7
|
+
import { PROJECT_PATHS, BACKUP_CONFIG } from "../../constants.js";
|
|
8
|
+
|
|
9
|
+
export { showStatus };
|
|
10
|
+
|
|
11
|
+
async function showStatus(): Promise<void> {
|
|
12
|
+
console.log(chalk.bold("\nOptiKit Status"));
|
|
13
|
+
console.log(chalk.gray("─".repeat(45)));
|
|
14
|
+
|
|
15
|
+
// Version info
|
|
16
|
+
if (validateFlutterProject(true)) {
|
|
17
|
+
try {
|
|
18
|
+
const current = getCurrentVersion();
|
|
19
|
+
const iosBuild = getCurrentIosBuildNumber();
|
|
20
|
+
console.log(chalk.cyan("\n Version:"), chalk.white.bold(formatVersion(current)));
|
|
21
|
+
console.log(chalk.gray(" Android Build:"), chalk.white(current.buildNumber));
|
|
22
|
+
console.log(chalk.gray(" iOS Build:"), chalk.white(iosBuild));
|
|
23
|
+
} catch {
|
|
24
|
+
console.log(chalk.yellow("\n Version:"), chalk.gray("Unable to read"));
|
|
25
|
+
}
|
|
26
|
+
} else {
|
|
27
|
+
console.log(chalk.yellow("\n Version:"), chalk.gray("Not a Flutter project"));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// FVM status
|
|
31
|
+
const fvmPath = path.join(process.cwd(), PROJECT_PATHS.FVM_FLUTTER_SDK);
|
|
32
|
+
const useFvm = fs.existsSync(fvmPath);
|
|
33
|
+
console.log(chalk.cyan(" Flutter:"), chalk.white(useFvm ? "FVM" : "Global SDK"));
|
|
34
|
+
|
|
35
|
+
// Backup count
|
|
36
|
+
const backupCount = countBackups(process.cwd());
|
|
37
|
+
console.log(chalk.cyan(" Backups:"), chalk.white(backupCount > 0 ? `${backupCount} file(s)` : "None"));
|
|
38
|
+
|
|
39
|
+
// Config
|
|
40
|
+
const configPath = getConfigPath();
|
|
41
|
+
console.log(chalk.cyan(" Config:"), chalk.white(configPath ?? "Not initialized (run: optikit init)"));
|
|
42
|
+
|
|
43
|
+
// Project structure
|
|
44
|
+
const hasIos = fs.existsSync(path.join(process.cwd(), PROJECT_PATHS.IOS_DIR));
|
|
45
|
+
const hasAndroid = fs.existsSync(path.join(process.cwd(), PROJECT_PATHS.ANDROID_DIR));
|
|
46
|
+
const platforms = [hasIos ? "iOS" : null, hasAndroid ? "Android" : null].filter(Boolean).join(", ");
|
|
47
|
+
console.log(chalk.cyan(" Platforms:"), chalk.white(platforms || "None detected"));
|
|
48
|
+
|
|
49
|
+
console.log(chalk.gray("\n" + "─".repeat(45) + "\n"));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function countBackups(dir: string): number {
|
|
53
|
+
let count = 0;
|
|
54
|
+
|
|
55
|
+
function search(currentDir: string): void {
|
|
56
|
+
try {
|
|
57
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
if (entry.isDirectory()) {
|
|
60
|
+
if (entry.name === BACKUP_CONFIG.DIR_NAME) {
|
|
61
|
+
count += fs.readdirSync(path.join(currentDir, entry.name)).length;
|
|
62
|
+
} else if (!entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
63
|
+
search(path.join(currentDir, entry.name));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} catch { /* skip inaccessible dirs */ }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
search(dir);
|
|
71
|
+
return count;
|
|
72
|
+
}
|