@vercel/geistdocs 1.0.8 → 1.1.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.
Files changed (2) hide show
  1. package/dist/index.js +1 -1
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import{program as C}from"commander";import{copyFile as Y,rm as tt}from"fs/promises";import{join as R}from"path";import{cancel as et,intro as st,isCancel as ot,log as nt,outro as rt,spinner as at,text as it}from"@clack/prompts";import{exec as Z}from"child_process";import{promisify as Q}from"util";var D="https://github.com/vercel/geistdocs";var m={stdio:"ignore"};var F="geistdocs-update",g=Q(Z);var ct="https://github.com/vercel/geistdocs.git",lt=async t=>{await g(`git clone --depth 1 --filter=blob:none --sparse ${ct} ${t}`,m),await g("git sparse-checkout set apps/template",{...m,cwd:t}),await g("mv apps/template/* apps/template/.[!.]* . 2>/dev/null || true",{...m,cwd:t,shell:"/bin/bash"}),await g("rm -rf apps .git",{...m,cwd:t})},pt=async()=>{await Y(".env.example",".env.local")},gt=async()=>{await tt(R("content","docs"),{recursive:!0,force:!0})},dt=async()=>{await g("pnpm install",m)},ft=async()=>{await g("git init",m),await g("git add .",m),await g('git commit -m "\u2728 Initial commit"',m)},mt=async()=>{let t=await it({message:"What is your project named?",placeholder:"my-app",validate(e){if(e.length===0)return"Please enter a project name."}});return ot(t)&&(et("Operation cancelled."),process.exit(0)),t.toString()},k=async t=>{try{st("Let's start a Geistdocs project!");let e=process.cwd(),s=t.name||await mt(),o=at(),n=R(e,s);o.start("Cloning template..."),await lt(s),o.message("Moving into project..."),process.chdir(n),o.message("Setting up environment variables..."),await pt(),o.message("Deleting demo content..."),await gt(),o.message("Installing dependencies..."),await dt(),t.disableGit||(o.message("Initializing Git repository..."),await ft()),o.stop("Project initialized successfully!"),rt("Run `pnpm dev` to start the development server.")}catch(e){let s=e instanceof Error?e.message:`Failed to initialize project: ${e}`;nt.error(s),process.exit(1)}};import{createHash as ut}from"crypto";import{existsSync as I}from"fs";import{readFile as N,writeFile as z}from"fs/promises";import{dirname as ht,parse as G,resolve as v}from"path";import{intro as wt,isCancel as yt,log as f,outro as $,spinner as Tt,text as xt}from"@clack/prompts";import{glob as $t}from"glob";import{Project as Pt,SyntaxKind as E}from"ts-morph";var A=10,vt="content/docs/**/*.mdx",O=".translation-cache.json",St=/\.[a-z]{2}\.mdx$/,bt=async t=>(await $t(t,{cwd:process.cwd(),absolute:!0,nodir:!0})).filter(s=>s.endsWith(".mdx")).filter(s=>!St.test(s)),Ft=t=>{let e=v(process.cwd(),t),n=new Pt({skipAddingFilesFromTsConfig:!0}).addSourceFileAtPath(e).getVariableDeclaration("translations");if(!n)throw new Error("Could not find translations export in config file");let a=n.getInitializer();if(!a||a.getKind()!==E.ObjectLiteralExpression)throw new Error("translations must be an object literal");let i=a.asKind(E.ObjectLiteralExpression);if(!i)throw new Error("Could not parse translations object");let d=i.getProperties().filter(l=>l.getKind()===E.PropertyAssignment).map(l=>l.asKind(E.PropertyAssignment)?.getName()).filter(l=>l!==void 0);if(d.length===0)throw new Error("No locales found in translations object");let[w,...b]=d;return b},Et=t=>ut("sha256").update(t).digest("hex"),Ct=async()=>{let t=v(process.cwd(),O);if(!I(t))return{};try{let e=await N(t,"utf-8");return JSON.parse(e)}catch{return f.warn("Failed to load translation cache, starting fresh"),{}}},_=async t=>{let e=v(process.cwd(),O);await z(e,JSON.stringify(t,null,2),"utf-8")},jt=(t,e,s,o)=>{let n=t[e];if(!n)return!0;let a=n[s];return a?a.hash!==o:!0},Dt=async(t,e,s)=>{let o=await N(t,"utf-8"),n=await fetch(s,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({content:o,locale:e})}),a=await n.text();if(!n.ok){let i=`Translation failed with status ${n.status}`;try{let d=JSON.parse(a);i=d.error||d.message||i}catch{i=a||i}throw new Error(i)}try{return JSON.parse(a).translation}catch{throw new Error(`Failed to parse response: ${a.slice(0,200)}`)}},At=async(t,e,s,o,n)=>{let a=ht(t),{name:i,ext:d}=G(t),w=v(a,`${i}.${e}${d}`);await z(w,s,"utf-8"),o[t]||(o[t]={}),o[t][e]={hash:n,translatedAt:new Date().toISOString()},await _(o)},M=async(t,e)=>{wt("Translate MDX content");let s=t??vt,o=[],n=e.url||process.env.GEISTDOCS_TRANSLATE_URL||"https://geistdocs.com/translate",a=e.config??"geistdocs.tsx";if(a)try{o=Ft(a),f.info(`Loaded ${o.length} locale(s) from config: ${o.join(", ")}`)}catch(r){f.error(`Failed to load config: ${r instanceof Error?r.message:String(r)}`),$("Translation cancelled"),process.exit(1)}let i=await bt(s);if(i.length===0&&(f.error(`No MDX files found matching pattern: ${s}`),$("Translation cancelled"),process.exit(1)),f.info(`Found ${i.length} MDX file(s) matching pattern`),o.length===0){let r=await xt({message:"Enter target locales (comma-separated):",placeholder:"cn,fr,es",validate:c=>{if(!c)return"At least one locale is required"}});yt(r)&&($("Translation cancelled"),process.exit(0)),o=r.split(",").map(c=>c.trim())}let d=v(process.cwd(),O),w=await Ct();I(d)||await _(w),f.info(`Using cache: ${d}`);let b=new Map;for(let r of i){let c=await N(r,"utf-8");b.set(r,Et(c))}let l=[],y=0;for(let r of o)for(let c of i){let x=b.get(c);if(!x)throw new Error(`Hash not found for file: ${c}`);e.force||jt(w,c,r,x)?l.push({filePath:c,locale:r,hash:x}):y+=1}if(l.length===0){f.success("All translations are up to date!"),$(`Skipped ${y} file(s) (unchanged)`);return}let T=[];for(let r=0;r<l.length;r+=A)T.push(l.slice(r,r+A));f.info(`Processing ${l.length} translation(s) in ${T.length} batch(es) of up to ${A}`),y>0&&f.info(`Skipped ${y} translation(s) (unchanged)`);let P=Tt();try{let r=[];P.start("Starting translation...");let c=0;for(let h=0;h<T.length;h++){let j=T[h];for(let p of j){let{name:L,ext:W}=G(p.filePath);P.message(`Translating ${L} to ${p.locale} - ${c}/${l.length} complete`);let q=await Dt(p.filePath,p.locale,n);await At(p.filePath,p.locale,q,w,p.hash),r.push({name:L,locale:p.locale,ext:W}),c+=1}h<T.length-1&&(P.message(`Batch ${h+1}/${T.length} complete. Pausing...`),await new Promise(p=>setTimeout(p,1e3)))}P.stop("Translation complete!");for(let{name:h,locale:j,ext:p}of r)f.success(`Saved: ${h}.${j}${p}`);let x=[`Translated ${r.length} file(s)`];y>0&&x.push(`Skipped ${y} unchanged`),$(x.join(" \u2022 "))}catch(r){let c=r instanceof Error?r.message:String(r);P.stop(`Translation failed: ${c}`),$("Exiting"),process.exit(1)}};import{copyFile as Nt,mkdir as K,rm as X}from"fs/promises";import{dirname as Ot,join as S}from"path";import{intro as Ut,log as u,outro as H,spinner as Lt}from"@clack/prompts";import{glob as Rt}from"glob";var V="apps/template",J=["**/*"],kt=["content/docs/**"],It=async t=>{let e=process.cwd(),s=S(e,t);await X(s,{recursive:!0,force:!0}),await K(s,{recursive:!0})},zt=async t=>await g(`git clone --depth 1 ${D} ${t}`),U=async t=>await X(t,{recursive:!0,force:!0}),Gt=async(t,e,s)=>(await Rt(t,{cwd:s,nodir:!0,dot:!0,ignore:[".git/**",...e]})).sort(),_t=async(t,e)=>{let s=process.cwd(),o=S(e,V,t),n=S(s,t);await K(Ot(n),{recursive:!0}),await Nt(o,n)},Mt=async(t,e,s)=>{let o={success:[],failed:[]};for(let n of t)try{s.message(`Updating ${n}...`),await _t(n,e),o.success.push(n)}catch(a){let i=a instanceof Error?a.message:`${a}`;o.failed.push({file:n,error:i})}return o},Ht=t=>{if(t.success.length>0){u.success(`Successfully updated ${t.success.length} files:`);for(let e of t.success)u.info(` \u2713 ${e}`)}if(t.failed.length>0){u.warn(`Failed to update ${t.failed.length} files:`);for(let{file:e,error:s}of t.failed)u.error(` \u2717 ${e}: ${s}`)}},B=async()=>{let t=process.cwd(),e=S(t,F);try{Ut("Let's update your Geistdocs project!"),u.info(`Repository: ${D}`);let s=Lt();s.start("Creating temporary directory..."),await It(F),s.message("Cloning repository..."),await zt(F),s.message("Scanning for files...");let o=S(e,V),n=await Gt(J,kt,o);if(s.stop(`Found ${n.length} files to update`),n.length===0){u.warn("No files matched the patterns. Nothing to update."),await U(e),H("Update cancelled.");return}u.info(`Patterns: ${J.length}`),u.info(`Files to update: ${n.length}`),s.start("Updating files...");let a=await Mt(n,e,s);s.message("Cleaning up..."),await U(e),s.stop("Update complete!"),Ht(a),H("Please review and test the changes carefully.")}catch(s){let o=s instanceof Error?s.message:`${s}`;try{await U(e)}catch{}u.error(`Failed to update project: ${o}`),process.exit(1)}};C.command("init").description("Initialize a new Geistdocs project").option("--name <name>","Name of the project").option("--disable-git","Disable git initialization").action(k);C.command("update").description("Update a Geistdocs project").action(B);C.command("translate [pattern]").description("Translate MDX file(s) to one or more locales (default: content/docs/**/*.mdx)").option("--config <path>","Path to config file to load locales from (defaults to geistdocs.tsx)").option("--url <url>","Custom translation API URL (defaults to GEISTDOCS_TRANSLATE_URL or https://geistdocs.com/translate)").option("--force","Force re-translation of all files, ignoring cache").action(M);C.parse(process.argv);
2
+ import{program as j}from"commander";import{copyFile as Q,rm as Y}from"fs/promises";import{join as L}from"path";import{cancel as tt,intro as et,isCancel as st,log as nt,outro as ot,spinner as rt,text as at}from"@clack/prompts";import{exec as q}from"child_process";import{promisify as Z}from"util";var D="https://github.com/vercel/geistdocs";var u={stdio:"ignore"};var E="geistdocs-update",d=Z(q);var it="https://github.com/vercel/geistdocs.git",ct=async t=>{await d(`git clone --depth 1 --filter=blob:none --sparse ${it} ${t}`,u),await d("git sparse-checkout set apps/template",{...u,cwd:t}),await d("mv apps/template/* apps/template/.[!.]* . 2>/dev/null || true",{...u,cwd:t,shell:"/bin/bash"}),await d("rm -rf apps .git",{...u,cwd:t})},lt=async()=>{await Q(".env.example",".env.local")},pt=async()=>{await Y(L("content","docs"),{recursive:!0,force:!0})},gt=async()=>{await d("pnpm install",u)},dt=async()=>{await d("git init",u),await d("git add .",u),await d('git commit -m "\u2728 Initial commit"',u)},ft=async()=>{let t=await at({message:"What is your project named?",placeholder:"my-app",validate(e){if(e.length===0)return"Please enter a project name."}});return st(t)&&(tt("Operation cancelled."),process.exit(0)),t.toString()},R=async t=>{try{et("Let's start a Geistdocs project!");let e=process.cwd(),n=t.name||await ft(),o=rt(),s=L(e,n);o.start("Cloning template..."),await ct(n),o.message("Moving into project..."),process.chdir(s),o.message("Setting up environment variables..."),await lt(),o.message("Deleting demo content..."),await pt(),o.message("Installing dependencies..."),await gt(),t.disableGit||(o.message("Initializing Git repository..."),await dt()),o.stop("Project initialized successfully!"),ot("Run `pnpm dev` to start the development server.")}catch(e){let n=e instanceof Error?e.message:`Failed to initialize project: ${e}`;nt.error(n),process.exit(1)}};import{createHash as mt}from"crypto";import{existsSync as I}from"fs";import{readFile as A,writeFile as z}from"fs/promises";import{dirname as ut,parse as G,resolve as v}from"path";import{intro as ht,isCancel as wt,log as f,outro as x,spinner as yt,text as Tt}from"@clack/prompts";import{glob as $t}from"glob";import{Project as xt,SyntaxKind as C}from"ts-morph";var U=10,Pt="content/docs/**/*.mdx",N=".translation-cache.json",vt=/\.[a-z]{2}\.mdx$/,St=async t=>(await $t(t,{cwd:process.cwd(),absolute:!0,nodir:!0})).filter(n=>n.endsWith(".mdx")).filter(n=>!vt.test(n)),bt=t=>{let e=v(process.cwd(),t),s=new xt({skipAddingFilesFromTsConfig:!0}).addSourceFileAtPath(e).getVariableDeclaration("translations");if(!s)throw new Error("Could not find translations export in config file");let a=s.getInitializer();if(!a||a.getKind()!==C.ObjectLiteralExpression)throw new Error("translations must be an object literal");let i=a.asKind(C.ObjectLiteralExpression);if(!i)throw new Error("Could not parse translations object");let l=i.getProperties().filter(p=>p.getKind()===C.PropertyAssignment).map(p=>p.asKind(C.PropertyAssignment)?.getName()).filter(p=>p!==void 0);if(l.length===0)throw new Error("No locales found in translations object");let[y,...b]=l;return b},Ft=t=>mt("sha256").update(t).digest("hex"),Et=async()=>{let t=v(process.cwd(),N);if(!I(t))return{};try{let e=await A(t,"utf-8");return JSON.parse(e)}catch{return f.warn("Failed to load translation cache, starting fresh"),{}}},_=async t=>{let e=v(process.cwd(),N);await z(e,JSON.stringify(t,null,2),"utf-8")},Ct=(t,e,n,o)=>{let s=t[e];if(!s)return!0;let a=s[n];return a?a.hash!==o:!0},jt=async(t,e,n)=>{let o=await A(t,"utf-8"),s=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({content:o,locale:e})}),a=await s.text();if(!s.ok){let i=`Translation failed with status ${s.status}`;try{let l=JSON.parse(a);i=l.error||l.message||i}catch{i=a||i}throw new Error(i)}try{return JSON.parse(a).translation}catch{throw new Error(`Failed to parse response: ${a.slice(0,200)}`)}},Dt=async(t,e,n,o,s)=>{let a=ut(t),{name:i,ext:l}=G(t),y=v(a,`${i}.${e}${l}`);await z(y,n,"utf-8"),o[t]||(o[t]={}),o[t][e]={hash:s,translatedAt:new Date().toISOString()},await _(o)},M=async(t,e)=>{ht("Translate MDX content");let n=t??Pt,o=[],s=e.url||process.env.GEISTDOCS_TRANSLATE_URL||"https://geistdocs.com/translate",a=e.config??"geistdocs.tsx";if(a)try{o=bt(a),f.info(`Loaded ${o.length} locale(s) from config: ${o.join(", ")}`)}catch(r){f.error(`Failed to load config: ${r instanceof Error?r.message:String(r)}`),x("Translation cancelled"),process.exit(1)}let i=await St(n);if(i.length===0&&(f.error(`No MDX files found matching pattern: ${n}`),x("Translation cancelled"),process.exit(1)),f.info(`Found ${i.length} MDX file(s) matching pattern`),o.length===0){let r=await Tt({message:"Enter target locales (comma-separated):",placeholder:"cn,fr,es",validate:c=>{if(!c)return"At least one locale is required"}});wt(r)&&(x("Translation cancelled"),process.exit(0)),o=r.split(",").map(c=>c.trim())}let l=v(process.cwd(),N),y=await Et();I(l)||await _(y),f.info(`Using cache: ${l}`);let b=new Map;for(let r of i){let c=await A(r,"utf-8");b.set(r,Ft(c))}let p=[],T=0;for(let r of o)for(let c of i){let $=b.get(c);if(!$)throw new Error(`Hash not found for file: ${c}`);e.force||Ct(y,c,r,$)?p.push({filePath:c,locale:r,hash:$}):T+=1}if(p.length===0){f.success("All translations are up to date!"),x(`Skipped ${T} file(s) (unchanged)`);return}let w=[];for(let r=0;r<p.length;r+=U)w.push(p.slice(r,r+U));f.info(`Processing ${p.length} translation(s) in ${w.length} batch(es) of up to ${U}`),T>0&&f.info(`Skipped ${T} translation(s) (unchanged)`);let P=yt();try{let r=[];P.start("Starting translation...");let c=0;for(let h=0;h<w.length;h++){let F=w[h];P.message(`Processing batch ${h+1}/${w.length} (${F.length} translations) - ${c}/${p.length} complete`),await Promise.all(F.map(async g=>{let{name:V,ext:B}=G(g.filePath),W=await jt(g.filePath,g.locale,s);await Dt(g.filePath,g.locale,W,y,g.hash),r.push({name:V,locale:g.locale,ext:B}),c+=1})),h<w.length-1&&(P.message(`Batch ${h+1}/${w.length} complete. Pausing...`),await new Promise(g=>setTimeout(g,1e3)))}P.stop("Translation complete!");for(let{name:h,locale:F,ext:g}of r)f.success(`Saved: ${h}.${F}${g}`);let $=[`Translated ${r.length} file(s)`];T>0&&$.push(`Skipped ${T} unchanged`),x($.join(" \u2022 "))}catch(r){let c=r instanceof Error?r.message:String(r);P.stop(`Translation failed: ${c}`),x("Exiting"),process.exit(1)}};import{copyFile as Ut,mkdir as H,rm as J}from"fs/promises";import{dirname as At,join as S}from"path";import{intro as Nt,log as m,outro as k,spinner as Ot}from"@clack/prompts";import{glob as Lt}from"glob";var K="apps/template",Rt=["**/*"],It=["content/docs/**"],zt=async t=>{let e=process.cwd(),n=S(e,t);await J(n,{recursive:!0,force:!0}),await H(n,{recursive:!0})},Gt=async t=>await d(`git clone --depth 1 ${D} ${t}`),O=async t=>await J(t,{recursive:!0,force:!0}),_t=async(t,e,n)=>(await Lt(t,{cwd:n,nodir:!0,dot:!0,ignore:[".git/**",...e]})).sort(),Mt=async(t,e)=>{let n=process.cwd(),o=S(e,K,t),s=S(n,t);await H(At(s),{recursive:!0}),await Ut(o,s)},kt=async(t,e,n)=>{let o={success:[],failed:[]};for(let s of t)try{n.message(`Updating ${s}...`),await Mt(s,e),o.success.push(s)}catch(a){let i=a instanceof Error?a.message:`${a}`;o.failed.push({file:s,error:i})}return o},Ht=t=>{if(t.success.length>0){m.success(`Successfully updated ${t.success.length} files:`);for(let e of t.success)m.info(` \u2713 ${e}`)}if(t.failed.length>0){m.warn(`Failed to update ${t.failed.length} files:`);for(let{file:e,error:n}of t.failed)m.error(` \u2717 ${e}: ${n}`)}},X=async t=>{let e=process.cwd(),n=S(e,E),o=t&&t.length>0?t:Rt;try{Nt("Let's update your Geistdocs project!"),m.info(`Repository: ${D}`),t&&t.length>0&&m.info(`Updating specific files: ${t.join(", ")}`);let s=Ot();s.start("Creating temporary directory..."),await zt(E),s.message("Cloning repository..."),await Gt(E),s.message("Scanning for files...");let a=S(n,K),i=await _t(o,t&&t.length>0?[]:It,a);if(s.stop(`Found ${i.length} files to update`),i.length===0){m.warn("No files matched the patterns. Nothing to update."),await O(n),k("Update cancelled.");return}m.info(`Patterns: ${o.length}`),m.info(`Files to update: ${i.length}`),s.start("Updating files...");let l=await kt(i,n,s);s.message("Cleaning up..."),await O(n),s.stop("Update complete!"),Ht(l),k("Please review and test the changes carefully.")}catch(s){let a=s instanceof Error?s.message:`${s}`;try{await O(n)}catch{}m.error(`Failed to update project: ${a}`),process.exit(1)}};j.command("init").description("Initialize a new Geistdocs project").option("--name <name>","Name of the project").option("--disable-git","Disable git initialization").action(R);j.command("update [files...]").description("Update a Geistdocs project (optionally specify files/patterns to update)").action(X);j.command("translate [pattern]").description("Translate MDX file(s) to one or more locales (default: content/docs/**/*.mdx)").option("--config <path>","Path to config file to load locales from (defaults to geistdocs.tsx)").option("--url <url>","Custom translation API URL (defaults to GEISTDOCS_TRANSLATE_URL or https://geistdocs.com/translate)").option("--force","Force re-translation of all files, ignoring cache").action(M);j.parse(process.argv);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@vercel/geistdocs",
3
3
  "description": "CLI for Geistdocs projects, including initialization and updating.",
4
- "version": "1.0.8",
4
+ "version": "1.1.0",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "geistdocs": "dist/index.js"
@@ -9,9 +9,6 @@
9
9
  "files": [
10
10
  "dist"
11
11
  ],
12
- "scripts": {
13
- "build": "tsup"
14
- },
15
12
  "dependencies": {
16
13
  "@clack/prompts": "^0.11.0",
17
14
  "commander": "^14.0.2",
@@ -24,5 +21,8 @@
24
21
  "@types/node": "^24.10.1",
25
22
  "tsup": "^8.5.1",
26
23
  "typescript": "^5.9.3"
24
+ },
25
+ "scripts": {
26
+ "build": "tsup"
27
27
  }
28
- }
28
+ }