diff --git a/.github/actions/ai-release-notes/action.yml b/.github/actions/ai-release-notes/action.yml deleted file mode 100644 index 575e49c97c..0000000000 --- a/.github/actions/ai-release-notes/action.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: AI Release Notes -description: Generate AI release notes using git and openai, outputs 'RELEASE_NOTES' and 'OPENAI_PROMPT' - -inputs: - OPENAI_API_KEY: - required: true - type: string - GHA_PAT: - required: true - type: string - model_name: - required: false - type: string - default: gpt-4o-mini - repo_path: - required: false - type: string - custom_prompt: - required: false - default: '' - type: string - git_ref: - required: true - type: string - head_ref: - required: true - type: string - base_ref: - required: true - type: string - -outputs: - RELEASE_NOTES: - description: "AI generated release notes" - value: ${{ steps.ai_release_notes.outputs.RELEASE_NOTES }} - OPENAI_PROMPT: - description: "Prompt used to generate release notes" - value: ${{ steps.ai_prompt.outputs.OPENAI_PROMPT }} - -env: - GITHUB_REF: ${{ inputs.git_ref }} - BASE_REF: ${{ inputs.base_ref }} - HEAD_REF: ${{ inputs.head_ref }} - -runs: - using: "composite" - steps: - - uses: actions/checkout@v4 - with: - repository: ${{ inputs.repo_path }} - token: ${{ inputs.GHA_PAT }} - ref: ${{ env.GITHUB_REF }} - fetch-depth: 0 - - - name: Set Workspace - shell: bash - run: | - pip install tiktoken - pip install pytz - - # Github outputs: 'OPENAI_PROMPT' - - name: Add Git Info to base prompt - id: ai_prompt - shell: bash - env: - BASE_REF: ${{ env.BASE_REF }} - HEAD_SHA: ${{ env.HEAD_SHA }} - PR_TITLE: ${{ github.event.pull_request.title }} - PR_BODY: ${{ github.event.pull_request.body }} - MODEL_NAME: ${{ inputs.model_name }} - CUSTOM_PROMPT: ${{ inputs.custom_prompt }} # Default: '' - run: python .github/scripts/release-notes-prompt.py - - # Github outputs: 'RELEASE_NOTES' - - name: Generate AI release notes - id: ai_release_notes - shell: bash - env: - OPENAI_API_KEY: ${{ inputs.OPENAI_API_KEY }} - CUSTOM_PROMPT: ${{ steps.ai_prompt.outputs.OPENAI_PROMPT }} - MODEL_NAME: ${{ inputs.model_name }} - run: python .github/scripts/ai-release-notes.py - diff --git a/.github/scripts/ai-release-notes.py b/.github/scripts/ai-release-notes.py deleted file mode 100644 index 5dc451810e..0000000000 --- a/.github/scripts/ai-release-notes.py +++ /dev/null @@ -1,123 +0,0 @@ -""" -AI-powered release notes generator that creates concise and informative release notes from git changes. - -This script uses OpenAI's API to analyze git changes (summary, diff, and commit log) and generate -well-formatted release notes in markdown. It focuses on important changes and their impact, -particularly highlighting new types and schemas while avoiding repetitive information. - -Environment Variables Required: - OPENAI_API_KEY: OpenAI API key for authentication - CHANGE_SUMMARY: Summary of changes made (optional if CUSTOM_PROMPT provided) - CHANGE_DIFF: Git diff of changes (optional if CUSTOM_PROMPT provided) - CHANGE_LOG: Git commit log (optional if CUSTOM_PROMPT provided) - GITHUB_OUTPUT: Path to GitHub output file - CUSTOM_PROMPT: Custom prompt to override default (optional) -""" - -import os -import requests # type: ignore -import json -import tiktoken # type: ignore - -OPENAI_API_KEY = os.environ["OPENAI_API_KEY"] -CHANGE_SUMMARY = os.environ.get('CHANGE_SUMMARY', '') -CHANGE_DIFF = os.environ.get('CHANGE_DIFF', '') -CHANGE_LOG = os.environ.get('CHANGE_LOG', '') -GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT") -OPEN_AI_BASE_URL = "https://api.openai.com/v1" -OPEN_API_HEADERS = {"Authorization": f"Bearer {OPENAI_API_KEY}", "Content-Type": "application/json"} -CUSTOM_PROMPT = os.environ.get('CUSTOM_PROMPT', '') -MODEL_NAME = os.environ.get('MODEL_NAME', 'gpt-3.5-turbo-16k') - -def num_tokens_from_string(string: str, model_name: str) -> int: - """ - Calculate the number of tokens in a text string for a specific model. - - Args: - string: The input text to count tokens for - model_name: Name of the OpenAI model to use for token counting - - Returns: - int: Number of tokens in the input string - """ - encoding = tiktoken.encoding_for_model(model_name) - num_tokens = len(encoding.encode(string)) - return num_tokens - -def truncate_to_token_limit(text, max_tokens, model_name): - """ - Truncate text to fit within a maximum token limit for a specific model. - - Args: - text: The input text to truncate - max_tokens: Maximum number of tokens allowed - model_name: Name of the OpenAI model to use for tokenization - - Returns: - str: Truncated text that fits within the token limit - """ - encoding = tiktoken.encoding_for_model(model_name) - encoded = encoding.encode(text) - truncated = encoded[:max_tokens] - return encoding.decode(truncated) - -def generate_release_notes(model_name): - """ - Generate release notes using OpenAI's API based on git changes. - - Uses the GPT-3.5-turbo model to analyze change summary, commit log, and code diff - to generate concise and informative release notes in markdown format. The notes - focus on important changes and their impact, with sections for new types/schemas - and other updates. - - Returns: - str: Generated release notes in markdown format - - Raises: - requests.exceptions.RequestException: If the OpenAI API request fails - """ - max_tokens = 14000 # Reserve some tokens for the response - - # Truncate inputs if necessary to fit within token limits - change_summary = '' if CUSTOM_PROMPT else truncate_to_token_limit(CHANGE_SUMMARY, 1000, model_name) - change_log = '' if CUSTOM_PROMPT else truncate_to_token_limit(CHANGE_LOG, 2000, model_name) - change_diff = '' if CUSTOM_PROMPT else truncate_to_token_limit(CHANGE_DIFF, max_tokens - num_tokens_from_string(change_summary, model_name) - num_tokens_from_string(change_log, model_name) - 1000, model_name) - - url = f"{OPEN_AI_BASE_URL}/chat/completions" - - # Construct prompt for OpenAI API - openai_prompt = CUSTOM_PROMPT if CUSTOM_PROMPT else f"""Based on the following summary of changes, commit log and code diff, please generate concise and informative release notes: - Summary of changes: - {change_summary} - Commit log: - {change_log} - Code Diff: - {json.dumps(change_diff)} - """ - - data = { - "model": model_name, - "messages": [{"role": "user", "content": openai_prompt}], - "temperature": 0.7, - "max_tokens": 1000, - } - - print("----------------------------------------------------------------------------------------------------------") - print("POST request to OpenAI") - print("----------------------------------------------------------------------------------------------------------") - ai_response = requests.post(url, headers=OPEN_API_HEADERS, json=data) - print(f"Status Code: {str(ai_response.status_code)}") - print(f"Response: {ai_response.text}") - ai_response.raise_for_status() - - return ai_response.json()["choices"][0]["message"]["content"] - -release_notes = generate_release_notes(MODEL_NAME) -print("----------------------------------------------------------------------------------------------------------") -print("OpenAI generated release notes") -print("----------------------------------------------------------------------------------------------------------") -print(release_notes) - -# Write the release notes to GITHUB_OUTPUT -with open(GITHUB_OUTPUT, "a") as outputs_file: - outputs_file.write(f"RELEASE_NOTES<= 3: - # Parse HEAD~1 (PR to generate notes for) - head_info = parse_merge_commit(commits[1]) - # Parse HEAD~2 (previous PR to compare against) - base_info = parse_merge_commit(commits[2]) - - if head_info and base_info: - # Set output for GitHub Actions - with open(os.environ['GITHUB_OUTPUT'], 'a') as gha_outputs: - gha_outputs.write(f"head_ref={head_info['sha']}\n") - gha_outputs.write(f"base_ref={base_info['sha']}") - - print(f"Head ref (PR #{head_info['pr_number']}): {head_info['sha']}") - print(f"Base ref (PR #{base_info['pr_number']}): {base_info['sha']}") - return head_info, base_info - - print("Could not find or parse sufficient merge history") - return None, None - -if __name__ == "__main__": - head_info, base_info = get_version_refs() \ No newline at end of file diff --git a/.github/scripts/overwrite_changeset_changelog.py b/.github/scripts/overwrite_changeset_changelog.py deleted file mode 100755 index 42e693d4ac..0000000000 --- a/.github/scripts/overwrite_changeset_changelog.py +++ /dev/null @@ -1,62 +0,0 @@ -""" -This script updates a specific version's release notes section in CHANGELOG.md with new content -or reformats existing content. - -The script: -1. Takes a version number, changelog path, and optionally new content as input from environment variables -2. Finds the section in the changelog for the specified version -3. Either: - a) Replaces the content with new content if provided, or - b) Reformats existing content by: - - Removing the first two lines of the changeset format - - Ensuring version numbers are wrapped in square brackets -4. Writes the updated changelog back to the file - -Environment Variables: - CHANGELOG_PATH: Path to the changelog file (defaults to 'CHANGELOG.md') - VERSION: The version number to update/format - PREV_VERSION: The previous version number (used to locate section boundaries) - NEW_CONTENT: Optional new content to insert for this version -""" - -#!/usr/bin/env python3 - -import os - -CHANGELOG_PATH = os.environ.get("CHANGELOG_PATH", "CHANGELOG.md") -VERSION = os.environ['VERSION'] -PREV_VERSION = os.environ.get("PREV_VERSION", "") -NEW_CONTENT = os.environ.get("NEW_CONTENT", "") - -def overwrite_changelog_section(changelog_text: str, new_content: str): - # Find the section for the specified version - version_pattern = f"## {VERSION}\n" - prev_version_pattern = f"## [{PREV_VERSION}]\n" - print(f"latest version: {VERSION}") - print(f"prev_version: {PREV_VERSION}") - - notes_start_index = changelog_text.find(version_pattern) + len(version_pattern) - notes_end_index = changelog_text.find(prev_version_pattern, notes_start_index) if PREV_VERSION and prev_version_pattern in changelog_text else len(changelog_text) - - if new_content: - return changelog_text[:notes_start_index] + f"{new_content}\n" + changelog_text[notes_end_index:] - else: - changeset_lines = changelog_text[notes_start_index:notes_end_index].split("\n") - # Remove the first two lines from the regular changeset format, ex: \n### Patch Changes - parsed_lines = "\n".join(changeset_lines[2:]) - updated_changelog = changelog_text[:notes_start_index] + parsed_lines + changelog_text[notes_end_index:] - updated_changelog = updated_changelog.replace(f"## {VERSION}", f"## [{VERSION}]") - return updated_changelog - -with open(CHANGELOG_PATH, 'r') as f: - changelog_content = f.read() - -new_changelog = overwrite_changelog_section(changelog_content, NEW_CONTENT) -print("----------------------------------------------------------------------------------") -print(new_changelog) -print("----------------------------------------------------------------------------------") -# Write back to CHANGELOG.md -with open(CHANGELOG_PATH, 'w') as f: - f.write(new_changelog) - -print(f"{CHANGELOG_PATH} updated successfully!") \ No newline at end of file diff --git a/.github/scripts/parse_changeset_changelog.py b/.github/scripts/parse_changeset_changelog.py deleted file mode 100755 index b21c444dd1..0000000000 --- a/.github/scripts/parse_changeset_changelog.py +++ /dev/null @@ -1,64 +0,0 @@ -""" -This script extracts the release notes section for a specific version from CHANGELOG.md. - -The script: -1. Takes a version number and changelog path as input from environment variables -2. Finds the section in the changelog for the specified version -3. Extracts the content between the current version header and the next version header - (or end of file if it's the latest version) -4. Outputs the extracted release notes to GITHUB_OUTPUT for use in creating GitHub releases - -Environment Variables: - GITHUB_OUTPUT: Path to GitHub Actions output file - CHANGELOG_PATH: Path to the changelog file (defaults to 'CHANGELOG.md') - VERSION: The version number to extract notes for -""" - -#!/usr/bin/env python3 - -import sys -import os -import subprocess - -GITHUB_OUTPUT = os.getenv("GITHUB_OUTPUT") -CHANGELOG_PATH = os.environ.get("CHANGELOG_PATH", "CHANGELOG.md") -VERSION = os.environ['VERSION'] - -def parse_changelog_section(content: str): - """Parse a specific version section from the changelog content. - - Args: - content: The full changelog content as a string - - Returns: - The formatted content for this version, or None if version not found - - Example: - >>> content = "## 1.2.0\\nChanges\\n## 1.1.0\\nOld changes" - >>> parse_changelog_section(content) - 'Changes\\n' - """ - # Find the section for the specified version - version_pattern = f"## {VERSION}\n" - print(f"latest version: {VERSION}") - notes_start_index = content.find(version_pattern) + len(version_pattern) - prev_version = subprocess.getoutput("git show origin/main:package.json | grep '\"version\":' | cut -d'\"' -f4") - print(f"prev_version: {prev_version}") - prev_version_pattern = f"## {prev_version}\n" - notes_end_index = content.find(prev_version_pattern, notes_start_index) if prev_version_pattern in content else len(content) - - return content[notes_start_index:notes_end_index] - -with open(CHANGELOG_PATH, 'r') as f: - content = f.read() - -formatted_content = parse_changelog_section(content) -if not formatted_content: - print(f"Version {VERSION} not found in changelog", file=sys.stderr) - sys.exit(1) - -print(formatted_content) - -# Write the extracted release notes to GITHUB_OUTPUT -with open(GITHUB_OUTPUT, "a") as gha_output: - gha_output.write(f"release-notes< and that contains [!IMPORTANT] - ellipsis_match = re.search(r'(.*?)', pr_body, re.DOTALL) - if ellipsis_match: - content = ellipsis_match.group(1).strip() - important_match = re.search(r'\[!IMPORTANT\](.*?)(?=\[!|$)', content, re.DOTALL) - if important_match: - important_text = important_match.group(1).strip() - important_text = re.sub(r'^-+\s*', '', important_text) - return important_text.strip() - return "" - -def extract_coderabbit_summary(pr_body): - # Find content between ## Summary by CodeRabbit and the next ## or end of text - summary_match = re.search(r'## Summary by CodeRabbit\s*\n(.*?)(?=\n##|$)', pr_body, re.DOTALL) - return summary_match.group(1).strip() if summary_match else "" - -def num_tokens_from_string(string: str, model_name: str) -> int: - """ - Calculate the number of tokens in a text string for a specific model. - - Args: - string: The input text to count tokens for - model_name: Name of the OpenAI model to use for token counting - - Returns: - int: Number of tokens in the input string - """ - encoding = tiktoken.encoding_for_model(model_name) - num_tokens = len(encoding.encode(string)) - return num_tokens - -def truncate_to_token_limit(text, max_tokens, model_name): - """ - Truncate text to fit within a maximum token limit for a specific model. - - Args: - text: The input text to truncate - max_tokens: Maximum number of tokens allowed - model_name: Name of the OpenAI model to use for tokenization - - Returns: - str: Truncated text that fits within the token limit - """ - encoding = tiktoken.encoding_for_model(model_name) - encoded = encoding.encode(text) - truncated = encoded[:max_tokens] - return encoding.decode(truncated) - -# Extract sections and combine into PR_OVERVIEW -description = extract_description_section(PR_BODY) -important = extract_ellipsis_important(PR_BODY) -summary = extract_coderabbit_summary(PR_BODY) - -PR_OVERVIEW = "\n\n".join(filter(None, [description, important, summary])) - -# Get git information -base_sha = subprocess.getoutput(f"git rev-parse origin/{BASE_REF}") if BASE_REF == 'main' else BASE_REF -diff_overview = subprocess.getoutput(f"git diff {base_sha}..{HEAD_SHA} --name-status | awk '{{print $2}}' | sort | uniq -c | awk '{{print $2 \": \" $1 \" files changed\"}}'") -git_log = subprocess.getoutput(f"git log {base_sha}..{HEAD_SHA} --pretty=format:'%h - %s (%an)' --reverse | head -n 50") -git_diff = subprocess.getoutput(f"git diff {base_sha}..{HEAD_SHA} --minimal --abbrev --ignore-cr-at-eol --ignore-space-at-eol --ignore-space-change --ignore-all-space --ignore-blank-lines --unified=0 --diff-filter=ACDMRT") - -max_tokens = 14000 # Reserve some tokens for the response -changes_summary = truncate_to_token_limit(diff_overview, 1000, MODEL_NAME) -git_logs = truncate_to_token_limit(git_log, 2000, MODEL_NAME) -changes_diff = truncate_to_token_limit(git_diff, max_tokens - num_tokens_from_string(changes_summary, MODEL_NAME) - num_tokens_from_string(git_logs, MODEL_NAME) - 1000, MODEL_NAME) - -# Get today's existing changelog if any -existing_changelog = EXISTING_NOTES if EXISTING_NOTES != "null" else None -existing_changelog_text = f"\nAdditional context:\n{existing_changelog}" if existing_changelog else "" -TODAY = datetime.now(timezone('US/Eastern')).isoformat(sep=' ', timespec='seconds') - -BASE_PROMPT = CUSTOM_PROMPT if CUSTOM_PROMPT else f"""Based on the following 'PR Information', please generate concise and informative release notes to be read by developers. -Format the release notes with markdown, and always use this structure: a descriptive and very short title (no more than 8 words) with heading level 2, a paragraph with a summary of changes (no header), and if applicable, sections for '🚀 New Features & Improvements', '🐛 Bugs Fixed' and '🔧 Other Updates', with heading level 3, skip respectively the sections if not applicable. -Finally include the following markdown comment with the PR merged date: . -Avoid being repetitive and focus on the most important changes and their impact, discard any mention of version bumps/updates, changeset files, environment variables or syntax updates. -PR Information:""" - -OPENAI_PROMPT = f"""{BASE_PROMPT} -Git log summary: -{changes_summary} -Commit Messages: -{git_logs} -PR Title: -{PR_TITLE} -PR Overview: -{PR_OVERVIEW}{existing_changelog_text} -Code Diff: -{json.dumps(changes_diff)}""" - -print("OpenAI Prompt") -print("----------------------------------------------------------------") -print(OPENAI_PROMPT) - -# Write the prompt to GITHUB_OUTPUT -with open(GITHUB_OUTPUT, "a") as outputs_file: - outputs_file.write(f"OPENAI_PROMPT<> $GITHUB_OUTPUT + - name: Patch package.json version + env: + COMMIT_COUNT: ${{ steps.count.outputs.total }} + run: | + node <<'EOF' + const fs = require('fs'); + const path = require('path'); + const pkgPath = path.join(__dirname, 'apps', 'vscode-nightly', 'package.nightly.json'); + const pkg = JSON.parse(fs.readFileSync(pkgPath,'utf8')); + const [maj, min] = pkg.version.split('.'); + pkg.version = `${maj}.${min}.${process.env.COMMIT_COUNT}`; + fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2)); + console.log(`🔖 Nightly version set to ${pkg.version}`); + EOF + - name: Build VSIX + run: pnpm build:nightly # Produces bin/roo-code-nightly-0.0.[count].vsix + - name: Publish to VS Code Marketplace + env: + VSCE_PAT: ${{ secrets.VSCE_PAT }} + run: npx vsce publish --packagePath "bin/$(/bin/ls bin | head -n1)" + - name: Publish to Open VSX Registry + env: + OVSX_PAT: ${{ secrets.OVSX_PAT }} + run: npx ovsx publish "bin/$(ls bin | head -n1)" diff --git a/apps/vscode-nightly/.gitignore b/apps/vscode-nightly/.gitignore new file mode 100644 index 0000000000..378eac25d3 --- /dev/null +++ b/apps/vscode-nightly/.gitignore @@ -0,0 +1 @@ +build diff --git a/apps/vscode-nightly/esbuild.mjs b/apps/vscode-nightly/esbuild.mjs new file mode 100644 index 0000000000..718b444cf6 --- /dev/null +++ b/apps/vscode-nightly/esbuild.mjs @@ -0,0 +1,152 @@ +import * as esbuild from "esbuild" +import * as fs from "fs" +import * as path from "path" +import { fileURLToPath } from "url" + +import { getGitSha, copyPaths, copyLocales, copyWasms, generatePackageJson } from "@roo-code/build" + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) + +async function main() { + const production = process.argv.includes("--production") + const minify = production + const sourcemap = !production + + const overrideJson = JSON.parse(fs.readFileSync(path.join(__dirname, "package.nightly.json"), "utf8")) + console.log(`[main] name: ${overrideJson.name}`) + console.log(`[main] version: ${overrideJson.version}`) + + const gitSha = getGitSha() + console.log(`[main] gitSha: ${gitSha}`) + + /** + * @type {import('esbuild').BuildOptions} + */ + const buildOptions = { + bundle: true, + minify, + sourcemap, + logLevel: "silent", + format: "cjs", + sourcesContent: false, + platform: "node", + define: { + "process.env.PKG_NAME": '"roo-code-nightly"', + "process.env.PKG_VERSION": `"${overrideJson.version}"`, + "process.env.PKG_OUTPUT_CHANNEL": '"Roo-Code-Nightly"', + ...(gitSha ? { "process.env.PKG_SHA": `"${gitSha}"` } : {}), + }, + } + + const srcDir = path.join(__dirname, "..", "..", "src") + const buildDir = path.join(__dirname, "build") + const distDir = path.join(buildDir, "dist") + + /** + * @type {import('esbuild').Plugin[]} + */ + const plugins = [ + { + name: "copy-src", + setup(build) { + build.onEnd(() => { + const paths = [ + ["../README.md", "README.md"], + ["../CHANGELOG.md", "CHANGELOG.md"], + ["../LICENSE", "LICENSE"], + [".vscodeignore", ".vscodeignore"], + ["assets", "assets"], + ["integrations", "integrations"], + ["node_modules/vscode-material-icons/generated", "assets/vscode-material-icons"], + ["../webview-ui/audio", "webview-ui/audio"], + ] + + copyPaths(paths, srcDir, buildDir) + + let count = 0 + + fs.readdirSync(path.join(srcDir)).forEach((file) => { + if (file.startsWith("package.nls")) { + fs.copyFileSync(path.join(srcDir, file), path.join(buildDir, file)) + count++ + } + }) + + console.log(`[copy-src] Copied ${count} package.nls*.json files to ${buildDir}`) + }) + }, + }, + { + name: "generate-package-json", + setup(build) { + build.onEnd(() => { + const packageJson = JSON.parse(fs.readFileSync(path.join(srcDir, "package.json"), "utf8")) + + const generatedPackageJson = generatePackageJson({ + packageJson, + overrideJson, + substitution: ["roo-cline", "roo-code-nightly"], + }) + + fs.writeFileSync(path.join(buildDir, "package.json"), JSON.stringify(generatedPackageJson, null, 2)) + console.log(`[generate-package-json] Generated package.json`) + }) + }, + }, + { + name: "copy-wasms", + setup(build) { + build.onEnd(() => { + copyWasms(srcDir, distDir) + }) + }, + }, + { + name: "copy-locales", + setup(build) { + build.onEnd(() => { + copyLocales(srcDir, distDir) + }) + }, + }, + ] + + /** + * @type {import('esbuild').BuildOptions} + */ + const extensionBuildOptions = { + ...buildOptions, + plugins, + entryPoints: [path.join(srcDir, "extension.ts")], + outfile: path.join(distDir, "extension.js"), + external: ["vscode"], + } + + /** + * @type {import('esbuild').BuildOptions} + */ + const workerBuildOptions = { + ...buildOptions, + entryPoints: [path.join(srcDir, "workers", "countTokens.ts")], + outdir: path.join(distDir, "workers"), + } + + const [extensionBuildContext, workerBuildContext] = await Promise.all([ + esbuild.context(extensionBuildOptions), + esbuild.context(workerBuildOptions), + ]) + + await Promise.all([ + extensionBuildContext.rebuild(), + extensionBuildContext.dispose(), + + workerBuildContext.rebuild(), + workerBuildContext.dispose(), + ]) +} + +main().catch((e) => { + console.error(e) + process.exit(1) +}) diff --git a/apps/vscode-nightly/eslintrc.json b/apps/vscode-nightly/eslintrc.json new file mode 100644 index 0000000000..9f099d6852 --- /dev/null +++ b/apps/vscode-nightly/eslintrc.json @@ -0,0 +1,25 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module" + }, + "plugins": ["@typescript-eslint"], + "rules": { + "@typescript-eslint/naming-convention": [ + "warn", + { + "selector": "import", + "format": ["camelCase", "PascalCase"] + } + ], + "@typescript-eslint/semi": "off", + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": ["error", { "varsIgnorePattern": "^_", "argsIgnorePattern": "^_" }], + "eqeqeq": "warn", + "no-throw-literal": "warn", + "semi": "off" + }, + "ignorePatterns": ["dist"] +} diff --git a/apps/vscode-nightly/package.json b/apps/vscode-nightly/package.json new file mode 100644 index 0000000000..b7d611276c --- /dev/null +++ b/apps/vscode-nightly/package.json @@ -0,0 +1,14 @@ +{ + "name": "@roo-code/vscode-nightly", + "description": "Nightly build for the Roo Code VSCode extension.", + "private": true, + "packageManager": "pnpm@10.8.1", + "scripts": { + "build": "rimraf build && pnpm --filter @roo-code/build build && node esbuild.mjs --production && pnpm --filter @roo-code/vscode-webview build --mode nightly", + "vsix": "pnpm build && cd build && mkdirp ../../../bin && npx vsce package --no-dependencies --out ../../../bin", + "clean": "rimraf build" + }, + "dependencies": { + "@roo-code/build": "workspace:^" + } +} diff --git a/apps/vscode-nightly/package.nightly.json b/apps/vscode-nightly/package.nightly.json new file mode 100644 index 0000000000..32ddaf8750 --- /dev/null +++ b/apps/vscode-nightly/package.nightly.json @@ -0,0 +1,8 @@ +{ + "name": "roo-code-nightly", + "displayName": "Roo Code Nightly", + "publisher": "RooVeterinaryInc", + "version": "0.0.1", + "icon": "assets/icons/icon-nightly.png", + "scripts": {} +} diff --git a/package.json b/package.json index 5f29646852..2a91b79fe5 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "test": "turbo test --log-order grouped --output-logs new-only", "format": "turbo format --log-order grouped --output-logs new-only", "build": "pnpm --filter roo-cline vsix", + "build:nightly": "pnpm --filter @roo-code/vscode-nightly vsix", "changeset": "changeset", "knip": "knip --include files", "update-contributors": "node scripts/update-contributors.js" @@ -19,12 +20,17 @@ "devDependencies": { "@changesets/cli": "^2.27.10", "@dotenvx/dotenvx": "^1.34.0", + "@vscode/vsce": "3.3.2", + "esbuild": "^0.25.0", "eslint": "^8.57.0", "husky": "^9.1.7", "knip": "^5.44.4", "lint-staged": "^15.2.11", + "mkdirp": "^3.0.1", "only-allow": "^1.2.1", + "ovsx": "0.10.2", "prettier": "^3.4.2", + "rimraf": "^6.0.1", "turbo": "^2.5.3", "typescript": "^5.4.5" }, diff --git a/packages/build/eslintrc.json b/packages/build/eslintrc.json new file mode 100644 index 0000000000..9f099d6852 --- /dev/null +++ b/packages/build/eslintrc.json @@ -0,0 +1,25 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module" + }, + "plugins": ["@typescript-eslint"], + "rules": { + "@typescript-eslint/naming-convention": [ + "warn", + { + "selector": "import", + "format": ["camelCase", "PascalCase"] + } + ], + "@typescript-eslint/semi": "off", + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": ["error", { "varsIgnorePattern": "^_", "argsIgnorePattern": "^_" }], + "eqeqeq": "warn", + "no-throw-literal": "warn", + "semi": "off" + }, + "ignorePatterns": ["dist"] +} diff --git a/packages/build/package.json b/packages/build/package.json new file mode 100644 index 0000000000..377630b0ed --- /dev/null +++ b/packages/build/package.json @@ -0,0 +1,17 @@ +{ + "name": "@roo-code/build", + "description": "ESBuild utilities for Roo Code.", + "private": true, + "type": "module", + "exports": "./dist/index.js", + "scripts": { + "build": "tsc" + }, + "devDependencies": { + "@types/node": "^22.15.20", + "vitest": "^3.1.3" + }, + "dependencies": { + "zod": "^3.24.2" + } +} diff --git a/packages/build/src/__tests__/index.test.ts b/packages/build/src/__tests__/index.test.ts new file mode 100644 index 0000000000..538957f3c7 --- /dev/null +++ b/packages/build/src/__tests__/index.test.ts @@ -0,0 +1,212 @@ +// npx vitest --globals run src/__tests__/index.test.ts + +import { generatePackageJson } from "../index.js" + +describe("generatePackageJson", () => { + it("should be a test", () => { + const generatedPackageJson = generatePackageJson({ + packageJson: { + name: "roo-cline", + displayName: "%extension.displayName%", + description: "%extension.description%", + publisher: "RooVeterinaryInc", + version: "3.17.2", + icon: "assets/icons/icon.png", + contributes: { + viewsContainers: { + activitybar: [ + { + id: "roo-cline-ActivityBar", + title: "%views.activitybar.title%", + icon: "assets/icons/icon.svg", + }, + ], + }, + views: { + "roo-cline-ActivityBar": [ + { + type: "webview", + id: "roo-cline.SidebarProvider", + name: "", + }, + ], + }, + commands: [ + { + command: "roo-cline.plusButtonClicked", + title: "%command.newTask.title%", + icon: "$(add)", + }, + { + command: "roo-cline.openInNewTab", + title: "%command.openInNewTab.title%", + category: "%configuration.title%", + }, + ], + menus: { + "editor/context": [ + { + submenu: "roo-cline.contextMenu", + group: "navigation", + }, + ], + "roo-cline.contextMenu": [ + { + command: "roo-cline.addToContext", + group: "1_actions@1", + }, + ], + "editor/title": [ + { + command: "roo-cline.plusButtonClicked", + group: "navigation@1", + when: "activeWebviewPanelId == roo-cline.TabPanelProvider", + }, + { + command: "roo-cline.settingsButtonClicked", + group: "navigation@6", + when: "activeWebviewPanelId == roo-cline.TabPanelProvider", + }, + ], + }, + submenus: [ + { + id: "roo-cline.contextMenu", + label: "%views.contextMenu.label%", + }, + { + id: "roo-cline.terminalMenu", + label: "%views.terminalMenu.label%", + }, + ], + configuration: { + title: "%configuration.title%", + properties: { + "roo-cline.allowedCommands": { + type: "array", + items: { + type: "string", + }, + default: ["npm test", "npm install", "tsc", "git log", "git diff", "git show"], + description: "%commands.allowedCommands.description%", + }, + "roo-cline.customStoragePath": { + type: "string", + default: "", + description: "%settings.customStoragePath.description%", + }, + }, + }, + }, + scripts: { + lint: "eslint **/*.ts", + }, + }, + overrideJson: { + name: "roo-code-nightly", + displayName: "Roo Code Nightly", + publisher: "RooVeterinaryInc", + version: "0.0.1", + icon: "assets/icons/icon-nightly.png", + scripts: {}, + }, + substitution: ["roo-cline", "roo-code-nightly"], + }) + + expect(generatedPackageJson).toStrictEqual({ + name: "roo-code-nightly", + displayName: "Roo Code Nightly", + description: "%extension.description%", + publisher: "RooVeterinaryInc", + version: "0.0.1", + icon: "assets/icons/icon-nightly.png", + contributes: { + viewsContainers: { + activitybar: [ + { + id: "roo-code-nightly-ActivityBar", + title: "%views.activitybar.title%", + icon: "assets/icons/icon.svg", + }, + ], + }, + views: { + "roo-code-nightly-ActivityBar": [ + { + type: "webview", + id: "roo-code-nightly.SidebarProvider", + name: "", + }, + ], + }, + commands: [ + { + command: "roo-code-nightly.plusButtonClicked", + title: "%command.newTask.title%", + icon: "$(add)", + }, + { + command: "roo-code-nightly.openInNewTab", + title: "%command.openInNewTab.title%", + category: "%configuration.title%", + }, + ], + menus: { + "editor/context": [ + { + submenu: "roo-code-nightly.contextMenu", + group: "navigation", + }, + ], + "roo-code-nightly.contextMenu": [ + { + command: "roo-code-nightly.addToContext", + group: "1_actions@1", + }, + ], + "editor/title": [ + { + command: "roo-code-nightly.plusButtonClicked", + group: "navigation@1", + when: "activeWebviewPanelId == roo-code-nightly.TabPanelProvider", + }, + { + command: "roo-code-nightly.settingsButtonClicked", + group: "navigation@6", + when: "activeWebviewPanelId == roo-code-nightly.TabPanelProvider", + }, + ], + }, + submenus: [ + { + id: "roo-code-nightly.contextMenu", + label: "%views.contextMenu.label%", + }, + { + id: "roo-code-nightly.terminalMenu", + label: "%views.terminalMenu.label%", + }, + ], + configuration: { + title: "%configuration.title%", + properties: { + "roo-code-nightly.allowedCommands": { + type: "array", + items: { + type: "string", + }, + default: ["npm test", "npm install", "tsc", "git log", "git diff", "git show"], + description: "%commands.allowedCommands.description%", + }, + "roo-code-nightly.customStoragePath": { + type: "string", + default: "", + description: "%settings.customStoragePath.description%", + }, + }, + }, + }, + scripts: {}, + }) + }) +}) diff --git a/packages/build/src/index.ts b/packages/build/src/index.ts new file mode 100644 index 0000000000..7e39a8abe3 --- /dev/null +++ b/packages/build/src/index.ts @@ -0,0 +1,176 @@ +import * as fs from "fs" +import * as path from "path" +import { execSync } from "child_process" + +import { ViewsContainer, Views, Menus, Configuration, contributesSchema } from "./types.js" + +export function getGitSha() { + let gitSha = undefined + + try { + gitSha = execSync("git rev-parse HEAD").toString().trim() + } catch (e) {} + + return gitSha +} + +export function copyPaths(copyPaths: [string, string][], srcDir: string, dstDir: string) { + copyPaths.forEach(([srcRelPath, dstRelPath]) => { + const stats = fs.lstatSync(path.join(srcDir, srcRelPath)) + + if (stats.isDirectory()) { + if (fs.existsSync(path.join(dstDir, dstRelPath))) { + fs.rmSync(path.join(dstDir, dstRelPath), { recursive: true }) + } + + fs.mkdirSync(path.join(dstDir, dstRelPath), { recursive: true }) + + const count = copyDir(path.join(srcDir, srcRelPath), path.join(dstDir, dstRelPath), 0) + console.log(`[copyPaths] Copied ${count} files from ${srcRelPath} to ${dstRelPath}`) + } else { + fs.copyFileSync(path.join(srcDir, srcRelPath), path.join(dstDir, dstRelPath)) + console.log(`[copyPaths] Copied ${srcRelPath} to ${dstRelPath}`) + } + }) +} + +export function copyDir(srcDir: string, dstDir: string, count: number): number { + const entries = fs.readdirSync(srcDir, { withFileTypes: true }) + + for (const entry of entries) { + const srcPath = path.join(srcDir, entry.name) + const dstPath = path.join(dstDir, entry.name) + + if (entry.isDirectory()) { + fs.mkdirSync(dstPath, { recursive: true }) + count = copyDir(srcPath, dstPath, count) + } else { + count = count + 1 + fs.copyFileSync(srcPath, dstPath) + } + } + + return count +} + +export function copyWasms(srcDir: string, distDir: string): void { + const nodeModulesDir = path.join(srcDir, "node_modules") + + fs.mkdirSync(distDir, { recursive: true }) + + // Tiktoken WASM file. + fs.copyFileSync( + path.join(nodeModulesDir, "tiktoken", "lite", "tiktoken_bg.wasm"), + path.join(distDir, "tiktoken_bg.wasm"), + ) + + console.log(`[copyWasms] Copied tiktoken WASMs to ${distDir}`) + + // Also copy Tiktoken WASMs to the workers directory. + const workersDir = path.join(distDir, "workers") + fs.mkdirSync(workersDir, { recursive: true }) + + fs.copyFileSync( + path.join(nodeModulesDir, "tiktoken", "lite", "tiktoken_bg.wasm"), + path.join(workersDir, "tiktoken_bg.wasm"), + ) + + console.log(`[copyWasms] Copied tiktoken WASMs to ${workersDir}`) + + // Main tree-sitter WASM file. + fs.copyFileSync( + path.join(nodeModulesDir, "web-tree-sitter", "tree-sitter.wasm"), + path.join(distDir, "tree-sitter.wasm"), + ) + + console.log(`[copyWasms] Copied tree-sitter.wasm to ${distDir}`) + + // Copy language-specific WASM files. + const languageWasmDir = path.join(nodeModulesDir, "tree-sitter-wasms", "out") + + if (!fs.existsSync(languageWasmDir)) { + throw new Error(`Directory does not exist: ${languageWasmDir}`) + } + + // Dynamically read all WASM files from the directory instead of using a hardcoded list. + const wasmFiles = fs.readdirSync(languageWasmDir).filter((file) => file.endsWith(".wasm")) + + wasmFiles.forEach((filename) => { + fs.copyFileSync(path.join(languageWasmDir, filename), path.join(distDir, filename)) + }) + + console.log(`[copyWasms] Copied ${wasmFiles.length} tree-sitter language wasms to ${distDir}`) +} + +export function copyLocales(srcDir: string, distDir: string): void { + const destDir = path.join(distDir, "i18n", "locales") + fs.mkdirSync(destDir, { recursive: true }) + const count = copyDir(path.join(srcDir, "i18n", "locales"), destDir, 0) + console.log(`[copyLocales] Copied ${count} locale files to ${destDir}`) +} + +export function generatePackageJson({ + packageJson: { contributes, ...packageJson }, + overrideJson, + substitution, +}: { + packageJson: Record + overrideJson: Record + substitution: [string, string] +}) { + const { viewsContainers, views, commands, menus, submenus, configuration } = contributesSchema.parse(contributes) + const [from, to] = substitution + + return { + ...packageJson, + ...overrideJson, + contributes: { + viewsContainers: transformArrayRecord(viewsContainers, from, to, ["id"]), + views: transformArrayRecord(views, from, to, ["id"]), + commands: transformArray(commands, from, to, "command"), + menus: transformArrayRecord(menus, from, to, ["command", "submenu", "when"]), + submenus: transformArray(submenus, from, to, "id"), + configuration: { + title: configuration.title, + properties: transformRecord(configuration.properties, from, to), + }, + }, + } +} + +function transformArrayRecord(obj: Record, from: string, to: string, props: string[]): T { + return Object.entries(obj).reduce( + (acc, [key, ary]) => ({ + ...acc, + [key.replace(from, to)]: ary.map((item) => { + const transformedItem = { ...item } + + for (const prop of props) { + if (prop in item && typeof item[prop] === "string") { + transformedItem[prop] = item[prop].replace(from, to) + } + } + + return transformedItem + }), + }), + {} as T, + ) +} + +function transformArray(arr: any[], from: string, to: string, idProp: string): T[] { + return arr.map(({ [idProp]: id, ...rest }) => ({ + [idProp]: id.replace(from, to), + ...rest, + })) +} + +function transformRecord(obj: Record, from: string, to: string): T { + return Object.entries(obj).reduce( + (acc, [key, value]) => ({ + ...acc, + [key.replace(from, to)]: value, + }), + {} as T, + ) +} diff --git a/packages/build/src/types.ts b/packages/build/src/types.ts new file mode 100644 index 0000000000..03e5c99419 --- /dev/null +++ b/packages/build/src/types.ts @@ -0,0 +1,98 @@ +import { z } from "zod" + +const viewsContainerSchema = z.record( + z.string(), + z.array( + z.object({ + id: z.string(), + title: z.string(), + icon: z.string(), + }), + ), +) + +export type ViewsContainer = z.infer + +const viewsSchema = z.record( + z.string(), + z.array( + z.object({ + type: z.string(), + id: z.string(), + name: z.string(), + }), + ), +) + +export type Views = z.infer + +const commandsSchema = z.array( + z.object({ + command: z.string(), + title: z.string(), + category: z.string().optional(), + icon: z.string().optional(), + }), +) + +export type Commands = z.infer + +const menuItemSchema = z.object({ + group: z.string(), + command: z.string().optional(), + submenu: z.string().optional(), + when: z.string().optional(), +}) + +export type MenuItem = z.infer + +const menusSchema = z.record(z.string(), z.array(menuItemSchema)) + +export type Menus = z.infer + +const submenusSchema = z.array( + z.object({ + id: z.string(), + label: z.string(), + }), +) + +export type Submenus = z.infer + +const configurationPropertySchema = z.object({ + type: z.union([ + z.literal("string"), + z.literal("array"), + z.literal("object"), + z.literal("boolean"), + z.literal("number"), + ]), + items: z + .object({ + type: z.string(), + }) + .optional(), + properties: z.record(z.string(), z.any()).optional(), + default: z.any().optional(), + description: z.string(), +}) + +export type ConfigurationProperty = z.infer + +const configurationSchema = z.object({ + title: z.string(), + properties: z.record(z.string(), configurationPropertySchema), +}) + +export type Configuration = z.infer + +export const contributesSchema = z.object({ + viewsContainers: viewsContainerSchema, + views: viewsSchema, + commands: commandsSchema, + menus: menusSchema, + submenus: submenusSchema, + configuration: configurationSchema, +}) + +export type Contributes = z.infer diff --git a/packages/build/tsconfig.json b/packages/build/tsconfig.json new file mode 100644 index 0000000000..4825dc81dd --- /dev/null +++ b/packages/build/tsconfig.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "esModuleInterop": true, + "incremental": false, + "isolatedModules": true, + "lib": ["es2022", "DOM", "DOM.Iterable"], + "module": "NodeNext", + "moduleDetection": "force", + "moduleResolution": "NodeNext", + "noUncheckedIndexedAccess": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "ES2022", + "types": ["vitest/globals"], + "outDir": "dist" + }, + "include": ["src"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3f61921fd4..cde314be68 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,12 @@ importers: '@dotenvx/dotenvx': specifier: ^1.34.0 version: 1.44.0 + '@vscode/vsce': + specifier: 3.3.2 + version: 3.3.2 + esbuild: + specifier: ^0.25.0 + version: 0.25.4 eslint: specifier: ^8.57.0 version: 8.57.1 @@ -22,16 +28,25 @@ importers: version: 9.1.7 knip: specifier: ^5.44.4 - version: 5.55.1(@types/node@22.15.18)(typescript@5.8.3) + version: 5.55.1(@types/node@22.15.20)(typescript@5.8.3) lint-staged: specifier: ^15.2.11 version: 15.5.2 + mkdirp: + specifier: ^3.0.1 + version: 3.0.1 only-allow: specifier: ^1.2.1 version: 1.2.1 + ovsx: + specifier: 0.10.2 + version: 0.10.2 prettier: specifier: ^3.4.2 version: 3.5.3 + rimraf: + specifier: ^6.0.1 + version: 6.0.1 turbo: specifier: ^2.5.3 version: 2.5.3 @@ -39,6 +54,12 @@ importers: specifier: ^5.4.5 version: 5.8.3 + apps/vscode-nightly: + dependencies: + '@roo-code/build': + specifier: workspace:^ + version: link:../../packages/build + e2e: devDependencies: '@roo-code/types': @@ -72,6 +93,19 @@ importers: specifier: 5.8.3 version: 5.8.3 + packages/build: + dependencies: + zod: + specifier: ^3.24.2 + version: 3.24.4 + devDependencies: + '@types/node': + specifier: ^22.15.20 + version: 22.15.20 + vitest: + specifier: ^3.1.3 + version: 3.1.3(@types/debug@4.1.12)(@types/node@22.15.20)(jiti@2.4.2)(jsdom@20.0.3)(lightningcss@1.29.2)(tsx@4.19.4)(yaml@2.8.0) + src: dependencies: '@anthropic-ai/bedrock-sdk': @@ -3451,6 +3485,9 @@ packages: '@types/node@22.15.18': resolution: {integrity: sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg==} + '@types/node@22.15.20': + resolution: {integrity: sha512-A6BohGFRGHAscJsTslDCA9JG7qSJr/DWUvrvY8yi9IgnGtMxCyat7vvQ//MFa0DnLsyuS3wYTpLdw4Hf+Q5JXw==} + '@types/parse-json@4.0.2': resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} @@ -11257,7 +11294,7 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 20.17.47 + '@types/node': 22.15.18 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -11270,14 +11307,14 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.47 + '@types/node': 22.15.18 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.9.0 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.17.47)(babel-plugin-macros@3.1.0) + jest-config: 29.7.0(@types/node@22.15.18)(babel-plugin-macros@3.1.0) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -11302,7 +11339,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.47 + '@types/node': 22.15.18 jest-mock: 29.7.0 '@jest/expect-utils@29.7.0': @@ -11320,7 +11357,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 18.19.100 + '@types/node': 22.15.18 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -11342,7 +11379,7 @@ snapshots: '@jest/transform': 29.7.0 '@jest/types': 29.6.3 '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 20.17.47 + '@types/node': 22.15.18 chalk: 4.1.2 collect-v8-coverage: 1.0.2 exit: 0.1.2 @@ -11412,7 +11449,7 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 - '@types/node': 20.17.47 + '@types/node': 22.15.18 '@types/yargs': 17.0.33 chalk: 4.1.2 @@ -13108,11 +13145,11 @@ snapshots: '@types/glob@8.1.0': dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.17.47 + '@types/node': 22.15.18 '@types/graceful-fs@4.1.9': dependencies: - '@types/node': 20.17.47 + '@types/node': 22.15.18 '@types/hast@3.0.4': dependencies: @@ -13142,7 +13179,7 @@ snapshots: '@types/jsdom@20.0.1': dependencies: - '@types/node': 18.19.100 + '@types/node': 22.15.18 '@types/tough-cookie': 4.0.5 parse5: 7.3.0 @@ -13172,12 +13209,12 @@ snapshots: '@types/node-fetch@2.6.12': dependencies: - '@types/node': 20.17.47 + '@types/node': 22.15.18 form-data: 4.0.2 '@types/node-ipc@9.2.3': dependencies: - '@types/node': 20.17.47 + '@types/node': 22.15.18 '@types/node@12.20.55': {} @@ -13193,6 +13230,10 @@ snapshots: dependencies: undici-types: 6.21.0 + '@types/node@22.15.20': + dependencies: + undici-types: 6.21.0 + '@types/parse-json@4.0.2': {} '@types/prop-types@15.7.14': {} @@ -13251,7 +13292,7 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 20.17.47 + '@types/node': 22.15.18 optional: true '@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@5.8.3))(eslint@8.57.1)(typescript@5.8.3)': @@ -13586,6 +13627,14 @@ snapshots: optionalDependencies: vite: 6.3.5(@types/node@20.17.47)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.4)(yaml@2.8.0) + '@vitest/mocker@3.1.3(vite@6.3.5(@types/node@22.15.20)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.4)(yaml@2.8.0))': + dependencies: + '@vitest/spy': 3.1.3 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 6.3.5(@types/node@22.15.20)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.4)(yaml@2.8.0) + '@vitest/pretty-format@3.1.3': dependencies: tinyrainbow: 2.0.0 @@ -16456,7 +16505,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.47 + '@types/node': 22.15.18 chalk: 4.1.2 co: 4.6.0 dedent: 1.6.0(babel-plugin-macros@3.1.0) @@ -16574,6 +16623,36 @@ snapshots: - babel-plugin-macros - supports-color + jest-config@29.7.0(@types/node@22.15.18)(babel-plugin-macros@3.1.0): + dependencies: + '@babel/core': 7.27.1 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.27.1) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + jest-circus: 29.7.0(babel-plugin-macros@3.1.0) + jest-environment-node: 29.7.0 + jest-get-type: 29.6.3 + jest-regex-util: 29.6.3 + jest-resolve: 29.7.0 + jest-runner: 29.7.0 + jest-util: 29.7.0 + jest-validate: 29.7.0 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 22.15.18 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + jest-diff@27.5.1: dependencies: chalk: 4.1.2 @@ -16606,7 +16685,7 @@ snapshots: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 '@types/jsdom': 20.0.1 - '@types/node': 18.19.100 + '@types/node': 22.15.18 jest-mock: 29.7.0 jest-util: 29.7.0 jsdom: 20.0.3 @@ -16620,7 +16699,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.47 + '@types/node': 22.15.18 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -16632,7 +16711,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.9 - '@types/node': 20.17.47 + '@types/node': 22.15.18 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -16678,7 +16757,7 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.17.47 + '@types/node': 22.15.18 jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -16713,7 +16792,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.47 + '@types/node': 22.15.18 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -16741,7 +16820,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.47 + '@types/node': 22.15.18 chalk: 4.1.2 cjs-module-lexer: 1.4.3 collect-v8-coverage: 1.0.2 @@ -16789,7 +16868,7 @@ snapshots: jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.17.47 + '@types/node': 22.15.18 chalk: 4.1.2 ci-info: 3.9.0 graceful-fs: 4.2.11 @@ -16808,7 +16887,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.17.47 + '@types/node': 22.15.18 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -16817,7 +16896,7 @@ snapshots: jest-worker@29.7.0: dependencies: - '@types/node': 20.17.47 + '@types/node': 22.15.18 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 @@ -17007,10 +17086,10 @@ snapshots: kleur@3.0.3: {} - knip@5.55.1(@types/node@22.15.18)(typescript@5.8.3): + knip@5.55.1(@types/node@22.15.20)(typescript@5.8.3): dependencies: '@nodelib/fs.walk': 1.2.8 - '@types/node': 22.15.18 + '@types/node': 22.15.20 enhanced-resolve: 5.18.1 fast-glob: 3.3.3 formatly: 0.2.3 @@ -20071,6 +20150,27 @@ snapshots: - tsx - yaml + vite-node@3.1.3(@types/node@22.15.20)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.4)(yaml@2.8.0): + dependencies: + cac: 6.7.14 + debug: 4.4.1(supports-color@8.1.1) + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 6.3.5(@types/node@22.15.20)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.4)(yaml@2.8.0) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + vite@6.3.5(@types/node@18.19.100)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.4)(yaml@2.8.0): dependencies: esbuild: 0.25.4 @@ -20103,6 +20203,22 @@ snapshots: tsx: 4.19.4 yaml: 2.8.0 + vite@6.3.5(@types/node@22.15.20)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.4)(yaml@2.8.0): + dependencies: + esbuild: 0.25.4 + fdir: 6.4.4(picomatch@4.0.2) + picomatch: 4.0.2 + postcss: 8.5.3 + rollup: 4.40.2 + tinyglobby: 0.2.13 + optionalDependencies: + '@types/node': 22.15.20 + fsevents: 2.3.3 + jiti: 2.4.2 + lightningcss: 1.29.2 + tsx: 4.19.4 + yaml: 2.8.0 + vitest@3.1.3(@types/debug@4.1.12)(@types/node@20.17.47)(jiti@2.4.2)(jsdom@20.0.3)(lightningcss@1.29.2)(tsx@4.19.4)(yaml@2.8.0): dependencies: '@vitest/expect': 3.1.3 @@ -20144,6 +20260,47 @@ snapshots: - tsx - yaml + vitest@3.1.3(@types/debug@4.1.12)(@types/node@22.15.20)(jiti@2.4.2)(jsdom@20.0.3)(lightningcss@1.29.2)(tsx@4.19.4)(yaml@2.8.0): + dependencies: + '@vitest/expect': 3.1.3 + '@vitest/mocker': 3.1.3(vite@6.3.5(@types/node@22.15.20)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.4)(yaml@2.8.0)) + '@vitest/pretty-format': 3.1.3 + '@vitest/runner': 3.1.3 + '@vitest/snapshot': 3.1.3 + '@vitest/spy': 3.1.3 + '@vitest/utils': 3.1.3 + chai: 5.2.0 + debug: 4.4.1(supports-color@8.1.1) + expect-type: 1.2.1 + magic-string: 0.30.17 + pathe: 2.0.3 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.13 + tinypool: 1.0.2 + tinyrainbow: 2.0.0 + vite: 6.3.5(@types/node@22.15.20)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.4)(yaml@2.8.0) + vite-node: 3.1.3(@types/node@22.15.20)(jiti@2.4.2)(lightningcss@1.29.2)(tsx@4.19.4)(yaml@2.8.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.12 + '@types/node': 22.15.20 + jsdom: 20.0.3 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + void-elements@3.1.0: {} vscode-jsonrpc@8.2.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 6c57767fab..84d450bd1a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,4 +1,6 @@ packages: - - 'src' - - 'webview-ui' - - 'e2e' + - "src" + - "webview-ui" + - "e2e" + - "apps/*" + - "packages/*" diff --git a/src/.gitignore b/src/.gitignore index c7d4d26e07..cdbce7731c 100644 --- a/src/.gitignore +++ b/src/.gitignore @@ -1,3 +1,5 @@ +README.md +CHANGELOG.md +LICENSE webview-ui - assets/vscode-material-icons diff --git a/src/.vscodeignore b/src/.vscodeignore index cdbdf13cb2..3aaf4b677b 100644 --- a/src/.vscodeignore +++ b/src/.vscodeignore @@ -1,6 +1,11 @@ # Exclude everything ** +# Include README.md, CHANGELOG.md and LICENSE +!README.md +!CHANGELOG.md +!LICENSE + # Include package.json !package.json !package.nls.* @@ -15,9 +20,6 @@ !webview-ui/build/assets/*.ttf !webview-ui/build/assets/*.css -# Include the license file -!LICENSE - # Include default themes JSON files used in getTheme !integrations/theme/default-themes/** diff --git a/src/LICENSE b/src/LICENSE deleted file mode 100644 index 302fa51c8a..0000000000 --- a/src/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2025 Roo Code, Inc. - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/src/assets/icons/icon-nightly.png b/src/assets/icons/icon-nightly.png new file mode 100644 index 0000000000..b0bef29cc9 Binary files /dev/null and b/src/assets/icons/icon-nightly.png differ diff --git a/src/esbuild.js b/src/esbuild.js index 12073f30dc..5bb554989a 100644 --- a/src/esbuild.js +++ b/src/esbuild.js @@ -5,6 +5,38 @@ const path = require("path") const production = process.argv.includes("--production") const watch = process.argv.includes("--watch") +/** + * @param {[string, string][]} copyPaths + * @param {string} srcDir + * @param {string} dstDir + * @returns {void} + */ +function copyPaths(copyPaths, srcDir, dstDir) { + copyPaths.forEach(([srcRelPath, dstRelPath]) => { + const stats = fs.lstatSync(path.join(srcDir, srcRelPath)) + + if (stats.isDirectory()) { + if (fs.existsSync(path.join(dstDir, dstRelPath))) { + fs.rmSync(path.join(dstDir, dstRelPath), { recursive: true }) + } + + fs.mkdirSync(path.join(dstDir, dstRelPath), { recursive: true }) + + const count = copyDir(path.join(srcDir, srcRelPath), path.join(dstDir, dstRelPath), 0) + console.log(`[copyPaths] Copied ${count} files from ${srcRelPath} to ${dstRelPath}`) + } else { + fs.copyFileSync(path.join(srcDir, srcRelPath), path.join(dstDir, dstRelPath)) + console.log(`[copyPaths] Copied ${srcRelPath} to ${dstRelPath}`) + } + }) +} + +/** + * @param {string} srcDir + * @param {string} dstDir + * @param {number} count + * @returns {number} + */ function copyDir(srcDir, dstDir, count) { const entries = fs.readdirSync(srcDir, { withFileTypes: true }) @@ -172,27 +204,17 @@ const copyAssets = { name: "copy-assets", setup(build) { build.onEnd(() => { - const copyPaths = [ - ["node_modules/vscode-material-icons/generated", "assets/vscode-material-icons"], - ["../webview-ui/audio", "webview-ui/audio"], - ] - - for (const [srcRelPath, dstRelPath] of copyPaths) { - const srcDir = path.join(__dirname, srcRelPath) - const dstDir = path.join(__dirname, dstRelPath) - - if (!fs.existsSync(srcDir)) { - throw new Error(`Directory does not exist: ${srcDir}`) - } - - if (fs.existsSync(dstDir)) { - fs.rmSync(dstDir, { recursive: true }) - } - - fs.mkdirSync(dstDir, { recursive: true }) - const count = copyDir(srcDir, dstDir, 0) - console.log(`[copy-assets] Copied ${count} assets from ${srcDir} to ${dstDir}`) - } + copyPaths( + [ + ["../README.md", "README.md"], + ["../CHANGELOG.md", "CHANGELOG.md"], + ["../LICENSE", "LICENSE"], + ["node_modules/vscode-material-icons/generated", "assets/vscode-material-icons"], + ["../webview-ui/audio", "webview-ui/audio"], + ], + __dirname, + __dirname, + ) }) }, } diff --git a/src/exports/roo-code.d.ts b/src/exports/roo-code.d.ts index e048db2b68..20220aa0cf 100644 --- a/src/exports/roo-code.d.ts +++ b/src/exports/roo-code.d.ts @@ -1545,6 +1545,7 @@ declare const Package: { readonly name: string readonly version: string readonly outputChannel: string + readonly sha: string | undefined } /** * ProviderName diff --git a/src/extension.ts b/src/extension.ts index 5304a5624b..cb71016f75 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -51,7 +51,7 @@ export async function activate(context: vscode.ExtensionContext) { extensionContext = context outputChannel = vscode.window.createOutputChannel(Package.outputChannel) context.subscriptions.push(outputChannel) - outputChannel.appendLine(`${Package.name} extension activated`) + outputChannel.appendLine(`${Package.name} extension activated - ${JSON.stringify(Package)}`) // Migrate old settings to new await migrateSettings(context, outputChannel) diff --git a/src/package.json b/src/package.json index d88a5c5b9f..3afb3b15d5 100644 --- a/src/package.json +++ b/src/package.json @@ -3,7 +3,7 @@ "displayName": "%extension.displayName%", "description": "%extension.description%", "publisher": "RooVeterinaryInc", - "version": "3.16.6", + "version": "3.17.2", "icon": "assets/icons/icon.png", "galleryBanner": { "color": "#617A91", @@ -323,13 +323,13 @@ "pretest": "node esbuild.js", "test": "jest -w=40% && vitest run", "format": "prettier --write .", - "build": "pnpm lint && pnpm check-types && node esbuild.js --production --bundle --external:vscode --format=cjs --platform=node && pnpm --filter @roo-code/vscode-webview build", - "build:development": "pnpm lint && pnpm check-types && node esbuild.js && pnpm --filter @roo-code/vscode-webview build", + "build": "pnpm lint && pnpm check-types && rimraf dist && node esbuild.js --production && rimraf webview-ui && pnpm --filter @roo-code/vscode-webview build", + "build:development": "pnpm lint && pnpm check-types && rimraf dist && node esbuild.js && rimraf webview-ui && pnpm --filter @roo-code/vscode-webview build", "publish:marketplace": "vsce publish --no-dependencies && ovsx publish --no-dependencies", "publish": "pnpm vsix && changeset publish && pnpm install --package-lock-only", "version-packages": "changeset version && pnpm install --package-lock-only", "vscode:prepublish": "pnpm build", - "vsix": "rimraf dist && rimraf webview-ui && rimraf ../bin && mkdirp ../bin && npx vsce package --no-dependencies --out ../bin", + "vsix": "mkdirp ../bin && npx vsce package --no-dependencies --out ../bin", "watch:esbuild": "node esbuild.js --watch", "watch:tsc": "tsc --noEmit --watch --project tsconfig.json", "generate-types": "tsx scripts/generate-types.mts" diff --git a/src/schemas/index.ts b/src/schemas/index.ts index da3817caf7..f780fb5702 100644 --- a/src/schemas/index.ts +++ b/src/schemas/index.ts @@ -19,10 +19,11 @@ import { publisher, name, version } from "../package.json" // by VSCode, but that build artifact is not used during the transpile step of // the build, so we still need this override mechanism. export const Package = { - publisher: process.env.PKG_PUBLISHER || publisher, + publisher, name: process.env.PKG_NAME || name, version: process.env.PKG_VERSION || version, outputChannel: process.env.PKG_OUTPUT_CHANNEL || "Roo-Code", + sha: process.env.PKG_SHA, } as const /** diff --git a/webview-ui/src/components/settings/About.tsx b/webview-ui/src/components/settings/About.tsx index debc55596d..fb045932c0 100644 --- a/webview-ui/src/components/settings/About.tsx +++ b/webview-ui/src/components/settings/About.tsx @@ -5,6 +5,7 @@ import { Info, Download, Upload, TriangleAlert } from "lucide-react" import { VSCodeCheckbox, VSCodeLink } from "@vscode/webview-ui-toolkit/react" +import { Package } from "@roo/schemas" import { TelemetrySetting } from "@roo/shared/TelemetrySetting" import { vscode } from "@/utils/vscode" @@ -15,17 +16,21 @@ import { SectionHeader } from "./SectionHeader" import { Section } from "./Section" type AboutProps = HTMLAttributes & { - version: string telemetrySetting: TelemetrySetting setTelemetrySetting: (setting: TelemetrySetting) => void } -export const About = ({ version, telemetrySetting, setTelemetrySetting, className, ...props }: AboutProps) => { +export const About = ({ telemetrySetting, setTelemetrySetting, className, ...props }: AboutProps) => { const { t } = useAppTranslation() return (
- +
{t("settings:sections.about")}
diff --git a/webview-ui/src/components/settings/SettingsView.tsx b/webview-ui/src/components/settings/SettingsView.tsx index f9c2425222..03fd417bec 100644 --- a/webview-ui/src/components/settings/SettingsView.tsx +++ b/webview-ui/src/components/settings/SettingsView.tsx @@ -99,7 +99,7 @@ const SettingsView = forwardRef(({ onDone, t const { t } = useAppTranslation() const extensionState = useExtensionState() - const { currentApiConfigName, listApiConfigMeta, uriScheme, version, settingsImportedAt } = extensionState + const { currentApiConfigName, listApiConfigMeta, uriScheme, settingsImportedAt } = extensionState const [isDiscardDialogShow, setDiscardDialogShow] = useState(false) const [isChangeDetected, setChangeDetected] = useState(false) @@ -638,11 +638,7 @@ const SettingsView = forwardRef(({ onDone, t {/* About Section */} {activeTab === "about" && ( - + )}
diff --git a/webview-ui/vite.config.ts b/webview-ui/vite.config.ts index 348aaa266c..fa00cdf0a8 100644 --- a/webview-ui/vite.config.ts +++ b/webview-ui/vite.config.ts @@ -1,5 +1,6 @@ import { resolve } from "path" import fs from "fs" +import { execSync } from "child_process" import { defineConfig, type Plugin } from "vite" import react from "@vitejs/plugin-react" @@ -21,18 +22,15 @@ function wasmPlugin(): Plugin { } } -// Custom plugin to write the server port to a file const writePortToFile = () => { return { name: "write-port-to-file", configureServer(server) { - // Write the port to a file when the server starts server.httpServer?.once("listening", () => { const address = server.httpServer.address() const port = typeof address === "object" && address ? address.port : null if (port) { - // Write to a file in the project root const portFilePath = resolve(__dirname, "../.vite-port") fs.writeFileSync(portFilePath, port.toString()) console.log(`[Vite Plugin] Server started on port ${port}`) @@ -45,46 +43,79 @@ const writePortToFile = () => { } } +function getGitSha() { + let gitSha: string | undefined = undefined + + try { + gitSha = execSync("git rev-parse HEAD").toString().trim() + } catch (e) {} + + return gitSha +} + // https://vitejs.dev/config/ -export default defineConfig({ - plugins: [react(), tailwindcss(), writePortToFile(), wasmPlugin()], - resolve: { - alias: { - "@": resolve(__dirname, "./src"), - "@src": resolve(__dirname, "./src"), - "@roo": resolve(__dirname, "../src"), +export default defineConfig(({ mode }) => { + let outDir = "../src/webview-ui/build" + + const define: Record = { + "process.platform": JSON.stringify(process.platform), + } + + // TODO: We can use `@roo-code/build` to generate `define` once the + // monorepo is deployed. + if (mode === "nightly") { + outDir = "../apps/vscode-nightly/build/webview-ui/build" + + const { name, version } = JSON.parse(fs.readFileSync("../apps/vscode-nightly/package.nightly.json", "utf8")) + + define["process.env.PKG_NAME"] = JSON.stringify(name) + define["process.env.PKG_VERSION"] = JSON.stringify(version) + define["process.env.PKG_OUTPUT_CHANNEL"] = JSON.stringify("Roo-Code-Nightly") + + const gitSha = getGitSha() + + if (gitSha) { + define["process.env.PKG_SHA"] = JSON.stringify(gitSha) + } + } + + return { + plugins: [react(), tailwindcss(), writePortToFile(), wasmPlugin()], + resolve: { + alias: { + "@": resolve(__dirname, "./src"), + "@src": resolve(__dirname, "./src"), + "@roo": resolve(__dirname, "../src"), + }, }, - }, - build: { - outDir: "../src/webview-ui/build", - emptyOutDir: true, - reportCompressedSize: false, - sourcemap: true, - rollupOptions: { - output: { - entryFileNames: `assets/[name].js`, - chunkFileNames: `assets/[name].js`, - assetFileNames: `assets/[name].[ext]`, + build: { + outDir, + emptyOutDir: true, + reportCompressedSize: false, + sourcemap: true, + rollupOptions: { + output: { + entryFileNames: `assets/[name].js`, + chunkFileNames: `assets/[name].js`, + assetFileNames: `assets/[name].[ext]`, + }, }, }, - }, - server: { - hmr: { - host: "localhost", - protocol: "ws", + server: { + hmr: { + host: "localhost", + protocol: "ws", + }, + cors: { + origin: "*", + methods: "*", + allowedHeaders: "*", + }, }, - cors: { - origin: "*", - methods: "*", - allowedHeaders: "*", + define, + optimizeDeps: { + exclude: ["@vscode/codicons", "vscode-oniguruma", "shiki"], }, - }, - define: { - "process.platform": JSON.stringify(process.platform), - "process.env.VSCODE_TEXTMATE_DEBUG": JSON.stringify(process.env.VSCODE_TEXTMATE_DEBUG), - }, - optimizeDeps: { - exclude: ["@vscode/codicons", "vscode-oniguruma", "shiki"], - }, - assetsInclude: ["**/*.wasm", "**/*.wav"], + assetsInclude: ["**/*.wasm", "**/*.wav"], + } })