You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

purgecss-cli

Package Overview
Dependencies
Maintainers
1
Versions
3
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

purgecss-cli - npm Package Compare versions

Comparing version
0.6.0
to
0.7.0
+2
-2
dist/cli.cjs
#!/usr/bin/env node
"use strict";var te=Object.create;var W=Object.defineProperty;var se=Object.getOwnPropertyDescriptor;var re=Object.getOwnPropertyNames;var oe=Object.getPrototypeOf,ne=Object.prototype.hasOwnProperty;var ie=(e,t,s,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of re(t))!ne.call(e,r)&&r!==s&&W(e,r,{get:()=>t[r],enumerable:!(o=se(t,r))||o.enumerable});return e};var n=(e,t,s)=>(s=e!=null?te(oe(e)):{},ie(t||!e||!e.__esModule?W(s,"default",{value:e,enumerable:!0}):s,e));var E=require("commander"),C=n(require("chalk"),1),Z=n(require("chokidar"),1),D=n(require("ora"),1);var O=n(require("path"),1),d=n(require("chalk"),1),H=require("cosmiconfig"),K=n(require("prompts"),1);var w=n(require("fs/promises"),1),A=n(require("path"),1),N=n(require("chalk"),1),T=e=>e.match(/[\w-:/%.]+(?<!:)/g)||[];function i(e){return e?(Array.isArray(e)?e.join(" "):String(e)).split(/[,\s]+/).map(s=>s.trim()).filter(Boolean):[]}async function v(e){e&&await w.default.mkdir(e,{recursive:!0}).catch(()=>{})}async function z(e){try{let t=await w.default.readFile(e,"utf8");return JSON.parse(t)}catch{return null}}async function R(e,t){await v(A.default.dirname(e)),await w.default.writeFile(e,JSON.stringify(t,null,2),"utf8")}function M(e){let t=i(e.safelist),o=(i(e.safelistPatterns)||[]).map(r=>{try{if(r.startsWith("/")&&r.lastIndexOf("/")>0){let a=r.lastIndexOf("/");return new RegExp(r.slice(1,a),r.slice(a+1))}return new RegExp(r)}catch{return console.error(N.default.yellow(`Warning: bad regex "${r}" ignored`)),null}}).filter(r=>!!r);return{safelist:t,safelistPatterns:o}}async function V(e){try{let t=e+".bak";await w.default.access(t).then(()=>!0).catch(()=>!1)||await w.default.copyFile(e,t)}catch{}}function g(e){if(e<1024)return`${e} B`;let t=e/1024;return t<1024?`${t.toFixed(1)} KB`:`${(t/1024).toFixed(2)} MB`}async function Q(){let e=O.default.resolve(process.cwd(),"purgecss-cli.config.json");if(await z(e)){console.log(d.default.yellow("purgecss-cli.config.json already exists."));return}await R(e,{content:["src/**/*.{html,js,jsx,ts,tsx,vue,svelte,md,mdx}"],css:["dist/**/*.css"],out:"pruned/",tailwind:!1,safelist:[],safelistPatterns:["^btn-","^(enter|leave)-"],rejected:!0,minify:!0,dryRun:!1,backup:!1,report:"reports/prune.json",watch:!1}),console.log(d.default.green("Created purgecss-cli.config.json"))}async function ae(e){let t=(0,H.cosmiconfig)("purgecss-cli");if(e.config){let s=O.default.resolve(process.cwd(),e.config);return t.load(s)}return t.search(process.cwd())}function G(e,t){let s={...e,...t||{}};return s.content=i(s.content),s.css=i(s.css),s.safelist=i(s.safelist),s.safelistPatterns=i(s.safelistPatterns),s}function ce(e){(!e.content||e.content.length===0)&&(console.error(d.default.red("No content globs provided.")),process.exit(2)),(!e.css||e.css.length===0)&&(console.error(d.default.red("No CSS globs provided.")),process.exit(2))}async function le(e){let t=!e.content||e.content.length===0,s=!e.css||e.css.length===0;if(!t&&!s)return e;console.log(d.default.cyan("Interactive setup - missing required options."));let o=await(0,K.default)([t&&{type:"text",name:"content",message:"Content globs (space/comma separated)",initial:"src/**/*.{html,js,jsx,ts,tsx,vue,svelte,md,mdx}"},s&&{type:"text",name:"css",message:"CSS globs (space/comma separated)",initial:"dist/**/*.css"}].filter(Boolean));return{...e,...o.content?{content:i(o.content)}:{},...o.css?{css:i(o.css)}:{}}}async function U(e){let t={...e},s=await ae(e);return s&&s.config?(console.log(d.default.gray(`Loaded config from ${s.filepath}`)),t=G(s.config,t)):t=G({},t),t=await le(t),ce(t),{content:t.content,css:t.css,out:t.out??"",tailwind:t.tailwind??!1,safelist:i(t.safelist),safelistPatterns:i(t.safelistPatterns),rejected:t.rejected??!1,dryRun:t.dryRun??!1,minify:t.minify??!1,backup:t.backup??!1,watch:t.watch??!1,report:t.report??""}}var q=n(require("fast-glob"),1),X=require("purgecss"),k=n(require("fs/promises"),1),h=n(require("path"),1),f=n(require("chalk"),1),Y=n(require("csso"),1);async function fe(e){let t=await(0,q.default)(e,{dot:!0,onlyFiles:!0,unique:!0});if(t.length===0)throw new Error("No content files matched.");return t}async function ue(e,t,s){return(await new X.PurgeCSS().purge({content:t,css:[e],safelist:{standard:s.safelist,deep:s.safelistPatterns,greedy:[]},extractors:s.tailwind?[{extractor:T,extensions:["html","js","jsx","ts","tsx","md","mdx","svelte","vue"]}]:[],rejected:!0}))[0]}async function $(e,t){let s=Date.now(),o={runAt:new Date().toISOString(),files:[],totals:{before:0,after:0,saved:0}},r=await(0,q.default)(e.css,{dot:!0,onlyFiles:!0,unique:!0});if(r.length===0)throw new Error("No CSS files matched.");let a=await fe(e.content),{safelist:c,safelistPatterns:F}=M(e);e.out&&await v(e.out);let l=0,y=0;for(let p of r){let _=await k.default.readFile(p,"utf8"),b=Buffer.byteLength(_,"utf8");l+=b;let m=await ue(p,a,{tailwind:e.tailwind,safelist:c,safelistPatterns:F}),S=m.css||"";e.minify&&(S=Y.minify(S,{restructure:!0}).css);let j=Buffer.byteLength(S,"utf8");y+=j;let P=e.out?h.default.join(e.out,h.default.basename(p)):p;if(e.rejected&&m.rejected&&m.rejected.length){let I=P+".rejected.txt",ee=[...new Set(m.rejected)].sort().join(`
`);e.dryRun||await k.default.writeFile(I,ee,"utf8"),t?.verbose&&console.log(f.default.gray(`Rejected list -> ${I} (${m.rejected.length})`))}e.dryRun||(await v(h.default.dirname(P)),e.backup&&!e.out&&await V(p),await k.default.writeFile(P,S,"utf8"));let J=b-j;console.log(`${f.default.cyan(h.default.basename(p))}: ${g(b)} \u2192 ${g(j)} ${f.default.green(`(-${g(J)})`)}`),o.files.push({file:p,out:P,beforeBytes:b,afterBytes:j,savedBytes:J,rejectedCount:m.rejected?.length??0})}o.totals.before=l,o.totals.after=y,o.totals.saved=l-y;let L=Date.now()-s;return r.length>1?console.log(f.default.bold(`Total: ${g(l)} \u2192 ${g(y)} ${f.default.green(`(-${g(l-y)})`)} in ${L}ms`)):console.log(f.default.gray(`Done in ${L}ms.`)),t?.reportPath&&(await R(h.default.resolve(process.cwd(),t.reportPath),o),console.log(f.default.gray(`Report written \u2192 ${t.reportPath}`))),o}E.program.name("purgecss-cli").description("Remove unused CSS using PurgeCSS (cosmiconfig + interactive prompts)").option("-c, --content <globs...>","Content files (html, js, tsx, vue, svelte, etc.) to scan").option("-s, --css <globs...>","CSS files or globs to prune").option("-o, --out <dir>","Output directory (defaults to overwriting input)","").option("--tailwind","Enable Tailwind-friendly extractor",!1).option("--safelist <items...>","Class/selector names to always keep (space/comma-separated)",[]).option("--safelist-patterns <regex...>","Regex(es) to keep, e.g. '^btn-'",[]).option("--rejected","Write a .rejected.txt file listing removed selectors",!1).option("--dry-run","Don't write files; just report sizes",!1).option("--minify","Minify output CSS (uses CSSO)",!1).option("--backup","Create a one-time *.bak for each CSS file before first overwrite",!1).option("--watch","Watch content/CSS and re-run pruning on change",!1).option("--report <file>","Write a JSON report with stats/rejected (e.g. reports/prune.json)").option("--config <file>","Path to a config file (js/json/yaml)").option("--init","Create a starter purgecss-cli.config.json in cwd",!1).option("--verbose","Extra logging",!1).showHelpAfterError(!0);E.program.parse(process.argv);var u=E.program.opts(),x=(D.default||D)({spinner:"dots"});(async()=>{try{if(u.init){await Q();return}let e=await U(u);if(e.watch){console.log(C.default.cyan("Watch mode enabled."));let t=[...e.content,...e.css],s=Z.default.watch(t,{ignoreInitial:!0}),o=!1,r=!1,a=async(c,F)=>{if(o){r=!0;return}o=!0,x.start(`Change detected (${c}: ${F}). Pruning.`);try{await $(e,{reportPath:u.report,verbose:u.verbose}),x.succeed("Prune complete.")}catch(l){x.fail("Prune failed."),console.error(C.default.red(l?.message||l))}finally{o=!1,r&&(r=!1,a("queued","multiple files"))}};s.on("add",c=>a("add",c)).on("change",c=>a("change",c)).on("unlink",c=>a("unlink",c)),await $(e,{reportPath:u.report,verbose:u.verbose}),console.log(C.default.gray("Watching for changes. Press Ctrl+C to exit."))}else x.start("Pruning CSS."),await $(e,{reportPath:u.report,verbose:u.verbose}),x.succeed("Done.")}catch(e){x.stop(),console.error(C.default.red("Error:"),e?.message||e),process.exit(1)}})();
"use strict";var te=Object.create;var W=Object.defineProperty;var se=Object.getOwnPropertyDescriptor;var re=Object.getOwnPropertyNames;var oe=Object.getPrototypeOf,ne=Object.prototype.hasOwnProperty;var ie=(e,t,s,o)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of re(t))!ne.call(e,r)&&r!==s&&W(e,r,{get:()=>t[r],enumerable:!(o=se(t,r))||o.enumerable});return e};var n=(e,t,s)=>(s=e!=null?te(oe(e)):{},ie(t||!e||!e.__esModule?W(s,"default",{value:e,enumerable:!0}):s,e));var E=require("commander"),C=n(require("chalk"),1),Z=n(require("chokidar"),1),D=n(require("ora"),1);var O=n(require("path"),1),m=n(require("chalk"),1),H=require("cosmiconfig"),K=n(require("prompts"),1);var w=n(require("fs/promises"),1),A=n(require("path"),1),N=n(require("chalk"),1),T=e=>e.match(/[\w-:/%.]+(?<!:)/g)||[];function i(e){return e?(Array.isArray(e)?e.join(" "):String(e)).split(/[,\s]+/).map(s=>s.trim()).filter(Boolean):[]}async function v(e){e&&await w.default.mkdir(e,{recursive:!0}).catch(()=>{})}async function z(e){try{let t=await w.default.readFile(e,"utf8");return JSON.parse(t)}catch{return null}}async function R(e,t){await v(A.default.dirname(e)),await w.default.writeFile(e,JSON.stringify(t,null,2),"utf8")}function M(e){let t=i(e.safelist),o=(i(e.safelistPatterns)||[]).map(r=>{try{if(r.startsWith("/")&&r.lastIndexOf("/")>0){let a=r.lastIndexOf("/");return new RegExp(r.slice(1,a),r.slice(a+1))}return new RegExp(r)}catch{return console.error(N.default.yellow(`Warning: bad regex "${r}" ignored`)),null}}).filter(r=>!!r);return{safelist:t,safelistPatterns:o}}async function V(e){try{let t=e+".bak";await w.default.access(t).then(()=>!0).catch(()=>!1)||await w.default.copyFile(e,t)}catch{}}function g(e){if(e<1024)return`${e} B`;let t=e/1024;return t<1024?`${t.toFixed(1)} KB`:`${(t/1024).toFixed(2)} MB`}async function Q(){let e=O.default.resolve(process.cwd(),"purgecss-cli.config.json");if(await z(e)){console.log(m.default.yellow("purgecss-cli.config.json already exists."));return}await R(e,{content:["**/*.{html,js,jsx,ts,tsx,vue,svelte,md,mdx}"],css:["**/*.css"],out:"pruned/",tailwind:!1,safelist:[],safelistPatterns:["^btn-","^(enter|leave)-"],rejected:!0,minify:!0,dryRun:!1,backup:!1,report:"reports/prune.json",watch:!1}),console.log(m.default.green("Created purgecss-cli.config.json"))}async function ae(e){let t=(0,H.cosmiconfig)("purgecss-cli");if(e.config){let s=O.default.resolve(process.cwd(),e.config);return t.load(s)}return t.search(process.cwd())}function G(e,t){let s={...e,...t||{}};return s.content=i(s.content),s.css=i(s.css),s.safelist=i(s.safelist),s.safelistPatterns=i(s.safelistPatterns),s}function ce(e){(!e.content||e.content.length===0)&&(console.error(m.default.red("No content globs provided.")),process.exit(2)),(!e.css||e.css.length===0)&&(console.error(m.default.red("No CSS globs provided.")),process.exit(2))}async function le(e){let t=!e.content||e.content.length===0,s=!e.css||e.css.length===0;if(!t&&!s)return e;console.log(m.default.cyan("Interactive setup - missing required options."));let o=await(0,K.default)([t&&{type:"text",name:"content",message:"Content globs (space/comma separated)",initial:"**/*.{html,js,jsx,ts,tsx,vue,svelte,md,mdx}"},s&&{type:"text",name:"css",message:"CSS globs (space/comma separated)",initial:"**/*.css"}].filter(Boolean));return{...e,...o.content?{content:i(o.content)}:{},...o.css?{css:i(o.css)}:{}}}async function U(e){let t={...e},s=await ae(e);return s&&s.config?(console.log(m.default.gray(`Loaded config from ${s.filepath}`)),t=G(s.config,t)):t=G({},t),t=await le(t),ce(t),{content:t.content,css:t.css,out:t.out??"",tailwind:t.tailwind??!1,safelist:i(t.safelist),safelistPatterns:i(t.safelistPatterns),rejected:t.rejected??!1,dryRun:t.dryRun??!1,minify:t.minify??!1,backup:t.backup??!1,watch:t.watch??!1,report:t.report??""}}var q=n(require("fast-glob"),1),X=require("purgecss"),k=n(require("fs/promises"),1),h=n(require("path"),1),f=n(require("chalk"),1),Y=n(require("csso"),1);async function fe(e){let t=await(0,q.default)(e,{dot:!0,onlyFiles:!0,unique:!0});if(t.length===0)throw new Error("No content files matched.");return t}async function ue(e,t,s){return(await new X.PurgeCSS().purge({content:t,css:[e],safelist:{standard:s.safelist,deep:s.safelistPatterns,greedy:[]},extractors:s.tailwind?[{extractor:T,extensions:["html","js","jsx","ts","tsx","md","mdx","svelte","vue"]}]:[],rejected:!0}))[0]}async function $(e,t){let s=Date.now(),o={runAt:new Date().toISOString(),files:[],totals:{before:0,after:0,saved:0}},r=await(0,q.default)(e.css,{dot:!0,onlyFiles:!0,unique:!0});if(r.length===0)throw new Error("No CSS files matched.");let a=await fe(e.content),{safelist:c,safelistPatterns:F}=M(e);e.out&&await v(e.out);let l=0,y=0;for(let p of r){let _=await k.default.readFile(p,"utf8"),b=Buffer.byteLength(_,"utf8");l+=b;let d=await ue(p,a,{tailwind:e.tailwind,safelist:c,safelistPatterns:F}),S=d.css||"";e.minify&&(S=Y.minify(S,{restructure:!0}).css);let j=Buffer.byteLength(S,"utf8");y+=j;let P=e.out?h.default.join(e.out,h.default.basename(p)):p;if(e.rejected&&d.rejected&&d.rejected.length){let I=P+".rejected.txt",ee=[...new Set(d.rejected)].sort().join(`
`);e.dryRun||await k.default.writeFile(I,ee,"utf8"),t?.verbose&&console.log(f.default.gray(`Rejected list -> ${I} (${d.rejected.length})`))}e.dryRun||(await v(h.default.dirname(P)),e.backup&&!e.out&&await V(p),await k.default.writeFile(P,S,"utf8"));let J=b-j;console.log(`${f.default.cyan(h.default.basename(p))}: ${g(b)} \u2192 ${g(j)} ${f.default.green(`(-${g(J)})`)}`),o.files.push({file:p,out:P,beforeBytes:b,afterBytes:j,savedBytes:J,rejectedCount:d.rejected?.length??0})}o.totals.before=l,o.totals.after=y,o.totals.saved=l-y;let L=Date.now()-s;return r.length>1?console.log(f.default.bold(`Total: ${g(l)} \u2192 ${g(y)} ${f.default.green(`(-${g(l-y)})`)} in ${L}ms`)):console.log(f.default.gray(`Done in ${L}ms.`)),t?.reportPath&&(await R(h.default.resolve(process.cwd(),t.reportPath),o),console.log(f.default.gray(`Report written \u2192 ${t.reportPath}`))),o}E.program.name("purgecss-cli").description("Remove unused CSS using PurgeCSS (cosmiconfig + interactive prompts)").option("-c, --content <globs...>","Content files (html, js, tsx, vue, svelte, etc.) to scan").option("-s, --css <globs...>","CSS files or globs to prune").option("-o, --out <dir>","Output directory (defaults to overwriting input)","").option("--tailwind","Enable Tailwind-friendly extractor",!1).option("--safelist <items...>","Class/selector names to always keep (space/comma-separated)",[]).option("--safelist-patterns <regex...>","Regex(es) to keep, e.g. '^btn-'",[]).option("--rejected","Write a .rejected.txt file listing removed selectors",!1).option("--dry-run","Don't write files; just report sizes",!1).option("--minify","Minify output CSS (uses CSSO)",!1).option("--backup","Create a one-time *.bak for each CSS file before first overwrite",!1).option("--watch","Watch content/CSS and re-run pruning on change",!1).option("--report <file>","Write a JSON report with stats/rejected (e.g. reports/prune.json)").option("--config <file>","Path to a config file (js/json/yaml)").option("--init","Create a starter purgecss-cli.config.json in cwd",!1).option("--verbose","Extra logging",!1).showHelpAfterError(!0);E.program.parse(process.argv);var u=E.program.opts(),x=(D.default||D)({spinner:"dots"});(async()=>{try{if(u.init){await Q();return}let e=await U(u);if(e.watch){console.log(C.default.cyan("Watch mode enabled."));let t=[...e.content,...e.css],s=Z.default.watch(t,{ignoreInitial:!0}),o=!1,r=!1,a=async(c,F)=>{if(o){r=!0;return}o=!0,x.start(`Change detected (${c}: ${F}). Pruning.`);try{await $(e,{reportPath:u.report,verbose:u.verbose}),x.succeed("Prune complete.")}catch(l){x.fail("Prune failed."),console.error(C.default.red(l?.message||l))}finally{o=!1,r&&(r=!1,a("queued","multiple files"))}};s.on("add",c=>a("add",c)).on("change",c=>a("change",c)).on("unlink",c=>a("unlink",c)),await $(e,{reportPath:u.report,verbose:u.verbose}),console.log(C.default.gray("Watching for changes. Press Ctrl+C to exit."))}else x.start("Pruning CSS."),await $(e,{reportPath:u.report,verbose:u.verbose}),x.succeed("Done.")}catch(e){x.stop(),console.error(C.default.red("Error:"),e?.message||e),process.exit(1)}})();
{
"name": "purgecss-cli",
"version": "0.6.0",
"version": "0.7.0",
"description": "CLI to remove unused CSS (TypeScript, cosmiconfig, interactive prompts, watch, minify, reports)",

@@ -5,0 +5,0 @@ "type": "module",