Skip to content

Commit 3da9053

Browse files
committed
Reuse the spinner and quiet install path for template and example fetches.
1 parent 9c15b3a commit 3da9053

3 files changed

Lines changed: 90 additions & 48 deletions

File tree

packages/openui-cli/src/commands/create-app.ts

Lines changed: 16 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@ import * as os from "node:os";
33
import * as path from "node:path";
44

55
import { resolveCloudApiKey, THESYS_KEYS_URL } from "../auth/mint";
6-
import { printLogTail, QUIET_COMMAND_CAPTURE_LIMIT } from "../lib/command-output";
7-
import { promptForProviderKey, resolveImmediate } from "../lib/create-helpers";
6+
import {
7+
promptForProviderKey,
8+
resolveImmediate,
9+
runDependencyInstall,
10+
withProgress,
11+
} from "../lib/create-helpers";
812
import { aiSetupFromTemplate, createFunnelProps } from "../lib/create-telemetry";
913
import type { CreateAppOptions, EnvResult, TemplateName } from "../lib/create-types";
1014
import {
@@ -21,15 +25,13 @@ import {
2125
resolveOverlay,
2226
type OverlayManifest,
2327
} from "../lib/overlays";
24-
import { runCommand } from "../lib/process-runner";
2528
import {
2629
rejectConflictingScaffoldSelectors,
2730
resolveProject,
2831
templatesFromOverlays,
2932
} from "../lib/projects";
3033
import { resolveArgs } from "../lib/resolve-args";
3134
import { resolveTemplateSource } from "../lib/scaffold-template";
32-
import { withSpinner } from "../lib/spinner";
3335
import { resolveAvailableTarget } from "../lib/target-dir";
3436
import { CliCancelledError, CreateError, telemetry } from "../lib/telemetry";
3537
import { cliErrorProperties, processErrorProperties } from "../lib/utils";
@@ -224,7 +226,9 @@ export async function runCreateApp(options: CreateAppOptions): Promise<void> {
224226
);
225227
}
226228

227-
const templateSource = await templateSourcePromise;
229+
const templateSource = template
230+
? await withProgress("Fetching template...", () => templateSourcePromise, options.verbose)
231+
: await templateSourcePromise;
228232
const templateDir = templateSource?.dir;
229233
const overlays = templateDir ? listOverlays(templateDir) : [];
230234
const overlayNames = overlays.map((overlay) => overlay.name);
@@ -261,9 +265,6 @@ export async function runCreateApp(options: CreateAppOptions): Promise<void> {
261265
"MISSING_REQUIRED_ARG",
262266
);
263267
}
264-
if (templateSource.origin === "github") {
265-
console.info("Checked out template from GitHub.\n");
266-
}
267268

