feat: fix for push private repo (#1618)
Some checks failed
Docker Publish / docker-build-publish (push) Has been cancelled
Update Stable Branch / prepare-release (push) Has been cancelled
Mark Stale Issues and Pull Requests / stale (push) Has been cancelled

* 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
This commit is contained in:
Stijnus 2025-04-08 22:20:54 +02:00 committed by GitHub
parent 552f08acea
commit 0202aefad9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 484 additions and 79 deletions

15
.eslintrc.json Normal file
View File

@ -0,0 +1,15 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"eslint:recommended",
"plugin:prettier/recommended"
],
"rules": {
// example: turn off console warnings
"no-console": "off"
}
}

View File

@ -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);

View File

@ -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<ToastOptions, 'type'> = {}) => {
toast(message, { ...options, type: 'info' });
},
[toast],
);
const warning = useCallback(
(message: string, options: Omit<ToastOptions, 'type'> = {}) => {
toast(message, { ...options, type: 'warning' });
},
[toast],
);
return { toast, success, error, info, warning };
}

View File

@ -388,11 +388,9 @@ export const Workbench = memo(
Toggle Terminal
</PanelHeaderButton>
<DropdownMenu.Root>
<DropdownMenu.Trigger className="bg-transparent">
<button className="text-sm flex items-center gap-1 text-bolt-elements-item-contentDefault bg-transparent enabled:hover:text-bolt-elements-item-contentActive rounded-md p-1 enabled:hover:bg-bolt-elements-item-backgroundActive disabled:cursor-not-allowed">
<div className="i-ph:box-arrow-up" />
Sync & Export
</button>
<DropdownMenu.Trigger className="text-sm flex items-center gap-1 text-bolt-elements-item-contentDefault bg-transparent enabled:hover:text-bolt-elements-item-contentActive rounded-md p-1 enabled:hover:bg-bolt-elements-item-backgroundActive disabled:cursor-not-allowed">
<div className="i-ph:box-arrow-up" />
Sync & Export
</DropdownMenu.Trigger>
<DropdownMenu.Content
className={classNames(
@ -491,12 +489,12 @@ export const Workbench = memo(
<PushToGitHubDialog
isOpen={isPushDialogOpen}
onClose={() => 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({

View File

@ -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<string> => {
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

229
app/utils/file-watcher.ts Normal file
View File

@ -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<string>(),
callbacks: new Map<string, Set<() => 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);
},
};
}
}

View File

@ -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",

View File

@ -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: {}