Compare commits

...

15 commits

Author SHA1 Message Date
Alamion
f77c6ea3f6
fixes:
- another node dependencies fix
2026-06-25 18:14:48 +03:00
Alamion
57fa0d1873
fixes:
- node dependencies fix
2026-06-25 18:11:01 +03:00
Alamion
2aeb8c1bee
feats:
- presets for JQL
  - rename all cached issues
  - rebuild cache command
  - bunch of vitests
fixes:
  - incorrect migration save/load settings
  - incorrect cache validation before obsidian init
2026-06-25 17:47:22 +03:00
Alamion
3278de8748
Merge pull request #28 from Alamion/testing
A bunch of minor fixes
2026-06-03 18:38:32 +03:00
Alamion
ad06c8f4d8
Fix:
- `jira-sync-block-start-` docs fix
- custom fields functions correct load on startup
- jira values highlight rollback due to contributor dropped feature full realization. (In current state it's too buggy to release)
2026-06-03 18:35:22 +03:00
Alamion
e3a2a01270
Merge remote-tracking branch 'slipstyle/feature/highlight-sync-sections' into testing
# Conflicts:
#	src/postprocessing/livePreview.ts
#	src/postprocessing/reading.ts
#	src/settings/default.ts
2026-05-09 14:59:06 +03:00
slips
d4d9746647 fix: resolve TypeScript errors in createUpdateIssue and markdownToAdf
- Fixed processFrontmatter typo (processFrontMatter)
- Fixed minimalIssue type assertion (as any as JiraIssue)
- Fixed AdfTaskListNode interface duplicates
- Fixed AdfBlockNode type union to use AdfTaskListNode
- Fixed statusMapping.fromJira second parameter (api_version)
2026-05-05 18:01:08 -04:00
slips
f259446b74 fix: return null for undefined inputs in markdown converters
- adfToMarkdown(undefined) now returns null instead of ''
- jiraToMarkdown(undefined) now returns null instead of ''
- Prevents empty strings from passing guard clause and clearing note fields
- Proper semantics: undefined = field not in issue, null = field cleared in Jira
2026-05-05 17:03:36 -04:00
slips
5229b2d1a5 fix: update status only — don't call updateJiraToLocal with partial issue
Passing {fields: {status: {name: ...}}} to updateJiraToLocal caused all
fromJira mappings to run with undefined inputs, producing empty strings
that overwrote description and comments. Now updates only the status
field in frontmatter and sync content markers directly.
2026-05-05 16:52:45 -04:00
slips
5df851934d revert: restore broad marker regex for custom field support 2026-04-27 11:05:29 -04:00
slips
1b400e7e55 fix: tighten marker regex to avoid matching CSS class names in docs 2026-04-27 11:02:18 -04:00
slips
aa6e36ba5d Fix block highlight viewport bug: scan full doc for block ranges 2026-04-27 09:05:58 -04:00
slips
3169273f5e fix: use inset box-shadow for block border to preserve list indentation
border-left + padding-left was overriding Obsidian's list item padding,
causing bullet and ordered list markers to overlap the accent bar. An
inset box-shadow achieves the same visual effect without touching layout.
2026-04-22 14:29:35 -04:00
slips
1562f4ff52 refactor: highlight synced content instead of markers
When highlight mode is on, hide markers as before but wrap the synced
content itself in a highlight decoration:
- Inline sections: background + outline on text between start/end markers
- Line sections: same treatment on text after the line marker
- Block sections: left accent border + tinted background on each content line

Reading mode wraps the same content in a jira-sync-content span.
Uses color-mix() for theme-aware accent tinting.
2026-04-22 11:20:32 -04:00
slips
f4732263f8 feat: add highlight sync sections setting
Add a toggle in General settings to make jira-sync markers visible as
styled labels instead of hiding them. When enabled:
- Markers render with tag-style pill appearance (theme-aware via --tag-* vars)
- Block content between jira-sync-block-start and jira-sync-end gets a
  left border accent line in live preview
- Works in both live preview (CM6) and reading mode
- Cursor inside a marker adds an accent outline (jira-sync-active)

Default is off, preserving existing hide-on-blur behaviour.
2026-04-22 11:09:08 -04:00
42 changed files with 1944 additions and 124 deletions

View file

@ -15,12 +15,12 @@ jobs:
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: '18.x'
node-version: '20.x'
- name: Build plugin
run: |
npm install
npm run build
yarn install --frozen-lockfile
yarn run build
- name: Create release
env:

6
.gitignore vendored
View file

@ -31,3 +31,9 @@ tmp/
# localization compiled
src/localization/compiled
# test configuration with credentials
tests/test-config.local.json
# test configuration with credentials
tests/test-config.local.json

View file

@ -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

View file

@ -69,9 +69,9 @@ The responsible person for this task is `jira-sync-inline-start-assignee`Bob`jir
Example:
```md
The responsible person for this task is `jira-sync-block-assignee`
The responsible person for this task is `jira-sync-block-start-assignee`
Bob
`jira-sync-end`, and the description is `jira-sync-block-description`
`jira-sync-end`, and the description is `jira-sync-block-start-description`
Some description
`jira-sync-end`.
```

View file

@ -68,9 +68,9 @@
Пример:
```md
Ответственным за эту задачу является `jira-sync-block-assignee`
Ответственным за эту задачу является `jira-sync-block-start-assignee`
Bob
`jira-sync-end`, а описание — `jira-sync-block-description`
`jira-sync-end`, а описание — `jira-sync-block-start-description`
Некоторое описание
`jira-sync-end`.
```

View file

@ -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'],

View file

@ -1,7 +1,7 @@
{
"id": "jira-sync",
"name": "Jira Issue Manager",
"version": "1.6.0",
"version": "1.7.2",
"minAppVersion": "1.10.1",
"description": "Get Jira issues, create and update them. Issue status and worklog management.",
"author": "Alamion",

View file

@ -1,6 +1,6 @@
{
"name": "obsidian-jira-sync",
"version": "1.6.0",
"version": "1.7.2",
"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": [],
@ -27,7 +30,7 @@
"@codemirror/view": "^6.38.1",
"@eslint/js": "^10.0.1",
"@types/lodash": "^4.17.16",
"@types/node": "^16.11.6",
"@types/node": "^20.0.0",
"@typescript-eslint/eslint-plugin": "^8.59.1",
"@typescript-eslint/parser": "^8.59.1",
"builtin-modules": "3.3.0",
@ -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",

View file

@ -6,3 +6,5 @@ export * from './createIssue';
export * from './getIssue';
export * from './updateIssue';
export * from './updateStatus';
export * from './rebuildCache';
export * from './rebuildCache';

View file

@ -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');
},
});
}

View file

@ -5,6 +5,7 @@ import { prepareJiraFieldsFromFile } from './commonPrepareData';
import { localToJiraFields, updateJiraToLocal } from '../tools/mapObsidianJiraFields';
import { JiraIssue, JiraTransitionType } from '../interfaces';
import { obsidianJiraFieldMappings } from '../default/obsidianJiraFieldsMapping';
import { updateJiraSyncContent } from '../tools/sectionTools';
export async function updateIssueFromFile(plugin: JiraPlugin, file: TFile): Promise<string> {
let fields = await prepareJiraFieldsFromFile(plugin, file);
@ -72,8 +73,24 @@ export async function updateStatusFromFile(
}
await updateJiraStatus(plugin, fields.key, transition.id);
await updateJiraToLocal(plugin, file, {
fields: { status: { name: transition.status } },
} as JiraIssue);
// Only update the status field. Calling updateJiraToLocal with a partial issue
// causes all fromJira mappings to run with undefined inputs, producing empty strings
// that overwrite description and comments.
const allMappings = { ...obsidianJiraFieldMappings, ...plugin.settings.fieldMapping.fieldMappings };
const statusMapping = allMappings['status'];
const minimalIssue = { fields: { status: { name: transition.status } } } as JiraIssue;
const localStatusValue = statusMapping
? statusMapping.fromJira(minimalIssue, plugin.getCurrentConnection()?.apiVersion)
: transition.status;
if (localStatusValue !== null && localStatusValue !== undefined) {
await plugin.app.fileManager.processFrontMatter(file, (frontmatter: any) => {
frontmatter['status'] = localStatusValue;
});
await plugin.app.vault.process(file, (fileContent) => {
return updateJiraSyncContent(fileContent, { status: String(localStatusValue) });
});
}
return fields.key;
}

View file

@ -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<void> {
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<TFile | null> {
const issuesFolder = plugin.app.vault.getAbstractFileByPath(plugin.settings.global.issuesFolder);
if (!issuesFolder || !(issuesFolder instanceof TFolder)) {
return null;
}
return await searchFolderForIssueKey(plugin, issuesFolder, issueKey);
}

View file

@ -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)'

View file

@ -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

View file

@ -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: Загрузка...

View file

@ -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: Файлы не нуждаются в переименовании

View file

@ -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,12 +42,13 @@ export default class JiraPlugin extends Plugin {
registerUpdateWorkLogManuallyCommand(this);
registerUpdateWorkLogBatchCommand(this);
registerAddCommentCommand(this);
registerRebuildCacheCommand(this);
// Add settings tab
this.addSettingTab(new JiraSettingTab(this.app, this));
// Handle Reading mode (post-processor for rendered markdown)
this.registerMarkdownPostProcessor(hideJiraPointersReading.bind(this));
this.registerMarkdownPostProcessor(hideJiraPointersReading());
// Handle Live Preview/Edit mode (CodeMirror extension)
this.registerEditorExtension(createJiraSyncExtension(this));
@ -58,13 +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);
this.settings = Object.assign({}, DEFAULT_SETTINGS, new_data);
// TODO: Cancel and delete migration check in future (approximately 2026-2027)
const { result: migratedData, changed: migrationChanged } = checkMigrateSettings(old_data);
this.settings = {
...DEFAULT_SETTINGS,
...migratedData,
fieldMapping: {
...DEFAULT_SETTINGS.fieldMapping,
...(migratedData?.fieldMapping || {}),
},
};
this.settings.fieldMapping.fieldMappings = await transform_string_to_functions_mappings(
this.settings.fieldMapping.fieldMappingsStrings,
);
if (migrationChanged) {
await this.saveSettings();
}
}
async saveSettings() {
@ -93,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;
}
}
@ -131,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;
}
}

View file

@ -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();
}
}

View file

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

View file

@ -5,3 +5,5 @@ export * from './IssueTypeModal';
export * from './IssueWorkLogModal';
export * from './ProjectModal';
export * from './JQLSearchModal';
export * from './ConfirmModal';
export * from './ConfirmModal';

View file

@ -31,32 +31,36 @@ export function createJiraSyncExtension(plugin: JiraPlugin): Extension {
buildDecorations(view: EditorView) {
const builder = new RangeSetBuilder<Decoration>();
const sel = view.state.selection;
const allDecs: Array<{ from: number; to: number; dec: Decoration }> = [];
for (let { from, to } of view.visibleRanges) {
const text = view.state.doc.sliceString(from, to);
const codeBlocks = this.findCodeBlocks(text, from);
// Collect all jira-sync marker positions
const markers: Array<{ start: number; end: number; name: string }> = [];
for (const match of text.matchAll(/`(jira-sync-[^`]+)`/g)) {
const start = from + match.index!;
const end = start + match[0].length;
const contentStart = start; // without first `
const contentEnd = end; // without last `
// Find code blocks to ignore
if (this.isInsideCodeBlock(contentStart, contentEnd, codeBlocks)) {
continue;
}
let className = 'jira-sync-hidden';
// Check if cursor is inside
const sel = view.state.selection;
if (sel.ranges.some((r) => r.from <= contentEnd && r.to >= contentStart)) {
className += ' jira-sync-active';
}
builder.add(start, end, Decoration.mark({ class: className }));
if (this.isInsideCodeBlock(start, end, codeBlocks)) continue;
markers.push({ start, end, name: match[1] });
}
// Always hide the markers themselves, show when cursor is inside
for (const { start, end } of markers) {
const isActive = sel.ranges.some((r) => r.from <= end && r.to >= start);
const cls = isActive ? 'jira-sync-hidden jira-sync-active' : 'jira-sync-hidden';
allDecs.push({ from: start, to: end, dec: Decoration.mark({ class: cls }) });
}
}
// RangeSetBuilder requires ranges in ascending order of `from`, then `to`
allDecs.sort((a, b) => a.from - b.from || a.to - b.to);
for (const { from, to, dec } of allDecs) {
builder.add(from, to, dec);
}
return builder.finish() as DecorationSet;

