diff --git a/.changeset/cli-init-claude-cursorignore.md b/.changeset/cli-init-claude-cursorignore.md new file mode 100644 index 00000000..a1602929 --- /dev/null +++ b/.changeset/cli-init-claude-cursorignore.md @@ -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. diff --git a/.changeset/fix-cli-dot-name-normalization.md b/.changeset/fix-cli-dot-name-normalization.md new file mode 100644 index 00000000..74ea8e56 --- /dev/null +++ b/.changeset/fix-cli-dot-name-normalization.md @@ -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 diff --git a/.changeset/mean-worms-notice.md b/.changeset/mean-worms-notice.md new file mode 100644 index 00000000..802729d9 --- /dev/null +++ b/.changeset/mean-worms-notice.md @@ -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`. diff --git a/.changeset/remove-cli-ui-flag.md b/.changeset/remove-cli-ui-flag.md new file mode 100644 index 00000000..0fa8e09b --- /dev/null +++ b/.changeset/remove-cli-ui-flag.md @@ -0,0 +1,5 @@ +--- +"@proofkit/cli": patch +--- + +Remove the `--ui` init flag. ProofKit now only scaffolds shadcn. diff --git a/.changeset/soften-project-name-spaces.md b/.changeset/soften-project-name-spaces.md new file mode 100644 index 00000000..0abec831 --- /dev/null +++ b/.changeset/soften-project-name-spaces.md @@ -0,0 +1,5 @@ +--- +"@proofkit/cli": patch +--- + +Allow spaces in project names by normalizing them to dashes diff --git a/.changeset/tidy-dots-speak.md b/.changeset/tidy-dots-speak.md new file mode 100644 index 00000000..5ca55665 --- /dev/null +++ b/.changeset/tidy-dots-speak.md @@ -0,0 +1,5 @@ +--- +"@proofkit/cli": patch +--- + +Clarify that `.` uses the current directory for `proofkit init` diff --git a/apps/docs/src/app/layout.tsx b/apps/docs/src/app/layout.tsx index 9f6ee70d..6126d729 100644 --- a/apps/docs/src/app/layout.tsx +++ b/apps/docs/src/app/layout.tsx @@ -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"; diff --git a/packages/cli/src/cli/init.ts b/packages/cli/src/cli/init.ts index 2286ca2c..aa9de9e3 100644 --- a/packages/cli/src/cli/init.ts +++ b/packages/cli/src/cli/init.ts @@ -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. */ @@ -77,7 +75,6 @@ const defaultOptions: CliFlags = { dataApiKey: "", fmServerURL: "", dataSource: undefined, - ui: "shadcn", }; export const makeInitCommand = () => { @@ -85,8 +82,6 @@ export const makeInitCommand = () => { .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) @@ -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) { @@ -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 diff --git a/packages/cli/src/core/planInit.ts b/packages/cli/src/core/planInit.ts index eaa0c802..3c4ec0dc 100644 --- a/packages/cli/src/core/planInit.ts +++ b/packages/cli/src/core/planInit.ts @@ -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" && diff --git a/packages/cli/src/helpers/createProject.ts b/packages/cli/src/helpers/createProject.ts index 9f579b88..63609d00 100644 --- a/packages/cli/src/helpers/createProject.ts +++ b/packages/cli/src/helpers/createProject.ts @@ -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", @@ -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, @@ -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 diff --git a/packages/cli/src/helpers/scaffoldProject.ts b/packages/cli/src/helpers/scaffoldProject.ts index 7905eb0a..f5667760 100644 --- a/packages/cli/src/helpers/scaffoldProject.ts +++ b/packages/cli/src/helpers/scaffoldProject.ts @@ -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(""); @@ -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); diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index c432aa26..a526696a 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -110,7 +110,7 @@ export const runDefaultCommand = (rawFlags?: Partial) => }); 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) { @@ -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( @@ -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), diff --git a/packages/cli/src/services/live.ts b/packages/cli/src/services/live.ts index 4f4ac7e1..cea4082e 100644 --- a/packages/cli/src/services/live.ts +++ b/packages/cli/src/services/live.ts @@ -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"); }, }; diff --git a/packages/cli/src/state.ts b/packages/cli/src/state.ts index a2bd8ebb..1130be82 100644 --- a/packages/cli/src/state.ts +++ b/packages/cli/src/state.ts @@ -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(), diff --git a/packages/cli/src/utils/parseNameAndPath.ts b/packages/cli/src/utils/parseNameAndPath.ts index a4d4507e..1947d0f8 100644 --- a/packages/cli/src/utils/parseNameAndPath.ts +++ b/packages/cli/src/utils/parseNameAndPath.ts @@ -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. * @@ -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("/"); @@ -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("/"); } diff --git a/packages/cli/src/utils/projectName.ts b/packages/cli/src/utils/projectName.ts index d5cb953a..ef944500 100644 --- a/packages/cli/src/utils/projectName.ts +++ b/packages/cli/src/utils/projectName.ts @@ -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) { @@ -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("@")); @@ -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 '_'"; } diff --git a/packages/cli/src/utils/validateAppName.ts b/packages/cli/src/utils/validateAppName.ts index b5b4e42e..e2173d94 100644 --- a/packages/cli/src/utils/validateAppName.ts +++ b/packages/cli/src/utils/validateAppName.ts @@ -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 @@ -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 '_'"; diff --git a/packages/cli/template/nextjs-mantine/AGENTS.md b/packages/cli/template/nextjs-mantine/AGENTS.md deleted file mode 100644 index 6b2924ba..00000000 --- a/packages/cli/template/nextjs-mantine/AGENTS.md +++ /dev/null @@ -1 +0,0 @@ -__AGENT_INSTRUCTIONS__ diff --git a/packages/cli/template/nextjs-mantine/CLAUDE.md b/packages/cli/template/nextjs-mantine/CLAUDE.md deleted file mode 120000 index 47dc3e3d..00000000 --- a/packages/cli/template/nextjs-mantine/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -AGENTS.md \ No newline at end of file diff --git a/packages/cli/template/nextjs-mantine/README.md b/packages/cli/template/nextjs-mantine/README.md deleted file mode 100644 index 4f416a4a..00000000 --- a/packages/cli/template/nextjs-mantine/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# 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: `<>` diff --git a/packages/cli/template/nextjs-mantine/_gitignore b/packages/cli/template/nextjs-mantine/_gitignore deleted file mode 100644 index 00bba9bb..00000000 --- a/packages/cli/template/nextjs-mantine/_gitignore +++ /dev/null @@ -1,37 +0,0 @@ -# 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 diff --git a/packages/cli/template/nextjs-mantine/components.json b/packages/cli/template/nextjs-mantine/components.json deleted file mode 100644 index 0d27c449..00000000 --- a/packages/cli/template/nextjs-mantine/components.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "$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": "tw:" - }, - "aliases": { - "components": "@/components", - "utils": "@/utils/styles", - "ui": "@/components/ui", - "lib": "@/utils", - "hooks": "@/utils/hooks" - }, - "iconLibrary": "lucide" -} diff --git a/packages/cli/template/nextjs-mantine/next.config.ts b/packages/cli/template/nextjs-mantine/next.config.ts deleted file mode 100644 index 9555317e..00000000 --- a/packages/cli/template/nextjs-mantine/next.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { type NextConfig } from "next"; - -// Import env here to validate during build. -import "./src/config/env"; - -const nextConfig: NextConfig = { - experimental: { - optimizePackageImports: ["@mantine/core", "@mantine/hooks"], - }, -}; - -export default nextConfig; diff --git a/packages/cli/template/nextjs-mantine/package.json b/packages/cli/template/nextjs-mantine/package.json deleted file mode 100644 index 5745b337..00000000 --- a/packages/cli/template/nextjs-mantine/package.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "name": "template", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "next dev --turbopack", - "build": "next build", - "start": "next start", - "lint": "ultracite check .", - "format": "ultracite fix .", - "proofkit": "proofkit", - "typegen": "npx @proofkit/typegen", - "typegen:ui": "npx @proofkit/typegen ui", - "deploy": "proofkit deploy" - }, - "dependencies": { - "@hookform/resolvers": "^5.1.1", - "@next-safe-action/adapter-react-hook-form": "^2.0.0", - "next-safe-action": "^8.0.4", - "react-hook-form": "^7.54.2", - "@tabler/icons-react": "^3.30.0", - "@mantine/core": "^7.17.0", - "@mantine/dates": "^7.17.0", - "@mantine/hooks": "^7.17.0", - "@mantine/modals": "^7.17.0", - "@mantine/notifications": "^7.17.0", - "mantine-react-table": "2.0.0-beta.9", - "@t3-oss/env-nextjs": "^0.12.0", - "dayjs": "^1.11.13", - "next": "^15.2.7", - "react": "19.0.0", - "react-dom": "19.0.0", - "zod": "^3.24.2" - }, - "devDependencies": { - "@types/node": "^20", - "@types/react": "npm:types-react@19.0.12", - "@types/react-dom": "npm:types-react-dom@19.0.4", - "postcss": "^8.4.41", - "ultracite": "7.0.8", - "postcss-preset-mantine": "^1.17.0", - "postcss-simple-vars": "^7.0.1", - "typescript": "^5" - }, - "pnpm": { - "overrides": { - "@types/react": "npm:types-react@19.0.0-rc.1", - "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1" - } - } -} diff --git a/packages/cli/template/nextjs-mantine/postcss.config.cjs b/packages/cli/template/nextjs-mantine/postcss.config.cjs deleted file mode 100644 index 085a0ef9..00000000 --- a/packages/cli/template/nextjs-mantine/postcss.config.cjs +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = { - plugins: { - "@tailwindcss/postcss": {}, - "postcss-preset-mantine": {}, - "postcss-simple-vars": { - variables: { - "mantine-breakpoint-xs": "36em", - "mantine-breakpoint-sm": "48em", - "mantine-breakpoint-md": "62em", - "mantine-breakpoint-lg": "75em", - "mantine-breakpoint-xl": "88em", - }, - }, - }, -}; diff --git a/packages/cli/template/nextjs-mantine/proofkit.json b/packages/cli/template/nextjs-mantine/proofkit.json deleted file mode 100644 index c536f9bf..00000000 --- a/packages/cli/template/nextjs-mantine/proofkit.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "auth": { "type": "none" }, - "envFile": ".env", - "appType": "browser", - "ui": "mantine", - "appliedUpgrades": ["cursorRules"] -} diff --git a/packages/cli/template/nextjs-mantine/public/favicon.ico b/packages/cli/template/nextjs-mantine/public/favicon.ico deleted file mode 100644 index ba9355b8..00000000 Binary files a/packages/cli/template/nextjs-mantine/public/favicon.ico and /dev/null differ diff --git a/packages/cli/template/nextjs-mantine/public/proofkit.png b/packages/cli/template/nextjs-mantine/public/proofkit.png deleted file mode 100644 index 2969f842..00000000 Binary files a/packages/cli/template/nextjs-mantine/public/proofkit.png and /dev/null differ diff --git a/packages/cli/template/nextjs-mantine/src/app/(main)/layout.tsx b/packages/cli/template/nextjs-mantine/src/app/(main)/layout.tsx deleted file mode 100644 index 57a8452b..00000000 --- a/packages/cli/template/nextjs-mantine/src/app/(main)/layout.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import AppShell from "@/components/AppShell/internal/AppShell"; -import React from "react"; - -export default function Layout({ children }: { children: React.ReactNode }) { - return {children}; -} diff --git a/packages/cli/template/nextjs-mantine/src/app/(main)/page.tsx b/packages/cli/template/nextjs-mantine/src/app/(main)/page.tsx deleted file mode 100644 index 0f180c04..00000000 --- a/packages/cli/template/nextjs-mantine/src/app/(main)/page.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { - ActionIcon, - Anchor, - AppShellFooter, - Box, - Code, - Container, - Group, - Image, - px, - Stack, - Text, - Title, -} from "@mantine/core"; -import { IconBrandGithub, IconExternalLink } from "@tabler/icons-react"; - -export default function Home() { - return ( - <> - - - ProofKit - Welcome! - - - This is the base template home page. To add more pages, components, - or other features, run the ProofKit CLI from within your project. - - __PNPM_COMMAND__ proofkit - - - To change this page, open src/app/(main)/page.tsx - - - - ProofKit Docs - - - - - - - - - - Sponsored by{" "} - - Proof+Geist - {" "} - and{" "} - - Ottomatic - - - - - - - - - - - - - - - ); -} diff --git a/packages/cli/template/nextjs-mantine/src/app/layout.tsx b/packages/cli/template/nextjs-mantine/src/app/layout.tsx deleted file mode 100644 index 9512cb63..00000000 --- a/packages/cli/template/nextjs-mantine/src/app/layout.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Suspense } from "react"; -import { theme } from "@/config/theme/mantine-theme"; -import { ColorSchemeScript, MantineProvider } from "@mantine/core"; -import { ModalsProvider } from "@mantine/modals"; -import { Notifications } from "@mantine/notifications"; - -import "@mantine/core/styles.css"; -import "@mantine/notifications/styles.css"; -import "@mantine/dates/styles.css"; -import "mantine-react-table/styles.css"; -import "@/config/theme/globals.css"; - -import { type Metadata } from "next"; - -export const metadata: Metadata = { - title: "My ProofKit App", - description: "Generated by proofkit", - icons: [{ rel: "icon", url: "/favicon.ico" }], -}; - -export default function RootLayout({ - children, -}: Readonly<{ children: React.ReactNode }>) { - return ( - - - - - - - - - - {children} - - - - ); -} diff --git a/packages/cli/template/nextjs-mantine/src/app/navigation.tsx b/packages/cli/template/nextjs-mantine/src/app/navigation.tsx deleted file mode 100644 index 887073db..00000000 --- a/packages/cli/template/nextjs-mantine/src/app/navigation.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import { type ProofKitRoute } from "@proofkit/cli"; - -export const primaryRoutes: ProofKitRoute[] = [ - { - label: "Dashboard", - type: "link", - href: "/", - exactMatch: true, - }, -]; - -export const secondaryRoutes: ProofKitRoute[] = []; diff --git a/packages/cli/template/nextjs-mantine/src/components/AppLogo.tsx b/packages/cli/template/nextjs-mantine/src/components/AppLogo.tsx deleted file mode 100644 index f5ea4966..00000000 --- a/packages/cli/template/nextjs-mantine/src/components/AppLogo.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { IconInfinity } from "@tabler/icons-react"; -import React from "react"; - -export default function AppLogo() { - return ; -} diff --git a/packages/cli/template/nextjs-mantine/src/components/AppShell/internal/AppShell.tsx b/packages/cli/template/nextjs-mantine/src/components/AppShell/internal/AppShell.tsx deleted file mode 100644 index 8c4270df..00000000 --- a/packages/cli/template/nextjs-mantine/src/components/AppShell/internal/AppShell.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Header } from "@/components/AppShell/internal/Header"; -import { AppShell, AppShellHeader, AppShellMain } from "@mantine/core"; -import React from "react"; - -import { headerHeight } from "./config"; - -export default function MainAppShell({ - children, -}: { - children: React.ReactNode; -}) { - return ( - - -
- - - {children} - - ); -} diff --git a/packages/cli/template/nextjs-mantine/src/components/AppShell/internal/Header.module.css b/packages/cli/template/nextjs-mantine/src/components/AppShell/internal/Header.module.css deleted file mode 100644 index 79d81bad..00000000 --- a/packages/cli/template/nextjs-mantine/src/components/AppShell/internal/Header.module.css +++ /dev/null @@ -1,40 +0,0 @@ -.header { - /* height: rem(56px); */ - margin-bottom: rem(120px); - background-color: var(--mantine-color-body); - border-bottom: rem(1px) solid - light-dark(var(--mantine-color-gray-3), var(--mantine-color-dark-4)); -} - -.inner { - /* height: rem(56px); */ - display: flex; - justify-content: space-between; - align-items: center; -} - -.link { - display: block; - line-height: 1; - padding: rem(8px) rem(12px); - border-radius: var(--mantine-radius-sm); - text-decoration: none; - color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-0)); - font-size: var(--mantine-font-size-sm); - font-weight: 500; - cursor: pointer; - background: none; - border: none; - - @mixin hover { - background-color: light-dark( - var(--mantine-color-gray-0), - var(--mantine-color-dark-6) - ); - } - - [data-mantine-color-scheme] &[data-active] { - background-color: var(--mantine-primary-color-filled); - color: var(--mantine-color-white); - } -} diff --git a/packages/cli/template/nextjs-mantine/src/components/AppShell/internal/Header.tsx b/packages/cli/template/nextjs-mantine/src/components/AppShell/internal/Header.tsx deleted file mode 100644 index 4409b1d6..00000000 --- a/packages/cli/template/nextjs-mantine/src/components/AppShell/internal/Header.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Box, Container, Group } from "@mantine/core"; - -import SlotHeaderCenter from "../slot-header-center"; -import SlotHeaderLeft from "../slot-header-left"; -import SlotHeaderRight from "../slot-header-right"; -import { headerHeight } from "./config"; -import classes from "./Header.module.css"; -import HeaderMobileMenu from "./HeaderMobileMenu"; - -export function Header() { - return ( -
- - - - - - - - - - - - - - -
- ); -} diff --git a/packages/cli/template/nextjs-mantine/src/components/AppShell/internal/HeaderMobileMenu.tsx b/packages/cli/template/nextjs-mantine/src/components/AppShell/internal/HeaderMobileMenu.tsx deleted file mode 100644 index 910104fb..00000000 --- a/packages/cli/template/nextjs-mantine/src/components/AppShell/internal/HeaderMobileMenu.tsx +++ /dev/null @@ -1,27 +0,0 @@ -"use client"; - -import { Burger, Menu } from "@mantine/core"; -import { useDisclosure } from "@mantine/hooks"; - -import SlotHeaderMobileMenuContent from "../slot-header-mobile-content"; - -export default function HeaderMobileMenu() { - const [opened, { toggle }] = useDisclosure(false); - - return ( - - - - - - - - - ); -} diff --git a/packages/cli/template/nextjs-mantine/src/components/AppShell/internal/HeaderNavLink.tsx b/packages/cli/template/nextjs-mantine/src/components/AppShell/internal/HeaderNavLink.tsx deleted file mode 100644 index 06ce2676..00000000 --- a/packages/cli/template/nextjs-mantine/src/components/AppShell/internal/HeaderNavLink.tsx +++ /dev/null @@ -1,35 +0,0 @@ -"use client"; - -import { type ProofKitRoute } from "@proofkit/cli"; -import { usePathname } from "next/navigation"; -import React from "react"; - -import classes from "./Header.module.css"; - -export default function HeaderNavLink(route: ProofKitRoute) { - const pathname = usePathname(); - - if (route.type === "function") { - return ( - - ); - } - - const isActive = route.exactMatch - ? pathname === route.href - : pathname.startsWith(route.href); - - if (route.type === "link") { - return ( - - {route.label} - - ); - } -} diff --git a/packages/cli/template/nextjs-mantine/src/components/AppShell/internal/config.ts b/packages/cli/template/nextjs-mantine/src/components/AppShell/internal/config.ts deleted file mode 100644 index ded639d0..00000000 --- a/packages/cli/template/nextjs-mantine/src/components/AppShell/internal/config.ts +++ /dev/null @@ -1 +0,0 @@ -export const headerHeight = 56; diff --git a/packages/cli/template/nextjs-mantine/src/components/AppShell/slot-header-center.tsx b/packages/cli/template/nextjs-mantine/src/components/AppShell/slot-header-center.tsx deleted file mode 100644 index 2de3b630..00000000 --- a/packages/cli/template/nextjs-mantine/src/components/AppShell/slot-header-center.tsx +++ /dev/null @@ -1,13 +0,0 @@ -/** - * DO NOT REMOVE / RENAME THIS FILE - * - * You may CUSTOMIZE the content of this file, but the ProofKit CLI expects - * this file to exist and may use it to inject content for other components. - * - * If you don't want it to be used, you may return null or an empty fragment - */ -export function SlotHeaderCenter() { - return null; -} - -export default SlotHeaderCenter; diff --git a/packages/cli/template/nextjs-mantine/src/components/AppShell/slot-header-left.tsx b/packages/cli/template/nextjs-mantine/src/components/AppShell/slot-header-left.tsx deleted file mode 100644 index 781fcbce..00000000 --- a/packages/cli/template/nextjs-mantine/src/components/AppShell/slot-header-left.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import Link from "next/link"; - -import AppLogo from "../AppLogo"; - -/** - * DO NOT REMOVE / RENAME THIS FILE - * - * You may CUSTOMIZE the content of this file, but the ProofKit CLI expects this file to exist and - * may use it to inject content for other components. - * - * If you don't want it to be used, you may return null or an empty fragment - */ -export function SlotHeaderLeft() { - return ( - <> - - - - - ); -} - -export default SlotHeaderLeft; diff --git a/packages/cli/template/nextjs-mantine/src/components/AppShell/slot-header-mobile-content.tsx b/packages/cli/template/nextjs-mantine/src/components/AppShell/slot-header-mobile-content.tsx deleted file mode 100644 index 9943f8a0..00000000 --- a/packages/cli/template/nextjs-mantine/src/components/AppShell/slot-header-mobile-content.tsx +++ /dev/null @@ -1,43 +0,0 @@ -"use client"; - -import { primaryRoutes } from "@/app/navigation"; -import { Menu } from "@mantine/core"; -import { useRouter } from "next/navigation"; - -/** - * DO NOT REMOVE / RENAME THIS FILE - * - * You may CUSTOMIZE the content of this file, but the ProofKit CLI expects - * this file to exist and may use it to inject content for other components. - * - * If you don't want it to be used, you may return null or an empty fragment - */ -export function SlotHeaderMobileMenuContent({ - closeMenu, -}: { - closeMenu: () => void; -}) { - const router = useRouter(); - return ( - <> - {primaryRoutes.map((route) => ( - { - closeMenu(); - if (route.type === "function") { - route.onClick(); - } else if (route.type === "link") { - router.push(route.href); - } - }} - > - {route.label} - - ))} - - ); -} - -export default SlotHeaderMobileMenuContent; diff --git a/packages/cli/template/nextjs-mantine/src/components/AppShell/slot-header-right.tsx b/packages/cli/template/nextjs-mantine/src/components/AppShell/slot-header-right.tsx deleted file mode 100644 index 6c392c95..00000000 --- a/packages/cli/template/nextjs-mantine/src/components/AppShell/slot-header-right.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { primaryRoutes } from "@/app/navigation"; -import { Group } from "@mantine/core"; - -import HeaderNavLink from "./internal/HeaderNavLink"; - -/** - * DO NOT REMOVE / RENAME THIS FILE - * - * You may CUSTOMIZE the content of this file, but the ProofKit CLI expects - * this file to exist and may use it to inject content for other components. - * - * If you don't want it to be used, you may return null or an empty fragment - */ -export function SlotHeaderRight() { - return ( - <> - - {primaryRoutes.map((route) => ( - - ))} - - - ); -} - -export default SlotHeaderRight; diff --git a/packages/cli/template/nextjs-mantine/src/config/env.ts b/packages/cli/template/nextjs-mantine/src/config/env.ts deleted file mode 100644 index 3c50ef8d..00000000 --- a/packages/cli/template/nextjs-mantine/src/config/env.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { createEnv } from "@t3-oss/env-nextjs"; -import { z } from "zod/v4"; - -export const env = createEnv({ - server: { - NODE_ENV: z - .enum(["development", "test", "production"]) - .default("development"), - }, - client: {}, - // For Next.js >= 13.4.4, you only need to destructure client variables: - experimental__runtimeEnv: {}, -}); diff --git a/packages/cli/template/nextjs-mantine/src/config/theme/globals.css b/packages/cli/template/nextjs-mantine/src/config/theme/globals.css deleted file mode 100644 index 0e2f76bb..00000000 --- a/packages/cli/template/nextjs-mantine/src/config/theme/globals.css +++ /dev/null @@ -1,125 +0,0 @@ -/* Add global styles here */ - -@import "tailwindcss" prefix(tw); -@import "tw-animate-css"; - -@custom-variant dark (&:is(.dark *)); - -:root { - --background: oklch(1 0 0); - --foreground: oklch(0.145 0 0); - --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.205 0 0); - --primary-foreground: oklch(0.985 0 0); - --secondary: oklch(0.97 0 0); - --secondary-foreground: oklch(0.205 0 0); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.556 0 0); - --accent: oklch(0.97 0 0); - --accent-foreground: oklch(0.205 0 0); - --destructive: oklch(0.577 0.245 27.325); - --destructive-foreground: oklch(0.577 0.245 27.325); - --border: oklch(0.922 0 0); - --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); - --chart-1: oklch(0.646 0.222 41.116); - --chart-2: oklch(0.6 0.118 184.704); - --chart-3: oklch(0.398 0.07 227.392); - --chart-4: oklch(0.828 0.189 84.429); - --chart-5: oklch(0.769 0.188 70.08); - --radius: 0.625rem; - --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.145 0 0); - --sidebar-primary: oklch(0.205 0 0); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.97 0 0); - --sidebar-accent-foreground: oklch(0.205 0 0); - --sidebar-border: oklch(0.922 0 0); - --sidebar-ring: oklch(0.708 0 0); -} - -.dark { - --background: oklch(0.145 0 0); - --foreground: oklch(0.985 0 0); - --card: oklch(0.145 0 0); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.145 0 0); - --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.985 0 0); - --primary-foreground: oklch(0.205 0 0); - --secondary: oklch(0.269 0 0); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.269 0 0); - --muted-foreground: oklch(0.708 0 0); - --accent: oklch(0.269 0 0); - --accent-foreground: oklch(0.985 0 0); - --destructive: oklch(0.396 0.141 25.723); - --destructive-foreground: oklch(0.637 0.237 25.331); - --border: oklch(0.269 0 0); - --input: oklch(0.269 0 0); - --ring: oklch(0.439 0 0); - --chart-1: oklch(0.488 0.243 264.376); - --chart-2: oklch(0.696 0.17 162.48); - --chart-3: oklch(0.769 0.188 70.08); - --chart-4: oklch(0.627 0.265 303.9); - --chart-5: oklch(0.645 0.246 16.439); - --sidebar: oklch(0.205 0 0); - --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.488 0.243 264.376); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.269 0 0); - --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(0.269 0 0); - --sidebar-ring: oklch(0.439 0 0); -} - -@theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --color-card: var(--card); - --color-card-foreground: var(--card-foreground); - --color-popover: var(--popover); - --color-popover-foreground: var(--popover-foreground); - --color-primary: var(--primary); - --color-primary-foreground: var(--primary-foreground); - --color-secondary: var(--secondary); - --color-secondary-foreground: var(--secondary-foreground); - --color-muted: var(--muted); - --color-muted-foreground: var(--muted-foreground); - --color-accent: var(--accent); - --color-accent-foreground: var(--accent-foreground); - --color-destructive: var(--destructive); - --color-destructive-foreground: var(--destructive-foreground); - --color-border: var(--border); - --color-input: var(--input); - --color-ring: var(--ring); - --color-chart-1: var(--chart-1); - --color-chart-2: var(--chart-2); - --color-chart-3: var(--chart-3); - --color-chart-4: var(--chart-4); - --color-chart-5: var(--chart-5); - --radius-sm: calc(var(--radius) - 4px); - --radius-md: calc(var(--radius) - 2px); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) + 4px); - --color-sidebar: var(--sidebar); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar-primary: var(--sidebar-primary); - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-border: var(--sidebar-border); - --color-sidebar-ring: var(--sidebar-ring); -} - -@layer base { - * { - @apply tw:border-border tw:outline-ring/50; - } - body { - @apply tw:bg-background tw:text-foreground; - } -} diff --git a/packages/cli/template/nextjs-mantine/src/config/theme/mantine-theme.ts b/packages/cli/template/nextjs-mantine/src/config/theme/mantine-theme.ts deleted file mode 100644 index 890db89c..00000000 --- a/packages/cli/template/nextjs-mantine/src/config/theme/mantine-theme.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { createTheme, type MantineColorsTuple } from "@mantine/core"; - -// generate your own set of colors here: https://mantine.dev/colors-generator -const brandColor: MantineColorsTuple = [ - "#ffebff", - "#f5d5fb", - "#e6a8f3", - "#d779eb", - "#cb51e4", - "#c337e0", - "#c029df", - "#a91cc6", - "#9715b1", - "#84099c", -]; - -export const theme = createTheme({ - primaryColor: "brand", - colors: { - brand: brandColor, - }, -}); diff --git a/packages/cli/template/nextjs-mantine/src/server/safe-action.ts b/packages/cli/template/nextjs-mantine/src/server/safe-action.ts deleted file mode 100644 index 7f62198a..00000000 --- a/packages/cli/template/nextjs-mantine/src/server/safe-action.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { createSafeActionClient } from "next-safe-action"; - -export const actionClient = createSafeActionClient(); diff --git a/packages/cli/template/nextjs-mantine/src/utils/notification-helpers.ts b/packages/cli/template/nextjs-mantine/src/utils/notification-helpers.ts deleted file mode 100644 index b5aa63e3..00000000 --- a/packages/cli/template/nextjs-mantine/src/utils/notification-helpers.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { - showNotification, - type NotificationData, -} from "@mantine/notifications"; - -export function showErrorNotification(): void; -export function showErrorNotification(props: NotificationData): void; -export function showErrorNotification(message: string): void; -export function showErrorNotification(args?: string | NotificationData): void { - const message = - typeof args === "string" ? args : "An unexpected error occurred."; - const defaultProps = typeof args === "string" ? {} : (args ?? {}); - - showNotification({ color: "red", title: "Error", message, ...defaultProps }); -} - -export function showSuccessNotification(): void; -export function showSuccessNotification(props: NotificationData): void; -export function showSuccessNotification(message: string): void; -export function showSuccessNotification( - args?: string | NotificationData, -): void { - const message = typeof args === "string" ? args : "Success!"; - const defaultProps = typeof args === "string" ? {} : (args ?? {}); - - showNotification({ - color: "green", - title: "Success", - message, - ...defaultProps, - }); -} diff --git a/packages/cli/template/nextjs-mantine/src/utils/styles.ts b/packages/cli/template/nextjs-mantine/src/utils/styles.ts deleted file mode 100644 index 1f4cd38e..00000000 --- a/packages/cli/template/nextjs-mantine/src/utils/styles.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { clsx } from "clsx"; -import { twMerge } from "tailwind-merge"; - -export function cn(...inputs: any[]) { - return twMerge(clsx(inputs)); -} diff --git a/packages/cli/template/nextjs-mantine/tsconfig.json b/packages/cli/template/nextjs-mantine/tsconfig.json deleted file mode 100644 index 51d0dbce..00000000 --- a/packages/cli/template/nextjs-mantine/tsconfig.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "compilerOptions": { - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "preserve", - "incremental": true, - "plugins": [ - { - "name": "next" - } - ], - "paths": { - "@/*": ["./src/*"] - }, - "target": "ES2017" - }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] -} diff --git a/packages/cli/template/nextjs-shadcn/CLAUDE.md b/packages/cli/template/nextjs-shadcn/CLAUDE.md deleted file mode 120000 index 47dc3e3d..00000000 --- a/packages/cli/template/nextjs-shadcn/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -AGENTS.md \ No newline at end of file diff --git a/packages/cli/template/nextjs-shadcn/CLAUDE.md b/packages/cli/template/nextjs-shadcn/CLAUDE.md new file mode 100644 index 00000000..43c994c2 --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/packages/cli/template/vite-wv/CLAUDE.md b/packages/cli/template/vite-wv/CLAUDE.md deleted file mode 120000 index 47dc3e3d..00000000 --- a/packages/cli/template/vite-wv/CLAUDE.md +++ /dev/null @@ -1 +0,0 @@ -AGENTS.md \ No newline at end of file diff --git a/packages/cli/template/vite-wv/CLAUDE.md b/packages/cli/template/vite-wv/CLAUDE.md new file mode 100644 index 00000000..43c994c2 --- /dev/null +++ b/packages/cli/template/vite-wv/CLAUDE.md @@ -0,0 +1 @@ +@AGENTS.md diff --git a/packages/cli/tests/cli.test.ts b/packages/cli/tests/cli.test.ts index 93302bf6..c107b38d 100644 --- a/packages/cli/tests/cli.test.ts +++ b/packages/cli/tests/cli.test.ts @@ -21,6 +21,7 @@ describe("proofkit CLI", () => { expect(output).toContain("--non-interactive"); expect(output).toContain("--no-install"); expect(output).toContain("--no-git"); + expect(output).not.toContain("--ui"); expect(output).not.toContain("--appType"); }); @@ -85,6 +86,7 @@ describe("proofkit CLI", () => { expect(result.stdout).toContain("ProofKit"); expect(result.stdout).toContain("Create a new project with ProofKit"); expect(result.stdout).toContain("--app-type"); + expect(result.stdout).not.toContain("--ui"); }); it("shows a clean invalid subcommand error by default", () => { diff --git a/packages/cli/tests/init-fixtures.ts b/packages/cli/tests/init-fixtures.ts index 63551e8e..71e04627 100644 --- a/packages/cli/tests/init-fixtures.ts +++ b/packages/cli/tests/init-fixtures.ts @@ -27,7 +27,7 @@ export function makeInitRequest(overrides: Partial = {}): InitReque }; } -export function getSharedTemplateDir(templateName: "nextjs-shadcn" | "nextjs-mantine" | "vite-wv") { +export function getSharedTemplateDir(templateName: "nextjs-shadcn" | "vite-wv") { return path.resolve(__dirname, `../../cli/template/${templateName}`); } @@ -39,6 +39,7 @@ export async function readScaffoldArtifacts(projectDir: string) { const typegenConfig = (await fs.pathExists(typegenPath)) ? await fs.readFile(typegenPath, "utf8") : undefined; const agentsPath = path.join(projectDir, "AGENTS.md"); const claudePath = path.join(projectDir, "CLAUDE.md"); + const cursorIgnorePath = path.join(projectDir, ".cursorignore"); const launchPath = path.join(projectDir, ".claude", "launch.json"); return { @@ -48,6 +49,7 @@ export async function readScaffoldArtifacts(projectDir: string) { typegenConfig, agentsFile: (await fs.pathExists(agentsPath)) ? await fs.readFile(agentsPath, "utf8") : undefined, claudeFile: (await fs.pathExists(claudePath)) ? await fs.readFile(claudePath, "utf8") : undefined, + cursorIgnoreFile: (await fs.pathExists(cursorIgnorePath)) ? await fs.readFile(cursorIgnorePath, "utf8") : undefined, launchConfig: (await fs.pathExists(launchPath)) ? await fs.readFile(launchPath, "utf8") : undefined, }; } diff --git a/packages/cli/tests/init-non-interactive-failures.test.ts b/packages/cli/tests/init-non-interactive-failures.test.ts index abbe0758..61dec826 100644 --- a/packages/cli/tests/init-non-interactive-failures.test.ts +++ b/packages/cli/tests/init-non-interactive-failures.test.ts @@ -68,21 +68,12 @@ describe("Init Non-Interactive Failure Paths", () => { expect(readdirSync(testDir).sort()).toEqual(["sentinel.txt"]); }); - it("fails fast for invalid non-interactive app names and does not create a project directory", () => { + it("normalizes spaces in non-interactive app names and creates the project directory", () => { const projectName = "Bad Name"; - const result = runInitExpectFailure([ - projectName, - "--non-interactive", - "--app-type", - "webviewer", - "--no-install", - "--no-git", - ]); - const output = `${result.stdout}\n${result.stderr}`; + runInitExpectSuccess([projectName, "--non-interactive", "--app-type", "webviewer", "--no-install", "--no-git"]); - expect(result.status).toBe(1); - expect(output).toContain("Name must consist of only lowercase alphanumeric characters, '-', and '_'"); + expect(existsSync(join(testDir, "bad-name"))).toBe(true); expect(existsSync(join(testDir, projectName))).toBe(false); }); diff --git a/packages/cli/tests/init-run-init-regression.test.ts b/packages/cli/tests/init-run-init-regression.test.ts index 0313d427..d8f8400e 100644 --- a/packages/cli/tests/init-run-init-regression.test.ts +++ b/packages/cli/tests/init-run-init-regression.test.ts @@ -23,7 +23,7 @@ const { execaMock: vi.fn(), mockState: { appType: undefined as "browser" | "webviewer" | undefined, - ui: "shadcn" as "shadcn" | "mantine", + ui: "shadcn" as const, projectDir: "/tmp/proofkit-regression", }, })); diff --git a/packages/cli/tests/init-scaffold-contract.test.ts b/packages/cli/tests/init-scaffold-contract.test.ts index 488591fa..e70471f5 100644 --- a/packages/cli/tests/init-scaffold-contract.test.ts +++ b/packages/cli/tests/init-scaffold-contract.test.ts @@ -135,6 +135,7 @@ describe("Init scaffold contract tests", () => { expect(existsSync(join(browserProjectDir, "package.json"))).toBe(true); expect(existsSync(join(browserProjectDir, "proofkit.json"))).toBe(true); expect(existsSync(join(browserProjectDir, ".env"))).toBe(true); + expect(existsSync(join(browserProjectDir, ".cursorignore"))).toBe(true); expect(existsSync(join(browserProjectDir, "src", "lib", "env.ts"))).toBe(true); expect(existsSync(join(browserProjectDir, "src", "app", "layout.tsx"))).toBe(true); expect(existsSync(join(browserProjectDir, "postcss.config.mjs"))).toBe(true); @@ -147,6 +148,8 @@ describe("Init scaffold contract tests", () => { expect(packageJson.proofkitMetadata?.initVersion).toBe(cliVersion); expect(packageJson.packageManager).toMatch(packageManagerPattern); expect(allProofkitDependenciesUseCurrentReleaseTag(packageJson)).toBe(true); + expect(readFileSync(join(browserProjectDir, "CLAUDE.md"), "utf-8")).toBe("@AGENTS.md\n"); + expect(readFileSync(join(browserProjectDir, ".cursorignore"), "utf-8")).toBe("CLAUDE.md\n"); const pkgManager = getPackageManagerName(packageJson); expect(outputSuggestsCommand(normalizedOutput, formatRunCommand(pkgManager, "typegen"))).toBe(false); @@ -172,6 +175,7 @@ describe("Init scaffold contract tests", () => { expect(existsSync(join(webviewerProjectDir, "proofkit.json"))).toBe(true); expect(existsSync(join(webviewerProjectDir, "proofkit-typegen.config.jsonc"))).toBe(true); expect(existsSync(join(webviewerProjectDir, ".env"))).toBe(true); + expect(existsSync(join(webviewerProjectDir, ".cursorignore"))).toBe(true); expect(existsSync(join(webviewerProjectDir, "src", "main.tsx"))).toBe(true); expect(existsSync(join(webviewerProjectDir, "scripts", "launch-fm.js"))).toBe(true); expect(existsSync(join(webviewerProjectDir, "scripts", "upload.js"))).toBe(true); @@ -185,6 +189,8 @@ describe("Init scaffold contract tests", () => { expect(packageJson.proofkitMetadata?.initVersion).toBe(cliVersion); expect(packageJson.packageManager).toMatch(packageManagerPattern); expect(allProofkitDependenciesUseCurrentReleaseTag(packageJson)).toBe(true); + expect(readFileSync(join(webviewerProjectDir, "CLAUDE.md"), "utf-8")).toBe("@AGENTS.md\n"); + expect(readFileSync(join(webviewerProjectDir, ".cursorignore"), "utf-8")).toBe("CLAUDE.md\n"); const pkgManager = getPackageManagerName(packageJson); expect(outputSuggestsCommand(normalizedOutput, formatRunCommand(pkgManager, "typegen"))).toBe(true); expect(outputSuggestsCommand(normalizedOutput, formatRunCommand(pkgManager, "launch-fm"))).toBe(true); diff --git a/packages/cli/tests/integration.test.ts b/packages/cli/tests/integration.test.ts index 775461a1..7b8a4980 100644 --- a/packages/cli/tests/integration.test.ts +++ b/packages/cli/tests/integration.test.ts @@ -58,7 +58,8 @@ describe("integration scaffold generation", () => { expect(await fs.pathExists(path.join(projectDir, "proofkit.json"))).toBe(true); expect(await fs.pathExists(path.join(projectDir, ".env"))).toBe(true); - const { packageJson, proofkitJson, envFile } = await readScaffoldArtifacts(projectDir); + const { packageJson, proofkitJson, envFile, claudeFile, cursorIgnoreFile } = + await readScaffoldArtifacts(projectDir); expect(packageJson.name).toBe("browser-app"); expect(packageJson.packageManager).toBe("pnpm@10.27.0"); @@ -73,6 +74,8 @@ describe("integration scaffold generation", () => { dataSources: [], envFile: ".env", }); + expect(claudeFile).toBe("@AGENTS.md\n"); + expect(cursorIgnoreFile).toBe("CLAUDE.md\n"); expect(envFile).toContain("# When adding additional environment variables"); expect(consoleTranscript.success.at(-1) ?? "").toContain("Created browser-app"); }); @@ -185,7 +188,8 @@ describe("integration scaffold generation", () => { await Effect.runPromise(layer(executeInitPlan(plan))); - const { packageJson, agentsFile, claudeFile, launchConfig } = await readScaffoldArtifacts(projectDir); + const { packageJson, agentsFile, claudeFile, cursorIgnoreFile, launchConfig } = + await readScaffoldArtifacts(projectDir); const routerFile = await fs.readFile(path.join(projectDir, "src/router.tsx"), "utf8"); const mainFile = await fs.readFile(path.join(projectDir, "src/main.tsx"), "utf8"); const queryDemoFile = await fs.readFile(path.join(projectDir, "src/routes/query-demo.tsx"), "utf8"); @@ -197,7 +201,8 @@ describe("integration scaffold generation", () => { expect(packageJson.devDependencies.ultracite).toBe("7.0.8"); expect(agentsFile).toContain("Use the ProofKit docs as the primary reference"); expect(agentsFile).toContain("npx @tanstack/intent@latest install"); - expect(claudeFile).toBe(agentsFile); + expect(claudeFile).toBe("@AGENTS.md\n"); + expect(cursorIgnoreFile).toBe("CLAUDE.md\n"); expect(launchConfig).toContain('"runtimeExecutable": "pnpm"'); expect(routerFile).toContain("createHashHistory"); expect(mainFile).toContain("QueryClientProvider"); diff --git a/packages/cli/tests/legacy-project-name-utils.test.ts b/packages/cli/tests/legacy-project-name-utils.test.ts new file mode 100644 index 00000000..07ebbca8 --- /dev/null +++ b/packages/cli/tests/legacy-project-name-utils.test.ts @@ -0,0 +1,19 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { parseNameAndPath } from "~/utils/parseNameAndPath.js"; +import { validateAppName } from "~/utils/validateAppName.js"; + +describe("legacy project name utils", () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("normalizes the current directory name when parsing '.'", () => { + vi.spyOn(process, "cwd").mockReturnValue("/tmp/My App"); + expect(parseNameAndPath(".")).toEqual(["my-app", "."]); + }); + + it("validates the normalized current directory name for '.'", () => { + vi.spyOn(process, "cwd").mockReturnValue("/tmp/My App"); + expect(validateAppName(".")).toBeUndefined(); + }); +}); diff --git a/packages/cli/tests/project-name.test.ts b/packages/cli/tests/project-name.test.ts index 02dedf13..4ee1ff63 100644 --- a/packages/cli/tests/project-name.test.ts +++ b/packages/cli/tests/project-name.test.ts @@ -11,13 +11,23 @@ describe("projectName utils", () => { expect(parseNameAndPath(".\\my-app\\")).toEqual(["my-app", "./my-app"]); }); + it("converts spaces to dashes when parsing the app name and directory", () => { + expect(parseNameAndPath("my app")).toEqual(["my-app", "my-app"]); + expect(validateAppName("my app")).toBeUndefined(); + }); + it("validates the actual current directory name when projectName is '.'", () => { vi.spyOn(process, "cwd").mockReturnValue("/tmp/My App"); - expect(validateAppName(".")).toBe("Name must consist of only lowercase alphanumeric characters, '-', and '_'"); + expect(validateAppName(".")).toBeUndefined(); }); it("accepts '.' when the current directory name is valid", () => { vi.spyOn(process, "cwd").mockReturnValue("/tmp/my-app"); expect(validateAppName(".")).toBeUndefined(); }); + + it("normalizes the current directory name when parsing '.'", () => { + vi.spyOn(process, "cwd").mockReturnValue("/tmp/My App"); + expect(parseNameAndPath(".")).toEqual(["my-app", "."]); + }); }); diff --git a/packages/cli/tests/test-layer.ts b/packages/cli/tests/test-layer.ts index c6d1955f..0624dcb9 100644 --- a/packages/cli/tests/test-layer.ts +++ b/packages/cli/tests/test-layer.ts @@ -324,12 +324,10 @@ export function makeTestLayer(options: { }), }), Layer.succeed(TemplateService, { - getTemplateDir: (appType: AppType, ui: UIType) => { + getTemplateDir: (appType: AppType, _ui: UIType) => { let templateName = "nextjs-shadcn"; if (appType === "webviewer") { templateName = "vite-wv"; - } else if (ui === "mantine") { - templateName = "nextjs-mantine"; } return path.resolve(__dirname, `../../cli/template/${templateName}`); },