mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-05-05 20:54:40 +00:00
* 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
230 lines
6.3 KiB
TypeScript
230 lines
6.3 KiB
TypeScript
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);
|
|
},
|
|
};
|
|
}
|
|
}
|