View file

@ -1,13 +1,12 @@
// For Reading mode - targets <code> elements in <p> tags
export function hideJiraPointersReading(element: HTMLElement) {
// In Reading mode, inline code becomes <code> elements
const codeElements = element.querySelectorAll(':not(pre) > code');
export function hideJiraPointersReading() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
return function (element: HTMLElement, _: any) {
const codeElements = Array.from(element.querySelectorAll(':not(pre) > code')).filter((el) =>
el.textContent?.startsWith('jira-sync-'),
) as HTMLElement[];
codeElements.forEach((codeEl) => {
const text = codeEl.textContent || '';
if (text.startsWith('jira-sync-')) {
for (const codeEl of codeElements) {
codeEl.addClass('jira-sync-hidden');
}
});
};
}

View file

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

View file

@ -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',
});

View file

@ -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,

View file

@ -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<void> {
}
// Remove invalid entries
entriesToRemove.forEach((issueKey) => {
plugin.removeIssueKeyFromCache(issueKey);
});
for (const issueKey of entriesToRemove) {
await plugin.removeIssueKeyFromCache(issueKey);
}
}

View file

@ -340,13 +340,13 @@ export async function transform_string_to_functions_mappings(
// Also convert to functions for runtime use
const transformedMappings: Record<string, FieldMapping> = {};
for (const [fieldName, { toJira, fromJira }] of Object.entries(mappings)) {
const toJiraFn = safeStringToFunction(toJira, 'toJira', extraValidate);
const fromJiraFn = safeStringToFunction(fromJira, 'fromJira', extraValidate);
const toJiraFn = await safeStringToFunction(toJira, 'toJira', extraValidate);
const fromJiraFn = await safeStringToFunction(fromJira, 'fromJira', extraValidate);
if (toJiraFn && fromJiraFn) {
transformedMappings[fieldName] = {
toJira: (await toJiraFn) as (value: any, api_version?: '2' | '3') => any,
fromJira: (await fromJiraFn) as (
toJira: toJiraFn as (value: any, api_version?: '2' | '3') => any,
fromJira: fromJiraFn as (
issue: JiraIssue,
api_version?: '2' | '3',
data_source?: Record<string, any>,

View file

@ -3,9 +3,10 @@
* @param str The Jira markup string
* @returns The Markdown string
*/
export function jiraToMarkdown(str: any): string {
export function jiraToMarkdown(str: any): string | null {
try {
if (str === null || str === undefined) return '';
if (str === undefined) return null;
if (str === null) return '';
// Initial normalization to string
let content: string = '';

View file

@ -47,9 +47,15 @@ interface AdfOrderedListNode {
content: AdfListItemNode[];
}
interface AdfTaskItemNode {
type: 'taskItem';
attrs: { localId: string; state: 'TODO' | 'DONE' };
content: AdfInlineContent[];
}
interface AdfTaskListNode {
type: 'taskList';
content: AdfListItemNode[];
content: AdfTaskItemNode[];
attrs?: { localId: string };
}
@ -426,7 +432,8 @@ function adfBlockToMarkdown(node: any): string {
}
}
export function adfToMarkdown(adf: any): string {
export function adfToMarkdown(adf: any): string | null {
if (adf === undefined) return null;
if (!adf || typeof adf !== 'object') return '';
const blocks: string[] = (adf.content || []).map((node: any) => adfBlockToMarkdown(node));
return blocks.filter((b) => b !== '').join('\n\n');

View file

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

183
tests/__mocks__/obsidian.ts Normal file
View file

@ -0,0 +1,183 @@
export class Plugin {
app: App;
settings: Record<string, any> = {};
constructor() {
this.app = new App();
}
async loadData(): Promise<any> {
return {};
}
async saveData(data: any): Promise<void> {}
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<string> {
return '';
}
async process(_file: TFile, _fn: (data: string) => string): Promise<string> {
return '';
}
async create(_path: string, _content: string): Promise<TFile> {
return new TFile();
}
async rename(_file: TFile, _newPath: string): Promise<void> {}
getFileByPath(_path: string): TFile | null {
return null;
}
getFolderByPath(_path: string): TFolder | null {
return null;
}
async createFolder(_path: string): Promise<void> {}
}
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<void> {
return Promise.resolve();
}
}
export class MetadataCache {
getFileCache(_file: TFile): any {
return null;
}
}
export class FileManager {
async processFrontMatter(_file: TFile, _fn: (frontmatter: any) => void): Promise<void> {}
}
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<any> {
return Promise.resolve({ status: 200, json: {}, text: '' });
}

View file

@ -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<any> {
const url = `${conn.jiraUrl}/rest/api/${conn.apiVersion}${urlPath}`;
const headers: Record<string, string> = {
'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<string | null> {
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);
});
});
}

View file

@ -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)');
});
});

