diff --git a/.changeset/clean-suns-matter.md b/.changeset/clean-suns-matter.md new file mode 100644 index 00000000..bfbf799c --- /dev/null +++ b/.changeset/clean-suns-matter.md @@ -0,0 +1,4 @@ +"@proofkit/typegen": patch +--- + +Make `@proofkit/fmdapi` and `@proofkit/fmodata` optional peers for `@proofkit/typegen`, and lazy-load each path so fmdapi-only and fmodata-only installs do not hard-require the other package. 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/fair-lamps-taste.md b/.changeset/fair-lamps-taste.md new file mode 100644 index 00000000..b370b3e1 --- /dev/null +++ b/.changeset/fair-lamps-taste.md @@ -0,0 +1,4 @@ +"@proofkit/typegen": patch +--- + +Widen OData client error typing to include message and details payloads from env/config validation. 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/fix-cli-parse-name-parent-paths.md b/.changeset/fix-cli-parse-name-parent-paths.md new file mode 100644 index 00000000..d5a1c543 --- /dev/null +++ b/.changeset/fix-cli-parse-name-parent-paths.md @@ -0,0 +1,5 @@ +--- +"@proofkit/cli": patch +--- + +Normalize only the final path segment in `parseNameAndPath`, preserving leading directory segments verbatim while keeping scoped-name parsing and `.` handling intact 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..0e6fae65 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. * @@ -19,24 +21,26 @@ import { removeTrailingSlash } from "./removeTrailingSlash.js"; */ export const parseNameAndPath = (rawInput: string) => { const input = removeTrailingSlash(rawInput); - const paths = input.split("/"); + const normalizedPaths = [...paths]; + const lastPathIndex = normalizedPaths.length - 1; - let appName = paths.at(-1) ?? ""; + let appName = (normalizedPaths.at(-1) ?? "").replace(whitespaceRegex, "-").toLowerCase(); + normalizedPaths[lastPathIndex] = appName; // 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) { - appName = paths.slice(indexOfDelimiter).join("/"); + const indexOfDelimiter = normalizedPaths.findIndex((p) => p.startsWith("@")); + if (indexOfDelimiter !== -1) { + appName = normalizedPaths.slice(indexOfDelimiter).join("/"); } - const path = paths.filter((p) => !p.startsWith("@")).join("/"); + const path = normalizedPaths.filter((p) => !p.startsWith("@")).join("/"); return [appName, path] as const; }; diff --git a/packages/cli/src/utils/projectName.ts b/packages/cli/src/utils/projectName.ts index d5cb953a..2c4ee329 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,30 +13,36 @@ 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 segments = normalized.split("/"); - let scopedAppName = segments.at(-1) ?? ""; + const normalizedProjectName = trimTrailingSlashes(projectName); + const segments = normalizedProjectName.split("/"); + const hasScopedPackage = (segments.at(-2) ?? "").startsWith("@"); + const packageSegmentCount = hasScopedPackage ? 2 : 1; + const leadingSegments = segments.slice(0, -packageSegmentCount); + const packageSegments = segments.slice(-packageSegmentCount); + const normalizedPackageSegments = packageSegments.map(normalizeProjectNameForPackage); + let scopedAppName = normalizedPackageSegments.join("/"); + let appDirPackageSegments = normalizedPackageSegments; if (scopedAppName === ".") { - scopedAppName = path.basename(path.resolve(process.cwd())); - } - - const scopeIndex = segments.findIndex((segment) => segment.startsWith("@")); - if (scopeIndex !== -1) { - scopedAppName = segments.slice(scopeIndex).join("/"); + scopedAppName = normalizeProjectNameForPackage(path.basename(path.resolve(process.cwd()))); + appDirPackageSegments = packageSegments; } - const appDir = segments.filter((segment) => !segment.startsWith("@")).join("/"); + const appDir = [...leadingSegments, ...appDirPackageSegments].join("/"); return [scopedAppName, appDir]; } 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..ed10cc3b 100644 --- a/packages/cli/tests/project-name.test.ts +++ b/packages/cli/tests/project-name.test.ts @@ -11,13 +11,34 @@ 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("preserves leading directory casing while normalizing only the package segment", () => { + expect(parseNameAndPath("Apps Folder/My App")).toEqual(["my-app", "Apps Folder/my-app"]); + }); + + it("normalizes scoped package segments without lowercasing leading directories", () => { + expect(parseNameAndPath("Apps Folder/@My Scope/My App")).toEqual([ + "@my-scope/my-app", + "Apps Folder/@my-scope/my-app", + ]); + }); + 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}`); }, diff --git a/packages/typegen/package.json b/packages/typegen/package.json index cc21d8a4..db67804b 100644 --- a/packages/typegen/package.json +++ b/packages/typegen/package.json @@ -75,8 +75,6 @@ "@commander-js/extra-typings": "^14.0.0", "@hono/node-server": "^1.19.8", "@hono/zod-validator": "^0.7.6", - "@proofkit/fmdapi": "workspace:*", - "@proofkit/fmodata": "workspace:*", "@tanstack/vite-config": "^0.2.1", "chalk": "5.4.1", "commander": "^14.0.2", @@ -94,7 +92,21 @@ "vite": "^6.4.1", "zod": "^4.3.5" }, + "peerDependencies": { + "@proofkit/fmdapi": "*", + "@proofkit/fmodata": "*" + }, + "peerDependenciesMeta": { + "@proofkit/fmdapi": { + "optional": true + }, + "@proofkit/fmodata": { + "optional": true + } + }, "devDependencies": { + "@proofkit/fmdapi": "workspace:*", + "@proofkit/fmodata": "workspace:*", "@tanstack/intent": "^0.0.19", "@types/fs-extra": "^11.0.4", "@types/semver": "^7.7.1", diff --git a/packages/typegen/src/cli.ts b/packages/typegen/src/cli.ts index 78788e7a..c7f6bb51 100644 --- a/packages/typegen/src/cli.ts +++ b/packages/typegen/src/cli.ts @@ -7,8 +7,6 @@ import chalk from "chalk"; import { config } from "dotenv"; import fs from "fs-extra"; import { parse } from "jsonc-parser"; -import { startServer } from "./server"; -import { generateTypedClients } from "./typegen"; import { typegenConfig } from "./types"; const defaultConfigPaths = ["proofkit-typegen.config.jsonc", "proofkit-typegen.config.json"]; @@ -87,6 +85,7 @@ async function runCodegen({ configLocation, resetOverrides = false }: ConfigArgs return process.exit(1); } + const { generateTypedClients } = await import("./typegen"); const result = await generateTypedClients(configParsed.data.config, { resetOverrides, postGenerateCommand: configParsed.data.postGenerateCommand, @@ -167,6 +166,7 @@ program } try { + const { startServer } = await import("./server"); const server = await startServer({ port, cwd: process.cwd(), diff --git a/packages/typegen/src/getLayoutMetadata.ts b/packages/typegen/src/getLayoutMetadata.ts index 4dc0d31e..26677226 100644 --- a/packages/typegen/src/getLayoutMetadata.ts +++ b/packages/typegen/src/getLayoutMetadata.ts @@ -1,9 +1,17 @@ -import type { DataApi } from "@proofkit/fmdapi"; -import { type clientTypes, FileMakerError } from "@proofkit/fmdapi"; +import type { clientTypes, DataApi } from "@proofkit/fmdapi"; import chalk from "chalk"; import type { F } from "ts-toolbelt"; import type { TSchema, ValueListsOptions } from "./types"; +function getErrorCode(error: unknown): string | undefined { + if (!error || typeof error !== "object" || !("code" in error)) { + return undefined; + } + + const { code } = error as { code?: unknown }; + return typeof code === "string" ? code : undefined; +} + /** * Calls the FileMaker Data API to get the layout metadata and returns a schema */ @@ -35,7 +43,7 @@ export const getLayoutMetadata = async (args: { const { client, valueLists = "ignore" } = args; const meta = await client.layoutMetadata().catch((err) => { - if (err instanceof FileMakerError && err.code === "105") { + if (getErrorCode(err) === "105") { console.log( chalk.bold.red("ERROR:"), "Skipping typegen for layout:", diff --git a/packages/typegen/src/optionalDeps.ts b/packages/typegen/src/optionalDeps.ts new file mode 100644 index 00000000..d8d9117f --- /dev/null +++ b/packages/typegen/src/optionalDeps.ts @@ -0,0 +1,26 @@ +export function isMissingDependencyError(error: unknown, packageName: string): boolean { + if (!error || typeof error !== "object") { + return false; + } + + const candidate = error as { code?: string; message?: string }; + const message = candidate.message ?? ""; + + if (candidate.code !== "ERR_MODULE_NOT_FOUND" && candidate.code !== "MODULE_NOT_FOUND") { + return false; + } + + return message.includes(packageName); +} + +export function createMissingDependencyError(packageName: string, feature: string): Error { + return new Error(`Missing optional dependency ${packageName}. Install it to use ${feature}.`); +} + +export function rethrowMissingDependency(error: unknown, packageName: string, feature: string): never { + if (isMissingDependencyError(error, packageName)) { + throw createMissingDependencyError(packageName, feature); + } + + throw error; +} diff --git a/packages/typegen/src/server/app.ts b/packages/typegen/src/server/app.ts index 3a51450b..77e27994 100644 --- a/packages/typegen/src/server/app.ts +++ b/packages/typegen/src/server/app.ts @@ -1,12 +1,11 @@ import path from "node:path"; import { zValidator } from "@hono/zod-validator"; -import { type clientTypes, FileMakerError } from "@proofkit/fmdapi"; +import type { clientTypes } from "@proofkit/fmdapi"; import fs from "fs-extra"; import { Hono } from "hono"; import type { ContentfulStatusCode } from "hono/utils/http-status"; import { parse } from "jsonc-parser"; import z from "zod/v4"; -import { downloadTableMetadata, parseMetadata } from "../fmodata"; import { generateTypedClients } from "../typegen"; import { typegenConfig, typegenConfigSingle, typegenConfigSingleForValidation } from "../types"; import { createClientFromConfig, createDataApiClient, createOdataClientFromConfig } from "./createDataApiClient"; @@ -46,6 +45,15 @@ function flattenLayouts( return result; } +function getErrorCode(error: unknown): string | undefined { + if (!error || typeof error !== "object" || !("code" in error)) { + return undefined; + } + + const { code } = error as { code?: unknown }; + return typeof code === "string" ? code : undefined; +} + export function createApiApp(context: ApiContext) { // Request-validation schema: add default `type` for backwards compatibility. // Important: we keep `typegenConfigSingleForValidation` as a pure discriminated union @@ -272,7 +280,7 @@ export function createApiApp(context: ApiContext) { const input = c.req.valid("query"); const configIndex = input.configIndex; - const result = createDataApiClient(context, configIndex); + const result = await createDataApiClient(context, configIndex); // Check if result is an error if ("error" in result) { @@ -309,7 +317,7 @@ export function createApiApp(context: ApiContext) { // Call layouts method - using type assertion as TypeScript has inference issues with DataApi return type // The layouts method exists but TypeScript can't infer it from the complex return type try { - if (!("layouts" in client)) { + if (typeof client.layouts !== "function") { return c.json({ error: "Layouts method not found" }, 500); } const layoutsResp = (await client.layouts()) as { @@ -328,17 +336,18 @@ export function createApiApp(context: ApiContext) { let suspectedField: "server" | "db" | "auth" | undefined; let fmErrorCode: string | undefined; - if (err instanceof FileMakerError) { - errorMessage = err.message; - fmErrorCode = err.code; + const nextFmErrorCode = getErrorCode(err); + if (nextFmErrorCode) { + errorMessage = err instanceof Error ? err.message : errorMessage; + fmErrorCode = nextFmErrorCode; // Infer suspected field from error code - if (err.code === "105") { + if (nextFmErrorCode === "105") { suspectedField = "db"; - errorMessage = `Database not found: ${err.message}`; - } else if (err.code === "212" || err.code === "952") { + errorMessage = `Database not found: ${err instanceof Error ? err.message : errorMessage}`; + } else if (nextFmErrorCode === "212" || nextFmErrorCode === "952") { suspectedField = "auth"; - errorMessage = `Authentication failed: ${err.message}`; + errorMessage = `Authentication failed: ${err instanceof Error ? err.message : errorMessage}`; } statusCode = 400; } else if (err instanceof TypeError) { @@ -398,6 +407,7 @@ export function createApiApp(context: ApiContext) { } const tableConfig = config.tables.find((t) => t.tableName === tableName); try { + const { downloadTableMetadata, parseMetadata } = await import("../fmodata"); // Download metadata for the specified table const tableMetadataXml = await downloadTableMetadata({ config, @@ -446,7 +456,7 @@ export function createApiApp(context: ApiContext) { return c.json({ error: "Invalid config type" }, 400); } try { - const result = createOdataClientFromConfig(config); + const result = await createOdataClientFromConfig(config); if ("error" in result) { return c.json( { @@ -487,7 +497,7 @@ export function createApiApp(context: ApiContext) { // Validate config type if (config.type === "fmdapi") { // Create client from config - const clientResult = createClientFromConfig(config); + const clientResult = await createClientFromConfig(config); // Check if client creation failed if ("error" in clientResult) { @@ -504,6 +514,18 @@ export function createApiApp(context: ApiContext) { // Test connection by calling layouts() try { + if (typeof client.layouts !== "function") { + return c.json( + { + ok: false, + error: "Layouts method not found", + statusCode: 500, + kind: "adapter_error", + message: "Layouts method not found", + }, + 500, + ); + } await client.layouts(); return c.json({ @@ -520,9 +542,10 @@ export function createApiApp(context: ApiContext) { let suspectedField: "server" | "db" | "auth" | undefined; let fmErrorCode: string | undefined; - if (err instanceof FileMakerError) { - errorMessage = err.message; - fmErrorCode = err.code; + const nextFmErrorCode = getErrorCode(err); + if (nextFmErrorCode) { + errorMessage = err instanceof Error ? err.message : errorMessage; + fmErrorCode = nextFmErrorCode; kind = "connection_error"; // Infer suspected field from error code @@ -530,12 +553,12 @@ export function createApiApp(context: ApiContext) { // 105 = Database not found // 212 = Authentication failed // 802 = Record not found (less relevant here) - if (err.code === "105") { + if (nextFmErrorCode === "105") { suspectedField = "db"; - errorMessage = `Database not found: ${err.message}`; - } else if (err.code === "212" || err.code === "952") { + errorMessage = `Database not found: ${err instanceof Error ? err.message : errorMessage}`; + } else if (nextFmErrorCode === "212" || nextFmErrorCode === "952") { suspectedField = "auth"; - errorMessage = `Authentication failed: ${err.message}`; + errorMessage = `Authentication failed: ${err instanceof Error ? err.message : errorMessage}`; } statusCode = 400; } else if (err instanceof TypeError) { @@ -564,7 +587,7 @@ export function createApiApp(context: ApiContext) { ); } } else if (config.type === "fmodata") { - const result = createOdataClientFromConfig(config); + const result = await createOdataClientFromConfig(config); if ("error" in result) { return c.json( { diff --git a/packages/typegen/src/server/createDataApiClient.ts b/packages/typegen/src/server/createDataApiClient.ts index 985f6529..5b863090 100644 --- a/packages/typegen/src/server/createDataApiClient.ts +++ b/packages/typegen/src/server/createDataApiClient.ts @@ -1,20 +1,26 @@ import path from "node:path"; -import DataApi from "@proofkit/fmdapi"; -import { FetchAdapter } from "@proofkit/fmdapi/adapters/fetch"; -import { FmMcpAdapter } from "@proofkit/fmdapi/adapters/fm-mcp"; -import { OttoAdapter, type OttoAPIKey } from "@proofkit/fmdapi/adapters/otto"; -import { memoryStore } from "@proofkit/fmdapi/tokenStore/memory"; -import { type Database, FMServerConnection } from "@proofkit/fmodata"; +import type { OttoAPIKey } from "@proofkit/fmdapi/adapters/otto"; import fs from "fs-extra"; import { parse } from "jsonc-parser"; import type { z } from "zod/v4"; import { defaultEnvNames, defaultFmMcpBaseUrl } from "../constants"; +import { rethrowMissingDependency } from "../optionalDeps"; import { typegenConfig, type typegenConfigSingle } from "../types"; import type { ApiContext } from "./app"; +interface LocalLayoutOrFolder { + name: string; + isFolder?: boolean; + folderLayoutNames?: LocalLayoutOrFolder[]; + table?: string; +} + export interface CreateClientResult { - // biome-ignore lint/suspicious/noExplicitAny: DataApi is a generic type - client: ReturnType>; + client: { + layouts?: () => Promise<{ + layouts: LocalLayoutOrFolder[]; + }>; + }; config: Extract, { type: "fmdapi" }>; server: string; db: string; @@ -46,17 +52,34 @@ type EnvVarsResult = auth: { apiKey: OttoAPIKey } | { username: string; password: string }; }; +export interface OdataClientResult { + db: { + listTableNames: () => Promise; + }; + connection: { + listDatabaseNames: () => Promise; + }; + server: string; + dbName: string; + authType: "apiKey" | "username"; +} + +export interface OdataClientError { + error: string; + statusCode: number; + details?: Record; + kind?: "missing_env" | "adapter_error" | "connection_error" | "unknown"; + suspectedField?: "server" | "db" | "auth"; + message?: string; +} + function getEnvVarsFromConfig(envNames: SingleConfig["envNames"]): EnvVarsResult { - // Helper to get env name, treating empty strings as undefined const getEnvName = (customName: string | undefined, defaultName: string) => customName && customName.trim() !== "" ? customName : defaultName; - // Resolve environment variables const server = process.env[getEnvName(envNames?.server, defaultEnvNames.server)]; const db = process.env[getEnvName(envNames?.db, defaultEnvNames.db)]; - // Always attempt to read all auth methods from environment variables, - // regardless of which type is specified in envNames.auth const apiKeyEnvName = envNames?.auth && "apiKey" in envNames.auth ? getEnvName(envNames.auth.apiKey, defaultEnvNames.apiKey) @@ -74,16 +97,7 @@ function getEnvVarsFromConfig(envNames: SingleConfig["envNames"]): EnvVarsResult const username = process.env[usernameEnvName]; const password = process.env[passwordEnvName]; - // Validate required env vars if (!(server && db && (apiKey || username))) { - console.error("Missing required environment variables", { - server, - db, - apiKey, - username, - }); - - // Build missing details object const missingDetails: { server?: boolean; db?: boolean; @@ -95,10 +109,6 @@ function getEnvVarsFromConfig(envNames: SingleConfig["envNames"]): EnvVarsResult auth: !(apiKey || username), }; - // Only report password as missing if server and db are both present, - // and username is set but password is missing. This ensures we don't - // incorrectly report password as missing when the actual error is about - // missing server or database. if (server && db && username && !password) { missingDetails.password = true; } @@ -106,7 +116,7 @@ function getEnvVarsFromConfig(envNames: SingleConfig["envNames"]): EnvVarsResult return { error: "Missing required environment variables", statusCode: 400, - kind: "missing_env" as const, + kind: "missing_env", details: { missing: missingDetails, }, @@ -121,7 +131,7 @@ function getEnvVarsFromConfig(envNames: SingleConfig["envNames"]): EnvVarsResult return "auth"; } return undefined; - })() as "server" | "db" | "auth" | undefined, + })(), message: (() => { if (!server) { return "Server URL environment variable is missing"; @@ -134,18 +144,17 @@ function getEnvVarsFromConfig(envNames: SingleConfig["envNames"]): EnvVarsResult }; } - // Validate password if username is provided if (username && !password) { return { error: "Password is required when using username authentication", statusCode: 400, - kind: "missing_env" as const, + kind: "missing_env", details: { missing: { password: true, }, }, - suspectedField: "auth" as const, + suspectedField: "auth", message: "Password environment variable is missing", }; } @@ -153,50 +162,98 @@ function getEnvVarsFromConfig(envNames: SingleConfig["envNames"]): EnvVarsResult return { server, db, - authType: (apiKey ? "apiKey" : "username") as "apiKey" | "username", + authType: apiKey ? "apiKey" : "username", auth: apiKey ? { apiKey: apiKey as OttoAPIKey } : { username: username ?? "", password: password ?? "" }, }; } -export interface OdataClientResult { - db: Database; - connection: FMServerConnection; - server: string; - dbName: string; - authType: "apiKey" | "username"; +async function loadFmdapiDeps() { + const [{ default: DataApi }, { FetchAdapter }, { FmMcpAdapter }, { OttoAdapter }, { memoryStore }] = + await Promise.all([ + import("@proofkit/fmdapi").catch((error: unknown) => + rethrowMissingDependency(error, "@proofkit/fmdapi", "fmdapi UI features"), + ), + import("@proofkit/fmdapi/adapters/fetch").catch((error: unknown) => + rethrowMissingDependency(error, "@proofkit/fmdapi", "fmdapi UI features"), + ), + import("@proofkit/fmdapi/adapters/fm-mcp").catch((error: unknown) => + rethrowMissingDependency(error, "@proofkit/fmdapi", "fmdapi UI features"), + ), + import("@proofkit/fmdapi/adapters/otto").catch((error: unknown) => + rethrowMissingDependency(error, "@proofkit/fmdapi", "fmdapi UI features"), + ), + import("@proofkit/fmdapi/tokenStore/memory").catch((error: unknown) => + rethrowMissingDependency(error, "@proofkit/fmdapi", "fmdapi UI features"), + ), + ]); + + return { DataApi, FetchAdapter, FmMcpAdapter, OttoAdapter, memoryStore }; } -export interface OdataClientError { - error: string; - statusCode: number; - kind?: "missing_env" | "adapter_error" | "connection_error" | "unknown"; - suspectedField?: "server" | "db" | "auth"; +async function loadFmodataDeps() { + const { FMServerConnection } = await import("@proofkit/fmodata").catch((error: unknown) => + rethrowMissingDependency(error, "@proofkit/fmodata", "fmodata UI features"), + ); + + return { FMServerConnection }; } -export function createOdataClientFromConfig(config: FmodataConfig): OdataClientResult | OdataClientError { +export async function createOdataClientFromConfig( + config: FmodataConfig, +): Promise { const result = getEnvVarsFromConfig(config.envNames); if ("error" in result) { return result; } - const { server, db: dbName, authType, auth } = result; - const connection = new FMServerConnection({ - serverUrl: server, - auth, - }); + const { server, db: dbName, authType, auth } = result; - const db = connection.database(dbName); + try { + const { FMServerConnection } = await loadFmodataDeps(); + const connection = new FMServerConnection({ + serverUrl: server, + auth, + }); + const db = connection.database(dbName); + + return { db, connection, server, dbName, authType }; + } catch (error) { + if (error instanceof TypeError) { + const message = error.message.toLowerCase(); + if (message.includes("invalid url") || message.includes("malformed")) { + return { + error: error.message, + statusCode: 400, + kind: "adapter_error", + suspectedField: "server", + }; + } + } - return { db, connection, server, dbName, authType }; + return { + error: error instanceof Error ? error.message : "Failed to create OData client", + statusCode: 500, + kind: "unknown", + }; + } } -/** - * Creates a DataApi client from an in-memory config object - * @param config The fmdapi config object - * @returns The client, server, and db, or an error object - */ -export function createClientFromConfig(config: FmdapiConfig): Omit | CreateClientError { - // FM MCP mode +export async function createClientFromConfig( + config: FmdapiConfig, +): Promise | CreateClientError> { + let deps: Awaited>; + try { + deps = await loadFmdapiDeps(); + } catch (error) { + return { + error: error instanceof Error ? error.message : "Failed to load fmdapi", + statusCode: 500, + kind: "unknown", + }; + } + + const { DataApi, FetchAdapter, FmMcpAdapter, OttoAdapter, memoryStore } = deps; + if (config.fmMcp != null && config.fmMcp.enabled !== false) { const fmMcpObj = config.fmMcp; @@ -209,11 +266,9 @@ export function createClientFromConfig(config: FmdapiConfig): Omit env var > default const baseUrl = fmMcpObj?.baseUrl || process.env[baseUrlEnvName] || defaultFmMcpBaseUrl; const connectedFileName = fmMcpObj?.connectedFileName || process.env[connectedFileNameEnvName]; - // connectedFileName is required (auto-discovery not available in sync context) if (!connectedFileName) { return { error: "Missing connectedFileName for FM MCP mode", @@ -226,8 +281,7 @@ export function createClientFromConfig(config: FmdapiConfig): Omit> = DataApi({ + const client = DataApi({ adapter: new FmMcpAdapter({ baseUrl, connectedFileName, @@ -235,7 +289,12 @@ export function createClientFromConfig(config: FmdapiConfig): Omit>; try { - client = + const client = "apiKey" in auth ? DataApi({ adapter: new OttoAdapter({ auth, server, db }), @@ -273,8 +330,14 @@ export function createClientFromConfig(config: FmdapiConfig): Omit { const fullPath = path.resolve(context.cwd, context.configPath); if (!fs.existsSync(fullPath)) { @@ -322,7 +374,6 @@ export function createDataApiClient(context: ApiContext, configIndex: number): C }; } - // Get config at index const configArray = Array.isArray(parsed.config) ? parsed.config : [parsed.config]; const config = configArray[configIndex]; @@ -333,7 +384,6 @@ export function createDataApiClient(context: ApiContext, configIndex: number): C }; } - // Validate config type if (config.type !== "fmdapi") { return { error: "Only fmdapi config type is supported", @@ -341,10 +391,8 @@ export function createDataApiClient(context: ApiContext, configIndex: number): C }; } - // Use the extracted helper function - const result = createClientFromConfig(config); + const result = await createClientFromConfig(config); - // Check if result is an error if ("error" in result) { return result; } diff --git a/packages/typegen/src/typegen.ts b/packages/typegen/src/typegen.ts index 14f904e3..bc0ba71e 100644 --- a/packages/typegen/src/typegen.ts +++ b/packages/typegen/src/typegen.ts @@ -1,9 +1,5 @@ import path from "node:path"; -import DataApi from "@proofkit/fmdapi"; -import { FetchAdapter } from "@proofkit/fmdapi/adapters/fetch"; -import { FmMcpAdapter } from "@proofkit/fmdapi/adapters/fm-mcp"; -import { OttoAdapter, type OttoAPIKey } from "@proofkit/fmdapi/adapters/otto"; -import { memoryStore } from "@proofkit/fmdapi/tokenStore/memory"; +import type { OttoAPIKey } from "@proofkit/fmdapi/adapters/otto"; import chalk from "chalk"; import fs from "fs-extra"; import semver from "semver"; @@ -13,10 +9,9 @@ import type { z } from "zod/v4"; import { buildLayoutClient } from "./buildLayoutClient"; import { buildOverrideFile, buildSchema } from "./buildSchema"; import { commentHeader, defaultEnvNames, overrideCommentHeader } from "./constants"; -import { generateODataTablesSingle } from "./fmodata/typegen"; import { formatAndSaveSourceFiles, runPostGenerateCommand } from "./formatting"; import { getEnvValues, validateAndLogEnvValues } from "./getEnvValues"; -import { getLayoutMetadata } from "./getLayoutMetadata"; +import { rethrowMissingDependency } from "./optionalDeps"; import { type BuildSchemaArgs, typegenConfig, type typegenConfigSingle } from "./types"; type GlobalOptions = Omit, "config">; @@ -95,6 +90,9 @@ export const generateTypedClients = async ( } } } else if (singleConfig.type === "fmodata") { + const { generateODataTablesSingle } = await import("./fmodata/typegen").catch((error: unknown) => + rethrowMissingDependency(error, "@proofkit/fmodata", "fmodata type generation"), + ); const outputPath = await generateODataTablesSingle(singleConfig, { cwd }); if (outputPath) { outputPaths.push(outputPath); @@ -269,6 +267,31 @@ const generateTypedClientsSingle = async ( await fs.ensureDir(rootDir); const clientIndexFilePath = path.join(rootDir, "client", "index.ts"); + const [ + { default: DataApi }, + { FetchAdapter }, + { FmMcpAdapter }, + { OttoAdapter }, + { memoryStore }, + { getLayoutMetadata }, + ] = await Promise.all([ + import("@proofkit/fmdapi").catch((error: unknown) => + rethrowMissingDependency(error, "@proofkit/fmdapi", "fmdapi type generation"), + ), + import("@proofkit/fmdapi/adapters/fetch").catch((error: unknown) => + rethrowMissingDependency(error, "@proofkit/fmdapi", "fmdapi type generation"), + ), + import("@proofkit/fmdapi/adapters/fm-mcp").catch((error: unknown) => + rethrowMissingDependency(error, "@proofkit/fmdapi", "fmdapi type generation"), + ), + import("@proofkit/fmdapi/adapters/otto").catch((error: unknown) => + rethrowMissingDependency(error, "@proofkit/fmdapi", "fmdapi type generation"), + ), + import("@proofkit/fmdapi/tokenStore/memory").catch((error: unknown) => + rethrowMissingDependency(error, "@proofkit/fmdapi", "fmdapi type generation"), + ), + import("./getLayoutMetadata"), + ]); let successCount = 0; let errorCount = 0; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d6cc517b..d35d456a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -392,7 +392,7 @@ importers: version: 11.0.0-rc.441(@trpc/server@11.0.0-rc.441) '@trpc/next': specifier: 11.0.0-rc.441 - version: 11.0.0-rc.441(@tanstack/react-query@5.90.16(react@19.2.3))(@trpc/client@11.0.0-rc.441(@trpc/server@11.0.0-rc.441))(@trpc/react-query@11.0.0-rc.441(@tanstack/react-query@5.90.16(react@19.2.3))(@trpc/client@11.0.0-rc.441(@trpc/server@11.0.0-rc.441))(@trpc/server@11.0.0-rc.441)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@trpc/server@11.0.0-rc.441)(next@16.1.1(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 11.0.0-rc.441(@tanstack/react-query@5.90.16(react@19.2.3))(@trpc/client@11.0.0-rc.441(@trpc/server@11.0.0-rc.441))(@trpc/react-query@11.0.0-rc.441(@tanstack/react-query@5.90.16(react@19.2.3))(@trpc/client@11.0.0-rc.441(@trpc/server@11.0.0-rc.441))(@trpc/server@11.0.0-rc.441)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@trpc/server@11.0.0-rc.441)(next@16.1.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@trpc/react-query': specifier: 11.0.0-rc.441 version: 11.0.0-rc.441(@tanstack/react-query@5.90.16(react@19.2.3))(@trpc/client@11.0.0-rc.441(@trpc/server@11.0.0-rc.441))(@trpc/server@11.0.0-rc.441)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) @@ -437,7 +437,7 @@ importers: version: 16.1.1(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) next-auth: specifier: ^4.24.13 - version: 4.24.13(next@16.1.1(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + version: 4.24.13(next@16.1.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3) postgres: specifier: ^3.4.8 version: 3.4.8 @@ -483,7 +483,7 @@ importers: devDependencies: vitest: specifier: ^4.0.17 - version: 4.0.17(@types/node@25.0.6)(happy-dom@20.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.7(@types/node@25.0.6)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.2) + version: 4.0.17(@types/node@25.0.6)(@vitest/ui@3.2.4)(happy-dom@20.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.7(@types/node@25.0.6)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.2) packages/fmdapi: dependencies: @@ -655,12 +655,6 @@ importers: '@hono/zod-validator': specifier: ^0.7.6 version: 0.7.6(hono@4.11.3)(zod@4.3.5) - '@proofkit/fmdapi': - specifier: workspace:* - version: link:../fmdapi - '@proofkit/fmodata': - specifier: workspace:* - version: link:../fmodata '@tanstack/vite-config': specifier: ^0.2.1 version: 0.2.1(@types/node@25.0.6)(rollup@4.55.1)(typescript@5.9.3)(vite@6.4.1(@types/node@25.0.6)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) @@ -710,6 +704,12 @@ importers: specifier: ^4.3.5 version: 4.3.5 devDependencies: + '@proofkit/fmdapi': + specifier: workspace:* + version: link:../fmdapi + '@proofkit/fmodata': + specifier: workspace:* + version: link:../fmodata '@tanstack/intent': specifier: ^0.0.19 version: 0.0.19 @@ -736,7 +736,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.17 - version: 4.0.17(@types/node@25.0.6)(happy-dom@20.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.7(@types/node@25.0.6)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.2) + version: 4.0.17(@types/node@25.0.6)(@vitest/ui@3.2.4)(happy-dom@20.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.7(@types/node@25.0.6)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.2) packages/typegen/web: dependencies: @@ -11772,7 +11772,7 @@ snapshots: dependencies: '@trpc/server': 11.0.0-rc.441 - '@trpc/next@11.0.0-rc.441(@tanstack/react-query@5.90.16(react@19.2.3))(@trpc/client@11.0.0-rc.441(@trpc/server@11.0.0-rc.441))(@trpc/react-query@11.0.0-rc.441(@tanstack/react-query@5.90.16(react@19.2.3))(@trpc/client@11.0.0-rc.441(@trpc/server@11.0.0-rc.441))(@trpc/server@11.0.0-rc.441)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@trpc/server@11.0.0-rc.441)(next@16.1.1(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + '@trpc/next@11.0.0-rc.441(@tanstack/react-query@5.90.16(react@19.2.3))(@trpc/client@11.0.0-rc.441(@trpc/server@11.0.0-rc.441))(@trpc/react-query@11.0.0-rc.441(@tanstack/react-query@5.90.16(react@19.2.3))(@trpc/client@11.0.0-rc.441(@trpc/server@11.0.0-rc.441))(@trpc/server@11.0.0-rc.441)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@trpc/server@11.0.0-rc.441)(next@16.1.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@trpc/client': 11.0.0-rc.441(@trpc/server@11.0.0-rc.441) '@trpc/server': 11.0.0-rc.441 @@ -12095,14 +12095,14 @@ snapshots: msw: 2.12.7(@types/node@22.19.5)(typescript@5.9.3) vite: 6.4.1(@types/node@22.19.5)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) - '@vitest/mocker@4.0.17(msw@2.12.7(@types/node@25.0.6)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.5)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))': + '@vitest/mocker@4.0.17(msw@2.12.7(@types/node@25.0.6)(typescript@5.9.3))(vite@6.4.1(@types/node@25.0.6)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@vitest/spy': 4.0.17 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: msw: 2.12.7(@types/node@25.0.6)(typescript@5.9.3) - vite: 6.4.1(@types/node@22.19.5)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) + vite: 6.4.1(@types/node@25.0.6)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) '@vitest/mocker@4.0.17(msw@2.12.7(@types/node@25.0.6)(typescript@5.9.3))(vite@6.4.1(@types/node@25.0.6)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2))': dependencies: @@ -14885,7 +14885,7 @@ snapshots: neo-async@2.6.2: {} - next-auth@4.24.13(next@16.1.1(@babel/core@7.28.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + next-auth@4.24.13(next@16.1.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: '@babel/runtime': 7.28.4 '@panva/hkdf': 1.2.1 @@ -16759,7 +16759,7 @@ snapshots: vitest@4.0.17(@types/node@25.0.6)(happy-dom@20.1.0)(jiti@1.21.7)(lightningcss@1.30.2)(msw@2.12.7(@types/node@25.0.6)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.2): dependencies: '@vitest/expect': 4.0.17 - '@vitest/mocker': 4.0.17(msw@2.12.7(@types/node@25.0.6)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.5)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/mocker': 4.0.17(msw@2.12.7(@types/node@25.0.6)(typescript@5.9.3))(vite@6.4.1(@types/node@25.0.6)(jiti@1.21.7)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) '@vitest/pretty-format': 4.0.17 '@vitest/runner': 4.0.17 '@vitest/snapshot': 4.0.17 @@ -16794,44 +16794,6 @@ snapshots: - tsx - yaml - vitest@4.0.17(@types/node@25.0.6)(happy-dom@20.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.7(@types/node@25.0.6)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.2): - dependencies: - '@vitest/expect': 4.0.17 - '@vitest/mocker': 4.0.17(msw@2.12.7(@types/node@25.0.6)(typescript@5.9.3))(vite@6.4.1(@types/node@22.19.5)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2)) - '@vitest/pretty-format': 4.0.17 - '@vitest/runner': 4.0.17 - '@vitest/snapshot': 4.0.17 - '@vitest/spy': 4.0.17 - '@vitest/utils': 4.0.17 - es-module-lexer: 1.7.0 - expect-type: 1.3.0 - magic-string: 0.30.21 - obug: 2.1.1 - pathe: 2.0.3 - picomatch: 4.0.3 - std-env: 3.10.0 - tinybench: 2.9.0 - tinyexec: 1.0.2 - tinyglobby: 0.2.15 - tinyrainbow: 3.0.3 - vite: 6.4.1(@types/node@25.0.6)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0)(yaml@2.8.2) - why-is-node-running: 2.3.0 - optionalDependencies: - '@types/node': 25.0.6 - happy-dom: 20.1.0 - transitivePeerDependencies: - - jiti - - less - - lightningcss - - msw - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - yaml - vscode-uri@3.1.0: {} walk-up-path@4.0.0: {}