npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@liflig/cdk-snapshot

v1.2.0

Published

Normalizes synthesized AWS CDK stacks for snapshot testing

Readme

@liflig/cdk-snapshot

npm ci node license

Snapshot testing for AWS CDK stacks. A stack is synthesized to CloudFormation and normalized before it is snapshotted. The CDK bootstrap version is dropped by default. Asset hashes, Lambda version suffixes and CDK Pipelines asset IDs change whenever an asset's content does; the options mask them, so a snapshot fails only when the infrastructure itself changed.

  • One normalization for node:test, Bun, Vitest and Jest.
  • All four record the same template. Switching runner means regenerating the snapshots once, see Switching runner.
  • Replaces jest-cdk-snapshot with the same options, defaults and serialization, see Migrating.

Install

bun add -d @liflig/cdk-snapshot
npm install --save-dev @liflig/cdk-snapshot
pnpm add -D @liflig/cdk-snapshot

The package is ESM, with a CommonJS build of the root and Jest entry points for CommonJS Jest projects.

Usage

Import the entry point for your runner. Jest, Vitest and Bun get a toMatchCdkSnapshot matcher; node:test has no expect, so it calls cdkTemplate directly.

node:test

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.

import test from "node:test";
import { cdkTemplate, configureCdkSnapshots } from "@liflig/cdk-snapshot/node";

configureCdkSnapshots();

test("my stack", (t) => {
  t.assert.snapshot(cdkTemplate(stack, { ignoreAssets: true }));
});

Write snapshots with node --test --test-update-snapshots.

Bun

import { expect, test } from "bun:test";
import "@liflig/cdk-snapshot/bun";

test("my stack", () => {
  expect(stack).toMatchCdkSnapshot({ ignoreAssets: true });
});

Vitest

import { expect, test } from "vitest";
import "@liflig/cdk-snapshot/vitest";

test("my stack", () => {
  expect(stack).toMatchCdkSnapshot({ ignoreAssets: true });
});

toMatchCdkSnapshot does not work in concurrent tests: it snapshots through the global expect, which Vitest cannot attribute to a test running concurrently. There, snapshot the template with the test's own expect instead, which records the same entry:

import { cdkTemplate } from "@liflig/cdk-snapshot/vitest";

test.concurrent("my stack", ({ expect }) => {
  expect(cdkTemplate(stack, { ignoreAssets: true })).toMatchSnapshot();
});

Jest

import "@liflig/cdk-snapshot/jest";

test("my stack", () => {
  expect(stack).toMatchCdkSnapshot({ ignoreAssets: true });
});

Instead of importing it in each test file, the entry point can be listed once in setupFilesAfterEnv.

A CommonJS test file, including one ts-jest or babel-jest compiles to CommonJS, loads the CommonJS build and needs no configuration. An ESM test file loads the ESM build, and needs Jest's ESM support enabled as any ESM test file does:

NODE_OPTIONS=--experimental-vm-modules jest

The Jest entry point uses the global expect, so it throws on import if Jest is configured with injectGlobals: false.

Matcher notes

toMatchCdkSnapshot also accepts propertyMatchers, forwarded to the runner's own snapshot assertion for values the normalizations do not cover:

expect(stack).toMatchCdkSnapshot({
  propertyMatchers: { Resources: expect.any(Object) },
});

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.

toMatchCdkSnapshot cannot be negated; .not throws rather than silently passing.

Under Jest and Vitest, toMatchCdkSnapshot counts as one assertion towards expect.assertions(). Bun counts it as two, since its expect exposes no way to correct the count.

Options

| Option | Type | Default | Effect | | --- | --- | --- | --- | | ignoreAssets | boolean | false | Replaces every Code property, every container definition's Image and the whole Parameters block with Any<Object> | | ignoreAssetHashes | boolean | false | Replaces the hash of every asset in the app with <ASSET_HASH>, wherever it appears | | ignoreBootstrapVersion | boolean | true | Drops the BootstrapVersion parameter and its check rule | | ignoreCurrentVersion | boolean | false | Masks the content hash on Lambda CurrentVersion logical IDs and every reference to them | | ignoreMetadata | boolean | false | Drops template and resource Metadata | | ignoreTags | boolean | false | Drops Tags from resource properties | | ignorePipelineAssets | boolean | false | Masks asset paths, IDs and destination suffixes in CDK Pipelines cdk-assets commands | | subsetResourceTypes | string[] | keep all | Keeps only resources of these CloudFormation types | | subsetResourceKeys | string[] | keep all | Keeps only resources with these logical IDs | | assetPlaceholder | unknown | anyObject | Token substituted for asset-derived values | | propertyMatchers | Record<string, unknown> | none | Matchers forwarded to the runner's snapshot assertion; matcher only, cdkTemplate does not take it |

