gosetup 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 ADDED
@@ -0,0 +1,15 @@
1
+ # react
2
+
3
+ To install dependencies:
4
+
5
+ ```bash
6
+ bun install
7
+ ```
8
+
9
+ To run:
10
+
11
+ ```bash
12
+ bun dev
13
+ ```
14
+
15
+ This project was created using `bun create tui`. [create-tui](https://git.new/create-tui) is the easiest way to get started with OpenTUI.
package/bin/gosetup ADDED
Binary file
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "gosetup",
3
+ "module": "src/index.tsx",
4
+ "version": "0.1.0",
5
+ "description": "TUI for scaffolding Go backend projects",
6
+ "author": "divin3circle",
7
+ "license": "MIT",
8
+ "homepage": "https://github.com/divin3circle/gosetup",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/divin3circle/gosetup.git"
12
+ },
13
+ "bin": {
14
+ "gosetup": "./bin/gosetup"
15
+ },
16
+ "type": "module",
17
+ "scripts": {
18
+ "dev": "bun run --watch src/index.tsx",
19
+ "build": "bun build src/index.tsx --compile --outfile ./bin/gosetup && chmod +x ./bin/gosetup",
20
+ "prepublishOnly": "bun run build"
21
+ },
22
+ "files": [
23
+ "bin",
24
+ "src",
25
+ "package.json",
26
+ "README.md"
27
+ ],
28
+ "devDependencies": {
29
+ "@types/bun": "latest"
30
+ },
31
+ "peerDependencies": {
32
+ "typescript": "^5"
33
+ },
34
+ "dependencies": {
35
+ "@opentui/core": "^0.1.69",
36
+ "@opentui/react": "^0.1.69",
37
+ "react": "^19.2.3"
38
+ }
39
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { createCliRenderer, RGBA, TextAttributes } from "@opentui/core";
4
+ import { createRoot } from "@opentui/react";
5
+ import { useState } from "react";
6
+ import { useKeyboard } from "@opentui/react";
7
+ import { $ } from "bun";
8
+ import { createDirectoryStructure, Mode } from "./utils/utils";
9
+ import { RootDirectory } from "./utils/data";
10
+
11
+ enum Screen {
12
+ Welcome,
13
+ Username,
14
+ Form,
15
+ Success,
16
+ }
17
+
18
+ function App() {
19
+ const [screen, setScreen] = useState(Screen.Welcome);
20
+ const [projectName, setProjectName] = useState("");
21
+ const [githubUsername, setGithubUsername] = useState("");
22
+ const [status, setStatus] = useState("");
23
+ const [loading, setLoading] = useState(false);
24
+
25
+ useKeyboard((key) => {
26
+ if (screen === Screen.Welcome && key.name === "return") {
27
+ setScreen(Screen.Username);
28
+ }
29
+ });
30
+
31
+ useKeyboard((key) => {
32
+ if (key.name === "escape") {
33
+ switch (screen) {
34
+ case Screen.Username:
35
+ setScreen(Screen.Welcome);
36
+ break;
37
+ case Screen.Form:
38
+ setScreen(Screen.Username);
39
+ break;
40
+ }
41
+ }
42
+ });
43
+
44
+ const handleSubmit = async (value: string) => {
45
+ if (!value.trim()) {
46
+ setStatus("Project name is required.");
47
+ return;
48
+ }
49
+ if (!githubUsername.trim()) {
50
+ setStatus("GitHub username is required.");
51
+ return;
52
+ }
53
+
54
+ setStatus("Creating project...");
55
+
56
+ try {
57
+ setLoading(true);
58
+ const name = value.trim();
59
+
60
+ const isCurrentDir = name === ".";
61
+
62
+ let targetPath: string;
63
+ let moduleName: string;
64
+
65
+ if (isCurrentDir) {
66
+ targetPath = ".";
67
+
68
+ const cwd = process.cwd();
69
+ moduleName = cwd.split("/").pop() || "app";
70
+ } else {
71
+ targetPath = name;
72
+ moduleName = name;
73
+ await $`mkdir -p ${targetPath}`;
74
+ }
75
+
76
+ await $`cd ${targetPath} && go mod init github.com/${githubUsername}/${moduleName}`;
77
+
78
+ await createDirectoryStructure(RootDirectory, targetPath);
79
+
80
+ await $`cd ${targetPath} && go mod tidy`;
81
+
82
+ setLoading(false);
83
+
84
+ if (isCurrentDir) {
85
+ setStatus(
86
+ `Project "${moduleName}" created successfully in current directory`
87
+ );
88
+ } else {
89
+ setStatus(`Project "${name}" created successfully in ./${name}`);
90
+ }
91
+
92
+ setScreen(Screen.Success);
93
+ } catch (error: any) {
94
+ setLoading(false);
95
+ setStatus(`Error: ${error.message}`);
96
+ }
97
+ };
98
+
99
+ return (
100
+ <box
101
+ flexGrow={1}
102
+ alignItems="center"
103
+ justifyContent="center"
104
+ backgroundColor={RGBA.fromHex(Mode.DARK)}
105
+ >
106
+ {screen === Screen.Welcome && (
107
+ <box flexDirection="column" alignItems="center">
108
+ <ascii-font
109
+ font="tiny"
110
+ text="Go Setup"
111
+ color={RGBA.fromHex(Mode.LIGHT)}
112
+ />
113
+ <text
114
+ attributes={TextAttributes.DIM | TextAttributes.BOLD}
115
+ marginTop={1}
116
+ >
117
+ Welcome to Go Backend Setup.
118
+ </text>
119
+ <text attributes={TextAttributes.BOLD} marginTop={2} fg="#ff6e58">
120
+ Press Enter to start
121
+ </text>
122
+ </box>
123
+ )}
124
+
125
+ {screen === Screen.Username && (
126
+ <box flexDirection="column" alignItems="center" width={50}>
127
+ <text
128
+ attributes={TextAttributes.DIM | TextAttributes.BOLD}
129
+ marginBottom={1}
130
+ >
131
+ Enter GitHub Username:
132
+ </text>
133
+ <input
134
+ value={githubUsername}
135
+ onChange={setGithubUsername}
136
+ onSubmit={() => setScreen(Screen.Form)}
137
+ placeholder="your-github-username"
138
+ focused
139
+ width="100%"
140
+ backgroundColor={RGBA.fromHex("#020202")}
141
+ />
142
+ <text attributes={TextAttributes.DIM} marginTop={4}>
143
+ [Esc to go back]
144
+ </text>
145
+ </box>
146
+ )}
147
+
148
+ {screen === Screen.Form && (
149
+ <box flexDirection="column" alignItems="center" width={50}>
150
+ <text attributes={TextAttributes.DIM} marginBottom={1}>
151
+ Enter Project Name:
152
+ </text>
153
+ <input
154
+ value={projectName}
155
+ onChange={setProjectName}
156
+ onSubmit={handleSubmit}
157
+ placeholder="my-go-app"
158
+ focused
159
+ width="100%"
160
+ backgroundColor={RGBA.fromHex("#020202")}
161
+ />
162
+ <text attributes={TextAttributes.DIM} marginTop={4}>
163
+ [Esc to go back]
164
+ </text>
165
+ </box>
166
+ )}
167
+
168
+ {screen === Screen.Success && (
169
+ <box flexDirection="column" alignItems="center">
170
+ <ascii-font text="Success!" font="tiny" />
171
+ <text marginTop={6} fg={RGBA.fromHex("#95f764")}>
172
+ {status}
173
+ </text>
174
+ <text marginTop={2}>Press Ctrl+C to exit.</text>
175
+ </box>
176
+ )}
177
+ </box>
178
+ );
179
+ }
180
+
181
+ async function main() {
182
+ const renderer = await createCliRenderer({
183
+ exitOnCtrlC: true,
184
+ });
185
+ createRoot(renderer).render(<App />);
186
+ renderer.start();
187
+ }
188
+
189
+ main().catch(console.error);
@@ -0,0 +1,289 @@
1
+ import type { IDirectory, IFile } from "./types";
2
+
3
+ const envLocalFile: IFile = {
4
+ name: ".env.local",
5
+ content: `# Environment variables
6
+ PORT=
7
+ POSTGRES_DB=
8
+ POSTGRES_USER=
9
+ POSTGRES_PASSWORD=
10
+ DB_URL=
11
+ TEST_DB_URL=
12
+ PRODUCTION_DB_URL=
13
+ `,
14
+ };
15
+
16
+ const gitignoreFile: IFile = {
17
+ name: ".gitignore",
18
+ content: `# Add more as needed
19
+ /database
20
+ ./database`,
21
+ };
22
+
23
+ const readmeFile: IFile = {
24
+ name: "README.md",
25
+ content: `# Go Setup Project
26
+ This is a Go project scaffolded using Go Setup.
27
+
28
+ ## Setup Instructions
29
+ 1. Ensure you have Go installed on your machine.
30
+ 2. Navigate to the project directory.
31
+ 3. Run \`go mod tidy\` to install dependencies.
32
+
33
+ ## Project Structure
34
+ - \`internal/\`: Contains internal application code.
35
+ - \`migrations/\`: Database migration files.
36
+
37
+ ## Usage
38
+ - To run the application, use \`go run main.go\`.
39
+
40
+ ## License
41
+ This project is licensed under the MIT License.
42
+ `,
43
+ };
44
+
45
+ const mainGoFile: IFile = {
46
+ name: "main.go",
47
+ content: `package main
48
+ import "fmt"
49
+
50
+ func main() {
51
+ fmt.Println("Hello, Go Setup!")
52
+ }
53
+ `,
54
+ };
55
+
56
+ const dockerfile: IFile = {
57
+ name: "Dockerfile",
58
+ content: `# Use the official Golang image as a base image
59
+ # Build stage
60
+ FROM golang:1.24-alpine AS builder
61
+
62
+ WORKDIR /app
63
+
64
+ # Copy go mod files
65
+ COPY go.mod go.sum ./
66
+ RUN go mod download
67
+
68
+ # Copy source code
69
+ COPY . .
70
+
71
+ # Build the application
72
+ RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
73
+
74
+ # Production stage
75
+ FROM alpine:latest
76
+
77
+ RUN apk --no-cache add ca-certificates
78
+
79
+ WORKDIR /root/
80
+
81
+ # Copy the binary from builder
82
+ COPY --from=builder /app/main .
83
+ COPY --from=builder /app/migrations ./migrations
84
+
85
+ EXPOSE 8080
86
+
87
+ CMD ["./main"]
88
+ `,
89
+ };
90
+
91
+ const dockerComposeFile: IFile = {
92
+ name: "docker-compose.yml",
93
+ content: `
94
+ version: "3.8"
95
+
96
+ services:
97
+ db:
98
+ container_name: "db"
99
+ image: postgres:12.4-alpine
100
+ volumes:
101
+ - "./database/postgres-data:/var/lib/postgresql/data:rw"
102
+ ports:
103
+ - "5432:5432"
104
+ environment:
105
+ POSTGRES_DB: postgres
106
+ POSTGRES_USER: postgres
107
+ POSTGRES_PASSWORD: postgres
108
+ restart: unless-stopped
109
+ test_db:
110
+ container_name: "test_db"
111
+ image: postgres:12.4-alpine
112
+ volumes:
113
+ - "./database/postgres-test-data:/var/lib/postgresql/data:rw"
114
+ ports:
115
+ - "5433:5432"
116
+ environment:
117
+ POSTGRES_DB: postgres
118
+ POSTGRES_USER: postgres
119
+ POSTGRES_PASSWORD: postgres
120
+ restart: unless-stopped
121
+ `,
122
+ };
123
+
124
+ const userHandlerFile: IFile = {
125
+ name: "user_handler.go",
126
+ content: `package api`,
127
+ };
128
+
129
+ const tokenHandlerFile: IFile = {
130
+ name: "token_handler.go",
131
+ content: `package api`,
132
+ };
133
+
134
+ const appFile: IFile = {
135
+ name: "app.go",
136
+ content: `package app`,
137
+ };
138
+
139
+ const middlewareFile: IFile = {
140
+ name: "middleware.go",
141
+ content: `package middleware`,
142
+ };
143
+
144
+ const utilsFile: IFile = {
145
+ name: "utils.go",
146
+ content: `package utils`,
147
+ };
148
+
149
+ const tokenFile: IFile = {
150
+ name: "token.go",
151
+ content: `package tokens`,
152
+ };
153
+
154
+ const routesFile: IFile = {
155
+ name: "routes.go",
156
+ content: `package routes`,
157
+ };
158
+
159
+ const storeFile: IFile = {
160
+ name: "db.go",
161
+ content: `package stores`,
162
+ };
163
+ const userStoreFile: IFile = {
164
+ name: "user_store.go",
165
+ content: `package stores`,
166
+ };
167
+ const tokenStoreFile: IFile = {
168
+ name: "token_store.go",
169
+ content: `package stores`,
170
+ };
171
+
172
+ const userMigrationFile: IFile = {
173
+ name: "00001_users.sql",
174
+ content: `
175
+ -- +goose Up
176
+ -- +goose StatementBegin
177
+
178
+ -- Enable UUID extension for gen_random_uuid()
179
+ CREATE EXTENSION IF NOT EXISTS "pgcrypto";
180
+
181
+ CREATE TABLE IF NOT EXISTS users (
182
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
183
+ username VARCHAR(50),
184
+ mobile_number VARCHAR(13),
185
+ hashed_password VARCHAR(255),
186
+ account_id VARCHAR(100),
187
+ profile_image_url VARCHAR(2048),
188
+ created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
189
+ updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
190
+ deleted_at TIMESTAMPTZ
191
+ );
192
+ -- +goose StatementEnd
193
+
194
+ -- +goose Down
195
+ -- +goose StatementBegin
196
+ DROP TABLE IF EXISTS users;
197
+ -- +goose StatementEnd
198
+ `,
199
+ };
200
+
201
+ const tokenMigrationFile: IFile = {
202
+ name: "00002_tokens.sql",
203
+ content: `
204
+ -- +goose Up
205
+ -- +goose StatementBegin
206
+ CREATE TABLE IF NOT EXISTS tokens (
207
+ hash BYTEA PRIMARY KEY,
208
+ user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
209
+ merchant_id UUID NOT NULL REFERENCES merchants(id) ON DELETE CASCADE,
210
+ expiry TIMESTAMP WITH TIME ZONE NOT NULL,
211
+ scope VARCHAR(255) NOT NULL
212
+ )
213
+ -- +goose StatementEnd
214
+
215
+ -- +goose Down
216
+ -- +goose StatementBegin
217
+ DROP TABLE tokens;
218
+ -- +goose StatementEnd
219
+ `,
220
+ };
221
+
222
+ const fsMigrationFile: IFile = {
223
+ name: "fs.go",
224
+ content: `package migrations
225
+
226
+ import (
227
+ "embed"
228
+ )
229
+
230
+ //go:embed *.sql
231
+ var FS embed.FS`,
232
+ };
233
+
234
+ const internalDirectories: IDirectory[] = [
235
+ {
236
+ name: "api",
237
+ files: [userHandlerFile, tokenHandlerFile],
238
+ },
239
+ {
240
+ name: "app",
241
+ files: [appFile],
242
+ },
243
+ {
244
+ name: "middleware",
245
+ files: [middlewareFile],
246
+ },
247
+ {
248
+ name: "utils",
249
+ files: [utilsFile],
250
+ },
251
+ {
252
+ name: "tokens",
253
+ files: [tokenFile],
254
+ },
255
+ {
256
+ name: "routes",
257
+ files: [routesFile],
258
+ },
259
+ {
260
+ name: "stores",
261
+ files: [storeFile, userStoreFile, tokenStoreFile],
262
+ },
263
+ ];
264
+
265
+ const internalDirectory: IDirectory = {
266
+ name: "internal",
267
+ subdirectories: internalDirectories,
268
+ files: [],
269
+ };
270
+
271
+ const migrationsDirectory: IDirectory = {
272
+ name: "migrations",
273
+ files: [fsMigrationFile, userMigrationFile, tokenMigrationFile],
274
+ };
275
+
276
+ const RootFiles: IFile[] = [
277
+ readmeFile,
278
+ gitignoreFile,
279
+ mainGoFile,
280
+ envLocalFile,
281
+ dockerfile,
282
+ dockerComposeFile,
283
+ ];
284
+
285
+ export const RootDirectory: IDirectory = {
286
+ name: ".",
287
+ subdirectories: [migrationsDirectory, internalDirectory],
288
+ files: RootFiles,
289
+ };
@@ -0,0 +1,12 @@
1
+ interface IFile {
2
+ name: string;
3
+ content?: string;
4
+ }
5
+
6
+ interface IDirectory {
7
+ name: string;
8
+ subdirectories?: IDirectory[];
9
+ files: IFile[];
10
+ }
11
+
12
+ export type { IFile, IDirectory };
@@ -0,0 +1,32 @@
1
+ import { $ } from "bun";
2
+ import type { IDirectory } from "./types";
3
+
4
+ export enum Mode {
5
+ LIGHT = "#fef7f7",
6
+ DARK = "#020202",
7
+ }
8
+
9
+ export async function createDirectoryStructure(
10
+ directory: IDirectory,
11
+ basePath: string
12
+ ): Promise<void> {
13
+ const currentPath =
14
+ directory.name === "." ? basePath : `${basePath}/${directory.name}`;
15
+
16
+ if (directory.name !== ".") {
17
+ await $`mkdir -p ${currentPath}`;
18
+ }
19
+
20
+ if (directory.files && directory.files.length > 0) {
21
+ for (const file of directory.files) {
22
+ const filePath = `${currentPath}/${file.name}`;
23
+ await Bun.write(filePath, file.content ?? "");
24
+ }
25
+ }
26
+
27
+ if (directory.subdirectories && directory.subdirectories.length > 0) {
28
+ for (const subdir of directory.subdirectories) {
29
+ await createDirectoryStructure(subdir, currentPath);
30
+ }
31
+ }
32
+ }