@liflig/cdk-snapshot 1.0.0 → 1.0.2

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/README.md CHANGED
@@ -2,27 +2,42 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@liflig/cdk-snapshot.svg)](https://www.npmjs.com/package/@liflig/cdk-snapshot)
4
4
  [![ci](https://github.com/capralifecycle/cdk-snapshot/actions/workflows/ci.yml/badge.svg)](https://github.com/capralifecycle/cdk-snapshot/actions/workflows/ci.yml)
5
+ [![node](https://img.shields.io/node/v/@liflig/cdk-snapshot.svg)](https://nodejs.org)
5
6
  [![license](https://img.shields.io/npm/l/@liflig/cdk-snapshot.svg)](LICENSE)
6
7
 
7
- Normalizes synthesized AWS CDK stacks for snapshot testing by stripping values such as asset hashes, bootstrap parameters, Lambda version suffixes and more, in order to produce more stable and usable snapshot files.
8
+ Snapshot testing for AWS CDK stacks. A stack is synthesized to CloudFormation and
9
+ normalized before it is snapshotted. The CDK bootstrap version is always dropped.
10
+ Asset hashes, Lambda version suffixes and CDK Pipelines asset IDs change whenever
11
+ an asset's content does; the [options](#options) mask them, so a snapshot fails only
12
+ when the infrastructure itself changed.
8
13
 
9
- The library provides a generic implementation with a thin adapter per test runner: `node:test`, Bun, Vitest and Jest. Everything is built around one pure function, `cdkTemplate`, which turns a stack into a normalized template object; each adapter wraps that function in whatever the runner's own snapshot assertion looks like. All four write the same snapshot bodies — only the file header differs — so the same `.snap` files stay valid if a project switches runner.
14
+ - One normalization for `node:test`, Bun, Vitest and Jest.
15
+ - All four record the same template. Switching runner means regenerating the snapshots
16
+ once, see [Switching runner](#switching-runner).
17
+ - Replaces `jest-cdk-snapshot` with the same options, defaults and serialization,
18
+ see [Migrating](#migrating-from-jest-cdk-snapshot).
10
19
 
11
20
  ## Install
12
21
 
13
22
  ```sh
14
23
  bun add -d @liflig/cdk-snapshot
24
+ npm install --save-dev @liflig/cdk-snapshot
25
+ pnpm add -D @liflig/cdk-snapshot
15
26
  ```
16
27
 
17
- `aws-cdk-lib` and `constructs` are peer dependencies. The package is ESM; CommonJS test files can `require()` it on Node 22.12 or later.
28
+ The package is ESM. Under Jest that needs configuration, see [Jest](#jest).
18
29
 
19
30
  ## Usage
20
31
 
21
- Import the entry point for your runner. Jest, Vitest and Bun get a `toMatchCdkSnapshot` matcher; `node:test` has no `expect` at the time of writing, so it calls `cdkTemplate` directly.
32
+ Import the entry point for your runner. Jest, Vitest and Bun get a `toMatchCdkSnapshot`
33
+ matcher; `node:test` has no `expect`, so it calls `cdkTemplate` directly.
22
34
 
23
35
  ### node:test
24
36
 
25
- `configureCdkSnapshots()` points `node:test` at `__snapshots__/*.snap` and the shared serializer. Call it once, before any test runs. It replaces the default serializer for _every_ snapshot in the run, not just CDK ones, so snapshots taken elsewhere in the same project will be reformatted.
37
+ `configureCdkSnapshots()` points `node:test` at `__snapshots__/*.snap` and the shared
38
+ serializer. Call it once, before any test runs. It replaces the default serializer for
39
+ _every_ snapshot in the run, not just CDK ones, so snapshots taken elsewhere in the same
40
+ project will be reformatted.
26
41
 
27
42
  ```js
28
43
  import test from "node:test";
@@ -59,6 +74,18 @@ test("my stack", () => {
59
74
  });
60
75
  ```
61
76
 
77
+ `toMatchCdkSnapshot` does not work in concurrent tests: it snapshots through the global
78
+ `expect`, which Vitest cannot attribute to a test running concurrently. There, snapshot
79
+ the template with the test's own `expect` instead, which records the same entry:
80
+
81
+ ```js
82
+ import { cdkTemplate } from "@liflig/cdk-snapshot/vitest";
83
+
84
+ test.concurrent("my stack", ({ expect }) => {
85
+ expect(cdkTemplate(stack, { ignoreAssets: true })).toMatchSnapshot();
86
+ });
87
+ ```
88
+
62
89
  ### Jest
63
90
 
64
91
  ```js
@@ -69,9 +96,23 @@ test("my stack", () => {
69
96
  });
70
97
  ```
71
98
 
72
- The Jest entry point uses the global `expect`, so it throws on import if Jest is configured with `injectGlobals: false`.
99
+ Jest loads this package as ESM, which it does only with its ESM support enabled:
100
+
101
+ ```sh
102
+ NODE_OPTIONS=--experimental-vm-modules jest
103
+ ```
104
+
105
+ A CommonJS test file additionally needs Node 24.9 or later, where Jest can `require()` an
106
+ ESM package. Below that, the test file has to be ESM or go through a transform that
107
+ compiles the package to CommonJS.
108
+
109
+ The Jest entry point uses the global `expect`, so it throws on import if Jest is
110
+ configured with `injectGlobals: false`.
73
111
 
74
- The matcher also accepts `propertyMatchers`, forwarded to the runner's own snapshot assertion for values the normalizations do not cover:
112
+ ### Matcher notes
113
+
114
+ `toMatchCdkSnapshot` also accepts `propertyMatchers`, forwarded to the runner's own
115
+ snapshot assertion for values the normalizations do not cover:
75
116
 
76
117
  ```js
77
118
  expect(stack).toMatchCdkSnapshot({
@@ -79,30 +120,82 @@ expect(stack).toMatchCdkSnapshot({
79
120
  });
80
121
  ```
81
122
 
82
- Every entry point also exports `cdkTemplate(stack, options)`. Reach for it to assert on the template without a snapshot. It leaves the stack untouched, so one stack can be synthesized repeatedly with different options.
123
+ Every entry point also exports `cdkTemplate(stack, options)`. Reach for it to assert on
124
+ the template without a snapshot. It leaves the stack untouched, so one stack can be
125
+ synthesized repeatedly with different options.
83
126
 
84
127
  `toMatchCdkSnapshot` cannot be negated; `.not` throws rather than silently passing.
85
128
 
86
- ## Options
87
-
88
- | Option | Default | Effect |
89
- | ------------------------ | ------------- | ----------------------------------------------------------------------------------- |
90
- | `ignoreAssets` | `false` | Replaces Lambda `Code`, container `Image` and the whole `Parameters` block with `Any<Object>` |
91
- | `ignoreBootstrapVersion` | `true` | Drops the `BootstrapVersion` parameter and its check rule |
92
- | `ignoreCurrentVersion` | `false` | Masks the content hash on Lambda `CurrentVersion` logical IDs |
93
- | `ignoreMetadata` | `false` | Drops template and resource `Metadata` |
94
- | `ignoreTags` | `false` | Drops `Tags` from resource properties |
95
- | `ignorePipelineAssets` | `false` | Masks asset paths and IDs in CDK Pipelines `cdk-assets` commands |
96
- | `subsetResourceTypes` | — | Keeps only resources of these CloudFormation types |
97
- | `subsetResourceKeys` | — | Keeps only resources with these logical IDs |
98
- | `assetPlaceholder` | `anyObject` | Token substituted for asset-derived values |
99
- | `propertyMatchers` | — | Matchers forwarded to the runner's snapshot assertion (matcher only) |
129
+ Under Jest and Vitest, `toMatchCdkSnapshot` counts as one assertion towards
130
+ `expect.assertions()`. Bun counts it as two, since its `expect` exposes no way to
131
+ correct the count.
100
132
 
101
- `subsetResourceTypes` and `subsetResourceKeys` intersect: given both, a resource is kept only if it matches both.
102
-
103
- `ignoreAssets` replaces the entire `Parameters` block rather than the individual asset parameters, matching what jest-cdk-snapshot does.
133
+ ## Options
104
134
 
105
- `anyObject` is exported from the package root. It is an asymmetric matcher that serializes as `Any<Object>` and matches any non-null object. The Bun entry point substitutes `expect.any(Object)` instead, because Bun's serializer only recognizes matchers built by its own `expect`.
135
+ | Option | Type | Default | Effect |
136
+ | --- | --- | --- | --- |
137
+ | `ignoreAssets` | `boolean` | `false` | Replaces every `Code` property, every container definition's `Image` and the whole `Parameters` block with `Any<Object>` |
138
+ | `ignoreBootstrapVersion` | `boolean` | `true` | Drops the `BootstrapVersion` parameter and its check rule |
139
+ | `ignoreCurrentVersion` | `boolean` | `false` | Masks the content hash on Lambda `CurrentVersion` logical IDs and every reference to them |
140
+ | `ignoreMetadata` | `boolean` | `false` | Drops template and resource `Metadata` |
141
+ | `ignoreTags` | `boolean` | `false` | Drops `Tags` from resource properties |
142
+ | `ignorePipelineAssets` | `boolean` | `false` | Masks asset paths, IDs and destination suffixes in CDK Pipelines `cdk-assets` commands |
143
+ | `subsetResourceTypes` | `string[]` | keep all | Keeps only resources of these CloudFormation types |
144
+ | `subsetResourceKeys` | `string[]` | keep all | Keeps only resources with these logical IDs |
145
+ | `assetPlaceholder` | `unknown` | `anyObject` | Token substituted for asset-derived values |
146
+ | `propertyMatchers` | `Record<string, unknown>` | none | Matchers forwarded to the runner's snapshot assertion; matcher only, `cdkTemplate` does not take it |
147
+
148
+ `subsetResourceTypes` and `subsetResourceKeys` intersect: given both, a resource is kept
149
+ only if it matches both.
150
+
151
+ `ignoreAssets` is coarse, matching what jest-cdk-snapshot does:
152
+
153
+ - It replaces the entire `Parameters` block. Under CDK's default synthesizer no parameter
154
+ carries an asset hash, so what disappears is the parameters the stack declares itself.
155
+ - It replaces values that are not assets as well, so a change to inline Lambda code, to a
156
+ `Code.fromBucket` key or to a registry image tag such as `nginx:1.27` does not show.
157
+ - Assets outside Lambda `Code` and container images keep their hash: Lambda layers,
158
+ `BucketDeployment` sources, Step Functions and API Gateway definitions read from files,
159
+ and nested stack templates.
160
+ - A function's `currentVersion` logical ID is a hash over its configuration, code
161
+ included, so a stack that uses it also needs `ignoreCurrentVersion` to stay stable.
162
+ - It does nothing to a template with no `Resources`.
163
+
164
+ `ignoreTags` drops the `Tags` property of each resource. Tags nested deeper stay, such as
165
+ those `Tags.of()` propagates into a launch template's `TagSpecifications`.
166
+
167
+ `ignorePipelineAssets` also drops the 8-character suffix CDK appends to each asset
168
+ destination, since that suffix changes with the asset's content too.
169
+
170
+ `anyObject` is exported from the package root. It is an asymmetric matcher that serializes
171
+ as `Any<Object>` and matches any non-null object. The Bun entry point substitutes
172
+ `expect.any(Object)` instead, because Bun's serializer only recognizes matchers built by
173
+ its own `expect`.
174
+
175
+ ## How it works
176
+
177
+ Everything is built around one pure function, `cdkTemplate`, which turns a stack into a
178
+ normalized template object. Each runner gets a thin adapter that wraps that function in
179
+ whatever the runner's own snapshot assertion looks like, so snapshots keep the naming and
180
+ format that runner already produces.
181
+
182
+ ## Switching runner
183
+
184
+ Regenerate the snapshots with the new runner. The templates they record stay the same; the
185
+ diff is limited to how each runner lays out the file around them:
186
+
187
+ | | Jest | Vitest | node:test | Bun |
188
+ | --- | --- | --- | --- | --- |
189
+ | Header | Jest's, and files without it are rejected | Vitest's | none | Bun's |
190
+ | Name of a test inside `describe` | `suite test 1` | `suite > test 1` | `suite > test 1` | `suite test 1` |
191
+ | Entry order | sorted | sorted | sorted | test order |
192
+ | Single-line snapshot, such as `{}` | inline | inline | on a line of its own | inline |
193
+ | Multi-line string inside a template | starts on its key's line | starts on its key's line | starts on its key's line | string and the comma after it on lines of their own |
194
+
195
+ Entry order does not affect matching, but it makes a regeneration diff look larger than it
196
+ is: two similar snapshots trading places reads as values changing.
197
+
198
+ `test/compat.test.ts` pins every row, so a runner that changes its format fails the build.
106
199
 
107
200
  ## Development
108
201
 
@@ -111,20 +204,48 @@ make build # install, format, typecheck, refresh snapshots, test
111
204
  make ci # what the CI workflow runs: refuses a stale lockfile, fails on an uncommitted snapshot change
112
205
  ```
113
206
 
114
- `make snapshots` regenerates the unit snapshots plus the shared fixture under all four runners, which `test/compat.test.ts` then compares against each other.
207
+ `make snapshots` regenerates the unit snapshots plus the shared fixture under all four
208
+ runners, which `test/compat.test.ts` then compares against each other.
209
+
210
+ `make compat-check` runs only the four runners and fails if their snapshots changed. CI
211
+ runs it on the oldest Node that `engines` in `package.json` allows.
115
212
 
116
213
  ## Migrating from jest-cdk-snapshot
117
214
 
118
- Change the import. Call sites and `.snap` files stay as they are, since the options, their defaults and the serialization all match.
215
+ Change the import. Call sites and `.snap` files stay as they are, since the options, their
216
+ defaults and the serialization all match.
119
217
 
120
218
  ```diff
121
219
  -import "jest-cdk-snapshot"
122
220
  +import "@liflig/cdk-snapshot/jest"
123
221
  ```
124
222
 
125
- Verified against liflig-cdk (64 snapshots) and cdk-cloudfront-auth: every snapshot passes under `jest --ci`, and a forced `--updateSnapshot` rewrites nothing.
223
+ Verified against two public CDK libraries, liflig-cdk and cdk-cloudfront-auth: every
224
+ existing snapshot passes under `jest --ci`, and a forced `--updateSnapshot` rewrites
225
+ nothing.
226
+
227
+ Jest now has to run with ESM support enabled, since this package is ESM — see
228
+ [Jest](#jest).
229
+
230
+ One option is gone. `yaml` is not supported, so a project snapshotting YAML has to
231
+ regenerate as JSON.
232
+
233
+ One option masks more. `ignorePipelineAssets` also drops the content-derived suffix that
234
+ recent CDK versions append to asset destinations, which jest-cdk-snapshot keeps. A
235
+ pipeline snapshot taken with it changes once, from `publish "111111111111-eu-west-1-2d2574cc"`
236
+ to `publish "111111111111-eu-west-1"`, and then stays put when asset content changes.
237
+
238
+ jest-cdk-snapshot's option type also extended `StageSynthesisOptions`, so it accepted
239
+ `skipValidation`, `validateOnSynthesis`, `force`, `errorOnDuplicateSynth` and
240
+ `aspectStabilization` while warning at runtime that they did nothing. Here the type
241
+ checker rejects them; delete them.
242
+
243
+ ## Releases
126
244
 
127
- Two of its options are gone. `yaml` is not supported, so a project snapshotting YAML has to regenerate as JSON. The no-op synthesis options it inherited from `StageSynthesisOptions` — `skipValidation`, `validateOnSynthesis`, `force` — are rejected by the type checker instead of warned about at runtime; delete them.
245
+ Released from `master` by [semantic-release](https://semantic-release.gitbook.io/) on
246
+ every merge, so commit messages follow
247
+ [Conventional Commits](https://www.conventionalcommits.org/). The changelog is the
248
+ [GitHub releases page](https://github.com/capralifecycle/cdk-snapshot/releases).
128
249
 
129
250
  ## License
130
251
 
package/lib/matcher.d.ts CHANGED
@@ -6,6 +6,13 @@ export interface ExpectLike {
6
6
  toMatchSnapshot(propertyMatchers?: Record<string, unknown>): void;
7
7
  };
8
8
  extend(matchers: Record<string, unknown>): void;
9
+ /** Jest and Vitest track assertion calls here; Bun has neither method. */
10
+ getState?(): {
11
+ assertionCalls: number;
12
+ };
13
+ setState?(state: {
14
+ assertionCalls: number;
15
+ }): void;
9
16
  }
10
17
  export type TemplateFn = (stack: Stack, options?: CdkTemplateOptions) => Record<string, unknown>;
11
18
  /**
package/lib/matcher.js CHANGED
@@ -11,6 +11,7 @@ export function registerCdkMatcher(expect, cdkTemplate) {
11
11
  throw new Error("toMatchCdkSnapshot cannot be negated with `.not`.");
12
12
  }
13
13
  const { propertyMatchers, ...templateOptions } = options;
14
+ const assertionCalls = expect.getState?.().assertionCalls;
14
15
  const assertion = expect(cdkTemplate(received, templateOptions));
15
16
  if (propertyMatchers) {
16
17
  assertion.toMatchSnapshot(propertyMatchers);
@@ -18,6 +19,9 @@ export function registerCdkMatcher(expect, cdkTemplate) {
18
19
  else {
19
20
  assertion.toMatchSnapshot();
20
21
  }
22
+ // The nested snapshot assertion must not count towards `expect.assertions()`.
23
+ if (assertionCalls !== undefined)
24
+ expect.setState?.({ assertionCalls });
21
25
  return { pass: true, message: () => "" };
22
26
  },
23
27
  });
package/lib/node.d.ts CHANGED
@@ -2,7 +2,7 @@ export { cdkTemplate } from "./index.js";
2
2
  export type { CdkSnapshotOptions, CdkTemplateOptions } from "./options.js";
3
3
  /**
4
4
  * Aligns `node:test` with the snapshot location and serialization the other
5
- * runners use, so one set of snapshot files serves all of them.
5
+ * runners use, so its snapshots record templates the same way theirs do.
6
6
  *
7
7
  * Call once, before any test runs.
8
8
  */
package/lib/node.js CHANGED
@@ -4,7 +4,7 @@ import { serialize } from "./serialize.js";
4
4
  export { cdkTemplate } from "./index.js";
5
5
  /**
6
6
  * Aligns `node:test` with the snapshot location and serialization the other
7
- * runners use, so one set of snapshot files serves all of them.
7
+ * runners use, so its snapshots record templates the same way theirs do.
8
8
  *
9
9
  * Call once, before any test runs.
10
10
  */
package/lib/normalize.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { anyObject } from "./placeholder.js";
2
2
  const currentVersionRegex = /^(.+CurrentVersion[0-9A-F]{8})[0-9a-f]{32}$/;
3
3
  const pipelineCdkAssetsRegex = /cdk-assets\s+--path\s+\\"([^\\/]+)\/.+?assets\.json\\"\s+--verbose\s+publish\s+\\"(.+?)\\"/g;
4
- const assetDestinationRegex = /:(.*)$/;
4
+ const assetDestinationRegex = /:(.*?)(?:-[0-9a-f]{8})?$/;
5
5
  const maskedVersionSuffix = "x".repeat(32);
6
6
  /**
7
7
  * Returns a copy of `template` with the configured normalizations applied. The
@@ -73,8 +73,9 @@ function maskCurrentVersions(tree) {
73
73
  });
74
74
  }
75
75
  /**
76
- * `cdk-assets ... publish "<hash>:<destination>"` — the hash changes on every
77
- * synth, the destination does not.
76
+ * `cdk-assets ... publish "<hash>:<account>-<region>-<suffix>"` — the hash and
77
+ * the 8-hex suffix follow the asset's content, the account and region do not.
78
+ * CDK versions before the suffix emit `<hash>:<account>-<region>`.
78
79
  */
79
80
  function maskPipelineAssets(tree) {
80
81
  transformStrings(tree, (value) => value.replace(pipelineCdkAssetsRegex, (_match, assemblyDir, asset) => {
package/lib/options.d.ts CHANGED
@@ -1,7 +1,13 @@
1
1
  export interface CdkTemplateOptions {
2
2
  /**
3
- * Replace asset-derived values — Lambda `Code`, container `Image`, and the
4
- * template parameters carrying asset hashes with {@link anyObject}.
3
+ * Replace every resource's `Code` property, every container definition's
4
+ * `Image`, and the whole `Parameters` block with
5
+ * {@link CdkTemplateOptions.assetPlaceholder}.
6
+ *
7
+ * Assets elsewhere, such as Lambda layers, keep their hash. A function using
8
+ * `currentVersion` also needs
9
+ * {@link CdkTemplateOptions.ignoreCurrentVersion}, since the version's
10
+ * logical ID hashes the code.
5
11
  */
6
12
  ignoreAssets?: boolean;
7
13
  /**
@@ -13,9 +19,15 @@ export interface CdkTemplateOptions {
13
19
  ignoreCurrentVersion?: boolean;
14
20
  /** Drop template and resource `Metadata`. */
15
21
  ignoreMetadata?: boolean;
16
- /** Drop `Tags` from resource properties. */
22
+ /**
23
+ * Drop each resource's `Tags` property. Tags nested deeper, such as a launch
24
+ * template's `TagSpecifications`, are kept.
25
+ */
17
26
  ignoreTags?: boolean;
18
- /** Mask asset paths and IDs inside CDK Pipelines `cdk-assets` commands. */
27
+ /**
28
+ * Mask asset paths, IDs and destination suffixes inside CDK Pipelines
29
+ * `cdk-assets` commands.
30
+ */
19
31
  ignorePipelineAssets?: boolean;
20
32
  /** Keep only resources of these CloudFormation types. */
21
33
  subsetResourceTypes?: string[];
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Stand-in for values that change on every synth, such as asset hashes.
2
+ * Stand-in for asset-derived values, which change whenever an asset's content
3
+ * does.
3
4
  * Serializes as `Any<Object>` so snapshots match across test runners.
4
5
  *
5
6
  * Bun accepts only matchers built by its own `expect`; the Bun entry point
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Stand-in for values that change on every synth, such as asset hashes.
2
+ * Stand-in for asset-derived values, which change whenever an asset's content
3
+ * does.
3
4
  * Serializes as `Any<Object>` so snapshots match across test runners.
4
5
  *
5
6
  * Bun accepts only matchers built by its own `expect`; the Bun entry point
@@ -1,5 +1,6 @@
1
1
  /**
2
- * Serializes a value the way Jest, Vitest and Bun serialize snapshots.
2
+ * Serializes a value the way Jest and Vitest serialize snapshots. Bun matches
3
+ * too, except for multi-line strings nested inside the value.
3
4
  *
4
5
  * `node:test` formats with `JSON.stringify` by default, which would make its
5
6
  * snapshots incompatible with the other runners.
package/lib/serialize.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { format, plugins } from "pretty-format";
2
2
  /**
3
- * Serializes a value the way Jest, Vitest and Bun serialize snapshots.
3
+ * Serializes a value the way Jest and Vitest serialize snapshots. Bun matches
4
+ * too, except for multi-line strings nested inside the value.
4
5
  *
5
6
  * `node:test` formats with `JSON.stringify` by default, which would make its
6
7
  * snapshots incompatible with the other runners.
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@liflig/cdk-snapshot",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Normalizes synthesized AWS CDK stacks for snapshot testing",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "author": "Liflig",
8
8
  "engines": {
9
- "node": ">=22.12.0"
9
+ "node": ">=22.13.0"
10
10
  },
11
11
  "repository": {
12
12
  "type": "git",
@@ -84,13 +84,13 @@
84
84
  "pretty-format": "^29.7.0"
85
85
  },
86
86
  "devDependencies": {
87
- "@biomejs/biome": "2.5.10",
87
+ "@biomejs/biome": "2.5.11",
88
88
  "@types/bun": "1.4.0",
89
89
  "@types/jest": "30.0.0",
90
90
  "@types/node": "24.13.3",
91
- "aws-cdk-lib": "2.266.0",
91
+ "aws-cdk-lib": "2.267.0",
92
92
  "constructs": "10.8.1",
93
- "jest": "30.4.2",
93
+ "jest": "30.5.0",
94
94
  "semantic-release": "25.0.9",
95
95
  "typescript": "7.0.2",
96
96
  "vitest": "4.1.11"