@kitschpatrol/repo-config 8.10.0 → 8.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import"cosmiconfig";import"cosmiconfig-typescript-loader";import{execa as e}from"execa";import t from"fs-extra";import n from"node:fs";import{availableParallelism as r}from"node:os";import i from"node:path";import{PassThrough as a,Transform as o}from"node:stream";import{packageUp as s,packageUpSync as c}from"package-up";import l from"yargs";import{hideBin as u}from"yargs/helpers";import d from"picocolors";import{stripVTControlCharacters as f}from"node:util";import p from"@pinojs/json-colorizer";import m from"decircular";import h from"deepmerge";import g from"json-stringify-pretty-compact";import{findWorkspaces as _,findWorkspacesRoot as v}from"find-workspaces";import y from"node:fs/promises";import{globby as b}from"globby";import x,{gt as S,minVersion as ee}from"semver";import{readWantedLockfile as te}from"@pnpm/lockfile.fs";var ne=`8.10.0`;const C=[`json`,`machine`,`native`],re=C;function w(e){return typeof e==`string`&&C.includes(e)}function T(){let e=process.env.KSC_FORMAT;return w(e)?e:`native`}function ie(){let{argv:e}=process;for(let[t,n]of e.entries()){let r;n===`--format`?r=e[t+1]:n.startsWith(`--format=`)&&(r=n.slice(9)),w(r)&&(process.env.KSC_FORMAT=r)}}function ae(){let{env:e}=process;return e.NO_COLOR!==void 0&&e.NO_COLOR!==``?!1:e.FORCE_COLOR===void 0?e.TERM!==`dumb`&&void 0:e.FORCE_COLOR!==`0`&&e.FORCE_COLOR!==`false`}function E(e,{ciColor:t=!0}={}){return ae()??(e.isTTY===!0||t&&!!process.env.CI)}function D(){return T()===`json`?process.stderr:process.stdout}function O(){return T()===`machine`?d.createColors(!1):d.createColors(E(D()))}function k(e){if(!e)return{FORCE_COLOR:`0`,NO_COLOR:`1`};let t=process.env.FORCE_COLOR;return t===void 0||t===``?{FORCE_COLOR:`1`}:{}}function oe(e,t){let n=i.isAbsolute(e)?e:i.resolve(t,e),r=i.relative(process.cwd(),n);return r===``?`.`:r}const se=/\s*\n\s*/gv,ce=/\r?\n/v;function le(e){let t=e.message.replaceAll(se,` `),n=e.rule===void 0?e.tool:`${e.tool}/${e.rule}`;return e.file===void 0?`${e.severity}: ${t} [${n}]`:`${e.file}:${e.line??1}:${e.column??1}: ${e.severity}: ${t} [${n}]`}function ue(e,t){let n=(e.file??`￿`).localeCompare(t.file??`￿`);if(n!==0)return n;let r=(e.line??0)-(t.line??0);return r===0?(e.column??0)-(t.column??0):r}function de(e,t){let n=t.toSorted(ue);return{cwd:process.cwd(),diagnostics:n,success:e.every(e=>e.exitCode===0),summary:{errors:n.filter(e=>e.severity===`error`).length,infos:n.filter(e=>e.severity===`info`).length,warnings:n.filter(e=>e.severity===`warning`).length},tools:e,version:1}}function A(e){return f(e).split(ce).map(e=>e.trimEnd()).filter(e=>e.trim().length>0)}function fe(e){return e instanceof Error&&`exitCode`in e&&typeof e.exitCode==`number`}function j(e){return E(process.stdout,{ciColor:!1})?me(e):pe(e)}function pe(e){return g(m(e),{indent:2,replacer(e,t){return typeof t==`function`?t.name:t}})}function me(e){return p(pe(e),{colors:{BRACKET:`gray`}})}const he=(e,t,n)=>{let r=[...e];for(let[i,a]of t.entries())r[i]===void 0?r[i]=n.cloneUnlessOtherwiseSpecified(a,n):n.isMergeableObject(a)?r[i]=M(e[i],a,n):e.includes(a)||r.push(a);return r};function M(e,t,n={arrayMerge:he}){return h(e,t,n)}function N(e){let{tasks:t}=e;return Array.isArray(t)?t:[]}function ge(e,t){let n=[...N(e)];for(let e of N(t)){let t=n.findIndex(t=>t.label!==void 0&&t.label===e.label);t===-1?n.push(e):n[t]=e}return{...e,...t,tasks:n}}function _e(e,t){return e.startsWith(t+i.sep)}function P(){let e=F(),t=/* @__PURE__ */ new Set([e]),n=_();if(n!==null)for(let r of n){let n=i.resolve(r.location);_e(n,e)&&t.add(n)}return[...t]}function F(){let e=c();if(e===void 0)throw Error(`No package.json found.`);return i.dirname(e)}function I(){let e=v();return e===null?F():i.resolve(e.location)}function ve(e){if(e===`workspace-root`)return I();if(e===`package-dir`)return F();if(typeof e==`string`){if(!t.pathExistsSync(e))throw Error(`Custom cwd directory does not exist: ${e}`);return e}return process.cwd()}async function ye(e,t){try{let{default:n}=await import(`prettier`),r=await n.resolveConfig(e),i=await n.format(t,{filepath:e,...r});await y.writeFile(e,i,`utf8`)}catch{console.warn(`Skipped formatting ${e} since Prettier is not installed.`)}}async function L(e){try{await ye(e,await y.readFile(e,`utf8`))}catch{}}const be=/\r?\n/v;function xe(e){let t=``,n=t=>t.filter(t=>{let n=f(t);return n.trim()!==``&&!e(n)}).map(e=>`${e}\n`).join(``);return new o({flush(e){e(void 0,n([t]))},transform(e,r,i){let a=(t+e.toString()).split(be);t=a.pop()??``,i(void 0,n(a))}})}function R(e,t){let n=T()===`native`?e:void 0,r=n===void 0||n===``?``:t===void 0?n:O()[t](n),i=``,a=e=>e.filter(e=>e.trim().length>0).map(e=>r===``?`${e}\n`:`${r} ${e}\n`).join(``);return new o({flush(e){e(void 0,a([i]))},transform(e,t,n){let r=(i+e.toString()).split(be);i=r.pop()??``,n(void 0,a(r))}})}async function Se(e){let t=[];return new Promise((n,r)=>{e.on(`data`,e=>{t.push(e)}),e.on(`error`,e=>{r(e)}),e.on(`end`,()=>{n(Buffer.concat(t).toString(`utf8`))})})}function z(e,t){return t===1?e:e+`s`}async function B(e){return typeof e==`function`?e():e}async function Ce(e,t,n,r,i){let a;if(r.logPrefix===void 0)a=e;else{let t=R(r.logPrefix,r.logColor);t.pipe(e),a=t}i&&a.write(O().bold(`Running: "${r.name}() with Positional arguments: ${String(t)} and Option flags: ${String(n)}"`)+`
2
+ import"cosmiconfig";import"cosmiconfig-typescript-loader";import{execa as e}from"execa";import t from"fs-extra";import n from"node:fs";import{availableParallelism as r}from"node:os";import i from"node:path";import{PassThrough as a,Transform as o}from"node:stream";import{packageUp as s,packageUpSync as c}from"package-up";import l from"yargs";import{hideBin as u}from"yargs/helpers";import d from"picocolors";import{stripVTControlCharacters as f}from"node:util";import p from"@pinojs/json-colorizer";import m from"decircular";import h from"deepmerge";import g from"json-stringify-pretty-compact";import{findWorkspaces as _,findWorkspacesRoot as v}from"find-workspaces";import y from"node:fs/promises";import{globby as b}from"globby";import x,{gt as S,minVersion as ee}from"semver";import{readWantedLockfile as te}from"@pnpm/lockfile.fs";var ne=`8.11.0`;const C=[`json`,`machine`,`native`],re=C;function w(e){return typeof e==`string`&&C.includes(e)}function T(){let e=process.env.KSC_FORMAT;return w(e)?e:`native`}function ie(){let{argv:e}=process;for(let[t,n]of e.entries()){let r;n===`--format`?r=e[t+1]:n.startsWith(`--format=`)&&(r=n.slice(9)),w(r)&&(process.env.KSC_FORMAT=r)}}function ae(){let{env:e}=process;return e.NO_COLOR!==void 0&&e.NO_COLOR!==``?!1:e.FORCE_COLOR===void 0?e.TERM!==`dumb`&&void 0:e.FORCE_COLOR!==`0`&&e.FORCE_COLOR!==`false`}function E(e,{ciColor:t=!0}={}){return ae()??(e.isTTY===!0||t&&!!process.env.CI)}function D(){return T()===`json`?process.stderr:process.stdout}function O(){return T()===`machine`?d.createColors(!1):d.createColors(E(D()))}function k(e){if(!e)return{FORCE_COLOR:`0`,NO_COLOR:`1`};let t=process.env.FORCE_COLOR;return t===void 0||t===``?{FORCE_COLOR:`1`}:{}}function oe(e,t){let n=i.isAbsolute(e)?e:i.resolve(t,e),r=i.relative(process.cwd(),n);return r===``?`.`:r}const se=/\s*\n\s*/gv,ce=/\r?\n/v;function le(e){let t=e.message.replaceAll(se,` `),n=e.rule===void 0?e.tool:`${e.tool}/${e.rule}`;return e.file===void 0?`${e.severity}: ${t} [${n}]`:`${e.file}:${e.line??1}:${e.column??1}: ${e.severity}: ${t} [${n}]`}function ue(e,t){let n=(e.file??`￿`).localeCompare(t.file??`￿`);if(n!==0)return n;let r=(e.line??0)-(t.line??0);return r===0?(e.column??0)-(t.column??0):r}function de(e,t){let n=t.toSorted(ue);return{cwd:process.cwd(),diagnostics:n,success:e.every(e=>e.exitCode===0),summary:{errors:n.filter(e=>e.severity===`error`).length,infos:n.filter(e=>e.severity===`info`).length,warnings:n.filter(e=>e.severity===`warning`).length},tools:e,version:1}}function A(e){return f(e).split(ce).map(e=>e.trimEnd()).filter(e=>e.trim().length>0)}function fe(e){return e instanceof Error&&`exitCode`in e&&typeof e.exitCode==`number`}function j(e){return E(process.stdout,{ciColor:!1})?me(e):pe(e)}function pe(e){return g(m(e),{indent:2,replacer(e,t){return typeof t==`function`?t.name:t}})}function me(e){return p(pe(e),{colors:{BRACKET:`gray`}})}const he=(e,t,n)=>{let r=[...e];for(let[i,a]of t.entries())r[i]===void 0?r[i]=n.cloneUnlessOtherwiseSpecified(a,n):n.isMergeableObject(a)?r[i]=M(e[i],a,n):e.includes(a)||r.push(a);return r};function M(e,t,n={arrayMerge:he}){return h(e,t,n)}function N(e){let{tasks:t}=e;return Array.isArray(t)?t:[]}function ge(e,t){let n=[...N(e)];for(let e of N(t)){let t=n.findIndex(t=>t.label!==void 0&&t.label===e.label);t===-1?n.push(e):n[t]=e}return{...e,...t,tasks:n}}function _e(e,t){return e.startsWith(t+i.sep)}function P(){let e=F(),t=/* @__PURE__ */ new Set([e]),n=_();if(n!==null)for(let r of n){let n=i.resolve(r.location);_e(n,e)&&t.add(n)}return[...t]}function F(){let e=c();if(e===void 0)throw Error(`No package.json found.`);return i.dirname(e)}function I(){let e=v();return e===null?F():i.resolve(e.location)}function ve(e){if(e===`workspace-root`)return I();if(e===`package-dir`)return F();if(typeof e==`string`){if(!t.pathExistsSync(e))throw Error(`Custom cwd directory does not exist: ${e}`);return e}return process.cwd()}async function ye(e,t){try{let{default:n}=await import(`prettier`),r=await n.resolveConfig(e),i=await n.format(t,{filepath:e,...r});await y.writeFile(e,i,`utf8`)}catch{console.warn(`Skipped formatting ${e} since Prettier is not installed.`)}}async function L(e){try{await ye(e,await y.readFile(e,`utf8`))}catch{}}const be=/\r?\n/v;function xe(e){let t=``,n=t=>t.filter(t=>{let n=f(t);return n.trim()!==``&&!e(n)}).map(e=>`${e}\n`).join(``);return new o({flush(e){e(void 0,n([t]))},transform(e,r,i){let a=(t+e.toString()).split(be);t=a.pop()??``,i(void 0,n(a))}})}function R(e,t){let n=T()===`native`?e:void 0,r=n===void 0||n===``?``:t===void 0?n:O()[t](n),i=``,a=e=>e.filter(e=>e.trim().length>0).map(e=>r===``?`${e}\n`:`${r} ${e}\n`).join(``);return new o({flush(e){e(void 0,a([i]))},transform(e,t,n){let r=(i+e.toString()).split(be);i=r.pop()??``,n(void 0,a(r))}})}async function Se(e){let t=[];return new Promise((n,r)=>{e.on(`data`,e=>{t.push(e)}),e.on(`error`,e=>{r(e)}),e.on(`end`,()=>{n(Buffer.concat(t).toString(`utf8`))})})}function z(e,t){return t===1?e:e+`s`}async function B(e){return typeof e==`function`?e():e}async function Ce(e,t,n,r,i){let a;if(r.logPrefix===void 0)a=e;else{let t=R(r.logPrefix,r.logColor);t.pipe(e),a=t}i&&a.write(O().bold(`Running: "${r.name}() with Positional arguments: ${String(t)} and Option flags: ${String(n)}"`)+`
3
3
  `);try{return await r.execute(a,t,n)}catch(e){return console.error(String(e)),1}}function V(e,t,n,r,a){return[...e.subcommands??[],...e.receiveOptionFlags?n:[],...r??[],...a&&e.cache!==void 0?[...e.cache.flags,`--cache-location`,i.join(I(),`node_modules`,`.cache`,`ksc`,e.cache.name)]:[],...e.receivePositionalArguments?t:[],...e.positionalArguments??[]]}async function we(t,n,r,i,o,s){let c=1,l;if(i.logPrefix===void 0)l=t;else{let e=R(i.logPrefix,i.logColor);e.pipe(t),l=e}let u=V(i,n,r,i.optionFlags,o),d=ve(i.cwdOverride);s&&l.write(`Running: "${i.name} ${u.join(` `)}"\n`);let f=i.prettyJsonOutput?new a:void 0,p=f??l,m=k(T()===`native`&&i.prettyJsonOutput!==!0&&E(D()));try{let t=e(i.name,u,{cwd:d,env:m,preferLocal:!0,reject:!1,stdin:`inherit`});if(i.outputFilter){let e=xe(i.outputFilter),n=xe(i.outputFilter);t.stdout.pipe(e).pipe(p,{end:!1}),t.stderr.pipe(n).pipe(p,{end:!1})}else t.stdout.pipe(p,{end:!1}),t.stderr.pipe(p,{end:!1});let n=await t;if(f!==void 0){f.end();let e=await Se(f),t=j(JSON.parse(e)).split(`
4
4
  `);for(let e of t)l.write(`${e}\n`)}c=n.exitCode??1}catch(e){console.error(`${i.name} failed with error:`),console.error(e),fe(e)&&(c=typeof e.exitCode==`number`?e.exitCode:1)}return c}function H(e){return`execute`in e}function U(e){return`kind`in e}async function W(t,n,r,i,a){let o=V(r,t,n,r.collect?.optionFlags??r.optionFlags,i),s=ve(r.cwdOverride);try{let t=e(r.name,o,{cwd:s,env:k(!1),preferLocal:!0,reject:!1,stdin:`inherit`});a!==void 0&&t.stderr.pipe(a,{end:!1});let n=await t;return{cwd:s,exitCode:n.exitCode??1,stderr:n.stderr,stdout:n.stdout}}catch(e){return console.error(`${r.name} failed with error:`),console.error(e),{cwd:s,exitCode:fe(e)&&typeof e.exitCode==`number`?e.exitCode:1,stderr:``,stdout:``}}}function Te(e){return typeof e==`object`&&!!e&&`version`in e&&e.version===1&&`diagnostics`in e&&Array.isArray(e.diagnostics)&&`success`in e&&typeof e.success==`boolean`&&`tools`in e&&Array.isArray(e.tools)}function G(e,t){return e===0&&t.some(({severity:e})=>e===`error`)?1:e}async function Ee(e,t,n,r,i){let o=Date.now(),s=()=>Date.now()-o;if(H(r)){if(r.collect!==void 0)try{let e=await r.collect(t,n),i=G(e.exitCode,e.diagnostics);return{diagnostics:e.diagnostics,exitCode:i,tools:[{durationMs:s(),exitCode:i,name:r.name,unparsed:e.unparsed}]}}catch(e){return{diagnostics:[],exitCode:1,tools:[{durationMs:s(),exitCode:1,name:r.name,unparsed:[String(e)]}]}}let e=``,i=new a;i.on(`data`,t=>{e+=t.toString()});let o;try{o=await r.execute(i,t,n)}catch(t){e+=`\n${String(t)}`,o=1}return{diagnostics:[],exitCode:o,tools:[{durationMs:s(),exitCode:o,name:r.name,unparsed:A(e)}]}}if(U(r))throw Error(`Command groups must be expanded before collection`);if(r.outputFormatAware){let a=await W(t,n,r,i,e);try{let e=JSON.parse(a.stdout);if(!Te(e))throw Error(`Child output is not a lint report`);return{diagnostics:e.diagnostics,exitCode:a.exitCode===0&&e.success&&e.diagnostics.every(({severity:e})=>e!==`error`)?0:1,tools:e.tools}}catch{return{diagnostics:[],exitCode:a.exitCode,tools:[{durationMs:s(),exitCode:a.exitCode,name:r.name,unparsed:A(a.stdout)}]}}}let c=await W(t,n,r,i);if(r.collect!==void 0)try{let{diagnostics:e,unparsed:t}=r.collect.parse(c),n=G(c.exitCode,e);return{diagnostics:e,exitCode:n,tools:[{durationMs:s(),exitCode:n,name:r.name,unparsed:t}]}}catch(e){return{diagnostics:[],exitCode:c.exitCode,tools:[{durationMs:s(),exitCode:c.exitCode,name:r.name,unparsed:[...A(c.stdout),...A(c.stderr),String(e)]}]}}return{diagnostics:[],exitCode:c.exitCode,tools:[{durationMs:s(),exitCode:c.exitCode,name:r.name,unparsed:[...A(c.stdout),...A(c.stderr)]}]}}const De=/^ksc-/v;function K(e){return e.replace(De,``)}function q(e){return e===void 0||e.length===0?[]:e.flatMap(e=>e.split(`,`)).map(e=>K(e.trim()))}function J(e){return e.option(`skip`,{array:!0,describe:`Tool names to skip (with or without "ksc-" prefix).`,type:`string`})}function Oe(e){return e.option(`format`,{choices:re,default:`native`,describe:`Output format: "native" streams each tool's own output, "machine" prints one parseable line per issue for editor problem matchers, "json" prints an aggregate report.`})}function ke(e){return e.option(`cache`,{boolean:!0,default:!0,describe:`Use tool-native caches stored below node_modules/.cache/ksc at the workspace root. Disable with --no-cache.`})}function Ae(e,t){return t===`native`||t===`machine`&&!H(e)&&!U(e)&&e.outputFormatAware===!0}function je(e,t){for(let n of t.diagnostics)e.write(`${le(n)}\n`);for(let n of t.tools)for(let t of n.unparsed)e.write(`${t}\n`)}function Me(){return Math.min(4,r())}var Ne=class{concurrency;active=0;queue=[];constructor(e){this.concurrency=e}async run(e){this.active>=this.concurrency&&await new Promise(e=>{this.queue.push(e)}),this.active+=1;try{return await e()}finally{--this.active,this.queue.shift()?.()}}};async function Pe(e){let n=e.cache??!1,r=e.concurrency??Me();if(!Number.isInteger(r)||r<1)throw Error(`Concurrency must be a positive integer.`);return n&&await t.ensureDir(i.join(I(),`node_modules`,`.cache`,`ksc`)),{cache:n,scheduler:new Ne(r)}}const Fe=/[*?\[\]\{\}]/v;function Y(e,t){return t===void 0?e:e.filter(e=>Fe.test(e)||n.statSync(e,{throwIfNoEntry:!1})?.isDirectory()===!0||t.includes(i.extname(e).slice(1).toLowerCase()))}function Ie(e,t){return e.length===0||Y(e,t).length>0}function Le(e,t){return e.positionalArgumentMode===`none`?[]:t.length>0?Y(t,e.positionalArgumentExtensions):e.positionalArgumentDefault===void 0?[]:[e.positionalArgumentDefault]}function Re(e,t){if(t.logPrefix===void 0)return e;let n=R(t.logPrefix,t.logColor);return n.pipe(e),n}function ze(e){return U(e)?`${e.name} ${e.subcommand}`:!H(e)&&e.subcommands!==void 0?[e.name,...e.subcommands].join(` `):e.name}async function Be(e,t){if(U(e)){let n=Re(t.logStream,e);try{let r=await B(e.commands);if(t.nativeVerbose){let n=[e.subcommand,...t.positionalArguments].join(` `),i=e.showResolvedCommands?` (${r.map(e=>ze(e)).join(`, `)})`:``;t.logStream.write(`Running: "${e.name} ${n}"${i}\n`)}let i=await Ve(r,{...t,logStream:n,nativeVerbose:e.verbose,positionalArguments:Le(e,t.positionalArguments)},e.parallel===!0);return{diagnostics:i.flatMap(({diagnostics:e})=>e),exitCode:+!i.every(({exitCode:e})=>e===0),machineOutcomes:i.flatMap(({machineOutcomes:e})=>e),name:e.name,tools:i.flatMap(({tools:e})=>e)}}catch(r){let i=String(r),a={durationMs:0,exitCode:1,name:e.name,unparsed:[i]};return t.format===`native`&&n.write(`${i}\n`),{diagnostics:[],exitCode:1,machineOutcomes:[{diagnostics:[],exitCode:1,tools:[a]}],name:e.name,tools:[a]}}}return t.scheduler.run(async()=>{if(Ae(e,t.format))return{diagnostics:[],exitCode:await(H(e)?Ce(t.logStream,t.positionalArguments,t.optionFlags,e,t.nativeVerbose):we(t.logStream,t.positionalArguments,t.optionFlags,e,t.cache,t.nativeVerbose)),machineOutcomes:[],name:e.name,tools:[]};let n=await Ee(t.logStream,t.positionalArguments,t.optionFlags,e,t.cache);return{diagnostics:n.diagnostics,exitCode:n.exitCode,machineOutcomes:[n],name:e.name,tools:n.tools}})}async function Ve(e,t,n){if(!n||t.scheduler.concurrency===1){let n=[];for(let r of e)n.push(await Be(r,t));return n}await Promise.resolve();let r=/* @__PURE__ */ new Map;for(let[t,n]of e.entries()){let e=n.stage??0,i=r.get(e)??[];i.push({command:n,index:t}),r.set(e,i)}let i=Array.from({length:e.length}),a=r.keys().toArray().toSorted((e,t)=>e-t);for(let e of a){let n=r.get(e)??[],a=await Promise.all(n.map(async({command:e,index:n})=>({index:n,outcome:await Be(e,t)})));for(let{index:e,outcome:t}of a)i[e]=t}return i.filter(e=>e!==void 0)}function He(e,t,n,r,i){let a=[],o=[],s=Ie(r,i);for(let e of t){let t=!U(e)||Ie(r,e.positionalArgumentExtensions);!s||!t||n.includes(K(e.name))?o.push(e):a.push(e)}if(n.length>0){let r=new Set(o.map(e=>K(e.name))),i=n.filter(e=>!r.has(e));if(i.length>0){let n=t.map(e=>K(e.name)).join(`, `);e.write(`⚠️ ${O().yellow(`Unrecognized --skip ${z(`value`,i.length)}: ${i.join(`, `)}. Available: ${n}`)}\n`)}}return{commandsToRun:a,skippedCommands:o}}async function X(e,t,n,r,i,a,o,s=`native`){let c=typeof s==`string`?{}:s,l=typeof s==`string`?s:c.format??`native`,{commandsToRun:u,skippedCommands:d}=He(e,r,o??[],t,c.positionalArgumentExtensions),f=l===`native`&&i,{cache:p,scheduler:m}=await Pe(c),h=await Ve(u,{cache:p,format:l,logStream:e,nativeVerbose:f,optionFlags:n,positionalArguments:Y(t,c.positionalArgumentExtensions),scheduler:m},c.parallel===!0),g=h.map(({exitCode:e,name:t})=>({exitCode:e,name:t})),_=h.flatMap(({tools:e})=>e),v=h.flatMap(({diagnostics:e})=>e);if(l===`machine`)for(let t of h)for(let n of t.machineOutcomes)je(e,n);let y=r.length,b=O();if(l===`native`&&d.length>0){let t=d.map(({name:e})=>e),n=b.bold(`${t.length} / ${y} ${z(`Command`,t.length)} Skipped:`);e.write(`⏭️ ${b.dim(n)} ${b.dim(t.join(`, `))}\n`)}if(l===`native`&&a){let t=g.filter(({exitCode:e})=>e===0).map(({name:e})=>e),n=g.filter(({exitCode:e})=>e!==0).map(({name:e})=>e);if(t.length>0){let n=b.bold(`${t.length} / ${y} ${z(`Command`,t.length)} Succeeded:`);e.write(`✅ ${b.green(n)} ${b.green(t.join(`, `))}\n`)}if(n.length>0){let t=b.bold(`${n.length} / ${y} ${z(`Command`,n.length)} Failed:`);e.write(`❌ ${b.red(t)} ${b.red(n.join(`, `))}\n`)}}return{exitCode:+!g.every(({exitCode:e})=>e===0),report:l===`json`?de(_,v):void 0}}async function Ue(e){let t=await s({cwd:e});return t===void 0?void 0:i.join(i.dirname(t),`init`)}async function We(e,r,a,o){let c=await s();if(c===void 0)throw Error("The `init` command must be used in a directory with a package.json file");let l=await Ue(import.meta.dirname);if(l===void 0)return e.write(`Error: Could not locate the package containing this CLI.
5
5
  `),1;let u=i.dirname(c),d=(r===`file`||r===`package`)&&a!==void 0&&o!==void 0;try{if(d){let n=Object.keys(o)[0],i=t.readJsonSync(c);if(r===`package`){e.write(`Merging: \nPackage config key "${n}" → "${u}" (Because --location is set to "package")\n`);let r=M(i,o);t.writeJSONSync(c,r,{spaces:` `}),await L(c)}else n!==void 0&&Object.keys(i).includes(n)&&(e.write(`Deleting: \nPackage config key "${n}" in "${u}" (Because --location is set to "file")\n`),delete i[n],t.writeJSONSync(c,i,{spaces:` `}),await L(c))}if(!await t.pathExists(l))return 0;if((await t.readdir(l)).length===0)return e.write(`Source directory "${l}" is empty.\n`),0;e.write(`Adding initial configuration files from:\n"${l}" → "${u}"\n`),await t.copy(l,u,{async filter(o,s){let c=n.statSync(o).isFile(),l=n.existsSync(s);if(c){if(d&&r===`package`&&o.includes(a))return l?(e.write(`Deleting: \n"${o}" → "${s}" (Because --location is set to "package")\n`),t.removeSync(s)):e.write(`Skipping: \n"${o}" → "${s}" (Because --location is set to "package")\n`),!1;if(l&&(s.includes(`.vscode/`)||s.includes(`package.json`))&&i.extname(s)===`.json`){e.write(`Merging: \n"${o}" → "${s}"\n`);let n=t.readJSONSync(o),r=t.readJSONSync(s),i=s.endsWith(`.vscode/tasks.json`)?ge(r,n):M(r,n);return t.writeJSONSync(s,i,{spaces:` `}),await L(s),!1}return l?(e.write(`Overwriting: \n"${o}" → "${s}"\n`),await L(s),!0):(e.write(`Copying: \n"${o}" → "${s}"\n`),await L(s),!0)}return!0},overwrite:!0})}catch(e){return console.error(String(e)),1}return 0}async function Ge(e){let{commands:{fix:t,init:n,lint:r,printConfig:i},description:a,logColor:o,logPrefix:s,name:c,showSummary:d,verbose:f}=e;ie();let p=R(s,o);p.pipe(D());let m=l(u(process.argv)).scriptName(c).usage(`$0 <command>`,a);n!==void 0&&m.command({builder(e){let t=n.locationOptionFlag?e.option(`location`,{choices:[`file`,`package`],default:`file`,describe:`Where to store the configuration.`,type:`string`}):e;return d?J(t):t},command:`init`,describe:n.description??`Initialize by copying starter config files to your project root${n.locationOptionFlag?` or to your package.json file.`:`.`}`,async handler(e){let t=n.locationOptionFlag?e.location:void 0,r=q(e.skip),{exitCode:i}=await X(p,[],t===void 0?[]:[`--location`,t],[{async execute(e,t,r){return We(e,r.at(1),n.configFile,n.configPackageJson)},name:`copyAndMergeInitFiles`},...n.commands??[]],void 0,void 0,r);process.exitCode=i}}),r!==void 0&&m.command({builder(e){let t=ke(Oe(r.positionalArgumentMode===`none`?e:e.positional(`files`,{array:!0,...r.positionalArgumentDefault!==void 0&&{default:r.positionalArgumentDefault},describe:`Files or glob pattern to lint.`,type:`string`})));return d?J(t):t},command:r.positionalArgumentMode===`none`?`lint`:r.positionalArgumentMode===`optional`?`lint [files..]`:`lint <files..>`,describe:r.description,async handler(e){let t=e.files??[],n=q(e.skip),i=T(),{exitCode:a,report:o}=await X(p,t,[],await B(r.commands),f,d,n,{cache:e.cache,format:i,parallel:r.parallel,positionalArgumentExtensions:r.positionalArgumentExtensions});o!==void 0&&process.stdout.write(`${j(o)}\n`),process.exitCode=a}}),t!==void 0&&m.command({builder(e){let n=ke(Oe(t.positionalArgumentMode===`none`?e:e.positional(`files`,{array:!0,...t.positionalArgumentDefault!==void 0&&{default:t.positionalArgumentDefault},describe:`Files or glob pattern to fix.`,type:`string`})));return d?J(n):n},command:t.positionalArgumentMode===`none`?`fix`:t.positionalArgumentMode===`optional`?`fix [files..]`:`fix <files..>`,describe:t.description,async handler(e){let n=e.files??[],r=q(e.skip),i=T(),{exitCode:a,report:o}=await X(p,n,[],await B(t.commands),void 0,void 0,r,{cache:e.cache,format:i,parallel:t.parallel,positionalArgumentExtensions:t.positionalArgumentExtensions});o!==void 0&&process.stdout.write(`${j(o)}\n`),process.exitCode=a}}),i!==void 0&&m.command({builder(e){let t=i.positionalArgumentMode===`none`?e:e.positional(`file`,{...i.positionalArgumentDefault!==void 0&&{default:i.positionalArgumentDefault},describe:`File or glob pattern to print configuration for.`,type:`string`});return d?J(t):t},command:i.positionalArgumentMode===`none`?`print-config`:i.positionalArgumentMode===`optional`?`print-config [file]`:`print-config <file>`,describe:i.description,async handler(e){let t=e.file??void 0,n=t===void 0?[]:[t],r=q(e.skip),{exitCode:a}=await X(p,n,[],await B(i.commands),f,d,r);process.exitCode=a}}),m.alias(`h`,`help`),m.version(ne),m.alias(`v`,`version`),m.help(),m.wrap(process.stdout.isTTY?Math.min(120,m.terminalWidth()):0),await m.parseAsync()}const Z={fileRun:`Matches files below the current working directory by default.`,monorepoRun:`In a monorepo, it will also run in all packages below the current working directory.`,monorepoSearch:`Searches up to the root of a monorepo if necessary.`,multiArgumentCaveat:`Will use file arguments / globs where possible if provided, but some of the invoked tools only operate at the package scope.`,multiOptionCaveat:`Will use option flags where possible if provided, but some of the invoked tools will ignore them.`,optionalFileRun:`Package-scoped by default, file-scoped if a file argument is provided.`,packageRun:`Package-scoped.`,packageSearch:`Package-scoped.`},Ke=[/Version 2\.0,?\s*January 2004/gv,/Version 1,?\s*February 1989/gv,/Version 2,?\s*June 1991/gv,/Version 3,?\s*29 June 2007/gv,/Version 2\.1,?\s*February 1999/gv,/Version 3,?\s*19 November 2007/gv,/Version 1\.0\s*-\s*August 17th,?\s*2003/gv,/Version 1\.1,?\s*March 2000/gv,/Version 1\.2,?\s*November 2002/gv,/Version 1\.3,?\s*3 November 2008/gv,/Version 1\.0\s*-\s*22 November 2005/gv,/Version 1\.1\s*-\s*26 February 2007/gv,/Version 2,?\s*December 2004/gv,/20 December 1996/gv,/December 20,?\s*1996/gv,/11 March 1996/gv,/28 March 2007/gv,/November 1,?\s*2008/gv,/August 1,?\s*2009/gv,/1 April 19\d{2}/gv,/Copyright \(c\) 2000-2006,?\s*The Perl Foundation/giv,/Copyright \(C\) (?:\d{4},?\s*)+Free Software Foundation/gv],qe=[`node_modules/**`,`test/**`];function Je(e){let t=e;for(let e of Ke)e.lastIndex=0,t=t.replace(e,e=>e.replaceAll(/\d/gv,`X`));return t}const Ye=/(\d{4})\s*-\s*(\d{4})/v,Xe=/(\d{4})/v;function Ze(e,t){let n=Je(e),r=Ye,i=r.exec(n);if(i){let[,n,a]=i;if(Number(a)!==t){let a=`${n}-${t}`;return e.slice(0,i.index)+e.slice(i.index).replace(r,()=>a)}return e}let a=Xe,o=a.exec(n);if(o){let[,n]=o;if(Number(n)!==t){let r=`${n}-${t}`;return e.slice(0,o.index)+e.slice(o.index).replace(a,()=>r)}return e}return e}async function Qe(e){let t=(/* @__PURE__ */ new Date()).getFullYear(),n=[`**/license.txt`,`**/license`,...qe.map(e=>`!${e}`)],r=await b(n,{caseSensitiveMatch:!1,cwd:F(),followSymbolicLinks:!1,gitignore:!0}),i=[];for(let n of r)try{let r=await y.readFile(n,`utf8`),a=Ze(r,t);a!==r&&(i.push(n),e&&await y.writeFile(n,a,`utf8`))}catch(e){console.error(`Failed to process ${n}:`,e)}return i}async function $e(e,t=!1){let n=await Qe(t);if(n.length>0){e.write(`${t?`Fixed`:`Found`} ${n.length} license ${z(`file`,n.length)} with outdated copyright year:\n`);for(let t of n)e.write(` - ${t}\n`);return+!t}return 0}async function et(e){return $e(e,!1)}async function tt(){let e=await Qe(!1);return{diagnostics:e.map(e=>({file:oe(e,F()),message:`License file has an outdated copyright year`,severity:`error`,tool:`copyright-year`})),exitCode:+(e.length>0),unparsed:[]}}async function nt(e){return $e(e,!0)}const Q=`pnpm-lock.yaml`;function rt(e){let n=I(),r=i.resolve(e);for(;;){if(t.existsSync(i.join(r,Q)))return r;if(r===n)break;let e=i.dirname(r);if(e===r)break;r=e}}function it(e,t,n){let r=`${e}@${t}`;return Object.hasOwn(n,r)?r:Object.hasOwn(n,t)?t:void 0}function at(e,t){return e===void 0||e===``?t===void 0||t===``?void 0:t:t===void 0||t===``||S(e,t)?e:t}async function ot(e){let t=rt(e);if(t===void 0||t===``)throw Error(`${Q} not found at or above "${e}".`);let n=i.join(t,Q),r=await te(t,{ignoreIncompatible:!1});if(!r?.importers)throw Error(`Lockfile at "${n}" is unreadable or missing importers.`);let a=r.packages??{},o,s,c={},l={};function u(e,t){let n,r=/* @__PURE__ */ new Set;function i(e,t){if(t.startsWith(`link:`))return;let o=it(e,t,a);if(o===void 0||o===``||r.has(o))return;r.add(o);let s=a[o];if(s===void 0)return;let c=s.engines?.node;if(c!==void 0&&c!==``){let e=ee(c)?.version;e!==void 0&&e!==``&&(n===void 0||n===``||S(e,n))&&(n=e)}if(s.dependencies)for(let[e,t]of Object.entries(s.dependencies))i(e,t)}return i(e,t),n}function d(e,t){for(let[n,r]of Object.entries(e)){let e=u(n,typeof r==`string`?r:r.version);if(e===void 0||e===``)continue;let i=t?l:c;i[e]??=/* @__PURE__ */ new Set,i[e].add(n);let a=t?s:o;(a===void 0||a===``||S(e,a))&&(t?s=e:o=e)}}let f=i.relative(t,e),p=f===``?`.`:f,m=r.importers[p];m!==void 0&&(m.dependencies&&d(m.dependencies,!1),m.devDependencies&&d(m.devDependencies,!0));let h=at(o,s),g=h===void 0?void 0:`>=${h}`;return{dependencies:o!==void 0&&o!==``?{topLevelCauses:[...c[o]??[]],version:`>=${o}`}:void 0,devDependencies:s!==void 0&&s!==``?{topLevelCauses:[...l[s]??[]],version:`>=${s}`}:void 0,lockfile:n,version:g}}function st(e){let t=e.devEngines;if(t?.runtime)return(Array.isArray(t.runtime)?t.runtime:[t.runtime]).find(e=>e.name===`node`)?.version}function ct(e,t){let n=e.devEngines??{};if(n.runtime){if(Array.isArray(n.runtime)){let e=n.runtime.find(e=>e.name===`node`);e===void 0?n.runtime.push({name:`node`,version:t}):e.version=t}else n.runtime.name===`node`?n.runtime.version=t:n.runtime=[n.runtime,{name:`node`,version:t}]}else n.runtime={name:`node`,version:t};e.devEngines=n}function lt(e){let t=e.devEngines;if(t?.runtime){if(Array.isArray(t.runtime)){t.runtime=t.runtime.filter(e=>e.name!==`node`);let[e]=t.runtime;e===void 0?delete t.runtime:t.runtime.length===1&&(t.runtime=e)}else t.runtime.name===`node`&&delete t.runtime;Object.keys(t).length===0&&delete e.devEngines}}function $(e){return e.length===0?``:` (from ${e.join(`, `)})`}async function ut(e,n){let r=i.join(n,`package.json`);if(!t.existsSync(r))return{issues:[],packageJsonPath:r};let a=t.readJsonSync(r),o=a.engines?.node,s=st(a),c=await ot(n),l=c.dependencies?.version,u=c.dependencies?.topLevelCauses??[],d=c.version,f=c.devDependencies?.topLevelCauses??[],p=[];if(l!==void 0){if(o===void 0){if(p.push(`Missing engines.node — suggest setting to "${l}"${$(u)}`),e){let e=a.engines??{};e.node=l,a.engines=e}}else{let t=x.minVersion(o),n=x.minVersion(l);t&&n&&x.lt(t,n)&&(p.push(`engines.node is "${o}" but production dependencies require at least "${l}"${$(u)}`),e&&(a.engines.node=l))}}let m=(()=>{if(l!==void 0&&l!==``&&o!==void 0&&o!==``){let e=x.minVersion(l),t=x.minVersion(o);if(e&&t)return x.gt(e,t)?l:o}return l??o})(),h=m!==void 0&&m!==``?x.minVersion(m):void 0,g=d!==void 0&&d!==``?x.minVersion(d):void 0,_=g&&h&&x.gt(g,h);if(d!==void 0&&_){if(s===void 0)p.push(`devDependencies require a higher Node.js minimum (${d}) than engines.node (${m??`none`}) — suggest adding devEngines.runtime for node with version "${d}"${$(f)}`),e&&ct(a,d);else{let t=x.minVersion(s);t&&x.lt(t,g)&&(p.push(`devEngines.runtime.version for node is "${s}" but all dependencies require at least "${d}"${$(f)}`),e&&ct(a,d))}}else if(s!==void 0){let t=x.minVersion(s);t&&h&&x.lte(t,h)&&(p.push(`devEngines.runtime.version for node is redundant (engines.node already covers the requirement) — suggest removing`),e&&lt(a))}return e&&p.length>0&&(t.writeJsonSync(r,a,{spaces:` `}),await L(r)),{issues:p,packageJsonPath:r}}async function dt(e,t){let n=P(),r=0;for(let i of n){let{issues:n,packageJsonPath:a}=await ut(t,i);if(n.length!==0){e.write(`${t?`Fixed`:`Found`} ${n.length} Node.js version ${z(`issue`,n.length)} in ${a}:\n`);for(let t of n)e.write(` - ${t}\n`);t||(r=1)}}return r}async function ft(){let e=P(),t=[];for(let n of e){let{issues:e,packageJsonPath:r}=await ut(!1,n);for(let n of e)t.push({file:oe(r,process.cwd()),message:n,severity:`error`,tool:`node-version`})}return{diagnostics:t,exitCode:+(t.length>0),unparsed:[]}}async function pt(e){let t=j(await ot(F())).split(`
@@ -1,148 +1,113 @@
1
1
  ---
2
2
  name: ksc
3
- description: >-
4
- Rules and conventions for projects using @kitschpatrol/shared-config. Covers
5
- ESLint, Prettier, TypeScript, CSpell, Knip, Stylelint, and Perfectionist configuration to avoid
6
- lint failures. Relevant when writing or modifying TypeScript, JavaScript, CSS,
7
- Markdown, JSON, YAML, or configuration files.
8
- user-invocable: true
3
+ description: Code conventions and lint workflow for projects that use @kitschpatrol/shared-config and its ksc CLI, which wraps ESLint, Prettier, TypeScript, Stylelint, CSpell, Knip, remark, and mdat. Use when writing or editing TypeScript, JavaScript, CSS, Markdown, JSON, or YAML in a project that depends on @kitschpatrol/shared-config, when running or fixing `ksc lint` / `ksc fix` failures, when writing eslint-disable comments, or when editing the tool config files it manages.
4
+ license: MIT
9
5
  ---
10
6
 
11
- # @kitschpatrol/shared-config Rules
12
-
13
- This project uses `@kitschpatrol/shared-config` for linting, formatting, and type checking. All tools are orchestrated via the `ksc` CLI. Always use **pnpm** — never npm, npx, or yarn. After writing code, run `pnpm ksc fix` to auto-fix formatting, sorting, and other fixable issues.
14
-
15
- ## Critical Type Rules
16
-
17
- These cause the most lint failures. Follow them strictly:
18
-
19
- - **No `null`** — Use `undefined` instead. Both the value `null` and the type `null` are banned (`unicorn/no-null`, `ts/no-restricted-types`). When interfacing with APIs that return `null`, convert immediately: `apiResult() ?? undefined`
20
- - **No `any`** — Use `unknown` instead (`ts/no-explicit-any`). Also enforced: `ts/no-unsafe-assignment`, `ts/no-unsafe-call`, `ts/no-unsafe-member-access`, `ts/no-unsafe-return`, `ts/no-unsafe-type-assertion`
21
- - **No `Buffer`** — Use `Uint8Array` instead
22
- - **No `object` type** — Use `Record<string, unknown>` instead
23
- - **Handle all promises** — `ts/no-floating-promises` is an error. Async functions returning promises must use `await`. Use `void someAsyncFunction()` for intentionally fire-and-forget calls. Functions that return a promise must be declared `async` (`ts/promise-function-async`)
24
- - **Strict template expressions** — Only strings and numbers are allowed in template literals (`ts/restrict-template-expressions` with `allowNumber: true`). Booleans, objects, nullish values must be converted explicitly: `` `flag: ${String(isReady)}` ``
25
- - **Strict plus operands** — Cannot implicitly add string + number (`ts/restrict-plus-operands`)
26
- - **Prefer nullish coalescing** — Use `??` not `||` for nullish checks (`ts/prefer-nullish-coalescing`). Use `?.` optional chains (`ts/prefer-optional-chain`)
27
- - **Check indexed access** — `noUncheckedIndexedAccess` is enabled: `array[i]` and `record[key]` are typed `T | undefined`. Narrow before use
28
-
29
- ## Naming Conventions
30
-
31
- - **Default**: `camelCase` for variables, functions, parameters
32
- - **Types**: `StrictPascalCase` for types, interfaces, enums, classes, type parameters
33
- - **Constants**: `const` variables may be `UPPER_CASE` or `camelCase`
34
- - **Booleans**: Variables typed as `boolean` MUST be prefixed with `is`, `has`, `can`, `should`, `will`, or `did` (e.g., `isReady`, `hasPermission`)
35
- - **Unused variables/parameters**: MUST have a leading underscore prefix (`_unused`, `_error`)
36
- - **Imports**: `camelCase` or `StrictPascalCase` (for component imports)
37
- - **Destructured variables**: `camelCase` or `StrictPascalCase` allowed
38
- - **File names**: `kebab-case` enforced by `unicorn/filename-case`
39
- - **Prefer `type` over `interface`**: `ts/consistent-type-definitions` enforces `type Foo = { ... }` not `interface Foo { ... }`
40
- - **Type imports/exports**: Use top-level style: `import type { Foo } from './bar'` and `export type { Foo }` — inline specifiers like `import { type Foo }` are flagged (`import/consistent-type-specifier-style` with `prefer-top-level`)
41
- - **Array types**: Simple types use `string[]`, complex types use `Array<string | number>` (`ts/array-type` with `array-simple`)
42
-
43
- ## Sorting — Do Not Manually Sort
44
-
45
- The **Perfectionist** plugin auto-sorts imports, object keys, type members, union/intersection types, and interfaces. All these rules have auto-fix. **Do not waste time manually sorting** — write code in any logical order, then run `pnpm ksc-eslint fix` or `pnpm ksc fix`.
46
-
47
- Perfectionist sorts alphabetically (natural, ascending) with domain-specific exceptions that preserve semantic grouping:
48
-
49
- - Coordinates: `x, y, z, w` and `x1, y1, x2, y2`
50
- - Dimensions: `width, height`
51
- - Colors: `r, g, b, a` / `red, green, blue, alpha` / `h, s, l, a`
52
- - Ranges: `min, max` / `start, end` / `open, close` / `pre, post`
53
-
54
- These patterns are matched by exact name, prefix, and suffix. If an object's keys all match one of these patterns, the domain order takes precedence over alphabetical.
55
-
56
- Import sorting: alphabetical with no newlines between groups. Internal modules (`~/`, `@/`, `$`) are grouped together.
57
-
58
- ## Code Style
59
-
60
- Formatting is handled by Prettier — do not fight it:
61
-
62
- - No semicolons
63
- - Single quotes
64
- - Tabs for indentation (except `.md`, `.mdx`, `.yml` which use spaces)
65
- - Trailing commas always
66
- - Print width: 100 characters
67
- - LF line endings
68
-
69
- Additional ESLint style rules:
70
-
71
- - **Always use braces** — `if`/`else`/loops require braces even for single statements (`curly`)
72
- - **Blank line after blocks** — A blank line is required between a block-like statement (`if`, `for`, `while`, `try`, function declarations, etc.) and the next statement (`stylistic/padding-line-between-statements`, auto-fixed)
73
- - **Comments must be capitalized** (`capitalized-comments`) — exception for commented-out code patterns like `if`, `else`, `const`, `import`, etc.
74
- - **No `Array.reduce()`** — Use `for...of` loops instead (`unicorn/no-array-reduce`)
75
- - **No `Array.forEach()`** — Use `for...of` loops instead (`unicorn/no-array-for-each`)
76
- - **Use `node:` protocol** for Node.js built-in imports: `import fs from 'node:fs'` (`unicorn/prefer-node-protocol`)
77
- - **No abbreviations** (`unicorn/name-replacements`) except these allowed ones: `i`, `j`, `acc`, `arg`, `args`, `db`, `dev`, `doc`, `docs`, `env`, `fn`, `lib`, `param`, `params`, `pkg`, `prop`, `props`, `ref`, `refs`, `sep`, `src`, `temp`, `util`, `utils`
78
- - **Explicit length checks** — Write `array.length > 0` not `array.length` (`unicorn/explicit-length-check`)
79
- - **Catch variable naming** — Use `error` not `err` or `e` (`unicorn/catch-error-name`)
80
-
81
- ## JSDoc
82
-
83
- - **Public exports require JSDoc** with a body description (`jsdoc/require-jsdoc` with `publicOnly: true`, `jsdoc/require-description`)
84
- - **No types in JSDoc** — TypeScript provides the types. Do not write `@param {string} name`, just `@param name` (`jsdoc/no-types`)
85
- - In `.ts` files, `@param` and `@returns` tags are not required — the TypeScript signature is sufficient
86
-
87
- ## ESLint Plugin Name Mappings
88
-
89
- ESLint plugins are renamed in this config. Use these short names in `eslint-disable` comments:
90
-
91
- - `@typescript-eslint` -> `ts` (e.g., `// eslint-disable-next-line ts/no-explicit-any`)
92
- - `import-x` -> `import`
93
- - `n` -> `node`
94
- - `vitest` -> `test`
95
- - `@eslint-community/eslint-comments` -> `eslint-comments`
96
- - `@eslint-react` -> `react`
97
- - `@stylistic` -> `stylistic`
98
- - `jsonc` -> `json`
99
- - `yml` -> `yaml`
100
- - `@html-eslint` -> `html`
101
- - `package-json` -> `json-package`
102
-
103
- ## Project Patterns and Tooling
104
-
105
- **Package manager**: Always use `pnpm`. Never use `npm`, `npx`, or `yarn`. Dependencies are already available locally — use `pnpm exec` to run binaries, `pnpm add` to install packages, `pnpm dlx` instead of `npx`.
106
-
107
- **Configuration files**: Each tool uses a typed factory function for configuration:
108
-
109
- - `eslint.config.ts` — `eslintConfig({ type: 'lib' })` or `eslintConfig({ type: 'app' })`
110
- - Library projects use `type: 'lib'`, application projects use `type: 'app'`
111
- - Framework support via flags: `eslintConfig({ astro: true })`, `eslintConfig({ svelte: true })`, `eslintConfig({ react: true })`
112
- - Frameworks are auto-detected from installed packages, but can be explicitly enabled
113
- - `prettier.config.ts` — `prettierConfig()` with optional overrides
114
- - `cspell.config.ts` — `cspellConfig()` with optional custom words
115
- - `knip.config.ts` — `knipConfig()` with optional ignore patterns
116
- - `stylelint.config.js` — `stylelintConfig()`
117
- - `.remarkrc.js` — `remarkConfig()`
118
- - `tsconfig.json` — extends `@kitschpatrol/typescript-config`
119
-
120
- **Linting commands**:
121
-
122
- - `pnpm ksc lint` / `pnpm ksc fix` — Run ALL tools at once, usually mapped to the package.json "lint" and "fix" scripts.
123
- - `pnpm ksc-eslint lint` / `pnpm ksc-eslint fix` — ESLint only
124
- - `pnpm ksc-prettier lint` / `pnpm ksc-prettier fix` — Prettier only
125
- - `pnpm ksc-typescript lint` — TypeScript type checking only
126
- - `pnpm ksc-cspell lint` / `pnpm ksc-cspell fix` — Spell checking only (`fix` removes unused words from the config's `words` array and sorts it)
127
- - `pnpm ksc-stylelint lint` / `pnpm ksc-stylelint fix` — CSS/style linting only
128
- - `pnpm ksc-remark lint` / `pnpm ksc-remark fix` — Markdown linting only
129
- - `pnpm ksc-knip lint` — Unused code/export detection
130
-
131
- Use individual commands to focus on specific linter errors instead of running the full suite.
132
-
133
- ## TypeScript Configuration
134
-
135
- Requires TypeScript 6, which enables all `strict` checks by default. Additional flags:
136
-
137
- - Target: `ES2025` with `DOM` lib
138
- - Module: `ESNext` with `bundler` module resolution
139
- - `noUncheckedIndexedAccess: true` — indexed access on arrays and records yields `T | undefined`
140
- - `noImplicitReturns: true` — if any code path returns a value, all paths must return explicitly
141
- - `noImplicitOverride: true` — class members that override a base member need the `override` keyword
142
- - `verbatimModuleSyntax: true` — imports/exports used only as types must be marked with `type`
143
- - `noFallthroughCasesInSwitch: true`
144
- - `erasableSyntaxOnly: true` — no `enum`, no runtime `namespace` blocks, no parameter properties. Only type-level syntax that can be erased is allowed.
145
- - `noUnusedLocals: true`, `noUnusedParameters: true`
146
- - `isolatedModules: true`
147
- - JSON imports enabled (`resolveJsonModule: true`)
148
- - `.ts` extensions allowed in imports (`allowImportingTsExtensions: true`)
7
+ # Working with @kitschpatrol/shared-config
8
+
9
+ The project lints, formats, and type checks through the `ksc` CLI. The rules are strict and warnings fail the run, so write code that matches the conventions below, then let the tools fix what they can.
10
+
11
+ ## Workflow
12
+
13
+ 1. Write the code.
14
+ 2. Run `pnpm ksc fix`. It applies every auto-fix (formatting, sorting, import style), then reports what remains.
15
+ 3. Fix the remaining issues by hand and repeat until `pnpm ksc lint` is clean.
16
+
17
+ Use pnpm, not npm, npx, or yarn. Projects usually map `pnpm lint` and `pnpm fix` to the commands above.
18
+
19
+ Both commands accept file arguments (`pnpm ksc fix src/thing.ts`) and `--skip <tool>`. Tools that only work on the whole package, such as TypeScript and Knip, ignore file arguments. To work on one tool's errors, run it alone:
20
+
21
+ | Command | Tool | `lint` | `fix` |
22
+ | ---------------- | ----------------------------------- | ------ | ----- |
23
+ | `ksc-eslint` | ESLint, including Markdown linting | yes | yes |
24
+ | `ksc-prettier` | Prettier | yes | yes |
25
+ | `ksc-typescript` | Type checking | yes | |
26
+ | `ksc-stylelint` | Stylelint | yes | yes |
27
+ | `ksc-cspell` | Spelling and letter case | yes | yes |
28
+ | `ksc-knip` | Unused files, exports, dependencies | yes | |
29
+ | `ksc-mdat` | Readme placeholder comments | yes | yes |
30
+
31
+ `pnpm ksc print-config [file]` shows the resolved configuration when a rule's settings are unclear.
32
+
33
+ ## Gotchas
34
+
35
+ - **Do not sort or format by hand.** Prettier owns formatting, and the Perfectionist ESLint plugin sorts imports, object keys, type members, and unions. `fix` handles both. Some keys intentionally sort semantically instead of alphabetically (`x, y, z`, `width, height`, `min, max`, `start, end`, `r, g, b, a`), so leave whatever order `fix` produces.
36
+ - **ESLint plugins are renamed.** Disable comments must use the short prefix, e.g. `// eslint-disable-next-line ts/no-explicit-any`:
37
+
38
+ | Upstream name | Prefix here |
39
+ | ----------------------------------- | ----------------- |
40
+ | `@typescript-eslint` | `ts` |
41
+ | `import-x` | `import` |
42
+ | `n` | `node` |
43
+ | `vitest` | `test` |
44
+ | `@eslint-community/eslint-comments` | `eslint-comments` |
45
+ | `@eslint-react` | `react` |
46
+ | `@stylistic` | `stylistic` |
47
+ | `jsonc` | `json` |
48
+ | `yml` | `yaml` |
49
+ | `@html-eslint` | `html` |
50
+ | `package-json` | `json-package` |
51
+
52
+ - **Unknown words fail CSpell.** Add legitimate project terms to the `words` array in `cspell.config.ts`. An unscoped `ksc fix` removes words that are no longer used anywhere.
53
+ - **Knip flags anything unused**, including exports, files, and dependencies. Remove dead code instead of leaving it exported.
54
+ - **Generated readme sections are owned by mdat.** Content between `<!-- keyword -->` and `<!-- /keyword -->` comments is regenerated by `ksc fix`. Do not edit it by hand.
55
+ - **ESLint is type-aware and never cached**, so it is the slow step. `--no-cache` disables the other tools' caches if results look stale.
56
+
57
+ ## TypeScript rules that fail most often
58
+
59
+ - **No `null`.** Use `undefined` (`unicorn/no-null`, `ts/no-restricted-types`). Convert at API boundaries: `apiResult() ?? undefined`. Comparing against `null` with `===` is allowed.
60
+ - **No `any`.** Use `unknown` and narrow (`ts/no-explicit-any`, plus `ts/no-unsafe-assignment`, `-call`, `-member-access`, `-return`).
61
+ - **No `Buffer` or `object` types.** Use `Uint8Array` and `Record<string, unknown>`.
62
+ - **Handle every promise** (`ts/no-floating-promises`). Use `await`, or `void someAsyncFunction()` for deliberate fire-and-forget. Functions that return a promise must be declared `async` (`ts/promise-function-async`).
63
+ - **Template literals accept only strings and numbers** (`ts/restrict-template-expressions`). Convert the rest: `` `ready: ${String(isReady)}` ``. `+` cannot mix strings and numbers (`ts/restrict-plus-operands`).
64
+ - **Conditions must be boolean** (`ts/strict-boolean-expressions`). Strings and numbers are not allowed as conditions, so write `text !== ''` and `array.length > 0` (`unicorn/explicit-length-check`). Nullable objects and nullable booleans are allowed.
65
+ - **Use `??` and `?.`** instead of `||` and `&&` chains for nullish values (`ts/prefer-nullish-coalescing`, `ts/prefer-optional-chain`).
66
+ - **Indexed access is `T | undefined`** (`noUncheckedIndexedAccess`). Narrow `array[i]` and `record[key]` before use.
67
+ - **Only erasable syntax** (`erasableSyntaxOnly`). No `enum`, runtime `namespace`, or constructor parameter properties.
68
+ - **Type-only imports and exports use separate top-level statements** (`verbatimModuleSyntax`, `import/consistent-type-specifier-style`): `import type { Foo } from './bar'` and `export type { Foo }`, never the inline `import { type Foo }`.
69
+ - **`type`, not `interface`** (`ts/consistent-type-definitions`).
70
+ - **Array types:** `string[]` for simple element types, `Array<string | number>` for anything else (`ts/array-type`).
71
+ - **Unused locals and parameters are errors.** Prefix intentionally unused ones with `_`.
72
+ - **Every code path returns** (`noImplicitReturns`), and overriding class members need `override`.
73
+
74
+ ## Naming
75
+
76
+ Enforced by `ts/naming-convention` and unicorn:
77
+
78
+ - `camelCase` for variables, functions, parameters, and properties. `const` variables may also be `UPPER_CASE`.
79
+ - `StrictPascalCase` for types, classes, and type parameters. Strict means `HttpClient`, not `HTTPClient`.
80
+ - Imports and destructured variables may be `camelCase` or `StrictPascalCase`.
81
+ - Properties that need quotes (e.g. `'Content-Type'`) are exempt.
82
+ - File names are `kebab-case` (`unicorn/filename-case`).
83
+ - No abbreviations (`unicorn/name-replacements`): `error` not `err`, `button` not `btn`, `index` not `idx`. The exceptions are `i`, `j`, `acc`, `arg`, `args`, `db`, `dev`, `doc`, `docs`, `env`, `fn`, `lib`, `param`, `params`, `pkg`, `prop`, `props`, `ref`, `refs`, `sep`, `src`, `temp`, `util`, and `utils`.
84
+ - Catch clause variables are named `error` (`unicorn/catch-error-name`).
85
+
86
+ ## Style
87
+
88
+ Prettier settings: no semicolons, single quotes, tabs (spaces in Markdown and YAML), trailing commas, 100 column width, LF line endings.
89
+
90
+ ESLint rules that are not auto-fixed or that change how code is written:
91
+
92
+ - Braces on every `if`, `else`, and loop body (`curly`).
93
+ - `for...of` instead of `.forEach()` (`unicorn/no-for-each`). `.reduce()` is allowed only for simple operations like sums (`unicorn/no-array-reduce`).
94
+ - `node:` protocol for Node.js built-ins: `import fs from 'node:fs'`.
95
+ - Comments start with a capital letter (`capitalized-comments`).
96
+ - Exported function declarations need a JSDoc comment with a description (`jsdoc/require-jsdoc`, `jsdoc/require-description`). Do not write types in JSDoc, so `@param name` not `@param {string} name`. `@param` and `@returns` tags are optional in TypeScript files.
97
+
98
+ ## Configuration files
99
+
100
+ Each tool is configured through a typed factory function. Pass overrides to the factory instead of replacing the shared config:
101
+
102
+ | File | Contents |
103
+ | --------------------- | ------------------------------------------------------------- |
104
+ | `eslint.config.ts` | `eslintConfig()` from `@kitschpatrol/eslint-config` |
105
+ | `prettier.config.ts` | `prettierConfig()` from `@kitschpatrol/prettier-config` |
106
+ | `cspell.config.ts` | `cspellConfig()` from `@kitschpatrol/cspell-config` |
107
+ | `knip.config.ts` | `knipConfig()` from `@kitschpatrol/knip-config` |
108
+ | `stylelint.config.js` | `stylelintConfig()` from `@kitschpatrol/stylelint-config` |
109
+ | `.remarkrc.js` | `remarkConfig()` from `@kitschpatrol/remark-config` |
110
+ | `mdat.config.ts` | `mdatConfig()` from `@kitschpatrol/mdat-config` |
111
+ | `tsconfig.json` | `"extends": "@kitschpatrol/typescript-config"` (TypeScript 6) |
112
+
113
+ Configs may instead live under the matching keys in `package.json`. `eslintConfig()` detects Astro, React, and Svelte from installed packages. Pass `{ astro: true }`, `{ react: true }`, or `{ svelte: true }` to force one on. Per-file rule overrides go in the factory's options, e.g. `eslintConfig({ ts: { overrides: { 'ts/no-explicit-any': 'off' } } })`.
@@ -107,10 +107,10 @@ jobs:
107
107
  env:
108
108
  HOMEPAGE: ${{ steps.homepage.outputs.url }}
109
109
  run: |
110
- sitemap_url="$(
110
+ sitemap_url="$({
111
111
  curl --fail --silent --show-error --location "$HOMEPAGE" |
112
112
  htmlq --base "$HOMEPAGE" --attribute href 'link[rel~="sitemap"]'
113
- )" 2> "$RUNNER_TEMP/htmlq.log" || true
113
+ } 2> "$RUNNER_TEMP/htmlq.log")" || true
114
114
  sitemap_url="${sitemap_url:-$HOMEPAGE}"
