{"_id":"@bosun-ai/snapify","_rev":"3-f12a23edd94e0d97f193703fc60db0f2","name":"@bosun-ai/snapify","dist-tags":{"latest":"0.3.0"},"versions":{"0.1.0":{"name":"@bosun-ai/snapify","version":"0.1.0","keywords":["shopify","playwright","visual-regression","snapshot","liquid"],"author":"","license":"MIT","_id":"@bosun-ai/snapify@0.1.0","maintainers":[{"name":"timonv","email":"mail@timonv.nl"}],"bin":{"snapify":"dist/cli.js"},"dist":{"shasum":"80cd20dc38449e01746ebf0180e8e64f82a853c9","tarball":"https://registry.npmjs.org/@bosun-ai/snapify/-/snapify-0.1.0.tgz","fileCount":24,"integrity":"sha512-e8+kGAvjGXVqsDdxLqry46f0AEzd915aZ82MPj6zwVl3DQ2QL6R8XGXUc7n8Ju8gTU4Fd06qEwLs1M7ZcihgLQ==","signatures":[{"sig":"MEQCIF/Ihblfv/p0swIHK30CfLk6PDPkmlQn5wstskqbcIaEAiBiw8ZttTmziuwXorq9GITkId/3PMGkGWx0B5OUQybUqQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":452993},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","gitHead":"c1f5ba5f9648586c26f213bfee9e135e653db52c","scripts":{"dev":"tsup src/index.ts src/cli.ts --format esm --dts --watch","test":"c8 --reporter=text --reporter=lcov npm run test:all","build":"tsup src/index.ts src/cli.ts --format esm --dts","clean":"rimraf dist","render":"node dist/cli.js render","security":"npm audit --omit=dev --audit-level=high","test:all":"tsx --test \"tests/**/*.test.ts\"","lint:knip":"knip --strict","test:unit":"tsx --test tests/templateAssembler.test.ts","test:integration":"tsx --test tests/shopify.integration.test.ts"},"_npmUser":{"name":"timonv","email":"mail@timonv.nl"},"_npmVersion":"11.4.2","description":"Visual regression snapshot runner for Shopify Liquid themes using Playwright.","directories":{},"_nodeVersion":"24.4.0","dependencies":{"pngjs":"^7.0.0","yargs":"^17.7.2","liquidjs":"^10.11.1","picocolors":"^1.0.0","pixelmatch":"^5.3.0","playwright":"^1.48.2"},"_hasShrinkwrap":false,"devDependencies":{"c8":"^10.1.3","tsx":"^4.17.0","knip":"^5.69.1","tsup":"^8.1.2","rimraf":"^5.0.10","typescript":"^5.6.2","@types/node":"^22.7.4","@types/yargs":"^17.0.34","conventional-recommended-bump":"^11.2.0"},"_npmOperationalInternal":{"tmp":"tmp/snapify_0.1.0_1763064294972_0.9179098513745498","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@bosun-ai/snapify","version":"0.2.0","keywords":["shopify","playwright","visual-regression","snapshot","liquid"],"author":{"name":"bosun-ai"},"license":"MIT","_id":"@bosun-ai/snapify@0.2.0","maintainers":[{"name":"timonv","email":"mail@timonv.nl"}],"bin":{"snapify":"dist/cli.js"},"dist":{"shasum":"0a7db6c269bf83966112dc98706bdf93478185f6","tarball":"https://registry.npmjs.org/@bosun-ai/snapify/-/snapify-0.2.0.tgz","fileCount":50,"integrity":"sha512-1bX4fHsK7zcTD9D6TZX3y5hgbrAiR+o4xzf/NKWEFC4PIW6jd3ZvOOe406MCEmNT+jXSXqWmj3ttYQxehNjQAQ==","signatures":[{"sig":"MEUCIQCf4twD6aUJlUEDppw5JjpcYr4sMWl8ldt0CKkkmWyOCgIgCPIPHA9mLpUQWa8P5P8rgmOPv1iOt7FDE85g3id17rs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1569611},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","gitHead":"91d0be868114af0be12ba351530fd9619c0b51af","scripts":{"dev":"tsup src/index.ts src/cli.ts --format esm --dts --watch","test":"npm run typecheck && c8 --reporter=text --reporter=lcov npm run test:all","build":"tsup src/index.ts src/cli.ts --format esm --dts","clean":"rimraf dist","render":"node dist/cli.js render","security":"npm audit --omit=dev --audit-level=high","test:all":"node --test --import tsx \"tests/**/*.test.ts\"","docs:json":"jsdoc -X src > .tmp-jsdoc.json","lint:knip":"knip --strict","test:unit":"node --test --import tsx tests/templateAssembler.test.ts","typecheck":"tsc --noEmit","docs:build":"jsdoc -r src -d .tmp-jsdoc && rimraf .tmp-jsdoc","test:integration":"node --test --import tsx tests/shopify.integration.test.ts"},"_npmUser":{"name":"timonv","email":"mail@timonv.nl"},"_npmVersion":"11.4.2","description":"Visual regression snapshot runner for Shopify Liquid themes using Playwright.","directories":{},"_nodeVersion":"24.4.0","dependencies":{"pngjs":"^7.0.0","yargs":"^18.0.0","liquidjs":"^10.24.0","picocolors":"^1.1.1","pixelmatch":"^7.1.0","playwright":"^1.57.0"},"_hasShrinkwrap":false,"devDependencies":{"c8":"^10.1.3","tsx":"^4.21.0","knip":"^5.71.0","tsup":"^8.5.1","jsdoc":"^4.0.5","rimraf":"^6.1.2","typescript":"^5.9.3","@types/node":"^24.10.1","@types/pngjs":"^6.0.5","@types/yargs":"^17.0.35","@types/pixelmatch":"^5.2.6","conventional-recommended-bump":"^11.2.0"},"_npmOperationalInternal":{"tmp":"tmp/snapify_0.2.0_1764964852351_0.10553860076775878","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@bosun-ai/snapify","version":"0.3.0","description":"Visual regression snapshot runner for Shopify Liquid themes using Playwright.","type":"module","main":"dist/index.js","types":"dist/index.d.ts","bin":{"snapify":"dist/cli.js"},"scripts":{"build":"tsup src/index.ts src/cli.ts --format esm --dts","dev":"tsup src/index.ts src/cli.ts --format esm --dts --watch","clean":"rimraf dist","render":"node dist/cli.js render","typecheck":"tsc --noEmit","test":"npm run typecheck && c8 --reporter=text --reporter=lcov npm run test:all","test:all":"node --test --import tsx \"tests/**/*.test.ts\"","test:integration":"node --test --import tsx tests/shopify.integration.test.ts","test:unit":"node --test --import tsx tests/templateAssembler.test.ts","security":"npm audit --omit=dev --audit-level=high","lint:knip":"knip --strict","docs:build":"jsdoc -r src -d .tmp-jsdoc && rimraf .tmp-jsdoc","docs:json":"jsdoc -X src > .tmp-jsdoc.json"},"keywords":["shopify","playwright","visual-regression","snapshot","liquid"],"author":{"name":"bosun-ai"},"license":"MIT","dependencies":{"liquidjs":"^10.24.0","picocolors":"^1.1.1","pixelmatch":"^7.1.0","playwright":"^1.57.0","pngjs":"^7.0.0","yargs":"^18.0.0"},"devDependencies":{"jsdoc":"^4.0.5","@types/node":"^24.10.1","@types/pixelmatch":"^5.2.6","@types/pngjs":"^6.0.5","@types/yargs":"^17.0.35","c8":"^10.1.3","conventional-recommended-bump":"^11.2.0","knip":"^5.71.0","rimraf":"^6.1.2","tsup":"^8.5.1","tsx":"^4.21.0","typescript":"^5.9.3"},"_id":"@bosun-ai/snapify@0.3.0","gitHead":"c2f91e55a8d6e8a0e995e0427a7c1925f756987b","_nodeVersion":"24.4.0","_npmVersion":"11.4.2","dist":{"integrity":"sha512-MemAJSxTdWNjbuXihSeiTuehctKVA/kU/iM+KAVye4RCwcRShRawCkF8iHKctfGwvAuKNWGaaSdEOvlivMC6Fg==","shasum":"3ecd8c569c22c82cd2e1b8c01fd07b54e8d6a506","tarball":"https://registry.npmjs.org/@bosun-ai/snapify/-/snapify-0.3.0.tgz","fileCount":53,"unpackedSize":1771595,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIFTUcnkemtNgogZTnYS6RtDXhDu+IBSAhxCXAnW0X0IEAiAFBgaxmnGeeBeS3W3p4IlM8wveoiHXLq2AlP8S6/oPLQ=="}]},"_npmUser":{"name":"timonv","email":"mail@timonv.nl"},"directories":{},"maintainers":[{"name":"timonv","email":"mail@timonv.nl"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/snapify_0.3.0_1765554191224_0.4492710032634455"},"_hasShrinkwrap":false}},"time":{"created":"2025-11-13T20:04:54.831Z","modified":"2025-12-12T15:43:11.616Z","0.1.0":"2025-11-13T20:04:55.155Z","0.2.0":"2025-12-05T20:00:52.539Z","0.3.0":"2025-12-12T15:43:11.421Z"},"author":{"name":"bosun-ai"},"license":"MIT","keywords":["shopify","playwright","visual-regression","snapshot","liquid"],"description":"Visual regression snapshot runner for Shopify Liquid themes using Playwright.","maintainers":[{"name":"timonv","email":"mail@timonv.nl"}],"readme":"# snapify\n\nVisual regression snapshots for Shopify themes using Playwright — no running dev server required.\n\nThis enables drastic refactoring without the fear of breaking existing themes, and makes it easy to add visual tests for new Liquid templates as you build them.\n\n## Key ideas\n\n- ✨ Render OS 2.0 JSON or Liquid templates entirely in-memory with LiquidJS and custom Shopify helpers.\n- 🧱 Resolves sections, snippets, and local-path includes just like a deployed theme.\n- 🎨 Inlines CSS/JS assets (including `{{ 'theme.css' | asset_url | stylesheet_tag }}`) so snapshots reflect final storefront styling.\n- 🖼️ Replaces `shopify://shop_images/...` references with deterministic SVG placeholders (respecting requested width/height) so tests never need the real CDN assets.\n- 🌐 Respects Shopify locale strings: load `locales/en.default.json` (or pass `locale`/`SNAPIFY_LOCALE`) and `{{ 'sections.*' | t }}` renders with the same copy as production.\n- 📸 Uses Playwright to capture screenshots and `pixelmatch` to diff against baselines.\n- 🧪 Ships both a programmatic API (`render`) and a CLI (`snapify render`).\n\n## Installation\n\n```bash\nnpm save --dev @bosun-ai/snapify playwright\nnpx playwright install --with-deps chromium\n```\n\nYou can then write your tests, or run the CLI against the current repository root (which already contains a full theme).\n\n## CLI usage\n\n```bash\nsnapify render <template> [options]\n```\n\nCommon flags (values from `snapify.config.js` are used as defaults when present):\n\n- `--theme-root` – root of the Shopify theme (defaults to `process.cwd()`).\n- `--layout` – override layout file (without `.liquid`).\n- `--data` – inline JSON or a path to a JSON file providing Liquid data.\n- `--styles`/`--styles-file` – inject additional CSS.\n- `--viewport 1440x900` – customize Playwright viewport.\n- `--snapshot-dir` – where snapshots live (defaults to `__snapshots__` in the theme root).\n- `--accept` / `-u` – replace the stored snapshot with the newly captured one.\n\nExample:\n\n```bash\nsnapify render index --theme-root .. --viewport 1440x900 --data ./fixtures/home.json\n```\n\n## Programmatic API (with assertions)\n\nThe `assertSnapshot` helper makes PNG the source of truth while still surfacing HTML drift for debugging.\n\n```ts\nimport { render, assertSnapshot } from 'snapify';\n\nconst snapshot = await render({\n  themeRoot: '/path/to/theme',\n  template: 'product',\n  locale: 'en.default',\n  layout: 'checkout',\n  data: { product: { title: 'Sample' } },\n  styles: '.debug-outline { outline: 1px solid red; }',\n  viewport: { width: 1440, height: 900 },\n  snapshot: {\n    name: 'product-page',\n    dir: './__snapshots__',\n    accept: process.env.CI ? false : true\n  }\n});\n\nassertSnapshot(snapshot, { htmlMode: 'warn' });\n```\n\nThe resolved object includes:\n\n- `htmlPath` / `screenshotPath` – stored baseline snapshot files.\n- `newHtmlPath` / `newScreenshotPath` – `.new` files written only when output differs.\n- `htmlChanged` / `imageChanged` – booleans for diff detection.\n- `status` – `'matched' | 'updated' | 'changed'`.\n\n## Extending Liquid constructs\n\nSnapify exposes the underlying LiquidJS engine so you can add your own tags and filters, using the same API Liquid provides:\n\n```ts\nimport { TemplateAssembler } from 'snapify/core/templateAssembler.js';\n\nconst assembler = new TemplateAssembler('/path/to/theme');\n\nassembler.extend((engine) => {\n  engine.registerFilter('shout', (value) => String(value ?? '').toUpperCase());\n  engine.registerTag('hello', {\n    parse() {},\n    async render() {\n      return '<span data-custom=\"hello\">hello</span>';\n    }\n  });\n});\n\nconst html = await assembler.compose({ template: 'index', layout: false });\n```\n\nCustom constructs participate in the same render pipeline as built-ins, so they work with snapshots and diagnostics.\n\n## Using Snapify in automated tests\n\nSnapify slots into Node's built-in test runner (or Jest/Vitest) so you can assert against baselines inside regular CI suites:\n\n```ts\n// tests/homepage.test.ts\nimport assert from 'node:assert/strict';\nimport test from 'node:test';\nimport path from 'node:path';\nimport { render } from 'snapify';\n\nconst THEME_ROOT = path.resolve('tests/theme');\nconst SNAPSHOT_DIR = path.join(THEME_ROOT, '__snapshots__');\nconst ACCEPT = Boolean(process.env.SNAPIFY_UPDATE_BASELINES);\n\ntest('index template matches stored baseline', async () => {\n  const snapshot = await render({\n    themeRoot: THEME_ROOT,\n    template: 'index',\n    data: { hero: { headline: 'Golden hour' } },\n    viewport: { width: 1280, height: 720 },\n    snapshot: {\n      name: 'index',\n      dir: SNAPSHOT_DIR,\n      accept: ACCEPT\n    }\n  });\n\n  if (ACCEPT) {\n    // Baselines refreshed locally; fail fast if this ever happens on CI.\n    assert.equal(snapshot.status, 'updated');\n    return;\n  }\n\n  assert.equal(snapshot.imageChanged, false, `Snapshot drift detected. Inspect ${snapshot.newScreenshotPath ?? 'n/a'} for details.`);\n  assert.equal(snapshot.htmlChanged, false, 'Rendered HTML should match the stored baseline');\n});\n```\n\nTips:\n\n- `SNAPIFY_UPDATE_BASELINES=1 npm test` refreshes every snapshot in bulk.\n- Keep `__snapshots__/*.png`/`.html` under version control; ignore `*.new.*`.\n- The README code samples and the Jest example are exercised by the automated test suite, so they stay in sync.\n\n### Jest example\n\nUsing Snapify inside Jest with TypeScript just requires enabling ESM support and invoking `render` + `assertSnapshot` within a test:\n\n```ts\n/**\n * @jest-environment node\n */\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { render, assertSnapshot } from 'snapify';\n\nconst __dirname = path.dirname(fileURLToPath(import.meta.url));\nconst THEME_ROOT = path.resolve(__dirname, '../theme');\n\ndescribe('product template', () => {\n  const snapshotDir = path.join(THEME_ROOT, '__snapshots__');\n  const accept = process.env.SNAPIFY_UPDATE_BASELINES === '1';\n\n  it('matches the stored baseline', async () => {\n    const snapshot = await render({\n      themeRoot: THEME_ROOT,\n      template: 'product',\n      snapshot: {\n        name: 'product',\n        dir: snapshotDir,\n        accept\n      }\n    });\n\n    if (accept) {\n      expect(snapshot.status).toBe('updated');\n      return;\n    }\n\n    assertSnapshot(snapshot, { htmlMode: 'warn' });\n  });\n});\n\n// snapify.config.js is picked up automatically if present:\n// export default { snapshot: { dir: '__snapshots__' }, browser: 'chromium' };\n// See examples/jest/homepage.test.ts in this repository for a complete, runnable example.\n```\n\nSet up Jest with `\"type\": \"module\"` (or `transform` rules for CommonJS), run `SNAPIFY_UPDATE_BASELINES=1 npx jest` locally to refresh baselines, and `npx jest` in CI to verify snapshots.\n\n## How rendering works\n\n1. **Liquid + sections.** `TemplateAssembler` configures LiquidJS with Shopify-like defaults, resolves JSON templates (sections, block order, `custom_css`) and plain `.liquid` templates.\n2. **Inline assets.** Filters such as `asset_url`, `stylesheet_tag`, and `script_tag` are re-implemented to read from `assets/` and inline their contents directly into the `<head>`.\n3. **Head injection.** Anything coming from filters or user-provided `styles` is piped through `content_for_header` (or injected at the top of `<head>` if a layout omits it) so the snapshot matches storefront styling.\n4. **Playwright capture.** HTML is handed to a headless Chromium page via `page.setContent`, and the resulting screenshot is compared with the baseline using `pixelmatch`.\n\n### Local-path includes\n\nSnapify keeps Liquid's `relativeReference` behavior enabled, so you can co-locate fixtures next to the template you are testing:\n\n```liquid\n{%- comment -%}sections/__snapify__/hero.liquid{%- endcomment -%}\n<section class=\"hero\">\n  {% render './partials/cta', label: 'Book a demo' %}\n</section>\n```\n\nPlace `sections/__snapify__/partials/cta.liquid` next to it and the renderer will resolve the relative include without needing to copy files into `snippets/`.\n\n## Testing multiple templates\n\nThis repository uses the Node test runner plus the `SNAPIFY_UPDATE_BASELINES` flag shown above. Run the following from the repo root:\n\n```bash\n# Refresh baselines locally\nSNAPIFY_UPDATE_BASELINES=1 npm test\n\n# Validate without touching stored baselines\nnpm test\n```\n\nArtifacts land under `__snapshots__/` inside your theme root so they can be reviewed or committed; `.new.*` files are transient and should stay untracked.\n","readmeFilename":"README.md"}