ccjk 2.3.2 → 2.4.2
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 +270 -444
- package/README.zh-CN.md +273 -447
- package/dist/chunks/api-providers.mjs +5 -35
- package/dist/chunks/auto-bootstrap.mjs +1 -1
- package/dist/chunks/ccr.mjs +5 -2
- package/dist/chunks/claude-wrapper.mjs +442 -0
- package/dist/chunks/cloud-sync.mjs +29 -0
- package/dist/chunks/constants.mjs +1 -1
- package/dist/chunks/context-manager.mjs +641 -0
- package/dist/chunks/context.mjs +248 -0
- package/dist/chunks/index2.mjs +2 -0
- package/dist/chunks/index3.mjs +19 -19
- package/dist/chunks/init.mjs +18 -8
- package/dist/chunks/marketplace.mjs +6 -2
- package/dist/chunks/mcp.mjs +1 -1
- package/dist/chunks/menu.mjs +3 -3
- package/dist/chunks/notification.mjs +27 -27
- package/dist/chunks/package.mjs +1 -1
- package/dist/chunks/platform.mjs +70 -21
- package/dist/chunks/skills-sync.mjs +1 -1
- package/dist/chunks/version-checker.mjs +31 -31
- package/dist/cli.mjs +55 -5
- package/dist/i18n/locales/en/context.json +32 -0
- package/dist/i18n/locales/en/marketplace.json +1 -0
- package/dist/i18n/locales/en/mcp.json +12 -1
- package/dist/i18n/locales/en/superpowers.json +46 -0
- package/dist/i18n/locales/zh-CN/context.json +32 -0
- package/dist/i18n/locales/zh-CN/marketplace.json +1 -0
- package/dist/i18n/locales/zh-CN/mcp.json +12 -1
- package/dist/i18n/locales/zh-CN/superpowers.json +46 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/shared/ccjk.QbS8EAOd.mjs +1019 -0
- package/dist/shared/ccjk.RR9TS76h.mjs +698 -0
- package/package.json +4 -1
- package/dist/shared/ccjk.Bi-m3LKY.mjs +0 -357
- package/dist/shared/ccjk.D-RZS4E2.mjs +0 -416
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, createWriteStream } from 'node:fs';
|
|
3
|
+
import { readFile, mkdir, rm } from 'node:fs/promises';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { Readable } from 'node:stream';
|
|
6
|
+
import { pipeline } from 'node:stream/promises';
|
|
7
|
+
import { join } from 'pathe';
|
|
8
|
+
import { extract } from 'tar';
|
|
9
|
+
import { exec } from 'tinyexec';
|
|
10
|
+
import { i18n } from '../chunks/index2.mjs';
|
|
11
|
+
import { writeFileAtomicAsync, exists, readJsonFile } from '../chunks/fs-operations.mjs';
|
|
12
|
+
|
|
13
|
+
const DEFAULT_REGISTRY_URL = "https://registry.api.claudehome.cn/v1";
|
|
14
|
+
const DEFAULT_CACHE_CONFIG = {
|
|
15
|
+
cacheDir: join(homedir(), ".ccjk", "cache"),
|
|
16
|
+
ttl: 3600,
|
|
17
|
+
// 1 hour
|
|
18
|
+
enabled: true
|
|
19
|
+
};
|
|
20
|
+
const BUILTIN_PACKAGES = [
|
|
21
|
+
// Built-in packages will be added here in future versions
|
|
22
|
+
];
|
|
23
|
+
function getCacheFilePath(cacheDir) {
|
|
24
|
+
return join(cacheDir, "registry-cache.json");
|
|
25
|
+
}
|
|
26
|
+
async function isCacheValid(cacheDir, ttl) {
|
|
27
|
+
const cachePath = getCacheFilePath(cacheDir);
|
|
28
|
+
if (!existsSync(cachePath)) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const content = await readFile(cachePath, "utf-8");
|
|
33
|
+
const cache = JSON.parse(content);
|
|
34
|
+
const cacheTime = new Date(cache.lastUpdated).getTime();
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
return now - cacheTime < ttl * 1e3;
|
|
37
|
+
} catch {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function readCachedRegistry(cacheDir) {
|
|
42
|
+
const cachePath = getCacheFilePath(cacheDir);
|
|
43
|
+
if (!existsSync(cachePath)) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const content = await readFile(cachePath, "utf-8");
|
|
48
|
+
return JSON.parse(content);
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async function writeCacheRegistry(cacheDir, registry) {
|
|
54
|
+
await mkdir(cacheDir, { recursive: true });
|
|
55
|
+
const cachePath = getCacheFilePath(cacheDir);
|
|
56
|
+
await writeFileAtomicAsync(cachePath, JSON.stringify(registry, null, 2));
|
|
57
|
+
}
|
|
58
|
+
async function fetchRemoteRegistry(registryUrl = DEFAULT_REGISTRY_URL, timeout = 3e4) {
|
|
59
|
+
const controller = new AbortController();
|
|
60
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
61
|
+
try {
|
|
62
|
+
const response = await fetch(`${registryUrl}/registry.json`, {
|
|
63
|
+
signal: controller.signal,
|
|
64
|
+
headers: {
|
|
65
|
+
"Accept": "application/json",
|
|
66
|
+
"User-Agent": "ccjk-cli"
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
71
|
+
}
|
|
72
|
+
return await response.json();
|
|
73
|
+
} finally {
|
|
74
|
+
clearTimeout(timeoutId);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
async function getRegistry(options = {}) {
|
|
78
|
+
const cacheConfig = { ...DEFAULT_CACHE_CONFIG, ...options.cache };
|
|
79
|
+
const registryUrl = options.registryUrl || DEFAULT_REGISTRY_URL;
|
|
80
|
+
if (cacheConfig.enabled && !options.forceRefresh) {
|
|
81
|
+
const cacheValid = await isCacheValid(cacheConfig.cacheDir, cacheConfig.ttl);
|
|
82
|
+
if (cacheValid) {
|
|
83
|
+
const cached = await readCachedRegistry(cacheConfig.cacheDir);
|
|
84
|
+
if (cached) {
|
|
85
|
+
return cached;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const registry = await fetchRemoteRegistry(registryUrl);
|
|
91
|
+
if (cacheConfig.enabled) {
|
|
92
|
+
await writeCacheRegistry(cacheConfig.cacheDir, registry);
|
|
93
|
+
}
|
|
94
|
+
return registry;
|
|
95
|
+
} catch {
|
|
96
|
+
const cached = await readCachedRegistry(cacheConfig.cacheDir);
|
|
97
|
+
if (cached) {
|
|
98
|
+
return cached;
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
version: "1.0.0",
|
|
102
|
+
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
103
|
+
url: registryUrl,
|
|
104
|
+
packages: BUILTIN_PACKAGES,
|
|
105
|
+
categories: {
|
|
106
|
+
"plugin": 0,
|
|
107
|
+
"skill": 0,
|
|
108
|
+
"workflow": 0,
|
|
109
|
+
"agent": 0,
|
|
110
|
+
"mcp-service": 0,
|
|
111
|
+
"output-style": 0,
|
|
112
|
+
"bundle": 0
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function searchPackages(options = {}) {
|
|
118
|
+
const registry = await getRegistry();
|
|
119
|
+
let packages = [...registry.packages];
|
|
120
|
+
if (options.query) {
|
|
121
|
+
const query = options.query.toLowerCase();
|
|
122
|
+
packages = packages.filter(
|
|
123
|
+
(pkg) => pkg.name.toLowerCase().includes(query) || pkg.id.toLowerCase().includes(query) || pkg.keywords.some((k) => k.toLowerCase().includes(query)) || Object.values(pkg.description).some((d) => d.toLowerCase().includes(query))
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
if (options.category) {
|
|
127
|
+
packages = packages.filter((pkg) => pkg.category === options.category);
|
|
128
|
+
}
|
|
129
|
+
if (options.author) {
|
|
130
|
+
packages = packages.filter(
|
|
131
|
+
(pkg) => pkg.author.toLowerCase().includes(options.author.toLowerCase())
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
if (options.verified) {
|
|
135
|
+
packages = packages.filter((pkg) => pkg.verified === options.verified);
|
|
136
|
+
}
|
|
137
|
+
if (options.keywords?.length) {
|
|
138
|
+
packages = packages.filter(
|
|
139
|
+
(pkg) => options.keywords.some((k) => pkg.keywords.includes(k))
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
if (options.minRating) {
|
|
143
|
+
packages = packages.filter((pkg) => pkg.rating >= options.minRating);
|
|
144
|
+
}
|
|
145
|
+
if (options.supportedTool) {
|
|
146
|
+
packages = packages.filter(
|
|
147
|
+
(pkg) => pkg.supportedTools?.includes(options.supportedTool)
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
const sortBy = options.sortBy || "downloads";
|
|
151
|
+
const sortDir = options.sortDir || "desc";
|
|
152
|
+
packages.sort((a, b) => {
|
|
153
|
+
let comparison = 0;
|
|
154
|
+
switch (sortBy) {
|
|
155
|
+
case "downloads":
|
|
156
|
+
comparison = a.downloads - b.downloads;
|
|
157
|
+
break;
|
|
158
|
+
case "rating":
|
|
159
|
+
comparison = a.rating - b.rating;
|
|
160
|
+
break;
|
|
161
|
+
case "updated":
|
|
162
|
+
comparison = new Date(a.updatedAt).getTime() - new Date(b.updatedAt).getTime();
|
|
163
|
+
break;
|
|
164
|
+
case "created":
|
|
165
|
+
comparison = new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime();
|
|
166
|
+
break;
|
|
167
|
+
case "name":
|
|
168
|
+
comparison = a.name.localeCompare(b.name);
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
return sortDir === "desc" ? -comparison : comparison;
|
|
172
|
+
});
|
|
173
|
+
const total = packages.length;
|
|
174
|
+
const offset = options.offset || 0;
|
|
175
|
+
const limit = options.limit || 20;
|
|
176
|
+
packages = packages.slice(offset, offset + limit);
|
|
177
|
+
return {
|
|
178
|
+
packages,
|
|
179
|
+
total,
|
|
180
|
+
offset,
|
|
181
|
+
limit,
|
|
182
|
+
query: options.query,
|
|
183
|
+
filters: options
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
async function getPackage(packageId) {
|
|
187
|
+
const registry = await getRegistry();
|
|
188
|
+
return registry.packages.find((pkg) => pkg.id === packageId) || null;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function resolveDependencies(pkg, visited = /* @__PURE__ */ new Set()) {
|
|
192
|
+
if (visited.has(pkg.id)) {
|
|
193
|
+
return {
|
|
194
|
+
package: pkg,
|
|
195
|
+
dependencies: [],
|
|
196
|
+
totalCount: 0,
|
|
197
|
+
hasCircular: true
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
visited.add(pkg.id);
|
|
201
|
+
const dependencies = [];
|
|
202
|
+
let totalCount = 0;
|
|
203
|
+
let hasCircular = false;
|
|
204
|
+
if (pkg.dependencies) {
|
|
205
|
+
for (const [depId, versionRange] of Object.entries(pkg.dependencies)) {
|
|
206
|
+
try {
|
|
207
|
+
const depPkg = await getPackage(depId);
|
|
208
|
+
if (!depPkg) {
|
|
209
|
+
throw new Error(`Dependency not found: ${depId}`);
|
|
210
|
+
}
|
|
211
|
+
if (!isVersionCompatible(depPkg.version, versionRange)) {
|
|
212
|
+
throw new Error(
|
|
213
|
+
`Version mismatch for ${depId}: required ${versionRange}, found ${depPkg.version}`
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
const nestedTree = await resolveDependencies(depPkg, new Set(visited));
|
|
217
|
+
const node = {
|
|
218
|
+
package: depPkg,
|
|
219
|
+
versionRange,
|
|
220
|
+
dependencies: nestedTree.dependencies,
|
|
221
|
+
circular: nestedTree.hasCircular
|
|
222
|
+
};
|
|
223
|
+
dependencies.push(node);
|
|
224
|
+
totalCount += 1 + nestedTree.totalCount;
|
|
225
|
+
if (nestedTree.hasCircular) {
|
|
226
|
+
hasCircular = true;
|
|
227
|
+
}
|
|
228
|
+
} catch (error) {
|
|
229
|
+
throw new Error(
|
|
230
|
+
`Failed to resolve dependency ${depId}: ${error instanceof Error ? error.message : String(error)}`
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return {
|
|
236
|
+
package: pkg,
|
|
237
|
+
dependencies,
|
|
238
|
+
totalCount,
|
|
239
|
+
hasCircular
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function isVersionCompatible(version, range) {
|
|
243
|
+
if (range === "*") {
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
const versionParts = parseVersion(version);
|
|
247
|
+
if (!versionParts) {
|
|
248
|
+
return false;
|
|
249
|
+
}
|
|
250
|
+
if (!range.match(/^[~^><]/)) {
|
|
251
|
+
return version === range;
|
|
252
|
+
}
|
|
253
|
+
if (range.startsWith("^")) {
|
|
254
|
+
const rangeParts = parseVersion(range.slice(1));
|
|
255
|
+
if (!rangeParts) {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
return versionParts.major === rangeParts.major && (versionParts.minor > rangeParts.minor || versionParts.minor === rangeParts.minor && versionParts.patch >= rangeParts.patch);
|
|
259
|
+
}
|
|
260
|
+
if (range.startsWith("~")) {
|
|
261
|
+
const rangeParts = parseVersion(range.slice(1));
|
|
262
|
+
if (!rangeParts) {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
return versionParts.major === rangeParts.major && versionParts.minor === rangeParts.minor && versionParts.patch >= rangeParts.patch;
|
|
266
|
+
}
|
|
267
|
+
if (range.startsWith(">=")) {
|
|
268
|
+
const rangeParts = parseVersion(range.slice(2));
|
|
269
|
+
if (!rangeParts) {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
return compareVersions(versionParts, rangeParts) >= 0;
|
|
273
|
+
}
|
|
274
|
+
if (range.startsWith(">")) {
|
|
275
|
+
const rangeParts = parseVersion(range.slice(1));
|
|
276
|
+
if (!rangeParts) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
return compareVersions(versionParts, rangeParts) > 0;
|
|
280
|
+
}
|
|
281
|
+
if (range.startsWith("<=")) {
|
|
282
|
+
const rangeParts = parseVersion(range.slice(2));
|
|
283
|
+
if (!rangeParts) {
|
|
284
|
+
return false;
|
|
285
|
+
}
|
|
286
|
+
return compareVersions(versionParts, rangeParts) <= 0;
|
|
287
|
+
}
|
|
288
|
+
if (range.startsWith("<")) {
|
|
289
|
+
const rangeParts = parseVersion(range.slice(1));
|
|
290
|
+
if (!rangeParts) {
|
|
291
|
+
return false;
|
|
292
|
+
}
|
|
293
|
+
return compareVersions(versionParts, rangeParts) < 0;
|
|
294
|
+
}
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
function parseVersion(version) {
|
|
298
|
+
const match = version.trim().match(/^(\d+)\.(\d+)\.(\d+)/);
|
|
299
|
+
if (!match) {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
return {
|
|
303
|
+
major: Number.parseInt(match[1], 10),
|
|
304
|
+
minor: Number.parseInt(match[2], 10),
|
|
305
|
+
patch: Number.parseInt(match[3], 10)
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
function compareVersions(a, b) {
|
|
309
|
+
if (a.major !== b.major) {
|
|
310
|
+
return a.major - b.major;
|
|
311
|
+
}
|
|
312
|
+
if (a.minor !== b.minor) {
|
|
313
|
+
return a.minor - b.minor;
|
|
314
|
+
}
|
|
315
|
+
return a.patch - b.patch;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const DEFAULT_INSTALL_DIR = join(homedir(), ".ccjk", "packages");
|
|
319
|
+
const INSTALLED_MANIFEST = join(homedir(), ".ccjk", "installed-packages.json");
|
|
320
|
+
const MAX_DOWNLOAD_RETRIES = 3;
|
|
321
|
+
const DOWNLOAD_RETRY_DELAY = 2e3;
|
|
322
|
+
const DOWNLOAD_TIMEOUT = 3e4;
|
|
323
|
+
async function getInstalledPackages() {
|
|
324
|
+
if (!existsSync(INSTALLED_MANIFEST)) {
|
|
325
|
+
return [];
|
|
326
|
+
}
|
|
327
|
+
try {
|
|
328
|
+
const content = await readFile(INSTALLED_MANIFEST, "utf-8");
|
|
329
|
+
return JSON.parse(content);
|
|
330
|
+
} catch {
|
|
331
|
+
return [];
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
async function saveInstalledPackages(packages) {
|
|
335
|
+
const dir = join(homedir(), ".ccjk");
|
|
336
|
+
await mkdir(dir, { recursive: true });
|
|
337
|
+
await writeFileAtomicAsync(INSTALLED_MANIFEST, JSON.stringify(packages, null, 2));
|
|
338
|
+
}
|
|
339
|
+
async function isPackageInstalled(packageId) {
|
|
340
|
+
const installed = await getInstalledPackages();
|
|
341
|
+
return installed.some((pkg) => pkg.package.id === packageId);
|
|
342
|
+
}
|
|
343
|
+
async function downloadPackage(url, destPath, retries = MAX_DOWNLOAD_RETRIES) {
|
|
344
|
+
let lastError = null;
|
|
345
|
+
for (let attempt = 1; attempt <= retries; attempt++) {
|
|
346
|
+
try {
|
|
347
|
+
await mkdir(join(destPath, ".."), { recursive: true });
|
|
348
|
+
const controller = new AbortController();
|
|
349
|
+
const timeoutId = setTimeout(() => controller.abort(), DOWNLOAD_TIMEOUT);
|
|
350
|
+
try {
|
|
351
|
+
const response = await fetch(url, {
|
|
352
|
+
signal: controller.signal
|
|
353
|
+
});
|
|
354
|
+
if (!response.ok) {
|
|
355
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
356
|
+
}
|
|
357
|
+
if (!response.body) {
|
|
358
|
+
throw new Error("Response body is null");
|
|
359
|
+
}
|
|
360
|
+
const fileStream = createWriteStream(destPath);
|
|
361
|
+
await pipeline(
|
|
362
|
+
Readable.fromWeb(response.body),
|
|
363
|
+
fileStream
|
|
364
|
+
);
|
|
365
|
+
clearTimeout(timeoutId);
|
|
366
|
+
return true;
|
|
367
|
+
} catch (error) {
|
|
368
|
+
clearTimeout(timeoutId);
|
|
369
|
+
throw error;
|
|
370
|
+
}
|
|
371
|
+
} catch (error) {
|
|
372
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
373
|
+
if (lastError.name === "AbortError") {
|
|
374
|
+
throw new Error(`Download timeout after ${DOWNLOAD_TIMEOUT}ms`);
|
|
375
|
+
}
|
|
376
|
+
if (attempt < retries) {
|
|
377
|
+
await new Promise((resolve) => setTimeout(resolve, DOWNLOAD_RETRY_DELAY * attempt));
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
throw new Error(
|
|
382
|
+
`Failed to download package after ${retries} attempts: ${lastError?.message || "Unknown error"}`
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
async function verifyChecksum(filePath, expectedChecksum) {
|
|
386
|
+
try {
|
|
387
|
+
const fileBuffer = await readFile(filePath);
|
|
388
|
+
const hash = createHash("sha256");
|
|
389
|
+
hash.update(fileBuffer);
|
|
390
|
+
const actualChecksum = hash.digest("hex");
|
|
391
|
+
if (actualChecksum !== expectedChecksum) {
|
|
392
|
+
throw new Error(
|
|
393
|
+
`Checksum mismatch: expected ${expectedChecksum}, got ${actualChecksum}`
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
return true;
|
|
397
|
+
} catch (error) {
|
|
398
|
+
throw new Error(
|
|
399
|
+
`Checksum verification failed: ${error instanceof Error ? error.message : String(error)}`
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
async function extractPackage(archivePath, destDir) {
|
|
404
|
+
try {
|
|
405
|
+
await mkdir(destDir, { recursive: true });
|
|
406
|
+
if (archivePath.endsWith(".tar.gz") || archivePath.endsWith(".tgz")) {
|
|
407
|
+
await extract({
|
|
408
|
+
file: archivePath,
|
|
409
|
+
cwd: destDir,
|
|
410
|
+
strip: 1
|
|
411
|
+
// Strip top-level directory
|
|
412
|
+
});
|
|
413
|
+
} else if (archivePath.endsWith(".zip")) {
|
|
414
|
+
const result = await exec("unzip", ["-q", "-o", archivePath, "-d", destDir]);
|
|
415
|
+
if (result.exitCode !== 0) {
|
|
416
|
+
throw new Error(`Unzip failed: ${result.stderr || "Unknown error"}`);
|
|
417
|
+
}
|
|
418
|
+
} else {
|
|
419
|
+
throw new Error(`Unsupported archive format: ${archivePath}`);
|
|
420
|
+
}
|
|
421
|
+
} catch (error) {
|
|
422
|
+
throw new Error(
|
|
423
|
+
`Failed to extract package: ${error instanceof Error ? error.message : String(error)}`
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
async function runPostInstall(installPath, manifest) {
|
|
428
|
+
if (!manifest.postInstall) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
try {
|
|
432
|
+
const scriptPath = join(installPath, manifest.postInstall);
|
|
433
|
+
if (!exists(scriptPath)) {
|
|
434
|
+
throw new Error(`Post-install script not found: ${scriptPath}`);
|
|
435
|
+
}
|
|
436
|
+
const result = await exec("node", [scriptPath], {
|
|
437
|
+
nodeOptions: {
|
|
438
|
+
cwd: installPath
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
if (result.exitCode !== 0) {
|
|
442
|
+
throw new Error(`Script exited with code ${result.exitCode}: ${result.stderr || ""}`);
|
|
443
|
+
}
|
|
444
|
+
} catch (error) {
|
|
445
|
+
throw new Error(
|
|
446
|
+
`Post-install script failed: ${error instanceof Error ? error.message : String(error)}`
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
async function rollbackInstallation(installPath, archivePath) {
|
|
451
|
+
try {
|
|
452
|
+
if (existsSync(installPath)) {
|
|
453
|
+
await rm(installPath, { recursive: true, force: true });
|
|
454
|
+
}
|
|
455
|
+
if (archivePath && existsSync(archivePath)) {
|
|
456
|
+
await rm(archivePath, { force: true });
|
|
457
|
+
}
|
|
458
|
+
} catch (error) {
|
|
459
|
+
console.error("Rollback failed:", error);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
async function installPackage(packageId, options = {}) {
|
|
463
|
+
const startTime = Date.now();
|
|
464
|
+
let archivePath;
|
|
465
|
+
let installPath;
|
|
466
|
+
try {
|
|
467
|
+
const alreadyInstalled = await isPackageInstalled(packageId);
|
|
468
|
+
if (alreadyInstalled && !options.force) {
|
|
469
|
+
const pkg2 = await getPackage(packageId);
|
|
470
|
+
if (!pkg2) {
|
|
471
|
+
return {
|
|
472
|
+
success: false,
|
|
473
|
+
package: {},
|
|
474
|
+
error: i18n.t("marketplace:packageNotFound", { name: packageId })
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
return {
|
|
478
|
+
success: true,
|
|
479
|
+
package: pkg2,
|
|
480
|
+
alreadyInstalled: true,
|
|
481
|
+
durationMs: Date.now() - startTime
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
const pkg = await getPackage(packageId);
|
|
485
|
+
if (!pkg) {
|
|
486
|
+
return {
|
|
487
|
+
success: false,
|
|
488
|
+
package: {},
|
|
489
|
+
error: i18n.t("marketplace:packageNotFound", { name: packageId })
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
if (!pkg.downloadUrl) {
|
|
493
|
+
return {
|
|
494
|
+
success: false,
|
|
495
|
+
package: pkg,
|
|
496
|
+
error: i18n.t("marketplace:noDownloadUrl", { name: pkg.name })
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
const warnings = [];
|
|
500
|
+
if (options.codeToolType && pkg.supportedTools) {
|
|
501
|
+
if (!pkg.supportedTools.includes(options.codeToolType)) {
|
|
502
|
+
warnings.push(
|
|
503
|
+
i18n.t("marketplace:incompatibleTool", {
|
|
504
|
+
tool: options.codeToolType,
|
|
505
|
+
supported: pkg.supportedTools.join(", ")
|
|
506
|
+
})
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
const dependencyResults = [];
|
|
511
|
+
if (options.installDependencies !== false && pkg.dependencies) {
|
|
512
|
+
try {
|
|
513
|
+
const dependencyTree = await resolveDependencies(pkg);
|
|
514
|
+
for (const dep of dependencyTree.dependencies) {
|
|
515
|
+
const depResult = await installPackage(dep.package.id, {
|
|
516
|
+
...options,
|
|
517
|
+
installDependencies: true
|
|
518
|
+
// Recursive dependency installation
|
|
519
|
+
});
|
|
520
|
+
dependencyResults.push(depResult);
|
|
521
|
+
if (!depResult.success) {
|
|
522
|
+
throw new Error(
|
|
523
|
+
`Failed to install dependency ${dep.package.id}: ${depResult.error}`
|
|
524
|
+
);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
} catch (error) {
|
|
528
|
+
return {
|
|
529
|
+
success: false,
|
|
530
|
+
package: pkg,
|
|
531
|
+
error: `Dependency resolution failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
532
|
+
durationMs: Date.now() - startTime
|
|
533
|
+
};
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
const targetDir = options.targetDir || DEFAULT_INSTALL_DIR;
|
|
537
|
+
installPath = join(targetDir, pkg.id);
|
|
538
|
+
await mkdir(installPath, { recursive: true });
|
|
539
|
+
const archiveExt = pkg.downloadUrl.endsWith(".zip") ? ".zip" : ".tar.gz";
|
|
540
|
+
archivePath = join(targetDir, `${pkg.id}-${pkg.version}${archiveExt}`);
|
|
541
|
+
try {
|
|
542
|
+
await downloadPackage(pkg.downloadUrl, archivePath);
|
|
543
|
+
} catch (error) {
|
|
544
|
+
throw new Error(
|
|
545
|
+
`Download failed: ${error instanceof Error ? error.message : String(error)}`
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
if (pkg.checksum && !options.skipChecksum) {
|
|
549
|
+
try {
|
|
550
|
+
await verifyChecksum(archivePath, pkg.checksum);
|
|
551
|
+
} catch (error) {
|
|
552
|
+
throw new Error(
|
|
553
|
+
`Checksum verification failed: ${error instanceof Error ? error.message : String(error)}`
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
try {
|
|
558
|
+
await extractPackage(archivePath, installPath);
|
|
559
|
+
} catch (error) {
|
|
560
|
+
throw new Error(
|
|
561
|
+
`Extraction failed: ${error instanceof Error ? error.message : String(error)}`
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
const manifestPath = join(installPath, "ccjk.json");
|
|
565
|
+
let manifest = null;
|
|
566
|
+
if (exists(manifestPath)) {
|
|
567
|
+
try {
|
|
568
|
+
manifest = readJsonFile(manifestPath);
|
|
569
|
+
} catch (error) {
|
|
570
|
+
warnings.push(
|
|
571
|
+
`Failed to read package manifest: ${error instanceof Error ? error.message : String(error)}`
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
if (manifest?.postInstall) {
|
|
576
|
+
try {
|
|
577
|
+
await runPostInstall(installPath, manifest);
|
|
578
|
+
} catch (error) {
|
|
579
|
+
warnings.push(
|
|
580
|
+
`Post-install script failed: ${error instanceof Error ? error.message : String(error)}`
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
if (archivePath && existsSync(archivePath)) {
|
|
585
|
+
await rm(archivePath, { force: true });
|
|
586
|
+
}
|
|
587
|
+
const installed = await getInstalledPackages();
|
|
588
|
+
const installedPackage = {
|
|
589
|
+
package: pkg,
|
|
590
|
+
path: installPath,
|
|
591
|
+
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
592
|
+
source: "marketplace",
|
|
593
|
+
enabled: true
|
|
594
|
+
};
|
|
595
|
+
const existingIndex = installed.findIndex((p) => p.package.id === pkg.id);
|
|
596
|
+
if (existingIndex >= 0) {
|
|
597
|
+
installed[existingIndex] = installedPackage;
|
|
598
|
+
} else {
|
|
599
|
+
installed.push(installedPackage);
|
|
600
|
+
}
|
|
601
|
+
await saveInstalledPackages(installed);
|
|
602
|
+
return {
|
|
603
|
+
success: true,
|
|
604
|
+
package: pkg,
|
|
605
|
+
installedPath: installPath,
|
|
606
|
+
dependencies: dependencyResults.length > 0 ? dependencyResults : void 0,
|
|
607
|
+
warnings: warnings.length > 0 ? warnings : void 0,
|
|
608
|
+
durationMs: Date.now() - startTime
|
|
609
|
+
};
|
|
610
|
+
} catch (error) {
|
|
611
|
+
if (installPath) {
|
|
612
|
+
await rollbackInstallation(installPath, archivePath);
|
|
613
|
+
}
|
|
614
|
+
return {
|
|
615
|
+
success: false,
|
|
616
|
+
package: {},
|
|
617
|
+
error: error instanceof Error ? error.message : String(error),
|
|
618
|
+
durationMs: Date.now() - startTime
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
async function uninstallPackage(packageId, options = {}) {
|
|
623
|
+
try {
|
|
624
|
+
const installed = await getInstalledPackages();
|
|
625
|
+
const pkg = installed.find((p) => p.package.id === packageId);
|
|
626
|
+
if (!pkg) {
|
|
627
|
+
return {
|
|
628
|
+
success: false,
|
|
629
|
+
packageId,
|
|
630
|
+
error: i18n.t("marketplace:packageNotInstalled", { name: packageId })
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
if (!options.force) {
|
|
634
|
+
const dependents = installed.filter(
|
|
635
|
+
(p) => p.package.dependencies && Object.keys(p.package.dependencies).includes(packageId)
|
|
636
|
+
);
|
|
637
|
+
if (dependents.length > 0) {
|
|
638
|
+
return {
|
|
639
|
+
success: false,
|
|
640
|
+
packageId,
|
|
641
|
+
error: i18n.t("marketplace:packageHasDependents", {
|
|
642
|
+
name: packageId,
|
|
643
|
+
dependents: dependents.map((p) => p.package.name).join(", ")
|
|
644
|
+
})
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
if (existsSync(pkg.path)) {
|
|
649
|
+
await rm(pkg.path, { recursive: true, force: true });
|
|
650
|
+
}
|
|
651
|
+
const updated = installed.filter((p) => p.package.id !== packageId);
|
|
652
|
+
await saveInstalledPackages(updated);
|
|
653
|
+
return {
|
|
654
|
+
success: true,
|
|
655
|
+
packageId
|
|
656
|
+
};
|
|
657
|
+
} catch (error) {
|
|
658
|
+
return {
|
|
659
|
+
success: false,
|
|
660
|
+
packageId,
|
|
661
|
+
error: error instanceof Error ? error.message : String(error)
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
async function updatePackage(packageId) {
|
|
666
|
+
const uninstallResult = await uninstallPackage(packageId, { keepConfig: true });
|
|
667
|
+
if (!uninstallResult.success) {
|
|
668
|
+
return {
|
|
669
|
+
success: false,
|
|
670
|
+
package: {},
|
|
671
|
+
error: uninstallResult.error
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
return await installPackage(packageId, { force: true });
|
|
675
|
+
}
|
|
676
|
+
async function checkForUpdates() {
|
|
677
|
+
const installed = await getInstalledPackages();
|
|
678
|
+
const updates = [];
|
|
679
|
+
for (const installedPkg of installed) {
|
|
680
|
+
const latestPkg = await getPackage(installedPkg.package.id);
|
|
681
|
+
if (!latestPkg)
|
|
682
|
+
continue;
|
|
683
|
+
if (latestPkg.version !== installedPkg.package.version) {
|
|
684
|
+
updates.push({
|
|
685
|
+
id: installedPkg.package.id,
|
|
686
|
+
currentVersion: installedPkg.package.version,
|
|
687
|
+
latestVersion: latestPkg.version,
|
|
688
|
+
breaking: false,
|
|
689
|
+
// TODO: Implement semver comparison
|
|
690
|
+
changelog: latestPkg.changelog,
|
|
691
|
+
releaseDate: latestPkg.updatedAt
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
return updates;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
export { getPackage as a, uninstallPackage as b, checkForUpdates as c, installPackage as d, getInstalledPackages as g, isPackageInstalled as i, searchPackages as s, updatePackage as u };
|