115
115
 
116
116
  pipx run --spec 'trafilatura==2.1.0' trafilatura --sitemap "$sitemap_url" --list \
@@ -15,3 +15,7 @@ allowBuilds:
15
15
  unrs-resolver: true
16
16
  minimumReleaseAgeExclude:
17
17
  - '@kitschpatrol/*'
18
+ peerDependencyRules:
19
+ allowedVersions:
20
+ # No eslint 10 release yet, but runtime-compatible (used via eslint-plugin-astro)
21
+ 'eslint-plugin-jsx-a11y>eslint': '10'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kitschpatrol/repo-config",
3
- "version": "8.10.0",
3
+ "version": "8.11.0",
4
4
  "description": "Repository configuration and GitHub workflows for @kitschpatrol/shared-config.",
5
5
  "keywords": [
6
6
  "shared-config",
@@ -33,19 +33,19 @@
33
33
  ],
34
34
  "dependencies": {
35
35
  "@pinojs/json-colorizer": "^4.0.0",
36
- "@pnpm/lockfile.fs": "^1100.2.8",
36
+ "@pnpm/lockfile.fs": "^1100.2.12",
37
37
  "cosmiconfig": "^10.0.1",
38
38
  "cosmiconfig-typescript-loader": "^6.3.0",
39
39
  "decircular": "^1.0.0",
40
40
  "deepmerge": "^4.3.1",
41
41
  "execa": "^10.0.1",
42
42
  "find-workspaces": "^0.3.1",
43
- "fs-extra": "^11.4.0",
43
+ "fs-extra": "^11.4.1",
44
44
  "globby": "^16.2.4",
45
45
  "json-stringify-pretty-compact": "^4.0.0",
46
46
  "package-up": "^5.0.0",
47
47
  "picocolors": "^1.1.1",
48
- "prettier": "^3.9.8",
48
+ "prettier": "^3.9.9",
49
49
  "semver": "^7.8.5",
50
50
  "yargs": "^18.2.0"
51
51
  },
