express-file-cluster 0.2.0 → 0.2.2
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 +367 -0
- package/dist/auth/index.cjs +2 -2
- package/dist/auth/index.d.cts +1 -1
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.js +1 -1
- package/dist/{chunk-DQGWFWBV.js → chunk-BHMFPULA.js} +1 -1
- package/dist/chunk-BHMFPULA.js.map +1 -0
- package/dist/{chunk-Z5YNIVQG.js → chunk-LPZEKFVN.js} +2 -4
- package/dist/{chunk-Z5YNIVQG.js.map → chunk-LPZEKFVN.js.map} +1 -1
- package/dist/{chunk-QB3X6LKZ.cjs → chunk-UUF2OYRW.cjs} +1 -1
- package/dist/{chunk-QB3X6LKZ.cjs.map → chunk-UUF2OYRW.cjs.map} +1 -1
- package/dist/{chunk-UDE6S53C.cjs → chunk-XH7O25BL.cjs} +2 -4
- package/dist/chunk-XH7O25BL.cjs.map +1 -0
- package/dist/cli/index.cjs.map +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +22 -16
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +16 -10
- package/dist/index.js.map +1 -1
- package/dist/tasks/index.cjs +2 -2
- package/dist/tasks/index.d.cts +1 -1
- package/dist/tasks/index.d.ts +1 -1
- package/dist/tasks/index.js +1 -1
- package/dist/{types-Dj9-sLOK.d.cts → types-DpegkYh2.d.cts} +1 -2
- package/dist/{types-Dj9-sLOK.d.ts → types-DpegkYh2.d.ts} +1 -2
- package/package.json +2 -2
- package/dist/chunk-DQGWFWBV.js.map +0 -1
- package/dist/chunk-UDE6S53C.cjs.map +0 -1
package/README.md
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
# express-file-cluster · `efc`
|
|
2
|
+
|
|
3
|
+
**File-based routing. Multi-core clustering. Background tasks. Zero boilerplate.**
|
|
4
|
+
|
|
5
|
+
EFC is an opinionated backend framework built on Express. Drop files in `src/api/` and they become routes. Every CPU core serves traffic automatically. Heavy work goes to a queue-backed task subsystem so requests stay fast.
|
|
6
|
+
|
|
7
|
+
> **Status: v0.2.1 (Beta).** The router, clustering, and auth are implemented. The MongoDB adapter and task queue backend are in active development.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Why EFC
|
|
12
|
+
|
|
13
|
+
Most Express apps grow the same way: a working prototype, then a maze of `router.get(...)` calls spread across files, a clustering setup copy-pasted from a blog post, and background jobs bolted on as an afterthought. EFC collapses all of that into conventions:
|
|
14
|
+
|
|
15
|
+
| Problem | EFC's answer |
|
|
16
|
+
|---|---|
|
|
17
|
+
| Route registration ceremony | The file tree **is** the route tree |
|
|
18
|
+
| Single-threaded Node under load | Auto-detected CPU count → worker processes |
|
|
19
|
+
| Blocking work on the request path | `enqueue()` ships it to a queue; respond immediately |
|
|
20
|
+
| Wiring auth, DB, and middleware by hand | `ignite()` — one call bootstraps everything |
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx create-efc-app my-api
|
|
28
|
+
cd my-api
|
|
29
|
+
efc start dev
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The interactive scaffolder asks for language, database, auth strategy, and whether you want clustering and background tasks — then writes the boilerplate, generates a `.env` with a real `JWT_SECRET`, and runs `npm install`.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Project Structure
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
my-api/
|
|
40
|
+
├── src/
|
|
41
|
+
│ ├── api/ # Every file here is a route
|
|
42
|
+
│ │ ├── health.ts # GET /health
|
|
43
|
+
│ │ ├── users/
|
|
44
|
+
│ │ │ ├── index.ts # GET /users • POST /users
|
|
45
|
+
│ │ │ └── [id].ts # GET /users/:id • DELETE /users/:id
|
|
46
|
+
│ │ └── posts/
|
|
47
|
+
│ │ └── [slug]/
|
|
48
|
+
│ │ └── comments.ts # GET /posts/:slug/comments
|
|
49
|
+
│ ├── tasks/ # Background jobs
|
|
50
|
+
│ │ ├── SendEmail.ts
|
|
51
|
+
│ │ └── ResizeImage.ts
|
|
52
|
+
│ ├── models/ # Engine-agnostic models
|
|
53
|
+
│ │ └── User.ts
|
|
54
|
+
│ └── index.ts # Framework entry point
|
|
55
|
+
├── efc.config.ts
|
|
56
|
+
├── .env # Gitignored — JWT_SECRET auto-filled
|
|
57
|
+
└── .env.example
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Routing rules:
|
|
61
|
+
|
|
62
|
+
| File | URL |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `api/health.ts` | `/health` |
|
|
65
|
+
| `api/users/index.ts` | `/users` |
|
|
66
|
+
| `api/users/[id].ts` | `/users/:id` |
|
|
67
|
+
| `api/posts/[slug]/comments.ts` | `/posts/:slug/comments` |
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Route Handlers
|
|
72
|
+
|
|
73
|
+
Export uppercase HTTP method names. Anything not exported returns **405 Method Not Allowed** automatically.
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
// src/api/users/index.ts
|
|
77
|
+
import type { Request, Response } from 'express';
|
|
78
|
+
import { User } from '../../models/User';
|
|
79
|
+
|
|
80
|
+
export const GET = async (req: Request, res: Response) => {
|
|
81
|
+
const users = await User.find();
|
|
82
|
+
res.json(users);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
export const POST = async (req: Request, res: Response) => {
|
|
86
|
+
const user = await User.create(req.body);
|
|
87
|
+
res.status(201).json({ id: user.id });
|
|
88
|
+
};
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
// src/api/users/[id].ts
|
|
93
|
+
import type { Request, Response } from 'express';
|
|
94
|
+
import { User } from '../../models/User';
|
|
95
|
+
import { HttpError } from 'express-file-cluster';
|
|
96
|
+
|
|
97
|
+
export const GET = async (req: Request, res: Response) => {
|
|
98
|
+
const user = await User.findById(req.params.id);
|
|
99
|
+
if (!user) throw new HttpError(404, 'User not found');
|
|
100
|
+
res.json(user);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export const DELETE = async (req: Request, res: Response) => {
|
|
104
|
+
await User.delete(req.params.id);
|
|
105
|
+
res.status(204).send();
|
|
106
|
+
};
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Middleware
|
|
112
|
+
|
|
113
|
+
Three tiers, each with a clear scope:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
// 1. Global — applies to every request
|
|
117
|
+
// CORS is built-in — configure it via CORS_ORIGINS in .env, not a separate package
|
|
118
|
+
ignite({ globalMiddlewares: [rateLimiter()] });
|
|
119
|
+
|
|
120
|
+
// 2. Route-level — applies to every handler in this file
|
|
121
|
+
export const middlewares = [requireAuth];
|
|
122
|
+
|
|
123
|
+
// 3. Handler-level — applies to one handler via compose()
|
|
124
|
+
import { compose } from 'express-file-cluster';
|
|
125
|
+
|
|
126
|
+
export const POST = compose(
|
|
127
|
+
validateBody(CreateUserSchema),
|
|
128
|
+
async (req, res) => { /* req.body is validated */ },
|
|
129
|
+
);
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Background Tasks
|
|
135
|
+
|
|
136
|
+
Tasks run off the request path — respond immediately, let the queue handle the work.
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
// src/tasks/SendEmail.ts
|
|
140
|
+
import { defineTask } from 'express-file-cluster/tasks';
|
|
141
|
+
|
|
142
|
+
interface Payload { to: string; subject: string; body: string }
|
|
143
|
+
|
|
144
|
+
export default defineTask<Payload>(async (payload) => {
|
|
145
|
+
await mailer.send(payload);
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
```ts
|
|
150
|
+
// src/tasks/ResizeImage.ts — CPU-bound: runs in a worker_threads thread
|
|
151
|
+
import { defineTask } from 'express-file-cluster/tasks';
|
|
152
|
+
|
|
153
|
+
export default defineTask<{ key: string; width: number }>(
|
|
154
|
+
{ thread: true },
|
|
155
|
+
async ({ key, width }) => {
|
|
156
|
+
const buf = await sharp(await download(key)).resize(width).toBuffer();
|
|
157
|
+
await upload(`${key}@${width}`, buf);
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
// Trigger from a route handler
|
|
164
|
+
import { enqueue } from 'express-file-cluster/tasks';
|
|
165
|
+
|
|
166
|
+
export const POST = async (req, res) => {
|
|
167
|
+
const user = await User.create(req.body);
|
|
168
|
+
await enqueue('SendEmail', { to: user.email, subject: 'Welcome!', body: '...' });
|
|
169
|
+
res.status(202).json({ id: user.id, queued: true });
|
|
170
|
+
};
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
Task options:
|
|
174
|
+
|
|
175
|
+
| Option | Default | Description |
|
|
176
|
+
|---|---|---|
|
|
177
|
+
| `thread` | `false` | Run in a `worker_threads` thread (CPU-bound work) |
|
|
178
|
+
| `retries` | `3` | Retry attempts before dead-lettering |
|
|
179
|
+
| `backoff` | `'exponential'` | Delay strategy between retries |
|
|
180
|
+
| `concurrency` | `tasks.concurrency` | Parallel jobs for this task |
|
|
181
|
+
| `schedule` | — | Cron expression for recurring tasks |
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Authentication
|
|
186
|
+
|
|
187
|
+
### `http-only` (recommended for SSR/SSG)
|
|
188
|
+
|
|
189
|
+
Tokens stored in `HttpOnly + Secure + SameSite=Strict` cookies.
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import { issueToken, revokeToken, requireAuth } from 'express-file-cluster/auth';
|
|
193
|
+
|
|
194
|
+
// src/api/auth/login.ts
|
|
195
|
+
export const POST = async (req, res) => {
|
|
196
|
+
const user = await verifyCredentials(req.body);
|
|
197
|
+
issueToken(res, { sub: user.id, role: user.role });
|
|
198
|
+
res.json({ message: 'Logged in' });
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// Protect a route
|
|
202
|
+
export const middlewares = [requireAuth];
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### `localStorage` (SPA-friendly)
|
|
206
|
+
|
|
207
|
+
Token returned in body; client attaches `Authorization: Bearer <token>`.
|
|
208
|
+
|
|
209
|
+
```ts
|
|
210
|
+
import { signToken } from 'express-file-cluster/auth';
|
|
211
|
+
|
|
212
|
+
export const POST = async (req, res) => {
|
|
213
|
+
const token = signToken({ sub: user.id });
|
|
214
|
+
res.json({ token });
|
|
215
|
+
};
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Bootstrapper
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
// src/index.ts
|
|
224
|
+
import { ignite } from 'express-file-cluster';
|
|
225
|
+
import path from 'path';
|
|
226
|
+
import { fileURLToPath } from 'url';
|
|
227
|
+
|
|
228
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
229
|
+
|
|
230
|
+
ignite({
|
|
231
|
+
port: Number(process.env.PORT) || 3000,
|
|
232
|
+
apiDir: path.join(__dirname, 'api'),
|
|
233
|
+
tasksDir: path.join(__dirname, 'tasks'),
|
|
234
|
+
|
|
235
|
+
database: 'mongodb',
|
|
236
|
+
databaseUrl: process.env.DATABASE_URL,
|
|
237
|
+
|
|
238
|
+
authStrategy: 'http-only',
|
|
239
|
+
jwtSecret: process.env.JWT_SECRET,
|
|
240
|
+
|
|
241
|
+
cluster: true, // false → single process (auto-disabled in dev)
|
|
242
|
+
workers: 4, // defaults to os.cpus().length
|
|
243
|
+
|
|
244
|
+
tasks: {
|
|
245
|
+
backend: 'bullmq',
|
|
246
|
+
redisUrl: process.env.REDIS_URL,
|
|
247
|
+
concurrency: 5,
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
globalMiddlewares: [],
|
|
251
|
+
onWorkerReady: (id) => console.log(`Worker ${id} ready`),
|
|
252
|
+
onWorkerCrash: (id, code) => console.error(`Worker ${id} crashed (${code})`),
|
|
253
|
+
});
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### `ignite()` options
|
|
257
|
+
|
|
258
|
+
| Option | Type | Default | Description |
|
|
259
|
+
|---|---|---|---|
|
|
260
|
+
| `port` | `number` | `3000` | HTTP listen port |
|
|
261
|
+
| `apiDir` | `string` | — | Path to route modules |
|
|
262
|
+
| `tasksDir` | `string` | — | Path to task modules |
|
|
263
|
+
| `database` | `'mongodb' \| 'postgresql'` | — | Database engine |
|
|
264
|
+
| `databaseUrl` | `string` | `DATABASE_URL` | Connection string |
|
|
265
|
+
| `authStrategy` | `'http-only' \| 'localStorage'` | — | Token delivery |
|
|
266
|
+
| `jwtSecret` | `string` | `JWT_SECRET` | JWT signing secret |
|
|
267
|
+
| `cluster` | `boolean` | `true` | Enable multi-core clustering |
|
|
268
|
+
| `workers` | `number` | `os.cpus().length` | Worker count override |
|
|
269
|
+
| `tasks` | `TaskConfig \| false` | `false` | Background task runtime |
|
|
270
|
+
| `cors` | `boolean \| CorsConfig` | `true` | CORS — origins driven by `CORS_ORIGINS` env var |
|
|
271
|
+
| `globalMiddlewares` | `RequestHandler[]` | `[]` | Applied to every route |
|
|
272
|
+
| `onWorkerReady` | `(id) => void` | — | Called when a worker boots |
|
|
273
|
+
| `onWorkerCrash` | `(id, code) => void` | — | Called before respawn |
|
|
274
|
+
| `onError` | `ErrorRequestHandler` | built-in | Override global error handler |
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## CLI Reference
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
# Development
|
|
282
|
+
efc start dev # Hot-reload single process, source maps, pretty logs
|
|
283
|
+
|
|
284
|
+
# Production
|
|
285
|
+
efc build prod # Type-check + compile to dist/ (tsup, dual CJS/ESM)
|
|
286
|
+
efc start prod # Run dist/ with clustering enabled
|
|
287
|
+
|
|
288
|
+
# Tests
|
|
289
|
+
efc run tests # Vitest (--watch, --coverage passthrough)
|
|
290
|
+
|
|
291
|
+
# Code generation
|
|
292
|
+
efc generate route users/[id] # → src/api/users/[id].ts
|
|
293
|
+
efc generate task ProcessPayment # → src/tasks/ProcessPayment.ts
|
|
294
|
+
efc generate middleware authorize # → src/middlewares/authorize.ts
|
|
295
|
+
|
|
296
|
+
# Diagnostics
|
|
297
|
+
efc routes # Print resolved route table (path → file → methods)
|
|
298
|
+
efc tasks # List registered background tasks
|
|
299
|
+
efc doctor # Validate config, env vars, DB connectivity
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Clustering Architecture
|
|
305
|
+
|
|
306
|
+
```
|
|
307
|
+
Master Process
|
|
308
|
+
┌────────────────────┐
|
|
309
|
+
│ fork × N workers │
|
|
310
|
+
│ respawn on crash │
|
|
311
|
+
└──┬──────┬──────┬───┘
|
|
312
|
+
│ │ │
|
|
313
|
+
Worker 1 Worker 2 Worker N
|
|
314
|
+
Pre-Flight lifecycle per worker:
|
|
315
|
+
1. Connect DB
|
|
316
|
+
2. Configure auth
|
|
317
|
+
3. Scan apiDir → route map
|
|
318
|
+
4. Register tasks
|
|
319
|
+
5. Mount routes on Express
|
|
320
|
+
6. Listen (OS round-robins connections)
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
CPU-bound tasks fan out further into `worker_threads` — the request loop stays unblocked at every layer.
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Error Handling
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
import { HttpError } from 'express-file-cluster';
|
|
331
|
+
|
|
332
|
+
// Throw from any handler — caught and formatted automatically
|
|
333
|
+
export const GET = async (req, res) => {
|
|
334
|
+
const user = await User.findById(req.params.id);
|
|
335
|
+
if (!user) throw new HttpError(404, 'User not found');
|
|
336
|
+
res.json(user);
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
// Override the global handler
|
|
340
|
+
ignite({
|
|
341
|
+
onError: (err, req, res, next) => {
|
|
342
|
+
logger.error(err);
|
|
343
|
+
res.status(err.statusCode ?? 500).json({ error: err.message });
|
|
344
|
+
},
|
|
345
|
+
});
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
## Environment Variables
|
|
351
|
+
|
|
352
|
+
`create-efc-app` generates `.env` (gitignored, `JWT_SECRET` pre-filled) and `.env.example` (committed, documented).
|
|
353
|
+
|
|
354
|
+
| Variable | Required | Description |
|
|
355
|
+
|---|---|---|
|
|
356
|
+
| `PORT` | No (default `3000`) | HTTP listen port |
|
|
357
|
+
| `NODE_ENV` | No | `development \| production \| test` |
|
|
358
|
+
| `DATABASE_URL` | Yes | MongoDB or PostgreSQL connection string |
|
|
359
|
+
| `JWT_SECRET` | Yes | JWT signing key — auto-generated by scaffolder |
|
|
360
|
+
| `JWT_EXPIRES_IN` | No (default `7d`) | Token lifetime |
|
|
361
|
+
| `COOKIE_DOMAIN` | No | Cookie domain for `http-only` auth |
|
|
362
|
+
| `REDIS_URL` | If using BullMQ | Redis connection for the task queue |
|
|
363
|
+
| `CORS_ORIGINS` | No | Comma-separated allowed origins — e.g. `http://localhost:3000,https://myapp.com` |
|
|
364
|
+
|
|
365
|
+
## License
|
|
366
|
+
|
|
367
|
+
MIT © 2026 EFC Contributors
|
package/dist/auth/index.cjs
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
|
|
7
|
-
var
|
|
7
|
+
var _chunkUUF2OYRWcjs = require('../chunk-UUF2OYRW.cjs');
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
exports.configureAuth =
|
|
14
|
+
exports.configureAuth = _chunkUUF2OYRWcjs.configureAuth; exports.issueToken = _chunkUUF2OYRWcjs.issueToken; exports.requireAuth = _chunkUUF2OYRWcjs.requireAuth; exports.revokeToken = _chunkUUF2OYRWcjs.revokeToken; exports.signToken = _chunkUUF2OYRWcjs.signToken;
|
|
15
15
|
//# sourceMappingURL=index.cjs.map
|
package/dist/auth/index.d.cts
CHANGED
package/dist/auth/index.d.ts
CHANGED
package/dist/auth/index.js
CHANGED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/auth/index.ts"],"sourcesContent":["import type { RequestHandler, Response } from 'express';\nimport { SignJWT, jwtVerify } from 'jose';\nimport type { AuthStrategy } from '../types.js';\n\ninterface AuthConfig {\n secret: string;\n strategy: AuthStrategy;\n expiresIn: string;\n cookieDomain?: string | undefined;\n}\n\nlet _config: AuthConfig | null = null;\n\nexport function configureAuth(config: AuthConfig): void {\n _config = config;\n}\n\nfunction getConfig(): AuthConfig {\n if (!_config) throw new Error('[EFC] Auth not configured — pass jwtSecret to ignite()');\n return _config;\n}\n\nexport async function issueToken(res: Response, payload: Record<string, unknown>): Promise<void> {\n const { secret, expiresIn, cookieDomain } = getConfig();\n const encodedSecret = new TextEncoder().encode(secret);\n const token = await new SignJWT(payload)\n .setProtectedHeader({ alg: 'HS256' })\n .setExpirationTime(expiresIn)\n .sign(encodedSecret);\n\n res.cookie('efc_token', token, {\n httpOnly: true,\n secure: process.env['NODE_ENV'] === 'production',\n sameSite: 'strict',\n ...(cookieDomain !== undefined && { domain: cookieDomain }),\n });\n}\n\nexport function revokeToken(res: Response): void {\n res.clearCookie('efc_token');\n}\n\nexport async function signToken(payload: Record<string, unknown>): Promise<string> {\n const { secret, expiresIn } = getConfig();\n const encodedSecret = new TextEncoder().encode(secret);\n return await new SignJWT(payload)\n .setProtectedHeader({ alg: 'HS256' })\n .setExpirationTime(expiresIn)\n .sign(encodedSecret);\n}\n\nexport const requireAuth: RequestHandler = async (req, res, next) => {\n const { secret, strategy } = getConfig();\n\n try {\n let token: string | undefined;\n\n if (strategy === 'http-only') {\n const cookies = (req as typeof req & { cookies: Record<string, string> }).cookies;\n token = cookies?.['efc_token'];\n } else {\n const auth = req.headers['authorization'];\n if (typeof auth === 'string' && auth.startsWith('Bearer ')) {\n token = auth.slice(7);\n }\n }\n\n if (!token) {\n res.status(401).json({ error: 'Unauthorized' });\n return;\n }\n\n const encodedSecret = new TextEncoder().encode(secret);\n const { payload } = await jwtVerify(token, encodedSecret);\n (req as typeof req & { user: unknown }).user = payload;\n next();\n } catch {\n res.status(401).json({ error: 'Unauthorized' });\n }\n};\n"],"mappings":";AACA,SAAS,SAAS,iBAAiB;AAUnC,IAAI,UAA6B;AAE1B,SAAS,cAAc,QAA0B;AACtD,YAAU;AACZ;AAEA,SAAS,YAAwB;AAC/B,MAAI,CAAC,QAAS,OAAM,IAAI,MAAM,6DAAwD;AACtF,SAAO;AACT;AAEA,eAAsB,WAAW,KAAe,SAAiD;AAC/F,QAAM,EAAE,QAAQ,WAAW,aAAa,IAAI,UAAU;AACtD,QAAM,gBAAgB,IAAI,YAAY,EAAE,OAAO,MAAM;AACrD,QAAM,QAAQ,MAAM,IAAI,QAAQ,OAAO,EACpC,mBAAmB,EAAE,KAAK,QAAQ,CAAC,EACnC,kBAAkB,SAAS,EAC3B,KAAK,aAAa;AAErB,MAAI,OAAO,aAAa,OAAO;AAAA,IAC7B,UAAU;AAAA,IACV,QAAQ,QAAQ,IAAI,UAAU,MAAM;AAAA,IACpC,UAAU;AAAA,IACV,GAAI,iBAAiB,UAAa,EAAE,QAAQ,aAAa;AAAA,EAC3D,CAAC;AACH;AAEO,SAAS,YAAY,KAAqB;AAC/C,MAAI,YAAY,WAAW;AAC7B;AAEA,eAAsB,UAAU,SAAmD;AACjF,QAAM,EAAE,QAAQ,UAAU,IAAI,UAAU;AACxC,QAAM,gBAAgB,IAAI,YAAY,EAAE,OAAO,MAAM;AACrD,SAAO,MAAM,IAAI,QAAQ,OAAO,EAC7B,mBAAmB,EAAE,KAAK,QAAQ,CAAC,EACnC,kBAAkB,SAAS,EAC3B,KAAK,aAAa;AACvB;AAEO,IAAM,cAA8B,OAAO,KAAK,KAAK,SAAS;AACnE,QAAM,EAAE,QAAQ,SAAS,IAAI,UAAU;AAEvC,MAAI;AACF,QAAI;AAEJ,QAAI,aAAa,aAAa;AAC5B,YAAM,UAAW,IAAyD;AAC1E,cAAQ,UAAU,WAAW;AAAA,IAC/B,OAAO;AACL,YAAM,OAAO,IAAI,QAAQ,eAAe;AACxC,UAAI,OAAO,SAAS,YAAY,KAAK,WAAW,SAAS,GAAG;AAC1D,gBAAQ,KAAK,MAAM,CAAC;AAAA,MACtB;AAAA,IACF;AAEA,QAAI,CAAC,OAAO;AACV,UAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,eAAe,CAAC;AAC9C;AAAA,IACF;AAEA,UAAM,gBAAgB,IAAI,YAAY,EAAE,OAAO,MAAM;AACrD,UAAM,EAAE,QAAQ,IAAI,MAAM,UAAU,OAAO,aAAa;AACxD,IAAC,IAAuC,OAAO;AAC/C,SAAK;AAAA,EACP,QAAQ;AACN,QAAI,OAAO,GAAG,EAAE,KAAK,EAAE,OAAO,eAAe,CAAC;AAAA,EAChD;AACF;","names":[]}
|
|
@@ -22,9 +22,7 @@ function setEnqueueImpl(impl) {
|
|
|
22
22
|
}
|
|
23
23
|
async function enqueue(name, payload) {
|
|
24
24
|
if (!_impl) {
|
|
25
|
-
throw new Error(
|
|
26
|
-
`[EFC] Task queue not initialised. Set tasks.backend in ignite() config.`
|
|
27
|
-
);
|
|
25
|
+
throw new Error(`[EFC] Task queue not initialised. Set tasks.backend in ignite() config.`);
|
|
28
26
|
}
|
|
29
27
|
return _impl(name, payload);
|
|
30
28
|
}
|
|
@@ -39,4 +37,4 @@ export {
|
|
|
39
37
|
enqueue,
|
|
40
38
|
registerTask
|
|
41
39
|
};
|
|
42
|
-
//# sourceMappingURL=chunk-
|
|
40
|
+
//# sourceMappingURL=chunk-LPZEKFVN.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tasks/index.ts"],"sourcesContent":["import type { TaskDefinition, TaskOptions } from '../types.js';\n\nexport const taskRegistry = new Map<string, TaskDefinition>();\n\ntype HandlerFn<T> = (payload: T) => Promise<void>;\ntype DefineTaskOverload = {\n <T>(handler: HandlerFn<T>): TaskDefinition<T>;\n <T>(options: TaskOptions, handler: HandlerFn<T>): TaskDefinition<T>;\n};\n\nexport const defineTask: DefineTaskOverload = <T>(\n handlerOrOptions: HandlerFn<T> | TaskOptions,\n maybeHandler?: HandlerFn<T>,\n): TaskDefinition<T> => {\n let options: TaskOptions = {};\n let handler: HandlerFn<T>;\n\n if (typeof handlerOrOptions === 'function') {\n handler = handlerOrOptions;\n } else {\n options = handlerOrOptions;\n if (!maybeHandler) throw new Error('[EFC] defineTask: handler function is required');\n handler = maybeHandler;\n }\n\n return {\n handler: handler as (payload: unknown) => Promise<void>,\n options,\n name: '',\n };\n};\n\ntype EnqueueImpl = (name: string, payload: unknown) => Promise<void>;\nlet _impl: EnqueueImpl | null = null;\n\nexport function setEnqueueImpl(impl: EnqueueImpl): void {\n _impl = impl;\n}\n\nexport async function enqueue<T>(name: string, payload: T): Promise<void> {\n if (!_impl) {\n throw new Error(
|
|
1
|
+
{"version":3,"sources":["../src/tasks/index.ts"],"sourcesContent":["import type { TaskDefinition, TaskOptions } from '../types.js';\n\nexport const taskRegistry = new Map<string, TaskDefinition>();\n\ntype HandlerFn<T> = (payload: T) => Promise<void>;\ntype DefineTaskOverload = {\n <T>(handler: HandlerFn<T>): TaskDefinition<T>;\n <T>(options: TaskOptions, handler: HandlerFn<T>): TaskDefinition<T>;\n};\n\nexport const defineTask: DefineTaskOverload = <T>(\n handlerOrOptions: HandlerFn<T> | TaskOptions,\n maybeHandler?: HandlerFn<T>,\n): TaskDefinition<T> => {\n let options: TaskOptions = {};\n let handler: HandlerFn<T>;\n\n if (typeof handlerOrOptions === 'function') {\n handler = handlerOrOptions;\n } else {\n options = handlerOrOptions;\n if (!maybeHandler) throw new Error('[EFC] defineTask: handler function is required');\n handler = maybeHandler;\n }\n\n return {\n handler: handler as (payload: unknown) => Promise<void>,\n options,\n name: '',\n };\n};\n\ntype EnqueueImpl = (name: string, payload: unknown) => Promise<void>;\nlet _impl: EnqueueImpl | null = null;\n\nexport function setEnqueueImpl(impl: EnqueueImpl): void {\n _impl = impl;\n}\n\nexport async function enqueue<T>(name: string, payload: T): Promise<void> {\n if (!_impl) {\n throw new Error(`[EFC] Task queue not initialised. Set tasks.backend in ignite() config.`);\n }\n return _impl(name, payload as unknown);\n}\n\nexport function registerTask(name: string, def: TaskDefinition): void {\n taskRegistry.set(name, { ...def, name });\n}\n"],"mappings":";AAEO,IAAM,eAAe,oBAAI,IAA4B;AAQrD,IAAM,aAAiC,CAC5C,kBACA,iBACsB;AACtB,MAAI,UAAuB,CAAC;AAC5B,MAAI;AAEJ,MAAI,OAAO,qBAAqB,YAAY;AAC1C,cAAU;AAAA,EACZ,OAAO;AACL,cAAU;AACV,QAAI,CAAC,aAAc,OAAM,IAAI,MAAM,gDAAgD;AACnF,cAAU;AAAA,EACZ;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,MAAM;AAAA,EACR;AACF;AAGA,IAAI,QAA4B;AAEzB,SAAS,eAAe,MAAyB;AACtD,UAAQ;AACV;AAEA,eAAsB,QAAW,MAAc,SAA2B;AACxE,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,yEAAyE;AAAA,EAC3F;AACA,SAAO,MAAM,MAAM,OAAkB;AACvC;AAEO,SAAS,aAAa,MAAc,KAA2B;AACpE,eAAa,IAAI,MAAM,EAAE,GAAG,KAAK,KAAK,CAAC;AACzC;","names":[]}
|
|
@@ -60,4 +60,4 @@ var requireAuth = async (req, res, next) => {
|
|
|
60
60
|
|
|
61
61
|
|
|
62
62
|
exports.configureAuth = configureAuth; exports.issueToken = issueToken; exports.revokeToken = revokeToken; exports.signToken = signToken; exports.requireAuth = requireAuth;
|
|
63
|
-
//# sourceMappingURL=chunk-
|
|
63
|
+
//# sourceMappingURL=chunk-UUF2OYRW.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/efc.js/efc.js/packages/core/dist/chunk-
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/efc.js/efc.js/packages/core/dist/chunk-UUF2OYRW.cjs","../src/auth/index.ts"],"names":[],"mappings":"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","file":"/home/runner/work/efc.js/efc.js/packages/core/dist/chunk-UUF2OYRW.cjs","sourcesContent":[null,"import type { RequestHandler, Response } from 'express';\nimport { SignJWT, jwtVerify } from 'jose';\nimport type { AuthStrategy } from '../types.js';\n\ninterface AuthConfig {\n secret: string;\n strategy: AuthStrategy;\n expiresIn: string;\n cookieDomain?: string | undefined;\n}\n\nlet _config: AuthConfig | null = null;\n\nexport function configureAuth(config: AuthConfig): void {\n _config = config;\n}\n\nfunction getConfig(): AuthConfig {\n if (!_config) throw new Error('[EFC] Auth not configured — pass jwtSecret to ignite()');\n return _config;\n}\n\nexport async function issueToken(res: Response, payload: Record<string, unknown>): Promise<void> {\n const { secret, expiresIn, cookieDomain } = getConfig();\n const encodedSecret = new TextEncoder().encode(secret);\n const token = await new SignJWT(payload)\n .setProtectedHeader({ alg: 'HS256' })\n .setExpirationTime(expiresIn)\n .sign(encodedSecret);\n\n res.cookie('efc_token', token, {\n httpOnly: true,\n secure: process.env['NODE_ENV'] === 'production',\n sameSite: 'strict',\n ...(cookieDomain !== undefined && { domain: cookieDomain }),\n });\n}\n\nexport function revokeToken(res: Response): void {\n res.clearCookie('efc_token');\n}\n\nexport async function signToken(payload: Record<string, unknown>): Promise<string> {\n const { secret, expiresIn } = getConfig();\n const encodedSecret = new TextEncoder().encode(secret);\n return await new SignJWT(payload)\n .setProtectedHeader({ alg: 'HS256' })\n .setExpirationTime(expiresIn)\n .sign(encodedSecret);\n}\n\nexport const requireAuth: RequestHandler = async (req, res, next) => {\n const { secret, strategy } = getConfig();\n\n try {\n let token: string | undefined;\n\n if (strategy === 'http-only') {\n const cookies = (req as typeof req & { cookies: Record<string, string> }).cookies;\n token = cookies?.['efc_token'];\n } else {\n const auth = req.headers['authorization'];\n if (typeof auth === 'string' && auth.startsWith('Bearer ')) {\n token = auth.slice(7);\n }\n }\n\n if (!token) {\n res.status(401).json({ error: 'Unauthorized' });\n return;\n }\n\n const encodedSecret = new TextEncoder().encode(secret);\n const { payload } = await jwtVerify(token, encodedSecret);\n (req as typeof req & { user: unknown }).user = payload;\n next();\n } catch {\n res.status(401).json({ error: 'Unauthorized' });\n }\n};\n"]}
|
|
@@ -22,9 +22,7 @@ function setEnqueueImpl(impl) {
|
|
|
22
22
|
}
|
|
23
23
|
async function enqueue(name, payload) {
|
|
24
24
|
if (!_impl) {
|
|
25
|
-
throw new Error(
|
|
26
|
-
`[EFC] Task queue not initialised. Set tasks.backend in ignite() config.`
|
|
27
|
-
);
|
|
25
|
+
throw new Error(`[EFC] Task queue not initialised. Set tasks.backend in ignite() config.`);
|
|
28
26
|
}
|
|
29
27
|
return _impl(name, payload);
|
|
30
28
|
}
|
|
@@ -39,4 +37,4 @@ function registerTask(name, def) {
|
|
|
39
37
|
|
|
40
38
|
|
|
41
39
|
exports.taskRegistry = taskRegistry; exports.defineTask = defineTask; exports.setEnqueueImpl = setEnqueueImpl; exports.enqueue = enqueue; exports.registerTask = registerTask;
|
|
42
|
-
//# sourceMappingURL=chunk-
|
|
40
|
+
//# sourceMappingURL=chunk-XH7O25BL.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/efc.js/efc.js/packages/core/dist/chunk-XH7O25BL.cjs","../src/tasks/index.ts"],"names":[],"mappings":"AAAA;ACEO,IAAM,aAAA,kBAAe,IAAI,GAAA,CAA4B,CAAA;AAQrD,IAAM,WAAA,EAAiC,CAC5C,gBAAA,EACA,YAAA,EAAA,GACsB;AACtB,EAAA,IAAI,QAAA,EAAuB,CAAC,CAAA;AAC5B,EAAA,IAAI,OAAA;AAEJ,EAAA,GAAA,CAAI,OAAO,iBAAA,IAAqB,UAAA,EAAY;AAC1C,IAAA,QAAA,EAAU,gBAAA;AAAA,EACZ,EAAA,KAAO;AACL,IAAA,QAAA,EAAU,gBAAA;AACV,IAAA,GAAA,CAAI,CAAC,YAAA,EAAc,MAAM,IAAI,KAAA,CAAM,gDAAgD,CAAA;AACnF,IAAA,QAAA,EAAU,YAAA;AAAA,EACZ;AAEA,EAAA,OAAO;AAAA,IACL,OAAA;AAAA,IACA,OAAA;AAAA,IACA,IAAA,EAAM;AAAA,EACR,CAAA;AACF,CAAA;AAGA,IAAI,MAAA,EAA4B,IAAA;AAEzB,SAAS,cAAA,CAAe,IAAA,EAAyB;AACtD,EAAA,MAAA,EAAQ,IAAA;AACV;AAEA,MAAA,SAAsB,OAAA,CAAW,IAAA,EAAc,OAAA,EAA2B;AACxE,EAAA,GAAA,CAAI,CAAC,KAAA,EAAO;AACV,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,uEAAA,CAAyE,CAAA;AAAA,EAC3F;AACA,EAAA,OAAO,KAAA,CAAM,IAAA,EAAM,OAAkB,CAAA;AACvC;AAEO,SAAS,YAAA,CAAa,IAAA,EAAc,GAAA,EAA2B;AACpE,EAAA,YAAA,CAAa,GAAA,CAAI,IAAA,EAAM,EAAE,GAAG,GAAA,EAAK,KAAK,CAAC,CAAA;AACzC;ADjBA;AACA;AACE;AACA;AACA;AACA;AACA;AACF,8KAAC","file":"/home/runner/work/efc.js/efc.js/packages/core/dist/chunk-XH7O25BL.cjs","sourcesContent":[null,"import type { TaskDefinition, TaskOptions } from '../types.js';\n\nexport const taskRegistry = new Map<string, TaskDefinition>();\n\ntype HandlerFn<T> = (payload: T) => Promise<void>;\ntype DefineTaskOverload = {\n <T>(handler: HandlerFn<T>): TaskDefinition<T>;\n <T>(options: TaskOptions, handler: HandlerFn<T>): TaskDefinition<T>;\n};\n\nexport const defineTask: DefineTaskOverload = <T>(\n handlerOrOptions: HandlerFn<T> | TaskOptions,\n maybeHandler?: HandlerFn<T>,\n): TaskDefinition<T> => {\n let options: TaskOptions = {};\n let handler: HandlerFn<T>;\n\n if (typeof handlerOrOptions === 'function') {\n handler = handlerOrOptions;\n } else {\n options = handlerOrOptions;\n if (!maybeHandler) throw new Error('[EFC] defineTask: handler function is required');\n handler = maybeHandler;\n }\n\n return {\n handler: handler as (payload: unknown) => Promise<void>,\n options,\n name: '',\n };\n};\n\ntype EnqueueImpl = (name: string, payload: unknown) => Promise<void>;\nlet _impl: EnqueueImpl | null = null;\n\nexport function setEnqueueImpl(impl: EnqueueImpl): void {\n _impl = impl;\n}\n\nexport async function enqueue<T>(name: string, payload: T): Promise<void> {\n if (!_impl) {\n throw new Error(`[EFC] Task queue not initialised. Set tasks.backend in ignite() config.`);\n }\n return _impl(name, payload as unknown);\n}\n\nexport function registerTask(name: string, def: TaskDefinition): void {\n taskRegistry.set(name, { ...def, name });\n}\n"]}
|
package/dist/cli/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/efc.js/efc.js/packages/core/dist/cli/index.cjs","../../src/cli/index.ts","../../src/cli/commands/start.ts","../../src/cli/commands/build.ts","../../src/cli/commands/run.ts","../../src/cli/commands/generate.ts","../../src/cli/commands/diagnostics.ts"],"names":["path","pc"],"mappings":"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","file":"/home/runner/work/efc.js/efc.js/packages/core/dist/cli/index.cjs","sourcesContent":[null,"#!/usr/bin/env node\nimport { Command } from 'commander';\nimport { startCommand } from './commands/start.js';\nimport { buildCommand } from './commands/build.js';\nimport { runCommand } from './commands/run.js';\nimport { generateCommand } from './commands/generate.js';\nimport { diagnosticsCommands } from './commands/diagnostics.js';\n\nconst program = new Command('efc')\n .description('Express File Cluster CLI')\n .version('0.1.0');\n\nprogram.addCommand(startCommand());\nprogram.addCommand(buildCommand());\nprogram.addCommand(runCommand());\nprogram.addCommand(generateCommand());\n\nfor (const cmd of diagnosticsCommands()) {\n program.addCommand(cmd);\n}\n\nprogram.parse(process.argv);\n","import { Command } from 'commander';\nimport { spawn } from 'node:child_process';\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport pc from 'picocolors';\n\nexport function startCommand(): Command {\n const cmd = new Command('start');\n\n cmd\n .argument('<mode>', 'dev | prod')\n .description('Start the EFC server')\n .action((mode: string) => {\n if (mode === 'dev') {\n startDev();\n } else if (mode === 'prod') {\n startProd();\n } else {\n console.error(pc.red(`Unknown mode: ${mode}. Use 'dev' or 'prod'.`));\n process.exit(1);\n }\n });\n\n return cmd;\n}\n\nfunction parseDotenv(cwd: string): Record<string, string> {\n const envFile = path.join(cwd, '.env');\n if (!fs.existsSync(envFile)) return {};\n const vars: Record<string, string> = {};\n for (const line of fs.readFileSync(envFile, 'utf8').split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n const eq = trimmed.indexOf('=');\n if (eq === -1) continue;\n const key = trimmed.slice(0, eq).trim();\n const raw = trimmed.slice(eq + 1).trim();\n vars[key] = raw.replace(/^(['\"])(.*)\\1$/, '$2');\n }\n return vars;\n}\n\nfunction startDev(): void {\n const entry = resolveEntry();\n if (!entry) {\n console.error(pc.red('[EFC] Could not find entry point. Expected src/index.ts or index.ts'));\n process.exit(1);\n }\n\n console.log(pc.cyan('[EFC] Starting development server…'));\n console.log(pc.dim(` Entry: ${entry}`));\n\n const cwd = process.cwd();\n const localTsx = path.join(cwd, 'node_modules', '.bin', 'tsx');\n const tsx = fs.existsSync(localTsx) ? localTsx : 'tsx';\n\n // .env values are base; existing process.env takes precedence (same as dotenv default)\n const env: NodeJS.ProcessEnv = { ...parseDotenv(cwd), ...process.env, NODE_ENV: 'development' };\n\n const child = spawn(tsx, ['watch', '--include', 'src', entry], { stdio: 'inherit', env });\n child.on('exit', (code) => process.exit(code ?? 0));\n}\n\nfunction startProd(): void {\n const cwd = process.cwd();\n const distEntry = path.join(cwd, 'dist', 'index.js');\n\n if (!fs.existsSync(distEntry)) {\n console.error(pc.red('[EFC] dist/index.js not found. Run `efc build prod` first.'));\n process.exit(1);\n }\n\n console.log(pc.cyan('[EFC] Starting production server…'));\n\n // Production env comes exclusively from process.env — no .env file loading.\n // Platforms (Docker, Kubernetes, Railway, Heroku, etc.) inject vars directly.\n const child = spawn('node', [distEntry], {\n stdio: 'inherit',\n env: { ...process.env, NODE_ENV: 'production' },\n });\n child.on('exit', (code) => process.exit(code ?? 0));\n}\n\nfunction resolveEntry(): string | null {\n const cwd = process.cwd();\n const candidates = [\n path.join(cwd, 'src', 'index.ts'),\n path.join(cwd, 'index.ts'),\n path.join(cwd, 'src', 'index.js'),\n path.join(cwd, 'index.js'),\n ];\n return candidates.find((f) => fs.existsSync(f)) ?? null;\n}\n","import { Command } from 'commander';\nimport { spawn } from 'node:child_process';\nimport pc from 'picocolors';\n\nexport function buildCommand(): Command {\n const cmd = new Command('build');\n\n cmd\n .argument('<mode>', 'prod')\n .description('Build the application for production')\n .action((mode: string) => {\n if (mode !== 'prod') {\n console.error(pc.red(`Unknown build mode: ${mode}. Use 'prod'.`));\n process.exit(1);\n }\n buildProd();\n });\n\n return cmd;\n}\n\nfunction buildProd(): void {\n console.log(pc.cyan('[EFC] Building for production…'));\n\n // Type-check first\n const tsc = spawn('npx', ['tsc', '--noEmit'], { stdio: 'inherit' });\n\n tsc.on('exit', (code) => {\n if (code !== 0) {\n console.error(pc.red('[EFC] TypeScript errors found. Fix them before building.'));\n process.exit(1);\n }\n\n const tsup = spawn('npx', ['tsup'], { stdio: 'inherit' });\n tsup.on('exit', (tsupCode) => {\n if (tsupCode === 0) {\n console.log(pc.green('[EFC] Build complete → dist/'));\n } else {\n process.exit(tsupCode ?? 1);\n }\n });\n });\n}\n","import { Command } from 'commander';\nimport { spawn } from 'node:child_process';\nimport pc from 'picocolors';\n\nexport function runCommand(): Command {\n const cmd = new Command('run');\n\n cmd\n .argument('<runner>', 'tests')\n .description('Run EFC sub-commands (tests)')\n .allowUnknownOption()\n .action((runner: string) => {\n if (runner === 'tests') {\n runTests(cmd.args.slice(1));\n } else {\n console.error(pc.red(`Unknown runner: ${runner}. Use 'tests'.`));\n process.exit(1);\n }\n });\n\n return cmd;\n}\n\nfunction runTests(extraArgs: string[]): void {\n console.log(pc.cyan('[EFC] Running tests via Vitest…'));\n const child = spawn('npx', ['vitest', 'run', ...extraArgs], { stdio: 'inherit' });\n child.on('exit', (code) => process.exit(code ?? 0));\n}\n","import { Command } from 'commander';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport pc from 'picocolors';\n\nexport function generateCommand(): Command {\n const cmd = new Command('generate').alias('g').description('Scaffold EFC modules');\n\n cmd\n .command('route <routePath>')\n .description('Scaffold a route module, e.g. users/[id]')\n .action((routePath: string) => generateRoute(routePath));\n\n cmd\n .command('task <name>')\n .description('Scaffold a background task module')\n .action((name: string) => generateTask(name));\n\n cmd\n .command('middleware <name>')\n .description('Scaffold a middleware module')\n .action((name: string) => generateMiddleware(name));\n\n return cmd;\n}\n\nfunction writeFile(filePath: string, content: string): void {\n const dir = path.dirname(filePath);\n fs.mkdirSync(dir, { recursive: true });\n if (fs.existsSync(filePath)) {\n console.error(pc.red(`File already exists: ${filePath}`));\n process.exit(1);\n }\n fs.writeFileSync(filePath, content, 'utf8');\n console.log(pc.green(` created ${path.relative(process.cwd(), filePath)}`));\n}\n\nfunction generateRoute(routePath: string): void {\n const cwd = process.cwd();\n const filePath = path.join(cwd, 'src', 'api', `${routePath}.ts`);\n\n const content = `import type { Request, Response } from 'express';\n\nexport const GET = async (req: Request, res: Response) => {\n res.json({ message: 'OK' });\n};\n\nexport const POST = async (req: Request, res: Response) => {\n res.status(201).json({ message: 'Created' });\n};\n`;\n\n writeFile(filePath, content);\n console.log(pc.cyan(`[EFC] Route scaffolded`));\n}\n\nfunction generateTask(name: string): void {\n const cwd = process.cwd();\n const filePath = path.join(cwd, 'src', 'tasks', `${name}.ts`);\n\n const content = `import { defineTask } from 'express-file-cluster/tasks';\n\ninterface ${name}Payload {\n // TODO: define payload fields\n}\n\nexport default defineTask<${name}Payload>(async (payload) => {\n // TODO: implement task logic\n console.log('[Task:${name}]', payload);\n});\n`;\n\n writeFile(filePath, content);\n console.log(pc.cyan(`[EFC] Task scaffolded`));\n}\n\nfunction generateMiddleware(name: string): void {\n const cwd = process.cwd();\n const filePath = path.join(cwd, 'src', 'middlewares', `${name}.ts`);\n\n const content = `import type { Request, Response, NextFunction } from 'express';\n\nexport function ${name}(req: Request, res: Response, next: NextFunction): void {\n // TODO: implement middleware logic\n next();\n}\n`;\n\n writeFile(filePath, content);\n console.log(pc.cyan(`[EFC] Middleware scaffolded`));\n}\n","import { Command } from 'commander';\nimport { scanDir } from '../../router/scan.js';\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport pc from 'picocolors';\n\nexport function diagnosticsCommands(): Command[] {\n return [routesCommand(), tasksCommand(), doctorCommand()];\n}\n\nfunction routesCommand(): Command {\n return new Command('routes')\n .description('Print the resolved route table')\n .action(() => {\n const apiDir = resolveApiDir();\n if (!apiDir) {\n console.error(pc.red('[EFC] Could not find apiDir (expected src/api)'));\n process.exit(1);\n }\n\n const routes = scanDir(apiDir);\n if (routes.length === 0) {\n console.log(pc.yellow('No routes found.'));\n return;\n }\n\n console.log(pc.bold('\\n Route Table\\n'));\n console.log(pc.dim(' ' + '─'.repeat(60)));\n for (const route of routes) {\n const rel = path.relative(process.cwd(), route.filePath);\n console.log(` ${pc.cyan(route.urlPath.padEnd(35))} ${pc.dim(rel)}`);\n }\n console.log(pc.dim(' ' + '─'.repeat(60)));\n console.log(pc.dim(`\\n ${routes.length} route(s) found\\n`));\n });\n}\n\nfunction tasksCommand(): Command {\n return new Command('tasks')\n .description('List registered background tasks')\n .action(() => {\n const tasksDir = resolveTasksDir();\n if (!tasksDir || !fs.existsSync(tasksDir)) {\n console.log(pc.yellow('No tasks directory found.'));\n return;\n }\n\n const files = fs.readdirSync(tasksDir).filter((f) => /\\.(ts|js)$/.test(f));\n if (files.length === 0) {\n console.log(pc.yellow('No tasks found.'));\n return;\n }\n\n console.log(pc.bold('\\n Background Tasks\\n'));\n for (const file of files) {\n console.log(` ${pc.cyan(path.basename(file, path.extname(file)))}`);\n }\n console.log();\n });\n}\n\nfunction doctorCommand(): Command {\n return new Command('doctor')\n .description('Validate config, env vars, and project setup')\n .action(() => {\n const cwd = process.cwd();\n const checks: { label: string; ok: boolean; hint?: string }[] = [\n {\n label: 'package.json exists',\n ok: fs.existsSync(path.join(cwd, 'package.json')),\n },\n {\n label: 'tsconfig.json exists',\n ok: fs.existsSync(path.join(cwd, 'tsconfig.json')),\n hint: 'Run `tsc --init` to create one',\n },\n {\n label: 'src/api directory exists',\n ok: fs.existsSync(path.join(cwd, 'src', 'api')),\n hint: 'Create src/api/ and add route files',\n },\n {\n label: 'DATABASE_URL set',\n ok: Boolean(process.env['DATABASE_URL']),\n hint: 'Add DATABASE_URL to .env',\n },\n {\n label: 'JWT_SECRET set',\n ok: Boolean(process.env['JWT_SECRET']),\n hint: 'Add JWT_SECRET to .env (generate: openssl rand -hex 64)',\n },\n ];\n\n console.log(pc.bold('\\n EFC Doctor\\n'));\n let allOk = true;\n for (const check of checks) {\n const icon = check.ok ? pc.green('✓') : pc.red('✗');\n console.log(` ${icon} ${check.label}`);\n if (!check.ok) {\n allOk = false;\n if (check.hint) console.log(pc.dim(` → ${check.hint}`));\n }\n }\n console.log();\n if (allOk) {\n console.log(pc.green(' All checks passed!\\n'));\n } else {\n console.log(pc.yellow(' Some checks failed. Fix the issues above.\\n'));\n process.exit(1);\n }\n });\n}\n\nfunction resolveApiDir(): string | null {\n const cwd = process.cwd();\n const candidates = [path.join(cwd, 'src', 'api'), path.join(cwd, 'api')];\n return candidates.find((d) => fs.existsSync(d)) ?? null;\n}\n\nfunction resolveTasksDir(): string | null {\n const cwd = process.cwd();\n const candidates = [path.join(cwd, 'src', 'tasks'), path.join(cwd, 'tasks')];\n return candidates.find((d) => fs.existsSync(d)) ?? null;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/efc.js/efc.js/packages/core/dist/cli/index.cjs","../../src/cli/index.ts","../../src/cli/commands/start.ts","../../src/cli/commands/build.ts","../../src/cli/commands/run.ts","../../src/cli/commands/generate.ts","../../src/cli/commands/diagnostics.ts"],"names":["path","pc"],"mappings":"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","file":"/home/runner/work/efc.js/efc.js/packages/core/dist/cli/index.cjs","sourcesContent":[null,"#!/usr/bin/env node\nimport { Command } from 'commander';\nimport { startCommand } from './commands/start.js';\nimport { buildCommand } from './commands/build.js';\nimport { runCommand } from './commands/run.js';\nimport { generateCommand } from './commands/generate.js';\nimport { diagnosticsCommands } from './commands/diagnostics.js';\n\nconst program = new Command('efc').description('Express File Cluster CLI').version('0.1.0');\n\nprogram.addCommand(startCommand());\nprogram.addCommand(buildCommand());\nprogram.addCommand(runCommand());\nprogram.addCommand(generateCommand());\n\nfor (const cmd of diagnosticsCommands()) {\n program.addCommand(cmd);\n}\n\nprogram.parse(process.argv);\n","import { Command } from 'commander';\nimport { spawn } from 'node:child_process';\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport pc from 'picocolors';\n\nexport function startCommand(): Command {\n const cmd = new Command('start');\n\n cmd\n .argument('<mode>', 'dev | prod')\n .description('Start the EFC server')\n .action((mode: string) => {\n if (mode === 'dev') {\n startDev();\n } else if (mode === 'prod') {\n startProd();\n } else {\n console.error(pc.red(`Unknown mode: ${mode}. Use 'dev' or 'prod'.`));\n process.exit(1);\n }\n });\n\n return cmd;\n}\n\nfunction parseDotenv(cwd: string): Record<string, string> {\n const envFile = path.join(cwd, '.env');\n if (!fs.existsSync(envFile)) return {};\n const vars: Record<string, string> = {};\n for (const line of fs.readFileSync(envFile, 'utf8').split('\\n')) {\n const trimmed = line.trim();\n if (!trimmed || trimmed.startsWith('#')) continue;\n const eq = trimmed.indexOf('=');\n if (eq === -1) continue;\n const key = trimmed.slice(0, eq).trim();\n const raw = trimmed.slice(eq + 1).trim();\n vars[key] = raw.replace(/^(['\"])(.*)\\1$/, '$2');\n }\n return vars;\n}\n\nfunction startDev(): void {\n const entry = resolveEntry();\n if (!entry) {\n console.error(pc.red('[EFC] Could not find entry point. Expected src/index.ts or index.ts'));\n process.exit(1);\n }\n\n console.log(pc.cyan('[EFC] Starting development server…'));\n console.log(pc.dim(` Entry: ${entry}`));\n\n const cwd = process.cwd();\n const localTsx = path.join(cwd, 'node_modules', '.bin', 'tsx');\n const tsx = fs.existsSync(localTsx) ? localTsx : 'tsx';\n\n // .env values are base; existing process.env takes precedence (same as dotenv default)\n const env: NodeJS.ProcessEnv = { ...parseDotenv(cwd), ...process.env, NODE_ENV: 'development' };\n\n const child = spawn(tsx, ['watch', '--include', 'src', entry], { stdio: 'inherit', env });\n child.on('exit', (code) => process.exit(code ?? 0));\n}\n\nfunction startProd(): void {\n const cwd = process.cwd();\n const distEntry = path.join(cwd, 'dist', 'index.js');\n\n if (!fs.existsSync(distEntry)) {\n console.error(pc.red('[EFC] dist/index.js not found. Run `efc build prod` first.'));\n process.exit(1);\n }\n\n console.log(pc.cyan('[EFC] Starting production server…'));\n\n // Production env comes exclusively from process.env — no .env file loading.\n // Platforms (Docker, Kubernetes, Railway, Heroku, etc.) inject vars directly.\n const child = spawn('node', [distEntry], {\n stdio: 'inherit',\n env: { ...process.env, NODE_ENV: 'production' },\n });\n child.on('exit', (code) => process.exit(code ?? 0));\n}\n\nfunction resolveEntry(): string | null {\n const cwd = process.cwd();\n const candidates = [\n path.join(cwd, 'src', 'index.ts'),\n path.join(cwd, 'index.ts'),\n path.join(cwd, 'src', 'index.js'),\n path.join(cwd, 'index.js'),\n ];\n return candidates.find((f) => fs.existsSync(f)) ?? null;\n}\n","import { Command } from 'commander';\nimport { spawn } from 'node:child_process';\nimport pc from 'picocolors';\n\nexport function buildCommand(): Command {\n const cmd = new Command('build');\n\n cmd\n .argument('<mode>', 'prod')\n .description('Build the application for production')\n .action((mode: string) => {\n if (mode !== 'prod') {\n console.error(pc.red(`Unknown build mode: ${mode}. Use 'prod'.`));\n process.exit(1);\n }\n buildProd();\n });\n\n return cmd;\n}\n\nfunction buildProd(): void {\n console.log(pc.cyan('[EFC] Building for production…'));\n\n // Type-check first\n const tsc = spawn('npx', ['tsc', '--noEmit'], { stdio: 'inherit' });\n\n tsc.on('exit', (code) => {\n if (code !== 0) {\n console.error(pc.red('[EFC] TypeScript errors found. Fix them before building.'));\n process.exit(1);\n }\n\n const tsup = spawn('npx', ['tsup'], { stdio: 'inherit' });\n tsup.on('exit', (tsupCode) => {\n if (tsupCode === 0) {\n console.log(pc.green('[EFC] Build complete → dist/'));\n } else {\n process.exit(tsupCode ?? 1);\n }\n });\n });\n}\n","import { Command } from 'commander';\nimport { spawn } from 'node:child_process';\nimport pc from 'picocolors';\n\nexport function runCommand(): Command {\n const cmd = new Command('run');\n\n cmd\n .argument('<runner>', 'tests')\n .description('Run EFC sub-commands (tests)')\n .allowUnknownOption()\n .action((runner: string) => {\n if (runner === 'tests') {\n runTests(cmd.args.slice(1));\n } else {\n console.error(pc.red(`Unknown runner: ${runner}. Use 'tests'.`));\n process.exit(1);\n }\n });\n\n return cmd;\n}\n\nfunction runTests(extraArgs: string[]): void {\n console.log(pc.cyan('[EFC] Running tests via Vitest…'));\n const child = spawn('npx', ['vitest', 'run', ...extraArgs], { stdio: 'inherit' });\n child.on('exit', (code) => process.exit(code ?? 0));\n}\n","import { Command } from 'commander';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport pc from 'picocolors';\n\nexport function generateCommand(): Command {\n const cmd = new Command('generate').alias('g').description('Scaffold EFC modules');\n\n cmd\n .command('route <routePath>')\n .description('Scaffold a route module, e.g. users/[id]')\n .action((routePath: string) => generateRoute(routePath));\n\n cmd\n .command('task <name>')\n .description('Scaffold a background task module')\n .action((name: string) => generateTask(name));\n\n cmd\n .command('middleware <name>')\n .description('Scaffold a middleware module')\n .action((name: string) => generateMiddleware(name));\n\n return cmd;\n}\n\nfunction writeFile(filePath: string, content: string): void {\n const dir = path.dirname(filePath);\n fs.mkdirSync(dir, { recursive: true });\n if (fs.existsSync(filePath)) {\n console.error(pc.red(`File already exists: ${filePath}`));\n process.exit(1);\n }\n fs.writeFileSync(filePath, content, 'utf8');\n console.log(pc.green(` created ${path.relative(process.cwd(), filePath)}`));\n}\n\nfunction generateRoute(routePath: string): void {\n const cwd = process.cwd();\n const filePath = path.join(cwd, 'src', 'api', `${routePath}.ts`);\n\n const content = `import type { Request, Response } from 'express';\n\nexport const GET = async (req: Request, res: Response) => {\n res.json({ message: 'OK' });\n};\n\nexport const POST = async (req: Request, res: Response) => {\n res.status(201).json({ message: 'Created' });\n};\n`;\n\n writeFile(filePath, content);\n console.log(pc.cyan(`[EFC] Route scaffolded`));\n}\n\nfunction generateTask(name: string): void {\n const cwd = process.cwd();\n const filePath = path.join(cwd, 'src', 'tasks', `${name}.ts`);\n\n const content = `import { defineTask } from 'express-file-cluster/tasks';\n\ninterface ${name}Payload {\n // TODO: define payload fields\n}\n\nexport default defineTask<${name}Payload>(async (payload) => {\n // TODO: implement task logic\n console.log('[Task:${name}]', payload);\n});\n`;\n\n writeFile(filePath, content);\n console.log(pc.cyan(`[EFC] Task scaffolded`));\n}\n\nfunction generateMiddleware(name: string): void {\n const cwd = process.cwd();\n const filePath = path.join(cwd, 'src', 'middlewares', `${name}.ts`);\n\n const content = `import type { Request, Response, NextFunction } from 'express';\n\nexport function ${name}(req: Request, res: Response, next: NextFunction): void {\n // TODO: implement middleware logic\n next();\n}\n`;\n\n writeFile(filePath, content);\n console.log(pc.cyan(`[EFC] Middleware scaffolded`));\n}\n","import { Command } from 'commander';\nimport { scanDir } from '../../router/scan.js';\nimport path from 'node:path';\nimport fs from 'node:fs';\nimport pc from 'picocolors';\n\nexport function diagnosticsCommands(): Command[] {\n return [routesCommand(), tasksCommand(), doctorCommand()];\n}\n\nfunction routesCommand(): Command {\n return new Command('routes').description('Print the resolved route table').action(() => {\n const apiDir = resolveApiDir();\n if (!apiDir) {\n console.error(pc.red('[EFC] Could not find apiDir (expected src/api)'));\n process.exit(1);\n }\n\n const routes = scanDir(apiDir);\n if (routes.length === 0) {\n console.log(pc.yellow('No routes found.'));\n return;\n }\n\n console.log(pc.bold('\\n Route Table\\n'));\n console.log(pc.dim(' ' + '─'.repeat(60)));\n for (const route of routes) {\n const rel = path.relative(process.cwd(), route.filePath);\n console.log(` ${pc.cyan(route.urlPath.padEnd(35))} ${pc.dim(rel)}`);\n }\n console.log(pc.dim(' ' + '─'.repeat(60)));\n console.log(pc.dim(`\\n ${routes.length} route(s) found\\n`));\n });\n}\n\nfunction tasksCommand(): Command {\n return new Command('tasks').description('List registered background tasks').action(() => {\n const tasksDir = resolveTasksDir();\n if (!tasksDir || !fs.existsSync(tasksDir)) {\n console.log(pc.yellow('No tasks directory found.'));\n return;\n }\n\n const files = fs.readdirSync(tasksDir).filter((f) => /\\.(ts|js)$/.test(f));\n if (files.length === 0) {\n console.log(pc.yellow('No tasks found.'));\n return;\n }\n\n console.log(pc.bold('\\n Background Tasks\\n'));\n for (const file of files) {\n console.log(` ${pc.cyan(path.basename(file, path.extname(file)))}`);\n }\n console.log();\n });\n}\n\nfunction doctorCommand(): Command {\n return new Command('doctor')\n .description('Validate config, env vars, and project setup')\n .action(() => {\n const cwd = process.cwd();\n const checks: { label: string; ok: boolean; hint?: string }[] = [\n {\n label: 'package.json exists',\n ok: fs.existsSync(path.join(cwd, 'package.json')),\n },\n {\n label: 'tsconfig.json exists',\n ok: fs.existsSync(path.join(cwd, 'tsconfig.json')),\n hint: 'Run `tsc --init` to create one',\n },\n {\n label: 'src/api directory exists',\n ok: fs.existsSync(path.join(cwd, 'src', 'api')),\n hint: 'Create src/api/ and add route files',\n },\n {\n label: 'DATABASE_URL set',\n ok: Boolean(process.env['DATABASE_URL']),\n hint: 'Add DATABASE_URL to .env',\n },\n {\n label: 'JWT_SECRET set',\n ok: Boolean(process.env['JWT_SECRET']),\n hint: 'Add JWT_SECRET to .env (generate: openssl rand -hex 64)',\n },\n ];\n\n console.log(pc.bold('\\n EFC Doctor\\n'));\n let allOk = true;\n for (const check of checks) {\n const icon = check.ok ? pc.green('✓') : pc.red('✗');\n console.log(` ${icon} ${check.label}`);\n if (!check.ok) {\n allOk = false;\n if (check.hint) console.log(pc.dim(` → ${check.hint}`));\n }\n }\n console.log();\n if (allOk) {\n console.log(pc.green(' All checks passed!\\n'));\n } else {\n console.log(pc.yellow(' Some checks failed. Fix the issues above.\\n'));\n process.exit(1);\n }\n });\n}\n\nfunction resolveApiDir(): string | null {\n const cwd = process.cwd();\n const candidates = [path.join(cwd, 'src', 'api'), path.join(cwd, 'api')];\n return candidates.find((d) => fs.existsSync(d)) ?? null;\n}\n\nfunction resolveTasksDir(): string | null {\n const cwd = process.cwd();\n const candidates = [path.join(cwd, 'src', 'tasks'), path.join(cwd, 'tasks')];\n return candidates.find((d) => fs.existsSync(d)) ?? null;\n}\n"]}
|