@ebowwa/pkg-ops 0.1.17 → 0.1.19
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/dist/bridge.d.ts +63 -0
- package/dist/bridge.d.ts.map +1 -1
- package/dist/config.d.ts +55 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -14
- package/dist/index.js.map +7 -9
- package/dist/types.d.ts +204 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +2 -2
- package/rust/src/lib.rs +438 -0
- package/rust/src/main.rs +177 -0
- package/src/bridge.ts +87 -0
- package/src/config.ts +173 -1
- package/src/index.ts +283 -7
- package/src/types.ts +240 -0
package/rust/src/main.rs
CHANGED
|
@@ -227,6 +227,183 @@ fn handle_installed_info(req: Request) -> ResponseEnvelope {
|
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
// ---------------------------------------------------------------------------
|
|
231
|
+
// Multi-Version Handlers
|
|
232
|
+
// ---------------------------------------------------------------------------
|
|
233
|
+
|
|
234
|
+
fn handle_list_versions(req: Request) -> ResponseEnvelope {
|
|
235
|
+
let params = match req.params.as_ref() {
|
|
236
|
+
Some(p) => p,
|
|
237
|
+
None => {
|
|
238
|
+
return ResponseEnvelope::Error(ErrorResponse::new(
|
|
239
|
+
req.id,
|
|
240
|
+
ErrorObject::invalid_params("Missing params"),
|
|
241
|
+
));
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
let package_name = match params["packageName"].as_str() {
|
|
246
|
+
Some(n) => n,
|
|
247
|
+
None => {
|
|
248
|
+
return ResponseEnvelope::Error(ErrorResponse::new(
|
|
249
|
+
req.id,
|
|
250
|
+
ErrorObject::invalid_params("Missing packageName"),
|
|
251
|
+
));
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
let versions = list_versions(package_name);
|
|
256
|
+
|
|
257
|
+
match Response::new(req.id.clone(), &versions) {
|
|
258
|
+
Ok(r) => ResponseEnvelope::Success(r),
|
|
259
|
+
Err(e) => ResponseEnvelope::Error(ErrorResponse::new(
|
|
260
|
+
req.id,
|
|
261
|
+
ErrorObject::internal_error(e.to_string()),
|
|
262
|
+
)),
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
fn handle_switch_version(req: Request) -> ResponseEnvelope {
|
|
267
|
+
let params = match req.params.as_ref() {
|
|
268
|
+
Some(p) => p,
|
|
269
|
+
None => {
|
|
270
|
+
return ResponseEnvelope::Error(ErrorResponse::new(
|
|
271
|
+
req.id,
|
|
272
|
+
ErrorObject::invalid_params("Missing params"),
|
|
273
|
+
));
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
let package_name = match params["packageName"].as_str() {
|
|
278
|
+
Some(n) => n,
|
|
279
|
+
None => {
|
|
280
|
+
return ResponseEnvelope::Error(ErrorResponse::new(
|
|
281
|
+
req.id,
|
|
282
|
+
ErrorObject::invalid_params("Missing packageName"),
|
|
283
|
+
));
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
let version = match params["version"].as_str() {
|
|
288
|
+
Some(v) => v,
|
|
289
|
+
None => {
|
|
290
|
+
return ResponseEnvelope::Error(ErrorResponse::new(
|
|
291
|
+
req.id,
|
|
292
|
+
ErrorObject::invalid_params("Missing version"),
|
|
293
|
+
));
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
let result = switch_version(package_name, version);
|
|
298
|
+
|
|
299
|
+
match Response::new(req.id.clone(), &result) {
|
|
300
|
+
Ok(r) => ResponseEnvelope::Success(r),
|
|
301
|
+
Err(e) => ResponseEnvelope::Error(ErrorResponse::new(
|
|
302
|
+
req.id,
|
|
303
|
+
ErrorObject::internal_error(e.to_string()),
|
|
304
|
+
)),
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
fn handle_prune_versions(req: Request) -> ResponseEnvelope {
|
|
309
|
+
let params = match req.params.as_ref() {
|
|
310
|
+
Some(p) => p,
|
|
311
|
+
None => {
|
|
312
|
+
return ResponseEnvelope::Error(ErrorResponse::new(
|
|
313
|
+
req.id,
|
|
314
|
+
ErrorObject::invalid_params("Missing params"),
|
|
315
|
+
));
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
let package_name = match params["packageName"].as_str() {
|
|
320
|
+
Some(n) => n,
|
|
321
|
+
None => {
|
|
322
|
+
return ResponseEnvelope::Error(ErrorResponse::new(
|
|
323
|
+
req.id,
|
|
324
|
+
ErrorObject::invalid_params("Missing packageName"),
|
|
325
|
+
));
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
let keep_count = params["keepCount"].as_u64().unwrap_or(2) as u32;
|
|
330
|
+
|
|
331
|
+
let result = prune_versions(package_name, keep_count);
|
|
332
|
+
|
|
333
|
+
match Response::new(req.id.clone(), &result) {
|
|
334
|
+
Ok(r) => ResponseEnvelope::Success(r),
|
|
335
|
+
Err(e) => ResponseEnvelope::Error(ErrorResponse::new(
|
|
336
|
+
req.id,
|
|
337
|
+
ErrorObject::internal_error(e.to_string()),
|
|
338
|
+
)),
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
fn handle_remove_version(req: Request) -> ResponseEnvelope {
|
|
343
|
+
let params = match req.params.as_ref() {
|
|
344
|
+
Some(p) => p,
|
|
345
|
+
None => {
|
|
346
|
+
return ResponseEnvelope::Error(ErrorResponse::new(
|
|
347
|
+
req.id,
|
|
348
|
+
ErrorObject::invalid_params("Missing params"),
|
|
349
|
+
));
|
|
350
|
+
}
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
let package_name = match params["packageName"].as_str() {
|
|
354
|
+
Some(n) => n,
|
|
355
|
+
None => {
|
|
356
|
+
return ResponseEnvelope::Error(ErrorResponse::new(
|
|
357
|
+
req.id,
|
|
358
|
+
ErrorObject::invalid_params("Missing packageName"),
|
|
359
|
+
));
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
let version = match params["version"].as_str() {
|
|
364
|
+
Some(v) => v,
|
|
365
|
+
None => {
|
|
366
|
+
return ResponseEnvelope::Error(ErrorResponse::new(
|
|
367
|
+
req.id,
|
|
368
|
+
ErrorObject::invalid_params("Missing version"),
|
|
369
|
+
));
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
let result = remove_version(package_name, version);
|
|
374
|
+
|
|
375
|
+
let response = match result {
|
|
376
|
+
Ok(msg) => serde_json::json!({
|
|
377
|
+
"success": true,
|
|
378
|
+
"message": msg
|
|
379
|
+
}),
|
|
380
|
+
Err(msg) => serde_json::json!({
|
|
381
|
+
"success": false,
|
|
382
|
+
"message": msg
|
|
383
|
+
}),
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
match Response::new(req.id.clone(), &response) {
|
|
387
|
+
Ok(r) => ResponseEnvelope::Success(r),
|
|
388
|
+
Err(e) => ResponseEnvelope::Error(ErrorResponse::new(
|
|
389
|
+
req.id,
|
|
390
|
+
ErrorObject::internal_error(e.to_string()),
|
|
391
|
+
)),
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
fn handle_get_multi_version_packages(req: Request) -> ResponseEnvelope {
|
|
396
|
+
let packages = get_multi_version_packages();
|
|
397
|
+
|
|
398
|
+
match Response::new(req.id.clone(), &packages) {
|
|
399
|
+
Ok(r) => ResponseEnvelope::Success(r),
|
|
400
|
+
Err(e) => ResponseEnvelope::Error(ErrorResponse::new(
|
|
401
|
+
req.id,
|
|
402
|
+
ErrorObject::internal_error(e.to_string()),
|
|
403
|
+
)),
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
230
407
|
// ---------------------------------------------------------------------------
|
|
231
408
|
// Router
|
|
232
409
|
// ---------------------------------------------------------------------------
|
package/src/bridge.ts
CHANGED
|
@@ -88,6 +88,44 @@ export interface InstalledPackageInfo {
|
|
|
88
88
|
version: string;
|
|
89
89
|
distSizeBytes: number | null;
|
|
90
90
|
installedAt: string | null;
|
|
91
|
+
/** Total number of installed versions */
|
|
92
|
+
totalVersions?: number;
|
|
93
|
+
/** All installed versions */
|
|
94
|
+
versions?: VersionInfo[];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Version information for a package.
|
|
99
|
+
*/
|
|
100
|
+
export interface VersionInfo {
|
|
101
|
+
version: string;
|
|
102
|
+
installedAt: string;
|
|
103
|
+
distSizeBytes: number | null;
|
|
104
|
+
fileCount: number | null;
|
|
105
|
+
active: boolean;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Result of switching versions.
|
|
110
|
+
*/
|
|
111
|
+
export interface SwitchResult {
|
|
112
|
+
success: boolean;
|
|
113
|
+
packageName: string;
|
|
114
|
+
fromVersion: string;
|
|
115
|
+
toVersion: string;
|
|
116
|
+
message: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Result of pruning old versions.
|
|
121
|
+
*/
|
|
122
|
+
export interface PruneResult {
|
|
123
|
+
success: boolean;
|
|
124
|
+
packageName: string;
|
|
125
|
+
removedVersions: string[];
|
|
126
|
+
keptVersions: string[];
|
|
127
|
+
freedBytes: number;
|
|
128
|
+
message: string;
|
|
91
129
|
}
|
|
92
130
|
|
|
93
131
|
interface JsonRpcRequest {
|
|
@@ -281,6 +319,55 @@ export class RustBridge {
|
|
|
281
319
|
return this.sendRequest("installedInfo", {}) as Promise<InstalledPackageInfo[]>;
|
|
282
320
|
}
|
|
283
321
|
|
|
322
|
+
// ---------------------------------------------------------------------------
|
|
323
|
+
// Multi-Version Methods
|
|
324
|
+
// ---------------------------------------------------------------------------
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* List all installed versions of a package.
|
|
328
|
+
*/
|
|
329
|
+
async listVersions(packageName: string): Promise<VersionInfo[]> {
|
|
330
|
+
return this.sendRequest("listVersions", { packageName }) as Promise<VersionInfo[]>;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Switch to a specific installed version.
|
|
335
|
+
*/
|
|
336
|
+
async switchVersion(packageName: string, version: string): Promise<SwitchResult> {
|
|
337
|
+
return this.sendRequest("switchVersion", { packageName, version }) as Promise<SwitchResult>;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Remove old versions, keeping only the N most recent.
|
|
342
|
+
*/
|
|
343
|
+
async pruneVersions(packageName: string, keepCount: number): Promise<PruneResult> {
|
|
344
|
+
return this.sendRequest("pruneVersions", { packageName, keepCount }) as Promise<PruneResult>;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Remove a specific version.
|
|
349
|
+
*/
|
|
350
|
+
async removeVersion(packageName: string, version: string): Promise<{ success: boolean; message: string }> {
|
|
351
|
+
return this.sendRequest("removeVersion", { packageName, version }) as Promise<{ success: boolean; message: string }>;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Get packages with multiple versions installed.
|
|
356
|
+
*/
|
|
357
|
+
async getMultiVersionPackages(): Promise<Array<{
|
|
358
|
+
packageName: string;
|
|
359
|
+
activeVersion: string;
|
|
360
|
+
totalVersions: number;
|
|
361
|
+
versions: string[];
|
|
362
|
+
}>> {
|
|
363
|
+
return this.sendRequest("getMultiVersionPackages", {}) as Promise<Array<{
|
|
364
|
+
packageName: string;
|
|
365
|
+
activeVersion: string;
|
|
366
|
+
totalVersions: number;
|
|
367
|
+
versions: string[];
|
|
368
|
+
}>>;
|
|
369
|
+
}
|
|
370
|
+
|
|
284
371
|
// ---------------------------------------------------------------------------
|
|
285
372
|
// Private Methods
|
|
286
373
|
// ---------------------------------------------------------------------------
|
package/src/config.ts
CHANGED
|
@@ -23,9 +23,23 @@ import { dirname } from "node:path";
|
|
|
23
23
|
// Types
|
|
24
24
|
// ---------------------------------------------------------------------------
|
|
25
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Metadata for a single installed version.
|
|
28
|
+
*/
|
|
29
|
+
export interface VersionMetadata {
|
|
30
|
+
/** ISO timestamp when this version was installed */
|
|
31
|
+
installedAt: string;
|
|
32
|
+
/** Size of the dist directory in bytes */
|
|
33
|
+
distSizeBytes: number | null;
|
|
34
|
+
/** Number of files in dist */
|
|
35
|
+
fileCount: number | null;
|
|
36
|
+
}
|
|
37
|
+
|
|
26
38
|
export interface PackageConfig {
|
|
27
|
-
/**
|
|
39
|
+
/** Currently active version (semver) */
|
|
28
40
|
version: string;
|
|
41
|
+
/** All installed versions with metadata */
|
|
42
|
+
versions: Record<string, VersionMetadata>;
|
|
29
43
|
/** Associated systemd service name (without .service suffix) */
|
|
30
44
|
service?: string;
|
|
31
45
|
/** Whether to auto-start the service after install */
|
|
@@ -190,3 +204,161 @@ export function parsePackageSpec(spec: string): { name: string; version: string
|
|
|
190
204
|
|
|
191
205
|
return { name, version };
|
|
192
206
|
}
|
|
207
|
+
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
// Multi-Version Helper Functions
|
|
210
|
+
// ---------------------------------------------------------------------------
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Get all installed versions for a package.
|
|
214
|
+
*/
|
|
215
|
+
export function getInstalledVersions(packageName: string): VersionMetadata[] {
|
|
216
|
+
const config = loadConfig();
|
|
217
|
+
const pkgConfig = config.packages[packageName];
|
|
218
|
+
if (!pkgConfig?.versions) {
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
return Object.entries(pkgConfig.versions).map(([version, meta]) => ({
|
|
222
|
+
version,
|
|
223
|
+
...meta,
|
|
224
|
+
}));
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Check if a specific version is installed.
|
|
229
|
+
*/
|
|
230
|
+
export function isVersionInstalled(packageName: string, version: string): boolean {
|
|
231
|
+
const config = loadConfig();
|
|
232
|
+
const pkgConfig = config.packages[packageName];
|
|
233
|
+
return !!(pkgConfig?.versions?.[version]);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Get the active version for a package.
|
|
238
|
+
*/
|
|
239
|
+
export function getActiveVersion(packageName: string): string | null {
|
|
240
|
+
const config = loadConfig();
|
|
241
|
+
const pkgConfig = config.packages[packageName];
|
|
242
|
+
return pkgConfig?.version ?? null;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Add a new version to the package config.
|
|
247
|
+
*/
|
|
248
|
+
export function addPackageVersion(
|
|
249
|
+
packageName: string,
|
|
250
|
+
version: string,
|
|
251
|
+
metadata: Omit<VersionMetadata, "installedAt"> & { installedAt?: string }
|
|
252
|
+
): void {
|
|
253
|
+
const config = loadConfig();
|
|
254
|
+
|
|
255
|
+
if (!config.packages[packageName]) {
|
|
256
|
+
config.packages[packageName] = {
|
|
257
|
+
version,
|
|
258
|
+
versions: {},
|
|
259
|
+
service: packageName.replace("@ebowwa/", ""),
|
|
260
|
+
autoStart: true,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
config.packages[packageName].versions[version] = {
|
|
265
|
+
installedAt: metadata.installedAt ?? new Date().toISOString(),
|
|
266
|
+
distSizeBytes: metadata.distSizeBytes ?? null,
|
|
267
|
+
fileCount: metadata.fileCount ?? null,
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
saveConfig(config);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Remove a version from the package config.
|
|
275
|
+
* Returns true if the version was removed, false if it didn't exist.
|
|
276
|
+
*/
|
|
277
|
+
export function removePackageVersion(packageName: string, version: string): boolean {
|
|
278
|
+
const config = loadConfig();
|
|
279
|
+
const pkgConfig = config.packages[packageName];
|
|
280
|
+
|
|
281
|
+
if (!pkgConfig?.versions?.[version]) {
|
|
282
|
+
return false;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
delete pkgConfig.versions[version];
|
|
286
|
+
|
|
287
|
+
// If removing the active version, switch to the most recent remaining version
|
|
288
|
+
if (pkgConfig.version === version) {
|
|
289
|
+
const remainingVersions = Object.keys(pkgConfig.versions).sort((a, b) => {
|
|
290
|
+
// Sort by installedAt descending (most recent first)
|
|
291
|
+
const aTime = pkgConfig.versions[a]?.installedAt ?? "";
|
|
292
|
+
const bTime = pkgConfig.versions[b]?.installedAt ?? "";
|
|
293
|
+
return bTime.localeCompare(aTime);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
if (remainingVersions.length > 0) {
|
|
297
|
+
pkgConfig.version = remainingVersions[0];
|
|
298
|
+
} else {
|
|
299
|
+
// No versions left, remove the package entirely
|
|
300
|
+
delete config.packages[packageName];
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
saveConfig(config);
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Set the active version for a package.
|
|
310
|
+
* Returns true if successful, false if version not installed.
|
|
311
|
+
*/
|
|
312
|
+
export function setActiveVersion(packageName: string, version: string): boolean {
|
|
313
|
+
const config = loadConfig();
|
|
314
|
+
const pkgConfig = config.packages[packageName];
|
|
315
|
+
|
|
316
|
+
if (!pkgConfig?.versions?.[version]) {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
pkgConfig.version = version;
|
|
321
|
+
saveConfig(config);
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Get the count of installed versions for a package.
|
|
327
|
+
*/
|
|
328
|
+
export function getVersionCount(packageName: string): number {
|
|
329
|
+
const config = loadConfig();
|
|
330
|
+
const pkgConfig = config.packages[packageName];
|
|
331
|
+
return Object.keys(pkgConfig?.versions ?? {}).length;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Get packages with multiple versions installed.
|
|
336
|
+
*/
|
|
337
|
+
export function getPackagesWithMultipleVersions(): Array<{
|
|
338
|
+
name: string;
|
|
339
|
+
activeVersion: string;
|
|
340
|
+
totalVersions: number;
|
|
341
|
+
versions: string[];
|
|
342
|
+
}> {
|
|
343
|
+
const config = loadConfig();
|
|
344
|
+
const result: Array<{
|
|
345
|
+
name: string;
|
|
346
|
+
activeVersion: string;
|
|
347
|
+
totalVersions: number;
|
|
348
|
+
versions: string[];
|
|
349
|
+
}> = [];
|
|
350
|
+
|
|
351
|
+
for (const [name, pkgConfig] of Object.entries(config.packages)) {
|
|
352
|
+
const versions = Object.keys(pkgConfig.versions ?? {});
|
|
353
|
+
if (versions.length > 1) {
|
|
354
|
+
result.push({
|
|
355
|
+
name,
|
|
356
|
+
activeVersion: pkgConfig.version,
|
|
357
|
+
totalVersions: versions.length,
|
|
358
|
+
versions: versions.sort((a, b) => b.localeCompare(a, undefined, { numeric: true })),
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return result;
|
|
364
|
+
}
|