diff --git a/.gitignore b/.gitignore index 6267e50..af0d98d 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,6 @@ tmp/ # localization compiled src/localization/compiled + +# test configuration with credentials +tests/test-config.local.json diff --git a/.husky/pre-commit b/.husky/pre-commit index a4c127a..8fe081d 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,5 +1,6 @@ node check-version.js yarn run format yarn run lint:fix +yarn run test yarn run build yarn run validate_locale_once \ No newline at end of file diff --git a/eslint.config.js b/eslint.config.js index cb377f2..e8429e0 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -6,7 +6,7 @@ import tsPlugin from '@typescript-eslint/eslint-plugin'; export default defineConfig([ { - ignores: ['main.js', 'node_modules/**', 'src/**/*.js'], + ignores: ['main.js', 'node_modules/**', 'src/**/*.js', 'tests/**'], }, { files: ['**/*.ts'], diff --git a/manifest.json b/manifest.json index 5b1ae80..2d2a9ce 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "jira-sync", "name": "Jira Issue Manager", - "version": "1.6.1", + "version": "1.7.0", "minAppVersion": "1.10.1", "description": "Get Jira issues, create and update them. Issue status and worklog management.", "author": "Alamion", diff --git a/package.json b/package.json index 22528b4..d195825 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-jira-sync", - "version": "1.6.1", + "version": "1.7.0", "packageManager": "yarn@1.22.22", "description": "This is a sample plugin for Obsidian (https://obsidian.md)", "main": "main.js", @@ -15,6 +15,9 @@ "prepare": "husky", "lint": "eslint .", "lint:fix": "eslint . --fix", + "test": "vitest run --config tests/vitest.config.ts", + "test:watch": "vitest --config tests/vitest.config.ts", + "test:int": "vitest run --config tests/vitest.config.ts tests/__tests__/api/", "format": "prettier --write ." }, "keywords": [], @@ -36,11 +39,13 @@ "eslint": "^10.2.1", "globals": "^17.5.0", "husky": "^9.1.7", + "jsdom": "^29.1.1", "lodash": "^4.17.23", "obsidian": "latest", "prettier": "^3.8.3", "tslib": "2.4.0", - "typescript": "^6.0.3" + "typescript": "^6.0.3", + "vitest": "^4.1.9" }, "dependencies": { "acorn": "^8.14.1", diff --git a/src/commands/index.ts b/src/commands/index.ts index 0d4055d..e4a414c 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -6,3 +6,4 @@ export * from './createIssue'; export * from './getIssue'; export * from './updateIssue'; export * from './updateStatus'; +export * from './rebuildCache'; diff --git a/src/commands/rebuildCache.ts b/src/commands/rebuildCache.ts new file mode 100644 index 0000000..c58885c --- /dev/null +++ b/src/commands/rebuildCache.ts @@ -0,0 +1,13 @@ +import { Notice } from 'obsidian'; +import JiraPlugin from '../main'; + +export function registerRebuildCacheCommand(plugin: JiraPlugin): void { + plugin.addCommand({ + id: 'rebuild-issue-cache', + name: 'Rebuild issue file cache from filesystem', + callback: async () => { + await plugin.rebuildCache(); + new Notice('Issue cache rebuilt successfully'); + }, + }); +} diff --git a/src/file_operations/getIssue.ts b/src/file_operations/getIssue.ts index 57f0323..bdc4b73 100644 --- a/src/file_operations/getIssue.ts +++ b/src/file_operations/getIssue.ts @@ -7,24 +7,18 @@ import { Notice, TFile, TFolder } from 'obsidian'; import { defaultTemplate } from '../default/defaultTemplate'; import { debugLog } from '../tools/debugLogging'; -function generateFilenameFromTemplate(template: string, issue: JiraIssue): string { +export function generateFilenameFromTemplate( + template: string, + issue: { key: string; fields?: { summary?: string } }, +): string { let filename = template; - - // Replace {summary} with sanitized summary const summary = issue.fields?.summary || ''; const sanitizedSummary = sanitizeFileName(summary); filename = filename.replace(/\{summary\}/g, sanitizedSummary); - - // Replace {key} with issue key const key = issue.key || ''; filename = filename.replace(/\{key\}/g, key); - - // Sanitize the entire filename to remove any prohibited characters filename = sanitizeFileName(filename); - - // Fallback if the result is empty or only whitespace if (!filename || filename.trim() === '') { - // Use key if available, otherwise use a default if (key) { filename = key; } else if (sanitizedSummary) { @@ -33,10 +27,53 @@ function generateFilenameFromTemplate(template: string, issue: JiraIssue): strin filename = 'jira-issue'; } } - return filename.trim(); } +export async function renameExistingIssueFiles(plugin: JiraPlugin): Promise<{ renamed: number; errors: string[] }> { + const cacheMap = plugin.getAllIssueKeysMap(); + const template = plugin.settings.fetchIssue.filenameTemplate || '{summary} ({key})'; + const issuesFolder = plugin.settings.global.issuesFolder; + let renamed = 0; + const errors: string[] = []; + + for (const [issueKey, currentPath] of cacheMap.entries()) { + try { + const file = plugin.app.vault.getFileByPath(currentPath); + if (!file) { + errors.push(`${issueKey}: file not found at ${currentPath}`); + continue; + } + + let summary = ''; + const metadata = plugin.app.metadataCache.getFileCache(file); + const cachedSummary = metadata?.frontmatter?.summary; + if (cachedSummary && typeof cachedSummary === 'string') { + summary = cachedSummary; + } + + const newFilename = generateFilenameFromTemplate(template, { key: issueKey, fields: { summary } }); + const newPath = `${issuesFolder}/${newFilename}.md`; + + if (newPath === currentPath) continue; + + const existingFile = plugin.app.vault.getFileByPath(newPath); + if (existingFile) { + errors.push(`${issueKey}: target path already exists: ${newPath}`); + continue; + } + + await plugin.app.vault.rename(file, newPath); + await plugin.setFilePathForIssueKey(issueKey, newPath); + renamed++; + } catch (error) { + errors.push(`${issueKey}: ${(error as Error).message}`); + } + } + + return { renamed, errors }; +} + export async function createOrUpdateIssueNote(plugin: JiraPlugin, issue: JiraIssue, filePath?: string): Promise { try { await ensureIssuesFolder(plugin); @@ -48,7 +85,6 @@ export async function createOrUpdateIssueNote(plugin: JiraPlugin, issue: JiraIss targetPath = filePath; targetFile = plugin.app.vault.getFileByPath(filePath); } else { - // First, try to find the file using cache const cachedPath = plugin.getFilePathForIssueKey(issue.key); if (cachedPath) { targetFile = plugin.app.vault.getFileByPath(cachedPath); @@ -60,19 +96,16 @@ export async function createOrUpdateIssueNote(plugin: JiraPlugin, issue: JiraIss } } - // If not found in cache or file doesn't exist, fallback to search if (!targetFile) { debugLog(`Issue ${issue.key} not in cache, searching filesystem...`); targetFile = await findFileByIssueKey(plugin, issue.key); if (targetFile) { targetPath = targetFile.path; - // Add to cache for future use - plugin.setFilePathForIssueKey(issue.key, targetPath); + await plugin.setFilePathForIssueKey(issue.key, targetPath); debugLog(`Found issue ${issue.key} via search, added to cache: ${targetPath}`); } } - // If still not found, create new file if (!targetFile) { const template = plugin.settings.fetchIssue.filenameTemplate || '{summary} ({key})'; const filename = generateFilenameFromTemplate(template, issue); @@ -84,12 +117,12 @@ export async function createOrUpdateIssueNote(plugin: JiraPlugin, issue: JiraIss if (targetFile) { await updateJiraToLocal(plugin, targetFile, issue); await plugin.app.workspace.openLinkText(targetFile.path, ''); + await plugin.setFilePathForIssueKey(issue.key, targetFile.path); } else { const newFile = await createNewIssueFile(plugin, targetPath); await updateJiraToLocal(plugin, newFile, issue); await plugin.app.workspace.openLinkText(newFile.path, ''); - // Add new file to cache - plugin.setFilePathForIssueKey(issue.key, newFile.path); + await plugin.setFilePathForIssueKey(issue.key, newFile.path); } new Notice(`Issue ${issue.key} imported successfully`); } catch (error: unknown) { @@ -108,7 +141,6 @@ async function createNewIssueFile(plugin: JiraPlugin, filePath: string): Promise if (templatePath && templatePath.trim() !== '') { const templateFile = plugin.app.vault.getFileByPath(templatePath); if (templateFile) { - // Use the template as initial content initialContent = await plugin.app.vault.read(templateFile); } else { new Notice(`Template file not found: ${templatePath}, using default template`); @@ -116,10 +148,8 @@ async function createNewIssueFile(plugin: JiraPlugin, filePath: string): Promise } if (initialContent === '') initialContent = defaultTemplate; - // Create the file with initial content await plugin.app.vault.create(filePath, initialContent); - // Get file reference and update frontmatter const newFile = plugin.app.vault.getFileByPath(filePath); if (!newFile) { throw new Error('Could not create file'); @@ -129,11 +159,9 @@ async function createNewIssueFile(plugin: JiraPlugin, filePath: string): Promise async function findFileByIssueKey(plugin: JiraPlugin, issueKey: string): Promise { const issuesFolder = plugin.app.vault.getAbstractFileByPath(plugin.settings.global.issuesFolder); - if (!issuesFolder || !(issuesFolder instanceof TFolder)) { return null; } - return await searchFolderForIssueKey(plugin, issuesFolder, issueKey); } diff --git a/src/localization/source/en/modals/jql_search.yaml b/src/localization/source/en/modals/jql_search.yaml index 8e5161b..86bb8b6 100644 --- a/src/localization/source/en/modals/jql_search.yaml +++ b/src/localization/source/en/modals/jql_search.yaml @@ -12,6 +12,16 @@ maxResults: submit: Search Issues +presets: + load: JQL Preset + load_desc: Load a saved JQL query preset + select: Select preset + save: Save as preset + save_desc: Save current JQL query as a named preset + save_hint: Preset name + save_btn: Save + delete: Delete preset + preview: total: Total {total} issues found showing: ' (showing first {limit} issues)' diff --git a/src/localization/source/en/settings/fetch_issue.yaml b/src/localization/source/en/settings/fetch_issue.yaml index 682d5b9..7ad65f4 100644 --- a/src/localization/source/en/settings/fetch_issue.yaml +++ b/src/localization/source/en/settings/fetch_issue.yaml @@ -26,3 +26,10 @@ filenameTemplate: desc: | Template for created issue filenames. Use {summary} for issue summary and {key} for issue key placeholder: '{summary} ({key})' + +rename_files: Rename existing files +no_files_to_rename: No files in cache to rename +rename_confirm: 'This will rename {count} cached file(s). Continue?' +rename_success: Successfully renamed {count} file(s) +rename_errors: 'Failed to rename {count} file(s)' +rename_no_changes: No files needed renaming diff --git a/src/localization/source/ru/modals/jql_search.yaml b/src/localization/source/ru/modals/jql_search.yaml index c13ff3a..886b68b 100644 --- a/src/localization/source/ru/modals/jql_search.yaml +++ b/src/localization/source/ru/modals/jql_search.yaml @@ -12,8 +12,18 @@ maxResults: submit: Найти задачи +presets: + load: JQL пресет + load_desc: Загрузить сохраненный JQL запрос + select: Выберите пресет + save: Сохранить как пресет + save_desc: Сохранить текущий JQL запрос как именованный пресет + save_hint: Имя пресета + save_btn: Сохранить + delete: Удалить пресет + preview: - total: '{total} задач найдено' + total: '{total} задач найдено' showing: ' (показаны первые {limit} задач)' error: '{error}' loading: Загрузка... diff --git a/src/localization/source/ru/settings/fetch_issue.yaml b/src/localization/source/ru/settings/fetch_issue.yaml index e10e6aa..a162865 100644 --- a/src/localization/source/ru/settings/fetch_issue.yaml +++ b/src/localization/source/ru/settings/fetch_issue.yaml @@ -21,3 +21,10 @@ filenameTemplate: desc: | Шаблон для названий файлов созданных задач. Используйте {summary} для названия задачи и {key} для ключа задачи placeholder: '{summary} ({key})' + +rename_files: Переименовать существующие файлы +no_files_to_rename: Нет файлов в кэше для переименования +rename_confirm: 'Будет переименовано {count} файл(ов). Продолжить?' +rename_success: 'Успешно переименовано {count} файл(ов)' +rename_errors: 'Не удалось переименовать {count} файл(ов)' +rename_no_changes: Файлы не нуждаются в переименовании diff --git a/src/main.ts b/src/main.ts index 72681c5..4bd52e9 100644 --- a/src/main.ts +++ b/src/main.ts @@ -11,11 +11,12 @@ import { registerUpdateIssueStatusCommand, registerBatchFetchIssuesCommand, registerAddCommentCommand, + registerRebuildCacheCommand, } from './commands'; import { transform_string_to_functions_mappings } from './tools/convertFunctionString'; import { createJiraSyncExtension } from './postprocessing/livePreview'; import { hideJiraPointersReading } from './postprocessing/reading'; -import { buildCacheFromFilesystem, validateCache } from './tools/cacheUtils'; +import { buildCacheFromFilesystem } from './tools/cacheUtils'; import { checkMigrateSettings } from './tools/migrateSettings'; export default class JiraPlugin extends Plugin { @@ -27,9 +28,8 @@ export default class JiraPlugin extends Plugin { async onload() { await this.loadSettings(); - // validate cache from settings + // initialize cache from settings this.initializeCache(); - await validateCache(this); // Register all commands registerUpdateIssueCommand(this); @@ -42,6 +42,7 @@ export default class JiraPlugin extends Plugin { registerUpdateWorkLogManuallyCommand(this); registerUpdateWorkLogBatchCommand(this); registerAddCommentCommand(this); + registerRebuildCacheCommand(this); // Add settings tab this.addSettingTab(new JiraSettingTab(this.app, this)); @@ -58,20 +59,26 @@ export default class JiraPlugin extends Plugin { async loadSettings() { const old_data = await this.loadData(); + // TODO: Cancel and delete migration check in future (approximately 2026-2027) - const new_data = checkMigrateSettings(old_data, this.saveSettings); + const { result: migratedData, changed: migrationChanged } = checkMigrateSettings(old_data); this.settings = { ...DEFAULT_SETTINGS, - ...new_data, + ...migratedData, fieldMapping: { ...DEFAULT_SETTINGS.fieldMapping, - ...(new_data?.fieldMapping || {}), + ...(migratedData?.fieldMapping || {}), }, }; + this.settings.fieldMapping.fieldMappings = await transform_string_to_functions_mappings( this.settings.fieldMapping.fieldMappingsStrings, ); + + if (migrationChanged) { + await this.saveSettings(); + } } async saveSettings() { @@ -100,36 +107,36 @@ export default class JiraPlugin extends Plugin { return this.settings.connections[this.settings.currentConnectionIndex]; } - setFilePathForIssueKey(issueKey: string, filePath: string) { + async setFilePathForIssueKey(issueKey: string, filePath: string) { this.issueKeyToFilePathCache.set(issueKey, filePath); this.settings.issueKeyToFilePathCache[issueKey] = filePath; - this.saveSettings(); + await this.saveSettings(); } - removeIssueKeyFromCache(issueKey: string) { + async removeIssueKeyFromCache(issueKey: string) { this.issueKeyToFilePathCache.delete(issueKey); delete this.settings.issueKeyToFilePathCache[issueKey]; - this.saveSettings(); + await this.saveSettings(); } - clearCache() { + async clearCache() { this.issueKeyToFilePathCache.clear(); this.settings.issueKeyToFilePathCache = {}; - this.saveSettings(); + await this.saveSettings(); } async rebuildCache() { - this.clearCache(); + await this.clearCache(); await buildCacheFromFilesystem(this); } private registerVaultEventListeners() { // Handle file renames this.registerEvent( - this.app.vault.on('rename', (file, oldPath) => { + this.app.vault.on('rename', async (file, oldPath) => { for (const [issueKey, cachedPath] of this.issueKeyToFilePathCache.entries()) { if (cachedPath === oldPath) { - this.setFilePathForIssueKey(issueKey, file.path); + await this.setFilePathForIssueKey(issueKey, file.path); break; } } @@ -138,10 +145,10 @@ export default class JiraPlugin extends Plugin { // Handle file deletions this.registerEvent( - this.app.vault.on('delete', (file) => { + this.app.vault.on('delete', async (file) => { for (const [issueKey, cachedPath] of this.issueKeyToFilePathCache.entries()) { if (cachedPath === file.path) { - this.removeIssueKeyFromCache(issueKey); + await this.removeIssueKeyFromCache(issueKey); break; } } diff --git a/src/modals/ConfirmModal.ts b/src/modals/ConfirmModal.ts new file mode 100644 index 0000000..ce936ee --- /dev/null +++ b/src/modals/ConfirmModal.ts @@ -0,0 +1,39 @@ +import { App, Modal, Setting } from 'obsidian'; + +export class ConfirmModal extends Modal { + private message: string; + private onConfirm: () => void; + private onCancel: () => void; + + constructor(app: App, message: string, onConfirm: () => void, onCancel: () => void) { + super(app); + this.message = message; + this.onConfirm = onConfirm; + this.onCancel = onCancel; + } + + onOpen() { + this.contentEl.createEl('h2', { text: this.message }); + + new Setting(this.contentEl) + .addButton((btn) => + btn + .setButtonText('Yes') + .setCta() + .onClick(() => { + this.close(); + this.onConfirm(); + }), + ) + .addButton((btn) => + btn.setButtonText('No').onClick(() => { + this.close(); + this.onCancel(); + }), + ); + } + + onClose() { + this.contentEl.empty(); + } +} diff --git a/src/modals/JQLSearchModal.ts b/src/modals/JQLSearchModal.ts index c565e00..4afde8b 100644 --- a/src/modals/JQLSearchModal.ts +++ b/src/modals/JQLSearchModal.ts @@ -1,13 +1,11 @@ -import { App, Modal, Setting } from 'obsidian'; +import { App, Modal, Setting, DropdownComponent } from 'obsidian'; import { useTranslations } from '../localization/translator'; import JiraPlugin from '../main'; import { JQLPreview } from '../settings/components/JQLPreview'; +import { JQLPreset } from '../settings/default'; const t = useTranslations('modals.jql_search').t; -/** - * Modal for searching issues by JQL query - */ export class JQLSearchModal extends Modal { private onSubmit: (jql: string) => void; private plugin: JiraPlugin; @@ -15,6 +13,8 @@ export class JQLSearchModal extends Modal { private previewEl?: HTMLElement; private preview?: JQLPreview; private debounceTimer?: number; + private presetName: string = ''; + private presetDropdown?: DropdownComponent; constructor(app: App, plugin: JiraPlugin, onSubmit: (jql: string) => void) { super(app); @@ -23,8 +23,68 @@ export class JQLSearchModal extends Modal { } onOpen() { + const conn = this.plugin.getCurrentConnection(); + if (conn?.lastJqlQuery) { + this.jql = conn.lastJqlQuery; + this.schedulePreview(); + } + this.contentEl.createEl('h2', { text: t('desc') }); + const presetSetting = new Setting(this.contentEl).setName(t('presets.load')).setDesc(t('presets.load_desc')); + + presetSetting.addDropdown((dropdown) => { + this.presetDropdown = dropdown; + this.populateDropdown(dropdown); + dropdown.onChange((value) => { + if (value) { + const preset = this.getPresets().find((p) => p.name === value); + if (preset) { + this.jql = preset.query; + const textarea = this.contentEl.querySelector('textarea'); + if (textarea) { + textarea.value = preset.query; + } + this.schedulePreview(); + } + } + }); + }); + + presetSetting.addButton((btn) => + btn.setButtonText(t('presets.delete')).onClick(() => { + const selectedName = this.presetDropdown?.getValue(); + if (selectedName) { + this.deletePreset(selectedName); + if (this.presetDropdown) { + this.populateDropdown(this.presetDropdown); + } + } + }), + ); + + new Setting(this.contentEl) + .setName(t('presets.save')) + .setDesc(t('presets.save_desc')) + .addText((text) => + text.setPlaceholder(t('presets.save_hint')).onChange((value) => { + this.presetName = value; + }), + ) + .addButton((btn) => + btn.setButtonText(t('presets.save_btn')).onClick(() => { + if (this.jql.trim() && this.presetName.trim()) { + this.savePreset(this.presetName.trim(), this.jql.trim()); + this.presetName = ''; + const nameInput = this.contentEl.querySelectorAll('input')[1]; + if (nameInput) nameInput.value = ''; + if (this.presetDropdown) { + this.populateDropdown(this.presetDropdown); + } + } + }), + ); + new Setting(this.contentEl) .setName(t('jql.name')) .setDesc(t('jql.desc')) @@ -38,7 +98,6 @@ export class JQLSearchModal extends Modal { text.inputEl.style.height = '100px'; }); - // Create preview container and initialize preview component this.previewEl = this.contentEl.createDiv(); this.preview = new JQLPreview(this.plugin, this.previewEl, 5); this.preview.loadPreview(''); @@ -49,17 +108,18 @@ export class JQLSearchModal extends Modal { .setCta() .onClick(() => { if (this.jql.trim()) { + this.saveLastQuery(this.jql.trim()); this.close(); this.onSubmit(this.jql.trim()); } }), ); - // Handle Enter key (Ctrl+Enter for textarea) this.contentEl.addEventListener('keydown', (event) => { if (event.key === 'Enter' && (event.ctrlKey || event.metaKey)) { event.preventDefault(); if (this.jql.trim()) { + this.saveLastQuery(this.jql.trim()); this.close(); this.onSubmit(this.jql.trim()); } @@ -67,21 +127,56 @@ export class JQLSearchModal extends Modal { }); } + private populateDropdown(dropdown: DropdownComponent) { + dropdown.selectEl.innerHTML = ''; + dropdown.addOption('', '— ' + t('presets.select') + ' —'); + for (const preset of this.getPresets()) { + dropdown.addOption(preset.name, preset.name); + } + } + + private getPresets(): JQLPreset[] { + const conn = this.plugin.getCurrentConnection(); + return conn?.jqlPresets || []; + } + + private savePreset(name: string, query: string) { + const conn = this.plugin.getCurrentConnection(); + if (!conn) return; + + const existingIndex = conn.jqlPresets.findIndex((p) => p.name === name); + if (existingIndex >= 0) { + conn.jqlPresets[existingIndex].query = query; + } else { + conn.jqlPresets.push({ name, query }); + } + this.plugin.saveSettings(); + } + + private deletePreset(name: string) { + const conn = this.plugin.getCurrentConnection(); + if (!conn) return; + conn.jqlPresets = conn.jqlPresets.filter((p) => p.name !== name); + this.plugin.saveSettings(); + } + + private saveLastQuery(jql: string) { + const conn = this.plugin.getCurrentConnection(); + if (!conn) return; + conn.lastJqlQuery = jql; + this.plugin.saveSettings(); + } + private schedulePreview() { if (this.debounceTimer) { window.clearTimeout(this.debounceTimer); } - - // Show loading immediately if there's text if (this.jql.trim() && this.preview) { this.preview.showLoading(); } - this.debounceTimer = window.setTimeout(() => { if (this.preview) { - this.preview.loadPreview(this.jql).catch(() => { - // Error handling is done within the preview component - }); + this.preview.loadPreview(this.jql).catch(() => {}); } }, 600); } diff --git a/src/modals/index.ts b/src/modals/index.ts index 081eedf..b5ad212 100644 --- a/src/modals/index.ts +++ b/src/modals/index.ts @@ -5,3 +5,4 @@ export * from './IssueTypeModal'; export * from './IssueWorkLogModal'; export * from './ProjectModal'; export * from './JQLSearchModal'; +export * from './ConfirmModal'; diff --git a/src/settings/components/ConnectionSettingsComponent.ts b/src/settings/components/ConnectionSettingsComponent.ts index 4b3e390..3aeb215 100644 --- a/src/settings/components/ConnectionSettingsComponent.ts +++ b/src/settings/components/ConnectionSettingsComponent.ts @@ -66,6 +66,8 @@ export class ConnectionSettingsComponent implements SettingsComponent { password: '', jiraUrl: '', apiVersion: '2' as const, + jqlPresets: [], + lastJqlQuery: '', }; plugin.settings.connections.push(newConnection); plugin.settings.currentConnectionIndex = plugin.settings.connections.length - 1; diff --git a/src/settings/components/FetchIssueComponent.ts b/src/settings/components/FetchIssueComponent.ts index f850b1d..eb69501 100644 --- a/src/settings/components/FetchIssueComponent.ts +++ b/src/settings/components/FetchIssueComponent.ts @@ -1,6 +1,8 @@ import { Notice, Setting } from 'obsidian'; import { SettingsComponent, SettingsComponentProps } from '../../interfaces/settingsTypes'; -import { fetchIssue } from '../../api'; +import { fetchIssue, validateSettings } from '../../api'; +import { renameExistingIssueFiles } from '../../file_operations/getIssue'; +import { ConfirmModal } from '../../modals/ConfirmModal'; import debounce from 'lodash/debounce'; import hljs from 'highlight.js'; import { useTranslations } from '../../localization/translator'; @@ -23,7 +25,6 @@ export class FetchIssueComponent implements SettingsComponent { render(containerEl: HTMLElement): void { const { plugin } = this.props; - // Filename template setting const filenameTemplateSetting = new Setting(containerEl) .setName(t('filenameTemplate.name')) .setDesc(t('filenameTemplate.desc')); @@ -37,6 +38,40 @@ export class FetchIssueComponent implements SettingsComponent { }); }); + filenameTemplateSetting.addButton((btn) => + btn.setButtonText(t('rename_files')).onClick(async () => { + if (!validateSettings(plugin)) return; + + const cacheSize = plugin.getAllIssueKeysMap().size; + if (cacheSize === 0) { + new Notice(t('no_files_to_rename')); + return; + } + + new ConfirmModal( + plugin.app, + t('rename_confirm', { count: cacheSize.toString() }), + async () => { + const result = await renameExistingIssueFiles(plugin); + if (result.renamed > 0) { + new Notice(t('rename_success', { count: result.renamed.toString() })); + } + if (result.errors.length > 0) { + new Notice( + t('rename_errors', { count: result.errors.length.toString() }) + + ': ' + + result.errors.slice(0, 3).join(', '), + ); + } + if (result.renamed === 0 && result.errors.length === 0) { + new Notice(t('rename_no_changes')); + } + }, + () => {}, + ).open(); + }), + ); + const link = plugin.getCurrentConnection()?.apiVersion === '3' ? 'https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-issue-search/#api-rest-api-3-search-jql-post' @@ -87,7 +122,6 @@ export class FetchIssueComponent implements SettingsComponent { }); }); - // Add input field for issue key new Setting(containerEl) .setName(t('key.name')) .setDesc(t('key.desc')) @@ -98,7 +132,6 @@ export class FetchIssueComponent implements SettingsComponent { }), ); - // Create container for issue data this.issueDataContainer = containerEl.createDiv({ cls: 'jira-raw-issue-container', }); diff --git a/src/settings/default.ts b/src/settings/default.ts index 8e07765..e888fa5 100644 --- a/src/settings/default.ts +++ b/src/settings/default.ts @@ -1,5 +1,10 @@ import { FieldMapping } from '../default/obsidianJiraFieldsMapping'; +export interface JQLPreset { + name: string; + query: string; +} + export interface ConnectionSettingsInterface { name: string; jiraUrl: string; @@ -9,6 +14,8 @@ export interface ConnectionSettingsInterface { username: string; email: string; password: string; + jqlPresets: JQLPreset[]; + lastJqlQuery: string; } export interface GlobalSettingsInterface { @@ -78,6 +85,8 @@ export const DEFAULT_SETTINGS: JiraSettingsInterface = { password: '', jiraUrl: '', apiVersion: '2', + jqlPresets: [], + lastJqlQuery: '', }, ], currentConnectionIndex: 0, diff --git a/src/tools/cacheUtils.ts b/src/tools/cacheUtils.ts index ae650c7..1eca776 100644 --- a/src/tools/cacheUtils.ts +++ b/src/tools/cacheUtils.ts @@ -28,7 +28,7 @@ async function scanFolderForIssueKeys(plugin: JiraPlugin, folder: TFolder): Prom const metadata = plugin.app.metadataCache.getFileCache(child); const issueKey = metadata?.frontmatter?.key; if (issueKey && typeof issueKey === 'string') { - plugin.setFilePathForIssueKey(issueKey, child.path); + await plugin.setFilePathForIssueKey(issueKey, child.path); } } else if (child instanceof TFolder) { await scanFolderForIssueKeys(plugin, child); @@ -51,7 +51,7 @@ export async function validateCache(plugin: JiraPlugin): Promise { } // Remove invalid entries - entriesToRemove.forEach((issueKey) => { - plugin.removeIssueKeyFromCache(issueKey); - }); + for (const issueKey of entriesToRemove) { + await plugin.removeIssueKeyFromCache(issueKey); + } } diff --git a/src/tools/migrateSettings.ts b/src/tools/migrateSettings.ts index 0072779..ba84a49 100644 --- a/src/tools/migrateSettings.ts +++ b/src/tools/migrateSettings.ts @@ -5,8 +5,8 @@ import { TimekeepSettingsInterface, } from '../settings/default'; -export function checkMigrateSettings(data: any, saveSettings: () => void): any { - if (!data || typeof data !== 'object') return data; +export function checkMigrateSettings(data: any): { result: any; changed: boolean } { + if (!data || typeof data !== 'object') return { result: data, changed: false }; const result = { ...data }; let data_changed = false; @@ -84,6 +84,8 @@ export function checkMigrateSettings(data: any, saveSettings: () => void): any { username: result.connection.username || '', email: result.connection.email || '', password: result.connection.password || '', + jqlPresets: [], + lastJqlQuery: '', }, ]; result.currentConnectionIndex = 0; @@ -91,9 +93,23 @@ export function checkMigrateSettings(data: any, saveSettings: () => void): any { data_changed = true; } - if (data_changed) { - saveSettings(); + // Ensure all connections have jqlPresets and lastJqlQuery + if (result.connections && Array.isArray(result.connections)) { + for (const conn of result.connections) { + if (!('jqlPresets' in conn)) { + conn.jqlPresets = []; + data_changed = true; + } + if (!('lastJqlQuery' in conn)) { + conn.lastJqlQuery = ''; + data_changed = true; + } + } } - return result; + if (data_changed) { + return { result, changed: true }; + } + + return { result, changed: false }; } diff --git a/tests/__mocks__/obsidian.ts b/tests/__mocks__/obsidian.ts new file mode 100644 index 0000000..47b07ba --- /dev/null +++ b/tests/__mocks__/obsidian.ts @@ -0,0 +1,183 @@ +export class Plugin { + app: App; + settings: Record = {}; + + constructor() { + this.app = new App(); + } + + async loadData(): Promise { + return {}; + } + + async saveData(data: any): Promise {} + + addCommand(_cmd: any): void {} + + addSettingTab(_tab: any): void {} + + registerEvent(_event: any): void {} + + registerEditorExtension(_ext: any): void {} + + registerMarkdownPostProcessor(_proc: any): void {} + + registerInterval(_id: number): number { + return 0; + } +} + +export class App { + vault: Vault; + workspace: Workspace; + metadataCache: MetadataCache; + fileManager: FileManager; +} + +export class Vault { + async read(_file: TFile): Promise { + return ''; + } + async process(_file: TFile, _fn: (data: string) => string): Promise { + return ''; + } + async create(_path: string, _content: string): Promise { + return new TFile(); + } + async rename(_file: TFile, _newPath: string): Promise {} + getFileByPath(_path: string): TFile | null { + return null; + } + getFolderByPath(_path: string): TFolder | null { + return null; + } + async createFolder(_path: string): Promise {} +} + +export class TFile { + path: string = ''; + extension: string = 'md'; + name: string = ''; + parent: TFolder | null = null; + vault: Vault = new Vault(); +} + +export class TFolder { + path: string = ''; + name: string = ''; + children: (TFile | TFolder)[] = []; + isRoot(): boolean { + return false; + } +} + +export class Workspace { + on(_name: string, _cb: (...args: any[]) => any): void {} + openLinkText(_path: string, _sourcePath?: string): Promise { + return Promise.resolve(); + } +} + +export class MetadataCache { + getFileCache(_file: TFile): any { + return null; + } +} + +export class FileManager { + async processFrontMatter(_file: TFile, _fn: (frontmatter: any) => void): Promise {} +} + +export class Modal { + app: App; + contentEl: HTMLElement; + + constructor(app: App) { + this.app = app; + this.contentEl = document.createElement('div'); + } + + open(): void {} + close(): void {} +} + +export class Setting { + constructor(_containerEl: HTMLElement) { + this.setName(''); + this.setDesc(''); + } + setName(_name: string): this { + return this; + } + setDesc(_desc: string): this { + return this; + } + addText(_cb: (text: TextComponent) => any): this { + return this; + } + addTextArea(_cb: (text: TextComponent) => any): this { + return this; + } + addButton(_cb: (btn: ButtonComponent) => any): this { + return this; + } + addDropdown(_cb: (dropdown: DropdownComponent) => any): this { + return this; + } +} + +export class TextComponent { + inputEl: HTMLInputElement; + constructor() { + this.inputEl = document.createElement('input'); + } + setPlaceholder(_placeholder: string): this { + return this; + } + setValue(_value: string): this { + return this; + } + onChange(_cb: (value: string) => void): this { + return this; + } +} + +export class ButtonComponent { + buttonEl: HTMLButtonElement; + constructor() { + this.buttonEl = document.createElement('button'); + } + setButtonText(_text: string): this { + return this; + } + setCta(): this { + return this; + } + onClick(_cb: () => void): this { + return this; + } +} + +export class DropdownComponent { + selectEl: HTMLSelectElement; + constructor() { + this.selectEl = document.createElement('select'); + } + addOption(_value: string, _display: string): this { + return this; + } + setValue(_value: string): this { + return this; + } + onChange(_cb: (value: string) => void): this { + return this; + } +} + +export class Notice { + constructor(_message: string) {} +} + +export function requestUrl(params: any): Promise { + return Promise.resolve({ status: 200, json: {}, text: '' }); +} diff --git a/tests/__tests__/api/issues.test.ts b/tests/__tests__/api/issues.test.ts new file mode 100644 index 0000000..3d3d7cf --- /dev/null +++ b/tests/__tests__/api/issues.test.ts @@ -0,0 +1,175 @@ +import { describe, it, expect, beforeAll } from 'vitest'; +import * as fs from 'fs'; +import * as path from 'path'; + +interface TestConnection { + name: string; + jiraUrl: string; + apiVersion: '2' | '3'; + authMethod: 'bearer' | 'basic'; + apiToken: string; + email?: string; +} + +interface TestConfig { + connections: TestConnection[]; +} + +function loadConfig(): TestConfig | null { + try { + const configPath = path.resolve(__dirname, '../../test-config.local.json'); + if (!fs.existsSync(configPath)) { + return null; + } + const raw = fs.readFileSync(configPath, 'utf-8'); + return JSON.parse(raw); + } catch { + return null; + } +} + +const config = loadConfig(); +const runIntegration = !!config && process.env.RUN_INTEGRATION_TESTS === 'true'; + +async function mockBaseRequest(conn: TestConnection, method: string, urlPath: string, body?: string): Promise { + const url = `${conn.jiraUrl}/rest/api/${conn.apiVersion}${urlPath}`; + const headers: Record = { + 'Content-Type': 'application/json', + }; + + if (conn.authMethod === 'bearer') { + headers['Authorization'] = `Bearer ${conn.apiToken}`; + } else if (conn.authMethod === 'basic' && conn.email) { + const encoded = Buffer.from(`${conn.email}:${conn.apiToken}`).toString('base64'); + headers['Authorization'] = `Basic ${encoded}`; + } + + const fetchArgs: RequestInit = { + method, + headers, + body: body || undefined, + }; + + const response = await fetch(url, fetchArgs); + const json = await response.json(); + + if (!response.ok) { + throw new Error(`HTTP ${response.status}: ${JSON.stringify(json)}`); + } + + return json; +} + +async function findExistingIssue(conn: TestConnection): Promise { + try { + const result = await mockBaseRequest( + conn, + 'POST', + '/search', + JSON.stringify({ jql: '', maxResults: 1, fields: ['key'] }), + ); + if (result.issues && result.issues.length > 0) { + return result.issues[0].key; + } + return null; + } catch { + return null; + } +} + +if (runIntegration) { + describe.each(config!.connections)('Integration: $name ($apiVersion)', (conn) => { + let existingIssueKey: string | null; + + beforeAll(async () => { + existingIssueKey = await findExistingIssue(conn); + }); + + it('fetches server info (self)', async () => { + const result = await mockBaseRequest(conn, 'GET', '/serverInfo'); + expect(result).toHaveProperty('baseUrl'); + expect(result).toHaveProperty('version'); + }); + + it('searches issues with empty JQL', async () => { + const result = await mockBaseRequest( + conn, + 'POST', + '/search', + JSON.stringify({ jql: '', maxResults: 1, fields: ['key', 'summary'] }), + ); + expect(result).toHaveProperty('issues'); + expect(Array.isArray(result.issues)).toBe(true); + if (result.issues.length > 0) { + expect(result.issues[0]).toHaveProperty('key'); + expect(result.issues[0]).toHaveProperty('fields'); + } + }); + + it('fetches a specific issue by key', async () => { + if (!existingIssueKey) { + console.warn('No existing issues found, skipping test'); + return; + } + const result = await mockBaseRequest(conn, 'GET', `/issue/${existingIssueKey}`); + expect(result).toHaveProperty('key', existingIssueKey); + expect(result).toHaveProperty('fields'); + expect(result.fields).toHaveProperty('summary'); + }); + + it('searches by project JQL', async () => { + if (!existingIssueKey) { + console.warn('No existing issues found, skipping test'); + return; + } + const projectKey = existingIssueKey.split('-')[0]; + const result = await mockBaseRequest( + conn, + 'POST', + '/search', + JSON.stringify({ jql: `project = ${projectKey}`, maxResults: 3, fields: ['key', 'summary'] }), + ); + expect(result).toHaveProperty('issues'); + expect(result.issues.length).toBeGreaterThan(0); + for (const issue of result.issues) { + expect(issue.key).toMatch(new RegExp(`^${projectKey}-`)); + } + }); + + it('fetches issue transitions', async () => { + if (!existingIssueKey) { + console.warn('No existing issues found, skipping test'); + return; + } + const result = await mockBaseRequest(conn, 'GET', `/issue/${existingIssueKey}/transitions`); + expect(result).toHaveProperty('transitions'); + expect(Array.isArray(result.transitions)).toBe(true); + }); + + if (conn.apiVersion === '3') { + it('uses approximate count endpoint (v3 specific)', async () => { + const result = await mockBaseRequest( + conn, + 'POST', + '/search/approximate-count', + JSON.stringify({ jql: '' }), + ); + expect(result).toHaveProperty('count'); + expect(typeof result.count).toBe('number'); + }); + } + }); +} else { + describe('Integration tests', () => { + it('are skipped unless RUN_INTEGRATION_TESTS=true and tests/test-config.local.json exists', () => { + if (config) { + console.info('Test config found. Set RUN_INTEGRATION_TESTS=true to run integration tests.'); + } else { + console.info( + 'No test config found. Create tests/test-config.local.json (see test-config.example.json).', + ); + } + expect(true).toBe(true); + }); + }); +} diff --git a/tests/__tests__/file_operations/getIssue.test.ts b/tests/__tests__/file_operations/getIssue.test.ts new file mode 100644 index 0000000..e0e741a --- /dev/null +++ b/tests/__tests__/file_operations/getIssue.test.ts @@ -0,0 +1,75 @@ +import { describe, it, expect } from 'vitest'; +import { sanitizeFileName } from '../../../src/tools/sanitizers'; + +function generateFilenameFromTemplate(template: string, issue: { key: string; fields: { summary?: string } }): string { + let filename = template; + const summary = issue.fields?.summary || ''; + const sanitizedSummary = sanitizeFileName(summary); + filename = filename.replace(/\{summary\}/g, sanitizedSummary); + const key = issue.key || ''; + filename = filename.replace(/\{key\}/g, key); + filename = sanitizeFileName(filename); + if (!filename || filename.trim() === '') { + if (key) { + filename = key; + } else if (sanitizedSummary) { + filename = sanitizedSummary; + } else { + filename = 'jira-issue'; + } + } + return filename.trim(); +} + +describe('generateFilenameFromTemplate', () => { + it('generates filename with summary and key default template', () => { + const issue = { key: 'PROJ-123', fields: { summary: 'Fix login bug' } }; + const result = generateFilenameFromTemplate('{summary} ({key})', issue); + expect(result).toBe('Fix login bug (PROJ-123)'); + }); + + it('handles template with only key', () => { + const issue = { key: 'PROJ-123', fields: { summary: 'Fix login bug' } }; + const result = generateFilenameFromTemplate('{key}', issue); + expect(result).toBe('PROJ-123'); + }); + + it('handles template with only summary', () => { + const issue = { key: 'PROJ-123', fields: { summary: 'Fix login bug' } }; + const result = generateFilenameFromTemplate('{summary}', issue); + expect(result).toBe('Fix login bug'); + }); + + it('sanitizes summary in filename', () => { + const issue = { key: 'PROJ-1', fields: { summary: 'bug: fix "critical" issue?' } }; + const result = generateFilenameFromTemplate('{summary}', issue); + expect(result).not.toContain(':'); + expect(result).not.toContain('"'); + expect(result).not.toContain('?'); + expect(result).toContain('-'); + }); + + it('falls back to key when summary is empty but template has non-template text', () => { + const issue = { key: 'PROJ-1', fields: { summary: '' } }; + const result = generateFilenameFromTemplate('{summary}', issue); + expect(result).toBe('PROJ-1'); + }); + + it('falls back to jira-issue when both key and summary are empty', () => { + const issue = { key: '', fields: { summary: '' } }; + const result = generateFilenameFromTemplate('{summary}', issue); + expect(result).toBe('jira-issue'); + }); + + it('preserves custom template structure', () => { + const issue = { key: 'PROJ-42', fields: { summary: 'My Task' } }; + const result = generateFilenameFromTemplate('{key}-{summary}', issue); + expect(result).toBe('PROJ-42-My Task'); + }); + + it('handles missing fields gracefully by showing non-template parts', () => { + const issue = { key: 'PROJ-1', fields: {} }; + const result = generateFilenameFromTemplate('{summary} ({key})', issue); + expect(result).toBe('(PROJ-1)'); + }); +}); diff --git a/tests/__tests__/localization/translator.test.ts b/tests/__tests__/localization/translator.test.ts new file mode 100644 index 0000000..e72f5f3 --- /dev/null +++ b/tests/__tests__/localization/translator.test.ts @@ -0,0 +1,55 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { useTranslations, t } from '../../../src/localization/translator'; + +describe('translator', () => { + const originalLanguage = window.localStorage.getItem('language'); + + afterEach(() => { + if (originalLanguage) { + window.localStorage.setItem('language', originalLanguage); + } else { + window.localStorage.removeItem('language'); + } + }); + + describe('useTranslations', () => { + it('returns translate function with correct prefix', () => { + const { t: translate } = useTranslations('modals.jql_search'); + const result = translate('desc'); + expect(result).toBe('Search issues by JQL query'); + }); + + it('returns locale string', () => { + const { locale } = useTranslations(); + expect(locale).toBe('en'); + }); + + it('returns correct locale when language is set', () => { + // ru locale may not be compiled; test with en + const { locale } = useTranslations(); + expect(locale).toBe('en'); + }); + }); + + describe('t', () => { + it('returns translation for valid key with prefix', () => { + const result = t('jql.name', 'modals.jql_search'); + expect(result).toBe('JQL Query'); + }); + + it('returns translation key when not found', () => { + const result = t('nonexistent.key', 'some_prefix'); + expect(result).toBe('some_prefix.nonexistent.key'); + }); + + it('replaces placeholders in translation', () => { + const result = t('total', 'modals.jql_search.preview', { total: '42' }); + expect(result).toBe('Total 42 issues found'); + }); + + it('returns command translation', () => { + const result = t('name', 'commands.batch_fetch_issues'); + expect(result).toBe('Batch Fetch Issues by JQL'); + }); + }); +}); diff --git a/tests/__tests__/setup.ts b/tests/__tests__/setup.ts new file mode 100644 index 0000000..1415348 --- /dev/null +++ b/tests/__tests__/setup.ts @@ -0,0 +1,4 @@ +import { vi } from 'vitest'; + +vi.stubGlobal('requestAnimationFrame', (cb: FrameRequestCallback) => setTimeout(cb, 0)); +vi.stubGlobal('cancelAnimationFrame', (id: number) => clearTimeout(id)); diff --git a/tests/__tests__/tools/convertFunctionString.test.ts b/tests/__tests__/tools/convertFunctionString.test.ts new file mode 100644 index 0000000..5390d2d --- /dev/null +++ b/tests/__tests__/tools/convertFunctionString.test.ts @@ -0,0 +1,62 @@ +import { describe, it, expect } from 'vitest'; +import { validateFunctionString } from '../../../src/tools/convertFunctionString'; + +describe('validateFunctionString', () => { + it('accepts empty string', async () => { + const result = await validateFunctionString(''); + expect(result.isValid).toBe(true); + }); + + it('accepts whitespace-only string', async () => { + const result = await validateFunctionString(' '); + expect(result.isValid).toBe(true); + }); + + it('rejects document reference', async () => { + const result = await validateFunctionString('document.title'); + expect(result.isValid).toBe(false); + expect(result.errorMessage).toContain('document'); + }); + + it('rejects window reference', async () => { + const result = await validateFunctionString('window.location'); + expect(result.isValid).toBe(false); + expect(result.errorMessage).toContain('window'); + }); + + it('rejects eval reference', async () => { + const result = await validateFunctionString('eval("1+1")'); + expect(result.isValid).toBe(false); + expect(result.errorMessage).toContain('eval'); + }); + + it('rejects Function reference', async () => { + const result = await validateFunctionString('Function("return 1")'); + expect(result.isValid).toBe(false); + expect(result.errorMessage).toContain('Function'); + }); + + it('rejects fetch reference', async () => { + const result = await validateFunctionString('fetch("/api")'); + expect(result.isValid).toBe(false); + expect(result.errorMessage).toContain('fetch'); + }); + + it('accepts simple property access', async () => { + const result = await validateFunctionString('issue.fields.summary', ['issue']); + expect(result.isValid).toBe(true); + }); + + it('accepts simple expressions', async () => { + const result = await validateFunctionString('value + "!"', ['value']); + expect(result.isValid).toBe(true); + }); + + it('accepts arrow function syntax', async () => { + const result = await validateFunctionString('(issue, api_version) => issue.fields.summary', [ + 'issue', + 'api_version', + ]); + expect(result.isValid).toBe(true); + }); +}); diff --git a/tests/__tests__/tools/sanitizers.test.ts b/tests/__tests__/tools/sanitizers.test.ts new file mode 100644 index 0000000..ed76e20 --- /dev/null +++ b/tests/__tests__/tools/sanitizers.test.ts @@ -0,0 +1,56 @@ +import { describe, it, expect } from 'vitest'; +import { sanitizeFileName } from '../../../src/tools/sanitizers'; + +describe('sanitizeFileName', () => { + it('replaces backslash with dash', () => { + expect(sanitizeFileName('foo\\bar')).toBe('foo-bar'); + }); + + it('replaces forward slash with dash', () => { + expect(sanitizeFileName('foo/bar')).toBe('foo-bar'); + }); + + it('replaces colon with dash', () => { + expect(sanitizeFileName('foo: bar')).toBe('foo- bar'); + }); + + it('replaces asterisk with dash', () => { + expect(sanitizeFileName('foo*bar')).toBe('foo-bar'); + }); + + it('replaces question mark with dash', () => { + expect(sanitizeFileName('foo?bar')).toBe('foo-bar'); + }); + + it('replaces double quote with dash', () => { + expect(sanitizeFileName('foo"bar')).toBe('foo-bar'); + }); + + it('replaces left angle bracket with dash', () => { + expect(sanitizeFileName('foo { + expect(sanitizeFileName('foo>bar')).toBe('foo-bar'); + }); + + it('replaces pipe with dash', () => { + expect(sanitizeFileName('foo|bar')).toBe('foo-bar'); + }); + + it('replaces all invalid characters', () => { + expect(sanitizeFileName('ac"d:e/f\\g*h?i|j')).toBe('a-b-c-d-e-f-g-h-i-j'); + }); + + it('returns empty string for empty input', () => { + expect(sanitizeFileName('')).toBe(''); + }); + + it('preserves valid characters', () => { + expect(sanitizeFileName('hello-world 123')).toBe('hello-world 123'); + }); + + it('handles unicode characters', () => { + expect(sanitizeFileName('привет мир')).toBe('привет мир'); + }); +}); diff --git a/tests/__tests__/tools/sectionTools.test.ts b/tests/__tests__/tools/sectionTools.test.ts new file mode 100644 index 0000000..f291e46 --- /dev/null +++ b/tests/__tests__/tools/sectionTools.test.ts @@ -0,0 +1,112 @@ +import { describe, it, expect } from 'vitest'; +import { + parseFileContent, + extractAllJiraSyncValuesFromContent, + updateJiraSyncContent, +} from '../../../src/tools/sectionTools'; + +describe('parseFileContent', () => { + it('parses a sync section (stops at next heading)', () => { + const content = '`jira-sync-section-status`\nIn Progress\n\n## Next Section\nmore text'; + const blocks = parseFileContent(content); + expect(blocks).toHaveLength(1); + expect(blocks[0].type).toBe('section'); + expect(blocks[0].name).toBe('status'); + expect(blocks[0].content).toBe('In Progress'); + }); + + it('parses a sync section without heading captures everything', () => { + const content = '`jira-sync-section-status`\nIn Progress\n\nmore text'; + const blocks = parseFileContent(content); + expect(blocks[0].type).toBe('section'); + expect(blocks[0].content).toContain('In Progress'); + expect(blocks[0].content).toContain('more text'); + }); + + it('parses a sync line (captures rest of line)', () => { + const content = 'text `jira-sync-line-assignee`johndoe more text'; + const blocks = parseFileContent(content); + expect(blocks[0].type).toBe('line'); + expect(blocks[0].name).toBe('assignee'); + expect(blocks[0].content).toBe('johndoe more text'); + }); + + it('parses an inline block', () => { + const content = 'hello `jira-sync-inline-start-summary`my issue`jira-sync-end` world'; + const blocks = parseFileContent(content); + expect(blocks).toHaveLength(1); + expect(blocks[0].type).toBe('inline'); + expect(blocks[0].name).toBe('summary'); + expect(blocks[0].content).toBe('my issue'); + }); + + it('parses a block', () => { + const content = 'before\n`jira-sync-block-start-description`\nline1\nline2\n`jira-sync-end`\nafter'; + const blocks = parseFileContent(content); + expect(blocks).toHaveLength(1); + expect(blocks[0].type).toBe('block'); + expect(blocks[0].name).toBe('description'); + expect(blocks[0].content).toBe('line1\nline2'); + }); + + it('returns empty array for content without sync markers', () => { + const content = 'just plain text\nno markers here'; + const blocks = parseFileContent(content); + expect(blocks).toHaveLength(0); + }); + + it('parses multiple blocks of different types', () => { + const content = [ + '`jira-sync-section-status`', + 'Done', + '', + '`jira-sync-line-assignee`john', + '`jira-sync-inline-start-priority`High`jira-sync-end`', + ].join('\n'); + const blocks = parseFileContent(content); + expect(blocks).toHaveLength(3); + }); +}); + +describe('extractAllJiraSyncValuesFromContent', () => { + it('extracts values into a dictionary', () => { + const content = '`jira-sync-section-status`\nDone\n`jira-sync-line-assignee`john'; + const result = extractAllJiraSyncValuesFromContent(content); + expect(result).toEqual({ status: 'Done', assignee: 'john' }); + }); + + it('returns empty object for content without sync markers', () => { + const result = extractAllJiraSyncValuesFromContent('plain text'); + expect(result).toEqual({}); + }); +}); + +describe('updateJiraSyncContent', () => { + it('updates a section with new content', () => { + const content = '`jira-sync-section-status`\nIn Progress'; + const result = updateJiraSyncContent(content, { status: 'Done' }); + expect(result).toContain('Done'); + }); + + it('does not modify content for unknown field names', () => { + const content = '`jira-sync-section-status`\nIn Progress'; + const result = updateJiraSyncContent(content, { assignee: 'john' }); + expect(result).toBe(content); + }); + + it('updates only the specified fields', () => { + const content = ['`jira-sync-section-status`', 'In Progress', '`jira-sync-line-assignee`jane'].join('\n'); + const result = updateJiraSyncContent(content, { status: 'Done' }); + expect(result).toContain('Done'); + expect(result).toContain('jane'); + }); + + it('preserves content after section when a heading boundary is present', () => { + const content = 'some header\n\n`jira-sync-section-status`\nold value\n\n## Next\nfooter'; + const result = updateJiraSyncContent(content, { status: 'new value' }); + expect(result).toContain('`jira-sync-section-status`'); + expect(result).toContain('new value'); + expect(result).toContain('some header'); + expect(result).toContain('footer'); + }); +}); diff --git a/tests/test-config.example.json b/tests/test-config.example.json new file mode 100644 index 0000000..5782f8f --- /dev/null +++ b/tests/test-config.example.json @@ -0,0 +1,19 @@ +{ + "connections": [ + { + "name": "Test Jira v2", + "jiraUrl": "http://jira-test.local:8080", + "apiVersion": "2", + "authMethod": "bearer", + "apiToken": "" + }, + { + "name": "Test Jira v3", + "jiraUrl": "https://your-domain.atlassian.net", + "apiVersion": "3", + "authMethod": "basic", + "apiToken": "", + "email": "" + } + ] +} diff --git a/tests/vitest.config.ts b/tests/vitest.config.ts new file mode 100644 index 0000000..a94ad1b --- /dev/null +++ b/tests/vitest.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'vitest/config'; +import path from 'path'; + +export default defineConfig({ + test: { + include: ['tests/__tests__/**/*.test.ts'], + environment: 'jsdom', + setupFiles: ['tests/__tests__/setup.ts'], + }, + resolve: { + alias: { + obsidian: path.resolve(__dirname, '__mocks__/obsidian.ts'), + }, + }, +}); diff --git a/tsconfig.json b/tsconfig.json index 8795bbf..d908c2e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -14,5 +14,6 @@ "lib": ["DOM", "ES2017", "ES2018", "ES2019", "ES2020"], "types": ["node"] }, - "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"] + "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"], + "exclude": ["tests/", "node_modules/"] } diff --git a/yarn.lock b/yarn.lock index 176c38e..4e828c7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,45 @@ # yarn lockfile v1 +"@asamuzakjp/css-color@^5.1.11": + version "5.1.11" + resolved "https://registry.npmmirror.com/@asamuzakjp/css-color/-/css-color-5.1.11.tgz#28a0aac8220a4cc19045ac3bd9a813d4060bd375" + integrity sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg== + dependencies: + "@asamuzakjp/generational-cache" "^1.0.1" + "@csstools/css-calc" "^3.2.0" + "@csstools/css-color-parser" "^4.1.0" + "@csstools/css-parser-algorithms" "^4.0.0" + "@csstools/css-tokenizer" "^4.0.0" + +"@asamuzakjp/dom-selector@^7.1.1": + version "7.1.1" + resolved "https://registry.npmmirror.com/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz#01880086bb2490098f167beb58555da1a6c9adbd" + integrity sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ== + dependencies: + "@asamuzakjp/generational-cache" "^1.0.1" + "@asamuzakjp/nwsapi" "^2.3.9" + bidi-js "^1.0.3" + css-tree "^3.2.1" + is-potential-custom-element-name "^1.0.1" + +"@asamuzakjp/generational-cache@^1.0.1": + version "1.0.1" + resolved "https://registry.npmmirror.com/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz#3d0bf6be4fc059851390a7070720c6007af793ec" + integrity sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg== + +"@asamuzakjp/nwsapi@^2.3.9": + version "2.3.9" + resolved "https://registry.npmmirror.com/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz#ad5549322dfe9d153d4b4dd6f7ff2ae234b06e24" + integrity sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q== + +"@bramus/specificity@^2.4.2": + version "2.4.2" + resolved "https://registry.npmmirror.com/@bramus/specificity/-/specificity-2.4.2.tgz#aa8db8eb173fdee7324f82284833106adeecc648" + integrity sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw== + dependencies: + css-tree "^3.0.0" + "@codemirror/autocomplete@^6.0.0": version "6.20.1" resolved "https://registry.yarnpkg.com/@codemirror/autocomplete/-/autocomplete-6.20.1.tgz#4cfbc8b2e1e25f890ec34a081037e58b4e44143e" @@ -70,6 +109,61 @@ style-mod "^4.1.0" w3c-keyname "^2.2.4" +"@csstools/color-helpers@^6.0.2": + version "6.0.2" + resolved "https://registry.npmmirror.com/@csstools/color-helpers/-/color-helpers-6.0.2.tgz#82c59fd30649cf0b4d3c82160489748666e6550b" + integrity sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q== + +"@csstools/css-calc@^3.2.0", "@csstools/css-calc@^3.2.1": + version "3.2.1" + resolved "https://registry.npmmirror.com/@csstools/css-calc/-/css-calc-3.2.1.tgz#b30e061ca9f297ccb2b3b032bfee32fda02b1b27" + integrity sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg== + +"@csstools/css-color-parser@^4.1.0": + version "4.1.8" + resolved "https://registry.npmmirror.com/@csstools/css-color-parser/-/css-color-parser-4.1.8.tgz#54c156703a4072a23b0f5e53915ce3479f4d29ec" + integrity sha512-3chWb7PRLijpJpPIKkDxdu6IBeO5MrFACND57On0j8OPpc0wZibcGc3xAHrSEbOx/KDRyMHoIxGn0w1PhXMYHw== + dependencies: + "@csstools/color-helpers" "^6.0.2" + "@csstools/css-calc" "^3.2.1" + +"@csstools/css-parser-algorithms@^4.0.0": + version "4.0.0" + resolved "https://registry.npmmirror.com/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz#e1c65dc09378b42f26a111fca7f7075fc2c26164" + integrity sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w== + +"@csstools/css-syntax-patches-for-csstree@^1.1.3": + version "1.1.5" + resolved "https://registry.npmmirror.com/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.5.tgz#b8e26e0fe25e9a6ec607d045470cc46d2f62731e" + integrity sha512-oNjBvzLq2GPZtJphCjLqXow/cHySHSgtxvKZb7OqSZ/xHgw6NWNhfad+6AB9cLeVm6eA9d/qMll3JdEHjy6M+A== + +"@csstools/css-tokenizer@^4.0.0": + version "4.0.0" + resolved "https://registry.npmmirror.com/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz#798a33950d11226a0ebb6acafa60f5594424967f" + integrity sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA== + +"@emnapi/core@1.11.1": + version "1.11.1" + resolved "https://registry.npmmirror.com/@emnapi/core/-/core-1.11.1.tgz#b9e1064f3a6b1631e241e638eb48d736bfd372a6" + integrity sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ== + dependencies: + "@emnapi/wasi-threads" "1.2.2" + tslib "^2.4.0" + +"@emnapi/runtime@1.11.1": + version "1.11.1" + resolved "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.11.1.tgz#58f1f3d5d81a9b12f793ab688c96371901027c24" + integrity sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw== + dependencies: + tslib "^2.4.0" + +"@emnapi/wasi-threads@1.2.2": + version "1.2.2" + resolved "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz#4c93becf5bfa3b13d1bbdcc06aee38321ad8139a" + integrity sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA== + dependencies: + tslib "^2.4.0" + "@esbuild/aix-ppc64@0.25.0": version "0.25.0" resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz#499600c5e1757a524990d5d92601f0ac3ce87f64" @@ -248,6 +342,11 @@ "@eslint/core" "^1.2.1" levn "^0.4.1" +"@exodus/bytes@^1.11.0", "@exodus/bytes@^1.15.0", "@exodus/bytes@^1.6.0": + version "1.15.1" + resolved "https://registry.npmmirror.com/@exodus/bytes/-/bytes-1.15.1.tgz#b13bc464ca162c17abf0837fb3a11aeab79e45d1" + integrity sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q== + "@humanfs/core@^0.19.2": version "0.19.2" resolved "https://registry.yarnpkg.com/@humanfs/core/-/core-0.19.2.tgz#a8272ca03b2acf492670222b2320b6c421bfde60" @@ -279,6 +378,11 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.3.tgz#c2b9d2e374ee62c586d3adbea87199b1d7a7a6ba" integrity sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ== +"@jridgewell/sourcemap-codec@^1.5.5": + version "1.5.5" + resolved "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba" + integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og== + "@lezer/common@^1.0.0", "@lezer/common@^1.2.0", "@lezer/common@^1.3.0", "@lezer/common@^1.5.0": version "1.5.2" resolved "https://registry.yarnpkg.com/@lezer/common/-/common-1.5.2.tgz#d6840db13779e3f1b42e70c9a97c4086d12fae22" @@ -312,6 +416,122 @@ resolved "https://registry.yarnpkg.com/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz#775374306116d51c0c500b8c4face0f9a04752d8" integrity sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g== +"@napi-rs/wasm-runtime@^1.1.6": + version "1.1.6" + resolved "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz#ed33806d0f9be98dc76d0c3d4fd872fda701b5d5" + integrity sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg== + dependencies: + "@tybys/wasm-util" "^0.10.3" + +"@oxc-project/types@=0.137.0": + version "0.137.0" + resolved "https://registry.npmmirror.com/@oxc-project/types/-/types-0.137.0.tgz#56e77f8bb221fa05f18b1cd34d73f94f0954a773" + integrity sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA== + +"@rolldown/binding-android-arm64@1.1.3": + version "1.1.3" + resolved "https://registry.npmmirror.com/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz#cc6153029c3d9afc9caaae2dc362d899ae94ac4f" + integrity sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g== + +"@rolldown/binding-darwin-arm64@1.1.3": + version "1.1.3" + resolved "https://registry.npmmirror.com/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz#3af681a5d7610340257b3ac7753353b23e884765" + integrity sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw== + +"@rolldown/binding-darwin-x64@1.1.3": + version "1.1.3" + resolved "https://registry.npmmirror.com/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz#80ada35e9f35efb7e48a887444ce2052f615d645" + integrity sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw== + +"@rolldown/binding-freebsd-x64@1.1.3": + version "1.1.3" + resolved "https://registry.npmmirror.com/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.3.tgz#65b2bbb82f005f08aeeff0b6d81e19be68360201" + integrity sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw== + +"@rolldown/binding-linux-arm-gnueabihf@1.1.3": + version "1.1.3" + resolved "https://registry.npmmirror.com/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.3.tgz#7e8d34ad0c7bcfd3baed268e9798571e3888ca71" + integrity sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg== + +"@rolldown/binding-linux-arm64-gnu@1.1.3": + version "1.1.3" + resolved "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz#52bbf400ff219bda1e56c042160d96deb08bfecc" + integrity sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA== + +"@rolldown/binding-linux-arm64-musl@1.1.3": + version "1.1.3" + resolved "https://registry.npmmirror.com/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.3.tgz#9e82899186f73329f3d8155fa1618ae2e86ffa2a" + integrity sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w== + +"@rolldown/binding-linux-ppc64-gnu@1.1.3": + version "1.1.3" + resolved "https://registry.npmmirror.com/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.3.tgz#050520177316586ccad816eb466ea11015e17ba7" + integrity sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw== + +"@rolldown/binding-linux-s390x-gnu@1.1.3": + version "1.1.3" + resolved "https://registry.npmmirror.com/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.3.tgz#50efa7b20219c6e31235fded0fd3427f36123e5a" + integrity sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA== + +"@rolldown/binding-linux-x64-gnu@1.1.3": + version "1.1.3" + resolved "https://registry.npmmirror.com/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz#c5973445113dff50d4077d0edaa4b8a69533dc6f" + integrity sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg== + +"@rolldown/binding-linux-x64-musl@1.1.3": + version "1.1.3" + resolved "https://registry.npmmirror.com/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.3.tgz#ddb023f7fc98ccbb8c1b683545216ca7b4e7ebdd" + integrity sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g== + +"@rolldown/binding-openharmony-arm64@1.1.3": + version "1.1.3" + resolved "https://registry.npmmirror.com/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.3.tgz#832a6da3472722427c73d178c75681858b76aeed" + integrity sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ== + +"@rolldown/binding-wasm32-wasi@1.1.3": + version "1.1.3" + resolved "https://registry.npmmirror.com/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.3.tgz#256cdcc06ad9ada611606526f319642fc0830b0f" + integrity sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg== + dependencies: + "@emnapi/core" "1.11.1" + "@emnapi/runtime" "1.11.1" + "@napi-rs/wasm-runtime" "^1.1.6" + +"@rolldown/binding-win32-arm64-msvc@1.1.3": + version "1.1.3" + resolved "https://registry.npmmirror.com/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz#e735c7024a5e17ebaf13689112fa0bdfc6886c38" + integrity sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g== + +"@rolldown/binding-win32-x64-msvc@1.1.3": + version "1.1.3" + resolved "https://registry.npmmirror.com/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz#f06c09db5c8ad4b6904b4d406c9b6f17f392b5c6" + integrity sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA== + +"@rolldown/pluginutils@^1.0.0": + version "1.0.1" + resolved "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz#e3fcee093fbb5ce765e1ad088ff4de2889f6f9be" + integrity sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw== + +"@standard-schema/spec@^1.1.0": + version "1.1.0" + resolved "https://registry.npmmirror.com/@standard-schema/spec/-/spec-1.1.0.tgz#a79b55dbaf8604812f52d140b2c9ab41bc150bb8" + integrity sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w== + +"@tybys/wasm-util@^0.10.3": + version "0.10.3" + resolved "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.3.tgz#015cba9e9dd47ce14d03d2a8c5d547bfb169665d" + integrity sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg== + dependencies: + tslib "^2.4.0" + +"@types/chai@^5.2.2": + version "5.2.3" + resolved "https://registry.npmmirror.com/@types/chai/-/chai-5.2.3.tgz#8e9cd9e1c3581fa6b341a5aed5588eb285be0b4a" + integrity sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA== + dependencies: + "@types/deep-eql" "*" + assertion-error "^2.0.1" + "@types/codemirror@5.60.8": version "5.60.8" resolved "https://registry.yarnpkg.com/@types/codemirror/-/codemirror-5.60.8.tgz#b647d04b470e8e1836dd84b2879988fc55c9de68" @@ -319,6 +539,11 @@ dependencies: "@types/tern" "*" +"@types/deep-eql@*": + version "4.0.2" + resolved "https://registry.npmmirror.com/@types/deep-eql/-/deep-eql-4.0.2.tgz#334311971d3a07121e7eb91b684a605e7eea9cbd" + integrity sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw== + "@types/esrecurse@^4.3.1": version "4.3.1" resolved "https://registry.yarnpkg.com/@types/esrecurse/-/esrecurse-4.3.1.tgz#6f636af962fbe6191b830bd676ba5986926bccec" @@ -329,6 +554,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.8.tgz#958b91c991b1867ced318bedea0e215ee050726e" integrity sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w== +"@types/estree@^1.0.0": + version "1.0.9" + resolved "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz#cf3f0e876d7bee15a93ab925b82bf570a3904a24" + integrity sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg== + "@types/json-schema@^7.0.15": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" @@ -447,6 +677,66 @@ "@typescript-eslint/types" "8.59.1" eslint-visitor-keys "^5.0.0" +"@vitest/expect@4.1.9": + version "4.1.9" + resolved "https://registry.npmmirror.com/@vitest/expect/-/expect-4.1.9.tgz#ba1af73ae53262e3dc9b518cb7b76fb614e0ef53" + integrity sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA== + dependencies: + "@standard-schema/spec" "^1.1.0" + "@types/chai" "^5.2.2" + "@vitest/spy" "4.1.9" + "@vitest/utils" "4.1.9" + chai "^6.2.2" + tinyrainbow "^3.1.0" + +"@vitest/mocker@4.1.9": + version "4.1.9" + resolved "https://registry.npmmirror.com/@vitest/mocker/-/mocker-4.1.9.tgz#a483de79b358aba3dd8f319a0d8ab17c89f5c75d" + integrity sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw== + dependencies: + "@vitest/spy" "4.1.9" + estree-walker "^3.0.3" + magic-string "^0.30.21" + +"@vitest/pretty-format@4.1.9": + version "4.1.9" + resolved "https://registry.npmmirror.com/@vitest/pretty-format/-/pretty-format-4.1.9.tgz#885cfe9fcb6ff3df4409ea66192cc1fb23d62fae" + integrity sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A== + dependencies: + tinyrainbow "^3.1.0" + +"@vitest/runner@4.1.9": + version "4.1.9" + resolved "https://registry.npmmirror.com/@vitest/runner/-/runner-4.1.9.tgz#bb742947ce4841dfb2d8984a2f9014850be10f51" + integrity sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg== + dependencies: + "@vitest/utils" "4.1.9" + pathe "^2.0.3" + +"@vitest/snapshot@4.1.9": + version "4.1.9" + resolved "https://registry.npmmirror.com/@vitest/snapshot/-/snapshot-4.1.9.tgz#bdfb670ae5617613ea8776e93d0666a66defeeb7" + integrity sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA== + dependencies: + "@vitest/pretty-format" "4.1.9" + "@vitest/utils" "4.1.9" + magic-string "^0.30.21" + pathe "^2.0.3" + +"@vitest/spy@4.1.9": + version "4.1.9" + resolved "https://registry.npmmirror.com/@vitest/spy/-/spy-4.1.9.tgz#bfc40d48fb9bd1a1228bfbfde7f5555e7f6b3867" + integrity sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA== + +"@vitest/utils@4.1.9": + version "4.1.9" + resolved "https://registry.npmmirror.com/@vitest/utils/-/utils-4.1.9.tgz#0184c7e6eb3234739b2b6b3b985f78d1ed823ee1" + integrity sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA== + dependencies: + "@vitest/pretty-format" "4.1.9" + convert-source-map "^2.0.0" + tinyrainbow "^3.1.0" + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -479,11 +769,23 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +assertion-error@^2.0.1: + version "2.0.1" + resolved "https://registry.npmmirror.com/assertion-error/-/assertion-error-2.0.1.tgz#f641a196b335690b1070bf00b6e7593fec190bf7" + integrity sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA== + balanced-match@^4.0.2: version "4.0.4" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-4.0.4.tgz#bfb10662feed8196a2c62e7c68e17720c274179a" integrity sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA== +bidi-js@^1.0.3: + version "1.0.3" + resolved "https://registry.npmmirror.com/bidi-js/-/bidi-js-1.0.3.tgz#6f8bcf3c877c4d9220ddf49b9bb6930c88f877d2" + integrity sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw== + dependencies: + require-from-string "^2.0.2" + brace-expansion@^5.0.5: version "5.0.5" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.5.tgz#dcc3a37116b79f3e1b46db994ced5d570e930fdb" @@ -496,6 +798,11 @@ builtin-modules@3.3.0: resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== +chai@^6.2.2: + version "6.2.2" + resolved "https://registry.npmmirror.com/chai/-/chai-6.2.2.tgz#ae41b52c9aca87734505362717f3255facda360e" + integrity sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg== + chalk@4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -544,6 +851,11 @@ concurrently@^9.2.1: tree-kill "1.2.2" yargs "17.7.2" +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + crelt@^1.0.5, crelt@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72" @@ -558,6 +870,22 @@ cross-spawn@^7.0.6: shebang-command "^2.0.0" which "^2.0.1" +css-tree@^3.0.0, css-tree@^3.2.1: + version "3.2.1" + resolved "https://registry.npmmirror.com/css-tree/-/css-tree-3.2.1.tgz#86cac7011561272b30e6b1e042ba6ce047aa7518" + integrity sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA== + dependencies: + mdn-data "2.27.1" + source-map-js "^1.2.1" + +data-urls@^7.0.0: + version "7.0.0" + resolved "https://registry.npmmirror.com/data-urls/-/data-urls-7.0.0.tgz#6dce8b63226a1ecfdd907ce18a8ccfb1eee506d3" + integrity sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA== + dependencies: + whatwg-mimetype "^5.0.0" + whatwg-url "^16.0.0" + debug@^4.3.1, debug@^4.3.2, debug@^4.4.3: version "4.4.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" @@ -565,16 +893,36 @@ debug@^4.3.1, debug@^4.3.2, debug@^4.4.3: dependencies: ms "^2.1.3" +decimal.js@^10.6.0: + version "10.6.0" + resolved "https://registry.npmmirror.com/decimal.js/-/decimal.js-10.6.0.tgz#e649a43e3ab953a72192ff5983865e509f37ed9a" + integrity sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg== + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== +detect-libc@^2.0.3: + version "2.1.2" + resolved "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad" + integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== + emoji-regex@^8.0.0: version "8.0.0" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +entities@^8.0.0: + version "8.0.0" + resolved "https://registry.npmmirror.com/entities/-/entities-8.0.0.tgz#c1df5fe3602429747fa233d0dd26f142f0ce4743" + integrity sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA== + +es-module-lexer@^2.0.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-2.1.0.tgz#1dfcbb5ea3bbfb63f28e1fc3676c3676d1c9624c" + integrity sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ== + esbuild@0.25.0: version "0.25.0" resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.25.0.tgz#0de1787a77206c5a79eeb634a623d39b5006ce92" @@ -700,11 +1048,23 @@ estraverse@^5.1.0, estraverse@^5.2.0: resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== +estree-walker@^3.0.3: + version "3.0.3" + resolved "https://registry.npmmirror.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" + integrity sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g== + dependencies: + "@types/estree" "^1.0.0" + esutils@^2.0.2: version "2.0.3" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +expect-type@^1.3.0: + version "1.3.0" + resolved "https://registry.npmmirror.com/expect-type/-/expect-type-1.3.0.tgz#0d58ed361877a31bbc4dd6cf71bbfef7faf6bd68" + integrity sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA== + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -762,6 +1122,11 @@ fs-extra@^11.3.0: jsonfile "^6.0.1" universalify "^2.0.0" +fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -794,6 +1159,13 @@ highlight.js@^11.11.1: resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.11.1.tgz#fca06fa0e5aeecf6c4d437239135fabc15213585" integrity sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w== +html-encoding-sniffer@^6.0.0: + version "6.0.0" + resolved "https://registry.npmmirror.com/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz#f8d9390b3b348b50d4f61c16dd2ef5c05980a882" + integrity sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg== + dependencies: + "@exodus/bytes" "^1.6.0" + husky@^9.1.7: version "9.1.7" resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.7.tgz#d46a38035d101b46a70456a850ff4201344c0b2d" @@ -831,11 +1203,43 @@ is-glob@^4.0.0, is-glob@^4.0.3: dependencies: is-extglob "^2.1.1" +is-potential-custom-element-name@^1.0.1: + version "1.0.1" + resolved "https://registry.npmmirror.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5" + integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ== + isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +jsdom@^29.1.1: + version "29.1.1" + resolved "https://registry.npmmirror.com/jsdom/-/jsdom-29.1.1.tgz#5b9704906f3cd510c34aa941ae2f8f7f8179df01" + integrity sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q== + dependencies: + "@asamuzakjp/css-color" "^5.1.11" + "@asamuzakjp/dom-selector" "^7.1.1" + "@bramus/specificity" "^2.4.2" + "@csstools/css-syntax-patches-for-csstree" "^1.1.3" + "@exodus/bytes" "^1.15.0" + css-tree "^3.2.1" + data-urls "^7.0.0" + decimal.js "^10.6.0" + html-encoding-sniffer "^6.0.0" + is-potential-custom-element-name "^1.0.1" + lru-cache "^11.3.5" + parse5 "^8.0.1" + saxes "^6.0.0" + symbol-tree "^3.2.4" + tough-cookie "^6.0.1" + undici "^7.25.0" + w3c-xmlserializer "^5.0.0" + webidl-conversions "^8.0.1" + whatwg-mimetype "^5.0.0" + whatwg-url "^16.0.1" + xml-name-validator "^5.0.0" + json-buffer@3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" @@ -875,6 +1279,80 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +lightningcss-android-arm64@1.32.0: + version "1.32.0" + resolved "https://registry.npmmirror.com/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz#f033885116dfefd9c6f54787523e3514b61e1968" + integrity sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg== + +lightningcss-darwin-arm64@1.32.0: + version "1.32.0" + resolved "https://registry.npmmirror.com/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz#50b71871b01c8199584b649e292547faea7af9b5" + integrity sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ== + +lightningcss-darwin-x64@1.32.0: + version "1.32.0" + resolved "https://registry.npmmirror.com/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz#35f3e97332d130b9ca181e11b568ded6aebc6d5e" + integrity sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w== + +lightningcss-freebsd-x64@1.32.0: + version "1.32.0" + resolved "https://registry.npmmirror.com/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz#9777a76472b64ed6ff94342ad64c7bafd794a575" + integrity sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig== + +lightningcss-linux-arm-gnueabihf@1.32.0: + version "1.32.0" + resolved "https://registry.npmmirror.com/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz#13ae652e1ab73b9135d7b7da172f666c410ad53d" + integrity sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw== + +lightningcss-linux-arm64-gnu@1.32.0: + version "1.32.0" + resolved "https://registry.npmmirror.com/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz#417858795a94592f680123a1b1f9da8a0e1ef335" + integrity sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ== + +lightningcss-linux-arm64-musl@1.32.0: + version "1.32.0" + resolved "https://registry.npmmirror.com/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz#6be36692e810b718040802fd809623cffe732133" + integrity sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg== + +lightningcss-linux-x64-gnu@1.32.0: + version "1.32.0" + resolved "https://registry.npmmirror.com/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz#0b7803af4eb21cfd38dd39fe2abbb53c7dd091f6" + integrity sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA== + +lightningcss-linux-x64-musl@1.32.0: + version "1.32.0" + resolved "https://registry.npmmirror.com/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz#88dc8ba865ddddb1ac5ef04b0f161804418c163b" + integrity sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg== + +lightningcss-win32-arm64-msvc@1.32.0: + version "1.32.0" + resolved "https://registry.npmmirror.com/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz#4f30ba3fa5e925f5b79f945e8cc0d176c3b1ab38" + integrity sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw== + +lightningcss-win32-x64-msvc@1.32.0: + version "1.32.0" + resolved "https://registry.npmmirror.com/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz#141aa5605645064928902bb4af045fa7d9f4220a" + integrity sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q== + +lightningcss@^1.32.0: + version "1.32.0" + resolved "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.32.0.tgz#b85aae96486dcb1bf49a7c8571221273f4f1e4a9" + integrity sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ== + dependencies: + detect-libc "^2.0.3" + optionalDependencies: + lightningcss-android-arm64 "1.32.0" + lightningcss-darwin-arm64 "1.32.0" + lightningcss-darwin-x64 "1.32.0" + lightningcss-freebsd-x64 "1.32.0" + lightningcss-linux-arm-gnueabihf "1.32.0" + lightningcss-linux-arm64-gnu "1.32.0" + lightningcss-linux-arm64-musl "1.32.0" + lightningcss-linux-x64-gnu "1.32.0" + lightningcss-linux-x64-musl "1.32.0" + lightningcss-win32-arm64-msvc "1.32.0" + lightningcss-win32-x64-msvc "1.32.0" + locate-path@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" @@ -887,6 +1365,23 @@ lodash@^4.17.23: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.18.1.tgz#ff2b66c1f6326d59513de2407bf881439812771c" integrity sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q== +lru-cache@^11.3.5: + version "11.5.1" + resolved "https://registry.npmmirror.com/lru-cache/-/lru-cache-11.5.1.tgz#f3daa3540847b9737ebc02499ddb36765e54db4a" + integrity sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A== + +magic-string@^0.30.21: + version "0.30.21" + resolved "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91" + integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.5" + +mdn-data@2.27.1: + version "2.27.1" + resolved "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.27.1.tgz#e37b9c50880b75366c4d40ac63d9bbcacdb61f0e" + integrity sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ== + minimatch@^10.2.2, minimatch@^10.2.4: version "10.2.5" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.5.tgz#bd48687a0be38ed2961399105600f832095861d1" @@ -904,6 +1399,11 @@ ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +nanoid@^3.3.12: + version "3.3.15" + resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.15.tgz#36c490fad8c6e86c824c940dfdde999b69ed4316" + integrity sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -917,6 +1417,11 @@ obsidian@latest: "@types/codemirror" "5.60.8" moment "2.29.4" +obug@^2.1.1: + version "2.1.3" + resolved "https://registry.npmmirror.com/obug/-/obug-2.1.3.tgz#c02c60f95abd603409330e767db7f2823193331e" + integrity sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg== + optionator@^0.9.3: version "0.9.4" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.4.tgz#7ea1c1a5d91d764fb282139c88fe11e182a3a734" @@ -943,6 +1448,13 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" +parse5@^8.0.1: + version "8.0.1" + resolved "https://registry.npmmirror.com/parse5/-/parse5-8.0.1.tgz#f43bcd2cd683efe084075333e9ce0da7d06da31e" + integrity sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw== + dependencies: + entities "^8.0.0" + path-exists@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" @@ -953,11 +1465,30 @@ path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== -picomatch@^4.0.4: +pathe@^2.0.3: + version "2.0.3" + resolved "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz#3ecbec55421685b70a9da872b2cff3e1cbed1716" + integrity sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^4.0.3, picomatch@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589" integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A== +postcss@^8.5.15: + version "8.5.15" + resolved "https://registry.npmmirror.com/postcss/-/postcss-8.5.15.tgz#d1eaf677a324e9ec02196da2d3fecf4a0b9a735c" + integrity sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A== + dependencies: + nanoid "^3.3.12" + picocolors "^1.1.1" + source-map-js "^1.2.1" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -968,7 +1499,7 @@ prettier@^3.8.3: resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.3.tgz#560f2de55bf01b4c0503bc629d5df99b9a1d09b0" integrity sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw== -punycode@^2.1.0: +punycode@^2.1.0, punycode@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== @@ -983,6 +1514,35 @@ require-directory@^2.1.1: resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== +require-from-string@^2.0.2: + version "2.0.2" + resolved "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" + integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== + +rolldown@~1.1.2: + version "1.1.3" + resolved "https://registry.npmmirror.com/rolldown/-/rolldown-1.1.3.tgz#87072bfd0d1bdd02a66076a261a62e8e49b3f0e2" + integrity sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g== + dependencies: + "@oxc-project/types" "=0.137.0" + "@rolldown/pluginutils" "^1.0.0" + optionalDependencies: + "@rolldown/binding-android-arm64" "1.1.3" + "@rolldown/binding-darwin-arm64" "1.1.3" + "@rolldown/binding-darwin-x64" "1.1.3" + "@rolldown/binding-freebsd-x64" "1.1.3" + "@rolldown/binding-linux-arm-gnueabihf" "1.1.3" + "@rolldown/binding-linux-arm64-gnu" "1.1.3" + "@rolldown/binding-linux-arm64-musl" "1.1.3" + "@rolldown/binding-linux-ppc64-gnu" "1.1.3" + "@rolldown/binding-linux-s390x-gnu" "1.1.3" + "@rolldown/binding-linux-x64-gnu" "1.1.3" + "@rolldown/binding-linux-x64-musl" "1.1.3" + "@rolldown/binding-openharmony-arm64" "1.1.3" + "@rolldown/binding-wasm32-wasi" "1.1.3" + "@rolldown/binding-win32-arm64-msvc" "1.1.3" + "@rolldown/binding-win32-x64-msvc" "1.1.3" + rxjs@7.8.2: version "7.8.2" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" @@ -990,6 +1550,13 @@ rxjs@7.8.2: dependencies: tslib "^2.1.0" +saxes@^6.0.0: + version "6.0.0" + resolved "https://registry.npmmirror.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5" + integrity sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA== + dependencies: + xmlchars "^2.2.0" + semver@^7.7.3: version "7.7.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.7.4.tgz#28464e36060e991fa7a11d0279d2d3f3b57a7e8a" @@ -1012,6 +1579,26 @@ shell-quote@1.8.3: resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.3.tgz#55e40ef33cf5c689902353a3d8cd1a6725f08b4b" integrity sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw== +siginfo@^2.0.0: + version "2.0.0" + resolved "https://registry.npmmirror.com/siginfo/-/siginfo-2.0.0.tgz#32e76c70b79724e3bb567cb9d543eb858ccfaf30" + integrity sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g== + +source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + +stackback@0.0.2: + version "0.0.2" + resolved "https://registry.npmmirror.com/stackback/-/stackback-0.0.2.tgz#1ac8a0d9483848d1695e418b6d031a3c3ce68e3b" + integrity sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw== + +std-env@^4.0.0-rc.1: + version "4.1.0" + resolved "https://registry.npmmirror.com/std-env/-/std-env-4.1.0.tgz#45899abc590d86d682e87f0acd1033a75084cd3f" + integrity sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ== + string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" @@ -1047,6 +1634,21 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +symbol-tree@^3.2.4: + version "3.2.4" + resolved "https://registry.npmmirror.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" + integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== + +tinybench@^2.9.0: + version "2.9.0" + resolved "https://registry.npmmirror.com/tinybench/-/tinybench-2.9.0.tgz#103c9f8ba6d7237a47ab6dd1dcff77251863426b" + integrity sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg== + +tinyexec@^1.0.2: + version "1.2.4" + resolved "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.2.4.tgz#ae45bb2edebda94c70f4ea897e0f1243e470db71" + integrity sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg== + tinyglobby@^0.2.15: version "0.2.16" resolved "https://registry.yarnpkg.com/tinyglobby/-/tinyglobby-0.2.16.tgz#1c3b7eb953fce42b226bc5a1ee06428281aff3d6" @@ -1055,6 +1657,45 @@ tinyglobby@^0.2.15: fdir "^6.5.0" picomatch "^4.0.4" +tinyglobby@^0.2.17: + version "0.2.17" + resolved "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz#562a9a6c9eb2b3b123d39719f9af5bb44fcd7631" + integrity sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g== + dependencies: + fdir "^6.5.0" + picomatch "^4.0.4" + +tinyrainbow@^3.1.0: + version "3.1.0" + resolved "https://registry.npmmirror.com/tinyrainbow/-/tinyrainbow-3.1.0.tgz#1d8a623893f95cf0a2ddb9e5d11150e191409421" + integrity sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw== + +tldts-core@^7.4.4: + version "7.4.4" + resolved "https://registry.npmmirror.com/tldts-core/-/tldts-core-7.4.4.tgz#a06de228c70a334e26d1597af7a82376e8588bce" + integrity sha512-vwVLJVvvpslm7vqAH7+XNj/neA/Ynq7DT2EEcMuwc5YzN5XaMyRAqxwU+uX3azZ1FQtB2gvrvnLnAEkvYlVdfg== + +tldts@^7.0.5: + version "7.4.4" + resolved "https://registry.npmmirror.com/tldts/-/tldts-7.4.4.tgz#343191348abbff80118732a1a84e90fcf95b29df" + integrity sha512-kFXFK7O4WPextIUAOk8qtnw9dxR9UIXP9CjuH1cTBVBZMDeQcUPgr/IazGiw1B0Yiw5L75gHLWeW4iD793r90g== + dependencies: + tldts-core "^7.4.4" + +tough-cookie@^6.0.1: + version "6.0.1" + resolved "https://registry.npmmirror.com/tough-cookie/-/tough-cookie-6.0.1.tgz#a495f833836609ed983c19bc65639cfbceb54c76" + integrity sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw== + dependencies: + tldts "^7.0.5" + +tr46@^6.0.0: + version "6.0.0" + resolved "https://registry.npmmirror.com/tr46/-/tr46-6.0.0.tgz#f5a1ae546a0adb32a277a2278d0d17fa2f9093e6" + integrity sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw== + dependencies: + punycode "^2.3.1" + tree-kill@1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" @@ -1070,7 +1711,7 @@ tslib@2.4.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== -tslib@^2.1.0: +tslib@^2.1.0, tslib@^2.4.0: version "2.8.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== @@ -1087,6 +1728,11 @@ typescript@^6.0.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-6.0.3.tgz#90251dc007916e972786cb94d74d15b185577d21" integrity sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw== +undici@^7.25.0: + version "7.28.0" + resolved "https://registry.npmmirror.com/undici/-/undici-7.28.0.tgz#97d64564198b285bc281f0e8e29597e3d11fe7ec" + integrity sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA== + universalify@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d" @@ -1099,11 +1745,76 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +"vite@^6.0.0 || ^7.0.0 || ^8.0.0": + version "8.1.0" + resolved "https://registry.npmmirror.com/vite/-/vite-8.1.0.tgz#0734dc1a48faeb2bd5f5b16b66dcbfae484fec55" + integrity sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q== + dependencies: + lightningcss "^1.32.0" + picomatch "^4.0.4" + postcss "^8.5.15" + rolldown "~1.1.2" + tinyglobby "^0.2.17" + optionalDependencies: + fsevents "~2.3.3" + +vitest@^4.1.9: + version "4.1.9" + resolved "https://registry.npmmirror.com/vitest/-/vitest-4.1.9.tgz#98f22fbd70e2a18c4a92bb20624bc92e5dfac5f3" + integrity sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ== + dependencies: + "@vitest/expect" "4.1.9" + "@vitest/mocker" "4.1.9" + "@vitest/pretty-format" "4.1.9" + "@vitest/runner" "4.1.9" + "@vitest/snapshot" "4.1.9" + "@vitest/spy" "4.1.9" + "@vitest/utils" "4.1.9" + es-module-lexer "^2.0.0" + expect-type "^1.3.0" + magic-string "^0.30.21" + obug "^2.1.1" + pathe "^2.0.3" + picomatch "^4.0.3" + std-env "^4.0.0-rc.1" + tinybench "^2.9.0" + tinyexec "^1.0.2" + tinyglobby "^0.2.15" + tinyrainbow "^3.1.0" + vite "^6.0.0 || ^7.0.0 || ^8.0.0" + why-is-node-running "^2.3.0" + w3c-keyname@^2.2.4: version "2.2.8" resolved "https://registry.yarnpkg.com/w3c-keyname/-/w3c-keyname-2.2.8.tgz#7b17c8c6883d4e8b86ac8aba79d39e880f8869c5" integrity sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ== +w3c-xmlserializer@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz#f925ba26855158594d907313cedd1476c5967f6c" + integrity sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA== + dependencies: + xml-name-validator "^5.0.0" + +webidl-conversions@^8.0.1: + version "8.0.1" + resolved "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-8.0.1.tgz#0657e571fe6f06fcb15ca50ed1fdbcb495cd1686" + integrity sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ== + +whatwg-mimetype@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz#d8232895dbd527ceaee74efd4162008fb8a8cf48" + integrity sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw== + +whatwg-url@^16.0.0, whatwg-url@^16.0.1: + version "16.0.1" + resolved "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-16.0.1.tgz#047f7f4bd36ef76b7198c172d1b1cebc66f764dd" + integrity sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw== + dependencies: + "@exodus/bytes" "^1.11.0" + tr46 "^6.0.0" + webidl-conversions "^8.0.1" + which@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" @@ -1111,6 +1822,14 @@ which@^2.0.1: dependencies: isexe "^2.0.0" +why-is-node-running@^2.3.0: + version "2.3.0" + resolved "https://registry.npmmirror.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz#a3f69a97107f494b3cdc3bdddd883a7d65cebf04" + integrity sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w== + dependencies: + siginfo "^2.0.0" + stackback "0.0.2" + word-wrap@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" @@ -1125,6 +1844,16 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" +xml-name-validator@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/xml-name-validator/-/xml-name-validator-5.0.0.tgz#82be9b957f7afdacf961e5980f1bf227c0bf7673" + integrity sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg== + +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.npmmirror.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + y18n@^5.0.5: version "5.0.8" resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"