diff --git a/.changeset/add-properties-file-plugin.md b/.changeset/add-properties-file-plugin.md
new file mode 100644
index 0000000000..cb592dc674
--- /dev/null
+++ b/.changeset/add-properties-file-plugin.md
@@ -0,0 +1,7 @@
+---
+"@inlang/plugin-properties-file": minor
+---
+
+feat: add .properties file format plugin
+
+Add support for Java .properties files as a translation storage format. Built on the `properties-file` npm package for robust parsing. Supports variable interpolation with `{variable}` syntax, inline comments for translator context, and optional key sorting.
diff --git a/packages/plugins/properties-file/.prettierrc b/packages/plugins/properties-file/.prettierrc
new file mode 100644
index 0000000000..3b3666e86e
--- /dev/null
+++ b/packages/plugins/properties-file/.prettierrc
@@ -0,0 +1,4 @@
+{
+ "useTabs": true,
+ "trailingComma": "es5"
+}
diff --git a/packages/plugins/properties-file/CHANGELOG.md b/packages/plugins/properties-file/CHANGELOG.md
new file mode 100644
index 0000000000..af76835163
--- /dev/null
+++ b/packages/plugins/properties-file/CHANGELOG.md
@@ -0,0 +1,10 @@
+# @inlang/plugin-properties-file
+
+## 0.1.0
+
+Initial release with support for:
+- Import/export of `.properties` files
+- Variable interpolation with `{variable}` syntax
+- Inline comment preservation
+- Key sorting (ascending/descending)
+- Multi-locale support
diff --git a/packages/plugins/properties-file/LICENSE b/packages/plugins/properties-file/LICENSE
new file mode 100644
index 0000000000..546b4fba6c
--- /dev/null
+++ b/packages/plugins/properties-file/LICENSE
@@ -0,0 +1,190 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to the Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by the Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding any notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ Copyright 2024 Opral US Inc.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/packages/plugins/properties-file/README.md b/packages/plugins/properties-file/README.md
new file mode 100644
index 0000000000..7b90b53b9e
--- /dev/null
+++ b/packages/plugins/properties-file/README.md
@@ -0,0 +1,88 @@
+# @inlang/plugin-properties-file
+
+Store translations in Java `.properties` files for use with [Paraglide JS](https://inlang.com/m/gerre34r/library-inlang-paraglideJs) and other inlang-compatible tools.
+
+Uses the [`properties-file`](https://github.com/properties-file/properties-file) package for robust parsing and serialization of the `.properties` format, including Unicode escapes, multi-line values, and comment handling.
+
+## Installation
+
+Add the plugin to your `project.inlang/settings.json`:
+
+```json
+{
+ "baseLocale": "en",
+ "locales": ["en", "fr", "de"],
+ "modules": [
+ "https://cdn.jsdelivr.net/npm/@inlang/plugin-properties-file@latest/dist/index.js"
+ ],
+ "plugin.inlang.propertiesFile": {
+ "pathPattern": "./messages/{locale}.properties"
+ }
+}
+```
+
+## File format
+
+Each locale has its own `.properties` file. Keys map directly to bundle IDs.
+
+```properties
+# Welcome message shown on the home page
+greeting = Hello {name}!
+items.count = You have {count} items
+farewell = Goodbye
+```
+
+## Variable interpolation
+
+Variables use the `{variableName}` syntax inside values:
+
+```properties
+welcome = Welcome back, {username}!
+notification = {sender} sent you {count} messages
+```
+
+These are converted to inlang expression pattern elements and declared as input variables on the bundle.
+
+## Comment support
+
+Comments immediately preceding a key-value pair are preserved during import and restored on export:
+
+```properties
+# This comment will be preserved
+greeting = Hello {name}!
+```
+
+## Settings reference
+
+| Setting | Type | Required | Description |
+| -------------- | ----------------------------- | -------- | ---------------------------------------------------------------- |
+| `pathPattern` | `string \| string[]` | Yes | Path(s) to `.properties` files. Must include `{locale}` and end with `.properties`. |
+| `sort` | `"asc" \| "desc"` | No | Sort keys alphabetically when exporting. |
+
+### Multiple path patterns
+
+You can specify multiple path patterns to load translations from several directories:
+
+```json
+{
+ "plugin.inlang.propertiesFile": {
+ "pathPattern": [
+ "./messages/{locale}.properties",
+ "./overrides/{locale}.properties"
+ ]
+ }
+}
+```
+
+### Key sorting
+
+Enable alphabetical key sorting on export:
+
+```json
+{
+ "plugin.inlang.propertiesFile": {
+ "pathPattern": "./messages/{locale}.properties",
+ "sort": "asc"
+ }
+}
+```
diff --git a/packages/plugins/properties-file/assets/icon.svg b/packages/plugins/properties-file/assets/icon.svg
new file mode 100644
index 0000000000..a8dc75e834
--- /dev/null
+++ b/packages/plugins/properties-file/assets/icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/packages/plugins/properties-file/build.js b/packages/plugins/properties-file/build.js
new file mode 100644
index 0000000000..7abf253ec6
--- /dev/null
+++ b/packages/plugins/properties-file/build.js
@@ -0,0 +1,34 @@
+import { context } from "esbuild";
+
+// eslint-disable-next-line no-undef
+const isProduction = process.env.NODE_ENV === "production";
+
+const ctx = await context({
+ entryPoints: ["./src/index.ts"],
+ outdir: "./dist",
+ // improve debugging by not minifying
+ minify: false,
+ // ----------------------------------
+ // allow top level await
+ // https://caniuse.com/mdn-javascript_operators_await_top_level
+ target: "es2022",
+ // inlang does not support import maps
+ bundle: true,
+ // esm to work in the browser
+ format: "esm",
+ //! extremly important to be platform neutral
+ //! to ensure that modules run in browser
+ //! and server contexts.
+ platform: "neutral",
+ // sourcemaps are unused at the moment
+ sourcemap: false,
+});
+
+if (isProduction === false) {
+ await ctx.watch();
+ // eslint-disable-next-line no-undef
+ console.info("Watching for changes...");
+} else {
+ await ctx.rebuild();
+ await ctx.dispose();
+}
diff --git a/packages/plugins/properties-file/marketplace-manifest.json b/packages/plugins/properties-file/marketplace-manifest.json
new file mode 100644
index 0000000000..24792ae0ce
--- /dev/null
+++ b/packages/plugins/properties-file/marketplace-manifest.json
@@ -0,0 +1,31 @@
+{
+ "$schema": "https://inlang.com/schema/marketplace-manifest",
+ "id": "plugin.inlang.propertiesFile",
+ "icon": "./assets/icon.svg",
+ "displayName": {
+ "en": "Properties file format"
+ },
+ "description": {
+ "en": "Store translations in Java .properties files with inline comments for translator context. Uses the properties-file parser for robust handling of the .properties format."
+ },
+ "pages": {
+ "/": "./README.md",
+ "/changelog": "./CHANGELOG.md"
+ },
+ "keywords": [
+ "properties",
+ "java",
+ "i18n",
+ "l10n",
+ "translation",
+ "storage",
+ "import",
+ "export",
+ "messages",
+ "plugin",
+ "comments"
+ ],
+ "publisherName": "community",
+ "license": "Apache-2.0",
+ "module": "https://cdn.jsdelivr.net/npm/@inlang/plugin-properties-file@latest/dist/index.js"
+}
diff --git a/packages/plugins/properties-file/package.json b/packages/plugins/properties-file/package.json
new file mode 100644
index 0000000000..d05d65c815
--- /dev/null
+++ b/packages/plugins/properties-file/package.json
@@ -0,0 +1,38 @@
+{
+ "name": "@inlang/plugin-properties-file",
+ "version": "0.1.0",
+ "type": "module",
+ "types": "./dist/index.d.ts",
+ "exports": {
+ ".": "./dist/index.js"
+ },
+ "files": ["./dist"],
+ "publishConfig": {
+ "access": "public"
+ },
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/opral/inlang",
+ "directory": "packages/plugins/properties-file"
+ },
+ "scripts": {
+ "dev": "node ./build.js",
+ "build": "NODE_ENV=production node ./build.js",
+ "test": "tsc --noEmit && vitest run --passWithNoTests",
+ "test:watch": "vitest",
+ "format": "prettier ./src --write",
+ "clean": "rm -rf ./dist ./node_modules"
+ },
+ "devDependencies": {
+ "@inlang/sdk": "workspace:*",
+ "@inlang/tsconfig": "workspace:*",
+ "@sinclair/typebox": "^0.31.17",
+ "esbuild": "^0.24.2",
+ "prettier": "^3.3.3",
+ "typescript": "^5.5.2",
+ "vitest": "^3.2.4"
+ },
+ "dependencies": {
+ "properties-file": "^5.0.4"
+ }
+}
diff --git a/packages/plugins/properties-file/src/import-export/exportFiles.ts b/packages/plugins/properties-file/src/import-export/exportFiles.ts
new file mode 100644
index 0000000000..7ba7e4fd5a
--- /dev/null
+++ b/packages/plugins/properties-file/src/import-export/exportFiles.ts
@@ -0,0 +1,121 @@
+import type {
+ Bundle,
+ ExportFile,
+ Message,
+ Variant,
+} from "@inlang/sdk";
+import { type plugin, PLUGIN_KEY } from "../plugin.js";
+
+export const exportFiles: NonNullable<(typeof plugin)["exportFiles"]> = async ({
+ bundles,
+ messages,
+ variants,
+ settings,
+}) => {
+ const files: Record<
+ string,
+ Array<{ key: string; value: string }>
+ > = {};
+
+ for (const message of messages) {
+ const bundle = bundles.find((b) => b.id === message.bundleId);
+ if (!bundle) {
+ continue;
+ }
+
+ const variantsOfMessage = variants.filter(
+ (v) => v.messageId === message.id
+ );
+
+ if (variantsOfMessage.length === 0) {
+ continue;
+ }
+
+ // Properties files do not support plural selectors or multi-variant messages.
+ // Throw an explicit error rather than silently dropping variants.
+ if (variantsOfMessage.length > 1) {
+ throw new Error(
+ `Message "${bundle.id}" (locale "${message.locale}") has ${variantsOfMessage.length} variants. ` +
+ `The .properties file format does not support multiple variants (plural/select). ` +
+ `Consider using a format that supports selectors, or simplify the message to a single variant.`
+ );
+ }
+
+ const variant = variantsOfMessage[0]!;
+
+ const serialized = serializePattern(variant.pattern);
+
+ if (!files[message.locale]) {
+ files[message.locale] = [];
+ }
+ files[message.locale]!.push({
+ key: bundle.id,
+ value: serialized,
+ });
+ }
+
+ const sortDirection = settings?.[PLUGIN_KEY]?.sort ?? undefined;
+
+ const result: ExportFile[] = [];
+
+ for (const locale in files) {
+ let entries = files[locale]!;
+
+ if (sortDirection === "asc") {
+ entries = entries.sort((a, b) => a.key.localeCompare(b.key));
+ } else if (sortDirection === "desc") {
+ entries = entries.sort((a, b) => b.key.localeCompare(a.key));
+ }
+
+ const lines: string[] = [];
+ for (const entry of entries) {
+ lines.push(`${entry.key} = ${entry.value}`);
+ }
+
+ const content = lines.join("\n") + "\n";
+
+ result.push({
+ locale,
+ content: new TextEncoder().encode(content),
+ name: locale + ".properties",
+ });
+ }
+
+ return result;
+};
+
+/**
+ * Escape a text value for safe inclusion in a .properties file.
+ *
+ * The .properties format treats backslashes, newlines, carriage returns,
+ * and tabs as control characters. These must be escaped to preserve the
+ * original value through a roundtrip.
+ */
+function escapePropertyValue(value: string): string {
+ return value
+ .replace(/\\/g, "\\\\")
+ .replace(/\n/g, "\\n")
+ .replace(/\r/g, "\\r")
+ .replace(/\t/g, "\\t");
+}
+
+function serializePattern(pattern: Variant["pattern"]): string {
+ let result = "";
+
+ for (const part of pattern) {
+ switch (part.type) {
+ case "text":
+ result += escapePropertyValue(part.value);
+ break;
+ case "expression":
+ if (part.arg.type === "variable-reference") {
+ result += `{${part.arg.name}}`;
+ break;
+ }
+ throw new Error("Unsupported expression type");
+ default:
+ throw new Error("Unsupported pattern element type");
+ }
+ }
+ return result;
+}
diff --git a/packages/plugins/properties-file/src/import-export/importFiles.ts b/packages/plugins/properties-file/src/import-export/importFiles.ts
new file mode 100644
index 0000000000..aed819e6e9
--- /dev/null
+++ b/packages/plugins/properties-file/src/import-export/importFiles.ts
@@ -0,0 +1,144 @@
+import type {
+ Bundle,
+ Declaration,
+ MessageImport,
+ Pattern,
+ VariantImport,
+} from "@inlang/sdk";
+import { type plugin } from "../plugin.js";
+import { getProperties } from "properties-file";
+
+export const importFiles: NonNullable<(typeof plugin)["importFiles"]> = async ({
+ files,
+}) => {
+ const bundles: Bundle[] = [];
+ const messages: MessageImport[] = [];
+ const variants: VariantImport[] = [];
+
+ for (const file of files) {
+ const content = new TextDecoder().decode(file.content);
+ const properties = getProperties(content);
+
+ for (const key in properties) {
+ const value = properties[key]!;
+
+ const result = parseBundle(key, file.locale, value);
+ messages.push(result.message);
+ variants.push(...result.variants);
+
+ const existingBundle = bundles.find((b) => b.id === result.bundle.id);
+ if (existingBundle === undefined) {
+ bundles.push(result.bundle);
+ } else {
+ // merge declarations without duplicates
+ existingBundle.declarations = unique([
+ ...existingBundle.declarations,
+ ...result.bundle.declarations,
+ ]);
+ }
+ }
+ }
+
+ return { bundles, messages, variants };
+};
+
+function parseBundle(
+ key: string,
+ locale: string,
+ value: string
+): {
+ bundle: Bundle;
+ message: MessageImport;
+ variants: VariantImport[];
+} {
+ const parsed = parsePattern(value);
+ const declarations = unique(parsed.declarations);
+
+ return {
+ bundle: {
+ id: key,
+ declarations,
+ },
+ message: {
+ bundleId: key,
+ selectors: [],
+ locale: locale,
+ },
+ variants: [
+ {
+ messageBundleId: key,
+ messageLocale: locale,
+ matches: [],
+ pattern: parsed.pattern,
+ },
+ ],
+ };
+}
+
+function parsePattern(value: string): {
+ declarations: Declaration[];
+ pattern: Pattern;
+} {
+ const pattern: Pattern = [];
+ const declarations: Declaration[] = [];
+ let buffer = "";
+
+ const flushBuffer = () => {
+ if (buffer.length > 0) {
+ pattern.push({ type: "text", value: buffer });
+ buffer = "";
+ }
+ };
+
+ for (let index = 0; index < value.length; index += 1) {
+ const char = value[index];
+
+ if (char === "{") {
+ const closingIndex = value.indexOf("}", index);
+
+ if (closingIndex === -1) {
+ buffer += char;
+ continue;
+ }
+
+ const placeholder = value.slice(index + 1, closingIndex);
+
+ // Only treat as a variable if the placeholder is a valid identifier
+ if (
+ placeholder.length > 0 &&
+ /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(placeholder)
+ ) {
+ flushBuffer();
+
+ declarations.push({
+ type: "input-variable",
+ name: placeholder,
+ });
+ pattern.push({
+ type: "expression",
+ arg: { type: "variable-reference", name: placeholder },
+ });
+ index = closingIndex;
+ continue;
+ }
+
+ // Not a valid variable reference, treat as literal text
+ buffer += char;
+ continue;
+ }
+
+ buffer += char;
+ }
+
+ flushBuffer();
+
+ return {
+ declarations,
+ pattern,
+ };
+}
+
+const unique = (arr: Array) =>
+ [...new Set(arr.map((item) => JSON.stringify(item)))].map((item) =>
+ JSON.parse(item)
+ );
diff --git a/packages/plugins/properties-file/src/import-export/roundtrip.test.ts b/packages/plugins/properties-file/src/import-export/roundtrip.test.ts
new file mode 100644
index 0000000000..bf3d6b8237
--- /dev/null
+++ b/packages/plugins/properties-file/src/import-export/roundtrip.test.ts
@@ -0,0 +1,502 @@
+import { expect, test } from "vitest";
+import { importFiles } from "./importFiles.js";
+import type {
+ Bundle,
+ Declaration,
+ Message,
+ Pattern,
+ Variant,
+} from "@inlang/sdk";
+import { exportFiles } from "./exportFiles.js";
+
+test("it handles simple key-value pairs without variables", async () => {
+ const imported = await runImportFiles(
+ "greeting = Hello World\nfarewell = Goodbye"
+ );
+
+ expect(imported.bundles).lengthOf(2);
+ expect(imported.messages).lengthOf(2);
+ expect(imported.variants).lengthOf(2);
+
+ expect(imported.bundles[0]?.id).toStrictEqual("greeting");
+ expect(imported.bundles[0]?.declarations).toStrictEqual([]);
+ expect(imported.bundles[1]?.id).toStrictEqual("farewell");
+
+ expect(imported.variants[0]?.pattern).toStrictEqual([
+ { type: "text", value: "Hello World" },
+ ]);
+ expect(imported.variants[1]?.pattern).toStrictEqual([
+ { type: "text", value: "Goodbye" },
+ ]);
+});
+
+test("it handles values with variable expressions", async () => {
+ const imported = await runImportFiles(
+ "greeting = Hello {name}!\nitems.count = You have {count} items"
+ );
+
+ expect(imported.bundles).lengthOf(2);
+ expect(imported.messages).lengthOf(2);
+ expect(imported.variants).lengthOf(2);
+
+ expect(imported.bundles[0]?.id).toStrictEqual("greeting");
+ expect(imported.bundles[0]?.declarations).toStrictEqual([
+ { type: "input-variable", name: "name" },
+ ] satisfies Declaration[]);
+
+ expect(imported.variants[0]?.pattern).toStrictEqual([
+ { type: "text", value: "Hello " },
+ {
+ type: "expression",
+ arg: { type: "variable-reference", name: "name" },
+ },
+ { type: "text", value: "!" },
+ ] satisfies Pattern);
+
+ expect(imported.bundles[1]?.id).toStrictEqual("items.count");
+ expect(imported.bundles[1]?.declarations).toStrictEqual([
+ { type: "input-variable", name: "count" },
+ ] satisfies Declaration[]);
+
+ expect(imported.variants[1]?.pattern).toStrictEqual([
+ { type: "text", value: "You have " },
+ {
+ type: "expression",
+ arg: { type: "variable-reference", name: "count" },
+ },
+ { type: "text", value: " items" },
+ ] satisfies Pattern);
+});
+
+test("it handles multiple variables in a single value", async () => {
+ const imported = await runImportFiles(
+ "message = {user} sent {count} messages to {recipient}"
+ );
+
+ expect(imported.bundles[0]?.declarations).toStrictEqual([
+ { type: "input-variable", name: "user" },
+ { type: "input-variable", name: "count" },
+ { type: "input-variable", name: "recipient" },
+ ] satisfies Declaration[]);
+
+ expect(imported.variants[0]?.pattern).toStrictEqual([
+ {
+ type: "expression",
+ arg: { type: "variable-reference", name: "user" },
+ },
+ { type: "text", value: " sent " },
+ {
+ type: "expression",
+ arg: { type: "variable-reference", name: "count" },
+ },
+ { type: "text", value: " messages to " },
+ {
+ type: "expression",
+ arg: { type: "variable-reference", name: "recipient" },
+ },
+ ] satisfies Pattern);
+});
+
+test("it handles empty values", async () => {
+ const imported = await runImportFiles("empty.key = ");
+
+ expect(imported.bundles).lengthOf(1);
+ expect(imported.bundles[0]?.id).toStrictEqual("empty.key");
+ expect(imported.variants[0]?.pattern).toStrictEqual([]);
+});
+
+test("it handles comments (they are ignored during import)", async () => {
+ const imported = await runImportFiles(
+ "# This is a greeting\ngreeting = Hello"
+ );
+
+ expect(imported.bundles).lengthOf(1);
+ expect(imported.bundles[0]?.id).toStrictEqual("greeting");
+ expect(imported.variants[0]?.pattern).toStrictEqual([
+ { type: "text", value: "Hello" },
+ ]);
+});
+
+test("it handles dot-separated keys", async () => {
+ const imported = await runImportFiles(
+ "section.subsection.key = Deep value\nsection.other = Other value"
+ );
+
+ expect(imported.bundles).lengthOf(2);
+ expect(imported.bundles[0]?.id).toStrictEqual("section.subsection.key");
+ expect(imported.bundles[1]?.id).toStrictEqual("section.other");
+});
+
+test("it handles multiple locales", async () => {
+ const imported = await importFiles({
+ settings: {} as any,
+ files: [
+ {
+ locale: "en",
+ content: new TextEncoder().encode(
+ "greeting = Hello {name}!\nfarewell = Goodbye"
+ ),
+ },
+ {
+ locale: "fr",
+ content: new TextEncoder().encode(
+ "greeting = Bonjour {name} !\nfarewell = Au revoir"
+ ),
+ },
+ ],
+ });
+
+ expect(imported.bundles).lengthOf(2);
+ expect(imported.messages).lengthOf(4);
+ expect(imported.variants).lengthOf(4);
+
+ // Bundle declarations should be merged from all locales
+ expect(imported.bundles[0]?.id).toStrictEqual("greeting");
+ expect(imported.bundles[0]?.declarations).toStrictEqual([
+ { type: "input-variable", name: "name" },
+ ]);
+
+ const exported = await runExportFiles(imported);
+ expect(exported).lengthOf(2);
+
+ const enContent = new TextDecoder().decode(
+ exported.find((e: any) => e.locale === "en")?.content
+ );
+ const frContent = new TextDecoder().decode(
+ exported.find((e: any) => e.locale === "fr")?.content
+ );
+
+ expect(enContent).toContain("greeting = Hello {name}!");
+ expect(enContent).toContain("farewell = Goodbye");
+ expect(frContent).toContain("greeting = Bonjour {name} !");
+ expect(frContent).toContain("farewell = Au revoir");
+});
+
+test("roundtrip: import then export then import produces same data", async () => {
+ const original =
+ "greeting = Hello {name}!\nitems.count = You have {count} items\nsimple = Just text\n";
+ const imported1 = await runImportFiles(original);
+ const exported = await runExportFiles(imported1);
+ const imported2 = await importFiles({
+ settings: {} as any,
+ files: [
+ {
+ locale: "en",
+ content: exported[0]!.content,
+ },
+ ],
+ });
+
+ expect(imported2.bundles.length).toStrictEqual(imported1.bundles.length);
+ expect(imported2.messages.length).toStrictEqual(imported1.messages.length);
+ expect(imported2.variants.length).toStrictEqual(imported1.variants.length);
+
+ for (let i = 0; i < imported1.bundles.length; i++) {
+ expect(imported2.bundles[i]?.id).toStrictEqual(imported1.bundles[i]?.id);
+ expect(imported2.bundles[i]?.declarations).toStrictEqual(
+ imported1.bundles[i]?.declarations
+ );
+ }
+
+ for (let i = 0; i < imported1.variants.length; i++) {
+ expect(imported2.variants[i]?.pattern).toStrictEqual(
+ imported1.variants[i]?.pattern
+ );
+ }
+});
+
+test("export sorts keys ascending when configured", async () => {
+ const imported = await runImportFiles(
+ "c.key = three\na.key = one\nb.key = two"
+ );
+
+ const settingsAsc = {
+ "plugin.inlang.propertiesFile": {
+ sort: "asc",
+ },
+ };
+ const exported = await runExportFiles(imported, settingsAsc);
+ const content = new TextDecoder().decode(exported[0]?.content);
+ const lines = content.split("\n").filter((l: string) => l.length > 0);
+
+ expect(lines[0]).toStrictEqual("a.key = one");
+ expect(lines[1]).toStrictEqual("b.key = two");
+ expect(lines[2]).toStrictEqual("c.key = three");
+});
+
+test("export sorts keys descending when configured", async () => {
+ const imported = await runImportFiles(
+ "a.key = one\nc.key = three\nb.key = two"
+ );
+
+ const settingsDesc = {
+ "plugin.inlang.propertiesFile": {
+ sort: "desc",
+ },
+ };
+ const exported = await runExportFiles(imported, settingsDesc);
+ const content = new TextDecoder().decode(exported[0]?.content);
+ const lines = content.split("\n").filter((l: string) => l.length > 0);
+
+ expect(lines[0]).toStrictEqual("c.key = three");
+ expect(lines[1]).toStrictEqual("b.key = two");
+ expect(lines[2]).toStrictEqual("a.key = one");
+});
+
+test("it handles the same variable used multiple times", async () => {
+ const imported = await runImportFiles(
+ "repeat = The value {value} appears twice: {value}"
+ );
+
+ expect(imported.bundles[0]?.declarations).toHaveLength(1);
+ expect(imported.bundles[0]?.declarations?.[0]).toMatchObject({
+ type: "input-variable",
+ name: "value",
+ });
+
+ expect(imported.variants[0]?.pattern).toEqual([
+ { type: "text", value: "The value " },
+ {
+ type: "expression",
+ arg: { type: "variable-reference", name: "value" },
+ },
+ { type: "text", value: " appears twice: " },
+ {
+ type: "expression",
+ arg: { type: "variable-reference", name: "value" },
+ },
+ ]);
+});
+
+test("it handles special characters in values", async () => {
+ const imported = await runImportFiles(
+ "special = Value with \\= equals and \\: colon"
+ );
+
+ expect(imported.bundles).lengthOf(1);
+ expect(imported.bundles[0]?.id).toStrictEqual("special");
+ // properties-file unescapes the value
+ expect(imported.variants[0]?.pattern).toStrictEqual([
+ { type: "text", value: "Value with = equals and : colon" },
+ ]);
+});
+
+test("it handles unicode escape sequences", async () => {
+ const imported = await runImportFiles("unicode = Hello \\u0057orld");
+
+ expect(imported.variants[0]?.pattern).toStrictEqual([
+ { type: "text", value: "Hello World" },
+ ]);
+});
+
+test("it handles colon as separator", async () => {
+ const imported = await runImportFiles("key : value with colon separator");
+
+ expect(imported.bundles).lengthOf(1);
+ expect(imported.bundles[0]?.id).toStrictEqual("key");
+ expect(imported.variants[0]?.pattern).toStrictEqual([
+ { type: "text", value: "value with colon separator" },
+ ]);
+});
+
+test("it handles values without any separator space", async () => {
+ const imported = await runImportFiles("key=value");
+
+ expect(imported.bundles).lengthOf(1);
+ expect(imported.bundles[0]?.id).toStrictEqual("key");
+ expect(imported.variants[0]?.pattern).toStrictEqual([
+ { type: "text", value: "value" },
+ ]);
+});
+
+test("it handles unclosed braces as literal text", async () => {
+ const imported = await runImportFiles("broken = Hello {world");
+
+ expect(imported.variants[0]?.pattern).toStrictEqual([
+ { type: "text", value: "Hello {world" },
+ ]);
+});
+
+test("handles inputs of a bundle even if one locale doesn't use all inputs", async () => {
+ const imported = await importFiles({
+ settings: {} as any,
+ files: [
+ {
+ locale: "en",
+ content: new TextEncoder().encode(
+ "message = Hello {username}! Welcome to {place}."
+ ),
+ },
+ {
+ locale: "de",
+ content: new TextEncoder().encode(
+ "message = Willkommen {username}!"
+ ),
+ },
+ ],
+ });
+
+ expect(imported.bundles).lengthOf(1);
+ expect(imported.messages).lengthOf(2);
+ expect(imported.variants).lengthOf(2);
+
+ expect(imported.bundles[0]?.declarations).toStrictEqual([
+ { type: "input-variable", name: "username" },
+ { type: "input-variable", name: "place" },
+ ]);
+
+ const exported = await runExportFiles(imported);
+
+ const enContent = new TextDecoder().decode(
+ exported.find((e: any) => e.locale === "en")?.content
+ );
+ const deContent = new TextDecoder().decode(
+ exported.find((e: any) => e.locale === "de")?.content
+ );
+
+ expect(enContent).toContain(
+ "message = Hello {username}! Welcome to {place}."
+ );
+ expect(deContent).toContain("message = Willkommen {username}!");
+});
+
+test("export file ends with a newline", async () => {
+ const imported = await runImportFiles("key = value");
+ const exported = await runExportFiles(imported);
+ const content = new TextDecoder().decode(exported[0]?.content);
+
+ expect(content.endsWith("\n")).toBe(true);
+});
+
+test("export file name uses locale and .properties extension", async () => {
+ const imported = await runImportFiles("key = value");
+ const exported = await runExportFiles(imported);
+
+ expect(exported[0]?.name).toStrictEqual("en.properties");
+});
+
+test("it escapes control characters in exported values", async () => {
+ const imported = await runImportFiles(
+ "multiline = line1\\nline2\\nline3\ntabbed = col1\\tcol2\nbackslash = path\\\\to\\\\file"
+ );
+
+ const exported = await runExportFiles(imported);
+ const content = new TextDecoder().decode(exported[0]?.content);
+
+ // The exported content should have escaped control characters
+ expect(content).toContain("multiline = line1\\nline2\\nline3");
+ expect(content).toContain("tabbed = col1\\tcol2");
+ expect(content).toContain("backslash = path\\\\to\\\\file");
+});
+
+test("it passes ICU MessageFormat syntax through as plain text", async () => {
+ const imported = await runImportFiles(
+ "item_count = {count, plural, one {1 item} other {{count} items}}\ngreeting = {gender, select, male {Mr.} female {Ms.} other {Mx.}} {name}"
+ );
+
+ expect(imported.bundles).lengthOf(2);
+ // ICU syntax is treated as a single variant with plain text — not parsed into selectors
+ expect(imported.variants).lengthOf(2);
+
+ // Roundtrip: ICU syntax should survive export unchanged
+ const exported = await runExportFiles(imported);
+ const content = new TextDecoder().decode(exported[0]?.content);
+ expect(content).toContain(
+ "item_count = {count, plural, one {1 item} other {{count} items}}"
+ );
+ expect(content).toContain(
+ "greeting = {gender, select, male {Mr.} female {Ms.} other {Mx.}} {name}"
+ );
+});
+
+test("it throws on multi-variant messages", async () => {
+ // Construct a message with multiple variants (simulating a plural)
+ const bundles: Bundle[] = [{ id: "item_count", declarations: [] }];
+ const messages: Message[] = [
+ {
+ id: "item_count_en",
+ bundleId: "item_count",
+ locale: "en",
+ selectors: [],
+ },
+ ];
+ const variants: Variant[] = [
+ {
+ id: "v1",
+ messageId: "item_count_en",
+ matches: [{ type: "literal-match", key: "count", value: "one" }],
+ pattern: [{ type: "text", value: "1 item" }],
+ },
+ {
+ id: "v2",
+ messageId: "item_count_en",
+ matches: [{ type: "literal-match", key: "count", value: "other" }],
+ pattern: [{ type: "text", value: "{count} items" }],
+ },
+ ];
+
+ await expect(
+ exportFiles({
+ bundles,
+ messages,
+ variants,
+ settings: {
+ baseLocale: "en",
+ locales: ["en"],
+ "plugin.inlang.propertiesFile": {
+ pathPattern: "./messages/{locale}.properties",
+ },
+ } as any,
+ })
+ ).rejects.toThrow(/does not support multiple variants/);
+});
+
+// convenience wrapper for less testing code
+function runImportFiles(propertiesContent: string) {
+ return importFiles({
+ settings: {} as any,
+ files: [
+ {
+ locale: "en",
+ content: new TextEncoder().encode(propertiesContent),
+ },
+ ],
+ });
+}
+
+// convenience wrapper for less testing code
+async function runExportFiles(
+ imported: Awaited>,
+ settings: Record = {}
+) {
+ // add ids which are undefined from the import
+ for (const message of imported.messages) {
+ if (message.id === undefined) {
+ message.id =
+ imported.messages.find(
+ (m: any) =>
+ m.bundleId === message.bundleId && m.locale === message.locale
+ )?.id ?? `${Math.random() * 1000}`;
+ }
+ }
+ for (const variant of imported.variants) {
+ if (variant.id === undefined) {
+ (variant as any).id = `${Math.random() * 1000}`;
+ }
+ if (variant.messageId === undefined) {
+ (variant as any).messageId = imported.messages.find(
+ (m: any) =>
+ m.bundleId === (variant as any).messageBundleId &&
+ m.locale === (variant as any).messageLocale
+ )?.id;
+ }
+ }
+
+ const exported = await exportFiles({
+ settings: settings as any,
+ bundles: imported.bundles as Bundle[],
+ messages: imported.messages as Message[],
+ variants: imported.variants as Variant[],
+ });
+ return exported;
+}
diff --git a/packages/plugins/properties-file/src/import-export/toBeImportedFiles.ts b/packages/plugins/properties-file/src/import-export/toBeImportedFiles.ts
new file mode 100644
index 0000000000..47c488889b
--- /dev/null
+++ b/packages/plugins/properties-file/src/import-export/toBeImportedFiles.ts
@@ -0,0 +1,21 @@
+import { PLUGIN_KEY, type plugin } from "../plugin.js";
+
+export const toBeImportedFiles: NonNullable<
+ (typeof plugin)["toBeImportedFiles"]
+> = async ({ settings }) => {
+ const result = [];
+ const pathPatterns = settings[PLUGIN_KEY]?.pathPattern
+ ? Array.isArray(settings[PLUGIN_KEY].pathPattern)
+ ? settings[PLUGIN_KEY].pathPattern
+ : [settings[PLUGIN_KEY].pathPattern]
+ : [];
+ for (const pathPattern of pathPatterns) {
+ for (const locale of settings.locales) {
+ result.push({
+ locale,
+ path: pathPattern.replace(/{locale}/, locale),
+ });
+ }
+ }
+ return result;
+};
diff --git a/packages/plugins/properties-file/src/index.ts b/packages/plugins/properties-file/src/index.ts
new file mode 100644
index 0000000000..5bfdad5599
--- /dev/null
+++ b/packages/plugins/properties-file/src/index.ts
@@ -0,0 +1,3 @@
+import { plugin } from "./plugin.js";
+
+export default plugin;
diff --git a/packages/plugins/properties-file/src/plugin.ts b/packages/plugins/properties-file/src/plugin.ts
new file mode 100644
index 0000000000..114ca7ac83
--- /dev/null
+++ b/packages/plugins/properties-file/src/plugin.ts
@@ -0,0 +1,17 @@
+import type { InlangPlugin } from "@inlang/sdk";
+import { PluginSettings } from "./settings.js";
+import { toBeImportedFiles } from "./import-export/toBeImportedFiles.js";
+import { importFiles } from "./import-export/importFiles.js";
+import { exportFiles } from "./import-export/exportFiles.js";
+
+export const PLUGIN_KEY = "plugin.inlang.propertiesFile";
+
+export const plugin: InlangPlugin<{
+ [PLUGIN_KEY]?: PluginSettings;
+}> = {
+ key: PLUGIN_KEY,
+ settingsSchema: PluginSettings,
+ toBeImportedFiles,
+ importFiles,
+ exportFiles,
+};
diff --git a/packages/plugins/properties-file/src/settings.ts b/packages/plugins/properties-file/src/settings.ts
new file mode 100644
index 0000000000..ea512fec5e
--- /dev/null
+++ b/packages/plugins/properties-file/src/settings.ts
@@ -0,0 +1,31 @@
+import { Type, type Static } from "@sinclair/typebox";
+
+const pathPatternString = Type.String({
+ pattern: ".*\\{locale\\}.*\\.properties$",
+ examples: [
+ "./messages/{locale}.properties",
+ "./i18n/{locale}.properties",
+ ],
+ title: "Path to language files",
+ description:
+ "Specify the pathPattern to locate .properties files in your repository. It must include `{locale}` and end with `.properties`.",
+});
+
+const pathPatternArray = Type.Array(pathPatternString, {
+ title: "Paths to language files",
+ description:
+ "Specify multiple pathPatterns to locate .properties files in your repository. Each must include `{locale}` and end with `.properties`.",
+});
+
+const sort = Type.Optional(
+ Type.Union([Type.Literal("asc"), Type.Literal("desc")], {
+ title: "Sort keys",
+ description: "Sort message keys when writing files.",
+ })
+);
+
+export type PluginSettings = Static;
+export const PluginSettings = Type.Object({
+ pathPattern: Type.Union([pathPatternString, pathPatternArray]),
+ sort,
+});
diff --git a/packages/plugins/properties-file/tsconfig.json b/packages/plugins/properties-file/tsconfig.json
new file mode 100644
index 0000000000..d7c934df41
--- /dev/null
+++ b/packages/plugins/properties-file/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "@inlang/tsconfig/default",
+ "include": ["src/**/*"],
+ "compilerOptions": {
+ "resolveJsonModule": true
+ }
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4b406edaf0..1b26dee9d0 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -712,6 +712,34 @@ importers:
specifier: 3.1.1
version: 3.1.1(@types/debug@4.1.12)(@types/node@24.10.2)(@vitest/browser@3.2.4(msw@2.10.2(@types/node@24.10.2)(typescript@5.9.3))(playwright@1.55.0)(vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.2.1))(happy-dom@18.0.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(msw@2.10.2(@types/node@24.10.2)(typescript@5.7.3))(sass-embedded@1.89.2)(terser@5.36.0)
+ packages/plugins/properties-file:
+ dependencies:
+ properties-file:
+ specifier: ^5.0.4
+ version: 5.0.4
+ devDependencies:
+ '@inlang/sdk':
+ specifier: workspace:*
+ version: link:../../sdk
+ '@inlang/tsconfig':
+ specifier: workspace:*
+ version: link:../../tsconfig
+ '@sinclair/typebox':
+ specifier: ^0.31.17
+ version: 0.31.28
+ esbuild:
+ specifier: ^0.24.2
+ version: 0.24.2
+ prettier:
+ specifier: ^3.3.3
+ version: 3.6.2
+ typescript:
+ specifier: ^5.5.2
+ version: 5.9.3
+ vitest:
+ specifier: ^3.2.4
+ version: 3.2.4(@types/debug@4.1.12)(@types/node@24.10.2)(@vitest/browser@3.2.4)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(msw@2.10.2(@types/node@24.10.2)(typescript@5.9.3))(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
+
packages/plugins/t-function-matcher:
dependencies:
'@inlang/sdk':
@@ -4674,9 +4702,6 @@ packages:
'@types/chai@4.3.20':
resolution: {integrity: sha512-/pC9HAB5I/xMlc5FP77qjCnI16ChlJfW0tGa0IUcFn38VJrTV6DeZ60NU5KZBtaOZqjdpwTWohz5HU1RrhiYxQ==}
- '@types/chai@5.2.2':
- resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==}
-
'@types/chai@5.2.3':
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
@@ -5875,7 +5900,7 @@ packages:
basic-ftp@5.0.5:
resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==}
engines: {node: '>=10.0.0'}
- deprecated: Security vulnerability fixed in 5.2.0, please upgrade
+ deprecated: Security vulnerability fixed in 5.2.1, please upgrade
before-after-hook@2.2.3:
resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==}
@@ -9650,6 +9675,10 @@ packages:
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+ properties-file@5.0.4:
+ resolution: {integrity: sha512-c3CqZLWoNN+zQvpn4z8GgVdBGWlSzbjPumDk1Yri2bdbw7CMzaOgoPsj271kJzNwznEtt4tIDByhZjrw1LzKBw==}
+ engines: {node: '>=0.4.0'}
+
property-information@6.5.0:
resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==}
@@ -10756,10 +10785,6 @@ packages:
resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==}
engines: {node: '>=18'}
- tinyglobby@0.2.14:
- resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==}
- engines: {node: '>=12.0.0'}
-
tinyglobby@0.2.15:
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
engines: {node: '>=12.0.0'}
@@ -14129,6 +14154,12 @@ snapshots:
- typescript
- verdaccio
+ '@nrwl/devkit@18.3.5(nx@18.3.5)':
+ dependencies:
+ '@nx/devkit': 18.3.5(nx@18.3.5)
+ transitivePeerDependencies:
+ - nx
+
'@nrwl/devkit@18.3.5(nx@21.4.1)':
dependencies:
'@nx/devkit': 18.3.5(nx@21.4.1)
@@ -14270,7 +14301,7 @@ snapshots:
'@nx/devkit@18.3.5(nx@18.3.5)':
dependencies:
- '@nrwl/devkit': 18.3.5(nx@21.4.1)
+ '@nrwl/devkit': 18.3.5(nx@18.3.5)
ejs: 3.1.10
enquirer: 2.3.6
ignore: 5.3.2
@@ -15885,10 +15916,6 @@ snapshots:
'@types/chai@4.3.20': {}
- '@types/chai@5.2.2':
- dependencies:
- '@types/deep-eql': 4.0.2
-
'@types/chai@5.2.3':
dependencies:
'@types/deep-eql': 4.0.2
@@ -16595,11 +16622,11 @@ snapshots:
- vite
optional: true
- '@vitest/browser@3.2.4(msw@2.10.2(@types/node@22.15.33)(typescript@5.7.3))(playwright@1.55.0)(vite@6.3.5(@types/node@22.15.33)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.2.1)':
+ '@vitest/browser@3.2.4(msw@2.10.2(@types/node@22.15.33)(typescript@5.7.3))(playwright@1.55.0)(vite@7.2.7(@types/node@22.15.33)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.2.1)':
dependencies:
'@testing-library/dom': 10.4.1
'@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1)
- '@vitest/mocker': 3.2.4(msw@2.10.2(@types/node@22.15.33)(typescript@5.7.3))(vite@6.3.5(@types/node@22.15.33)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))
+ '@vitest/mocker': 3.2.4(msw@2.10.2(@types/node@22.15.33)(typescript@5.7.3))(vite@7.2.7(@types/node@22.15.33)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))
'@vitest/utils': 3.2.4
magic-string: 0.30.21
sirv: 3.0.2
@@ -16751,7 +16778,7 @@ snapshots:
tinyrainbow: 2.0.0
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@22.15.33)(@vitest/browser@3.2.4)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(msw@2.10.2(@types/node@22.15.33)(typescript@5.7.3))(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
optionalDependencies:
- '@vitest/browser': 3.2.4(msw@2.10.2(@types/node@22.15.33)(typescript@5.7.3))(playwright@1.55.0)(vite@6.3.5(@types/node@22.15.33)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.2.1)
+ '@vitest/browser': 3.2.4(msw@2.10.2(@types/node@22.15.33)(typescript@5.7.3))(playwright@1.55.0)(vite@7.2.7(@types/node@22.15.33)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.2.1)
transitivePeerDependencies:
- supports-color
@@ -16784,7 +16811,7 @@ snapshots:
'@vitest/expect@3.2.4':
dependencies:
- '@types/chai': 5.2.2
+ '@types/chai': 5.2.3
'@vitest/spy': 3.2.4
'@vitest/utils': 3.2.4
chai: 5.3.3
@@ -16845,23 +16872,14 @@ snapshots:
msw: 2.10.2(@types/node@24.10.2)(typescript@5.7.3)
vite: 5.4.19(@types/node@24.10.2)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)
- '@vitest/mocker@3.2.4(msw@2.10.2(@types/node@22.15.33)(typescript@5.7.3))(vite@6.3.5(@types/node@22.15.33)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))':
+ '@vitest/mocker@3.2.4(msw@2.10.2(@types/node@22.15.33)(typescript@5.7.3))(vite@7.2.7(@types/node@22.15.33)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))':
dependencies:
'@vitest/spy': 3.2.4
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
msw: 2.10.2(@types/node@22.15.33)(typescript@5.7.3)
- vite: 6.3.5(@types/node@22.15.33)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
-
- '@vitest/mocker@3.2.4(msw@2.10.2(@types/node@24.10.2)(typescript@5.7.3))(vite@6.3.5(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))':
- dependencies:
- '@vitest/spy': 3.2.4
- estree-walker: 3.0.3
- magic-string: 0.30.21
- optionalDependencies:
- msw: 2.10.2(@types/node@24.10.2)(typescript@5.7.3)
- vite: 6.3.5(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
+ vite: 7.2.7(@types/node@22.15.33)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
'@vitest/mocker@3.2.4(msw@2.10.2(@types/node@24.10.2)(typescript@5.7.3))(vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))':
dependencies:
@@ -16871,16 +16889,6 @@ snapshots:
optionalDependencies:
msw: 2.10.2(@types/node@24.10.2)(typescript@5.7.3)
vite: 7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
- optional: true
-
- '@vitest/mocker@3.2.4(msw@2.10.2(@types/node@24.10.2)(typescript@5.9.3))(vite@6.3.5(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))':
- dependencies:
- '@vitest/spy': 3.2.4
- estree-walker: 3.0.3
- magic-string: 0.30.21
- optionalDependencies:
- msw: 2.10.2(@types/node@24.10.2)(typescript@5.9.3)
- vite: 6.3.5(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
'@vitest/mocker@3.2.4(msw@2.10.2(@types/node@24.10.2)(typescript@5.9.3))(vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))':
dependencies:
@@ -16890,7 +16898,6 @@ snapshots:
optionalDependencies:
msw: 2.10.2(@types/node@24.10.2)(typescript@5.9.3)
vite: 7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
- optional: true
'@vitest/mocker@4.0.16(msw@2.10.2(@types/node@22.15.33)(typescript@5.7.3))(vite@7.2.7(@types/node@22.15.33)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))':
dependencies:
@@ -22277,6 +22284,8 @@ snapshots:
object-assign: 4.1.1
react-is: 16.13.1
+ properties-file@5.0.4: {}
+
property-information@6.5.0: {}
property-information@7.1.0: {}
@@ -23632,11 +23641,6 @@ snapshots:
tinyexec@1.0.2: {}
- tinyglobby@0.2.14:
- dependencies:
- fdir: 6.5.0(picomatch@4.0.3)
- picomatch: 4.0.3
-
tinyglobby@0.2.15:
dependencies:
fdir: 6.5.0(picomatch@4.0.3)
@@ -23876,8 +23880,7 @@ snapshots:
typescript@5.7.3: {}
- typescript@5.9.3:
- optional: true
+ typescript@5.9.3: {}
ufo@1.6.1: {}
@@ -24171,15 +24174,16 @@ snapshots:
- supports-color
- terser
- vite-node@3.2.4(@types/node@22.15.33)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0):
+ vite-node@3.2.4(@types/node@22.15.33)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1):
dependencies:
cac: 6.7.14
debug: 4.4.3
es-module-lexer: 1.7.0
pathe: 2.0.3
- vite: 5.4.19(@types/node@22.15.33)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)
+ vite: 7.2.7(@types/node@22.15.33)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
transitivePeerDependencies:
- '@types/node'
+ - jiti
- less
- lightningcss
- sass
@@ -24188,16 +24192,19 @@ snapshots:
- sugarss
- supports-color
- terser
+ - tsx
+ - yaml
- vite-node@3.2.4(@types/node@24.10.2)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0):
+ vite-node@3.2.4(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1):
dependencies:
cac: 6.7.14
debug: 4.4.3
es-module-lexer: 1.7.0
pathe: 2.0.3
- vite: 5.4.19(@types/node@24.10.2)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)
+ vite: 7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
transitivePeerDependencies:
- '@types/node'
+ - jiti
- less
- lightningcss
- sass
@@ -24206,6 +24213,8 @@ snapshots:
- sugarss
- supports-color
- terser
+ - tsx
+ - yaml
vite-plugin-generate-file@0.2.0:
dependencies:
@@ -24288,18 +24297,6 @@ snapshots:
sass-embedded: 1.89.2
terser: 5.36.0
- vite@5.4.19(@types/node@22.15.33)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0):
- dependencies:
- esbuild: 0.21.5
- postcss: 8.5.6
- rollup: 4.53.2
- optionalDependencies:
- '@types/node': 22.15.33
- fsevents: 2.3.3
- lightningcss: 1.30.2
- sass-embedded: 1.89.2
- terser: 5.36.0
-
vite@5.4.19(@types/node@24.10.2)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0):
dependencies:
esbuild: 0.21.5
@@ -24330,42 +24327,6 @@ snapshots:
tsx: 4.20.5
yaml: 2.8.1
- vite@6.3.5(@types/node@22.15.33)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1):
- dependencies:
- esbuild: 0.25.12
- fdir: 6.5.0(picomatch@4.0.3)
- picomatch: 4.0.3
- postcss: 8.5.6
- rollup: 4.53.2
- tinyglobby: 0.2.15
- optionalDependencies:
- '@types/node': 22.15.33
- fsevents: 2.3.3
- jiti: 2.6.1
- lightningcss: 1.30.2
- sass-embedded: 1.89.2
- terser: 5.36.0
- tsx: 4.20.5
- yaml: 2.8.1
-
- vite@6.3.5(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1):
- dependencies:
- esbuild: 0.25.12
- fdir: 6.5.0(picomatch@4.0.3)
- picomatch: 4.0.3
- postcss: 8.5.6
- rollup: 4.53.2
- tinyglobby: 0.2.15
- optionalDependencies:
- '@types/node': 24.10.2
- fsevents: 2.3.3
- jiti: 2.6.1
- lightningcss: 1.30.2
- sass-embedded: 1.89.2
- terser: 5.36.0
- tsx: 4.20.5
- yaml: 2.8.1
-
vite@7.2.7(@types/node@22.15.33)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1):
dependencies:
esbuild: 0.25.12
@@ -24639,33 +24600,33 @@ snapshots:
vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.15.33)(@vitest/browser@3.2.4)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(msw@2.10.2(@types/node@22.15.33)(typescript@5.7.3))(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1):
dependencies:
- '@types/chai': 5.2.2
+ '@types/chai': 5.2.3
'@vitest/expect': 3.2.4
- '@vitest/mocker': 3.2.4(msw@2.10.2(@types/node@22.15.33)(typescript@5.7.3))(vite@6.3.5(@types/node@22.15.33)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))
+ '@vitest/mocker': 3.2.4(msw@2.10.2(@types/node@22.15.33)(typescript@5.7.3))(vite@7.2.7(@types/node@22.15.33)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))
'@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.1
+ debug: 4.4.3
expect-type: 1.2.2
- magic-string: 0.30.18
+ magic-string: 0.30.21
pathe: 2.0.3
picomatch: 4.0.3
- std-env: 3.9.0
+ std-env: 3.10.0
tinybench: 2.9.0
tinyexec: 0.3.2
- tinyglobby: 0.2.14
+ tinyglobby: 0.2.15
tinypool: 1.1.1
tinyrainbow: 2.0.0
- vite: 6.3.5(@types/node@22.15.33)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
- vite-node: 3.2.4(@types/node@22.15.33)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)
+ vite: 7.2.7(@types/node@22.15.33)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
+ vite-node: 3.2.4(@types/node@22.15.33)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/debug': 4.1.12
'@types/node': 22.15.33
- '@vitest/browser': 3.2.4(msw@2.10.2(@types/node@22.15.33)(typescript@5.7.3))(playwright@1.55.0)(vite@6.3.5(@types/node@22.15.33)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.2.1)
+ '@vitest/browser': 3.2.4(msw@2.10.2(@types/node@22.15.33)(typescript@5.7.3))(playwright@1.55.0)(vite@7.2.7(@types/node@22.15.33)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.4)(webdriverio@9.2.1)
happy-dom: 18.0.1
jsdom: 27.3.0(postcss@8.5.6)
transitivePeerDependencies:
@@ -24684,28 +24645,28 @@ snapshots:
vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.2)(@vitest/browser@3.2.4)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(msw@2.10.2(@types/node@24.10.2)(typescript@5.7.3))(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1):
dependencies:
- '@types/chai': 5.2.2
+ '@types/chai': 5.2.3
'@vitest/expect': 3.2.4
- '@vitest/mocker': 3.2.4(msw@2.10.2(@types/node@24.10.2)(typescript@5.7.3))(vite@6.3.5(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))
+ '@vitest/mocker': 3.2.4(msw@2.10.2(@types/node@24.10.2)(typescript@5.7.3))(vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))
'@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.1
+ debug: 4.4.3
expect-type: 1.2.2
- magic-string: 0.30.18
+ magic-string: 0.30.21
pathe: 2.0.3
picomatch: 4.0.3
- std-env: 3.9.0
+ std-env: 3.10.0
tinybench: 2.9.0
tinyexec: 0.3.2
- tinyglobby: 0.2.14
+ tinyglobby: 0.2.15
tinypool: 1.1.1
tinyrainbow: 2.0.0
- vite: 6.3.5(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
- vite-node: 3.2.4(@types/node@24.10.2)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)
+ vite: 7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
+ vite-node: 3.2.4(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/debug': 4.1.12
@@ -24729,28 +24690,28 @@ snapshots:
vitest@3.2.4(@types/debug@4.1.12)(@types/node@24.10.2)(@vitest/browser@3.2.4)(happy-dom@18.0.1)(jiti@2.6.1)(jsdom@27.3.0(postcss@8.5.6))(lightningcss@1.30.2)(msw@2.10.2(@types/node@24.10.2)(typescript@5.9.3))(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1):
dependencies:
- '@types/chai': 5.2.2
+ '@types/chai': 5.2.3
'@vitest/expect': 3.2.4
- '@vitest/mocker': 3.2.4(msw@2.10.2(@types/node@24.10.2)(typescript@5.9.3))(vite@6.3.5(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))
+ '@vitest/mocker': 3.2.4(msw@2.10.2(@types/node@24.10.2)(typescript@5.9.3))(vite@7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1))
'@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.1
+ debug: 4.4.3
expect-type: 1.2.2
- magic-string: 0.30.18
+ magic-string: 0.30.21
pathe: 2.0.3
picomatch: 4.0.3
- std-env: 3.9.0
+ std-env: 3.10.0
tinybench: 2.9.0
tinyexec: 0.3.2
- tinyglobby: 0.2.14
+ tinyglobby: 0.2.15
tinypool: 1.1.1
tinyrainbow: 2.0.0
- vite: 6.3.5(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
- vite-node: 3.2.4(@types/node@24.10.2)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)
+ vite: 7.2.7(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
+ vite-node: 3.2.4(@types/node@24.10.2)(jiti@2.6.1)(lightningcss@1.30.2)(sass-embedded@1.89.2)(terser@5.36.0)(tsx@4.20.5)(yaml@2.8.1)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/debug': 4.1.12