deploy-bbc 1.1.0 → 1.2.1

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.
Files changed (55) hide show
  1. package/.cspell.json +38 -0
  2. package/CLAUDE.md +85 -11
  3. package/README.md +358 -0
  4. package/cli/package.json +9 -9
  5. package/cli/src/cli/index.ts +8 -3
  6. package/cli/src/helpers/generate-docker-compose.ts +1 -1
  7. package/cli/src/helpers/generate-dockerfile.ts +1 -1
  8. package/cli/src/helpers/log-next-steps.ts +23 -20
  9. package/cli/src/helpers/scaffold-project.ts +30 -4
  10. package/cli/src/installers/auth.ts +1 -1
  11. package/cli/src/templates/base/.env.example +1 -1
  12. package/cli/src/templates/base/package.json +2 -1
  13. package/cli/src/templates/base/src/config/index.ts +1 -1
  14. package/cli/src/templates/base/src/controllers/user/create-user.controller.ts +84 -0
  15. package/cli/src/templates/base/src/controllers/user/delete-user.controller.ts +60 -0
  16. package/cli/src/templates/base/src/controllers/user/get-user.controller.ts +74 -0
  17. package/cli/src/templates/base/src/controllers/user/get-users.controller.ts +41 -0
  18. package/cli/src/templates/base/src/controllers/user/update-user.controller.ts +111 -0
  19. package/cli/src/templates/base/src/models/user.model.ts +83 -0
  20. package/cli/src/templates/base/src/routes/index.ts +5 -0
  21. package/cli/src/templates/base/src/routes/user.route.ts +17 -0
  22. package/cli/src/templates/base/src/types/common/api-response.types.ts +30 -0
  23. package/cli/src/templates/base/src/types/index.ts +5 -2
  24. package/cli/src/templates/base/src/types/models/user.types.ts +26 -0
  25. package/cli/src/templates/base-bun-native/.env.example +1 -1
  26. package/cli/src/templates/base-bun-native/package.json +2 -1
  27. package/cli/src/templates/base-bun-native/src/config/index.ts +1 -1
  28. package/cli/src/templates/base-bun-native/src/controllers/user/create-user.controller.ts +76 -0
  29. package/cli/src/templates/base-bun-native/src/controllers/user/delete-user.controller.ts +53 -0
  30. package/cli/src/templates/base-bun-native/src/controllers/user/get-user.controller.ts +67 -0
  31. package/cli/src/templates/base-bun-native/src/controllers/user/get-users.controller.ts +40 -0
  32. package/cli/src/templates/base-bun-native/src/controllers/user/update-user.controller.ts +101 -0
  33. package/cli/src/templates/base-bun-native/src/index.ts +3 -3
  34. package/cli/src/templates/base-bun-native/src/models/user.model.ts +83 -0
  35. package/cli/src/templates/base-bun-native/src/routes/index.ts +10 -6
  36. package/cli/src/templates/base-bun-native/src/routes/user.route.ts +50 -0
  37. package/cli/src/templates/base-bun-native/src/types/common/api-response.types.ts +30 -0
  38. package/cli/src/templates/base-bun-native/src/types/index.ts +5 -2
  39. package/cli/src/templates/base-bun-native/src/types/models/user.types.ts +26 -0
  40. package/cli/src/templates/base-express/.env.example +1 -1
  41. package/cli/src/templates/base-express/package.json +2 -1
  42. package/cli/src/templates/base-express/src/config/index.ts +1 -1
  43. package/cli/src/templates/base-express/src/controllers/user/create-user.controller.ts +75 -0
  44. package/cli/src/templates/base-express/src/controllers/user/delete-user.controller.ts +56 -0
  45. package/cli/src/templates/base-express/src/controllers/user/get-user.controller.ts +70 -0
  46. package/cli/src/templates/base-express/src/controllers/user/get-users.controller.ts +41 -0
  47. package/cli/src/templates/base-express/src/controllers/user/update-user.controller.ts +102 -0
  48. package/cli/src/templates/base-express/src/models/user.model.ts +83 -0
  49. package/cli/src/templates/base-express/src/routes/index.ts +5 -0
  50. package/cli/src/templates/base-express/src/routes/user.route.ts +17 -0
  51. package/cli/src/templates/base-express/src/types/common/api-response.types.ts +30 -0
  52. package/cli/src/templates/base-express/src/types/index.ts +5 -2
  53. package/cli/src/templates/base-express/src/types/models/user.types.ts +26 -0
  54. package/cli/src/utils/parse-name-and-path.ts +18 -2
  55. package/package.json +3 -3
