Skip to content
Draft
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
21 changes: 21 additions & 0 deletions packages/cli/src/cli/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { parseNameAndPath } from "~/utils/parseNameAndPath.js";
import { validateAppName } from "~/utils/validateAppName.js";
import { promptForFileMakerDataSource } from "./add/data-source/filemaker.js";
import { abortIfCancel } from "./utils.js";
import { setSettings, type Settings } from "~/utils/parseSettings.js";

interface CliFlags {
noGit: boolean;
Expand All @@ -39,6 +40,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 +75,7 @@ const defaultOptions: CliFlags = {
dataApiKey: "",
fmServerURL: "",
dataSource: undefined,
ui: "shadcn",
};

export const makeInitCommand = () => {
Expand All @@ -82,6 +86,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 +172,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 @@ -238,6 +246,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"],
};
setSettings(initialSettings);

// for webviewer apps FM is required, so don't ask
let dataSource =
state.appType === "webviewer" ? "filemaker" : cliOptions.dataSource;
Expand Down
54 changes: 41 additions & 13 deletions packages/cli/src/helpers/createProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ export const createBareProject = async ({
noInstall,
});

// Add new base dependencies for Tailwind v4 and shadcn/ui
// 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 +45,50 @@ 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,
projectDir: state.projectDir,
});
addPackageDependency({
dependencies: MANTINE_DEV_DEPS,
devMode: true,
projectDir: state.projectDir,
});
} else {
addPackageDependency({
dependencies: SHADCN_BASE_DEPS,
devMode: false,
projectDir: state.projectDir,
});
addPackageDependency({
dependencies: SHADCN_BASE_DEV_DEPS,
devMode: true,
projectDir: state.projectDir,
});
}

// Install the selected packages
installPackages({
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
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(undefined),
projectDir: z.string().default(process.cwd()),
authType: z.enum(["clerk", "fmaddon"]).optional(),
emailProvider: z.enum(["plunk", "resend", "none"]).optional(),
Expand Down
36 changes: 33 additions & 3 deletions packages/cli/src/utils/parseSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,12 @@ export type DataSource = z.infer<typeof dataSourceSchema>;

export const appTypes = ["browser", "webviewer"] as const;

export const uiTypes = ["shadcn", "mantine"] as const;
export type Ui = (typeof uiTypes)[number];

const settingsSchema = z.object({
appType: z.enum(appTypes).default("browser"),
ui: z.enum(uiTypes).default("shadcn"),
auth: authSchema,
envFile: z.string().default(".env"),
dataSources: z.array(dataSourceSchema).default([]),
Expand All @@ -60,11 +64,37 @@ let settings: Settings | undefined;
export const getSettings = () => {
if (settings) return settings;

const settingsFile: unknown = fs.readJSONSync(
path.join(state.projectDir, "proofkit.json")
);
const settingsPath = path.join(state.projectDir, "proofkit.json");
const settingsFile: unknown = fs.readJSONSync(settingsPath);

const parsed = settingsSchema.parse(settingsFile);

// Persist missing ui field for older projects; auto-detect mantine if present
const hasUiInFile = typeof (settingsFile as Record<string, unknown>)?.ui === "string";
if (!hasUiInFile) {
try {
const pkgJson = fs.readJSONSync(path.join(state.projectDir, "package.json")) as {
dependencies?: Record<string, string>;
devDependencies?: Record<string, string>;
};
const depHas = (name: string) =>
Boolean(pkgJson.dependencies?.[name] || pkgJson.devDependencies?.[name]);
const detectedUi: Ui = depHas("@mantine/core") ? "mantine" : "shadcn";
const nextSettings = { ...parsed, ui: detectedUi } as Settings;
fs.writeJSONSync(settingsPath, nextSettings, { spaces: 2 });
settings = nextSettings;
state.appType = nextSettings.appType;
return settings;
} catch {
// If detection fails, just persist default
const nextSettings = { ...parsed } as Settings;
fs.writeJSONSync(settingsPath, nextSettings, { spaces: 2 });
settings = nextSettings;
state.appType = nextSettings.appType;
return settings;
}
}

settings = parsed;
state.appType = parsed.appType;
return settings;
Expand Down
7 changes: 7 additions & 0 deletions packages/cli/template/nextjs-mantine/proofkit.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"auth": { "type": "none" },
"envFile": ".env",
"appType": "browser",
"ui": "mantine",
"appliedUpgrades": ["cursorRules"]
}
3 changes: 3 additions & 0 deletions packages/cli/template/nextjs-shadcn/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"plugins": ["prettier-plugin-tailwindcss"]
}
27 changes: 27 additions & 0 deletions packages/cli/template/nextjs-shadcn/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# ProofKit NextJS Template

This is a [NextJS](https://nextjs.org/) project bootstrapped with `@proofkit/cli`. Learn more at [proofkit.dev](https://proofkit.dev)

## What's next? How do I make an app with this?

While this template is designed to be a minimal starting point, the proofkit CLI will guide you through adding additional features and pages.

To add new things to your project, simply run the `proofkit` script from the project's root directory.

e.g. `npm run proofkit` or `pnpm proofkit` etc.

For more information, see the full [ProofKit documentation](https://proofkit.dev).

## Project Structure

ProofKit projects have an opinionated structure to help you get started and some conventions must be maintained to ensure that the CLI can properly inject new features and components.

The `src` directory is the home for your application code. It is used for most things except for configuration and is organized as follows:

- `app` - NextJS app router, where your pages and routes are defined
- `components` - Shared components used throughout the app
- `server` - Code that connects to backend databases and services that should not be exposed in the browser

Anytime you see an `internal` folder, you should not modify any files inside. These files are maintained exclusively by the ProofKit CLI and changes to them may be overwritten.

Anytime you see a componet file that begins with `slot-`, you _may_ modify the content, but do not rename, remove, or move them. These are desigend to be customized, but are still used by the CLI to inject additional content. If a slot is not needed by your app, you can have the compoment return `null` or an empty fragment: `<></>`
37 changes: 37 additions & 0 deletions packages/cli/template/nextjs-shadcn/_gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

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

# local env files
.env*.local
.env

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
21 changes: 21 additions & 0 deletions packages/cli/template/nextjs-shadcn/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "",
"css": "src/config/theme/globals.css",
"baseColor": "neutral",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/utils/styles",
"ui": "@/components/ui",
"lib": "@/utils",
"hooks": "@/utils/hooks"
},
"iconLibrary": "lucide"
}
8 changes: 8 additions & 0 deletions packages/cli/template/nextjs-shadcn/next.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { type NextConfig } from "next";

// Import env here to validate during build.
import "./src/config/env";

const nextConfig: NextConfig = {};

export default nextConfig;
Loading