Skip to content

Commit eeecc96

Browse files
simplesagarclaude
andauthored
feat: add deployment loader animation (#841)
## Summary - Adds a new `Loader` class that displays a rotating square animation during deployments - Integrates the loader into the Gram Functions deployment process to provide visual feedback ## Implementation Details The `Loader` class provides a clean terminal animation using rotating square characters (◰ ◳ ◲ ◱) that displays while the `gram push` command is running. This improves the user experience by providing visual feedback during the deployment process. <img width="458" height="192" alt="CleanShot 2025-11-13 at 08 11 05@2x" src="https://github.com/user-attachments/assets/5aaee871-867a-4e76-8a44-841eaeef1db8" /> ## Test plan - [x] Deploy a function and verify the loader animation displays correctly - [x] Verify the loader clears properly after deployment completes - [x] Test that deployment errors still show correctly with the loader 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude <[email protected]>
1 parent 5d65184 commit eeecc96

File tree

5 files changed

+71
-19
lines changed

5 files changed

+71
-19
lines changed

.changeset/clean-suits-flow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@gram-ai/functions": patch
3+
---
4+
5+
Deployment loader animation shown during `npm run push` while deployment asset is being uploaded.

pnpm-lock.yaml

Lines changed: 20 additions & 19 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ts-framework/functions/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
}
5656
},
5757
"dependencies": {
58+
"@clack/prompts": "^0.11.0",
5859
"@logtape/logtape": "^1.1.1",
5960
"@logtape/pretty": "^1.1.1",
6061
"@stricli/core": "^1.2.4",

ts-framework/functions/src/build/gram.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { dirname, join, relative, resolve } from "node:path";
66
import { createInterface } from "node:readline";
77
import { $, ProcessPromise, chalk } from "zx";
88
import { isCI, type ParsedUserConfig } from "./config.ts";
9+
import { Loader } from "./loader.ts";
910

1011
type Artifacts = {
1112
funcFilename: string;
@@ -178,6 +179,11 @@ export async function deployFunction(logger: Logger, config: ParsedUserConfig) {
178179
}
179180

180181
logger.info("Deploying function with Gram CLI");
182+
183+
// Start the loader animation
184+
const loader = new Loader("deploying function...");
185+
loader.start();
186+
181187
const pushcmd = $({
182188
stdio: ["pipe", "pipe", "pipe"],
183189
})`gram ${pushArgs}`
@@ -188,6 +194,9 @@ export async function deployFunction(logger: Logger, config: ParsedUserConfig) {
188194

189195
const result = await pushcmd;
190196

197+
// Stop the loader animation
198+
loader.stop();
199+
191200
if (result.exitCode !== 0) {
192201
throw new Error(
193202
`Gram CLI push command failed with exit code ${result.exitCode}`,
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { spinner } from "@clack/prompts";
2+
3+
/**
4+
* Loader wrapper using Clack's spinner, which gracefully handles non-interactive terminals.
5+
* In CI/CD environments, it falls back to simple text output instead of animations.
6+
*/
7+
export class Loader {
8+
private spinnerInstance: ReturnType<typeof spinner> | null = null;
9+
private readonly message: string;
10+
11+
constructor(message: string) {
12+
this.message = message;
13+
}
14+
15+
/**
16+
* Start the loader animation.
17+
*/
18+
start(): void {
19+
if (this.spinnerInstance) {
20+
return; // Already running
21+
}
22+
23+
this.spinnerInstance = spinner();
24+
this.spinnerInstance.start(this.message);
25+
}
26+
27+
/**
28+
* Stop the loader animation and clear the line.
29+
*/
30+
stop(): void {
31+
if (this.spinnerInstance) {
32+
this.spinnerInstance.stop();
33+
this.spinnerInstance = null;
34+
}
35+
}
36+
}

0 commit comments

Comments
 (0)