diff --git a/.github/labeler.yml b/.github/labeler.yml index 49c1a75..fdbcde9 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -7,3 +7,6 @@ config: theme: - "themes/**" + +UI: + - "UI/**/**" diff --git a/UI/git_sidebar/gitman.js b/UI/git_sidebar/gitman.js new file mode 100644 index 0000000..dcfabc7 --- /dev/null +++ b/UI/git_sidebar/gitman.js @@ -0,0 +1,48 @@ +class GitManager { + constructor(options = {}) { + this.api = options.api; + this.workspace = options.workspace || null; + } + + setWorkspace(workspace) { + this.workspace = workspace || null; + } + + async run(operation, ...args) { + if (!this.workspace) throw new Error('Open a folder to use Git.'); + if (!this.api || typeof this.api.run !== 'function') throw new Error('Git API is unavailable.'); + const result = await this.api.run(operation, this.workspace, args); + if (!result.ok) { + const error = new Error(result.stderr || result.stdout || 'Git command failed.'); + error.code = result.code; + throw error; + } + return result.stdout; + } + + async init() { + return this.run('init'); + } + + async addAll() { + return this.run('addAll'); + } + + async commit(message) { + return this.run('commit', message); + } + + async status() { + return this.run('status'); + } + + async push() { + return this.run('push'); + } + + async pull() { + return this.run('pull'); + } +} + +module.exports = GitManager; diff --git a/UI/git_sidebar/gitman.test.js b/UI/git_sidebar/gitman.test.js new file mode 100644 index 0000000..8c82933 --- /dev/null +++ b/UI/git_sidebar/gitman.test.js @@ -0,0 +1,16 @@ +const assert = require('node:assert/strict'); +const test = require('node:test'); +const GitManager = require('./gitman'); + +test('passes the selected workspace and arguments to the Git bridge', async () => { + let request; + const manager = new GitManager({ api: { run: async (...args) => { request = args; return { ok: true, stdout: 'done' }; } } }); + manager.setWorkspace('/selected/workspace'); + assert.equal(await manager.commit('message with spaces'), 'done'); + assert.deepEqual(request, ['commit', '/selected/workspace', ['message with spaces']]); +}); + +test('rejects structured bridge errors', async () => { + const manager = new GitManager({ workspace: '/workspace', api: { run: async () => ({ ok: false, stderr: 'failed', code: 1 }) } }); + await assert.rejects(manager.status(), /failed/); +}); diff --git a/UI/git_sidebar/gitstub.js b/UI/git_sidebar/gitstub.js new file mode 100644 index 0000000..2db84e7 --- /dev/null +++ b/UI/git_sidebar/gitstub.js @@ -0,0 +1,50 @@ +class StatusStub { + static parse(raw) { + if (!raw || !raw.trim()) { + return []; + } + + return raw.split(/\r?\n/).filter(Boolean).map(line => { + const code = line.slice(0, 2); + const file = line.slice(2).trim(); + return { code, file }; + }); + } +} + +class BranchStub { + static parse(raw) { + const lines = raw.split("\n").filter(Boolean); + + let current = null; + const branches = []; + + for (const line of lines) { + if (line.startsWith("*")) { + current = line.replace(/^\*\s*/, "").trim(); + branches.push(current); + } else { + branches.push(line.trim()); + } + } + return { current, branches }; + } +} + +class LogStub { + static parse(raw) { + if (!raw.trim()) return []; + + return raw.split("\n").filter(Boolean).map(line => { + const [hash, author, message, date] = line.split("|"); + return { hash, author, message, date }; + }); + } +} + + +module.exports = { + StatusStub, + BranchStub, + LogStub +}; diff --git a/UI/git_sidebar/gitstub.test.js b/UI/git_sidebar/gitstub.test.js new file mode 100644 index 0000000..17f1999 --- /dev/null +++ b/UI/git_sidebar/gitstub.test.js @@ -0,0 +1,10 @@ +const assert = require('node:assert/strict'); +const test = require('node:test'); +const { StatusStub } = require('./gitstub'); + +test('parses modified, added, deleted, renamed, and untracked files', () => { + assert.deepEqual(StatusStub.parse(' M modified.js\nA added.js\n D deleted.js\nR old.js -> new.js\n?? untracked.js'), [ + { code: ' M', file: 'modified.js' }, { code: 'A ', file: 'added.js' }, { code: ' D', file: 'deleted.js' }, + { code: 'R ', file: 'old.js -> new.js' }, { code: '??', file: 'untracked.js' } + ]); +}); diff --git a/UI/git_sidebar/gitui.js b/UI/git_sidebar/gitui.js new file mode 100644 index 0000000..5b0bc14 --- /dev/null +++ b/UI/git_sidebar/gitui.js @@ -0,0 +1,135 @@ +const GitManager = require('./gitman'); +const { StatusStub } = require('./gitstub'); + +class GitUi { + constructor(containerEl, options = {}) { + this.containerEl = containerEl; + this.gitman = options.gitman || new GitManager({ api: options.gitApi }); + this.workspace = null; + this.state = { mode: 'empty', entries: [], message: 'Open a folder to use Git.', messageType: 'info' }; + this.listening = false; + } + + async setWorkspace(workspace) { + this.workspace = workspace || null; + this.gitman.setWorkspace(this.workspace); + if (!this.workspace) { + this.state = { mode: 'empty', entries: [], message: 'Open a folder to use Git.', messageType: 'info' }; + this.render(); + return; + } + await this.refresh(); + } + + initListeners() { + if (!this.containerEl || this.listening) return; + this.listening = true; + this.containerEl.addEventListener('click', (event) => this.handleClick(event)); + this.containerEl.addEventListener('input', (event) => { + if (event.target.id === 'git-commit-message') this.renderActionState(); + }); + } + + init_listners() { this.initListeners(); } + + async handleClick(event) { + const action = event.target.dataset?.gitAction; + if (!action) return; + if (action === 'refresh') return this.refresh(); + const operations = { init: 'init', stage: 'addAll', commit: 'commit', pull: 'pull', push: 'push' }; + const method = operations[action]; + if (!method) return; + const message = action === 'commit' ? this.containerEl.querySelector('#git-commit-message')?.value.trim() : undefined; + if (action === 'commit' && !message) return; + await this.runAction(method, message); + } + + async runAction(method, argument) { + this.setMessage(`${this.actionLabel(method)}…`, 'loading'); + try { + const output = await this.gitman[method](argument); + await this.refresh(`${this.actionLabel(method)} completed${output ? `: ${output}` : '.'}`); + } catch (error) { + this.setMessage(error.message || String(error), 'error'); + } + } + + actionLabel(method) { + return { init: 'Initializing repository', addAll: 'Staging files', commit: 'Committing changes', pull: 'Pulling changes', push: 'Pushing changes' }[method]; + } + + async refresh(successMessage = '') { + if (!this.workspace) return; + this.state = { ...this.state, mode: 'loading', message: 'Loading repository status…', messageType: 'loading' }; + this.render(); + try { + const raw = await this.gitman.status(); + this.state = { mode: 'repository', entries: StatusStub.parse(raw), message: successMessage || 'Repository status is up to date.', messageType: successMessage ? 'success' : 'info' }; + } catch (error) { + if (/not a git repository/i.test(error.message || '')) { + this.state = { mode: 'non-repository', entries: [], message: 'This folder is not a Git repository.', messageType: 'info' }; + } else { + this.state = { mode: 'error', entries: [], message: error.message || String(error), messageType: 'error' }; + } + } + this.render(); + } + + setMessage(message, messageType) { + this.state.message = message; + this.state.messageType = messageType; + this.render(); + } + + create(tagName, properties = {}, text = '') { + const element = this.containerEl.ownerDocument.createElement(tagName); + Object.assign(element, properties); + if (text) element.textContent = text; + return element; + } + + button(label, action, disabled = false) { + const button = this.create('button', { type: 'button', disabled }, label); + button.dataset.gitAction = action; + return button; + } + + render() { + if (!this.containerEl || !this.containerEl.ownerDocument) return; + const panel = this.create('section', { className: 'git-panel' }); + panel.append(this.create('h2', { className: 'git-title' }, 'Source Control')); + const status = this.create('p', { className: `git-message git-message--${this.state.messageType}`, role: this.state.messageType === 'error' ? 'alert' : 'status' }, this.state.message); + panel.append(status); + + if (this.state.mode === 'non-repository') { + panel.append(this.button('Init repository', 'init')); + } else if (this.state.mode === 'repository' || this.state.mode === 'loading' || this.state.mode === 'error') { + const busy = this.state.mode === 'loading' || this.state.messageType === 'loading'; + const toolbar = this.create('div', { className: 'git-actions' }); + toolbar.append(this.button('Refresh', 'refresh', busy), this.button('Stage all', 'stage', busy || !this.state.entries.length), this.button('Pull', 'pull', busy), this.button('Push', 'push', busy)); + panel.append(toolbar); + const input = this.create('input', { id: 'git-commit-message', className: 'git-commit-input', type: 'text', placeholder: 'Commit message', disabled: busy || !this.state.entries.length }); + input.setAttribute('aria-label', 'Commit message'); + panel.append(input, this.button('Commit', 'commit', true)); + const list = this.create('ul', { className: 'git-status-list' }); + list.setAttribute('aria-label', 'Changed files'); + if (!this.state.entries.length) list.append(this.create('li', { className: 'git-clean' }, 'No changes')); + for (const entry of this.state.entries) { + const item = this.create('li', { className: 'git-status-entry' }); + item.append(this.create('span', { className: 'git-status-code' }, entry.code), this.create('span', { className: 'git-status-file', title: entry.file }, entry.file)); + list.append(item); + } + panel.append(list); + } + this.containerEl.replaceChildren(panel); + this.renderActionState(); + } + + renderActionState() { + const input = this.containerEl?.querySelector?.('#git-commit-message'); + const commit = this.containerEl?.querySelector?.('[data-git-action="commit"]'); + if (input && commit) commit.disabled = input.disabled || !input.value.trim(); + } +} + +module.exports = { GitUi }; diff --git a/UI/git_sidebar/gitui.test.js b/UI/git_sidebar/gitui.test.js new file mode 100644 index 0000000..dab0f42 --- /dev/null +++ b/UI/git_sidebar/gitui.test.js @@ -0,0 +1,71 @@ +const assert = require('node:assert/strict'); +const test = require('node:test'); +const { GitUi } = require('./gitui'); + +class FakeElement { + constructor(tagName, document) { this.tagName = tagName; this.ownerDocument = document; this.children = []; this.dataset = {}; this.disabled = false; this.value = ''; } + append(...children) { this.children.push(...children); } + replaceChildren(...children) { this.children = children; } + setAttribute(name, value) { this[name] = value; } + addEventListener() {} + querySelector(selector) { + const matches = (element) => selector.startsWith('#') ? element.id === selector.slice(1) : selector === '[data-git-action="commit"]' && element.dataset.gitAction === 'commit'; + const visit = (element) => matches(element) ? element : element.children.map(visit).find(Boolean); + return visit(this); + } + get textContent() { return this._text || this.children.map((child) => child.textContent).join(' '); } + set textContent(value) { this._text = value; } +} + +function createContainer() { + const document = { createElement: (tagName) => new FakeElement(tagName, document) }; + return new FakeElement('div', document); +} + +function deferred() { + let resolve; + const promise = new Promise((done) => { resolve = done; }); + return { promise, resolve }; +} + +test('renders empty, loading, repository, success, non-repository, and error states', async () => { + const container = createContainer(); + const pending = deferred(); + const gitman = { setWorkspace() {}, status: () => pending.promise }; + const ui = new GitUi(container, { gitman }); + ui.render(); + assert.match(container.textContent, /Open a folder/); + const refresh = ui.setWorkspace('/workspace'); + assert.match(container.textContent, /Loading repository status/); + pending.resolve(' M changed.js\n?? new.js'); + await refresh; + assert.match(container.textContent, /changed\.js/); + ui.setMessage('Staging files completed.', 'success'); + assert.match(container.textContent, /completed/); + gitman.status = async () => { throw new Error('fatal: not a git repository'); }; + await ui.refresh(); + assert.match(container.textContent, /Init repository/); + gitman.status = async () => { throw new Error('permission denied'); }; + await ui.refresh(); + assert.match(container.textContent, /permission denied/); +}); + +test('actions invoke the expected operation and refresh status', async () => { + let stageCalls = 0; + let statusCalls = 0; + const gitman = { setWorkspace() {}, status: async () => { statusCalls++; return ' M file.js'; }, addAll: async () => { stageCalls++; return ''; } }; + const ui = new GitUi(createContainer(), { gitman }); + await ui.setWorkspace('/workspace'); + await ui.runAction('addAll'); + assert.equal(stageCalls, 1); + assert.equal(statusCalls, 2); + assert.equal(ui.state.messageType, 'success'); +}); + +test('clearing the workspace clears repository state', async () => { + const ui = new GitUi(createContainer(), { gitman: { setWorkspace() {}, status: async () => '' } }); + await ui.setWorkspace('/workspace'); + await ui.setWorkspace(null); + assert.equal(ui.state.mode, 'empty'); + assert.deepEqual(ui.state.entries, []); +}); diff --git a/UI/tab_manager/tabman.js b/UI/tab_manager/tabman.js index 933ef08..27f429d 100644 --- a/UI/tab_manager/tabman.js +++ b/UI/tab_manager/tabman.js @@ -139,3 +139,6 @@ function escapeHtml(value) { '"': '"' }[char])); } + +// TODO: Needs fixing so module exports work. +// module.exports = TabManager; \ No newline at end of file diff --git a/docs/index.html b/docs/index.html index 6e39ff5..3258427 100644 --- a/docs/index.html +++ b/docs/index.html @@ -43,6 +43,7 @@

