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
1 change: 1 addition & 0 deletions apps/cli/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export const ADDON_COMPATIBILITY = {
fumadocs: [],
opentui: [],
wxt: [],
"docker-compose": [],
skills: [],
none: [],
} as const;
35 changes: 31 additions & 4 deletions apps/cli/src/prompts/addons.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { DEFAULT_CONFIG } from "../constants";
import { type Addons, AddonsSchema, type Auth, type Frontend } from "../types";
import {
type Addons,
AddonsSchema,
type Auth,
type Backend,
type Frontend,
type Runtime,
} from "../types";
import { getCompatibleAddons, validateAddonCompatibility } from "../utils/compatibility-rules";
import { UserCancelledError } from "../utils/errors";
import { isCancel, navigableGroupMultiselect } from "./navigable";
Expand Down Expand Up @@ -71,6 +78,10 @@ function getAddonDisplay(addon: Addons): { label: string; hint: string } {
label = "Skills";
hint = "AI coding agent skills for your stack";
break;
case "docker-compose":
label = "Docker Compose";
hint = "Containerize your app for deployment";
break;
default:
label = addon;
hint = `Add ${addon}`;
Expand All @@ -82,11 +93,17 @@ function getAddonDisplay(addon: Addons): { label: string; hint: string } {
const ADDON_GROUPS = {
Tooling: ["turborepo", "biome", "oxlint", "ultracite", "husky", "lefthook"],
Documentation: ["starlight", "fumadocs"],
Extensions: ["pwa", "tauri", "opentui", "wxt"],
Extensions: ["pwa", "tauri", "opentui", "wxt", "docker-compose"],
AI: ["ruler", "skills"],
};

export async function getAddonsChoice(addons?: Addons[], frontends?: Frontend[], auth?: Auth) {
export async function getAddonsChoice(
addons?: Addons[],
frontends?: Frontend[],
auth?: Auth,
backend?: Backend,
runtime?: Runtime,
) {
if (addons !== undefined) return addons;

const allAddons = AddonsSchema.options.filter((addon) => addon !== "none");
Expand All @@ -100,7 +117,13 @@ export async function getAddonsChoice(addons?: Addons[], frontends?: Frontend[],
const frontendsArray = frontends || [];

for (const addon of allAddons) {
const { isCompatible } = validateAddonCompatibility(addon, frontendsArray, auth);
const { isCompatible } = validateAddonCompatibility(
addon,
frontendsArray,
auth,
backend,
runtime,
);
if (!isCompatible) continue;

const { label, hint } = getAddonDisplay(addon);
Expand Down Expand Up @@ -152,6 +175,8 @@ export async function getAddonsToAdd(
frontend: Frontend[],
existingAddons: Addons[] = [],
auth?: Auth,
backend?: Backend,
runtime?: Runtime,
) {
const groupedOptions: Record<string, AddonOption[]> = {
Tooling: [],
Expand All @@ -167,6 +192,8 @@ export async function getAddonsToAdd(
frontendArray,
existingAddons,
auth,
backend,
runtime,
);

for (const addon of compatibleAddons) {
Expand Down
9 changes: 8 additions & 1 deletion apps/cli/src/prompts/config-prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,14 @@ export async function gatherConfig(
auth: ({ results }) => getAuthChoice(flags.auth, results.backend, results.frontend),
payments: ({ results }) =>
getPaymentsChoice(flags.payments, results.auth, results.backend, results.frontend),
addons: ({ results }) => getAddonsChoice(flags.addons, results.frontend, results.auth),
addons: ({ results }) =>
getAddonsChoice(
flags.addons,
results.frontend,
results.auth,
results.backend,
results.runtime,
),
examples: ({ results }) =>
getExamplesChoice(
flags.examples,
Expand Down
35 changes: 32 additions & 3 deletions apps/cli/src/utils/compatibility-rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
Frontend,
Payments,
ProjectConfig,
Runtime,
ServerDeploy,
WebDeploy,
} from "../types";
Expand Down Expand Up @@ -255,6 +256,8 @@ export function validateAddonCompatibility(
addon: Addons,
frontend: Frontend[],
_auth?: Auth,
backend?: Backend,
runtime?: Runtime,
): { isCompatible: boolean; reason?: string } {
const compatibleFrontends = ADDON_COMPATIBILITY[addon];

Expand All @@ -272,6 +275,22 @@ export function validateAddonCompatibility(
}
}

// Docker Compose specific compatibility checks
if (addon === "docker-compose") {
if (backend === "convex") {
return {
isCompatible: false,
reason: "docker-compose is not compatible with Convex backend (managed service)",
};
}
if (runtime === "workers") {
return {
isCompatible: false,
reason: "docker-compose is not compatible with Cloudflare Workers runtime",
};
}
}

return { isCompatible: true };
}

Expand All @@ -280,13 +299,15 @@ export function getCompatibleAddons(
frontend: Frontend[],
existingAddons: Addons[] = [],
auth?: Auth,
backend?: Backend,
runtime?: Runtime,
) {
return allAddons.filter((addon) => {
if (existingAddons.includes(addon)) return false;

if (addon === "none") return false;

const { isCompatible } = validateAddonCompatibility(addon, frontend, auth);
const { isCompatible } = validateAddonCompatibility(addon, frontend, auth, backend, runtime);
return isCompatible;
});
}
Expand All @@ -295,12 +316,20 @@ export function validateAddonsAgainstFrontends(
addons: Addons[] = [],
frontends: Frontend[] = [],
auth?: Auth,
backend?: Backend,
runtime?: Runtime,
): ValidationResult {
for (const addon of addons) {
if (addon === "none") continue;
const { isCompatible, reason } = validateAddonCompatibility(addon, frontends, auth);
const { isCompatible, reason } = validateAddonCompatibility(
addon,
frontends,
auth,
backend,
runtime,
);
if (!isCompatible) {
return validationErr(`Incompatible addon/frontend combination: ${reason}`);
return validationErr(`Incompatible addon combination: ${reason}`);
}
}
return Result.ok(undefined);
Expand Down
16 changes: 14 additions & 2 deletions apps/cli/src/utils/config-validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,13 @@ export function validateFullConfig(
}

if (config.addons && config.addons.length > 0) {
yield* validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth);
yield* validateAddonsAgainstFrontends(
config.addons,
config.frontend,
config.auth,
config.backend,
config.runtime,
);
config.addons = [...new Set(config.addons)];
}

Expand Down Expand Up @@ -507,7 +513,13 @@ export function validateConfigForProgrammaticUse(config: Partial<ProjectConfig>)
);

if (config.addons && config.addons.length > 0) {
yield* validateAddonsAgainstFrontends(config.addons, config.frontend, config.auth);
yield* validateAddonsAgainstFrontends(
config.addons,
config.frontend,
config.auth,
config.backend,
config.runtime,
);
}

yield* validateExamplesCompatibility(
Expand Down
Loading