create-nexwire 4.0.0 → 4.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/express/README.md +13 -7
- package/express/package.json +2 -2
- package/express/src/config/session.ts +1 -1
- package/express/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
- package/express/src/database/migrations/sqlite/meta/_journal.json +1 -1
- package/express/src/database/schema.ts +3 -3
- package/express/src/framework/cache/cache.ts +82 -81
- package/express/src/framework/database/seed.ts +1 -1
- package/express/src/framework/facade.ts +1 -24
- package/express/src/framework/http/app.ts +5 -5
- package/express/src/framework/http/openapi.ts +1 -1
- package/express/src/framework/http/router.ts +3 -18
- package/express/src/framework/http/types.ts +19 -0
- package/express/src/framework/http/validation.ts +18 -18
- package/express/src/framework/maker-cli/middleware/core.mjs +41 -47
- package/express/src/framework/maker-cli/module/core.mjs +920 -684
- package/express/src/framework/maker-cli/module/index.mjs +202 -140
- package/express/src/framework/maker-cli/runtime/core.mjs +1 -2
- package/express/src/framework/maker-cli/stubs/controller/openapi.ts.stub +40 -14
- package/express/src/framework/maker-cli/stubs/controller/openapi.with-model.ts.stub +40 -15
- package/express/src/framework/maker-cli/stubs/controller/plain.ts.stub +42 -16
- package/express/src/framework/maker-cli/stubs/controller/standalone.ts.stub +47 -0
- package/express/src/framework/maker-cli/stubs/facade/name.ts.stub +12 -0
- package/express/src/framework/maker-cli/stubs/helper/name.ts.stub +14 -0
- package/express/src/framework/maker-cli/stubs/middleware/name.local.ts.stub +15 -0
- package/express/src/framework/maker-cli/stubs/model/name.mysql.ts.stub +9 -8
- package/express/src/framework/maker-cli/stubs/model/name.postgresql.ts.stub +9 -8
- package/express/src/framework/maker-cli/stubs/model/name.sqlite.ts.stub +9 -8
- package/express/src/framework/maker-cli/stubs/notification/route.api.ts.stub +50 -50
- package/express/src/framework/maker-cli/stubs/notification/route.plain.ts.stub +11 -11
- package/express/src/framework/maker-cli/stubs/route/api.ts.stub +93 -79
- package/express/src/framework/maker-cli/stubs/route/plain.ts.stub +23 -10
- package/express/src/framework/maker-cli/stubs/route/standalone.api.ts.stub +113 -0
- package/express/src/framework/maker-cli/stubs/route/standalone.plain.ts.stub +37 -0
- package/express/src/framework/maker-cli/stubs/{controller/schema.plain.ts.stub → schema/name.plain.ts.stub} +13 -13
- package/express/src/framework/maker-cli/stubs/{controller/schema.ts.stub → schema/name.ts.stub} +32 -32
- package/express/src/framework/maker-cli/stubs/service/name.plain.ts.stub +67 -0
- package/express/src/framework/maker-cli/stubs/service/name.ts.stub +65 -0
- package/express/src/framework/maker-cli/stubs/type/name.ts.stub +22 -0
- package/express/src/framework/maker-cli/utils/naming.mjs +56 -13
- package/express/src/framework/modules/discover.ts +57 -54
- package/express/src/framework/notification/index.ts +1 -1
- package/express/src/framework/realtime/broadcast.ts +46 -46
- package/express/src/framework/server.ts +2 -1
- package/express/src/framework/session/session.ts +102 -98
- package/express/src/framework/support/mail.ts +48 -48
- package/express/src/framework/support/status-codes.ts +75 -0
- package/express/src/middlewares/auth-middleware.ts +1 -1
- package/express/src/modules/auth/__tests__/forgetpass.test.ts +200 -0
- package/express/src/modules/auth/__tests__/login.test.ts +171 -0
- package/express/src/modules/auth/__tests__/logout.test.ts +60 -0
- package/express/src/modules/auth/__tests__/logoutalldevices.test.ts +49 -0
- package/express/src/modules/auth/__tests__/me.test.ts +91 -0
- package/express/src/modules/auth/__tests__/refreshtoken.test.ts +305 -0
- package/express/src/modules/auth/__tests__/register.test.ts +291 -0
- package/express/src/modules/auth/__tests__/resetpass.test.ts +206 -0
- package/express/src/modules/auth/__tests__/verifyemail.test.ts +183 -0
- package/express/src/modules/auth/controllers/auth.ts +254 -0
- package/express/src/modules/auth/controllers/{role.controller.ts → role.ts} +25 -15
- package/{hono/src/modules/auth/database/models/notifications.ts → express/src/modules/auth/database/models/notification.ts} +1 -1
- package/express/src/modules/auth/database/models/role.ts +1 -1
- package/express/src/modules/auth/database/models/user.ts +1 -1
- package/express/src/modules/auth/facade.ts +12 -0
- package/express/src/modules/auth/{controllers/auth.helpers.ts → helpers/auth.ts} +3 -4
- package/express/src/modules/auth/jobs/registeruser.ts +23 -23
- package/express/src/modules/auth/routes/{api.ts → index.ts} +150 -150
- package/express/src/modules/auth/routes/role.ts +33 -33
- package/{hono/src/modules/auth/controllers/auth.schema.ts → express/src/modules/auth/schemas/auth.ts} +102 -102
- package/express/src/modules/auth/services/forgetpassword.ts +66 -0
- package/express/src/modules/auth/services/login.ts +63 -0
- package/express/src/modules/auth/services/logout.ts +25 -0
- package/express/src/modules/auth/services/logoutalldevices.ts +19 -0
- package/express/src/modules/auth/services/me.ts +31 -0
- package/express/src/modules/auth/services/refreshtoken.ts +110 -0
- package/express/src/modules/auth/services/register.ts +141 -0
- package/express/src/modules/auth/services/resetpassword.ts +69 -0
- package/express/src/modules/auth/services/verifyemail.ts +77 -0
- package/express/src/modules/auth/types/auth.ts +31 -0
- package/express/src/resources/src/assets/images/logo.png +0 -0
- package/express/src/resources/src/pages/auth/forgetPassword.vue +1 -0
- package/express/src/resources/src/pages/auth/login.vue +1 -1
- package/express/src/storage/database/nexwire.sqlite +0 -0
- package/hono/README.md +13 -7
- package/hono/package.json +1 -1
- package/hono/src/config/session.ts +1 -1
- package/hono/src/database/migrations/sqlite/meta/0000_snapshot.json +1 -1
- package/hono/src/database/migrations/sqlite/meta/_journal.json +1 -1
- package/hono/src/database/schema.ts +3 -3
- package/hono/src/framework/cache/cache.ts +82 -81
- package/hono/src/framework/database/seed.ts +1 -1
- package/hono/src/framework/http/app.ts +5 -5
- package/hono/src/framework/http/validation.ts +21 -21
- package/hono/src/framework/maker-cli/middleware/core.mjs +40 -46
- package/hono/src/framework/maker-cli/module/core.mjs +401 -168
- package/hono/src/framework/maker-cli/module/index.mjs +73 -11
- package/hono/src/framework/maker-cli/stubs/controller/openapi.ts.stub +28 -16
- package/hono/src/framework/maker-cli/stubs/controller/openapi.with-model.ts.stub +28 -17
- package/hono/src/framework/maker-cli/stubs/controller/plain.ts.stub +28 -16
- package/hono/src/framework/maker-cli/stubs/controller/standalone.ts.stub +56 -0
- package/hono/src/framework/maker-cli/stubs/facade/name.ts.stub +12 -0
- package/hono/src/framework/maker-cli/stubs/helper/name.ts.stub +14 -0
- package/hono/src/framework/maker-cli/stubs/middleware/name.local.ts.stub +15 -0
- package/hono/src/framework/maker-cli/stubs/model/name.mysql.ts.stub +9 -8
- package/hono/src/framework/maker-cli/stubs/model/name.postgresql.ts.stub +9 -8
- package/hono/src/framework/maker-cli/stubs/model/name.sqlite.ts.stub +9 -8
- package/hono/src/framework/maker-cli/stubs/notification/route.api.ts.stub +49 -49
- package/hono/src/framework/maker-cli/stubs/notification/route.plain.ts.stub +11 -11
- package/hono/src/framework/maker-cli/stubs/route/api.ts.stub +93 -79
- package/hono/src/framework/maker-cli/stubs/route/plain.ts.stub +23 -10
- package/hono/src/framework/maker-cli/stubs/route/standalone.api.ts.stub +112 -0
- package/hono/src/framework/maker-cli/stubs/route/standalone.plain.ts.stub +37 -0
- package/hono/src/framework/maker-cli/stubs/{controller/schema.plain.ts.stub → schema/name.plain.ts.stub} +13 -13
- package/hono/src/framework/maker-cli/stubs/{controller/schema.ts.stub → schema/name.ts.stub} +32 -32
- package/hono/src/framework/maker-cli/stubs/service/name.plain.ts.stub +67 -0
- package/hono/src/framework/maker-cli/stubs/service/name.ts.stub +65 -0
- package/hono/src/framework/maker-cli/stubs/type/name.ts.stub +22 -0
- package/hono/src/framework/maker-cli/utils/naming.mjs +56 -13
- package/hono/src/framework/modules/discover.ts +57 -54
- package/hono/src/framework/notification/index.ts +1 -1
- package/hono/src/framework/realtime/broadcast.ts +46 -46
- package/hono/src/framework/server.ts +1 -1
- package/hono/src/framework/session/session.ts +121 -116
- package/hono/src/middlewares/auth-middleware.ts +1 -1
- package/hono/src/modules/auth/__tests__/forgetpass.test.ts +170 -0
- package/hono/src/modules/auth/__tests__/login.test.ts +154 -0
- package/hono/src/modules/auth/__tests__/logout.test.ts +53 -0
- package/hono/src/modules/auth/__tests__/logoutalldevices.test.ts +34 -0
- package/hono/src/modules/auth/__tests__/me.test.ts +87 -0
- package/hono/src/modules/auth/__tests__/refreshtoken.test.ts +305 -0
- package/hono/src/modules/auth/__tests__/register.test.ts +258 -0
- package/hono/src/modules/auth/__tests__/resetpass.test.ts +214 -0
- package/hono/src/modules/auth/__tests__/verifyemail.test.ts +189 -0
- package/hono/src/modules/auth/controllers/auth.ts +238 -0
- package/hono/src/modules/auth/controllers/{role.controller.ts → role.ts} +25 -25
- package/{express/src/modules/auth/database/models/notifications.ts → hono/src/modules/auth/database/models/notification.ts} +1 -1
- package/hono/src/modules/auth/database/models/role.ts +1 -1
- package/hono/src/modules/auth/database/models/user.ts +1 -1
- package/hono/src/modules/auth/facade.ts +12 -0
- package/hono/src/modules/auth/{controllers/auth.helpers.ts → helpers/auth.ts} +5 -6
- package/hono/src/modules/auth/jobs/registeruser.ts +23 -23
- package/hono/src/modules/auth/routes/{api.ts → index.ts} +150 -150
- package/hono/src/modules/auth/routes/role.ts +33 -33
- package/{express/src/modules/auth/controllers/auth.schema.ts → hono/src/modules/auth/schemas/auth.ts} +102 -102
- package/hono/src/modules/auth/services/forgetpassword.ts +58 -0
- package/hono/src/modules/auth/services/login.ts +61 -0
- package/hono/src/modules/auth/services/logout.ts +24 -0
- package/hono/src/modules/auth/services/logoutalldevices.ts +19 -0
- package/hono/src/modules/auth/services/me.ts +31 -0
- package/hono/src/modules/auth/services/refreshtoken.ts +110 -0
- package/hono/src/modules/auth/services/register.ts +132 -0
- package/hono/src/modules/auth/services/resetpassword.ts +69 -0
- package/hono/src/modules/auth/services/verifyemail.ts +77 -0
- package/hono/src/modules/auth/types/auth.ts +31 -0
- package/hono/src/resources/src/assets/images/logo.png +0 -0
- package/hono/src/storage/database/nexwire.sqlite +0 -0
- package/package.json +1 -1
- package/express/src/framework/maker-cli/stubs/example/console.ts.stub +0 -34
- package/express/src/framework/maker-cli/stubs/example/controller.ts.stub +0 -418
- package/express/src/framework/maker-cli/stubs/example/job.ts.stub +0 -75
- package/express/src/framework/maker-cli/stubs/example/route.api.ts.stub +0 -207
- package/express/src/framework/maker-cli/stubs/example/route.plain.ts.stub +0 -18
- package/express/src/framework/maker-cli/stubs/example/schema.plain.ts.stub +0 -42
- package/express/src/framework/maker-cli/stubs/example/schema.ts.stub +0 -42
- package/express/src/modules/auth/controllers/auth.controller.ts +0 -358
- package/hono/src/framework/maker-cli/stubs/example/console.ts.stub +0 -34
- package/hono/src/framework/maker-cli/stubs/example/controller.ts.stub +0 -505
- package/hono/src/framework/maker-cli/stubs/example/job.ts.stub +0 -75
- package/hono/src/framework/maker-cli/stubs/example/route.api.ts.stub +0 -207
- package/hono/src/framework/maker-cli/stubs/example/route.plain.ts.stub +0 -18
- package/hono/src/framework/maker-cli/stubs/example/schema.plain.ts.stub +0 -42
- package/hono/src/framework/maker-cli/stubs/example/schema.ts.stub +0 -42
- package/hono/src/modules/auth/controllers/auth.controller.ts +0 -413
|
@@ -5,25 +5,28 @@ import { drizzleGenerateArgs, ensureDatabaseDirectory, ensureMigrationMeta, sync
|
|
|
5
5
|
import { detectDialect, openApiEnabled } from "../utils/env-db.mjs";
|
|
6
6
|
import { writeFiles } from "../utils/file-ops.mjs";
|
|
7
7
|
import { hasFlag } from "../utils/flags.mjs";
|
|
8
|
-
import { assertName, pascal } from "../utils/naming.mjs";
|
|
8
|
+
import { assertName, camelCase, pascal } from "../utils/naming.mjs";
|
|
9
9
|
import { packageScript, runNodeScript } from "../utils/process.mjs";
|
|
10
10
|
|
|
11
11
|
const stubsRoot = path.resolve(import.meta.dirname, "../stubs");
|
|
12
12
|
|
|
13
13
|
/** Template stubs used for module file generation. */
|
|
14
14
|
const STUBS = {
|
|
15
|
+
schema: {
|
|
16
|
+
openapi: "schema/name.ts.stub",
|
|
17
|
+
plain: "schema/name.plain.ts.stub"
|
|
18
|
+
},
|
|
15
19
|
controller: {
|
|
16
20
|
openapi: "controller/openapi.ts.stub",
|
|
17
21
|
openapiWithModel: "controller/openapi.with-model.ts.stub",
|
|
18
22
|
plain: "controller/plain.ts.stub",
|
|
19
|
-
|
|
20
|
-
openapi: "controller/schema.ts.stub",
|
|
21
|
-
plain: "controller/schema.plain.ts.stub"
|
|
22
|
-
}
|
|
23
|
+
standalone: "controller/standalone.ts.stub"
|
|
23
24
|
},
|
|
24
25
|
route: {
|
|
25
26
|
api: "route/api.ts.stub",
|
|
26
|
-
plain: "route/plain.ts.stub"
|
|
27
|
+
plain: "route/plain.ts.stub",
|
|
28
|
+
standaloneApi: "route/standalone.api.ts.stub",
|
|
29
|
+
standalonePlain: "route/standalone.plain.ts.stub"
|
|
27
30
|
},
|
|
28
31
|
model: {
|
|
29
32
|
named: {
|
|
@@ -35,16 +38,21 @@ const STUBS = {
|
|
|
35
38
|
seeder: {
|
|
36
39
|
named: "seeder/name.ts.stub"
|
|
37
40
|
},
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
service: {
|
|
42
|
+
named: "service/name.ts.stub",
|
|
43
|
+
plain: "service/name.plain.ts.stub"
|
|
44
|
+
},
|
|
45
|
+
facade: {
|
|
46
|
+
named: "facade/name.ts.stub"
|
|
47
|
+
},
|
|
48
|
+
helper: {
|
|
49
|
+
named: "helper/name.ts.stub"
|
|
50
|
+
},
|
|
51
|
+
middleware: {
|
|
52
|
+
local: "middleware/name.local.ts.stub"
|
|
53
|
+
},
|
|
54
|
+
type: {
|
|
55
|
+
named: "type/name.ts.stub"
|
|
48
56
|
},
|
|
49
57
|
job: {
|
|
50
58
|
named: "job/name.ts.stub"
|
|
@@ -86,7 +94,95 @@ async function stub(name, values = {}) {
|
|
|
86
94
|
|
|
87
95
|
/** Resolve the canonical module root path inside src/modules. */
|
|
88
96
|
function moduleRoot(moduleName) {
|
|
89
|
-
|
|
97
|
+
const base = path.resolve(process.cwd(), "src/modules");
|
|
98
|
+
const root = path.resolve(base, moduleName);
|
|
99
|
+
if (root !== base && !root.startsWith(base + path.sep)) {
|
|
100
|
+
throw new Error(`Module path escapes src/modules: ${moduleName}`);
|
|
101
|
+
}
|
|
102
|
+
return root;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Leaf folder name of a module. `admin/post` -> `post`, `post` -> `post`.
|
|
107
|
+
* File names and class names always come from the leaf; imports come from the
|
|
108
|
+
* full path.
|
|
109
|
+
*/
|
|
110
|
+
function leafName(moduleName) {
|
|
111
|
+
const segments = String(moduleName).split("/").filter(Boolean);
|
|
112
|
+
return segments[segments.length - 1] || String(moduleName);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Panel prefix of a module, everything before the leaf. `admin/post` -> `admin`,
|
|
117
|
+
* `admin/reporting/deep` -> `admin/reporting`, `post` -> "".
|
|
118
|
+
*/
|
|
119
|
+
function panelOf(moduleName) {
|
|
120
|
+
const segments = String(moduleName).split("/").filter(Boolean);
|
|
121
|
+
return segments.slice(0, -1).join("/");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Scalar tag for a module. Panels become a nested tag group so Scalar renders
|
|
126
|
+
* `Admin / Post` instead of flattening everything under one panel.
|
|
127
|
+
*/
|
|
128
|
+
function moduleLabel(moduleName) {
|
|
129
|
+
return String(moduleName)
|
|
130
|
+
.split("/")
|
|
131
|
+
.filter(Boolean)
|
|
132
|
+
.map((segment) => pascal(segment))
|
|
133
|
+
.join(" / ");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Parse `--path=<panel>` into a normalized module prefix.
|
|
138
|
+
* A panel is only a folder separator, so nested values are allowed:
|
|
139
|
+
* `--path=admin` and `--path=admin/reporting` both validate.
|
|
140
|
+
*/
|
|
141
|
+
function parsePanelPath(flags) {
|
|
142
|
+
const matches = flags.filter((flag) => flag.startsWith("--path"));
|
|
143
|
+
if (matches.length === 0) return "";
|
|
144
|
+
if (matches.length > 1) throw new Error("--path can only be given once.");
|
|
145
|
+
const raw = matches[0].slice("--path".length).replace(/^=/, "").trim();
|
|
146
|
+
if (!raw) throw new Error("--path needs a value, e.g. --path=admin");
|
|
147
|
+
const segments = raw
|
|
148
|
+
.split(/[\\/]+/)
|
|
149
|
+
.filter(Boolean)
|
|
150
|
+
.map((segment) => segment.toLowerCase());
|
|
151
|
+
if (segments.length === 0) throw new Error(`Invalid --path value: ${raw}`);
|
|
152
|
+
for (const segment of segments) {
|
|
153
|
+
if (!/^[a-z0-9][a-z0-9-_]*$/.test(segment)) {
|
|
154
|
+
throw new Error(`Invalid --path segment "${segment}". Use lowercase letters, numbers and dashes.`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return segments.join("/");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** True when the path is a directory holding at least one entry. */
|
|
161
|
+
async function dirHasFiles(target) {
|
|
162
|
+
try {
|
|
163
|
+
const entries = await fs.readdir(target);
|
|
164
|
+
return entries.length > 0;
|
|
165
|
+
} catch {
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Resolve the module a subcommand targets.
|
|
172
|
+
*
|
|
173
|
+
* `rawModule` is always the leaf name the developer typed. Subcommands accept the
|
|
174
|
+
* same `--path=<panel>` flag as `module:make`, so `panelme --path=admin` addresses
|
|
175
|
+
* src/modules/admin/panelme instead of failing with "module does not exist".
|
|
176
|
+
* Flags may arrive either in the positional slot or in extraFlags depending on
|
|
177
|
+
* whether a name was given, so every candidate string is scanned.
|
|
178
|
+
*/
|
|
179
|
+
function resolveModuleRef(rawModule, ...flagGroups) {
|
|
180
|
+
const raw = assertName(rawModule, "Module name");
|
|
181
|
+
const panel = parsePanelPath(flagGroups.flat().filter((value) => typeof value === "string"));
|
|
182
|
+
const moduleName = panel ? `${panel}/${raw}` : raw;
|
|
183
|
+
// Derive the leaf from the resolved path so `admin/post` passed directly as the
|
|
184
|
+
// module argument still yields `post` for filenames and default names.
|
|
185
|
+
return { moduleName, leaf: leafName(moduleName) };
|
|
90
186
|
}
|
|
91
187
|
|
|
92
188
|
/** Ensure a module directory exists, throwing a helpful error if not. */
|
|
@@ -104,43 +200,87 @@ async function assertModuleExists(moduleName) {
|
|
|
104
200
|
return root;
|
|
105
201
|
}
|
|
106
202
|
|
|
107
|
-
/**
|
|
108
|
-
|
|
109
|
-
|
|
203
|
+
/** Build the placeholder substitution map shared by module file generators. */
|
|
204
|
+
function moduleFileVars(moduleName, controller) {
|
|
205
|
+
return {
|
|
206
|
+
module: moduleName,
|
|
207
|
+
controller,
|
|
208
|
+
ClassName: pascal(controller),
|
|
209
|
+
name: leafName(moduleName),
|
|
210
|
+
tableVariable: `${controller}s`
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Generate a controller file for a module.
|
|
216
|
+
* When a matching service exists the controller delegates to it; otherwise it
|
|
217
|
+
* falls back to the standalone shape so the file never imports a missing module.
|
|
218
|
+
*/
|
|
219
|
+
async function controllerFile(moduleName, controllerName, openApi, withService) {
|
|
110
220
|
const controller = controllerName.trim().toLowerCase();
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
221
|
+
const controllerStub = withService
|
|
222
|
+
? openApi
|
|
223
|
+
? STUBS.controller.openapi
|
|
224
|
+
: STUBS.controller.plain
|
|
225
|
+
: STUBS.controller.standalone;
|
|
226
|
+
return await stub(controllerStub, moduleFileVars(moduleName, controller));
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** Generate a schema file for a module. */
|
|
230
|
+
async function schemaFile(moduleName, controllerName, openApi) {
|
|
231
|
+
const controller = controllerName.trim().toLowerCase();
|
|
232
|
+
const schemaStub = openApi ? STUBS.schema.openapi : STUBS.schema.plain;
|
|
233
|
+
return await stub(schemaStub, moduleFileVars(moduleName, controller));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Generate a service file for a module. withModel picks the Drizzle-backed variant. */
|
|
237
|
+
async function serviceFile(moduleName, controllerName, withModel) {
|
|
238
|
+
const controller = controllerName.trim().toLowerCase();
|
|
239
|
+
const serviceStub = withModel ? STUBS.service.named : STUBS.service.plain;
|
|
240
|
+
return await stub(serviceStub, moduleFileVars(moduleName, controller));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Build the full default scaffold for module:make. */
|
|
244
|
+
async function moduleFiles(moduleName, openApi) {
|
|
245
|
+
const name = leafName(moduleName);
|
|
246
|
+
const vars = moduleFileVars(moduleName, name);
|
|
247
|
+
// The with-model controller variant only exists for the openapi style.
|
|
248
|
+
const controllerStub = openApi ? STUBS.controller.openapiWithModel : STUBS.controller.plain;
|
|
118
249
|
return {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}),
|
|
125
|
-
[`controllers/${controller}.controller.ts`]: await stub(controllerStub, {
|
|
126
|
-
module: moduleName,
|
|
127
|
-
controller,
|
|
128
|
-
ClassName: pascal(controller),
|
|
129
|
-
name: moduleName,
|
|
130
|
-
tableVariable: `${controller}s`
|
|
131
|
-
})
|
|
250
|
+
"facade.ts": await stub(STUBS.facade.named, vars),
|
|
251
|
+
[`controllers/${name}.ts`]: await stub(controllerStub, vars),
|
|
252
|
+
[`schemas/${name}.ts`]: await schemaFile(moduleName, name, openApi),
|
|
253
|
+
// module:make also writes database/models/<name>.ts, so the service can query it.
|
|
254
|
+
[`services/${name}.ts`]: await serviceFile(moduleName, name, true)
|
|
132
255
|
};
|
|
133
256
|
}
|
|
134
257
|
|
|
135
|
-
/**
|
|
258
|
+
/**
|
|
259
|
+
* Generate a wired route file. Only module:make uses this: it owns the whole
|
|
260
|
+
* module, so the controller and schema it just created are known to exist.
|
|
261
|
+
*/
|
|
136
262
|
async function routeTemplate(moduleName, controllerName = moduleName) {
|
|
137
|
-
const controller = controllerName
|
|
263
|
+
const controller = leafName(controllerName);
|
|
138
264
|
const routeStub = openApiEnabled() ? STUBS.route.api : STUBS.route.plain;
|
|
139
265
|
return await stub(routeStub, {
|
|
140
266
|
module: moduleName,
|
|
141
267
|
controller,
|
|
142
268
|
ClassName: pascal(controller),
|
|
143
|
-
ModuleClass:
|
|
269
|
+
ModuleClass: moduleLabel(moduleName)
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Generate a barebone route file that wires to nothing. module:make-route uses
|
|
275
|
+
* this so the developer decides which controller and schema to attach.
|
|
276
|
+
*/
|
|
277
|
+
async function standaloneRouteTemplate(moduleName, routeName) {
|
|
278
|
+
const routeStub = openApiEnabled() ? STUBS.route.standaloneApi : STUBS.route.standalonePlain;
|
|
279
|
+
return await stub(routeStub, {
|
|
280
|
+
module: moduleName,
|
|
281
|
+
controller: routeName,
|
|
282
|
+
ClassName: pascal(routeName),
|
|
283
|
+
ModuleClass: moduleLabel(moduleName)
|
|
144
284
|
});
|
|
145
285
|
}
|
|
146
286
|
|
|
@@ -168,14 +308,6 @@ async function namedSeederTemplate(moduleName, modelName, className) {
|
|
|
168
308
|
});
|
|
169
309
|
}
|
|
170
310
|
|
|
171
|
-
/** Comment out every line of content (used for seeders generated without --force). */
|
|
172
|
-
function asCommentedSeeder(content) {
|
|
173
|
-
return content
|
|
174
|
-
.split("\n")
|
|
175
|
-
.map((line) => (line ? `// ${line}` : "//"))
|
|
176
|
-
.join("\n");
|
|
177
|
-
}
|
|
178
|
-
|
|
179
311
|
/** Check if a file path exists on disk. */
|
|
180
312
|
async function pathExists(filePath) {
|
|
181
313
|
try {
|
|
@@ -186,40 +318,22 @@ async function pathExists(filePath) {
|
|
|
186
318
|
}
|
|
187
319
|
}
|
|
188
320
|
|
|
189
|
-
/**
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
321
|
+
/**
|
|
322
|
+
* Modules scaffolded before 4.1.0 kept schemas and services inside controllers/
|
|
323
|
+
* and used suffixed filenames. Returns the first legacy path that still exists
|
|
324
|
+
* so lookups resolve for existing projects.
|
|
325
|
+
*/
|
|
326
|
+
function legacyPath(moduleRootPath, folder, baseName) {
|
|
327
|
+
if (folder === "schemas") {
|
|
328
|
+
return path.join(moduleRootPath, "controllers", `${baseName}.schema.ts`);
|
|
196
329
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
let entries = [];
|
|
200
|
-
try {
|
|
201
|
-
entries = await fs.readdir(controllerDir, { withFileTypes: true });
|
|
202
|
-
} catch {
|
|
203
|
-
return moduleName;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
let latest = "";
|
|
207
|
-
let latestMtime = -1;
|
|
208
|
-
for (const entry of entries) {
|
|
209
|
-
if (!entry.isFile() || !entry.name.endsWith(".controller.ts")) continue;
|
|
210
|
-
const baseName = entry.name.replace(/\.controller\.ts$/, "");
|
|
211
|
-
const schemaPath = path.join(controllerDir, `${baseName}.schema.ts`);
|
|
212
|
-
if (!(await pathExists(schemaPath))) continue;
|
|
213
|
-
const fullPath = path.join(controllerDir, entry.name);
|
|
214
|
-
const stats = await fs.stat(fullPath);
|
|
215
|
-
const mtime = stats.mtimeMs || 0;
|
|
216
|
-
if (mtime > latestMtime) {
|
|
217
|
-
latest = baseName;
|
|
218
|
-
latestMtime = mtime;
|
|
219
|
-
}
|
|
330
|
+
if (folder === "helpers") {
|
|
331
|
+
return path.join(moduleRootPath, "controllers", `${baseName}.helpers.ts`);
|
|
220
332
|
}
|
|
221
|
-
|
|
222
|
-
|
|
333
|
+
if (folder === "services") {
|
|
334
|
+
return path.join(moduleRootPath, "controllers", `${baseName}.service.ts`);
|
|
335
|
+
}
|
|
336
|
+
return path.join(moduleRootPath, "controllers", `${baseName}.controller.ts`);
|
|
223
337
|
}
|
|
224
338
|
|
|
225
339
|
/** Write a file with safety checks (dry-run support, force overwrite protection). */
|
|
@@ -235,53 +349,97 @@ async function writeFileSafe(filePath, content, flags = [], label = "File") {
|
|
|
235
349
|
await fs.writeFile(filePath, content);
|
|
236
350
|
}
|
|
237
351
|
|
|
238
|
-
/**
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
352
|
+
/**
|
|
353
|
+
* Generate a complete module scaffold (facade, schema, service, controller, route, model, seeder).
|
|
354
|
+
* Pass `--path=<panel>` to nest the module under a panel folder, e.g.
|
|
355
|
+
* `module:make post --path=admin` writes to src/modules/admin/post/.
|
|
356
|
+
*/
|
|
357
|
+
export async function makeModule(rawName, flags = []) {
|
|
358
|
+
const result = await createModule(rawName, flags);
|
|
359
|
+
reportModule(result);
|
|
360
|
+
return result;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Create several modules in one run: `module:make post tag testme`.
|
|
365
|
+
*
|
|
366
|
+
* A name that cannot be created is reported and the batch continues, so one bad
|
|
367
|
+
* argument never costs you the modules that were fine. The caller decides the
|
|
368
|
+
* exit code from `failed`.
|
|
369
|
+
*/
|
|
370
|
+
export async function makeModules(rawNames, flags = []) {
|
|
371
|
+
const created = [];
|
|
372
|
+
const failed = [];
|
|
373
|
+
for (const rawName of rawNames) {
|
|
374
|
+
try {
|
|
375
|
+
created.push(await createModule(rawName, flags));
|
|
376
|
+
} catch (error) {
|
|
377
|
+
failed.push({ name: rawName, reason: error.message });
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (created.length) {
|
|
382
|
+
console.log(`Module${created.length > 1 ? "s" : ""} ready: ${created.map((item) => item.moduleName).join(", ")}`);
|
|
383
|
+
const panel = created[0].panel;
|
|
384
|
+
if (panel) {
|
|
385
|
+
console.log(` panel ${panel} -> ${created.map((item) => `/api/${item.moduleName}`).join(", ")}`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
for (const item of failed) {
|
|
389
|
+
console.log(` skipped ${item.name}: ${item.reason}`);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
return { created, failed };
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** Print the per-module summary for a single-module run. */
|
|
396
|
+
function reportModule({ moduleName, panel, leaf }) {
|
|
397
|
+
console.log(`Module ready: ${moduleName}`);
|
|
398
|
+
if (panel) console.log(`Panel: ${panel} -> /api/${moduleName}`);
|
|
399
|
+
console.log(`Database files: models/${leaf}.ts, seeders/${leaf}.ts`);
|
|
254
400
|
console.log(`Route style: ${openApiEnabled() ? "openapi" : "plain"}`);
|
|
401
|
+
if (panel) {
|
|
402
|
+
console.log(`Fix unresolved imports with: bun maker alias`);
|
|
403
|
+
}
|
|
255
404
|
}
|
|
256
405
|
|
|
257
|
-
/**
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
406
|
+
/**
|
|
407
|
+
* Scaffold one module.
|
|
408
|
+
*
|
|
409
|
+
* Refuses to touch a module folder that already has content. Without this guard
|
|
410
|
+
* writeFiles overwrites unconditionally, so re-running module:make on an
|
|
411
|
+
* existing module silently destroys the developer's edits to the controller,
|
|
412
|
+
* service, schema and route.
|
|
413
|
+
*/
|
|
414
|
+
async function createModule(rawName, flags = []) {
|
|
415
|
+
const leaf = assertName(rawName, "Module name");
|
|
416
|
+
const panel = parsePanelPath(flags);
|
|
417
|
+
const moduleName = panel ? `${panel}/${leaf}` : leaf;
|
|
418
|
+
const root = moduleRoot(moduleName);
|
|
419
|
+
const force = hasFlag(flags, "--force") || hasFlag(flags, "--yes");
|
|
420
|
+
if (!force && (await dirHasFiles(root))) {
|
|
421
|
+
throw new Error(`Module already exists: src/modules/${moduleName}. Re-run with --force to overwrite its files.`);
|
|
422
|
+
}
|
|
261
423
|
const openApi = openApiEnabled();
|
|
262
|
-
await
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}),
|
|
269
|
-
"routes/api.ts": await stub(openApi ? STUBS.example.routeApi : STUBS.example.routePlain, { module: moduleName }),
|
|
270
|
-
[`jobs/${moduleName}.ts`]: await stub(STUBS.example.job, { module: moduleName }),
|
|
271
|
-
[`console/${moduleName}.ts`]: await stub(STUBS.example.console, { module: moduleName })
|
|
272
|
-
});
|
|
424
|
+
const files = await moduleFiles(moduleName, openApi);
|
|
425
|
+
const route = await routeTemplate(moduleName);
|
|
426
|
+
const dialect = detectDialect();
|
|
427
|
+
const model = await namedModelTemplate(moduleName, leaf, dialect);
|
|
428
|
+
const seeder = await namedSeederTemplate(moduleName, leaf, leaf);
|
|
429
|
+
await writeFiles(root, { ...files, "routes/index.ts": route });
|
|
273
430
|
await fs.mkdir(path.join(root, "database", "models"), { recursive: true });
|
|
274
431
|
await fs.mkdir(path.join(root, "database", "seeders"), { recursive: true });
|
|
275
|
-
|
|
276
|
-
|
|
432
|
+
await fs.writeFile(path.join(root, "database", "models", `${leaf}.ts`), model);
|
|
433
|
+
await fs.writeFile(path.join(root, "database", "seeders", `${leaf}.ts`), seeder);
|
|
434
|
+
return { moduleName, panel, leaf };
|
|
277
435
|
}
|
|
278
436
|
|
|
279
437
|
/** Generate a route file for an existing module. */
|
|
280
438
|
export async function makeRoute(rawModule, rawControllerOrFlag, extraFlags = []) {
|
|
281
|
-
const moduleName =
|
|
439
|
+
const { moduleName, leaf } = resolveModuleRef(rawModule, rawControllerOrFlag, extraFlags);
|
|
282
440
|
const root = await assertModuleExists(moduleName);
|
|
283
441
|
const flags = [];
|
|
284
|
-
let routeName =
|
|
442
|
+
let routeName = leaf;
|
|
285
443
|
let controllerExplicit = false;
|
|
286
444
|
if (rawControllerOrFlag) {
|
|
287
445
|
if (rawControllerOrFlag.startsWith("--")) flags.push(rawControllerOrFlag);
|
|
@@ -291,9 +449,8 @@ export async function makeRoute(rawModule, rawControllerOrFlag, extraFlags = [])
|
|
|
291
449
|
}
|
|
292
450
|
}
|
|
293
451
|
flags.push(...extraFlags);
|
|
294
|
-
const
|
|
295
|
-
const
|
|
296
|
-
const routeFile = controllerExplicit ? `${routeName}.ts` : "api.ts";
|
|
452
|
+
const route = await standaloneRouteTemplate(moduleName, routeName);
|
|
453
|
+
const routeFile = controllerExplicit ? `${routeName}.ts` : "index.ts";
|
|
297
454
|
const routePath = path.join(root, `routes/${routeFile}`);
|
|
298
455
|
const dryRun = hasFlag(flags, "--dry-run");
|
|
299
456
|
const force = hasFlag(flags, "--force") || hasFlag(flags, "--yes");
|
|
@@ -302,7 +459,7 @@ export async function makeRoute(rawModule, rawControllerOrFlag, extraFlags = [])
|
|
|
302
459
|
try {
|
|
303
460
|
await fs.access(routePath);
|
|
304
461
|
exists = true;
|
|
305
|
-
} catch {}
|
|
462
|
+
} catch { }
|
|
306
463
|
if (exists && !force)
|
|
307
464
|
throw new Error(`Route file already exists: ${path.relative(process.cwd(), routePath)}. Re-run with --force to overwrite.`);
|
|
308
465
|
await fs.mkdir(path.dirname(routePath), { recursive: true });
|
|
@@ -311,31 +468,36 @@ export async function makeRoute(rawModule, rawControllerOrFlag, extraFlags = [])
|
|
|
311
468
|
}
|
|
312
469
|
|
|
313
470
|
/** Generate a notification module with controller, routes, and job. */
|
|
314
|
-
export async function makeNotificationModule(rawName = "notification") {
|
|
315
|
-
const moduleName =
|
|
316
|
-
const root =
|
|
471
|
+
export async function makeNotificationModule(rawName = "notification", flags = []) {
|
|
472
|
+
const { moduleName, leaf } = resolveModuleRef(rawName, flags);
|
|
473
|
+
const root = moduleRoot(moduleName);
|
|
474
|
+
const force = hasFlag(flags, "--force") || hasFlag(flags, "--yes");
|
|
475
|
+
if (!force && (await dirHasFiles(root))) {
|
|
476
|
+
throw new Error(`Module already exists: src/modules/${moduleName}. Re-run with --force to overwrite its files.`);
|
|
477
|
+
}
|
|
317
478
|
const openApi = openApiEnabled();
|
|
318
479
|
|
|
319
480
|
await writeFiles(root, {
|
|
320
|
-
[`controllers/${
|
|
481
|
+
[`controllers/${leaf}.ts`]: await stub(STUBS.notification.controller, {
|
|
321
482
|
module: moduleName
|
|
322
483
|
}),
|
|
323
|
-
[`
|
|
484
|
+
[`schemas/${leaf}.ts`]: await stub(openApi ? STUBS.notification.schema.openapi : STUBS.notification.schema.plain, {
|
|
324
485
|
module: moduleName
|
|
325
486
|
}),
|
|
326
|
-
"routes/
|
|
327
|
-
[`jobs/${
|
|
487
|
+
"routes/index.ts": await stub(openApi ? STUBS.notification.routeApi : STUBS.notification.routePlain, { module: moduleName }),
|
|
488
|
+
[`jobs/${leaf}.ts`]: await stub(STUBS.notification.job, { module: moduleName })
|
|
328
489
|
});
|
|
329
490
|
|
|
330
491
|
console.log(`Notification module ready: ${moduleName}`);
|
|
492
|
+
if (panelOf(moduleName)) console.log(`Panel: ${panelOf(moduleName)} -> /api/${moduleName}`);
|
|
331
493
|
console.log(`Route style: ${openApi ? "openapi" : "plain"}`);
|
|
332
494
|
console.log("See docs (packages/docs/guide/notification.md) for Vue UI integration.");
|
|
333
495
|
}
|
|
334
496
|
|
|
335
497
|
/** Soft-delete a module by moving it to storage trash. */
|
|
336
498
|
export async function deleteModule(rawName, flags = []) {
|
|
337
|
-
const name =
|
|
338
|
-
if (
|
|
499
|
+
const { moduleName: name, leaf } = resolveModuleRef(rawName, flags);
|
|
500
|
+
if (leaf === "notification") {
|
|
339
501
|
console.log("Use `bun maker module:delete-notification` to remove the notification module (removes UI files too).");
|
|
340
502
|
return;
|
|
341
503
|
}
|
|
@@ -352,7 +514,8 @@ export async function deleteModule(rawName, flags = []) {
|
|
|
352
514
|
if (!stats.isDirectory()) throw new Error(`Module path is not a directory: src/modules/${name}`);
|
|
353
515
|
const trashRoot = path.resolve(process.cwd(), "src/storage/trash/modules");
|
|
354
516
|
const stamp = new Date().toISOString().replace(/[.:]/g, "-");
|
|
355
|
-
|
|
517
|
+
// Trash keeps the panel path so admin/post stays distinguishable from admin/user.
|
|
518
|
+
const trashPath = path.join(trashRoot, `${name.replaceAll("/", "-")}-${stamp}`);
|
|
356
519
|
const dryRun = hasFlag(flags, "--dry-run");
|
|
357
520
|
const confirmed = hasFlag(flags, "--yes") || hasFlag(flags, "--force");
|
|
358
521
|
if (dryRun) return;
|
|
@@ -373,11 +536,11 @@ export async function deleteModule(rawName, flags = []) {
|
|
|
373
536
|
|
|
374
537
|
/** Remove a notification module (backend only — moves to trash). */
|
|
375
538
|
export async function deleteNotificationModule(rawName = "notification", flags = []) {
|
|
376
|
-
const moduleName =
|
|
539
|
+
const { moduleName, leaf } = resolveModuleRef(rawName, flags);
|
|
377
540
|
const dryRun = hasFlag(flags, "--dry-run");
|
|
378
541
|
const confirmed = hasFlag(flags, "--yes") || hasFlag(flags, "--force");
|
|
379
542
|
|
|
380
|
-
const modulePath =
|
|
543
|
+
const modulePath = moduleRoot(moduleName);
|
|
381
544
|
|
|
382
545
|
if (dryRun) {
|
|
383
546
|
const exists = await fs
|
|
@@ -390,7 +553,7 @@ export async function deleteNotificationModule(rawName = "notification", flags =
|
|
|
390
553
|
|
|
391
554
|
if (!confirmed) {
|
|
392
555
|
throw new Error(
|
|
393
|
-
`Refusing to delete notification module without confirmation. Re-run with: bun maker module:delete-notification ${
|
|
556
|
+
`Refusing to delete notification module without confirmation. Re-run with: bun maker module:delete-notification ${leaf} --yes${panelOf(moduleName) ? ` --path=${panelOf(moduleName)}` : ""}`
|
|
394
557
|
);
|
|
395
558
|
}
|
|
396
559
|
|
|
@@ -453,28 +616,93 @@ export async function cleanModuleTrash(rawName, flags = []) {
|
|
|
453
616
|
for (const name of matches) await fs.rm(path.join(trashRoot, name), { recursive: true, force: true });
|
|
454
617
|
}
|
|
455
618
|
|
|
456
|
-
/**
|
|
457
|
-
|
|
458
|
-
|
|
619
|
+
/**
|
|
620
|
+
* Split "<module> <nameOrFlag> [extraFlags]" into a validated name plus flags.
|
|
621
|
+
* Shared by the per-file commands so they all accept --force/--dry-run the same way.
|
|
622
|
+
*/
|
|
623
|
+
function parseNameArg(rawNameOrFlag, extraFlags, kindLabel, fallback) {
|
|
459
624
|
const flags = [];
|
|
460
|
-
let
|
|
461
|
-
if (
|
|
462
|
-
if (
|
|
463
|
-
else
|
|
625
|
+
let name = fallback;
|
|
626
|
+
if (rawNameOrFlag) {
|
|
627
|
+
if (rawNameOrFlag.startsWith("--")) flags.push(rawNameOrFlag);
|
|
628
|
+
else name = assertName(rawNameOrFlag, kindLabel);
|
|
464
629
|
}
|
|
465
630
|
flags.push(...extraFlags);
|
|
631
|
+
return { name, flags };
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/** Generate a controller for an existing module. */
|
|
635
|
+
export async function makeController(rawModule, rawControllerOrFlag, extraFlags = []) {
|
|
636
|
+
const { moduleName, leaf } = resolveModuleRef(rawModule, rawControllerOrFlag, extraFlags);
|
|
637
|
+
const { name, flags } = parseNameArg(rawControllerOrFlag, extraFlags, "Controller name", leaf);
|
|
466
638
|
const root = await assertModuleExists(moduleName);
|
|
467
|
-
const
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
}
|
|
639
|
+
const lower = name.toLowerCase();
|
|
640
|
+
const hasService = (await pathExists(path.join(root, "services", `${lower}.ts`))) || (await pathExists(legacyPath(root, "services", lower)));
|
|
641
|
+
const content = await controllerFile(moduleName, name, openApiEnabled(), hasService);
|
|
642
|
+
await writeFileSafe(path.join(root, `controllers/${lower}.ts`), content, flags, "Controller file");
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/** Generate a schema file for an existing module. */
|
|
646
|
+
export async function makeSchema(rawModule, rawNameOrFlag, extraFlags = []) {
|
|
647
|
+
const { moduleName, leaf } = resolveModuleRef(rawModule, rawNameOrFlag, extraFlags);
|
|
648
|
+
const { name, flags } = parseNameArg(rawNameOrFlag, extraFlags, "Schema name", leaf);
|
|
649
|
+
const root = await assertModuleExists(moduleName);
|
|
650
|
+
const content = await schemaFile(moduleName, name, openApiEnabled());
|
|
651
|
+
await writeFileSafe(path.join(root, `schemas/${name.toLowerCase()}.ts`), content, flags, "Schema file");
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Generate a service file for an existing module.
|
|
656
|
+
* Pass --with-model when the module has database/models/<name>.ts so the
|
|
657
|
+
* service queries the table instead of returning the empty defaults.
|
|
658
|
+
*/
|
|
659
|
+
export async function makeService(rawModule, rawNameOrFlag, extraFlags = []) {
|
|
660
|
+
const { moduleName, leaf } = resolveModuleRef(rawModule, rawNameOrFlag, extraFlags);
|
|
661
|
+
const { name, flags } = parseNameArg(rawNameOrFlag, extraFlags, "Service name", leaf);
|
|
662
|
+
const root = await assertModuleExists(moduleName);
|
|
663
|
+
const lower = name.toLowerCase();
|
|
664
|
+
const withModel = hasFlag(flags, "--with-model") || (await pathExists(path.join(root, "database", "models", `${lower}.ts`)));
|
|
665
|
+
const content = await serviceFile(moduleName, name, withModel);
|
|
666
|
+
await writeFileSafe(path.join(root, `services/${lower}.ts`), content, flags, "Service file");
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/** Generate a helper file for an existing module. */
|
|
670
|
+
export async function makeHelper(rawModule, rawNameOrFlag, extraFlags = []) {
|
|
671
|
+
const { moduleName, leaf } = resolveModuleRef(rawModule, rawNameOrFlag, extraFlags);
|
|
672
|
+
const { name, flags } = parseNameArg(rawNameOrFlag, extraFlags, "Helper name", leaf);
|
|
673
|
+
const root = await assertModuleExists(moduleName);
|
|
674
|
+
const content = await stub(STUBS.helper.named, { module: moduleName, name, Name: pascal(name) });
|
|
675
|
+
await writeFileSafe(path.join(root, `helpers/${name}.ts`), content, flags, "Helper file");
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/** Generate a module-local middleware file for an existing module. */
|
|
679
|
+
export async function makeLocalMiddleware(rawModule, rawNameOrFlag, extraFlags = []) {
|
|
680
|
+
const { moduleName, leaf } = resolveModuleRef(rawModule, rawNameOrFlag, extraFlags);
|
|
681
|
+
const { name, flags } = parseNameArg(rawNameOrFlag, extraFlags, "Middleware name", leaf);
|
|
682
|
+
const root = await assertModuleExists(moduleName);
|
|
683
|
+
const content = await stub(STUBS.middleware.local, {
|
|
684
|
+
module: moduleName,
|
|
685
|
+
name,
|
|
686
|
+
camel: camelCase(name),
|
|
687
|
+
Name: pascal(name)
|
|
688
|
+
});
|
|
689
|
+
await writeFileSafe(path.join(root, `middlewares/${name}.ts`), content, flags, "Middleware file");
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/** Generate a types file for an existing module. */
|
|
693
|
+
export async function makeType(rawModule, rawNameOrFlag, extraFlags = []) {
|
|
694
|
+
const { moduleName, leaf } = resolveModuleRef(rawModule, rawNameOrFlag, extraFlags);
|
|
695
|
+
const { name, flags } = parseNameArg(rawNameOrFlag, extraFlags, "Type name", leaf);
|
|
696
|
+
const root = await assertModuleExists(moduleName);
|
|
697
|
+
const content = await stub(STUBS.type.named, { module: moduleName, name, Name: pascal(name) });
|
|
698
|
+
await writeFileSafe(path.join(root, `types/${name}.ts`), content, flags, "Type file");
|
|
471
699
|
}
|
|
472
700
|
|
|
473
701
|
/** Generate a model file for an existing module. */
|
|
474
702
|
export async function makeModel(rawModule, rawNameOrFlag, extraFlags = []) {
|
|
475
|
-
const moduleName =
|
|
703
|
+
const { moduleName, leaf } = resolveModuleRef(rawModule, rawNameOrFlag, extraFlags);
|
|
476
704
|
const flags = [];
|
|
477
|
-
let name =
|
|
705
|
+
let name = leaf;
|
|
478
706
|
if (rawNameOrFlag) {
|
|
479
707
|
if (rawNameOrFlag.startsWith("--")) flags.push(rawNameOrFlag);
|
|
480
708
|
else name = assertName(rawNameOrFlag, "Model name");
|
|
@@ -488,9 +716,9 @@ export async function makeModel(rawModule, rawNameOrFlag, extraFlags = []) {
|
|
|
488
716
|
|
|
489
717
|
/** Generate a seeder file for an existing module model. */
|
|
490
718
|
export async function makeSeeder(rawModule, rawNameOrFlag, extraFlags = []) {
|
|
491
|
-
const moduleName =
|
|
719
|
+
const { moduleName, leaf } = resolveModuleRef(rawModule, rawNameOrFlag, extraFlags);
|
|
492
720
|
const flags = [];
|
|
493
|
-
let name =
|
|
721
|
+
let name = leaf;
|
|
494
722
|
if (rawNameOrFlag) {
|
|
495
723
|
if (rawNameOrFlag.startsWith("--")) flags.push(rawNameOrFlag);
|
|
496
724
|
else name = assertName(rawNameOrFlag, "Seeder name");
|
|
@@ -503,34 +731,35 @@ export async function makeSeeder(rawModule, rawNameOrFlag, extraFlags = []) {
|
|
|
503
731
|
try {
|
|
504
732
|
await fs.access(modelPath);
|
|
505
733
|
} catch {
|
|
506
|
-
|
|
734
|
+
// Fall back to the module's own model. This must use the leaf, not
|
|
735
|
+
// moduleName: for a panel module moduleName is "admin/testme", so using it
|
|
736
|
+
// would probe database/models/admin/testme.ts and never match.
|
|
737
|
+
modelName = leaf;
|
|
507
738
|
modelPath = path.join(root, "database", "models", `${modelName}.ts`);
|
|
508
739
|
|
|
509
740
|
try {
|
|
510
741
|
await fs.access(modelPath);
|
|
511
742
|
console.log(`Model '${name}' not found, using module model '${modelName}' for seeder '${name}'.`);
|
|
512
743
|
} catch {
|
|
744
|
+
const panel = panelOf(moduleName);
|
|
745
|
+
const suggest = panel ? `${leaf} ${name} --path=${panel}` : `${leaf} ${name}`;
|
|
513
746
|
throw new Error(
|
|
514
|
-
`Model not found for seeder: src/modules/${moduleName}/database/models/${name}.ts. Create it first with: bun maker module:make-model ${
|
|
747
|
+
`Model not found for seeder: src/modules/${moduleName}/database/models/${name}.ts. Create it first with: bun maker module:make-model ${suggest}`
|
|
515
748
|
);
|
|
516
749
|
}
|
|
517
750
|
}
|
|
518
751
|
|
|
519
|
-
await
|
|
520
|
-
|
|
521
|
-
await namedSeederTemplate(moduleName, modelName, name),
|
|
522
|
-
flags,
|
|
523
|
-
"Seeder file"
|
|
524
|
-
);
|
|
752
|
+
const seeder = await namedSeederTemplate(moduleName, modelName, name);
|
|
753
|
+
await writeFileSafe(path.join(root, `database/seeders/${name}.ts`), seeder, flags, "Seeder file");
|
|
525
754
|
|
|
526
755
|
console.log(`Seeder ready: ${moduleName}/${name}`);
|
|
527
756
|
}
|
|
528
757
|
|
|
529
758
|
/** Generate a job file for an existing module. */
|
|
530
759
|
export async function makeJob(rawModule, rawNameOrFlag, extraFlags = []) {
|
|
531
|
-
const moduleName =
|
|
760
|
+
const { moduleName, leaf } = resolveModuleRef(rawModule, rawNameOrFlag, extraFlags);
|
|
532
761
|
const flags = [];
|
|
533
|
-
let name =
|
|
762
|
+
let name = leaf;
|
|
534
763
|
if (rawNameOrFlag) {
|
|
535
764
|
if (rawNameOrFlag.startsWith("--")) flags.push(rawNameOrFlag);
|
|
536
765
|
else name = assertName(rawNameOrFlag, "Job name");
|
|
@@ -542,9 +771,9 @@ export async function makeJob(rawModule, rawNameOrFlag, extraFlags = []) {
|
|
|
542
771
|
|
|
543
772
|
/** Generate a schedule/console file for an existing module. */
|
|
544
773
|
export async function makeSchedule(rawModule, rawNameOrFlag, extraFlags = []) {
|
|
545
|
-
const moduleName =
|
|
774
|
+
const { moduleName, leaf } = resolveModuleRef(rawModule, rawNameOrFlag, extraFlags);
|
|
546
775
|
const flags = [];
|
|
547
|
-
let name =
|
|
776
|
+
let name = leaf;
|
|
548
777
|
if (rawNameOrFlag) {
|
|
549
778
|
if (rawNameOrFlag.startsWith("--")) flags.push(rawNameOrFlag);
|
|
550
779
|
else name = assertName(rawNameOrFlag, "Schedule name");
|
|
@@ -580,7 +809,8 @@ export async function listModules() {
|
|
|
580
809
|
}
|
|
581
810
|
|
|
582
811
|
/** Check that a module has at least one seeder file. */
|
|
583
|
-
export async function assertModuleHasSeeders(
|
|
812
|
+
export async function assertModuleHasSeeders(rawModule, flags = []) {
|
|
813
|
+
const { moduleName } = resolveModuleRef(rawModule, flags);
|
|
584
814
|
await assertModuleExists(moduleName);
|
|
585
815
|
const files = await glob(`${moduleName}/database/seeders/*.{ts,js}`, {
|
|
586
816
|
cwd: path.resolve(process.cwd(), "src/modules"),
|
|
@@ -628,7 +858,10 @@ async function generateModuleSchemaTemp(moduleName) {
|
|
|
628
858
|
const exports = [];
|
|
629
859
|
const tempDir = path.resolve(process.cwd(), "src/storage/tmp");
|
|
630
860
|
await fs.mkdir(tempDir, { recursive: true });
|
|
631
|
-
|
|
861
|
+
// Flatten the panel path: "admin/testme" must not become a nested
|
|
862
|
+
// storage/tmp/schema.admin/testme.<ts>.ts path.
|
|
863
|
+
const slug = moduleName.replaceAll("/", "-");
|
|
864
|
+
const tempSchemaPath = path.join(tempDir, `schema.${slug}.${Date.now()}.ts`);
|
|
632
865
|
const tempSchemaDir = path.dirname(tempSchemaPath);
|
|
633
866
|
for (const file of files.sort()) {
|
|
634
867
|
const absoluteFile = path.join(backendSrc, file);
|
|
@@ -645,7 +878,7 @@ async function generateModuleSchemaTemp(moduleName) {
|
|
|
645
878
|
|
|
646
879
|
/** Generate and run migrations for a single module using a temporary schema. */
|
|
647
880
|
export async function runModuleMigrate(rawModuleName, rawArgs = []) {
|
|
648
|
-
const moduleName =
|
|
881
|
+
const { moduleName } = resolveModuleRef(rawModuleName, rawArgs);
|
|
649
882
|
await syncMigrationDialect();
|
|
650
883
|
await ensureMigrationMeta();
|
|
651
884
|
await ensureDatabaseDirectory();
|
|
@@ -661,15 +894,15 @@ export async function runModuleMigrate(rawModuleName, rawArgs = []) {
|
|
|
661
894
|
} finally {
|
|
662
895
|
if (previousSchema == null) delete process.env.DRIZZLE_SCHEMA;
|
|
663
896
|
else process.env.DRIZZLE_SCHEMA = previousSchema;
|
|
664
|
-
if (!keepTemp) await fs.rm(tempSchemaPath, { force: true }).catch(() => {});
|
|
897
|
+
if (!keepTemp) await fs.rm(tempSchemaPath, { force: true }).catch(() => { });
|
|
665
898
|
}
|
|
666
899
|
}
|
|
667
900
|
|
|
668
901
|
/** Generate a unit test file for a module. */
|
|
669
902
|
export async function makeTest(rawModule, rawNameOrFlag, extraFlags = []) {
|
|
670
|
-
const moduleName =
|
|
903
|
+
const { moduleName, leaf } = resolveModuleRef(rawModule, rawNameOrFlag, extraFlags);
|
|
671
904
|
const flags = [];
|
|
672
|
-
let name =
|
|
905
|
+
let name = leaf;
|
|
673
906
|
if (rawNameOrFlag) {
|
|
674
907
|
if (rawNameOrFlag.startsWith("--")) flags.push(rawNameOrFlag);
|
|
675
908
|
else name = assertName(rawNameOrFlag, "Test name");
|