From 0b1bd4875058934255abf3adf8c144232fca6a00 Mon Sep 17 00:00:00 2001 From: Caio Pizzol Date: Sun, 1 Feb 2026 18:13:08 -0300 Subject: [PATCH] feat: new cli app --- .github/workflows/release.yml | 9 ++ apps/cli/.gitignore | 14 ++ apps/cli/.releaserc.cjs | 31 +++++ apps/cli/README.md | 71 ++++++++++ apps/cli/package.json | 33 +++++ apps/cli/src/__tests__/commands.test.ts | 79 +++++++++++ apps/cli/src/commands/read.ts | 20 +++ apps/cli/src/commands/replace.ts | 49 +++++++ apps/cli/src/commands/search.ts | 73 ++++++++++ apps/cli/src/index.ts | 175 ++++++++++++++++++++++++ apps/cli/src/lib/editor.ts | 85 ++++++++++++ apps/cli/tsconfig.json | 11 ++ eslint.config.mjs | 1 + lefthook.yml | 8 ++ pnpm-lock.yaml | 142 +++++++++++++++++-- pnpm-workspace.yaml | 1 + 16 files changed, 789 insertions(+), 13 deletions(-) create mode 100644 apps/cli/.gitignore create mode 100644 apps/cli/.releaserc.cjs create mode 100644 apps/cli/README.md create mode 100644 apps/cli/package.json create mode 100644 apps/cli/src/__tests__/commands.test.ts create mode 100644 apps/cli/src/commands/read.ts create mode 100644 apps/cli/src/commands/replace.ts create mode 100644 apps/cli/src/commands/search.ts create mode 100644 apps/cli/src/index.ts create mode 100644 apps/cli/src/lib/editor.ts create mode 100644 apps/cli/tsconfig.json diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0c963d5d6d..63e63bd6db 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -6,6 +6,7 @@ on: - main paths: - 'packages/**' + - 'apps/**' - '!**.md' workflow_dispatch: @@ -90,6 +91,14 @@ jobs: working-directory: packages/esign run: pnpx semantic-release + - name: Release cli + env: + GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + working-directory: apps/cli + run: pnpx semantic-release + # Sync stable back to main after stable release # - name: Sync stable to main # if: github.ref == 'refs/heads/stable' diff --git a/apps/cli/.gitignore b/apps/cli/.gitignore new file mode 100644 index 0000000000..b22604b141 --- /dev/null +++ b/apps/cli/.gitignore @@ -0,0 +1,14 @@ +# dependencies (bun install) +node_modules + +# output +dist + +# env files +.env + +# caches +*.tsbuildinfo + +# Finder (MacOS) folder config +.DS_Store diff --git a/apps/cli/.releaserc.cjs b/apps/cli/.releaserc.cjs new file mode 100644 index 0000000000..23a298a16a --- /dev/null +++ b/apps/cli/.releaserc.cjs @@ -0,0 +1,31 @@ +/* eslint-env node */ +const branch = process.env.GITHUB_REF_NAME || process.env.CI_COMMIT_BRANCH; + +const config = { + branches: [ + { name: 'stable', channel: 'latest' }, + { name: 'main', prerelease: 'next', channel: 'next' }, + ], + tagFormat: 'cli-v${version}', + plugins: [ + '@semantic-release/commit-analyzer', + '@semantic-release/release-notes-generator', + ['@semantic-release/npm', { npmPublish: true }], + ], +}; + +const isPrerelease = config.branches.some((b) => typeof b === 'object' && b.name === branch && b.prerelease); + +if (!isPrerelease) { + config.plugins.push([ + '@semantic-release/git', + { + assets: ['package.json'], + message: 'chore(cli): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}', + }, + ]); +} + +config.plugins.push('@semantic-release/github'); + +module.exports = config; diff --git a/apps/cli/README.md b/apps/cli/README.md new file mode 100644 index 0000000000..c03574b780 --- /dev/null +++ b/apps/cli/README.md @@ -0,0 +1,71 @@ +# @superdoc-dev/cli + +The command-line interface for [SuperDoc](https://superdoc.dev) — DOCX editing in your terminal. + +```bash +npx @superdoc-dev/cli search "CONFIDENTIAL" ./legal/*.docx +``` + +## Commands + +| Command | Status | Description | +|---------|--------|-------------| +| `search` | Available | Find text across documents | +| `replace` | Available | Find and replace text | +| `replace --track` | Coming soon | Replace with track changes | +| `read` | Available | Extract plain text | +| `diff` | Coming soon | Compare two documents | +| `convert` | Coming soon | DOCX ↔ HTML ↔ Markdown | +| `comments` | Coming soon | List, add, resolve comments | +| `accept` | Coming soon | Accept/reject track changes | + +Powered by the SuperDoc document engine. Bulk operations, glob patterns, JSON output. + +## Install + +```bash +npm install -g @superdoc-dev/cli +``` + +Or run directly: + +```bash +npx @superdoc-dev/cli +``` + +## Usage + +```bash +# Search across documents +superdoc search "indemnification" ./contracts/*.docx + +# Find and replace +superdoc replace "ACME Corp" "Globex Inc" ./merger/*.docx + +# Extract text +superdoc read ./proposal.docx + +# JSON output for scripting +superdoc search "Article 7" ./**/*.docx --json +``` + +## Options + +| Flag | Description | +|------|-------------| +| `--json` | Machine-readable output | +| `--help` | Show help | + +## AI Integration + +Works with AI coding assistants. Add a skill file so Claude Code, Cursor, etc. know to use `superdoc` for DOCX operations instead of python-docx. + +*Skill setup guide coming soon.* + +## Part of SuperDoc + +This CLI is part of the [SuperDoc](https://github.com/superdoc-dev/superdoc) project — an open source document editor bringing Microsoft Word to the web. Use it alongside the editor, or standalone for document automation. + +## License + +AGPL-3.0 · [Enterprise license available](https://superdoc.dev) diff --git a/apps/cli/package.json b/apps/cli/package.json new file mode 100644 index 0000000000..760c25858f --- /dev/null +++ b/apps/cli/package.json @@ -0,0 +1,33 @@ +{ + "name": "@superdoc-dev/cli", + "version": "0.0.1", + "type": "module", + "bin": { + "superdoc": "./dist/index.js" + }, + "files": [ + "dist" + ], + "scripts": { + "dev": "bun run src/index.ts", + "build": "bun build src/index.ts --outdir dist --target node --format esm", + "test": "bun test", + "lint": "eslint .", + "lint:fix": "eslint --fix .", + "format": "prettier --write .", + "typecheck": "tsc --noEmit", + "prepublishOnly": "pnpm run build", + "release": "pnpx semantic-release", + "release:dry-run": "pnpx semantic-release --dry-run" + }, + "dependencies": { + "fast-glob": "^3.3.2", + "superdoc": "workspace:*" + }, + "devDependencies": { + "@types/bun": "catalog:", + "@types/node": "catalog:", + "typescript": "catalog:" + }, + "module": "src/index.ts" +} diff --git a/apps/cli/src/__tests__/commands.test.ts b/apps/cli/src/__tests__/commands.test.ts new file mode 100644 index 0000000000..243d88a715 --- /dev/null +++ b/apps/cli/src/__tests__/commands.test.ts @@ -0,0 +1,79 @@ +import { describe, expect, test, beforeAll, afterAll } from 'bun:test'; +import { copyFile, rm, mkdir } from 'node:fs/promises'; +import { join } from 'node:path'; +import { read } from '../commands/read'; +import { search } from '../commands/search'; +import { replace } from '../commands/replace'; + +const TEST_DIR = join(import.meta.dir, 'fixtures'); +const SAMPLE_DOC = join(TEST_DIR, 'sample.docx'); + +describe('CLI Commands', () => { + beforeAll(async () => { + await mkdir(TEST_DIR, { recursive: true }); + // Copy a test document to our fixtures folder + const sourceDoc = join(import.meta.dir, '../../../../e2e-tests/test-data/basic-documents/advanced-text.docx'); + await copyFile(sourceDoc, SAMPLE_DOC); + }); + + afterAll(async () => { + await rm(TEST_DIR, { recursive: true, force: true }); + }); + + describe('read', () => { + test('reads document content', async () => { + const result = await read(SAMPLE_DOC); + + expect(result).toHaveProperty('path', SAMPLE_DOC); + expect(result).toHaveProperty('content'); + expect(typeof result.content).toBe('string'); + expect(result.content.length).toBeGreaterThan(0); + }); + }); + + describe('search', () => { + test('finds text in document', async () => { + const result = await search('the', [SAMPLE_DOC]); + + expect(result).toHaveProperty('totalMatches'); + expect(result).toHaveProperty('files'); + expect(result.files).toHaveLength(1); + expect(result.files[0].path).toBe(SAMPLE_DOC); + }); + + test('returns empty for non-matching pattern', async () => { + const result = await search('xyz123nonexistent', [SAMPLE_DOC]); + + expect(result.totalMatches).toBe(0); + expect(result.files).toHaveLength(0); + }); + }); + + describe('replace', () => { + test('replaces text in document', async () => { + // Create a copy for replace test + const replaceCopy = join(TEST_DIR, 'replace-test.docx'); + await copyFile(SAMPLE_DOC, replaceCopy); + + // First verify the text exists + const beforeSearch = await search('the', [replaceCopy]); + const beforeCount = beforeSearch.totalMatches; + + if (beforeCount > 0) { + // Replace and verify + const result = await replace('the', 'THE', [replaceCopy]); + + expect(result).toHaveProperty('totalReplacements'); + expect(result.totalReplacements).toBe(beforeCount); + expect(result.files).toHaveLength(1); + expect(result.files[0].replacements).toBe(beforeCount); + + // Verify the replacement happened + const afterSearch = await search('THE', [replaceCopy]); + expect(afterSearch.totalMatches).toBe(beforeCount); + } + + await rm(replaceCopy); + }); + }); +}); diff --git a/apps/cli/src/commands/read.ts b/apps/cli/src/commands/read.ts new file mode 100644 index 0000000000..c8d21e00f6 --- /dev/null +++ b/apps/cli/src/commands/read.ts @@ -0,0 +1,20 @@ +import { closeDocument, getDocumentText, openDocument } from '../lib/editor'; + +export interface ReadResult { + path: string; + content: string; +} + +/** + * Read a document and output its text content + */ +export async function read(filePath: string): Promise { + const doc = await openDocument(filePath); + + try { + const content = getDocumentText(doc); + return { path: filePath, content }; + } finally { + closeDocument(doc); + } +} diff --git a/apps/cli/src/commands/replace.ts b/apps/cli/src/commands/replace.ts new file mode 100644 index 0000000000..da22979b52 --- /dev/null +++ b/apps/cli/src/commands/replace.ts @@ -0,0 +1,49 @@ +import { closeDocument, openDocument, replaceInDocument, saveDocument } from '../lib/editor'; + +export interface ReplaceFileResult { + path: string; + replacements: number; +} + +export interface ReplaceResult { + find: string; + replace: string; + files: ReplaceFileResult[]; + totalReplacements: number; +} + +/** + * Replace pattern in a single file + */ +async function replaceInFile(filePath: string, find: string, replace: string): Promise { + const doc = await openDocument(filePath); + + try { + const replacements = replaceInDocument(doc, find, replace); + + if (replacements > 0) { + await saveDocument(doc); + } + + return { path: filePath, replacements }; + } finally { + closeDocument(doc); + } +} + +/** + * Replace a pattern across multiple files + */ +export async function replace(find: string, replaceWith: string, filePaths: string[]): Promise { + const results = await Promise.all(filePaths.map((fp) => replaceInFile(fp, find, replaceWith))); + + const filesWithReplacements = results.filter((r) => r.replacements > 0); + const totalReplacements = results.reduce((sum, r) => sum + r.replacements, 0); + + return { + find, + replace: replaceWith, + files: filesWithReplacements, + totalReplacements, + }; +} diff --git a/apps/cli/src/commands/search.ts b/apps/cli/src/commands/search.ts new file mode 100644 index 0000000000..de70b7a2d6 --- /dev/null +++ b/apps/cli/src/commands/search.ts @@ -0,0 +1,73 @@ +import { closeDocument, getDocumentText, openDocument, searchDocument } from '../lib/editor'; + +export interface SearchMatch { + from: number; + to: number; + text: string; + context?: string; +} + +export interface SearchFileResult { + path: string; + matches: SearchMatch[]; +} + +export interface SearchResult { + pattern: string; + files: SearchFileResult[]; + totalMatches: number; +} + +/** + * Extract context around a match position + */ +function getMatchContext(fullText: string, from: number, to: number, contextChars = 40): string { + const start = Math.max(0, from - contextChars); + const end = Math.min(fullText.length, to + contextChars); + + let context = fullText.slice(start, end); + + // Add ellipsis if truncated + if (start > 0) context = `...${context}`; + if (end < fullText.length) context = `${context}...`; + + return context.replace(/\n/g, ' '); +} + +/** + * Search for a pattern in a single file + */ +async function searchFile(filePath: string, pattern: string): Promise { + const doc = await openDocument(filePath); + + try { + const matches = searchDocument(doc, pattern); + const fullText = getDocumentText(doc); + + return { + path: filePath, + matches: matches.map((m) => ({ + ...m, + context: getMatchContext(fullText, m.from, m.to), + })), + }; + } finally { + closeDocument(doc); + } +} + +/** + * Search for a pattern across multiple files + */ +export async function search(pattern: string, filePaths: string[]): Promise { + const results = await Promise.all(filePaths.map((fp) => searchFile(fp, pattern))); + + const filesWithMatches = results.filter((r) => r.matches.length > 0); + const totalMatches = filesWithMatches.reduce((sum, r) => sum + r.matches.length, 0); + + return { + pattern, + files: filesWithMatches, + totalMatches, + }; +} diff --git a/apps/cli/src/index.ts b/apps/cli/src/index.ts new file mode 100644 index 0000000000..478057d09a --- /dev/null +++ b/apps/cli/src/index.ts @@ -0,0 +1,175 @@ +#!/usr/bin/env node + +import { glob } from 'fast-glob'; +import { read } from './commands/read'; +import { type ReplaceResult, replace } from './commands/replace'; +import { type SearchResult, search } from './commands/search'; + +const HELP = ` +superdoc — DOCX editing in your terminal + +Commands: + search Find text across documents + replace Find and replace text + read Extract plain text + +Options: + --json Machine-readable output + --help Show this message + +Examples: + superdoc search "indemnification" ./contracts/*.docx + superdoc replace "ACME Corp" "Globex Inc" ./merger/*.docx + superdoc read ./proposal.docx + +Docs: https://github.com/superdoc-dev/superdoc +`; + +/** + * Expand glob patterns to file paths + */ +async function expandGlobs(patterns: string[]): Promise { + const files: string[] = []; + + for (const pattern of patterns) { + if (pattern.includes('*')) { + const matches = await glob(pattern, { absolute: true }); + for (const file of matches) { + if (file.endsWith('.docx')) { + files.push(file); + } + } + } else { + files.push(pattern); + } + } + + return files; +} + +/** + * Format search results for human-readable output + */ +function formatSearchResult(result: SearchResult): string { + const lines: string[] = []; + + lines.push(`Found ${result.totalMatches} matches in ${result.files.length} files`); + lines.push(''); + + for (const file of result.files) { + lines.push(` ${file.path}: ${file.matches.length} matches`); + for (const match of file.matches.slice(0, 3)) { + lines.push(` "${match.context}"`); + } + if (file.matches.length > 3) { + lines.push(` ... and ${file.matches.length - 3} more`); + } + } + + return lines.join('\n'); +} + +/** + * Format replace results for human-readable output + */ +function formatReplaceResult(result: ReplaceResult): string { + const lines: string[] = []; + + lines.push(`Updated ${result.files.length} files (${result.totalReplacements} replacements total)`); + + for (const file of result.files) { + lines.push(` ${file.path}: ${file.replacements} replacements`); + } + + return lines.join('\n'); +} + +async function main() { + const args = process.argv.slice(2); + + if (args.length === 0 || args.includes('--help') || args.includes('-h')) { + console.log(HELP); + process.exit(0); + } + + const jsonOutput = args.includes('--json'); + const filteredArgs = args.filter((a) => a !== '--json'); + + const [command, ...rest] = filteredArgs; + + try { + switch (command) { + case 'search': { + if (rest.length < 2) { + console.error('Usage: superdoc search '); + process.exit(1); + } + const [pattern, ...filePatterns] = rest; + const files = await expandGlobs(filePatterns); + + if (files.length === 0) { + console.error('No .docx files found matching the pattern'); + process.exit(1); + } + + const result = await search(pattern, files); + + if (jsonOutput) { + console.log(JSON.stringify(result, null, 2)); + } else { + console.log(formatSearchResult(result)); + } + break; + } + + case 'replace': { + if (rest.length < 3) { + console.error('Usage: superdoc replace '); + process.exit(1); + } + const [find, replaceWith, ...filePatterns] = rest; + const files = await expandGlobs(filePatterns); + + if (files.length === 0) { + console.error('No .docx files found matching the pattern'); + process.exit(1); + } + + const result = await replace(find, replaceWith, files); + + if (jsonOutput) { + console.log(JSON.stringify(result, null, 2)); + } else { + console.log(formatReplaceResult(result)); + } + break; + } + + case 'read': { + if (rest.length < 1) { + console.error('Usage: superdoc read '); + process.exit(1); + } + const [filePath] = rest; + const result = await read(filePath); + + if (jsonOutput) { + console.log(JSON.stringify(result, null, 2)); + } else { + console.log(result.content); + } + break; + } + + default: + console.error(`Unknown command: ${command}`); + console.log(HELP); + process.exit(1); + } + } catch (error) { + console.error('Error:', error instanceof Error ? error.message : error); + process.exit(1); + } +} + +main(); diff --git a/apps/cli/src/lib/editor.ts b/apps/cli/src/lib/editor.ts new file mode 100644 index 0000000000..97c7bfa1d1 --- /dev/null +++ b/apps/cli/src/lib/editor.ts @@ -0,0 +1,85 @@ +import { readFile, writeFile } from 'node:fs/promises'; +import { Editor } from 'superdoc/super-editor'; + +export interface DocumentEditor { + editor: Editor; + path: string; +} + +/** + * Opens a document in headless mode using the new Editor.open() API + */ +export async function openDocument(path: string): Promise { + const buffer = await readFile(path); + + const editor = await Editor.open(buffer, { + documentId: path, + }); + + return { editor, path }; +} + +/** + * Saves the document back to disk + */ +export async function saveDocument(doc: DocumentEditor): Promise { + const result = await doc.editor.exportDocument({ format: 'docx' }); + // In headless mode, exportDocument returns a Buffer/Uint8Array directly + await writeFile(doc.path, result as Buffer); +} + +/** + * Closes and cleans up the editor + */ +export function closeDocument(doc: DocumentEditor): void { + doc.editor.destroy(); +} + +/** + * Gets the plain text content of the document + */ +export function getDocumentText(doc: DocumentEditor): string { + const { state } = doc.editor; + return state.doc.textContent; +} + +/** + * Search for text in the document + * Returns array of matches with positions + */ +export function searchDocument( + doc: DocumentEditor, + pattern: string, +): Array<{ from: number; to: number; text: string }> { + type Match = { from: number; to: number; text: string }; + const matches = doc.editor.commands.search?.(pattern, { + highlight: false, + }) as Match[] | undefined; + if (!matches) return []; + return matches.map((m) => ({ + from: m.from, + to: m.to, + text: m.text, + })); +} + +/** + * Replace all occurrences of a pattern with replacement text + * Returns the number of replacements made + */ +export function replaceInDocument(doc: DocumentEditor, find: string, replaceWith: string): number { + // Search for all matches + const matches = searchDocument(doc, find); + if (matches.length === 0) return 0; + + // Sort matches by position descending (replace from end to start to avoid position shifts) + const sortedMatches = [...matches].sort((a, b) => b.from - a.from); + + // Replace each match using editor chain + for (const match of sortedMatches) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (doc.editor.chain() as any).setTextSelection({ from: match.from, to: match.to }).insertContent(replaceWith).run(); + } + + return matches.length; +} diff --git a/apps/cli/tsconfig.json b/apps/cli/tsconfig.json new file mode 100644 index 0000000000..543910d92a --- /dev/null +++ b/apps/cli/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "skipLibCheck": true, + "types": ["bun"] + }, + "include": ["src"] +} diff --git a/eslint.config.mjs b/eslint.config.mjs index 2f8ab46139..eba622b284 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -133,6 +133,7 @@ export default [ // Temporarily all "@"-prefixed imports. This should likely be changed to use https://github.com/pzmosquito/eslint-import-resolver-vite to properly resolve these aliases ignore: [ '^@.*$', + '^bun:.*$', // Bun built-in modules ], } ] diff --git a/lefthook.yml b/lefthook.yml index a849f1ae92..137a0b7269 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -11,6 +11,14 @@ pre-commit: glob: "*.{js,jsx,ts,tsx}" run: pnpm exec eslint --fix {staged_files} stage_fixed: true + cli-build: + root: "apps/cli/" + glob: "apps/cli/**/*.ts" + run: pnpm run build + cli-test: + root: "apps/cli/" + glob: "apps/cli/**/*.ts" + run: pnpm run test commit-msg: commands: diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 47811918e3..ec218895af 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,6 +48,9 @@ catalogs: '@testing-library/user-event': specifier: ^14.6.1 version: 14.6.1 + '@types/bun': + specifier: ^1.3.8 + version: 1.3.8 '@types/node': specifier: 22.19.2 version: 22.19.2 @@ -384,6 +387,25 @@ importers: specifier: 'catalog:' version: 3.2.0 + apps/cli: + dependencies: + fast-glob: + specifier: ^3.3.2 + version: 3.3.3 + superdoc: + specifier: workspace:* + version: link:../../packages/superdoc + devDependencies: + '@types/bun': + specifier: 'catalog:' + version: 1.3.8 + '@types/node': + specifier: 'catalog:' + version: 22.19.2 + typescript: + specifier: 'catalog:' + version: 5.9.3 + apps/docs: devDependencies: documentation: @@ -492,7 +514,7 @@ importers: version: 5.9.3 vitest: specifier: 'catalog:' - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.3.4)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) vue: specifier: 'catalog:' version: 3.5.25(typescript@5.9.3) @@ -517,7 +539,7 @@ importers: version: 8.49.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.3) '@vitest/coverage-v8': specifier: 'catalog:' - version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.3.4)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2)) + version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2)) concurrently: specifier: 'catalog:' version: 9.2.1 @@ -541,7 +563,7 @@ importers: version: 5.9.3 vitest: specifier: 'catalog:' - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.3.4)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) packages/esign: devDependencies: @@ -592,7 +614,7 @@ importers: version: 4.5.4(@types/node@22.19.2)(rollup@4.53.3)(typescript@5.9.3)(vite@7.2.7(@types/node@22.19.2)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) vitest: specifier: 'catalog:' - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.3.4)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) packages/esign/demo: dependencies: @@ -651,7 +673,7 @@ importers: version: 5.9.3 vitest: specifier: 'catalog:' - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.3.4)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) packages/layout-engine/layout-bridge: dependencies: @@ -688,7 +710,7 @@ importers: version: 5.9.3 vitest: specifier: 'catalog:' - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.3.4)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) packages/layout-engine/layout-engine: dependencies: @@ -734,7 +756,7 @@ importers: devDependencies: vitest: specifier: 'catalog:' - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.3.4)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) packages/layout-engine/pm-adapter: dependencies: @@ -774,7 +796,7 @@ importers: version: link:../painters/dom vitest: specifier: 'catalog:' - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.3.4)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) packages/layout-engine/style-engine: dependencies: @@ -790,7 +812,7 @@ importers: version: 5.9.3 vitest: specifier: 'catalog:' - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.3.4)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) packages/layout-engine/tests: dependencies: @@ -1083,7 +1105,7 @@ importers: version: 0.24.0(patch_hash=b69b54a87bfd9531260555cfb2fda7c94e6f75c124e3a3fae26d64272bbe86ff)(rollup@4.53.3)(vite@7.2.7(@types/node@22.19.2)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) vitest: specifier: 'catalog:' - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.3.4)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) xml-js: specifier: 'catalog:' version: 1.6.11 @@ -1137,7 +1159,7 @@ importers: version: 4.5.4(@types/node@22.19.2)(rollup@4.53.3)(typescript@5.9.3)(vite@7.2.7(@types/node@22.19.2)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) vitest: specifier: 'catalog:' - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.3.4)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) packages/template-builder/demo: dependencies: @@ -1174,7 +1196,7 @@ importers: devDependencies: vitest: specifier: 'catalog:' - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.3.4)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) shared/common: devDependencies: @@ -1189,7 +1211,7 @@ importers: version: 5.9.3 vitest: specifier: 'catalog:' - version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.3.4)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) + version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) vue: specifier: 'catalog:' version: 3.5.25(typescript@5.9.3) @@ -3110,6 +3132,9 @@ packages: '@types/babel__traverse@7.28.0': resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/bun@1.3.8': + resolution: {integrity: sha512-3LvWJ2q5GerAXYxO2mffLTqOzEu5qnhEAlh48Vnu8WQfnmSwbgagjGZV6BoHKJztENYEDn6QmVd949W4uESRJA==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -4079,6 +4104,9 @@ packages: builtin-status-codes@3.0.0: resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==} + bun-types@1.3.8: + resolution: {integrity: sha512-fL99nxdOWvV4LqjmC+8Q9kW3M4QTtTR1eePs94v5ctGqU8OeceWrSUaRw3JYb7tU3FkMIAjkueehrHPPPGKi5Q==} + bundle-require@5.1.0: resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} @@ -5607,6 +5635,10 @@ packages: resolution: {integrity: sha512-rfbiwB6OKxZFIFQ7SRnCPB2WL9WhyXsFoTfecYgeCeFSOBxvkWLaXsdv5ehzJrfqwXQmDephAKWLRQoFoJwrew==} engines: {node: '>=20.0.0'} + happy-dom@20.4.0: + resolution: {integrity: sha512-RDeQm3dT9n0A5f/TszjUmNCLEuPnMGv3Tv4BmNINebz/h17PA6LMBcxJ5FrcqltNBMh9jA/8ufgDdBYUdBt+eg==} + engines: {node: '>=20.0.0'} + has-bigints@1.1.0: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} @@ -12514,6 +12546,10 @@ snapshots: dependencies: '@babel/types': 7.28.6 + '@types/bun@1.3.8': + dependencies: + bun-types: 1.3.8 + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -12993,6 +13029,25 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2))': + dependencies: + '@ampproject/remapping': 2.3.0 + '@bcoe/v8-coverage': 1.0.2 + ast-v8-to-istanbul: 0.3.8 + debug: 4.4.3(supports-color@5.5.0) + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.6 + istanbul-reports: 3.2.0 + magic-string: 0.30.21 + magicast: 0.3.5 + std-env: 3.10.0 + test-exclude: 7.0.1 + tinyrainbow: 2.0.0 + vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2) + transitivePeerDependencies: + - supports-color + '@vitest/expect@3.2.4': dependencies: '@types/chai': 5.2.3 @@ -13684,6 +13739,10 @@ snapshots: builtin-status-codes@3.0.0: {} + bun-types@1.3.8: + dependencies: + '@types/node': 22.19.2 + bundle-require@5.1.0(esbuild@0.27.1): dependencies: esbuild: 0.27.1 @@ -15649,6 +15708,19 @@ snapshots: - bufferutil - utf-8-validate + happy-dom@20.4.0: + dependencies: + '@types/node': 22.19.2 + '@types/whatwg-mimetype': 3.0.2 + '@types/ws': 8.18.1 + entities: 4.5.0 + whatwg-mimetype: 3.0.0 + ws: 8.18.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + optional: true + has-bigints@1.1.0: {} has-flag@3.0.0: {} @@ -20979,6 +21051,50 @@ snapshots: - tsx - yaml + vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.2)(happy-dom@20.4.0)(jiti@2.6.1)(jsdom@27.3.0(canvas@3.2.0)(postcss@8.5.6))(tsx@4.21.0)(yaml@2.8.2): + dependencies: + '@types/chai': 5.2.3 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@7.2.7(@types/node@22.19.2)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.3.3 + debug: 4.4.3(supports-color@5.5.0) + expect-type: 1.3.0 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 7.2.7(@types/node@22.19.2)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) + vite-node: 3.2.4(@types/node@22.19.2)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.12 + '@types/node': 22.19.2 + happy-dom: 20.4.0 + jsdom: 27.3.0(canvas@3.2.0)(postcss@8.5.6) + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + vm-browserify@1.1.2: {} vooks@0.2.12(vue@3.5.25(typescript@5.9.3)): diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c1cd0fe8c7..37091fb7a1 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -29,6 +29,7 @@ catalog: '@playwright/test': ^1.57.0 '@semantic-release/changelog': ^6.0.3 '@semantic-release/git': ^10.0.1 + '@types/bun': ^1.3.8 '@types/node': 22.19.2 '@typescript-eslint/eslint-plugin': ^8.49.0 '@typescript-eslint/parser': ^8.49.0