Every path the plugin owns is under $DSH_HOME. Only two directories in the container this runs in are persistent volumes ($DSH_HOME and the workspace root); $HOME/.local -- where 'uv tool install' puts slopo and its CPython by default -- is on the container's writable layer and dies on recreation, not merely on restart. renderSlopoConfig writes from a closed allow-list because slopo treats an unknown config key as a hard error, and quotes every scalar because a path can contain a colon or a leading dash. Verified against slopo 0.8.0 itself: it reads the generated file back with no complaint and round-trips all 19 keys. The embedding API key is deliberately absent -- slopo reads SLOPO_EMBEDDING_API_KEY from the environment ahead of the YAML value, so the secret never lands on disk.
80 lines
3.1 KiB
JavaScript
80 lines
3.1 KiB
JavaScript
/**
|
|
* Where this plugin keeps things on disk.
|
|
*
|
|
* Every path here is deliberately under `$DSH_HOME`, because `$DSH_HOME` is the
|
|
* one directory that survives the container this harness usually runs in. The
|
|
* uv-managed slopo install and the CPython it needs would otherwise live in
|
|
* `~/.local`, which is on the container's writable layer and is lost whenever
|
|
* the container is recreated rather than merely restarted.
|
|
*
|
|
* Relative paths are never used: slopo resolves every relative path against the
|
|
* *process working directory*, not against the config file that named it, so a
|
|
* relative path in a config invoked as `--config /elsewhere/x.yaml` still lands
|
|
* in whatever directory the caller happened to be in.
|
|
*/
|
|
|
|
import { createHash } from 'node:crypto'
|
|
import { homedir } from 'node:os'
|
|
import { basename, join, resolve } from 'node:path'
|
|
|
|
/** Plugin name, used for the `$DSH_HOME/plugin-data/<name>` directory. */
|
|
export const PLUGIN_NAME = 'dsh-slopo'
|
|
|
|
/** The dsh home directory: `$DSH_HOME`, else `~/.dsh`. */
|
|
export function resolveDshHome(env = process.env) {
|
|
const configured = typeof env.DSH_HOME === 'string' ? env.DSH_HOME.trim() : ''
|
|
return configured === '' ? join(homedir(), '.dsh') : resolve(configured)
|
|
}
|
|
|
|
/**
|
|
* A stable, filesystem-safe key for one workspace.
|
|
*
|
|
* The basename is for a human reading the directory listing; the hash is what
|
|
* actually identifies it, so two checkouts both named `app` cannot collide.
|
|
*/
|
|
export function workspaceKey(cwd) {
|
|
const absolute = resolve(cwd)
|
|
const name =
|
|
basename(absolute)
|
|
.replace(/[^A-Za-z0-9._-]+/g, '-')
|
|
.replace(/^[-.]+|-+$/g, '') || 'workspace'
|
|
const hash = createHash('sha256').update(absolute).digest('hex').slice(0, 8)
|
|
return `${name}-${hash}`
|
|
}
|
|
|
|
/** Per-workspace state: the slopo config, index database, ignore file and log. */
|
|
export function workspaceStateDir(dshHome, cwd) {
|
|
return join(dshHome, 'plugin-data', PLUGIN_NAME, 'workspaces', workspaceKey(cwd))
|
|
}
|
|
|
|
/** Root of this plugin's own data, independent of any workspace. */
|
|
export function pluginDataDir(dshHome) {
|
|
return join(dshHome, 'plugin-data', PLUGIN_NAME)
|
|
}
|
|
|
|
/**
|
|
* The directory `uv tool install` writes the slopo venv and shim into.
|
|
*
|
|
* Redirected here from its `~/.local/share/uv/tools` default for the durability
|
|
* reason in the file header.
|
|
*/
|
|
export function managedToolDir(dshHome) {
|
|
return join(pluginDataDir(dshHome), 'uv-tools')
|
|
}
|
|
|
|
/** The directory uv keeps its downloaded CPython builds in. */
|
|
export function managedPythonDir(dshHome) {
|
|
return join(pluginDataDir(dshHome), 'uv-python')
|
|
}
|
|
|
|
/** The slopo executable inside {@link managedToolDir}. */
|
|
export function managedSlopoPath(dshHome) {
|
|
return join(managedToolDir(dshHome), 'bin', process.platform === 'win32' ? 'slopo.exe' : 'slopo')
|
|
}
|
|
|
|
/** Slopo's own default install location, for machines that already have one. */
|
|
export function userLocalSlopoPath(env = process.env) {
|
|
const home = typeof env.HOME === 'string' && env.HOME !== '' ? env.HOME : homedir()
|
|
return join(home, '.local', 'bin', process.platform === 'win32' ? 'slopo.exe' : 'slopo')
|
|
}
|