fix: Fixed issue with templates in hono rpc setup 8decf908
Steve · 2025-05-05 23:38 3 file(s) · +246 −67
index.js +22 −64
10 10
import { execa } from 'execa';
11 11
import degit from 'degit';
12 12
import figlet from 'figlet';
13 +
import { defaultTemplate, shadcnTemplate, tailwindTemplate } from './utils/templates.js';
13 14
14 15
const __filename = fileURLToPath(import.meta.url);
15 16
const __dirname = path.dirname(__filename);
288 289
      });
289 290
290 291
      if (useRpc) {
291 -
        await patchFilesForRPC(projectPath);
292 +
        await patchFilesForRPC(projectPath, templateChoice);
292 293
      }
293 294
    }
294 295
304 305
  }
305 306
}
306 307
307 -
async function patchFilesForRPC(projectPath) {
308 +
async function patchFilesForRPC(projectPath, templateChoice) {
308 309
  const spinner = ora('Setting up RPC client...').start();
309 310
310 311
  try {
312 313
    const clientPkgPath = path.join(projectPath, 'client', 'package.json');
313 314
    const clientPkg = await fs.readJson(clientPkgPath);
314 315
315 -
    // Make sure hono client is in dependencies
316 316
    if (!clientPkg.dependencies.hono) {
317 -
      clientPkg.dependencies.hono = "^4.7.7";
317 +
      await execa('bun', ['install','hono'], { cwd: projectPath });
318 318
    }
319 319
320 320
    await fs.writeJson(clientPkgPath, clientPkg, { spaces: 2 });
321 321
322 -
    // 2. Server modification - targeted approach based on known structure
322 +
    // 2. Server modification for RPC export type
323 323
    const serverIndexPath = path.join(projectPath, 'server', 'src', 'index.ts');
324 324
    let serverContent = await fs.readFile(serverIndexPath, 'utf8');
325 325
326 -
    // If the server doesn't already have the RPC structure, update it
327 326
    if (!serverContent.includes('export type AppType')) {
328 -
      // Create the target server content based on the template
327 +
      // Update server content to export the type
329 328
      const updatedServerContent = `import { Hono } from 'hono'
330 329
import { cors } from 'hono/cors'
331 330
import type { ApiResponse } from 'shared/dist'
354 353
      await fs.writeFile(serverIndexPath, updatedServerContent, 'utf8');
355 354
    }
356 355
357 -
    // 3. Update App.tsx with RPC implementation
356 +
    // 3. Update App.tsx based on template selection using switch statement
358 357
    const appTsxPath = path.join(projectPath, 'client', 'src', 'App.tsx');
359 -
    let appTsxContent = await fs.readFile(appTsxPath, 'utf8');
360 358
361 -
    // Only make changes if RPC isn't already set up
362 -
    if (!appTsxContent.includes('import { hc } from \'hono/client\'')) {
363 -
      // Find the key parts of the file we need to preserve
364 -
      const importReactMatch = appTsxContent.match(/import\s+{\s*useState\s*}.*?from\s+['"]react['"]/);
365 -
      const importBeaverMatch = appTsxContent.match(/import\s+beaver\s+from\s+['"]\.\/assets\/beaver\.svg['"]/);
366 -
      const importSharedMatch = appTsxContent.match(/import.*?from\s+['"]shared['"]/);
367 -
      const importCssMatch = appTsxContent.match(/import\s+['"]\.\/App\.css['"]/);
359 +
    // Determine template content based on the template type
360 +
    let updatedAppContent;
368 361
369 -
      // Make sure we found the required parts
370 -
      if (importReactMatch && importBeaverMatch && importCssMatch) {
371 -
        // Get the current return JSX part
372 -
        const returnJsxMatch = appTsxContent.match(/return\s*\(\s*<>([\s\S]*?)<\/>/);
373 -
374 -
        if (returnJsxMatch) {
375 -
          // Create the updated App.tsx content
376 -
          const updatedAppContent = `import { useState } from 'react'
377 -
import beaver from './assets/beaver.svg'
378 -
import type { AppType } from '../../server/src'
379 -
import { hc } from 'hono/client'
380 -
${importSharedMatch ? importSharedMatch[0] : 'import { ApiResponse } from \'shared\''}
381 -
import './App.css'
382 -
383 -
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000"
384 -
385 -
const client = hc<AppType>(SERVER_URL);
386 -
387 -
type ResponseType = Awaited<ReturnType<typeof client.hello.$get>>;
388 -
389 -
function App() {
390 -
  const [data, setData] = useState<Awaited<ReturnType<ResponseType["json"]>> | undefined>()
391 -
392 -
  async function sendRequest() {
393 -
    try {
394 -
      const res = await client.hello.$get()
395 -
396 -
      if(!res.ok){
397 -
        console.log("Error fetching data")
398 -
        return
399 -
      }
400 -
401 -
      const data = await res.json()
402 -
      setData(data)
403 -
    } catch (error) {
404 -
      console.log(error)
362 +
    // Select template based on choice
363 +
    switch (templateChoice) {
364 +
      case 'shadcn':
365 +
        updatedAppContent = shadcnTemplate;
366 +
        break;
367 +
      case 'tailwind':
368 +
        updatedAppContent = tailwindTemplate;
369 +
        break;
370 +
      case 'default':
371 +
      default:
372 +
        updatedAppContent = defaultTemplate;
373 +
        break;
405 374
    }
406 -
  }
407 375
408 -
  ${returnJsxMatch[0]}
409 -
410 -
  )
411 -
}
412 -
export default App`;
413 -
414 -
          await fs.writeFile(appTsxPath, updatedAppContent, 'utf8');
415 -
        }
416 -
      }
417 -
    }
418 -
376 +
    await fs.writeFile(appTsxPath, updatedAppContent, 'utf8');
419 377
    spinner.succeed('RPC client setup completed');
420 378
    return true;
421 379
  } catch (err) {
package.json +4 −3
1 1
{
2 2
  "name": "create-bhvr",
3 -
  "version": "0.3.0",
3 +
  "version": "0.3.1",
4 4
  "description": "Create a new bhvr project",
5 5
  "main": "index.js",
6 6
  "type": "module",
7 7
  "bin": {
8 -
    "create-vrhb": "index.js"
8 +
    "create-bhvr": "index.js"
9 9
  },
10 10
  "files": [
11 -
    "index.js"
11 +
    "index.js",
12 +
    "utils"
12 13
  ],
13 14
  "scripts": {
14 15
    "test": "echo \"Error: no test specified\" && exit 1"
utils/templates.js (added) +220 −0
1 +
export const honoRpcTemplate = `import { Hono } from 'hono'
2 +
import { cors } from 'hono/cors'
3 +
import type { ApiResponse } from 'shared/dist'
4 +
5 +
const app = new Hono()
6 +
7 +
app.use(cors())
8 +
9 +
const routes = app.get('/', (c) => {
10 +
  return c.text('Hello Hono!')
11 +
})
12 +
13 +
.get('/hello', async (c) => {
14 +
15 +
  const data: ApiResponse = {
16 +
    message: "Hello BHVR!",
17 +
    success: true
18 +
  }
19 +
20 +
  return c.json(data, { status: 200 })
21 +
})
22 +
23 +
export type AppType = typeof routes
24 +
export default app`;
25 +
26 +
export const tailwindTemplate = `import { useState } from 'react'
27 +
import beaver from './assets/beaver.svg'
28 +
import type { AppType } from 'server'
29 +
import { hc } from 'hono/client'
30 +
31 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000"
32 +
33 +
type ResponseType = Awaited<ReturnType<typeof client.hello.$get>>;
34 +
35 +
const client = hc<AppType>(SERVER_URL);
36 +
37 +
function App() {
38 +
  const [data, setData] = useState<Awaited<ReturnType<ResponseType["json"]>> | undefined>()
39 +
40 +
  async function sendRequest() {
41 +
    try {
42 +
      const res = await client.hello.$get()
43 +
      if (!res.ok) {
44 +
        console.log("Error fetching data")
45 +
        return
46 +
      }
47 +
      const data = await res.json()
48 +
      setData(data)
49 +
    } catch (error) {
50 +
      console.log(error)
51 +
    }
52 +
  }
53 +
54 +
  return (
55 +
    <div className="max-w-xl mx-auto flex flex-col gap-6 items-center justify-center min-h-screen">
56 +
      <a href="https://github.com/stevedylandev/bhvr" target="_blank">
57 +
        <img
58 +
          src={beaver}
59 +
          className="w-16 h-16 cursor-pointer"
60 +
          alt="beaver logo"
61 +
        />
62 +
      </a>
63 +
      <h1 className="text-5xl font-black">bhvr</h1>
64 +
      <h2 className="text-2xl font-bold">Bun + Hono + Vite + React</h2>
65 +
      <p>A typesafe fullstack monorepo</p>
66 +
      <div className='flex items-center gap-4'>
67 +
        <button
68 +
          onClick={sendRequest}
69 +
          className="bg-black text-white px-2.5 py-1.5 rounded-md"
70 +
        >
71 +
          Call API
72 +
        </button>
73 +
        <a target='_blank' href="https://bhvr.dev" className='border-1 border-black text-black px-2.5 py-1.5 rounded-md'>
74 +
          Docs
75 +
        </a>
76 +
      </div>
77 +
        {data && (
78 +
          <pre className="bg-gray-100 p-4 rounded-md">
79 +
            <code>
80 +
            Message: {data.message} <br />
81 +
            Success: {data.success.toString()}
82 +
            </code>
83 +
          </pre>
84 +
        )}
85 +
    </div>
86 +
  )
87 +
}
88 +
89 +
export default App`
90 +
91 +
export const shadcnTemplate = `import { useState } from 'react'
92 +
import beaver from './assets/beaver.svg'
93 +
import { Button } from './components/ui/button'
94 +
import type { AppType } from 'server'
95 +
import { hc } from 'hono/client'
96 +
97 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000"
98 +
99 +
const client = hc<AppType>(SERVER_URL);
100 +
101 +
type ResponseType = Awaited<ReturnType<typeof client.hello.$get>>;
102 +
103 +
function App() {
104 +
  const [data, setData] = useState<Awaited<ReturnType<ResponseType["json"]>> | undefined>()
105 +
106 +
  async function sendRequest() {
107 +
    try {
108 +
      const res = await client.hello.$get()
109 +
      if (!res.ok) {
110 +
        console.log("Error fetching data")
111 +
        return
112 +
      }
113 +
      const data = await res.json()
114 +
      setData(data)
115 +
    } catch (error) {
116 +
      console.log(error)
117 +
    }
118 +
  }
119 +
120 +
  return (
121 +
    <div className="max-w-xl mx-auto flex flex-col gap-6 items-center justify-center min-h-screen">
122 +
      <a href="https://github.com/stevedylandev/bhvr" target="_blank">
123 +
        <img
124 +
          src={beaver}
125 +
          className="w-16 h-16 cursor-pointer"
126 +
          alt="beaver logo"
127 +
        />
128 +
      </a>
129 +
      <h1 className="text-5xl font-black">bhvr</h1>
130 +
      <h2 className="text-2xl font-bold">Bun + Hono + Vite + React</h2>
131 +
      <p>A typesafe fullstack monorepo</p>
132 +
      <div className='flex items-center gap-4'>
133 +
        <Button
134 +
          onClick={sendRequest}
135 +
        >
136 +
          Call API
137 +
        </Button>
138 +
        <Button
139 +
          variant='secondary'
140 +
          asChild
141 +
        >
142 +
          <a target='_blank' href="https://bhvr.dev">
143 +
          Docs
144 +
          </a>
145 +
        </Button>
146 +
      </div>
147 +
        {data && (
148 +
          <pre className="bg-gray-100 p-4 rounded-md">
149 +
            <code>
150 +
            Message: {data.message} <br />
151 +
            Success: {data.success.toString()}
152 +
            </code>
153 +
          </pre>
154 +
        )}
155 +
    </div>
156 +
  )
157 +
}
158 +
159 +
export default App`;
160 +
161 +
export const defaultTemplate = `import { useState } from 'react'
162 +
import beaver from './assets/beaver.svg'
163 +
import type { AppType } from 'server'
164 +
import { hc } from 'hono/client'
165 +
import './App.css'
166 +
167 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000"
168 +
169 +
const client = hc<AppType>(SERVER_URL);
170 +
171 +
type ResponseType = Awaited<ReturnType<typeof client.hello.$get>>;
172 +
173 +
function App() {
174 +
  const [data, setData] = useState<Awaited<ReturnType<ResponseType["json"]>> | undefined>()
175 +
176 +
  async function sendRequest() {
177 +
    try {
178 +
      const res = await client.hello.$get()
179 +
      if (!res.ok) {
180 +
        console.log("Error fetching data")
181 +
        return
182 +
      }
183 +
      const data = await res.json()
184 +
      setData(data)
185 +
    } catch (error) {
186 +
      console.log(error)
187 +
    }
188 +
  }
189 +
190 +
  return (
191 +
    <>
192 +
      <div>
193 +
        <a href="https://github.com/stevedylandev/bhvr" target="_blank">
194 +
          <img src={beaver} className="logo" alt="beaver logo" />
195 +
        </a>
196 +
      </div>
197 +
      <h1>bhvr</h1>
198 +
      <h2>Bun + Hono + Vite + React</h2>
199 +
      <p>A typesafe fullstack monorepo</p>
200 +
      <div className="card">
201 +
        <div className='button-container'>
202 +
          <button onClick={sendRequest}>
203 +
            Call API
204 +
          </button>
205 +
          <a className='docs-link' target='_blank' href="https://bhvr.dev">Docs</a>
206 +
        </div>
207 +
        {data && (
208 +
          <pre className='response'>
209 +
            <code>
210 +
            Message: {data.message} <br />
211 +
            Success: {data.success.toString()}
212 +
            </code>
213 +
          </pre>
214 +
        )}
215 +
      </div>
216 +
    </>
217 +
  )
218 +
}
219 +
220 +
export default App`;