Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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
1 change: 1 addition & 0 deletions apps/cli/src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export async function parseCliArguments(): Promise<YargsArgv> {
"native-nativewind",
"native-unistyles",
"svelte",
"angular",
"solid",
"none",
],
Expand Down
2 changes: 2 additions & 0 deletions apps/cli/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export const dependencyVersionMap = {
mongoose: "^8.14.0",

"vite-plugin-pwa": "^0.21.2",
"@angular/service-worker" : "^20.0.2",
"@vite-pwa/assets-generator": "^0.2.6",

"@tauri-apps/cli": "^2.4.0",
Expand Down Expand Up @@ -92,6 +93,7 @@ export const dependencyVersionMap = {
"@orpc/tanstack-query": "^1.4.1",

"@trpc/tanstack-react-query": "^11.0.0",
"@tanstack/angular-query-experimental": "5.80.2",
"@trpc/server": "^11.0.0",
"@trpc/client": "^11.0.0",

Expand Down
48 changes: 44 additions & 4 deletions apps/cli/src/helpers/project-generation/template-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { globby } from "globby";
import { PKG_ROOT } from "../../constants";
import type { ProjectConfig } from "../../types";
import { processTemplate } from "../../utils/template-processor";
import { addPackageDependency } from "../../utils/add-package-deps";

async function processAndCopyFiles(
sourcePattern: string | string[],
Expand Down Expand Up @@ -70,12 +71,13 @@ export async function setupFrontendTemplates(
const hasNuxtWeb = context.frontend.includes("nuxt");
const hasSvelteWeb = context.frontend.includes("svelte");
const hasSolidWeb = context.frontend.includes("solid");
const hasAngularWeb = context.frontend.includes("angular");
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 || hasSvelteWeb || hasSolidWeb || hasAngularWeb) {
const webAppDir = path.join(projectDir, "apps/web");
await fs.ensureDir(webAppDir);

Expand Down Expand Up @@ -180,6 +182,23 @@ export async function setupFrontendTemplates(
} else {
}
}
} else if (hasAngularWeb) {
const angularBaseDir = path.join(PKG_ROOT, "templates/frontend/angular");
if (await fs.pathExists(angularBaseDir)) {
await processAndCopyFiles("**/*", angularBaseDir, webAppDir, context);
} else {
}

if (!isConvex && (context.api === "orpc" || context.api === "trpc")) {
const apiWebAngularDir = path.join(
PKG_ROOT,
`templates/api/${context.api}/web/angular`,
);
if (await fs.pathExists(apiWebAngularDir)) {
await processAndCopyFiles("**/*", apiWebAngularDir, webAppDir, context);
} else {
}
}
}
}

Expand Down Expand Up @@ -378,7 +397,7 @@ export async function setupAuthTemplate(
const hasNativeWind = context.frontend.includes("native-nativewind");
const hasUnistyles = context.frontend.includes("native-unistyles");
const hasNative = hasNativeWind || hasUnistyles;

const hasAngularWeb = context.frontend.includes("angular");
if (serverAppDirExists) {
const authServerBaseSrc = path.join(PKG_ROOT, "templates/auth/server/base");
if (await fs.pathExists(authServerBaseSrc)) {
Expand Down Expand Up @@ -435,7 +454,7 @@ export async function setupAuthTemplate(
}

if (
(hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb) &&
(hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb || hasAngularWeb) &&
webAppDirExists
) {
if (hasReactWeb) {
Expand Down Expand Up @@ -503,6 +522,12 @@ export async function setupAuthTemplate(
} else {
}
}
} else if (hasAngularWeb) {
const authWebAngularSrc = path.join(PKG_ROOT, "templates/auth/web/angular");
if (await fs.pathExists(authWebAngularSrc)) {
await processAndCopyFiles("**/*", authWebAngularSrc, webAppDir, context);
} else {
}
}
}

Expand Down Expand Up @@ -570,6 +595,15 @@ export async function setupAddonsTemplate(
)
) {
addonSrcDir = path.join(PKG_ROOT, "templates/addons/pwa/apps/web/vite");
} else if (context.frontend.includes("angular")) {
addonSrcDir = path.join(
PKG_ROOT,
"templates/addons/pwa/apps/web/angular",
);
await addPackageDependency({
dependencies: ["@angular/service-worker"],
projectDir: webAppDir,
});
} else {
continue;
}
Expand Down Expand Up @@ -608,7 +642,7 @@ export async function setupExamplesTemplate(
const hasNuxtWeb = context.frontend.includes("nuxt");
const hasSvelteWeb = context.frontend.includes("svelte");
const hasSolidWeb = context.frontend.includes("solid");

const hasAngularWeb = context.frontend.includes("angular");
for (const example of context.examples) {
if (example === "none") continue;

Expand Down Expand Up @@ -758,6 +792,12 @@ export async function setupExamplesTemplate(
);
} else {
}
} else if (hasAngularWeb) {
const exampleWebAngularSrc = path.join(exampleBaseDir, "web/angular");
if (await fs.pathExists(exampleWebAngularSrc)) {
await processAndCopyFiles("**/*", exampleWebAngularSrc, webAppDir, context);
} else {
}
}
}

Expand Down
35 changes: 32 additions & 3 deletions apps/cli/src/helpers/setup/api-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export async function setupApi(config: ProjectConfig): Promise<void> {
const hasNuxtWeb = frontend.includes("nuxt");
const hasSvelteWeb = frontend.includes("svelte");
const hasSolidWeb = frontend.includes("solid");

const hasAngularWeb = frontend.includes("angular");
if (!isConvex && api !== "none") {
const serverDir = path.join(projectDir, "apps/server");
const serverDirExists = await fs.pathExists(serverDir);
Expand Down Expand Up @@ -106,6 +106,25 @@ export async function setupApi(config: ProjectConfig): Promise<void> {
projectDir: webDir,
});
}
} else if (hasAngularWeb) {
if(api === "trpc"){
await addPackageDependency({
dependencies: [
"@trpc/client",
"@trpc/server",
],
projectDir: webDir,
});
} else if (api === "orpc") {
await addPackageDependency({
dependencies: [
"@orpc/tanstack-query",
"@orpc/client",
"@orpc/server",
],
projectDir: webDir,
});
}
}
}

