|
| 1 | +import { ParameterSetting, Plan, StatefulParameter, getPty } from '@codifycli/plugin-core'; |
| 2 | + |
| 3 | +import { RustConfig } from './rust-resource.js'; |
| 4 | + |
| 5 | +function packageName(pkg: string): string { |
| 6 | + const atIndex = pkg.lastIndexOf('@'); |
| 7 | + return atIndex > 0 ? pkg.slice(0, atIndex) : pkg; |
| 8 | +} |
| 9 | + |
| 10 | +function packageVersion(pkg: string): string | undefined { |
| 11 | + const atIndex = pkg.lastIndexOf('@'); |
| 12 | + return atIndex > 0 ? pkg.slice(atIndex + 1) : undefined; |
| 13 | +} |
| 14 | + |
| 15 | +function parseCargoList(output: string): string[] { |
| 16 | + return output |
| 17 | + .split('\n') |
| 18 | + .filter((line) => /^\S+\s+v[\d.]+.*:$/.test(line.trim())) |
| 19 | + .map((line) => { |
| 20 | + const match = line.trim().match(/^(\S+)\s+v([\d.]+[^\s:]*):/); |
| 21 | + return match ? `${match[1]}@${match[2]}` : null; |
| 22 | + }) |
| 23 | + .filter((x): x is string => x !== null); |
| 24 | +} |
| 25 | + |
| 26 | +export class CargoPackagesParameter extends StatefulParameter<RustConfig, string[]> { |
| 27 | + getSettings(): ParameterSetting { |
| 28 | + return { |
| 29 | + type: 'array', |
| 30 | + isElementEqual: this.isEqual, |
| 31 | + filterInStatelessMode: (desired, current) => |
| 32 | + current.filter((c) => desired.some((d) => packageName(d) === packageName(c))), |
| 33 | + }; |
| 34 | + } |
| 35 | + |
| 36 | + async refresh(): Promise<string[] | null> { |
| 37 | + const $ = getPty(); |
| 38 | + const { data } = await $.spawnSafe('cargo install --list', { interactive: true }); |
| 39 | + if (!data) return []; |
| 40 | + return parseCargoList(data); |
| 41 | + } |
| 42 | + |
| 43 | + async add(valuesToAdd: string[]): Promise<void> { |
| 44 | + await this.install(valuesToAdd); |
| 45 | + } |
| 46 | + |
| 47 | + async modify(newValue: string[], previousValue: string[], plan: Plan<RustConfig>): Promise<void> { |
| 48 | + const toInstall = newValue.filter((n) => !previousValue.some((p) => packageName(n) === packageName(p))); |
| 49 | + const toUninstall = previousValue.filter((p) => !newValue.some((n) => packageName(n) === packageName(p))); |
| 50 | + |
| 51 | + if (plan.isStateful && toUninstall.length > 0) { |
| 52 | + await this.uninstall(toUninstall); |
| 53 | + } |
| 54 | + await this.install(toInstall); |
| 55 | + } |
| 56 | + |
| 57 | + async remove(valuesToRemove: string[]): Promise<void> { |
| 58 | + await this.uninstall(valuesToRemove); |
| 59 | + } |
| 60 | + |
| 61 | + private async install(packages: string[]): Promise<void> { |
| 62 | + if (packages.length === 0) return; |
| 63 | + const $ = getPty(); |
| 64 | + for (const pkg of packages) { |
| 65 | + const name = packageName(pkg); |
| 66 | + const version = packageVersion(pkg); |
| 67 | + const versionFlag = version ? ` --version ${version}` : ''; |
| 68 | + await $.spawn(`cargo install${versionFlag} ${name}`, { interactive: true }); |
| 69 | + } |
| 70 | + } |
| 71 | + |
| 72 | + private async uninstall(packages: string[]): Promise<void> { |
| 73 | + if (packages.length === 0) return; |
| 74 | + const $ = getPty(); |
| 75 | + await $.spawn(`cargo uninstall ${packages.map(packageName).join(' ')}`, { interactive: true }); |
| 76 | + } |
| 77 | + |
| 78 | + isEqual(desired: string, current: string): boolean { |
| 79 | + if (!desired.includes('@')) { |
| 80 | + return packageName(desired) === packageName(current); |
| 81 | + } |
| 82 | + return desired === current; |
| 83 | + } |
| 84 | +} |
0 commit comments