kaven-cli 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +93 -0
  3. package/dist/commands/auth/login.js +44 -0
  4. package/dist/commands/auth/logout.js +25 -0
  5. package/dist/commands/auth/whoami.js +35 -0
  6. package/dist/commands/index.js +1 -0
  7. package/dist/commands/marketplace/install.js +59 -0
  8. package/dist/commands/marketplace/list.js +44 -0
  9. package/dist/commands/module/add.js +69 -0
  10. package/dist/commands/module/doctor.js +70 -0
  11. package/dist/commands/module/remove.js +58 -0
  12. package/dist/commands/modules/add.js +53 -0
  13. package/dist/commands/modules/list.js +40 -0
  14. package/dist/commands/modules/remove.js +54 -0
  15. package/dist/commands/telemetry/view.js +27 -0
  16. package/dist/core/AuthService.js +61 -0
  17. package/dist/core/ManifestParser.js +52 -0
  18. package/dist/core/MarkerService.js +62 -0
  19. package/dist/core/ModuleDoctor.js +162 -0
  20. package/dist/core/ModuleInstaller.js +66 -0
  21. package/dist/core/api/KavenApiClient.js +61 -0
  22. package/dist/core/auth/AuthManager.js +91 -0
  23. package/dist/core/index.js +1 -0
  24. package/dist/core/modules/Injector.js +86 -0
  25. package/dist/core/modules/ModuleInstaller.js +63 -0
  26. package/dist/core/modules/ModuleManager.js +59 -0
  27. package/dist/core/modules/ModuleRemover.js +60 -0
  28. package/dist/index.js +91 -0
  29. package/dist/infrastructure/Container.js +39 -0
  30. package/dist/infrastructure/MarketplaceClient.js +73 -0
  31. package/dist/infrastructure/TelemetryBuffer.js +71 -0
  32. package/dist/infrastructure/TransactionalFileSystem.js +74 -0
  33. package/dist/infrastructure/index.js +1 -0
  34. package/dist/lib/config.js +66 -0
  35. package/dist/lib/errors.js +32 -0
  36. package/dist/lib/logger.js +70 -0
  37. package/dist/types/manifest.js +45 -0
  38. package/dist/types/markers.js +10 -0
  39. package/dist/types/module.js +49 -0
  40. package/package.json +64 -0
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.moduleRemove = moduleRemove;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const ora_1 = __importDefault(require("ora"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const fs_extra_1 = __importDefault(require("fs-extra"));
11
+ const ModuleInstaller_1 = require("../../core/ModuleInstaller");
12
+ const TelemetryBuffer_1 = require("../../infrastructure/TelemetryBuffer");
13
+ const MarkerService_1 = require("../../core/MarkerService");
14
+ async function moduleRemove(moduleName, projectRoot) {
15
+ const telemetry = TelemetryBuffer_1.TelemetryBuffer.getInstance();
16
+ const startTime = Date.now();
17
+ telemetry.capture("cli.module.remove.start", { moduleName });
18
+ const root = projectRoot || process.cwd();
19
+ const spinner = (0, ora_1.default)(`Removendo módulo ${moduleName}...`).start();
20
+ try {
21
+ const markerService = new MarkerService_1.MarkerService();
22
+ const installer = new ModuleInstaller_1.ModuleInstaller(root, markerService);
23
+ // 1. Verificar se o módulo está na config
24
+ const configPath = path_1.default.join(root, "kaven.json");
25
+ if (!(await fs_extra_1.default.pathExists(configPath))) {
26
+ throw new Error("Arquivo kaven.json não encontrado. Este é um projeto Kaven?");
27
+ }
28
+ const config = await fs_extra_1.default.readJson(configPath);
29
+ if (!config.modules || !config.modules[moduleName]) {
30
+ throw new Error(`O módulo ${moduleName} não está instalado.`);
31
+ }
32
+ // 2. Carregar manifest do cache
33
+ const manifestPath = path_1.default.join(root, ".kaven", "modules", moduleName, "module.json");
34
+ if (!(await fs_extra_1.default.pathExists(manifestPath))) {
35
+ throw new Error(`Cache do manifest para ${moduleName} não encontrado em ${manifestPath}. A remoção precisa do manifest original.`);
36
+ }
37
+ const manifest = await fs_extra_1.default.readJson(manifestPath);
38
+ // 3. Desinstalar
39
+ spinner.text = `Removendo injeções de ${moduleName}...`;
40
+ await installer.uninstall(manifest);
41
+ // 4. Atualizar config
42
+ spinner.text = "Atualizando configuração do projeto...";
43
+ delete config.modules[moduleName];
44
+ await fs_extra_1.default.writeJson(configPath, config, { spaces: 2 });
45
+ // 5. Limpar cache do manifest
46
+ await fs_extra_1.default.remove(path_1.default.dirname(manifestPath));
47
+ (0, ora_1.default)().succeed(chalk_1.default.green(`Módulo ${moduleName} removido com sucesso!`));
48
+ telemetry.capture("cli.module.remove.success", { moduleName }, Date.now() - startTime);
49
+ await telemetry.flush();
50
+ }
51
+ catch (error) {
52
+ telemetry.capture("cli.module.remove.error", { error: error.message }, Date.now() - startTime);
53
+ await telemetry.flush();
54
+ (0, ora_1.default)().fail(chalk_1.default.red(`Falha ao remover módulo ${moduleName}:`));
55
+ spinner.fail(chalk_1.default.red(`${error instanceof Error ? error.message : String(error)}`));
56
+ process.exit(1);
57
+ }
58
+ }
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.addCommand = void 0;
7
+ const commander_1 = require("commander");
8
+ const path_1 = __importDefault(require("path"));
9
+ const fs_extra_1 = __importDefault(require("fs-extra"));
10
+ const ModuleManager_1 = require("../../core/modules/ModuleManager");
11
+ const ModuleInstaller_1 = require("../../core/modules/ModuleInstaller");
12
+ const logger_1 = require("../../lib/logger");
13
+ exports.addCommand = new commander_1.Command('add')
14
+ .description('Install a module')
15
+ .argument('<slug>', 'Module slug to install')
16
+ .action(async (slug) => {
17
+ try {
18
+ const projectRoot = process.cwd();
19
+ // Locate modules source:
20
+ // In dev: ./modules
21
+ // In prod (installed): ../modules relative to dist
22
+ // For now, assume we run from CLI root or use logic to find 'modules' folder.
23
+ // Ideally CLI should be installed in a way that 'modules' folder is known.
24
+ // Fallback logic for finding modules source:
25
+ // 1. Env var KAVEN_MODULES_PATH
26
+ // 2. Local ./modules
27
+ // 3. Resolve relative to __dirname (dist) -> ../modules
28
+ let modulesSource = process.env.KAVEN_MODULES_PATH;
29
+ if (!modulesSource) {
30
+ if (await fs_extra_1.default.pathExists(path_1.default.resolve('modules'))) {
31
+ modulesSource = path_1.default.resolve('modules');
32
+ }
33
+ else {
34
+ modulesSource = path_1.default.resolve(__dirname, '../../modules');
35
+ }
36
+ }
37
+ if (!await fs_extra_1.default.pathExists(modulesSource)) {
38
+ logger_1.Logger.error(`Module source not found at ${modulesSource}`);
39
+ return;
40
+ }
41
+ const manifestPath = path_1.default.join(modulesSource, slug, 'module.json');
42
+ if (!await fs_extra_1.default.pathExists(manifestPath)) {
43
+ logger_1.Logger.error(`Module ${slug} not found in catalog.`);
44
+ return;
45
+ }
46
+ const manifest = await ModuleManager_1.ModuleManager.loadManifest(manifestPath);
47
+ await ModuleInstaller_1.ModuleInstaller.install(manifest, projectRoot, modulesSource);
48
+ }
49
+ catch (error) {
50
+ logger_1.Logger.error('Failed to add module', error);
51
+ process.exit(1);
52
+ }
53
+ });
@@ -0,0 +1,40 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.listCommand = void 0;
7
+ const commander_1 = require("commander");
8
+ const path_1 = __importDefault(require("path"));
9
+ const ModuleManager_1 = require("../../core/modules/ModuleManager");
10
+ const logger_1 = require("../../lib/logger");
11
+ const chalk_1 = __importDefault(require("chalk"));
12
+ exports.listCommand = new commander_1.Command('list')
13
+ .description('List available modules')
14
+ .action(async () => {
15
+ try {
16
+ logger_1.Logger.startSpinner('Loading modules...');
17
+ // By default, Kaven CLI looks for modules in the current project's ./kaven-cli/modules
18
+ // OR internal modules directory. For dev/bootstrap, we use the local 'modules' dir relative to CWD or __dirname.
19
+ // Strategy:
20
+ // 1. Check local repo structure (dev mode): ./modules
21
+ // 2. Check installed structure (production): ../modules
22
+ const modulesPath = path_1.default.resolve('modules');
23
+ const modules = await ModuleManager_1.ModuleManager.listAvailableModules(modulesPath);
24
+ logger_1.Logger.stopSpinner();
25
+ if (modules.length === 0) {
26
+ logger_1.Logger.warn('No modules found in ' + modulesPath);
27
+ return;
28
+ }
29
+ logger_1.Logger.info(`Found ${modules.length} modules:`);
30
+ modules.forEach(mod => {
31
+ console.log('');
32
+ console.log(chalk_1.default.bold.cyan(mod.displayName) + ' ' + chalk_1.default.dim(`(${mod.slug})`));
33
+ console.log(chalk_1.default.white(mod.description));
34
+ console.log(chalk_1.default.dim(`Version: ${mod.version}`));
35
+ });
36
+ }
37
+ catch (error) {
38
+ logger_1.Logger.error('Failed to list modules', error);
39
+ }
40
+ });
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.removeCommand = void 0;
7
+ const commander_1 = require("commander");
8
+ const path_1 = __importDefault(require("path"));
9
+ const fs_extra_1 = __importDefault(require("fs-extra"));
10
+ const ModuleManager_1 = require("../../core/modules/ModuleManager");
11
+ const ModuleRemover_1 = require("../../core/modules/ModuleRemover");
12
+ const logger_1 = require("../../lib/logger");
13
+ exports.removeCommand = new commander_1.Command('remove')
14
+ .description('Remove a module')
15
+ .argument('<slug>', 'Module slug to remove')
16
+ .action(async (slug) => {
17
+ try {
18
+ const projectRoot = process.cwd();
19
+ // For removal, we need the manifest to know what to eject.
20
+ // We look for the installed module first?
21
+ // Doc 3: "The engine behaves as if it’s 'playing back' the manifest in reverse."
22
+ // We need the manifest. Where do we get it?
23
+ // Option A: From the installed module directory (if we copied the manifest there).
24
+ // Option B: From the store (but what if version changed?).
25
+ // Best practice: We should have copied module.json to the installed location.
26
+ // Current Installer (Phase 2) does NOT copy module.json to installed location explicitly (it copies `api` folder content).
27
+ // Let's check `ModuleInstaller.ts`:
28
+ // if (manifest.slug) -> copy `api` to `modules/slug`.
29
+ // Does `api` contain `module.json`? No, `module.json` is at root of module source.
30
+ // FIX REQUIRED: We should probably look up the manifest from the *store* (catalog) matching the installed version,
31
+ // OR we should store the manifest upon installation.
32
+ // For MVP (Phase 3), let's assume we can find the manifest in the same Store logic as `add`.
33
+ let modulesSource = process.env.KAVEN_MODULES_PATH;
34
+ if (!modulesSource) {
35
+ if (await fs_extra_1.default.pathExists(path_1.default.resolve('modules'))) {
36
+ modulesSource = path_1.default.resolve('modules');
37
+ }
38
+ else {
39
+ modulesSource = path_1.default.resolve(__dirname, '../../modules');
40
+ }
41
+ }
42
+ const manifestPath = path_1.default.join(modulesSource, slug, 'module.json');
43
+ if (!await fs_extra_1.default.pathExists(manifestPath)) {
44
+ logger_1.Logger.error(`Module manifest not found in store for ${slug}. Cannot safely remove.`);
45
+ return;
46
+ }
47
+ const manifest = await ModuleManager_1.ModuleManager.loadManifest(manifestPath);
48
+ await ModuleRemover_1.ModuleRemover.remove(manifest, projectRoot);
49
+ }
50
+ catch (error) {
51
+ logger_1.Logger.error('Failed to remove module', error);
52
+ process.exit(1);
53
+ }
54
+ });
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.telemetryView = telemetryView;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const TelemetryBuffer_1 = require("../../infrastructure/TelemetryBuffer");
9
+ async function telemetryView(limit = 10) {
10
+ const telemetry = TelemetryBuffer_1.TelemetryBuffer.getInstance();
11
+ const events = await telemetry.getRecentEvents(limit);
12
+ if (events.length === 0) {
13
+ console.log(chalk_1.default.yellow("\nNenhum evento de telemetria encontrado localmente.\n"));
14
+ return;
15
+ }
16
+ console.log(chalk_1.default.blue.bold(`\n📊 Últimos ${events.length} eventos de telemetria:\n`));
17
+ events.forEach((e) => {
18
+ const time = chalk_1.default.gray(`[${new Date(e.timestamp).toLocaleTimeString()}]`);
19
+ const status = e.event.includes("error") ? chalk_1.default.red("✖") : chalk_1.default.green("✔");
20
+ const duration = e.duration ? chalk_1.default.yellow(` (${e.duration}ms)`) : "";
21
+ console.log(`${time} ${status} ${chalk_1.default.cyan(e.event)}${duration}`);
22
+ if (e.metadata && Object.keys(e.metadata).length > 0) {
23
+ console.log(chalk_1.default.gray(` > ${JSON.stringify(e.metadata)}`));
24
+ }
25
+ });
26
+ console.log(chalk_1.default.gray("\nLog local: ~/.kaven/telemetry.log\n"));
27
+ }
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.AuthService = void 0;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const os_1 = __importDefault(require("os"));
10
+ class AuthService {
11
+ constructor() {
12
+ this.configPath = path_1.default.join(os_1.default.homedir(), ".kaven", "auth.json");
13
+ }
14
+ async storeToken(token) {
15
+ const configDir = path_1.default.dirname(this.configPath);
16
+ await fs_extra_1.default.ensureDir(configDir);
17
+ // Salvar token em JSON
18
+ await fs_extra_1.default.writeJson(this.configPath, { token }, { spaces: 2 });
19
+ // Definir permissões restritas (0600 - leitura/escrita apenas pelo dono) no Linux
20
+ if (process.platform !== "win32") {
21
+ await fs_extra_1.default.chmod(this.configPath, 0o600);
22
+ }
23
+ }
24
+ async getToken() {
25
+ if (!(await fs_extra_1.default.pathExists(this.configPath))) {
26
+ return null;
27
+ }
28
+ try {
29
+ const data = await fs_extra_1.default.readJson(this.configPath);
30
+ return data.token || null;
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ }
36
+ async clearToken() {
37
+ if (await fs_extra_1.default.pathExists(this.configPath)) {
38
+ await fs_extra_1.default.remove(this.configPath);
39
+ }
40
+ }
41
+ async isAuthenticated() {
42
+ const token = await this.getToken();
43
+ return !!token;
44
+ }
45
+ /**
46
+ * Mock decoding of JWT for now.
47
+ * In a real implementation, we would use a library like 'jsonwebtoken'.
48
+ */
49
+ async getUserInfo() {
50
+ const token = await this.getToken();
51
+ if (!token)
52
+ return null;
53
+ // Simulate decoding (mocked)
54
+ return {
55
+ id: "usr-mock-123",
56
+ email: "user@example.com",
57
+ name: "Kaven Explorador",
58
+ };
59
+ }
60
+ }
61
+ exports.AuthService = AuthService;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ManifestParser = void 0;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const manifest_1 = require("../types/manifest");
9
+ const zod_1 = require("zod");
10
+ class ManifestParser {
11
+ async parse(manifestPath) {
12
+ if (!(await fs_extra_1.default.pathExists(manifestPath))) {
13
+ throw new Error(`Manifest not found: ${manifestPath}`);
14
+ }
15
+ const content = await fs_extra_1.default.readFile(manifestPath, "utf-8");
16
+ let json;
17
+ try {
18
+ json = JSON.parse(content);
19
+ }
20
+ catch {
21
+ throw new Error(`Failed to parse manifest JSON: ${manifestPath}`);
22
+ }
23
+ try {
24
+ return manifest_1.ModuleManifestSchema.parse(json);
25
+ }
26
+ catch (error) {
27
+ if (error instanceof zod_1.ZodError) {
28
+ throw new Error(`Invalid manifest: \n${this.formatZodError(error)}`);
29
+ }
30
+ throw error;
31
+ }
32
+ }
33
+ async validate(manifestPath) {
34
+ try {
35
+ await this.parse(manifestPath);
36
+ return { valid: true, errors: [] };
37
+ }
38
+ catch (error) {
39
+ const errorMessage = error instanceof Error ? error.message : String(error);
40
+ return {
41
+ valid: false,
42
+ errors: [errorMessage],
43
+ };
44
+ }
45
+ }
46
+ formatZodError(error) {
47
+ return error.issues
48
+ .map((err) => ` - ${err.path.map(String).join(".")}: ${err.message}`)
49
+ .join("\n");
50
+ }
51
+ }
52
+ exports.ManifestParser = ManifestParser;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MarkerService = void 0;
4
+ const markers_1 = require("../types/markers");
5
+ class MarkerService {
6
+ hasModule(fileContent, moduleName) {
7
+ const marker = (0, markers_1.createMarker)(moduleName);
8
+ return (fileContent.includes(marker.beginMarker) &&
9
+ fileContent.includes(marker.endMarker));
10
+ }
11
+ detectMarkers(fileContent, moduleName) {
12
+ const marker = (0, markers_1.createMarker)(moduleName);
13
+ const lines = fileContent.split("\n");
14
+ let beginLine;
15
+ let endLine;
16
+ for (let i = 0; i < lines.length; i++) {
17
+ if (lines[i].includes(marker.beginMarker)) {
18
+ beginLine = i;
19
+ }
20
+ if (lines[i].includes(marker.endMarker)) {
21
+ endLine = i;
22
+ break;
23
+ }
24
+ }
25
+ if (beginLine !== undefined && endLine !== undefined) {
26
+ const content = lines.slice(beginLine + 1, endLine).join("\n");
27
+ return {
28
+ found: true,
29
+ beginLine,
30
+ endLine,
31
+ content,
32
+ };
33
+ }
34
+ return { found: false };
35
+ }
36
+ injectModule(fileContent, anchor, moduleName, code) {
37
+ if (this.hasModule(fileContent, moduleName)) {
38
+ throw new Error(`Module ${moduleName} already injected`);
39
+ }
40
+ if (!fileContent.includes(anchor)) {
41
+ throw new Error(`Anchor not found: ${anchor}`);
42
+ }
43
+ const marker = (0, markers_1.createMarker)(moduleName);
44
+ const markedCode = `\n${marker.beginMarker}\n${code}\n${marker.endMarker}\n`;
45
+ return fileContent.replace(anchor, `${anchor}${markedCode}`);
46
+ }
47
+ removeModule(fileContent, moduleName) {
48
+ const marker = (0, markers_1.createMarker)(moduleName);
49
+ const beginMarker = this.escapeRegex(marker.beginMarker);
50
+ const endMarker = this.escapeRegex(marker.endMarker);
51
+ const regex = new RegExp(`\\n?${beginMarker}[\\s\\S]*?${endMarker}\\n?`, "g");
52
+ const result = fileContent.replace(regex, "");
53
+ if (result === fileContent) {
54
+ throw new Error(`Module ${moduleName} not found in file`);
55
+ }
56
+ return result;
57
+ }
58
+ escapeRegex(str) {
59
+ return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
60
+ }
61
+ }
62
+ exports.MarkerService = MarkerService;
@@ -0,0 +1,162 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ModuleDoctor = void 0;
7
+ const fs_extra_1 = __importDefault(require("fs-extra"));
8
+ const path_1 = __importDefault(require("path"));
9
+ class ModuleDoctor {
10
+ constructor(projectRoot, markerService, manifestParser) {
11
+ this.projectRoot = projectRoot;
12
+ this.markerService = markerService;
13
+ this.manifestParser = manifestParser;
14
+ }
15
+ async checkAll() {
16
+ const results = [];
17
+ results.push(...(await this.checkAnchors()));
18
+ results.push(...(await this.checkMarkers()));
19
+ results.push(...(await this.checkDependencies()));
20
+ return results;
21
+ }
22
+ async checkAnchors() {
23
+ const results = [];
24
+ const expectedAnchors = [
25
+ { file: "apps/api/src/index.ts", anchor: "// [ANCHOR:ROUTES]" },
26
+ { file: "apps/api/src/index.ts", anchor: "// [ANCHOR:MIDDLEWARE]" },
27
+ { file: "apps/admin/app/layout.tsx", anchor: "// [ANCHOR:NAV_ITEMS]" },
28
+ ];
29
+ for (const { file, anchor } of expectedAnchors) {
30
+ const filePath = path_1.default.join(this.projectRoot, file);
31
+ if (!(await fs_extra_1.default.pathExists(filePath))) {
32
+ results.push({
33
+ type: "anchor",
34
+ severity: "warning",
35
+ message: `File not found: ${file}`,
36
+ file,
37
+ fixable: false,
38
+ });
39
+ continue;
40
+ }
41
+ const content = await fs_extra_1.default.readFile(filePath, "utf-8");
42
+ if (!content.includes(anchor)) {
43
+ results.push({
44
+ type: "anchor",
45
+ severity: "error",
46
+ message: `Missing anchor: ${anchor}`,
47
+ file,
48
+ fixable: false,
49
+ });
50
+ }
51
+ }
52
+ return results;
53
+ }
54
+ async checkMarkers() {
55
+ const results = [];
56
+ const config = await this.readKavenConfig();
57
+ const installedModules = config.modules.filter((m) => m.installed);
58
+ for (const module of installedModules) {
59
+ const manifestPath = path_1.default.join(this.projectRoot, ".kaven/modules", module.name, "module.json");
60
+ if (!(await fs_extra_1.default.pathExists(manifestPath))) {
61
+ results.push({
62
+ type: "marker",
63
+ severity: "error",
64
+ message: `Manifest not found for installed module: ${module.name}`,
65
+ fixable: false,
66
+ });
67
+ continue;
68
+ }
69
+ try {
70
+ const manifest = await this.manifestParser.parse(manifestPath);
71
+ for (const injection of manifest.injections) {
72
+ const filePath = path_1.default.join(this.projectRoot, injection.file);
73
+ if (!(await fs_extra_1.default.pathExists(filePath))) {
74
+ results.push({
75
+ type: "marker",
76
+ severity: "error",
77
+ message: `Injection target not found: ${injection.file}`,
78
+ file: injection.file,
79
+ fixable: false,
80
+ });
81
+ continue;
82
+ }
83
+ const content = await fs_extra_1.default.readFile(filePath, "utf-8");
84
+ const moduleName = injection.moduleName || module.name;
85
+ const detection = this.markerService.detectMarkers(content, moduleName);
86
+ if (!detection.found) {
87
+ results.push({
88
+ type: "marker",
89
+ severity: "error",
90
+ message: `Module ${module.name} not injected in ${injection.file}`,
91
+ file: injection.file,
92
+ fixable: true,
93
+ });
94
+ }
95
+ }
96
+ }
97
+ catch (error) {
98
+ results.push({
99
+ type: "marker",
100
+ severity: "error",
101
+ message: `Invalid manifest for module ${module.name}: ${error instanceof Error ? error.message : String(error)}`,
102
+ fixable: false,
103
+ });
104
+ }
105
+ }
106
+ return results;
107
+ }
108
+ async checkDependencies() {
109
+ const results = [];
110
+ const config = await this.readKavenConfig();
111
+ const installedModules = config.modules.filter((m) => m.installed);
112
+ const packageJsonPath = path_1.default.join(this.projectRoot, "package.json");
113
+ if (!(await fs_extra_1.default.pathExists(packageJsonPath))) {
114
+ results.push({
115
+ type: "dependency",
116
+ severity: "error",
117
+ message: "package.json not found",
118
+ fixable: false,
119
+ });
120
+ return results;
121
+ }
122
+ const packageJson = await fs_extra_1.default.readJSON(packageJsonPath);
123
+ for (const module of installedModules) {
124
+ const manifestPath = path_1.default.join(this.projectRoot, ".kaven/modules", module.name, "module.json");
125
+ if (!(await fs_extra_1.default.pathExists(manifestPath)))
126
+ continue;
127
+ try {
128
+ const manifest = await this.manifestParser.parse(manifestPath);
129
+ for (const dep of manifest.dependencies.npm) {
130
+ const [name] = dep.split("@");
131
+ const hasInDeps = packageJson.dependencies?.[name];
132
+ const hasInDevDeps = packageJson.devDependencies?.[name];
133
+ if (!hasInDeps && !hasInDevDeps) {
134
+ results.push({
135
+ type: "dependency",
136
+ severity: "warning",
137
+ message: `Missing npm dependency: ${dep}`,
138
+ fixable: true,
139
+ });
140
+ }
141
+ }
142
+ }
143
+ catch {
144
+ // Ignorar erros de manifest aqui, já tratados em checkMarkers
145
+ }
146
+ }
147
+ return results;
148
+ }
149
+ async readKavenConfig() {
150
+ const configPath = path_1.default.join(this.projectRoot, "kaven.config.json");
151
+ if (!(await fs_extra_1.default.pathExists(configPath))) {
152
+ return { modules: [] };
153
+ }
154
+ try {
155
+ return await fs_extra_1.default.readJSON(configPath);
156
+ }
157
+ catch {
158
+ return { modules: [] };
159
+ }
160
+ }
161
+ }
162
+ exports.ModuleDoctor = ModuleDoctor;
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ModuleInstaller = void 0;
7
+ const TransactionalFileSystem_1 = require("../infrastructure/TransactionalFileSystem");
8
+ const fs_extra_1 = __importDefault(require("fs-extra"));
9
+ const path_1 = __importDefault(require("path"));
10
+ class ModuleInstaller {
11
+ constructor(projectRoot, markerService) {
12
+ this.projectRoot = projectRoot;
13
+ this.markerService = markerService;
14
+ }
15
+ async install(manifest) {
16
+ const tx = new TransactionalFileSystem_1.TransactionalFileSystem(this.projectRoot);
17
+ try {
18
+ const filesToModify = Array.from(new Set(manifest.injections.map((inj) => inj.file)));
19
+ await tx.backup(filesToModify);
20
+ for (const injection of manifest.injections) {
21
+ await this.injectCode(injection);
22
+ }
23
+ await tx.commit();
24
+ }
25
+ catch (error) {
26
+ const errorMessage = error instanceof Error ? error.message : String(error);
27
+ console.error(`❌ Installation failed: ${errorMessage}`);
28
+ console.log(`🔄 Rolling back...`);
29
+ await tx.rollback();
30
+ throw error;
31
+ }
32
+ }
33
+ async uninstall(manifest) {
34
+ const tx = new TransactionalFileSystem_1.TransactionalFileSystem(this.projectRoot);
35
+ try {
36
+ const filesToModify = Array.from(new Set(manifest.injections.map((inj) => inj.file)));
37
+ await tx.backup(filesToModify);
38
+ // Na desinstalação, removemos por arquivo para evitar múltiplas tentativas
39
+ // de remover marcadores que o regex global já removeu.
40
+ for (const fileName of filesToModify) {
41
+ await this.removeCode(fileName, manifest.name);
42
+ }
43
+ await tx.commit();
44
+ }
45
+ catch (error) {
46
+ const errorMessage = error instanceof Error ? error.message : String(error);
47
+ console.error(`❌ Removal failed: ${errorMessage}`);
48
+ console.log(`🔄 Rolling back...`);
49
+ await tx.rollback();
50
+ throw error;
51
+ }
52
+ }
53
+ async injectCode(injection) {
54
+ const filePath = path_1.default.join(this.projectRoot, injection.file);
55
+ const content = await fs_extra_1.default.readFile(filePath, "utf-8");
56
+ const updated = this.markerService.injectModule(content, injection.anchor, injection.moduleName || "unnamed", injection.code);
57
+ await fs_extra_1.default.writeFile(filePath, updated);
58
+ }
59
+ async removeCode(fileName, moduleName) {
60
+ const filePath = path_1.default.join(this.projectRoot, fileName);
61
+ const content = await fs_extra_1.default.readFile(filePath, "utf-8");
62
+ const updated = this.markerService.removeModule(content, moduleName);
63
+ await fs_extra_1.default.writeFile(filePath, updated);
64
+ }
65
+ }
66
+ exports.ModuleInstaller = ModuleInstaller;