kaven-cli 0.3.0 → 0.3.5
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/README.md +147 -84
- package/README.pt-BR.md +334 -0
- package/dist/core/ModuleDoctor.js +4 -4
- package/dist/core/ProjectInitializer.js +39 -2
- package/dist/core/SignatureVerifier.js +7 -4
- package/dist/index.js +0 -0
- package/package.json +9 -3
- package/dist/commands/modules/add.js +0 -53
- package/dist/commands/modules/list.js +0 -40
- package/dist/commands/modules/remove.js +0 -54
- package/dist/core/api/KavenApiClient.js +0 -61
- package/dist/core/auth/AuthManager.js +0 -91
- package/dist/core/modules/Injector.js +0 -86
- package/dist/core/modules/ModuleInstaller.js +0 -63
- package/dist/core/modules/ModuleManager.js +0 -59
- package/dist/core/modules/ModuleRemover.js +0 -60
- package/dist/lib/config.js +0 -66
- package/dist/lib/errors.js +0 -32
- package/dist/lib/logger.js +0 -70
- package/dist/types/module.js +0 -49
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.KavenApiClient = exports.ApiError = void 0;
|
|
4
|
-
const AuthManager_1 = require("../auth/AuthManager");
|
|
5
|
-
const errors_1 = require("../../lib/errors");
|
|
6
|
-
class ApiError extends errors_1.KavenError {
|
|
7
|
-
constructor(message, status) {
|
|
8
|
-
super(message, 'API_ERROR');
|
|
9
|
-
this.status = status;
|
|
10
|
-
this.name = 'ApiError';
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
exports.ApiError = ApiError;
|
|
14
|
-
class KavenApiClient {
|
|
15
|
-
static async fetch(endpoint, options = {}) {
|
|
16
|
-
const url = `${this.baseUrl}${endpoint}`;
|
|
17
|
-
const headers = {
|
|
18
|
-
'Content-Type': 'application/json',
|
|
19
|
-
'Accept': 'application/json',
|
|
20
|
-
...options.headers,
|
|
21
|
-
};
|
|
22
|
-
// Inject token if available
|
|
23
|
-
const session = await AuthManager_1.AuthManager.getSession();
|
|
24
|
-
if (session?.accessToken) {
|
|
25
|
-
headers['Authorization'] = `Bearer ${session.accessToken}`;
|
|
26
|
-
}
|
|
27
|
-
try {
|
|
28
|
-
// We rely on Global Fetch (Node 20+)
|
|
29
|
-
const response = await fetch(url, { ...options, headers });
|
|
30
|
-
if (!response.ok) {
|
|
31
|
-
throw new ApiError(`Request failed: ${response.statusText}`, response.status);
|
|
32
|
-
}
|
|
33
|
-
// Check if response is JSON
|
|
34
|
-
const contentType = response.headers.get('content-type');
|
|
35
|
-
if (contentType && contentType.includes('application/json')) {
|
|
36
|
-
return await response.json();
|
|
37
|
-
}
|
|
38
|
-
return {}; // Return empty object for 204 or non-json
|
|
39
|
-
}
|
|
40
|
-
catch (error) {
|
|
41
|
-
if (error instanceof ApiError)
|
|
42
|
-
throw error;
|
|
43
|
-
throw new ApiError(`Network error: ${error instanceof Error ? error.message : String(error)}`, 0);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
// --- Endpoints ---
|
|
47
|
-
static async listMarketplaceModules() {
|
|
48
|
-
return this.fetch('/modules');
|
|
49
|
-
}
|
|
50
|
-
static async initiateDeviceFlow() {
|
|
51
|
-
return this.fetch('/auth/device/code', { method: 'POST' });
|
|
52
|
-
}
|
|
53
|
-
static async pollToken(deviceCode) {
|
|
54
|
-
return this.fetch('/auth/token', {
|
|
55
|
-
method: 'POST',
|
|
56
|
-
body: JSON.stringify({ device_code: deviceCode, grant_type: 'urn:ietf:params:oauth:grant-type:device_code' })
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
exports.KavenApiClient = KavenApiClient;
|
|
61
|
-
KavenApiClient.baseUrl = process.env.KAVEN_API_URL || 'https://api.kaven.dev';
|
|
@@ -1,91 +0,0 @@
|
|
|
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.AuthManager = 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
|
-
const logger_1 = require("../../lib/logger");
|
|
11
|
-
const KavenApiClient_1 = require("../api/KavenApiClient");
|
|
12
|
-
class AuthManager {
|
|
13
|
-
static setConfigDir(dir) {
|
|
14
|
-
this.configDir = dir;
|
|
15
|
-
this.credentialsFile = path_1.default.join(dir, 'credentials.json');
|
|
16
|
-
}
|
|
17
|
-
static async getSession() {
|
|
18
|
-
if (!await fs_extra_1.default.pathExists(this.credentialsFile)) {
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
try {
|
|
22
|
-
return await fs_extra_1.default.readJson(this.credentialsFile);
|
|
23
|
-
}
|
|
24
|
-
catch {
|
|
25
|
-
return null;
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
static async isAuthenticated() {
|
|
29
|
-
const session = await this.getSession();
|
|
30
|
-
return !!session?.accessToken;
|
|
31
|
-
}
|
|
32
|
-
static async saveSession(session) {
|
|
33
|
-
await fs_extra_1.default.ensureDir(this.configDir);
|
|
34
|
-
await fs_extra_1.default.writeJson(this.credentialsFile, session, { spaces: 2 });
|
|
35
|
-
}
|
|
36
|
-
static async logout() {
|
|
37
|
-
await fs_extra_1.default.remove(this.credentialsFile);
|
|
38
|
-
logger_1.Logger.success('Logged out successfully');
|
|
39
|
-
}
|
|
40
|
-
static async login() {
|
|
41
|
-
try {
|
|
42
|
-
const { device_code, user_code, verification_uri, expires_in } = await KavenApiClient_1.KavenApiClient.initiateDeviceFlow();
|
|
43
|
-
logger_1.Logger.box('Device Authentication', [
|
|
44
|
-
`1. Open: ${verification_uri}`,
|
|
45
|
-
`2. Code: ${user_code}`,
|
|
46
|
-
]);
|
|
47
|
-
logger_1.Logger.startSpinner('Waiting for authentication...');
|
|
48
|
-
// Poll logic with simple linear backoff/timeout inside polling loop logic
|
|
49
|
-
// Real impl implies KavenApiClient.pollToken loops or we loop here.
|
|
50
|
-
// KavenApiClient.pollToken signature above suggests single call, so we loop here.
|
|
51
|
-
const start = Date.now();
|
|
52
|
-
const timeout = expires_in * 1000;
|
|
53
|
-
while (Date.now() - start < timeout) {
|
|
54
|
-
try {
|
|
55
|
-
// In real world, pollToken throws "pending" or 400 until done.
|
|
56
|
-
// For this stub/MVP, we assume pollToken either waits or returns immediately.
|
|
57
|
-
// If we are simulating "real" behavior, pollToken would fail multiple times.
|
|
58
|
-
await new Promise(r => setTimeout(r, 2000)); // Sleep 2s
|
|
59
|
-
const tokens = await KavenApiClient_1.KavenApiClient.pollToken(device_code);
|
|
60
|
-
if (tokens.access_token) {
|
|
61
|
-
await this.saveSession({
|
|
62
|
-
accessToken: tokens.access_token,
|
|
63
|
-
refreshToken: tokens.refresh_token,
|
|
64
|
-
});
|
|
65
|
-
logger_1.Logger.succeedSpinner('Authenticated successfully!');
|
|
66
|
-
return true;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
catch (error) {
|
|
70
|
-
// Ignore pending errors, propagate others
|
|
71
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
72
|
-
if (!msg.includes('authorization_pending')) {
|
|
73
|
-
// If we can't distinguish, we might break early. For now, we assume simulated env (see test).
|
|
74
|
-
// If simulated, it succeeds immediately.
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
logger_1.Logger.stopSpinner();
|
|
79
|
-
logger_1.Logger.error('Authentication timed out');
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
catch (error) {
|
|
83
|
-
logger_1.Logger.stopSpinner();
|
|
84
|
-
logger_1.Logger.error('Login failed', error);
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
exports.AuthManager = AuthManager;
|
|
90
|
-
AuthManager.configDir = process.env.KAVEN_CONFIG_DIR || path_1.default.join(os_1.default.homedir(), '.kaven');
|
|
91
|
-
AuthManager.credentialsFile = path_1.default.join(AuthManager.configDir, 'credentials.json');
|
|
@@ -1,86 +0,0 @@
|
|
|
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.Injector = void 0;
|
|
7
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
-
const logger_1 = require("../../lib/logger");
|
|
9
|
-
class Injector {
|
|
10
|
-
/**
|
|
11
|
-
* Injects content into a file relative to an anchor.
|
|
12
|
-
* Idempotency is guaranteed by checking if content is already present.
|
|
13
|
-
*/
|
|
14
|
-
static async inject(targetPath, anchor, content, strategy = 'append') {
|
|
15
|
-
if (!await fs_extra_1.default.pathExists(targetPath)) {
|
|
16
|
-
logger_1.Logger.warn(`Injector target not found: ${targetPath}`);
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
const fileContent = await fs_extra_1.default.readFile(targetPath, 'utf8');
|
|
20
|
-
// 1. Check idempotency (is content already there?)
|
|
21
|
-
if (fileContent.includes(content)) {
|
|
22
|
-
// Content already exists, skip
|
|
23
|
-
return true;
|
|
24
|
-
}
|
|
25
|
-
// 2. Find anchor
|
|
26
|
-
// We assume anchor is like "KAVEN_MODULE_IMPORTS" and exists in file as "// [KAVEN_MODULE_IMPORTS]"
|
|
27
|
-
// But the manifest only provides the raw string info.
|
|
28
|
-
// Doc 3 standardizes anchors as `// [NAME]`.
|
|
29
|
-
// However, for maximum compatibility with current manifest (which just says "KAVEN_MODULE_IMPORTS"),
|
|
30
|
-
// we construct the search string.
|
|
31
|
-
const anchorPattern = `// [${anchor}]`;
|
|
32
|
-
if (!fileContent.includes(anchorPattern) && !fileContent.includes(anchor)) {
|
|
33
|
-
logger_1.Logger.warn(`Injector anchor '${anchor}' not found in ${targetPath}`);
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
// Use the exact string found in file if possible, or default to constructed one
|
|
37
|
-
const anchorString = fileContent.includes(anchorPattern) ? anchorPattern : anchor;
|
|
38
|
-
let newContent = fileContent;
|
|
39
|
-
if (strategy === 'append') {
|
|
40
|
-
// Padrão: <Conteudo Injetado>\n<Anchor>
|
|
41
|
-
// Ou seja, insere ANTES do anchor para preservar o anchor no final do bloco?
|
|
42
|
-
// Doc 3: "O CLI injeta conteúdo substituindo o anchor por: <snippet>\n<anchor>"
|
|
43
|
-
// Isso significa que o anchor é empurrado para baixo.
|
|
44
|
-
newContent = fileContent.replace(anchorString, `${content}\n${anchorString}`);
|
|
45
|
-
}
|
|
46
|
-
else if (strategy === 'prepend') {
|
|
47
|
-
newContent = fileContent.replace(anchorString, `${anchorString}\n${content}`);
|
|
48
|
-
}
|
|
49
|
-
else if (strategy === 'replace') {
|
|
50
|
-
newContent = fileContent.replace(anchorString, content);
|
|
51
|
-
}
|
|
52
|
-
await fs_extra_1.default.writeFile(targetPath, newContent, 'utf8');
|
|
53
|
-
return true;
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Removes content from a file.
|
|
57
|
-
*/
|
|
58
|
-
static async eject(targetPath, content) {
|
|
59
|
-
if (!await fs_extra_1.default.pathExists(targetPath)) {
|
|
60
|
-
logger_1.Logger.warn(`Injector target not found: ${targetPath}`);
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
const fileContent = await fs_extra_1.default.readFile(targetPath, 'utf8');
|
|
64
|
-
if (!fileContent.includes(content)) {
|
|
65
|
-
// Content not present, nothing to do
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
// Attempt to remove content + newline before or after
|
|
69
|
-
// Strategy: Replace content literal.
|
|
70
|
-
// We try to clean up surrounding newlines too if possible, but safely.
|
|
71
|
-
// For now, simple replacement is safest to start.
|
|
72
|
-
// We also need to handle the case where content was injected with a newline appended/prepended.
|
|
73
|
-
// Ideally we match exactly what was injected.
|
|
74
|
-
// Since `inject` does `${content}\n${anchorString}`, checks if we can remove `${content}\n`.
|
|
75
|
-
let newContent = fileContent.replace(content + '\n', '');
|
|
76
|
-
if (newContent === fileContent) {
|
|
77
|
-
newContent = fileContent.replace('\n' + content, '');
|
|
78
|
-
}
|
|
79
|
-
if (newContent === fileContent) {
|
|
80
|
-
newContent = fileContent.replace(content, '');
|
|
81
|
-
}
|
|
82
|
-
await fs_extra_1.default.writeFile(targetPath, newContent, 'utf8');
|
|
83
|
-
return true;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
exports.Injector = Injector;
|
|
@@ -1,63 +0,0 @@
|
|
|
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 path_1 = __importDefault(require("path"));
|
|
8
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
-
const Injector_1 = require("./Injector");
|
|
10
|
-
const config_1 = require("../../lib/config");
|
|
11
|
-
const logger_1 = require("../../lib/logger");
|
|
12
|
-
const errors_1 = require("../../lib/errors");
|
|
13
|
-
class ModuleInstaller {
|
|
14
|
-
static async install(manifest, projectRoot, modulesSourceRoot) {
|
|
15
|
-
logger_1.Logger.startSpinner(`Installing module: ${manifest.displayName}...`);
|
|
16
|
-
try {
|
|
17
|
-
// 1. Validation (Env vars, Compat)
|
|
18
|
-
// TODO: Env check against .env.example or interactive prompt
|
|
19
|
-
// 2. Copy Files
|
|
20
|
-
const moduleSourceDir = path_1.default.join(modulesSourceRoot, manifest.slug);
|
|
21
|
-
// 2a. API
|
|
22
|
-
if (await fs_extra_1.default.pathExists(path_1.default.join(moduleSourceDir, 'api'))) {
|
|
23
|
-
const dest = path_1.default.join(projectRoot, 'apps/api/src/modules', manifest.slug);
|
|
24
|
-
await fs_extra_1.default.copy(path_1.default.join(moduleSourceDir, 'api'), dest);
|
|
25
|
-
}
|
|
26
|
-
// 2b. Admin
|
|
27
|
-
if (await fs_extra_1.default.pathExists(path_1.default.join(moduleSourceDir, 'admin'))) {
|
|
28
|
-
const dest = path_1.default.join(projectRoot, 'apps/admin/app/[locale]/(dashboard)', manifest.slug);
|
|
29
|
-
await fs_extra_1.default.copy(path_1.default.join(moduleSourceDir, 'admin'), dest);
|
|
30
|
-
}
|
|
31
|
-
// 3. Injections
|
|
32
|
-
for (const injection of manifest.injections) {
|
|
33
|
-
const targetPath = path_1.default.join(projectRoot, injection.targetFile);
|
|
34
|
-
await Injector_1.Injector.inject(targetPath, injection.anchor, injection.content, injection.strategy);
|
|
35
|
-
}
|
|
36
|
-
// 4. Update Config
|
|
37
|
-
if (await config_1.ConfigManager.exists(projectRoot)) {
|
|
38
|
-
const config = await config_1.ConfigManager.load(projectRoot);
|
|
39
|
-
// Add to optional modules
|
|
40
|
-
if (!config.kaven.modules.optional)
|
|
41
|
-
config.kaven.modules.optional = {};
|
|
42
|
-
config.kaven.modules.optional[manifest.slug] = true;
|
|
43
|
-
// Add to customizations
|
|
44
|
-
if (!config.kaven.customizations.addedModules.includes(manifest.slug)) {
|
|
45
|
-
config.kaven.customizations.addedModules.push(manifest.slug);
|
|
46
|
-
}
|
|
47
|
-
// Remove from removedModules
|
|
48
|
-
config.kaven.customizations.removedModules = config.kaven.customizations.removedModules.filter(m => m !== manifest.slug);
|
|
49
|
-
await config_1.ConfigManager.save(projectRoot, config);
|
|
50
|
-
}
|
|
51
|
-
else {
|
|
52
|
-
logger_1.Logger.warn('kaven.config.json not found, skipping config update.');
|
|
53
|
-
}
|
|
54
|
-
logger_1.Logger.succeedSpinner(`Module ${manifest.displayName} installed successfully.`);
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
logger_1.Logger.stopSpinner();
|
|
58
|
-
logger_1.Logger.error(`Failed to install module ${manifest.slug}`, error);
|
|
59
|
-
throw new errors_1.ModuleError(`Installation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
exports.ModuleInstaller = ModuleInstaller;
|
|
@@ -1,59 +0,0 @@
|
|
|
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.ModuleManager = void 0;
|
|
7
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
-
const path_1 = __importDefault(require("path"));
|
|
9
|
-
const zod_1 = require("zod");
|
|
10
|
-
const module_1 = require("../../types/module");
|
|
11
|
-
const errors_1 = require("../../lib/errors");
|
|
12
|
-
const logger_1 = require("../../lib/logger");
|
|
13
|
-
class ModuleManager {
|
|
14
|
-
/**
|
|
15
|
-
* Loads and validates a module.json file.
|
|
16
|
-
*/
|
|
17
|
-
static async loadManifest(manifestPath) {
|
|
18
|
-
if (!await fs_extra_1.default.pathExists(manifestPath)) {
|
|
19
|
-
throw new errors_1.ModuleError(`Manifest not found at ${manifestPath}`);
|
|
20
|
-
}
|
|
21
|
-
try {
|
|
22
|
-
const content = await fs_extra_1.default.readJson(manifestPath);
|
|
23
|
-
return module_1.ModuleManifestSchema.parse(content);
|
|
24
|
-
}
|
|
25
|
-
catch (error) {
|
|
26
|
-
if (error instanceof zod_1.z.ZodError) {
|
|
27
|
-
throw new errors_1.ModuleError(`Invalid manifest at ${manifestPath}: ${error.issues.map((e) => `${e.path.join('.')}: ${e.message}`).join(', ')}`);
|
|
28
|
-
}
|
|
29
|
-
throw new errors_1.ModuleError(`Failed to read manifest: ${error}`);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
/**
|
|
33
|
-
* Scans a directory for available modules (modules that have module.json).
|
|
34
|
-
* @param sourcePath Directory containing module folders
|
|
35
|
-
*/
|
|
36
|
-
static async listAvailableModules(sourcePath) {
|
|
37
|
-
if (!await fs_extra_1.default.pathExists(sourcePath)) {
|
|
38
|
-
return [];
|
|
39
|
-
}
|
|
40
|
-
const entries = await fs_extra_1.default.readdir(sourcePath, { withFileTypes: true });
|
|
41
|
-
const modules = [];
|
|
42
|
-
for (const entry of entries) {
|
|
43
|
-
if (entry.isDirectory()) {
|
|
44
|
-
const manifestPath = path_1.default.join(sourcePath, entry.name, 'module.json');
|
|
45
|
-
if (await fs_extra_1.default.pathExists(manifestPath)) {
|
|
46
|
-
try {
|
|
47
|
-
const manifest = await this.loadManifest(manifestPath);
|
|
48
|
-
modules.push(manifest);
|
|
49
|
-
}
|
|
50
|
-
catch (error) {
|
|
51
|
-
logger_1.Logger.warn(`Skipping invalid module in ${entry.name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return modules;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
exports.ModuleManager = ModuleManager;
|
|
@@ -1,60 +0,0 @@
|
|
|
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.ModuleRemover = void 0;
|
|
7
|
-
const path_1 = __importDefault(require("path"));
|
|
8
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
-
const Injector_1 = require("./Injector");
|
|
10
|
-
const config_1 = require("../../lib/config");
|
|
11
|
-
const logger_1 = require("../../lib/logger");
|
|
12
|
-
const errors_1 = require("../../lib/errors");
|
|
13
|
-
class ModuleRemover {
|
|
14
|
-
static async remove(manifest, projectRoot) {
|
|
15
|
-
logger_1.Logger.startSpinner(`Removing module: ${manifest.displayName}...`);
|
|
16
|
-
try {
|
|
17
|
-
// 1. Eject Code
|
|
18
|
-
// We process injections in reverse order to minimize drift issues (LIFO)?
|
|
19
|
-
// Actually order shouldn't matter if logic is precise, but reverse is safer for nested injections.
|
|
20
|
-
const injections = [...manifest.injections].reverse();
|
|
21
|
-
for (const injection of injections) {
|
|
22
|
-
const targetPath = path_1.default.join(projectRoot, injection.targetFile);
|
|
23
|
-
await Injector_1.Injector.eject(targetPath, injection.content);
|
|
24
|
-
}
|
|
25
|
-
// 2. Remove Files
|
|
26
|
-
// 2a. API
|
|
27
|
-
const apiDest = path_1.default.join(projectRoot, 'apps/api/src/modules', manifest.slug);
|
|
28
|
-
if (await fs_extra_1.default.pathExists(apiDest)) {
|
|
29
|
-
await fs_extra_1.default.remove(apiDest);
|
|
30
|
-
}
|
|
31
|
-
// 2b. Admin
|
|
32
|
-
const adminDest = path_1.default.join(projectRoot, 'apps/admin/app/[locale]/(dashboard)', manifest.slug);
|
|
33
|
-
if (await fs_extra_1.default.pathExists(adminDest)) {
|
|
34
|
-
await fs_extra_1.default.remove(adminDest);
|
|
35
|
-
}
|
|
36
|
-
// 3. Update Config
|
|
37
|
-
if (await config_1.ConfigManager.exists(projectRoot)) {
|
|
38
|
-
const config = await config_1.ConfigManager.load(projectRoot);
|
|
39
|
-
// Remove from optional modules
|
|
40
|
-
if (config.kaven.modules.optional && config.kaven.modules.optional[manifest.slug]) {
|
|
41
|
-
config.kaven.modules.optional[manifest.slug] = false;
|
|
42
|
-
}
|
|
43
|
-
// Add to removedModules
|
|
44
|
-
if (!config.kaven.customizations.removedModules.includes(manifest.slug)) {
|
|
45
|
-
config.kaven.customizations.removedModules.push(manifest.slug);
|
|
46
|
-
}
|
|
47
|
-
// Remove from addedModules
|
|
48
|
-
config.kaven.customizations.addedModules = config.kaven.customizations.addedModules.filter(m => m !== manifest.slug);
|
|
49
|
-
await config_1.ConfigManager.save(projectRoot, config);
|
|
50
|
-
}
|
|
51
|
-
logger_1.Logger.succeedSpinner(`Module ${manifest.displayName} removed successfully.`);
|
|
52
|
-
}
|
|
53
|
-
catch (error) {
|
|
54
|
-
logger_1.Logger.stopSpinner();
|
|
55
|
-
logger_1.Logger.error(`Failed to remove module ${manifest.slug}`, error);
|
|
56
|
-
throw new errors_1.ModuleError(`Removal failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
exports.ModuleRemover = ModuleRemover;
|
package/dist/lib/config.js
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
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.ConfigManager = exports.KavenConfigSchema = void 0;
|
|
7
|
-
const zod_1 = require("zod");
|
|
8
|
-
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
-
const path_1 = __importDefault(require("path"));
|
|
10
|
-
const errors_1 = require("./errors");
|
|
11
|
-
exports.KavenConfigSchema = zod_1.z.object({
|
|
12
|
-
name: zod_1.z.string(),
|
|
13
|
-
version: zod_1.z.string(),
|
|
14
|
-
kaven: zod_1.z.object({
|
|
15
|
-
version: zod_1.z.string(),
|
|
16
|
-
features: zod_1.z.object({
|
|
17
|
-
multiTenant: zod_1.z.boolean().default(true),
|
|
18
|
-
database: zod_1.z.enum(['postgresql', 'mysql']).default('postgresql'),
|
|
19
|
-
payment: zod_1.z.enum(['stripe', 'paddle', 'pix', 'none']).default('none').optional(),
|
|
20
|
-
}),
|
|
21
|
-
modules: zod_1.z.object({
|
|
22
|
-
core: zod_1.z.record(zod_1.z.boolean()).default({}),
|
|
23
|
-
optional: zod_1.z.record(zod_1.z.boolean()).default({}),
|
|
24
|
-
}),
|
|
25
|
-
customizations: zod_1.z.object({
|
|
26
|
-
addedModules: zod_1.z.array(zod_1.z.string()).default([]),
|
|
27
|
-
removedModules: zod_1.z.array(zod_1.z.string()).default([]),
|
|
28
|
-
}).default({
|
|
29
|
-
addedModules: [],
|
|
30
|
-
removedModules: [],
|
|
31
|
-
}),
|
|
32
|
-
}),
|
|
33
|
-
});
|
|
34
|
-
const CONFIG_FILENAME = 'kaven.config.json';
|
|
35
|
-
class ConfigManager {
|
|
36
|
-
static async load(projectRoot) {
|
|
37
|
-
const configPath = path_1.default.join(projectRoot, CONFIG_FILENAME);
|
|
38
|
-
if (!await fs_extra_1.default.pathExists(configPath)) {
|
|
39
|
-
throw new errors_1.ConfigError(`Configuration file not found at ${configPath}`);
|
|
40
|
-
}
|
|
41
|
-
try {
|
|
42
|
-
const content = await fs_extra_1.default.readJson(configPath);
|
|
43
|
-
return exports.KavenConfigSchema.parse(content);
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
if (error instanceof zod_1.z.ZodError) {
|
|
47
|
-
throw new errors_1.ConfigError(`Invalid configuration: ${error.issues.map(e => `${e.path.join('.')}: ${e.message}`).join(', ')}`);
|
|
48
|
-
}
|
|
49
|
-
throw new errors_1.ConfigError(`Failed to read configuration: ${error}`);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
static async save(projectRoot, config) {
|
|
53
|
-
const configPath = path_1.default.join(projectRoot, CONFIG_FILENAME);
|
|
54
|
-
try {
|
|
55
|
-
const validated = exports.KavenConfigSchema.parse(config);
|
|
56
|
-
await fs_extra_1.default.writeJson(configPath, validated, { spaces: 2 });
|
|
57
|
-
}
|
|
58
|
-
catch (error) {
|
|
59
|
-
throw new errors_1.ConfigError(`Failed to save configuration: ${error}`);
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
static async exists(projectRoot) {
|
|
63
|
-
return fs_extra_1.default.pathExists(path_1.default.join(projectRoot, CONFIG_FILENAME));
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
exports.ConfigManager = ConfigManager;
|
package/dist/lib/errors.js
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.AuthError = exports.ModuleError = exports.ConfigError = exports.KavenError = void 0;
|
|
4
|
-
class KavenError extends Error {
|
|
5
|
-
constructor(message, code = 'KAVEN_ERROR') {
|
|
6
|
-
super(message);
|
|
7
|
-
this.code = code;
|
|
8
|
-
this.name = 'KavenError';
|
|
9
|
-
}
|
|
10
|
-
}
|
|
11
|
-
exports.KavenError = KavenError;
|
|
12
|
-
class ConfigError extends KavenError {
|
|
13
|
-
constructor(message) {
|
|
14
|
-
super(message, 'CONFIG_ERROR');
|
|
15
|
-
this.name = 'ConfigError';
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
exports.ConfigError = ConfigError;
|
|
19
|
-
class ModuleError extends KavenError {
|
|
20
|
-
constructor(message) {
|
|
21
|
-
super(message, 'MODULE_ERROR');
|
|
22
|
-
this.name = 'ModuleError';
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
exports.ModuleError = ModuleError;
|
|
26
|
-
class AuthError extends KavenError {
|
|
27
|
-
constructor(message) {
|
|
28
|
-
super(message, 'AUTH_ERROR');
|
|
29
|
-
this.name = 'AuthError';
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
exports.AuthError = AuthError;
|
package/dist/lib/logger.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
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.Logger = void 0;
|
|
7
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
-
const ora_1 = __importDefault(require("ora"));
|
|
9
|
-
class Logger {
|
|
10
|
-
static info(message) {
|
|
11
|
-
console.log(chalk_1.default.blue('ℹ'), message);
|
|
12
|
-
}
|
|
13
|
-
static succeedSpinner(message) {
|
|
14
|
-
this.success(message);
|
|
15
|
-
}
|
|
16
|
-
static success(message) {
|
|
17
|
-
if (this.spinner) {
|
|
18
|
-
this.spinner.succeed(chalk_1.default.green(message));
|
|
19
|
-
this.spinner = null;
|
|
20
|
-
}
|
|
21
|
-
else {
|
|
22
|
-
console.log(chalk_1.default.green('✔'), message);
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
static warn(message) {
|
|
26
|
-
console.log(chalk_1.default.yellow('⚠'), message);
|
|
27
|
-
}
|
|
28
|
-
static error(message, error) {
|
|
29
|
-
if (this.spinner) {
|
|
30
|
-
this.spinner.fail(chalk_1.default.red(message));
|
|
31
|
-
this.spinner = null;
|
|
32
|
-
}
|
|
33
|
-
else {
|
|
34
|
-
console.error(chalk_1.default.red('✖'), message);
|
|
35
|
-
}
|
|
36
|
-
if (error instanceof Error) {
|
|
37
|
-
console.error(chalk_1.default.dim(error.stack));
|
|
38
|
-
}
|
|
39
|
-
else if (error) {
|
|
40
|
-
console.error(chalk_1.default.dim(JSON.stringify(error)));
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
static startSpinner(text) {
|
|
44
|
-
if (this.spinner) {
|
|
45
|
-
this.spinner.text = text;
|
|
46
|
-
}
|
|
47
|
-
else {
|
|
48
|
-
this.spinner = (0, ora_1.default)(text).start();
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
static stopSpinner() {
|
|
52
|
-
if (this.spinner) {
|
|
53
|
-
this.spinner.stop();
|
|
54
|
-
this.spinner = null;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
static box(title, lines) {
|
|
58
|
-
const width = Math.max(title.length + 4, ...lines.map(l => l.length + 4));
|
|
59
|
-
const border = '─'.repeat(width);
|
|
60
|
-
console.log(chalk_1.default.cyan(`┌${border}┐`));
|
|
61
|
-
console.log(chalk_1.default.cyan(`│ ${chalk_1.default.bold(title.padEnd(width - 2))} │`));
|
|
62
|
-
console.log(chalk_1.default.cyan(`│${border}│`));
|
|
63
|
-
lines.forEach(line => {
|
|
64
|
-
console.log(chalk_1.default.cyan(`│ ${line.padEnd(width - 2)} │`));
|
|
65
|
-
});
|
|
66
|
-
console.log(chalk_1.default.cyan(`└${border}┘`));
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
exports.Logger = Logger;
|
|
70
|
-
Logger.spinner = null;
|
package/dist/types/module.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ModuleManifestSchema = exports.InjectionSchema = void 0;
|
|
4
|
-
const zod_1 = require("zod");
|
|
5
|
-
exports.InjectionSchema = zod_1.z.object({
|
|
6
|
-
targetFile: zod_1.z.string(), // relative to project root
|
|
7
|
-
anchor: zod_1.z.string(), // e.g. "KAVEN_MODULE_IMPORTS"
|
|
8
|
-
content: zod_1.z.string(), // content to inject
|
|
9
|
-
strategy: zod_1.z.enum(['append', 'prepend', 'replace']).default('append'),
|
|
10
|
-
});
|
|
11
|
-
exports.ModuleManifestSchema = zod_1.z.object({
|
|
12
|
-
id: zod_1.z.string().uuid().optional(), // optional for local dev
|
|
13
|
-
slug: zod_1.z.string().min(3).regex(/^[a-z0-9-]+$/),
|
|
14
|
-
version: zod_1.z.string().regex(/^\d+\.\d+\.\d+.*$/), // SemVer
|
|
15
|
-
displayName: zod_1.z.string(),
|
|
16
|
-
description: zod_1.z.string(),
|
|
17
|
-
category: zod_1.z.string().default('features'),
|
|
18
|
-
publisher: zod_1.z.string().default('kaven'),
|
|
19
|
-
// Compatibility
|
|
20
|
-
compat: zod_1.z.object({
|
|
21
|
-
kaven: zod_1.z.string(), // SemVer range e.g. "^2.0.0"
|
|
22
|
-
node: zod_1.z.string().optional(),
|
|
23
|
-
}),
|
|
24
|
-
// Dependencies
|
|
25
|
-
dependencies: zod_1.z.record(zod_1.z.string()).optional(), // npm deps
|
|
26
|
-
devDependencies: zod_1.z.record(zod_1.z.string()).optional(),
|
|
27
|
-
peerModules: zod_1.z.record(zod_1.z.string()).optional(), // other kaven modules
|
|
28
|
-
// Configuration Requirements (Env Vars)
|
|
29
|
-
env: zod_1.z.array(zod_1.z.object({
|
|
30
|
-
key: zod_1.z.string(),
|
|
31
|
-
description: zod_1.z.string(),
|
|
32
|
-
required: zod_1.z.boolean().default(true),
|
|
33
|
-
defaultValue: zod_1.z.string().optional(),
|
|
34
|
-
})).default([]),
|
|
35
|
-
// File Copying mappings (TO-BE: flexible mappings)
|
|
36
|
-
// For AS-IS compatibility, we assume standard structure (api/admin)
|
|
37
|
-
// Injections
|
|
38
|
-
injections: zod_1.z.array(exports.InjectionSchema).default([]),
|
|
39
|
-
// Database
|
|
40
|
-
database: zod_1.z.object({
|
|
41
|
-
schemaPath: zod_1.z.string().optional(), // path to schema.prisma fragment
|
|
42
|
-
migrations: zod_1.z.boolean().default(false),
|
|
43
|
-
}).optional(),
|
|
44
|
-
// Tasks (hooks)
|
|
45
|
-
hooks: zod_1.z.object({
|
|
46
|
-
postInstall: zod_1.z.array(zod_1.z.string()).default([]),
|
|
47
|
-
preRemove: zod_1.z.array(zod_1.z.string()).default([]),
|
|
48
|
-
}).optional(),
|
|
49
|
-
});
|