Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions apps/cli/src/helpers/core/api-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ function getFrontendType(frontend: Frontend[]): {
hasNuxtWeb: boolean;
hasSvelteWeb: boolean;
hasSolidWeb: boolean;
hasAstroWeb: boolean;
hasNative: boolean;
} {
const reactBasedFrontends = [
Expand All @@ -40,6 +41,7 @@ function getFrontendType(frontend: Frontend[]): {
hasNuxtWeb: frontend.includes("nuxt"),
hasSvelteWeb: frontend.includes("svelte"),
hasSolidWeb: frontend.includes("solid"),
hasAstroWeb: frontend.includes("astro"),
hasNative: frontend.some((f) => nativeFrontends.includes(f)),
};
}
Expand Down Expand Up @@ -108,6 +110,15 @@ function getApiDependencies(
"@tanstack/solid-router-devtools",
],
};
} else if (frontendType.hasAstroWeb && api === "orpc") {
deps.web = {
dependencies: [
"@orpc/tanstack-query",
"@orpc/client",
"@tanstack/react-query",
],
devDependencies: ["@tanstack/react-query-devtools"],
};
}

if (api === "trpc") {
Expand Down
1 change: 1 addition & 0 deletions apps/cli/src/helpers/core/auth-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ export async function setupAuth(config: ProjectConfig) {
"tanstack-start",
"next",
"nuxt",
"astro",
"svelte",
"solid",
].includes(f),
Expand Down
3 changes: 3 additions & 0 deletions apps/cli/src/helpers/core/create-readme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ function generateStackDescription(
const hasTanstackStart = frontend.includes("tanstack-start");
const hasSvelte = frontend.includes("svelte");
const hasNuxt = frontend.includes("nuxt");
const hasAstro = frontend.includes("astro");
const hasSolid = frontend.includes("solid");
const hasFrontendNone = frontend.length === 0 || frontend.includes("none");

Expand All @@ -190,6 +191,8 @@ function generateStackDescription(
parts.push("SvelteKit");
} else if (hasNuxt) {
parts.push("Nuxt");
} else if (hasAstro) {
parts.push("Astro");
} else if (hasSolid) {
parts.push("SolidJS");
}
Expand Down
6 changes: 6 additions & 0 deletions apps/cli/src/helpers/core/env-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export async function setupEnvironmentVariables(config: ProjectConfig) {
const hasTanStackStart = frontend.includes("tanstack-start");
const hasNextJs = frontend.includes("next");
const hasNuxt = frontend.includes("nuxt");
const hasAstro = frontend.includes("astro");
const hasSvelte = frontend.includes("svelte");
const hasSolid = frontend.includes("solid");
const hasWebFrontend =
Expand All @@ -114,6 +115,7 @@ export async function setupEnvironmentVariables(config: ProjectConfig) {
hasTanStackStart ||
hasNextJs ||
hasNuxt ||
hasAstro ||
hasSolid ||
hasSvelte;

Expand Down Expand Up @@ -281,12 +283,16 @@ export async function setupEnvironmentVariables(config: ProjectConfig) {
if (!(await fs.pathExists(serverDir))) {
return;
}

const envPath = path.join(serverDir, ".env");

let corsOrigin = "http://localhost:3001";
if (hasReactRouter || hasSvelte) {
corsOrigin = "http://localhost:5173";
}
if (hasAstro) {
corsOrigin = "http://localhost:4321";
}

let databaseUrl: string | null = null;

Expand Down
1 change: 1 addition & 0 deletions apps/cli/src/helpers/core/payments-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export async function setupPayments(config: ProjectConfig) {
"tanstack-start",
"next",
"nuxt",
"astro",
"svelte",
"solid",
].includes(f),
Expand Down
1 change: 1 addition & 0 deletions apps/cli/src/helpers/core/post-installation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export async function displayPostInstallInstructions(
"next",
"tanstack-start",
"nuxt",
"astro",
"svelte",
"solid",
].includes(f),
Expand Down
20 changes: 19 additions & 1 deletion apps/cli/src/helpers/core/template-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,15 @@ export async function setupFrontendTemplates(
["tanstack-router", "react-router", "tanstack-start", "next"].includes(f),
);
const hasNuxtWeb = context.frontend.includes("nuxt");
const hasAstroWeb = context.frontend.includes("astro");
const hasSvelteWeb = context.frontend.includes("svelte");
const hasSolidWeb = context.frontend.includes("solid");
const hasNativeWind = context.frontend.includes("native-nativewind");
const hasUnistyles = context.frontend.includes("native-unistyles");
const _hasNative = hasNativeWind || hasUnistyles;
const isConvex = context.backend === "convex";

if (hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb) {
if (hasReactWeb || hasNuxtWeb || hasAstroWeb || hasSvelteWeb || hasSolidWeb) {
const webAppDir = path.join(projectDir, "apps/web");
await fs.ensureDir(webAppDir);

Expand Down Expand Up @@ -140,6 +141,23 @@ export async function setupFrontendTemplates(
} else {
}
}
} else if (hasAstroWeb) {
const astroBaseDir = path.join(PKG_ROOT, "templates/frontend/astro");
if (await fs.pathExists(astroBaseDir)) {
await processAndCopyFiles("**/*", astroBaseDir, webAppDir, context);
} else {
}

if (!isConvex && context.api === "orpc") {
const apiWebAstroDir = path.join(
PKG_ROOT,
`templates/api/${context.api}/web/astro`,
);
if (await fs.pathExists(apiWebAstroDir)) {
await processAndCopyFiles("**/*", apiWebAstroDir, webAppDir, context);
} else {
}
}
} else if (hasSvelteWeb) {
const svelteBaseDir = path.join(PKG_ROOT, "templates/frontend/svelte");
if (await fs.pathExists(svelteBaseDir)) {
Expand Down
5 changes: 5 additions & 0 deletions apps/cli/src/prompts/frontend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ export async function getFrontendChoice(
label: "Nuxt",
hint: "The Progressive Web Framework for Vue.js",
},
{
value: "astro" as const,
label: "Astro",
hint: "All-in-one web framework for content-driven websites",
},
{
value: "svelte" as const,
label: "Svelte",
Expand Down
1 change: 1 addition & 0 deletions apps/cli/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const FrontendSchema = z
"tanstack-start",
"next",
"nuxt",
"astro",
"native-nativewind",
"native-unistyles",
"svelte",
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/utils/compatibility-rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function ensureSingleWebAndNative(frontends: Frontend[]) {
const { web, native } = splitFrontends(frontends);
if (web.length > 1) {
exitWithError(
"Cannot select multiple web frameworks. Choose only one of: tanstack-router, tanstack-start, react-router, next, nuxt, svelte, solid",
"Cannot select multiple web frameworks. Choose only one of: tanstack-router, tanstack-start, react-router, next, nuxt, astro, svelte, solid",
);
}
if (native.length > 1) {
Expand Down
1 change: 1 addition & 0 deletions apps/cli/src/utils/compatibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const WEB_FRAMEWORKS: readonly Frontend[] = [
"tanstack-start",
"next",
"nuxt",
"astro",
"svelte",
"solid",
] as const;
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { orpc, queryClient } from '../lib/orpc';
import { useQuery, QueryClientProvider } from '@tanstack/react-query';

function ApiStatus() {
const healthCheck = useQuery(orpc.healthCheck.queryOptions());

return (
<section className="rounded-lg border p-4">
<h2 className="mb-2 font-medium">API Status</h2>
<div className="flex items-center gap-2">
<div
className={`h-2 w-2 rounded-full ${
healthCheck.data ? "bg-green-500" : "bg-red-500"
}`}
></div>
<span className="text-muted-foreground text-sm">
{healthCheck.isLoading
? "Checking..."
: healthCheck.data
? "Connected"
: "Disconnected"}
</span>
</div>
{healthCheck.data && (
<div className="mt-2 text-sm text-green-600">
Response: {JSON.stringify(healthCheck.data)}
</div>
)}
</section>
);
}

export function OrpcApiStatus() {
return (
<QueryClientProvider client={queryClient}>
<ApiStatus />
</QueryClientProvider>
);
}
35 changes: 35 additions & 0 deletions apps/cli/templates/api/orpc/web/astro/src/lib/orpc.ts.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { createORPCClient, onError } from "@orpc/client";
import { BatchLinkPlugin } from "@orpc/client/plugins";
import { RPCLink } from "@orpc/client/fetch";
import { createTanstackQueryUtils } from "@orpc/tanstack-query";
import { QueryCache, QueryClient } from "@tanstack/react-query";
import type { AppRouterClient } from "../../../server/src/routers/index";

export const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: (error) => {
console.error(`Error: ${error.message}`);
},
}),
});

export const link = new RPCLink({
url: `${import.meta.env.VITE_SERVER_URL}/rpc`,
plugins: [
new BatchLinkPlugin({
groups: [{
condition: () => true,
context: {},
}],
}),
],
interceptors: [
onError((error) => {
console.error(`RPC Error: ${error.message}`, error)
})
],
});

export const client: AppRouterClient = createORPCClient(link);

export const orpc = createTanstackQueryUtils(client);
24 changes: 24 additions & 0 deletions apps/cli/templates/frontend/astro/_gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# build output
dist/
# generated types
.astro/

# dependencies
node_modules/

# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*


# environment variables
.env
.env.production

# macOS-specific files
.DS_Store

# jetbrains setting folder
.idea/
15 changes: 15 additions & 0 deletions apps/cli/templates/frontend/astro/astro.config.mjs.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { defineConfig } from 'astro/config';
import tailwindcss from '@tailwindcss/vite';
{{#if (eq api "orpc")}}
import react from '@astrojs/react';
{{/if}}

export default defineConfig({
vite: {
plugins: [tailwindcss()]
},
{{#if (eq api "orpc")}}
integrations: [react()],
{{/if}}
output: 'static'
});
37 changes: 37 additions & 0 deletions apps/cli/templates/frontend/astro/package.json.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "web",
"type": "module",
"version": "0.1.0",
"private": true,
"license": "MIT",
"engines": {
"node": ">= 22"
},
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"astro": "^5.2.4",
"@tailwindcss/vite": "^4.1.13",
"tailwindcss": "^4.1.13",
"typescript": "^5.9.2",
"zod": "^4.1.5"{{#if (eq api "orpc")}},
"@astrojs/react": "^4.1.3",
"@types/react": "^19.0.18",
"@types/react-dom": "^19.0.18",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"@orpc/tanstack-query": "^1.9.0",
"@orpc/client": "^1.9.0",
"@tanstack/react-query": "^5.85.5"{{/if}}
},
"devDependencies": {
"@astrojs/check": "^0.9.4",
{{#if (eq api "orpc")}}
"@tanstack/react-query-devtools": "^5.85.5"{{/if}}
}
}
9 changes: 9 additions & 0 deletions apps/cli/templates/frontend/astro/public/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions apps/cli/templates/frontend/astro/public/robots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Example: Allow all bots to scan and index your site.
# Full syntax: https://developers.google.com/search/docs/advanced/robots/create-robots-txt

User-agent: *
Allow: /
26 changes: 26 additions & 0 deletions apps/cli/templates/frontend/astro/src/layouts/Layout.astro.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
import '../styles/global.css';


interface Props {
title?: string | undefined;
description?: string | undefined;
}

const { title, description = "Built with Better-T-Stack"} = Astro.props;

---

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="description" content=`{{projectName}} - ${description}` />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>{title}</title>
</head>
<body>
<slot />
</body>
</html>
Loading