Skip to content

upgrade csb to latest and update git utils #2449

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion apps/web/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"@codemirror/lang-javascript": "^6.2.3",
"@codemirror/lang-json": "^6.0.1",
"@codemirror/lang-markdown": "^6.1.7",
"@codesandbox/sdk": "^1.1.6",
"@codesandbox/sdk": "^2.0.3",
"@onlook/constants": "*",
"@onlook/db": "*",
"@onlook/email": "*",
Expand Down Expand Up @@ -101,6 +101,7 @@
"zod": "^3.24.3"
},
"devDependencies": {
"@codesandbox/pitcher-client": "^1.1.7",
"@eslint/eslintrc": "^3.3.1",
"@onlook/typescript": "*",
"@tailwindcss/postcss": "^4.0.15",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { SandboxManager } from "@/components/store/editor/sandbox";
import type { WebSocketSession } from "@codesandbox/sdk";
import type { SandboxClient } from "@codesandbox/sdk";

// System reserved names (Windows compatibility)
export const RESERVED_NAMES = [
Expand Down Expand Up @@ -82,7 +82,7 @@ export const doesFolderExist = (files: string[], folderPath: string): boolean =>
});
};

export const createFileInSandbox = async (session: WebSocketSession, filePath: string, content: string = '', sandboxManager: SandboxManager): Promise<void> => {
export const createFileInSandbox = async (session: SandboxClient, filePath: string, content: string = '', sandboxManager: SandboxManager): Promise<void> => {
try {
if (!session) {
throw new Error('No sandbox session available');
Expand All @@ -94,7 +94,7 @@ export const createFileInSandbox = async (session: WebSocketSession, filePath: s
}
};

export const createFolderInSandbox = async (session: WebSocketSession, folderPath: string, sandboxManager: SandboxManager): Promise<void> => {
export const createFolderInSandbox = async (session: SandboxClient, folderPath: string, sandboxManager: SandboxManager): Promise<void> => {
try {
if (!session) {
throw new Error('No sandbox session available');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import { ProcessedFileType, type NextJsProjectValidation, type ProcessedFile } from '@/app/projects/types';
import { api } from '@/trpc/react';
import { Routes } from '@/utils/constants';
import { type SandboxBrowserSession, type WebSocketSession } from '@codesandbox/sdk';
import type { SandboxClient } from '@codesandbox/sdk';
import { connectToSandbox } from '@codesandbox/sdk/browser';
import { SandboxTemplates, Templates } from '@onlook/constants';
import { generate, injectPreloadScript, parse } from '@onlook/parser';
Expand Down Expand Up @@ -92,7 +92,7 @@ export const ProjectCreationProvider = ({
},
});

const browserSession: SandboxBrowserSession = await startSandbox({
const browserSession = await startSandbox({
sandboxId: forkedSandbox.sandboxId,
userId: user.id,
});
Expand Down Expand Up @@ -262,7 +262,7 @@ export const useProjectCreation = (): ProjectCreationContextValue => {
return context;
};

export const uploadToSandbox = async (files: ProcessedFile[], session: WebSocketSession) => {
export const uploadToSandbox = async (files: ProcessedFile[], session: SandboxClient) => {
for (const file of files) {
try {
if (file.type === ProcessedFileType.BINARY) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import type { WatchEvent, Watcher, WebSocketSession } from '@codesandbox/sdk';
import type { SandboxClient, WatchEvent, Watcher } from '@codesandbox/sdk';
import { FileEventBus } from './file-event-bus';

interface FileWatcherOptions {
session: WebSocketSession;
session: SandboxClient;
onFileChange: (event: WatchEvent) => Promise<void>;
excludePatterns?: string[];
fileEventBus: FileEventBus;
}

export class FileWatcher {
private watcher: Watcher | null = null;
private readonly session: WebSocketSession;
private readonly session: SandboxClient;
private readonly onFileChange: (event: WatchEvent) => Promise<void>;
private readonly excludePatterns: string[];
private readonly eventBus: FileEventBus;
Expand Down
4 changes: 2 additions & 2 deletions apps/web/client/src/components/store/editor/sandbox/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ReaddirEntry, WatchEvent, WebSocketSession } from '@codesandbox/sdk';
import type { ReaddirEntry, SandboxClient, WatchEvent } from '@codesandbox/sdk';
import { EXCLUDED_SYNC_DIRECTORIES, JSX_FILE_EXTENSIONS } from '@onlook/constants';
import { type SandboxFile, type TemplateNode } from '@onlook/models';
import { getContentFromTemplateNode, getTemplateNodeChild } from '@onlook/parser';
Expand Down Expand Up @@ -389,7 +389,7 @@ export class SandboxManager {
}
}

async handleFileRenameEvent(event: WatchEvent, session: WebSocketSession) {
async handleFileRenameEvent(event: WatchEvent, session: SandboxClient) {
// This mean rename a file or a folder, move a file or a folder
const [oldPath, newPath] = event.paths;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { api } from '@/trpc/client';
import type { WebSocketSession } from '@codesandbox/sdk';
import type { SandboxClient } from '@codesandbox/sdk';
import { connectToSandbox } from '@codesandbox/sdk/browser';
import { makeAutoObservable } from 'mobx';
import type { EditorEngine } from '../engine';
import { CLISessionImpl, CLISessionType, type CLISession, type TerminalSession } from './terminal';

export class SessionManager {
session: WebSocketSession | null = null;
session: SandboxClient | null = null;
isConnecting = false;
terminalSessions: Map<string, CLISession> = new Map();
activeTerminalSessionId: string = 'cli';
Expand Down Expand Up @@ -36,7 +36,7 @@ export class SessionManager {
return this.terminalSessions.get(id) as TerminalSession | undefined;
}

async createTerminalSessions(session: WebSocketSession) {
async createTerminalSessions(session: SandboxClient) {
const task = new CLISessionImpl('Server (readonly)', CLISessionType.TASK, session, this.editorEngine.error);
this.terminalSessions.set(task.id, task);
const terminal = new CLISessionImpl('CLI', CLISessionType.TERMINAL, session, this.editorEngine.error);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Task, Terminal, WebSocketSession } from '@codesandbox/sdk';
import type { SandboxClient, Task, Terminal } from '@codesandbox/sdk';
import { Terminal as XTerm } from '@xterm/xterm';
import { v4 as uuidv4 } from 'uuid';
import type { ErrorManager } from '../error';
Expand Down Expand Up @@ -37,7 +37,7 @@ export class CLISessionImpl implements CLISession {
constructor(
public readonly name: string,
public readonly type: CLISessionType,
private readonly session: WebSocketSession,
private readonly session: SandboxClient,
private readonly errorManager: ErrorManager,
) {
this.id = uuidv4();
Expand Down
134 changes: 36 additions & 98 deletions apps/web/client/src/components/store/editor/version/git.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
import { type GitCommit } from '@onlook/git';
import {
addCommitNoteCommand,
checkUserEmailCommand,
checkUserNameCommand,
commitCommand,
getCommitNoteCommand,
initCommand,
logCommand,
parseGitLog,
parseGitStatusOutput,
restoreToCommitCommand,
stageAllCommand,
statusCommand,
userEmailCommand,
userNameCommand,
type GitCommit
} from '@onlook/git';
import stripAnsi from 'strip-ansi';
import type { EditorEngine } from '../engine';

export const ONLOOK_DISPLAY_NAME_NOTE_REF = 'refs/notes/onlook-display-name';

export interface GitStatus {
files: string[];
}
Expand Down Expand Up @@ -39,8 +54,8 @@ export class GitManager {
}

// Check if user.name is set
const nameResult = await this.runCommand('git config user.name');
const emailResult = await this.runCommand('git config user.email');
const nameResult = await this.runCommand(checkUserNameCommand());
const emailResult = await this.runCommand(checkUserEmailCommand());

const hasName = nameResult.success && nameResult.output.trim();
const hasEmail = emailResult.success && emailResult.output.trim();
Expand All @@ -52,7 +67,7 @@ export class GitManager {

// Set user.name if not configured
if (!hasName) {
const nameConfigResult = await this.runCommand('git config user.name "Onlook"');
const nameConfigResult = await this.runCommand(userNameCommand());
if (!nameConfigResult.success) {
console.error('Failed to set git user.name:', nameConfigResult.error);
}
Expand All @@ -61,7 +76,7 @@ export class GitManager {
// Set user.email if not configured
if (!hasEmail) {
const emailConfigResult = await this.runCommand(
'git config user.email "support@onlook.com"',
userEmailCommand(),
);
if (!emailConfigResult.success) {
console.error('Failed to set git user.email:', emailConfigResult.error);
Expand Down Expand Up @@ -94,7 +109,7 @@ export class GitManager {
console.log('Initializing git repository...');

// Initialize git repository
const initResult = await this.runCommand('git init');
const initResult = await this.runCommand(initCommand());
if (!initResult.success) {
console.error('Failed to initialize git repository:', initResult.error);
return false;
Expand All @@ -119,14 +134,15 @@ export class GitManager {
*/
async getStatus(): Promise<GitStatus | null> {
try {
const status = await this.editorEngine?.sandbox.session.session?.git.status();
if (!status) {
const statusResult = await this.runCommand(statusCommand());
if (!statusResult.success) {
console.error('Failed to get git status');
return null;
}

const files = parseGitStatusOutput(statusResult.output);
return {
files: Object.keys(status.changedFiles || {}),
files,
};
} catch (error) {
console.error('Failed to get git status:', error);
Expand All @@ -138,15 +154,15 @@ export class GitManager {
* Stage all files
*/
async stageAll(): Promise<GitCommandResult> {
return this.runCommand('git add .');
return this.runCommand(stageAllCommand());
}

/**
* Create a commit
*/
async commit(message: string): Promise<GitCommandResult> {
const escapedMessage = message.replace(/\"/g, '\\"');
return this.runCommand(`git commit --allow-empty --no-verify -m "${escapedMessage}"`);
return this.runCommand(commitCommand(escapedMessage));
}

/**
Expand All @@ -155,11 +171,12 @@ export class GitManager {
async listCommits(): Promise<GitCommit[]> {
try {
const result = await this.runCommand(
'git log --pretty=format:"%H|%an <%ae>|%ad|%s" --date=iso',
logCommand(),
);

if (result.success && result.output) {
return this.parseGitLog(result.output);
const cleanOutput = stripAnsi(result.output);
return parseGitLog(cleanOutput);
}

return [];
Expand All @@ -173,7 +190,7 @@ export class GitManager {
* Checkout/restore to a specific commit
*/
async restoreToCommit(commitOid: string): Promise<GitCommandResult> {
return this.runCommand(`git restore --source ${commitOid} .`);
return this.runCommand(restoreToCommitCommand(commitOid));
}

/**
Expand All @@ -182,7 +199,7 @@ export class GitManager {
async addCommitNote(commitOid: string, displayName: string): Promise<GitCommandResult> {
const escapedDisplayName = displayName.replace(/\"/g, '\\"');
return this.runCommand(
`git notes --ref=${ONLOOK_DISPLAY_NAME_NOTE_REF} add -f -m "${escapedDisplayName}" ${commitOid}`,
addCommitNoteCommand(commitOid, escapedDisplayName),
);
}

Expand All @@ -192,10 +209,10 @@ export class GitManager {
async getCommitNote(commitOid: string): Promise<string | null> {
try {
const result = await this.runCommand(
`git notes --ref=${ONLOOK_DISPLAY_NAME_NOTE_REF} show ${commitOid}`,
getCommitNoteCommand(commitOid),
true,
);
return result.success ? this.formatGitLogOutput(result.output) : null;
return result.success ? stripAnsi(result.output) : null;
} catch (error) {
console.warn('Failed to get commit note', error);
return null;
Expand Down Expand Up @@ -231,83 +248,4 @@ export class GitManager {
};
}
}

/**
* Parse git log output into GitCommit objects
*/
private parseGitLog(rawOutput: string): GitCommit[] {
const cleanOutput = this.formatGitLogOutput(rawOutput);

if (!cleanOutput) {
return [];
}

const commits: GitCommit[] = [];
const lines = cleanOutput.split('\n').filter((line) => line.trim());

for (const line of lines) {
if (!line.trim()) continue;

// Handle the new format: <hash>|<author>|<date>|<message>
// The hash might have a prefix that we need to handle
let cleanLine = line;

// If line starts with escape sequences followed by =, extract everything after =
const escapeMatch = cleanLine.match(/^[^\w]*=?(.+)$/);
if (escapeMatch) {
cleanLine = escapeMatch[1] || '';
}

const parts = cleanLine.split('|');
if (parts.length >= 4) {
const hash = parts[0]?.trim();
const authorLine = parts[1]?.trim();
const dateLine = parts[2]?.trim();
const message = parts.slice(3).join('|').trim();

if (!hash || !authorLine || !dateLine) continue;

// Parse author name and email
const authorMatch = authorLine.match(/^(.+?)\s*<(.+?)>$/);
const authorName = authorMatch?.[1]?.trim() || authorLine;
const authorEmail = authorMatch?.[2]?.trim() || '';

// Parse date to timestamp
const timestamp = Math.floor(new Date(dateLine).getTime() / 1000);

commits.push({
oid: hash,
message: message || 'No message',
author: {
name: authorName,
email: authorEmail,
},
timestamp: timestamp,
displayName: message || null,
});
}
}

return commits;
}

private formatGitLogOutput(input: string): string {
// Handle sequences with ESC characters anywhere within them
// Pattern to match sequences like [?1h<ESC>= and [K<ESC>[?1l<ESC>>
const ansiWithEscPattern = /\[[0-9;?a-zA-Z\x1b]*[a-zA-Z=>/]*/g;

// Handle standard ANSI escape sequences starting with ESC
const ansiEscapePattern = /\x1b\[[0-9;?a-zA-Z]*[a-zA-Z=>/]*/g;

// Handle control characters
const controlChars = /[\x00-\x09\x0B-\x1F\x7F]/g;

const cleanOutput = input
.replace(ansiWithEscPattern, '') // Remove sequences with ESC chars in middle
.replace(ansiEscapePattern, '') // Remove standard ESC sequences
.replace(controlChars, '') // Remove control characters
.trim();

return cleanOutput;
}
}
Loading
Loading