lvlup
Version:
A simple version manager solution for packages
26 lines (24 loc) • 17 kB
JavaScript
import je from"os";import{hideBin as Oo}from"yargs/helpers";import $o from"yargs/yargs";var h=o=>`\x1B[${o}m`,e={black:h(30),blink:h(5),blue:h(34),bright:h(1),cyan:h(36),green:h(32),magenta:h(35),red:h(31),white:h(37),yellow:h(33),stop:"\x1B[0m"};import Be from"path";var C="lvlup",u=`${e.green}${C}${e.stop}`,c=`.${C}`,q="CHANGELOG.md",v=process.cwd(),g=Be.resolve(v,c),W=`
${e.bright}${e.blue}________________________________
_ _ _ _
| | __ _| | | | | |_ __
| | \\ \\ / / | | | | | '_ \\
| |__\\ V /| |___ | |_| | |_) |
|_____\\_/ |_____| \\___/| .__/
|_|
________________________________${e.stop}`;var l={Patch:"patch",Minor:"minor",Major:"major"};import De from"fs/promises";import Ge from"path";var H={log:"",info:e.blue,warn:e.yellow,error:e.red};var E=class{log(r,t){this.printLogMessageByLevel({logLevel:"log",message:r,options:t})}info(r,t){this.printLogMessageByLevel({logLevel:"info",message:r,options:t})}warn(r,t){this.printLogMessageByLevel({logLevel:"warn",message:r,options:t})}error(r,t){this.printLogMessageByLevel({logLevel:"error",message:r,options:t})}printLogMessageByLevel(r){let{logLevel:t,message:n,options:s}=r;s?.newLineBefore&&console.log(""),console[t](`\u{1F3A9} ${H[t]}${t}${e.stop}`,n),s?.newLineAfter&&console.log("")}},i=new E;async function x(){try{let o=Ge.resolve(g,"config.json"),r=(await De.readFile(o)).toString();return{configJsonAsObject:JSON.parse(r)}}catch(o){throw i.error(`Failed to read ${u}'s config.json file...`),o}}import qe from"fs/promises";import We from"path";async function _(){try{let o=We.resolve(v,"package.json"),r=(await qe.readFile(o)).toString();return{packageJsonAsObject:JSON.parse(r),packageJsonAsString:r}}catch(o){throw i.error("Failed to read the project's package.json file..."),o}}import{execSync as U}from"node:child_process";function z(){try{U("git rev-parse --is-inside-work-tree",{encoding:"utf8",stdio:"pipe"})}catch{throw i.error("This project is not a git repository. The add command requires git to track your changes."),new Error("Not a git repository")}if(He().length===0)throw i.error("No git changes found. Make and save changes to your code before running add.",{newLineBefore:!0}),new Error("No git changes to commit")}function He(){return U("git status --porcelain",{encoding:"utf8"}).split(`
`).filter(r=>r.trim().length>0)}import Ue from"fs";import ze from"path";function b(){return Ue.existsSync(ze.resolve(process.cwd(),c))}function Y(){if(!b())throw i.error(`There is no ${c} folder.`),i.error(`If this is the first time '${u}' have been used in this project, run 'yarn ${u} init' to get set up.`),i.error(`If you expected there to be a ${c}, you should check git history for when the folder was removed to ensure you do not lose any configuration.`),new Error(`${c} directory doesn't exists`)}import{execSync as Z}from"child_process";async function K(o){let r={filenameFullPath:"no-name"};try{let{filenameFullPath:t,commitMessage:n}=o;r.filenameFullPath=t;let s=n.replace(/"/g,'\\"').replace(/`/g,"\\`");Z(`git add ${t}`),Z(`git commit -m "docs(lvlup): ${s}"`)}catch(t){throw i.error(`[Git Error] Failed to commit the file '${r.filenameFullPath}'...`),t}}import Ze from"fs";import Ke from"path";import{humanId as Qe}from"human-id";function Q(o){return o.replace(/\n/g,`\r
`)}import Ye from"os";function X(){return Ye.platform()==="win32"}var ee=`---
"{{packageName}}": {{semverLevel}}
---
{{commitMessage}}
`;async function oe(o){let r="no-name";try{let{packageName:t,semverLevel:n,commitMessage:s}=o;r=`${Qe({separator:"-",capitalize:!1})}.md`;let m=ee.replace("{{packageName}}",t).replace("{{semverLevel}}",n).replace("{{commitMessage}}",s),y=X()?Q(m):m,p=Ke.resolve(g,r);return Ze.writeFileSync(p,y),p}catch(t){throw i.error(`Failed to create file '${r}'...`),t}}import A from"os";async function re(o){let{packageName:r,semverLevel:t}=o;console.log(`${A.EOL}=== Summary of changes ===${A.EOL}`),console.log(`${e.green}${t}:${e.stop}`,r,A.EOL)}import M from"os";import{input as Xe}from"@inquirer/prompts";import{CreateFileError as eo,ExternalEditor as oo,LaunchEditorError as ro,ReadFileError as to,RemoveFileError as no}from"external-editor";function O(o){return o.replace(/^#.*\n?/gm,"").replace(/\n+$/g,"").replace(/\n\n+/,`
`).replace(/\s\s+/g," ").trim()}function ne(o){o.editor.bin="code",o.editor.args=o.editor.args.concat("--wait","--no-sandbox")}var io=`${M.EOL}${M.EOL}# An empty message aborts the editor.${M.EOL}# Please enter a summary for your changes.`;async function ie(o){let{editor:r}=o??{};console.log(`${e.green} \u2728 Please enter a summary for this change (this will appear in the CHANGELOG).`),console.log(`${e.black} \u2728 (submit empty line to open external editor)`);let n=await Xe({message:"Summary >"})||so(r);return O(n)}function so(o="vim"){try{let r=new oo(io,{postfix:".md",prefix:`${C}-`});o==="code"&&ne(r);let t=r.run();if(r.lastExitStatus!==0)throw new Error("The editor exited with a non-zero code");try{r.cleanup()}catch(n){if(n instanceof no)i.error("Failed to remove the temporary file");else throw n}return t}catch(r){throw r instanceof eo?i.error("Failed to create the temporary file"):r instanceof to?i.error("Failed to read the temporary file"):r instanceof ro&&i.error("Failed to launch your editor"),r}}import{confirm as ao}from"@inquirer/prompts";async function se(){return await ao({message:"\u2728 Is this your desired change?",default:!0,theme:{style:{defaultAnswer:()=>`${e.black}(Y/n) \u203A ${e.blue}true`}}})}import{Separator as ae,select as mo}from"@inquirer/prompts";async function me(o){let{packageName:r,currentVersion:t}=o;return await mo({message:`\u2728 What kind of change is this for ${r}? (current version is ${t})`,choices:[new ae,{name:l.Patch,value:l.Patch,description:"For bug fixes and patches",disabled:!1},{name:l.Minor,value:l.Minor,description:"For when adding a new feature or ability"},{name:l.Major,value:l.Major,description:"For when there are breaking changes"},new ae]})}import po from"fs";function pe(o){if(!o)return;let r;try{r=po.readFileSync(o,"utf-8")}catch{throw i.error(`Could not read --message-file: ${o}`,{newLineBefore:!0}),new Error}let t=O(r);if(!t)throw i.error("Summary cannot be empty... exiting...",{newLineBefore:!0}),new Error;return t}var ce="add [FLAGS]",le="Add new change";function ge(o){o.option("skip",{description:"Adding the skip option will not prompt the confirmation step, and basically skip it.",type:"boolean",default:!1}).example("lvlup add --skip","Would skip the confirmation step."),o.option("level",{alias:"l",type:"string",choices:["major","minor","patch"],description:"Semver bump type (major, minor, or patch). With --message or --message-file, runs with no prompts; otherwise you are prompted for any missing flag."}).option("message",{alias:"m",type:"string",description:"Summary for the experience (CHANGELOG). Mutually exclusive with --message-file. With --level, runs with no prompts; otherwise you are prompted for any missing flag."}).option("message-file",{alias:"f",type:"string",description:"Read the summary from a file. Mutually exclusive with --message. With --level, runs with no prompts; otherwise you are prompted for any missing flag."}).conflicts("message","message-file").example('lvlup add --level minor --message "Add widget API"',"Fully non-interactive add (for scripts, CI, and automation).").example('lvlup add -l patch -m "Fix null handling"',"Fully non-interactive add using short flags.").example("lvlup add --level minor --message-file ./release-notes.md","Fully non-interactive add with summary from a file."),o.option("editor",{type:"string",choices:["vi","vim","nano","code"],description:"Choose the external editor for editing your message."}).example("lvlup add --editor code","Would open up VsCode as editor when you hit enter on the insert message prompt.")}async function F(o){let{skip:r,editor:t,level:n,message:s,messageFile:a}=o,{packageJsonAsObject:m}=await _(),{version:y,name:p}=m;Y();let{configJsonAsObject:f}=await x();(f.add?.requireGitChanges??!0)&&z();let D=n??await me({packageName:p,currentVersion:y}),G=s??pe(a)??await ie({editor:t});if(!G)throw i.error("commit message cannot be empty... exiting...",{newLineBefore:!0}),new Error;re({packageName:p,semverLevel:D}),(n&&(s||a)||r||await se())&&await co({packageName:p,semverLevel:D,commitMessage:G})}async function co(o){let{packageName:r,semverLevel:t,commitMessage:n}=o,{configJsonAsObject:s}=await x(),{afterAdd:a}=s.commit??{},m=await oe({packageName:r,semverLevel:t,commitMessage:n});a?(await K({filenameFullPath:m,commitMessage:n}),i.info("\u2705 LVLUP added an experience file and committed it",{newLineBefore:!0})):i.info("\u2705 LVLUP added an experience file. Please go over it and then commit it",{newLineBefore:!0}),i.info("\u2705 If you want to modify the experience, or expand its summary, you can find it here:"),i.info(`\u2705 ${e.yellow}${m}${e.stop}`,{newLineAfter:!0})}import he from"node:os";import lo from"fs";import go from"gray-matter";function fe(o){return o.replace(/^[\s\r\n]+|[\s\r\n]+$/g,"")}async function $(o){let{mdVersionFilePaths:r,packageName:t}=o,n={major:[],minor:[],patch:[]};return r.forEach(s=>{let a=lo.readFileSync(s,"utf-8"),{data:m,content:y}=go(a),p=s.split("/").pop(),f=fe(y);if(!m?.[t])return;let d=m[t];if(d===l.Major)return n.major.push({level:l.Major,filename:p,description:f});if(d===l.Minor)return n.minor.push({level:l.Minor,filename:p,description:f});if(d===l.Patch)return n.patch.push({level:l.Patch,filename:p,description:f});throw new Error(`Couldn't find semver level on file ${e.red}${s}${e.stop} related to ${t}. File is corrupted...`)}),n}import k from"path";import{glob as fo}from"glob";async function P(){return(await fo(`${c}/*.md`,{ignore:[`${c}/README.md`]})).map(t=>{let n=t.replace(k.join(c,k.sep),"");return k.resolve(g,n)})}function S(o){return!o.major.length&&!o.minor.length&&!o.patch.length}function de(o){let[r,t,n]=o.split(".").map(Number);if(!(Number.isInteger(r)&&Number.isInteger(t)&&Number.isInteger(n)))throw i.error("The version listed under your package.json is corrupted! Expected to see <number>.<number>.<number>"),new Error("The value for `version` listed under your package.json is invalid... please fix it and try again");return{major:r,minor:t,patch:n}}async function V(o){let{changes:r,currentVersion:t}=o,n={...t};return r.major.length?(n.major+=1,n.minor=0,n.patch=0):r.minor.length?(n.minor+=1,n.patch=0):n.patch+=1,`${n.major}.${n.minor}.${n.patch}`}import{execSync as w}from"node:child_process";import uo from"node:fs";async function R(o){let{mdVersionFilePaths:r,version:t}=o;r.forEach(s=>{try{w(`git add -u ${s}`)}catch{i.warn(`WARNING! 'git add' operation failed. Detected an md version file which probably was not committed. Path to file was: ${e.yellow}${s}`)}}),w("git add package.json");let n=uo.readdirSync(".").find(s=>s.toLowerCase()==="changelog.md");n&&w(`git add ${n}`),w('git commit -m "RELEASING: Releasing 1 package"'),w(`git tag -a v${t} -m "Release version ${t}"`)}import ho from"fs/promises";async function N(o){let{mdVersionFilePaths:r}=o,t=[];r.forEach(n=>{let s=ho.unlink(n);t.push(s)}),await Promise.all(t)}import T from"fs";import L from"os";import yo from"path";function ue(o){return o.charAt(0).toUpperCase()+o.substring(1)}async function I(o){let{packageName:r,nextVersion:t,changes:n}=o,s=yo.resolve(v,q),a=`# ${r}`,m=`## ${t}`;for(let p in n)n[p].length&&(m=`${m}${L.EOL}${L.EOL}### ${ue(p)} Changes${L.EOL}`,n[p].forEach(f=>{let d=f.description.replace(/\n/,`
`);m=`${m}${L.EOL}- ${d}`}));T.existsSync(s)?a=T.readFileSync(s,"utf-8"):a=`${a}${L.EOL}`;let y=a.replace(`# ${r}`,`# ${r}${L.EOL}${L.EOL}${m}`);T.writeFileSync(s,y,"utf-8")}import _o from"fs/promises";import vo from"path";async function j(o){let{packageJsonAsString:r,prevVersion:t,nextVersion:n}=o,s=r.replace(t,n);await _o.writeFile(vo.resolve(v,"package.json"),s)}var ye="bump",_e="Uses all md version files added by the `add` command to calculate and bump the package's version";async function J(o){try{let{packageJsonAsObject:r,packageJsonAsString:t}=await _(),{name:n,version:s}=r,a=de(s),{configJsonAsObject:m}=await x(),{afterBump:y}=m.commit??{},p=await P(),f=await $({packageName:n,mdVersionFilePaths:p});if(S(f))return i.warn("No unreleased changes found, exiting.");let d=await V({changes:f,currentVersion:a});i.info(`New package version: ${e.yellow}${d}${e.stop}`),await j({packageJsonAsString:t,prevVersion:s,nextVersion:d}),await N({mdVersionFilePaths:p}),await I({packageName:n,nextVersion:d,changes:f}),y?(await R({mdVersionFilePaths:p,version:d}),i.info("\u2705 LVLUP - All files have been updated and committed. You're ready to publish!",{newLineBefore:!0,newLineAfter:!0})):i.info("\u2705 LVLUP - All files have been updated but not yet committed. Please review them, commit them, and then you'll be ready to publish.",{newLineBefore:!0,newLineAfter:!0})}catch(r){i.error("Something went wrong..."),console.error(r),console.log(`${he.EOL}${e.red}Existed.${he.EOL}`)}}import $e from"os";import ve from"fs";import Le from"path";function xe(){let{dirname:o}=import.meta,r=Le.resolve(o,"default.config.json"),t=Le.resolve(g,"config.json"),n=ve.createReadStream(r),s=ve.createWriteStream(t);n.pipe(s)}import Lo from"fs";function we(){Lo.mkdirSync(g,{recursive:!0})}import Ce from"fs";import be from"path";function Oe(){let{dirname:o}=import.meta,r=be.resolve(o,"default.README.md"),t=be.resolve(g,"README.md"),n=Ce.createReadStream(r),s=Ce.createWriteStream(t);n.pipe(s)}var Pe="init",Se="To start using lvlup, you first need to run the init command.";async function B(o){b()&&(i.warn(`Looks like you've already initialized ${u}. You should be able to run ${u} commands without a problems.`),process.exit(0)),we(),xe(),Oe(),i.info(`Thanks for choosing ${u} to help manage your versioning and publishing${$e.EOL}`),i.info(`You should be able to start using ${u} now!${$e.EOL}`),i.info(`info We have added a '${c}' folder, and a couple of files to help you out:`),i.info(`- ${e.blue}${c}/README.md${e.stop} contains information about using ${u}`),i.info(`- ${e.blue}${c}/config.json${e.stop} is our default config`)}import{execSync as xo}from"child_process";var Ee="publish",Ae="publishes the package to your designated registry using the rules you specified.";async function Me(o){let{packageJsonAsObject:r}=await _(),{private:t,publishConfig:n}=r;if(t)return i.warn("CANNOT publish a package that is private!");let s=n.access==="public"?"--access=public":"";xo(`npm publish ${s}`)}import{Table as wo}from"console-table-printer";var Co={headerTop:{left:`${e.blue}\u250C${e.stop}`,mid:`${e.blue}\u252C${e.stop}`,right:`${e.blue}\u2510${e.stop}`,other:`${e.blue}\u2500${e.stop}`},headerBottom:{left:`${e.blue}\u251C${e.stop}`,mid:`${e.blue}\u253C${e.stop}`,right:`${e.blue}\u2524${e.stop}`,other:`${e.blue}\u2500${e.stop}`},tableBottom:{left:`${e.blue}\u2514${e.stop}`,mid:`${e.blue}\u2534${e.stop}`,right:`${e.blue}\u2518${e.stop}`,other:`${e.blue}\u2500${e.stop}`},vertical:`${e.blue}\u2502${e.stop}`};function Fe(o){new wo({rows:o,columns:[{name:"filename",alignment:"left",color:"yellow",title:`${e.green}Filename${e.stop}`},{name:"level",alignment:"center",color:"cyan",title:`${e.green}Level${e.stop}`},{name:"description",alignment:"left",color:"white",title:`${e.green}Description${e.stop}`,maxLen:80}],style:Co}).printTable()}function ke(){console.log(`${e.blue}
_ _
___| |_ __ _| |_ _ _ ___
/ __| __/ _\` | __| | | / __|
\\__ \\ || (_| | |_| |_| \\__ \\
|___/\\__\\__,_|\\__|\\__,_|___/
${e.stop}`)}var Ve="status",Re="Show the status before bumping the package's version";async function Ne(o){let{packageJsonAsObject:r}=await _(),{name:t}=r,n=await P(),s=await $({packageName:t,mdVersionFilePaths:n}),a=[...s.major,...s.minor,...s.patch];if(S(s))return i.info("0 changes found. You are up-to-date");ke(),Fe(a)}var bo={init:B,add:F,status:Ne,bump:J,publish:Me};async function Te(o){try{let{commands:r,flags:t}=o,[n]=r;await bo[n](t)}catch(r){}}async function Ie(){console.log("1.2.0")}var Je=$o(Oo(process.argv)).completion().scriptName(`${e.green}lvlup${e.stop}`).version(!1).command(Pe,Se).command(ce,le,ge).command(Ve,Re).command(ye,_e).command(Ee,Ae).options({v:{alias:"version",type:"boolean",description:"Show lvlup version",default:!1,global:!1},h:{alias:"help",type:"boolean",description:"Show help manual",default:!1,global:!0}}).showHelpOnFail(!1,"Specify --help for available options").strict().updateStrings({"Positionals:":`${e.blue}Positionals:${e.stop}`,"Commands:":`${e.blue}Commands:${e.stop}`,"Options:":`${e.blue}Flags:${e.stop}`,"Examples:":`${e.blue}Examples:${e.stop}`}).help(!1);async function Po(){let o=Je.parse(),{$0:r,_:t,...n}=o;if(n.version&&(await Ie(),process.exit(0)),n.help||!t.length){let s=await Je.getHelp(),a=`${W}${je.EOL}${je.EOL}${s}`;console.log(a),process.exit(0)}await Te({commands:t,flags:n})}Po();