Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/ui-default-shadcn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@proofkit/cli": minor
---

CLI defaults to shadcn/ui for new projects. Legacy Mantine templates are still available via a hidden `--ui mantine` flag during `init`. The selected UI is persisted in `proofkit.json` as `ui`. Existing projects using Mantine are auto-detected and remain fully supported. For shadcn-based projects, adding new pages or auth via `proofkit add` is temporarily disabled while we work on a new component system.
5 changes: 5 additions & 0 deletions packages/cli/src/cli/add/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ export async function runAddAuthAction() {
if (settings.appType !== "browser") {
return p.cancel(`Auth is not supported for your app type.`);
}
if (settings.ui === "shadcn") {
return p.cancel(
"Adding auth is not yet supported for shadcn-based projects."
);
}

const authType =
state.authType ??
Expand Down
9 changes: 9 additions & 0 deletions packages/cli/src/cli/add/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,15 @@ export const runAdd = async (name: string | undefined) => {
})
);

// For shadcn projects, block adding new pages or auth for now
if (settings.ui === "shadcn") {
if (addType === "page" || addType === "auth") {
return p.cancel(
"Adding new pages or auth is not yet supported for shadcn-based projects."
);
}
}

if (addType === "auth") {
await runAddAuthAction();
} else if (addType === "data") {
Expand Down
5 changes: 5 additions & 0 deletions packages/cli/src/cli/add/page/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ export const runAddPageAction = async (opts?: {
const projectDir = state.projectDir;

const settings = getSettings();
if (settings.ui === "shadcn") {
return p.cancel(
"Adding pages is not yet supported for shadcn-based projects."
);
}

const templates =
state.appType === "browser"
Expand Down
45 changes: 36 additions & 9 deletions packages/cli/src/cli/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,19 @@ import { initializeGit } from "~/helpers/git.js";
import { installDependencies } from "~/helpers/installDependencies.js";
import { logNextSteps } from "~/helpers/logNextSteps.js";
import { setImportAlias } from "~/helpers/setImportAlias.js";
import { getRegistryUrl, shadcnInstall } from "~/helpers/shadcn-cli.js";
import { buildPkgInstallerMap } from "~/installers/index.js";
import { ensureWebViewerAddonInstalled } from "~/installers/proofkit-webviewer.js";
import { initProgramState, state } from "~/state.js";
import { addPackageDependency } from "~/utils/addPackageDependency.js";
import { getVersion } from "~/utils/getProofKitVersion.js";
import { getUserPkgManager } from "~/utils/getUserPkgManager.js";
import { parseNameAndPath } from "~/utils/parseNameAndPath.js";
import {
getSettings,
setSettings,
type Settings,
} from "~/utils/parseSettings.js";
import { validateAppName } from "~/utils/validateAppName.js";
import { promptForFileMakerDataSource } from "./add/data-source/filemaker.js";
import { abortIfCancel } from "./utils.js";
Expand All @@ -39,6 +45,8 @@ interface CliFlags {
fmServerURL: string;
auth: "none" | "next-auth" | "clerk";
dataSource?: "filemaker" | "none" | "supabase";
/** @internal UI library selection; hidden flag */
ui?: "shadcn" | "mantine";
/** @internal Used in CI. */
CI: boolean;
/** @internal Used in CI. */
Expand Down Expand Up @@ -72,6 +80,7 @@ const defaultOptions: CliFlags = {
dataApiKey: "",
fmServerURL: "",
dataSource: undefined,
ui: "shadcn",
};

export const makeInitCommand = () => {
Expand All @@ -82,6 +91,8 @@ export const makeInitCommand = () => {
"The name of the application, as well as the name of the directory to create"
)
.option("--appType [type]", "The type of app to create", undefined)
// hidden UI selector; default is shadcn; pass --ui mantine to opt-in legacy Mantine templates
.option("--ui [ui]", undefined, undefined)
.option("--server [url]", "The URL of your FileMaker Server", undefined)
.option(
"--adminApiKey [key]",
Expand Down Expand Up @@ -166,6 +177,8 @@ type ProofKitPackageJSON = PackageJson & {
export const runInit = async (name?: string, opts?: CliFlags) => {
const pkgManager = getUserPkgManager();
const cliOptions = opts ?? defaultOptions;
// capture ui choice early into state
state.ui = (cliOptions.ui ?? "shadcn") as "shadcn" | "mantine";

const projectName =
name ||
Expand Down Expand Up @@ -211,13 +224,7 @@ export const runInit = async (name?: string, opts?: CliFlags) => {
noInstall: cliOptions.noInstall,
appRouter: cliOptions.appRouter,
});
state.projectDir = projectDir;
setImportAlias(projectDir, "@/");
addPackageDependency({
dependencies: ["@proofkit/cli", "@types/node"],
devMode: true,
projectDir,
});

// Write name to package.json
const pkgJson = fs.readJSONSync(
Expand All @@ -238,6 +245,19 @@ export const runInit = async (name?: string, opts?: CliFlags) => {
spaces: 2,
});

// Ensure proofkit.json exists with initial settings including ui
const initialSettings: Settings = {
appType: state.appType ?? "browser",
ui: (state.ui as "shadcn" | "mantine") ?? "shadcn",
auth: { type: "none" },
envFile: ".env",
dataSources: [],
tanstackQuery: false,
replacedMainPage: false,
appliedUpgrades: ["cursorRules"],
};
const { registryUrl } = setSettings(initialSettings);

// for webviewer apps FM is required, so don't ask
let dataSource =
state.appType === "webviewer" ? "filemaker" : cliOptions.dataSource;
Expand Down Expand Up @@ -285,11 +305,18 @@ export const runInit = async (name?: string, opts?: CliFlags) => {

await askForAuth({ projectDir });

if (!cliOptions.noInstall) {
await installDependencies({ projectDir });
await runCodegenCommand();
await installDependencies({ projectDir });

if (state.ui === "shadcn") {
await shadcnInstall([
`${getRegistryUrl()}/r/mode-toggle`,
"sonner",
"button",
]);
}

await runCodegenCommand();

if (!cliOptions.noGit) {
await initializeGit(projectDir);
}
Expand Down
5 changes: 5 additions & 0 deletions packages/cli/src/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,8 @@ ${" ".repeat(61 - versionCharLength)}v${version}
`;
export const DEFAULT_APP_NAME = "my-proofkit-app";
export const CREATE_FM_APP = cliName;

export const DEFAULT_REGISTRY_URL =
process.env.NODE_ENV === "development"
? "http://localhost:3005"
: "https://proofkit.dev";
58 changes: 45 additions & 13 deletions packages/cli/src/helpers/createProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { state } from "~/state.js";
import { addPackageDependency } from "~/utils/addPackageDependency.js";
import { getUserPkgManager } from "~/utils/getUserPkgManager.js";
import { replaceTextInFiles } from "./replaceText.js";
import { shadcnInstall } from "./shadcn-cli.js";

interface CreateProjectOptions {
projectName: string;
Expand All @@ -34,9 +35,14 @@ export const createBareProject = async ({
noInstall,
});

// Add new base dependencies for Tailwind v4 and shadcn/ui
addPackageDependency({
dependencies: ["@proofkit/cli", "@types/node"],
devMode: true,
});

// Add new base dependencies for Tailwind v4 and shadcn/ui or legacy Mantine
// These should match the plan and dependencyVersionMap
const BASE_DEPS = [
const SHADCN_BASE_DEPS = [
"@radix-ui/react-slot",
"@tailwindcss/postcss",
"class-variance-authority",
Expand All @@ -45,22 +51,48 @@ export const createBareProject = async ({
"tailwind-merge",
"tailwindcss",
"tw-animate-css",
"next-themes",
] as AvailableDependencies[];
const BASE_DEV_DEPS = [
const SHADCN_BASE_DEV_DEPS = [
"prettier",
"prettier-plugin-tailwindcss",
] as AvailableDependencies[];

addPackageDependency({
dependencies: BASE_DEPS,
devMode: false,
projectDir: state.projectDir,
});
addPackageDependency({
dependencies: BASE_DEV_DEPS,
devMode: true,
projectDir: state.projectDir,
});
const MANTINE_DEPS = [
"@mantine/core",
"@mantine/dates",
"@mantine/hooks",
"@mantine/modals",
"@mantine/notifications",
"mantine-react-table",
] as AvailableDependencies[];
const MANTINE_DEV_DEPS = [
"postcss",
"postcss-preset-mantine",
"postcss-simple-vars",
] as AvailableDependencies[];

if (state.ui === "mantine") {
addPackageDependency({
dependencies: MANTINE_DEPS,
devMode: false,
});
addPackageDependency({
dependencies: MANTINE_DEV_DEPS,
devMode: true,
});
} else if (state.ui === "shadcn") {
addPackageDependency({
dependencies: SHADCN_BASE_DEPS,
devMode: false,
});
addPackageDependency({
dependencies: SHADCN_BASE_DEV_DEPS,
devMode: true,
});
} else {
throw new Error(`Unsupported UI library: ${state.ui}`);
}

// Install the selected packages
installPackages({
Expand Down
26 changes: 20 additions & 6 deletions packages/cli/src/helpers/installDependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const execWithSpinner = async (
args?: string[];
stdout?: StdoutStderrOption;
onDataHandle?: (spinner: Ora) => (data: Buffer) => void;
loadingMessage?: string;
}
) => {
const { onDataHandle, args = ["install"], stdout = "pipe" } = options;
Expand All @@ -24,7 +25,9 @@ const execWithSpinner = async (
args.push("--prefer-offline");
}

const spinner = ora(`Running ${pkgManager} ${args.join(" ")} ...`).start();
const spinner = ora(
options.loadingMessage ?? `Running ${pkgManager} ${args.join(" ")} ...`
).start();
const subprocess = execa(pkgManager, args, { cwd: projectDir, stdout });

await new Promise<void>((res, rej) => {
Expand Down Expand Up @@ -93,14 +96,20 @@ export const installDependencies = async (args?: { projectDir?: string }) => {

export const runExecCommand = async ({
command,
projectDir,
projectDir = state.projectDir,
successMessage,
loadingMessage,
}: {
command: string[];
projectDir: string;
projectDir?: string;
successMessage?: string;
loadingMessage?: string;
}) => {
const spinner = await _runExecCommand({ projectDir, command });
const spinner = await _runExecCommand({
projectDir,
command,
loadingMessage,
});

// If the spinner was used to show the progress, use succeed method on it
// If not, use the succeed on a new spinner
Expand All @@ -116,10 +125,12 @@ export const runExecCommand = async ({
export const _runExecCommand = async ({
projectDir,
command,
loadingMessage,
}: {
projectDir: string;
exec?: boolean;
command: string[];
loadingMessage?: string;
}): Promise<Ora | null> => {
const pkgManager = getUserPkgManager();
switch (pkgManager) {
Expand All @@ -133,8 +144,9 @@ export const _runExecCommand = async ({
return null;
// When using yarn or pnpm, use the stdout stream and ora spinner to show the progress
case "pnpm":
return execWithSpinner(projectDir, "pnpx", {
args: [...command],
return execWithSpinner(projectDir, "pnpm", {
args: ["dlx", ...command],
loadingMessage,
onDataHandle: (spinner) => (data) => {
const text = data.toString();

Expand All @@ -148,6 +160,7 @@ export const _runExecCommand = async ({
case "yarn":
return execWithSpinner(projectDir, pkgManager, {
args: [...command],
loadingMessage,
onDataHandle: (spinner) => (data) => {
spinner.text = data.toString();
},
Expand All @@ -157,6 +170,7 @@ export const _runExecCommand = async ({
return execWithSpinner(projectDir, "bunx", {
stdout: "ignore",
args: [...command],
loadingMessage,
});
}
};
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/helpers/scaffoldProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ export const scaffoldProject = async ({

const srcDir = path.join(
PKG_ROOT,
state.appType === "browser" ? "template/nextjs" : "template/vite-wv"
state.appType === "browser"
? `template/${state.ui === "mantine" ? "nextjs-mantine" : "nextjs-shadcn"}`
: "template/vite-wv"
);

if (!noInstall) {
Expand Down
18 changes: 18 additions & 0 deletions packages/cli/src/helpers/shadcn-cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { DEFAULT_REGISTRY_URL } from "~/consts.js";
import { getSettings } from "~/utils/parseSettings.js";
import { runExecCommand } from "./installDependencies.js";

export async function shadcnInstall(components: string | string[]) {
const componentsArray = Array.isArray(components) ? components : [components];
const command = ["shadcn@latest", "add", ...componentsArray];
await runExecCommand({
command,
loadingMessage: "Installing components...",
successMessage: "Components installed successfully!",
});
}

export function getRegistryUrl(): string {
const url = getSettings().registryUrl ?? DEFAULT_REGISTRY_URL;
return url.endsWith("/") ? url.slice(0, -1) : url;
}
11 changes: 11 additions & 0 deletions packages/cli/src/installers/dependencyVersionMap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,16 @@ export const dependencyVersionMap = {

// Icons (for shadcn/ui)
"lucide-react": "^0.518.0",

// Mantine UI
"@mantine/core": "^7.15.0",
"@mantine/dates": "^7.15.0",
"@mantine/hooks": "^7.15.0",
"@mantine/modals": "^7.15.0",
"@mantine/notifications": "^7.15.0",
"mantine-react-table": "^2.0.0",

// Theme utilities
"next-themes": "^0.4.6",
} as const;
export type AvailableDependencies = keyof typeof dependencyVersionMap;
1 change: 1 addition & 0 deletions packages/cli/src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const schema = z
.optional()
.catch(undefined),
appType: z.enum(["browser", "webviewer"]).optional().catch(undefined),
ui: z.enum(["shadcn", "mantine"]).optional().catch("mantine"),
projectDir: z.string().default(process.cwd()),
authType: z.enum(["clerk", "fmaddon"]).optional(),
emailProvider: z.enum(["plunk", "resend", "none"]).optional(),
Expand Down
Loading
Loading