diff --git a/package.json b/package.json index 1c18f2f..7b30508 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,9 @@ { "private": true, "scripts": { - "build": "pnpm run build:parser && pnpm run build:language-service && pnpm run build:language-server && pnpm run build:typescript-plugin && pnpm run build:vscode", + "build": "pnpm run build:parser && pnpm run build:prettier-plugin && pnpm run build:language-service && pnpm run build:language-server && pnpm run build:typescript-plugin && pnpm run build:vscode", "build:parser": "cd ./packages/parser && pnpm build", + "build:prettier-plugin": "cd ./packages/prettier-plugin && pnpm build", "build:language-service": "cd ./packages/language-service && pnpm build", "build:language-server": "cd ./packages/language-server && pnpm build", "build:typescript-plugin": "cd ./packages/typescript-plugin && pnpm build", diff --git a/packages/prettier-plugin/.gitignore b/packages/prettier-plugin/.gitignore new file mode 100644 index 0000000..f1ff06d --- /dev/null +++ b/packages/prettier-plugin/.gitignore @@ -0,0 +1 @@ +lib/ \ No newline at end of file diff --git a/packages/prettier-plugin/package.json b/packages/prettier-plugin/package.json new file mode 100644 index 0000000..1cb81f7 --- /dev/null +++ b/packages/prettier-plugin/package.json @@ -0,0 +1,32 @@ +{ + "name": "prettier-plugin-tempblot", + "version": "0.0.1", + "type": "module", + "description": "Prettier plugin for Tempblot templates", + "exports": { + ".": { + "types": "./lib/index.d.ts", + "import": "./lib/index.js", + "default": "./lib/index.js" + } + }, + "files": [ + "lib" + ], + "scripts": { + "build": "tsc --build", + "prepack": "tsc --build --clean && tsc --build" + }, + "dependencies": { + "tempblot-parser": "workspace:*" + }, + "devDependencies": { + "@types/node": "^24.5.2", + "prettier": "^3.6.2", + "typescript": "^6.0.3" + }, + "peerDependencies": { + "prettier": ">=3.0.0" + }, + "sideEffects": false +} diff --git a/packages/prettier-plugin/src/index.ts b/packages/prettier-plugin/src/index.ts new file mode 100644 index 0000000..d8e29e0 --- /dev/null +++ b/packages/prettier-plugin/src/index.ts @@ -0,0 +1,96 @@ +import type { AstPath, Parser, Plugin, Printer } from 'prettier'; +import { parseTempblotRoot, type ParsedRoot, type RootBlock } from 'tempblot-parser'; + +interface TempblotAst { + type: 'TempblotDocument'; + source: string; + root: ParsedRoot; +} + +const parser: Parser = { + astFormat: 'tempblot-ast', + parse(source) { + return { + type: 'TempblotDocument', + source, + root: parseTempblotRoot(source), + }; + }, + locStart() { + return 0; + }, + locEnd(node) { + return node.source.length; + }, +}; + +const printer: Printer = { + print(path) { + return formatDocument(path); + }, +}; + +const plugin: Plugin = { + languages: [ + { + name: 'Tempblot', + parsers: ['tempblot'], + extensions: ['.blot'], + vscodeLanguageIds: ['tempblot'], + }, + ], + parsers: { + tempblot: parser, + }, + printers: { + 'tempblot-ast': printer, + }, +}; + +export default plugin; +export const languages = plugin.languages; +export const parsers = plugin.parsers; +export const printers = plugin.printers; + +function formatDocument(path: AstPath) { + const ast = path.node; + const blocks = ast.root.blocks; + + if (blocks.length === 0) { + return ensureTrailingNewline(ast.source.trim()); + } + + const leadingText = ast.source.slice(0, blocks[0].start).trim(); + const trailingText = ast.source.slice(blocks[blocks.length - 1].end).trim(); + const parts = [ + leadingText, + ...blocks.map((block) => formatBlock(ast.source, block)), + trailingText, + ].filter((part) => part.length > 0); + + return ensureTrailingNewline(parts.join('\n\n')); +} + +function formatBlock(source: string, block: RootBlock) { + const openTag = formatOpenTag(block); + const closeTag = ``; + const contents = source.slice(block.startTagEnd, block.endTagStart).trim(); + + if (!contents) { + return `${openTag}\n${closeTag}`; + } + + return `${openTag}\n${contents}\n${closeTag}`; +} + +function formatOpenTag(block: RootBlock) { + const attributes = Object.entries(block.attributes) + .map(([name, value]) => `${name}="${value}"`) + .join(' '); + + return attributes ? `<${block.tag} ${attributes}>` : `<${block.tag}>`; +} + +function ensureTrailingNewline(text: string) { + return `${text}\n`; +} diff --git a/packages/prettier-plugin/tsconfig.json b/packages/prettier-plugin/tsconfig.json new file mode 100644 index 0000000..d921b32 --- /dev/null +++ b/packages/prettier-plugin/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "include": ["src"], + "compilerOptions": { + "declaration": true, + "outDir": "lib", + "rootDir": "src" + }, + "references": [{ "path": "../parser" }] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4a4a3c2..366af16 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -184,6 +184,22 @@ importers: specifier: ^4.1.6 version: 4.1.6(@types/node@24.12.4)(vite@8.0.12(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(yaml@2.9.0)) + packages/prettier-plugin: + dependencies: + tempblot-parser: + specifier: workspace:* + version: link:../parser + devDependencies: + '@types/node': + specifier: ^24.5.2 + version: 24.12.4 + prettier: + specifier: ^3.6.2 + version: 3.8.3 + typescript: + specifier: ^6.0.3 + version: 6.0.3 + packages/typescript-plugin: dependencies: '@volar/typescript': -- 2.51.2 From bc86bd80484104e217de2982166e872242c1bf50 Mon Sep 17 00:00:00 2001 From: Corbin Crutchley Date: Thu, 14 May 2026 01:02:20 -0400 Subject: [PATCH 2/3] chore: delegate plugins --- packages/prettier-plugin/src/index.ts | 100 ++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 6 deletions(-) diff --git a/packages/prettier-plugin/src/index.ts b/packages/prettier-plugin/src/index.ts index d8e29e0..e583f7f 100644 --- a/packages/prettier-plugin/src/index.ts +++ b/packages/prettier-plugin/src/index.ts @@ -1,19 +1,23 @@ -import type { AstPath, Parser, Plugin, Printer } from 'prettier'; +import * as prettier from 'prettier'; +import type { AstPath, Options, Parser, ParserOptions, Plugin, Printer } from 'prettier'; import { parseTempblotRoot, type ParsedRoot, type RootBlock } from 'tempblot-parser'; interface TempblotAst { type: 'TempblotDocument'; source: string; root: ParsedRoot; + formattedBlocks: Map; } const parser: Parser = { astFormat: 'tempblot-ast', - parse(source) { + async parse(source, options) { + const root = parseTempblotRoot(source); return { type: 'TempblotDocument', source, - root: parseTempblotRoot(source), + root, + formattedBlocks: await formatBlocks(source, root.blocks, options), }; }, locStart() { @@ -64,17 +68,17 @@ function formatDocument(path: AstPath) { const trailingText = ast.source.slice(blocks[blocks.length - 1].end).trim(); const parts = [ leadingText, - ...blocks.map((block) => formatBlock(ast.source, block)), + ...blocks.map((block) => formatBlock(ast, block)), trailingText, ].filter((part) => part.length > 0); return ensureTrailingNewline(parts.join('\n\n')); } -function formatBlock(source: string, block: RootBlock) { +function formatBlock(ast: TempblotAst, block: RootBlock) { const openTag = formatOpenTag(block); const closeTag = ``; - const contents = source.slice(block.startTagEnd, block.endTagStart).trim(); + const contents = ast.formattedBlocks.get(block) ?? ast.source.slice(block.startTagEnd, block.endTagStart).trim(); if (!contents) { return `${openTag}\n${closeTag}`; @@ -94,3 +98,87 @@ function formatOpenTag(block: RootBlock) { function ensureTrailingNewline(text: string) { return `${text}\n`; } + +async function formatBlocks(source: string, blocks: RootBlock[], options: ParserOptions) { + const formattedBlocks = new Map(); + + await Promise.all(blocks.map(async (block) => { + const parserName = getBlockParser(block); + const rawContents = source.slice(block.startTagEnd, block.endTagStart).trim(); + + if (!parserName || !rawContents) { + formattedBlocks.set(block, rawContents); + return; + } + + formattedBlocks.set(block, await formatEmbedded(rawContents, parserName, options)); + })); + + return formattedBlocks; +} + +async function formatEmbedded(source: string, parserName: string, options: ParserOptions) { + try { + return (await prettier.format(source, { + ...copyPrettierOptions(options), + parser: parserName, + })).trim(); + } catch { + return source.trim(); + } +} + +function getBlockParser(block: RootBlock) { + if (block.tag === 'setup') { + return 'typescript'; + } + + if (block.tag !== 'output') { + return undefined; + } + + return getOutputParser(block.attributes.lang); +} + +function getOutputParser(lang: string | undefined) { + switch (lang) { + case undefined: + case 'json': + case 'jsonc': + return 'json'; + case 'yaml': + case 'yml': + return 'yaml'; + case 'html': + return 'html'; + case 'css': + return 'css'; + case 'javascript': + case 'js': + return 'babel'; + case 'typescript': + case 'ts': + return 'typescript'; + default: + return undefined; + } +} + +function copyPrettierOptions(options: ParserOptions): Options { + return { + arrowParens: options.arrowParens, + bracketSameLine: options.bracketSameLine, + bracketSpacing: options.bracketSpacing, + endOfLine: options.endOfLine, + htmlWhitespaceSensitivity: options.htmlWhitespaceSensitivity, + printWidth: options.printWidth, + proseWrap: options.proseWrap, + quoteProps: options.quoteProps, + semi: options.semi, + singleAttributePerLine: options.singleAttributePerLine, + singleQuote: options.singleQuote, + tabWidth: options.tabWidth, + trailingComma: options.trailingComma, + useTabs: options.useTabs, + }; +} -- 2.51.2 From 8d20ed7b4591802a6d4a0a81a0e2db299de483f9 Mon Sep 17 00:00:00 2001 From: Corbin Crutchley Date: Thu, 14 May 2026 01:15:05 -0400 Subject: [PATCH 3/3] chore: add tests using CLI Testing Library --- packages/prettier-plugin/package.json | 7 +- .../tests/__snapshots__/index.spec.ts.snap | 75 +++++++++++ .../prettier-plugin/tests/fixtures/.gitignore | 2 + packages/prettier-plugin/tests/index.spec.ts | 125 ++++++++++++++++++ pnpm-lock.yaml | 110 +++++++++++++++ 5 files changed, 317 insertions(+), 2 deletions(-) create mode 100644 packages/prettier-plugin/tests/__snapshots__/index.spec.ts.snap create mode 100644 packages/prettier-plugin/tests/fixtures/.gitignore create mode 100644 packages/prettier-plugin/tests/index.spec.ts diff --git a/packages/prettier-plugin/package.json b/packages/prettier-plugin/package.json index 1cb81f7..c585d3f 100644 --- a/packages/prettier-plugin/package.json +++ b/packages/prettier-plugin/package.json @@ -15,15 +15,18 @@ ], "scripts": { "build": "tsc --build", - "prepack": "tsc --build --clean && tsc --build" + "prepack": "tsc --build --clean && tsc --build", + "test:lib": "vitest" }, "dependencies": { "tempblot-parser": "workspace:*" }, "devDependencies": { "@types/node": "^24.5.2", + "cli-testing-library": "^3.0.0", "prettier": "^3.6.2", - "typescript": "^6.0.3" + "typescript": "^6.0.3", + "vitest": "^4.1.6" }, "peerDependencies": { "prettier": ">=3.0.0" diff --git a/packages/prettier-plugin/tests/__snapshots__/index.spec.ts.snap b/packages/prettier-plugin/tests/__snapshots__/index.spec.ts.snap new file mode 100644 index 0000000..b2b3433 --- /dev/null +++ b/packages/prettier-plugin/tests/__snapshots__/index.spec.ts.snap @@ -0,0 +1,75 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`formats .blot files through Prettier CLI 1`] = ` +" +const value = { hello: "world" }; + + + +{ "hello": "world" } +" +`; + +exports[`formats css output 1`] = ` +" +.card { + color: red; + background: white; +} + +" +`; + +exports[`formats html output 1`] = ` +" +
+

Hello

+

World

+
+
+" +`; + +exports[`formats javascript output 1`] = ` +" +const run = () => ({ hello: "world" }); + +" +`; + +exports[`formats setup and json output 1`] = ` +" + + +const value = { hello: "world", items: [1, 2, 3] }; + + + +{ "hello": "world", "items": [1, 2, 3] } + +" +`; + +exports[`formats typescript output 1`] = ` +" +const value: Record = { hello: "world" }; + +" +`; + +exports[`formats unknown output language 1`] = ` +" +leave this alone + +" +`; + +exports[`formats yaml output 1`] = ` +" +hello: world +items: + - 1 + - 2 + +" +`; diff --git a/packages/prettier-plugin/tests/fixtures/.gitignore b/packages/prettier-plugin/tests/fixtures/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/packages/prettier-plugin/tests/fixtures/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/packages/prettier-plugin/tests/index.spec.ts b/packages/prettier-plugin/tests/index.spec.ts new file mode 100644 index 0000000..58a3831 --- /dev/null +++ b/packages/prettier-plugin/tests/index.spec.ts @@ -0,0 +1,125 @@ +import * as fs from "node:fs/promises"; +import * as path from "node:path"; +import { fileURLToPath } from "node:url"; + +import { render } from "cli-testing-library"; +import * as prettier from "prettier"; +import { expect, test } from "vitest"; + +import plugin from "../src/index.js"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const packageRoot = path.resolve(__dirname, ".."); + +test.each([ + [ + "setup and json output", + ` + + +const value={hello:"world",items:[1,2,3]} + + + +{"hello":"world","items":[1,2,3]} + +`, + ], + [ + "yaml output", + ` + +hello: world +items: +- 1 +- 2 + +`, + ], + [ + "html output", + ` + +

Hello

World

+
+`, + ], + [ + "css output", + ` + +.card{color:red;background:white} + +`, + ], + [ + "javascript output", + ` + +const run=()=>({hello:"world"}) + +`, + ], + [ + "typescript output", + ` + +const value:Record={hello:"world"} + +`, + ], + [ + "unknown output language", + ` + + leave this alone + +`, + ], +])("formats %s", async (_name, source) => { + await expect(format(source.trim())).resolves.toMatchSnapshot(); +}); + +test("formats .blot files through Prettier CLI", async () => { + const fixturePath = path.join(packageRoot, "tests", "fixtures", "cli.blot"); + const fixtureArg = "tests/fixtures/cli.blot"; + + try { + await fs.writeFile( + fixturePath, + ` + +const value={hello:"world"} + + +{"hello":"world"} + +`.trim(), + ); + + const cli = await render(`pnpm exec prettier --plugin ./lib/index.js ${fixtureArg}`, [], { + cwd: packageRoot, + }); + + await waitForExit(cli); + + expect(cli.getStdallStr()).toMatchSnapshot(); + expect(cli.hasExit()).toEqual({ exitCode: 0 }); + } finally { + await fs.rm(fixturePath, { force: true }); + } +}); + +function format(source: string) { + return prettier.format(source, { + parser: "tempblot", + plugins: [plugin], + }); +} + +async function waitForExit(cli: Awaited>) { + while (!cli.hasExit()) { + await new Promise((resolve) => setTimeout(resolve, 10)); + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 366af16..3393364 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -193,12 +193,18 @@ importers: '@types/node': specifier: ^24.5.2 version: 24.12.4 + cli-testing-library: + specifier: ^3.0.0 + version: 3.0.1(vitest@4.1.6(@types/node@24.12.4)(vite@8.0.12(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(yaml@2.9.0))) prettier: specifier: ^3.6.2 version: 3.8.3 typescript: specifier: ^6.0.3 version: 6.0.3 + vitest: + specifier: ^4.1.6 + version: 4.1.6(@types/node@24.12.4)(vite@8.0.12(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(yaml@2.9.0)) packages/typescript-plugin: dependencies: @@ -369,6 +375,10 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + '@babel/template@7.28.6': resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} @@ -1666,6 +1676,10 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + any-promise@1.3.0: resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} @@ -1830,6 +1844,21 @@ packages: resolution: {integrity: sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==} engines: {node: 10.* || >= 12.*} + cli-testing-library@3.0.1: + resolution: {integrity: sha512-fkQ8D2hQS53RP3s0yuCMHmTfPUMEqtVtJG0rs13MNE2khnkSaY8MsNxN7rSJZAzOOVsSg2I2F2XjEITJwf5dFg==} + engines: {node: '>=16'} + peerDependencies: + '@jest/expect': ^29.0.0 + '@jest/globals': ^29.0.0 + vitest: ^3.0.0 + peerDependenciesMeta: + '@jest/expect': + optional: true + '@jest/globals': + optional: true + vitest: + optional: true + cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} @@ -2185,6 +2214,10 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} + engines: {node: '>=18'} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -2330,6 +2363,10 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + indent-string@5.0.0: + resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} + engines: {node: '>=12'} + inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. @@ -2369,6 +2406,10 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} + is-hexadecimal@2.0.1: resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} @@ -3023,6 +3064,10 @@ packages: recma-stringify@1.0.0: resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + redent@4.0.0: + resolution: {integrity: sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==} + engines: {node: '>=12'} + regex-recursion@6.0.2: resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} @@ -3226,6 +3271,10 @@ packages: resolution: {integrity: sha512-kUMbT1oBJCpgrnKoSr0o6wPtvRWT9W9UKvGLwfJYO2WuahZRHOpEyL1ckyMGgMWh0UdpmaoFqKKD29WTomNEGA==} engines: {node: '>=8'} + slice-ansi@7.1.2: + resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} + engines: {node: '>=18'} + smol-toml@1.6.1: resolution: {integrity: sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==} engines: {node: '>= 18'} @@ -3267,6 +3316,18 @@ packages: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + + strip-indent@4.1.1: + resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==} + engines: {node: '>=12'} + strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} @@ -3344,6 +3405,10 @@ packages: resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} engines: {node: '>=14.14'} + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} @@ -4081,6 +4146,8 @@ snapshots: '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 + '@babel/runtime@7.29.2': {} + '@babel/template@7.28.6': dependencies: '@babel/code-frame': 7.29.0 @@ -5035,6 +5102,8 @@ snapshots: dependencies: color-convert: 2.0.1 + ansi-styles@6.2.3: {} + any-promise@1.3.0: {} anymatch@3.1.3: @@ -5294,6 +5363,19 @@ snapshots: optionalDependencies: '@colors/colors': 1.5.0 + cli-testing-library@3.0.1(vitest@4.1.6(@types/node@24.12.4)(vite@8.0.12(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(yaml@2.9.0))): + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/runtime': 7.29.2 + picocolors: 1.1.1 + redent: 4.0.0 + slice-ansi: 7.1.2 + strip-ansi: 7.2.0 + strip-final-newline: 4.0.0 + tree-kill: 1.2.2 + optionalDependencies: + vitest: 4.1.6(@types/node@24.12.4)(vite@8.0.12(@types/node@24.12.4)(esbuild@0.28.0)(jiti@2.7.0)(yaml@2.9.0)) + cliui@7.0.4: dependencies: string-width: 4.2.3 @@ -5652,6 +5734,8 @@ snapshots: get-caller-file@2.0.5: {} + get-east-asian-width@1.6.0: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -5935,6 +6019,8 @@ snapshots: ieee754@1.2.1: {} + indent-string@5.0.0: {} + inflight@1.0.6: dependencies: once: 1.4.0 @@ -5963,6 +6049,10 @@ snapshots: is-fullwidth-code-point@3.0.0: {} + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.6.0 + is-hexadecimal@2.0.1: {} is-inside-container@1.0.0: @@ -6956,6 +7046,11 @@ snapshots: unified: 11.0.5 vfile: 6.0.3 + redent@4.0.0: + dependencies: + indent-string: 5.0.0 + strip-indent: 4.1.1 + regex-recursion@6.0.2: dependencies: regex-utilities: 2.3.0 @@ -7294,6 +7389,11 @@ snapshots: dependencies: unicode-emoji-modifier-base: 1.0.0 + slice-ansi@7.1.2: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + smol-toml@1.6.1: {} source-map-js@1.2.1: {} @@ -7329,6 +7429,14 @@ snapshots: dependencies: ansi-regex: 5.0.1 + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + strip-final-newline@4.0.0: {} + + strip-indent@4.1.1: {} + strip-json-comments@2.0.1: {} strip-json-comments@5.0.3: {} @@ -7408,6 +7516,8 @@ snapshots: tmp@0.2.5: {} + tree-kill@1.2.2: {} + trim-lines@3.0.1: {} trough@2.2.0: {}