View file

@ -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');
});
});
});

4
tests/__tests__/setup.ts Normal file
View file

@ -0,0 +1,4 @@
import { vi } from 'vitest';
vi.stubGlobal('requestAnimationFrame', (cb: FrameRequestCallback) => setTimeout(cb, 0));
vi.stubGlobal('cancelAnimationFrame', (id: number) => clearTimeout(id));

View file

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

View file

@ -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<bar')).toBe('foo-bar');
});
it('replaces right angle bracket with dash', () => {
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('a<b>c"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('привет мир');
});
});

View file

@ -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');
});
});

View file

@ -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": ""
}
]
}

15
tests/vitest.config.ts Normal file
View file

@ -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'),
},
},
});

View file

@ -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/"]
}

750
yarn.lock
View file

@ -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"
@ -339,10 +569,12 @@
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.24.tgz#4ae334fc62c0e915ca8ed8e35dcc6d4eeb29215f"
integrity sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==
"@types/node@^16.11.6":
version "16.18.126"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.126.tgz#27875faa2926c0f475b39a8bb1e546c0176f8d4b"
integrity sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw==
"@types/node@^20.0.0":
version "20.19.43"
resolved "https://registry.npmmirror.com/@types/node/-/node-20.19.43.tgz#fcecf580ba42a0db55cf404c372c97973c376c97"
integrity sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==
dependencies:
undici-types "~6.21.0"
"@types/tern@*":
version "0.23.9"
@ -447,6 +679,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 +771,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 +800,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 +853,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 +872,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 +895,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 +1050,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 +1124,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 +1161,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 +1205,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 +1281,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 +1367,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 +1401,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 +1419,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 +1450,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 +1467,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 +1501,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 +1516,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 +1552,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 +1581,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 +1636,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 +1659,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 +1713,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 +1730,16 @@ typescript@^6.0.3:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-6.0.3.tgz#90251dc007916e972786cb94d74d15b185577d21"
integrity sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==
undici-types@~6.21.0:
version "6.21.0"
resolved "https://registry.npmmirror.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb"
integrity sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==
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 +1752,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 +1829,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 +1851,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"