Expand Down Expand Up @@ -142,7 +161,7 @@ export async function setupApi(config: ProjectConfig): Promise<void> {
];
const needsSolidQuery = frontend.includes("solid");
const needsReactQuery = frontend.some((f) => reactBasedFrontends.includes(f));

const needsAngularQuery = frontend.includes("angular");
if (needsReactQuery && !isConvex) {
const reactQueryDeps: AvailableDependencies[] = ["@tanstack/react-query"];
const reactQueryDevDeps: AvailableDependencies[] = [
Expand Down Expand Up @@ -206,7 +225,17 @@ export async function setupApi(config: ProjectConfig): Promise<void> {
}
}
}

if(needsAngularQuery && !isConvex){
if(webDirExists){
const webPkgJsonPath = path.join(webDir, "package.json");
if (await fs.pathExists(webPkgJsonPath)) {
await addPackageDependency({
dependencies: ["@tanstack/angular-query-experimental"],
projectDir: webDir,
});
}
}
}
if (isConvex) {
if (webDirExists) {
const webPkgJsonPath = path.join(webDir, "package.json");
Expand Down
6 changes: 4 additions & 2 deletions apps/cli/src/prompts/addons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,17 @@ export async function getAddonsChoice(
frontends?.includes("react-router") ||
frontends?.includes("tanstack-router") ||
frontends?.includes("solid") ||
frontends?.includes("next");
frontends?.includes("next") ||
frontends?.includes("angular");

const hasCompatibleTauriFrontend =
frontends?.includes("react-router") ||
frontends?.includes("tanstack-router") ||
frontends?.includes("nuxt") ||
frontends?.includes("svelte") ||
frontends?.includes("solid") ||
frontends?.includes("next");
frontends?.includes("next") ||
frontends?.includes("angular");

const allPossibleOptions: AddonOption[] = [
{
Expand Down
3 changes: 2 additions & 1 deletion apps/cli/src/prompts/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export async function getApiChoice(
const includesNuxt = frontend?.includes("nuxt");
const includesSvelte = frontend?.includes("svelte");
const includesSolid = frontend?.includes("solid");
const includesAngular = frontend?.includes("angular");

let apiOptions = [
{
Expand All @@ -35,7 +36,7 @@ export async function getApiChoice(
},
];

if (includesNuxt || includesSvelte || includesSolid) {
if (includesNuxt || includesSvelte || includesSolid || includesAngular) {
apiOptions = [
{
value: "orpc" as const,
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/prompts/backend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export async function getBackendFrameworkChoice(
if (backendFramework !== undefined) return backendFramework;

const hasIncompatibleFrontend = frontends?.some(
(f) => f === "nuxt" || f === "solid",
(f) => f === "nuxt" || f === "solid" || f === "angular",
);

const backendOptions: Array<{
Expand Down
2 changes: 1 addition & 1 deletion apps/cli/src/prompts/examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export async function getExamplesChoice(
},
];

if (backend !== "elysia" && !frontends?.includes("solid")) {
if (backend !== "elysia" && !frontends?.includes("solid") && !frontends?.includes("angular")) {
options.push({
value: "ai" as const,
label: "AI Chat",
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 @@ -71,6 +71,11 @@ export async function getFrontendChoice(
label: "TanStack Start (beta)",
hint: "SSR, Server Functions, API Routes and more with TanStack Router",
},
{
value: "angular" as const,
label: "Angular",
hint: "The web framework that empowers developers to build fast, reliable applications",
},
];

const webOptions = allWebOptions.filter((option) => {
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 @@ -29,6 +29,7 @@ export type Frontend =
| "native-unistyles"
| "svelte"
| "solid"
| "angular"
| "none";
export type DatabaseSetup =
| "turso"
Expand Down
2 changes: 2 additions & 0 deletions apps/cli/src/utils/template-processor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ handlebars.registerHelper("or", (a, b) => a || b);

handlebars.registerHelper("eq", (a, b) => a === b);

handlebars.registerHelper("not", (a) => !a);

handlebars.registerHelper(
"includes",
(array, value) => Array.isArray(array) && array.includes(value),
Expand Down
29 changes: 19 additions & 10 deletions apps/cli/src/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ export function processAndValidateFlags(
f === "next" ||
f === "nuxt" ||
f === "svelte" ||
f === "solid",
f === "solid" ||
f === "angular",
);
const nativeFrontends = validOptions.filter(
(f) => f === "native-nativewind" || f === "native-unistyles",
Expand Down Expand Up @@ -189,7 +190,7 @@ export function processAndValidateFlags(

if (providedFlags.has("frontend") && options.frontend) {
const incompatibleFrontends = options.frontend.filter(
(f) => f === "nuxt" || f === "solid",
(f) => f === "nuxt" || f === "solid" || f === "angular",
);
if (incompatibleFrontends.length > 0) {
consola.fatal(
Expand Down Expand Up @@ -398,16 +399,16 @@ export function validateConfigCompatibility(
const includesNuxt = effectiveFrontend?.includes("nuxt");
const includesSvelte = effectiveFrontend?.includes("svelte");
const includesSolid = effectiveFrontend?.includes("solid");

const includesAngular = effectiveFrontend?.includes("angular");
if (
(includesNuxt || includesSvelte || includesSolid) &&
(includesNuxt || includesSvelte || includesSolid || includesAngular) &&
effectiveApi === "trpc"
) {
consola.fatal(
`tRPC API is not supported with '${
includesNuxt ? "nuxt" : includesSvelte ? "svelte" : "solid"
includesNuxt ? "nuxt" : includesSvelte ? "svelte" : includesSolid ? "solid" : "angular"
}' frontend. Please use --api orpc or --api none or remove '${
includesNuxt ? "nuxt" : includesSvelte ? "svelte" : "solid"
includesNuxt ? "nuxt" : includesSvelte ? "svelte" : includesSolid ? "solid" : "angular"
}' from --frontend.`,
);
process.exit(1);
Expand All @@ -423,14 +424,16 @@ export function validateConfigCompatibility(
f === "tanstack-router" ||
f === "react-router" ||
f === "solid" ||
f === "next";
f === "next" ||
f === "angular";
const isTauriCompatible =
f === "tanstack-router" ||
f === "react-router" ||
f === "nuxt" ||
f === "svelte" ||
f === "solid" ||
f === "next";
f === "next" ||
f === "angular";

if (config.addons?.includes("pwa") && config.addons?.includes("tauri")) {
return isPwaCompatible && isTauriCompatible;
Expand All @@ -448,11 +451,11 @@ export function validateConfigCompatibility(
let incompatibleReason = "Selected frontend is not compatible.";
if (config.addons.includes("pwa")) {
incompatibleReason =
"PWA requires tanstack-router, react-router, next, or solid.";
"PWA requires tanstack-router, react-router, next, angular, or solid.";
}
if (config.addons.includes("tauri")) {
incompatibleReason =
"Tauri requires tanstack-router, react-router, nuxt, svelte, solid, or next.";
"Tauri requires tanstack-router, react-router, nuxt, svelte, solid, next, or angular.";
}
consola.fatal(
`Incompatible addon/frontend combination: ${incompatibleReason}`,
Expand Down Expand Up @@ -498,5 +501,11 @@ export function validateConfigCompatibility(
);
process.exit(1);
}
if (config.examples.includes("ai") && includesAngular) {
consola.fatal(
"The 'ai' example is not compatible with the Angular frontend.",
);
process.exit(1);
}
}
}
Loading