create-nexwire 4.1.1 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/express/gitignore-stub +31 -24
- package/express/package.json +3 -3
- package/express/src/database/migrations/sqlite/0000_init.sql +60 -60
- 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/maker-cli/db/index.mjs +1 -1
- package/express/src/framework/maker-cli/deploy/core.mjs +0 -1
- package/express/src/framework/maker-cli/index.mjs +13 -3
- package/express/src/framework/maker-cli/module/core.mjs +121 -43
- package/express/src/framework/maker-cli/runtime/core.mjs +26 -7
- package/express/src/framework/maker-cli/runtime/index.mjs +120 -104
- package/express/src/framework/maker-cli/stubs/controller/openapi.ts.stub +6 -6
- package/express/src/framework/maker-cli/stubs/controller/plain.ts.stub +6 -6
- package/express/src/framework/maker-cli/stubs/helper/name.ts.stub +1 -1
- package/express/src/framework/maker-cli/stubs/seeder/name.ts.stub +17 -19
- package/express/src/framework/maker-cli/stubs/seeder/standalone.ts.stub +22 -0
- package/express/src/framework/maker-cli/stubs/service/name.plain.ts.stub +2 -2
- package/express/src/framework/maker-cli/stubs/service/name.ts.stub +1 -1
- package/express/src/framework/maker-cli/utils/help.mjs +374 -50
- package/express/src/framework/maker-cli/utils/naming.mjs +83 -20
- package/express/src/framework/maker-cli/utils/project-commands.mjs +140 -0
- package/express/src/framework/queue/queue.ts +402 -342
- package/express/src/modules/auth/database/seeders/role.ts +18 -18
- package/express/src/modules/auth/database/seeders/user.ts +35 -35
- package/express/src/storage/database/nexwire.sqlite +0 -0
- package/express/vitest.config.ts +38 -23
- package/hono/gitignore-stub +31 -24
- package/hono/package.json +3 -3
- package/hono/src/database/migrations/sqlite/0000_init.sql +60 -60
- 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/database/paginate.ts +253 -253
- package/hono/src/framework/maker-cli/db/index.mjs +1 -1
- package/hono/src/framework/maker-cli/deploy/core.mjs +0 -1
- package/hono/src/framework/maker-cli/index.mjs +13 -3
- package/hono/src/framework/maker-cli/module/core.mjs +116 -38
- package/hono/src/framework/maker-cli/runtime/core.mjs +24 -6
- package/hono/src/framework/maker-cli/runtime/index.mjs +120 -104
- package/hono/src/framework/maker-cli/stubs/controller/openapi.ts.stub +6 -6
- package/hono/src/framework/maker-cli/stubs/controller/plain.ts.stub +6 -6
- package/hono/src/framework/maker-cli/stubs/helper/name.ts.stub +1 -1
- package/hono/src/framework/maker-cli/stubs/seeder/name.ts.stub +17 -19
- package/hono/src/framework/maker-cli/stubs/seeder/standalone.ts.stub +22 -0
- package/hono/src/framework/maker-cli/stubs/service/name.plain.ts.stub +2 -2
- package/hono/src/framework/maker-cli/stubs/service/name.ts.stub +1 -1
- package/hono/src/framework/maker-cli/utils/help.mjs +374 -50
- package/hono/src/framework/maker-cli/utils/naming.mjs +83 -20
- package/hono/src/framework/maker-cli/utils/project-commands.mjs +140 -0
- package/hono/src/framework/queue/queue.ts +402 -342
- package/hono/src/modules/auth/database/seeders/role.ts +18 -18
- package/hono/src/modules/auth/database/seeders/user.ts +35 -35
- package/hono/src/storage/database/nexwire.sqlite +0 -0
- package/hono/vitest.config.ts +38 -23
- package/package.json +1 -1
- package/express/src/framework/maker-cli/stubs/controller/openapi.with-model.ts.stub +0 -73
- package/hono/src/framework/maker-cli/stubs/controller/openapi.with-model.ts.stub +0 -69
|
@@ -5,7 +5,7 @@ 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, camelCase, pascal } from "../utils/naming.mjs";
|
|
8
|
+
import { assertName, camelCase, normalizeName, pascal, plural, snakeCase } 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");
|
|
@@ -18,7 +18,6 @@ const STUBS = {
|
|
|
18
18
|
},
|
|
19
19
|
controller: {
|
|
20
20
|
openapi: "controller/openapi.ts.stub",
|
|
21
|
-
openapiWithModel: "controller/openapi.with-model.ts.stub",
|
|
22
21
|
plain: "controller/plain.ts.stub",
|
|
23
22
|
standalone: "controller/standalone.ts.stub"
|
|
24
23
|
},
|
|
@@ -36,7 +35,8 @@ const STUBS = {
|
|
|
36
35
|
}
|
|
37
36
|
},
|
|
38
37
|
seeder: {
|
|
39
|
-
named: "seeder/name.ts.stub"
|
|
38
|
+
named: "seeder/name.ts.stub",
|
|
39
|
+
standalone: "seeder/standalone.ts.stub"
|
|
40
40
|
},
|
|
41
41
|
service: {
|
|
42
42
|
named: "service/name.ts.stub",
|
|
@@ -185,6 +185,35 @@ function resolveModuleRef(rawModule, ...flagGroups) {
|
|
|
185
185
|
return { moduleName, leaf: leafName(moduleName) };
|
|
186
186
|
}
|
|
187
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Find an existing module directory whose folder name spells the requested
|
|
190
|
+
* module differently, only in the separators used (`my_module` vs `my-module`).
|
|
191
|
+
*
|
|
192
|
+
* Names are canonicalised to kebab-case, so a module scaffolded before this
|
|
193
|
+
* happened can live in a folder the canonical name no longer resolves to.
|
|
194
|
+
* Without this lookup, normalising the name would lock a developer out of their
|
|
195
|
+
* own module. Matching is segment by segment so a panel path such as
|
|
196
|
+
* `admin/reporting` keeps working, and each candidate is a real directory
|
|
197
|
+
* inside src/modules, so the result cannot escape it.
|
|
198
|
+
*/
|
|
199
|
+
async function resolveExistingModuleDir(moduleName) {
|
|
200
|
+
let current = path.resolve(process.cwd(), "src/modules");
|
|
201
|
+
for (const segment of moduleName.split("/").filter(Boolean)) {
|
|
202
|
+
let entries;
|
|
203
|
+
try {
|
|
204
|
+
entries = await fs.readdir(current, { withFileTypes: true });
|
|
205
|
+
} catch {
|
|
206
|
+
return "";
|
|
207
|
+
}
|
|
208
|
+
const directories = entries.filter((entry) => entry.isDirectory());
|
|
209
|
+
const exact = directories.find((entry) => entry.name === segment);
|
|
210
|
+
const loose = exact ?? directories.find((entry) => normalizeName(entry.name) === normalizeName(segment));
|
|
211
|
+
if (!loose) return "";
|
|
212
|
+
current = path.join(current, loose.name);
|
|
213
|
+
}
|
|
214
|
+
return current;
|
|
215
|
+
}
|
|
216
|
+
|
|
188
217
|
/** Ensure a module directory exists, throwing a helpful error if not. */
|
|
189
218
|
async function assertModuleExists(moduleName) {
|
|
190
219
|
const root = moduleRoot(moduleName);
|
|
@@ -192,6 +221,8 @@ async function assertModuleExists(moduleName) {
|
|
|
192
221
|
try {
|
|
193
222
|
stats = await fs.stat(root);
|
|
194
223
|
} catch {
|
|
224
|
+
const legacy = await resolveExistingModuleDir(moduleName);
|
|
225
|
+
if (legacy) return legacy;
|
|
195
226
|
throw new Error(`Module does not exist: ${moduleName}. Create it first with: bun maker module:make ${moduleName}`);
|
|
196
227
|
}
|
|
197
228
|
if (!stats.isDirectory()) {
|
|
@@ -200,14 +231,28 @@ async function assertModuleExists(moduleName) {
|
|
|
200
231
|
return root;
|
|
201
232
|
}
|
|
202
233
|
|
|
203
|
-
/**
|
|
234
|
+
/**
|
|
235
|
+
* Build the placeholder substitution map shared by module file generators.
|
|
236
|
+
*
|
|
237
|
+
* The same developer-supplied name reaches four different places in a stub, and
|
|
238
|
+
* each needs a different shape: import paths use the kebab file name, the
|
|
239
|
+
* service identifier is camelCase, the table identifier is camelCase plural and
|
|
240
|
+
* the SQL table name is snake_case plural. Deriving all of them from one
|
|
241
|
+
* canonical name is what keeps a multi-word name from landing in an identifier
|
|
242
|
+
* position as `game-configService`, which parses as subtraction.
|
|
243
|
+
*/
|
|
204
244
|
function moduleFileVars(moduleName, controller) {
|
|
245
|
+
const base = normalizeName(controller);
|
|
205
246
|
return {
|
|
206
247
|
module: moduleName,
|
|
207
|
-
|
|
208
|
-
|
|
248
|
+
// File name on disk and therefore every import path that points at it.
|
|
249
|
+
controller: base,
|
|
250
|
+
// Identifier the stubs export and call.
|
|
251
|
+
ServiceName: `${camelCase(base)}Service`,
|
|
252
|
+
ClassName: pascal(base),
|
|
209
253
|
name: leafName(moduleName),
|
|
210
|
-
|
|
254
|
+
tableName: plural(snakeCase(base)),
|
|
255
|
+
tableVariable: plural(camelCase(base))
|
|
211
256
|
};
|
|
212
257
|
}
|
|
213
258
|
|
|
@@ -244,8 +289,7 @@ async function serviceFile(moduleName, controllerName, withModel) {
|
|
|
244
289
|
async function moduleFiles(moduleName, openApi) {
|
|
245
290
|
const name = leafName(moduleName);
|
|
246
291
|
const vars = moduleFileVars(moduleName, name);
|
|
247
|
-
|
|
248
|
-
const controllerStub = openApi ? STUBS.controller.openapiWithModel : STUBS.controller.plain;
|
|
292
|
+
const controllerStub = openApi ? STUBS.controller.openapi : STUBS.controller.plain;
|
|
249
293
|
return {
|
|
250
294
|
"facade.ts": await stub(STUBS.facade.named, vars),
|
|
251
295
|
[`controllers/${name}.ts`]: await stub(controllerStub, vars),
|
|
@@ -286,25 +330,28 @@ async function standaloneRouteTemplate(moduleName, routeName) {
|
|
|
286
330
|
|
|
287
331
|
/** Generate a named model file for a module. */
|
|
288
332
|
async function namedModelTemplate(moduleName, name, dialect) {
|
|
333
|
+
const base = normalizeName(name);
|
|
289
334
|
return await stub(STUBS.model.named[dialect], {
|
|
290
335
|
module: moduleName,
|
|
291
|
-
name,
|
|
292
|
-
controller:
|
|
293
|
-
ClassName: pascal(
|
|
294
|
-
tableName:
|
|
295
|
-
tableVariable:
|
|
336
|
+
name: base,
|
|
337
|
+
controller: base,
|
|
338
|
+
ClassName: pascal(base),
|
|
339
|
+
tableName: plural(snakeCase(base)),
|
|
340
|
+
tableVariable: plural(camelCase(base))
|
|
296
341
|
});
|
|
297
342
|
}
|
|
298
343
|
|
|
299
344
|
/** Generate a seeder file for a module model. */
|
|
300
345
|
async function namedSeederTemplate(moduleName, modelName, className) {
|
|
346
|
+
const base = normalizeName(modelName);
|
|
301
347
|
return await stub(STUBS.seeder.named, {
|
|
302
348
|
module: moduleName,
|
|
303
|
-
|
|
349
|
+
// Import path: the model file is named after the model, in kebab-case.
|
|
350
|
+
name: base,
|
|
304
351
|
controller: className,
|
|
305
352
|
ClassName: pascal(className),
|
|
306
|
-
tableName:
|
|
307
|
-
tableVariable:
|
|
353
|
+
tableName: plural(snakeCase(base)),
|
|
354
|
+
tableVariable: plural(camelCase(base))
|
|
308
355
|
});
|
|
309
356
|
}
|
|
310
357
|
|
|
@@ -714,7 +761,16 @@ export async function makeModel(rawModule, rawNameOrFlag, extraFlags = []) {
|
|
|
714
761
|
await writeFileSafe(modelPath, await namedModelTemplate(moduleName, name, dialect), flags, "Model file");
|
|
715
762
|
}
|
|
716
763
|
|
|
717
|
-
/**
|
|
764
|
+
/**
|
|
765
|
+
* Generate a seeder file.
|
|
766
|
+
*
|
|
767
|
+
* A seeder does not have to own the table it writes to, and does not have to
|
|
768
|
+
* write to only one table, so a missing model is not fatal. When the model is
|
|
769
|
+
* there we import it and pre-wire the insert; when it is not we emit the
|
|
770
|
+
* standalone shape and let the developer choose what to write. That is the
|
|
771
|
+
* same trade the controller makes when there is no service, and the service
|
|
772
|
+
* makes when there is no model. Seeder was the only generator that refused.
|
|
773
|
+
*/
|
|
718
774
|
export async function makeSeeder(rawModule, rawNameOrFlag, extraFlags = []) {
|
|
719
775
|
const { moduleName, leaf } = resolveModuleRef(rawModule, rawNameOrFlag, extraFlags);
|
|
720
776
|
const flags = [];
|
|
@@ -725,36 +781,58 @@ export async function makeSeeder(rawModule, rawNameOrFlag, extraFlags = []) {
|
|
|
725
781
|
}
|
|
726
782
|
flags.push(...extraFlags);
|
|
727
783
|
const root = await assertModuleExists(moduleName);
|
|
728
|
-
let modelName = name;
|
|
729
|
-
let modelPath = path.join(root, "database", "models", `${modelName}.ts`);
|
|
730
|
-
|
|
731
|
-
try {
|
|
732
|
-
await fs.access(modelPath);
|
|
733
|
-
} catch {
|
|
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;
|
|
738
|
-
modelPath = path.join(root, "database", "models", `${modelName}.ts`);
|
|
739
784
|
|
|
740
|
-
|
|
741
|
-
|
|
785
|
+
// The fallback probes the leaf, not moduleName: for a panel module moduleName
|
|
786
|
+
// is "admin/testme", so using it would look for database/models/admin/testme.ts
|
|
787
|
+
// and never match.
|
|
788
|
+
const modelName = await existingModelName(root, name, leaf);
|
|
789
|
+
let seeder;
|
|
790
|
+
if (modelName) {
|
|
791
|
+
seeder = await namedSeederTemplate(moduleName, modelName, name);
|
|
792
|
+
if (modelName !== name) {
|
|
742
793
|
console.log(`Model '${name}' not found, using module model '${modelName}' for seeder '${name}'.`);
|
|
743
|
-
} catch {
|
|
744
|
-
const panel = panelOf(moduleName);
|
|
745
|
-
const suggest = panel ? `${leaf} ${name} --path=${panel}` : `${leaf} ${name}`;
|
|
746
|
-
throw new Error(
|
|
747
|
-
`Model not found for seeder: src/modules/${moduleName}/database/models/${name}.ts. Create it first with: bun maker module:make-model ${suggest}`
|
|
748
|
-
);
|
|
749
794
|
}
|
|
795
|
+
} else {
|
|
796
|
+
seeder = await stub(STUBS.seeder.standalone, {
|
|
797
|
+
module: moduleName,
|
|
798
|
+
name,
|
|
799
|
+
ClassName: pascal(name),
|
|
800
|
+
// The stub shows this as the conventional table name, so derive it the
|
|
801
|
+
// same way the bound stub does - it is what a model of this name exports.
|
|
802
|
+
tableVariable: plural(camelCase(name))
|
|
803
|
+
});
|
|
804
|
+
const available = await listModels(root);
|
|
805
|
+
console.log(`No model '${name}' in module ${moduleName}, so this is a standalone seeder: it imports no model and writes nothing until you fill it in.`);
|
|
806
|
+
console.log(
|
|
807
|
+
available.length
|
|
808
|
+
? ` Models in this module: ${available.join(", ")}. Run module:make-seeder ${moduleName} <one of these> for a seeder with the model already imported.`
|
|
809
|
+
: ` This module has no models yet. Create one with: bun maker module:make-model ${moduleName} ${name}`
|
|
810
|
+
);
|
|
750
811
|
}
|
|
751
812
|
|
|
752
|
-
const seeder = await namedSeederTemplate(moduleName, modelName, name);
|
|
753
813
|
await writeFileSafe(path.join(root, `database/seeders/${name}.ts`), seeder, flags, "Seeder file");
|
|
754
814
|
|
|
755
815
|
console.log(`Seeder ready: ${moduleName}/${name}`);
|
|
756
816
|
}
|
|
757
817
|
|
|
818
|
+
/** The model a seeder binds to: the requested one, else the module's own. Null when neither exists. */
|
|
819
|
+
async function existingModelName(root, name, leaf) {
|
|
820
|
+
for (const candidate of [name, leaf]) {
|
|
821
|
+
if (await pathExists(path.join(root, "database", "models", `${candidate}.ts`))) return candidate;
|
|
822
|
+
}
|
|
823
|
+
return null;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
/** Model file names in a module, without the extension. Empty when there are none. */
|
|
827
|
+
async function listModels(root) {
|
|
828
|
+
try {
|
|
829
|
+
const entries = await fs.readdir(path.join(root, "database", "models"));
|
|
830
|
+
return entries.filter((f) => f.endsWith(".ts")).map((f) => f.slice(0, -3));
|
|
831
|
+
} catch {
|
|
832
|
+
return [];
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
758
836
|
/** Generate a job file for an existing module. */
|
|
759
837
|
export async function makeJob(rawModule, rawNameOrFlag, extraFlags = []) {
|
|
760
838
|
const { moduleName, leaf } = resolveModuleRef(rawModule, rawNameOrFlag, extraFlags);
|
|
@@ -498,24 +498,42 @@ export async function runVite(configPath) {
|
|
|
498
498
|
await runCommand(localBin("vite"), ["--config", configPath]);
|
|
499
499
|
}
|
|
500
500
|
|
|
501
|
-
/**
|
|
501
|
+
/**
|
|
502
|
+
* Strip the argument separator npm inserts.
|
|
503
|
+
*
|
|
504
|
+
* `npm run maker test -- posts` reaches us as `test posts`, but
|
|
505
|
+
* `npm run maker test -- -- posts` reaches us as `test -- posts`, because npm
|
|
506
|
+
* eats one separator and forwards the other. Vitest reads a bare `--` as a
|
|
507
|
+
* positional filter that matches no test file, which silently runs the whole
|
|
508
|
+
* suite instead of the one you asked for - so the command appears to work while
|
|
509
|
+
* doing something else. Dropping a leading `--` makes both spellings behave the
|
|
510
|
+
* same, and makes the passthrough behave identically across package managers.
|
|
511
|
+
*/
|
|
512
|
+
function vitestArgs(rawArgs) {
|
|
513
|
+
return rawArgs[0] === "--" ? rawArgs.slice(1) : rawArgs;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/** Run Vitest tests once (backend). */
|
|
502
517
|
export async function runTest(rawArgs = []) {
|
|
503
|
-
|
|
518
|
+
// "run" is required. Bare `vitest` is watch mode, which never exits - so
|
|
519
|
+
// without this, `maker test` hung in any interactive terminal while CI and
|
|
520
|
+
// piped runs exited normally, and the two looked identical from a log.
|
|
521
|
+
await runCommand(localBin("vitest"), ["run", ...vitestArgs(rawArgs)]);
|
|
504
522
|
}
|
|
505
523
|
|
|
506
524
|
/** Run Vitest in watch mode (backend). */
|
|
507
525
|
export async function runTestWatch(rawArgs = []) {
|
|
508
|
-
await runCommand(localBin("vitest"), rawArgs);
|
|
526
|
+
await runCommand(localBin("vitest"), vitestArgs(rawArgs));
|
|
509
527
|
}
|
|
510
528
|
|
|
511
|
-
/** Run Vitest with coverage (backend). */
|
|
529
|
+
/** Run Vitest once with coverage (backend). */
|
|
512
530
|
export async function runTestCoverage(rawArgs = []) {
|
|
513
|
-
await runCommand(localBin("vitest"), ["--coverage", ...rawArgs]);
|
|
531
|
+
await runCommand(localBin("vitest"), ["run", "--coverage", ...vitestArgs(rawArgs)]);
|
|
514
532
|
}
|
|
515
533
|
|
|
516
534
|
/** Run Vitest in UI mode (backend). */
|
|
517
535
|
export async function runTestUI(rawArgs = []) {
|
|
518
|
-
await runCommand(localBin("vitest"), ["--ui", ...rawArgs]);
|
|
536
|
+
await runCommand(localBin("vitest"), ["--ui", ...vitestArgs(rawArgs)]);
|
|
519
537
|
}
|
|
520
538
|
|
|
521
539
|
/** Clear Vite cache directories. */
|
|
@@ -1,104 +1,120 @@
|
|
|
1
|
-
import { clearViteCache, runRuntime, runTest, runTestCoverage, runTestUI, runTestWatch, runUi, runVite } from "./core.mjs";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
.
|
|
8
|
-
.
|
|
9
|
-
.option("--
|
|
10
|
-
.option("--
|
|
11
|
-
.option("--with
|
|
12
|
-
.option("--with-
|
|
13
|
-
.option("--with-
|
|
14
|
-
.
|
|
15
|
-
.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
.
|
|
19
|
-
.
|
|
20
|
-
.option("--
|
|
21
|
-
.option("--
|
|
22
|
-
.option("--
|
|
23
|
-
.
|
|
24
|
-
.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
.
|
|
28
|
-
.
|
|
29
|
-
.option("--
|
|
30
|
-
.option("--
|
|
31
|
-
.option("--
|
|
32
|
-
.
|
|
33
|
-
.
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
.
|
|
37
|
-
.
|
|
38
|
-
.
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
.
|
|
42
|
-
.
|
|
43
|
-
.option("--
|
|
44
|
-
.option("--
|
|
45
|
-
.
|
|
46
|
-
.
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
.
|
|
50
|
-
.
|
|
51
|
-
.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
.
|
|
55
|
-
.
|
|
56
|
-
.
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
.
|
|
60
|
-
.
|
|
61
|
-
.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
.
|
|
65
|
-
.
|
|
66
|
-
.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
.
|
|
70
|
-
.
|
|
71
|
-
.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
.
|
|
75
|
-
.
|
|
76
|
-
.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
});
|
|
88
|
-
program
|
|
89
|
-
.command("test
|
|
90
|
-
.description("Run Vitest backend tests
|
|
91
|
-
.allowUnknownOption(true)
|
|
92
|
-
.action(async () => {
|
|
93
|
-
const args = process.argv.slice(3);
|
|
94
|
-
await
|
|
95
|
-
});
|
|
96
|
-
program
|
|
97
|
-
.command("test:
|
|
98
|
-
.description("Run Vitest backend tests in
|
|
99
|
-
.allowUnknownOption(true)
|
|
100
|
-
.action(async () => {
|
|
101
|
-
const args = process.argv.slice(3);
|
|
102
|
-
await
|
|
103
|
-
});
|
|
104
|
-
|
|
1
|
+
import { clearViteCache, runRuntime, runTest, runTestCoverage, runTestUI, runTestWatch, runUi, runVite } from "./core.mjs";
|
|
2
|
+
import { initProjectCommands, PROJECT_COMMANDS_PATH } from "../utils/project-commands.mjs";
|
|
3
|
+
|
|
4
|
+
/** Register runtime commands (dev, serve, queue, schedule, UI tools) on the CLI program. */
|
|
5
|
+
export function registerRuntimeCommands(program, rawArgs) {
|
|
6
|
+
program
|
|
7
|
+
.command("dev")
|
|
8
|
+
.description("Start API + Vue 3 UI plus optional workers/tools")
|
|
9
|
+
.option("--view <tools>", "Comma-separated UI tools to launch (redis,maildev,studio)")
|
|
10
|
+
.option("--viewer <tools>", "Alias for --view (npm expands --view to --viewer)")
|
|
11
|
+
.option("--with <tools>", "Alias for --view")
|
|
12
|
+
.option("--with-redis-view", "Launch Redis Commander")
|
|
13
|
+
.option("--with-maildev", "Launch MailDev")
|
|
14
|
+
.option("--with-db-studio", "Launch Drizzle Studio")
|
|
15
|
+
.allowUnknownOption(true)
|
|
16
|
+
.action(async () => runRuntime("dev", rawArgs));
|
|
17
|
+
program
|
|
18
|
+
.command("serve")
|
|
19
|
+
.description("Start HTTP server (dist first if built, else src)")
|
|
20
|
+
.option("--prod", "Run production build")
|
|
21
|
+
.option("--runtime <name>", "Runtime: node or bun", "node")
|
|
22
|
+
.option("--watch", "Watch for file changes (src mode)")
|
|
23
|
+
.option("--src", "Force source mode even if dist exists")
|
|
24
|
+
.allowUnknownOption(true)
|
|
25
|
+
.action(async () => runRuntime("serve", rawArgs));
|
|
26
|
+
program
|
|
27
|
+
.command("queue:work")
|
|
28
|
+
.description("Start queue worker (dist first if built, else src)")
|
|
29
|
+
.option("--queue <name>", "Queue name", "default")
|
|
30
|
+
.option("--prod", "Run production build")
|
|
31
|
+
.option("--runtime <name>", "Runtime: node or bun", "node")
|
|
32
|
+
.option("--src", "Force source mode even if dist exists")
|
|
33
|
+
.allowUnknownOption(true)
|
|
34
|
+
.action(async () => runRuntime("queue:work", rawArgs));
|
|
35
|
+
program
|
|
36
|
+
.command("queue:clear")
|
|
37
|
+
.description("Clear all queue keys")
|
|
38
|
+
.allowUnknownOption(true)
|
|
39
|
+
.action(async () => runRuntime("queue:clear", rawArgs));
|
|
40
|
+
program
|
|
41
|
+
.command("schedule:work")
|
|
42
|
+
.description("Start scheduler worker (dist first if built, else src)")
|
|
43
|
+
.option("--prod", "Run production build")
|
|
44
|
+
.option("--runtime <name>", "Runtime: node or bun", "node")
|
|
45
|
+
.option("--src", "Force source mode even if dist exists")
|
|
46
|
+
.allowUnknownOption(true)
|
|
47
|
+
.action(async () => runRuntime("schedule:work", rawArgs));
|
|
48
|
+
program
|
|
49
|
+
.command("maildev:view")
|
|
50
|
+
.description("Start MailDev SMTP and web UI")
|
|
51
|
+
.allowUnknownOption(true)
|
|
52
|
+
.action(async () => runUi("maildev:view"));
|
|
53
|
+
program
|
|
54
|
+
.command("redis:view")
|
|
55
|
+
.description("Start Redis Commander UI")
|
|
56
|
+
.allowUnknownOption(true)
|
|
57
|
+
.action(async () => runUi("redis:view"));
|
|
58
|
+
program
|
|
59
|
+
.command("ui:dev")
|
|
60
|
+
.description("Start Vue 3 UI from src/resources")
|
|
61
|
+
.allowUnknownOption(true)
|
|
62
|
+
.action(async () => runVite("src/resources/vite.config.ts"));
|
|
63
|
+
program
|
|
64
|
+
.command("admin:dev")
|
|
65
|
+
.description("Start admin UI dev server")
|
|
66
|
+
.allowUnknownOption(true)
|
|
67
|
+
.action(async () => runVite("src/resources/vite.config.ts"));
|
|
68
|
+
program
|
|
69
|
+
.command("vite:cache:clear")
|
|
70
|
+
.description("Clear Vite cache folders (cross-platform)")
|
|
71
|
+
.allowUnknownOption(true)
|
|
72
|
+
.action(async () => clearViteCache());
|
|
73
|
+
program
|
|
74
|
+
.command("maker:init")
|
|
75
|
+
.description(`Create ${PROJECT_COMMANDS_PATH} for your own commands`)
|
|
76
|
+
.option("--force", "Overwrite an existing file")
|
|
77
|
+
.allowUnknownOption(true)
|
|
78
|
+
.action(async (options) => {
|
|
79
|
+
const { created, overwritten } = await initProjectCommands(process.cwd(), { force: Boolean(options?.force) });
|
|
80
|
+
// created = written into a new file; overwritten = replaced under --force.
|
|
81
|
+
// Neither means the file was already there and was left alone.
|
|
82
|
+
console.log(
|
|
83
|
+
created || overwritten
|
|
84
|
+
? `Add your commands to ${PROJECT_COMMANDS_PATH}, then run: maker --help`
|
|
85
|
+
: `${PROJECT_COMMANDS_PATH} already exists and was left alone. Edit it to add commands, or re-run with --force to replace it.`
|
|
86
|
+
);
|
|
87
|
+
});
|
|
88
|
+
program
|
|
89
|
+
.command("test")
|
|
90
|
+
.description("Run Vitest backend tests")
|
|
91
|
+
.allowUnknownOption(true)
|
|
92
|
+
.action(async () => {
|
|
93
|
+
const args = process.argv.slice(3);
|
|
94
|
+
await runTest(args);
|
|
95
|
+
});
|
|
96
|
+
program
|
|
97
|
+
.command("test:watch")
|
|
98
|
+
.description("Run Vitest backend tests in watch mode")
|
|
99
|
+
.allowUnknownOption(true)
|
|
100
|
+
.action(async () => {
|
|
101
|
+
const args = process.argv.slice(3);
|
|
102
|
+
await runTestWatch(args);
|
|
103
|
+
});
|
|
104
|
+
program
|
|
105
|
+
.command("test:coverage")
|
|
106
|
+
.description("Run Vitest backend tests with code coverage")
|
|
107
|
+
.allowUnknownOption(true)
|
|
108
|
+
.action(async () => {
|
|
109
|
+
const args = process.argv.slice(3);
|
|
110
|
+
await runTestCoverage(args);
|
|
111
|
+
});
|
|
112
|
+
program
|
|
113
|
+
.command("test:ui")
|
|
114
|
+
.description("Run Vitest backend tests in UI mode")
|
|
115
|
+
.allowUnknownOption(true)
|
|
116
|
+
.action(async () => {
|
|
117
|
+
const args = process.argv.slice(3);
|
|
118
|
+
await runTestUI(args);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Handler } from "hono";
|
|
2
2
|
import { HttpStatusCodes } from "@/framework/facade.js";
|
|
3
|
-
import { {{
|
|
3
|
+
import { {{ServiceName}} } from "@/modules/{{module}}/services/{{controller}}.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Why: Returns every {{controller}} row for list screens.
|
|
@@ -8,7 +8,7 @@ import { {{controller}}Service } from "@/modules/{{module}}/services/{{controlle
|
|
|
8
8
|
* Where: GET /{{controller}} route.
|
|
9
9
|
*/
|
|
10
10
|
export const index: Handler = async (c: any) => {
|
|
11
|
-
const data = await {{
|
|
11
|
+
const data = await {{ServiceName}}.list();
|
|
12
12
|
|
|
13
13
|
return c.json({ message: "{{controller}} list fetched successfully", data });
|
|
14
14
|
};
|
|
@@ -20,7 +20,7 @@ export const index: Handler = async (c: any) => {
|
|
|
20
20
|
*/
|
|
21
21
|
export const show: Handler = async (c: any) => {
|
|
22
22
|
const { id } = c.req.valid("param");
|
|
23
|
-
const data = await {{
|
|
23
|
+
const data = await {{ServiceName}}.findOne(id);
|
|
24
24
|
|
|
25
25
|
if (!data) return c.json({ message: "{{controller}} not found" }, HttpStatusCodes.NOT_FOUND);
|
|
26
26
|
|
|
@@ -34,7 +34,7 @@ export const show: Handler = async (c: any) => {
|
|
|
34
34
|
*/
|
|
35
35
|
export const store: Handler = async (c: any) => {
|
|
36
36
|
const body = c.req.valid("json");
|
|
37
|
-
const data = await {{
|
|
37
|
+
const data = await {{ServiceName}}.create(body);
|
|
38
38
|
|
|
39
39
|
return c.json({ message: "{{controller}} created successfully", data }, HttpStatusCodes.CREATED);
|
|
40
40
|
};
|
|
@@ -47,7 +47,7 @@ export const store: Handler = async (c: any) => {
|
|
|
47
47
|
export const update: Handler = async (c: any) => {
|
|
48
48
|
const { id } = c.req.valid("param");
|
|
49
49
|
const body = c.req.valid("json");
|
|
50
|
-
const data = await {{
|
|
50
|
+
const data = await {{ServiceName}}.update(id, body);
|
|
51
51
|
|
|
52
52
|
if (!data) return c.json({ message: "{{controller}} not found" }, HttpStatusCodes.NOT_FOUND);
|
|
53
53
|
|
|
@@ -61,7 +61,7 @@ export const update: Handler = async (c: any) => {
|
|
|
61
61
|
*/
|
|
62
62
|
export const destroy: Handler = async (c: any) => {
|
|
63
63
|
const { id } = c.req.valid("param");
|
|
64
|
-
const data = await {{
|
|
64
|
+
const data = await {{ServiceName}}.remove(id);
|
|
65
65
|
|
|
66
66
|
if (!data) return c.json({ message: "{{controller}} not found" }, HttpStatusCodes.NOT_FOUND);
|
|
67
67
|
|
|
@@ -2,7 +2,7 @@ import type { Handler } from "hono";
|
|
|
2
2
|
import { HttpStatusCodes } from "@/framework/facade.js";
|
|
3
3
|
import { validate } from "@/framework/facade.js";
|
|
4
4
|
import { Create{{ClassName}}Schema, Update{{ClassName}}Schema, {{ClassName}}IdParamsSchema } from "@/modules/{{module}}/schemas/{{controller}}.js";
|
|
5
|
-
import { {{
|
|
5
|
+
import { {{ServiceName}} } from "@/modules/{{module}}/services/{{controller}}.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Why: Returns every {{controller}} row for list screens.
|
|
@@ -10,7 +10,7 @@ import { {{controller}}Service } from "@/modules/{{module}}/services/{{controlle
|
|
|
10
10
|
* Where: GET /{{controller}} route.
|
|
11
11
|
*/
|
|
12
12
|
export const index: Handler = async (c: any) => {
|
|
13
|
-
const data = await {{
|
|
13
|
+
const data = await {{ServiceName}}.list();
|
|
14
14
|
|
|
15
15
|
return c.json({ message: "{{controller}} list fetched successfully", data });
|
|
16
16
|
};
|
|
@@ -22,7 +22,7 @@ export const index: Handler = async (c: any) => {
|
|
|
22
22
|
*/
|
|
23
23
|
export const show: Handler = async (c: any) => {
|
|
24
24
|
const params = await validate({{ClassName}}IdParamsSchema, c.req.param());
|
|
25
|
-
const data = await {{
|
|
25
|
+
const data = await {{ServiceName}}.findOne(params.id);
|
|
26
26
|
|
|
27
27
|
if (!data) return c.json({ message: "{{controller}} not found" }, HttpStatusCodes.NOT_FOUND);
|
|
28
28
|
|
|
@@ -36,7 +36,7 @@ export const show: Handler = async (c: any) => {
|
|
|
36
36
|
*/
|
|
37
37
|
export const store: Handler = async (c: any) => {
|
|
38
38
|
const body = await validate(Create{{ClassName}}Schema, await c.req.json());
|
|
39
|
-
const data = await {{
|
|
39
|
+
const data = await {{ServiceName}}.create(body);
|
|
40
40
|
|
|
41
41
|
return c.json({ message: "{{controller}} created successfully", data }, HttpStatusCodes.CREATED);
|
|
42
42
|
};
|
|
@@ -49,7 +49,7 @@ export const store: Handler = async (c: any) => {
|
|
|
49
49
|
export const update: Handler = async (c: any) => {
|
|
50
50
|
const params = await validate({{ClassName}}IdParamsSchema, c.req.param());
|
|
51
51
|
const body = await validate(Update{{ClassName}}Schema, await c.req.json());
|
|
52
|
-
const data = await {{
|
|
52
|
+
const data = await {{ServiceName}}.update(params.id, body);
|
|
53
53
|
|
|
54
54
|
if (!data) return c.json({ message: "{{controller}} not found" }, HttpStatusCodes.NOT_FOUND);
|
|
55
55
|
|
|
@@ -63,7 +63,7 @@ export const update: Handler = async (c: any) => {
|
|
|
63
63
|
*/
|
|
64
64
|
export const destroy: Handler = async (c: any) => {
|
|
65
65
|
const params = await validate({{ClassName}}IdParamsSchema, c.req.param());
|
|
66
|
-
const data = await {{
|
|
66
|
+
const data = await {{ServiceName}}.remove(params.id);
|
|
67
67
|
|
|
68
68
|
if (!data) return c.json({ message: "{{controller}} not found" }, HttpStatusCodes.NOT_FOUND);
|
|
69
69
|
|