268269
const backendFramework = project.name;
269270
if (!overlayNames.includes(backendFramework)) {
@@ -484,41 +485,22 @@ export async function runCreateApp(options: CreateAppOptions): Promise<void> {
484485
template,
485486
ai_setup: aiSetup,
486487
});
487-
const runInstall = () =>
488-
options.verbose
489-
? runCommand(packageManager.runCmd, installArgs, targetDir)
490-
: runCommand(packageManager.runCmd, installArgs, targetDir, {
491-
echo: false,
492-
stdin: "ignore",
493-
captureLimit: QUIET_COMMAND_CAPTURE_LIMIT,
494-
env: {
495-
...process.env,
496-
npm_config_loglevel: "error",
497-
NPM_CONFIG_LOGLEVEL: "error",
498-
},
499-
});
500-
501-
if (options.verbose) {
502-
console.info(`Installing dependencies with: ${installCmd}\n`);
503-
}
504-
const installResult = options.verbose
505-
? await runInstall()
506-
: await withSpinner("Installing dependencies...", runInstall);
488+
const installResult = await runDependencyInstall({
489+
verbose: options.verbose,
490+
command: packageManager.runCmd,
491+
args: installArgs,
492+
cwd: targetDir,
493+
installCmd,
494+
});
507495
if (!installResult.error && installResult.status === 0) {
508496
dependencyInstalled = true;
509-
if (!options.verbose) {
510-
console.info("✓ Dependencies installed\n");
511-
}
512497
telemetry.capture("cli_dependency_install_succeeded", {
513498
...createFunnelProps("dependency_install_succeeded"),
514499
template,
515500
ai_setup: aiSetup,
516501
dependency_installed: dependencyInstalled,
517502
});
518503
} else {
519-
if (!options.verbose) {
520-
printLogTail(installResult.diagnosticTail, "install log (tail)");
521-
}
522504
const properties = processErrorProperties(installResult, "dependency_install", {
523505
error_class: "dependency",
524506
error_code: "NONZERO_EXIT",

packages/openui-cli/src/commands/create-example.ts

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
import * as fs from "node:fs";
22
import * as path from "node:path";
33

4-
import { promptForProviderKey, resolveImmediate } from "../lib/create-helpers";
4+
import {
5+
promptForProviderKey,
6+
resolveImmediate,
7+
runDependencyInstall,
8+
withProgress,
9+
} from "../lib/create-helpers";
510
import { createFunnelProps } from "../lib/create-telemetry";
611
import type { CreateAppOptions, EnvResult } from "../lib/create-types";
712
import { resolveInstallPackageManager } from "../lib/detect-package-manager";
813
import { runDevCommand } from "../lib/dev-server";
914
import { runSkillInstall, shouldInstallSkill } from "../lib/install-skill";
10-
import { runCommand } from "../lib/process-runner";
1115
import type { ExampleProject } from "../lib/projects";
1216
import { scaffoldExample, upsertEnvKey } from "../lib/scaffold-example";
1317
import { CliCancelledError, CreateError, telemetry } from "../lib/telemetry";
@@ -61,16 +65,18 @@ export async function runCreateExample(params: {
6165
example: example.name,
6266
});
6367
try {
64-
const origin = await scaffoldExample({
65-
example,
66-
targetDir,
67-
name,
68-
packageManager: packageManager.name,
69-
sourceRoot,
70-
});
71-
if (origin === "github") {
72-
console.info("Checked out example from GitHub.\n");
73-
}
68+
await withProgress(
69+
"Fetching example...",
70+
() =>
71+
scaffoldExample({
72+
example,
73+
targetDir,
74+
name,
75+
packageManager: packageManager.name,
76+
sourceRoot,
77+
}),
78+
options.verbose,
79+
);
7480
if (packageManager.name !== "npm") {
7581
fs.rmSync(path.join(targetDir, "package-lock.json"), { force: true });
7682
}
@@ -189,12 +195,17 @@ export async function runCreateExample(params: {
189195
});
190196
console.info(`Skipping dependency install (--no-install). Run \`${installCmd}\` later.\n`);
191197
} else {
192-
console.info(`Installing dependencies with: ${installCmd}\n`);
193198
telemetry.capture("cli_dependency_install_started", {
194199
...createFunnelProps("dependency_install_started"),
195200
example: example.name,
196201
});
197-
const installResult = await runCommand(packageManager.runCmd, installArgs, targetDir);
202+
const installResult = await runDependencyInstall({
203+
verbose: options.verbose,
204+
command: packageManager.runCmd,
205+
args: installArgs,
206+
cwd: targetDir,
207+
installCmd,
208+
});
198209
if (!installResult.error && installResult.status === 0) {
199210
dependencyInstalled = true;
200211
telemetry.capture("cli_dependency_install_succeeded", {

packages/openui-cli/src/lib/create-helpers.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import { printLogTail, QUIET_COMMAND_CAPTURE_LIMIT } from "./command-output";
2+
import { runCommand, type CommandResult } from "./process-runner";
3+
import { withSpinner } from "./spinner";
14
import { CliCancelledError } from "./telemetry";
25

36
const isInteractiveTerminal = () => Boolean(process.stdin.isTTY && process.stdout.isTTY);
@@ -31,6 +34,52 @@ export function resolveImmediate(
3134
return { immediate: true, installDependencies: true, source: "interactive_default" };
3235
}
3336

37+
/** Spinner in quiet mode; print the label and stream output when `--verbose`. */
38+
export async function withProgress<T>(
39+
label: string,
40+
run: () => Promise<T>,
41+
verbose?: boolean,
42+
): Promise<T> {
43+
if (verbose) {
44+
console.info(`${label}\n`);
45+
return run();
46+
}
47+
return withSpinner(label, run);
48+
}
49+
50+
export async function runDependencyInstall(params: {
51+
verbose?: boolean;
52+
command: string;
53+
args: string[];
54+
cwd: string;
55+
installCmd: string;
56+
}): Promise<CommandResult> {
57+
const { verbose, command, args, cwd, installCmd } = params;
58+
const runInstall = () =>
59+
verbose
60+
? runCommand(command, args, cwd)
61+
: runCommand(command, args, cwd, {
62+
echo: false,
63+
stdin: "ignore",
64+
captureLimit: QUIET_COMMAND_CAPTURE_LIMIT,
65+
env: {
66+
...process.env,
67+
npm_config_loglevel: "error",
68+
NPM_CONFIG_LOGLEVEL: "error",
69+
},
70+
});
71+
72+
if (verbose) console.info(`Installing dependencies with: ${installCmd}\n`);
73+
const result = verbose
74+
? await runInstall()
75+
: await withSpinner("Installing dependencies...", runInstall);
76+
77+
const ok = !result.error && result.status === 0;
78+
if (ok && !verbose) console.info("✓ Dependencies installed\n");
79+
if (!ok && !verbose) printLogTail(result.diagnosticTail, "install log (tail)");
80+
return result;
81+
}
82+
3483
export async function promptForProviderKey(envKey = "OPENAI_API_KEY"): Promise<string | null> {
3584
try {
3685
const { input } = await import("@inquirer/prompts");

0 commit comments

Comments
 (0)