@@ -10,8 +10,9 @@
10
10
  "type-check": "tsc --noEmit"
11
11
  },
12
12
  "dependencies": {
13
+ "dotenv": "^16.3.1",
13
14
  "express": "^4.18.2",
14
- "dotenv": "^16.3.1"
15
+ "zod": "^3.22.4"
15
16
  },
16
17
  "devDependencies": {
17
18
  "@types/bun": "latest",
@@ -3,6 +3,6 @@ import { load_env } from "../utils/env.js";
3
3
  load_env();
4
4
 
5
5
  export const config = {
6
- port: parseInt(process.env.PORT || "3000", 10),
6
+ port: parseInt(process.env.PORT || "8000", 10),
7
7
  nodeEnv: process.env.NODE_ENV || "development",
8
8
  } as const;
@@ -0,0 +1,75 @@
1
+ import type { Request, Response } from "express";
2
+ import { z } from "zod";
3
+ import { user_model } from "../../models/user.model.js";
4
+ import type { ApiResponse, ApiError } from "../../types/common/api-response.types.js";
5
+ import type { UserResponse } from "../../types/models/user.types.js";
6
+
7
+ /**
8
+ * Validation schema
9
+ */
10
+ const create_user_schema = z.object({
11
+ email: z.string().email("Invalid email address"),
12
+ name: z.string().min(2, "Name must be at least 2 characters").max(100, "Name must not exceed 100 characters"),
13
+ });
14
+
15
+ /**
16
+ * Helper to format user response
17
+ */
18
+ function format_user_response(user: any): UserResponse {
19
+ return {
20
+ id: user.id,
21
+ email: user.email,
22
+ name: user.name,
23
+ createdAt: user.createdAt.toISOString(),
24
+ updatedAt: user.updatedAt.toISOString(),
25
+ };
26
+ }
27
+
28
+ /**
29
+ * Create a new user
30
+ * POST /users
31
+ */
32
+ export async function create_user(req: Request, res: Response) {
33
+ try {
34
+ // Validate request body
35
+ const validation = create_user_schema.safeParse(req.body);
36
+ if (!validation.success) {
37
+ const errorResponse: ApiError = {
38
+ success: false,
39
+ error: "Validation failed",
40
+ details: validation.error.errors.map((err) => ({
41
+ field: err.path.join("."),
42
+ message: err.message,
43
+ })),
44
+ };
45
+ return res.status(400).json(errorResponse);
46
+ }
47
+
48
+ // Check if email already exists
49
+ const existing_user = await user_model.find_by_email(validation.data.email);
50
+ if (existing_user) {
51
+ const errorResponse: ApiError = {
52
+ success: false,
53
+ error: "User with this email already exists",
54
+ };
55
+ return res.status(409).json(errorResponse);
56
+ }
57
+
58
+ // Create user
59
+ const user = await user_model.create(validation.data);
60
+
61
+ const response: ApiResponse<UserResponse> = {
62
+ success: true,
63
+ data: format_user_response(user),
64
+ message: "User created successfully",
65
+ };
66
+
67
+ res.status(201).json(response);
68
+ } catch (error) {
69
+ const errorResponse: ApiError = {
70
+ success: false,
71
+ error: "Failed to create user",
72
+ };
73
+ res.status(500).json(errorResponse);
74
+ }
75
+ }
@@ -0,0 +1,56 @@
1
+ import type { Request, Response } from "express";
2
+ import { z } from "zod";
3
+ import { user_model } from "../../models/user.model.js";
4
+ import type { ApiResponse, ApiError } from "../../types/common/api-response.types.js";
5
+
6
+ /**
7
+ * Validation schema
8
+ */
9
+ const user_id_schema = z.string().uuid("Invalid user ID format");
10
+
11
+ /**
12
+ * Delete user by ID
13
+ * DELETE /users/:id
14
+ */
15
+ export async function delete_user(req: Request, res: Response) {
16
+ try {
17
+ const { id } = req.params;
18
+
19
+ // Validate ID format
20
+ const validation = user_id_schema.safeParse(id);
21
+ if (!validation.success) {
22
+ const errorResponse: ApiError = {
23
+ success: false,
24
+ error: "Invalid user ID",
25
+ details: validation.error.errors.map((err) => ({
26
+ field: err.path.join("."),
27
+ message: err.message,
28
+ })),
29
+ };
30
+ return res.status(400).json(errorResponse);
31
+ }
32
+
33
+ const deleted = await user_model.delete(id);
34
+
35
+ if (!deleted) {
36
+ const errorResponse: ApiError = {
37
+ success: false,
38
+ error: "User not found",
39
+ };
40
+ return res.status(404).json(errorResponse);
41
+ }
42
+
43
+ const response: ApiResponse = {
44
+ success: true,
45
+ message: "User deleted successfully",
46
+ };
47
+
48
+ res.json(response);
49
+ } catch (error) {
50
+ const errorResponse: ApiError = {
51
+ success: false,
52
+ error: "Failed to delete user",
53
+ };
54
+ res.status(500).json(errorResponse);
55
+ }
56
+ }
@@ -0,0 +1,70 @@
1
+ import type { Request, Response } from "express";
2
+ import { z } from "zod";
3
+ import { user_model } from "../../models/user.model.js";
4
+ import type { ApiResponse, ApiError } from "../../types/common/api-response.types.js";
5
+ import type { UserResponse } from "../../types/models/user.types.js";
6
+
7
+ /**
8
+ * Validation schema
9
+ */
10
+ const user_id_schema = z.string().uuid("Invalid user ID format");
11
+
12
+ /**
13
+ * Helper to format user response
14
+ */
15
+ function format_user_response(user: any): UserResponse {
16
+ return {
17
+ id: user.id,
18
+ email: user.email,
19
+ name: user.name,
20
+ createdAt: user.createdAt.toISOString(),
21
+ updatedAt: user.updatedAt.toISOString(),
22
+ };
23
+ }
24
+
25
+ /**
26
+ * Get user by ID
27
+ * GET /users/:id
28
+ */
29
+ export async function get_user(req: Request, res: Response) {
30
+ try {
31
+ const { id } = req.params;
32
+
33
+ // Validate ID format
34
+ const validation = user_id_schema.safeParse(id);
35
+ if (!validation.success) {
36
+ const errorResponse: ApiError = {
37
+ success: false,
38
+ error: "Invalid user ID",
39
+ details: validation.error.errors.map((err) => ({
40
+ field: err.path.join("."),
41
+ message: err.message,
42
+ })),
43
+ };
44
+ return res.status(400).json(errorResponse);
45
+ }
46
+
47
+ const user = await user_model.find_by_id(id);
48
+
49
+ if (!user) {
50
+ const errorResponse: ApiError = {
51
+ success: false,
52
+ error: "User not found",
53
+ };
54
+ return res.status(404).json(errorResponse);
55
+ }
56
+
57
+ const response: ApiResponse<UserResponse> = {
58
+ success: true,
59
+ data: format_user_response(user),
60
+ };
61
+
62
+ res.json(response);
63
+ } catch (error) {
64
+ const errorResponse: ApiError = {
65
+ success: false,
66
+ error: "Failed to fetch user",
67
+ };
68
+ res.status(500).json(errorResponse);
69
+ }
70
+ }
@@ -0,0 +1,41 @@
1
+ import type { Request, Response } from "express";
2
+ import { user_model } from "../../models/user.model.js";
3
+ import type { ApiResponse, ApiError } from "../../types/common/api-response.types.js";
4
+ import type { UserResponse } from "../../types/models/user.types.js";
5
+
6
+ /**
7
+ * Helper to format user response
8
+ */
9
+ function format_user_response(user: any): UserResponse {
10
+ return {
11
+ id: user.id,
12
+ email: user.email,
13
+ name: user.name,
14
+ createdAt: user.createdAt.toISOString(),
15
+ updatedAt: user.updatedAt.toISOString(),
16
+ };
17
+ }
18
+
19
+ /**
20
+ * Get all users
21
+ * GET /users
22
+ */
23
+ export async function get_users(req: Request, res: Response) {
24
+ try {
25
+ const users = await user_model.find_all();
26
+ const formatted_users = users.map(format_user_response);
27
+
28
+ const response: ApiResponse<UserResponse[]> = {
29
+ success: true,
30
+ data: formatted_users,
31
+ };
32
+
33
+ res.json(response);
34
+ } catch (error) {
35
+ const errorResponse: ApiError = {
36
+ success: false,
37
+ error: "Failed to fetch users",
38
+ };
39
+ res.status(500).json(errorResponse);
40
+ }
41
+ }
@@ -0,0 +1,102 @@
1
+ import type { Request, Response } from "express";
2
+ import { z } from "zod";
3
+ import { user_model } from "../../models/user.model.js";
4
+ import type { ApiResponse, ApiError } from "../../types/common/api-response.types.js";
5
+ import type { UserResponse } from "../../types/models/user.types.js";
6
+
7
+ /**
8
+ * Validation schemas
9
+ */
10
+ const user_id_schema = z.string().uuid("Invalid user ID format");
11
+ const update_user_schema = z.object({
12
+ email: z.string().email("Invalid email address").optional(),
13
+ name: z.string().min(2, "Name must be at least 2 characters").max(100, "Name must not exceed 100 characters").optional(),
14
+ });
15
+
16
+ /**
17
+ * Helper to format user response
18
+ */
19
+ function format_user_response(user: any): UserResponse {
20
+ return {
21
+ id: user.id,
22
+ email: user.email,
23
+ name: user.name,
24
+ createdAt: user.createdAt.toISOString(),
25
+ updatedAt: user.updatedAt.toISOString(),
26
+ };
27
+ }
28
+
29
+ /**
30
+ * Update user by ID
31
+ * PATCH /users/:id
32
+ */
33
+ export async function update_user(req: Request, res: Response) {
34
+ try {
35
+ const { id } = req.params;
36
+
37
+ // Validate ID format
38
+ const id_validation = user_id_schema.safeParse(id);
39
+ if (!id_validation.success) {
40
+ const errorResponse: ApiError = {
41
+ success: false,
42
+ error: "Invalid user ID",
43
+ details: id_validation.error.errors.map((err) => ({
44
+ field: err.path.join("."),
45
+ message: err.message,
46
+ })),
47
+ };
48
+ return res.status(400).json(errorResponse);
49
+ }
50
+
51
+ // Validate request body
52
+ const body_validation = update_user_schema.safeParse(req.body);
53
+ if (!body_validation.success) {
54
+ const errorResponse: ApiError = {
55
+ success: false,
56
+ error: "Validation failed",
57
+ details: body_validation.error.errors.map((err) => ({
58
+ field: err.path.join("."),
59
+ message: err.message,
60
+ })),
61
+ };
62
+ return res.status(400).json(errorResponse);
63
+ }
64
+
65
+ // Check if email is being updated and if it already exists
66
+ if (body_validation.data.email) {
67
+ const existing_user = await user_model.find_by_email(body_validation.data.email);
68
+ if (existing_user && existing_user.id !== id) {
69
+ const errorResponse: ApiError = {
70
+ success: false,
71
+ error: "User with this email already exists",
72
+ };
73
+ return res.status(409).json(errorResponse);
74
+ }
75
+ }
76
+
77
+ // Update user
78
+ const user = await user_model.update(id, body_validation.data);
79
+
80
+ if (!user) {
81
+ const errorResponse: ApiError = {
82
+ success: false,
83
+ error: "User not found",
84
+ };
85
+ return res.status(404).json(errorResponse);
86
+ }
87
+
88
+ const response: ApiResponse<UserResponse> = {
89
+ success: true,
90
+ data: format_user_response(user),
91
+ message: "User updated successfully",
92
+ };
93
+
94
+ res.json(response);
95
+ } catch (error) {
96
+ const errorResponse: ApiError = {
97
+ success: false,
98
+ error: "Failed to update user",
99
+ };
100
+ res.status(500).json(errorResponse);
101
+ }
102
+ }
@@ -0,0 +1,83 @@
1
+ import type { User, CreateUserInput, UpdateUserInput } from "../types/models/user.types.js";
2
+
3
+ /**
4
+ * User model
5
+ *
6
+ * This is a simple in-memory implementation.
7
+ * Replace with your database ORM (Drizzle, Prisma, etc.) in production.
8
+ */
9
+
10
+ // In-memory storage (for demonstration)
11
+ const users: User[] = [];
12
+
13
+ export const user_model = {
14
+ /**
15
+ * Find all users
16
+ */
17
+ find_all: async (): Promise<User[]> => {
18
+ return users;
19
+ },
20
+
21
+ /**
22
+ * Find user by ID
23
+ */
24
+ find_by_id: async (id: string): Promise<User | undefined> => {
25
+ return users.find((user) => user.id === id);
26
+ },
27
+
28
+ /**
29
+ * Find user by email
30
+ */
31
+ find_by_email: async (email: string): Promise<User | undefined> => {
32
+ return users.find((user) => user.email === email);
33
+ },
34
+
35
+ /**
36
+ * Create a new user
37
+ */
38
+ create: async (input: CreateUserInput): Promise<User> => {
39
+ const user: User = {
40
+ id: crypto.randomUUID(),
41
+ email: input.email,
42
+ name: input.name,
43
+ createdAt: new Date(),
44
+ updatedAt: new Date(),
45
+ };
46
+
47
+ users.push(user);
48
+ return user;
49
+ },
50
+
51
+ /**
52
+ * Update user by ID
53
+ */
54
+ update: async (id: string, input: UpdateUserInput): Promise<User | undefined> => {
55
+ const index = users.findIndex((user) => user.id === id);
56
+
57
+ if (index === -1) {
58
+ return undefined;
59
+ }
60
+
61
+ users[index] = {
62
+ ...users[index],
63
+ ...input,
64
+ updatedAt: new Date(),
65
+ };
66
+
67
+ return users[index];
68
+ },
69
+
70
+ /**
71
+ * Delete user by ID
72
+ */
73
+ delete: async (id: string): Promise<boolean> => {
74
+ const index = users.findIndex((user) => user.id === id);
75
+
76
+ if (index === -1) {
77
+ return false;
78
+ }
79
+
80
+ users.splice(index, 1);
81
+ return true;
82
+ },
83
+ };
@@ -1,7 +1,9 @@
1
1
  import express from "express";
2
+ import user_route from "./user.route.js";
2
3
 
3
4
  const router = express.Router();
4
5
 
6
+ // Root route
5
7
  router.get("/", (req, res) => {
6
8
  res.json({
7
9
  message: "Welcome to your Bun backend!",
@@ -9,4 +11,7 @@ router.get("/", (req, res) => {
9
11
  });
10
12
  });
11
13
 
14
+ // User routes
15
+ router.use("/users", user_route);
16
+
12
17
  export default router;
@@ -0,0 +1,17 @@
1
+ import express from "express";
2
+ import { get_users } from "../controllers/user/get-users.controller.js";
3
+ import { get_user } from "../controllers/user/get-user.controller.js";
4
+ import { create_user } from "../controllers/user/create-user.controller.js";
5
+ import { update_user } from "../controllers/user/update-user.controller.js";
6
+ import { delete_user } from "../controllers/user/delete-user.controller.js";
7
+
8
+ const user_route = express.Router();
9
+
10
+ // User CRUD routes
11
+ user_route.get("/", get_users);
12
+ user_route.get("/:id", get_user);
13
+ user_route.post("/", create_user);
14
+ user_route.patch("/:id", update_user);
15
+ user_route.delete("/:id", delete_user);
16
+
17
+ export default user_route;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Common API response types
3
+ */
4
+
5
+ export interface ApiResponse<T = unknown> {
6
+ success: boolean;
7
+ data?: T;
8
+ error?: string;
9
+ message?: string;
10
+ }
11
+
12
+ export interface PaginatedResponse<T> extends ApiResponse<T[]> {
13
+ pagination: {
14
+ page: number;
15
+ limit: number;
16
+ total: number;
17
+ totalPages: number;
18
+ };
19
+ }
20
+
21
+ export interface ValidationError {
22
+ field: string;
23
+ message: string;
24
+ }
25
+
26
+ export interface ApiError {
27
+ success: false;
28
+ error: string;
29
+ details?: ValidationError[];
30
+ }
@@ -1,2 +1,5 @@
1
- // Add your TypeScript types and interfaces here
2
- export {};
1
+ // Export common types
2
+ export * from "./common/api-response.types.js";
3
+
4
+ // Export model types
5
+ export * from "./models/user.types.js";
@@ -0,0 +1,26 @@
1
+ /**
2
+ * User model types
3
+ */
4
+
5
+ export interface User {
6
+ id: string;
7
+ email: string;
8
+ name: string;
9
+ createdAt: Date;
10
+ updatedAt: Date;
11
+ }
12
+
13
+ export interface CreateUserInput {
14
+ email: string;
15
+ name: string;
16
+ }
17
+
18
+ export interface UpdateUserInput {
19
+ email?: string;
20
+ name?: string;
21
+ }
22
+
23
+ export type UserResponse = Omit<User, "createdAt" | "updatedAt"> & {
24
+ createdAt: string;
25
+ updatedAt: string;
26
+ };
@@ -1,19 +1,35 @@
1
1
  import path from "path";
2
2
 
3
- export interface ParsedNameAndPath {
3
+ export type ParsedNameAndPath = {
4
4
  projectName: string;
5
5
  projectDir: string;
6
6
  }
7
7
 
8
8
  /**
9
9
  * Parses the app name input to extract project name and directory path.
10
- * Handles relative paths (./my-app), absolute paths, and simple names.
10
+ * Handles:
11
+ * - Current directory: "." (sets up in current directory)
12
+ * - Relative paths: "./my-app", "../my-app"
13
+ * - Home paths: "~/my-app"
14
+ * - Absolute paths: "/path/to/my-app"
15
+ * - Simple names: "my-app" (creates subdirectory in current directory)
11
16
  *
12
17
  * @param appName - The app name or path provided by the user
13
18
  * @returns Object containing projectName (kebab-case) and projectDir (absolute path)
14
19
  * @throws Error if the name contains invalid characters (spaces, special chars except - and _)
15
20
  */
16
21
  export function parse_name_and_path(appName: string): ParsedNameAndPath {
22
+ // Handle special case: current directory
23
+ if (appName === ".") {
24
+ const currentDir = process.cwd();
25
+ const currentDirName = path.basename(currentDir);
26
+
27
+ return {
28
+ projectName: currentDirName.replace(/_/g, "-").toLowerCase(),
29
+ projectDir: currentDir,
30
+ };
31
+ }
32
+
17
33
  // Validate app name - no spaces or invalid characters
18
34
  // Allow only alphanumeric, hyphens, underscores, slashes, and dots (for paths)
19
35
  const pathRegex = /^[a-zA-Z0-9\-_/.]+$/;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deploy-bbc",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "CLI to bootstrap production-ready backends with Bun (Best Backend Code)",
5
5
  "workspaces": [
6
6
  "cli"
@@ -14,7 +14,7 @@
14
14
  "devDependencies": {
15
15
  "@types/bun": "latest",
16
16
  "bun-types": "^1.3.7",
17
- "prettier": "^3.1.1",
18
- "typescript": "^5.3.3"
17
+ "prettier": "^3.8.1",
18
+ "typescript": "^5.9.3"
19
19
  }
20
20
  }