kibinrpc 0.1.0 → 0.2.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.
@@ -0,0 +1 @@
1
+ export { };
package/dist/index.mjs ADDED
@@ -0,0 +1,194 @@
1
+ #!/usr/bin/env node
2
+ import { cancel, intro, isCancel, multiselect, outro, spinner, text } from "@clack/prompts";
3
+ import { mkdirSync, writeFileSync } from "node:fs";
4
+ import { dirname, join } from "node:path";
5
+ //#region src/scaffold.ts
6
+ function write(root, path, content) {
7
+ const full = join(root, path);
8
+ mkdirSync(dirname(full), { recursive: true });
9
+ writeFileSync(full, content, "utf-8");
10
+ }
11
+ function scaffold({ name, templates }) {
12
+ const hasBackend = templates.includes("backend");
13
+ const hasFrontend = templates.includes("frontend");
14
+ const root = join(process.cwd(), name);
15
+ const deps = {};
16
+ const devDeps = { typescript: "latest" };
17
+ if (hasBackend) deps["@kibinrpc/server"] = "latest";
18
+ if (hasFrontend) {
19
+ deps["@kibinrpc/client"] = "latest";
20
+ deps.react = "^19";
21
+ deps["react-dom"] = "^19";
22
+ devDeps.vite = "latest";
23
+ devDeps["@vitejs/plugin-react"] = "latest";
24
+ }
25
+ if (hasBackend && hasFrontend) deps.hono = "latest";
26
+ const scripts = {};
27
+ if (hasBackend) scripts["dev:server"] = "tsx --watch src/server/index.ts";
28
+ if (hasFrontend) scripts["dev:client"] = "vite";
29
+ scripts.dev = hasBackend && hasFrontend ? "pnpm dev:server & pnpm dev:client" : hasBackend ? "pnpm dev:server" : "pnpm dev:client";
30
+ write(root, "package.json", JSON.stringify({
31
+ name,
32
+ version: "0.1.0",
33
+ private: true,
34
+ type: "module",
35
+ scripts,
36
+ dependencies: deps,
37
+ devDependencies: devDeps
38
+ }, null, 2));
39
+ write(root, "tsconfig.json", JSON.stringify({
40
+ compilerOptions: {
41
+ target: "ESNext",
42
+ module: "ESNext",
43
+ moduleResolution: "bundler",
44
+ strict: true,
45
+ skipLibCheck: true,
46
+ ...hasFrontend ? { jsx: "react-jsx" } : {}
47
+ },
48
+ include: ["src"]
49
+ }, null, 2));
50
+ if (hasBackend) {
51
+ write(root, "src/server/router.ts", `import { createRouter, defineActions, KibinError } from '@kibinrpc/server'
52
+
53
+ const userActions = defineActions({
54
+ async getUser(id: string) {
55
+ // TODO: fetch from database
56
+ throw new KibinError('NOT_FOUND', 'User not found')
57
+ },
58
+ })
59
+
60
+ export const router = createRouter({ user: userActions })
61
+ export type AppRouter = typeof router
62
+ `);
63
+ const port = hasFrontend ? 3001 : 3e3;
64
+ write(root, "src/server/index.ts", `import { router } from './router.js'
65
+ ${hasFrontend ? "import { Hono } from 'hono'\nimport { serve } from '@hono/node-server'" : ""}
66
+
67
+ ${hasFrontend ? `const app = new Hono()
68
+
69
+ app.post('/api/rpc', (c) => router.handler(c.req.raw))
70
+
71
+ serve({ fetch: app.fetch, port: ${port} }, () => {
72
+ console.log('Server running on http://localhost:${port}')
73
+ })` : `import { createServer } from 'node:http'
74
+
75
+ const server = createServer(async (req, res) => {
76
+ const url = new URL(req.url ?? '/', \`http://\${req.headers.host}\`)
77
+ if (url.pathname === '/api/rpc' && req.method === 'POST') {
78
+ const chunks: Buffer[] = []
79
+ for await (const chunk of req) chunks.push(chunk)
80
+ const body = Buffer.concat(chunks).toString()
81
+ const request = new Request(\`http://localhost:${port}\${url.pathname}\`, {
82
+ method: 'POST',
83
+ headers: { 'Content-Type': 'application/json' },
84
+ body,
85
+ })
86
+ const response = await router.handler(request)
87
+ res.writeHead(response.status, { 'Content-Type': 'application/json' })
88
+ res.end(await response.text())
89
+ } else {
90
+ res.writeHead(404)
91
+ res.end()
92
+ }
93
+ })
94
+
95
+ server.listen(${port}, () => console.log('Server running on http://localhost:${port}'))`}
96
+ `);
97
+ }
98
+ if (hasFrontend) {
99
+ write(root, "src/client/main.tsx", `import { createRoot } from 'react-dom/client'
100
+ import { createKibinClient } from '@kibinrpc/client'
101
+ ${hasBackend ? "import type { AppRouter } from '../server/router.js'" : ""}
102
+
103
+ const client = createKibinClient${hasBackend ? "<AppRouter>" : ""}({ baseUrl: ${hasBackend ? "'http://localhost:3001/api/rpc'" : "'/api/rpc'"} })
104
+
105
+ function App() {
106
+ return <h1>Hello from kibinrpc!</h1>
107
+ }
108
+
109
+ createRoot(document.getElementById('root')!).render(<App />)
110
+ `);
111
+ write(root, "index.html", `<!DOCTYPE html>
112
+ <html lang="en">
113
+ <head>
114
+ <meta charset="UTF-8" />
115
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
116
+ <title>${name}</title>
117
+ </head>
118
+ <body>
119
+ <div id="root"></div>
120
+ <script type="module" src="/src/client/main.tsx"><\/script>
121
+ </body>
122
+ </html>
123
+ `);
124
+ write(root, "vite.config.ts", `import { defineConfig } from 'vite'
125
+ import react from '@vitejs/plugin-react'
126
+
127
+ export default defineConfig({
128
+ plugins: [react()],${hasBackend ? `\n server: { proxy: { '/api': 'http://localhost:3001' } },` : ""}
129
+ })
130
+ `);
131
+ }
132
+ write(root, "README.md", `# ${name}
133
+
134
+ A kibinrpc project with ${[hasBackend && "backend", hasFrontend && "frontend"].filter(Boolean).join(" + ")}.
135
+
136
+ ## Getting started
137
+
138
+ \`\`\`sh
139
+ pnpm install
140
+ pnpm dev
141
+ \`\`\`
142
+ `);
143
+ }
144
+ //#endregion
145
+ //#region src/index.ts
146
+ async function main() {
147
+ intro(" create kibinrpc ");
148
+ const name = await text({
149
+ message: "Project name",
150
+ placeholder: "my-app",
151
+ validate: (v) => {
152
+ if (!v.trim()) return "Please enter a project name";
153
+ if (!/^[a-z0-9-]+$/.test(v.trim())) return "Only lowercase letters, numbers and hyphens allowed";
154
+ }
155
+ });
156
+ if (isCancel(name)) {
157
+ cancel("Cancelled");
158
+ process.exit(0);
159
+ }
160
+ const templates = await multiselect({
161
+ message: "What would you like to include?",
162
+ options: [{
163
+ value: "backend",
164
+ label: "Backend",
165
+ hint: "server router + actions"
166
+ }, {
167
+ value: "frontend",
168
+ label: "Frontend",
169
+ hint: "React + Vite client"
170
+ }],
171
+ initialValues: ["backend", "frontend"],
172
+ required: true
173
+ });
174
+ if (isCancel(templates)) {
175
+ cancel("Cancelled");
176
+ process.exit(0);
177
+ }
178
+ const s = spinner();
179
+ s.start(`Creating ${name}`);
180
+ scaffold({
181
+ name: name.trim(),
182
+ templates
183
+ });
184
+ s.stop(`Created ${name}`);
185
+ outro(`Next steps:\n cd ${name}\n pnpm install\n pnpm dev`);
186
+ }
187
+ main().catch((err) => {
188
+ console.error(err);
189
+ process.exit(1);
190
+ });
191
+ //#endregion
192
+ export {};
193
+
194
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/scaffold.ts","../src/index.ts"],"sourcesContent":["import { mkdirSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\nexport type TemplateId = 'backend' | 'frontend';\n\nexport interface ScaffoldOptions {\n\tname: string;\n\ttemplates: TemplateId[];\n}\n\nfunction write(root: string, path: string, content: string): void {\n\tconst full = join(root, path);\n\tmkdirSync(dirname(full), { recursive: true });\n\twriteFileSync(full, content, 'utf-8');\n}\n\nexport function scaffold({ name, templates }: ScaffoldOptions): void {\n\tconst hasBackend = templates.includes('backend');\n\tconst hasFrontend = templates.includes('frontend');\n\tconst root = join(process.cwd(), name);\n\n\t// package.json\n\tconst deps: Record<string, string> = {};\n\tconst devDeps: Record<string, string> = {\n\t\ttypescript: 'latest',\n\t};\n\n\tif (hasBackend) deps['@kibinrpc/server'] = 'latest';\n\tif (hasFrontend) {\n\t\tdeps['@kibinrpc/client'] = 'latest';\n\t\tdeps.react = '^19';\n\t\tdeps['react-dom'] = '^19';\n\t\tdevDeps.vite = 'latest';\n\t\tdevDeps['@vitejs/plugin-react'] = 'latest';\n\t}\n\tif (hasBackend && hasFrontend) {\n\t\tdeps.hono = 'latest';\n\t}\n\n\tconst scripts: Record<string, string> = {};\n\tif (hasBackend) scripts['dev:server'] = 'tsx --watch src/server/index.ts';\n\tif (hasFrontend) scripts['dev:client'] = 'vite';\n\tscripts.dev =\n\t\thasBackend && hasFrontend\n\t\t\t? 'pnpm dev:server & pnpm dev:client'\n\t\t\t: hasBackend\n\t\t\t\t? 'pnpm dev:server'\n\t\t\t\t: 'pnpm dev:client';\n\n\twrite(\n\t\troot,\n\t\t'package.json',\n\t\tJSON.stringify(\n\t\t\t{\n\t\t\t\tname,\n\t\t\t\tversion: '0.1.0',\n\t\t\t\tprivate: true,\n\t\t\t\ttype: 'module',\n\t\t\t\tscripts,\n\t\t\t\tdependencies: deps,\n\t\t\t\tdevDependencies: devDeps,\n\t\t\t},\n\t\t\tnull,\n\t\t\t2,\n\t\t),\n\t);\n\n\t// tsconfig.json\n\twrite(\n\t\troot,\n\t\t'tsconfig.json',\n\t\tJSON.stringify(\n\t\t\t{\n\t\t\t\tcompilerOptions: {\n\t\t\t\t\ttarget: 'ESNext',\n\t\t\t\t\tmodule: 'ESNext',\n\t\t\t\t\tmoduleResolution: 'bundler',\n\t\t\t\t\tstrict: true,\n\t\t\t\t\tskipLibCheck: true,\n\t\t\t\t\t...(hasFrontend ? { jsx: 'react-jsx' } : {}),\n\t\t\t\t},\n\t\t\t\tinclude: ['src'],\n\t\t\t},\n\t\t\tnull,\n\t\t\t2,\n\t\t),\n\t);\n\n\t// Backend\n\tif (hasBackend) {\n\t\twrite(\n\t\t\troot,\n\t\t\t'src/server/router.ts',\n\t\t\t`import { createRouter, defineActions, KibinError } from '@kibinrpc/server'\n\nconst userActions = defineActions({\n async getUser(id: string) {\n // TODO: fetch from database\n throw new KibinError('NOT_FOUND', 'User not found')\n },\n})\n\nexport const router = createRouter({ user: userActions })\nexport type AppRouter = typeof router\n`,\n\t\t);\n\n\t\tconst port = hasFrontend ? 3001 : 3000;\n\t\twrite(\n\t\t\troot,\n\t\t\t'src/server/index.ts',\n\t\t\t`import { router } from './router.js'\n${hasFrontend ? \"import { Hono } from 'hono'\\nimport { serve } from '@hono/node-server'\" : ''}\n\n${\n\thasFrontend\n\t\t? `const app = new Hono()\n\napp.post('/api/rpc', (c) => router.handler(c.req.raw))\n\nserve({ fetch: app.fetch, port: ${port} }, () => {\n console.log('Server running on http://localhost:${port}')\n})`\n\t\t: `import { createServer } from 'node:http'\n\nconst server = createServer(async (req, res) => {\n const url = new URL(req.url ?? '/', \\`http://\\${req.headers.host}\\`)\n if (url.pathname === '/api/rpc' && req.method === 'POST') {\n const chunks: Buffer[] = []\n for await (const chunk of req) chunks.push(chunk)\n const body = Buffer.concat(chunks).toString()\n const request = new Request(\\`http://localhost:${port}\\${url.pathname}\\`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body,\n })\n const response = await router.handler(request)\n res.writeHead(response.status, { 'Content-Type': 'application/json' })\n res.end(await response.text())\n } else {\n res.writeHead(404)\n res.end()\n }\n})\n\nserver.listen(${port}, () => console.log('Server running on http://localhost:${port}'))`\n}\n`,\n\t\t);\n\t}\n\n\t// Frontend\n\tif (hasFrontend) {\n\t\tconst rpcUrl = hasBackend ? \"'http://localhost:3001/api/rpc'\" : \"'/api/rpc'\";\n\n\t\twrite(\n\t\t\troot,\n\t\t\t'src/client/main.tsx',\n\t\t\t`import { createRoot } from 'react-dom/client'\nimport { createKibinClient } from '@kibinrpc/client'\n${hasBackend ? \"import type { AppRouter } from '../server/router.js'\" : ''}\n\nconst client = createKibinClient${hasBackend ? '<AppRouter>' : ''}({ baseUrl: ${rpcUrl} })\n\nfunction App() {\n return <h1>Hello from kibinrpc!</h1>\n}\n\ncreateRoot(document.getElementById('root')!).render(<App />)\n`,\n\t\t);\n\n\t\twrite(\n\t\t\troot,\n\t\t\t'index.html',\n\t\t\t`<!DOCTYPE html>\n<html lang=\"en\">\n <head>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>${name}</title>\n </head>\n <body>\n <div id=\"root\"></div>\n <script type=\"module\" src=\"/src/client/main.tsx\"></script>\n </body>\n</html>\n`,\n\t\t);\n\n\t\twrite(\n\t\t\troot,\n\t\t\t'vite.config.ts',\n\t\t\t`import { defineConfig } from 'vite'\nimport react from '@vitejs/plugin-react'\n\nexport default defineConfig({\n plugins: [react()],${hasBackend ? `\\n server: { proxy: { '/api': 'http://localhost:3001' } },` : ''}\n})\n`,\n\t\t);\n\t}\n\n\t// README\n\tconst parts = [hasBackend && 'backend', hasFrontend && 'frontend'].filter(Boolean);\n\twrite(\n\t\troot,\n\t\t'README.md',\n\t\t`# ${name}\n\nA kibinrpc project with ${parts.join(' + ')}.\n\n## Getting started\n\n\\`\\`\\`sh\npnpm install\npnpm dev\n\\`\\`\\`\n`,\n\t);\n}\n","#!/usr/bin/env node\nimport { cancel, intro, isCancel, multiselect, outro, spinner, text } from '@clack/prompts';\nimport { scaffold, type TemplateId } from './scaffold.js';\n\nasync function main() {\n\tintro(' create kibinrpc ');\n\n\tconst name = await text({\n\t\tmessage: 'Project name',\n\t\tplaceholder: 'my-app',\n\t\tvalidate: (v) => {\n\t\t\tif (!v.trim()) return 'Please enter a project name';\n\t\t\tif (!/^[a-z0-9-]+$/.test(v.trim()))\n\t\t\t\treturn 'Only lowercase letters, numbers and hyphens allowed';\n\t\t},\n\t});\n\tif (isCancel(name)) {\n\t\tcancel('Cancelled');\n\t\tprocess.exit(0);\n\t}\n\n\tconst templates = await multiselect<\n\t\t{ value: TemplateId; label: string; hint: string }[],\n\t\tTemplateId\n\t>({\n\t\tmessage: 'What would you like to include?',\n\t\toptions: [\n\t\t\t{ value: 'backend', label: 'Backend', hint: 'server router + actions' },\n\t\t\t{ value: 'frontend', label: 'Frontend', hint: 'React + Vite client' },\n\t\t],\n\t\tinitialValues: ['backend', 'frontend'],\n\t\trequired: true,\n\t});\n\tif (isCancel(templates)) {\n\t\tcancel('Cancelled');\n\t\tprocess.exit(0);\n\t}\n\n\tconst s = spinner();\n\ts.start(`Creating ${name}`);\n\tscaffold({ name: name.trim(), templates });\n\ts.stop(`Created ${name}`);\n\n\toutro(`Next steps:\\n cd ${name}\\n pnpm install\\n pnpm dev`);\n}\n\nmain().catch((err) => {\n\tconsole.error(err);\n\tprocess.exit(1);\n});\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,16 +1,26 @@
1
1
  {
2
2
  "name": "kibinrpc",
3
- "version": "0.1.0",
4
- "description": "CLI for kibinrpc",
3
+ "version": "0.2.0",
4
+ "description": "Create a new kibinrpc project",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {
8
- "kibinrpc": "./bin/kibinrpc.js"
8
+ "kibinrpc": "./dist/index.mjs"
9
9
  },
10
10
  "files": [
11
- "bin"
11
+ "dist"
12
12
  ],
13
+ "dependencies": {
14
+ "@clack/prompts": "^0.10.1"
15
+ },
16
+ "devDependencies": {
17
+ "@types/node": "^25.9.1"
18
+ },
13
19
  "engines": {
14
- "node": ">=18"
20
+ "node": ">=22"
21
+ },
22
+ "scripts": {
23
+ "build": "tsdown",
24
+ "dev": "tsdown --watch"
15
25
  }
16
26
  }
package/bin/kibinrpc.js DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- console.log('kibinrpc');