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 @@
@@ -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; }
+}