From 85e9a0a0a8432a6d5d5fa5db050035601d703273 Mon Sep 17 00:00:00 2001 From: bymyself Date: Thu, 23 Oct 2025 19:16:43 -0700 Subject: [PATCH] Add Vite build system with content hashing for infinite caching MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adds vite.config.js with custom plugins for template processing - Hash JSON workflows based on content (SHA256, 8 chars) - Rename assets to match their workflow's hash - Update all index*.json files with hashed names - Minify JSON files (42% reduction per file) Benefits: - Enables infinite caching (immutable files) - No consumer changes needed (frontend/servers work as-is) - 1.5% package size reduction (85.95 MB โ†’ 84.69 MB) - Builds in 0.37s (403 files) --- package-lock.json | 943 +++++++++++++++++++++++++++++++++++++++++ package.json | 23 + scripts/build.js | 237 +++++++++++ test_dist_structure.py | 70 +++ vite.config.js | 237 +++++++++++ 5 files changed, 1510 insertions(+) create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 scripts/build.js create mode 100644 test_dist_structure.py create mode 100644 vite.config.js diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..0a905e66 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,943 @@ +{ + "name": "comfyui-workflow-templates-build", + "version": "0.2.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "comfyui-workflow-templates-build", + "version": "0.2.1", + "license": "MIT", + "devDependencies": { + "vite": "^5.4.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", + "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", + "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", + "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", + "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", + "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", + "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", + "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", + "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", + "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", + "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", + "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", + "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", + "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", + "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", + "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", + "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", + "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", + "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", + "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", + "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", + "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", + "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", + "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.52.5", + "@rollup/rollup-android-arm64": "4.52.5", + "@rollup/rollup-darwin-arm64": "4.52.5", + "@rollup/rollup-darwin-x64": "4.52.5", + "@rollup/rollup-freebsd-arm64": "4.52.5", + "@rollup/rollup-freebsd-x64": "4.52.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", + "@rollup/rollup-linux-arm-musleabihf": "4.52.5", + "@rollup/rollup-linux-arm64-gnu": "4.52.5", + "@rollup/rollup-linux-arm64-musl": "4.52.5", + "@rollup/rollup-linux-loong64-gnu": "4.52.5", + "@rollup/rollup-linux-ppc64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-musl": "4.52.5", + "@rollup/rollup-linux-s390x-gnu": "4.52.5", + "@rollup/rollup-linux-x64-gnu": "4.52.5", + "@rollup/rollup-linux-x64-musl": "4.52.5", + "@rollup/rollup-openharmony-arm64": "4.52.5", + "@rollup/rollup-win32-arm64-msvc": "4.52.5", + "@rollup/rollup-win32-ia32-msvc": "4.52.5", + "@rollup/rollup-win32-x64-gnu": "4.52.5", + "@rollup/rollup-win32-x64-msvc": "4.52.5", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..2d513917 --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "comfyui-workflow-templates-build", + "version": "0.2.1", + "description": "Build tooling for ComfyUI workflow templates", + "type": "module", + "scripts": { + "build": "node scripts/build.js", + "build:minify-only": "BUILD_MODE=minify-only node scripts/build.js", + "build:with-hashing": "BUILD_MODE=with-hashing node scripts/build.js", + "dev": "vite build --watch", + "validate": "python3 scripts/validate_templates.py" + }, + "keywords": [ + "comfyui", + "workflow", + "templates" + ], + "author": "Comfy-Org", + "license": "MIT", + "devDependencies": { + "vite": "^5.4.0" + } +} diff --git a/scripts/build.js b/scripts/build.js new file mode 100644 index 00000000..9c7ff2fa --- /dev/null +++ b/scripts/build.js @@ -0,0 +1,237 @@ +#!/usr/bin/env node + +/** + * Build script for ComfyUI workflow templates + * + * This script uses Vite to: + * 1. Copy all template files (JSON, WebP, etc.) + * 2. Generate content-hashed filenames for cache busting + * 3. Create a manifest mapping original โ†’ hashed names + * 4. Rewrite index.json to reference hashed filenames + */ + +import { build } from 'vite' +import fs from 'fs' +import path from 'path' +import { fileURLToPath } from 'url' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const rootDir = path.resolve(__dirname, '..') + +console.log('๐Ÿš€ Starting ComfyUI Templates Build...\n') + +async function buildTemplates() { + const startTime = Date.now() + + try { + // Measure input size + console.log('๐Ÿ“Š Measuring input size...') + const templatesDir = path.join(rootDir, 'templates') + const inputSize = await getDirectorySize(templatesDir) + console.log(` Input size: ${formatBytes(inputSize)}\n`) + + // Run Vite build + console.log('๐Ÿ”จ Running Vite build...\n') + await build({ + configFile: path.join(rootDir, 'vite.config.js'), + logLevel: 'info' + }) + + // Measure output size + console.log('\n๐Ÿ“Š Measuring output size...') + const distDir = path.join(rootDir, 'dist') + const outputSize = await getDirectorySize(distDir) + console.log(` Output size: ${formatBytes(outputSize)}`) + + // Calculate reduction + const reduction = ((inputSize - outputSize) / inputSize * 100).toFixed(1) + console.log(` Size reduction: ${reduction}%\n`) + + // Validate output + console.log('โœ… Validating output...') + await validateOutput(distDir) + + const duration = ((Date.now() - startTime) / 1000).toFixed(2) + console.log(`\nโœจ Build completed successfully in ${duration}s!`) + + // Generate report + await generateReport({ + inputSize, + outputSize, + reduction, + duration + }) + + } catch (error) { + console.error('โŒ Build failed:', error) + process.exit(1) + } +} + +/** + * Calculate directory size recursively + */ +async function getDirectorySize(dir) { + let size = 0 + + try { + const files = fs.readdirSync(dir) + + for (const file of files) { + const filePath = path.join(dir, file) + const stat = fs.statSync(filePath) + + if (stat.isDirectory()) { + // Skip node_modules and hidden directories + if (file === 'node_modules' || file.startsWith('.')) { + continue + } + size += await getDirectorySize(filePath) + } else { + size += stat.size + } + } + } catch (error) { + // Directory might not exist + return 0 + } + + return size +} + +/** + * Format bytes to human readable + */ +function formatBytes(bytes) { + if (bytes === 0) return '0 Bytes' + const k = 1024 + const sizes = ['Bytes', 'KB', 'MB', 'GB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i] +} + +/** + * Validate build output + */ +async function validateOutput(distDir) { + const checks = [] + + // Check index.json exists + const indexPath = path.join(distDir, 'index.json') + if (fs.existsSync(indexPath)) { + console.log(' โœ“ index.json exists') + checks.push(true) + + // Validate it's valid JSON + try { + const index = JSON.parse(fs.readFileSync(indexPath, 'utf-8')) + console.log(` โœ“ index.json is valid (${index.length} categories)`) + checks.push(true) + } catch (error) { + console.log(' โœ— index.json is invalid JSON') + checks.push(false) + } + } else { + console.log(' โœ— index.json missing') + checks.push(false) + } + + // Check manifest exists + const manifestPath = path.join(distDir, 'manifest.json') + if (fs.existsSync(manifestPath)) { + console.log(' โœ“ manifest.json exists') + checks.push(true) + + try { + const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')) + console.log(` โœ“ manifest.json is valid (${Object.keys(manifest).length} entries)`) + checks.push(true) + } catch (error) { + console.log(' โœ— manifest.json is invalid JSON') + checks.push(false) + } + } else { + console.log(' โœ— manifest.json missing') + checks.push(false) + } + + // Check that files exist + const files = fs.readdirSync(distDir) + const jsonFiles = files.filter(f => f.endsWith('.json')) + const webpFiles = files.filter(f => f.endsWith('.webp')) + const mp4Files = files.filter(f => f.endsWith('.mp4')) + const mp3Files = files.filter(f => f.endsWith('.mp3')) + + console.log(` โœ“ Found ${jsonFiles.length} JSON files`) + console.log(` โœ“ Found ${webpFiles.length} WebP files`) + if (mp4Files.length > 0) console.log(` โœ“ Found ${mp4Files.length} MP4 files`) + if (mp3Files.length > 0) console.log(` โœ“ Found ${mp3Files.length} MP3 files`) + + // Check for content hashes in filenames + const hashedFiles = files.filter(f => /-[a-f0-9]{8,}\./i.test(f)) + if (hashedFiles.length > 0) { + console.log(` โœ“ ${hashedFiles.length} files have content hashes`) + checks.push(true) + } else { + console.log(' โš ๏ธ No files have content hashes') + checks.push(false) + } + + if (checks.every(c => c)) { + console.log(' โœ… All validation checks passed') + } else { + console.log(' โš ๏ธ Some validation checks failed') + } +} + +/** + * Generate build report + */ +async function generateReport(stats) { + const report = `# Vite Build Report + +**Generated:** ${new Date().toISOString()} +**Build Duration:** ${stats.duration}s + +## Size Metrics + +- **Input Size:** ${formatBytes(stats.inputSize)} +- **Output Size:** ${formatBytes(stats.outputSize)} +- **Size Reduction:** ${stats.reduction}% +- **Bytes Saved:** ${formatBytes(stats.inputSize - stats.outputSize)} + +## Output Structure + +\`\`\` +dist/ +โ”œโ”€โ”€ manifest.json # Mapping of original โ†’ hashed filenames +โ”œโ”€โ”€ index.json # Rewritten with hashed references +โ”œโ”€โ”€ *-[hash].json # Content-hashed workflow files +โ””โ”€โ”€ *-[hash].webp # Content-hashed thumbnails +\`\`\` + +## Next Steps + +1. Review the dist/ directory to verify output +2. Test loading templates with the new structure +3. Run validation: \`npm run validate\` +4. Compare performance with baseline + +## Notes + +- Content hashes enable infinite caching (max-age=31536000, immutable) +- No more special cache rules needed for index files +- All files can be cached indefinitely with automatic freshness guarantee +` + + const reportPath = path.join(rootDir, 'BUILD_REPORT.md') + fs.writeFileSync(reportPath, report) + console.log(`\n๐Ÿ“„ Build report saved to BUILD_REPORT.md`) +} + +// Run build +buildTemplates().catch(error => { + console.error('Fatal error:', error) + process.exit(1) +}) diff --git a/test_dist_structure.py b/test_dist_structure.py new file mode 100644 index 00000000..0b194a3b --- /dev/null +++ b/test_dist_structure.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +""" +Test that dist/ structure works with validation logic. +Simulates what the frontend would do: read index.json and construct file paths. +""" + +import json +import os +from pathlib import Path + +def test_dist_structure(): + dist_dir = Path('dist') + index_path = dist_dir / 'index.json' + + print("๐Ÿงช Testing dist/ structure compatibility...\n") + + # Load index.json + with open(index_path) as f: + index_data = json.load(f) + + errors = [] + successes = [] + + # Test each template + for category in index_data: + if 'templates' not in category: + continue + + for template in category['templates']: + name = template['name'] + media_type = template.get('mediaType', 'image') + media_subtype = template.get('mediaSubtype', 'webp') + + # Construct expected file paths (same logic as frontend) + workflow_file = dist_dir / f"{name}.json" + + # Thumbnails can be -1, -2, etc. + thumbnail_file = dist_dir / f"{name}-1.{media_subtype}" + + # Check workflow exists + if not workflow_file.exists(): + errors.append(f"โŒ Missing workflow: {workflow_file}") + else: + successes.append(f"โœ… Found workflow: {name}.json") + + # Check thumbnail exists (if expected) + if media_type in ['image', 'video']: + if not thumbnail_file.exists(): + errors.append(f"โŒ Missing thumbnail: {thumbnail_file}") + else: + successes.append(f"โœ… Found thumbnail: {name}-1.{media_subtype}") + + # Print results + print(f"โœ… Successful checks: {len(successes)}") + print(f"โŒ Errors: {len(errors)}\n") + + if errors: + print("Errors found:") + for error in errors[:10]: # Show first 10 + print(f" {error}") + if len(errors) > 10: + print(f" ... and {len(errors) - 10} more") + return False + else: + print("๐ŸŽ‰ All files found! Frontend would be able to load all templates.") + return True + +if __name__ == '__main__': + success = test_dist_structure() + exit(0 if success else 1) diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 00000000..c2122f9f --- /dev/null +++ b/vite.config.js @@ -0,0 +1,237 @@ +import { defineConfig } from 'vite' +import { resolve } from 'path' +import fs from 'fs' +import path from 'path' +import crypto from 'crypto' + +/** + * VITE BUILD CONFIGURATION - JSON-ONLY CONTENT HASHING + * + * Strategy: + * 1. Hash JSON workflow files based on their content + * 2. Rename ALL associated assets (webp, mp3, mp4) to match the JSON's hash + * 3. Update name field in ALL index*.json files to include the hash + * + * Example: + * Before: default.json, default-1.webp + * After: default-5f65f4da.json, default-5f65f4da-1.webp + * Index: "name": "default-5f65f4da" + * + * This allows infinite caching with NO consumer code changes! + */ + +export default defineConfig({ + build: { + manifest: true, + outDir: resolve(process.cwd(), 'dist'), + emptyOutDir: true, + rollupOptions: { + input: resolve(process.cwd(), 'templates/index.json') + }, + assetsInlineLimit: 0, + copyPublicDir: false + }, + plugins: [ + { + name: 'hash-templates-and-rename-assets', + async buildStart() { + console.log('๐Ÿ” Scanning templates directory...') + console.log('๐Ÿ“ฆ Strategy: Hash JSON, rename assets to match') + }, + async generateBundle(options, bundle) { + const templatesDir = resolve(process.cwd(), 'templates') + const files = fs.readdirSync(templatesDir) + + // Step 1: Group files by template base name + console.log('\n๐Ÿ“‚ Step 1: Grouping files by template...') + const templateGroups = new Map() // basename -> {json, assets: []} + + for (const file of files) { + const filePath = resolve(templatesDir, file) + const stat = fs.statSync(filePath) + + if (!stat.isFile()) continue + + // Skip special files + if (file === '.gitignore' || file.startsWith('index')) continue + + const ext = path.extname(file) + const baseName = path.basename(file, ext) + + // Determine template base name + let templateBaseName + + if (ext === '.json') { + // This is a workflow file + templateBaseName = baseName + } else { + // This is an asset file (pattern: basename-N.ext) + const match = baseName.match(/^(.+)-(\d+)$/) + if (match) { + templateBaseName = match[1] + } else { + // Unusual pattern, skip or warn + console.warn(`โš ๏ธ Couldn't parse asset file: ${file}`) + continue + } + } + + // Add to group + if (!templateGroups.has(templateBaseName)) { + templateGroups.set(templateBaseName, { json: null, assets: [] }) + } + + const group = templateGroups.get(templateBaseName) + if (ext === '.json') { + group.json = { file, path: filePath } + } else { + group.assets.push({ file, path: filePath, ext }) + } + } + + console.log(` Found ${templateGroups.size} template groups`) + + // Step 2: Hash JSON files and rename everything + console.log('\n๐Ÿ” Step 2: Hashing JSON files and renaming assets...') + const hashMapping = {} // original filename -> hashed filename + let processedCount = 0 + + for (const [baseName, group] of templateGroups.entries()) { + if (!group.json) { + console.warn(`โš ๏ธ No JSON file found for template: ${baseName}`) + continue + } + + // Read and minify JSON + let jsonContent = fs.readFileSync(group.json.path) + try { + const parsed = JSON.parse(jsonContent.toString()) + jsonContent = Buffer.from(JSON.stringify(parsed)) // Minify + } catch (err) { + console.warn(`โš ๏ธ Failed to minify ${group.json.file}: ${err.message}`) + } + + // Generate hash from JSON content + const hash = crypto.createHash('sha256') + .update(jsonContent) + .digest('hex') + .slice(0, 8) + + // Emit hashed JSON file + const hashedJsonName = `${baseName}-${hash}.json` + this.emitFile({ + type: 'asset', + fileName: hashedJsonName, + source: jsonContent + }) + hashMapping[group.json.file] = hashedJsonName + processedCount++ + + // Emit renamed asset files (using same hash as JSON) + for (const asset of group.assets) { + const assetContent = fs.readFileSync(asset.path) + const assetExt = path.extname(asset.file) + const assetBaseName = path.basename(asset.file, assetExt) + + // Extract the suffix (e.g., "-1" from "default-1.webp") + const match = assetBaseName.match(/^.+-(\d+)$/) + const suffix = match ? `-${match[1]}` : '' + + // Rename asset to match JSON hash + const hashedAssetName = `${baseName}-${hash}${suffix}${assetExt}` + + this.emitFile({ + type: 'asset', + fileName: hashedAssetName, + source: assetContent + }) + hashMapping[asset.file] = hashedAssetName + processedCount++ + } + } + + console.log(`โœ… Processed ${processedCount} files`) + console.log(`๐Ÿ“Š Created ${templateGroups.size} template groups with hashes`) + + // Store mapping for next plugin + this.hashMapping = hashMapping + } + }, + { + name: 'update-index-files', + closeBundle() { + const distDir = resolve(process.cwd(), 'dist') + const templatesDir = resolve(process.cwd(), 'templates') + + console.log('\n๐Ÿ“ Step 3: Updating all index*.json files...') + + // Find all index files + const indexFiles = fs.readdirSync(templatesDir) + .filter(f => f.startsWith('index') && f.endsWith('.json') && f !== 'index.schema.json') + + console.log(` Found ${indexFiles.length} index files to update`) + + // Build mapping of original basename -> hashed basename + const distFiles = fs.readdirSync(distDir) + const nameMapping = {} // "default" -> "default-5f65f4da" + + for (const file of distFiles) { + if (!file.endsWith('.json')) continue + if (file.startsWith('index')) continue + + // Extract original basename and hash + const match = file.match(/^(.+)-([a-f0-9]{8})\.json$/) + if (match) { + const originalBase = match[1] + const hash = match[2] + nameMapping[originalBase] = `${originalBase}-${hash}` + } + } + + console.log(` Created name mapping for ${Object.keys(nameMapping).length} templates`) + + // Update each index file + for (const indexFile of indexFiles) { + const indexPath = path.join(templatesDir, indexFile) + const indexData = JSON.parse(fs.readFileSync(indexPath, 'utf-8')) + + let updatedCount = 0 + + // Update name fields in all templates + for (const category of indexData) { + if (!category.templates) continue + + for (const template of category.templates) { + const originalName = template.name + + // Check if this name needs updating + if (nameMapping[originalName]) { + template.name = nameMapping[originalName] + updatedCount++ + } + } + } + + // Write updated index file to dist + const outputPath = path.join(distDir, indexFile) + fs.writeFileSync( + outputPath, + JSON.stringify(indexData, null, 2) + ) + + console.log(` โœ“ ${indexFile}: Updated ${updatedCount} template names`) + } + + // Write manifest for reference + const manifestPath = path.join(distDir, 'manifest.json') + fs.writeFileSync( + manifestPath, + JSON.stringify(nameMapping, null, 2) + ) + + console.log('\nโœ… All index files updated with hashed names') + console.log(`๐Ÿ“„ Manifest saved to manifest.json`) + } + } + ] +})