zerostarter 0.0.23 → 0.0.24-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# zerostarter
|
|
2
|
+
|
|
3
|
+
Go from zero to a production-ready SaaS, rebranded and ready to ship.
|
|
4
|
+
|
|
5
|
+
`zerostarter` scaffolds [ZeroStarter](https://zerostarter.dev), a full-stack TypeScript starter (Next.js web, Hono API, Drizzle + Postgres, better-auth), into a fresh, rebranded project in one command. It fetches the latest starter, renames it to your directory, installs dependencies, and (when Docker is running) provisions a local Postgres and runs migrations.
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<img src="https://raw.githubusercontent.com/nrjdalal/zerostarter/canary/.github/assets/cli.gif" alt="bunx zerostarter init scaffolds a rebranded product from ZeroStarter" width="900" />
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# In a new, empty directory (its name becomes your project name):
|
|
15
|
+
bunx zerostarter init
|
|
16
|
+
|
|
17
|
+
# Start the dev servers (web on :3000, api on :4000)
|
|
18
|
+
bun run dev
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
That is the whole setup. When Docker is running, `init` provisions a local Postgres and migrates for you; otherwise set `POSTGRES_URL` in `.env` (a hosted database like Neon works) and run `bun run db:migrate`.
|
|
22
|
+
|
|
23
|
+
## Commands
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
bunx zerostarter <command> [dir] [options]
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
| Command | What it does |
|
|
30
|
+
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
31
|
+
| `init [dir]` | Scaffold ZeroStarter into `dir` (default `.`) as a fresh product. Fetches, rebrands to the dir name, installs, and optionally provisions Postgres. |
|
|
32
|
+
| `reinit [dir]` | Re-scaffold an existing git repo (default `.`) as a fresh ZeroStarter, keeping `.git` and your `.env*` files so history, remote, and secrets survive. |
|
|
33
|
+
| `sync [dir]` | Re-baseline an existing fork (default `.`) on the latest ZeroStarter, preserving your content, branding, `package.json` identity, and favicon. |
|
|
34
|
+
|
|
35
|
+
### `init`
|
|
36
|
+
|
|
37
|
+
Scaffold a new product. Run it in an empty directory (its name becomes the project name), or pass a name: `bunx zerostarter init my-app`.
|
|
38
|
+
|
|
39
|
+
| Option | Description |
|
|
40
|
+
| -------------- | --------------------------------------------------------------------- |
|
|
41
|
+
| `-y`, `--yes` | Skip prompts, taking defaults (provisions Postgres when Docker is up) |
|
|
42
|
+
| `--db` | Provision a local Postgres (via pglaunch) and migrate; needs Docker |
|
|
43
|
+
| `--dry-run` | Print the plan without writing anything |
|
|
44
|
+
| `-h`, `--help` | Display help |
|
|
45
|
+
|
|
46
|
+
### `reinit`
|
|
47
|
+
|
|
48
|
+
Re-scaffold an existing repo in place. Every file is deleted (except `.git` and `.env*`), the latest ZeroStarter is fetched, and it is rebranded to the directory name. The commit lands on the current branch; push when ready. Requires a clean tree.
|
|
49
|
+
|
|
50
|
+
| Option | Description |
|
|
51
|
+
| -------------- | ---------------------------- |
|
|
52
|
+
| `-y`, `--yes` | Skip the confirmation prompt |
|
|
53
|
+
| `-h`, `--help` | Display help |
|
|
54
|
+
|
|
55
|
+
### `sync`
|
|
56
|
+
|
|
57
|
+
Re-baseline a fork on the latest ZeroStarter. A gitpick overlay updates the starter files while your content, `public/marketing`, branding, `package.json` identity, and favicon are preserved. Requires a clean tree; lands as a reviewable diff you commit yourself.
|
|
58
|
+
|
|
59
|
+
## Requirements
|
|
60
|
+
|
|
61
|
+
- **[Bun](https://bun.sh)** runs the scaffolded project. If it is missing, the CLI offers to install it for you.
|
|
62
|
+
- **Docker** (optional) lets `init` provision a local Postgres and migrate automatically. Without it, point `POSTGRES_URL` at any Postgres and run `bun run db:migrate`.
|
|
63
|
+
|
|
64
|
+
## Links
|
|
65
|
+
|
|
66
|
+
- Docs: [zerostarter.dev/docs](https://zerostarter.dev/docs)
|
|
67
|
+
- Repository: [github.com/nrjdalal/zerostarter](https://github.com/nrjdalal/zerostarter)
|
|
68
|
+
|
|
69
|
+
## License
|
|
70
|
+
|
|
71
|
+
MIT
|
package/dist/bin/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
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-
|
|
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-DnrCkuf-.mjs";import{basename as f,delimiter as p,dirname as m,join as h,parse as g,resolve as _}from"node:path";import{existsSync as v,readFileSync as ee,readdirSync as y}from"node:fs";import{spawn as b}from"node:child_process";import{access as te}from"node:fs/promises";import{createInterface as ne}from"node:readline/promises";import{randomBytes as re}from"node:crypto";import{parseArgs as ie}from"node:util";import{homedir as ae}from"node:os";const x=(e,t,n=process.stdout)=>r=>n.isTTY?`\x1b[${e}m${r}\x1b[${t}m`:r,S=x(`36`,`39`),C=x(`32`,`39`),w=x(`33`,`39`),T=x(`90`,`39`),E=x(`2`,`22`),D=x(`31`,`39`,process.stderr),oe=x(`2`,`22`,process.stderr),O=x(`38;5;208`,`39`),k=[`◇`,`◆`],A=(e,t)=>{let n=(e%k.length+k.length)%k.length;return`${S(k[n])} ${t}`},j={barStart:`┌`,bar:`│`,barEnd:`└`,active:`◆`,submit:`◇`,radioOn:`●`,radioOff:`○`,warn:`▲`,error:`■`,barH:`─`,cornerTR:`╮`,connectL:`├`,cornerBL:`╰`,cornerBR:`╯`};var se=class extends Error{constructor(...e){super(...e),this.name=`SubprocessError`,this.stdout=``,this.stderr=``}};const ce=[`.exe`,`.com`],M=e=>e.replaceAll(/([()\][%!^"`<>&|;, *?])/g,`^$1`),le=e=>M(M(`"${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(`${_(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)?[M(e),t.map(le),{...n,shell:!0}]:[e,t,n]},N=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=b(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})})})},P=async(e,t,n)=>{let{stdout:r}=await N(e,t,{cwd:n,stdio:[`ignore`,`pipe`,`pipe`]});return r},fe=async(e,t,n)=>{await N(e,t,{cwd:n,stdio:`inherit`})},F=async(e,t)=>{try{return await N(e,t,{stdio:`ignore`}),!0}catch{return!1}},pe=RegExp(`\x1B\\[[0-9;?]*[a-zA-Z]`,`g`),me=e=>e.replace(pe,``),he=new WeakSet,ge=e=>{if(process.stderr.write(`\n ${D(e instanceof Error?e.message:String(e))}\n`),!(e instanceof se)||he.has(e))return;let t=(e.stderr||e.stdout||``).trim();if(!t)return;let n=me(t).split(`
|
|
3
3
|
`).map(e=>e.trimEnd()).filter(Boolean).slice(-12);for(let e of n)process.stderr.write(` ${oe(e)}\n`)},_e=async(e,t,n)=>{let r=process.stdout,i=n.label??``;if(!r.isTTY){i&&r.write(` ${S(j.active)} ${i}\n`),await fe(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[0J`),c=0)},m=()=>{p();let e=0;r.write(` ${A(s,i)}\n`),e++;for(let t of o.slice(-a))r.write(` ${T(j.bar)} ${E(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(me(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 N(e,t,{cwd:n.cwd,stdio:[`ignore`,`pipe`,`pipe`]},h)}catch(e){throw v(),p(),f(!0),r.write(u),e&&typeof e==`object`&&he.add(e),e}v(),p(),f(!0),r.write(` ${S(j.active)} ${n.done??i}\n`);for(let e of o.slice(-a))r.write(` ${T(j.bar)} ${E(e.slice(0,Math.max(0,d()-5)))}\n`)};var ve={name:`zerostarter`,version:`0.0.
|
|
4
|
+
`);l=t.pop()??``;for(let e of t)o.push(me(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 N(e,t,{cwd:n.cwd,stdio:[`ignore`,`pipe`,`pipe`]},h)}catch(e){throw v(),p(),f(!0),r.write(u),e&&typeof e==`object`&&he.add(e),e}v(),p(),f(!0),r.write(` ${S(j.active)} ${n.done??i}\n`);for(let e of o.slice(-a))r.write(` ${T(j.bar)} ${E(e.slice(0,Math.max(0,d()-5)))}\n`)};var ve={name:`zerostarter`,version:`0.0.24-canary.0`,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 I=()=>!!(process.stdin.isTTY&&process.stdout.isTTY),ye=(e,t=e)=>process.stdout.isTTY?`\x1b]8;;${e}\x07${t}\x1b]8;;\x07`:e,L=e=>ye(e,S(e)),R=e=>{process.stdout.write(`\n ${T(j.barStart)} ${e}\n ${T(j.bar)}\n`)},z=e=>{process.stdout.write(` ${T(j.bar)}\n ${T(j.barEnd)} ${e}\n`)},B=(e=`Cancelled`)=>{process.stdout.write(` ${T(j.barEnd)} ${D(e)}\n`)},V=e=>e.replace(RegExp(`\x1B\\[[0-9;?]*[a-zA-Z]`,`g`),``).replace(RegExp(`\x1B\\]8;;.*?\\x07`,`g`),``).length,H=(e,t=``,n=!1)=>{let r=`\n${e}\n`.split(`
|
|
5
5
|
`),i=V(t),a=r.reduce((e,t)=>Math.max(e,V(t)),i),o=Math.max(a,54)+2,s=r.map(e=>` ${T(j.bar)} ${e}${` `.repeat(o-V(e))}${T(j.bar)}`).join(`
|
|
6
6
|
`),c=n?j.cornerBL:j.connectL;process.stdout.write(` ${S(j.active)} ${t} ${T(j.barH.repeat(Math.max(o-i-1,1))+j.cornerTR)}\n${s}\n ${T(c+j.barH.repeat(o+2)+j.cornerBR)}\n`)},U=(e,t=[])=>{process.stdout.write(` ${S(j.active)} ${e}\n`);for(let e of t)process.stdout.write(` ${T(j.bar)} ${e}\n`)},be=e=>{process.stdout.write(` ${C(j.active)} ${C(e)}\n`)},W=(e,t=[])=>{process.stdout.write(` ${w(j.warn)} ${w(e)}\n`);for(let e of t)process.stdout.write(` ${T(j.bar)} ${w(e)}\n`)},xe=async(e,t,n)=>{let r=process.stdout,i=null;if(r.isTTY){let t=0;r.write(` ${A(0,e)}`),i=setInterval(()=>{t+=1,r.write(`\r\x1b[K ${A(t,e)}`)},400)}try{let e=await n();return i&&clearInterval(i),r.write(r.isTTY?`\r\x1b[K ${S(j.active)} ${t}\n`:` ${S(j.active)} ${t}\n`),e}catch(e){throw i&&clearInterval(i),r.isTTY&&r.write(`\r\x1B[K`),e}},Se=e=>{let t=e?`${C(j.radioOn)} Yes`:E(`${j.radioOff} Yes`),n=e?E(`${j.radioOff} No`):`${C(j.radioOn)} No`;return` ${T(j.bar)} ${t} ${E(`/`)} ${n}`},G=async(e,t=!0)=>{let n=process.stdout;return I()?new Promise(r=>{let i=t,a=process.stdin,o=Math.max(1,Math.ceil((5+V(e))/(n.columns||80)));n.write(` ${S(j.submit)} ${e}\n`),n.write(Se(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(` ${S(j.submit)} ${E(e)}\n`),B(),process.exit(130)):t===`\r`||t===`
|
|
7
7
|
`?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${Se(i)}`))}a.on(`data`,u)}):(n.write(` ${S(j.active)} ${e} ${E(t?`Yes`:`No`)}\n`),t)},Ce=async(e,t=``)=>{process.stdout.write(` ${S(j.submit)} ${e}${t?` ${E(`(${t})`)}`:``}\n`);let n=ne({input:process.stdin,output:process.stdout});try{return(await n.question(` ${T(j.bar)} `)).trim()||t}finally{n.close()}},we=`pglaunch@5.5.7`,Te=async()=>F(`docker`,[`info`]),Ee=e=>{let r=h(e,`.env`);if(!s(r)){let i=h(e,`.env.example`);n(r,s(i)?t(i):``)}return r},De=(e,n)=>{let r=`${n}=`,i=t(e).split(`
|
|
@@ -101,4 +101,4 @@ const docsConfig = {
|
|
|
101
101
|
|
|
102
102
|
export default docsConfig
|
|
103
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
|
|
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=/[ \t]*\{[^}\r\n]*href:[ \t]*["']\/hire["'][^}\r\n]*\},?[ \t]*\r?\n/,R=(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))},z=e=>{let t=P(e,`web/next/src/lib/fonts.ts`),n=P(e,`web/next/src/lib/marketing/fonts.ts`),r=P(e,`web/next/src/components/navbar/home.tsx`);if(_(r,L),c(n))throw Error(`web/next/src/lib/marketing/fonts.ts survived the fork strip (add web/next/src/lib/marketing/ to .gitpickignore).`);if(c(t)&&l(t).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(c(r)&&/href:\s*["']\/hire["']/.test(l(r)))throw Error(`navbar/home.tsx still has the /hire entry after fixDangling (regex drift). Update packages/cli/src/convert.ts.`)},B=(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)},V=(e,t)=>{I(e),R(e,t),z(e),B(e,t)};export{p as a,l as c,u as d,g as f,b as i,h as l,z as n,c as o,w as r,m as s,V as t,d as u};
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{t as e}from"./convert-
|
|
1
|
+
import{t as e}from"./convert-DnrCkuf-.mjs";export{e as convertRepo};
|