kaven-cli 0.1.0-alpha.1 → 0.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/README.md +221 -45
- package/dist/commands/auth/login.js +97 -19
- package/dist/commands/auth/logout.js +4 -6
- package/dist/commands/auth/whoami.js +12 -11
- package/dist/commands/cache/index.js +43 -0
- package/dist/commands/config/index.js +128 -0
- package/dist/commands/init/index.js +209 -0
- package/dist/commands/init-ci/index.js +153 -0
- package/dist/commands/license/index.js +10 -0
- package/dist/commands/license/status.js +44 -0
- package/dist/commands/license/tier-table.js +46 -0
- package/dist/commands/marketplace/browse.js +219 -0
- package/dist/commands/marketplace/install.js +233 -29
- package/dist/commands/marketplace/list.js +94 -16
- package/dist/commands/module/doctor.js +143 -38
- package/dist/commands/module/publish.js +291 -0
- package/dist/commands/upgrade/check.js +162 -0
- package/dist/commands/upgrade/index.js +218 -0
- package/dist/core/AuthService.js +207 -14
- package/dist/core/CacheManager.js +151 -0
- package/dist/core/ConfigManager.js +165 -0
- package/dist/core/EnvManager.js +196 -0
- package/dist/core/ErrorRecovery.js +191 -0
- package/dist/core/LicenseService.js +118 -0
- package/dist/core/ModuleDoctor.js +286 -0
- package/dist/core/ModuleInstaller.js +136 -2
- package/dist/core/ProjectInitializer.js +117 -0
- package/dist/core/RegistryResolver.js +94 -0
- package/dist/core/ScriptRunner.js +72 -0
- package/dist/core/SignatureVerifier.js +72 -0
- package/dist/index.js +265 -20
- package/dist/infrastructure/MarketplaceClient.js +388 -64
- package/dist/infrastructure/errors.js +61 -0
- package/dist/types/auth.js +2 -0
- package/dist/types/marketplace.js +2 -0
- package/package.json +15 -2
|
@@ -0,0 +1,165 @@
|
|
|
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.ConfigManager = exports.configSchema = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const zod_1 = require("zod");
|
|
11
|
+
const CONFIG_DIR = path_1.default.join(os_1.default.homedir(), ".kaven");
|
|
12
|
+
const CONFIG_PATH = path_1.default.join(CONFIG_DIR, "config.json");
|
|
13
|
+
exports.configSchema = zod_1.z.object({
|
|
14
|
+
registry: zod_1.z.string().url().default("https://marketplace.kaven.sh"),
|
|
15
|
+
telemetry: zod_1.z.boolean().default(true),
|
|
16
|
+
theme: zod_1.z.enum(["light", "dark"]).default("dark"),
|
|
17
|
+
locale: zod_1.z.string().default("en-US"),
|
|
18
|
+
// Custom registry support
|
|
19
|
+
customRegistry: zod_1.z.string().url().optional(),
|
|
20
|
+
// For storing user preferences
|
|
21
|
+
lastLogin: zod_1.z.string().datetime().optional(),
|
|
22
|
+
projectDefaults: zod_1.z
|
|
23
|
+
.object({
|
|
24
|
+
dbUrl: zod_1.z.string().optional(),
|
|
25
|
+
emailProvider: zod_1.z.enum(["postmark", "resend", "ses", "smtp"]).optional(),
|
|
26
|
+
locale: zod_1.z.string().optional(),
|
|
27
|
+
currency: zod_1.z.string().optional(),
|
|
28
|
+
})
|
|
29
|
+
.optional(),
|
|
30
|
+
});
|
|
31
|
+
class ConfigManager {
|
|
32
|
+
constructor() {
|
|
33
|
+
this.config = {
|
|
34
|
+
registry: "https://marketplace.kaven.sh",
|
|
35
|
+
telemetry: true,
|
|
36
|
+
theme: "dark",
|
|
37
|
+
locale: "en-US",
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
async initialize() {
|
|
41
|
+
await fs_extra_1.default.ensureDir(CONFIG_DIR);
|
|
42
|
+
if (await fs_extra_1.default.pathExists(CONFIG_PATH)) {
|
|
43
|
+
try {
|
|
44
|
+
const raw = await fs_extra_1.default.readJson(CONFIG_PATH);
|
|
45
|
+
const parsed = exports.configSchema.safeParse(raw);
|
|
46
|
+
if (parsed.success) {
|
|
47
|
+
this.config = parsed.data;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
// If validation fails, use defaults
|
|
51
|
+
this.config = {
|
|
52
|
+
registry: "https://marketplace.kaven.sh",
|
|
53
|
+
telemetry: true,
|
|
54
|
+
theme: "dark",
|
|
55
|
+
locale: "en-US",
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// If file is corrupted, start fresh
|
|
61
|
+
this.config = {
|
|
62
|
+
registry: "https://marketplace.kaven.sh",
|
|
63
|
+
telemetry: true,
|
|
64
|
+
theme: "dark",
|
|
65
|
+
locale: "en-US",
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// Initialize with defaults
|
|
71
|
+
this.config = {
|
|
72
|
+
registry: "https://marketplace.kaven.sh",
|
|
73
|
+
telemetry: true,
|
|
74
|
+
theme: "dark",
|
|
75
|
+
locale: "en-US",
|
|
76
|
+
};
|
|
77
|
+
await this.persist();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get config value with env var override support
|
|
82
|
+
* Priority: ENV VAR > config file > CLI arg > default
|
|
83
|
+
*/
|
|
84
|
+
get(key, envVarName) {
|
|
85
|
+
// Check environment variable override
|
|
86
|
+
if (envVarName) {
|
|
87
|
+
const envValue = process.env[envVarName];
|
|
88
|
+
if (envValue !== undefined) {
|
|
89
|
+
return envValue;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Check config file
|
|
93
|
+
const value = this.config[key];
|
|
94
|
+
if (value !== undefined) {
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
// Return default from schema
|
|
98
|
+
const defaults = exports.configSchema.parse({});
|
|
99
|
+
return defaults[key];
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Set config value and persist to disk
|
|
103
|
+
*/
|
|
104
|
+
async set(key, value) {
|
|
105
|
+
const updateObj = { [key]: value };
|
|
106
|
+
const updated = exports.configSchema.safeParse({ ...this.config, ...updateObj });
|
|
107
|
+
if (!updated.success) {
|
|
108
|
+
const errors = updated.error.issues
|
|
109
|
+
.map((issue) => `${issue.path.join(".")}: ${issue.message}`)
|
|
110
|
+
.join(", ");
|
|
111
|
+
throw new Error(`Invalid config: ${errors}`);
|
|
112
|
+
}
|
|
113
|
+
this.config = updated.data;
|
|
114
|
+
await this.persist();
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Get all config
|
|
118
|
+
*/
|
|
119
|
+
getAll() {
|
|
120
|
+
return { ...this.config };
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Reset config to defaults
|
|
124
|
+
*/
|
|
125
|
+
async reset() {
|
|
126
|
+
this.config = {
|
|
127
|
+
registry: "https://marketplace.kaven.sh",
|
|
128
|
+
telemetry: true,
|
|
129
|
+
theme: "dark",
|
|
130
|
+
locale: "en-US",
|
|
131
|
+
};
|
|
132
|
+
await this.persist();
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Persist config to disk
|
|
136
|
+
*/
|
|
137
|
+
async persist() {
|
|
138
|
+
await fs_extra_1.default.ensureDir(CONFIG_DIR);
|
|
139
|
+
await fs_extra_1.default.writeJson(CONFIG_PATH, this.config, { spaces: 2 });
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Get registry URL (custom or default)
|
|
143
|
+
*/
|
|
144
|
+
getRegistry() {
|
|
145
|
+
return (this.config.customRegistry || this.config.registry || "https://marketplace.kaven.sh");
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Check if telemetry is enabled (can be overridden by env var)
|
|
149
|
+
*/
|
|
150
|
+
isTelemetryEnabled() {
|
|
151
|
+
if (process.env.KAVEN_TELEMETRY === "0") {
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
return this.config.telemetry !== false;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Get config directory path
|
|
158
|
+
*/
|
|
159
|
+
getConfigDir() {
|
|
160
|
+
return CONFIG_DIR;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
exports.ConfigManager = ConfigManager;
|
|
164
|
+
// Export singleton instance
|
|
165
|
+
exports.configManager = new ConfigManager();
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.EnvManager = void 0;
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const readline = __importStar(require("readline"));
|
|
43
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
44
|
+
class EnvManager {
|
|
45
|
+
async injectEnvVars(moduleSlug, envVars, options) {
|
|
46
|
+
if (options.skipEnv || !envVars || envVars.length === 0) {
|
|
47
|
+
return { added: 0, skipped: 0 };
|
|
48
|
+
}
|
|
49
|
+
const envFilePath = path.join(options.projectDir, options.envFile ?? '.env');
|
|
50
|
+
const existingContent = this.readEnvFile(envFilePath);
|
|
51
|
+
const existingVars = this.parseEnvFile(existingContent);
|
|
52
|
+
const newVars = [];
|
|
53
|
+
let skipped = 0;
|
|
54
|
+
console.log(chalk_1.default.bold(`\n Environment variables for '${moduleSlug}':\n`));
|
|
55
|
+
for (const envDef of envVars) {
|
|
56
|
+
if (existingVars.has(envDef.name)) {
|
|
57
|
+
console.log(chalk_1.default.dim(` ${envDef.name} — already set, skipping`));
|
|
58
|
+
skipped++;
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
let value;
|
|
62
|
+
if (envDef.sensitive) {
|
|
63
|
+
value = await this.promptPassword(` ${envDef.name} (${envDef.description})${envDef.default ? ' [****]' : ''}: `);
|
|
64
|
+
if (!value && envDef.default)
|
|
65
|
+
value = envDef.default;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
const defaultHint = envDef.default ? ` [${envDef.default}]` : '';
|
|
69
|
+
value = await this.promptInput(` ${envDef.name} (${envDef.description})${defaultHint}: `, envDef.default);
|
|
70
|
+
}
|
|
71
|
+
if (envDef.required && !value) {
|
|
72
|
+
console.log(chalk_1.default.yellow(` ${envDef.name} is required.`));
|
|
73
|
+
value = envDef.sensitive
|
|
74
|
+
? await this.promptPassword(` ${envDef.name}: `)
|
|
75
|
+
: await this.promptInput(` ${envDef.name}: `);
|
|
76
|
+
if (!value) {
|
|
77
|
+
console.log(chalk_1.default.yellow(` Skipping ${envDef.name} — set it manually in .env`));
|
|
78
|
+
skipped++;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
newVars.push({ name: envDef.name, value });
|
|
83
|
+
}
|
|
84
|
+
if (newVars.length === 0) {
|
|
85
|
+
console.log(chalk_1.default.dim(' No new environment variables to add.'));
|
|
86
|
+
return { added: 0, skipped };
|
|
87
|
+
}
|
|
88
|
+
const markerBlock = this.buildMarkerBlock(moduleSlug, newVars);
|
|
89
|
+
this.appendToEnvFile(envFilePath, existingContent, markerBlock);
|
|
90
|
+
console.log(chalk_1.default.green(`\n Added ${newVars.length} environment variable(s) to ${options.envFile ?? '.env'}`));
|
|
91
|
+
return { added: newVars.length, skipped };
|
|
92
|
+
}
|
|
93
|
+
removeEnvVars(moduleSlug, options) {
|
|
94
|
+
const envFiles = ['.env', '.env.local', '.env.development', '.env.production'];
|
|
95
|
+
if (options.envFile)
|
|
96
|
+
envFiles.unshift(options.envFile);
|
|
97
|
+
let totalRemoved = 0;
|
|
98
|
+
for (const envFile of envFiles) {
|
|
99
|
+
const envFilePath = path.join(options.projectDir, envFile);
|
|
100
|
+
if (!fs.existsSync(envFilePath))
|
|
101
|
+
continue;
|
|
102
|
+
const content = fs.readFileSync(envFilePath, 'utf-8');
|
|
103
|
+
const beginMarker = `# [KAVEN_MODULE:${moduleSlug} BEGIN]`;
|
|
104
|
+
const endMarker = `# [KAVEN_MODULE:${moduleSlug} END]`;
|
|
105
|
+
const beginIdx = content.indexOf(beginMarker);
|
|
106
|
+
const endIdx = content.indexOf(endMarker);
|
|
107
|
+
if (beginIdx === -1 || endIdx === -1)
|
|
108
|
+
continue;
|
|
109
|
+
const block = content.substring(beginIdx, endIdx + endMarker.length);
|
|
110
|
+
const varCount = block.split('\n').filter(l => /^[A-Z_]+=/.test(l)).length;
|
|
111
|
+
const before = content.substring(0, beginIdx).replace(/\n+$/, '\n');
|
|
112
|
+
const after = content.substring(endIdx + endMarker.length + 1);
|
|
113
|
+
fs.writeFileSync(envFilePath, before + after);
|
|
114
|
+
totalRemoved += varCount;
|
|
115
|
+
if (varCount > 0) {
|
|
116
|
+
console.log(chalk_1.default.dim(` Removed ${varCount} env var(s) from ${envFile}`));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return totalRemoved;
|
|
120
|
+
}
|
|
121
|
+
readEnvFile(filePath) {
|
|
122
|
+
try {
|
|
123
|
+
return fs.readFileSync(filePath, 'utf-8');
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
return '';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
parseEnvFile(content) {
|
|
130
|
+
const vars = new Map();
|
|
131
|
+
for (const line of content.split('\n')) {
|
|
132
|
+
const match = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
|
|
133
|
+
if (match)
|
|
134
|
+
vars.set(match[1], match[2]);
|
|
135
|
+
}
|
|
136
|
+
return vars;
|
|
137
|
+
}
|
|
138
|
+
buildMarkerBlock(moduleSlug, vars) {
|
|
139
|
+
return [
|
|
140
|
+
`# [KAVEN_MODULE:${moduleSlug} BEGIN]`,
|
|
141
|
+
...vars.map(v => `${v.name}=${v.value}`),
|
|
142
|
+
`# [KAVEN_MODULE:${moduleSlug} END]`,
|
|
143
|
+
].join('\n');
|
|
144
|
+
}
|
|
145
|
+
appendToEnvFile(filePath, existingContent, block) {
|
|
146
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
147
|
+
const separator = existingContent.endsWith('\n') || existingContent === '' ? '\n' : '\n\n';
|
|
148
|
+
fs.writeFileSync(filePath, existingContent + separator + block + '\n');
|
|
149
|
+
}
|
|
150
|
+
promptInput(message, defaultValue) {
|
|
151
|
+
return new Promise(resolve => {
|
|
152
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
153
|
+
rl.question(message, answer => {
|
|
154
|
+
rl.close();
|
|
155
|
+
resolve(answer || defaultValue || '');
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
promptPassword(message) {
|
|
160
|
+
return new Promise(resolve => {
|
|
161
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
162
|
+
// Hide input by listening to keypress but readline doesn't natively support this
|
|
163
|
+
// For simplicity, use standard prompt (production would use a proper password lib)
|
|
164
|
+
process.stdout.write(message);
|
|
165
|
+
process.stdin.setRawMode?.(true);
|
|
166
|
+
process.stdin.resume();
|
|
167
|
+
let password = '';
|
|
168
|
+
const handler = (key) => {
|
|
169
|
+
const char = key.toString();
|
|
170
|
+
if (char === '\r' || char === '\n') {
|
|
171
|
+
process.stdin.setRawMode?.(false);
|
|
172
|
+
process.stdin.pause();
|
|
173
|
+
process.stdin.removeListener('data', handler);
|
|
174
|
+
process.stdout.write('\n');
|
|
175
|
+
rl.close();
|
|
176
|
+
resolve(password);
|
|
177
|
+
}
|
|
178
|
+
else if (char === '\u0003') {
|
|
179
|
+
process.exit();
|
|
180
|
+
}
|
|
181
|
+
else if (char === '\u007f') {
|
|
182
|
+
password = password.slice(0, -1);
|
|
183
|
+
process.stdout.clearLine(0);
|
|
184
|
+
process.stdout.cursorTo(0);
|
|
185
|
+
process.stdout.write(message + '*'.repeat(password.length));
|
|
186
|
+
}
|
|
187
|
+
else {
|
|
188
|
+
password += char;
|
|
189
|
+
process.stdout.write('*');
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
process.stdin.on('data', handler);
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
exports.EnvManager = EnvManager;
|
|
@@ -0,0 +1,191 @@
|
|
|
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.ErrorRecovery = void 0;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
9
|
+
const os_1 = __importDefault(require("os"));
|
|
10
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
const ora_1 = __importDefault(require("ora"));
|
|
12
|
+
/**
|
|
13
|
+
* C2.7: Error recovery and validation system
|
|
14
|
+
*/
|
|
15
|
+
class ErrorRecovery {
|
|
16
|
+
constructor() {
|
|
17
|
+
this.backupDir = path_1.default.join(os_1.default.homedir(), ".kaven", "backups");
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create a backup of the project state before operation
|
|
21
|
+
*/
|
|
22
|
+
async createBackup(projectPath, operationName) {
|
|
23
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").substring(0, 19);
|
|
24
|
+
const backupName = `${operationName}-${timestamp}`;
|
|
25
|
+
const backupPath = path_1.default.join(this.backupDir, backupName);
|
|
26
|
+
try {
|
|
27
|
+
await fs_extra_1.default.ensureDir(this.backupDir);
|
|
28
|
+
// Backup key files
|
|
29
|
+
const files = ["package.json", "pnpm-lock.yaml", ".env", "prisma/schema.prisma"];
|
|
30
|
+
for (const file of files) {
|
|
31
|
+
const source = path_1.default.join(projectPath, file);
|
|
32
|
+
const dest = path_1.default.join(backupPath, file);
|
|
33
|
+
if (await fs_extra_1.default.pathExists(source)) {
|
|
34
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(dest));
|
|
35
|
+
await fs_extra_1.default.copy(source, dest);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return backupPath;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
throw new Error(`Failed to create backup: ${error instanceof Error ? error.message : String(error)}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Rollback from backup
|
|
46
|
+
*/
|
|
47
|
+
async rollback(projectPath, backupPath) {
|
|
48
|
+
try {
|
|
49
|
+
if (!(await fs_extra_1.default.pathExists(backupPath))) {
|
|
50
|
+
throw new Error(`Backup not found at ${backupPath}`);
|
|
51
|
+
}
|
|
52
|
+
const files = ["package.json", "pnpm-lock.yaml", ".env", "prisma/schema.prisma"];
|
|
53
|
+
for (const file of files) {
|
|
54
|
+
const source = path_1.default.join(backupPath, file);
|
|
55
|
+
const dest = path_1.default.join(projectPath, file);
|
|
56
|
+
if (await fs_extra_1.default.pathExists(source)) {
|
|
57
|
+
await fs_extra_1.default.copy(source, dest, { overwrite: true });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
throw new Error(`Failed to rollback: ${error instanceof Error ? error.message : String(error)}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Pre-operation validation
|
|
67
|
+
*/
|
|
68
|
+
async validatePreConditions(projectPath) {
|
|
69
|
+
const errors = [];
|
|
70
|
+
// Check package.json exists
|
|
71
|
+
if (!(await fs_extra_1.default.pathExists(path_1.default.join(projectPath, "package.json")))) {
|
|
72
|
+
errors.push("package.json not found");
|
|
73
|
+
}
|
|
74
|
+
// Check node_modules exists
|
|
75
|
+
if (!(await fs_extra_1.default.pathExists(path_1.default.join(projectPath, "node_modules")))) {
|
|
76
|
+
errors.push("Dependencies not installed. Run: pnpm install");
|
|
77
|
+
}
|
|
78
|
+
// Check if git repo (for safety)
|
|
79
|
+
const gitDir = path_1.default.join(projectPath, ".git");
|
|
80
|
+
if (!(await fs_extra_1.default.pathExists(gitDir))) {
|
|
81
|
+
errors.push("Not a git repository. Initialize with: git init && git add . && git commit");
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
valid: errors.length === 0,
|
|
85
|
+
errors,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Post-operation health check
|
|
90
|
+
*/
|
|
91
|
+
async validatePostConditions(projectPath) {
|
|
92
|
+
const issues = [];
|
|
93
|
+
// Check package.json is valid JSON
|
|
94
|
+
try {
|
|
95
|
+
const packageJson = await fs_extra_1.default.readJson(path_1.default.join(projectPath, "package.json"));
|
|
96
|
+
if (!packageJson.name) {
|
|
97
|
+
issues.push("package.json missing name field");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
issues.push("package.json is invalid JSON");
|
|
102
|
+
}
|
|
103
|
+
// Check prisma schema if it exists
|
|
104
|
+
const schemaPath = path_1.default.join(projectPath, "prisma", "schema.prisma");
|
|
105
|
+
if (await fs_extra_1.default.pathExists(schemaPath)) {
|
|
106
|
+
const content = await fs_extra_1.default.readFile(schemaPath, "utf-8");
|
|
107
|
+
if (!content.includes("datasource db")) {
|
|
108
|
+
issues.push("prisma/schema.prisma missing datasource");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// Check .env exists or .env.example exists
|
|
112
|
+
const envPath = path_1.default.join(projectPath, ".env");
|
|
113
|
+
const envExamplePath = path_1.default.join(projectPath, ".env.example");
|
|
114
|
+
if (!(await fs_extra_1.default.pathExists(envPath)) && !(await fs_extra_1.default.pathExists(envExamplePath))) {
|
|
115
|
+
issues.push("No .env or .env.example file found");
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
healthy: issues.length === 0,
|
|
119
|
+
issues,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Run operation with automatic rollback on failure
|
|
124
|
+
*/
|
|
125
|
+
async withRollback(projectPath, operationName, operation) {
|
|
126
|
+
// Pre-validation
|
|
127
|
+
const preCheck = await this.validatePreConditions(projectPath);
|
|
128
|
+
if (!preCheck.valid) {
|
|
129
|
+
throw new Error(`Pre-conditions not met:\n${preCheck.errors.map((e) => ` - ${e}`).join("\n")}`);
|
|
130
|
+
}
|
|
131
|
+
// Create backup
|
|
132
|
+
const spinner = (0, ora_1.default)("Creating backup...").start();
|
|
133
|
+
const backupPath = await this.createBackup(projectPath, operationName);
|
|
134
|
+
spinner.succeed(`Backup created at ${backupPath}`);
|
|
135
|
+
try {
|
|
136
|
+
// Run operation
|
|
137
|
+
const result = await operation();
|
|
138
|
+
// Post-validation
|
|
139
|
+
const postCheck = await this.validatePostConditions(projectPath);
|
|
140
|
+
if (!postCheck.healthy) {
|
|
141
|
+
spinner.warn("Post-operation issues detected:");
|
|
142
|
+
for (const issue of postCheck.issues) {
|
|
143
|
+
console.log(chalk_1.default.yellow(` ⚠ ${issue}`));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
// Rollback on failure
|
|
150
|
+
spinner.start("Rolling back...");
|
|
151
|
+
try {
|
|
152
|
+
await this.rollback(projectPath, backupPath);
|
|
153
|
+
spinner.succeed("Rolled back successfully");
|
|
154
|
+
}
|
|
155
|
+
catch (rollbackError) {
|
|
156
|
+
spinner.fail(`Rollback failed: ${rollbackError}`);
|
|
157
|
+
console.error(chalk_1.default.red("Manual intervention required:"));
|
|
158
|
+
console.error(chalk_1.default.gray(`Backup available at: ${backupPath}`));
|
|
159
|
+
}
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* List available backups
|
|
165
|
+
*/
|
|
166
|
+
async listBackups() {
|
|
167
|
+
if (!(await fs_extra_1.default.pathExists(this.backupDir))) {
|
|
168
|
+
return [];
|
|
169
|
+
}
|
|
170
|
+
const entries = await fs_extra_1.default.readdir(this.backupDir);
|
|
171
|
+
return entries.map((name) => ({
|
|
172
|
+
name,
|
|
173
|
+
path: path_1.default.join(this.backupDir, name),
|
|
174
|
+
timestamp: name.substring(name.lastIndexOf("-") + 1),
|
|
175
|
+
}));
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Clean old backups (keep last N)
|
|
179
|
+
*/
|
|
180
|
+
async cleanupBackups(keepCount = 5) {
|
|
181
|
+
const backups = await this.listBackups();
|
|
182
|
+
if (backups.length <= keepCount) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const toDelete = backups.slice(keepCount);
|
|
186
|
+
for (const backup of toDelete) {
|
|
187
|
+
await fs_extra_1.default.remove(backup.path);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
exports.ErrorRecovery = ErrorRecovery;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.LicenseService = void 0;
|
|
40
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
41
|
+
const path_1 = __importDefault(require("path"));
|
|
42
|
+
const os_1 = __importDefault(require("os"));
|
|
43
|
+
const CACHE_FILE = path_1.default.join(os_1.default.homedir(), '.kaven', 'cache', 'licenses.json');
|
|
44
|
+
const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
45
|
+
class LicenseService {
|
|
46
|
+
constructor() {
|
|
47
|
+
this.cacheDir = path_1.default.join(os_1.default.homedir(), '.kaven', 'cache');
|
|
48
|
+
}
|
|
49
|
+
/** Luhn mod-31 checksum format check (KAVEN-{TIER}-{8RANDOM}-{CHECKSUM}) */
|
|
50
|
+
isValidFormat(licenseKey) {
|
|
51
|
+
const pattern = /^KAVEN-(STARTER|COMPLETE|PRO|ENTERPRISE)-[A-Z0-9]{8}-[A-Z0-9]{2}$/;
|
|
52
|
+
return pattern.test(licenseKey);
|
|
53
|
+
}
|
|
54
|
+
async readCache() {
|
|
55
|
+
try {
|
|
56
|
+
const raw = await promises_1.default.readFile(CACHE_FILE, 'utf-8');
|
|
57
|
+
return JSON.parse(raw);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
return {};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
async writeCache(cache) {
|
|
64
|
+
await promises_1.default.mkdir(this.cacheDir, { recursive: true });
|
|
65
|
+
await promises_1.default.writeFile(CACHE_FILE, JSON.stringify(cache, null, 2), 'utf-8');
|
|
66
|
+
}
|
|
67
|
+
isCacheValid(entry) {
|
|
68
|
+
return Date.now() - entry.validatedAt < CACHE_TTL_MS;
|
|
69
|
+
}
|
|
70
|
+
async getCached(licenseKey) {
|
|
71
|
+
const cache = await this.readCache();
|
|
72
|
+
const entry = cache[licenseKey];
|
|
73
|
+
if (entry && this.isCacheValid(entry))
|
|
74
|
+
return entry;
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
async setCached(entry) {
|
|
78
|
+
const cache = await this.readCache();
|
|
79
|
+
cache[entry.key] = { ...entry, validatedAt: Date.now() };
|
|
80
|
+
await this.writeCache(cache);
|
|
81
|
+
}
|
|
82
|
+
async validateLicense(licenseKey, requiredTier) {
|
|
83
|
+
// 1. Check cache first
|
|
84
|
+
const cached = await this.getCached(licenseKey);
|
|
85
|
+
if (cached) {
|
|
86
|
+
return { valid: true, tier: cached.tier, expiresAt: cached.expiresAt, source: 'cache' };
|
|
87
|
+
}
|
|
88
|
+
// 2. Format check (offline Luhn-style)
|
|
89
|
+
if (!this.isValidFormat(licenseKey)) {
|
|
90
|
+
throw new Error('Invalid license key format');
|
|
91
|
+
}
|
|
92
|
+
// 3. API validation
|
|
93
|
+
const { MarketplaceClient } = await Promise.resolve().then(() => __importStar(require('../infrastructure/MarketplaceClient.js')));
|
|
94
|
+
const client = new MarketplaceClient();
|
|
95
|
+
const result = await client.validateLicense(licenseKey, requiredTier);
|
|
96
|
+
// Cache on success
|
|
97
|
+
await this.setCached({
|
|
98
|
+
key: licenseKey,
|
|
99
|
+
tier: result.tier,
|
|
100
|
+
expiresAt: result.expiresAt,
|
|
101
|
+
validatedAt: Date.now(),
|
|
102
|
+
});
|
|
103
|
+
return { ...result, source: 'api' };
|
|
104
|
+
}
|
|
105
|
+
async getLicenseStatus(licenseKey) {
|
|
106
|
+
const { MarketplaceClient } = await Promise.resolve().then(() => __importStar(require('../infrastructure/MarketplaceClient.js')));
|
|
107
|
+
const client = new MarketplaceClient();
|
|
108
|
+
return client.getLicenseStatus(licenseKey);
|
|
109
|
+
}
|
|
110
|
+
tierLevel(tier) {
|
|
111
|
+
const levels = { STARTER: 1, COMPLETE: 2, PRO: 3, ENTERPRISE: 4 };
|
|
112
|
+
return levels[tier.toUpperCase()] ?? 0;
|
|
113
|
+
}
|
|
114
|
+
userHasRequiredTier(userTier, requiredTier) {
|
|
115
|
+
return this.tierLevel(userTier) >= this.tierLevel(requiredTier);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
exports.LicenseService = LicenseService;
|