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
|
@@ -5,11 +5,103 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.moduleDoctor = moduleDoctor;
|
|
7
7
|
const chalk_1 = __importDefault(require("chalk"));
|
|
8
|
+
const child_process_1 = require("child_process");
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
11
|
const ModuleDoctor_1 = require("../../core/ModuleDoctor");
|
|
9
12
|
const MarkerService_1 = require("../../core/MarkerService");
|
|
10
13
|
const ManifestParser_1 = require("../../core/ManifestParser");
|
|
14
|
+
function severityPrefix(severity) {
|
|
15
|
+
switch (severity) {
|
|
16
|
+
case "error":
|
|
17
|
+
return chalk_1.default.red("[ERROR]");
|
|
18
|
+
case "warning":
|
|
19
|
+
return chalk_1.default.yellow("[WARN] ");
|
|
20
|
+
case "info":
|
|
21
|
+
return chalk_1.default.cyan("[INFO] ");
|
|
22
|
+
default:
|
|
23
|
+
return chalk_1.default.green("[OK] ");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/** Attempt auto-fixes for fixable issues. */
|
|
27
|
+
async function applyFixes(results, projectRoot) {
|
|
28
|
+
const fixable = results.filter((r) => r.fixable);
|
|
29
|
+
if (fixable.length === 0) {
|
|
30
|
+
console.log(chalk_1.default.gray(" No automatically fixable issues found."));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
for (const result of fixable) {
|
|
34
|
+
const msg = result.message.toLowerCase();
|
|
35
|
+
// Missing npm deps: run pnpm install
|
|
36
|
+
if (msg.includes("missing npm dependency") || msg.includes("pnpm install")) {
|
|
37
|
+
console.log(chalk_1.default.blue(` Fixing: ${result.message}`));
|
|
38
|
+
try {
|
|
39
|
+
(0, child_process_1.execSync)("pnpm install", { cwd: projectRoot, stdio: "inherit" });
|
|
40
|
+
console.log(chalk_1.default.green(" ✓ pnpm install completed"));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
console.log(chalk_1.default.red(" ✗ pnpm install failed"));
|
|
44
|
+
}
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
// Stale Prisma: run prisma generate
|
|
48
|
+
if (msg.includes("prisma")) {
|
|
49
|
+
console.log(chalk_1.default.blue(` Fixing: ${result.message}`));
|
|
50
|
+
try {
|
|
51
|
+
(0, child_process_1.execSync)("npx prisma generate", { cwd: projectRoot, stdio: "inherit" });
|
|
52
|
+
console.log(chalk_1.default.green(" ✓ npx prisma generate completed"));
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
console.log(chalk_1.default.red(" ✗ npx prisma generate failed"));
|
|
56
|
+
}
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
// Missing env vars: append placeholder
|
|
60
|
+
if (msg.includes("missing env vars") && result.file === ".env") {
|
|
61
|
+
const envPath = path_1.default.join(projectRoot, ".env");
|
|
62
|
+
const envExamplePath = path_1.default.join(projectRoot, ".env.example");
|
|
63
|
+
console.log(chalk_1.default.blue(` Fixing: ${result.message}`));
|
|
64
|
+
try {
|
|
65
|
+
const exampleContent = await fs_extra_1.default.readFile(envExamplePath, "utf-8");
|
|
66
|
+
const envContent = (await fs_extra_1.default.pathExists(envPath))
|
|
67
|
+
? await fs_extra_1.default.readFile(envPath, "utf-8")
|
|
68
|
+
: "";
|
|
69
|
+
const parseKeys = (content) => {
|
|
70
|
+
const keys = new Set();
|
|
71
|
+
for (const line of content.split("\n")) {
|
|
72
|
+
const trimmed = line.trim();
|
|
73
|
+
if (trimmed.startsWith("#") || !trimmed.includes("="))
|
|
74
|
+
continue;
|
|
75
|
+
const key = trimmed.split("=")[0].trim();
|
|
76
|
+
if (key)
|
|
77
|
+
keys.add(key);
|
|
78
|
+
}
|
|
79
|
+
return keys;
|
|
80
|
+
};
|
|
81
|
+
const exampleKeys = parseKeys(exampleContent);
|
|
82
|
+
const envKeys = parseKeys(envContent);
|
|
83
|
+
let appendContent = "\n# Added by kaven doctor --fix\n";
|
|
84
|
+
for (const key of exampleKeys) {
|
|
85
|
+
if (!envKeys.has(key)) {
|
|
86
|
+
appendContent += `${key}=PLACEHOLDER\n`;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
await fs_extra_1.default.appendFile(envPath, appendContent);
|
|
90
|
+
console.log(chalk_1.default.green(" ✓ Placeholder env vars appended to .env"));
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
console.log(chalk_1.default.red(" ✗ Could not append env vars"));
|
|
94
|
+
}
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
// Generic fallback
|
|
98
|
+
console.log(chalk_1.default.yellow(` Manual action required: ${result.message}`));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
11
101
|
async function moduleDoctor(options) {
|
|
12
|
-
|
|
102
|
+
if (!options.json) {
|
|
103
|
+
console.log(chalk_1.default.blue("Running module doctor...\n"));
|
|
104
|
+
}
|
|
13
105
|
const markerService = new MarkerService_1.MarkerService();
|
|
14
106
|
const manifestParser = new ManifestParser_1.ManifestParser();
|
|
15
107
|
const doctor = new ModuleDoctor_1.ModuleDoctor(process.cwd(), markerService, manifestParser);
|
|
@@ -18,53 +110,66 @@ async function moduleDoctor(options) {
|
|
|
18
110
|
results = await doctor.checkAll();
|
|
19
111
|
}
|
|
20
112
|
catch (error) {
|
|
21
|
-
|
|
113
|
+
if (options.json) {
|
|
114
|
+
console.log(JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error), results: [] }));
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
console.error(chalk_1.default.red(`[ERROR] Heavy failure during doctor audit: ${error instanceof Error ? error.message : String(error)}`));
|
|
118
|
+
}
|
|
22
119
|
process.exit(1);
|
|
120
|
+
return;
|
|
23
121
|
}
|
|
24
|
-
|
|
25
|
-
|
|
122
|
+
// JSON output mode
|
|
123
|
+
if (options.json) {
|
|
124
|
+
const errors = results.filter((r) => r.severity === "error");
|
|
125
|
+
const warnings = results.filter((r) => r.severity === "warning");
|
|
126
|
+
console.log(JSON.stringify({
|
|
127
|
+
success: errors.length === 0,
|
|
128
|
+
errors: errors.length,
|
|
129
|
+
warnings: warnings.length,
|
|
130
|
+
results,
|
|
131
|
+
}, null, 2));
|
|
132
|
+
process.exit(errors.length > 0 ? 1 : warnings.length > 0 ? 2 : 0);
|
|
26
133
|
return;
|
|
27
134
|
}
|
|
28
|
-
//
|
|
135
|
+
// Human-readable output
|
|
136
|
+
for (const result of results) {
|
|
137
|
+
const prefix = severityPrefix(result.severity);
|
|
138
|
+
console.log(`${prefix} ${result.message}`);
|
|
139
|
+
if (result.file) {
|
|
140
|
+
console.log(chalk_1.default.gray(` file: ${result.file}`));
|
|
141
|
+
}
|
|
142
|
+
if (result.fixable && !options.fix) {
|
|
143
|
+
console.log(chalk_1.default.gray(` (fixable: run with --fix)`));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
29
146
|
const errors = results.filter((r) => r.severity === "error");
|
|
30
147
|
const warnings = results.filter((r) => r.severity === "warning");
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
console.log(chalk_1.default.red(` ${err.message}`));
|
|
35
|
-
if (err.file)
|
|
36
|
-
console.log(chalk_1.default.gray(` file: ${err.file}`));
|
|
37
|
-
if (err.fixable)
|
|
38
|
-
console.log(chalk_1.default.yellow(` (💡 tip: run with --fix to attempt repair)`));
|
|
39
|
-
console.log();
|
|
40
|
-
}
|
|
148
|
+
console.log();
|
|
149
|
+
if (errors.length === 0 && warnings.length === 0) {
|
|
150
|
+
console.log(chalk_1.default.green("[OK] All checks passed! Your project is healthy."));
|
|
41
151
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
console.log(chalk_1.default.yellow(` ${warn.message}`));
|
|
46
|
-
if (warn.file)
|
|
47
|
-
console.log(chalk_1.default.gray(` file: ${warn.file}`));
|
|
48
|
-
console.log();
|
|
152
|
+
else {
|
|
153
|
+
if (errors.length > 0) {
|
|
154
|
+
console.log(chalk_1.default.red(`[ERROR] Found ${errors.length} error(s)`));
|
|
49
155
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
console.log(chalk_1.default.blue("🔧 Attempting to fix issues...\n"));
|
|
53
|
-
const fixable = results.filter((r) => r.fixable);
|
|
54
|
-
if (fixable.length === 0) {
|
|
55
|
-
console.log(chalk_1.default.gray(" No issues are automatically fixable yet."));
|
|
156
|
+
if (warnings.length > 0) {
|
|
157
|
+
console.log(chalk_1.default.yellow(`[WARN] Found ${warnings.length} warning(s)`));
|
|
56
158
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
// Por enquanto, apenas reportamos que a reparação automática
|
|
61
|
-
// será implementada conforme o ModuleInstaller for refinado.
|
|
62
|
-
console.log(chalk_1.default.yellow(` 🚧 Manual repair required for: ${result.message}. Autofix coming in future release.`));
|
|
159
|
+
if (!options.fix) {
|
|
160
|
+
console.log(chalk_1.default.gray("\nTip: Run with --fix to attempt automatic repairs"));
|
|
161
|
+
console.log(chalk_1.default.gray("Try: kaven module doctor --fix"));
|
|
63
162
|
}
|
|
64
|
-
console.log(chalk_1.default.green("\n✅ Audit complete. Fixes were logged for manual action.\n"));
|
|
65
163
|
}
|
|
66
|
-
|
|
67
|
-
console.log(
|
|
164
|
+
if (options.fix) {
|
|
165
|
+
console.log();
|
|
166
|
+
console.log(chalk_1.default.blue("Applying auto-fixes...\n"));
|
|
167
|
+
await applyFixes(results, process.cwd());
|
|
168
|
+
console.log(chalk_1.default.green("\nAuto-fix completed."));
|
|
68
169
|
}
|
|
69
|
-
|
|
170
|
+
// Exit codes: 0=all pass, 1=errors, 2=warnings only
|
|
171
|
+
if (errors.length > 0)
|
|
172
|
+
process.exit(1);
|
|
173
|
+
if (warnings.length > 0)
|
|
174
|
+
process.exit(2);
|
|
70
175
|
}
|
|
@@ -0,0 +1,291 @@
|
|
|
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.moduleJsonSchema = void 0;
|
|
40
|
+
exports.modulePublish = modulePublish;
|
|
41
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
42
|
+
const ora_1 = __importDefault(require("ora"));
|
|
43
|
+
const path_1 = __importDefault(require("path"));
|
|
44
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
45
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
46
|
+
const os_1 = __importDefault(require("os"));
|
|
47
|
+
const zod_1 = require("zod");
|
|
48
|
+
const AuthService_1 = require("../../core/AuthService");
|
|
49
|
+
const MarketplaceClient_1 = require("../../infrastructure/MarketplaceClient");
|
|
50
|
+
// Zod schema for module.json
|
|
51
|
+
exports.moduleJsonSchema = zod_1.z.object({
|
|
52
|
+
name: zod_1.z.string().min(1),
|
|
53
|
+
slug: zod_1.z.string().min(1).regex(/^[a-z0-9-]+$/),
|
|
54
|
+
version: zod_1.z.string().regex(/^\d+\.\d+\.\d+$/),
|
|
55
|
+
description: zod_1.z.string().min(1),
|
|
56
|
+
author: zod_1.z.string().optional(),
|
|
57
|
+
license: zod_1.z.string().optional(),
|
|
58
|
+
tier: zod_1.z.enum(["free", "starter", "complete", "pro"]),
|
|
59
|
+
});
|
|
60
|
+
const SIGNING_KEY_PATH = path_1.default.join(os_1.default.homedir(), ".kaven", "signing-key.json");
|
|
61
|
+
/** Load or generate Ed25519 signing key pair. */
|
|
62
|
+
async function getSigningKey() {
|
|
63
|
+
if (await fs_extra_1.default.pathExists(SIGNING_KEY_PATH)) {
|
|
64
|
+
try {
|
|
65
|
+
const stored = await fs_extra_1.default.readJson(SIGNING_KEY_PATH);
|
|
66
|
+
const privateKey = crypto_1.default.createPrivateKey({
|
|
67
|
+
key: Buffer.from(stored.privateKey, "base64"),
|
|
68
|
+
type: "pkcs8",
|
|
69
|
+
format: "der",
|
|
70
|
+
});
|
|
71
|
+
const publicKey = crypto_1.default.createPublicKey(privateKey);
|
|
72
|
+
return { privateKey, publicKey };
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Fall through to generate new key
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const { privateKey, publicKey } = crypto_1.default.generateKeyPairSync("ed25519");
|
|
79
|
+
const privateKeyDer = privateKey.export({ type: "pkcs8", format: "der" });
|
|
80
|
+
const publicKeyDer = publicKey.export({ type: "spki", format: "der" });
|
|
81
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(SIGNING_KEY_PATH));
|
|
82
|
+
await fs_extra_1.default.writeJson(SIGNING_KEY_PATH, {
|
|
83
|
+
privateKey: privateKeyDer.toString("base64"),
|
|
84
|
+
publicKey: publicKeyDer.toString("base64"),
|
|
85
|
+
}, { spaces: 2 });
|
|
86
|
+
if (process.platform !== "win32") {
|
|
87
|
+
await fs_extra_1.default.chmod(SIGNING_KEY_PATH, 0o600);
|
|
88
|
+
}
|
|
89
|
+
return { privateKey, publicKey };
|
|
90
|
+
}
|
|
91
|
+
/** Generate SHA-256 checksum of a file. */
|
|
92
|
+
async function sha256File(filePath) {
|
|
93
|
+
const data = await fs_extra_1.default.readFile(filePath);
|
|
94
|
+
return crypto_1.default.createHash("sha256").update(data).digest("hex");
|
|
95
|
+
}
|
|
96
|
+
/** Create tar.gz archive of current directory, excluding common noise. */
|
|
97
|
+
async function createTarball(sourceDir, outputPath) {
|
|
98
|
+
const tar = await Promise.resolve().then(() => __importStar(require("tar")));
|
|
99
|
+
await tar.create({
|
|
100
|
+
gzip: true,
|
|
101
|
+
file: outputPath,
|
|
102
|
+
cwd: sourceDir,
|
|
103
|
+
filter: (filePath) => {
|
|
104
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
105
|
+
const excluded = [
|
|
106
|
+
"node_modules",
|
|
107
|
+
".git",
|
|
108
|
+
"dist",
|
|
109
|
+
".env",
|
|
110
|
+
];
|
|
111
|
+
for (const exc of excluded) {
|
|
112
|
+
if (normalized.startsWith(exc + "/") ||
|
|
113
|
+
normalized === exc ||
|
|
114
|
+
normalized.endsWith(".log")) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return true;
|
|
119
|
+
},
|
|
120
|
+
}, ["."]);
|
|
121
|
+
}
|
|
122
|
+
async function modulePublish(options) {
|
|
123
|
+
const cwd = process.cwd();
|
|
124
|
+
// 1. Read and validate module.json
|
|
125
|
+
const moduleJsonPath = path_1.default.join(cwd, "module.json");
|
|
126
|
+
if (!(await fs_extra_1.default.pathExists(moduleJsonPath))) {
|
|
127
|
+
console.error(chalk_1.default.red("Error: module.json not found in current directory."));
|
|
128
|
+
console.error(chalk_1.default.gray("Try: run this command from inside a module directory"));
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
let moduleJson;
|
|
132
|
+
try {
|
|
133
|
+
const raw = await fs_extra_1.default.readJson(moduleJsonPath);
|
|
134
|
+
const result = exports.moduleJsonSchema.safeParse(raw);
|
|
135
|
+
if (!result.success) {
|
|
136
|
+
console.error(chalk_1.default.red("Error: Invalid module.json:"));
|
|
137
|
+
for (const issue of result.error.issues) {
|
|
138
|
+
console.error(chalk_1.default.red(` - ${issue.path.join(".")}: ${issue.message}`));
|
|
139
|
+
}
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
moduleJson = result.data;
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
console.error(chalk_1.default.red(`Error: Failed to parse module.json: ${error instanceof Error ? error.message : String(error)}`));
|
|
146
|
+
process.exit(1);
|
|
147
|
+
}
|
|
148
|
+
console.log();
|
|
149
|
+
console.log(chalk_1.default.bold(`Publishing module: ${moduleJson.name} v${moduleJson.version}`));
|
|
150
|
+
console.log(chalk_1.default.gray(`Slug: ${moduleJson.slug} | Tier: ${moduleJson.tier}`));
|
|
151
|
+
console.log();
|
|
152
|
+
// 2. Create tar.gz
|
|
153
|
+
const tarballPath = path_1.default.join(os_1.default.tmpdir(), `kaven-${moduleJson.slug}-${moduleJson.version}.tar.gz`);
|
|
154
|
+
const packageSpinner = (0, ora_1.default)("Creating module package...").start();
|
|
155
|
+
try {
|
|
156
|
+
await createTarball(cwd, tarballPath);
|
|
157
|
+
const stats = await fs_extra_1.default.stat(tarballPath);
|
|
158
|
+
packageSpinner.succeed(`Package created (${(stats.size / 1024).toFixed(1)} KB)`);
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
packageSpinner.fail("Failed to create package");
|
|
162
|
+
console.error(chalk_1.default.red(error instanceof Error ? error.message : String(error)));
|
|
163
|
+
await fs_extra_1.default.remove(tarballPath).catch(() => { });
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
// 3. Generate checksum
|
|
167
|
+
const checksumSpinner = (0, ora_1.default)("Computing SHA-256 checksum...").start();
|
|
168
|
+
let checksum;
|
|
169
|
+
try {
|
|
170
|
+
checksum = await sha256File(tarballPath);
|
|
171
|
+
checksumSpinner.succeed(`Checksum: ${checksum.substring(0, 16)}...`);
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
checksumSpinner.fail("Failed to compute checksum");
|
|
175
|
+
await fs_extra_1.default.remove(tarballPath).catch(() => { });
|
|
176
|
+
process.exit(1);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
// 4. Sign the checksum
|
|
180
|
+
const signSpinner = (0, ora_1.default)("Signing package...").start();
|
|
181
|
+
let signatureBase64;
|
|
182
|
+
let publicKeyBase64;
|
|
183
|
+
try {
|
|
184
|
+
const { privateKey, publicKey } = await getSigningKey();
|
|
185
|
+
const signature = crypto_1.default.sign(null, Buffer.from(checksum), privateKey);
|
|
186
|
+
signatureBase64 = signature.toString("base64");
|
|
187
|
+
const publicKeyDer = publicKey.export({
|
|
188
|
+
type: "spki", format: "der",
|
|
189
|
+
});
|
|
190
|
+
publicKeyBase64 = publicKeyDer.toString("base64");
|
|
191
|
+
signSpinner.succeed(`Package signed (${signatureBase64.substring(0, 16)}...)`);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
signSpinner.fail("Failed to sign package");
|
|
195
|
+
await fs_extra_1.default.remove(tarballPath).catch(() => { });
|
|
196
|
+
process.exit(1);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (options.dryRun) {
|
|
200
|
+
console.log();
|
|
201
|
+
console.log(chalk_1.default.yellow("Dry-run mode: skipping upload and release creation."));
|
|
202
|
+
console.log(chalk_1.default.green("✅ Package validated successfully."));
|
|
203
|
+
await fs_extra_1.default.remove(tarballPath).catch(() => { });
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
// 5. Get presigned upload URL
|
|
207
|
+
const authService = new AuthService_1.AuthService();
|
|
208
|
+
try {
|
|
209
|
+
await authService.getValidToken();
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
console.error(chalk_1.default.red("Error: Not authenticated. Run 'kaven auth login' first."));
|
|
213
|
+
await fs_extra_1.default.remove(tarballPath).catch(() => { });
|
|
214
|
+
process.exit(1);
|
|
215
|
+
return;
|
|
216
|
+
}
|
|
217
|
+
const client = new MarketplaceClient_1.MarketplaceClient(authService);
|
|
218
|
+
const stats = await fs_extra_1.default.stat(tarballPath);
|
|
219
|
+
const urlSpinner = (0, ora_1.default)("Getting upload URL...").start();
|
|
220
|
+
let uploadUrl;
|
|
221
|
+
let s3Key;
|
|
222
|
+
try {
|
|
223
|
+
const uploadUrlResult = await client.getUploadUrl(moduleJson.slug, moduleJson.version, stats.size);
|
|
224
|
+
uploadUrl = uploadUrlResult.uploadUrl;
|
|
225
|
+
s3Key = uploadUrlResult.s3Key;
|
|
226
|
+
urlSpinner.succeed("Upload URL received");
|
|
227
|
+
}
|
|
228
|
+
catch (error) {
|
|
229
|
+
urlSpinner.fail("Failed to get upload URL");
|
|
230
|
+
console.error(chalk_1.default.red(error instanceof Error ? error.message : String(error)));
|
|
231
|
+
await fs_extra_1.default.remove(tarballPath).catch(() => { });
|
|
232
|
+
process.exit(1);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
// 6. Upload tar.gz to presigned URL
|
|
236
|
+
const uploadSpinner = (0, ora_1.default)(`Uploading package (${(stats.size / 1024).toFixed(1)} KB)...`).start();
|
|
237
|
+
try {
|
|
238
|
+
const fileBuffer = await fs_extra_1.default.readFile(tarballPath);
|
|
239
|
+
const response = await fetch(uploadUrl, {
|
|
240
|
+
method: "PUT",
|
|
241
|
+
headers: {
|
|
242
|
+
"Content-Type": "application/gzip",
|
|
243
|
+
"Content-Length": String(stats.size),
|
|
244
|
+
},
|
|
245
|
+
body: fileBuffer,
|
|
246
|
+
});
|
|
247
|
+
if (!response.ok) {
|
|
248
|
+
throw new Error(`Upload failed: ${response.status} ${response.statusText}`);
|
|
249
|
+
}
|
|
250
|
+
uploadSpinner.succeed("Package uploaded successfully");
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
uploadSpinner.fail("Upload failed");
|
|
254
|
+
console.error(chalk_1.default.red(error instanceof Error ? error.message : String(error)));
|
|
255
|
+
await fs_extra_1.default.remove(tarballPath).catch(() => { });
|
|
256
|
+
process.exit(1);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
// 7. Create release record
|
|
260
|
+
const releaseSpinner = (0, ora_1.default)("Creating release record...").start();
|
|
261
|
+
try {
|
|
262
|
+
const release = await client.createRelease({
|
|
263
|
+
moduleSlug: moduleJson.slug,
|
|
264
|
+
version: moduleJson.version,
|
|
265
|
+
s3Key,
|
|
266
|
+
checksum,
|
|
267
|
+
signature: signatureBase64,
|
|
268
|
+
publicKey: publicKeyBase64,
|
|
269
|
+
changelog: options.changelog,
|
|
270
|
+
});
|
|
271
|
+
releaseSpinner.succeed(`Release created: ${moduleJson.slug}@${release.version} (ID: ${release.id})`);
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
releaseSpinner.fail("Failed to create release");
|
|
275
|
+
console.error(chalk_1.default.red(error instanceof Error ? error.message : String(error)));
|
|
276
|
+
await fs_extra_1.default.remove(tarballPath).catch(() => { });
|
|
277
|
+
process.exit(1);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
// Cleanup temp file
|
|
281
|
+
await fs_extra_1.default.remove(tarballPath).catch(() => { });
|
|
282
|
+
console.log();
|
|
283
|
+
console.log(chalk_1.default.green(`✅ Published ${moduleJson.name} v${moduleJson.version} to the Kaven Marketplace!`));
|
|
284
|
+
console.log(chalk_1.default.gray(`View your module at: https://marketplace.kaven.sh/modules/${moduleJson.slug}`));
|
|
285
|
+
// Show next steps
|
|
286
|
+
console.log();
|
|
287
|
+
console.log(chalk_1.default.bold("Next steps:"));
|
|
288
|
+
console.log(chalk_1.default.gray(" 1. Share your module with the community"));
|
|
289
|
+
console.log(chalk_1.default.gray(" 2. Monitor installation metrics"));
|
|
290
|
+
console.log(chalk_1.default.gray(" 3. Update module with 'kaven module publish' when ready"));
|
|
291
|
+
}
|
|
@@ -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.upgradeCheck = upgradeCheck;
|
|
7
|
+
exports.upgradeInstall = upgradeInstall;
|
|
8
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
const ora_1 = __importDefault(require("ora"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const child_process_1 = require("child_process");
|
|
12
|
+
const fs_1 = __importDefault(require("fs"));
|
|
13
|
+
const packageJsonPath = path_1.default.join(__dirname, "../../..", "package.json");
|
|
14
|
+
const PACKAGE_JSON = JSON.parse(fs_1.default.readFileSync(packageJsonPath, "utf8"));
|
|
15
|
+
const CURRENT_VERSION = PACKAGE_JSON.version;
|
|
16
|
+
const CLI_NAME = "kaven-cli";
|
|
17
|
+
/**
|
|
18
|
+
* C2.3: Check for CLI updates
|
|
19
|
+
*/
|
|
20
|
+
async function upgradeCheck() {
|
|
21
|
+
const spinner = (0, ora_1.default)("Checking for updates...").start();
|
|
22
|
+
try {
|
|
23
|
+
// Fetch latest version from npm registry
|
|
24
|
+
const response = await fetch(`https://registry.npmjs.org/${CLI_NAME}`);
|
|
25
|
+
if (!response.ok) {
|
|
26
|
+
spinner.fail("Could not check for updates");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const data = (await response.json());
|
|
30
|
+
const distTags = data["dist-tags"];
|
|
31
|
+
const latestVersion = distTags?.latest || CURRENT_VERSION;
|
|
32
|
+
spinner.stop();
|
|
33
|
+
console.log();
|
|
34
|
+
console.log(chalk_1.default.bold("Version Check:"));
|
|
35
|
+
console.log(` Current: ${chalk_1.default.cyan(CURRENT_VERSION)}`);
|
|
36
|
+
console.log(` Latest: ${chalk_1.default.cyan(latestVersion)}`);
|
|
37
|
+
console.log();
|
|
38
|
+
if (latestVersion === CURRENT_VERSION) {
|
|
39
|
+
console.log(chalk_1.default.green("✅ You're on the latest version!"));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Check if update available
|
|
43
|
+
const current = parseVersion(CURRENT_VERSION);
|
|
44
|
+
const latest = parseVersion(latestVersion);
|
|
45
|
+
if (latest.major > current.major ||
|
|
46
|
+
(latest.major === current.major && latest.minor > current.minor) ||
|
|
47
|
+
(latest.major === current.major &&
|
|
48
|
+
latest.minor === current.minor &&
|
|
49
|
+
latest.patch > current.patch)) {
|
|
50
|
+
console.log(chalk_1.default.yellow(`⚠ Update available: ${latestVersion}`));
|
|
51
|
+
console.log();
|
|
52
|
+
console.log(chalk_1.default.bold("To upgrade, run:"));
|
|
53
|
+
console.log(chalk_1.default.cyan(` npm install -g ${CLI_NAME}@latest`));
|
|
54
|
+
console.log(chalk_1.default.cyan(` or`));
|
|
55
|
+
console.log(chalk_1.default.cyan(` pnpm add -g ${CLI_NAME}@latest`));
|
|
56
|
+
console.log();
|
|
57
|
+
console.log(chalk_1.default.gray("Release notes: https://github.com/kaven-co/kaven-cli/releases"));
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
console.log(chalk_1.default.green("✅ You're on the latest version!"));
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
spinner.fail(`Check failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
65
|
+
console.log(chalk_1.default.gray("You can manually check at: https://www.npmjs.com/package/kaven-cli"));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* C2.3: Install latest CLI version
|
|
70
|
+
*/
|
|
71
|
+
async function upgradeInstall() {
|
|
72
|
+
console.log();
|
|
73
|
+
const spinner = (0, ora_1.default)("Fetching latest version...").start();
|
|
74
|
+
try {
|
|
75
|
+
// Get latest version
|
|
76
|
+
const response = await fetch(`https://registry.npmjs.org/${CLI_NAME}`);
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
throw new Error("Failed to fetch package info");
|
|
79
|
+
}
|
|
80
|
+
const data = (await response.json());
|
|
81
|
+
const distTags = data["dist-tags"];
|
|
82
|
+
const latestVersion = distTags?.latest || CURRENT_VERSION;
|
|
83
|
+
if (latestVersion === CURRENT_VERSION) {
|
|
84
|
+
spinner.succeed("Already on latest version");
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
spinner.text = `Installing ${CLI_NAME}@${latestVersion}...`;
|
|
88
|
+
// Determine package manager
|
|
89
|
+
const packageManager = process.env.npm_config_user_agent?.includes("pnpm")
|
|
90
|
+
? "pnpm"
|
|
91
|
+
: "npm";
|
|
92
|
+
// Install with appropriate package manager
|
|
93
|
+
const exitCode = await runCommand(packageManager, [
|
|
94
|
+
"install",
|
|
95
|
+
"-g",
|
|
96
|
+
`${CLI_NAME}@${latestVersion}`,
|
|
97
|
+
]);
|
|
98
|
+
if (exitCode !== 0) {
|
|
99
|
+
spinner.fail(`Installation failed with exit code ${exitCode}`);
|
|
100
|
+
console.error(chalk_1.default.gray(`Try: ${packageManager} install -g ${CLI_NAME}@latest`));
|
|
101
|
+
process.exit(1);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
spinner.succeed(`Updated to ${latestVersion}`);
|
|
105
|
+
// Health check after install
|
|
106
|
+
const healthSpinner = (0, ora_1.default)("Running health check...").start();
|
|
107
|
+
const health = await verifyInstallation();
|
|
108
|
+
if (health.ok) {
|
|
109
|
+
healthSpinner.succeed("Installation verified");
|
|
110
|
+
console.log();
|
|
111
|
+
console.log(chalk_1.default.green("✅ CLI upgraded successfully!"));
|
|
112
|
+
console.log(chalk_1.default.gray("Try: kaven --version"));
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
healthSpinner.warn("Installation verification failed");
|
|
116
|
+
console.log(chalk_1.default.yellow(health.errors.join("\n")));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
spinner.fail(`Installation failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Verify CLI installation is working
|
|
126
|
+
*/
|
|
127
|
+
async function verifyInstallation() {
|
|
128
|
+
const errors = [];
|
|
129
|
+
try {
|
|
130
|
+
// Check if kaven command is available
|
|
131
|
+
const code = await runCommand("kaven", ["--version"], { stdio: "pipe" });
|
|
132
|
+
if (code !== 0) {
|
|
133
|
+
errors.push("kaven command not available in PATH");
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
catch {
|
|
137
|
+
errors.push("Could not execute kaven command");
|
|
138
|
+
}
|
|
139
|
+
return { ok: errors.length === 0, errors };
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Run shell command
|
|
143
|
+
*/
|
|
144
|
+
function runCommand(cmd, args, options) {
|
|
145
|
+
return new Promise((resolve, reject) => {
|
|
146
|
+
const proc = (0, child_process_1.spawn)(cmd, args, options || { stdio: "inherit" });
|
|
147
|
+
proc.on("error", reject);
|
|
148
|
+
proc.on("close", (code) => resolve(code ?? 0));
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Parse semver version
|
|
153
|
+
*/
|
|
154
|
+
function parseVersion(version) {
|
|
155
|
+
const cleaned = version.replace(/^v/, "").split("-")[0];
|
|
156
|
+
const [major, minor, patch] = cleaned.split(".").map(Number);
|
|
157
|
+
return {
|
|
158
|
+
major: major || 0,
|
|
159
|
+
minor: minor || 0,
|
|
160
|
+
patch: patch || 0,
|
|
161
|
+
};
|
|
162
|
+
}
|