Features

  • ANTI AI: The IDE has NO AI FEATURES
  • C support: This IDE nativly supports C and C++ syntax highlighting.
  • Python and WebDev Language support.
  • +
  • Git support.
  • diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..7c88e86 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,71 @@ +import js from "@eslint/js"; +import globals from "globals"; +import { defineConfig } from "eslint/config"; + +export default defineConfig([ + // Backend: Node + CommonJS (GitManager, stubs, runner, Electron main/preload) + { + files: [ + "main.js", + "preload.js", + "main/**/*.js", + "preload/**/*.js", + "backend/**/*.js", + "git/**/*.js", + "UI/git_sidebar/**.js", + "UI/codebase_indexer/cb_index.js", + "patchgen.js" + ], + extends: [js.configs.recommended], + languageOptions: { + sourceType: "commonjs", + globals: { + ...globals.node, + ...globals.commonjs, + ...globals.es2021, + + // Electron globals + BrowserWindow: "readonly", + ipcMain: "readonly", + ipcRenderer: "readonly", + contextBridge: "readonly" + } + } + }, + + { + files: ["UI/codebase_indexer/editor_integration.js"], + extends: [js.configs.recommended], + languageOptions: { + sourceType: "commonjs", + globals: { ...globals.node, ...globals.browser } + } + }, + + { + files: ["UI/tab_manager/**/*.js"], + extends: [js.configs.recommended], + languageOptions: { + sourceType: "module", + globals: { ...globals.browser } + } + }, + + // Renderer: Browser + AMD (Monaco) + { + files: ["renderer/**/*.js"], + extends: [js.configs.recommended], + languageOptions: { + sourceType: "script", // browser JS + globals: { + ...globals.browser, + + // Monaco AMD loader + require: "readonly", + define: "readonly", + + monaco: "readonly" + } + } + } +]); diff --git a/index.html b/index.html index 31e542e..9cdf270 100644 --- a/index.html +++ b/index.html @@ -82,6 +82,9 @@
    Explorer
    +
    @@ -453,8 +456,13 @@ tabManager.onNewTabRequest = () => createNewTab(); async function refreshFileTree(directoryPath) { - if (!directoryPath) return; + if (!directoryPath) { + currentDirectory = null; + await window.api.setGitWorkspace(null); + return; + } currentDirectory = directoryPath; + await window.api.setGitWorkspace(directoryPath); await renderDirectory(directoryPath, document.getElementById('tree-root')); } diff --git a/main.js b/main.js index 2ff1eca..75ec086 100644 --- a/main.js +++ b/main.js @@ -3,6 +3,7 @@ const { app, BrowserWindow, ipcMain, dialog } = require('electron'); const path = require('path'); const fs = require('fs'); const { spawn } = require('child_process'); +const { runGitRequest } = require('./main/git-service'); let clangdProcess = null; let clangdBuffer = Buffer.alloc(0); @@ -321,6 +322,8 @@ ipcMain.handle('directory:list', async (_event, directoryPath) => { .sort((left, right) => Number(right.isDirectory) - Number(left.isDirectory) || left.name.localeCompare(right.name)); }); +ipcMain.handle('git:run', (_event, request) => runGitRequest(request)); + ipcMain.handle('clangd:start', (event, rootPath) => startClangd(event, rootPath)); ipcMain.on('clangd:message', (_event, message) => sendToClangd(message)); ipcMain.on('clangd:stop', stopClangd); @@ -342,4 +345,4 @@ app.on('before-quit', (event) => { app.on('will-quit', () => { stopClangd(); stopPyright(); -}); \ No newline at end of file +}); diff --git a/main/git-service.js b/main/git-service.js new file mode 100644 index 0000000..fa7b527 --- /dev/null +++ b/main/git-service.js @@ -0,0 +1,37 @@ +const path = require('node:path'); +const { execFile } = require('node:child_process'); + +const OPERATIONS = Object.freeze({ + init: ['init'], status: ['status', '--short'], addAll: ['add', '--all'], pull: ['pull'], push: ['push'] +}); + +function validateGitRequest(request) { + if (!request || typeof request !== 'object') throw new Error('Invalid Git request.'); + const { operation, workspace, args = [] } = request; + if (!path.isAbsolute(workspace || '')) throw new Error('A valid workspace path is required.'); + if (!Array.isArray(args) || args.some((argument) => typeof argument !== 'string')) throw new Error('Invalid Git arguments.'); + if (operation === 'commit') { + if (args.length !== 1 || !args[0].trim() || args[0].length > 1000) throw new Error('A commit message is required.'); + return { workspace: path.resolve(workspace), gitArgs: ['commit', '-m', args[0]] }; + } + if (!Object.hasOwn(OPERATIONS, operation) || args.length) throw new Error('Unsupported Git operation.'); + return { workspace: path.resolve(workspace), gitArgs: OPERATIONS[operation] }; +} + +function runGitRequest(request, executor = execFile) { + let command; + try { command = validateGitRequest(request); } catch (error) { + return Promise.resolve({ ok: false, stdout: '', stderr: error.message, code: 'INVALID_REQUEST' }); + } + return new Promise((resolve) => { + executor('git', command.gitArgs, { cwd: command.workspace }, (error, stdout = '', stderr = '') => { + if (error) { + resolve({ ok: false, stdout: stdout.trim(), stderr: stderr.trim() || error.message, code: typeof error.code === 'number' ? error.code : 'GIT_ERROR' }); + return; + } + resolve({ ok: true, stdout: stdout.trim(), stderr: stderr.trim(), code: 0 }); + }); + }); +} + +module.exports = { OPERATIONS, validateGitRequest, runGitRequest }; diff --git a/main/git-service.test.js b/main/git-service.test.js new file mode 100644 index 0000000..7337759 --- /dev/null +++ b/main/git-service.test.js @@ -0,0 +1,18 @@ +const assert = require('node:assert/strict'); +const test = require('node:test'); +const { runGitRequest, validateGitRequest } = require('./git-service'); + +test('accepts allowlisted operations and rejects unsupported operations', async () => { + assert.deepEqual(validateGitRequest({ operation: 'status', workspace: '/workspace' }).gitArgs, ['status', '--short']); + const result = await runGitRequest({ operation: 'shell', workspace: '/workspace' }); + assert.equal(result.ok, false); + assert.equal(result.code, 'INVALID_REQUEST'); +}); + +test('uses the selected workspace as cwd and keeps commit arguments separate', async () => { + let invocation; + const executor = (...args) => { invocation = args.slice(0, 3); args[3](null, 'committed\n', ''); }; + const result = await runGitRequest({ operation: 'commit', workspace: '/selected/project', args: ['fix; echo unsafe'] }, executor); + assert.equal(result.ok, true); + assert.deepEqual(invocation, ['git', ['commit', '-m', 'fix; echo unsafe'], { cwd: '/selected/project' }]); +}); diff --git a/package-lock.json b/package-lock.json index ccd0fdf..d8d46b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,8 +14,72 @@ "vscode-ws-jsonrpc": "^3.5.0" }, "devDependencies": { + "@eslint/js": "^10.0.1", "electron": "^44.3.0", - "electron-builder": "^26.15.3" + "electron-builder": "^26.15.3", + "eslint": "^10.10.0", + "globals": "^17.12.0" + } + }, + "node_modules/@cacheable/memory": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.2.0.tgz", + "integrity": "sha512-CTLKqLItRCEixEAewD3/j9DB3/o96gpTPD4eJ1v+DGOlxZRZncRQkGYqqnAGCscYd6RNeXfGeiuCphsPtqyIfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/utils": "^2.5.0", + "@keyv/bigmap": "^1.3.1", + "hookified": "^1.15.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/memory/node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.4.0", + "hookified": "^1.15.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/memory/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/@cacheable/utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.5.0.tgz", + "integrity": "sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.5.1", + "keyv": "^5.6.0" + } + }, + "node_modules/@cacheable/utils/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" } }, "node_modules/@codingame/monaco-vscode-api": { @@ -705,6 +769,239 @@ "node": ">=14.14" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-array/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.3.tgz", + "integrity": "sha512-IkO+/KEUvwbVpiURZg+P7zF74z5Jxe0UgJxVni+RtoHQ6IZieXaO02kmadomap/q+l6bc/jdPGGqTjhuZnuz1Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@isaacs/fs-minipass": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", @@ -718,6 +1015,13 @@ "node": ">=18.0.0" } }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "dev": true, + "license": "MIT" + }, "node_modules/@malept/cross-spawn-promise": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-2.0.0.tgz", @@ -890,6 +1194,20 @@ "@types/ms": "*" } }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/fs-extra": { "version": "9.0.13", "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz", @@ -907,6 +1225,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/keyv": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", @@ -977,6 +1302,29 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/agent-base": { "version": "7.1.4", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", @@ -1404,6 +1752,20 @@ "node": ">=6.0.0" } }, + "node_modules/cacheable": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.5.0.tgz", + "integrity": "sha512-60cyAOytib/OzBw1JNSoSV/boK1AtHryDIjvVBk7XbN4ugfkM3+Sry7fEjNgPMGgOjuaZPAp8ruZ0Cxafwyq9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/memory": "^2.2.0", + "@cacheable/utils": "^2.5.0", + "hookified": "^1.15.0", + "keyv": "^5.6.0", + "qified": "^0.10.1" + } + }, "node_modules/cacheable-lookup": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", @@ -1433,6 +1795,16 @@ "node": ">=8" } }, + "node_modules/cacheable/node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -1686,6 +2058,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/defer-to-connect": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", @@ -2125,7 +2504,6 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", - "optional": true, "engines": { "node": ">=10" }, @@ -2133,26 +2511,258 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/exponential-backoff": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", - "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "node_modules/eslint": { + "version": "10.10.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.10.0.tgz", + "integrity": "sha512-NPXn6r5zl4uET1DAVPaOwzX3rut4c0wcmw3dWJAfOsTM5+TogXo0DDjz8pwm/hL8cyVNpHqeK4JpN0NjnyFFNw==", "dev": true, - "license": "Apache-2.0" + "license": "MIT", + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.3", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "11.1.5 || >11.1.6 <12", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", "dev": true, - "license": "MIT" + "license": "BSD-2-Clause", + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } }, - "node_modules/fast-uri": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", - "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, - "funding": [ + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", + "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", + "dev": true, + "funding": [ { "type": "github", "url": "https://github.com/sponsors/fastify" @@ -2182,6 +2792,16 @@ } } }, + "node_modules/file-entry-cache": { + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.5.tgz", + "integrity": "sha512-+PFTHITI08JIGhnNpGNI8T8inUpgZfk3GNEqfT9R2zZV2iFXg3CvqzSl/uEhs7TSGujYRELEANyDvS8Fj7+S7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^6.1.23" + } + }, "node_modules/filelist": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.6.tgz", @@ -2192,6 +2812,42 @@ "minimatch": "^5.0.1" } }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "6.1.23", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.23.tgz", + "integrity": "sha512-f++BY9pTk+983xK1FLzlLpmM0i0z+jHmx3QESGkURMXujQZz1k5wzwX6hjnQ8goaD0B+sYnDK1yZ6MTyZfUaqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cacheable": "^2.5.0", + "flatted": "^3.4.2", + "hookified": "^1.15.0" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, "node_modules/form-data": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", @@ -2328,6 +2984,19 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/glob/node_modules/brace-expansion": { "version": "1.1.18", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", @@ -2371,6 +3040,19 @@ "node": ">=10.0" } }, + "node_modules/globals": { + "version": "17.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.12.0.tgz", + "integrity": "sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globalthis": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", @@ -2488,6 +3170,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hashery": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.1.tgz", + "integrity": "sha512-iZyKG96/JwPz1N55vj2Ie2vXbhu440zfUfJvSwEqEbeLluk7NnapfGqa7LH0mOsnDxTF85Mx8/dyR6HfqcbmbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.15.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/hasown": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", @@ -2501,6 +3196,13 @@ "node": ">= 0.4" } }, + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "dev": true, + "license": "MIT" + }, "node_modules/hosted-git-info": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", @@ -2563,6 +3265,26 @@ "node": ">= 14" } }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -2582,6 +3304,16 @@ "dev": true, "license": "ISC" }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -2592,6 +3324,19 @@ "node": ">=8" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -2696,6 +3441,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -2747,6 +3499,36 @@ "dev": true, "license": "MIT" }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/lodash": { "version": "4.18.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", @@ -2981,6 +3763,13 @@ "dev": true, "license": "MIT" }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, "node_modules/node-abi": { "version": "4.35.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.35.0.tgz", @@ -3132,6 +3921,24 @@ "wrappy": "1" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/p-cancelable": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", @@ -3158,6 +3965,32 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -3289,6 +4122,16 @@ "node": "^12.20.0 || >=14" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/proc-log": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-6.1.0.tgz", @@ -3353,6 +4196,16 @@ "once": "^1.3.1" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/pvtsutils": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", @@ -3373,6 +4226,26 @@ "node": ">=16.0.0" } }, + "node_modules/qified": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.10.1.tgz", + "integrity": "sha512-+Owyggi9IxT1ePKGafcI87ubSmxol6smwJ+RAHDQlx9+9cPwFWDiKFFCPuWhr9ignlGpZ9vDQLw67N4dcTVFEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^2.1.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/qified/node_modules/hookified": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-2.2.0.tgz", + "integrity": "sha512-p/LgFzRN5FeoD3DLS6bkUapeye6E4SI6yJs6KetENd18S+FBthqYq2amJUWpt5z0EQwwHemidjY5OqJGEKm5uA==", + "dev": true, + "license": "MIT" + }, "node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -3854,6 +4727,19 @@ "dev": true, "license": "0BSD" }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-fest": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", @@ -3925,6 +4811,16 @@ "node": ">=14.14" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/utf8-byte-length": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", @@ -4041,6 +4937,16 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/package.json b/package.json index 6da74ee..ef9740b 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "scripts": { "start": "electron .", "dist:win": "electron-builder --win nsis", - "test": "node --test" + "test": "node --test", + "lint": "eslint ." }, "keywords": [ "electron", @@ -27,8 +28,11 @@ "vscode-ws-jsonrpc": "^3.5.0" }, "devDependencies": { + "@eslint/js": "^10.0.1", "electron": "^44.3.0", - "electron-builder": "^26.15.3" + "electron-builder": "^26.15.3", + "eslint": "^10.10.0", + "globals": "^17.12.0" }, "overrides": { "dompurify": "^3.4.13" diff --git a/patchgen.js b/patchgen.js new file mode 100644 index 0000000..1d715bd --- /dev/null +++ b/patchgen.js @@ -0,0 +1,55 @@ +const fs = require("fs"); +const path = require("path"); + +function generateUnifiedDiff(oldFile, newFile) { + const oldText = fs.readFileSync(oldFile, "utf8").split("\n"); + const newText = fs.readFileSync(newFile, "utf8").split("\n"); + + let patch = ""; + patch += `--- ${path.basename(oldFile)}\n`; + patch += `+++ ${path.basename(newFile)}\n`; + + let i = 0; + let j = 0; + + while (i < oldText.length || j < newText.length) { + const oldLine = oldText[i]; + const newLine = newText[j]; + + if (oldLine === newLine) { + i++; + j++; + continue; + } + + // Both lines exist but differ + if (oldLine !== undefined && newLine !== undefined) { + patch += `@@ -${i + 1} +${j + 1} @@\n`; + patch += `-${oldLine}\n`; + patch += `+${newLine}\n`; + i++; + j++; + continue; + } + + // Old line removed + if (oldLine !== undefined) { + patch += `@@ -${i + 1},1 +${j},0 @@\n`; + patch += `-${oldLine}\n`; + i++; + continue; + } + + // New line added + if (newLine !== undefined) { + patch += `@@ -${i},0 +${j + 1},1 @@\n`; + patch += `+${newLine}\n`; + j++; + continue; + } + } + + return patch; +} + +module.exports = { generateUnifiedDiff }; diff --git a/patchgen.test.js b/patchgen.test.js new file mode 100644 index 0000000..c177c0b --- /dev/null +++ b/patchgen.test.js @@ -0,0 +1,36 @@ +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const os = require('node:os'); +const path = require('node:path'); +const test = require('node:test'); +const { generateUnifiedDiff } = require('./patchgen'); + +function withFiles(oldContent, newContent, assertion) { + const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'satiscode-patchgen-')); + const oldFile = path.join(directory, 'old.txt'); + const newFile = path.join(directory, 'new.txt'); + + try { + fs.writeFileSync(oldFile, oldContent); + fs.writeFileSync(newFile, newContent); + assertion(generateUnifiedDiff(oldFile, newFile)); + } finally { + fs.rmSync(directory, { recursive: true, force: true }); + } +} + +test('exports generateUnifiedDiff through CommonJS', () => { + assert.equal(typeof generateUnifiedDiff, 'function'); +}); + +test('uses an explicit zero-length new range for a deletion', () => { + withFiles('first\nremoved', 'first', (patch) => { + assert.match(patch, /@@ -2,1 \+1,0 @@\n-removed\n/); + }); +}); + +test('uses an explicit zero-length old range for an addition', () => { + withFiles('first', 'first\nadded', (patch) => { + assert.match(patch, /@@ -1,0 \+2,1 @@\n\+added\n/); + }); +}); diff --git a/preload.js b/preload.js index 41a6e5e..f3ac7e2 100644 --- a/preload.js +++ b/preload.js @@ -1,5 +1,16 @@ const { contextBridge, ipcRenderer } = require('electron'); const { indexer } = require('./UI/codebase_indexer/cb_index'); +const { GitUi } = require('./UI/git_sidebar/gitui'); + +let gitUi = null; +const gitApi = { run: (operation, workspace, args = []) => ipcRenderer.invoke('git:run', { operation, workspace, args }) }; + +globalThis.addEventListener('DOMContentLoaded', () => { + const container = globalThis.document.getElementById('git-sidebar-mount'); + gitUi = new GitUi(container, { gitApi }); + gitUi.render(); + gitUi.initListeners(); +}); contextBridge.exposeInMainWorld('api', { openFile: () => ipcRenderer.invoke('dialog:openFile'), @@ -8,6 +19,13 @@ contextBridge.exposeInMainWorld('api', { saveFile: (data) => ipcRenderer.invoke('file:save', data), listDirectory: (directoryPath) => ipcRenderer.invoke('directory:list', directoryPath), openFolder: () => ipcRenderer.invoke('dialog:openFolder'), + setGitWorkspace: (workspace) => gitUi?.setWorkspace(workspace), + gitStatus: (workspace) => gitApi.run('status', workspace), + gitInit: (workspace) => gitApi.run('init', workspace), + gitAddAll: (workspace) => gitApi.run('addAll', workspace), + gitCommit: (workspace, message) => gitApi.run('commit', workspace, [message]), + gitPull: (workspace) => gitApi.run('pull', workspace), + gitPush: (workspace) => gitApi.run('push', workspace), updateCodebaseIndex: (documentId, text) => indexer.updateActiveDocument(documentId, text), queryCodebaseGhostText: (prefix) => indexer.queryGhostText(prefix), @@ -29,6 +47,5 @@ contextBridge.exposeInMainWorld('api', { onPyrightMessage: (callback) => ipcRenderer.on('pyright:message', (_event, message) => callback(message)), onPyrightStderr: (callback) => ipcRenderer.on('pyright:stderr', (_event, message) => callback(message)), onPyrightError: (callback) => ipcRenderer.on('pyright:error', (_event, message) => callback(message)), - onPyrightExit: (callback) => ipcRenderer.on('pyright:exit', (_event) => callback()) + onPyrightExit: (callback) => ipcRenderer.on('pyright:exit', () => callback()) }); - diff --git a/styles/style1.css b/styles/style1.css index a0977cf..0e1b697 100644 --- a/styles/style1.css +++ b/styles/style1.css @@ -195,6 +195,20 @@ body { padding-left: 0; } +#git-sidebar { + width: min(260px, 28vw); + min-width: 190px; + padding: 10px; + background-color: var(--vscode-sidebar-bg); + border-right: 1px solid var(--vscode-border); + overflow-y: auto; +} + +#git-sidebar input, +#git-sidebar button { + width: 100%; +} + .tree-entry { display: flex; align-items: center; @@ -578,3 +592,36 @@ body { outline: 2px solid var(--vscode-focus-border); outline-offset: -2px; } + +.git-panel { + display: flex; + flex-direction: column; + gap: 8px; + min-width: 0; +} + +.git-title { margin: 0; font-size: 12px; text-transform: uppercase; letter-spacing: 0.5px; } +.git-message { margin: 0; padding: 7px; border-radius: 3px; color: var(--vscode-text-muted); overflow-wrap: anywhere; } +.git-message--success { color: #89d185; } +.git-message--error { color: #f48771; } +.git-message--loading { color: var(--vscode-text-main); } +.git-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 6px; } +.git-panel button, .git-commit-input { box-sizing: border-box; min-height: 30px; border: 1px solid var(--vscode-border); border-radius: 4px; padding: 5px 8px; font: 12px var(--font-ui); } +.git-panel button { background-color: var(--vscode-toolbar-bg); color: var(--vscode-text-main); cursor: pointer; } +.git-panel button:hover:not(:disabled) { background-color: var(--vscode-hover-item); } +.git-panel button:disabled { cursor: default; opacity: 0.5; } +.git-panel button:focus-visible, .git-commit-input:focus-visible { outline: 2px solid var(--vscode-focus-border); outline-offset: 1px; } +.git-commit-input { + background: var(--vscode-editor-bg); + color: var(--vscode-text-main); +} +.git-status-list { margin: 4px 0 0; padding: 0; list-style: none; overflow-y: auto; } +.git-status-entry { display: flex; gap: 8px; padding: 5px 2px; min-width: 0; } +.git-status-code { flex: 0 0 22px; color: #cca700; font-family: monospace; } +.git-status-file { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.git-clean { color: var(--vscode-text-muted); padding: 6px 2px; } + +@media (max-width: 760px) { + #git-sidebar { width: 190px; min-width: 160px; padding: 7px; } + .git-actions { grid-template-columns: 1fr; } +}