feat(hub): guide starter setup (#3651)
* feat(hub): guide setup with provider IDs * fix(hub): preserve login setup state * feat(hub): select starter agent runtime Use the connected daemon's provider snapshot so generated Hub bundles only contain complete provider, model, and mode combinations. * fix(hub): stage starter runtime choices Keep providers with valid modes selectable when the daemon has no default mode, so users explicitly choose the complete runtime. * fix(hub): scope runtime choices to workspace Provider capabilities can vary by repository, so request the snapshot for the bundle directory that guided setup will deploy.
This commit is contained in:
@@ -21,28 +21,40 @@ import type { HubCredentialStore } from "./credentials.js";
|
||||
import type { HubDaemonConnection } from "./daemon-client.js";
|
||||
import { withHubDaemon } from "./daemon-client.js";
|
||||
import { HubCommandError } from "./error.js";
|
||||
import type { HubConfigurationResources, HubHttpClient, HubProject } from "./hub-client/index.js";
|
||||
import type {
|
||||
HubConfigurationResources,
|
||||
HubHttpClient,
|
||||
HubProject,
|
||||
HubSetupResources,
|
||||
} from "./hub-client/index.js";
|
||||
import {
|
||||
createHubInitBundle,
|
||||
createHubInitScaffold,
|
||||
hubLoginResumeCommand,
|
||||
planHubInitOpening,
|
||||
resolveHubInitConnection,
|
||||
resolveHubInitProjects,
|
||||
type HubInitProvider,
|
||||
} from "./init-plan.js";
|
||||
import type { CliLoginFlow } from "./login-flow.js";
|
||||
import { runHubLogin } from "./login.js";
|
||||
import { runHubConnect } from "./connect.js";
|
||||
import { runHubDeployBundle } from "./deploy.js";
|
||||
import { runHubProjects } from "./projects.js";
|
||||
import type { HubReporter } from "./reporter.js";
|
||||
import { normalizeHubOrigin } from "./origin.js";
|
||||
import {
|
||||
availableStarterAgentProviders,
|
||||
selectedStarterAgentRuntime,
|
||||
suggestedStarterAgentChoice,
|
||||
type HubStarterAgentProvider,
|
||||
type HubStarterAgentRuntime,
|
||||
} from "./starter-agent-runtime.js";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
const DAEMON_READY_TIMEOUT_MS = 60_000;
|
||||
const DAEMON_READY_POLL_MS = 250;
|
||||
|
||||
interface HubInitEnvironment {
|
||||
export interface HubGuidedSetupEnvironment {
|
||||
env: Readonly<Record<string, string | undefined>>;
|
||||
credentials: HubCredentialStore;
|
||||
hub: HubHttpClient;
|
||||
@@ -50,6 +62,19 @@ interface HubInitEnvironment {
|
||||
daemon: HubDaemonConnection;
|
||||
reporter: HubReporter;
|
||||
cwd(): string;
|
||||
isInteractive?(): boolean;
|
||||
prompts?: {
|
||||
confirm(message: string, initialValue: boolean): Promise<boolean>;
|
||||
select(options: Parameters<typeof select<string>>[0]): Promise<string>;
|
||||
text(options: Parameters<typeof text>[0]): Promise<string>;
|
||||
message(value: string): void;
|
||||
};
|
||||
}
|
||||
|
||||
interface HubGuidedSetupState {
|
||||
origin?: string;
|
||||
daemonId?: string;
|
||||
deploy?: boolean;
|
||||
}
|
||||
|
||||
class HubInitCancelledError extends Error {}
|
||||
@@ -61,7 +86,7 @@ function initErrorMessage(error: unknown): string {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
|
||||
export function addHubInitCommand(parent: Command, environment: HubInitEnvironment): void {
|
||||
export function addHubInitCommand(parent: Command, environment: HubGuidedSetupEnvironment): void {
|
||||
parent
|
||||
.command("init")
|
||||
.description("Create and optionally deploy a safe starter Hub bundle")
|
||||
@@ -79,8 +104,15 @@ export function addHubInitCommand(parent: Command, environment: HubInitEnvironme
|
||||
});
|
||||
}
|
||||
|
||||
export async function runHubInit(environment: HubInitEnvironment): Promise<void> {
|
||||
requireInteractiveTerminal();
|
||||
export async function runHubInit(environment: HubGuidedSetupEnvironment): Promise<void> {
|
||||
await runHubGuidedSetup(environment);
|
||||
}
|
||||
|
||||
export async function runHubGuidedSetup(
|
||||
environment: HubGuidedSetupEnvironment,
|
||||
state: HubGuidedSetupState = {},
|
||||
): Promise<void> {
|
||||
requireInteractiveTerminal(environment);
|
||||
intro("Set up Paseo Hub");
|
||||
|
||||
const cwd = environment.cwd();
|
||||
@@ -91,28 +123,24 @@ export async function runHubInit(environment: HubInitEnvironment): Promise<void>
|
||||
});
|
||||
if (
|
||||
opening.replaceExisting &&
|
||||
!(await requiredConfirm("Replace the existing .paseo/ Hub bundle?", false))
|
||||
!(await requiredConfirm(environment, "Replace the existing .paseo/ Hub bundle?", false))
|
||||
) {
|
||||
throw new HubInitCancelledError("Existing .paseo/ bundle left unchanged.");
|
||||
}
|
||||
|
||||
const origin = await ensureLogin(activeLogin?.origin, environment);
|
||||
const daemonId = await ensureDaemonConnection(origin, environment);
|
||||
const origin = state.origin ?? (await ensureLogin(activeLogin?.origin, environment));
|
||||
const daemonId = state.daemonId ?? (await ensureDaemonConnection(origin, environment));
|
||||
const project = await chooseProject(origin, environment);
|
||||
const resources = await loadConfigurationResources(origin, environment);
|
||||
const daemon = resources.daemons.find(({ id }) => id === daemonId);
|
||||
if (daemon === undefined) {
|
||||
throw new HubCommandError(
|
||||
"HUB_DAEMON_RESOURCE_MISSING",
|
||||
"The connected daemon is not available in this Hub organization. Reconnect it and try again.",
|
||||
);
|
||||
}
|
||||
const resources = await loadSetupResources(origin, environment);
|
||||
const daemon = await loadDaemonResource(origin, daemonId, environment);
|
||||
log.success(`Connected as ${daemon.slug}`);
|
||||
const provider = await chooseProvider();
|
||||
const providerFilters = await collectProviderFilters(provider, resources, cwd);
|
||||
const agent = await chooseStarterAgentRuntime(environment, cwd);
|
||||
const provider = await chooseProvider(environment);
|
||||
const providerFilters = await collectProviderFilters(provider, resources, cwd, environment);
|
||||
const scaffold = createHubInitScaffold({
|
||||
cwd,
|
||||
daemonSlug: daemon.slug,
|
||||
agent,
|
||||
provider,
|
||||
providerFilters,
|
||||
});
|
||||
@@ -132,7 +160,7 @@ export async function runHubInit(environment: HubInitEnvironment): Promise<void>
|
||||
await writeScaffold(cwd, scaffold, opening.replaceExisting);
|
||||
log.success(`Created .paseo/hub.yml and ${scaffold.workflowPath}`);
|
||||
|
||||
const deploy = await requiredConfirm("Deploy now?", true);
|
||||
const deploy = state.deploy ?? (await requiredConfirm(environment, "Deploy now?", true));
|
||||
if (deploy) {
|
||||
await withSpinner("Deploying bundle", async () => {
|
||||
await runHubDeployBundle({ project: project.slug, hub: origin }, bundle, {
|
||||
@@ -145,7 +173,7 @@ export async function runHubInit(environment: HubInitEnvironment): Promise<void>
|
||||
});
|
||||
log.success("Deployed");
|
||||
} else {
|
||||
log.message(`Skipped deployment. Run: paseo hub deploy -p ${project.slug}`);
|
||||
reportMessage(environment, `Skipped deployment. Run: paseo hub deploy -p ${project.slug}`);
|
||||
}
|
||||
|
||||
const activityUrl = new URL(`/projects/${project.slug}/activity`, origin).toString();
|
||||
@@ -153,11 +181,33 @@ export async function runHubInit(environment: HubInitEnvironment): Promise<void>
|
||||
outro(deploy ? "Hub is ready" : "Hub bundle is ready");
|
||||
}
|
||||
|
||||
export async function continueHubGuidedSetup(
|
||||
origin: string,
|
||||
environment: HubGuidedSetupEnvironment,
|
||||
): Promise<void> {
|
||||
if (!(await requiredConfirm(environment, `Connect this daemon to ${origin}?`, true))) {
|
||||
reportMessage(
|
||||
environment,
|
||||
`Skipped daemon connection. Run: ${hubLoginResumeCommand("connect", origin)}; then paseo hub init`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
const daemonId = await ensureDaemonConnection(origin, environment, true);
|
||||
if (!(await requiredConfirm(environment, "Initialize and deploy a starter workflow?", true))) {
|
||||
reportMessage(
|
||||
environment,
|
||||
`Skipped starter workflow. Run: ${hubLoginResumeCommand("init", origin)}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
await runHubGuidedSetup(environment, { origin, daemonId, deploy: true });
|
||||
}
|
||||
|
||||
async function ensureLogin(
|
||||
activeOrigin: string | undefined,
|
||||
environment: HubInitEnvironment,
|
||||
environment: HubGuidedSetupEnvironment,
|
||||
): Promise<string> {
|
||||
const endpoint = await requiredSelect({
|
||||
const endpoint = await requiredSelect(environment, {
|
||||
message: "Hub endpoint",
|
||||
initialValue:
|
||||
activeOrigin === undefined || activeOrigin === DEFAULT_HUB_ORIGIN ? "hosted" : "custom",
|
||||
@@ -169,7 +219,7 @@ async function ensureLogin(
|
||||
const origin =
|
||||
endpoint === "hosted"
|
||||
? DEFAULT_HUB_ORIGIN
|
||||
: await requiredText({
|
||||
: await requiredText(environment, {
|
||||
message: "Custom Hub URL",
|
||||
initialValue:
|
||||
activeOrigin === undefined || activeOrigin === DEFAULT_HUB_ORIGIN
|
||||
@@ -189,23 +239,17 @@ async function ensureLogin(
|
||||
log.success(`Logged in to ${normalizedOrigin}`);
|
||||
return normalizedOrigin;
|
||||
}
|
||||
await runHubLogin(
|
||||
normalizedOrigin,
|
||||
{},
|
||||
{
|
||||
env: environment.env,
|
||||
credentials: environment.credentials,
|
||||
flow: environment.login,
|
||||
reporter: environment.reporter,
|
||||
},
|
||||
);
|
||||
environment.reporter.progress(`Logging in to ${normalizedOrigin}`);
|
||||
const credential = await environment.login.authorize(normalizedOrigin);
|
||||
environment.credentials.save({ origin: normalizedOrigin, credential });
|
||||
log.success(`Logged in to ${normalizedOrigin}`);
|
||||
return normalizedOrigin;
|
||||
}
|
||||
|
||||
async function ensureDaemonConnection(
|
||||
origin: string,
|
||||
environment: HubInitEnvironment,
|
||||
environment: HubGuidedSetupEnvironment,
|
||||
confirmed = false,
|
||||
): Promise<string> {
|
||||
const status = await withHubDaemon(environment.daemon, undefined, async (daemon) =>
|
||||
daemon.getHubStatus().then((response) => response.status),
|
||||
@@ -223,9 +267,19 @@ async function ensureDaemonConnection(
|
||||
`This daemon is connected to ${connection.origin}. Disconnect it before running Hub init for ${origin}.`,
|
||||
);
|
||||
}
|
||||
if (!(await requiredConfirm(`Connect this daemon to ${origin}?`, true))) {
|
||||
if (
|
||||
!confirmed &&
|
||||
!(await requiredConfirm(environment, `Connect this daemon to ${origin}?`, true))
|
||||
) {
|
||||
throw new HubInitCancelledError("A connected daemon is required to create the bundle.");
|
||||
}
|
||||
return connectDaemon(origin, environment);
|
||||
}
|
||||
|
||||
async function connectDaemon(
|
||||
origin: string,
|
||||
environment: HubGuidedSetupEnvironment,
|
||||
): Promise<string> {
|
||||
await runHubConnect(
|
||||
origin,
|
||||
{},
|
||||
@@ -276,7 +330,10 @@ async function waitForDaemonReady(
|
||||
);
|
||||
}
|
||||
|
||||
async function chooseProject(origin: string, environment: HubInitEnvironment): Promise<HubProject> {
|
||||
async function chooseProject(
|
||||
origin: string,
|
||||
environment: HubGuidedSetupEnvironment,
|
||||
): Promise<HubProject> {
|
||||
const result = await runHubProjects(
|
||||
{ hub: origin },
|
||||
{
|
||||
@@ -295,7 +352,7 @@ async function chooseProject(origin: string, environment: HubInitEnvironment): P
|
||||
if (resolution.kind === "selected") {
|
||||
return resolution.project;
|
||||
}
|
||||
const slug = await requiredSelect({
|
||||
const slug = await requiredSelect(environment, {
|
||||
message: "Project",
|
||||
options: resolution.projects.map((project) => ({
|
||||
value: project.slug,
|
||||
@@ -313,8 +370,8 @@ async function chooseProject(origin: string, environment: HubInitEnvironment): P
|
||||
return project;
|
||||
}
|
||||
|
||||
async function chooseProvider(): Promise<HubInitProvider> {
|
||||
return requiredSelect({
|
||||
async function chooseProvider(environment: HubGuidedSetupEnvironment): Promise<HubInitProvider> {
|
||||
return requiredSelect(environment, {
|
||||
message: "Trigger provider",
|
||||
options: [
|
||||
{ value: "github", label: "GitHub", hint: "issue or pull request comment" },
|
||||
@@ -324,10 +381,101 @@ async function chooseProvider(): Promise<HubInitProvider> {
|
||||
});
|
||||
}
|
||||
|
||||
async function chooseStarterAgentRuntime(
|
||||
environment: HubGuidedSetupEnvironment,
|
||||
cwd: string,
|
||||
): Promise<HubStarterAgentRuntime> {
|
||||
const snapshot = await withHubDaemon(environment.daemon, undefined, (daemon) =>
|
||||
daemon.getProvidersSnapshot({ cwd }),
|
||||
);
|
||||
const providers = availableStarterAgentProviders(snapshot.entries);
|
||||
if (providers.length === 0) {
|
||||
throw new HubCommandError(
|
||||
"HUB_AGENT_RUNTIME_REQUIRED",
|
||||
"No usable agent runtime is available from this daemon. Configure an enabled provider with a selectable model, then run paseo hub init again.",
|
||||
);
|
||||
}
|
||||
const provider = await chooseStarterAgentProvider(environment, providers);
|
||||
const model = await chooseStarterAgentModel(environment, provider);
|
||||
const mode = await chooseStarterAgentMode(environment, provider);
|
||||
const runtime = selectedStarterAgentRuntime(provider, model, mode);
|
||||
if (runtime === undefined) {
|
||||
throw new HubCommandError(
|
||||
"HUB_AGENT_RUNTIME_SELECTION_INVALID",
|
||||
"The selected starter agent runtime is no longer available. Run paseo hub init again.",
|
||||
);
|
||||
}
|
||||
return runtime;
|
||||
}
|
||||
|
||||
async function chooseStarterAgentProvider(
|
||||
environment: HubGuidedSetupEnvironment,
|
||||
providers: readonly HubStarterAgentProvider[],
|
||||
): Promise<HubStarterAgentProvider> {
|
||||
const suggested = suggestedStarterAgentChoice(providers);
|
||||
const selected = await requiredSelect(environment, {
|
||||
message: "Starter agent provider",
|
||||
...(suggested === undefined ? {} : { initialValue: suggested.id }),
|
||||
options: providers.map((provider) => ({
|
||||
value: provider.id,
|
||||
label: provider.label,
|
||||
...(provider.suggested ? { hint: "suggested" } : {}),
|
||||
})),
|
||||
});
|
||||
const provider = providers.find((candidate) => candidate.id === selected);
|
||||
if (provider === undefined) throw invalidStarterAgentSelection();
|
||||
return provider;
|
||||
}
|
||||
|
||||
async function chooseStarterAgentModel(
|
||||
environment: HubGuidedSetupEnvironment,
|
||||
provider: HubStarterAgentProvider,
|
||||
): Promise<string> {
|
||||
const suggested = suggestedStarterAgentChoice(provider.models);
|
||||
const selected = await requiredSelect(environment, {
|
||||
message: "Starter agent model",
|
||||
...(suggested === undefined ? {} : { initialValue: suggested.id }),
|
||||
options: provider.models.map((model) => ({
|
||||
value: model.id,
|
||||
label: model.label,
|
||||
...(model.suggested ? { hint: "suggested" } : {}),
|
||||
})),
|
||||
});
|
||||
if (!provider.models.some((model) => model.id === selected)) throw invalidStarterAgentSelection();
|
||||
return selected;
|
||||
}
|
||||
|
||||
async function chooseStarterAgentMode(
|
||||
environment: HubGuidedSetupEnvironment,
|
||||
provider: HubStarterAgentProvider,
|
||||
): Promise<string | undefined> {
|
||||
if (provider.modes.length === 0) return undefined;
|
||||
const suggested = suggestedStarterAgentChoice(provider.modes);
|
||||
const selected = await requiredSelect(environment, {
|
||||
message: "Starter agent mode",
|
||||
...(suggested === undefined ? {} : { initialValue: suggested.id }),
|
||||
options: provider.modes.map((mode) => ({
|
||||
value: mode.id,
|
||||
label: mode.label,
|
||||
...(mode.suggested ? { hint: "suggested" } : {}),
|
||||
})),
|
||||
});
|
||||
if (!provider.modes.some((mode) => mode.id === selected)) throw invalidStarterAgentSelection();
|
||||
return selected;
|
||||
}
|
||||
|
||||
function invalidStarterAgentSelection(): HubCommandError {
|
||||
return new HubCommandError(
|
||||
"HUB_AGENT_RUNTIME_SELECTION_INVALID",
|
||||
"The selected starter agent runtime is no longer available. Run paseo hub init again.",
|
||||
);
|
||||
}
|
||||
|
||||
async function collectProviderFilters(
|
||||
provider: HubInitProvider,
|
||||
resources: HubConfigurationResources,
|
||||
resources: HubSetupResources,
|
||||
cwd: string,
|
||||
environment: HubGuidedSetupEnvironment,
|
||||
): Promise<Readonly<Record<string, string>>> {
|
||||
if (provider === "github") {
|
||||
const [login, originRemote] = await Promise.all([
|
||||
@@ -349,7 +497,7 @@ async function collectProviderFilters(
|
||||
);
|
||||
}
|
||||
return {
|
||||
user: await requiredText({
|
||||
user: await requiredText(environment, {
|
||||
message: "Your GitHub username (only this user can trigger the bot)",
|
||||
initialValue: login,
|
||||
}),
|
||||
@@ -359,32 +507,34 @@ async function collectProviderFilters(
|
||||
if (provider === "slack") {
|
||||
return {
|
||||
workspace: await chooseConnection(
|
||||
environment,
|
||||
"Slack workspace",
|
||||
resources.slack.map(({ slug, teamName }) => ({ slug, label: teamName })),
|
||||
resources.slack.map(({ teamId, teamName }) => ({ id: teamId, label: teamName })),
|
||||
),
|
||||
user: await requiredText({
|
||||
message: "Your Slack username (only this user can trigger the bot)",
|
||||
user: await requiredText(environment, {
|
||||
message: "Your Slack member ID (only this user can trigger the bot)",
|
||||
}),
|
||||
};
|
||||
}
|
||||
return {
|
||||
guild: await chooseConnection(
|
||||
environment,
|
||||
"Discord server",
|
||||
resources.discord.map(({ slug, guildName }) => ({ slug, label: guildName })),
|
||||
resources.discord.map(({ guildId, guildName }) => ({ id: guildId, label: guildName })),
|
||||
),
|
||||
user: await requiredText({
|
||||
message: "Your Discord username (only this user can trigger the bot)",
|
||||
user: await requiredText(environment, {
|
||||
message: "Your Discord user ID (only this user can trigger the bot)",
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
async function loadConfigurationResources(
|
||||
async function loadSetupResources(
|
||||
origin: string,
|
||||
environment: HubInitEnvironment,
|
||||
): Promise<HubConfigurationResources> {
|
||||
environment: HubGuidedSetupEnvironment,
|
||||
): Promise<HubSetupResources> {
|
||||
try {
|
||||
return await withSpinner("Loading Hub connections", () =>
|
||||
environment.hub.listConfigurationResources(
|
||||
environment.hub.listSetupResources(
|
||||
origin,
|
||||
resolveHubCredential({
|
||||
options: { origin },
|
||||
@@ -398,16 +548,41 @@ async function loadConfigurationResources(
|
||||
if (error instanceof HubCommandError && error.code === "HUB_NOT_FOUND") {
|
||||
throw new HubCommandError(
|
||||
"HUB_UPDATE_REQUIRED",
|
||||
"This Hub does not support guided setup. Update the Hub and try again.",
|
||||
"This Hub needs an update before guided setup can use provider IDs. Update Hub and try again.",
|
||||
);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadDaemonResource(
|
||||
origin: string,
|
||||
daemonId: string,
|
||||
environment: HubGuidedSetupEnvironment,
|
||||
): Promise<HubConfigurationResources["daemons"][number]> {
|
||||
const resources = await environment.hub.listConfigurationResources(
|
||||
origin,
|
||||
resolveHubCredential({
|
||||
options: { origin },
|
||||
env: environment.env,
|
||||
credentials: environment.credentials,
|
||||
origin,
|
||||
}),
|
||||
);
|
||||
const daemon = resources.daemons.find(({ id }) => id === daemonId);
|
||||
if (daemon === undefined) {
|
||||
throw new HubCommandError(
|
||||
"HUB_DAEMON_RESOURCE_MISSING",
|
||||
"The connected daemon is not available in this Hub organization. Reconnect it and try again.",
|
||||
);
|
||||
}
|
||||
return daemon;
|
||||
}
|
||||
|
||||
async function chooseConnection(
|
||||
environment: HubGuidedSetupEnvironment,
|
||||
message: string,
|
||||
connections: readonly { slug: string; label: string }[],
|
||||
connections: readonly { id: string; label: string }[],
|
||||
): Promise<string> {
|
||||
if (connections.length === 0) {
|
||||
throw new HubCommandError(
|
||||
@@ -415,10 +590,10 @@ async function chooseConnection(
|
||||
`No ${message.toLowerCase()} is connected in this Hub organization. Connect one and try again.`,
|
||||
);
|
||||
}
|
||||
if (connections.length === 1) return connections[0]!.slug;
|
||||
return requiredSelect({
|
||||
if (connections.length === 1) return connections[0]!.id;
|
||||
return requiredSelect(environment, {
|
||||
message,
|
||||
options: connections.map(({ slug, label }) => ({ value: slug, label })),
|
||||
options: connections.map(({ id, label }) => ({ value: id, label })),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -523,36 +698,62 @@ async function withSpinner<T>(
|
||||
}
|
||||
}
|
||||
|
||||
async function requiredText(options: Parameters<typeof text>[0]): Promise<string> {
|
||||
const answer = await text({
|
||||
async function requiredText(
|
||||
environment: HubGuidedSetupEnvironment,
|
||||
options: Parameters<typeof text>[0],
|
||||
): Promise<string> {
|
||||
const request = {
|
||||
...options,
|
||||
validate(value) {
|
||||
validate(value: string | undefined) {
|
||||
const input = value ?? "";
|
||||
const customError = options.validate?.(input);
|
||||
if (customError !== undefined) return customError;
|
||||
return input.trim().length === 0 ? "A value is required" : undefined;
|
||||
},
|
||||
});
|
||||
};
|
||||
const answer =
|
||||
environment.prompts === undefined
|
||||
? await text(request)
|
||||
: await environment.prompts.text(request);
|
||||
if (isCancel(answer)) throw new HubInitCancelledError("Hub init cancelled.");
|
||||
return answer.trim();
|
||||
}
|
||||
|
||||
async function requiredConfirm(message: string, initialValue: boolean): Promise<boolean> {
|
||||
const answer = await confirm({ message, initialValue });
|
||||
async function requiredConfirm(
|
||||
environment: HubGuidedSetupEnvironment,
|
||||
message: string,
|
||||
initialValue: boolean,
|
||||
): Promise<boolean> {
|
||||
const answer =
|
||||
environment.prompts === undefined
|
||||
? await confirm({ message, initialValue })
|
||||
: await environment.prompts.confirm(message, initialValue);
|
||||
if (isCancel(answer)) throw new HubInitCancelledError("Hub init cancelled.");
|
||||
return answer;
|
||||
}
|
||||
|
||||
async function requiredSelect<T extends string>(
|
||||
environment: HubGuidedSetupEnvironment,
|
||||
options: Parameters<typeof select<T>>[0],
|
||||
): Promise<T> {
|
||||
const answer = await select<T>(options);
|
||||
const answer =
|
||||
environment.prompts === undefined
|
||||
? await select<T>(options)
|
||||
: ((await environment.prompts.select(options as Parameters<typeof select<string>>[0])) as T);
|
||||
if (isCancel(answer)) throw new HubInitCancelledError("Hub init cancelled.");
|
||||
return answer;
|
||||
}
|
||||
|
||||
function requireInteractiveTerminal(): void {
|
||||
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
||||
function requireInteractiveTerminal(environment: HubGuidedSetupEnvironment): void {
|
||||
if (!(environment.isInteractive?.() ?? (process.stdin.isTTY && process.stdout.isTTY))) {
|
||||
throw new HubCommandError("HUB_INIT_INTERACTIVE_REQUIRED", "paseo hub init requires a TTY.");
|
||||
}
|
||||
}
|
||||
|
||||
function reportMessage(environment: HubGuidedSetupEnvironment, message: string): void {
|
||||
if (environment.prompts === undefined) {
|
||||
log.message(message);
|
||||
return;
|
||||
}
|
||||
environment.prompts.message(message);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user