baasix 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/README.md +355 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.mjs +2521 -0
- package/package.json +56 -0
- package/src/commands/extension.ts +447 -0
- package/src/commands/generate.ts +485 -0
- package/src/commands/init.ts +1409 -0
- package/src/commands/migrate.ts +573 -0
- package/src/index.ts +39 -0
- package/src/utils/api-client.ts +121 -0
- package/src/utils/get-config.ts +69 -0
- package/src/utils/get-package-info.ts +12 -0
- package/src/utils/package-manager.ts +62 -0
- package/tsconfig.json +19 -0
- package/tsup.config.ts +16 -0
|
@@ -0,0 +1,1409 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
cancel,
|
|
6
|
+
confirm,
|
|
7
|
+
intro,
|
|
8
|
+
isCancel,
|
|
9
|
+
log,
|
|
10
|
+
outro,
|
|
11
|
+
select,
|
|
12
|
+
spinner,
|
|
13
|
+
text,
|
|
14
|
+
multiselect,
|
|
15
|
+
} from "@clack/prompts";
|
|
16
|
+
import chalk from "chalk";
|
|
17
|
+
import { Command } from "commander";
|
|
18
|
+
import crypto from "node:crypto";
|
|
19
|
+
import { detectPackageManager, installDependencies, type PackageManager } from "../utils/package-manager.js";
|
|
20
|
+
|
|
21
|
+
type ProjectTemplate = "api" | "nextjs" | "nextjs-app";
|
|
22
|
+
|
|
23
|
+
interface InitOptions {
|
|
24
|
+
cwd: string;
|
|
25
|
+
template?: ProjectTemplate;
|
|
26
|
+
name?: string;
|
|
27
|
+
yes?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface ProjectConfig {
|
|
31
|
+
projectName: string;
|
|
32
|
+
template: ProjectTemplate;
|
|
33
|
+
// Database
|
|
34
|
+
databaseUrl: string;
|
|
35
|
+
// Features
|
|
36
|
+
socketEnabled: boolean;
|
|
37
|
+
multiTenant: boolean;
|
|
38
|
+
publicRegistration: boolean;
|
|
39
|
+
// Storage
|
|
40
|
+
storageDriver: "LOCAL" | "S3";
|
|
41
|
+
s3Config?: {
|
|
42
|
+
endpoint: string;
|
|
43
|
+
bucket: string;
|
|
44
|
+
accessKey: string;
|
|
45
|
+
secretKey: string;
|
|
46
|
+
region: string;
|
|
47
|
+
};
|
|
48
|
+
// Cache
|
|
49
|
+
cacheAdapter: "memory" | "redis";
|
|
50
|
+
redisUrl?: string;
|
|
51
|
+
// Auth
|
|
52
|
+
authServices: string[];
|
|
53
|
+
// Mail
|
|
54
|
+
mailEnabled: boolean;
|
|
55
|
+
// OpenAPI
|
|
56
|
+
openApiEnabled: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function generateSecret(length: number = 64): string {
|
|
60
|
+
return crypto.randomBytes(length).toString("base64url").slice(0, length);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function initAction(opts: InitOptions) {
|
|
64
|
+
const cwd = path.resolve(opts.cwd);
|
|
65
|
+
|
|
66
|
+
intro(chalk.bgCyan.black(" Baasix Project Setup "));
|
|
67
|
+
|
|
68
|
+
// Get project name
|
|
69
|
+
let projectName = opts.name;
|
|
70
|
+
if (!projectName) {
|
|
71
|
+
const result = await text({
|
|
72
|
+
message: "What is your project name?",
|
|
73
|
+
placeholder: "my-baasix-app",
|
|
74
|
+
defaultValue: "my-baasix-app",
|
|
75
|
+
validate: (value) => {
|
|
76
|
+
if (!value) return "Project name is required";
|
|
77
|
+
if (!/^[a-z0-9-_]+$/i.test(value)) return "Project name must be alphanumeric with dashes or underscores";
|
|
78
|
+
return undefined;
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (isCancel(result)) {
|
|
83
|
+
cancel("Operation cancelled");
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
projectName = result as string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Select template
|
|
90
|
+
let template = opts.template;
|
|
91
|
+
if (!template) {
|
|
92
|
+
const result = await select({
|
|
93
|
+
message: "Select a project template:",
|
|
94
|
+
options: [
|
|
95
|
+
{
|
|
96
|
+
value: "api",
|
|
97
|
+
label: "API Only",
|
|
98
|
+
hint: "Baasix server with basic configuration",
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
value: "nextjs-app",
|
|
102
|
+
label: "Next.js (App Router)",
|
|
103
|
+
hint: "Next.js 14+ with App Router and SDK integration",
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
value: "nextjs",
|
|
107
|
+
label: "Next.js (Pages Router)",
|
|
108
|
+
hint: "Next.js with Pages Router and SDK integration",
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (isCancel(result)) {
|
|
114
|
+
cancel("Operation cancelled");
|
|
115
|
+
process.exit(0);
|
|
116
|
+
}
|
|
117
|
+
template = result as ProjectTemplate;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Collect configuration options
|
|
121
|
+
const config = await collectProjectConfig(projectName, template, opts.yes);
|
|
122
|
+
if (!config) {
|
|
123
|
+
cancel("Operation cancelled");
|
|
124
|
+
process.exit(0);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const projectPath = path.join(cwd, projectName);
|
|
128
|
+
|
|
129
|
+
// Check if directory exists
|
|
130
|
+
if (existsSync(projectPath)) {
|
|
131
|
+
const overwrite = await confirm({
|
|
132
|
+
message: `Directory ${projectName} already exists. Overwrite?`,
|
|
133
|
+
initialValue: false,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (isCancel(overwrite) || !overwrite) {
|
|
137
|
+
cancel("Operation cancelled");
|
|
138
|
+
process.exit(0);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const s = spinner();
|
|
143
|
+
s.start("Creating project structure...");
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
// Create project directory
|
|
147
|
+
await fs.mkdir(projectPath, { recursive: true });
|
|
148
|
+
|
|
149
|
+
// Generate based on template
|
|
150
|
+
if (template === "api") {
|
|
151
|
+
await createApiProject(projectPath, config);
|
|
152
|
+
} else if (template === "nextjs-app" || template === "nextjs") {
|
|
153
|
+
await createNextJsProject(projectPath, config, template === "nextjs-app");
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
s.stop("Project structure created");
|
|
157
|
+
|
|
158
|
+
// Detect package manager
|
|
159
|
+
const packageManager = detectPackageManager(cwd);
|
|
160
|
+
|
|
161
|
+
// Install dependencies
|
|
162
|
+
const shouldInstall = opts.yes || await confirm({
|
|
163
|
+
message: `Install dependencies with ${packageManager}?`,
|
|
164
|
+
initialValue: true,
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
if (shouldInstall && !isCancel(shouldInstall)) {
|
|
168
|
+
s.start("Installing dependencies...");
|
|
169
|
+
try {
|
|
170
|
+
await installDependencies({
|
|
171
|
+
dependencies: [],
|
|
172
|
+
packageManager,
|
|
173
|
+
cwd: projectPath,
|
|
174
|
+
});
|
|
175
|
+
s.stop("Dependencies installed");
|
|
176
|
+
} catch (error) {
|
|
177
|
+
s.stop("Failed to install dependencies");
|
|
178
|
+
log.warn(`Run ${chalk.cyan(`cd ${projectName} && ${packageManager} install`)} to install manually`);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
outro(chalk.green("✨ Project created successfully!"));
|
|
183
|
+
|
|
184
|
+
// Print next steps
|
|
185
|
+
console.log();
|
|
186
|
+
console.log(chalk.bold("Next steps:"));
|
|
187
|
+
console.log(` ${chalk.cyan(`cd ${projectName}`)}`);
|
|
188
|
+
if (template === "api") {
|
|
189
|
+
console.log(` ${chalk.cyan("# Review and update your .env file")}`);
|
|
190
|
+
console.log(` ${chalk.cyan(`${packageManager} run dev`)}`);
|
|
191
|
+
} else {
|
|
192
|
+
console.log(` ${chalk.cyan(`${packageManager} run dev`)} ${chalk.dim("# Start Next.js frontend")}`);
|
|
193
|
+
console.log();
|
|
194
|
+
console.log(chalk.dim(" Note: This is a frontend-only project. You need a separate Baasix API."));
|
|
195
|
+
console.log(chalk.dim(` To create an API: ${chalk.cyan("npx @tspvivek/baasix-cli init --template api")}`));
|
|
196
|
+
}
|
|
197
|
+
console.log();
|
|
198
|
+
|
|
199
|
+
} catch (error) {
|
|
200
|
+
s.stop("Failed to create project");
|
|
201
|
+
log.error(error instanceof Error ? error.message : "Unknown error");
|
|
202
|
+
process.exit(1);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function collectProjectConfig(
|
|
207
|
+
projectName: string,
|
|
208
|
+
template: ProjectTemplate,
|
|
209
|
+
skipPrompts?: boolean
|
|
210
|
+
): Promise<ProjectConfig | null> {
|
|
211
|
+
// If skipPrompts is true, return default configuration
|
|
212
|
+
if (skipPrompts) {
|
|
213
|
+
return {
|
|
214
|
+
projectName,
|
|
215
|
+
template,
|
|
216
|
+
databaseUrl: "postgresql://postgres:password@localhost:5432/baasix",
|
|
217
|
+
socketEnabled: false,
|
|
218
|
+
multiTenant: false,
|
|
219
|
+
publicRegistration: true,
|
|
220
|
+
storageDriver: "LOCAL",
|
|
221
|
+
s3Config: undefined,
|
|
222
|
+
cacheAdapter: "memory",
|
|
223
|
+
redisUrl: undefined,
|
|
224
|
+
authServices: ["LOCAL"],
|
|
225
|
+
mailEnabled: false,
|
|
226
|
+
openApiEnabled: true,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Database URL
|
|
231
|
+
const dbUrl = await text({
|
|
232
|
+
message: "PostgreSQL connection URL:",
|
|
233
|
+
placeholder: "postgresql://postgres:password@localhost:5432/baasix",
|
|
234
|
+
defaultValue: "postgresql://postgres:password@localhost:5432/baasix",
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
if (isCancel(dbUrl)) return null;
|
|
238
|
+
|
|
239
|
+
// Multi-tenant
|
|
240
|
+
const multiTenant = await confirm({
|
|
241
|
+
message: "Enable multi-tenancy?",
|
|
242
|
+
initialValue: false,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
if (isCancel(multiTenant)) return null;
|
|
246
|
+
|
|
247
|
+
// Public registration
|
|
248
|
+
const publicRegistration = await confirm({
|
|
249
|
+
message: "Allow public user registration?",
|
|
250
|
+
initialValue: true,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
if (isCancel(publicRegistration)) return null;
|
|
254
|
+
|
|
255
|
+
// Real-time / Socket
|
|
256
|
+
const socketEnabled = await confirm({
|
|
257
|
+
message: "Enable real-time features (WebSocket)?",
|
|
258
|
+
initialValue: false,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
if (isCancel(socketEnabled)) return null;
|
|
262
|
+
|
|
263
|
+
// Storage driver
|
|
264
|
+
const storageDriver = await select({
|
|
265
|
+
message: "Select storage driver:",
|
|
266
|
+
options: [
|
|
267
|
+
{ value: "LOCAL", label: "Local Storage", hint: "Store files locally in uploads folder" },
|
|
268
|
+
{ value: "S3", label: "S3 Compatible", hint: "AWS S3, DigitalOcean Spaces, MinIO, etc." },
|
|
269
|
+
],
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
if (isCancel(storageDriver)) return null;
|
|
273
|
+
|
|
274
|
+
let s3Config: ProjectConfig["s3Config"];
|
|
275
|
+
if (storageDriver === "S3") {
|
|
276
|
+
const endpoint = await text({
|
|
277
|
+
message: "S3 endpoint:",
|
|
278
|
+
placeholder: "s3.amazonaws.com",
|
|
279
|
+
defaultValue: "s3.amazonaws.com",
|
|
280
|
+
});
|
|
281
|
+
if (isCancel(endpoint)) return null;
|
|
282
|
+
|
|
283
|
+
const bucket = await text({
|
|
284
|
+
message: "S3 bucket name:",
|
|
285
|
+
placeholder: "my-bucket",
|
|
286
|
+
});
|
|
287
|
+
if (isCancel(bucket)) return null;
|
|
288
|
+
|
|
289
|
+
const accessKey = await text({
|
|
290
|
+
message: "S3 Access Key ID:",
|
|
291
|
+
placeholder: "AKIAIOSFODNN7EXAMPLE",
|
|
292
|
+
});
|
|
293
|
+
if (isCancel(accessKey)) return null;
|
|
294
|
+
|
|
295
|
+
const secretKey = await text({
|
|
296
|
+
message: "S3 Secret Access Key:",
|
|
297
|
+
placeholder: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
|
|
298
|
+
});
|
|
299
|
+
if (isCancel(secretKey)) return null;
|
|
300
|
+
|
|
301
|
+
const region = await text({
|
|
302
|
+
message: "S3 Region:",
|
|
303
|
+
placeholder: "us-east-1",
|
|
304
|
+
defaultValue: "us-east-1",
|
|
305
|
+
});
|
|
306
|
+
if (isCancel(region)) return null;
|
|
307
|
+
|
|
308
|
+
s3Config = {
|
|
309
|
+
endpoint: endpoint as string,
|
|
310
|
+
bucket: bucket as string,
|
|
311
|
+
accessKey: accessKey as string,
|
|
312
|
+
secretKey: secretKey as string,
|
|
313
|
+
region: region as string,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Cache adapter
|
|
318
|
+
const cacheAdapter = await select({
|
|
319
|
+
message: "Select cache adapter:",
|
|
320
|
+
options: [
|
|
321
|
+
{ value: "memory", label: "In-Memory", hint: "Simple, good for development" },
|
|
322
|
+
{ value: "redis", label: "Redis/Valkey", hint: "Recommended for production" },
|
|
323
|
+
],
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
if (isCancel(cacheAdapter)) return null;
|
|
327
|
+
|
|
328
|
+
let redisUrl: string | undefined;
|
|
329
|
+
if (cacheAdapter === "redis") {
|
|
330
|
+
const url = await text({
|
|
331
|
+
message: "Redis connection URL:",
|
|
332
|
+
placeholder: "redis://localhost:6379",
|
|
333
|
+
defaultValue: "redis://localhost:6379",
|
|
334
|
+
});
|
|
335
|
+
if (isCancel(url)) return null;
|
|
336
|
+
redisUrl = url as string;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Auth services
|
|
340
|
+
const authServices = await multiselect({
|
|
341
|
+
message: "Select authentication methods:",
|
|
342
|
+
options: [
|
|
343
|
+
{ value: "LOCAL", label: "Email/Password", hint: "Built-in authentication" },
|
|
344
|
+
{ value: "GOOGLE", label: "Google OAuth" },
|
|
345
|
+
{ value: "FACEBOOK", label: "Facebook OAuth" },
|
|
346
|
+
{ value: "GITHUB", label: "GitHub OAuth" },
|
|
347
|
+
{ value: "APPLE", label: "Apple Sign In" },
|
|
348
|
+
],
|
|
349
|
+
initialValues: ["LOCAL"],
|
|
350
|
+
required: true,
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
if (isCancel(authServices)) return null;
|
|
354
|
+
|
|
355
|
+
// OpenAPI
|
|
356
|
+
const openApiEnabled = await confirm({
|
|
357
|
+
message: "Enable OpenAPI documentation (Swagger)?",
|
|
358
|
+
initialValue: true,
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
if (isCancel(openApiEnabled)) return null;
|
|
362
|
+
|
|
363
|
+
// Mail (optional)
|
|
364
|
+
const mailEnabled = await confirm({
|
|
365
|
+
message: "Configure email sending?",
|
|
366
|
+
initialValue: false,
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
if (isCancel(mailEnabled)) return null;
|
|
370
|
+
|
|
371
|
+
return {
|
|
372
|
+
projectName,
|
|
373
|
+
template,
|
|
374
|
+
databaseUrl: dbUrl as string,
|
|
375
|
+
socketEnabled: socketEnabled as boolean,
|
|
376
|
+
multiTenant: multiTenant as boolean,
|
|
377
|
+
publicRegistration: publicRegistration as boolean,
|
|
378
|
+
storageDriver: storageDriver as "LOCAL" | "S3",
|
|
379
|
+
s3Config,
|
|
380
|
+
cacheAdapter: cacheAdapter as "memory" | "redis",
|
|
381
|
+
redisUrl,
|
|
382
|
+
authServices: authServices as string[],
|
|
383
|
+
mailEnabled: mailEnabled as boolean,
|
|
384
|
+
openApiEnabled: openApiEnabled as boolean,
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async function createApiProject(projectPath: string, config: ProjectConfig) {
|
|
389
|
+
const secretKey = generateSecret(64);
|
|
390
|
+
|
|
391
|
+
// package.json
|
|
392
|
+
const packageJson = {
|
|
393
|
+
name: config.projectName,
|
|
394
|
+
version: "0.1.0",
|
|
395
|
+
type: "module",
|
|
396
|
+
scripts: {
|
|
397
|
+
dev: "node --watch server.js",
|
|
398
|
+
start: "node server.js",
|
|
399
|
+
},
|
|
400
|
+
dependencies: {
|
|
401
|
+
"@tspvivek/baasix": "latest",
|
|
402
|
+
"dotenv": "^16.3.1",
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
await fs.writeFile(
|
|
407
|
+
path.join(projectPath, "package.json"),
|
|
408
|
+
JSON.stringify(packageJson, null, 2)
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
// server.js
|
|
412
|
+
const serverJs = `import { startServer } from "@tspvivek/baasix";
|
|
413
|
+
|
|
414
|
+
startServer({
|
|
415
|
+
port: process.env.PORT || 8056,
|
|
416
|
+
logger: {
|
|
417
|
+
level: process.env.LOG_LEVEL || "info",
|
|
418
|
+
pretty: process.env.NODE_ENV !== "production",
|
|
419
|
+
},
|
|
420
|
+
}).catch((error) => {
|
|
421
|
+
console.error("Failed to start server:", error);
|
|
422
|
+
process.exit(1);
|
|
423
|
+
});
|
|
424
|
+
`;
|
|
425
|
+
|
|
426
|
+
await fs.writeFile(path.join(projectPath, "server.js"), serverJs);
|
|
427
|
+
|
|
428
|
+
// Generate .env file based on config
|
|
429
|
+
const envContent = generateEnvContent(config, secretKey);
|
|
430
|
+
await fs.writeFile(path.join(projectPath, ".env"), envContent);
|
|
431
|
+
|
|
432
|
+
// .env.example (sanitized version)
|
|
433
|
+
const envExample = generateEnvExample(config);
|
|
434
|
+
await fs.writeFile(path.join(projectPath, ".env.example"), envExample);
|
|
435
|
+
|
|
436
|
+
// .gitignore
|
|
437
|
+
const gitignore = `node_modules/
|
|
438
|
+
.env
|
|
439
|
+
uploads/
|
|
440
|
+
logs/
|
|
441
|
+
dist/
|
|
442
|
+
.cache/
|
|
443
|
+
.temp/
|
|
444
|
+
`;
|
|
445
|
+
|
|
446
|
+
await fs.writeFile(path.join(projectPath, ".gitignore"), gitignore);
|
|
447
|
+
|
|
448
|
+
// Create extensions directory
|
|
449
|
+
await fs.mkdir(path.join(projectPath, "extensions"), { recursive: true });
|
|
450
|
+
await fs.writeFile(
|
|
451
|
+
path.join(projectPath, "extensions", ".gitkeep"),
|
|
452
|
+
"# Place your Baasix extensions here\n"
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
// Create uploads directory (for local storage)
|
|
456
|
+
if (config.storageDriver === "LOCAL") {
|
|
457
|
+
await fs.mkdir(path.join(projectPath, "uploads"), { recursive: true });
|
|
458
|
+
await fs.writeFile(path.join(projectPath, "uploads", ".gitkeep"), "");
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Create migrations directory
|
|
462
|
+
await fs.mkdir(path.join(projectPath, "migrations"), { recursive: true });
|
|
463
|
+
await fs.writeFile(path.join(projectPath, "migrations", ".gitkeep"), "");
|
|
464
|
+
|
|
465
|
+
// README.md
|
|
466
|
+
const readme = generateReadme(config);
|
|
467
|
+
await fs.writeFile(path.join(projectPath, "README.md"), readme);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function generateEnvContent(config: ProjectConfig, secretKey: string): string {
|
|
471
|
+
const lines: string[] = [];
|
|
472
|
+
|
|
473
|
+
// Server section
|
|
474
|
+
lines.push("#-----------------------------------");
|
|
475
|
+
lines.push("# Server");
|
|
476
|
+
lines.push("#-----------------------------------");
|
|
477
|
+
lines.push("PORT=8056");
|
|
478
|
+
lines.push("HOST=localhost");
|
|
479
|
+
lines.push("NODE_ENV=development");
|
|
480
|
+
lines.push("DEBUGGING=false");
|
|
481
|
+
lines.push("");
|
|
482
|
+
|
|
483
|
+
// Database section
|
|
484
|
+
lines.push("#-----------------------------------");
|
|
485
|
+
lines.push("# Database");
|
|
486
|
+
lines.push("#-----------------------------------");
|
|
487
|
+
lines.push(`DATABASE_URL="${config.databaseUrl}"`);
|
|
488
|
+
lines.push("DATABASE_LOGGING=false");
|
|
489
|
+
lines.push("DATABASE_POOL_MAX=20");
|
|
490
|
+
lines.push("DATABASE_POOL_MIN=0");
|
|
491
|
+
lines.push("");
|
|
492
|
+
|
|
493
|
+
// Security section
|
|
494
|
+
lines.push("#-----------------------------------");
|
|
495
|
+
lines.push("# Security");
|
|
496
|
+
lines.push("#-----------------------------------");
|
|
497
|
+
lines.push(`SECRET_KEY=${secretKey}`);
|
|
498
|
+
lines.push("ACCESS_TOKEN_EXPIRES_IN=31536000");
|
|
499
|
+
lines.push("");
|
|
500
|
+
|
|
501
|
+
// Multi-tenancy section
|
|
502
|
+
lines.push("#-----------------------------------");
|
|
503
|
+
lines.push("# Multi-tenancy");
|
|
504
|
+
lines.push("#-----------------------------------");
|
|
505
|
+
lines.push(`MULTI_TENANT=${config.multiTenant}`);
|
|
506
|
+
lines.push(`PUBLIC_REGISTRATION=${config.publicRegistration}`);
|
|
507
|
+
if (!config.multiTenant) {
|
|
508
|
+
lines.push("DEFAULT_ROLE_REGISTERED=user");
|
|
509
|
+
}
|
|
510
|
+
lines.push("");
|
|
511
|
+
|
|
512
|
+
// Socket section
|
|
513
|
+
lines.push("#-----------------------------------");
|
|
514
|
+
lines.push("# Real-time (WebSocket)");
|
|
515
|
+
lines.push("#-----------------------------------");
|
|
516
|
+
lines.push(`SOCKET_ENABLED=${config.socketEnabled}`);
|
|
517
|
+
if (config.socketEnabled) {
|
|
518
|
+
lines.push('SOCKET_CORS_ENABLED_ORIGINS="http://localhost:3000,http://localhost:8056"');
|
|
519
|
+
lines.push("SOCKET_PATH=/realtime");
|
|
520
|
+
if (config.cacheAdapter === "redis" && config.redisUrl) {
|
|
521
|
+
lines.push("SOCKET_REDIS_ENABLED=true");
|
|
522
|
+
lines.push(`SOCKET_REDIS_URL=${config.redisUrl}`);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
lines.push("");
|
|
526
|
+
|
|
527
|
+
// Cache section
|
|
528
|
+
lines.push("#-----------------------------------");
|
|
529
|
+
lines.push("# Cache");
|
|
530
|
+
lines.push("#-----------------------------------");
|
|
531
|
+
lines.push("CACHE_ENABLED=true");
|
|
532
|
+
lines.push(`CACHE_ADAPTER=${config.cacheAdapter}`);
|
|
533
|
+
lines.push("CACHE_TTL=300");
|
|
534
|
+
lines.push("CACHE_STRATEGY=explicit");
|
|
535
|
+
if (config.cacheAdapter === "memory") {
|
|
536
|
+
lines.push("CACHE_SIZE_GB=1");
|
|
537
|
+
} else if (config.cacheAdapter === "redis" && config.redisUrl) {
|
|
538
|
+
lines.push(`CACHE_REDIS_URL=${config.redisUrl}`);
|
|
539
|
+
}
|
|
540
|
+
lines.push("");
|
|
541
|
+
|
|
542
|
+
// Storage section
|
|
543
|
+
lines.push("#-----------------------------------");
|
|
544
|
+
lines.push("# Storage");
|
|
545
|
+
lines.push("#-----------------------------------");
|
|
546
|
+
if (config.storageDriver === "LOCAL") {
|
|
547
|
+
lines.push('STORAGE_SERVICES_ENABLED="LOCAL"');
|
|
548
|
+
lines.push('STORAGE_DEFAULT_SERVICE="LOCAL"');
|
|
549
|
+
lines.push("STORAGE_TEMP_PATH=./.temp");
|
|
550
|
+
lines.push("");
|
|
551
|
+
lines.push("# Local Storage");
|
|
552
|
+
lines.push("LOCAL_STORAGE_DRIVER=LOCAL");
|
|
553
|
+
lines.push('LOCAL_STORAGE_PATH="./uploads"');
|
|
554
|
+
} else if (config.storageDriver === "S3" && config.s3Config) {
|
|
555
|
+
lines.push('STORAGE_SERVICES_ENABLED="S3"');
|
|
556
|
+
lines.push('STORAGE_DEFAULT_SERVICE="S3"');
|
|
557
|
+
lines.push("STORAGE_TEMP_PATH=./.temp");
|
|
558
|
+
lines.push("");
|
|
559
|
+
lines.push("# S3 Compatible Storage");
|
|
560
|
+
lines.push("S3_STORAGE_DRIVER=S3");
|
|
561
|
+
lines.push(`S3_STORAGE_ENDPOINT=${config.s3Config.endpoint}`);
|
|
562
|
+
lines.push(`S3_STORAGE_BUCKET=${config.s3Config.bucket}`);
|
|
563
|
+
lines.push(`S3_STORAGE_ACCESS_KEY_ID=${config.s3Config.accessKey}`);
|
|
564
|
+
lines.push(`S3_STORAGE_SECRET_ACCESS_KEY=${config.s3Config.secretKey}`);
|
|
565
|
+
lines.push(`S3_STORAGE_REGION=${config.s3Config.region}`);
|
|
566
|
+
}
|
|
567
|
+
lines.push("");
|
|
568
|
+
|
|
569
|
+
// Auth section
|
|
570
|
+
lines.push("#-----------------------------------");
|
|
571
|
+
lines.push("# Authentication");
|
|
572
|
+
lines.push("#-----------------------------------");
|
|
573
|
+
lines.push(`AUTH_SERVICES_ENABLED=${config.authServices.join(",")}`);
|
|
574
|
+
lines.push('AUTH_APP_URL="http://localhost:3000,http://localhost:8056"');
|
|
575
|
+
lines.push("");
|
|
576
|
+
|
|
577
|
+
if (config.authServices.includes("GOOGLE")) {
|
|
578
|
+
lines.push("# Google OAuth");
|
|
579
|
+
lines.push("GOOGLE_CLIENT_ID=your_google_client_id");
|
|
580
|
+
lines.push("GOOGLE_CLIENT_SECRET=your_google_client_secret");
|
|
581
|
+
lines.push("");
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if (config.authServices.includes("FACEBOOK")) {
|
|
585
|
+
lines.push("# Facebook OAuth");
|
|
586
|
+
lines.push("FACEBOOK_CLIENT_ID=your_facebook_client_id");
|
|
587
|
+
lines.push("FACEBOOK_CLIENT_SECRET=your_facebook_client_secret");
|
|
588
|
+
lines.push("");
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (config.authServices.includes("GITHUB")) {
|
|
592
|
+
lines.push("# GitHub OAuth");
|
|
593
|
+
lines.push("GITHUB_CLIENT_ID=your_github_client_id");
|
|
594
|
+
lines.push("GITHUB_CLIENT_SECRET=your_github_client_secret");
|
|
595
|
+
lines.push("");
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (config.authServices.includes("APPLE")) {
|
|
599
|
+
lines.push("# Apple Sign In");
|
|
600
|
+
lines.push("APPLE_CLIENT_ID=your_apple_client_id");
|
|
601
|
+
lines.push("APPLE_CLIENT_SECRET=your_apple_client_secret");
|
|
602
|
+
lines.push("APPLE_TEAM_ID=your_apple_team_id");
|
|
603
|
+
lines.push("APPLE_KEY_ID=your_apple_key_id");
|
|
604
|
+
lines.push("");
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// CORS section
|
|
608
|
+
lines.push("#-----------------------------------");
|
|
609
|
+
lines.push("# CORS");
|
|
610
|
+
lines.push("#-----------------------------------");
|
|
611
|
+
lines.push('AUTH_CORS_ALLOWED_ORIGINS="http://localhost:3000,http://localhost:8056"');
|
|
612
|
+
lines.push("AUTH_CORS_ALLOW_ANY_PORT=true");
|
|
613
|
+
lines.push("AUTH_CORS_CREDENTIALS=true");
|
|
614
|
+
lines.push("");
|
|
615
|
+
|
|
616
|
+
// Cookies section
|
|
617
|
+
lines.push("#-----------------------------------");
|
|
618
|
+
lines.push("# Cookies");
|
|
619
|
+
lines.push("#-----------------------------------");
|
|
620
|
+
lines.push("AUTH_COOKIE_HTTP_ONLY=true");
|
|
621
|
+
lines.push("AUTH_COOKIE_SECURE=false");
|
|
622
|
+
lines.push("AUTH_COOKIE_SAME_SITE=lax");
|
|
623
|
+
lines.push("AUTH_COOKIE_PATH=/");
|
|
624
|
+
lines.push("");
|
|
625
|
+
|
|
626
|
+
// Mail section
|
|
627
|
+
if (config.mailEnabled) {
|
|
628
|
+
lines.push("#-----------------------------------");
|
|
629
|
+
lines.push("# Mail");
|
|
630
|
+
lines.push("#-----------------------------------");
|
|
631
|
+
lines.push('MAIL_SENDERS_ENABLED="SMTP"');
|
|
632
|
+
lines.push('MAIL_DEFAULT_SENDER="SMTP"');
|
|
633
|
+
lines.push("SEND_WELCOME_EMAIL=true");
|
|
634
|
+
lines.push("");
|
|
635
|
+
lines.push("# SMTP Configuration");
|
|
636
|
+
lines.push("SMTP_SMTP_HOST=smtp.example.com");
|
|
637
|
+
lines.push("SMTP_SMTP_PORT=587");
|
|
638
|
+
lines.push("SMTP_SMTP_SECURE=false");
|
|
639
|
+
lines.push("SMTP_SMTP_USER=your_smtp_user");
|
|
640
|
+
lines.push("SMTP_SMTP_PASS=your_smtp_password");
|
|
641
|
+
lines.push('SMTP_FROM_ADDRESS="Your App" <noreply@example.com>');
|
|
642
|
+
lines.push("");
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// OpenAPI section
|
|
646
|
+
lines.push("#-----------------------------------");
|
|
647
|
+
lines.push("# OpenAPI Documentation");
|
|
648
|
+
lines.push("#-----------------------------------");
|
|
649
|
+
lines.push(`OPENAPI_ENABLED=${config.openApiEnabled}`);
|
|
650
|
+
if (config.openApiEnabled) {
|
|
651
|
+
lines.push("OPENAPI_INCLUDE_AUTH=true");
|
|
652
|
+
lines.push("OPENAPI_INCLUDE_SCHEMA=true");
|
|
653
|
+
lines.push("OPENAPI_INCLUDE_PERMISSIONS=true");
|
|
654
|
+
}
|
|
655
|
+
lines.push("");
|
|
656
|
+
|
|
657
|
+
return lines.join("\n");
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function generateEnvExample(config: ProjectConfig): string {
|
|
661
|
+
const lines: string[] = [];
|
|
662
|
+
|
|
663
|
+
lines.push("# Database (PostgreSQL 14+ required)");
|
|
664
|
+
lines.push('DATABASE_URL="postgresql://username:password@localhost:5432/baasix"');
|
|
665
|
+
lines.push("");
|
|
666
|
+
lines.push("# Server");
|
|
667
|
+
lines.push("PORT=8056");
|
|
668
|
+
lines.push("NODE_ENV=development");
|
|
669
|
+
lines.push("");
|
|
670
|
+
lines.push("# Security (REQUIRED - generate unique keys)");
|
|
671
|
+
lines.push("SECRET_KEY=your-secret-key-minimum-32-characters-long");
|
|
672
|
+
lines.push("");
|
|
673
|
+
lines.push("# Features");
|
|
674
|
+
lines.push(`MULTI_TENANT=${config.multiTenant}`);
|
|
675
|
+
lines.push(`PUBLIC_REGISTRATION=${config.publicRegistration}`);
|
|
676
|
+
lines.push(`SOCKET_ENABLED=${config.socketEnabled}`);
|
|
677
|
+
lines.push("");
|
|
678
|
+
lines.push("# Storage");
|
|
679
|
+
lines.push(`STORAGE_DEFAULT_SERVICE="${config.storageDriver}"`);
|
|
680
|
+
if (config.storageDriver === "LOCAL") {
|
|
681
|
+
lines.push('LOCAL_STORAGE_PATH="./uploads"');
|
|
682
|
+
} else {
|
|
683
|
+
lines.push("S3_STORAGE_ENDPOINT=your-s3-endpoint");
|
|
684
|
+
lines.push("S3_STORAGE_BUCKET=your-bucket-name");
|
|
685
|
+
lines.push("S3_STORAGE_ACCESS_KEY_ID=your-access-key");
|
|
686
|
+
lines.push("S3_STORAGE_SECRET_ACCESS_KEY=your-secret-key");
|
|
687
|
+
}
|
|
688
|
+
lines.push("");
|
|
689
|
+
lines.push("# Cache");
|
|
690
|
+
lines.push(`CACHE_ADAPTER=${config.cacheAdapter}`);
|
|
691
|
+
if (config.cacheAdapter === "redis") {
|
|
692
|
+
lines.push("CACHE_REDIS_URL=redis://localhost:6379");
|
|
693
|
+
}
|
|
694
|
+
lines.push("");
|
|
695
|
+
lines.push("# Auth");
|
|
696
|
+
lines.push(`AUTH_SERVICES_ENABLED=${config.authServices.join(",")}`);
|
|
697
|
+
lines.push("");
|
|
698
|
+
|
|
699
|
+
return lines.join("\n");
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
function generateReadme(config: ProjectConfig): string {
|
|
703
|
+
return `# ${config.projectName}
|
|
704
|
+
|
|
705
|
+
A Baasix Backend-as-a-Service project.
|
|
706
|
+
|
|
707
|
+
## Configuration
|
|
708
|
+
|
|
709
|
+
| Feature | Status |
|
|
710
|
+
|---------|--------|
|
|
711
|
+
| Multi-tenancy | ${config.multiTenant ? "✅ Enabled" : "❌ Disabled"} |
|
|
712
|
+
| Public Registration | ${config.publicRegistration ? "✅ Enabled" : "❌ Disabled"} |
|
|
713
|
+
| Real-time (WebSocket) | ${config.socketEnabled ? "✅ Enabled" : "❌ Disabled"} |
|
|
714
|
+
| Storage | ${config.storageDriver} |
|
|
715
|
+
| Cache | ${config.cacheAdapter} |
|
|
716
|
+
| Auth Methods | ${config.authServices.join(", ")} |
|
|
717
|
+
| OpenAPI Docs | ${config.openApiEnabled ? "✅ Enabled" : "❌ Disabled"} |
|
|
718
|
+
|
|
719
|
+
## Getting Started
|
|
720
|
+
|
|
721
|
+
1. **Configure your database**
|
|
722
|
+
|
|
723
|
+
Edit \`.env\` and verify your PostgreSQL connection:
|
|
724
|
+
\`\`\`
|
|
725
|
+
DATABASE_URL="postgresql://username:password@localhost:5432/baasix"
|
|
726
|
+
\`\`\`
|
|
727
|
+
|
|
728
|
+
2. **Start the server**
|
|
729
|
+
|
|
730
|
+
\`\`\`bash
|
|
731
|
+
npm run dev
|
|
732
|
+
\`\`\`
|
|
733
|
+
|
|
734
|
+
3. **Access the API**
|
|
735
|
+
|
|
736
|
+
- API: http://localhost:8056
|
|
737
|
+
- ${config.openApiEnabled ? "Swagger UI: http://localhost:8056/documentation" : "OpenAPI: Disabled"}
|
|
738
|
+
- Default admin: admin@baasix.com / admin@123
|
|
739
|
+
|
|
740
|
+
## Project Structure
|
|
741
|
+
|
|
742
|
+
\`\`\`
|
|
743
|
+
${config.projectName}/
|
|
744
|
+
├── .env # Environment configuration
|
|
745
|
+
├── .env.example # Example configuration
|
|
746
|
+
├── package.json
|
|
747
|
+
├── server.js # Server entry point
|
|
748
|
+
├── extensions/ # Custom hooks and endpoints
|
|
749
|
+
├── migrations/ # Database migrations
|
|
750
|
+
${config.storageDriver === "LOCAL" ? "└── uploads/ # Local file storage" : ""}
|
|
751
|
+
\`\`\`
|
|
752
|
+
|
|
753
|
+
## Extensions
|
|
754
|
+
|
|
755
|
+
Place your custom hooks and endpoints in the \`extensions/\` directory:
|
|
756
|
+
|
|
757
|
+
- **Endpoint extensions**: Add custom API routes
|
|
758
|
+
- **Hook extensions**: Add lifecycle hooks (before/after CRUD)
|
|
759
|
+
|
|
760
|
+
See [Extensions Documentation](https://baasix.com/docs/extensions) for details.
|
|
761
|
+
|
|
762
|
+
## Migrations
|
|
763
|
+
|
|
764
|
+
\`\`\`bash
|
|
765
|
+
# Create a migration
|
|
766
|
+
npx baasix migrate create -n create_products_table
|
|
767
|
+
|
|
768
|
+
# Run migrations
|
|
769
|
+
npx baasix migrate run
|
|
770
|
+
|
|
771
|
+
# Check status
|
|
772
|
+
npx baasix migrate status
|
|
773
|
+
\`\`\`
|
|
774
|
+
|
|
775
|
+
## Documentation
|
|
776
|
+
|
|
777
|
+
- [Baasix Documentation](https://baasix.com/docs)
|
|
778
|
+
- [SDK Guide](https://baasix.com/docs/sdk-guide)
|
|
779
|
+
- [API Reference](https://baasix.com/docs/api-reference)
|
|
780
|
+
`;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
function generateNextJsEnvContent(config: ProjectConfig): string {
|
|
784
|
+
const lines: string[] = [];
|
|
785
|
+
|
|
786
|
+
// Next.js environment variables (frontend only)
|
|
787
|
+
lines.push("#-----------------------------------");
|
|
788
|
+
lines.push("# Baasix API Connection");
|
|
789
|
+
lines.push("#-----------------------------------");
|
|
790
|
+
lines.push("# URL of your Baasix API server");
|
|
791
|
+
lines.push("NEXT_PUBLIC_BAASIX_URL=http://localhost:8056");
|
|
792
|
+
lines.push("");
|
|
793
|
+
lines.push("# Note: Create a separate Baasix API project using:");
|
|
794
|
+
lines.push("# npx @tspvivek/baasix-cli init --template api");
|
|
795
|
+
lines.push("");
|
|
796
|
+
|
|
797
|
+
return lines.join("\n");
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
async function createNextJsProject(projectPath: string, config: ProjectConfig, useAppRouter: boolean) {
|
|
801
|
+
// package.json - Frontend only, no API dependencies
|
|
802
|
+
const packageJson = {
|
|
803
|
+
name: config.projectName,
|
|
804
|
+
version: "0.1.0",
|
|
805
|
+
private: true,
|
|
806
|
+
scripts: {
|
|
807
|
+
dev: "next dev",
|
|
808
|
+
build: "next build",
|
|
809
|
+
start: "next start",
|
|
810
|
+
lint: "next lint",
|
|
811
|
+
},
|
|
812
|
+
dependencies: {
|
|
813
|
+
"@tspvivek/baasix-sdk": "latest",
|
|
814
|
+
next: "^14.0.0",
|
|
815
|
+
react: "^18.2.0",
|
|
816
|
+
"react-dom": "^18.2.0",
|
|
817
|
+
},
|
|
818
|
+
devDependencies: {
|
|
819
|
+
"@types/node": "^20.0.0",
|
|
820
|
+
"@types/react": "^18.2.0",
|
|
821
|
+
"@types/react-dom": "^18.2.0",
|
|
822
|
+
typescript: "^5.0.0",
|
|
823
|
+
},
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
await fs.writeFile(
|
|
827
|
+
path.join(projectPath, "package.json"),
|
|
828
|
+
JSON.stringify(packageJson, null, 2)
|
|
829
|
+
);
|
|
830
|
+
|
|
831
|
+
// .env.local - Only frontend environment variables
|
|
832
|
+
const envContent = generateNextJsEnvContent(config);
|
|
833
|
+
await fs.writeFile(path.join(projectPath, ".env.local"), envContent);
|
|
834
|
+
|
|
835
|
+
// tsconfig.json
|
|
836
|
+
const tsconfig = {
|
|
837
|
+
compilerOptions: {
|
|
838
|
+
target: "es5",
|
|
839
|
+
lib: ["dom", "dom.iterable", "esnext"],
|
|
840
|
+
allowJs: true,
|
|
841
|
+
skipLibCheck: true,
|
|
842
|
+
strict: true,
|
|
843
|
+
noEmit: true,
|
|
844
|
+
esModuleInterop: true,
|
|
845
|
+
module: "esnext",
|
|
846
|
+
moduleResolution: "bundler",
|
|
847
|
+
resolveJsonModule: true,
|
|
848
|
+
isolatedModules: true,
|
|
849
|
+
jsx: "preserve",
|
|
850
|
+
incremental: true,
|
|
851
|
+
plugins: [{ name: "next" }],
|
|
852
|
+
paths: {
|
|
853
|
+
"@/*": [useAppRouter ? "./src/*" : "./*"],
|
|
854
|
+
},
|
|
855
|
+
},
|
|
856
|
+
include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
857
|
+
exclude: ["node_modules"],
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
await fs.writeFile(
|
|
861
|
+
path.join(projectPath, "tsconfig.json"),
|
|
862
|
+
JSON.stringify(tsconfig, null, 2)
|
|
863
|
+
);
|
|
864
|
+
|
|
865
|
+
// next.config.mjs
|
|
866
|
+
const nextConfig = `/** @type {import('next').NextConfig} */
|
|
867
|
+
const nextConfig = {
|
|
868
|
+
reactStrictMode: true,
|
|
869
|
+
};
|
|
870
|
+
|
|
871
|
+
export default nextConfig;
|
|
872
|
+
`;
|
|
873
|
+
|
|
874
|
+
await fs.writeFile(path.join(projectPath, "next.config.mjs"), nextConfig);
|
|
875
|
+
|
|
876
|
+
if (useAppRouter) {
|
|
877
|
+
// App Router structure
|
|
878
|
+
await fs.mkdir(path.join(projectPath, "src", "app"), { recursive: true });
|
|
879
|
+
await fs.mkdir(path.join(projectPath, "src", "lib"), { recursive: true });
|
|
880
|
+
|
|
881
|
+
// src/lib/baasix.ts - SDK client
|
|
882
|
+
const baasixClient = `import { createBaasix } from "@tspvivek/baasix-sdk";
|
|
883
|
+
|
|
884
|
+
export const baasix = createBaasix({
|
|
885
|
+
url: process.env.NEXT_PUBLIC_BAASIX_URL || "http://localhost:8056",
|
|
886
|
+
authMode: "jwt",
|
|
887
|
+
autoRefresh: true,
|
|
888
|
+
});
|
|
889
|
+
|
|
890
|
+
// Re-export for convenience
|
|
891
|
+
export type { User, Role, QueryParams, Filter } from "@tspvivek/baasix-sdk";
|
|
892
|
+
`;
|
|
893
|
+
|
|
894
|
+
await fs.writeFile(path.join(projectPath, "src", "lib", "baasix.ts"), baasixClient);
|
|
895
|
+
|
|
896
|
+
// src/app/layout.tsx
|
|
897
|
+
const layout = `import type { Metadata } from "next";
|
|
898
|
+
import "./globals.css";
|
|
899
|
+
|
|
900
|
+
export const metadata: Metadata = {
|
|
901
|
+
title: "${config.projectName}",
|
|
902
|
+
description: "Built with Baasix",
|
|
903
|
+
};
|
|
904
|
+
|
|
905
|
+
export default function RootLayout({
|
|
906
|
+
children,
|
|
907
|
+
}: {
|
|
908
|
+
children: React.ReactNode;
|
|
909
|
+
}) {
|
|
910
|
+
return (
|
|
911
|
+
<html lang="en">
|
|
912
|
+
<body>{children}</body>
|
|
913
|
+
</html>
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
`;
|
|
917
|
+
|
|
918
|
+
await fs.writeFile(path.join(projectPath, "src", "app", "layout.tsx"), layout);
|
|
919
|
+
|
|
920
|
+
// src/app/globals.css
|
|
921
|
+
const globalsCss = `* {
|
|
922
|
+
box-sizing: border-box;
|
|
923
|
+
padding: 0;
|
|
924
|
+
margin: 0;
|
|
925
|
+
}
|
|
926
|
+
|
|
927
|
+
html,
|
|
928
|
+
body {
|
|
929
|
+
max-width: 100vw;
|
|
930
|
+
overflow-x: hidden;
|
|
931
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
body {
|
|
935
|
+
background: #0a0a0a;
|
|
936
|
+
color: #ededed;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
a {
|
|
940
|
+
color: #0070f3;
|
|
941
|
+
text-decoration: none;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
a:hover {
|
|
945
|
+
text-decoration: underline;
|
|
946
|
+
}
|
|
947
|
+
`;
|
|
948
|
+
|
|
949
|
+
await fs.writeFile(path.join(projectPath, "src", "app", "globals.css"), globalsCss);
|
|
950
|
+
|
|
951
|
+
// src/app/page.tsx
|
|
952
|
+
const page = `"use client";
|
|
953
|
+
|
|
954
|
+
import { useState, useEffect } from "react";
|
|
955
|
+
import { baasix, type User } from "@/lib/baasix";
|
|
956
|
+
|
|
957
|
+
export default function Home() {
|
|
958
|
+
const [user, setUser] = useState<User | null>(null);
|
|
959
|
+
const [loading, setLoading] = useState(true);
|
|
960
|
+
const [error, setError] = useState<string | null>(null);
|
|
961
|
+
|
|
962
|
+
useEffect(() => {
|
|
963
|
+
baasix.auth.getCachedUser().then((u) => {
|
|
964
|
+
setUser(u);
|
|
965
|
+
setLoading(false);
|
|
966
|
+
}).catch(() => {
|
|
967
|
+
setLoading(false);
|
|
968
|
+
});
|
|
969
|
+
}, []);
|
|
970
|
+
|
|
971
|
+
const handleLogin = async () => {
|
|
972
|
+
setError(null);
|
|
973
|
+
try {
|
|
974
|
+
const { user } = await baasix.auth.login({
|
|
975
|
+
email: "admin@baasix.com",
|
|
976
|
+
password: "admin@123",
|
|
977
|
+
});
|
|
978
|
+
setUser(user);
|
|
979
|
+
} catch (err) {
|
|
980
|
+
setError("Login failed. Make sure your Baasix API server is running.");
|
|
981
|
+
console.error("Login failed:", err);
|
|
982
|
+
}
|
|
983
|
+
};
|
|
984
|
+
|
|
985
|
+
const handleLogout = async () => {
|
|
986
|
+
await baasix.auth.logout();
|
|
987
|
+
setUser(null);
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
if (loading) {
|
|
991
|
+
return (
|
|
992
|
+
<main style={{ padding: "2rem", textAlign: "center" }}>
|
|
993
|
+
<p>Loading...</p>
|
|
994
|
+
</main>
|
|
995
|
+
);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
return (
|
|
999
|
+
<main style={{ padding: "2rem", maxWidth: "800px", margin: "0 auto" }}>
|
|
1000
|
+
<h1 style={{ marginBottom: "1rem" }}>🚀 ${config.projectName}</h1>
|
|
1001
|
+
<p style={{ marginBottom: "2rem", color: "#888" }}>
|
|
1002
|
+
Next.js Frontend with Baasix SDK
|
|
1003
|
+
</p>
|
|
1004
|
+
|
|
1005
|
+
{error && (
|
|
1006
|
+
<div style={{ padding: "1rem", background: "#3a1a1a", borderRadius: "8px", marginBottom: "1rem", color: "#ff6b6b" }}>
|
|
1007
|
+
{error}
|
|
1008
|
+
</div>
|
|
1009
|
+
)}
|
|
1010
|
+
|
|
1011
|
+
{user ? (
|
|
1012
|
+
<div>
|
|
1013
|
+
<p style={{ marginBottom: "1rem" }}>
|
|
1014
|
+
Welcome, <strong>{user.email}</strong>!
|
|
1015
|
+
</p>
|
|
1016
|
+
<button
|
|
1017
|
+
onClick={handleLogout}
|
|
1018
|
+
style={{
|
|
1019
|
+
padding: "0.5rem 1rem",
|
|
1020
|
+
background: "#333",
|
|
1021
|
+
color: "#fff",
|
|
1022
|
+
border: "none",
|
|
1023
|
+
borderRadius: "4px",
|
|
1024
|
+
cursor: "pointer",
|
|
1025
|
+
}}
|
|
1026
|
+
>
|
|
1027
|
+
Logout
|
|
1028
|
+
</button>
|
|
1029
|
+
</div>
|
|
1030
|
+
) : (
|
|
1031
|
+
<div>
|
|
1032
|
+
<p style={{ marginBottom: "1rem" }}>Not logged in</p>
|
|
1033
|
+
<button
|
|
1034
|
+
onClick={handleLogin}
|
|
1035
|
+
style={{
|
|
1036
|
+
padding: "0.5rem 1rem",
|
|
1037
|
+
background: "#0070f3",
|
|
1038
|
+
color: "#fff",
|
|
1039
|
+
border: "none",
|
|
1040
|
+
borderRadius: "4px",
|
|
1041
|
+
cursor: "pointer",
|
|
1042
|
+
}}
|
|
1043
|
+
>
|
|
1044
|
+
Login as Admin
|
|
1045
|
+
</button>
|
|
1046
|
+
</div>
|
|
1047
|
+
)}
|
|
1048
|
+
|
|
1049
|
+
<div style={{ marginTop: "3rem", padding: "1rem", background: "#111", borderRadius: "8px" }}>
|
|
1050
|
+
<h2 style={{ marginBottom: "0.5rem", fontSize: "1.2rem" }}>Getting Started</h2>
|
|
1051
|
+
<p style={{ marginBottom: "1rem", color: "#888", fontSize: "0.9rem" }}>
|
|
1052
|
+
This is a frontend-only Next.js app. You need a separate Baasix API server.
|
|
1053
|
+
</p>
|
|
1054
|
+
<ol style={{ paddingLeft: "1.5rem", lineHeight: "1.8" }}>
|
|
1055
|
+
<li>Create a Baasix API project: <code>npx @tspvivek/baasix-cli init --template api</code></li>
|
|
1056
|
+
<li>Start the API server: <code>cd your-api && npm run dev</code></li>
|
|
1057
|
+
<li>Update <code>.env.local</code> with your API URL if needed</li>
|
|
1058
|
+
<li>Start this Next.js app: <code>npm run dev</code></li>
|
|
1059
|
+
</ol>
|
|
1060
|
+
</div>
|
|
1061
|
+
|
|
1062
|
+
<div style={{ marginTop: "1.5rem", padding: "1rem", background: "#111", borderRadius: "8px" }}>
|
|
1063
|
+
<h2 style={{ marginBottom: "0.5rem", fontSize: "1.2rem" }}>API Connection</h2>
|
|
1064
|
+
<p style={{ color: "#888", fontSize: "0.9rem" }}>
|
|
1065
|
+
Currently configured to connect to: <code>{process.env.NEXT_PUBLIC_BAASIX_URL || "http://localhost:8056"}</code>
|
|
1066
|
+
</p>
|
|
1067
|
+
</div>
|
|
1068
|
+
</main>
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
`;
|
|
1072
|
+
|
|
1073
|
+
await fs.writeFile(path.join(projectPath, "src", "app", "page.tsx"), page);
|
|
1074
|
+
|
|
1075
|
+
} else {
|
|
1076
|
+
// Pages Router structure
|
|
1077
|
+
await fs.mkdir(path.join(projectPath, "pages"), { recursive: true });
|
|
1078
|
+
await fs.mkdir(path.join(projectPath, "lib"), { recursive: true });
|
|
1079
|
+
await fs.mkdir(path.join(projectPath, "styles"), { recursive: true });
|
|
1080
|
+
|
|
1081
|
+
// lib/baasix.ts
|
|
1082
|
+
const baasixClient = `import { createBaasix } from "@tspvivek/baasix-sdk";
|
|
1083
|
+
|
|
1084
|
+
export const baasix = createBaasix({
|
|
1085
|
+
url: process.env.NEXT_PUBLIC_BAASIX_URL || "http://localhost:8056",
|
|
1086
|
+
authMode: "jwt",
|
|
1087
|
+
autoRefresh: true,
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
export type { User, Role, QueryParams, Filter } from "@tspvivek/baasix-sdk";
|
|
1091
|
+
`;
|
|
1092
|
+
|
|
1093
|
+
await fs.writeFile(path.join(projectPath, "lib", "baasix.ts"), baasixClient);
|
|
1094
|
+
|
|
1095
|
+
// pages/_app.tsx
|
|
1096
|
+
const app = `import type { AppProps } from "next/app";
|
|
1097
|
+
import "@/styles/globals.css";
|
|
1098
|
+
|
|
1099
|
+
export default function App({ Component, pageProps }: AppProps) {
|
|
1100
|
+
return <Component {...pageProps} />;
|
|
1101
|
+
}
|
|
1102
|
+
`;
|
|
1103
|
+
|
|
1104
|
+
await fs.writeFile(path.join(projectPath, "pages", "_app.tsx"), app);
|
|
1105
|
+
|
|
1106
|
+
// styles/globals.css
|
|
1107
|
+
const globalsCss = `* {
|
|
1108
|
+
box-sizing: border-box;
|
|
1109
|
+
padding: 0;
|
|
1110
|
+
margin: 0;
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
html,
|
|
1114
|
+
body {
|
|
1115
|
+
max-width: 100vw;
|
|
1116
|
+
overflow-x: hidden;
|
|
1117
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
body {
|
|
1121
|
+
background: #0a0a0a;
|
|
1122
|
+
color: #ededed;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1125
|
+
a {
|
|
1126
|
+
color: #0070f3;
|
|
1127
|
+
text-decoration: none;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
a:hover {
|
|
1131
|
+
text-decoration: underline;
|
|
1132
|
+
}
|
|
1133
|
+
`;
|
|
1134
|
+
|
|
1135
|
+
await fs.writeFile(path.join(projectPath, "styles", "globals.css"), globalsCss);
|
|
1136
|
+
|
|
1137
|
+
// pages/index.tsx - Frontend only with SDK
|
|
1138
|
+
const index = `import { useState, useEffect } from "react";
|
|
1139
|
+
import { baasix, type User } from "@/lib/baasix";
|
|
1140
|
+
|
|
1141
|
+
export default function Home() {
|
|
1142
|
+
const [user, setUser] = useState<User | null>(null);
|
|
1143
|
+
const [loading, setLoading] = useState(true);
|
|
1144
|
+
const [error, setError] = useState<string | null>(null);
|
|
1145
|
+
|
|
1146
|
+
useEffect(() => {
|
|
1147
|
+
baasix.auth.getCachedUser().then((u) => {
|
|
1148
|
+
setUser(u);
|
|
1149
|
+
setLoading(false);
|
|
1150
|
+
}).catch(() => {
|
|
1151
|
+
setLoading(false);
|
|
1152
|
+
});
|
|
1153
|
+
}, []);
|
|
1154
|
+
|
|
1155
|
+
const handleLogin = async () => {
|
|
1156
|
+
setError(null);
|
|
1157
|
+
try {
|
|
1158
|
+
const { user } = await baasix.auth.login({
|
|
1159
|
+
email: "admin@baasix.com",
|
|
1160
|
+
password: "admin@123",
|
|
1161
|
+
});
|
|
1162
|
+
setUser(user);
|
|
1163
|
+
} catch (err) {
|
|
1164
|
+
setError("Login failed. Make sure your Baasix API server is running.");
|
|
1165
|
+
console.error("Login failed:", err);
|
|
1166
|
+
}
|
|
1167
|
+
};
|
|
1168
|
+
|
|
1169
|
+
const handleLogout = async () => {
|
|
1170
|
+
await baasix.auth.logout();
|
|
1171
|
+
setUser(null);
|
|
1172
|
+
};
|
|
1173
|
+
|
|
1174
|
+
if (loading) {
|
|
1175
|
+
return (
|
|
1176
|
+
<main style={{ padding: "2rem", textAlign: "center" }}>
|
|
1177
|
+
<p>Loading...</p>
|
|
1178
|
+
</main>
|
|
1179
|
+
);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
return (
|
|
1183
|
+
<main style={{ padding: "2rem", maxWidth: "800px", margin: "0 auto" }}>
|
|
1184
|
+
<h1 style={{ marginBottom: "1rem" }}>🚀 ${config.projectName}</h1>
|
|
1185
|
+
<p style={{ marginBottom: "2rem", color: "#888" }}>
|
|
1186
|
+
Next.js Frontend with Baasix SDK
|
|
1187
|
+
</p>
|
|
1188
|
+
|
|
1189
|
+
{error && (
|
|
1190
|
+
<div style={{ padding: "1rem", background: "#3a1a1a", borderRadius: "8px", marginBottom: "1rem", color: "#ff6b6b" }}>
|
|
1191
|
+
{error}
|
|
1192
|
+
</div>
|
|
1193
|
+
)}
|
|
1194
|
+
|
|
1195
|
+
{user ? (
|
|
1196
|
+
<div>
|
|
1197
|
+
<p style={{ marginBottom: "1rem" }}>
|
|
1198
|
+
Welcome, <strong>{user.email}</strong>!
|
|
1199
|
+
</p>
|
|
1200
|
+
<button
|
|
1201
|
+
onClick={handleLogout}
|
|
1202
|
+
style={{
|
|
1203
|
+
padding: "0.5rem 1rem",
|
|
1204
|
+
background: "#333",
|
|
1205
|
+
color: "#fff",
|
|
1206
|
+
border: "none",
|
|
1207
|
+
borderRadius: "4px",
|
|
1208
|
+
cursor: "pointer",
|
|
1209
|
+
}}
|
|
1210
|
+
>
|
|
1211
|
+
Logout
|
|
1212
|
+
</button>
|
|
1213
|
+
</div>
|
|
1214
|
+
) : (
|
|
1215
|
+
<div>
|
|
1216
|
+
<p style={{ marginBottom: "1rem" }}>Not logged in</p>
|
|
1217
|
+
<button
|
|
1218
|
+
onClick={handleLogin}
|
|
1219
|
+
style={{
|
|
1220
|
+
padding: "0.5rem 1rem",
|
|
1221
|
+
background: "#0070f3",
|
|
1222
|
+
color: "#fff",
|
|
1223
|
+
border: "none",
|
|
1224
|
+
borderRadius: "4px",
|
|
1225
|
+
cursor: "pointer",
|
|
1226
|
+
}}
|
|
1227
|
+
>
|
|
1228
|
+
Login as Admin
|
|
1229
|
+
</button>
|
|
1230
|
+
</div>
|
|
1231
|
+
)}
|
|
1232
|
+
|
|
1233
|
+
<div style={{ marginTop: "3rem", padding: "1rem", background: "#111", borderRadius: "8px" }}>
|
|
1234
|
+
<h2 style={{ marginBottom: "0.5rem", fontSize: "1.2rem" }}>Getting Started</h2>
|
|
1235
|
+
<p style={{ marginBottom: "1rem", color: "#888", fontSize: "0.9rem" }}>
|
|
1236
|
+
This is a frontend-only Next.js app. You need a separate Baasix API server.
|
|
1237
|
+
</p>
|
|
1238
|
+
<ol style={{ paddingLeft: "1.5rem", lineHeight: "1.8" }}>
|
|
1239
|
+
<li>Create a Baasix API project: <code>npx @tspvivek/baasix-cli init --template api</code></li>
|
|
1240
|
+
<li>Start the API server: <code>cd your-api && npm run dev</code></li>
|
|
1241
|
+
<li>Update <code>.env.local</code> with your API URL if needed</li>
|
|
1242
|
+
<li>Start this Next.js app: <code>npm run dev</code></li>
|
|
1243
|
+
</ol>
|
|
1244
|
+
</div>
|
|
1245
|
+
|
|
1246
|
+
<div style={{ marginTop: "1.5rem", padding: "1rem", background: "#111", borderRadius: "8px" }}>
|
|
1247
|
+
<h2 style={{ marginBottom: "0.5rem", fontSize: "1.2rem" }}>API Connection</h2>
|
|
1248
|
+
<p style={{ color: "#888", fontSize: "0.9rem" }}>
|
|
1249
|
+
Currently configured to connect to: <code>{process.env.NEXT_PUBLIC_BAASIX_URL || "http://localhost:8056"}</code>
|
|
1250
|
+
</p>
|
|
1251
|
+
</div>
|
|
1252
|
+
</main>
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
1255
|
+
`;
|
|
1256
|
+
|
|
1257
|
+
await fs.writeFile(path.join(projectPath, "pages", "index.tsx"), index);
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
// .gitignore - No api/ folder references since this is frontend-only
|
|
1261
|
+
const gitignore = `# Dependencies
|
|
1262
|
+
node_modules/
|
|
1263
|
+
.pnp
|
|
1264
|
+
.pnp.js
|
|
1265
|
+
|
|
1266
|
+
# Testing
|
|
1267
|
+
coverage/
|
|
1268
|
+
|
|
1269
|
+
# Next.js
|
|
1270
|
+
.next/
|
|
1271
|
+
out/
|
|
1272
|
+
build/
|
|
1273
|
+
|
|
1274
|
+
# Environment
|
|
1275
|
+
.env
|
|
1276
|
+
.env.local
|
|
1277
|
+
.env.development.local
|
|
1278
|
+
.env.test.local
|
|
1279
|
+
.env.production.local
|
|
1280
|
+
|
|
1281
|
+
# Misc
|
|
1282
|
+
.DS_Store
|
|
1283
|
+
*.pem
|
|
1284
|
+
npm-debug.log*
|
|
1285
|
+
yarn-debug.log*
|
|
1286
|
+
yarn-error.log*
|
|
1287
|
+
|
|
1288
|
+
# Vercel
|
|
1289
|
+
.vercel
|
|
1290
|
+
|
|
1291
|
+
# TypeScript
|
|
1292
|
+
*.tsbuildinfo
|
|
1293
|
+
next-env.d.ts
|
|
1294
|
+
`;
|
|
1295
|
+
|
|
1296
|
+
await fs.writeFile(path.join(projectPath, ".gitignore"), gitignore);
|
|
1297
|
+
|
|
1298
|
+
// README.md - Frontend-only documentation
|
|
1299
|
+
const readme = `# ${config.projectName}
|
|
1300
|
+
|
|
1301
|
+
A Next.js frontend project that connects to a Baasix API server using the SDK.
|
|
1302
|
+
|
|
1303
|
+
## Architecture
|
|
1304
|
+
|
|
1305
|
+
This is a **frontend-only** project. You need a separate Baasix API server running.
|
|
1306
|
+
|
|
1307
|
+
\`\`\`
|
|
1308
|
+
┌─────────────────┐ HTTP/WS ┌─────────────────┐
|
|
1309
|
+
│ Next.js App │ ◄──────────────► │ Baasix API │
|
|
1310
|
+
│ (Frontend) │ via SDK │ (Backend) │
|
|
1311
|
+
└─────────────────┘ └─────────────────┘
|
|
1312
|
+
Port 3000 Port 8056
|
|
1313
|
+
\`\`\`
|
|
1314
|
+
|
|
1315
|
+
## Getting Started
|
|
1316
|
+
|
|
1317
|
+
### 1. Start your Baasix API Server
|
|
1318
|
+
|
|
1319
|
+
If you don't have a Baasix API project yet, create one:
|
|
1320
|
+
|
|
1321
|
+
\`\`\`bash
|
|
1322
|
+
npx @tspvivek/baasix-cli init --template api my-api
|
|
1323
|
+
cd my-api
|
|
1324
|
+
npm install
|
|
1325
|
+
npm run dev
|
|
1326
|
+
\`\`\`
|
|
1327
|
+
|
|
1328
|
+
### 2. Configure this Frontend
|
|
1329
|
+
|
|
1330
|
+
Update \`.env.local\` if your API is running on a different URL:
|
|
1331
|
+
|
|
1332
|
+
\`\`\`
|
|
1333
|
+
NEXT_PUBLIC_BAASIX_URL=http://localhost:8056
|
|
1334
|
+
\`\`\`
|
|
1335
|
+
|
|
1336
|
+
### 3. Start the Frontend
|
|
1337
|
+
|
|
1338
|
+
\`\`\`bash
|
|
1339
|
+
npm install
|
|
1340
|
+
npm run dev
|
|
1341
|
+
\`\`\`
|
|
1342
|
+
|
|
1343
|
+
### 4. Open your browser
|
|
1344
|
+
|
|
1345
|
+
- Frontend: http://localhost:3000
|
|
1346
|
+
|
|
1347
|
+
## Default Admin Credentials
|
|
1348
|
+
|
|
1349
|
+
Use these credentials to login (configured in your API server):
|
|
1350
|
+
|
|
1351
|
+
- Email: admin@baasix.com
|
|
1352
|
+
- Password: admin@123
|
|
1353
|
+
|
|
1354
|
+
## Project Structure
|
|
1355
|
+
|
|
1356
|
+
\`\`\`
|
|
1357
|
+
${config.projectName}/
|
|
1358
|
+
├── .env.local # API URL configuration
|
|
1359
|
+
├── package.json
|
|
1360
|
+
${useAppRouter ? `├── src/
|
|
1361
|
+
│ ├── app/ # Next.js App Router pages
|
|
1362
|
+
│ │ ├── layout.tsx
|
|
1363
|
+
│ │ ├── page.tsx
|
|
1364
|
+
│ │ └── globals.css
|
|
1365
|
+
│ └── lib/
|
|
1366
|
+
│ └── baasix.ts # SDK client` : `├── pages/ # Next.js Pages Router
|
|
1367
|
+
│ ├── _app.tsx
|
|
1368
|
+
│ └── index.tsx
|
|
1369
|
+
├── lib/
|
|
1370
|
+
│ └── baasix.ts # SDK client
|
|
1371
|
+
└── styles/
|
|
1372
|
+
└── globals.css`}
|
|
1373
|
+
\`\`\`
|
|
1374
|
+
|
|
1375
|
+
## SDK Usage
|
|
1376
|
+
|
|
1377
|
+
The SDK is pre-configured in \`${useAppRouter ? 'src/lib/baasix.ts' : 'lib/baasix.ts'}\`:
|
|
1378
|
+
|
|
1379
|
+
\`\`\`typescript
|
|
1380
|
+
import { baasix } from "${useAppRouter ? '@/lib/baasix' : '@/lib/baasix'}";
|
|
1381
|
+
|
|
1382
|
+
// Authentication
|
|
1383
|
+
const { user } = await baasix.auth.login({ email, password });
|
|
1384
|
+
await baasix.auth.logout();
|
|
1385
|
+
|
|
1386
|
+
// CRUD operations
|
|
1387
|
+
const items = await baasix.items("posts").list();
|
|
1388
|
+
const item = await baasix.items("posts").create({ title: "Hello" });
|
|
1389
|
+
await baasix.items("posts").update(id, { title: "Updated" });
|
|
1390
|
+
await baasix.items("posts").delete(id);
|
|
1391
|
+
\`\`\`
|
|
1392
|
+
|
|
1393
|
+
## Documentation
|
|
1394
|
+
|
|
1395
|
+
- [Baasix Documentation](https://baasix.com/docs)
|
|
1396
|
+
- [SDK Guide](https://baasix.com/docs/sdk-guide)
|
|
1397
|
+
- [Next.js Documentation](https://nextjs.org/docs)
|
|
1398
|
+
`;
|
|
1399
|
+
|
|
1400
|
+
await fs.writeFile(path.join(projectPath, "README.md"), readme);
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
export const init = new Command("init")
|
|
1404
|
+
.description("Initialize a new Baasix project")
|
|
1405
|
+
.option("-c, --cwd <path>", "Working directory", process.cwd())
|
|
1406
|
+
.option("-t, --template <template>", "Project template (api, nextjs, nextjs-app)")
|
|
1407
|
+
.option("-n, --name <name>", "Project name")
|
|
1408
|
+
.option("-y, --yes", "Skip confirmation prompts")
|
|
1409
|
+
.action(initAction);
|