create-nexgen 1.0.4
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/package.json +26 -0
- package/src/index.js +108 -0
- package/template/.dockerignore +14 -0
- package/template/.env +58 -0
- package/template/.env.example +59 -0
- package/template/.prettierignore +5 -0
- package/template/.prettierrc +8 -0
- package/template/README.md +447 -0
- package/template/drizzle.config.ts +29 -0
- package/template/eslint.config.js +52 -0
- package/template/gitignore-stub +24 -0
- package/template/package.json +96 -0
- package/template/public/assets/AuthLayout-CbswhpjJ.js +1 -0
- package/template/public/assets/Button-_7aQ7gHL.js +1 -0
- package/template/public/assets/Input-CLNJXmKc.css +1 -0
- package/template/public/assets/Input-z8GI8Aqo.js +1 -0
- package/template/public/assets/InputPasswordToggle-BxlzVGp3.js +1 -0
- package/template/public/assets/InputPasswordToggle-C77FI9Eg.css +1 -0
- package/template/public/assets/Layout-DotR1sQC.js +1 -0
- package/template/public/assets/Refresh-BdqsPPBC.js +1 -0
- package/template/public/assets/admin-ui-CU34rLdN.js +1 -0
- package/template/public/assets/bootstrap-icons-BeopsB42.woff +0 -0
- package/template/public/assets/bootstrap-icons-mSm7cUeB.woff2 +0 -0
- package/template/public/assets/dashboard-CwybEyLc.js +1 -0
- package/template/public/assets/dashboard-Dc4d-Pi7.css +1 -0
- package/template/public/assets/forgetPassword-CKEJaXsq.js +1 -0
- package/template/public/assets/index-Bleyx5dm.js +64 -0
- package/template/public/assets/index-DUw8E6Yg.css +1 -0
- package/template/public/assets/login-DC7PTlQF.js +1 -0
- package/template/public/assets/realtime-test-BPQdrFym.css +1 -0
- package/template/public/assets/realtime-test-tQZ0rBEJ.js +1 -0
- package/template/public/assets/register-3O7Qs28C.js +1 -0
- package/template/public/assets/resetPassword-A5AzMWKs.js +1 -0
- package/template/public/assets/verifyEmail-DDBEQHOv.js +1 -0
- package/template/public/index.html +17 -0
- package/template/src/database/migrations/mysql/0000_init.sql +73 -0
- package/template/src/database/migrations/mysql/meta/0000_snapshot.json +484 -0
- package/template/src/database/migrations/mysql/meta/_journal.json +13 -0
- package/template/src/database/schema.ts +4 -0
- package/template/src/env.ts +107 -0
- package/template/src/framework/cache/cache.ts +81 -0
- package/template/src/framework/database/connection.ts +168 -0
- package/template/src/framework/database/optional-db-drivers.d.ts +9 -0
- package/template/src/framework/database/paginate.ts +200 -0
- package/template/src/framework/database/schema.ts +26 -0
- package/template/src/framework/database/seed.ts +33 -0
- package/template/src/framework/events/dispatcher.ts +57 -0
- package/template/src/framework/facade.ts +27 -0
- package/template/src/framework/http/app.ts +61 -0
- package/template/src/framework/http/cors.ts +19 -0
- package/template/src/framework/http/logger.ts +85 -0
- package/template/src/framework/http/openapi.ts +34 -0
- package/template/src/framework/http/ratelimiter.ts +13 -0
- package/template/src/framework/http/router.ts +76 -0
- package/template/src/framework/http/static.ts +33 -0
- package/template/src/framework/http/validation.ts +24 -0
- package/template/src/framework/kernel.ts +40 -0
- package/template/src/framework/maker-cli/src/index.mjs +51 -0
- package/template/src/framework/maker-cli/src/levels/level-1/env-db.mjs +57 -0
- package/template/src/framework/maker-cli/src/levels/level-1/file-ops.mjs +30 -0
- package/template/src/framework/maker-cli/src/levels/level-1/flags.mjs +16 -0
- package/template/src/framework/maker-cli/src/levels/level-1/help.mjs +24 -0
- package/template/src/framework/maker-cli/src/levels/level-1/naming.mjs +13 -0
- package/template/src/framework/maker-cli/src/levels/level-1/process.mjs +47 -0
- package/template/src/framework/maker-cli/src/levels/level-2/db/core.mjs +299 -0
- package/template/src/framework/maker-cli/src/levels/level-2/db/index.mjs +177 -0
- package/template/src/framework/maker-cli/src/levels/level-2/deploy/core.mjs +635 -0
- package/template/src/framework/maker-cli/src/levels/level-2/deploy/index.mjs +145 -0
- package/template/src/framework/maker-cli/src/levels/level-2/module/core.mjs +707 -0
- package/template/src/framework/maker-cli/src/levels/level-2/module/index.mjs +116 -0
- package/template/src/framework/maker-cli/src/levels/level-2/runtime/build-frontend.mjs +16 -0
- package/template/src/framework/maker-cli/src/levels/level-2/runtime/core.mjs +311 -0
- package/template/src/framework/maker-cli/src/levels/level-2/runtime/index.mjs +71 -0
- package/template/src/framework/maker-cli/stubs/controller/openapi.ts.stub +55 -0
- package/template/src/framework/maker-cli/stubs/controller/openapi.with-model.ts.stub +56 -0
- package/template/src/framework/maker-cli/stubs/controller/plain.ts.stub +57 -0
- package/template/src/framework/maker-cli/stubs/controller/schema.plain.ts.stub +13 -0
- package/template/src/framework/maker-cli/stubs/controller/schema.ts.stub +32 -0
- package/template/src/framework/maker-cli/stubs/deploy/Dockerfile.bun.stub +49 -0
- package/template/src/framework/maker-cli/stubs/deploy/Dockerfile.pnpm.stub +53 -0
- package/template/src/framework/maker-cli/stubs/deploy/Dockerfile.stub +49 -0
- package/template/src/framework/maker-cli/stubs/deploy/Dockerfile.yarn.stub +53 -0
- package/template/src/framework/maker-cli/stubs/deploy/README.stub +55 -0
- package/template/src/framework/maker-cli/stubs/deploy/compose/mysql.server.stub +29 -0
- package/template/src/framework/maker-cli/stubs/deploy/compose/postgres.server.stub +29 -0
- package/template/src/framework/maker-cli/stubs/deploy/compose/sqlite.stub +29 -0
- package/template/src/framework/maker-cli/stubs/deploy/env/mysql.server.stub +73 -0
- package/template/src/framework/maker-cli/stubs/deploy/env/postgres.server.stub +73 -0
- package/template/src/framework/maker-cli/stubs/deploy/env/sqlite.stub +72 -0
- package/template/src/framework/maker-cli/stubs/deploy/scripts/auto-migrate.sh.stub +15 -0
- package/template/src/framework/maker-cli/stubs/deploy/server/README.stub +77 -0
- package/template/src/framework/maker-cli/stubs/deploy/server/compose/noredis.stub +118 -0
- package/template/src/framework/maker-cli/stubs/deploy/server/compose/redis.dev.stub +131 -0
- package/template/src/framework/maker-cli/stubs/deploy/server/compose/redis.stub +129 -0
- package/template/src/framework/maker-cli/stubs/deploy/server/env/local.example.stub +10 -0
- package/template/src/framework/maker-cli/stubs/deploy/server/env/noredis.stub +24 -0
- package/template/src/framework/maker-cli/stubs/deploy/server/env/redis.stub +24 -0
- package/template/src/framework/maker-cli/stubs/deploy/server/nginx-vhost/README.stub +15 -0
- package/template/src/framework/maker-cli/stubs/deploy/server/nginx-vhost/app.example.com.stub +12 -0
- package/template/src/framework/maker-cli/stubs/deploy/server/pgadmin/servers.stub +13 -0
- package/template/src/framework/maker-cli/stubs/deploy/server/redis/redis.conf.stub +6 -0
- package/template/src/framework/maker-cli/stubs/deploy/supervisor/noredis.stub +53 -0
- package/template/src/framework/maker-cli/stubs/deploy/supervisor/redis.stub +69 -0
- package/template/src/framework/maker-cli/stubs/deploy/workflow/local.json.stub +24 -0
- package/template/src/framework/maker-cli/stubs/deploy/workflow/remote.json.stub +20 -0
- package/template/src/framework/maker-cli/stubs/example/console.ts.stub +33 -0
- package/template/src/framework/maker-cli/stubs/example/controller.ts.stub +503 -0
- package/template/src/framework/maker-cli/stubs/example/job.ts.stub +74 -0
- package/template/src/framework/maker-cli/stubs/example/route.api.ts.stub +206 -0
- package/template/src/framework/maker-cli/stubs/example/schema.ts.stub +41 -0
- package/template/src/framework/maker-cli/stubs/job/name.ts.stub +24 -0
- package/template/src/framework/maker-cli/stubs/model/name.mysql.ts.stub +8 -0
- package/template/src/framework/maker-cli/stubs/model/name.postgresql.ts.stub +8 -0
- package/template/src/framework/maker-cli/stubs/model/name.sqlite.ts.stub +8 -0
- package/template/src/framework/maker-cli/stubs/notification/NotificationBell.vue.stub +218 -0
- package/template/src/framework/maker-cli/stubs/notification/controller.ts.stub +85 -0
- package/template/src/framework/maker-cli/stubs/notification/index.vue.stub +211 -0
- package/template/src/framework/maker-cli/stubs/notification/job.ts.stub +12 -0
- package/template/src/framework/maker-cli/stubs/notification/route.api.ts.stub +49 -0
- package/template/src/framework/maker-cli/stubs/notification/schema.ts.stub +25 -0
- package/template/src/framework/maker-cli/stubs/route/api.ts.stub +79 -0
- package/template/src/framework/maker-cli/stubs/route/plain.ts.stub +10 -0
- package/template/src/framework/maker-cli/stubs/schedule/name.ts.stub +35 -0
- package/template/src/framework/maker-cli/stubs/seeder/name.ts.stub +17 -0
- package/template/src/framework/modules/discover.ts +54 -0
- package/template/src/framework/modules/routes.ts +26 -0
- package/template/src/framework/notification/index.ts +109 -0
- package/template/src/framework/queue/clear.ts +20 -0
- package/template/src/framework/queue/queue.ts +213 -0
- package/template/src/framework/queue/ui.ts +104 -0
- package/template/src/framework/queue/worker.ts +33 -0
- package/template/src/framework/realtime/broadcast.ts +27 -0
- package/template/src/framework/realtime/index.ts +1 -0
- package/template/src/framework/realtime/socket-cookie.ts +65 -0
- package/template/src/framework/realtime/socket.ts +132 -0
- package/template/src/framework/realtime/types.ts +6 -0
- package/template/src/framework/realtime/ui.ts +16 -0
- package/template/src/framework/redis/client.ts +126 -0
- package/template/src/framework/scheduler/lock.ts +124 -0
- package/template/src/framework/scheduler/run.ts +26 -0
- package/template/src/framework/scheduler/scheduler.ts +82 -0
- package/template/src/framework/server.ts +147 -0
- package/template/src/framework/session/session.ts +116 -0
- package/template/src/framework/storage/storage.ts +743 -0
- package/template/src/framework/support/cookie.ts +78 -0
- package/template/src/framework/support/jwt.ts +45 -0
- package/template/src/framework/support/lifecycle.ts +35 -0
- package/template/src/framework/support/logger.ts +102 -0
- package/template/src/framework/support/mail.ts +43 -0
- package/template/src/framework/support/password.ts +23 -0
- package/template/src/framework/support/url.ts +25 -0
- package/template/src/middlewares/auth-middleware.ts +98 -0
- package/template/src/middlewares/role-middleware.ts +24 -0
- package/template/src/modules/auth/controllers/auth.controller.ts +445 -0
- package/template/src/modules/auth/controllers/auth.helpers.ts +110 -0
- package/template/src/modules/auth/controllers/auth.schema.ts +102 -0
- package/template/src/modules/auth/controllers/role.controller.ts +25 -0
- package/template/src/modules/auth/database/models/notifications.ts +22 -0
- package/template/src/modules/auth/database/models/role.ts +14 -0
- package/template/src/modules/auth/database/models/user.ts +46 -0
- package/template/src/modules/auth/database/seeders/role.ts +19 -0
- package/template/src/modules/auth/database/seeders/user.ts +33 -0
- package/template/src/modules/auth/jobs/forgetpass.ts +18 -0
- package/template/src/modules/auth/jobs/registeruser.ts +31 -0
- package/template/src/modules/auth/jobs/verifyemail.ts +18 -0
- package/template/src/modules/auth/routes/api.ts +151 -0
- package/template/src/modules/auth/routes/role.ts +39 -0
- package/template/src/modules/welcome/controllers/welcome.controller.ts +14 -0
- package/template/src/modules/welcome/controllers/welcome.schema.ts +6 -0
- package/template/src/modules/welcome/database/models/welcome.ts +6 -0
- package/template/src/modules/welcome/routes/api.ts +20 -0
- package/template/src/resources/index.html +16 -0
- package/template/src/resources/src/App.vue +5 -0
- package/template/src/resources/src/assets/css/styles.css +14934 -0
- package/template/src/resources/src/assets/css/styles.css.map +1 -0
- package/template/src/resources/src/assets/images/favicon/favicon.ico +0 -0
- package/template/src/resources/src/assets/images/favicon/favicon1.ico +0 -0
- package/template/src/resources/src/assets/images/logo-1.png +0 -0
- package/template/src/resources/src/assets/images/logo-dark-sm.png +0 -0
- package/template/src/resources/src/assets/images/logo-dark.png +0 -0
- package/template/src/resources/src/assets/images/logo-dark1.png +0 -0
- package/template/src/resources/src/assets/images/logo-sm.png +0 -0
- package/template/src/resources/src/assets/images/logo1.png +0 -0
- package/template/src/resources/src/assets/images/logo2.png +0 -0
- package/template/src/resources/src/assets/scss/custom.css +217 -0
- package/template/src/resources/src/assets/scss/custom.css.map +1 -0
- package/template/src/resources/src/assets/scss/custom.scss +1100 -0
- package/template/src/resources/src/components/Button.vue +35 -0
- package/template/src/resources/src/components/Checkbox.vue +29 -0
- package/template/src/resources/src/components/FloatButton.vue +36 -0
- package/template/src/resources/src/components/Href.vue +32 -0
- package/template/src/resources/src/components/Input.vue +227 -0
- package/template/src/resources/src/components/InputGroup.vue +153 -0
- package/template/src/resources/src/components/InputPasswordToggle.vue +226 -0
- package/template/src/resources/src/components/Modal.vue +102 -0
- package/template/src/resources/src/components/Pagebar.vue +28 -0
- package/template/src/resources/src/components/Refresh.vue +26 -0
- package/template/src/resources/src/components/Select.vue +390 -0
- package/template/src/resources/src/components/Spinner.vue +42 -0
- package/template/src/resources/src/components/Switch.vue +65 -0
- package/template/src/resources/src/components/TextArea.vue +121 -0
- package/template/src/resources/src/components/Toast.vue +56 -0
- package/template/src/resources/src/components/datatable/DataTableSkeleton.vue +99 -0
- package/template/src/resources/src/components/datatable/Pagination.vue +161 -0
- package/template/src/resources/src/components/datatable/SelectOpption.vue +54 -0
- package/template/src/resources/src/components/datatable/index.vue +237 -0
- package/template/src/resources/src/composables/useAuth.ts +52 -0
- package/template/src/resources/src/composables/useBrowserDetect.ts +5 -0
- package/template/src/resources/src/composables/useDialog.ts +5 -0
- package/template/src/resources/src/composables/useGum.ts +3 -0
- package/template/src/resources/src/composables/usePulse.ts +5 -0
- package/template/src/resources/src/env.d.ts +20 -0
- package/template/src/resources/src/helpers/nformatter.ts +10 -0
- package/template/src/resources/src/helpers/utils.ts +68 -0
- package/template/src/resources/src/layouts/AuthLayout.vue +20 -0
- package/template/src/resources/src/layouts/Layout/Footer.vue +23 -0
- package/template/src/resources/src/layouts/Layout/Header.vue +90 -0
- package/template/src/resources/src/layouts/Layout/Sidebar.vue +137 -0
- package/template/src/resources/src/layouts/Layout/index.vue +76 -0
- package/template/src/resources/src/main.ts +27 -0
- package/template/src/resources/src/pages/auth/forgetPassword.vue +76 -0
- package/template/src/resources/src/pages/auth/login.vue +93 -0
- package/template/src/resources/src/pages/auth/register.vue +130 -0
- package/template/src/resources/src/pages/auth/resetPassword.vue +119 -0
- package/template/src/resources/src/pages/auth/verifyEmail.vue +60 -0
- package/template/src/resources/src/pages/dashboard/index.vue +76 -0
- package/template/src/resources/src/plugins/axios.ts +33 -0
- package/template/src/resources/src/plugins/browserDetect.ts +55 -0
- package/template/src/resources/src/plugins/dialog.ts +167 -0
- package/template/src/resources/src/plugins/gum.ts +343 -0
- package/template/src/resources/src/plugins/pulse.ts +141 -0
- package/template/src/resources/src/plugins/routeProgress.ts +87 -0
- package/template/src/resources/src/router/index.ts +85 -0
- package/template/src/resources/src/stores/admin-ui.ts +148 -0
- package/template/src/resources/src/stores/auth.ts +151 -0
- package/template/src/resources/tsconfig.json +19 -0
- package/template/src/resources/vite.config.ts +43 -0
- package/template/src/storage/logs/app.log +20179 -0
- package/template/src/storage/logs/fatal.log +727 -0
- package/template/tsconfig.json +20 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why: Central public API surface for framework consumers.
|
|
3
|
+
* When: Modules/middlewares need framework utilities.
|
|
4
|
+
* Where: App-layer imports should target this file.
|
|
5
|
+
* How: Re-exports stable, reusable helpers while hiding setup internals.
|
|
6
|
+
*/
|
|
7
|
+
export { cache } from "@/framework/cache/cache.js";
|
|
8
|
+
export { db, database } from "@/framework/database/connection.js";
|
|
9
|
+
export { paginate, paginateQuery, paginateTable, type PaginatedResult } from "@/framework/database/paginate.js";
|
|
10
|
+
export { command, dispatchCommand, dispatchEvent } from "@/framework/events/dispatcher.js";
|
|
11
|
+
export { createRouter, group } from "@/framework/http/router.js";
|
|
12
|
+
export { validate } from "@/framework/http/validation.js";
|
|
13
|
+
export { createRoute, z } from "@hono/zod-openapi";
|
|
14
|
+
export * as HttpStatusCodes from "stoker/http-status-codes";
|
|
15
|
+
export { jsonContent } from "stoker/openapi/helpers";
|
|
16
|
+
export { queue, queueJob, shouldQueue } from "@/framework/queue/queue.js";
|
|
17
|
+
export { defineSchedule } from "@/framework/scheduler/scheduler.js";
|
|
18
|
+
export { session } from "@/framework/session/session.js";
|
|
19
|
+
export { cookie } from "@/framework/support/cookie.js";
|
|
20
|
+
export { jwt } from "@/framework/support/jwt.js";
|
|
21
|
+
export { logger } from "@/framework/support/logger.js";
|
|
22
|
+
export { mail } from "@/framework/support/mail.js";
|
|
23
|
+
export { password } from "@/framework/support/password.js";
|
|
24
|
+
export { urls } from "@/framework/support/url.js";
|
|
25
|
+
export { storage } from "@/framework/storage/storage.js";
|
|
26
|
+
export { notify } from "@/framework/notification/index.js";
|
|
27
|
+
export * as lodash from "lodash-es";
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { serveEmojiFavicon } from "stoker/middlewares";
|
|
2
|
+
import { createRoute, z } from "@hono/zod-openapi";
|
|
3
|
+
import { env } from "@/env.js";
|
|
4
|
+
import { configureOpenApi } from "@/framework/http/openapi.js";
|
|
5
|
+
import { corsMiddleware } from "@/framework/http/cors.js";
|
|
6
|
+
import { rateLimiterMiddleware } from "@/framework/http/ratelimiter.js";
|
|
7
|
+
import { hasFrontendBuild, storageStaticMiddleware } from "@/framework/http/static.js";
|
|
8
|
+
import { sessionMiddleware } from "@/framework/session/session.js";
|
|
9
|
+
import { loggerMiddleware, notFound, onError } from "@/framework/http/logger.js";
|
|
10
|
+
import { createRouter } from "@/framework/http/router.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Why: Builds the main HTTP app with middleware, health, and error handlers.
|
|
14
|
+
* When: Kernel creates application server instance.
|
|
15
|
+
* Where: Framework bootstrap path.
|
|
16
|
+
* How: Composes router, optional OpenAPI, middleware stack, and fallbacks.
|
|
17
|
+
*/
|
|
18
|
+
export function createHttpApp() {
|
|
19
|
+
const app = createRouter();
|
|
20
|
+
|
|
21
|
+
const healthRoute = createRoute({
|
|
22
|
+
path: "/health",
|
|
23
|
+
method: "get",
|
|
24
|
+
tags: ["System"],
|
|
25
|
+
responses: {
|
|
26
|
+
200: {
|
|
27
|
+
description: "Application health status",
|
|
28
|
+
content: {
|
|
29
|
+
"application/json": {
|
|
30
|
+
schema: z.object({ message: z.string() })
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (env.OPEN_API) {
|
|
38
|
+
configureOpenApi(app);
|
|
39
|
+
app.api(healthRoute, (c: any) => c.json({ message: "Application is healthy" }));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
app.use("*", sessionMiddleware);
|
|
43
|
+
app.use("*", corsMiddleware);
|
|
44
|
+
app.use("*", loggerMiddleware);
|
|
45
|
+
app.use("*", rateLimiterMiddleware);
|
|
46
|
+
app.use("/storage/*", storageStaticMiddleware);
|
|
47
|
+
app.use(serveEmojiFavicon("🚀"));
|
|
48
|
+
|
|
49
|
+
if (!hasFrontendBuild()) {
|
|
50
|
+
app.get("/", (c: any) => c.json({ name: env.APP_NAME, ok: true }));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!env.OPEN_API) {
|
|
54
|
+
app.get("/health", (c: any) => c.json({ message: "Application is healthy" }));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
app.notFound(notFound);
|
|
58
|
+
app.onError(onError);
|
|
59
|
+
|
|
60
|
+
return app;
|
|
61
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { cors } from "hono/cors";
|
|
2
|
+
import { env } from "@/env.js";
|
|
3
|
+
import { parseCsvOrFallback } from "@/framework/support/lifecycle.js";
|
|
4
|
+
|
|
5
|
+
const corsOrigins = parseCsvOrFallback(env.CORS_ORIGIN, []);
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Why: Applies credential-aware CORS policy.
|
|
9
|
+
* When: Global HTTP middleware processing.
|
|
10
|
+
* Where: App middleware stack.
|
|
11
|
+
* How: Reflects origin for wildcard mode or validates against allowlist.
|
|
12
|
+
*/
|
|
13
|
+
export const corsMiddleware = cors({
|
|
14
|
+
origin: (origin) => {
|
|
15
|
+
if (env.CORS_ORIGIN === "*") return origin;
|
|
16
|
+
return corsOrigins.includes(origin) ? origin : undefined;
|
|
17
|
+
},
|
|
18
|
+
credentials: true
|
|
19
|
+
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import type { Context, Next } from "hono";
|
|
2
|
+
import { logger } from "@/framework/support/logger.js";
|
|
3
|
+
|
|
4
|
+
function ip(c: Context) {
|
|
5
|
+
return (
|
|
6
|
+
c.req.header("cf-connecting-ip") ||
|
|
7
|
+
c.req.header("x-forwarded-for") ||
|
|
8
|
+
c.req.header("x-real-ip") ||
|
|
9
|
+
null
|
|
10
|
+
);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function shouldSkipHttpLog(pathname: string) {
|
|
14
|
+
return pathname.startsWith("/bullmq");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Why: Logs each HTTP request with latency and request metadata.
|
|
19
|
+
* When: Applied globally to app routes.
|
|
20
|
+
* Where: HTTP middleware stack.
|
|
21
|
+
* How: Captures start time, assigns request id, logs after downstream response.
|
|
22
|
+
*/
|
|
23
|
+
export async function loggerMiddleware(c: Context, next: Next) {
|
|
24
|
+
const start = Date.now();
|
|
25
|
+
const requestId = crypto.randomUUID();
|
|
26
|
+
|
|
27
|
+
c.set("requestId", requestId);
|
|
28
|
+
await next();
|
|
29
|
+
|
|
30
|
+
if (shouldSkipHttpLog(c.req.path)) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
logger.info("HTTP Request", {
|
|
35
|
+
requestId,
|
|
36
|
+
method: c.req.method,
|
|
37
|
+
path: c.req.path,
|
|
38
|
+
url: c.req.url,
|
|
39
|
+
status: c.res.status,
|
|
40
|
+
duration_ms: Date.now() - start,
|
|
41
|
+
ip: ip(c),
|
|
42
|
+
userAgent: c.req.header("user-agent") || null
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Why: Standard 404 handler with structured log context.
|
|
48
|
+
* When: No route matches request path.
|
|
49
|
+
* Where: App notFound handler registration.
|
|
50
|
+
* How: Emits warning log and returns JSON 404 payload.
|
|
51
|
+
*/
|
|
52
|
+
export function notFound(c: Context) {
|
|
53
|
+
logger.warn("404 Not Found", {
|
|
54
|
+
requestId: c.get("requestId") || null,
|
|
55
|
+
method: c.req.method,
|
|
56
|
+
path: c.req.path,
|
|
57
|
+
url: c.req.url,
|
|
58
|
+
ip: ip(c),
|
|
59
|
+
userAgent: c.req.header("user-agent") || null
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
return c.json({ message: "Not Found" }, 404);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Why: Centralized unhandled error responder and logger.
|
|
67
|
+
* When: Route/middleware throws uncaught error.
|
|
68
|
+
* Where: App onError handler registration.
|
|
69
|
+
* How: Logs stack + request context and returns status/message JSON.
|
|
70
|
+
*/
|
|
71
|
+
export function onError(error: any, c: Context) {
|
|
72
|
+
logger.error("Unhandled Application Error", {
|
|
73
|
+
requestId: c.get("requestId") || null,
|
|
74
|
+
name: error?.name || "Error",
|
|
75
|
+
message: error?.message,
|
|
76
|
+
stack: error?.stack,
|
|
77
|
+
method: c.req.method,
|
|
78
|
+
path: c.req.path,
|
|
79
|
+
url: c.req.url,
|
|
80
|
+
ip: ip(c),
|
|
81
|
+
userAgent: c.req.header("user-agent") || null
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
return c.json({ message: error?.message || "Internal Server Error" }, error?.status || 500);
|
|
85
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Scalar } from "@scalar/hono-api-reference";
|
|
2
|
+
import { env } from "@/env.js";
|
|
3
|
+
import type { NexgenRouter } from "@/framework/http/router.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Why: Registers OpenAPI document and interactive docs UI.
|
|
7
|
+
* When: OPEN_API is enabled during app boot.
|
|
8
|
+
* Where: HTTP app setup.
|
|
9
|
+
* How: Exposes `/doc` spec and `/api-docs` Scalar viewer.
|
|
10
|
+
*/
|
|
11
|
+
export function configureOpenApi(app: NexgenRouter) {
|
|
12
|
+
app.doc("/doc", {
|
|
13
|
+
openapi: "3.0.0",
|
|
14
|
+
info: {
|
|
15
|
+
title: `${env.APP_NAME} API`,
|
|
16
|
+
version: "0.1.0"
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
app.get(
|
|
21
|
+
"/api-docs",
|
|
22
|
+
Scalar({
|
|
23
|
+
url: "/doc",
|
|
24
|
+
layout: "classic",
|
|
25
|
+
theme: "moon",
|
|
26
|
+
pageTitle: `${env.APP_NAME} API`,
|
|
27
|
+
defaultHttpClient: {
|
|
28
|
+
targetKey: "js",
|
|
29
|
+
clientKey: "fetch"
|
|
30
|
+
},
|
|
31
|
+
defaultOpenAllTags: true
|
|
32
|
+
})
|
|
33
|
+
);
|
|
34
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { rateLimiter } from "hono-rate-limiter";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Why: Protects API from burst abuse and accidental flooding.
|
|
5
|
+
* When: Applied globally in HTTP middleware chain.
|
|
6
|
+
* Where: App bootstrap middleware stack.
|
|
7
|
+
* How: Limits requests per IP window using forwarded IP key.
|
|
8
|
+
*/
|
|
9
|
+
export const rateLimiterMiddleware = rateLimiter({
|
|
10
|
+
windowMs: 60_000,
|
|
11
|
+
limit: 60,
|
|
12
|
+
keyGenerator: (c) => c.req.header("x-forwarded-for") ?? ""
|
|
13
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { OpenAPIHono } from "@hono/zod-openapi";
|
|
2
|
+
import { defaultHook } from "stoker/openapi";
|
|
3
|
+
|
|
4
|
+
export type NexgenRouter = OpenAPIHono & {
|
|
5
|
+
group: (...middlewares: any[]) => NexgenRouter;
|
|
6
|
+
api: (route: any, handlerOrMiddlewares: any, handler?: any) => NexgenRouter;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Why: Creates the framework router with OpenAPI + helper methods.
|
|
11
|
+
* When: Building root app and module route files.
|
|
12
|
+
* Where: HTTP app bootstrap and route stubs.
|
|
13
|
+
* How: Wraps OpenAPIHono and adds group/api convenience APIs.
|
|
14
|
+
*/
|
|
15
|
+
export function createRouter(): NexgenRouter {
|
|
16
|
+
const router = new OpenAPIHono({ strict: false, defaultHook }) as NexgenRouter;
|
|
17
|
+
const baseOpenapi = router.openapi.bind(router);
|
|
18
|
+
|
|
19
|
+
router.group = function (...middlewares) {
|
|
20
|
+
for (const middleware of middlewares) {
|
|
21
|
+
this.use("*", middleware);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return this;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
router.api = function (route: any, handlerOrMiddlewares: any, handler?: any) {
|
|
28
|
+
if (!Array.isArray(handlerOrMiddlewares)) {
|
|
29
|
+
baseOpenapi(route, handlerOrMiddlewares);
|
|
30
|
+
return this;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const middlewares = handlerOrMiddlewares;
|
|
34
|
+
const finalHandler = handler;
|
|
35
|
+
|
|
36
|
+
if (typeof finalHandler !== "function") {
|
|
37
|
+
throw new Error("api(route, middlewares, handler) requires a handler function");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const wrappedHandler = async (c: any) => {
|
|
41
|
+
let index = 0;
|
|
42
|
+
|
|
43
|
+
const next = async (): Promise<any> => {
|
|
44
|
+
if (index < middlewares.length) {
|
|
45
|
+
const middleware = middlewares[index++];
|
|
46
|
+
let downstream: any;
|
|
47
|
+
const result = await middleware(c, async () => {
|
|
48
|
+
downstream = await next();
|
|
49
|
+
return downstream;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return result === undefined ? downstream : result;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return await finalHandler(c);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
return await next();
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
baseOpenapi(route, wrappedHandler);
|
|
62
|
+
return this;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
return router;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Why: Shorthand for creating grouped router middleware chain.
|
|
70
|
+
* When: Route files need shared middleware composition.
|
|
71
|
+
* Where: Module route declarations.
|
|
72
|
+
* How: Creates a router instance and applies all provided middlewares.
|
|
73
|
+
*/
|
|
74
|
+
export function group(...middlewares: any[]) {
|
|
75
|
+
return createRouter().group(...middlewares);
|
|
76
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { serveStatic } from "@hono/node-server/serve-static";
|
|
2
|
+
import fsSync from "node:fs";
|
|
3
|
+
|
|
4
|
+
export function hasFrontendBuild() {
|
|
5
|
+
return fsSync.existsSync("public/index.html");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Why: Serves public storage files under `/storage/*` URL space.
|
|
10
|
+
* When: Clients request uploaded public assets.
|
|
11
|
+
* Where: App middleware stack.
|
|
12
|
+
* How: Maps `/storage` path prefix to local storage public directory.
|
|
13
|
+
*/
|
|
14
|
+
export const storageStaticMiddleware = serveStatic({
|
|
15
|
+
root: "./src/storage/app/public",
|
|
16
|
+
rewriteRequestPath: (path) => path.replace(/^\/storage/, "")
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Why: Serves built frontend static assets.
|
|
21
|
+
* When: SPA build exists in `public` directory.
|
|
22
|
+
* Where: Kernel frontend integration.
|
|
23
|
+
* How: Uses node static middleware rooted at `./public`.
|
|
24
|
+
*/
|
|
25
|
+
export const frontendStaticMiddleware = serveStatic({ root: "./public" });
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Why: Serves SPA entry file for client-side routed paths.
|
|
29
|
+
* When: Non-API frontend route fallback is needed.
|
|
30
|
+
* Where: Kernel catch-all frontend route.
|
|
31
|
+
* How: Always returns `public/index.html`.
|
|
32
|
+
*/
|
|
33
|
+
export const frontendIndexMiddleware = serveStatic({ path: "./public/index.html" });
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Why: Centralizes schema validation with consistent error shape.
|
|
5
|
+
* When: Non-route code needs explicit Zod validation.
|
|
6
|
+
* Where: Controllers/helpers/services.
|
|
7
|
+
* How: Runs safeParse and throws 422 payload on failure.
|
|
8
|
+
*/
|
|
9
|
+
export async function validate<T extends z.ZodTypeAny>(
|
|
10
|
+
schema: T,
|
|
11
|
+
data: unknown
|
|
12
|
+
): Promise<z.infer<T>> {
|
|
13
|
+
const result = schema.safeParse(data);
|
|
14
|
+
|
|
15
|
+
if (!result.success) {
|
|
16
|
+
throw {
|
|
17
|
+
status: 422,
|
|
18
|
+
message: "Validation failed",
|
|
19
|
+
errors: result.error.flatten()
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return result.data;
|
|
24
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { env } from "@/env.js";
|
|
2
|
+
import { createHttpApp } from "@/framework/http/app.js";
|
|
3
|
+
import { registerModuleRoutes } from "@/framework/modules/routes.js";
|
|
4
|
+
import { initDatabase } from "@/framework/database/connection.js";
|
|
5
|
+
import { initRedis } from "@/framework/redis/client.js";
|
|
6
|
+
import { bootQueueJobs } from "@/framework/queue/queue.js";
|
|
7
|
+
import { setupBullBoard } from "@/framework/queue/ui.js";
|
|
8
|
+
import { frontendIndexMiddleware, frontendStaticMiddleware, hasFrontendBuild } from "@/framework/http/static.js";
|
|
9
|
+
import { storage } from "@/framework/storage/storage.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Why: Assembles app kernel and boots all framework dependencies.
|
|
13
|
+
* When: HTTP server startup.
|
|
14
|
+
* Where: Called by server runtime entrypoint.
|
|
15
|
+
* How: Initializes storage/db/redis/queues/events/routes and optional frontend.
|
|
16
|
+
*/
|
|
17
|
+
export async function createKernel() {
|
|
18
|
+
await storage.init();
|
|
19
|
+
|
|
20
|
+
const app = createHttpApp();
|
|
21
|
+
|
|
22
|
+
await initDatabase();
|
|
23
|
+
await initRedis();
|
|
24
|
+
await bootQueueJobs();
|
|
25
|
+
await registerModuleRoutes(app);
|
|
26
|
+
|
|
27
|
+
const bullBoard = setupBullBoard();
|
|
28
|
+
if (typeof bullBoard.route === "function") {
|
|
29
|
+
bullBoard.route(app);
|
|
30
|
+
} else {
|
|
31
|
+
app.route(bullBoard.basePath, bullBoard.route);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (env.FRONTEND && hasFrontendBuild()) {
|
|
35
|
+
app.use("/*", frontendStaticMiddleware);
|
|
36
|
+
app.get("/*", frontendIndexMiddleware);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return { app, bullBoard };
|
|
40
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
import { config } from "dotenv";
|
|
5
|
+
import { registerModuleCommands } from "./levels/level-2/module/index.mjs";
|
|
6
|
+
import { registerDbCommands } from "./levels/level-2/db/index.mjs";
|
|
7
|
+
import { registerRuntimeCommands } from "./levels/level-2/runtime/index.mjs";
|
|
8
|
+
import { makerCommandPrefix, showHelp } from "./levels/level-1/help.mjs";
|
|
9
|
+
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
const [command] = args;
|
|
12
|
+
|
|
13
|
+
let deployCommands = new Set();
|
|
14
|
+
let registerDeployCommands = () => {};
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const deploy = await import("./levels/level-2/deploy/index.mjs");
|
|
18
|
+
deployCommands = deploy.deployCommands;
|
|
19
|
+
registerDeployCommands = deploy.registerDeployCommands;
|
|
20
|
+
} catch {
|
|
21
|
+
/* deploy module not available until user runs deploy:create */
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (!deployCommands.has(command || "")) {
|
|
25
|
+
config({ path: ".env", quiet: true });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const program = new Command();
|
|
30
|
+
const namePrefix = makerCommandPrefix();
|
|
31
|
+
program
|
|
32
|
+
.name(namePrefix ? `${namePrefix} maker` : "maker")
|
|
33
|
+
.allowUnknownOption(true)
|
|
34
|
+
.allowExcessArguments(true)
|
|
35
|
+
.helpOption("-h, --help")
|
|
36
|
+
.addHelpText("beforeAll", "nexgen maker\n");
|
|
37
|
+
|
|
38
|
+
registerModuleCommands(program, args);
|
|
39
|
+
registerDeployCommands(program, args);
|
|
40
|
+
registerDbCommands(program, args);
|
|
41
|
+
registerRuntimeCommands(program, args);
|
|
42
|
+
|
|
43
|
+
if (!command) {
|
|
44
|
+
showHelp(program);
|
|
45
|
+
} else {
|
|
46
|
+
await program.parseAsync(args, { from: "user" });
|
|
47
|
+
}
|
|
48
|
+
} catch (error) {
|
|
49
|
+
console.error(error instanceof Error ? error.message : error);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const DEFAULT_DATABASE_URL = "sqlite:./src/storage/database/nexgen.sqlite";
|
|
2
|
+
|
|
3
|
+
/** Detect current DB dialect from DATABASE_URL (sqlite/mysql/postgresql). Falls back to sqlite. */
|
|
4
|
+
export function detectDialect() {
|
|
5
|
+
const url = String(process.env.DATABASE_URL || DEFAULT_DATABASE_URL).toLowerCase();
|
|
6
|
+
if (url.startsWith("mysql") || url.startsWith("mariadb")) return "mysql";
|
|
7
|
+
if (url.startsWith("postgres") || url.startsWith("postgresql")) return "postgresql";
|
|
8
|
+
return "sqlite";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Return the raw DATABASE_URL string or the default sqlite fallback. */
|
|
12
|
+
export function databaseUrl() {
|
|
13
|
+
return String(process.env.DATABASE_URL || DEFAULT_DATABASE_URL);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Parse DATABASE_URL into a URL object. Returns null if invalid. */
|
|
17
|
+
export function parsedDatabaseUrl() {
|
|
18
|
+
try {
|
|
19
|
+
return new URL(databaseUrl());
|
|
20
|
+
} catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Extract the database name from the pathname of a parsed DATABASE_URL. */
|
|
26
|
+
export function databaseNameFromUrl(url) {
|
|
27
|
+
const pathname = String(url?.pathname || "").replace(/^\/+/, "");
|
|
28
|
+
return pathname ? decodeURIComponent(pathname) : "";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Check if OPEN_API env is enabled (defaults to true). */
|
|
32
|
+
export function openApiEnabled() {
|
|
33
|
+
const value = String(process.env.OPEN_API ?? "true").trim().toLowerCase();
|
|
34
|
+
return value !== "false" && value !== "0" && value !== "no";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Check if REDIS env is enabled (defaults to false). */
|
|
38
|
+
export function redisEnabled() {
|
|
39
|
+
const value = String(process.env.REDIS ?? "false").trim().toLowerCase();
|
|
40
|
+
return value === "true" || value === "1" || value === "yes";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Detect DB dialect for deploy context (uses raw DATABASE_URL or explicit URL). */
|
|
44
|
+
export function detectDeployDatabase(urlOverride) {
|
|
45
|
+
const url = String(urlOverride || process.env.DATABASE_URL || "").toLowerCase();
|
|
46
|
+
if (url.startsWith("mysql") || url.startsWith("mariadb")) return "mysql";
|
|
47
|
+
if (url.startsWith("postgres") || url.startsWith("postgresql")) return "postgres";
|
|
48
|
+
return "sqlite";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Convert a sqlite:// URL to a filesystem path. */
|
|
52
|
+
export function sqlitePathFromUrl(url) {
|
|
53
|
+
if (url.startsWith("sqlite:///")) return url.replace("sqlite:///", "/");
|
|
54
|
+
if (url.startsWith("sqlite://")) return url.replace("sqlite://", "");
|
|
55
|
+
if (url.startsWith("sqlite:")) return url.replace("sqlite:", "");
|
|
56
|
+
return url;
|
|
57
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
/** Write a file only if it does not already exist. Returns true if created. */
|
|
5
|
+
export async function writeFileIfMissing(filePath, content) {
|
|
6
|
+
try {
|
|
7
|
+
await fs.access(filePath);
|
|
8
|
+
console.log(`Skipped existing: ${path.relative(process.cwd(), filePath)}`);
|
|
9
|
+
return false;
|
|
10
|
+
} catch {
|
|
11
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
12
|
+
await fs.writeFile(filePath, content);
|
|
13
|
+
console.log(`Created: ${path.relative(process.cwd(), filePath)}`);
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Write a file, overwriting if it exists. */
|
|
19
|
+
export async function writeFileAlways(filePath, content) {
|
|
20
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
21
|
+
await fs.writeFile(filePath, content);
|
|
22
|
+
console.log(`Updated: ${path.relative(process.cwd(), filePath)}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Write multiple files under a root directory using writeFileAlways. */
|
|
26
|
+
export async function writeFiles(root, files) {
|
|
27
|
+
for (const [relativePath, content] of Object.entries(files)) {
|
|
28
|
+
await writeFileAlways(path.join(root, relativePath), content);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Check if a specific flag name exists in the flags array. */
|
|
2
|
+
export function hasFlag(flags, name) {
|
|
3
|
+
return flags.includes(name);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/** Extract the value of a --key=value flag from the flags array. Returns fallback if not found. */
|
|
7
|
+
export function getOption(flags = [], name, fallback = "") {
|
|
8
|
+
const item = flags.find((flag) => flag.startsWith(`${name}=`));
|
|
9
|
+
return item ? item.slice(name.length + 1) : fallback;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function stripWorkflowFlags(flags = []) {
|
|
13
|
+
return flags.filter(
|
|
14
|
+
(flag) => !["--server-only", "--app-only", "--refresh", "--dry-run"].includes(flag) && !flag.startsWith("--config=")
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Why: Detects the package manager used to run the CLI, for help usage text.
|
|
3
|
+
* When: Building the program name shown in the "Usage:" line.
|
|
4
|
+
* Where: Maker CLI entry flow.
|
|
5
|
+
* How: Reads npm config user-agent and maps to the corresponding prefix string.
|
|
6
|
+
*/
|
|
7
|
+
export function makerCommandPrefix() {
|
|
8
|
+
const ua = String(process.env.npm_config_user_agent || "").toLowerCase();
|
|
9
|
+
if (ua.startsWith("pnpm/")) return "pnpm";
|
|
10
|
+
if (ua.startsWith("yarn/")) return "yarn";
|
|
11
|
+
if (ua.startsWith("bun/")) return "bun";
|
|
12
|
+
if (ua.startsWith("npm/")) return "npm run";
|
|
13
|
+
return "";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Why: Shows commander-generated help for the maker CLI program.
|
|
18
|
+
* When: CLI runs without a valid command.
|
|
19
|
+
* Where: Maker CLI entry flow and error handlers.
|
|
20
|
+
* How: Delegates to commander's outputHelp for consistent automatically-synced output.
|
|
21
|
+
*/
|
|
22
|
+
export function showHelp(program) {
|
|
23
|
+
program.outputHelp();
|
|
24
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** Validate that a name value exists and return it trimmed+lowercased. Throws if empty. */
|
|
2
|
+
export function assertName(value, label) {
|
|
3
|
+
if (!value) {
|
|
4
|
+
throw new Error(`${label} is required.`);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
return value.trim().toLowerCase();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Convert a kebab-case or snake_case string to PascalCase. */
|
|
11
|
+
export function pascal(input) {
|
|
12
|
+
return input.replace(/(^\w|[-_]\w)/g, (part) => part.replace(/[-_]/g, "").toUpperCase());
|
|
13
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import fsSync from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
|
|
5
|
+
/** Run a Node.js script as a child process using the current Node executable. */
|
|
6
|
+
export async function runNodeScript(script, args = [], options = {}) {
|
|
7
|
+
await new Promise((resolve, reject) => {
|
|
8
|
+
const child = spawn(process.execPath, [script, ...args], { stdio: options.silent ? "ignore" : "inherit" });
|
|
9
|
+
child.on("exit", (code) => code === 0 ? resolve() : reject(new Error(`${script} ${args.join(" ")} failed with code ${code}`)));
|
|
10
|
+
child.on("error", reject);
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Resolve the path to a script inside a package in node_modules. */
|
|
15
|
+
export function packageScript(packageName, scriptPath) {
|
|
16
|
+
return path.resolve(process.cwd(), "node_modules", packageName, scriptPath);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Find a local .bin executable (handles OS extensions like .cmd on Windows). */
|
|
20
|
+
export function localBin(name) {
|
|
21
|
+
const extensions = process.platform === "win32" ? [".cmd", ".exe", ".ps1", ""] : [""];
|
|
22
|
+
|
|
23
|
+
for (const extension of extensions) {
|
|
24
|
+
const candidate = path.resolve(process.cwd(), "node_modules", ".bin", `${name}${extension}`);
|
|
25
|
+
if (fsSync.existsSync(candidate)) return candidate;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
throw new Error(`Missing local binary: ${name}. Install project dependencies first.`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Run an arbitrary command as a child process. Uses shell mode for Windows scripts. */
|
|
32
|
+
export async function runCommand(commandName, commandArgs = [], options = {}) {
|
|
33
|
+
await new Promise((resolve, reject) => {
|
|
34
|
+
const isWindowsScript = process.platform === "win32" && /\.(cmd|ps1)$/i.test(commandName);
|
|
35
|
+
const child = isWindowsScript
|
|
36
|
+
? spawn(
|
|
37
|
+
[commandName, ...commandArgs].map((part) => (/\s/.test(part) ? `"${part.replace(/"/g, '\\"')}"` : part)).join(" "),
|
|
38
|
+
{ shell: true, stdio: options.silent ? "ignore" : "inherit" }
|
|
39
|
+
)
|
|
40
|
+
: spawn(commandName, commandArgs, { shell: false, stdio: options.silent ? "ignore" : "inherit" });
|
|
41
|
+
|
|
42
|
+
child.on("exit", (code) =>
|
|
43
|
+
code === 0 ? resolve() : reject(new Error(`${commandName} ${commandArgs.join(" ")} failed with code ${code}`))
|
|
44
|
+
);
|
|
45
|
+
child.on("error", reject);
|
|
46
|
+
});
|
|
47
|
+
}
|