From 0202aefad9b69ee2f171049ee103b4a9fdc23284 Mon Sep 17 00:00:00 2001 From: Stijnus <72551117+Stijnus@users.noreply.github.com> Date: Tue, 8 Apr 2025 22:20:54 +0200 Subject: [PATCH] feat: fix for push private repo (#1618) * feat: push private repo # GitHub Integration Changelog ## Fixed - Fixed issue where repositories marked as private weren't being created with private visibility - Added support for changing repository visibility (public/private) when pushing to existing repositories - Fixed 404 errors when pushing files after changing repository visibility ## Added - Added clear user warnings when changing repository visibility from public to private or vice versa - Implemented delays after visibility changes to allow GitHub API to fully process the change - Added retry mechanism (up to 3 attempts with increasing delays) for pushing files after visibility changes - Added repository data refresh before pushing to ensure latest reference data ## Improved - Enhanced error logging and handling for all GitHub API operations - Updated return value handling to use actual repository URLs from the API response - Added comprehensive logging to track repository creation and update operations * cleanup * Update Workbench.client.tsx --- .eslintrc.json | 15 ++ .../components/PushToGitHubDialog.tsx | 17 +- app/components/ui/use-toast.ts | 28 ++- app/components/workbench/Workbench.client.tsx | 16 +- app/lib/stores/workbench.ts | 202 ++++++++++----- app/utils/file-watcher.ts | 229 ++++++++++++++++++ package.json | 4 +- pnpm-lock.yaml | 52 +++- 8 files changed, 484 insertions(+), 79 deletions(-) create mode 100644 .eslintrc.json create mode 100644 app/utils/file-watcher.ts diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000..3f4eb97d --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,15 @@ +{ + "env": { + "browser": true, + "es2021": true + }, + "extends": [ + "eslint:recommended", + "plugin:prettier/recommended" + ], + "rules": { + // example: turn off console warnings + "no-console": "off" + } + } + \ No newline at end of file diff --git a/app/components/@settings/tabs/connections/components/PushToGitHubDialog.tsx b/app/components/@settings/tabs/connections/components/PushToGitHubDialog.tsx index 350c60f0..43ca4936 100644 --- a/app/components/@settings/tabs/connections/components/PushToGitHubDialog.tsx +++ b/app/components/@settings/tabs/connections/components/PushToGitHubDialog.tsx @@ -136,15 +136,24 @@ export function PushToGitHubDialog({ isOpen, onClose, onPush }: PushToGitHubDial const octokit = new Octokit({ auth: connection.token }); try { - await octokit.repos.get({ + const { data: existingRepo } = await octokit.repos.get({ owner: connection.user.login, repo: repoName, }); // If we get here, the repo exists - const confirmOverwrite = window.confirm( - `Repository "${repoName}" already exists. Do you want to update it? This will add or modify files in the repository.`, - ); + let confirmMessage = `Repository "${repoName}" already exists. Do you want to update it? This will add or modify files in the repository.`; + + // Add visibility change warning if needed + if (existingRepo.private !== isPrivate) { + const visibilityChange = isPrivate + ? 'This will also change the repository from public to private.' + : 'This will also change the repository from private to public.'; + + confirmMessage += `\n\n${visibilityChange}`; + } + + const confirmOverwrite = window.confirm(confirmMessage); if (!confirmOverwrite) { setIsLoading(false); diff --git a/app/components/ui/use-toast.ts b/app/components/ui/use-toast.ts index cadbfef3..95b9fec4 100644 --- a/app/components/ui/use-toast.ts +++ b/app/components/ui/use-toast.ts @@ -1,6 +1,18 @@ import { useCallback } from 'react'; import { toast as toastify } from 'react-toastify'; +// Configure standard toast settings +export const configuredToast = { + success: (message: string, options = {}) => toastify.success(message, { autoClose: 3000, ...options }), + error: (message: string, options = {}) => toastify.error(message, { autoClose: 3000, ...options }), + info: (message: string, options = {}) => toastify.info(message, { autoClose: 3000, ...options }), + warning: (message: string, options = {}) => toastify.warning(message, { autoClose: 3000, ...options }), + loading: (message: string, options = {}) => toastify.loading(message, { autoClose: 3000, ...options }), +}; + +// Export the original toast for cases where specific configuration is needed +export { toastify as toast }; + interface ToastOptions { type?: 'success' | 'error' | 'info' | 'warning'; duration?: number; @@ -36,5 +48,19 @@ export function useToast() { [toast], ); - return { toast, success, error }; + const info = useCallback( + (message: string, options: Omit = {}) => { + toast(message, { ...options, type: 'info' }); + }, + [toast], + ); + + const warning = useCallback( + (message: string, options: Omit = {}) => { + toast(message, { ...options, type: 'warning' }); + }, + [toast], + ); + + return { toast, success, error, info, warning }; } diff --git a/app/components/workbench/Workbench.client.tsx b/app/components/workbench/Workbench.client.tsx index 1408a648..3a87686f 100644 --- a/app/components/workbench/Workbench.client.tsx +++ b/app/components/workbench/Workbench.client.tsx @@ -388,11 +388,9 @@ export const Workbench = memo( Toggle Terminal - - + +
+ Sync & Export setIsPushDialogOpen(false)} - onPush={async (repoName, username, token) => { + onPush={async (repoName, username, token, isPrivate) => { try { - const commitMessage = prompt('Please enter a commit message:', 'Initial commit') || 'Initial commit'; - await workbenchStore.pushToGitHub(repoName, commitMessage, username, token); + console.log('Dialog onPush called with isPrivate =', isPrivate); - const repoUrl = `https://github.com/${username}/${repoName}`; + const commitMessage = prompt('Please enter a commit message:', 'Initial commit') || 'Initial commit'; + const repoUrl = await workbenchStore.pushToGitHub(repoName, commitMessage, username, token, isPrivate); if (updateChatMestaData && !metadata?.gitUrl) { updateChatMestaData({ diff --git a/app/lib/stores/workbench.ts b/app/lib/stores/workbench.ts index 68d83e24..6dda32a1 100644 --- a/app/lib/stores/workbench.ts +++ b/app/lib/stores/workbench.ts @@ -600,7 +600,13 @@ export class WorkbenchStore { return syncedFiles; } - async pushToGitHub(repoName: string, commitMessage?: string, githubUsername?: string, ghToken?: string) { + async pushToGitHub( + repoName: string, + commitMessage?: string, + githubUsername?: string, + ghToken?: string, + isPrivate: boolean = false, + ) { try { // Use cookies if username and token are not provided const githubToken = ghToken || Cookies.get('githubToken'); @@ -610,26 +616,72 @@ export class WorkbenchStore { throw new Error('GitHub token or username is not set in cookies or provided.'); } + // Log the isPrivate flag to verify it's being properly passed + console.log(`pushToGitHub called with isPrivate=${isPrivate}`); + // Initialize Octokit with the auth token const octokit = new Octokit({ auth: githubToken }); // Check if the repository already exists before creating it let repo: RestEndpointMethodTypes['repos']['get']['response']['data']; + let visibilityJustChanged = false; try { const resp = await octokit.repos.get({ owner, repo: repoName }); repo = resp.data; + console.log('Repository already exists, using existing repo'); + + // Check if we need to update visibility of existing repo + if (repo.private !== isPrivate) { + console.log( + `Updating repository visibility from ${repo.private ? 'private' : 'public'} to ${isPrivate ? 'private' : 'public'}`, + ); + + try { + // Update repository visibility using the update method + const { data: updatedRepo } = await octokit.repos.update({ + owner, + repo: repoName, + private: isPrivate, + }); + + console.log('Repository visibility updated successfully'); + repo = updatedRepo; + visibilityJustChanged = true; + + // Add a delay after changing visibility to allow GitHub to fully process the change + console.log('Waiting for visibility change to propagate...'); + await new Promise((resolve) => setTimeout(resolve, 3000)); // 3 second delay + } catch (visibilityError) { + console.error('Failed to update repository visibility:', visibilityError); + + // Continue with push even if visibility update fails + } + } } catch (error) { if (error instanceof Error && 'status' in error && error.status === 404) { // Repository doesn't exist, so create a new one - const { data: newRepo } = await octokit.repos.createForAuthenticatedUser({ + console.log(`Creating new repository with private=${isPrivate}`); + + // Create new repository with specified privacy setting + const createRepoOptions = { name: repoName, - private: false, + private: isPrivate, auto_init: true, - }); + }; + + console.log('Create repo options:', createRepoOptions); + + const { data: newRepo } = await octokit.repos.createForAuthenticatedUser(createRepoOptions); + + console.log('Repository created:', newRepo.html_url, 'Private:', newRepo.private); repo = newRepo; + + // Add a small delay after creating a repository to allow GitHub to fully initialize it + console.log('Waiting for repository to initialize...'); + await new Promise((resolve) => setTimeout(resolve, 2000)); // 2 second delay } else { - console.log('cannot create repo!'); + console.error('Cannot create repo:', error); throw error; // Some other error occurred } } @@ -641,68 +693,102 @@ export class WorkbenchStore { throw new Error('No files found to push'); } - // Create blobs for each file - const blobs = await Promise.all( - Object.entries(files).map(async ([filePath, dirent]) => { - if (dirent?.type === 'file' && dirent.content) { - const { data: blob } = await octokit.git.createBlob({ - owner: repo.owner.login, - repo: repo.name, - content: Buffer.from(dirent.content).toString('base64'), - encoding: 'base64', - }); - return { path: extractRelativePath(filePath), sha: blob.sha }; + // Function to push files with retry logic + const pushFilesToRepo = async (attempt = 1): Promise => { + const maxAttempts = 3; + + try { + console.log(`Pushing files to repository (attempt ${attempt}/${maxAttempts})...`); + + // Create blobs for each file + const blobs = await Promise.all( + Object.entries(files).map(async ([filePath, dirent]) => { + if (dirent?.type === 'file' && dirent.content) { + const { data: blob } = await octokit.git.createBlob({ + owner: repo.owner.login, + repo: repo.name, + content: Buffer.from(dirent.content).toString('base64'), + encoding: 'base64', + }); + return { path: extractRelativePath(filePath), sha: blob.sha }; + } + + return null; + }), + ); + + const validBlobs = blobs.filter(Boolean); // Filter out any undefined blobs + + if (validBlobs.length === 0) { + throw new Error('No valid files to push'); } - return null; - }), - ); + // Refresh repository reference to ensure we have the latest data + const repoRefresh = await octokit.repos.get({ owner, repo: repoName }); + repo = repoRefresh.data; - const validBlobs = blobs.filter(Boolean); // Filter out any undefined blobs + // Get the latest commit SHA (assuming main branch, update dynamically if needed) + const { data: ref } = await octokit.git.getRef({ + owner: repo.owner.login, + repo: repo.name, + ref: `heads/${repo.default_branch || 'main'}`, // Handle dynamic branch + }); + const latestCommitSha = ref.object.sha; - if (validBlobs.length === 0) { - throw new Error('No valid files to push'); - } + // Create a new tree + const { data: newTree } = await octokit.git.createTree({ + owner: repo.owner.login, + repo: repo.name, + base_tree: latestCommitSha, + tree: validBlobs.map((blob) => ({ + path: blob!.path, + mode: '100644', + type: 'blob', + sha: blob!.sha, + })), + }); - // Get the latest commit SHA (assuming main branch, update dynamically if needed) - const { data: ref } = await octokit.git.getRef({ - owner: repo.owner.login, - repo: repo.name, - ref: `heads/${repo.default_branch || 'main'}`, // Handle dynamic branch - }); - const latestCommitSha = ref.object.sha; + // Create a new commit + const { data: newCommit } = await octokit.git.createCommit({ + owner: repo.owner.login, + repo: repo.name, + message: commitMessage || 'Initial commit from your app', + tree: newTree.sha, + parents: [latestCommitSha], + }); - // Create a new tree - const { data: newTree } = await octokit.git.createTree({ - owner: repo.owner.login, - repo: repo.name, - base_tree: latestCommitSha, - tree: validBlobs.map((blob) => ({ - path: blob!.path, - mode: '100644', - type: 'blob', - sha: blob!.sha, - })), - }); + // Update the reference + await octokit.git.updateRef({ + owner: repo.owner.login, + repo: repo.name, + ref: `heads/${repo.default_branch || 'main'}`, // Handle dynamic branch + sha: newCommit.sha, + }); - // Create a new commit - const { data: newCommit } = await octokit.git.createCommit({ - owner: repo.owner.login, - repo: repo.name, - message: commitMessage || 'Initial commit from your app', - tree: newTree.sha, - parents: [latestCommitSha], - }); + console.log('Files successfully pushed to repository'); - // Update the reference - await octokit.git.updateRef({ - owner: repo.owner.login, - repo: repo.name, - ref: `heads/${repo.default_branch || 'main'}`, // Handle dynamic branch - sha: newCommit.sha, - }); + return repo.html_url; + } catch (error) { + console.error(`Error during push attempt ${attempt}:`, error); - alert(`Repository created and code pushed: ${repo.html_url}`); + // If we've just changed visibility and this is not our last attempt, wait and retry + if ((visibilityJustChanged || attempt === 1) && attempt < maxAttempts) { + const delayMs = attempt * 2000; // Increasing delay with each attempt + console.log(`Waiting ${delayMs}ms before retry...`); + await new Promise((resolve) => setTimeout(resolve, delayMs)); + + return pushFilesToRepo(attempt + 1); + } + + throw error; // Rethrow if we're out of attempts + } + }; + + // Execute the push function with retry logic + const repoUrl = await pushFilesToRepo(); + + // Return the repository URL + return repoUrl; } catch (error) { console.error('Error pushing to GitHub:', error); throw error; // Rethrow the error for further handling diff --git a/app/utils/file-watcher.ts b/app/utils/file-watcher.ts new file mode 100644 index 00000000..6917efb3 --- /dev/null +++ b/app/utils/file-watcher.ts @@ -0,0 +1,229 @@ +import type { WebContainer } from '@webcontainer/api'; +import { WORK_DIR } from './constants'; + +// Global object to track watcher state +const watcherState = { + fallbackEnabled: tryLoadFallbackState(), + watchingPaths: new Set(), + callbacks: new Map void>>(), + pollingInterval: null as NodeJS.Timeout | null, +}; + +// Try to load the fallback state from localStorage +function tryLoadFallbackState(): boolean { + try { + if (typeof localStorage !== 'undefined') { + const state = localStorage.getItem('bolt-file-watcher-fallback'); + return state === 'true'; + } + } catch { + console.warn('[FileWatcher] Failed to load fallback state from localStorage'); + } + return false; +} + +// Save the fallback state to localStorage +function saveFallbackState(state: boolean) { + try { + if (typeof localStorage !== 'undefined') { + localStorage.setItem('bolt-file-watcher-fallback', state ? 'true' : 'false'); + } + } catch { + console.warn('[FileWatcher] Failed to save fallback state to localStorage'); + } +} + +/** + * Safe file watcher that falls back to polling when native file watching fails + * + * @param webcontainer The WebContainer instance + * @param pattern File pattern to watch + * @param callback Function to call when files change + * @returns An object with a close method + */ +export async function safeWatch(webcontainer: WebContainer, pattern: string = '**/*', callback: () => void) { + // Register the callback + if (!watcherState.callbacks.has(pattern)) { + watcherState.callbacks.set(pattern, new Set()); + } + + watcherState.callbacks.get(pattern)!.add(callback); + + // If we're already using fallback mode, don't try native watchers again + if (watcherState.fallbackEnabled) { + // Make sure polling is active + ensurePollingActive(); + + // Return a cleanup function + return { + close: () => { + const callbacks = watcherState.callbacks.get(pattern); + + if (callbacks) { + callbacks.delete(callback); + + if (callbacks.size === 0) { + watcherState.callbacks.delete(pattern); + } + } + }, + }; + } + + // Try to use native file watching + try { + const watcher = await webcontainer.fs.watch(pattern, { persistent: true }); + watcherState.watchingPaths.add(pattern); + + // Use the native watch events + (watcher as any).addEventListener('change', () => { + // Call all callbacks for this pattern + const callbacks = watcherState.callbacks.get(pattern); + + if (callbacks) { + callbacks.forEach((cb) => cb()); + } + }); + + // Return an object with a close method + return { + close: () => { + try { + watcher.close(); + watcherState.watchingPaths.delete(pattern); + + const callbacks = watcherState.callbacks.get(pattern); + + if (callbacks) { + callbacks.delete(callback); + + if (callbacks.size === 0) { + watcherState.callbacks.delete(pattern); + } + } + } catch (error) { + console.warn('[FileWatcher] Error closing watcher:', error); + } + }, + }; + } catch (error) { + console.warn('[FileWatcher] Native file watching failed:', error); + console.info('[FileWatcher] Falling back to polling mechanism for file changes'); + + // Switch to fallback mode for all future watches + watcherState.fallbackEnabled = true; + saveFallbackState(true); + + // Start polling + ensurePollingActive(); + + // Return a mock watcher object + return { + close: () => { + const callbacks = watcherState.callbacks.get(pattern); + + if (callbacks) { + callbacks.delete(callback); + + if (callbacks.size === 0) { + watcherState.callbacks.delete(pattern); + } + } + + // If no more callbacks, stop polling + if (watcherState.callbacks.size === 0 && watcherState.pollingInterval) { + clearInterval(watcherState.pollingInterval); + watcherState.pollingInterval = null; + } + }, + }; + } +} + +// Ensure polling is active +function ensurePollingActive() { + if (watcherState.pollingInterval) { + return; + } + + // Set up a polling interval that calls all callbacks + watcherState.pollingInterval = setInterval(() => { + // Call all registered callbacks + for (const [, callbacks] of watcherState.callbacks.entries()) { + callbacks.forEach((callback) => callback()); + } + }, 3000); // Poll every 3 seconds + + // Clean up interval when window unloads + if (typeof window !== 'undefined') { + window.addEventListener('beforeunload', () => { + if (watcherState.pollingInterval) { + clearInterval(watcherState.pollingInterval); + watcherState.pollingInterval = null; + } + }); + } +} + +// SafeWatchPaths mimics the webcontainer.internal.watchPaths method but with fallback +export function safeWatchPaths( + webcontainer: WebContainer, + config: { include: string[]; exclude?: string[]; includeContent?: boolean }, + callback: any, +) { + // Create a valid mock event to prevent undefined errors + const createMockEvent = () => ({ + type: 'change', + path: `${WORK_DIR}/mock-path.txt`, + buffer: new Uint8Array(0), + }); + + // Start with polling if we already know native watching doesn't work + if (watcherState.fallbackEnabled) { + console.info('[FileWatcher] Using fallback polling for watchPaths'); + ensurePollingActive(); + + const interval = setInterval(() => { + // Use our helper to create a valid event + const mockEvent = createMockEvent(); + + // Wrap in the expected structure of nested arrays + callback([[mockEvent]]); + }, 3000); + + return { + close: () => { + clearInterval(interval); + }, + }; + } + + // Try native watching + try { + return webcontainer.internal.watchPaths(config, callback); + } catch (error) { + console.warn('[FileWatcher] Native watchPaths failed:', error); + console.info('[FileWatcher] Using fallback polling for watchPaths'); + + // Mark as using fallback + watcherState.fallbackEnabled = true; + saveFallbackState(true); + + // Set up polling + ensurePollingActive(); + + const interval = setInterval(() => { + // Use our helper to create a valid event + const mockEvent = createMockEvent(); + + // Wrap in the expected structure of nested arrays + callback([[mockEvent]]); + }, 3000); + + return { + close: () => { + clearInterval(interval); + }, + }; + } +} diff --git a/package.json b/package.json index 74f9504a..5c1a793a 100644 --- a/package.json +++ b/package.json @@ -181,13 +181,15 @@ "crypto-browserify": "^3.12.1", "electron": "^33.2.0", "electron-builder": "^25.1.8", + "eslint-config-prettier": "^10.1.1", + "eslint-plugin-prettier": "^5.2.6", "fast-glob": "^3.3.2", "husky": "9.1.7", "is-ci": "^3.0.1", "jsdom": "^26.0.0", "node-fetch": "^3.3.2", "pnpm": "^9.14.4", - "prettier": "^3.4.1", + "prettier": "^3.5.3", "rimraf": "^4.4.1", "sass-embedded": "^1.81.0", "stream-browserify": "^3.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2f87d14d..3cb9c53c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -405,6 +405,12 @@ importers: electron-builder: specifier: ^25.1.8 version: 25.1.8(electron-builder-squirrel-windows@25.1.8(dmg-builder@25.1.8)) + eslint-config-prettier: + specifier: ^10.1.1 + version: 10.1.1(eslint@9.23.0(jiti@1.21.7)) + eslint-plugin-prettier: + specifier: ^5.2.6 + version: 5.2.6(eslint-config-prettier@10.1.1(eslint@9.23.0(jiti@1.21.7)))(eslint@9.23.0(jiti@1.21.7))(prettier@3.5.3) fast-glob: specifier: ^3.3.2 version: 3.3.3 @@ -424,7 +430,7 @@ importers: specifier: ^9.14.4 version: 9.15.9 prettier: - specifier: ^3.4.1 + specifier: ^3.5.3 version: 3.5.3 rimraf: specifier: ^4.4.1 @@ -2186,6 +2192,10 @@ packages: resolution: {integrity: sha512-vsJDAkYR6qCPu+ioGScGiMYR7LvZYIXh/dlQeviqoTWNCVfKTLYD/LkNWH4Mxsv2a5vpIRc77FN5DnmK1eBggQ==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@pkgr/core@0.2.1': + resolution: {integrity: sha512-VzgHzGblFmUeBmmrk55zPyrQIArQN4vujc9shWytaPdB3P7qhi0cpaiKIr7tlCmFv2lYUwnLospIqjL9ZSAhhg==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@polka/url@1.0.0-next.28': resolution: {integrity: sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==} @@ -4554,6 +4564,12 @@ packages: peerDependencies: eslint: '>=6.0.0' + eslint-config-prettier@10.1.1: + resolution: {integrity: sha512-4EQQr6wXwS+ZJSzaR5ZCrYgLxqvUjdXctaEtBqHcbkW944B1NQyO4qpdHQbXBONfwxXdkAY81HH4+LUfrg+zPw==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + eslint-config-prettier@9.1.0: resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} hasBin: true @@ -4577,8 +4593,8 @@ packages: peerDependencies: eslint: '>=6.0.0' - eslint-plugin-prettier@5.2.5: - resolution: {integrity: sha512-IKKP8R87pJyMl7WWamLgPkloB16dagPIdd2FjBDbyRYPKo93wS/NbCOPh6gH+ieNLC+XZrhJt/kWj0PS/DFdmg==} + eslint-plugin-prettier@5.2.6: + resolution: {integrity: sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: '@types/eslint': '>=8.0.0' @@ -7389,6 +7405,10 @@ packages: resolution: {integrity: sha512-R1urvuyiTaWfeCggqEvpDJwAlDVdsT9NM+IP//Tk2x7qHCkSvBk/fwFgw/TLAHzZlrAnnazMcRw0ZD8HlYFTEQ==} engines: {node: ^14.18.0 || >=16.0.0} + synckit@0.11.3: + resolution: {integrity: sha512-szhWDqNNI9etJUvbZ1/cx1StnZx8yMmFxme48SwR4dty4ioSY50KEZlpv0qAfgc1fpRzuh9hBXEzoCpJ779dLg==} + engines: {node: ^14.18.0 || >=16.0.0} + tabbable@6.2.0: resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} @@ -8837,7 +8857,7 @@ snapshots: eslint: 9.23.0(jiti@1.21.7) eslint-config-prettier: 9.1.0(eslint@9.23.0(jiti@1.21.7)) eslint-plugin-jsonc: 2.20.0(eslint@9.23.0(jiti@1.21.7)) - eslint-plugin-prettier: 5.2.5(eslint-config-prettier@9.1.0(eslint@9.23.0(jiti@1.21.7)))(eslint@9.23.0(jiti@1.21.7))(prettier@3.5.3) + eslint-plugin-prettier: 5.2.6(eslint-config-prettier@9.1.0(eslint@9.23.0(jiti@1.21.7)))(eslint@9.23.0(jiti@1.21.7))(prettier@3.5.3) globals: 15.15.0 typescript-eslint: 8.28.0(eslint@9.23.0(jiti@1.21.7))(typescript@5.8.2) transitivePeerDependencies: @@ -9938,6 +9958,8 @@ snapshots: '@pkgr/core@0.2.0': {} + '@pkgr/core@0.2.1': {} + '@polka/url@1.0.0-next.28': {} '@radix-ui/number@1.1.0': {} @@ -12925,6 +12947,10 @@ snapshots: eslint: 9.23.0(jiti@1.21.7) semver: 7.7.1 + eslint-config-prettier@10.1.1(eslint@9.23.0(jiti@1.21.7)): + dependencies: + eslint: 9.23.0(jiti@1.21.7) + eslint-config-prettier@9.1.0(eslint@9.23.0(jiti@1.21.7)): dependencies: eslint: 9.23.0(jiti@1.21.7) @@ -12949,12 +12975,21 @@ snapshots: transitivePeerDependencies: - '@eslint/json' - eslint-plugin-prettier@5.2.5(eslint-config-prettier@9.1.0(eslint@9.23.0(jiti@1.21.7)))(eslint@9.23.0(jiti@1.21.7))(prettier@3.5.3): + eslint-plugin-prettier@5.2.6(eslint-config-prettier@10.1.1(eslint@9.23.0(jiti@1.21.7)))(eslint@9.23.0(jiti@1.21.7))(prettier@3.5.3): dependencies: eslint: 9.23.0(jiti@1.21.7) prettier: 3.5.3 prettier-linter-helpers: 1.0.0 - synckit: 0.10.3 + synckit: 0.11.3 + optionalDependencies: + eslint-config-prettier: 10.1.1(eslint@9.23.0(jiti@1.21.7)) + + eslint-plugin-prettier@5.2.6(eslint-config-prettier@9.1.0(eslint@9.23.0(jiti@1.21.7)))(eslint@9.23.0(jiti@1.21.7))(prettier@3.5.3): + dependencies: + eslint: 9.23.0(jiti@1.21.7) + prettier: 3.5.3 + prettier-linter-helpers: 1.0.0 + synckit: 0.11.3 optionalDependencies: eslint-config-prettier: 9.1.0(eslint@9.23.0(jiti@1.21.7)) @@ -16448,6 +16483,11 @@ snapshots: '@pkgr/core': 0.2.0 tslib: 2.8.1 + synckit@0.11.3: + dependencies: + '@pkgr/core': 0.2.1 + tslib: 2.8.1 + tabbable@6.2.0: {} tailwind-merge@2.6.0: {}