@hublo/sentinel 1.2.0-alpha.34 → 1.2.0-alpha.35

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.
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  registerAdapters,
8
8
  resolve,
9
9
  setDefaultRunner
10
- } from "./chunk-XZLF7FBK.js";
10
+ } from "./chunk-GQID2U34.js";
11
11
  export {
12
12
  BaseAdapter,
13
13
  all,
@@ -1,8 +1,9 @@
1
1
  import { UserConfig, Plugin } from 'vite';
2
2
  export { Plugin, PluginOption, UserConfig, UserConfigExport, defineConfig, loadEnv, mergeConfig } from 'vite';
3
+ export { A as Alias, D as DecoratorMetadataOptions, d as decoratorMetadata, t as tsconfigAliases } from '../../../tsconfig-aliases-Ce6axdJ4.js';
3
4
 
4
5
  /** A transformer as the target declares it, passed through to the config unchanged. */
5
- interface TransformerDeclaration$1 {
6
+ interface TransformerDeclaration {
6
7
  name: string;
7
8
  options?: Record<string, unknown>;
8
9
  }
@@ -41,7 +42,7 @@ interface NestServiceOptions {
41
42
  * without it succeeds and silently costs them most of their published contract (measured on
42
43
  * `institution`: 385 insertions, 1378 deletions in the committed OpenAPI document).
43
44
  */
44
- transformers?: readonly TransformerDeclaration$1[];
45
+ transformers?: readonly TransformerDeclaration[];
45
46
  /**
46
47
  * Files copied into the output beside the bundle, in the shape `@nx/webpack` declared them.
47
48
  *
@@ -60,27 +61,6 @@ interface NestServiceOptions {
60
61
  }
61
62
  declare const nestService: (options: NestServiceOptions) => UserConfig;
62
63
 
63
- /** A TypeScript transformer a service runs at build time, as its build target declares it. */
64
- interface TransformerDeclaration {
65
- /** The package to load it from, e.g. `@nestjs/swagger/plugin`. */
66
- name: string;
67
- /** Passed to the transformer's own factory, unread here. */
68
- options?: Record<string, unknown>;
69
- }
70
- interface DecoratorMetadataOptions {
71
- /** The module being built. Its tsconfig is the one whose emit must be preserved. */
72
- root: string;
73
- /**
74
- * An explicit tsconfig, when the service does not use either conventional name.
75
- *
76
- * Relative paths resolve against `root`.
77
- */
78
- tsconfig?: string;
79
- /** The transformers this service declared, translated from its webpack target. */
80
- transformers?: readonly TransformerDeclaration[];
81
- }
82
- declare const decoratorMetadata: (options: DecoratorMetadataOptions) => Plugin;
83
-
84
64
  interface NodeManifestOptions {
85
65
  /** The nx project name, which is how the graph is keyed. */
86
66
  project: string;
@@ -115,16 +95,4 @@ interface NodeManifestOptions {
115
95
  */
116
96
  declare const nodeManifest: (options: NodeManifestOptions) => Plugin;
117
97
 
118
- interface Alias {
119
- find: RegExp;
120
- replacement: string;
121
- }
122
- /**
123
- * Read the mappings from the workspace's base tsconfig.
124
- *
125
- * Longest pattern first, because Vite takes the first alias that matches and the mappings
126
- * overlap by design: `@front/theme/node` must not be swallowed by `@front/theme`.
127
- */
128
- declare const tsconfigAliases: (workspaceRoot: string, file?: string) => Alias[];
129
-
130
- export { type Alias, type DecoratorMetadataOptions, type NestServiceOptions, type NodeManifestOptions, decoratorMetadata, nestService, nodeManifest, tsconfigAliases };
98
+ export { type NestServiceOptions, type NodeManifestOptions, nestService, nodeManifest };
@@ -1,5 +1,10 @@
1
+ import {
2
+ decoratorMetadata,
3
+ tsconfigAliases
4
+ } from "../../../chunk-3TDUIKVQ.js";
5
+
1
6
  // src/roles/build/nest/nest-service.ts
2
- import { join as join4 } from "path";
7
+ import { join as join3 } from "path";
3
8
  import { mergeConfig } from "vite";
4
9
 
5
10
  // src/roles/build/nest/copy-assets.ts
@@ -76,161 +81,8 @@ var copyAssets = (options) => ({
76
81
  }
77
82
  });
78
83
 
79
- // src/roles/build/nest/decorator-metadata.ts
80
- import { existsSync as existsSync2 } from "fs";
81
- import { createRequire } from "module";
82
- import path from "path";
83
- var TYPESCRIPT_SOURCE = /\.ts$/;
84
- var TSCONFIG_CANDIDATES = ["tsconfig.app.json", "tsconfig.json"];
85
- function loadCompiler(root) {
86
- const require2 = createRequire(path.join(root, "noop.js"));
87
- let compiler;
88
- try {
89
- compiler = require2("typescript");
90
- } catch {
91
- throw new Error(
92
- `sentinel build(nest): no \`typescript\` resolvable from ${root}. The build emits decorator metadata with the module's own compiler, so one has to be installed there.`
93
- );
94
- }
95
- if (typeof compiler.createProgram !== "function") {
96
- throw new Error(
97
- `sentinel build(nest): the typescript resolved from ${root} (${compiler.version ?? "unknown"}) has no createProgram API. TypeScript 7 is the native port and exposes none; the emit API lives under @typescript/typescript6. Point this module's \`typescript\` at a compiler with an emit API, which is the one its \`typecheck\` target already uses.`
98
- );
99
- }
100
- return compiler;
101
- }
102
- function resolveTsconfig(options) {
103
- if (options.tsconfig) {
104
- const explicit = path.resolve(options.root, options.tsconfig);
105
- if (!existsSync2(explicit)) {
106
- throw new Error(`sentinel build(nest): tsconfig not found at ${explicit}`);
107
- }
108
- return explicit;
109
- }
110
- for (const candidate of TSCONFIG_CANDIDATES) {
111
- const found = path.join(options.root, candidate);
112
- if (existsSync2(found)) return found;
113
- }
114
- throw new Error(
115
- `sentinel build(nest): no ${TSCONFIG_CANDIDATES.join(" or ")} in ${options.root}. Pass \`tsconfig\` if this service keeps it elsewhere.`
116
- );
117
- }
118
- function readConfig(ts, options) {
119
- const configPath = resolveTsconfig(options);
120
- const parsed = ts.getParsedCommandLineOfConfigFile(configPath, {}, {
121
- ...ts.sys,
122
- onUnRecoverableConfigFileDiagnostic: (diagnostic) => {
123
- throw new Error(
124
- `sentinel build(nest): could not read ${configPath}: ` + ts.flattenDiagnosticMessageText(diagnostic.messageText, " ")
125
- );
126
- }
127
- });
128
- return {
129
- fileNames: parsed?.fileNames ?? [],
130
- compilerOptions: {
131
- ...parsed?.options,
132
- // ESM out, so Rollup sees imports and exports rather than an opaque `require` it cannot
133
- // follow. The service still SHIPS as CJS: that conversion is the bundler's, further down.
134
- module: ts.ModuleKind.ESNext,
135
- // Vite consumes the map; the tsconfig's own answer is about a different pipeline.
136
- sourceMap: true,
137
- inlineSourceMap: false,
138
- inlineSources: false,
139
- // Types are another target's job, and emitting them here would write into the source tree.
140
- declaration: false,
141
- declarationMap: false,
142
- emitDeclarationOnly: false,
143
- noEmit: false,
144
- // `composite` projects refuse to emit without a `tsBuildInfoFile`, and there is no
145
- // incremental build here to inform.
146
- composite: false,
147
- incremental: false
148
- }
149
- };
150
- }
151
- function loadTransformers(root, program, declared) {
152
- const require2 = createRequire(path.join(root, "noop.js"));
153
- return declared.map(({ name, options }) => {
154
- let loaded;
155
- try {
156
- loaded = require2(name);
157
- } catch (error) {
158
- throw new Error(
159
- `sentinel build(nest): the build target declares the transformer \`${name}\`, which cannot be loaded from ${root}: ${error instanceof Error ? error.message : String(error)}`,
160
- { cause: error }
161
- );
162
- }
163
- if (typeof loaded.before !== "function") {
164
- throw new Error(
165
- `sentinel build(nest): \`${name}\` exports no \`before\` factory, so it cannot run as a TypeScript transformer. Building without it would drop whatever it contributes.`
166
- );
167
- }
168
- const factory = loaded.before;
169
- return factory(options, program);
170
- });
171
- }
172
- var decoratorMetadata = (options) => {
173
- const typeAware = (options.transformers ?? []).length > 0;
174
- let ts;
175
- let program;
176
- let compilerOptions;
177
- let before = [];
178
- const withoutTypes = [];
179
- return {
180
- name: "sentinel:decorator-metadata",
181
- // Before Vite's own transform, so esbuild never sees the decorators it cannot handle, and so
182
- // the source this plugin reads from the Program is still the source on disk.
183
- enforce: "pre",
184
- buildStart() {
185
- ts = loadCompiler(options.root);
186
- const config = readConfig(ts, options);
187
- compilerOptions = config.compilerOptions;
188
- if (!typeAware) return;
189
- program = ts.createProgram(config.fileNames, config.compilerOptions);
190
- before = loadTransformers(options.root, program, options.transformers ?? []);
191
- },
192
- transform(code, id) {
193
- if (!TYPESCRIPT_SOURCE.test(id) || id.includes("node_modules")) return null;
194
- if (ts === void 0 || compilerOptions === void 0) {
195
- throw new Error(
196
- `sentinel build(nest): the compiler was never prepared, so ${id} would be emitted with defaults rather than this module's tsconfig.`
197
- );
198
- }
199
- const sourceFile = program?.getSourceFile(id);
200
- if (program === void 0 || sourceFile === void 0 || sourceFile.text !== code) {
201
- if (typeAware) withoutTypes.push(id);
202
- const output = ts.transpileModule(code, { fileName: id, compilerOptions });
203
- return { code: output.outputText, map: output.sourceMapText ?? null };
204
- }
205
- let emitted;
206
- let map;
207
- program.emit(
208
- sourceFile,
209
- (fileName, text) => {
210
- if (fileName.endsWith(".map")) map = text;
211
- else emitted = text;
212
- },
213
- void 0,
214
- false,
215
- { before }
216
- );
217
- if (emitted === void 0) {
218
- throw new Error(`sentinel build(nest): TypeScript emitted nothing for ${id}.`);
219
- }
220
- return { code: emitted, map: map ?? null };
221
- },
222
- closeBundle() {
223
- if (withoutTypes.length === 0) return;
224
- const shown = withoutTypes.slice(0, 5).map((file) => path.relative(options.root, file));
225
- this.warn(
226
- `sentinel build(nest): ${withoutTypes.length} file(s) were emitted without type information, so their decorator metadata may differ from what ts-loader produced (${shown.join(", ")}${withoutTypes.length > shown.length ? ", \u2026" : ""}). They are outside this module's tsconfig, or another plugin rewrote them first.`
227
- );
228
- }
229
- };
230
- };
231
-
232
84
  // src/roles/build/nest/node-manifest.ts
233
- import { existsSync as existsSync3, writeFileSync } from "fs";
85
+ import { existsSync as existsSync2, writeFileSync } from "fs";
234
86
  import { isBuiltin } from "module";
235
87
  import { join as join2 } from "path";
236
88
  var DEFAULT_PROVIDED_BY_IMAGE = ["@prisma"];
@@ -260,7 +112,7 @@ var nodeManifest = (options) => {
260
112
  },
261
113
  async closeBundle() {
262
114
  const entry = options.entry ?? "main.js";
263
- if (!existsSync3(join2(options.outDir, entry))) return;
115
+ if (!existsSync2(join2(options.outDir, entry))) return;
264
116
  const { createProjectGraphAsync } = await import("nx/src/devkit-exports.js");
265
117
  const { createPackageJson, createLockFile, getLockFileName } = await import("@nx/js");
266
118
  const graph = await createProjectGraphAsync({ exitOnError: false });
@@ -294,30 +146,10 @@ var assertManifestCovers = (required, manifest, options) => {
294
146
  );
295
147
  };
296
148
 
297
- // src/roles/build/nest/tsconfig-aliases.ts
298
- import { readFileSync } from "fs";
299
- import { join as join3 } from "path";
300
- var stripLineComments = (json) => json.replace(/^\s*\/\/.*$/gm, "");
301
- var toAlias = (workspaceRoot, pattern, target) => {
302
- const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/\\\*/g, "(.*)");
303
- return {
304
- find: new RegExp(`^${escaped}$`),
305
- replacement: join3(workspaceRoot, target.replace(/\*/g, "$1"))
306
- };
307
- };
308
- var tsconfigAliases = (workspaceRoot, file = "tsconfig.base.json") => {
309
- const raw = readFileSync(join3(workspaceRoot, file), "utf8");
310
- const { compilerOptions } = JSON.parse(stripLineComments(raw));
311
- return Object.entries(compilerOptions?.paths ?? {}).flatMap(([pattern, targets]) => {
312
- const [target] = targets;
313
- return target === void 0 ? [] : [toAlias(workspaceRoot, pattern, target)];
314
- }).sort((a, b) => b.find.source.length - a.find.source.length);
315
- };
316
-
317
149
  // src/roles/build/nest/nest-service.ts
318
150
  var baseConfig = (options) => {
319
- const outDir = join4(options.workspaceRoot, options.outDir ?? `dist/${options.project}`);
320
- const entry = join4(options.root, options.entry ?? "src/main.ts");
151
+ const outDir = join3(options.workspaceRoot, options.outDir ?? `dist/${options.project}`);
152
+ const entry = join3(options.root, options.entry ?? "src/main.ts");
321
153
  return {
322
154
  plugins: [
323
155
  decoratorMetadata({
@@ -0,0 +1,17 @@
1
+ import { ViteUserConfig } from 'vitest/config';
2
+ export { ConfigEnv, TestUserConfig, ViteUserConfig, defineConfig, mergeConfig } from 'vitest/config';
3
+ export { loadEnv } from 'vite';
4
+ export { A as Alias, D as DecoratorMetadataOptions, d as decoratorMetadata, t as tsconfigAliases } from '../../../tsconfig-aliases-Ce6axdJ4.js';
5
+
6
+ interface NestTestOptions {
7
+ /** The module's own directory: where its specs live and what its config is relative to. */
8
+ root: string;
9
+ /** The workspace root, which is where `tsconfig.base.json` and its path aliases are. */
10
+ workspaceRoot: string;
11
+ /** Merged over the base. For what a module genuinely needs to differ on, nothing else. */
12
+ overrides?: ViteUserConfig;
13
+ }
14
+ /** The config, with the module's own overrides merged over it. */
15
+ declare function nestTestConfig(options: NestTestOptions): ViteUserConfig;
16
+
17
+ export { type NestTestOptions, nestTestConfig };
@@ -0,0 +1,103 @@
1
+ import {
2
+ decoratorMetadata,
3
+ tsconfigAliases
4
+ } from "../../../chunk-3TDUIKVQ.js";
5
+
6
+ // src/roles/test/nest/toolchain.ts
7
+ import { defineConfig, mergeConfig } from "vitest/config";
8
+ import { loadEnv } from "vite";
9
+
10
+ // src/roles/test/nest/nest-test-config.ts
11
+ import { existsSync } from "fs";
12
+ import { dirname, join } from "path";
13
+ function axiosAlias(root) {
14
+ const manifest = join(root, "node_modules", "axios", "package.json");
15
+ return existsSync(manifest) ? [{ find: /^axios$/, replacement: dirname(manifest) }] : [];
16
+ }
17
+ function baseConfig(options) {
18
+ const { root, workspaceRoot } = options;
19
+ return {
20
+ /*
21
+ * Nest reads constructor parameter types from `emitDecoratorMetadata`, and esbuild does not
22
+ * emit it. Without this plugin every injected dependency arrives `undefined`, which surfaces
23
+ * far from its cause. It is the BUILD role's plugin, imported rather than reimplemented: it
24
+ * already carries the two compile modes copied from nx, and a second copy here would be a
25
+ * second thing to keep in step.
26
+ */
27
+ plugins: [decoratorMetadata({ root })],
28
+ resolve: {
29
+ alias: [
30
+ ...axiosAlias(root),
31
+ /*
32
+ * `jest-mock-extended` loads `@jest/globals`, which refuses to run outside jest. 2491
33
+ * files import it, 104 of them under `libs/` as SHARED helpers, so migrating those helpers
34
+ * breaks every module still on jest and leaving them breaks every module moved to Vitest.
35
+ * Old and new therefore do not cohabit on shared helpers, which would have killed the
36
+ * per-module plan.
37
+ *
38
+ * This one line removes the constraint: an unmigrated helper resolves to the Vitest fork
39
+ * inside an adopted module and keeps resolving to the jest one everywhere else.
40
+ * `vitest-mock-extended@5.1.1` is a fork of the same package and exports the same names.
41
+ *
42
+ * Measured on `mission`: failing suites went from 159 to 10.
43
+ */
44
+ { find: /^jest-mock-extended$/, replacement: "vitest-mock-extended" },
45
+ ...tsconfigAliases(workspaceRoot)
46
+ ]
47
+ },
48
+ test: {
49
+ globals: true,
50
+ environment: "node",
51
+ root,
52
+ /*
53
+ * Both extensions, because jest ran both. `**\/*.spec.ts` alone read green while missing
54
+ * three `.test.ts` files and 23 tests, with nothing saying so: a counter does not compare to
55
+ * itself, it compares to the reference.
56
+ */
57
+ include: ["**/*.spec.ts", "**/*.test.ts"],
58
+ /*
59
+ * Kept, and it is not a performance knob. Nest registers metadata as an import SIDE EFFECT:
60
+ * a decorator writes into a catalog when its module loads. Sharing a module registry across
61
+ * files lets one suite see what another registered, and the failure appears in whichever
62
+ * file happens to run second.
63
+ */
64
+ isolate: true
65
+ }
66
+ };
67
+ }
68
+ function asAliasArray(alias) {
69
+ if (alias === void 0) return [];
70
+ if (Array.isArray(alias)) return alias;
71
+ return Object.entries(alias).map(([find, replacement]) => ({
72
+ find,
73
+ replacement
74
+ }));
75
+ }
76
+ function nestTestConfig(options) {
77
+ const base = baseConfig(options);
78
+ if (options.overrides === void 0) return base;
79
+ return {
80
+ ...base,
81
+ ...options.overrides,
82
+ plugins: [...base.plugins ?? [], ...options.overrides.plugins ?? []],
83
+ resolve: {
84
+ ...base.resolve,
85
+ ...options.overrides.resolve,
86
+ // The module's own aliases come AFTER sentinel's, so a module that needs to win can.
87
+ alias: [
88
+ ...asAliasArray(base.resolve?.alias),
89
+ ...asAliasArray(options.overrides.resolve?.alias)
90
+ ]
91
+ },
92
+ test: { ...base.test, ...options.overrides.test }
93
+ };
94
+ }
95
+ export {
96
+ decoratorMetadata,
97
+ defineConfig,
98
+ loadEnv,
99
+ mergeConfig,
100
+ nestTestConfig,
101
+ tsconfigAliases
102
+ };
103
+ //# sourceMappingURL=toolchain.js.map
@@ -0,0 +1,15 @@
1
+ import * as msw_node from 'msw/node';
2
+
3
+ /**
4
+ * No default handlers, deliberately.
5
+ *
6
+ * The repo's own server had `defaultHandlers = []` too, so nothing is lost. A handler here would
7
+ * be a Hublo endpoint inside a package meant to be usable by anyone, which is the line between
8
+ * what sentinel owns (the config) and what an app owns (what it mocks).
9
+ *
10
+ * A suite adds its own with `server.use(...)`, which is how all 600 files that touch this already
11
+ * work.
12
+ */
13
+ declare const server: msw_node.SetupServer;
14
+
15
+ export { server };
@@ -0,0 +1,50 @@
1
+ // src/roles/test/setup/nest.ts
2
+ import { afterAll, afterEach, beforeAll } from "vitest";
3
+
4
+ // src/roles/test/setup/msw.ts
5
+ import { setupServer } from "msw/node";
6
+ var server = setupServer();
7
+ function installMswLifecycle(hooks) {
8
+ hooks.beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
9
+ hooks.afterAll(() => server.close());
10
+ }
11
+
12
+ // src/roles/test/setup/workspace.ts
13
+ function isMissingModule(error) {
14
+ return error?.code === "ERR_MODULE_NOT_FOUND";
15
+ }
16
+ async function optional(specifier, use) {
17
+ try {
18
+ use(await import(
19
+ /* @vite-ignore */
20
+ specifier
21
+ ));
22
+ } catch (error) {
23
+ if (!isMissingModule(error)) throw error;
24
+ }
25
+ }
26
+ async function loadEnvironment() {
27
+ await optional("dotenv-flow", (module) => {
28
+ const flow = module;
29
+ const config = flow.config ?? flow.default?.config;
30
+ config?.({ silent: true, purge_dotenv: true });
31
+ });
32
+ }
33
+ async function pinLuxonZone() {
34
+ await optional("luxon", (module) => {
35
+ const settings = module.Settings;
36
+ if (settings) settings.defaultZone = "utc";
37
+ });
38
+ }
39
+ async function installWorkspaceSetup() {
40
+ await loadEnvironment();
41
+ await pinLuxonZone();
42
+ }
43
+
44
+ // src/roles/test/setup/nest.ts
45
+ installMswLifecycle({ beforeAll, afterEach, afterAll });
46
+ await installWorkspaceSetup();
47
+ export {
48
+ server
49
+ };
50
+ //# sourceMappingURL=nest.js.map
@@ -0,0 +1,36 @@
1
+ import { Plugin } from 'vite';
2
+
3
+ /** A TypeScript transformer a service runs at build time, as its build target declares it. */
4
+ interface TransformerDeclaration {
5
+ /** The package to load it from, e.g. `@nestjs/swagger/plugin`. */
6
+ name: string;
7
+ /** Passed to the transformer's own factory, unread here. */
8
+ options?: Record<string, unknown>;
9
+ }
10
+ interface DecoratorMetadataOptions {
11
+ /** The module being built. Its tsconfig is the one whose emit must be preserved. */
12
+ root: string;
13
+ /**
14
+ * An explicit tsconfig, when the service does not use either conventional name.
15
+ *
16
+ * Relative paths resolve against `root`.
17
+ */
18
+ tsconfig?: string;
19
+ /** The transformers this service declared, translated from its webpack target. */
20
+ transformers?: readonly TransformerDeclaration[];
21
+ }
22
+ declare const decoratorMetadata: (options: DecoratorMetadataOptions) => Plugin;
23
+
24
+ interface Alias {
25
+ find: RegExp;
26
+ replacement: string;
27
+ }
28
+ /**
29
+ * Read the mappings from the workspace's base tsconfig.
30
+ *
31
+ * Longest pattern first, because Vite takes the first alias that matches and the mappings
32
+ * overlap by design: `@front/theme/node` must not be swallowed by `@front/theme`.
33
+ */
34
+ declare const tsconfigAliases: (workspaceRoot: string, file?: string) => Alias[];
35
+
36
+ export { type Alias as A, type DecoratorMetadataOptions as D, decoratorMetadata as d, tsconfigAliases as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hublo/sentinel",
3
- "version": "1.2.0-alpha.34",
3
+ "version": "1.2.0-alpha.35",
4
4
  "description": "One CLI that guards code health across Hublo repos: shared lint/typescript/build/test presets, static & dynamic analysis, and architecture checks.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -49,6 +49,14 @@
49
49
  "./test/react": {
50
50
  "types": "./dist/roles/test/react/toolchain.d.ts",
51
51
  "import": "./dist/roles/test/react/toolchain.js"
52
+ },
53
+ "./test/nest": {
54
+ "types": "./dist/roles/test/nest/toolchain.d.ts",
55
+ "import": "./dist/roles/test/nest/toolchain.js"
56
+ },
57
+ "./test/setup/nest": {
58
+ "types": "./dist/roles/test/setup/nest.d.ts",
59
+ "import": "./dist/roles/test/setup/nest.js"
52
60
  }
53
61
  },
54
62
  "files": [
@@ -62,6 +70,7 @@
62
70
  "@nx/eslint-plugin": "23.0.1",
63
71
  "@tailwindcss/vite": "4.1.18",
64
72
  "@vitejs/plugin-react": "6.0.1",
73
+ "@vitest/coverage-v8": "4.1.4",
65
74
  "commander": "^13.0.0",
66
75
  "eslint-plugin-jest-dom": "5.5.0",
67
76
  "eslint-plugin-no-barrel-files": "1.2.1",
@@ -69,6 +78,7 @@
69
78
  "eslint-plugin-storybook": "10.3.5",
70
79
  "eslint-plugin-styled-components-a11y": "2.2.0",
71
80
  "jsonc-parser": "^3.3.1",
81
+ "msw": "1.3.3",
72
82
  "nitro": "3.0.260415-beta",
73
83
  "oxc-parser": "0.147.0",
74
84
  "oxfmt": "0.63.0",