mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-05-05 12:44:38 +00:00
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
This commit is contained in:
parent
552f08acea
commit
0202aefad9
15
.eslintrc.json
Normal file
15
.eslintrc.json
Normal 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"
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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 };
|
||||
}
|
||||
|
@ -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({
|
||||
|
@ -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
229
app/utils/file-watcher.ts
Normal 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);
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
@ -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",
|
||||
|
@ -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: {}
|
||||
|
Loading…
Reference in New Issue
Block a user