Skip to content

Commit fd86d51

Browse files
add sponsors, builder, docs command in cli
1 parent dc88590 commit fd86d51

File tree

5 files changed

+136
-1
lines changed

5 files changed

+136
-1
lines changed

.changeset/cold-cups-fetch.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"create-better-t-stack": patch
3+
---
4+
5+
add sponsors, builder, docs command

apps/cli/src/index.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ import { trackProjectCreation } from "./utils/analytics";
3434
import { displayConfig } from "./utils/display-config";
3535
import { generateReproducibleCommand } from "./utils/generate-reproducible-command";
3636
import { getLatestCLIVersion } from "./utils/get-latest-cli-version";
37+
import { openUrl } from "./utils/open-url";
3738
import { renderTitle } from "./utils/render-title";
39+
import { displaySponsors, fetchSponsors } from "./utils/sponsors";
3840
import { getProvidedFlags, processAndValidateFlags } from "./validation";
3941

4042
const t = trpcServer.initTRPC.create();
@@ -299,6 +301,41 @@ const router = t.router({
299301
};
300302
await createProjectHandler(combinedInput);
301303
}),
304+
sponsors: t.procedure
305+
.meta({ description: "Show Better-T Stack sponsors" })
306+
.mutation(async () => {
307+
try {
308+
renderTitle();
309+
intro(pc.magenta("Better-T Stack Sponsors"));
310+
const sponsors = await fetchSponsors();
311+
displaySponsors(sponsors);
312+
} catch (error) {
313+
consola.error(error);
314+
process.exit(1);
315+
}
316+
}),
317+
docs: t.procedure
318+
.meta({ description: "Open Better-T Stack documentation" })
319+
.mutation(async () => {
320+
const DOCS_URL = "https://better-t-stack.amanv.dev/docs";
321+
try {
322+
await openUrl(DOCS_URL);
323+
log.success(pc.blue("Opened docs in your default browser."));
324+
} catch {
325+
log.message(`Please visit ${DOCS_URL}`);
326+
}
327+
}),
328+
builder: t.procedure
329+
.meta({ description: "Open the web-based stack builder" })
330+
.mutation(async () => {
331+
const BUILDER_URL = "https://better-t-stack.amanv.dev/new";
332+
try {
333+
await openUrl(BUILDER_URL);
334+
log.success(pc.blue("Opened builder in your default browser."));
335+
} catch {
336+
log.message(`Please visit ${BUILDER_URL}`);
337+
}
338+
}),
302339
});
303340

304341
createCli({

apps/cli/src/utils/open-url.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { log } from "@clack/prompts";
2+
import { execa } from "execa";
3+
4+
export async function openUrl(url: string): Promise<void> {
5+
const platform = process.platform;
6+
let command: string;
7+
let args: string[] = [];
8+
9+
if (platform === "darwin") {
10+
command = "open";
11+
args = [url];
12+
} else if (platform === "win32") {
13+
command = "cmd";
14+
args = ["/c", "start", "", url.replace(/&/g, "^&")];
15+
} else {
16+
command = "xdg-open";
17+
args = [url];
18+
}
19+
20+
try {
21+
await execa(command, args, { stdio: "ignore" });
22+
} catch {
23+
log.message(`Please open ${url} in your browser.`);
24+
}
25+
}

apps/cli/src/utils/sponsors.ts

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { log, outro, spinner } from "@clack/prompts";
2+
import pc from "picocolors";
3+
4+
export interface SponsorEntry {
5+
readonly sponsor: {
6+
readonly login: string;
7+
readonly name?: string | null;
8+
readonly avatarUrl?: string | null;
9+
readonly websiteUrl?: string | null;
10+
readonly linkUrl?: string | null;
11+
readonly type?: string;
12+
};
13+
readonly isOneTime: boolean;
14+
readonly monthlyDollars?: number;
15+
readonly tierName?: string;
16+
}
17+
18+
export const SPONSORS_JSON_URL = "https://sponsors.amanv.dev/sponsors.json";
19+
20+
export async function fetchSponsors(
21+
url: string = SPONSORS_JSON_URL,
22+
): Promise<SponsorEntry[]> {
23+
const s = spinner();
24+
s.start("Fetching sponsors…");
25+
26+
const response = await fetch(url);
27+
if (!response.ok) {
28+
s.stop(pc.red(`Failed to fetch sponsors: ${response.statusText}`));
29+
throw new Error(`Failed to fetch sponsors: ${response.statusText}`);
30+
}
31+
32+
const sponsors = (await response.json()) as SponsorEntry[];
33+
s.stop("Sponsors fetched successfully!");
34+
return sponsors;
35+
}
36+
37+
export function displaySponsors(sponsors: SponsorEntry[]): void {
38+
if (sponsors.length === 0) {
39+
log.info("No sponsors found. You can be the first one! ✨");
40+
outro(
41+
pc.cyan(
42+
"Visit https://github.com/sponsors/AmanVarshney01 to become a sponsor.",
43+
),
44+
);
45+
return;
46+
}
47+
48+
sponsors.forEach((entry: SponsorEntry, idx: number) => {
49+
const sponsor = entry.sponsor;
50+
const displayName = sponsor.name ?? sponsor.login;
51+
const tier = entry.tierName ? ` (${entry.tierName})` : "";
52+
53+
log.step(`${idx + 1}. ${pc.green(displayName)}${pc.yellow(tier)}`);
54+
log.message(` ${pc.dim("GitHub:")} https://github.com/${sponsor.login}`);
55+
56+
const website = sponsor.websiteUrl ?? sponsor.linkUrl;
57+
if (website) {
58+
log.message(` ${pc.dim("Website:")} ${website}`);
59+
}
60+
});
61+
62+
log.message("");
63+
outro(
64+
pc.magenta(
65+
"Visit https://github.com/sponsors/AmanVarshney01 to become a sponsor.",
66+
),
67+
);
68+
}

apps/web/src/app/(home)/_components/sponsors-section.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ export default function SponsorsSection() {
158158
>
159159
<Github className="h-4 w-4" />
160160
<span className="truncate">
161-
github.com/{entry.sponsor.login}
161+
{entry.sponsor.login}
162162
</span>
163163
</a>
164164
{(entry.sponsor.websiteUrl ||

0 commit comments

Comments
 (0)