diff --git a/packages/cli/README.md b/packages/cli/README.md index e3d136ed..59e2f085 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -70,19 +70,24 @@ Here's a comprehensive list of configuration options available in the `bucket.co "baseUrl": "https://app.bucket.co", "apiUrl": "https://app.bucket.co/api", "appId": "ap123456789", - "typesOutput": "gen/features.ts", + "typesOutput": [ + { + "path": "gen/features.ts", + "format": "react" + } + ], "keyFormat": "camelCase" } ``` -| Option | Description | Default | -| ------------- | --------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | -| `$schema` | Autocompletion for the config. `latest` can be replaced with a specific version. | "https://unpkg.com/@bucketco/cli@latest/schema.json" | -| `baseUrl` | Base URL for Bucket services. | "https://app.bucket.co" | -| `apiUrl` | API URL for Bucket services (overrides baseUrl for API calls). | "https://app.bucket.co/api" | -| `appId` | Your Bucket application ID. | Required | -| `typesOutput` | Path where TypeScript types will be generated. | "gen/features.ts" | -| `keyFormat` | Format for feature keys (options: custom, pascalCase, camelCase, snakeCaseUpper, snakeCaseLower, kebabCaseUpper, kebabCaseLower). | "custom" | +| Option | Description | Default | +| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | +| `$schema` | Autocompletion for the config. `latest` can be replaced with a specific version. | "https://unpkg.com/@bucketco/cli@latest/schema.json" | +| `baseUrl` | Base URL for Bucket services. | "https://app.bucket.co" | +| `apiUrl` | API URL for Bucket services (overrides baseUrl for API calls). | "https://app.bucket.co/api" | +| `appId` | Your Bucket application ID. | Required | +| `typesOutput` | Path(s) where TypeScript types will be generated. Can be a string or an array of objects with `path` and `format` properties. Available formats: `react` and `node`. | "gen/features.ts" with format "react" | +| `keyFormat` | Format for feature keys (options: custom, pascalCase, camelCase, snakeCaseUpper, snakeCaseLower, kebabCaseUpper, kebabCaseLower). | "custom" | You can override these settings using command-line options for individual commands. @@ -93,12 +98,12 @@ You can override these settings using command-line options for individual comman Initialize a new Bucket configuration in your project. This creates a `bucket.config.json` file with your settings and prompts for any required information not provided via options. ```bash -bucket init [--force] +bucket init [--overwrite] ``` Options: -- `--force`: Overwrite existing configuration file if one exists +- `--overwrite`: Overwrite existing configuration file if one exists - `--app-id `: Set the application ID - `--key-format `: Set the key format for features @@ -107,7 +112,7 @@ Options: All-in-one command to get started quickly. This command combines `init`, feature creation, and type generation in a single step. Use this for the fastest way to get up and running with Bucket. ```bash -bucket new "My Feature" [--key my-feature] [--app-id ap123456789] [--key-format custom] [--out gen/features.ts] +bucket new "My Feature" [--key my-feature] [--app-id ap123456789] [--key-format custom] [--out gen/features.ts] [--format react] ``` Options: @@ -116,6 +121,7 @@ Options: - `--app-id`: App ID to use - `--key-format`: Format for feature keys (custom, snake, camel, etc.) - `--out`: Path to generate TypeScript types +- `--format`: Format of the generated types (react or node) If you prefer more control over each step, you can use the individual commands (`init`, `features create`, `features types`) instead. @@ -170,13 +176,14 @@ Options: Generate TypeScript types for your features. This ensures type safety when using Bucket features in your TypeScript/JavaScript applications. ```bash -bucket features types [--app-id ap123456789] [--out gen/features.ts] +bucket features types [--app-id ap123456789] [--out gen/features.ts] [--format react] ``` Options: - `--app-id`: App ID to use - `--out`: Path to generate TypeScript types +- `--format`: Format of the generated types (react or node) ### `bucket apps` diff --git a/packages/cli/commands/features.ts b/packages/cli/commands/features.ts index 825dd34a..27b21e35 100644 --- a/packages/cli/commands/features.ts +++ b/packages/cli/commands/features.ts @@ -5,15 +5,16 @@ import { mkdir, writeFile } from "node:fs/promises"; import { dirname, isAbsolute, join, relative } from "node:path"; import ora, { Ora } from "ora"; -import { createFeature, listFeatures } from "../services/features.js"; +import { createFeature, Feature, listFeatures } from "../services/features.js"; import { configStore } from "../stores/config.js"; import { handleError, MissingAppIdError } from "../utils/errors.js"; -import { genDTS, genFeatureKey, KeyFormatPatterns } from "../utils/gen.js"; +import { genFeatureKey, genTypes, KeyFormatPatterns } from "../utils/gen.js"; import { appIdOption, featureKeyOption, featureNameArgument, keyFormatOption, + typesFormatOption, typesOutOption, } from "../utils/options.js"; @@ -80,16 +81,19 @@ export const listFeaturesAction = async () => { }; export const generateTypesAction = async () => { - const { baseUrl, appId, typesOutput } = configStore.getConfig(); + const { baseUrl, appId } = configStore.getConfig(); + const typesOutput = configStore.getConfig("typesOutput"); let spinner: Ora | undefined; - let featureKeys: string[] = []; + let features: Feature[] = []; try { if (!appId) throw new MissingAppIdError(); spinner = ora( `Loading features of app ${chalk.cyan(appId)} at ${chalk.cyan(baseUrl)}...`, ).start(); - featureKeys = (await listFeatures(appId)).map(({ key }) => key); + features = await listFeatures(appId, { + includeRemoteConfigs: true, + }); spinner.succeed( `Loaded features of app ${chalk.cyan(appId)} at ${chalk.cyan(baseUrl)}`, ); @@ -101,15 +105,22 @@ export const generateTypesAction = async () => { try { spinner = ora("Generating feature types...").start(); - const types = genDTS(featureKeys); const projectPath = configStore.getProjectPath(); - const outPath = isAbsolute(typesOutput) - ? typesOutput - : join(projectPath, typesOutput); - await mkdir(dirname(outPath), { recursive: true }); - await writeFile(outPath, types); + + // Generate types for each output configuration + for (const output of typesOutput) { + const types = await genTypes(features, output.format); + const outPath = isAbsolute(output.path) + ? output.path + : join(projectPath, output.path); + + await mkdir(dirname(outPath), { recursive: true }); + await writeFile(outPath, types); + spinner.text = `Generated ${output.format} types for ${chalk.cyan(appId)} in ${chalk.cyan(relative(projectPath, outPath))}.`; + } + spinner.succeed( - `Generated types for ${chalk.cyan(appId)} in ${chalk.cyan(relative(projectPath, outPath))}.`, + `Generated types for ${chalk.cyan(appId)} in ${typesOutput.length} location(s).`, ); } catch (error) { spinner?.fail("Type generation failed"); @@ -142,12 +153,17 @@ export function registerFeatureCommands(cli: Command) { .description("Generate feature types") .addOption(appIdOption) .addOption(typesOutOption) + .addOption(typesFormatOption) .action(generateTypesAction); // Update the config with the cli override values featuresCommand.hook("preAction", (_, command) => { - const { appId, keyFormat, out } = command.opts(); - configStore.setConfig({ appId, keyFormat, typesOutput: out }); + const { appId, keyFormat, out, format } = command.opts(); + configStore.setConfig({ + appId, + keyFormat, + typesOutput: out ? [{ path: out, format: format || "react" }] : undefined, + }); }); cli.addCommand(featuresCommand); diff --git a/packages/cli/commands/init.ts b/packages/cli/commands/init.ts index 9d064973..a0667d78 100644 --- a/packages/cli/commands/init.ts +++ b/packages/cli/commands/init.ts @@ -5,13 +5,13 @@ import { relative } from "node:path"; import ora, { Ora } from "ora"; import { App, listApps } from "../services/bootstrap.js"; -import { configStore } from "../stores/config.js"; +import { configStore, typeFormats } from "../stores/config.js"; import { chalkBrand, DEFAULT_TYPES_OUTPUT } from "../utils/constants.js"; import { handleError } from "../utils/errors.js"; -import { initOverrideOption } from "../utils/options.js"; +import { overwriteOption } from "../utils/options.js"; type InitArgs = { - force?: boolean; + overwrite?: boolean; }; export const initAction = async (args: InitArgs = {}) => { @@ -21,9 +21,9 @@ export const initAction = async (args: InitArgs = {}) => { try { // Check if config already exists const configPath = configStore.getConfigPath(); - if (configPath && !args.force) { + if (configPath && !args.overwrite) { throw new Error( - "Bucket is already initialized. Use --force to overwrite.", + "Bucket is already initialized. Use --overwrite to overwrite.", ); } @@ -74,16 +74,26 @@ export const initAction = async (args: InitArgs = {}) => { default: DEFAULT_TYPES_OUTPUT, }); + // Get types output format + const typesFormat = await select({ + message: "What is the output format?", + choices: typeFormats.map((format) => ({ + name: format, + value: format, + })), + default: "react", + }); + // Update config configStore.setConfig({ appId, keyFormat, - typesOutput, + typesOutput: [{ path: typesOutput, format: typesFormat }], }); // Create config file spinner = ora("Creating configuration...").start(); - await configStore.saveConfigFile(args.force); + await configStore.saveConfigFile(args.overwrite); spinner.succeed( `Configuration created at ${chalk.cyan(relative(process.cwd(), configStore.getConfigPath()!))}`, ); @@ -97,6 +107,6 @@ export function registerInitCommand(cli: Command) { cli .command("init") .description("Initialize a new Bucket configuration") - .addOption(initOverrideOption) + .addOption(overwriteOption) .action(initAction); } diff --git a/packages/cli/commands/new.ts b/packages/cli/commands/new.ts index 22abc7b9..e2777d0f 100644 --- a/packages/cli/commands/new.ts +++ b/packages/cli/commands/new.ts @@ -8,6 +8,7 @@ import { featureKeyOption, featureNameArgument, keyFormatOption, + typesFormatOption, typesOutOption, } from "../utils/options.js"; @@ -39,13 +40,18 @@ export function registerNewCommand(cli: Command) { .addOption(appIdOption) .addOption(keyFormatOption) .addOption(typesOutOption) + .addOption(typesFormatOption) .addOption(featureKeyOption) .addArgument(featureNameArgument) .action(newAction); // Update the config with the cli override values cli.hook("preAction", (command) => { - const { appId, keyFormat, out } = command.opts(); - configStore.setConfig({ appId, keyFormat, typesOutput: out }); + const { appId, keyFormat, out, format } = command.opts(); + configStore.setConfig({ + appId, + keyFormat, + typesOutput: out ? [{ path: out, format: format || "react" }] : undefined, + }); }); } diff --git a/packages/cli/index.ts b/packages/cli/index.ts index c0817cbd..e95f568e 100755 --- a/packages/cli/index.ts +++ b/packages/cli/index.ts @@ -11,6 +11,7 @@ import { registerNewCommand } from "./commands/new.js"; import { authStore } from "./stores/auth.js"; import { configStore } from "./stores/config.js"; import { apiUrlOption, baseUrlOption, debugOption } from "./utils/options.js"; +import { stripTrailingSlash } from "./utils/path.js"; async function main() { // Must load tokens and config before anything else @@ -25,9 +26,12 @@ async function main() { // Pre-action hook program.hook("preAction", () => { const { debug, baseUrl, apiUrl } = program.opts(); + const cleanedBaseUrl = stripTrailingSlash(baseUrl?.trim()); configStore.setConfig({ - baseUrl, - apiUrl: apiUrl || (baseUrl && `${baseUrl}/api`), + baseUrl: cleanedBaseUrl, + apiUrl: + stripTrailingSlash(apiUrl) || + (cleanedBaseUrl && `${cleanedBaseUrl}/api`), }); if (debug) { diff --git a/packages/cli/package.json b/packages/cli/package.json index e038a5df..d4564d67 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@bucketco/cli", - "version": "0.1.2", + "version": "0.1.3", "packageManager": "yarn@4.1.1", "description": "CLI for Bucket service", "main": "./dist/index.js", @@ -31,10 +31,14 @@ "scripts": { "build": "tsc && shx chmod +x dist/index.js", "bucket": "yarn build && ./dist/index.js", + "test": "vitest -c vite.config.js", + "test:ci": "vitest run -c vite.config.js --reporter=default --reporter=junit --outputFile=junit.xml", + "coverage": "vitest run --coverage", "lint": "eslint .", "lint:ci": "eslint --output-file eslint-report.json --format json .", "prettier": "prettier --check .", - "format": "yarn lint --fix && yarn prettier --write" + "format": "yarn lint --fix && yarn prettier --write", + "preversion": "yarn lint && yarn prettier && yarn vitest run -c vite.config.js && yarn build" }, "dependencies": { "@inquirer/prompts": "^5.3.8", @@ -42,6 +46,7 @@ "chalk": "^5.3.0", "change-case": "^5.4.4", "commander": "^12.1.0", + "fast-deep-equal": "^3.1.3", "find-up": "^7.0.0", "json5": "^2.2.3", "open": "^10.1.0", @@ -54,6 +59,7 @@ "eslint": "^9.21.0", "prettier": "^3.5.2", "shx": "^0.3.4", - "typescript": "^5.5.4" + "typescript": "^5.5.4", + "vitest": "^3.0.8" } } diff --git a/packages/cli/schema.json b/packages/cli/schema.json index 4d22ab5d..fd5100f7 100644 --- a/packages/cli/schema.json +++ b/packages/cli/schema.json @@ -13,7 +13,15 @@ "type": "string" }, "typesOutput": { - "type": "string" + "type": "array", + "items": { + "type": "object", + "properties": { + "path": { "type": "string" }, + "format": { "type": "string", "enum": ["react", "node"] } + }, + "required": ["path"] + } }, "keyFormat": { "type": "string", diff --git a/packages/cli/services/features.ts b/packages/cli/services/features.ts index a0763baf..2ce2f715 100644 --- a/packages/cli/services/features.ts +++ b/packages/cli/services/features.ts @@ -1,22 +1,51 @@ import { authRequest } from "../utils/auth.js"; -type Feature = { +export type RemoteConfigVariant = { + key?: string; + payload?: any; +}; + +export type RemoteConfig = { + variants: [ + { + variant: RemoteConfigVariant; + }, + ]; +}; + +export type Feature = { name: string; key: string; + remoteConfigs: RemoteConfig[]; }; -type FeaturesResponse = { +export type FeaturesResponse = { data: Feature[]; }; -export async function listFeatures(appId: string): Promise { +export type ListOptions = { + includeRemoteConfigs?: boolean; +}; + +export async function listFeatures( + appId: string, + options: ListOptions = {}, +): Promise { const response = await authRequest( `/apps/${appId}/features`, + { + params: { + sortBy: "name", + sortOrder: "desc", + includeRemoteConfigs: options.includeRemoteConfigs ? "true" : "false", + }, + }, ); - return response.data.map(({ name, key }) => ({ + return response.data.map(({ name, key, remoteConfigs }) => ({ name, key, + remoteConfigs, })); } diff --git a/packages/cli/stores/config.ts b/packages/cli/stores/config.ts index 644d1265..21126ae5 100644 --- a/packages/cli/stores/config.ts +++ b/packages/cli/stores/config.ts @@ -1,4 +1,5 @@ import { Ajv, ValidateFunction } from "ajv"; +import equal from "fast-deep-equal"; import { findUp } from "find-up"; import JSON5 from "json5"; import { readFile, writeFile } from "node:fs/promises"; @@ -13,6 +14,7 @@ import { SCHEMA_URL, } from "../utils/constants.js"; import { ConfigValidationError, handleError } from "../utils/errors.js"; +import { stripTrailingSlash } from "../utils/path.js"; export const keyFormats = [ "custom", @@ -23,15 +25,22 @@ export const keyFormats = [ "kebabCaseUpper", "kebabCaseLower", ] as const; - export type KeyFormat = (typeof keyFormats)[number]; +export const typeFormats = ["react", "node"] as const; +export type TypeFormat = (typeof typeFormats)[number]; + +export type TypesOutput = { + path: string; + format: TypeFormat; +}; + type Config = { $schema: string; baseUrl: string; apiUrl: string; appId: string | undefined; - typesOutput: string; + typesOutput: TypesOutput[]; keyFormat: KeyFormat; }; @@ -40,10 +49,21 @@ const defaultConfig: Config = { baseUrl: DEFAULT_BASE_URL, apiUrl: DEFAULT_API_URL, appId: undefined, - typesOutput: DEFAULT_TYPES_OUTPUT, + typesOutput: [{ path: DEFAULT_TYPES_OUTPUT, format: "react" }], keyFormat: "custom", }; +// Helper to normalize typesOutput to array format +export function normalizeTypesOutput( + output?: string | TypesOutput[], +): TypesOutput[] | undefined { + if (!output) return undefined; + if (typeof output === "string") { + return [{ path: output, format: "react" }]; + } + return output; +} + class ConfigStore { protected config: Config = { ...defaultConfig }; protected configPath: string | undefined; @@ -88,7 +108,15 @@ class ConfigStore { if (!this.configPath) return; const content = await readFile(this.configPath, "utf-8"); - const parsed = JSON5.parse(content); + const parsed = JSON5.parse>(content); + + // Normalize values + if (parsed.baseUrl) + parsed.baseUrl = stripTrailingSlash(parsed.baseUrl.trim()); + if (parsed.apiUrl) + parsed.apiUrl = stripTrailingSlash(parsed.apiUrl.trim()); + if (parsed.typesOutput?.length) + parsed.typesOutput = normalizeTypesOutput(parsed.typesOutput); if (!this.validateConfig!(parsed)) { void handleError( @@ -117,7 +145,7 @@ class ConfigStore { const key = untypedKey as keyof Config; if ( !["$schema"].includes(key) && - configWithoutDefaults[key] === defaultConfig[key] + equal(configWithoutDefaults[key], defaultConfig[key]) ) { delete configWithoutDefaults[key]; } diff --git a/packages/cli/test/json.test.ts b/packages/cli/test/json.test.ts new file mode 100644 index 00000000..bcbc1a6f --- /dev/null +++ b/packages/cli/test/json.test.ts @@ -0,0 +1,425 @@ +import { describe, expect, it } from "vitest"; + +import { + JSONToType, + mergeTypeASTs, + stringifyTypeAST, + toTypeAST, + TypeAST, +} from "../utils/json.js"; + +describe("JSON utilities", () => { + describe("toTypeAST", () => { + it("should handle primitive values", () => { + expect(toTypeAST("test")).toEqual({ kind: "primitive", type: "string" }); + expect(toTypeAST(42)).toEqual({ kind: "primitive", type: "number" }); + expect(toTypeAST(true)).toEqual({ kind: "primitive", type: "boolean" }); + expect(toTypeAST(null)).toEqual({ kind: "primitive", type: "null" }); + }); + + it("should handle arrays", () => { + expect(toTypeAST([1, 2, 3])).toEqual({ + kind: "array", + elementType: { kind: "primitive", type: "number" }, + }); + + expect(toTypeAST([])).toEqual({ + kind: "array", + elementType: { kind: "primitive", type: "any" }, + }); + }); + + it("should handle arrays with mixed element types", () => { + expect(toTypeAST([1, "test", true])).toEqual({ + kind: "array", + elementType: { + kind: "union", + types: [ + { kind: "primitive", type: "number" }, + { kind: "primitive", type: "string" }, + { kind: "primitive", type: "boolean" }, + ], + }, + }); + + expect(toTypeAST([{ name: "John" }, { age: 30 }])).toEqual({ + kind: "array", + elementType: { + kind: "object", + properties: [ + { + key: "name", + type: { kind: "primitive", type: "string" }, + optional: true, + }, + { + key: "age", + type: { kind: "primitive", type: "number" }, + optional: true, + }, + ], + }, + }); + }); + + it("should handle objects", () => { + expect(toTypeAST({ name: "John", age: 30 })).toEqual({ + kind: "object", + properties: [ + { + key: "name", + type: { kind: "primitive", type: "string" }, + optional: false, + }, + { + key: "age", + type: { kind: "primitive", type: "number" }, + optional: false, + }, + ], + }); + }); + + it("should handle nested structures", () => { + const input = { + user: { + name: "John", + contacts: [{ email: "john@example.com" }], + }, + }; + + const expected: TypeAST = { + kind: "object", + properties: [ + { + key: "user", + type: { + kind: "object", + properties: [ + { + key: "name", + type: { kind: "primitive", type: "string" }, + optional: false, + }, + { + key: "contacts", + type: { + kind: "array", + elementType: { + kind: "object", + properties: [ + { + key: "email", + type: { kind: "primitive", type: "string" }, + optional: false, + }, + ], + }, + }, + optional: false, + }, + ], + }, + optional: false, + }, + ], + }; + + expect(toTypeAST(input)).toEqual(expected); + }); + }); + + describe("mergeTypeASTs", () => { + it("should handle empty array", () => { + expect(mergeTypeASTs([])).toEqual({ kind: "primitive", type: "any" }); + }); + + it("should return the same AST for single item arrays", () => { + const ast: TypeAST = { kind: "primitive", type: "string" }; + expect(mergeTypeASTs([ast])).toEqual(ast); + }); + + it("should merge same primitive types", () => { + const types: TypeAST[] = [ + { kind: "primitive", type: "number" }, + { kind: "primitive", type: "number" }, + ]; + expect(mergeTypeASTs(types)).toEqual({ + kind: "primitive", + type: "number", + }); + }); + + it("should create union for different primitive types", () => { + const types: TypeAST[] = [ + { kind: "primitive", type: "string" }, + { kind: "primitive", type: "number" }, + ]; + expect(mergeTypeASTs(types)).toEqual({ + kind: "union", + types: [ + { kind: "primitive", type: "string" }, + { kind: "primitive", type: "number" }, + ], + }); + }); + + it("should merge array types", () => { + const types: TypeAST[] = [ + { kind: "array", elementType: { kind: "primitive", type: "number" } }, + { kind: "array", elementType: { kind: "primitive", type: "string" } }, + ]; + expect(mergeTypeASTs(types)).toEqual({ + kind: "array", + elementType: { + kind: "union", + types: [ + { kind: "primitive", type: "number" }, + { kind: "primitive", type: "string" }, + ], + }, + }); + }); + + it("should merge object types and mark missing properties as optional", () => { + const types: TypeAST[] = [ + { + kind: "object", + properties: [ + { + key: "name", + type: { kind: "primitive", type: "string" }, + optional: false, + }, + { + key: "age", + type: { kind: "primitive", type: "number" }, + optional: false, + }, + ], + }, + { + kind: "object", + properties: [ + { + key: "name", + type: { kind: "primitive", type: "string" }, + optional: false, + }, + { + key: "email", + type: { kind: "primitive", type: "string" }, + optional: false, + }, + ], + }, + ]; + + expect(mergeTypeASTs(types)).toEqual({ + kind: "object", + properties: [ + { + key: "name", + type: { kind: "primitive", type: "string" }, + optional: false, + }, + { + key: "age", + type: { kind: "primitive", type: "number" }, + optional: true, + }, + { + key: "email", + type: { kind: "primitive", type: "string" }, + optional: true, + }, + ], + }); + }); + + it("should create union for mixed kinds", () => { + const types: TypeAST[] = [ + { kind: "primitive", type: "string" }, + { kind: "array", elementType: { kind: "primitive", type: "number" } }, + ]; + + expect(mergeTypeASTs(types)).toEqual({ + kind: "union", + types, + }); + }); + }); + + describe("stringifyTypeAST", () => { + it("should stringify primitive types", () => { + expect(stringifyTypeAST({ kind: "primitive", type: "string" })).toBe( + "string", + ); + expect(stringifyTypeAST({ kind: "primitive", type: "number" })).toBe( + "number", + ); + expect(stringifyTypeAST({ kind: "primitive", type: "boolean" })).toBe( + "boolean", + ); + expect(stringifyTypeAST({ kind: "primitive", type: "null" })).toBe( + "null", + ); + }); + + it("should stringify array types", () => { + expect( + stringifyTypeAST({ + kind: "array", + elementType: { kind: "primitive", type: "string" }, + }), + ).toBe("(string)[]"); + }); + + it("should stringify object types", () => { + const ast: TypeAST = { + kind: "object", + properties: [ + { + key: "name", + type: { kind: "primitive", type: "string" }, + optional: false, + }, + { + key: "age", + type: { kind: "primitive", type: "number" }, + optional: true, + }, + ], + }; + + const expected = `{\n name: string,\n age?: number\n}`; + expect(stringifyTypeAST(ast)).toBe(expected); + }); + + it("should stringify empty objects", () => { + expect(stringifyTypeAST({ kind: "object", properties: [] })).toBe("{}"); + }); + + it("should stringify union types", () => { + const ast: TypeAST = { + kind: "union", + types: [ + { kind: "primitive", type: "string" }, + { kind: "primitive", type: "number" }, + ], + }; + + expect(stringifyTypeAST(ast)).toBe("string | number"); + }); + + it("should handle complex nested types", () => { + const ast: TypeAST = { + kind: "object", + properties: [ + { + key: "user", + type: { + kind: "object", + properties: [ + { + key: "name", + type: { kind: "primitive", type: "string" }, + optional: false, + }, + { + key: "contacts", + type: { + kind: "array", + elementType: { + kind: "object", + properties: [ + { + key: "email", + type: { kind: "primitive", type: "string" }, + optional: false, + }, + ], + }, + }, + optional: false, + }, + ], + }, + optional: false, + }, + ], + }; + + const expected = + `{\n user: {\n name: string,\n contacts: ({\n email: string\n })[]` + + `\n }\n}`; + expect(stringifyTypeAST(ast)).toBe(expected); + }); + }); + + describe("JSONToType", () => { + it("should handle empty arrays", () => { + expect(JSONToType([])).toBeNull(); + }); + + it("should generate type for array of primitives", () => { + expect(JSONToType([1, 2, 3])).toBe("number"); + expect(JSONToType(["a", "b", "c"])).toBe("string"); + expect(JSONToType([1, "a", true])).toBe("number | string | boolean"); + }); + + it("should handle arrays with simple mixed element types", () => { + const expected = "(number | string | boolean)[]"; + expect(JSONToType([["a", true], [1]])).toBe(expected); + }); + + it("should handle arrays with advanced mixed element types", () => { + const expected = + "(number | string | boolean | {\n id?: number,\n name?: string\n})[]"; + expect( + JSONToType([ + [1, "a", true], + [{ id: 1 }, { name: "test" }], + ]), + ).toBe(expected); + }); + + it("should merge arrays with nested arrays of mixed element types", () => { + const expected = "((boolean | number | string | {\n id: number\n})[])[]"; + expect(JSONToType([[[1, "test"], [true]], [[{ id: 1 }]]])).toBe(expected); + }); + + it("should generate type for array of objects", () => { + const expected = `{\n name: string,\n age?: number,\n email?: string\n}`; + expect( + JSONToType([ + { name: "John", age: 30 }, + { name: "Jane", email: "jane@example.com" }, + ]), + ).toBe(expected); + }); + + it("should handle complex nested structures", () => { + const expected = + `{\n user: {\n name: string,\n settings: {\n theme?: string,` + + `\n notifications?: boolean\n }\n }\n}`; + + expect( + JSONToType([ + { + user: { + name: "John", + settings: { theme: "dark" }, + }, + }, + { + user: { + name: "Jane", + settings: { notifications: true }, + }, + }, + ]), + ).toBe(expected); + }); + }); +}); diff --git a/packages/cli/utils/auth.ts b/packages/cli/utils/auth.ts index 09777677..d95e90c2 100644 --- a/packages/cli/utils/auth.ts +++ b/packages/cli/utils/auth.ts @@ -103,7 +103,9 @@ export async function authenticateUser(baseUrl: string) { export async function authRequest>( url: string, - options?: RequestInit, + options?: RequestInit & { + params?: Record; + }, retryCount = 0, ): Promise { const { baseUrl, apiUrl } = configStore.getConfig(); @@ -114,7 +116,14 @@ export async function authRequest>( return authRequest(url, options); } - const response = await fetch(`${apiUrl}${url}`, { + const resolvedUrl = new URL(`${apiUrl}/${url}`); + if (options?.params) { + Object.entries(options.params).forEach(([key, value]) => { + resolvedUrl.searchParams.append(key, value); + }); + } + + const response = await fetch(resolvedUrl, { ...options, headers: { ...options?.headers, diff --git a/packages/cli/utils/gen.ts b/packages/cli/utils/gen.ts index 8e44b0e5..88363980 100644 --- a/packages/cli/utils/gen.ts +++ b/packages/cli/utils/gen.ts @@ -1,5 +1,11 @@ import { camelCase, kebabCase, pascalCase, snakeCase } from "change-case"; +import { Feature, RemoteConfig } from "../services/features.js"; + +import { JSONToType } from "./json.js"; + +export type GenFormat = "react" | "node"; + // Keep in sync with Bucket main repo export const KeyFormats = [ "custom", @@ -63,19 +69,59 @@ export const KeyFormatPatterns: Record = { }, }; +function indentLines(str: string, indent = 2, lineBreak = "\n"): string { + const indentStr = " ".repeat(indent); + return str + .split(lineBreak) + .map((line) => `${indentStr}${line}`) + .join(lineBreak); +} + export function genFeatureKey(input: string, format: KeyFormat): string { return KeyFormatPatterns[format].transform(input); } -export const genDTS = (keys: string[]) => { +export function genRemoteConfig(remoteConfigs?: RemoteConfig[]) { + const variants = remoteConfigs?.[0]?.variants; + if (!variants?.length) return; + return JSONToType( + remoteConfigs![0].variants?.map(({ variant: { payload } }) => payload), + ); +} + +export function genTypes(features: Feature[], format: GenFormat = "react") { + const configDefs = new Map(); + features.forEach(({ key, name, remoteConfigs }) => { + const definition = genRemoteConfig(remoteConfigs); + if (!definition) return; + const configName = `${pascalCase(name)}ConfigPayload`; + configDefs.set(key, { name: configName, definition }); + }); + return /* ts */ ` // DO NOT EDIT THIS FILE. IT IS GENERATED BY THE BUCKET CLI AND WILL BE OVERWRITTEN. // eslint-disable // prettier-ignore -declare module "@bucketco/react-sdk" { - interface Features { -${keys.map((key) => ` "${key}": boolean;`).join("\n")} +import "@bucketco/${format}-sdk"; + +declare module "@bucketco/${format}-sdk" { + export interface Features { +${features + .map(({ key }) => { + const config = configDefs.get(key); + return indentLines( + `"${key}": ${config?.definition ? `{ config: { payload: ${config.name} } }` : "boolean"};`, + 4, + ); + }) + .join("\n")} } + +${Array.from(configDefs.values()) + .map(({ name, definition }) => { + return indentLines(`export type ${name} = ${definition}`); + }) + .join("\n\n")} } `.trim(); -}; +} diff --git a/packages/cli/utils/json.ts b/packages/cli/utils/json.ts new file mode 100644 index 00000000..b5e9480d --- /dev/null +++ b/packages/cli/utils/json.ts @@ -0,0 +1,206 @@ +type JSONPrimitive = + | number + | string + | boolean + | null + | JSONPrimitive[] + | { [key: string]: JSONPrimitive }; + +export type PrimitiveAST = { kind: "primitive"; type: string }; +export type ArrayAST = { kind: "array"; elementType: TypeAST }; +export type ObjectAST = { + kind: "object"; + properties: { key: string; type: TypeAST; optional: boolean }[]; +}; +export type UnionAST = { kind: "union"; types: TypeAST[] }; + +// Type AST to represent TypeScript types +export type TypeAST = PrimitiveAST | ArrayAST | ObjectAST | UnionAST; + +// Convert JSON value to TypeAST +export function toTypeAST(value: JSONPrimitive, path: string[] = []): TypeAST { + if (value === null) return { kind: "primitive", type: "null" }; + + if (Array.isArray(value)) { + if (value.length === 0) { + return { + kind: "array", + elementType: { kind: "primitive", type: "any" }, + }; + } + + // Process all elements in the array instead of just the first one + const elementTypes = value.map((item, index) => + toTypeAST(item, [...path, index.toString()]), + ); + + return { + kind: "array", + elementType: mergeTypeASTs(elementTypes), + }; + } + + if (typeof value === "object") { + return { + kind: "object", + properties: Object.entries(value).map(([key, val]) => ({ + key, + type: toTypeAST(val, [...path, key]), + optional: false, + })), + }; + } + + return { kind: "primitive", type: typeof value }; +} + +// Merge multiple TypeASTs into one +export function mergeTypeASTs(types: TypeAST[]): TypeAST { + if (types.length === 0) return { kind: "primitive", type: "any" }; + if (types.length === 1) return types[0]; + + // Group ASTs by kind + const byKind = { + union: types.filter((t) => t.kind === "union"), + primitive: types.filter((t) => t.kind === "primitive"), + array: types.filter((t) => t.kind === "array"), + object: types.filter((t) => t.kind === "object"), + }; + + // Create a union for mixed kinds + const hasMixedKinds = + byKind.union.length > 0 || // If we have any unions, treat it as mixed kinds + (byKind.primitive.length > 0 && + (byKind.array.length > 0 || byKind.object.length > 0)) || + (byKind.array.length > 0 && byKind.object.length > 0); + + if (hasMixedKinds) { + // If there are existing unions, flatten them into the current union + if (byKind.union.length > 0) { + // Flatten existing unions and collect types by category + const flattenedTypes: TypeAST[] = []; + const objectsToMerge: ObjectAST[] = [...byKind.object]; + const arraysToMerge: ArrayAST[] = [...byKind.array]; + + // Add primitives directly + flattenedTypes.push(...byKind.primitive); + + // Process union types + for (const unionType of byKind.union) { + for (const type of unionType.types) { + if (type.kind === "object") { + objectsToMerge.push(type); + } else if (type.kind === "array") { + arraysToMerge.push(type); + } else { + flattenedTypes.push(type); + } + } + } + + // Merge objects and arrays if they exist + if (objectsToMerge.length > 0) { + flattenedTypes.push(mergeTypeASTs(objectsToMerge)); + } + + if (arraysToMerge.length > 0) { + flattenedTypes.push(mergeTypeASTs(arraysToMerge)); + } + + return { kind: "union", types: flattenedTypes }; + } + + return { kind: "union", types }; + } + + // Handle primitives + if (byKind.primitive.length === types.length) { + const uniqueTypes = [...new Set(byKind.primitive.map((p) => p.type))]; + return uniqueTypes.length === 1 + ? { kind: "primitive", type: uniqueTypes[0] } + : { + kind: "union", + types: uniqueTypes.map((type) => ({ kind: "primitive", type })), + }; + } + + // Merge arrays + if (byKind.array.length === types.length) { + return { + kind: "array", + elementType: mergeTypeASTs(byKind.array.map((a) => a.elementType)), + }; + } + + // Merge objects + if (byKind.object.length === types.length) { + // Get all unique property keys + const allKeys = [ + ...new Set( + byKind.object.flatMap((obj) => obj.properties.map((p) => p.key)), + ), + ]; + + // Merge properties with same keys + const mergedProperties = allKeys.map((key) => { + const props = byKind.object + .map((obj) => obj.properties.find((p) => p.key === key)) + .filter((obj) => !!obj); + + return { + key, + type: mergeTypeASTs(props.map((p) => p.type)), + optional: byKind.object.some( + (obj) => !obj.properties.some((p) => p.key === key), + ), + }; + }); + + return { kind: "object", properties: mergedProperties }; + } + + // Fallback + return { kind: "primitive", type: "any" }; +} + +// Stringify TypeAST to TypeScript type declaration +export function stringifyTypeAST(ast: TypeAST, nestLevel = 0): string { + const indent = " ".repeat(nestLevel * 2); + const nextIndent = " ".repeat((nestLevel + 1) * 2); + + switch (ast.kind) { + case "primitive": + return ast.type; + + case "array": + return `(${stringifyTypeAST(ast.elementType, nestLevel)})[]`; + + case "object": + if (ast.properties.length === 0) return "{}"; + + return `{\n${ast.properties + .map( + ({ key, optional, type }) => + `${nextIndent}${key}${optional ? "?" : ""}: ${stringifyTypeAST( + type, + nestLevel + 1, + )}`, + ) + .join(",\n")}\n${indent}}`; + + case "union": + if (ast.types.length === 0) return "any"; + if (ast.types.length === 1) + return stringifyTypeAST(ast.types[0], nestLevel); + return ast.types + .map((type) => stringifyTypeAST(type, nestLevel)) + .join(" | "); + } +} + +// Convert JSON array to TypeScript type +export function JSONToType(json: JSONPrimitive[]): string | null { + if (!json.length) return null; + + return stringifyTypeAST(mergeTypeASTs(json.map((item) => toTypeAST(item)))); +} diff --git a/packages/cli/utils/options.ts b/packages/cli/utils/options.ts index 749e6553..92046ab4 100644 --- a/packages/cli/utils/options.ts +++ b/packages/cli/utils/options.ts @@ -21,16 +21,21 @@ export const appIdOption = new Option( `Bucket App ID. Falls back to appId value in ${CONFIG_FILE_NAME}.`, ); -export const initOverrideOption = new Option( - "-f, --force", +export const overwriteOption = new Option( + "--overwrite", "Force initialization and overwrite existing configuration.", ); export const typesOutOption = new Option( "-o, --out [path]", - `Output path for generated feature types. Falls back to typesOutput value in ${CONFIG_FILE_NAME}.`, + `Single output path for generated feature types. Falls back to typesOutput value in ${CONFIG_FILE_NAME}.`, ); +export const typesFormatOption = new Option( + "-f, --format [format]", + "Single output format for generated feature types", +).choices(["react", "node"]); + export const keyFormatOption = new Option( "--key-format [format]", `Feature key format. Falls back to keyFormat value in ${CONFIG_FILE_NAME}.`, diff --git a/packages/cli/utils/path.ts b/packages/cli/utils/path.ts new file mode 100644 index 00000000..a608cf1d --- /dev/null +++ b/packages/cli/utils/path.ts @@ -0,0 +1,3 @@ +export function stripTrailingSlash(str: T): T { + return str?.endsWith("/") ? (str.slice(0, -1) as T) : str; +} diff --git a/packages/cli/vite.config.js b/packages/cli/vite.config.js new file mode 100644 index 00000000..7b433523 --- /dev/null +++ b/packages/cli/vite.config.js @@ -0,0 +1,14 @@ +import { defineConfig } from "vite"; + +export default defineConfig({ + test: { + environment: "node", + exclude: [ + "**/node_modules/**", + "**/dist/**", + "**/.{idea,git,cache,output,temp}/**", + "**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*", + "**/example/**", + ], + }, +}); diff --git a/packages/node-sdk/example/bucket.ts b/packages/node-sdk/example/bucket.ts index 87e4ab8a..edbca501 100644 --- a/packages/node-sdk/example/bucket.ts +++ b/packages/node-sdk/example/bucket.ts @@ -1,7 +1,7 @@ import { BucketClient, Context } from "../src"; import { FeatureOverrides } from "../src/types"; -type CreateConfig = { +type CreateConfigPayload = { minimumLength: number; }; @@ -10,10 +10,8 @@ declare module "../src/types" { interface Features { "show-todos": boolean; "create-todos": { - isEnabled: boolean; config: { - key: string; - payload: CreateConfig; + payload: CreateConfigPayload; }; }; "delete-todos": boolean; diff --git a/packages/node-sdk/package.json b/packages/node-sdk/package.json index 54ea829c..c8beb05c 100644 --- a/packages/node-sdk/package.json +++ b/packages/node-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@bucketco/node-sdk", - "version": "1.6.0", + "version": "1.6.1", "license": "MIT", "repository": { "type": "git", diff --git a/packages/node-sdk/src/index.ts b/packages/node-sdk/src/index.ts index 7c2ccca2..8f1de37b 100644 --- a/packages/node-sdk/src/index.ts +++ b/packages/node-sdk/src/index.ts @@ -12,7 +12,7 @@ export type { FeatureOverridesFn, FeatureRemoteConfig, Features, - FullFeatureOverride, + FeatureType, HttpClient, HttpClientResponse, IdType, diff --git a/packages/node-sdk/src/types.ts b/packages/node-sdk/src/types.ts index 87172267..ea620bd1 100644 --- a/packages/node-sdk/src/types.ts +++ b/packages/node-sdk/src/types.ts @@ -171,15 +171,20 @@ export interface Feature< track(): Promise; } -export type FullFeatureOverride = { - isEnabled: boolean; +export type FeatureType = { config?: { - key: string; payload: any; }; }; -export type FeatureOverride = FullFeatureOverride | boolean; +export type FeatureOverride = + | (FeatureType & { + isEnabled: boolean; + config?: { + key: string; + }; + }) + | boolean; /** * Describes a collection of evaluated features. @@ -200,7 +205,7 @@ export interface Features {} export type TypedFeatures = keyof Features extends never ? Record : { - [FeatureKey in keyof Features]: Features[FeatureKey] extends FullFeatureOverride + [FeatureKey in keyof Features]: Features[FeatureKey] extends FeatureType ? Feature : Feature; }; @@ -214,7 +219,7 @@ export type FeatureOverrides = Partial< keyof Features extends never ? Record : { - [FeatureKey in keyof Features]: Features[FeatureKey] extends FullFeatureOverride + [FeatureKey in keyof Features]: Features[FeatureKey] extends FeatureOverride ? Features[FeatureKey] : Exclude; } diff --git a/packages/react-sdk/dev/plain/app.tsx b/packages/react-sdk/dev/plain/app.tsx index fb35b846..dd22267b 100644 --- a/packages/react-sdk/dev/plain/app.tsx +++ b/packages/react-sdk/dev/plain/app.tsx @@ -14,7 +14,13 @@ import { // Extending the Features interface to define the available features declare module "../../src" { interface Features { - huddles: boolean; + huddles: { + config: { + payload: { + maxParticipants: number; + }; + }; + }; } } diff --git a/packages/react-sdk/package.json b/packages/react-sdk/package.json index abe100bb..c5c22d1d 100644 --- a/packages/react-sdk/package.json +++ b/packages/react-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@bucketco/react-sdk", - "version": "3.1.1", + "version": "3.1.2", "license": "MIT", "repository": { "type": "git", diff --git a/packages/react-sdk/src/index.tsx b/packages/react-sdk/src/index.tsx index ca135838..55cbb8c9 100644 --- a/packages/react-sdk/src/index.tsx +++ b/packages/react-sdk/src/index.tsx @@ -33,16 +33,93 @@ export type { UserContext, }; +export type EmptyFeatureRemoteConfig = { key: undefined; payload: undefined }; + +export type FeatureType = { + config?: { + payload: any; + }; +}; + +/** + * A remotely managed configuration value for a feature. + */ +export type FeatureRemoteConfig = + | { + /** + * The key of the matched configuration value. + */ + key: string; + + /** + * The optional user-supplied payload data. + */ + payload: any; + } + | EmptyFeatureRemoteConfig; + +/** + * Describes a feature + */ +export interface Feature< + TConfig extends FeatureType["config"] | undefined = EmptyFeatureRemoteConfig, +> { + /** + * The key of the feature. + */ + key: string; + + /** + * If the feature is enabled. + */ + isEnabled: boolean; + + /** + * If the feature is loading. + */ + isLoading: boolean; + + /* + * Optional user-defined configuration. + */ + config: TConfig extends undefined + ? EmptyFeatureRemoteConfig + : TConfig & { + key: string; + }; + + /** + * Track feature usage in Bucket. + */ + track(): Promise | undefined; + /** + * Request feedback from the user. + */ + requestFeedback: (opts: RequestFeedbackOptions) => void; +} + // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface Features {} -const SDK_VERSION = `react-sdk/${version}`; +/** + * Describes a collection of evaluated feature. + * + * @remarks + * This types falls back to a generic Record if the Features interface + * has not been extended. + * + */ +export type TypedFeatures = keyof Features extends never + ? Record + : { + [TypedFeatureKey in keyof Features]: Features[TypedFeatureKey] extends FeatureType + ? Feature + : Feature; + }; -export type MaterializedFeatures = keyof Features extends never - ? Record - : Features; +export type FeatureKey = keyof TypedFeatures; -export type FeatureKey = keyof MaterializedFeatures; +const SDK_VERSION = `react-sdk/${version}`; type ProviderContextType = { client?: BucketClient; @@ -172,26 +249,6 @@ export type RequestFeedbackOptions = Omit< "featureKey" | "featureId" >; -export type EmptyConfig = { - key: undefined; - payload: undefined; -}; - -export type Feature = { - isEnabled: boolean; - isLoading: boolean; - config: MaterializedFeatures[TKey] extends boolean - ? EmptyConfig - : - | { - key: string; - payload: MaterializedFeatures[TKey]; - } - | EmptyConfig; - track: () => void; - requestFeedback: (opts: RequestFeedbackOptions) => void; -}; - /** * Returns the state of a given feature for the current context, e.g. * @@ -205,7 +262,7 @@ export type Feature = { */ export function useFeature( key: TKey, -): Feature { +): TypedFeatures[TKey] { const client = useClient(); const { features: { isLoading }, @@ -217,9 +274,13 @@ export function useFeature( if (isLoading || !client) { return { + key, isLoading, isEnabled: false, - config: { key: undefined, payload: undefined }, + config: { + key: undefined, + payload: undefined, + } as TypedFeatures[TKey]["config"], track, requestFeedback, }; @@ -228,6 +289,7 @@ export function useFeature( const feature = client.getFeature(key); return { + key, isLoading, track, requestFeedback, @@ -235,7 +297,7 @@ export function useFeature( return feature.isEnabled ?? false; }, get config() { - return feature.config as Feature["config"]; + return feature.config as TypedFeatures[TKey]["config"]; }, }; } diff --git a/packages/react-sdk/test/usage.test.tsx b/packages/react-sdk/test/usage.test.tsx index 87ec1559..3bf999f2 100644 --- a/packages/react-sdk/test/usage.test.tsx +++ b/packages/react-sdk/test/usage.test.tsx @@ -240,6 +240,7 @@ describe("useFeature", () => { }); expect(result.current).toStrictEqual({ + key: "huddle", isEnabled: false, isLoading: true, config: { key: undefined, payload: undefined }, @@ -257,6 +258,7 @@ describe("useFeature", () => { await waitFor(() => { expect(result.current).toStrictEqual({ + key: "huddle", config: { key: undefined, payload: undefined }, isEnabled: false, isLoading: false, @@ -275,6 +277,7 @@ describe("useFeature", () => { await waitFor(() => { expect(result.current).toStrictEqual({ + key: "abc", isEnabled: true, isLoading: false, config: { diff --git a/vitest.workspace.js b/vitest.workspace.js index 1ff8757f..8bc5a4eb 100644 --- a/vitest.workspace.js +++ b/vitest.workspace.js @@ -6,4 +6,5 @@ export default defineWorkspace([ "./packages/openfeature-node-provider/vite.config.js", "./packages/node-sdk/vite.config.js", "./packages/react-sdk/vite.config.mjs", + "./packages/cli/vite.config.js", ]); diff --git a/yarn.lock b/yarn.lock index c11cb521..06c1a173 100644 --- a/yarn.lock +++ b/yarn.lock @@ -473,6 +473,7 @@ __metadata: change-case: "npm:^5.4.4" commander: "npm:^12.1.0" eslint: "npm:^9.21.0" + fast-deep-equal: "npm:^3.1.3" find-up: "npm:^7.0.0" json5: "npm:^2.2.3" open: "npm:^10.1.0" @@ -480,6 +481,7 @@ __metadata: prettier: "npm:^3.5.2" shx: "npm:^0.3.4" typescript: "npm:^5.5.4" + vitest: "npm:^3.0.8" bin: bucket: ./dist/index.js languageName: unknown @@ -520,7 +522,16 @@ __metadata: languageName: unknown linkType: soft -"@bucketco/node-sdk@npm:1.6.0, @bucketco/node-sdk@workspace:packages/node-sdk": +"@bucketco/node-sdk@npm:1.6.0": + version: 1.6.0 + resolution: "@bucketco/node-sdk@npm:1.6.0" + dependencies: + "@bucketco/flag-evaluation": "npm:~0.1.0" + checksum: 10c0/a18d7f70d9f332307e53e4d43eb3c438f1c79f3ecb004371e4e7347a3f0165b7c44a96179b508ad4a8634869c11eb00bdfaada3969b211fc45d573e951e01c9f + languageName: node + linkType: hard + +"@bucketco/node-sdk@workspace:packages/node-sdk": version: 0.0.0-use.local resolution: "@bucketco/node-sdk@workspace:packages/node-sdk" dependencies: @@ -1096,6 +1107,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/aix-ppc64@npm:0.25.0" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/android-arm64@npm:0.21.5" @@ -1103,6 +1121,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/android-arm64@npm:0.25.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/android-arm@npm:0.21.5" @@ -1110,6 +1135,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/android-arm@npm:0.25.0" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/android-x64@npm:0.21.5" @@ -1117,6 +1149,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/android-x64@npm:0.25.0" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/darwin-arm64@npm:0.21.5" @@ -1124,6 +1163,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/darwin-arm64@npm:0.25.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/darwin-x64@npm:0.21.5" @@ -1131,6 +1177,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/darwin-x64@npm:0.25.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/freebsd-arm64@npm:0.21.5" @@ -1138,6 +1191,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/freebsd-arm64@npm:0.25.0" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/freebsd-x64@npm:0.21.5" @@ -1145,6 +1205,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/freebsd-x64@npm:0.25.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-arm64@npm:0.21.5" @@ -1152,6 +1219,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-arm64@npm:0.25.0" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-arm@npm:0.21.5" @@ -1159,6 +1233,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-arm@npm:0.25.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-ia32@npm:0.21.5" @@ -1166,6 +1247,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-ia32@npm:0.25.0" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-loong64@npm:0.21.5" @@ -1173,6 +1261,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-loong64@npm:0.25.0" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-mips64el@npm:0.21.5" @@ -1180,6 +1275,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-mips64el@npm:0.25.0" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-ppc64@npm:0.21.5" @@ -1187,6 +1289,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-ppc64@npm:0.25.0" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-riscv64@npm:0.21.5" @@ -1194,6 +1303,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-riscv64@npm:0.25.0" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-s390x@npm:0.21.5" @@ -1201,6 +1317,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-s390x@npm:0.25.0" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/linux-x64@npm:0.21.5" @@ -1208,6 +1331,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/linux-x64@npm:0.25.0" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/netbsd-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/netbsd-arm64@npm:0.25.0" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/netbsd-x64@npm:0.21.5" @@ -1215,6 +1352,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/netbsd-x64@npm:0.25.0" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openbsd-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/openbsd-arm64@npm:0.25.0" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/openbsd-x64@npm:0.21.5" @@ -1222,6 +1373,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/openbsd-x64@npm:0.25.0" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/sunos-x64@npm:0.21.5" @@ -1229,6 +1387,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/sunos-x64@npm:0.25.0" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/win32-arm64@npm:0.21.5" @@ -1236,6 +1401,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/win32-arm64@npm:0.25.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/win32-ia32@npm:0.21.5" @@ -1243,6 +1415,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/win32-ia32@npm:0.25.0" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.21.5": version: 0.21.5 resolution: "@esbuild/win32-x64@npm:0.21.5" @@ -1250,6 +1429,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-x64@npm:0.25.0": + version: 0.25.0 + resolution: "@esbuild/win32-x64@npm:0.25.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": version: 4.4.0 resolution: "@eslint-community/eslint-utils@npm:4.4.0" @@ -2626,6 +2812,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-android-arm-eabi@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.34.9" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@rollup/rollup-android-arm64@npm:4.21.3": version: 4.21.3 resolution: "@rollup/rollup-android-arm64@npm:4.21.3" @@ -2647,6 +2840,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-android-arm64@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-android-arm64@npm:4.34.9" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-darwin-arm64@npm:4.21.3": version: 4.21.3 resolution: "@rollup/rollup-darwin-arm64@npm:4.21.3" @@ -2668,6 +2868,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-darwin-arm64@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-darwin-arm64@npm:4.34.9" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-darwin-x64@npm:4.21.3": version: 4.21.3 resolution: "@rollup/rollup-darwin-x64@npm:4.21.3" @@ -2689,6 +2896,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-darwin-x64@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-darwin-x64@npm:4.34.9" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@rollup/rollup-freebsd-arm64@npm:4.34.8": version: 4.34.8 resolution: "@rollup/rollup-freebsd-arm64@npm:4.34.8" @@ -2696,6 +2910,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-freebsd-arm64@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.34.9" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-freebsd-x64@npm:4.34.8": version: 4.34.8 resolution: "@rollup/rollup-freebsd-x64@npm:4.34.8" @@ -2703,6 +2924,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-freebsd-x64@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-freebsd-x64@npm:4.34.9" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@rollup/rollup-linux-arm-gnueabihf@npm:4.21.3": version: 4.21.3 resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.21.3" @@ -2724,6 +2952,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm-gnueabihf@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.34.9" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-arm-musleabihf@npm:4.21.3": version: 4.21.3 resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.21.3" @@ -2745,6 +2980,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm-musleabihf@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.34.9" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-arm64-gnu@npm:4.21.3": version: 4.21.3 resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.21.3" @@ -2766,6 +3008,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm64-gnu@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.34.9" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-arm64-musl@npm:4.21.3": version: 4.21.3 resolution: "@rollup/rollup-linux-arm64-musl@npm:4.21.3" @@ -2787,6 +3036,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm64-musl@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.34.9" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-loongarch64-gnu@npm:4.34.8": version: 4.34.8 resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.34.8" @@ -2794,6 +3050,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-loongarch64-gnu@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.34.9" + conditions: os=linux & cpu=loong64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-powerpc64le-gnu@npm:4.21.3": version: 4.21.3 resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.21.3" @@ -2815,6 +3078,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-powerpc64le-gnu@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-linux-powerpc64le-gnu@npm:4.34.9" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-riscv64-gnu@npm:4.21.3": version: 4.21.3 resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.21.3" @@ -2836,6 +3106,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-riscv64-gnu@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.34.9" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-s390x-gnu@npm:4.21.3": version: 4.21.3 resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.21.3" @@ -2857,6 +3134,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-s390x-gnu@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.34.9" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-x64-gnu@npm:4.21.3": version: 4.21.3 resolution: "@rollup/rollup-linux-x64-gnu@npm:4.21.3" @@ -2878,6 +3162,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-x64-gnu@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.34.9" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-x64-musl@npm:4.21.3": version: 4.21.3 resolution: "@rollup/rollup-linux-x64-musl@npm:4.21.3" @@ -2899,6 +3190,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-x64-musl@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.34.9" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-win32-arm64-msvc@npm:4.21.3": version: 4.21.3 resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.21.3" @@ -2920,6 +3218,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-arm64-msvc@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.34.9" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-win32-ia32-msvc@npm:4.21.3": version: 4.21.3 resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.21.3" @@ -2941,6 +3246,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-ia32-msvc@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.34.9" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@rollup/rollup-win32-x64-msvc@npm:4.21.3": version: 4.21.3 resolution: "@rollup/rollup-win32-x64-msvc@npm:4.21.3" @@ -2962,6 +3274,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-x64-msvc@npm:4.34.9": + version: 4.34.9 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.34.9" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@rtsao/scc@npm:^1.1.0": version: 1.1.0 resolution: "@rtsao/scc@npm:1.1.0" @@ -3810,6 +4129,18 @@ __metadata: languageName: node linkType: hard +"@vitest/expect@npm:3.0.8": + version: 3.0.8 + resolution: "@vitest/expect@npm:3.0.8" + dependencies: + "@vitest/spy": "npm:3.0.8" + "@vitest/utils": "npm:3.0.8" + chai: "npm:^5.2.0" + tinyrainbow: "npm:^2.0.0" + checksum: 10c0/48aebec816f5a1b1f64f82b474ccfba537801a654f9547c581ed1c2d30b5de72207b643d3db2ac2869809a63a585425df30f65481f86d2bbbf979d8f235661bd + languageName: node + linkType: hard + "@vitest/mocker@npm:2.1.9": version: 2.1.9 resolution: "@vitest/mocker@npm:2.1.9" @@ -3829,6 +4160,25 @@ __metadata: languageName: node linkType: hard +"@vitest/mocker@npm:3.0.8": + version: 3.0.8 + resolution: "@vitest/mocker@npm:3.0.8" + dependencies: + "@vitest/spy": "npm:3.0.8" + estree-walker: "npm:^3.0.3" + magic-string: "npm:^0.30.17" + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + checksum: 10c0/bc89a31a5ebba900bb965b05d1fab581ae2872b6ddc17734f2a8433b9a3c7ae1fa0efd5f13bf03cf8075864b47954e8fcf609cf3a8258f0451375d68b81f135b + languageName: node + linkType: hard + "@vitest/pretty-format@npm:2.1.9, @vitest/pretty-format@npm:^2.1.9": version: 2.1.9 resolution: "@vitest/pretty-format@npm:2.1.9" @@ -3841,6 +4191,15 @@ __metadata: languageName: node linkType: hard +"@vitest/pretty-format@npm:3.0.8, @vitest/pretty-format@npm:^3.0.8": + version: 3.0.8 + resolution: "@vitest/pretty-format@npm:3.0.8" + dependencies: + tinyrainbow: "npm:^2.0.0" + checksum: 10c0/9133052605f16966db91d5e495afb5e32c3eb9215602248710bc3fd9034b1b511d1a7f1093571afee8664beb2a83303d42f1d5896fdba2a39adbb5ca9af788f7 + languageName: node + linkType: hard + "@vitest/runner@npm:1.6.1": version: 1.6.1 resolution: "@vitest/runner@npm:1.6.1" @@ -3862,6 +4221,16 @@ __metadata: languageName: node linkType: hard +"@vitest/runner@npm:3.0.8": + version: 3.0.8 + resolution: "@vitest/runner@npm:3.0.8" + dependencies: + "@vitest/utils": "npm:3.0.8" + pathe: "npm:^2.0.3" + checksum: 10c0/9a9d48dc82ca7101209b21309e18a4720e77d6015bf00a60ace6130e362320158d110f48cf9aa221e5e744729fe8a198811dd69e598688ffbb78c2fce2a842a1 + languageName: node + linkType: hard + "@vitest/snapshot@npm:1.6.1": version: 1.6.1 resolution: "@vitest/snapshot@npm:1.6.1" @@ -3884,6 +4253,17 @@ __metadata: languageName: node linkType: hard +"@vitest/snapshot@npm:3.0.8": + version: 3.0.8 + resolution: "@vitest/snapshot@npm:3.0.8" + dependencies: + "@vitest/pretty-format": "npm:3.0.8" + magic-string: "npm:^0.30.17" + pathe: "npm:^2.0.3" + checksum: 10c0/40564f60f7d166d10a03e9d1f8780daef164c76b2d85c1c8f5800168f907929c815395ac5c1f5c824da5ff29286f874e22dd8874b52044a53e0d858be67ceeb7 + languageName: node + linkType: hard + "@vitest/spy@npm:1.6.1": version: 1.6.1 resolution: "@vitest/spy@npm:1.6.1" @@ -3902,6 +4282,15 @@ __metadata: languageName: node linkType: hard +"@vitest/spy@npm:3.0.8": + version: 3.0.8 + resolution: "@vitest/spy@npm:3.0.8" + dependencies: + tinyspy: "npm:^3.0.2" + checksum: 10c0/7a940e6fbf5e6903758dfd904dedc9223df72ffa2a3d8c988706c2626c0fd3f9b129452bcd7af40bda014831f15ddb23ad7c1a7e42900acf4f3432b0c2bc8fb5 + languageName: node + linkType: hard + "@vitest/utils@npm:1.6.1": version: 1.6.1 resolution: "@vitest/utils@npm:1.6.1" @@ -3925,6 +4314,17 @@ __metadata: languageName: node linkType: hard +"@vitest/utils@npm:3.0.8": + version: 3.0.8 + resolution: "@vitest/utils@npm:3.0.8" + dependencies: + "@vitest/pretty-format": "npm:3.0.8" + loupe: "npm:^3.1.3" + tinyrainbow: "npm:^2.0.0" + checksum: 10c0/929e71582d27f5ec2fe422d72112471b36517620beb2c4398c116598ca55b36340b0fa97958d8584bc05153d92dbd60324664d5b623ec6eed8c72e50e226633c + languageName: node + linkType: hard + "@volar/language-core@npm:1.11.1, @volar/language-core@npm:~1.11.1": version: 1.11.1 resolution: "@volar/language-core@npm:1.11.1" @@ -5447,6 +5847,19 @@ __metadata: languageName: node linkType: hard +"chai@npm:^5.2.0": + version: 5.2.0 + resolution: "chai@npm:5.2.0" + dependencies: + assertion-error: "npm:^2.0.1" + check-error: "npm:^2.1.1" + deep-eql: "npm:^5.0.1" + loupe: "npm:^3.1.0" + pathval: "npm:^2.0.0" + checksum: 10c0/dfd1cb719c7cebb051b727672d382a35338af1470065cb12adb01f4ee451bbf528e0e0f9ab2016af5fc1eea4df6e7f4504dc8443f8f00bd8fb87ad32dc516f7d + languageName: node + linkType: hard + "chalk@npm:4.1.0": version: 4.1.0 resolution: "chalk@npm:4.1.0" @@ -6193,7 +6606,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:^4.3.7": +"debug@npm:^4.3.7, debug@npm:^4.4.0": version: 4.4.0 resolution: "debug@npm:4.4.0" dependencies: @@ -6908,7 +7321,7 @@ __metadata: languageName: node linkType: hard -"es-module-lexer@npm:^1.5.4": +"es-module-lexer@npm:^1.5.4, es-module-lexer@npm:^1.6.0": version: 1.6.0 resolution: "es-module-lexer@npm:1.6.0" checksum: 10c0/667309454411c0b95c476025929881e71400d74a746ffa1ff4cb450bd87f8e33e8eef7854d68e401895039ac0bac64e7809acbebb6253e055dd49ea9e3ea9212 @@ -7087,6 +7500,92 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:^0.25.0": + version: 0.25.0 + resolution: "esbuild@npm:0.25.0" + dependencies: + "@esbuild/aix-ppc64": "npm:0.25.0" + "@esbuild/android-arm": "npm:0.25.0" + "@esbuild/android-arm64": "npm:0.25.0" + "@esbuild/android-x64": "npm:0.25.0" + "@esbuild/darwin-arm64": "npm:0.25.0" + "@esbuild/darwin-x64": "npm:0.25.0" + "@esbuild/freebsd-arm64": "npm:0.25.0" + "@esbuild/freebsd-x64": "npm:0.25.0" + "@esbuild/linux-arm": "npm:0.25.0" + "@esbuild/linux-arm64": "npm:0.25.0" + "@esbuild/linux-ia32": "npm:0.25.0" + "@esbuild/linux-loong64": "npm:0.25.0" + "@esbuild/linux-mips64el": "npm:0.25.0" + "@esbuild/linux-ppc64": "npm:0.25.0" + "@esbuild/linux-riscv64": "npm:0.25.0" + "@esbuild/linux-s390x": "npm:0.25.0" + "@esbuild/linux-x64": "npm:0.25.0" + "@esbuild/netbsd-arm64": "npm:0.25.0" + "@esbuild/netbsd-x64": "npm:0.25.0" + "@esbuild/openbsd-arm64": "npm:0.25.0" + "@esbuild/openbsd-x64": "npm:0.25.0" + "@esbuild/sunos-x64": "npm:0.25.0" + "@esbuild/win32-arm64": "npm:0.25.0" + "@esbuild/win32-ia32": "npm:0.25.0" + "@esbuild/win32-x64": "npm:0.25.0" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/5767b72da46da3cfec51661647ec850ddbf8a8d0662771139f10ef0692a8831396a0004b2be7966cecdb08264fb16bdc16290dcecd92396fac5f12d722fa013d + languageName: node + linkType: hard + "escalade@npm:^3.1.1": version: 3.1.1 resolution: "escalade@npm:3.1.1" @@ -10618,7 +11117,7 @@ __metadata: languageName: node linkType: hard -"loupe@npm:^3.1.2": +"loupe@npm:^3.1.2, loupe@npm:^3.1.3": version: 3.1.3 resolution: "loupe@npm:3.1.3" checksum: 10c0/f5dab4144254677de83a35285be1b8aba58b3861439ce4ba65875d0d5f3445a4a496daef63100ccf02b2dbc25bf58c6db84c9cb0b96d6435331e9d0a33b48541 @@ -10682,7 +11181,7 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:^0.30.12": +"magic-string@npm:^0.30.12, magic-string@npm:^0.30.17": version: 0.30.17 resolution: "magic-string@npm:0.30.17" dependencies: @@ -11330,6 +11829,15 @@ __metadata: languageName: node linkType: hard +"nanoid@npm:^3.3.8": + version: 3.3.9 + resolution: "nanoid@npm:3.3.9" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/4515abe53db7b150cf77074558efc20d8e916d6910d557b5ce72e8bbf6f8e7554d3d7a0d180bfa65e5d8e99aa51b207aa8a3bf5f3b56233897b146d592e30b24 + languageName: node + linkType: hard + "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0" @@ -12563,6 +13071,13 @@ __metadata: languageName: node linkType: hard +"pathe@npm:^2.0.3": + version: 2.0.3 + resolution: "pathe@npm:2.0.3" + checksum: 10c0/c118dc5a8b5c4166011b2b70608762e260085180bb9e33e80a50dcdb1e78c010b1624f4280c492c92b05fc276715a4c357d1f9edc570f8f1b3d90b6839ebaca1 + languageName: node + linkType: hard + "pathval@npm:^1.1.1": version: 1.1.1 resolution: "pathval@npm:1.1.1" @@ -12598,6 +13113,13 @@ __metadata: languageName: node linkType: hard +"picocolors@npm:^1.1.1": + version: 1.1.1 + resolution: "picocolors@npm:1.1.1" + checksum: 10c0/e2e3e8170ab9d7c7421969adaa7e1b31434f789afb9b3f115f6b96d91945041ac3ceb02e9ec6fe6510ff036bcc0bf91e69a1772edc0b707e12b19c0f2d6bcf58 + languageName: node + linkType: hard + "picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" @@ -13208,6 +13730,17 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.5.3": + version: 8.5.3 + resolution: "postcss@npm:8.5.3" + dependencies: + nanoid: "npm:^3.3.8" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/b75510d7b28c3ab728c8733dd01538314a18c52af426f199a3c9177e63eb08602a3938bfb66b62dc01350b9aed62087eabbf229af97a1659eb8d3513cec823b3 + languageName: node + linkType: hard + "preact@npm:^10.22.1": version: 10.22.1 resolution: "preact@npm:10.22.1" @@ -14138,6 +14671,78 @@ __metadata: languageName: node linkType: hard +"rollup@npm:^4.30.1": + version: 4.34.9 + resolution: "rollup@npm:4.34.9" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.34.9" + "@rollup/rollup-android-arm64": "npm:4.34.9" + "@rollup/rollup-darwin-arm64": "npm:4.34.9" + "@rollup/rollup-darwin-x64": "npm:4.34.9" + "@rollup/rollup-freebsd-arm64": "npm:4.34.9" + "@rollup/rollup-freebsd-x64": "npm:4.34.9" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.34.9" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.34.9" + "@rollup/rollup-linux-arm64-gnu": "npm:4.34.9" + "@rollup/rollup-linux-arm64-musl": "npm:4.34.9" + "@rollup/rollup-linux-loongarch64-gnu": "npm:4.34.9" + "@rollup/rollup-linux-powerpc64le-gnu": "npm:4.34.9" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.34.9" + "@rollup/rollup-linux-s390x-gnu": "npm:4.34.9" + "@rollup/rollup-linux-x64-gnu": "npm:4.34.9" + "@rollup/rollup-linux-x64-musl": "npm:4.34.9" + "@rollup/rollup-win32-arm64-msvc": "npm:4.34.9" + "@rollup/rollup-win32-ia32-msvc": "npm:4.34.9" + "@rollup/rollup-win32-x64-msvc": "npm:4.34.9" + "@types/estree": "npm:1.0.6" + fsevents: "npm:~2.3.2" + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-freebsd-arm64": + optional: true + "@rollup/rollup-freebsd-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-loongarch64-gnu": + optional: true + "@rollup/rollup-linux-powerpc64le-gnu": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 10c0/dd0be1f7c4f8a93040026be13ecc39259fb55313db0dac7eafd97a3ac01ab4584e6b1a8afd86b0259dcf391699d5560a678abe6c0729af0aa4f2d5df70f05c8c + languageName: node + linkType: hard + "rrweb-cssom@npm:^0.6.0": version: 0.6.0 resolution: "rrweb-cssom@npm:0.6.0" @@ -15435,7 +16040,7 @@ __metadata: languageName: node linkType: hard -"tinyexec@npm:^0.3.1": +"tinyexec@npm:^0.3.1, tinyexec@npm:^0.3.2": version: 0.3.2 resolution: "tinyexec@npm:0.3.2" checksum: 10c0/3efbf791a911be0bf0821eab37a3445c2ba07acc1522b1fa84ae1e55f10425076f1290f680286345ed919549ad67527d07281f1c19d584df3b74326909eb1f90 @@ -15459,7 +16064,7 @@ __metadata: languageName: node linkType: hard -"tinypool@npm:^1.0.1": +"tinypool@npm:^1.0.1, tinypool@npm:^1.0.2": version: 1.0.2 resolution: "tinypool@npm:1.0.2" checksum: 10c0/31ac184c0ff1cf9a074741254fe9ea6de95026749eb2b8ec6fd2b9d8ca94abdccda731f8e102e7f32e72ed3b36d32c6975fd5f5523df3f1b6de6c3d8dfd95e63 @@ -15473,6 +16078,13 @@ __metadata: languageName: node linkType: hard +"tinyrainbow@npm:^2.0.0": + version: 2.0.0 + resolution: "tinyrainbow@npm:2.0.0" + checksum: 10c0/c83c52bef4e0ae7fb8ec6a722f70b5b6fa8d8be1c85792e829f56c0e1be94ab70b293c032dc5048d4d37cfe678f1f5babb04bdc65fd123098800148ca989184f + languageName: node + linkType: hard + "tinyspy@npm:^2.2.0": version: 2.2.0 resolution: "tinyspy@npm:2.2.0" @@ -16323,6 +16935,21 @@ __metadata: languageName: node linkType: hard +"vite-node@npm:3.0.8": + version: 3.0.8 + resolution: "vite-node@npm:3.0.8" + dependencies: + cac: "npm:^6.7.14" + debug: "npm:^4.4.0" + es-module-lexer: "npm:^1.6.0" + pathe: "npm:^2.0.3" + vite: "npm:^5.0.0 || ^6.0.0" + bin: + vite-node: vite-node.mjs + checksum: 10c0/1e7243ad04edc71ccff67b1a686cc85b59ad803645b83c524eab6cde92d6c8f06d595cc99cd3236b4017de27d6760808c419711cd728471eb36ec9a6734ef651 + languageName: node + linkType: hard + "vite-plugin-dts@npm:^4.0.0-beta.1": version: 4.0.0-beta.1 resolution: "vite-plugin-dts@npm:4.0.0-beta.1" @@ -16368,6 +16995,58 @@ __metadata: languageName: node linkType: hard +"vite@npm:^5.0.0 || ^6.0.0": + version: 6.2.1 + resolution: "vite@npm:6.2.1" + dependencies: + esbuild: "npm:^0.25.0" + fsevents: "npm:~2.3.3" + postcss: "npm:^8.5.3" + rollup: "npm:^4.30.1" + peerDependencies: + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: ">=1.21.0" + less: "*" + lightningcss: ^1.21.0 + sass: "*" + sass-embedded: "*" + stylus: "*" + sugarss: "*" + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/2c024376a840eae2ce9cfba98d62e9f1eae93caa8304875854dbc0740414aedcfbe157c2244567bd456cdb60a300312af02ae9b5c63c147d35cf4da3a0591312 + languageName: node + linkType: hard + "vite@npm:^5.0.0, vite@npm:^5.0.13, vite@npm:^5.3.5": version: 5.4.6 resolution: "vite@npm:5.4.6" @@ -16501,6 +17180,59 @@ __metadata: languageName: node linkType: hard +"vitest@npm:^3.0.8": + version: 3.0.8 + resolution: "vitest@npm:3.0.8" + dependencies: + "@vitest/expect": "npm:3.0.8" + "@vitest/mocker": "npm:3.0.8" + "@vitest/pretty-format": "npm:^3.0.8" + "@vitest/runner": "npm:3.0.8" + "@vitest/snapshot": "npm:3.0.8" + "@vitest/spy": "npm:3.0.8" + "@vitest/utils": "npm:3.0.8" + chai: "npm:^5.2.0" + debug: "npm:^4.4.0" + expect-type: "npm:^1.1.0" + magic-string: "npm:^0.30.17" + pathe: "npm:^2.0.3" + std-env: "npm:^3.8.0" + tinybench: "npm:^2.9.0" + tinyexec: "npm:^0.3.2" + tinypool: "npm:^1.0.2" + tinyrainbow: "npm:^2.0.0" + vite: "npm:^5.0.0 || ^6.0.0" + vite-node: "npm:3.0.8" + why-is-node-running: "npm:^2.3.0" + peerDependencies: + "@edge-runtime/vm": "*" + "@types/debug": ^4.1.12 + "@types/node": ^18.0.0 || ^20.0.0 || >=22.0.0 + "@vitest/browser": 3.0.8 + "@vitest/ui": 3.0.8 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@types/debug": + optional: true + "@types/node": + optional: true + "@vitest/browser": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + bin: + vitest: vitest.mjs + checksum: 10c0/007a951c4e10ceda1eecad38e5bcc7aa25ed90269614e1394eb2c5fa5f51bbe05d915bcec27fc2e18da8bdea27cea80d428095ef818b97857c51422fddda34ff + languageName: node + linkType: hard + "vitest@npm:~1.6.0": version: 1.6.1 resolution: "vitest@npm:1.6.1"