diff --git a/.changeset/proud-mangos-look.md b/.changeset/proud-mangos-look.md new file mode 100644 index 000000000..6767bb4cb --- /dev/null +++ b/.changeset/proud-mangos-look.md @@ -0,0 +1,5 @@ +--- +"@ucdjs/ucd-store": minor +--- + +implement analyze on ucd-store diff --git a/packages/cli/package.json b/packages/cli/package.json index 0c9c99ff6..67e5a32d0 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -36,7 +36,7 @@ "node": ">=22.17" }, "scripts": { - "build": "tsdown", + "build": "tsdown --tsconfig=./tsconfig.build.json", "clean": "git clean -xdf dist node_modules", "lint": "eslint .", "typecheck": "tsc --noEmit" diff --git a/packages/cli/src/cmd/store/analyze.ts b/packages/cli/src/cmd/store/analyze.ts new file mode 100644 index 000000000..84d6336ec --- /dev/null +++ b/packages/cli/src/cmd/store/analyze.ts @@ -0,0 +1,115 @@ +/* eslint-disable no-console */ +import type { Prettify } from "@luxass/utils"; +import type { CLIArguments } from "../../cli-utils"; +import type { CLIStoreCmdSharedFlags } from "./_shared"; +import { UCDStoreUnsupportedFeature } from "@ucdjs/ucd-store"; +import { green, red } from "farver/fast"; +import { printHelp } from "../../cli-utils"; +import { assertRemoteOrStoreDir, createStoreFromFlags, SHARED_FLAGS } from "./_shared"; + +export interface CLIStoreAnalyzeCmdOptions { + flags: CLIArguments>; + versions?: string[]; +} + +export async function runAnalyzeStore({ flags, versions }: CLIStoreAnalyzeCmdOptions) { + if (flags?.help || flags?.h) { + printHelp({ + headline: "Analyze UCD Store", + commandName: "ucd store analyze", + usage: "[...versions] [...flags]", + tables: { + Flags: [ + ...SHARED_FLAGS, + ["--check-orphaned", "Check for orphaned files in the store."], + ["--json", "Output analyze information in JSON format."], + ["--help (-h)", "See all available flags."], + ], + }, + }); + return; + } + + if (!versions || versions.length === 0) { + console.info("No specific versions provided. Analyzing all versions in the store."); + } + + const { + storeDir, + json, + remote, + baseUrl, + patterns, + checkOrphaned, + } = flags; + + try { + assertRemoteOrStoreDir(flags); + + const store = await createStoreFromFlags({ + baseUrl, + storeDir, + remote, + patterns, + }); + + if (store == null) { + console.error("Error: Failed to create UCD store."); + return; + } + + const result = await store.analyze({ + checkOrphaned: !!checkOrphaned, + versions: versions || [], + }); + + if (json) { + console.info(JSON.stringify(result, null, 2)); + return; + } + + for (const { version, fileCount, isComplete, missingFiles, orphanedFiles, totalFileCount } of result) { + console.info(`Version: ${version}`); + if (isComplete) { + console.info(` Status: ${green("complete")}`); + } else { + console.warn(` Status: ${red("incomplete")}`); + } + console.info(` Files: ${fileCount}`); + if (missingFiles && missingFiles.length > 0) { + console.warn(` Missing files: ${missingFiles.length}`); + } + if (orphanedFiles && orphanedFiles.length > 0) { + console.warn(` Orphaned files: ${orphanedFiles.length}`); + } + + if (totalFileCount) { + console.info(` Total files expected: ${totalFileCount}`); + } + } + } catch (err) { + if (err instanceof UCDStoreUnsupportedFeature) { + console.error(red(`\n❌ Error: Unsupported feature:`)); + console.error(` ${err.message}`); + console.error(""); + console.error("This store does not support the analyze operation."); + console.error("Please check the store capabilities or use a different store type."); + return; + } + + let message = "Unknown error"; + if (err instanceof Error) { + message = err.message; + } else if (typeof err === "string") { + message = err; + } + + console.error(red(`\n❌ Error analyzing store:`)); + console.error(` ${message}`); + console.error("Please check the store configuration and try again."); + console.error("If you believe this is a bug, please report it at https://github.com/ucdjs/ucd/issues"); + } +} diff --git a/packages/cli/src/cmd/store/root.ts b/packages/cli/src/cmd/store/root.ts index 2a315cad3..fe5a3fa6c 100644 --- a/packages/cli/src/cmd/store/root.ts +++ b/packages/cli/src/cmd/store/root.ts @@ -14,7 +14,7 @@ const CODEGEN_SUBCOMMANDS = [ "init", "repair", "clean", - "status", + "analyze", ] as const; export type Subcommand = (typeof CODEGEN_SUBCOMMANDS)[number]; @@ -70,9 +70,9 @@ export async function runStoreRoot(subcommand: string, { flags }: CLIStoreCmdOpt return; } - if (subcommand === "status") { - const { runStatusStore } = await import("./status"); - await runStatusStore({ flags }); + if (subcommand === "analyze") { + const { runAnalyzeStore } = await import("./analyze"); + await runAnalyzeStore({ flags }); return; } diff --git a/packages/cli/src/cmd/store/status.ts b/packages/cli/src/cmd/store/status.ts deleted file mode 100644 index 3cc8eaa5d..000000000 --- a/packages/cli/src/cmd/store/status.ts +++ /dev/null @@ -1,79 +0,0 @@ -/* eslint-disable unused-imports/no-unused-vars */ - -import type { Prettify } from "@luxass/utils"; -import type { CLIArguments } from "../../cli-utils"; -import type { CLIStoreCmdSharedFlags } from "./_shared"; -// import { createLocalUCDStore, createRemoteUCDStore } from "@ucdjs/ucd-store"; -import { printHelp } from "../../cli-utils"; -import { SHARED_FLAGS } from "./_shared"; - -export interface CLIStoreStatusCmdOptions { - flags: CLIArguments>; -} - -export async function runStatusStore({ flags }: CLIStoreStatusCmdOptions) { - if (flags?.help || flags?.h) { - printHelp({ - headline: "Show UCD Store Status", - commandName: "ucd store status", - usage: "[...flags]", - tables: { - Flags: [ - ...SHARED_FLAGS, - ["--json", "Output status information in JSON format."], - ["--help (-h)", "See all available flags."], - ], - }, - }); - return; - } - - const { - storeDir, - json, - remote, - baseUrl, - patterns, - } = flags; - - // let store: UCDStore | null = null; - // if (remote) { - // store = await createRemoteUCDStore({ - // baseUrl, - // globalFilters: patterns, - // }); - // } else { - // store = await createLocalUCDStore({ - // basePath: storeDir, - // baseUrl, - // globalFilters: patterns, - // }); - // } - - // if (store == null) { - // console.error("Error: Failed to create UCD store."); - // return; - // } - - // const result = await store.analyze(); - - // if (!result.success) { - // console.error("Error: Failed to analyze UCD store."); - // console.error(result.error); - // return; - // } - - // if (json) { - // console.log(JSON.stringify(result, null, 2)); - // } else { - // console.log("UCD Store Status:"); - // console.log(`Total files: ${result.totalFiles}`); - // for (const [version, info] of Object.entries(result.versions)) { - // console.log(`Version: ${version}`); - // console.log(` Total files: ${info.fileCount}`); - // console.log(` Is Complete: ${info.isComplete}`); - // } - // } -} diff --git a/packages/env/package.json b/packages/env/package.json index dd6fa1fbc..32a89dbc1 100644 --- a/packages/env/package.json +++ b/packages/env/package.json @@ -32,7 +32,7 @@ "node": ">=22.17" }, "scripts": { - "build": "tsdown", + "build": "tsdown --tsconfig=./tsconfig.build.json", "dev": "tsdown --watch", "clean": "git clean -xdf dist node_modules", "lint": "eslint .", diff --git a/packages/fetch/package.json b/packages/fetch/package.json index f97163380..cb28722f4 100644 --- a/packages/fetch/package.json +++ b/packages/fetch/package.json @@ -32,7 +32,7 @@ "node": ">=22.17" }, "scripts": { - "build": "tsdown", + "build": "tsdown --tsconfig=./tsconfig.build.json", "dev": "tsdown --watch", "clean": "git clean -xdf dist node_modules", "lint": "eslint .", diff --git a/packages/fs-bridge/eslint.config.js b/packages/fs-bridge/eslint.config.js index c1bf22109..ecc434a96 100644 --- a/packages/fs-bridge/eslint.config.js +++ b/packages/fs-bridge/eslint.config.js @@ -1,11 +1,11 @@ // @ts-check -import { luxass } from "@luxass/eslint-config"; +import { GLOB_TESTS, luxass } from "@luxass/eslint-config"; export default luxass({ type: "lib", pnpm: true, }).append({ - ignores: ["src/bridges/node.ts"], + ignores: ["src/bridges/node.ts", ...GLOB_TESTS], rules: { "no-restricted-imports": ["error", { patterns: [ diff --git a/packages/fs-bridge/package.json b/packages/fs-bridge/package.json index cc7590598..2458475ba 100644 --- a/packages/fs-bridge/package.json +++ b/packages/fs-bridge/package.json @@ -35,7 +35,7 @@ "node": ">=22.17" }, "scripts": { - "build": "tsdown", + "build": "tsdown --tsconfig=./tsconfig.build.json", "dev": "tsdown --watch", "clean": "git clean -xdf dist node_modules", "lint": "eslint .", diff --git a/packages/fs-bridge/src/bridges/http.ts b/packages/fs-bridge/src/bridges/http.ts index eac5847a8..c357b97a3 100644 --- a/packages/fs-bridge/src/bridges/http.ts +++ b/packages/fs-bridge/src/bridges/http.ts @@ -90,7 +90,7 @@ const HTTPFileSystemBridge = defineFileSystemBridge({ const entries: FSEntry[] = []; for (const entry of data) { if (entry.type === "directory") { - const children = await this.listdir(entry.path, true); + const children = await this.listdir(joinURL(path, entry.path), true); entries.push({ type: "directory", name: entry.name, diff --git a/packages/fs-bridge/test/bridges/http.test.ts b/packages/fs-bridge/test/bridges/http.test.ts index ef63db973..25949913d 100644 --- a/packages/fs-bridge/test/bridges/http.test.ts +++ b/packages/fs-bridge/test/bridges/http.test.ts @@ -120,19 +120,19 @@ describe("http fs-bridge", () => { { type: "file" as const, name: "file1.txt", - path: "/dir/file1.txt", + path: "/file1.txt", lastModified: Date.now(), }, { type: "file" as const, name: "file2.txt", - path: "/dir/file2.txt", + path: "/file2.txt", lastModified: Date.now(), }, { type: "directory" as const, name: "subdir", - path: "/dir/subdir", + path: "/subdir", lastModified: Date.now(), }, ] satisfies FileEntry[]; @@ -154,18 +154,18 @@ describe("http fs-bridge", () => { expect(files).toEqual([ { name: "file1.txt", - path: "/dir/file1.txt", + path: "/file1.txt", type: "file", }, { name: "file2.txt", - path: "/dir/file2.txt", + path: "/file2.txt", type: "file", }, { children: [], name: "subdir", - path: "/dir/subdir", + path: "/subdir", type: "directory", }, @@ -177,7 +177,7 @@ describe("http fs-bridge", () => { { type: "file" as const, name: "nested.txt", - path: "/dir/subdir/nested.txt", + path: "/nested.txt", lastModified: Date.now(), }, ] satisfies FileEntry[]; @@ -199,14 +199,14 @@ describe("http fs-bridge", () => { const files = await bridge.listdir("dir", true); expect(files).toEqual([ - { type: "file", name: "file1.txt", path: "/dir/file1.txt" }, - { type: "file", name: "file2.txt", path: "/dir/file2.txt" }, + { type: "file", name: "file1.txt", path: "/file1.txt" }, + { type: "file", name: "file2.txt", path: "/file2.txt" }, { type: "directory", name: "subdir", - path: "/dir/subdir", + path: "/subdir", children: [ - { type: "file", name: "nested.txt", path: "/dir/subdir/nested.txt" }, + { type: "file", name: "nested.txt", path: "/nested.txt" }, ], }, ]); @@ -260,13 +260,13 @@ describe("http fs-bridge", () => { { type: "file" as const, name: "accessible.txt", - path: "/dir/accessible.txt", + path: "/accessible.txt", lastModified: Date.now(), }, { type: "directory" as const, name: "inaccessible", - path: "/dir/inaccessible", + path: "/inaccessible", lastModified: Date.now(), }, ] satisfies FileEntry[]), { @@ -290,10 +290,10 @@ describe("http fs-bridge", () => { const files = await bridge.listdir("dir", true); expect(files).toEqual([ - { type: "file", name: "accessible.txt", path: "/dir/accessible.txt" }, - { type: "directory", name: "inaccessible", path: "/dir/inaccessible", children: [] }, + { type: "file", name: "accessible.txt", path: "/accessible.txt" }, + { type: "directory", name: "inaccessible", path: "/inaccessible", children: [] }, ]); - expect(flattenFilePaths(files)).not.toContain("/dir/inaccessible/another-file.txt"); + expect(flattenFilePaths(files)).not.toContain("/inaccessible/another-file.txt"); }); }); diff --git a/packages/schema-gen/package.json b/packages/schema-gen/package.json index 6eb5c8590..b68a59629 100644 --- a/packages/schema-gen/package.json +++ b/packages/schema-gen/package.json @@ -32,7 +32,7 @@ "node": ">=22.17" }, "scripts": { - "build": "tsdown", + "build": "tsdown --tsconfig=./tsconfig.build.json", "dev": "tsdown --watch", "clean": "git clean -xdf dist node_modules", "lint": "eslint .", diff --git a/packages/schemas/package.json b/packages/schemas/package.json index cb6c5c32a..1a93df6bf 100644 --- a/packages/schemas/package.json +++ b/packages/schemas/package.json @@ -32,7 +32,7 @@ "node": ">=22.17" }, "scripts": { - "build": "tsdown", + "build": "tsdown --tsconfig=./tsconfig.build.json", "dev": "tsdown --watch", "clean": "git clean -xdf dist node_modules", "lint": "eslint .", diff --git a/packages/ucd-store/eslint.config.js b/packages/ucd-store/eslint.config.js index dd1f5bfbb..c3c48ce4e 100644 --- a/packages/ucd-store/eslint.config.js +++ b/packages/ucd-store/eslint.config.js @@ -1,11 +1,11 @@ // @ts-check -import { luxass } from "@luxass/eslint-config"; +import { GLOB_TESTS, luxass } from "@luxass/eslint-config"; export default luxass({ type: "lib", pnpm: true, }).append({ - ignores: ["playgrounds/node-playground.ts"], + ignores: ["playgrounds/node-playground.ts", ...GLOB_TESTS], rules: { "no-restricted-imports": ["error", { patterns: [ diff --git a/packages/ucd-store/package.json b/packages/ucd-store/package.json index dc970856b..655bdfdc2 100644 --- a/packages/ucd-store/package.json +++ b/packages/ucd-store/package.json @@ -32,7 +32,7 @@ "node": ">=22.17" }, "scripts": { - "build": "tsdown", + "build": "tsdown --tsconfig=./tsconfig.build.json", "dev": "tsdown --watch", "clean": "git clean -xdf dist node_modules", "lint": "eslint .", @@ -58,6 +58,7 @@ "@ucdjs/tsconfig": "workspace:*", "@ucdjs/tsdown-config": "workspace:*", "eslint": "catalog:linting", + "memfs": "catalog:dev", "publint": "catalog:dev", "tsdown": "catalog:dev", "tsx": "catalog:dev", diff --git a/packages/ucd-store/src/internal/capabilities.ts b/packages/ucd-store/src/internal/capabilities.ts index 63dd5602f..2b56468db 100644 --- a/packages/ucd-store/src/internal/capabilities.ts +++ b/packages/ucd-store/src/internal/capabilities.ts @@ -85,32 +85,3 @@ function getRequiredCapabilities(feature: keyof StoreCapabilities): FileSystemBr } return capabilities; } - -interface HasFileSystemBridge { - fs: FileSystemBridgeOperationsWithSymbol; -} - -export function requiresCapabilities(capability?: K) { - return function < - T extends HasFileSystemBridge, - M extends (...args: any[]) => Promise, - >( - target: T, - propertyKey: string | symbol, - descriptor: TypedPropertyDescriptor, - ): TypedPropertyDescriptor { - const originalMethod = descriptor.value!; - - const _capability = capability || propertyKey as K; - if (!_capability || !CAPABILITY_REQUIREMENTS[_capability]) { - throw new Error(`Invalid capability: ${_capability}`); - } - - descriptor.value = async function (this: T, ...args: Parameters): Promise>> { - assertCapabilities(_capability, this.fs); - return await originalMethod.apply(this, args); - } as M; - - return descriptor; - }; -} diff --git a/packages/ucd-store/src/internal/files.ts b/packages/ucd-store/src/internal/files.ts new file mode 100644 index 000000000..d2748ecd1 --- /dev/null +++ b/packages/ucd-store/src/internal/files.ts @@ -0,0 +1,38 @@ +import type { UCDClient } from "@ucdjs/fetch"; +import { isApiError } from "@ucdjs/fetch"; +import { flattenFilePaths } from "@ucdjs/utils"; +import { UCDStoreError } from "../errors"; + +/** + * Retrieves the expected file paths for a specific Unicode version from the API. + * + * This method fetches the canonical list of files that should exist for a given + * Unicode version by making an API call to the UCD service. The returned file + * paths represent the complete set of files that should be present in a properly + * synchronized store for the specified version. + * + * @param {UCDClient} client - The UCD client instance for making API requests + * @param {string} version - The Unicode version to get expected file paths for + * @returns {Promise} A promise that resolves to an array of file paths that should exist for the version + * + * @throws {UCDStoreError} When the API request fails or returns an error + */ +export async function getExpectedFilePaths( + client: UCDClient, + version: string, +): Promise { + // fetch the expected files for this version from the API + const { data, error } = await client.GET("/api/v1/versions/{version}/file-tree", { + params: { + path: { + version, + }, + }, + }); + + if (isApiError(error) || error != null || (data == null && error == null)) { + throw new UCDStoreError(`Failed to fetch expected files for version '${version}': ${error?.message}`); + } + + return flattenFilePaths(data!); +} diff --git a/packages/ucd-store/src/store.ts b/packages/ucd-store/src/store.ts index a93d73579..90fc7402c 100644 --- a/packages/ucd-store/src/store.ts +++ b/packages/ucd-store/src/store.ts @@ -1,17 +1,18 @@ -import type { UCDClient } from "@ucdjs/fetch"; +import type { UCDClient, UnicodeTreeNode } from "@ucdjs/fetch"; import type { FileSystemBridgeOperationsWithSymbol } from "@ucdjs/fs-bridge"; import type { UCDStoreManifest } from "@ucdjs/schemas"; import type { PathFilter } from "@ucdjs/utils"; -import type { StoreCapabilities, UCDStoreOptions } from "./types"; -import { invariant, prependLeadingSlash } from "@luxass/utils"; +import type { AnalyzeOptions, StoreCapabilities, UCDStoreOptions, VersionAnalysis } from "./types"; +import { invariant, prependLeadingSlash, trimLeadingSlash } from "@luxass/utils"; import { UCDJS_API_BASE_URL } from "@ucdjs/env"; import { createClient, isApiError } from "@ucdjs/fetch"; import { UCDStoreManifestSchema } from "@ucdjs/schemas"; -import { createPathFilter, safeJsonParse } from "@ucdjs/utils"; +import { createPathFilter, flattenFilePaths, safeJsonParse } from "@ucdjs/utils"; import defu from "defu"; import { join } from "pathe"; -import { UCDStoreError } from "./errors"; -import { inferStoreCapabilities } from "./internal/capabilities"; +import { UCDStoreError, UCDStoreVersionNotFoundError } from "./errors"; +import { assertCapabilities, inferStoreCapabilities } from "./internal/capabilities"; +import { getExpectedFilePaths } from "./internal/files"; export class UCDStore { /** @@ -39,10 +40,11 @@ export class UCDStore { }; constructor(options: UCDStoreOptions) { - const { baseUrl, globalFilters, fs, basePath } = defu(options, { + const { baseUrl, globalFilters, fs, basePath, versions } = defu(options, { baseUrl: UCDJS_API_BASE_URL, globalFilters: [], basePath: "", + versions: [], }); if (fs == null) { @@ -55,6 +57,7 @@ export class UCDStore { this.#filter = createPathFilter(globalFilters); this.#fs = fs as FileSystemBridgeOperationsWithSymbol; this.#capabilities = inferStoreCapabilities(this.#fs); + this.#versions = versions; this.#manifestPath = join(this.basePath, ".ucd-store.json"); } @@ -66,7 +69,7 @@ export class UCDStore { * allowing the store to work with different storage backends (local filesystem, * remote HTTP, in-memory, etc.) through a unified interface. * - * @returns {FileSystemBridgeOperations} The FileSystemBridge instance configured for this store + * @returns {FileSystemBridgeOperationsWithSymbol} The FileSystemBridge instance configured for this store */ get fs(): FileSystemBridgeOperationsWithSymbol { return this.#fs; @@ -111,6 +114,27 @@ export class UCDStore { return Object.freeze([...this.#versions]); } + async getFileTree(version: string, extraFilters?: string[]): Promise { + if (!this.#versions.includes(version)) { + throw new UCDStoreVersionNotFoundError(version); + } + + const files = await this.#fs.listdir(join(this.basePath, version), true); + + // TODO: handle the cases where we wanna filter child files. + return files.filter(({ path }) => this.#filter(trimLeadingSlash(path), extraFilters)); + } + + async getFilePaths(version: string, extraFilters?: string[]): Promise { + if (!this.#versions.includes(version)) { + throw new UCDStoreVersionNotFoundError(version); + } + + const tree = await this.getFileTree(version, extraFilters); + + return flattenFilePaths(tree); + } + /** * Initialize the store - loads existing data or creates new structure */ @@ -142,6 +166,76 @@ export class UCDStore { } } + async analyze(options: AnalyzeOptions): Promise { + assertCapabilities("analyze", this.#fs); + const { + checkOrphaned = false, + versions = this.#versions, + } = options; + + let versionAnalyses: VersionAnalysis[] = []; + + try { + const promises = versions.map(async (version) => { + if (!this.versions.includes(version)) { + throw new UCDStoreVersionNotFoundError(version); + } + + return this.#analyzeVersion(version, { + checkOrphaned, + }); + }); + + versionAnalyses = await Promise.all(promises); + + return versionAnalyses; + } catch (err) { + console.error(`Error during store analysis: ${err instanceof Error ? err.message : String(err)}`); + return versionAnalyses; + } + } + + async #analyzeVersion(version: string, options: AnalyzeOptions): Promise { + assertCapabilities("analyze", this.#fs); + const { checkOrphaned } = options; + + if (!this.#versions.includes(version)) { + throw new UCDStoreVersionNotFoundError(version); + } + + // get the expected files for this version + const expectedFiles = await getExpectedFilePaths(this.#client, version); + + // get the actual files from the store + const actualFiles = await this.getFilePaths(version); + + const orphanedFiles: string[] = []; + const missingFiles: string[] = []; + + for (const expectedFile of expectedFiles) { + if (!actualFiles.includes(expectedFile)) { + missingFiles.push(expectedFile); + } + } + + for (const actualFile of actualFiles) { + // if file is not in expected files, it's orphaned + if (checkOrphaned && !expectedFiles.includes(actualFile)) { + orphanedFiles.push(actualFile); + } + } + + const isComplete = orphanedFiles.length === 0 && missingFiles.length === 0; + return { + version, + orphanedFiles, + missingFiles, + totalFileCount: expectedFiles.length, + fileCount: actualFiles.length, + isComplete, + }; + } + async #loadVersionsFromStore(): Promise { try { const manifestContent = await this.#fs.read(this.#manifestPath); diff --git a/packages/ucd-store/src/types.ts b/packages/ucd-store/src/types.ts index 495028d94..2bf7f04c2 100644 --- a/packages/ucd-store/src/types.ts +++ b/packages/ucd-store/src/types.ts @@ -58,3 +58,52 @@ export interface StoreCapabilities { */ repair: boolean; } + +export interface AnalyzeOptions { + /** + * Whether to check for orphaned files in the store. + * Orphaned files are those that are not referenced by any Unicode version or data. + * This can help identify files that are no longer needed. + */ + checkOrphaned?: boolean; + + /** + * Specific versions to analyze (if not provided, analyzes all) + */ + versions?: string[]; +} + +export interface VersionAnalysis { + /** + * Analyzed Unicode version + * This should be in the format "major.minor.patch" (e.g., "15.0.0") + */ + version: string; + + /** + * List of orphaned files (files that exist but shouldn't) + */ + orphanedFiles: string[]; + + /** + * List of missing files (if any) + */ + missingFiles: string[]; + + /** + * Total number of files expected for this version + */ + totalFileCount: number; + + /** + * Number of files found for this version + */ + fileCount: number; + + /** + * Whether the version is complete + * This means all expected files are present and no orphaned files exist. + * If this is false, it indicates that some files are missing or there are orphaned files. + */ + isComplete: boolean; +} diff --git a/packages/ucd-store/test/__shared.ts b/packages/ucd-store/test/__shared.ts index 661e095b7..493f88f72 100644 --- a/packages/ucd-store/test/__shared.ts +++ b/packages/ucd-store/test/__shared.ts @@ -1,4 +1,9 @@ +import type { FSEntry } from "@ucdjs/fs-bridge"; +import type Dirent from "memfs/lib/Dirent"; +import { dirname, join, relative } from "node:path"; +import { prependLeadingSlash, trimTrailingSlash } from "@luxass/utils"; import { defineFileSystemBridge } from "@ucdjs/fs-bridge"; +import { memfs } from "memfs"; export const createReadOnlyMockFS = defineFileSystemBridge({ capabilities: { @@ -46,38 +51,105 @@ export const createMemoryMockFS = defineFileSystemBridge({ capabilities: { read: true, write: true, - listdir: false, + listdir: true, mkdir: false, exists: true, rm: false, }, state: { - map: new Map(), + fs: memfs().fs, }, setup({ state }) { return { async read(path) { - const content = state.map.get(path); - if (content === undefined) { - throw new Error(`File not found: ${path}`); - } - return content; + return state.fs.readFileSync(path, "utf8") as string; }, async exists(path) { - return state.map.has(path); + return state.fs.existsSync(path); }, - async listdir() { - throw new Error("listdir not supported"); + async listdir(path, recursive = false) { + function createFSEntry(entry: Dirent): FSEntry { + const name = entry.name.toString(); + const pathFromName = prependLeadingSlash(trimTrailingSlash(name)); + return entry.isDirectory() + ? { + type: "directory", + name, + path: pathFromName, + children: [], + } + : { + type: "file", + name, + path: pathFromName, + }; + } + + if (!recursive) { + const entries = state.fs.readdirSync(path, { withFileTypes: true }) as Dirent[]; + return entries.map((entry) => createFSEntry(entry)); + } + + const allEntries = state.fs.readdirSync(path, { + withFileTypes: true, + recursive: true, + }) as Dirent[]; + + const entryMap = new Map(); + const rootEntries: FSEntry[] = []; + + for (const entry of allEntries) { + const entryPath = entry.parentPath || entry.path; + const relativeToTarget = relative(path, entryPath); + const fsEntry = createFSEntry(entry); + + const entryRelativePath = relativeToTarget + ? join(relativeToTarget, entry.name.toString()) + : entry.name.toString(); + + entryMap.set(entryRelativePath, fsEntry); + + if (!relativeToTarget) { + rootEntries.push(fsEntry); + } + } + + for (const [entryPath, entry] of entryMap) { + const parentPath = dirname(entryPath); + if (parentPath && parentPath !== ".") { + const parent = entryMap.get(parentPath); + if (parent?.type === "directory") { + parent.children!.push(entry); + } + } + } + + return rootEntries; }, async mkdir(path) { - state.map.set(path, ""); + state.fs.mkdirSync(path); }, async rm(path) { - state.map.delete(path); + state.fs.rmSync(path); }, - async write(path, data) { - state.map.set(path, data); + async write(path, data, encoding = "utf8") { + // ensure the directory exists + const dir = dirname(path); + if (!state.fs.existsSync(dir)) { + state.fs.mkdirSync(dir, { recursive: true }); + } + + state.fs.writeFileSync(path, data, { + encoding, + }); }, }; }, }); + +export function stripChildrenFromEntries(entries: TObj[]): Omit[] { + return entries.map((entry) => { + const { children: _children, ...rest } = entry; + return rest; + }); +} diff --git a/packages/ucd-store/test/internal/files.test.ts b/packages/ucd-store/test/internal/files.test.ts new file mode 100644 index 000000000..c45d4d953 --- /dev/null +++ b/packages/ucd-store/test/internal/files.test.ts @@ -0,0 +1,80 @@ +import type { ApiError, UnicodeTree } from "@ucdjs/fetch"; +import { HttpResponse, mockFetch } from "#msw-utils"; +import { UCDJS_API_BASE_URL } from "@ucdjs/env"; +import { client } from "@ucdjs/fetch"; +import { describe, expect, it } from "vitest"; +import { UCDStoreError } from "../../src/errors"; +import { getExpectedFilePaths } from "../../src/internal/files"; + +describe("getExpectedFilePaths", () => { + it("should return flattened file paths for valid version", async () => { + mockFetch([ + ["GET", `${UCDJS_API_BASE_URL}/api/v1/versions/:version/file-tree`, () => { + return HttpResponse.json([ + { + type: "file", + name: "ReadMe.txt", + path: "/ReadMe.txt", + lastModified: Date.now(), + }, + { + type: "file", + name: "UnicodeData.txt", + path: "/UnicodeData.txt", + lastModified: Date.now(), + }, + { + type: "directory", + name: "ucd", + path: "/ucd", + lastModified: Date.now(), + children: [ + { + type: "file", + name: "emoji-data.txt", + path: "/emoji-data.txt", + lastModified: Date.now(), + }, + ], + }, + ] satisfies UnicodeTree); + }], + ]); + + const result = await getExpectedFilePaths(client, "15.0.0"); + + expect(result).toEqual([ + "/ReadMe.txt", + "/UnicodeData.txt", + "/ucd/emoji-data.txt", + ]); + }); + + it("should throw UCDStoreError when API returns error", async () => { + mockFetch([ + ["GET", `${UCDJS_API_BASE_URL}/api/v1/versions/:version/file-tree`, () => { + return HttpResponse.json({ + message: "Version not found", + status: 404, + timestamp: new Date().toISOString(), + } satisfies ApiError, { status: 404 }); + }], + ]); + + await expect( + getExpectedFilePaths(client, "15.0.0"), + ).rejects.toThrow(UCDStoreError); + }); + + it("should handle empty file tree", async () => { + mockFetch([ + ["GET", `${UCDJS_API_BASE_URL}/api/v1/versions/:version/file-tree`, () => { + return HttpResponse.json([], { status: 200 }); + }], + ]); + + const result = await getExpectedFilePaths(client, "15.0.0"); + + expect(result).toEqual([]); + }); +}); diff --git a/packages/ucd-store/test/store-analyze.test.ts b/packages/ucd-store/test/store-analyze.test.ts new file mode 100644 index 000000000..4af79b724 --- /dev/null +++ b/packages/ucd-store/test/store-analyze.test.ts @@ -0,0 +1,372 @@ +import { HttpResponse, mockFetch } from "#msw-utils"; +import { UCDJS_API_BASE_URL } from "@ucdjs/env"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { testdir } from "vitest-testdirs"; +import { createHTTPUCDStore, createNodeUCDStore, createUCDStore } from "../src/factory"; +import { createMemoryMockFS, stripChildrenFromEntries } from "./__shared"; + +describe("analyze operations", () => { + beforeEach(() => { + vi.clearAllMocks(); + vi.unstubAllEnvs(); + }); + + const mockFiles = [ + { + type: "file", + name: "ArabicShaping.txt", + path: "/ArabicShaping.txt", + lastModified: 1644920820000, + }, + { + type: "file", + name: "BidiBrackets.txt", + path: "/BidiBrackets.txt", + lastModified: 1651584360000, + }, + { + type: "directory", + name: "extracted", + path: "/extracted", + lastModified: 1724676960000, + children: [ + { + type: "file", + name: "DerivedBidiClass.txt", + path: "/DerivedBidiClass.txt", + lastModified: 1724609100000, + }, + ], + }, + ]; + + describe("local store analyze operations", () => { + it("should analyze local store with complete files", async () => { + const storeStructure = { + "15.0.0": { + "ArabicShaping.txt": "Arabic shaping data", + "BidiBrackets.txt": "Bidi brackets data", + "extracted": { + "DerivedBidiClass.txt": "Derived bidi class data", + }, + }, + ".ucd-store.json": JSON.stringify({ + "15.0.0": "/15.0.0", + }), + }; + + const storeDir = await testdir(storeStructure); + + mockFetch([ + ["GET", `${UCDJS_API_BASE_URL}/api/v1/versions/15.0.0/file-tree`, () => { + return HttpResponse.json(mockFiles); + }], + ]); + + const store = await createNodeUCDStore({ + basePath: storeDir, + }); + + const analyzeResult = await store.analyze({ checkOrphaned: false }); + + expect(analyzeResult).toHaveLength(1); + expect(analyzeResult[0]?.version).toBe("15.0.0"); + expect(analyzeResult[0]?.isComplete).toBe(true); + expect(analyzeResult[0]?.fileCount).toBe(3); + expect(analyzeResult[0]?.orphanedFiles).toEqual([]); + expect(analyzeResult[0]?.missingFiles).toEqual([]); + }); + + it("should analyze local store with orphaned files", async () => { + const storeStructure = { + "15.0.0": { + "ArabicShaping.txt": "Arabic shaping data", + "BidiBrackets.txt": "Bidi brackets data", + "extracted": { + "DerivedBidiClass.txt": "Derived bidi class data", + }, + "OrphanedFile.txt": "This shouldn't be here", + }, + ".ucd-store.json": JSON.stringify({ + "15.0.0": "/15.0.0", + }), + }; + + const storeDir = await testdir(storeStructure); + + mockFetch([ + ["GET", `${UCDJS_API_BASE_URL}/api/v1/versions/15.0.0/file-tree`, () => { + return HttpResponse.json(mockFiles); + }], + ]); + + const store = await createNodeUCDStore({ + basePath: storeDir, + }); + + const analysisResult = await store.analyze({ checkOrphaned: true }); + + expect(analysisResult).toHaveLength(1); + expect(analysisResult[0]?.version).toBe("15.0.0"); + expect(analysisResult[0]?.isComplete).toBe(false); + expect(analysisResult[0]?.fileCount).toBe(4); + expect(analysisResult[0]?.orphanedFiles).toContain("/OrphanedFile.txt"); + expect(analysisResult[0]?.missingFiles).toEqual([]); + }); + + it("should analyze multiple versions", async () => { + const storeStructure = { + "15.0.0": { + "ArabicShaping.txt": "Arabic shaping data v15.0.0", + }, + "15.1.0": { + "BidiBrackets.txt": "Bidi brackets data v15.1.0", + }, + ".ucd-store.json": JSON.stringify({ + "15.0.0": "/15.0.0", + "15.1.0": "/15.1.0", + }), + }; + + const storeDir = await testdir(storeStructure); + + mockFetch([ + ["GET", `${UCDJS_API_BASE_URL}/api/v1/versions/:version/file-tree`, ({ params }) => { + const { version } = params; + if (version === "15.0.0") { + return HttpResponse.json([mockFiles[0]]); + } + if (version === "15.1.0") { + return HttpResponse.json([mockFiles[1]]); + } + return HttpResponse.json([]); + }], + ]); + + const store = await createNodeUCDStore({ + basePath: storeDir, + }); + + const analysisResult = await store.analyze({ checkOrphaned: false }); + + expect(analysisResult).toHaveLength(2); + + const v15_0_0 = analysisResult.find((v) => v.version === "15.0.0"); + const v15_1_0 = analysisResult.find((v) => v.version === "15.1.0"); + + expect(v15_0_0?.isComplete).toBe(true); + expect(v15_0_0?.fileCount).toBe(1); + expect(v15_1_0?.isComplete).toBe(true); + expect(v15_1_0?.fileCount).toBe(1); + }); + + it("should analyze specific versions only", async () => { + const storeStructure = { + "15.0.0": { + "ArabicShaping.txt": "Arabic shaping data", + }, + "15.1.0": { + "BidiBrackets.txt": "Bidi brackets data", + }, + ".ucd-store.json": JSON.stringify({ + "15.0.0": "/15.0.0", + "15.1.0": "/15.1.0", + }), + }; + + const storeDir = await testdir(storeStructure); + + mockFetch([ + ["GET", `${UCDJS_API_BASE_URL}/api/v1/versions/15.0.0/file-tree`, () => { + return HttpResponse.json([mockFiles[0]]); + }], + ]); + + const store = await createNodeUCDStore({ + basePath: storeDir, + }); + + const analysisResult = await store.analyze({ + versions: ["15.0.0"], + checkOrphaned: false, + }); + + expect(analysisResult).toHaveLength(1); + expect(analysisResult[0]?.version).toBe("15.0.0"); + }); + + it("should handle version not found error", async () => { + const storeStructure = { + "15.0.0": { + "ArabicShaping.txt": "Arabic shaping data", + }, + ".ucd-store.json": JSON.stringify({ + "15.0.0": "/15.0.0", + }), + }; + + const storeDir = await testdir(storeStructure); + + const store = await createNodeUCDStore({ + basePath: storeDir, + }); + + const analysisResult = await store.analyze({ + versions: ["99.99.99"], + checkOrphaned: false, + }); + + expect(analysisResult).toEqual([]); + }); + + it("should handle API errors gracefully", async () => { + const storeStructure = { + "15.0.0": { + "ArabicShaping.txt": "Arabic shaping data", + }, + ".ucd-store.json": JSON.stringify({ + "15.0.0": "/15.0.0", + }), + }; + + const storeDir = await testdir(storeStructure); + + mockFetch([ + ["GET", `${UCDJS_API_BASE_URL}/api/v1/versions/15.0.0/file-tree`, () => { + return new Response(null, { status: 500 }); + }], + ]); + + const store = await createNodeUCDStore({ + basePath: storeDir, + }); + + const analysisResult = await store.analyze({ checkOrphaned: false }); + + expect(analysisResult).toEqual([]); + }); + }); + + describe("remote store analyze operations", () => { + it("should analyze remote store with complete files", async () => { + mockFetch([ + [["GET", "HEAD"], `${UCDJS_API_BASE_URL}/api/v1/files/.ucd-store.json`, () => { + return HttpResponse.json({ + "15.0.0": "/15.0.0", + }); + }], + ["GET", `${UCDJS_API_BASE_URL}/api/v1/versions/15.0.0/file-tree`, () => { + return HttpResponse.json(mockFiles); + }], + ["GET", `${UCDJS_API_BASE_URL}/api/v1/files/15.0.0/:file?`, ({ params }) => { + const file = params.file; + + if (file === "extracted") { + return HttpResponse.json(mockFiles[2]?.children); + } + + return HttpResponse.json(stripChildrenFromEntries(mockFiles)); + }], + ]); + + const store = await createHTTPUCDStore(); + + const analysisResult = await store.analyze({ checkOrphaned: false }); + + expect(analysisResult).toHaveLength(1); + expect(analysisResult[0]?.version).toBe("15.0.0"); + expect(analysisResult[0]?.isComplete).toBe(true); + expect(analysisResult[0]?.fileCount).toBe(3); + }); + + it("should handle remote store with no versions", async () => { + mockFetch([ + [["GET", "HEAD"], `${UCDJS_API_BASE_URL}/api/v1/files/.ucd-store.json`, () => { + return HttpResponse.json({}); + }], + ]); + + const store = await createHTTPUCDStore(); + + const analysisResult = await store.analyze({ checkOrphaned: false }); + + expect(analysisResult).toEqual([]); + + const totalFileCount = analysisResult.reduce((sum, { fileCount }) => sum + fileCount, 0); + expect(totalFileCount).toBe(0); + }); + }); + + describe("custom store analyze operations", () => { + it("should analyze store with custom filesystem bridge", async () => { + const customFS = createMemoryMockFS(); + await customFS.write("/.ucd-store.json", JSON.stringify({ + "15.0.0": "/15.0.0", + })); + await customFS.write("/15.0.0/ArabicShaping.txt", "Arabic shaping data"); + + mockFetch([ + ["GET", `${UCDJS_API_BASE_URL}/api/v1/files/15.0.0`, () => { + return HttpResponse.json([mockFiles[0]]); + }], + ]); + + mockFetch([ + ["GET", `${UCDJS_API_BASE_URL}/api/v1/versions/15.0.0/file-tree`, () => { + return HttpResponse.json([mockFiles[0]]); + }], + ]); + + const store = await createUCDStore({ + basePath: "/", + fs: customFS, + }); + + const analysisResult = await store.analyze({ checkOrphaned: false }); + + expect(analysisResult).toHaveLength(1); + expect(analysisResult[0]?.isComplete).toBe(true); + }); + }); + + it("should handle empty store", async () => { + const storeDir = await testdir({ + ".ucd-store.json": JSON.stringify({}), + }); + + const store = await createNodeUCDStore({ + basePath: storeDir, + }); + + const analysisResult = await store.analyze({ checkOrphaned: false }); + + expect(analysisResult).toEqual([]); + expect(analysisResult.length).toBe(0); + }); + + it("should analyze store with no files", async () => { + const storeDir = await testdir({ + "15.0.0": {}, + ".ucd-store.json": JSON.stringify({ + "15.0.0": "/15.0.0", + }), + }); + + mockFetch([ + ["GET", `${UCDJS_API_BASE_URL}/api/v1/versions/15.0.0/file-tree`, () => { + return HttpResponse.json([]); + }], + ]); + + const store = await createNodeUCDStore({ + basePath: storeDir, + }); + + const analysisResult = await store.analyze({ checkOrphaned: false }); + + expect(analysisResult).toHaveLength(1); + expect(analysisResult[0]?.version).toBe("15.0.0"); + expect(analysisResult[0]?.fileCount).toBe(0); + expect(analysisResult[0]?.isComplete).toBe(true); + }); +}); diff --git a/packages/ucd-store/tsconfig.build.json b/packages/ucd-store/tsconfig.build.json index 088a39580..ec6cc954e 100644 --- a/packages/ucd-store/tsconfig.build.json +++ b/packages/ucd-store/tsconfig.build.json @@ -1,8 +1,5 @@ { "extends": "@ucdjs/tsconfig/base.build", - "compilerOptions": { - "experimentalDecorators": true - }, "include": ["src"], "exclude": ["dist"] } diff --git a/packages/utils/package.json b/packages/utils/package.json index 10d6a38f4..02bcd321e 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -32,7 +32,7 @@ "node": ">=22.17" }, "scripts": { - "build": "tsdown", + "build": "tsdown --tsconfig=./tsconfig.build.json", "dev": "tsdown --watch", "clean": "git clean -xdf dist node_modules", "lint": "eslint .", @@ -44,7 +44,6 @@ "@ucdjs/env": "workspace:*", "@ucdjs/fetch": "workspace:*", "defu": "catalog:prod", - "memfs": "catalog:prod", "picomatch": "catalog:prod", "zod": "catalog:prod" }, diff --git a/packages/utils/src/flatten.ts b/packages/utils/src/flatten.ts index 4e4060ffc..4eab583e6 100644 --- a/packages/utils/src/flatten.ts +++ b/packages/utils/src/flatten.ts @@ -23,6 +23,10 @@ import { prependLeadingSlash } from "@luxass/utils"; export function flattenFilePaths(entries: UnicodeTreeNode[], prefix: string = ""): string[] { const paths: string[] = []; + if (!Array.isArray(entries)) { + throw new TypeError("Expected 'entries' to be an array of UnicodeTreeNode"); + } + for (const file of entries) { const fullPath = prefix ? `${prefix}${prependLeadingSlash(file.path ?? file.name)}` diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 79ee0de4c..ba02bfa27 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,6 +15,9 @@ catalogs: '@types/yargs-parser': specifier: ^21.0.3 version: 21.0.3 + memfs: + specifier: ^4.23.0 + version: 4.25.0 nanotar: specifier: ^0.2.0 version: 0.2.0 @@ -38,8 +41,8 @@ catalogs: specifier: ^1.52.3 version: 1.52.3 '@luxass/eslint-config': - specifier: ^5.2.0 - version: 5.2.0 + specifier: ^5.2.1 + version: 5.2.1 '@luxass/spectral-ruleset': specifier: ^1.1.0 version: 1.1.0 @@ -102,9 +105,6 @@ catalogs: knitwork: specifier: ^1.2.0 version: 1.2.0 - memfs: - specifier: ^4.23.0 - version: 4.23.0 openapi-fetch: specifier: ^0.14.0 version: 0.14.0 @@ -273,7 +273,7 @@ importers: version: 0.8.58(@cloudflare/workers-types@4.20250726.0)(@vitest/runner@3.2.4)(@vitest/snapshot@3.2.4)(vitest@3.2.4) '@luxass/eslint-config': specifier: catalog:linting - version: 5.2.0(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4) + version: 5.2.1(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4) '@luxass/spectral-ruleset': specifier: catalog:linting version: 1.1.0 @@ -334,7 +334,7 @@ importers: version: 1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3) '@luxass/eslint-config': specifier: catalog:linting - version: 5.2.0(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4) + version: 5.2.1(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4) '@tailwindcss/vite': specifier: catalog:web version: 4.1.11(vite@7.0.6(@types/node@24.0.7)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.43.1)(tsx@4.20.3)(yaml@2.8.0)) @@ -401,7 +401,7 @@ importers: devDependencies: '@luxass/eslint-config': specifier: catalog:linting - version: 5.2.0(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4) + version: 5.2.1(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4) '@types/yargs-parser': specifier: catalog:dev version: 21.0.3 @@ -435,7 +435,7 @@ importers: devDependencies: '@luxass/eslint-config': specifier: catalog:linting - version: 5.2.0(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4) + version: 5.2.1(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4) '@ucdjs/tsconfig': specifier: workspace:* version: link:../../tooling/tsconfig @@ -466,7 +466,7 @@ importers: devDependencies: '@luxass/eslint-config': specifier: catalog:linting - version: 5.2.0(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4) + version: 5.2.1(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4) '@luxass/utils': specifier: catalog:prod version: 2.6.2 @@ -521,7 +521,7 @@ importers: devDependencies: '@luxass/eslint-config': specifier: catalog:linting - version: 5.2.0(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4) + version: 5.2.1(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4) '@ucdjs/tsconfig': specifier: workspace:* version: link:../../tooling/tsconfig @@ -573,7 +573,7 @@ importers: devDependencies: '@luxass/eslint-config': specifier: catalog:linting - version: 5.2.0(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4) + version: 5.2.1(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4) '@ucdjs/tsconfig': specifier: workspace:* version: link:../../tooling/tsconfig @@ -607,7 +607,7 @@ importers: devDependencies: '@luxass/eslint-config': specifier: catalog:linting - version: 5.2.0(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4) + version: 5.2.1(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4) '@ucdjs/tsconfig': specifier: workspace:* version: link:../../tooling/tsconfig @@ -662,7 +662,7 @@ importers: devDependencies: '@luxass/eslint-config': specifier: catalog:linting - version: 5.2.0(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4) + version: 5.2.1(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4) '@ucdjs/tsconfig': specifier: workspace:* version: link:../../tooling/tsconfig @@ -672,6 +672,9 @@ importers: eslint: specifier: catalog:linting version: 9.32.0(jiti@2.4.2) + memfs: + specifier: catalog:dev + version: 4.25.0 publint: specifier: catalog:dev version: 0.3.12 @@ -705,9 +708,6 @@ importers: defu: specifier: catalog:prod version: 6.1.4 - memfs: - specifier: catalog:prod - version: 4.23.0 picomatch: specifier: catalog:prod version: 4.0.3 @@ -717,7 +717,7 @@ importers: devDependencies: '@luxass/eslint-config': specifier: catalog:linting - version: 5.2.0(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4) + version: 5.2.1(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4) '@types/picomatch': specifier: catalog:dev version: 4.0.2 @@ -763,7 +763,7 @@ importers: version: 4.20250726.0 '@luxass/eslint-config': specifier: catalog:linting - version: 5.2.0(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4) + version: 5.2.1(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4) '@ucdjs/tsconfig': specifier: workspace:* version: link:../../tooling/tsconfig @@ -781,7 +781,7 @@ importers: devDependencies: '@luxass/eslint-config': specifier: catalog:linting - version: 5.2.0(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4) + version: 5.2.1(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4) '@typescript-eslint/utils': specifier: catalog:linting version: 8.38.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3) @@ -1674,8 +1674,8 @@ packages: peerDependencies: tslib: '2' - '@luxass/eslint-config@5.2.0': - resolution: {integrity: sha512-iCo+MP8V8qHVpVg+PqDgg/K+DeVyIvWRgG/vhulyZ8MPMyG5cBz+gax1rciw1zBz3mrOFmzIOXWP2K3TN770cA==} + '@luxass/eslint-config@5.2.1': + resolution: {integrity: sha512-EIUzYEiThpc+OmmJqVl4NCIarpg3gUM9lrQOB5ZIMfjWRt/ltoeqcFex9LJwbfG7aW8udKAbS1XLZcrCpi5i+g==} engines: {node: '>=20'} peerDependencies: '@eslint-react/eslint-plugin': ^1.19.0 @@ -3148,8 +3148,8 @@ packages: peerDependencies: eslint: ^9.5.0 - eslint-flat-config-utils@2.1.0: - resolution: {integrity: sha512-6fjOJ9tS0k28ketkUcQ+kKptB4dBZY2VijMZ9rGn8Cwnn1SH0cZBoPXT8AHBFHxmHcLFQK9zbELDinZ2Mr1rng==} + eslint-flat-config-utils@2.1.1: + resolution: {integrity: sha512-K8eaPkBemHkfbYsZH7z4lZ/tt6gNSsVh535Wh9W9gQBS2WjvfUbbVr2NZR3L1yiRCLuOEimYfPxCxODczD4Opg==} eslint-formatting-reporter@0.0.0: resolution: {integrity: sha512-k9RdyTqxqN/wNYVaTk/ds5B5rA8lgoAmvceYN7bcZMBwU7TuXx5ntewJv81eF3pIL/CiJE+pJZm36llG8yhyyw==} @@ -3201,8 +3201,8 @@ packages: typescript: optional: true - eslint-plugin-jsdoc@52.0.0: - resolution: {integrity: sha512-KZjaoTWWUIml6K6zyPvwCYlLoMDQ69taSdTcdTIavBUoJCIWUfYcsRIw4n9dzllMouqdxiFfKW33EAbBLBu1HA==} + eslint-plugin-jsdoc@52.0.1: + resolution: {integrity: sha512-zJdjpC9z4x28BBdCoxH+/h0BdDLZ9KXQGVKU9gHt3TQJ9kMraep+DgfTIVaXu9u1wy0HyhK2eFMp3icQBV4dkw==} engines: {node: '>=20.11.0'} peerDependencies: eslint: ^7.0.0 || ^8.0.0 || ^9.0.0 @@ -3331,8 +3331,8 @@ packages: '@typescript-eslint/eslint-plugin': optional: true - eslint-plugin-vue@10.3.0: - resolution: {integrity: sha512-A0u9snqjCfYaPnqqOaH6MBLVWDUIN4trXn8J3x67uDcXvR7X6Ut8p16N+nYhMCQ9Y7edg2BIRGzfyZsY0IdqoQ==} + eslint-plugin-vue@10.4.0: + resolution: {integrity: sha512-K6tP0dW8FJVZLQxa2S7LcE1lLw3X8VvB3t887Q6CLrFVxHYBXGANbXvwNzYIu6Ughx1bSJ5BDT0YB3ybPT39lw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: '@typescript-eslint/parser': ^7.0.0 || ^8.0.0 @@ -3426,9 +3426,6 @@ packages: resolution: {integrity: sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==} engines: {node: '>=12.0.0'} - exsolve@1.0.5: - resolution: {integrity: sha512-pz5dvkYYKQ1AHVrgOzBKWeP4u4FRb3a6DNK2ucr0OoNwYIU4QWsJ+NM36LLzORT+z845MzKHHhpXiUF5nvQoJg==} - exsolve@1.0.7: resolution: {integrity: sha512-VO5fQUzZtI6C+vx4w/4BWJpg3s/5l+6pRQEHzFRM8WFi4XffSP1Z+4qi7GbjWbvRQEbdIco5mIMq+zX4rPuLrw==} @@ -4186,8 +4183,8 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} - memfs@4.23.0: - resolution: {integrity: sha512-SucHN2lcWf0jrnw+jP6FoVW6l/zGJiXfNMdApZzG0x/0mAIMdwAeR5mjfsCH5U3BoqpUEtqzz+dSQSO0H/eqxg==} + memfs@4.25.0: + resolution: {integrity: sha512-VZhVrmQJFuWhjGAye01xO2N7SQZ507j3rq5mfebhcXbYTj7b4iapuA1w+LPJEp+uGKtDTYmBWZa9BNZDem1yuA==} engines: {node: '>= 4.0.0'} merge2@1.4.1: @@ -6659,7 +6656,7 @@ snapshots: dependencies: tslib: 2.8.1 - '@luxass/eslint-config@5.2.0(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4)': + '@luxass/eslint-config@5.2.1(@eslint-react/eslint-plugin@1.52.3(eslint@9.32.0(jiti@2.4.2))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3))(@vue/compiler-sfc@3.5.13)(eslint-plugin-format@1.0.1(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-hooks@5.2.0(eslint@9.32.0(jiti@2.4.2)))(eslint-plugin-react-refresh@0.4.20(eslint@9.32.0(jiti@2.4.2)))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4)': dependencies: '@antfu/install-pkg': 1.1.0 '@clack/prompts': 0.11.0 @@ -6671,11 +6668,11 @@ snapshots: '@vitest/eslint-plugin': 1.3.4(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3)(vitest@3.2.4) eslint: 9.32.0(jiti@2.4.2) eslint-config-flat-gitignore: 2.1.0(eslint@9.32.0(jiti@2.4.2)) - eslint-flat-config-utils: 2.1.0 + eslint-flat-config-utils: 2.1.1 eslint-merge-processors: 2.0.0(eslint@9.32.0(jiti@2.4.2)) eslint-plugin-antfu: 3.1.1(eslint@9.32.0(jiti@2.4.2)) eslint-plugin-import-lite: 0.3.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3) - eslint-plugin-jsdoc: 52.0.0(eslint@9.32.0(jiti@2.4.2)) + eslint-plugin-jsdoc: 52.0.1(eslint@9.32.0(jiti@2.4.2)) eslint-plugin-jsonc: 2.20.1(eslint@9.32.0(jiti@2.4.2)) eslint-plugin-n: 17.21.3(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3) eslint-plugin-perfectionist: 4.15.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3) @@ -6684,7 +6681,7 @@ snapshots: eslint-plugin-toml: 0.12.0(eslint@9.32.0(jiti@2.4.2)) eslint-plugin-unicorn: 60.0.0(eslint@9.32.0(jiti@2.4.2)) eslint-plugin-unused-imports: 4.1.4(@typescript-eslint/eslint-plugin@8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.32.0(jiti@2.4.2)) - eslint-plugin-vue: 10.3.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.32.0(jiti@2.4.2))(vue-eslint-parser@10.2.0(eslint@9.32.0(jiti@2.4.2))) + eslint-plugin-vue: 10.4.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.32.0(jiti@2.4.2))(vue-eslint-parser@10.2.0(eslint@9.32.0(jiti@2.4.2))) eslint-plugin-yml: 1.18.0(eslint@9.32.0(jiti@2.4.2)) eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.13)(eslint@9.32.0(jiti@2.4.2)) globals: 16.3.0 @@ -8402,7 +8399,7 @@ snapshots: '@eslint/compat': 1.2.8(eslint@9.32.0(jiti@2.4.2)) eslint: 9.32.0(jiti@2.4.2) - eslint-flat-config-utils@2.1.0: + eslint-flat-config-utils@2.1.1: dependencies: pathe: 2.0.3 @@ -8453,7 +8450,7 @@ snapshots: optionalDependencies: typescript: 5.8.3 - eslint-plugin-jsdoc@52.0.0(eslint@9.32.0(jiti@2.4.2)): + eslint-plugin-jsdoc@52.0.1(eslint@9.32.0(jiti@2.4.2)): dependencies: '@es-joy/jsdoccomment': 0.52.0 are-docs-informative: 0.0.2 @@ -8697,7 +8694,7 @@ snapshots: optionalDependencies: '@typescript-eslint/eslint-plugin': 8.38.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3) - eslint-plugin-vue@10.3.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.32.0(jiti@2.4.2))(vue-eslint-parser@10.2.0(eslint@9.32.0(jiti@2.4.2))): + eslint-plugin-vue@10.4.0(@typescript-eslint/parser@8.38.0(eslint@9.32.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.32.0(jiti@2.4.2))(vue-eslint-parser@10.2.0(eslint@9.32.0(jiti@2.4.2))): dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@9.32.0(jiti@2.4.2)) eslint: 9.32.0(jiti@2.4.2) @@ -8817,8 +8814,6 @@ snapshots: expect-type@1.2.1: {} - exsolve@1.0.5: {} - exsolve@1.0.7: {} extendable-error@0.1.7: {} @@ -9616,7 +9611,7 @@ snapshots: dependencies: '@types/mdast': 4.0.4 - memfs@4.23.0: + memfs@4.25.0: dependencies: '@jsonjoy.com/json-pack': 1.2.0(tslib@2.8.1) '@jsonjoy.com/util': 1.6.0(tslib@2.8.1) @@ -10133,7 +10128,7 @@ snapshots: pkg-types@2.1.0: dependencies: confbox: 0.2.2 - exsolve: 1.0.5 + exsolve: 1.0.7 pathe: 2.0.3 pluralize@8.0.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 6ebda849f..3445b500d 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -23,7 +23,7 @@ catalogs: "@cloudflare/vitest-pool-workers": ^0.8.58 linting: - "@luxass/eslint-config": ^5.2.0 + "@luxass/eslint-config": ^5.2.1 "@stoplight/spectral-cli": ^6.15.0 eslint: ^9.32.0 eslint-plugin-format: ^1.0.1 @@ -48,7 +48,6 @@ catalogs: p-limit: ^6.2.0 knitwork: ^1.2.0 picomatch: ^4.0.3 - memfs: ^4.23.0 apache-autoindex-parse: ^3.0.0 openapi-fetch: ^0.14.0 pathe: ^2.0.3 @@ -64,6 +63,7 @@ catalogs: nanotar: ^0.2.0 openapi-typescript: ^7.8.0 tsx: ^4.20.3 + memfs: ^4.23.0 workers: wrangler: ^4.26.1