create-alabjs 0.1.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/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "create-alabjs",
3
+ "version": "0.1.0",
4
+ "description": "Scaffold a new AlabJS app",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "create-alab": "./dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc -p tsconfig.json",
12
+ "dev": "tsc -p tsconfig.json --watch",
13
+ "typecheck": "tsc --noEmit",
14
+ "clean": "rm -rf dist"
15
+ },
16
+ "devDependencies": {
17
+ "typescript": "^5.8.0",
18
+ "@types/node": "^22.0.0"
19
+ },
20
+ "engines": {
21
+ "node": ">=22"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/thinkgrid-labs/alabjs.git"
26
+ }
27
+ }
package/src/index.ts ADDED
@@ -0,0 +1,377 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * create-alab — scaffold a new Alab project
4
+ *
5
+ * Usage:
6
+ * npx create-alab@latest my-app
7
+ * npx create-alab@latest my-app --template dashboard
8
+ * npx create-alab@latest my-app --template blog
9
+ */
10
+ import { mkdir, writeFile } from "node:fs/promises";
11
+ import { resolve, join } from "node:path";
12
+ import { parseArgs } from "node:util";
13
+
14
+ const { positionals, values } = parseArgs({
15
+ allowPositionals: true,
16
+ options: {
17
+ template: { type: "string", short: "t", default: "basic" },
18
+ },
19
+ });
20
+
21
+ const projectName = positionals[0] ?? "my-alab-app";
22
+ const template = (values.template as string) ?? "basic";
23
+ const targetDir = resolve(process.cwd(), projectName);
24
+
25
+ console.log(`\n alab creating ${projectName} (template: ${template})\n`);
26
+
27
+ // ─── shared files (same across all templates) ──────────────────────────────────
28
+
29
+ async function writeSharedFiles(dir: string) {
30
+ await writeFile(
31
+ join(dir, "package.json"),
32
+ JSON.stringify(
33
+ {
34
+ name: projectName,
35
+ private: true,
36
+ version: "0.1.0",
37
+ type: "module",
38
+ scripts: {
39
+ dev: "alab dev",
40
+ build: "alab build",
41
+ start: "alab start",
42
+ },
43
+ dependencies: {
44
+ alab: "^0.1.0",
45
+ react: "^19.1.0",
46
+ "react-dom": "^19.1.0",
47
+ tailwindcss: "^4.0.0",
48
+ "@tailwindcss/vite": "^4.0.0",
49
+ },
50
+ devDependencies: {
51
+ "@types/react": "^19.1.0",
52
+ "@types/react-dom": "^19.1.0",
53
+ typescript: "^5.8.0",
54
+ },
55
+ engines: {
56
+ node: ">=22",
57
+ },
58
+ },
59
+ null,
60
+ 2,
61
+ ),
62
+ );
63
+
64
+ await writeFile(
65
+ join(dir, "tsconfig.json"),
66
+ JSON.stringify(
67
+ {
68
+ compilerOptions: {
69
+ target: "ES2022",
70
+ lib: ["ES2022", "DOM", "DOM.Iterable"],
71
+ module: "ESNext",
72
+ moduleResolution: "Bundler",
73
+ jsx: "react-jsx",
74
+ strict: true,
75
+ exactOptionalPropertyTypes: true,
76
+ noUncheckedIndexedAccess: true,
77
+ noImplicitOverride: true,
78
+ noImplicitReturns: true,
79
+ isolatedModules: true,
80
+ verbatimModuleSyntax: true,
81
+ skipLibCheck: true,
82
+ },
83
+ include: ["app/**/*"],
84
+ },
85
+ null,
86
+ 2,
87
+ ),
88
+ );
89
+
90
+ await writeFile(join(dir, ".gitignore"), "node_modules/\n.alabjs/\ndist/\n.DS_Store\n");
91
+ await writeFile(join(dir, "app", "globals.css"), `@import "tailwindcss";\n`);
92
+ }
93
+
94
+ // ─── basic template ────────────────────────────────────────────────────────────
95
+
96
+ async function scaffoldBasic(dir: string) {
97
+ await mkdir(dir, { recursive: true });
98
+ await mkdir(join(dir, "app"), { recursive: true });
99
+ await mkdir(join(dir, "app", "users", "[id]"), { recursive: true });
100
+ await mkdir(join(dir, "public"), { recursive: true });
101
+
102
+ await writeSharedFiles(dir);
103
+
104
+ await writeFile(
105
+ join(dir, "app", "page.tsx"),
106
+ `import type { PageMetadata } from "alabjs";
107
+
108
+ export const metadata: PageMetadata = {
109
+ title: "Alab App",
110
+ description: "Built with Alab — the blazing-fast React framework.",
111
+ };
112
+
113
+ export default function HomePage() {
114
+ return (
115
+ <main className="flex min-h-screen flex-col items-center justify-center p-8">
116
+ <h1 className="text-4xl font-bold tracking-tight">Welcome to Alab</h1>
117
+ <p className="mt-4 text-lg text-gray-600">
118
+ Edit <code className="font-mono bg-gray-100 px-1 rounded">app/page.tsx</code> to get started.
119
+ </p>
120
+ </main>
121
+ );
122
+ }
123
+ `,
124
+ );
125
+
126
+ await writeFile(
127
+ join(dir, "app", "users", "[id]", "page.server.ts"),
128
+ `import { defineServerFn } from "alabjs/server";
129
+
130
+ export const getUser = defineServerFn(async ({ params }) => {
131
+ // Replace with your real data source
132
+ return { id: params["id"] ?? "", name: \`User \${params["id"] ?? ""}\` };
133
+ });
134
+ `,
135
+ );
136
+
137
+ await writeFile(
138
+ join(dir, "app", "users", "[id]", "page.tsx"),
139
+ `import type { AlabPage } from "alabjs";
140
+ import type { getUser } from "./page.server";
141
+ import { useServerData } from "alabjs/client";
142
+
143
+ const UserPage: AlabPage<"/users/[id]"> = ({ params }) => {
144
+ const user = useServerData<typeof getUser>("getUser", params);
145
+ return (
146
+ <main className="p-8">
147
+ <h1 className="text-2xl font-bold">{user.name}</h1>
148
+ <p className="text-gray-500">id: {user.id}</p>
149
+ </main>
150
+ );
151
+ };
152
+
153
+ export default UserPage;
154
+ `,
155
+ );
156
+ }
157
+
158
+ // ─── dashboard template ────────────────────────────────────────────────────────
159
+
160
+ async function scaffoldDashboard(dir: string) {
161
+ await mkdir(dir, { recursive: true });
162
+ await mkdir(join(dir, "app"), { recursive: true });
163
+ await mkdir(join(dir, "app", "analytics"), { recursive: true });
164
+ await mkdir(join(dir, "app", "settings"), { recursive: true });
165
+ await mkdir(join(dir, "public"), { recursive: true });
166
+
167
+ await writeSharedFiles(dir);
168
+
169
+ await writeFile(
170
+ join(dir, "app", "layout.tsx"),
171
+ `import type { ReactNode } from "react";
172
+
173
+ export function Layout({ children }: { children: ReactNode }) {
174
+ return (
175
+ <div className="flex min-h-screen">
176
+ <nav className="w-64 bg-gray-900 text-white flex flex-col p-6 gap-2">
177
+ <span className="text-xl font-bold mb-6">Dashboard</span>
178
+ <a href="/" className="px-3 py-2 rounded hover:bg-gray-700 transition-colors">
179
+ Overview
180
+ </a>
181
+ <a href="/analytics" className="px-3 py-2 rounded hover:bg-gray-700 transition-colors">
182
+ Analytics
183
+ </a>
184
+ <a href="/settings" className="px-3 py-2 rounded hover:bg-gray-700 transition-colors">
185
+ Settings
186
+ </a>
187
+ </nav>
188
+ <main className="flex-1 p-8 bg-gray-50">{children}</main>
189
+ </div>
190
+ );
191
+ }
192
+ `,
193
+ );
194
+
195
+ await writeFile(
196
+ join(dir, "app", "page.tsx"),
197
+ `import type { PageMetadata } from "alabjs";
198
+ import { Layout } from "./layout";
199
+
200
+ export const metadata: PageMetadata = {
201
+ title: "Dashboard",
202
+ description: "Dashboard overview.",
203
+ };
204
+
205
+ export default function DashboardPage() {
206
+ return (
207
+ <Layout>
208
+ <h1 className="text-3xl font-bold mb-8">Overview</h1>
209
+ <div className="grid grid-cols-2 gap-6">
210
+ <div className="bg-white rounded-xl shadow p-6">
211
+ <p className="text-sm text-gray-500 uppercase tracking-wide">Total Users</p>
212
+ <p className="text-4xl font-bold mt-2">1,284</p>
213
+ </div>
214
+ <div className="bg-white rounded-xl shadow p-6">
215
+ <p className="text-sm text-gray-500 uppercase tracking-wide">Revenue</p>
216
+ <p className="text-4xl font-bold mt-2">$48,320</p>
217
+ </div>
218
+ <div className="bg-white rounded-xl shadow p-6">
219
+ <p className="text-sm text-gray-500 uppercase tracking-wide">Active Sessions</p>
220
+ <p className="text-4xl font-bold mt-2">342</p>
221
+ </div>
222
+ <div className="bg-white rounded-xl shadow p-6">
223
+ <p className="text-sm text-gray-500 uppercase tracking-wide">Conversion Rate</p>
224
+ <p className="text-4xl font-bold mt-2">3.6%</p>
225
+ </div>
226
+ </div>
227
+ </Layout>
228
+ );
229
+ }
230
+ `,
231
+ );
232
+
233
+ await writeFile(
234
+ join(dir, "app", "analytics", "page.tsx"),
235
+ `import type { PageMetadata } from "alabjs";
236
+ import { Layout } from "../layout";
237
+
238
+ export const metadata: PageMetadata = {
239
+ title: "Analytics",
240
+ description: "Analytics overview.",
241
+ };
242
+
243
+ export default function AnalyticsPage() {
244
+ return (
245
+ <Layout>
246
+ <h1 className="text-3xl font-bold mb-8">Analytics</h1>
247
+ <p className="text-gray-500">Charts and analytics data will appear here.</p>
248
+ </Layout>
249
+ );
250
+ }
251
+ `,
252
+ );
253
+
254
+ await writeFile(
255
+ join(dir, "app", "settings", "page.tsx"),
256
+ `import type { PageMetadata } from "alabjs";
257
+ import { Layout } from "../layout";
258
+
259
+ export const metadata: PageMetadata = {
260
+ title: "Settings",
261
+ description: "Application settings.",
262
+ };
263
+
264
+ export default function SettingsPage() {
265
+ return (
266
+ <Layout>
267
+ <h1 className="text-3xl font-bold mb-8">Settings</h1>
268
+ <p className="text-gray-500">Application settings will appear here.</p>
269
+ </Layout>
270
+ );
271
+ }
272
+ `,
273
+ );
274
+ }
275
+
276
+ // ─── blog template ─────────────────────────────────────────────────────────────
277
+
278
+ async function scaffoldBlog(dir: string) {
279
+ await mkdir(dir, { recursive: true });
280
+ await mkdir(join(dir, "app"), { recursive: true });
281
+ await mkdir(join(dir, "app", "posts", "[slug]"), { recursive: true });
282
+ await mkdir(join(dir, "public"), { recursive: true });
283
+
284
+ await writeSharedFiles(dir);
285
+
286
+ await writeFile(
287
+ join(dir, "app", "page.tsx"),
288
+ `import type { PageMetadata } from "alabjs";
289
+
290
+ export const metadata: PageMetadata = {
291
+ title: "Blog",
292
+ description: "Latest posts.",
293
+ };
294
+
295
+ const posts = [
296
+ { slug: "hello-world", title: "Hello World", excerpt: "Welcome to the blog.", date: "2026-01-01" },
297
+ { slug: "getting-started", title: "Getting Started with Alab", excerpt: "Learn how to build fast apps with Alab.", date: "2026-02-14" },
298
+ { slug: "server-functions", title: "Server Functions Deep Dive", excerpt: "Type-safe server logic without API boilerplate.", date: "2026-03-01" },
299
+ ];
300
+
301
+ export default function BlogListPage() {
302
+ return (
303
+ <main className="max-w-2xl mx-auto px-4 py-12">
304
+ <h1 className="text-4xl font-bold mb-10">Blog</h1>
305
+ <ul className="flex flex-col gap-8">
306
+ {posts.map((post) => (
307
+ <li key={post.slug}>
308
+ <a href={\`/posts/\${post.slug}\`} className="group block">
309
+ <p className="text-sm text-gray-400 mb-1">{post.date}</p>
310
+ <h2 className="text-2xl font-semibold group-hover:text-blue-600 transition-colors">{post.title}</h2>
311
+ <p className="mt-2 text-gray-600">{post.excerpt}</p>
312
+ </a>
313
+ </li>
314
+ ))}
315
+ </ul>
316
+ </main>
317
+ );
318
+ }
319
+ `,
320
+ );
321
+
322
+ await writeFile(
323
+ join(dir, "app", "posts", "[slug]", "page.server.ts"),
324
+ `import { defineServerFn } from "alabjs/server";
325
+
326
+ const posts = [
327
+ { slug: "hello-world", title: "Hello World", content: "Welcome to the blog. This is your first post.", date: "2026-01-01" },
328
+ { slug: "getting-started", title: "Getting Started with Alab", content: "Alab makes it easy to build fast, type-safe React apps.", date: "2026-02-14" },
329
+ { slug: "server-functions", title: "Server Functions Deep Dive", content: "Server functions let you write server-only logic with full type safety on the client.", date: "2026-03-01" },
330
+ ];
331
+
332
+ export const getPost = defineServerFn(async ({ params }) => {
333
+ const post = posts.find((p) => p.slug === params["slug"]);
334
+ if (!post) throw new Error(\`Post not found: \${params["slug"] ?? ""}\`);
335
+ return post;
336
+ });
337
+ `,
338
+ );
339
+
340
+ await writeFile(
341
+ join(dir, "app", "posts", "[slug]", "page.tsx"),
342
+ `import type { AlabPage } from "alabjs";
343
+ import type { getPost } from "./page.server";
344
+ import { useServerData } from "alabjs/client";
345
+
346
+ export const ssr = true;
347
+
348
+ const PostPage: AlabPage<"/posts/[slug]"> = ({ params }) => {
349
+ const post = useServerData<typeof getPost>("getPost", params);
350
+ return (
351
+ <main className="max-w-2xl mx-auto px-4 py-12">
352
+ <p className="text-sm text-gray-400 mb-2">{post.date}</p>
353
+ <h1 className="text-4xl font-bold mb-6">{post.title}</h1>
354
+ <p className="text-lg text-gray-700 leading-relaxed">{post.content}</p>
355
+ </main>
356
+ );
357
+ };
358
+
359
+ export default PostPage;
360
+ `,
361
+ );
362
+ }
363
+
364
+ // ─── run ───────────────────────────────────────────────────────────────────────
365
+
366
+ if (template === "dashboard") {
367
+ await scaffoldDashboard(targetDir);
368
+ } else if (template === "blog") {
369
+ await scaffoldBlog(targetDir);
370
+ } else {
371
+ await scaffoldBasic(targetDir);
372
+ }
373
+
374
+ console.log(` done! Next steps:\n`);
375
+ console.log(` cd ${projectName}`);
376
+ console.log(` pnpm install`);
377
+ console.log(` pnpm dev\n`);
package/tsconfig.json ADDED
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist",
6
+ "types": ["node"]
7
+ },
8
+ "include": ["src/**/*"]
9
+ }