zerostarter 0.0.0-0227a80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/index.d.mts +1 -0
- package/dist/bin/index.mjs +66 -0
- package/dist/convert-DMU8fiBc.mjs +104 -0
- package/dist/index.d.mts +9 -0
- package/dist/index.mjs +1 -0
- package/package.json +32 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{a as e,c as t,d as n,f as r,i,l as a,n as o,o as s,r as c,s as l,t as u,u as d}from"../convert-DMU8fiBc.mjs";import{basename as f,delimiter as p,join as m,resolve as h}from"node:path";import{existsSync as g,readdirSync as _}from"node:fs";import{createInterface as v}from"node:readline/promises";import{parseArgs as y}from"node:util";import{randomBytes as b}from"node:crypto";import{spawn as ee}from"node:child_process";import{access as te}from"node:fs/promises";import{homedir as ne}from"node:os";var re={name:`zerostarter`,version:`0.0.0-0227a80`,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`,test:`bun test`},devDependencies:{"@packages/config":`workspace:*`,"@types/bun":`catalog:`,"@types/node":`catalog:`,tsdown:`catalog:`,typescript:`catalog:`}};const x=(e,t=process.stdout)=>n=>t.isTTY?`\x1b[${e}m${n}\x1b[0m`:n,S=x(`38;5;208`),C=x(`38;2;63;185;80`),w=x(`38;2;210;153;34`),T=x(`38;2;56;189;248`),E=x(`38;2;110;118;129`),D=x(`2`),O=x(`38;2;248;81;73`,process.stderr),k={barStart:`┌`,bar:`│`,barEnd:`└`,active:`◆`,submit:`◇`,radioOn:`●`,radioOff:`○`,warn:`▲`,error:`■`,barH:`─`,cornerTR:`╮`,connectL:`├`,cornerBR:`╯`},A=()=>!!(process.stdin.isTTY&&process.stdout.isTTY),j=(e,t=e)=>process.stdout.isTTY?`\x1b]8;;${e}\x07${t}\x1b]8;;\x07`:e,M=e=>{process.stdout.write(`\n${E(k.barStart)} ${e}\n`)},N=e=>{process.stdout.write(`${E(k.bar)}\n${E(k.barEnd)} ${e}\n`)},P=(e=`Cancelled`)=>{process.stdout.write(`${E(k.bar)}\n${E(k.barEnd)} ${O(e)}\n`)},F=e=>e.replace(RegExp(`\x1B\\[[0-9;?]*[a-zA-Z]`,`g`),``).replace(RegExp(`\x1B\\]8;;.*?\\x07`,`g`),``).length,ie=(e,t=``)=>{let n=`\n${e}\n`.split(`
|
|
3
|
+
`),r=F(t),i=Math.max(n.reduce((e,t)=>Math.max(e,F(t)),r),r)+2,a=n.map(e=>`${E(k.bar)} ${e}${` `.repeat(i-F(e))}${E(k.bar)}`).join(`
|
|
4
|
+
`);process.stdout.write(`${E(k.bar)}\n${T(k.submit)} ${t} ${E(k.barH.repeat(Math.max(i-r-1,1))+k.cornerTR)}\n${a}\n${E(k.connectL+k.barH.repeat(i+2)+k.cornerBR)}\n`)},I=(e,t=[])=>{process.stdout.write(`${E(k.bar)}\n${T(k.submit)} ${e}\n`);for(let e of t)process.stdout.write(`${E(k.bar)} ${e}\n`)},L=(e,t=[])=>{process.stdout.write(`${E(k.bar)}\n${w(k.warn)} ${w(e)}\n`);for(let e of t)process.stdout.write(`${E(k.bar)} ${w(e)}\n`)},ae=e=>{let t=e?`${C(k.radioOn)} Yes`:D(`${k.radioOff} Yes`),n=e?D(`${k.radioOff} No`):`${C(k.radioOn)} No`;return`${E(k.bar)} ${t} ${D(`/`)} ${n}`},R=async(e,t=!0)=>{let n=process.stdout;return A()?new Promise(r=>{let i=t,a=process.stdin;n.write(`${E(k.bar)}\n${T(k.active)} ${e}\n`),n.write(ae(i)),a.setRawMode(!0),a.resume(),a.setEncoding(`utf8`);let o=()=>{a.setRawMode(!1),a.pause(),a.removeListener(`data`,c)},s=()=>{o(),n.write(`\r\x1B[K`),n.write(`\x1B[1A\r\x1B[K`),n.write(`${T(k.submit)} ${e} ${D(i?`Yes`:`No`)}\n`),r(i)};function c(t){t===``?(o(),n.write(`\r\x1b[K\x1b[1A\r\x1b[K${D(k.submit)} ${D(e)}\n`),P(),process.exit(130)):t===`\r`||t===`
|
|
5
|
+
`?s():t===`y`||t===`Y`?(i=!0,s()):t===`n`||t===`N`?(i=!1,s()):(t===`\x1B[C`||t===`\x1B[D`||t===`\x1B[A`||t===`\x1B[B`||t===`h`||t===`l`)&&(i=!i,n.write(`\r\x1b[K${ae(i)}`))}a.on(`data`,c)}):(n.write(`${E(k.bar)}\n${T(k.submit)} ${e} ${D(t?`Yes`:`No`)}\n`),t)},oe=async(e,t=``)=>{process.stdout.write(`${E(k.bar)}\n${T(k.active)} ${e}${t?` ${D(`(${t})`)}`:``}\n`);let n=v({input:process.stdin,output:process.stdout});try{return(await n.question(`${E(k.bar)} `)).trim()||t}finally{n.close()}};var se=class extends Error{constructor(...e){super(...e),this.name=`SubprocessError`,this.stdout=``,this.stderr=``}};const ce=[`.exe`,`.com`],z=e=>e.replaceAll(/([()\][%!^"`<>&|;, *?])/g,`^$1`),le=e=>z(z(`"${e.replaceAll(/(\\*)"/g,`$1$1\\"`).replace(/(\\*)$/,`$1$1`)}"`)),ue=async(e,t,n)=>{if(ce.some(t=>e.toLowerCase().endsWith(t)))return!0;let r=(n.PATH||n.Path||``).split(p).filter(Boolean).map(e=>e.replace(/^"(.*)"$/,`$1`));try{return await Promise.any([t,...r].flatMap(t=>ce.map(n=>te(`${h(t,e)}${n}`)))),!0}catch{return!1}},de=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 ue(e,i,a)?[z(e),t.map(le),{...n,shell:!0}]:[e,t,n]},B=async(e,t,n,r)=>{let i=[e,...t].join(` `),[a,o,s]=await de(e,t,n);s.shell&&o.length>0&&(a=[a,...o].join(` `),o=[]);let c=ee(a,o,s),l=``,u=``;return new Promise((e,t)=>{let n=(e,n,r)=>{let i=new se(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})})})},V=async(e,t,n)=>{let{stdout:r}=await B(e,t,{cwd:n,stdio:[`ignore`,`pipe`,`pipe`]});return r},H=async(e,t,n)=>{await B(e,t,{cwd:n,stdio:`inherit`})},U=async(e,t)=>{try{return await B(e,t,{stdio:`ignore`}),!0}catch{return!1}},fe=RegExp(`\x1B\\[[0-9;?]*[a-zA-Z]`,`g`),pe=e=>e.replace(fe,``),W=e=>e>=1e3?`[${(e/1e3).toFixed(2)}s]`:`[${e.toFixed(2)}ms]`,G=async(e,t,n)=>{let r=Date.now(),i=process.stdout;if(!i.isTTY){i.write(`${E(k.bar)}\n${T(k.active)} ${n.label}\n`),await H(e,t,n.cwd);return}let a=n.lines??3,o=[],s=0,c=``,l=``,u=()=>(i.columns||80)-1,d=e=>{i.write(e?`\x1B[?7h`:`\x1B[?7l`)},f=()=>{s>0&&(i.write(`[${s}A[0J`),s=0)},p=()=>{f();let e=o.slice(-a);for(let t of e)i.write(`${E(k.bar)} ${D(t.slice(0,Math.max(0,u()-3)))}\n`);s=e.length},m=e=>{l+=e,c+=e;let t=c.split(`
|
|
6
|
+
`);c=t.pop()??``;for(let e of t)o.push(pe(e).replace(/\r/g,``).replace(/\t/g,` `).trimEnd()),p()};i.write(`${E(k.bar)}\n${T(k.active)} ${n.label}\n`),d(!1);try{await B(e,t,{cwd:n.cwd,stdio:[`ignore`,`pipe`,`pipe`]},m)}catch(e){throw f(),d(!0),i.write(l),e}f(),d(!0);let h=n.summarize(l,Date.now()-r).trim();i.write(`[1A\r[K${T(k.submit)} ${h||n.label}\n`)},me=`pglaunch@5.5.7`,he=async()=>U(`docker`,[`info`]),ge=e=>{let r=m(e,`.env`);if(!s(r)){let i=m(e,`.env.example`);n(r,s(i)?t(i):``)}return r},_e=(e,n)=>{let r=`${n}=`,i=t(e).split(`
|
|
7
|
+
`).find(e=>e.startsWith(r));return i?i.slice(r.length).trim():``},ve=(e,r,i)=>{let a=`${r}=`,o=t(e).split(`
|
|
8
|
+
`),s=o.findIndex(e=>e.startsWith(a));if(s>=0)o[s]=`${a}${i}`;else{for(;o.length&&o[o.length-1]===``;)o.pop();o.push(`${a}${i}`,``)}n(e,o.join(`
|
|
9
|
+
`))},ye=e=>{let t=ge(e);_e(t,`BETTER_AUTH_SECRET`)||ve(t,`BETTER_AUTH_SECRET`,b(32).toString(`base64`))},K=e=>{let t=m(e,`.env`);return s(t)&&_e(t,`POSTGRES_URL`)!==``},be=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]:``}},xe=async(e,t)=>{let n=t?[`--bun`,me,`-k`,`-c`]:[`--bun`,me,`-k`],r;try{r=await V(`bunx`,n,e)}catch(e){let t=e;r=[t.stdout,t.stderr].filter(Boolean).join(`
|
|
10
|
+
`)}return be(r)},Se=e=>new Promise(t=>setTimeout(t,e)),Ce=async e=>{if(e)for(let t=0;t<30;t++){if(await U(`docker`,[`exec`,e,`pg_isready`,`-U`,`postgres`]))return;await Se(1e3)}},we=async e=>{let t=ge(e),n=await xe(e,!1)||await xe(e,!0);if(!n)throw Error(`pglaunch did not print a connection URL`);ve(t,`POSTGRES_URL`,n.url),await Ce(n.container);let r=Te(e);await G(`bun`,[`run`,`db:migrate`],{cwd:e,label:`Provisioning a local Postgres`,summarize:(e,t)=>e.includes(`migrations applied successfully`)?`${r} migration${r===1?``:`s`} applied ${W(t)}`:`Database migrated ${W(t)}`})},Te=e=>{try{return _(m(e,`packages/db/drizzle`)).filter(e=>e.endsWith(`.sql`)).length}catch{return 0}},q=async e=>{await G(`bun`,[`install`],{cwd:e,label:`Installing dependencies`,summarize:e=>(e.match(/[\d,]+ packages installed[^\n]*/g)??e.match(/Checked [^\n]*install[^\n]*/g))?.at(-1)?.trim()??`Dependencies installed`})},Ee=async()=>{if(!await U(`bun`,[`--version`]))throw Error(`bun is not on PATH`)},De=async(e=Ee,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}},Oe=async(e,t=`main`)=>{await V(`bunx`,[`--bun`,`gitpick@6.0.0`,`https://github.com/nrjdalal/zerostarter/tree/${t}`,e])},ke=async(e,t=`main`)=>{await V(`bunx`,[`--bun`,`gitpick@6.0.0`,`https://github.com/nrjdalal/zerostarter/tree/${t}`,e,`-o`])},Ae=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()},je=async e=>(await V(`git`,[`status`,`--porcelain`],e)).trim()===``,Me=async(e,t)=>{for(let n of t)try{await V(`git`,[`checkout`,`--`,n],e),await V(`git`,[`clean`,`-fd`,`--`,n],e)}catch{}},Ne=async e=>{await V(`git`,[`reset`,`--hard`,`HEAD`],e),await V(`git`,[`clean`,`-fd`],e)},Pe=async(e,t,n)=>{if(!s(m(e,`.git`)))throw Error(t);if(!await je(e))throw Error(n)},Fe=async(e,t,n)=>{try{await n()}catch(n){throw await Ne(e),console.log(t),n}},Ie=async e=>{await V(`git`,[`init`,`-q`,`-b`,`canary`],e)},Le=async(e,t)=>{await V(`git`,[`branch`,t],e)},J=async(e,t)=>{await V(`git`,[`add`,`-A`],e);try{await V(`git`,[`commit`,`--no-verify`,`-q`,`-m`,t],e)}catch{}},Re=(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`,ze=(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`]},Be=(e=process.argv.slice(2))=>[`zerostarter`,...e].join(` `),Ve=()=>m(process.env.BUN_INSTALL||m(ne(),`.bun`),`bin`),Y=async(e=!1)=>{if(await De())return;let t=Re(),n=`bunx --bun ${Be()}`;if(console.error(O(`Seems like you are using ${t===`unknown`?`npx/pnpx`:t} or Windows, try using:`)),console.error(S(` ${n}`)),console.error(``),e||A()&&await R(`Or install bun now and continue?`,!0)){let{cmd:e,args:t}=ze();console.error(`Installing bun from https://bun.sh/install ...`);try{if(await H(e,t),process.env.PATH=`${Ve()}${p}${process.env.PATH||``}`,await De())return;console.error(O(`
|
|
11
|
+
bun is installed but isn't on this shell's PATH yet. Open a new terminal and re-run:`)),console.error(S(` ${n}`))}catch{console.error(O(`
|
|
12
|
+
Could not install bun. Install it from https://bun.sh, then re-run:`)),console.error(S(` ${n}`))}}process.exit(1)},He=e=>!g(e)||_(e).filter(e=>e!==`.git`).length===0,X=e=>s(m(e,`packages/config/src/site.ts`)),Ue=async e=>{let{positionals:t,values:n}=y({allowPositionals:!0,args:e,options:{db:{type:`boolean`},"dry-run":{type:`boolean`},help:{short:`h`,type:`boolean`},yes:{short:`y`,type:`boolean`}}});if(n.help){console.log(`Usage:
|
|
13
|
+
$ bunx zerostarter init [dir] [options]
|
|
14
|
+
|
|
15
|
+
Scaffold ZeroStarter into dir (default .) as a fresh product. The author's
|
|
16
|
+
content and public assets are left out for you to supply; the
|
|
17
|
+
dir name becomes the project name and site.ts + package.json are rebranded. If
|
|
18
|
+
the dir already holds a ZeroStarter clone it is used in place; otherwise the
|
|
19
|
+
latest ZeroStarter is fetched into it first.
|
|
20
|
+
|
|
21
|
+
Options:
|
|
22
|
+
-y, --yes Skip prompts, taking defaults (provisions Postgres when Docker is running)
|
|
23
|
+
--db Provision a local Postgres (pglaunch) and migrate; needs Docker
|
|
24
|
+
--dry-run Print the plan without writing anything
|
|
25
|
+
-h, --help Display help`);return}n[`dry-run`]||await Y(!!n.yes);let r=A()&&!n.yes,i=t[0]??`.`,a=h(i),o=X(a);if(!o&&!He(a)){if(!r)throw Error(`Directory is not empty. Run it in an empty directory, or pass a project name: bunx zerostarter init <name>`);let e=await oe(`Directory isn't empty. Project name`);if(!e)throw Error(`No directory name provided.`);i=e}let c=h(i),l=f(c),d={name:l};if(n[`dry-run`]){console.log(`bunx zerostarter init (dry run)`),console.log(` target: ${c}`),console.log(` name: ${l}`),console.log(` mode: ${X(c)?`in place`:`fetch first`}`);return}if(M(T(j(`https://zerostarter.dev`))),o&&r&&!await R(w(`Convert ${c} in place? This rewrites files and commits.`),!1)){P(`Aborted`);return}X(c)||(await Oe(c),I(`Fetched the latest ZeroStarter`)),s(m(c,`.git`))||(await Ie(c),await J(c,`ci(init): scaffold from zerostarter`),await Le(c,`main`)),u(c,d),I(`Rebranded to ${l}`),await q(c),await J(c,`ci(init): re-baseline as ${l}`),ye(c);let p=!1,g=await he(),_=K(c),v=!1;if(_?n.db&&L(`--db ignored: POSTGRES_URL is already set in .env.`):v=n.db?!0:r?await R(`Provision a local Postgres database now?`,g):g,v&&g)try{await we(c),p=!0}catch(e){let t=e.stderr,n=t?String(t).trim():e instanceof Error?e.message:String(e);K(c)?L(`Postgres is provisioned, but the migration failed; run bun run db:migrate.`,n?[n]:[]):L(`Database setup failed; set POSTGRES_URL in .env yourself.`,n?[n]:[])}else v&&L(`Docker isn't running, so the database wasn't provisioned. Set POSTGRES_URL in .env, or start Docker and re-run for automatic setup.`);let b=[];c!==process.cwd()&&b.push(S(`cd ${i}`)),K(c)||b.push(S(`set POSTGRES_URL in .env`)),p||b.push(S(`bun run db:migrate`)),b.push(S(`bun run dev`)),I(C(`${l} is ready`)),ie(b.join(`
|
|
26
|
+
`),`Next steps`),ie([`Edit ${S(`packages/config/src/site.ts`)} to manage branding`,`Edit ${S(`web/next/content`)} to manage blog and docs`].join(`
|
|
27
|
+
`),`Make it yours`),p||console.log(`
|
|
28
|
+
It needs a Postgres database to run: a hosted one like Neon works, or a local Docker one (re-run with Docker running to auto-provision). OAuth and other credentials are optional.`)},We=async t=>{let{positionals:n,values:r}=y({allowPositionals:!0,args:t,options:{help:{short:`h`,type:`boolean`},yes:{short:`y`,type:`boolean`}}});if(r.help){console.log(`Usage:
|
|
29
|
+
$ bunx zerostarter reinit [dir] [options]
|
|
30
|
+
|
|
31
|
+
Re-scaffold an existing git repo (default .) as a fresh ZeroStarter: delete every
|
|
32
|
+
file (keeping .git and your .env* files, so history, remote, and secrets survive), fetch the
|
|
33
|
+
latest ZeroStarter, and rebrand to the dir name. The commit lands on the current
|
|
34
|
+
branch; push when ready.
|
|
35
|
+
|
|
36
|
+
Options:
|
|
37
|
+
-y, --yes Skip the confirmation prompt
|
|
38
|
+
-h, --help Display help`);return}await Y(!!r.yes);let i=h(n[0]??`.`),a=f(i),o=A()&&!r.yes;if(await Pe(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.`),M(T(j(`https://zerostarter.dev`))),o&&!await R(`Delete every file in ${i} (keeping .git and .env*) and re-scaffold as ${a}?`,!1)){P(`Aborted`);return}await Fe(i,w(`reinit failed; restored your committed files (deleted gitignored files, except .env*, are gone).`),async()=>{e(i),await ke(i),I(`Fetched the latest ZeroStarter (kept .git and .env*)`),u(i,{name:a}),I(`Rebranded to ${a}`),await q(i),ye(i),await J(i,`ci(reinit): re-baseline as ${a}`)}),N(C(`${a} re-scaffolded; .git history, remote, and .env* files are intact`)),console.log(),console.log(`Next steps:`),K(i)||console.log(` ${S(`set POSTGRES_URL in .env`)} # your Postgres connection string`),console.log(` ${S(`bun run db:migrate`)}`),console.log(` ${S(`bun run dev`)}`),console.log(` ${S(`git push`)} # to your existing remote`)},Ge=async e=>{let{positionals:t,values:n}=y({allowPositionals:!0,args:e,options:{help:{short:`h`,type:`boolean`}}});if(n.help){console.log(`Usage:
|
|
39
|
+
$ bunx zerostarter sync [dir] [options]
|
|
40
|
+
|
|
41
|
+
Re-baseline an existing fork (default .) on the latest ZeroStarter: a gitpick overlay
|
|
42
|
+
updates the starter files while your content, public/marketing, branding, package.json
|
|
43
|
+
identity, and favicon are preserved. Requires a clean tree; lands as a reviewable diff
|
|
44
|
+
you commit yourself.
|
|
45
|
+
|
|
46
|
+
Options:
|
|
47
|
+
-h, --help Display help`);return}await Y();let u=h(t[0]??`.`);await Pe(u,`No git repository in ${u}. Run sync inside an existing fork.`,`Working tree has uncommitted changes. Commit or stash them first so the sync lands as a reviewable diff.`),M(T(j(`https://zerostarter.dev`)));let f=m(u,`package.json`),p=new Map(l(u).map(e=>[e,a(e)])),g=i(await Ae());g.length===0&&L(`No PRESERVE_ON_SYNC directive found; fork-owned files may be overwritten.`),await Fe(u,w(`Sync failed; rolled the working tree back to your last commit.`),async()=>{await ke(u),o(u),d(m(u,`.gitpickignore`));for(let[e,t]of p)s(e)&&r(e,c(t,a(e),e===f));await Me(u,g)}),I(`Overlaid the latest ZeroStarter (content, public/marketing, and branding preserved)`),await q(u),N(S(`Synced to the latest ZeroStarter`)),console.log(),console.log(`Starter files were updated (edits to them were overwritten); files you added and your`),console.log(`content, public/marketing, and branding were preserved.`),console.log(w(`Review the diff and commit: git -C ${u} status`))},{author:Z,name:Q,version:Ke}=re,$=`Version:
|
|
48
|
+
${Q}@${Ke}
|
|
49
|
+
|
|
50
|
+
Go from zero to a production-ready SaaS, rebranded and ready to ship.
|
|
51
|
+
|
|
52
|
+
Usage:
|
|
53
|
+
$ ${Q} <command> [options]
|
|
54
|
+
|
|
55
|
+
Commands:
|
|
56
|
+
init [dir] Scaffold ZeroStarter into dir (default .) as a fresh product.
|
|
57
|
+
The dir name becomes the project name.
|
|
58
|
+
reinit [dir] Re-scaffold an existing git repo as a fresh ZeroStarter (keeps .git and .env)
|
|
59
|
+
sync Re-baseline an existing fork on ZeroStarter's latest scaffold
|
|
60
|
+
|
|
61
|
+
Options:
|
|
62
|
+
-v, --version Display version
|
|
63
|
+
-h, --help Display help
|
|
64
|
+
|
|
65
|
+
Author:
|
|
66
|
+
${Z.name} <${Z.email}> (${j(Z.url)})`;(async()=>{try{let e=process.argv.slice(2),t=e[0],n=e.slice(1);switch(t){case`init`:return await Ue(n);case`reinit`:return await We(n);case`sync`:return await Ge(n);case void 0:case`-h`:case`--help`:console.log($);return;case`-v`:case`--version`:console.log(`${Q}@${Ke}`);return;default:console.error(O(`Unknown command: ${t}\n`)),console.log($),process.exit(1)}}catch(e){console.error(O(e instanceof Error?e.message:String(e))),process.exit(1)}})();export{};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import{dirname as e,join as t}from"node:path";import{existsSync as n,mkdirSync as r,readFileSync as i,readdirSync as a,rmSync as o,writeFileSync as s}from"node:fs";const c=e=>n(e),l=e=>i(e,`utf8`),u=(t,n)=>{r(e(t),{recursive:!0}),s(t,n)},d=e=>{o(e,{force:!0,recursive:!0})},f=new Set([`node_modules`,`.next`,`.turbo`,`dist`]),p=(e,n=!0)=>{for(let r of a(e,{withFileTypes:!0})){let i=t(e,r.name);if(r.name===`.git`){if(n)continue;o(i,{force:!0,recursive:!0});continue}r.isDirectory()?f.has(r.name)?o(i,{force:!0,recursive:!0}):(p(i,!1),a(i).length===0&&o(i,{force:!0,recursive:!0})):r.name.startsWith(`.env`)||o(i,{force:!0})}},m=e=>{let n=[];for(let r of a(e,{withFileTypes:!0})){if(r.name===`.git`||f.has(r.name))continue;let i=t(e,r.name);r.isDirectory()?n.push(...m(i)):r.name===`package.json`&&n.push(i)}return n},h=e=>JSON.parse(l(e)),g=(e,t)=>{u(e,`${JSON.stringify(t,null,2)}\n`)},_=(e,t)=>{if(!n(e))return!1;let r=l(e),i=r.replace(t,``);return i===r?!1:(s(e,i),!0)},v=[`homepage`,`bugs`,`license`,`author`,`repository`,`funding`],y=[`name`,`version`,...v],b=e=>{let t=e.split(`
|
|
2
|
+
`).find(e=>e.trim().startsWith(`# PRESERVE_ON_SYNC`));return t?t.trim().slice(18).replace(/^\s*-\s*/,``).split(`,`).map(e=>e.trim()).filter(Boolean):[]},x=(e,t)=>{let n={...e,...t};return Object.keys(n).length>0?n:void 0},S=(e,t)=>{let n=Array.isArray(e)?e:[],r=Array.isArray(t)?t:[];return n.length||r.length?[...new Set([...r,...n])]:void 0},C=[`dependencies`,`devDependencies`,`optionalDependencies`,`peerDependencies`,`resolutions`,`catalog`,`scripts`,`overrides`],w=(e,t,n)=>{let r={...t};for(let n of Object.keys(e))n in t||(r[n]=e[n]);for(let n of C){let i=x(e[n],t[n]);i&&(r[n]=i)}let i=S(e.workspaces,t.workspaces);if(i&&(r.workspaces=i),n)for(let t of y)t in e?r[t]=e[t]:delete r[t];return r},T=({name:e})=>{let t=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.
|
|
3
|
+
export const site = {
|
|
4
|
+
name: "${t}",
|
|
5
|
+
description: "${t} is just getting started. Tell its story here.",
|
|
6
|
+
tagline: "Your tagline, ready when you are.",
|
|
7
|
+
social: {
|
|
8
|
+
github: "",
|
|
9
|
+
x: "",
|
|
10
|
+
discord: "",
|
|
11
|
+
},
|
|
12
|
+
// Local-only dev agent identity (api/hono agents router).
|
|
13
|
+
agent: {
|
|
14
|
+
name: "LocalAgent",
|
|
15
|
+
email: "agent@local.host",
|
|
16
|
+
},
|
|
17
|
+
// Injectable long-form text blocks. A product sets its own, or leaves them empty.
|
|
18
|
+
apiReferenceDescription: "",
|
|
19
|
+
llmsFullPreamble: "",
|
|
20
|
+
} as const
|
|
21
|
+
|
|
22
|
+
export type Site = typeof site
|
|
23
|
+
`},E=()=>`import { redirect } from "next/navigation"
|
|
24
|
+
|
|
25
|
+
// Fresh fork: redirect to the waitlist until you build your real home page.
|
|
26
|
+
export default function Home() {
|
|
27
|
+
redirect("/waitlist")
|
|
28
|
+
}
|
|
29
|
+
`,D=()=>'# AGENTS.md\n\nGuidance for AI coding agents working in this repository.\n\n## Instructions\n\n- ALWAYS: Use `@/` for imports, and follow the `design` skill for UI and styling conventions.\n- ALWAYS: Keep documentation in sync with every change.\n- NEVER: Include "Co-authored-by" in commit messages.\n- 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.\n\n## Skills\n\nCustom skills live in `.agents/skills` (symlinked to `.claude/skills`). Start with the `codebase-map` skill to orient, then load the task skill that fits (`api-endpoint`, `db-migration`, `dev`, `design`, and more).\n',O=({name:e})=>`# ${e.charAt(0).toUpperCase()+e.slice(1)}
|
|
30
|
+
|
|
31
|
+
Built on top of [ZeroStarter](https://zerostarter.dev).
|
|
32
|
+
|
|
33
|
+
## Development
|
|
34
|
+
|
|
35
|
+
\`\`\`bash
|
|
36
|
+
bun run dev
|
|
37
|
+
\`\`\`
|
|
38
|
+
|
|
39
|
+
Web runs on http://localhost:3000 and the API on http://localhost:4000.
|
|
40
|
+
`,k=()=>`---
|
|
41
|
+
slug: /docs
|
|
42
|
+
title: Introduction
|
|
43
|
+
description: Documentation.
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
# Introduction
|
|
47
|
+
|
|
48
|
+
This is your documentation home. Start writing, this page is yours.
|
|
49
|
+
`,A=()=>`---
|
|
50
|
+
slug: /console/docs
|
|
51
|
+
title: Introduction
|
|
52
|
+
description: Internal documentation.
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
# Introduction
|
|
56
|
+
|
|
57
|
+
Your team's internal docs live here.
|
|
58
|
+
`,j=e=>`---
|
|
59
|
+
title: Blog
|
|
60
|
+
description: Latest articles and updates
|
|
61
|
+
createdAt: ${e}
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Recent Posts
|
|
65
|
+
|
|
66
|
+
<BlogPostList />
|
|
67
|
+
`,M=e=>`---
|
|
68
|
+
title: Hello World
|
|
69
|
+
description: The first post on your new blog.
|
|
70
|
+
createdAt: ${e}
|
|
71
|
+
publishedAt: ${e}
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Hello World
|
|
75
|
+
|
|
76
|
+
Your very first post. Replace it with something worth reading.
|
|
77
|
+
`,N=()=>`import type { DocsConfig } from "./src/lib/docs/types"
|
|
78
|
+
|
|
79
|
+
const docsConfig = {
|
|
80
|
+
docs: {
|
|
81
|
+
"Getting Started": [
|
|
82
|
+
{
|
|
83
|
+
"/docs": {
|
|
84
|
+
title: "Introduction",
|
|
85
|
+
description: "Documentation.",
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
],
|
|
89
|
+
},
|
|
90
|
+
console: {
|
|
91
|
+
"Getting Started": [
|
|
92
|
+
{
|
|
93
|
+
"/console/docs": {
|
|
94
|
+
title: "Introduction",
|
|
95
|
+
description: "Internal documentation.",
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
} satisfies DocsConfig
|
|
101
|
+
|
|
102
|
+
export default docsConfig
|
|
103
|
+
`,P=(e,...n)=>t(e,...n),F=e=>e.toLowerCase().replace(/[^a-z0-9]+/g,`-`).replace(/^-+|-+$/g,``)||`app`,I=e=>{let t=P(e,`.gitpickignore`);if(c(t)){for(let n of l(t).split(`
|
|
104
|
+
`)){let t=n.trim();if(!(!t||t.startsWith(`#`))){if(/[*?![\]]/.test(t))throw Error(`.gitpickignore entry "${t}" is not a literal path; the in-place converter only supports literal paths (a glob or negation would diverge from gitpick's fetch).`);d(P(e,t))}}d(t)}},L=/\r?\nexport const caveat = localFont\(\{[\s\S]*?\r?\n\}\)\r?\n/,R=/\r?\nexport const newsreader = localFont\(\{[\s\S]*?\r?\n\}\)\r?\n/,z=/[ \t]*\{[^}\r\n]*href:[ \t]*["']\/hire["'][^}\r\n]*\},?[ \t]*\r?\n/,B=(e,t)=>{let n=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;u(P(e,`web/next/content/docs/index.mdx`),k()),u(P(e,`web/next/content/blog/index.mdx`),j(a)),u(P(e,`web/next/content/blog/hello-world.mdx`),M(a)),u(P(e,`web/next/content/console/docs/index.mdx`),A()),u(P(e,`web/next/docs.config.ts`),N()),u(P(e,`web/next/public/.gitkeep`),``),u(P(e,`web/next/src/app/page.tsx`),E()),u(P(e,`AGENTS.md`),D()),u(P(e,`README.md`),O(t))},V=e=>{let t=P(e,`web/next/src/lib/fonts.ts`),n=P(e,`web/next/src/components/navbar/home.tsx`);if(_(t,L),_(t,R),_(n,z),c(t)&&l(t).includes(`fonts/marketing/`))throw Error(`fonts.ts still references fonts/marketing/ after fixDangling (regex drift). Update packages/cli/src/convert.ts.`);if(c(n)&&/href:\s*["']\/hire["']/.test(l(n)))throw Error(`navbar/home.tsx still has the /hire entry after fixDangling (regex drift). Update packages/cli/src/convert.ts.`)},H=(e,t)=>{u(P(e,`packages/config/src/site.ts`),T(t));let n=P(e,`package.json`),r=h(n);r.name=F(t.name),r.version=`0.0.0`;for(let e of v)delete r[e];g(n,r)},U=(e,t)=>{I(e),B(e,t),V(e),H(e,t)};export{p as a,l as c,u as d,g as f,b as i,h as l,V as n,c as o,w as r,m as s,U as t,d as u};
|
package/dist/index.d.mts
ADDED
package/dist/index.mjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{t as e}from"./convert-DMU8fiBc.mjs";export{e as convertRepo};
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "zerostarter",
|
|
3
|
+
"version": "0.0.0-0227a80",
|
|
4
|
+
"description": "Go from zero to a production-ready SaaS, rebranded and ready to ship.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"cli",
|
|
7
|
+
"saas",
|
|
8
|
+
"scaffold",
|
|
9
|
+
"starter",
|
|
10
|
+
"template",
|
|
11
|
+
"zerostarter"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://github.com/nrjdalal/zerostarter#readme",
|
|
14
|
+
"bugs": "https://github.com/nrjdalal/zerostarter/issues",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"author": {
|
|
17
|
+
"name": "Neeraj Dalal",
|
|
18
|
+
"email": "admin@nrjdalal.com",
|
|
19
|
+
"url": "https://nrjdalal.com"
|
|
20
|
+
},
|
|
21
|
+
"repository": "nrjdalal/zerostarter",
|
|
22
|
+
"funding": "https://github.com/sponsors/nrjdalal",
|
|
23
|
+
"bin": {
|
|
24
|
+
"zerostarter": "dist/bin/index.mjs"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"type": "module",
|
|
30
|
+
"types": "./dist/index.d.mts",
|
|
31
|
+
"exports": "./dist/index.mjs"
|
|
32
|
+
}
|