progy 0.13.0 → 0.13.2

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/dist/cli.js CHANGED
@@ -164,7 +164,7 @@ ${B.message?` > ${B.message.replace(/\n/g,`
164
164
 
165
165
  `:`\u274C Failed
166
166
 
167
- `)+q}}var $8=process.env.PROGY_API_URL||"https://progy.francy.workers.dev",N1=G8({baseURL:`${$8}/api/auth`,plugins:[K8()],fetchOptions:{async onRequest(q){let Q=await O0();if(Q.token)q.options.headers={...q.options.headers,Authorization:`Bearer ${Q.token}`}}}});var i=H0(tq(),".progy","solutions-repo");async function E1(q){try{return await aq(q),!0}catch{return!1}}class w1{static async runGit(q,Q){return new Promise((Y)=>{let X=V8("git",q,{cwd:Q}),J="",W="";X.stdout?.on("data",(K)=>J+=K.toString()),X.stderr?.on("data",(K)=>W+=K.toString()),X.on("close",(K)=>Y({success:K===0,stdout:J,stderr:W}))})}static async getGitHubAccessToken(){try{let q=await N1.getAccessToken({providerId:"github"});if(q?.data?.accessToken)return q.data.accessToken;return null}catch(q){return console.error(`[SYNC] Failed to get Access Token: ${q}`),null}}static async getGitHubUser(q){try{let Q=await fetch("https://api.github.com/user",{headers:{Authorization:`Bearer ${q}`}});if(!Q.ok)return null;return await Q.json()}catch{return null}}static async isPaidUser(){try{let Q=(await N1.getSession())?.data?.user?.subscription;return Q==="pro"||Q==="lifetime"}catch{return!1}}static async ensureRepo(){if(!await this.isPaidUser())return!1;let q=await this.getGitHubAccessToken();if(!q)return console.warn("[SYNC] No GitHub access token found. Sync disabled."),!1;let Q=await this.getGitHubUser(q);if(!Q)return console.warn("[SYNC] Could not fetch GitHub user. Sync disabled."),!1;let Y=`https://x-access-token:${q}@github.com/${Q.login}/progy-solutions.git`;if(!await E1(i)){if(await Z1(H0(tq(),".progy"),{recursive:!0}),console.log("[SYNC] Attempting to clone progy-solutions repo..."),!(await this.runGit(["clone",Y,i])).success){console.log(`[SYNC] Repo not found on GitHub account ${Q.login}. Creating private 'progy-solutions'...`);let J=await fetch("https://api.github.com/user/repos",{method:"POST",headers:{Authorization:`Bearer ${q}`,"Content-Type":"application/json"},body:JSON.stringify({name:"progy-solutions",private:!0,description:"My Progy course solutions"})});if(J.ok){console.log("[SYNC] Successfully created repo on GitHub.");let W=await this.runGit(["clone",Y,i]);if(!W.success)return console.error(`[SYNC] Clone failed even after creation: ${W.stderr}`),!1}else return console.error(`[SYNC] Failed to create repo: ${await J.text()}`),!1}}else await this.runGit(["remote","set-url","origin",Y],i);return!0}static async syncUp(q,Q,Y){try{if(!await this.ensureRepo())return;let X=q.replace(/https?:\/\//i,"").replace(/[\/:]/g,"__"),J=H0(i,X,Q);if(await Z1(J,{recursive:!0}),(await aq(Y)).isDirectory())await D1(Y,J,{recursive:!0});else{let $=Y.split(/[\\/]/).pop();await D1(Y,H0(J,$))}if(await this.runGit(["add","."],i),(await this.runGit(["status","--porcelain"],i)).stdout.trim().length>0){console.log("[SYNC] Changes detected. Committing..."),await this.runGit(["commit","-m",`Sync solution: ${q}/${Q}`],i);let $=await this.runGit(["push"],i);if($.success)console.log(`[SYNC] Successfully pushed solution for ${Q} to GitHub.`);else console.warn(`[SYNC] Push failed: ${$.stderr}`)}else console.log(`[SYNC] No changes to commit for ${Q}.`)}catch(X){console.error(`[SYNC] Error during syncUp: ${X}`)}}static async hydrateCourse(q,Q,Y){try{if(!await this.ensureRepo())return;console.log("[SYNC] Fetching solutions from GitHub...");let X=await this.runGit(["pull"],i);if(!X.success)console.warn(`[SYNC] Pull failed: ${X.stderr}`);let J=q.replace(/https?:\/\//i,"").replace(/[\/:]/g,"__"),W=H0(i,J);if(!await E1(W)){console.log(`[SYNC] No solutions found for ${q} in GitHub repository.`);return}console.log(`[SYNC] Hydrating ${q} solutions into ${Q}...`);let K=H0(Q,Y);if(!await E1(K))await Z1(K,{recursive:!0});await D1(W,K,{recursive:!0}),console.log("[SYNC] Hydration complete.")}catch(X){console.error(`[SYNC] Error during hydration: ${X}`)}}}var T1={name:"progy",version:"0.13.0",description:"The interactive CLI and learning platform for Progy courses.",license:"MIT",repository:{type:"git",url:"git+https://github.com/fhorray/progy.git"},keywords:["cli","education","learning","rust","go","interactive"],author:"Progy Team",type:"module",main:"./dist/cli.js",module:"./dist/cli.js",bin:{progy:"./dist/cli.js"},files:["dist"],publishConfig:{access:"public"},scripts:{dev:"bun run --hot src/backend/server.ts",start:"bun run src/backend/server.ts",build:"bun run ../../scripts/build.ts",prepublishOnly:"bun run build"},devDependencies:{"@nanostores/query":"^0.3.4","@nanostores/react":"^0.7.2","@radix-ui/react-dialog":"^1.1.15","@radix-ui/react-dropdown-menu":"^2.1.16","@radix-ui/react-label":"^2.1.8","@radix-ui/react-tabs":"^1.1.13","@types/adm-zip":"^0.5.7","@types/bun":"latest","@types/react":"^19.0.0","@types/react-dom":"^19.0.0",autoprefixer:"^10.4.19","class-variance-authority":"^0.7.0",clsx:"^2.1.1","highlight.js":"^11.11.1","lucide-react":"^0.394.0",nanostores:"^0.10.3","radix-ui":"^1.0.1",react:"19.2.4","react-dom":"19.2.4","react-markdown":"^9.0.1","rehype-highlight":"^7.0.0","tailwind-merge":"^2.3.0"},peerDependencies:{typescript:"^5"},dependencies:{"@ai-sdk/react":"^3.0.79",ai:"^6.0.77","adm-zip":"^0.5.16","better-auth":"^1.4.18",cmdk:"^1.1.1",commander:"^12.1.0","use-stick-to-bottom":"^1.1.3",zod:"^4.3.6"}};var F1=T(b0(),".progy"),P1=T(F1,"config.json"),j8=process.env.PROGY_API_URL||"https://progy.francy.workers.dev",R8=process.env.PROGY_FRONTEND_URL||"https://progy.francy.workers.dev",q9=!import.meta.file.includes("node_modules"),I8=q9?"\x1B[33m(local/dev)\x1B[0m":"\x1B[32m(npm)\x1B[0m";if(!process.argv.includes("--completion")&&!process.argv.includes("completion"))console.log(`\x1B[1m[Progy]\x1B[0m v${T1.version} ${I8}`);var A0="course.json";async function f(q){try{return await U8(q),!0}catch{return!1}}async function M8(){let q=T(import.meta.dir,"../../../courses");if(await f(q))return q;return null}async function N8(q){if(!await f(F1))await Y0(F1,{recursive:!0});await p(P1,JSON.stringify({token:q}))}async function Z8(){if(!await f(P1))return null;try{return JSON.parse(await C0(P1,"utf-8")).token||null}catch{return null}}function D8(q){let Q=process.platform==="win32"?"start":process.platform==="darwin"?"open":"xdg-open";eq(Q,[q],{shell:!0}).unref()}async function Q9(q,Q,Y){console.log(`[INFO] Starting UI in ${Q?"OFFLINE":"ONLINE"} mode...`);let J=import.meta.file.endsWith(".ts")?"ts":"js",K=["run",T(import.meta.dir,"backend",`server.${J}`)];if(process.env.ENABLE_HMR==="true")K.splice(1,0,"--hot");let $=eq("bun",K,{stdio:"inherit",env:{...process.env,PROG_CWD:q,PROGY_OFFLINE:Q?"true":"false"}}),L=()=>{if($&&!$.killed)$.kill();process.exit(0)};if(process.on("SIGINT",L),process.on("SIGTERM",L),process.on("exit",()=>{if($&&!$.killed)$.kill()}),Y){console.log("[SYNC] Auto-save enabled.");let{watch:B}=await import("fs"),I=null,N=B(q,{recursive:!0},(w,V)=>{if(!V||V.includes(".git")||V.includes("node_modules"))return;if(I)clearTimeout(I);I=setTimeout(async()=>{try{await J0.sync(q,Y)}catch(z){console.error(`[SYNC] Failed to save: ${z}`)}},1000)});$.on("close",()=>{N.close(),console.log("[SYNC] Final save..."),J0.sync(q,Y).then(()=>process.exit(0))})}else $.on("close",(B)=>process.exit(B??0))}async function d0(q,Q){let Y=process.cwd(),X=!!Q.offline,J=Y,W=null;if(q&&q.endsWith(".progy")&&await f(q))W=O1(q);else{let $=(await z8(Y)).filter((L)=>L.endsWith(".progy"));if($.length>0){if($.length>1)console.warn(`[WARN] Multiple .progy files found. Using ${$[0]}`);W=T(Y,$[0])}}if(W){console.log(`[OPEN] Opening course: ${H8(W)}...`);try{if(J=await J0.unpack(W),console.log(`[KT] Runtime ready at: ${J}`),!X)try{let K=await e.validateCourse(J);await w1.hydrateCourse(K.id,J,K.content.exercises)}catch(K){console.warn(`[SYNC] Hydration skipped: ${K}`)}}catch(K){console.error(`[ERROR] Failed to unpack course: ${K}`),process.exit(1)}}else if(await f(T(Y,A0)))console.log("[INFO] Legacy course directory detected.");else console.log("[INFO] No .progy file or course structure found."),console.log("Run 'progy init' to create a new course.");await Q9(J,X,W)}r.name("progy").description("Universal programming course runner").version(`${T1.version} ${q9?"(local/dev)":"(npm)"}`);r.command("validate").description("Validate the current directory as a Progy course").argument("[path]","Path to course directory",".").action(async(q)=>{let Q=O1(q);console.log(`[VAL] Validating course at: ${Q}`);try{let Y=await e.validateCourse(Q);console.log(`
167
+ `)+q}}var $8=process.env.PROGY_API_URL||"https://progy.francy.workers.dev",N1=G8({baseURL:`${$8}/api/auth`,plugins:[K8()],fetchOptions:{async onRequest(q){let Q=await O0();if(Q.token)q.options.headers={...q.options.headers,Authorization:`Bearer ${Q.token}`}}}});var i=H0(tq(),".progy","solutions-repo");async function E1(q){try{return await aq(q),!0}catch{return!1}}class w1{static async runGit(q,Q){return new Promise((Y)=>{let X=V8("git",q,{cwd:Q}),J="",W="";X.stdout?.on("data",(K)=>J+=K.toString()),X.stderr?.on("data",(K)=>W+=K.toString()),X.on("close",(K)=>Y({success:K===0,stdout:J,stderr:W}))})}static async getGitHubAccessToken(){try{let q=await N1.getAccessToken({providerId:"github"});if(q?.data?.accessToken)return q.data.accessToken;return null}catch(q){return console.error(`[SYNC] Failed to get Access Token: ${q}`),null}}static async getGitHubUser(q){try{let Q=await fetch("https://api.github.com/user",{headers:{Authorization:`Bearer ${q}`}});if(!Q.ok)return null;return await Q.json()}catch{return null}}static async isPaidUser(){try{let Q=(await N1.getSession())?.data?.user?.subscription;return Q==="pro"||Q==="lifetime"}catch{return!1}}static async ensureRepo(){if(!await this.isPaidUser())return!1;let q=await this.getGitHubAccessToken();if(!q)return console.warn("[SYNC] No GitHub access token found. Sync disabled."),!1;let Q=await this.getGitHubUser(q);if(!Q)return console.warn("[SYNC] Could not fetch GitHub user. Sync disabled."),!1;let Y=`https://x-access-token:${q}@github.com/${Q.login}/progy-solutions.git`;if(!await E1(i)){if(await Z1(H0(tq(),".progy"),{recursive:!0}),console.log("[SYNC] Attempting to clone progy-solutions repo..."),!(await this.runGit(["clone",Y,i])).success){console.log(`[SYNC] Repo not found on GitHub account ${Q.login}. Creating private 'progy-solutions'...`);let J=await fetch("https://api.github.com/user/repos",{method:"POST",headers:{Authorization:`Bearer ${q}`,"Content-Type":"application/json"},body:JSON.stringify({name:"progy-solutions",private:!0,description:"My Progy course solutions"})});if(J.ok){console.log("[SYNC] Successfully created repo on GitHub.");let W=await this.runGit(["clone",Y,i]);if(!W.success)return console.error(`[SYNC] Clone failed even after creation: ${W.stderr}`),!1}else return console.error(`[SYNC] Failed to create repo: ${await J.text()}`),!1}}else await this.runGit(["remote","set-url","origin",Y],i);return!0}static async syncUp(q,Q,Y){try{if(!await this.ensureRepo())return;let X=q.replace(/https?:\/\//i,"").replace(/[\/:]/g,"__"),J=H0(i,X,Q);if(await Z1(J,{recursive:!0}),(await aq(Y)).isDirectory())await D1(Y,J,{recursive:!0});else{let $=Y.split(/[\\/]/).pop();await D1(Y,H0(J,$))}if(await this.runGit(["add","."],i),(await this.runGit(["status","--porcelain"],i)).stdout.trim().length>0){console.log("[SYNC] Changes detected. Committing..."),await this.runGit(["commit","-m",`Sync solution: ${q}/${Q}`],i);let $=await this.runGit(["push"],i);if($.success)console.log(`[SYNC] Successfully pushed solution for ${Q} to GitHub.`);else console.warn(`[SYNC] Push failed: ${$.stderr}`)}else console.log(`[SYNC] No changes to commit for ${Q}.`)}catch(X){console.error(`[SYNC] Error during syncUp: ${X}`)}}static async hydrateCourse(q,Q,Y){try{if(!await this.ensureRepo())return;console.log("[SYNC] Fetching solutions from GitHub...");let X=await this.runGit(["pull"],i);if(!X.success)console.warn(`[SYNC] Pull failed: ${X.stderr}`);let J=q.replace(/https?:\/\//i,"").replace(/[\/:]/g,"__"),W=H0(i,J);if(!await E1(W)){console.log(`[SYNC] No solutions found for ${q} in GitHub repository.`);return}console.log(`[SYNC] Hydrating ${q} solutions into ${Q}...`);let K=H0(Q,Y);if(!await E1(K))await Z1(K,{recursive:!0});await D1(W,K,{recursive:!0}),console.log("[SYNC] Hydration complete.")}catch(X){console.error(`[SYNC] Error during hydration: ${X}`)}}}var T1={name:"progy",version:"0.13.2",description:"The interactive CLI and learning platform for Progy courses.",license:"MIT",repository:{type:"git",url:"git+https://github.com/fhorray/progy.git"},keywords:["cli","education","learning","rust","go","interactive"],author:"Progy Team",type:"module",main:"./dist/cli.js",module:"./dist/cli.js",bin:{progy:"./dist/cli.js"},files:["dist"],publishConfig:{access:"public"},scripts:{dev:"bun run --hot src/backend/server.ts",start:"bun run src/backend/server.ts",build:"bun run ../../scripts/build.ts",prepublishOnly:"bun run build"},devDependencies:{"@nanostores/query":"^0.3.4","@nanostores/react":"^0.7.2","@radix-ui/react-dialog":"^1.1.15","@radix-ui/react-dropdown-menu":"^2.1.16","@radix-ui/react-label":"^2.1.8","@radix-ui/react-tabs":"^1.1.13","@types/adm-zip":"^0.5.7","@types/bun":"latest","@types/react":"^19.0.0","@types/react-dom":"^19.0.0",autoprefixer:"^10.4.19","class-variance-authority":"^0.7.0",clsx:"^2.1.1","highlight.js":"^11.11.1","lucide-react":"^0.394.0",nanostores:"^0.10.3","radix-ui":"^1.0.1",react:"19.2.4","react-dom":"19.2.4","react-markdown":"^9.0.1","rehype-highlight":"^7.0.0","tailwind-merge":"^2.3.0"},peerDependencies:{typescript:"^5"},dependencies:{"@ai-sdk/react":"^3.0.79",ai:"^6.0.77","adm-zip":"^0.5.16","better-auth":"^1.4.18",cmdk:"^1.1.1",commander:"^12.1.0","use-stick-to-bottom":"^1.1.3",zod:"^4.3.6"}};var F1=T(b0(),".progy"),P1=T(F1,"config.json"),j8=process.env.PROGY_API_URL||"https://progy.francy.workers.dev",R8=process.env.PROGY_FRONTEND_URL||"https://progy.francy.workers.dev",q9=!import.meta.file.includes("node_modules"),I8=q9?"\x1B[33m(local/dev)\x1B[0m":"\x1B[32m(npm)\x1B[0m";if(!process.argv.includes("--completion")&&!process.argv.includes("completion"))console.log(`\x1B[1m[Progy]\x1B[0m v${T1.version} ${I8}`);var A0="course.json";async function f(q){try{return await U8(q),!0}catch{return!1}}async function M8(){let q=T(import.meta.dir,"../../../courses");if(await f(q))return q;return null}async function N8(q){if(!await f(F1))await Y0(F1,{recursive:!0});await p(P1,JSON.stringify({token:q}))}async function Z8(){if(!await f(P1))return null;try{return JSON.parse(await C0(P1,"utf-8")).token||null}catch{return null}}function D8(q){let Q=process.platform==="win32"?"start":process.platform==="darwin"?"open":"xdg-open";eq(Q,[q],{shell:!0}).unref()}async function Q9(q,Q,Y){console.log(`[INFO] Starting UI in ${Q?"OFFLINE":"ONLINE"} mode...`);let J=import.meta.file.endsWith(".ts")?"ts":"js",K=["run",T(import.meta.dir,"backend",`server.${J}`)];if(process.env.ENABLE_HMR==="true")K.splice(1,0,"--hot");let $=eq("bun",K,{stdio:"inherit",env:{...process.env,PROG_CWD:q,PROGY_OFFLINE:Q?"true":"false"}}),L=()=>{if($&&!$.killed)$.kill();process.exit(0)};if(process.on("SIGINT",L),process.on("SIGTERM",L),process.on("exit",()=>{if($&&!$.killed)$.kill()}),Y){console.log("[SYNC] Auto-save enabled.");let{watch:B}=await import("fs"),I=null,N=B(q,{recursive:!0},(w,V)=>{if(!V||V.includes(".git")||V.includes("node_modules"))return;if(I)clearTimeout(I);I=setTimeout(async()=>{try{await J0.sync(q,Y)}catch(z){console.error(`[SYNC] Failed to save: ${z}`)}},1000)});$.on("close",()=>{N.close(),console.log("[SYNC] Final save..."),J0.sync(q,Y).then(()=>process.exit(0))})}else $.on("close",(B)=>process.exit(B??0))}async function d0(q,Q){let Y=process.cwd(),X=!!Q.offline,J=Y,W=null;if(q&&q.endsWith(".progy")&&await f(q))W=O1(q);else{let $=(await z8(Y)).filter((L)=>L.endsWith(".progy"));if($.length>0){if($.length>1)console.warn(`[WARN] Multiple .progy files found. Using ${$[0]}`);W=T(Y,$[0])}}if(W){console.log(`[OPEN] Opening course: ${H8(W)}...`);try{if(J=await J0.unpack(W),console.log(`[KT] Runtime ready at: ${J}`),!X)try{let K=await e.validateCourse(J);await w1.hydrateCourse(K.id,J,K.content.exercises)}catch(K){console.warn(`[SYNC] Hydration skipped: ${K}`)}}catch(K){console.error(`[ERROR] Failed to unpack course: ${K}`),process.exit(1)}}else if(await f(T(Y,A0)))console.log("[INFO] Legacy course directory detected.");else console.log("[INFO] No .progy file or course structure found."),console.log("Run 'progy init' to create a new course.");await Q9(J,X,W)}r.name("progy").description("Universal programming course runner").version(`${T1.version} ${q9?"(local/dev)":"(npm)"}`);r.command("validate").description("Validate the current directory as a Progy course").argument("[path]","Path to course directory",".").action(async(q)=>{let Q=O1(q);console.log(`[VAL] Validating course at: ${Q}`);try{let Y=await e.validateCourse(Q);console.log(`
168
168
  \u2705 Course is Valid!`),console.log(` ID: ${Y.id}`),console.log(` Name: ${Y.name}`),console.log(` Content: ${Y.content.root}`)}catch(Y){console.error(`
169
169
  \u274C Validation Failed:`),console.error(Y.message),process.exit(1)}});r.command("dev").description("Run the current directory as a course (Hot-reload/No-packaging)").option("--offline","Run in offline mode").action(async(q)=>{let Q=process.cwd();try{await e.validateCourse(Q)}catch(Y){console.error("[ERROR] Current directory is not a valid course:"),console.error(Y.message),process.exit(1)}console.log(`[DEV] Starting in Development Mode (Source: ${Q})`),await Q9(Q,!!q.offline,null)});r.command("pack").description("Package the current directory into a .progy file").option("-o, --out <file>","Output filename").action(async(q)=>{let Q=process.cwd();try{let Y=await e.validateCourse(Q),X=q.out||`${Y.id}.progy`,J=O1(X);console.log(`[PACK] Packaging course '${Y.id}'...`),await J0.pack(Q,J),console.log(`[SUCCESS] Created: ${X}`)}catch(Y){console.error(`
170
170
  \u274C Packaging Failed:`),console.error(Y.message),process.exit(1)}});r.command("init").description("Initialize a new course in the current directory").option("-c, --course <course>","Language/Course to initialize (e.g., rust)").option("--offline","Run in offline mode (Guest access, local storage only)").action(async(q)=>{let Q=process.cwd(),Y=!!q.offline,X=T(Q,A0),J=await f(X),W=null,K=q.course||"generic";if(J&&!q.course){console.log(`[INFO] Detected '${A0}'. Starting progy...`);let V=JSON.parse(await C0(X,"utf-8"));console.log(`[INFO] Course '${V.name}' found.`),await d0(void 0,{offline:Y});return}let $=null;if(!Y){if($=await Z8(),!$)console.error("\u274C Authentication required for Online Mode."),process.exit(1)}if(console.log(`[INFO] Initializing ${K} course in ${Q}...`),K==="generic"){let V=x0.generic;if(!V)throw Error("Generic template not found");await p(T(Q,A0),JSON.stringify(V.courseJson,null,2)),await p(T(Q,"SETUP.md"),V.setupMd);let z=T(Q,"content","01_intro");await Y0(z,{recursive:!0}),await p(T(z,"README.md"),V.introReadme),await p(T(z,V.introFilename),V.introCode);let j=T(Q,"runner");await Y0(j,{recursive:!0}),await p(T(j,"README.md"),r1),await p(T(j,"index.js"),`