progy 0.11.1 → 0.12.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/README.md +15 -15
- package/dist/backend/server.js +1 -1
- package/dist/cli.js +86 -22
- package/dist/public/index.html +40 -40
- package/dist/public/main.js +26 -26
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
# app
|
|
2
|
-
|
|
3
|
-
To install dependencies:
|
|
4
|
-
|
|
5
|
-
```bash
|
|
6
|
-
bun install
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
To run:
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
bun run index.ts
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
This project was created using `bun init` in bun v1.3.6. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
|
|
1
|
+
# app
|
|
2
|
+
|
|
3
|
+
To install dependencies:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
bun install
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
To run:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
bun run index.ts
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This project was created using `bun init` in bun v1.3.6. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
|
package/dist/backend/server.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
var U$=Object.create;var{getPrototypeOf:H$,defineProperty:o,getOwnPropertyNames:K$}=Object;var w$=Object.prototype.hasOwnProperty;var a$=($,V,Q)=>{Q=$!=null?U$(H$($)):{};let Y=V||!$||!$.__esModule?o(Q,"default",{value:$,enumerable:!0}):Q;for(let Z of K$($))if(!w$.call(Y,Z))o(Y,Z,{get:()=>$[Z],enumerable:!0});return Y};var n$=($,V)=>()=>(V||$((V={exports:{}}).exports,V),V.exports);var d$=import.meta.require;var{serve:m$}=globalThis.Bun;import{join as P}from"path";var i={"/api/health":Response.json({status:"ok"})};import{readdir as h,readFile as _,writeFile as m,mkdir as c,exists as A}from"fs/promises";import{join as H}from"path";import{homedir as M$}from"os";import{spawn as D$}from"child_process";var q=process.env.PROG_CWD||process.cwd(),e=H(M$(),".progy"),l=H(e,"config.json"),t=H(q,"course.json"),C=H(q,".progy"),L$=H(C,"exercises.json"),O=H(C,"progress.json"),$$=process.env.PROGY_OFFLINE==="true",$V=process.env.PROGY_API_URL||"https://progy
|
|
2
|
+
var U$=Object.create;var{getPrototypeOf:H$,defineProperty:o,getOwnPropertyNames:K$}=Object;var w$=Object.prototype.hasOwnProperty;var a$=($,V,Q)=>{Q=$!=null?U$(H$($)):{};let Y=V||!$||!$.__esModule?o(Q,"default",{value:$,enumerable:!0}):Q;for(let Z of K$($))if(!w$.call(Y,Z))o(Y,Z,{get:()=>$[Z],enumerable:!0});return Y};var n$=($,V)=>()=>(V||$((V={exports:{}}).exports,V),V.exports);var d$=import.meta.require;var{serve:m$}=globalThis.Bun;import{join as P}from"path";var i={"/api/health":Response.json({status:"ok"})};import{readdir as h,readFile as _,writeFile as m,mkdir as c,exists as A}from"fs/promises";import{join as H}from"path";import{homedir as M$}from"os";import{spawn as D$}from"child_process";var q=process.env.PROG_CWD||process.cwd(),e=H(M$(),".progy"),l=H(e,"config.json"),t=H(q,"course.json"),C=H(q,".progy"),L$=H(C,"exercises.json"),O=H(C,"progress.json"),$$=process.env.PROGY_OFFLINE==="true",$V=process.env.PROGY_API_URL||"https://progy.francy.workers.dev",F={stats:{totalXp:0,currentStreak:0,longestStreak:0,lastActiveDate:null},exercises:{},quizzes:{},achievements:[]},k=null,g=null,s=0;async function j$(){try{if(await A(t)){let $=await _(t,"utf-8");return JSON.parse($)}return null}catch($){return console.warn(`[WARN] Failed to read course.json: ${$}`),null}}async function E(){if(!k)k=await j$();return k}async function G(){if(await A(l))return JSON.parse(await _(l,"utf-8"));return{}}async function x($){let Q={...await G(),...$};return await c(e,{recursive:!0}),await m(l,JSON.stringify(Q,null,2)),Q}async function p(){if($$){try{if(await A(O)){let V=await _(O,"utf-8"),Q=JSON.parse(V);return console.log(`[OFFLINE] Loaded local progress. XP: ${Q?.stats.totalXp}`),Q}}catch(V){console.warn(`[WARN] Failed to read ${O}: ${V}`)}return JSON.parse(JSON.stringify(F))}await E();let $=await G();if($?.token&&k?.id){console.log(`[ONLINE] Fetching progress for ${k.id}...`);try{let V=await q$(k.id,$.token);if(V)return console.log(`[ONLINE] Loaded cloud progress. XP: ${V.stats.totalXp}`),V;else return console.log("[ONLINE] No existing cloud progress found. Returning default."),JSON.parse(JSON.stringify(F))}catch(V){throw console.error(`[CRITICAL] Failed to fetch cloud progress: ${V}`),V}}return JSON.parse(JSON.stringify(F))}async function u($){if($$){try{await c(C,{recursive:!0}),await m(O,JSON.stringify($,null,2))}catch(Q){console.error(`[ERROR] Failed to save local progress: ${Q}`)}return}await E();let V=await G();if(V?.token&&k?.id)await N$(k.id,$,V.token)}async function N$($,V,Q){let Y=process.env.PROGY_API_URL||"https://progy.francy.workers.dev";try{let Z=await fetch(`${Y}/api/progress/sync`,{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${Q}`},body:JSON.stringify({courseId:$,data:V})});if(Z.ok)console.log("[ONLINE] Successfully saved to cloud.");else console.warn(`[ONLINE] Cloud save failed: ${Z.status}`)}catch(Z){console.error(`[ONLINE] Connection error during save: ${Z}`)}}async function q$($,V){let Q=process.env.PROGY_API_URL||"https://progy.francy.workers.dev";try{let Y=await fetch(`${Q}/api/progress/get?courseId=${$}`,{headers:{Authorization:`Bearer ${V}`}});if(Y.ok)return await Y.json();if(Y.status===404)return null;throw Error(`Cloud fetch failed with ${Y.status}`)}catch(Y){throw Y}}function y($){let V=new Date().toISOString().split("T")[0];if($.lastActiveDate===V)return $;let Q=new Date;Q.setDate(Q.getDate()-1);let Y=Q.toISOString().split("T")[0];if($.lastActiveDate===Y)$.currentStreak+=1;else $.currentStreak=1;if($.currentStreak>$.longestStreak)$.longestStreak=$.currentStreak;return $.lastActiveDate=V,$}var r=($)=>$.replace(/_/g," ").replace(/([a-zA-Z])(\d+)/g,"$1 $2").replace(/\b\w/g,(V)=>V.toUpperCase());async function V$($){if(g&&Date.now()-s<5000)return g;let V=$.content.exercises,Q=H(q,V);if(!await A(Q))return{};let Z=(await h(Q)).filter((J)=>!J.startsWith(".")&&J!=="README.md"&&J!=="mod.rs"&&J!=="practice");Z.sort((J,X)=>{let D=parseInt(J.split("_")[0]||"999"),M=parseInt(X.split("_")[0]||"999");return D-M});let W={};async function w(J,X,D,M,j,v,N){let b=r(D);if(j[D]?.title)b=j[D].title;let L=H(v,X.name);if(X.isDirectory()){let I=["exercise.rs","main.rs","index.ts","main.go","index.js"];for(let S of I){let z=H(L,S);if(await A(z)){L=z;break}}}if(await A(L)&&(await Bun.file(L).stat()).isFile())try{let S=(await _(L,"utf-8")).match(/\/\/\s*(?:Title|title):\s*(.+)/);if(S&&S[1])b=S[1].trim()}catch{}let K={id:`${J}/${X.name}`,module:J,moduleTitle:M,name:X.name,exerciseName:D,friendlyName:b,path:H(v,X.name)};if(X.isDirectory()){let I=H(v,X.name,"quiz.json");N[J].push({...K,markdownPath:H(v,X.name,"README.md"),hasQuiz:await A(I),type:"directory"})}else if(X.isFile()){if(X.name.endsWith(".test.ts")||X.name==="package.json")return;N[J].push({...K,markdownPath:null,type:"file"})}}for(let J of Z){let X=H(Q,J);if((await Bun.file(X).stat()).isDirectory()){W[J]=[];let M=await h(X,{withFileTypes:!0}),j=r(J),v={},N=H(X,"info.toml");if(await A(N))try{let z=await _(N,"utf-8"),U=Bun.TOML.parse(z);if(U.module?.message)j=U.module.message;if(Array.isArray(U.exercises)){for(let T of U.exercises)if(T.name)v[T.name]=T}else if(U.exercises&&typeof U.exercises==="object")for(let[T,R]of Object.entries(U.exercises))v[T]=typeof R==="string"?{title:R}:R}catch(z){console.warn(`[WARN] Failed to parse info.toml: ${z}`)}let b=(z)=>{let U=z.match(/^(\d+)_/);return U?parseInt(U[1]||"0"):9999},L=new Map;for(let z of M){if(z.name.startsWith(".")||z.name==="README.md"||z.name==="mod.rs"||z.name==="info.toml")continue;L.set(z.name.split(".")[0]||"",z)}let K=Object.keys(v),I=new Set;for(let z of K){let U=L.get(z);if(U)I.add(z),await w(J,U,z,j,v,X,W)}let S=M.filter((z)=>{let U=z.name.split(".")[0]||"";return!I.has(U)&&!z.name.startsWith(".")&&z.name!=="README.md"&&z.name!=="mod.rs"&&z.name!=="info.toml"});S.sort((z,U)=>{let T=b(z.name||""),R=b(U.name||"");return T!==R?T-R:(z.name||"").localeCompare(U.name||"")});for(let z of S)await w(J,z,z.name.split(".")[0]||"",j,v,X,W)}}let B=H(Q,"practice");if(await A(B)){W.practice=[];let J=await h(B);for(let X of J)if(X.endsWith(".rs")||X.endsWith(".ts")||X.endsWith(".js")||X.endsWith(".go"))W.practice.push({id:`practice/${X}`,module:"practice",name:X,exerciseName:X.split(".")[0],path:H(B,X),markdownPath:null,type:"file"})}return await c(C,{recursive:!0}),await m(L$,JSON.stringify(W,null,2)),g=W,s=Date.now(),W}async function Q$($){let V=[];for(let Q of $.checks)if(Q.type==="command")try{let Y=Q.command.split(" "),Z=Y[0];if(!Z)continue;let W=D$(Z,Y.slice(1),{stdio:"ignore"}),w=await new Promise((B)=>{W.on("close",(J)=>B(J===0)),W.on("error",()=>B(!1))});V.push({name:Q.name,status:w?"pass":"fail",message:w?"Found":"Not found"})}catch(Y){V.push({name:Q.name,status:"fail",message:String(Y)})}return V}function X$($,V){try{let Y=null,Z=$.match(/__SRP_BEGIN__\s*([\s\S]*?)\s*__SRP_END__/);if(Z&&Z[1])Y=Z[1].trim();else{let W=$.match(/\{[\s\S]*\}/g);if(W&&W.length>0)Y=W[W.length-1]?.trim()??null}if(Y){let W=Y.indexOf("{"),w=Y.lastIndexOf("}");if(W!==-1&&w!==-1){let B=JSON.parse(Y.substring(W,w+1)),J=`## ${B.success?"\u2705 Success":"\u274C Failed"}
|
|
3
3
|
|
|
4
4
|
> ${B.summary}
|
|
5
5
|
|