From e08a80a319d1f298d6f2f2a95e5873ae8f6c0527 Mon Sep 17 00:00:00 2001 From: Riley Smith Date: Wed, 11 Mar 2026 15:11:44 -0700 Subject: [PATCH 01/20] feat: strip html and properly create test cases --- src/interfaces/Responses.ts | 4 +- src/services/apiService.ts | 2 +- src/services/testingService.ts | 147 +++++++++++++++++++++++++-------- src/sidebarProvider.ts | 24 ++++-- 4 files changed, 133 insertions(+), 44 deletions(-) diff --git a/src/interfaces/Responses.ts b/src/interfaces/Responses.ts index 19db2d4..5e7ef1f 100644 --- a/src/interfaces/Responses.ts +++ b/src/interfaces/Responses.ts @@ -17,4 +17,6 @@ export type LoginResponse = ApiResponse<{ token: string; }>; -export type GradableResponse = ApiResponse; \ No newline at end of file +export type GradableResponse = ApiResponse<{ + [key: string]: Gradable; +}>; \ No newline at end of file diff --git a/src/services/apiService.ts b/src/services/apiService.ts index b7f6809..2ce5ede 100644 --- a/src/services/apiService.ts +++ b/src/services/apiService.ts @@ -117,7 +117,7 @@ export class ApiService { Array.isArray(res.data.test_cases) && res.data.test_cases.length > 0; - for (;;) { + for (; ;) { if (token?.isCancellationRequested) { throw new Error('Cancelled'); } diff --git a/src/services/testingService.ts b/src/services/testingService.ts index 03dc494..a2bb026 100644 --- a/src/services/testingService.ts +++ b/src/services/testingService.ts @@ -1,6 +1,6 @@ import * as vscode from 'vscode'; import { ApiService } from './apiService'; -import type { AutoGraderDetailsData, TestCase } from '../interfaces/AutoGraderDetails'; +import type { AutoGraderDetails, AutoGraderDetailsData, TestCase, Autocheck } from '../interfaces/AutoGraderDetails'; const CONTROLLER_ID = 'submittyAutograder'; const CONTROLLER_LABEL = 'Submitty Autograder'; @@ -56,10 +56,78 @@ export class TestingService { return item; } + /** + * Run a single gradeable in the Test Explorer using an already-fetched autograder result. + * Used when the user clicks "Grade" in the sidebar: submit → poll → then report here. + */ + runGradeableWithResult(term: string, courseId: string, gradeableId: string, label: string, result: AutoGraderDetails): void { + const item = this.addGradeable(term, courseId, gradeableId, label); + this.syncTestCaseChildren(item, result.data); + + const run = this.controller.createTestRun(new vscode.TestRunRequest([item])); + run.started(item); + run.appendOutput(`Autograder completed for ${item.label}.\r\n`); + this.reportGradeableResult(run, item, result.data); + run.end(); + } + private getGradeableMeta(item: vscode.TestItem): GradeableMeta | undefined { return this.gradeableMeta.get(item); } + /** + * Convert HTML from autograder actual/expected into plain text for the Test Explorer diff. + * Strips tags and decodes common entities so the diff view is readable. + */ + private stripHtml(html: string): string { + if (!html || typeof html !== 'string') { + return ''; + } + const text = html + .replace(//gi, '\n') + .replace(/<\/div>/gi, '\n') + .replace(/<\/p>/gi, '\n') + .replace(/<[^>]+>/g, '') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/ /g, ' '); + return text.replace(/\n{3,}/g, '\n\n').trim(); + } + + private formatAutocheckOutput(autochecks: Autocheck[] | undefined, getValue: (ac: Autocheck) => string): string { + if (!autochecks?.length) { + return ''; + } + const parts = autochecks.map((ac) => { + const value = this.stripHtml(getValue(ac)); + if (!value) { + return ''; + } + return `[${ac.description}]\n${value}`; + }); + return parts.filter(Boolean).join('\n\n'); + } + + /** + * Format the messages array from all autochecks (e.g. "ERROR: ..." with type failure/warning). + */ + private formatAutocheckMessages(autochecks: Autocheck[] | undefined): string { + if (!autochecks?.length) { + return ''; + } + const parts = autochecks.map((ac) => { + const msgLines = (ac.messages ?? []).map((m) => ` • ${m.message}${m.type ? ` (${m.type})` : ''}`); + if (msgLines.length === 0) { + return ''; + } + return `[${ac.description}]\n${msgLines.join('\n')}`; + }); + return parts.filter(Boolean).join('\n\n'); + } + private async resolveHandler(item: vscode.TestItem | undefined): Promise { if (!item) { return; @@ -98,6 +166,49 @@ export class TestingService { } } + private reportGradeableResult(run: vscode.TestRun, item: vscode.TestItem, _data: AutoGraderDetailsData): void { + const start = Date.now(); + let allPassed = true; + item.children.forEach((child) => { + const tc = this.testCaseMeta.get(child); + run.started(child); + if (tc) { + const passed = tc.points_received >= (tc.points_available ?? 0); + if (!passed) { + allPassed = false; + } + const duration = Date.now() - start; + const messageParts = [tc.testcase_message, tc.details].filter(Boolean); + const formattedMessages = this.formatAutocheckMessages(tc.autochecks); + if (formattedMessages) { + messageParts.push('--- Messages ---', formattedMessages); + } + const messageText = messageParts.join('\n') || 'Failed'; + if (passed) { + run.passed(child, duration); + } else { + const msg = new vscode.TestMessage(messageText); + msg.expectedOutput = this.formatAutocheckOutput(tc.autochecks, (ac) => ac.expected); + msg.actualOutput = this.formatAutocheckOutput(tc.autochecks, (ac) => ac.actual); + run.failed(child, msg, duration); + } + } else { + run.passed(child, 0); + } + }); + + if (item.children.size === 0) { + run.appendOutput(`No test cases in response.\r\n`); + run.failed(item, new vscode.TestMessage('No test cases returned.'), 0); + } else { + if (allPassed) { + run.passed(item, Date.now() - start); + } else { + run.failed(item, new vscode.TestMessage('Some test cases failed.'), Date.now() - start); + } + } + } + private async runHandler(request: vscode.TestRunRequest, token: vscode.CancellationToken): Promise { const run = this.controller.createTestRun(request); const queue: vscode.TestItem[] = []; @@ -137,39 +248,7 @@ export class TestingService { ); const data = result.data; this.syncTestCaseChildren(item, data); - - let allPassed = true; - const start = Date.now(); - item.children.forEach((child) => { - const tc = this.testCaseMeta.get(child); - run.started(child); - if (tc) { - const passed = tc.points_received >= (tc.points_available ?? 0); - if (!passed) { - allPassed = false; - } - const duration = Date.now() - start; - const message = [tc.testcase_message, tc.details].filter(Boolean).join('\n') || undefined; - if (passed) { - run.passed(child, duration); - } else { - run.failed(child, new vscode.TestMessage(message || 'Failed'), duration); - } - } else { - run.passed(child, 0); - } - }); - - if (item.children.size === 0) { - run.appendOutput(`No test cases in response.\r\n`); - run.failed(item, new vscode.TestMessage('No test cases returned.'), 0); - } else { - if (allPassed) { - run.passed(item, Date.now() - start); - } else { - run.failed(item, new vscode.TestMessage('Some test cases failed.'), Date.now() - start); - } - } + this.reportGradeableResult(run, item, data); } catch (e) { const err = e instanceof Error ? e.message : String(e); run.appendOutput(`Error: ${err}\r\n`); diff --git a/src/sidebarProvider.ts b/src/sidebarProvider.ts index a0c2861..9393e21 100644 --- a/src/sidebarProvider.ts +++ b/src/sidebarProvider.ts @@ -3,6 +3,7 @@ import { getClassesHtml } from './sidebarContent'; import { ApiService } from './services/apiService'; import { AuthService } from './services/authService'; import type { TestingService } from './services/testingService'; +import { Gradable } from './interfaces/Gradables'; export class SidebarProvider implements vscode.WebviewViewProvider { private _view?: vscode.WebviewView; @@ -109,7 +110,7 @@ export class SidebarProvider implements vscode.WebviewViewProvider { let gradables: { id: string; title: string }[] = []; try { const gradableResponse = await this.apiService.fetchGradables(course.title, course.semester); - gradables = (gradableResponse.data || []).map((g) => ({ id: g.id, title: g.title || g.id })); + gradables = Object.values(gradableResponse.data || {}).map((g: Gradable) => ({ id: g.id, title: g.title || g.id })); } catch (e) { console.warn(`Failed to fetch gradables for ${course.title}:`, e); } @@ -135,8 +136,14 @@ export class SidebarProvider implements vscode.WebviewViewProvider { private async handleGrade(term: string, courseId: string, gradeableId: string, view: vscode.WebviewView): Promise { try { this.testingService?.addGradeable(term, courseId, gradeableId, gradeableId); - const gradeDetails = await this.apiService.fetchGradeDetails(term, courseId, gradeableId); - const previousAttempts = await this.apiService.fetchPreviousAttempts(term, courseId, gradeableId); // Fetch previous attempts + + view.webview.postMessage({ command: 'gradeStarted', message: 'Submitting for grading...' }); + await this.apiService.submitVCSGradable(term, courseId, gradeableId); + + view.webview.postMessage({ command: 'gradeStarted', message: 'Grading in progress. Polling for results...' }); + const gradeDetails = await this.apiService.pollGradeDetailsUntilComplete(term, courseId, gradeableId); + + const previousAttempts = await this.apiService.fetchPreviousAttempts(term, courseId, gradeableId); view.webview.postMessage({ command: 'displayGrade', @@ -145,21 +152,22 @@ export class SidebarProvider implements vscode.WebviewViewProvider { courseId, gradeableId, gradeDetails, - previousAttempts, // Include previous attempts + previousAttempts, } }); - // Send message to PanelProvider vscode.commands.executeCommand('extension.showGradePanel', { term, courseId, gradeableId, gradeDetails, - previousAttempts, // Include previous attempts + previousAttempts, }); + + this.testingService?.runGradeableWithResult(term, courseId, gradeableId, gradeableId, gradeDetails); } catch (error: any) { - vscode.window.showErrorMessage(`Failed to fetch grade details: ${error.message}`); - view.webview.postMessage({ command: 'error', message: `Failed to fetch grade details: ${error.message}` }); + vscode.window.showErrorMessage(`Failed to grade: ${error.message}`); + view.webview.postMessage({ command: 'error', message: `Failed to grade: ${error.message}` }); } } From 8ccb21f08584953e29cf92c7f999c444e38d3f06 Mon Sep 17 00:00:00 2001 From: Riley Smith Date: Wed, 11 Mar 2026 16:16:19 -0700 Subject: [PATCH 02/20] feat: first pass at autograding flow --- src/extension.ts | 4 +++- src/sidebarProvider.ts | 19 ++++++++++++++++--- src/typings/message.ts | 11 +++++++++++ 3 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 src/typings/message.ts diff --git a/src/extension.ts b/src/extension.ts index 09c7bfb..95b26d8 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2,11 +2,13 @@ import * as vscode from 'vscode'; import { SidebarProvider } from './sidebarProvider'; import { ApiService } from './services/apiService'; import { TestingService } from './services/testingService'; +import { GitService } from './services/gitService'; export function activate(context: vscode.ExtensionContext): void { const apiService = ApiService.getInstance(context, ''); const testingService = new TestingService(context, apiService); - const sidebarProvider = new SidebarProvider(context, testingService); + const gitService = new GitService(); + const sidebarProvider = new SidebarProvider(context, testingService, gitService); context.subscriptions.push( vscode.window.registerWebviewViewProvider('submittyWebview', sidebarProvider) diff --git a/src/sidebarProvider.ts b/src/sidebarProvider.ts index 9393e21..b00f564 100644 --- a/src/sidebarProvider.ts +++ b/src/sidebarProvider.ts @@ -2,6 +2,7 @@ import * as vscode from 'vscode'; import { getClassesHtml } from './sidebarContent'; import { ApiService } from './services/apiService'; import { AuthService } from './services/authService'; +import { GitService } from './services/gitService'; import type { TestingService } from './services/testingService'; import { Gradable } from './interfaces/Gradables'; @@ -13,7 +14,8 @@ export class SidebarProvider implements vscode.WebviewViewProvider { constructor( private readonly context: vscode.ExtensionContext, - private readonly testingService?: TestingService + private readonly testingService?: TestingService, + private readonly gitService?: GitService ) { this.apiService = ApiService.getInstance(this.context, ""); this.authService = AuthService.getInstance(this.context); @@ -23,7 +25,7 @@ export class SidebarProvider implements vscode.WebviewViewProvider { webviewView: vscode.WebviewView, _context: vscode.WebviewViewResolveContext, _token: vscode.CancellationToken - ) { + ): Promise { this._view = webviewView; webviewView.webview.options = { @@ -83,7 +85,7 @@ export class SidebarProvider implements vscode.WebviewViewProvider { } } - private async handleMessage(message: any, view: vscode.WebviewView) { + private async handleMessage(message: any, view: vscode.WebviewView): Promise { switch (message.command) { case 'fetchAndDisplayCourses': const token = await this.authService.getAuthorizationToken(); @@ -137,6 +139,17 @@ export class SidebarProvider implements vscode.WebviewViewProvider { try { this.testingService?.addGradeable(term, courseId, gradeableId, gradeableId); + if (this.gitService) { + view.webview.postMessage({ command: 'gradeStarted', message: 'Staging and committing...' }); + const commitMessage = new Date().toLocaleString(undefined, { + dateStyle: 'short', + timeStyle: 'medium', + }); + await this.gitService.commit(commitMessage, { all: true }); + view.webview.postMessage({ command: 'gradeStarted', message: 'Pushing...' }); + await this.gitService.push(); + } + view.webview.postMessage({ command: 'gradeStarted', message: 'Submitting for grading...' }); await this.apiService.submitVCSGradable(term, courseId, gradeableId); diff --git a/src/typings/message.ts b/src/typings/message.ts new file mode 100644 index 0000000..6e743b7 --- /dev/null +++ b/src/typings/message.ts @@ -0,0 +1,11 @@ +export const MessageCommand = { + FETCH_AND_DISPLAY_COURSES: 'fetchAndDisplayCourses', + GRADE: 'grade', + GRADE_STARTED: 'gradeStarted', + GRADE_COMPLETED: 'gradeCompleted', + GRADE_ERROR: 'gradeError', + GRADE_CANCELLED: 'gradeCancelled', + GRADE_PAUSED: 'gradePaused', + GRASE_RESUMED: 'gradeResumed', + GRADE_ABORTED: 'gradeAborted', +} as const; \ No newline at end of file From bd686da35245492ec64b05c739bdceba66c6bf29 Mon Sep 17 00:00:00 2001 From: Riley Smith Date: Thu, 19 Mar 2026 23:20:03 -0700 Subject: [PATCH 03/20] first pass at preloading gradeables when loading extension --- src/extension.ts | 27 ++ src/services/apiService.ts | 54 ++-- src/services/authService.ts | 43 ++- src/services/courseRepoResolver.ts | 163 ++++++++++ src/services/gitService.ts | 29 +- src/sidebar/classes.html | 8 +- src/sidebarProvider.ts | 145 +++++++-- src/typings/message.ts | 9 +- src/typings/vscode-git.d.ts | 498 ++++++++++++++++++++++++++++- 9 files changed, 891 insertions(+), 85 deletions(-) create mode 100644 src/services/courseRepoResolver.ts diff --git a/src/extension.ts b/src/extension.ts index 95b26d8..30d2371 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -3,17 +3,44 @@ import { SidebarProvider } from './sidebarProvider'; import { ApiService } from './services/apiService'; import { TestingService } from './services/testingService'; import { GitService } from './services/gitService'; +import { AuthService } from './services/authService'; +import { CourseRepoResolver } from './services/courseRepoResolver'; +import type { Gradable } from './interfaces/Gradables'; export function activate(context: vscode.ExtensionContext): void { const apiService = ApiService.getInstance(context, ''); const testingService = new TestingService(context, apiService); const gitService = new GitService(); + const authService = AuthService.getInstance(context); const sidebarProvider = new SidebarProvider(context, testingService, gitService); context.subscriptions.push( vscode.window.registerWebviewViewProvider('submittyWebview', sidebarProvider) ); + // Preload gradables into the Test Explorer when the workspace appears + // to be a course-tied repo. + void (async () => { + try { + await authService.initialize(); + const resolver = new CourseRepoResolver(apiService, authService, gitService); + const courseContext = await resolver.resolveCourseContextFromRepo(); + if (!courseContext) { + return; + } + + const gradablesResponse = await apiService.fetchGradables(courseContext.courseId, courseContext.term); + const gradables = Object.values(gradablesResponse.data); + + for (const g of gradables) { + testingService.addGradeable(courseContext.term, courseContext.courseId, g.id, g.title || g.id); + } + } catch (e) { + const err = e instanceof Error ? e.message : String(e); + console.warn(`Failed to preload gradables: ${err}`); + } + })(); + } export function deactivate() { } \ No newline at end of file diff --git a/src/services/apiService.ts b/src/services/apiService.ts index 2ce5ede..d35964e 100644 --- a/src/services/apiService.ts +++ b/src/services/apiService.ts @@ -2,10 +2,22 @@ import * as vscode from 'vscode'; import { ApiClient } from './apiClient'; - import { CourseResponse, LoginResponse, GradableResponse } from '../interfaces/Responses'; import { AutoGraderDetails } from '../interfaces/AutoGraderDetails'; +function getErrorMessage(error: unknown, fallback: string): string { + if (error instanceof Error) { + return error.message || fallback; + } + if (typeof error === 'object' && error) { + const maybeAxiosError = error as { response?: { data?: { message?: unknown } } }; + const msg = maybeAxiosError.response?.data?.message; + if (typeof msg === 'string' && msg.trim()) { + return msg; + } + } + return fallback; +} export class ApiService { private client: ApiClient; @@ -16,12 +28,12 @@ export class ApiService { } // set token for local api client - setAuthorizationToken(token: string) { + setAuthorizationToken(token: string): void { this.client.setToken(token); } // set base URL for local api client - setBaseUrl(baseUrl: string) { + setBaseUrl(baseUrl: string): void { this.client.setBaseURL(baseUrl); } @@ -43,8 +55,8 @@ export class ApiService { const token: string = response.data.data.token; return token; - } catch (error: any) { - throw new Error(error.response?.data?.message || error.message || 'Login failed.'); + } catch (error: unknown) { + throw new Error(getErrorMessage(error, 'Login failed.')); } } @@ -52,8 +64,8 @@ export class ApiService { try { const response = await this.client.get('/api/me'); return response.data; - } catch (error: any) { - throw new Error(error.response?.data?.message || 'Failed to fetch me.'); + } catch (error: unknown) { + throw new Error(getErrorMessage(error, 'Failed to fetch me.')); } } @@ -61,13 +73,13 @@ export class ApiService { /** * Fetch all courses for the authenticated user */ - async fetchCourses(token?: string): Promise { + async fetchCourses(_token?: string): Promise { try { const response = await this.client.get('/api/courses'); return response.data; - } catch (error: any) { + } catch (error: unknown) { console.error('Error fetching courses:', error); - throw new Error(error.response?.data?.message || 'Failed to fetch courses.'); + throw new Error(getErrorMessage(error, 'Failed to fetch courses.')); } } @@ -76,9 +88,9 @@ export class ApiService { const url = `/api/${term}/${courseId}/gradeables`; const response = await this.client.get(url); return response.data; - } catch (error: any) { + } catch (error: unknown) { console.error('Error fetching gradables:', error); - throw new Error(error.response?.data?.message || 'Failed to fetch gradables.'); + throw new Error(getErrorMessage(error, 'Failed to fetch gradables.')); } } @@ -89,9 +101,9 @@ export class ApiService { try { const response = await this.client.get(`/api/${term}/${courseId}/gradeable/${gradeableId}/values`); return response.data; - } catch (error: any) { + } catch (error: unknown) { console.error('Error fetching grade details:', error); - throw new Error(error.response?.data?.message || 'Failed to fetch grade details.'); + throw new Error(getErrorMessage(error, 'Failed to fetch grade details.')); } } @@ -140,9 +152,9 @@ export class ApiService { const url = `/api/${term}/${courseId}/gradeable/${gradeableId}/upload?vcs_upload=true&git_repo_id=true`; const response = await this.client.post(url); return response.data; - } catch (error: any) { - console.error('Error submitting VCS gradable:', error); - throw new Error(error.response?.data?.message || 'Failed to submit VCS gradable.'); + } catch (error: unknown) { + console.error('Error submitt`ing VCS gradable:', error); + throw new Error(getErrorMessage(error, 'Failed to submit VCS gradable.')); } } @@ -150,14 +162,14 @@ export class ApiService { /** * Fetch previous attempts for a specific homework assignment */ - async fetchPreviousAttempts(term: string, courseId: string, gradeableId: string): Promise { + async fetchPreviousAttempts(term: string, courseId: string, gradeableId: string): Promise { try { const url = `/api/${term}/${courseId}/gradeable/${gradeableId}/attempts`; - const response = await this.client.get(url); + const response = await this.client.get(url); return response.data; - } catch (error: any) { + } catch (error: unknown) { console.error('Error fetching previous attempts:', error); - throw new Error(error.response?.data?.message || 'Failed to fetch previous attempts.'); + throw new Error(getErrorMessage(error, 'Failed to fetch previous attempts.')); } } diff --git a/src/services/authService.ts b/src/services/authService.ts index bedc58f..afbd445 100644 --- a/src/services/authService.ts +++ b/src/services/authService.ts @@ -13,7 +13,7 @@ export class AuthService { this.apiService = ApiService.getInstance(context, ""); } - async initialize() { + async initialize(): Promise { console.log("Initializing AuthService"); // Get base URL from configuration @@ -31,6 +31,36 @@ export class AuthService { // Token exists, set it on the API service this.apiService.setAuthorizationToken(token); console.log("Token set on API service"); + + // If baseUrl isn't configured yet, fetch it now so API calls work. + if (!baseUrl) { + const inputUrl = await vscode.window.showInputBox({ + prompt: 'Enter Submitty API URL', + placeHolder: 'https://example.submitty.edu', + ignoreFocusOut: true, + validateInput: (value) => { + if (!value || value.trim().length === 0) { + return 'URL is required'; + } + try { + new URL(value); + return null; + } catch { + return 'Please enter a valid URL'; + } + }, + }); + + if (!inputUrl) { + return; + } + + baseUrl = inputUrl.trim(); + + await config.update('baseUrl', baseUrl, vscode.ConfigurationTarget.Global); + this.apiService.setBaseUrl(baseUrl); + } + return; } @@ -110,19 +140,20 @@ export class AuthService { await this.login(userId.trim(), password); vscode.window.showInformationMessage('Successfully logged in to Submitty'); - } catch (error: any) { - vscode.window.showErrorMessage(`Login failed: ${error.message}`); + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); + vscode.window.showErrorMessage(`Login failed: ${err}`); throw error; } } // store token - private async storeToken(token: string) { + private async storeToken(token: string): Promise { await keytar.setPassword('submittyToken', 'submittyToken', token); } // get token - private async getToken() { + private async getToken(): Promise { return await keytar.getPassword('submittyToken', 'submittyToken'); } @@ -135,7 +166,7 @@ export class AuthService { const token = await this.apiService.login(userId, password); this.apiService.setAuthorizationToken(token); // store token in system keychain - this.storeToken(token); + await this.storeToken(token); return token; } diff --git a/src/services/courseRepoResolver.ts b/src/services/courseRepoResolver.ts new file mode 100644 index 0000000..fae5ada --- /dev/null +++ b/src/services/courseRepoResolver.ts @@ -0,0 +1,163 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; +import { ApiService } from './apiService'; +import { AuthService } from './authService'; +import { GitService } from './gitService'; +import type { Course } from '../interfaces/Courses'; + +export interface CourseRepoContext { + term: string; + courseId: string; +} + +function normalizeForMatch(input: string): string { + return input + .toLowerCase() + // Keep only alphanumerics so variants like "Fall 2024" vs "fall2024" match. + .replace(/[^a-z0-9]/g, ''); +} + +function readTextFileSafe(filePath: string): string | null { + try { + return fs.readFileSync(filePath, 'utf8'); + } catch { + return null; + } +} + +function getGitDirPath(repoRootPath: string): string | null { + const gitEntryPath = path.join(repoRootPath, '.git'); + if (!fs.existsSync(gitEntryPath)) { + return null; + } + + try { + const stat = fs.statSync(gitEntryPath); + if (stat.isDirectory()) { + return gitEntryPath; + } + + if (stat.isFile()) { + // Worktrees/linked clones can have a .git file like: "gitdir: /abs/path/to/.git/worktrees/..." + const gitFileContents = readTextFileSafe(gitEntryPath); + if (!gitFileContents) { + return null; + } + + const match = gitFileContents.match(/^\s*gitdir:\s*(.+)\s*$/m); + if (!match?.[1]) { + return null; + } + + const gitdirRaw = match[1].trim(); + return path.isAbsolute(gitdirRaw) ? gitdirRaw : path.resolve(repoRootPath, gitdirRaw); + } + } catch { + return null; + } + + return null; +} + +function extractGitRemoteUrlsFromConfig(gitConfigText: string): string[] { + const urls: string[] = []; + + // Example: + // [remote "origin"] + // url = https://example/.../term/courseId/... + const urlRegex = /^\s*url\s*=\s*(.+)\s*$/gim; + let match: RegExpExecArray | null = null; + // eslint-disable-next-line no-cond-assign + while ((match = urlRegex.exec(gitConfigText))) { + const rawUrl = match[1]?.trim(); + if (rawUrl) { + urls.push(rawUrl); + } + } + + return urls; +} + +export class CourseRepoResolver { + constructor( + private readonly apiService: ApiService, + private readonly authService: AuthService, + private readonly gitService: GitService + ) {} + + async resolveCourseContextFromRepo(): Promise { + const repo = this.gitService.getRepository(); + if (!repo) { + return null; + } + + const repoRootPath = repo.rootUri.fsPath; + const gitDirPath = getGitDirPath(repoRootPath); + if (!gitDirPath) { + return null; + } + + const gitConfigText = readTextFileSafe(path.join(gitDirPath, 'config')); + if (!gitConfigText) { + return null; + } + + const remoteUrls = extractGitRemoteUrlsFromConfig(gitConfigText); + if (remoteUrls.length === 0) { + return null; + } + + const token = await this.authService.getAuthorizationToken(); + if (!token) { + // No auth token -> can't map remotes to courses via API. + return null; + } + + const baseUrl = vscode.workspace.getConfiguration('submitty').get('baseUrl', ''); + if (!baseUrl) { + // Without baseUrl, we can't call the API. + return null; + } + + this.apiService.setBaseUrl(baseUrl); + this.apiService.setAuthorizationToken(token); + + // Fetch courses and match based on whether their (term, courseId) strings appear in remote URLs. + const coursesResponse = await this.apiService.fetchCourses(token); + const courses = coursesResponse.data.unarchived_courses; + + const remoteText = remoteUrls.join(' '); + const remoteNorm = normalizeForMatch(remoteText); + + let best: { course: Course; score: number } | null = null; + + for (const course of courses) { + const courseIdNorm = normalizeForMatch(course.title); + const termNorm = normalizeForMatch(course.semester); + + let score = 0; + + if (remoteNorm.includes(courseIdNorm)) { + score += 6; + } + if (remoteNorm.includes(termNorm)) { + score += 3; + } + if (remoteText.toLowerCase().includes(course.display_name.toLowerCase())) { + score += 1; + } + + if (!best || score > best.score) { + best = { course, score }; + } + } + + if (!best || best.score < 6) { + return null; + } + + return { term: best.course.semester, courseId: best.course.title }; + } +} + diff --git a/src/services/gitService.ts b/src/services/gitService.ts index ed1db65..4e4c889 100644 --- a/src/services/gitService.ts +++ b/src/services/gitService.ts @@ -1,14 +1,20 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-redundant-type-constituents */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ import * as vscode from 'vscode'; -import type { GitApi, GitExtension, Repository, CommitOptions, ForcePushMode } from '../typings/vscode-git'; +import type { GitExtension, Repository, CommitOptions, ForcePushMode } from '../typings/vscode-git'; +import { API } from '../typings/vscode-git'; /** * Service that delegates to the built-in vscode.git extension for * push, pull, and commit in the current workspace repository. */ export class GitService { - private gitApi: GitApi | null = null; + private gitApi: API | null = null; - private getApi(): GitApi | null { + private getApi(): API | null { if (this.gitApi !== null) { return this.gitApi; } @@ -33,13 +39,13 @@ export class GitService { return null; } if (uri) { - return api.getRepository(uri) ?? null; + return api.getRepository(uri); } const folder = vscode.workspace.workspaceFolders?.[0]; if (!folder) { return api.repositories.length > 0 ? api.repositories[0] : null; } - return api.getRepository(folder.uri) ?? api.repositories[0] ?? null; + return api.getRepository(folder.uri) ?? api.repositories[0]; } /** @@ -50,7 +56,20 @@ export class GitService { if (!repo) { throw new Error('No Git repository found. Open a workspace folder that is a Git repo.'); } + + // check to see if there are any changes to commit + const status = (await repo.status()) as unknown as { + modified: unknown[]; + untracked: unknown[]; + deleted: unknown[]; + }; + + if (status.modified.length === 0 && status.untracked.length === 0 && status.deleted.length === 0) { + throw new Error('No changes to commit.'); + } await repo.commit(message, options); + + } /** diff --git a/src/sidebar/classes.html b/src/sidebar/classes.html index 553b5e4..23bb2c0 100644 --- a/src/sidebar/classes.html +++ b/src/sidebar/classes.html @@ -143,9 +143,11 @@

Courses

e.stopPropagation(); vscode.postMessage({ command: 'grade', - term: this.dataset.term, - courseId: this.dataset.courseId, - gradeableId: this.dataset.gradeableId, + data: { + term: this.dataset.term, + courseId: this.dataset.courseId, + gradeableId: this.dataset.gradeableId, + }, }); }); }); diff --git a/src/sidebarProvider.ts b/src/sidebarProvider.ts index b00f564..3665694 100644 --- a/src/sidebarProvider.ts +++ b/src/sidebarProvider.ts @@ -5,12 +5,16 @@ import { AuthService } from './services/authService'; import { GitService } from './services/gitService'; import type { TestingService } from './services/testingService'; import { Gradable } from './interfaces/Gradables'; +import { TestingService } from './services/testingService'; +import { MessageCommand } from './typings/message'; export class SidebarProvider implements vscode.WebviewViewProvider { private _view?: vscode.WebviewView; private apiService: ApiService; private authService: AuthService; private isInitialized: boolean = false; + private visibilityDisposable?: vscode.Disposable; + private isLoadingCourses: boolean = false; constructor( private readonly context: vscode.ExtensionContext, @@ -36,6 +40,15 @@ export class SidebarProvider implements vscode.WebviewViewProvider { // Initially show blank screen webviewView.webview.html = this.getBlankHtml(); + // Reload courses any time the view becomes visible again (e.g. user + // closes/hides the panel and comes back). + this.visibilityDisposable?.dispose(); + this.visibilityDisposable = webviewView.onDidChangeVisibility(async () => { + if (webviewView.visible) { + await this.loadCourses(); + } + }); + // Initialize authentication when sidebar is opened (only once) if (!this.isInitialized) { this.isInitialized = true; @@ -68,6 +81,11 @@ export class SidebarProvider implements vscode.WebviewViewProvider { return; } + if (this.isLoadingCourses) { + return; + } + + this.isLoadingCourses = true; try { const token = await this.authService.getAuthorizationToken(); if (!token) { @@ -79,29 +97,75 @@ export class SidebarProvider implements vscode.WebviewViewProvider { // Fetch and display courses await this.fetchAndDisplayCourses(token, this._view); - } catch (error: any) { + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); console.error('Failed to load courses:', error); - vscode.window.showErrorMessage(`Failed to load courses: ${error.message}`); + vscode.window.showErrorMessage(`Failed to load courses: ${err}`); + } finally { + this.isLoadingCourses = false; } } - private async handleMessage(message: any, view: vscode.WebviewView): Promise { - switch (message.command) { - case 'fetchAndDisplayCourses': - const token = await this.authService.getAuthorizationToken(); - if (token) { - await this.fetchAndDisplayCourses(token, view); + private async handleMessage(message: unknown, view: vscode.WebviewView): Promise { + console.log('handleMessage', message); + if (!message || typeof message !== 'object') { + return; + } + const msg = message as { command?: unknown; data?: unknown }; + if (typeof msg.command !== 'string') { + return; + } + + switch (msg.command) { + case MessageCommand.FETCH_AND_DISPLAY_COURSES: + try { + const token = await this.authService.getAuthorizationToken(); + if (token) { + await this.fetchAndDisplayCourses(token, view); + } + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); + console.error('Failed to fetch and display courses:', error); + view.webview.postMessage({ + command: MessageCommand.ERROR, + data: { message: `Failed to fetch and display courses: ${err}` }, + }); } break; - case 'grade': - await this.handleGrade(message.term, message.courseId, message.gradeableId, view); + case MessageCommand.GRADE: + try { + const data = msg.data; + if (!data || typeof data !== 'object') { + throw new Error('Missing grade payload.'); + } + const dataObj = data as Record; + const term = typeof dataObj.term === 'string' ? dataObj.term : null; + const courseId = typeof dataObj.courseId === 'string' ? dataObj.courseId : null; + const gradeableId = typeof dataObj.gradeableId === 'string' ? dataObj.gradeableId : null; + + if (!term || !courseId || !gradeableId) { + throw new Error('Invalid grade payload.'); + } + console.log('handleGrade', term, courseId, gradeableId); + await this.handleGrade(term, courseId, gradeableId, view); + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); + console.error('Failed to grade:', error); + view.webview.postMessage({ + command: MessageCommand.ERROR, + data: { message: `Failed to grade: ${err}` }, + }); + } break; default: - vscode.window.showWarningMessage(`Unknown command: ${message.command}`); + vscode.window.showWarningMessage(`Unknown command: ${msg.command}`); + view.webview.postMessage({ + command: MessageCommand.ERROR, + data: { message: `Unknown command: ${msg.command}` }, + }); break; } } - private async fetchAndDisplayCourses(token: string, view: vscode.WebviewView): Promise { try { const courses = await this.apiService.fetchCourses(token); @@ -126,12 +190,16 @@ export class SidebarProvider implements vscode.WebviewViewProvider { ); view.webview.postMessage({ - command: 'displayCourses', + command: MessageCommand.DISPLAY_COURSES, data: { courses: coursesWithGradables }, }); - } catch (error: any) { - vscode.window.showErrorMessage(`Failed to fetch courses: ${error.message}`); - view.webview.postMessage({ command: 'error', message: `Failed to fetch courses: ${error.message}` }); + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); + vscode.window.showErrorMessage(`Failed to fetch courses: ${err}`); + view.webview.postMessage({ + command: MessageCommand.ERROR, + data: { message: `Failed to fetch courses: ${err}` }, + }); } } @@ -140,26 +208,37 @@ export class SidebarProvider implements vscode.WebviewViewProvider { this.testingService?.addGradeable(term, courseId, gradeableId, gradeableId); if (this.gitService) { - view.webview.postMessage({ command: 'gradeStarted', message: 'Staging and committing...' }); + view.webview.postMessage({ command: MessageCommand.GRADE_STARTED, data: { message: 'Staging and committing...' } }); const commitMessage = new Date().toLocaleString(undefined, { dateStyle: 'short', timeStyle: 'medium', }); - await this.gitService.commit(commitMessage, { all: true }); - view.webview.postMessage({ command: 'gradeStarted', message: 'Pushing...' }); - await this.gitService.push(); + try { + await this.gitService.commit(commitMessage, { all: true }); + view.webview.postMessage({ command: MessageCommand.GRADE_STARTED, data: { message: 'Pushing...' } }); + await this.gitService.push(); + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); + if (err === 'No changes to commit.') { + view.webview.postMessage({ + command: MessageCommand.GRADE_STARTED, + data: { message: 'No changes to commit. Skipping git push.' }, + }); + } else { + throw error; + } + } } - view.webview.postMessage({ command: 'gradeStarted', message: 'Submitting for grading...' }); + view.webview.postMessage({ command: MessageCommand.GRADE_STARTED, data: { message: 'Submitting for grading...' } }); await this.apiService.submitVCSGradable(term, courseId, gradeableId); - view.webview.postMessage({ command: 'gradeStarted', message: 'Grading in progress. Polling for results...' }); + view.webview.postMessage({ command: MessageCommand.GRADE_STARTED, data: { message: 'Grading in progress. Polling for results...' } }); const gradeDetails = await this.apiService.pollGradeDetailsUntilComplete(term, courseId, gradeableId); - const previousAttempts = await this.apiService.fetchPreviousAttempts(term, courseId, gradeableId); view.webview.postMessage({ - command: 'displayGrade', + command: MessageCommand.GRADE_COMPLETED, data: { term, courseId, @@ -169,18 +248,14 @@ export class SidebarProvider implements vscode.WebviewViewProvider { } }); - vscode.commands.executeCommand('extension.showGradePanel', { - term, - courseId, - gradeableId, - gradeDetails, - previousAttempts, - }); - this.testingService?.runGradeableWithResult(term, courseId, gradeableId, gradeableId, gradeDetails); - } catch (error: any) { - vscode.window.showErrorMessage(`Failed to grade: ${error.message}`); - view.webview.postMessage({ command: 'error', message: `Failed to grade: ${error.message}` }); + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); + vscode.window.showErrorMessage(`Failed to grade: ${err}`); + view.webview.postMessage({ + command: MessageCommand.ERROR, + data: { message: `Failed to grade: ${err}` }, + }); } } diff --git a/src/typings/message.ts b/src/typings/message.ts index 6e743b7..7a0a860 100644 --- a/src/typings/message.ts +++ b/src/typings/message.ts @@ -1,5 +1,6 @@ export const MessageCommand = { FETCH_AND_DISPLAY_COURSES: 'fetchAndDisplayCourses', + DISPLAY_COURSES: 'displayCourses', GRADE: 'grade', GRADE_STARTED: 'gradeStarted', GRADE_COMPLETED: 'gradeCompleted', @@ -8,4 +9,10 @@ export const MessageCommand = { GRADE_PAUSED: 'gradePaused', GRASE_RESUMED: 'gradeResumed', GRADE_ABORTED: 'gradeAborted', -} as const; \ No newline at end of file + ERROR: 'error', +} as const; + +export type WebViewMessage = { + command: (typeof MessageCommand)[keyof typeof MessageCommand]; + [key: string]: string | number | boolean | object | null | undefined; +}; \ No newline at end of file diff --git a/src/typings/vscode-git.d.ts b/src/typings/vscode-git.d.ts index 898cff1..d467672 100644 --- a/src/typings/vscode-git.d.ts +++ b/src/typings/vscode-git.d.ts @@ -1,8 +1,18 @@ -/** - * Minimal typings for the built-in Git extension API (vscode.git). - * Used for push, pull, and commit in GitService. - */ -import type { Uri } from 'vscode'; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Uri, Event, Disposable, ProviderResult, Command, CancellationToken } from 'vscode'; +export { ProviderResult } from 'vscode'; + +export interface Git { + readonly path: string; +} + +export interface InputBox { + value: string; +} export const enum ForcePushMode { Force, @@ -10,33 +20,493 @@ export const enum ForcePushMode { ForceWithLeaseIfIncludes, } +export const enum RefType { + Head, + RemoteHead, + Tag +} + +export interface Ref { + readonly type: RefType; + readonly name?: string; + readonly commit?: string; + readonly commitDetails?: Commit; + readonly remote?: string; +} + +export interface UpstreamRef { + readonly remote: string; + readonly name: string; + readonly commit?: string; +} + +export interface Branch extends Ref { + readonly upstream?: UpstreamRef; + readonly ahead?: number; + readonly behind?: number; +} + +export interface CommitShortStat { + readonly files: number; + readonly insertions: number; + readonly deletions: number; +} + +export interface Commit { + readonly hash: string; + readonly message: string; + readonly parents: string[]; + readonly authorDate?: Date; + readonly authorName?: string; + readonly authorEmail?: string; + readonly commitDate?: Date; + readonly shortStat?: CommitShortStat; +} + +export interface Submodule { + readonly name: string; + readonly path: string; + readonly url: string; +} + +export interface Remote { + readonly name: string; + readonly fetchUrl?: string; + readonly pushUrl?: string; + readonly isReadOnly: boolean; +} + +export interface Worktree { + readonly name: string; + readonly path: string; + readonly ref: string; + readonly main: boolean; + readonly detached: boolean; +} + +export const enum Status { + INDEX_MODIFIED, + INDEX_ADDED, + INDEX_DELETED, + INDEX_RENAMED, + INDEX_COPIED, + + MODIFIED, + DELETED, + UNTRACKED, + IGNORED, + INTENT_TO_ADD, + INTENT_TO_RENAME, + TYPE_CHANGED, + + ADDED_BY_US, + ADDED_BY_THEM, + DELETED_BY_US, + DELETED_BY_THEM, + BOTH_ADDED, + BOTH_DELETED, + BOTH_MODIFIED +} + +export interface Change { + + /** + * Returns either `originalUri` or `renameUri`, depending + * on whether this change is a rename change. When + * in doubt always use `uri` over the other two alternatives. + */ + readonly uri: Uri; + readonly originalUri: Uri; + readonly renameUri: Uri | undefined; + readonly status: Status; +} + +export interface DiffChange extends Change { + readonly insertions: number; + readonly deletions: number; +} + +export type RepositoryKind = 'repository' | 'submodule' | 'worktree'; + +export interface RepositoryState { + readonly HEAD: Branch | undefined; + readonly refs: Ref[]; + readonly remotes: Remote[]; + readonly submodules: Submodule[]; + readonly worktrees: Worktree[]; + readonly rebaseCommit: Commit | undefined; + + readonly mergeChanges: Change[]; + readonly indexChanges: Change[]; + readonly workingTreeChanges: Change[]; + readonly untrackedChanges: Change[]; + + readonly onDidChange: Event; +} + +export interface RepositoryUIState { + readonly selected: boolean; + readonly onDidChange: Event; +} + +export interface RepositoryAccessDetails { + readonly rootUri: Uri; + readonly lastAccessTime: number; +} + +/** + * Log options. + */ +export interface LogOptions { + /** Max number of log entries to retrieve. If not specified, the default is 32. */ + readonly maxEntries?: number; + readonly path?: string; + /** A commit range, such as "0a47c67f0fb52dd11562af48658bc1dff1d75a38..0bb4bdea78e1db44d728fd6894720071e303304f" */ + readonly range?: string; + readonly reverse?: boolean; + readonly sortByAuthorDate?: boolean; + readonly shortStats?: boolean; + readonly author?: string; + readonly grep?: string; + readonly refNames?: string[]; + readonly maxParents?: number; + readonly skip?: number; +} + export interface CommitOptions { all?: boolean | 'tracked'; amend?: boolean; signoff?: boolean; + /** + * true - sign the commit + * false - do not sign the commit + * undefined - use the repository/global git config + */ signCommit?: boolean; empty?: boolean; noVerify?: boolean; + requireUserConfig?: boolean; + useEditor?: boolean; + verbose?: boolean; + /** + * string - execute the specified command after the commit operation + * undefined - execute the command specified in git.postCommitCommand + * after the commit operation + * null - do not execute any command after the commit operation + */ + postCommitCommand?: string | null; +} + +export interface FetchOptions { + remote?: string; + ref?: string; + all?: boolean; + prune?: boolean; + depth?: number; +} + +export interface InitOptions { + defaultBranch?: string; +} + +export interface CloneOptions { + parentPath?: Uri; + /** + * ref is only used if the repository cache is missed. + */ + ref?: string; + recursive?: boolean; + /** + * If no postCloneAction is provided, then the users setting for git.openAfterClone is used. + */ + postCloneAction?: 'none'; +} + +export interface RefQuery { + readonly contains?: string; + readonly count?: number; + readonly pattern?: string | string[]; + readonly sort?: 'alphabetically' | 'committerdate' | 'creatordate'; +} + +export interface BranchQuery extends RefQuery { + readonly remote?: boolean; } export interface Repository { + readonly rootUri: Uri; - commit(message: string, opts?: CommitOptions): Promise; + readonly inputBox: InputBox; + readonly state: RepositoryState; + readonly ui: RepositoryUIState; + readonly kind: RepositoryKind; + + readonly onDidCommit: Event; + readonly onDidCheckout: Event; + + getConfigs(): Promise<{ key: string; value: string; }[]>; + getConfig(key: string): Promise; + setConfig(key: string, value: string): Promise; + unsetConfig(key: string): Promise; + getGlobalConfig(key: string): Promise; + + getObjectDetails(treeish: string, path: string): Promise<{ mode: string, object: string, size: number }>; + detectObjectType(object: string): Promise<{ mimetype: string, encoding?: string }>; + buffer(ref: string, path: string): Promise; + show(ref: string, path: string): Promise; + getCommit(ref: string): Promise; + + add(paths: string[]): Promise; + revert(paths: string[]): Promise; + clean(paths: string[]): Promise; + + apply(patch: string, reverse?: boolean): Promise; + apply(patch: string, options?: { allowEmpty?: boolean; reverse?: boolean; threeWay?: boolean; }): Promise; + diff(cached?: boolean): Promise; + diffWithHEAD(): Promise; + diffWithHEAD(path: string): Promise; + diffWithHEADShortStats(path?: string): Promise; + diffWith(ref: string): Promise; + diffWith(ref: string, path: string): Promise; + diffIndexWithHEAD(): Promise; + diffIndexWithHEAD(path: string): Promise; + diffIndexWithHEADShortStats(path?: string): Promise; + diffIndexWith(ref: string): Promise; + diffIndexWith(ref: string, path: string): Promise; + diffBlobs(object1: string, object2: string): Promise; + diffBetween(ref1: string, ref2: string): Promise; + diffBetween(ref1: string, ref2: string, path: string): Promise; + diffBetweenPatch(ref1: string, ref2: string, path?: string): Promise; + diffBetweenWithStats(ref1: string, ref2: string, path?: string): Promise; + diffBetweenWithStats2(ref: string, path?: string): Promise; + + hashObject(data: string): Promise; + + createBranch(name: string, checkout: boolean, ref?: string): Promise; + deleteBranch(name: string, force?: boolean): Promise; + getBranch(name: string): Promise; + getBranches(query: BranchQuery, cancellationToken?: CancellationToken): Promise; + getBranchBase(name: string): Promise; + setBranchUpstream(name: string, upstream: string): Promise; + + checkIgnore(paths: string[]): Promise>; + + getRefs(query: RefQuery, cancellationToken?: CancellationToken): Promise; + + getMergeBase(ref1: string, ref2: string): Promise; + + tag(name: string, message: string, ref?: string | undefined): Promise; + deleteTag(name: string): Promise; + + status(): Promise; + checkout(treeish: string): Promise; + + addRemote(name: string, url: string): Promise; + removeRemote(name: string): Promise; + renameRemote(name: string, newName: string): Promise; + + fetch(options?: FetchOptions): Promise; + fetch(remote?: string, ref?: string, depth?: number): Promise; pull(unshallow?: boolean): Promise; - push( - remoteName?: string, - branchName?: string, - setUpstream?: boolean, - force?: ForcePushMode - ): Promise; + push(remoteName?: string, branchName?: string, setUpstream?: boolean, force?: ForcePushMode): Promise; + + blame(path: string): Promise; + log(options?: LogOptions): Promise; + + commit(message: string, opts?: CommitOptions): Promise; + merge(ref: string): Promise; + mergeAbort(): Promise; + rebase(branch: string): Promise; + + createStash(options?: { message?: string; includeUntracked?: boolean; staged?: boolean }): Promise; + applyStash(index?: number): Promise; + popStash(index?: number): Promise; + dropStash(index?: number): Promise; + + createWorktree(options?: { path?: string; commitish?: string; branch?: string }): Promise; + deleteWorktree(path: string, options?: { force?: boolean }): Promise; + + migrateChanges(sourceRepositoryPath: string, options?: { confirmation?: boolean; deleteFromSource?: boolean; untracked?: boolean }): Promise; + + generateRandomBranchName(): Promise; + + isBranchProtected(branch?: Branch): boolean; +} + +export interface RemoteSource { + readonly name: string; + readonly description?: string; + readonly url: string | string[]; +} + +export interface RemoteSourceProvider { + readonly name: string; + readonly icon?: string; // codicon name + readonly supportsQuery?: boolean; + getRemoteSources(query?: string): ProviderResult; + getBranches?(url: string): ProviderResult; + publishRepository?(repository: Repository): Promise; +} + +export interface RemoteSourcePublisher { + readonly name: string; + readonly icon?: string; // codicon name + publishRepository(repository: Repository): Promise; +} + +export interface Credentials { + readonly username: string; + readonly password: string; +} + +export interface CredentialsProvider { + getCredentials(host: Uri): ProviderResult; +} + +export interface PostCommitCommandsProvider { + getCommands(repository: Repository): Command[]; +} + +export interface PushErrorHandler { + handlePushError(repository: Repository, remote: Remote, refspec: string, error: Error & { gitErrorCode: GitErrorCodes }): Promise; +} + +export interface BranchProtection { + readonly remote: string; + readonly rules: BranchProtectionRule[]; +} + +export interface BranchProtectionRule { + readonly include?: string[]; + readonly exclude?: string[]; +} + +export interface BranchProtectionProvider { + onDidChangeBranchProtection: Event; + provideBranchProtection(): BranchProtection[]; } -export interface GitApi { +export interface AvatarQueryCommit { + readonly hash: string; + readonly authorName?: string; + readonly authorEmail?: string; +} + +export interface AvatarQuery { + readonly commits: AvatarQueryCommit[]; + readonly size: number; +} + +export interface SourceControlHistoryItemDetailsProvider { + provideAvatar(repository: Repository, query: AvatarQuery): ProviderResult>; + provideHoverCommands(repository: Repository): ProviderResult; + provideMessageLinks(repository: Repository, message: string): ProviderResult; +} + +export type APIState = 'uninitialized' | 'initialized'; + +export interface PublishEvent { + repository: Repository; + branch?: string; +} + +export interface API { + readonly state: APIState; + readonly onDidChangeState: Event; + readonly onDidPublish: Event; + readonly git: Git; readonly repositories: Repository[]; + readonly recentRepositories: Iterable; + readonly onDidOpenRepository: Event; + readonly onDidCloseRepository: Event; + + toGitUri(uri: Uri, ref: string): Uri; getRepository(uri: Uri): Repository | null; + getRepositoryRoot(uri: Uri): Promise; + getRepositoryWorkspace(uri: Uri): Promise; + init(root: Uri, options?: InitOptions): Promise; + /** + * Checks the cache of known cloned repositories, and clones if the repository is not found. + * Make sure to pass `postCloneAction` 'none' if you want to have the uri where you can find the repository returned. + * @returns The URI of a folder or workspace file which, when opened, will open the cloned repository. + */ + clone(uri: Uri, options?: CloneOptions): Promise; + openRepository(root: Uri): Promise; + + registerRemoteSourcePublisher(publisher: RemoteSourcePublisher): Disposable; + registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable; + registerCredentialsProvider(provider: CredentialsProvider): Disposable; + registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable; + registerPushErrorHandler(handler: PushErrorHandler): Disposable; + registerBranchProtectionProvider(root: Uri, provider: BranchProtectionProvider): Disposable; + registerSourceControlHistoryItemDetailsProvider(provider: SourceControlHistoryItemDetailsProvider): Disposable; } export interface GitExtension { + readonly enabled: boolean; - getAPI(version: 1): GitApi; + readonly onDidChangeEnablement: Event; + + /** + * Returns a specific API version. + * + * Throws error if git extension is disabled. You can listen to the + * [GitExtension.onDidChangeEnablement](#GitExtension.onDidChangeEnablement) event + * to know when the extension becomes enabled/disabled. + * + * @param version Version number. + * @returns API instance + */ + getAPI(version: 1): API; } + +export const enum GitErrorCodes { + BadConfigFile = 'BadConfigFile', + BadRevision = 'BadRevision', + AuthenticationFailed = 'AuthenticationFailed', + NoUserNameConfigured = 'NoUserNameConfigured', + NoUserEmailConfigured = 'NoUserEmailConfigured', + NoRemoteRepositorySpecified = 'NoRemoteRepositorySpecified', + NotAGitRepository = 'NotAGitRepository', + NotASafeGitRepository = 'NotASafeGitRepository', + NotAtRepositoryRoot = 'NotAtRepositoryRoot', + Conflict = 'Conflict', + StashConflict = 'StashConflict', + UnmergedChanges = 'UnmergedChanges', + PushRejected = 'PushRejected', + ForcePushWithLeaseRejected = 'ForcePushWithLeaseRejected', + ForcePushWithLeaseIfIncludesRejected = 'ForcePushWithLeaseIfIncludesRejected', + RemoteConnectionError = 'RemoteConnectionError', + DirtyWorkTree = 'DirtyWorkTree', + CantOpenResource = 'CantOpenResource', + GitNotFound = 'GitNotFound', + CantCreatePipe = 'CantCreatePipe', + PermissionDenied = 'PermissionDenied', + CantAccessRemote = 'CantAccessRemote', + RepositoryNotFound = 'RepositoryNotFound', + RepositoryIsLocked = 'RepositoryIsLocked', + BranchNotFullyMerged = 'BranchNotFullyMerged', + NoRemoteReference = 'NoRemoteReference', + InvalidBranchName = 'InvalidBranchName', + BranchAlreadyExists = 'BranchAlreadyExists', + NoLocalChanges = 'NoLocalChanges', + NoStashFound = 'NoStashFound', + LocalChangesOverwritten = 'LocalChangesOverwritten', + NoUpstreamBranch = 'NoUpstreamBranch', + IsInSubmodule = 'IsInSubmodule', + WrongCase = 'WrongCase', + CantLockRef = 'CantLockRef', + CantRebaseMultipleBranches = 'CantRebaseMultipleBranches', + PatchDoesNotApply = 'PatchDoesNotApply', + NoPathFound = 'NoPathFound', + UnknownPath = 'UnknownPath', + EmptyCommitMessage = 'EmptyCommitMessage', + BranchFastForwardRejected = 'BranchFastForwardRejected', + BranchNotYetBorn = 'BranchNotYetBorn', + TagConflict = 'TagConflict', + CherryPickEmpty = 'CherryPickEmpty', + CherryPickConflict = 'CherryPickConflict', + WorktreeContainsChanges = 'WorktreeContainsChanges', + WorktreeAlreadyExists = 'WorktreeAlreadyExists', + WorktreeBranchAlreadyUsed = 'WorktreeBranchAlreadyUsed' +} \ No newline at end of file From 01bc793296711d082d76c2064438a7c00eeed19f Mon Sep 17 00:00:00 2001 From: Riley Smith Date: Thu, 19 Mar 2026 23:42:12 -0700 Subject: [PATCH 04/20] format + lint --- .github/workflows/lint_and_test.yml | 4 +- .vscode-test.mjs | 2 +- .vscode/extensions.json | 12 +- .vscode/launch.json | 26 +- .vscode/settings.json | 16 +- .vscode/tasks.json | 32 +- CHANGELOG.md | 2 +- README.md | 6 + eslint.config.mjs | 21 +- package.json | 2 +- src/extension.ts | 78 +++-- src/interfaces/AutoGraderDetails.ts | 55 ++-- src/interfaces/Courses.ts | 14 +- src/interfaces/Gradables.ts | 26 +- src/interfaces/Responses.ts | 21 +- src/services/apiClient.ts | 268 ++++++++------- src/services/apiService.ts | 353 +++++++++++--------- src/services/authService.ts | 319 +++++++++--------- src/services/courseRepoResolver.ts | 267 +++++++-------- src/services/gitService.ts | 178 +++++----- src/services/testingService.ts | 486 +++++++++++++++------------ src/sidebar/login.html | 128 +++---- src/sidebarContent.ts | 20 +- src/sidebarProvider.ts | 494 +++++++++++++++------------- src/test/extension.test.ts | 10 +- src/typings/message.ts | 28 +- 26 files changed, 1543 insertions(+), 1325 deletions(-) diff --git a/.github/workflows/lint_and_test.yml b/.github/workflows/lint_and_test.yml index b75b3b0..79e7179 100644 --- a/.github/workflows/lint_and_test.yml +++ b/.github/workflows/lint_and_test.yml @@ -18,8 +18,8 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: "20" - cache: "npm" + node-version: '20' + cache: 'npm' - name: Install dependencies run: npm ci diff --git a/.vscode-test.mjs b/.vscode-test.mjs index b62ba25..49fac78 100644 --- a/.vscode-test.mjs +++ b/.vscode-test.mjs @@ -1,5 +1,5 @@ import { defineConfig } from '@vscode/test-cli'; export default defineConfig({ - files: 'out/test/**/*.test.js', + files: 'out/test/**/*.test.js', }); diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 186459d..5906abf 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,8 +1,8 @@ { - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "dbaeumer.vscode-eslint", - "ms-vscode.extension-test-runner" - ] + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "dbaeumer.vscode-eslint", + "ms-vscode.extension-test-runner" + ] } diff --git a/.vscode/launch.json b/.vscode/launch.json index 8880465..a0ca3cb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,19 +3,15 @@ // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 { - "version": "0.2.0", - "configurations": [ - { - "name": "Run Extension", - "type": "extensionHost", - "request": "launch", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}" - ], - "outFiles": [ - "${workspaceFolder}/out/**/*.js" - ], - "preLaunchTask": "${defaultBuildTask}" - } - ] + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "outFiles": ["${workspaceFolder}/out/**/*.js"], + "preLaunchTask": "${defaultBuildTask}" + } + ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index afdab66..ffeaf91 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,11 +1,11 @@ // Place your settings in this file to overwrite default and user settings. { - "files.exclude": { - "out": false // set this to true to hide the "out" folder with the compiled JS files - }, - "search.exclude": { - "out": true // set this to false to include "out" folder in search results - }, - // Turn off tsc task auto detection since we have the necessary tasks as npm scripts - "typescript.tsc.autoDetect": "off" + "files.exclude": { + "out": false // set this to true to hide the "out" folder with the compiled JS files + }, + "search.exclude": { + "out": true // set this to false to include "out" folder in search results + }, + // Turn off tsc task auto detection since we have the necessary tasks as npm scripts + "typescript.tsc.autoDetect": "off" } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 3b17e53..078ff7e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,20 +1,20 @@ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format { - "version": "2.0.0", - "tasks": [ - { - "type": "npm", - "script": "watch", - "problemMatcher": "$tsc-watch", - "isBackground": true, - "presentation": { - "reveal": "never" - }, - "group": { - "kind": "build", - "isDefault": true - } - } - ] + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "watch", + "problemMatcher": "$tsc-watch", + "isBackground": true, + "presentation": { + "reveal": "never" + }, + "group": { + "kind": "build", + "isDefault": true + } + } + ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index c9a250a..1a7244b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,4 +6,4 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how ## [Unreleased] -- Initial release \ No newline at end of file +- Initial release diff --git a/README.md b/README.md index a816d42..73f2092 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,25 @@ # Submitty Extension for VS Code ## Overview + The Submitty Extension for VS Code integrates the Submitty grading system directly into Visual Studio Code, allowing users to easily submit assignments, view grades, and interact with their courses without leaving the editor. ## Features + - **Assignment Submission**: Submit assignments directly from VS Code. - **Grade Retrieval**: View grades and feedback within the editor. - **Course Management**: Access course information and assignment details. - **Error & Feedback Display**: Get inline feedback on submissions. ## Setup + 1. Open the **Submitty Extension**. 2. Enter your **Submitty server URL**. 3. Authenticate using your **username and password**. 4. Select your **course** from the available list. ## Usage + - **Submit an Assignment**: 1. Open the relevant assignment file. 2. Click on the HW you want graded. @@ -24,9 +28,11 @@ The Submitty Extension for VS Code integrates the Submitty grading system direct - Open the Submitty panel to view assignment grades and instructor feedback. ## Requirements + - A valid Submitty account. ## Roadmap + - [ ] Allow users to access homeowrk - [ ] Figure out a way to grade homework and display results back to users - [ ] Display test results with feedback diff --git a/eslint.config.mjs b/eslint.config.mjs index 1645563..e0906fa 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -9,7 +9,15 @@ export default defineConfig([ // --- 1. Global Ignores --- // Files and directories to ignore across the entire project { - ignores: ['out/**', 'dist/**', '**/*.d.ts', 'node_modules/**', '.vscode-test/**', '.vscode-test.mjs', 'eslint.config.mjs'], + ignores: [ + 'out/**', + 'dist/**', + '**/*.d.ts', + 'node_modules/**', + '.vscode-test/**', + '.vscode-test.mjs', + 'eslint.config.mjs', + ], }, // --- 2. Base Configurations (Applied to ALL files by default) --- @@ -31,7 +39,7 @@ export default defineConfig([ parserOptions: { projectService: true, }, - } + }, }, // --- 3. Configuration for VS Code Extension (Node.js/TypeScript) --- @@ -66,10 +74,10 @@ export default defineConfig([ format: ['camelCase', 'PascalCase'], }, ], - 'curly': 'warn', // Require curly braces for all control statements - 'eqeqeq': 'warn', // Require the use of '===' and '!==' + curly: 'warn', // Require curly braces for all control statements + eqeqeq: 'warn', // Require the use of '===' and '!==' 'no-throw-literal': 'warn', // Disallow throwing literals as exceptions - 'semi': 'off', // Let Prettier handle semicolons (or enforce no semicolons) + semi: 'off', // Let Prettier handle semicolons (or enforce no semicolons) '@typescript-eslint/no-floating-promises': 'error', // Good for async operations '@typescript-eslint/explicit-function-return-type': [ 'warn', @@ -90,5 +98,4 @@ export default defineConfig([ '@typescript-eslint/no-explicit-any': 'off', // Or 'warn' depending on your preference }, }, - -]); \ No newline at end of file +]); diff --git a/package.json b/package.json index 4766aab..1a9a1cf 100644 --- a/package.json +++ b/package.json @@ -93,4 +93,4 @@ "axios": "^1.7.8", "keytar": "^7.9.0" } -} \ No newline at end of file +} diff --git a/src/extension.ts b/src/extension.ts index 30d2371..26ebc24 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -8,39 +8,57 @@ import { CourseRepoResolver } from './services/courseRepoResolver'; import type { Gradable } from './interfaces/Gradables'; export function activate(context: vscode.ExtensionContext): void { - const apiService = ApiService.getInstance(context, ''); - const testingService = new TestingService(context, apiService); - const gitService = new GitService(); - const authService = AuthService.getInstance(context); - const sidebarProvider = new SidebarProvider(context, testingService, gitService); + const apiService = ApiService.getInstance(context, ''); + const testingService = new TestingService(context, apiService); + const gitService = new GitService(); + const authService = AuthService.getInstance(context); + const sidebarProvider = new SidebarProvider( + context, + testingService, + gitService + ); - context.subscriptions.push( - vscode.window.registerWebviewViewProvider('submittyWebview', sidebarProvider) - ); + context.subscriptions.push( + vscode.window.registerWebviewViewProvider( + 'submittyWebview', + sidebarProvider + ) + ); - // Preload gradables into the Test Explorer when the workspace appears - // to be a course-tied repo. - void (async () => { - try { - await authService.initialize(); - const resolver = new CourseRepoResolver(apiService, authService, gitService); - const courseContext = await resolver.resolveCourseContextFromRepo(); - if (!courseContext) { - return; - } + // Preload gradables into the Test Explorer when the workspace appears + // to be a course-tied repo. + void (async () => { + try { + await authService.initialize(); + const resolver = new CourseRepoResolver( + apiService, + authService, + gitService + ); + const courseContext = await resolver.resolveCourseContextFromRepo(); + if (!courseContext) { + return; + } - const gradablesResponse = await apiService.fetchGradables(courseContext.courseId, courseContext.term); - const gradables = Object.values(gradablesResponse.data); - - for (const g of gradables) { - testingService.addGradeable(courseContext.term, courseContext.courseId, g.id, g.title || g.id); - } - } catch (e) { - const err = e instanceof Error ? e.message : String(e); - console.warn(`Failed to preload gradables: ${err}`); - } - })(); + const gradablesResponse = await apiService.fetchGradables( + courseContext.courseId, + courseContext.term + ); + const gradables = Object.values(gradablesResponse.data); + for (const g of gradables) { + testingService.addGradeable( + courseContext.term, + courseContext.courseId, + g.id, + g.title || g.id + ); + } + } catch (e) { + const err = e instanceof Error ? e.message : String(e); + console.warn(`Failed to preload gradables: ${err}`); + } + })(); } -export function deactivate() { } \ No newline at end of file +export function deactivate() {} diff --git a/src/interfaces/AutoGraderDetails.ts b/src/interfaces/AutoGraderDetails.ts index 7a94b18..d654df4 100644 --- a/src/interfaces/AutoGraderDetails.ts +++ b/src/interfaces/AutoGraderDetails.ts @@ -1,42 +1,41 @@ export interface AutoGraderDetails { - status: string - data: AutoGraderDetailsData + status: string; + data: AutoGraderDetailsData; } export interface AutoGraderDetailsData { - is_queued: boolean - queue_position: number - is_grading: boolean - has_submission: boolean - autograding_complete: boolean - has_active_version: boolean - highest_version: number - total_points: number - total_percent: number - test_cases: TestCase[] + is_queued: boolean; + queue_position: number; + is_grading: boolean; + has_submission: boolean; + autograding_complete: boolean; + has_active_version: boolean; + highest_version: number; + total_points: number; + total_percent: number; + test_cases: TestCase[]; } export interface TestCase { - name: string - details: string - is_extra_credit: boolean - points_available: number - has_extra_results: boolean - points_received: number - testcase_message: string - autochecks: Autocheck[] + name: string; + details: string; + is_extra_credit: boolean; + points_available: number; + has_extra_results: boolean; + points_received: number; + testcase_message: string; + autochecks: Autocheck[]; } export interface Autocheck { - description: string - messages: Message[] - diff_viewer: Record - expected: string - actual: string + description: string; + messages: Message[]; + diff_viewer: Record; + expected: string; + actual: string; } export interface Message { - message: string - type: string + message: string; + type: string; } - diff --git a/src/interfaces/Courses.ts b/src/interfaces/Courses.ts index 9d60f51..354aaf8 100644 --- a/src/interfaces/Courses.ts +++ b/src/interfaces/Courses.ts @@ -1,8 +1,8 @@ export interface Course { - semester: string; - title: string; - display_name: string; - display_semester: string; - user_group: number; - registration_section: string; -} \ No newline at end of file + semester: string; + title: string; + display_name: string; + display_semester: string; + user_group: number; + registration_section: string; +} diff --git a/src/interfaces/Gradables.ts b/src/interfaces/Gradables.ts index eef1dad..a8dff20 100644 --- a/src/interfaces/Gradables.ts +++ b/src/interfaces/Gradables.ts @@ -1,18 +1,18 @@ export interface Gradable { - id: string - title: string - instructions_url: string - gradeable_type: string - syllabus_bucket: string - section: number - section_name: string - due_date: DueDate - vcs_repository: string - vcs_subdirectory: string + id: string; + title: string; + instructions_url: string; + gradeable_type: string; + syllabus_bucket: string; + section: number; + section_name: string; + due_date: DueDate; + vcs_repository: string; + vcs_subdirectory: string; } export interface DueDate { - date: string - timezone_type: number - timezone: string + date: string; + timezone_type: number; + timezone: string; } diff --git a/src/interfaces/Responses.ts b/src/interfaces/Responses.ts index 5e7ef1f..6f6c2e0 100644 --- a/src/interfaces/Responses.ts +++ b/src/interfaces/Responses.ts @@ -1,22 +1,21 @@ -import { Course } from "./Courses"; -import { Gradable } from "./Gradables"; - +import { Course } from './Courses'; +import { Gradable } from './Gradables'; export interface ApiResponse { - status: string; - data: T; - message?: string; + status: string; + data: T; + message?: string; } export type CourseResponse = ApiResponse<{ - unarchived_courses: Course[]; - dropped_courses: Course[]; + unarchived_courses: Course[]; + dropped_courses: Course[]; }>; export type LoginResponse = ApiResponse<{ - token: string; + token: string; }>; export type GradableResponse = ApiResponse<{ - [key: string]: Gradable; -}>; \ No newline at end of file + [key: string]: Gradable; +}>; diff --git a/src/services/apiClient.ts b/src/services/apiClient.ts index dc0ce93..d3fd209 100644 --- a/src/services/apiClient.ts +++ b/src/services/apiClient.ts @@ -3,131 +3,145 @@ import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; export class ApiClient { - private client: AxiosInstance; - - constructor(baseURL: string = '', defaultHeaders: Record = {}) { - this.client = axios.create({ - baseURL, - headers: { - 'Content-Type': 'application/json', - ...defaultHeaders, - }, - timeout: 30000, // 30 seconds timeout - }); - - // Request interceptor - this.client.interceptors.request.use( - (config) => { - // Add any request logging or modification here - return config; - }, - (error: Error) => { - return Promise.reject(new Error(error.message || 'Request failed')); - } - ); - - // Response interceptor - this.client.interceptors.response.use( - (response) => { - return response; - }, - (error: Error) => { - // Handle common errors here - return Promise.reject(new Error(error.message || 'Response failed')); - } - ); - } - - /** - * Set the base URL for all requests - */ - setBaseURL(baseURL: string): void { - this.client.defaults.baseURL = baseURL; - } - - /** - * Set default headers for all requests - */ - setDefaultHeaders(headers: Record): void { - this.client.defaults.headers.common = { - ...this.client.defaults.headers.common, - ...headers, - }; - } - - /** - * Set the Authorization token for all requests - */ - setToken(token: string): void { - this.client.defaults.headers.common['Authorization'] = `${token}`; - } - - /** - * GET request - */ - async get(url: string, config?: AxiosRequestConfig): Promise> { - return this.client.get(url, config); - } - - /** - * POST request - */ - async post( - url: string, - data?: any, - config?: AxiosRequestConfig - ): Promise> { - return this.client.post(url, data, config); - } - - /** - * PUT request - */ - async put( - url: string, - data?: any, - config?: AxiosRequestConfig - ): Promise> { - return this.client.put(url, data, config); - } - - /** - * PATCH request - */ - async patch( - url: string, - data?: any, - config?: AxiosRequestConfig - ): Promise> { - return this.client.patch(url, data, config); - } - - /** - * DELETE request - */ - async delete(url: string, config?: AxiosRequestConfig): Promise> { - return this.client.delete(url, config); - } - - /** - * HEAD request - */ - async head(url: string, config?: AxiosRequestConfig): Promise> { - return this.client.head(url, config); - } - - /** - * OPTIONS request - */ - async options(url: string, config?: AxiosRequestConfig): Promise> { - return this.client.options(url, config); - } - - /** - * Get the underlying axios instance for advanced usage - */ - getAxiosInstance(): AxiosInstance { - return this.client; - } + private client: AxiosInstance; + + constructor( + baseURL: string = '', + defaultHeaders: Record = {} + ) { + this.client = axios.create({ + baseURL, + headers: { + 'Content-Type': 'application/json', + ...defaultHeaders, + }, + timeout: 30000, // 30 seconds timeout + }); + + // Request interceptor + this.client.interceptors.request.use( + config => { + // Add any request logging or modification here + return config; + }, + (error: Error) => { + return Promise.reject(new Error(error.message || 'Request failed')); + } + ); + + // Response interceptor + this.client.interceptors.response.use( + response => { + return response; + }, + (error: Error) => { + // Handle common errors here + return Promise.reject(new Error(error.message || 'Response failed')); + } + ); + } + + /** + * Set the base URL for all requests + */ + setBaseURL(baseURL: string): void { + this.client.defaults.baseURL = baseURL; + } + + /** + * Set default headers for all requests + */ + setDefaultHeaders(headers: Record): void { + this.client.defaults.headers.common = { + ...this.client.defaults.headers.common, + ...headers, + }; + } + + /** + * Set the Authorization token for all requests + */ + setToken(token: string): void { + this.client.defaults.headers.common['Authorization'] = `${token}`; + } + + /** + * GET request + */ + async get( + url: string, + config?: AxiosRequestConfig + ): Promise> { + return this.client.get(url, config); + } + + /** + * POST request + */ + async post( + url: string, + data?: any, + config?: AxiosRequestConfig + ): Promise> { + return this.client.post(url, data, config); + } + + /** + * PUT request + */ + async put( + url: string, + data?: any, + config?: AxiosRequestConfig + ): Promise> { + return this.client.put(url, data, config); + } + + /** + * PATCH request + */ + async patch( + url: string, + data?: any, + config?: AxiosRequestConfig + ): Promise> { + return this.client.patch(url, data, config); + } + + /** + * DELETE request + */ + async delete( + url: string, + config?: AxiosRequestConfig + ): Promise> { + return this.client.delete(url, config); + } + + /** + * HEAD request + */ + async head( + url: string, + config?: AxiosRequestConfig + ): Promise> { + return this.client.head(url, config); + } + + /** + * OPTIONS request + */ + async options( + url: string, + config?: AxiosRequestConfig + ): Promise> { + return this.client.options(url, config); + } + + /** + * Get the underlying axios instance for advanced usage + */ + getAxiosInstance(): AxiosInstance { + return this.client; + } } - diff --git a/src/services/apiService.ts b/src/services/apiService.ts index d35964e..0706c47 100644 --- a/src/services/apiService.ts +++ b/src/services/apiService.ts @@ -2,181 +2,214 @@ import * as vscode from 'vscode'; import { ApiClient } from './apiClient'; -import { CourseResponse, LoginResponse, GradableResponse } from '../interfaces/Responses'; +import { + CourseResponse, + LoginResponse, + GradableResponse, +} from '../interfaces/Responses'; import { AutoGraderDetails } from '../interfaces/AutoGraderDetails'; function getErrorMessage(error: unknown, fallback: string): string { - if (error instanceof Error) { - return error.message || fallback; + if (error instanceof Error) { + return error.message || fallback; + } + if (typeof error === 'object' && error) { + const maybeAxiosError = error as { + response?: { data?: { message?: unknown } }; + }; + const msg = maybeAxiosError.response?.data?.message; + if (typeof msg === 'string' && msg.trim()) { + return msg; } - if (typeof error === 'object' && error) { - const maybeAxiosError = error as { response?: { data?: { message?: unknown } } }; - const msg = maybeAxiosError.response?.data?.message; - if (typeof msg === 'string' && msg.trim()) { - return msg; - } - } - return fallback; + } + return fallback; } export class ApiService { - private client: ApiClient; - private static instance: ApiService; - - constructor(private context: vscode.ExtensionContext, apiBaseUrl: string) { - this.client = new ApiClient(apiBaseUrl); - } - - // set token for local api client - setAuthorizationToken(token: string): void { - this.client.setToken(token); - } + private client: ApiClient; + private static instance: ApiService; + + constructor( + private context: vscode.ExtensionContext, + apiBaseUrl: string + ) { + this.client = new ApiClient(apiBaseUrl); + } + + // set token for local api client + setAuthorizationToken(token: string): void { + this.client.setToken(token); + } + + // set base URL for local api client + setBaseUrl(baseUrl: string): void { + this.client.setBaseURL(baseUrl); + } + + /** + * Login to the Submitty API + */ + async login(userId: string, password: string): Promise { + try { + const response = await this.client.post( + '/api/token', + { + user_id: userId, + password: password, + }, + { + headers: { 'Content-Type': 'multipart/form-data' }, + } + ); - // set base URL for local api client - setBaseUrl(baseUrl: string): void { - this.client.setBaseURL(baseUrl); + const token: string = response.data.data.token; + return token; + } catch (error: unknown) { + throw new Error(getErrorMessage(error, 'Login failed.')); } - - /** - * Login to the Submitty API - */ - async login(userId: string, password: string): Promise { - try { - const response = await this.client.post( - '/api/token', - { - user_id: userId, - password: password, - }, - { - headers: { 'Content-Type': 'multipart/form-data' }, - } - ); - - const token: string = response.data.data.token; - return token; - } catch (error: unknown) { - throw new Error(getErrorMessage(error, 'Login failed.')); - } + } + + async fetchMe(): Promise { + try { + const response = await this.client.get('/api/me'); + return response.data; + } catch (error: unknown) { + throw new Error(getErrorMessage(error, 'Failed to fetch me.')); } - - async fetchMe(): Promise { - try { - const response = await this.client.get('/api/me'); - return response.data; - } catch (error: unknown) { - throw new Error(getErrorMessage(error, 'Failed to fetch me.')); - } + } + + /** + * Fetch all courses for the authenticated user + */ + async fetchCourses(_token?: string): Promise { + try { + const response = await this.client.get('/api/courses'); + return response.data; + } catch (error: unknown) { + console.error('Error fetching courses:', error); + throw new Error(getErrorMessage(error, 'Failed to fetch courses.')); } - - - /** - * Fetch all courses for the authenticated user - */ - async fetchCourses(_token?: string): Promise { - try { - const response = await this.client.get('/api/courses'); - return response.data; - } catch (error: unknown) { - console.error('Error fetching courses:', error); - throw new Error(getErrorMessage(error, 'Failed to fetch courses.')); - } + } + + async fetchGradables( + courseId: string, + term: string + ): Promise { + try { + const url = `/api/${term}/${courseId}/gradeables`; + const response = await this.client.get(url); + return response.data; + } catch (error: unknown) { + console.error('Error fetching gradables:', error); + throw new Error(getErrorMessage(error, 'Failed to fetch gradables.')); } - - async fetchGradables(courseId: string, term: string): Promise { - try { - const url = `/api/${term}/${courseId}/gradeables`; - const response = await this.client.get(url); - return response.data; - } catch (error: unknown) { - console.error('Error fetching gradables:', error); - throw new Error(getErrorMessage(error, 'Failed to fetch gradables.')); - } + } + + /** + * Fetch grade details for a specific homework assignment + */ + async fetchGradeDetails( + term: string, + courseId: string, + gradeableId: string + ): Promise { + try { + const response = await this.client.get( + `/api/${term}/${courseId}/gradeable/${gradeableId}/values` + ); + return response.data; + } catch (error: unknown) { + console.error('Error fetching grade details:', error); + throw new Error(getErrorMessage(error, 'Failed to fetch grade details.')); } - - /** - * Fetch grade details for a specific homework assignment - */ - async fetchGradeDetails(term: string, courseId: string, gradeableId: string): Promise { - try { - const response = await this.client.get(`/api/${term}/${courseId}/gradeable/${gradeableId}/values`); - return response.data; - } catch (error: unknown) { - console.error('Error fetching grade details:', error); - throw new Error(getErrorMessage(error, 'Failed to fetch grade details.')); - } + } + + /** + * Poll fetchGradeDetails until autograding_complete is true and test_cases has data. + * @param intervalMs Delay between requests (default 2000) + * @param timeoutMs Stop after this many ms (default 300000 = 5 min); 0 = no timeout + * @returns The final AutoGraderDetails with complete data + */ + async pollGradeDetailsUntilComplete( + term: string, + courseId: string, + gradeableId: string, + options?: { + intervalMs?: number; + timeoutMs?: number; + token?: vscode.CancellationToken; } - - /** - * Poll fetchGradeDetails until autograding_complete is true and test_cases has data. - * @param intervalMs Delay between requests (default 2000) - * @param timeoutMs Stop after this many ms (default 300000 = 5 min); 0 = no timeout - * @returns The final AutoGraderDetails with complete data - */ - async pollGradeDetailsUntilComplete( - term: string, - courseId: string, - gradeableId: string, - options?: { intervalMs?: number; timeoutMs?: number; token?: vscode.CancellationToken } - ): Promise { - const intervalMs = options?.intervalMs ?? 2000; - const timeoutMs = options?.timeoutMs ?? 300000; - const token = options?.token; - const deadline = timeoutMs > 0 ? Date.now() + timeoutMs : 0; - - const isComplete = (res: AutoGraderDetails): boolean => - res?.data?.autograding_complete === true && - Array.isArray(res.data.test_cases) && - res.data.test_cases.length > 0; - - for (; ;) { - if (token?.isCancellationRequested) { - throw new Error('Cancelled'); - } - if (deadline > 0 && Date.now() >= deadline) { - throw new Error('Autograding did not complete within the timeout.'); - } - - const result = await this.fetchGradeDetails(term, courseId, gradeableId); - if (isComplete(result)) { - return result; - } - - await new Promise((r) => setTimeout(r, intervalMs)); - } + ): Promise { + const intervalMs = options?.intervalMs ?? 2000; + const timeoutMs = options?.timeoutMs ?? 300000; + const token = options?.token; + const deadline = timeoutMs > 0 ? Date.now() + timeoutMs : 0; + + const isComplete = (res: AutoGraderDetails): boolean => + res?.data?.autograding_complete === true && + Array.isArray(res.data.test_cases) && + res.data.test_cases.length > 0; + + for (;;) { + if (token?.isCancellationRequested) { + throw new Error('Cancelled'); + } + if (deadline > 0 && Date.now() >= deadline) { + throw new Error('Autograding did not complete within the timeout.'); + } + + const result = await this.fetchGradeDetails(term, courseId, gradeableId); + if (isComplete(result)) { + return result; + } + + await new Promise(r => setTimeout(r, intervalMs)); } - - async submitVCSGradable(term: string, courseId: string, gradeableId: string): Promise { - try { - // git_repo_id is literally not used, but is required by the API *ugh* - const url = `/api/${term}/${courseId}/gradeable/${gradeableId}/upload?vcs_upload=true&git_repo_id=true`; - const response = await this.client.post(url); - return response.data; - } catch (error: unknown) { - console.error('Error submitt`ing VCS gradable:', error); - throw new Error(getErrorMessage(error, 'Failed to submit VCS gradable.')); - } + } + + async submitVCSGradable( + term: string, + courseId: string, + gradeableId: string + ): Promise { + try { + // git_repo_id is literally not used, but is required by the API *ugh* + const url = `/api/${term}/${courseId}/gradeable/${gradeableId}/upload?vcs_upload=true&git_repo_id=true`; + const response = await this.client.post(url); + return response.data; + } catch (error: unknown) { + console.error('Error submitt`ing VCS gradable:', error); + throw new Error(getErrorMessage(error, 'Failed to submit VCS gradable.')); } - - - /** - * Fetch previous attempts for a specific homework assignment - */ - async fetchPreviousAttempts(term: string, courseId: string, gradeableId: string): Promise { - try { - const url = `/api/${term}/${courseId}/gradeable/${gradeableId}/attempts`; - const response = await this.client.get(url); - return response.data; - } catch (error: unknown) { - console.error('Error fetching previous attempts:', error); - throw new Error(getErrorMessage(error, 'Failed to fetch previous attempts.')); - } + } + + /** + * Fetch previous attempts for a specific homework assignment + */ + async fetchPreviousAttempts( + term: string, + courseId: string, + gradeableId: string + ): Promise { + try { + const url = `/api/${term}/${courseId}/gradeable/${gradeableId}/attempts`; + const response = await this.client.get(url); + return response.data; + } catch (error: unknown) { + console.error('Error fetching previous attempts:', error); + throw new Error( + getErrorMessage(error, 'Failed to fetch previous attempts.') + ); } - - static getInstance(context: vscode.ExtensionContext, apiBaseUrl: string): ApiService { - if (!ApiService.instance) { - ApiService.instance = new ApiService(context, apiBaseUrl); - } - return ApiService.instance; + } + + static getInstance( + context: vscode.ExtensionContext, + apiBaseUrl: string + ): ApiService { + if (!ApiService.instance) { + ApiService.instance = new ApiService(context, apiBaseUrl); } -} \ No newline at end of file + return ApiService.instance; + } +} diff --git a/src/services/authService.ts b/src/services/authService.ts index afbd445..0e03e17 100644 --- a/src/services/authService.ts +++ b/src/services/authService.ts @@ -3,177 +3,190 @@ import { ApiService } from './apiService'; import * as keytar from 'keytar'; export class AuthService { - // we need to store the token in the global state, but also store it in the - // system keychain - private context: vscode.ExtensionContext; - private apiService: ApiService; - private static instance: AuthService; - constructor(context: vscode.ExtensionContext, apiBaseUrl: string = "") { - this.context = context; - this.apiService = ApiService.getInstance(context, ""); + // we need to store the token in the global state, but also store it in the + // system keychain + private context: vscode.ExtensionContext; + private apiService: ApiService; + private static instance: AuthService; + constructor(context: vscode.ExtensionContext, apiBaseUrl: string = '') { + this.context = context; + this.apiService = ApiService.getInstance(context, ''); + } + + async initialize(): Promise { + console.log('Initializing AuthService'); + + // Get base URL from configuration + const config = vscode.workspace.getConfiguration('submitty'); + let baseUrl = config.get('baseUrl', ''); + + // If base URL is configured, set it on the API service + if (baseUrl) { + this.apiService.setBaseUrl(baseUrl); } - async initialize(): Promise { - console.log("Initializing AuthService"); - - // Get base URL from configuration - const config = vscode.workspace.getConfiguration('submitty'); - let baseUrl = config.get('baseUrl', ''); - - // If base URL is configured, set it on the API service - if (baseUrl) { - this.apiService.setBaseUrl(baseUrl); - } - - const token = await this.getToken(); - console.log("Token:", token); - if (token) { - // Token exists, set it on the API service - this.apiService.setAuthorizationToken(token); - console.log("Token set on API service"); - - // If baseUrl isn't configured yet, fetch it now so API calls work. - if (!baseUrl) { - const inputUrl = await vscode.window.showInputBox({ - prompt: 'Enter Submitty API URL', - placeHolder: 'https://example.submitty.edu', - ignoreFocusOut: true, - validateInput: (value) => { - if (!value || value.trim().length === 0) { - return 'URL is required'; - } - try { - new URL(value); - return null; - } catch { - return 'Please enter a valid URL'; - } - }, - }); - - if (!inputUrl) { - return; - } - - baseUrl = inputUrl.trim(); - - await config.update('baseUrl', baseUrl, vscode.ConfigurationTarget.Global); - this.apiService.setBaseUrl(baseUrl); - } - - return; - } - - console.log("No token found, prompting for credentials"); - - // If no base URL is configured, prompt for it - if (!baseUrl) { - const inputUrl = await vscode.window.showInputBox({ - prompt: 'Enter Submitty API URL', - placeHolder: 'https://example.submitty.edu', - ignoreFocusOut: true, - validateInput: (value) => { - if (!value || value.trim().length === 0) { - return 'URL is required'; - } - try { - new URL(value); - return null; - } catch { - return 'Please enter a valid URL'; - } - } - }); - - if (!inputUrl) { - // User cancelled - return; + const token = await this.getToken(); + console.log('Token:', token); + if (token) { + // Token exists, set it on the API service + this.apiService.setAuthorizationToken(token); + console.log('Token set on API service'); + + // If baseUrl isn't configured yet, fetch it now so API calls work. + if (!baseUrl) { + const inputUrl = await vscode.window.showInputBox({ + prompt: 'Enter Submitty API URL', + placeHolder: 'https://example.submitty.edu', + ignoreFocusOut: true, + validateInput: value => { + if (!value || value.trim().length === 0) { + return 'URL is required'; } - - baseUrl = inputUrl.trim(); - - // Save base URL to configuration - await config.update('baseUrl', baseUrl, vscode.ConfigurationTarget.Global); - - // Set the base URL on the API service - this.apiService.setBaseUrl(baseUrl); - } - - const userId = await vscode.window.showInputBox({ - prompt: 'Enter your Submitty username', - placeHolder: 'Username', - ignoreFocusOut: true, - validateInput: (value) => { - if (!value || value.trim().length === 0) { - return 'Username is required'; - } - return null; + try { + new URL(value); + return null; + } catch { + return 'Please enter a valid URL'; } + }, }); - if (!userId) { - // User cancelled - return; + if (!inputUrl) { + return; } - const password = await vscode.window.showInputBox({ - prompt: 'Enter your Submitty password', - placeHolder: 'Password', - password: true, - ignoreFocusOut: true, - validateInput: (value) => { - if (!value || value.trim().length === 0) { - return 'Password is required'; - } - return null; - } - }); - - if (!password) { - // User cancelled - return; - } + baseUrl = inputUrl.trim(); - // Update API service with URL and login - try { - // Perform login - await this.login(userId.trim(), password); + await config.update( + 'baseUrl', + baseUrl, + vscode.ConfigurationTarget.Global + ); + this.apiService.setBaseUrl(baseUrl); + } - vscode.window.showInformationMessage('Successfully logged in to Submitty'); - } catch (error: unknown) { - const err = error instanceof Error ? error.message : String(error); - vscode.window.showErrorMessage(`Login failed: ${err}`); - throw error; - } + return; } - // store token - private async storeToken(token: string): Promise { - await keytar.setPassword('submittyToken', 'submittyToken', token); + console.log('No token found, prompting for credentials'); + + // If no base URL is configured, prompt for it + if (!baseUrl) { + const inputUrl = await vscode.window.showInputBox({ + prompt: 'Enter Submitty API URL', + placeHolder: 'https://example.submitty.edu', + ignoreFocusOut: true, + validateInput: value => { + if (!value || value.trim().length === 0) { + return 'URL is required'; + } + try { + new URL(value); + return null; + } catch { + return 'Please enter a valid URL'; + } + }, + }); + + if (!inputUrl) { + // User cancelled + return; + } + + baseUrl = inputUrl.trim(); + + // Save base URL to configuration + await config.update( + 'baseUrl', + baseUrl, + vscode.ConfigurationTarget.Global + ); + + // Set the base URL on the API service + this.apiService.setBaseUrl(baseUrl); } - // get token - private async getToken(): Promise { - return await keytar.getPassword('submittyToken', 'submittyToken'); - } + const userId = await vscode.window.showInputBox({ + prompt: 'Enter your Submitty username', + placeHolder: 'Username', + ignoreFocusOut: true, + validateInput: value => { + if (!value || value.trim().length === 0) { + return 'Username is required'; + } + return null; + }, + }); - // public method to get token - async getAuthorizationToken(): Promise { - return await this.getToken(); + if (!userId) { + // User cancelled + return; } - private async login(userId: string, password: string): Promise { - const token = await this.apiService.login(userId, password); - this.apiService.setAuthorizationToken(token); - // store token in system keychain - await this.storeToken(token); - return token; + const password = await vscode.window.showInputBox({ + prompt: 'Enter your Submitty password', + placeHolder: 'Password', + password: true, + ignoreFocusOut: true, + validateInput: value => { + if (!value || value.trim().length === 0) { + return 'Password is required'; + } + return null; + }, + }); + + if (!password) { + // User cancelled + return; } - static getInstance(context: vscode.ExtensionContext, apiBaseUrl: string = ""): AuthService { - if (!AuthService.instance) { - AuthService.instance = new AuthService(context); - } - return AuthService.instance; + // Update API service with URL and login + try { + // Perform login + await this.login(userId.trim(), password); + + vscode.window.showInformationMessage( + 'Successfully logged in to Submitty' + ); + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); + vscode.window.showErrorMessage(`Login failed: ${err}`); + throw error; + } + } + + // store token + private async storeToken(token: string): Promise { + await keytar.setPassword('submittyToken', 'submittyToken', token); + } + + // get token + private async getToken(): Promise { + return await keytar.getPassword('submittyToken', 'submittyToken'); + } + + // public method to get token + async getAuthorizationToken(): Promise { + return await this.getToken(); + } + + private async login(userId: string, password: string): Promise { + const token = await this.apiService.login(userId, password); + this.apiService.setAuthorizationToken(token); + // store token in system keychain + await this.storeToken(token); + return token; + } + + static getInstance( + context: vscode.ExtensionContext, + apiBaseUrl: string = '' + ): AuthService { + if (!AuthService.instance) { + AuthService.instance = new AuthService(context); } + return AuthService.instance; + } } diff --git a/src/services/courseRepoResolver.ts b/src/services/courseRepoResolver.ts index fae5ada..07bb787 100644 --- a/src/services/courseRepoResolver.ts +++ b/src/services/courseRepoResolver.ts @@ -7,157 +7,164 @@ import { GitService } from './gitService'; import type { Course } from '../interfaces/Courses'; export interface CourseRepoContext { - term: string; - courseId: string; + term: string; + courseId: string; } function normalizeForMatch(input: string): string { - return input - .toLowerCase() - // Keep only alphanumerics so variants like "Fall 2024" vs "fall2024" match. - .replace(/[^a-z0-9]/g, ''); + return ( + input + .toLowerCase() + // Keep only alphanumerics so variants like "Fall 2024" vs "fall2024" match. + .replace(/[^a-z0-9]/g, '') + ); } function readTextFileSafe(filePath: string): string | null { - try { - return fs.readFileSync(filePath, 'utf8'); - } catch { - return null; - } + try { + return fs.readFileSync(filePath, 'utf8'); + } catch { + return null; + } } function getGitDirPath(repoRootPath: string): string | null { - const gitEntryPath = path.join(repoRootPath, '.git'); - if (!fs.existsSync(gitEntryPath)) { - return null; + const gitEntryPath = path.join(repoRootPath, '.git'); + if (!fs.existsSync(gitEntryPath)) { + return null; + } + + try { + const stat = fs.statSync(gitEntryPath); + if (stat.isDirectory()) { + return gitEntryPath; } - try { - const stat = fs.statSync(gitEntryPath); - if (stat.isDirectory()) { - return gitEntryPath; - } - - if (stat.isFile()) { - // Worktrees/linked clones can have a .git file like: "gitdir: /abs/path/to/.git/worktrees/..." - const gitFileContents = readTextFileSafe(gitEntryPath); - if (!gitFileContents) { - return null; - } - - const match = gitFileContents.match(/^\s*gitdir:\s*(.+)\s*$/m); - if (!match?.[1]) { - return null; - } - - const gitdirRaw = match[1].trim(); - return path.isAbsolute(gitdirRaw) ? gitdirRaw : path.resolve(repoRootPath, gitdirRaw); - } - } catch { + if (stat.isFile()) { + // Worktrees/linked clones can have a .git file like: "gitdir: /abs/path/to/.git/worktrees/..." + const gitFileContents = readTextFileSafe(gitEntryPath); + if (!gitFileContents) { return null; - } + } + + const match = gitFileContents.match(/^\s*gitdir:\s*(.+)\s*$/m); + if (!match?.[1]) { + return null; + } + const gitdirRaw = match[1].trim(); + return path.isAbsolute(gitdirRaw) + ? gitdirRaw + : path.resolve(repoRootPath, gitdirRaw); + } + } catch { return null; + } + + return null; } function extractGitRemoteUrlsFromConfig(gitConfigText: string): string[] { - const urls: string[] = []; - - // Example: - // [remote "origin"] - // url = https://example/.../term/courseId/... - const urlRegex = /^\s*url\s*=\s*(.+)\s*$/gim; - let match: RegExpExecArray | null = null; - // eslint-disable-next-line no-cond-assign - while ((match = urlRegex.exec(gitConfigText))) { - const rawUrl = match[1]?.trim(); - if (rawUrl) { - urls.push(rawUrl); - } + const urls: string[] = []; + + // Example: + // [remote "origin"] + // url = https://example/.../term/courseId/... + const urlRegex = /^\s*url\s*=\s*(.+)\s*$/gim; + let match: RegExpExecArray | null = null; + + while ((match = urlRegex.exec(gitConfigText))) { + const rawUrl = match[1]?.trim(); + if (rawUrl) { + urls.push(rawUrl); } + } - return urls; + return urls; } export class CourseRepoResolver { - constructor( - private readonly apiService: ApiService, - private readonly authService: AuthService, - private readonly gitService: GitService - ) {} - - async resolveCourseContextFromRepo(): Promise { - const repo = this.gitService.getRepository(); - if (!repo) { - return null; - } - - const repoRootPath = repo.rootUri.fsPath; - const gitDirPath = getGitDirPath(repoRootPath); - if (!gitDirPath) { - return null; - } - - const gitConfigText = readTextFileSafe(path.join(gitDirPath, 'config')); - if (!gitConfigText) { - return null; - } - - const remoteUrls = extractGitRemoteUrlsFromConfig(gitConfigText); - if (remoteUrls.length === 0) { - return null; - } - - const token = await this.authService.getAuthorizationToken(); - if (!token) { - // No auth token -> can't map remotes to courses via API. - return null; - } - - const baseUrl = vscode.workspace.getConfiguration('submitty').get('baseUrl', ''); - if (!baseUrl) { - // Without baseUrl, we can't call the API. - return null; - } - - this.apiService.setBaseUrl(baseUrl); - this.apiService.setAuthorizationToken(token); - - // Fetch courses and match based on whether their (term, courseId) strings appear in remote URLs. - const coursesResponse = await this.apiService.fetchCourses(token); - const courses = coursesResponse.data.unarchived_courses; - - const remoteText = remoteUrls.join(' '); - const remoteNorm = normalizeForMatch(remoteText); - - let best: { course: Course; score: number } | null = null; - - for (const course of courses) { - const courseIdNorm = normalizeForMatch(course.title); - const termNorm = normalizeForMatch(course.semester); - - let score = 0; - - if (remoteNorm.includes(courseIdNorm)) { - score += 6; - } - if (remoteNorm.includes(termNorm)) { - score += 3; - } - if (remoteText.toLowerCase().includes(course.display_name.toLowerCase())) { - score += 1; - } - - if (!best || score > best.score) { - best = { course, score }; - } - } - - if (!best || best.score < 6) { - return null; - } - - return { term: best.course.semester, courseId: best.course.title }; + constructor( + private readonly apiService: ApiService, + private readonly authService: AuthService, + private readonly gitService: GitService + ) {} + + async resolveCourseContextFromRepo(): Promise { + const repo = this.gitService.getRepository(); + if (!repo) { + return null; } -} + const repoRootPath = repo.rootUri.fsPath; + const gitDirPath = getGitDirPath(repoRootPath); + if (!gitDirPath) { + return null; + } + + const gitConfigText = readTextFileSafe(path.join(gitDirPath, 'config')); + if (!gitConfigText) { + return null; + } + + const remoteUrls = extractGitRemoteUrlsFromConfig(gitConfigText); + if (remoteUrls.length === 0) { + return null; + } + + const token = await this.authService.getAuthorizationToken(); + if (!token) { + // No auth token -> can't map remotes to courses via API. + return null; + } + + const baseUrl = vscode.workspace + .getConfiguration('submitty') + .get('baseUrl', ''); + if (!baseUrl) { + // Without baseUrl, we can't call the API. + return null; + } + + this.apiService.setBaseUrl(baseUrl); + this.apiService.setAuthorizationToken(token); + + // Fetch courses and match based on whether their (term, courseId) strings appear in remote URLs. + const coursesResponse = await this.apiService.fetchCourses(token); + const courses = coursesResponse.data.unarchived_courses; + + const remoteText = remoteUrls.join(' '); + const remoteNorm = normalizeForMatch(remoteText); + + let best: { course: Course; score: number } | null = null; + + for (const course of courses) { + const courseIdNorm = normalizeForMatch(course.title); + const termNorm = normalizeForMatch(course.semester); + + let score = 0; + + if (remoteNorm.includes(courseIdNorm)) { + score += 6; + } + if (remoteNorm.includes(termNorm)) { + score += 3; + } + if ( + remoteText.toLowerCase().includes(course.display_name.toLowerCase()) + ) { + score += 1; + } + + if (!best || score > best.score) { + best = { course, score }; + } + } + + if (!best || best.score < 6) { + return null; + } + + return { term: best.course.semester, courseId: best.course.title }; + } +} diff --git a/src/services/gitService.ts b/src/services/gitService.ts index 4e4c889..c32ef19 100644 --- a/src/services/gitService.ts +++ b/src/services/gitService.ts @@ -1,10 +1,10 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-redundant-type-constituents */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ import * as vscode from 'vscode'; -import type { GitExtension, Repository, CommitOptions, ForcePushMode } from '../typings/vscode-git'; +import type { + GitExtension, + Repository, + CommitOptions, + ForcePushMode, +} from '../typings/vscode-git'; import { API } from '../typings/vscode-git'; /** @@ -12,95 +12,103 @@ import { API } from '../typings/vscode-git'; * push, pull, and commit in the current workspace repository. */ export class GitService { - private gitApi: API | null = null; + private gitApi: API | null = null; - private getApi(): API | null { - if (this.gitApi !== null) { - return this.gitApi; - } - const ext = vscode.extensions.getExtension('vscode.git'); - if (!ext?.isActive) { - return null; - } - try { - this.gitApi = ext.exports.getAPI(1); - return this.gitApi; - } catch { - return null; - } + private getApi(): API | null { + if (this.gitApi !== null) { + return this.gitApi; } - - /** - * Get the Git repository for the given URI, or the first workspace folder. - */ - getRepository(uri?: vscode.Uri): Repository | null { - const api = this.getApi(); - if (!api) { - return null; - } - if (uri) { - return api.getRepository(uri); - } - const folder = vscode.workspace.workspaceFolders?.[0]; - if (!folder) { - return api.repositories.length > 0 ? api.repositories[0] : null; - } - return api.getRepository(folder.uri) ?? api.repositories[0]; + const ext = vscode.extensions.getExtension('vscode.git'); + if (!ext?.isActive) { + return null; } + try { + this.gitApi = ext.exports.getAPI(1); + return this.gitApi; + } catch { + return null; + } + } - /** - * Commit changes in the repository. Optionally stage all changes first. - */ - async commit(message: string, options?: CommitOptions): Promise { - const repo = this.getRepository(); - if (!repo) { - throw new Error('No Git repository found. Open a workspace folder that is a Git repo.'); - } - - // check to see if there are any changes to commit - const status = (await repo.status()) as unknown as { - modified: unknown[]; - untracked: unknown[]; - deleted: unknown[]; - }; + /** + * Get the Git repository for the given URI, or the first workspace folder. + */ + getRepository(uri?: vscode.Uri): Repository | null { + const api = this.getApi(); + if (!api) { + return null; + } + if (uri) { + return api.getRepository(uri); + } + const folder = vscode.workspace.workspaceFolders?.[0]; + if (!folder) { + return api.repositories.length > 0 ? api.repositories[0] : null; + } + return api.getRepository(folder.uri) ?? api.repositories[0]; + } - if (status.modified.length === 0 && status.untracked.length === 0 && status.deleted.length === 0) { - throw new Error('No changes to commit.'); - } - await repo.commit(message, options); + /** + * Commit changes in the repository. Optionally stage all changes first. + */ + async commit(message: string, options?: CommitOptions): Promise { + const repo = this.getRepository(); + if (!repo) { + throw new Error( + 'No Git repository found. Open a workspace folder that is a Git repo.' + ); + } + // check to see if there are any changes to commit + const status = (await repo.status()) as unknown as { + modified: unknown[]; + untracked: unknown[]; + deleted: unknown[]; + }; + if ( + status.modified.length === 0 && + status.untracked.length === 0 && + status.deleted.length === 0 + ) { + throw new Error('No changes to commit.'); } + await repo.commit(message, options); + } - /** - * Pull from the current branch's upstream. - */ - async pull(): Promise { - const repo = this.getRepository(); - if (!repo) { - throw new Error('No Git repository found. Open a workspace folder that is a Git repo.'); - } - await repo.pull(); + /** + * Pull from the current branch's upstream. + */ + async pull(): Promise { + const repo = this.getRepository(); + if (!repo) { + throw new Error( + 'No Git repository found. Open a workspace folder that is a Git repo.' + ); } + await repo.pull(); + } - /** - * Push the current branch. Optionally set upstream or force push. - */ - async push(options?: { - remote?: string; - branch?: string; - setUpstream?: boolean; - force?: ForcePushMode; - }): Promise { - const repo = this.getRepository(); - if (!repo) { - throw new Error('No Git repository found. Open a workspace folder that is a Git repo.'); - } - await repo.push( - options?.remote, - options?.branch, - options?.setUpstream, - options?.force - ); + /** + * Push the current branch. Optionally set upstream or force push. + */ + async push(options?: { + remote?: string; + branch?: string; + setUpstream?: boolean; + force?: ForcePushMode; + }): Promise { + const repo = this.getRepository(); + if (!repo) { + throw new Error( + 'No Git repository found. Open a workspace folder that is a Git repo.' + ); } + await repo.push( + options?.remote, + options?.branch, + options?.setUpstream, + options?.force + ); + } } diff --git a/src/services/testingService.ts b/src/services/testingService.ts index a2bb026..d7b9b5d 100644 --- a/src/services/testingService.ts +++ b/src/services/testingService.ts @@ -1,6 +1,11 @@ import * as vscode from 'vscode'; import { ApiService } from './apiService'; -import type { AutoGraderDetails, AutoGraderDetailsData, TestCase, Autocheck } from '../interfaces/AutoGraderDetails'; +import type { + AutoGraderDetails, + AutoGraderDetailsData, + TestCase, + Autocheck, +} from '../interfaces/AutoGraderDetails'; const CONTROLLER_ID = 'submittyAutograder'; const CONTROLLER_LABEL = 'Submitty Autograder'; @@ -9,253 +14,304 @@ const POLL_INTERVAL_MS = 2000; const POLL_TIMEOUT_MS = 300000; // 5 min interface GradeableMeta { - term: string; - courseId: string; - gradeableId: string; + term: string; + courseId: string; + gradeableId: string; } export class TestingService { - private controller: vscode.TestController; - private rootItem: vscode.TestItem; - private gradeableMeta = new WeakMap(); - private testCaseMeta = new WeakMap(); + private controller: vscode.TestController; + private rootItem: vscode.TestItem; + private gradeableMeta = new WeakMap(); + private testCaseMeta = new WeakMap(); - constructor( - private readonly context: vscode.ExtensionContext, - private readonly apiService: ApiService - ) { - this.controller = vscode.tests.createTestController(CONTROLLER_ID, CONTROLLER_LABEL); - this.rootItem = this.controller.createTestItem(ROOT_ID, 'Submitty', undefined); - this.rootItem.canResolveChildren = true; - this.controller.items.add(this.rootItem); + constructor( + private readonly context: vscode.ExtensionContext, + private readonly apiService: ApiService + ) { + this.controller = vscode.tests.createTestController( + CONTROLLER_ID, + CONTROLLER_LABEL + ); + this.rootItem = this.controller.createTestItem( + ROOT_ID, + 'Submitty', + undefined + ); + this.rootItem.canResolveChildren = true; + this.controller.items.add(this.rootItem); - this.controller.resolveHandler = async (item) => this.resolveHandler(item); - const runProfile = this.controller.createRunProfile( - 'Run', - vscode.TestRunProfileKind.Run, - (request, token) => this.runHandler(request, token) - ); - runProfile.isDefault = true; + this.controller.resolveHandler = async item => this.resolveHandler(item); + const runProfile = this.controller.createRunProfile( + 'Run', + vscode.TestRunProfileKind.Run, + (request, token) => this.runHandler(request, token) + ); + runProfile.isDefault = true; - context.subscriptions.push(this.controller); - } + context.subscriptions.push(this.controller); + } - /** - * Add a gradeable to the Test Explorer so the user can run it and see results. - * Call this when the user triggers "Grade" or "Run autograder" for a gradeable. - */ - addGradeable(term: string, courseId: string, gradeableId: string, label: string): vscode.TestItem { - const id = `${term}/${courseId}/${gradeableId}`; - let item = this.rootItem.children.get(id); - if (!item) { - item = this.controller.createTestItem(id, label, undefined); - item.canResolveChildren = true; - this.gradeableMeta.set(item, { term, courseId, gradeableId }); - this.rootItem.children.add(item); - } - return item; + /** + * Add a gradeable to the Test Explorer so the user can run it and see results. + * Call this when the user triggers "Grade" or "Run autograder" for a gradeable. + */ + addGradeable( + term: string, + courseId: string, + gradeableId: string, + label: string + ): vscode.TestItem { + const id = `${term}/${courseId}/${gradeableId}`; + let item = this.rootItem.children.get(id); + if (!item) { + item = this.controller.createTestItem(id, label, undefined); + item.canResolveChildren = true; + this.gradeableMeta.set(item, { term, courseId, gradeableId }); + this.rootItem.children.add(item); } + return item; + } - /** - * Run a single gradeable in the Test Explorer using an already-fetched autograder result. - * Used when the user clicks "Grade" in the sidebar: submit → poll → then report here. - */ - runGradeableWithResult(term: string, courseId: string, gradeableId: string, label: string, result: AutoGraderDetails): void { - const item = this.addGradeable(term, courseId, gradeableId, label); - this.syncTestCaseChildren(item, result.data); + /** + * Run a single gradeable in the Test Explorer using an already-fetched autograder result. + * Used when the user clicks "Grade" in the sidebar: submit → poll → then report here. + */ + runGradeableWithResult( + term: string, + courseId: string, + gradeableId: string, + label: string, + result: AutoGraderDetails + ): void { + const item = this.addGradeable(term, courseId, gradeableId, label); + this.syncTestCaseChildren(item, result.data); - const run = this.controller.createTestRun(new vscode.TestRunRequest([item])); - run.started(item); - run.appendOutput(`Autograder completed for ${item.label}.\r\n`); - this.reportGradeableResult(run, item, result.data); - run.end(); + const run = this.controller.createTestRun( + new vscode.TestRunRequest([item]) + ); + run.started(item); + run.appendOutput(`Autograder completed for ${item.label}.\r\n`); + this.reportGradeableResult(run, item, result.data); + run.end(); + } + + private getGradeableMeta(item: vscode.TestItem): GradeableMeta | undefined { + return this.gradeableMeta.get(item); + } + + /** + * Convert HTML from autograder actual/expected into plain text for the Test Explorer diff. + * Strips tags and decodes common entities so the diff view is readable. + */ + private stripHtml(html: string): string { + if (!html || typeof html !== 'string') { + return ''; } + const text = html + .replace(//gi, '\n') + .replace(/<\/div>/gi, '\n') + .replace(/<\/p>/gi, '\n') + .replace(/<[^>]+>/g, '') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/ /g, ' '); + return text.replace(/\n{3,}/g, '\n\n').trim(); + } - private getGradeableMeta(item: vscode.TestItem): GradeableMeta | undefined { - return this.gradeableMeta.get(item); + private formatAutocheckOutput( + autochecks: Autocheck[] | undefined, + getValue: (ac: Autocheck) => string + ): string { + if (!autochecks?.length) { + return ''; } + const parts = autochecks.map(ac => { + const value = this.stripHtml(getValue(ac)); + if (!value) { + return ''; + } + return `[${ac.description}]\n${value}`; + }); + return parts.filter(Boolean).join('\n\n'); + } - /** - * Convert HTML from autograder actual/expected into plain text for the Test Explorer diff. - * Strips tags and decodes common entities so the diff view is readable. - */ - private stripHtml(html: string): string { - if (!html || typeof html !== 'string') { - return ''; - } - const text = html - .replace(//gi, '\n') - .replace(/<\/div>/gi, '\n') - .replace(/<\/p>/gi, '\n') - .replace(/<[^>]+>/g, '') - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/&/g, '&') - .replace(/"/g, '"') - .replace(/'/g, "'") - .replace(/ /g, ' '); - return text.replace(/\n{3,}/g, '\n\n').trim(); + /** + * Format the messages array from all autochecks (e.g. "ERROR: ..." with type failure/warning). + */ + private formatAutocheckMessages(autochecks: Autocheck[] | undefined): string { + if (!autochecks?.length) { + return ''; } + const parts = autochecks.map(ac => { + const msgLines = (ac.messages ?? []).map( + m => ` • ${m.message}${m.type ? ` (${m.type})` : ''}` + ); + if (msgLines.length === 0) { + return ''; + } + return `[${ac.description}]\n${msgLines.join('\n')}`; + }); + return parts.filter(Boolean).join('\n\n'); + } - private formatAutocheckOutput(autochecks: Autocheck[] | undefined, getValue: (ac: Autocheck) => string): string { - if (!autochecks?.length) { - return ''; - } - const parts = autochecks.map((ac) => { - const value = this.stripHtml(getValue(ac)); - if (!value) { - return ''; - } - return `[${ac.description}]\n${value}`; - }); - return parts.filter(Boolean).join('\n\n'); + private async resolveHandler( + item: vscode.TestItem | undefined + ): Promise { + if (!item) { + return; + } + const meta = this.getGradeableMeta(item); + if (!meta) { + return; + } + // Resolve: poll until complete and populate children (test cases) + try { + const result = await this.apiService.pollGradeDetailsUntilComplete( + meta.term, + meta.courseId, + meta.gradeableId, + { intervalMs: POLL_INTERVAL_MS, timeoutMs: POLL_TIMEOUT_MS } + ); + this.syncTestCaseChildren(item, result.data); + } catch (e) { + console.error('Submitty testing resolve failed:', e); } + } - /** - * Format the messages array from all autochecks (e.g. "ERROR: ..." with type failure/warning). - */ - private formatAutocheckMessages(autochecks: Autocheck[] | undefined): string { - if (!autochecks?.length) { - return ''; - } - const parts = autochecks.map((ac) => { - const msgLines = (ac.messages ?? []).map((m) => ` • ${m.message}${m.type ? ` (${m.type})` : ''}`); - if (msgLines.length === 0) { - return ''; - } - return `[${ac.description}]\n${msgLines.join('\n')}`; - }); - return parts.filter(Boolean).join('\n\n'); + private syncTestCaseChildren( + gradeableItem: vscode.TestItem, + data: AutoGraderDetailsData + ): void { + const cases = data.test_cases ?? []; + for (let i = 0; i < cases.length; i++) { + const tc = cases[i]; + const id = `tc-${i}-${tc.name ?? i}`; + let child = gradeableItem.children.get(id); + if (!child) { + child = this.controller.createTestItem( + id, + tc.name || `Test ${i + 1}`, + undefined + ); + this.testCaseMeta.set(child, tc); + gradeableItem.children.add(child); + } else { + this.testCaseMeta.set(child, tc); + } } + } - private async resolveHandler(item: vscode.TestItem | undefined): Promise { - if (!item) { - return; + private reportGradeableResult( + run: vscode.TestRun, + item: vscode.TestItem, + _data: AutoGraderDetailsData + ): void { + const start = Date.now(); + let allPassed = true; + item.children.forEach(child => { + const tc = this.testCaseMeta.get(child); + run.started(child); + if (tc) { + const passed = tc.points_received >= (tc.points_available ?? 0); + if (!passed) { + allPassed = false; } - const meta = this.getGradeableMeta(item); - if (!meta) { - return; + const duration = Date.now() - start; + const messageParts = [tc.testcase_message, tc.details].filter(Boolean); + const formattedMessages = this.formatAutocheckMessages(tc.autochecks); + if (formattedMessages) { + messageParts.push('--- Messages ---', formattedMessages); } - // Resolve: poll until complete and populate children (test cases) - try { - const result = await this.apiService.pollGradeDetailsUntilComplete( - meta.term, - meta.courseId, - meta.gradeableId, - { intervalMs: POLL_INTERVAL_MS, timeoutMs: POLL_TIMEOUT_MS } - ); - this.syncTestCaseChildren(item, result.data); - } catch (e) { - console.error('Submitty testing resolve failed:', e); + const messageText = messageParts.join('\n') || 'Failed'; + if (passed) { + run.passed(child, duration); + } else { + const msg = new vscode.TestMessage(messageText); + msg.expectedOutput = this.formatAutocheckOutput( + tc.autochecks, + ac => ac.expected + ); + msg.actualOutput = this.formatAutocheckOutput( + tc.autochecks, + ac => ac.actual + ); + run.failed(child, msg, duration); } - } + } else { + run.passed(child, 0); + } + }); - private syncTestCaseChildren(gradeableItem: vscode.TestItem, data: AutoGraderDetailsData): void { - const cases = data.test_cases ?? []; - for (let i = 0; i < cases.length; i++) { - const tc = cases[i]; - const id = `tc-${i}-${tc.name ?? i}`; - let child = gradeableItem.children.get(id); - if (!child) { - child = this.controller.createTestItem(id, tc.name || `Test ${i + 1}`, undefined); - this.testCaseMeta.set(child, tc); - gradeableItem.children.add(child); - } else { - this.testCaseMeta.set(child, tc); - } - } + if (item.children.size === 0) { + run.appendOutput(`No test cases in response.\r\n`); + run.failed(item, new vscode.TestMessage('No test cases returned.'), 0); + } else { + if (allPassed) { + run.passed(item, Date.now() - start); + } else { + run.failed( + item, + new vscode.TestMessage('Some test cases failed.'), + Date.now() - start + ); + } } + } - private reportGradeableResult(run: vscode.TestRun, item: vscode.TestItem, _data: AutoGraderDetailsData): void { - const start = Date.now(); - let allPassed = true; - item.children.forEach((child) => { - const tc = this.testCaseMeta.get(child); - run.started(child); - if (tc) { - const passed = tc.points_received >= (tc.points_available ?? 0); - if (!passed) { - allPassed = false; - } - const duration = Date.now() - start; - const messageParts = [tc.testcase_message, tc.details].filter(Boolean); - const formattedMessages = this.formatAutocheckMessages(tc.autochecks); - if (formattedMessages) { - messageParts.push('--- Messages ---', formattedMessages); - } - const messageText = messageParts.join('\n') || 'Failed'; - if (passed) { - run.passed(child, duration); - } else { - const msg = new vscode.TestMessage(messageText); - msg.expectedOutput = this.formatAutocheckOutput(tc.autochecks, (ac) => ac.expected); - msg.actualOutput = this.formatAutocheckOutput(tc.autochecks, (ac) => ac.actual); - run.failed(child, msg, duration); - } - } else { - run.passed(child, 0); - } - }); + private async runHandler( + request: vscode.TestRunRequest, + token: vscode.CancellationToken + ): Promise { + const run = this.controller.createTestRun(request); + const queue: vscode.TestItem[] = []; - if (item.children.size === 0) { - run.appendOutput(`No test cases in response.\r\n`); - run.failed(item, new vscode.TestMessage('No test cases returned.'), 0); + if (request.include) { + request.include.forEach(t => { + if (t.id === ROOT_ID) { + this.rootItem.children.forEach(c => queue.push(c)); } else { - if (allPassed) { - run.passed(item, Date.now() - start); - } else { - run.failed(item, new vscode.TestMessage('Some test cases failed.'), Date.now() - start); - } + queue.push(t); } + }); + } else { + this.rootItem.children.forEach(t => queue.push(t)); } - private async runHandler(request: vscode.TestRunRequest, token: vscode.CancellationToken): Promise { - const run = this.controller.createTestRun(request); - const queue: vscode.TestItem[] = []; - - if (request.include) { - request.include.forEach((t) => { - if (t.id === ROOT_ID) { - this.rootItem.children.forEach((c) => queue.push(c)); - } else { - queue.push(t); - } - }); - } else { - this.rootItem.children.forEach((t) => queue.push(t)); - } + while (queue.length > 0 && !token.isCancellationRequested) { + const item = queue.shift()!; + if (request.exclude?.includes(item)) { + continue; + } - while (queue.length > 0 && !token.isCancellationRequested) { - const item = queue.shift()!; - if (request.exclude?.includes(item)) { - continue; - } + const meta = this.getGradeableMeta(item); + if (!meta) { + continue; + } - const meta = this.getGradeableMeta(item); - if (!meta) { - continue; - } + run.started(item); + run.appendOutput(`Polling grade details for ${item.label}...\r\n`); - run.started(item); - run.appendOutput(`Polling grade details for ${item.label}...\r\n`); - - try { - const result = await this.apiService.pollGradeDetailsUntilComplete( - meta.term, - meta.courseId, - meta.gradeableId, - { intervalMs: POLL_INTERVAL_MS, timeoutMs: POLL_TIMEOUT_MS, token } - ); - const data = result.data; - this.syncTestCaseChildren(item, data); - this.reportGradeableResult(run, item, data); - } catch (e) { - const err = e instanceof Error ? e.message : String(e); - run.appendOutput(`Error: ${err}\r\n`); - run.failed(item, new vscode.TestMessage(err), 0); - } - } - - run.end(); + try { + const result = await this.apiService.pollGradeDetailsUntilComplete( + meta.term, + meta.courseId, + meta.gradeableId, + { intervalMs: POLL_INTERVAL_MS, timeoutMs: POLL_TIMEOUT_MS, token } + ); + const data = result.data; + this.syncTestCaseChildren(item, data); + this.reportGradeableResult(run, item, data); + } catch (e) { + const err = e instanceof Error ? e.message : String(e); + run.appendOutput(`Error: ${err}\r\n`); + run.failed(item, new vscode.TestMessage(err), 0); + } } + + run.end(); + } } diff --git a/src/sidebar/login.html b/src/sidebar/login.html index 6308db9..b466d15 100644 --- a/src/sidebar/login.html +++ b/src/sidebar/login.html @@ -1,84 +1,84 @@ - + - - + + - - + +

Submitty Login

- - + +
- - + +
- - + +
- - \ No newline at end of file + + diff --git a/src/sidebarContent.ts b/src/sidebarContent.ts index ff9de23..a16a84c 100644 --- a/src/sidebarContent.ts +++ b/src/sidebarContent.ts @@ -3,11 +3,21 @@ import * as path from 'path'; import * as fs from 'fs'; export function getLoginHtml(context: vscode.ExtensionContext): string { - const filePath = path.join(context.extensionPath, 'src', 'sidebar', 'login.html'); - return fs.readFileSync(filePath, 'utf8'); + const filePath = path.join( + context.extensionPath, + 'src', + 'sidebar', + 'login.html' + ); + return fs.readFileSync(filePath, 'utf8'); } export function getClassesHtml(context: vscode.ExtensionContext): string { - const filePath = path.join(context.extensionPath, 'src', 'sidebar', 'classes.html'); - return fs.readFileSync(filePath, 'utf8'); -} \ No newline at end of file + const filePath = path.join( + context.extensionPath, + 'src', + 'sidebar', + 'classes.html' + ); + return fs.readFileSync(filePath, 'utf8'); +} diff --git a/src/sidebarProvider.ts b/src/sidebarProvider.ts index 3665694..fdeb712 100644 --- a/src/sidebarProvider.ts +++ b/src/sidebarProvider.ts @@ -9,258 +9,311 @@ import { TestingService } from './services/testingService'; import { MessageCommand } from './typings/message'; export class SidebarProvider implements vscode.WebviewViewProvider { - private _view?: vscode.WebviewView; - private apiService: ApiService; - private authService: AuthService; - private isInitialized: boolean = false; - private visibilityDisposable?: vscode.Disposable; - private isLoadingCourses: boolean = false; + private _view?: vscode.WebviewView; + private apiService: ApiService; + private authService: AuthService; + private isInitialized: boolean = false; + private visibilityDisposable?: vscode.Disposable; + private isLoadingCourses: boolean = false; - constructor( - private readonly context: vscode.ExtensionContext, - private readonly testingService?: TestingService, - private readonly gitService?: GitService - ) { - this.apiService = ApiService.getInstance(this.context, ""); - this.authService = AuthService.getInstance(this.context); - } + constructor( + private readonly context: vscode.ExtensionContext, + private readonly testingService?: TestingService, + private readonly gitService?: GitService + ) { + this.apiService = ApiService.getInstance(this.context, ''); + this.authService = AuthService.getInstance(this.context); + } - async resolveWebviewView( - webviewView: vscode.WebviewView, - _context: vscode.WebviewViewResolveContext, - _token: vscode.CancellationToken - ): Promise { - this._view = webviewView; + async resolveWebviewView( + webviewView: vscode.WebviewView, + _context: vscode.WebviewViewResolveContext, + _token: vscode.CancellationToken + ): Promise { + this._view = webviewView; - webviewView.webview.options = { - enableScripts: true, - localResourceRoots: [vscode.Uri.joinPath(this.context.extensionUri, 'src', 'webview')], - }; + webviewView.webview.options = { + enableScripts: true, + localResourceRoots: [ + vscode.Uri.joinPath(this.context.extensionUri, 'src', 'webview'), + ], + }; - // Initially show blank screen - webviewView.webview.html = this.getBlankHtml(); + // Initially show blank screen + webviewView.webview.html = this.getBlankHtml(); - // Reload courses any time the view becomes visible again (e.g. user - // closes/hides the panel and comes back). - this.visibilityDisposable?.dispose(); - this.visibilityDisposable = webviewView.onDidChangeVisibility(async () => { - if (webviewView.visible) { - await this.loadCourses(); - } - }); + // Reload courses any time the view becomes visible again (e.g. user + // closes/hides the panel and comes back). + this.visibilityDisposable?.dispose(); + this.visibilityDisposable = webviewView.onDidChangeVisibility(async () => { + if (webviewView.visible) { + await this.loadCourses(); + } + }); - // Initialize authentication when sidebar is opened (only once) - if (!this.isInitialized) { - this.isInitialized = true; - try { - await this.authService.initialize(); + // Initialize authentication when sidebar is opened (only once) + if (!this.isInitialized) { + this.isInitialized = true; + try { + await this.authService.initialize(); - // After authentication, fetch and display courses - await this.loadCourses(); - } catch (error: any) { - console.error('Authentication initialization failed:', error); - // Error is already shown to user in authService - } - } else { - // If already initialized, just load courses - await this.loadCourses(); - } + // After authentication, fetch and display courses + await this.loadCourses(); + } catch (error: any) { + console.error('Authentication initialization failed:', error); + // Error is already shown to user in authService + } + } else { + // If already initialized, just load courses + await this.loadCourses(); + } + + // Handle messages from the webview + webviewView.webview.onDidReceiveMessage( + async message => { + await this.handleMessage(message, webviewView); + }, + undefined, + this.context.subscriptions + ); + } - // Handle messages from the webview - webviewView.webview.onDidReceiveMessage( - async (message) => { - await this.handleMessage(message, webviewView); - }, - undefined, - this.context.subscriptions - ); + private async loadCourses(): Promise { + if (!this._view) { + return; } - private async loadCourses(): Promise { - if (!this._view) { - return; - } + if (this.isLoadingCourses) { + return; + } - if (this.isLoadingCourses) { - return; - } + this.isLoadingCourses = true; + try { + const token = await this.authService.getAuthorizationToken(); + if (!token) { + return; + } - this.isLoadingCourses = true; - try { - const token = await this.authService.getAuthorizationToken(); - if (!token) { - return; - } + // Show classes HTML + this._view.webview.html = getClassesHtml(this.context); - // Show classes HTML - this._view.webview.html = getClassesHtml(this.context); + // Fetch and display courses + await this.fetchAndDisplayCourses(token, this._view); + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); + console.error('Failed to load courses:', error); + vscode.window.showErrorMessage(`Failed to load courses: ${err}`); + } finally { + this.isLoadingCourses = false; + } + } - // Fetch and display courses - await this.fetchAndDisplayCourses(token, this._view); - } catch (error: unknown) { - const err = error instanceof Error ? error.message : String(error); - console.error('Failed to load courses:', error); - vscode.window.showErrorMessage(`Failed to load courses: ${err}`); - } finally { - this.isLoadingCourses = false; - } + private async handleMessage( + message: unknown, + view: vscode.WebviewView + ): Promise { + console.log('handleMessage', message); + if (!message || typeof message !== 'object') { + return; + } + const msg = message as { command?: unknown; data?: unknown }; + if (typeof msg.command !== 'string') { + return; } - private async handleMessage(message: unknown, view: vscode.WebviewView): Promise { - console.log('handleMessage', message); - if (!message || typeof message !== 'object') { - return; - } - const msg = message as { command?: unknown; data?: unknown }; - if (typeof msg.command !== 'string') { - return; + switch (msg.command) { + case MessageCommand.FETCH_AND_DISPLAY_COURSES: + try { + const token = await this.authService.getAuthorizationToken(); + if (token) { + await this.fetchAndDisplayCourses(token, view); + } + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); + console.error('Failed to fetch and display courses:', error); + view.webview.postMessage({ + command: MessageCommand.ERROR, + data: { message: `Failed to fetch and display courses: ${err}` }, + }); } + break; + case MessageCommand.GRADE: + try { + const data = msg.data; + if (!data || typeof data !== 'object') { + throw new Error('Missing grade payload.'); + } + const dataObj = data as Record; + const term = typeof dataObj.term === 'string' ? dataObj.term : null; + const courseId = + typeof dataObj.courseId === 'string' ? dataObj.courseId : null; + const gradeableId = + typeof dataObj.gradeableId === 'string' + ? dataObj.gradeableId + : null; - switch (msg.command) { - case MessageCommand.FETCH_AND_DISPLAY_COURSES: - try { - const token = await this.authService.getAuthorizationToken(); - if (token) { - await this.fetchAndDisplayCourses(token, view); - } - } catch (error: unknown) { - const err = error instanceof Error ? error.message : String(error); - console.error('Failed to fetch and display courses:', error); - view.webview.postMessage({ - command: MessageCommand.ERROR, - data: { message: `Failed to fetch and display courses: ${err}` }, - }); - } - break; - case MessageCommand.GRADE: - try { - const data = msg.data; - if (!data || typeof data !== 'object') { - throw new Error('Missing grade payload.'); - } - const dataObj = data as Record; - const term = typeof dataObj.term === 'string' ? dataObj.term : null; - const courseId = typeof dataObj.courseId === 'string' ? dataObj.courseId : null; - const gradeableId = typeof dataObj.gradeableId === 'string' ? dataObj.gradeableId : null; - - if (!term || !courseId || !gradeableId) { - throw new Error('Invalid grade payload.'); - } - console.log('handleGrade', term, courseId, gradeableId); - await this.handleGrade(term, courseId, gradeableId, view); - } catch (error: unknown) { - const err = error instanceof Error ? error.message : String(error); - console.error('Failed to grade:', error); - view.webview.postMessage({ - command: MessageCommand.ERROR, - data: { message: `Failed to grade: ${err}` }, - }); - } - break; - default: - vscode.window.showWarningMessage(`Unknown command: ${msg.command}`); - view.webview.postMessage({ - command: MessageCommand.ERROR, - data: { message: `Unknown command: ${msg.command}` }, - }); - break; + if (!term || !courseId || !gradeableId) { + throw new Error('Invalid grade payload.'); + } + console.log('handleGrade', term, courseId, gradeableId); + await this.handleGrade(term, courseId, gradeableId, view); + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); + console.error('Failed to grade:', error); + view.webview.postMessage({ + command: MessageCommand.ERROR, + data: { message: `Failed to grade: ${err}` }, + }); } + break; + default: + vscode.window.showWarningMessage(`Unknown command: ${msg.command}`); + view.webview.postMessage({ + command: MessageCommand.ERROR, + data: { message: `Unknown command: ${msg.command}` }, + }); + break; } - private async fetchAndDisplayCourses(token: string, view: vscode.WebviewView): Promise { - try { - const courses = await this.apiService.fetchCourses(token); - const unarchived = courses.data.unarchived_courses; + } + private async fetchAndDisplayCourses( + token: string, + view: vscode.WebviewView + ): Promise { + try { + const courses = await this.apiService.fetchCourses(token); + const unarchived = courses.data.unarchived_courses; - const coursesWithGradables = await Promise.all( - unarchived.map(async (course) => { - let gradables: { id: string; title: string }[] = []; - try { - const gradableResponse = await this.apiService.fetchGradables(course.title, course.semester); - gradables = Object.values(gradableResponse.data || {}).map((g: Gradable) => ({ id: g.id, title: g.title || g.id })); - } catch (e) { - console.warn(`Failed to fetch gradables for ${course.title}:`, e); - } - return { - semester: course.semester, - title: course.title, - display_name: course.display_name || course.title, - gradables, - }; - }) + const coursesWithGradables = await Promise.all( + unarchived.map(async course => { + let gradables: { id: string; title: string }[] = []; + try { + const gradableResponse = await this.apiService.fetchGradables( + course.title, + course.semester + ); + gradables = Object.values(gradableResponse.data || {}).map( + (g: Gradable) => ({ id: g.id, title: g.title || g.id }) ); + } catch (e) { + console.warn(`Failed to fetch gradables for ${course.title}:`, e); + } + return { + semester: course.semester, + title: course.title, + display_name: course.display_name || course.title, + gradables, + }; + }) + ); - view.webview.postMessage({ - command: MessageCommand.DISPLAY_COURSES, - data: { courses: coursesWithGradables }, - }); + view.webview.postMessage({ + command: MessageCommand.DISPLAY_COURSES, + data: { courses: coursesWithGradables }, + }); + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); + vscode.window.showErrorMessage(`Failed to fetch courses: ${err}`); + view.webview.postMessage({ + command: MessageCommand.ERROR, + data: { message: `Failed to fetch courses: ${err}` }, + }); + } + } + + private async handleGrade( + term: string, + courseId: string, + gradeableId: string, + view: vscode.WebviewView + ): Promise { + try { + this.testingService?.addGradeable( + term, + courseId, + gradeableId, + gradeableId + ); + + if (this.gitService) { + view.webview.postMessage({ + command: MessageCommand.GRADE_STARTED, + data: { message: 'Staging and committing...' }, + }); + const commitMessage = new Date().toLocaleString(undefined, { + dateStyle: 'short', + timeStyle: 'medium', + }); + try { + await this.gitService.commit(commitMessage, { all: true }); + view.webview.postMessage({ + command: MessageCommand.GRADE_STARTED, + data: { message: 'Pushing...' }, + }); + await this.gitService.push(); } catch (error: unknown) { - const err = error instanceof Error ? error.message : String(error); - vscode.window.showErrorMessage(`Failed to fetch courses: ${err}`); + const err = error instanceof Error ? error.message : String(error); + if (err === 'No changes to commit.') { view.webview.postMessage({ - command: MessageCommand.ERROR, - data: { message: `Failed to fetch courses: ${err}` }, + command: MessageCommand.GRADE_STARTED, + data: { message: 'No changes to commit. Skipping git push.' }, }); + } else { + throw error; + } } - } + } - private async handleGrade(term: string, courseId: string, gradeableId: string, view: vscode.WebviewView): Promise { - try { - this.testingService?.addGradeable(term, courseId, gradeableId, gradeableId); + view.webview.postMessage({ + command: MessageCommand.GRADE_STARTED, + data: { message: 'Submitting for grading...' }, + }); + await this.apiService.submitVCSGradable(term, courseId, gradeableId); - if (this.gitService) { - view.webview.postMessage({ command: MessageCommand.GRADE_STARTED, data: { message: 'Staging and committing...' } }); - const commitMessage = new Date().toLocaleString(undefined, { - dateStyle: 'short', - timeStyle: 'medium', - }); - try { - await this.gitService.commit(commitMessage, { all: true }); - view.webview.postMessage({ command: MessageCommand.GRADE_STARTED, data: { message: 'Pushing...' } }); - await this.gitService.push(); - } catch (error: unknown) { - const err = error instanceof Error ? error.message : String(error); - if (err === 'No changes to commit.') { - view.webview.postMessage({ - command: MessageCommand.GRADE_STARTED, - data: { message: 'No changes to commit. Skipping git push.' }, - }); - } else { - throw error; - } - } - } - - view.webview.postMessage({ command: MessageCommand.GRADE_STARTED, data: { message: 'Submitting for grading...' } }); - await this.apiService.submitVCSGradable(term, courseId, gradeableId); - - view.webview.postMessage({ command: MessageCommand.GRADE_STARTED, data: { message: 'Grading in progress. Polling for results...' } }); - const gradeDetails = await this.apiService.pollGradeDetailsUntilComplete(term, courseId, gradeableId); - const previousAttempts = await this.apiService.fetchPreviousAttempts(term, courseId, gradeableId); + view.webview.postMessage({ + command: MessageCommand.GRADE_STARTED, + data: { message: 'Grading in progress. Polling for results...' }, + }); + const gradeDetails = await this.apiService.pollGradeDetailsUntilComplete( + term, + courseId, + gradeableId + ); + const previousAttempts = await this.apiService.fetchPreviousAttempts( + term, + courseId, + gradeableId + ); - view.webview.postMessage({ - command: MessageCommand.GRADE_COMPLETED, - data: { - term, - courseId, - gradeableId, - gradeDetails, - previousAttempts, - } - }); + view.webview.postMessage({ + command: MessageCommand.GRADE_COMPLETED, + data: { + term, + courseId, + gradeableId, + gradeDetails, + previousAttempts, + }, + }); - this.testingService?.runGradeableWithResult(term, courseId, gradeableId, gradeableId, gradeDetails); - } catch (error: unknown) { - const err = error instanceof Error ? error.message : String(error); - vscode.window.showErrorMessage(`Failed to grade: ${err}`); - view.webview.postMessage({ - command: MessageCommand.ERROR, - data: { message: `Failed to grade: ${err}` }, - }); - } + this.testingService?.runGradeableWithResult( + term, + courseId, + gradeableId, + gradeableId, + gradeDetails + ); + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); + vscode.window.showErrorMessage(`Failed to grade: ${err}`); + view.webview.postMessage({ + command: MessageCommand.ERROR, + data: { message: `Failed to grade: ${err}` }, + }); } + } - private getBlankHtml(): string { - return ` + private getBlankHtml(): string { + return ` @@ -279,6 +332,5 @@ export class SidebarProvider implements vscode.WebviewViewProvider { `; - } + } } - diff --git a/src/test/extension.test.ts b/src/test/extension.test.ts index 4ca0ab4..17e2eab 100644 --- a/src/test/extension.test.ts +++ b/src/test/extension.test.ts @@ -6,10 +6,10 @@ import * as vscode from 'vscode'; // import * as myExtension from '../../extension'; suite('Extension Test Suite', () => { - vscode.window.showInformationMessage('Start all tests.'); + vscode.window.showInformationMessage('Start all tests.'); - test('Sample test', () => { - assert.strictEqual(-1, [1, 2, 3].indexOf(5)); - assert.strictEqual(-1, [1, 2, 3].indexOf(0)); - }); + test('Sample test', () => { + assert.strictEqual(-1, [1, 2, 3].indexOf(5)); + assert.strictEqual(-1, [1, 2, 3].indexOf(0)); + }); }); diff --git a/src/typings/message.ts b/src/typings/message.ts index 7a0a860..74cdd08 100644 --- a/src/typings/message.ts +++ b/src/typings/message.ts @@ -1,18 +1,18 @@ export const MessageCommand = { - FETCH_AND_DISPLAY_COURSES: 'fetchAndDisplayCourses', - DISPLAY_COURSES: 'displayCourses', - GRADE: 'grade', - GRADE_STARTED: 'gradeStarted', - GRADE_COMPLETED: 'gradeCompleted', - GRADE_ERROR: 'gradeError', - GRADE_CANCELLED: 'gradeCancelled', - GRADE_PAUSED: 'gradePaused', - GRASE_RESUMED: 'gradeResumed', - GRADE_ABORTED: 'gradeAborted', - ERROR: 'error', + FETCH_AND_DISPLAY_COURSES: 'fetchAndDisplayCourses', + DISPLAY_COURSES: 'displayCourses', + GRADE: 'grade', + GRADE_STARTED: 'gradeStarted', + GRADE_COMPLETED: 'gradeCompleted', + GRADE_ERROR: 'gradeError', + GRADE_CANCELLED: 'gradeCancelled', + GRADE_PAUSED: 'gradePaused', + GRASE_RESUMED: 'gradeResumed', + GRADE_ABORTED: 'gradeAborted', + ERROR: 'error', } as const; export type WebViewMessage = { - command: (typeof MessageCommand)[keyof typeof MessageCommand]; - [key: string]: string | number | boolean | object | null | undefined; -}; \ No newline at end of file + command: (typeof MessageCommand)[keyof typeof MessageCommand]; + [key: string]: string | number | boolean | object | null | undefined; +}; From 50620b98fdcd25a459a2eb9d7b07f2a7a7b87913 Mon Sep 17 00:00:00 2001 From: Riley Smith Date: Fri, 20 Mar 2026 14:15:27 -0700 Subject: [PATCH 05/20] fix double import --- src/sidebarProvider.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/sidebarProvider.ts b/src/sidebarProvider.ts index fdeb712..935a8f7 100644 --- a/src/sidebarProvider.ts +++ b/src/sidebarProvider.ts @@ -3,7 +3,6 @@ import { getClassesHtml } from './sidebarContent'; import { ApiService } from './services/apiService'; import { AuthService } from './services/authService'; import { GitService } from './services/gitService'; -import type { TestingService } from './services/testingService'; import { Gradable } from './interfaces/Gradables'; import { TestingService } from './services/testingService'; import { MessageCommand } from './typings/message'; @@ -295,13 +294,15 @@ export class SidebarProvider implements vscode.WebviewViewProvider { }, }); - this.testingService?.runGradeableWithResult( - term, - courseId, - gradeableId, - gradeableId, - gradeDetails - ); + if (this.testingService) { + this.testingService.runGradeableWithResult( + term, + courseId, + gradeableId, + gradeableId, + gradeDetails + ); + } } catch (error: unknown) { const err = error instanceof Error ? error.message : String(error); vscode.window.showErrorMessage(`Failed to grade: ${err}`); From 3580a08ae0949379c24e8203862f0f641a78a988 Mon Sep 17 00:00:00 2001 From: Riley Smith Date: Wed, 11 Mar 2026 15:11:44 -0700 Subject: [PATCH 06/20] feat: strip html and properly create test cases --- src/interfaces/Responses.ts | 4 +- src/services/apiService.ts | 2 +- src/services/testingService.ts | 147 +++++++++++++++++++++++++-------- src/sidebarProvider.ts | 24 ++++-- 4 files changed, 133 insertions(+), 44 deletions(-) diff --git a/src/interfaces/Responses.ts b/src/interfaces/Responses.ts index 19db2d4..5e7ef1f 100644 --- a/src/interfaces/Responses.ts +++ b/src/interfaces/Responses.ts @@ -17,4 +17,6 @@ export type LoginResponse = ApiResponse<{ token: string; }>; -export type GradableResponse = ApiResponse; \ No newline at end of file +export type GradableResponse = ApiResponse<{ + [key: string]: Gradable; +}>; \ No newline at end of file diff --git a/src/services/apiService.ts b/src/services/apiService.ts index b7f6809..2ce5ede 100644 --- a/src/services/apiService.ts +++ b/src/services/apiService.ts @@ -117,7 +117,7 @@ export class ApiService { Array.isArray(res.data.test_cases) && res.data.test_cases.length > 0; - for (;;) { + for (; ;) { if (token?.isCancellationRequested) { throw new Error('Cancelled'); } diff --git a/src/services/testingService.ts b/src/services/testingService.ts index 03dc494..a2bb026 100644 --- a/src/services/testingService.ts +++ b/src/services/testingService.ts @@ -1,6 +1,6 @@ import * as vscode from 'vscode'; import { ApiService } from './apiService'; -import type { AutoGraderDetailsData, TestCase } from '../interfaces/AutoGraderDetails'; +import type { AutoGraderDetails, AutoGraderDetailsData, TestCase, Autocheck } from '../interfaces/AutoGraderDetails'; const CONTROLLER_ID = 'submittyAutograder'; const CONTROLLER_LABEL = 'Submitty Autograder'; @@ -56,10 +56,78 @@ export class TestingService { return item; } + /** + * Run a single gradeable in the Test Explorer using an already-fetched autograder result. + * Used when the user clicks "Grade" in the sidebar: submit → poll → then report here. + */ + runGradeableWithResult(term: string, courseId: string, gradeableId: string, label: string, result: AutoGraderDetails): void { + const item = this.addGradeable(term, courseId, gradeableId, label); + this.syncTestCaseChildren(item, result.data); + + const run = this.controller.createTestRun(new vscode.TestRunRequest([item])); + run.started(item); + run.appendOutput(`Autograder completed for ${item.label}.\r\n`); + this.reportGradeableResult(run, item, result.data); + run.end(); + } + private getGradeableMeta(item: vscode.TestItem): GradeableMeta | undefined { return this.gradeableMeta.get(item); } + /** + * Convert HTML from autograder actual/expected into plain text for the Test Explorer diff. + * Strips tags and decodes common entities so the diff view is readable. + */ + private stripHtml(html: string): string { + if (!html || typeof html !== 'string') { + return ''; + } + const text = html + .replace(//gi, '\n') + .replace(/<\/div>/gi, '\n') + .replace(/<\/p>/gi, '\n') + .replace(/<[^>]+>/g, '') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/ /g, ' '); + return text.replace(/\n{3,}/g, '\n\n').trim(); + } + + private formatAutocheckOutput(autochecks: Autocheck[] | undefined, getValue: (ac: Autocheck) => string): string { + if (!autochecks?.length) { + return ''; + } + const parts = autochecks.map((ac) => { + const value = this.stripHtml(getValue(ac)); + if (!value) { + return ''; + } + return `[${ac.description}]\n${value}`; + }); + return parts.filter(Boolean).join('\n\n'); + } + + /** + * Format the messages array from all autochecks (e.g. "ERROR: ..." with type failure/warning). + */ + private formatAutocheckMessages(autochecks: Autocheck[] | undefined): string { + if (!autochecks?.length) { + return ''; + } + const parts = autochecks.map((ac) => { + const msgLines = (ac.messages ?? []).map((m) => ` • ${m.message}${m.type ? ` (${m.type})` : ''}`); + if (msgLines.length === 0) { + return ''; + } + return `[${ac.description}]\n${msgLines.join('\n')}`; + }); + return parts.filter(Boolean).join('\n\n'); + } + private async resolveHandler(item: vscode.TestItem | undefined): Promise { if (!item) { return; @@ -98,6 +166,49 @@ export class TestingService { } } + private reportGradeableResult(run: vscode.TestRun, item: vscode.TestItem, _data: AutoGraderDetailsData): void { + const start = Date.now(); + let allPassed = true; + item.children.forEach((child) => { + const tc = this.testCaseMeta.get(child); + run.started(child); + if (tc) { + const passed = tc.points_received >= (tc.points_available ?? 0); + if (!passed) { + allPassed = false; + } + const duration = Date.now() - start; + const messageParts = [tc.testcase_message, tc.details].filter(Boolean); + const formattedMessages = this.formatAutocheckMessages(tc.autochecks); + if (formattedMessages) { + messageParts.push('--- Messages ---', formattedMessages); + } + const messageText = messageParts.join('\n') || 'Failed'; + if (passed) { + run.passed(child, duration); + } else { + const msg = new vscode.TestMessage(messageText); + msg.expectedOutput = this.formatAutocheckOutput(tc.autochecks, (ac) => ac.expected); + msg.actualOutput = this.formatAutocheckOutput(tc.autochecks, (ac) => ac.actual); + run.failed(child, msg, duration); + } + } else { + run.passed(child, 0); + } + }); + + if (item.children.size === 0) { + run.appendOutput(`No test cases in response.\r\n`); + run.failed(item, new vscode.TestMessage('No test cases returned.'), 0); + } else { + if (allPassed) { + run.passed(item, Date.now() - start); + } else { + run.failed(item, new vscode.TestMessage('Some test cases failed.'), Date.now() - start); + } + } + } + private async runHandler(request: vscode.TestRunRequest, token: vscode.CancellationToken): Promise { const run = this.controller.createTestRun(request); const queue: vscode.TestItem[] = []; @@ -137,39 +248,7 @@ export class TestingService { ); const data = result.data; this.syncTestCaseChildren(item, data); - - let allPassed = true; - const start = Date.now(); - item.children.forEach((child) => { - const tc = this.testCaseMeta.get(child); - run.started(child); - if (tc) { - const passed = tc.points_received >= (tc.points_available ?? 0); - if (!passed) { - allPassed = false; - } - const duration = Date.now() - start; - const message = [tc.testcase_message, tc.details].filter(Boolean).join('\n') || undefined; - if (passed) { - run.passed(child, duration); - } else { - run.failed(child, new vscode.TestMessage(message || 'Failed'), duration); - } - } else { - run.passed(child, 0); - } - }); - - if (item.children.size === 0) { - run.appendOutput(`No test cases in response.\r\n`); - run.failed(item, new vscode.TestMessage('No test cases returned.'), 0); - } else { - if (allPassed) { - run.passed(item, Date.now() - start); - } else { - run.failed(item, new vscode.TestMessage('Some test cases failed.'), Date.now() - start); - } - } + this.reportGradeableResult(run, item, data); } catch (e) { const err = e instanceof Error ? e.message : String(e); run.appendOutput(`Error: ${err}\r\n`); diff --git a/src/sidebarProvider.ts b/src/sidebarProvider.ts index a0c2861..9393e21 100644 --- a/src/sidebarProvider.ts +++ b/src/sidebarProvider.ts @@ -3,6 +3,7 @@ import { getClassesHtml } from './sidebarContent'; import { ApiService } from './services/apiService'; import { AuthService } from './services/authService'; import type { TestingService } from './services/testingService'; +import { Gradable } from './interfaces/Gradables'; export class SidebarProvider implements vscode.WebviewViewProvider { private _view?: vscode.WebviewView; @@ -109,7 +110,7 @@ export class SidebarProvider implements vscode.WebviewViewProvider { let gradables: { id: string; title: string }[] = []; try { const gradableResponse = await this.apiService.fetchGradables(course.title, course.semester); - gradables = (gradableResponse.data || []).map((g) => ({ id: g.id, title: g.title || g.id })); + gradables = Object.values(gradableResponse.data || {}).map((g: Gradable) => ({ id: g.id, title: g.title || g.id })); } catch (e) { console.warn(`Failed to fetch gradables for ${course.title}:`, e); } @@ -135,8 +136,14 @@ export class SidebarProvider implements vscode.WebviewViewProvider { private async handleGrade(term: string, courseId: string, gradeableId: string, view: vscode.WebviewView): Promise { try { this.testingService?.addGradeable(term, courseId, gradeableId, gradeableId); - const gradeDetails = await this.apiService.fetchGradeDetails(term, courseId, gradeableId); - const previousAttempts = await this.apiService.fetchPreviousAttempts(term, courseId, gradeableId); // Fetch previous attempts + + view.webview.postMessage({ command: 'gradeStarted', message: 'Submitting for grading...' }); + await this.apiService.submitVCSGradable(term, courseId, gradeableId); + + view.webview.postMessage({ command: 'gradeStarted', message: 'Grading in progress. Polling for results...' }); + const gradeDetails = await this.apiService.pollGradeDetailsUntilComplete(term, courseId, gradeableId); + + const previousAttempts = await this.apiService.fetchPreviousAttempts(term, courseId, gradeableId); view.webview.postMessage({ command: 'displayGrade', @@ -145,21 +152,22 @@ export class SidebarProvider implements vscode.WebviewViewProvider { courseId, gradeableId, gradeDetails, - previousAttempts, // Include previous attempts + previousAttempts, } }); - // Send message to PanelProvider vscode.commands.executeCommand('extension.showGradePanel', { term, courseId, gradeableId, gradeDetails, - previousAttempts, // Include previous attempts + previousAttempts, }); + + this.testingService?.runGradeableWithResult(term, courseId, gradeableId, gradeableId, gradeDetails); } catch (error: any) { - vscode.window.showErrorMessage(`Failed to fetch grade details: ${error.message}`); - view.webview.postMessage({ command: 'error', message: `Failed to fetch grade details: ${error.message}` }); + vscode.window.showErrorMessage(`Failed to grade: ${error.message}`); + view.webview.postMessage({ command: 'error', message: `Failed to grade: ${error.message}` }); } } From 0fe341229b9eb62a60b0a0dd516b7176c776d87e Mon Sep 17 00:00:00 2001 From: Riley Smith Date: Wed, 11 Mar 2026 16:16:19 -0700 Subject: [PATCH 07/20] feat: first pass at autograding flow --- src/extension.ts | 4 +++- src/sidebarProvider.ts | 19 ++++++++++++++++--- src/typings/message.ts | 11 +++++++++++ 3 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 src/typings/message.ts diff --git a/src/extension.ts b/src/extension.ts index 09c7bfb..95b26d8 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -2,11 +2,13 @@ import * as vscode from 'vscode'; import { SidebarProvider } from './sidebarProvider'; import { ApiService } from './services/apiService'; import { TestingService } from './services/testingService'; +import { GitService } from './services/gitService'; export function activate(context: vscode.ExtensionContext): void { const apiService = ApiService.getInstance(context, ''); const testingService = new TestingService(context, apiService); - const sidebarProvider = new SidebarProvider(context, testingService); + const gitService = new GitService(); + const sidebarProvider = new SidebarProvider(context, testingService, gitService); context.subscriptions.push( vscode.window.registerWebviewViewProvider('submittyWebview', sidebarProvider) diff --git a/src/sidebarProvider.ts b/src/sidebarProvider.ts index 9393e21..b00f564 100644 --- a/src/sidebarProvider.ts +++ b/src/sidebarProvider.ts @@ -2,6 +2,7 @@ import * as vscode from 'vscode'; import { getClassesHtml } from './sidebarContent'; import { ApiService } from './services/apiService'; import { AuthService } from './services/authService'; +import { GitService } from './services/gitService'; import type { TestingService } from './services/testingService'; import { Gradable } from './interfaces/Gradables'; @@ -13,7 +14,8 @@ export class SidebarProvider implements vscode.WebviewViewProvider { constructor( private readonly context: vscode.ExtensionContext, - private readonly testingService?: TestingService + private readonly testingService?: TestingService, + private readonly gitService?: GitService ) { this.apiService = ApiService.getInstance(this.context, ""); this.authService = AuthService.getInstance(this.context); @@ -23,7 +25,7 @@ export class SidebarProvider implements vscode.WebviewViewProvider { webviewView: vscode.WebviewView, _context: vscode.WebviewViewResolveContext, _token: vscode.CancellationToken - ) { + ): Promise { this._view = webviewView; webviewView.webview.options = { @@ -83,7 +85,7 @@ export class SidebarProvider implements vscode.WebviewViewProvider { } } - private async handleMessage(message: any, view: vscode.WebviewView) { + private async handleMessage(message: any, view: vscode.WebviewView): Promise { switch (message.command) { case 'fetchAndDisplayCourses': const token = await this.authService.getAuthorizationToken(); @@ -137,6 +139,17 @@ export class SidebarProvider implements vscode.WebviewViewProvider { try { this.testingService?.addGradeable(term, courseId, gradeableId, gradeableId); + if (this.gitService) { + view.webview.postMessage({ command: 'gradeStarted', message: 'Staging and committing...' }); + const commitMessage = new Date().toLocaleString(undefined, { + dateStyle: 'short', + timeStyle: 'medium', + }); + await this.gitService.commit(commitMessage, { all: true }); + view.webview.postMessage({ command: 'gradeStarted', message: 'Pushing...' }); + await this.gitService.push(); + } + view.webview.postMessage({ command: 'gradeStarted', message: 'Submitting for grading...' }); await this.apiService.submitVCSGradable(term, courseId, gradeableId); diff --git a/src/typings/message.ts b/src/typings/message.ts new file mode 100644 index 0000000..6e743b7 --- /dev/null +++ b/src/typings/message.ts @@ -0,0 +1,11 @@ +export const MessageCommand = { + FETCH_AND_DISPLAY_COURSES: 'fetchAndDisplayCourses', + GRADE: 'grade', + GRADE_STARTED: 'gradeStarted', + GRADE_COMPLETED: 'gradeCompleted', + GRADE_ERROR: 'gradeError', + GRADE_CANCELLED: 'gradeCancelled', + GRADE_PAUSED: 'gradePaused', + GRASE_RESUMED: 'gradeResumed', + GRADE_ABORTED: 'gradeAborted', +} as const; \ No newline at end of file From 989963e81f7512c21f89f1ac05a69f1220c410bc Mon Sep 17 00:00:00 2001 From: Riley Smith Date: Thu, 19 Mar 2026 23:20:03 -0700 Subject: [PATCH 08/20] first pass at preloading gradeables when loading extension --- src/extension.ts | 27 ++ src/services/apiService.ts | 54 ++-- src/services/authService.ts | 43 ++- src/services/courseRepoResolver.ts | 163 ++++++++++ src/services/gitService.ts | 29 +- src/sidebar/classes.html | 8 +- src/sidebarProvider.ts | 145 +++++++-- src/typings/message.ts | 9 +- src/typings/vscode-git.d.ts | 498 ++++++++++++++++++++++++++++- 9 files changed, 891 insertions(+), 85 deletions(-) create mode 100644 src/services/courseRepoResolver.ts diff --git a/src/extension.ts b/src/extension.ts index 95b26d8..30d2371 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -3,17 +3,44 @@ import { SidebarProvider } from './sidebarProvider'; import { ApiService } from './services/apiService'; import { TestingService } from './services/testingService'; import { GitService } from './services/gitService'; +import { AuthService } from './services/authService'; +import { CourseRepoResolver } from './services/courseRepoResolver'; +import type { Gradable } from './interfaces/Gradables'; export function activate(context: vscode.ExtensionContext): void { const apiService = ApiService.getInstance(context, ''); const testingService = new TestingService(context, apiService); const gitService = new GitService(); + const authService = AuthService.getInstance(context); const sidebarProvider = new SidebarProvider(context, testingService, gitService); context.subscriptions.push( vscode.window.registerWebviewViewProvider('submittyWebview', sidebarProvider) ); + // Preload gradables into the Test Explorer when the workspace appears + // to be a course-tied repo. + void (async () => { + try { + await authService.initialize(); + const resolver = new CourseRepoResolver(apiService, authService, gitService); + const courseContext = await resolver.resolveCourseContextFromRepo(); + if (!courseContext) { + return; + } + + const gradablesResponse = await apiService.fetchGradables(courseContext.courseId, courseContext.term); + const gradables = Object.values(gradablesResponse.data); + + for (const g of gradables) { + testingService.addGradeable(courseContext.term, courseContext.courseId, g.id, g.title || g.id); + } + } catch (e) { + const err = e instanceof Error ? e.message : String(e); + console.warn(`Failed to preload gradables: ${err}`); + } + })(); + } export function deactivate() { } \ No newline at end of file diff --git a/src/services/apiService.ts b/src/services/apiService.ts index 2ce5ede..d35964e 100644 --- a/src/services/apiService.ts +++ b/src/services/apiService.ts @@ -2,10 +2,22 @@ import * as vscode from 'vscode'; import { ApiClient } from './apiClient'; - import { CourseResponse, LoginResponse, GradableResponse } from '../interfaces/Responses'; import { AutoGraderDetails } from '../interfaces/AutoGraderDetails'; +function getErrorMessage(error: unknown, fallback: string): string { + if (error instanceof Error) { + return error.message || fallback; + } + if (typeof error === 'object' && error) { + const maybeAxiosError = error as { response?: { data?: { message?: unknown } } }; + const msg = maybeAxiosError.response?.data?.message; + if (typeof msg === 'string' && msg.trim()) { + return msg; + } + } + return fallback; +} export class ApiService { private client: ApiClient; @@ -16,12 +28,12 @@ export class ApiService { } // set token for local api client - setAuthorizationToken(token: string) { + setAuthorizationToken(token: string): void { this.client.setToken(token); } // set base URL for local api client - setBaseUrl(baseUrl: string) { + setBaseUrl(baseUrl: string): void { this.client.setBaseURL(baseUrl); } @@ -43,8 +55,8 @@ export class ApiService { const token: string = response.data.data.token; return token; - } catch (error: any) { - throw new Error(error.response?.data?.message || error.message || 'Login failed.'); + } catch (error: unknown) { + throw new Error(getErrorMessage(error, 'Login failed.')); } } @@ -52,8 +64,8 @@ export class ApiService { try { const response = await this.client.get('/api/me'); return response.data; - } catch (error: any) { - throw new Error(error.response?.data?.message || 'Failed to fetch me.'); + } catch (error: unknown) { + throw new Error(getErrorMessage(error, 'Failed to fetch me.')); } } @@ -61,13 +73,13 @@ export class ApiService { /** * Fetch all courses for the authenticated user */ - async fetchCourses(token?: string): Promise { + async fetchCourses(_token?: string): Promise { try { const response = await this.client.get('/api/courses'); return response.data; - } catch (error: any) { + } catch (error: unknown) { console.error('Error fetching courses:', error); - throw new Error(error.response?.data?.message || 'Failed to fetch courses.'); + throw new Error(getErrorMessage(error, 'Failed to fetch courses.')); } } @@ -76,9 +88,9 @@ export class ApiService { const url = `/api/${term}/${courseId}/gradeables`; const response = await this.client.get(url); return response.data; - } catch (error: any) { + } catch (error: unknown) { console.error('Error fetching gradables:', error); - throw new Error(error.response?.data?.message || 'Failed to fetch gradables.'); + throw new Error(getErrorMessage(error, 'Failed to fetch gradables.')); } } @@ -89,9 +101,9 @@ export class ApiService { try { const response = await this.client.get(`/api/${term}/${courseId}/gradeable/${gradeableId}/values`); return response.data; - } catch (error: any) { + } catch (error: unknown) { console.error('Error fetching grade details:', error); - throw new Error(error.response?.data?.message || 'Failed to fetch grade details.'); + throw new Error(getErrorMessage(error, 'Failed to fetch grade details.')); } } @@ -140,9 +152,9 @@ export class ApiService { const url = `/api/${term}/${courseId}/gradeable/${gradeableId}/upload?vcs_upload=true&git_repo_id=true`; const response = await this.client.post(url); return response.data; - } catch (error: any) { - console.error('Error submitting VCS gradable:', error); - throw new Error(error.response?.data?.message || 'Failed to submit VCS gradable.'); + } catch (error: unknown) { + console.error('Error submitt`ing VCS gradable:', error); + throw new Error(getErrorMessage(error, 'Failed to submit VCS gradable.')); } } @@ -150,14 +162,14 @@ export class ApiService { /** * Fetch previous attempts for a specific homework assignment */ - async fetchPreviousAttempts(term: string, courseId: string, gradeableId: string): Promise { + async fetchPreviousAttempts(term: string, courseId: string, gradeableId: string): Promise { try { const url = `/api/${term}/${courseId}/gradeable/${gradeableId}/attempts`; - const response = await this.client.get(url); + const response = await this.client.get(url); return response.data; - } catch (error: any) { + } catch (error: unknown) { console.error('Error fetching previous attempts:', error); - throw new Error(error.response?.data?.message || 'Failed to fetch previous attempts.'); + throw new Error(getErrorMessage(error, 'Failed to fetch previous attempts.')); } } diff --git a/src/services/authService.ts b/src/services/authService.ts index bedc58f..afbd445 100644 --- a/src/services/authService.ts +++ b/src/services/authService.ts @@ -13,7 +13,7 @@ export class AuthService { this.apiService = ApiService.getInstance(context, ""); } - async initialize() { + async initialize(): Promise { console.log("Initializing AuthService"); // Get base URL from configuration @@ -31,6 +31,36 @@ export class AuthService { // Token exists, set it on the API service this.apiService.setAuthorizationToken(token); console.log("Token set on API service"); + + // If baseUrl isn't configured yet, fetch it now so API calls work. + if (!baseUrl) { + const inputUrl = await vscode.window.showInputBox({ + prompt: 'Enter Submitty API URL', + placeHolder: 'https://example.submitty.edu', + ignoreFocusOut: true, + validateInput: (value) => { + if (!value || value.trim().length === 0) { + return 'URL is required'; + } + try { + new URL(value); + return null; + } catch { + return 'Please enter a valid URL'; + } + }, + }); + + if (!inputUrl) { + return; + } + + baseUrl = inputUrl.trim(); + + await config.update('baseUrl', baseUrl, vscode.ConfigurationTarget.Global); + this.apiService.setBaseUrl(baseUrl); + } + return; } @@ -110,19 +140,20 @@ export class AuthService { await this.login(userId.trim(), password); vscode.window.showInformationMessage('Successfully logged in to Submitty'); - } catch (error: any) { - vscode.window.showErrorMessage(`Login failed: ${error.message}`); + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); + vscode.window.showErrorMessage(`Login failed: ${err}`); throw error; } } // store token - private async storeToken(token: string) { + private async storeToken(token: string): Promise { await keytar.setPassword('submittyToken', 'submittyToken', token); } // get token - private async getToken() { + private async getToken(): Promise { return await keytar.getPassword('submittyToken', 'submittyToken'); } @@ -135,7 +166,7 @@ export class AuthService { const token = await this.apiService.login(userId, password); this.apiService.setAuthorizationToken(token); // store token in system keychain - this.storeToken(token); + await this.storeToken(token); return token; } diff --git a/src/services/courseRepoResolver.ts b/src/services/courseRepoResolver.ts new file mode 100644 index 0000000..fae5ada --- /dev/null +++ b/src/services/courseRepoResolver.ts @@ -0,0 +1,163 @@ +import * as vscode from 'vscode'; +import * as fs from 'fs'; +import * as path from 'path'; +import { ApiService } from './apiService'; +import { AuthService } from './authService'; +import { GitService } from './gitService'; +import type { Course } from '../interfaces/Courses'; + +export interface CourseRepoContext { + term: string; + courseId: string; +} + +function normalizeForMatch(input: string): string { + return input + .toLowerCase() + // Keep only alphanumerics so variants like "Fall 2024" vs "fall2024" match. + .replace(/[^a-z0-9]/g, ''); +} + +function readTextFileSafe(filePath: string): string | null { + try { + return fs.readFileSync(filePath, 'utf8'); + } catch { + return null; + } +} + +function getGitDirPath(repoRootPath: string): string | null { + const gitEntryPath = path.join(repoRootPath, '.git'); + if (!fs.existsSync(gitEntryPath)) { + return null; + } + + try { + const stat = fs.statSync(gitEntryPath); + if (stat.isDirectory()) { + return gitEntryPath; + } + + if (stat.isFile()) { + // Worktrees/linked clones can have a .git file like: "gitdir: /abs/path/to/.git/worktrees/..." + const gitFileContents = readTextFileSafe(gitEntryPath); + if (!gitFileContents) { + return null; + } + + const match = gitFileContents.match(/^\s*gitdir:\s*(.+)\s*$/m); + if (!match?.[1]) { + return null; + } + + const gitdirRaw = match[1].trim(); + return path.isAbsolute(gitdirRaw) ? gitdirRaw : path.resolve(repoRootPath, gitdirRaw); + } + } catch { + return null; + } + + return null; +} + +function extractGitRemoteUrlsFromConfig(gitConfigText: string): string[] { + const urls: string[] = []; + + // Example: + // [remote "origin"] + // url = https://example/.../term/courseId/... + const urlRegex = /^\s*url\s*=\s*(.+)\s*$/gim; + let match: RegExpExecArray | null = null; + // eslint-disable-next-line no-cond-assign + while ((match = urlRegex.exec(gitConfigText))) { + const rawUrl = match[1]?.trim(); + if (rawUrl) { + urls.push(rawUrl); + } + } + + return urls; +} + +export class CourseRepoResolver { + constructor( + private readonly apiService: ApiService, + private readonly authService: AuthService, + private readonly gitService: GitService + ) {} + + async resolveCourseContextFromRepo(): Promise { + const repo = this.gitService.getRepository(); + if (!repo) { + return null; + } + + const repoRootPath = repo.rootUri.fsPath; + const gitDirPath = getGitDirPath(repoRootPath); + if (!gitDirPath) { + return null; + } + + const gitConfigText = readTextFileSafe(path.join(gitDirPath, 'config')); + if (!gitConfigText) { + return null; + } + + const remoteUrls = extractGitRemoteUrlsFromConfig(gitConfigText); + if (remoteUrls.length === 0) { + return null; + } + + const token = await this.authService.getAuthorizationToken(); + if (!token) { + // No auth token -> can't map remotes to courses via API. + return null; + } + + const baseUrl = vscode.workspace.getConfiguration('submitty').get('baseUrl', ''); + if (!baseUrl) { + // Without baseUrl, we can't call the API. + return null; + } + + this.apiService.setBaseUrl(baseUrl); + this.apiService.setAuthorizationToken(token); + + // Fetch courses and match based on whether their (term, courseId) strings appear in remote URLs. + const coursesResponse = await this.apiService.fetchCourses(token); + const courses = coursesResponse.data.unarchived_courses; + + const remoteText = remoteUrls.join(' '); + const remoteNorm = normalizeForMatch(remoteText); + + let best: { course: Course; score: number } | null = null; + + for (const course of courses) { + const courseIdNorm = normalizeForMatch(course.title); + const termNorm = normalizeForMatch(course.semester); + + let score = 0; + + if (remoteNorm.includes(courseIdNorm)) { + score += 6; + } + if (remoteNorm.includes(termNorm)) { + score += 3; + } + if (remoteText.toLowerCase().includes(course.display_name.toLowerCase())) { + score += 1; + } + + if (!best || score > best.score) { + best = { course, score }; + } + } + + if (!best || best.score < 6) { + return null; + } + + return { term: best.course.semester, courseId: best.course.title }; + } +} + diff --git a/src/services/gitService.ts b/src/services/gitService.ts index ed1db65..4e4c889 100644 --- a/src/services/gitService.ts +++ b/src/services/gitService.ts @@ -1,14 +1,20 @@ +/* eslint-disable @typescript-eslint/no-unsafe-assignment */ +/* eslint-disable @typescript-eslint/no-redundant-type-constituents */ +/* eslint-disable @typescript-eslint/no-unsafe-call */ +/* eslint-disable @typescript-eslint/no-unsafe-member-access */ +/* eslint-disable @typescript-eslint/no-unsafe-return */ import * as vscode from 'vscode'; -import type { GitApi, GitExtension, Repository, CommitOptions, ForcePushMode } from '../typings/vscode-git'; +import type { GitExtension, Repository, CommitOptions, ForcePushMode } from '../typings/vscode-git'; +import { API } from '../typings/vscode-git'; /** * Service that delegates to the built-in vscode.git extension for * push, pull, and commit in the current workspace repository. */ export class GitService { - private gitApi: GitApi | null = null; + private gitApi: API | null = null; - private getApi(): GitApi | null { + private getApi(): API | null { if (this.gitApi !== null) { return this.gitApi; } @@ -33,13 +39,13 @@ export class GitService { return null; } if (uri) { - return api.getRepository(uri) ?? null; + return api.getRepository(uri); } const folder = vscode.workspace.workspaceFolders?.[0]; if (!folder) { return api.repositories.length > 0 ? api.repositories[0] : null; } - return api.getRepository(folder.uri) ?? api.repositories[0] ?? null; + return api.getRepository(folder.uri) ?? api.repositories[0]; } /** @@ -50,7 +56,20 @@ export class GitService { if (!repo) { throw new Error('No Git repository found. Open a workspace folder that is a Git repo.'); } + + // check to see if there are any changes to commit + const status = (await repo.status()) as unknown as { + modified: unknown[]; + untracked: unknown[]; + deleted: unknown[]; + }; + + if (status.modified.length === 0 && status.untracked.length === 0 && status.deleted.length === 0) { + throw new Error('No changes to commit.'); + } await repo.commit(message, options); + + } /** diff --git a/src/sidebar/classes.html b/src/sidebar/classes.html index 553b5e4..23bb2c0 100644 --- a/src/sidebar/classes.html +++ b/src/sidebar/classes.html @@ -143,9 +143,11 @@

Courses

e.stopPropagation(); vscode.postMessage({ command: 'grade', - term: this.dataset.term, - courseId: this.dataset.courseId, - gradeableId: this.dataset.gradeableId, + data: { + term: this.dataset.term, + courseId: this.dataset.courseId, + gradeableId: this.dataset.gradeableId, + }, }); }); }); diff --git a/src/sidebarProvider.ts b/src/sidebarProvider.ts index b00f564..3665694 100644 --- a/src/sidebarProvider.ts +++ b/src/sidebarProvider.ts @@ -5,12 +5,16 @@ import { AuthService } from './services/authService'; import { GitService } from './services/gitService'; import type { TestingService } from './services/testingService'; import { Gradable } from './interfaces/Gradables'; +import { TestingService } from './services/testingService'; +import { MessageCommand } from './typings/message'; export class SidebarProvider implements vscode.WebviewViewProvider { private _view?: vscode.WebviewView; private apiService: ApiService; private authService: AuthService; private isInitialized: boolean = false; + private visibilityDisposable?: vscode.Disposable; + private isLoadingCourses: boolean = false; constructor( private readonly context: vscode.ExtensionContext, @@ -36,6 +40,15 @@ export class SidebarProvider implements vscode.WebviewViewProvider { // Initially show blank screen webviewView.webview.html = this.getBlankHtml(); + // Reload courses any time the view becomes visible again (e.g. user + // closes/hides the panel and comes back). + this.visibilityDisposable?.dispose(); + this.visibilityDisposable = webviewView.onDidChangeVisibility(async () => { + if (webviewView.visible) { + await this.loadCourses(); + } + }); + // Initialize authentication when sidebar is opened (only once) if (!this.isInitialized) { this.isInitialized = true; @@ -68,6 +81,11 @@ export class SidebarProvider implements vscode.WebviewViewProvider { return; } + if (this.isLoadingCourses) { + return; + } + + this.isLoadingCourses = true; try { const token = await this.authService.getAuthorizationToken(); if (!token) { @@ -79,29 +97,75 @@ export class SidebarProvider implements vscode.WebviewViewProvider { // Fetch and display courses await this.fetchAndDisplayCourses(token, this._view); - } catch (error: any) { + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); console.error('Failed to load courses:', error); - vscode.window.showErrorMessage(`Failed to load courses: ${error.message}`); + vscode.window.showErrorMessage(`Failed to load courses: ${err}`); + } finally { + this.isLoadingCourses = false; } } - private async handleMessage(message: any, view: vscode.WebviewView): Promise { - switch (message.command) { - case 'fetchAndDisplayCourses': - const token = await this.authService.getAuthorizationToken(); - if (token) { - await this.fetchAndDisplayCourses(token, view); + private async handleMessage(message: unknown, view: vscode.WebviewView): Promise { + console.log('handleMessage', message); + if (!message || typeof message !== 'object') { + return; + } + const msg = message as { command?: unknown; data?: unknown }; + if (typeof msg.command !== 'string') { + return; + } + + switch (msg.command) { + case MessageCommand.FETCH_AND_DISPLAY_COURSES: + try { + const token = await this.authService.getAuthorizationToken(); + if (token) { + await this.fetchAndDisplayCourses(token, view); + } + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); + console.error('Failed to fetch and display courses:', error); + view.webview.postMessage({ + command: MessageCommand.ERROR, + data: { message: `Failed to fetch and display courses: ${err}` }, + }); } break; - case 'grade': - await this.handleGrade(message.term, message.courseId, message.gradeableId, view); + case MessageCommand.GRADE: + try { + const data = msg.data; + if (!data || typeof data !== 'object') { + throw new Error('Missing grade payload.'); + } + const dataObj = data as Record; + const term = typeof dataObj.term === 'string' ? dataObj.term : null; + const courseId = typeof dataObj.courseId === 'string' ? dataObj.courseId : null; + const gradeableId = typeof dataObj.gradeableId === 'string' ? dataObj.gradeableId : null; + + if (!term || !courseId || !gradeableId) { + throw new Error('Invalid grade payload.'); + } + console.log('handleGrade', term, courseId, gradeableId); + await this.handleGrade(term, courseId, gradeableId, view); + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); + console.error('Failed to grade:', error); + view.webview.postMessage({ + command: MessageCommand.ERROR, + data: { message: `Failed to grade: ${err}` }, + }); + } break; default: - vscode.window.showWarningMessage(`Unknown command: ${message.command}`); + vscode.window.showWarningMessage(`Unknown command: ${msg.command}`); + view.webview.postMessage({ + command: MessageCommand.ERROR, + data: { message: `Unknown command: ${msg.command}` }, + }); break; } } - private async fetchAndDisplayCourses(token: string, view: vscode.WebviewView): Promise { try { const courses = await this.apiService.fetchCourses(token); @@ -126,12 +190,16 @@ export class SidebarProvider implements vscode.WebviewViewProvider { ); view.webview.postMessage({ - command: 'displayCourses', + command: MessageCommand.DISPLAY_COURSES, data: { courses: coursesWithGradables }, }); - } catch (error: any) { - vscode.window.showErrorMessage(`Failed to fetch courses: ${error.message}`); - view.webview.postMessage({ command: 'error', message: `Failed to fetch courses: ${error.message}` }); + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); + vscode.window.showErrorMessage(`Failed to fetch courses: ${err}`); + view.webview.postMessage({ + command: MessageCommand.ERROR, + data: { message: `Failed to fetch courses: ${err}` }, + }); } } @@ -140,26 +208,37 @@ export class SidebarProvider implements vscode.WebviewViewProvider { this.testingService?.addGradeable(term, courseId, gradeableId, gradeableId); if (this.gitService) { - view.webview.postMessage({ command: 'gradeStarted', message: 'Staging and committing...' }); + view.webview.postMessage({ command: MessageCommand.GRADE_STARTED, data: { message: 'Staging and committing...' } }); const commitMessage = new Date().toLocaleString(undefined, { dateStyle: 'short', timeStyle: 'medium', }); - await this.gitService.commit(commitMessage, { all: true }); - view.webview.postMessage({ command: 'gradeStarted', message: 'Pushing...' }); - await this.gitService.push(); + try { + await this.gitService.commit(commitMessage, { all: true }); + view.webview.postMessage({ command: MessageCommand.GRADE_STARTED, data: { message: 'Pushing...' } }); + await this.gitService.push(); + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); + if (err === 'No changes to commit.') { + view.webview.postMessage({ + command: MessageCommand.GRADE_STARTED, + data: { message: 'No changes to commit. Skipping git push.' }, + }); + } else { + throw error; + } + } } - view.webview.postMessage({ command: 'gradeStarted', message: 'Submitting for grading...' }); + view.webview.postMessage({ command: MessageCommand.GRADE_STARTED, data: { message: 'Submitting for grading...' } }); await this.apiService.submitVCSGradable(term, courseId, gradeableId); - view.webview.postMessage({ command: 'gradeStarted', message: 'Grading in progress. Polling for results...' }); + view.webview.postMessage({ command: MessageCommand.GRADE_STARTED, data: { message: 'Grading in progress. Polling for results...' } }); const gradeDetails = await this.apiService.pollGradeDetailsUntilComplete(term, courseId, gradeableId); - const previousAttempts = await this.apiService.fetchPreviousAttempts(term, courseId, gradeableId); view.webview.postMessage({ - command: 'displayGrade', + command: MessageCommand.GRADE_COMPLETED, data: { term, courseId, @@ -169,18 +248,14 @@ export class SidebarProvider implements vscode.WebviewViewProvider { } }); - vscode.commands.executeCommand('extension.showGradePanel', { - term, - courseId, - gradeableId, - gradeDetails, - previousAttempts, - }); - this.testingService?.runGradeableWithResult(term, courseId, gradeableId, gradeableId, gradeDetails); - } catch (error: any) { - vscode.window.showErrorMessage(`Failed to grade: ${error.message}`); - view.webview.postMessage({ command: 'error', message: `Failed to grade: ${error.message}` }); + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); + vscode.window.showErrorMessage(`Failed to grade: ${err}`); + view.webview.postMessage({ + command: MessageCommand.ERROR, + data: { message: `Failed to grade: ${err}` }, + }); } } diff --git a/src/typings/message.ts b/src/typings/message.ts index 6e743b7..7a0a860 100644 --- a/src/typings/message.ts +++ b/src/typings/message.ts @@ -1,5 +1,6 @@ export const MessageCommand = { FETCH_AND_DISPLAY_COURSES: 'fetchAndDisplayCourses', + DISPLAY_COURSES: 'displayCourses', GRADE: 'grade', GRADE_STARTED: 'gradeStarted', GRADE_COMPLETED: 'gradeCompleted', @@ -8,4 +9,10 @@ export const MessageCommand = { GRADE_PAUSED: 'gradePaused', GRASE_RESUMED: 'gradeResumed', GRADE_ABORTED: 'gradeAborted', -} as const; \ No newline at end of file + ERROR: 'error', +} as const; + +export type WebViewMessage = { + command: (typeof MessageCommand)[keyof typeof MessageCommand]; + [key: string]: string | number | boolean | object | null | undefined; +}; \ No newline at end of file diff --git a/src/typings/vscode-git.d.ts b/src/typings/vscode-git.d.ts index 898cff1..d467672 100644 --- a/src/typings/vscode-git.d.ts +++ b/src/typings/vscode-git.d.ts @@ -1,8 +1,18 @@ -/** - * Minimal typings for the built-in Git extension API (vscode.git). - * Used for push, pull, and commit in GitService. - */ -import type { Uri } from 'vscode'; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Uri, Event, Disposable, ProviderResult, Command, CancellationToken } from 'vscode'; +export { ProviderResult } from 'vscode'; + +export interface Git { + readonly path: string; +} + +export interface InputBox { + value: string; +} export const enum ForcePushMode { Force, @@ -10,33 +20,493 @@ export const enum ForcePushMode { ForceWithLeaseIfIncludes, } +export const enum RefType { + Head, + RemoteHead, + Tag +} + +export interface Ref { + readonly type: RefType; + readonly name?: string; + readonly commit?: string; + readonly commitDetails?: Commit; + readonly remote?: string; +} + +export interface UpstreamRef { + readonly remote: string; + readonly name: string; + readonly commit?: string; +} + +export interface Branch extends Ref { + readonly upstream?: UpstreamRef; + readonly ahead?: number; + readonly behind?: number; +} + +export interface CommitShortStat { + readonly files: number; + readonly insertions: number; + readonly deletions: number; +} + +export interface Commit { + readonly hash: string; + readonly message: string; + readonly parents: string[]; + readonly authorDate?: Date; + readonly authorName?: string; + readonly authorEmail?: string; + readonly commitDate?: Date; + readonly shortStat?: CommitShortStat; +} + +export interface Submodule { + readonly name: string; + readonly path: string; + readonly url: string; +} + +export interface Remote { + readonly name: string; + readonly fetchUrl?: string; + readonly pushUrl?: string; + readonly isReadOnly: boolean; +} + +export interface Worktree { + readonly name: string; + readonly path: string; + readonly ref: string; + readonly main: boolean; + readonly detached: boolean; +} + +export const enum Status { + INDEX_MODIFIED, + INDEX_ADDED, + INDEX_DELETED, + INDEX_RENAMED, + INDEX_COPIED, + + MODIFIED, + DELETED, + UNTRACKED, + IGNORED, + INTENT_TO_ADD, + INTENT_TO_RENAME, + TYPE_CHANGED, + + ADDED_BY_US, + ADDED_BY_THEM, + DELETED_BY_US, + DELETED_BY_THEM, + BOTH_ADDED, + BOTH_DELETED, + BOTH_MODIFIED +} + +export interface Change { + + /** + * Returns either `originalUri` or `renameUri`, depending + * on whether this change is a rename change. When + * in doubt always use `uri` over the other two alternatives. + */ + readonly uri: Uri; + readonly originalUri: Uri; + readonly renameUri: Uri | undefined; + readonly status: Status; +} + +export interface DiffChange extends Change { + readonly insertions: number; + readonly deletions: number; +} + +export type RepositoryKind = 'repository' | 'submodule' | 'worktree'; + +export interface RepositoryState { + readonly HEAD: Branch | undefined; + readonly refs: Ref[]; + readonly remotes: Remote[]; + readonly submodules: Submodule[]; + readonly worktrees: Worktree[]; + readonly rebaseCommit: Commit | undefined; + + readonly mergeChanges: Change[]; + readonly indexChanges: Change[]; + readonly workingTreeChanges: Change[]; + readonly untrackedChanges: Change[]; + + readonly onDidChange: Event; +} + +export interface RepositoryUIState { + readonly selected: boolean; + readonly onDidChange: Event; +} + +export interface RepositoryAccessDetails { + readonly rootUri: Uri; + readonly lastAccessTime: number; +} + +/** + * Log options. + */ +export interface LogOptions { + /** Max number of log entries to retrieve. If not specified, the default is 32. */ + readonly maxEntries?: number; + readonly path?: string; + /** A commit range, such as "0a47c67f0fb52dd11562af48658bc1dff1d75a38..0bb4bdea78e1db44d728fd6894720071e303304f" */ + readonly range?: string; + readonly reverse?: boolean; + readonly sortByAuthorDate?: boolean; + readonly shortStats?: boolean; + readonly author?: string; + readonly grep?: string; + readonly refNames?: string[]; + readonly maxParents?: number; + readonly skip?: number; +} + export interface CommitOptions { all?: boolean | 'tracked'; amend?: boolean; signoff?: boolean; + /** + * true - sign the commit + * false - do not sign the commit + * undefined - use the repository/global git config + */ signCommit?: boolean; empty?: boolean; noVerify?: boolean; + requireUserConfig?: boolean; + useEditor?: boolean; + verbose?: boolean; + /** + * string - execute the specified command after the commit operation + * undefined - execute the command specified in git.postCommitCommand + * after the commit operation + * null - do not execute any command after the commit operation + */ + postCommitCommand?: string | null; +} + +export interface FetchOptions { + remote?: string; + ref?: string; + all?: boolean; + prune?: boolean; + depth?: number; +} + +export interface InitOptions { + defaultBranch?: string; +} + +export interface CloneOptions { + parentPath?: Uri; + /** + * ref is only used if the repository cache is missed. + */ + ref?: string; + recursive?: boolean; + /** + * If no postCloneAction is provided, then the users setting for git.openAfterClone is used. + */ + postCloneAction?: 'none'; +} + +export interface RefQuery { + readonly contains?: string; + readonly count?: number; + readonly pattern?: string | string[]; + readonly sort?: 'alphabetically' | 'committerdate' | 'creatordate'; +} + +export interface BranchQuery extends RefQuery { + readonly remote?: boolean; } export interface Repository { + readonly rootUri: Uri; - commit(message: string, opts?: CommitOptions): Promise; + readonly inputBox: InputBox; + readonly state: RepositoryState; + readonly ui: RepositoryUIState; + readonly kind: RepositoryKind; + + readonly onDidCommit: Event; + readonly onDidCheckout: Event; + + getConfigs(): Promise<{ key: string; value: string; }[]>; + getConfig(key: string): Promise; + setConfig(key: string, value: string): Promise; + unsetConfig(key: string): Promise; + getGlobalConfig(key: string): Promise; + + getObjectDetails(treeish: string, path: string): Promise<{ mode: string, object: string, size: number }>; + detectObjectType(object: string): Promise<{ mimetype: string, encoding?: string }>; + buffer(ref: string, path: string): Promise; + show(ref: string, path: string): Promise; + getCommit(ref: string): Promise; + + add(paths: string[]): Promise; + revert(paths: string[]): Promise; + clean(paths: string[]): Promise; + + apply(patch: string, reverse?: boolean): Promise; + apply(patch: string, options?: { allowEmpty?: boolean; reverse?: boolean; threeWay?: boolean; }): Promise; + diff(cached?: boolean): Promise; + diffWithHEAD(): Promise; + diffWithHEAD(path: string): Promise; + diffWithHEADShortStats(path?: string): Promise; + diffWith(ref: string): Promise; + diffWith(ref: string, path: string): Promise; + diffIndexWithHEAD(): Promise; + diffIndexWithHEAD(path: string): Promise; + diffIndexWithHEADShortStats(path?: string): Promise; + diffIndexWith(ref: string): Promise; + diffIndexWith(ref: string, path: string): Promise; + diffBlobs(object1: string, object2: string): Promise; + diffBetween(ref1: string, ref2: string): Promise; + diffBetween(ref1: string, ref2: string, path: string): Promise; + diffBetweenPatch(ref1: string, ref2: string, path?: string): Promise; + diffBetweenWithStats(ref1: string, ref2: string, path?: string): Promise; + diffBetweenWithStats2(ref: string, path?: string): Promise; + + hashObject(data: string): Promise; + + createBranch(name: string, checkout: boolean, ref?: string): Promise; + deleteBranch(name: string, force?: boolean): Promise; + getBranch(name: string): Promise; + getBranches(query: BranchQuery, cancellationToken?: CancellationToken): Promise; + getBranchBase(name: string): Promise; + setBranchUpstream(name: string, upstream: string): Promise; + + checkIgnore(paths: string[]): Promise>; + + getRefs(query: RefQuery, cancellationToken?: CancellationToken): Promise; + + getMergeBase(ref1: string, ref2: string): Promise; + + tag(name: string, message: string, ref?: string | undefined): Promise; + deleteTag(name: string): Promise; + + status(): Promise; + checkout(treeish: string): Promise; + + addRemote(name: string, url: string): Promise; + removeRemote(name: string): Promise; + renameRemote(name: string, newName: string): Promise; + + fetch(options?: FetchOptions): Promise; + fetch(remote?: string, ref?: string, depth?: number): Promise; pull(unshallow?: boolean): Promise; - push( - remoteName?: string, - branchName?: string, - setUpstream?: boolean, - force?: ForcePushMode - ): Promise; + push(remoteName?: string, branchName?: string, setUpstream?: boolean, force?: ForcePushMode): Promise; + + blame(path: string): Promise; + log(options?: LogOptions): Promise; + + commit(message: string, opts?: CommitOptions): Promise; + merge(ref: string): Promise; + mergeAbort(): Promise; + rebase(branch: string): Promise; + + createStash(options?: { message?: string; includeUntracked?: boolean; staged?: boolean }): Promise; + applyStash(index?: number): Promise; + popStash(index?: number): Promise; + dropStash(index?: number): Promise; + + createWorktree(options?: { path?: string; commitish?: string; branch?: string }): Promise; + deleteWorktree(path: string, options?: { force?: boolean }): Promise; + + migrateChanges(sourceRepositoryPath: string, options?: { confirmation?: boolean; deleteFromSource?: boolean; untracked?: boolean }): Promise; + + generateRandomBranchName(): Promise; + + isBranchProtected(branch?: Branch): boolean; +} + +export interface RemoteSource { + readonly name: string; + readonly description?: string; + readonly url: string | string[]; +} + +export interface RemoteSourceProvider { + readonly name: string; + readonly icon?: string; // codicon name + readonly supportsQuery?: boolean; + getRemoteSources(query?: string): ProviderResult; + getBranches?(url: string): ProviderResult; + publishRepository?(repository: Repository): Promise; +} + +export interface RemoteSourcePublisher { + readonly name: string; + readonly icon?: string; // codicon name + publishRepository(repository: Repository): Promise; +} + +export interface Credentials { + readonly username: string; + readonly password: string; +} + +export interface CredentialsProvider { + getCredentials(host: Uri): ProviderResult; +} + +export interface PostCommitCommandsProvider { + getCommands(repository: Repository): Command[]; +} + +export interface PushErrorHandler { + handlePushError(repository: Repository, remote: Remote, refspec: string, error: Error & { gitErrorCode: GitErrorCodes }): Promise; +} + +export interface BranchProtection { + readonly remote: string; + readonly rules: BranchProtectionRule[]; +} + +export interface BranchProtectionRule { + readonly include?: string[]; + readonly exclude?: string[]; +} + +export interface BranchProtectionProvider { + onDidChangeBranchProtection: Event; + provideBranchProtection(): BranchProtection[]; } -export interface GitApi { +export interface AvatarQueryCommit { + readonly hash: string; + readonly authorName?: string; + readonly authorEmail?: string; +} + +export interface AvatarQuery { + readonly commits: AvatarQueryCommit[]; + readonly size: number; +} + +export interface SourceControlHistoryItemDetailsProvider { + provideAvatar(repository: Repository, query: AvatarQuery): ProviderResult>; + provideHoverCommands(repository: Repository): ProviderResult; + provideMessageLinks(repository: Repository, message: string): ProviderResult; +} + +export type APIState = 'uninitialized' | 'initialized'; + +export interface PublishEvent { + repository: Repository; + branch?: string; +} + +export interface API { + readonly state: APIState; + readonly onDidChangeState: Event; + readonly onDidPublish: Event; + readonly git: Git; readonly repositories: Repository[]; + readonly recentRepositories: Iterable; + readonly onDidOpenRepository: Event; + readonly onDidCloseRepository: Event; + + toGitUri(uri: Uri, ref: string): Uri; getRepository(uri: Uri): Repository | null; + getRepositoryRoot(uri: Uri): Promise; + getRepositoryWorkspace(uri: Uri): Promise; + init(root: Uri, options?: InitOptions): Promise; + /** + * Checks the cache of known cloned repositories, and clones if the repository is not found. + * Make sure to pass `postCloneAction` 'none' if you want to have the uri where you can find the repository returned. + * @returns The URI of a folder or workspace file which, when opened, will open the cloned repository. + */ + clone(uri: Uri, options?: CloneOptions): Promise; + openRepository(root: Uri): Promise; + + registerRemoteSourcePublisher(publisher: RemoteSourcePublisher): Disposable; + registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable; + registerCredentialsProvider(provider: CredentialsProvider): Disposable; + registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable; + registerPushErrorHandler(handler: PushErrorHandler): Disposable; + registerBranchProtectionProvider(root: Uri, provider: BranchProtectionProvider): Disposable; + registerSourceControlHistoryItemDetailsProvider(provider: SourceControlHistoryItemDetailsProvider): Disposable; } export interface GitExtension { + readonly enabled: boolean; - getAPI(version: 1): GitApi; + readonly onDidChangeEnablement: Event; + + /** + * Returns a specific API version. + * + * Throws error if git extension is disabled. You can listen to the + * [GitExtension.onDidChangeEnablement](#GitExtension.onDidChangeEnablement) event + * to know when the extension becomes enabled/disabled. + * + * @param version Version number. + * @returns API instance + */ + getAPI(version: 1): API; } + +export const enum GitErrorCodes { + BadConfigFile = 'BadConfigFile', + BadRevision = 'BadRevision', + AuthenticationFailed = 'AuthenticationFailed', + NoUserNameConfigured = 'NoUserNameConfigured', + NoUserEmailConfigured = 'NoUserEmailConfigured', + NoRemoteRepositorySpecified = 'NoRemoteRepositorySpecified', + NotAGitRepository = 'NotAGitRepository', + NotASafeGitRepository = 'NotASafeGitRepository', + NotAtRepositoryRoot = 'NotAtRepositoryRoot', + Conflict = 'Conflict', + StashConflict = 'StashConflict', + UnmergedChanges = 'UnmergedChanges', + PushRejected = 'PushRejected', + ForcePushWithLeaseRejected = 'ForcePushWithLeaseRejected', + ForcePushWithLeaseIfIncludesRejected = 'ForcePushWithLeaseIfIncludesRejected', + RemoteConnectionError = 'RemoteConnectionError', + DirtyWorkTree = 'DirtyWorkTree', + CantOpenResource = 'CantOpenResource', + GitNotFound = 'GitNotFound', + CantCreatePipe = 'CantCreatePipe', + PermissionDenied = 'PermissionDenied', + CantAccessRemote = 'CantAccessRemote', + RepositoryNotFound = 'RepositoryNotFound', + RepositoryIsLocked = 'RepositoryIsLocked', + BranchNotFullyMerged = 'BranchNotFullyMerged', + NoRemoteReference = 'NoRemoteReference', + InvalidBranchName = 'InvalidBranchName', + BranchAlreadyExists = 'BranchAlreadyExists', + NoLocalChanges = 'NoLocalChanges', + NoStashFound = 'NoStashFound', + LocalChangesOverwritten = 'LocalChangesOverwritten', + NoUpstreamBranch = 'NoUpstreamBranch', + IsInSubmodule = 'IsInSubmodule', + WrongCase = 'WrongCase', + CantLockRef = 'CantLockRef', + CantRebaseMultipleBranches = 'CantRebaseMultipleBranches', + PatchDoesNotApply = 'PatchDoesNotApply', + NoPathFound = 'NoPathFound', + UnknownPath = 'UnknownPath', + EmptyCommitMessage = 'EmptyCommitMessage', + BranchFastForwardRejected = 'BranchFastForwardRejected', + BranchNotYetBorn = 'BranchNotYetBorn', + TagConflict = 'TagConflict', + CherryPickEmpty = 'CherryPickEmpty', + CherryPickConflict = 'CherryPickConflict', + WorktreeContainsChanges = 'WorktreeContainsChanges', + WorktreeAlreadyExists = 'WorktreeAlreadyExists', + WorktreeBranchAlreadyUsed = 'WorktreeBranchAlreadyUsed' +} \ No newline at end of file From 368d9d1894122e359a0414dc4c307364ef25c1f2 Mon Sep 17 00:00:00 2001 From: Riley Smith Date: Thu, 19 Mar 2026 23:42:12 -0700 Subject: [PATCH 09/20] format + lint --- .vscode-test.mjs | 2 +- .vscode/extensions.json | 12 +- .vscode/launch.json | 26 +- .vscode/settings.json | 16 +- .vscode/tasks.json | 32 +- CHANGELOG.md | 2 +- README.md | 6 + eslint.config.mjs | 21 +- package.json | 2 +- src/extension.ts | 78 +++-- src/interfaces/AutoGraderDetails.ts | 55 ++-- src/interfaces/Courses.ts | 14 +- src/interfaces/Gradables.ts | 26 +- src/interfaces/Responses.ts | 21 +- src/services/apiClient.ts | 268 ++++++++------- src/services/apiService.ts | 353 +++++++++++--------- src/services/authService.ts | 319 +++++++++--------- src/services/courseRepoResolver.ts | 267 +++++++-------- src/services/gitService.ts | 178 +++++----- src/services/testingService.ts | 486 +++++++++++++++------------ src/sidebar/login.html | 128 +++---- src/sidebarContent.ts | 20 +- src/sidebarProvider.ts | 494 +++++++++++++++------------- src/test/extension.test.ts | 10 +- src/typings/message.ts | 28 +- 25 files changed, 1541 insertions(+), 1323 deletions(-) diff --git a/.vscode-test.mjs b/.vscode-test.mjs index b62ba25..49fac78 100644 --- a/.vscode-test.mjs +++ b/.vscode-test.mjs @@ -1,5 +1,5 @@ import { defineConfig } from '@vscode/test-cli'; export default defineConfig({ - files: 'out/test/**/*.test.js', + files: 'out/test/**/*.test.js', }); diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 186459d..5906abf 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,8 +1,8 @@ { - // See http://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format - "recommendations": [ - "dbaeumer.vscode-eslint", - "ms-vscode.extension-test-runner" - ] + // See http://go.microsoft.com/fwlink/?LinkId=827846 + // for the documentation about the extensions.json format + "recommendations": [ + "dbaeumer.vscode-eslint", + "ms-vscode.extension-test-runner" + ] } diff --git a/.vscode/launch.json b/.vscode/launch.json index 8880465..a0ca3cb 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -3,19 +3,15 @@ // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 { - "version": "0.2.0", - "configurations": [ - { - "name": "Run Extension", - "type": "extensionHost", - "request": "launch", - "args": [ - "--extensionDevelopmentPath=${workspaceFolder}" - ], - "outFiles": [ - "${workspaceFolder}/out/**/*.js" - ], - "preLaunchTask": "${defaultBuildTask}" - } - ] + "version": "0.2.0", + "configurations": [ + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "args": ["--extensionDevelopmentPath=${workspaceFolder}"], + "outFiles": ["${workspaceFolder}/out/**/*.js"], + "preLaunchTask": "${defaultBuildTask}" + } + ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index afdab66..ffeaf91 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,11 +1,11 @@ // Place your settings in this file to overwrite default and user settings. { - "files.exclude": { - "out": false // set this to true to hide the "out" folder with the compiled JS files - }, - "search.exclude": { - "out": true // set this to false to include "out" folder in search results - }, - // Turn off tsc task auto detection since we have the necessary tasks as npm scripts - "typescript.tsc.autoDetect": "off" + "files.exclude": { + "out": false // set this to true to hide the "out" folder with the compiled JS files + }, + "search.exclude": { + "out": true // set this to false to include "out" folder in search results + }, + // Turn off tsc task auto detection since we have the necessary tasks as npm scripts + "typescript.tsc.autoDetect": "off" } diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 3b17e53..078ff7e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,20 +1,20 @@ // See https://go.microsoft.com/fwlink/?LinkId=733558 // for the documentation about the tasks.json format { - "version": "2.0.0", - "tasks": [ - { - "type": "npm", - "script": "watch", - "problemMatcher": "$tsc-watch", - "isBackground": true, - "presentation": { - "reveal": "never" - }, - "group": { - "kind": "build", - "isDefault": true - } - } - ] + "version": "2.0.0", + "tasks": [ + { + "type": "npm", + "script": "watch", + "problemMatcher": "$tsc-watch", + "isBackground": true, + "presentation": { + "reveal": "never" + }, + "group": { + "kind": "build", + "isDefault": true + } + } + ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index c9a250a..1a7244b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,4 +6,4 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how ## [Unreleased] -- Initial release \ No newline at end of file +- Initial release diff --git a/README.md b/README.md index a816d42..73f2092 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,25 @@ # Submitty Extension for VS Code ## Overview + The Submitty Extension for VS Code integrates the Submitty grading system directly into Visual Studio Code, allowing users to easily submit assignments, view grades, and interact with their courses without leaving the editor. ## Features + - **Assignment Submission**: Submit assignments directly from VS Code. - **Grade Retrieval**: View grades and feedback within the editor. - **Course Management**: Access course information and assignment details. - **Error & Feedback Display**: Get inline feedback on submissions. ## Setup + 1. Open the **Submitty Extension**. 2. Enter your **Submitty server URL**. 3. Authenticate using your **username and password**. 4. Select your **course** from the available list. ## Usage + - **Submit an Assignment**: 1. Open the relevant assignment file. 2. Click on the HW you want graded. @@ -24,9 +28,11 @@ The Submitty Extension for VS Code integrates the Submitty grading system direct - Open the Submitty panel to view assignment grades and instructor feedback. ## Requirements + - A valid Submitty account. ## Roadmap + - [ ] Allow users to access homeowrk - [ ] Figure out a way to grade homework and display results back to users - [ ] Display test results with feedback diff --git a/eslint.config.mjs b/eslint.config.mjs index 1645563..e0906fa 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -9,7 +9,15 @@ export default defineConfig([ // --- 1. Global Ignores --- // Files and directories to ignore across the entire project { - ignores: ['out/**', 'dist/**', '**/*.d.ts', 'node_modules/**', '.vscode-test/**', '.vscode-test.mjs', 'eslint.config.mjs'], + ignores: [ + 'out/**', + 'dist/**', + '**/*.d.ts', + 'node_modules/**', + '.vscode-test/**', + '.vscode-test.mjs', + 'eslint.config.mjs', + ], }, // --- 2. Base Configurations (Applied to ALL files by default) --- @@ -31,7 +39,7 @@ export default defineConfig([ parserOptions: { projectService: true, }, - } + }, }, // --- 3. Configuration for VS Code Extension (Node.js/TypeScript) --- @@ -66,10 +74,10 @@ export default defineConfig([ format: ['camelCase', 'PascalCase'], }, ], - 'curly': 'warn', // Require curly braces for all control statements - 'eqeqeq': 'warn', // Require the use of '===' and '!==' + curly: 'warn', // Require curly braces for all control statements + eqeqeq: 'warn', // Require the use of '===' and '!==' 'no-throw-literal': 'warn', // Disallow throwing literals as exceptions - 'semi': 'off', // Let Prettier handle semicolons (or enforce no semicolons) + semi: 'off', // Let Prettier handle semicolons (or enforce no semicolons) '@typescript-eslint/no-floating-promises': 'error', // Good for async operations '@typescript-eslint/explicit-function-return-type': [ 'warn', @@ -90,5 +98,4 @@ export default defineConfig([ '@typescript-eslint/no-explicit-any': 'off', // Or 'warn' depending on your preference }, }, - -]); \ No newline at end of file +]); diff --git a/package.json b/package.json index 4766aab..1a9a1cf 100644 --- a/package.json +++ b/package.json @@ -93,4 +93,4 @@ "axios": "^1.7.8", "keytar": "^7.9.0" } -} \ No newline at end of file +} diff --git a/src/extension.ts b/src/extension.ts index 30d2371..26ebc24 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -8,39 +8,57 @@ import { CourseRepoResolver } from './services/courseRepoResolver'; import type { Gradable } from './interfaces/Gradables'; export function activate(context: vscode.ExtensionContext): void { - const apiService = ApiService.getInstance(context, ''); - const testingService = new TestingService(context, apiService); - const gitService = new GitService(); - const authService = AuthService.getInstance(context); - const sidebarProvider = new SidebarProvider(context, testingService, gitService); + const apiService = ApiService.getInstance(context, ''); + const testingService = new TestingService(context, apiService); + const gitService = new GitService(); + const authService = AuthService.getInstance(context); + const sidebarProvider = new SidebarProvider( + context, + testingService, + gitService + ); - context.subscriptions.push( - vscode.window.registerWebviewViewProvider('submittyWebview', sidebarProvider) - ); + context.subscriptions.push( + vscode.window.registerWebviewViewProvider( + 'submittyWebview', + sidebarProvider + ) + ); - // Preload gradables into the Test Explorer when the workspace appears - // to be a course-tied repo. - void (async () => { - try { - await authService.initialize(); - const resolver = new CourseRepoResolver(apiService, authService, gitService); - const courseContext = await resolver.resolveCourseContextFromRepo(); - if (!courseContext) { - return; - } + // Preload gradables into the Test Explorer when the workspace appears + // to be a course-tied repo. + void (async () => { + try { + await authService.initialize(); + const resolver = new CourseRepoResolver( + apiService, + authService, + gitService + ); + const courseContext = await resolver.resolveCourseContextFromRepo(); + if (!courseContext) { + return; + } - const gradablesResponse = await apiService.fetchGradables(courseContext.courseId, courseContext.term); - const gradables = Object.values(gradablesResponse.data); - - for (const g of gradables) { - testingService.addGradeable(courseContext.term, courseContext.courseId, g.id, g.title || g.id); - } - } catch (e) { - const err = e instanceof Error ? e.message : String(e); - console.warn(`Failed to preload gradables: ${err}`); - } - })(); + const gradablesResponse = await apiService.fetchGradables( + courseContext.courseId, + courseContext.term + ); + const gradables = Object.values(gradablesResponse.data); + for (const g of gradables) { + testingService.addGradeable( + courseContext.term, + courseContext.courseId, + g.id, + g.title || g.id + ); + } + } catch (e) { + const err = e instanceof Error ? e.message : String(e); + console.warn(`Failed to preload gradables: ${err}`); + } + })(); } -export function deactivate() { } \ No newline at end of file +export function deactivate() {} diff --git a/src/interfaces/AutoGraderDetails.ts b/src/interfaces/AutoGraderDetails.ts index 7a94b18..d654df4 100644 --- a/src/interfaces/AutoGraderDetails.ts +++ b/src/interfaces/AutoGraderDetails.ts @@ -1,42 +1,41 @@ export interface AutoGraderDetails { - status: string - data: AutoGraderDetailsData + status: string; + data: AutoGraderDetailsData; } export interface AutoGraderDetailsData { - is_queued: boolean - queue_position: number - is_grading: boolean - has_submission: boolean - autograding_complete: boolean - has_active_version: boolean - highest_version: number - total_points: number - total_percent: number - test_cases: TestCase[] + is_queued: boolean; + queue_position: number; + is_grading: boolean; + has_submission: boolean; + autograding_complete: boolean; + has_active_version: boolean; + highest_version: number; + total_points: number; + total_percent: number; + test_cases: TestCase[]; } export interface TestCase { - name: string - details: string - is_extra_credit: boolean - points_available: number - has_extra_results: boolean - points_received: number - testcase_message: string - autochecks: Autocheck[] + name: string; + details: string; + is_extra_credit: boolean; + points_available: number; + has_extra_results: boolean; + points_received: number; + testcase_message: string; + autochecks: Autocheck[]; } export interface Autocheck { - description: string - messages: Message[] - diff_viewer: Record - expected: string - actual: string + description: string; + messages: Message[]; + diff_viewer: Record; + expected: string; + actual: string; } export interface Message { - message: string - type: string + message: string; + type: string; } - diff --git a/src/interfaces/Courses.ts b/src/interfaces/Courses.ts index 9d60f51..354aaf8 100644 --- a/src/interfaces/Courses.ts +++ b/src/interfaces/Courses.ts @@ -1,8 +1,8 @@ export interface Course { - semester: string; - title: string; - display_name: string; - display_semester: string; - user_group: number; - registration_section: string; -} \ No newline at end of file + semester: string; + title: string; + display_name: string; + display_semester: string; + user_group: number; + registration_section: string; +} diff --git a/src/interfaces/Gradables.ts b/src/interfaces/Gradables.ts index eef1dad..a8dff20 100644 --- a/src/interfaces/Gradables.ts +++ b/src/interfaces/Gradables.ts @@ -1,18 +1,18 @@ export interface Gradable { - id: string - title: string - instructions_url: string - gradeable_type: string - syllabus_bucket: string - section: number - section_name: string - due_date: DueDate - vcs_repository: string - vcs_subdirectory: string + id: string; + title: string; + instructions_url: string; + gradeable_type: string; + syllabus_bucket: string; + section: number; + section_name: string; + due_date: DueDate; + vcs_repository: string; + vcs_subdirectory: string; } export interface DueDate { - date: string - timezone_type: number - timezone: string + date: string; + timezone_type: number; + timezone: string; } diff --git a/src/interfaces/Responses.ts b/src/interfaces/Responses.ts index 5e7ef1f..6f6c2e0 100644 --- a/src/interfaces/Responses.ts +++ b/src/interfaces/Responses.ts @@ -1,22 +1,21 @@ -import { Course } from "./Courses"; -import { Gradable } from "./Gradables"; - +import { Course } from './Courses'; +import { Gradable } from './Gradables'; export interface ApiResponse { - status: string; - data: T; - message?: string; + status: string; + data: T; + message?: string; } export type CourseResponse = ApiResponse<{ - unarchived_courses: Course[]; - dropped_courses: Course[]; + unarchived_courses: Course[]; + dropped_courses: Course[]; }>; export type LoginResponse = ApiResponse<{ - token: string; + token: string; }>; export type GradableResponse = ApiResponse<{ - [key: string]: Gradable; -}>; \ No newline at end of file + [key: string]: Gradable; +}>; diff --git a/src/services/apiClient.ts b/src/services/apiClient.ts index dc0ce93..d3fd209 100644 --- a/src/services/apiClient.ts +++ b/src/services/apiClient.ts @@ -3,131 +3,145 @@ import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'; export class ApiClient { - private client: AxiosInstance; - - constructor(baseURL: string = '', defaultHeaders: Record = {}) { - this.client = axios.create({ - baseURL, - headers: { - 'Content-Type': 'application/json', - ...defaultHeaders, - }, - timeout: 30000, // 30 seconds timeout - }); - - // Request interceptor - this.client.interceptors.request.use( - (config) => { - // Add any request logging or modification here - return config; - }, - (error: Error) => { - return Promise.reject(new Error(error.message || 'Request failed')); - } - ); - - // Response interceptor - this.client.interceptors.response.use( - (response) => { - return response; - }, - (error: Error) => { - // Handle common errors here - return Promise.reject(new Error(error.message || 'Response failed')); - } - ); - } - - /** - * Set the base URL for all requests - */ - setBaseURL(baseURL: string): void { - this.client.defaults.baseURL = baseURL; - } - - /** - * Set default headers for all requests - */ - setDefaultHeaders(headers: Record): void { - this.client.defaults.headers.common = { - ...this.client.defaults.headers.common, - ...headers, - }; - } - - /** - * Set the Authorization token for all requests - */ - setToken(token: string): void { - this.client.defaults.headers.common['Authorization'] = `${token}`; - } - - /** - * GET request - */ - async get(url: string, config?: AxiosRequestConfig): Promise> { - return this.client.get(url, config); - } - - /** - * POST request - */ - async post( - url: string, - data?: any, - config?: AxiosRequestConfig - ): Promise> { - return this.client.post(url, data, config); - } - - /** - * PUT request - */ - async put( - url: string, - data?: any, - config?: AxiosRequestConfig - ): Promise> { - return this.client.put(url, data, config); - } - - /** - * PATCH request - */ - async patch( - url: string, - data?: any, - config?: AxiosRequestConfig - ): Promise> { - return this.client.patch(url, data, config); - } - - /** - * DELETE request - */ - async delete(url: string, config?: AxiosRequestConfig): Promise> { - return this.client.delete(url, config); - } - - /** - * HEAD request - */ - async head(url: string, config?: AxiosRequestConfig): Promise> { - return this.client.head(url, config); - } - - /** - * OPTIONS request - */ - async options(url: string, config?: AxiosRequestConfig): Promise> { - return this.client.options(url, config); - } - - /** - * Get the underlying axios instance for advanced usage - */ - getAxiosInstance(): AxiosInstance { - return this.client; - } + private client: AxiosInstance; + + constructor( + baseURL: string = '', + defaultHeaders: Record = {} + ) { + this.client = axios.create({ + baseURL, + headers: { + 'Content-Type': 'application/json', + ...defaultHeaders, + }, + timeout: 30000, // 30 seconds timeout + }); + + // Request interceptor + this.client.interceptors.request.use( + config => { + // Add any request logging or modification here + return config; + }, + (error: Error) => { + return Promise.reject(new Error(error.message || 'Request failed')); + } + ); + + // Response interceptor + this.client.interceptors.response.use( + response => { + return response; + }, + (error: Error) => { + // Handle common errors here + return Promise.reject(new Error(error.message || 'Response failed')); + } + ); + } + + /** + * Set the base URL for all requests + */ + setBaseURL(baseURL: string): void { + this.client.defaults.baseURL = baseURL; + } + + /** + * Set default headers for all requests + */ + setDefaultHeaders(headers: Record): void { + this.client.defaults.headers.common = { + ...this.client.defaults.headers.common, + ...headers, + }; + } + + /** + * Set the Authorization token for all requests + */ + setToken(token: string): void { + this.client.defaults.headers.common['Authorization'] = `${token}`; + } + + /** + * GET request + */ + async get( + url: string, + config?: AxiosRequestConfig + ): Promise> { + return this.client.get(url, config); + } + + /** + * POST request + */ + async post( + url: string, + data?: any, + config?: AxiosRequestConfig + ): Promise> { + return this.client.post(url, data, config); + } + + /** + * PUT request + */ + async put( + url: string, + data?: any, + config?: AxiosRequestConfig + ): Promise> { + return this.client.put(url, data, config); + } + + /** + * PATCH request + */ + async patch( + url: string, + data?: any, + config?: AxiosRequestConfig + ): Promise> { + return this.client.patch(url, data, config); + } + + /** + * DELETE request + */ + async delete( + url: string, + config?: AxiosRequestConfig + ): Promise> { + return this.client.delete(url, config); + } + + /** + * HEAD request + */ + async head( + url: string, + config?: AxiosRequestConfig + ): Promise> { + return this.client.head(url, config); + } + + /** + * OPTIONS request + */ + async options( + url: string, + config?: AxiosRequestConfig + ): Promise> { + return this.client.options(url, config); + } + + /** + * Get the underlying axios instance for advanced usage + */ + getAxiosInstance(): AxiosInstance { + return this.client; + } } - diff --git a/src/services/apiService.ts b/src/services/apiService.ts index d35964e..0706c47 100644 --- a/src/services/apiService.ts +++ b/src/services/apiService.ts @@ -2,181 +2,214 @@ import * as vscode from 'vscode'; import { ApiClient } from './apiClient'; -import { CourseResponse, LoginResponse, GradableResponse } from '../interfaces/Responses'; +import { + CourseResponse, + LoginResponse, + GradableResponse, +} from '../interfaces/Responses'; import { AutoGraderDetails } from '../interfaces/AutoGraderDetails'; function getErrorMessage(error: unknown, fallback: string): string { - if (error instanceof Error) { - return error.message || fallback; + if (error instanceof Error) { + return error.message || fallback; + } + if (typeof error === 'object' && error) { + const maybeAxiosError = error as { + response?: { data?: { message?: unknown } }; + }; + const msg = maybeAxiosError.response?.data?.message; + if (typeof msg === 'string' && msg.trim()) { + return msg; } - if (typeof error === 'object' && error) { - const maybeAxiosError = error as { response?: { data?: { message?: unknown } } }; - const msg = maybeAxiosError.response?.data?.message; - if (typeof msg === 'string' && msg.trim()) { - return msg; - } - } - return fallback; + } + return fallback; } export class ApiService { - private client: ApiClient; - private static instance: ApiService; - - constructor(private context: vscode.ExtensionContext, apiBaseUrl: string) { - this.client = new ApiClient(apiBaseUrl); - } - - // set token for local api client - setAuthorizationToken(token: string): void { - this.client.setToken(token); - } + private client: ApiClient; + private static instance: ApiService; + + constructor( + private context: vscode.ExtensionContext, + apiBaseUrl: string + ) { + this.client = new ApiClient(apiBaseUrl); + } + + // set token for local api client + setAuthorizationToken(token: string): void { + this.client.setToken(token); + } + + // set base URL for local api client + setBaseUrl(baseUrl: string): void { + this.client.setBaseURL(baseUrl); + } + + /** + * Login to the Submitty API + */ + async login(userId: string, password: string): Promise { + try { + const response = await this.client.post( + '/api/token', + { + user_id: userId, + password: password, + }, + { + headers: { 'Content-Type': 'multipart/form-data' }, + } + ); - // set base URL for local api client - setBaseUrl(baseUrl: string): void { - this.client.setBaseURL(baseUrl); + const token: string = response.data.data.token; + return token; + } catch (error: unknown) { + throw new Error(getErrorMessage(error, 'Login failed.')); } - - /** - * Login to the Submitty API - */ - async login(userId: string, password: string): Promise { - try { - const response = await this.client.post( - '/api/token', - { - user_id: userId, - password: password, - }, - { - headers: { 'Content-Type': 'multipart/form-data' }, - } - ); - - const token: string = response.data.data.token; - return token; - } catch (error: unknown) { - throw new Error(getErrorMessage(error, 'Login failed.')); - } + } + + async fetchMe(): Promise { + try { + const response = await this.client.get('/api/me'); + return response.data; + } catch (error: unknown) { + throw new Error(getErrorMessage(error, 'Failed to fetch me.')); } - - async fetchMe(): Promise { - try { - const response = await this.client.get('/api/me'); - return response.data; - } catch (error: unknown) { - throw new Error(getErrorMessage(error, 'Failed to fetch me.')); - } + } + + /** + * Fetch all courses for the authenticated user + */ + async fetchCourses(_token?: string): Promise { + try { + const response = await this.client.get('/api/courses'); + return response.data; + } catch (error: unknown) { + console.error('Error fetching courses:', error); + throw new Error(getErrorMessage(error, 'Failed to fetch courses.')); } - - - /** - * Fetch all courses for the authenticated user - */ - async fetchCourses(_token?: string): Promise { - try { - const response = await this.client.get('/api/courses'); - return response.data; - } catch (error: unknown) { - console.error('Error fetching courses:', error); - throw new Error(getErrorMessage(error, 'Failed to fetch courses.')); - } + } + + async fetchGradables( + courseId: string, + term: string + ): Promise { + try { + const url = `/api/${term}/${courseId}/gradeables`; + const response = await this.client.get(url); + return response.data; + } catch (error: unknown) { + console.error('Error fetching gradables:', error); + throw new Error(getErrorMessage(error, 'Failed to fetch gradables.')); } - - async fetchGradables(courseId: string, term: string): Promise { - try { - const url = `/api/${term}/${courseId}/gradeables`; - const response = await this.client.get(url); - return response.data; - } catch (error: unknown) { - console.error('Error fetching gradables:', error); - throw new Error(getErrorMessage(error, 'Failed to fetch gradables.')); - } + } + + /** + * Fetch grade details for a specific homework assignment + */ + async fetchGradeDetails( + term: string, + courseId: string, + gradeableId: string + ): Promise { + try { + const response = await this.client.get( + `/api/${term}/${courseId}/gradeable/${gradeableId}/values` + ); + return response.data; + } catch (error: unknown) { + console.error('Error fetching grade details:', error); + throw new Error(getErrorMessage(error, 'Failed to fetch grade details.')); } - - /** - * Fetch grade details for a specific homework assignment - */ - async fetchGradeDetails(term: string, courseId: string, gradeableId: string): Promise { - try { - const response = await this.client.get(`/api/${term}/${courseId}/gradeable/${gradeableId}/values`); - return response.data; - } catch (error: unknown) { - console.error('Error fetching grade details:', error); - throw new Error(getErrorMessage(error, 'Failed to fetch grade details.')); - } + } + + /** + * Poll fetchGradeDetails until autograding_complete is true and test_cases has data. + * @param intervalMs Delay between requests (default 2000) + * @param timeoutMs Stop after this many ms (default 300000 = 5 min); 0 = no timeout + * @returns The final AutoGraderDetails with complete data + */ + async pollGradeDetailsUntilComplete( + term: string, + courseId: string, + gradeableId: string, + options?: { + intervalMs?: number; + timeoutMs?: number; + token?: vscode.CancellationToken; } - - /** - * Poll fetchGradeDetails until autograding_complete is true and test_cases has data. - * @param intervalMs Delay between requests (default 2000) - * @param timeoutMs Stop after this many ms (default 300000 = 5 min); 0 = no timeout - * @returns The final AutoGraderDetails with complete data - */ - async pollGradeDetailsUntilComplete( - term: string, - courseId: string, - gradeableId: string, - options?: { intervalMs?: number; timeoutMs?: number; token?: vscode.CancellationToken } - ): Promise { - const intervalMs = options?.intervalMs ?? 2000; - const timeoutMs = options?.timeoutMs ?? 300000; - const token = options?.token; - const deadline = timeoutMs > 0 ? Date.now() + timeoutMs : 0; - - const isComplete = (res: AutoGraderDetails): boolean => - res?.data?.autograding_complete === true && - Array.isArray(res.data.test_cases) && - res.data.test_cases.length > 0; - - for (; ;) { - if (token?.isCancellationRequested) { - throw new Error('Cancelled'); - } - if (deadline > 0 && Date.now() >= deadline) { - throw new Error('Autograding did not complete within the timeout.'); - } - - const result = await this.fetchGradeDetails(term, courseId, gradeableId); - if (isComplete(result)) { - return result; - } - - await new Promise((r) => setTimeout(r, intervalMs)); - } + ): Promise { + const intervalMs = options?.intervalMs ?? 2000; + const timeoutMs = options?.timeoutMs ?? 300000; + const token = options?.token; + const deadline = timeoutMs > 0 ? Date.now() + timeoutMs : 0; + + const isComplete = (res: AutoGraderDetails): boolean => + res?.data?.autograding_complete === true && + Array.isArray(res.data.test_cases) && + res.data.test_cases.length > 0; + + for (;;) { + if (token?.isCancellationRequested) { + throw new Error('Cancelled'); + } + if (deadline > 0 && Date.now() >= deadline) { + throw new Error('Autograding did not complete within the timeout.'); + } + + const result = await this.fetchGradeDetails(term, courseId, gradeableId); + if (isComplete(result)) { + return result; + } + + await new Promise(r => setTimeout(r, intervalMs)); } - - async submitVCSGradable(term: string, courseId: string, gradeableId: string): Promise { - try { - // git_repo_id is literally not used, but is required by the API *ugh* - const url = `/api/${term}/${courseId}/gradeable/${gradeableId}/upload?vcs_upload=true&git_repo_id=true`; - const response = await this.client.post(url); - return response.data; - } catch (error: unknown) { - console.error('Error submitt`ing VCS gradable:', error); - throw new Error(getErrorMessage(error, 'Failed to submit VCS gradable.')); - } + } + + async submitVCSGradable( + term: string, + courseId: string, + gradeableId: string + ): Promise { + try { + // git_repo_id is literally not used, but is required by the API *ugh* + const url = `/api/${term}/${courseId}/gradeable/${gradeableId}/upload?vcs_upload=true&git_repo_id=true`; + const response = await this.client.post(url); + return response.data; + } catch (error: unknown) { + console.error('Error submitt`ing VCS gradable:', error); + throw new Error(getErrorMessage(error, 'Failed to submit VCS gradable.')); } - - - /** - * Fetch previous attempts for a specific homework assignment - */ - async fetchPreviousAttempts(term: string, courseId: string, gradeableId: string): Promise { - try { - const url = `/api/${term}/${courseId}/gradeable/${gradeableId}/attempts`; - const response = await this.client.get(url); - return response.data; - } catch (error: unknown) { - console.error('Error fetching previous attempts:', error); - throw new Error(getErrorMessage(error, 'Failed to fetch previous attempts.')); - } + } + + /** + * Fetch previous attempts for a specific homework assignment + */ + async fetchPreviousAttempts( + term: string, + courseId: string, + gradeableId: string + ): Promise { + try { + const url = `/api/${term}/${courseId}/gradeable/${gradeableId}/attempts`; + const response = await this.client.get(url); + return response.data; + } catch (error: unknown) { + console.error('Error fetching previous attempts:', error); + throw new Error( + getErrorMessage(error, 'Failed to fetch previous attempts.') + ); } - - static getInstance(context: vscode.ExtensionContext, apiBaseUrl: string): ApiService { - if (!ApiService.instance) { - ApiService.instance = new ApiService(context, apiBaseUrl); - } - return ApiService.instance; + } + + static getInstance( + context: vscode.ExtensionContext, + apiBaseUrl: string + ): ApiService { + if (!ApiService.instance) { + ApiService.instance = new ApiService(context, apiBaseUrl); } -} \ No newline at end of file + return ApiService.instance; + } +} diff --git a/src/services/authService.ts b/src/services/authService.ts index afbd445..0e03e17 100644 --- a/src/services/authService.ts +++ b/src/services/authService.ts @@ -3,177 +3,190 @@ import { ApiService } from './apiService'; import * as keytar from 'keytar'; export class AuthService { - // we need to store the token in the global state, but also store it in the - // system keychain - private context: vscode.ExtensionContext; - private apiService: ApiService; - private static instance: AuthService; - constructor(context: vscode.ExtensionContext, apiBaseUrl: string = "") { - this.context = context; - this.apiService = ApiService.getInstance(context, ""); + // we need to store the token in the global state, but also store it in the + // system keychain + private context: vscode.ExtensionContext; + private apiService: ApiService; + private static instance: AuthService; + constructor(context: vscode.ExtensionContext, apiBaseUrl: string = '') { + this.context = context; + this.apiService = ApiService.getInstance(context, ''); + } + + async initialize(): Promise { + console.log('Initializing AuthService'); + + // Get base URL from configuration + const config = vscode.workspace.getConfiguration('submitty'); + let baseUrl = config.get('baseUrl', ''); + + // If base URL is configured, set it on the API service + if (baseUrl) { + this.apiService.setBaseUrl(baseUrl); } - async initialize(): Promise { - console.log("Initializing AuthService"); - - // Get base URL from configuration - const config = vscode.workspace.getConfiguration('submitty'); - let baseUrl = config.get('baseUrl', ''); - - // If base URL is configured, set it on the API service - if (baseUrl) { - this.apiService.setBaseUrl(baseUrl); - } - - const token = await this.getToken(); - console.log("Token:", token); - if (token) { - // Token exists, set it on the API service - this.apiService.setAuthorizationToken(token); - console.log("Token set on API service"); - - // If baseUrl isn't configured yet, fetch it now so API calls work. - if (!baseUrl) { - const inputUrl = await vscode.window.showInputBox({ - prompt: 'Enter Submitty API URL', - placeHolder: 'https://example.submitty.edu', - ignoreFocusOut: true, - validateInput: (value) => { - if (!value || value.trim().length === 0) { - return 'URL is required'; - } - try { - new URL(value); - return null; - } catch { - return 'Please enter a valid URL'; - } - }, - }); - - if (!inputUrl) { - return; - } - - baseUrl = inputUrl.trim(); - - await config.update('baseUrl', baseUrl, vscode.ConfigurationTarget.Global); - this.apiService.setBaseUrl(baseUrl); - } - - return; - } - - console.log("No token found, prompting for credentials"); - - // If no base URL is configured, prompt for it - if (!baseUrl) { - const inputUrl = await vscode.window.showInputBox({ - prompt: 'Enter Submitty API URL', - placeHolder: 'https://example.submitty.edu', - ignoreFocusOut: true, - validateInput: (value) => { - if (!value || value.trim().length === 0) { - return 'URL is required'; - } - try { - new URL(value); - return null; - } catch { - return 'Please enter a valid URL'; - } - } - }); - - if (!inputUrl) { - // User cancelled - return; + const token = await this.getToken(); + console.log('Token:', token); + if (token) { + // Token exists, set it on the API service + this.apiService.setAuthorizationToken(token); + console.log('Token set on API service'); + + // If baseUrl isn't configured yet, fetch it now so API calls work. + if (!baseUrl) { + const inputUrl = await vscode.window.showInputBox({ + prompt: 'Enter Submitty API URL', + placeHolder: 'https://example.submitty.edu', + ignoreFocusOut: true, + validateInput: value => { + if (!value || value.trim().length === 0) { + return 'URL is required'; } - - baseUrl = inputUrl.trim(); - - // Save base URL to configuration - await config.update('baseUrl', baseUrl, vscode.ConfigurationTarget.Global); - - // Set the base URL on the API service - this.apiService.setBaseUrl(baseUrl); - } - - const userId = await vscode.window.showInputBox({ - prompt: 'Enter your Submitty username', - placeHolder: 'Username', - ignoreFocusOut: true, - validateInput: (value) => { - if (!value || value.trim().length === 0) { - return 'Username is required'; - } - return null; + try { + new URL(value); + return null; + } catch { + return 'Please enter a valid URL'; } + }, }); - if (!userId) { - // User cancelled - return; + if (!inputUrl) { + return; } - const password = await vscode.window.showInputBox({ - prompt: 'Enter your Submitty password', - placeHolder: 'Password', - password: true, - ignoreFocusOut: true, - validateInput: (value) => { - if (!value || value.trim().length === 0) { - return 'Password is required'; - } - return null; - } - }); - - if (!password) { - // User cancelled - return; - } + baseUrl = inputUrl.trim(); - // Update API service with URL and login - try { - // Perform login - await this.login(userId.trim(), password); + await config.update( + 'baseUrl', + baseUrl, + vscode.ConfigurationTarget.Global + ); + this.apiService.setBaseUrl(baseUrl); + } - vscode.window.showInformationMessage('Successfully logged in to Submitty'); - } catch (error: unknown) { - const err = error instanceof Error ? error.message : String(error); - vscode.window.showErrorMessage(`Login failed: ${err}`); - throw error; - } + return; } - // store token - private async storeToken(token: string): Promise { - await keytar.setPassword('submittyToken', 'submittyToken', token); + console.log('No token found, prompting for credentials'); + + // If no base URL is configured, prompt for it + if (!baseUrl) { + const inputUrl = await vscode.window.showInputBox({ + prompt: 'Enter Submitty API URL', + placeHolder: 'https://example.submitty.edu', + ignoreFocusOut: true, + validateInput: value => { + if (!value || value.trim().length === 0) { + return 'URL is required'; + } + try { + new URL(value); + return null; + } catch { + return 'Please enter a valid URL'; + } + }, + }); + + if (!inputUrl) { + // User cancelled + return; + } + + baseUrl = inputUrl.trim(); + + // Save base URL to configuration + await config.update( + 'baseUrl', + baseUrl, + vscode.ConfigurationTarget.Global + ); + + // Set the base URL on the API service + this.apiService.setBaseUrl(baseUrl); } - // get token - private async getToken(): Promise { - return await keytar.getPassword('submittyToken', 'submittyToken'); - } + const userId = await vscode.window.showInputBox({ + prompt: 'Enter your Submitty username', + placeHolder: 'Username', + ignoreFocusOut: true, + validateInput: value => { + if (!value || value.trim().length === 0) { + return 'Username is required'; + } + return null; + }, + }); - // public method to get token - async getAuthorizationToken(): Promise { - return await this.getToken(); + if (!userId) { + // User cancelled + return; } - private async login(userId: string, password: string): Promise { - const token = await this.apiService.login(userId, password); - this.apiService.setAuthorizationToken(token); - // store token in system keychain - await this.storeToken(token); - return token; + const password = await vscode.window.showInputBox({ + prompt: 'Enter your Submitty password', + placeHolder: 'Password', + password: true, + ignoreFocusOut: true, + validateInput: value => { + if (!value || value.trim().length === 0) { + return 'Password is required'; + } + return null; + }, + }); + + if (!password) { + // User cancelled + return; } - static getInstance(context: vscode.ExtensionContext, apiBaseUrl: string = ""): AuthService { - if (!AuthService.instance) { - AuthService.instance = new AuthService(context); - } - return AuthService.instance; + // Update API service with URL and login + try { + // Perform login + await this.login(userId.trim(), password); + + vscode.window.showInformationMessage( + 'Successfully logged in to Submitty' + ); + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); + vscode.window.showErrorMessage(`Login failed: ${err}`); + throw error; + } + } + + // store token + private async storeToken(token: string): Promise { + await keytar.setPassword('submittyToken', 'submittyToken', token); + } + + // get token + private async getToken(): Promise { + return await keytar.getPassword('submittyToken', 'submittyToken'); + } + + // public method to get token + async getAuthorizationToken(): Promise { + return await this.getToken(); + } + + private async login(userId: string, password: string): Promise { + const token = await this.apiService.login(userId, password); + this.apiService.setAuthorizationToken(token); + // store token in system keychain + await this.storeToken(token); + return token; + } + + static getInstance( + context: vscode.ExtensionContext, + apiBaseUrl: string = '' + ): AuthService { + if (!AuthService.instance) { + AuthService.instance = new AuthService(context); } + return AuthService.instance; + } } diff --git a/src/services/courseRepoResolver.ts b/src/services/courseRepoResolver.ts index fae5ada..07bb787 100644 --- a/src/services/courseRepoResolver.ts +++ b/src/services/courseRepoResolver.ts @@ -7,157 +7,164 @@ import { GitService } from './gitService'; import type { Course } from '../interfaces/Courses'; export interface CourseRepoContext { - term: string; - courseId: string; + term: string; + courseId: string; } function normalizeForMatch(input: string): string { - return input - .toLowerCase() - // Keep only alphanumerics so variants like "Fall 2024" vs "fall2024" match. - .replace(/[^a-z0-9]/g, ''); + return ( + input + .toLowerCase() + // Keep only alphanumerics so variants like "Fall 2024" vs "fall2024" match. + .replace(/[^a-z0-9]/g, '') + ); } function readTextFileSafe(filePath: string): string | null { - try { - return fs.readFileSync(filePath, 'utf8'); - } catch { - return null; - } + try { + return fs.readFileSync(filePath, 'utf8'); + } catch { + return null; + } } function getGitDirPath(repoRootPath: string): string | null { - const gitEntryPath = path.join(repoRootPath, '.git'); - if (!fs.existsSync(gitEntryPath)) { - return null; + const gitEntryPath = path.join(repoRootPath, '.git'); + if (!fs.existsSync(gitEntryPath)) { + return null; + } + + try { + const stat = fs.statSync(gitEntryPath); + if (stat.isDirectory()) { + return gitEntryPath; } - try { - const stat = fs.statSync(gitEntryPath); - if (stat.isDirectory()) { - return gitEntryPath; - } - - if (stat.isFile()) { - // Worktrees/linked clones can have a .git file like: "gitdir: /abs/path/to/.git/worktrees/..." - const gitFileContents = readTextFileSafe(gitEntryPath); - if (!gitFileContents) { - return null; - } - - const match = gitFileContents.match(/^\s*gitdir:\s*(.+)\s*$/m); - if (!match?.[1]) { - return null; - } - - const gitdirRaw = match[1].trim(); - return path.isAbsolute(gitdirRaw) ? gitdirRaw : path.resolve(repoRootPath, gitdirRaw); - } - } catch { + if (stat.isFile()) { + // Worktrees/linked clones can have a .git file like: "gitdir: /abs/path/to/.git/worktrees/..." + const gitFileContents = readTextFileSafe(gitEntryPath); + if (!gitFileContents) { return null; - } + } + + const match = gitFileContents.match(/^\s*gitdir:\s*(.+)\s*$/m); + if (!match?.[1]) { + return null; + } + const gitdirRaw = match[1].trim(); + return path.isAbsolute(gitdirRaw) + ? gitdirRaw + : path.resolve(repoRootPath, gitdirRaw); + } + } catch { return null; + } + + return null; } function extractGitRemoteUrlsFromConfig(gitConfigText: string): string[] { - const urls: string[] = []; - - // Example: - // [remote "origin"] - // url = https://example/.../term/courseId/... - const urlRegex = /^\s*url\s*=\s*(.+)\s*$/gim; - let match: RegExpExecArray | null = null; - // eslint-disable-next-line no-cond-assign - while ((match = urlRegex.exec(gitConfigText))) { - const rawUrl = match[1]?.trim(); - if (rawUrl) { - urls.push(rawUrl); - } + const urls: string[] = []; + + // Example: + // [remote "origin"] + // url = https://example/.../term/courseId/... + const urlRegex = /^\s*url\s*=\s*(.+)\s*$/gim; + let match: RegExpExecArray | null = null; + + while ((match = urlRegex.exec(gitConfigText))) { + const rawUrl = match[1]?.trim(); + if (rawUrl) { + urls.push(rawUrl); } + } - return urls; + return urls; } export class CourseRepoResolver { - constructor( - private readonly apiService: ApiService, - private readonly authService: AuthService, - private readonly gitService: GitService - ) {} - - async resolveCourseContextFromRepo(): Promise { - const repo = this.gitService.getRepository(); - if (!repo) { - return null; - } - - const repoRootPath = repo.rootUri.fsPath; - const gitDirPath = getGitDirPath(repoRootPath); - if (!gitDirPath) { - return null; - } - - const gitConfigText = readTextFileSafe(path.join(gitDirPath, 'config')); - if (!gitConfigText) { - return null; - } - - const remoteUrls = extractGitRemoteUrlsFromConfig(gitConfigText); - if (remoteUrls.length === 0) { - return null; - } - - const token = await this.authService.getAuthorizationToken(); - if (!token) { - // No auth token -> can't map remotes to courses via API. - return null; - } - - const baseUrl = vscode.workspace.getConfiguration('submitty').get('baseUrl', ''); - if (!baseUrl) { - // Without baseUrl, we can't call the API. - return null; - } - - this.apiService.setBaseUrl(baseUrl); - this.apiService.setAuthorizationToken(token); - - // Fetch courses and match based on whether their (term, courseId) strings appear in remote URLs. - const coursesResponse = await this.apiService.fetchCourses(token); - const courses = coursesResponse.data.unarchived_courses; - - const remoteText = remoteUrls.join(' '); - const remoteNorm = normalizeForMatch(remoteText); - - let best: { course: Course; score: number } | null = null; - - for (const course of courses) { - const courseIdNorm = normalizeForMatch(course.title); - const termNorm = normalizeForMatch(course.semester); - - let score = 0; - - if (remoteNorm.includes(courseIdNorm)) { - score += 6; - } - if (remoteNorm.includes(termNorm)) { - score += 3; - } - if (remoteText.toLowerCase().includes(course.display_name.toLowerCase())) { - score += 1; - } - - if (!best || score > best.score) { - best = { course, score }; - } - } - - if (!best || best.score < 6) { - return null; - } - - return { term: best.course.semester, courseId: best.course.title }; + constructor( + private readonly apiService: ApiService, + private readonly authService: AuthService, + private readonly gitService: GitService + ) {} + + async resolveCourseContextFromRepo(): Promise { + const repo = this.gitService.getRepository(); + if (!repo) { + return null; } -} + const repoRootPath = repo.rootUri.fsPath; + const gitDirPath = getGitDirPath(repoRootPath); + if (!gitDirPath) { + return null; + } + + const gitConfigText = readTextFileSafe(path.join(gitDirPath, 'config')); + if (!gitConfigText) { + return null; + } + + const remoteUrls = extractGitRemoteUrlsFromConfig(gitConfigText); + if (remoteUrls.length === 0) { + return null; + } + + const token = await this.authService.getAuthorizationToken(); + if (!token) { + // No auth token -> can't map remotes to courses via API. + return null; + } + + const baseUrl = vscode.workspace + .getConfiguration('submitty') + .get('baseUrl', ''); + if (!baseUrl) { + // Without baseUrl, we can't call the API. + return null; + } + + this.apiService.setBaseUrl(baseUrl); + this.apiService.setAuthorizationToken(token); + + // Fetch courses and match based on whether their (term, courseId) strings appear in remote URLs. + const coursesResponse = await this.apiService.fetchCourses(token); + const courses = coursesResponse.data.unarchived_courses; + + const remoteText = remoteUrls.join(' '); + const remoteNorm = normalizeForMatch(remoteText); + + let best: { course: Course; score: number } | null = null; + + for (const course of courses) { + const courseIdNorm = normalizeForMatch(course.title); + const termNorm = normalizeForMatch(course.semester); + + let score = 0; + + if (remoteNorm.includes(courseIdNorm)) { + score += 6; + } + if (remoteNorm.includes(termNorm)) { + score += 3; + } + if ( + remoteText.toLowerCase().includes(course.display_name.toLowerCase()) + ) { + score += 1; + } + + if (!best || score > best.score) { + best = { course, score }; + } + } + + if (!best || best.score < 6) { + return null; + } + + return { term: best.course.semester, courseId: best.course.title }; + } +} diff --git a/src/services/gitService.ts b/src/services/gitService.ts index 4e4c889..c32ef19 100644 --- a/src/services/gitService.ts +++ b/src/services/gitService.ts @@ -1,10 +1,10 @@ -/* eslint-disable @typescript-eslint/no-unsafe-assignment */ -/* eslint-disable @typescript-eslint/no-redundant-type-constituents */ -/* eslint-disable @typescript-eslint/no-unsafe-call */ -/* eslint-disable @typescript-eslint/no-unsafe-member-access */ -/* eslint-disable @typescript-eslint/no-unsafe-return */ import * as vscode from 'vscode'; -import type { GitExtension, Repository, CommitOptions, ForcePushMode } from '../typings/vscode-git'; +import type { + GitExtension, + Repository, + CommitOptions, + ForcePushMode, +} from '../typings/vscode-git'; import { API } from '../typings/vscode-git'; /** @@ -12,95 +12,103 @@ import { API } from '../typings/vscode-git'; * push, pull, and commit in the current workspace repository. */ export class GitService { - private gitApi: API | null = null; + private gitApi: API | null = null; - private getApi(): API | null { - if (this.gitApi !== null) { - return this.gitApi; - } - const ext = vscode.extensions.getExtension('vscode.git'); - if (!ext?.isActive) { - return null; - } - try { - this.gitApi = ext.exports.getAPI(1); - return this.gitApi; - } catch { - return null; - } + private getApi(): API | null { + if (this.gitApi !== null) { + return this.gitApi; } - - /** - * Get the Git repository for the given URI, or the first workspace folder. - */ - getRepository(uri?: vscode.Uri): Repository | null { - const api = this.getApi(); - if (!api) { - return null; - } - if (uri) { - return api.getRepository(uri); - } - const folder = vscode.workspace.workspaceFolders?.[0]; - if (!folder) { - return api.repositories.length > 0 ? api.repositories[0] : null; - } - return api.getRepository(folder.uri) ?? api.repositories[0]; + const ext = vscode.extensions.getExtension('vscode.git'); + if (!ext?.isActive) { + return null; } + try { + this.gitApi = ext.exports.getAPI(1); + return this.gitApi; + } catch { + return null; + } + } - /** - * Commit changes in the repository. Optionally stage all changes first. - */ - async commit(message: string, options?: CommitOptions): Promise { - const repo = this.getRepository(); - if (!repo) { - throw new Error('No Git repository found. Open a workspace folder that is a Git repo.'); - } - - // check to see if there are any changes to commit - const status = (await repo.status()) as unknown as { - modified: unknown[]; - untracked: unknown[]; - deleted: unknown[]; - }; + /** + * Get the Git repository for the given URI, or the first workspace folder. + */ + getRepository(uri?: vscode.Uri): Repository | null { + const api = this.getApi(); + if (!api) { + return null; + } + if (uri) { + return api.getRepository(uri); + } + const folder = vscode.workspace.workspaceFolders?.[0]; + if (!folder) { + return api.repositories.length > 0 ? api.repositories[0] : null; + } + return api.getRepository(folder.uri) ?? api.repositories[0]; + } - if (status.modified.length === 0 && status.untracked.length === 0 && status.deleted.length === 0) { - throw new Error('No changes to commit.'); - } - await repo.commit(message, options); + /** + * Commit changes in the repository. Optionally stage all changes first. + */ + async commit(message: string, options?: CommitOptions): Promise { + const repo = this.getRepository(); + if (!repo) { + throw new Error( + 'No Git repository found. Open a workspace folder that is a Git repo.' + ); + } + // check to see if there are any changes to commit + const status = (await repo.status()) as unknown as { + modified: unknown[]; + untracked: unknown[]; + deleted: unknown[]; + }; + if ( + status.modified.length === 0 && + status.untracked.length === 0 && + status.deleted.length === 0 + ) { + throw new Error('No changes to commit.'); } + await repo.commit(message, options); + } - /** - * Pull from the current branch's upstream. - */ - async pull(): Promise { - const repo = this.getRepository(); - if (!repo) { - throw new Error('No Git repository found. Open a workspace folder that is a Git repo.'); - } - await repo.pull(); + /** + * Pull from the current branch's upstream. + */ + async pull(): Promise { + const repo = this.getRepository(); + if (!repo) { + throw new Error( + 'No Git repository found. Open a workspace folder that is a Git repo.' + ); } + await repo.pull(); + } - /** - * Push the current branch. Optionally set upstream or force push. - */ - async push(options?: { - remote?: string; - branch?: string; - setUpstream?: boolean; - force?: ForcePushMode; - }): Promise { - const repo = this.getRepository(); - if (!repo) { - throw new Error('No Git repository found. Open a workspace folder that is a Git repo.'); - } - await repo.push( - options?.remote, - options?.branch, - options?.setUpstream, - options?.force - ); + /** + * Push the current branch. Optionally set upstream or force push. + */ + async push(options?: { + remote?: string; + branch?: string; + setUpstream?: boolean; + force?: ForcePushMode; + }): Promise { + const repo = this.getRepository(); + if (!repo) { + throw new Error( + 'No Git repository found. Open a workspace folder that is a Git repo.' + ); } + await repo.push( + options?.remote, + options?.branch, + options?.setUpstream, + options?.force + ); + } } diff --git a/src/services/testingService.ts b/src/services/testingService.ts index a2bb026..d7b9b5d 100644 --- a/src/services/testingService.ts +++ b/src/services/testingService.ts @@ -1,6 +1,11 @@ import * as vscode from 'vscode'; import { ApiService } from './apiService'; -import type { AutoGraderDetails, AutoGraderDetailsData, TestCase, Autocheck } from '../interfaces/AutoGraderDetails'; +import type { + AutoGraderDetails, + AutoGraderDetailsData, + TestCase, + Autocheck, +} from '../interfaces/AutoGraderDetails'; const CONTROLLER_ID = 'submittyAutograder'; const CONTROLLER_LABEL = 'Submitty Autograder'; @@ -9,253 +14,304 @@ const POLL_INTERVAL_MS = 2000; const POLL_TIMEOUT_MS = 300000; // 5 min interface GradeableMeta { - term: string; - courseId: string; - gradeableId: string; + term: string; + courseId: string; + gradeableId: string; } export class TestingService { - private controller: vscode.TestController; - private rootItem: vscode.TestItem; - private gradeableMeta = new WeakMap(); - private testCaseMeta = new WeakMap(); + private controller: vscode.TestController; + private rootItem: vscode.TestItem; + private gradeableMeta = new WeakMap(); + private testCaseMeta = new WeakMap(); - constructor( - private readonly context: vscode.ExtensionContext, - private readonly apiService: ApiService - ) { - this.controller = vscode.tests.createTestController(CONTROLLER_ID, CONTROLLER_LABEL); - this.rootItem = this.controller.createTestItem(ROOT_ID, 'Submitty', undefined); - this.rootItem.canResolveChildren = true; - this.controller.items.add(this.rootItem); + constructor( + private readonly context: vscode.ExtensionContext, + private readonly apiService: ApiService + ) { + this.controller = vscode.tests.createTestController( + CONTROLLER_ID, + CONTROLLER_LABEL + ); + this.rootItem = this.controller.createTestItem( + ROOT_ID, + 'Submitty', + undefined + ); + this.rootItem.canResolveChildren = true; + this.controller.items.add(this.rootItem); - this.controller.resolveHandler = async (item) => this.resolveHandler(item); - const runProfile = this.controller.createRunProfile( - 'Run', - vscode.TestRunProfileKind.Run, - (request, token) => this.runHandler(request, token) - ); - runProfile.isDefault = true; + this.controller.resolveHandler = async item => this.resolveHandler(item); + const runProfile = this.controller.createRunProfile( + 'Run', + vscode.TestRunProfileKind.Run, + (request, token) => this.runHandler(request, token) + ); + runProfile.isDefault = true; - context.subscriptions.push(this.controller); - } + context.subscriptions.push(this.controller); + } - /** - * Add a gradeable to the Test Explorer so the user can run it and see results. - * Call this when the user triggers "Grade" or "Run autograder" for a gradeable. - */ - addGradeable(term: string, courseId: string, gradeableId: string, label: string): vscode.TestItem { - const id = `${term}/${courseId}/${gradeableId}`; - let item = this.rootItem.children.get(id); - if (!item) { - item = this.controller.createTestItem(id, label, undefined); - item.canResolveChildren = true; - this.gradeableMeta.set(item, { term, courseId, gradeableId }); - this.rootItem.children.add(item); - } - return item; + /** + * Add a gradeable to the Test Explorer so the user can run it and see results. + * Call this when the user triggers "Grade" or "Run autograder" for a gradeable. + */ + addGradeable( + term: string, + courseId: string, + gradeableId: string, + label: string + ): vscode.TestItem { + const id = `${term}/${courseId}/${gradeableId}`; + let item = this.rootItem.children.get(id); + if (!item) { + item = this.controller.createTestItem(id, label, undefined); + item.canResolveChildren = true; + this.gradeableMeta.set(item, { term, courseId, gradeableId }); + this.rootItem.children.add(item); } + return item; + } - /** - * Run a single gradeable in the Test Explorer using an already-fetched autograder result. - * Used when the user clicks "Grade" in the sidebar: submit → poll → then report here. - */ - runGradeableWithResult(term: string, courseId: string, gradeableId: string, label: string, result: AutoGraderDetails): void { - const item = this.addGradeable(term, courseId, gradeableId, label); - this.syncTestCaseChildren(item, result.data); + /** + * Run a single gradeable in the Test Explorer using an already-fetched autograder result. + * Used when the user clicks "Grade" in the sidebar: submit → poll → then report here. + */ + runGradeableWithResult( + term: string, + courseId: string, + gradeableId: string, + label: string, + result: AutoGraderDetails + ): void { + const item = this.addGradeable(term, courseId, gradeableId, label); + this.syncTestCaseChildren(item, result.data); - const run = this.controller.createTestRun(new vscode.TestRunRequest([item])); - run.started(item); - run.appendOutput(`Autograder completed for ${item.label}.\r\n`); - this.reportGradeableResult(run, item, result.data); - run.end(); + const run = this.controller.createTestRun( + new vscode.TestRunRequest([item]) + ); + run.started(item); + run.appendOutput(`Autograder completed for ${item.label}.\r\n`); + this.reportGradeableResult(run, item, result.data); + run.end(); + } + + private getGradeableMeta(item: vscode.TestItem): GradeableMeta | undefined { + return this.gradeableMeta.get(item); + } + + /** + * Convert HTML from autograder actual/expected into plain text for the Test Explorer diff. + * Strips tags and decodes common entities so the diff view is readable. + */ + private stripHtml(html: string): string { + if (!html || typeof html !== 'string') { + return ''; } + const text = html + .replace(//gi, '\n') + .replace(/<\/div>/gi, '\n') + .replace(/<\/p>/gi, '\n') + .replace(/<[^>]+>/g, '') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/&/g, '&') + .replace(/"/g, '"') + .replace(/'/g, "'") + .replace(/ /g, ' '); + return text.replace(/\n{3,}/g, '\n\n').trim(); + } - private getGradeableMeta(item: vscode.TestItem): GradeableMeta | undefined { - return this.gradeableMeta.get(item); + private formatAutocheckOutput( + autochecks: Autocheck[] | undefined, + getValue: (ac: Autocheck) => string + ): string { + if (!autochecks?.length) { + return ''; } + const parts = autochecks.map(ac => { + const value = this.stripHtml(getValue(ac)); + if (!value) { + return ''; + } + return `[${ac.description}]\n${value}`; + }); + return parts.filter(Boolean).join('\n\n'); + } - /** - * Convert HTML from autograder actual/expected into plain text for the Test Explorer diff. - * Strips tags and decodes common entities so the diff view is readable. - */ - private stripHtml(html: string): string { - if (!html || typeof html !== 'string') { - return ''; - } - const text = html - .replace(//gi, '\n') - .replace(/<\/div>/gi, '\n') - .replace(/<\/p>/gi, '\n') - .replace(/<[^>]+>/g, '') - .replace(/</g, '<') - .replace(/>/g, '>') - .replace(/&/g, '&') - .replace(/"/g, '"') - .replace(/'/g, "'") - .replace(/ /g, ' '); - return text.replace(/\n{3,}/g, '\n\n').trim(); + /** + * Format the messages array from all autochecks (e.g. "ERROR: ..." with type failure/warning). + */ + private formatAutocheckMessages(autochecks: Autocheck[] | undefined): string { + if (!autochecks?.length) { + return ''; } + const parts = autochecks.map(ac => { + const msgLines = (ac.messages ?? []).map( + m => ` • ${m.message}${m.type ? ` (${m.type})` : ''}` + ); + if (msgLines.length === 0) { + return ''; + } + return `[${ac.description}]\n${msgLines.join('\n')}`; + }); + return parts.filter(Boolean).join('\n\n'); + } - private formatAutocheckOutput(autochecks: Autocheck[] | undefined, getValue: (ac: Autocheck) => string): string { - if (!autochecks?.length) { - return ''; - } - const parts = autochecks.map((ac) => { - const value = this.stripHtml(getValue(ac)); - if (!value) { - return ''; - } - return `[${ac.description}]\n${value}`; - }); - return parts.filter(Boolean).join('\n\n'); + private async resolveHandler( + item: vscode.TestItem | undefined + ): Promise { + if (!item) { + return; + } + const meta = this.getGradeableMeta(item); + if (!meta) { + return; + } + // Resolve: poll until complete and populate children (test cases) + try { + const result = await this.apiService.pollGradeDetailsUntilComplete( + meta.term, + meta.courseId, + meta.gradeableId, + { intervalMs: POLL_INTERVAL_MS, timeoutMs: POLL_TIMEOUT_MS } + ); + this.syncTestCaseChildren(item, result.data); + } catch (e) { + console.error('Submitty testing resolve failed:', e); } + } - /** - * Format the messages array from all autochecks (e.g. "ERROR: ..." with type failure/warning). - */ - private formatAutocheckMessages(autochecks: Autocheck[] | undefined): string { - if (!autochecks?.length) { - return ''; - } - const parts = autochecks.map((ac) => { - const msgLines = (ac.messages ?? []).map((m) => ` • ${m.message}${m.type ? ` (${m.type})` : ''}`); - if (msgLines.length === 0) { - return ''; - } - return `[${ac.description}]\n${msgLines.join('\n')}`; - }); - return parts.filter(Boolean).join('\n\n'); + private syncTestCaseChildren( + gradeableItem: vscode.TestItem, + data: AutoGraderDetailsData + ): void { + const cases = data.test_cases ?? []; + for (let i = 0; i < cases.length; i++) { + const tc = cases[i]; + const id = `tc-${i}-${tc.name ?? i}`; + let child = gradeableItem.children.get(id); + if (!child) { + child = this.controller.createTestItem( + id, + tc.name || `Test ${i + 1}`, + undefined + ); + this.testCaseMeta.set(child, tc); + gradeableItem.children.add(child); + } else { + this.testCaseMeta.set(child, tc); + } } + } - private async resolveHandler(item: vscode.TestItem | undefined): Promise { - if (!item) { - return; + private reportGradeableResult( + run: vscode.TestRun, + item: vscode.TestItem, + _data: AutoGraderDetailsData + ): void { + const start = Date.now(); + let allPassed = true; + item.children.forEach(child => { + const tc = this.testCaseMeta.get(child); + run.started(child); + if (tc) { + const passed = tc.points_received >= (tc.points_available ?? 0); + if (!passed) { + allPassed = false; } - const meta = this.getGradeableMeta(item); - if (!meta) { - return; + const duration = Date.now() - start; + const messageParts = [tc.testcase_message, tc.details].filter(Boolean); + const formattedMessages = this.formatAutocheckMessages(tc.autochecks); + if (formattedMessages) { + messageParts.push('--- Messages ---', formattedMessages); } - // Resolve: poll until complete and populate children (test cases) - try { - const result = await this.apiService.pollGradeDetailsUntilComplete( - meta.term, - meta.courseId, - meta.gradeableId, - { intervalMs: POLL_INTERVAL_MS, timeoutMs: POLL_TIMEOUT_MS } - ); - this.syncTestCaseChildren(item, result.data); - } catch (e) { - console.error('Submitty testing resolve failed:', e); + const messageText = messageParts.join('\n') || 'Failed'; + if (passed) { + run.passed(child, duration); + } else { + const msg = new vscode.TestMessage(messageText); + msg.expectedOutput = this.formatAutocheckOutput( + tc.autochecks, + ac => ac.expected + ); + msg.actualOutput = this.formatAutocheckOutput( + tc.autochecks, + ac => ac.actual + ); + run.failed(child, msg, duration); } - } + } else { + run.passed(child, 0); + } + }); - private syncTestCaseChildren(gradeableItem: vscode.TestItem, data: AutoGraderDetailsData): void { - const cases = data.test_cases ?? []; - for (let i = 0; i < cases.length; i++) { - const tc = cases[i]; - const id = `tc-${i}-${tc.name ?? i}`; - let child = gradeableItem.children.get(id); - if (!child) { - child = this.controller.createTestItem(id, tc.name || `Test ${i + 1}`, undefined); - this.testCaseMeta.set(child, tc); - gradeableItem.children.add(child); - } else { - this.testCaseMeta.set(child, tc); - } - } + if (item.children.size === 0) { + run.appendOutput(`No test cases in response.\r\n`); + run.failed(item, new vscode.TestMessage('No test cases returned.'), 0); + } else { + if (allPassed) { + run.passed(item, Date.now() - start); + } else { + run.failed( + item, + new vscode.TestMessage('Some test cases failed.'), + Date.now() - start + ); + } } + } - private reportGradeableResult(run: vscode.TestRun, item: vscode.TestItem, _data: AutoGraderDetailsData): void { - const start = Date.now(); - let allPassed = true; - item.children.forEach((child) => { - const tc = this.testCaseMeta.get(child); - run.started(child); - if (tc) { - const passed = tc.points_received >= (tc.points_available ?? 0); - if (!passed) { - allPassed = false; - } - const duration = Date.now() - start; - const messageParts = [tc.testcase_message, tc.details].filter(Boolean); - const formattedMessages = this.formatAutocheckMessages(tc.autochecks); - if (formattedMessages) { - messageParts.push('--- Messages ---', formattedMessages); - } - const messageText = messageParts.join('\n') || 'Failed'; - if (passed) { - run.passed(child, duration); - } else { - const msg = new vscode.TestMessage(messageText); - msg.expectedOutput = this.formatAutocheckOutput(tc.autochecks, (ac) => ac.expected); - msg.actualOutput = this.formatAutocheckOutput(tc.autochecks, (ac) => ac.actual); - run.failed(child, msg, duration); - } - } else { - run.passed(child, 0); - } - }); + private async runHandler( + request: vscode.TestRunRequest, + token: vscode.CancellationToken + ): Promise { + const run = this.controller.createTestRun(request); + const queue: vscode.TestItem[] = []; - if (item.children.size === 0) { - run.appendOutput(`No test cases in response.\r\n`); - run.failed(item, new vscode.TestMessage('No test cases returned.'), 0); + if (request.include) { + request.include.forEach(t => { + if (t.id === ROOT_ID) { + this.rootItem.children.forEach(c => queue.push(c)); } else { - if (allPassed) { - run.passed(item, Date.now() - start); - } else { - run.failed(item, new vscode.TestMessage('Some test cases failed.'), Date.now() - start); - } + queue.push(t); } + }); + } else { + this.rootItem.children.forEach(t => queue.push(t)); } - private async runHandler(request: vscode.TestRunRequest, token: vscode.CancellationToken): Promise { - const run = this.controller.createTestRun(request); - const queue: vscode.TestItem[] = []; - - if (request.include) { - request.include.forEach((t) => { - if (t.id === ROOT_ID) { - this.rootItem.children.forEach((c) => queue.push(c)); - } else { - queue.push(t); - } - }); - } else { - this.rootItem.children.forEach((t) => queue.push(t)); - } + while (queue.length > 0 && !token.isCancellationRequested) { + const item = queue.shift()!; + if (request.exclude?.includes(item)) { + continue; + } - while (queue.length > 0 && !token.isCancellationRequested) { - const item = queue.shift()!; - if (request.exclude?.includes(item)) { - continue; - } + const meta = this.getGradeableMeta(item); + if (!meta) { + continue; + } - const meta = this.getGradeableMeta(item); - if (!meta) { - continue; - } + run.started(item); + run.appendOutput(`Polling grade details for ${item.label}...\r\n`); - run.started(item); - run.appendOutput(`Polling grade details for ${item.label}...\r\n`); - - try { - const result = await this.apiService.pollGradeDetailsUntilComplete( - meta.term, - meta.courseId, - meta.gradeableId, - { intervalMs: POLL_INTERVAL_MS, timeoutMs: POLL_TIMEOUT_MS, token } - ); - const data = result.data; - this.syncTestCaseChildren(item, data); - this.reportGradeableResult(run, item, data); - } catch (e) { - const err = e instanceof Error ? e.message : String(e); - run.appendOutput(`Error: ${err}\r\n`); - run.failed(item, new vscode.TestMessage(err), 0); - } - } - - run.end(); + try { + const result = await this.apiService.pollGradeDetailsUntilComplete( + meta.term, + meta.courseId, + meta.gradeableId, + { intervalMs: POLL_INTERVAL_MS, timeoutMs: POLL_TIMEOUT_MS, token } + ); + const data = result.data; + this.syncTestCaseChildren(item, data); + this.reportGradeableResult(run, item, data); + } catch (e) { + const err = e instanceof Error ? e.message : String(e); + run.appendOutput(`Error: ${err}\r\n`); + run.failed(item, new vscode.TestMessage(err), 0); + } } + + run.end(); + } } diff --git a/src/sidebar/login.html b/src/sidebar/login.html index 6308db9..b466d15 100644 --- a/src/sidebar/login.html +++ b/src/sidebar/login.html @@ -1,84 +1,84 @@ - + - - + + - - + +

Submitty Login

- - + +
- - + +
- - + +
- - \ No newline at end of file + + diff --git a/src/sidebarContent.ts b/src/sidebarContent.ts index ff9de23..a16a84c 100644 --- a/src/sidebarContent.ts +++ b/src/sidebarContent.ts @@ -3,11 +3,21 @@ import * as path from 'path'; import * as fs from 'fs'; export function getLoginHtml(context: vscode.ExtensionContext): string { - const filePath = path.join(context.extensionPath, 'src', 'sidebar', 'login.html'); - return fs.readFileSync(filePath, 'utf8'); + const filePath = path.join( + context.extensionPath, + 'src', + 'sidebar', + 'login.html' + ); + return fs.readFileSync(filePath, 'utf8'); } export function getClassesHtml(context: vscode.ExtensionContext): string { - const filePath = path.join(context.extensionPath, 'src', 'sidebar', 'classes.html'); - return fs.readFileSync(filePath, 'utf8'); -} \ No newline at end of file + const filePath = path.join( + context.extensionPath, + 'src', + 'sidebar', + 'classes.html' + ); + return fs.readFileSync(filePath, 'utf8'); +} diff --git a/src/sidebarProvider.ts b/src/sidebarProvider.ts index 3665694..fdeb712 100644 --- a/src/sidebarProvider.ts +++ b/src/sidebarProvider.ts @@ -9,258 +9,311 @@ import { TestingService } from './services/testingService'; import { MessageCommand } from './typings/message'; export class SidebarProvider implements vscode.WebviewViewProvider { - private _view?: vscode.WebviewView; - private apiService: ApiService; - private authService: AuthService; - private isInitialized: boolean = false; - private visibilityDisposable?: vscode.Disposable; - private isLoadingCourses: boolean = false; + private _view?: vscode.WebviewView; + private apiService: ApiService; + private authService: AuthService; + private isInitialized: boolean = false; + private visibilityDisposable?: vscode.Disposable; + private isLoadingCourses: boolean = false; - constructor( - private readonly context: vscode.ExtensionContext, - private readonly testingService?: TestingService, - private readonly gitService?: GitService - ) { - this.apiService = ApiService.getInstance(this.context, ""); - this.authService = AuthService.getInstance(this.context); - } + constructor( + private readonly context: vscode.ExtensionContext, + private readonly testingService?: TestingService, + private readonly gitService?: GitService + ) { + this.apiService = ApiService.getInstance(this.context, ''); + this.authService = AuthService.getInstance(this.context); + } - async resolveWebviewView( - webviewView: vscode.WebviewView, - _context: vscode.WebviewViewResolveContext, - _token: vscode.CancellationToken - ): Promise { - this._view = webviewView; + async resolveWebviewView( + webviewView: vscode.WebviewView, + _context: vscode.WebviewViewResolveContext, + _token: vscode.CancellationToken + ): Promise { + this._view = webviewView; - webviewView.webview.options = { - enableScripts: true, - localResourceRoots: [vscode.Uri.joinPath(this.context.extensionUri, 'src', 'webview')], - }; + webviewView.webview.options = { + enableScripts: true, + localResourceRoots: [ + vscode.Uri.joinPath(this.context.extensionUri, 'src', 'webview'), + ], + }; - // Initially show blank screen - webviewView.webview.html = this.getBlankHtml(); + // Initially show blank screen + webviewView.webview.html = this.getBlankHtml(); - // Reload courses any time the view becomes visible again (e.g. user - // closes/hides the panel and comes back). - this.visibilityDisposable?.dispose(); - this.visibilityDisposable = webviewView.onDidChangeVisibility(async () => { - if (webviewView.visible) { - await this.loadCourses(); - } - }); + // Reload courses any time the view becomes visible again (e.g. user + // closes/hides the panel and comes back). + this.visibilityDisposable?.dispose(); + this.visibilityDisposable = webviewView.onDidChangeVisibility(async () => { + if (webviewView.visible) { + await this.loadCourses(); + } + }); - // Initialize authentication when sidebar is opened (only once) - if (!this.isInitialized) { - this.isInitialized = true; - try { - await this.authService.initialize(); + // Initialize authentication when sidebar is opened (only once) + if (!this.isInitialized) { + this.isInitialized = true; + try { + await this.authService.initialize(); - // After authentication, fetch and display courses - await this.loadCourses(); - } catch (error: any) { - console.error('Authentication initialization failed:', error); - // Error is already shown to user in authService - } - } else { - // If already initialized, just load courses - await this.loadCourses(); - } + // After authentication, fetch and display courses + await this.loadCourses(); + } catch (error: any) { + console.error('Authentication initialization failed:', error); + // Error is already shown to user in authService + } + } else { + // If already initialized, just load courses + await this.loadCourses(); + } + + // Handle messages from the webview + webviewView.webview.onDidReceiveMessage( + async message => { + await this.handleMessage(message, webviewView); + }, + undefined, + this.context.subscriptions + ); + } - // Handle messages from the webview - webviewView.webview.onDidReceiveMessage( - async (message) => { - await this.handleMessage(message, webviewView); - }, - undefined, - this.context.subscriptions - ); + private async loadCourses(): Promise { + if (!this._view) { + return; } - private async loadCourses(): Promise { - if (!this._view) { - return; - } + if (this.isLoadingCourses) { + return; + } - if (this.isLoadingCourses) { - return; - } + this.isLoadingCourses = true; + try { + const token = await this.authService.getAuthorizationToken(); + if (!token) { + return; + } - this.isLoadingCourses = true; - try { - const token = await this.authService.getAuthorizationToken(); - if (!token) { - return; - } + // Show classes HTML + this._view.webview.html = getClassesHtml(this.context); - // Show classes HTML - this._view.webview.html = getClassesHtml(this.context); + // Fetch and display courses + await this.fetchAndDisplayCourses(token, this._view); + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); + console.error('Failed to load courses:', error); + vscode.window.showErrorMessage(`Failed to load courses: ${err}`); + } finally { + this.isLoadingCourses = false; + } + } - // Fetch and display courses - await this.fetchAndDisplayCourses(token, this._view); - } catch (error: unknown) { - const err = error instanceof Error ? error.message : String(error); - console.error('Failed to load courses:', error); - vscode.window.showErrorMessage(`Failed to load courses: ${err}`); - } finally { - this.isLoadingCourses = false; - } + private async handleMessage( + message: unknown, + view: vscode.WebviewView + ): Promise { + console.log('handleMessage', message); + if (!message || typeof message !== 'object') { + return; + } + const msg = message as { command?: unknown; data?: unknown }; + if (typeof msg.command !== 'string') { + return; } - private async handleMessage(message: unknown, view: vscode.WebviewView): Promise { - console.log('handleMessage', message); - if (!message || typeof message !== 'object') { - return; - } - const msg = message as { command?: unknown; data?: unknown }; - if (typeof msg.command !== 'string') { - return; + switch (msg.command) { + case MessageCommand.FETCH_AND_DISPLAY_COURSES: + try { + const token = await this.authService.getAuthorizationToken(); + if (token) { + await this.fetchAndDisplayCourses(token, view); + } + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); + console.error('Failed to fetch and display courses:', error); + view.webview.postMessage({ + command: MessageCommand.ERROR, + data: { message: `Failed to fetch and display courses: ${err}` }, + }); } + break; + case MessageCommand.GRADE: + try { + const data = msg.data; + if (!data || typeof data !== 'object') { + throw new Error('Missing grade payload.'); + } + const dataObj = data as Record; + const term = typeof dataObj.term === 'string' ? dataObj.term : null; + const courseId = + typeof dataObj.courseId === 'string' ? dataObj.courseId : null; + const gradeableId = + typeof dataObj.gradeableId === 'string' + ? dataObj.gradeableId + : null; - switch (msg.command) { - case MessageCommand.FETCH_AND_DISPLAY_COURSES: - try { - const token = await this.authService.getAuthorizationToken(); - if (token) { - await this.fetchAndDisplayCourses(token, view); - } - } catch (error: unknown) { - const err = error instanceof Error ? error.message : String(error); - console.error('Failed to fetch and display courses:', error); - view.webview.postMessage({ - command: MessageCommand.ERROR, - data: { message: `Failed to fetch and display courses: ${err}` }, - }); - } - break; - case MessageCommand.GRADE: - try { - const data = msg.data; - if (!data || typeof data !== 'object') { - throw new Error('Missing grade payload.'); - } - const dataObj = data as Record; - const term = typeof dataObj.term === 'string' ? dataObj.term : null; - const courseId = typeof dataObj.courseId === 'string' ? dataObj.courseId : null; - const gradeableId = typeof dataObj.gradeableId === 'string' ? dataObj.gradeableId : null; - - if (!term || !courseId || !gradeableId) { - throw new Error('Invalid grade payload.'); - } - console.log('handleGrade', term, courseId, gradeableId); - await this.handleGrade(term, courseId, gradeableId, view); - } catch (error: unknown) { - const err = error instanceof Error ? error.message : String(error); - console.error('Failed to grade:', error); - view.webview.postMessage({ - command: MessageCommand.ERROR, - data: { message: `Failed to grade: ${err}` }, - }); - } - break; - default: - vscode.window.showWarningMessage(`Unknown command: ${msg.command}`); - view.webview.postMessage({ - command: MessageCommand.ERROR, - data: { message: `Unknown command: ${msg.command}` }, - }); - break; + if (!term || !courseId || !gradeableId) { + throw new Error('Invalid grade payload.'); + } + console.log('handleGrade', term, courseId, gradeableId); + await this.handleGrade(term, courseId, gradeableId, view); + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); + console.error('Failed to grade:', error); + view.webview.postMessage({ + command: MessageCommand.ERROR, + data: { message: `Failed to grade: ${err}` }, + }); } + break; + default: + vscode.window.showWarningMessage(`Unknown command: ${msg.command}`); + view.webview.postMessage({ + command: MessageCommand.ERROR, + data: { message: `Unknown command: ${msg.command}` }, + }); + break; } - private async fetchAndDisplayCourses(token: string, view: vscode.WebviewView): Promise { - try { - const courses = await this.apiService.fetchCourses(token); - const unarchived = courses.data.unarchived_courses; + } + private async fetchAndDisplayCourses( + token: string, + view: vscode.WebviewView + ): Promise { + try { + const courses = await this.apiService.fetchCourses(token); + const unarchived = courses.data.unarchived_courses; - const coursesWithGradables = await Promise.all( - unarchived.map(async (course) => { - let gradables: { id: string; title: string }[] = []; - try { - const gradableResponse = await this.apiService.fetchGradables(course.title, course.semester); - gradables = Object.values(gradableResponse.data || {}).map((g: Gradable) => ({ id: g.id, title: g.title || g.id })); - } catch (e) { - console.warn(`Failed to fetch gradables for ${course.title}:`, e); - } - return { - semester: course.semester, - title: course.title, - display_name: course.display_name || course.title, - gradables, - }; - }) + const coursesWithGradables = await Promise.all( + unarchived.map(async course => { + let gradables: { id: string; title: string }[] = []; + try { + const gradableResponse = await this.apiService.fetchGradables( + course.title, + course.semester + ); + gradables = Object.values(gradableResponse.data || {}).map( + (g: Gradable) => ({ id: g.id, title: g.title || g.id }) ); + } catch (e) { + console.warn(`Failed to fetch gradables for ${course.title}:`, e); + } + return { + semester: course.semester, + title: course.title, + display_name: course.display_name || course.title, + gradables, + }; + }) + ); - view.webview.postMessage({ - command: MessageCommand.DISPLAY_COURSES, - data: { courses: coursesWithGradables }, - }); + view.webview.postMessage({ + command: MessageCommand.DISPLAY_COURSES, + data: { courses: coursesWithGradables }, + }); + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); + vscode.window.showErrorMessage(`Failed to fetch courses: ${err}`); + view.webview.postMessage({ + command: MessageCommand.ERROR, + data: { message: `Failed to fetch courses: ${err}` }, + }); + } + } + + private async handleGrade( + term: string, + courseId: string, + gradeableId: string, + view: vscode.WebviewView + ): Promise { + try { + this.testingService?.addGradeable( + term, + courseId, + gradeableId, + gradeableId + ); + + if (this.gitService) { + view.webview.postMessage({ + command: MessageCommand.GRADE_STARTED, + data: { message: 'Staging and committing...' }, + }); + const commitMessage = new Date().toLocaleString(undefined, { + dateStyle: 'short', + timeStyle: 'medium', + }); + try { + await this.gitService.commit(commitMessage, { all: true }); + view.webview.postMessage({ + command: MessageCommand.GRADE_STARTED, + data: { message: 'Pushing...' }, + }); + await this.gitService.push(); } catch (error: unknown) { - const err = error instanceof Error ? error.message : String(error); - vscode.window.showErrorMessage(`Failed to fetch courses: ${err}`); + const err = error instanceof Error ? error.message : String(error); + if (err === 'No changes to commit.') { view.webview.postMessage({ - command: MessageCommand.ERROR, - data: { message: `Failed to fetch courses: ${err}` }, + command: MessageCommand.GRADE_STARTED, + data: { message: 'No changes to commit. Skipping git push.' }, }); + } else { + throw error; + } } - } + } - private async handleGrade(term: string, courseId: string, gradeableId: string, view: vscode.WebviewView): Promise { - try { - this.testingService?.addGradeable(term, courseId, gradeableId, gradeableId); + view.webview.postMessage({ + command: MessageCommand.GRADE_STARTED, + data: { message: 'Submitting for grading...' }, + }); + await this.apiService.submitVCSGradable(term, courseId, gradeableId); - if (this.gitService) { - view.webview.postMessage({ command: MessageCommand.GRADE_STARTED, data: { message: 'Staging and committing...' } }); - const commitMessage = new Date().toLocaleString(undefined, { - dateStyle: 'short', - timeStyle: 'medium', - }); - try { - await this.gitService.commit(commitMessage, { all: true }); - view.webview.postMessage({ command: MessageCommand.GRADE_STARTED, data: { message: 'Pushing...' } }); - await this.gitService.push(); - } catch (error: unknown) { - const err = error instanceof Error ? error.message : String(error); - if (err === 'No changes to commit.') { - view.webview.postMessage({ - command: MessageCommand.GRADE_STARTED, - data: { message: 'No changes to commit. Skipping git push.' }, - }); - } else { - throw error; - } - } - } - - view.webview.postMessage({ command: MessageCommand.GRADE_STARTED, data: { message: 'Submitting for grading...' } }); - await this.apiService.submitVCSGradable(term, courseId, gradeableId); - - view.webview.postMessage({ command: MessageCommand.GRADE_STARTED, data: { message: 'Grading in progress. Polling for results...' } }); - const gradeDetails = await this.apiService.pollGradeDetailsUntilComplete(term, courseId, gradeableId); - const previousAttempts = await this.apiService.fetchPreviousAttempts(term, courseId, gradeableId); + view.webview.postMessage({ + command: MessageCommand.GRADE_STARTED, + data: { message: 'Grading in progress. Polling for results...' }, + }); + const gradeDetails = await this.apiService.pollGradeDetailsUntilComplete( + term, + courseId, + gradeableId + ); + const previousAttempts = await this.apiService.fetchPreviousAttempts( + term, + courseId, + gradeableId + ); - view.webview.postMessage({ - command: MessageCommand.GRADE_COMPLETED, - data: { - term, - courseId, - gradeableId, - gradeDetails, - previousAttempts, - } - }); + view.webview.postMessage({ + command: MessageCommand.GRADE_COMPLETED, + data: { + term, + courseId, + gradeableId, + gradeDetails, + previousAttempts, + }, + }); - this.testingService?.runGradeableWithResult(term, courseId, gradeableId, gradeableId, gradeDetails); - } catch (error: unknown) { - const err = error instanceof Error ? error.message : String(error); - vscode.window.showErrorMessage(`Failed to grade: ${err}`); - view.webview.postMessage({ - command: MessageCommand.ERROR, - data: { message: `Failed to grade: ${err}` }, - }); - } + this.testingService?.runGradeableWithResult( + term, + courseId, + gradeableId, + gradeableId, + gradeDetails + ); + } catch (error: unknown) { + const err = error instanceof Error ? error.message : String(error); + vscode.window.showErrorMessage(`Failed to grade: ${err}`); + view.webview.postMessage({ + command: MessageCommand.ERROR, + data: { message: `Failed to grade: ${err}` }, + }); } + } - private getBlankHtml(): string { - return ` + private getBlankHtml(): string { + return ` @@ -279,6 +332,5 @@ export class SidebarProvider implements vscode.WebviewViewProvider { `; - } + } } - diff --git a/src/test/extension.test.ts b/src/test/extension.test.ts index 4ca0ab4..17e2eab 100644 --- a/src/test/extension.test.ts +++ b/src/test/extension.test.ts @@ -6,10 +6,10 @@ import * as vscode from 'vscode'; // import * as myExtension from '../../extension'; suite('Extension Test Suite', () => { - vscode.window.showInformationMessage('Start all tests.'); + vscode.window.showInformationMessage('Start all tests.'); - test('Sample test', () => { - assert.strictEqual(-1, [1, 2, 3].indexOf(5)); - assert.strictEqual(-1, [1, 2, 3].indexOf(0)); - }); + test('Sample test', () => { + assert.strictEqual(-1, [1, 2, 3].indexOf(5)); + assert.strictEqual(-1, [1, 2, 3].indexOf(0)); + }); }); diff --git a/src/typings/message.ts b/src/typings/message.ts index 7a0a860..74cdd08 100644 --- a/src/typings/message.ts +++ b/src/typings/message.ts @@ -1,18 +1,18 @@ export const MessageCommand = { - FETCH_AND_DISPLAY_COURSES: 'fetchAndDisplayCourses', - DISPLAY_COURSES: 'displayCourses', - GRADE: 'grade', - GRADE_STARTED: 'gradeStarted', - GRADE_COMPLETED: 'gradeCompleted', - GRADE_ERROR: 'gradeError', - GRADE_CANCELLED: 'gradeCancelled', - GRADE_PAUSED: 'gradePaused', - GRASE_RESUMED: 'gradeResumed', - GRADE_ABORTED: 'gradeAborted', - ERROR: 'error', + FETCH_AND_DISPLAY_COURSES: 'fetchAndDisplayCourses', + DISPLAY_COURSES: 'displayCourses', + GRADE: 'grade', + GRADE_STARTED: 'gradeStarted', + GRADE_COMPLETED: 'gradeCompleted', + GRADE_ERROR: 'gradeError', + GRADE_CANCELLED: 'gradeCancelled', + GRADE_PAUSED: 'gradePaused', + GRASE_RESUMED: 'gradeResumed', + GRADE_ABORTED: 'gradeAborted', + ERROR: 'error', } as const; export type WebViewMessage = { - command: (typeof MessageCommand)[keyof typeof MessageCommand]; - [key: string]: string | number | boolean | object | null | undefined; -}; \ No newline at end of file + command: (typeof MessageCommand)[keyof typeof MessageCommand]; + [key: string]: string | number | boolean | object | null | undefined; +}; From a9a1b9428ef857811ebd07921db7195830c1b761 Mon Sep 17 00:00:00 2001 From: Riley Smith Date: Fri, 20 Mar 2026 14:15:27 -0700 Subject: [PATCH 10/20] fix double import --- src/sidebarProvider.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/sidebarProvider.ts b/src/sidebarProvider.ts index fdeb712..935a8f7 100644 --- a/src/sidebarProvider.ts +++ b/src/sidebarProvider.ts @@ -3,7 +3,6 @@ import { getClassesHtml } from './sidebarContent'; import { ApiService } from './services/apiService'; import { AuthService } from './services/authService'; import { GitService } from './services/gitService'; -import type { TestingService } from './services/testingService'; import { Gradable } from './interfaces/Gradables'; import { TestingService } from './services/testingService'; import { MessageCommand } from './typings/message'; @@ -295,13 +294,15 @@ export class SidebarProvider implements vscode.WebviewViewProvider { }, }); - this.testingService?.runGradeableWithResult( - term, - courseId, - gradeableId, - gradeableId, - gradeDetails - ); + if (this.testingService) { + this.testingService.runGradeableWithResult( + term, + courseId, + gradeableId, + gradeableId, + gradeDetails + ); + } } catch (error: unknown) { const err = error instanceof Error ? error.message : String(error); vscode.window.showErrorMessage(`Failed to grade: ${err}`); From f9284c9917be444819de6f63a1d7b6719457a89a Mon Sep 17 00:00:00 2001 From: Riley Smith Date: Thu, 30 Apr 2026 01:28:14 -0700 Subject: [PATCH 11/20] fix bugs with git service --- package.json | 2 +- src/services/gitService.ts | 21 ++++++++-------- src/sidebarProvider.ts | 51 +++++++++++++++++++++++++++----------- 3 files changed, 47 insertions(+), 27 deletions(-) diff --git a/package.json b/package.json index 1a9a1cf..b8f95de 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ { "type": "webview", "id": "submittyWebview", - "name": "Sidebar", + "name": "Submitty", "icon": "media/duck.png" } ] diff --git a/src/services/gitService.ts b/src/services/gitService.ts index c32ef19..d68022e 100644 --- a/src/services/gitService.ts +++ b/src/services/gitService.ts @@ -59,18 +59,17 @@ export class GitService { ); } - // check to see if there are any changes to commit - const status = (await repo.status()) as unknown as { - modified: unknown[]; - untracked: unknown[]; - deleted: unknown[]; - }; + console.log(repo); - if ( - status.modified.length === 0 && - status.untracked.length === 0 && - status.deleted.length === 0 - ) { + // Refresh repository state before checking tracked/untracked changes. + await repo.status(); + + const hasChanges = + repo.state.indexChanges.length > 0 || + repo.state.workingTreeChanges.length > 0 || + repo.state.untrackedChanges.length > 0; + + if (!hasChanges) { throw new Error('No changes to commit.'); } await repo.commit(message, options); diff --git a/src/sidebarProvider.ts b/src/sidebarProvider.ts index 935a8f7..eca3c66 100644 --- a/src/sidebarProvider.ts +++ b/src/sidebarProvider.ts @@ -7,6 +7,7 @@ import { Gradable } from './interfaces/Gradables'; import { TestingService } from './services/testingService'; import { MessageCommand } from './typings/message'; + export class SidebarProvider implements vscode.WebviewViewProvider { private _view?: vscode.WebviewView; private apiService: ApiService; @@ -226,19 +227,30 @@ export class SidebarProvider implements vscode.WebviewViewProvider { view: vscode.WebviewView ): Promise { try { - this.testingService?.addGradeable( + const gradeableTestItem = this.testingService?.addGradeable( term, courseId, gradeableId, gradeableId ); + if (gradeableTestItem) { + try { + await vscode.commands.executeCommand('workbench.view.testing.focus'); + await vscode.commands.executeCommand( + 'vscode.revealTestInExplorer', + gradeableTestItem + ); + } catch (error: unknown) { + console.warn('Unable to focus Testing panel:', error); + } + } if (this.gitService) { view.webview.postMessage({ command: MessageCommand.GRADE_STARTED, data: { message: 'Staging and committing...' }, }); - const commitMessage = new Date().toLocaleString(undefined, { + const commitMessage = new Date().toLocaleString('en-US', { dateStyle: 'short', timeStyle: 'medium', }); @@ -251,6 +263,7 @@ export class SidebarProvider implements vscode.WebviewViewProvider { await this.gitService.push(); } catch (error: unknown) { const err = error instanceof Error ? error.message : String(error); + console.error('Failed to commit and push:', error); if (err === 'No changes to commit.') { view.webview.postMessage({ command: MessageCommand.GRADE_STARTED, @@ -261,22 +274,29 @@ export class SidebarProvider implements vscode.WebviewViewProvider { } } } - - view.webview.postMessage({ - command: MessageCommand.GRADE_STARTED, - data: { message: 'Submitting for grading...' }, - }); + await this.apiService.submitVCSGradable(term, courseId, gradeableId); - view.webview.postMessage({ - command: MessageCommand.GRADE_STARTED, - data: { message: 'Grading in progress. Polling for results...' }, - }); - const gradeDetails = await this.apiService.pollGradeDetailsUntilComplete( - term, - courseId, - gradeableId + const gradeDetails = await vscode.window.withProgress( + { + title: 'Grading in progress...', + location: vscode.ProgressLocation.Notification, + cancellable: true, + }, + async (progress, token) => { + progress.report({ + message: 'Waiting for autograder results...', + }); + + return this.apiService.pollGradeDetailsUntilComplete( + term, + courseId, + gradeableId, + { token } + ); + } ); + const previousAttempts = await this.apiService.fetchPreviousAttempts( term, courseId, @@ -305,6 +325,7 @@ export class SidebarProvider implements vscode.WebviewViewProvider { } } catch (error: unknown) { const err = error instanceof Error ? error.message : String(error); + console.error('Failed to grade:', error); vscode.window.showErrorMessage(`Failed to grade: ${err}`); view.webview.postMessage({ command: MessageCommand.ERROR, From 7eb1cb215b7bc653a196f6c70333e71bd00732f4 Mon Sep 17 00:00:00 2001 From: Riley Smith Date: Thu, 30 Apr 2026 01:31:53 -0700 Subject: [PATCH 12/20] code cleanup --- src/services/authService.ts | 5 ----- src/sidebarProvider.ts | 4 +--- 2 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/services/authService.ts b/src/services/authService.ts index 0e03e17..0ca2dff 100644 --- a/src/services/authService.ts +++ b/src/services/authService.ts @@ -14,7 +14,6 @@ export class AuthService { } async initialize(): Promise { - console.log('Initializing AuthService'); // Get base URL from configuration const config = vscode.workspace.getConfiguration('submitty'); @@ -26,11 +25,9 @@ export class AuthService { } const token = await this.getToken(); - console.log('Token:', token); if (token) { // Token exists, set it on the API service this.apiService.setAuthorizationToken(token); - console.log('Token set on API service'); // If baseUrl isn't configured yet, fetch it now so API calls work. if (!baseUrl) { @@ -68,8 +65,6 @@ export class AuthService { return; } - console.log('No token found, prompting for credentials'); - // If no base URL is configured, prompt for it if (!baseUrl) { const inputUrl = await vscode.window.showInputBox({ diff --git a/src/sidebarProvider.ts b/src/sidebarProvider.ts index eca3c66..3a065e8 100644 --- a/src/sidebarProvider.ts +++ b/src/sidebarProvider.ts @@ -112,7 +112,6 @@ export class SidebarProvider implements vscode.WebviewViewProvider { message: unknown, view: vscode.WebviewView ): Promise { - console.log('handleMessage', message); if (!message || typeof message !== 'object') { return; } @@ -155,7 +154,6 @@ export class SidebarProvider implements vscode.WebviewViewProvider { if (!term || !courseId || !gradeableId) { throw new Error('Invalid grade payload.'); } - console.log('handleGrade', term, courseId, gradeableId); await this.handleGrade(term, courseId, gradeableId, view); } catch (error: unknown) { const err = error instanceof Error ? error.message : String(error); @@ -274,7 +272,7 @@ export class SidebarProvider implements vscode.WebviewViewProvider { } } } - + await this.apiService.submitVCSGradable(term, courseId, gradeableId); const gradeDetails = await vscode.window.withProgress( From 0610866de6c628694addb1929776c6784733c581 Mon Sep 17 00:00:00 2001 From: Riley Smith Date: Thu, 30 Apr 2026 01:33:36 -0700 Subject: [PATCH 13/20] lint + format --- .github/workflows/lint_and_test.yml | 2 +- src/extension.ts | 3 +-- src/services/authService.ts | 5 ++--- src/sidebarProvider.ts | 1 - 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/lint_and_test.yml b/.github/workflows/lint_and_test.yml index fd0fc25..7e660d3 100644 --- a/.github/workflows/lint_and_test.yml +++ b/.github/workflows/lint_and_test.yml @@ -21,7 +21,7 @@ jobs: uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - cache: "npm" + cache: 'npm' - name: Install dependencies run: npm ci diff --git a/src/extension.ts b/src/extension.ts index 26ebc24..c2550a5 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -5,7 +5,6 @@ import { TestingService } from './services/testingService'; import { GitService } from './services/gitService'; import { AuthService } from './services/authService'; import { CourseRepoResolver } from './services/courseRepoResolver'; -import type { Gradable } from './interfaces/Gradables'; export function activate(context: vscode.ExtensionContext): void { const apiService = ApiService.getInstance(context, ''); @@ -61,4 +60,4 @@ export function activate(context: vscode.ExtensionContext): void { })(); } -export function deactivate() {} +export function deactivate(): void {} diff --git a/src/services/authService.ts b/src/services/authService.ts index 0ca2dff..bdcde29 100644 --- a/src/services/authService.ts +++ b/src/services/authService.ts @@ -10,11 +10,10 @@ export class AuthService { private static instance: AuthService; constructor(context: vscode.ExtensionContext, apiBaseUrl: string = '') { this.context = context; - this.apiService = ApiService.getInstance(context, ''); + this.apiService = ApiService.getInstance(context, apiBaseUrl); } async initialize(): Promise { - // Get base URL from configuration const config = vscode.workspace.getConfiguration('submitty'); let baseUrl = config.get('baseUrl', ''); @@ -180,7 +179,7 @@ export class AuthService { apiBaseUrl: string = '' ): AuthService { if (!AuthService.instance) { - AuthService.instance = new AuthService(context); + AuthService.instance = new AuthService(context, apiBaseUrl); } return AuthService.instance; } diff --git a/src/sidebarProvider.ts b/src/sidebarProvider.ts index 3a065e8..4db5ae9 100644 --- a/src/sidebarProvider.ts +++ b/src/sidebarProvider.ts @@ -7,7 +7,6 @@ import { Gradable } from './interfaces/Gradables'; import { TestingService } from './services/testingService'; import { MessageCommand } from './typings/message'; - export class SidebarProvider implements vscode.WebviewViewProvider { private _view?: vscode.WebviewView; private apiService: ApiService; From 167a6743edfe6410004217dbd80f14b6688f0a57 Mon Sep 17 00:00:00 2001 From: Riley Smith Date: Fri, 1 May 2026 11:52:17 -0700 Subject: [PATCH 14/20] bump packages --- package-lock.json | 4256 ++++++++++----------------------------------- package.json | 28 +- 2 files changed, 945 insertions(+), 3339 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5ef846b..4b92f27 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,13 +16,10 @@ "@types/mocha": "^10.0.7", "@types/node": "20.x", "@types/vscode": "^1.95.0", - "@vscode/test-cli": "^0.0.9", + "@vscode/test-cli": "^0.0.11", "@vscode/test-electron": "^2.4.0", "eslint": "^9.39.1", "eslint-config-prettier": "^9.1.0", - "eslint-plugin-react": "^7.37.5", - "eslint-plugin-react-hooks": "^7.0.1", - "eslint-plugin-react-refresh": "^0.4.24", "globals": "^16.5.0", "prettier": "^3.3.3", "rimraf": "^3.0.2", @@ -33,287 +30,17 @@ "vscode": "^1.95.0" } }, - "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", - "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", - "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", - "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.5", - "@babel/types": "^7.28.5", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz", - "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.28.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", - "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", - "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.5" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", - "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.5", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.5", - "@babel/template": "^7.27.2", - "@babel/types": "^7.28.5", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", - "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", - "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, "license": "MIT", "dependencies": { @@ -329,34 +56,48 @@ "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.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", - "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", + "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.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", "dev": true, "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.7", "debug": "^4.3.1", - "minimatch": "^3.1.2" + "minimatch": "^3.1.5" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -365,9 +106,9 @@ } }, "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -404,20 +145,20 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", - "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.4", + "ajv": "^6.14.0", "debug": "^4.3.2", "espree": "^10.0.1", "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.1", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, "engines": { @@ -428,9 +169,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -452,9 +193,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -465,9 +206,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.1.tgz", - "integrity": "sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", "dev": true, "license": "MIT", "engines": { @@ -502,34 +243,49 @@ } }, "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "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.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "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.1", + "@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" }, @@ -557,6 +313,7 @@ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^5.1.2", "string-width-cjs": "npm:string-width@^4.2.0", @@ -569,78 +326,32 @@ "node": ">=12" } }, - "node_modules/@isaacs/cliui/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/@isaacs/cliui/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", - "dev": true + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", @@ -658,6 +369,7 @@ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "dev": true, + "license": "MIT", "optional": true, "engines": { "node": ">=14" @@ -674,7 +386,8 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/json-schema": { "version": "7.0.15", @@ -687,33 +400,41 @@ "version": "10.0.10", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz", "integrity": "sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { - "version": "20.17.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.14.tgz", - "integrity": "sha512-w6qdYetNL5KRBiSClK/KWai+2IMEJuAj+EujKCumalFOwXtvOXaEan9AuwcRID2IcOIAWSIfR495hBtgKlx2zg==", + "version": "20.19.39", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", + "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", "dev": true, + "license": "MIT", "dependencies": { - "undici-types": "~6.19.2" + "undici-types": "~6.21.0" } }, "node_modules/@types/vscode": { - "version": "1.96.0", - "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.96.0.tgz", - "integrity": "sha512-qvZbSZo+K4ZYmmDuaodMbAa67Pl6VDQzLKFka6rq+3WUTY4Kro7Bwoi0CuZLO/wema0ygcmpwow7zZfPJTs5jg==", - "dev": true + "version": "1.118.0", + "resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.118.0.tgz", + "integrity": "sha512-Ah6eTlqDcwIMELEVwQMO++rJAFBRz/oLluLD/vWdYrH1KuI9kfpaM+7pg0OvvascgcJy+ghLCERAYouM4QbzGw==", + "dev": true, + "license": "MIT" }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.1.tgz", - "integrity": "sha512-HQWSicah4s9z2/HifRPQ6b6R7G+SBx64JlFQpgSSHWPKdvCZX57XCbszg/bapbRsOEv42q5tayTYcEFpACcX1w==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.1.tgz", + "integrity": "sha512-BOziFIfE+6osHO9FoJG4zjoHUcvI7fTNBSpdAwrNH0/TLvzjsk2oo8XSSOT2HhqUyhZPfHv4UOffoJ9oEEQ7Ag==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.48.1", - "@typescript-eslint/types": "^8.48.1", - "debug": "^4.3.4" + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/type-utils": "8.59.1", + "@typescript-eslint/utils": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -723,29 +444,34 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "@typescript-eslint/parser": "^8.59.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/project-service/node_modules/@typescript-eslint/types": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.1.tgz", - "integrity": "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", "dev": true, "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": ">= 4" } }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.1.tgz", - "integrity": "sha512-k0Jhs4CpEffIBm6wPaCXBAD7jxBtrHjrSgtfCjUvPp9AZ78lXKdTR8fxyZO5y4vWNlOvYXRtngSZNSn+H53Jkw==", + "node_modules/@typescript-eslint/parser": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.1.tgz", + "integrity": "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==", "dev": true, "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", + "debug": "^4.4.3" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -754,21 +480,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.1.tgz", - "integrity": "sha512-1jEop81a3LrJQLTf/1VfPQdhIY4PlGDBc/i67EVWObrtvcziysbLN3oReexHOM6N3jyXgCrkBsZpqwH0hiDOQg==", + "node_modules/@typescript-eslint/project-service": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.1.tgz", + "integrity": "sha512-+MuHQlHiEr00Of/IQbE/MmEoi44znZHbR/Pz7Opq4HryUOlRi+/44dro9Ycy8Fyo+/024IWtw8m4JUMCGTYxDg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.48.1", - "@typescript-eslint/typescript-estree": "8.48.1", - "@typescript-eslint/utils": "8.48.1", - "debug": "^4.3.4", - "ts-api-utils": "^2.1.0" + "@typescript-eslint/tsconfig-utils": "^8.59.1", + "@typescript-eslint/types": "^8.59.1", + "debug": "^4.4.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -778,16 +503,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.1.tgz", - "integrity": "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.1.tgz", + "integrity": "sha512-LwuHQI4pDOYVKvmH2dkaJo6YZCSgouVgnS/z7yBPKBMvgtBvyLqiLy9Z6b7+m/TRcX1NFYUqZetI5Y+aT4GEfg==", "dev": true, "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1" + }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -796,23 +524,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.1.tgz", - "integrity": "sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.1.tgz", + "integrity": "sha512-/0nEyPbX7gRsk0Uwfe4ALwwgxuA66d/l2mhRDNlAvaj4U3juhUtJNq0DsY8M2AYwwb9rEq2hrC3IcIcEt++iJA==", "dev": true, "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.48.1", - "@typescript-eslint/tsconfig-utils": "8.48.1", - "@typescript-eslint/types": "8.48.1", - "@typescript-eslint/visitor-keys": "8.48.1", - "debug": "^4.3.4", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.1.0" - }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -821,18 +538,21 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.1.tgz", - "integrity": "sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.1.tgz", + "integrity": "sha512-klWPBR2ciQHS3f++ug/mVnWKPjBUo7icEL3FAO1lhAR1Z1i5NQYZ1EannMSRYcq5qCv5wNALlXr6fksRHyYl7w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.48.1", - "eslint-visitor-keys": "^4.2.1" + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/utils": "8.59.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -840,67 +560,42 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@typescript-eslint/type-utils/node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.8.4" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/utils": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.1.tgz", - "integrity": "sha512-fAnhLrDjiVfey5wwFRwrweyRlCmdz5ZxXz2G/4cLn0YDLjTapmN4gcCsTBR1N2rWnZSDeWpYtgLDsJt+FpmcwA==", + "node_modules/@typescript-eslint/types": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.1.tgz", + "integrity": "sha512-ZDCjgccSdYPw5Bxh+my4Z0lJU96ZDN7jbBzvmEn0FZx3RtU1C7VWl6NbDx94bwY3V5YsgwRzJPOgeY2Q/nLG8A==", "dev": true, "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.48.1", - "@typescript-eslint/types": "8.48.1", - "@typescript-eslint/typescript-estree": "8.48.1" - }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.1.tgz", - "integrity": "sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.1.tgz", + "integrity": "sha512-OUd+vJS05sSkOip+BkZ/2NS8RMxrAAJemsC6vU3kmfLyeaJT0TftHkV9mcx2107MmsBVXXexhVu4F0TZXyMl4g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.48.1", - "@typescript-eslint/visitor-keys": "8.48.1" + "@typescript-eslint/project-service": "8.59.1", + "@typescript-eslint/tsconfig-utils": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/visitor-keys": "8.59.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -908,38 +603,61 @@ "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.1.tgz", - "integrity": "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==", + "node_modules/@typescript-eslint/typescript-estree/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.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "node": "18 || 20 || >=22" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.1.tgz", - "integrity": "sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==", + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.48.1", - "@typescript-eslint/tsconfig-utils": "8.48.1", - "@typescript-eslint/types": "8.48.1", - "@typescript-eslint/visitor-keys": "8.48.1", - "debug": "^4.3.4", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.1.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.1.tgz", + "integrity": "sha512-3pIeoXhCeYH9FSCBI8P3iNwJlGuzPlYKkTlen2O9T1DSeeg8UG8jstq6BLk+Mda0qup7mgk4z4XL4OzRaxZ8LA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.59.1", + "@typescript-eslint/types": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -949,18 +667,19 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.1.tgz", - "integrity": "sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.1.tgz", + "integrity": "sha512-LdDNl6C5iJExcM0Yh0PwAIBb9PrSiCsWamF/JyEZawm3kFDnRoaq3LGE4bpyRao/fWeGKKyw7icx0YxrLFC5Cg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.48.1", - "eslint-visitor-keys": "^4.2.1" + "@typescript-eslint/types": "8.59.1", + "eslint-visitor-keys": "^5.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -970,37 +689,25 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/utils/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "node_modules/@typescript-eslint/visitor-keys/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, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/@typescript-eslint/utils/node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, "node_modules/@vscode/test-cli": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.9.tgz", - "integrity": "sha512-vsl5/ueE3Jf0f6XzB0ECHHMsd5A0Yu6StElb8a+XsubZW7kHNAOw4Y3TSSuDzKEpLnJ92nbMy1Zl+KLGCE6NaA==", + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.11.tgz", + "integrity": "sha512-qO332yvzFqGhBMJrp6TdwbIydiHgCtxXc2Nl6M58mbH/Z+0CyLR76Jzv4YWPEthhrARprzCRJUqzFvTHFhTj7Q==", "dev": true, + "license": "MIT", "dependencies": { "@types/mocha": "^10.0.2", "c8": "^9.1.0", @@ -1008,7 +715,7 @@ "enhanced-resolve": "^5.15.0", "glob": "^10.3.10", "minimatch": "^9.0.3", - "mocha": "^10.2.0", + "mocha": "^11.1.0", "supports-color": "^9.4.0", "yargs": "^17.7.2" }, @@ -1020,15 +727,16 @@ } }, "node_modules/@vscode/test-electron": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.4.1.tgz", - "integrity": "sha512-Gc6EdaLANdktQ1t+zozoBVRynfIsMKMc94Svu1QreOBC8y76x4tvaK32TljrLi1LI2+PK58sDVbL7ALdqf3VRQ==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/@vscode/test-electron/-/test-electron-2.5.2.tgz", + "integrity": "sha512-8ukpxv4wYe0iWMRQU18jhzJOHkeGKbnw7xWRX3Zw1WJA4cEKbHcmmLPdPrPtL6rhDcrlCZN+xKRpv09n4gRHYg==", "dev": true, + "license": "MIT", "dependencies": { "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.5", "jszip": "^3.10.1", - "ora": "^7.0.1", + "ora": "^8.1.0", "semver": "^7.6.2" }, "engines": { @@ -1036,12 +744,11 @@ } }, "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1060,18 +767,19 @@ } }, "node_modules/agent-base": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", - "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 14" } }, "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, "license": "MIT", "dependencies": { @@ -1085,22 +793,17 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { @@ -1108,6 +811,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -1123,6 +827,7 @@ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, + "license": "ISC", "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -1135,155 +840,8 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", - "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-includes": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz", - "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.24.0", - "es-object-atoms": "^1.1.1", - "get-intrinsic": "^1.3.0", - "is-string": "^1.1.1", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.findlast": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", - "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", - "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", - "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.tosorted": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", - "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.3", - "es-errors": "^1.3.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", - "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/async-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", - "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } + "license": "Python-2.0" }, "node_modules/asynckit": { "version": "0.4.0", @@ -1291,38 +849,23 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/axios": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", - "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.15.2.tgz", + "integrity": "sha512-wLrXxPtcrPTsNlJmKjkPnNPK2Ihe0hn0wGSaTEiHRPxwjvJwT3hKmXF4dpqxmPO9SoNb2FsYXj/xEo0gHN+D5A==", "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" } }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", @@ -1341,23 +884,15 @@ "type": "consulting", "url": "https://feross.org/support" } - ] - }, - "node_modules/baseline-browser-mapping": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.0.tgz", - "integrity": "sha512-Mh++g+2LPfzZToywfE1BUzvZbfOY52Nil0rn9H1CPC5DJ7fX+Vir7nToBeoiSbB1zTNeGYbELEvJESujgGrzXw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.js" - } + ], + "license": "MIT" }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -1366,12 +901,12 @@ } }, "node_modules/bl": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", - "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", - "dev": true, + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", "dependencies": { - "buffer": "^6.0.3", + "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } @@ -1380,7 +915,7 @@ "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, + "license": "MIT", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -1391,9 +926,9 @@ } }, "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.0.tgz", + "integrity": "sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==", "dev": true, "license": "MIT", "dependencies": { @@ -1405,6 +940,7 @@ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { "fill-range": "^7.1.1" }, @@ -1416,48 +952,13 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } + "license": "ISC" }, "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "funding": [ { "type": "github", @@ -1472,9 +973,10 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "dependencies": { "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "ieee754": "^1.1.13" } }, "node_modules/c8": { @@ -1482,6 +984,7 @@ "resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz", "integrity": "sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==", "dev": true, + "license": "ISC", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@istanbuljs/schema": "^0.1.3", @@ -1502,25 +1005,6 @@ "node": ">=14.14.0" } }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "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", @@ -1534,23 +1018,6 @@ "node": ">= 0.4" } }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -1566,6 +1033,7 @@ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -1573,32 +1041,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001759", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001759.tgz", - "integrity": "sha512-Pzfx9fOKoKvevQf8oCXoyNRQ5QyxJj+3O0Rqx2V5oxT61KGx8+n6hV/IUyJeifUci2clnmmKVpvtiqRzgiWjSw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -1615,6 +1063,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -1627,6 +1076,7 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, + "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -1653,15 +1103,16 @@ "license": "ISC" }, "node_modules/cli-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", - "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", "dev": true, + "license": "MIT", "dependencies": { - "restore-cursor": "^4.0.0" + "restore-cursor": "^5.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -1672,6 +1123,7 @@ "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" }, @@ -1684,6 +1136,7 @@ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, + "license": "ISC", "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", @@ -1693,17 +1146,29 @@ "node": ">=12" } }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/cliui/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cliui/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -1713,11 +1178,25 @@ "node": ">=8" } }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cliui/node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -1735,6 +1214,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -1746,7 +1226,8 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/combined-stream": { "version": "1.0.8", @@ -1764,25 +1245,29 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, + "license": "MIT", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -1792,65 +1277,12 @@ "node": ">= 8" } }, - "node_modules/data-view-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", - "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", - "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/inspect-js" - } - }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", - "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, + "license": "MIT", "dependencies": { "ms": "^2.1.3" }, @@ -1868,6 +1300,7 @@ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -1903,43 +1336,8 @@ "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 - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "license": "MIT" }, "node_modules/delayed-stream": { "version": "1.0.0", @@ -1960,27 +1358,15 @@ } }, "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.3.1" } }, - "node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -1999,20 +1385,15 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true - }, - "node_modules/electron-to-chromium": { - "version": "1.5.263", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.263.tgz", - "integrity": "sha512-DrqJ11Knd+lo+dv+lltvfMDLU27g14LMdH2b0O3Pio4uk0x+z7OR+JrmyacTPN2M8w3BrZ7/RTwG3R9B7irPlg==", "dev": true, - "license": "ISC" + "license": "MIT" }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/end-of-stream": { "version": "1.4.5", @@ -2024,87 +1405,19 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.18.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", - "integrity": "sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ==", + "version": "5.21.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.0.tgz", + "integrity": "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.3" }, "engines": { "node": ">=10.13.0" } }, - "node_modules/es-abstract": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz", - "integrity": "sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.3.0", - "get-proto": "^1.0.1", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.2.1", - "is-set": "^2.0.3", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.1", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.4", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "stop-iteration-iterator": "^1.1.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.19" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2123,34 +1436,6 @@ "node": ">= 0.4" } }, - "node_modules/es-iterator-helpers": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", - "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", - "es-errors": "^1.3.0", - "es-set-tostringtag": "^2.0.3", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.6", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "iterator.prototype": "^1.1.4", - "safe-array-concat": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -2178,42 +1463,12 @@ "node": ">= 0.4" } }, - "node_modules/es-shim-unscopables": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", - "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-to-primitive": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", - "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7", - "is-date-object": "^1.0.5", - "is-symbol": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -2223,6 +1478,7 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -2231,26 +1487,25 @@ } }, "node_modules/eslint": { - "version": "9.39.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.1.tgz", - "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.1", + "@eslint/config-array": "^0.21.2", "@eslint/config-helpers": "^0.4.2", "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.1", - "@eslint/js": "9.39.1", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", "@eslint/plugin-kit": "^0.4.1", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", - "ajv": "^6.12.4", + "ajv": "^6.14.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", @@ -2269,7 +1524,7 @@ "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -2304,103 +1559,6 @@ "eslint": ">=7.0.0" } }, - "node_modules/eslint-plugin-react": { - "version": "7.37.5", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", - "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.8", - "array.prototype.findlast": "^1.2.5", - "array.prototype.flatmap": "^1.3.3", - "array.prototype.tosorted": "^1.1.4", - "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.2.1", - "estraverse": "^5.3.0", - "hasown": "^2.0.2", - "jsx-ast-utils": "^2.4.1 || ^3.0.0", - "minimatch": "^3.1.2", - "object.entries": "^1.1.9", - "object.fromentries": "^2.0.8", - "object.values": "^1.2.1", - "prop-types": "^15.8.1", - "resolve": "^2.0.0-next.5", - "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.12", - "string.prototype.repeat": "^1.0.0" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", - "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.24.4", - "@babel/parser": "^7.24.4", - "hermes-parser": "^0.25.1", - "zod": "^3.25.0 || ^4.0.0", - "zod-validation-error": "^3.5.0 || ^4.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" - } - }, - "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.24.tgz", - "integrity": "sha512-nLHIW7TEq3aLrEYWpVaJ1dRgFR+wLDPN8e8FpYAql/bMV2oBEfC37K0gLEGgv9fy66juNShSMV8OkTqzltcG/w==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "eslint": ">=8.40" - } - }, - "node_modules/eslint-plugin-react/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint-plugin-react/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/eslint-plugin-react/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/eslint-scope": { "version": "8.4.0", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", @@ -2419,21 +1577,22 @@ } }, "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==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, + "license": "Apache-2.0", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -2441,24 +1600,12 @@ "concat-map": "0.0.1" } }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/eslint/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" }, @@ -2467,10 +1614,11 @@ } }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -2496,24 +1644,12 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/esquery": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", - "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", + "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" }, @@ -2539,6 +1675,7 @@ "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" } @@ -2580,7 +1717,8 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/file-entry-cache": { "version": "8.0.0", @@ -2600,6 +1738,7 @@ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -2612,6 +1751,7 @@ "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" @@ -2628,6 +1768,7 @@ "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true, + "license": "BSD-3-Clause", "bin": { "flat": "cli.js" } @@ -2647,22 +1788,23 @@ } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "funding": [ { "type": "individual", "url": "https://github.com/sponsors/RubenVerborgh" } ], + "license": "MIT", "engines": { "node": ">=4.0" }, @@ -2672,29 +1814,14 @@ } } }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/foreground-child": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", - "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", "dev": true, + "license": "ISC", "dependencies": { - "cross-spawn": "^7.0.0", + "cross-spawn": "^7.0.6", "signal-exit": "^4.0.1" }, "engines": { @@ -2705,9 +1832,9 @@ } }, "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -2730,7 +1857,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/fsevents": { "version": "2.3.3", @@ -2738,6 +1866,7 @@ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -2755,66 +1884,29 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/function.prototype.name": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", - "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "functions-have-names": "^1.2.3", - "hasown": "^2.0.2", - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/functions-have-names": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", - "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/generator-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", - "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true, + "license": "ISC", "engines": { "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-east-asian-width": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", + "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -2852,24 +1944,6 @@ "node": ">= 0.4" } }, - "node_modules/get-symbol-description": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", - "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -2880,6 +1954,7 @@ "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -2902,6 +1977,7 @@ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "dev": true, + "license": "ISC", "dependencies": { "is-glob": "^4.0.1" }, @@ -2922,23 +1998,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/globalthis": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", - "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -2955,63 +2014,17 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true - }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, - "node_modules/has-bigints": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", - "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "license": "ISC" }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-proto": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", - "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", - "dev": true, "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.0" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, "node_modules/has-symbols": { @@ -3042,9 +2055,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -3058,38 +2071,24 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", "dev": true, + "license": "MIT", "bin": { "he": "bin/he" } }, - "node_modules/hermes-estree": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", - "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", - "dev": true, - "license": "MIT" - }, - "node_modules/hermes-parser": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", - "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "hermes-estree": "0.25.1" - } - }, "node_modules/html-escaper": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", "dev": true, + "license": "MIT", "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" @@ -3103,6 +2102,7 @@ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "dev": true, + "license": "MIT", "dependencies": { "agent-base": "^7.1.2", "debug": "4" @@ -3128,13 +2128,15 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "BSD-3-Clause" }, "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" } @@ -3143,7 +2145,8 @@ "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/import-fresh": { "version": "3.3.1", @@ -3167,6 +2170,7 @@ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.8.19" } @@ -3177,6 +2181,7 @@ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, + "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -3185,7 +2190,8 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" }, "node_modules/ini": { "version": "1.3.8", @@ -3193,481 +2199,128 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, - "node_modules/internal-slot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", - "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/is-array-buffer": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", - "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" + "binary-extensions": "^2.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/is-async-function": { + "node_modules/is-extglob": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", - "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", - "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10.0" } }, - "node_modules/is-bigint": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", - "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "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", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, "license": "MIT", - "dependencies": { - "has-bigints": "^1.0.2" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "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": { - "binary-extensions": "^2.0.0" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", - "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, "engines": { - "node": ">= 0.4" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.12.0" } }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/is-data-view": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", - "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/is-date-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", - "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, "engines": { - "node": ">= 0.4" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-finalizationregistry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", - "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-function": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", - "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.4", - "generator-function": "^2.0.0", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "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, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-interactive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", - "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", - "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-number-object": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", - "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-set": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", - "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", - "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-string": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", - "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-symbol": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", - "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-weakmap": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", - "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", - "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-weakset": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", - "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=8" } @@ -3677,6 +2330,7 @@ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", @@ -3691,6 +2345,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -3699,10 +2354,11 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -3711,29 +2367,12 @@ "node": ">=8" } }, - "node_modules/iterator.prototype": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", - "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "get-proto": "^1.0.0", - "has-symbols": "^1.1.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/cliui": "^8.0.2" }, @@ -3744,13 +2383,6 @@ "@pkgjs/parseargs": "^0.11.0" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, "node_modules/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", @@ -3764,19 +2396,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -3795,42 +2414,15 @@ "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 - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsx-ast-utils": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", - "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-includes": "^3.1.6", - "array.prototype.flat": "^1.3.1", - "object.assign": "^4.1.4", - "object.values": "^1.1.6" - }, - "engines": { - "node": ">=4.0" - } + "license": "MIT" }, "node_modules/jszip": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", "dev": true, + "license": "(MIT OR GPL-3.0-or-later)", "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", @@ -3864,6 +2456,7 @@ "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" @@ -3877,6 +2470,7 @@ "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", "dev": true, + "license": "MIT", "dependencies": { "immediate": "~3.0.5" } @@ -3886,6 +2480,7 @@ "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" }, @@ -3900,13 +2495,15 @@ "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" @@ -3918,30 +2515,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/make-dir": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, + "license": "MIT", "dependencies": { "semver": "^7.5.3" }, @@ -3982,13 +2568,17 @@ "node": ">= 0.6" } }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", "dev": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/mimic-response": { @@ -4004,12 +2594,13 @@ } }, "node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" @@ -4028,10 +2619,11 @@ } }, "node_modules/minipass": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", - "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "dev": true, + "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" } @@ -4043,30 +2635,32 @@ "license": "MIT" }, "node_modules/mocha": { - "version": "10.8.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", - "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", + "version": "11.7.5", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.5.tgz", + "integrity": "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-colors": "^4.1.3", "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", + "chokidar": "^4.0.1", "debug": "^4.3.5", - "diff": "^5.2.0", + "diff": "^7.0.0", "escape-string-regexp": "^4.0.0", "find-up": "^5.0.0", - "glob": "^8.1.0", + "glob": "^10.4.5", "he": "^1.2.0", + "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", + "minimatch": "^9.0.5", "ms": "^2.1.3", + "picocolors": "^1.1.1", "serialize-javascript": "^6.0.2", "strip-json-comments": "^3.1.1", "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^16.2.0", - "yargs-parser": "^20.2.9", + "workerpool": "^9.2.0", + "yargs": "^17.7.2", + "yargs-parser": "^21.1.1", "yargs-unparser": "^2.0.0" }, "bin": { @@ -4074,136 +2668,61 @@ "mocha": "bin/mocha.js" }, "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/mocha/node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/mocha/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/mocha/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/mocha/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/mocha/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/mocha/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "node_modules/mocha/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", "dev": true, + "license": "MIT", "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">=10" + "node": ">= 14.16.0" }, "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + "url": "https://paulmillr.com/funding/" } }, - "node_modules/mocha/node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "node_modules/mocha/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, - "node_modules/mocha/node_modules/yargs-parser": { - "version": "20.2.9", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", - "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/napi-build-utils": { "version": "2.0.0", @@ -4215,12 +2734,13 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/node-abi": { - "version": "3.85.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.85.0.tgz", - "integrity": "sha512-zsFhmbkAzwhTft6nd3VxcG0cvJsT70rL+BIGHWVq5fi6MwGrHwzqKaxXE+Hl2GmnGItnDKPPkO5/LQqjVkIdFg==", + "version": "3.90.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.90.0.tgz", + "integrity": "sha512-pZNQT7UnYlMwMBy5N1lV5X/YLTbZM5ncytN3xL7CHEzhDN8uVe0u55yaPUJICIJjaCW8NrM5BFdqr7HLweStNA==", "license": "MIT", "dependencies": { "semver": "^7.3.5" @@ -4235,148 +2755,36 @@ "integrity": "sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==", "license": "MIT" }, - "node_modules/node-releases": { - "version": "2.0.27", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", - "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", - "dev": true, - "license": "MIT" - }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", - "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.entries": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", - "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.fromentries": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", - "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.values": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", - "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", "dependencies": { "wrappy": "1" } }, "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", "dev": true, + "license": "MIT", "dependencies": { - "mimic-fn": "^2.1.0" + "mimic-function": "^5.0.0" }, "engines": { - "node": ">=6" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -4387,6 +2795,7 @@ "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", @@ -4400,45 +2809,35 @@ } }, "node_modules/ora": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-7.0.1.tgz", - "integrity": "sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.2.0.tgz", + "integrity": "sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^5.3.0", - "cli-cursor": "^4.0.0", - "cli-spinners": "^2.9.0", + "cli-cursor": "^5.0.0", + "cli-spinners": "^2.9.2", "is-interactive": "^2.0.0", - "is-unicode-supported": "^1.3.0", - "log-symbols": "^5.1.0", - "stdin-discarder": "^0.1.0", - "string-width": "^6.1.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.2", + "string-width": "^7.2.0", "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, "node_modules/ora/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, + "license": "MIT", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -4447,87 +2846,71 @@ } }, "node_modules/ora/node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", + "dev": true, + "license": "MIT" }, "node_modules/ora/node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/ora/node_modules/log-symbols": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", - "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", - "dev": true, - "dependencies": { - "chalk": "^5.0.0", - "is-unicode-supported": "^1.1.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/string-width": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-6.1.0.tgz", - "integrity": "sha512-k01swCJAgQmuADB0YIc+7TuatfNvTBVOoaUWJjTB9R4VJzR5vNWzf5t42ESVZFPS8xTySF7CAdV4t/aaIm3UnQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", "dev": true, + "license": "MIT", "dependencies": { - "eastasianwidth": "^0.2.0", - "emoji-regex": "^10.2.1", - "strip-ansi": "^7.0.1" + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" }, "engines": { - "node": ">=16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ora/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "node_modules/ora/node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, + "license": "MIT", "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/own-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", - "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "node_modules/ora/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">= 0.4" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/p-limit": { @@ -4535,6 +2918,7 @@ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, + "license": "MIT", "dependencies": { "yocto-queue": "^0.1.0" }, @@ -4550,6 +2934,7 @@ "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" }, @@ -4564,13 +2949,15 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", - "dev": true + "dev": true, + "license": "BlueOak-1.0.0" }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true + "dev": true, + "license": "(MIT AND Zlib)" }, "node_modules/parent-module": { "version": "1.0.1", @@ -4590,6 +2977,7 @@ "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" } @@ -4599,6 +2987,7 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -4608,22 +2997,17 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, "node_modules/path-scurry": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" @@ -4643,10 +3027,11 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -4654,20 +3039,11 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, "node_modules/prebuild-install": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", "license": "MIT", "dependencies": { "detect-libc": "^2.0.0", @@ -4695,14 +3071,15 @@ "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/prettier": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.7.4.tgz", - "integrity": "sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", + "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", "dev": true, "license": "MIT", "bin": { @@ -4719,29 +3096,22 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "node_modules/prop-types": { - "version": "15.8.1", - "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", - "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "dev": true, - "license": "MIT", - "dependencies": { - "loose-envify": "^1.4.0", - "object-assign": "^4.1.1", - "react-is": "^16.13.1" - } + "license": "MIT" }, "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } }, "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", @@ -4763,6 +3133,7 @@ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "dev": true, + "license": "MIT", "dependencies": { "safe-buffer": "^5.1.0" } @@ -4791,18 +3162,12 @@ "node": ">=0.10.0" } }, - "node_modules/react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, - "license": "MIT" - }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -4818,6 +3183,7 @@ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", "dev": true, + "license": "MIT", "dependencies": { "picomatch": "^2.2.1" }, @@ -4825,75 +3191,14 @@ "node": ">=8.10.0" } }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", - "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", - "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve": { - "version": "2.0.0-next.5", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.5.tgz", - "integrity": "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.13.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, "node_modules/resolve-from": { @@ -4907,33 +3212,29 @@ } }, "node_modules/restore-cursor": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", - "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "dev": true, + "license": "MIT", "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/restore-cursor/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, + "license": "ISC", "dependencies": { "glob": "^7.1.3" }, @@ -4945,9 +3246,9 @@ } }, "node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -4959,8 +3260,9 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -4977,10 +3279,11 @@ } }, "node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -4988,84 +3291,17 @@ "node": "*" } }, - "node_modules/safe-array-concat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", - "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-array-concat/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, "node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/safe-push-apply": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", - "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-push-apply/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "license": "MIT" }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -5078,70 +3314,24 @@ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" } }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-function-name": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", - "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", - "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, + "license": "MIT", "dependencies": { "shebang-regex": "^3.0.0" }, @@ -5154,84 +3344,9 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, "node_modules/signal-exit": { @@ -5239,6 +3354,7 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, + "license": "ISC", "engines": { "node": ">=14" }, @@ -5292,38 +3408,23 @@ } }, "node_modules/stdin-discarder": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", - "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", "dev": true, - "dependencies": { - "bl": "^5.0.0" - }, + "license": "MIT", "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/stop-iteration-iterator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", - "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } @@ -5333,6 +3434,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", "dev": true, + "license": "MIT", "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", @@ -5351,6 +3453,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -5360,142 +3463,59 @@ "node": ">=8" } }, - "node_modules/string-width-cjs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/string.prototype.matchall": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", - "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.6", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.6", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "internal-slot": "^1.1.0", - "regexp.prototype.flags": "^1.5.3", - "set-function-name": "^2.0.2", - "side-channel": "^1.1.0" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.repeat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", - "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" + "node": ">=8" } }, - "node_modules/string.prototype.trim": { - "version": "1.2.10", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", - "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-object-atoms": "^1.0.0", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "license": "MIT" }, - "node_modules/string.prototype.trimend": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", - "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.2", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", - "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" + "ansi-regex": "^6.2.2" }, "engines": { - "node": ">= 0.4" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/chalk/strip-ansi?sponsor=1" } }, - "node_modules/strip-ansi": { + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, + "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1" }, @@ -5503,15 +3523,12 @@ "node": ">=8" } }, - "node_modules/strip-ansi-cjs": { - "name": "strip-ansi", - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, + "license": "MIT", "engines": { "node": ">=8" } @@ -5521,6 +3538,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -5533,6 +3551,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -5540,26 +3559,18 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", "dev": true, "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">=6" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "engines": { - "node": ">=6" + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/tar-fs": { @@ -5590,41 +3601,6 @@ "node": ">=6" } }, - "node_modules/tar-stream/node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "license": "MIT", - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/tar-stream/node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, "node_modules/tar-stream/node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -5644,6 +3620,7 @@ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", "dev": true, + "license": "ISC", "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", @@ -5654,9 +3631,9 @@ } }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { @@ -5668,8 +3645,9 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -5686,379 +3664,83 @@ } }, "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "license": "Apache-2.0", - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "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, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", - "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", - "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", - "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "for-each": "^0.3.3", - "gopd": "^1.0.1", - "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0", - "reflect.getprototypeof": "^1.0.6" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typescript": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", - "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", - "dev": true, - "peer": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.48.1.tgz", - "integrity": "sha512-FbOKN1fqNoXp1hIl5KYpObVrp0mCn+CLgn479nmu2IsRMrx2vyv74MmsBLVlhg8qVwNFGbXSp8fh1zp8pEoC2A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.48.1", - "@typescript-eslint/parser": "8.48.1", - "@typescript-eslint/typescript-estree": "8.48.1", - "@typescript-eslint/utils": "8.48.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.1.tgz", - "integrity": "sha512-X63hI1bxl5ohelzr0LY5coufyl0LJNthld+abwxpCoo6Gq+hSqhKwci7MUWkXo67mzgUK6YFByhmaHmUcuBJmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.48.1", - "@typescript-eslint/type-utils": "8.48.1", - "@typescript-eslint/utils": "8.48.1", - "@typescript-eslint/visitor-keys": "8.48.1", - "graphemer": "^1.4.0", - "ignore": "^7.0.0", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.1.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.48.1", - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/parser": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.1.tgz", - "integrity": "sha512-PC0PDZfJg8sP7cmKe6L3QIL8GZwU5aRvUFedqSIpw3B+QjRSUZeeITC2M5XKeMXEzL6wccN196iy3JLwKNvDVA==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@typescript-eslint/scope-manager": "8.48.1", - "@typescript-eslint/types": "8.48.1", - "@typescript-eslint/typescript-estree": "8.48.1", - "@typescript-eslint/visitor-keys": "8.48.1", - "debug": "^4.3.4" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <6.0.0" - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/scope-manager": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.1.tgz", - "integrity": "sha512-rj4vWQsytQbLxC5Bf4XwZ0/CKd362DkWMUkviT7DCS057SK64D5lH74sSGzhI6PDD2HCEq02xAP9cX68dYyg1w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.48.1", - "@typescript-eslint/visitor-keys": "8.48.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/types": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.1.tgz", - "integrity": "sha512-+fZ3LZNeiELGmimrujsDCT4CRIbq5oXdHe7chLiW8qzqyPMnn1puNstCrMNVAqwcl2FdIxkuJ4tOs/RFDBVc/Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + }, + "engines": { + "node": "*" } }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.1.tgz", - "integrity": "sha512-/9wQ4PqaefTK6POVTjJaYS0bynCgzh6ClJHGSBj06XEHjkfylzB+A3qvyaXnErEZSaxhIo4YdyBgq6j4RysxDg==", + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.48.1", - "@typescript-eslint/tsconfig-utils": "8.48.1", - "@typescript-eslint/types": "8.48.1", - "@typescript-eslint/visitor-keys": "8.48.1", - "debug": "^4.3.4", - "minimatch": "^9.0.4", - "semver": "^7.6.0", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.1.0" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=12.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/typescript-eslint/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.48.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.1.tgz", - "integrity": "sha512-BmxxndzEWhE4TIEEMBs8lP3MBWN3jFPs/p6gPm/wkv02o41hI6cq9AuSmGAaTTHPtA1FTi2jBre4A9rm5ZmX+Q==", + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.48.1", - "eslint-visitor-keys": "^4.2.1" - }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=12.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/typescript-eslint/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=12" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/typescript-eslint/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, "engines": { - "node": ">= 4" + "node": ">=8.0" } }, - "node_modules/typescript-eslint/node_modules/ts-api-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", - "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { @@ -6068,62 +3750,76 @@ "typescript": ">=4.8.4" } }, - "node_modules/unbox-primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", - "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "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": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" + "prelude-ls": "^1.2.1" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.8.0" } }, - "node_modules/undici-types": { - "version": "6.19.8", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", - "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", - "dev": true + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } }, - "node_modules/update-browserslist-db": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.0.tgz", - "integrity": "sha512-Dn+NlSF/7+0lVSEZ57SYQg6/E44arLzsVOGgrElBn/BlG1B8WKdbLppOocFrXwRNTkNlgdGNaBgH1o0lggDPiw==", + "node_modules/typescript-eslint": { + "version": "8.59.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.1.tgz", + "integrity": "sha512-xqDcFVBmlrltH64lklOVp1wYxgJr6LVdg3NamBgH2OOQDLFdTKfIZXF5PfghrnXQKXZGTQs8tr1vL7fJvq8CTQ==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], "license": "MIT", "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" + "@typescript-eslint/eslint-plugin": "8.59.1", + "@typescript-eslint/parser": "8.59.1", + "@typescript-eslint/typescript-estree": "8.59.1", + "@typescript-eslint/utils": "8.59.1" }, - "bin": { - "update-browserslist-db": "cli.js" + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "browserslist": ">= 4.21.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -6137,13 +3833,15 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" }, "node_modules/v8-to-istanbul": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz", "integrity": "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==", "dev": true, + "license": "ISC", "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", @@ -6158,6 +3856,7 @@ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -6168,122 +3867,29 @@ "node": ">= 8" } }, - "node_modules/which-boxed-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", - "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", - "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.1.0", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type/node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, - "license": "MIT" - }, - "node_modules/which-collection": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", - "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.19", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", - "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "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/workerpool": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", - "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", - "dev": true + "version": "9.3.4", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-9.3.4.tgz", + "integrity": "sha512-TmPRQYYSAnnDiEB0P/Ytip7bFGvqnSU6I2BcuSw7Hx+JSg/DsUi5ebYfc8GYaSdpuvOcEs6dXxPurOYpe9QFwg==", + "dev": true, + "license": "Apache-2.0" }, "node_modules/wrap-ansi": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", @@ -6302,6 +3908,7 @@ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -6314,17 +3921,29 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/wrap-ansi-cjs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -6334,23 +3953,25 @@ "node": ">=8" } }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "engines": { - "node": ">=12" + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" + "engines": { + "node": ">=8" } }, "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, + "license": "MIT", "engines": { "node": ">=12" }, @@ -6358,47 +3979,28 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", - "dev": true, - "dependencies": { - "ansi-regex": "^6.0.1" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, + "license": "MIT", "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -6417,6 +4019,7 @@ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", "dev": true, + "license": "ISC", "engines": { "node": ">=12" } @@ -6426,6 +4029,7 @@ "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", "dev": true, + "license": "MIT", "dependencies": { "camelcase": "^6.0.0", "decamelize": "^4.0.0", @@ -6436,17 +4040,29 @@ "node": ">=10" } }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/yargs/node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/yargs/node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, + "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -6456,41 +4072,31 @@ "node": ">=8" } }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } - }, - "node_modules/zod": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz", - "integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==", - "dev": true, - "license": "MIT", - "peer": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-validation-error": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", - "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" - } } } } diff --git a/package.json b/package.json index b8f95de..cca88e5 100644 --- a/package.json +++ b/package.json @@ -75,22 +75,22 @@ "test": "vscode-test" }, "devDependencies": { - "@eslint/js": "^9.39.1", - "@types/mocha": "^10.0.7", - "@types/node": "20.x", - "@types/vscode": "^1.95.0", - "@vscode/test-cli": "^0.0.9", - "@vscode/test-electron": "^2.4.0", - "eslint": "^9.39.1", - "eslint-config-prettier": "^9.1.0", - "globals": "^16.5.0", - "prettier": "^3.3.3", - "rimraf": "^3.0.2", - "typescript": "^5.4.5", - "typescript-eslint": "^8.46.3" + "@eslint/js": "^10.0.1", + "@types/mocha": "^10.0.10", + "@types/node": "25.x", + "@types/vscode": "^1.118.0", + "@vscode/test-cli": "^0.0.11", + "@vscode/test-electron": "^2.5.2", + "eslint": "^10.3.0", + "eslint-config-prettier": "^10.1.8", + "globals": "^17.6.0", + "prettier": "^3.8.3", + "rimraf": "^6.1.3", + "typescript": "^6.0.3", + "typescript-eslint": "^8.59.1" }, "dependencies": { - "axios": "^1.7.8", + "axios": "^1.15.2", "keytar": "^7.9.0" } } From 567eed368c5e10740cf97ef28c2d9be00b8582e6 Mon Sep 17 00:00:00 2001 From: Riley Smith Date: Fri, 1 May 2026 11:52:47 -0700 Subject: [PATCH 15/20] bump packages again --- package-lock.json | 500 +++++++++++++++++++++------------------------- 1 file changed, 228 insertions(+), 272 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4b92f27..9eb83d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,23 +8,23 @@ "name": "submittyextension", "version": "0.0.1", "dependencies": { - "axios": "^1.7.8", + "axios": "^1.15.2", "keytar": "^7.9.0" }, "devDependencies": { - "@eslint/js": "^9.39.1", - "@types/mocha": "^10.0.7", - "@types/node": "20.x", - "@types/vscode": "^1.95.0", + "@eslint/js": "^10.0.1", + "@types/mocha": "^10.0.10", + "@types/node": "25.x", + "@types/vscode": "^1.118.0", "@vscode/test-cli": "^0.0.11", - "@vscode/test-electron": "^2.4.0", - "eslint": "^9.39.1", - "eslint-config-prettier": "^9.1.0", - "globals": "^16.5.0", - "prettier": "^3.3.3", - "rimraf": "^3.0.2", - "typescript": "^5.4.5", - "typescript-eslint": "^8.46.3" + "@vscode/test-electron": "^2.5.2", + "eslint": "^10.3.0", + "eslint-config-prettier": "^10.1.8", + "globals": "^17.6.0", + "prettier": "^3.8.3", + "rimraf": "^6.1.3", + "typescript": "^6.0.3", + "typescript-eslint": "^8.59.1" }, "engines": { "vscode": "^1.95.0" @@ -80,166 +80,128 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.21.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", - "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "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": "^2.1.7", + "@eslint/object-schema": "^3.0.5", "debug": "^4.3.1", - "minimatch": "^3.1.5" + "minimatch": "^10.2.4" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "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", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, "engines": { - "node": "*" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "18 || 20 || >=22" } }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@types/json-schema": "^7.0.15" + "balanced-match": "^4.0.2" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "18 || 20 || >=22" } }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "MIT", + "license": "BlueOak-1.0.0", "dependencies": { - "ajv": "^6.14.0", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.5", - "strip-json-comments": "^3.1.1" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "18 || 20 || >=22" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "node_modules/@eslint/config-helpers": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.5.5.tgz", + "integrity": "sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" + "@eslint/core": "^1.2.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "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": "ISC", + "license": "Apache-2.0", "dependencies": { - "brace-expansion": "^1.1.7" + "@types/json-schema": "^7.0.15" }, "engines": { - "node": "*" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "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": "^18.18.0 || ^20.9.0 || >=21.1.0" + "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": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "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": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.1.tgz", + "integrity": "sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.17.0", + "@eslint/core": "^1.2.1", "levn": "^0.4.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, "node_modules/@humanfs/core": { @@ -375,6 +337,13 @@ "node": ">=14" } }, + "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.8", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", @@ -404,13 +373,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.19.39", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", - "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", + "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.19.0" } }, "node_modules/@types/vscode": { @@ -689,19 +658,6 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/visitor-keys/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, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, "node_modules/@vscode/test-cli": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.11.tgz", @@ -1018,16 +974,6 @@ "node": ">= 0.4" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -1487,33 +1433,30 @@ } }, "node_modules/eslint": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", - "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.3.0.tgz", + "integrity": "sha512-XbEXaRva5cF0ZQB8w6MluHA0kZZfV2DuCMJ3ozyEOHLwDpZX2Lmm/7Pp0xdJmI0GL1W05VH5VwIFHEm1Vcw2gw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.2", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "9.39.4", - "@eslint/plugin-kit": "^0.4.1", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.5.5", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.1", "@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", - "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.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": "^8.0.0", @@ -1523,8 +1466,7 @@ "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.5", + "minimatch": "^10.2.4", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, @@ -1532,7 +1474,7 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://eslint.org/donate" @@ -1547,57 +1489,74 @@ } }, "node_modules/eslint-config-prettier": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.2.tgz", - "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", "bin": { "eslint-config-prettier": "bin/cli.js" }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, "peerDependencies": { "eslint": ">=7.0.0" } }, "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "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": "BSD-2-Clause", "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "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, "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" } }, + "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": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/eslint/node_modules/glob-parent": { @@ -1614,31 +1573,34 @@ } }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "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.15.0", + "acorn": "^8.16.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" + "eslint-visitor-keys": "^5.0.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { "url": "https://opencollective.com/eslint" @@ -1986,9 +1948,9 @@ } }, "node_modules/globals": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", - "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", + "version": "17.6.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.6.0.tgz", + "integrity": "sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==", "dev": true, "license": "MIT", "engines": { @@ -2148,23 +2110,6 @@ "dev": true, "license": "MIT" }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -2491,13 +2436,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -2959,19 +2897,6 @@ "dev": true, "license": "(MIT AND Zlib)" }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3201,16 +3126,6 @@ "node": ">=0.10.0" } }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/restore-cursor": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", @@ -3229,66 +3144,107 @@ } }, "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-6.1.3.tgz", + "integrity": "sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "glob": "^7.1.3" + "glob": "^13.0.3", + "package-json-from-dist": "^1.0.1" }, "bin": { - "rimraf": "bin.js" + "rimraf": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/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/rimraf/node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rimraf/node_modules/lru-cache": { + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.5.tgz", + "integrity": "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/path-scurry": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/safe-buffer": { @@ -3776,9 +3732,9 @@ } }, "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3814,9 +3770,9 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.19.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", + "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", "dev": true, "license": "MIT" }, From 14641f39de4c99e662f3a7bd03a386aaa6d87da8 Mon Sep 17 00:00:00 2001 From: Riley Smith Date: Sat, 2 May 2026 23:20:31 -0700 Subject: [PATCH 16/20] fix typescript compiling --- .nvmrc | 1 + package-lock.json | 16 ++++++++-------- package.json | 2 +- tsconfig.json | 5 +++-- 4 files changed, 13 insertions(+), 11 deletions(-) create mode 100644 .nvmrc diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..0452007 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +24.14.1 \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 9eb83d9..94411ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "devDependencies": { "@eslint/js": "^10.0.1", "@types/mocha": "^10.0.10", - "@types/node": "25.x", + "@types/node": "^24.12.2", "@types/vscode": "^1.118.0", "@vscode/test-cli": "^0.0.11", "@vscode/test-electron": "^2.5.2", @@ -373,13 +373,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.6.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.6.0.tgz", - "integrity": "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==", + "version": "24.12.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", + "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.19.0" + "undici-types": "~7.16.0" } }, "node_modules/@types/vscode": { @@ -3770,9 +3770,9 @@ } }, "node_modules/undici-types": { - "version": "7.19.2", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.19.2.tgz", - "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, diff --git a/package.json b/package.json index cca88e5..155b5cc 100644 --- a/package.json +++ b/package.json @@ -77,7 +77,7 @@ "devDependencies": { "@eslint/js": "^10.0.1", "@types/mocha": "^10.0.10", - "@types/node": "25.x", + "@types/node": "^24.12.2", "@types/vscode": "^1.118.0", "@vscode/test-cli": "^0.0.11", "@vscode/test-electron": "^2.5.2", diff --git a/tsconfig.json b/tsconfig.json index eba9455..a249db6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,10 @@ { "compilerOptions": { - "module": "Node16", + "module": "NodeNext", "target": "ES2022", "outDir": "out", - "lib": ["ES2022"], + "lib": ["ES2022", "DOM"], + "types": ["vscode", "node", "mocha"], "sourceMap": true, "rootDir": "src", "strict": true /* enable all strict type-checking options */ From 84bd9b61ba85503d30d16798c1ad88b1018d6b23 Mon Sep 17 00:00:00 2001 From: Riley Smith Date: Sun, 3 May 2026 00:37:16 -0700 Subject: [PATCH 17/20] fix lint errors --- src/services/apiService.ts | 30 ++++++++++++++++++++++-------- src/services/courseRepoResolver.ts | 7 +++++-- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/services/apiService.ts b/src/services/apiService.ts index 0706c47..02fc330 100644 --- a/src/services/apiService.ts +++ b/src/services/apiService.ts @@ -65,7 +65,9 @@ export class ApiService { const token: string = response.data.data.token; return token; } catch (error: unknown) { - throw new Error(getErrorMessage(error, 'Login failed.')); + throw new Error(getErrorMessage(error, 'Login failed.'), { + cause: error, + }); } } @@ -74,7 +76,9 @@ export class ApiService { const response = await this.client.get('/api/me'); return response.data; } catch (error: unknown) { - throw new Error(getErrorMessage(error, 'Failed to fetch me.')); + throw new Error(getErrorMessage(error, 'Failed to fetch me.'), { + cause: error, + }); } } @@ -87,7 +91,9 @@ export class ApiService { return response.data; } catch (error: unknown) { console.error('Error fetching courses:', error); - throw new Error(getErrorMessage(error, 'Failed to fetch courses.')); + throw new Error(getErrorMessage(error, 'Failed to fetch courses.'), { + cause: error, + }); } } @@ -101,7 +107,9 @@ export class ApiService { return response.data; } catch (error: unknown) { console.error('Error fetching gradables:', error); - throw new Error(getErrorMessage(error, 'Failed to fetch gradables.')); + throw new Error(getErrorMessage(error, 'Failed to fetch gradables.'), { + cause: error, + }); } } @@ -120,7 +128,10 @@ export class ApiService { return response.data; } catch (error: unknown) { console.error('Error fetching grade details:', error); - throw new Error(getErrorMessage(error, 'Failed to fetch grade details.')); + throw new Error( + getErrorMessage(error, 'Failed to fetch grade details.'), + { cause: error } + ); } } @@ -178,8 +189,10 @@ export class ApiService { const response = await this.client.post(url); return response.data; } catch (error: unknown) { - console.error('Error submitt`ing VCS gradable:', error); - throw new Error(getErrorMessage(error, 'Failed to submit VCS gradable.')); + console.error('Error submitting VCS gradable:', error); + throw new Error(getErrorMessage(error, 'Failed to submit VCS gradable.'), { + cause: error, + }); } } @@ -198,7 +211,8 @@ export class ApiService { } catch (error: unknown) { console.error('Error fetching previous attempts:', error); throw new Error( - getErrorMessage(error, 'Failed to fetch previous attempts.') + getErrorMessage(error, 'Failed to fetch previous attempts.'), + { cause: error } ); } } diff --git a/src/services/courseRepoResolver.ts b/src/services/courseRepoResolver.ts index 07bb787..7ccffe5 100644 --- a/src/services/courseRepoResolver.ts +++ b/src/services/courseRepoResolver.ts @@ -71,9 +71,12 @@ function extractGitRemoteUrlsFromConfig(gitConfigText: string): string[] { // [remote "origin"] // url = https://example/.../term/courseId/... const urlRegex = /^\s*url\s*=\s*(.+)\s*$/gim; - let match: RegExpExecArray | null = null; - while ((match = urlRegex.exec(gitConfigText))) { + for ( + let match = urlRegex.exec(gitConfigText); + match !== null; + match = urlRegex.exec(gitConfigText) + ) { const rawUrl = match[1]?.trim(); if (rawUrl) { urls.push(rawUrl); From 5aa1f846dc9633c65c282ebad08099f061dc30a2 Mon Sep 17 00:00:00 2001 From: Riley Smith Date: Sun, 3 May 2026 00:43:08 -0700 Subject: [PATCH 18/20] code cleanup --- src/services/gitService.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/services/gitService.ts b/src/services/gitService.ts index d68022e..a397754 100644 --- a/src/services/gitService.ts +++ b/src/services/gitService.ts @@ -59,8 +59,6 @@ export class GitService { ); } - console.log(repo); - // Refresh repository state before checking tracked/untracked changes. await repo.status(); From ccd814af586b5e4e0a30f0251d00345fb5b1d48a Mon Sep 17 00:00:00 2001 From: Riley Smith Date: Fri, 8 May 2026 10:44:08 -0700 Subject: [PATCH 19/20] Fix merge conflict issues --- package-lock.json | 7 +++ src/interfaces/Responses.ts | 8 --- src/services/apiService.ts | 80 +++------------------------- src/services/authService.ts | 75 -------------------------- src/services/gitService.ts | 34 ------------ src/services/testingService.ts | 96 +++------------------------------- src/sidebarProvider.ts | 61 ++------------------- 7 files changed, 23 insertions(+), 338 deletions(-) diff --git a/package-lock.json b/package-lock.json index b6371c7..94411ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2944,6 +2944,13 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/picomatch": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", diff --git a/src/interfaces/Responses.ts b/src/interfaces/Responses.ts index a6c39cf..6f6c2e0 100644 --- a/src/interfaces/Responses.ts +++ b/src/interfaces/Responses.ts @@ -1,27 +1,19 @@ import { Course } from './Courses'; import { Gradable } from './Gradables'; -import { Course } from './Courses'; -import { Gradable } from './Gradables'; export interface ApiResponse { status: string; data: T; message?: string; - status: string; - data: T; - message?: string; } export type CourseResponse = ApiResponse<{ unarchived_courses: Course[]; dropped_courses: Course[]; - unarchived_courses: Course[]; - dropped_courses: Course[]; }>; export type LoginResponse = ApiResponse<{ token: string; - token: string; }>; export type GradableResponse = ApiResponse<{ diff --git a/src/services/apiService.ts b/src/services/apiService.ts index f1d25b8..aad4b94 100644 --- a/src/services/apiService.ts +++ b/src/services/apiService.ts @@ -28,15 +28,7 @@ function getErrorMessage(error: unknown, fallback: string): string { export class ApiService { private client: ApiClient; private static instance: ApiService; - private client: ApiClient; - private static instance: ApiService; - constructor( - private context: vscode.ExtensionContext, - apiBaseUrl: string - ) { - this.client = new ApiClient(apiBaseUrl); - } constructor( private context: vscode.ExtensionContext, apiBaseUrl: string @@ -54,21 +46,6 @@ export class ApiService { this.client.setBaseURL(baseUrl); } - /** - * Login to the Submitty API - */ - async login(userId: string, password: string): Promise { - try { - const response = await this.client.post( - '/api/token', - { - user_id: userId, - password: password, - }, - { - headers: { 'Content-Type': 'multipart/form-data' }, - } - ); /** * Login to the Submitty API */ @@ -158,26 +135,6 @@ export class ApiService { } } - /** - * Poll fetchGradeDetails until autograding_complete is true and test_cases has data. - * @param intervalMs Delay between requests (default 2000) - * @param timeoutMs Stop after this many ms (default 300000 = 5 min); 0 = no timeout - * @returns The final AutoGraderDetails with complete data - */ - async pollGradeDetailsUntilComplete( - term: string, - courseId: string, - gradeableId: string, - options?: { - intervalMs?: number; - timeoutMs?: number; - token?: vscode.CancellationToken; - } - ): Promise { - const intervalMs = options?.intervalMs ?? 2000; - const timeoutMs = options?.timeoutMs ?? 300000; - const token = options?.token; - const deadline = timeoutMs > 0 ? Date.now() + timeoutMs : 0; /** * Poll fetchGradeDetails until autograding_complete is true and test_cases has data. * @param intervalMs Delay between requests (default 2000) @@ -199,22 +156,11 @@ export class ApiService { const token = options?.token; const deadline = timeoutMs > 0 ? Date.now() + timeoutMs : 0; - const isComplete = (res: AutoGraderDetails): boolean => - res?.data?.autograding_complete === true && - Array.isArray(res.data.test_cases) && - res.data.test_cases.length > 0; const isComplete = (res: AutoGraderDetails): boolean => res?.data?.autograding_complete === true && Array.isArray(res.data.test_cases) && res.data.test_cases.length > 0; - for (;;) { - if (token?.isCancellationRequested) { - throw new Error('Cancelled'); - } - if (deadline > 0 && Date.now() >= deadline) { - throw new Error('Autograding did not complete within the timeout.'); - } for (;;) { if (token?.isCancellationRequested) { throw new Error('Cancelled'); @@ -223,10 +169,6 @@ export class ApiService { throw new Error('Autograding did not complete within the timeout.'); } - const result = await this.fetchGradeDetails(term, courseId, gradeableId); - if (isComplete(result)) { - return result; - } const result = await this.fetchGradeDetails(term, courseId, gradeableId); if (isComplete(result)) { return result; @@ -235,9 +177,6 @@ export class ApiService { await new Promise(r => setTimeout(r, intervalMs)); } } - await new Promise(r => setTimeout(r, intervalMs)); - } - } async submitVCSGradable( term: string, @@ -251,9 +190,12 @@ export class ApiService { return response.data; } catch (error: unknown) { console.error('Error submitting VCS gradable:', error); - throw new Error(getErrorMessage(error, 'Failed to submit VCS gradable.'), { - cause: error, - }); + throw new Error( + getErrorMessage(error, 'Failed to submit VCS gradable.'), + { + cause: error, + } + ); } } @@ -287,14 +229,4 @@ export class ApiService { } return ApiService.instance; } - static getInstance( - context: vscode.ExtensionContext, - apiBaseUrl: string - ): ApiService { - if (!ApiService.instance) { - ApiService.instance = new ApiService(context, apiBaseUrl); - } - return ApiService.instance; - } } - diff --git a/src/services/authService.ts b/src/services/authService.ts index 62457e1..bdcde29 100644 --- a/src/services/authService.ts +++ b/src/services/authService.ts @@ -18,10 +18,6 @@ export class AuthService { const config = vscode.workspace.getConfiguration('submitty'); let baseUrl = config.get('baseUrl', ''); - // If base URL is configured, set it on the API service - if (baseUrl) { - this.apiService.setBaseUrl(baseUrl); - } // If base URL is configured, set it on the API service if (baseUrl) { this.apiService.setBaseUrl(baseUrl); @@ -68,24 +64,6 @@ export class AuthService { return; } - // If no base URL is configured, prompt for it - if (!baseUrl) { - const inputUrl = await vscode.window.showInputBox({ - prompt: 'Enter Submitty API URL', - placeHolder: 'https://example.submitty.edu', - ignoreFocusOut: true, - validateInput: value => { - if (!value || value.trim().length === 0) { - return 'URL is required'; - } - try { - new URL(value); - return null; - } catch { - return 'Please enter a valid URL'; - } - }, - }); // If no base URL is configured, prompt for it if (!baseUrl) { const inputUrl = await vscode.window.showInputBox({ @@ -105,24 +83,13 @@ export class AuthService { }, }); - if (!inputUrl) { - // User cancelled - return; - } if (!inputUrl) { // User cancelled return; } - baseUrl = inputUrl.trim(); baseUrl = inputUrl.trim(); - // Save base URL to configuration - await config.update( - 'baseUrl', - baseUrl, - vscode.ConfigurationTarget.Global - ); // Save base URL to configuration await config.update( 'baseUrl', @@ -133,21 +100,7 @@ export class AuthService { // Set the base URL on the API service this.apiService.setBaseUrl(baseUrl); } - // Set the base URL on the API service - this.apiService.setBaseUrl(baseUrl); - } - const userId = await vscode.window.showInputBox({ - prompt: 'Enter your Submitty username', - placeHolder: 'Username', - ignoreFocusOut: true, - validateInput: value => { - if (!value || value.trim().length === 0) { - return 'Username is required'; - } - return null; - }, - }); const userId = await vscode.window.showInputBox({ prompt: 'Enter your Submitty username', placeHolder: 'Username', @@ -160,27 +113,11 @@ export class AuthService { }, }); - if (!userId) { - // User cancelled - return; - } if (!userId) { // User cancelled return; } - const password = await vscode.window.showInputBox({ - prompt: 'Enter your Submitty password', - placeHolder: 'Password', - password: true, - ignoreFocusOut: true, - validateInput: value => { - if (!value || value.trim().length === 0) { - return 'Password is required'; - } - return null; - }, - }); const password = await vscode.window.showInputBox({ prompt: 'Enter your Submitty password', placeHolder: 'Password', @@ -194,19 +131,11 @@ export class AuthService { }, }); - if (!password) { - // User cancelled - return; - } if (!password) { // User cancelled return; } - // Update API service with URL and login - try { - // Perform login - await this.login(userId.trim(), password); // Update API service with URL and login try { // Perform login @@ -232,10 +161,6 @@ export class AuthService { return await keytar.getPassword('submittyToken', 'submittyToken'); } - // public method to get token - async getAuthorizationToken(): Promise { - return await this.getToken(); - } // public method to get token async getAuthorizationToken(): Promise { return await this.getToken(); diff --git a/src/services/gitService.ts b/src/services/gitService.ts index f98a4aa..a397754 100644 --- a/src/services/gitService.ts +++ b/src/services/gitService.ts @@ -73,18 +73,6 @@ export class GitService { await repo.commit(message, options); } - /** - * Pull from the current branch's upstream. - */ - async pull(): Promise { - const repo = this.getRepository(); - if (!repo) { - throw new Error( - 'No Git repository found. Open a workspace folder that is a Git repo.' - ); - } - await repo.pull(); - } /** * Pull from the current branch's upstream. */ @@ -120,26 +108,4 @@ export class GitService { options?.force ); } - /** - * Push the current branch. Optionally set upstream or force push. - */ - async push(options?: { - remote?: string; - branch?: string; - setUpstream?: boolean; - force?: ForcePushMode; - }): Promise { - const repo = this.getRepository(); - if (!repo) { - throw new Error( - 'No Git repository found. Open a workspace folder that is a Git repo.' - ); - } - await repo.push( - options?.remote, - options?.branch, - options?.setUpstream, - options?.force - ); - } } diff --git a/src/services/testingService.ts b/src/services/testingService.ts index 4aeb258..4ae3b35 100644 --- a/src/services/testingService.ts +++ b/src/services/testingService.ts @@ -17,9 +17,6 @@ interface GradeableMeta { term: string; courseId: string; gradeableId: string; - term: string; - courseId: string; - gradeableId: string; } export class TestingService { @@ -27,26 +24,7 @@ export class TestingService { private rootItem: vscode.TestItem; private gradeableMeta = new WeakMap(); private testCaseMeta = new WeakMap(); - private controller: vscode.TestController; - private rootItem: vscode.TestItem; - private gradeableMeta = new WeakMap(); - private testCaseMeta = new WeakMap(); - constructor( - private readonly context: vscode.ExtensionContext, - private readonly apiService: ApiService - ) { - this.controller = vscode.tests.createTestController( - CONTROLLER_ID, - CONTROLLER_LABEL - ); - this.rootItem = this.controller.createTestItem( - ROOT_ID, - 'Submitty', - undefined - ); - this.rootItem.canResolveChildren = true; - this.controller.items.add(this.rootItem); constructor( private readonly context: vscode.ExtensionContext, private readonly apiService: ApiService @@ -63,13 +41,6 @@ export class TestingService { this.rootItem.canResolveChildren = true; this.controller.items.add(this.rootItem); - this.controller.resolveHandler = async item => this.resolveHandler(item); - const runProfile = this.controller.createRunProfile( - 'Run', - vscode.TestRunProfileKind.Run, - (request, token) => this.runHandler(request, token) - ); - runProfile.isDefault = true; this.controller.resolveHandler = async item => this.resolveHandler(item); const runProfile = this.controller.createRunProfile( 'Run', @@ -80,8 +51,6 @@ export class TestingService { context.subscriptions.push(this.controller); } - context.subscriptions.push(this.controller); - } /** * Add a gradeable to the Test Explorer so the user can run it and see results. @@ -127,9 +96,6 @@ export class TestingService { run.end(); } - private getGradeableMeta(item: vscode.TestItem): GradeableMeta | undefined { - return this.gradeableMeta.get(item); - } private getGradeableMeta(item: vscode.TestItem): GradeableMeta | undefined { return this.gradeableMeta.get(item); } @@ -216,28 +182,6 @@ export class TestingService { } } - private syncTestCaseChildren( - gradeableItem: vscode.TestItem, - data: AutoGraderDetailsData - ): void { - const cases = data.test_cases ?? []; - for (let i = 0; i < cases.length; i++) { - const tc = cases[i]; - const id = `tc-${i}-${tc.name ?? i}`; - let child = gradeableItem.children.get(id); - if (!child) { - child = this.controller.createTestItem( - id, - tc.name || `Test ${i + 1}`, - undefined - ); - this.testCaseMeta.set(child, tc); - gradeableItem.children.add(child); - } else { - this.testCaseMeta.set(child, tc); - } - } - } private syncTestCaseChildren( gradeableItem: vscode.TestItem, data: AutoGraderDetailsData @@ -305,16 +249,14 @@ export class TestingService { if (item.children.size === 0) { run.appendOutput(`No test cases in response.\r\n`); run.failed(item, new vscode.TestMessage('No test cases returned.'), 0); + } else if (allPassed) { + run.passed(item, Date.now() - start); } else { - if (allPassed) { - run.passed(item, Date.now() - start); - } else { - run.failed( - item, - new vscode.TestMessage('Some test cases failed.'), - Date.now() - start - ); - } + run.failed( + item, + new vscode.TestMessage('Some test cases failed.'), + Date.now() - start + ); } } @@ -325,17 +267,6 @@ export class TestingService { const run = this.controller.createTestRun(request); const queue: vscode.TestItem[] = []; - if (request.include) { - request.include.forEach(t => { - if (t.id === ROOT_ID) { - this.rootItem.children.forEach(c => queue.push(c)); - } else { - queue.push(t); - } - }); - } else { - this.rootItem.children.forEach(t => queue.push(t)); - } if (request.include) { request.include.forEach(t => { if (t.id === ROOT_ID) { @@ -348,28 +279,17 @@ export class TestingService { this.rootItem.children.forEach(t => queue.push(t)); } - while (queue.length > 0 && !token.isCancellationRequested) { - const item = queue.shift()!; - if (request.exclude?.includes(item)) { - continue; - } while (queue.length > 0 && !token.isCancellationRequested) { const item = queue.shift()!; if (request.exclude?.includes(item)) { continue; } - const meta = this.getGradeableMeta(item); - if (!meta) { - continue; - } const meta = this.getGradeableMeta(item); if (!meta) { continue; } - run.started(item); - run.appendOutput(`Polling grade details for ${item.label}...\r\n`); run.started(item); run.appendOutput(`Polling grade details for ${item.label}...\r\n`); @@ -392,6 +312,4 @@ export class TestingService { run.end(); } - run.end(); - } } diff --git a/src/sidebarProvider.ts b/src/sidebarProvider.ts index 6f609d4..173cdf4 100644 --- a/src/sidebarProvider.ts +++ b/src/sidebarProvider.ts @@ -31,12 +31,6 @@ export class SidebarProvider implements vscode.WebviewViewProvider { ): Promise { this._view = webviewView; - webviewView.webview.options = { - enableScripts: true, - localResourceRoots: [ - vscode.Uri.joinPath(this.context.extensionUri, 'src', 'webview'), - ], - }; webviewView.webview.options = { enableScripts: true, localResourceRoots: [ @@ -44,11 +38,8 @@ export class SidebarProvider implements vscode.WebviewViewProvider { ], }; - // Initially show blank screen webviewView.webview.html = this.getBlankHtml(); - // Reload courses any time the view becomes visible again (e.g. user - // closes/hides the panel and comes back). this.visibilityDisposable?.dispose(); this.visibilityDisposable = webviewView.onDidChangeVisibility(async () => { if (webviewView.visible) { @@ -56,48 +47,18 @@ export class SidebarProvider implements vscode.WebviewViewProvider { } }); - // Initialize authentication when sidebar is opened (only once) - if (!this.isInitialized) { - this.isInitialized = true; - try { - await this.authService.initialize(); - // Initialize authentication when sidebar is opened (only once) if (!this.isInitialized) { this.isInitialized = true; try { await this.authService.initialize(); - - // After authentication, fetch and display courses await this.loadCourses(); - } catch (error: any) { + } catch (error: unknown) { console.error('Authentication initialization failed:', error); - // Error is already shown to user in authService } } else { - // If already initialized, just load courses - await this.loadCourses(); - } - // After authentication, fetch and display courses - await this.loadCourses(); - } catch (error: any) { - console.error('Authentication initialization failed:', error); - // Error is already shown to user in authService - } - } else { - // If already initialized, just load courses await this.loadCourses(); } - // Handle messages from the webview - webviewView.webview.onDidReceiveMessage( - async message => { - await this.handleMessage(message, webviewView); - }, - undefined, - this.context.subscriptions - ); - } - // Handle messages from the webview webviewView.webview.onDidReceiveMessage( async message => { await this.handleMessage(message, webviewView); @@ -108,15 +69,7 @@ export class SidebarProvider implements vscode.WebviewViewProvider { } private async loadCourses(): Promise { - if (!this._view) { - return; - } - private async loadCourses(): Promise { - if (!this._view) { - return; - } - - if (this.isLoadingCourses) { + if (!this._view || this.isLoadingCourses) { return; } @@ -127,12 +80,7 @@ export class SidebarProvider implements vscode.WebviewViewProvider { return; } - // Show classes HTML - this._view.webview.html = getClassesHtml(this.context); - // Show classes HTML this._view.webview.html = getClassesHtml(this.context); - - // Fetch and display courses await this.fetchAndDisplayCourses(token, this._view); } catch (error: unknown) { const err = error instanceof Error ? error.message : String(error); @@ -205,9 +153,9 @@ export class SidebarProvider implements vscode.WebviewViewProvider { command: MessageCommand.ERROR, data: { message: `Unknown command: ${msg.command}` }, }); - break; } } + private async fetchAndDisplayCourses( token: string, view: vscode.WebviewView @@ -367,8 +315,6 @@ export class SidebarProvider implements vscode.WebviewViewProvider { } } - private getBlankHtml(): string { - return ` private getBlankHtml(): string { return ` @@ -390,5 +336,4 @@ export class SidebarProvider implements vscode.WebviewViewProvider { `; } - } } From 1f02f699f289c20f48037b78040ba59fb37d4d89 Mon Sep 17 00:00:00 2001 From: Riley Smith Date: Fri, 8 May 2026 10:58:10 -0700 Subject: [PATCH 20/20] add third party licenses --- LICENSE-THIRD-PARTY.md | 10 ++++++++++ package.json | 2 +- src/typings/vscode-git.d.ts | 19 +++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 LICENSE-THIRD-PARTY.md diff --git a/LICENSE-THIRD-PARTY.md b/LICENSE-THIRD-PARTY.md new file mode 100644 index 0000000..b686dbb --- /dev/null +++ b/LICENSE-THIRD-PARTY.md @@ -0,0 +1,10 @@ +Third Party Licenses +==================== + +While Submitty VSCode is made available under the [BSD "3-Clause" License](https://github.com/Submitty/Submitty-VSCode/blob/master/LICENSE.md) +we utilize several third-party libraries to help power various components. Below is a list of these components and their relevant copyrights, copied when the source was included/last updated within Submitty. + + +| Component | Copyright | Url | License | +|--------------|----------------------------------------------------------------------|-----|---------| +| vscode-git | Copyright (c) Microsoft Corporation. All rights reserved. | https://code.visualstudio.com | [MIT License](https://github.com/microsoft/vscode/blob/main/LICENSE.txt) | \ No newline at end of file diff --git a/package.json b/package.json index 155b5cc..f7ac05d 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "test": "vscode-test" }, "devDependencies": { - "@eslint/js": "^10.0.1", + "@eslint/js": "10.0.1", "@types/mocha": "^10.0.10", "@types/node": "^24.12.2", "@types/vscode": "^1.118.0", diff --git a/src/typings/vscode-git.d.ts b/src/typings/vscode-git.d.ts index d467672..5bcc51d 100644 --- a/src/typings/vscode-git.d.ts +++ b/src/typings/vscode-git.d.ts @@ -1,6 +1,25 @@ /*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. + * MIT License + Copyright (c) 2015 - present Microsoft Corporation + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *--------------------------------------------------------------------------------------------*/ import { Uri, Event, Disposable, ProviderResult, Command, CancellationToken } from 'vscode';