litestar-vite-plugin 0.15.0-rc.1 → 0.15.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,122 @@
1
+ import { exec } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import { createRequire } from "node:module";
4
+ import path from "node:path";
5
+ import { promisify } from "node:util";
6
+ import { resolveInstallHint, resolvePackageExecutor } from "../install-hint.js";
7
+ import { emitPagePropsTypes } from "./emit-page-props-types.js";
8
+ const execAsync = promisify(exec);
9
+ const nodeRequire = createRequire(import.meta.url);
10
+ function findOpenApiTsConfig(projectRoot) {
11
+ const candidates = [path.resolve(projectRoot, "openapi-ts.config.ts"), path.resolve(projectRoot, "hey-api.config.ts"), path.resolve(projectRoot, ".hey-api.config.ts")];
12
+ return candidates.find((p) => fs.existsSync(p)) || null;
13
+ }
14
+ function buildHeyApiPlugins(config) {
15
+ const plugins = ["@hey-api/typescript", "@hey-api/schemas"];
16
+ if (config.generateSdk) {
17
+ plugins.push("@hey-api/sdk", config.sdkClientPlugin);
18
+ }
19
+ if (config.generateZod) {
20
+ plugins.push("zod");
21
+ }
22
+ return plugins;
23
+ }
24
+ async function runHeyApiGeneration(config, configPath, plugins, logger) {
25
+ const { projectRoot, openapiPath, output, executor, generateZod } = config;
26
+ const sdkOutput = path.join(output, "api");
27
+ if (process.env.VITEST || process.env.VITE_TEST || process.env.NODE_ENV === "test" || process.env.CI) {
28
+ try {
29
+ nodeRequire.resolve("@hey-api/openapi-ts/package.json", { paths: [projectRoot] });
30
+ } catch {
31
+ throw new Error("@hey-api/openapi-ts not installed");
32
+ }
33
+ }
34
+ let args;
35
+ if (configPath) {
36
+ args = ["@hey-api/openapi-ts", "--file", configPath];
37
+ } else {
38
+ const relativeOpenapiPath = path.relative(projectRoot, path.resolve(projectRoot, openapiPath));
39
+ args = ["@hey-api/openapi-ts", "-i", relativeOpenapiPath, "-o", sdkOutput];
40
+ if (plugins.length) {
41
+ args.push("--plugins", ...plugins);
42
+ }
43
+ }
44
+ if (generateZod) {
45
+ try {
46
+ nodeRequire.resolve("zod", { paths: [projectRoot] });
47
+ } catch {
48
+ logger?.warn(`zod not installed - run: ${resolveInstallHint()} zod`);
49
+ }
50
+ }
51
+ const cmd = resolvePackageExecutor(args.join(" "), executor);
52
+ await execAsync(cmd, { cwd: projectRoot });
53
+ return sdkOutput;
54
+ }
55
+ async function runTypeGeneration(config, options = {}) {
56
+ const { logger } = options;
57
+ const startTime = Date.now();
58
+ const result = {
59
+ generated: false,
60
+ generatedFiles: [],
61
+ skippedFiles: [],
62
+ durationMs: 0,
63
+ warnings: [],
64
+ errors: []
65
+ };
66
+ try {
67
+ const { projectRoot, openapiPath, pagePropsPath, output, generateSdk, generatePageProps } = config;
68
+ const absoluteOpenapiPath = path.resolve(projectRoot, openapiPath);
69
+ const absolutePagePropsPath = path.resolve(projectRoot, pagePropsPath);
70
+ const configPath = findOpenApiTsConfig(projectRoot);
71
+ const shouldGenerateSdk = configPath || generateSdk;
72
+ if (fs.existsSync(absoluteOpenapiPath) && shouldGenerateSdk) {
73
+ const plugins = buildHeyApiPlugins(config);
74
+ logger?.info("Generating TypeScript types...");
75
+ if (configPath) {
76
+ const relConfigPath = path.relative(projectRoot, configPath);
77
+ logger?.info(`openapi-ts config: ${relConfigPath}`);
78
+ }
79
+ try {
80
+ const outputPath = await runHeyApiGeneration(config, configPath, plugins, logger);
81
+ result.generatedFiles.push(outputPath);
82
+ result.generated = true;
83
+ } catch (error) {
84
+ const message = error instanceof Error ? error.message : String(error);
85
+ if (message.includes("not found") || message.includes("ENOENT") || message.includes("not installed")) {
86
+ const zodHint = config.generateZod ? " zod" : "";
87
+ const warning = `@hey-api/openapi-ts not installed - run: ${resolveInstallHint()} -D @hey-api/openapi-ts${zodHint}`;
88
+ result.warnings.push(warning);
89
+ logger?.warn(warning);
90
+ } else {
91
+ result.errors.push(message);
92
+ logger?.error(`Type generation failed: ${message}`);
93
+ }
94
+ }
95
+ }
96
+ if (generatePageProps && fs.existsSync(absolutePagePropsPath)) {
97
+ try {
98
+ const changed = await emitPagePropsTypes(absolutePagePropsPath, output);
99
+ const pagePropsOutput = path.join(output, "page-props.ts");
100
+ if (changed) {
101
+ result.generatedFiles.push(pagePropsOutput);
102
+ result.generated = true;
103
+ } else {
104
+ result.skippedFiles.push(pagePropsOutput);
105
+ }
106
+ } catch (error) {
107
+ const message = error instanceof Error ? error.message : String(error);
108
+ result.errors.push(`Page props generation failed: ${message}`);
109
+ logger?.error(`Page props generation failed: ${message}`);
110
+ }
111
+ }
112
+ } finally {
113
+ result.durationMs = Date.now() - startTime;
114
+ }
115
+ return result;
116
+ }
117
+ export {
118
+ buildHeyApiPlugins,
119
+ findOpenApiTsConfig,
120
+ runHeyApiGeneration,
121
+ runTypeGeneration
122
+ };
@@ -1,15 +1,11 @@
1
- import { exec } from "node:child_process";
2
1
  import fs from "node:fs";
