fix(cli): install managed runtime with required Python (#152)

This commit is contained in:
Andrey Avtomonov 2026-05-19 18:18:38 +02:00 committed by GitHub
parent 7cec0041eb
commit af0567c57e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 347 additions and 39 deletions

View file

@ -29,8 +29,18 @@ export interface KtxSetupProjectArgs {
allowBack?: boolean;
}
export type KtxSetupCreatedProjectCleanup =
| { kind: 'remove-project-dir'; projectDir: string }
| { kind: 'remove-ktx-scaffold'; projectDir: string };
export type KtxSetupProjectResult =
| { status: 'ready'; projectDir: string; project: KtxLocalProject; confirmedCreation?: boolean }
| {
status: 'ready';
projectDir: string;
project: KtxLocalProject;
confirmedCreation?: boolean;
createdProjectCleanup?: KtxSetupCreatedProjectCleanup;
}
| { status: 'back'; projectDir: string }
| { status: 'cancelled'; projectDir: string }
| { status: 'missing-input'; projectDir: string };
@ -49,7 +59,12 @@ export interface KtxSetupProjectDeps {
}
type PromptProjectDirResult =
| { status: 'selected'; projectDir: string; confirmedCreation: boolean }
| {
status: 'selected';
projectDir: string;
confirmedCreation: boolean;
createdProjectCleanup?: KtxSetupCreatedProjectCleanup;
}
| { status: 'cancelled'; projectDir: string }
| { status: 'missing-input'; projectDir: string }
| { status: 'back'; projectDir: string };
@ -92,12 +107,29 @@ async function existingFolderState(
}
type ConfirmProjectDirResult =
| { status: 'confirmed'; confirmedCreation: boolean }
| {
status: 'confirmed';
confirmedCreation: boolean;
createdProjectCleanup?: KtxSetupCreatedProjectCleanup;
}
| { status: 'choose-another' }
| { status: 'back' }
| { status: 'cancelled' }
| { status: 'not-directory' };
function cleanupForFolderState(
projectDir: string,
state: Awaited<ReturnType<typeof existingFolderState>>,
): KtxSetupCreatedProjectCleanup | undefined {
if (state === 'missing') {
return { kind: 'remove-project-dir', projectDir };
}
if (state === 'empty-directory') {
return { kind: 'remove-ktx-scaffold', projectDir };
}
return undefined;
}
async function confirmProjectDir(
selectedDir: string,
io: KtxCliIo,
@ -137,7 +169,7 @@ async function confirmProjectDir(
if (action === 'choose-another') return { status: 'choose-another' };
if (action === 'back') return { status: 'back' };
if (action !== 'create') return { status: 'cancelled' };
return { status: 'confirmed', confirmedCreation: true };
return { status: 'confirmed', confirmedCreation: true, createdProjectCleanup: cleanupForFolderState(selectedDir, state) };
}
async function normalizeSetupGitignore(projectDir: string): Promise<void> {
@ -220,10 +252,28 @@ async function promptForNewProjectDir(
if (confirmed.status === 'choose-another') continue;
if (confirmed.status === 'back') return { status: 'back', projectDir };
if (confirmed.status === 'cancelled') return { status: 'cancelled', projectDir };
return { status: 'selected', projectDir: selectedDir, confirmedCreation: confirmed.confirmedCreation };
return {
status: 'selected',
projectDir: selectedDir,
confirmedCreation: confirmed.confirmedCreation,
...(confirmed.createdProjectCleanup ? { createdProjectCleanup: confirmed.createdProjectCleanup } : {}),
};
}
}
async function createProjectWithCleanup(
projectDir: string,
deps: KtxSetupProjectDeps,
): Promise<{ project: KtxLocalProject; createdProjectCleanup?: KtxSetupCreatedProjectCleanup }> {
const state = await existingFolderState(projectDir);
const project = await createProject(projectDir, deps);
const createdProjectCleanup = cleanupForFolderState(projectDir, state);
return {
project,
...(createdProjectCleanup ? { createdProjectCleanup } : {}),
};
}
export async function runKtxSetupProjectStep(
args: KtxSetupProjectArgs,
io: KtxCliIo,
@ -244,9 +294,14 @@ export async function runKtxSetupProjectStep(
}
if (args.mode === 'new') {
const project = await createProject(projectDir, deps);
const { project, createdProjectCleanup } = await createProjectWithCleanup(projectDir, deps);
printProjectSummary(io, projectDir);
return { status: 'ready', projectDir, project };
return {
status: 'ready',
projectDir,
project,
...(createdProjectCleanup ? { createdProjectCleanup } : {}),
};
}
if (args.mode === 'prompt-new') {
@ -277,6 +332,7 @@ export async function runKtxSetupProjectStep(
projectDir: selected.projectDir,
project,
confirmedCreation: selected.confirmedCreation,
...(selected.createdProjectCleanup ? { createdProjectCleanup: selected.createdProjectCleanup } : {}),
};
}
@ -291,9 +347,14 @@ export async function runKtxSetupProjectStep(
io.stderr.write('Missing setup choice: pass --new or --yes to create a project in non-interactive setup.\n');
return { status: 'missing-input', projectDir };
}
const project = await createProject(projectDir, deps);
const { project, createdProjectCleanup } = await createProjectWithCleanup(projectDir, deps);
printProjectSummary(io, projectDir);
return { status: 'ready', projectDir, project };
return {
status: 'ready',
projectDir,
project,
...(createdProjectCleanup ? { createdProjectCleanup } : {}),
};
}
if (!io.stdout.isTTY && !deps.prompts) {
@ -332,9 +393,14 @@ export async function runKtxSetupProjectStep(
}
if (choice === 'current') {
const project = await createProject(projectDir, deps);
const { project, createdProjectCleanup } = await createProjectWithCleanup(projectDir, deps);
printProjectSummary(io, projectDir);
return { status: 'ready', projectDir, project };
return {
status: 'ready',
projectDir,
project,
...(createdProjectCleanup ? { createdProjectCleanup } : {}),
};
}
if (choice === 'new-default') {
@ -349,6 +415,7 @@ export async function runKtxSetupProjectStep(
projectDir: defaultProjectDir,
project,
confirmedCreation: confirmed.confirmedCreation,
...(confirmed.createdProjectCleanup ? { createdProjectCleanup: confirmed.createdProjectCleanup } : {}),
};
}
@ -372,7 +439,13 @@ export async function runKtxSetupProjectStep(
if (confirmed.status === 'cancelled') return { status: 'cancelled', projectDir };
const project = await createProject(customDir, deps);
printProjectSummary(io, customDir);
return { status: 'ready', projectDir: customDir, project, confirmedCreation: confirmed.confirmedCreation };
return {
status: 'ready',
projectDir: customDir,
project,
confirmedCreation: confirmed.confirmedCreation,
...(confirmed.createdProjectCleanup ? { createdProjectCleanup: confirmed.createdProjectCleanup } : {}),
};
}
prompts.cancel('Setup cancelled.');