feat(cli): friendly missing-project status and per-project daemon state (#87)

- Block project-aware commands when ktx.yaml is absent and render a
  friendly "run ktx setup" message (plain or JSON) instead of leaking
  ENOENT or "Project: ..." noise.
- Make ktx status project detect the missing config and emit the same
  message via a shared renderMissingProjectMessage helper.
- Move the managed Python daemon state, stdout, and stderr files out of
  the shared runtime root into {projectDir}/.ktx/runtime so multiple
  projects no longer share a single daemon record.
- Simplify the runtime install root to ~/.ktx/runtime on every platform
  and split the daemon-specific paths into managedPythonDaemonLayout,
  threading projectDir through start, stop, and stop-all paths.
This commit is contained in:
Andrey Avtomonov 2026-05-14 14:35:55 +02:00 committed by GitHub
parent 6d7d90571e
commit e28b10454a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 450 additions and 248 deletions

View file

@ -61,6 +61,15 @@ export interface ManagedPythonRuntimeLayout {
assetManifestPath: string;
pythonPath: string;
daemonPath: string;
}
export interface ManagedPythonDaemonLayoutOptions extends ManagedPythonRuntimeLayoutOptions {
projectDir: string;
}
export interface ManagedPythonDaemonLayout extends ManagedPythonRuntimeLayout {
projectDir: string;
daemonStateDir: string;
daemonStatePath: string;
daemonStdoutPath: string;
daemonStderrPath: string;
@ -114,17 +123,11 @@ function defaultAssetDir(): string {
return fileURLToPath(new URL('../assets/python/', import.meta.url));
}
function runtimeRootFor(input: Required<Pick<ManagedPythonRuntimeLayoutOptions, 'platform' | 'env' | 'homeDir'>>): string {
function runtimeRootFor(input: { env: NodeJS.ProcessEnv; homeDir: string }): string {
if (input.env.KTX_RUNTIME_ROOT) {
return input.env.KTX_RUNTIME_ROOT;
}
if (input.platform === 'darwin') {
return join(input.homeDir, 'Library', 'Application Support', 'kaelio', 'ktx', 'runtime');
}
if (input.platform === 'win32') {
return join(input.env.LOCALAPPDATA ?? join(input.homeDir, 'AppData', 'Local'), 'Kaelio', 'KTX', 'runtime');
}
return join(input.env.XDG_DATA_HOME ?? join(input.homeDir, '.local', 'share'), 'kaelio', 'ktx', 'runtime');
return join(input.homeDir, '.ktx', 'runtime');
}
function executablePath(venvDir: string, platform: NodeJS.Platform, name: string): string {
@ -138,7 +141,7 @@ export function managedPythonRuntimeLayout(options: ManagedPythonRuntimeLayoutOp
const platform = options.platform ?? process.platform;
const env = options.env ?? process.env;
const homeDir = options.homeDir ?? homedir();
const runtimeRoot = options.runtimeRoot ?? runtimeRootFor({ platform, env, homeDir });
const runtimeRoot = options.runtimeRoot ?? runtimeRootFor({ env, homeDir });
const versionDir = join(runtimeRoot, options.cliVersion);
const venvDir = join(versionDir, '.venv');
const assetDir = options.assetDir ?? defaultAssetDir();
@ -154,9 +157,19 @@ export function managedPythonRuntimeLayout(options: ManagedPythonRuntimeLayoutOp
assetManifestPath: join(assetDir, 'manifest.json'),
pythonPath: executablePath(venvDir, platform, 'python'),
daemonPath: executablePath(venvDir, platform, 'ktx-daemon'),
daemonStatePath: join(versionDir, 'daemon.json'),
daemonStdoutPath: join(versionDir, 'daemon.stdout.log'),
daemonStderrPath: join(versionDir, 'daemon.stderr.log'),
};
}
export function managedPythonDaemonLayout(options: ManagedPythonDaemonLayoutOptions): ManagedPythonDaemonLayout {
const runtime = managedPythonRuntimeLayout(options);
const daemonStateDir = join(options.projectDir, '.ktx', 'runtime');
return {
...runtime,
projectDir: options.projectDir,
daemonStateDir,
daemonStatePath: join(daemonStateDir, 'daemon.json'),
daemonStdoutPath: join(daemonStateDir, 'daemon.stdout.log'),
daemonStderrPath: join(daemonStateDir, 'daemon.stderr.log'),
};
}