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/cli-init-claude-cursorignore.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@proofkit/cli": patch
---

Init now writes `CLAUDE.md` as `@AGENTS.md` and adds `.cursorignore` to keep `CLAUDE.md` out of Cursor scans.
5 changes: 5 additions & 0 deletions .changeset/fix-cli-dot-name-normalization.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@proofkit/cli": patch
---

Normalize and validate `.`-derived CLI project names from the current directory consistently, including whitespace-to-dash conversion and lowercasing
5 changes: 5 additions & 0 deletions .changeset/mean-worms-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@proofkit/cli": patch
---

Drop the unused `nextjs-mantine` scaffold from the current CLI and always scaffold browser apps from `nextjs-shadcn`.
5 changes: 5 additions & 0 deletions .changeset/remove-cli-ui-flag.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@proofkit/cli": patch
---

Remove the `--ui` init flag. ProofKit now only scaffolds shadcn.
5 changes: 5 additions & 0 deletions .changeset/soften-project-name-spaces.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@proofkit/cli": patch
---

Allow spaces in project names by normalizing them to dashes
5 changes: 5 additions & 0 deletions .changeset/tidy-dots-speak.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@proofkit/cli": patch
---

Clarify that `.` uses the current directory for `proofkit init`
2 changes: 1 addition & 1 deletion apps/docs/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import "./global.css";
import type { Metadata } from "next";
import { RootProvider } from "fumadocs-ui/provider/next";
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import type { ReactNode } from "react";

Expand Down
41 changes: 10 additions & 31 deletions packages/cli/src/cli/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,6 @@ 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 non-interactive mode. */
Expand Down Expand Up @@ -77,16 +75,13 @@ const defaultOptions: CliFlags = {
dataApiKey: "",
fmServerURL: "",
dataSource: undefined,
ui: "shadcn",
};

