zerostarter 0.1.34 → 0.1.35

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 CHANGED
@@ -58,6 +58,8 @@ Re-baseline a fork on the latest ZeroStarter. A gitpick overlay updates the star
58
58
 
59
59
  Skills you own are preserved too: sync only rewrites a skill whose `source` is this repo. One you authored, one you vendored from a tool, one you took from another upstream, one you edited, or one whose sync note you removed keeps your version, and sync names it in the summary rather than replacing it.
60
60
 
61
+ `AGENTS.md` ships to a fork whole, rebranded, and follows the same contract: sync hands you the starter's latest while your copy is unedited, and keeps yours once you have changed it.
62
+
61
63
  ## Requirements
62
64
 
63
65
  - **[Bun](https://bun.sh)** runs the scaffolded project. If it is missing, the CLI offers to install it for you.
@@ -1,15 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import{A as e,C as t,D as n,E as r,M as i,N as a,O as o,P as s,S as c,T as l,_ as u,a as d,b as f,c as p,d as m,f as h,g,h as _,i as ee,j as v,k as y,l as b,m as te,n as x,o as S,p as C,r as ne,s as re,t as ie,u as ae,v as w,w as T,x as E,y as D}from"../convert-SFS6yxN2.mjs";import{basename as oe,delimiter as se,dirname as ce,join as O,parse as le,resolve as k}from"node:path";import{existsSync as A,readFileSync as ue,readdirSync as de}from"node:fs";import{randomBytes as fe}from"node:crypto";import{createInterface as pe}from"node:readline/promises";import{parseArgs as me}from"node:util";import{homedir as he}from"node:os";var ge={name:`zerostarter`,version:`0.1.34`,description:`Go from zero to a production-ready SaaS, rebranded and ready to ship.`,keywords:[`cli`,`saas`,`scaffold`,`starter`,`template`,`zerostarter`],homepage:`https://github.com/nrjdalal/zerostarter#readme`,bugs:`https://github.com/nrjdalal/zerostarter/issues`,license:`MIT`,author:{name:`Neeraj Dalal`,email:`admin@nrjdalal.com`,url:`https://nrjdalal.com`},repository:`nrjdalal/zerostarter`,funding:`https://github.com/sponsors/nrjdalal`,bin:{zerostarter:`dist/bin/index.mjs`},files:[`dist`],type:`module`,types:`./dist/index.d.mts`,exports:`./dist/index.mjs`,scripts:{build:`tsdown`,"check-types":`tsc --noEmit`,dev:`tsdown --watch`},devDependencies:{"@packages/config":`workspace:*`,"@types/bun":`catalog:`,"@types/node":`catalog:`,tsdown:`catalog:`,typescript:`catalog:`}};const j=()=>!!(process.stdin.isTTY&&process.stdout.isTTY),M=(e,t=e)=>process.stdout.isTTY?`\x1b]8;;${e}\x07${t}\x1b]8;;\x07`:e,N=e=>M(e,u(e)),P=e=>{process.stdout.write(`\n ${D(g.barStart)} ${e}\n ${D(g.bar)}\n`)},F=e=>{process.stdout.write(` ${D(g.bar)}\n ${D(g.barEnd)} ${e}\n`)},I=(e=`Cancelled`)=>{process.stdout.write(` ${D(g.barEnd)} ${t(e)}\n`)},L=e=>e.replace(/* @__PURE__ */ RegExp(`\x1B\\[[0-9;?]*[a-zA-Z]`,`g`),``).replace(/* @__PURE__ */ RegExp(`\x1B\\]8;;.*?\\x07`,`g`),``).length,R=(e,t=``,n=!1)=>{let r=`\n${e}\n`.split(`
3
- `),i=L(t),a=r.reduce((e,t)=>Math.max(e,L(t)),i),o=Math.max(a,54)+2,s=r.map(e=>` ${D(g.bar)} ${e}${` `.repeat(o-L(e))}${D(g.bar)}`).join(`
4
- `),c=n?g.cornerBL:g.connectL;process.stdout.write(` ${u(g.active)} ${t} ${D(g.barH.repeat(Math.max(o-i-1,1))+g.cornerTR)}\n${s}\n ${D(c+g.barH.repeat(o+2)+g.cornerBR)}\n`)},z=(e,t=[])=>{process.stdout.write(` ${u(g.active)} ${e}\n`);for(let e of t)process.stdout.write(` ${D(g.bar)} ${e}\n`)},_e=e=>{process.stdout.write(` ${f(g.active)} ${f(e)}\n`)},B=(e,t=[])=>{process.stdout.write(` ${T(g.warn)} ${T(e)}\n`);for(let e of t)process.stdout.write(` ${D(g.bar)} ${T(e)}\n`)},ve=async(e,t,n)=>{let r=process.stdout,i=null;if(r.isTTY){let t=0;r.write(` ${c(0,e)}`),i=setInterval(()=>{t+=1,r.write(`\r\x1b[K ${c(t,e)}`)},400)}try{let e=await n();return i&&clearInterval(i),r.write(r.isTTY?`\r\x1b[K ${u(g.active)} ${t}\n`:` ${u(g.active)} ${t}\n`),e}catch(e){throw i&&clearInterval(i),r.isTTY&&r.write(`\r\x1B[K`),e}},ye=e=>{let t=e?`${f(g.radioOn)} Yes`:w(`${g.radioOff} Yes`),n=e?w(`${g.radioOff} No`):`${f(g.radioOn)} No`;return` ${D(g.bar)} ${t} ${w(`/`)} ${n}`},V=async(e,t=!0)=>{let n=process.stdout;return j()?new Promise(r=>{let i=t,a=process.stdin,o=Math.max(1,Math.ceil((5+L(e))/(n.columns||80)));n.write(` ${u(g.submit)} ${e}\n`),n.write(ye(i)),a.setRawMode(!0),a.resume(),a.setEncoding(`utf8`);let s=()=>{a.setRawMode(!1),a.pause(),a.removeListener(`data`,d)},c=()=>{n.write(`\r\x1b[K\x1b[${o}A\r\x1b[0J`)},l=()=>{s(),c(),r(i)};function d(t){t===``?(s(),c(),n.write(` ${u(g.submit)} ${w(e)}\n`),I(),process.exit(130)):t===`\r`||t===`
5
- `?l():t===`y`||t===`Y`?(i=!0,l()):t===`n`||t===`N`?(i=!1,l()):(t===`\x1B[C`||t===`\x1B[D`||t===`\x1B[A`||t===`\x1B[B`||t===`h`||t===`l`)&&(i=!i,n.write(`\r\x1b[K${ye(i)}`))}a.on(`data`,d)}):(n.write(` ${u(g.active)} ${e} ${w(t?`Yes`:`No`)}\n`),t)},be=async(e,t)=>{if(t.length===0)return[];let n=process.stdout,r=new Set(t.filter(e=>e.checked).map(e=>e.value)),i=()=>t.filter(e=>r.has(e.value)).map(e=>e.label).join(`, `)||`none`;return j()?new Promise(a=>{let o=process.stdin,s=0,c=e=>{let n=t[e],i=r.has(n.value)?f(g.checkboxOn):w(g.checkboxOff),a=e===s?n.label:w(n.label);return` ${D(g.bar)} ${i} ${a}`},l=e=>{e||n.write(`\x1b[${t.length}A`);for(let e=0;e<t.length;e++)n.write(`\r\x1b[K${c(e)}\n`)};n.write(` ${u(g.submit)} ${e}\n`),l(!0),o.setRawMode(!0),o.resume(),o.setEncoding(`utf8`);let d=()=>{o.setRawMode(!1),o.pause(),o.removeListener(`data`,p)};function p(o){if(o===``)d(),n.write(`\x1b[${t.length+1}A\r\x1b[0J ${u(g.submit)} ${w(e)}\n`),I(),process.exit(130);else if(o===`\r`||o===`
6
- `)d(),n.write(`\x1b[${t.length+1}A\r\x1b[0J ${u(g.submit)} ${e} ${w(i())}\n`),a([...r]);else if(o===`\x1B[A`||o===`k`)s=(s-1+t.length)%t.length,l(!1);else if(o===`\x1B[B`||o===`j`)s=(s+1)%t.length,l(!1);else if(o===` `){let e=t[s].value;r.has(e)?r.delete(e):r.add(e),l(!1)}}o.on(`data`,p)}):(n.write(` ${u(g.submit)} ${e} ${w(i())}\n`),[...r])},xe=async(e,t=``)=>{process.stdout.write(` ${u(g.submit)} ${e}${t?` ${w(`(${t})`)}`:``}\n`);let n=pe({input:process.stdin,output:process.stdout});try{return(await n.question(` ${D(g.bar)} `)).trim()||t}finally{n.close()}},Se=`pglaunch@5.5.7`,Ce=async()=>m(`docker`,[`info`]),we=e=>{let t=O(e,`.env`);if(!n(t)){let r=O(e,`.env.example`);i(t,n(r)?y(r):``)}return t},H=(e,t)=>{let n=`${t}=`,r=y(e).split(`
7
- `).find(e=>e.startsWith(n));return r?r.slice(n.length).trim():``},U=(e,t,n)=>{let r=`${t}=`,a=y(e).split(`
8
- `),o=a.findIndex(e=>e.startsWith(r));if(o>=0)a[o]=`${r}${n}`;else{for(;a.length&&a[a.length-1]===``;)a.pop();a.push(`${r}${n}`,``)}i(e,a.join(`
9
- `))},Te=e=>{let t=we(e);H(t,`BETTER_AUTH_SECRET`)||U(t,`BETTER_AUTH_SECRET`,fe(32).toString(`base64`)),H(t,`AGENT_SIGNIN_ENABLED`)||U(t,`AGENT_SIGNIN_ENABLED`,`true`)},W=e=>{let t=O(e,`.env`);return n(t)&&H(t,`POSTGRES_URL`)!==``},Ee=e=>{let t=e.match(/postgres(?:ql)?:\/\/[\w.:@\-/%?=&]+/);if(!t)return null;let n=e.match(/name "([^" ]+) :\d+" started/),r=e.match(/similar name "([^"]+)"/);return{url:t[0],container:n?n[1]:r?r[1]:``}},De=async(e,t)=>{let n=t?[`--bun`,Se,`-k`,`-c`]:[`--bun`,Se,`-k`],r;try{r=await C(`bunx`,n,e)}catch(e){let t=e;r=[t.stdout,t.stderr].filter(Boolean).join(`
10
- `)}return{launch:Ee(r),out:r}},Oe=e=>new Promise(t=>setTimeout(t,e)),ke=async e=>{if(e)for(let t=0;t<30;t++){if(await m(`docker`,[`exec`,e,`pg_isready`,`-U`,`postgres`]))return;await Oe(1e3)}},Ae=async e=>{let t=we(e),n=await De(e,!1),r=n.launch?n:await De(e,!0);if(!r.launch)throw Error(r.out.trim()||`pglaunch did not print a connection URL`);U(t,`POSTGRES_URL`,r.launch.url),await ke(r.launch.container),await _(`bun`,[`run`,`db:migrate`],{cwd:e,label:`Provisioning a local Postgres database`,done:`Provisioned a local Postgres database`})},G=async e=>{await _(`bun`,[`install`],{cwd:e,label:`Installing dependencies`,done:`Installed dependencies`})},je=async()=>{if(!await m(`bun`,[`--version`]))throw Error(`bun is not on PATH`)},Me=async(e=je,t=!!process.versions.bun,n=process.env.npm_config_user_agent||``)=>{if(t||n.startsWith(`bun/`))return!0;try{return await e(),!0}catch{return!1}},Ne=async(e,t=`main`)=>{await C(`bunx`,[`--bun`,`gitpick@6.0.0`,`https://github.com/nrjdalal/zerostarter/tree/${t}`,e])},Pe=async(e,t=`main`)=>{await C(`bunx`,[`--bun`,`gitpick@6.0.0`,`https://github.com/nrjdalal/zerostarter/tree/${t}`,e,`-o`])},Fe=async(e=`main`)=>{let t=`https://raw.githubusercontent.com/nrjdalal/zerostarter/${e}/.gitpickignore`,n=await fetch(t);if(!n.ok)throw Error(`Could not fetch .gitpickignore from ${e} (HTTP ${n.status}).`);return n.text()},Ie=async e=>(await C(`git`,[`status`,`--porcelain`],e)).trim()===``,Le=async(e,t)=>{for(let n of t)try{await C(`git`,[`checkout`,`--`,n],e),await C(`git`,[`clean`,`-fd`,`--`,n],e)}catch{}},Re=async e=>{await C(`git`,[`reset`,`--hard`,`HEAD`],e),await C(`git`,[`clean`,`-fd`],e)},ze=async(e,t,r)=>{if(!n(O(e,`.git`)))throw Error(t);if(!await Ie(e))throw Error(r)},Be=async(e,t,n)=>{try{await n()}catch(n){throw await Re(e),console.log(t),n}},Ve=async e=>{await C(`git`,[`init`,`-q`,`-b`,`canary`],e)},He=async(e,t)=>{await C(`git`,[`branch`,t],e)},K=async(e,t)=>{await C(`git`,[`add`,`-A`],e);try{await C(`git`,[`commit`,`--no-verify`,`-q`,`-m`,t],e)}catch{}},q=(e,n)=>{try{return me(n)}catch(n){process.stderr.write(`${t(n instanceof Error?n.message:String(n))}\n\n`),process.stdout.write(`${e}\n`),process.exit(1)}},Ue=(e=process.env.npm_config_user_agent||``)=>e.startsWith(`bun/`)?`bunx`:e.startsWith(`pnpm/`)?`pnpm dlx`:e.startsWith(`yarn/`)?`yarn dlx`:e.startsWith(`npm/`)?`npx`:`unknown`,We=(e=process.platform)=>e===`win32`?{cmd:`powershell`,args:[`-Command`,`irm bun.sh/install.ps1 | iex`]}:{cmd:`bash`,args:[`-c`,`curl -fsSL https://bun.sh/install | bash`]},Ge=(e=process.argv.slice(2))=>[`zerostarter`,...e].join(` `),Ke=()=>O(process.env.BUN_INSTALL||O(he(),`.bun`),`bin`),J=async(e=!1)=>{if(await Me())return;let n=Ue(),r=`bunx --bun ${Ge()}`;if(console.error(t(`Seems like you are using ${n===`unknown`?`npx/pnpx`:n} or Windows, try using:`)),console.error(E(` ${r}`)),console.error(``),e||j()&&await V(`Or install bun now and continue?`,!0)){let{cmd:e,args:n}=We();console.error(`Installing bun from https://bun.sh/install ...`);try{if(await te(e,n),process.env.PATH=`${Ke()}${se}${process.env.PATH||``}`,await Me())return;console.error(t(`
11
- bun is installed but isn't on this shell's PATH yet. Open a new terminal and re-run:`)),console.error(E(` ${r}`))}catch{console.error(t(`
12
- Could not install bun. Install it from https://bun.sh, then re-run:`)),console.error(E(` ${r}`))}}process.exit(1)},Y=[{value:`allowlist`,flag:`allowlist`,label:`Allowlist (grant console access by domain)`},{value:`apiDocs`,flag:`api-docs`,label:`API docs`},{value:`blog`,flag:`blog`,label:`Blog`},{value:`docs`,flag:`docs`,label:`Docs`},{value:`internalDocs`,flag:`internal-docs`,label:`Internal (console) docs`},{value:`waitlist`,flag:`waitlist`,label:`Waitlist (else a plain landing home)`}],qe=`Usage:
2
+ import{A as e,C as t,D as n,E as r,F as i,I as a,M as o,N as s,O as c,P as l,S as u,T as d,_ as f,a as p,b as m,c as ee,d as h,f as g,g as _,h as v,i as y,j as b,k as x,l as S,m as C,n as te,o as ne,p as w,r as re,s as ie,t as T,u as E,v as D,w as ae,x as O,y as k}from"../convert-DjNN2nhd.mjs";import{basename as oe,delimiter as se,dirname as ce,join as A,parse as le,resolve as j}from"node:path";import{existsSync as M,readFileSync as ue,readdirSync as de}from"node:fs";import{randomBytes as fe}from"node:crypto";import{createInterface as pe}from"node:readline/promises";import{parseArgs as me}from"node:util";import{homedir as he}from"node:os";var ge={name:`zerostarter`,version:`0.1.35`,description:`Go from zero to a production-ready SaaS, rebranded and ready to ship.`,keywords:[`cli`,`saas`,`scaffold`,`starter`,`template`,`zerostarter`],homepage:`https://github.com/nrjdalal/zerostarter#readme`,bugs:`https://github.com/nrjdalal/zerostarter/issues`,license:`MIT`,author:{name:`Neeraj Dalal`,email:`admin@nrjdalal.com`,url:`https://nrjdalal.com`},repository:`nrjdalal/zerostarter`,funding:`https://github.com/sponsors/nrjdalal`,bin:{zerostarter:`dist/bin/index.mjs`},files:[`dist`],type:`module`,types:`./dist/index.d.mts`,exports:`./dist/index.mjs`,scripts:{build:`tsdown`,"check-types":`tsc --noEmit`,dev:`tsdown --watch`},devDependencies:{"@packages/config":`workspace:*`,"@types/bun":`catalog:`,"@types/node":`catalog:`,tsdown:`catalog:`,typescript:`catalog:`}};const N=()=>!!(process.stdin.isTTY&&process.stdout.isTTY),_e=(e,t=e)=>process.stdout.isTTY?`\x1b]8;;${e}\x07${t}\x1b]8;;\x07`:e,P=e=>_e(e,k(e)),F=e=>{process.stdout.write(`\n ${O(D.barStart)} ${e}\n ${O(D.bar)}\n`)},I=e=>{process.stdout.write(` ${O(D.bar)}\n ${O(D.barEnd)} ${e}\n`)},L=(e=`Cancelled`)=>{process.stdout.write(` ${O(D.barEnd)} ${d(e)}\n`)},R=e=>e.replace(/* @__PURE__ */ RegExp(`\x1B\\[[0-9;?]*[a-zA-Z]`,`g`),``).replace(/* @__PURE__ */ RegExp(`\x1B\\]8;;.*?\\x07`,`g`),``).length,z=(e,t=``,n=!1)=>{let r=`\n${e}\n`.split(`
3
+ `),i=R(t),a=r.reduce((e,t)=>Math.max(e,R(t)),i),o=Math.max(a,54)+2,s=r.map(e=>` ${O(D.bar)} ${e}${` `.repeat(o-R(e))}${O(D.bar)}`).join(`
4
+ `),c=n?D.cornerBL:D.connectL;process.stdout.write(` ${k(D.active)} ${t} ${O(D.barH.repeat(Math.max(o-i-1,1))+D.cornerTR)}\n${s}\n ${O(c+D.barH.repeat(o+2)+D.cornerBR)}\n`)},B=(e,t=[])=>{process.stdout.write(` ${k(D.active)} ${e}\n`);for(let e of t)process.stdout.write(` ${O(D.bar)} ${e}\n`)},ve=e=>{process.stdout.write(` ${u(D.active)} ${u(e)}\n`)},V=(e,t=[])=>{process.stdout.write(` ${r(D.warn)} ${r(e)}\n`);for(let e of t)process.stdout.write(` ${O(D.bar)} ${r(e)}\n`)},ye=async(e,t,n)=>{let r=process.stdout,i=null;if(r.isTTY){let t=0;r.write(` ${ae(0,e)}`),i=setInterval(()=>{t+=1,r.write(`\r\x1b[K ${ae(t,e)}`)},400)}try{let e=await n();return i&&clearInterval(i),r.write(r.isTTY?`\r\x1b[K ${k(D.active)} ${t}\n`:` ${k(D.active)} ${t}\n`),e}catch(e){throw i&&clearInterval(i),r.isTTY&&r.write(`\r\x1B[K`),e}},be=e=>{let t=e?`${u(D.radioOn)} Yes`:m(`${D.radioOff} Yes`),n=e?m(`${D.radioOff} No`):`${u(D.radioOn)} No`;return` ${O(D.bar)} ${t} ${m(`/`)} ${n}`},H=async(e,t=!0)=>{let n=process.stdout;return N()?new Promise(r=>{let i=t,a=process.stdin,o=Math.max(1,Math.ceil((5+R(e))/(n.columns||80)));n.write(` ${k(D.submit)} ${e}\n`),n.write(be(i)),a.setRawMode(!0),a.resume(),a.setEncoding(`utf8`);let s=()=>{a.setRawMode(!1),a.pause(),a.removeListener(`data`,u)},c=()=>{n.write(`\r\x1b[K\x1b[${o}A\r\x1b[0J`)},l=()=>{s(),c(),r(i)};function u(t){t===``?(s(),c(),n.write(` ${k(D.submit)} ${m(e)}\n`),L(),process.exit(130)):t===`\r`||t===`
5
+ `?l():t===`y`||t===`Y`?(i=!0,l()):t===`n`||t===`N`?(i=!1,l()):(t===`\x1B[C`||t===`\x1B[D`||t===`\x1B[A`||t===`\x1B[B`||t===`h`||t===`l`)&&(i=!i,n.write(`\r\x1b[K${be(i)}`))}a.on(`data`,u)}):(n.write(` ${k(D.active)} ${e} ${m(t?`Yes`:`No`)}\n`),t)},xe=async(e,t)=>{if(t.length===0)return[];let n=process.stdout,r=new Set(t.filter(e=>e.checked).map(e=>e.value)),i=()=>t.filter(e=>r.has(e.value)).map(e=>e.label).join(`, `)||`none`;return N()?new Promise(a=>{let o=process.stdin,s=0,c=e=>{let n=t[e],i=r.has(n.value)?u(D.checkboxOn):m(D.checkboxOff),a=e===s?n.label:m(n.label);return` ${O(D.bar)} ${i} ${a}`},l=e=>{e||n.write(`\x1b[${t.length}A`);for(let e=0;e<t.length;e++)n.write(`\r\x1b[K${c(e)}\n`)};n.write(` ${k(D.submit)} ${e}\n`),l(!0),o.setRawMode(!0),o.resume(),o.setEncoding(`utf8`);let d=()=>{o.setRawMode(!1),o.pause(),o.removeListener(`data`,f)};function f(o){if(o===``)d(),n.write(`\x1b[${t.length+1}A\r\x1b[0J ${k(D.submit)} ${m(e)}\n`),L(),process.exit(130);else if(o===`\r`||o===`
6
+ `)d(),n.write(`\x1b[${t.length+1}A\r\x1b[0J ${k(D.submit)} ${e} ${m(i())}\n`),a([...r]);else if(o===`\x1B[A`||o===`k`)s=(s-1+t.length)%t.length,l(!1);else if(o===`\x1B[B`||o===`j`)s=(s+1)%t.length,l(!1);else if(o===` `){let e=t[s].value;r.has(e)?r.delete(e):r.add(e),l(!1)}}o.on(`data`,f)}):(n.write(` ${k(D.submit)} ${e} ${m(i())}\n`),[...r])},Se=async(e,t=``)=>{process.stdout.write(` ${k(D.submit)} ${e}${t?` ${m(`(${t})`)}`:``}\n`);let n=pe({input:process.stdin,output:process.stdout});try{return(await n.question(` ${O(D.bar)} `)).trim()||t}finally{n.close()}},Ce=`pglaunch@5.5.7`,we=async()=>w(`docker`,[`info`]),Te=e=>{let t=A(e,`.env`);if(!x(t)){let n=A(e,`.env.example`);l(t,x(n)?b(n):``)}return t},U=(e,t)=>{let n=`${t}=`,r=b(e).split(`
7
+ `).find(e=>e.startsWith(n));return r?r.slice(n.length).trim():``},W=(e,t,n)=>{let r=`${t}=`,i=b(e).split(`
8
+ `),a=i.findIndex(e=>e.startsWith(r));if(a>=0)i[a]=`${r}${n}`;else{for(;i.length&&i[i.length-1]===``;)i.pop();i.push(`${r}${n}`,``)}l(e,i.join(`
9
+ `))},Ee=e=>{let t=Te(e);U(t,`BETTER_AUTH_SECRET`)||W(t,`BETTER_AUTH_SECRET`,fe(32).toString(`base64`)),U(t,`AGENT_SIGNIN_ENABLED`)||W(t,`AGENT_SIGNIN_ENABLED`,`true`)},G=e=>{let t=A(e,`.env`);return x(t)&&U(t,`POSTGRES_URL`)!==``},De=e=>{let t=e.match(/postgres(?:ql)?:\/\/[\w.:@\-/%?=&]+/);if(!t)return null;let n=e.match(/name "([^" ]+) :\d+" started/),r=e.match(/similar name "([^"]+)"/);return{url:t[0],container:n?n[1]:r?r[1]:``}},Oe=async(e,t)=>{let n=t?[`--bun`,Ce,`-k`,`-c`]:[`--bun`,Ce,`-k`],r;try{r=await v(`bunx`,n,e)}catch(e){let t=e;r=[t.stdout,t.stderr].filter(Boolean).join(`
10
+ `)}return{launch:De(r),out:r}},ke=e=>new Promise(t=>setTimeout(t,e)),Ae=async e=>{if(e)for(let t=0;t<30;t++){if(await w(`docker`,[`exec`,e,`pg_isready`,`-U`,`postgres`]))return;await ke(1e3)}},je=async e=>{let t=Te(e),n=await Oe(e,!1),r=n.launch?n:await Oe(e,!0);if(!r.launch)throw Error(r.out.trim()||`pglaunch did not print a connection URL`);W(t,`POSTGRES_URL`,r.launch.url),await Ae(r.launch.container),await f(`bun`,[`run`,`db:migrate`],{cwd:e,label:`Provisioning a local Postgres database`,done:`Provisioned a local Postgres database`})},K=async e=>{await f(`bun`,[`install`],{cwd:e,label:`Installing dependencies`,done:`Installed dependencies`})},Me=async()=>{if(!await w(`bun`,[`--version`]))throw Error(`bun is not on PATH`)},Ne=async(e=Me,t=!!process.versions.bun,n=process.env.npm_config_user_agent||``)=>{if(t||n.startsWith(`bun/`))return!0;try{return await e(),!0}catch{return!1}},Pe=async(e,t=`main`)=>{await v(`bunx`,[`--bun`,`gitpick@6.0.0`,`https://github.com/nrjdalal/zerostarter/tree/${t}`,e])},Fe=async(e,t=`main`)=>{await v(`bunx`,[`--bun`,`gitpick@6.0.0`,`https://github.com/nrjdalal/zerostarter/tree/${t}`,e,`-o`])},Ie=async(e=`main`)=>{let t=`https://raw.githubusercontent.com/nrjdalal/zerostarter/${e}/.gitpickignore`,n=await fetch(t);if(!n.ok)throw Error(`Could not fetch .gitpickignore from ${e} (HTTP ${n.status}).`);return n.text()},Le=async e=>(await v(`git`,[`status`,`--porcelain`],e)).trim()===``,Re=async(e,t)=>{for(let n of t)try{await v(`git`,[`checkout`,`--`,n],e),await v(`git`,[`clean`,`-fd`,`--`,n],e)}catch{}},ze=async e=>{await v(`git`,[`reset`,`--hard`,`HEAD`],e),await v(`git`,[`clean`,`-fd`],e)},Be=async(e,t,n)=>{if(!x(A(e,`.git`)))throw Error(t);if(!await Le(e))throw Error(n)},Ve=async(e,t,n)=>{try{await n()}catch(n){throw await ze(e),console.log(t),n}},He=async e=>{await v(`git`,[`init`,`-q`,`-b`,`canary`],e)},Ue=async(e,t)=>{await v(`git`,[`branch`,t],e)},q=async(e,t)=>{await v(`git`,[`add`,`-A`],e);try{await v(`git`,[`commit`,`--no-verify`,`-q`,`-m`,t],e)}catch{}},J=(e,t)=>{try{return me(t)}catch(t){process.stderr.write(`${d(t instanceof Error?t.message:String(t))}\n\n`),process.stdout.write(`${e}\n`),process.exit(1)}},We=(e=process.env.npm_config_user_agent||``)=>e.startsWith(`bun/`)?`bunx`:e.startsWith(`pnpm/`)?`pnpm dlx`:e.startsWith(`yarn/`)?`yarn dlx`:e.startsWith(`npm/`)?`npx`:`unknown`,Ge=(e=process.platform)=>e===`win32`?{cmd:`powershell`,args:[`-Command`,`irm bun.sh/install.ps1 | iex`]}:{cmd:`bash`,args:[`-c`,`curl -fsSL https://bun.sh/install | bash`]},Ke=(e=process.argv.slice(2))=>[`zerostarter`,...e].join(` `),qe=()=>A(process.env.BUN_INSTALL||A(he(),`.bun`),`bin`),Y=async(e=!1)=>{if(await Ne())return;let n=We(),r=`bunx --bun ${Ke()}`;if(console.error(d(`Seems like you are using ${n===`unknown`?`npx/pnpx`:n} or Windows, try using:`)),console.error(t(` ${r}`)),console.error(``),e||N()&&await H(`Or install bun now and continue?`,!0)){let{cmd:e,args:n}=Ge();console.error(`Installing bun from https://bun.sh/install ...`);try{if(await _(e,n),process.env.PATH=`${qe()}${se}${process.env.PATH||``}`,await Ne())return;console.error(d(`
11
+ bun is installed but isn't on this shell's PATH yet. Open a new terminal and re-run:`)),console.error(t(` ${r}`))}catch{console.error(d(`
12
+ Could not install bun. Install it from https://bun.sh, then re-run:`)),console.error(t(` ${r}`))}}process.exit(1)},X=[{value:`allowlist`,flag:`allowlist`,label:`Allowlist (grant console access by domain)`},{value:`apiDocs`,flag:`api-docs`,label:`API docs`},{value:`blog`,flag:`blog`,label:`Blog`},{value:`docs`,flag:`docs`,label:`Docs`},{value:`internalDocs`,flag:`internal-docs`,label:`Internal (console) docs`},{value:`waitlist`,flag:`waitlist`,label:`Waitlist (else a plain landing home)`}],Je=`Usage:
13
13
  $ bunx zerostarter init [dir] [options]
14
14
 
15
15
  Scaffold ZeroStarter into dir (default .) as a fresh product. The author's
@@ -30,10 +30,10 @@ Features (default on, except the waitlist; pass any flag to skip the interactive
30
30
  --blog, --no-blog The /blog
31
31
  --docs, --no-docs The /docs
32
32
  --internal-docs, --no-internal-docs The /console/docs internal docs
33
- --waitlist, --no-waitlist The /waitlist (off leaves a plain landing home)`,Je=e=>!A(e)||de(e).filter(e=>e!==`.git`).length===0,X=e=>n(O(e,`packages/config/src/site.ts`)),Ye=e=>{let t=e,{root:n}=le(t);for(;;){if(A(O(t,`bun.lock`))||A(O(t,`bun.lockb`)))return t;let e=O(t,`package.json`);if(A(e))try{if(JSON.parse(ue(e,`utf8`)).workspaces)return t}catch{}if(t===n)return null;t=ce(t)}},Xe=async e=>{let{positionals:t,values:r}=q(qe,{allowPositionals:!0,args:e,options:{canary:{type:`boolean`},db:{type:`boolean`},"dry-run":{type:`boolean`},help:{short:`h`,type:`boolean`},yes:{short:`y`,type:`boolean`},"api-docs":{type:`boolean`},"no-api-docs":{type:`boolean`},blog:{type:`boolean`},"no-blog":{type:`boolean`},docs:{type:`boolean`},"no-docs":{type:`boolean`},"internal-docs":{type:`boolean`},"no-internal-docs":{type:`boolean`},waitlist:{type:`boolean`},"no-waitlist":{type:`boolean`}}});if(r.help){console.log(qe);return}r[`dry-run`]||await J(!!r.yes);let i=j()&&!r.yes,a=r.canary?`canary`:`main`,o=r,s=Y.some(e=>o[e.flag]||o[`no-${e.flag}`]),c={...d};for(let e of Y)c[e.value]=o[`no-${e.flag}`]?!1:o[e.flag]?!0:d[e.value];let l=t[0]??`.`,u=k(l),f=X(u),p=!f&&!Je(u);if(!f&&!r[`dry-run`]){let e=Ye(p?process.cwd():ce(u));if(e)throw Error(`Cannot scaffold inside an existing project (a workspace was found at ${e}). Run it in a fresh directory outside that project.`)}if(r[`dry-run`]||P(N(`https://zerostarter.dev`)),p){if(!i)throw Error(`Directory is not empty. Run it in an empty directory, or pass a project name: bunx zerostarter init <name>`);let e=await xe(`What should we name your project?`);if(!e)throw Error(`No directory name provided.`);l=e}let m=k(l),h=oe(m),g={name:h},_=`--canary ignored: converting the existing checkout in place, so nothing is fetched.`,ee=e=>Y.filter(t=>e[t.value]).map(e=>e.value).join(`, `)||`none`;if(r[`dry-run`]){console.log(`bunx zerostarter init (dry run)`),console.log(` target: ${m}`),console.log(` name: ${h}`),console.log(` mode: ${X(m)?`in place`:`fetch ${a}`}`),console.log(` features: ${ee(c)}`),X(m)&&r.canary&&console.log(` note: ${_}`);return}if(f&&i&&!await V(`Convert ${h} in place? This rewrites files and commits.`,!1)){I(`Aborted`);return}if(i&&!s){let e=new Set(await be(`Which optional surfaces should this fork ship with?`,Y.map(e=>({value:e.value,label:e.label,checked:d[e.value]}))));for(let t of Y)c[t.value]=e.has(t.value)}if(X(m))r.canary&&B(_);else{let e=r.canary?` (canary)`:``;await ve(`Fetching the latest ZeroStarter${e}`,`Fetched the latest ZeroStarter${e}`,()=>Ne(m,a))}n(O(m,`.git`))||(await Ve(m),await K(m,`ci(init): scaffold from zerostarter`),await He(m,`main`)),await ve(`Rebranding to ${h}`,`Rebranded to ${h}`,()=>ie(m,g,c)),await G(m),await b(m),await K(m,`ci(init): re-baseline as ${h}`),Te(m);let v=!1,y=await Ce(),te=W(m),x=!1;if(te?r.db&&B(`--db ignored: POSTGRES_URL is already set in .env.`):x=r.db?!0:i?await V(`Provision a local Postgres database?`,y):y,x&&y)try{await Ae(m),v=!0}catch(e){if(W(m))B(`Postgres is provisioned, but the migration failed; run bun run db:migrate.`);else{let t=e instanceof Error?e.message.trim():String(e);B(`Database setup failed; set POSTGRES_URL in .env yourself.`,t?t.split(`
34
- `).filter(e=>e.trim()).slice(-5):[])}}else x&&B(`Docker isn't running, so the database wasn't provisioned. Set POSTGRES_URL in .env, or start Docker and re-run for automatic setup.`);R([`${E(`packages/config/src/site.ts`)} and ${E(`web/next/content`)}`,`to manage branding and blogs & docs respectively`].join(`
35
- `),`Edit and make it yours`),_e(`${h} is ready`);let S=[];m!==process.cwd()&&S.push(E(`cd ${l}`)),W(m)||S.push(`set POSTGRES_URL in .env`),v||S.push(E(`bun run db:migrate`)),S.push(E(`bun run dev`)),R(S.join(`
36
- `),`Next steps`),F(`Learn more ${N(`https://zerostarter.dev/docs`)}`)},Ze=`Usage:
33
+ --waitlist, --no-waitlist The /waitlist (off leaves a plain landing home)`,Ye=e=>!M(e)||de(e).filter(e=>e!==`.git`).length===0,Z=e=>x(A(e,`packages/config/src/site.ts`)),Xe=e=>{let t=e,{root:n}=le(t);for(;;){if(M(A(t,`bun.lock`))||M(A(t,`bun.lockb`)))return t;let e=A(t,`package.json`);if(M(e))try{if(JSON.parse(ue(e,`utf8`)).workspaces)return t}catch{}if(t===n)return null;t=ce(t)}},Ze=async e=>{let{positionals:n,values:r}=J(Je,{allowPositionals:!0,args:e,options:{canary:{type:`boolean`},db:{type:`boolean`},"dry-run":{type:`boolean`},help:{short:`h`,type:`boolean`},yes:{short:`y`,type:`boolean`},"api-docs":{type:`boolean`},"no-api-docs":{type:`boolean`},blog:{type:`boolean`},"no-blog":{type:`boolean`},docs:{type:`boolean`},"no-docs":{type:`boolean`},"internal-docs":{type:`boolean`},"no-internal-docs":{type:`boolean`},waitlist:{type:`boolean`},"no-waitlist":{type:`boolean`}}});if(r.help){console.log(Je);return}r[`dry-run`]||await Y(!!r.yes);let i=N()&&!r.yes,a=r.canary?`canary`:`main`,o=r,s=X.some(e=>o[e.flag]||o[`no-${e.flag}`]),c={...g};for(let e of X)c[e.value]=o[`no-${e.flag}`]?!1:o[e.flag]?!0:g[e.value];let l=n[0]??`.`,u=j(l),d=Z(u),f=!d&&!Ye(u);if(!d&&!r[`dry-run`]){let e=Xe(f?process.cwd():ce(u));if(e)throw Error(`Cannot scaffold inside an existing project (a workspace was found at ${e}). Run it in a fresh directory outside that project.`)}if(r[`dry-run`]||F(P(`https://zerostarter.dev`)),f){if(!i)throw Error(`Directory is not empty. Run it in an empty directory, or pass a project name: bunx zerostarter init <name>`);let e=await Se(`What should we name your project?`);if(!e)throw Error(`No directory name provided.`);l=e}let p=j(l),m=oe(p),ee={name:m},h=`--canary ignored: converting the existing checkout in place, so nothing is fetched.`,_=e=>X.filter(t=>e[t.value]).map(e=>e.value).join(`, `)||`none`;if(r[`dry-run`]){console.log(`bunx zerostarter init (dry run)`),console.log(` target: ${p}`),console.log(` name: ${m}`),console.log(` mode: ${Z(p)?`in place`:`fetch ${a}`}`),console.log(` features: ${_(c)}`),Z(p)&&r.canary&&console.log(` note: ${h}`);return}if(d&&i&&!await H(`Convert ${m} in place? This rewrites files and commits.`,!1)){L(`Aborted`);return}if(i&&!s){let e=new Set(await xe(`Which optional surfaces should this fork ship with?`,X.map(e=>({value:e.value,label:e.label,checked:g[e.value]}))));for(let t of X)c[t.value]=e.has(t.value)}if(Z(p))r.canary&&V(h);else{let e=r.canary?` (canary)`:``;await ye(`Fetching the latest ZeroStarter${e}`,`Fetched the latest ZeroStarter${e}`,()=>Pe(p,a))}x(A(p,`.git`))||(await He(p),await q(p,`ci(init): scaffold from zerostarter`),await Ue(p,`main`)),await ye(`Rebranding to ${m}`,`Rebranded to ${m}`,()=>T(p,ee,c)),await K(p),await E(p),await q(p,`ci(init): re-baseline as ${m}`),Ee(p);let v=!1,y=await we(),b=G(p),S=!1;if(b?r.db&&V(`--db ignored: POSTGRES_URL is already set in .env.`):S=r.db?!0:i?await H(`Provision a local Postgres database?`,y):y,S&&y)try{await je(p),v=!0}catch(e){if(G(p))V(`Postgres is provisioned, but the migration failed; run bun run db:migrate.`);else{let t=e instanceof Error?e.message.trim():String(e);V(`Database setup failed; set POSTGRES_URL in .env yourself.`,t?t.split(`
34
+ `).filter(e=>e.trim()).slice(-5):[])}}else S&&V(`Docker isn't running, so the database wasn't provisioned. Set POSTGRES_URL in .env, or start Docker and re-run for automatic setup.`);z([`${t(`packages/config/src/site.ts`)} and ${t(`web/next/content`)}`,`to manage branding and blogs & docs respectively`].join(`
35
+ `),`Edit and make it yours`),ve(`${m} is ready`);let C=[];p!==process.cwd()&&C.push(t(`cd ${l}`)),G(p)||C.push(`set POSTGRES_URL in .env`),v||C.push(t(`bun run db:migrate`)),C.push(t(`bun run dev`)),z(C.join(`
36
+ `),`Next steps`),I(`Learn more ${P(`https://zerostarter.dev/docs`)}`)},Qe=`Usage:
37
37
  $ bunx zerostarter reinit [dir] [options]
38
38
 
39
39
  Re-scaffold an existing git repo (default .) as a fresh ZeroStarter: delete every
@@ -43,24 +43,24 @@ branch; push when ready.
43
43
 
44
44
  Options:
45
45
  -y, --yes Skip the confirmation prompt
46
- -h, --help Display help`,Qe=async e=>{let{positionals:t,values:n}=q(Ze,{allowPositionals:!0,args:e,options:{help:{short:`h`,type:`boolean`},yes:{short:`y`,type:`boolean`}}});if(n.help){console.log(Ze);return}await J(!!n.yes);let i=k(t[0]??`.`),a=oe(i),o=j()&&!n.yes;if(await ze(i,`No git repository in ${i}. reinit re-scaffolds an existing repo; use init for a new project.`,`Working tree has uncommitted changes. Commit or stash them first; reinit deletes every tracked file.`),P(N(`https://zerostarter.dev`)),o&&!await V(`Delete every file in ${a} (keeping .git and .env*) and re-scaffold?`,!1)){I(`Aborted`);return}await Be(i,T(`reinit failed; restored your committed files (deleted gitignored files, except .env*, are gone).`),async()=>{r(i),await Pe(i),z(`Fetched the latest ZeroStarter (kept .git and .env*)`),ie(i,{name:a}),z(`Rebranded to ${a}`),await G(i),await b(i),Te(i),await K(i,`ci(reinit): re-baseline as ${a}`)});let s=[];W(i)||s.push(`set POSTGRES_URL in .env`),s.push(E(`bun run db:migrate`)),s.push(E(`bun run dev`)),s.push(E(`git push`)),R(s.join(`
47
- `),`Next steps`),F(f(`${a} re-scaffolded; .git history, remote, and .env* files are intact`))},$e=`Usage:
46
+ -h, --help Display help`,$e=async e=>{let{positionals:n,values:i}=J(Qe,{allowPositionals:!0,args:e,options:{help:{short:`h`,type:`boolean`},yes:{short:`y`,type:`boolean`}}});if(i.help){console.log(Qe);return}await Y(!!i.yes);let a=j(n[0]??`.`),o=oe(a),s=N()&&!i.yes;if(await Be(a,`No git repository in ${a}. reinit re-scaffolds an existing repo; use init for a new project.`,`Working tree has uncommitted changes. Commit or stash them first; reinit deletes every tracked file.`),F(P(`https://zerostarter.dev`)),s&&!await H(`Delete every file in ${o} (keeping .git and .env*) and re-scaffold?`,!1)){L(`Aborted`);return}await Ve(a,r(`reinit failed; restored your committed files (deleted gitignored files, except .env*, are gone).`),async()=>{c(a),await Fe(a),B(`Fetched the latest ZeroStarter (kept .git and .env*)`),T(a,{name:o}),B(`Rebranded to ${o}`),await K(a),await E(a),Ee(a),await q(a,`ci(reinit): re-baseline as ${o}`)});let l=[];G(a)||l.push(`set POSTGRES_URL in .env`),l.push(t(`bun run db:migrate`)),l.push(t(`bun run dev`)),l.push(t(`git push`)),z(l.join(`
47
+ `),`Next steps`),I(u(`${o} re-scaffolded; .git history, remote, and .env* files are intact`))},et=`Usage:
48
48
  $ bunx zerostarter sync [dir] [options]
49
49
 
50
50
  Re-baseline an existing fork (default .) on the latest ZeroStarter: a gitpick overlay
51
51
  updates the starter files while your content, public/marketing, branding, package.json
52
- identity, and favicon are preserved. Requires a clean tree; lands as a reviewable diff
52
+ identity, favicon, and any skill or AGENTS.md you have edited are preserved. Requires a clean tree; lands as a reviewable diff
53
53
  you commit yourself.
54
54
 
55
55
  Options:
56
- -h, --help Display help`,et=async t=>{let{positionals:r,values:i}=q($e,{allowPositionals:!0,args:t,options:{help:{short:`h`,type:`boolean`}}});if(i.help){console.log($e);return}await J();let c=k(r[0]??`.`);await ze(c,`No git repository in ${c}. Run sync inside an existing fork.`,`Working tree has uncommitted changes. Commit or stash them first so the sync lands as a reviewable diff.`),P(N(`https://zerostarter.dev`));let u=O(c,`package.json`),d=new Map(o(c).map(t=>[t,e(t)])),f=s(await Fe()).preserve;f.length===0&&B(`No PRESERVE_ON_SYNC directive found; fork-owned files may be overwritten.`);let m=ne(c),h=ae(c),g=S();await Be(c,T(`Sync failed; rolled the working tree back to your last commit.`),async()=>{await Pe(c),x(c,m),v(O(c,`.gitpickignore`));for(let[t,r]of d)n(t)&&a(t,l(r,e(t),t===u));ee(c),g=p(c,h),await Le(c,f)}),z(`Overlaid the latest ZeroStarter (content, public/marketing, and branding preserved)`),await G(c),await b(c),re(c)&&B(`Your AGENTS.md has no skills-table markers, so the pre-commit hook that fills them fails.`,[`Add these two pairs under a ## Skills heading, then commit:`,`<!-- skills:custom --> <!-- /skills:custom -->`,`<!-- skills:vendored --> <!-- /skills:vendored -->`]);let _=e=>e.map(e=>`.agents/skills/${e}/SKILL.md`),y=e=>e.length===1?``:`s`;g.forkOwned.length>0&&z(`Left ${g.forkOwned.length} skill${y(g.forkOwned)} you own untouched:`,_(g.forkOwned)),g.customized.length>0&&(B(`Kept your edits to ${g.customized.length} skill${y(g.customized)}, so they did not take the update:`,_(g.customized)),z(`To take upstream's version instead, delete the skill directory and sync again.`)),g.unverified.length>0&&(B(`Updated ${g.unverified.length} skill${y(g.unverified)} with no sync record, so any edits of yours are in the diff rather than the file:`,_(g.unverified)),z(`Restore any with: git restore --source=HEAD -- .agents/skills/<name>/SKILL.md. Later syncs track them.`)),R([`Starter files were updated (edits overwritten); your content, public/marketing, branding, and the skills you own were preserved.`,T(`Review the diff and commit: git -C ${c} status`)].join(`
57
- `),`Review the changes`),F(E(`Synced to the latest ZeroStarter`))},{author:Z,name:Q,version:tt}=ge,$=`Version:
58
- ${Q}@${tt}
56
+ -h, --help Display help`,tt=async c=>{let{positionals:l,values:u}=J(et,{allowPositionals:!0,args:c,options:{help:{short:`h`,type:`boolean`}}});if(u.help){console.log(et);return}await Y();let d=j(l[0]??`.`);await Be(d,`No git repository in ${d}. Run sync inside an existing fork.`,`Working tree has uncommitted changes. Commit or stash them first so the sync lands as a reviewable diff.`),F(P(`https://zerostarter.dev`));let f=A(d,`package.json`),m=new Map(e(d).map(e=>[e,o(e)])),g=a(await Ie()).preserve;g.length===0&&V(`No PRESERVE_ON_SYNC directive found; fork-owned files may be overwritten.`);let _=re(d),v=h(d),b=ie(d),C=p(),w=`absent`;await Ve(d,r(`Sync failed; rolled the working tree back to your last commit.`),async()=>{await Fe(d),te(d,_),s(A(d,`.gitpickignore`));for(let[e,t]of m)x(e)&&i(e,n(t,o(e),e===f));y(d),C=S(d,v);let e=ie(d);await Re(d,g),w=ee(d,{before:b,upstream:e})}),B(`Overlaid the latest ZeroStarter (content, public/marketing, and branding preserved)`),await K(d),await E(d),ne(d)&&V(`Your AGENTS.md has no skills-table markers, so the pre-commit hook that fills them fails.`,[`Add these two pairs under a ## Skills heading, then commit:`,`<!-- skills:custom --> <!-- /skills:custom -->`,`<!-- skills:vendored --> <!-- /skills:vendored -->`]);let T=e=>e.map(e=>`.agents/skills/${e}/SKILL.md`),D=e=>e.length===1?``:`s`;C.forkOwned.length>0&&B(`Left ${C.forkOwned.length} skill${D(C.forkOwned)} you own untouched:`,T(C.forkOwned)),C.customized.length>0&&(V(`Kept your edits to ${C.customized.length} skill${D(C.customized)}, so they did not take the update:`,T(C.customized)),B(`To take upstream's version instead, delete the skill directory and sync again.`)),w===`customized`&&B(`Kept your AGENTS.md, which you have edited, so it did not take the update. To take the starter's version instead, delete it, commit that on its own, and sync again.`),w===`forkOwned`&&B(`Kept your AGENTS.md. The starter now ships its full agent guide; to take it, delete AGENTS.md, commit that on its own, and sync again.`),C.unverified.length>0&&(V(`Updated ${C.unverified.length} skill${D(C.unverified)} with no sync record, so any edits of yours are in the diff rather than the file:`,T(C.unverified)),B(`Restore any with: git restore --source=HEAD -- .agents/skills/<name>/SKILL.md. Later syncs track them.`)),z([`Starter files were updated (edits overwritten); your content, public/marketing, branding, and the skills and agent guide you own were preserved.`,r(`Review the diff and commit: git -C ${d} status`)].join(`
57
+ `),`Review the changes`),I(t(`Synced to the latest ZeroStarter`))},{author:Q,name:$,version:nt}=ge,rt=`Version:
58
+ ${$}@${nt}
59
59
 
60
60
  Go from zero to a production-ready SaaS, rebranded and ready to ship.
61
61
 
62
62
  Usage:
63
- $ ${Q} <command> [options]
63
+ $ ${$} <command> [options]
64
64
 
65
65
  Commands:
66
66
  init [dir] Scaffold ZeroStarter into dir (default .) as a fresh product.
@@ -73,4 +73,4 @@ Options:
73
73
  -h, --help Display help
74
74
 
75
75
  Author:
76
- ${Z.name} <${Z.email}> (${M(Z.url)})`;(async()=>{try{let e=process.argv.slice(2),n=e[0],r=e.slice(1);switch(n){case`init`:return await Xe(r);case`reinit`:return await Qe(r);case`sync`:return await et(r);case void 0:case`-h`:case`--help`:console.log($);return;case`-v`:case`--version`:console.log(`${Q}@${tt}`);return;default:console.error(t(`Unknown command: ${n}\n`)),console.log($),process.exit(1)}}catch(e){h(e),process.exit(1)}})();export{};
76
+ ${Q.name} <${Q.email}> (${_e(Q.url)})`;(async()=>{try{let e=process.argv.slice(2),t=e[0],n=e.slice(1);switch(t){case`init`:return await Ze(n);case`reinit`:return await $e(n);case`sync`:return await tt(n);case void 0:case`-h`:case`--help`:console.log(rt);return;case`-v`:case`--version`:console.log(`${$}@${nt}`);return;default:console.error(d(`Unknown command: ${t}\n`)),console.log(rt),process.exit(1)}}catch(e){C(e),process.exit(1)}})();export{};
@@ -0,0 +1,161 @@
1
+ import{delimiter as e,dirname as t,isAbsolute as n,join as r,relative as i,resolve as a,sep as o}from"node:path";import{existsSync as s,mkdirSync as c,readFileSync as l,readdirSync as u,rmSync as d,writeFileSync as f}from"node:fs";import{createHash as p}from"node:crypto";import{spawn as m}from"node:child_process";import{access as h}from"node:fs/promises";const g=e=>{let t=[],n=[],r=!1;for(let i of e.split(`
2
+ `)){let e=i.trim();if(e){if(!r&&e.startsWith(`# PRESERVE_ON_SYNC`)){n=e.slice(18).replace(/^\s*-\s*/,``).split(`,`).map(e=>e.trim()).filter(Boolean),r=!0;continue}e.startsWith(`#`)||t.push(e)}}return{excludes:t,preserve:n}},_=e=>s(e),v=e=>l(e,`utf8`),y=(e,n)=>{c(t(e),{recursive:!0}),f(e,n)},b=e=>{d(e,{force:!0,recursive:!0})},x=/* @__PURE__ */ new Set([`node_modules`,`.next`,`.turbo`,`dist`]),S=(e,t=!0)=>{for(let n of u(e,{withFileTypes:!0})){let i=r(e,n.name);if(n.name===`.git`){if(t)continue;d(i,{force:!0,recursive:!0});continue}n.isDirectory()?x.has(n.name)?d(i,{force:!0,recursive:!0}):(S(i,!1),u(i).length===0&&d(i,{force:!0,recursive:!0})):n.name.startsWith(`.env`)||d(i,{force:!0})}},C=e=>{let t=[];for(let n of u(e,{withFileTypes:!0})){if(n.name===`.git`||x.has(n.name))continue;let i=r(e,n.name);n.isDirectory()?t.push(...C(i)):n.name===`package.json`&&t.push(i)}return t},w=e=>JSON.parse(v(e)),T=(e,t)=>{y(e,`${JSON.stringify(t,null,2)}\n`)},ee=(e,t)=>{if(!s(e))return!1;let n=v(e),r=n.replace(t,``);return r!==n&&(f(e,r),!0)},E=[`homepage`,`bugs`,`license`,`author`,`repository`,`funding`],te=[`name`,`version`,...E],ne=(e,t)=>{let n={...e,...t};return Object.keys(n).length>0?n:void 0},re=(e,t)=>{let n=Array.isArray(e)?e:[],r=Array.isArray(t)?t:[];return n.length||r.length?[.../* @__PURE__ */ new Set([...r,...n])]:void 0},ie=[`dependencies`,`devDependencies`,`optionalDependencies`,`peerDependencies`,`resolutions`,`catalog`,`scripts`,`overrides`],ae=(e,t,n)=>{let r={...t};for(let n of Object.keys(e))n in t||(r[n]=e[n]);for(let n of ie){let i=ne(e[n],t[n]);i&&(r[n]=i)}let i=re(e.workspaces,t.workspaces);if(i&&(r.workspaces=i),n)for(let t of te)t in e?r[t]=e[t]:delete r[t];return r},D=(e,t,n=process.stdout)=>r=>n.isTTY?`\x1b[${e}m${r}\x1b[${t}m`:r,O=D(`36`,`39`),oe=D(`32`,`39`),se=D(`33`,`39`),k=D(`90`,`39`),A=D(`2`,`22`),ce=D(`31`,`39`,process.stderr),le=D(`2`,`22`,process.stderr),ue=D(`38;5;208`,`39`),j=[`◇`,`◆`],M=(e,t)=>{let n=(e%j.length+j.length)%j.length;return`${O(j[n])} ${t}`},N={barStart:`┌`,bar:`│`,barEnd:`└`,active:`◆`,submit:`◇`,radioOn:`●`,radioOff:`○`,checkboxOn:`◼`,checkboxOff:`◻`,warn:`▲`,error:`■`,barH:`─`,cornerTR:`╮`,connectL:`├`,cornerBL:`╰`,cornerBR:`╯`};var P=class extends Error{constructor(...e){super(...e),this.name=`SubprocessError`,this.stdout=``,this.stderr=``}};const F=[`.exe`,`.com`],I=e=>e.replaceAll(/([()\][%!^"`<>&|;, *?])/g,`^$1`),de=e=>I(I(`"${e.replaceAll(/(\\*)"/g,`$1$1\\"`).replace(/(\\*)$/,`$1$1`)}"`)),fe=async(t,n,r)=>{if(F.some(e=>t.toLowerCase().endsWith(e)))return!0;let i=(r.PATH||r.Path||``).split(e).filter(Boolean).map(e=>e.replace(/^"(.*)"$/,`$1`));try{return await Promise.any([n,...i].flatMap(e=>F.map(n=>h(`${a(e,t)}${n}`)))),!0}catch{return!1}},pe=async(e,t,n,r=process.platform===`win32`)=>{let i=n.cwd?String(n.cwd):`.`,a=n.env||process.env;return r&&!n.shell&&!await fe(e,i,a)?[I(e),t.map(de),{...n,shell:!0}]:[e,t,n]},L=async(e,t,n,r)=>{let i=[e,...t].join(` `),[a,o,s]=await pe(e,t,n);s.shell&&o.length>0&&(a=[a,...o].join(` `),o=[]);let c=m(a,o,s),l=``,u=``;return new Promise((e,t)=>{let n=(e,n,r)=>{let i=new P(e,r?{cause:r}:void 0);i.stdout=l,i.stderr=u,i.exitCode=n,t(i)},a=e=>{e.code!==`ERR_STREAM_PREMATURE_CLOSE`&&e.code!==`EPIPE`&&n(`Command failed: ${i}`,void 0,e)};c.stdout&&(c.stdout.setEncoding(`utf8`),c.stdout.on(`data`,e=>{l+=e,r?.(e)}),c.stdout.on(`error`,a)),c.stderr&&(c.stderr.setEncoding(`utf8`),c.stderr.on(`data`,e=>{u+=e,r?.(e)}),c.stderr.on(`error`,a)),c.on(`error`,e=>n(`Command failed: ${i}`,void 0,e)),c.on(`close`,(t,r)=>{if(r)return n(`Command was terminated with ${r}: ${i}`);if(t!==0)return n(`Command failed with exit code ${t}: ${i}`,t??void 0);e({stdout:l,stderr:u})})})},R=async(e,t,n)=>{let{stdout:r}=await L(e,t,{cwd:n,stdio:[`ignore`,`pipe`,`pipe`]});return r},z=async(e,t,n)=>{await L(e,t,{cwd:n,stdio:`inherit`})},me=async(e,t)=>{try{return await L(e,t,{stdio:`ignore`}),!0}catch{return!1}},he=/* @__PURE__ */ RegExp(`\x1B\\[[0-9;?]*[a-zA-Z]`,`g`),B=e=>e.replace(he,``),V=/* @__PURE__ */ new WeakSet,ge=e=>{if(process.stderr.write(`\n ${ce(e instanceof Error?e.message:String(e))}\n`),!(e instanceof P)||V.has(e))return;let t=(e.stderr||e.stdout||``).trim();if(!t)return;let n=B(t).split(`
3
+ `).map(e=>e.trimEnd()).filter(Boolean).slice(-12);for(let e of n)process.stderr.write(` ${le(e)}\n`)},_e=async(e,t,n)=>{let r=process.stdout,i=n.label??``;if(!r.isTTY){i&&r.write(` ${O(N.active)} ${i}\n`),await z(e,t,n.cwd);return}let a=n.lines??3,o=[],s=0,c=0,l=``,u=``,d=()=>(r.columns||80)-1,f=e=>{r.write(e?`\x1B[?7h`:`\x1B[?7l`)},p=()=>{c>0&&(r.write(`[${c}A`),c=0)},m=()=>{p();let e=0;r.write(` ${M(s,i)}\n`),e++;for(let t of o.slice(-a))r.write(` ${k(N.bar)} ${A(t.slice(0,Math.max(0,d()-5)))}\n`),e++;c=e},h=e=>{u+=e,l+=e;let t=l.split(`
4
+ `);l=t.pop()??``;for(let e of t)o.push(B(e).replace(/\r/g,``).replace(/\t/g,` `).trimEnd()),m()};m(),f(!1);let g=()=>{f(!0),process.exit(130)};process.once(`SIGINT`,g);let _=setInterval(()=>{s+=1,m()},400),v=()=>{clearInterval(_),process.removeListener(`SIGINT`,g)};try{await L(e,t,{cwd:n.cwd,stdio:[`ignore`,`pipe`,`pipe`]},h)}catch(e){throw v(),p(),f(!0),r.write(u),e&&typeof e==`object`&&V.add(e),e}v(),p(),f(!0),r.write(` ${O(N.active)} ${n.done??i}\n`);for(let e of o.slice(-a))r.write(` ${k(N.bar)} ${A(e.slice(0,Math.max(0,d()-5)))}\n`)},H={allowlist:!1,apiDocs:!0,blog:!0,docs:!0,internalDocs:!0,waitlist:!1},ve=e=>`// Optional surfaces a fork enables or disables. Typed boolean (not \`as const\`) so a fork can flip them and the runtime gates are not dead code. Off means the routes 404 and the links, nav, sitemap, llms, and search drop the surface. waitlist off makes the home a plain landing page.
5
+ export const features = {
6
+ allowlist: ${e.allowlist},
7
+ apiDocs: ${e.apiDocs},
8
+ blog: ${e.blog},
9
+ docs: ${e.docs},
10
+ internalDocs: ${e.internalDocs},
11
+ waitlist: ${e.waitlist},
12
+ }
13
+
14
+ export type Feature = keyof typeof features`,ye=({name:e},t=H)=>{let n=e.charAt(0).toUpperCase()+e.slice(1);return`// Brand identity for this app: the single source a fork edits to rebrand. web reads it via lib/config.ts.
15
+ export const site = {
16
+ name: ${JSON.stringify(n)},
17
+ description: ${JSON.stringify(`${n} is just getting started. Tell its story here.`)},
18
+ tagline: "Your tagline, ready when you are.",
19
+ social: {
20
+ discord: "",
21
+ github: "",
22
+ x: "",
23
+ },
24
+ // Local-only dev agent identity (api/hono agents router).
25
+ agent: {
26
+ name: "LocalAgent",
27
+ email: "agent@local.host",
28
+ },
29
+ // Injectable long-form text blocks. A product sets its own, or leaves them empty.
30
+ apiReferenceDescription: "",
31
+ llmsFullPreamble: "",
32
+ } as const
33
+
34
+ export type Site = typeof site
35
+
36
+ ${ve(t)}
37
+ `},be=()=>`import { features, site } from "@packages/config/site"
38
+ import { redirect } from "next/navigation"
39
+
40
+ // Fresh fork: the waitlist capture when the waitlist feature is on, otherwise a plain landing page. Replace this with your real home when ready.
41
+ export default function Home() {
42
+ if (features.waitlist) redirect("/waitlist")
43
+
44
+ return (
45
+ <main className="flex min-h-svh flex-col items-center justify-center p-8 text-center">
46
+ <div className="mx-auto flex w-full max-w-xl flex-col items-center">
47
+ <h1 className="mb-4 text-5xl font-bold tracking-tight sm:text-6xl">{site.name}</h1>
48
+ <p className="text-muted-foreground max-w-md text-lg">{site.tagline}</p>
49
+ </div>
50
+ </main>
51
+ )
52
+ }
53
+ `,U=()=>`# AGENTS.md
54
+
55
+ Guidance for AI coding agents working in this repository.
56
+
57
+ ## Instructions
58
+
59
+ - ALWAYS: Use \`@/\` for imports, and follow the \`design\` skill for UI and styling conventions.
60
+ - ALWAYS: Keep documentation in sync with every change.
61
+ - NEVER: Include "Co-authored-by" in commit messages.
62
+ - NEVER: Use em-dashes (the long dash, U+2014) in code, comments, docs, or copy. Regular hyphens are fine; for a pause or aside, use a comma, colon, or period.
63
+
64
+ ## Skills
65
+
66
+ Skills live in \`.agents/skills\` (symlinked to \`.claude/skills\` and \`.github/skills\`, so every agent tool reads the same files). Each is a \`SKILL.md\` with a \`description\` trigger and a literal procedure. Start with \`codebase-map\` to orient, then load the task skill that fits.
67
+
68
+ These tables are generated from each skill's own description by \`bun .github/scripts/skills-manager.ts\`, which a pre-commit hook runs for you. **Custom** skills are maintained here; **vendored** skills are copied verbatim from an upstream project (re-vendor to update, do not hand-edit).
69
+
70
+ **Custom**
71
+
72
+ <!-- skills:custom -->
73
+
74
+ <!-- /skills:custom -->
75
+
76
+ **Vendored** (upstream, copied verbatim)
77
+
78
+ <!-- skills:vendored -->
79
+
80
+ <!-- /skills:vendored -->
81
+ `,xe=({name:e})=>`# ${e.charAt(0).toUpperCase()+e.slice(1)}
82
+
83
+ Built on top of [ZeroStarter](https://zerostarter.dev).
84
+
85
+ ## Development
86
+
87
+ \`\`\`bash
88
+ bun run dev
89
+ \`\`\`
90
+
91
+ This serves named \`.localhost\` dev URLs via portless (\`bunx portless list\` shows them); \`PORTLESS=0 bun run dev\` uses fixed ports instead (web \`:3000\`, api \`:4000\`).
92
+ `,Se=()=>`---
93
+ slug: /docs
94
+ title: Introduction
95
+ description: Documentation.
96
+ ---
97
+
98
+ # Introduction
99
+
100
+ This is your documentation home. Start writing, this page is yours.
101
+ `,Ce=()=>`---
102
+ slug: /console/docs
103
+ title: Introduction
104
+ description: Internal documentation.
105
+ ---
106
+
107
+ # Introduction
108
+
109
+ Your team's internal docs live here.
110
+ `,we=e=>`---
111
+ title: Blog
112
+ description: Latest articles and updates
113
+ createdAt: ${e}
114
+ ---
115
+
116
+ ## Recent Posts
117
+
118
+ <BlogPostList />
119
+ `,Te=e=>`---
120
+ title: Hello World
121
+ description: The first post on your new blog.
122
+ createdAt: ${e}
123
+ publishedAt: ${e}
124
+ ---
125
+
126
+ ## Hello World
127
+
128
+ Your very first post. Replace it with something worth reading.
129
+ `,Ee=()=>`import type { DocsConfig } from "./src/lib/docs"
130
+
131
+ const docsConfig = {
132
+ docs: {
133
+ "Getting Started": [
134
+ {
135
+ "/docs": {
136
+ title: "Introduction",
137
+ description: "Documentation.",
138
+ },
139
+ },
140
+ ],
141
+ },
142
+ console: {
143
+ "Getting Started": [
144
+ {
145
+ "/console/docs": {
146
+ title: "Introduction",
147
+ description: "Internal documentation.",
148
+ },
149
+ },
150
+ ],
151
+ },
152
+ } satisfies DocsConfig
153
+
154
+ export default docsConfig
155
+ `,W=`https://github.com/nrjdalal/zerostarter`,G=`.agents/skills/.sync.json`,K=`main`,q=`AGENTS.md`,J=()=>({adopted:[],customized:[],forkOwned:[],unverified:[]}),Y=e=>p(`sha256`).update(e.replace(/\r\n/g,`
156
+ `)).digest(`hex`).slice(0,12),X=e=>e.toLowerCase().replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``)||`app`,Z=(e,t,n)=>e.replaceAll(`ZeroStarter`,()=>n).replace(/(?<!bunx )zerostarter(?! scaffolding CLI)/g,t),De=`> Synced from ${W}.`,Oe=()=>`> [!CAUTION]\n${De} Customize this skill or remove this note to stop syncing.`,ke=e=>e.replace(/^https?:\/\/github\.com\//,``).replace(/\/+$/,``),Ae=ke(W),je=e=>{let t=e.split(`
157
+ `);if(t[0]===void 0||t[0].trim()!==`---`)return``;for(let e of t.slice(1)){if(e.trim()===`---`)break;if(e.startsWith(`source:`))return e.slice(7).trim()}return``},Me=e=>u(e,{withFileTypes:!0}).filter(t=>t.isDirectory()&&_(r(e,t.name,`SKILL.md`))).map(e=>e.name).sort(),Ne=e=>{let t=r(e,G);if(!_(t))return{};try{return w(t)}catch{return{}}},Pe=e=>{let t=r(e,`.agents/skills`),n=/* @__PURE__ */ new Map;if(!_(t))return n;for(let e of Me(t))n.set(e,v(r(t,e,`SKILL.md`)));return n},Fe=(e,t,n,r,i=!1)=>{let a=e.replace(/\r\n/g,`
158
+ `).split(`
159
+ `),o=a.indexOf(`---`),s=a.indexOf(`---`,o+1);if(o!==0||s===-1)throw Error(`.agents/skills/${r}/SKILL.md has no frontmatter to reconcile (the skill shape drifted). Update packages/cli/src/skills.ts.`);let c=je(e),l=i||c===``||c===`local`?W:c,u=a.slice(o+1,s).map(e=>e.startsWith(`source:`)?`source: ${l}`:Z(e,t,n));u.some(e=>e.startsWith(`source:`))||u.push(`source: ${l}`);let d=Z(a.slice(s+1).join(`
160
+ `),t,n).replace(/^\n+/,``),f=l===W?`${Oe()}\n\n`:``;return`---\n${u.join(`
161
+ `)}\n---\n\n${f}${d}`},Ie=(e,t,n)=>{let i=r(e,`.agents/skills`),a=J();if(!_(i))return a;let o=X(t.name),s=Ne(e),c={};for(let e of Me(i)){let l=r(i,e,`SKILL.md`),u=v(l),d=Fe(u,o,t.name,e),f=n===void 0?void 0:n.get(e),p=s[e];if(f!==void 0){let n=je(f),r=t=>{y(l,f),t.push(e),p&&(c[e]=p)},i=ke(n);if(n!==``&&i!==Ae){r(a.forkOwned);continue}if(i===Ae&&!f.includes(De)){r(a.forkOwned);continue}if(p){if(p.written!==Y(f)){r(a.customized);continue}}else Y(f)!==Y(d)&&Y(f)!==Y(Fe(u,o,t.name,e,!0))&&a.unverified.push(e)}y(l,d),c[e]={ref:K,upstream:Y(u),written:Y(d)},a.adopted.push(e)}return s[`AGENTS.md`]&&(c[q]=s[q]),T(r(e,G),c),a},Q=e=>e.replace(/(<!-- skills:(custom|vendored) -->)[\s\S]*?(<!-- \/skills:\2 -->)/g,`$1$3`),Le=(e,t,n)=>{let i=r(e,q),a=r(e,G),o=Ne(e),s=X(t.name),c=(e,t)=>(y(i,t),o[q]={ref:K,upstream:Y(e),written:Y(Q(t))},T(a,o),`adopted`);if(n===void 0){if(!_(i))return c(U(),U());let e=v(i);return c(e,Z(e,s,t.name))}let{before:l,upstream:u}=n;if(u===void 0||u===l)return`absent`;if(l!==void 0){let e=o[q];if(!(e?e.written===Y(Q(l)):Y(Q(l))===Y(Q(U()))))return y(i,l),e?`customized`:`forkOwned`}return c(u,Z(u,s,t.name))},Re=e=>{let t=r(e,`AGENTS.md`);if(!_(t))return!1;let n=v(t);return!n.includes(`<!-- skills:custom -->`)||!n.includes(`<!-- skills:vendored -->`)},ze=async e=>{if(_(r(e,`.github/scripts/skills-manager.ts`)))try{await R(`bun`,[`.github/scripts/skills-manager.ts`],e)}catch{}},Be=(e,t)=>{let n=w(r(e,`package.json`)).name;return n?Ie(e,{name:n},t):J()},Ve=(e,t)=>{let n=w(r(e,`package.json`)).name;return n?Le(e,{name:n},t):`absent`},He=e=>{let t=r(e,q);return _(t)?v(t):void 0},$=(e,...t)=>r(e,...t),Ue=e=>{let t=$(e,`.gitpickignore`);if(!_(t))return;let s=a(e),c=(e,t)=>{throw Error(`.gitpickignore entry "${e}" ${t}; the in-place converter only removes literal paths under the root.`)},l=g(v(t)).excludes.map(e=>{/[*?![\]]/.test(e)&&c(e,`is not a literal path (a glob or negation would diverge from gitpick's fetch)`);let t=i(s,a(s,e));return(n(e)||!t||t===`..`||t.startsWith(`..${o}`)||n(t))&&c(e,`is not inside the project`),r(s,t)});for(let e of l)b(e);b(t)},We=/[ \t]*\{[^}\r\n]*href:[ \t]*["']\/hire["'][^}\r\n]*\},?[ \t]*\r?\n/,Ge=(e,t)=>{let n=/* @__PURE__ */ new Date,r=n.toISOString().slice(0,10),i=[n.getFullYear(),n.getMonth()+1,n.getDate()].map(e=>String(e).padStart(2,`0`)).join(`-`),a=i<r?i:r;y($(e,`web/next/content/docs/index.mdx`),Se()),y($(e,`web/next/content/blog/index.mdx`),we(a)),y($(e,`web/next/content/blog/hello-world.mdx`),Te(a)),y($(e,`web/next/content/console/docs/index.mdx`),Ce()),y($(e,`web/next/docs.config.ts`),Ee()),y($(e,`web/next/public/.gitkeep`),``),y($(e,`web/next/src/app/page.tsx`),be()),y($(e,`README.md`),xe(t))},Ke=`web/next/src/lib/marketing/fonts.ts`,qe=e=>_($(e,Ke)),Je=(e,t=!1)=>{let n=$(e,`web/next/src/lib/fonts.ts`),r=$(e,Ke),i=$(e,`web/next/src/components/common/navbar.tsx`);if(ee(i,We),_(r)&&!t)throw Error(`web/next/src/lib/marketing/fonts.ts survived the fork strip (add web/next/src/lib/marketing/ to .gitpickignore).`);if(_(n)&&v(n).includes(`fonts/marketing/`))throw Error(`fonts.ts references fonts/marketing/; author-only marketing fonts must live in the fork-excluded web/next/src/lib/marketing/, not the shared fonts.ts.`);if(_(i)&&/href:\s*["']\/hire["']/.test(v(i)))throw Error(`common/navbar.tsx still has the /hire entry after fixDangling (regex drift). Update packages/cli/src/convert.ts.`)},Ye=(e,t)=>{for(let[n,r]of[[`web/next`,t],[`api/hono`,`api.${t}`]]){let t=$(e,n,`package.json`),i=w(t),a=i.portless;if(!a||typeof a!=`object`||Array.isArray(a))throw Error(`${n}/package.json has no "portless" object to rebrand (the dev-URL setup drifted). Update packages/cli/src/convert.ts.`);let o=a;o.name=r,T(t,i)}},Xe=e=>{let t=w($(e,`package.json`)).name;t&&Ye(e,X(t))},Ze=(e,t,n)=>{y($(e,`packages/config/src/site.ts`),ye(t,n));let r=X(t.name),i=$(e,`package.json`),a=w(i);a.name=r,a.version=`0.0.0`;for(let e of E)delete a[e];T(i,a),Ye(e,r)},Qe=(e,t,n=H)=>{Ue(e),Ge(e,t),Je(e),Ze(e,t,n),Ie(e,t),Le(e,t)};export{C as A,ue as C,ae as D,se as E,T as F,g as I,w as M,b as N,S as O,y as P,oe as S,ce as T,_e as _,J as a,A as b,Ve as c,Pe as d,H as f,z as g,R as h,Xe as i,v as j,_ as k,Be as l,ge as m,Je as n,Re as o,me as p,qe as r,He as s,Qe as t,ze as u,N as v,M as w,k as x,O as y};
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{t as e}from"./convert-SFS6yxN2.mjs";export{e as convertRepo};
1
+ import{t as e}from"./convert-DjNN2nhd.mjs";export{e as convertRepo};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zerostarter",
3
- "version": "0.1.34",
3
+ "version": "0.1.35",
4
4
  "description": "Go from zero to a production-ready SaaS, rebranded and ready to ship.",
5
5
  "keywords": [
6
6
  "cli",
@@ -1,161 +0,0 @@
1
- import{delimiter as e,dirname as t,isAbsolute as n,join as r,relative as i,resolve as a,sep as o}from"node:path";import{existsSync as s,mkdirSync as c,readFileSync as l,readdirSync as u,rmSync as d,writeFileSync as f}from"node:fs";import{createHash as p}from"node:crypto";import{spawn as m}from"node:child_process";import{access as h}from"node:fs/promises";const g=e=>{let t=[],n=[],r=!1;for(let i of e.split(`
2
- `)){let e=i.trim();if(e){if(!r&&e.startsWith(`# PRESERVE_ON_SYNC`)){n=e.slice(18).replace(/^\s*-\s*/,``).split(`,`).map(e=>e.trim()).filter(Boolean),r=!0;continue}e.startsWith(`#`)||t.push(e)}}return{excludes:t,preserve:n}},_=e=>s(e),v=e=>l(e,`utf8`),y=(e,n)=>{c(t(e),{recursive:!0}),f(e,n)},b=e=>{d(e,{force:!0,recursive:!0})},x=/* @__PURE__ */ new Set([`node_modules`,`.next`,`.turbo`,`dist`]),S=(e,t=!0)=>{for(let n of u(e,{withFileTypes:!0})){let i=r(e,n.name);if(n.name===`.git`){if(t)continue;d(i,{force:!0,recursive:!0});continue}n.isDirectory()?x.has(n.name)?d(i,{force:!0,recursive:!0}):(S(i,!1),u(i).length===0&&d(i,{force:!0,recursive:!0})):n.name.startsWith(`.env`)||d(i,{force:!0})}},ee=e=>{let t=[];for(let n of u(e,{withFileTypes:!0})){if(n.name===`.git`||x.has(n.name))continue;let i=r(e,n.name);n.isDirectory()?t.push(...ee(i)):n.name===`package.json`&&t.push(i)}return t},C=e=>JSON.parse(v(e)),w=(e,t)=>{y(e,`${JSON.stringify(t,null,2)}\n`)},te=(e,t)=>{if(!s(e))return!1;let n=v(e),r=n.replace(t,``);return r!==n&&(f(e,r),!0)},T=[`homepage`,`bugs`,`license`,`author`,`repository`,`funding`],ne=[`name`,`version`,...T],re=(e,t)=>{let n={...e,...t};return Object.keys(n).length>0?n:void 0},ie=(e,t)=>{let n=Array.isArray(e)?e:[],r=Array.isArray(t)?t:[];return n.length||r.length?[.../* @__PURE__ */ new Set([...r,...n])]:void 0},ae=[`dependencies`,`devDependencies`,`optionalDependencies`,`peerDependencies`,`resolutions`,`catalog`,`scripts`,`overrides`],oe=(e,t,n)=>{let r={...t};for(let n of Object.keys(e))n in t||(r[n]=e[n]);for(let n of ae){let i=re(e[n],t[n]);i&&(r[n]=i)}let i=ie(e.workspaces,t.workspaces);if(i&&(r.workspaces=i),n)for(let t of ne)t in e?r[t]=e[t]:delete r[t];return r},E=(e,t,n=process.stdout)=>r=>n.isTTY?`\x1b[${e}m${r}\x1b[${t}m`:r,D=E(`36`,`39`),se=E(`32`,`39`),ce=E(`33`,`39`),O=E(`90`,`39`),k=E(`2`,`22`),A=E(`31`,`39`,process.stderr),le=E(`2`,`22`,process.stderr),ue=E(`38;5;208`,`39`),j=[`◇`,`◆`],M=(e,t)=>{let n=(e%j.length+j.length)%j.length;return`${D(j[n])} ${t}`},N={barStart:`┌`,bar:`│`,barEnd:`└`,active:`◆`,submit:`◇`,radioOn:`●`,radioOff:`○`,checkboxOn:`◼`,checkboxOff:`◻`,warn:`▲`,error:`■`,barH:`─`,cornerTR:`╮`,connectL:`├`,cornerBL:`╰`,cornerBR:`╯`};var P=class extends Error{constructor(...e){super(...e),this.name=`SubprocessError`,this.stdout=``,this.stderr=``}};const F=[`.exe`,`.com`],I=e=>e.replaceAll(/([()\][%!^"`<>&|;, *?])/g,`^$1`),de=e=>I(I(`"${e.replaceAll(/(\\*)"/g,`$1$1\\"`).replace(/(\\*)$/,`$1$1`)}"`)),fe=async(t,n,r)=>{if(F.some(e=>t.toLowerCase().endsWith(e)))return!0;let i=(r.PATH||r.Path||``).split(e).filter(Boolean).map(e=>e.replace(/^"(.*)"$/,`$1`));try{return await Promise.any([n,...i].flatMap(e=>F.map(n=>h(`${a(e,t)}${n}`)))),!0}catch{return!1}},pe=async(e,t,n,r=process.platform===`win32`)=>{let i=n.cwd?String(n.cwd):`.`,a=n.env||process.env;return r&&!n.shell&&!await fe(e,i,a)?[I(e),t.map(de),{...n,shell:!0}]:[e,t,n]},L=async(e,t,n,r)=>{let i=[e,...t].join(` `),[a,o,s]=await pe(e,t,n);s.shell&&o.length>0&&(a=[a,...o].join(` `),o=[]);let c=m(a,o,s),l=``,u=``;return new Promise((e,t)=>{let n=(e,n,r)=>{let i=new P(e,r?{cause:r}:void 0);i.stdout=l,i.stderr=u,i.exitCode=n,t(i)},a=e=>{e.code!==`ERR_STREAM_PREMATURE_CLOSE`&&e.code!==`EPIPE`&&n(`Command failed: ${i}`,void 0,e)};c.stdout&&(c.stdout.setEncoding(`utf8`),c.stdout.on(`data`,e=>{l+=e,r?.(e)}),c.stdout.on(`error`,a)),c.stderr&&(c.stderr.setEncoding(`utf8`),c.stderr.on(`data`,e=>{u+=e,r?.(e)}),c.stderr.on(`error`,a)),c.on(`error`,e=>n(`Command failed: ${i}`,void 0,e)),c.on(`close`,(t,r)=>{if(r)return n(`Command was terminated with ${r}: ${i}`);if(t!==0)return n(`Command failed with exit code ${t}: ${i}`,t??void 0);e({stdout:l,stderr:u})})})},R=async(e,t,n)=>{let{stdout:r}=await L(e,t,{cwd:n,stdio:[`ignore`,`pipe`,`pipe`]});return r},z=async(e,t,n)=>{await L(e,t,{cwd:n,stdio:`inherit`})},me=async(e,t)=>{try{return await L(e,t,{stdio:`ignore`}),!0}catch{return!1}},he=/* @__PURE__ */ RegExp(`\x1B\\[[0-9;?]*[a-zA-Z]`,`g`),B=e=>e.replace(he,``),V=/* @__PURE__ */ new WeakSet,ge=e=>{if(process.stderr.write(`\n ${A(e instanceof Error?e.message:String(e))}\n`),!(e instanceof P)||V.has(e))return;let t=(e.stderr||e.stdout||``).trim();if(!t)return;let n=B(t).split(`
3
- `).map(e=>e.trimEnd()).filter(Boolean).slice(-12);for(let e of n)process.stderr.write(` ${le(e)}\n`)},_e=async(e,t,n)=>{let r=process.stdout,i=n.label??``;if(!r.isTTY){i&&r.write(` ${D(N.active)} ${i}\n`),await z(e,t,n.cwd);return}let a=n.lines??3,o=[],s=0,c=0,l=``,u=``,d=()=>(r.columns||80)-1,f=e=>{r.write(e?`\x1B[?7h`:`\x1B[?7l`)},p=()=>{c>0&&(r.write(`[${c}A`),c=0)},m=()=>{p();let e=0;r.write(` ${M(s,i)}\n`),e++;for(let t of o.slice(-a))r.write(` ${O(N.bar)} ${k(t.slice(0,Math.max(0,d()-5)))}\n`),e++;c=e},h=e=>{u+=e,l+=e;let t=l.split(`
4
- `);l=t.pop()??``;for(let e of t)o.push(B(e).replace(/\r/g,``).replace(/\t/g,` `).trimEnd()),m()};m(),f(!1);let g=()=>{f(!0),process.exit(130)};process.once(`SIGINT`,g);let _=setInterval(()=>{s+=1,m()},400),v=()=>{clearInterval(_),process.removeListener(`SIGINT`,g)};try{await L(e,t,{cwd:n.cwd,stdio:[`ignore`,`pipe`,`pipe`]},h)}catch(e){throw v(),p(),f(!0),r.write(u),e&&typeof e==`object`&&V.add(e),e}v(),p(),f(!0),r.write(` ${D(N.active)} ${n.done??i}\n`);for(let e of o.slice(-a))r.write(` ${O(N.bar)} ${k(e.slice(0,Math.max(0,d()-5)))}\n`)},H=`https://github.com/nrjdalal/zerostarter`,U=`.agents/skills/.sync.json`,W=()=>({adopted:[],customized:[],forkOwned:[],unverified:[]}),G=e=>p(`sha256`).update(e.replace(/\r\n/g,`
5
- `)).digest(`hex`).slice(0,12),K=e=>e.toLowerCase().replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``)||`app`,q=(e,t,n)=>e.replaceAll(`ZeroStarter`,()=>n).replace(/(?<!bunx )zerostarter(?! scaffolding CLI)/g,t),J=`> Synced from ${H}.`,ve=()=>`> [!CAUTION]\n${J} Customize this skill or remove this note to stop syncing.`,Y=e=>e.replace(/^https?:\/\/github\.com\//,``).replace(/\/+$/,``),X=Y(H),Z=e=>{let t=e.split(`
6
- `);if(t[0]===void 0||t[0].trim()!==`---`)return``;for(let e of t.slice(1)){if(e.trim()===`---`)break;if(e.startsWith(`source:`))return e.slice(7).trim()}return``},ye=e=>u(e,{withFileTypes:!0}).filter(t=>t.isDirectory()&&_(r(e,t.name,`SKILL.md`))).map(e=>e.name).sort(),be=e=>{let t=r(e,U);if(!_(t))return{};try{return C(t)}catch{return{}}},xe=e=>{let t=r(e,`.agents/skills`),n=/* @__PURE__ */ new Map;if(!_(t))return n;for(let e of ye(t))n.set(e,v(r(t,e,`SKILL.md`)));return n},Se=(e,t,n,r,i=!1)=>{let a=e.replace(/\r\n/g,`
7
- `).split(`
8
- `),o=a.indexOf(`---`),s=a.indexOf(`---`,o+1);if(o!==0||s===-1)throw Error(`.agents/skills/${r}/SKILL.md has no frontmatter to reconcile (the skill shape drifted). Update packages/cli/src/skills.ts.`);let c=Z(e),l=i||c===``||c===`local`?H:c,u=a.slice(o+1,s).map(e=>e.startsWith(`source:`)?`source: ${l}`:q(e,t,n));u.some(e=>e.startsWith(`source:`))||u.push(`source: ${l}`);let d=q(a.slice(s+1).join(`
9
- `),t,n).replace(/^\n+/,``),f=l===H?`${ve()}\n\n`:``;return`---\n${u.join(`
10
- `)}\n---\n\n${f}${d}`},Ce=(e,t,n)=>{let i=r(e,`.agents/skills`),a=W();if(!_(i))return a;let o=K(t.name),s=be(e),c={};for(let e of ye(i)){let l=r(i,e,`SKILL.md`),u=v(l),d=Se(u,o,t.name,e),f=n===void 0?void 0:n.get(e),p=s[e];if(f!==void 0){let n=Z(f),r=t=>{y(l,f),t.push(e),p&&(c[e]=p)},i=Y(n);if(n!==``&&i!==X){r(a.forkOwned);continue}if(i===X&&!f.includes(J)){r(a.forkOwned);continue}if(p){if(p.written!==G(f)){r(a.customized);continue}}else G(f)!==G(d)&&G(f)!==G(Se(u,o,t.name,e,!0))&&a.unverified.push(e)}y(l,d),c[e]={ref:`main`,upstream:G(u),written:G(d)},a.adopted.push(e)}return w(r(e,U),c),a},we=e=>{let t=r(e,`AGENTS.md`);if(!_(t))return!1;let n=v(t);return!n.includes(`<!-- skills:custom -->`)||!n.includes(`<!-- skills:vendored -->`)},Te=async e=>{if(_(r(e,`.github/scripts/skills-manager.ts`)))try{await R(`bun`,[`.github/scripts/skills-manager.ts`],e)}catch{}},Ee=(e,t)=>{let n=C(r(e,`package.json`)).name;return n?Ce(e,{name:n},t):W()},Q={allowlist:!1,apiDocs:!0,blog:!0,docs:!0,internalDocs:!0,waitlist:!1},De=e=>`// Optional surfaces a fork enables or disables. Typed boolean (not \`as const\`) so a fork can flip them and the runtime gates are not dead code. Off means the routes 404 and the links, nav, sitemap, llms, and search drop the surface. waitlist off makes the home a plain landing page.
11
- export const features = {
12
- allowlist: ${e.allowlist},
13
- apiDocs: ${e.apiDocs},
14
- blog: ${e.blog},
15
- docs: ${e.docs},
16
- internalDocs: ${e.internalDocs},
17
- waitlist: ${e.waitlist},
18
- }
19
-
20
- export type Feature = keyof typeof features`,Oe=({name:e},t=Q)=>{let n=e.charAt(0).toUpperCase()+e.slice(1);return`// Brand identity for this app: the single source a fork edits to rebrand. web reads it via lib/config.ts.
21
- export const site = {
22
- name: ${JSON.stringify(n)},
23
- description: ${JSON.stringify(`${n} is just getting started. Tell its story here.`)},
24
- tagline: "Your tagline, ready when you are.",
25
- social: {
26
- discord: "",
27
- github: "",
28
- x: "",
29
- },
30
- // Local-only dev agent identity (api/hono agents router).
31
- agent: {
32
- name: "LocalAgent",
33
- email: "agent@local.host",
34
- },
35
- // Injectable long-form text blocks. A product sets its own, or leaves them empty.
36
- apiReferenceDescription: "",
37
- llmsFullPreamble: "",
38
- } as const
39
-
40
- export type Site = typeof site
41
-
42
- ${De(t)}
43
- `},ke=()=>`import { features, site } from "@packages/config/site"
44
- import { redirect } from "next/navigation"
45
-
46
- // Fresh fork: the waitlist capture when the waitlist feature is on, otherwise a plain landing page. Replace this with your real home when ready.
47
- export default function Home() {
48
- if (features.waitlist) redirect("/waitlist")
49
-
50
- return (
51
- <main className="flex min-h-svh flex-col items-center justify-center p-8 text-center">
52
- <div className="mx-auto flex w-full max-w-xl flex-col items-center">
53
- <h1 className="mb-4 text-5xl font-bold tracking-tight sm:text-6xl">{site.name}</h1>
54
- <p className="text-muted-foreground max-w-md text-lg">{site.tagline}</p>
55
- </div>
56
- </main>
57
- )
58
- }
59
- `,Ae=()=>`# AGENTS.md
60
-
61
- Guidance for AI coding agents working in this repository.
62
-
63
- ## Instructions
64
-
65
- - ALWAYS: Use \`@/\` for imports, and follow the \`design\` skill for UI and styling conventions.
66
- - ALWAYS: Keep documentation in sync with every change.
67
- - NEVER: Include "Co-authored-by" in commit messages.
68
- - NEVER: Use em-dashes (the long dash, U+2014) in code, comments, docs, or copy. Regular hyphens are fine; for a pause or aside, use a comma, colon, or period.
69
-
70
- ## Skills
71
-
72
- Skills live in \`.agents/skills\` (symlinked to \`.claude/skills\` and \`.github/skills\`, so every agent tool reads the same files). Each is a \`SKILL.md\` with a \`description\` trigger and a literal procedure. Start with \`codebase-map\` to orient, then load the task skill that fits.
73
-
74
- These tables are generated from each skill's own description by \`bun .github/scripts/skills-manager.ts\`, which a pre-commit hook runs for you. **Custom** skills are maintained here; **vendored** skills are copied verbatim from an upstream project (re-vendor to update, do not hand-edit).
75
-
76
- **Custom**
77
-
78
- <!-- skills:custom -->
79
-
80
- <!-- /skills:custom -->
81
-
82
- **Vendored** (upstream, copied verbatim)
83
-
84
- <!-- skills:vendored -->
85
-
86
- <!-- /skills:vendored -->
87
- `,je=({name:e})=>`# ${e.charAt(0).toUpperCase()+e.slice(1)}
88
-
89
- Built on top of [ZeroStarter](https://zerostarter.dev).
90
-
91
- ## Development
92
-
93
- \`\`\`bash
94
- bun run dev
95
- \`\`\`
96
-
97
- This serves named \`.localhost\` dev URLs via portless (\`bunx portless list\` shows them); \`PORTLESS=0 bun run dev\` uses fixed ports instead (web \`:3000\`, api \`:4000\`).
98
- `,Me=()=>`---
99
- slug: /docs
100
- title: Introduction
101
- description: Documentation.
102
- ---
103
-
104
- # Introduction
105
-
106
- This is your documentation home. Start writing, this page is yours.
107
- `,Ne=()=>`---
108
- slug: /console/docs
109
- title: Introduction
110
- description: Internal documentation.
111
- ---
112
-
113
- # Introduction
114
-
115
- Your team's internal docs live here.
116
- `,Pe=e=>`---
117
- title: Blog
118
- description: Latest articles and updates
119
- createdAt: ${e}
120
- ---
121
-
122
- ## Recent Posts
123
-
124
- <BlogPostList />
125
- `,Fe=e=>`---
126
- title: Hello World
127
- description: The first post on your new blog.
128
- createdAt: ${e}
129
- publishedAt: ${e}
130
- ---
131
-
132
- ## Hello World
133
-
134
- Your very first post. Replace it with something worth reading.
135
- `,Ie=()=>`import type { DocsConfig } from "./src/lib/docs"
136
-
137
- const docsConfig = {
138
- docs: {
139
- "Getting Started": [
140
- {
141
- "/docs": {
142
- title: "Introduction",
143
- description: "Documentation.",
144
- },
145
- },
146
- ],
147
- },
148
- console: {
149
- "Getting Started": [
150
- {
151
- "/console/docs": {
152
- title: "Introduction",
153
- description: "Internal documentation.",
154
- },
155
- },
156
- ],
157
- },
158
- } satisfies DocsConfig
159
-
160
- export default docsConfig
161
- `,$=(e,...t)=>r(e,...t),Le=e=>{let t=$(e,`.gitpickignore`);if(!_(t))return;let s=a(e),c=(e,t)=>{throw Error(`.gitpickignore entry "${e}" ${t}; the in-place converter only removes literal paths under the root.`)},l=g(v(t)).excludes.map(e=>{/[*?![\]]/.test(e)&&c(e,`is not a literal path (a glob or negation would diverge from gitpick's fetch)`);let t=i(s,a(s,e));return(n(e)||!t||t===`..`||t.startsWith(`..${o}`)||n(t))&&c(e,`is not inside the project`),r(s,t)});for(let e of l)b(e);b(t)},Re=/[ \t]*\{[^}\r\n]*href:[ \t]*["']\/hire["'][^}\r\n]*\},?[ \t]*\r?\n/,ze=(e,t)=>{let n=/* @__PURE__ */ new Date,r=n.toISOString().slice(0,10),i=[n.getFullYear(),n.getMonth()+1,n.getDate()].map(e=>String(e).padStart(2,`0`)).join(`-`),a=i<r?i:r;y($(e,`web/next/content/docs/index.mdx`),Me()),y($(e,`web/next/content/blog/index.mdx`),Pe(a)),y($(e,`web/next/content/blog/hello-world.mdx`),Fe(a)),y($(e,`web/next/content/console/docs/index.mdx`),Ne()),y($(e,`web/next/docs.config.ts`),Ie()),y($(e,`web/next/public/.gitkeep`),``),y($(e,`web/next/src/app/page.tsx`),ke()),y($(e,`AGENTS.md`),Ae()),y($(e,`README.md`),je(t))},Be=`web/next/src/lib/marketing/fonts.ts`,Ve=e=>_($(e,Be)),He=(e,t=!1)=>{let n=$(e,`web/next/src/lib/fonts.ts`),r=$(e,Be),i=$(e,`web/next/src/components/common/navbar.tsx`);if(te(i,Re),_(r)&&!t)throw Error(`web/next/src/lib/marketing/fonts.ts survived the fork strip (add web/next/src/lib/marketing/ to .gitpickignore).`);if(_(n)&&v(n).includes(`fonts/marketing/`))throw Error(`fonts.ts references fonts/marketing/; author-only marketing fonts must live in the fork-excluded web/next/src/lib/marketing/, not the shared fonts.ts.`);if(_(i)&&/href:\s*["']\/hire["']/.test(v(i)))throw Error(`common/navbar.tsx still has the /hire entry after fixDangling (regex drift). Update packages/cli/src/convert.ts.`)},Ue=(e,t)=>{for(let[n,r]of[[`web/next`,t],[`api/hono`,`api.${t}`]]){let t=$(e,n,`package.json`),i=C(t),a=i.portless;if(!a||typeof a!=`object`||Array.isArray(a))throw Error(`${n}/package.json has no "portless" object to rebrand (the dev-URL setup drifted). Update packages/cli/src/convert.ts.`);let o=a;o.name=r,w(t,i)}},We=e=>{let t=C($(e,`package.json`)).name;t&&Ue(e,K(t))},Ge=(e,t,n)=>{y($(e,`packages/config/src/site.ts`),Oe(t,n));let r=K(t.name),i=$(e,`package.json`),a=C(i);a.name=r,a.version=`0.0.0`;for(let e of T)delete a[e];w(i,a),Ue(e,r)},Ke=(e,t,n=Q)=>{Le(e),ze(e,t),He(e),Ge(e,t,n),Ce(e,t)};export{C as A,A as C,_ as D,S as E,y as M,w as N,ee as O,g as P,M as S,oe as T,D as _,Q as a,se as b,Ee as c,me as d,ge as f,N as g,_e as h,We as i,b as j,v as k,Te as l,z as m,He as n,W as o,R as p,Ve as r,we as s,Ke as t,xe as u,k as v,ce as w,ue as x,O as y};