package/readme.md CHANGED
@@ -175,7 +175,7 @@ ksc-repo lint
175
175
  | Option | Description | Type | Default |
176
176
  | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | ---------- |
177
177
  | `--format` | Output format: "native" streams each tool's own output, "machine" prints one parseable line per issue for editor problem matchers, "json" prints an aggregate report. | `"json"` `"machine"` `"native"` | `"native"` |
178
- | `--cache` | Use tool-native caches stored below node\_modules/.cache/ksc at the workspace root. Disable with --no-cache. | `boolean` | `true` |
178
+ | `--cache` | Use tool-native caches stored below node_modules/.cache/ksc at the workspace root. Disable with --no-cache. | `boolean` | `true` |
179
179
  | `--help`<br>`-h` | Show help | `boolean` | |
180
180
  | `--version`<br>`-v` | Show version number | `boolean` | |
181
181
 
@@ -192,7 +192,7 @@ ksc-repo fix
192
192
  | Option | Description | Type | Default |
193
193
  | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | ---------- |
194
194
  | `--format` | Output format: "native" streams each tool's own output, "machine" prints one parseable line per issue for editor problem matchers, "json" prints an aggregate report. | `"json"` `"machine"` `"native"` | `"native"` |
195
- | `--cache` | Use tool-native caches stored below node\_modules/.cache/ksc at the workspace root. Disable with --no-cache. | `boolean` | `true` |
195
+ | `--cache` | Use tool-native caches stored below node_modules/.cache/ksc at the workspace root. Disable with --no-cache. | `boolean` | `true` |
196
196
  | `--help`<br>`-h` | Show help | `boolean` | |
197
197
  | `--version`<br>`-v` | Show version number | `boolean` | |
198
198