diff --git a/.git-hooks/sync-versions.ts b/.githooks/sync-versions.ts similarity index 52% rename from .git-hooks/sync-versions.ts rename to .githooks/sync-versions.ts index 602b7f4b..ee57cc07 100644 --- a/.git-hooks/sync-versions.ts +++ b/.githooks/sync-versions.ts @@ -3,9 +3,9 @@ * Version Sync Script * Auto-sync all sub-package versions before commit */ -import { readFileSync, writeFileSync } from 'node:fs' +import { readFileSync, writeFileSync, readdirSync, existsSync, statSync } from 'node:fs' import { execSync } from 'node:child_process' -import { resolve } from 'node:path' +import { resolve, join } from 'node:path' import process from 'node:process' interface PackageEntry { @@ -47,21 +47,74 @@ const packages: readonly PackageEntry[] = [ { path: 'doc/package.json', name: 'doc' }, ] -let changed = false +// Discover all libraries and their npm sub-packages +function discoverLibraryPackages(): PackageEntry[] { + const entries: PackageEntry[] = [] + const librariesDir = resolve('libraries') + if (!existsSync(librariesDir)) return entries + for (const lib of readdirSync(librariesDir)) { + const libDir = join(librariesDir, lib) + if (!statSync(libDir).isDirectory()) continue + const libPkg = join(libDir, 'package.json') + if (existsSync(libPkg)) { + entries.push({ path: `libraries/${lib}/package.json`, name: `lib:${lib}` }) + } + } + return entries +} -for (const pkg of packages) { - const fullPath = resolve(pkg.path) - const content = readFileSync(fullPath, 'utf-8') - const pkgJson: PackageJson = JSON.parse(content) +// Discover npm platform sub-packages under a given directory (e.g. cli/npm/) +function discoverNpmSubPackages(baseDir: string, prefix: string): PackageEntry[] { + const entries: PackageEntry[] = [] + const npmDir = resolve(baseDir, 'npm') + if (!existsSync(npmDir) || !statSync(npmDir).isDirectory()) return entries + for (const platform of readdirSync(npmDir)) { + const platformDir = join(npmDir, platform) + if (!statSync(platformDir).isDirectory()) continue + const platformPkg = join(platformDir, 'package.json') + if (existsSync(platformPkg)) { + entries.push({ path: `${baseDir}/npm/${platform}/package.json`, name: `${prefix}/${platform}` }) + } + } + return entries +} - if (pkgJson.version !== rootVersion) { - console.log(` ✓ ${pkg.name}: version ${pkgJson.version} → ${rootVersion}`) - pkgJson.version = rootVersion - changed = true +// Discover all packages under packages/ +function discoverPackagesDir(): PackageEntry[] { + const entries: PackageEntry[] = [] + const packagesDir = resolve('packages') + if (!existsSync(packagesDir)) return entries + for (const pkg of readdirSync(packagesDir)) { + const pkgDir = join(packagesDir, pkg) + if (!statSync(pkgDir).isDirectory()) continue + const pkgFile = join(pkgDir, 'package.json') + if (existsSync(pkgFile)) { + entries.push({ path: `packages/${pkg}/package.json`, name: `pkg:${pkg}` }) + } } + return entries +} + +const libraryPackages = discoverLibraryPackages() +const packagesPackages = discoverPackagesDir() +const cliNpmPackages = discoverNpmSubPackages('cli', 'cli-napi') + +let changed = false + +for (const pkg of [...packages, ...libraryPackages, ...packagesPackages, ...cliNpmPackages]) { + const fullPath = resolve(pkg.path) + try { + const content = readFileSync(fullPath, 'utf-8').replace(/^\uFEFF/, '') + const pkgJson: PackageJson = JSON.parse(content) - if (changed) { - writeFileSync(fullPath, JSON.stringify(pkgJson, null, 2) + '\n', 'utf-8') + if (pkgJson.version !== rootVersion) { + console.log(` ✓ ${pkg.name}: version ${pkgJson.version} → ${rootVersion}`) + pkgJson.version = rootVersion + writeFileSync(fullPath, JSON.stringify(pkgJson, null, 2) + '\n', 'utf-8') + changed = true + } + } catch { + console.log(`⚠️ ${pkg.path} not found or invalid, skipping`) } } @@ -96,8 +149,7 @@ try { // Sync version field in tnmsc.example.json files const exampleConfigPaths = [ - 'cli/public/tnmsc.example.json', - 'packages/init-bundle/public/public/tnmsc.example.json', + 'libraries/init-bundle/public/public/tnmsc.example.json', ] for (const examplePath of exampleConfigPaths) { @@ -119,8 +171,19 @@ for (const examplePath of exampleConfigPaths) { if (changed) { console.log('\n📦 Versions synced, auto-staging changes...') try { + const filesToStage = [ + 'cli/package.json', + 'gui/package.json', + 'doc/package.json', + 'gui/src-tauri/Cargo.toml', + 'gui/src-tauri/tauri.conf.json', + 'libraries/init-bundle/public/public/tnmsc.example.json', + ...libraryPackages.map(p => p.path), + ...packagesPackages.map(p => p.path), + ...cliNpmPackages.map(p => p.path), + ] execSync( - 'git add cli/package.json gui/package.json doc/package.json gui/src-tauri/Cargo.toml gui/src-tauri/tauri.conf.json cli/public/tnmsc.example.json packages/init-bundle/public/public/tnmsc.example.json', + `git add ${filesToStage.join(' ')}`, { stdio: 'inherit' } ) console.log('✅ Staged modified files') diff --git a/.git-hooks/tsconfig.json b/.githooks/tsconfig.json similarity index 100% rename from .git-hooks/tsconfig.json rename to .githooks/tsconfig.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2c2fe96c..45710153 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@v4 - name: Install GTK development dependencies - run: sudo apt-get update && sudo apt-get install -y libgtk-3-dev libglib2.0-dev pkg-config + run: sudo apt-get update && sudo apt-get install -y libgtk-3-dev libglib2.0-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf pkg-config - name: Setup pnpm uses: pnpm/action-setup@v4 @@ -46,3 +46,20 @@ jobs: - name: Typecheck run: pnpm exec turbo run typecheck + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + + - name: Cache cargo + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-ci-${{ hashFiles('Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-ci- + + - name: Rust tests (excluding GUI) + run: cargo test --workspace --exclude memory-sync-gui diff --git a/.github/workflows/release-cli-binary.yml b/.github/workflows/release-cli-binary.yml new file mode 100644 index 00000000..299e8cbc --- /dev/null +++ b/.github/workflows/release-cli-binary.yml @@ -0,0 +1,140 @@ +name: Release CLI Binary + +concurrency: + group: ${{ github.workflow }}-${{ inputs.version || github.run_id }} + cancel-in-progress: true + +on: + workflow_call: + inputs: + version: + required: true + type: string + workflow_dispatch: + inputs: + version: + description: 'Version to release (without v prefix, e.g. 2026.10222.0)' + required: true + type: string + +permissions: + contents: read + +jobs: + build-cli-binary: + strategy: + fail-fast: false + matrix: + include: + - platform: ubuntu-24.04 + target: x86_64-unknown-linux-gnu + binary: tnmsc + archive: tnmsc-linux-x86_64.tar.gz + - platform: ubuntu-24.04 + target: aarch64-unknown-linux-gnu + binary: tnmsc + archive: tnmsc-linux-aarch64.tar.gz + cross: true + - platform: macos-14 + target: aarch64-apple-darwin + binary: tnmsc + archive: tnmsc-darwin-aarch64.tar.gz + - platform: macos-14 + target: x86_64-apple-darwin + binary: tnmsc + archive: tnmsc-darwin-x86_64.tar.gz + - platform: windows-latest + target: x86_64-pc-windows-msvc + binary: tnmsc.exe + archive: tnmsc-windows-x86_64.zip + + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v4 + + # 1. Build plugin-runtime.mjs first (needed for embedded-runtime feature) + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 25 + + - name: Get pnpm store directory + id: pnpm-cache + shell: bash + run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT + + - uses: actions/cache@v4 + name: Setup pnpm cache + with: + path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install & bundle plugin-runtime + shell: bash + run: | + pnpm install --frozen-lockfile + pnpm exec turbo run build --filter=@truenine/memory-sync-cli... + ls -la cli/dist/plugin-runtime.mjs + + # 2. Build Rust binary with embedded plugin-runtime.mjs + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - name: Install cross-compilation tools (aarch64-linux) + if: matrix.cross + run: | + sudo apt-get update + sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu + echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV + + - name: Cache cargo registry & target + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-${{ matrix.target }}-cargo-cli-${{ hashFiles('Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.target }}-cargo-cli- + + - name: Build tnmsc binary (release, with embedded runtime) + run: cargo build --release --target ${{ matrix.target }} -p tnmsc --features embedded-runtime + + - name: Run tests (native only) + if: ${{ !matrix.cross }} + run: cargo test --release --target ${{ matrix.target }} -p tnmsc --features embedded-runtime + + # 3. Package binary + plugin-runtime.mjs + - name: Package (unix) + if: runner.os != 'Windows' + shell: bash + run: | + mkdir -p staging + cp target/${{ matrix.target }}/release/${{ matrix.binary }} staging/ + cp cli/dist/plugin-runtime.mjs staging/ + cd staging + tar czf ../${{ matrix.archive }} * + + - name: Package (windows) + if: runner.os == 'Windows' + shell: pwsh + run: | + New-Item -ItemType Directory -Force -Path staging + Copy-Item "target/${{ matrix.target }}/release/${{ matrix.binary }}" staging/ + Copy-Item "cli/dist/plugin-runtime.mjs" staging/ + Compress-Archive -Path staging/* -DestinationPath ${{ matrix.archive }} + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: cli-${{ matrix.target }} + path: ${{ matrix.archive }} + if-no-files-found: error diff --git a/.github/workflows/release-cli-napi.yml b/.github/workflows/release-cli-napi.yml new file mode 100644 index 00000000..cf35bed8 --- /dev/null +++ b/.github/workflows/release-cli-napi.yml @@ -0,0 +1,163 @@ +name: Release CLI Napi Packages + +concurrency: + group: ${{ github.workflow }}-${{ inputs.version || github.run_id }} + cancel-in-progress: false + +on: + workflow_call: + inputs: + version: + required: true + type: string + workflow_dispatch: + inputs: + version: + description: 'Version to release (without v prefix, e.g. 2026.10222.0)' + required: true + type: string + +permissions: + contents: read + +jobs: + build-napi: + strategy: + fail-fast: false + matrix: + target: + - os: ubuntu-24.04 + rust: x86_64-unknown-linux-gnu + suffix: linux-x64-gnu + - os: ubuntu-24.04 + rust: aarch64-unknown-linux-gnu + suffix: linux-arm64-gnu + cross: true + - os: macos-14 + rust: aarch64-apple-darwin + suffix: darwin-arm64 + - os: macos-14 + rust: x86_64-apple-darwin + suffix: darwin-x64 + - os: windows-latest + rust: x86_64-pc-windows-msvc + suffix: win32-x64-msvc + + runs-on: ${{ matrix.target.os }} + steps: + - uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 25 + + - name: Get pnpm store directory + id: pnpm-cache + shell: bash + run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT + + - uses: actions/cache@v4 + name: Setup pnpm cache + with: + path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target.rust }} + + - name: Install cross-compilation tools (aarch64-linux) + if: matrix.target.cross + run: | + sudo apt-get update + sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu + echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV + + - name: Cache cargo registry & target + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-${{ matrix.target.rust }}-cargo-napi-${{ hashFiles('Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-${{ matrix.target.rust }}-cargo-napi- + + - name: Build all napi libraries + shell: bash + run: | + for lib in logger md-compiler config init-bundle; do + echo "Building napi for $lib..." + (cd "libraries/$lib" && pnpm exec napi build --platform --release --target ${{ matrix.target.rust }} --output-dir dist -- --features napi) + done + + - name: Collect .node files into CLI platform package + shell: bash + run: | + target_dir="cli/npm/${{ matrix.target.suffix }}" + for lib in logger md-compiler config init-bundle; do + cp libraries/$lib/dist/*.node "$target_dir/" + done + echo "Contents of $target_dir:" + ls -la "$target_dir/" + + - name: Upload CLI platform package + uses: actions/upload-artifact@v4 + with: + name: cli-napi-${{ matrix.target.suffix }} + path: cli/npm/${{ matrix.target.suffix }}/ + if-no-files-found: error + + publish-cli-napi: + needs: build-napi + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 25 + registry-url: https://registry.npmjs.org/ + + - name: Download all platform artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + pattern: cli-napi-* + + - name: Distribute artifacts to cli/npm/ directories + shell: bash + run: | + for artifact_dir in artifacts/cli-napi-*/; do + suffix=$(basename "$artifact_dir" | sed 's/cli-napi-//') + target_dir="cli/npm/${suffix}" + echo "Copying from ${artifact_dir} to ${target_dir}" + cp "${artifact_dir}"*.node "$target_dir/" || { echo "ERROR: no .node files found in ${artifact_dir}"; exit 1; } + done + + - name: Publish CLI platform sub-packages + shell: bash + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + for dir in cli/npm/*/; do + if [ -f "${dir}package.json" ]; then + echo "Publishing ${dir}..." + (cd "$dir" && pnpm publish --access public --no-git-checks) || echo "⚠️ Failed to publish ${dir}, may already exist" + fi + done diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index 46a0ec3e..0831db45 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -2,7 +2,7 @@ name: Release CLI concurrency: group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true + cancel-in-progress: false on: push: @@ -14,52 +14,54 @@ permissions: contents: write jobs: - release-cli: + # 1. 版本检查(快速,决定是否继续) + check-version: runs-on: ubuntu-24.04 outputs: publish: ${{ steps.check.outputs.publish }} version: ${{ steps.check.outputs.version }} steps: - uses: actions/checkout@v4 - with: - token: ${{ secrets.GH_PAT }} - name: Check if should publish id: check - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | version=$(jq -r '.version' cli/package.json) name=$(jq -r '.name' cli/package.json) - npm_version=$(npm view "$name" version 2>/dev/null || echo "") - - # 检查 GitHub Release 是否已存在 - gh_release_exists="false" - if gh release view "v${version}" &> /dev/null; then - gh_release_exists="true" - fi + npm_version=$(npm view "$name" version --registry https://registry.npmjs.org/ 2>/dev/null || echo "") - # 只有当 npm 版本不同且 GitHub Release 不存在时才发布 - if [[ "$version" != "$npm_version" && "$gh_release_exists" == "false" ]]; then - echo "Version $version not published to npm or GitHub, will publish" + if [[ "$version" != "$npm_version" ]]; then + echo "Version $version not published to npm, will publish" echo "publish=true" >> "$GITHUB_OUTPUT" echo "version=$version" >> "$GITHUB_OUTPUT" else - if [[ "$version" == "$npm_version" ]]; then - echo "Version $version already published to npm, skipping" - fi - if [[ "$gh_release_exists" == "true" ]]; then - echo "GitHub Release v${version} already exists, skipping" - fi + echo "Version $version already published to npm, skipping" echo "publish=false" >> "$GITHUB_OUTPUT" fi + # 2. 先发布架构包(用户安装主包时 optionalDependencies 已就绪) + release-cli-napi: + needs: check-version + if: needs.check-version.outputs.publish == 'true' + uses: ./.github/workflows/release-cli-napi.yml + with: + version: ${{ needs.check-version.outputs.version }} + secrets: inherit + + # 3. 架构包就绪后,发布主包到 npm + publish-cli: + needs: [check-version, release-cli-napi] + if: needs.check-version.outputs.publish == 'true' + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.GH_PAT }} + - name: Setup pnpm - if: steps.check.outputs.publish == 'true' uses: pnpm/action-setup@v4 - name: Setup Node - if: steps.check.outputs.publish == 'true' uses: actions/setup-node@v4 with: node-version: 25 @@ -67,12 +69,10 @@ jobs: cache: 'pnpm' - name: Get pnpm store directory - if: steps.check.outputs.publish == 'true' id: pnpm-cache run: echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT - uses: actions/cache@v4 - if: steps.check.outputs.publish == 'true' name: Setup pnpm store cache with: path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} @@ -81,22 +81,30 @@ jobs: ${{ runner.os }}-pnpm-store- - name: Install & Build - if: steps.check.outputs.publish == 'true' run: | pnpm install --frozen-lockfile pnpm exec turbo run build --filter=@truenine/memory-sync-cli... - name: Publish to npm - if: steps.check.outputs.publish == 'true' working-directory: ./cli run: pnpm publish --access public --no-git-checks env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + # 4. 构建 CLI 独立二进制(仅 artifact,不发 Release) + release-cli-binary: + needs: [check-version, release-cli-napi] + if: needs.check-version.outputs.publish == 'true' + uses: ./.github/workflows/release-cli-binary.yml + with: + version: ${{ needs.check-version.outputs.version }} + secrets: inherit + + # 5. 构建 GUI 并创建 GitHub Release release-gui: - needs: release-cli - if: needs.release-cli.outputs.publish == 'true' + needs: check-version + if: needs.check-version.outputs.publish == 'true' uses: ./.github/workflows/release-gui.yml with: - version: ${{ needs.release-cli.outputs.version }} + version: ${{ needs.check-version.outputs.version }} secrets: inherit diff --git a/.github/workflows/release-gui.yml b/.github/workflows/release-gui.yml index 36e63651..e86b0a2e 100644 --- a/.github/workflows/release-gui.yml +++ b/.github/workflows/release-gui.yml @@ -131,6 +131,8 @@ jobs: needs: build-gui runs-on: ubuntu-24.04 steps: + - uses: actions/checkout@v4 + - name: Download all artifacts uses: actions/download-artifact@v4 with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9745110a..b8e14f99 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,8 +38,25 @@ jobs: - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Install Rust stable + uses: dtolnay/rust-toolchain@stable + + - name: Cache cargo + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-test-${{ hashFiles('Cargo.lock') }} + restore-keys: | + ${{ runner.os }}-cargo-test- + - name: Generate Tauri icons run: pnpm -F @truenine/memory-sync-gui run generate:icons - name: Run tests run: pnpm exec turbo run test + + - name: Rust tests (excluding GUI) + run: cargo test --workspace --exclude memory-sync-gui diff --git a/.gitignore b/.gitignore index ee1fb05c..c66f4654 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,6 @@ node_modules/ dist/ *.tsbuildinfo -.DS_Store -Thumbs.db *.log coverage/ .nyc_output @@ -17,9 +15,13 @@ README.md .editorconfig .eslintcache .turbo - .tnmsc.json - -# Cargo +!libraries/init-bundle/public/.idea/ +!libraries/init-bundle/public/.idea/** +!libraries/init-bundle/public/.vscode/ +!libraries/init-bundle/public/.vscode/** +!libraries/init-bundle/public/.editorconfig +cli/npm/**/*.node +libraries/**/dist/*.node **/target/ !**/Cargo.lock diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..e1741cc1 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,5901 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "aws-lc-rs" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9a7b350e3bb1767102698302bc37256cbd48422809984b98d292c40e2579aa9" +dependencies = [ + "aws-lc-sys", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.37.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b092fe214090261288111db7a2b2c2118e5a7f30dc2569f1732c4069a6840549" +dependencies = [ + "cc", + "cmake", + "dunce", + "fs_extra", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "brotli" +version = "8.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd8b9603c7aa97359dbd97ecf258968c95f3adddd6db2f7e7a5bef101c84560" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "874bb8112abecc98cbd6d81ea4fa7e94fb9449648c93cc89aa40c81c24d7de03" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +dependencies = [ + "serde", +] + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.11.0", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629a66d692cb9ff1a1c664e41771b3dcaf961985a9774c0eb0bd1b51cf60a48" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "cargo_toml" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" +dependencies = [ + "serde", + "toml 0.9.12+spec-1.1.0", +] + +[[package]] +name = "cc" +version = "1.2.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link 0.2.1", +] + +[[package]] +name = "clap" +version = "4.5.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "clap_lex" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" + +[[package]] +name = "cmake" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75443c44cd6b379beb8c5b45d85d0773baf31cce901fe7bb252f4eff3008ef7d" +dependencies = [ + "cc", +] + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "affbf0190ed2caf063e3def54ff444b449371d55c58e513a95ab98eca50adb49" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.11.0", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.11.0", + "core-foundation", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.29.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93d03419cb5950ccfd3daf3ff1c7a36ace64609a1a8746d493df1ca0afde0fa" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "matches", + "phf 0.10.1", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ctor" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "424e0138278faeb2b401f174ad17e715c829512d74f3d1e81eb43365c2e0590e" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.117", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dispatch2" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" +dependencies = [ + "bitflags 2.11.0", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dlopen2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +dependencies = [ + "serde", +] + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dtor" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "404d02eeb088a82cfd873006cb713fe411306c7d182c344905e101fb1167d301" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "embed-resource" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55a075fc573c64510038d7ee9abc7990635863992f83ebc52c8b433b8411a02e" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 0.9.12+spec-1.1.0", + "vswhom", + "winreg", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e8918065695684b2b0702da20382d5ae6065cf3327bc2d6436bd49a71ce9f3" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "filetime" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" +dependencies = [ + "cfg-if", + "libc", + "libredox", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi", + "wasip2", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139ef39800118c7683f2fd3c98c1b23c09ae076556b435f8e9064ae108aaeeec" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", + "wasip3", +] + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.11.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "html5ever" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c" +dependencies = [ + "log", + "mac", + "markup5ever", + "match_token", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ab2d4f250c3d7b1c9fcdff1cece94ea4e2dfbec68614f7b87cb205f24ca9d11" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "pin-utils", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" +dependencies = [ + "byteorder", + "png", +] + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017" +dependencies = [ + "equivalent", + "hashbrown 0.16.1", + "serde", + "serde_core", +] + +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "iri-string" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c91338f0783edbd6195decb37bae672fd3b165faffb89bf7b9e6942f8b1a731a" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itoa" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e709f3e3d22866f9c25b3aff01af289b18422cc8b4262fb19103ee80fe513d" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "json5" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733a844dbd6fef128e98cb4487b887cb55454d92cd9994b1bafe004fabbe670c" +dependencies = [ + "serde", + "ucd-trie", +] + +[[package]] +name = "jsonptr" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.11.0", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "kuchikiki" +version = "0.8.8-speedreader" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02cb977175687f33fa4afa0c95c112b987ea1443e5a51c8f8ff27dc618270cc2" +dependencies = [ + "cssparser", + "html5ever", + "indexmap 2.13.0", + "selectors", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading 0.7.4", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.182" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libloading" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754ca22de805bb5744484a5b151a9e1a8e837d5dc232c2d7d8c2e3492edc8b60" +dependencies = [ + "cfg-if", + "windows-link 0.2.1", +] + +[[package]] +name = "libredox" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0b95e02c851351f877147b7deea7b1afb1df71b63aa5f8270716e0c5720616" +dependencies = [ + "bitflags 2.11.0", + "libc", + "redox_syscall 0.7.1", +] + +[[package]] +name = "libyml" +version = "0.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3302702afa434ffa30847a83305f0a69d6abd74293b6554c18ec85c7ef30c980" +dependencies = [ + "anyhow", + "version_check", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "markdown" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5cab8f2cadc416a82d2e783a1946388b31654d391d1c7d92cc1f03e295b1deb" +dependencies = [ + "unicode-id", +] + +[[package]] +name = "markup5ever" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18" +dependencies = [ + "log", + "phf 0.11.3", + "phf_codegen 0.11.3", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "match_token" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memory-sync-gui" +version = "2026.10223.10555" +dependencies = [ + "dirs", + "json5", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-shell", + "tauri-plugin-updater", + "tnmsc-config", + "tnmsc-logger", + "tnmsc-plugin-shared", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minisign-verify" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e856fdd13623a2f5f2f54676a4ee49502a96a80ef4a62bcedd23d52427c44d43" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", + "windows-sys 0.61.2", +] + +[[package]] +name = "muda" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01c1738382f66ed56b3b9c8119e794a2e23148ac8ea214eda86622d4cb9d415a" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "once_cell", + "png", + "serde", + "thiserror 2.0.18", + "windows-sys 0.60.2", +] + +[[package]] +name = "napi" +version = "3.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6944d0bf100571cd6e1a98a316cdca262deb6fccf8d93f5ae1502ca3fc88bd3" +dependencies = [ + "bitflags 2.11.0", + "ctor 0.6.3", + "futures", + "napi-build", + "napi-sys", + "nohash-hasher", + "rustc-hash", +] + +[[package]] +name = "napi-build" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d376940fd5b723c6893cd1ee3f33abbfd86acb1cd1ec079f3ab04a2a3bc4d3b1" + +[[package]] +name = "napi-derive" +version = "3.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c914b5e420182bfb73504e0607592cdb8e2e21437d450883077669fb72a114d" +dependencies = [ + "convert_case 0.11.0", + "ctor 0.6.3", + "napi-derive-backend", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "napi-derive-backend" +version = "5.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0864cf6a82e2cfb69067374b64c9253d7e910e5b34db833ed7495dda56ccb18" +dependencies = [ + "convert_case 0.11.0", + "proc-macro2", + "quote", + "semver", + "syn 2.0.117", +] + +[[package]] +name = "napi-sys" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eb602b84d7c1edae45e50bbf1374696548f36ae179dfa667f577e384bb90c2b" +dependencies = [ + "libloading 0.9.0", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.11.0", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "num-conv" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf97ec579c3c42f953ef76dbf8d55ac91fb219dde70e49aa4a6b7d74e9919050" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1207a7e20ad57b847bbddc6776b968420d38292bbfe2089accff5e19e82454c" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff32365de1b6743cb203b710788263c44a03de03802daf96092f2da4fe6ba4d7" +dependencies = [ + "proc-macro-crate 3.4.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "objc2" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c2599ce0ec54857b29ce62166b0ed9b4f6f1a70ccc9a71165b6154caca8c05" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.11.0", + "block2", + "libc", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-text", + "objc2-core-video", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.11.0", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.11.0", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-core-video" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-io-surface", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.11.0", + "block2", + "libc", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-javascript-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a1e6550c4caed348956ce3370c9ffeca70bb1dbed4fa96112e7c6170e074586" +dependencies = [ + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-osa-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f112d1746737b0da274ef79a23aac283376f335f4095a083a267a082f21db0c0" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-security" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe137109bd1e8b5a99390f77a7d8b2961dafc1a1c5db8f2e60329ad6d895a" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.11.0", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" +dependencies = [ + "bitflags 2.11.0", + "block2", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "objc2-javascript-core", + "objc2-security", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "open" +version = "5.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43bb73a7fa3799b198970490a51174027ba0d4ec504b03cd08caf513d40024bc" +dependencies = [ + "dunce", + "is-wsl", + "libc", + "pathdiff", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "os_pipe" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "osakit" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "732c71caeaa72c065bb69d7ea08717bd3f4863a4f451402fc9513e29dbd5261b" +dependencies = [ + "objc2", + "objc2-foundation", + "objc2-osa-kit", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "pathdiff" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_shared 0.8.0", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_macros 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_macros" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.2", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plist" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07" +dependencies = [ + "base64 0.22.1", + "indexmap 2.13.0", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro-crate" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "219cb19e96be00ab2e37d6e299658a0cfa83e52429179969b0f0121b4ac46983" +dependencies = [ + "toml_edit 0.23.10+spec-1.0.0", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.38.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31" +dependencies = [ + "aws-lc-rs", + "bytes", + "getrandom 0.3.4", + "lru-slab", + "rand 0.9.2", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.18", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.60.2", +] + +[[package]] +name = "quote" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "redox_syscall" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35985aa610addc02e24fc232012c86fd11f14111180f902b67e2d5331f8ebf2b" +dependencies = [ + "bitflags 2.11.0", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.17", + "libredox", + "thiserror 2.0.18", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + +[[package]] +name = "regex-syntax" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" + +[[package]] +name = "reqwest" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04e9018c9d814e5f30cc16a0f03271aeab3571e609612d9fe78c1aa8d11c2f62" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "rustls-platform-verifier", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.11.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls" +version = "0.23.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c665f33d38cea657d9614f766881e4d510e0eda4239891eea56b4cadcf01801b" +dependencies = [ + "aws-lc-rs", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pki-types" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-platform-verifier" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d99feebc72bae7ab76ba994bb5e121b8d83d910ca40b36e0921f53becc41784" +dependencies = [ + "core-foundation", + "core-foundation-sys", + "jni", + "log", + "once_cell", + "rustls", + "rustls-native-certs", + "rustls-platform-verifier-android", + "rustls-webpki", + "security-framework", + "security-framework-sys", + "webpki-root-certs", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustls-platform-verifier-android" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" + +[[package]] +name = "rustls-webpki" +version = "0.103.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" +dependencies = [ + "aws-lc-rs", + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.117", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.11.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "selectors" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c37578180969d00692904465fb7f6b3d50b9a2b952b87c23d0e2e5cb5013416" +dependencies = [ + "bitflags 1.3.2", + "cssparser", + "derive_more", + "fxhash", + "log", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc", + "smallvec", +] + +[[package]] +name = "semver" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8bbf91e5a4d6315eee45e704372590b30e260ee83af6639d64557f51b067776" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_with" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fa237f2807440d238e0364a218270b98f767a00d3dada77b1c53ae88940e2e7" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.13.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52a8e3ca0ca629121f70ab50f95249e5a6f925cc0f6ffe8256c45b728875706c" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_yml" +version = "0.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59e2dd588bf1597a252c3b920e0143eb99b0f76e4e082f4c92ce34fbc9e71ddd" +dependencies = [ + "indexmap 2.13.0", + "itoa", + "libyml", + "memchr", + "ryu", + "serde", + "version_check", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "servo_arc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52aa42f8fdf0fed91e5ce7f23d8138441002fa31dca008acf47e6fd4721f741" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shared_child" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e362d9935bc50f019969e2f9ecd66786612daae13e8f277be7bfb66e8bed3f7" +dependencies = [ + "libc", + "sigchld", + "windows-sys 0.60.2", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "sigchld" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47106eded3c154e70176fc83df9737335c94ce22f821c32d17ed1db1f83badb1" +dependencies = [ + "libc", + "os_pipe", + "signal-hook", +] + +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "socket2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f4aa3ad99f2088c990dfa82d367e19cb29268ed67c574d10d0a4bfe71f07e0" +dependencies = [ + "libc", + "windows-sys 0.60.2", +] + +[[package]] +name = "softbuffer" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" +dependencies = [ + "bytemuck", + "js-sys", + "ndk", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle", + "redox_syscall 0.5.18", + "tracing", + "wasm-bindgen", + "web-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "swift-rs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.2", + "version-compare", +] + +[[package]] +name = "tao" +version = "0.34.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7" +dependencies = [ + "bitflags 2.11.0", + "block2", + "core-foundation", + "core-graphics", + "crossbeam-channel", + "dispatch", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "once_cell", + "parking_lot", + "raw-window-handle", + "scopeguard", + "tao-macros", + "unicode-segmentation", + "url", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tar" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "463ae8677aa6d0f063a900b9c41ecd4ac2b7ca82f0b058cc4491540e55b20129" +dependencies = [ + "anyhow", + "bytes", + "cookie", + "dirs", + "dunce", + "embed_plist", + "getrandom 0.3.4", + "glob", + "gtk", + "heck 0.5.0", + "http", + "jni", + "libc", + "log", + "mime", + "muda", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "percent-encoding", + "plist", + "raw-window-handle", + "reqwest", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror 2.0.18", + "tokio", + "tray-icon", + "url", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows", +] + +[[package]] +name = "tauri-build" +version = "2.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca7bd893329425df750813e95bd2b643d5369d929438da96d5bbb7cc2c918f74" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "toml 0.9.12+spec-1.1.0", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac423e5859d9f9ccdd32e3cf6a5866a15bedbf25aa6630bcb2acde9468f6ae3" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.117", + "tauri-utils", + "thiserror 2.0.18", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6a1bd2861ff0c8766b1d38b32a6a410f6dc6532d4ef534c47cfb2236092f59" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.117", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692a77abd8b8773e107a42ec0e05b767b8d2b7ece76ab36c6c3947e34df9f53f" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars 0.8.22", + "serde", + "serde_json", + "tauri-utils", + "toml 0.9.12+spec-1.1.0", + "walkdir", +] + +[[package]] +name = "tauri-plugin-shell" +version = "2.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8457dbf9e2bab1edd8df22bb2c20857a59a9868e79cb3eac5ed639eec4d0c73b" +dependencies = [ + "encoding_rs", + "log", + "open", + "os_pipe", + "regex", + "schemars 0.8.22", + "serde", + "serde_json", + "shared_child", + "tauri", + "tauri-plugin", + "thiserror 2.0.18", + "tokio", +] + +[[package]] +name = "tauri-plugin-updater" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fe8e9bebd88fc222938ffdfbdcfa0307081423bd01e3252fc337d8bde81fc61" +dependencies = [ + "base64 0.22.1", + "dirs", + "flate2", + "futures-util", + "http", + "infer", + "log", + "minisign-verify", + "osakit", + "percent-encoding", + "reqwest", + "rustls", + "semver", + "serde", + "serde_json", + "tar", + "tauri", + "tauri-plugin", + "tempfile", + "thiserror 2.0.18", + "time", + "tokio", + "url", + "windows-sys 0.60.2", + "zip", +] + +[[package]] +name = "tauri-runtime" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b885ffeac82b00f1f6fd292b6e5aabfa7435d537cef57d11e38a489956535651" +dependencies = [ + "cookie", + "dpi", + "gtk", + "http", + "jni", + "objc2", + "objc2-ui-kit", + "objc2-web-kit", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror 2.0.18", + "url", + "webkit2gtk", + "webview2-com", + "windows", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5204682391625e867d16584fedc83fc292fb998814c9f7918605c789cd876314" +dependencies = [ + "gtk", + "http", + "jni", + "log", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "once_cell", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcd169fccdff05eff2c1033210b9b94acd07a47e6fa9a3431cf09cfd4f01c87e" +dependencies = [ + "anyhow", + "brotli", + "cargo_metadata", + "ctor 0.2.9", + "dunce", + "glob", + "html5ever", + "http", + "infer", + "json-patch", + "kuchikiki", + "log", + "memchr", + "phf 0.11.3", + "proc-macro2", + "quote", + "regex", + "schemars 0.8.22", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.18", + "toml 0.9.12+spec-1.1.0", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1087b111fe2b005e42dbdc1990fc18593234238d47453b0c99b7de1c9ab2c1e0" +dependencies = [ + "dunce", + "embed-resource", + "toml 0.9.12+spec-1.1.0", +] + +[[package]] +name = "tempfile" +version = "3.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0136791f7c95b1f6dd99f9cc786b91bb81c3800b639b3478e561ddb7be95e5f1" +dependencies = [ + "fastrand", + "getrandom 0.4.1", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa5fdc3bce6191a1dbc8c02d5c8bffcf557bafa17c124c5264a458f1b0613fa" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tnmsc" +version = "2026.10222.0" +dependencies = [ + "clap", + "dirs", + "reqwest", + "serde", + "serde_json", + "tnmsc-config", + "tnmsc-init-bundle", + "tnmsc-input-plugins", + "tnmsc-logger", + "tnmsc-md-compiler", + "tnmsc-plugin-shared", +] + +[[package]] +name = "tnmsc-config" +version = "2026.10222.0" +dependencies = [ + "dirs", + "napi", + "napi-build", + "napi-derive", + "serde", + "serde_json", + "sha2", + "tnmsc-logger", +] + +[[package]] +name = "tnmsc-init-bundle" +version = "2026.10222.0" +dependencies = [ + "napi", + "napi-build", + "napi-derive", + "serde_json", +] + +[[package]] +name = "tnmsc-input-plugins" +version = "2026.10222.0" +dependencies = [ + "base64 0.22.1", + "glob", + "napi", + "napi-build", + "napi-derive", + "serde", + "serde_json", + "sha2", + "tnmsc-config", + "tnmsc-init-bundle", + "tnmsc-logger", + "tnmsc-md-compiler", + "tnmsc-plugin-shared", +] + +[[package]] +name = "tnmsc-logger" +version = "2026.10222.0" +dependencies = [ + "napi", + "napi-build", + "napi-derive", + "serde", + "serde_json", +] + +[[package]] +name = "tnmsc-md-compiler" +version = "2026.10222.0" +dependencies = [ + "markdown", + "napi", + "napi-build", + "napi-derive", + "regex-lite", + "serde", + "serde_json", + "serde_yml", + "tnmsc-logger", +] + +[[package]] +name = "tnmsc-plugin-shared" +version = "2026.10222.0" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tokio" +version = "1.49.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap 2.13.0", + "serde_core", + "serde_spanned 1.0.4", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.14", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.13.0", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.13.0", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.23.10+spec-1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84c8b9f757e028cee9fa244aea147aab2a9ec09d5325a9b01e0a49730c2b5269" +dependencies = [ + "indexmap 2.13.0", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "winnow 0.7.14", +] + +[[package]] +name = "toml_parser" +version = "1.0.9+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" +dependencies = [ + "winnow 0.7.14", +] + +[[package]] +name = "toml_writer" +version = "1.0.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.11.0", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tray-icon" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e85aa143ceb072062fc4d6356c1b520a51d636e7bc8e77ec94be3608e5e80c" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "once_cell", + "png", + "serde", + "thiserror 2.0.18", + "windows-sys 0.60.2", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicode-id" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ba288e709927c043cbe476718d37be306be53fb1fafecd0dbe36d072be2580" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "uuid" +version = "1.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b672338555252d43fd2240c714dc444b8c6fb0a5c5335e65a07bba7742735ddb" +dependencies = [ + "getrandom 0.4.1", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.2+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec1adf1535672f5b7824f817792b1afd731d7e843d2d04ec8f27e8cb51edd8ac" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe88540d1c934c4ec8e6db0afa536876c5441289d7f9f9123d4f065ac1250a6b" +dependencies = [ + "cfg-if", + "futures-util", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e638317c08b21663aed4d2b9a2091450548954695ff4efa75bff5fa546b3b1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c64760850114d03d5f65457e96fc988f11f01d38fbaa51b254e4ab5809102af" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60eecd4fe26177cfa3339eb00b4a36445889ba3ad37080c2429879718e20ca41" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.13.0", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasm-streams" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15053d8d85c7eccdbefef60f06769760a563c7f0a9d6902a13d35c7800b0ad65" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap 2.13.0", + "semver", +] + +[[package]] +name = "web-sys" +version = "0.3.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6bb20ed2d9572df8584f6dc81d68a41a625cadc6f15999d649a70ce7e3597a" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webpki-root-certs" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "804f18a4ac2676ffb4e8b5b5fa9ae38af06df08162314f96a68d2a363e21a8ca" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webview2-com" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows", + "windows-core 0.61.2", + "windows-implement", + "windows-interface", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "webview2-com-sys" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" +dependencies = [ + "thiserror 2.0.18", + "windows", + "windows-core 0.61.2", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window-vibrancy" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +dependencies = [ + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", + "windows-sys 0.59.0", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb" +dependencies = [ + "windows-targets 0.53.5", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.53.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" +dependencies = [ + "windows-link 0.2.1", + "windows_aarch64_gnullvm 0.53.1", + "windows_aarch64_msvc 0.53.1", + "windows_i686_gnu 0.53.1", + "windows_i686_gnullvm 0.53.1", + "windows_i686_msvc 0.53.1", + "windows_x86_64_gnu 0.53.1", + "windows_x86_64_gnullvm 0.53.1", + "windows_x86_64_msvc 0.53.1", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_i686_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5364e9d77fcdeeaa6062ced926ee3381faa2ee02d3eb83a5c27a8825540829" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck 0.5.0", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck 0.5.0", + "indexmap 2.13.0", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags 2.11.0", + "indexmap 2.13.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.13.0", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "wry" +version = "0.54.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb26159b420aa77684589a744ae9a9461a95395b848764ad12290a14d960a11a" +dependencies = [ + "base64 0.22.1", + "block2", + "cookie", + "crossbeam-channel", + "dirs", + "dpi", + "dunce", + "gdkx11", + "gtk", + "html5ever", + "http", + "javascriptcore-rs", + "jni", + "kuchikiki", + "libc", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.18", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zip" +version = "4.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa8cd6af31c3b31c6631b8f483848b91589021b28fffe50adada48d4f4d2ed1" +dependencies = [ + "arbitrary", + "crc32fast", + "indexmap 2.13.0", + "memchr", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..b3686f69 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,70 @@ +[workspace] +resolver = "2" +members = [ + "cli", + "libraries/logger", + "libraries/md-compiler", + "libraries/config", + "libraries/plugin-shared", + "libraries/input-plugins", + "libraries/init-bundle", + "gui/src-tauri", +] + +[workspace.package] +version = "2026.10222.0" +edition = "2024" +license = "AGPL-3.0-only" +authors = ["TrueNine"] +repository = "https://github.com/TrueNine/memory-sync" + +[workspace.dependencies] +# Internal crates +tnmsc-logger = { path = "libraries/logger" } +tnmsc-md-compiler = { path = "libraries/md-compiler" } +tnmsc-config = { path = "libraries/config" } +tnmsc-plugin-shared = { path = "libraries/plugin-shared" } +tnmsc-input-plugins = { path = "libraries/input-plugins" } +tnmsc-init-bundle = { path = "libraries/init-bundle" } + +# Serialization +serde = { version = "1", features = ["derive"] } +serde_json = "1" +serde_yml = "0.0.12" + +# CLI +clap = { version = "4", features = ["derive"] } + +# Filesystem & system +dirs = "6" +glob = "0.3" + +# Crypto & encoding +sha2 = "0.10" +base64 = "0.22" + +# HTTP +reqwest = { version = "0.13", features = ["blocking", "json"] } + +# Markdown / MDX +markdown = "1.0.0" + +# NAPI-RS (Node.js native addon bindings) +napi = { version = "3", features = ["napi4"] } +napi-derive = "3" +napi-build = "2" + +# Tauri +tauri = { version = "2", features = ["tray-icon"] } +tauri-build = { version = "2", features = [] } +tauri-plugin-shell = "2" +tauri-plugin-updater = "2" + +# JSON5 (for GUI log parsing compat) +json5 = "1" + +[profile.release] +strip = true +lto = true +opt-level = "z" +codegen-units = 1 diff --git a/cli/Cargo.toml b/cli/Cargo.toml new file mode 100644 index 00000000..ad360308 --- /dev/null +++ b/cli/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "tnmsc" +description = "Cross-AI-tool prompt synchronisation CLI" +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true + +[[bin]] +name = "tnmsc" +path = "src/main.rs" + +[features] +default = [] +embedded-runtime = [] + +[dependencies] +tnmsc-logger = { workspace = true } +tnmsc-md-compiler = { workspace = true } +tnmsc-config = { workspace = true } +tnmsc-plugin-shared = { workspace = true } +tnmsc-input-plugins = { workspace = true } +tnmsc-init-bundle = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +clap = { workspace = true } +dirs = { workspace = true } +reqwest = { version = "0.13", default-features = false, features = ["blocking", "json", "rustls", "rustls-native-certs"] } diff --git a/cli/build.rs b/cli/build.rs new file mode 100644 index 00000000..377f60bc --- /dev/null +++ b/cli/build.rs @@ -0,0 +1,29 @@ +use std::env; +use std::fs; +use std::path::Path; + +fn main() { + // Only process embedded-runtime when the feature is enabled + if env::var("CARGO_FEATURE_EMBEDDED_RUNTIME").is_ok() { + let out_dir = env::var("OUT_DIR").expect("OUT_DIR not set"); + let dest = Path::new(&out_dir).join("plugin-runtime.mjs"); + + // Look for plugin-runtime.mjs relative to the crate root + let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"); + let source = Path::new(&manifest_dir).join("dist/plugin-runtime.mjs"); + + if source.exists() { + fs::copy(&source, &dest).expect("Failed to copy plugin-runtime.mjs to OUT_DIR"); + println!("cargo:rerun-if-changed={}", source.display()); + } else { + // Write empty placeholder so include_str! doesn't fail + fs::write(&dest, "").expect("Failed to write empty plugin-runtime.mjs"); + println!( + "cargo:warning=plugin-runtime.mjs not found at {}. Build with 'pnpm -F @truenine/memory-sync-cli run bundle' first.", + source.display() + ); + } + + println!("cargo:rerun-if-changed=dist/plugin-runtime.mjs"); + } +} diff --git a/cli/env.d.ts b/cli/env.d.ts index ba9e0160..0acdf7e3 100644 --- a/cli/env.d.ts +++ b/cli/env.d.ts @@ -14,6 +14,6 @@ declare const __CLI_PACKAGE_NAME__: string /** * Kiro global powers registry JSON string injected at build time - * from public/kiro_global_powers_registry.json + * from init-bundle (public/kiro_global_powers_registry.json) */ declare const __KIRO_GLOBAL_POWERS_REGISTRY__: string diff --git a/cli/eslint.config.ts b/cli/eslint.config.ts index 13a6c4cf..f4cfccb6 100644 --- a/cli/eslint.config.ts +++ b/cli/eslint.config.ts @@ -19,14 +19,16 @@ const config = eslint10({ 'aindex/**', '*.md', '**/*.md', + '*.toml', + '**/*.toml', '.kiro/**', '.claude/**', '.factory/**', 'src/AGENTS.md', - 'public/**', '.skills/**', '**/.skills/**', - '.agent/**' + '.agent/**', + 'scripts/**' ] }) diff --git a/cli/npm/.gitignore b/cli/npm/.gitignore new file mode 100644 index 00000000..13391bc1 --- /dev/null +++ b/cli/npm/.gitignore @@ -0,0 +1,3 @@ +* +!.gitignore +!*/package.json diff --git a/cli/package.json b/cli/package.json index 55422638..d4051d9f 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,7 +1,7 @@ { "name": "@truenine/memory-sync-cli", "type": "module", - "version": "2026.10222.10836", + "version": "2026.10223.10555", "description": "TrueNine Memory Synchronization CLI", "author": "TrueNine", "license": "AGPL-3.0-only", @@ -40,6 +40,7 @@ }, "scripts": { "build": "run-s build:deps check bundle", + "build:napi": "tsx ../scripts/copy-napi.ts", "build:deps": "pnpm exec turbo run build --filter=...@truenine/memory-sync-cli --filter=!@truenine/memory-sync-cli", "bundle": "pnpm exec tsdown", "check": "run-p typecheck lint", @@ -54,11 +55,23 @@ "@clack/prompts": "catalog:", "fast-glob": "catalog:", "fs-extra": "catalog:", + "jiti": "2.6.1", "jsonc-parser": "catalog:", + "lightningcss": "1.31.1", "picocolors": "catalog:", "picomatch": "catalog:", + "tsx": "4.21.0", + "vitest": "^4.0.18", + "yaml": "2.8.2", "zod": "catalog:" }, + "optionalDependencies": { + "@truenine/memory-sync-cli-darwin-arm64": "workspace:*", + "@truenine/memory-sync-cli-darwin-x64": "workspace:*", + "@truenine/memory-sync-cli-linux-arm64-gnu": "workspace:*", + "@truenine/memory-sync-cli-linux-x64-gnu": "workspace:*", + "@truenine/memory-sync-cli-win32-x64-msvc": "workspace:*" + }, "devDependencies": { "@truenine/desk-paths": "workspace:*", "@truenine/init-bundle": "workspace:*", @@ -87,6 +100,7 @@ "@truenine/plugin-input-rule": "workspace:*", "@truenine/plugin-input-shadow-project": "workspace:*", "@truenine/plugin-input-shared": "workspace:*", + "@truenine/plugin-input-shared-ignore": "workspace:*", "@truenine/plugin-input-skill-sync-effect": "workspace:*", "@truenine/plugin-input-subagent": "workspace:*", "@truenine/plugin-input-vscode-config": "workspace:*", diff --git a/cli/public/.gitignore b/cli/public/.gitignore deleted file mode 100644 index b76842a9..00000000 --- a/cli/public/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# MDX 编译测试结果 -verify.testresult.mdx diff --git a/cli/public/gitignore b/cli/public/gitignore deleted file mode 100644 index 9a471f2a..00000000 --- a/cli/public/gitignore +++ /dev/null @@ -1,24 +0,0 @@ -CLAUDE.md -GEMINI.md -AGENTS.md -WARP.md - -.agent/ -.claude/ -.factory/ -.cursor/ -.codebuddy/ -.qoder/ -.windsurf/ -.kiro/ -.aiassistant/ - -.skills/ - -nul -node_modules/ - -.cursorignore -.qoderignore -.warpindexignore -.editorconfig diff --git a/cli/public/tnmsc.example.json b/cli/public/tnmsc.example.json deleted file mode 100644 index 2593e279..00000000 --- a/cli/public/tnmsc.example.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "version": "2026.10222.10836", - "workspaceDir": "~/project", - "shadowSourceProject": { - "name": "tnmsc-shadow", - "skill": { - "src": "src/skills", - "dist": "dist/skills" - }, - "fastCommand": { - "src": "src/commands", - "dist": "dist/commands" - }, - "subAgent": { - "src": "src/agents", - "dist": "dist/agents" - }, - "rule": { - "src": "src/rules", - "dist": "dist/rules" - }, - "globalMemory": { - "src": "app/global.cn.mdx", - "dist": "dist/global.mdx" - }, - "workspaceMemory": { - "src": "app/workspace.cn.mdx", - "dist": "dist/app/workspace.mdx" - }, - "project": { - "src": "app", - "dist": "dist/app" - } - }, - "logLevel": "info", - "profile": { - "name": "Your Name", - "username": "your_username", - "gender": "male", - "birthday": "1990-01-01" - } -} diff --git a/cli/public/verify.mdx b/cli/public/verify.mdx deleted file mode 100644 index aa936cf9..00000000 --- a/cli/public/verify.mdx +++ /dev/null @@ -1,607 +0,0 @@ ---- -name: mdx-syntax-verification -description: MDX 语法验证与编写指南 -keywords: - - mdx - - syntax - - verification - - guide -inclusion: manual ---- - -{/* - MDX 语法验证与示范文件 - 用于验证 memory-sync-cli 的 MDX 编译器支持的所有语法特性 - 同时作为用户编写 MDX 提示词文件的参考指南 -*/} - -export const VERSION = '1.0.0' -export const AUTHOR = 'TrueNine' - -{/* 复杂 export 语法示例 */} -export const NUMBERS = [1, 2, 3, 4, 5] -export const CONFIG = { - debug: true, - maxRetries: 3, - timeout: 5000 -} -export const NESTED = { - level1: { - level2: { - value: 'deep' - } - }, - array: ['a', 'b', 'c'] -} -export const metadata = { - category: 'documentation', - priority: 1 -} - -# MDX 语法验证与编写指南 - -本文档展示 `memory-sync-cli` 支持的所有 MDX 语法特性,同时作为编写提示词文件的参考。 -文档版本: 1.0.0,作者: TrueNine - ---- - -## 1. YAML Front Matter(元数据) - -文件顶部的 `---` 包裹区域用于定义元数据: - -```yaml ---- -name: my-prompt # 提示词名称(必填) -description: 描述信息 # 简短描述 -keywords: # 关键词列表 - - keyword1 - - keyword2 -inclusion: manual # 包含方式: manual | fileMatch | always -fileMatchPattern: '*.ts' # 文件匹配模式(inclusion: fileMatch 时使用) ---- -``` - -### 支持的 Front Matter 字段 - -| 字段 | 类型 | 说明 | -|------|------|------| -| `name` | string | 提示词唯一标识 | -| `description` | string | 简短描述 | -| `keywords` | string[] | 搜索关键词 | -| `inclusion` | enum | 包含策略 | -| `fileMatchPattern` | string | glob 匹配模式 | - ---- - -## 2. ESM Export 语法 - -MDX 支持 JavaScript/TypeScript 的 export 语法,导出的值会被提取到元数据中: - -### 2.1 基础导出 - -```mdx -export const CONFIG_NAME = 'my-config' -export const ENABLED = true -export const COUNT = 42 -``` - -### 2.2 数组导出 - -```mdx -export const FEATURES = ['feature1', 'feature2'] -export const NUMBERS = [1, 2, 3, 4, 5] -``` - -### 2.3 对象导出 - -```mdx -export const CONFIG = { - debug: true, - maxRetries: 3, - timeout: 5000 -} -``` - -### 2.4 嵌套结构 - -```mdx -export const NESTED = { - level1: { - level2: { - value: 'deep' - } - }, - array: ['a', 'b', 'c'] -} -``` - -### 2.5 metadata 特殊处理 - -名为 `metadata` 的导出会被展开合并到元数据中: - -```mdx -export const metadata = { - category: 'documentation', - priority: 1 -} -{/* 等效于分别导出 category 和 priority */} -``` - -### 2.6 支持的值类型 - -- 字符串: `"hello"`, `'world'`, `` `template` `` -- 数字: `42`, `3.14`, `-10` -- 布尔: `true`, `false` -- null: `null` -- 数组: `[1, 2, 3]` -- 对象: `{ key: "value" }` - -**注意**: 不支持函数调用、变量引用(除 scope 中的)、模板字符串表达式等动态值。 - ---- - -## 3. 表达式插值 `{expression}` - -### 3.1 简单变量引用 - -使用花括号插入变量值: - -- 用户名: {profile.name} -- 用户昵称: {profile.username} -- 当前平台: {os.platform} -- 系统架构: {os.arch} -- Shell 类型: {os.shellKind} -- 操作系统: {os.kind} - -### 3.2 工具名称引用 - -不同 AI 工具有不同的工具命名,使用 `tool.*` 统一引用: - -- 网页搜索: {tool.websearch} -- 网页抓取: {tool.webfetch} -- 读取文件: {tool.readFile} -- 写入文件: {tool.writeFile} -- 执行命令: {tool.executeCommand} -- 代码搜索: {tool.grep} - -### 3.3 复杂表达式 - -支持 JavaScript 表达式求值: - - -```mdx -{/* 算术运算 */} -{1 + 2 + 3} - -{/* 字符串拼接 */} -{"Hello" + " " + "World"} - -{/* 三元表达式 */} -{os.kind === 'linux' ? 'Linux 系统' : '其他系统'} - -{/* 比较运算 */} -{5 > 3} - -{/* 逻辑运算 */} -{true && false} - -{/* 方法调用 */} -{profile.name?.toUpperCase()} - -{/* 数组访问 */} -{['first', 'second', 'third'][0]} -``` - -### 3.4 环境变量 - -通过 `env.*` 访问环境变量: - -- NODE_ENV: {env.NODE_ENV} -- DEBUG: {env.DEBUG} - ---- - -## 4. 内置组件 - -### 4.1 `` 组件 - 条件渲染 - -`` 组件用于包裹 Markdown 内容,支持条件渲染: - -#### 基础用法(直接透传) - -```mdx - - # 这段内容会直接输出 - 无条件包含的内容 - -``` - -#### 条件渲染(when 属性) - -```mdx -{/* 字符串条件 */} - - 这段内容会显示 - - - - 这段内容不会显示 - - -{/* 表达式条件 */} - - ## Linux 专属配置 - 这段内容仅在 Linux 系统上显示 - - - - ## macOS 专属配置 - 这段内容仅在 macOS 系统上显示 - - - - ## Windows 专属配置 - 这段内容仅在 Windows 系统上显示 - -``` - -#### 实际示例 - - - -### Linux 用户须知 - -你正在使用 Linux 系统,推荐使用以下命令: - -```bash -# 安装依赖 -sudo apt-get install build-essential - -# 设置权限 -chmod +x ./script.sh -``` - - - - - -### macOS 用户须知 - -你正在使用 macOS,推荐使用 Homebrew: - -```bash -# 安装 Homebrew -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -``` - - - - - -### Windows 用户须知 - -你正在使用 Windows,推荐使用 PowerShell: - -```powershell -# 设置执行策略 -Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser -``` - - - -### 4.2 `` 组件 - 行内条件文本 - -`` 组件用于在行内进行条件判断并插入文本,适合需要根据条件切换单词或短语的场景: - -#### 基础用法 - -```mdx -使用 PowerShell终端 执行命令 -``` - -#### 多条件切换 - -```mdx -系统: WindowsmacOSLinux -``` - -#### 实际示例 - -请打开 PowerShellTerminal.app终端 并执行以下命令。 - -当前系统为 WindowsmacOSLinux,推荐使用 BashZshFishPowerShellCMD 作为默认 Shell。 - -#### 与 `` 的区别 - -| 特性 | `` | `` | -|------|--------|-------------| -| 输出类型 | 块级内容 | 行内文本 | -| 支持嵌套 Markdown | ✅ | ❌ | -| 适用场景 | 多行内容、代码块 | 单词、短语替换 | - ---- - -## 5. 标准 Markdown 语法 - -### 5.1 标题层级 - -```markdown -# 一级标题 -## 二级标题 -### 三级标题 -#### 四级标题 -##### 五级标题 -###### 六级标题 -``` - -### 5.2 文本格式 - -- **粗体文本** 使用 `**text**` -- *斜体文本* 使用 `*text*` -- ~~删除线~~ 使用 `~~text~~` -- `行内代码` 使用反引号 -- ***粗斜体*** 使用 `***text***` - -### 5.3 列表 - -#### 无序列表 - -- 项目一 -- 项目二 - - 嵌套项目 A - - 嵌套项目 B -- 项目三 - -#### 有序列表 - -1. 第一步 -2. 第二步 - 1. 子步骤 2.1 - 2. 子步骤 2.2 -3. 第三步 - -#### 任务列表(GFM) - -- [x] 已完成任务 -- [ ] 未完成任务 -- [ ] 待办事项 - -### 5.4 引用块 - -> 这是一段引用 -> 可以跨多行 -> -> > 嵌套引用也支持 - -### 5.5 代码块 - -#### 带语法高亮 - -```typescript -interface Plugin { - readonly name: string - readonly type: PluginKind - execute(): Promise -} -``` - -```python -def hello(name: str) -> str: - return f"Hello, {name}!" -``` - -```json -{ - "name": "example", - "version": "1.0.0" -} -``` - -### 5.6 表格(GFM) - -| 左对齐 | 居中对齐 | 右对齐 | -|:-------|:--------:|-------:| -| 内容1 | 内容2 | 内容3 | -| A | B | C | - -### 5.7 链接与图片 - -- [外部链接](https://example.com) -- [带标题的链接](https://example.com "链接标题") -- ![图片替代文本](https://via.placeholder.com/150 "图片标题") - -### 5.8 水平分割线 - ---- - -*** - -___ - -### 5.9 脚注(GFM) - -这是一段带脚注的文本[^1]。 - -[^1]: 这是脚注内容。 - ---- - -## 6. JSX 注释 - -MDX 中使用 JSX 风格注释,编译时会被移除: - -```mdx -{/* 这是单行注释 */} - -{/* - 这是多行注释 - 可以跨越多行 -*/} -``` - -{/* 这段注释不会出现在最终输出中 */} - ---- - -## 7. 全局作用域参考 - -### 7.1 `profile` - 用户信息 - -```typescript -interface UserProfile { - name?: string // 用户名 - username?: string // 昵称 - gender?: string // 性别 - birthday?: string // 生日 - [key: string]: unknown -} -``` - -### 7.2 `tool` - 工具名称映射 - -```typescript -interface ToolReferences { - websearch?: string // 网页搜索 - webfetch?: string // 网页抓取 - readFile?: string // 读取文件 - writeFile?: string // 写入文件 - executeCommand?: string // 执行命令 - todolistWrite?: string // 待办写入 - grep?: string // 代码搜索 -} -``` - -不同 AI 工具的预设: - -| 工具 | readFile | writeFile | executeCommand | -|------|----------|-----------|----------------| -| Default | read_file | write_file | execute_command | -| Claude Code | Read | Write | Execute | -| Kiro | readFile | fsWrite | executeBash | - -### 7.3 `os` - 操作系统信息 - -```typescript -interface OsInfo { - platform?: string // 平台标识 - arch?: string // CPU 架构 - hostname?: string // 主机名 - homedir?: string // 用户目录 - tmpdir?: string // 临时目录 - type?: string // 系统类型 - release?: string // 系统版本 - shellKind?: ShellKind // Shell 类型 - kind?: OsKind // 简化系统类型 -} - -enum OsKind { - Win = 'win', - Mac = 'mac', - Linux = 'linux', - Unknown = 'unknown' -} - -enum ShellKind { - Bash = 'bash', - Zsh = 'zsh', - Fish = 'fish', - PowerShell = 'powershell', - Cmd = 'cmd' -} -``` - -### 7.4 `env` - 环境变量 - -访问系统环境变量: - -```mdx -{env.NODE_ENV} -{env.HOME} -{env.PATH} -``` - ---- - -## 8. 最佳实践 - -### 8.1 文件命名 - -- 使用 `.mdx` 扩展名 -- 使用 kebab-case 命名: `my-prompt-file.mdx` -- 中文本地化文件: `my-prompt.cn.mdx` - -### 8.2 Front Matter 规范 - -```yaml ---- -name: unique-identifier # 必填,唯一标识 -description: 简短描述 # 推荐,便于理解 -keywords: # 推荐,便于搜索 - - relevant - - keywords ---- -``` - -### 8.3 条件内容组织 - -```mdx -{/* 通用内容放在最前面 */} -# 通用指南 - -这些内容适用于所有平台。 - -{/* 平台特定内容使用 包裹 */} - -## Linux 特定内容 - - - -## macOS 特定内容 - -``` - -### 8.4 工具引用 - -始终使用 `tool.*` 而非硬编码工具名: - -```mdx -{/* ✅ 推荐 */} -使用 {tool.readFile} 读取文件内容 - -{/* ❌ 避免 */} -使用 read_file 读取文件内容 -``` - -### 8.5 表达式安全 - -```mdx -{/* ✅ 使用可选链避免空值错误 */} -{profile.name?.toUpperCase()} - -{/* ✅ 提供默认值 */} -{profile.name ?? 'Unknown User'} -``` - ---- - -## 9. 验证清单 - -使用此文件验证 MDX 编译器时,检查以下功能: - -- [ ] YAML Front Matter 正确解析 -- [ ] ESM export 语句正确处理 -- [ ] 简单变量插值 `{variable}` -- [ ] 属性访问 `{obj.prop}` -- [ ] 复杂表达式求值 -- [ ] `` 组件基础渲染 -- [ ] `` 字符串条件 -- [ ] `` 表达式条件 -- [ ] JSX 注释正确移除 -- [ ] GFM 表格渲染 -- [ ] GFM 任务列表渲染 -- [ ] 代码块语法高亮标记保留 -- [ ] 嵌套列表正确缩进 - ---- - -*本文档由 memory-sync-cli 项目维护* diff --git a/cli/public/verify.test.ts b/cli/public/verify.test.ts deleted file mode 100644 index a400e889..00000000 --- a/cli/public/verify.test.ts +++ /dev/null @@ -1,186 +0,0 @@ -/** - * MDX 语法验证测试 - * 编译 verify.mdx 并输出结果到 verify.testresult.mdx - */ - -import { mdxToMd } from '@truenine/md-compiler' -import type { MdxGlobalScope } from '@truenine/md-compiler/globals' -import * as fs from 'node:fs' -import * as path from 'node:path' -import { fileURLToPath } from 'node:url' -import { describe, expect, it } from 'vitest' - -const __dirname = path.dirname(fileURLToPath(import.meta.url)) -const VERIFY_INPUT = path.join(__dirname, 'verify.mdx') -const VERIFY_OUTPUT = path.join(__dirname, 'verify.testresult.mdx') - -// 模拟全局作用域 -const mockGlobalScope: MdxGlobalScope = { - profile: { - name: 'TrueNine', - username: 'truenine', - gender: 'male', - birthday: '1990-01-01', - }, - tool: { - websearch: 'web_search', - webfetch: 'web_fetch', - readFile: 'read_file', - writeFile: 'write_file', - executeCommand: 'execute_command', - todolistWrite: 'todolist_write', - grep: 'grep_search', - }, - os: { - platform: 'linux', - arch: 'x64', - hostname: 'dev-machine', - homedir: '/home/truenine', - tmpdir: '/tmp', - type: 'Linux', - release: '6.1.0', - shellKind: 'bash' as const, - kind: 'linux' as const, - }, - env: { - NODE_ENV: 'test', - DEBUG: 'true', - HOME: '/home/truenine', - }, -} - -describe('verify.mdx compilation', () => { - it('should compile verify.mdx and output to verify.testresult.mdx', async () => { - // 读取源文件 - const sourceContent = fs.readFileSync(VERIFY_INPUT, 'utf-8') - expect(sourceContent).toBeTruthy() - - // 编译 MDX - const result = await mdxToMd(sourceContent, { - globalScope: mockGlobalScope, - extractMetadata: true, - basePath: __dirname, - }) - - // 验证编译结果 - expect(result.content).toBeTruthy() - expect(result.metadata).toBeDefined() - - // 验证元数据提取 - expect(result.metadata.fields).toHaveProperty('name', 'mdx-syntax-verification') - expect(result.metadata.fields).toHaveProperty('description') - expect(result.metadata.fields).toHaveProperty('keywords') - - // 验证复杂 export 提取 - expect(result.metadata.fields).toHaveProperty('VERSION', '1.0.0') - expect(result.metadata.fields).toHaveProperty('AUTHOR', 'TrueNine') - expect(result.metadata.fields).toHaveProperty('NUMBERS') - expect(result.metadata.fields['NUMBERS']).toEqual([1, 2, 3, 4, 5]) - expect(result.metadata.fields).toHaveProperty('CONFIG') - expect(result.metadata.fields['CONFIG']).toEqual({ debug: true, maxRetries: 3, timeout: 5000 }) - expect(result.metadata.fields).toHaveProperty('NESTED') - // metadata 对象会被展开 - expect(result.metadata.fields).toHaveProperty('category', 'documentation') - expect(result.metadata.fields).toHaveProperty('priority', 1) - - // 验证表达式求值 - expect(result.content).toContain('TrueNine') // profile.name - expect(result.content).toContain('truenine') // profile.username - expect(result.content).toContain('linux') // os.platform - expect(result.content).toContain('x64') // os.arch - expect(result.content).toContain('bash') // os.shellKind - - // 验证工具名称替换 - expect(result.content).toContain('web_search') // tool.websearch - expect(result.content).toContain('read_file') // tool.readFile - - // 验证条件渲染 - Linux 内容应该存在 - expect(result.content).toContain('Linux 用户须知') - expect(result.content).toContain('sudo apt-get') - - // 验证条件渲染 - macOS/Windows 内容不应该存在(实际示例部分) - expect(result.content).not.toContain('macOS 用户须知') - expect(result.content).not.toContain('Windows 用户须知') - expect(result.content).not.toContain('安装 Homebrew') // macOS 示例中的具体命令 - expect(result.content).not.toContain('Set-ExecutionPolicy') // Windows 示例中的具体命令 - - // 验证 JSX 注释被正确移除(不在代码块中的注释) - expect(result.content).not.toContain('这段注释不会出现在最终输出中') - expect(result.content).not.toContain('MDX 语法验证与示范文件') - - // 验证 export 语句被移除(元数据提取时) - expect(result.content).not.toContain('export const VERSION') - expect(result.content).not.toContain('export const AUTHOR') - - // 验证 export 的值被提取到 metadata - expect(result.metadata.fields).toHaveProperty('VERSION', '1.0.0') - expect(result.metadata.fields).toHaveProperty('AUTHOR', 'TrueNine') - - // 写入结果文件 - const outputContent = buildOutputContent(result.content, result.metadata) - fs.writeFileSync(VERIFY_OUTPUT, outputContent, 'utf-8') - - // 验证输出文件已创建 - expect(fs.existsSync(VERIFY_OUTPUT)).toBe(true) - - console.log(`✅ 编译成功,结果已写入: ${VERIFY_OUTPUT}`) - }) - - it('should handle all markdown syntax correctly', async () => { - const sourceContent = fs.readFileSync(VERIFY_INPUT, 'utf-8') - const result = await mdxToMd(sourceContent, { - globalScope: mockGlobalScope, - extractMetadata: true, - }) - - // 验证标准 Markdown 语法保留 - expect(result.content).toContain('# MDX 语法验证与编写指南') - expect(result.content).toContain('## ') - expect(result.content).toContain('### ') - expect(result.content).toContain('**') - expect(result.content).toContain('*') - expect(result.content).toContain('```') - expect(result.content).toContain('|') - expect(result.content).toContain('- ') - expect(result.content).toContain('1. ') - expect(result.content).toContain('> ') - expect(result.content).toContain('[') - expect(result.content).toContain('](') - expect(result.content).toContain('---') - }) - - it('should handle GFM extensions', async () => { - const sourceContent = fs.readFileSync(VERIFY_INPUT, 'utf-8') - const result = await mdxToMd(sourceContent, { - globalScope: mockGlobalScope, - extractMetadata: true, - }) - - // 验证 GFM 表格 - expect(result.content).toMatch(/\|.*\|.*\|/) - - // 验证任务列表(markdown 输出中方括号被转义) - expect(result.content).toContain('[x]') - expect(result.content).toContain('[ ]') - - // 验证删除线 - expect(result.content).toContain('~~') - }) -}) - -/** - * 构建输出内容,包含编译信息头 - */ -function buildOutputContent(content: string, metadata: { fields: Record, source: string }): string { - const header = `--- -# 编译结果文件 -# 源文件: verify.mdx -# 编译时间: ${new Date().toISOString()} -# 元数据来源: ${metadata.source} -compiled: true -originalName: ${metadata.fields['name'] ?? 'unknown'} ---- - -` - return header + content -} diff --git a/cli/src/bridge/mod.rs b/cli/src/bridge/mod.rs new file mode 100644 index 00000000..ab8b3f48 --- /dev/null +++ b/cli/src/bridge/mod.rs @@ -0,0 +1,3 @@ +//! Node.js bridge — spawns Node.js child process for plugin runtime commands. + +pub mod node; diff --git a/cli/src/bridge/node.rs b/cli/src/bridge/node.rs new file mode 100644 index 00000000..66c3d91d --- /dev/null +++ b/cli/src/bridge/node.rs @@ -0,0 +1,350 @@ +//! Node.js process spawning for plugin runtime commands. +//! +//! Locates the bundled JS entry point and spawns `node` to execute +//! plugin-dependent commands (execute, dry-run, clean, plugins). + +use std::path::PathBuf; +use std::process::{Command, ExitCode, Stdio}; + +use tnmsc_logger::create_logger; +use serde_json::Value; + +/// Strip Windows extended-length path prefix (`\\?\`) which Node.js cannot handle. +fn strip_win_prefix(path: PathBuf) -> PathBuf { + let s = path.to_string_lossy(); + if let Some(stripped) = s.strip_prefix(r"\\?\") { + PathBuf::from(stripped) + } else { + path + } +} + +const PACKAGE_NAME: &str = "@truenine/memory-sync-cli"; + +/// Locate the plugin runtime JS entry point. +/// +/// Search order: +/// 1. `/plugin-runtime.mjs` (release archive: binary + JS co-located) +/// 2. `/../dist/plugin-runtime.mjs` (dev mode: cli/dist/) +/// 3. `/../cli/dist/plugin-runtime.mjs` (dev mode from repo root) +/// 4. `/dist/plugin-runtime.mjs` (fallback) +/// 5. `/cli/dist/plugin-runtime.mjs` (fallback from repo root cwd) +/// 6. npm/pnpm global install: `/@truenine/memory-sync-cli/dist/plugin-runtime.mjs` +/// 7. Embedded JS extracted to `~/.aindex/.cache/plugin-runtime-.mjs` +fn find_plugin_runtime() -> Option { + let mut candidates: Vec = Vec::new(); + + // Relative to binary location + if let Ok(exe) = std::env::current_exe() { + if let Some(exe_dir) = exe.parent() { + candidates.push(exe_dir.join("plugin-runtime.mjs")); + candidates.push(exe_dir.join("../dist/plugin-runtime.mjs")); + candidates.push(exe_dir.join("../cli/dist/plugin-runtime.mjs")); + } + } + + // Relative to CWD + if let Ok(cwd) = std::env::current_dir() { + candidates.push(cwd.join("dist/plugin-runtime.mjs")); + candidates.push(cwd.join("cli/dist/plugin-runtime.mjs")); + } + + // npm/pnpm global package locations + for global_root in find_npm_global_roots() { + candidates.push(global_root.join(PACKAGE_NAME).join("dist/plugin-runtime.mjs")); + } + + for candidate in &candidates { + let normalized = candidate.canonicalize().ok().unwrap_or_else(|| candidate.clone()); + if normalized.exists() { + return Some(strip_win_prefix(normalized)); + } + } + + // Last resort: extract embedded JS to cache + extract_embedded_runtime() +} + +/// Find pnpm/npm global node_modules roots. +fn find_npm_global_roots() -> Vec { + let mut roots = Vec::new(); + + // `pnpm root -g` output (preferred) + if let Some(path) = run_silent("pnpm", &["root", "-g"]) { + roots.push(PathBuf::from(path)); + } + + // `npm root -g` output + if let Some(path) = run_silent("npm", &["root", "-g"]) { + roots.push(PathBuf::from(path)); + } + + // Common fallback locations (pnpm first) + if let Some(home) = dirs::home_dir() { + roots.push(home.join("AppData/Local/pnpm/global/5/node_modules")); + roots.push(home.join("AppData/Local/pnpm/global/node_modules")); + roots.push(home.join(".local/share/pnpm/global/5/node_modules")); + roots.push(home.join(".local/share/pnpm/global/node_modules")); + roots.push(home.join("AppData/Roaming/npm/node_modules")); + roots.push(home.join(".npm-global/lib/node_modules")); + } + + // nvm-managed node paths + #[cfg(not(windows))] + if let Some(home) = dirs::home_dir() { + let nvm_dir = home.join(".nvm/versions/node"); + if let Ok(entries) = std::fs::read_dir(&nvm_dir) { + for entry in entries.flatten() { + roots.push(entry.path().join("lib/node_modules")); + } + } + } + + roots +} + +/// Run a command silently and return trimmed stdout. +fn run_silent(cmd: &str, args: &[&str]) -> Option { + Command::new(cmd) + .args(args) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .output() + .ok() + .and_then(|o| { + if o.status.success() { + String::from_utf8(o.stdout).ok().map(|s| s.trim().to_string()) + } else { + None + } + }) + .filter(|s| !s.is_empty()) +} + +/// Embedded plugin-runtime.mjs content (set by build.rs, empty if not available). +/// This allows the standalone binary to work without an external JS file. +#[cfg(feature = "embedded-runtime")] +const EMBEDDED_RUNTIME: &str = include_str!(concat!(env!("OUT_DIR"), "/plugin-runtime.mjs")); + +#[cfg(not(feature = "embedded-runtime"))] +const EMBEDDED_RUNTIME: &str = ""; + +/// Extract embedded JS to `~/.aindex/.cache/plugin-runtime-.mjs`. +fn extract_embedded_runtime() -> Option { + if EMBEDDED_RUNTIME.is_empty() { + return None; + } + + let version = env!("CARGO_PKG_VERSION"); + let cache_dir = dirs::home_dir()?.join(".aindex/.cache"); + let cache_file = cache_dir.join(format!("plugin-runtime-{version}.mjs")); + + // Already extracted and up-to-date + if cache_file.exists() { + return Some(cache_file); + } + + // Extract + std::fs::create_dir_all(&cache_dir).ok()?; + std::fs::write(&cache_file, EMBEDDED_RUNTIME).ok()?; + Some(cache_file) +} + +/// Find the `node` executable. +fn find_node() -> Option { + // Try `node` in PATH + if Command::new("node").arg("--version").stdout(Stdio::null()).stderr(Stdio::null()).status().is_ok() { + return Some("node".to_string()); + } + None +} + +/// Run a Node.js plugin runtime command. +/// +/// Spawns: `node [--json] [extra_args...]` +/// Inherits stdin/stdout/stderr so the Node.js process output goes directly to terminal. +pub fn run_node_command(subcommand: &str, json_mode: bool, extra_args: &[&str]) -> ExitCode { + let logger = create_logger("NodeBridge", None); + + // Find node + let node = match find_node() { + Some(n) => n, + None => { + logger.error( + Value::String("Node.js not found in PATH. Plugin commands require Node.js.".into()), + None, + ); + return ExitCode::FAILURE; + } + }; + + // Find plugin runtime + let runtime_path = match find_plugin_runtime() { + Some(p) => p, + None => { + logger.error( + Value::String("Plugin runtime not found. Install via 'pnpm add -g @truenine/memory-sync-cli' or place plugin-runtime.mjs next to the binary.".into()), + None, + ); + logger.debug( + Value::String("Searched: binary dir, CWD, npm/pnpm global, embedded cache".into()), + None, + ); + return ExitCode::FAILURE; + } + }; + + logger.debug( + Value::String("spawning node process".into()), + Some(serde_json::json!({ + "node": &node, + "runtime": runtime_path.to_string_lossy(), + "subcommand": subcommand, + "json": json_mode + })), + ); + + let mut cmd = Command::new(&node); + cmd.arg(&runtime_path); + cmd.arg(subcommand); + + if json_mode { + cmd.arg("--json"); + } + + for arg in extra_args { + cmd.arg(arg); + } + + // Inherit stdio so Node.js output goes directly to terminal + cmd.stdin(Stdio::inherit()); + cmd.stdout(Stdio::inherit()); + cmd.stderr(Stdio::inherit()); + + match cmd.status() { + Ok(status) => { + if status.success() { + ExitCode::SUCCESS + } else { + ExitCode::from(status.code().unwrap_or(1) as u8) + } + } + Err(e) => { + logger.error( + Value::String("failed to spawn node process".into()), + Some(serde_json::json!({"error": e.to_string()})), + ); + ExitCode::FAILURE + } + } +} + +/// Run the fallback: spawn `node ` with full process.argv passthrough. +/// Used when plugin-runtime.mjs is not available but index.mjs is. +#[allow(dead_code)] +pub fn run_node_fallback(args: &[String]) -> ExitCode { + let logger = create_logger("NodeBridge", None); + + let node = match find_node() { + Some(n) => n, + None => { + logger.error( + Value::String("Node.js not found in PATH.".into()), + None, + ); + return ExitCode::FAILURE; + } + }; + + // Find index.mjs (the existing TS CLI entry) + let index_path = find_index_mjs(); + let runtime = match index_path { + Some(p) => p, + None => { + logger.error( + Value::String("CLI entry point not found (index.mjs). Run 'pnpm -F @truenine/memory-sync-cli build' first.".into()), + None, + ); + return ExitCode::FAILURE; + } + }; + + let mut cmd = Command::new(&node); + cmd.arg(&runtime); + for arg in args { + cmd.arg(arg); + } + cmd.stdin(Stdio::inherit()); + cmd.stdout(Stdio::inherit()); + cmd.stderr(Stdio::inherit()); + + match cmd.status() { + Ok(status) => { + if status.success() { + ExitCode::SUCCESS + } else { + ExitCode::from(status.code().unwrap_or(1) as u8) + } + } + Err(e) => { + logger.error( + Value::String("failed to spawn node process".into()), + Some(serde_json::json!({"error": e.to_string()})), + ); + ExitCode::FAILURE + } + } +} + +#[allow(dead_code)] +fn find_index_mjs() -> Option { + let candidates: Vec = { + let mut c = Vec::new(); + if let Ok(exe) = std::env::current_exe() { + if let Some(exe_dir) = exe.parent() { + c.push(exe_dir.join("index.mjs")); + c.push(exe_dir.join("../dist/index.mjs")); + c.push(exe_dir.join("../cli/dist/index.mjs")); + } + } + if let Ok(cwd) = std::env::current_dir() { + c.push(cwd.join("dist/index.mjs")); + c.push(cwd.join("cli/dist/index.mjs")); + } + c + }; + + for candidate in &candidates { + let normalized = candidate.canonicalize().ok().unwrap_or_else(|| candidate.clone()); + if normalized.exists() { + return Some(strip_win_prefix(normalized)); + } + } + None +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_strip_win_prefix_with_prefix() { + let path = PathBuf::from(r"\\?\C:\Users\test\file.mjs"); + let result = strip_win_prefix(path); + assert_eq!(result, PathBuf::from(r"C:\Users\test\file.mjs")); + } + + #[test] + fn test_strip_win_prefix_without_prefix() { + let path = PathBuf::from(r"C:\Users\test\file.mjs"); + let result = strip_win_prefix(path.clone()); + assert_eq!(result, path); + } + + #[test] + fn test_strip_win_prefix_unix_path() { + let path = PathBuf::from("/home/user/file.mjs"); + let result = strip_win_prefix(path.clone()); + assert_eq!(result, path); + } +} diff --git a/cli/src/cli.rs b/cli/src/cli.rs new file mode 100644 index 00000000..192d11eb --- /dev/null +++ b/cli/src/cli.rs @@ -0,0 +1,348 @@ +//! CLI argument parsing using clap derive API. +//! +//! Mirrors the TS `PluginPipeline.parseArgs()` + `resolveCommand()` + `resolveLogLevel()`. + +use clap::{Parser, Subcommand, Args}; + +/// Cross-AI-tool prompt synchronisation CLI +#[derive(Parser, Debug)] +#[command( + name = "tnmsc", + version = env!("CARGO_PKG_VERSION"), + about = "Memory Sync CLI — Synchronize AI memory and configuration files across projects.", + disable_help_subcommand = true, +)] +pub struct Cli { + #[command(subcommand)] + pub command: Option, + + /// Output results as JSON (suppresses all log output) + #[arg(short = 'j', long = "json", global = true)] + pub json: bool, + + /// Set log level to trace (most verbose) + #[arg(long = "trace", global = true)] + pub trace: bool, + + /// Set log level to debug + #[arg(long = "debug", global = true)] + pub debug: bool, + + /// Set log level to info + #[arg(long = "info", global = true)] + pub info: bool, + + /// Set log level to warn + #[arg(long = "warn", global = true)] + pub warn: bool, + + /// Set log level to error + #[arg(long = "error", global = true)] + pub error: bool, +} + +#[derive(Subcommand, Debug)] +pub enum CliCommand { + /// Show help message + Help, + + /// Show version information + Version, + + /// Check if CLI version is outdated against npm registry + Outdated, + + /// Initialize directory structure based on configuration + Init, + + /// Preview changes without writing files + #[command(name = "dry-run")] + DryRun, + + /// Remove all generated output files and directories + Clean(CleanArgs), + + /// Set or show configuration values + Config(ConfigArgs), + + /// List all registered plugins + Plugins, +} + +#[derive(Args, Debug)] +pub struct CleanArgs { + /// Preview cleanup without removing files + #[arg(short = 'n', long = "dry-run")] + pub dry_run: bool, +} + +#[derive(Args, Debug)] +pub struct ConfigArgs { + /// Show merged configuration as JSON + #[arg(long = "show")] + pub show: bool, + + /// Configuration key=value pairs to set + #[arg(long = "set", value_name = "KEY=VALUE")] + pub set: Vec, + + /// Positional key=value pairs + pub positional: Vec, +} + +/// Resolved log level from CLI flags. +/// When multiple flags are provided, the most verbose wins. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ResolvedLogLevel { + Trace, + Debug, + Info, + Warn, + Error, +} + +impl ResolvedLogLevel { + fn priority(self) -> u8 { + match self { + Self::Trace => 0, + Self::Debug => 1, + Self::Info => 2, + Self::Warn => 3, + Self::Error => 4, + } + } + + #[allow(dead_code)] + pub fn as_str(self) -> &'static str { + match self { + Self::Trace => "trace", + Self::Debug => "debug", + Self::Info => "info", + Self::Warn => "warn", + Self::Error => "error", + } + } + + pub fn to_logger_level(self) -> tnmsc_logger::LogLevel { + match self { + Self::Trace => tnmsc_logger::LogLevel::Trace, + Self::Debug => tnmsc_logger::LogLevel::Debug, + Self::Info => tnmsc_logger::LogLevel::Info, + Self::Warn => tnmsc_logger::LogLevel::Warn, + Self::Error => tnmsc_logger::LogLevel::Error, + } + } +} + +/// Resolve log level from CLI flags. +/// When multiple flags are set, the most verbose (lowest priority number) wins. +pub fn resolve_log_level(cli: &Cli) -> Option { + let mut levels = Vec::new(); + if cli.trace { levels.push(ResolvedLogLevel::Trace); } + if cli.debug { levels.push(ResolvedLogLevel::Debug); } + if cli.info { levels.push(ResolvedLogLevel::Info); } + if cli.warn { levels.push(ResolvedLogLevel::Warn); } + if cli.error { levels.push(ResolvedLogLevel::Error); } + + if levels.is_empty() { + return None; + } + + levels.into_iter().min_by_key(|l| l.priority()) +} + +/// Resolved command after processing CLI args. +/// Maps clap subcommands to the internal command enum used by the runner. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum ResolvedCommand { + Help, + Version, + Outdated, + Init, + Execute, + DryRun, + Clean, + DryRunClean, + Config(Vec<(String, String)>), + ConfigShow, + Plugins, +} + +/// Parse --set and positional key=value pairs into (key, value) tuples. +fn parse_key_value_pairs(args: &ConfigArgs) -> Vec<(String, String)> { + let mut pairs = Vec::new(); + + for s in &args.set { + if let Some(eq_idx) = s.find('=') { + if eq_idx > 0 { + pairs.push((s[..eq_idx].to_string(), s[eq_idx + 1..].to_string())); + } + } + } + + for s in &args.positional { + if let Some(eq_idx) = s.find('=') { + if eq_idx > 0 { + pairs.push((s[..eq_idx].to_string(), s[eq_idx + 1..].to_string())); + } + } + } + + pairs +} + +/// Resolve the command to execute from parsed CLI args. +pub fn resolve_command(cli: &Cli) -> ResolvedCommand { + match &cli.command { + None => ResolvedCommand::Execute, + Some(CliCommand::Help) => ResolvedCommand::Help, + Some(CliCommand::Version) => ResolvedCommand::Version, + Some(CliCommand::Outdated) => ResolvedCommand::Outdated, + Some(CliCommand::Init) => ResolvedCommand::Init, + Some(CliCommand::DryRun) => ResolvedCommand::DryRun, + Some(CliCommand::Clean(args)) => { + if args.dry_run { + ResolvedCommand::DryRunClean + } else { + ResolvedCommand::Clean + } + } + Some(CliCommand::Config(args)) => { + if args.show { + ResolvedCommand::ConfigShow + } else { + let pairs = parse_key_value_pairs(args); + if pairs.is_empty() { + // No key=value pairs and no --show: default to execute + ResolvedCommand::Execute + } else { + ResolvedCommand::Config(pairs) + } + } + } + Some(CliCommand::Plugins) => ResolvedCommand::Plugins, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn parse(args: &[&str]) -> Cli { + Cli::try_parse_from(args).unwrap() + } + + #[test] + fn test_no_args_defaults_to_execute() { + let cli = parse(&["tnmsc"]); + assert_eq!(resolve_command(&cli), ResolvedCommand::Execute); + } + + #[test] + fn test_help_subcommand() { + let cli = parse(&["tnmsc", "help"]); + assert_eq!(resolve_command(&cli), ResolvedCommand::Help); + } + + #[test] + fn test_version_subcommand() { + let cli = parse(&["tnmsc", "version"]); + assert_eq!(resolve_command(&cli), ResolvedCommand::Version); + } + + #[test] + fn test_outdated_subcommand() { + let cli = parse(&["tnmsc", "outdated"]); + assert_eq!(resolve_command(&cli), ResolvedCommand::Outdated); + } + + #[test] + fn test_init_subcommand() { + let cli = parse(&["tnmsc", "init"]); + assert_eq!(resolve_command(&cli), ResolvedCommand::Init); + } + + #[test] + fn test_dry_run_subcommand() { + let cli = parse(&["tnmsc", "dry-run"]); + assert_eq!(resolve_command(&cli), ResolvedCommand::DryRun); + } + + #[test] + fn test_clean_subcommand() { + let cli = parse(&["tnmsc", "clean"]); + assert_eq!(resolve_command(&cli), ResolvedCommand::Clean); + } + + #[test] + fn test_clean_dry_run() { + let cli = parse(&["tnmsc", "clean", "--dry-run"]); + assert_eq!(resolve_command(&cli), ResolvedCommand::DryRunClean); + } + + #[test] + fn test_clean_short_dry_run() { + let cli = parse(&["tnmsc", "clean", "-n"]); + assert_eq!(resolve_command(&cli), ResolvedCommand::DryRunClean); + } + + #[test] + fn test_config_show() { + let cli = parse(&["tnmsc", "config", "--show"]); + assert_eq!(resolve_command(&cli), ResolvedCommand::ConfigShow); + } + + #[test] + fn test_config_set() { + let cli = parse(&["tnmsc", "config", "workspaceDir=~/my-project"]); + assert_eq!( + resolve_command(&cli), + ResolvedCommand::Config(vec![("workspaceDir".into(), "~/my-project".into())]) + ); + } + + #[test] + fn test_config_set_flag() { + let cli = parse(&["tnmsc", "config", "--set", "logLevel=debug"]); + assert_eq!( + resolve_command(&cli), + ResolvedCommand::Config(vec![("logLevel".into(), "debug".into())]) + ); + } + + #[test] + fn test_plugins_subcommand() { + let cli = parse(&["tnmsc", "plugins"]); + assert_eq!(resolve_command(&cli), ResolvedCommand::Plugins); + } + + #[test] + fn test_json_flag() { + let cli = parse(&["tnmsc", "--json"]); + assert!(cli.json); + } + + #[test] + fn test_json_short_flag() { + let cli = parse(&["tnmsc", "-j"]); + assert!(cli.json); + } + + #[test] + fn test_log_level_trace() { + let cli = parse(&["tnmsc", "--trace"]); + assert_eq!(resolve_log_level(&cli), Some(ResolvedLogLevel::Trace)); + } + + #[test] + fn test_log_level_multiple_most_verbose_wins() { + let cli = parse(&["tnmsc", "--warn", "--debug"]); + assert_eq!(resolve_log_level(&cli), Some(ResolvedLogLevel::Debug)); + } + + #[test] + fn test_no_log_level() { + let cli = parse(&["tnmsc"]); + assert_eq!(resolve_log_level(&cli), None); + } +} diff --git a/cli/src/commands/bridge.rs b/cli/src/commands/bridge.rs new file mode 100644 index 00000000..d3d18de0 --- /dev/null +++ b/cli/src/commands/bridge.rs @@ -0,0 +1,23 @@ +use std::process::ExitCode; + +use crate::bridge::node::run_node_command; + +pub fn execute(json_mode: bool) -> ExitCode { + run_node_command("execute", json_mode, &[]) +} + +pub fn dry_run(json_mode: bool) -> ExitCode { + run_node_command("dry-run", json_mode, &[]) +} + +pub fn clean(json_mode: bool) -> ExitCode { + run_node_command("clean", json_mode, &[]) +} + +pub fn dry_run_clean(json_mode: bool) -> ExitCode { + run_node_command("clean", json_mode, &["--dry-run"]) +} + +pub fn plugins(json_mode: bool) -> ExitCode { + run_node_command("plugins", json_mode, &[]) +} diff --git a/cli/src/commands/config_cmd.rs b/cli/src/commands/config_cmd.rs new file mode 100644 index 00000000..f56fa4aa --- /dev/null +++ b/cli/src/commands/config_cmd.rs @@ -0,0 +1,72 @@ +use std::process::ExitCode; + +use tnmsc_config::ConfigLoader; +use tnmsc_logger::create_logger; + +pub fn execute(pairs: &[(String, String)]) -> ExitCode { + let logger = create_logger("config", None); + let cwd = match std::env::current_dir() { + Ok(p) => p, + Err(e) => { + logger.error( + serde_json::Value::String(format!("Failed to get current directory: {e}")), + None, + ); + return ExitCode::FAILURE; + } + }; + + let result = ConfigLoader::with_defaults().load(&cwd); + let mut config = result.config; + + for (key, value) in pairs { + match key.as_str() { + "workspaceDir" => config.workspace_dir = Some(value.clone()), + "logLevel" => config.log_level = Some(value.clone()), + _ => { + logger.warn( + serde_json::Value::String(format!("Unknown config key: {key}")), + None, + ); + } + } + } + + let config_path = tnmsc_config::get_global_config_path(); + match serde_json::to_string_pretty(&config) { + Ok(json) => { + if let Some(parent) = config_path.parent() { + let _ = std::fs::create_dir_all(parent); + } + match std::fs::write(&config_path, &json) { + Ok(()) => { + logger.info( + serde_json::Value::String(format!( + "Config saved to {}", + config_path.display() + )), + None, + ); + ExitCode::SUCCESS + } + Err(e) => { + logger.error( + serde_json::Value::String(format!( + "Failed to write config to {}: {e}", + config_path.display() + )), + None, + ); + ExitCode::FAILURE + } + } + } + Err(e) => { + logger.error( + serde_json::Value::String(format!("Failed to serialize config: {e}")), + None, + ); + ExitCode::FAILURE + } + } +} diff --git a/cli/src/commands/config_show.rs b/cli/src/commands/config_show.rs new file mode 100644 index 00000000..44f1f8bf --- /dev/null +++ b/cli/src/commands/config_show.rs @@ -0,0 +1,33 @@ +use std::process::ExitCode; + +use tnmsc_config::ConfigLoader; +use tnmsc_logger::create_logger; + +pub fn execute() -> ExitCode { + let logger = create_logger("config-show", None); + let cwd = match std::env::current_dir() { + Ok(p) => p, + Err(e) => { + logger.error( + serde_json::Value::String(format!("Failed to get current directory: {e}")), + None, + ); + return ExitCode::FAILURE; + } + }; + + let result = ConfigLoader::with_defaults().load(&cwd); + match serde_json::to_string_pretty(&result.config) { + Ok(json) => { + println!("{json}"); + ExitCode::SUCCESS + } + Err(e) => { + logger.error( + serde_json::Value::String(format!("Failed to serialize config: {e}")), + None, + ); + ExitCode::FAILURE + } + } +} diff --git a/cli/src/commands/help.rs b/cli/src/commands/help.rs new file mode 100644 index 00000000..06b57cf6 --- /dev/null +++ b/cli/src/commands/help.rs @@ -0,0 +1,28 @@ +use std::process::ExitCode; + +pub fn execute() -> ExitCode { + println!("tnmsc — Memory Sync CLI"); + println!(); + println!("USAGE:"); + println!(" tnmsc [OPTIONS] [COMMAND]"); + println!(); + println!("COMMANDS:"); + println!(" (default) Sync AI memory and configuration files"); + println!(" dry-run Preview changes without writing files"); + println!(" clean Remove all generated output files"); + println!(" init Initialize directory structure"); + println!(" config Set or show configuration values"); + println!(" plugins List all registered plugins"); + println!(" version Show version information"); + println!(" outdated Check if CLI version is outdated"); + println!(" help Show this help message"); + println!(); + println!("OPTIONS:"); + println!(" -j, --json Output results as JSON"); + println!(" --trace Set log level to trace"); + println!(" --debug Set log level to debug"); + println!(" --info Set log level to info"); + println!(" --warn Set log level to warn"); + println!(" --error Set log level to error"); + ExitCode::SUCCESS +} diff --git a/cli/src/commands/init.rs b/cli/src/commands/init.rs new file mode 100644 index 00000000..8e0095e0 --- /dev/null +++ b/cli/src/commands/init.rs @@ -0,0 +1,74 @@ +use std::process::ExitCode; + +use tnmsc_init_bundle::BUNDLES; +use tnmsc_logger::create_logger; + +pub fn execute() -> ExitCode { + let logger = create_logger("init", None); + let cwd = match std::env::current_dir() { + Ok(p) => p, + Err(e) => { + logger.error( + serde_json::Value::String(format!("Failed to get current directory: {e}")), + None, + ); + return ExitCode::FAILURE; + } + }; + + let bundles = BUNDLES; + if bundles.is_empty() { + logger.warn( + serde_json::Value::String( + "No init bundles available. Build the native library first.".into(), + ), + None, + ); + return ExitCode::SUCCESS; + } + + let mut written = 0usize; + for bundle in bundles { + let target = cwd.join(&bundle.path); + if let Some(parent) = target.parent() { + if let Err(e) = std::fs::create_dir_all(parent) { + logger.warn( + serde_json::Value::String(format!( + "Could not create directory {}: {e}", + parent.display() + )), + None, + ); + continue; + } + } + if target.exists() { + logger.debug( + serde_json::Value::String(format!("Skipping existing: {}", bundle.path)), + None, + ); + continue; + } + match std::fs::write(&target, &bundle.content) { + Ok(()) => { + logger.info( + serde_json::Value::String(format!("Created: {}", bundle.path)), + None, + ); + written += 1; + } + Err(e) => { + logger.warn( + serde_json::Value::String(format!("Failed to write {}: {e}", bundle.path)), + None, + ); + } + } + } + + logger.info( + serde_json::Value::String(format!("Init complete: {written} file(s) created")), + None, + ); + ExitCode::SUCCESS +} diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs new file mode 100644 index 00000000..81d459fb --- /dev/null +++ b/cli/src/commands/mod.rs @@ -0,0 +1,7 @@ +pub mod help; +pub mod version; +pub mod outdated; +pub mod init; +pub mod config_cmd; +pub mod config_show; +pub mod bridge; diff --git a/cli/src/commands/outdated.rs b/cli/src/commands/outdated.rs new file mode 100644 index 00000000..a8bc0ebe --- /dev/null +++ b/cli/src/commands/outdated.rs @@ -0,0 +1,34 @@ +use std::process::ExitCode; + +use tnmsc_logger::create_logger; + +pub fn execute() -> ExitCode { + let logger = create_logger("outdated", None); + let current = env!("CARGO_PKG_VERSION"); + + let output = std::process::Command::new("npm") + .args(["view", "@truenine/memory-sync-cli", "version", "--json"]) + .output(); + + match output { + Ok(out) if out.status.success() => { + let raw = String::from_utf8_lossy(&out.stdout); + let latest = raw.trim().trim_matches('"'); + if latest == current { + println!("tnmsc is up to date: {current}"); + } else { + println!("tnmsc is outdated: {current} → {latest}"); + println!("Run: npm install -g @truenine/memory-sync-cli"); + return ExitCode::from(1); + } + ExitCode::SUCCESS + } + _ => { + logger.warn( + serde_json::Value::String("Could not check npm registry for latest version".into()), + None, + ); + ExitCode::SUCCESS + } + } +} diff --git a/cli/src/commands/version.rs b/cli/src/commands/version.rs new file mode 100644 index 00000000..8321606a --- /dev/null +++ b/cli/src/commands/version.rs @@ -0,0 +1,6 @@ +use std::process::ExitCode; + +pub fn execute() -> ExitCode { + println!("{}", env!("CARGO_PKG_VERSION")); + ExitCode::SUCCESS +} diff --git a/cli/src/config.ts b/cli/src/config.ts index 6b637cd6..46df89d3 100644 --- a/cli/src/config.ts +++ b/cli/src/config.ts @@ -29,11 +29,13 @@ const DEFAULT_SHADOW_SOURCE_PROJECT: Required = { } const DEFAULT_OPTIONS: Required = { - ...DEFAULT_USER_CONFIG, + version: DEFAULT_USER_CONFIG.version ?? '0.0.0', + workspaceDir: DEFAULT_USER_CONFIG.workspaceDir ?? '~/project', + logLevel: DEFAULT_USER_CONFIG.logLevel ?? 'info', shadowSourceProject: DEFAULT_SHADOW_SOURCE_PROJECT, fastCommandSeriesOptions: {}, plugins: [] -} as Required +} /** * Convert UserConfigFile to PluginOptions @@ -205,7 +207,9 @@ export async function defineConfig(options: PluginOptions | DefineConfigOptions }) } - ensureShadowProjectConfigLink(path.join(mergedOptions.workspaceDir, mergedOptions.shadowSourceProject.name), logger) // Auto-link .tnmsc.json into shadow source project dir + if (mergedOptions.workspaceDir != null) { // Auto-link .tnmsc.json into shadow source project dir (skip if workspaceDir unavailable, e.g. CI without napi binary) + ensureShadowProjectConfigLink(path.join(mergedOptions.workspaceDir, mergedOptions.shadowSourceProject.name), logger) + } const baseCtx: Omit = { // Base context without dependencyContext, globalScope, scopeRegistry (will be provided by pipeline) logger, diff --git a/cli/src/constants.ts b/cli/src/constants.ts index ef11916a..747cb185 100644 --- a/cli/src/constants.ts +++ b/cli/src/constants.ts @@ -1,5 +1,5 @@ import type {UserConfigFile} from '@truenine/plugin-shared' -import {bundles} from '@truenine/init-bundle' +import {bundles, getDefaultConfigContent} from '@truenine/init-bundle' export const PathPlaceholders = { USER_HOME: '~', @@ -7,4 +7,5 @@ export const PathPlaceholders = { } as const type DefaultUserConfig = Readonly>> // Default user config type -export const DEFAULT_USER_CONFIG = JSON.parse(bundles['public/tnmsc.example.json'].content) as DefaultUserConfig // Imported from @truenine/init-bundle package +const _bundleContent = bundles['public/tnmsc.example.json']?.content ?? getDefaultConfigContent() +export const DEFAULT_USER_CONFIG = JSON.parse(_bundleContent) as DefaultUserConfig // Imported from @truenine/init-bundle package diff --git a/cli/src/main.rs b/cli/src/main.rs new file mode 100644 index 00000000..f33f88d7 --- /dev/null +++ b/cli/src/main.rs @@ -0,0 +1,49 @@ +//! tnmsc — Rust CLI entry point. +//! +//! Pure Rust commands: help, version, outdated, init, config, config-show +//! Bridge commands (Node.js): execute, dry-run, clean, plugins + +mod cli; +mod commands; +mod bridge; + +use std::process::ExitCode; + +use clap::Parser; +use tnmsc_logger::set_global_log_level; + +use cli::{Cli, ResolvedCommand, resolve_command, resolve_log_level}; + +fn main() -> ExitCode { + let cli = Cli::parse(); + + // Resolve and set global log level + if let Some(level) = resolve_log_level(&cli) { + set_global_log_level(level.to_logger_level()); + } + + // In JSON mode, suppress all log output + let json_mode = cli.json; + if json_mode { + set_global_log_level(tnmsc_logger::LogLevel::Silent); + } + + let command = resolve_command(&cli); + + match command { + // Pure Rust commands + ResolvedCommand::Help => commands::help::execute(), + ResolvedCommand::Version => commands::version::execute(), + ResolvedCommand::Outdated => commands::outdated::execute(), + ResolvedCommand::Init => commands::init::execute(), + ResolvedCommand::Config(pairs) => commands::config_cmd::execute(&pairs), + ResolvedCommand::ConfigShow => commands::config_show::execute(), + + // Bridge commands (delegate to Node.js plugin runtime) + ResolvedCommand::Execute => commands::bridge::execute(json_mode), + ResolvedCommand::DryRun => commands::bridge::dry_run(json_mode), + ResolvedCommand::Clean => commands::bridge::clean(json_mode), + ResolvedCommand::DryRunClean => commands::bridge::dry_run_clean(json_mode), + ResolvedCommand::Plugins => commands::bridge::plugins(json_mode), + } +} diff --git a/cli/src/plugin-runtime.ts b/cli/src/plugin-runtime.ts new file mode 100644 index 00000000..9182f7c5 --- /dev/null +++ b/cli/src/plugin-runtime.ts @@ -0,0 +1,113 @@ +import type {OutputCleanContext, OutputWriteContext} from '@truenine/plugin-shared' +/** + * Plugin Runtime Entry Point + * + * Streamlined entry for the Rust CLI binary to spawn via Node.js. + * Accepts a subcommand and flags, executes the plugin pipeline, + * and outputs results to stdout. + * + * Usage: node plugin-runtime.mjs [--json] [--dry-run] + * + * Subcommands: execute, dry-run, clean, plugins + */ +import type {Command, CommandContext} from '@/commands' +import type {PipelineConfig} from '@/config' +import * as fs from 'node:fs' +import * as path from 'node:path' +import process from 'node:process' +import {createLogger, setGlobalLogLevel} from '@truenine/plugin-shared' +import * as glob from 'fast-glob' +import { + CleanCommand, + DryRunCleanCommand, + DryRunOutputCommand, + ExecuteCommand, + JsonOutputCommand, + PluginsCommand +} from '@/commands' +import userPluginConfigPromise from './plugin.config' + +/** + * Parse runtime arguments. + * Expected: node plugin-runtime.mjs [--json] [--dry-run] + */ +function parseRuntimeArgs(argv: string[]): {subcommand: string, json: boolean, dryRun: boolean} { + const args = argv.slice(2) // Skip node and script path + let subcommand = 'execute' + let json = false + let dryRun = false + + for (const arg of args) { + if (arg === '--json' || arg === '-j') json = true + else if (arg === '--dry-run' || arg === '-n') dryRun = true + else if (!arg.startsWith('-')) subcommand = arg + } + + return {subcommand, json, dryRun} +} + +/** + * Resolve command from subcommand string. + */ +function resolveRuntimeCommand(subcommand: string, dryRun: boolean): Command { + switch (subcommand) { + case 'execute': return new ExecuteCommand() + case 'dry-run': return new DryRunOutputCommand() + case 'clean': return dryRun ? new DryRunCleanCommand() : new CleanCommand() + case 'plugins': return new PluginsCommand() + default: return new ExecuteCommand() + } +} + +async function main(): Promise { + const {subcommand, json, dryRun} = parseRuntimeArgs(process.argv) + + if (json) setGlobalLogLevel('silent') + + const userPluginConfig: PipelineConfig = await userPluginConfigPromise + + let command = resolveRuntimeCommand(subcommand, dryRun) + + if (json) { + const selfJsonCommands = new Set(['plugins']) + if (!selfJsonCommands.has(command.name)) command = new JsonOutputCommand(command) + } + + const {context, outputPlugins, userConfigOptions} = userPluginConfig + const logger = createLogger('PluginRuntime') + + const createCleanContext = (dry: boolean): OutputCleanContext => ({ + logger, + fs, + path, + glob, + collectedInputContext: context, + dryRun: dry + }) + + const createWriteContext = (dry: boolean): OutputWriteContext => ({ + logger, + fs, + path, + glob, + collectedInputContext: context, + dryRun: dry, + registeredPluginNames: [...outputPlugins].map(p => p.name) + }) + + const commandCtx: CommandContext = { + logger, + outputPlugins: [...outputPlugins], + collectedInputContext: context, + userConfigOptions, + createCleanContext, + createWriteContext + } + + await command.execute(commandCtx) +} + +main().catch((e: unknown) => { + console.error(e) + process.exit(1) +}) diff --git a/cli/src/plugin.config.ts b/cli/src/plugin.config.ts index 7572a328..4dfaeb01 100644 --- a/cli/src/plugin.config.ts +++ b/cli/src/plugin.config.ts @@ -20,6 +20,7 @@ import {ProjectPromptInputPlugin} from '@truenine/plugin-input-project-prompt' import {ReadmeMdInputPlugin} from '@truenine/plugin-input-readme' import {RuleInputPlugin} from '@truenine/plugin-input-rule' import {ShadowProjectInputPlugin} from '@truenine/plugin-input-shadow-project' +import {AIAgentIgnoreInputPlugin} from '@truenine/plugin-input-shared-ignore' import {SkillNonSrcFileSyncEffectInputPlugin} from '@truenine/plugin-input-skill-sync-effect' import {SubAgentInputPlugin} from '@truenine/plugin-input-subagent' import {VSCodeConfigInputPlugin} from '@truenine/plugin-input-vscode-config' @@ -78,6 +79,7 @@ export default defineConfig({ new ProjectPromptInputPlugin(), new ReadmeMdInputPlugin(), new GitIgnoreInputPlugin(), - new GitExcludeInputPlugin() + new GitExcludeInputPlugin(), + new AIAgentIgnoreInputPlugin() ] }) diff --git a/cli/src/versionCheck.ts b/cli/src/versionCheck.ts index 220654e6..d5db5209 100644 --- a/cli/src/versionCheck.ts +++ b/cli/src/versionCheck.ts @@ -140,7 +140,7 @@ export function logVersionCheckResult(result: VersionCheckResult, logger: ILogge switch (status) { case 'outdated': logger.warn(`Version outdated: ${localVersion} → ${remoteVersion}. Run 'npm i -g ${getPackageName()}@latest' to update.`); break case 'current': - if (result.error != null) logger.error(`Version check failed: ${result.error}`) + if (result.error != null) logger.warn(`Version check skipped: ${result.error}`) else logger.info(`Version ${localVersion} is up to date.`) break case 'development': @@ -169,6 +169,6 @@ export function startupVersionCheck(logger: ILogger): void { }) .catch((err: unknown) => { const message = err instanceof Error ? err.message : 'Unknown error' - logger.error(`Version check failed: ${message}`) + logger.warn(`Version check skipped: ${message}`) }) } diff --git a/cli/tsdown.config.ts b/cli/tsdown.config.ts index 5d207de9..f5e144e3 100644 --- a/cli/tsdown.config.ts +++ b/cli/tsdown.config.ts @@ -5,9 +5,7 @@ import {defineConfig} from 'tsdown' import {TNMSC_JSON_SCHEMA} from './src/schema.ts' const pkg = JSON.parse(readFileSync('./package.json', 'utf8')) as {version: string, name: string} -type BundleMap = Readonly> -const bundleMap = bundles as unknown as BundleMap -const kiroGlobalPowersRegistry: string = bundleMap['public/kiro_global_powers_registry.json']?.content ?? '' +const kiroGlobalPowersRegistry = bundles['public/kiro_global_powers_registry.json']?.content ?? '{"version":"1.0.0","powers":{},"repoSources":{}}' export default defineConfig([ { @@ -41,6 +39,31 @@ export default defineConfig([ } } }, + { + entry: ['./src/plugin-runtime.ts'], + platform: 'node', + sourcemap: false, + unbundle: false, + inlineOnly: false, + alias: { + '@': resolve('src') + }, + noExternal: [ + '@truenine/logger', + 'fast-glob', + '@truenine/desk-paths', + '@truenine/init-bundle', + '@truenine/md-compiler' + ], + format: ['esm'], + minify: true, + dts: false, + define: { + __CLI_VERSION__: JSON.stringify(pkg.version), + __CLI_PACKAGE_NAME__: JSON.stringify(pkg.name), + __KIRO_GLOBAL_POWERS_REGISTRY__: kiroGlobalPowersRegistry + } + }, { entry: ['./src/globals.ts'], platform: 'node', diff --git a/cli/vite.config.ts b/cli/vite.config.ts index a4a546e2..afe89a5b 100644 --- a/cli/vite.config.ts +++ b/cli/vite.config.ts @@ -3,13 +3,8 @@ import {fileURLToPath, URL} from 'node:url' import {bundles} from '@truenine/init-bundle' import {defineConfig} from 'vite' -type BundleMap = Readonly> -const bundleMap = bundles as unknown as BundleMap - const pkg = JSON.parse(readFileSync('./package.json', 'utf8')) as {version: string, name: string} -const kiroGlobalPowersRegistry: string = bundleMap['public/kiro_global_powers_registry.json']?.content ?? '' -const tnmscExample: string = bundleMap['public/tnmsc.example.json']?.content ?? '' -const gitignoreTemplate: string = bundleMap['public/gitignore']?.content ?? '' +const kiroGlobalPowersRegistry = bundles['public/kiro_global_powers_registry.json']?.content ?? '{"version":"1.0.0","powers":{},"repoSources":{}}' export default defineConfig({ resolve: { @@ -20,8 +15,6 @@ export default defineConfig({ define: { __CLI_VERSION__: JSON.stringify(pkg.version), __CLI_PACKAGE_NAME__: JSON.stringify(pkg.name), - __KIRO_GLOBAL_POWERS_REGISTRY__: kiroGlobalPowersRegistry, - __TEMPLATE_TNMSC_EXAMPLE__: JSON.stringify(tnmscExample), - __TEMPLATE_GITIGNORE__: JSON.stringify(gitignoreTemplate) + __KIRO_GLOBAL_POWERS_REGISTRY__: kiroGlobalPowersRegistry } }) diff --git a/doc/package.json b/doc/package.json index 278ffc31..4b585c3a 100644 --- a/doc/package.json +++ b/doc/package.json @@ -1,6 +1,6 @@ { "name": "@truenine/memory-sync-docs", - "version": "2026.10222.10836", + "version": "2026.10223.10555", "private": true, "description": "Documentation site for @truenine/memory-sync, built with Next.js 16 and MDX.", "engines": { diff --git a/gui/package.json b/gui/package.json index c2695cc7..4221127d 100644 --- a/gui/package.json +++ b/gui/package.json @@ -1,6 +1,6 @@ { "name": "@truenine/memory-sync-gui", - "version": "2026.10222.10836", + "version": "2026.10223.10555", "private": true, "engines": { "node": ">=25.2.1", diff --git a/gui/src-tauri/Cargo.toml b/gui/src-tauri/Cargo.toml index 33c988f9..315d2d40 100644 --- a/gui/src-tauri/Cargo.toml +++ b/gui/src-tauri/Cargo.toml @@ -1,22 +1,29 @@ [package] name = "memory-sync-gui" -version = "2026.10222.10836" +version = "2026.10223.10555" description = "Memory Sync desktop GUI application" -authors = ["TrueNine"] -edition = "2021" +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true [lib] name = "app_lib" crate-type = ["staticlib", "cdylib", "rlib"] [build-dependencies] -tauri-build = { version = "2", features = [] } +tauri-build = { workspace = true } [dependencies] -tauri = { version = "2", features = ["tray-icon"] } -tauri-plugin-shell = "2" -tauri-plugin-updater = "2" -serde = { version = "1", features = ["derive"] } -serde_json = "1" -json5 = "1" -dirs = "6" +tauri = { workspace = true } +tauri-plugin-shell = { workspace = true } +tauri-plugin-updater = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +json5 = { workspace = true } +dirs = { workspace = true } + +# tnmsc libraries (for future direct integration) +tnmsc-logger = { workspace = true } +tnmsc-config = { workspace = true } +tnmsc-plugin-shared = { workspace = true } diff --git a/gui/src-tauri/src/commands.rs b/gui/src-tauri/src/commands.rs index c215867f..498b7fb3 100644 --- a/gui/src-tauri/src/commands.rs +++ b/gui/src-tauri/src/commands.rs @@ -303,6 +303,7 @@ mod tests { } #[test] + #[cfg(unix)] fn resolve_cli_path_from_path_env() { let _guard = ENV_TEST_LOCK.lock().expect("env test lock"); let bin_name = cli_binary_name(); @@ -312,28 +313,33 @@ mod tests { let old_path = env::var_os("PATH"); let old_home = env::var_os("HOME"); - env::set_var("PATH", &dir); - // Isolate from fallback test: use same dir as HOME so fallback dirs - // (e.g. $HOME/.local/share/pnpm) do not exist and PATH is the only match. - env::set_var("HOME", &dir); + unsafe { + env::set_var("PATH", &dir); + // Isolate from fallback test: use same dir as HOME so fallback dirs + // (e.g. $HOME/.local/share/pnpm) do not exist and PATH is the only match. + env::set_var("HOME", &dir); + } let resolved = resolve_cli_path(); - if let Some(old) = old_path { - env::set_var("PATH", old); - } else { - env::remove_var("PATH"); - } - if let Some(old) = old_home { - env::set_var("HOME", old); - } else { - env::remove_var("HOME"); + unsafe { + if let Some(old) = old_path { + env::set_var("PATH", old); + } else { + env::remove_var("PATH"); + } + if let Some(old) = old_home { + env::set_var("HOME", old); + } else { + env::remove_var("HOME"); + } } assert_eq!(resolved, Some(bin)); } #[test] + #[cfg(unix)] fn resolve_cli_path_from_fallback_dirs() { let _guard = ENV_TEST_LOCK.lock().expect("env test lock"); let bin_name = cli_binary_name(); @@ -345,20 +351,24 @@ mod tests { let old_path = env::var_os("PATH"); let old_home = env::var_os("HOME"); - env::set_var("PATH", ""); - env::set_var("HOME", &home); + unsafe { + env::set_var("PATH", ""); + env::set_var("HOME", &home); + } let resolved = resolve_cli_path(); - if let Some(old) = old_path { - env::set_var("PATH", old); - } else { - env::remove_var("PATH"); - } - if let Some(old) = old_home { - env::set_var("HOME", old); - } else { - env::remove_var("HOME"); + unsafe { + if let Some(old) = old_path { + env::set_var("PATH", old); + } else { + env::remove_var("PATH"); + } + if let Some(old) = old_home { + env::set_var("HOME", old); + } else { + env::remove_var("HOME"); + } } assert_eq!(resolved, Some(bin)); diff --git a/gui/src-tauri/tauri.conf.json b/gui/src-tauri/tauri.conf.json index 33eb17ee..b1828b19 100644 --- a/gui/src-tauri/tauri.conf.json +++ b/gui/src-tauri/tauri.conf.json @@ -1,6 +1,6 @@ { "$schema": "https://schema.tauri.app/config/2", - "version": "2026.10222.10836", + "version": "2026.10223.10555", "productName": "Memory Sync", "identifier": "org.truenine.memory-sync", "build": { diff --git a/libraries/config/Cargo.toml b/libraries/config/Cargo.toml new file mode 100644 index 00000000..67195625 --- /dev/null +++ b/libraries/config/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "tnmsc-config" +description = "Configuration loading, merging, and validation for tnmsc" +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true + +[lib] +crate-type = ["rlib", "cdylib"] + +[features] +default = [] +napi = ["dep:napi", "dep:napi-derive"] + +[dependencies] +tnmsc-logger = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +dirs = { workspace = true } +sha2 = { workspace = true } +napi = { workspace = true, optional = true } +napi-derive = { workspace = true, optional = true } + +[build-dependencies] +napi-build = { workspace = true } diff --git a/libraries/config/build.rs b/libraries/config/build.rs new file mode 100644 index 00000000..f2be9938 --- /dev/null +++ b/libraries/config/build.rs @@ -0,0 +1,4 @@ +fn main() { + #[cfg(feature = "napi")] + napi_build::setup(); +} diff --git a/packages/logger/eslint.config.ts b/libraries/config/eslint.config.ts similarity index 80% rename from packages/logger/eslint.config.ts rename to libraries/config/eslint.config.ts index 2b7b269c..d1de0a15 100644 --- a/packages/logger/eslint.config.ts +++ b/libraries/config/eslint.config.ts @@ -9,7 +9,7 @@ const config = eslint10({ type: 'lib', typescript: { strictTypescriptEslint: true, - tsconfigPath: resolve(configDir, 'tsconfig.eslint.json'), + tsconfigPath: resolve(configDir, 'tsconfig.json'), parserOptions: { allowDefaultProject: true } @@ -17,7 +17,9 @@ const config = eslint10({ ignores: [ '.turbo/**', '*.md', - '**/*.md' + '**/*.md', + '**/*.toml', + '**/*.d.ts' ] }) diff --git a/libraries/config/index.d.ts b/libraries/config/index.d.ts new file mode 100644 index 00000000..e69de29b diff --git a/libraries/config/package.json b/libraries/config/package.json new file mode 100644 index 00000000..55d1fb6d --- /dev/null +++ b/libraries/config/package.json @@ -0,0 +1,52 @@ +{ + "name": "@truenine/config", + "type": "module", + "version": "2026.10223.10555", + "private": true, + "description": "Rust-powered configuration loader for Node.js", + "license": "AGPL-3.0-only", + "exports": { + "./package.json": "./package.json", + ".": { + "types": "./dist/index.d.mts", + "import": "./dist/index.mjs" + } + }, + "module": "dist/index.mjs", + "types": "dist/index.d.mts", + "files": [ + "dist" + ], + "napi": { + "binaryName": "napi-config", + "targets": [ + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu", + "aarch64-unknown-linux-gnu", + "aarch64-apple-darwin", + "x86_64-apple-darwin" + ] + }, + "scripts": { + "build": "tsdown", + "build:all": "run-s build:native build", + "build:native": "napi build --platform --release --output-dir dist -- --features napi", + "build:native:debug": "napi build --platform --output-dir dist -- --features napi", + "build:ts": "tsdown", + "check": "run-p typecheck lint", + "lint": "eslint --cache .", + "lintfix": "eslint --fix --cache .", + "prepublishOnly": "run-s build", + "test": "run-s test:rust test:ts", + "test:rust": "tsx ../../scripts/cargo-test.ts", + "test:ts": "vitest run --passWithNoTests", + "typecheck": "tsc --noEmit -p tsconfig.lib.json" + }, + "devDependencies": { + "@napi-rs/cli": "^3.5.1", + "npm-run-all2": "catalog:", + "tsdown": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" + } +} diff --git a/libraries/config/src/index.ts b/libraries/config/src/index.ts new file mode 100644 index 00000000..5ffc82c1 --- /dev/null +++ b/libraries/config/src/index.ts @@ -0,0 +1,81 @@ +import {createRequire} from 'node:module' +import process from 'node:process' + +interface NapiConfigModule { + loadUserConfig: (cwd: string) => string + getGlobalConfigPathStr: () => string + mergeConfigs: (baseJson: string, overJson: string) => string + loadConfigFromFile: (filePath: string) => string | null +} + +let napiBinding: NapiConfigModule | null = null + +try { + const _require = createRequire(import.meta.url) + const {platform, arch} = process + const platforms: Record = { + 'win32-x64': ['napi-config.win32-x64-msvc', 'win32-x64-msvc'], + 'linux-x64': ['napi-config.linux-x64-gnu', 'linux-x64-gnu'], + 'linux-arm64': ['napi-config.linux-arm64-gnu', 'linux-arm64-gnu'], + 'darwin-arm64': ['napi-config.darwin-arm64', 'darwin-arm64'], + 'darwin-x64': ['napi-config.darwin-x64', 'darwin-x64'] + } + const entry = platforms[`${platform}-${arch}`] + if (entry != null) { + const [local, suffix] = entry + try { + napiBinding = _require(`./${local}.node`) as NapiConfigModule + } + catch { + try { + const pkg = _require(`@truenine/memory-sync-cli-${suffix}`) as Record + napiBinding = pkg['config'] as NapiConfigModule + } + catch {} + } + } +} +catch {} // Native module not available — no pure-TS fallback for config + +if (napiBinding == null) { + console.warn('[tnmsc:config] Native module not available — config operations will return empty/default values. Install the platform-specific package for your OS to enable native config loading.') +} + +/** + * Load and merge user configuration from the given cwd directory. + * Returns the merged config as a parsed object. + */ +export function loadUserConfig(cwd: string): Record { + if (napiBinding == null) return {} + return JSON.parse(napiBinding.loadUserConfig(cwd)) as Record +} + +/** + * Get the global config file path (~/.aindex/.tnmsc.json). + */ +export function getGlobalConfigPath(): string { + if (napiBinding != null) return napiBinding.getGlobalConfigPathStr() + + const home = process.env['HOME'] ?? process.env['USERPROFILE'] ?? '~' + return `${home}/.aindex/.tnmsc.json` +} + +/** + * Merge two config objects. `over` fields take priority over `base`. + */ +export function mergeConfigs( + base: Record, + over: Record +): Record { + if (napiBinding == null) return {...base, ...over} + return JSON.parse(napiBinding.mergeConfigs(JSON.stringify(base), JSON.stringify(over))) as Record +} + +/** + * Load config from a specific file path. Returns null if not found. + */ +export function loadConfigFromFile(filePath: string): Record | null { + if (napiBinding == null) return null + const result = napiBinding.loadConfigFromFile(filePath) + return result != null ? JSON.parse(result) as Record : null +} diff --git a/libraries/config/src/lib.rs b/libraries/config/src/lib.rs new file mode 100644 index 00000000..b23b40d5 --- /dev/null +++ b/libraries/config/src/lib.rs @@ -0,0 +1,998 @@ +#![deny(clippy::all)] + +//! Configuration loading, merging, and validation. +//! +//! Reads `~/.aindex/.tnmsc.json` (global) and `./.tnmsc.json` (cwd), +//! merges with priority: CWD > global > defaults. + +use std::collections::HashMap; +use std::fs; +use std::path::{Path, PathBuf}; + +use sha2::{Digest, Sha256}; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +use tnmsc_logger::{Logger, create_logger}; + +// --------------------------------------------------------------------------- +// Constants +// --------------------------------------------------------------------------- + +pub const DEFAULT_CONFIG_FILE_NAME: &str = ".tnmsc.json"; +pub const DEFAULT_GLOBAL_CONFIG_DIR: &str = ".aindex"; + +// --------------------------------------------------------------------------- +// Types — mirrors TS ConfigTypes.schema.ts +// --------------------------------------------------------------------------- + +/// A source/dist path pair. Both paths are relative to the shadow source project root. +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +pub struct DirPair { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub src: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub dist: Option, +} + +impl DirPair { + fn merge(a: &Option, b: &Option) -> Option { + match (a, b) { + (None, None) => None, + (Some(v), None) => Some(v.clone()), + (None, Some(v)) => Some(v.clone()), + (Some(base), Some(over)) => Some(DirPair { + src: over.src.clone().or_else(|| base.src.clone()), + dist: over.dist.clone().or_else(|| base.dist.clone()), + }), + } + } +} + +/// Shadow source project configuration. +/// All paths are relative to `/`. +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct ShadowSourceProjectConfig { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub skill: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub fast_command: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub sub_agent: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub rule: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub global_memory: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_memory: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub project: Option, +} + +/// Per-plugin fast command series override options. +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct FastCommandSeriesPluginOverride { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub include_series_prefix: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub series_separator: Option, +} + +/// Fast command series configuration options. +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct FastCommandSeriesOptions { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub include_series_prefix: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub plugin_overrides: Option>, +} + +/// User profile information. Supports arbitrary key-value pairs. +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +pub struct UserProfile { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub username: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub gender: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub birthday: Option, + #[serde(flatten)] + pub extra: HashMap, +} + +/// User configuration file (.tnmsc.json). +/// All fields are optional — missing fields use default values. +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct UserConfigFile { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub version: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace_dir: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub shadow_source_project: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub log_level: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub fast_command_series_options: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub profile: Option, +} + +// --------------------------------------------------------------------------- +// Result types +// --------------------------------------------------------------------------- + +/// Result of loading a single config file. +#[derive(Debug, Clone)] +pub struct ConfigLoadResult { + pub config: UserConfigFile, + pub source: Option, + pub found: bool, +} + +/// Result of loading and merging all configurations. +#[derive(Debug, Clone)] +pub struct MergedConfigResult { + pub config: UserConfigFile, + pub sources: Vec, + pub found: bool, +} + +/// Validation result for global config. +#[derive(Debug, Clone)] +pub struct GlobalConfigValidationResult { + pub valid: bool, + pub exists: bool, + pub errors: Vec, + pub should_exit: bool, +} + +// --------------------------------------------------------------------------- +// Path helpers +// --------------------------------------------------------------------------- + +fn home_dir() -> Option { + dirs::home_dir() +} + +/// Resolve `~` prefix to the user's home directory. +pub fn resolve_tilde(p: &str) -> PathBuf { + if let Some(rest) = p.strip_prefix('~') { + if let Some(home) = home_dir() { + let rest = rest.strip_prefix('/').or_else(|| rest.strip_prefix('\\')).unwrap_or(rest); + return home.join(rest); + } + } + PathBuf::from(p) +} + +/// Get the global config file path: `~/.aindex/.tnmsc.json` +pub fn get_global_config_path() -> PathBuf { + match home_dir() { + Some(home) => home.join(DEFAULT_GLOBAL_CONFIG_DIR).join(DEFAULT_CONFIG_FILE_NAME), + None => PathBuf::from(DEFAULT_GLOBAL_CONFIG_DIR).join(DEFAULT_CONFIG_FILE_NAME), + } +} + +// --------------------------------------------------------------------------- +// Merge logic +// --------------------------------------------------------------------------- + +fn merge_shadow_source_project( + a: &Option, + b: &Option, +) -> Option { + match (a, b) { + (None, None) => None, + (Some(v), None) => Some(v.clone()), + (None, Some(v)) => Some(v.clone()), + (Some(base), Some(over)) => Some(ShadowSourceProjectConfig { + name: over.name.clone().or_else(|| base.name.clone()), + skill: DirPair::merge(&base.skill, &over.skill), + fast_command: DirPair::merge(&base.fast_command, &over.fast_command), + sub_agent: DirPair::merge(&base.sub_agent, &over.sub_agent), + rule: DirPair::merge(&base.rule, &over.rule), + global_memory: DirPair::merge(&base.global_memory, &over.global_memory), + workspace_memory: DirPair::merge(&base.workspace_memory, &over.workspace_memory), + project: DirPair::merge(&base.project, &over.project), + }), + } +} + +/// Merge two configs. `over` fields take priority over `base`. +pub fn merge_configs_pair(base: &UserConfigFile, over: &UserConfigFile) -> UserConfigFile { + let merged_shadow = merge_shadow_source_project( + &base.shadow_source_project, + &over.shadow_source_project, + ); + + UserConfigFile { + version: over.version.clone().or_else(|| base.version.clone()), + workspace_dir: over.workspace_dir.clone().or_else(|| base.workspace_dir.clone()), + shadow_source_project: merged_shadow, + log_level: over.log_level.clone().or_else(|| base.log_level.clone()), + fast_command_series_options: over.fast_command_series_options.clone() + .or_else(|| base.fast_command_series_options.clone()), + profile: over.profile.clone().or_else(|| base.profile.clone()), + } +} + +/// Merge a list of configs. First has highest priority, last has lowest. +fn merge_configs(configs: &[UserConfigFile]) -> UserConfigFile { + if configs.is_empty() { + return UserConfigFile::default(); + } + if configs.len() == 1 { + return configs[0].clone(); + } + // Reverse: merge from lowest to highest priority + let mut result = UserConfigFile::default(); + for config in configs.iter().rev() { + result = merge_configs_pair(&result, config); + } + result +} + +// --------------------------------------------------------------------------- +// ConfigLoader +// --------------------------------------------------------------------------- + +/// Options for ConfigLoader. +#[derive(Debug, Clone, Default)] +pub struct ConfigLoaderOptions { + pub config_file_name: Option, + pub search_paths: Vec, + pub search_cwd: Option, + pub search_global: Option, +} + +/// ConfigLoader handles discovery and loading of user configuration files. +/// +/// Search order (first found wins at each level): +/// 1. Custom search paths (highest priority) +/// 2. CWD: `./.tnmsc.json` +/// 3. Global: `~/.aindex/.tnmsc.json` (lowest priority) +/// +/// Configurations are merged with earlier sources having higher priority. +pub struct ConfigLoader { + config_file_name: String, + search_cwd: bool, + search_global: bool, + custom_search_paths: Vec, + logger: Logger, +} + +impl ConfigLoader { + pub fn new(options: ConfigLoaderOptions) -> Self { + Self { + config_file_name: options.config_file_name + .unwrap_or_else(|| DEFAULT_CONFIG_FILE_NAME.to_string()), + search_cwd: options.search_cwd.unwrap_or(true), + search_global: options.search_global.unwrap_or(true), + custom_search_paths: options.search_paths, + logger: create_logger("ConfigLoader", None), + } + } + + pub fn with_defaults() -> Self { + Self::new(ConfigLoaderOptions::default()) + } + + /// Get the list of config file paths to search. + pub fn get_search_paths(&self, cwd: &Path) -> Vec { + let mut paths = Vec::new(); + + // Custom search paths first (highest priority) + for p in &self.custom_search_paths { + paths.push(resolve_tilde(p)); + } + + // CWD config + if self.search_cwd { + paths.push(cwd.join(&self.config_file_name)); + } + + // Global config (lowest priority) + if self.search_global { + paths.push(get_global_config_path()); + } + + paths + } + + /// Load a single config file. + pub fn load_from_file(&self, file_path: &Path) -> ConfigLoadResult { + let resolved = if file_path.starts_with("~") { + resolve_tilde(&file_path.to_string_lossy()) + } else { + file_path.to_path_buf() + }; + + if !resolved.exists() { + return ConfigLoadResult { + config: UserConfigFile::default(), + source: None, + found: false, + }; + } + + match fs::read_to_string(&resolved) { + Ok(content) => match self.parse_config(&content, &resolved) { + Ok(config) => { + self.logger.debug( + Value::String("loaded".into()), + Some(serde_json::json!({"source": resolved.to_string_lossy()})), + ); + ConfigLoadResult { + config, + source: Some(resolved.to_string_lossy().into_owned()), + found: true, + } + } + Err(_) => ConfigLoadResult { + config: UserConfigFile::default(), + source: None, + found: false, + }, + }, + Err(e) => { + self.logger.warn( + Value::String("load failed".into()), + Some(serde_json::json!({ + "path": resolved.to_string_lossy(), + "error": e.to_string() + })), + ); + ConfigLoadResult { + config: UserConfigFile::default(), + source: None, + found: false, + } + } + } + } + + /// Load and merge all config files. + pub fn load(&self, cwd: &Path) -> MergedConfigResult { + let search_paths = self.get_search_paths(cwd); + let mut loaded: Vec = Vec::new(); + + for path in &search_paths { + let result = self.load_from_file(path); + if result.found { + loaded.push(result); + } + } + + let configs: Vec = loaded.iter().map(|r| r.config.clone()).collect(); + let merged = merge_configs(&configs); + let sources: Vec = loaded.iter() + .filter_map(|r| r.source.clone()) + .collect(); + + MergedConfigResult { + config: merged, + sources, + found: !loaded.is_empty(), + } + } + + fn parse_config(&self, content: &str, file_path: &Path) -> Result { + let parsed: Value = serde_json::from_str(content) + .map_err(|e| format!("Invalid JSON in {}: {}", file_path.display(), e))?; + + if !parsed.is_object() { + return Err(format!("Config must be a JSON object in {}", file_path.display())); + } + + // Deserialize with serde — invalid fields are silently ignored (like Zod's safeParse) + match serde_json::from_value::(parsed.clone()) { + Ok(config) => Ok(config), + Err(e) => { + self.logger.warn( + Value::String("validation warnings".into()), + Some(serde_json::json!({ + "path": file_path.to_string_lossy(), + "error": e.to_string() + })), + ); + // Fallback: try to extract what we can + Ok(serde_json::from_value::(Value::Object(Default::default())) + .unwrap_or_default()) + } + } + } +} + +// --------------------------------------------------------------------------- +// Convenience functions +// --------------------------------------------------------------------------- + +/// Load user configuration using default loader. +pub fn load_user_config(cwd: &Path) -> MergedConfigResult { + ConfigLoader::with_defaults().load(cwd) +} + +// --------------------------------------------------------------------------- +// Config file management +// --------------------------------------------------------------------------- + +/// Write a config file with pretty JSON formatting. +pub fn write_config(path: &Path, config: &UserConfigFile, logger: &Logger) { + if let Some(parent) = path.parent() { + if !parent.exists() { + let _ = fs::create_dir_all(parent); + } + } + + match serde_json::to_string_pretty(config) { + Ok(json) => { + let content = format!("{}\n", json); + match fs::write(path, content) { + Ok(()) => { + logger.info( + Value::String("global config created".into()), + Some(serde_json::json!({"path": path.to_string_lossy()})), + ); + } + Err(e) => { + logger.warn( + Value::String("failed to write config".into()), + Some(serde_json::json!({ + "path": path.to_string_lossy(), + "error": e.to_string() + })), + ); + } + } + } + Err(e) => { + logger.warn( + Value::String("failed to serialize config".into()), + Some(serde_json::json!({"error": e.to_string()})), + ); + } + } +} + +/// Compute SHA-256 hex digest of file contents. +fn sha256_file(path: &Path) -> Option { + let data = fs::read(path).ok()?; + let mut hasher = Sha256::new(); + hasher.update(&data); + Some(format!("{:x}", hasher.finalize())) +} + +/// Check if a path is a symlink. +fn is_symlink(path: &Path) -> bool { + fs::symlink_metadata(path) + .map(|m| m.file_type().is_symlink()) + .unwrap_or(false) +} + +/// Read the target of a symlink. +fn read_symlink_target(path: &Path) -> Option { + fs::read_link(path).ok() +} + +/// Ensure a local config file is linked (symlink preferred) to the global config. +/// +/// On every run: +/// - If local is a correct symlink → no-op +/// - If local is a stale symlink → delete and recreate +/// - If local is a regular file with different content → sync back to global, then recreate link +/// - If local is a regular file with same content → delete and recreate link +/// +/// Falls back to a file copy when symlink creation fails. +pub fn ensure_config_link(local_path: &Path, global_path: &Path, logger: &Logger) { + if !global_path.exists() { + return; + } + + if local_path.exists() || is_symlink(local_path) { + if is_symlink(local_path) { + if let Some(target) = read_symlink_target(local_path) { + // Canonicalize for comparison + let target_canon = fs::canonicalize(&target).unwrap_or(target); + let global_canon = fs::canonicalize(global_path) + .unwrap_or_else(|_| global_path.to_path_buf()); + if target_canon == global_canon { + return; // correct symlink, no-op + } + } + // stale symlink — delete + let _ = fs::remove_file(local_path); + } else { + // Regular file — check content hash + let local_hash = sha256_file(local_path); + let global_hash = sha256_file(global_path); + if local_hash != global_hash { + // local differs: sync back to global + let _ = fs::copy(local_path, global_path); + logger.debug( + Value::String("synced local config back to global".into()), + Some(serde_json::json!({ + "src": local_path.to_string_lossy(), + "dest": global_path.to_string_lossy() + })), + ); + } + let _ = fs::remove_file(local_path); + } + } + + // Try symlink first + #[cfg(unix)] + let symlink_result = std::os::unix::fs::symlink(global_path, local_path); + #[cfg(windows)] + let symlink_result = std::os::windows::fs::symlink_file(global_path, local_path); + + match symlink_result { + Ok(()) => { + logger.debug( + Value::String("linked config".into()), + Some(serde_json::json!({ + "link": local_path.to_string_lossy(), + "target": global_path.to_string_lossy() + })), + ); + } + Err(_) => { + // Fallback: copy + match fs::copy(global_path, local_path) { + Ok(_) => { + logger.warn( + Value::String("symlink unavailable, copied config (auto-sync disabled)".into()), + Some(serde_json::json!({"dest": local_path.to_string_lossy()})), + ); + } + Err(e) => { + logger.warn( + Value::String("failed to link or copy config".into()), + Some(serde_json::json!({ + "path": local_path.to_string_lossy(), + "error": e.to_string() + })), + ); + } + } + } + } +} + +/// Ensure the shadow source project directory has a `.tnmsc.json` symlink +/// pointing to the global config. +pub fn ensure_shadow_project_config_link(shadow_project_dir: &str, logger: &Logger) { + let resolved = resolve_tilde(shadow_project_dir); + if !resolved.exists() { + return; + } + let global_path = get_global_config_path(); + let config_path = resolved.join(DEFAULT_CONFIG_FILE_NAME); + ensure_config_link(&config_path, &global_path, logger); +} + +/// Validate global config file strictly. +/// +/// - If config doesn't exist: create default config, log warn, continue +/// - If config is invalid: delete and recreate, log error, return should_exit=true +pub fn validate_and_ensure_global_config( + default_config: &UserConfigFile, +) -> GlobalConfigValidationResult { + let logger = create_logger("ConfigLoader", None); + let config_path = get_global_config_path(); + + if !config_path.exists() { + logger.warn( + Value::String("global config not found, creating default config".into()), + Some(serde_json::json!({"path": config_path.to_string_lossy()})), + ); + write_config(&config_path, default_config, &logger); + return GlobalConfigValidationResult { + valid: true, + exists: false, + errors: vec![], + should_exit: false, + }; + } + + // Try to read + let content = match fs::read_to_string(&config_path) { + Ok(c) => c, + Err(e) => { + let msg = format!("Failed to read config: {}", e); + logger.error( + Value::String("failed to read global config".into()), + Some(serde_json::json!({ + "path": config_path.to_string_lossy(), + "error": e.to_string() + })), + ); + return recreate_config_and_exit(&config_path, default_config, &logger, vec![msg]); + } + }; + + // Try to parse JSON + let parsed: Value = match serde_json::from_str(&content) { + Ok(v) => v, + Err(e) => { + let msg = format!("Invalid JSON: {}", e); + logger.error( + Value::String("invalid JSON in global config".into()), + Some(serde_json::json!({ + "path": config_path.to_string_lossy(), + "error": e.to_string() + })), + ); + return recreate_config_and_exit(&config_path, default_config, &logger, vec![msg]); + } + }; + + // Must be an object + if !parsed.is_object() { + logger.error( + Value::String("global config must be a JSON object".into()), + Some(serde_json::json!({"path": config_path.to_string_lossy()})), + ); + return recreate_config_and_exit( + &config_path, + default_config, + &logger, + vec!["Config must be a JSON object".into()], + ); + } + + // Try to deserialize + if let Err(e) = serde_json::from_value::(parsed) { + let msg = format!("Config validation error: {}", e); + logger.error( + Value::String("config validation error".into()), + Some(serde_json::json!({ + "path": config_path.to_string_lossy(), + "error": e.to_string() + })), + ); + return recreate_config_and_exit(&config_path, default_config, &logger, vec![msg]); + } + + GlobalConfigValidationResult { + valid: true, + exists: true, + errors: vec![], + should_exit: false, + } +} + +fn recreate_config_and_exit( + config_path: &Path, + default_config: &UserConfigFile, + logger: &Logger, + errors: Vec, +) -> GlobalConfigValidationResult { + if let Err(_) = fs::remove_file(config_path) { + logger.warn( + Value::String("failed to delete invalid config".into()), + Some(serde_json::json!({"path": config_path.to_string_lossy()})), + ); + } else { + logger.info( + Value::String("deleted invalid config".into()), + Some(serde_json::json!({"path": config_path.to_string_lossy()})), + ); + } + + write_config(config_path, default_config, logger); + logger.error( + Value::String("recreated default config, please review and restart".into()), + Some(serde_json::json!({"path": config_path.to_string_lossy()})), + ); + + GlobalConfigValidationResult { + valid: false, + exists: true, + errors, + should_exit: true, + } +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_resolve_tilde() { + let resolved = resolve_tilde("~/test/path"); + if let Some(home) = home_dir() { + assert_eq!(resolved, home.join("test").join("path")); + } + } + + #[test] + fn test_resolve_tilde_no_tilde() { + let resolved = resolve_tilde("/absolute/path"); + assert_eq!(resolved, PathBuf::from("/absolute/path")); + } + + #[test] + fn test_user_config_file_default() { + let config = UserConfigFile::default(); + assert!(config.version.is_none()); + assert!(config.workspace_dir.is_none()); + assert!(config.shadow_source_project.is_none()); + assert!(config.log_level.is_none()); + } + + #[test] + fn test_user_config_file_deserialize() { + let json = r#"{ + "workspaceDir": "~/myworkspace", + "logLevel": "debug" + }"#; + let config: UserConfigFile = serde_json::from_str(json).unwrap(); + assert_eq!(config.workspace_dir.as_deref(), Some("~/myworkspace")); + assert_eq!(config.log_level.as_deref(), Some("debug")); + } + + #[test] + fn test_user_config_file_deserialize_with_shadow_project() { + let json = r#"{ + "shadowSourceProject": { + "name": "aindex", + "skill": {"src": "src/skills", "dist": "dist/skills"}, + "fastCommand": {"src": "src/commands", "dist": "dist/commands"}, + "subAgent": {"src": "src/agents", "dist": "dist/agents"}, + "rule": {"src": "src/rules", "dist": "dist/rules"}, + "globalMemory": {"src": "app/global.cn.mdx", "dist": "dist/global.mdx"}, + "workspaceMemory": {"src": "app/workspace.cn.mdx", "dist": "dist/app/workspace.mdx"}, + "project": {"src": "app", "dist": "dist/app"} + } + }"#; + let config: UserConfigFile = serde_json::from_str(json).unwrap(); + let sp = config.shadow_source_project.unwrap(); + assert_eq!(sp.name.as_deref(), Some("aindex")); + assert_eq!(sp.skill.as_ref().unwrap().src.as_deref(), Some("src/skills")); + } + + #[test] + fn test_user_config_file_deserialize_with_profile() { + let json = r#"{ + "profile": { + "name": "Zhang San", + "username": "zhangsan", + "gender": "male", + "birthday": "1990-01-01", + "customField": "custom value" + } + }"#; + let config: UserConfigFile = serde_json::from_str(json).unwrap(); + let profile = config.profile.unwrap(); + assert_eq!(profile.name.as_deref(), Some("Zhang San")); + assert_eq!(profile.extra.get("customField").and_then(|v| v.as_str()), Some("custom value")); + } + + #[test] + fn test_user_config_file_roundtrip() { + let config = UserConfigFile { + workspace_dir: Some("~/workspace".into()), + log_level: Some("info".into()), + ..Default::default() + }; + let json = serde_json::to_string(&config).unwrap(); + let parsed: UserConfigFile = serde_json::from_str(&json).unwrap(); + assert_eq!(config, parsed); + } + + #[test] + fn test_merge_configs_empty() { + let result = merge_configs(&[]); + assert_eq!(result, UserConfigFile::default()); + } + + #[test] + fn test_merge_configs_single() { + let config = UserConfigFile { + workspace_dir: Some("~/ws".into()), + ..Default::default() + }; + let result = merge_configs(&[config.clone()]); + assert_eq!(result, config); + } + + #[test] + fn test_merge_configs_priority() { + let cwd_config = UserConfigFile { + workspace_dir: Some("~/cwd-workspace".into()), + log_level: Some("debug".into()), + ..Default::default() + }; + let global_config = UserConfigFile { + workspace_dir: Some("~/global-workspace".into()), + log_level: Some("info".into()), + shadow_source_project: Some(ShadowSourceProjectConfig { + name: Some("global-shadow".into()), + ..Default::default() + }), + ..Default::default() + }; + + // cwd_config is first (highest priority) + let result = merge_configs(&[cwd_config, global_config]); + assert_eq!(result.workspace_dir.as_deref(), Some("~/cwd-workspace")); + assert_eq!(result.log_level.as_deref(), Some("debug")); + assert_eq!( + result.shadow_source_project.as_ref().and_then(|s| s.name.as_deref()), + Some("global-shadow") + ); + } + + #[test] + fn test_merge_shadow_source_project_deep() { + let cwd_config = UserConfigFile { + shadow_source_project: Some(ShadowSourceProjectConfig { + name: Some("cwd-shadow".into()), + skill: Some(DirPair { + src: Some("custom/skills".into()), + dist: Some("custom/dist/skills".into()), + }), + ..Default::default() + }), + ..Default::default() + }; + let global_config = UserConfigFile { + shadow_source_project: Some(ShadowSourceProjectConfig { + name: Some("global-shadow".into()), + skill: Some(DirPair { + src: Some("src/skills".into()), + dist: Some("dist/skills".into()), + }), + fast_command: Some(DirPair { + src: Some("src/commands".into()), + dist: Some("dist/commands".into()), + }), + ..Default::default() + }), + ..Default::default() + }; + + let result = merge_configs(&[cwd_config, global_config]); + let sp = result.shadow_source_project.unwrap(); + assert_eq!(sp.name.as_deref(), Some("cwd-shadow")); + assert_eq!(sp.skill.as_ref().unwrap().src.as_deref(), Some("custom/skills")); + assert_eq!(sp.fast_command.as_ref().unwrap().src.as_deref(), Some("src/commands")); + } + + #[test] + fn test_config_loader_search_paths() { + let loader = ConfigLoader::with_defaults(); + let cwd = PathBuf::from("/workspace/project"); + let paths = loader.get_search_paths(&cwd); + + assert!(paths.contains(&cwd.join(DEFAULT_CONFIG_FILE_NAME))); + assert!(paths.contains(&get_global_config_path())); + } + + #[test] + fn test_config_loader_search_paths_no_cwd() { + let loader = ConfigLoader::new(ConfigLoaderOptions { + search_cwd: Some(false), + ..Default::default() + }); + let cwd = PathBuf::from("/workspace/project"); + let paths = loader.get_search_paths(&cwd); + + assert!(!paths.contains(&cwd.join(DEFAULT_CONFIG_FILE_NAME))); + } + + #[test] + fn test_config_loader_search_paths_no_global() { + let loader = ConfigLoader::new(ConfigLoaderOptions { + search_global: Some(false), + ..Default::default() + }); + let cwd = PathBuf::from("/workspace/project"); + let paths = loader.get_search_paths(&cwd); + + assert!(!paths.contains(&get_global_config_path())); + } + + #[test] + fn test_config_loader_custom_search_paths() { + let loader = ConfigLoader::new(ConfigLoaderOptions { + search_paths: vec!["/custom/config/path".into()], + ..Default::default() + }); + let cwd = PathBuf::from("/workspace/project"); + let paths = loader.get_search_paths(&cwd); + + assert_eq!(paths[0], PathBuf::from("/custom/config/path")); + } + + #[test] + fn test_config_loader_load_nonexistent() { + let loader = ConfigLoader::with_defaults(); + let result = loader.load_from_file(Path::new("/nonexistent/.tnmsc.json")); + assert!(!result.found); + assert!(result.source.is_none()); + } + + #[test] + fn test_dir_pair_merge() { + let a = Some(DirPair { src: Some("a-src".into()), dist: Some("a-dist".into()) }); + let b = Some(DirPair { src: Some("b-src".into()), dist: None }); + let merged = DirPair::merge(&a, &b).unwrap(); + assert_eq!(merged.src.as_deref(), Some("b-src")); + assert_eq!(merged.dist.as_deref(), Some("a-dist")); + } + + #[test] + fn test_global_config_path() { + let path = get_global_config_path(); + let path_str = path.to_string_lossy(); + assert!(path_str.contains(DEFAULT_GLOBAL_CONFIG_DIR)); + assert!(path_str.contains(DEFAULT_CONFIG_FILE_NAME)); + } +} + + +// =========================================================================== +// NAPI binding layer (only compiled with --features napi) +// =========================================================================== + +#[cfg(feature = "napi")] +mod napi_binding { + use napi_derive::napi; + use super::*; + + /// Load and merge user configuration from the given cwd directory. + /// Returns the merged config as a JSON string. + #[napi] + pub fn load_user_config(cwd: String) -> napi::Result { + let path = std::path::Path::new(&cwd); + let result = ConfigLoader::with_defaults().load(path); + serde_json::to_string(&result.config) + .map_err(|e| napi::Error::from_reason(e.to_string())) + } + + /// Get the global config file path (~/.aindex/.tnmsc.json). + #[napi] + pub fn get_global_config_path_str() -> String { + get_global_config_path().to_string_lossy().into_owned() + } + + /// Merge two config JSON strings. `over` fields take priority over `base`. + #[napi] + pub fn merge_configs(base_json: String, over_json: String) -> napi::Result { + let base: UserConfigFile = serde_json::from_str(&base_json) + .map_err(|e| napi::Error::from_reason(format!("base: {e}")))?; + let over: UserConfigFile = serde_json::from_str(&over_json) + .map_err(|e| napi::Error::from_reason(format!("over: {e}")))?; + let merged = merge_configs_pair(&base, &over); + serde_json::to_string(&merged) + .map_err(|e| napi::Error::from_reason(e.to_string())) + } + + /// Load config from a specific file path. Returns JSON string or null if not found. + #[napi] + pub fn load_config_from_file(file_path: String) -> napi::Result> { + let loader = ConfigLoader::with_defaults(); + let result = loader.load_from_file(std::path::Path::new(&file_path)); + if !result.found { + return Ok(None); + } + let json = serde_json::to_string(&result.config) + .map_err(|e| napi::Error::from_reason(e.to_string()))?; + Ok(Some(json)) + } +} diff --git a/packages/logger/tsconfig.json b/libraries/config/tsconfig.json similarity index 96% rename from packages/logger/tsconfig.json rename to libraries/config/tsconfig.json index 03cd50a3..0950f1da 100644 --- a/packages/logger/tsconfig.json +++ b/libraries/config/tsconfig.json @@ -59,9 +59,7 @@ "src/**/*", "env.d.ts", "eslint.config.ts", - "tsdown.config.ts", - "vite.config.ts", - "vitest.config.ts" + "tsdown.config.ts" ], "exclude": [ "../node_modules", diff --git a/packages/md-compiler/tsconfig.lib.json b/libraries/config/tsconfig.lib.json similarity index 93% rename from packages/md-compiler/tsconfig.lib.json rename to libraries/config/tsconfig.lib.json index b2449b37..7df70332 100644 --- a/packages/md-compiler/tsconfig.lib.json +++ b/libraries/config/tsconfig.lib.json @@ -5,7 +5,7 @@ "composite": true, "rootDir": "./src", "noEmit": false, - "outDir": "../dist", + "outDir": "./dist", "skipLibCheck": true }, "include": [ diff --git a/packages/logger/tsdown.config.ts b/libraries/config/tsdown.config.ts similarity index 100% rename from packages/logger/tsdown.config.ts rename to libraries/config/tsdown.config.ts diff --git a/libraries/init-bundle/Cargo.toml b/libraries/init-bundle/Cargo.toml new file mode 100644 index 00000000..62321d86 --- /dev/null +++ b/libraries/init-bundle/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "tnmsc-init-bundle" +description = "Embedded file templates for tnmsc init command" +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true + +[lib] +crate-type = ["rlib", "cdylib"] + +[features] +default = [] +napi = ["dep:napi", "dep:napi-derive"] + +[dependencies] +napi = { workspace = true, optional = true } +napi-derive = { workspace = true, optional = true } + +[dev-dependencies] +serde_json = { workspace = true } + +[build-dependencies] +napi-build = { workspace = true } diff --git a/libraries/init-bundle/build.rs b/libraries/init-bundle/build.rs new file mode 100644 index 00000000..f2be9938 --- /dev/null +++ b/libraries/init-bundle/build.rs @@ -0,0 +1,4 @@ +fn main() { + #[cfg(feature = "napi")] + napi_build::setup(); +} diff --git a/packages/md-compiler/eslint.config.ts b/libraries/init-bundle/eslint.config.ts similarity index 66% rename from packages/md-compiler/eslint.config.ts rename to libraries/init-bundle/eslint.config.ts index 13a6c4cf..d1de0a15 100644 --- a/packages/md-compiler/eslint.config.ts +++ b/libraries/init-bundle/eslint.config.ts @@ -9,24 +9,17 @@ const config = eslint10({ type: 'lib', typescript: { strictTypescriptEslint: true, - tsconfigPath: resolve(configDir, 'tsconfig.eslint.json'), + tsconfigPath: resolve(configDir, 'tsconfig.json'), parserOptions: { allowDefaultProject: true } }, ignores: [ '.turbo/**', - 'aindex/**', '*.md', '**/*.md', - '.kiro/**', - '.claude/**', - '.factory/**', - 'src/AGENTS.md', - 'public/**', - '.skills/**', - '**/.skills/**', - '.agent/**' + '**/*.toml', + '**/*.d.ts' ] }) diff --git a/libraries/init-bundle/index.d.ts b/libraries/init-bundle/index.d.ts new file mode 100644 index 00000000..e69de29b diff --git a/libraries/init-bundle/package.json b/libraries/init-bundle/package.json new file mode 100644 index 00000000..f1662b5b --- /dev/null +++ b/libraries/init-bundle/package.json @@ -0,0 +1,52 @@ +{ + "name": "@truenine/init-bundle", + "type": "module", + "version": "2026.10223.10555", + "private": true, + "description": "Rust-powered embedded file templates for tnmsc init command", + "license": "AGPL-3.0-only", + "exports": { + "./package.json": "./package.json", + ".": { + "types": "./dist/index.d.mts", + "import": "./dist/index.mjs" + } + }, + "module": "dist/index.mjs", + "types": "dist/index.d.mts", + "files": [ + "dist" + ], + "napi": { + "binaryName": "napi-init-bundle", + "targets": [ + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu", + "aarch64-unknown-linux-gnu", + "aarch64-apple-darwin", + "x86_64-apple-darwin" + ] + }, + "scripts": { + "build": "tsdown", + "build:all": "run-s build:native build", + "build:native": "napi build --platform --release --output-dir dist -- --features napi", + "build:native:debug": "napi build --platform --output-dir dist -- --features napi", + "build:ts": "tsdown", + "check": "run-p typecheck lint", + "lint": "eslint --cache .", + "lintfix": "eslint --fix --cache .", + "prepublishOnly": "run-s build", + "test": "run-s test:rust test:ts", + "test:rust": "tsx ../../scripts/cargo-test.ts", + "test:ts": "vitest run --passWithNoTests", + "typecheck": "tsc --noEmit -p tsconfig.lib.json" + }, + "devDependencies": { + "@napi-rs/cli": "^3.5.1", + "npm-run-all2": "catalog:", + "tsdown": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" + } +} diff --git a/packages/init-bundle/public/.editorconfig b/libraries/init-bundle/public/.editorconfig similarity index 100% rename from packages/init-bundle/public/.editorconfig rename to libraries/init-bundle/public/.editorconfig diff --git a/packages/init-bundle/public/.gitignore b/libraries/init-bundle/public/.gitignore similarity index 100% rename from packages/init-bundle/public/.gitignore rename to libraries/init-bundle/public/.gitignore diff --git a/packages/init-bundle/public/.idea/.gitignore b/libraries/init-bundle/public/.idea/.gitignore similarity index 100% rename from packages/init-bundle/public/.idea/.gitignore rename to libraries/init-bundle/public/.idea/.gitignore diff --git a/packages/init-bundle/public/.idea/codeStyles/Project.xml b/libraries/init-bundle/public/.idea/codeStyles/Project.xml similarity index 100% rename from packages/init-bundle/public/.idea/codeStyles/Project.xml rename to libraries/init-bundle/public/.idea/codeStyles/Project.xml diff --git a/packages/init-bundle/public/.idea/codeStyles/codeStyleConfig.xml b/libraries/init-bundle/public/.idea/codeStyles/codeStyleConfig.xml similarity index 100% rename from packages/init-bundle/public/.idea/codeStyles/codeStyleConfig.xml rename to libraries/init-bundle/public/.idea/codeStyles/codeStyleConfig.xml diff --git a/packages/init-bundle/public/.vscode/extensions.json b/libraries/init-bundle/public/.vscode/extensions.json similarity index 100% rename from packages/init-bundle/public/.vscode/extensions.json rename to libraries/init-bundle/public/.vscode/extensions.json diff --git a/packages/init-bundle/public/.vscode/settings.json b/libraries/init-bundle/public/.vscode/settings.json similarity index 99% rename from packages/init-bundle/public/.vscode/settings.json rename to libraries/init-bundle/public/.vscode/settings.json index ef308687..3489537f 100644 --- a/packages/init-bundle/public/.vscode/settings.json +++ b/libraries/init-bundle/public/.vscode/settings.json @@ -132,6 +132,6 @@ "sound": "on" }, "kiroAgent.experiments": { - + } } diff --git a/packages/init-bundle/public/app/global.cn.mdx b/libraries/init-bundle/public/app/global.cn.mdx similarity index 100% rename from packages/init-bundle/public/app/global.cn.mdx rename to libraries/init-bundle/public/app/global.cn.mdx diff --git a/packages/init-bundle/public/public/exclude b/libraries/init-bundle/public/public/exclude similarity index 100% rename from packages/init-bundle/public/public/exclude rename to libraries/init-bundle/public/public/exclude diff --git a/packages/init-bundle/public/public/gitignore b/libraries/init-bundle/public/public/gitignore similarity index 100% rename from packages/init-bundle/public/public/gitignore rename to libraries/init-bundle/public/public/gitignore diff --git a/cli/public/kiro_global_powers_registry.json b/libraries/init-bundle/public/public/kiro_global_powers_registry.json similarity index 100% rename from cli/public/kiro_global_powers_registry.json rename to libraries/init-bundle/public/public/kiro_global_powers_registry.json diff --git a/packages/init-bundle/public/public/tnmsc.example.json b/libraries/init-bundle/public/public/tnmsc.example.json similarity index 96% rename from packages/init-bundle/public/public/tnmsc.example.json rename to libraries/init-bundle/public/public/tnmsc.example.json index fe4f4cb3..b27d84fa 100644 --- a/packages/init-bundle/public/public/tnmsc.example.json +++ b/libraries/init-bundle/public/public/tnmsc.example.json @@ -1,6 +1,6 @@ { "$schema": "https://unpkg.com/@truenine/memory-sync-cli/dist/tnmsc.schema.json", - "version": "2026.10222.10836", + "version": "2026.10223.10555", "workspaceDir": "~/project", "shadowSourceProject": { "name": "tnmsc-shadow", diff --git a/packages/init-bundle/public/src/skills/prompt-builder/child-memory-prompt.cn.mdx b/libraries/init-bundle/public/src/skills/prompt-builder/child-memory-prompt.cn.mdx similarity index 100% rename from packages/init-bundle/public/src/skills/prompt-builder/child-memory-prompt.cn.mdx rename to libraries/init-bundle/public/src/skills/prompt-builder/child-memory-prompt.cn.mdx diff --git a/packages/init-bundle/public/src/skills/prompt-builder/global-memory-prompt.cn.mdx b/libraries/init-bundle/public/src/skills/prompt-builder/global-memory-prompt.cn.mdx similarity index 100% rename from packages/init-bundle/public/src/skills/prompt-builder/global-memory-prompt.cn.mdx rename to libraries/init-bundle/public/src/skills/prompt-builder/global-memory-prompt.cn.mdx diff --git a/packages/init-bundle/public/src/skills/prompt-builder/root-memory-prompt.cn.mdx b/libraries/init-bundle/public/src/skills/prompt-builder/root-memory-prompt.cn.mdx similarity index 100% rename from packages/init-bundle/public/src/skills/prompt-builder/root-memory-prompt.cn.mdx rename to libraries/init-bundle/public/src/skills/prompt-builder/root-memory-prompt.cn.mdx diff --git a/libraries/init-bundle/src/index.ts b/libraries/init-bundle/src/index.ts new file mode 100644 index 00000000..d8217d57 --- /dev/null +++ b/libraries/init-bundle/src/index.ts @@ -0,0 +1,67 @@ +import {createRequire} from 'node:module' +import process from 'node:process' + +export interface RuntimeBundleItem { + readonly path: string + readonly content: string +} + +export type RuntimeBundles = Readonly> + +interface NapiInitBundleModule { + getBundles: () => RuntimeBundleItem[] + getDefaultConfigContentStr: () => string + getBundleByPath: (path: string) => RuntimeBundleItem | null +} + +let napiBinding: NapiInitBundleModule | null = null + +try { + const _require = createRequire(import.meta.url) + const {platform, arch} = process + const platforms: Record = { + 'win32-x64': ['napi-init-bundle.win32-x64-msvc', 'win32-x64-msvc'], + 'linux-x64': ['napi-init-bundle.linux-x64-gnu', 'linux-x64-gnu'], + 'linux-arm64': ['napi-init-bundle.linux-arm64-gnu', 'linux-arm64-gnu'], + 'darwin-arm64': ['napi-init-bundle.darwin-arm64', 'darwin-arm64'], + 'darwin-x64': ['napi-init-bundle.darwin-x64', 'darwin-x64'] + } + const entry = platforms[`${platform}-${arch}`] + if (entry != null) { + const [local, suffix] = entry + try { + napiBinding = _require(`./${local}.node`) as NapiInitBundleModule + } + catch { + try { + const pkg = _require(`@truenine/memory-sync-cli-${suffix}`) as Record + napiBinding = pkg['initBundle'] as NapiInitBundleModule + } + catch {} + } + } +} +catch {} // Native module not available — no pure-TS fallback for init-bundle + +if (napiBinding == null && process.env['__TNMSC_INIT_BUNDLE_WARNED__'] == null) { + process.env['__TNMSC_INIT_BUNDLE_WARNED__'] = '1' + console.warn('[tnmsc:init-bundle] Native module not available — init templates will be empty. Install the platform-specific package for your OS to enable embedded file templates.') +} + +function buildBundlesMap(): RuntimeBundles { + if (napiBinding == null) return {} + const items = napiBinding.getBundles() + return Object.fromEntries(items.map(item => [item.path, item])) +} + +export const bundles: RuntimeBundles = buildBundlesMap() + +export function getDefaultConfigContent(): string { + if (napiBinding == null) return '{}' + return napiBinding.getDefaultConfigContentStr() +} + +export function getBundleByPath(path: string): RuntimeBundleItem | null { + if (napiBinding == null) return null + return napiBinding.getBundleByPath(path) +} diff --git a/libraries/init-bundle/src/lib.rs b/libraries/init-bundle/src/lib.rs new file mode 100644 index 00000000..540a303a --- /dev/null +++ b/libraries/init-bundle/src/lib.rs @@ -0,0 +1,179 @@ +#![deny(clippy::all)] + +//! Embedded file templates for the `tnmsc init` command. +//! +//! Templates are embedded at compile time via `include_str!()`. +//! Mirrors the TS `@truenine/init-bundle` package's `bundlePaths` list. + +/// A single bundle item: relative path + embedded content. +pub struct BundleItem { + pub path: &'static str, + pub content: &'static str, +} + +/// Base path for include_str! macros (relative to this source file). +/// Points to: packages/init-bundle/public/ +const _BASE: &str = "packages/init-bundle/public"; + +/// All embedded bundle items, matching the TS `bundlePaths` list. +pub static BUNDLES: &[BundleItem] = &[ + BundleItem { + path: "app/global.cn.mdx", + content: include_str!("../public/app/global.cn.mdx"), + }, + BundleItem { + path: ".idea/.gitignore", + content: include_str!("../public/.idea/.gitignore"), + }, + BundleItem { + path: ".idea/codeStyles/Project.xml", + content: include_str!("../public/.idea/codeStyles/Project.xml"), + }, + BundleItem { + path: ".idea/codeStyles/codeStyleConfig.xml", + content: include_str!("../public/.idea/codeStyles/codeStyleConfig.xml"), + }, + BundleItem { + path: ".vscode/settings.json", + content: include_str!("../public/.vscode/settings.json"), + }, + BundleItem { + path: ".vscode/extensions.json", + content: include_str!("../public/.vscode/extensions.json"), + }, + BundleItem { + path: ".editorconfig", + content: include_str!("../public/.editorconfig"), + }, + BundleItem { + path: ".gitignore", + content: include_str!("../public/.gitignore"), + }, + BundleItem { + path: "public/tnmsc.example.json", + content: include_str!("../public/public/tnmsc.example.json"), + }, + BundleItem { + path: "public/exclude", + content: include_str!("../public/public/exclude"), + }, + BundleItem { + path: "public/gitignore", + content: include_str!("../public/public/gitignore"), + }, + BundleItem { + path: "public/kiro_global_powers_registry.json", + content: include_str!("../public/public/kiro_global_powers_registry.json"), + }, + BundleItem { + path: "src/skills/prompt-builder/global-memory-prompt.cn.mdx", + content: include_str!("../public/src/skills/prompt-builder/global-memory-prompt.cn.mdx"), + }, + BundleItem { + path: "src/skills/prompt-builder/root-memory-prompt.cn.mdx", + content: include_str!("../public/src/skills/prompt-builder/root-memory-prompt.cn.mdx"), + }, + BundleItem { + path: "src/skills/prompt-builder/child-memory-prompt.cn.mdx", + content: include_str!("../public/src/skills/prompt-builder/child-memory-prompt.cn.mdx"), + }, +]; + +/// Get the default user config JSON content (from `public/tnmsc.example.json`). +pub fn get_default_config_content() -> &'static str { + BUNDLES + .iter() + .find(|b| b.path == "public/tnmsc.example.json") + .map(|b| b.content) + .unwrap_or("{}") +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_bundles_not_empty() { + assert!(!BUNDLES.is_empty()); + assert_eq!(BUNDLES.len(), 15); + } + + #[test] + fn test_each_bundle_has_content() { + for bundle in BUNDLES { + assert!(!bundle.path.is_empty(), "Bundle path should not be empty"); + assert!(!bundle.content.is_empty(), "Bundle content for '{}' should not be empty", bundle.path); + } + } + + #[test] + fn test_default_config_is_valid_json() { + let content = get_default_config_content(); + let parsed: serde_json::Result = serde_json::from_str(content); + assert!(parsed.is_ok(), "Default config should be valid JSON"); + let val = parsed.unwrap(); + assert!(val.is_object(), "Default config should be a JSON object"); + } + + #[test] + fn test_bundle_paths_match_ts() { + let expected_paths = [ + "app/global.cn.mdx", + ".idea/.gitignore", + ".idea/codeStyles/Project.xml", + ".idea/codeStyles/codeStyleConfig.xml", + ".vscode/settings.json", + ".vscode/extensions.json", + ".editorconfig", + ".gitignore", + "public/tnmsc.example.json", + "public/exclude", + "public/gitignore", + "public/kiro_global_powers_registry.json", + "src/skills/prompt-builder/global-memory-prompt.cn.mdx", + "src/skills/prompt-builder/root-memory-prompt.cn.mdx", + "src/skills/prompt-builder/child-memory-prompt.cn.mdx", + ]; + for (i, expected) in expected_paths.iter().enumerate() { + assert_eq!(BUNDLES[i].path, *expected, "Bundle path mismatch at index {i}"); + } + } +} + +// =========================================================================== +// NAPI binding layer (only compiled with --features napi) +// =========================================================================== + +#[cfg(feature = "napi")] +mod napi_binding { + use napi_derive::napi; + use super::{BUNDLES, get_default_config_content}; + + #[napi(object)] + pub struct NapiBundleItem { + pub path: String, + pub content: String, + } + + #[napi] + pub fn get_bundles() -> Vec { + BUNDLES.iter().map(|b| NapiBundleItem { + path: b.path.to_string(), + content: b.content.to_string(), + }).collect() + } + + #[napi] + pub fn get_default_config_content_str() -> String { + get_default_config_content().to_string() + } + + #[napi] + pub fn get_bundle_by_path(path: String) -> Option { + BUNDLES.iter().find(|b| b.path == path).map(|b| NapiBundleItem { + path: b.path.to_string(), + content: b.content.to_string(), + }) + } +} + diff --git a/packages/md-compiler/tsconfig.json b/libraries/init-bundle/tsconfig.json similarity index 78% rename from packages/md-compiler/tsconfig.json rename to libraries/init-bundle/tsconfig.json index c31ab7f9..0950f1da 100644 --- a/packages/md-compiler/tsconfig.json +++ b/libraries/init-bundle/tsconfig.json @@ -3,15 +3,15 @@ "compilerOptions": { "noUncheckedSideEffectImports": true, "incremental": true, - "composite": false, // Projects - "target": "ESNext", // Language and Environment + "composite": false, + "target": "ESNext", "lib": [ "ESNext" ], "moduleDetection": "force", "useDefineForClassFields": true, - "baseUrl": ".", // Path Mapping - "module": "ESNext", // Module Resolution + "baseUrl": ".", + "module": "ESNext", "moduleResolution": "Bundler", "paths": { "@/*": [ @@ -20,7 +20,7 @@ }, "resolveJsonModule": true, "allowImportingTsExtensions": true, - "strict": true, // Type Checking - Maximum Strictness + "strict": true, "strictBindCallApply": true, "strictFunctionTypes": true, "strictNullChecks": true, @@ -39,7 +39,7 @@ "noUnusedLocals": true, "noUnusedParameters": true, "useUnknownInCatchVariables": true, - "declaration": true, // Emit + "declaration": true, "declarationMap": true, "importHelpers": true, "newLine": "lf", @@ -49,19 +49,17 @@ "sourceMap": true, "stripInternal": true, "allowSyntheticDefaultImports": true, - "esModuleInterop": true, // Interop Constraints + "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "isolatedModules": true, "verbatimModuleSyntax": true, - "skipLibCheck": true // Completeness + "skipLibCheck": true }, "include": [ "src/**/*", "env.d.ts", "eslint.config.ts", - "tsdown.config.ts", - "vite.config.ts", - "vitest.config.ts" + "tsdown.config.ts" ], "exclude": [ "../node_modules", diff --git a/packages/logger/tsconfig.lib.json b/libraries/init-bundle/tsconfig.lib.json similarity index 93% rename from packages/logger/tsconfig.lib.json rename to libraries/init-bundle/tsconfig.lib.json index b2449b37..7df70332 100644 --- a/packages/logger/tsconfig.lib.json +++ b/libraries/init-bundle/tsconfig.lib.json @@ -5,7 +5,7 @@ "composite": true, "rootDir": "./src", "noEmit": false, - "outDir": "../dist", + "outDir": "./dist", "skipLibCheck": true }, "include": [ diff --git a/libraries/init-bundle/tsdown.config.ts b/libraries/init-bundle/tsdown.config.ts new file mode 100644 index 00000000..5cfddf9a --- /dev/null +++ b/libraries/init-bundle/tsdown.config.ts @@ -0,0 +1,18 @@ +import {resolve} from 'node:path' +import {defineConfig} from 'tsdown' + +export default defineConfig([ + { + entry: ['./src/index.ts', '!**/*.{spec,test}.*'], + platform: 'node', + sourcemap: false, + unbundle: false, + inlineOnly: false, + alias: { + '@': resolve('src') + }, + format: ['esm'], + minify: false, + dts: {sourcemap: false} + } +]) diff --git a/libraries/input-plugins/Cargo.toml b/libraries/input-plugins/Cargo.toml new file mode 100644 index 00000000..e4999868 --- /dev/null +++ b/libraries/input-plugins/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "tnmsc-input-plugins" +description = "All 17 input plugins for tnmsc pipeline" +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true + +[lib] +crate-type = ["rlib", "cdylib"] + +[features] +default = [] +napi = ["dep:napi", "dep:napi-derive"] + +[dependencies] +tnmsc-logger = { workspace = true } +tnmsc-md-compiler = { workspace = true } +tnmsc-config = { workspace = true } +tnmsc-plugin-shared = { workspace = true } +tnmsc-init-bundle = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +sha2 = { workspace = true } +base64 = { workspace = true } +glob = { workspace = true } +napi = { workspace = true, optional = true } +napi-derive = { workspace = true, optional = true } + +[build-dependencies] +napi-build = { workspace = true } diff --git a/libraries/input-plugins/build.rs b/libraries/input-plugins/build.rs new file mode 100644 index 00000000..f2be9938 --- /dev/null +++ b/libraries/input-plugins/build.rs @@ -0,0 +1,4 @@ +fn main() { + #[cfg(feature = "napi")] + napi_build::setup(); +} diff --git a/libraries/input-plugins/eslint.config.ts b/libraries/input-plugins/eslint.config.ts new file mode 100644 index 00000000..d1de0a15 --- /dev/null +++ b/libraries/input-plugins/eslint.config.ts @@ -0,0 +1,26 @@ +import {dirname, resolve} from 'node:path' +import {fileURLToPath} from 'node:url' + +import eslint10 from '@truenine/eslint10-config' + +const configDir = dirname(fileURLToPath(import.meta.url)) + +const config = eslint10({ + type: 'lib', + typescript: { + strictTypescriptEslint: true, + tsconfigPath: resolve(configDir, 'tsconfig.json'), + parserOptions: { + allowDefaultProject: true + } + }, + ignores: [ + '.turbo/**', + '*.md', + '**/*.md', + '**/*.toml', + '**/*.d.ts' + ] +}) + +export default config as unknown diff --git a/libraries/input-plugins/index.d.ts b/libraries/input-plugins/index.d.ts new file mode 100644 index 00000000..e69de29b diff --git a/libraries/input-plugins/package.json b/libraries/input-plugins/package.json new file mode 100644 index 00000000..87788b3c --- /dev/null +++ b/libraries/input-plugins/package.json @@ -0,0 +1,72 @@ +{ + "name": "@truenine/input-plugins", + "type": "module", + "version": "2026.10223.10555", + "private": true, + "description": "Rust-powered input plugins for tnmsc pipeline (stub)", + "license": "AGPL-3.0-only", + "exports": { + "./package.json": "./package.json", + ".": { + "types": "./dist/index.d.mts", + "import": "./dist/index.mjs" + } + }, + "module": "dist/index.mjs", + "types": "dist/index.d.mts", + "files": [ + "dist" + ], + "napi": { + "binaryName": "napi-input-plugins", + "npmDir": "npm", + "packageName": "@truenine/input-plugins", + "targets": [ + "x86_64-pc-windows-msvc", + "x86_64-pc-windows-gnu", + "x86_64-unknown-linux-gnu", + "aarch64-apple-darwin", + "x86_64-apple-darwin" + ] + }, + "scripts": { + "build": "tsdown", + "build:all": "run-s build:native build", + "build:native": "napi build --platform --release --output-dir dist -- --features napi", + "build:native:debug": "napi build --platform --output-dir dist -- --features napi", + "build:ts": "tsdown", + "check": "run-p typecheck lint", + "lint": "eslint --cache .", + "lintfix": "eslint --fix --cache .", + "prepublishOnly": "run-s build", + "test": "run-s test:rust test:ts", + "test:rust": "tsx ../../scripts/cargo-test.ts", + "test:ts": "vitest run --passWithNoTests", + "typecheck": "tsc --noEmit -p tsconfig.lib.json" + }, + "dependencies": { + "@emnapi/runtime": "1.8.1", + "lightningcss": "1.31.1", + "synckit": "0.11.12", + "tsx": "4.21.0", + "yaml": "2.8.2" + }, + "optionalDependencies": { + "@truenine/input-plugins-darwin-arm64": "workspace:*", + "@truenine/input-plugins-darwin-x64": "workspace:*", + "@truenine/input-plugins-linux-x64-gnu": "workspace:*", + "@truenine/input-plugins-win32-x64-gnu": "workspace:*", + "@truenine/input-plugins-win32-x64-msvc": "workspace:*" + }, + "devDependencies": { + "@napi-rs/cli": "^3.5.1", + "npm-run-all2": "catalog:", + "tsdown": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + } +} diff --git a/libraries/input-plugins/src/index.ts b/libraries/input-plugins/src/index.ts new file mode 100644 index 00000000..42991b2e --- /dev/null +++ b/libraries/input-plugins/src/index.ts @@ -0,0 +1 @@ +export {} // Stub — Rust input-plugins library is currently empty; will be populated when implemented diff --git a/libraries/input-plugins/src/lib.rs b/libraries/input-plugins/src/lib.rs new file mode 100644 index 00000000..1aca1523 --- /dev/null +++ b/libraries/input-plugins/src/lib.rs @@ -0,0 +1,10 @@ +#![deny(clippy::all)] + +//! All 17 input plugins for the tnmsc pipeline. +//! +//! Plugins are grouped by type: +//! - File readers (workspace, gitignore, editorconfig, vscode, jetbrains) +//! - MDX directory scanners (fast-command, sub-agent, rule, global-memory) +//! - Complex plugins (shadow-project, skill, project-prompt, readme) +//! - Effect plugins (md-cleanup, orphan-cleanup, skill-sync) + diff --git a/libraries/input-plugins/tsconfig.json b/libraries/input-plugins/tsconfig.json new file mode 100644 index 00000000..0950f1da --- /dev/null +++ b/libraries/input-plugins/tsconfig.json @@ -0,0 +1,68 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "noUncheckedSideEffectImports": true, + "incremental": true, + "composite": false, + "target": "ESNext", + "lib": [ + "ESNext" + ], + "moduleDetection": "force", + "useDefineForClassFields": true, + "baseUrl": ".", + "module": "ESNext", + "moduleResolution": "Bundler", + "paths": { + "@/*": [ + "./src/*" + ] + }, + "resolveJsonModule": true, + "allowImportingTsExtensions": true, + "strict": true, + "strictBindCallApply": true, + "strictFunctionTypes": true, + "strictNullChecks": true, + "strictPropertyInitialization": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "alwaysStrict": true, + "exactOptionalPropertyTypes": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noPropertyAccessFromIndexSignature": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "useUnknownInCatchVariables": true, + "declaration": true, + "declarationMap": true, + "importHelpers": true, + "newLine": "lf", + "noEmit": true, + "noEmitHelpers": false, + "removeComments": false, + "sourceMap": true, + "stripInternal": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": [ + "src/**/*", + "env.d.ts", + "eslint.config.ts", + "tsdown.config.ts" + ], + "exclude": [ + "../node_modules", + "dist" + ] +} diff --git a/packages/init-bundle/tsconfig.lib.json b/libraries/input-plugins/tsconfig.lib.json similarity index 87% rename from packages/init-bundle/tsconfig.lib.json rename to libraries/input-plugins/tsconfig.lib.json index 7f296505..7df70332 100644 --- a/packages/init-bundle/tsconfig.lib.json +++ b/libraries/input-plugins/tsconfig.lib.json @@ -2,13 +2,14 @@ "$schema": "https://json.schemastore.org/tsconfig", "extends": "./tsconfig.json", "compilerOptions": { + "composite": true, + "rootDir": "./src", "noEmit": false, "outDir": "./dist", "skipLibCheck": true }, "include": [ "src/**/*", - "structure.config.ts", "env.d.ts" ], "exclude": [ diff --git a/libraries/input-plugins/tsdown.config.ts b/libraries/input-plugins/tsdown.config.ts new file mode 100644 index 00000000..5cfddf9a --- /dev/null +++ b/libraries/input-plugins/tsdown.config.ts @@ -0,0 +1,18 @@ +import {resolve} from 'node:path' +import {defineConfig} from 'tsdown' + +export default defineConfig([ + { + entry: ['./src/index.ts', '!**/*.{spec,test}.*'], + platform: 'node', + sourcemap: false, + unbundle: false, + inlineOnly: false, + alias: { + '@': resolve('src') + }, + format: ['esm'], + minify: false, + dts: {sourcemap: false} + } +]) diff --git a/libraries/logger/Cargo.toml b/libraries/logger/Cargo.toml new file mode 100644 index 00000000..c9500744 --- /dev/null +++ b/libraries/logger/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "tnmsc-logger" +description = "Structured JSON logger with ANSI color support for tnmsc" +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true + +[lib] +crate-type = ["rlib", "cdylib"] + +[features] +default = [] +napi = ["dep:napi", "dep:napi-derive"] + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } +napi = { workspace = true, optional = true } +napi-derive = { workspace = true, optional = true } + +[build-dependencies] +napi-build = { workspace = true } diff --git a/libraries/logger/build.rs b/libraries/logger/build.rs new file mode 100644 index 00000000..f2be9938 --- /dev/null +++ b/libraries/logger/build.rs @@ -0,0 +1,4 @@ +fn main() { + #[cfg(feature = "napi")] + napi_build::setup(); +} diff --git a/libraries/logger/eslint.config.ts b/libraries/logger/eslint.config.ts new file mode 100644 index 00000000..d1de0a15 --- /dev/null +++ b/libraries/logger/eslint.config.ts @@ -0,0 +1,26 @@ +import {dirname, resolve} from 'node:path' +import {fileURLToPath} from 'node:url' + +import eslint10 from '@truenine/eslint10-config' + +const configDir = dirname(fileURLToPath(import.meta.url)) + +const config = eslint10({ + type: 'lib', + typescript: { + strictTypescriptEslint: true, + tsconfigPath: resolve(configDir, 'tsconfig.json'), + parserOptions: { + allowDefaultProject: true + } + }, + ignores: [ + '.turbo/**', + '*.md', + '**/*.md', + '**/*.toml', + '**/*.d.ts' + ] +}) + +export default config as unknown diff --git a/libraries/logger/index.d.ts b/libraries/logger/index.d.ts new file mode 100644 index 00000000..e69de29b diff --git a/libraries/logger/package.json b/libraries/logger/package.json new file mode 100644 index 00000000..fc97cd9d --- /dev/null +++ b/libraries/logger/package.json @@ -0,0 +1,52 @@ +{ + "name": "@truenine/logger", + "type": "module", + "version": "2026.10223.10555", + "private": true, + "description": "Rust-powered structured logger for Node.js with pure-TS fallback", + "license": "AGPL-3.0-only", + "exports": { + "./package.json": "./package.json", + ".": { + "types": "./dist/index.d.mts", + "import": "./dist/index.mjs" + } + }, + "module": "dist/index.mjs", + "types": "dist/index.d.mts", + "files": [ + "dist" + ], + "napi": { + "binaryName": "napi-logger", + "targets": [ + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu", + "aarch64-unknown-linux-gnu", + "aarch64-apple-darwin", + "x86_64-apple-darwin" + ] + }, + "scripts": { + "build": "tsdown", + "build:all": "run-s build:native build", + "build:native": "napi build --platform --release --output-dir dist -- --features napi", + "build:native:debug": "napi build --platform --output-dir dist -- --features napi", + "build:ts": "tsdown", + "check": "run-p typecheck lint", + "lint": "eslint --cache .", + "lintfix": "eslint --fix --cache .", + "prepublishOnly": "run-s build", + "test": "run-s test:rust test:ts", + "test:rust": "tsx ../../scripts/cargo-test.ts", + "test:ts": "vitest run --passWithNoTests", + "typecheck": "tsc --noEmit -p tsconfig.lib.json" + }, + "devDependencies": { + "@napi-rs/cli": "^3.5.1", + "npm-run-all2": "catalog:", + "tsdown": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" + } +} diff --git a/libraries/logger/src/index.ts b/libraries/logger/src/index.ts new file mode 100644 index 00000000..7844e11d --- /dev/null +++ b/libraries/logger/src/index.ts @@ -0,0 +1,255 @@ +import {createRequire} from 'node:module' +import process from 'node:process' + +export type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace' | 'fatal' | 'silent' + +export interface ILogger { + error: (message: string | object, ...meta: unknown[]) => void + warn: (message: string | object, ...meta: unknown[]) => void + info: (message: string | object, ...meta: unknown[]) => void + debug: (message: string | object, ...meta: unknown[]) => void + trace: (message: string | object, ...meta: unknown[]) => void + fatal: (message: string | object, ...meta: unknown[]) => void +} // Napi binding types (loaded at runtime) + +interface NapiLoggerInstance { + error: (message: string) => void + errorWithMeta: (message: string, meta: string) => void + warn: (message: string) => void + warnWithMeta: (message: string, meta: string) => void + info: (message: string) => void + infoWithMeta: (message: string, meta: string) => void + debug: (message: string) => void + debugWithMeta: (message: string, meta: string) => void + trace: (message: string) => void + traceWithMeta: (message: string, meta: string) => void + fatal: (message: string) => void + fatalWithMeta: (message: string, meta: string) => void +} + +interface NapiLoggerModule { + createLogger: (namespace: string, level?: string) => NapiLoggerInstance + setGlobalLogLevel: (level: string) => void + getGlobalLogLevel: () => string | undefined +} // Load napi binding (CJS) with fallback to pure-TS implementation + +let napiBinding: NapiLoggerModule | null = null + +try { + const require = createRequire(import.meta.url) + const {platform, arch} = process + const platforms: Record = { + 'win32-x64': ['napi-logger.win32-x64-msvc', 'win32-x64-msvc'], + 'linux-x64': ['napi-logger.linux-x64-gnu', 'linux-x64-gnu'], + 'linux-arm64': ['napi-logger.linux-arm64-gnu', 'linux-arm64-gnu'], + 'darwin-arm64': ['napi-logger.darwin-arm64', 'darwin-arm64'], + 'darwin-x64': ['napi-logger.darwin-x64', 'darwin-x64'] + } + const entry = platforms[`${platform}-${arch}`] + if (entry != null) { + const [local, suffix] = entry + try { + napiBinding = require(`./${local}.node`) as NapiLoggerModule + } + catch { + try { + const pkg = require(`@truenine/memory-sync-cli-${suffix}`) as Record + napiBinding = pkg['logger'] as NapiLoggerModule + } + catch {} + } + } +} +catch {} // Native module not available — fall back to pure-TS implementation + +const colors = { + reset: '\x1B[0m', + red: '\x1B[31m', + yellow: '\x1B[33m', + cyan: '\x1B[36m', + magenta: '\x1B[35m', + gray: '\x1B[90m', + blue: '\x1B[34m', + green: '\x1B[32m', + white: '\x1B[37m', + dim: '\x1B[2m', + bgRed: '\x1B[41m' +} as const + +const colorize = { + red: (text: string) => `${colors.red}${text}${colors.reset}`, + yellow: (text: string) => `${colors.yellow}${text}${colors.reset}`, + cyan: (text: string) => `${colors.cyan}${text}${colors.reset}`, + magenta: (text: string) => `${colors.magenta}${text}${colors.reset}`, + gray: (text: string) => `${colors.gray}${text}${colors.reset}`, + blue: (text: string) => `${colors.blue}${text}${colors.reset}`, + green: (text: string) => `${colors.green}${text}${colors.reset}`, + white: (text: string) => `${colors.white}${text}${colors.reset}`, + dim: (text: string) => `${colors.dim}${text}${colors.reset}`, + bgRed: (text: string) => `${colors.bgRed}${text}${colors.reset}` +} + +let globalLogLevel: LogLevel | undefined + +const LEVEL_COLORS: Record string> = { + error: colorize.red, + warn: colorize.yellow, + info: colorize.cyan, + debug: colorize.magenta, + trace: colorize.gray, + fatal: colorize.bgRed +} + +const LEVEL_PRIORITY: Record = { + silent: 0, + fatal: 1, + error: 2, + warn: 3, + info: 4, + debug: 5, + trace: 6 +} + +function colorizeValue(value: unknown): string { + if (value === null) return colorize.dim('null') + if (typeof value === 'undefined') return colorize.dim('undefined') + if (typeof value === 'boolean') return colorize.yellow(String(value)) + if (typeof value === 'number') return colorize.blue(String(value)) + if (typeof value === 'string') return colorize.green(`"${value}"`) + if (Array.isArray(value)) { + if (value.length === 0) return '[]' + return `[${value.map(v => colorizeValue(v)).join(',')}]` + } + if (value instanceof Error) { + const errorObj: Record = { + name: value.name, + message: value.message, + stack: value.stack + } + for (const key of Object.getOwnPropertyNames(value)) { + if (key !== 'name' && key !== 'message' && key !== 'stack') errorObj[key] = (value as unknown as Record)[key] + } + return tsToJson(errorObj) + } + if (typeof value === 'object') return tsToJson(value as Record) + return String(value) +} + +function tsToJson(obj: Record): string { + const entries = Object.entries(obj) + if (entries.length === 0) return '{}' + const parts = entries.map(([k, v]) => { + const key = colorize.magenta(`"${k}"`) + return `${key}:${colorizeValue(v)}` + }) + return `{${parts.join(',')}}` +} + +function getTimestamp(): string { + const now = new Date() + const hours = String(now.getHours()).padStart(2, '0') + const minutes = String(now.getMinutes()).padStart(2, '0') + const seconds = String(now.getSeconds()).padStart(2, '0') + const ms = String(now.getMilliseconds()).padStart(3, '0') + return `${hours}:${minutes}:${seconds}.${ms}` +} + +function formatLog(level: LogLevel, namespace: string, message: unknown, meta?: Record): void { + const timestamp = getTimestamp() + const colorFn = LEVEL_COLORS[level] ?? colorize.white + const messageStr = String(message) + const hasMeta = meta != null && Object.keys(meta).length > 0 + const isEmptyMessage = messageStr === '' + const base = {$: [timestamp, colorFn(level.toUpperCase()), namespace]} + const _ = hasMeta ? isEmptyMessage ? meta : {[messageStr]: meta} : message + const output = tsToJson({...base, _} as unknown as Record) + if (level === 'error' || level === 'fatal') console.error(output) + else if (level === 'warn') console.warn(output) + // eslint-disable-next-line no-console + else if (level === 'debug' || level === 'trace') console.debug(output) + // eslint-disable-next-line no-console + else console.log(output) +} + +function createTsLevelMethod(level: LogLevel, namespace: string, currentLevel: LogLevel) { + const levelPriority = LEVEL_PRIORITY[level] + const currentPriority = LEVEL_PRIORITY[currentLevel] + return (messageOrObject: string | object, ...meta: unknown[]): void => { + if (levelPriority > currentPriority) return + if (typeof messageOrObject === 'string') { + const metaObj = meta.length === 1 && typeof meta[0] === 'object' && meta[0] !== null + ? meta[0] as Record + : meta.length > 0 ? {args: meta} : void 0 + formatLog(level, namespace, messageOrObject, metaObj) + } else if (typeof messageOrObject === 'object' && messageOrObject !== null) formatLog(level, namespace, '', messageOrObject as Record) + else formatLog(level, namespace, messageOrObject) + } +} + +function createTsFallbackLogger(namespace: string, logLevel?: LogLevel): ILogger { + const level = logLevel ?? globalLogLevel ?? (process.env['LOG_LEVEL'] as LogLevel) ?? 'info' + return { + error: createTsLevelMethod('error', namespace, level), + warn: createTsLevelMethod('warn', namespace, level), + info: createTsLevelMethod('info', namespace, level), + debug: createTsLevelMethod('debug', namespace, level), + trace: createTsLevelMethod('trace', namespace, level), + fatal: createTsLevelMethod('fatal', namespace, level) + } +} // Napi adapter — wraps NapiLoggerInstance to implement ILogger + +function serializeMeta(message: string | object, meta: unknown[]): {msg: string, metaStr: string | undefined} { + if (typeof message !== 'string') return {msg: '', metaStr: JSON.stringify(message)} + + const metaObj = meta.length === 1 && typeof meta[0] === 'object' && meta[0] !== null + ? meta[0] + : meta.length > 0 ? {args: meta} : void 0 + return {msg: message, metaStr: metaObj != null ? JSON.stringify(metaObj) : void 0} +} + +function createNapiAdapter(instance: NapiLoggerInstance): ILogger { + function makeMethod( + plain: (msg: string) => void, + withMeta: (msg: string, meta: string) => void + ) { + return (message: string | object, ...meta: unknown[]): void => { + const {msg, metaStr} = serializeMeta(message, meta) + if (metaStr != null) withMeta(msg, metaStr) + else plain(msg) + } + } + return { + error: makeMethod(m => instance.error(m), (m, s) => instance.errorWithMeta(m, s)), + warn: makeMethod(m => instance.warn(m), (m, s) => instance.warnWithMeta(m, s)), + info: makeMethod(m => instance.info(m), (m, s) => instance.infoWithMeta(m, s)), + debug: makeMethod(m => instance.debug(m), (m, s) => instance.debugWithMeta(m, s)), + trace: makeMethod(m => instance.trace(m), (m, s) => instance.traceWithMeta(m, s)), + fatal: makeMethod(m => instance.fatal(m), (m, s) => instance.fatalWithMeta(m, s)) + } +} // Public API + +/** + * Set the global log level for all loggers. + */ +export function setGlobalLogLevel(level: LogLevel): void { + globalLogLevel = level + napiBinding?.setGlobalLogLevel(level) +} + +/** + * Get the current global log level. + */ +export function getGlobalLogLevel(): LogLevel | undefined { + if (napiBinding != null) return napiBinding.getGlobalLogLevel() as LogLevel | undefined + return globalLogLevel +} + +/** + * Create a logger. Uses Rust napi-logger when available, falls back to pure-TS. + */ +export function createLogger(namespace: string, logLevel?: LogLevel): ILogger { + if (napiBinding == null) return createTsFallbackLogger(namespace, logLevel) + + const instance = napiBinding.createLogger(namespace, logLevel) + return createNapiAdapter(instance) +} diff --git a/libraries/logger/src/lib.rs b/libraries/logger/src/lib.rs new file mode 100644 index 00000000..a2b7c1e1 --- /dev/null +++ b/libraries/logger/src/lib.rs @@ -0,0 +1,560 @@ +#![deny(clippy::all)] + +//! Structured JSON logger with ANSI color support. +//! +//! Output format: `{"$":["HH:MM:SS.mmm","LEVEL","namespace"],"_":{...payload}}` +//! +//! This logger is designed to be consumed by both CLI (human-readable with colors) +//! and GUI (parsed as JSON after stripping ANSI codes). + +use std::sync::atomic::{AtomicU8, Ordering}; + +use serde::Serialize; +use serde_json::Value; + +// --------------------------------------------------------------------------- +// ANSI colors +// --------------------------------------------------------------------------- + +const RESET: &str = "\x1B[0m"; +const RED: &str = "\x1B[31m"; +const YELLOW: &str = "\x1B[33m"; +const CYAN: &str = "\x1B[36m"; +const MAGENTA: &str = "\x1B[35m"; +const GRAY: &str = "\x1B[90m"; +const BLUE: &str = "\x1B[34m"; +const GREEN: &str = "\x1B[32m"; +const WHITE: &str = "\x1B[37m"; +const DIM: &str = "\x1B[2m"; +const BG_RED: &str = "\x1B[41m"; + +fn colorize(color: &str, text: &str) -> String { + format!("{color}{text}{RESET}") +} + +// --------------------------------------------------------------------------- +// Log levels +// --------------------------------------------------------------------------- + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize)] +#[serde(rename_all = "lowercase")] +pub enum LogLevel { + Silent, + Fatal, + Error, + Warn, + Info, + Debug, + Trace, +} + +impl LogLevel { + fn priority(self) -> u8 { + match self { + Self::Silent => 0, + Self::Fatal => 1, + Self::Error => 2, + Self::Warn => 3, + Self::Info => 4, + Self::Debug => 5, + Self::Trace => 6, + } + } + + fn color_fn(self) -> fn(&str) -> String { + match self { + Self::Error => |s| colorize(RED, s), + Self::Warn => |s| colorize(YELLOW, s), + Self::Info => |s| colorize(CYAN, s), + Self::Debug => |s| colorize(MAGENTA, s), + Self::Trace => |s| colorize(GRAY, s), + Self::Fatal => |s| colorize(BG_RED, s), + Self::Silent => |s| colorize(WHITE, s), + } + } + + fn as_str(self) -> &'static str { + match self { + Self::Silent => "silent", + Self::Fatal => "fatal", + Self::Error => "error", + Self::Warn => "warn", + Self::Info => "info", + Self::Debug => "debug", + Self::Trace => "trace", + } + } + + pub fn from_str_loose(s: &str) -> Option { + match s.to_ascii_lowercase().as_str() { + "silent" => Some(Self::Silent), + "fatal" => Some(Self::Fatal), + "error" => Some(Self::Error), + "warn" => Some(Self::Warn), + "info" => Some(Self::Info), + "debug" => Some(Self::Debug), + "trace" => Some(Self::Trace), + _ => None, + } + } +} + +// --------------------------------------------------------------------------- +// LogRecord (the structured return value) +// --------------------------------------------------------------------------- + +#[derive(Debug, Clone, Serialize)] +pub struct LogRecord { + #[serde(rename = "$")] + pub meta: (String, String, String), + #[serde(rename = "_")] + pub payload: Value, +} + +// --------------------------------------------------------------------------- +// Global log level +// --------------------------------------------------------------------------- + +static GLOBAL_LOG_LEVEL: AtomicU8 = AtomicU8::new(255); // 255 = unset + +/// Set the global log level for all loggers. +pub fn set_global_log_level(level: LogLevel) { + GLOBAL_LOG_LEVEL.store(level.priority(), Ordering::Relaxed); +} + +/// Get the current global log level. +pub fn get_global_log_level() -> Option { + let v = GLOBAL_LOG_LEVEL.load(Ordering::Relaxed); + if v == 255 { None } else { priority_to_level(v) } +} + +fn priority_to_level(p: u8) -> Option { + match p { + 0 => Some(LogLevel::Silent), + 1 => Some(LogLevel::Fatal), + 2 => Some(LogLevel::Error), + 3 => Some(LogLevel::Warn), + 4 => Some(LogLevel::Info), + 5 => Some(LogLevel::Debug), + 6 => Some(LogLevel::Trace), + _ => None, + } +} + +fn resolve_log_level(explicit: Option) -> LogLevel { + if let Some(l) = explicit { + return l; + } + if let Some(l) = get_global_log_level() { + return l; + } + if let Ok(env_val) = std::env::var("LOG_LEVEL") { + if let Some(l) = LogLevel::from_str_loose(&env_val) { + return l; + } + } + LogLevel::Info +} + +// --------------------------------------------------------------------------- +// Colorized JSON formatting (matches TS toJson / colorizeValue) +// --------------------------------------------------------------------------- + +fn colorize_value(val: &Value) -> String { + match val { + Value::Null => colorize(DIM, "null"), + Value::Bool(b) => colorize(YELLOW, &b.to_string()), + Value::Number(n) => colorize(BLUE, &n.to_string()), + Value::String(s) => colorize(GREEN, &format!("\"{}\"", s)), + Value::Array(arr) => { + if arr.is_empty() { + "[]".to_string() + } else { + let parts: Vec = arr.iter().map(colorize_value).collect(); + format!("[{}]", parts.join(",")) + } + } + Value::Object(map) => { + if map.is_empty() { + "{}".to_string() + } else { + let parts: Vec = map + .iter() + .map(|(k, v)| { + let key = colorize(MAGENTA, &format!("\"{}\"", k)); + format!("{}:{}", key, colorize_value(v)) + }) + .collect(); + format!("{{{}}}", parts.join(",")) + } + } + } +} + +fn to_colored_json(val: &Value) -> String { + colorize_value(val) +} + +// --------------------------------------------------------------------------- +// Timestamp +// --------------------------------------------------------------------------- + +#[allow(dead_code)] +fn get_timestamp() -> String { + let now = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default(); + let total_ms = now.as_millis(); + let ms = (total_ms % 1000) as u32; + let total_secs = (total_ms / 1000) as u64; + let secs = (total_secs % 60) as u32; + let total_mins = total_secs / 60; + let mins = (total_mins % 60) as u32; + let hours = ((total_mins / 60) % 24) as u32; + + format!("{:02}:{:02}:{:02}.{:03}", hours, mins, secs, ms) +} + +#[cfg(windows)] +fn get_local_timestamp() -> String { + #[repr(C)] + struct SystemTime { + w_year: u16, + w_month: u16, + w_day_of_week: u16, + w_day: u16, + w_hour: u16, + w_minute: u16, + w_second: u16, + w_milliseconds: u16, + } + unsafe extern "system" { + fn GetLocalTime(lp_system_time: *mut SystemTime); + } + let mut st = SystemTime { + w_year: 0, w_month: 0, w_day_of_week: 0, w_day: 0, + w_hour: 0, w_minute: 0, w_second: 0, w_milliseconds: 0, + }; + unsafe { GetLocalTime(&mut st); } + format!("{:02}:{:02}:{:02}.{:03}", st.w_hour, st.w_minute, st.w_second, st.w_milliseconds) +} + +#[cfg(not(windows))] +fn get_local_timestamp() -> String { + get_timestamp() +} + +fn timestamp() -> String { + get_local_timestamp() +} + +// --------------------------------------------------------------------------- +// Format and print +// --------------------------------------------------------------------------- + +fn format_log( + level: LogLevel, + namespace: &str, + message: &Value, + meta: Option<&Value>, +) -> LogRecord { + let ts = timestamp(); + let color_fn = level.color_fn(); + + let payload = match meta { + Some(meta_val) if meta_val.is_object() && !meta_val.as_object().unwrap().is_empty() => { + let msg_str = match message { + Value::String(s) => s.clone(), + _ => String::new(), + }; + if msg_str.is_empty() { + meta_val.clone() + } else { + let mut map = serde_json::Map::new(); + map.insert(msg_str, meta_val.clone()); + Value::Object(map) + } + } + _ => message.clone(), + }; + + let record = LogRecord { + meta: (ts.clone(), level.as_str().to_string(), namespace.to_string()), + payload: payload.clone(), + }; + + let colored_level = color_fn(&level.as_str().to_ascii_uppercase()); + let base_meta = Value::Array(vec![ + Value::String(ts), + Value::String(colored_level), + Value::String(namespace.to_string()), + ]); + + let mut output_map = serde_json::Map::new(); + output_map.insert("$".to_string(), base_meta); + output_map.insert("_".to_string(), payload); + let output = to_colored_json(&Value::Object(output_map)); + + match level { + LogLevel::Error | LogLevel::Fatal => eprintln!("{}", output), + LogLevel::Warn => eprintln!("{}", output), + LogLevel::Debug | LogLevel::Trace => eprintln!("{}", output), + _ => println!("{}", output), + } + + record +} + +// --------------------------------------------------------------------------- +// Logger +// --------------------------------------------------------------------------- + +pub struct Logger { + namespace: String, + level: LogLevel, +} + +impl Logger { + pub fn error(&self, message: impl Into, meta: Option) -> Option { + self.log(LogLevel::Error, message.into(), meta) + } + + pub fn warn(&self, message: impl Into, meta: Option) -> Option { + self.log(LogLevel::Warn, message.into(), meta) + } + + pub fn info(&self, message: impl Into, meta: Option) -> Option { + self.log(LogLevel::Info, message.into(), meta) + } + + pub fn debug(&self, message: impl Into, meta: Option) -> Option { + self.log(LogLevel::Debug, message.into(), meta) + } + + pub fn trace(&self, message: impl Into, meta: Option) -> Option { + self.log(LogLevel::Trace, message.into(), meta) + } + + pub fn fatal(&self, message: impl Into, meta: Option) -> Option { + self.log(LogLevel::Fatal, message.into(), meta) + } + + fn log(&self, level: LogLevel, message: Value, meta: Option) -> Option { + if level.priority() > self.level.priority() { + return None; + } + Some(format_log(level, &self.namespace, &message, meta.as_ref())) + } +} + +/// Create a new logger with the given namespace and optional log level. +pub fn create_logger(namespace: &str, log_level: Option) -> Logger { + Logger { + namespace: namespace.to_string(), + level: resolve_log_level(log_level), + } +} + +// --------------------------------------------------------------------------- +// Convenience macros +// --------------------------------------------------------------------------- + +#[macro_export] +macro_rules! log_info { + ($logger:expr, $msg:expr) => { + $logger.info(serde_json::Value::String($msg.to_string()), None) + }; + ($logger:expr, $msg:expr, $meta:expr) => { + $logger.info(serde_json::Value::String($msg.to_string()), Some($meta)) + }; +} + +#[macro_export] +macro_rules! log_error { + ($logger:expr, $msg:expr) => { + $logger.error(serde_json::Value::String($msg.to_string()), None) + }; + ($logger:expr, $msg:expr, $meta:expr) => { + $logger.error(serde_json::Value::String($msg.to_string()), Some($meta)) + }; +} + +#[macro_export] +macro_rules! log_warn { + ($logger:expr, $msg:expr) => { + $logger.warn(serde_json::Value::String($msg.to_string()), None) + }; + ($logger:expr, $msg:expr, $meta:expr) => { + $logger.warn(serde_json::Value::String($msg.to_string()), Some($meta)) + }; +} + +#[macro_export] +macro_rules! log_debug { + ($logger:expr, $msg:expr) => { + $logger.debug(serde_json::Value::String($msg.to_string()), None) + }; + ($logger:expr, $msg:expr, $meta:expr) => { + $logger.debug(serde_json::Value::String($msg.to_string()), Some($meta)) + }; +} + +// =========================================================================== +// NAPI binding layer (only compiled with --features napi) +// =========================================================================== + +#[cfg(feature = "napi")] +mod napi_binding { + use napi_derive::napi; + use serde_json::Value; + use super::{LogLevel, Logger, create_logger as core_create_logger, set_global_log_level as core_set_global, get_global_log_level as core_get_global}; + + fn parse_level(s: &str) -> Option { + LogLevel::from_str_loose(s) + } + + #[napi] + pub struct NapiLogger { + inner: Logger, + } + + #[napi] + impl NapiLogger { + #[napi] + pub fn error(&self, message: String) { + self.inner.error(Value::String(message), None); + } + + #[napi] + pub fn error_with_meta(&self, message: String, meta: String) { + let meta_val: Value = serde_json::from_str(&meta).unwrap_or(Value::String(meta)); + self.inner.error(Value::String(message), Some(meta_val)); + } + + #[napi] + pub fn warn(&self, message: String) { + self.inner.warn(Value::String(message), None); + } + + #[napi] + pub fn warn_with_meta(&self, message: String, meta: String) { + let meta_val: Value = serde_json::from_str(&meta).unwrap_or(Value::String(meta)); + self.inner.warn(Value::String(message), Some(meta_val)); + } + + #[napi] + pub fn info(&self, message: String) { + self.inner.info(Value::String(message), None); + } + + #[napi] + pub fn info_with_meta(&self, message: String, meta: String) { + let meta_val: Value = serde_json::from_str(&meta).unwrap_or(Value::String(meta)); + self.inner.info(Value::String(message), Some(meta_val)); + } + + #[napi] + pub fn debug(&self, message: String) { + self.inner.debug(Value::String(message), None); + } + + #[napi] + pub fn debug_with_meta(&self, message: String, meta: String) { + let meta_val: Value = serde_json::from_str(&meta).unwrap_or(Value::String(meta)); + self.inner.debug(Value::String(message), Some(meta_val)); + } + + #[napi] + pub fn trace(&self, message: String) { + self.inner.trace(Value::String(message), None); + } + + #[napi] + pub fn trace_with_meta(&self, message: String, meta: String) { + let meta_val: Value = serde_json::from_str(&meta).unwrap_or(Value::String(meta)); + self.inner.trace(Value::String(message), Some(meta_val)); + } + + #[napi] + pub fn fatal(&self, message: String) { + self.inner.fatal(Value::String(message), None); + } + + #[napi] + pub fn fatal_with_meta(&self, message: String, meta: String) { + let meta_val: Value = serde_json::from_str(&meta).unwrap_or(Value::String(meta)); + self.inner.fatal(Value::String(message), Some(meta_val)); + } + } + + #[napi] + pub fn create_logger(namespace: String, level: Option) -> NapiLogger { + let log_level = level.as_deref().and_then(parse_level); + NapiLogger { + inner: core_create_logger(&namespace, log_level), + } + } + + #[napi] + pub fn set_global_log_level(level: String) { + if let Some(l) = parse_level(&level) { + core_set_global(l); + } + } + + #[napi] + pub fn get_global_log_level() -> Option { + core_get_global().map(|l| l.as_str().to_string()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_log_level_priority() { + assert!(LogLevel::Silent.priority() < LogLevel::Fatal.priority()); + assert!(LogLevel::Fatal.priority() < LogLevel::Error.priority()); + assert!(LogLevel::Error.priority() < LogLevel::Warn.priority()); + assert!(LogLevel::Warn.priority() < LogLevel::Info.priority()); + assert!(LogLevel::Info.priority() < LogLevel::Debug.priority()); + assert!(LogLevel::Debug.priority() < LogLevel::Trace.priority()); + } + + #[test] + fn test_log_level_from_str() { + assert_eq!(LogLevel::from_str_loose("info"), Some(LogLevel::Info)); + assert_eq!(LogLevel::from_str_loose("INFO"), Some(LogLevel::Info)); + assert_eq!(LogLevel::from_str_loose("Debug"), Some(LogLevel::Debug)); + assert_eq!(LogLevel::from_str_loose("unknown"), None); + } + + #[test] + fn test_colorize_value() { + let val = Value::String("hello".to_string()); + let colored = colorize_value(&val); + assert!(colored.contains("hello")); + assert!(colored.contains(GREEN)); + } + + #[test] + fn test_create_logger_default_level() { + let logger = create_logger("test", None); + assert_eq!(logger.level, LogLevel::Info); + } + + #[test] + fn test_logger_filters_by_level() { + let logger = create_logger("test", Some(LogLevel::Warn)); + assert!(logger.log(LogLevel::Info, Value::String("hi".into()), None).is_none()); + assert!(logger.log(LogLevel::Error, Value::String("err".into()), None).is_some()); + } + + #[test] + fn test_global_log_level() { + set_global_log_level(LogLevel::Debug); + assert_eq!(get_global_log_level(), Some(LogLevel::Debug)); + GLOBAL_LOG_LEVEL.store(255, Ordering::Relaxed); + } +} diff --git a/libraries/logger/tsconfig.json b/libraries/logger/tsconfig.json new file mode 100644 index 00000000..0950f1da --- /dev/null +++ b/libraries/logger/tsconfig.json @@ -0,0 +1,68 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "noUncheckedSideEffectImports": true, + "incremental": true, + "composite": false, + "target": "ESNext", + "lib": [ + "ESNext" + ], + "moduleDetection": "force", + "useDefineForClassFields": true, + "baseUrl": ".", + "module": "ESNext", + "moduleResolution": "Bundler", + "paths": { + "@/*": [ + "./src/*" + ] + }, + "resolveJsonModule": true, + "allowImportingTsExtensions": true, + "strict": true, + "strictBindCallApply": true, + "strictFunctionTypes": true, + "strictNullChecks": true, + "strictPropertyInitialization": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "alwaysStrict": true, + "exactOptionalPropertyTypes": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noPropertyAccessFromIndexSignature": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "useUnknownInCatchVariables": true, + "declaration": true, + "declarationMap": true, + "importHelpers": true, + "newLine": "lf", + "noEmit": true, + "noEmitHelpers": false, + "removeComments": false, + "sourceMap": true, + "stripInternal": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": [ + "src/**/*", + "env.d.ts", + "eslint.config.ts", + "tsdown.config.ts" + ], + "exclude": [ + "../node_modules", + "dist" + ] +} diff --git a/packages/init-bundle/tsconfig.eslint.json b/libraries/logger/tsconfig.lib.json similarity index 50% rename from packages/init-bundle/tsconfig.eslint.json rename to libraries/logger/tsconfig.lib.json index 585b38ee..7df70332 100644 --- a/packages/init-bundle/tsconfig.eslint.json +++ b/libraries/logger/tsconfig.lib.json @@ -2,22 +2,20 @@ "$schema": "https://json.schemastore.org/tsconfig", "extends": "./tsconfig.json", "compilerOptions": { - "noEmit": true, + "composite": true, + "rootDir": "./src", + "noEmit": false, + "outDir": "./dist", "skipLibCheck": true }, "include": [ - "src/**/*.ts", - "src/**/*.test.ts", - "src/**/*.spec.ts", - "env.d.ts", - "eslint.config.ts", - "tsdown.config.ts", - "vite.config.ts", - "vitest.config.ts" + "src/**/*", + "env.d.ts" ], "exclude": [ "../node_modules", "dist", - "coverage" + "**/*.spec.ts", + "**/*.test.ts" ] } diff --git a/libraries/logger/tsdown.config.ts b/libraries/logger/tsdown.config.ts new file mode 100644 index 00000000..5cfddf9a --- /dev/null +++ b/libraries/logger/tsdown.config.ts @@ -0,0 +1,18 @@ +import {resolve} from 'node:path' +import {defineConfig} from 'tsdown' + +export default defineConfig([ + { + entry: ['./src/index.ts', '!**/*.{spec,test}.*'], + platform: 'node', + sourcemap: false, + unbundle: false, + inlineOnly: false, + alias: { + '@': resolve('src') + }, + format: ['esm'], + minify: false, + dts: {sourcemap: false} + } +]) diff --git a/libraries/md-compiler/Cargo.toml b/libraries/md-compiler/Cargo.toml new file mode 100644 index 00000000..195206f6 --- /dev/null +++ b/libraries/md-compiler/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "tnmsc-md-compiler" +description = "MDX to Markdown compiler with expression evaluation and JSX component processing" +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true + +[lib] +crate-type = ["rlib", "cdylib"] + +[features] +default = [] +napi = ["dep:napi", "dep:napi-derive", "dep:regex-lite"] + +[dependencies] +tnmsc-logger = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } +serde_yml = { workspace = true } +markdown = { workspace = true } +napi = { workspace = true, optional = true } +napi-derive = { workspace = true, optional = true } +regex-lite = { version = "0.1", optional = true } + +[build-dependencies] +napi-build = { workspace = true } diff --git a/libraries/md-compiler/build.rs b/libraries/md-compiler/build.rs new file mode 100644 index 00000000..f2be9938 --- /dev/null +++ b/libraries/md-compiler/build.rs @@ -0,0 +1,4 @@ +fn main() { + #[cfg(feature = "napi")] + napi_build::setup(); +} diff --git a/packages/init-bundle/eslint.config.ts b/libraries/md-compiler/eslint.config.ts similarity index 84% rename from packages/init-bundle/eslint.config.ts rename to libraries/md-compiler/eslint.config.ts index 13a6c4cf..f2c61717 100644 --- a/packages/init-bundle/eslint.config.ts +++ b/libraries/md-compiler/eslint.config.ts @@ -9,7 +9,7 @@ const config = eslint10({ type: 'lib', typescript: { strictTypescriptEslint: true, - tsconfigPath: resolve(configDir, 'tsconfig.eslint.json'), + tsconfigPath: resolve(configDir, 'tsconfig.json'), parserOptions: { allowDefaultProject: true } @@ -26,7 +26,9 @@ const config = eslint10({ 'public/**', '.skills/**', '**/.skills/**', - '.agent/**' + '.agent/**', + '**/*.toml', + '**/*.d.ts' ] }) diff --git a/libraries/md-compiler/index.d.ts b/libraries/md-compiler/index.d.ts new file mode 100644 index 00000000..e69de29b diff --git a/libraries/md-compiler/package.json b/libraries/md-compiler/package.json new file mode 100644 index 00000000..dd928908 --- /dev/null +++ b/libraries/md-compiler/package.json @@ -0,0 +1,77 @@ +{ + "name": "@truenine/md-compiler", + "type": "module", + "version": "2026.10223.10555", + "private": true, + "description": "Rust-powered MDX→Markdown compiler for Node.js with pure-TS fallback", + "license": "AGPL-3.0-only", + "exports": { + "./package.json": "./package.json", + ".": { + "types": "./dist/index.d.mts", + "import": "./dist/index.mjs" + }, + "./globals": { + "types": "./dist/globals/index.d.mts", + "import": "./dist/globals/index.mjs" + }, + "./errors": { + "types": "./dist/errors/index.d.mts", + "import": "./dist/errors/index.mjs" + }, + "./markdown": { + "types": "./dist/markdown/index.d.mts", + "import": "./dist/markdown/index.mjs" + } + }, + "module": "dist/index.mjs", + "types": "dist/index.d.mts", + "files": [ + "dist" + ], + "napi": { + "binaryName": "napi-md-compiler", + "targets": [ + "x86_64-pc-windows-msvc", + "x86_64-unknown-linux-gnu", + "aarch64-unknown-linux-gnu", + "aarch64-apple-darwin", + "x86_64-apple-darwin" + ] + }, + "scripts": { + "build": "tsdown", + "build:all": "run-s build:native build", + "build:native": "napi build --platform --release --output-dir dist -- --features napi", + "build:native:debug": "napi build --platform --output-dir dist -- --features napi", + "build:ts": "tsdown", + "check": "run-p typecheck lint", + "lint": "eslint --cache .", + "lintfix": "eslint --fix --cache .", + "prepublishOnly": "run-s build", + "test": "run-s test:rust test:ts", + "test:rust": "tsx ../../scripts/cargo-test.ts", + "test:ts": "vitest run --passWithNoTests", + "typecheck": "tsc --noEmit -p tsconfig.lib.json" + }, + "dependencies": { + "mdast-util-mdx": "catalog:", + "remark-frontmatter": "catalog:", + "remark-gfm": "catalog:", + "remark-mdx": "catalog:", + "remark-parse": "catalog:", + "remark-stringify": "catalog:", + "unified": "catalog:", + "yaml": "catalog:" + }, + "devDependencies": { + "@napi-rs/cli": "^3.5.1", + "@types/estree": "catalog:", + "@types/estree-jsx": "catalog:", + "@types/mdast": "catalog:", + "npm-run-all2": "catalog:", + "tsdown": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" + } +} diff --git a/packages/md-compiler/src/compiler/backward-compat.test.ts b/libraries/md-compiler/src/compiler/backward-compat.test.ts similarity index 100% rename from packages/md-compiler/src/compiler/backward-compat.test.ts rename to libraries/md-compiler/src/compiler/backward-compat.test.ts diff --git a/packages/md-compiler/src/compiler/component-processor.property.test.ts b/libraries/md-compiler/src/compiler/component-processor.property.test.ts similarity index 100% rename from packages/md-compiler/src/compiler/component-processor.property.test.ts rename to libraries/md-compiler/src/compiler/component-processor.property.test.ts diff --git a/packages/md-compiler/src/compiler/component-processor.ts b/libraries/md-compiler/src/compiler/component-processor.ts similarity index 100% rename from packages/md-compiler/src/compiler/component-processor.ts rename to libraries/md-compiler/src/compiler/component-processor.ts diff --git a/packages/md-compiler/src/compiler/component-registry.property.test.ts b/libraries/md-compiler/src/compiler/component-registry.property.test.ts similarity index 100% rename from packages/md-compiler/src/compiler/component-registry.property.test.ts rename to libraries/md-compiler/src/compiler/component-registry.property.test.ts diff --git a/packages/md-compiler/src/compiler/component-registry.test.ts b/libraries/md-compiler/src/compiler/component-registry.test.ts similarity index 100% rename from packages/md-compiler/src/compiler/component-registry.test.ts rename to libraries/md-compiler/src/compiler/component-registry.test.ts diff --git a/packages/md-compiler/src/compiler/component-registry.ts b/libraries/md-compiler/src/compiler/component-registry.ts similarity index 100% rename from packages/md-compiler/src/compiler/component-registry.ts rename to libraries/md-compiler/src/compiler/component-registry.ts diff --git a/packages/md-compiler/src/compiler/export-parser.ts b/libraries/md-compiler/src/compiler/export-parser.ts similarity index 100% rename from packages/md-compiler/src/compiler/export-parser.ts rename to libraries/md-compiler/src/compiler/export-parser.ts diff --git a/packages/md-compiler/src/compiler/expression-eval.property.test.ts b/libraries/md-compiler/src/compiler/expression-eval.property.test.ts similarity index 100% rename from packages/md-compiler/src/compiler/expression-eval.property.test.ts rename to libraries/md-compiler/src/compiler/expression-eval.property.test.ts diff --git a/packages/md-compiler/src/compiler/expression-eval.test.ts b/libraries/md-compiler/src/compiler/expression-eval.test.ts similarity index 100% rename from packages/md-compiler/src/compiler/expression-eval.test.ts rename to libraries/md-compiler/src/compiler/expression-eval.test.ts diff --git a/packages/md-compiler/src/compiler/expression-eval.ts b/libraries/md-compiler/src/compiler/expression-eval.ts similarity index 100% rename from packages/md-compiler/src/compiler/expression-eval.ts rename to libraries/md-compiler/src/compiler/expression-eval.ts diff --git a/packages/md-compiler/src/compiler/index.ts b/libraries/md-compiler/src/compiler/index.ts similarity index 100% rename from packages/md-compiler/src/compiler/index.ts rename to libraries/md-compiler/src/compiler/index.ts diff --git a/packages/md-compiler/src/compiler/jsx-converter.ts b/libraries/md-compiler/src/compiler/jsx-converter.ts similarity index 100% rename from packages/md-compiler/src/compiler/jsx-converter.ts rename to libraries/md-compiler/src/compiler/jsx-converter.ts diff --git a/packages/md-compiler/src/compiler/jsx-expression-eval.ts b/libraries/md-compiler/src/compiler/jsx-expression-eval.ts similarity index 100% rename from packages/md-compiler/src/compiler/jsx-expression-eval.ts rename to libraries/md-compiler/src/compiler/jsx-expression-eval.ts diff --git a/packages/md-compiler/src/compiler/mdx-to-md.property.test.ts b/libraries/md-compiler/src/compiler/mdx-to-md.property.test.ts similarity index 100% rename from packages/md-compiler/src/compiler/mdx-to-md.property.test.ts rename to libraries/md-compiler/src/compiler/mdx-to-md.property.test.ts diff --git a/packages/md-compiler/src/compiler/mdx-to-md.test.ts b/libraries/md-compiler/src/compiler/mdx-to-md.test.ts similarity index 100% rename from packages/md-compiler/src/compiler/mdx-to-md.test.ts rename to libraries/md-compiler/src/compiler/mdx-to-md.test.ts diff --git a/packages/md-compiler/src/compiler/mdx-to-md.ts b/libraries/md-compiler/src/compiler/mdx-to-md.ts similarity index 100% rename from packages/md-compiler/src/compiler/mdx-to-md.ts rename to libraries/md-compiler/src/compiler/mdx-to-md.ts diff --git a/packages/md-compiler/src/compiler/parser.property.test.ts b/libraries/md-compiler/src/compiler/parser.property.test.ts similarity index 100% rename from packages/md-compiler/src/compiler/parser.property.test.ts rename to libraries/md-compiler/src/compiler/parser.property.test.ts diff --git a/packages/md-compiler/src/compiler/parser.ts b/libraries/md-compiler/src/compiler/parser.ts similarity index 100% rename from packages/md-compiler/src/compiler/parser.ts rename to libraries/md-compiler/src/compiler/parser.ts diff --git a/packages/md-compiler/src/compiler/transformer.ts b/libraries/md-compiler/src/compiler/transformer.ts similarity index 100% rename from packages/md-compiler/src/compiler/transformer.ts rename to libraries/md-compiler/src/compiler/transformer.ts diff --git a/packages/md-compiler/src/compiler/types.ts b/libraries/md-compiler/src/compiler/types.ts similarity index 100% rename from packages/md-compiler/src/compiler/types.ts rename to libraries/md-compiler/src/compiler/types.ts diff --git a/packages/md-compiler/src/components/Md.test.ts b/libraries/md-compiler/src/components/Md.test.ts similarity index 100% rename from packages/md-compiler/src/components/Md.test.ts rename to libraries/md-compiler/src/components/Md.test.ts diff --git a/packages/md-compiler/src/components/Md.ts b/libraries/md-compiler/src/components/Md.ts similarity index 100% rename from packages/md-compiler/src/components/Md.ts rename to libraries/md-compiler/src/components/Md.ts diff --git a/packages/md-compiler/src/components/index.ts b/libraries/md-compiler/src/components/index.ts similarity index 100% rename from packages/md-compiler/src/components/index.ts rename to libraries/md-compiler/src/components/index.ts diff --git a/packages/md-compiler/src/errors/index.ts b/libraries/md-compiler/src/errors/index.ts similarity index 100% rename from packages/md-compiler/src/errors/index.ts rename to libraries/md-compiler/src/errors/index.ts diff --git a/libraries/md-compiler/src/expression_eval.rs b/libraries/md-compiler/src/expression_eval.rs new file mode 100644 index 00000000..8011abad --- /dev/null +++ b/libraries/md-compiler/src/expression_eval.rs @@ -0,0 +1,349 @@ +//! Expression evaluation for MDX `{expression}` syntax. +//! +//! Supports: +//! - Simple variable references: `{os.platform}`, `{profile.name}` +//! - String literals: `"hello"`, `'world'` +//! - Ternary expressions: `{condition ? "a" : "b"}` +//! - Equality comparisons: `{os.platform === "win32"}` +//! - Boolean literals: `{true}`, `{false}` + +use std::collections::HashMap; +use serde_json::Value; + +/// Evaluation scope — a map of variable names to their values. +pub type EvaluationScope = HashMap; + +/// Evaluate an expression string within a scope. +/// +/// The expression is the content inside `{...}` braces (without the braces). +pub fn evaluate_expression(expression: &str, scope: &EvaluationScope) -> Result { + let trimmed = expression.trim(); + + if trimmed.is_empty() { + return Ok(String::new()); + } + + // Check for string literal first + if let Some(s) = try_parse_string_literal(trimmed) { + return Ok(s); + } + + // Check for boolean/number/null/undefined literal before variable references + if let Some(s) = try_parse_literal(trimmed) { + return Ok(s); + } + + // Check for simple variable reference: identifier.property.nested + if is_simple_reference(trimmed) { + return evaluate_simple_reference(trimmed, scope); + } + + // Check for ternary expression: condition ? consequent : alternate + if let Some(result) = try_evaluate_ternary(trimmed, scope) { + return result; + } + + // Check for equality comparison: a === b, a !== b, a == b, a != b + if let Some(result) = try_evaluate_comparison(trimmed, scope) { + return result; + } + + // Check for logical NOT: !expr + if let Some(rest) = trimmed.strip_prefix('!') { + let inner = evaluate_expression(rest.trim(), scope)?; + let is_truthy = is_truthy_str(&inner); + return Ok((!is_truthy).to_string()); + } + + // Fallback: try as a simple reference anyway + evaluate_simple_reference(trimmed, scope) +} + +/// Check if a string is a simple variable reference (e.g., `os.platform`). +fn is_simple_reference(s: &str) -> bool { + // Match: identifier(.property)* + let mut chars = s.chars().peekable(); + + // First char must be letter, underscore, or $ + match chars.peek() { + Some(c) if c.is_ascii_alphabetic() || *c == '_' || *c == '$' => { chars.next(); } + _ => return false, + } + + for c in chars { + if c.is_ascii_alphanumeric() || c == '_' || c == '$' || c == '.' { + continue; + } + return false; + } + + true +} + +/// Evaluate a simple variable reference like `os.platform` or `profile.name`. +fn evaluate_simple_reference(reference: &str, scope: &EvaluationScope) -> Result { + let parts: Vec<&str> = reference.split('.').collect(); + let root_var = parts[0]; + + let root_value = scope.get(root_var) + .ok_or_else(|| format!("Undefined namespace: \"{}\" in expression \"{}\"", root_var, reference))?; + + let mut value = root_value.clone(); + for &prop in &parts[1..] { + match &value { + Value::Object(map) => { + value = map.get(prop) + .cloned() + .ok_or_else(|| format!("Undefined variable: \"{}\" in expression \"{}\"", prop, reference))?; + } + Value::Null => { + return Err(format!("Cannot read property \"{}\" of null in expression \"{}\"", prop, reference)); + } + _ => { + return Err(format!( + "Cannot read property \"{}\" of {} in expression \"{}\"", + prop, + value_type_name(&value), + reference + )); + } + } + } + + Ok(convert_to_string(&value)) +} + +/// Try to parse a string literal ("..." or '...'). +fn try_parse_string_literal(s: &str) -> Option { + if (s.starts_with('"') && s.ends_with('"')) || (s.starts_with('\'') && s.ends_with('\'')) { + if s.len() >= 2 { + return Some(s[1..s.len() - 1].to_string()); + } + } + None +} + +/// Try to parse a boolean or number literal. +fn try_parse_literal(s: &str) -> Option { + match s { + "true" => Some("true".to_string()), + "false" => Some("false".to_string()), + "null" | "undefined" => Some(String::new()), + _ => { + // Try number + if s.parse::().is_ok() { + Some(s.to_string()) + } else { + None + } + } + } +} + +/// Try to evaluate a ternary expression: `condition ? consequent : alternate`. +fn try_evaluate_ternary(s: &str, scope: &EvaluationScope) -> Option> { + // Find the `?` that's not inside quotes or nested expressions + let question_pos = find_operator(s, '?')?; + let condition = s[..question_pos].trim(); + let rest = s[question_pos + 1..].trim(); + + // Find the `:` in the rest + let colon_pos = find_operator(rest, ':')?; + let consequent = rest[..colon_pos].trim(); + let alternate = rest[colon_pos + 1..].trim(); + + // Evaluate condition + let cond_result = match evaluate_expression(condition, scope) { + Ok(v) => v, + Err(e) => return Some(Err(e)), + }; + + let is_true = is_truthy_str(&cond_result); + + let branch = if is_true { consequent } else { alternate }; + Some(evaluate_expression(branch, scope)) +} + +/// Try to evaluate a comparison expression. +fn try_evaluate_comparison(s: &str, scope: &EvaluationScope) -> Option> { + // Check for ===, !==, ==, != + for (op, negate) in &[("===", false), ("!==", true), ("==", false), ("!=", true)] { + if let Some(pos) = s.find(op) { + let left = s[..pos].trim(); + let right = s[pos + op.len()..].trim(); + + let left_val = match evaluate_expression(left, scope) { + Ok(v) => v, + Err(e) => return Some(Err(e)), + }; + let right_val = match evaluate_expression(right, scope) { + Ok(v) => v, + Err(e) => return Some(Err(e)), + }; + + let equal = left_val == right_val; + let result = if *negate { !equal } else { equal }; + return Some(Ok(result.to_string())); + } + } + None +} + +/// Find the position of an operator character, skipping quoted strings and nested braces/parens. +fn find_operator(s: &str, op: char) -> Option { + let mut depth = 0i32; + let mut in_single_quote = false; + let mut in_double_quote = false; + let mut in_backtick = false; + + for (i, c) in s.char_indices() { + match c { + '\'' if !in_double_quote && !in_backtick => in_single_quote = !in_single_quote, + '"' if !in_single_quote && !in_backtick => in_double_quote = !in_double_quote, + '`' if !in_single_quote && !in_double_quote => in_backtick = !in_backtick, + '(' | '{' | '[' if !in_single_quote && !in_double_quote && !in_backtick => depth += 1, + ')' | '}' | ']' if !in_single_quote && !in_double_quote && !in_backtick => depth -= 1, + c2 if c2 == op && depth == 0 && !in_single_quote && !in_double_quote && !in_backtick => { + return Some(i); + } + _ => {} + } + } + None +} + +/// Check if a string value is "truthy" (non-empty, not "false", not "0", not "undefined", not "null"). +fn is_truthy_str(s: &str) -> bool { + !s.is_empty() && s != "false" && s != "0" && s != "undefined" && s != "null" +} + +/// Get a human-readable type name for a JSON value. +fn value_type_name(v: &Value) -> &'static str { + match v { + Value::Null => "null", + Value::Bool(_) => "boolean", + Value::Number(_) => "number", + Value::String(_) => "string", + Value::Array(_) => "array", + Value::Object(_) => "object", + } +} + +/// Convert a JSON value to its string representation. +pub fn convert_to_string(value: &Value) -> String { + match value { + Value::Null => String::new(), + Value::Bool(b) => b.to_string(), + Value::Number(n) => n.to_string(), + Value::String(s) => s.clone(), + Value::Array(_) | Value::Object(_) => { + serde_json::to_string(value).unwrap_or_else(|_| String::new()) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + fn make_scope() -> EvaluationScope { + let mut scope = EvaluationScope::new(); + scope.insert("os".into(), json!({"platform": "win32", "arch": "x64"})); + scope.insert("profile".into(), json!({"name": "TrueNine", "username": "truenine"})); + scope.insert("tool".into(), json!({"name": "cursor"})); + scope + } + + #[test] + fn test_simple_reference() { + let scope = make_scope(); + assert_eq!(evaluate_expression("os.platform", &scope).unwrap(), "win32"); + assert_eq!(evaluate_expression("profile.name", &scope).unwrap(), "TrueNine"); + assert_eq!(evaluate_expression("tool.name", &scope).unwrap(), "cursor"); + } + + #[test] + fn test_undefined_namespace() { + let scope = make_scope(); + assert!(evaluate_expression("unknown.prop", &scope).is_err()); + } + + #[test] + fn test_undefined_property() { + let scope = make_scope(); + assert!(evaluate_expression("os.nonexistent", &scope).is_err()); + } + + #[test] + fn test_string_literal() { + let scope = make_scope(); + assert_eq!(evaluate_expression("\"hello\"", &scope).unwrap(), "hello"); + assert_eq!(evaluate_expression("'world'", &scope).unwrap(), "world"); + } + + #[test] + fn test_boolean_literal() { + let scope = make_scope(); + assert_eq!(evaluate_expression("true", &scope).unwrap(), "true"); + assert_eq!(evaluate_expression("false", &scope).unwrap(), "false"); + } + + #[test] + fn test_empty_expression() { + let scope = make_scope(); + assert_eq!(evaluate_expression("", &scope).unwrap(), ""); + assert_eq!(evaluate_expression(" ", &scope).unwrap(), ""); + } + + #[test] + fn test_ternary() { + let scope = make_scope(); + assert_eq!( + evaluate_expression("os.platform === \"win32\" ? \"windows\" : \"other\"", &scope).unwrap(), + "windows" + ); + assert_eq!( + evaluate_expression("os.platform === \"linux\" ? \"linux\" : \"other\"", &scope).unwrap(), + "other" + ); + } + + #[test] + fn test_equality() { + let scope = make_scope(); + assert_eq!(evaluate_expression("os.platform === \"win32\"", &scope).unwrap(), "true"); + assert_eq!(evaluate_expression("os.platform !== \"win32\"", &scope).unwrap(), "false"); + assert_eq!(evaluate_expression("os.platform === \"linux\"", &scope).unwrap(), "false"); + } + + #[test] + fn test_negation() { + let scope = make_scope(); + // evaluate_expression("true") -> "true", is_truthy("true") -> true, !true -> false + assert_eq!(evaluate_expression("!true", &scope).unwrap(), "false"); + assert_eq!(evaluate_expression("!false", &scope).unwrap(), "true"); + } + + #[test] + fn test_null_undefined() { + let scope = make_scope(); + assert_eq!(evaluate_expression("null", &scope).unwrap(), ""); + assert_eq!(evaluate_expression("undefined", &scope).unwrap(), ""); + } + + #[test] + fn test_number_literal() { + let scope = make_scope(); + assert_eq!(evaluate_expression("42", &scope).unwrap(), "42"); + assert_eq!(evaluate_expression("3.14", &scope).unwrap(), "3.14"); + } + + #[test] + fn test_convert_to_string() { + assert_eq!(convert_to_string(&Value::Null), ""); + assert_eq!(convert_to_string(&Value::Bool(true)), "true"); + assert_eq!(convert_to_string(&json!(42)), "42"); + assert_eq!(convert_to_string(&json!("hello")), "hello"); + } +} diff --git a/packages/md-compiler/src/globals/index.ts b/libraries/md-compiler/src/globals/index.ts similarity index 100% rename from packages/md-compiler/src/globals/index.ts rename to libraries/md-compiler/src/globals/index.ts diff --git a/packages/md-compiler/src/index.ts b/libraries/md-compiler/src/index.ts similarity index 100% rename from packages/md-compiler/src/index.ts rename to libraries/md-compiler/src/index.ts diff --git a/libraries/md-compiler/src/lib.rs b/libraries/md-compiler/src/lib.rs new file mode 100644 index 00000000..00011ea2 --- /dev/null +++ b/libraries/md-compiler/src/lib.rs @@ -0,0 +1,181 @@ +#![deny(clippy::all)] + +//! MDX to Markdown compiler. +//! +//! Uses `markdown-rs` (by wooorm, same author as remark) for MDX parsing, +//! with custom expression evaluation, JSX component processing, and +//! AST-to-Markdown serialization. + +pub mod parser; +pub mod expression_eval; +pub mod serializer; +pub mod transformer; +pub mod mdx_to_md; + +pub use expression_eval::EvaluationScope; +pub use mdx_to_md::{ + ExportMetadata, + MdxGlobalScope, + MdxToMdOptions, + MdxToMdResult, + mdx_to_md, + mdx_to_md_with_metadata, +}; +pub use parser::parse_mdx; +pub use serializer::serialize; +pub use transformer::ProcessingContext; + +// =========================================================================== +// NAPI binding layer (only compiled with --features napi) +// =========================================================================== + +#[cfg(feature = "napi")] +mod napi_binding { + use napi_derive::napi; + use serde_json::Value; + use super::{mdx_to_md, MdxToMdOptions, EvaluationScope}; + + #[napi(object)] + pub struct ParsedMarkdown { + pub yaml_front_matter_json: Option, + pub raw_front_matter: Option, + pub content_without_front_matter: String, + } + +// --------------------------------------------------------------------------- +// mdxToMd — convert MDX source to plain Markdown +// --------------------------------------------------------------------------- + +/// Convert MDX source to plain Markdown. +/// Returns the converted Markdown string, or throws on parse error. +#[napi] +pub fn mdx_to_md_str(content: String) -> napi::Result { + mdx_to_md(&content, None).map_err(|e| napi::Error::from_reason(e.to_string())) +} + +/// Convert MDX source to plain Markdown with a JSON scope string. +/// `scope_json` should be a JSON object string, e.g. `{"os":{"platform":"win32"}}`. +#[napi] +pub fn mdx_to_md_with_scope(content: String, scope_json: String) -> napi::Result { + let scope: EvaluationScope = + serde_json::from_str(&scope_json).map_err(|e| napi::Error::from_reason(e.to_string()))?; + let opts = MdxToMdOptions { + scope: Some(scope), + ..Default::default() + }; + mdx_to_md(&content, Some(opts)).map_err(|e| napi::Error::from_reason(e.to_string())) +} + +// --------------------------------------------------------------------------- +// buildFrontMatter / buildMarkdownWithFrontMatter +// --------------------------------------------------------------------------- + +/// Build a YAML front matter block from a JSON object string. +/// Returns a string like `---\nkey: value\n---`. +#[napi] +pub fn build_front_matter(front_matter_json: String) -> napi::Result { + let obj: Value = + serde_json::from_str(&front_matter_json).map_err(|e| napi::Error::from_reason(e.to_string()))?; + + let map = match &obj { + Value::Object(m) => m, + _ => return Err(napi::Error::from_reason("frontMatter must be a JSON object")), + }; + + let cleaned: serde_json::Map = map + .iter() + .filter(|(_, v)| !v.is_null()) + .map(|(k, v)| (k.clone(), v.clone())) + .collect(); + + if cleaned.is_empty() { + return Ok("---\n---".to_string()); + } + + let yaml_str = serde_yml::to_string(&Value::Object(cleaned)) + .map_err(|e| napi::Error::from_reason(e.to_string()))?; + let yaml_trimmed = yaml_str.trim_end(); + Ok(format!("---\n{yaml_trimmed}\n---")) +} + +/// Build a Markdown string with YAML front matter prepended. +/// `front_matter_json` is a JSON object string; pass `"null"` or `"{}"` to skip. +#[napi] +pub fn build_markdown_with_front_matter( + front_matter_json: Option, + content: String, +) -> napi::Result { + let fm = match front_matter_json { + None => return Ok(content), + Some(ref s) if s == "null" || s == "{}" => return Ok(content), + Some(ref s) => s, + }; + + let obj: Value = + serde_json::from_str(fm).map_err(|e| napi::Error::from_reason(e.to_string()))?; + + match &obj { + Value::Null => return Ok(content), + Value::Object(m) if m.is_empty() => return Ok(content), + _ => {} + } + + let fm_block = build_front_matter(fm.to_string())?; + Ok(format!("{fm_block}\n{content}")) +} + +// --------------------------------------------------------------------------- +// parseMarkdown — extract front matter + content +// --------------------------------------------------------------------------- + +/// Parse a Markdown/MDX string and extract YAML front matter and content. +#[napi] +pub fn parse_markdown(raw_content: String) -> ParsedMarkdown { + let front_matter_regex = regex_lite::Regex::new(r"(?s)^---\r?\n(.*?)\r?\n---\r?\n?").ok(); + + if let Some(re) = &front_matter_regex { + if let Some(caps) = re.captures(&raw_content) { + let raw_fm = caps.get(1).map(|m| m.as_str().to_string()); + let full_match = caps.get(0).map(|m| m.end()).unwrap_or(0); + let content_without = raw_content[full_match..].to_string(); + + let yaml_json = raw_fm.as_deref().and_then(|fm| { + serde_yml::from_str::(fm) + .ok() + .and_then(|v| serde_json::to_string(&v).ok()) + }); + + return ParsedMarkdown { + yaml_front_matter_json: yaml_json, + raw_front_matter: raw_fm, + content_without_front_matter: content_without, + }; + } + } + + ParsedMarkdown { + yaml_front_matter_json: None, + raw_front_matter: None, + content_without_front_matter: raw_content, + } +} + +/// Transform MDX-style link/image references to plain .md extensions. +#[napi] +pub fn transform_mdx_references_to_md(content: String) -> String { + let re = regex_lite::Regex::new(r"(!?\[)([^\]]*?)(\]\()([^)]+)(\))").unwrap(); + re.replace_all(&content, |caps: ®ex_lite::Captures| { + let prefix = &caps[1]; + let text = caps[2].replace(".mdx", ".md"); + let middle = &caps[3]; + let url = &caps[4]; + let suffix = &caps[5]; + let transformed_url = if url.starts_with("http://") || url.starts_with("https://") || url.starts_with("//") { + url.to_string() + } else { + url.replace(".mdx", ".md") + }; + format!("{prefix}{text}{middle}{transformed_url}{suffix}") + }).into_owned() +} +} // mod napi_binding diff --git a/packages/md-compiler/src/markdown/index.ts b/libraries/md-compiler/src/markdown/index.ts similarity index 53% rename from packages/md-compiler/src/markdown/index.ts rename to libraries/md-compiler/src/markdown/index.ts index 24f1471b..67f78a99 100644 --- a/packages/md-compiler/src/markdown/index.ts +++ b/libraries/md-compiler/src/markdown/index.ts @@ -1,6 +1,49 @@ import type {Root, RootContent} from 'mdast' +import {createRequire} from 'node:module' +import process from 'node:process' import * as YAML from 'yaml' -import {parseMdx} from '../compiler/parser' + +import {parseMdx} from '../compiler/parser' // Napi binding types + +interface NapiMdCompilerModule { + buildFrontMatter: (frontMatterJson: string) => string + buildMarkdownWithFrontMatter: (frontMatterJson: string | null | undefined, content: string) => string + parseMarkdown: (rawContent: string) => { + yamlFrontMatterJson?: string + rawFrontMatter?: string + contentWithoutFrontMatter: string + } + transformMdxReferencesToMd: (content: string) => string +} + +let napiBinding: NapiMdCompilerModule | null = null + +try { + const require = createRequire(import.meta.url) + const {platform, arch} = process + const platforms: Record = { + 'win32-x64': ['napi-md-compiler.win32-x64-msvc', 'win32-x64-msvc'], + 'linux-x64': ['napi-md-compiler.linux-x64-gnu', 'linux-x64-gnu'], + 'linux-arm64': ['napi-md-compiler.linux-arm64-gnu', 'linux-arm64-gnu'], + 'darwin-arm64': ['napi-md-compiler.darwin-arm64', 'darwin-arm64'], + 'darwin-x64': ['napi-md-compiler.darwin-x64', 'darwin-x64'] + } + const entry = platforms[`${platform}-${arch}`] + if (entry != null) { + const [local, suffix] = entry + try { + napiBinding = require(`../${local}.node`) as NapiMdCompilerModule + } + catch { + try { + const pkg = require(`@truenine/memory-sync-cli-${suffix}`) as Record + napiBinding = pkg['mdCompiler'] as NapiMdCompilerModule + } + catch {} + } + } +} +catch {} // Native module not available — fall back to pure-TS implementation export interface ParsedMarkdown> { readonly yamlFrontMatter?: Y @@ -13,12 +56,13 @@ export interface ParsedMarkdown> { export interface BuildMarkdownOptions { readonly singleQuote?: boolean readonly lineWidth?: number -} +} // buildFrontMatter export function buildFrontMatter( frontMatter: Record, options?: BuildMarkdownOptions ): string { + if (napiBinding != null && options == null) return napiBinding.buildFrontMatter(JSON.stringify(frontMatter)) const cleanedFrontMatter = Object.fromEntries( Object.entries(frontMatter).filter(([_, v]) => v != null) ) @@ -28,17 +72,21 @@ export function buildFrontMatter( lineWidth: options?.lineWidth ?? 0 }).trimEnd() return `---\n${yamlStr}\n---` -} +} // buildMarkdownWithFrontMatter export function buildMarkdownWithFrontMatter( frontMatter: Record | undefined | null, content: string, options?: BuildMarkdownOptions ): string { + if (napiBinding != null && options == null) { + if (frontMatter == null || Object.keys(frontMatter).length === 0) return content + return napiBinding.buildMarkdownWithFrontMatter(JSON.stringify(frontMatter), content) + } if (frontMatter == null || Object.keys(frontMatter).length === 0) return content const fmStr = buildFrontMatter(frontMatter, options) return `${fmStr}\n${content}` -} +} // buildRawFrontMatter — TS only (no napi equivalent needed) export function buildRawFrontMatter( frontMatter: Record, @@ -52,15 +100,33 @@ export function buildRawFrontMatter( singleQuote: options?.singleQuote ?? false, lineWidth: options?.lineWidth ?? 0 }).trimEnd() -} +} // doubleQuoted — TS only (YAML-specific helper) export function doubleQuoted(value: string): unknown { const s = new YAML.Scalar(value) s.type = YAML.Scalar.QUOTE_DOUBLE return s -} +} // parseMarkdown export function parseMarkdown>(rawContent: string): ParsedMarkdown { + if (napiBinding != null) { + const result = napiBinding.parseMarkdown(rawContent) + const yamlFrontMatter = result.yamlFrontMatterJson != null + ? JSON.parse(result.yamlFrontMatterJson) as Y + : void 0 + const ast = parseMdx(rawContent) // Still need the AST for consumers that use markdownAst/markdownContents + const markdownContents: RootContent[] = [] + for (const node of ast.children as (RootContent & {type: string})[]) { + if (node.type !== 'yaml') markdownContents.push(node) + } + return { + ...yamlFrontMatter != null && {yamlFrontMatter}, + ...result.rawFrontMatter != null && {rawFrontMatter: result.rawFrontMatter}, + markdownAst: ast, + markdownContents, + contentWithoutFrontMatter: result.contentWithoutFrontMatter + } + } const ast = parseMdx(rawContent) let yamlFrontMatter: Y | undefined, rawFrontMatter: string | undefined @@ -86,9 +152,10 @@ export function parseMarkdown>(rawContent: string): markdownContents, contentWithoutFrontMatter } -} +} // transformMdxReferencesToMd export function transformMdxReferencesToMd(content: string): string { + if (napiBinding != null) return napiBinding.transformMdxReferencesToMd(content) return content.replaceAll( /(!?\[)([^\]]*)(\]\()([^)]+)(\))/g, (_match, prefix: string, text: string, middle: string, url: string, suffix: string) => { diff --git a/packages/md-compiler/src/markdown/markdown.test.ts b/libraries/md-compiler/src/markdown/markdown.test.ts similarity index 100% rename from packages/md-compiler/src/markdown/markdown.test.ts rename to libraries/md-compiler/src/markdown/markdown.test.ts diff --git a/libraries/md-compiler/src/mdx_to_md.rs b/libraries/md-compiler/src/mdx_to_md.rs new file mode 100644 index 00000000..de1921a0 --- /dev/null +++ b/libraries/md-compiler/src/mdx_to_md.rs @@ -0,0 +1,304 @@ +//! Main entry point for MDX-to-Markdown conversion. +//! +//! Parses MDX source, transforms the AST (evaluating expressions, expanding components), +//! and serializes back to Markdown. + +use std::collections::HashMap; +use serde_json::Value; + +use crate::expression_eval::EvaluationScope; +use crate::parser::parse_mdx; +use crate::serializer::serialize; +use crate::transformer::{ProcessingContext, transform_ast}; + +/// Global scope for MDX compilation (os, env, profile, tool info). +#[derive(Debug, Clone, Default)] +pub struct MdxGlobalScope { + pub os: Option>, + pub env: Option>, + pub profile: Option>, + pub tool: Option>, +} + +/// Options for the `mdx_to_md` function. +#[derive(Debug, Clone, Default)] +pub struct MdxToMdOptions { + pub scope: Option, + pub base_path: Option, + pub global_scope: Option, + pub extract_metadata: bool, +} + +/// Result of MDX-to-Markdown conversion when metadata extraction is enabled. +#[derive(Debug, Clone)] +pub struct MdxToMdResult { + pub content: String, + pub metadata: ExportMetadata, +} + +/// Extracted metadata from YAML frontmatter and export statements. +#[derive(Debug, Clone, Default)] +pub struct ExportMetadata { + pub yaml_front_matter: Option>, + pub exports: HashMap, +} + +/// Merge global scope with custom scope. Custom scope takes priority. +fn merge_scopes( + global_scope: &Option, + custom_scope: &Option, +) -> EvaluationScope { + let mut result = EvaluationScope::new(); + + if let Some(gs) = global_scope { + if let Some(os) = &gs.os { + result.insert("os".into(), serde_json::to_value(os).unwrap_or(Value::Null)); + } + if let Some(env) = &gs.env { + result.insert("env".into(), serde_json::to_value(env).unwrap_or(Value::Null)); + } + if let Some(profile) = &gs.profile { + result.insert("profile".into(), serde_json::to_value(profile).unwrap_or(Value::Null)); + } + if let Some(tool) = &gs.tool { + result.insert("tool".into(), serde_json::to_value(tool).unwrap_or(Value::Null)); + } + } + + if let Some(cs) = custom_scope { + for (key, value) in cs { + // Deep merge objects, override primitives + if let (Some(Value::Object(existing)), Value::Object(new_map)) = (result.get(key), value) { + let mut merged = existing.clone(); + for (k, v) in new_map { + merged.insert(k.clone(), v.clone()); + } + result.insert(key.clone(), Value::Object(merged)); + } else { + result.insert(key.clone(), value.clone()); + } + } + } + + result +} + +/// Extract YAML frontmatter from the AST. +fn extract_yaml_frontmatter(ast: &markdown::mdast::Node) -> Option> { + if let markdown::mdast::Node::Root(root) = ast { + for child in &root.children { + if let markdown::mdast::Node::Yaml(yaml) = child { + if let Ok(parsed) = serde_yml::from_str::(&yaml.value) { + if let Value::Object(map) = parsed { + return Some(map.into_iter().collect()); + } + } + } + } + } + None +} + +/// Extract export metadata from lines starting with "export ". +/// Since markdown-rs doesn't always parse ESM as MdxjsEsm nodes, +/// we also do a pre-pass on the source text. +fn extract_exports_from_source(source: &str) -> HashMap { + let mut exports = HashMap::new(); + + for line in source.lines() { + let trimmed = line.trim(); + if !trimmed.starts_with("export ") { + continue; + } + + // Try to parse: export const NAME = VALUE + if let Some(rest) = trimmed.strip_prefix("export const ") { + if let Some(eq_pos) = rest.find('=') { + let name = rest[..eq_pos].trim(); + let value_str = rest[eq_pos + 1..].trim(); + if let Ok(val) = serde_json::from_str::(value_str) { + exports.insert(name.to_string(), val); + } + } + } + } + + exports +} + +/// Remove YAML frontmatter and ESM export nodes from the AST. +fn strip_metadata_nodes(ast: &markdown::mdast::Node) -> markdown::mdast::Node { + if let markdown::mdast::Node::Root(root) = ast { + let filtered: Vec = root.children.iter() + .filter(|child| { + !matches!(child, markdown::mdast::Node::Yaml(_) | markdown::mdast::Node::MdxjsEsm(_)) + }) + .cloned() + .collect(); + return markdown::mdast::Node::Root(markdown::mdast::Root { + children: filtered, + position: root.position.clone(), + }); + } + ast.clone() +} + +/// Convert MDX source to Markdown. +/// +/// This is the main entry point, equivalent to the TS `mdxToMd()` function. +pub fn mdx_to_md(content: &str, options: Option) -> Result { + let opts = options.unwrap_or_default(); + let ast = parse_mdx(content)?; + let merged_scope = merge_scopes(&opts.global_scope, &opts.scope); + let ctx = ProcessingContext::new(merged_scope); + let transformed = transform_ast(&ast, &ctx); + Ok(serialize(&transformed)) +} + +/// Convert MDX source to Markdown with metadata extraction. +pub fn mdx_to_md_with_metadata( + content: &str, + options: Option, +) -> Result { + let opts = options.unwrap_or_default(); + let ast = parse_mdx(content)?; + + // Extract metadata + let yaml_fm = extract_yaml_frontmatter(&ast); + let exports = extract_exports_from_source(content); + + let mut metadata = ExportMetadata { + yaml_front_matter: yaml_fm.clone(), + exports, + }; + + // Merge YAML frontmatter into exports (exports take priority) + if let Some(yaml) = &yaml_fm { + for (k, v) in yaml { + if !metadata.exports.contains_key(k) { + metadata.exports.insert(k.clone(), v.clone()); + } + } + } + + // Strip metadata nodes from AST + let stripped = strip_metadata_nodes(&ast); + + let merged_scope = merge_scopes(&opts.global_scope, &opts.scope); + let ctx = ProcessingContext::new(merged_scope); + let transformed = transform_ast(&stripped, &ctx); + let markdown = serialize(&transformed); + + Ok(MdxToMdResult { + content: markdown, + metadata, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json::json; + + fn make_options() -> MdxToMdOptions { + let mut scope = EvaluationScope::new(); + scope.insert("os".into(), json!({"platform": "win32"})); + scope.insert("profile".into(), json!({"name": "TrueNine"})); + MdxToMdOptions { + scope: Some(scope), + ..Default::default() + } + } + + #[test] + fn test_simple_markdown() { + let result = mdx_to_md("# Hello\n\nWorld\n", None).unwrap(); + assert!(result.contains("# Hello")); + assert!(result.contains("World")); + } + + #[test] + fn test_expression_evaluation() { + let result = mdx_to_md("Platform: {os.platform}\n", Some(make_options())).unwrap(); + assert!(result.contains("Platform: win32"), "Got: {}", result); + } + + #[test] + fn test_md_component() { + let result = mdx_to_md( + "\n\nVisible\n\n\n", + Some(make_options()), + ).unwrap(); + assert!(result.contains("Visible"), "Got: {}", result); + } + + #[test] + fn test_md_component_false() { + let result = mdx_to_md( + "\n\nHidden\n\n\n", + Some(make_options()), + ).unwrap(); + assert!(!result.contains("Hidden"), "Got: {}", result); + } + + #[test] + fn test_metadata_extraction() { + let source = "---\ndescription: test skill\n---\n\n# Hello\n"; + let result = mdx_to_md_with_metadata(source, Some(make_options())).unwrap(); + assert!(result.content.contains("# Hello")); + assert!(!result.content.contains("---")); + assert_eq!( + result.metadata.exports.get("description").and_then(|v| v.as_str()), + Some("test skill") + ); + } + + #[test] + fn test_export_extraction() { + let source = "export const meta = {\"name\": \"test\"}\n\n# Hello\n"; + let result = mdx_to_md_with_metadata(source, Some(make_options())).unwrap(); + assert!(result.content.contains("# Hello")); + let meta = result.metadata.exports.get("meta"); + assert!(meta.is_some(), "Expected meta export, got: {:?}", result.metadata.exports); + } + + #[test] + fn test_global_scope() { + let opts = MdxToMdOptions { + global_scope: Some(MdxGlobalScope { + os: Some({ + let mut m = HashMap::new(); + m.insert("platform".into(), json!("linux")); + m + }), + ..Default::default() + }), + ..Default::default() + }; + let result = mdx_to_md("OS: {os.platform}\n", Some(opts)).unwrap(); + assert!(result.contains("OS: linux"), "Got: {}", result); + } + + #[test] + fn test_scope_merge_priority() { + let mut custom = EvaluationScope::new(); + custom.insert("os".into(), json!({"platform": "darwin"})); + + let opts = MdxToMdOptions { + global_scope: Some(MdxGlobalScope { + os: Some({ + let mut m = HashMap::new(); + m.insert("platform".into(), json!("linux")); + m.insert("arch".into(), json!("x64")); + m + }), + ..Default::default() + }), + scope: Some(custom), + ..Default::default() + }; + let result = mdx_to_md("OS: {os.platform}\n", Some(opts)).unwrap(); + // Custom scope should override global + assert!(result.contains("OS: darwin"), "Got: {}", result); + } +} diff --git a/libraries/md-compiler/src/parser.rs b/libraries/md-compiler/src/parser.rs new file mode 100644 index 00000000..ae447c7c --- /dev/null +++ b/libraries/md-compiler/src/parser.rs @@ -0,0 +1,86 @@ +//! MDX parser using `markdown-rs`. +//! +//! Parses MDX source into an mdast AST with MDX extensions, GFM, and frontmatter. + +use markdown::{mdast::Node, to_mdast, ParseOptions}; + +/// Parse an MDX string into an mdast AST. +/// +/// Enables: MDX (JSX, expressions, ESM), GFM (tables, task lists, strikethrough), +/// and YAML frontmatter — matching the TS remark-parse + remark-mdx + remark-gfm + remark-frontmatter setup. +pub fn parse_mdx(source: &str) -> Result { + let mut options = ParseOptions::mdx(); + options.constructs.frontmatter = true; + options.constructs.gfm_autolink_literal = true; + options.constructs.gfm_strikethrough = true; + options.constructs.gfm_table = true; + options.constructs.gfm_task_list_item = true; + + to_mdast(source, &options).map_err(|e| e.to_string()) +} + +#[cfg(test)] +mod tests { + use super::*; + use markdown::mdast::Node; + + #[test] + fn test_parse_simple_markdown() { + let result = parse_mdx("# Hello\n\nWorld\n"); + assert!(result.is_ok()); + let node = result.unwrap(); + match &node { + Node::Root(root) => { + assert!(root.children.len() >= 2); + } + _ => panic!("Expected Root node"), + } + } + + #[test] + fn test_parse_frontmatter() { + let result = parse_mdx("---\ntitle: test\n---\n\n# Hello\n"); + assert!(result.is_ok()); + let node = result.unwrap(); + match &node { + Node::Root(root) => { + assert!(matches!(&root.children[0], Node::Yaml(_))); + } + _ => panic!("Expected Root node"), + } + } + + #[test] + fn test_parse_mdx_expression() { + let result = parse_mdx("Text with {expr} inline\n"); + assert!(result.is_ok()); + } + + #[test] + fn test_parse_mdx_jsx_flow() { + let result = parse_mdx("\n content\n\n"); + assert!(result.is_ok()); + let node = result.unwrap(); + match &node { + Node::Root(root) => { + let has_jsx = root.children.iter().any(|c| matches!(c, Node::MdxJsxFlowElement(_))); + assert!(has_jsx, "Expected MdxJsxFlowElement"); + } + _ => panic!("Expected Root node"), + } + } + + #[test] + fn test_parse_gfm_table() { + let result = parse_mdx("| a | b |\n| - | - |\n| 1 | 2 |\n"); + assert!(result.is_ok()); + let node = result.unwrap(); + match &node { + Node::Root(root) => { + let has_table = root.children.iter().any(|c| matches!(c, Node::Table(_))); + assert!(has_table, "Expected Table node"); + } + _ => panic!("Expected Root node"), + } + } +} diff --git a/libraries/md-compiler/src/serializer.rs b/libraries/md-compiler/src/serializer.rs new file mode 100644 index 00000000..6cab7253 --- /dev/null +++ b/libraries/md-compiler/src/serializer.rs @@ -0,0 +1,374 @@ +//! AST-to-Markdown serializer. +//! +//! Converts an mdast AST back into Markdown text. +//! Matches the output style of remark-stringify with: +//! - `-` for bullet lists +//! - `` ` `` for fenced code blocks +//! - `*` for emphasis and strong + +use markdown::mdast::Node; + +/// Serialize an mdast AST node to Markdown string. +pub fn serialize(node: &Node) -> String { + let mut output = String::new(); + serialize_node(node, &mut output, &SerializeContext::default()); + // Trim trailing whitespace per line and collapse multiple blank lines + let trimmed = output + .lines() + .map(|l| l.trim_end()) + .collect::>() + .join("\n"); + trimmed.trim().to_string() +} + +#[derive(Default, Clone)] +struct SerializeContext { + /// Current list nesting depth + list_depth: usize, + /// Whether we're inside a tight list + tight: bool, +} + +fn serialize_node(node: &Node, out: &mut String, ctx: &SerializeContext) { + match node { + Node::Root(root) => { + serialize_children(&root.children, out, ctx); + } + Node::Yaml(yaml) => { + out.push_str("---\n"); + out.push_str(&yaml.value); + out.push_str("\n---\n\n"); + } + Node::Heading(heading) => { + for _ in 0..heading.depth { + out.push('#'); + } + out.push(' '); + serialize_inline_children(&heading.children, out, ctx); + out.push_str("\n\n"); + } + Node::Paragraph(para) => { + serialize_inline_children(¶.children, out, ctx); + out.push_str("\n\n"); + } + Node::Text(text) => { + out.push_str(&text.value); + } + Node::Strong(strong) => { + out.push_str("**"); + serialize_inline_children(&strong.children, out, ctx); + out.push_str("**"); + } + Node::Emphasis(em) => { + out.push('*'); + serialize_inline_children(&em.children, out, ctx); + out.push('*'); + } + Node::InlineCode(code) => { + out.push('`'); + out.push_str(&code.value); + out.push('`'); + } + Node::Code(code) => { + out.push_str("```"); + if let Some(lang) = &code.lang { + out.push_str(lang); + } + out.push('\n'); + out.push_str(&code.value); + out.push_str("\n```\n\n"); + } + Node::Link(link) => { + out.push('['); + serialize_inline_children(&link.children, out, ctx); + out.push_str("]("); + out.push_str(&link.url); + if let Some(title) = &link.title { + out.push_str(" \""); + out.push_str(title); + out.push('"'); + } + out.push(')'); + } + Node::Image(img) => { + out.push_str("!["); + out.push_str(&img.alt); + out.push_str("]("); + out.push_str(&img.url); + if let Some(title) = &img.title { + out.push_str(" \""); + out.push_str(title); + out.push('"'); + } + out.push(')'); + } + Node::List(list) => { + let child_ctx = SerializeContext { + list_depth: ctx.list_depth + 1, + tight: !list.spread, + }; + for (i, child) in list.children.iter().enumerate() { + if let Node::ListItem(item) = child { + let indent = " ".repeat(ctx.list_depth); + if list.ordered { + let start = list.start.unwrap_or(1) as usize; + out.push_str(&format!("{}{}. ", indent, start + i)); + } else { + out.push_str(&format!("{}- ", indent)); + } + serialize_list_item_children(&item.children, out, &child_ctx); + if !child_ctx.tight || i < list.children.len() - 1 { + // Don't add extra newline for tight lists + if child_ctx.tight { + out.push('\n'); + } + } + } + } + out.push('\n'); + } + Node::ListItem(item) => { + // Handled by List + serialize_children(&item.children, out, ctx); + } + Node::Blockquote(bq) => { + let content = { + let mut buf = String::new(); + serialize_children(&bq.children, &mut buf, ctx); + buf + }; + for line in content.trim_end().lines() { + if line.is_empty() { + out.push_str(">\n"); + } else { + out.push_str("> "); + out.push_str(line); + out.push('\n'); + } + } + out.push('\n'); + } + Node::ThematicBreak(_) => { + out.push_str("---\n\n"); + } + Node::Html(html) => { + out.push_str(&html.value); + out.push_str("\n\n"); + } + Node::Table(table) => { + serialize_table(table, out); + } + Node::Delete(del) => { + out.push_str("~~"); + serialize_inline_children(&del.children, out, ctx); + out.push_str("~~"); + } + Node::Break(_) => { + out.push_str("\\\n"); + } + // MDX nodes — these should be handled by the transformer before serialization. + // If they reach here, output them as-is or skip. + Node::MdxFlowExpression(expr) => { + out.push('{'); + out.push_str(&expr.value); + out.push_str("}\n\n"); + } + Node::MdxTextExpression(expr) => { + out.push('{'); + out.push_str(&expr.value); + out.push('}'); + } + Node::MdxjsEsm(esm) => { + out.push_str(&esm.value); + out.push_str("\n\n"); + } + Node::MdxJsxFlowElement(_) | Node::MdxJsxTextElement(_) => { + // JSX elements that weren't transformed — skip + } + // Nodes we don't need to handle specially + Node::Definition(def) => { + out.push_str(&format!("[{}]: {}", def.identifier, def.url)); + if let Some(title) = &def.title { + out.push_str(&format!(" \"{}\"", title)); + } + out.push_str("\n\n"); + } + Node::FootnoteDefinition(fd) => { + out.push_str(&format!("[^{}]: ", fd.identifier)); + serialize_children(&fd.children, out, ctx); + } + Node::FootnoteReference(fr) => { + out.push_str(&format!("[^{}]", fr.identifier)); + } + Node::ImageReference(ir) => { + out.push_str(&format!("![{}][{}]", ir.alt, ir.identifier)); + } + Node::LinkReference(lr) => { + out.push('['); + serialize_inline_children(&lr.children, out, ctx); + out.push_str(&format!("][{}]", lr.identifier)); + } + // Catch-all for any remaining node types + _ => {} + } +} + +fn serialize_children(children: &[Node], out: &mut String, ctx: &SerializeContext) { + for child in children { + serialize_node(child, out, ctx); + } +} + +fn serialize_inline_children(children: &[Node], out: &mut String, ctx: &SerializeContext) { + for child in children { + serialize_node(child, out, ctx); + } +} + +fn serialize_list_item_children(children: &[Node], out: &mut String, ctx: &SerializeContext) { + for (i, child) in children.iter().enumerate() { + match child { + Node::Paragraph(para) => { + serialize_inline_children(¶.children, out, ctx); + if i < children.len() - 1 { + out.push('\n'); + } + } + Node::List(_) => { + out.push('\n'); + serialize_node(child, out, ctx); + } + _ => { + serialize_node(child, out, ctx); + } + } + } +} + +fn serialize_table(table: &markdown::mdast::Table, out: &mut String) { + if table.children.is_empty() { + return; + } + + // Header row + if let Some(Node::TableRow(header)) = table.children.first() { + out.push('|'); + for cell in &header.children { + if let Node::TableCell(tc) = cell { + out.push(' '); + let mut buf = String::new(); + serialize_inline_children(&tc.children, &mut buf, &SerializeContext::default()); + out.push_str(&buf); + out.push_str(" |"); + } + } + out.push('\n'); + + // Separator row + out.push('|'); + for (i, _) in header.children.iter().enumerate() { + let align = table.align.get(i).copied().unwrap_or(markdown::mdast::AlignKind::None); + match align { + markdown::mdast::AlignKind::Left => out.push_str(" :--- |"), + markdown::mdast::AlignKind::Right => out.push_str(" ---: |"), + markdown::mdast::AlignKind::Center => out.push_str(" :---: |"), + markdown::mdast::AlignKind::None => out.push_str(" --- |"), + } + } + out.push('\n'); + } + + // Data rows + for row in table.children.iter().skip(1) { + if let Node::TableRow(tr) = row { + out.push('|'); + for cell in &tr.children { + if let Node::TableCell(tc) = cell { + out.push(' '); + let mut buf = String::new(); + serialize_inline_children(&tc.children, &mut buf, &SerializeContext::default()); + out.push_str(&buf); + out.push_str(" |"); + } + } + out.push('\n'); + } + } + out.push('\n'); +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parser::parse_mdx; + + fn roundtrip(input: &str) -> String { + let ast = parse_mdx(input).unwrap(); + serialize(&ast) + } + + #[test] + fn test_heading() { + assert_eq!(roundtrip("# Hello\n"), "# Hello"); + } + + #[test] + fn test_paragraph() { + assert_eq!(roundtrip("Hello world\n"), "Hello world"); + } + + #[test] + fn test_code_block() { + let input = "```js\nconsole.log(\"hi\")\n```\n"; + let output = roundtrip(input); + assert!(output.contains("```js")); + assert!(output.contains("console.log")); + } + + #[test] + fn test_list() { + let input = "- item 1\n- item 2\n- item 3\n"; + let output = roundtrip(input); + assert!(output.contains("- item 1")); + assert!(output.contains("- item 2")); + assert!(output.contains("- item 3")); + } + + #[test] + fn test_link() { + let input = "[text](https://example.com)\n"; + let output = roundtrip(input); + assert!(output.contains("[text](https://example.com)")); + } + + #[test] + fn test_strong_emphasis() { + let input = "**bold** and *italic*\n"; + let output = roundtrip(input); + assert!(output.contains("**bold**")); + assert!(output.contains("*italic*")); + } + + #[test] + fn test_frontmatter() { + let input = "---\ntitle: test\n---\n\n# Hello\n"; + let output = roundtrip(input); + assert!(output.starts_with("---\ntitle: test\n---")); + assert!(output.contains("# Hello")); + } + + #[test] + fn test_blockquote() { + let input = "> quoted text\n"; + let output = roundtrip(input); + assert!(output.contains("> quoted text")); + } + + #[test] + fn test_table() { + let input = "| a | b |\n| - | - |\n| 1 | 2 |\n"; + let output = roundtrip(input); + assert!(output.contains("| a |")); + assert!(output.contains("| 1 |")); + } +} diff --git a/libraries/md-compiler/src/transformer.rs b/libraries/md-compiler/src/transformer.rs new file mode 100644 index 00000000..bd33c81a --- /dev/null +++ b/libraries/md-compiler/src/transformer.rs @@ -0,0 +1,680 @@ +//! AST transformer for MDX-to-Markdown conversion. +//! +//! Walks the mdast AST, evaluating expressions, expanding components, +//! and converting JSX elements to Markdown equivalents. + +use std::collections::HashMap; +use markdown::mdast::*; +use crate::expression_eval::{EvaluationScope, evaluate_expression}; + +// --------------------------------------------------------------------------- +// Processing context +// --------------------------------------------------------------------------- + +/// Component handler function type. +pub type ComponentHandler = Box Vec + Send + Sync>; + +/// Processing context passed through the AST transformation. +pub struct ProcessingContext { + pub scope: EvaluationScope, + pub components: HashMap, + pub processing_stack: Vec, + pub base_path: Option, +} + +impl ProcessingContext { + pub fn new(scope: EvaluationScope) -> Self { + let mut ctx = Self { + scope, + components: HashMap::new(), + processing_stack: Vec::new(), + base_path: None, + }; + register_built_in_components(&mut ctx); + ctx + } +} + +// --------------------------------------------------------------------------- +// Built-in components: and +// --------------------------------------------------------------------------- + +fn register_built_in_components(ctx: &mut ProcessingContext) { + // — conditional block wrapper + ctx.components.insert("Md".to_string(), Box::new(|element, ctx| { + if !evaluate_when_condition(element, ctx) { + return vec![]; + } + transform_children(&element.children, ctx) + })); + + // — conditional inline text + ctx.components.insert("Md.Line".to_string(), Box::new(|element, ctx| { + if !evaluate_when_condition(element, ctx) { + return vec![]; + } + let text = extract_text_content(&element.children, &ctx.scope); + if text.is_empty() { + return vec![]; + } + vec![Node::Text(Text { + value: text, + position: None, + })] + })); +} + +/// Evaluate the `when` attribute of a JSX element. +fn evaluate_when_condition(element: &MdxJsxFlowElement, ctx: &ProcessingContext) -> bool { + for attr in &element.attributes { + if let AttributeContent::Property(prop) = attr { + if prop.name == "when" { + return match &prop.value { + Some(AttributeValue::Literal(s)) => s == "true", + Some(AttributeValue::Expression(expr)) => { + match evaluate_expression(&expr.value, &ctx.scope) { + Ok(v) => is_truthy(&v), + Err(_) => false, + } + } + None => false, + }; + } + } + } + true // No `when` attribute = always true +} + +/// Check the `when` condition for text elements too. +fn evaluate_when_condition_text(element: &MdxJsxTextElement, ctx: &ProcessingContext) -> bool { + for attr in &element.attributes { + if let AttributeContent::Property(prop) = attr { + if prop.name == "when" { + return match &prop.value { + Some(AttributeValue::Literal(s)) => s == "true", + Some(AttributeValue::Expression(expr)) => { + match evaluate_expression(&expr.value, &ctx.scope) { + Ok(v) => is_truthy(&v), + Err(_) => false, + } + } + None => false, + }; + } + } + } + true +} + +fn is_truthy(s: &str) -> bool { + !s.is_empty() && s != "false" && s != "0" && s != "undefined" && s != "null" +} + +/// Extract text content from child nodes, evaluating expressions. +fn extract_text_content(children: &[Node], scope: &EvaluationScope) -> String { + let mut result = String::new(); + for child in children { + match child { + Node::Text(t) => result.push_str(&t.value), + Node::MdxTextExpression(expr) => { + if let Ok(val) = evaluate_expression(&expr.value, scope) { + result.push_str(&val); + } + } + _ => { + if let Some(children) = get_children(child) { + result.push_str(&extract_text_content(children, scope)); + } + } + } + } + result +} + +// --------------------------------------------------------------------------- +// JSX to Markdown conversion (for HTML-like elements) +// --------------------------------------------------------------------------- + +fn convert_jsx_to_markdown(element: &MdxJsxFlowElement, ctx: &ProcessingContext) -> Option> { + let name = element.name.as_deref()?.to_lowercase(); + match name.as_str() { + "pre" => convert_pre_element(element, ctx), + "a" => convert_link_element(element, ctx), + "strong" | "b" => convert_strong_element(element, ctx), + "em" | "i" => convert_emphasis_element(element, ctx), + "img" => convert_image_element(element, ctx), + "blockquote" => convert_blockquote_element(element, ctx), + _ => None, + } +} + +fn convert_jsx_text_to_markdown(element: &MdxJsxTextElement, ctx: &ProcessingContext) -> Option> { + let name = element.name.as_deref()?.to_lowercase(); + match name.as_str() { + "a" => convert_link_text_element(element, ctx), + "strong" | "b" => convert_strong_text_element(element, ctx), + "em" | "i" => convert_emphasis_text_element(element, ctx), + _ => None, + } +} + +fn get_attribute_value(attrs: &[AttributeContent], name: &str, scope: &EvaluationScope) -> Option { + for attr in attrs { + if let AttributeContent::Property(prop) = attr { + if prop.name == name { + return match &prop.value { + Some(AttributeValue::Literal(s)) => Some(s.clone()), + Some(AttributeValue::Expression(expr)) => { + evaluate_expression(&expr.value, scope).ok() + } + None => Some(String::new()), + }; + } + } + } + None +} + +fn convert_pre_element(element: &MdxJsxFlowElement, ctx: &ProcessingContext) -> Option> { + // Find child + let code_child = element.children.iter().find_map(|child| { + match child { + Node::MdxJsxFlowElement(el) if el.name.as_deref().map(|n| n.to_lowercase()) == Some("code".into()) => Some(el), + _ => None, + } + })?; + + let class_name = get_attribute_value(&code_child.attributes, "className", &ctx.scope).unwrap_or_default(); + let lang = regex_extract_lang(&class_name); + let code_text = extract_text_content(&code_child.children, &ctx.scope); + + Some(vec![Node::Code(Code { + value: code_text.trim().to_string(), + lang: lang.map(|s| s.to_string()), + meta: None, + position: None, + })]) +} + +fn regex_extract_lang(class_name: &str) -> Option<&str> { + // Match "language-xxx" + if let Some(start) = class_name.find("language-") { + let rest = &class_name[start + 9..]; + let end = rest.find(|c: char| !c.is_ascii_alphanumeric() && c != '-' && c != '_').unwrap_or(rest.len()); + if end > 0 { + return Some(&rest[..end]); + } + } + None +} + +fn convert_link_element(element: &MdxJsxFlowElement, ctx: &ProcessingContext) -> Option> { + let href = get_attribute_value(&element.attributes, "href", &ctx.scope)?; + if href.is_empty() { return None; } + let text = extract_text_content(&element.children, &ctx.scope); + let title = get_attribute_value(&element.attributes, "title", &ctx.scope); + Some(vec![Node::Paragraph(Paragraph { + children: vec![Node::Link(Link { + url: href, + title, + children: vec![Node::Text(Text { value: text, position: None })], + position: None, + })], + position: None, + })]) +} + +fn convert_link_text_element(element: &MdxJsxTextElement, ctx: &ProcessingContext) -> Option> { + let href = get_attribute_value(&element.attributes, "href", &ctx.scope)?; + if href.is_empty() { return None; } + let text = extract_text_content(&element.children, &ctx.scope); + let title = get_attribute_value(&element.attributes, "title", &ctx.scope); + Some(vec![Node::Link(Link { + url: href, + title, + children: vec![Node::Text(Text { value: text, position: None })], + position: None, + })]) +} + +fn convert_strong_element(element: &MdxJsxFlowElement, ctx: &ProcessingContext) -> Option> { + let text = extract_text_content(&element.children, &ctx.scope); + Some(vec![Node::Paragraph(Paragraph { + children: vec![Node::Strong(Strong { + children: vec![Node::Text(Text { value: text, position: None })], + position: None, + })], + position: None, + })]) +} + +fn convert_strong_text_element(element: &MdxJsxTextElement, ctx: &ProcessingContext) -> Option> { + let text = extract_text_content(&element.children, &ctx.scope); + Some(vec![Node::Strong(Strong { + children: vec![Node::Text(Text { value: text, position: None })], + position: None, + })]) +} + +fn convert_emphasis_element(element: &MdxJsxFlowElement, ctx: &ProcessingContext) -> Option> { + let text = extract_text_content(&element.children, &ctx.scope); + Some(vec![Node::Paragraph(Paragraph { + children: vec![Node::Emphasis(Emphasis { + children: vec![Node::Text(Text { value: text, position: None })], + position: None, + })], + position: None, + })]) +} + +fn convert_emphasis_text_element(element: &MdxJsxTextElement, ctx: &ProcessingContext) -> Option> { + let text = extract_text_content(&element.children, &ctx.scope); + Some(vec![Node::Emphasis(Emphasis { + children: vec![Node::Text(Text { value: text, position: None })], + position: None, + })]) +} + +fn convert_image_element(element: &MdxJsxFlowElement, ctx: &ProcessingContext) -> Option> { + let src = get_attribute_value(&element.attributes, "src", &ctx.scope)?; + if src.is_empty() { return None; } + let alt = get_attribute_value(&element.attributes, "alt", &ctx.scope).unwrap_or_default(); + let title = get_attribute_value(&element.attributes, "title", &ctx.scope); + Some(vec![Node::Paragraph(Paragraph { + children: vec![Node::Image(Image { + url: src, + alt, + title, + position: None, + })], + position: None, + })]) +} + +fn convert_blockquote_element(element: &MdxJsxFlowElement, ctx: &ProcessingContext) -> Option> { + let text = extract_text_content(&element.children, &ctx.scope); + Some(vec![Node::Blockquote(Blockquote { + children: vec![Node::Paragraph(Paragraph { + children: vec![Node::Text(Text { value: text, position: None })], + position: None, + })], + position: None, + })]) +} + +// --------------------------------------------------------------------------- +// Core AST transformation +// --------------------------------------------------------------------------- + +/// Transform the root AST, processing all MDX nodes. +pub fn transform_ast(root: &Node, ctx: &ProcessingContext) -> Node { + match root { + Node::Root(r) => { + let new_children = transform_children(&r.children, ctx); + Node::Root(Root { + children: new_children, + position: r.position.clone(), + }) + } + _ => root.clone(), + } +} + +/// Transform a list of child nodes. +fn transform_children(children: &[Node], ctx: &ProcessingContext) -> Vec { + let mut result = Vec::new(); + + for child in children { + match child { + // Skip ESM nodes (export/import statements) + Node::MdxjsEsm(_) => {} + + // Flow expressions: {expression} + Node::MdxFlowExpression(expr) => { + let trimmed = expr.value.trim(); + // Skip block comments + if trimmed.starts_with("/*") && trimmed.ends_with("*/") { + continue; + } + match evaluate_expression(&expr.value, &ctx.scope) { + Ok(val) if !val.is_empty() => { + result.push(Node::Paragraph(Paragraph { + children: vec![Node::Text(Text { value: val, position: None })], + position: None, + })); + } + _ => {} + } + } + + // JSX flow elements: or + Node::MdxJsxFlowElement(element) => { + let name = element.name.as_deref().unwrap_or(""); + + // Check if it's a registered component + if let Some(handler) = ctx.components.get(name) { + let nodes = handler(element, ctx); + // Recursively transform the handler output + result.extend(transform_children(&nodes, ctx)); + } else if let Some(converted) = convert_jsx_to_markdown(element, ctx) { + result.extend(converted); + } + // Unknown JSX elements are silently skipped + } + + // Nodes with children — recurse + Node::Paragraph(para) => { + let new_children = transform_inline_children(¶.children, ctx); + if !new_children.is_empty() { + result.push(Node::Paragraph(Paragraph { + children: new_children, + position: para.position.clone(), + })); + } + } + Node::Heading(h) => { + let new_children = transform_inline_children(&h.children, ctx); + result.push(Node::Heading(Heading { + children: new_children, + position: h.position.clone(), + depth: h.depth, + })); + } + Node::Blockquote(bq) => { + let new_children = transform_children(&bq.children, ctx); + result.push(Node::Blockquote(Blockquote { + children: new_children, + position: bq.position.clone(), + })); + } + Node::List(list) => { + let new_children: Vec = list.children.iter().map(|item| { + if let Node::ListItem(li) = item { + Node::ListItem(ListItem { + children: transform_children(&li.children, ctx), + position: li.position.clone(), + spread: li.spread, + checked: li.checked, + }) + } else { + item.clone() + } + }).collect(); + result.push(Node::List(List { + children: new_children, + position: list.position.clone(), + ordered: list.ordered, + start: list.start, + spread: list.spread, + })); + } + Node::Link(link) => { + let new_children = transform_inline_children(&link.children, ctx); + // Simplify link text that looks like file paths + let simplified = new_children.into_iter().map(|c| { + if let Node::Text(t) = &c { + if t.value.contains('/') && t.value.contains('.') { + if let Some(basename) = t.value.rsplit('/').next() { + return Node::Text(Text { value: basename.to_string(), position: t.position.clone() }); + } + } + } + c + }).collect(); + result.push(Node::Link(Link { + children: simplified, + position: link.position.clone(), + url: link.url.clone(), + title: link.title.clone(), + })); + } + Node::Strong(s) => { + let new_children = transform_inline_children(&s.children, ctx); + result.push(Node::Strong(Strong { + children: new_children, + position: s.position.clone(), + })); + } + Node::Emphasis(e) => { + let new_children = transform_inline_children(&e.children, ctx); + result.push(Node::Emphasis(Emphasis { + children: new_children, + position: e.position.clone(), + })); + } + Node::Delete(d) => { + let new_children = transform_inline_children(&d.children, ctx); + result.push(Node::Delete(Delete { + children: new_children, + position: d.position.clone(), + })); + } + Node::Table(table) => { + let new_children: Vec = table.children.iter().map(|row| { + if let Node::TableRow(tr) = row { + let new_cells: Vec = tr.children.iter().map(|cell| { + if let Node::TableCell(tc) = cell { + Node::TableCell(TableCell { + children: transform_inline_children(&tc.children, ctx), + position: tc.position.clone(), + }) + } else { + cell.clone() + } + }).collect(); + Node::TableRow(TableRow { + children: new_cells, + position: tr.position.clone(), + }) + } else { + row.clone() + } + }).collect(); + result.push(Node::Table(Table { + children: new_children, + position: table.position.clone(), + align: table.align.clone(), + })); + } + + // Leaf nodes — pass through + _ => { + result.push(child.clone()); + } + } + } + + result +} + +/// Transform inline children (within paragraphs, headings, etc.) +fn transform_inline_children(children: &[Node], ctx: &ProcessingContext) -> Vec { + let mut result = Vec::new(); + + for child in children { + match child { + // Inline text expressions: {expression} + Node::MdxTextExpression(expr) => { + let trimmed = expr.value.trim(); + if trimmed.starts_with("/*") && trimmed.ends_with("*/") { + continue; + } + match evaluate_expression(&expr.value, &ctx.scope) { + Ok(val) => { + result.push(Node::Text(Text { value: val, position: None })); + } + Err(_) => { + // Keep expression as-is on error + result.push(Node::Text(Text { value: String::new(), position: None })); + } + } + } + + // Inline JSX: or + Node::MdxJsxTextElement(element) => { + let name = element.name.as_deref().unwrap_or(""); + + // Check registered components + if name == "Md.Line" { + if evaluate_when_condition_text(element, ctx) { + let text = extract_text_content(&element.children, &ctx.scope); + if !text.is_empty() { + result.push(Node::Text(Text { value: text, position: None })); + } + } + } else if name == "Md" { + if evaluate_when_condition_text(element, ctx) { + let transformed = transform_inline_children(&element.children, ctx); + result.extend(transformed); + } + } else if let Some(converted) = convert_jsx_text_to_markdown(element, ctx) { + result.extend(converted); + } + // Unknown inline JSX elements are silently skipped + } + + // Recurse into inline containers + Node::Strong(s) => { + let new_children = transform_inline_children(&s.children, ctx); + result.push(Node::Strong(Strong { + children: new_children, + position: s.position.clone(), + })); + } + Node::Emphasis(e) => { + let new_children = transform_inline_children(&e.children, ctx); + result.push(Node::Emphasis(Emphasis { + children: new_children, + position: e.position.clone(), + })); + } + Node::Link(link) => { + let new_children = transform_inline_children(&link.children, ctx); + result.push(Node::Link(Link { + children: new_children, + position: link.position.clone(), + url: link.url.clone(), + title: link.title.clone(), + })); + } + Node::Delete(d) => { + let new_children = transform_inline_children(&d.children, ctx); + result.push(Node::Delete(Delete { + children: new_children, + position: d.position.clone(), + })); + } + + // Leaf nodes — pass through + _ => { + result.push(child.clone()); + } + } + } + + result +} + +/// Get children of a node, if it has any. +fn get_children(node: &Node) -> Option<&Vec> { + match node { + Node::Root(n) => Some(&n.children), + Node::Paragraph(n) => Some(&n.children), + Node::Heading(n) => Some(&n.children), + Node::Blockquote(n) => Some(&n.children), + Node::List(n) => Some(&n.children), + Node::ListItem(n) => Some(&n.children), + Node::Strong(n) => Some(&n.children), + Node::Emphasis(n) => Some(&n.children), + Node::Link(n) => Some(&n.children), + Node::Delete(n) => Some(&n.children), + Node::Table(n) => Some(&n.children), + Node::TableRow(n) => Some(&n.children), + Node::TableCell(n) => Some(&n.children), + Node::MdxJsxFlowElement(n) => Some(&n.children), + Node::MdxJsxTextElement(n) => Some(&n.children), + Node::FootnoteDefinition(n) => Some(&n.children), + _ => None, + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parser::parse_mdx; + use crate::serializer::serialize; + use serde_json::json; + + fn make_scope() -> EvaluationScope { + let mut scope = EvaluationScope::new(); + scope.insert("os".into(), json!({"platform": "win32"})); + scope.insert("profile".into(), json!({"name": "TrueNine"})); + scope.insert("tool".into(), json!({"name": "cursor"})); + scope + } + + fn compile(source: &str, scope: EvaluationScope) -> String { + let ast = parse_mdx(source).unwrap(); + let ctx = ProcessingContext::new(scope); + let transformed = transform_ast(&ast, &ctx); + serialize(&transformed) + } + + #[test] + fn test_expression_in_paragraph() { + let result = compile("Platform: {os.platform}\n", make_scope()); + assert!(result.contains("Platform: win32"), "Got: {}", result); + } + + #[test] + fn test_md_component_when_true() { + let result = compile("\n\nVisible content\n\n\n", make_scope()); + assert!(result.contains("Visible content"), "Got: {}", result); + } + + #[test] + fn test_md_component_when_false() { + let result = compile("\n\nHidden content\n\n\n", make_scope()); + assert!(!result.contains("Hidden content"), "Got: {}", result); + } + + #[test] + fn test_md_line_component() { + let result = compile( + "Before\n\nName: {profile.name}\n\nAfter\n", + make_scope(), + ); + assert!(result.contains("Name: TrueNine"), "Got: {}", result); + } + + #[test] + fn test_md_line_when_false() { + let result = compile( + "Hidden\n", + make_scope(), + ); + assert!(!result.contains("Hidden"), "Got: {}", result); + } + + #[test] + fn test_passthrough_markdown() { + let result = compile("# Title\n\nParagraph text.\n\n- item 1\n- item 2\n", make_scope()); + assert!(result.contains("# Title"), "Got: {}", result); + assert!(result.contains("Paragraph text"), "Got: {}", result); + assert!(result.contains("- item 1"), "Got: {}", result); + } + + #[test] + fn test_frontmatter_stripped() { + let result = compile("---\ntitle: test\n---\n\n# Hello\n", make_scope()); + // Frontmatter should be preserved (not stripped by transformer — that's the caller's job) + assert!(result.contains("# Hello"), "Got: {}", result); + } + + #[test] + fn test_code_block_preserved() { + let result = compile("```js\nconsole.log(\"hi\")\n```\n", make_scope()); + assert!(result.contains("```js"), "Got: {}", result); + assert!(result.contains("console.log"), "Got: {}", result); + } +} diff --git a/packages/init-bundle/tsconfig.json b/libraries/md-compiler/tsconfig.json similarity index 100% rename from packages/init-bundle/tsconfig.json rename to libraries/md-compiler/tsconfig.json diff --git a/libraries/md-compiler/tsconfig.lib.json b/libraries/md-compiler/tsconfig.lib.json new file mode 100644 index 00000000..7df70332 --- /dev/null +++ b/libraries/md-compiler/tsconfig.lib.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "compilerOptions": { + "composite": true, + "rootDir": "./src", + "noEmit": false, + "outDir": "./dist", + "skipLibCheck": true + }, + "include": [ + "src/**/*", + "env.d.ts" + ], + "exclude": [ + "../node_modules", + "dist", + "**/*.spec.ts", + "**/*.test.ts" + ] +} diff --git a/packages/md-compiler/tsdown.config.ts b/libraries/md-compiler/tsdown.config.ts similarity index 100% rename from packages/md-compiler/tsdown.config.ts rename to libraries/md-compiler/tsdown.config.ts diff --git a/packages/logger/vite.config.ts b/libraries/md-compiler/vite.config.ts similarity index 100% rename from packages/logger/vite.config.ts rename to libraries/md-compiler/vite.config.ts diff --git a/libraries/md-compiler/vitest.config.ts b/libraries/md-compiler/vitest.config.ts new file mode 100644 index 00000000..3c0bd704 --- /dev/null +++ b/libraries/md-compiler/vitest.config.ts @@ -0,0 +1,17 @@ +import {fileURLToPath} from 'node:url' +import {configDefaults, defineConfig, mergeConfig} from 'vitest/config' +import viteConfig from './vite.config' + +export default mergeConfig( + viteConfig, + defineConfig({ + test: { + environment: 'node', + exclude: [...configDefaults.exclude], + root: fileURLToPath(new URL('./', import.meta.url)), + testTimeout: 30000, + onConsoleLog: () => false, + passWithNoTests: true + } + }) +) diff --git a/libraries/plugin-shared/Cargo.toml b/libraries/plugin-shared/Cargo.toml new file mode 100644 index 00000000..afe6d88d --- /dev/null +++ b/libraries/plugin-shared/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "tnmsc-plugin-shared" +description = "Shared types and data structures for tnmsc plugins" +version.workspace = true +edition.workspace = true +license.workspace = true +authors.workspace = true +repository.workspace = true + +[lib] +crate-type = ["rlib"] + +[dependencies] +serde = { workspace = true } +serde_json = { workspace = true } diff --git a/libraries/plugin-shared/src/lib.rs b/libraries/plugin-shared/src/lib.rs new file mode 100644 index 00000000..69c739cc --- /dev/null +++ b/libraries/plugin-shared/src/lib.rs @@ -0,0 +1,622 @@ +//! Shared types and data structures for tnmsc plugins. +//! +//! Defines `CollectedInputContext`, `RelativePath`, plugin traits, +//! and other types shared between input plugins, CLI, and output runtime. + +use std::collections::HashMap; +use std::path::PathBuf; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +// --------------------------------------------------------------------------- +// Enums +// --------------------------------------------------------------------------- + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum PluginKind { + Input, + Output, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum PromptKind { + GlobalMemory, + ProjectRootMemory, + ProjectChildrenMemory, + FastCommand, + SubAgent, + Skill, + SkillChildDoc, + SkillResource, + SkillMcpConfig, + Readme, + Rule, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum RuleScope { + Project, + Global, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum FilePathKind { + Relative, + Absolute, + Root, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum IDEKind { + VSCode, + IntellijIDEA, + Git, + EditorConfig, + Original, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum NamingCaseKind { + CamelCase, + PascalCase, + SnakeCase, + KebabCase, + UpperCase, + LowerCase, + Original, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum SkillResourceEncoding { + Text, + Base64, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum SkillResourceCategory { + Code, + Data, + Document, + Config, + Script, + Image, + Binary, + Other, +} + +// --------------------------------------------------------------------------- +// Path types +// --------------------------------------------------------------------------- + +/// Relative path with base path for computing absolute paths. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RelativePath { + pub path_kind: FilePathKind, + pub path: String, + pub base_path: String, + /// Pre-computed absolute path for serialization to Node.js + #[serde(skip_serializing_if = "Option::is_none")] + pub absolute_path: Option, + /// Pre-computed directory name for serialization to Node.js + #[serde(skip_serializing_if = "Option::is_none")] + pub directory_name: Option, +} + +impl RelativePath { + pub fn new(path: &str, base_path: &str) -> Self { + let abs = PathBuf::from(base_path).join(path); + let dir_name = PathBuf::from(path) + .parent() + .map(|p| p.to_string_lossy().into_owned()) + .unwrap_or_default(); + Self { + path_kind: FilePathKind::Relative, + path: path.to_string(), + base_path: base_path.to_string(), + absolute_path: Some(abs.to_string_lossy().into_owned()), + directory_name: Some(dir_name), + } + } + + pub fn get_absolute_path(&self) -> String { + self.absolute_path.clone().unwrap_or_else(|| { + PathBuf::from(&self.base_path) + .join(&self.path) + .to_string_lossy() + .into_owned() + }) + } + + pub fn get_directory_name(&self) -> String { + self.directory_name.clone().unwrap_or_else(|| { + PathBuf::from(&self.path) + .parent() + .map(|p| p.to_string_lossy().into_owned()) + .unwrap_or_default() + }) + } +} + +/// Root path (workspace root). +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RootPath { + pub path_kind: FilePathKind, + pub path: String, +} + +impl RootPath { + pub fn new(path: &str) -> Self { + Self { + path_kind: FilePathKind::Root, + path: path.to_string(), + } + } +} + +// --------------------------------------------------------------------------- +// YAML front matter types +// --------------------------------------------------------------------------- + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct YAMLFrontMatter { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub naming_case: Option, + #[serde(flatten)] + pub extra: HashMap, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CommonYAMLFrontMatter { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub naming_case: Option, + #[serde(flatten)] + pub extra: HashMap, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RuleYAMLFrontMatter { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default)] + pub globs: Vec, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub scope: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub seri_name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub naming_case: Option, + #[serde(flatten)] + pub extra: HashMap, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FastCommandYAMLFrontMatter { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub argument_hint: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub allow_tools: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub naming_case: Option, + #[serde(flatten)] + pub extra: HashMap, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SubAgentYAMLFrontMatter { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub model: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub color: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub argument_hint: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub allow_tools: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub naming_case: Option, + #[serde(flatten)] + pub extra: HashMap, +} + +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SkillYAMLFrontMatter { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub description: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub display_name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub author: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub version: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub keywords: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub allow_tools: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub naming_case: Option, + #[serde(flatten)] + pub extra: HashMap, +} + +// --------------------------------------------------------------------------- +// Prompt types +// --------------------------------------------------------------------------- + +/// Rule prompt with glob patterns. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RulePrompt { + #[serde(rename = "type")] + pub prompt_type: PromptKind, + pub content: String, + pub length: usize, + pub dir: RelativePath, + pub series: String, + pub rule_name: String, + pub globs: Vec, + pub scope: RuleScope, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub seri_name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub yaml_front_matter: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub raw_mdx_content: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub markdown_contents: Option>, +} + +/// Fast command prompt. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct FastCommandPrompt { + #[serde(rename = "type")] + pub prompt_type: PromptKind, + pub content: String, + pub length: usize, + pub dir: RelativePath, + pub command_name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub series: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub global_only: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub yaml_front_matter: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub raw_mdx_content: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub markdown_contents: Option>, +} + +/// Sub-agent prompt. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SubAgentPrompt { + #[serde(rename = "type")] + pub prompt_type: PromptKind, + pub content: String, + pub length: usize, + pub dir: RelativePath, + pub agent_name: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub series: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub yaml_front_matter: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub raw_mdx_content: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub markdown_contents: Option>, +} + +/// Skill child document. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SkillChildDoc { + #[serde(rename = "type")] + pub prompt_type: PromptKind, + pub content: String, + pub length: usize, + pub relative_path: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub markdown_contents: Option>, +} + +/// Skill resource file. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SkillResource { + #[serde(rename = "type")] + pub prompt_type: PromptKind, + pub extension: String, + pub file_name: String, + pub relative_path: String, + pub content: String, + pub encoding: SkillResourceEncoding, + pub category: SkillResourceCategory, + pub length: usize, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub mime_type: Option, +} + +/// MCP server configuration entry. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct McpServerConfig { + pub command: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub args: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub env: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub disabled: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub auto_approve: Option>, +} + +/// Skill MCP configuration (mcp.json). +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SkillMcpConfig { + #[serde(rename = "type")] + pub prompt_type: PromptKind, + pub mcp_servers: HashMap, + pub raw_content: String, +} + +/// Skill prompt. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SkillPrompt { + #[serde(rename = "type")] + pub prompt_type: PromptKind, + pub content: String, + pub length: usize, + pub dir: RelativePath, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub yaml_front_matter: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub mcp_config: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub child_docs: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub resources: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub markdown_contents: Option>, +} + +/// Global memory prompt. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GlobalMemoryPrompt { + #[serde(rename = "type")] + pub prompt_type: PromptKind, + pub content: String, + pub length: usize, + pub dir: RelativePath, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub markdown_contents: Option>, +} + +/// Readme prompt. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ReadmePrompt { + #[serde(rename = "type")] + pub prompt_type: PromptKind, + pub content: String, + pub length: usize, + pub dir: RelativePath, + pub project_name: String, + pub target_dir: RelativePath, + pub is_root: bool, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub markdown_contents: Option>, +} + +// --------------------------------------------------------------------------- +// IDE config types +// --------------------------------------------------------------------------- + +/// IDE configuration file. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ProjectIDEConfigFile { + #[serde(rename = "type")] + pub ide_type: IDEKind, + pub content: String, + pub length: usize, + pub dir: RelativePath, + pub file_path_kind: FilePathKind, +} + +// --------------------------------------------------------------------------- +// Project & Workspace +// --------------------------------------------------------------------------- + +/// Project within a workspace. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Project { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub name: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub dir_from_workspace_path: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub root_memory_prompt: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub child_memory_prompts: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub is_prompt_source_project: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub project_config: Option, +} + +/// Workspace containing projects. +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Workspace { + pub directory: RootPath, + #[serde(default)] + pub projects: Vec, +} + +// --------------------------------------------------------------------------- +// CollectedInputContext — the main bridge type +// --------------------------------------------------------------------------- + +/// All collected input information, serialized from Rust to Node.js output runtime. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CollectedInputContext { + #[serde(default, skip_serializing_if = "Option::is_none")] + pub workspace: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub vscode_config_files: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub jetbrains_config_files: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub editor_config_files: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub fast_commands: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub sub_agents: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub skills: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub rules: Option>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub global_memory: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub global_git_ignore: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub shadow_git_exclude: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub shadow_source_project_dir: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub readme_prompts: Option>, +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_relative_path() { + let rp = RelativePath::new("src/skills/test.mdx", "/home/user/workspace/aindex"); + assert_eq!(rp.path, "src/skills/test.mdx"); + assert_eq!(rp.base_path, "/home/user/workspace/aindex"); + assert!(rp.get_absolute_path().contains("src/skills/test.mdx")); + assert_eq!(rp.get_directory_name(), "src/skills"); + } + + #[test] + fn test_collected_input_context_default() { + let ctx = CollectedInputContext::default(); + assert!(ctx.workspace.is_none()); + assert!(ctx.fast_commands.is_none()); + } + + #[test] + fn test_collected_input_context_serialize() { + let ctx = CollectedInputContext { + workspace: Some(Workspace { + directory: RootPath::new("/workspace"), + projects: vec![], + }), + global_git_ignore: Some("node_modules/\n".to_string()), + ..Default::default() + }; + let json = serde_json::to_string(&ctx).unwrap(); + assert!(json.contains("workspace")); + assert!(json.contains("globalGitIgnore")); + // Fields that are None should not appear + assert!(!json.contains("fastCommands")); + } + + #[test] + fn test_collected_input_context_roundtrip() { + let ctx = CollectedInputContext { + workspace: Some(Workspace { + directory: RootPath::new("/workspace"), + projects: vec![Project { + name: Some("test-project".into()), + ..Default::default() + }], + }), + fast_commands: Some(vec![FastCommandPrompt { + prompt_type: PromptKind::FastCommand, + content: "# Test Command\n\nDo something.".into(), + length: 30, + dir: RelativePath::new("commands/test.mdx", "/workspace/aindex/dist"), + command_name: "test".into(), + series: Some("default".into()), + global_only: None, + yaml_front_matter: Some(FastCommandYAMLFrontMatter { + description: Some("A test command".into()), + ..Default::default() + }), + raw_mdx_content: None, + markdown_contents: None, + }]), + ..Default::default() + }; + + let json = serde_json::to_string_pretty(&ctx).unwrap(); + let parsed: CollectedInputContext = serde_json::from_str(&json).unwrap(); + assert_eq!(parsed.workspace.as_ref().unwrap().projects.len(), 1); + assert_eq!(parsed.fast_commands.as_ref().unwrap().len(), 1); + assert_eq!(parsed.fast_commands.as_ref().unwrap()[0].command_name, "test"); + } + + #[test] + fn test_rule_prompt_serialize() { + let rule = RulePrompt { + prompt_type: PromptKind::Rule, + content: "# Rule\n\nDo this.".into(), + length: 17, + dir: RelativePath::new("rules/default/test.mdx", "/workspace/aindex/dist"), + series: "default".into(), + rule_name: "test".into(), + globs: vec!["**/*.ts".into(), "**/*.tsx".into()], + scope: RuleScope::Project, + seri_name: None, + yaml_front_matter: None, + raw_mdx_content: None, + markdown_contents: None, + }; + let json = serde_json::to_string(&rule).unwrap(); + assert!(json.contains("\"type\":\"Rule\"")); + assert!(json.contains("\"globs\"")); + } + + #[test] + fn test_enums_serialize() { + assert_eq!(serde_json::to_string(&PromptKind::FastCommand).unwrap(), "\"FastCommand\""); + assert_eq!(serde_json::to_string(&RuleScope::Global).unwrap(), "\"global\""); + assert_eq!(serde_json::to_string(&IDEKind::VSCode).unwrap(), "\"VSCode\""); + assert_eq!(serde_json::to_string(&SkillResourceEncoding::Base64).unwrap(), "\"base64\""); + } +} diff --git a/package.json b/package.json index 47502eaf..808bf3f0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@truenine/memory-sync", - "version": "2026.10222.10836", + "version": "2026.10223.10555", "description": "Cross-AI-tool prompt synchronisation toolkit (CLI + Tauri desktop GUI) — one ruleset, multi-target adaptation. Monorepo powered by pnpm + Turbo.", "license": "AGPL-3.0-only", "keywords": [ @@ -36,8 +36,10 @@ "typecheck": "turbo typecheck", "dev:doc": "pnpm -C doc dev", "build:doc": "pnpm -C doc build", - "sync-versions": "tsx .git-hooks/sync-versions.ts", - "postinstall": "simple-git-hooks" + "sync-versions": "tsx .githooks/sync-versions.ts", + "build:native": "tsx scripts/build-native.ts", + "build:native:copy": "tsx scripts/copy-napi.ts", + "postinstall": "simple-git-hooks && pnpm run build:native" }, "author": { "email": "truenine304520@gmail.com", @@ -48,13 +50,15 @@ "node": ">= 22" }, "devEngines": { - "node": ">= 25.6.1" + "node": ">= 25.6.1", + "rust": ">= 1.87.0" }, "simple-git-hooks": { - "pre-commit": "pnpm tsx .git-hooks/sync-versions.ts || true" + "pre-commit": "pnpm tsx .githooks/sync-versions.ts || true" }, "packageManager": "pnpm@10.30.1", "devDependencies": { + "@napi-rs/cli": "^3.5.1", "@truenine/eslint10-config": "catalog:", "@types/node": "catalog:", "eslint": "catalog:", diff --git a/packages/desk-paths/package.json b/packages/desk-paths/package.json index adff96ab..74b4280c 100644 --- a/packages/desk-paths/package.json +++ b/packages/desk-paths/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/desk-paths", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "", "exports": { "./package.json": "./package.json", diff --git a/packages/init-bundle/.gitignore b/packages/init-bundle/.gitignore deleted file mode 100644 index 7e9ede57..00000000 --- a/packages/init-bundle/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -!public/ -!public/.editorconfig -!public/.idea/ -!public/.vscode/ diff --git a/packages/init-bundle/package.json b/packages/init-bundle/package.json deleted file mode 100644 index 96e858b4..00000000 --- a/packages/init-bundle/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "@truenine/init-bundle", - "type": "module", - "version": "1.0.0", - "description": "Internal initialization bundle - templates and configs sourced from aindex/", - "keywords": [ - "init", - "bundle", - "templates", - "aindex" - ], - "exports": { - "./package.json": "./package.json", - ".": { - "types": "./dist/index.d.mts", - "import": "./dist/index.mjs" - } - }, - "module": "dist/index.mjs", - "types": "dist/index.d.mts", - "scripts": { - "build": "tsdown --config-loader unrun", - "lint": "eslint --cache .", - "typecheck": "tsc --noEmit -p tsconfig.lib.json", - "check": "run-p typecheck lint", - "lintfix": "eslint --fix --cache .", - "test": "vitest run", - "prepublishOnly": "run-s build" - }, - "dependencies": {} -} diff --git a/packages/init-bundle/public/public/kiro_global_powers_registry.json b/packages/init-bundle/public/public/kiro_global_powers_registry.json deleted file mode 100644 index e4c4e70b..00000000 --- a/packages/init-bundle/public/public/kiro_global_powers_registry.json +++ /dev/null @@ -1,318 +0,0 @@ -{ - "version": "1.0.0", - "powers": { - "postman": { - "name": "postman", - "description": "Automate API testing and collection management with Postman - create workspaces, collections, environments, and run tests programmatically", - "displayName": "API Testing with Postman", - "author": "Postman", - "iconUrl": "https://prod.download.desktop.kiro.dev/powers/icons/postman.png", - "repositoryUrl": "https://github.com/kirodotdev/powers/tree/main/postman", - "license": "", - "repositoryCloneUrl": "git@github.com:kirodotdev/powers.git", - "pathInRepo": "postman", - "repositoryBranch": "main", - "installed": false, - "keywords": [], - "source": { - "type": "registry" - } - }, - "figma": { - "name": "figma", - "description": "Connect Figma designs to code components - automatically generate design system rules, map UI components to Figma designs, and maintain design-code consistency", - "displayName": "Design to Code with Figma", - "author": "Figma", - "iconUrl": "https://prod.download.desktop.kiro.dev/powers/icons/figma.png", - "repositoryUrl": "https://github.com/kirodotdev/powers/tree/main/figma", - "license": "", - "repositoryCloneUrl": "git@github.com:kirodotdev/powers.git", - "pathInRepo": "figma", - "repositoryBranch": "main", - "installed": false, - "keywords": [], - "source": { - "type": "registry" - } - }, - "netlify-deployment": { - "name": "netlify-deployment", - "description": "Deploy React, Next.js, Vue, and other modern web apps to Netlify's global CDN with automatic builds.", - "displayName": "Deploy web apps with Netlify", - "author": "Netlify", - "iconUrl": "https://prod.download.desktop.kiro.dev/powers/icons/netlify.png", - "repositoryUrl": "https://github.com/netlify/context-and-tools/tree/main/context/steering/netlify-deployment-power", - "license": "", - "repositoryCloneUrl": "git@github.com:netlify/context-and-tools.git", - "pathInRepo": "context/steering/netlify-deployment-power", - "repositoryBranch": "main", - "installed": false, - "keywords": [], - "source": { - "type": "registry" - } - }, - "amazon-aurora-postgresql": { - "name": "amazon-aurora-postgresql", - "description": "Build applications backed by Aurora PostgreSQL by leveraging Aurora PostgreSQL specific best practices.", - "displayName": "Build applications with Aurora PostgreSQL", - "author": "AWS", - "iconUrl": "https://prod.download.desktop.kiro.dev/powers/icons/amazon-aurora.png", - "repositoryUrl": "https://github.com/awslabs/mcp/tree/main/src/postgres-mcp-server/kiro_power", - "license": "", - "repositoryCloneUrl": "git@github.com:awslabs/mcp.git", - "pathInRepo": "src/postgres-mcp-server/kiro_power", - "repositoryBranch": "main", - "installed": false, - "keywords": [], - "source": { - "type": "registry" - } - }, - "supabase-hosted": { - "name": "supabase-hosted", - "description": "Build applications with Supabase's Postgres database, authentication, storage, and real-time subscriptions", - "displayName": "Build a backend with Supabase", - "author": "Supabase", - "iconUrl": "https://prod.download.desktop.kiro.dev/powers/icons/supabase.png", - "repositoryUrl": "https://github.com/supabase-community/kiro-powers/tree/main/powers/supabase-hosted", - "license": "", - "repositoryCloneUrl": "git@github.com:supabase-community/kiro-powers.git", - "pathInRepo": "powers/supabase-hosted", - "repositoryBranch": "main", - "installed": false, - "keywords": [], - "source": { - "type": "registry" - } - }, - "supabase-local": { - "name": "supabase-local", - "description": "Local development with Supabase allows you to work on your projects in a self-contained environment on your local machine.", - "displayName": "Build a backend (local) with Supabase", - "author": "Supabase", - "iconUrl": "https://prod.download.desktop.kiro.dev/powers/icons/supabase.png", - "repositoryUrl": "https://github.com/supabase-community/kiro-powers/tree/main/powers/supabase-local", - "license": "", - "repositoryCloneUrl": "git@github.com:supabase-community/kiro-powers.git", - "pathInRepo": "powers/supabase-local", - "repositoryBranch": "main", - "installed": false, - "keywords": [], - "source": { - "type": "registry" - } - }, - "terraform": { - "name": "terraform", - "description": "Build and manage Infrastructure as Code with Terraform - access registry providers, modules, policies, and HCP Terraform workflow management", - "displayName": "Deploy infrastructure with Terraform", - "author": "HashiCorp", - "iconUrl": "https://prod.download.desktop.kiro.dev/powers/icons/terraform.png", - "repositoryUrl": "https://github.com/kirodotdev/powers/tree/main/terraform", - "license": "", - "repositoryCloneUrl": "git@github.com:kirodotdev/powers.git", - "pathInRepo": "terraform", - "repositoryBranch": "main", - "installed": false, - "keywords": [], - "source": { - "type": "registry" - } - }, - "strands": { - "name": "strands", - "description": "Build AI agents with Strands Agent SDK using Bedrock, Anthropic, OpenAI, Gemini, or Llama models", - "displayName": "Build an agent with Strands", - "author": "AWS", - "iconUrl": "https://prod.download.desktop.kiro.dev/powers/icons/strands.png", - "repositoryUrl": "https://github.com/kirodotdev/powers/tree/main/strands", - "license": "", - "repositoryCloneUrl": "git@github.com:kirodotdev/powers.git", - "pathInRepo": "strands", - "repositoryBranch": "main", - "installed": false, - "keywords": [], - "source": { - "type": "registry" - } - }, - "aws-agentcore": { - "name": "aws-agentcore", - "description": "Amazon Bedrock AgentCore is an agentic platform for building, deploying, and operating effective agents.", - "displayName": "Build an agent with Amazon Bedrock AgentCore", - "author": "AWS", - "iconUrl": "https://prod.download.desktop.kiro.dev/powers/icons/agentcore.png", - "repositoryUrl": "https://github.com/kirodotdev/powers/tree/main/aws-agentcore", - "license": "", - "repositoryCloneUrl": "git@github.com:kirodotdev/powers.git", - "pathInRepo": "aws-agentcore", - "repositoryBranch": "main", - "installed": false, - "keywords": [], - "source": { - "type": "registry" - } - }, - "neon": { - "name": "neon", - "description": "Serverless Postgres with database branching, autoscaling, and scale-to-zero - perfect for modern development workflows", - "displayName": "Build a database with Neon", - "author": "Neon", - "iconUrl": "https://prod.download.desktop.kiro.dev/powers/icons/neon.png", - "repositoryUrl": "https://github.com/kirodotdev/powers/tree/main/neon", - "license": "", - "repositoryCloneUrl": "git@github.com:kirodotdev/powers.git", - "pathInRepo": "neon", - "repositoryBranch": "main", - "installed": false, - "keywords": [], - "source": { - "type": "registry" - } - }, - "datadog": { - "name": "datadog", - "description": "Query logs, metrics, traces, RUM events, incidents, and monitors from Datadog for production debugging and performance analysis", - "displayName": "Datadog Observability", - "author": "Datadog", - "iconUrl": "https://prod.download.desktop.kiro.dev/powers/icons/datadog.png", - "repositoryUrl": "https://github.com/kirodotdev/powers/tree/main/datadog", - "license": "", - "repositoryCloneUrl": "git@github.com:kirodotdev/powers.git", - "pathInRepo": "datadog", - "repositoryBranch": "main", - "installed": false, - "keywords": [], - "source": { - "type": "registry" - } - }, - "dynatrace": { - "name": "dynatrace", - "description": "Query logs, metrics, traces, problems, and Kubernetes events from Dynatrace using DQL for production debugging and performance analysis", - "displayName": "Dynatrace Observability", - "author": "Dynatrace", - "iconUrl": "https://prod.download.desktop.kiro.dev/powers/icons/dynatrace.png", - "repositoryUrl": "https://github.com/kirodotdev/powers/tree/main/dynatrace", - "license": "", - "repositoryCloneUrl": "git@github.com:kirodotdev/powers.git", - "pathInRepo": "dynatrace", - "repositoryBranch": "main", - "installed": false, - "keywords": [], - "source": { - "type": "registry" - } - }, - "stripe": { - "name": "stripe", - "description": "Build payment integrations with Stripe - accept payments, manage subscriptions, handle billing, and process refunds", - "displayName": "Stripe Payments", - "author": "Stripe", - "iconUrl": "https://prod.download.desktop.kiro.dev/powers/icons/stripe.png", - "repositoryUrl": "https://github.com/kirodotdev/powers/tree/main/stripe", - "license": "", - "repositoryCloneUrl": "git@github.com:kirodotdev/powers.git", - "pathInRepo": "stripe", - "repositoryBranch": "main", - "installed": false, - "keywords": [], - "source": { - "type": "registry" - } - }, - "aws-infrastructure-as-code": { - "name": "aws-infrastructure-as-code", - "description": "Build well-architected AWS infrastructure with CDK using latest documentation, best practices, and code samples. Validate CloudFormation templates, check resource configuration security compliance, and troubleshoot deployments.", - "displayName": "Build AWS infrastructure with CDK and CloudFormation", - "author": "AWS", - "iconUrl": "https://prod.download.desktop.kiro.dev/powers/icons/iac.png", - "repositoryUrl": "https://github.com/kirodotdev/powers/tree/main/aws-infrastructure-as-code", - "license": "", - "repositoryCloneUrl": "git@github.com:kirodotdev/powers.git", - "pathInRepo": "aws-infrastructure-as-code", - "repositoryBranch": "main", - "installed": false, - "keywords": [], - "source": { - "type": "registry" - } - }, - "power-builder": { - "name": "power-builder", - "description": "Complete guide for building and testing new Kiro Powers with templates, best practices, and validation", - "displayName": "Build a Power", - "author": "Kiro Team", - "iconUrl": "https://prod.download.desktop.kiro.dev/powers/icons/power.png", - "repositoryUrl": "https://github.com/kirodotdev/powers/tree/main/power-builder", - "license": "", - "repositoryCloneUrl": "git@github.com:kirodotdev/powers.git", - "pathInRepo": "power-builder", - "repositoryBranch": "main", - "installed": false, - "keywords": [], - "source": { - "type": "registry" - } - }, - "saas-builder": { - "name": "saas-builder", - "description": "Build production ready multi-tenant SaaS applications with serverless architecture, integrated billing, and enterprise grade security", - "displayName": "SaaS Builder", - "author": "Allen Helton", - "iconUrl": "https://prod.download.desktop.kiro.dev/powers/icons/power.png", - "repositoryUrl": "https://github.com/kirodotdev/powers/tree/main/saas-builder", - "license": "", - "repositoryCloneUrl": "git@github.com:kirodotdev/powers.git", - "pathInRepo": "saas-builder", - "repositoryBranch": "main", - "installed": false, - "keywords": [], - "source": { - "type": "registry" - } - }, - "cloud-architect": { - "name": "cloud-architect", - "description": "Build AWS infrastructure with CDK in Python following AWS Well-Architected framework best practices", - "displayName": "Build infrastructure on AWS", - "author": "Christian Bonzelet", - "iconUrl": "https://prod.download.desktop.kiro.dev/powers/icons/power.png", - "repositoryUrl": "https://github.com/kirodotdev/powers/tree/main/cloud-architect", - "license": "", - "repositoryCloneUrl": "git@github.com:kirodotdev/powers.git", - "pathInRepo": "cloud-architect", - "repositoryBranch": "main", - "installed": false, - "keywords": [], - "source": { - "type": "registry" - } - }, - "aurora-dsql": { - "name": "aurora-dsql", - "description": "For PostgreSQL compatible serverless distributed SQL database with Aurora DSQL, manage schemas, execute queries, and handle migrations with DSQL-specific constraints", - "displayName": "Deploy a distributed SQL database on AWS", - "author": "Rolf Koski", - "iconUrl": "https://prod.download.desktop.kiro.dev/powers/icons/power.png", - "repositoryUrl": "https://github.com/kirodotdev/powers/tree/main/aurora-dsql", - "license": "", - "repositoryCloneUrl": "git@github.com:kirodotdev/powers.git", - "pathInRepo": "aurora-dsql", - "repositoryBranch": "main", - "installed": false, - "keywords": [], - "source": { - "type": "registry" - } - } - }, - "repoSources": {}, - "lastUpdated": "2025-12-28T20:19:10.824Z", - "kiroRecommendedRepo": { - "url": "https://prod.download.desktop.kiro.dev/powers/default_registry.json", - "lastFetch": "2025-12-28T20:19:10.823Z", - "powerCount": 18 - } -} diff --git a/packages/init-bundle/src/index.test.ts b/packages/init-bundle/src/index.test.ts deleted file mode 100644 index 70f89331..00000000 --- a/packages/init-bundle/src/index.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -import type {BundleKey, RuntimeBundleItem, RuntimeBundles} from './index' - -import {describe, expect, it} from 'vitest' -import {bundles} from './index' - -describe('init-bundle exports', () => { - describe('bundles', () => { - it('should exist and be a valid object', () => { - expect(bundles).toBeDefined() - expect(typeof bundles).toBe('object') - }) - - it('should have expected bundle keys', () => { - const expectedKeys = [ - 'app/global.cn.mdx', - '.vscode/settings.json', - '.editorconfig', - 'public/tnmsc.example.json' - ] - expectedKeys.forEach(key => expect(bundles).toHaveProperty(key)) - }) - - it('each bundle should have path and content, where path = key', () => { - for (const [key, bundle] of Object.entries(bundles)) { - expect(bundle).toHaveProperty('path') - expect(bundle).toHaveProperty('content') - - expect(bundle.path).toBe(key) // path = key - - expect(bundle.content.length).toBeGreaterThan(0) // content 应该是实际内容(非空) - } - }) - - describe('specific bundles', () => { - it('app/global.cn.mdx should have matching path', () => expect(bundles['app/global.cn.mdx'].path).toBe('app/global.cn.mdx')) - - it('.vscode/settings.json should have matching path', () => expect(bundles['.vscode/settings.json'].path).toBe('.vscode/settings.json')) - - it('public/tnmsc.example.json should be valid JSON', () => { - const {content} = bundles['public/tnmsc.example.json'] - expect(() => JSON.parse(content)).not.toThrow() - - const parsed = JSON.parse(content) - expect(parsed).toHaveProperty('workspaceDir') - expect(parsed).toHaveProperty('profile') - }) - }) - }) - - describe('type exports', () => { - it('bundleKey type should work as string literal union', () => { - const key: BundleKey = 'app/global.cn.mdx' - expect(key).toBe('app/global.cn.mdx') - }) - - it('runtimeBundleItem type should work', () => { - const item: RuntimeBundleItem = {path: 'test/path', content: 'test content'} - expect(item.path).toBe('test/path') - }) - - it('runtimeBundles type should work', () => { - const testBundles: RuntimeBundles = bundles - expect(testBundles['app/global.cn.mdx']).toBeDefined() - }) - }) -}) diff --git a/packages/init-bundle/src/index.ts b/packages/init-bundle/src/index.ts deleted file mode 100644 index 5d33f1dd..00000000 --- a/packages/init-bundle/src/index.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Init Bundle - 项目初始化包 - * - * 导出强类型的 bundles 对象 - * key = path(相对于 public 的路径) - */ - -import type {RuntimeBundleItem, RuntimeBundles} from '../structure.config' -import {bundlePaths} from '../structure.config' - -declare const INJECTED: Readonly> // 构建时注入的 content 映射 - -let _injected: Readonly> = INJECTED // eslint-disable-line prefer-const -- 缓存注入常量 - -/** - * 强类型的 Bundles 对象 - * key: 相对于 public 的路径(如 'app/global.cn.mdx') - * value: { path, content } - */ -export const bundles: RuntimeBundles = Object.fromEntries( - bundlePaths.map(path => [ - path, - {path, content: _injected[path] ?? ''} satisfies RuntimeBundleItem - ]) -) as RuntimeBundles - -/** 重新导出类型 */ -export type { - BundleKey, - RuntimeBundleItem, - RuntimeBundles -} from '../structure.config' diff --git a/packages/init-bundle/structure.config.ts b/packages/init-bundle/structure.config.ts deleted file mode 100644 index 2a58c7ae..00000000 --- a/packages/init-bundle/structure.config.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * 项目结构配置 - 单一数据源 - * - * 设计原则: - * 1. 只声明相对于 public 的路径列表 - * 2. key = path,无需重复定义 - * 3. 通过 as const 提供强类型推导 - */ - -/** 运行时 Bundle 项:包含路径和内容 */ -export interface RuntimeBundleItem { - readonly path: string // 相对于 public 的路径(= key) - readonly content: string // 文件内容 -} - -/** - * Bundle 路径列表 - * 每个路径相对于 public,同时也是 bundles 的 key - */ -export const bundlePaths = [ - 'app/global.cn.mdx', // 全局记忆模板 - - '.idea/.gitignore', // IDE 配置 - JetBrains - '.idea/codeStyles/Project.xml', - '.idea/codeStyles/codeStyleConfig.xml', - - '.vscode/settings.json', // IDE 配置 - VSCode - '.vscode/extensions.json', - - '.editorconfig', // 通用配置 - '.gitignore', - - 'public/tnmsc.example.json', // 独立文件 - 'public/exclude', - 'public/gitignore', - 'public/kiro_global_powers_registry.json', - - 'src/skills/prompt-builder/global-memory-prompt.cn.mdx', // Prompt 指南 - 'src/skills/prompt-builder/root-memory-prompt.cn.mdx', - 'src/skills/prompt-builder/child-memory-prompt.cn.mdx' -] as const - -/** 从路径列表推断 bundle key 类型 */ -export type BundleKey = (typeof bundlePaths)[number] - -/** 运行时 Bundles 类型:强类型的 key-value 映射 */ -export type RuntimeBundles = { - readonly [K in BundleKey]: RuntimeBundleItem -} - -export const PUBLIC_BASE = './public' // 导出配置的 AINDEX 基础路径(用于构建工具) diff --git a/packages/init-bundle/tsconfig.test.json b/packages/init-bundle/tsconfig.test.json deleted file mode 100644 index 65c3c9ad..00000000 --- a/packages/init-bundle/tsconfig.test.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/tsconfig", - "extends": "./tsconfig.json", - "compilerOptions": { - "lib": [ - "ESNext", - "DOM" - ], - "types": [ - "vitest/globals", - "node" - ] - }, - "include": [ - "src/**/*.spec.ts", - "src/**/*.test.ts", - "vitest.config.ts", - "vite.config.ts", - "env.d.ts" - ], - "exclude": [ - "../node_modules", - "dist" - ] -} diff --git a/packages/init-bundle/tsdown.config.ts b/packages/init-bundle/tsdown.config.ts deleted file mode 100644 index 96e0424d..00000000 --- a/packages/init-bundle/tsdown.config.ts +++ /dev/null @@ -1,40 +0,0 @@ -import {readFileSync} from 'node:fs' -import {resolve} from 'node:path' -import {defineConfig} from 'tsdown' -import {bundlePaths, PUBLIC_BASE} from './structure.config' - -/** - * 生成 INJECTED 对象的内容 - * key: path(相对于 public) - * value: 文件内容 - */ -function generateInjectedContent(): Record { - const content: Record = {} - - for (const path of bundlePaths) { - const absolutePath = resolve(__dirname, PUBLIC_BASE, path) - content[path] = readFileSync(absolutePath, 'utf8') - } - - return content -} - -/** - * tsdown 配置 - * 将所有 bundle 内容合并为单一 INJECTED 对象注入 - */ -export default defineConfig([ - { - entry: ['./src/index.ts', '!**/*.{spec,test}.*'], - platform: 'node', - sourcemap: false, - unbundle: false, - inlineOnly: false, - format: ['esm'], - minify: false, - dts: {sourcemap: false}, - define: { - INJECTED: JSON.stringify(generateInjectedContent()) - } - } -]) diff --git a/packages/init-bundle/vite.config.ts b/packages/init-bundle/vite.config.ts deleted file mode 100644 index 711df1db..00000000 --- a/packages/init-bundle/vite.config.ts +++ /dev/null @@ -1,38 +0,0 @@ -import {readFileSync} from 'node:fs' -import {resolve} from 'node:path' -import {fileURLToPath, URL} from 'node:url' -import {defineConfig} from 'vite' -import {bundlePaths, PUBLIC_BASE} from './structure.config' - -const __dirname = fileURLToPath(new URL('.', import.meta.url)) - -/** - * 生成 INJECTED 对象的内容 - * key: path(相对于 public) - * value: 文件内容 - */ -function generateInjectedContent(): Record { - const content: Record = {} - - for (const path of bundlePaths) { - const absolutePath = resolve(__dirname, PUBLIC_BASE, path) - content[path] = readFileSync(absolutePath, 'utf8') - } - - return content -} - -/** - * Vite 配置 - * 与 tsdown 共用相同的注入逻辑 - */ -export default defineConfig({ - resolve: { - alias: { - '@': fileURLToPath(new URL('./src', import.meta.url)) - } - }, - define: { - INJECTED: JSON.stringify(generateInjectedContent()) - } -}) diff --git a/packages/init-bundle/vitest.config.ts b/packages/init-bundle/vitest.config.ts deleted file mode 100644 index bd344383..00000000 --- a/packages/init-bundle/vitest.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -import {fileURLToPath} from 'node:url' - -import {configDefaults, defineConfig, mergeConfig} from 'vitest/config' - -import viteConfig from './vite.config' - -export default mergeConfig( - viteConfig, - defineConfig({ - test: { - environment: 'node', - exclude: [...configDefaults.exclude, 'e2e/*'], - root: fileURLToPath(new URL('./', import.meta.url)), - typecheck: { - enabled: true, - tsconfig: './tsconfig.test.json' - }, - testTimeout: 30000, // Property-based tests run more iterations - onConsoleLog: () => false, // Minimal output: suppress console logs, show summary only - coverage: { - provider: 'v8', - reporter: ['text', 'json', 'html'], - exclude: [ - 'node_modules/', - 'dist/', - '**/*.test.ts', - '**/*.property.test.ts' - ] - } - } - }) -) diff --git a/packages/logger/src/index.ts b/packages/logger/src/index.ts deleted file mode 100644 index 0de71972..00000000 --- a/packages/logger/src/index.ts +++ /dev/null @@ -1,216 +0,0 @@ -import process from 'node:process' - -const colors = { - reset: '\x1B[0m', - red: '\x1B[31m', - yellow: '\x1B[33m', - cyan: '\x1B[36m', - magenta: '\x1B[35m', - gray: '\x1B[90m', - blue: '\x1B[34m', - green: '\x1B[32m', - white: '\x1B[37m', - dim: '\x1B[2m', - bgRed: '\x1B[41m' -} as const - -const colorize = { - red: (text: string) => `${colors.red}${text}${colors.reset}`, - yellow: (text: string) => `${colors.yellow}${text}${colors.reset}`, - cyan: (text: string) => `${colors.cyan}${text}${colors.reset}`, - magenta: (text: string) => `${colors.magenta}${text}${colors.reset}`, - gray: (text: string) => `${colors.gray}${text}${colors.reset}`, - blue: (text: string) => `${colors.blue}${text}${colors.reset}`, - green: (text: string) => `${colors.green}${text}${colors.reset}`, - white: (text: string) => `${colors.white}${text}${colors.reset}`, - dim: (text: string) => `${colors.dim}${text}${colors.reset}`, - bgRed: (text: string) => `${colors.bgRed}${text}${colors.reset}` -} - -export type LogLevel = 'error' | 'warn' | 'info' | 'debug' | 'trace' | 'fatal' | 'silent' - -export interface ILogger { - error: (message: string | object, ...meta: unknown[]) => void - warn: (message: string | object, ...meta: unknown[]) => void - info: (message: string | object, ...meta: unknown[]) => void - debug: (message: string | object, ...meta: unknown[]) => void - trace: (message: string | object, ...meta: unknown[]) => void - fatal: (message: string | object, ...meta: unknown[]) => void -} - -interface LogRecord { - $: [datetime: string, level: LogLevel, namespace: string] - _: M | {[message in M]: object} | object -} - -interface LeveledLogMethod { - (message: string, ...meta: unknown[]): LogRecord - (message: unknown): LogRecord - (infoObject: object): LogRecord -} - -let globalLogLevel: LogLevel | undefined - -const LEVEL_COLORS: Record string> = { - error: colorize.red, - warn: colorize.yellow, - info: colorize.cyan, - debug: colorize.magenta, - trace: colorize.gray, - fatal: colorize.bgRed -} - -const LEVEL_PRIORITY: Record = { - silent: 0, - fatal: 1, - error: 2, - warn: 3, - info: 4, - debug: 5, - trace: 6 -} - -function colorizeValue(value: unknown): string { - if (value === null) return colorize.dim('null') - if (typeof value === 'undefined') return colorize.dim('undefined') - if (typeof value === 'boolean') return colorize.yellow(String(value)) - if (typeof value === 'number') return colorize.blue(String(value)) - if (typeof value === 'string') return colorize.green(`"${value}"`) - if (Array.isArray(value)) { - if (value.length === 0) return '[]' - return `[${value.map(v => colorizeValue(v)).join(',')}]` - } - if (value instanceof Error) { - const errorObj: Record = { - name: value.name, - message: value.message, - stack: value.stack - } - for (const key of Object.getOwnPropertyNames(value)) { - if (key !== 'name' && key !== 'message' && key !== 'stack') errorObj[key] = (value as unknown as Record)[key] - } - return toJson(errorObj) - } - if (typeof value === 'object') return toJson(value as Record) - return String(value) -} - -function toJson(obj: Record): string { - const entries = Object.entries(obj) - if (entries.length === 0) return '{}' - const parts = entries.map(([k, v]) => { - const key = colorize.magenta(`"${k}"`) - return `${key}:${colorizeValue(v)}` - }) - return `{${parts.join(',')}}` -} - -function getTimestamp(): string { - const now = new Date() - const hours = String(now.getHours()).padStart(2, '0') - const minutes = String(now.getMinutes()).padStart(2, '0') - const seconds = String(now.getSeconds()).padStart(2, '0') - const ms = String(now.getMilliseconds()).padStart(3, '0') - return `${hours}:${minutes}:${seconds}.${ms}` -} - -function formatLog( - level: LogLevel, - namespace: string, - message: unknown, - meta?: Record -): LogRecord { - const timestamp = getTimestamp() - const colorFn = LEVEL_COLORS[level] ?? colorize.white - - const messageStr = String(message) - const hasMeta = meta != null && Object.keys(meta).length > 0 - const isEmptyMessage = messageStr === '' - - const record: LogRecord = { - $: [timestamp, level, namespace], - _: hasMeta - ? isEmptyMessage ? meta : {[messageStr]: meta} - : message as string - } - - const base = { - $: [timestamp, colorFn(level.toUpperCase()), namespace] - } - const _ = hasMeta - ? isEmptyMessage ? meta : {[messageStr]: meta} - : message - const output = toJson({...base, _} as unknown as Record) - - if (level === 'error' || level === 'fatal') console.error(output) - else if (level === 'warn') console.warn(output) - // eslint-disable-next-line no-console - else if (level === 'debug' || level === 'trace') console.debug(output) - // eslint-disable-next-line no-console - else console.log(output) - - return record -} - -function createLeveledMethod( - level: LogLevel, - namespace: string, - currentLevel: LogLevel -): LeveledLogMethod { - const levelPriority = LEVEL_PRIORITY[level] - const currentPriority = LEVEL_PRIORITY[currentLevel] - - return (messageOrObject: unknown, ...meta: unknown[]): LogRecord => { - if (levelPriority > currentPriority) { - return { - $: [getTimestamp(), level, namespace], - _: messageOrObject as string - } - } - - if (typeof messageOrObject === 'string') { - const metaObj = meta.length === 1 && typeof meta[0] === 'object' && meta[0] !== null - ? meta[0] as Record - : meta.length > 0 - ? {args: meta} - : void 0 - return formatLog(level, namespace, messageOrObject, metaObj) - } - - if (typeof messageOrObject === 'object' && messageOrObject !== null) return formatLog(level, namespace, '', messageOrObject as Record) - - return formatLog(level, namespace, messageOrObject) - } -} - -/** - * Set the global log level for all loggers. - * This should be called early in the application lifecycle, - * before plugins are initialized. - * - * @param level - The log level to set globally - */ -export function setGlobalLogLevel(level: LogLevel): void { - globalLogLevel = level -} - -/** - * Get the current global log level. - * @returns The global log level, or undefined if not set - */ -export function getGlobalLogLevel(): LogLevel | undefined { - return globalLogLevel -} - -export function createLogger(namespace: string, logLevel?: LogLevel): ILogger { - const level = logLevel ?? globalLogLevel ?? (process.env['LOG_LEVEL'] as LogLevel) ?? 'info' - - return { - error: createLeveledMethod('error', namespace, level), - warn: createLeveledMethod('warn', namespace, level), - info: createLeveledMethod('info', namespace, level), - debug: createLeveledMethod('debug', namespace, level), - trace: createLeveledMethod('trace', namespace, level), - fatal: createLeveledMethod('fatal', namespace, level) - } -} diff --git a/packages/logger/tsconfig.eslint.json b/packages/logger/tsconfig.eslint.json deleted file mode 100644 index 585b38ee..00000000 --- a/packages/logger/tsconfig.eslint.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/tsconfig", - "extends": "./tsconfig.json", - "compilerOptions": { - "noEmit": true, - "skipLibCheck": true - }, - "include": [ - "src/**/*.ts", - "src/**/*.test.ts", - "src/**/*.spec.ts", - "env.d.ts", - "eslint.config.ts", - "tsdown.config.ts", - "vite.config.ts", - "vitest.config.ts" - ], - "exclude": [ - "../node_modules", - "dist", - "coverage" - ] -} diff --git a/packages/logger/tsconfig.test.json b/packages/logger/tsconfig.test.json deleted file mode 100644 index 65c3c9ad..00000000 --- a/packages/logger/tsconfig.test.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/tsconfig", - "extends": "./tsconfig.json", - "compilerOptions": { - "lib": [ - "ESNext", - "DOM" - ], - "types": [ - "vitest/globals", - "node" - ] - }, - "include": [ - "src/**/*.spec.ts", - "src/**/*.test.ts", - "vitest.config.ts", - "vite.config.ts", - "env.d.ts" - ], - "exclude": [ - "../node_modules", - "dist" - ] -} diff --git a/packages/logger/vitest.config.ts b/packages/logger/vitest.config.ts deleted file mode 100644 index a06eb3a7..00000000 --- a/packages/logger/vitest.config.ts +++ /dev/null @@ -1,33 +0,0 @@ -import {fileURLToPath} from 'node:url' - -import {configDefaults, defineConfig, mergeConfig} from 'vitest/config' - -import viteConfig from './vite.config' - -export default mergeConfig( - viteConfig, - defineConfig({ - test: { - environment: 'node', - exclude: [...configDefaults.exclude, 'e2e/*'], - root: fileURLToPath(new URL('./', import.meta.url)), - typecheck: { - enabled: true, - tsconfig: './tsconfig.test.json' - }, - testTimeout: 30000, - onConsoleLog: () => false, - passWithNoTests: true, - coverage: { - provider: 'v8', - reporter: ['text', 'json', 'html'], - exclude: [ - 'node_modules/', - 'dist/', - '**/*.test.ts', - '**/*.property.test.ts' - ] - } - } - }) -) diff --git a/packages/md-compiler/package.json b/packages/md-compiler/package.json deleted file mode 100644 index f424ba92..00000000 --- a/packages/md-compiler/package.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "name": "@truenine/md-compiler", - "type": "module", - "version": "1.0.0", - "description": "", - "exports": { - "./package.json": "./package.json", - ".": { - "types": "./dist/index.d.mts", - "import": "./dist/index.mjs" - }, - "./globals": { - "types": "./dist/globals/index.d.mts", - "import": "./dist/globals/index.mjs" - }, - "./errors": { - "types": "./dist/errors/index.d.mts", - "import": "./dist/errors/index.mjs" - }, - "./markdown": { - "types": "./dist/markdown/index.d.mts", - "import": "./dist/markdown/index.mjs" - } - }, - "module": "dist/index.mjs", - "types": "dist/index.d.mts", - "scripts": { - "build": "run-s check bundle", - "bundle": "tsdown", - "lint": "eslint --cache .", - "typecheck": "tsc --noEmit -p tsconfig.lib.json", - "check": "run-p typecheck lint", - "lintfix": "eslint --fix --cache .", - "test": "vitest run", - "prepublishOnly": "run-s build" - }, - "dependencies": { - "mdast-util-mdx": "catalog:", - "remark-frontmatter": "catalog:", - "remark-gfm": "catalog:", - "remark-mdx": "catalog:", - "remark-parse": "catalog:", - "remark-stringify": "catalog:", - "unified": "catalog:", - "yaml": "catalog:" - }, - "devDependencies": { - "@types/estree": "catalog:", - "@types/estree-jsx": "catalog:", - "@types/mdast": "catalog:" - } -} diff --git a/packages/md-compiler/tsconfig.eslint.json b/packages/md-compiler/tsconfig.eslint.json deleted file mode 100644 index 585b38ee..00000000 --- a/packages/md-compiler/tsconfig.eslint.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/tsconfig", - "extends": "./tsconfig.json", - "compilerOptions": { - "noEmit": true, - "skipLibCheck": true - }, - "include": [ - "src/**/*.ts", - "src/**/*.test.ts", - "src/**/*.spec.ts", - "env.d.ts", - "eslint.config.ts", - "tsdown.config.ts", - "vite.config.ts", - "vitest.config.ts" - ], - "exclude": [ - "../node_modules", - "dist", - "coverage" - ] -} diff --git a/packages/md-compiler/tsconfig.test.json b/packages/md-compiler/tsconfig.test.json deleted file mode 100644 index 65c3c9ad..00000000 --- a/packages/md-compiler/tsconfig.test.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$schema": "https://json.schemastore.org/tsconfig", - "extends": "./tsconfig.json", - "compilerOptions": { - "lib": [ - "ESNext", - "DOM" - ], - "types": [ - "vitest/globals", - "node" - ] - }, - "include": [ - "src/**/*.spec.ts", - "src/**/*.test.ts", - "vitest.config.ts", - "vite.config.ts", - "env.d.ts" - ], - "exclude": [ - "../node_modules", - "dist" - ] -} diff --git a/packages/md-compiler/vite.config.ts b/packages/md-compiler/vite.config.ts deleted file mode 100644 index 2dcc5646..00000000 --- a/packages/md-compiler/vite.config.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {fileURLToPath, URL} from 'node:url' -import {defineConfig} from 'vite' - -export default defineConfig({ - resolve: { - alias: { - '@': fileURLToPath(new URL('./src', import.meta.url)) - } - } -}) diff --git a/packages/md-compiler/vitest.config.ts b/packages/md-compiler/vitest.config.ts deleted file mode 100644 index bd344383..00000000 --- a/packages/md-compiler/vitest.config.ts +++ /dev/null @@ -1,32 +0,0 @@ -import {fileURLToPath} from 'node:url' - -import {configDefaults, defineConfig, mergeConfig} from 'vitest/config' - -import viteConfig from './vite.config' - -export default mergeConfig( - viteConfig, - defineConfig({ - test: { - environment: 'node', - exclude: [...configDefaults.exclude, 'e2e/*'], - root: fileURLToPath(new URL('./', import.meta.url)), - typecheck: { - enabled: true, - tsconfig: './tsconfig.test.json' - }, - testTimeout: 30000, // Property-based tests run more iterations - onConsoleLog: () => false, // Minimal output: suppress console logs, show summary only - coverage: { - provider: 'v8', - reporter: ['text', 'json', 'html'], - exclude: [ - 'node_modules/', - 'dist/', - '**/*.test.ts', - '**/*.property.test.ts' - ] - } - } - }) -) diff --git a/packages/plugin-agentskills-compact/package.json b/packages/plugin-agentskills-compact/package.json index 099fbef9..e6402ca2 100644 --- a/packages/plugin-agentskills-compact/package.json +++ b/packages/plugin-agentskills-compact/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-agentskills-compact", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "Generic Agent Skills (compact) output plugin for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-agentsmd/package.json b/packages/plugin-agentsmd/package.json index 4a4ce16f..9d12f6d0 100644 --- a/packages/plugin-agentsmd/package.json +++ b/packages/plugin-agentsmd/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-agentsmd", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "AGENTS.md output plugin for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-antigravity/package.json b/packages/plugin-antigravity/package.json index 1de8732a..ef2a16e5 100644 --- a/packages/plugin-antigravity/package.json +++ b/packages/plugin-antigravity/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-antigravity", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "Antigravity output plugin for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-claude-code-cli/package.json b/packages/plugin-claude-code-cli/package.json index 7531bd93..2f46af5b 100644 --- a/packages/plugin-claude-code-cli/package.json +++ b/packages/plugin-claude-code-cli/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-claude-code-cli", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "Claude Code CLI output plugin", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-cursor/package.json b/packages/plugin-cursor/package.json index bb181993..e9e23a70 100644 --- a/packages/plugin-cursor/package.json +++ b/packages/plugin-cursor/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-cursor", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "Cursor IDE output plugin", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-droid-cli/package.json b/packages/plugin-droid-cli/package.json index 3c5c7331..85964814 100644 --- a/packages/plugin-droid-cli/package.json +++ b/packages/plugin-droid-cli/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-droid-cli", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "Droid CLI output plugin for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-editorconfig/package.json b/packages/plugin-editorconfig/package.json index 2bcf4d30..88e0e0d1 100644 --- a/packages/plugin-editorconfig/package.json +++ b/packages/plugin-editorconfig/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-editorconfig", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "EditorConfig output plugin for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-gemini-cli/package.json b/packages/plugin-gemini-cli/package.json index d59b8258..04305b0e 100644 --- a/packages/plugin-gemini-cli/package.json +++ b/packages/plugin-gemini-cli/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-gemini-cli", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "Gemini CLI output plugin for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-git-exclude/package.json b/packages/plugin-git-exclude/package.json index f2406edf..4084a400 100644 --- a/packages/plugin-git-exclude/package.json +++ b/packages/plugin-git-exclude/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-git-exclude", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "Git exclude output plugin for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-input-agentskills/package.json b/packages/plugin-input-agentskills/package.json index cb925c9e..45ee012a 100644 --- a/packages/plugin-input-agentskills/package.json +++ b/packages/plugin-input-agentskills/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-input-agentskills", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "plugin-input-agentskills for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-input-editorconfig/package.json b/packages/plugin-input-editorconfig/package.json index 7c792215..f71fd212 100644 --- a/packages/plugin-input-editorconfig/package.json +++ b/packages/plugin-input-editorconfig/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-input-editorconfig", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "plugin-input-editorconfig for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-input-fast-command/package.json b/packages/plugin-input-fast-command/package.json index 31febbd9..c41c5335 100644 --- a/packages/plugin-input-fast-command/package.json +++ b/packages/plugin-input-fast-command/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-input-fast-command", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "plugin-input-fast-command for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-input-git-exclude/package.json b/packages/plugin-input-git-exclude/package.json index f505646c..044c4a67 100644 --- a/packages/plugin-input-git-exclude/package.json +++ b/packages/plugin-input-git-exclude/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-input-git-exclude", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "plugin-input-git-exclude for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-input-gitignore/package.json b/packages/plugin-input-gitignore/package.json index 858cacbe..2ca8b935 100644 --- a/packages/plugin-input-gitignore/package.json +++ b/packages/plugin-input-gitignore/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-input-gitignore", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "plugin-input-gitignore for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-input-gitignore/src/GitIgnoreInputPlugin.ts b/packages/plugin-input-gitignore/src/GitIgnoreInputPlugin.ts index addf3fa0..ffb80a00 100644 --- a/packages/plugin-input-gitignore/src/GitIgnoreInputPlugin.ts +++ b/packages/plugin-input-gitignore/src/GitIgnoreInputPlugin.ts @@ -6,8 +6,8 @@ import {BaseFileInputPlugin} from '@truenine/plugin-input-shared' type BundleMap = Readonly> const bundleMap = bundles as unknown as BundleMap -function getGitignoreTemplate(): string { // 从 bundles 获取 gitignore 模板内容(public/exclude) - return bundleMap['public/gitignore']?.content ?? '' +function getGitignoreTemplate(): string | undefined { // 从 bundles 获取 gitignore 模板内容(public/exclude) + return bundleMap['public/gitignore']?.content } /** @@ -16,7 +16,8 @@ function getGitignoreTemplate(): string { // 从 bundles 获取 gitignore 模板 */ export class GitIgnoreInputPlugin extends BaseFileInputPlugin { constructor() { - super('GitIgnoreInputPlugin', {fallbackContent: getGitignoreTemplate()}) + const template = getGitignoreTemplate() + super('GitIgnoreInputPlugin', template != null ? {fallbackContent: template} : {}) } protected getFilePath(shadowProjectDir: string): string { diff --git a/packages/plugin-input-global-memory/package.json b/packages/plugin-input-global-memory/package.json index 01a5726a..b5513daa 100644 --- a/packages/plugin-input-global-memory/package.json +++ b/packages/plugin-input-global-memory/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-input-global-memory", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "plugin-input-global-memory for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-input-jetbrains-config/package.json b/packages/plugin-input-jetbrains-config/package.json index aa883d66..24818975 100644 --- a/packages/plugin-input-jetbrains-config/package.json +++ b/packages/plugin-input-jetbrains-config/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-input-jetbrains-config", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "plugin-input-jetbrains-config for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-input-md-cleanup-effect/package.json b/packages/plugin-input-md-cleanup-effect/package.json index 58e3a2e1..8f1d8835 100644 --- a/packages/plugin-input-md-cleanup-effect/package.json +++ b/packages/plugin-input-md-cleanup-effect/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-input-md-cleanup-effect", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "plugin-input-md-cleanup-effect for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-input-orphan-cleanup-effect/package.json b/packages/plugin-input-orphan-cleanup-effect/package.json index 08952c35..441a702f 100644 --- a/packages/plugin-input-orphan-cleanup-effect/package.json +++ b/packages/plugin-input-orphan-cleanup-effect/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-input-orphan-cleanup-effect", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "plugin-input-orphan-cleanup-effect for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-input-project-prompt/package.json b/packages/plugin-input-project-prompt/package.json index a0b2b159..e82c6cdf 100644 --- a/packages/plugin-input-project-prompt/package.json +++ b/packages/plugin-input-project-prompt/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-input-project-prompt", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "plugin-input-project-prompt for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-input-readme/package.json b/packages/plugin-input-readme/package.json index 2204323c..1380dbf7 100644 --- a/packages/plugin-input-readme/package.json +++ b/packages/plugin-input-readme/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-input-readme", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "plugin-input-readme for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-input-rule/package.json b/packages/plugin-input-rule/package.json index 31229ce4..c59c1601 100644 --- a/packages/plugin-input-rule/package.json +++ b/packages/plugin-input-rule/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-input-rule", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "plugin-input-rule for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-input-shadow-project/package.json b/packages/plugin-input-shadow-project/package.json index 57c51d40..dd3dcd1c 100644 --- a/packages/plugin-input-shadow-project/package.json +++ b/packages/plugin-input-shadow-project/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-input-shadow-project", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "plugin-input-shadow-project for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-input-shared-ignore/eslint.config.ts b/packages/plugin-input-shared-ignore/eslint.config.ts new file mode 100644 index 00000000..12cc00fc --- /dev/null +++ b/packages/plugin-input-shared-ignore/eslint.config.ts @@ -0,0 +1,17 @@ +import {dirname, resolve} from 'node:path' +import {fileURLToPath} from 'node:url' +import eslint10 from '@truenine/eslint10-config' + +const configDir = dirname(fileURLToPath(import.meta.url)) + +const config = eslint10({ + type: 'lib', + typescript: { + strictTypescriptEslint: true, + tsconfigPath: resolve(configDir, 'tsconfig.json'), + parserOptions: {allowDefaultProject: true} + }, + ignores: ['.turbo/**', '*.md', '**/*.md'] +}) + +export default config as unknown diff --git a/packages/logger/package.json b/packages/plugin-input-shared-ignore/package.json similarity index 59% rename from packages/logger/package.json rename to packages/plugin-input-shared-ignore/package.json index 97bac897..b8307b08 100644 --- a/packages/logger/package.json +++ b/packages/plugin-input-shared-ignore/package.json @@ -1,8 +1,9 @@ { - "name": "@truenine/logger", + "name": "@truenine/plugin-input-shared-ignore", "type": "module", - "version": "1.0.0", - "description": "Lightweight colorized logger for CLI tools", + "version": "2026.10223.10555", + "private": true, + "description": "AI agent ignore file input plugin for memory-sync", "exports": { "./package.json": "./package.json", ".": { @@ -18,9 +19,12 @@ "typecheck": "tsc --noEmit -p tsconfig.lib.json", "check": "run-p typecheck lint", "lintfix": "eslint --fix --cache .", - "test": "vitest run", + "test": "vitest run --passWithNoTests", "prepublishOnly": "run-s build" }, "dependencies": {}, - "devDependencies": {} + "devDependencies": { + "@truenine/plugin-input-shared": "workspace:*", + "@truenine/plugin-shared": "workspace:*" + } } diff --git a/packages/plugin-input-shared-ignore/src/AIAgentIgnoreInputPlugin.ts b/packages/plugin-input-shared-ignore/src/AIAgentIgnoreInputPlugin.ts new file mode 100644 index 00000000..2cfb14b4 --- /dev/null +++ b/packages/plugin-input-shared-ignore/src/AIAgentIgnoreInputPlugin.ts @@ -0,0 +1,47 @@ +import type {AIAgentIgnoreConfigFile, CollectedInputContext, InputPluginContext} from '@truenine/plugin-shared' +import {AbstractInputPlugin} from '@truenine/plugin-input-shared' +import {SHADOW_SOURCE_FILE_NAMES} from '@truenine/plugin-shared' + +const IGNORE_FILE_NAMES: readonly string[] = [ + SHADOW_SOURCE_FILE_NAMES.QODER_IGNORE, + SHADOW_SOURCE_FILE_NAMES.CURSOR_IGNORE, + SHADOW_SOURCE_FILE_NAMES.WARP_INDEX_IGNORE, + SHADOW_SOURCE_FILE_NAMES.AI_IGNORE, + SHADOW_SOURCE_FILE_NAMES.CODEIUM_IGNORE, + '.kiroignore', + '.traeignore' +] as const + +/** + * Input plugin that reads AI agent ignore files from shadow source project root. + * Reads files like .kiroignore, .aiignore, .cursorignore, etc. + * and populates aiAgentIgnoreConfigFiles in CollectedInputContext. + */ +export class AIAgentIgnoreInputPlugin extends AbstractInputPlugin { + constructor() { + super('AIAgentIgnoreInputPlugin') + } + + collect(ctx: InputPluginContext): Partial { + const {shadowProjectDir} = this.resolveBasePaths(ctx.userConfigOptions) + const results: AIAgentIgnoreConfigFile[] = [] + + for (const fileName of IGNORE_FILE_NAMES) { + const filePath = ctx.path.join(shadowProjectDir, fileName) + if (!ctx.fs.existsSync(filePath)) { + this.log.debug({action: 'collect', message: 'Ignore file not found', path: filePath}) + continue + } + const content = ctx.fs.readFileSync(filePath, 'utf8') + if (content.length === 0) { + this.log.debug({action: 'collect', message: 'Ignore file is empty', path: filePath}) + continue + } + results.push({fileName, content}) + this.log.debug({action: 'collect', message: 'Loaded ignore file', path: filePath, fileName}) + } + + if (results.length === 0) return {} + return {aiAgentIgnoreConfigFiles: results} + } +} diff --git a/packages/plugin-input-shared-ignore/src/index.ts b/packages/plugin-input-shared-ignore/src/index.ts new file mode 100644 index 00000000..64bf7709 --- /dev/null +++ b/packages/plugin-input-shared-ignore/src/index.ts @@ -0,0 +1,3 @@ +export { + AIAgentIgnoreInputPlugin +} from './AIAgentIgnoreInputPlugin' diff --git a/packages/plugin-input-shared-ignore/tsconfig.json b/packages/plugin-input-shared-ignore/tsconfig.json new file mode 100644 index 00000000..6ccdd2ab --- /dev/null +++ b/packages/plugin-input-shared-ignore/tsconfig.json @@ -0,0 +1,54 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "compilerOptions": { + "noUncheckedSideEffectImports": true, + "incremental": true, + "composite": false, + "target": "ESNext", + "lib": ["ESNext"], + "moduleDetection": "force", + "useDefineForClassFields": true, + "baseUrl": ".", + "module": "ESNext", + "moduleResolution": "Bundler", + "paths": { "@/*": ["./src/*"] }, + "resolveJsonModule": true, + "allowImportingTsExtensions": true, + "strict": true, + "strictBindCallApply": true, + "strictFunctionTypes": true, + "strictNullChecks": true, + "strictPropertyInitialization": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "alwaysStrict": true, + "exactOptionalPropertyTypes": true, + "noFallthroughCasesInSwitch": true, + "noImplicitAny": true, + "noImplicitOverride": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noPropertyAccessFromIndexSignature": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "useUnknownInCatchVariables": true, + "declaration": true, + "declarationMap": true, + "importHelpers": true, + "newLine": "lf", + "noEmit": true, + "noEmitHelpers": false, + "removeComments": false, + "sourceMap": true, + "stripInternal": true, + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "skipLibCheck": true + }, + "include": ["src/**/*", "eslint.config.ts", "tsdown.config.ts", "vitest.config.ts"], + "exclude": ["../node_modules", "dist"] +} diff --git a/packages/plugin-input-shared-ignore/tsconfig.lib.json b/packages/plugin-input-shared-ignore/tsconfig.lib.json new file mode 100644 index 00000000..2a3b86e2 --- /dev/null +++ b/packages/plugin-input-shared-ignore/tsconfig.lib.json @@ -0,0 +1,7 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "extends": "./tsconfig.json", + "compilerOptions": { "composite": true, "rootDir": "./src", "noEmit": false, "outDir": "../dist", "skipLibCheck": true }, + "include": ["src/**/*"], + "exclude": ["../node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"] +} diff --git a/packages/plugin-input-shared-ignore/tsdown.config.ts b/packages/plugin-input-shared-ignore/tsdown.config.ts new file mode 100644 index 00000000..63062043 --- /dev/null +++ b/packages/plugin-input-shared-ignore/tsdown.config.ts @@ -0,0 +1,16 @@ +import {resolve} from 'node:path' +import {defineConfig} from 'tsdown' + +export default defineConfig([ + { + entry: ['./src/index.ts', '!**/*.{spec,test}.*'], + platform: 'node', + sourcemap: false, + unbundle: false, + inlineOnly: false, + alias: {'@': resolve('src')}, + format: ['esm'], + minify: false, + dts: {sourcemap: false} + } +]) diff --git a/packages/plugin-input-shared/package.json b/packages/plugin-input-shared/package.json index e92f1655..652e78ef 100644 --- a/packages/plugin-input-shared/package.json +++ b/packages/plugin-input-shared/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-input-shared", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "Shared abstract base classes and scope management for memory-sync input plugins", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-input-skill-sync-effect/package.json b/packages/plugin-input-skill-sync-effect/package.json index 608b8302..1402f0c3 100644 --- a/packages/plugin-input-skill-sync-effect/package.json +++ b/packages/plugin-input-skill-sync-effect/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-input-skill-sync-effect", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "plugin-input-skill-sync-effect for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-input-subagent/package.json b/packages/plugin-input-subagent/package.json index caf1c176..a4d73b38 100644 --- a/packages/plugin-input-subagent/package.json +++ b/packages/plugin-input-subagent/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-input-subagent", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "plugin-input-subagent for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-input-vscode-config/package.json b/packages/plugin-input-vscode-config/package.json index 36b541e8..882f7227 100644 --- a/packages/plugin-input-vscode-config/package.json +++ b/packages/plugin-input-vscode-config/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-input-vscode-config", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "plugin-input-vscode-config for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-input-workspace/package.json b/packages/plugin-input-workspace/package.json index 0522c24b..d44e7a71 100644 --- a/packages/plugin-input-workspace/package.json +++ b/packages/plugin-input-workspace/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-input-workspace", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "plugin-input-workspace for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-jetbrains-ai-codex/package.json b/packages/plugin-jetbrains-ai-codex/package.json index 6efffac4..560d0e64 100644 --- a/packages/plugin-jetbrains-ai-codex/package.json +++ b/packages/plugin-jetbrains-ai-codex/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-jetbrains-ai-codex", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "JetBrains AI Assistant Codex output plugin for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-jetbrains-codestyle/package.json b/packages/plugin-jetbrains-codestyle/package.json index 95be3dd0..9a987666 100644 --- a/packages/plugin-jetbrains-codestyle/package.json +++ b/packages/plugin-jetbrains-codestyle/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-jetbrains-codestyle", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "JetBrains IDE code style config output plugin for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-kiro-ide/package.json b/packages/plugin-kiro-ide/package.json index 71c13b33..7ac1ead6 100644 --- a/packages/plugin-kiro-ide/package.json +++ b/packages/plugin-kiro-ide/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-kiro-ide", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "Kiro IDE output plugin", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-openai-codex-cli/package.json b/packages/plugin-openai-codex-cli/package.json index 4d851536..91de40b7 100644 --- a/packages/plugin-openai-codex-cli/package.json +++ b/packages/plugin-openai-codex-cli/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-openai-codex-cli", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "OpenAI Codex CLI output plugin for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-opencode-cli/package.json b/packages/plugin-opencode-cli/package.json index be8d531f..3f1ac192 100644 --- a/packages/plugin-opencode-cli/package.json +++ b/packages/plugin-opencode-cli/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-opencode-cli", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "Opencode CLI output plugin", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-output-shared/package.json b/packages/plugin-output-shared/package.json index ba1ca59d..5d957535 100644 --- a/packages/plugin-output-shared/package.json +++ b/packages/plugin-output-shared/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-output-shared", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "Shared abstract base classes and utilities for memory-sync output plugins", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-qoder-ide/package.json b/packages/plugin-qoder-ide/package.json index 7e1f8544..6039681e 100644 --- a/packages/plugin-qoder-ide/package.json +++ b/packages/plugin-qoder-ide/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-qoder-ide", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "Qoder IDE output plugin", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-readme/package.json b/packages/plugin-readme/package.json index 64652a73..ea260a1c 100644 --- a/packages/plugin-readme/package.json +++ b/packages/plugin-readme/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-readme", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "README.md output plugin for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-shared/package.json b/packages/plugin-shared/package.json index e307f110..23652fd6 100644 --- a/packages/plugin-shared/package.json +++ b/packages/plugin-shared/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-shared", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "Shared types, enums, errors, and base classes for memory-sync plugins", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-shared/src/constants.ts b/packages/plugin-shared/src/constants.ts index eefdb77a..d7e089cb 100644 --- a/packages/plugin-shared/src/constants.ts +++ b/packages/plugin-shared/src/constants.ts @@ -1,5 +1,5 @@ import type {UserConfigFile} from './types/ConfigTypes.schema' -import {bundles} from '@truenine/init-bundle' +import {bundles, getDefaultConfigContent} from '@truenine/init-bundle' export const PathPlaceholders = { USER_HOME: '~', @@ -7,4 +7,5 @@ export const PathPlaceholders = { } as const type DefaultUserConfig = Readonly>> // Default user config type -export const DEFAULT_USER_CONFIG = JSON.parse(bundles['public/tnmsc.example.json'].content) as DefaultUserConfig // Imported from @truenine/init-bundle package +const _bundleContent = bundles['public/tnmsc.example.json']?.content ?? getDefaultConfigContent() +export const DEFAULT_USER_CONFIG = JSON.parse(_bundleContent) as DefaultUserConfig // Imported from @truenine/init-bundle package diff --git a/packages/plugin-trae-ide/package.json b/packages/plugin-trae-ide/package.json index 97ef01be..455ad125 100644 --- a/packages/plugin-trae-ide/package.json +++ b/packages/plugin-trae-ide/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-trae-ide", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "Trae IDE output plugin", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-vscode/package.json b/packages/plugin-vscode/package.json index 63c6ed37..ba56fbd0 100644 --- a/packages/plugin-vscode/package.json +++ b/packages/plugin-vscode/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-vscode", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "VS Code IDE config output plugin for memory-sync", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-warp-ide/package.json b/packages/plugin-warp-ide/package.json index ab92b0e7..119a0a7a 100644 --- a/packages/plugin-warp-ide/package.json +++ b/packages/plugin-warp-ide/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-warp-ide", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "Warp IDE output plugin", "exports": { "./package.json": "./package.json", diff --git a/packages/plugin-windsurf/package.json b/packages/plugin-windsurf/package.json index 27a9cc63..f1636787 100644 --- a/packages/plugin-windsurf/package.json +++ b/packages/plugin-windsurf/package.json @@ -1,7 +1,8 @@ { "name": "@truenine/plugin-windsurf", "type": "module", - "version": "1.0.0", + "version": "2026.10223.10555", + "private": true, "description": "Windsurf IDE output plugin", "exports": { "./package.json": "./package.json", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f5e91c5f..6ac0a4f9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -22,11 +22,11 @@ catalogs: specifier: ^4.2.0 version: 4.2.0 '@tanstack/react-router': - specifier: ^1.162.1 - version: 1.162.1 + specifier: ^1.162.2 + version: 1.162.2 '@tanstack/router-plugin': - specifier: ^1.162.1 - version: 1.162.1 + specifier: ^1.162.2 + version: 1.162.2 '@tauri-apps/api': specifier: ^2.10.1 version: 2.10.1 @@ -179,6 +179,9 @@ importers: .: devDependencies: + '@napi-rs/cli': + specifier: ^3.5.1 + version: 3.5.1(@emnapi/runtime@1.8.1)(@types/node@25.3.0) '@truenine/eslint10-config': specifier: 'catalog:' version: 2026.10209.11105(d12526f81920c349c9fa664256242f8c) @@ -227,15 +230,30 @@ importers: fs-extra: specifier: 'catalog:' version: 11.3.3 + jiti: + specifier: 2.6.1 + version: 2.6.1 jsonc-parser: specifier: 'catalog:' version: 3.3.1 + lightningcss: + specifier: 1.31.1 + version: 1.31.1 picocolors: specifier: 'catalog:' version: 1.1.1 picomatch: specifier: 'catalog:' version: 4.0.3 + tsx: + specifier: 4.21.0 + version: 4.21.0 + vitest: + specifier: ^4.0.18 + version: 4.0.18(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + yaml: + specifier: 2.8.2 + version: 2.8.2 zod: specifier: 'catalog:' version: 4.3.6 @@ -245,13 +263,13 @@ importers: version: link:../packages/desk-paths '@truenine/init-bundle': specifier: workspace:* - version: link:../packages/init-bundle + version: link:../libraries/init-bundle '@truenine/logger': specifier: workspace:* - version: link:../packages/logger + version: link:../libraries/logger '@truenine/md-compiler': specifier: workspace:* - version: link:../packages/md-compiler + version: link:../libraries/md-compiler '@truenine/plugin-agentskills-compact': specifier: workspace:* version: link:../packages/plugin-agentskills-compact @@ -321,6 +339,9 @@ importers: '@truenine/plugin-input-shared': specifier: workspace:* version: link:../packages/plugin-input-shared + '@truenine/plugin-input-shared-ignore': + specifier: workspace:* + version: link:../packages/plugin-input-shared-ignore '@truenine/plugin-input-skill-sync-effect': specifier: workspace:* version: link:../packages/plugin-input-skill-sync-effect @@ -384,6 +405,32 @@ importers: zod-to-json-schema: specifier: 'catalog:' version: 3.25.1(zod@4.3.6) + optionalDependencies: + '@truenine/memory-sync-cli-darwin-arm64': + specifier: workspace:* + version: link:npm/darwin-arm64 + '@truenine/memory-sync-cli-darwin-x64': + specifier: workspace:* + version: link:npm/darwin-x64 + '@truenine/memory-sync-cli-linux-arm64-gnu': + specifier: workspace:* + version: link:npm/linux-arm64-gnu + '@truenine/memory-sync-cli-linux-x64-gnu': + specifier: workspace:* + version: link:npm/linux-x64-gnu + '@truenine/memory-sync-cli-win32-x64-msvc': + specifier: workspace:* + version: link:npm/win32-x64-msvc + + cli/npm/darwin-arm64: {} + + cli/npm/darwin-x64: {} + + cli/npm/linux-arm64-gnu: {} + + cli/npm/linux-x64-gnu: {} + + cli/npm/win32-x64-msvc: {} doc: dependencies: @@ -423,10 +470,10 @@ importers: version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/react-router': specifier: 'catalog:' - version: 1.162.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 1.162.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) '@tanstack/router-plugin': specifier: 'catalog:' - version: 1.162.1(@tanstack/react-router@1.162.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) + version: 1.162.2(@tanstack/react-router@1.162.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2)) '@tauri-apps/api': specifier: 'catalog:' version: 2.10.1 @@ -486,13 +533,111 @@ importers: specifier: 'catalog:' version: 1.4.0 - packages/desk-paths: {} + libraries/config: + devDependencies: + '@napi-rs/cli': + specifier: ^3.5.1 + version: 3.5.1(@emnapi/runtime@1.8.1)(@types/node@25.3.0) + npm-run-all2: + specifier: 'catalog:' + version: 8.0.4 + tsdown: + specifier: 'catalog:' + version: 0.20.3(synckit@0.11.12)(typescript@5.9.3) + typescript: + specifier: 'catalog:' + version: 5.9.3 + vitest: + specifier: 'catalog:' + version: 4.0.18(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - packages/init-bundle: {} + libraries/init-bundle: + devDependencies: + '@napi-rs/cli': + specifier: ^3.5.1 + version: 3.5.1(@emnapi/runtime@1.8.1)(@types/node@25.3.0) + npm-run-all2: + specifier: 'catalog:' + version: 8.0.4 + tsdown: + specifier: 'catalog:' + version: 0.20.3(synckit@0.11.12)(typescript@5.9.3) + typescript: + specifier: 'catalog:' + version: 5.9.3 + vitest: + specifier: 'catalog:' + version: 4.0.18(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) - packages/logger: {} + libraries/input-plugins: + dependencies: + '@emnapi/runtime': + specifier: 1.8.1 + version: 1.8.1 + lightningcss: + specifier: 1.31.1 + version: 1.31.1 + synckit: + specifier: 0.11.12 + version: 0.11.12 + tsx: + specifier: 4.21.0 + version: 4.21.0 + yaml: + specifier: 2.8.2 + version: 2.8.2 + devDependencies: + '@napi-rs/cli': + specifier: ^3.5.1 + version: 3.5.1(@emnapi/runtime@1.8.1)(@types/node@25.3.0) + npm-run-all2: + specifier: 'catalog:' + version: 8.0.4 + tsdown: + specifier: 'catalog:' + version: 0.20.3(synckit@0.11.12)(typescript@5.9.3) + typescript: + specifier: 'catalog:' + version: 5.9.3 + vitest: + specifier: 'catalog:' + version: 4.0.18(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + optionalDependencies: + '@truenine/input-plugins-darwin-arm64': + specifier: workspace:* + version: link:npm/darwin-arm64 + '@truenine/input-plugins-darwin-x64': + specifier: workspace:* + version: link:npm/darwin-x64 + '@truenine/input-plugins-linux-x64-gnu': + specifier: workspace:* + version: link:npm/linux-x64-gnu + '@truenine/input-plugins-win32-x64-gnu': + specifier: workspace:* + version: link:npm/win32-x64-gnu + '@truenine/input-plugins-win32-x64-msvc': + specifier: workspace:* + version: link:npm/win32-x64-msvc - packages/md-compiler: + libraries/logger: + devDependencies: + '@napi-rs/cli': + specifier: ^3.5.1 + version: 3.5.1(@emnapi/runtime@1.8.1)(@types/node@25.3.0) + npm-run-all2: + specifier: 'catalog:' + version: 8.0.4 + tsdown: + specifier: 'catalog:' + version: 0.20.3(synckit@0.11.12)(typescript@5.9.3) + typescript: + specifier: 'catalog:' + version: 5.9.3 + vitest: + specifier: 'catalog:' + version: 4.0.18(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + + libraries/md-compiler: dependencies: mdast-util-mdx: specifier: 'catalog:' @@ -519,6 +664,9 @@ importers: specifier: 'catalog:' version: 2.8.2 devDependencies: + '@napi-rs/cli': + specifier: ^3.5.1 + version: 3.5.1(@emnapi/runtime@1.8.1)(@types/node@25.3.0) '@types/estree': specifier: 'catalog:' version: 1.0.8 @@ -528,12 +676,26 @@ importers: '@types/mdast': specifier: 'catalog:' version: 4.0.4 + npm-run-all2: + specifier: 'catalog:' + version: 8.0.4 + tsdown: + specifier: 'catalog:' + version: 0.20.3(synckit@0.11.12)(typescript@5.9.3) + typescript: + specifier: 'catalog:' + version: 5.9.3 + vitest: + specifier: 'catalog:' + version: 4.0.18(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) + + packages/desk-paths: {} packages/plugin-agentskills-compact: devDependencies: '@truenine/md-compiler': specifier: workspace:* - version: link:../md-compiler + version: link:../../libraries/md-compiler '@truenine/plugin-output-shared': specifier: workspace:* version: link:../plugin-output-shared @@ -563,7 +725,7 @@ importers: devDependencies: '@truenine/md-compiler': specifier: workspace:* - version: link:../md-compiler + version: link:../../libraries/md-compiler '@truenine/plugin-output-shared': specifier: workspace:* version: link:../plugin-output-shared @@ -575,7 +737,7 @@ importers: devDependencies: '@truenine/md-compiler': specifier: workspace:* - version: link:../md-compiler + version: link:../../libraries/md-compiler '@truenine/plugin-output-shared': specifier: workspace:* version: link:../plugin-output-shared @@ -626,7 +788,7 @@ importers: devDependencies: '@truenine/md-compiler': specifier: workspace:* - version: link:../md-compiler + version: link:../../libraries/md-compiler '@truenine/plugin-input-shared': specifier: workspace:* version: link:../plugin-input-shared @@ -647,7 +809,7 @@ importers: devDependencies: '@truenine/md-compiler': specifier: workspace:* - version: link:../md-compiler + version: link:../../libraries/md-compiler '@truenine/plugin-input-shared': specifier: workspace:* version: link:../plugin-input-shared @@ -668,7 +830,7 @@ importers: devDependencies: '@truenine/init-bundle': specifier: workspace:* - version: link:../init-bundle + version: link:../../libraries/init-bundle '@truenine/plugin-input-shared': specifier: workspace:* version: link:../plugin-input-shared @@ -680,7 +842,7 @@ importers: devDependencies: '@truenine/md-compiler': specifier: workspace:* - version: link:../md-compiler + version: link:../../libraries/md-compiler '@truenine/plugin-input-shared': specifier: workspace:* version: link:../plugin-input-shared @@ -725,7 +887,7 @@ importers: devDependencies: '@truenine/md-compiler': specifier: workspace:* - version: link:../md-compiler + version: link:../../libraries/md-compiler '@truenine/plugin-input-shared': specifier: workspace:* version: link:../plugin-input-shared @@ -737,7 +899,7 @@ importers: devDependencies: '@truenine/md-compiler': specifier: workspace:* - version: link:../md-compiler + version: link:../../libraries/md-compiler '@truenine/plugin-input-shared': specifier: workspace:* version: link:../plugin-input-shared @@ -749,7 +911,7 @@ importers: devDependencies: '@truenine/md-compiler': specifier: workspace:* - version: link:../md-compiler + version: link:../../libraries/md-compiler '@truenine/plugin-input-shared': specifier: workspace:* version: link:../plugin-input-shared @@ -773,7 +935,7 @@ importers: devDependencies: '@truenine/md-compiler': specifier: workspace:* - version: link:../md-compiler + version: link:../../libraries/md-compiler '@truenine/plugin-shared': specifier: workspace:* version: link:../plugin-shared @@ -781,6 +943,15 @@ importers: specifier: 'catalog:' version: 3.3.3 + packages/plugin-input-shared-ignore: + devDependencies: + '@truenine/plugin-input-shared': + specifier: workspace:* + version: link:../plugin-input-shared + '@truenine/plugin-shared': + specifier: workspace:* + version: link:../plugin-shared + packages/plugin-input-skill-sync-effect: devDependencies: '@truenine/plugin-input-shared': @@ -797,7 +968,7 @@ importers: devDependencies: '@truenine/md-compiler': specifier: workspace:* - version: link:../md-compiler + version: link:../../libraries/md-compiler '@truenine/plugin-input-shared': specifier: workspace:* version: link:../plugin-input-shared @@ -830,7 +1001,7 @@ importers: version: link:../desk-paths '@truenine/md-compiler': specifier: workspace:* - version: link:../md-compiler + version: link:../../libraries/md-compiler '@truenine/plugin-output-shared': specifier: workspace:* version: link:../plugin-output-shared @@ -851,7 +1022,7 @@ importers: devDependencies: '@truenine/md-compiler': specifier: workspace:* - version: link:../md-compiler + version: link:../../libraries/md-compiler '@truenine/plugin-output-shared': specifier: workspace:* version: link:../plugin-output-shared @@ -866,7 +1037,7 @@ importers: devDependencies: '@truenine/md-compiler': specifier: workspace:* - version: link:../md-compiler + version: link:../../libraries/md-compiler '@truenine/plugin-output-shared': specifier: workspace:* version: link:../plugin-output-shared @@ -878,7 +1049,7 @@ importers: devDependencies: '@truenine/md-compiler': specifier: workspace:* - version: link:../md-compiler + version: link:../../libraries/md-compiler '@truenine/plugin-output-shared': specifier: workspace:* version: link:../plugin-output-shared @@ -900,7 +1071,7 @@ importers: version: link:../desk-paths '@truenine/md-compiler': specifier: workspace:* - version: link:../md-compiler + version: link:../../libraries/md-compiler '@truenine/plugin-input-shared': specifier: workspace:* version: link:../plugin-input-shared @@ -915,7 +1086,7 @@ importers: devDependencies: '@truenine/md-compiler': specifier: workspace:* - version: link:../md-compiler + version: link:../../libraries/md-compiler '@truenine/plugin-output-shared': specifier: workspace:* version: link:../plugin-output-shared @@ -943,13 +1114,13 @@ importers: devDependencies: '@truenine/init-bundle': specifier: workspace:* - version: link:../init-bundle + version: link:../../libraries/init-bundle '@truenine/logger': specifier: workspace:* - version: link:../logger + version: link:../../libraries/logger '@truenine/md-compiler': specifier: workspace:* - version: link:../md-compiler + version: link:../../libraries/md-compiler fast-glob: specifier: 'catalog:' version: 3.3.3 @@ -985,7 +1156,7 @@ importers: devDependencies: '@truenine/md-compiler': specifier: workspace:* - version: link:../md-compiler + version: link:../../libraries/md-compiler '@truenine/plugin-output-shared': specifier: workspace:* version: link:../plugin-output-shared @@ -1600,6 +1771,140 @@ packages: cpu: [x64] os: [win32] + '@inquirer/ansi@2.0.3': + resolution: {integrity: sha512-g44zhR3NIKVs0zUesa4iMzExmZpLUdTLRMCStqX3GE5NT6VkPcxQGJ+uC8tDgBUC/vB1rUhUd55cOf++4NZcmw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + + '@inquirer/checkbox@5.0.7': + resolution: {integrity: sha512-OGJykc3mpe4kiNXwXlDlP4MFqZso5QOoXJaJrmTJI+Y+gq68wxTyCUIFv34qgwZTHnGGeqwUKGOi4oxptTe+ZQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/confirm@6.0.7': + resolution: {integrity: sha512-lKdNloHLnGoBUUwprxKFd+SpkAnyQTBrZACFPtxDq9GiLICD2t+CaeJ1Ku4goZsGPyBIFc2YYpmDSJLEXoc16g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@11.1.4': + resolution: {integrity: sha512-1HvwyASF0tE/7W8geTTn0ydiWb463pq4SBIpaWcVabTrw55+CiRmytV9eZoqt3ohchsPw4Vv60jfNiI6YljVUg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/editor@5.0.7': + resolution: {integrity: sha512-d36tisyvmxH7H+LICTeTofrKmJ+R1jAYV8q0VTYh96cm8mP2BdGh9TAIqbCGcciX8/dr0fJW+VJq3jAnco5xfg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/expand@5.0.7': + resolution: {integrity: sha512-h2RRFzDdeXOXLrJOUAaHzyR1HbiZlrl/NxorOAgNrzhiSThbwEFVOf88lJzbF5WXGrQ2RwqK2h0xAE7eo8QP5w==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/external-editor@2.0.3': + resolution: {integrity: sha512-LgyI7Agbda74/cL5MvA88iDpvdXI2KuMBCGRkbCl2Dg1vzHeOgs+s0SDcXV7b+WZJrv2+ERpWSM65Fpi9VfY3w==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@2.0.3': + resolution: {integrity: sha512-y09iGt3JKoOCBQ3w4YrSJdokcD8ciSlMIWsD+auPu+OZpfxLuyz+gICAQ6GCBOmJJt4KEQGHuZSVff2jiNOy7g==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + + '@inquirer/input@5.0.7': + resolution: {integrity: sha512-b+eKk/eUvKLQ6c+rDu9u4I1+twdjOfrEaw9NURDpCrWYJTWL1/JQEudZi0AeqXDGcn0tMdhlfpEfjcqr33B/qw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/number@4.0.7': + resolution: {integrity: sha512-/l5KxcLFFexzOwh8DcVOI7zgVQCwcBt/9yHWtvMdYvaYLMK5J31BSR/fO3Z9WauA21qwAkDGRvYNHIG4vR6JwA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/password@5.0.7': + resolution: {integrity: sha512-h3Rgzb8nFMxgK6X5246MtwTX/rXs5Z58DbeuUKI6W5dQ+CZusEunNeT7rosdB+Upn79BkfZJO0AaiH8MIi9v1A==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/prompts@8.2.1': + resolution: {integrity: sha512-76knJFW2oXdI6If5YRmEoT5u7l+QroXYrMiINFcb97LsyECgsbO9m6iWlPuhBtaFgNITPHQCk3wbex38q8gsjg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/rawlist@5.2.3': + resolution: {integrity: sha512-EuvV6N/T3xDmRVihAOqfnbmtHGdu26TocRKANvcX/7nLLD8QO0c22Dtlc5C15+V433d9v0E0SSyqywdNCIXfLg==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/search@4.1.3': + resolution: {integrity: sha512-6BE8MqVMakEiLDRtrwj9fbx6AYhuj7McW3GOkOoEiQ5Qkh6v6f5HCoYNqSRE4j6nT+u+73518iUQPE+mZYlAjA==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/select@5.0.7': + resolution: {integrity: sha512-1JUJIR+Z2PsvwP6VWty7aE0aCPaT2cy2c4Vp3LPhL2Pi3+aXewAld/AyJ/CW9XWx1JbKxmdElfvls/G/7jG7ZQ==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/type@4.0.3': + resolution: {integrity: sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw==} + engines: {node: '>=23.5.0 || ^22.13.0 || ^21.7.0 || ^20.12.0'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -1632,9 +1937,356 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + '@napi-rs/cli@3.5.1': + resolution: {integrity: sha512-XBfLQRDcB3qhu6bazdMJsecWW55kR85l5/k0af9BIBELXQSsCFU0fzug7PX8eQp6vVdm7W/U3z6uP5WmITB2Gw==} + engines: {node: '>= 16'} + hasBin: true + peerDependencies: + '@emnapi/runtime': ^1.7.1 + peerDependenciesMeta: + '@emnapi/runtime': + optional: true + + '@napi-rs/cross-toolchain@1.0.3': + resolution: {integrity: sha512-ENPfLe4937bsKVTDA6zdABx4pq9w0tHqRrJHyaGxgaPq03a2Bd1unD5XSKjXJjebsABJ+MjAv1A2OvCgK9yehg==} + peerDependencies: + '@napi-rs/cross-toolchain-arm64-target-aarch64': ^1.0.3 + '@napi-rs/cross-toolchain-arm64-target-armv7': ^1.0.3 + '@napi-rs/cross-toolchain-arm64-target-ppc64le': ^1.0.3 + '@napi-rs/cross-toolchain-arm64-target-s390x': ^1.0.3 + '@napi-rs/cross-toolchain-arm64-target-x86_64': ^1.0.3 + '@napi-rs/cross-toolchain-x64-target-aarch64': ^1.0.3 + '@napi-rs/cross-toolchain-x64-target-armv7': ^1.0.3 + '@napi-rs/cross-toolchain-x64-target-ppc64le': ^1.0.3 + '@napi-rs/cross-toolchain-x64-target-s390x': ^1.0.3 + '@napi-rs/cross-toolchain-x64-target-x86_64': ^1.0.3 + peerDependenciesMeta: + '@napi-rs/cross-toolchain-arm64-target-aarch64': + optional: true + '@napi-rs/cross-toolchain-arm64-target-armv7': + optional: true + '@napi-rs/cross-toolchain-arm64-target-ppc64le': + optional: true + '@napi-rs/cross-toolchain-arm64-target-s390x': + optional: true + '@napi-rs/cross-toolchain-arm64-target-x86_64': + optional: true + '@napi-rs/cross-toolchain-x64-target-aarch64': + optional: true + '@napi-rs/cross-toolchain-x64-target-armv7': + optional: true + '@napi-rs/cross-toolchain-x64-target-ppc64le': + optional: true + '@napi-rs/cross-toolchain-x64-target-s390x': + optional: true + '@napi-rs/cross-toolchain-x64-target-x86_64': + optional: true + + '@napi-rs/lzma-android-arm-eabi@1.4.5': + resolution: {integrity: sha512-Up4gpyw2SacmyKWWEib06GhiDdF+H+CCU0LAV8pnM4aJIDqKKd5LHSlBht83Jut6frkB0vwEPmAkv4NjQ5u//Q==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@napi-rs/lzma-android-arm64@1.4.5': + resolution: {integrity: sha512-uwa8sLlWEzkAM0MWyoZJg0JTD3BkPknvejAFG2acUA1raXM8jLrqujWCdOStisXhqQjZ2nDMp3FV6cs//zjfuQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@napi-rs/lzma-darwin-arm64@1.4.5': + resolution: {integrity: sha512-0Y0TQLQ2xAjVabrMDem1NhIssOZzF/y/dqetc6OT8mD3xMTDtF8u5BqZoX3MyPc9FzpsZw4ksol+w7DsxHrpMA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@napi-rs/lzma-darwin-x64@1.4.5': + resolution: {integrity: sha512-vR2IUyJY3En+V1wJkwmbGWcYiT8pHloTAWdW4pG24+51GIq+intst6Uf6D/r46citObGZrlX0QvMarOkQeHWpw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@napi-rs/lzma-freebsd-x64@1.4.5': + resolution: {integrity: sha512-XpnYQC5SVovO35tF0xGkbHYjsS6kqyNCjuaLQ2dbEblFRr5cAZVvsJ/9h7zj/5FluJPJRDojVNxGyRhTp4z2lw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@napi-rs/lzma-linux-arm-gnueabihf@1.4.5': + resolution: {integrity: sha512-ic1ZZMoRfRMwtSwxkyw4zIlbDZGC6davC9r+2oX6x9QiF247BRqqT94qGeL5ZP4Vtz0Hyy7TEViWhx5j6Bpzvw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@napi-rs/lzma-linux-arm64-gnu@1.4.5': + resolution: {integrity: sha512-asEp7FPd7C1Yi6DQb45a3KPHKOFBSfGuJWXcAd4/bL2Fjetb2n/KK2z14yfW8YC/Fv6x3rBM0VAZKmJuz4tysg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@napi-rs/lzma-linux-arm64-musl@1.4.5': + resolution: {integrity: sha512-yWjcPDgJ2nIL3KNvi4536dlT/CcCWO0DUyEOlBs/SacG7BeD6IjGh6yYzd3/X1Y3JItCbZoDoLUH8iB1lTXo3w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@napi-rs/lzma-linux-ppc64-gnu@1.4.5': + resolution: {integrity: sha512-0XRhKuIU/9ZjT4WDIG/qnX7Xz7mSQHYZo9Gb3MP2gcvBgr6BA4zywQ9k3gmQaPn9ECE+CZg2V7DV7kT+x2pUMQ==} + engines: {node: '>= 10'} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@napi-rs/lzma-linux-riscv64-gnu@1.4.5': + resolution: {integrity: sha512-QrqDIPEUUB23GCpyQj/QFyMlr8SGxxyExeZz9OWFnHfb70kXdTLWrHS/hEI1Ru+lSbQ/6xRqeoGyQ4Aqdg+/RA==} + engines: {node: '>= 10'} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@napi-rs/lzma-linux-s390x-gnu@1.4.5': + resolution: {integrity: sha512-k8RVM5aMhW86E9H0QXdquwojew4H3SwPxbRVbl49/COJQWCUjGi79X6mYruMnMPEznZinUiT1jgKbFo2A00NdA==} + engines: {node: '>= 10'} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@napi-rs/lzma-linux-x64-gnu@1.4.5': + resolution: {integrity: sha512-6rMtBgnIq2Wcl1rQdZsnM+rtCcVCbws1nF8S2NzaUsVaZv8bjrPiAa0lwg4Eqnn1d9lgwqT+cZgm5m+//K08Kw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@napi-rs/lzma-linux-x64-musl@1.4.5': + resolution: {integrity: sha512-eiadGBKi7Vd0bCArBUOO/qqRYPHt/VQVvGyYvDFt6C2ZSIjlD+HuOl+2oS1sjf4CFjK4eDIog6EdXnL0NE6iyQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@napi-rs/lzma-wasm32-wasi@1.4.5': + resolution: {integrity: sha512-+VyHHlr68dvey6fXc2hehw9gHVFIW3TtGF1XkcbAu65qVXsA9D/T+uuoRVqhE+JCyFHFrO0ixRbZDRK1XJt1sA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@napi-rs/lzma-win32-arm64-msvc@1.4.5': + resolution: {integrity: sha512-eewnqvIyyhHi3KaZtBOJXohLvwwN27gfS2G/YDWdfHlbz1jrmfeHAmzMsP5qv8vGB+T80TMHNkro4kYjeh6Deg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@napi-rs/lzma-win32-ia32-msvc@1.4.5': + resolution: {integrity: sha512-OeacFVRCJOKNU/a0ephUfYZ2Yt+NvaHze/4TgOwJ0J0P4P7X1mHzN+ig9Iyd74aQDXYqc7kaCXA2dpAOcH87Cg==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@napi-rs/lzma-win32-x64-msvc@1.4.5': + resolution: {integrity: sha512-T4I1SamdSmtyZgDXGAGP+y5LEK5vxHUFwe8mz6D4R7Sa5/WCxTcCIgPJ9BD7RkpO17lzhlaM2vmVvMy96Lvk9Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@napi-rs/lzma@1.4.5': + resolution: {integrity: sha512-zS5LuN1OBPAyZpda2ZZgYOEDC+xecUdAGnrvbYzjnLXkrq/OBC3B9qcRvlxbDR3k5H/gVfvef1/jyUqPknqjbg==} + engines: {node: '>= 10'} + + '@napi-rs/tar-android-arm-eabi@1.1.0': + resolution: {integrity: sha512-h2Ryndraj/YiKgMV/r5by1cDusluYIRT0CaE0/PekQ4u+Wpy2iUVqvzVU98ZPnhXaNeYxEvVJHNGafpOfaD0TA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@napi-rs/tar-android-arm64@1.1.0': + resolution: {integrity: sha512-DJFyQHr1ZxNZorm/gzc1qBNLF/FcKzcH0V0Vwan5P+o0aE2keQIGEjJ09FudkF9v6uOuJjHCVDdK6S6uHtShAw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@napi-rs/tar-darwin-arm64@1.1.0': + resolution: {integrity: sha512-Zz2sXRzjIX4e532zD6xm2SjXEym6MkvfCvL2RMpG2+UwNVDVscHNcz3d47Pf3sysP2e2af7fBB3TIoK2f6trPw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@napi-rs/tar-darwin-x64@1.1.0': + resolution: {integrity: sha512-EI+CptIMNweT0ms9S3mkP/q+J6FNZ1Q6pvpJOEcWglRfyfQpLqjlC0O+dptruTPE8VamKYuqdjxfqD8hifZDOA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@napi-rs/tar-freebsd-x64@1.1.0': + resolution: {integrity: sha512-J0PIqX+pl6lBIAckL/c87gpodLbjZB1OtIK+RDscKC9NLdpVv6VGOxzUV/fYev/hctcE8EfkLbgFOfpmVQPg2g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@napi-rs/tar-linux-arm-gnueabihf@1.1.0': + resolution: {integrity: sha512-SLgIQo3f3EjkZ82ZwvrEgFvMdDAhsxCYjyoSuWfHCz0U16qx3SuGCp8+FYOPYCECHN3ZlGjXnoAIt9ERd0dEUg==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@napi-rs/tar-linux-arm64-gnu@1.1.0': + resolution: {integrity: sha512-d014cdle52EGaH6GpYTQOP9Py7glMO1zz/+ynJPjjzYFSxvdYx0byrjumZk2UQdIyGZiJO2MEFpCkEEKFSgPYA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@napi-rs/tar-linux-arm64-musl@1.1.0': + resolution: {integrity: sha512-L/y1/26q9L/uBqiW/JdOb/Dc94egFvNALUZV2WCGKQXc6UByPBMgdiEyW2dtoYxYYYYc+AKD+jr+wQPcvX2vrQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@napi-rs/tar-linux-ppc64-gnu@1.1.0': + resolution: {integrity: sha512-EPE1K/80RQvPbLRJDJs1QmCIcH+7WRi0F73+oTe1582y9RtfGRuzAkzeBuAGRXAQEjRQw/RjtNqr6UTJ+8UuWQ==} + engines: {node: '>= 10'} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@napi-rs/tar-linux-s390x-gnu@1.1.0': + resolution: {integrity: sha512-B2jhWiB1ffw1nQBqLUP1h4+J1ovAxBOoe5N2IqDMOc63fsPZKNqF1PvO/dIem8z7LL4U4bsfmhy3gBfu547oNQ==} + engines: {node: '>= 10'} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@napi-rs/tar-linux-x64-gnu@1.1.0': + resolution: {integrity: sha512-tbZDHnb9617lTnsDMGo/eAMZxnsQFnaRe+MszRqHguKfMwkisc9CCJnks/r1o84u5fECI+J/HOrKXgczq/3Oww==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@napi-rs/tar-linux-x64-musl@1.1.0': + resolution: {integrity: sha512-dV6cODlzbO8u6Anmv2N/ilQHq/AWz0xyltuXoLU3yUyXbZcnWYZuB2rL8OBGPmqNcD+x9NdScBNXh7vWN0naSQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@napi-rs/tar-wasm32-wasi@1.1.0': + resolution: {integrity: sha512-jIa9nb2HzOrfH0F8QQ9g3WE4aMH5vSI5/1NYVNm9ysCmNjCCtMXCAhlI3WKCdm/DwHf0zLqdrrtDFXODcNaqMw==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@napi-rs/tar-win32-arm64-msvc@1.1.0': + resolution: {integrity: sha512-vfpG71OB0ijtjemp3WTdmBKJm9R70KM8vsSExMsIQtV0lVzP07oM1CW6JbNRPXNLhRoue9ofYLiUDk8bE0Hckg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@napi-rs/tar-win32-ia32-msvc@1.1.0': + resolution: {integrity: sha512-hGPyPW60YSpOSgzfy68DLBHgi6HxkAM+L59ZZZPMQ0TOXjQg+p2EW87+TjZfJOkSpbYiEkULwa/f4a2hcVjsqQ==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@napi-rs/tar-win32-x64-msvc@1.1.0': + resolution: {integrity: sha512-L6Ed1DxXK9YSCMyvpR8MiNAyKNkQLjsHsHK9E0qnHa8NzLFqzDKhvs5LfnWxM2kJ+F7m/e5n9zPm24kHb3LsVw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@napi-rs/tar@1.1.0': + resolution: {integrity: sha512-7cmzIu+Vbupriudo7UudoMRH2OA3cTw67vva8MxeoAe5S7vPFI7z0vp0pMXiA25S8IUJefImQ90FeJjl8fjEaQ==} + engines: {node: '>= 10'} + '@napi-rs/wasm-runtime@1.1.1': resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} + '@napi-rs/wasm-tools-android-arm-eabi@1.0.1': + resolution: {integrity: sha512-lr07E/l571Gft5v4aA1dI8koJEmF1F0UigBbsqg9OWNzg80H3lDPO+auv85y3T/NHE3GirDk7x/D3sLO57vayw==} + engines: {node: '>= 10'} + cpu: [arm] + os: [android] + + '@napi-rs/wasm-tools-android-arm64@1.0.1': + resolution: {integrity: sha512-WDR7S+aRLV6LtBJAg5fmjKkTZIdrEnnQxgdsb7Cf8pYiMWBHLU+LC49OUVppQ2YSPY0+GeYm9yuZWW3kLjJ7Bg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@napi-rs/wasm-tools-darwin-arm64@1.0.1': + resolution: {integrity: sha512-qWTI+EEkiN0oIn/N2gQo7+TVYil+AJ20jjuzD2vATS6uIjVz+Updeqmszi7zq7rdFTLp6Ea3/z4kDKIfZwmR9g==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@napi-rs/wasm-tools-darwin-x64@1.0.1': + resolution: {integrity: sha512-bA6hubqtHROR5UI3tToAF/c6TDmaAgF0SWgo4rADHtQ4wdn0JeogvOk50gs2TYVhKPE2ZD2+qqt7oBKB+sxW3A==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@napi-rs/wasm-tools-freebsd-x64@1.0.1': + resolution: {integrity: sha512-90+KLBkD9hZEjPQW1MDfwSt5J1L46EUKacpCZWyRuL6iIEO5CgWU0V/JnEgFsDOGyyYtiTvHc5bUdUTWd4I9Vg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@napi-rs/wasm-tools-linux-arm64-gnu@1.0.1': + resolution: {integrity: sha512-rG0QlS65x9K/u3HrKafDf8cFKj5wV2JHGfl8abWgKew0GVPyp6vfsDweOwHbWAjcHtp2LHi6JHoW80/MTHm52Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@napi-rs/wasm-tools-linux-arm64-musl@1.0.1': + resolution: {integrity: sha512-jAasbIvjZXCgX0TCuEFQr+4D6Lla/3AAVx2LmDuMjgG4xoIXzjKWl7c4chuaD+TI+prWT0X6LJcdzFT+ROKGHQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@napi-rs/wasm-tools-linux-x64-gnu@1.0.1': + resolution: {integrity: sha512-Plgk5rPqqK2nocBGajkMVbGm010Z7dnUgq0wtnYRZbzWWxwWcXfZMPa8EYxrK4eE8SzpI7VlZP1tdVsdjgGwMw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@napi-rs/wasm-tools-linux-x64-musl@1.0.1': + resolution: {integrity: sha512-GW7AzGuWxtQkyHknHWYFdR0CHmW6is8rG2Rf4V6GNmMpmwtXt/ItWYWtBe4zqJWycMNazpfZKSw/BpT7/MVCXQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@napi-rs/wasm-tools-wasm32-wasi@1.0.1': + resolution: {integrity: sha512-/nQVSTrqSsn7YdAc2R7Ips/tnw5SPUcl3D7QrXCNGPqjbatIspnaexvaOYNyKMU6xPu+pc0BTnKVmqhlJJCPLA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@napi-rs/wasm-tools-win32-arm64-msvc@1.0.1': + resolution: {integrity: sha512-PFi7oJIBu5w7Qzh3dwFea3sHRO3pojMsaEnUIy22QvsW+UJfNQwJCryVrpoUt8m4QyZXI+saEq/0r4GwdoHYFQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@napi-rs/wasm-tools-win32-ia32-msvc@1.0.1': + resolution: {integrity: sha512-gXkuYzxQsgkj05Zaq+KQTkHIN83dFAwMcTKa2aQcpYPRImFm2AQzEyLtpXmyCWzJ0F9ZYAOmbSyrNew8/us6bw==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@napi-rs/wasm-tools-win32-x64-msvc@1.0.1': + resolution: {integrity: sha512-rEAf05nol3e3eei2sRButmgXP+6ATgm0/38MKhz9Isne82T4rPIMYsCIFj0kOisaGeVwoi2fnm7O9oWp5YVnYQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@napi-rs/wasm-tools@1.0.1': + resolution: {integrity: sha512-enkZYyuCdo+9jneCPE/0fjIta4wWnvVN9hBo2HuiMpRF0q3lzv1J6b/cl7i0mxZUKhBrV3aCKDBQnCOhwKbPmQ==} + engines: {node: '>= 10'} + '@next/env@16.1.6': resolution: {integrity: sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==} @@ -1716,6 +2368,58 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@octokit/auth-token@6.0.0': + resolution: {integrity: sha512-P4YJBPdPSpWTQ1NU4XYdvHvXJJDxM6YwpS0FZHRgP7YFkdVxsWcpWGy/NVqlAA7PcPCnMacXlRm1y2PFZRWL/w==} + engines: {node: '>= 20'} + + '@octokit/core@7.0.6': + resolution: {integrity: sha512-DhGl4xMVFGVIyMwswXeyzdL4uXD5OGILGX5N8Y+f6W7LhC1Ze2poSNrkF/fedpVDHEEZ+PHFW0vL14I+mm8K3Q==} + engines: {node: '>= 20'} + + '@octokit/endpoint@11.0.3': + resolution: {integrity: sha512-FWFlNxghg4HrXkD3ifYbS/IdL/mDHjh9QcsNyhQjN8dplUoZbejsdpmuqdA76nxj2xoWPs7p8uX2SNr9rYu0Ag==} + engines: {node: '>= 20'} + + '@octokit/graphql@9.0.3': + resolution: {integrity: sha512-grAEuupr/C1rALFnXTv6ZQhFuL1D8G5y8CN04RgrO4FIPMrtm+mcZzFG7dcBm+nq+1ppNixu+Jd78aeJOYxlGA==} + engines: {node: '>= 20'} + + '@octokit/openapi-types@27.0.0': + resolution: {integrity: sha512-whrdktVs1h6gtR+09+QsNk2+FO+49j6ga1c55YZudfEG+oKJVvJLQi3zkOm5JjiUXAagWK2tI2kTGKJ2Ys7MGA==} + + '@octokit/plugin-paginate-rest@14.0.0': + resolution: {integrity: sha512-fNVRE7ufJiAA3XUrha2omTA39M6IXIc6GIZLvlbsm8QOQCYvpq/LkMNGyFlB1d8hTDzsAXa3OKtybdMAYsV/fw==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-request-log@6.0.0': + resolution: {integrity: sha512-UkOzeEN3W91/eBq9sPZNQ7sUBvYCqYbrrD8gTbBuGtHEuycE4/awMXcYvx6sVYo7LypPhmQwwpUe4Yyu4QZN5Q==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/plugin-rest-endpoint-methods@17.0.0': + resolution: {integrity: sha512-B5yCyIlOJFPqUUeiD0cnBJwWJO8lkJs5d8+ze9QDP6SvfiXSz1BF+91+0MeI1d2yxgOhU/O+CvtiZ9jSkHhFAw==} + engines: {node: '>= 20'} + peerDependencies: + '@octokit/core': '>=6' + + '@octokit/request-error@7.1.0': + resolution: {integrity: sha512-KMQIfq5sOPpkQYajXHwnhjCC0slzCNScLHs9JafXc4RAJI+9f+jNDlBNaIMTvazOPLgb4BnlhGJOTbnN0wIjPw==} + engines: {node: '>= 20'} + + '@octokit/request@10.0.8': + resolution: {integrity: sha512-SJZNwY9pur9Agf7l87ywFi14W+Hd9Jg6Ifivsd33+/bGUQIjNujdFiXII2/qSlN2ybqUHfp5xpekMEjIBTjlSw==} + engines: {node: '>= 20'} + + '@octokit/rest@22.0.1': + resolution: {integrity: sha512-Jzbhzl3CEexhnivb1iQ0KJ7s5vvjMWcmRtq5aUsKmKDrRW6z3r84ngmiFKFvpZjpiU/9/S6ITPFRpn5s/3uQJw==} + engines: {node: '>= 20'} + + '@octokit/types@16.0.0': + resolution: {integrity: sha512-sKq+9r1Mm4efXW1FCk7hFSeJo4QKreL/tTbR0rz/qx/r1Oa2VV83LTA/H/MuCOX7uCIJmQVRKBcbmWoySjAnSg==} + '@oxc-project/types@0.112.0': resolution: {integrity: sha512-m6RebKHIRsax2iCwVpYW2ErQwa4ywHJrE4sCK3/8JK8ZZAWOKXaRJFl/uP51gaVyyXlaS4+chU1nSCdzYf6QqQ==} @@ -1821,141 +2525,141 @@ packages: '@rolldown/pluginutils@1.0.0-rc.3': resolution: {integrity: sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==} - '@rollup/rollup-android-arm-eabi@4.58.0': - resolution: {integrity: sha512-mr0tmS/4FoVk1cnaeN244A/wjvGDNItZKR8hRhnmCzygyRXYtKF5jVDSIILR1U97CTzAYmbgIj/Dukg62ggG5w==} + '@rollup/rollup-android-arm-eabi@4.59.0': + resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.58.0': - resolution: {integrity: sha512-+s++dbp+/RTte62mQD9wLSbiMTV+xr/PeRJEc/sFZFSBRlHPNPVaf5FXlzAL77Mr8FtSfQqCN+I598M8U41ccQ==} + '@rollup/rollup-android-arm64@4.59.0': + resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.58.0': - resolution: {integrity: sha512-MFWBwTcYs0jZbINQBXHfSrpSQJq3IUOakcKPzfeSznONop14Pxuqa0Kg19GD0rNBMPQI2tFtu3UzapZpH0Uc1Q==} + '@rollup/rollup-darwin-arm64@4.59.0': + resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.58.0': - resolution: {integrity: sha512-yiKJY7pj9c9JwzuKYLFaDZw5gma3fI9bkPEIyofvVfsPqjCWPglSHdpdwXpKGvDeYDms3Qal8qGMEHZ1M/4Udg==} + '@rollup/rollup-darwin-x64@4.59.0': + resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.58.0': - resolution: {integrity: sha512-x97kCoBh5MOevpn/CNK9W1x8BEzO238541BGWBc315uOlN0AD/ifZ1msg+ZQB05Ux+VF6EcYqpiagfLJ8U3LvQ==} + '@rollup/rollup-freebsd-arm64@4.59.0': + resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.58.0': - resolution: {integrity: sha512-Aa8jPoZ6IQAG2eIrcXPpjRcMjROMFxCt1UYPZZtCxRV68WkuSigYtQ/7Zwrcr2IvtNJo7T2JfDXyMLxq5L4Jlg==} + '@rollup/rollup-freebsd-x64@4.59.0': + resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.58.0': - resolution: {integrity: sha512-Ob8YgT5kD/lSIYW2Rcngs5kNB/44Q2RzBSPz9brf2WEtcGR7/f/E9HeHn1wYaAwKBni+bdXEwgHvUd0x12lQSA==} + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} cpu: [arm] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm-musleabihf@4.58.0': - resolution: {integrity: sha512-K+RI5oP1ceqoadvNt1FecL17Qtw/n9BgRSzxif3rTL2QlIu88ccvY+Y9nnHe/cmT5zbH9+bpiJuG1mGHRVwF4Q==} + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} cpu: [arm] os: [linux] libc: [musl] - '@rollup/rollup-linux-arm64-gnu@4.58.0': - resolution: {integrity: sha512-T+17JAsCKUjmbopcKepJjHWHXSjeW7O5PL7lEFaeQmiVyw4kkc5/lyYKzrv6ElWRX/MrEWfPiJWqbTvfIvjM1Q==} + '@rollup/rollup-linux-arm64-gnu@4.59.0': + resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} cpu: [arm64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-arm64-musl@4.58.0': - resolution: {integrity: sha512-cCePktb9+6R9itIJdeCFF9txPU7pQeEHB5AbHu/MKsfH/k70ZtOeq1k4YAtBv9Z7mmKI5/wOLYjQ+B9QdxR6LA==} + '@rollup/rollup-linux-arm64-musl@4.59.0': + resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} cpu: [arm64] os: [linux] libc: [musl] - '@rollup/rollup-linux-loong64-gnu@4.58.0': - resolution: {integrity: sha512-iekUaLkfliAsDl4/xSdoCJ1gnnIXvoNz85C8U8+ZxknM5pBStfZjeXgB8lXobDQvvPRCN8FPmmuTtH+z95HTmg==} + '@rollup/rollup-linux-loong64-gnu@4.59.0': + resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} cpu: [loong64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-loong64-musl@4.58.0': - resolution: {integrity: sha512-68ofRgJNl/jYJbxFjCKE7IwhbfxOl1muPN4KbIqAIe32lm22KmU7E8OPvyy68HTNkI2iV/c8y2kSPSm2mW/Q9Q==} + '@rollup/rollup-linux-loong64-musl@4.59.0': + resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} cpu: [loong64] os: [linux] libc: [musl] - '@rollup/rollup-linux-ppc64-gnu@4.58.0': - resolution: {integrity: sha512-dpz8vT0i+JqUKuSNPCP5SYyIV2Lh0sNL1+FhM7eLC457d5B9/BC3kDPp5BBftMmTNsBarcPcoz5UGSsnCiw4XQ==} + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} cpu: [ppc64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-ppc64-musl@4.58.0': - resolution: {integrity: sha512-4gdkkf9UJ7tafnweBCR/mk4jf3Jfl0cKX9Np80t5i78kjIH0ZdezUv/JDI2VtruE5lunfACqftJ8dIMGN4oHew==} + '@rollup/rollup-linux-ppc64-musl@4.59.0': + resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} cpu: [ppc64] os: [linux] libc: [musl] - '@rollup/rollup-linux-riscv64-gnu@4.58.0': - resolution: {integrity: sha512-YFS4vPnOkDTD/JriUeeZurFYoJhPf9GQQEF/v4lltp3mVcBmnsAdjEWhr2cjUCZzZNzxCG0HZOvJU44UGHSdzw==} + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} cpu: [riscv64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-riscv64-musl@4.58.0': - resolution: {integrity: sha512-x2xgZlFne+QVNKV8b4wwaCS8pwq3y14zedZ5DqLzjdRITvreBk//4Knbcvm7+lWmms9V9qFp60MtUd0/t/PXPw==} + '@rollup/rollup-linux-riscv64-musl@4.59.0': + resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} cpu: [riscv64] os: [linux] libc: [musl] - '@rollup/rollup-linux-s390x-gnu@4.58.0': - resolution: {integrity: sha512-jIhrujyn4UnWF8S+DHSkAkDEO3hLX0cjzxJZPLF80xFyzyUIYgSMRcYQ3+uqEoyDD2beGq7Dj7edi8OnJcS/hg==} + '@rollup/rollup-linux-s390x-gnu@4.59.0': + resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} cpu: [s390x] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-gnu@4.58.0': - resolution: {integrity: sha512-+410Srdoh78MKSJxTQ+hZ/Mx+ajd6RjjPwBPNd0R3J9FtL6ZA0GqiiyNjCO9In0IzZkCNrpGymSfn+kgyPQocg==} + '@rollup/rollup-linux-x64-gnu@4.59.0': + resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} cpu: [x64] os: [linux] libc: [glibc] - '@rollup/rollup-linux-x64-musl@4.58.0': - resolution: {integrity: sha512-ZjMyby5SICi227y1MTR3VYBpFTdZs823Rs/hpakufleBoufoOIB6jtm9FEoxn/cgO7l6PM2rCEl5Kre5vX0QrQ==} + '@rollup/rollup-linux-x64-musl@4.59.0': + resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} cpu: [x64] os: [linux] libc: [musl] - '@rollup/rollup-openbsd-x64@4.58.0': - resolution: {integrity: sha512-ds4iwfYkSQ0k1nb8LTcyXw//ToHOnNTJtceySpL3fa7tc/AsE+UpUFphW126A6fKBGJD5dhRvg8zw1rvoGFxmw==} + '@rollup/rollup-openbsd-x64@4.59.0': + resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} cpu: [x64] os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.58.0': - resolution: {integrity: sha512-fd/zpJniln4ICdPkjWFhZYeY/bpnaN9pGa6ko+5WD38I0tTqk9lXMgXZg09MNdhpARngmxiCg0B0XUamNw/5BQ==} + '@rollup/rollup-openharmony-arm64@4.59.0': + resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.58.0': - resolution: {integrity: sha512-YpG8dUOip7DCz3nr/JUfPbIUo+2d/dy++5bFzgi4ugOGBIox+qMbbqt/JoORwvI/C9Kn2tz6+Bieoqd5+B1CjA==} + '@rollup/rollup-win32-arm64-msvc@4.59.0': + resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.58.0': - resolution: {integrity: sha512-b9DI8jpFQVh4hIXFr0/+N/TzLdpBIoPzjt0Rt4xJbW3mzguV3mduR9cNgiuFcuL/TeORejJhCWiAXe3E/6PxWA==} + '@rollup/rollup-win32-ia32-msvc@4.59.0': + resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.58.0': - resolution: {integrity: sha512-CSrVpmoRJFN06LL9xhkitkwUcTZtIotYAF5p6XOR2zW0Zz5mzb3IPpcoPhB02frzMHFNo1reQ9xSF5fFm3hUsQ==} + '@rollup/rollup-win32-x64-gnu@4.59.0': + resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.58.0': - resolution: {integrity: sha512-QFsBgQNTnh5K0t/sBsjJLq24YVqEIVkGpfN2VHsnN90soZyhaiA9UUHufcctVNL4ypJY0wrwad0wslx2KJQ1/w==} + '@rollup/rollup-win32-x64-msvc@4.59.0': + resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} cpu: [x64] os: [win32] @@ -2076,8 +2780,8 @@ packages: resolution: {integrity: sha512-Kp/WSt411ZWYvgXy6uiv5RmhHrz9cAml05AQPrtdAp7eUqvIDbMGPnML25OKbzR3RJ1q4wgENxDTvlGPa9+Mww==} engines: {node: '>=20.19'} - '@tanstack/react-router@1.162.1': - resolution: {integrity: sha512-HF2uSWqLqENWNH7vn+qnz1QY9ZrVunwLNUO57Lonvq5X20tziN/AK5p3z0A4zExej9I5SgEcG6Z/eaIv7aGhPA==} + '@tanstack/react-router@1.162.2': + resolution: {integrity: sha512-tZL7ASKTrLZ3HDxyNxSYqRCPKof267/SWlfOXmEyL+yI4zjp5QT9RHq12MJrbOGqCT8TsGGknNdimHBODltQWQ==} engines: {node: '>=20.19'} peerDependencies: react: '>=18.0.0 || >=19.0.0' @@ -2089,20 +2793,20 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tanstack/router-core@1.162.1': - resolution: {integrity: sha512-zq/ePd7UhWE1NkY4DZJ/a//2O+yiwOxkCqbFF+v++twnQUsKkTUepUln30S9yrPcnZFe76PlHnIzZSl5UeKm9w==} + '@tanstack/router-core@1.162.2': + resolution: {integrity: sha512-su/X6YP7LIhiy3FWkvCb5JL2LVmfupMXavvfyuPQayWb+E1NJQhYU8dbEcSGB0sNIMZeAmz0iIBSV2MVvuzQ8g==} engines: {node: '>=20.19'} - '@tanstack/router-generator@1.162.1': - resolution: {integrity: sha512-64NCZBR8QnhiwJRMtF2HOmoLsf4SgLubkzOIYasmJXJ2he24g8/hNNUY5ofhe2pGER74GVASrtOF3SSYCndaKw==} + '@tanstack/router-generator@1.162.2': + resolution: {integrity: sha512-XjT7OYbQNR+lqbWYNVG/7NUAbOLHAYaaRZVJXo6P+gpTE+1oJXHCZEnFzCvNh3K2DTtfVU2V+ohYHP+Cq/quQQ==} engines: {node: '>=20.19'} - '@tanstack/router-plugin@1.162.1': - resolution: {integrity: sha512-Rmuu3InFdPaDbv1vds53jZv+l6iqmFBttDb0HHXrG+/ECIk4y5oXG8mnpvt9vo8BMfy4KRCrylYuxueL1eAMRQ==} + '@tanstack/router-plugin@1.162.2': + resolution: {integrity: sha512-Hy4EkdbJ86UB5xifsZvrZEbEyQ6z8E9hgyhJ2mnkqNjyotQVKI+qpfqfexSRbAoF+wmHW8SX+5xjwLD/MQnNwg==} engines: {node: '>=20.19'} peerDependencies: '@rsbuild/core': '>=1.0.2' - '@tanstack/react-router': ^1.162.1 + '@tanstack/react-router': ^1.162.2 vite: '>=5.0.0 || >=6.0.0 || >=7.0.0' vite-plugin-solid: ^2.11.10 webpack: '>=5.92.0' @@ -2595,6 +3299,9 @@ packages: resolution: {integrity: sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==} engines: {node: '>=14'} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -2616,18 +3323,18 @@ packages: bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - balanced-match@4.0.3: - resolution: {integrity: sha512-1pHv8LX9CpKut1Zp4EXey7Z8OfH11ONNH6Dhi2WDUt31VVZFXZzKwXcysBgqSumFCmR+0dqjMK5v5JiFHzi0+g==} - engines: {node: 20 || >=22} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} baseline-browser-mapping@2.10.0: resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} engines: {node: '>=6.0.0'} hasBin: true + before-after-hook@4.0.0: + resolution: {integrity: sha512-q6tR3RPqIB1pMiTRMFcZwuG5T8vwp+vUvEG0vuI6B+Rikh5BfPp2fQ82c925FOs+b0lcFQ8CFrL+KbilfZFhOQ==} + binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} @@ -2638,12 +3345,9 @@ packages: boolbase@1.0.0: resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - - brace-expansion@5.0.2: - resolution: {integrity: sha512-Pdk8c9poy+YhOgVWw1JNN22/HcivgKWwpxKq04M/jTmHyCZn12WPJebZxdjSa5TmBqISrUSgNYU3eRORljfCCw==} - engines: {node: 20 || >=22} + brace-expansion@5.0.3: + resolution: {integrity: sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==} + engines: {node: 18 || 20 || >=22} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -2662,8 +3366,8 @@ packages: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} - caniuse-lite@1.0.30001770: - resolution: {integrity: sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==} + caniuse-lite@1.0.30001772: + resolution: {integrity: sha512-mIwLZICj+ntVTw4BT2zfp+yu/AqV6GMKfJVJMx3MwPxs+uk/uj2GLl2dH8LQbjiLDX66amCga5nKFyDgRR43kg==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} @@ -2687,6 +3391,9 @@ packages: character-reference-invalid@2.0.1: resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + chardet@2.1.1: + resolution: {integrity: sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==} + chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -2705,13 +3412,25 @@ packages: resolution: {integrity: sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw==} engines: {node: '>=4'} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + clipanion@4.0.0-rc.4: + resolution: {integrity: sha512-CXkMQxU6s9GklO/1f714dkKBMu1lopS1WFF0B8o4AxPykR1hpozxSiUZ5ZUeBjfPgCWqbcNOtZVFhB8Lkfp1+Q==} + peerDependencies: + typanion: '*' + clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + comment-parser@1.4.1: resolution: {integrity: sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==} engines: {node: '>= 12.0.0'} @@ -2850,6 +3569,14 @@ packages: electron-to-chromium@1.5.302: resolution: {integrity: sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==} + emnapi@1.8.1: + resolution: {integrity: sha512-34i2BbgHx1LnEO4JCGQYo6h6s4e4KrdWtdTHfllBNLbXSHPmdIHplxKejfabsRK+ukNciqVdalB+fxMibqHdaQ==} + peerDependencies: + node-addon-api: '>= 6.1.0' + peerDependenciesMeta: + node-addon-api: + optional: true + empathic@2.0.0: resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==} engines: {node: '>=14'} @@ -2920,13 +3647,13 @@ packages: peerDependencies: eslint: '>=8.40.0' - eslint-json-compat-utils@0.2.1: - resolution: {integrity: sha512-YzEodbDyW8DX8bImKhAcCeu/L31Dd/70Bidx2Qex9OFUtgzXLqtfWL4Hr5fM/aCCB8QUZLuJur0S9k6UfgFkfg==} + eslint-json-compat-utils@0.2.2: + resolution: {integrity: sha512-KcTUifi8VSSHkrOY0FzB7smuTZRU9T2nCrcCy6k2b+Q77+uylBQVIxN4baVCIWvWJEpud+IsrYgco4JJ6io05g==} engines: {node: '>=12'} peerDependencies: '@eslint/json': '*' eslint: '*' - jsonc-eslint-parser: ^2.4.0 + jsonc-eslint-parser: ^2.4.0 || ^3.0.0 peerDependenciesMeta: '@eslint/json': optional: true @@ -3165,6 +3892,9 @@ packages: resolution: {integrity: sha512-IE9csY7lnhxBnA8g/WI5eg/hygA6MGWJMSNfFRrBlXUciADEhS1EDB0SIsMSvzubzIlOBbVITSsypCsW717poA==} engines: {node: '>=12.17.0'} + fast-content-type-parse@3.0.0: + resolution: {integrity: sha512-ZvLdcY8P+N8mGQJahJV5G4U88CSvT1rP8ApL6uETe88MBXrBHAkZlSEySdUlyztF7ccb+Znos3TFqaepHxdhBg==} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -3185,6 +3915,15 @@ packages: fast-levenshtein@2.0.6: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + fast-string-truncated-width@3.0.3: + resolution: {integrity: sha512-0jjjIEL6+0jag3l2XWWizO64/aZVtpiGE3t0Zgqxv0DPuxiMjvB3M24fCyhZUO4KomJQPj3LTSUnDP3GpdwC0g==} + + fast-string-width@3.0.2: + resolution: {integrity: sha512-gX8LrtNEI5hq8DVUfRQMbr5lpaS4nMIWV+7XEbXk2b8kiQIizgnlr12B4dA3ZEx3308ze0O4Q1R+cHts8kyUJg==} + + fast-wrap-ansi@0.2.0: + resolution: {integrity: sha512-rLV8JHxTyhVmFYhBJuMujcrHqOT2cnO5Zxj37qROj23CP39GXubJRBUFF0z8KFK77Uc0SukZUf7JZhsVEQ6n8w==} + fastq@1.20.1: resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} @@ -3284,6 +4023,10 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + iconv-lite@0.7.2: + resolution: {integrity: sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==} + engines: {node: '>=0.10.0'} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -3386,6 +4129,10 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + jsdoc-type-pratt-parser@4.8.0: resolution: {integrity: sha512-iZ8Bdb84lWRuGHamRXFyML07r21pcwBrLkHEuHgEY5UbCouBwv7ECknDRKzsQIXMiqpPymqtIf8TC/shYKB5rw==} engines: {node: '>=12.0.0'} @@ -3412,6 +4159,9 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + json-with-bigint@3.5.3: + resolution: {integrity: sha512-QObKu6nxy7NsxqR0VK4rkXnsNr5L9ElJaGEg+ucJ6J7/suoKZ0n+p76cu9aCqowytxEbwYNzvrMerfMkXneF5A==} + json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} @@ -3728,8 +4478,8 @@ packages: resolution: {integrity: sha512-+G4CpNBxa5MprY+04MbgOw1v7So6n5JY166pFi9KfYwT78fxScCeSNQSNzp6dpPSW2rONOps6Ocam1wFhCgoVw==} engines: {node: 18 || 20 || >=22} - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + minimatch@9.0.6: + resolution: {integrity: sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==} engines: {node: '>=16 || 14 >=14.17'} mlly@1.8.0: @@ -3741,6 +4491,10 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + mute-stream@3.0.0: + resolution: {integrity: sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw==} + engines: {node: ^20.17.0 || >=22.9.0} + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -4037,14 +4791,17 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} hasBin: true - rollup@4.58.0: - resolution: {integrity: sha512-wbT0mBmWbIvvq8NeEYWWvevvxnOyhKChir47S66WCxw1SXqhw7ssIYejnQEVt7XYQpsj2y8F9PM+Cr3SNEa0gw==} + rollup@4.59.0: + resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} @@ -4090,6 +4847,10 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + simple-git-hooks@2.13.1: resolution: {integrity: sha512-WszCLXwT4h2k1ufIXAgsbiTOazqqevFCIncOuUBZJ91DdvWcC5+OFkluWRQPrcuSYd8fjq+o2y1QfWqYMoAToQ==} hasBin: true @@ -4289,6 +5050,9 @@ packages: tw-animate-css@1.4.0: resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} + typanion@3.14.0: + resolution: {integrity: sha512-ZW/lVMRabETuYCd9O9ZvMhAh8GslSqaUjxmK/JLPCh6l73CvLBiuXswj/+7LdnWOgYsQ130FqLzFz5aGT4I3Ug==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -4335,6 +5099,9 @@ packages: unist-util-visit@5.1.0: resolution: {integrity: sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==} + universal-user-agent@7.0.3: + resolution: {integrity: sha512-TmnEAEAsBJVZM/AADELsK76llnwcf9vMKuPz8JflO1frO8Lchitr0fNaN9d+Ap0BjKtqWqd/J17qeDnXh8CL2A==} + universalify@2.0.1: resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} engines: {node: '>= 10.0.0'} @@ -4765,7 +5532,6 @@ snapshots: '@emnapi/runtime@1.8.1': dependencies: tslib: 2.8.1 - optional: true '@emnapi/wasi-threads@1.1.0': dependencies: @@ -5035,6 +5801,125 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true + '@inquirer/ansi@2.0.3': {} + + '@inquirer/checkbox@5.0.7(@types/node@25.3.0)': + dependencies: + '@inquirer/ansi': 2.0.3 + '@inquirer/core': 11.1.4(@types/node@25.3.0) + '@inquirer/figures': 2.0.3 + '@inquirer/type': 4.0.3(@types/node@25.3.0) + optionalDependencies: + '@types/node': 25.3.0 + + '@inquirer/confirm@6.0.7(@types/node@25.3.0)': + dependencies: + '@inquirer/core': 11.1.4(@types/node@25.3.0) + '@inquirer/type': 4.0.3(@types/node@25.3.0) + optionalDependencies: + '@types/node': 25.3.0 + + '@inquirer/core@11.1.4(@types/node@25.3.0)': + dependencies: + '@inquirer/ansi': 2.0.3 + '@inquirer/figures': 2.0.3 + '@inquirer/type': 4.0.3(@types/node@25.3.0) + cli-width: 4.1.0 + fast-wrap-ansi: 0.2.0 + mute-stream: 3.0.0 + signal-exit: 4.1.0 + optionalDependencies: + '@types/node': 25.3.0 + + '@inquirer/editor@5.0.7(@types/node@25.3.0)': + dependencies: + '@inquirer/core': 11.1.4(@types/node@25.3.0) + '@inquirer/external-editor': 2.0.3(@types/node@25.3.0) + '@inquirer/type': 4.0.3(@types/node@25.3.0) + optionalDependencies: + '@types/node': 25.3.0 + + '@inquirer/expand@5.0.7(@types/node@25.3.0)': + dependencies: + '@inquirer/core': 11.1.4(@types/node@25.3.0) + '@inquirer/type': 4.0.3(@types/node@25.3.0) + optionalDependencies: + '@types/node': 25.3.0 + + '@inquirer/external-editor@2.0.3(@types/node@25.3.0)': + dependencies: + chardet: 2.1.1 + iconv-lite: 0.7.2 + optionalDependencies: + '@types/node': 25.3.0 + + '@inquirer/figures@2.0.3': {} + + '@inquirer/input@5.0.7(@types/node@25.3.0)': + dependencies: + '@inquirer/core': 11.1.4(@types/node@25.3.0) + '@inquirer/type': 4.0.3(@types/node@25.3.0) + optionalDependencies: + '@types/node': 25.3.0 + + '@inquirer/number@4.0.7(@types/node@25.3.0)': + dependencies: + '@inquirer/core': 11.1.4(@types/node@25.3.0) + '@inquirer/type': 4.0.3(@types/node@25.3.0) + optionalDependencies: + '@types/node': 25.3.0 + + '@inquirer/password@5.0.7(@types/node@25.3.0)': + dependencies: + '@inquirer/ansi': 2.0.3 + '@inquirer/core': 11.1.4(@types/node@25.3.0) + '@inquirer/type': 4.0.3(@types/node@25.3.0) + optionalDependencies: + '@types/node': 25.3.0 + + '@inquirer/prompts@8.2.1(@types/node@25.3.0)': + dependencies: + '@inquirer/checkbox': 5.0.7(@types/node@25.3.0) + '@inquirer/confirm': 6.0.7(@types/node@25.3.0) + '@inquirer/editor': 5.0.7(@types/node@25.3.0) + '@inquirer/expand': 5.0.7(@types/node@25.3.0) + '@inquirer/input': 5.0.7(@types/node@25.3.0) + '@inquirer/number': 4.0.7(@types/node@25.3.0) + '@inquirer/password': 5.0.7(@types/node@25.3.0) + '@inquirer/rawlist': 5.2.3(@types/node@25.3.0) + '@inquirer/search': 4.1.3(@types/node@25.3.0) + '@inquirer/select': 5.0.7(@types/node@25.3.0) + optionalDependencies: + '@types/node': 25.3.0 + + '@inquirer/rawlist@5.2.3(@types/node@25.3.0)': + dependencies: + '@inquirer/core': 11.1.4(@types/node@25.3.0) + '@inquirer/type': 4.0.3(@types/node@25.3.0) + optionalDependencies: + '@types/node': 25.3.0 + + '@inquirer/search@4.1.3(@types/node@25.3.0)': + dependencies: + '@inquirer/core': 11.1.4(@types/node@25.3.0) + '@inquirer/figures': 2.0.3 + '@inquirer/type': 4.0.3(@types/node@25.3.0) + optionalDependencies: + '@types/node': 25.3.0 + + '@inquirer/select@5.0.7(@types/node@25.3.0)': + dependencies: + '@inquirer/ansi': 2.0.3 + '@inquirer/core': 11.1.4(@types/node@25.3.0) + '@inquirer/figures': 2.0.3 + '@inquirer/type': 4.0.3(@types/node@25.3.0) + optionalDependencies: + '@types/node': 25.3.0 + + '@inquirer/type@4.0.3(@types/node@25.3.0)': + optionalDependencies: + '@types/node': 25.3.0 + '@jridgewell/gen-mapping@0.3.13': dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -5071,6 +5956,187 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) + '@napi-rs/cli@3.5.1(@emnapi/runtime@1.8.1)(@types/node@25.3.0)': + dependencies: + '@inquirer/prompts': 8.2.1(@types/node@25.3.0) + '@napi-rs/cross-toolchain': 1.0.3 + '@napi-rs/wasm-tools': 1.0.1 + '@octokit/rest': 22.0.1 + clipanion: 4.0.0-rc.4(typanion@3.14.0) + colorette: 2.0.20 + emnapi: 1.8.1 + es-toolkit: 1.44.0 + js-yaml: 4.1.1 + obug: 2.1.1 + semver: 7.7.4 + typanion: 3.14.0 + optionalDependencies: + '@emnapi/runtime': 1.8.1 + transitivePeerDependencies: + - '@napi-rs/cross-toolchain-arm64-target-aarch64' + - '@napi-rs/cross-toolchain-arm64-target-armv7' + - '@napi-rs/cross-toolchain-arm64-target-ppc64le' + - '@napi-rs/cross-toolchain-arm64-target-s390x' + - '@napi-rs/cross-toolchain-arm64-target-x86_64' + - '@napi-rs/cross-toolchain-x64-target-aarch64' + - '@napi-rs/cross-toolchain-x64-target-armv7' + - '@napi-rs/cross-toolchain-x64-target-ppc64le' + - '@napi-rs/cross-toolchain-x64-target-s390x' + - '@napi-rs/cross-toolchain-x64-target-x86_64' + - '@types/node' + - node-addon-api + - supports-color + + '@napi-rs/cross-toolchain@1.0.3': + dependencies: + '@napi-rs/lzma': 1.4.5 + '@napi-rs/tar': 1.1.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@napi-rs/lzma-android-arm-eabi@1.4.5': + optional: true + + '@napi-rs/lzma-android-arm64@1.4.5': + optional: true + + '@napi-rs/lzma-darwin-arm64@1.4.5': + optional: true + + '@napi-rs/lzma-darwin-x64@1.4.5': + optional: true + + '@napi-rs/lzma-freebsd-x64@1.4.5': + optional: true + + '@napi-rs/lzma-linux-arm-gnueabihf@1.4.5': + optional: true + + '@napi-rs/lzma-linux-arm64-gnu@1.4.5': + optional: true + + '@napi-rs/lzma-linux-arm64-musl@1.4.5': + optional: true + + '@napi-rs/lzma-linux-ppc64-gnu@1.4.5': + optional: true + + '@napi-rs/lzma-linux-riscv64-gnu@1.4.5': + optional: true + + '@napi-rs/lzma-linux-s390x-gnu@1.4.5': + optional: true + + '@napi-rs/lzma-linux-x64-gnu@1.4.5': + optional: true + + '@napi-rs/lzma-linux-x64-musl@1.4.5': + optional: true + + '@napi-rs/lzma-wasm32-wasi@1.4.5': + dependencies: + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@napi-rs/lzma-win32-arm64-msvc@1.4.5': + optional: true + + '@napi-rs/lzma-win32-ia32-msvc@1.4.5': + optional: true + + '@napi-rs/lzma-win32-x64-msvc@1.4.5': + optional: true + + '@napi-rs/lzma@1.4.5': + optionalDependencies: + '@napi-rs/lzma-android-arm-eabi': 1.4.5 + '@napi-rs/lzma-android-arm64': 1.4.5 + '@napi-rs/lzma-darwin-arm64': 1.4.5 + '@napi-rs/lzma-darwin-x64': 1.4.5 + '@napi-rs/lzma-freebsd-x64': 1.4.5 + '@napi-rs/lzma-linux-arm-gnueabihf': 1.4.5 + '@napi-rs/lzma-linux-arm64-gnu': 1.4.5 + '@napi-rs/lzma-linux-arm64-musl': 1.4.5 + '@napi-rs/lzma-linux-ppc64-gnu': 1.4.5 + '@napi-rs/lzma-linux-riscv64-gnu': 1.4.5 + '@napi-rs/lzma-linux-s390x-gnu': 1.4.5 + '@napi-rs/lzma-linux-x64-gnu': 1.4.5 + '@napi-rs/lzma-linux-x64-musl': 1.4.5 + '@napi-rs/lzma-wasm32-wasi': 1.4.5 + '@napi-rs/lzma-win32-arm64-msvc': 1.4.5 + '@napi-rs/lzma-win32-ia32-msvc': 1.4.5 + '@napi-rs/lzma-win32-x64-msvc': 1.4.5 + + '@napi-rs/tar-android-arm-eabi@1.1.0': + optional: true + + '@napi-rs/tar-android-arm64@1.1.0': + optional: true + + '@napi-rs/tar-darwin-arm64@1.1.0': + optional: true + + '@napi-rs/tar-darwin-x64@1.1.0': + optional: true + + '@napi-rs/tar-freebsd-x64@1.1.0': + optional: true + + '@napi-rs/tar-linux-arm-gnueabihf@1.1.0': + optional: true + + '@napi-rs/tar-linux-arm64-gnu@1.1.0': + optional: true + + '@napi-rs/tar-linux-arm64-musl@1.1.0': + optional: true + + '@napi-rs/tar-linux-ppc64-gnu@1.1.0': + optional: true + + '@napi-rs/tar-linux-s390x-gnu@1.1.0': + optional: true + + '@napi-rs/tar-linux-x64-gnu@1.1.0': + optional: true + + '@napi-rs/tar-linux-x64-musl@1.1.0': + optional: true + + '@napi-rs/tar-wasm32-wasi@1.1.0': + dependencies: + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@napi-rs/tar-win32-arm64-msvc@1.1.0': + optional: true + + '@napi-rs/tar-win32-ia32-msvc@1.1.0': + optional: true + + '@napi-rs/tar-win32-x64-msvc@1.1.0': + optional: true + + '@napi-rs/tar@1.1.0': + optionalDependencies: + '@napi-rs/tar-android-arm-eabi': 1.1.0 + '@napi-rs/tar-android-arm64': 1.1.0 + '@napi-rs/tar-darwin-arm64': 1.1.0 + '@napi-rs/tar-darwin-x64': 1.1.0 + '@napi-rs/tar-freebsd-x64': 1.1.0 + '@napi-rs/tar-linux-arm-gnueabihf': 1.1.0 + '@napi-rs/tar-linux-arm64-gnu': 1.1.0 + '@napi-rs/tar-linux-arm64-musl': 1.1.0 + '@napi-rs/tar-linux-ppc64-gnu': 1.1.0 + '@napi-rs/tar-linux-s390x-gnu': 1.1.0 + '@napi-rs/tar-linux-x64-gnu': 1.1.0 + '@napi-rs/tar-linux-x64-musl': 1.1.0 + '@napi-rs/tar-wasm32-wasi': 1.1.0 + '@napi-rs/tar-win32-arm64-msvc': 1.1.0 + '@napi-rs/tar-win32-ia32-msvc': 1.1.0 + '@napi-rs/tar-win32-x64-msvc': 1.1.0 + '@napi-rs/wasm-runtime@1.1.1': dependencies: '@emnapi/core': 1.8.1 @@ -5078,6 +6144,63 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true + '@napi-rs/wasm-tools-android-arm-eabi@1.0.1': + optional: true + + '@napi-rs/wasm-tools-android-arm64@1.0.1': + optional: true + + '@napi-rs/wasm-tools-darwin-arm64@1.0.1': + optional: true + + '@napi-rs/wasm-tools-darwin-x64@1.0.1': + optional: true + + '@napi-rs/wasm-tools-freebsd-x64@1.0.1': + optional: true + + '@napi-rs/wasm-tools-linux-arm64-gnu@1.0.1': + optional: true + + '@napi-rs/wasm-tools-linux-arm64-musl@1.0.1': + optional: true + + '@napi-rs/wasm-tools-linux-x64-gnu@1.0.1': + optional: true + + '@napi-rs/wasm-tools-linux-x64-musl@1.0.1': + optional: true + + '@napi-rs/wasm-tools-wasm32-wasi@1.0.1': + dependencies: + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@napi-rs/wasm-tools-win32-arm64-msvc@1.0.1': + optional: true + + '@napi-rs/wasm-tools-win32-ia32-msvc@1.0.1': + optional: true + + '@napi-rs/wasm-tools-win32-x64-msvc@1.0.1': + optional: true + + '@napi-rs/wasm-tools@1.0.1': + optionalDependencies: + '@napi-rs/wasm-tools-android-arm-eabi': 1.0.1 + '@napi-rs/wasm-tools-android-arm64': 1.0.1 + '@napi-rs/wasm-tools-darwin-arm64': 1.0.1 + '@napi-rs/wasm-tools-darwin-x64': 1.0.1 + '@napi-rs/wasm-tools-freebsd-x64': 1.0.1 + '@napi-rs/wasm-tools-linux-arm64-gnu': 1.0.1 + '@napi-rs/wasm-tools-linux-arm64-musl': 1.0.1 + '@napi-rs/wasm-tools-linux-x64-gnu': 1.0.1 + '@napi-rs/wasm-tools-linux-x64-musl': 1.0.1 + '@napi-rs/wasm-tools-wasm32-wasi': 1.0.1 + '@napi-rs/wasm-tools-win32-arm64-msvc': 1.0.1 + '@napi-rs/wasm-tools-win32-ia32-msvc': 1.0.1 + '@napi-rs/wasm-tools-win32-x64-msvc': 1.0.1 + '@next/env@16.1.6': {} '@next/eslint-plugin-next@16.1.0': @@ -5126,6 +6249,69 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.20.1 + '@octokit/auth-token@6.0.0': {} + + '@octokit/core@7.0.6': + dependencies: + '@octokit/auth-token': 6.0.0 + '@octokit/graphql': 9.0.3 + '@octokit/request': 10.0.8 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + before-after-hook: 4.0.0 + universal-user-agent: 7.0.3 + + '@octokit/endpoint@11.0.3': + dependencies: + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + + '@octokit/graphql@9.0.3': + dependencies: + '@octokit/request': 10.0.8 + '@octokit/types': 16.0.0 + universal-user-agent: 7.0.3 + + '@octokit/openapi-types@27.0.0': {} + + '@octokit/plugin-paginate-rest@14.0.0(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + '@octokit/types': 16.0.0 + + '@octokit/plugin-request-log@6.0.0(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + + '@octokit/plugin-rest-endpoint-methods@17.0.0(@octokit/core@7.0.6)': + dependencies: + '@octokit/core': 7.0.6 + '@octokit/types': 16.0.0 + + '@octokit/request-error@7.1.0': + dependencies: + '@octokit/types': 16.0.0 + + '@octokit/request@10.0.8': + dependencies: + '@octokit/endpoint': 11.0.3 + '@octokit/request-error': 7.1.0 + '@octokit/types': 16.0.0 + fast-content-type-parse: 3.0.0 + json-with-bigint: 3.5.3 + universal-user-agent: 7.0.3 + + '@octokit/rest@22.0.1': + dependencies: + '@octokit/core': 7.0.6 + '@octokit/plugin-paginate-rest': 14.0.0(@octokit/core@7.0.6) + '@octokit/plugin-request-log': 6.0.0(@octokit/core@7.0.6) + '@octokit/plugin-rest-endpoint-methods': 17.0.0(@octokit/core@7.0.6) + + '@octokit/types@16.0.0': + dependencies: + '@octokit/openapi-types': 27.0.0 + '@oxc-project/types@0.112.0': {} '@pkgr/core@0.2.9': {} @@ -5189,79 +6375,79 @@ snapshots: '@rolldown/pluginutils@1.0.0-rc.3': {} - '@rollup/rollup-android-arm-eabi@4.58.0': + '@rollup/rollup-android-arm-eabi@4.59.0': optional: true - '@rollup/rollup-android-arm64@4.58.0': + '@rollup/rollup-android-arm64@4.59.0': optional: true - '@rollup/rollup-darwin-arm64@4.58.0': + '@rollup/rollup-darwin-arm64@4.59.0': optional: true - '@rollup/rollup-darwin-x64@4.58.0': + '@rollup/rollup-darwin-x64@4.59.0': optional: true - '@rollup/rollup-freebsd-arm64@4.58.0': + '@rollup/rollup-freebsd-arm64@4.59.0': optional: true - '@rollup/rollup-freebsd-x64@4.58.0': + '@rollup/rollup-freebsd-x64@4.59.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.58.0': + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.58.0': + '@rollup/rollup-linux-arm-musleabihf@4.59.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.58.0': + '@rollup/rollup-linux-arm64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.58.0': + '@rollup/rollup-linux-arm64-musl@4.59.0': optional: true - '@rollup/rollup-linux-loong64-gnu@4.58.0': + '@rollup/rollup-linux-loong64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-loong64-musl@4.58.0': + '@rollup/rollup-linux-loong64-musl@4.59.0': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.58.0': + '@rollup/rollup-linux-ppc64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-ppc64-musl@4.58.0': + '@rollup/rollup-linux-ppc64-musl@4.59.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.58.0': + '@rollup/rollup-linux-riscv64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.58.0': + '@rollup/rollup-linux-riscv64-musl@4.59.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.58.0': + '@rollup/rollup-linux-s390x-gnu@4.59.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.58.0': + '@rollup/rollup-linux-x64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-x64-musl@4.58.0': + '@rollup/rollup-linux-x64-musl@4.59.0': optional: true - '@rollup/rollup-openbsd-x64@4.58.0': + '@rollup/rollup-openbsd-x64@4.59.0': optional: true - '@rollup/rollup-openharmony-arm64@4.58.0': + '@rollup/rollup-openharmony-arm64@4.59.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.58.0': + '@rollup/rollup-win32-arm64-msvc@4.59.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.58.0': + '@rollup/rollup-win32-ia32-msvc@4.59.0': optional: true - '@rollup/rollup-win32-x64-gnu@4.58.0': + '@rollup/rollup-win32-x64-gnu@4.59.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.58.0': + '@rollup/rollup-win32-x64-msvc@4.59.0': optional: true '@sindresorhus/base62@1.0.0': {} @@ -5354,11 +6540,11 @@ snapshots: '@tanstack/history@1.161.4': {} - '@tanstack/react-router@1.162.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + '@tanstack/react-router@1.162.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': dependencies: '@tanstack/history': 1.161.4 '@tanstack/react-store': 0.9.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@tanstack/router-core': 1.162.1 + '@tanstack/router-core': 1.162.2 isbot: 5.1.35 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) @@ -5372,7 +6558,7 @@ snapshots: react-dom: 19.2.4(react@19.2.4) use-sync-external-store: 1.6.0(react@19.2.4) - '@tanstack/router-core@1.162.1': + '@tanstack/router-core@1.162.2': dependencies: '@tanstack/history': 1.161.4 '@tanstack/store': 0.9.1 @@ -5382,9 +6568,9 @@ snapshots: tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - '@tanstack/router-generator@1.162.1': + '@tanstack/router-generator@1.162.2': dependencies: - '@tanstack/router-core': 1.162.1 + '@tanstack/router-core': 1.162.2 '@tanstack/router-utils': 1.161.4 '@tanstack/virtual-file-routes': 1.161.4 prettier: 3.8.1 @@ -5395,7 +6581,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.162.1(@tanstack/react-router@1.162.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': + '@tanstack/router-plugin@1.162.2(@tanstack/react-router@1.162.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4))(vite@7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) @@ -5403,15 +6589,15 @@ snapshots: '@babel/template': 7.28.6 '@babel/traverse': 7.29.0 '@babel/types': 7.29.0 - '@tanstack/router-core': 1.162.1 - '@tanstack/router-generator': 1.162.1 + '@tanstack/router-core': 1.162.2 + '@tanstack/router-generator': 1.162.2 '@tanstack/router-utils': 1.161.4 '@tanstack/virtual-file-routes': 1.161.4 chokidar: 3.6.0 unplugin: 2.3.11 zod: 3.25.76 optionalDependencies: - '@tanstack/react-router': 1.162.1(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@tanstack/react-router': 1.162.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4) vite: 7.3.1(@types/node@25.3.0)(jiti@2.6.1)(lightningcss@1.31.1)(tsx@4.21.0)(yaml@2.8.2) transitivePeerDependencies: - supports-color @@ -5767,7 +6953,7 @@ snapshots: '@typescript-eslint/types': 8.50.0 '@typescript-eslint/visitor-keys': 8.50.0 debug: 4.4.3 - minimatch: 9.0.5 + minimatch: 9.0.6 semver: 7.7.4 tinyglobby: 0.2.15 ts-api-utils: 2.4.0(typescript@5.9.3) @@ -5782,7 +6968,7 @@ snapshots: '@typescript-eslint/types': 8.56.0 '@typescript-eslint/visitor-keys': 8.56.0 debug: 4.4.3 - minimatch: 9.0.5 + minimatch: 9.0.6 semver: 7.7.4 tinyglobby: 0.2.15 ts-api-utils: 2.4.0(typescript@5.9.3) @@ -6022,6 +7208,8 @@ snapshots: are-docs-informative@0.0.2: {} + argparse@2.0.1: {} + assertion-error@2.0.1: {} ast-kit@3.0.0-beta.1: @@ -6051,25 +7239,21 @@ snapshots: bail@2.0.2: {} - balanced-match@1.0.2: {} - - balanced-match@4.0.3: {} + balanced-match@4.0.4: {} baseline-browser-mapping@2.10.0: {} + before-after-hook@4.0.0: {} + binary-extensions@2.3.0: {} birpc@4.0.0: {} boolbase@1.0.0: {} - brace-expansion@2.0.2: + brace-expansion@5.0.3: dependencies: - balanced-match: 1.0.2 - - brace-expansion@5.0.2: - dependencies: - balanced-match: 4.0.3 + balanced-match: 4.0.4 braces@3.0.3: dependencies: @@ -6078,7 +7262,7 @@ snapshots: browserslist@4.28.1: dependencies: baseline-browser-mapping: 2.10.0 - caniuse-lite: 1.0.30001770 + caniuse-lite: 1.0.30001772 electron-to-chromium: 1.5.302 node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) @@ -6087,7 +7271,7 @@ snapshots: cac@6.7.14: {} - caniuse-lite@1.0.30001770: {} + caniuse-lite@1.0.30001772: {} ccount@2.0.1: {} @@ -6103,6 +7287,8 @@ snapshots: character-reference-invalid@2.0.1: {} + chardet@2.1.1: {} + chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -6127,10 +7313,18 @@ snapshots: dependencies: escape-string-regexp: 1.0.5 + cli-width@4.1.0: {} + client-only@0.0.1: {} + clipanion@4.0.0-rc.4(typanion@3.14.0): + dependencies: + typanion: 3.14.0 + clsx@2.1.1: {} + colorette@2.0.20: {} + comment-parser@1.4.1: {} comment-parser@1.4.5: {} @@ -6234,6 +7428,8 @@ snapshots: electron-to-chromium@1.5.302: {} + emnapi@1.8.1: {} + empathic@2.0.0: {} enhanced-resolve@5.19.0: @@ -6312,7 +7508,7 @@ snapshots: eslint: 10.0.1(jiti@2.6.1) prettier-linter-helpers: 1.0.1 - eslint-json-compat-utils@0.2.1(eslint@10.0.1(jiti@2.6.1))(jsonc-eslint-parser@2.4.2): + eslint-json-compat-utils@0.2.2(eslint@10.0.1(jiti@2.6.1))(jsonc-eslint-parser@2.4.2): dependencies: eslint: 10.0.1(jiti@2.6.1) esquery: 1.7.0 @@ -6383,7 +7579,7 @@ snapshots: diff-sequences: 27.5.1 eslint: 10.0.1(jiti@2.6.1) eslint-compat-utils: 0.6.5(eslint@10.0.1(jiti@2.6.1)) - eslint-json-compat-utils: 0.2.1(eslint@10.0.1(jiti@2.6.1))(jsonc-eslint-parser@2.4.2) + eslint-json-compat-utils: 0.2.2(eslint@10.0.1(jiti@2.6.1))(jsonc-eslint-parser@2.4.2) espree: 10.4.0 graphemer: 1.4.0 jsonc-eslint-parser: 2.4.2 @@ -6644,6 +7840,8 @@ snapshots: dependencies: pure-rand: 7.0.1 + fast-content-type-parse@3.0.0: {} + fast-deep-equal@3.1.3: {} fast-diff@1.3.0: {} @@ -6668,6 +7866,16 @@ snapshots: fast-levenshtein@2.0.6: {} + fast-string-truncated-width@3.0.3: {} + + fast-string-width@3.0.2: + dependencies: + fast-string-truncated-width: 3.0.3 + + fast-wrap-ansi@0.2.0: + dependencies: + fast-string-width: 3.0.2 + fastq@1.20.1: dependencies: reusify: 1.1.0 @@ -6747,6 +7955,10 @@ snapshots: html-escaper@2.0.2: {} + iconv-lite@0.7.2: + dependencies: + safer-buffer: 2.1.2 + ignore@5.3.2: {} ignore@7.0.5: {} @@ -6819,6 +8031,10 @@ snapshots: js-tokens@4.0.0: {} + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + jsdoc-type-pratt-parser@4.8.0: {} jsdoc-type-pratt-parser@7.0.0: {} @@ -6833,6 +8049,8 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + json-with-bigint@3.5.3: {} + json5@2.2.3: {} jsonc-eslint-parser@2.4.2: @@ -7403,11 +8621,11 @@ snapshots: minimatch@10.2.2: dependencies: - brace-expansion: 5.0.2 + brace-expansion: 5.0.3 - minimatch@9.0.5: + minimatch@9.0.6: dependencies: - brace-expansion: 2.0.2 + brace-expansion: 5.0.3 mlly@1.8.0: dependencies: @@ -7423,6 +8641,8 @@ snapshots: ms@2.1.3: {} + mute-stream@3.0.0: {} + nanoid@3.3.11: {} natural-compare@1.4.0: {} @@ -7434,7 +8654,7 @@ snapshots: '@next/env': 16.1.6 '@swc/helpers': 0.5.15 baseline-browser-mapping: 2.10.0 - caniuse-lite: 1.0.30001770 + caniuse-lite: 1.0.30001772 postcss: 8.4.31 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) @@ -7748,41 +8968,43 @@ snapshots: '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.3 '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.3 - rollup@4.58.0: + rollup@4.59.0: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.58.0 - '@rollup/rollup-android-arm64': 4.58.0 - '@rollup/rollup-darwin-arm64': 4.58.0 - '@rollup/rollup-darwin-x64': 4.58.0 - '@rollup/rollup-freebsd-arm64': 4.58.0 - '@rollup/rollup-freebsd-x64': 4.58.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.58.0 - '@rollup/rollup-linux-arm-musleabihf': 4.58.0 - '@rollup/rollup-linux-arm64-gnu': 4.58.0 - '@rollup/rollup-linux-arm64-musl': 4.58.0 - '@rollup/rollup-linux-loong64-gnu': 4.58.0 - '@rollup/rollup-linux-loong64-musl': 4.58.0 - '@rollup/rollup-linux-ppc64-gnu': 4.58.0 - '@rollup/rollup-linux-ppc64-musl': 4.58.0 - '@rollup/rollup-linux-riscv64-gnu': 4.58.0 - '@rollup/rollup-linux-riscv64-musl': 4.58.0 - '@rollup/rollup-linux-s390x-gnu': 4.58.0 - '@rollup/rollup-linux-x64-gnu': 4.58.0 - '@rollup/rollup-linux-x64-musl': 4.58.0 - '@rollup/rollup-openbsd-x64': 4.58.0 - '@rollup/rollup-openharmony-arm64': 4.58.0 - '@rollup/rollup-win32-arm64-msvc': 4.58.0 - '@rollup/rollup-win32-ia32-msvc': 4.58.0 - '@rollup/rollup-win32-x64-gnu': 4.58.0 - '@rollup/rollup-win32-x64-msvc': 4.58.0 + '@rollup/rollup-android-arm-eabi': 4.59.0 + '@rollup/rollup-android-arm64': 4.59.0 + '@rollup/rollup-darwin-arm64': 4.59.0 + '@rollup/rollup-darwin-x64': 4.59.0 + '@rollup/rollup-freebsd-arm64': 4.59.0 + '@rollup/rollup-freebsd-x64': 4.59.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.59.0 + '@rollup/rollup-linux-arm-musleabihf': 4.59.0 + '@rollup/rollup-linux-arm64-gnu': 4.59.0 + '@rollup/rollup-linux-arm64-musl': 4.59.0 + '@rollup/rollup-linux-loong64-gnu': 4.59.0 + '@rollup/rollup-linux-loong64-musl': 4.59.0 + '@rollup/rollup-linux-ppc64-gnu': 4.59.0 + '@rollup/rollup-linux-ppc64-musl': 4.59.0 + '@rollup/rollup-linux-riscv64-gnu': 4.59.0 + '@rollup/rollup-linux-riscv64-musl': 4.59.0 + '@rollup/rollup-linux-s390x-gnu': 4.59.0 + '@rollup/rollup-linux-x64-gnu': 4.59.0 + '@rollup/rollup-linux-x64-musl': 4.59.0 + '@rollup/rollup-openbsd-x64': 4.59.0 + '@rollup/rollup-openharmony-arm64': 4.59.0 + '@rollup/rollup-win32-arm64-msvc': 4.59.0 + '@rollup/rollup-win32-ia32-msvc': 4.59.0 + '@rollup/rollup-win32-x64-gnu': 4.59.0 + '@rollup/rollup-win32-x64-msvc': 4.59.0 fsevents: 2.3.3 run-parallel@1.2.0: dependencies: queue-microtask: 1.2.3 + safer-buffer@2.1.2: {} + scheduler@0.27.0: {} scslre@0.3.0: @@ -7843,6 +9065,8 @@ snapshots: siginfo@2.0.0: {} + signal-exit@4.1.0: {} + simple-git-hooks@2.13.1: {} sisteransi@1.0.5: {} @@ -8005,6 +9229,8 @@ snapshots: tw-animate-css@1.4.0: {} + typanion@3.14.0: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -8072,6 +9298,8 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 + universal-user-agent@7.0.3: {} + universalify@2.0.1: {} unplugin@2.3.11: @@ -8136,7 +9364,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.58.0 + rollup: 4.59.0 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 25.3.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 9c013763..eb31ff91 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,6 +1,13 @@ packages: - cli + - cli/npm/* - packages/* + - libraries/* + - libraries/logger/npm/* + - libraries/md-compiler/npm/* + - libraries/config/npm/* + - libraries/init-bundle/npm/* + - libraries/input-plugins/npm/* - gui - doc @@ -10,8 +17,8 @@ catalog: '@monaco-editor/react': ^4.7.0 '@next/mdx': ^16.1.6 '@tailwindcss/vite': ^4.2.0 - '@tanstack/react-router': ^1.162.1 - '@tanstack/router-plugin': ^1.162.1 + '@tanstack/react-router': ^1.162.2 + '@tanstack/router-plugin': ^1.162.2 '@tauri-apps/api': ^2.10.1 '@tauri-apps/cli': ^2.10.0 '@tauri-apps/plugin-shell': ^2.3.5 diff --git a/scripts/build-native.ts b/scripts/build-native.ts new file mode 100644 index 00000000..4b30953a --- /dev/null +++ b/scripts/build-native.ts @@ -0,0 +1,76 @@ +#!/usr/bin/env tsx +import {execFileSync, execSync} from 'node:child_process' +import {existsSync} from 'node:fs' +import {homedir} from 'node:os' +import {dirname, join, resolve} from 'node:path' +import process from 'node:process' +import {fileURLToPath} from 'node:url' + +const LIBRARIES = ['logger', 'md-compiler', 'config', 'init-bundle'] as const + +const __dirname = import.meta.dirname ?? dirname(fileURLToPath(import.meta.url)) +const root = resolve(__dirname, '..') + +function findCargo(): string | null { + const candidates: string[] = [ + process.env['CARGO'] ?? '', + join(homedir(), '.cargo', 'bin', 'cargo'), + join(homedir(), '.cargo', 'bin', 'cargo.exe'), + 'cargo', + ].filter(Boolean) + + for (const c of candidates) { + try { + if (c === 'cargo') { + execFileSync(c, ['--version'], {stdio: 'ignore'}) + return c + } + if (existsSync(c)) return c + } catch {} + } + return null +} + +const cargo = findCargo() +if (cargo == null) { + console.warn('[build-native] cargo not found, skipping native build') + console.warn('[build-native] Install Rust: https://rustup.rs') + process.exit(0) +} + +console.log(`[build-native] Using cargo: ${cargo}`) + +const cargoDir = dirname(cargo) +const envWithCargo = { + ...process.env, + CARGO: cargo, + PATH: `${cargoDir}${process.platform === 'win32' ? ';' : ':'}${process.env['PATH'] ?? ''}`, +} + +let failed = false +for (const lib of LIBRARIES) { + const libDir = join(root, 'libraries', lib) + console.log(`[build-native] Building ${lib}...`) + try { + execSync( + 'npx napi build --platform --release --output-dir dist -- --features napi', + {stdio: 'inherit', cwd: libDir, env: envWithCargo}, + ) + } catch { + console.error(`[build-native] ${lib}: build failed`) + failed = true + } +} + +if (failed) { + console.warn('[build-native] Some libraries failed to build, skipping copy') + console.warn('[build-native] Ensure Rust toolchain + linker are available, then run: pnpm run build:native') + process.exit(0) +} + +console.log('[build-native] All libraries built, copying .node files...') +try { + execSync('tsx scripts/copy-napi.ts', {stdio: 'inherit', cwd: root}) +} catch { + console.warn('[build-native] copy-napi failed, .node files may not be in place') +} diff --git a/scripts/cargo-test.ts b/scripts/cargo-test.ts new file mode 100644 index 00000000..aeb2c7a4 --- /dev/null +++ b/scripts/cargo-test.ts @@ -0,0 +1,40 @@ +#!/usr/bin/env tsx +import {execFileSync} from 'node:child_process' +import {existsSync} from 'node:fs' +import {homedir} from 'node:os' +import {join} from 'node:path' + +const candidates: string[] = [ + process.env['CARGO'] ?? '', + join(homedir(), '.cargo', 'bin', 'cargo'), + join(homedir(), '.cargo', 'bin', 'cargo.exe'), + 'cargo' +].filter(Boolean) + +let cargoPath: string | null = null +for (const c of candidates) { + if (c === 'cargo' || existsSync(c)) { + cargoPath = c + break + } +} + +if (cargoPath == null) { + console.error('[cargo-test] cargo not found. Install Rust: https://rustup.rs') + process.exit(1) +} + +const args = process.argv.slice(2) +try { + execFileSync(cargoPath, ['test', ...args], {stdio: 'inherit', cwd: process.cwd()}) +} +catch (err) { + const status = err instanceof Error && 'status' in err + ? ((err as NodeJS.ErrnoException & {status?: number}).status ?? 1) + : 1 + if (status === 101) { + console.error('[cargo-test] Rust build failed (likely missing linker/toolchain). Install Visual Studio Build Tools: https://aka.ms/vs/17/release/vs_BuildTools.exe') + process.exit(1) + } + process.exit(status) +} diff --git a/scripts/copy-napi.ts b/scripts/copy-napi.ts new file mode 100644 index 00000000..f7616dec --- /dev/null +++ b/scripts/copy-napi.ts @@ -0,0 +1,57 @@ +#!/usr/bin/env tsx +import {cpSync, existsSync, mkdirSync, readdirSync} from 'node:fs' +import {join, resolve} from 'node:path' +import process from 'node:process' + +const LIBRARIES = ['logger', 'md-compiler', 'config', 'init-bundle'] as const + +const PLATFORM_MAP: Record = { + 'win32-x64': 'win32-x64-msvc', + 'linux-x64': 'linux-x64-gnu', + 'linux-arm64': 'linux-arm64-gnu', + 'darwin-arm64': 'darwin-arm64', + 'darwin-x64': 'darwin-x64', +} + +const root = resolve(import.meta.dirname, '..') +const suffix = PLATFORM_MAP[`${process.platform}-${process.arch}`] + +if (suffix == null) { + console.warn(`[copy-napi] Unsupported platform: ${process.platform}-${process.arch}, skipping`) + process.exit(0) +} + +const targetDir = join(root, 'cli', 'npm', suffix) +mkdirSync(targetDir, {recursive: true}) + +let copied = 0 + +for (const lib of LIBRARIES) { + const libDist = join(root, 'libraries', lib, 'dist') + if (!existsSync(libDist)) { + console.warn(`[copy-napi] ${lib}: dist/ not found, skipping (run napi build first)`) + continue + } + const nodeFiles = readdirSync(libDist).filter(f => f.endsWith('.node')) + if (nodeFiles.length === 0) { + console.warn(`[copy-napi] ${lib}: no .node files in dist/, skipping (run napi build first)`) + continue + } + for (const file of nodeFiles) { + const src = join(libDist, file) + const dst = join(targetDir, file) + cpSync(src, dst) + console.log(`[copy-napi] ${lib}: ${file} → cli/npm/${suffix}/`) + copied++ + } +} + +if (copied > 0) { + console.log(`[copy-napi] Done: ${copied} file(s) copied to cli/npm/${suffix}/`) +} else { + console.warn('[copy-napi] No .node files found. Build napi first:') + console.warn(' pnpm -F @truenine/logger run build:native') + console.warn(' pnpm -F @truenine/md-compiler run build:native') + console.warn(' pnpm -F @truenine/config run build:native') + console.warn(' pnpm -F @truenine/init-bundle run build:native') +} diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json new file mode 100644 index 00000000..bad3fd78 --- /dev/null +++ b/scripts/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "types": ["node"], + "strict": true, + "noEmit": true, + "skipLibCheck": true + }, + "include": ["./**/*.ts"] +} diff --git a/turbo.json b/turbo.json index 7f0c112e..ebd0ee8d 100644 --- a/turbo.json +++ b/turbo.json @@ -3,7 +3,7 @@ "tasks": { "build": { "dependsOn": ["^build"], - "outputs": ["dist/**"] + "outputs": ["dist/**", "*.node"] }, "test": { "dependsOn": ["build"],