3
- import { createRequire } from "node:module";
4
2
  import path from "node:path";
5
- import { promisify } from "node:util";
6
3
  import colors from "picocolors";
7
- import { resolveInstallHint, resolvePackageExecutor } from "../install-hint.js";
8
4
  import { debounce } from "./debounce.js";
9
5
  import { emitPagePropsTypes } from "./emit-page-props-types.js";
10
6
  import { formatPath } from "./format-path.js";
11
- const execAsync = promisify(exec);
12
- const nodeRequire = createRequire(import.meta.url);
7
+ import { shouldRunOpenApiTs, updateOpenApiTsCache } from "./typegen-cache.js";
8
+ import { buildHeyApiPlugins, findOpenApiTsConfig, runHeyApiGeneration } from "./typegen-core.js";
13
9
  async function getFileMtime(filePath) {
14
10
  const stat = await fs.promises.stat(filePath);
15
11
  return stat.mtimeMs.toString();
@@ -21,8 +17,14 @@ function createLitestarTypeGenPlugin(typesConfig, options) {
21
17
  let server = null;
22
18
  let isGenerating = false;
23
19
  let resolvedConfig = null;
24
- let _chosenConfigPath = null;
25
- async function runTypeGeneration() {
20
+ function createViteLogger() {
21
+ return {
22
+ info: (message) => resolvedConfig?.logger.info(`${colors.cyan("\u2022")} ${message}`),
23
+ warn: (message) => resolvedConfig?.logger.warn(`${colors.yellow("!")} ${message}`),
24
+ error: (message) => resolvedConfig?.logger.error(`${colors.red("\u2717")} ${message}`)
25
+ };
26
+ }
27
+ async function runTypeGenerationWithCache() {
26
28
  if (isGenerating) {
27
29
  return false;
28
30
  }
@@ -30,49 +32,70 @@ function createLitestarTypeGenPlugin(typesConfig, options) {
30
32
  const startTime = Date.now();
31
33
  try {
32
34
  const projectRoot = resolvedConfig?.root ?? process.cwd();
33
- const openapiPath = path.resolve(projectRoot, typesConfig.openapiPath);
34
- const pagePropsPath = path.resolve(projectRoot, typesConfig.pagePropsPath);
35
+ const absoluteOpenapiPath = path.resolve(projectRoot, typesConfig.openapiPath);
36
+ const absolutePagePropsPath = path.resolve(projectRoot, typesConfig.pagePropsPath);
35
37
  let generated = false;
36
- const candidates = [path.resolve(projectRoot, "openapi-ts.config.ts"), path.resolve(projectRoot, "hey-api.config.ts"), path.resolve(projectRoot, ".hey-api.config.ts")];
37
- const configPath = candidates.find((p) => fs.existsSync(p)) || null;
38
- _chosenConfigPath = configPath;
39
- const shouldRunOpenApiTs = configPath || typesConfig.generateSdk;
40
- if (fs.existsSync(openapiPath) && shouldRunOpenApiTs) {
41
- resolvedConfig?.logger.info(`${colors.cyan("\u2022")} Generating TypeScript types...`);
42
- if (resolvedConfig && configPath) {
43
- const relConfigPath = formatPath(configPath, resolvedConfig.root);
44
- resolvedConfig.logger.info(`${colors.cyan("\u2022")} openapi-ts config: ${colors.yellow(relConfigPath)}`);
45
- }
46
- const sdkOutput = path.join(typesConfig.output, "api");
47
- let args;
48
- if (configPath) {
49
- args = ["@hey-api/openapi-ts", "--file", configPath];
50
- } else {
51
- args = ["@hey-api/openapi-ts", "-i", typesConfig.openapiPath, "-o", sdkOutput];
52
- const plugins = ["@hey-api/typescript", "@hey-api/schemas"];
53
- if (typesConfig.generateSdk) {
54
- plugins.push("@hey-api/sdk", sdkClientPlugin);
55
- }
56
- if (typesConfig.generateZod) {
57
- plugins.push("zod");
58
- }
59
- if (plugins.length) {
60
- args.push("--plugins", ...plugins);
38
+ const logger = createViteLogger();
39
+ const configPath = findOpenApiTsConfig(projectRoot);
40
+ const shouldGenerateSdk = configPath || typesConfig.generateSdk;
41
+ if (fs.existsSync(absoluteOpenapiPath) && shouldGenerateSdk) {
42
+ const plugins = buildHeyApiPlugins({
43
+ generateSdk: typesConfig.generateSdk,
44
+ generateZod: typesConfig.generateZod,
45
+ sdkClientPlugin
46
+ });
47
+ const cacheOptions = {
48
+ generateSdk: typesConfig.generateSdk,
49
+ generateZod: typesConfig.generateZod,
50
+ plugins
51
+ };
52
+ const shouldRun = await shouldRunOpenApiTs(absoluteOpenapiPath, configPath, cacheOptions);
53
+ if (shouldRun) {
54
+ logger.info("Generating TypeScript types...");
55
+ if (configPath && resolvedConfig) {
56
+ const relConfigPath = formatPath(configPath, resolvedConfig.root);
57
+ logger.info(`openapi-ts config: ${colors.yellow(relConfigPath)}`);
61
58
  }
62
- }
63
- if (typesConfig.generateZod) {
59
+ const coreConfig = {
60
+ projectRoot,
61
+ openapiPath: typesConfig.openapiPath,
62
+ output: typesConfig.output,
63
+ pagePropsPath: typesConfig.pagePropsPath,
64
+ generateSdk: typesConfig.generateSdk,
65
+ generateZod: typesConfig.generateZod,
66
+ generatePageProps: false,
67
+ // Handle separately below
68
+ sdkClientPlugin,
69
+ executor
70
+ };
64
71
  try {
65
- nodeRequire.resolve("zod", { paths: [projectRoot] });
66
- } catch {
67
- resolvedConfig?.logger.warn(`${colors.yellow("!")} zod not installed - run: ${resolveInstallHint()} zod`);
72
+ await runHeyApiGeneration(coreConfig, configPath, plugins, logger);
73
+ await updateOpenApiTsCache(absoluteOpenapiPath, configPath, cacheOptions);
74
+ generated = true;
75
+ } catch (error) {
76
+ const message = error instanceof Error ? error.message : String(error);
77
+ if (message.includes("not found") || message.includes("ENOENT")) {
78
+ logger.warn("@hey-api/openapi-ts not installed");
79
+ } else {
80
+ logger.error(`Type generation failed: ${message}`);
81
+ }
68
82
  }
83
+ } else {
84
+ resolvedConfig?.logger.info(`${colors.cyan("\u2022")} TypeScript types ${colors.dim("(unchanged)")}`);
69
85
  }
70
- await execAsync(resolvePackageExecutor(args.join(" "), executor), { cwd: projectRoot });
71
- generated = true;
72
86
  }
73
- if (typesConfig.generatePageProps && fs.existsSync(pagePropsPath)) {
74
- await emitPagePropsTypes(pagePropsPath, typesConfig.output);
75
- generated = true;
87
+ if (typesConfig.generatePageProps && fs.existsSync(absolutePagePropsPath)) {
88
+ try {
89
+ const changed = await emitPagePropsTypes(absolutePagePropsPath, typesConfig.output);
90
+ if (changed) {
91
+ generated = true;
92
+ } else {
93
+ resolvedConfig?.logger.info(`${colors.cyan("\u2022")} Page props types ${colors.dim("(unchanged)")}`);
94
+ }
95
+ } catch (error) {
96
+ const message = error instanceof Error ? error.message : String(error);
97
+ logger.error(`Page props generation failed: ${message}`);
98
+ }
76
99
  }
77
100
  if (generated && resolvedConfig) {
78
101
  const duration = Date.now() - startTime;
@@ -91,22 +114,13 @@ function createLitestarTypeGenPlugin(typesConfig, options) {
91
114
  return true;
92
115
  } catch (error) {
93
116
  const message = error instanceof Error ? error.message : String(error);
94
- if (resolvedConfig) {
95
- if (message.includes("not found") || message.includes("ENOENT")) {
96
- const zodHint = typesConfig.generateZod ? " zod" : "";
97
- resolvedConfig.logger.warn(
98
- `${colors.cyan("litestar-vite")} ${colors.yellow("@hey-api/openapi-ts not installed")} - run: ${resolveInstallHint()} -D @hey-api/openapi-ts${zodHint}`
99
- );
100
- } else {
101
- resolvedConfig.logger.error(`${colors.cyan("litestar-vite")} ${colors.red("type generation failed:")} ${message}`);
102
- }
103
- }
117
+ resolvedConfig?.logger.error(`${colors.cyan("litestar-vite")} ${colors.red("type generation failed:")} ${message}`);
104
118
  return false;
105
119
  } finally {
106
120
  isGenerating = false;
107
121
  }
108
122
  }
109
- const debouncedRunTypeGeneration = debounce(runTypeGeneration, typesConfig.debounce);
123
+ const debouncedRunTypeGeneration = debounce(runTypeGenerationWithCache, typesConfig.debounce);
110
124
  return {
111
125
  name: pluginName,
112
126
  enforce: "pre",
@@ -144,7 +158,7 @@ Solutions:
144
158
  const hasOpenapi = fs.existsSync(openapiPath);
145
159
  const hasPageProps = typesConfig.generatePageProps && fs.existsSync(pagePropsPath);
146
160
  if (hasOpenapi || hasPageProps) {
147
- await runTypeGeneration();
161
+ await runTypeGenerationWithCache();
148
162
  }
149
163
  }
150
164
  },
@@ -0,0 +1,16 @@
1
+ export interface WriteResult {
2
+ changed: boolean;
3
+ path: string;
4
+ }
5
+ /**
6
+ * Write file only if content differs from existing.
7
+ * Uses direct Buffer comparison (more efficient than hashing for small files).
8
+ *
9
+ * @param filePath - Absolute path to file
10
+ * @param content - Content to write
11
+ * @param options - Write options
12
+ * @returns WriteResult indicating whether file was changed
13
+ */
14
+ export declare function writeIfChanged(filePath: string, content: string, options?: {
15
+ encoding?: BufferEncoding;
16
+ }): Promise<WriteResult>;
@@ -0,0 +1,19 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ async function writeIfChanged(filePath, content, options) {
4
+ const encoding = options?.encoding ?? "utf-8";
5
+ const newBuffer = Buffer.from(content, encoding);
6
+ try {
7
+ const existing = await fs.promises.readFile(filePath);
8
+ if (existing.equals(newBuffer)) {
9
+ return { changed: false, path: filePath };
10
+ }
11
+ } catch {
12
+ }
13
+ await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
14
+ await fs.promises.writeFile(filePath, newBuffer);
15
+ return { changed: true, path: filePath };
16
+ }
17
+ export {
18
+ writeIfChanged
19
+ };
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+ import colors from "picocolors";
3
+ import { readBridgeConfig } from "./shared/bridge-schema.js";
4
+ import { runTypeGeneration } from "./shared/typegen-core.js";
5
+ function parseArgs() {
6
+ const args = process.argv.slice(2);
7
+ return {
8
+ verbose: args.includes("--verbose")
9
+ };
10
+ }
11
+ function log(message) {
12
+ console.log(message);
13
+ }
14
+ function logError(message) {
15
+ console.error(message);
16
+ }
17
+ function createConsoleLogger() {
18
+ return {
19
+ info: (message) => log(`${colors.cyan("\u2022")} ${message}`),
20
+ warn: (message) => log(`${colors.yellow("!")} ${message}`),
21
+ error: (message) => logError(`${colors.red("\u2717")} ${message}`)
22
+ };
23
+ }
24
+ async function main() {
25
+ parseArgs();
26
+ const projectRoot = process.cwd();
27
+ const bridgeConfig = readBridgeConfig();
28
+ if (!bridgeConfig) {
29
+ logError(`${colors.red("\u2717")} .litestar.json not found`);
30
+ logError(" Run 'litestar run' or 'litestar assets generate-types' first to generate it.");
31
+ process.exit(1);
32
+ }
33
+ const typesConfig = bridgeConfig.types;
34
+ if (!typesConfig || !typesConfig.enabled) {
35
+ log(`${colors.yellow("!")} Type generation is disabled in configuration`);
36
+ process.exit(0);
37
+ }
38
+ const coreConfig = {
39
+ projectRoot,
40
+ openapiPath: typesConfig.openapiPath,
41
+ output: typesConfig.output,
42
+ pagePropsPath: typesConfig.pagePropsPath,
43
+ generateSdk: typesConfig.generateSdk,
44
+ generateZod: typesConfig.generateZod,
45
+ generatePageProps: typesConfig.generatePageProps,
46
+ sdkClientPlugin: "@hey-api/client-fetch",
47
+ // Default for CLI
48
+ executor: bridgeConfig.executor
49
+ };
50
+ const logger = createConsoleLogger();
51
+ const result = await runTypeGeneration(coreConfig, { logger });
52
+ if (result.errors.length > 0) {
53
+ process.exit(1);
54
+ }
55
+ for (const file of result.skippedFiles) {
56
+ const name = file.includes("page-props") ? "Page props types" : file;
57
+ log(`${colors.cyan("\u2022")} ${name} ${colors.dim("(unchanged)")}`);
58
+ }
59
+ if (result.generated) {
60
+ log(`${colors.green("\u2713")} TypeScript artifacts updated ${colors.dim(`(${result.durationMs}ms)`)}`);
61
+ }
62
+ }
63
+ main().catch((error) => {
64
+ logError(`${colors.red("\u2717")} Unexpected error: ${error instanceof Error ? error.message : String(error)}`);
65
+ process.exit(1);
66
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "litestar-vite-plugin",
3
- "version": "0.15.0-rc.1",
3
+ "version": "0.15.0-rc.3",
4
4
  "type": "module",
5
5
  "description": "Litestar plugin for Vite.",
6
6
  "keywords": [
@@ -15,6 +15,8 @@
15
15
  },
16
16
  "license": "MIT",
17
17
  "author": "Litestar",
18
+ "main": "./dist/js/index.js",
19
+ "module": "./dist/js/index.js",
18
20
  "exports": {
19
21
  ".": {
20
22
  "types": "./dist/js/index.d.ts",
@@ -39,24 +41,51 @@
39
41
  "./nuxt": {
40
42
  "types": "./dist/js/nuxt.d.ts",
41
43
  "import": "./dist/js/nuxt.js"
44
+ },
45
+ "./inertia": {
46
+ "types": "./dist/js/inertia-types.d.ts",
47
+ "import": "./dist/js/inertia-types.js"
42
48
  }
43
49
  },
44
50
  "types": "./dist/js/index.d.ts",
51
+ "typesVersions": {
52
+ "*": {
53
+ "helpers": [
54
+ "./dist/js/helpers/index.d.ts"
55
+ ],
56
+ "inertia-helpers": [
57
+ "./dist/js/inertia-helpers/index.d.ts"
58
+ ],
59
+ "astro": [
60
+ "./dist/js/astro.d.ts"
61
+ ],
62
+ "sveltekit": [
63
+ "./dist/js/sveltekit.d.ts"
64
+ ],
65
+ "nuxt": [
66
+ "./dist/js/nuxt.d.ts"
67
+ ],
68
+ "inertia": [
69
+ "./dist/js/inertia-types.d.ts"
70
+ ]
71
+ }
72
+ },
45
73
  "files": [
46
74
  "dist/js/**/*",
47
75
  "tools/clean.js"
48
76
  ],
49
77
  "bin": {
50
- "clean-orphaned-assets": "tools/clean.js"
78
+ "clean-orphaned-assets": "tools/clean.js",
79
+ "litestar-vite-typegen": "dist/js/typegen-cli.js"
51
80
  },
52
81
  "scripts": {
53
82
  "build": "npm run build-plugin && npm run build-helpers && npm run build-inertia-helpers && npm run build-integrations",
54
83
  "build-plugin": "rm -rf dist/js && npm run build-plugin-types && npm run build-plugin-esm && cp src/js/src/dev-server-index.html dist/js/",
55
84
  "build-plugin-types": "tsc --project src/js/tsconfig.json --emitDeclarationOnly",
56
- "build-plugin-esm": "esbuild src/js/src/index.ts --platform=node --format=esm --outfile=dist/js/index.js && esbuild src/js/src/install-hint.ts --platform=node --format=esm --outfile=dist/js/install-hint.js && esbuild src/js/src/litestar-meta.ts --platform=node --format=esm --outfile=dist/js/litestar-meta.js && mkdir -p dist/js/shared && esbuild src/js/src/shared/bridge-schema.ts src/js/src/shared/debounce.ts src/js/src/shared/format-path.ts src/js/src/shared/logger.ts src/js/src/shared/emit-page-props-types.ts src/js/src/shared/typegen-plugin.ts --platform=node --format=esm --outdir=dist/js/shared",
85
+ "build-plugin-esm": "esbuild src/js/src/index.ts --platform=node --format=esm --outfile=dist/js/index.js && esbuild src/js/src/install-hint.ts --platform=node --format=esm --outfile=dist/js/install-hint.js && esbuild src/js/src/litestar-meta.ts --platform=node --format=esm --outfile=dist/js/litestar-meta.js && esbuild src/js/src/typegen-cli.ts --platform=node --format=esm --outfile=dist/js/typegen-cli.js --banner:js='#!/usr/bin/env node' && mkdir -p dist/js/shared && esbuild src/js/src/shared/bridge-schema.ts src/js/src/shared/debounce.ts src/js/src/shared/format-path.ts src/js/src/shared/logger.ts src/js/src/shared/emit-page-props-types.ts src/js/src/shared/typegen-plugin.ts src/js/src/shared/typegen-core.ts src/js/src/shared/write-if-changed.ts src/js/src/shared/typegen-cache.ts --platform=node --format=esm --outdir=dist/js/shared",
57
86
  "build-helpers": "rm -rf dist/js/helpers && tsc --project src/js/tsconfig.helpers.json",
58
87
  "build-inertia-helpers": "rm -rf dist/js/inertia-helpers && tsc --project src/js/tsconfig.inertia-helpers.json",
59
- "build-integrations": "esbuild src/js/src/astro.ts src/js/src/sveltekit.ts src/js/src/nuxt.ts --platform=node --format=esm --outdir=dist/js",
88
+ "build-integrations": "esbuild src/js/src/astro.ts src/js/src/sveltekit.ts src/js/src/nuxt.ts src/js/src/inertia-types.ts --platform=node --format=esm --outdir=dist/js",
60
89
  "lint": "biome check --vcs-enabled=false src/js/src src/js/tests",
61
90
  "test": "vitest --config ./src/js/vitest.config.ts run"
62
91
  },