@scania/tegel-cli
Version:
CLI tool for copying and transforming Tegel components with custom prefixes
2 lines • 16.5 kB
JavaScript
import {a}from'./chunk-HFCPHFYJ.js';import {Command}from'commander';import L from'prompts';import U from'chalk';import Z from'semver';import p from'path';import D from'fs-extra';import {cosmiconfig}from'cosmiconfig';import {z as z$1}from'zod';import {glob}from'glob';var I="tegel.config.json",W=z$1.object({pattern:z$1.union([z$1.instanceof(RegExp),z$1.string()]),replacement:z$1.union([z$1.string(),z$1.any()]),description:z$1.string().optional(),fileTypes:z$1.array(z$1.string()).optional(),priority:z$1.number().optional()}),E=z$1.object({version:z$1.string().optional(),prefix:z$1.string().regex(/^[a-z][a-z0-9-]*$/,{message:"Prefix must start with lowercase letter and contain only lowercase letters, numbers, and hyphens"}),targetDir:z$1.string(),transforms:z$1.object({customRules:z$1.array(W).optional()}).optional(),aliases:z$1.record(z$1.string()),includeTests:z$1.boolean().optional()}),A={prefix:"tds",targetDir:"./src/components/tegel",aliases:{"@tegel/utils":"@/src/components/tegel/utils","@tegel/mixins":"@/src/components/tegel/mixins","@tegel/tokens":"@/src/components/tegel/tokens","@tegel/types":"@/src/components/tegel/types","@tegel/components":"@/src/components/tegel"},includeTests:false},z=class{config=null;configPath=null;explorer=cosmiconfig("tegel",{searchPlaces:["package.json","tegel.config.json","tegel.config.js","tegel.config.mjs","tegel.config.cjs",".tegelrc.json",".tegelrc"]});async load(e=process.cwd()){try{let t=await this.explorer.search(e);if(t){a.debug(`Found config at: ${t.filepath}`),this.configPath=t.filepath;let s=E.parse(t.config);return this.config=s,s}return a.debug("No config found, using defaults"),this.config=A,A}catch(t){throw t instanceof z$1.ZodError?(a.error("Invalid configuration:",t),new Error(`Configuration validation failed: ${t.errors.map(s=>s.message).join(", ")}`)):t}}async save(e,t=process.cwd()){let s=p.join(t,I),n={...A,...e},o=E.parse(n);await D.writeJSON(s,o,{spaces:2}),this.config=o,this.configPath=s,a.success(`Configuration saved to ${s}`);}async init(e=process.cwd(),t={}){let s=p.join(e,I);if(await D.pathExists(s))throw new Error(`Configuration already exists at ${s}`);let n={...A,...t};await this.save(n,e);}get(){if(!this.config)throw new Error("Configuration not loaded. Call load() first.");return this.config}getPath(){return this.configPath}update(e){if(!this.config)throw new Error("Configuration not loaded. Call load() first.");return this.config={...this.config,...e},this.config=E.parse(this.config),this.config}static async validate(e){try{return E.parse(e),!0}catch(t){return t instanceof z$1.ZodError&&(a.error("Configuration validation failed:"),t.errors.forEach(s=>{a.error(` ${s.path.join(".")}: ${s.message}`);})),false}}resolvePath(e){if(!this.configPath)return p.resolve(process.cwd(),e);let t=p.dirname(this.configPath);return p.resolve(t,e)}getTargetDir(){let e=this.get();return this.resolvePath(e.targetDir)}},$=new z;var b=class d{componentsPath;componentMap;tagToComponentMap=new Map;constructor(e,t){this.componentsPath=e,this.componentMap=t,Array.from(t.entries()).forEach(([s,n])=>{this.tagToComponentMap.set(n.tag,s);});}async analyzeAll(){a.debug("Analyzing component dependencies..."),await Promise.all(Array.from(this.componentMap.values()).map(async e=>{let t=await this.analyzeComponent(e);e.dependencies.internal=Array.from(t.components),e.dependencies.utilities=Array.from(t.utilities),e.dependencies.mixins=Array.from(t.mixins),e.dependencies.assets=Array.from(t.assets),e.dependencies.types=Array.from(t.types);})),a.debug("Dependency analysis complete");}async analyzeComponent(e){let t={components:new Set,utilities:new Set,mixins:new Set,assets:new Set,types:new Set},s=[p.join(this.componentsPath,e.files.component),...e.files.styles.map(n=>p.join(this.componentsPath,n)),...(e.files.tests||[]).map(n=>p.join(this.componentsPath,n))];return await Promise.all(s.map(async n=>{if(await D.pathExists(n)){let o=await D.readFile(n,"utf-8");n.endsWith(".ts")||n.endsWith(".tsx")?await this.analyzeTypeScriptContent(o,t,n):(n.endsWith(".scss")||n.endsWith(".sass"))&&await d.analyzeScssContent(o,t);}})),t}async analyzeTypeScriptContent(e,t,s){this.extractImports(e,t),d.extractInternalDependencies(e).forEach(i=>{let r=this.tagToComponentMap.get(`tds-${i}`);r&&t.components.add(r);}),d.extractTypeDependencies(e).forEach(i=>t.types.add(i)),d.handleSpecialCases(e,t,s),await this.detectSubcomponentDependencies(t,s);}extractImports(e,t){let s=/import\s+(?:type\s+)?(?:{[^}]+}|[^;]+)\s+from\s+['"]([^'"]+)['"]/g,n;for(;(n=s.exec(e))!==null;){let o=n[1],i=n[0];if(o.includes("/utils/")){let r=p.basename(o).replace(/\.(js|ts|tsx)$/,"");t.utilities.add(r);}if((o.includes("@scania/tegel-icons")||o.includes("types/icons"))&&(t.types.add("icons"),i.includes("{")&&!i.includes("type ")&&t.components.add("icon")),o.includes("/types/")||o.includes("../types/")||o.includes("../../types/")){let r=p.basename(o,".ts").replace(/\.(js|tsx)$/,"");t.types.add(r.toLowerCase());}if(o.startsWith("./")&&(o.includes("IconsArray")||o.includes("scaniaIconsArray")||o.includes("tratonIconsArray"))){let r=p.basename(o);t.assets.add(r);}if(o.includes("../")&&o.includes("components/")&&!o.includes(".scss")){let r=o.split("/"),c=r.indexOf("components");if(c!==-1&&c+1<r.length){let g=r[c+1];this.componentMap.has(g)&&t.components.add(g);}}}}static extractInternalDependencies(e){let t=new Set;[/<(\/?)(tds-[a-z-]+)/g,/['"]tds-([a-z-]+)['"/]/g,/(?:^|\s|,)(?:\.|#)?tds-([a-z-]+)(?:[\s[\]{,:)>+~]|$)/gm,/querySelector(?:All)?\s*\([\s\S]*?['"`](?:\.|#)?tds-([a-z-]+)/g,/getElementsByTagName\s*\([\s\S]*?['"`]tds-([a-z-]+)/g,/createElement\s*\([\s\S]*?['"`]tds-([a-z-]+)/g,/customElements\.(?:define|get|whenDefined)\s*\([\s\S]*?['"`]tds-([a-z-]+)/g,/tagName\s*===?\s*['"`]tds-([a-z-]+)['"`]/gi,/tagName\s*===?\s*['"`]TDS-([A-Z-]+)['"`]/g,/tag:\s*['"`]tds-([a-z-]+)['"`]/g,/HTML(?:Tds|TDS)([A-Z][a-zA-Z]+)Element/g].forEach(o=>{Array.from(e.matchAll(o)).forEach(r=>{let c=r[2]||r[1];o.source.includes("TDS-([A-Z-]+)")&&(c=c?.toLowerCase()),o.source.includes("HTML(?:Tds|TDS)")&&(c=c?.replace(/([A-Z])/g,"-$1").toLowerCase().slice(1)),c&&!c.startsWith("-")&&t.add(c);});});let n=e.match(/tag:\s*['"`]tds-([a-z-]+)['"`]/);return n&&t.delete(n[1]),t}static extractTypeDependencies(e){let t=new Set,s=e.matchAll(/import\s+(?:type\s+)?{([^}]+)}\s+from\s+['"]([^'"]+)['"]/g);return Array.from(s).forEach(n=>{let o=n[1],i=n[2];i.includes("components/")&&!i.includes(".scss")&&o.split(",").map(c=>c.trim()).forEach(c=>{let g=c.match(/Tds([A-Z][a-z]+)/);g&&t.add(g[1].toLowerCase());}),(i.includes("types/icons")||i.includes("@scania/tegel-icons"))&&t.add("icons");}),t}static handleSpecialCases(e,t,s){let n=p.basename(p.dirname(s));n==="dropdown"&&(e.includes("TDS-DROPDOWN-OPTION")||e.includes("HTMLTdsDropdownOptionElement")||e.includes("tds-dropdown-option"))&&t.components.add("dropdown-option"),(e.includes("<tds-icon")||e.includes("tds-icon"))&&t.components.add("icon"),n==="table"&&["table-header","table-body","table-footer","table-header-cell","table-body-cell"].forEach(i=>{(e.includes(`tds-${i}`)||e.includes(`TDS-${i.toUpperCase()}`))&&t.components.add(i);}),n==="toast"&&(e.includes("tds-toast-item")||e.includes("TDS-TOAST-ITEM"))&&t.components.add("toast-item");}async detectSubcomponentDependencies(e,t){let s=p.dirname(t);try{if(!await D.pathExists(s))return;let o=(await D.readdir(s,{withFileTypes:!0})).filter(i=>i.isDirectory());for(let i of o){let r=i.name;this.componentMap.has(r)&&e.components.add(r);}}catch{a.debug(`Could not read component directory: ${s}`);}}static async analyzeScssContent(e,t){let s=/@import\s+['"]([^'"]+)['"]/g,n;for(;(n=s.exec(e))!==null;){let i=n[1];if(i.includes("mixins/")){let r=p.basename(i,".scss").replace(/^_/,"");t.mixins.add(r);}}let o=/@use\s+['"]([^'"]+)['"]/g;for(;(n=o.exec(e))!==null;){let i=n[1];if(i.includes("mixins/")){let r=p.basename(i,".scss").replace(/^_/,"");t.mixins.add(r);}}}getFullDependencyTree(e){let t=new Set,s=[e];for(;s.length>0;){let n=s.shift();if(t.has(n))continue;t.add(n);let o=this.componentMap.get(n);o&&o.dependencies.internal&&o.dependencies.internal.forEach(i=>{t.has(i)||s.push(i);});}return t.delete(e),t}getAllUtilities(e){let t=new Set,s=this.getFullDependencyTree(e);return s.add(e),Array.from(s).forEach(n=>{let o=this.componentMap.get(n);o&&o.dependencies.utilities&&o.dependencies.utilities.forEach(i=>t.add(i));}),t}getAllMixins(e){let t=new Set,s=this.getFullDependencyTree(e);return s.add(e),Array.from(s).forEach(n=>{let o=this.componentMap.get(n);o&&o.dependencies.mixins&&o.dependencies.mixins.forEach(i=>t.add(i));}),t}getAllTypes(e){let t=new Set,s=this.getFullDependencyTree(e);return s.add(e),Array.from(s).forEach(n=>{let o=this.componentMap.get(n);o&&o.dependencies.types&&o.dependencies.types.forEach(i=>t.add(i));}),t}getAllAssets(e){let t=new Set,s=this.getFullDependencyTree(e);return s.add(e),Array.from(s).forEach(n=>{let o=this.componentMap.get(n);o&&o.dependencies.assets&&o.dependencies.assets.forEach(i=>t.add(i));}),t}};var j=class d{componentsPath;cache=new Map;topLevelComponents=new Map;subComponents=new Map;constructor(e){this.componentsPath=e;}async scanAll(){a.debug(`Scanning components in: ${this.componentsPath}`);let e=await this.findTopLevelComponentDirectories();return await Promise.all(e.map(s=>this.scanComponentDirectory(s))),await new b(this.componentsPath,this.cache).analyzeAll(),a.debug(`Found ${this.cache.size} total components (${this.topLevelComponents.size} top-level, ${this.subComponents.size} sub-components)`),{allComponents:this.cache,topLevelComponents:this.topLevelComponents,subComponents:this.subComponents}}async scanComponentDirectory(e){let t=await this.scanComponent(e);t&&(this.cache.set(t.name,t),t.metadata.isSubComponent?this.subComponents.set(t.name,t):this.topLevelComponents.set(t.name,t));let s=await d.findSubComponentDirectories(e);await Promise.all(s.map(n=>this.scanComponentDirectory(n)));}async scanComponent(e){let t=p.basename(e);if(t.startsWith("_")||t==="test"||t==="tests")return null;try{let s=await d.findComponentFile(e);if(!s)return a.debug(`No component file found in ${e}`),null;let n=await d.extractMetadata(s);if(!n.tag)return null;let i=p.relative(this.componentsPath,e).split(p.sep).filter(l=>l!==""),r=i.length,c=p.basename(e),g=r>1?i[i.length-2]:null,x=g&&c===g,P=i[0]==="tabs"&&c.endsWith("-tabs")&&r===2,h=r>1&&!x&&!P,v=await d.findComponentFiles(e);return {name:t,tag:n.tag,files:{component:p.relative(this.componentsPath,s),styles:v.styles.map(l=>p.relative(this.componentsPath,l)),tests:v.tests.map(l=>p.relative(this.componentsPath,l)),types:v.types.map(l=>p.relative(this.componentsPath,l)),stories:v.stories.map(l=>p.relative(this.componentsPath,l))},dependencies:{internal:[],utilities:[],mixins:[],types:[]},metadata:{version:"1.33.0",stability:d.determineStability(e),description:n.description,isSubComponent:h,parentComponent:h?this.getParentComponentName(e):void 0}}}catch(s){return a.warn(`Failed to scan component ${t}: ${s}`),null}}async findTopLevelComponentDirectories(){let e=p.join(this.componentsPath,"*/");return (await glob(e)).filter(s=>{let n=p.basename(s);return !n.startsWith("_")&&n!=="test"&&n!=="tests"})}static async findSubComponentDirectories(e){let t=p.join(e,"*/"),s=await glob(t),n=[],o=s.filter(r=>{let c=p.basename(r);return !c.startsWith("_")&&c!=="test"&&c!=="tests"}).map(async r=>await d.hasComponentFile(r)?r:null),i=await Promise.all(o);return n.push(...i.filter(r=>r!==null)),n}static async hasComponentFile(e){return await d.findComponentFile(e)!==null}static async findComponentFile(e){let t=p.basename(e),s=[p.join(e,`${t}.tsx`),p.join(e,"index.tsx"),...await glob(p.join(e,"*.tsx"))];return (await Promise.all(s.map(async o=>{if(await D.pathExists(o)){let i=await D.readFile(o,"utf-8");if(i.includes("@Component")&&i.includes("tag:"))return o}return null}))).find(o=>o!==null)||null}static async extractMetadata(e){let t=await D.readFile(e,"utf-8"),s={},n=t.match(/tag:\s*['"]([^'"]+)['"]/);n&&([,s.tag]=n);let o=t.match(/\/\*\*([^*]|\*(?!\/))*\*\/\s*@Component/s);if(o){let i=o[0],r=i.match(/@description\s+(.+?)(?:@|\*\/)/s);if(r)s.description=r[1].trim();else {let c=i.match(/\/\*\*\s*\n\s*\*\s*([^@\n]+)/);c&&(s.description=c[1].trim());}}return s}static async findComponentFiles(e){let t={styles:[],tests:[],types:[],stories:[]};t.styles=await glob(p.join(e,"*.scss"));let s=p.join(e,"test");return await D.pathExists(s)&&(t.tests=await glob(p.join(s,"**/*.{ts,tsx,js,jsx}"))),t.types=await glob(p.join(e,"*.d.ts")),t.stories=await glob(p.join(e,"*.stories.{ts,tsx,js,jsx}")),t}static determineStability(e){return e.includes("_beta")?"beta":e.includes("_experimental")?"experimental":"stable"}getParentComponentName(e){return p.relative(this.componentsPath,e).split(p.sep)[0]}getComponent(e){return this.cache.get(e)}getAllComponents(){return Array.from(this.cache.values())}getTopLevelComponents(){return Array.from(this.topLevelComponents.values())}findByTag(e){return Array.from(this.cache.values()).find(t=>t.tag===e)}getComponentNames(){return Array.from(this.cache.keys())}getTopLevelComponentNames(){return Array.from(this.topLevelComponents.keys())}};var ht=new Command().name("update").description("Update existing Tegel components to the latest version").argument("[components...]","specific component names to update").option("-a, --all","update all installed components").option("--dry-run","preview changes without writing files").option("-f, --force","skip confirmation prompts").action(async(d,e)=>{try{let t=await $.load(),{resolveTegelSource:s}=await import('./tegel-source-resolver-RSPXN7WC.js'),n=await s();a.debug(`Using Tegel source from: ${n.root}`),a.debug(`Tegel version: ${n.version}`),a.debug(`Current config version: ${t.version}`);let o=typeof n.version=="string"&&typeof t.version=="string"&&Z.gt(n.version,t.version);if(o)a.info(`New Tegel version available: ${U.cyan(t.version)} \u2192 ${U.green(n.version)}`);else if(!d.length&&!e.all){a.info("All components are up to date");return}a.startSpinner("Scanning for installed components...");let r=await new j(n.componentsPath).scanAll(),{allComponents:c}=r,g=new Set,x=p.resolve(t.targetDir);if(await D.pathExists(x)){let l=await D.readdir(x);await Promise.all(l.map(async u=>{let y=p.join(x,u);(await D.stat(y)).isDirectory()&&c.has(u)&&g.add(u);}));}if(a.stopSpinner(!0,`Found ${g.size} installed components`),g.size===0){a.warn("No installed components found");return}let S=[];if(e.all||o&&d.length===0)S=Array.from(g);else if(d.length>0){let l=d.filter(u=>!g.has(u));l.length>0&&(a.error(`Components not installed: ${l.join(", ")}`),a.info("Installed components:"),a.list(Array.from(g).sort()),process.exit(1)),S=d;}else {let l=Array.from(g).sort().map(y=>({title:y,value:y,description:c.get(y)?.metadata.description})),u=await L({type:"multiselect",name:"components",message:"Select components to update",choices:l,hint:"Space to select, Enter to confirm",instructions:!1});if(!u.components||u.components.length===0){a.info("No components selected");return}S=u.components;}let P=new b(n.componentsPath,c);await P.analyzeAll();let h=new Set;if(S.forEach(l=>{h.add(l),P.getFullDependencyTree(l).forEach(y=>{g.has(y)&&h.add(y);});}),a.newline(),a.info(`Will update ${h.size} component(s):`),a.list(Array.from(h).sort()),e.dryRun){a.newline(),a.info("Dry run mode - no files will be written");return}if(!e.force){let{proceed:l}=await L({type:"confirm",name:"proceed",message:o?`Update all components to version ${n.version}?`:"Continue with update?",initial:!0});if(!l){a.info("Update cancelled");return}}a.newline(),a.startSpinner("Updating components...");let{ComponentInstaller:v}=await import('./component-installer-W6T72F6F.js');t.version=n.version;let C=await v.install({components:Array.from(h),componentMap:c,analyzer:P,config:t,tegelSource:n,force:!0,dryRun:!1,update:!0});C.success?(a.stopSpinner(!0,`Successfully updated ${C.installedComponents.length} component(s)`),await $.save(t),a.newline(),a.success("Update complete!"),a.info(`Updated to version: ${n.version}`),C.copiedFiles.length>0&&(a.newline(),a.info(`Updated ${C.copiedFiles.length} files`))):(a.stopSpinner(!1,"Update failed"),C.errors&&C.errors.forEach(l=>a.error(l)),process.exit(1));}catch(t){a.error("Failed to update components:",t instanceof Error?t:void 0),process.exit(1);}});export{$ as a,b,j as c,ht as d};//# sourceMappingURL=chunk-U5L6KJOP.js.map
//# sourceMappingURL=chunk-U5L6KJOP.js.map