export const makeInitCommand = () => {
const initCommand = new Command("init")
.description("Create a new project with ProofKit")
.argument("[dir]", "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]", "Admin API key for OttoFMS. If provided, will skip login prompt", undefined)
.option("--fileName [name]", "The name of the FileMaker file to use for the web app", undefined)
Expand Down Expand Up @@ -198,8 +193,7 @@ export const runInit = async (name?: string, opts?: CliFlags) => {
const nonInteractive = isNonInteractiveMode();
const noInstall = cliOptions.noInstall ?? (opts as { install?: boolean } | undefined)?.install === false;
const noGit = cliOptions.noGit ?? (opts as { git?: boolean } | undefined)?.git === false;
// capture ui choice early into state
state.ui = (cliOptions.ui ?? "shadcn") as "shadcn" | "mantine";
state.ui = "shadcn";

let projectName = name;
if (!projectName) {
Expand Down Expand Up @@ -299,30 +293,15 @@ export const runInit = async (name?: string, opts?: CliFlags) => {
spaces: 2,
});

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

// for webviewer apps FM is required, so don't ask
Expand Down
7 changes: 6 additions & 1 deletion packages/cli/src/core/planInit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,12 @@ export function planInit(
path: path.join(targetDir, ".env"),
content: createEnvFileContent(),
},
writes: [],
writes: [
{
path: path.join(targetDir, ".cursorignore"),
content: "CLAUDE.md\n",
},
],
commands: [
...(request.noInstall ? [] : [{ type: "install" as const }]),
...(request.dataSource === "filemaker" &&
Expand Down
31 changes: 3 additions & 28 deletions packages/cli/src/helpers/createProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ export const createBareProject = async ({
devMode: true,
});

// Add new base dependencies for Tailwind v4 and shadcn/ui or legacy Mantine
// These should match the plan and dependencyVersionMap
// Add base deps for current templates. Legacy Mantine projects remain supported elsewhere.
const NEXT_SHADCN_BASE_DEPS = [
"@radix-ui/react-slot",
"@tailwindcss/postcss",
Expand Down Expand Up @@ -72,31 +71,7 @@ export const createBareProject = async ({
const SHADCN_BASE_DEV_DEPS = ["ultracite"] as AvailableDependencies[];
const VITE_SHADCN_BASE_DEV_DEPS = ["@proofkit/typegen", "ultracite"] as AvailableDependencies[];

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",
"ultracite",
] as AvailableDependencies[];

if (state.ui === "mantine") {
addPackageDependency({
dependencies: MANTINE_DEPS,
devMode: false,
});
addPackageDependency({
dependencies: MANTINE_DEV_DEPS,
devMode: true,
});
} else if (state.ui === "shadcn") {
if (state.ui === "shadcn") {
addPackageDependency({
dependencies: state.appType === "webviewer" ? VITE_SHADCN_BASE_DEPS : NEXT_SHADCN_BASE_DEPS,
devMode: false,
Expand All @@ -106,7 +81,7 @@ export const createBareProject = async ({
devMode: true,
});
} else {
throw new Error(`Unsupported UI library: ${state.ui}`);
throw new Error(`Unsupported scaffold UI library: ${state.ui}`);
}

// Install the selected packages
Expand Down
8 changes: 2 additions & 6 deletions packages/cli/src/helpers/scaffoldProject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,7 @@ export const scaffoldProject = async ({
}: InstallerOptions & { force?: boolean }) => {
const projectDir = state.projectDir;

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

if (noInstall) {
logger.info("");
Expand Down Expand Up @@ -129,6 +124,7 @@ export const scaffoldProject = async ({

// Rename gitignore
fs.renameSync(path.join(projectDir, "_gitignore"), path.join(projectDir, ".gitignore"));
fs.writeFileSync(path.join(projectDir, ".cursorignore"), "CLAUDE.md\n", "utf8");

const scaffoldedName = projectName === "." ? "App" : chalk.cyan.bold(projectName);

Expand Down
4 changes: 1 addition & 3 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export const runDefaultCommand = (rawFlags?: Partial<CliFlags>) =>
});

const initDirectoryArg = optionalArg(textArg({ name: "dir" })).pipe(
withArgDescription("The project name or target directory"),
withArgDescription("The project name or target directory. Use `.` for the current directory, best when it is empty."),
);

function optionalTextOption(name: string, description: string) {
Expand Down Expand Up @@ -147,7 +147,6 @@ function makeInitCommand() {
{
dir: initDirectoryArg,
appType: optionalChoiceOption("app-type", ["browser", "webviewer"] as const, "The type of app to create"),
ui: optionalChoiceOption("ui", ["shadcn", "mantine"] as const, "The UI scaffold to create"),
server: optionalTextOption("server", "The URL of your FileMaker Server"),
adminApiKey: optionalTextOption("admin-api-key", "Admin API key for OttoFMS"),
fileName: optionalTextOption(
Expand Down Expand Up @@ -180,7 +179,6 @@ function makeInitCommand() {
const flags: CliFlags = {
...defaultCliFlags,
appType: getOrUndefined(options.appType),
ui: getOrUndefined(options.ui),
server: getOrUndefined(options.server),
adminApiKey: getOrUndefined(options.adminApiKey),
fileName: getOrUndefined(options.fileName),
Expand Down
5 changes: 1 addition & 4 deletions packages/cli/src/services/live.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,13 +184,10 @@ const fileSystemService = {
};

const templateService = {
getTemplateDir: (appType: AppType, ui: UIType) => {
getTemplateDir: (appType: AppType, _ui: UIType) => {
if (appType === "webviewer") {
return path.join(TEMPLATE_ROOT, "vite-wv");
}
if (ui === "mantine") {
return path.join(TEMPLATE_ROOT, "nextjs-mantine");
}
return path.join(TEMPLATE_ROOT, "nextjs-shadcn");
},
};
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const schema = z
localBuild: z.boolean().default(false),
baseCommand: z.enum(["add", "init", "deploy", "upgrade", "remove"]).optional().catch(undefined),
appType: z.enum(["browser", "webviewer"]).optional().catch(undefined),
ui: z.enum(["shadcn", "mantine"]).optional().catch("mantine"),
ui: z.enum(["shadcn", "mantine"]).optional().catch("shadcn"),
projectDir: z.string().default(process.cwd()),
authType: z.enum(["clerk", "fmaddon"]).optional(),
emailProvider: z.enum(["plunk", "resend", "none"]).optional(),
Expand Down
8 changes: 5 additions & 3 deletions packages/cli/src/utils/parseNameAndPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import pathModule from "node:path";

import { removeTrailingSlash } from "./removeTrailingSlash.js";

const whitespaceRegex = /\s+/g;

/**
* Parses the appName and its path from the user input.
*
Expand All @@ -18,7 +20,7 @@ import { removeTrailingSlash } from "./removeTrailingSlash.js";
* - dir/app => ["app", "dir/app"]
*/
export const parseNameAndPath = (rawInput: string) => {
const input = removeTrailingSlash(rawInput);
const input = removeTrailingSlash(rawInput).replace(whitespaceRegex, "-").toLowerCase();

const paths = input.split("/");

Expand All @@ -27,12 +29,12 @@ export const parseNameAndPath = (rawInput: string) => {
// If the user ran `npx proofkit .` or similar, the appName should be the current directory
if (appName === ".") {
const parsedCwd = pathModule.resolve(process.cwd());
appName = pathModule.basename(parsedCwd);
appName = pathModule.basename(parsedCwd).replace(whitespaceRegex, "-").toLowerCase();
}

// If the first part is a @, it's a scoped package
const indexOfDelimiter = paths.findIndex((p) => p.startsWith("@"));
if (paths.findIndex((p) => p.startsWith("@")) !== -1) {
if (indexOfDelimiter !== -1) {
appName = paths.slice(indexOfDelimiter).join("/");
}

Expand Down
13 changes: 9 additions & 4 deletions packages/cli/src/utils/projectName.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import path from "node:path";

const TRAILING_SLASHES_REGEX = /\/+$/;
const PATH_SEPARATOR_REGEX = /\\/g;
const WHITESPACE_REGEX = /\s+/g;
const VALID_APP_NAME_REGEX = /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;

function normalizeProjectName(value: string) {
Expand All @@ -12,13 +13,17 @@ function trimTrailingSlashes(value: string) {
return normalizeProjectName(value).replace(TRAILING_SLASHES_REGEX, "");
}

function normalizeProjectNameForPackage(value: string) {
return trimTrailingSlashes(value).replace(WHITESPACE_REGEX, "-").toLowerCase();
}

export function parseNameAndPath(projectName: string): [scopedAppName: string, appDir: string] {
const normalized = trimTrailingSlashes(projectName);
const normalized = normalizeProjectNameForPackage(projectName);
const segments = normalized.split("/");
let scopedAppName = segments.at(-1) ?? "";

if (scopedAppName === ".") {
scopedAppName = path.basename(path.resolve(process.cwd()));
scopedAppName = normalizeProjectNameForPackage(path.basename(path.resolve(process.cwd())));
}

const scopeIndex = segments.findIndex((segment) => segment.startsWith("@"));
Expand All @@ -32,10 +37,10 @@ export function parseNameAndPath(projectName: string): [scopedAppName: string, a
}

export function validateAppName(projectName: string) {
const normalized = trimTrailingSlashes(projectName);
const normalized = normalizeProjectNameForPackage(projectName);
if (normalized === ".") {
const currentDirName = path.basename(path.resolve(process.cwd()));
return VALID_APP_NAME_REGEX.test(currentDirName)
return VALID_APP_NAME_REGEX.test(currentDirName.replace(WHITESPACE_REGEX, "-").toLowerCase())
? undefined
: "Name must consist of only lowercase alphanumeric characters, '-', and '_'";
}
Expand Down
11 changes: 9 additions & 2 deletions packages/cli/src/utils/validateAppName.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import path from "node:path";

import { removeTrailingSlash } from "./removeTrailingSlash.js";

const validationRegExp = /^(?:@[a-z0-9-*~][a-z0-9-*._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/;
const whitespaceRegex = /\s+/g;

//Validate a string against allowed package.json names
export const validateAppName = (rawInput: string) => {
const input = removeTrailingSlash(rawInput);
const input = removeTrailingSlash(rawInput).replace(whitespaceRegex, "-").toLowerCase();
const paths = input.split("/");

// If the first part is a @, it's a scoped package
Expand All @@ -15,7 +18,11 @@ export const validateAppName = (rawInput: string) => {
appName = paths.slice(indexOfDelimiter).join("/");
}

if (input === "." || validationRegExp.test(appName ?? "")) {
if (input === ".") {
appName = path.basename(path.resolve(process.cwd())).replace(whitespaceRegex, "-").toLowerCase();
}

if (validationRegExp.test(appName ?? "")) {
return;
}
return "Name must consist of only lowercase alphanumeric characters, '-', and '_'";
Expand Down
1 change: 0 additions & 1 deletion packages/cli/template/nextjs-mantine/AGENTS.md

This file was deleted.

1 change: 0 additions & 1 deletion packages/cli/template/nextjs-mantine/CLAUDE.md

This file was deleted.

27 changes: 0 additions & 27 deletions packages/cli/template/nextjs-mantine/README.md

This file was deleted.

Loading
Loading