diff --git a/package.json b/package.json index af7cfd05d..be2ae15e4 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "scripts": { "setup": "node ./scripts/setup.js", "watch:converters": "npm run watch --workspace=packages/bruno-converters", - "dev": "concurrently --kill-others \"npm run dev:web\" \"npm run dev:electron\"", + "dev": "node ./scripts/dev.js", "watch": "npm run dev:watch", "dev:watch": "node ./scripts/dev-hot-reload.js", "dev:web": "npm run dev --workspace=packages/bruno-app", diff --git a/packages/bruno-app/.gitignore b/packages/bruno-app/.gitignore index 4cf2783ee..9413da0c9 100644 --- a/packages/bruno-app/.gitignore +++ b/packages/bruno-app/.gitignore @@ -34,4 +34,5 @@ yarn-error.log* .next/ dist/ -.env \ No newline at end of file +.env +storybook-static/ \ No newline at end of file diff --git a/packages/bruno-app/src/themes/dark/dark.js b/packages/bruno-app/src/themes/dark/dark.js index 813db6542..1a50ee52e 100644 --- a/packages/bruno-app/src/themes/dark/dark.js +++ b/packages/bruno-app/src/themes/dark/dark.js @@ -1,27 +1,27 @@ -import { rgba } from 'polished'; +import { rgba, lighten } from 'polished'; export const palette = { primary: { SOLID: 'hsl(39, 74%, 59%)', TEXT: 'hsl(39, 74%, 64%)', - STRONG: 'hsl(39, 74%, 69%)', + STRONG: 'hsl(39, 74%, 64%)', SUBTLE: 'hsl(39, 74%, 54%)' }, hues: { - RED: 'hsl(0, 70%, 71%)', - ROSE: 'hsl(350, 65%, 78%)', - BROWN: 'hsl(41, 52%, 77%)', - ORANGE: 'hsl(24, 75%, 75%)', - YELLOW: 'hsl(48, 68%, 74%)', - LIME: 'hsl(75, 65%, 71%)', - GREEN: 'hsl(140, 59%, 71%)', - TEAL: 'hsl(170, 55%, 71%)', - CYAN: 'hsl(190, 70%, 76%)', - BLUE: 'hsl(202, 82%, 80%)', - INDIGO: 'hsl(225, 73%, 82%)', - VIOLET: 'hsl(260, 65%, 81%)', - PURPLE: 'hsl(285, 60%, 78%)', - PINK: 'hsl(320, 65%, 81%)' + RED: 'hsl(8, 70%, 52%)', + ROSE: 'hsl(367, 84%, 70%)', + BROWN: 'hsl(35, 65%, 72%)', + ORANGE: 'hsl(24, 88%, 72%)', + YELLOW: 'hsl(41, 93%, 72%)', + GREEN: 'hsl(140, 72%, 68%)', + GREEN_DARK: 'hsl(160, 90%, 44%)', + TEAL: 'hsl(170, 70%, 60%)', + CYAN: 'hsl(190, 82%, 72%)', + BLUE: 'hsl(210, 90%, 76%)', + INDIGO: 'hsl(202, 88%, 66%)', + VIOLET: 'hsl(260, 75%, 78%)', + PURPLE: 'hsl(285, 72%, 75%)', + PINK: 'hsl(305, 59%, 74%)' }, system: { CONTROL_ACCENT: '#D9A342' @@ -64,17 +64,25 @@ palette.intent = { }; palette.syntax = { - DEFINITION: palette.hues.GREEN, - PROPERTY: palette.hues.BLUE, - STRING: palette.hues.BROWN, - NUMBER: palette.hues.GREEN, - ATOM: palette.hues.YELLOW, - VARIABLE: palette.hues.ROSE, + // Core language structure KEYWORD: palette.hues.ROSE, - COMMENT: palette.text.SUBTEXT1, - OPERATOR: palette.hues.BROWN, TAG: palette.hues.ROSE, - TAG_BRACKET: palette.text.SUBTEXT1 + // Identifiers & properties (collapsed) + VARIABLE: palette.hues.PINK, + PROPERTY: palette.hues.BLUE, + DEFINITION: palette.hues.BLUE, + + // Literals + STRING: palette.hues.BROWN, + NUMBER: palette.hues.PINK, + ATOM: palette.hues.ROSE, + + // Operators & punctuation (quiet) + OPERATOR: palette.text.SUBTEXT1, + TAG_BRACKET: palette.text.SUBTEXT1, + + // Comments should recede + COMMENT: palette.text.SUBTEXT0 }; const colors = { @@ -230,13 +238,13 @@ const darkTheme = { get: palette.hues.GREEN, post: palette.hues.INDIGO, put: palette.hues.ORANGE, - delete: palette.hues.RED, + delete: lighten(0.08, palette.hues.RED), patch: palette.hues.ORANGE, options: palette.hues.TEAL, head: palette.hues.CYAN }, - grpc: palette.hues.BROWN, + grpc: palette.hues.TEAL, ws: palette.hues.ORANGE, gql: palette.hues.PINK }, @@ -402,7 +410,7 @@ const darkTheme = { bg: palette.background.BASE }, variable: { - valid: palette.hues.GREEN, + valid: palette.hues.GREEN_DARK, invalid: palette.hues.RED, prompt: palette.hues.BLUE }, diff --git a/packages/bruno-app/src/themes/dark/vscode.js b/packages/bruno-app/src/themes/dark/vscode.js index 48c014ef6..c8bba5e11 100644 --- a/packages/bruno-app/src/themes/dark/vscode.js +++ b/packages/bruno-app/src/themes/dark/vscode.js @@ -44,7 +44,7 @@ const colors = { BORDER_LIGHT: '#3c3c3c', CODEMIRROR_TOKENS: { - DEFINITION: '#4ec9b0', + DEFINITION: '#9cdcfe', PROPERTY: '#9cdcfe', STRING: '#ce9178', NUMBER: '#b5cea8', diff --git a/packages/bruno-app/src/themes/light/light.js b/packages/bruno-app/src/themes/light/light.js index 68556b14e..4d307e9b2 100644 --- a/packages/bruno-app/src/themes/light/light.js +++ b/packages/bruno-app/src/themes/light/light.js @@ -62,17 +62,25 @@ palette.intent = { }; palette.syntax = { - DEFINITION: palette.hues.INDIGO, - PROPERTY: palette.hues.BLUE, - STRING: palette.hues.BROWN, - NUMBER: palette.hues.GREEN, - ATOM: palette.hues.PURPLE, - VARIABLE: palette.hues.PINK, + // Core language structure KEYWORD: palette.hues.ROSE, - COMMENT: palette.text.SUBTEXT0, - OPERATOR: palette.hues.BLUE, TAG: palette.hues.ROSE, - TAG_BRACKET: palette.text.SUBTEXT0 + // Identifiers & properties (collapsed) + VARIABLE: palette.hues.PINK, + PROPERTY: palette.hues.BLUE, + DEFINITION: palette.hues.BLUE, + + // Literals + STRING: palette.hues.BROWN, + NUMBER: palette.hues.PINK, + ATOM: palette.hues.ROSE, + + // Operators & punctuation (quiet) + OPERATOR: palette.text.SUBTEXT1, + TAG_BRACKET: palette.text.SUBTEXT1, + + // Comments should recede + COMMENT: palette.text.SUBTEXT0 }; const lightTheme = { diff --git a/packages/bruno-electron/src/index.js b/packages/bruno-electron/src/index.js index 5e57e4c94..c6b6e577c 100644 --- a/packages/bruno-electron/src/index.js +++ b/packages/bruno-electron/src/index.js @@ -195,8 +195,9 @@ app.on('ready', async () => { mainWindow.once('ready-to-show', () => { mainWindow.show(); }); + const devPort = process.env.BRUNO_DEV_PORT || 3000; const url = isDev - ? 'http://localhost:3000' + ? `http://localhost:${devPort}` : format({ pathname: path.join(__dirname, '../web/index.html'), protocol: 'file:', diff --git a/scripts/dev.js b/scripts/dev.js new file mode 100644 index 000000000..a4a441149 --- /dev/null +++ b/scripts/dev.js @@ -0,0 +1,99 @@ +const { spawn } = require('child_process'); +const path = require('path'); + +// ANSI color codes +const colors = { + reset: '\x1b[0m', + bright: '\x1b[1m', + dim: '\x1b[2m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + magenta: '\x1b[35m', + cyan: '\x1b[36m', + red: '\x1b[31m' +}; + +const log = { + info: (msg) => console.log(`${colors.cyan}ℹ${colors.reset} ${msg}`), + success: (msg) => console.log(`${colors.green}✓${colors.reset} ${msg}`), + warn: (msg) => console.log(`${colors.yellow}⚠${colors.reset} ${msg}`), + error: (msg) => console.log(`${colors.red}✗${colors.reset} ${msg}`), + label: (label, msg) => console.log(`${colors.bright}${colors.magenta}[${label}]${colors.reset} ${msg}`) +}; + +const rootDir = path.join(__dirname, '..'); +const webDir = path.join(rootDir, 'packages/bruno-app'); +const electronDir = path.join(rootDir, 'packages/bruno-electron'); + +let electronProcess = null; +let detectedPort = null; + +// Regex to match rsbuild's local URL output (e.g., "➜ Local: http://localhost:3000/") +const portRegex = /Local:\s+http:\/\/localhost:(\d+)/; + +console.log(`\n${colors.bright}${colors.yellow}🚀 Starting Bruno development environment...${colors.reset}\n`); + +// Start the rsbuild dev server +const webProcess = spawn('npm', ['run', 'dev'], { + cwd: webDir, + stdio: ['inherit', 'pipe', 'pipe'], + shell: true +}); + +webProcess.stdout.on('data', (data) => { + const output = data.toString(); + process.stdout.write(output); + + // Try to detect the port from rsbuild output + if (!detectedPort) { + const match = output.match(portRegex); + if (match) { + detectedPort = match[1]; + log.success(`Detected dev server on port ${colors.bright}${detectedPort}${colors.reset}`); + startElectron(detectedPort); + } + } +}); + +webProcess.stderr.on('data', (data) => { + process.stderr.write(data.toString()); +}); + +webProcess.on('close', (code) => { + log.info(`Web process exited with code ${code}`); + cleanup(); +}); + +function startElectron(port) { + log.info(`Starting Electron with ${colors.cyan}BRUNO_DEV_PORT=${port}${colors.reset}`); + + electronProcess = spawn('npm', ['run', 'dev'], { + cwd: electronDir, + stdio: 'inherit', + shell: true, + env: { + ...process.env, + BRUNO_DEV_PORT: port + } + }); + + electronProcess.on('close', (code) => { + log.info(`Electron process exited with code ${code}`); + cleanup(); + }); +} + +function cleanup() { + if (webProcess && !webProcess.killed) { + webProcess.kill(); + } + if (electronProcess && !electronProcess.killed) { + electronProcess.kill(); + } + process.exit(0); +} + +// Handle termination signals +process.on('SIGINT', cleanup); +process.on('SIGTERM', cleanup); diff --git a/scripts/pr-checkout.js b/scripts/pr-checkout.js new file mode 100755 index 000000000..6f8e2a955 --- /dev/null +++ b/scripts/pr-checkout.js @@ -0,0 +1,88 @@ +#!/usr/bin/env node + +const { execSync } = require('child_process'); +const path = require('path'); +const fs = require('fs'); + +const prNumber = process.argv[2]; + +if (!prNumber || !/^\d+$/.test(prNumber)) { + console.error('Usage: node scripts/pr-checkout.js '); + process.exit(1); +} + +const repoRoot = path.resolve(__dirname, '..'); +const repoName = path.basename(repoRoot); +const worktreesDir = path.resolve(repoRoot, '..', `${repoName}-worktrees`); +const worktreePath = path.join(worktreesDir, `pr-${prNumber}`); + +function log(...args) { + console.error(...args); +} + +function run(cmd, options = {}) { + log(`$ ${cmd}`); + return execSync(cmd, { encoding: 'utf-8', cwd: repoRoot, stdio: 'inherit', ...options }); +} + +function runCapture(cmd) { + return execSync(cmd, { encoding: 'utf-8', cwd: repoRoot }).trim(); +} + +// Check if gh CLI is available +try { + runCapture('gh --version'); +} catch { + console.error('Error: GitHub CLI (gh) is not installed. Install it from https://cli.github.com/'); + process.exit(1); +} + +// Get PR info +log(`\nFetching PR #${prNumber} info...`); +let prBranch, prHeadRepo; +try { + const prInfo = JSON.parse(runCapture(`gh pr view ${prNumber} --json headRefName,headRepository,headRepositoryOwner`)); + prBranch = prInfo.headRefName; + prHeadRepo = `${prInfo.headRepositoryOwner.login}/${prInfo.headRepository.name}`; + log(`PR branch: ${prBranch}`); + log(`PR repo: ${prHeadRepo}`); +} catch (error) { + console.error(`Error: Could not fetch PR #${prNumber}. Make sure the PR exists and you're authenticated with gh.`); + process.exit(1); +} + +// Check if worktree already exists +if (fs.existsSync(worktreePath)) { + log(`\nWorktree already exists at ${worktreePath}`); + log(`To remove it, run: git worktree remove ${worktreePath}`); + console.log(worktreePath); + process.exit(0); +} + +// Create worktrees directory if needed +if (!fs.existsSync(worktreesDir)) { + log(`\nCreating worktrees directory: ${worktreesDir}`); + fs.mkdirSync(worktreesDir, { recursive: true }); +} + +// Fetch the PR +log(`\nFetching PR #${prNumber}...`); +run(`gh pr checkout ${prNumber} --detach`, { stdio: 'pipe' }); + +// Get the current commit after checkout +const prCommit = runCapture('git rev-parse HEAD'); + +// Go back to original branch +const originalBranch = runCapture('git rev-parse --abbrev-ref @{-1} 2>/dev/null || git rev-parse --abbrev-ref HEAD'); +run(`git checkout ${originalBranch}`, { stdio: 'pipe' }); + +// Create the worktree +log(`\nCreating worktree at ${worktreePath}...`); +run(`git worktree add ${worktreePath} ${prCommit}`); + +log(`\n✓ PR #${prNumber} checked out to: ${worktreePath}`); +log(`\nTo remove the worktree later:`); +log(` git worktree remove ${worktreePath}`); + +// Output path to stdout for cd integration +console.log(worktreePath);