@paralect/hive 0.1.48 → 0.1.50-alpha.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/.cursor/commands/deslop.md +12 -0
- package/.hive/.babelrc +3 -0
- package/.hive/.cursor/commands/add-endpoint.md +262 -0
- package/.hive/.cursor/commands/add-handler.md +137 -0
- package/.hive/.cursor/commands/add-middleware.md +95 -0
- package/.hive/.cursor/commands/add-resource.md +71 -0
- package/.hive/.cursor/commands/add-scheduler.md +138 -0
- package/.hive/.cursor/commands/add-service.md +188 -0
- package/.hive/.cursor/skills/hive-auth/SKILL.md +134 -0
- package/.hive/.cursor/skills/hive-database/SKILL.md +103 -0
- package/.hive/.cursor/skills/hive-endpoint/SKILL.md +103 -0
- package/.hive/.cursor/skills/hive-handler/SKILL.md +88 -0
- package/.hive/.cursor/skills/hive-mapping/SKILL.md +85 -0
- package/.hive/.cursor/skills/hive-middleware/SKILL.md +104 -0
- package/.hive/.cursor/skills/hive-overview/SKILL.md +50 -0
- package/.hive/.cursor/skills/hive-scheduler/SKILL.md +94 -0
- package/.hive/.cursor/skills/hive-schema/SKILL.md +73 -0
- package/.hive/.cursor/skills/hive-service/SKILL.md +90 -0
- package/.hive/.dockerignore +1 -0
- package/.hive/Dockerfile +22 -0
- package/.hive/Dockerfile.dev +33 -0
- package/.hive/Dockerfile.prod +29 -0
- package/.hive/README.md +11 -0
- package/.hive/bin/deploy.sh +5 -0
- package/.hive/bin/start.sh +2 -0
- package/.hive/bootstrap-hive.js +118 -0
- package/.hive/deploy/api/Chart.yaml +6 -0
- package/.hive/deploy/api/staging.yaml +3 -0
- package/.hive/deploy/api/templates/deployment.yaml +44 -0
- package/.hive/deploy/api/templates/ingress.yaml +26 -0
- package/.hive/deploy/api/templates/service.yaml +14 -0
- package/.hive/deploy/script/Dockerfile +39 -0
- package/.hive/deploy/script/package-lock.json +1499 -0
- package/.hive/deploy/script/package.json +12 -0
- package/.hive/deploy/script/src/config.js +48 -0
- package/.hive/deploy/script/src/index.js +108 -0
- package/.hive/deploy/script/src/util.js +19 -0
- package/.hive/initial-data.json +176 -0
- package/.hive/package-lock.json +10242 -0
- package/.hive/package.json +98 -0
- package/.hive/ship_logo.png +0 -0
- package/.hive/src/app-config/app.js +3 -0
- package/.hive/src/app-config/assertEnv.js +15 -0
- package/.hive/src/app-config/index.js +62 -0
- package/.hive/src/app.js +69 -0
- package/.hive/src/assets/emails/components/header.mjml +13 -0
- package/.hive/src/assets/emails/dist/.gitkeep +0 -0
- package/.hive/src/assets/emails/signup-welcome.mjml +34 -0
- package/.hive/src/assets/emails/styles/index.mjml +77 -0
- package/.hive/src/autoMap/addHandlers.js +142 -0
- package/.hive/src/autoMap/getDependentFields.js +37 -0
- package/.hive/src/autoMap/mapSchema.js +99 -0
- package/.hive/src/autoMap/schemaMappings.js +13 -0
- package/.hive/src/autoMap/schemaMappings.json +3 -0
- package/.hive/src/bullMqBus.js +21 -0
- package/.hive/src/bullMqWrapper.js +23 -0
- package/.hive/src/db.js +52 -0
- package/.hive/src/emails/MyEmailComponent.jsx +14 -0
- package/.hive/src/emails/compiled/MyEmailComponent.js +18 -0
- package/.hive/src/emails/compiled/compiled/MyEmailComponent.js +18 -0
- package/.hive/src/helpers/db/ifUpdated.js +22 -0
- package/.hive/src/helpers/getMiddlewares.js +38 -0
- package/.hive/src/helpers/getResourceEndpoints.js +28 -0
- package/.hive/src/helpers/getResources.js +32 -0
- package/.hive/src/helpers/getSchemas.js +50 -0
- package/.hive/src/helpers/importHandlers.js +29 -0
- package/.hive/src/helpers/isZodArray.js +13 -0
- package/.hive/src/helpers/prettierFormat.js +8 -0
- package/.hive/src/helpers/schema/db.schema.js +9 -0
- package/.hive/src/helpers/schema/pagination.schema.js +14 -0
- package/.hive/src/ioEmitter.js +9 -0
- package/.hive/src/jsconfig.json +5 -0
- package/.hive/src/lib/node-mongo/.github/workflows/npm-publish.yml +32 -0
- package/.hive/src/lib/node-mongo/API.md +654 -0
- package/.hive/src/lib/node-mongo/CHANGELOG.md +98 -0
- package/.hive/src/lib/node-mongo/README.md +97 -0
- package/.hive/src/lib/node-mongo/package-lock.json +3682 -0
- package/.hive/src/lib/node-mongo/package.json +74 -0
- package/.hive/src/lib/node-mongo/src/index.js +64 -0
- package/.hive/src/lib/node-mongo/src/mongo-query-service.js +78 -0
- package/.hive/src/lib/node-mongo/src/mongo-service-error.js +15 -0
- package/.hive/src/lib/node-mongo/src/mongo-service.js +303 -0
- package/.hive/src/logger.js +43 -0
- package/.hive/src/middlewares/allowNoAuth.js +9 -0
- package/.hive/src/middlewares/attachUser.js +41 -0
- package/.hive/src/middlewares/global/extractUserTokens.js +15 -0
- package/.hive/src/middlewares/global/tryToAttachUser.js +33 -0
- package/.hive/src/middlewares/isAuthorized.js +18 -0
- package/.hive/src/middlewares/shouldExist.js +37 -0
- package/.hive/src/middlewares/shouldNotExist.js +19 -0
- package/.hive/src/middlewares/uploadFile.js +5 -0
- package/.hive/src/middlewares/validate.js +32 -0
- package/.hive/src/migrations/migration.js +8 -0
- package/.hive/src/migrations/migration.service.js +73 -0
- package/.hive/src/migrations/migrations/1.js +22 -0
- package/.hive/src/migrations/migrations-log/migration-log.schema.js +13 -0
- package/.hive/src/migrations/migrations-log/migration-log.service.js +50 -0
- package/.hive/src/migrations/migrations.schema.js +6 -0
- package/.hive/src/migrations/migrator.js +75 -0
- package/.hive/src/migrator.js +4 -0
- package/.hive/src/resources/_dev/endpoints/triggerSchedulerHandler.js +32 -0
- package/.hive/src/resources/health/endpoints/get.js +19 -0
- package/.hive/src/resources/schemaMappings/schemaMappings.schema.js +6 -0
- package/.hive/src/resources/tokens/methods/generateSecureToken.js +9 -0
- package/.hive/src/resources/tokens/methods/setToken.js +8 -0
- package/.hive/src/resources/tokens/methods/storeToken.js +35 -0
- package/.hive/src/resources/tokens/tokens.schema.js +11 -0
- package/.hive/src/resources/users/endpoints/getCurrentUser.js +14 -0
- package/.hive/src/resources/users/endpoints/getUserProfile.js +19 -0
- package/.hive/src/resources/users/handlers/test.js +1 -0
- package/.hive/src/resources/users/methods/ensureUserCreated.js +68 -0
- package/.hive/src/resources/users/users.schema.js +16 -0
- package/.hive/src/routes/index.js +172 -0
- package/.hive/src/routes/middlewares/attachCustomErrors.js +28 -0
- package/.hive/src/routes/middlewares/routeErrorHandler.js +27 -0
- package/.hive/src/scheduler/handlers/sendDailyReport.example.js +7 -0
- package/.hive/src/scheduler.js +32 -0
- package/.hive/src/security.util.js +38 -0
- package/.hive/src/services/emailService.js +15 -0
- package/.hive/src/services/globalTest.js +0 -0
- package/.hive/src/services/setCookie.js +21 -0
- package/.hive/src/socketIo.js +99 -0
- package/.hive/tsconfig.json +31 -0
- package/AGENTS.md +96 -0
- package/README.md +271 -0
- package/cli/helpers/docker.js +59 -0
- package/cli/helpers/envCheck.js +123 -0
- package/cli/helpers/findPort.js +32 -0
- package/cli/hive.js +155 -15
- package/package.json +1 -1
- package/starter/.cursor/commands/add-endpoint.md +262 -0
- package/starter/.cursor/commands/add-handler.md +137 -0
- package/starter/.cursor/commands/add-middleware.md +95 -0
- package/starter/.cursor/commands/add-resource.md +71 -0
- package/starter/.cursor/commands/add-scheduler.md +138 -0
- package/starter/.cursor/commands/add-service.md +188 -0
- package/starter/.cursor/skills/hive-auth/SKILL.md +134 -0
- package/starter/.cursor/skills/hive-database/SKILL.md +103 -0
- package/starter/.cursor/skills/hive-endpoint/SKILL.md +103 -0
- package/starter/.cursor/skills/hive-handler/SKILL.md +88 -0
- package/starter/.cursor/skills/hive-mapping/SKILL.md +85 -0
- package/starter/.cursor/skills/hive-middleware/SKILL.md +104 -0
- package/starter/.cursor/skills/hive-overview/SKILL.md +50 -0
- package/starter/.cursor/skills/hive-scheduler/SKILL.md +94 -0
- package/starter/.cursor/skills/hive-schema/SKILL.md +73 -0
- package/starter/.cursor/skills/hive-service/SKILL.md +90 -0
- package/starter/src/app.js +4 -3
- package/test-app/.cursor/commands/add-endpoint.md +262 -0
- package/test-app/.cursor/commands/add-handler.md +137 -0
- package/test-app/.cursor/commands/add-middleware.md +95 -0
- package/test-app/.cursor/commands/add-resource.md +71 -0
- package/test-app/.cursor/commands/add-scheduler.md +138 -0
- package/test-app/.cursor/commands/add-service.md +188 -0
- package/test-app/.cursor/skills/hive-auth/SKILL.md +134 -0
- package/test-app/.cursor/skills/hive-database/SKILL.md +103 -0
- package/test-app/.cursor/skills/hive-endpoint/SKILL.md +103 -0
- package/test-app/.cursor/skills/hive-handler/SKILL.md +88 -0
- package/test-app/.cursor/skills/hive-mapping/SKILL.md +85 -0
- package/test-app/.cursor/skills/hive-middleware/SKILL.md +104 -0
- package/test-app/.cursor/skills/hive-overview/SKILL.md +50 -0
- package/test-app/.cursor/skills/hive-scheduler/SKILL.md +94 -0
- package/test-app/.cursor/skills/hive-schema/SKILL.md +73 -0
- package/test-app/.cursor/skills/hive-service/SKILL.md +90 -0
- package/test-app/package-lock.json +8684 -0
- package/test-app/package.json +21 -0
package/README.md
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
# 🐝 Hive
|
|
2
|
+
|
|
3
|
+
**An AI-native backend framework for engineers who are *pissed off* about the complexity of modern technology.**
|
|
4
|
+
|
|
5
|
+
Hive preconfigures the entire backend stack: database, cache, queues, event handlers, auth, real-time — so you don't duct-tape different pieces yourself. It dictates a clean architecture that's easy to understand, easy to read, and scales to millions of users.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @paralect/hive
|
|
9
|
+
|
|
10
|
+
hive init my-app
|
|
11
|
+
cd my-app
|
|
12
|
+
hive run ./src
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Your API is live. MongoDB, validation, auth infrastructure, real-time events — all preconfigured.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## 🤖 AI-Native Development
|
|
20
|
+
|
|
21
|
+
Hive is designed for AI coding assistants. Open your project in Cursor and build with natural language:
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
add-resource tasks
|
|
25
|
+
add-endpoint tasks post title, status, assignee: { _id }
|
|
26
|
+
add-endpoint tasks get page, perPage
|
|
27
|
+
add-handler tasks
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Commands
|
|
31
|
+
|
|
32
|
+
| Command | What it does |
|
|
33
|
+
|---------|--------------|
|
|
34
|
+
| `add-resource tasks` | Create resource with schema + folders |
|
|
35
|
+
| `add-endpoint tasks post title, dueOn` | Create POST endpoint with inferred types |
|
|
36
|
+
| `add-endpoint tasks get page, perPage` | Create GET list endpoint |
|
|
37
|
+
| `add-endpoint tasks update /:id title` | Create PUT endpoint |
|
|
38
|
+
| `add-handler tasks` | Create event handlers for DB changes |
|
|
39
|
+
| `add-middleware isAdmin` | Create custom middleware |
|
|
40
|
+
| `add-service slack sendMessage` | Create service with go-to library |
|
|
41
|
+
| `add-scheduler sendReport daily at 9am` | Create cron job |
|
|
42
|
+
|
|
43
|
+
The AI understands Hive conventions and generates production-ready code.
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## What's Preconfigured
|
|
48
|
+
|
|
49
|
+
| Concern | Solution |
|
|
50
|
+
|---------|----------|
|
|
51
|
+
| Database | MongoDB with auto-generated services per schema |
|
|
52
|
+
| Validation | Zod schemas, auto-applied to all endpoints |
|
|
53
|
+
| Auth | Token-based auth infrastructure, ready to extend |
|
|
54
|
+
| Events | Persistent handlers that react to DB changes |
|
|
55
|
+
| Real-time | Socket.io with Redis adapter for scaling |
|
|
56
|
+
| Queues | BullMQ for background jobs |
|
|
57
|
+
| Scheduler | Cron-based jobs, no external service needed |
|
|
58
|
+
| Auto-sync | Embedded documents stay fresh automatically |
|
|
59
|
+
|
|
60
|
+
You focus on your product. Hive handles the plumbing.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Architecture
|
|
65
|
+
|
|
66
|
+
Every feature is a **resource** — a folder with predictable structure:
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
src/resources/{name}/
|
|
70
|
+
├── {name}.schema.js # Data shape (required)
|
|
71
|
+
├── endpoints/ # API routes
|
|
72
|
+
├── handlers/ # React to DB events
|
|
73
|
+
└── methods/ # Shared logic
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
This scales. New developers understand it in minutes. Your codebase stays clean at 10 endpoints or 1000.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 📦 Schemas
|
|
81
|
+
|
|
82
|
+
Define your data once. Hive creates the collection and service.
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
import { z } from 'zod';
|
|
86
|
+
import dbSchema from 'helpers/schema/db.schema.js';
|
|
87
|
+
|
|
88
|
+
const schema = dbSchema.extend({
|
|
89
|
+
title: z.coerce.string().nullable().optional(),
|
|
90
|
+
status: z.coerce.string().default('pending'),
|
|
91
|
+
assignee: z.object({
|
|
92
|
+
_id: z.string(),
|
|
93
|
+
fullName: z.coerce.string().nullable().optional(),
|
|
94
|
+
}).nullable().optional(),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
export default schema;
|
|
98
|
+
export const secureFields = ['internalNotes'];
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Every schema gets `_id`, `createdOn`, `updatedOn` automatically.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 🔌 Endpoints
|
|
106
|
+
|
|
107
|
+
Four exports. No router config.
|
|
108
|
+
|
|
109
|
+
```javascript
|
|
110
|
+
import { z } from 'zod';
|
|
111
|
+
import db from 'db';
|
|
112
|
+
|
|
113
|
+
const tasksService = db.services.tasks;
|
|
114
|
+
|
|
115
|
+
export const middlewares = [];
|
|
116
|
+
|
|
117
|
+
export const requestSchema = z.object({
|
|
118
|
+
page: z.coerce.number().default(1),
|
|
119
|
+
perPage: z.coerce.number().default(20),
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
export const handler = async (ctx) => {
|
|
123
|
+
const { page, perPage } = ctx.validatedData;
|
|
124
|
+
return tasksService.find({}, { page, perPage, sort: '-createdOn' });
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export const endpoint = {
|
|
128
|
+
url: '/',
|
|
129
|
+
method: 'get',
|
|
130
|
+
};
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Route auto-generated as `GET /tasks`.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 🗄️ Database
|
|
138
|
+
|
|
139
|
+
Auto-generated services for every schema:
|
|
140
|
+
|
|
141
|
+
```javascript
|
|
142
|
+
import db from 'db';
|
|
143
|
+
|
|
144
|
+
const tasksService = db.services.tasks;
|
|
145
|
+
|
|
146
|
+
// Find
|
|
147
|
+
const { results, count } = await tasksService.find({ status: 'active' }, { page: 1, perPage: 20 });
|
|
148
|
+
const task = await tasksService.findOne({ _id: id });
|
|
149
|
+
|
|
150
|
+
// Create
|
|
151
|
+
const task = await tasksService.create({ title: 'New', user: ctx.state.user });
|
|
152
|
+
|
|
153
|
+
// Update
|
|
154
|
+
await tasksService.updateOne({ _id: id }, (doc) => ({ ...doc, status: 'done' }));
|
|
155
|
+
|
|
156
|
+
// Delete
|
|
157
|
+
await tasksService.remove({ _id: id });
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## ⚡ Handlers
|
|
163
|
+
|
|
164
|
+
React to database changes. Perfect for side effects, notifications, syncing data.
|
|
165
|
+
|
|
166
|
+
```javascript
|
|
167
|
+
import db from 'db';
|
|
168
|
+
import ifUpdated from 'helpers/db/ifUpdated';
|
|
169
|
+
|
|
170
|
+
const tasksService = db.services.tasks;
|
|
171
|
+
|
|
172
|
+
tasksService.on('created', async ({ doc }) => {
|
|
173
|
+
// Send notification, create activity log, etc.
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
tasksService.on('updated', ifUpdated(['status'], async ({ doc, prevDoc }) => {
|
|
177
|
+
// Only runs when status changed
|
|
178
|
+
}));
|
|
179
|
+
|
|
180
|
+
tasksService.on('removed', async ({ doc }) => {
|
|
181
|
+
// Cleanup related data
|
|
182
|
+
});
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 🛡️ Middlewares
|
|
188
|
+
|
|
189
|
+
Built-in: `allowNoAuth`, `isAuthorized`, `shouldExist`, `attachUser`
|
|
190
|
+
|
|
191
|
+
Custom:
|
|
192
|
+
|
|
193
|
+
```javascript
|
|
194
|
+
export default async (ctx, next) => {
|
|
195
|
+
if (!ctx.state.user?.isAdmin) ctx.throw(403, 'Admin only');
|
|
196
|
+
return next();
|
|
197
|
+
};
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## ⏰ Scheduler
|
|
203
|
+
|
|
204
|
+
Background jobs with cron:
|
|
205
|
+
|
|
206
|
+
```javascript
|
|
207
|
+
import db from 'db';
|
|
208
|
+
|
|
209
|
+
export const handler = async () => {
|
|
210
|
+
await db.services.invoices.updateMany(
|
|
211
|
+
{ isPaid: { $ne: true }, dueOn: { $lt: new Date() } },
|
|
212
|
+
(doc) => ({ ...doc, isOverdue: true })
|
|
213
|
+
);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export const cron = '0 9 * * *';
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## 🔄 Auto-Sync
|
|
222
|
+
|
|
223
|
+
Embedded documents stay fresh. When a user updates their name, all tasks with that user update automatically.
|
|
224
|
+
|
|
225
|
+
```json
|
|
226
|
+
{
|
|
227
|
+
"tasks": {
|
|
228
|
+
"assignee": { "schema": "users" }
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## CLI
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
hive init [name] # Create new project
|
|
239
|
+
hive run [path] # Start dev server
|
|
240
|
+
hive prepare [path] # Build for production
|
|
241
|
+
hive deploy # Deploy to Hive Cloud
|
|
242
|
+
hive install <plugin> # Install plugin
|
|
243
|
+
hive login # Login to Hive Cloud
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## Project Structure
|
|
249
|
+
|
|
250
|
+
```
|
|
251
|
+
my-app/
|
|
252
|
+
├── src/
|
|
253
|
+
│ ├── resources/ # Your features
|
|
254
|
+
│ ├── middlewares/ # Custom middlewares
|
|
255
|
+
│ ├── services/ # External APIs
|
|
256
|
+
│ └── scheduler/handlers/
|
|
257
|
+
└── .hive/ # Framework (don't edit)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Tech Stack
|
|
263
|
+
|
|
264
|
+
Proven, boring technology:
|
|
265
|
+
|
|
266
|
+
- **Koa** — Node.js framework
|
|
267
|
+
- **MongoDB** — Document database
|
|
268
|
+
- **Zod** — Schema validation
|
|
269
|
+
- **Socket.io** — Real-time
|
|
270
|
+
- **BullMQ** — Job queues
|
|
271
|
+
- **Redis** — Cache & pub/sub
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
const { execSync } = require('child_process');
|
|
2
|
+
|
|
3
|
+
const isDockerInstalled = () => {
|
|
4
|
+
try {
|
|
5
|
+
execSync('docker --version', { stdio: 'ignore' });
|
|
6
|
+
return true;
|
|
7
|
+
} catch {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const getContainerStatus = (name) => {
|
|
13
|
+
try {
|
|
14
|
+
const result = execSync(`docker inspect --format='{{.State.Status}}' ${name}`, {
|
|
15
|
+
encoding: 'utf8',
|
|
16
|
+
stdio: ['pipe', 'pipe', 'ignore']
|
|
17
|
+
});
|
|
18
|
+
return result.trim();
|
|
19
|
+
} catch {
|
|
20
|
+
return null; // Container doesn't exist
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const startContainer = async (name, image, port, extraArgs = '') => {
|
|
25
|
+
const status = getContainerStatus(name);
|
|
26
|
+
|
|
27
|
+
if (status === 'running') {
|
|
28
|
+
console.log(` ${name} already running`);
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (status === 'exited' || status === 'created') {
|
|
33
|
+
console.log(` Starting ${name}...`);
|
|
34
|
+
execSync(`docker start ${name}`, { stdio: 'inherit' });
|
|
35
|
+
return true;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Container doesn't exist, create it
|
|
39
|
+
console.log(` Creating ${name}...`);
|
|
40
|
+
const cmd = `docker run -d --name ${name} -p ${port}:${port} ${extraArgs} ${image}`;
|
|
41
|
+
execSync(cmd, { stdio: 'inherit' });
|
|
42
|
+
return true;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const startMongo = async () => {
|
|
46
|
+
return startContainer('hive-mongo', 'mongo:7', 27017);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const startRedis = async () => {
|
|
50
|
+
return startContainer('hive-redis', 'redis:7-alpine', 6379);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
module.exports = {
|
|
54
|
+
isDockerInstalled,
|
|
55
|
+
getContainerStatus,
|
|
56
|
+
startContainer,
|
|
57
|
+
startMongo,
|
|
58
|
+
startRedis,
|
|
59
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const inquirer = require('inquirer');
|
|
4
|
+
|
|
5
|
+
const hashCode = (str) => {
|
|
6
|
+
let hash = 0;
|
|
7
|
+
for (let i = 0; i < str.length; i++) {
|
|
8
|
+
const char = str.charCodeAt(i);
|
|
9
|
+
hash = ((hash << 5) - hash) + char;
|
|
10
|
+
hash = hash & hash;
|
|
11
|
+
}
|
|
12
|
+
return Math.abs(hash);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const readEnvFile = (envPath) => {
|
|
16
|
+
if (!fs.existsSync(envPath)) {
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const content = fs.readFileSync(envPath, 'utf8');
|
|
21
|
+
const env = {};
|
|
22
|
+
|
|
23
|
+
content.split('\n').forEach(line => {
|
|
24
|
+
const trimmed = line.trim();
|
|
25
|
+
if (trimmed && !trimmed.startsWith('#')) {
|
|
26
|
+
const [key, ...valueParts] = trimmed.split('=');
|
|
27
|
+
if (key) {
|
|
28
|
+
env[key.trim()] = valueParts.join('=').trim();
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
return env;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const writeEnvFile = (envPath, env) => {
|
|
37
|
+
const content = Object.entries(env)
|
|
38
|
+
.map(([key, value]) => `${key}=${value}`)
|
|
39
|
+
.join('\n') + '\n';
|
|
40
|
+
|
|
41
|
+
fs.writeFileSync(envPath, content);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const updateEnvFile = (envPath, updates) => {
|
|
45
|
+
const env = readEnvFile(envPath);
|
|
46
|
+
Object.assign(env, updates);
|
|
47
|
+
writeEnvFile(envPath, env);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const ensureEnvConfig = async (projectDir) => {
|
|
51
|
+
const envPath = path.join(projectDir, '.env');
|
|
52
|
+
const env = readEnvFile(envPath);
|
|
53
|
+
const projectName = path.basename(projectDir);
|
|
54
|
+
const updates = {};
|
|
55
|
+
|
|
56
|
+
// Check MONGODB_URI
|
|
57
|
+
if (!env.MONGODB_URI) {
|
|
58
|
+
const { mongoChoice } = await inquirer.prompt([{
|
|
59
|
+
type: 'list',
|
|
60
|
+
name: 'mongoChoice',
|
|
61
|
+
message: 'MONGODB_URI not set. How would you like to configure MongoDB?',
|
|
62
|
+
choices: [
|
|
63
|
+
{ name: 'Use local Docker (localhost:27017)', value: 'local' },
|
|
64
|
+
{ name: 'Enter custom URI', value: 'custom' },
|
|
65
|
+
]
|
|
66
|
+
}]);
|
|
67
|
+
|
|
68
|
+
if (mongoChoice === 'local') {
|
|
69
|
+
updates.MONGODB_URI = `mongodb://localhost:27017/${projectName}`;
|
|
70
|
+
} else {
|
|
71
|
+
const { mongoUri } = await inquirer.prompt([{
|
|
72
|
+
type: 'input',
|
|
73
|
+
name: 'mongoUri',
|
|
74
|
+
message: 'Enter MongoDB URI:',
|
|
75
|
+
default: `mongodb://localhost:27017/${projectName}`
|
|
76
|
+
}]);
|
|
77
|
+
updates.MONGODB_URI = mongoUri;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Check REDIS_URI
|
|
82
|
+
if (!env.REDIS_URI) {
|
|
83
|
+
const { redisChoice } = await inquirer.prompt([{
|
|
84
|
+
type: 'list',
|
|
85
|
+
name: 'redisChoice',
|
|
86
|
+
message: 'REDIS_URI not set. How would you like to configure Redis?',
|
|
87
|
+
choices: [
|
|
88
|
+
{ name: 'Use local Docker (localhost:6379)', value: 'local' },
|
|
89
|
+
{ name: 'Enter custom URI', value: 'custom' },
|
|
90
|
+
{ name: 'Skip (Redis optional)', value: 'skip' },
|
|
91
|
+
]
|
|
92
|
+
}]);
|
|
93
|
+
|
|
94
|
+
if (redisChoice === 'local') {
|
|
95
|
+
const redisDb = hashCode(projectName) % 16;
|
|
96
|
+
updates.REDIS_URI = `redis://localhost:6379/${redisDb}`;
|
|
97
|
+
} else if (redisChoice === 'custom') {
|
|
98
|
+
const { redisUri } = await inquirer.prompt([{
|
|
99
|
+
type: 'input',
|
|
100
|
+
name: 'redisUri',
|
|
101
|
+
message: 'Enter Redis URI:',
|
|
102
|
+
default: `redis://localhost:6379/0`
|
|
103
|
+
}]);
|
|
104
|
+
updates.REDIS_URI = redisUri;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Write updates if any
|
|
109
|
+
if (Object.keys(updates).length > 0) {
|
|
110
|
+
updateEnvFile(envPath, updates);
|
|
111
|
+
console.log(`\n Updated .env with: ${Object.keys(updates).join(', ')}\n`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return { ...env, ...updates };
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
module.exports = {
|
|
118
|
+
readEnvFile,
|
|
119
|
+
writeEnvFile,
|
|
120
|
+
updateEnvFile,
|
|
121
|
+
ensureEnvConfig,
|
|
122
|
+
hashCode,
|
|
123
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const net = require('net');
|
|
2
|
+
|
|
3
|
+
const isPortAvailable = (port) => {
|
|
4
|
+
return new Promise((resolve) => {
|
|
5
|
+
const server = net.createServer();
|
|
6
|
+
|
|
7
|
+
server.once('error', () => {
|
|
8
|
+
resolve(false);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
server.once('listening', () => {
|
|
12
|
+
server.close();
|
|
13
|
+
resolve(true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
server.listen(port, '127.0.0.1');
|
|
17
|
+
});
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const findAvailablePort = async (startPort = 3001, maxAttempts = 100) => {
|
|
21
|
+
for (let port = startPort; port < startPort + maxAttempts; port++) {
|
|
22
|
+
if (await isPortAvailable(port)) {
|
|
23
|
+
return port;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
throw new Error(`No available port found between ${startPort} and ${startPort + maxAttempts - 1}`);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
module.exports = {
|
|
30
|
+
isPortAvailable,
|
|
31
|
+
findAvailablePort,
|
|
32
|
+
};
|