From 5544f68598d80eb00c6214c3fea09766aacf9cdb Mon Sep 17 00:00:00 2001 From: Eric Luce <37158449+eluce2@users.noreply.github.com> Date: Mon, 16 Mar 2026 22:00:02 -0500 Subject: [PATCH 1/6] feat(cli): revamp webviewer app scaffolding --- .agents/skills/create-changeset/SKILL.md | 140 + .changeset/cli-webviewer-scaffold-fmhttp.md | 12 + .../docs/cli/webviewer/getting-started.mdx | 42 +- .../content/docs/cli/webviewer/overview.mdx | 6 +- apps/docs/content/docs/typegen/config.mdx | 4 +- biome.json | 2 +- packages/cli/src/cli/add/auth.ts | 7 +- .../cli/src/cli/add/data-source/filemaker.ts | 80 +- packages/cli/src/cli/add/data-source/index.ts | 5 + packages/cli/src/cli/add/index.ts | 3 +- packages/cli/src/cli/add/page/index.ts | 7 +- packages/cli/src/cli/init.ts | 37 +- packages/cli/src/cli/remove/data-source.ts | 7 +- packages/cli/src/cli/typegen/index.ts | 20 +- packages/cli/src/generators/fmdapi.ts | 106 +- packages/cli/src/globalOptions.ts | 6 +- packages/cli/src/globals.d.ts | 1 + packages/cli/src/helpers/createProject.ts | 24 +- packages/cli/src/helpers/fmHttp.ts | 56 + packages/cli/src/helpers/git.ts | 11 + packages/cli/src/helpers/logNextSteps.ts | 22 +- packages/cli/src/helpers/scaffoldProject.ts | 45 +- packages/cli/src/index.ts | 11 +- .../src/installers/dependencyVersionMap.ts | 11 +- packages/cli/src/installers/react-email.ts | 6 +- packages/cli/src/state.ts | 9 +- packages/cli/src/utils/getProofKitVersion.ts | 15 + packages/cli/template/vite-wv/_gitignore | 1 + packages/cli/template/vite-wv/components.json | 12 +- packages/cli/template/vite-wv/index.html | 4 +- packages/cli/template/vite-wv/package.json | 57 +- packages/cli/template/vite-wv/pnpm-lock.yaml | 2294 ----------------- .../cli/template/vite-wv/postcss.config.cjs | 15 - .../vite-wv/proofkit-typegen.config.jsonc | 18 + packages/cli/template/vite-wv/proofkit.json | 5 +- .../cli/template/vite-wv/scripts/filemaker.js | 96 + .../cli/template/vite-wv/scripts/launch-fm.js | 19 + .../cli/template/vite-wv/scripts/launch-fm.sh | 3 - .../cli/template/vite-wv/scripts/upload.js | 31 +- packages/cli/template/vite-wv/src/App.tsx | 84 + .../vite-wv/src/components/AppLogo.tsx | 5 - .../src/components/full-screen-loader.tsx | 9 - .../cli/template/vite-wv/src/config/env.ts | 16 - .../vite-wv/src/config/theme/globals.css | 125 - .../vite-wv/src/config/theme/mantine-theme.ts | 22 - packages/cli/template/vite-wv/src/index.css | 96 + .../cli/template/vite-wv/src/lib/utils.ts | 6 + packages/cli/template/vite-wv/src/main.tsx | 47 +- .../cli/template/vite-wv/src/routeTree.gen.ts | 111 - .../template/vite-wv/src/routes/__root.tsx | 21 - .../cli/template/vite-wv/src/routes/index.tsx | 51 - .../template/vite-wv/src/routes/secondary.tsx | 26 - .../vite-wv/src/utils/notification-helpers.ts | 32 - .../cli/template/vite-wv/src/utils/styles.ts | 6 - packages/cli/template/vite-wv/tsconfig.json | 4 +- packages/cli/template/vite-wv/vite.config.ts | 9 +- packages/cli/tests/browser-apps.test.ts | 9 +- packages/cli/tests/webviewer-apps.test.ts | 129 + packages/cli/tsdown.config.ts | 2 + packages/fmdapi/skills/fmdapi-client/SKILL.md | 6 + packages/fmodata/package.json | 1 + .../fmodata/skills/fmodata-client/SKILL.md | 6 + .../skills/webviewer-integration/SKILL.md | 6 + pnpm-lock.yaml | 35 +- 64 files changed, 1133 insertions(+), 2981 deletions(-) create mode 100644 .agents/skills/create-changeset/SKILL.md create mode 100644 .changeset/cli-webviewer-scaffold-fmhttp.md create mode 100644 packages/cli/src/helpers/fmHttp.ts delete mode 100644 packages/cli/template/vite-wv/pnpm-lock.yaml delete mode 100644 packages/cli/template/vite-wv/postcss.config.cjs create mode 100644 packages/cli/template/vite-wv/proofkit-typegen.config.jsonc create mode 100644 packages/cli/template/vite-wv/scripts/filemaker.js create mode 100644 packages/cli/template/vite-wv/scripts/launch-fm.js delete mode 100755 packages/cli/template/vite-wv/scripts/launch-fm.sh create mode 100644 packages/cli/template/vite-wv/src/App.tsx delete mode 100644 packages/cli/template/vite-wv/src/components/AppLogo.tsx delete mode 100644 packages/cli/template/vite-wv/src/components/full-screen-loader.tsx delete mode 100644 packages/cli/template/vite-wv/src/config/env.ts delete mode 100644 packages/cli/template/vite-wv/src/config/theme/globals.css delete mode 100644 packages/cli/template/vite-wv/src/config/theme/mantine-theme.ts create mode 100644 packages/cli/template/vite-wv/src/index.css create mode 100644 packages/cli/template/vite-wv/src/lib/utils.ts delete mode 100644 packages/cli/template/vite-wv/src/routeTree.gen.ts delete mode 100644 packages/cli/template/vite-wv/src/routes/__root.tsx delete mode 100644 packages/cli/template/vite-wv/src/routes/index.tsx delete mode 100644 packages/cli/template/vite-wv/src/routes/secondary.tsx delete mode 100644 packages/cli/template/vite-wv/src/utils/notification-helpers.ts delete mode 100644 packages/cli/template/vite-wv/src/utils/styles.ts create mode 100644 packages/cli/tests/webviewer-apps.test.ts diff --git a/.agents/skills/create-changeset/SKILL.md b/.agents/skills/create-changeset/SKILL.md new file mode 100644 index 00000000..2b96c853 --- /dev/null +++ b/.agents/skills/create-changeset/SKILL.md @@ -0,0 +1,140 @@ +--- +name: create-changeset +description: Analyze git changes and create changesets for package releases. Use when preparing pull requests, creating PRs, when branch has commits ready for review, or when user mentions changeset or version bump. +--- +# Create Changeset Skill + +## Purpose + +This skill automatically analyzes branch changes and creates appropriate changesets for package releases in this monorepo. It examines git history, determines version bump types based on Conventional Commits, and generates properly formatted changeset files. + +## When to Invoke + +Automatically invoke this skill when: +- User is preparing to create a pull request +- User mentions "PR", "pull request", or "ready for review" +- Branch has commits ready for review +- User explicitly mentions "changeset" or "version bump" + +Do NOT invoke when: +- User is only pushing changes without creating a PR +- Only documentation files have changed (README, .md files) +- Only CI/CD configuration has changed (.github/workflows/) +- Only development tool configuration has changed (eslint, prettier, etc.) +- Commits are only `docs:`, `chore:`, `ci:`, or `test:` types that don't affect packages + +## Pre-execution Validation + +Before creating a changeset, check if a changeset file already exists for the current changes: +- Look for `.changeset/*.md` files (excluding README.md) +- If exists, ask user: "A changeset already exists. Create another one?" + +## Implementation Steps + +### 1. Check Current State + +Execute `git log main..HEAD` (or `origin/main..HEAD`) to check for committed changes on the branch. If no commits exist, exit early without creating a changeset. + +### 2. Analyze Changes + +Use `git diff main...HEAD` (or `origin/main...HEAD`) to analyze **committed changes only**. + +Identify which packages are affected by checking files under `packages/*/`. Review commit messages using `git log main..HEAD --oneline` (or `origin/main..HEAD`). + +### 3. Determine Version Bump Type + +Analyze commit messages following Conventional Commits 1.0.0 format: + +- **major**: Contains `BREAKING CHANGE` in commit body, or breaking changes detected in code + - API signature changes + - Removed exports or features + - Incompatible behavior changes +- **minor**: Starts with `feat:` or `feat(scope):` - new features (backward compatible) + - New components or functionality + - New props or options (with defaults) + - New exports +- **patch**: Starts with `fix:` or `fix(scope):` - bug fixes and minor improvements + - Bug fixes + - Performance improvements + - Minor style updates +- **skip**: Other types (`chore:`, `docs:`, `ci:`, `test:`) typically don't require changesets unless they affect package functionality + +Review actual code changes to confirm the appropriate version bump. **When in doubt between minor and patch, prefer patch for safety.** + +If the version bump is ambiguous or unclear: +- Ask user for clarification +- Explain the reasoning behind the suggested bump type +- Allow user to override the suggestion + +If all commits are types that don't require changesets (`docs:`, `chore:`, `ci:`, `test:`), exit early without creating a changeset. + +### 4. Generate Changeset + +Create a changeset file with a descriptive filename in `.changeset/` directory. + +**Filename format:** +- Use kebab-case with `.md` extension +- Examples: `.changeset/add-new-button.md`, `.changeset/fix-layout-bug.md`, `.changeset/update-icon-props.md` + +**File content format:** +```markdown +--- +"@proofkit/package-name": major|minor|patch +--- + +Clear description of the change +``` + +**Example for single package:** +```markdown +--- +"@proofkit/cli": minor +--- + +Add new Button variant for secondary actions +``` + +**Example for multiple packages:** +```markdown +--- +"@proofkit/cli": minor +"@proofkit/typegen": patch +--- + +- spindle-ui: Add new Button variant for secondary actions +- spindle-tokens: Fix color token contrast ratio +``` + +**Important guidelines:** +- The description should be user-friendly as it will appear in CHANGELOG +- **Use the same language as the commit messages** (Japanese or English). If commit messages are mixed, prefer Japanese. +- Split changesets into separate files when the same package has changes with different purposes (e.g., new feature + bug fix, breaking change + internal refactoring) +- This creates individual top-level items in release notes, making it easier for readers to understand the intent of each change +- Example: Create `.changeset/add-secondary-button.md` for a new feature and `.changeset/fix-button-layout.md` for a bug fix, even if both target the same package + +### 5. Lint Changeset + +Execute `pnpm textlint .changeset/.md` to validate the changeset file. + +**Error handling:** +- If linting errors occur, attempt to auto-fix common issues: + - Spacing and punctuation + - Common grammar mistakes +- Re-run textlint after auto-fix +- If errors persist: + - Display error details to user + - Ask user for guidance on how to fix + - Do NOT proceed to commit until lint passes + +### 6. Verify and Commit + +Display the generated changeset for review: +- Show the file path +- Show the file content +- Confirm it accurately reflects the changes + +Once verified, commit the changeset file: +```bash +git add .changeset/.md +git commit -m "chore: add changeset" +``` diff --git a/.changeset/cli-webviewer-scaffold-fmhttp.md b/.changeset/cli-webviewer-scaffold-fmhttp.md new file mode 100644 index 00000000..543fa904 --- /dev/null +++ b/.changeset/cli-webviewer-scaffold-fmhttp.md @@ -0,0 +1,12 @@ +--- +"@proofkit/cli": minor +"@proofkit/typegen": minor +"@proofkit/fmdapi": patch +"@proofkit/fmodata": patch +"@proofkit/webviewer": patch +--- + +- cli: Revamp the WebViewer Vite template and harden `proofkit init` (ignore hidden files, improve non-interactive prompts, stop generating Cursor rules). +- cli: Install typegen skills locally when scaffolding projects. +- typegen: Add optional `fmHttp` config for using an FM HTTP proxy during metadata fetching. +- fmdapi/fmodata/webviewer: Add initial Codex skills for client and integration workflows. diff --git a/apps/docs/content/docs/cli/webviewer/getting-started.mdx b/apps/docs/content/docs/cli/webviewer/getting-started.mdx index 0da8e0b4..3ff5661d 100644 --- a/apps/docs/content/docs/cli/webviewer/getting-started.mdx +++ b/apps/docs/content/docs/cli/webviewer/getting-started.mdx @@ -11,24 +11,18 @@ import { DynamicCodeBlock } from "fumadocs-ui/components/dynamic-codeblock"; // import Prerequisites from "../../../components/Prerequisites.mdx"; import InitCommand from "@/components/InitCommand"; -Creating a ProofKit project for a FileMaker WebViewer is extremely similar to the browser-based version as covered in the [main Getting Started guide](/docs/cli/guides/getting-started). This document will simply highlight the key differences. +Creating a ProofKit project for a FileMaker WebViewer is extremely similar to the browser-based version as covered in the [main Getting Started guide](/docs/cli/guides/getting-started). This document highlights the WebViewer-specific workflow. ## Prep your FileMaker file -- The file must be hosted on a FileMaker server running OttoFMS (4.7 or later) -- The server must have the Data API enabled. -- You must have a user account that has the `fmrest` extended privilege enabled. -- The Full Access user account must have the `fmurlscript` extended privilege enabled (Allow URLs to run FileMaker scripts) - - - The ProofKit CLI uses the metadata endpoint of the Data API to learn about the - layouts and fields in your file to generate type-safe code that can be used in - your webviewer. This is only for development though; once you build your web - code for production, it can interact directly with the FileMaker Pro client - and will work even offline. - -In a future release, we may provide a way to scaffold a new webviewer project for use with local development, but there are other tradeoffs to consider. +- Install the ProofKit WebViewer add-on into the file you want to target. +- Make sure the file has the scripts needed by your WebViewer workflow, such as `Launch Web Viewer for Dev` and `UploadWebviewerWidget`. +- If you want local typegen without a hosted server, start the FM HTTP bridge and keep a connected FileMaker file open. + + A new WebViewer project can be scaffolded without OttoFMS or hosted FileMaker credentials. + When you are ready to generate layout clients, ProofKit prefers the local FM HTTP path first. + Hosted FileMaker server setup is still available later when you want it. ## Creating a ProofKit Project @@ -39,29 +33,27 @@ Run the following command to scaffold a new ProofKit project, choosing the `webv Refer to the [main Getting Started guide](/docs/cli/guides/getting-started) for more details on the prompts and options available. -## Install the ProofKit WebViewer Addon +The scaffolded app already includes Tailwind v4, shadcn config, helper scripts, and a starter `proofkit-typegen.config.jsonc` file wired for FM HTTP. -The ProofKit CLI will automatically install the ProofKit WebViewer to your computer, but you must install it into your FileMaker file to receive the necessary tables, layouts, and scripts. +## Generate layout clients later -To easily install the add-on, enter Layout mode on any layout and open the "Add-ons" tab in the left pane. Click the + icon in the lower left corner, then select the "ProofKit Auth" add-on from the list. +When you are ready to add generated FileMaker clients: - -If you don't see the add-on after initialing your project with the CLI, quit and re-open FileMaker Pro. - +- Edit `proofkit-typegen.config.jsonc` and add the layouts you want. +- Run `pnpm typegen` or `pnpm typegen:ui`. +- If FM HTTP is running with a connected file, ProofKit can use that local path instead of a hosted server. ## Developing with the WebViewer -When you run the dev server with the `dev` command, a localhost URL will be displayed for you to see your project in a browser. **DO NOT OPEN THIS URL IN A BROWSER** +When you run the dev server with the `dev` command, a localhost URL will be displayed. Unlike older templates, the starter app is browser-safe and can render there normally. You still need FileMaker to test script execution and real WebViewer behavior. In modern FileMaker versions, the WebViewer is just as capable as any other browser, but if your users are going to exclusively be running this code in a WebViewer, you should also be developing with the the WebViewer as your browser. Plus, it's the only way to test the FileMaker interactions that you will likely build. -Thankfully, the ProofKit WebViewer Addon makes it easy to switch your webviewer from dev mode and production mode. - With your dev server running (e.g. `pnpm dev`), open a **seperate** terminal instance and run: -which will open your FileMaker file in FileMaker Pro and run the `Launch Web Viewer for Dev` script. This script will ensure that you're on the correct layout and that the WebViewer is running in dev mode—pointing to your localhost URL. +This command prefers a locally connected FM HTTP file first, then falls back to `FM_SERVER` and `FM_DATABASE` from `.env`. It opens FileMaker Pro and runs the `Launch Web Viewer for Dev` script so your WebViewer points at the local dev server. You can use the other utility scripts to build your own version of this behavior: @@ -82,7 +74,7 @@ The final command is simply a shortcut for running the `build` and `upload` comm The `build` command will compile your project into a single html file, which will be output to the `dist` directory. -The `upload` command will use the fmp URL protocol to call a script in your FileMaker file with the path to the html file. The FileMaker script will read the data from that file and insert it into a single-record field in the `ProofKitWV` table. +The `upload` command uses the fmp URL protocol to call a script in your FileMaker file with the path to the built html file. Like `launch-fm`, it prefers a locally connected FM HTTP file before falling back to hosted server env vars. If you are working with a seperated Dev/Production environments and running migrations to deploy your code to production, the uploaded code will not transfer to your production environment. See the [Deployment Methods](/webviewer/deployment-methods) page for more details. diff --git a/apps/docs/content/docs/cli/webviewer/overview.mdx b/apps/docs/content/docs/cli/webviewer/overview.mdx index 2782e05e..d34da3ca 100644 --- a/apps/docs/content/docs/cli/webviewer/overview.mdx +++ b/apps/docs/content/docs/cli/webviewer/overview.mdx @@ -11,7 +11,7 @@ The FileMaker Web Viewer object is a powerful way to build rich, interactive exp #### What is the difference between the WebViewer and the browser-based project created with the ProofKit CLI? -The primary difference is the framework used. For browser-based projects, we use [Next.js](https://nextjs.org/) and the WebViewer project uses [Vite](https://vitejs.dev/) and [Tanstack Router](https://tanstack.com/router). Next.js is a full-stack framework, meaning that it results in some code that is expected to run on a server AND some code that runs in the browser. Vite allows us to build s single bundle of code for the browser that can be store in FileMaker and if needed, we can use the FileMaker Pro client as the "server" to run private functions and "host" the code. +The primary difference is the framework and runtime shape. Browser-based projects use [Next.js](https://nextjs.org/) while the WebViewer starter uses [Vite](https://vitejs.dev/) with a small browser-only React app. That keeps the WebViewer scaffold close to a stock Vite starter while still being ready for ProofKit WebViewer integration, Tailwind v4, and shadcn component installs. #### Can I build WebViewer applications using Next.js? @@ -20,3 +20,7 @@ Yes! This is sometimes preferred if you already hae a web app and want to keep t #### Does this technique work in Web Direct? Yes. The ProofKit WebViewer Addon includes a custom function that wraps your web code so the injected FileMaker JavaScript code will still interface with the WebViewer, even in Web Direct. + +#### Do I need a hosted FileMaker server to start? + +No. You can scaffold the WebViewer app without hosted FileMaker credentials. When you are ready to generate layout clients, ProofKit prefers the local FM HTTP path first and only needs hosted server setup if you choose that route. diff --git a/apps/docs/content/docs/typegen/config.mdx b/apps/docs/content/docs/typegen/config.mdx index b7c10f00..4c9f8051 100644 --- a/apps/docs/content/docs/typegen/config.mdx +++ b/apps/docs/content/docs/typegen/config.mdx @@ -76,9 +76,9 @@ The typegen tool is configured using the `proofkit-typegen-config.jsonc` file at "If set, will generate the client using the @proofkit/webviewer package", }, fmHttp: { - type: "{ scriptName?: string }", + type: "{ enabled?: boolean; scriptName?: string; baseUrl?: string; connectedFileName?: string }", description: - "Optional local-only adapter mode for typegen metadata fetch. Generated clients still use the @proofkit/webviewer adapter.", + "Optional local FM HTTP mode for metadata fetching during typegen. Generated runtime clients still use the WebViewer adapter.", }, generateClient: { type: "boolean", diff --git a/biome.json b/biome.json index 9216cd84..bcd7708d 100644 --- a/biome.json +++ b/biome.json @@ -62,7 +62,7 @@ "arrowParentheses": "always", "quoteStyle": "double" }, - "globals": ["__FMDAPI_VERSION__", "__BETTER_AUTH_VERSION__"] + "globals": ["__FMDAPI_VERSION__", "__BETTER_AUTH_VERSION__", "__WEBVIEWER_VERSION__"] }, "json": { "formatter": { diff --git a/packages/cli/src/cli/add/auth.ts b/packages/cli/src/cli/add/auth.ts index 48055538..960811ca 100644 --- a/packages/cli/src/cli/add/auth.ts +++ b/packages/cli/src/cli/add/auth.ts @@ -4,8 +4,8 @@ import { Command } from "commander"; import { z } from "zod/v4"; import { addAuth } from "~/generators/auth.js"; -import { ciOption, debugOption } from "~/globalOptions.js"; -import { initProgramState, state } from "~/state.js"; +import { ciOption, debugOption, nonInteractiveOption } from "~/globalOptions.js"; +import { initProgramState, isNonInteractiveMode, state } from "~/state.js"; import { getSettings } from "~/utils/parseSettings.js"; import { abortIfCancel } from "../utils.js"; @@ -44,7 +44,7 @@ export async function runAddAuthAction() { if (type === "fmaddon") { const emailProviderAnswer = state.emailProvider ?? - (state.ci ? "none" : undefined) ?? + (isNonInteractiveMode() ? "none" : undefined) ?? abortIfCancel( await select({ message: `What email provider do you want to use?\n${chalk.dim( @@ -88,6 +88,7 @@ export const makeAddAuthCommand = () => { .option("--emailProvider ", "Email provider to use (only for FM Add-on Auth)") .option("--apiKey ", "API key to use for the email provider (only for FM Add-on Auth)") .addOption(ciOption) + .addOption(nonInteractiveOption) .addOption(debugOption) .action(async () => { diff --git a/packages/cli/src/cli/add/data-source/filemaker.ts b/packages/cli/src/cli/add/data-source/filemaker.ts index d3c2d825..c04404b1 100644 --- a/packages/cli/src/cli/add/data-source/filemaker.ts +++ b/packages/cli/src/cli/add/data-source/filemaker.ts @@ -5,8 +5,10 @@ import type { z } from "zod/v4"; import { createDataAPIKey, getOttoFMSToken, listAPIKeys, listFiles } from "~/cli/ottofms.js"; import { abortIfCancel } from "~/cli/utils.js"; -import { addToFmschemaConfig } from "~/generators/fmdapi.js"; +import { addLayout, addToFmschemaConfig, ensureWebviewerFmHttpConfig } from "~/generators/fmdapi.js"; +import { getFmHttpStatus } from "~/helpers/fmHttp.js"; import { fetchServerVersions } from "~/helpers/version-fetcher.js"; +import { isNonInteractiveMode } from "~/state.js"; import { addPackageDependency } from "~/utils/addPackageDependency.js"; import { addToEnv } from "~/utils/addToEnvs.js"; import { type dataSourceSchema, getSettings, setSettings } from "~/utils/parseSettings.js"; @@ -30,6 +32,82 @@ export async function promptForFileMakerDataSource({ }) { const settings = getSettings(); + if (settings.appType === "webviewer") { + const fmHttpStatus = await getFmHttpStatus(); + const connectedFileName = fmHttpStatus.connectedFiles[0]; + const localDataSourceName = opts.name ?? "filemaker"; + + if (!opts.server && fmHttpStatus.healthy && connectedFileName) { + addPackageDependency({ + projectDir, + dependencies: ["@proofkit/fmdapi"], + devMode: false, + }); + + await ensureWebviewerFmHttpConfig({ + projectDir, + connectedFileName, + dataSourceName: localDataSourceName, + baseUrl: fmHttpStatus.baseUrl, + }); + + if (opts.layoutName && opts.schemaName) { + await addLayout({ + projectDir, + dataSourceName: localDataSourceName, + schemas: [ + { + layoutName: opts.layoutName, + schemaName: opts.schemaName, + valueLists: "allowEmpty", + }, + ], + }); + } else { + p.note( + `Detected local FM HTTP at ${fmHttpStatus.baseUrl} with connected file "${connectedFileName}". Edit ${chalk.cyan( + "proofkit-typegen.config.jsonc", + )} to add layouts, then run ${chalk.cyan("pnpm typegen")} or ${chalk.cyan("pnpm typegen:ui")}.`, + "Local FileMaker detected", + ); + } + + return; + } + + if (!opts.server && isNonInteractiveMode()) { + throw new Error( + "No local FM HTTP connection was detected and no FileMaker server was provided. Start the local FM HTTP proxy with a connected file or rerun with --server.", + ); + } + + if (!opts.server) { + const fallbackAction = abortIfCancel( + await p.select({ + message: + "Local FM HTTP was not detected. Do you want to continue with hosted FileMaker server setup or skip for now?", + options: [ + { + label: "Continue with hosted setup", + value: "hosted", + }, + { + label: "Skip for now", + value: "skip", + }, + ], + }), + ); + + if (fallbackAction === "skip") { + p.note( + `You can come back later with ${chalk.cyan("proofkit add data")} after starting FM HTTP locally or when you have a hosted server ready.`, + ); + return; + } + } + } + const existingFmDataSourceNames = settings.dataSources.filter((ds) => ds.type === "fm").map((ds) => ds.name); const server = await getValidFileMakerServerUrl(opts.server); diff --git a/packages/cli/src/cli/add/data-source/index.ts b/packages/cli/src/cli/add/data-source/index.ts index ca486ad8..ec33ad2c 100644 --- a/packages/cli/src/cli/add/data-source/index.ts +++ b/packages/cli/src/cli/add/data-source/index.ts @@ -3,6 +3,8 @@ import { Command } from "commander"; import { z } from "zod/v4"; import { ensureProofKitProject } from "~/cli/utils.js"; +import { ciOption, nonInteractiveOption } from "~/globalOptions.js"; +import { initProgramState } from "~/state.js"; import { promptForFileMakerDataSource } from "./filemaker.js"; const dataSourceType = z.enum(["fm", "supabase"]); @@ -30,8 +32,11 @@ export const runAddDataSourceCommand = async () => { export const makeAddDataSourceCommand = () => { const addDataSourceCommand = new Command("data"); addDataSourceCommand.description("Add a new data source to your project"); + addDataSourceCommand.addOption(ciOption); + addDataSourceCommand.addOption(nonInteractiveOption); addDataSourceCommand.hook("preAction", (_thisCommand, actionCommand) => { + initProgramState(actionCommand.opts()); const settings = ensureProofKitProject({ commandName: "add" }); actionCommand.setOptionValue("settings", settings); }); diff --git a/packages/cli/src/cli/add/index.ts b/packages/cli/src/cli/add/index.ts index 4cc49092..aa15ebe8 100644 --- a/packages/cli/src/cli/add/index.ts +++ b/packages/cli/src/cli/add/index.ts @@ -3,7 +3,7 @@ import type { RegistryIndex } from "@proofkit/registry"; import { Command } from "commander"; import { capitalize, groupBy, uniq } from "es-toolkit"; import ora from "ora"; -import { ciOption, debugOption } from "~/globalOptions.js"; +import { ciOption, debugOption, nonInteractiveOption } from "~/globalOptions.js"; import { initProgramState, state } from "~/state.js"; import { logger } from "~/utils/logger.js"; import { getSettings, type Settings } from "~/utils/parseSettings.js"; @@ -164,6 +164,7 @@ export const makeAddCommand = () => { .description("Add a new component to your project") .argument("[name]", "Type of component to add") .addOption(ciOption) + .addOption(nonInteractiveOption) .addOption(debugOption) .option("--noInstall", "Do not run your package manager install command", false) .action(async (name, options) => { diff --git a/packages/cli/src/cli/add/page/index.ts b/packages/cli/src/cli/add/page/index.ts index 14e02cbd..6ad307b4 100644 --- a/packages/cli/src/cli/add/page/index.ts +++ b/packages/cli/src/cli/add/page/index.ts @@ -9,8 +9,8 @@ import { nextjsTemplates, wvTemplates } from "~/cli/add/page/templates.js"; import { PKG_ROOT } from "~/consts.js"; import { getExistingSchemas } from "~/generators/fmdapi.js"; import { addRouteToNav } from "~/generators/route.js"; -import { ciOption, debugOption } from "~/globalOptions.js"; -import { initProgramState, state } from "~/state.js"; +import { ciOption, debugOption, nonInteractiveOption } from "~/globalOptions.js"; +import { initProgramState, isNonInteractiveMode, state } from "~/state.js"; import { getUserPkgManager } from "~/utils/getUserPkgManager.js"; import { type DataSource, getSettings, mergeSettings } from "~/utils/parseSettings.js"; import { abortIfCancel, ensureProofKitProject } from "../../utils.js"; @@ -38,7 +38,7 @@ export const runAddPageAction = async (opts?: { let routeName = opts?.routeName; let replacedMainPage = settings.replacedMainPage; - if (state.appType === "webviewer" && !replacedMainPage && !state.ci && !routeName) { + if (state.appType === "webviewer" && !replacedMainPage && !isNonInteractiveMode() && !routeName) { const replaceMainPage = abortIfCancel( await p.select({ message: "Do you want to replace the default page?", @@ -183,6 +183,7 @@ export const makeAddPageCommand = () => { }); addPageCommand.addOption(ciOption); + addPageCommand.addOption(nonInteractiveOption); addPageCommand.addOption(debugOption); addPageCommand.hook("preAction", () => { diff --git a/packages/cli/src/cli/init.ts b/packages/cli/src/cli/init.ts index ab8c1698..18f83c2d 100644 --- a/packages/cli/src/cli/init.ts +++ b/packages/cli/src/cli/init.ts @@ -8,15 +8,14 @@ import type { PackageJson } from "type-fest"; import { DEFAULT_APP_NAME } from "~/consts.js"; import { addAuth } from "~/generators/auth.js"; import { runCodegenCommand } from "~/generators/fmdapi.js"; -import { ciOption, debugOption } from "~/globalOptions.js"; +import { ciOption, debugOption, nonInteractiveOption } from "~/globalOptions.js"; import { createBareProject } from "~/helpers/createProject.js"; import { initializeGit } from "~/helpers/git.js"; import { installDependencies } from "~/helpers/installDependencies.js"; import { logNextSteps } from "~/helpers/logNextSteps.js"; import { setImportAlias } from "~/helpers/setImportAlias.js"; import { buildPkgInstallerMap } from "~/installers/index.js"; -import { ensureWebViewerAddonInstalled } from "~/installers/proofkit-webviewer.js"; -import { initProgramState, state } from "~/state.js"; +import { initProgramState, isNonInteractiveMode, state } from "~/state.js"; import { getVersion } from "~/utils/getProofKitVersion.js"; import { getUserPkgManager } from "~/utils/getUserPkgManager.js"; import { parseNameAndPath } from "~/utils/parseNameAndPath.js"; @@ -43,6 +42,8 @@ interface CliFlags { ui?: "shadcn" | "mantine"; /** @internal Used in CI. */ CI: boolean; + /** @internal Used in non-interactive mode. */ + nonInteractive?: boolean; /** @internal Used in CI. */ tailwind: boolean; /** @internal Used in CI. */ @@ -95,6 +96,7 @@ export const makeInitCommand = () => { .option("--noGit", "Explicitly tell the CLI to not initialize a new git repo in the project", false) .option("--noInstall", "Explicitly tell the CLI to not run the package manager's install command", false) .addOption(ciOption) + .addOption(nonInteractiveOption) .addOption(debugOption) .action(runInit); @@ -132,21 +134,26 @@ type ProofKitPackageJSON = PackageJson & { export const runInit = async (name?: string, opts?: CliFlags) => { const pkgManager = getUserPkgManager(); const cliOptions = opts ?? defaultOptions; + const nonInteractive = isNonInteractiveMode(); // capture ui choice early into state state.ui = (cliOptions.ui ?? "shadcn") as "shadcn" | "mantine"; - const projectName = - name || - abortIfCancel( + let projectName = name; + if (!projectName) { + if (nonInteractive) { + throw new Error("Project name is required in non-interactive mode."); + } + projectName = abortIfCancel( await text({ message: "What will your project be called?", defaultValue: DEFAULT_APP_NAME, validate: validateAppName, }), ).toString(); + } if (!state.appType) { - state.appType = state.ci + state.appType = nonInteractive ? "browser" : (abortIfCancel( await select({ @@ -209,7 +216,7 @@ export const runInit = async (name?: string, opts?: CliFlags) => { dataSources: [], tanstackQuery: false, replacedMainPage: false, - appliedUpgrades: ["cursorRules"], + appliedUpgrades: [], reactEmail: false, reactEmailServer: false, registryTemplates: [], @@ -225,7 +232,10 @@ export const runInit = async (name?: string, opts?: CliFlags) => { setSettings(initialSettings); // for webviewer apps FM is required, so don't ask - let dataSource = state.appType === "webviewer" ? "filemaker" : cliOptions.dataSource; + let dataSource = + state.appType === "webviewer" + ? (cliOptions.dataSource ?? "none") + : (cliOptions.dataSource ?? (nonInteractive ? "none" : undefined)); if (!dataSource) { dataSource = abortIfCancel( await select({ @@ -259,11 +269,6 @@ export const runInit = async (name?: string, opts?: CliFlags) => { layoutName: cliOptions.layoutName, schemaName: cliOptions.schemaName, }); - - // Now that we have the data source set up, check for webviewer layouts if needed - if (state.appType === "webviewer") { - await ensureWebViewerAddonInstalled(); - } } else if (dataSource === "supabase") { // TODO: add supabase } @@ -272,7 +277,9 @@ export const runInit = async (name?: string, opts?: CliFlags) => { await installDependencies({ projectDir }); - await runCodegenCommand(); + if (dataSource === "filemaker") { + await runCodegenCommand(); + } if (!cliOptions.noGit) { await initializeGit(projectDir); diff --git a/packages/cli/src/cli/remove/data-source.ts b/packages/cli/src/cli/remove/data-source.ts index 5e2df1f2..0bafd0e1 100644 --- a/packages/cli/src/cli/remove/data-source.ts +++ b/packages/cli/src/cli/remove/data-source.ts @@ -6,8 +6,8 @@ import fs from "fs-extra"; import { z } from "zod/v4"; import { removeFromFmschemaConfig, runCodegenCommand } from "~/generators/fmdapi.js"; -import { ciOption, debugOption } from "~/globalOptions.js"; -import { initProgramState, state } from "~/state.js"; +import { ciOption, debugOption, nonInteractiveOption } from "~/globalOptions.js"; +import { initProgramState, isNonInteractiveMode, state } from "~/state.js"; import { type DataSource, getSettings, setSettings } from "~/utils/parseSettings.js"; import { abortIfCancel, ensureProofKitProject, UserAbortedError } from "../utils.js"; @@ -78,7 +78,7 @@ export const runRemoveDataSourceCommand = async (name?: string) => { } let confirmed = true; - if (!state.ci) { + if (!isNonInteractiveMode()) { confirmed = abortIfCancel( await p.confirm({ message: `Are you sure you want to remove the data source "${dataSourceName}"? This will only remove it from your configuration, not replace any possible usage, which may cause TypeScript errors.`, @@ -133,6 +133,7 @@ export const makeRemoveDataSourceCommand = () => { .description("Remove a data source from your project") .option("--name ", "Name of the data source to remove") .addOption(ciOption) + .addOption(nonInteractiveOption) .addOption(debugOption) .action(async (options) => { const schema = z.object({ diff --git a/packages/cli/src/cli/typegen/index.ts b/packages/cli/src/cli/typegen/index.ts index f2d860b6..23a4f61c 100644 --- a/packages/cli/src/cli/typegen/index.ts +++ b/packages/cli/src/cli/typegen/index.ts @@ -4,24 +4,8 @@ import { runCodegenCommand } from "~/generators/fmdapi.js"; import type { Settings } from "~/utils/parseSettings.js"; import { ensureProofKitProject } from "../utils.js"; -export async function runTypegen(opts: { settings: Settings }) { - const dataSources = opts.settings.dataSources; - let generateFmTypes = false; - for await (const db of dataSources) { - if (db.type === "supabase") { - throw new Error("Supabase is not supported yet"); - } - if (db.type === "fm") { - console.log(`Detected FileMaker database, generating types for ${db.name}...`); - generateFmTypes = true; - } else { - throw new Error("Unable to generate types for unknown database type"); - } - } - - if (generateFmTypes) { - await runCodegenCommand(); - } +export async function runTypegen(_opts: { settings: Settings }) { + await runCodegenCommand(); } export const makeTypegenCommand = () => { diff --git a/packages/cli/src/generators/fmdapi.ts b/packages/cli/src/generators/fmdapi.ts index 66bed1e8..a7c16ea9 100644 --- a/packages/cli/src/generators/fmdapi.ts +++ b/packages/cli/src/generators/fmdapi.ts @@ -8,7 +8,8 @@ import { SyntaxKind } from "ts-morph"; import type { z } from "zod/v4"; import { state } from "~/state.js"; -import { type envNamesSchema, getSettings } from "~/utils/parseSettings.js"; +import { logger } from "~/utils/logger.js"; +import type { envNamesSchema } from "~/utils/parseSettings.js"; import { getNewProject } from "~/utils/ts-morph.js"; // Input schema for functions like addLayout @@ -34,6 +35,8 @@ interface FullProofkitTypegenJsonFile { config: AnyDataSourceConfig | AnyDataSourceConfig[]; } +const typegenConfigFileName = "proofkit-typegen.config.jsonc"; + // Helper function to normalize data sources by adding default type for backwards compatibility // This mirrors the zod preprocess in @proofkit/typegen that defaults type to "fmdapi" function normalizeDataSource(ds: AnyDataSourceConfig): AnyDataSourceConfig { @@ -106,7 +109,7 @@ export async function addLayout({ runCodegen?: boolean; dataSourceName: string; }) { - const jsonConfigPath = path.join(projectDir, "proofkit-typegen.config.jsonc"); + const jsonConfigPath = path.join(projectDir, typegenConfigFileName); let fileContent = await readJsonConfigFile(jsonConfigPath); if (!fileContent) { @@ -173,7 +176,7 @@ export async function addConfig({ projectDir: string; runCodegen?: boolean; }) { - const jsonConfigPath = path.join(projectDir, "proofkit-typegen.config.jsonc"); + const jsonConfigPath = path.join(projectDir, typegenConfigFileName); let fileContent = await readJsonConfigFile(jsonConfigPath); const configsToAdd = Array.isArray(config) ? config : [config]; @@ -198,26 +201,83 @@ export async function addConfig({ } } -export async function runCodegenCommand() { - const projectDir = state.projectDir; - const settings = getSettings(); - if (settings.dataSources.length === 0) { - console.log("no data sources found, skipping typegen"); +export async function ensureWebviewerFmHttpConfig({ + projectDir, + connectedFileName, + dataSourceName = "filemaker", + baseUrl, +}: { + projectDir: string; + connectedFileName?: string; + dataSourceName?: string; + baseUrl?: string; +}) { + const newConfig: FmdapiDataSourceConfig = { + type: "fmdapi", + path: `./src/config/schemas/${dataSourceName}`, + clearOldFiles: true, + clientSuffix: "Layout", + webviewerScriptName: "ExecuteDataApi", + envNames: undefined, + layouts: [], + fmHttp: { + enabled: true, + ...(baseUrl ? { baseUrl } : {}), + ...(connectedFileName ? { connectedFileName } : {}), + }, + }; + + const jsonConfigPath = path.join(projectDir, typegenConfigFileName); + let fileContent = await readJsonConfigFile(jsonConfigPath); + + if (!fileContent) { + fileContent = { + $schema: "https://proofkit.dev/typegen-config-schema.json", + config: [newConfig], + }; + await writeJsonConfigFile(jsonConfigPath, fileContent); return; } - const hasFileMakerDataSources = settings.dataSources.some((ds) => ds.type === "fm"); + const configArray = Array.isArray(fileContent.config) ? fileContent.config : [fileContent.config]; + if (!Array.isArray(fileContent.config)) { + fileContent.config = configArray; + } - if (hasFileMakerDataSources) { - const config = await readJsonConfigFile(path.join(projectDir, "proofkit-typegen.config.jsonc")); - if (!config) { - throw new Error("proofkit-typegen.config.jsonc not found"); - } + const existingConfigIndex = configArray.findIndex( + (config): config is FmdapiDataSourceConfig => config.type === "fmdapi" && config.path === newConfig.path, + ); - // make sure to load the .env file - dotenvConfig({ path: path.join(projectDir, ".env") }); - await generateTypedClients(config.config, { cwd: projectDir }); + if (existingConfigIndex === -1) { + configArray.push(newConfig); + } else { + const existingConfig = configArray[existingConfigIndex] as FmdapiDataSourceConfig; + configArray[existingConfigIndex] = { + ...existingConfig, + ...newConfig, + layouts: existingConfig.layouts ?? [], + fmHttp: { + enabled: true, + ...(existingConfig.fmHttp ?? {}), + ...(newConfig.fmHttp ?? {}), + }, + }; } + + await writeJsonConfigFile(jsonConfigPath, fileContent); +} + +export async function runCodegenCommand() { + const projectDir = state.projectDir; + const config = await readJsonConfigFile(path.join(projectDir, typegenConfigFileName)); + if (!config) { + logger.info("no typegen config found, skipping typegen"); + return; + } + + // make sure to load the .env file + dotenvConfig({ path: path.join(projectDir, ".env") }); + await generateTypedClients(config.config, { cwd: projectDir }); } export function getClientSuffix({ @@ -227,7 +287,7 @@ export function getClientSuffix({ projectDir?: string; dataSourceName: string; }): string { - const jsonConfigPath = path.join(projectDir, "proofkit-typegen.config.jsonc"); + const jsonConfigPath = path.join(projectDir, typegenConfigFileName); if (!fs.existsSync(jsonConfigPath)) { return "Client"; } @@ -258,7 +318,7 @@ export function getExistingSchemas({ projectDir?: string; dataSourceName: string; }): { layout?: string; schemaName?: string }[] { - const jsonConfigPath = path.join(projectDir, "proofkit-typegen.config.jsonc"); + const jsonConfigPath = path.join(projectDir, typegenConfigFileName); if (!fs.existsSync(jsonConfigPath)) { return []; } @@ -297,7 +357,7 @@ export async function addToFmschemaConfig({ envNames?: z.infer; }) { const projectDir = state.projectDir; - const jsonConfigPath = path.join(projectDir, "proofkit-typegen.config.jsonc"); + const jsonConfigPath = path.join(projectDir, typegenConfigFileName); let fileContent = await readJsonConfigFile(jsonConfigPath); const newDataSource: FmdapiDataSourceConfig = { @@ -386,7 +446,7 @@ export function getFieldNamesForSchema({ schemaName, dataSourceName }: { schemaN export async function removeFromFmschemaConfig({ dataSourceName }: { dataSourceName: string }) { const projectDir = state.projectDir; - const jsonConfigPath = path.join(projectDir, "proofkit-typegen.config.jsonc"); + const jsonConfigPath = path.join(projectDir, typegenConfigFileName); const fileContent = await readJsonConfigFile(jsonConfigPath); if (!fileContent) { @@ -417,11 +477,11 @@ export async function removeLayout({ dataSourceName: string; runCodegen?: boolean; }) { - const jsonConfigPath = path.join(projectDir, "proofkit-typegen.config.jsonc"); + const jsonConfigPath = path.join(projectDir, typegenConfigFileName); const fileContent = await readJsonConfigFile(jsonConfigPath); if (!fileContent) { - throw new Error("proofkit-typegen.config.jsonc not found, cannot remove layout."); + throw new Error(`${typegenConfigFileName} not found, cannot remove layout.`); } let dataSourceModified = false; diff --git a/packages/cli/src/globalOptions.ts b/packages/cli/src/globalOptions.ts index cb0caf65..5fbdeef7 100644 --- a/packages/cli/src/globalOptions.ts +++ b/packages/cli/src/globalOptions.ts @@ -1,4 +1,8 @@ import { Option } from "commander"; -export const ciOption = new Option("--ci", "Run in CI mode").default(false); +export const ciOption = new Option("--ci", "Deprecated alias for --non-interactive").default(false); +export const nonInteractiveOption = new Option( + "--non-interactive", + "Never prompt for input; fail with a clear error when required values are missing", +).default(false); export const debugOption = new Option("--debug", "Run in debug mode").default(false); diff --git a/packages/cli/src/globals.d.ts b/packages/cli/src/globals.d.ts index 07842412..3da73d1f 100644 --- a/packages/cli/src/globals.d.ts +++ b/packages/cli/src/globals.d.ts @@ -1,2 +1,3 @@ declare const __FMDAPI_VERSION__: string; declare const __BETTER_AUTH_VERSION__: string; +declare const __WEBVIEWER_VERSION__: string; diff --git a/packages/cli/src/helpers/createProject.ts b/packages/cli/src/helpers/createProject.ts index 5ae77dc4..0450ec78 100644 --- a/packages/cli/src/helpers/createProject.ts +++ b/packages/cli/src/helpers/createProject.ts @@ -36,7 +36,7 @@ export const createBareProject = async ({ projectName, scopedAppName, packages, // Add new base dependencies for Tailwind v4 and shadcn/ui or legacy Mantine // These should match the plan and dependencyVersionMap - const SHADCN_BASE_DEPS = [ + const NEXT_SHADCN_BASE_DEPS = [ "@radix-ui/react-slot", "@tailwindcss/postcss", "class-variance-authority", @@ -47,7 +47,21 @@ export const createBareProject = async ({ projectName, scopedAppName, packages, "tw-animate-css", "next-themes", ] as AvailableDependencies[]; + const VITE_SHADCN_BASE_DEPS = [ + "@radix-ui/react-slot", + "@tailwindcss/vite", + "@proofkit/fmdapi", + "@proofkit/webviewer", + "class-variance-authority", + "clsx", + "lucide-react", + "tailwind-merge", + "tailwindcss", + "tw-animate-css", + "zod", + ] as AvailableDependencies[]; const SHADCN_BASE_DEV_DEPS = [] as AvailableDependencies[]; + const VITE_SHADCN_BASE_DEV_DEPS = ["@proofkit/typegen"] as AvailableDependencies[]; const MANTINE_DEPS = [ "@mantine/core", @@ -70,17 +84,13 @@ export const createBareProject = async ({ projectName, scopedAppName, packages, }); } else if (state.ui === "shadcn") { addPackageDependency({ - dependencies: SHADCN_BASE_DEPS, + dependencies: state.appType === "webviewer" ? VITE_SHADCN_BASE_DEPS : NEXT_SHADCN_BASE_DEPS, devMode: false, }); addPackageDependency({ - dependencies: SHADCN_BASE_DEV_DEPS, + dependencies: state.appType === "webviewer" ? VITE_SHADCN_BASE_DEV_DEPS : SHADCN_BASE_DEV_DEPS, devMode: true, }); - addPackageDependency({ - dependencies: ["zod"], - devMode: false, - }); } else { throw new Error(`Unsupported UI library: ${state.ui}`); } diff --git a/packages/cli/src/helpers/fmHttp.ts b/packages/cli/src/helpers/fmHttp.ts new file mode 100644 index 00000000..89799b75 --- /dev/null +++ b/packages/cli/src/helpers/fmHttp.ts @@ -0,0 +1,56 @@ +const defaultBaseUrl = process.env.FM_HTTP_BASE_URL ?? "http://127.0.0.1:1365"; +const REQUEST_TIMEOUT_MS = 3000; + +export interface FmHttpStatus { + baseUrl: string; + healthy: boolean; + connectedFiles: string[]; +} + +async function fetchWithTimeout(url: string): Promise { + const controller = new AbortController(); + const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS); + + try { + return await fetch(url, { signal: controller.signal }); + } catch { + return null; + } finally { + clearTimeout(timeoutId); + } +} + +async function readJson(url: string): Promise { + const response = await fetchWithTimeout(url); + + if (!response?.ok) { + return null; + } + + return await response.json().catch(() => null); +} + +export async function getFmHttpStatus(baseUrl = defaultBaseUrl): Promise { + const healthResponse = await fetchWithTimeout(`${baseUrl}/health`); + + if (!healthResponse?.ok) { + return { + baseUrl, + healthy: false, + connectedFiles: [], + }; + } + + const connectedFiles = await readJson(`${baseUrl}/connectedFiles`); + + return { + baseUrl, + healthy: true, + connectedFiles: Array.isArray(connectedFiles) ? connectedFiles : [], + }; +} + +export async function detectConnectedFmFile(baseUrl = defaultBaseUrl): Promise { + const status = await getFmHttpStatus(baseUrl); + return status.connectedFiles[0]; +} diff --git a/packages/cli/src/helpers/git.ts b/packages/cli/src/helpers/git.ts index 217a03c1..2819db93 100644 --- a/packages/cli/src/helpers/git.ts +++ b/packages/cli/src/helpers/git.ts @@ -6,6 +6,7 @@ import { execa } from "execa"; import fs from "fs-extra"; import ora from "ora"; +import { isNonInteractiveMode } from "~/state.js"; import { logger } from "~/utils/logger.js"; const isGitInstalled = (dir: string): boolean => { @@ -70,6 +71,11 @@ export const initializeGit = async (projectDir: string) => { if (isInside && isRoot) { // Dir is a root git repo spinner.stop(); + if (isNonInteractiveMode()) { + throw new Error( + `Cannot initialize git in non-interactive mode because "${dirName}" already contains a git repository.`, + ); + } const overwriteGit = await p.confirm({ message: `${chalk.redBright.bold( "Warning:", @@ -86,6 +92,11 @@ export const initializeGit = async (projectDir: string) => { } else if (isInside && !isRoot) { // Dir is inside a git worktree spinner.stop(); + if (isNonInteractiveMode()) { + throw new Error( + `Cannot initialize git in non-interactive mode because "${dirName}" is already inside a git worktree.`, + ); + } const initializeChildGitRepo = await p.confirm({ message: `${chalk.redBright.bold( "Warning:", diff --git a/packages/cli/src/helpers/logNextSteps.ts b/packages/cli/src/helpers/logNextSteps.ts index b50ffd77..5b7c845d 100644 --- a/packages/cli/src/helpers/logNextSteps.ts +++ b/packages/cli/src/helpers/logNextSteps.ts @@ -2,9 +2,13 @@ import chalk from "chalk"; import { DEFAULT_APP_NAME } from "~/consts.js"; import type { InstallerOptions } from "~/installers/index.js"; +import { state } from "~/state.js"; import { getUserPkgManager } from "~/utils/getUserPkgManager.js"; import { logger } from "~/utils/logger.js"; +const formatRunCommand = (pkgManager: ReturnType, command: string) => + ["npm", "bun"].includes(pkgManager) ? `${pkgManager} run ${command}` : `${pkgManager} ${command}`; + // This logs the next steps that the user should take in order to advance the project export const logNextSteps = ({ projectName = DEFAULT_APP_NAME, @@ -28,17 +32,17 @@ export const logNextSteps = ({ } logger.dim("\nStart the dev server to view your app in a browser:"); - if (["npm", "bun"].includes(pkgManager)) { - logger.info(` ${pkgManager} run dev`); - } else { - logger.info(` ${pkgManager} dev`); + logger.info(` ${formatRunCommand(pkgManager, "dev")}`); + + if (state.appType === "webviewer") { + logger.dim("\nWhen you're ready to generate FileMaker clients:"); + logger.info(` ${formatRunCommand(pkgManager, "typegen")}`); + + logger.dim("\nTo open the starter inside FileMaker once your file is ready:"); + logger.info(` ${formatRunCommand(pkgManager, "launch-fm")}`); } logger.dim("\nOr, run the ProofKit command again to add more to your project:"); - if (["npm", "bun"].includes(pkgManager)) { - logger.info(` ${pkgManager} run proofkit`); - } else { - logger.info(` ${pkgManager} proofkit`); - } + logger.info(` ${formatRunCommand(pkgManager, "proofkit")}`); logger.dim("(Must be inside the project directory)"); }; diff --git a/packages/cli/src/helpers/scaffoldProject.ts b/packages/cli/src/helpers/scaffoldProject.ts index d06a0a0f..45821d85 100644 --- a/packages/cli/src/helpers/scaffoldProject.ts +++ b/packages/cli/src/helpers/scaffoldProject.ts @@ -6,10 +6,33 @@ import ora from "ora"; import { PKG_ROOT } from "~/consts.js"; import type { InstallerOptions } from "~/installers/index.js"; -import { state } from "~/state.js"; -import { copyCursorRules } from "~/upgrades/cursorRules.js"; +import { isNonInteractiveMode, state } from "~/state.js"; import { logger } from "~/utils/logger.js"; +const AGENT_METADATA_DIRS = new Set([".agents", ".claude", ".clawed", ".clinerules", ".cursor", ".windsurf"]); + +function getMeaningfulDirectoryEntries(projectDir: string): string[] { + return fs.readdirSync(projectDir).filter((entry) => { + if (AGENT_METADATA_DIRS.has(entry)) { + return false; + } + + const entryPath = path.join(projectDir, entry); + let stats: fs.Stats; + try { + stats = fs.lstatSync(entryPath); + } catch { + return false; + } + + if (stats.isFile() && entry.startsWith(".")) { + return false; + } + + return true; + }); +} + // This bootstraps the base Next.js application export const scaffoldProject = async ({ projectName, pkgManager, noInstall }: InstallerOptions) => { const projectDir = state.projectDir; @@ -30,11 +53,24 @@ export const scaffoldProject = async ({ projectName, pkgManager, noInstall }: In const spinner = ora(`Scaffolding in: ${projectDir}...\n`).start(); if (fs.existsSync(projectDir)) { - if (fs.readdirSync(projectDir).length === 0) { + const meaningfulEntries = getMeaningfulDirectoryEntries(projectDir); + + if (meaningfulEntries.length === 0) { if (projectName !== ".") { spinner.info(`${chalk.cyan.bold(projectName)} exists but is empty, continuing...\n`); } } else { + if (isNonInteractiveMode()) { + spinner.fail( + `${chalk.redBright.bold("Error:")} ${chalk.cyan.bold( + projectName, + )} already exists and isn't empty. Remove the existing files or choose a different directory.`, + ); + throw new Error( + `Cannot initialize into a non-empty directory in non-interactive mode: ${meaningfulEntries.join(", ")}`, + ); + } + spinner.stopAndPersist(); const overwriteDir = await p.select({ message: `${chalk.redBright.bold("Warning:")} ${chalk.cyan.bold( @@ -85,9 +121,6 @@ export const scaffoldProject = async ({ projectName, pkgManager, noInstall }: In // Copy the main template fs.copySync(srcDir, projectDir); - // Copy cursor rules - copyCursorRules(); - // Rename gitignore fs.renameSync(path.join(projectDir, "_gitignore"), path.join(projectDir, ".gitignore")); diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index ee1ae363..888b64b7 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -13,8 +13,8 @@ import { makeTypegenCommand } from "./cli/typegen/index.js"; import { makeUpgradeCommand } from "./cli/update/makeUpgradeCommand.js"; import { UserAbortedError } from "./cli/utils.js"; import { npmName } from "./consts.js"; -import { ciOption } from "./globalOptions.js"; -import { initProgramState, state } from "./state.js"; +import { ciOption, nonInteractiveOption } from "./globalOptions.js"; +import { initProgramState, isNonInteractiveMode } from "./state.js"; import { getVersion } from "./utils/getProofKitVersion.js"; import { getSettings, type Settings } from "./utils/parseSettings.js"; import { checkAndRenderVersionWarning } from "./utils/renderVersionWarning.js"; @@ -31,6 +31,7 @@ const main = async () => { .version(version) .command("default", { hidden: true, isDefault: true }) .addOption(ciOption) + .addOption(nonInteractiveOption) .action(async (args) => { initProgramState(args); @@ -41,8 +42,10 @@ const main = async () => { // void } - if (state.ci) { - logger.warn("Running in CI mode"); + if (isNonInteractiveMode()) { + throw new Error( + "The default command is interactive-only in non-interactive mode. Run an explicit command such as `proofkit init --non-interactive`.", + ); } if (settings) { diff --git a/packages/cli/src/installers/dependencyVersionMap.ts b/packages/cli/src/installers/dependencyVersionMap.ts index 8eadc1ad..8969d819 100644 --- a/packages/cli/src/installers/dependencyVersionMap.ts +++ b/packages/cli/src/installers/dependencyVersionMap.ts @@ -2,6 +2,8 @@ import { getFmdapiVersion, getNodeMajorVersion, getProofkitBetterAuthVersion, + getProofkitWebviewerVersion, + getTypegenVersion, getVersion, } from "~/utils/getProofKitVersion.js"; @@ -34,10 +36,11 @@ export const dependencyVersionMap = { tailwindcss: "^4.1.10", postcss: "^8.4.41", "@tailwindcss/postcss": "^4.1.10", + "@tailwindcss/vite": "^4.2.1", "class-variance-authority": "^0.7.1", clsx: "^2.1.1", - "tailwind-merge": "^3.3.1", - "tw-animate-css": "^1.3.4", + "tailwind-merge": "^3.5.0", + "tw-animate-css": "^1.4.0", // tRPC "@trpc/client": "^11.0.0-rc.446", @@ -53,9 +56,11 @@ export const dependencyVersionMap = { // FileMaker Data API "@proofkit/fmdapi": `^${getFmdapiVersion()}`, + "@proofkit/webviewer": `^${getProofkitWebviewerVersion()}`, // ProofKit "@proofkit/cli": `^${getVersion()}`, + "@proofkit/typegen": `^${getTypegenVersion()}`, // Tanstack Query "@tanstack/react-query": "^5.59.0", @@ -85,7 +90,7 @@ export const dependencyVersionMap = { "@radix-ui/react-slot": "^1.2.3", // Icons (for shadcn/ui) - "lucide-react": "^0.518.0", + "lucide-react": "^0.577.0", // better-auth "better-auth": "^1.3.4", diff --git a/packages/cli/src/installers/react-email.ts b/packages/cli/src/installers/react-email.ts index 7f734771..401fe6e0 100644 --- a/packages/cli/src/installers/react-email.ts +++ b/packages/cli/src/installers/react-email.ts @@ -8,7 +8,7 @@ import type { PackageJson } from "type-fest"; import { abortIfCancel } from "~/cli/utils.js"; import { PKG_ROOT } from "~/consts.js"; import { installDependencies } from "~/helpers/installDependencies.js"; -import { state } from "~/state.js"; +import { isNonInteractiveMode, state } from "~/state.js"; import { addPackageDependency } from "~/utils/addPackageDependency.js"; import { addToEnv } from "~/utils/addToEnvs.js"; import { logger } from "~/utils/logger.js"; @@ -113,7 +113,7 @@ export async function installPlunk({ project }: { project?: Project }) { let apiKey: string; if (typeof state.apiKey === "string") { apiKey = state.apiKey; - } else if (state.ci) { + } else if (isNonInteractiveMode()) { apiKey = ""; } else { apiKey = abortIfCancel( @@ -167,7 +167,7 @@ export async function installResend({ project }: { project?: Project }) { let apiKey: string; if (typeof state.apiKey === "string") { apiKey = state.apiKey; - } else if (state.ci) { + } else if (isNonInteractiveMode()) { apiKey = ""; } else { apiKey = abortIfCancel( diff --git a/packages/cli/src/state.ts b/packages/cli/src/state.ts index 9776e111..58711d4b 100644 --- a/packages/cli/src/state.ts +++ b/packages/cli/src/state.ts @@ -3,6 +3,7 @@ import { z } from "zod/v4"; const schema = z .object({ ci: z.boolean().default(false), + nonInteractive: z.boolean().default(false), debug: z.boolean().default(false), localBuild: z.boolean().default(false), baseCommand: z.enum(["add", "init", "deploy", "upgrade", "remove"]).optional().catch(undefined), @@ -21,6 +22,12 @@ export let state: ProgramState = schema.parse({}); export function initProgramState(args: unknown) { const parsed = schema.safeParse(args); if (parsed.success) { - state = { ...state, ...parsed.data }; + const mergedState = { ...state, ...parsed.data }; + const nonInteractive = mergedState.nonInteractive || mergedState.ci; + state = { ...mergedState, ci: nonInteractive, nonInteractive }; } } + +export function isNonInteractiveMode() { + return state.nonInteractive || state.ci; +} diff --git a/packages/cli/src/utils/getProofKitVersion.ts b/packages/cli/src/utils/getProofKitVersion.ts index af106ba6..1b45df6a 100644 --- a/packages/cli/src/utils/getProofKitVersion.ts +++ b/packages/cli/src/utils/getProofKitVersion.ts @@ -28,3 +28,18 @@ export const getNodeMajorVersion = () => { export const getProofkitBetterAuthVersion = () => { return __BETTER_AUTH_VERSION__; }; + +export const getProofkitWebviewerVersion = () => { + return __WEBVIEWER_VERSION__; +}; + +export const getTypegenVersion = () => { + const packageJsonPath = path.join(PKG_ROOT, "packages", "typegen", "package.json"); + + try { + const packageJsonContent = fs.readJSONSync(packageJsonPath) as PackageJson; + return packageJsonContent.version ?? "1.1.0-beta.16"; + } catch { + return "1.1.0-beta.16"; + } +}; diff --git a/packages/cli/template/vite-wv/_gitignore b/packages/cli/template/vite-wv/_gitignore index ed335a7b..984db15a 100644 --- a/packages/cli/template/vite-wv/_gitignore +++ b/packages/cli/template/vite-wv/_gitignore @@ -2,6 +2,7 @@ .DS_Store *.local *.log* +.env* # Dist node_modules diff --git a/packages/cli/template/vite-wv/components.json b/packages/cli/template/vite-wv/components.json index 0d27c449..13e1db0b 100644 --- a/packages/cli/template/vite-wv/components.json +++ b/packages/cli/template/vite-wv/components.json @@ -1,21 +1,21 @@ { "$schema": "https://ui.shadcn.com/schema.json", "style": "new-york", - "rsc": true, + "rsc": false, "tsx": true, "tailwind": { "config": "", - "css": "src/config/theme/globals.css", + "css": "src/index.css", "baseColor": "neutral", "cssVariables": true, - "prefix": "tw:" + "prefix": "" }, "aliases": { "components": "@/components", - "utils": "@/utils/styles", + "utils": "@/lib/utils", "ui": "@/components/ui", - "lib": "@/utils", - "hooks": "@/utils/hooks" + "lib": "@/lib", + "hooks": "@/hooks" }, "iconLibrary": "lucide" } diff --git a/packages/cli/template/vite-wv/index.html b/packages/cli/template/vite-wv/index.html index a1f7d6ce..93e935bb 100644 --- a/packages/cli/template/vite-wv/index.html +++ b/packages/cli/template/vite-wv/index.html @@ -3,11 +3,11 @@ - ProofKit WebViewer + ProofKit WebViewer Starter -
+
diff --git a/packages/cli/template/vite-wv/package.json b/packages/cli/template/vite-wv/package.json index 8ba455cd..786ef5b7 100644 --- a/packages/cli/template/vite-wv/package.json +++ b/packages/cli/template/vite-wv/package.json @@ -5,51 +5,32 @@ "type": "module", "scripts": { "build": "vite build", - "build:upload": "pnpm build && pnpm upload", + "build:upload": "__PNPM_COMMAND__ build && __PNPM_COMMAND__ upload", "dev": "vite", - "launch-fm": "./scripts/launch-fm.sh", + "launch-fm": "node ./scripts/launch-fm.js", "proofkit": "proofkit", "serve": "vite preview", "start": "vite", - "typegen": "proofkit typegen", - "upload": "node scripts/upload.js", - "lint": "biome check", - "format": "biome format --write" + "typegen": "typegen", + "typegen:ui": "typegen ui", + "upload": "node ./scripts/upload.js", + "lint": "biome check .", + "format": "biome format --write ." }, "dependencies": { - "@mantine/core": "^7.15.1", - "@mantine/dates": "^7.15.1", - "@mantine/hooks": "^7.15.1", - "@mantine/modals": "^7.15.1", - "@mantine/notifications": "^7.15.1", - "@proofkit/webviewer": "^3.0.0", - "@proofkit/fmdapi": "^5.0.0", - "@t3-oss/env-core": "^0.12.0", - "@tabler/icons-react": "^3.26.0", - "@tanstack/react-query": "^5.69.0", - "@tanstack/react-router": "^1.114.27", - "mantine-react-table": "2.0.0-beta.7", - "react": "^19.0.0", - "react-dom": "^19.0.0", - "vite-plugin-singlefile": "^2.1.0", - "zod": "^3.24.1" + "react": "^19.2.4", + "react-dom": "^19.2.4" }, "devDependencies": { - "@biomejs/biome": "2.3.11", - "@tanstack/react-query-devtools": "^5.69.0", - "ultracite": "7.0.8", - "@tanstack/react-router-devtools": "^1.114.27", - "@tanstack/router-plugin": "^1.114.27", - "@types/node": "^22.13.13", - "@types/react": "^19.0.12", - "@types/react-dom": "^19.0.4", - "@vitejs/plugin-react": "^4.3.4", - "dotenv": "^16.4.7", - "open": "^10.1.0", - "postcss": "^8.5.3", - "postcss-preset-mantine": "^1.17.0", - "postcss-simple-vars": "^7.0.1", - "typescript": "^5", - "vite": "^6.2.3" + "@biomejs/biome": "2.4.7", + "@proofkit/typegen": "^1.1.0-beta.16", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^5.2.0", + "dotenv": "^17.3.1", + "open": "^11.0.0", + "typescript": "^5.9.3", + "vite": "^7.3.1", + "vite-plugin-singlefile": "^2.3.2" } } diff --git a/packages/cli/template/vite-wv/pnpm-lock.yaml b/packages/cli/template/vite-wv/pnpm-lock.yaml deleted file mode 100644 index c96e659d..00000000 --- a/packages/cli/template/vite-wv/pnpm-lock.yaml +++ /dev/null @@ -1,2294 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@mantine/core': - specifier: ^7.14.0 - version: 7.14.0(@mantine/hooks@7.14.0(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mantine/hooks': - specifier: ^7.14.0 - version: 7.14.0(react@18.3.1) - '@mantine/modals': - specifier: ^7.14.0 - version: 7.14.0(@mantine/core@7.14.0(@mantine/hooks@7.14.0(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@7.14.0(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mantine/notifications': - specifier: ^7.14.0 - version: 7.14.0(@mantine/core@7.14.0(@mantine/hooks@7.14.0(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@7.14.0(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@tabler/icons-react': - specifier: ^3.22.0 - version: 3.22.0(react@18.3.1) - '@tanstack/react-router': - specifier: ^1.81.6 - version: 1.81.6(@tanstack/router-generator@1.81.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@tanstack/router-devtools': - specifier: ^1.81.6 - version: 1.81.6(@tanstack/react-router@1.81.6(@tanstack/router-generator@1.81.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(csstype@3.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react: - specifier: ^18.3.1 - version: 18.3.1 - react-dom: - specifier: ^18.3.1 - version: 18.3.1(react@18.3.1) - vite-plugin-singlefile: - specifier: ^2.0.3 - version: 2.0.3(rollup@4.26.0)(vite@5.4.11(@types/node@22.9.0)(sugarss@4.0.1(postcss@8.4.49))) - devDependencies: - '@tanstack/router-plugin': - specifier: ^1.81.6 - version: 1.81.6(vite@5.4.11(@types/node@22.9.0)(sugarss@4.0.1(postcss@8.4.49))) - '@types/node': - specifier: ^22.9.0 - version: 22.9.0 - '@types/react': - specifier: ^18.3.3 - version: 18.3.12 - '@types/react-dom': - specifier: ^18.3.0 - version: 18.3.1 - '@vitejs/plugin-react': - specifier: ^4.3.2 - version: 4.3.3(vite@5.4.11(@types/node@22.9.0)(sugarss@4.0.1(postcss@8.4.49))) - dotenv: - specifier: ^16.4.5 - version: 16.4.5 - open: - specifier: ^10.1.0 - version: 10.1.0 - postcss: - specifier: ^8.4.49 - version: 8.4.49 - postcss-preset-mantine: - specifier: ^1.17.0 - version: 1.17.0(postcss@8.4.49) - postcss-simple-vars: - specifier: ^7.0.1 - version: 7.0.1(postcss@8.4.49) - vite: - specifier: ^5.4.8 - version: 5.4.11(@types/node@22.9.0)(sugarss@4.0.1(postcss@8.4.49)) - -packages: - - '@ampproject/remapping@2.3.0': - resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} - engines: {node: '>=6.0.0'} - - '@babel/code-frame@7.26.2': - resolution: {integrity: sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.26.2': - resolution: {integrity: sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.26.0': - resolution: {integrity: sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.26.2': - resolution: {integrity: sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.25.9': - resolution: {integrity: sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.25.9': - resolution: {integrity: sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.26.0': - resolution: {integrity: sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-plugin-utils@7.25.9': - resolution: {integrity: sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.25.9': - resolution: {integrity: sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.25.9': - resolution: {integrity: sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.25.9': - resolution: {integrity: sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.26.0': - resolution: {integrity: sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.26.2': - resolution: {integrity: sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/plugin-syntax-jsx@7.25.9': - resolution: {integrity: sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-typescript@7.25.9': - resolution: {integrity: sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx-self@7.25.9': - resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-react-jsx-source@7.25.9': - resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/runtime@7.26.0': - resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} - engines: {node: '>=6.9.0'} - - '@babel/template@7.25.9': - resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.25.9': - resolution: {integrity: sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.26.0': - resolution: {integrity: sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==} - engines: {node: '>=6.9.0'} - - '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - - '@esbuild/aix-ppc64@0.23.1': - resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm64@0.23.1': - resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.23.1': - resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.23.1': - resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.23.1': - resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.23.1': - resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-arm64@0.23.1': - resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.23.1': - resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm64@0.23.1': - resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.23.1': - resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.23.1': - resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.23.1': - resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.23.1': - resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.23.1': - resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.23.1': - resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.23.1': - resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@esbuild/linux-x64@0.23.1': - resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.23.1': - resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.23.1': - resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.23.1': - resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.23.1': - resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.23.1': - resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.23.1': - resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.23.1': - resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@floating-ui/core@1.6.8': - resolution: {integrity: sha512-7XJ9cPU+yI2QeLS+FCSlqNFZJq8arvswefkZrYI1yQBbftw6FyrZOxYSh+9S7z7TpeWlRt9zJ5IhM1WIL334jA==} - - '@floating-ui/dom@1.6.12': - resolution: {integrity: sha512-NP83c0HjokcGVEMeoStg317VD9W7eDlGK7457dMBANbKA6GJZdc7rjujdgqzTaz93jkGgc5P/jeWbaCHnMNc+w==} - - '@floating-ui/react-dom@2.1.2': - resolution: {integrity: sha512-06okr5cgPzMNBy+Ycse2A6udMi4bqwW/zgBF/rwjcNqWkyr82Mcg8b0vjX8OJpZFy/FKjJmw6wV7t44kK6kW7A==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - - '@floating-ui/react@0.26.27': - resolution: {integrity: sha512-jLP72x0Kr2CgY6eTYi/ra3VA9LOkTo4C+DUTrbFgFOExKy3omYVmwMjNKqxAHdsnyLS96BIDLcO2SlnsNf8KUQ==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - - '@floating-ui/utils@0.2.8': - resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==} - - '@jridgewell/gen-mapping@0.3.5': - resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} - engines: {node: '>=6.0.0'} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/set-array@1.2.1': - resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.0': - resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} - - '@jridgewell/trace-mapping@0.3.25': - resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - - '@mantine/core@7.14.0': - resolution: {integrity: sha512-Osj3nwCXFhOVHIoDtpEpciP7huPhGmG/0w+Zol5tKJ9SG5trV4NDfdFwFcNoxx5al8L6eCLS/fJhloFXaqhOnA==} - peerDependencies: - '@mantine/hooks': 7.14.0 - react: ^18.x || ^19.x - react-dom: ^18.x || ^19.x - - '@mantine/hooks@7.14.0': - resolution: {integrity: sha512-BJ577AoQ5KnvbuaG174TYAmL2UqcX9qh9aL0aOx+gqyMM6GWeBXUXWx1kcMCzaDbYZwfQptU476fpSjHdcLjMw==} - peerDependencies: - react: ^18.x || ^19.x - - '@mantine/modals@7.14.0': - resolution: {integrity: sha512-+9NeQnvH3dARoPXqJyq68FUt8+YWf/n1iNGM6ssbysrK2RMRZd/+XjdhRwGVi464Lg4UUT/QKbMos9CGrNxj9A==} - peerDependencies: - '@mantine/core': 7.14.0 - '@mantine/hooks': 7.14.0 - react: ^18.x || ^19.x - react-dom: ^18.x || ^19.x - - '@mantine/notifications@7.14.0': - resolution: {integrity: sha512-CEpGRYj7xtzYQ8VLHN+tespWkTH3U1ghHKxKeJNmfcSjf7VcMbTa0LlKgszUE6A4CZma32RsxhDXdvgcQ3xmUg==} - peerDependencies: - '@mantine/core': 7.14.0 - '@mantine/hooks': 7.14.0 - react: ^18.x || ^19.x - react-dom: ^18.x || ^19.x - - '@mantine/store@7.14.0': - resolution: {integrity: sha512-qI0XnQZkHuWYbe9Mn6kFObka4x26RINnDpyJGSiK6on+VwDWGJ3gn1dfFlQa2zboVtA6OUXHyxDlwALHNJwiZw==} - peerDependencies: - react: ^18.x || ^19.x - - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - - '@rollup/rollup-android-arm-eabi@4.26.0': - resolution: {integrity: sha512-gJNwtPDGEaOEgejbaseY6xMFu+CPltsc8/T+diUTTbOQLqD+bnrJq9ulH6WD69TqwqWmrfRAtUv30cCFZlbGTQ==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.26.0': - resolution: {integrity: sha512-YJa5Gy8mEZgz5JquFruhJODMq3lTHWLm1fOy+HIANquLzfIOzE9RA5ie3JjCdVb9r46qfAQY/l947V0zfGJ0OQ==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.26.0': - resolution: {integrity: sha512-ErTASs8YKbqTBoPLp/kA1B1Um5YSom8QAc4rKhg7b9tyyVqDBlQxy7Bf2wW7yIlPGPg2UODDQcbkTlruPzDosw==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.26.0': - resolution: {integrity: sha512-wbgkYDHcdWW+NqP2mnf2NOuEbOLzDblalrOWcPyY6+BRbVhliavon15UploG7PpBRQ2bZJnbmh8o3yLoBvDIHA==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.26.0': - resolution: {integrity: sha512-Y9vpjfp9CDkAG4q/uwuhZk96LP11fBz/bYdyg9oaHYhtGZp7NrbkQrj/66DYMMP2Yo/QPAsVHkV891KyO52fhg==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.26.0': - resolution: {integrity: sha512-A/jvfCZ55EYPsqeaAt/yDAG4q5tt1ZboWMHEvKAH9Zl92DWvMIbnZe/f/eOXze65aJaaKbL+YeM0Hz4kLQvdwg==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.26.0': - resolution: {integrity: sha512-paHF1bMXKDuizaMODm2bBTjRiHxESWiIyIdMugKeLnjuS1TCS54MF5+Y5Dx8Ui/1RBPVRE09i5OUlaLnv8OGnA==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.26.0': - resolution: {integrity: sha512-cwxiHZU1GAs+TMxvgPfUDtVZjdBdTsQwVnNlzRXC5QzIJ6nhfB4I1ahKoe9yPmoaA/Vhf7m9dB1chGPpDRdGXg==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.26.0': - resolution: {integrity: sha512-4daeEUQutGRCW/9zEo8JtdAgtJ1q2g5oHaoQaZbMSKaIWKDQwQ3Yx0/3jJNmpzrsScIPtx/V+1AfibLisb3AMQ==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-musl@4.26.0': - resolution: {integrity: sha512-eGkX7zzkNxvvS05ROzJ/cO/AKqNvR/7t1jA3VZDi2vRniLKwAWxUr85fH3NsvtxU5vnUUKFHKh8flIBdlo2b3Q==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-powerpc64le-gnu@4.26.0': - resolution: {integrity: sha512-Odp/lgHbW/mAqw/pU21goo5ruWsytP7/HCC/liOt0zcGG0llYWKrd10k9Fj0pdj3prQ63N5yQLCLiE7HTX+MYw==} - cpu: [ppc64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.26.0': - resolution: {integrity: sha512-MBR2ZhCTzUgVD0OJdTzNeF4+zsVogIR1U/FsyuFerwcqjZGvg2nYe24SAHp8O5sN8ZkRVbHwlYeHqcSQ8tcYew==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.26.0': - resolution: {integrity: sha512-YYcg8MkbN17fMbRMZuxwmxWqsmQufh3ZJFxFGoHjrE7bv0X+T6l3glcdzd7IKLiwhT+PZOJCblpnNlz1/C3kGQ==} - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-x64-gnu@4.26.0': - resolution: {integrity: sha512-ZuwpfjCwjPkAOxpjAEjabg6LRSfL7cAJb6gSQGZYjGhadlzKKywDkCUnJ+KEfrNY1jH5EEoSIKLCb572jSiglA==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-musl@4.26.0': - resolution: {integrity: sha512-+HJD2lFS86qkeF8kNu0kALtifMpPCZU80HvwztIKnYwym3KnA1os6nsX4BGSTLtS2QVAGG1P3guRgsYyMA0Yhg==} - cpu: [x64] - os: [linux] - - '@rollup/rollup-win32-arm64-msvc@4.26.0': - resolution: {integrity: sha512-WUQzVFWPSw2uJzX4j6YEbMAiLbs0BUysgysh8s817doAYhR5ybqTI1wtKARQKo6cGop3pHnrUJPFCsXdoFaimQ==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.26.0': - resolution: {integrity: sha512-D4CxkazFKBfN1akAIY6ieyOqzoOoBV1OICxgUblWxff/pSjCA2khXlASUx7mK6W1oP4McqhgcCsu6QaLj3WMWg==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.26.0': - resolution: {integrity: sha512-2x8MO1rm4PGEP0xWbubJW5RtbNLk3puzAMaLQd3B3JHVw4KcHlmXcO+Wewx9zCoo7EUFiMlu/aZbCJ7VjMzAag==} - cpu: [x64] - os: [win32] - - '@tabler/icons-react@3.22.0': - resolution: {integrity: sha512-pOnn+IqZpnkYsEKRvbXXLXwXhYwg4cy1fEVr5SRrgAYJXkobpDjFTdVHlab0HEBXY5AE1NjsMlVeK6H/8Vv2uQ==} - peerDependencies: - react: '>= 16' - - '@tabler/icons@3.22.0': - resolution: {integrity: sha512-IfgGzhFph5OBr2wTieWL/hyAs0FThnq9O155a6kfGYxqx7h5LQw91wnRswhEaGhXCcfmR7ZVDUr9H+x4b9Pb8g==} - - '@tanstack/history@1.81.6': - resolution: {integrity: sha512-ZbqZszXEU5dyNCjpo9hvXeJKUS+n4wlgpym+b2fIPSwV3yVLC3M34MVK5fmmUcrRFKpBT9vgInMNoL2OZ5U3XQ==} - engines: {node: '>=12'} - - '@tanstack/react-router@1.81.6': - resolution: {integrity: sha512-Vl8CoWQTcf5+0MVeH6fuZYEOmSbIkQ7Q5oV9EuIejifBDlQStkGfeaGIguZ/3iXxZFpJ2kIgK4JzH966+xkK5g==} - engines: {node: '>=12'} - peerDependencies: - '@tanstack/router-generator': 1.81.6 - react: '>=18' - react-dom: '>=18' - peerDependenciesMeta: - '@tanstack/router-generator': - optional: true - - '@tanstack/react-store@0.5.6': - resolution: {integrity: sha512-SitIpS5jTj28DajjLpWbIX+YetmJL+6PRY0DKKiCGBKfYIqj3ryODQYF3jB3SNoR9ifUA/jFkqbJdBKFtWd+AQ==} - peerDependencies: - react: ^17.0.0 || ^18.0.0 - react-dom: ^17.0.0 || ^18.0.0 - - '@tanstack/router-devtools@1.81.6': - resolution: {integrity: sha512-GzLSGgR32o+uhk5dPx3rNZRYllQY+06743Poyx+uB+/NF/L88YJxVKynG/D89q4t6ZbiCwv3Ys6pFVmVcSpkLA==} - engines: {node: '>=12'} - peerDependencies: - '@tanstack/react-router': ^1.81.6 - react: '>=18' - react-dom: '>=18' - - '@tanstack/router-generator@1.81.6': - resolution: {integrity: sha512-8UXYMQ3ADDdCY4X7gcOJRMz2hGPrCTfPzobzIkzRjCKPnJ6L3slWXjeJ3WCGp6vavE2zlKIGzLXNbjCigb6fig==} - engines: {node: '>=12'} - - '@tanstack/router-plugin@1.81.6': - resolution: {integrity: sha512-QHTn5C2EyK8VgGJRgIKH5T0+W5Wqhi1bueLXKOyESdrsEK0ko95e8uizw4LEm7bqwl6SBtW+MSj8opNzJepY2w==} - engines: {node: '>=12'} - peerDependencies: - '@rsbuild/core': '>=1.0.2' - vite: '>=5.0.0' - webpack: '>=5.92.0' - peerDependenciesMeta: - '@rsbuild/core': - optional: true - vite: - optional: true - webpack: - optional: true - - '@tanstack/store@0.5.5': - resolution: {integrity: sha512-EOSrgdDAJExbvRZEQ/Xhh9iZchXpMN+ga1Bnk8Nmygzs8TfiE6hbzThF+Pr2G19uHL6+DTDTHhJ8VQiOd7l4tA==} - - '@tanstack/virtual-file-routes@1.81.6': - resolution: {integrity: sha512-HhVZ42j2ciER0tga+s7lsQ6bBvJ3vvMg72aHUy2vZunwR16NVCJysDEWBBR7ln3rZak5JWijGEDHoyT2XjsWjA==} - engines: {node: '>=12'} - - '@types/babel__core@7.20.5': - resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} - - '@types/babel__generator@7.6.8': - resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==} - - '@types/babel__template@7.4.4': - resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} - - '@types/babel__traverse@7.20.6': - resolution: {integrity: sha512-r1bzfrm0tomOI8g1SzvCaQHo6Lcv6zu0EA+W2kHrt8dyrHQxGzBBL4kdkzIS+jBMV+EYcMAEAqXqYaLJq5rOZg==} - - '@types/estree@1.0.6': - resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} - - '@types/node@22.9.0': - resolution: {integrity: sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==} - - '@types/prop-types@15.7.13': - resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} - - '@types/react-dom@18.3.1': - resolution: {integrity: sha512-qW1Mfv8taImTthu4KoXgDfLuk4bydU6Q/TkADnDWWHwi4NX4BR+LWfTp2sVmTqRrsHvyDDTelgelxJ+SsejKKQ==} - - '@types/react@18.3.12': - resolution: {integrity: sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==} - - '@vitejs/plugin-react@4.3.3': - resolution: {integrity: sha512-NooDe9GpHGqNns1i8XDERg0Vsg5SSYRhRxxyTGogUdkdNt47jal+fbuYi+Yfq6pzRCKXyoPcWisfxE6RIM3GKA==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - vite: ^4.2.0 || ^5.0.0 - - acorn@8.14.0: - resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} - engines: {node: '>=0.4.0'} - hasBin: true - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - - babel-dead-code-elimination@1.0.6: - resolution: {integrity: sha512-JxFi9qyRJpN0LjEbbjbN8g0ux71Qppn9R8Qe3k6QzHg2CaKsbUQtbn307LQGiDLGjV6JCtEFqfxzVig9MyDCHQ==} - - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - - browserslist@4.24.2: - resolution: {integrity: sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - bundle-name@4.1.0: - resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} - engines: {node: '>=18'} - - camelcase-css@2.0.1: - resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} - engines: {node: '>= 6'} - - caniuse-lite@1.0.30001680: - resolution: {integrity: sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==} - - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - - clsx@2.1.1: - resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} - engines: {node: '>=6'} - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - - csstype@3.1.3: - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - - debug@4.3.7: - resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - default-browser-id@5.0.0: - resolution: {integrity: sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA==} - engines: {node: '>=18'} - - default-browser@5.2.1: - resolution: {integrity: sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg==} - engines: {node: '>=18'} - - define-lazy-prop@3.0.0: - resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} - engines: {node: '>=12'} - - detect-node-es@1.1.0: - resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - - dom-helpers@5.2.1: - resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} - - dotenv@16.4.5: - resolution: {integrity: sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==} - engines: {node: '>=12'} - - electron-to-chromium@1.5.58: - resolution: {integrity: sha512-al2l4r+24ZFL7WzyPTlyD0fC33LLzvxqLCwurtBibVPghRGO9hSTl+tis8t1kD7biPiH/en4U0I7o/nQbYeoVA==} - - esbuild@0.21.5: - resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} - engines: {node: '>=12'} - hasBin: true - - esbuild@0.23.1: - resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} - engines: {node: '>=18'} - hasBin: true - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - fast-glob@3.3.2: - resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} - engines: {node: '>=8.6.0'} - - fastq@1.17.1: - resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} - - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - get-nonce@1.0.1: - resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} - engines: {node: '>=6'} - - get-tsconfig@4.8.1: - resolution: {integrity: sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} - - goober@2.1.16: - resolution: {integrity: sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==} - peerDependencies: - csstype: ^3.0.10 - - invariant@2.2.4: - resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} - - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - - is-docker@3.0.0: - resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - hasBin: true - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-inside-container@1.0.0: - resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} - engines: {node: '>=14.16'} - hasBin: true - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-wsl@3.1.0: - resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} - engines: {node: '>=16'} - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - jsesc@3.0.2: - resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} - engines: {node: '>=6'} - hasBin: true - - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - nanoid@3.3.7: - resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - node-releases@2.0.18: - resolution: {integrity: sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==} - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - open@10.1.0: - resolution: {integrity: sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw==} - engines: {node: '>=18'} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - postcss-js@4.0.1: - resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} - engines: {node: ^12 || ^14 || >= 16} - peerDependencies: - postcss: ^8.4.21 - - postcss-mixins@9.0.4: - resolution: {integrity: sha512-XVq5jwQJDRu5M1XGkdpgASqLk37OqkH4JCFDXl/Dn7janOJjCTEKL+36cnRVy7bMtoBzALfO7bV7nTIsFnUWLA==} - engines: {node: '>=14.0'} - peerDependencies: - postcss: ^8.2.14 - - postcss-nested@6.2.0: - resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} - engines: {node: '>=12.0'} - peerDependencies: - postcss: ^8.2.14 - - postcss-preset-mantine@1.17.0: - resolution: {integrity: sha512-ji1PMDBUf2Vsx/HE5faMSs1+ff6qE6YRulTr4Ja+6HD3gop8rSMTCYdpN7KrdsEg079kfBKkO/PaKhG9uR0zwQ==} - peerDependencies: - postcss: '>=8.0.0' - - postcss-selector-parser@6.1.2: - resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} - engines: {node: '>=4'} - - postcss-simple-vars@7.0.1: - resolution: {integrity: sha512-5GLLXaS8qmzHMOjVxqkk1TZPf1jMqesiI7qLhnlyERalG0sMbHIbJqrcnrpmZdKCLglHnRHoEBB61RtGTsj++A==} - engines: {node: '>=14.0'} - peerDependencies: - postcss: ^8.2.1 - - postcss@8.4.49: - resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} - engines: {node: ^10 || ^12 || >=14} - - prettier@3.3.3: - resolution: {integrity: sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==} - engines: {node: '>=14'} - hasBin: true - - prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - - react-dom@18.3.1: - resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} - peerDependencies: - react: ^18.3.1 - - react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - - react-number-format@5.4.2: - resolution: {integrity: sha512-cg//jVdS49PYDgmcYoBnMMHl4XNTMuV723ZnHD2aXYtWWWqbVF3hjQ8iB+UZEuXapLbeA8P8H+1o6ZB1lcw3vg==} - peerDependencies: - react: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 - react-dom: ^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 - - react-refresh@0.14.2: - resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} - engines: {node: '>=0.10.0'} - - react-remove-scroll-bar@2.3.6: - resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - react-remove-scroll@2.6.0: - resolution: {integrity: sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - react-style-singleton@2.2.1: - resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - react-textarea-autosize@8.5.4: - resolution: {integrity: sha512-eSSjVtRLcLfFwFcariT77t9hcbVJHQV76b51QjQGarQIHml2+gM2lms0n3XrhnDmgK5B+/Z7TmQk5OHNzqYm/A==} - engines: {node: '>=10'} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - - react-transition-group@4.4.5: - resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} - peerDependencies: - react: '>=16.6.0' - react-dom: '>=16.6.0' - - react@18.3.1: - resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} - engines: {node: '>=0.10.0'} - - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - - regenerator-runtime@0.14.1: - resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} - - resolve-pkg-maps@1.0.0: - resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - - reusify@1.0.4: - resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - - rollup@4.26.0: - resolution: {integrity: sha512-ilcl12hnWonG8f+NxU6BlgysVA0gvY2l8N0R84S1HcINbW20bvwuCngJkkInV6LXhwRpucsW5k1ovDwEdBVrNg==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - run-applescript@7.0.0: - resolution: {integrity: sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A==} - engines: {node: '>=18'} - - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - - scheduler@0.23.2: - resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} - - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - sugarss@4.0.1: - resolution: {integrity: sha512-WCjS5NfuVJjkQzK10s8WOBY+hhDxxNt/N6ZaGwxFZ+wN3/lKKFSaaKUNecULcTTvE4urLcKaZFQD8vO0mOZujw==} - engines: {node: '>=12.0'} - peerDependencies: - postcss: ^8.3.3 - - tabbable@6.2.0: - resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} - - tiny-invariant@1.3.3: - resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - - tiny-warning@1.0.3: - resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tsx@4.19.2: - resolution: {integrity: sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==} - engines: {node: '>=18.0.0'} - hasBin: true - - type-fest@4.26.1: - resolution: {integrity: sha512-yOGpmOAL7CkKe/91I5O3gPICmJNLJ1G4zFYVAsRHg7M64biSnPtRj0WNQt++bRkjYOqjWXrhnUw1utzmVErAdg==} - engines: {node: '>=16'} - - undici-types@6.19.8: - resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} - - unplugin@1.16.0: - resolution: {integrity: sha512-5liCNPuJW8dqh3+DM6uNM2EI3MLLpCKp/KY+9pB5M2S2SR2qvvDHhKgBOaTWEbZTAws3CXfB0rKTIolWKL05VQ==} - engines: {node: '>=14.0.0'} - - update-browserslist-db@1.1.1: - resolution: {integrity: sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - - use-callback-ref@1.3.2: - resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - use-composed-ref@1.3.0: - resolution: {integrity: sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - - use-isomorphic-layout-effect@1.1.2: - resolution: {integrity: sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - use-latest@1.2.1: - resolution: {integrity: sha512-xA+AVm/Wlg3e2P/JiItTziwS7FK92LWrDB0p+hgXloIMuVCeJJ8v6f0eeHyPZaJrM+usM1FkFfbNCrJGs8A/zw==} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - use-sidecar@1.1.2: - resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0 - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - use-sync-external-store@1.2.2: - resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - vite-plugin-singlefile@2.0.3: - resolution: {integrity: sha512-OEBEwdX8nCGPSdtaB1D7rryYnT+YfPTS8ojL1TDyeUF+bWDCTfRriQqw6T0vl9EbKI/KMg7szN3awst6cLrKkA==} - engines: {node: '>18.0.0'} - peerDependencies: - rollup: ^4.24.3 - vite: ^5.4.10 - - vite@5.4.11: - resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - peerDependencies: - '@types/node': ^18.0.0 || >=20.0.0 - less: '*' - lightningcss: ^1.21.0 - sass: '*' - sass-embedded: '*' - stylus: '*' - sugarss: '*' - terser: ^5.4.0 - peerDependenciesMeta: - '@types/node': - optional: true - less: - optional: true - lightningcss: - optional: true - sass: - optional: true - sass-embedded: - optional: true - stylus: - optional: true - sugarss: - optional: true - terser: - optional: true - - webpack-virtual-modules@0.6.2: - resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} - - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - - zod@3.23.8: - resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} - -snapshots: - - '@ampproject/remapping@2.3.0': - dependencies: - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - - '@babel/code-frame@7.26.2': - dependencies: - '@babel/helper-validator-identifier': 7.25.9 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/compat-data@7.26.2': {} - - '@babel/core@7.26.0': - dependencies: - '@ampproject/remapping': 2.3.0 - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.2 - '@babel/helper-compilation-targets': 7.25.9 - '@babel/helper-module-transforms': 7.26.0(@babel/core@7.26.0) - '@babel/helpers': 7.26.0 - '@babel/parser': 7.26.2 - '@babel/template': 7.25.9 - '@babel/traverse': 7.25.9 - '@babel/types': 7.26.0 - convert-source-map: 2.0.0 - debug: 4.3.7 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/generator@7.26.2': - dependencies: - '@babel/parser': 7.26.2 - '@babel/types': 7.26.0 - '@jridgewell/gen-mapping': 0.3.5 - '@jridgewell/trace-mapping': 0.3.25 - jsesc: 3.0.2 - - '@babel/helper-compilation-targets@7.25.9': - dependencies: - '@babel/compat-data': 7.26.2 - '@babel/helper-validator-option': 7.25.9 - browserslist: 4.24.2 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-module-imports@7.25.9': - dependencies: - '@babel/traverse': 7.25.9 - '@babel/types': 7.26.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.26.0(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 - '@babel/helper-module-imports': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 - '@babel/traverse': 7.25.9 - transitivePeerDependencies: - - supports-color - - '@babel/helper-plugin-utils@7.25.9': {} - - '@babel/helper-string-parser@7.25.9': {} - - '@babel/helper-validator-identifier@7.25.9': {} - - '@babel/helper-validator-option@7.25.9': {} - - '@babel/helpers@7.26.0': - dependencies: - '@babel/template': 7.25.9 - '@babel/types': 7.26.0 - - '@babel/parser@7.26.2': - dependencies: - '@babel/types': 7.26.0 - - '@babel/plugin-syntax-jsx@7.25.9(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 - - '@babel/plugin-syntax-typescript@7.25.9(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 - - '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 - - '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.0)': - dependencies: - '@babel/core': 7.26.0 - '@babel/helper-plugin-utils': 7.25.9 - - '@babel/runtime@7.26.0': - dependencies: - regenerator-runtime: 0.14.1 - - '@babel/template@7.25.9': - dependencies: - '@babel/code-frame': 7.26.2 - '@babel/parser': 7.26.2 - '@babel/types': 7.26.0 - - '@babel/traverse@7.25.9': - dependencies: - '@babel/code-frame': 7.26.2 - '@babel/generator': 7.26.2 - '@babel/parser': 7.26.2 - '@babel/template': 7.25.9 - '@babel/types': 7.26.0 - debug: 4.3.7 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - - '@babel/types@7.26.0': - dependencies: - '@babel/helper-string-parser': 7.25.9 - '@babel/helper-validator-identifier': 7.25.9 - - '@esbuild/aix-ppc64@0.21.5': - optional: true - - '@esbuild/aix-ppc64@0.23.1': - optional: true - - '@esbuild/android-arm64@0.21.5': - optional: true - - '@esbuild/android-arm64@0.23.1': - optional: true - - '@esbuild/android-arm@0.21.5': - optional: true - - '@esbuild/android-arm@0.23.1': - optional: true - - '@esbuild/android-x64@0.21.5': - optional: true - - '@esbuild/android-x64@0.23.1': - optional: true - - '@esbuild/darwin-arm64@0.21.5': - optional: true - - '@esbuild/darwin-arm64@0.23.1': - optional: true - - '@esbuild/darwin-x64@0.21.5': - optional: true - - '@esbuild/darwin-x64@0.23.1': - optional: true - - '@esbuild/freebsd-arm64@0.21.5': - optional: true - - '@esbuild/freebsd-arm64@0.23.1': - optional: true - - '@esbuild/freebsd-x64@0.21.5': - optional: true - - '@esbuild/freebsd-x64@0.23.1': - optional: true - - '@esbuild/linux-arm64@0.21.5': - optional: true - - '@esbuild/linux-arm64@0.23.1': - optional: true - - '@esbuild/linux-arm@0.21.5': - optional: true - - '@esbuild/linux-arm@0.23.1': - optional: true - - '@esbuild/linux-ia32@0.21.5': - optional: true - - '@esbuild/linux-ia32@0.23.1': - optional: true - - '@esbuild/linux-loong64@0.21.5': - optional: true - - '@esbuild/linux-loong64@0.23.1': - optional: true - - '@esbuild/linux-mips64el@0.21.5': - optional: true - - '@esbuild/linux-mips64el@0.23.1': - optional: true - - '@esbuild/linux-ppc64@0.21.5': - optional: true - - '@esbuild/linux-ppc64@0.23.1': - optional: true - - '@esbuild/linux-riscv64@0.21.5': - optional: true - - '@esbuild/linux-riscv64@0.23.1': - optional: true - - '@esbuild/linux-s390x@0.21.5': - optional: true - - '@esbuild/linux-s390x@0.23.1': - optional: true - - '@esbuild/linux-x64@0.21.5': - optional: true - - '@esbuild/linux-x64@0.23.1': - optional: true - - '@esbuild/netbsd-x64@0.21.5': - optional: true - - '@esbuild/netbsd-x64@0.23.1': - optional: true - - '@esbuild/openbsd-arm64@0.23.1': - optional: true - - '@esbuild/openbsd-x64@0.21.5': - optional: true - - '@esbuild/openbsd-x64@0.23.1': - optional: true - - '@esbuild/sunos-x64@0.21.5': - optional: true - - '@esbuild/sunos-x64@0.23.1': - optional: true - - '@esbuild/win32-arm64@0.21.5': - optional: true - - '@esbuild/win32-arm64@0.23.1': - optional: true - - '@esbuild/win32-ia32@0.21.5': - optional: true - - '@esbuild/win32-ia32@0.23.1': - optional: true - - '@esbuild/win32-x64@0.21.5': - optional: true - - '@esbuild/win32-x64@0.23.1': - optional: true - - '@floating-ui/core@1.6.8': - dependencies: - '@floating-ui/utils': 0.2.8 - - '@floating-ui/dom@1.6.12': - dependencies: - '@floating-ui/core': 1.6.8 - '@floating-ui/utils': 0.2.8 - - '@floating-ui/react-dom@2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@floating-ui/dom': 1.6.12 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@floating-ui/react@0.26.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@floating-ui/react-dom': 2.1.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@floating-ui/utils': 0.2.8 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - tabbable: 6.2.0 - - '@floating-ui/utils@0.2.8': {} - - '@jridgewell/gen-mapping@0.3.5': - dependencies: - '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 - '@jridgewell/trace-mapping': 0.3.25 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/set-array@1.2.1': {} - - '@jridgewell/sourcemap-codec@1.5.0': {} - - '@jridgewell/trace-mapping@0.3.25': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.0 - - '@mantine/core@7.14.0(@mantine/hooks@7.14.0(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@floating-ui/react': 0.26.27(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mantine/hooks': 7.14.0(react@18.3.1) - clsx: 2.1.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-number-format: 5.4.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - react-remove-scroll: 2.6.0(@types/react@18.3.12)(react@18.3.1) - react-textarea-autosize: 8.5.4(@types/react@18.3.12)(react@18.3.1) - type-fest: 4.26.1 - transitivePeerDependencies: - - '@types/react' - - '@mantine/hooks@7.14.0(react@18.3.1)': - dependencies: - react: 18.3.1 - - '@mantine/modals@7.14.0(@mantine/core@7.14.0(@mantine/hooks@7.14.0(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@7.14.0(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@mantine/core': 7.14.0(@mantine/hooks@7.14.0(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mantine/hooks': 7.14.0(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@mantine/notifications@7.14.0(@mantine/core@7.14.0(@mantine/hooks@7.14.0(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mantine/hooks@7.14.0(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@mantine/core': 7.14.0(@mantine/hooks@7.14.0(react@18.3.1))(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mantine/hooks': 7.14.0(react@18.3.1) - '@mantine/store': 7.14.0(react@18.3.1) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - - '@mantine/store@7.14.0(react@18.3.1)': - dependencies: - react: 18.3.1 - - '@nodelib/fs.scandir@2.1.5': - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - '@nodelib/fs.stat@2.0.5': {} - - '@nodelib/fs.walk@1.2.8': - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.17.1 - - '@rollup/rollup-android-arm-eabi@4.26.0': - optional: true - - '@rollup/rollup-android-arm64@4.26.0': - optional: true - - '@rollup/rollup-darwin-arm64@4.26.0': - optional: true - - '@rollup/rollup-darwin-x64@4.26.0': - optional: true - - '@rollup/rollup-freebsd-arm64@4.26.0': - optional: true - - '@rollup/rollup-freebsd-x64@4.26.0': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.26.0': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.26.0': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.26.0': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.26.0': - optional: true - - '@rollup/rollup-linux-powerpc64le-gnu@4.26.0': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.26.0': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.26.0': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.26.0': - optional: true - - '@rollup/rollup-linux-x64-musl@4.26.0': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.26.0': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.26.0': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.26.0': - optional: true - - '@tabler/icons-react@3.22.0(react@18.3.1)': - dependencies: - '@tabler/icons': 3.22.0 - react: 18.3.1 - - '@tabler/icons@3.22.0': {} - - '@tanstack/history@1.81.6': {} - - '@tanstack/react-router@1.81.6(@tanstack/router-generator@1.81.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@tanstack/history': 1.81.6 - '@tanstack/react-store': 0.5.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - jsesc: 3.0.2 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - tiny-invariant: 1.3.3 - tiny-warning: 1.0.3 - optionalDependencies: - '@tanstack/router-generator': 1.81.6 - - '@tanstack/react-store@0.5.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@tanstack/store': 0.5.5 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - use-sync-external-store: 1.2.2(react@18.3.1) - - '@tanstack/router-devtools@1.81.6(@tanstack/react-router@1.81.6(@tanstack/router-generator@1.81.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(csstype@3.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@tanstack/react-router': 1.81.6(@tanstack/router-generator@1.81.6)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - clsx: 2.1.1 - goober: 2.1.16(csstype@3.1.3) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - transitivePeerDependencies: - - csstype - - '@tanstack/router-generator@1.81.6': - dependencies: - '@tanstack/virtual-file-routes': 1.81.6 - prettier: 3.3.3 - tsx: 4.19.2 - zod: 3.23.8 - - '@tanstack/router-plugin@1.81.6(vite@5.4.11(@types/node@22.9.0)(sugarss@4.0.1(postcss@8.4.49)))': - dependencies: - '@babel/core': 7.26.0 - '@babel/generator': 7.26.2 - '@babel/parser': 7.26.2 - '@babel/plugin-syntax-jsx': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-syntax-typescript': 7.25.9(@babel/core@7.26.0) - '@babel/template': 7.25.9 - '@babel/traverse': 7.25.9 - '@babel/types': 7.26.0 - '@tanstack/router-generator': 1.81.6 - '@tanstack/virtual-file-routes': 1.81.6 - '@types/babel__core': 7.20.5 - '@types/babel__generator': 7.6.8 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.20.6 - babel-dead-code-elimination: 1.0.6 - chokidar: 3.6.0 - unplugin: 1.16.0 - zod: 3.23.8 - optionalDependencies: - vite: 5.4.11(@types/node@22.9.0)(sugarss@4.0.1(postcss@8.4.49)) - transitivePeerDependencies: - - supports-color - - '@tanstack/store@0.5.5': {} - - '@tanstack/virtual-file-routes@1.81.6': {} - - '@types/babel__core@7.20.5': - dependencies: - '@babel/parser': 7.26.2 - '@babel/types': 7.26.0 - '@types/babel__generator': 7.6.8 - '@types/babel__template': 7.4.4 - '@types/babel__traverse': 7.20.6 - - '@types/babel__generator@7.6.8': - dependencies: - '@babel/types': 7.26.0 - - '@types/babel__template@7.4.4': - dependencies: - '@babel/parser': 7.26.2 - '@babel/types': 7.26.0 - - '@types/babel__traverse@7.20.6': - dependencies: - '@babel/types': 7.26.0 - - '@types/estree@1.0.6': {} - - '@types/node@22.9.0': - dependencies: - undici-types: 6.19.8 - - '@types/prop-types@15.7.13': {} - - '@types/react-dom@18.3.1': - dependencies: - '@types/react': 18.3.12 - - '@types/react@18.3.12': - dependencies: - '@types/prop-types': 15.7.13 - csstype: 3.1.3 - - '@vitejs/plugin-react@4.3.3(vite@5.4.11(@types/node@22.9.0)(sugarss@4.0.1(postcss@8.4.49)))': - dependencies: - '@babel/core': 7.26.0 - '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.0) - '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.0) - '@types/babel__core': 7.20.5 - react-refresh: 0.14.2 - vite: 5.4.11(@types/node@22.9.0)(sugarss@4.0.1(postcss@8.4.49)) - transitivePeerDependencies: - - supports-color - - acorn@8.14.0: {} - - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - - babel-dead-code-elimination@1.0.6: - dependencies: - '@babel/core': 7.26.0 - '@babel/parser': 7.26.2 - '@babel/traverse': 7.25.9 - '@babel/types': 7.26.0 - transitivePeerDependencies: - - supports-color - - binary-extensions@2.3.0: {} - - braces@3.0.3: - dependencies: - fill-range: 7.1.1 - - browserslist@4.24.2: - dependencies: - caniuse-lite: 1.0.30001680 - electron-to-chromium: 1.5.58 - node-releases: 2.0.18 - update-browserslist-db: 1.1.1(browserslist@4.24.2) - - bundle-name@4.1.0: - dependencies: - run-applescript: 7.0.0 - - camelcase-css@2.0.1: {} - - caniuse-lite@1.0.30001680: {} - - chokidar@3.6.0: - dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - - clsx@2.1.1: {} - - convert-source-map@2.0.0: {} - - cssesc@3.0.0: {} - - csstype@3.1.3: {} - - debug@4.3.7: - dependencies: - ms: 2.1.3 - - default-browser-id@5.0.0: {} - - default-browser@5.2.1: - dependencies: - bundle-name: 4.1.0 - default-browser-id: 5.0.0 - - define-lazy-prop@3.0.0: {} - - detect-node-es@1.1.0: {} - - dom-helpers@5.2.1: - dependencies: - '@babel/runtime': 7.26.0 - csstype: 3.1.3 - - dotenv@16.4.5: {} - - electron-to-chromium@1.5.58: {} - - esbuild@0.21.5: - optionalDependencies: - '@esbuild/aix-ppc64': 0.21.5 - '@esbuild/android-arm': 0.21.5 - '@esbuild/android-arm64': 0.21.5 - '@esbuild/android-x64': 0.21.5 - '@esbuild/darwin-arm64': 0.21.5 - '@esbuild/darwin-x64': 0.21.5 - '@esbuild/freebsd-arm64': 0.21.5 - '@esbuild/freebsd-x64': 0.21.5 - '@esbuild/linux-arm': 0.21.5 - '@esbuild/linux-arm64': 0.21.5 - '@esbuild/linux-ia32': 0.21.5 - '@esbuild/linux-loong64': 0.21.5 - '@esbuild/linux-mips64el': 0.21.5 - '@esbuild/linux-ppc64': 0.21.5 - '@esbuild/linux-riscv64': 0.21.5 - '@esbuild/linux-s390x': 0.21.5 - '@esbuild/linux-x64': 0.21.5 - '@esbuild/netbsd-x64': 0.21.5 - '@esbuild/openbsd-x64': 0.21.5 - '@esbuild/sunos-x64': 0.21.5 - '@esbuild/win32-arm64': 0.21.5 - '@esbuild/win32-ia32': 0.21.5 - '@esbuild/win32-x64': 0.21.5 - - esbuild@0.23.1: - optionalDependencies: - '@esbuild/aix-ppc64': 0.23.1 - '@esbuild/android-arm': 0.23.1 - '@esbuild/android-arm64': 0.23.1 - '@esbuild/android-x64': 0.23.1 - '@esbuild/darwin-arm64': 0.23.1 - '@esbuild/darwin-x64': 0.23.1 - '@esbuild/freebsd-arm64': 0.23.1 - '@esbuild/freebsd-x64': 0.23.1 - '@esbuild/linux-arm': 0.23.1 - '@esbuild/linux-arm64': 0.23.1 - '@esbuild/linux-ia32': 0.23.1 - '@esbuild/linux-loong64': 0.23.1 - '@esbuild/linux-mips64el': 0.23.1 - '@esbuild/linux-ppc64': 0.23.1 - '@esbuild/linux-riscv64': 0.23.1 - '@esbuild/linux-s390x': 0.23.1 - '@esbuild/linux-x64': 0.23.1 - '@esbuild/netbsd-x64': 0.23.1 - '@esbuild/openbsd-arm64': 0.23.1 - '@esbuild/openbsd-x64': 0.23.1 - '@esbuild/sunos-x64': 0.23.1 - '@esbuild/win32-arm64': 0.23.1 - '@esbuild/win32-ia32': 0.23.1 - '@esbuild/win32-x64': 0.23.1 - - escalade@3.2.0: {} - - fast-glob@3.3.2: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - - fastq@1.17.1: - dependencies: - reusify: 1.0.4 - - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - - fsevents@2.3.3: - optional: true - - gensync@1.0.0-beta.2: {} - - get-nonce@1.0.1: {} - - get-tsconfig@4.8.1: - dependencies: - resolve-pkg-maps: 1.0.0 - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - - globals@11.12.0: {} - - goober@2.1.16(csstype@3.1.3): - dependencies: - csstype: 3.1.3 - - invariant@2.2.4: - dependencies: - loose-envify: 1.4.0 - - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - - is-docker@3.0.0: {} - - is-extglob@2.1.1: {} - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - - is-inside-container@1.0.0: - dependencies: - is-docker: 3.0.0 - - is-number@7.0.0: {} - - is-wsl@3.1.0: - dependencies: - is-inside-container: 1.0.0 - - js-tokens@4.0.0: {} - - jsesc@3.0.2: {} - - json5@2.2.3: {} - - loose-envify@1.4.0: - dependencies: - js-tokens: 4.0.0 - - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 - - merge2@1.4.1: {} - - micromatch@4.0.8: - dependencies: - braces: 3.0.3 - picomatch: 2.3.1 - - ms@2.1.3: {} - - nanoid@3.3.7: {} - - node-releases@2.0.18: {} - - normalize-path@3.0.0: {} - - object-assign@4.1.1: {} - - open@10.1.0: - dependencies: - default-browser: 5.2.1 - define-lazy-prop: 3.0.0 - is-inside-container: 1.0.0 - is-wsl: 3.1.0 - - picocolors@1.1.1: {} - - picomatch@2.3.1: {} - - postcss-js@4.0.1(postcss@8.4.49): - dependencies: - camelcase-css: 2.0.1 - postcss: 8.4.49 - - postcss-mixins@9.0.4(postcss@8.4.49): - dependencies: - fast-glob: 3.3.2 - postcss: 8.4.49 - postcss-js: 4.0.1(postcss@8.4.49) - postcss-simple-vars: 7.0.1(postcss@8.4.49) - sugarss: 4.0.1(postcss@8.4.49) - - postcss-nested@6.2.0(postcss@8.4.49): - dependencies: - postcss: 8.4.49 - postcss-selector-parser: 6.1.2 - - postcss-preset-mantine@1.17.0(postcss@8.4.49): - dependencies: - postcss: 8.4.49 - postcss-mixins: 9.0.4(postcss@8.4.49) - postcss-nested: 6.2.0(postcss@8.4.49) - - postcss-selector-parser@6.1.2: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - - postcss-simple-vars@7.0.1(postcss@8.4.49): - dependencies: - postcss: 8.4.49 - - postcss@8.4.49: - dependencies: - nanoid: 3.3.7 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - prettier@3.3.3: {} - - prop-types@15.8.1: - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 - - queue-microtask@1.2.3: {} - - react-dom@18.3.1(react@18.3.1): - dependencies: - loose-envify: 1.4.0 - react: 18.3.1 - scheduler: 0.23.2 - - react-is@16.13.1: {} - - react-number-format@5.4.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - react-refresh@0.14.2: {} - - react-remove-scroll-bar@2.3.6(@types/react@18.3.12)(react@18.3.1): - dependencies: - react: 18.3.1 - react-style-singleton: 2.2.1(@types/react@18.3.12)(react@18.3.1) - tslib: 2.8.1 - optionalDependencies: - '@types/react': 18.3.12 - - react-remove-scroll@2.6.0(@types/react@18.3.12)(react@18.3.1): - dependencies: - react: 18.3.1 - react-remove-scroll-bar: 2.3.6(@types/react@18.3.12)(react@18.3.1) - react-style-singleton: 2.2.1(@types/react@18.3.12)(react@18.3.1) - tslib: 2.8.1 - use-callback-ref: 1.3.2(@types/react@18.3.12)(react@18.3.1) - use-sidecar: 1.1.2(@types/react@18.3.12)(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.12 - - react-style-singleton@2.2.1(@types/react@18.3.12)(react@18.3.1): - dependencies: - get-nonce: 1.0.1 - invariant: 2.2.4 - react: 18.3.1 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 18.3.12 - - react-textarea-autosize@8.5.4(@types/react@18.3.12)(react@18.3.1): - dependencies: - '@babel/runtime': 7.26.0 - react: 18.3.1 - use-composed-ref: 1.3.0(react@18.3.1) - use-latest: 1.2.1(@types/react@18.3.12)(react@18.3.1) - transitivePeerDependencies: - - '@types/react' - - react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@babel/runtime': 7.26.0 - dom-helpers: 5.2.1 - loose-envify: 1.4.0 - prop-types: 15.8.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - react@18.3.1: - dependencies: - loose-envify: 1.4.0 - - readdirp@3.6.0: - dependencies: - picomatch: 2.3.1 - - regenerator-runtime@0.14.1: {} - - resolve-pkg-maps@1.0.0: {} - - reusify@1.0.4: {} - - rollup@4.26.0: - dependencies: - '@types/estree': 1.0.6 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.26.0 - '@rollup/rollup-android-arm64': 4.26.0 - '@rollup/rollup-darwin-arm64': 4.26.0 - '@rollup/rollup-darwin-x64': 4.26.0 - '@rollup/rollup-freebsd-arm64': 4.26.0 - '@rollup/rollup-freebsd-x64': 4.26.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.26.0 - '@rollup/rollup-linux-arm-musleabihf': 4.26.0 - '@rollup/rollup-linux-arm64-gnu': 4.26.0 - '@rollup/rollup-linux-arm64-musl': 4.26.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.26.0 - '@rollup/rollup-linux-riscv64-gnu': 4.26.0 - '@rollup/rollup-linux-s390x-gnu': 4.26.0 - '@rollup/rollup-linux-x64-gnu': 4.26.0 - '@rollup/rollup-linux-x64-musl': 4.26.0 - '@rollup/rollup-win32-arm64-msvc': 4.26.0 - '@rollup/rollup-win32-ia32-msvc': 4.26.0 - '@rollup/rollup-win32-x64-msvc': 4.26.0 - fsevents: 2.3.3 - - run-applescript@7.0.0: {} - - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - - scheduler@0.23.2: - dependencies: - loose-envify: 1.4.0 - - semver@6.3.1: {} - - source-map-js@1.2.1: {} - - sugarss@4.0.1(postcss@8.4.49): - dependencies: - postcss: 8.4.49 - - tabbable@6.2.0: {} - - tiny-invariant@1.3.3: {} - - tiny-warning@1.0.3: {} - - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - - tslib@2.8.1: {} - - tsx@4.19.2: - dependencies: - esbuild: 0.23.1 - get-tsconfig: 4.8.1 - optionalDependencies: - fsevents: 2.3.3 - - type-fest@4.26.1: {} - - undici-types@6.19.8: {} - - unplugin@1.16.0: - dependencies: - acorn: 8.14.0 - webpack-virtual-modules: 0.6.2 - - update-browserslist-db@1.1.1(browserslist@4.24.2): - dependencies: - browserslist: 4.24.2 - escalade: 3.2.0 - picocolors: 1.1.1 - - use-callback-ref@1.3.2(@types/react@18.3.12)(react@18.3.1): - dependencies: - react: 18.3.1 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 18.3.12 - - use-composed-ref@1.3.0(react@18.3.1): - dependencies: - react: 18.3.1 - - use-isomorphic-layout-effect@1.1.2(@types/react@18.3.12)(react@18.3.1): - dependencies: - react: 18.3.1 - optionalDependencies: - '@types/react': 18.3.12 - - use-latest@1.2.1(@types/react@18.3.12)(react@18.3.1): - dependencies: - react: 18.3.1 - use-isomorphic-layout-effect: 1.1.2(@types/react@18.3.12)(react@18.3.1) - optionalDependencies: - '@types/react': 18.3.12 - - use-sidecar@1.1.2(@types/react@18.3.12)(react@18.3.1): - dependencies: - detect-node-es: 1.1.0 - react: 18.3.1 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 18.3.12 - - use-sync-external-store@1.2.2(react@18.3.1): - dependencies: - react: 18.3.1 - - util-deprecate@1.0.2: {} - - vite-plugin-singlefile@2.0.3(rollup@4.26.0)(vite@5.4.11(@types/node@22.9.0)(sugarss@4.0.1(postcss@8.4.49))): - dependencies: - micromatch: 4.0.8 - rollup: 4.26.0 - vite: 5.4.11(@types/node@22.9.0)(sugarss@4.0.1(postcss@8.4.49)) - - vite@5.4.11(@types/node@22.9.0)(sugarss@4.0.1(postcss@8.4.49)): - dependencies: - esbuild: 0.21.5 - postcss: 8.4.49 - rollup: 4.26.0 - optionalDependencies: - '@types/node': 22.9.0 - fsevents: 2.3.3 - sugarss: 4.0.1(postcss@8.4.49) - - webpack-virtual-modules@0.6.2: {} - - yallist@3.1.1: {} - - zod@3.23.8: {} diff --git a/packages/cli/template/vite-wv/postcss.config.cjs b/packages/cli/template/vite-wv/postcss.config.cjs deleted file mode 100644 index 085a0ef9..00000000 --- a/packages/cli/template/vite-wv/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/vite-wv/proofkit-typegen.config.jsonc b/packages/cli/template/vite-wv/proofkit-typegen.config.jsonc new file mode 100644 index 00000000..bed0eafc --- /dev/null +++ b/packages/cli/template/vite-wv/proofkit-typegen.config.jsonc @@ -0,0 +1,18 @@ +{ + "$schema": "https://proofkit.dev/typegen-config-schema.json", + "config": { + "type": "fmdapi", + "path": "./src/config/schemas/filemaker", + "clearOldFiles": true, + "clientSuffix": "Layout", + "validator": "zod/v4", + "webviewerScriptName": "ExecuteDataApi", + "fmHttp": { + "enabled": true + }, + "layouts": [ + // Add layouts here when you're ready to generate clients. + // { "layoutName": "API_Customers", "schemaName": "Customers" } + ] + } +} diff --git a/packages/cli/template/vite-wv/proofkit.json b/packages/cli/template/vite-wv/proofkit.json index 028f87b5..3bd029c2 100644 --- a/packages/cli/template/vite-wv/proofkit.json +++ b/packages/cli/template/vite-wv/proofkit.json @@ -1,6 +1,9 @@ { + "ui": "shadcn", "auth": { "type": "none" }, "envFile": ".env", "appType": "webviewer", - "appliedUpgrades": ["shadcn"] + "dataSources": [], + "replacedMainPage": false, + "registryTemplates": [] } diff --git a/packages/cli/template/vite-wv/scripts/filemaker.js b/packages/cli/template/vite-wv/scripts/filemaker.js new file mode 100644 index 00000000..172f9c3d --- /dev/null +++ b/packages/cli/template/vite-wv/scripts/filemaker.js @@ -0,0 +1,96 @@ +import { resolve } from "node:path"; +import dotenv from "dotenv"; +import { fileURLToPath } from "node:url"; + +const currentDirectory = fileURLToPath(new URL(".", import.meta.url)); +const envPath = resolve(currentDirectory, "../.env"); + +dotenv.config({ path: envPath }); + +const defaultFmHttpBaseUrl = process.env.FM_HTTP_BASE_URL ?? "http://127.0.0.1:1365"; + +function stripFileExtension(fileName) { + return fileName.replace(/\.fmp12$/i, ""); +} + +async function getConnectedFiles(baseUrl = defaultFmHttpBaseUrl) { + const healthResponse = await fetch(`${baseUrl}/health`).catch(() => null); + if (!healthResponse?.ok) { + return []; + } + + const connectedFiles = await fetch(`${baseUrl}/connectedFiles`) + .then((response) => (response.ok ? response.json() : [])) + .catch(() => []); + + return Array.isArray(connectedFiles) ? connectedFiles : []; +} + +function normalizeTarget(fileName) { + return stripFileExtension(fileName).toLowerCase(); +} + +export async function resolveFileMakerTarget() { + const connectedFiles = await getConnectedFiles(); + const targetFromEnv = process.env.FM_DATABASE ? normalizeTarget(process.env.FM_DATABASE) : undefined; + + if (targetFromEnv) { + const matches = connectedFiles.filter((connectedFile) => normalizeTarget(connectedFile) === targetFromEnv); + if (matches.length === 1) { + return { + fileName: stripFileExtension(matches[0]), + host: "$", + source: "fm-http", + }; + } + + if (connectedFiles.length > 0) { + throw new Error( + `FM_DATABASE is set to "${process.env.FM_DATABASE}" but no matching connected file was found via FM HTTP.`, + ); + } + } + + if (connectedFiles.length === 1) { + return { + fileName: stripFileExtension(connectedFiles[0]), + host: "$", + source: "fm-http", + }; + } + + if (connectedFiles.length > 1) { + throw new Error( + `Multiple FileMaker files are connected via FM HTTP (${connectedFiles.join(", ")}). Set FM_DATABASE to choose one.`, + ); + } + + const serverValue = process.env.FM_SERVER; + const databaseValue = process.env.FM_DATABASE; + + if (serverValue && databaseValue) { + let hostname; + try { + hostname = new URL(serverValue).hostname; + } catch { + hostname = serverValue.replace(/^https?:\/\//, "").replace(/\/.*$/, ""); + } + + return { + fileName: stripFileExtension(databaseValue), + host: hostname, + source: "env", + }; + } + + return null; +} + +export function buildFmpUrl({ host, fileName, scriptName, parameter }) { + const params = new URLSearchParams({ script: scriptName }); + if (parameter) { + params.set("param", parameter); + } + + return `fmp://${host}/${encodeURIComponent(fileName)}?${params.toString()}`; +} diff --git a/packages/cli/template/vite-wv/scripts/launch-fm.js b/packages/cli/template/vite-wv/scripts/launch-fm.js new file mode 100644 index 00000000..638e2f1a --- /dev/null +++ b/packages/cli/template/vite-wv/scripts/launch-fm.js @@ -0,0 +1,19 @@ +import open from "open"; +import { buildFmpUrl, resolveFileMakerTarget } from "./filemaker.js"; + +const target = await resolveFileMakerTarget(); + +if (!target) { + console.error( + "Could not resolve a FileMaker file. Start the local FM HTTP proxy with a connected file, or set FM_SERVER and FM_DATABASE in .env.", + ); + process.exit(1); +} + +await open( + buildFmpUrl({ + host: target.host, + fileName: target.fileName, + scriptName: "Launch Web Viewer for Dev", + }), +); diff --git a/packages/cli/template/vite-wv/scripts/launch-fm.sh b/packages/cli/template/vite-wv/scripts/launch-fm.sh deleted file mode 100755 index 6256433a..00000000 --- a/packages/cli/template/vite-wv/scripts/launch-fm.sh +++ /dev/null @@ -1,3 +0,0 @@ -source .env -SERVER_HOST=$(echo $FM_SERVER | sed 's|https://||' | sed 's|/.*||') -open "fmp://$SERVER_HOST/$FM_DATABASE?script=Launch Web Viewer for Dev" diff --git a/packages/cli/template/vite-wv/scripts/upload.js b/packages/cli/template/vite-wv/scripts/upload.js index 51334ddb..cce9457d 100644 --- a/packages/cli/template/vite-wv/scripts/upload.js +++ b/packages/cli/template/vite-wv/scripts/upload.js @@ -1,21 +1,24 @@ import open from "open"; import { resolve } from "path"; -import dotenv from "dotenv"; import { fileURLToPath } from "url"; +import { buildFmpUrl, resolveFileMakerTarget } from "./filemaker.js"; const currentDirectory = fileURLToPath(new URL(".", import.meta.url)); - -const { parsed } = dotenv.config({ - path: resolve(currentDirectory, "../.env"), -}); - -const server = new URL(parsed.FM_SERVER).hostname; -const file = parsed.FM_DATABASE.replace(/\.fmp12$/, ""); -const uploadScript = "UploadWebviewerWidget"; - const thePath = resolve(currentDirectory, "../dist", "index.html"); -const url = `fmp://${server}/${file}?script=${uploadScript}¶m=${encodeURIComponent( - thePath -)}`; +const target = await resolveFileMakerTarget(); + +if (!target) { + console.error( + "Could not resolve a FileMaker file. Start the local FM HTTP proxy with a connected file, or set FM_SERVER and FM_DATABASE in .env.", + ); + process.exit(1); +} -open(url); +await open( + buildFmpUrl({ + host: target.host, + fileName: target.fileName, + scriptName: "UploadWebviewerWidget", + parameter: thePath, + }), +); diff --git a/packages/cli/template/vite-wv/src/App.tsx b/packages/cli/template/vite-wv/src/App.tsx new file mode 100644 index 00000000..7b96455e --- /dev/null +++ b/packages/cli/template/vite-wv/src/App.tsx @@ -0,0 +1,84 @@ +import { globalSettings } from "@proofkit/webviewer"; +import type { LucideIcon } from "lucide-react"; +import { Database, Layers, Sparkles } from "lucide-react"; + +type Step = { + readonly icon: LucideIcon; + readonly title: string; + readonly body: string; +}; + +globalSettings.setWebViewerName("web"); + +const steps: readonly Step[] = [ + { + icon: Database, + title: "Connect FileMaker later", + body: "This starter renders safely in a normal browser. When you are ready, wire in FM HTTP or hosted FileMaker setup with ProofKit commands.", + }, + { + icon: Layers, + title: "Generate clients when ready", + body: "Add layouts to proofkit-typegen.config.jsonc, then run your typegen script to create strongly typed layout clients.", + }, + { + icon: Sparkles, + title: "Add shadcn components fast", + body: "Tailwind v4 and shadcn are already initialized, so agents and developers can add components without extra setup.", + }, +] as const; + +export default function App() { + return ( +
+
+
+
+ + ProofKit WebViewer Starter +
+ +
+
+

+ React + TypeScript + Vite +

+

+ Build browser-safe FileMaker WebViewer apps without scaffolding against a hosted server. +

+

+ This starter stays intentionally small, but it is already ready for Tailwind v4, shadcn component + installs, and later ProofKit typegen output. +

+ +
+ pnpm dev + pnpm typegen + pnpm launch-fm +
+
+ + +
+ +
+ {steps.map((step) => ( +
+ +

{step.title}

+

{step.body}

+
+ ))} +
+
+
+
+ ); +} diff --git a/packages/cli/template/vite-wv/src/components/AppLogo.tsx b/packages/cli/template/vite-wv/src/components/AppLogo.tsx deleted file mode 100644 index d95a8385..00000000 --- a/packages/cli/template/vite-wv/src/components/AppLogo.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { IconInfinity } from "@tabler/icons-react"; - -export default function AppLogo() { - return ; -} diff --git a/packages/cli/template/vite-wv/src/components/full-screen-loader.tsx b/packages/cli/template/vite-wv/src/components/full-screen-loader.tsx deleted file mode 100644 index e44762ea..00000000 --- a/packages/cli/template/vite-wv/src/components/full-screen-loader.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { Center, Loader } from "@mantine/core"; - -export default function FullScreenLoader() { - return ( -
- -
- ); -} diff --git a/packages/cli/template/vite-wv/src/config/env.ts b/packages/cli/template/vite-wv/src/config/env.ts deleted file mode 100644 index 2dbe511a..00000000 --- a/packages/cli/template/vite-wv/src/config/env.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { createEnv } from "@t3-oss/env-core"; -import { z } from "zod/v4"; -import { type OttoAPIKey } from "@proofkit/fmdapi"; - -export const env = createEnv({ - server: { - NODE_ENV: z - .enum(["development", "test", "production"]) - .default("development"), - }, - - clientPrefix: "VITE_", - client: {}, - runtimeEnv: process.env, - emptyStringAsUndefined: true, -}); diff --git a/packages/cli/template/vite-wv/src/config/theme/globals.css b/packages/cli/template/vite-wv/src/config/theme/globals.css deleted file mode 100644 index 0e2f76bb..00000000 --- a/packages/cli/template/vite-wv/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/vite-wv/src/config/theme/mantine-theme.ts b/packages/cli/template/vite-wv/src/config/theme/mantine-theme.ts deleted file mode 100644 index 890db89c..00000000 --- a/packages/cli/template/vite-wv/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/vite-wv/src/index.css b/packages/cli/template/vite-wv/src/index.css new file mode 100644 index 00000000..6a1d0b1f --- /dev/null +++ b/packages/cli/template/vite-wv/src/index.css @@ -0,0 +1,96 @@ +@import "tailwindcss"; +@import "tw-animate-css"; + +:root { + --background: hsl(42 33% 98%); + --foreground: hsl(222 47% 11%); + --card: hsl(0 0% 100%); + --card-foreground: hsl(222 47% 11%); + --popover: hsl(0 0% 100%); + --popover-foreground: hsl(222 47% 11%); + --primary: hsl(197 82% 44%); + --primary-foreground: hsl(210 40% 98%); + --secondary: hsl(210 20% 93%); + --secondary-foreground: hsl(222 47% 11%); + --muted: hsl(42 21% 94%); + --muted-foreground: hsl(215 16% 40%); + --accent: hsl(32 88% 92%); + --accent-foreground: hsl(24 10% 10%); + --destructive: hsl(0 72% 51%); + --destructive-foreground: hsl(210 40% 98%); + --border: hsl(30 14% 86%); + --input: hsl(30 14% 86%); + --ring: hsl(197 82% 44%); + --radius: 1rem; +} + +.dark { + color-scheme: dark; + --background: hsl(221 39% 11%); + --foreground: hsl(44 23% 92%); + --card: hsl(222 33% 15%); + --card-foreground: hsl(44 23% 92%); + --popover: hsl(222 33% 15%); + --popover-foreground: hsl(44 23% 92%); + --primary: hsl(190 82% 62%); + --primary-foreground: hsl(222 47% 11%); + --secondary: hsl(219 19% 22%); + --secondary-foreground: hsl(44 23% 92%); + --muted: hsl(219 19% 22%); + --muted-foreground: hsl(215 20% 72%); + --accent: hsl(27 42% 28%); + --accent-foreground: hsl(44 23% 92%); + --destructive: hsl(0 63% 54%); + --destructive-foreground: hsl(210 40% 98%); + --border: hsl(219 19% 26%); + --input: hsl(219 19% 26%); + --ring: hsl(190 82% 62%); +} + +@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); + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); +} + +@layer base { + * { + @apply border-border; + } + + html { + color-scheme: light; + } + + body { + background-color: var(--background); + color: var(--foreground); + font-family: + "Instrument Sans", + Inter, + ui-sans-serif, + system-ui, + sans-serif; + min-width: 320px; + } +} diff --git a/packages/cli/template/vite-wv/src/lib/utils.ts b/packages/cli/template/vite-wv/src/lib/utils.ts new file mode 100644 index 00000000..a5ef1935 --- /dev/null +++ b/packages/cli/template/vite-wv/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/packages/cli/template/vite-wv/src/main.tsx b/packages/cli/template/vite-wv/src/main.tsx index 3d04e4fa..71199209 100644 --- a/packages/cli/template/vite-wv/src/main.tsx +++ b/packages/cli/template/vite-wv/src/main.tsx @@ -1,42 +1,15 @@ import React from "react"; import ReactDOM from "react-dom/client"; -import { - RouterProvider, - createHashHistory, - createRouter, -} from "@tanstack/react-router"; -import { routeTree } from "./routeTree.gen"; -import { MantineProvider } from "@mantine/core"; -import { theme } from "./config/theme/mantine-theme"; +import App from "./App"; +import "./index.css"; -import "@mantine/core/styles.css"; -import "mantine-react-table/styles.css"; -import "./config/theme/globals.css"; - -// Hash history is used since we are using a single file build -const hashHistory = createHashHistory(); - -// Set up a Router instance -const router = createRouter({ - routeTree, - defaultPreload: "intent", - history: hashHistory, -}); - -// Register things for typesafety -declare module "@tanstack/react-router" { - interface Register { - router: typeof router; - } +const rootElement = document.getElementById("root"); +if (!rootElement) { + throw new Error("Root element with id 'root' not found"); } -const rootElement = document.getElementById("app")!; - -if (!rootElement.innerHTML) { - const root = ReactDOM.createRoot(rootElement); - root.render( - - - , - ); -} +ReactDOM.createRoot(rootElement).render( + + + , +); diff --git a/packages/cli/template/vite-wv/src/routeTree.gen.ts b/packages/cli/template/vite-wv/src/routeTree.gen.ts deleted file mode 100644 index ac6bb048..00000000 --- a/packages/cli/template/vite-wv/src/routeTree.gen.ts +++ /dev/null @@ -1,111 +0,0 @@ - - -// @ts-nocheck - -// noinspection JSUnusedGlobalSymbols - -// This file was automatically generated by TanStack Router. -// You should NOT make any changes in this file as it will be overwritten. -// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. - -// Import Routes - -import { Route as rootRoute } from "./routes/__root"; -import { Route as SecondaryImport } from "./routes/secondary"; -import { Route as IndexImport } from "./routes/index"; - -// Create/Update Routes - -const SecondaryRoute = SecondaryImport.update({ - id: "/secondary", - path: "/secondary", - getParentRoute: () => rootRoute, -} as any); - -const IndexRoute = IndexImport.update({ - id: "/", - path: "/", - getParentRoute: () => rootRoute, -} as any); - -// Populate the FileRoutesByPath interface - -declare module "@tanstack/react-router" { - interface FileRoutesByPath { - "/": { - id: "/"; - path: "/"; - fullPath: "/"; - preLoaderRoute: typeof IndexImport; - parentRoute: typeof rootRoute; - }; - "/secondary": { - id: "/secondary"; - path: "/secondary"; - fullPath: "/secondary"; - preLoaderRoute: typeof SecondaryImport; - parentRoute: typeof rootRoute; - }; - } -} - -// Create and export the route tree - -export interface FileRoutesByFullPath { - "/": typeof IndexRoute; - "/secondary": typeof SecondaryRoute; -} - -export interface FileRoutesByTo { - "/": typeof IndexRoute; - "/secondary": typeof SecondaryRoute; -} - -export interface FileRoutesById { - __root__: typeof rootRoute; - "/": typeof IndexRoute; - "/secondary": typeof SecondaryRoute; -} - -export interface FileRouteTypes { - fileRoutesByFullPath: FileRoutesByFullPath; - fullPaths: "/" | "/secondary"; - fileRoutesByTo: FileRoutesByTo; - to: "/" | "/secondary"; - id: "__root__" | "/" | "/secondary"; - fileRoutesById: FileRoutesById; -} - -export interface RootRouteChildren { - IndexRoute: typeof IndexRoute; - SecondaryRoute: typeof SecondaryRoute; -} - -const rootRouteChildren: RootRouteChildren = { - IndexRoute: IndexRoute, - SecondaryRoute: SecondaryRoute, -}; - -export const routeTree = rootRoute - ._addFileChildren(rootRouteChildren) - ._addFileTypes(); - -/* ROUTE_MANIFEST_START -{ - "routes": { - "__root__": { - "filePath": "__root.tsx", - "children": [ - "/", - "/secondary" - ] - }, - "/": { - "filePath": "index.tsx" - }, - "/secondary": { - "filePath": "secondary.tsx" - } - } -} -ROUTE_MANIFEST_END */ diff --git a/packages/cli/template/vite-wv/src/routes/__root.tsx b/packages/cli/template/vite-wv/src/routes/__root.tsx deleted file mode 100644 index 485e5a61..00000000 --- a/packages/cli/template/vite-wv/src/routes/__root.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { Outlet, createRootRoute } from "@tanstack/react-router"; -import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; - -export const Route = createRootRoute({ - component: RootComponent, -}); - -/** - * This component wraps all other routes - */ -function RootComponent() { - return ( - <> - - - {process.env.NODE_ENV === "development" && ( - - )} - - ); -} diff --git a/packages/cli/template/vite-wv/src/routes/index.tsx b/packages/cli/template/vite-wv/src/routes/index.tsx deleted file mode 100644 index 5a05996b..00000000 --- a/packages/cli/template/vite-wv/src/routes/index.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { Anchor, Box, Code, Container, Image, px, Stack, Text, Title } from "@mantine/core"; -import { IconExternalLink } from "@tabler/icons-react"; -import { createFileRoute, Link } from "@tanstack/react-router"; - -export const Route = createFileRoute("/")({ - component: HomeComponent, -}); - -function HomeComponent() { - return ( - - - ProofKit - Welcome! - - - This is the base template page. To add more pages, components, or other features, run the ProofKit CLI from - within your project. - - pnpm proofkit - - - To change this page, open src/routes/index.tsx - - - - ProofKit Docs - - - - - Need to build multiple webviewer widgets? Check out the secondary page - - - - ); -} diff --git a/packages/cli/template/vite-wv/src/routes/secondary.tsx b/packages/cli/template/vite-wv/src/routes/secondary.tsx deleted file mode 100644 index a27baaec..00000000 --- a/packages/cli/template/vite-wv/src/routes/secondary.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { Code, Container, Stack, Text, Title } from "@mantine/core"; -import { createFileRoute, Link } from "@tanstack/react-router"; - -export const Route = createFileRoute("/secondary")({ - component: RouteComponent, -}); - -function RouteComponent() { - return ( - - - Secondary Page - - Use hidden pages like this to embed multiple webviewer widgets into a single HTML bundle for your FileMaker - solution. - - - See how to navigate via a FileMaker script in the EXAMPLE: Navigation script - - - Go back to the home page - - - - ); -} diff --git a/packages/cli/template/vite-wv/src/utils/notification-helpers.ts b/packages/cli/template/vite-wv/src/utils/notification-helpers.ts deleted file mode 100644 index b5aa63e3..00000000 --- a/packages/cli/template/vite-wv/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/vite-wv/src/utils/styles.ts b/packages/cli/template/vite-wv/src/utils/styles.ts deleted file mode 100644 index 1f4cd38e..00000000 --- a/packages/cli/template/vite-wv/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/vite-wv/tsconfig.json b/packages/cli/template/vite-wv/tsconfig.json index dbc649b3..1565cf30 100644 --- a/packages/cli/template/vite-wv/tsconfig.json +++ b/packages/cli/template/vite-wv/tsconfig.json @@ -6,9 +6,11 @@ "target": "ESNext", "module": "ESNext", "moduleResolution": "Bundler", + "baseUrl": ".", "noEmit": true, "paths": { "@/*": ["./src/*"] } - } + }, + "include": ["src"] } diff --git a/packages/cli/template/vite-wv/vite.config.ts b/packages/cli/template/vite-wv/vite.config.ts index 5dcfdd48..16f57ac0 100644 --- a/packages/cli/template/vite-wv/vite.config.ts +++ b/packages/cli/template/vite-wv/vite.config.ts @@ -1,10 +1,9 @@ -import path from "path"; -import { defineConfig } from "vite"; +import path from "node:path"; import react from "@vitejs/plugin-react"; -import { TanStackRouterVite } from "@tanstack/router-plugin/vite"; +import tailwindcss from "@tailwindcss/vite"; +import { defineConfig } from "vite"; import { viteSingleFile } from "vite-plugin-singlefile"; -// https://vitejs.dev/config/ export default defineConfig({ server: { port: 5175, @@ -14,5 +13,5 @@ export default defineConfig({ "@": path.resolve(__dirname, "./src"), }, }, - plugins: [TanStackRouterVite({}), react(), viteSingleFile()], + plugins: [react(), tailwindcss(), viteSingleFile()], }); diff --git a/packages/cli/tests/browser-apps.test.ts b/packages/cli/tests/browser-apps.test.ts index a9a9243a..2553ec76 100644 --- a/packages/cli/tests/browser-apps.test.ts +++ b/packages/cli/tests/browser-apps.test.ts @@ -36,12 +36,12 @@ describe("Non-Interactive CLI Tests", () => { 30_000, // 30s timeout for cleanup of large node_modules ); - it("should create a project with FileMaker integration in CI mode", () => { + it("should create a browser project with FileMaker integration in non-interactive mode", () => { // Build the command with all necessary flags for non-interactive mode const command = [ `node "${cliPath}" init`, projectName, - "--ci", + "--non-interactive", "--appType browser", "--dataSource filemaker", `--server "${testEnv.OTTO_SERVER_URL}"`, @@ -56,10 +56,7 @@ describe("Non-Interactive CLI Tests", () => { expect(() => { execSync(command, { cwd: testDir, - env: { - ...process.env, - CI: "true", - }, + env: process.env, encoding: "utf-8", }); }).not.toThrow(); diff --git a/packages/cli/tests/webviewer-apps.test.ts b/packages/cli/tests/webviewer-apps.test.ts new file mode 100644 index 00000000..6339267c --- /dev/null +++ b/packages/cli/tests/webviewer-apps.test.ts @@ -0,0 +1,129 @@ +import { execSync } from "node:child_process"; +import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { join } from "node:path"; +import { beforeEach, describe, expect, it } from "vitest"; + +import { verifyProjectBuilds } from "./test-utils"; + +const nonInteractiveDirectoryError = /already exists and isn't empty/; + +describe("WebViewer CLI Tests", () => { + const testDir = join(__dirname, "..", "..", "tmp", "cli-tests"); + const cliPath = join(__dirname, "..", "dist", "index.js"); + const projectName = "test-webviewer-project"; + const projectDir = join(testDir, projectName); + + beforeEach(() => { + if (existsSync(projectDir)) { + rmSync(projectDir, { recursive: true, force: true }); + } + mkdirSync(testDir, { recursive: true }); + }); + + it("should create a webviewer project without FileMaker server setup", () => { + const command = [`node "${cliPath}" init`, projectName, "--non-interactive", "--appType webviewer", "--noGit"].join( + " ", + ); + + expect(() => { + execSync(command, { + cwd: testDir, + env: process.env, + encoding: "utf-8", + }); + }).not.toThrow(); + + expect(existsSync(projectDir)).toBe(true); + expect(existsSync(join(projectDir, "package.json"))).toBe(true); + expect(existsSync(join(projectDir, "proofkit.json"))).toBe(true); + expect(existsSync(join(projectDir, "proofkit-typegen.config.jsonc"))).toBe(true); + + const packageJson = JSON.parse(readFileSync(join(projectDir, "package.json"), "utf-8")); + expect(packageJson.scripts.typegen).toBe("typegen"); + expect(packageJson.scripts["typegen:ui"]).toBe("typegen ui"); + expect(packageJson.devDependencies["@proofkit/typegen"]).toBe("^1.1.0-beta.16"); + + const proofkitConfig = JSON.parse(readFileSync(join(projectDir, "proofkit.json"), "utf-8")); + expect(proofkitConfig.appType).toBe("webviewer"); + expect(proofkitConfig.dataSources).toEqual([]); + + verifyProjectBuilds(projectDir); + }); + + it("should allow agent-only folders in non-interactive mode", () => { + mkdirSync(projectDir, { recursive: true }); + mkdirSync(join(projectDir, ".cursor"), { recursive: true }); + writeFileSync(join(projectDir, ".cursor", "rules.mdc"), "placeholder"); + + const command = [ + `node "${cliPath}" init`, + projectName, + "--non-interactive", + "--appType webviewer", + "--noGit", + "--noInstall", + ].join(" "); + + expect(() => { + execSync(command, { + cwd: testDir, + env: process.env, + encoding: "utf-8", + }); + }).not.toThrow(); + + expect(existsSync(join(projectDir, "package.json"))).toBe(true); + expect(existsSync(join(projectDir, ".cursor"))).toBe(true); + expect(existsSync(join(projectDir, ".cursor", "rules"))).toBe(false); + }); + + it("should allow hidden files in non-interactive mode", () => { + mkdirSync(projectDir, { recursive: true }); + writeFileSync(join(projectDir, ".DS_Store"), "placeholder"); + + const command = [ + `node "${cliPath}" init`, + projectName, + "--non-interactive", + "--appType webviewer", + "--noGit", + "--noInstall", + ].join(" "); + + expect(() => { + execSync(command, { + cwd: testDir, + env: process.env, + encoding: "utf-8", + }); + }).not.toThrow(); + + expect(existsSync(join(projectDir, "package.json"))).toBe(true); + expect(existsSync(join(projectDir, ".DS_Store"))).toBe(true); + }); + + it("should fail without prompting when a non-interactive target directory has real files", () => { + mkdirSync(projectDir, { recursive: true }); + writeFileSync(join(projectDir, "README.md"), "existing content"); + + const command = [ + `node "${cliPath}" init`, + projectName, + "--non-interactive", + "--appType webviewer", + "--noGit", + "--noInstall", + ].join(" "); + + expect(() => { + execSync(command, { + cwd: testDir, + env: process.env, + encoding: "utf-8", + stdio: "pipe", + }); + }).toThrow(nonInteractiveDirectoryError); + + expect(existsSync(join(projectDir, "package.json"))).toBe(false); + }); +}); diff --git a/packages/cli/tsdown.config.ts b/packages/cli/tsdown.config.ts index 5dc1002b..b07dd699 100644 --- a/packages/cli/tsdown.config.ts +++ b/packages/cli/tsdown.config.ts @@ -23,6 +23,7 @@ const readPackageVersion = (packagePath: string) => { const FMDAPI_VERSION = readPackageVersion("fmdapi"); const BETTER_AUTH_VERSION = readPackageVersion("better-auth"); +const WEBVIEWER_VERSION = readPackageVersion("webviewer"); export default defineConfig({ clean: true, @@ -42,6 +43,7 @@ export default defineConfig({ values: { __FMDAPI_VERSION__: JSON.stringify(FMDAPI_VERSION), __BETTER_AUTH_VERSION__: JSON.stringify(BETTER_AUTH_VERSION), + __WEBVIEWER_VERSION__: JSON.stringify(WEBVIEWER_VERSION), __REGISTRY_URL__: JSON.stringify(isDev ? "http://localhost:3005" : "https://proofkit.dev"), }, }), diff --git a/packages/fmdapi/skills/fmdapi-client/SKILL.md b/packages/fmdapi/skills/fmdapi-client/SKILL.md index 013c1bef..2dcd8139 100644 --- a/packages/fmdapi/skills/fmdapi-client/SKILL.md +++ b/packages/fmdapi/skills/fmdapi-client/SKILL.md @@ -26,6 +26,12 @@ Install the package: pnpm add @proofkit/fmdapi ``` +If you want the companion `typegen-setup` skill available locally in this project, also install: + +```bash +pnpm add -D @proofkit/typegen@* +``` + ### OttoAdapter (recommended) Requires [OttoFMS](https://ottofms.com/) installed on the FileMaker Server. No token management needed — the proxy handles sessions. diff --git a/packages/fmodata/package.json b/packages/fmodata/package.json index 70466829..9333c584 100644 --- a/packages/fmodata/package.json +++ b/packages/fmodata/package.json @@ -46,6 +46,7 @@ "cli-table3": "^0.6.5", "commander": "^14.0.2", "dotenv": "^16.6.1", + "effect": "^3.20.0", "es-toolkit": "^1.43.0", "neverthrow": "^8.2.0", "odata-query": "^8.0.7" diff --git a/packages/fmodata/skills/fmodata-client/SKILL.md b/packages/fmodata/skills/fmodata-client/SKILL.md index 73431586..81926a86 100644 --- a/packages/fmodata/skills/fmodata-client/SKILL.md +++ b/packages/fmodata/skills/fmodata-client/SKILL.md @@ -23,6 +23,12 @@ sources: > **Prerequisites:** This skill requires `typegen-setup`. Schema files (fields, entity IDs) should be generated by `@proofkit/typegen` — do NOT manually add fields or change entity IDs (`FMFID`/`FMTID`), as these values come from FileMaker metadata and guessing them will cause silent failures. You MAY add/edit `readValidator`, `writeValidator`, `defaultSelect`, `navigationPaths`, and other options on existing fields. +To make the `typegen-setup` skill available locally in this project, install: + +```bash +pnpm add -D @proofkit/typegen@* +``` + ### 1. Create a server connection ```ts diff --git a/packages/webviewer/skills/webviewer-integration/SKILL.md b/packages/webviewer/skills/webviewer-integration/SKILL.md index 85714eae..1c63e6f1 100644 --- a/packages/webviewer/skills/webviewer-integration/SKILL.md +++ b/packages/webviewer/skills/webviewer-integration/SKILL.md @@ -27,6 +27,12 @@ globalSettings.setWebViewerName("web"); const result = await fmFetch("MyScript", { key: "value" }); ``` +If you also want the local `typegen-setup` skill for generating `@proofkit/fmdapi` clients that work with `WebViewerAdapter`, install: + +```bash +pnpm add -D @proofkit/typegen@^1.1.0-beta.0 +``` + The `setWebViewerName` call is required for `fmFetch` to work. It tells the FM `SendCallback` script which webviewer to call back into. Set it once at app initialization. ## Core Patterns diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 10b465f3..39356858 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,7 +44,7 @@ importers: version: 5.9.3 ultracite: specifier: 7.0.8 - version: 7.0.8(typescript@5.9.3) + version: 7.0.8(effect@3.20.0)(typescript@5.9.3) vitest: specifier: ^4.0.17 version: 4.0.17(@types/node@22.19.5)(happy-dom@20.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(msw@2.12.7(@types/node@22.19.5)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.2) @@ -446,7 +446,7 @@ importers: version: 5.9.3 ultracite: specifier: 7.0.8 - version: 7.0.8(typescript@5.9.3) + version: 7.0.8(effect@3.20.0)(typescript@5.9.3) vitest: specifier: ^4.0.17 version: 4.0.17(@types/node@22.19.5)(happy-dom@20.1.0)(jiti@1.21.7)(lightningcss@1.30.2)(msw@2.12.7(@types/node@22.19.5)(typescript@5.9.3))(tsx@4.21.0)(yaml@2.8.2) @@ -532,6 +532,9 @@ importers: dotenv: specifier: ^16.6.1 version: 16.6.1 + effect: + specifier: ^3.20.0 + version: 3.20.0 es-toolkit: specifier: ^1.43.0 version: 1.43.0 @@ -5007,6 +5010,9 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + effect@3.20.0: + resolution: {integrity: sha512-qMLfDJscrNG8p/aw+IkT9W7fgj50Z4wG5bLBy0Txsxz8iUHjDIkOgO3SV0WZfnQbNG2VJYb0b+rDLMrhM4+Krw==} + electron-to-chromium@1.5.267: resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} @@ -5237,6 +5243,10 @@ packages: extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} + fast-check@3.23.2: + resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} + engines: {node: '>=8.0.0'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -6890,6 +6900,9 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + qs@6.14.1: resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==} engines: {node: '>=0.6'} @@ -12157,6 +12170,11 @@ snapshots: ee-first@1.1.1: {} + effect@3.20.0: + dependencies: + '@standard-schema/spec': 1.1.0 + fast-check: 3.23.2 + electron-to-chromium@1.5.267: {} emoji-regex@10.6.0: {} @@ -12530,6 +12548,10 @@ snapshots: extendable-error@0.1.7: {} + fast-check@3.23.2: + dependencies: + pure-rand: 6.1.0 + fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -14551,6 +14573,8 @@ snapshots: punycode@2.3.1: {} + pure-rand@6.1.0: {} + qs@6.14.1: dependencies: side-channel: 1.1.0 @@ -15444,11 +15468,12 @@ snapshots: trough@2.2.0: {} - trpc-cli@0.12.2(@trpc/server@11.8.1(typescript@5.9.3))(zod@4.3.5): + trpc-cli@0.12.2(@trpc/server@11.8.1(typescript@5.9.3))(effect@3.20.0)(zod@4.3.5): dependencies: commander: 14.0.2 optionalDependencies: '@trpc/server': 11.8.1(typescript@5.9.3) + effect: 3.20.0 zod: 4.3.5 ts-morph@18.0.0: @@ -15583,7 +15608,7 @@ snapshots: uglify-js@3.19.3: optional: true - ultracite@7.0.8(typescript@5.9.3): + ultracite@7.0.8(effect@3.20.0)(typescript@5.9.3): dependencies: '@clack/prompts': 0.11.0 '@trpc/server': 11.8.1(typescript@5.9.3) @@ -15592,7 +15617,7 @@ snapshots: jsonc-parser: 3.3.1 nypm: 0.6.2 oxlint: 1.39.0 - trpc-cli: 0.12.2(@trpc/server@11.8.1(typescript@5.9.3))(zod@4.3.5) + trpc-cli: 0.12.2(@trpc/server@11.8.1(typescript@5.9.3))(effect@3.20.0)(zod@4.3.5) zod: 4.3.5 transitivePeerDependencies: - '@orpc/server' From c95f25d01a1bbbc5ff3364c7217ff6c317724062 Mon Sep 17 00:00:00 2001 From: Eric Luce <37158449+eluce2@users.noreply.github.com> Date: Tue, 17 Mar 2026 11:51:18 -0500 Subject: [PATCH 2/6] feat(cli): add TYPEGEN_VERSION to globals and update related configurations - Added __TYPEGEN_VERSION__ to the globals in biome.json and updated the CLI configuration to read the Typegen version. - Enhanced the FileMaker data source handling by persisting new data source settings. - Improved error handling in the FileMaker data source prompt. - Cleaned up the project scaffolding logic for better directory entry checks. - Refactored getTypegenVersion to directly return the global TYPEGEN_VERSION. --- .agents/skills/create-changeset/SKILL.md | 4 ++-- biome.json | 2 +- .../cli/src/cli/add/data-source/filemaker.ts | 22 +++++++++++++++++++ packages/cli/src/cli/init.ts | 2 +- packages/cli/src/globals.d.ts | 1 + packages/cli/src/helpers/scaffoldProject.ts | 10 +-------- packages/cli/src/utils/getProofKitVersion.ts | 9 +------- packages/cli/tsdown.config.ts | 2 ++ 8 files changed, 31 insertions(+), 21 deletions(-) diff --git a/.agents/skills/create-changeset/SKILL.md b/.agents/skills/create-changeset/SKILL.md index 2b96c853..da741837 100644 --- a/.agents/skills/create-changeset/SKILL.md +++ b/.agents/skills/create-changeset/SKILL.md @@ -101,8 +101,8 @@ Add new Button variant for secondary actions "@proofkit/typegen": patch --- -- spindle-ui: Add new Button variant for secondary actions -- spindle-tokens: Fix color token contrast ratio +- @proofkit/cli: Add new Button variant for secondary actions +- @proofkit/typegen: Fix color token contrast ratio ``` **Important guidelines:** diff --git a/biome.json b/biome.json index bcd7708d..282f819d 100644 --- a/biome.json +++ b/biome.json @@ -62,7 +62,7 @@ "arrowParentheses": "always", "quoteStyle": "double" }, - "globals": ["__FMDAPI_VERSION__", "__BETTER_AUTH_VERSION__", "__WEBVIEWER_VERSION__"] + "globals": ["__FMDAPI_VERSION__", "__BETTER_AUTH_VERSION__", "__WEBVIEWER_VERSION__", "__TYPEGEN_VERSION__"] }, "json": { "formatter": { diff --git a/packages/cli/src/cli/add/data-source/filemaker.ts b/packages/cli/src/cli/add/data-source/filemaker.ts index c04404b1..a00f6046 100644 --- a/packages/cli/src/cli/add/data-source/filemaker.ts +++ b/packages/cli/src/cli/add/data-source/filemaker.ts @@ -51,6 +51,26 @@ export async function promptForFileMakerDataSource({ baseUrl: fmHttpStatus.baseUrl, }); + // Persist the datasource in project settings + const newDataSource: z.infer = { + type: "fm", + name: localDataSourceName, + envNames: + localDataSourceName === "filemaker" + ? { + database: "FM_DATABASE", + server: "FM_SERVER", + apiKey: "OTTO_API_KEY", + } + : { + database: `${localDataSourceName.toUpperCase()}_FM_DATABASE`, + server: `${localDataSourceName.toUpperCase()}_FM_SERVER`, + apiKey: `${localDataSourceName.toUpperCase()}_OTTO_API_KEY`, + }, + }; + settings.dataSources.push(newDataSource); + setSettings(settings); + if (opts.layoutName && opts.schemaName) { await addLayout({ projectDir, @@ -63,6 +83,8 @@ export async function promptForFileMakerDataSource({ }, ], }); + } else if (opts.layoutName || opts.schemaName) { + throw new Error("Both --layoutName and --schemaName must be provided together."); } else { p.note( `Detected local FM HTTP at ${fmHttpStatus.baseUrl} with connected file "${connectedFileName}". Edit ${chalk.cyan( diff --git a/packages/cli/src/cli/init.ts b/packages/cli/src/cli/init.ts index 18f83c2d..487236f3 100644 --- a/packages/cli/src/cli/init.ts +++ b/packages/cli/src/cli/init.ts @@ -234,7 +234,7 @@ export const runInit = async (name?: string, opts?: CliFlags) => { // for webviewer apps FM is required, so don't ask let dataSource = state.appType === "webviewer" - ? (cliOptions.dataSource ?? "none") + ? (cliOptions.dataSource ?? "filemaker") : (cliOptions.dataSource ?? (nonInteractive ? "none" : undefined)); if (!dataSource) { dataSource = abortIfCancel( diff --git a/packages/cli/src/globals.d.ts b/packages/cli/src/globals.d.ts index 3da73d1f..edd6438c 100644 --- a/packages/cli/src/globals.d.ts +++ b/packages/cli/src/globals.d.ts @@ -1,3 +1,4 @@ declare const __FMDAPI_VERSION__: string; declare const __BETTER_AUTH_VERSION__: string; declare const __WEBVIEWER_VERSION__: string; +declare const __TYPEGEN_VERSION__: string; diff --git a/packages/cli/src/helpers/scaffoldProject.ts b/packages/cli/src/helpers/scaffoldProject.ts index 45821d85..2c714a18 100644 --- a/packages/cli/src/helpers/scaffoldProject.ts +++ b/packages/cli/src/helpers/scaffoldProject.ts @@ -17,15 +17,7 @@ function getMeaningfulDirectoryEntries(projectDir: string): string[] { return false; } - const entryPath = path.join(projectDir, entry); - let stats: fs.Stats; - try { - stats = fs.lstatSync(entryPath); - } catch { - return false; - } - - if (stats.isFile() && entry.startsWith(".")) { + if (entry.startsWith(".")) { return false; } diff --git a/packages/cli/src/utils/getProofKitVersion.ts b/packages/cli/src/utils/getProofKitVersion.ts index 1b45df6a..496e46a2 100644 --- a/packages/cli/src/utils/getProofKitVersion.ts +++ b/packages/cli/src/utils/getProofKitVersion.ts @@ -34,12 +34,5 @@ export const getProofkitWebviewerVersion = () => { }; export const getTypegenVersion = () => { - const packageJsonPath = path.join(PKG_ROOT, "packages", "typegen", "package.json"); - - try { - const packageJsonContent = fs.readJSONSync(packageJsonPath) as PackageJson; - return packageJsonContent.version ?? "1.1.0-beta.16"; - } catch { - return "1.1.0-beta.16"; - } + return __TYPEGEN_VERSION__; }; diff --git a/packages/cli/tsdown.config.ts b/packages/cli/tsdown.config.ts index b07dd699..c9df787c 100644 --- a/packages/cli/tsdown.config.ts +++ b/packages/cli/tsdown.config.ts @@ -24,6 +24,7 @@ const readPackageVersion = (packagePath: string) => { const FMDAPI_VERSION = readPackageVersion("fmdapi"); const BETTER_AUTH_VERSION = readPackageVersion("better-auth"); const WEBVIEWER_VERSION = readPackageVersion("webviewer"); +const TYPEGEN_VERSION = readPackageVersion("typegen"); export default defineConfig({ clean: true, @@ -44,6 +45,7 @@ export default defineConfig({ __FMDAPI_VERSION__: JSON.stringify(FMDAPI_VERSION), __BETTER_AUTH_VERSION__: JSON.stringify(BETTER_AUTH_VERSION), __WEBVIEWER_VERSION__: JSON.stringify(WEBVIEWER_VERSION), + __TYPEGEN_VERSION__: JSON.stringify(TYPEGEN_VERSION), __REGISTRY_URL__: JSON.stringify(isDev ? "http://localhost:3005" : "https://proofkit.dev"), }, }), From 35077ea586be1682df9ea551b938151201fa514a Mon Sep 17 00:00:00 2001 From: Eric Luce <37158449+eluce2@users.noreply.github.com> Date: Tue, 17 Mar 2026 12:26:41 -0500 Subject: [PATCH 3/6] Use release tags for ProofKit dependency installs --- .../src/installers/dependencyVersionMap.ts | 24 +++-- .../cli/src/utils/proofkitReleaseChannel.ts | 88 +++++++++++++++++++ packages/cli/tests/release-channel.test.ts | 42 +++++++++ 3 files changed, 140 insertions(+), 14 deletions(-) create mode 100644 packages/cli/src/utils/proofkitReleaseChannel.ts create mode 100644 packages/cli/tests/release-channel.test.ts diff --git a/packages/cli/src/installers/dependencyVersionMap.ts b/packages/cli/src/installers/dependencyVersionMap.ts index 8969d819..1fe846bd 100644 --- a/packages/cli/src/installers/dependencyVersionMap.ts +++ b/packages/cli/src/installers/dependencyVersionMap.ts @@ -1,17 +1,22 @@ import { - getFmdapiVersion, getNodeMajorVersion, - getProofkitBetterAuthVersion, - getProofkitWebviewerVersion, - getTypegenVersion, - getVersion, } from "~/utils/getProofKitVersion.js"; +import { getProofkitReleaseTag } from "~/utils/proofkitReleaseChannel.js"; + +const proofkitReleaseTag = getProofkitReleaseTag(); /* * This maps the necessary packages to a version. * This improves performance significantly over fetching it from the npm registry. */ export const dependencyVersionMap = { + // Resolve to "latest" or "beta" based on current changeset state / versions. + "@proofkit/fmdapi": proofkitReleaseTag, + "@proofkit/webviewer": proofkitReleaseTag, + "@proofkit/cli": proofkitReleaseTag, + "@proofkit/typegen": proofkitReleaseTag, + "@proofkit/better-auth": proofkitReleaseTag, + // NextAuth.js "next-auth": "beta", "next-auth-adapter-filemaker": "beta", @@ -54,14 +59,6 @@ export const dependencyVersionMap = { "@clerk/nextjs": "^6.3.1", "@clerk/themes": "^2.1.33", - // FileMaker Data API - "@proofkit/fmdapi": `^${getFmdapiVersion()}`, - "@proofkit/webviewer": `^${getProofkitWebviewerVersion()}`, - - // ProofKit - "@proofkit/cli": `^${getVersion()}`, - "@proofkit/typegen": `^${getTypegenVersion()}`, - // Tanstack Query "@tanstack/react-query": "^5.59.0", "@tanstack/react-query-devtools": "^5.59.0", @@ -94,7 +91,6 @@ export const dependencyVersionMap = { // better-auth "better-auth": "^1.3.4", - "@proofkit/better-auth": `${getProofkitBetterAuthVersion()}`, "@daveyplate/better-auth-ui": "^2.1.3", // Mantine UI diff --git a/packages/cli/src/utils/proofkitReleaseChannel.ts b/packages/cli/src/utils/proofkitReleaseChannel.ts new file mode 100644 index 00000000..d09fe785 --- /dev/null +++ b/packages/cli/src/utils/proofkitReleaseChannel.ts @@ -0,0 +1,88 @@ +import path from "node:path"; +import fs from "fs-extra"; +import semver from "semver"; + +import { + getFmdapiVersion, + getProofkitBetterAuthVersion, + getProofkitWebviewerVersion, + getTypegenVersion, + getVersion, +} from "~/utils/getProofKitVersion.js"; + +export type ProofkitReleaseTag = "latest" | "beta"; + +interface ChangesetPreState { + mode?: string; + tag?: string; +} + +function findRepoRootWithChangeset(startDir: string): string | null { + let currentDir = path.resolve(startDir); + const { root } = path.parse(currentDir); + + while (currentDir !== root) { + if (fs.existsSync(path.join(currentDir, ".changeset"))) { + return currentDir; + } + currentDir = path.dirname(currentDir); + } + + return null; +} + +function readChangesetPreState(startDir = process.cwd()): ChangesetPreState | null { + const repoRoot = findRepoRootWithChangeset(startDir); + if (!repoRoot) { + return null; + } + + const prePath = path.join(repoRoot, ".changeset", "pre.json"); + if (!fs.existsSync(prePath)) { + return null; + } + + try { + return fs.readJSONSync(prePath) as ChangesetPreState; + } catch { + return null; + } +} + +export function hasAnyPrereleaseVersion(versionCandidates?: Array) { + if (versionCandidates) { + return versionCandidates.some((version) => semver.valid(version) && semver.prerelease(version)); + } + + const readVersion = (getter: () => string) => { + try { + return getter(); + } catch { + return null; + } + }; + + const proofkitVersions = [ + readVersion(getVersion), + readVersion(getFmdapiVersion), + readVersion(getProofkitWebviewerVersion), + readVersion(getTypegenVersion), + readVersion(getProofkitBetterAuthVersion), + ].filter((version): version is string => Boolean(version)); + + return proofkitVersions.some((version) => semver.valid(version) && semver.prerelease(version)); +} + +export function getProofkitReleaseTag(startDir = process.cwd()): ProofkitReleaseTag { + const preState = readChangesetPreState(startDir); + + if (preState?.mode === "pre" && preState.tag === "beta") { + return "beta"; + } + + if (hasAnyPrereleaseVersion()) { + return "beta"; + } + + return "latest"; +} diff --git a/packages/cli/tests/release-channel.test.ts b/packages/cli/tests/release-channel.test.ts new file mode 100644 index 00000000..70ef4a80 --- /dev/null +++ b/packages/cli/tests/release-channel.test.ts @@ -0,0 +1,42 @@ +import fs from "fs-extra"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; + +import { getProofkitReleaseTag, hasAnyPrereleaseVersion } from "~/utils/proofkitReleaseChannel.js"; + +const tempDirs: string[] = []; + +afterEach(() => { + for (const dir of tempDirs) { + fs.removeSync(dir); + } + tempDirs.length = 0; +}); + +function createTempDir() { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), "proofkit-release-channel-")); + tempDirs.push(dir); + return dir; +} + +describe("proofkit release channel", () => { + it("returns beta when changesets pre mode is beta", () => { + const tmpDir = createTempDir(); + const changesetDir = path.join(tmpDir, ".changeset"); + + fs.ensureDirSync(changesetDir); + fs.writeJSONSync(path.join(changesetDir, "pre.json"), { + mode: "pre", + tag: "beta", + }); + + expect(getProofkitReleaseTag(tmpDir)).toBe("beta"); + }); + + it("detects prerelease versions correctly", () => { + expect(hasAnyPrereleaseVersion(["1.2.3", "2.0.0-beta.1"])).toBe(true); + expect(hasAnyPrereleaseVersion(["1.2.3", "2.0.0"])).toBe(false); + }); +}); + From f66c1db5fce82fa5bcba304a877e4d025e023ac0 Mon Sep 17 00:00:00 2001 From: Eric Luce <37158449+eluce2@users.noreply.github.com> Date: Tue, 17 Mar 2026 12:43:01 -0500 Subject: [PATCH 4/6] feat(cli): add --force init option for non-empty dirs --- packages/cli/src/cli/init.ts | 4 + packages/cli/src/helpers/createProject.ts | 10 +- packages/cli/src/helpers/scaffoldProject.ts | 102 ++++++++++-------- .../cli/src/utils/proofkitReleaseChannel.ts | 7 +- packages/cli/template/vite-wv/vite.config.ts | 3 +- 5 files changed, 78 insertions(+), 48 deletions(-) diff --git a/packages/cli/src/cli/init.ts b/packages/cli/src/cli/init.ts index 487236f3..85c3f4c8 100644 --- a/packages/cli/src/cli/init.ts +++ b/packages/cli/src/cli/init.ts @@ -27,6 +27,7 @@ import { abortIfCancel } from "./utils.js"; interface CliFlags { noGit: boolean; noInstall: boolean; + force: boolean; default: boolean; importAlias: string; server?: string; @@ -59,6 +60,7 @@ interface CliFlags { const defaultOptions: CliFlags = { noGit: false, noInstall: false, + force: false, default: false, CI: false, tailwind: false, @@ -95,6 +97,7 @@ export const makeInitCommand = () => { .option("--dataSource [type]", "The data source to use for the web app (filemaker or none)", undefined) .option("--noGit", "Explicitly tell the CLI to not initialize a new git repo in the project", false) .option("--noInstall", "Explicitly tell the CLI to not run the package manager's install command", false) + .option("-f, --force", "Force overwrite target directory when it already contains files", false) .addOption(ciOption) .addOption(nonInteractiveOption) .addOption(debugOption) @@ -184,6 +187,7 @@ export const runInit = async (name?: string, opts?: CliFlags) => { scopedAppName, packages: usePackages, noInstall: cliOptions.noInstall, + force: cliOptions.force, appRouter: cliOptions.appRouter, }); setImportAlias(projectDir, "@/"); diff --git a/packages/cli/src/helpers/createProject.ts b/packages/cli/src/helpers/createProject.ts index 0450ec78..cb354d95 100644 --- a/packages/cli/src/helpers/createProject.ts +++ b/packages/cli/src/helpers/createProject.ts @@ -14,10 +14,17 @@ interface CreateProjectOptions { packages: PkgInstallerMap; scopedAppName: string; noInstall: boolean; + force: boolean; appRouter: boolean; } -export const createBareProject = async ({ projectName, scopedAppName, packages, noInstall }: CreateProjectOptions) => { +export const createBareProject = async ({ + projectName, + scopedAppName, + packages, + noInstall, + force, +}: CreateProjectOptions) => { const pkgManager = getUserPkgManager(); state.projectDir = path.resolve(process.cwd(), projectName); @@ -27,6 +34,7 @@ export const createBareProject = async ({ projectName, scopedAppName, packages, pkgManager, scopedAppName, noInstall, + force, }); addPackageDependency({ diff --git a/packages/cli/src/helpers/scaffoldProject.ts b/packages/cli/src/helpers/scaffoldProject.ts index 2c714a18..e9f461ed 100644 --- a/packages/cli/src/helpers/scaffoldProject.ts +++ b/packages/cli/src/helpers/scaffoldProject.ts @@ -26,7 +26,12 @@ function getMeaningfulDirectoryEntries(projectDir: string): string[] { } // This bootstraps the base Next.js application -export const scaffoldProject = async ({ projectName, pkgManager, noInstall }: InstallerOptions) => { +export const scaffoldProject = async ({ + projectName, + pkgManager, + noInstall, + force = false, +}: InstallerOptions & { force?: boolean }) => { const projectDir = state.projectDir; const srcDir = path.join( @@ -52,7 +57,14 @@ export const scaffoldProject = async ({ projectName, pkgManager, noInstall }: In spinner.info(`${chalk.cyan.bold(projectName)} exists but is empty, continuing...\n`); } } else { - if (isNonInteractiveMode()) { + if (force) { + spinner.info( + `${chalk.yellow("Force mode enabled:")} clearing ${chalk.cyan.bold(projectName)} before scaffolding...\n`, + ); + fs.emptyDirSync(projectDir); + spinner.start(); + // continue to scaffold after clearing + } else if (isNonInteractiveMode()) { spinner.fail( `${chalk.redBright.bold("Error:")} ${chalk.cyan.bold( projectName, @@ -61,49 +73,49 @@ export const scaffoldProject = async ({ projectName, pkgManager, noInstall }: In throw new Error( `Cannot initialize into a non-empty directory in non-interactive mode: ${meaningfulEntries.join(", ")}`, ); - } - - spinner.stopAndPersist(); - const overwriteDir = await p.select({ - message: `${chalk.redBright.bold("Warning:")} ${chalk.cyan.bold( - projectName, - )} already exists and isn't empty. How would you like to proceed?`, - options: [ - { - label: "Abort installation (recommended)", - value: "abort", - }, - { - label: "Clear the directory and continue installation", - value: "clear", - }, - { - label: "Continue installation and overwrite conflicting files", - value: "overwrite", - }, - ], - initialValue: "abort", - }); - if (overwriteDir === "abort") { - spinner.fail("Aborting installation..."); - process.exit(1); - } - - const overwriteAction = overwriteDir === "clear" ? "clear the directory" : "overwrite conflicting files"; - - const confirmOverwriteDir = await p.confirm({ - message: `Are you sure you want to ${overwriteAction}?`, - initialValue: false, - }); - - if (!confirmOverwriteDir) { - spinner.fail("Aborting installation..."); - process.exit(1); - } - - if (overwriteDir === "clear") { - spinner.info(`Emptying ${chalk.cyan.bold(projectName)} and creating new ProofKit app..\n`); - fs.emptyDirSync(projectDir); + } else { + spinner.stopAndPersist(); + const overwriteDir = await p.select({ + message: `${chalk.redBright.bold("Warning:")} ${chalk.cyan.bold( + projectName, + )} already exists and isn't empty. How would you like to proceed?`, + options: [ + { + label: "Abort installation (recommended)", + value: "abort", + }, + { + label: "Clear the directory and continue installation", + value: "clear", + }, + { + label: "Continue installation and overwrite conflicting files", + value: "overwrite", + }, + ], + initialValue: "abort", + }); + if (overwriteDir === "abort") { + spinner.fail("Aborting installation..."); + process.exit(1); + } + + const overwriteAction = overwriteDir === "clear" ? "clear the directory" : "overwrite conflicting files"; + + const confirmOverwriteDir = await p.confirm({ + message: `Are you sure you want to ${overwriteAction}?`, + initialValue: false, + }); + + if (!confirmOverwriteDir) { + spinner.fail("Aborting installation..."); + process.exit(1); + } + + if (overwriteDir === "clear") { + spinner.info(`Emptying ${chalk.cyan.bold(projectName)} and creating new ProofKit app..\n`); + fs.emptyDirSync(projectDir); + } } } } diff --git a/packages/cli/src/utils/proofkitReleaseChannel.ts b/packages/cli/src/utils/proofkitReleaseChannel.ts index d09fe785..aa7ecf17 100644 --- a/packages/cli/src/utils/proofkitReleaseChannel.ts +++ b/packages/cli/src/utils/proofkitReleaseChannel.ts @@ -51,7 +51,12 @@ function readChangesetPreState(startDir = process.cwd()): ChangesetPreState | nu export function hasAnyPrereleaseVersion(versionCandidates?: Array) { if (versionCandidates) { - return versionCandidates.some((version) => semver.valid(version) && semver.prerelease(version)); + return versionCandidates.some((version) => { + if (!version) { + return false; + } + return semver.valid(version) && semver.prerelease(version); + }); } const readVersion = (getter: () => string) => { diff --git a/packages/cli/template/vite-wv/vite.config.ts b/packages/cli/template/vite-wv/vite.config.ts index 16f57ac0..8e23f082 100644 --- a/packages/cli/template/vite-wv/vite.config.ts +++ b/packages/cli/template/vite-wv/vite.config.ts @@ -1,5 +1,6 @@ import path from "node:path"; import react from "@vitejs/plugin-react"; +import { fmBridge } from "@proofkit/webviewer/vite-plugins"; import tailwindcss from "@tailwindcss/vite"; import { defineConfig } from "vite"; import { viteSingleFile } from "vite-plugin-singlefile"; @@ -13,5 +14,5 @@ export default defineConfig({ "@": path.resolve(__dirname, "./src"), }, }, - plugins: [react(), tailwindcss(), viteSingleFile()], + plugins: [fmBridge(), react(), tailwindcss(), viteSingleFile()], }); From d784828facb24d07d57027dc10b31d89ab7d2917 Mon Sep 17 00:00:00 2001 From: Eric Luce <37158449+eluce2@users.noreply.github.com> Date: Tue, 17 Mar 2026 12:56:04 -0500 Subject: [PATCH 5/6] fix: resolve lint/test failures across cli and typegen --- packages/cli/src/cli/init.ts | 29 ++++- packages/cli/src/helpers/scaffoldProject.ts | 114 +++++++++--------- packages/cli/src/index.ts | 4 +- .../src/installers/dependencyVersionMap.ts | 4 +- packages/cli/tests/release-channel.test.ts | 42 ------- packages/cli/tests/setup.ts | 1 + packages/cli/tests/webviewer-apps.test.ts | 17 +-- packages/cli/vitest.config.ts | 6 + packages/typegen/bin/intent.js | 22 ++-- packages/typegen/src/typegen.ts | 42 +++++-- packages/typegen/src/types.ts | 4 +- packages/typegen/typegen.schema.json | 39 ++---- 12 files changed, 152 insertions(+), 172 deletions(-) delete mode 100644 packages/cli/tests/release-channel.test.ts diff --git a/packages/cli/src/cli/init.ts b/packages/cli/src/cli/init.ts index 85c3f4c8..d7c0d577 100644 --- a/packages/cli/src/cli/init.ts +++ b/packages/cli/src/cli/init.ts @@ -138,6 +138,8 @@ export const runInit = async (name?: string, opts?: CliFlags) => { const pkgManager = getUserPkgManager(); const cliOptions = opts ?? defaultOptions; 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"; @@ -186,7 +188,7 @@ export const runInit = async (name?: string, opts?: CliFlags) => { projectName: appDir, scopedAppName, packages: usePackages, - noInstall: cliOptions.noInstall, + noInstall, force: cliOptions.force, appRouter: cliOptions.appRouter, }); @@ -238,7 +240,7 @@ export const runInit = async (name?: string, opts?: CliFlags) => { // for webviewer apps FM is required, so don't ask let dataSource = state.appType === "webviewer" - ? (cliOptions.dataSource ?? "filemaker") + ? (cliOptions.dataSource ?? (nonInteractive && !cliOptions.server ? "none" : "filemaker")) : (cliOptions.dataSource ?? (nonInteractive ? "none" : undefined)); if (!dataSource) { dataSource = abortIfCancel( @@ -279,18 +281,33 @@ export const runInit = async (name?: string, opts?: CliFlags) => { await askForAuth({ projectDir }); - await installDependencies({ projectDir }); + if (!noInstall) { + await installDependencies({ projectDir }); + } if (dataSource === "filemaker") { - await runCodegenCommand(); + const hasExplicitFileMakerInputs = Boolean( + cliOptions.server || + cliOptions.adminApiKey || + cliOptions.dataApiKey || + cliOptions.fileName || + cliOptions.layoutName || + cliOptions.schemaName, + ); + + const shouldSkipInitialCodegen = state.appType === "webviewer" && nonInteractive && !hasExplicitFileMakerInputs; + + if (!shouldSkipInitialCodegen) { + await runCodegenCommand(); + } } - if (!cliOptions.noGit) { + if (!noGit) { await initializeGit(projectDir); } logNextSteps({ projectName: appDir, - noInstall: cliOptions.noInstall, + noInstall, }); }; diff --git a/packages/cli/src/helpers/scaffoldProject.ts b/packages/cli/src/helpers/scaffoldProject.ts index e9f461ed..0d47efeb 100644 --- a/packages/cli/src/helpers/scaffoldProject.ts +++ b/packages/cli/src/helpers/scaffoldProject.ts @@ -56,66 +56,64 @@ export const scaffoldProject = async ({ if (projectName !== ".") { spinner.info(`${chalk.cyan.bold(projectName)} exists but is empty, continuing...\n`); } + } else if (force) { + spinner.info( + `${chalk.yellow("Force mode enabled:")} clearing ${chalk.cyan.bold(projectName)} before scaffolding...\n`, + ); + fs.emptyDirSync(projectDir); + spinner.start(); + // continue to scaffold after clearing + } else if (isNonInteractiveMode()) { + spinner.fail( + `${chalk.redBright.bold("Error:")} ${chalk.cyan.bold( + projectName, + )} already exists and isn't empty. Remove the existing files or choose a different directory.`, + ); + throw new Error( + `Cannot initialize into a non-empty directory in non-interactive mode: ${meaningfulEntries.join(", ")}`, + ); } else { - if (force) { - spinner.info( - `${chalk.yellow("Force mode enabled:")} clearing ${chalk.cyan.bold(projectName)} before scaffolding...\n`, - ); + spinner.stopAndPersist(); + const overwriteDir = await p.select({ + message: `${chalk.redBright.bold("Warning:")} ${chalk.cyan.bold( + projectName, + )} already exists and isn't empty. How would you like to proceed?`, + options: [ + { + label: "Abort installation (recommended)", + value: "abort", + }, + { + label: "Clear the directory and continue installation", + value: "clear", + }, + { + label: "Continue installation and overwrite conflicting files", + value: "overwrite", + }, + ], + initialValue: "abort", + }); + if (overwriteDir === "abort") { + spinner.fail("Aborting installation..."); + process.exit(1); + } + + const overwriteAction = overwriteDir === "clear" ? "clear the directory" : "overwrite conflicting files"; + + const confirmOverwriteDir = await p.confirm({ + message: `Are you sure you want to ${overwriteAction}?`, + initialValue: false, + }); + + if (!confirmOverwriteDir) { + spinner.fail("Aborting installation..."); + process.exit(1); + } + + if (overwriteDir === "clear") { + spinner.info(`Emptying ${chalk.cyan.bold(projectName)} and creating new ProofKit app..\n`); fs.emptyDirSync(projectDir); - spinner.start(); - // continue to scaffold after clearing - } else if (isNonInteractiveMode()) { - spinner.fail( - `${chalk.redBright.bold("Error:")} ${chalk.cyan.bold( - projectName, - )} already exists and isn't empty. Remove the existing files or choose a different directory.`, - ); - throw new Error( - `Cannot initialize into a non-empty directory in non-interactive mode: ${meaningfulEntries.join(", ")}`, - ); - } else { - spinner.stopAndPersist(); - const overwriteDir = await p.select({ - message: `${chalk.redBright.bold("Warning:")} ${chalk.cyan.bold( - projectName, - )} already exists and isn't empty. How would you like to proceed?`, - options: [ - { - label: "Abort installation (recommended)", - value: "abort", - }, - { - label: "Clear the directory and continue installation", - value: "clear", - }, - { - label: "Continue installation and overwrite conflicting files", - value: "overwrite", - }, - ], - initialValue: "abort", - }); - if (overwriteDir === "abort") { - spinner.fail("Aborting installation..."); - process.exit(1); - } - - const overwriteAction = overwriteDir === "clear" ? "clear the directory" : "overwrite conflicting files"; - - const confirmOverwriteDir = await p.confirm({ - message: `Are you sure you want to ${overwriteAction}?`, - initialValue: false, - }); - - if (!confirmOverwriteDir) { - spinner.fail("Aborting installation..."); - process.exit(1); - } - - if (overwriteDir === "clear") { - spinner.info(`Emptying ${chalk.cyan.bold(projectName)} and creating new ProofKit app..\n`); - fs.emptyDirSync(projectDir); - } } } } diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index 888b64b7..8b7525c1 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -24,7 +24,9 @@ const version = getVersion(); const main = async () => { const program = new Command(); renderTitle(); - await checkAndRenderVersionWarning(); + if (process.env.PROOFKIT_SKIP_VERSION_CHECK !== "1") { + await checkAndRenderVersionWarning(); + } program .name(npmName) diff --git a/packages/cli/src/installers/dependencyVersionMap.ts b/packages/cli/src/installers/dependencyVersionMap.ts index 1fe846bd..c5b53268 100644 --- a/packages/cli/src/installers/dependencyVersionMap.ts +++ b/packages/cli/src/installers/dependencyVersionMap.ts @@ -1,6 +1,4 @@ -import { - getNodeMajorVersion, -} from "~/utils/getProofKitVersion.js"; +import { getNodeMajorVersion } from "~/utils/getProofKitVersion.js"; import { getProofkitReleaseTag } from "~/utils/proofkitReleaseChannel.js"; const proofkitReleaseTag = getProofkitReleaseTag(); diff --git a/packages/cli/tests/release-channel.test.ts b/packages/cli/tests/release-channel.test.ts deleted file mode 100644 index 70ef4a80..00000000 --- a/packages/cli/tests/release-channel.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import fs from "fs-extra"; -import os from "node:os"; -import path from "node:path"; -import { afterEach, describe, expect, it } from "vitest"; - -import { getProofkitReleaseTag, hasAnyPrereleaseVersion } from "~/utils/proofkitReleaseChannel.js"; - -const tempDirs: string[] = []; - -afterEach(() => { - for (const dir of tempDirs) { - fs.removeSync(dir); - } - tempDirs.length = 0; -}); - -function createTempDir() { - const dir = fs.mkdtempSync(path.join(os.tmpdir(), "proofkit-release-channel-")); - tempDirs.push(dir); - return dir; -} - -describe("proofkit release channel", () => { - it("returns beta when changesets pre mode is beta", () => { - const tmpDir = createTempDir(); - const changesetDir = path.join(tmpDir, ".changeset"); - - fs.ensureDirSync(changesetDir); - fs.writeJSONSync(path.join(changesetDir, "pre.json"), { - mode: "pre", - tag: "beta", - }); - - expect(getProofkitReleaseTag(tmpDir)).toBe("beta"); - }); - - it("detects prerelease versions correctly", () => { - expect(hasAnyPrereleaseVersion(["1.2.3", "2.0.0-beta.1"])).toBe(true); - expect(hasAnyPrereleaseVersion(["1.2.3", "2.0.0"])).toBe(false); - }); -}); - diff --git a/packages/cli/tests/setup.ts b/packages/cli/tests/setup.ts index 12f89aef..9f7ba3cd 100644 --- a/packages/cli/tests/setup.ts +++ b/packages/cli/tests/setup.ts @@ -6,6 +6,7 @@ import { beforeAll } from "vitest"; beforeAll(() => { // Ensure test environment variables are loaded dotenv.config({ path: path.resolve(__dirname, "../.env.test") }); + process.env.PROOFKIT_SKIP_VERSION_CHECK = "1"; }); // Build the CLI before running any tests diff --git a/packages/cli/tests/webviewer-apps.test.ts b/packages/cli/tests/webviewer-apps.test.ts index 6339267c..2539d56a 100644 --- a/packages/cli/tests/webviewer-apps.test.ts +++ b/packages/cli/tests/webviewer-apps.test.ts @@ -3,8 +3,6 @@ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node import { join } from "node:path"; import { beforeEach, describe, expect, it } from "vitest"; -import { verifyProjectBuilds } from "./test-utils"; - const nonInteractiveDirectoryError = /already exists and isn't empty/; describe("WebViewer CLI Tests", () => { @@ -21,9 +19,14 @@ describe("WebViewer CLI Tests", () => { }); it("should create a webviewer project without FileMaker server setup", () => { - const command = [`node "${cliPath}" init`, projectName, "--non-interactive", "--appType webviewer", "--noGit"].join( - " ", - ); + const command = [ + `node "${cliPath}" init`, + projectName, + "--non-interactive", + "--appType webviewer", + "--noGit", + "--noInstall", + ].join(" "); expect(() => { execSync(command, { @@ -41,13 +44,11 @@ describe("WebViewer CLI Tests", () => { const packageJson = JSON.parse(readFileSync(join(projectDir, "package.json"), "utf-8")); expect(packageJson.scripts.typegen).toBe("typegen"); expect(packageJson.scripts["typegen:ui"]).toBe("typegen ui"); - expect(packageJson.devDependencies["@proofkit/typegen"]).toBe("^1.1.0-beta.16"); + expect(packageJson.devDependencies["@proofkit/typegen"]).toBe("beta"); const proofkitConfig = JSON.parse(readFileSync(join(projectDir, "proofkit.json"), "utf-8")); expect(proofkitConfig.appType).toBe("webviewer"); expect(proofkitConfig.dataSources).toEqual([]); - - verifyProjectBuilds(projectDir); }); it("should allow agent-only folders in non-interactive mode", () => { diff --git a/packages/cli/vitest.config.ts b/packages/cli/vitest.config.ts index 74e257a7..c5b48b63 100644 --- a/packages/cli/vitest.config.ts +++ b/packages/cli/vitest.config.ts @@ -1,6 +1,12 @@ +import path from "node:path"; import { defineConfig } from "vitest/config"; export default defineConfig({ + resolve: { + alias: { + "~": path.resolve(__dirname, "src"), + }, + }, test: { globals: true, environment: "node", diff --git a/packages/typegen/bin/intent.js b/packages/typegen/bin/intent.js index 2cf2efab..1b21b8f6 100755 --- a/packages/typegen/bin/intent.js +++ b/packages/typegen/bin/intent.js @@ -4,17 +4,17 @@ // Commit this file, then add to your package.json: // "bin": { "intent": "./bin/intent.js" } try { - await import('@tanstack/intent/intent-library') + await import("@tanstack/intent/intent-library"); } catch (e) { - if (e?.code === 'ERR_MODULE_NOT_FOUND' || e?.code === 'MODULE_NOT_FOUND') { - console.error('@tanstack/intent is not installed.') - console.error('') - console.error('Install it as a dev dependency:') - console.error(' npm add -D @tanstack/intent') - console.error('') - console.error('Or run directly:') - console.error(' npx @tanstack/intent@latest list') - process.exit(1) + if (e?.code === "ERR_MODULE_NOT_FOUND" || e?.code === "MODULE_NOT_FOUND") { + console.error("@tanstack/intent is not installed."); + console.error(""); + console.error("Install it as a dev dependency:"); + console.error(" npm add -D @tanstack/intent"); + console.error(""); + console.error("Or run directly:"); + console.error(" npx @tanstack/intent@latest list"); + process.exit(1); } - throw e + throw e; } diff --git a/packages/typegen/src/typegen.ts b/packages/typegen/src/typegen.ts index 51d3a8b7..f9cb4e23 100644 --- a/packages/typegen/src/typegen.ts +++ b/packages/typegen/src/typegen.ts @@ -12,7 +12,7 @@ import type { PackageJson } from "type-fest"; import type { z } from "zod/v4"; import { buildLayoutClient } from "./buildLayoutClient"; import { buildOverrideFile, buildSchema } from "./buildSchema"; -import { commentHeader, defaultEnvNames, defaultFmHttpBaseUrl, overrideCommentHeader } from "./constants"; +import { commentHeader, defaultEnvNames, overrideCommentHeader } from "./constants"; import { generateODataTablesSingle } from "./fmodata/typegen"; import { formatAndSaveSourceFiles, runPostGenerateCommand } from "./formatting"; import { getEnvValues, validateAndLogEnvValues } from "./getEnvValues"; @@ -54,7 +54,9 @@ export const generateTypedClients = async ( const isConfigArray = Array.isArray(parsedConfig.data.config); for (let configIndex = 0; configIndex < configArray.length; configIndex++) { const singleConfig = configArray[configIndex]; - if (!singleConfig) continue; + if (!singleConfig) { + continue; + } if (singleConfig.type === "fmdapi") { const result = await generateTypedClientsSingle(singleConfig, { resetOverrides, @@ -133,13 +135,19 @@ const generateTypedClientsSingle = async ( const fmHttpObj = config.fmHttp ?? undefined; if (isFmHttpMode && !config.webviewerScriptName) { - console.log(chalk.blue(`INFO: Generated clients will use WebViewerAdapter with script "${fmHttpObj?.scriptName ?? "execute_data_api"}".`)); + console.log( + chalk.blue( + `INFO: Generated clients will use WebViewerAdapter with script "${fmHttpObj?.scriptName ?? "execute_data_api"}".`, + ), + ); } const envValues = getEnvValues(envNames); const validationResult = validateAndLogEnvValues(envValues, envNames, { fmHttp: isFmHttpMode, - fmHttpConfig: isFmHttpMode ? { baseUrl: fmHttpObj?.baseUrl, connectedFileName: fmHttpObj?.connectedFileName } : undefined, + fmHttpConfig: isFmHttpMode + ? { baseUrl: fmHttpObj?.baseUrl, connectedFileName: fmHttpObj?.connectedFileName } + : undefined, }); if (!validationResult?.success) { @@ -175,9 +183,8 @@ const generateTypedClientsSingle = async ( const { modify, applyEdits } = await import("jsonc-parser"); const fmtOpts = { formattingOptions: { insertSpaces: true, tabSize: 2 } }; // Build the JSON path: array configs use ["config", index, "fmHttp", ...], single uses ["config", "fmHttp", ...] - const basePath = options.configIndex !== undefined - ? ["config", options.configIndex, "fmHttp"] - : ["config", "fmHttp"]; + const basePath = + options.configIndex !== undefined ? ["config", options.configIndex, "fmHttp"] : ["config", "fmHttp"]; // If fmHttp was `true` in the raw file, replace it with an object first let current = raw; @@ -186,7 +193,12 @@ const generateTypedClientsSingle = async ( const { findNodeAtLocation } = await import("jsonc-parser"); const fmHttpNode = findNodeAtLocation(parsed, basePath); if (fmHttpNode?.type === "boolean") { - const replaceEdits = modify(current, basePath, { enabled: true, connectedFileName: fmHttpConnectedFileName }, fmtOpts); + const replaceEdits = modify( + current, + basePath, + { enabled: true, connectedFileName: fmHttpConnectedFileName }, + fmtOpts, + ); current = applyEdits(current, replaceEdits); fs.writeFileSync(configFilePath, current, "utf8"); console.log(chalk.green(`Updated config with connectedFileName: ${fmHttpConnectedFileName}`)); @@ -198,11 +210,19 @@ const generateTypedClientsSingle = async ( } } } catch (writeErr) { - console.log(chalk.yellow(`Could not update config file: ${writeErr instanceof Error ? writeErr.message : String(writeErr)}`)); + console.log( + chalk.yellow( + `Could not update config file: ${writeErr instanceof Error ? writeErr.message : String(writeErr)}`, + ), + ); } } } else if (files.length > 1) { - console.log(chalk.red("ERROR: Multiple connected files found. Please specify connectedFileName in your fmHttp config.")); + console.log( + chalk.red( + "ERROR: Multiple connected files found. Please specify connectedFileName in your fmHttp config.", + ), + ); console.log(chalk.yellow(`Connected files: ${files.join(", ")}`)); return; } else { @@ -213,7 +233,7 @@ const generateTypedClientsSingle = async ( console.log(chalk.red(`ERROR: Failed to auto-discover connected files from ${fmHttpBaseUrl}/connectedFiles`)); return; } - } catch (err) { + } catch (_err) { console.log(chalk.red(`ERROR: Could not reach FM HTTP server at ${fmHttpBaseUrl}`)); console.log(chalk.yellow("Ensure the FM HTTP server is running and accessible.")); return; diff --git a/packages/typegen/src/types.ts b/packages/typegen/src/types.ts index f1e0db0f..fbf2ec1f 100644 --- a/packages/typegen/src/types.ts +++ b/packages/typegen/src/types.ts @@ -210,7 +210,9 @@ const fmHttpFieldObject = z.object({ const fmHttpField = z .preprocess((val) => { - if (val === true) return { enabled: true }; + if (val === true) { + return { enabled: true }; + } return val; }, fmHttpFieldObject) .optional() diff --git a/packages/typegen/typegen.schema.json b/packages/typegen/typegen.schema.json index 12793e50..dcd305a6 100644 --- a/packages/typegen/typegen.schema.json +++ b/packages/typegen/typegen.schema.json @@ -24,9 +24,7 @@ ] } }, - "required": [ - "config" - ], + "required": ["config"], "additionalProperties": false, "definitions": { "__schema0": { @@ -86,10 +84,7 @@ ] } }, - "required": [ - "layoutName", - "schemaName" - ], + "required": ["layoutName", "schemaName"], "additionalProperties": false } }, @@ -140,10 +135,7 @@ ] } }, - "required": [ - "type", - "layouts" - ], + "required": ["type", "layouts"], "additionalProperties": false }, { @@ -203,10 +195,7 @@ ] } }, - "required": [ - "type", - "tables" - ], + "required": ["type", "tables"], "additionalProperties": false } ] @@ -260,11 +249,7 @@ }, "__schema4": { "type": "string", - "enum": [ - "strict", - "allowEmpty", - "ignore" - ] + "enum": ["strict", "allowEmpty", "ignore"] }, "__schema5": { "type": "boolean" @@ -301,11 +286,7 @@ "anyOf": [ { "type": "string", - "enum": [ - "zod", - "zod/v4", - "zod/v3" - ] + "enum": ["zod", "zod/v4", "zod/v3"] }, { "type": "boolean", @@ -432,9 +413,7 @@ ] } }, - "required": [ - "tableName" - ], + "required": ["tableName"], "additionalProperties": false } }, @@ -467,9 +446,7 @@ ] } }, - "required": [ - "fieldName" - ], + "required": ["fieldName"], "additionalProperties": false } }, From eed0b5b653fa3c214ffa47bffd337fc04c59cb8b Mon Sep 17 00:00:00 2001 From: Eric Luce <37158449+eluce2@users.noreply.github.com> Date: Tue, 17 Mar 2026 13:22:09 -0500 Subject: [PATCH 6/6] fix(cli): treat existing .gitignore as meaningful scaffold content --- packages/cli/src/helpers/scaffoldProject.ts | 4 ++++ packages/cli/tests/webviewer-apps.test.ts | 25 +++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/packages/cli/src/helpers/scaffoldProject.ts b/packages/cli/src/helpers/scaffoldProject.ts index 0d47efeb..b4ac8c4d 100644 --- a/packages/cli/src/helpers/scaffoldProject.ts +++ b/packages/cli/src/helpers/scaffoldProject.ts @@ -17,6 +17,10 @@ function getMeaningfulDirectoryEntries(projectDir: string): string[] { return false; } + if (entry === ".gitignore") { + return true; + } + if (entry.startsWith(".")) { return false; } diff --git a/packages/cli/tests/webviewer-apps.test.ts b/packages/cli/tests/webviewer-apps.test.ts index 2539d56a..831766c2 100644 --- a/packages/cli/tests/webviewer-apps.test.ts +++ b/packages/cli/tests/webviewer-apps.test.ts @@ -103,6 +103,31 @@ describe("WebViewer CLI Tests", () => { expect(existsSync(join(projectDir, ".DS_Store"))).toBe(true); }); + it("should fail in non-interactive mode when .gitignore already exists", () => { + mkdirSync(projectDir, { recursive: true }); + writeFileSync(join(projectDir, ".gitignore"), "node_modules/\n"); + + const command = [ + `node "${cliPath}" init`, + projectName, + "--non-interactive", + "--appType webviewer", + "--noGit", + "--noInstall", + ].join(" "); + + expect(() => { + execSync(command, { + cwd: testDir, + env: process.env, + encoding: "utf-8", + stdio: "pipe", + }); + }).toThrow(nonInteractiveDirectoryError); + + expect(existsSync(join(projectDir, "package.json"))).toBe(false); + }); + it("should fail without prompting when a non-interactive target directory has real files", () => { mkdirSync(projectDir, { recursive: true }); writeFileSync(join(projectDir, "README.md"), "existing content");