blast-shield
Version:
Technical debt protection tool for any codebase
7 lines (6 loc) • 8.11 kB
JavaScript
import {logger}from'@blast-shield/logger';import {Command}from'commander';import {loadConfig,applyProfile,debtChecker,profiles}from'@blast-shield/core';import {renderDebtResults,renderDebtSummary}from'@blast-shield/display';import*as m from'fs';import {createHash}from'crypto';import*as v from'path';import*as g from'readline';var C=["defensive","balanced","aggressive","extreme","maintenance","custom"];function w(e,t,r){let o=(t||e.profile||"balanced").toLowerCase();return o==="custom"?r?{...e,profile:"custom"}:(console.warn('\u26A0\uFE0F Warning: profile "custom" requires explicit weights. Falling back to "balanced" profile.'),applyProfile(e,"balanced")):(r&&console.warn(`\u26A0\uFE0F Warning: custom weights are ignored when using named profile "${o}". Use profile "custom" to define custom weights.`),C.includes(o)?applyProfile(e,o):(console.warn(`\u26A0\uFE0F Warning: Unknown profile "${o}". Falling back to "balanced" profile.`),applyProfile(e,"balanced")))}var F={format(e,t){let r=e.details.fileMetrics.filter(n=>!n.failed&&!n.skipped&&Object.values(n.debtCounts).some(i=>i>0)),o={version:"0.0.31",timestamp:new Date().toISOString(),config:{profile:t.profile??"balanced",maxDebtScore:t.maxDebtScore},summary:{debtScore:e.debtScore,maxDebtScore:t.maxDebtScore,exceededThreshold:e.exceededThreshold,totalFiles:e.details.fileMetrics.length,filesWithDebt:r.length,failedFiles:e.failedFiles.length,skippedFiles:e.skippedFiles.length},debtCounts:e.details.debtCounts,files:r.map(n=>({path:n.filePath,debtCounts:n.debtCounts}))};return JSON.stringify(o,null,2)}};var h=[{id:"blast-shield/TODO",name:"TodoComment",defaultConfiguration:{level:"warning"}},{id:"blast-shield/FIXME",name:"FixmeComment",defaultConfiguration:{level:"error"}},{id:"blast-shield/HACK",name:"HackComment",defaultConfiguration:{level:"warning"}},{id:"blast-shield/BUG",name:"BugComment",defaultConfiguration:{level:"error"}},{id:"blast-shield/OPTIMIZE",name:"OptimizeComment",defaultConfiguration:{level:"note"}},{id:"blast-shield/REFACTOR",name:"RefactorComment",defaultConfiguration:{level:"note"}},{id:"blast-shield/NOTE",name:"NoteComment",defaultConfiguration:{level:"note"}}],N=Object.fromEntries(h.map((e,t)=>[e.id,t]));function T(e,t,r){return createHash("sha256").update(`${e}:${t}:${r}`).digest("hex").slice(0,16)}function k(e){return v.relative(process.cwd(),e).replace(/\\/g,"/")}var S={format(e,t){let r=e.details.fileMetrics.filter(i=>!i.failed&&!i.skipped&&Object.values(i.debtCounts).some(s=>s>0)),o=[];for(let i of r)for(let[s,f]of Object.entries(i.debtCounts)){if(f<=0)continue;let u=`blast-shield/${s.toUpperCase()}`,y=N[u],$=h[y].defaultConfiguration.level;o.push({ruleId:u,ruleIndex:y,level:$,message:{text:`Found ${f} ${s.toUpperCase()} comment(s) in ${i.filePath}`},locations:[{physicalLocation:{artifactLocation:{uri:k(i.filePath),uriBaseId:"%SRCROOT%"}}}],partialFingerprints:{"primaryLocationLineHash/v1":T(i.filePath,s,f)}});}return JSON.stringify({$schema:"https://json.schemastore.org/sarif-2.1.0.json",version:"2.1.0",runs:[{tool:{driver:{name:"blast-shield",version:"0.0.31",informationUri:"https://github.com/arnaud-zg/blast-shield",rules:h}},results:o}]},null,2)}};var x={format(e,t){let r=Object.values(e.details.debtCounts).reduce((o,n)=>o+n,0);return renderDebtResults({debtCounts:{...e.details.debtCounts,total:r},totalDebt:r}),renderDebtSummary(e.details.debtCounts,e.debtScore,t.maxDebtScore,t.healthTarget??null,t.profile,null),""}};var U={terminal:x,json:F,sarif:S};function O(e){return U[e]}function R(e,t,r=false){if(t&&m.writeFileSync(t,e,"utf8"),r||!t)try{process.stdout.write(`${e}
`);}catch(o){if(o.code!=="EPIPE")throw o}}function E(e){e.option("-c, --config <path>","Path to config JSON","blast-shield.config.json").option("-s, --src <glob>","Source glob pattern (excludes node_modules, dist, build)").option("-r, --generate-report","Generate detailed technical debt report").option("-p, --profile <profile>","Debt profile (defensive, balanced, aggressive, extreme)").option("--format <name>","Output format: terminal, json, sarif","terminal").option("--output-file <path>","Write output to file instead of stdout").option("--stdout","Emit machine-readable payload to stdout (use with --format json|sarif)").action(async t=>{let r;try{let{config:o,weightsExplicitlyDefined:n}=loadConfig(t.config);V(o,t),r=w(o,t.profile,n);}catch(o){H(o);return}await q(r,t.format,t.outputFile,t.stdout??false);});}function V(e,t){t.src&&(e.src=t.src),t.generateReport!==void 0&&(e.generateReport=t.generateReport);}async function q(e,t="terminal",r,o=false){let n=false;t==="terminal"&&r&&logger.warn("--output-file has no effect with --format terminal");try{let i=await debtChecker(e);if(i.failedFiles.length>0&&logger.warn(`Analysis completed with ${i.failedFiles.length} failed file(s).`),t==="terminal"){let s=Object.values(i.details.debtCounts).reduce((f,u)=>f+u,0);renderDebtResults({debtCounts:{...i.details.debtCounts,total:s},totalDebt:s}),renderDebtSummary(i.details.debtCounts,i.debtScore,e.maxDebtScore,e.healthTarget??null,e.profile,null);}else {let f=O(t).format(i,e);R(f,r,o);}n=i.exceededThreshold;}catch(i){i instanceof Error?logger.error(`Debt checker failed: ${i.message}`):logger.error("Debt checker failed with an unknown error"),process.exit(2);}n&&process.exit(1);}function H(e){e instanceof Error?logger.error(`Error: ${e.message}`):logger.error("An unknown error occurred"),process.exit(2);}function j(e){e.command("init").description("Initialize a new blast-shield configuration file").option("-o, --output <path>","Output path for the config file","blast-shield.config.json").action(async t=>{try{await B(t.output);}catch(r){K(r);}});}async function B(e){if(m.existsSync(e)&&(logger.warn(`Configuration file '${e}' already exists.`),!await G("Do you want to overwrite it?"))){logger.info("Initialization cancelled.");return}let t=await Y();logger.info("Select a debt profile:"),logger.info(` defensive - ${profiles.defensive.description}`),logger.info(` balanced - ${profiles.balanced.description} (recommended)`),logger.info(` aggressive - ${profiles.aggressive.description}`),logger.info(` extreme - ${profiles.extreme.description}`);let r=await X(),o=r,n=profiles[o],i={src:t,profile:o,generateReport:o==="defensive"||o==="balanced",maxDebtScore:n.maxDebtScore??100,healthTarget:n.healthTarget??80,weights:{...n.weights},ignorePatterns:["dist/**","build/**","coverage/**","node_modules/**",".git/**","tmp/**","*.test.*","*.spec.*"],respectGitignore:true},s=v.dirname(e);s!=="."&&!m.existsSync(s)&&m.mkdirSync(s,{recursive:true}),m.writeFileSync(e,JSON.stringify(i,null,2)),logger.success(`Configuration file created at '${e}' with '${r}' profile.`);}async function G(e){let t=g.createInterface({input:process.stdin,output:process.stdout});return new Promise(r=>{t.question(`${e} (y/n): `,o=>{t.close(),r(o.toLowerCase()==="y"||o.toLowerCase()==="yes");});})}async function X(){let e=g.createInterface({input:process.stdin,output:process.stdout}),t=Object.keys(profiles).filter(r=>r!=="custom"&&r!=="maintenance");return new Promise(r=>{e.question(`Enter profile (${t.join("/")}) [balanced]: `,o=>{e.close();let n=o.trim().toLowerCase();!n||!t.includes(n)?r("balanced"):r(n);});})}async function Y(){let e=g.createInterface({input:process.stdin,output:process.stdout});return new Promise(t=>{e.question("Enter the source file glob pattern for your project (e.g. src/**/*.{js,ts}): ",r=>{e.close(),r.trim()?t(r.trim()):(logger.warn("No src pattern provided. You must specify this in your config later."),t(""));});})}function K(e){e instanceof Error?logger.error(`Error: ${e.message}`):logger.error("An unknown error occurred"),process.exit(1);}async function P(){let e=new Command;e.name("blast-shield").description("CLI to protect your codebase from technical debt").version("0.0.31"),E(e),j(e),await e.parseAsync();}P().catch(e=>{logger.error("Unexpected error","INFO",e instanceof Error?e.message:String(e)),process.exit(1);});
// Blast Shield v0.0.31
// Technical Debt Protection Tool
// Copyright © 2026 | Made with ❤️