subsetResourceTypes and subsetResourceKeys intersect: given both, a resource is kept only if it matches both.

ignoreAssets is coarse, matching what jest-cdk-snapshot does:

  • It replaces the entire Parameters block. Under CDK's default synthesizer no parameter carries an asset hash, so what disappears is the parameters the stack declares itself.
  • It replaces values that are not assets as well, so a change to inline Lambda code, to a Code.fromBucket key or to a registry image tag such as nginx:1.27 does not show.
  • Assets outside Lambda Code and container images keep their hash: Lambda layers, BucketDeployment sources, Step Functions and API Gateway definitions read from files, and nested stack templates. ignoreAssetHashes covers them.
  • A function's currentVersion logical ID is a hash over its configuration, code included, so a stack that uses it also needs ignoreCurrentVersion to stay stable.
  • It does nothing to a template with no Resources.

ignoreAssetHashes is the precise alternative. It reads the hash of every file and container image asset from the asset manifests the app synthesizes, CDK Pipelines stages included, and replaces exactly those hashes wherever a string in the template holds one:

 "Code": {
   "S3Bucket": "cdk-hnb659fds-assets-112233445566-eu-west-1",
-  "S3Key": "9b8fce7ae7f25ef82fdbaf6b72523b99d0875c0c9c826642819fb51f11d9b125.zip",
+  "S3Key": "<ASSET_HASH>.zip",
 },

Everything else stays visible: the stack's parameters, inline code, registry image tags and any hash that belongs to no asset. A function using currentVersion still needs ignoreCurrentVersion, and a CDK Pipeline still needs ignorePipelineAssets for its destination suffixes, which are not asset hashes. The hashes come from the asset manifests that CDK's default synthesizer writes.

ignoreTags drops the Tags property of each resource. Tags nested deeper stay, such as those Tags.of() propagates into a launch template's TagSpecifications.

ignorePipelineAssets also drops the 8-character suffix CDK appends to each asset destination, since that suffix changes with the asset's content too.

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.

How it works

Everything is built around one function, cdkTemplate, which synthesizes a stack and hands the template to a pure normalizer. Each runner gets a thin adapter that wraps that function in whatever the runner's own snapshot assertion looks like, so snapshots keep the naming and format that runner already produces.

Switching runner

Regenerate the snapshots with the new runner. The templates they record stay the same; the diff is limited to how each runner lays out the file around them:

| | Jest | Vitest | node:test | Bun | | --- | --- | --- | --- | --- | | Header | Jest's, and files without it are rejected | Vitest's | none | Bun's | | Name of a test inside describe | suite test 1 | suite > test 1 | suite > test 1 | suite test 1 | | Entry order | sorted | sorted | sorted | test order | | Single-line snapshot, such as {} | inline | inline | on a line of its own | inline | | 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 |

Entry order does not affect matching, but it makes a regeneration diff look larger than it is: two similar snapshots trading places reads as values changing.

test/compat.test.ts pins every row, so a runner that changes its format fails the build.

Development

make build   # install, format, typecheck, refresh snapshots, test
make ci      # what the CI workflow runs: refuses a stale lockfile, fails on an uncommitted snapshot change

make snapshots regenerates the unit snapshots plus the shared fixture under all four runners, Jest once as ESM and once as CommonJS, which test/compat.test.ts then compares against each other.

make compat-check runs only the four runners and fails if their snapshots changed. CI runs it on the oldest Node that engines in package.json allows.

Migrating from jest-cdk-snapshot

Change the import, or the setupFilesAfterEnv entry. Call sites and .snap files stay as they are, since the options, their defaults and the serialization all match, and the Jest configuration stays as it is, CommonJS or ESM.

-import "jest-cdk-snapshot"
+import "@liflig/cdk-snapshot/jest"

Verified against two public CDK libraries, liflig-cdk and cdk-cloudfront-auth: every existing snapshot passes under jest --ci, and a forced --updateSnapshot rewrites nothing.

One option is gone. yaml is not supported, so a project snapshotting YAML has to regenerate as JSON.

One option masks more. ignorePipelineAssets also drops the content-derived suffix that recent CDK versions append to asset destinations, which jest-cdk-snapshot keeps. A pipeline snapshot taken with it changes once, from publish "111111111111-eu-west-1-2d2574cc" to publish "111111111111-eu-west-1", and then stays put when asset content changes.

jest-cdk-snapshot's option type also extended StageSynthesisOptions, so it accepted skipValidation, validateOnSynthesis, force, errorOnDuplicateSynth and aspectStabilization while warning at runtime that they did nothing. Here the type checker rejects them; delete them.

Releases

Released from master by semantic-release on every merge, so commit messages follow Conventional Commits. The changelog is the GitHub releases page.

License

MIT, see LICENSE.