db-model-router 1.0.3 → 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/README.md +178 -14
- package/docs/SKILL.md +154 -109
- package/package.json +10 -2
- package/src/cli/commands/help.js +180 -0
- package/src/cli/commands/init.js +42 -14
- package/src/cli/commands/inspect.js +20 -3
- package/src/cli/generate-model.js +5 -4
- package/src/cli/generate-route.js +28 -22
- package/src/cli/init/dependencies.js +14 -5
- package/src/cli/init/generators.js +1073 -64
- package/src/cli/init/prompt.js +37 -5
- package/src/cli/init.js +148 -25
- package/src/cli/main.js +90 -10
- package/src/index.js +2 -0
- package/src/schema/schema-printer.js +1 -5
- package/src/schema/schema-to-meta.js +4 -0
- package/src/schema/schema-validator.js +3 -1
package/src/cli/init/prompt.js
CHANGED
|
@@ -5,6 +5,7 @@ const inquirer = require("inquirer");
|
|
|
5
5
|
const VALID_FRAMEWORKS = ["ultimate-express", "express"];
|
|
6
6
|
const VALID_DATABASES = [
|
|
7
7
|
"mysql",
|
|
8
|
+
"mariadb",
|
|
8
9
|
"postgres",
|
|
9
10
|
"sqlite3",
|
|
10
11
|
"mongodb",
|
|
@@ -56,12 +57,17 @@ function parseInitArgs(argv) {
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
// Boolean flags
|
|
59
|
-
for (const flag of ["rateLimiting", "helmet", "logger"]) {
|
|
60
|
+
for (const flag of ["rateLimiting", "helmet", "logger", "loki"]) {
|
|
60
61
|
if (args[flag] !== undefined) {
|
|
61
62
|
partial[flag] = parseBool(args[flag]);
|
|
62
63
|
}
|
|
63
64
|
}
|
|
64
65
|
|
|
66
|
+
// Output directory
|
|
67
|
+
if (args.output !== undefined && args.output !== "true") {
|
|
68
|
+
partial.output = args.output;
|
|
69
|
+
}
|
|
70
|
+
|
|
65
71
|
return partial;
|
|
66
72
|
}
|
|
67
73
|
|
|
@@ -114,12 +120,22 @@ async function promptUser(prefilledAnswers) {
|
|
|
114
120
|
});
|
|
115
121
|
}
|
|
116
122
|
|
|
123
|
+
if (prefilled.output === undefined) {
|
|
124
|
+
questions.push({
|
|
125
|
+
type: "input",
|
|
126
|
+
name: "output",
|
|
127
|
+
message:
|
|
128
|
+
"Output directory for backend source files (leave empty for root):",
|
|
129
|
+
default: "",
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
117
133
|
if (prefilled.rateLimiting === undefined) {
|
|
118
134
|
questions.push({
|
|
119
135
|
type: "confirm",
|
|
120
136
|
name: "rateLimiting",
|
|
121
137
|
message: "Enable rate limiting?",
|
|
122
|
-
default:
|
|
138
|
+
default: true,
|
|
123
139
|
});
|
|
124
140
|
}
|
|
125
141
|
|
|
@@ -128,7 +144,7 @@ async function promptUser(prefilledAnswers) {
|
|
|
128
144
|
type: "confirm",
|
|
129
145
|
name: "helmet",
|
|
130
146
|
message: "Enable Helmet security headers?",
|
|
131
|
-
default:
|
|
147
|
+
default: true,
|
|
132
148
|
});
|
|
133
149
|
}
|
|
134
150
|
|
|
@@ -136,8 +152,8 @@ async function promptUser(prefilledAnswers) {
|
|
|
136
152
|
questions.push({
|
|
137
153
|
type: "confirm",
|
|
138
154
|
name: "logger",
|
|
139
|
-
message: "Enable request/response logger?",
|
|
140
|
-
default:
|
|
155
|
+
message: "Enable request/response logger (Winston)?",
|
|
156
|
+
default: true,
|
|
141
157
|
});
|
|
142
158
|
}
|
|
143
159
|
|
|
@@ -147,6 +163,22 @@ async function promptUser(prefilledAnswers) {
|
|
|
147
163
|
}
|
|
148
164
|
|
|
149
165
|
const prompted = await inquirer.prompt(questions);
|
|
166
|
+
|
|
167
|
+
// Follow-up: if logger is enabled, ask about Loki
|
|
168
|
+
if (prompted.logger && prefilled.loki === undefined) {
|
|
169
|
+
const lokiAnswer = await inquirer.prompt([
|
|
170
|
+
{
|
|
171
|
+
type: "confirm",
|
|
172
|
+
name: "loki",
|
|
173
|
+
message: "Send logs to Grafana Loki?",
|
|
174
|
+
default: false,
|
|
175
|
+
},
|
|
176
|
+
]);
|
|
177
|
+
prompted.loki = lokiAnswer.loki;
|
|
178
|
+
} else if (!prompted.logger && prefilled.logger === undefined) {
|
|
179
|
+
prompted.loki = false;
|
|
180
|
+
}
|
|
181
|
+
|
|
150
182
|
return Object.assign({}, prefilled, prompted);
|
|
151
183
|
}
|
|
152
184
|
|
package/src/cli/init.js
CHANGED
|
@@ -7,14 +7,26 @@ const { execSync } = require("child_process");
|
|
|
7
7
|
|
|
8
8
|
const {
|
|
9
9
|
generateAppJs,
|
|
10
|
+
generateAppJsV2,
|
|
10
11
|
generateEnvFile,
|
|
11
12
|
generateEnvExample,
|
|
12
13
|
generateLoggerMiddleware,
|
|
13
|
-
generateMigrateScript,
|
|
14
|
-
generateAddMigrationScript,
|
|
15
14
|
generateInitialMigration,
|
|
16
15
|
generateSessionMigration,
|
|
17
16
|
generateGitignore,
|
|
17
|
+
generateDockerfile,
|
|
18
|
+
generateDockerignore,
|
|
19
|
+
generateGrafanaDatasources,
|
|
20
|
+
generateDockerCompose,
|
|
21
|
+
generateCloudBeaverDataSources,
|
|
22
|
+
generateSessionJs,
|
|
23
|
+
generateMigrateModule,
|
|
24
|
+
generateAddMigrationModule,
|
|
25
|
+
generateSecurityJs,
|
|
26
|
+
generateHealthRoute,
|
|
27
|
+
generateRouteIndexFile,
|
|
28
|
+
generateDbModule,
|
|
29
|
+
randomPassword,
|
|
18
30
|
} = require("./init/generators");
|
|
19
31
|
|
|
20
32
|
const { collectDependencies, getScripts } = require("./init/dependencies");
|
|
@@ -61,40 +73,139 @@ function safeWriteFile(filePath, content) {
|
|
|
61
73
|
* Creates directories and writes files. Skips files that already exist.
|
|
62
74
|
* Returns the list of generated filenames for the summary.
|
|
63
75
|
* @param {import('./init/types').InitAnswers} answers
|
|
76
|
+
* @param {string} [outputDir] - relative directory for source files (e.g. "backend")
|
|
64
77
|
* @returns {{ files: string[], migrationFiles: string[] }}
|
|
65
78
|
*/
|
|
66
|
-
function generateFiles(answers) {
|
|
79
|
+
function generateFiles(answers, outputDir) {
|
|
67
80
|
const files = [];
|
|
68
81
|
const migrationFiles = [];
|
|
69
82
|
|
|
83
|
+
// Resolve source directory (outputDir-relative or cwd)
|
|
84
|
+
const srcBase = outputDir || ".";
|
|
85
|
+
|
|
86
|
+
// Generate random secrets (shared between .env and docker-compose)
|
|
87
|
+
const secrets = {
|
|
88
|
+
dbPass: randomPassword(),
|
|
89
|
+
redisPass: answers.session === "redis" ? randomPassword() : "",
|
|
90
|
+
sessionSecret: randomPassword(32),
|
|
91
|
+
};
|
|
92
|
+
|
|
70
93
|
// Create directories
|
|
71
|
-
|
|
72
|
-
|
|
94
|
+
const dirs = [
|
|
95
|
+
path.join(srcBase, "middleware"),
|
|
96
|
+
path.join(srcBase, "migrations"),
|
|
97
|
+
path.join(srcBase, "commons"),
|
|
98
|
+
path.join(srcBase, "route"),
|
|
99
|
+
];
|
|
100
|
+
// SQLite3 needs a data/ folder for the database file
|
|
101
|
+
if (answers.database === "sqlite3") {
|
|
102
|
+
dirs.push("data");
|
|
73
103
|
}
|
|
74
|
-
|
|
75
|
-
fs.
|
|
104
|
+
for (const dir of dirs) {
|
|
105
|
+
if (!fs.existsSync(dir)) {
|
|
106
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
107
|
+
}
|
|
76
108
|
}
|
|
77
109
|
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
if (safeWriteFile(".
|
|
110
|
+
// Root-level files (always in cwd, not in outputDir)
|
|
111
|
+
// app.js uses the v2 generator that links commons/route modules
|
|
112
|
+
if (safeWriteFile("app.js", generateAppJsV2(answers, outputDir || "")))
|
|
113
|
+
files.push("app.js");
|
|
114
|
+
if (safeWriteFile(".env", generateEnvFile(answers, secrets)))
|
|
115
|
+
files.push(".env");
|
|
81
116
|
if (safeWriteFile(".env.example", generateEnvExample(answers)))
|
|
82
117
|
files.push(".env.example");
|
|
83
|
-
|
|
84
|
-
const loggerPath = path.join("middleware", "logger.js");
|
|
85
|
-
if (safeWriteFile(loggerPath, generateLoggerMiddleware(answers)))
|
|
86
|
-
files.push("middleware/logger.js");
|
|
87
|
-
|
|
88
|
-
if (safeWriteFile("migrate.js", generateMigrateScript(answers)))
|
|
89
|
-
files.push("migrate.js");
|
|
90
|
-
if (safeWriteFile("add_migration.js", generateAddMigrationScript(answers)))
|
|
91
|
-
files.push("add_migration.js");
|
|
92
118
|
if (safeWriteFile(".gitignore", generateGitignore()))
|
|
93
119
|
files.push(".gitignore");
|
|
94
120
|
|
|
95
|
-
//
|
|
121
|
+
// docker-compose.yml (if the database needs Docker)
|
|
122
|
+
const dockerCompose = generateDockerCompose(answers, secrets);
|
|
123
|
+
if (dockerCompose !== null) {
|
|
124
|
+
if (safeWriteFile("docker-compose.yml", dockerCompose))
|
|
125
|
+
files.push("docker-compose.yml");
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// CloudBeaver data-sources.json (auto-connect config)
|
|
129
|
+
const cbDataSources = generateCloudBeaverDataSources(answers, secrets);
|
|
130
|
+
if (cbDataSources !== null) {
|
|
131
|
+
const cbDir = ".cloudbeaver";
|
|
132
|
+
if (!fs.existsSync(cbDir)) {
|
|
133
|
+
fs.mkdirSync(cbDir, { recursive: true });
|
|
134
|
+
}
|
|
135
|
+
const cbPath = path.join(cbDir, "data-sources.json");
|
|
136
|
+
if (safeWriteFile(cbPath, cbDataSources))
|
|
137
|
+
files.push(".cloudbeaver/data-sources.json");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Grafana datasource provisioning (when loki is enabled)
|
|
141
|
+
if (answers.loki) {
|
|
142
|
+
const grafanaDir = ".grafana";
|
|
143
|
+
if (!fs.existsSync(grafanaDir)) {
|
|
144
|
+
fs.mkdirSync(grafanaDir, { recursive: true });
|
|
145
|
+
}
|
|
146
|
+
const grafanaPath = path.join(grafanaDir, "datasources.yml");
|
|
147
|
+
if (safeWriteFile(grafanaPath, generateGrafanaDatasources()))
|
|
148
|
+
files.push(".grafana/datasources.yml");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Dockerfile and .dockerignore
|
|
152
|
+
if (safeWriteFile("Dockerfile", generateDockerfile(answers, outputDir)))
|
|
153
|
+
files.push("Dockerfile");
|
|
154
|
+
if (safeWriteFile(".dockerignore", generateDockerignore()))
|
|
155
|
+
files.push(".dockerignore");
|
|
156
|
+
|
|
157
|
+
// Source files inside outputDir (or cwd if no outputDir)
|
|
158
|
+
const loggerPath = path.join(srcBase, "middleware", "logger.js");
|
|
159
|
+
if (safeWriteFile(loggerPath, generateLoggerMiddleware(answers)))
|
|
160
|
+
files.push(path.join(srcBase, "middleware/logger.js"));
|
|
161
|
+
|
|
162
|
+
// commons/session.js
|
|
163
|
+
const sessionPath = path.join(srcBase, "commons", "session.js");
|
|
164
|
+
if (safeWriteFile(sessionPath, generateSessionJs(answers)))
|
|
165
|
+
files.push(path.join(srcBase, "commons/session.js"));
|
|
166
|
+
|
|
167
|
+
// commons/migrate.js
|
|
168
|
+
const migratePath = path.join(srcBase, "commons", "migrate.js");
|
|
169
|
+
if (safeWriteFile(migratePath, generateMigrateModule(answers, outputDir)))
|
|
170
|
+
files.push(path.join(srcBase, "commons/migrate.js"));
|
|
171
|
+
|
|
172
|
+
// commons/add_migration.js
|
|
173
|
+
const addMigrationPath = path.join(srcBase, "commons", "add_migration.js");
|
|
174
|
+
if (
|
|
175
|
+
safeWriteFile(
|
|
176
|
+
addMigrationPath,
|
|
177
|
+
generateAddMigrationModule(answers, outputDir),
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
files.push(path.join(srcBase, "commons/add_migration.js"));
|
|
181
|
+
|
|
182
|
+
// commons/security.js
|
|
183
|
+
const securityPath = path.join(srcBase, "commons", "security.js");
|
|
184
|
+
if (safeWriteFile(securityPath, generateSecurityJs(answers)))
|
|
185
|
+
files.push(path.join(srcBase, "commons/security.js"));
|
|
186
|
+
|
|
187
|
+
// commons/db.js
|
|
188
|
+
const dbPath = path.join(srcBase, "commons", "db.js");
|
|
189
|
+
if (safeWriteFile(dbPath, generateDbModule(answers)))
|
|
190
|
+
files.push(path.join(srcBase, "commons/db.js"));
|
|
191
|
+
|
|
192
|
+
// route/health.js
|
|
193
|
+
const healthPath = path.join(srcBase, "route", "health.js");
|
|
194
|
+
if (safeWriteFile(healthPath, generateHealthRoute()))
|
|
195
|
+
files.push(path.join(srcBase, "route/health.js"));
|
|
196
|
+
|
|
197
|
+
// route/index.js
|
|
198
|
+
const routeIndexPath = path.join(srcBase, "route", "index.js");
|
|
199
|
+
if (safeWriteFile(routeIndexPath, generateRouteIndexFile()))
|
|
200
|
+
files.push(path.join(srcBase, "route/index.js"));
|
|
201
|
+
|
|
202
|
+
// Initial migration (inside outputDir/migrations)
|
|
96
203
|
const initialMigration = generateInitialMigration(answers);
|
|
97
|
-
const initialPath = path.join(
|
|
204
|
+
const initialPath = path.join(
|
|
205
|
+
srcBase,
|
|
206
|
+
"migrations",
|
|
207
|
+
initialMigration.filename,
|
|
208
|
+
);
|
|
98
209
|
if (safeWriteFile(initialPath, initialMigration.content)) {
|
|
99
210
|
migrationFiles.push(initialMigration.filename);
|
|
100
211
|
}
|
|
@@ -102,8 +213,12 @@ function generateFiles(answers) {
|
|
|
102
213
|
// Conditional session migration
|
|
103
214
|
const sessionMigration = generateSessionMigration(answers);
|
|
104
215
|
if (sessionMigration !== null) {
|
|
105
|
-
const
|
|
106
|
-
|
|
216
|
+
const sessionMigPath = path.join(
|
|
217
|
+
srcBase,
|
|
218
|
+
"migrations",
|
|
219
|
+
sessionMigration.filename,
|
|
220
|
+
);
|
|
221
|
+
if (safeWriteFile(sessionMigPath, sessionMigration.content)) {
|
|
107
222
|
migrationFiles.push(sessionMigration.filename);
|
|
108
223
|
}
|
|
109
224
|
}
|
|
@@ -114,8 +229,9 @@ function generateFiles(answers) {
|
|
|
114
229
|
/**
|
|
115
230
|
* Update package.json with scripts and dependencies from the answers.
|
|
116
231
|
* @param {import('./init/types').InitAnswers} answers
|
|
232
|
+
* @param {string} [outputDir] - relative output directory for source files
|
|
117
233
|
*/
|
|
118
|
-
function updatePackageJson(answers) {
|
|
234
|
+
function updatePackageJson(answers, outputDir) {
|
|
119
235
|
let raw;
|
|
120
236
|
try {
|
|
121
237
|
raw = fs.readFileSync("package.json", "utf8");
|
|
@@ -135,8 +251,9 @@ function updatePackageJson(answers) {
|
|
|
135
251
|
}
|
|
136
252
|
|
|
137
253
|
const { dependencies, devDependencies } = collectDependencies(answers);
|
|
138
|
-
const scripts = getScripts();
|
|
254
|
+
const scripts = getScripts(outputDir);
|
|
139
255
|
|
|
256
|
+
pkg.type = "module";
|
|
140
257
|
pkg.scripts = Object.assign({}, pkg.scripts || {}, scripts);
|
|
141
258
|
pkg.dependencies = Object.assign({}, pkg.dependencies || {}, dependencies);
|
|
142
259
|
pkg.devDependencies = Object.assign(
|
|
@@ -247,6 +364,9 @@ Options:
|
|
|
247
364
|
cockroachdb, oracle, redis, dynamodb
|
|
248
365
|
--db <name> Alias for --database
|
|
249
366
|
--session <type> Session store: memory, redis, database
|
|
367
|
+
--output <dir> Directory for backend source files (e.g. --output backend).
|
|
368
|
+
package.json stays in root; index.js, commons/, route/,
|
|
369
|
+
middleware/, and migrations/ go inside the output folder.
|
|
250
370
|
--rateLimiting Enable rate limiting (express-rate-limit)
|
|
251
371
|
--helmet Enable Helmet security headers
|
|
252
372
|
--logger Enable request/response logger (express-mung)
|
|
@@ -256,6 +376,9 @@ Examples:
|
|
|
256
376
|
# Fully non-interactive (LLM-friendly)
|
|
257
377
|
db-model-router-init --framework express --database postgres --session redis --rateLimiting --helmet --logger
|
|
258
378
|
|
|
379
|
+
# With output directory
|
|
380
|
+
db-model-router-init --framework express --database postgres --output backend --yes
|
|
381
|
+
|
|
259
382
|
# Partial — only prompts for missing values
|
|
260
383
|
db-model-router-init --database mysql --session memory
|
|
261
384
|
|
package/src/cli/main.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
3
|
|
|
4
4
|
const { parseFlags, OutputContext } = require("./flags");
|
|
@@ -8,6 +8,7 @@ const inspectCmd = require("./commands/inspect");
|
|
|
8
8
|
const generateCmd = require("./commands/generate");
|
|
9
9
|
const doctorCmd = require("./commands/doctor");
|
|
10
10
|
const diffCmd = require("./commands/diff");
|
|
11
|
+
const helpCmd = require("./commands/help");
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Map of subcommand names to their handler functions.
|
|
@@ -18,6 +19,7 @@ const COMMANDS = {
|
|
|
18
19
|
generate: generateCmd,
|
|
19
20
|
doctor: doctorCmd,
|
|
20
21
|
diff: diffCmd,
|
|
22
|
+
help: helpCmd,
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
/**
|
|
@@ -29,23 +31,74 @@ const COMMAND_DESCRIPTIONS = {
|
|
|
29
31
|
generate: "Generate models, routes, tests, and OpenAPI spec from a schema",
|
|
30
32
|
doctor: "Validate schema, check dependencies, and verify file sync",
|
|
31
33
|
diff: "Preview changes between current files and what the schema would produce",
|
|
34
|
+
help: "Show help for a command",
|
|
32
35
|
};
|
|
33
36
|
|
|
34
37
|
/**
|
|
35
|
-
*
|
|
38
|
+
* Per-command flag summaries shown in the general help overview.
|
|
39
|
+
*/
|
|
40
|
+
const COMMAND_FLAGS = {
|
|
41
|
+
init: [
|
|
42
|
+
["--from <path>", "Read config from a schema file"],
|
|
43
|
+
["--framework <name>", "express, ultimate-express"],
|
|
44
|
+
[
|
|
45
|
+
"--database <name>",
|
|
46
|
+
"mysql, mariadb, postgres, sqlite3, mongodb, mssql, cockroachdb, oracle, redis, dynamodb",
|
|
47
|
+
],
|
|
48
|
+
["--db <name>", "Alias for --database"],
|
|
49
|
+
["--session <type>", "memory, redis, database"],
|
|
50
|
+
["--output <dir>", "Directory for backend source files"],
|
|
51
|
+
["--rateLimiting", "Enable rate limiting (default: yes)"],
|
|
52
|
+
["--helmet", "Enable Helmet security headers (default: yes)"],
|
|
53
|
+
["--logger", "Enable Winston + Loki logger (default: yes)"],
|
|
54
|
+
],
|
|
55
|
+
inspect: [
|
|
56
|
+
["--type <adapter>", "Database adapter (required)"],
|
|
57
|
+
["--env <path>", "Path to .env file"],
|
|
58
|
+
["--out <path>", "Output file (default: dbmr.schema.json)"],
|
|
59
|
+
["--tables <list>", "Comma-separated table filter"],
|
|
60
|
+
],
|
|
61
|
+
generate: [
|
|
62
|
+
["--from <path>", "Schema file (default: dbmr.schema.json)"],
|
|
63
|
+
["--models", "Generate only model files"],
|
|
64
|
+
["--routes", "Generate only route files"],
|
|
65
|
+
["--openapi", "Generate only OpenAPI spec"],
|
|
66
|
+
["--tests", "Generate only test files"],
|
|
67
|
+
["--llm-docs", "Generate only LLM documentation"],
|
|
68
|
+
],
|
|
69
|
+
doctor: [["--from <path>", "Schema file (default: dbmr.schema.json)"]],
|
|
70
|
+
diff: [["--from <path>", "Schema file (default: dbmr.schema.json)"]],
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Print help message listing all available subcommands with their flags.
|
|
36
75
|
*/
|
|
37
76
|
function printHelp() {
|
|
38
77
|
console.log("Usage: db-model-router <command> [options]\n");
|
|
39
|
-
|
|
78
|
+
|
|
79
|
+
console.log("Commands:\n");
|
|
40
80
|
for (const [name, desc] of Object.entries(COMMAND_DESCRIPTIONS)) {
|
|
81
|
+
if (name === "help") {
|
|
82
|
+
console.log(` ${name.padEnd(12)} ${desc}`);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
41
85
|
console.log(` ${name.padEnd(12)} ${desc}`);
|
|
86
|
+
const flags = COMMAND_FLAGS[name];
|
|
87
|
+
if (flags) {
|
|
88
|
+
for (const [flag, info] of flags) {
|
|
89
|
+
console.log(` ${flag.padEnd(22)} ${info}`);
|
|
90
|
+
}
|
|
91
|
+
console.log("");
|
|
92
|
+
}
|
|
42
93
|
}
|
|
43
|
-
|
|
44
|
-
console.log("
|
|
45
|
-
console.log(" --
|
|
46
|
-
console.log(" --
|
|
47
|
-
console.log(" --
|
|
48
|
-
console.log(" --
|
|
94
|
+
|
|
95
|
+
console.log("Global flags (all commands):");
|
|
96
|
+
console.log(" --yes Accept all defaults without prompting");
|
|
97
|
+
console.log(" --json Output machine-readable JSON");
|
|
98
|
+
console.log(" --dry-run Preview actions without side effects");
|
|
99
|
+
console.log(" --no-install Skip npm install step");
|
|
100
|
+
console.log(" --help Show help for a command");
|
|
101
|
+
console.log('\nRun "db-model-router help <command>" for detailed usage.');
|
|
49
102
|
}
|
|
50
103
|
|
|
51
104
|
/**
|
|
@@ -65,11 +118,38 @@ function printUnknown(cmd) {
|
|
|
65
118
|
async function main(argv) {
|
|
66
119
|
const { subcommand, flags, args } = parseFlags(argv);
|
|
67
120
|
|
|
68
|
-
if (!subcommand
|
|
121
|
+
if (!subcommand) {
|
|
69
122
|
printHelp();
|
|
70
123
|
return;
|
|
71
124
|
}
|
|
72
125
|
|
|
126
|
+
// `help <command>` — extract the topic from the second positional arg
|
|
127
|
+
if (subcommand === "help") {
|
|
128
|
+
// Re-parse to grab the second positional word as the help topic.
|
|
129
|
+
// parseFlags puts the first positional into subcommand; the second
|
|
130
|
+
// positional ends up as a key in args (if it looks like a flag value)
|
|
131
|
+
// or is lost. So we grab it directly from argv.
|
|
132
|
+
const topic =
|
|
133
|
+
argv.find((a, i) => i > 0 && !a.startsWith("-") && argv[0] === "help") ||
|
|
134
|
+
argv.find(
|
|
135
|
+
(a, i) => i > 0 && !a.startsWith("-") && argv.indexOf("help") < i,
|
|
136
|
+
);
|
|
137
|
+
args._command = topic || null;
|
|
138
|
+
const ctx = new OutputContext(flags);
|
|
139
|
+
await helpCmd(args, flags, ctx, { printHelp });
|
|
140
|
+
ctx.flush();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// `<command> --help` — show detailed help for that command
|
|
145
|
+
if (flags.help) {
|
|
146
|
+
args._command = subcommand;
|
|
147
|
+
const ctx = new OutputContext(flags);
|
|
148
|
+
await helpCmd(args, flags, ctx, { printHelp });
|
|
149
|
+
ctx.flush();
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
73
153
|
if (!COMMANDS[subcommand]) {
|
|
74
154
|
printUnknown(subcommand);
|
|
75
155
|
process.exitCode = 1;
|
package/src/index.js
CHANGED
|
@@ -2,6 +2,7 @@ const model = require("./commons/model.js");
|
|
|
2
2
|
const route = require("./commons/route.js");
|
|
3
3
|
const routers = {
|
|
4
4
|
mysql: "./mysql/db.js",
|
|
5
|
+
mariadb: "./mysql/db.js",
|
|
5
6
|
postgresql: "./postgres/db.js",
|
|
6
7
|
postgres: "./postgres/db.js",
|
|
7
8
|
oracle: "./oracle/db.js",
|
|
@@ -29,6 +30,7 @@ function init(DB_TYPE) {
|
|
|
29
30
|
if (err.code === "MODULE_NOT_FOUND") {
|
|
30
31
|
const driverMap = {
|
|
31
32
|
mysql: "mysql2",
|
|
33
|
+
mariadb: "mysql2",
|
|
32
34
|
postgresql: "pg",
|
|
33
35
|
postgres: "pg",
|
|
34
36
|
oracle: "oracledb",
|
|
@@ -30,13 +30,9 @@ function printSchema(schema) {
|
|
|
30
30
|
const table = schema.tables[name];
|
|
31
31
|
const tableDef = {
|
|
32
32
|
columns: table.columns,
|
|
33
|
+
pk: table.pk || "id",
|
|
33
34
|
};
|
|
34
35
|
|
|
35
|
-
// Include pk if not the default "id"
|
|
36
|
-
if (table.pk && table.pk !== "id") {
|
|
37
|
-
tableDef.pk = table.pk;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
36
|
// Preserve unique if not the default [pk]
|
|
41
37
|
const defaultUnique = [table.pk || "id"];
|
|
42
38
|
const hasCustomUnique =
|
|
@@ -46,6 +46,10 @@ function schemaToModelMeta(schema) {
|
|
|
46
46
|
const structure = {};
|
|
47
47
|
for (const [colName, rule] of Object.entries(tableDef.columns)) {
|
|
48
48
|
if (!excludeSet.has(colName)) {
|
|
49
|
+
// Strip auto_increment and datetime columns from model structure
|
|
50
|
+
// (DB handles these automatically)
|
|
51
|
+
const baseType = rule.replace(/^required\|/, "");
|
|
52
|
+
if (baseType === "auto_increment") continue;
|
|
49
53
|
structure[colName] = rule;
|
|
50
54
|
}
|
|
51
55
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const VALID_ADAPTERS = new Set([
|
|
4
4
|
"mysql",
|
|
5
|
+
"mariadb",
|
|
5
6
|
"postgres",
|
|
6
7
|
"sqlite3",
|
|
7
8
|
"mongodb",
|
|
@@ -14,7 +15,8 @@ const VALID_ADAPTERS = new Set([
|
|
|
14
15
|
|
|
15
16
|
const VALID_FRAMEWORKS = new Set(["express", "ultimate-express"]);
|
|
16
17
|
|
|
17
|
-
const COLUMN_RULE_RE =
|
|
18
|
+
const COLUMN_RULE_RE =
|
|
19
|
+
/^(required\|)?(string|integer|numeric|boolean|object|datetime|auto_increment)$/;
|
|
18
20
|
|
|
19
21
|
class SchemaValidationError extends Error {
|
|
20
22
|
constructor(errors) {
|