@nocobase/cli 2.1.0-alpha.21 → 2.1.0-alpha.23
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/dist/commands/download.js +170 -37
- package/dist/commands/env/add.js +39 -12
- package/dist/commands/init.js +90 -47
- package/dist/commands/install.js +191 -57
- package/dist/commands/prompts-stages.js +6 -0
- package/dist/commands/prompts-test.js +6 -0
- package/dist/lib/api-client.js +49 -5
- package/dist/lib/cli-locale.js +115 -0
- package/dist/lib/env-auth.js +2 -2
- package/dist/lib/prompt-catalog.js +87 -58
- package/dist/lib/prompt-validators.js +9 -8
- package/dist/lib/prompt-web-ui.js +143 -74
- package/dist/lib/run-npm.js +10 -10
- package/dist/lib/runtime-generator.js +12 -1
- package/dist/locale/en-US.json +333 -0
- package/dist/locale/zh-CN.json +333 -0
- package/package.json +5 -3
package/dist/commands/init.js
CHANGED
|
@@ -14,6 +14,7 @@ import path from 'node:path';
|
|
|
14
14
|
import { stdin as stdinStream, stdout as stdoutStream } from 'node:process';
|
|
15
15
|
import { getEnv, upsertEnv } from "../lib/auth-store.js";
|
|
16
16
|
import { runPromptCatalog, } from "../lib/prompt-catalog.js";
|
|
17
|
+
import { applyCliLocale, localeText, translateCli } from "../lib/cli-locale.js";
|
|
17
18
|
import { runPromptCatalogWebUI, } from "../lib/prompt-web-ui.js";
|
|
18
19
|
import { validateApiBaseUrl, validateEnvKey } from "../lib/prompt-validators.js";
|
|
19
20
|
import { run } from "../lib/run-npm.js";
|
|
@@ -25,6 +26,7 @@ const DEFAULT_INIT_API_BASE_URL = 'http://localhost:13000/api';
|
|
|
25
26
|
const DEFAULT_INIT_APP_NAME = 'local';
|
|
26
27
|
const DOWNLOAD_OUTPUT_DIR_PROMPT = Download.prompts.outputDir;
|
|
27
28
|
const CONFIG_SCOPE = 'project';
|
|
29
|
+
const initText = (key, values) => localeText(`commands.init.${key}`, values);
|
|
28
30
|
function withExtraHidden(def, extraHidden) {
|
|
29
31
|
if (def.type === 'run') {
|
|
30
32
|
return def;
|
|
@@ -46,6 +48,13 @@ function downloadInNewInstallOnly(def) {
|
|
|
46
48
|
function argvHasToken(argv, tokens) {
|
|
47
49
|
return tokens.some((token) => argv.includes(token));
|
|
48
50
|
}
|
|
51
|
+
function resolveInitDownloadVersion(results) {
|
|
52
|
+
const preset = String(results.version ?? '').trim();
|
|
53
|
+
if (preset === 'other') {
|
|
54
|
+
return String(results.otherVersion ?? '').trim();
|
|
55
|
+
}
|
|
56
|
+
return preset;
|
|
57
|
+
}
|
|
49
58
|
function shouldAllowExistingInitEnv() {
|
|
50
59
|
return argvHasToken(process.argv.slice(2), ['--force', '-f']);
|
|
51
60
|
}
|
|
@@ -63,7 +72,7 @@ async function validateInitAppName(value) {
|
|
|
63
72
|
if (shouldAllowExistingInitEnv()) {
|
|
64
73
|
return undefined;
|
|
65
74
|
}
|
|
66
|
-
return
|
|
75
|
+
return translateCli('commands.init.validation.envExists', { envName });
|
|
67
76
|
}
|
|
68
77
|
return undefined;
|
|
69
78
|
}
|
|
@@ -71,20 +80,31 @@ function highlightInitValidationMessage(message) {
|
|
|
71
80
|
return message.replace(/Env "([^"]+)"/, (_match, envName) => `Env ${pc.cyan(pc.bold(`"${envName}"`))}`);
|
|
72
81
|
}
|
|
73
82
|
function formatInitValidationMessage(message) {
|
|
74
|
-
if (/"appName" is required/.test(message)) {
|
|
75
|
-
return [
|
|
76
|
-
'App name is required when prompts are skipped.',
|
|
77
|
-
'The app name is also the CLI env name. Use `nb init --yes --env <envName>` to continue.',
|
|
78
|
-
].join('\n');
|
|
79
|
-
}
|
|
80
83
|
return message;
|
|
81
84
|
}
|
|
82
85
|
function formatResumeEnvRequiredMessage() {
|
|
83
86
|
return [
|
|
84
|
-
'
|
|
85
|
-
'
|
|
87
|
+
translateCli('commands.init.messages.resumeEnvRequired'),
|
|
88
|
+
translateCli('commands.init.messages.resumeEnvHelp'),
|
|
89
|
+
].join('\n');
|
|
90
|
+
}
|
|
91
|
+
function formatSkippedAppNameRequiredMessage() {
|
|
92
|
+
return [
|
|
93
|
+
translateCli('commands.init.messages.appNameRequiredWhenSkipped'),
|
|
94
|
+
translateCli('commands.init.messages.appNameEnvHelp'),
|
|
86
95
|
].join('\n');
|
|
87
96
|
}
|
|
97
|
+
function initTitle() {
|
|
98
|
+
return translateCli('commands.init.messages.title');
|
|
99
|
+
}
|
|
100
|
+
function logInitUiReady(command, url) {
|
|
101
|
+
p.log.step(translateCli('commands.init.messages.uiReady'));
|
|
102
|
+
p.log.info(translateCli('commands.init.messages.uiReadyHelp'));
|
|
103
|
+
command.log(`URL: ${url}`);
|
|
104
|
+
}
|
|
105
|
+
function logInitUiBrowserOpenFallback() {
|
|
106
|
+
p.log.warn(translateCli('commands.init.messages.uiOpenBrowserFallback'));
|
|
107
|
+
}
|
|
88
108
|
export default class Init extends Command {
|
|
89
109
|
static summary = 'Set up NocoBase so coding agents can connect and work with it';
|
|
90
110
|
static description = `Set up NocoBase for coding agents in the current workspace.
|
|
@@ -119,23 +139,23 @@ Prompt modes:
|
|
|
119
139
|
static prompts = {
|
|
120
140
|
appName: {
|
|
121
141
|
type: 'text',
|
|
122
|
-
message: '
|
|
123
|
-
placeholder:
|
|
142
|
+
message: initText('prompts.appName.message'),
|
|
143
|
+
placeholder: initText('prompts.appName.placeholder'),
|
|
124
144
|
required: true,
|
|
125
145
|
validate: validateInitAppName,
|
|
126
146
|
},
|
|
127
147
|
hasNocobase: {
|
|
128
148
|
type: 'select',
|
|
129
149
|
variant: 'radio',
|
|
130
|
-
message: '
|
|
150
|
+
message: initText('prompts.hasNocobase.message'),
|
|
131
151
|
options: [
|
|
132
152
|
{
|
|
133
153
|
value: 'no',
|
|
134
|
-
label:
|
|
154
|
+
label: initText('prompts.hasNocobase.noLabel'),
|
|
135
155
|
},
|
|
136
156
|
{
|
|
137
157
|
value: 'yes',
|
|
138
|
-
label: '
|
|
158
|
+
label: initText('prompts.hasNocobase.yesLabel'),
|
|
139
159
|
},
|
|
140
160
|
],
|
|
141
161
|
initialValue: 'no',
|
|
@@ -144,14 +164,14 @@ Prompt modes:
|
|
|
144
164
|
},
|
|
145
165
|
installSkills: {
|
|
146
166
|
type: 'boolean',
|
|
147
|
-
message: '
|
|
167
|
+
message: initText('prompts.installSkills.message'),
|
|
148
168
|
initialValue: true,
|
|
149
169
|
yesInitialValue: true,
|
|
150
170
|
},
|
|
151
171
|
apiBaseUrl: existingAppOnly({
|
|
152
172
|
type: 'text',
|
|
153
|
-
message: '
|
|
154
|
-
placeholder:
|
|
173
|
+
message: initText('prompts.apiBaseUrl.message'),
|
|
174
|
+
placeholder: initText('prompts.apiBaseUrl.placeholder'),
|
|
155
175
|
required: true,
|
|
156
176
|
validate: validateApiBaseUrl,
|
|
157
177
|
}),
|
|
@@ -164,6 +184,7 @@ Prompt modes:
|
|
|
164
184
|
fetchSource: newInstallOnly(Install.appPrompts.fetchSource),
|
|
165
185
|
source: downloadInNewInstallOnly(Download.prompts.source),
|
|
166
186
|
version: downloadInNewInstallOnly(Download.prompts.version),
|
|
187
|
+
otherVersion: downloadInNewInstallOnly(Download.prompts.otherVersion),
|
|
167
188
|
dockerRegistry: downloadInNewInstallOnly(Download.prompts.dockerRegistry),
|
|
168
189
|
dockerPlatform: downloadInNewInstallOnly(Download.prompts.dockerPlatform),
|
|
169
190
|
dockerSave: downloadInNewInstallOnly(Download.prompts.dockerSave),
|
|
@@ -194,8 +215,9 @@ Prompt modes:
|
|
|
194
215
|
devDependencies: downloadInNewInstallOnly(Download.prompts.devDependencies),
|
|
195
216
|
build: downloadInNewInstallOnly(Download.prompts.build),
|
|
196
217
|
buildDts: downloadInNewInstallOnly(Download.prompts.buildDts),
|
|
197
|
-
builtinDb: newInstallOnly(Install.dbPrompts.builtinDb),
|
|
198
218
|
dbDialect: newInstallOnly(Install.dbPrompts.dbDialect),
|
|
219
|
+
builtinDb: newInstallOnly(Install.dbPrompts.builtinDb),
|
|
220
|
+
builtinDbImage: newInstallOnly(Install.dbPrompts.builtinDbImage),
|
|
199
221
|
dbHost: newInstallOnly(Install.dbPrompts.dbHost),
|
|
200
222
|
dbPort: newInstallOnly(Install.dbPrompts.dbPort),
|
|
201
223
|
dbDatabase: newInstallOnly(Install.dbPrompts.dbDatabase),
|
|
@@ -209,12 +231,12 @@ Prompt modes:
|
|
|
209
231
|
static flags = {
|
|
210
232
|
yes: Flags.boolean({
|
|
211
233
|
char: 'y',
|
|
212
|
-
description: 'Skip prompts and create a new local NocoBase app. Requires an
|
|
234
|
+
description: 'Skip prompts and create a new local NocoBase app. Requires an env name.',
|
|
213
235
|
default: false,
|
|
214
236
|
}),
|
|
215
237
|
env: Flags.string({
|
|
216
238
|
char: 'e',
|
|
217
|
-
description: '
|
|
239
|
+
description: 'Env name for this setup. Required with --yes and --resume',
|
|
218
240
|
}),
|
|
219
241
|
'install-skills': Flags.boolean({
|
|
220
242
|
description: 'Install NocoBase AI coding skills (`nocobase/skills`) for this workspace',
|
|
@@ -224,6 +246,10 @@ Prompt modes:
|
|
|
224
246
|
description: 'Open the guided setup flow in a local browser form (not valid with --yes)',
|
|
225
247
|
default: false,
|
|
226
248
|
}),
|
|
249
|
+
verbose: Flags.boolean({
|
|
250
|
+
description: 'Show detailed command output',
|
|
251
|
+
default: false,
|
|
252
|
+
}),
|
|
227
253
|
'ui-host': Flags.string({
|
|
228
254
|
description: 'Host for the local --ui setup server (default: 127.0.0.1)',
|
|
229
255
|
}),
|
|
@@ -236,6 +262,7 @@ Prompt modes:
|
|
|
236
262
|
};
|
|
237
263
|
async run() {
|
|
238
264
|
const parsedResult = await this.parse(Init);
|
|
265
|
+
applyCliLocale(parsedResult.flags.locale);
|
|
239
266
|
const flags = parsedResult.flags;
|
|
240
267
|
const normalizedFlags = { ...flags };
|
|
241
268
|
if (normalizedFlags.ui && normalizedFlags.yes) {
|
|
@@ -254,7 +281,7 @@ Prompt modes:
|
|
|
254
281
|
p.log.error(formatResumeEnvRequiredMessage());
|
|
255
282
|
this.exit(1);
|
|
256
283
|
}
|
|
257
|
-
p.intro(
|
|
284
|
+
p.intro(initTitle());
|
|
258
285
|
if (Boolean(normalizedFlags['install-skills'])) {
|
|
259
286
|
try {
|
|
260
287
|
p.log.step('Installing NocoBase agent skills (npx -y skills add nocobase/skills)');
|
|
@@ -282,17 +309,17 @@ Prompt modes:
|
|
|
282
309
|
const useBrowserUi = Boolean(normalizedFlags.ui);
|
|
283
310
|
let presetValues = this.buildPresetValuesFromFlags(normalizedFlags);
|
|
284
311
|
if (normalizedFlags.yes && !String(presetValues.appName ?? '').trim()) {
|
|
285
|
-
const formatted =
|
|
312
|
+
const formatted = formatSkippedAppNameRequiredMessage();
|
|
286
313
|
p.log.error(highlightInitValidationMessage(formatted));
|
|
287
314
|
this.exit(1);
|
|
288
315
|
}
|
|
289
316
|
const appName = String(presetValues.appName ?? '').trim();
|
|
290
317
|
if (useBrowserUi) {
|
|
291
|
-
p.intro(
|
|
292
|
-
p.log.info('
|
|
318
|
+
p.intro(initTitle());
|
|
319
|
+
p.log.info(translateCli('commands.init.messages.uiOpening'));
|
|
293
320
|
}
|
|
294
321
|
else {
|
|
295
|
-
p.intro(
|
|
322
|
+
p.intro(initTitle());
|
|
296
323
|
if (normalizedFlags.yes) {
|
|
297
324
|
p.log.info(`Prompts skipped (--yes). NocoBase will be installed for env "${appName}" using the provided flags and safe defaults.`);
|
|
298
325
|
}
|
|
@@ -310,14 +337,14 @@ Prompt modes:
|
|
|
310
337
|
},
|
|
311
338
|
host: normalizedFlags['ui-host']?.trim() || '127.0.0.1',
|
|
312
339
|
port: normalizedFlags['ui-port'] ?? 0,
|
|
313
|
-
pageTitle: '
|
|
314
|
-
documentHeading: '
|
|
315
|
-
documentHint: '
|
|
316
|
-
onServerStart: ({
|
|
317
|
-
this
|
|
340
|
+
pageTitle: initText('webUi.pageTitle'),
|
|
341
|
+
documentHeading: initText('webUi.documentHeading'),
|
|
342
|
+
documentHint: initText('webUi.documentHint'),
|
|
343
|
+
onServerStart: ({ url }) => {
|
|
344
|
+
logInitUiReady(this, url);
|
|
318
345
|
},
|
|
319
|
-
onOpenBrowserError: (
|
|
320
|
-
|
|
346
|
+
onOpenBrowserError: (_url, _err) => {
|
|
347
|
+
logInitUiBrowserOpenFallback();
|
|
321
348
|
},
|
|
322
349
|
});
|
|
323
350
|
}
|
|
@@ -390,6 +417,7 @@ Prompt modes:
|
|
|
390
417
|
'app-root-path': flags['app-root-path'] ?? '',
|
|
391
418
|
'storage-path': flags['storage-path'] ?? '',
|
|
392
419
|
},
|
|
420
|
+
warnOnPortFallback: false,
|
|
393
421
|
});
|
|
394
422
|
if (appInitialValues.appPort !== undefined) {
|
|
395
423
|
out.appPort = appInitialValues.appPort;
|
|
@@ -405,6 +433,7 @@ Prompt modes:
|
|
|
405
433
|
flags,
|
|
406
434
|
downloadResults: downloadSeed,
|
|
407
435
|
dbPreset: presetValues,
|
|
436
|
+
warnOnPortFallback: false,
|
|
408
437
|
});
|
|
409
438
|
for (const [key, value] of Object.entries(dbInitial)) {
|
|
410
439
|
if (!Object.prototype.hasOwnProperty.call(presetValues, key)) {
|
|
@@ -417,8 +446,8 @@ Prompt modes:
|
|
|
417
446
|
const c = Init.prompts;
|
|
418
447
|
return [
|
|
419
448
|
{
|
|
420
|
-
sectionTitle: '
|
|
421
|
-
sectionDescription: '
|
|
449
|
+
sectionTitle: initText('webUi.gettingStarted.title'),
|
|
450
|
+
sectionDescription: initText('webUi.gettingStarted.description'),
|
|
422
451
|
catalog: {
|
|
423
452
|
appName: c.appName,
|
|
424
453
|
hasNocobase: c.hasNocobase,
|
|
@@ -426,8 +455,8 @@ Prompt modes:
|
|
|
426
455
|
},
|
|
427
456
|
},
|
|
428
457
|
{
|
|
429
|
-
sectionTitle: '
|
|
430
|
-
sectionDescription: '
|
|
458
|
+
sectionTitle: initText('webUi.connectExistingApp.title'),
|
|
459
|
+
sectionDescription: initText('webUi.connectExistingApp.description'),
|
|
431
460
|
catalog: {
|
|
432
461
|
apiBaseUrl: c.apiBaseUrl,
|
|
433
462
|
authType: c.authType,
|
|
@@ -435,8 +464,8 @@ Prompt modes:
|
|
|
435
464
|
},
|
|
436
465
|
},
|
|
437
466
|
{
|
|
438
|
-
sectionTitle: '
|
|
439
|
-
sectionDescription: '
|
|
467
|
+
sectionTitle: initText('webUi.createNewApp.title'),
|
|
468
|
+
sectionDescription: initText('webUi.createNewApp.description'),
|
|
440
469
|
catalog: {
|
|
441
470
|
lang: c.lang,
|
|
442
471
|
appRootPath: c.appRootPath,
|
|
@@ -446,11 +475,12 @@ Prompt modes:
|
|
|
446
475
|
},
|
|
447
476
|
},
|
|
448
477
|
{
|
|
449
|
-
sectionTitle: '
|
|
450
|
-
sectionDescription: '
|
|
478
|
+
sectionTitle: initText('webUi.downloadAppFiles.title'),
|
|
479
|
+
sectionDescription: initText('webUi.downloadAppFiles.description'),
|
|
451
480
|
catalog: {
|
|
452
481
|
source: c.source,
|
|
453
482
|
version: c.version,
|
|
483
|
+
otherVersion: c.otherVersion,
|
|
454
484
|
dockerRegistry: c.dockerRegistry,
|
|
455
485
|
dockerPlatform: c.dockerPlatform,
|
|
456
486
|
dockerSave: c.dockerSave,
|
|
@@ -464,11 +494,12 @@ Prompt modes:
|
|
|
464
494
|
},
|
|
465
495
|
},
|
|
466
496
|
{
|
|
467
|
-
sectionTitle: '
|
|
468
|
-
sectionDescription: '
|
|
497
|
+
sectionTitle: initText('webUi.configureDatabase.title'),
|
|
498
|
+
sectionDescription: initText('webUi.configureDatabase.description'),
|
|
469
499
|
catalog: {
|
|
470
|
-
builtinDb: c.builtinDb,
|
|
471
500
|
dbDialect: c.dbDialect,
|
|
501
|
+
builtinDb: c.builtinDb,
|
|
502
|
+
builtinDbImage: c.builtinDbImage,
|
|
472
503
|
dbHost: c.dbHost,
|
|
473
504
|
dbPort: c.dbPort,
|
|
474
505
|
dbDatabase: c.dbDatabase,
|
|
@@ -477,8 +508,8 @@ Prompt modes:
|
|
|
477
508
|
},
|
|
478
509
|
},
|
|
479
510
|
{
|
|
480
|
-
sectionTitle: '
|
|
481
|
-
sectionDescription: '
|
|
511
|
+
sectionTitle: initText('webUi.createAdminAccount.title'),
|
|
512
|
+
sectionDescription: initText('webUi.createAdminAccount.description'),
|
|
482
513
|
catalog: {
|
|
483
514
|
rootUsername: c.rootUsername,
|
|
484
515
|
rootEmail: c.rootEmail,
|
|
@@ -521,6 +552,9 @@ Prompt modes:
|
|
|
521
552
|
if (flags['db-dialect'] !== undefined && String(flags['db-dialect']).trim() !== '') {
|
|
522
553
|
preset.dbDialect = String(flags['db-dialect']).trim();
|
|
523
554
|
}
|
|
555
|
+
if (flags['builtin-db-image'] !== undefined && String(flags['builtin-db-image']).trim() !== '') {
|
|
556
|
+
preset.builtinDbImage = String(flags['builtin-db-image']).trim();
|
|
557
|
+
}
|
|
524
558
|
if (flags['db-host'] !== undefined && String(flags['db-host']).trim() !== '') {
|
|
525
559
|
preset.dbHost = String(flags['db-host']).trim();
|
|
526
560
|
}
|
|
@@ -593,7 +627,7 @@ Prompt modes:
|
|
|
593
627
|
const envName = String(results.appName ?? DEFAULT_INIT_APP_NAME).trim() || DEFAULT_INIT_APP_NAME;
|
|
594
628
|
const appPort = String(results.appPort ?? '').trim();
|
|
595
629
|
const source = String(results.source ?? '').trim();
|
|
596
|
-
const version =
|
|
630
|
+
const version = resolveInitDownloadVersion(results);
|
|
597
631
|
const dockerRegistry = String(results.dockerRegistry ?? '').trim();
|
|
598
632
|
const dockerPlatform = String(results.dockerPlatform ?? '').trim();
|
|
599
633
|
const gitUrl = String(results.gitUrl ?? '').trim();
|
|
@@ -601,6 +635,7 @@ Prompt modes:
|
|
|
601
635
|
const appRootPath = String(results.appRootPath ?? '').trim();
|
|
602
636
|
const storagePath = String(results.storagePath ?? '').trim();
|
|
603
637
|
const dbDialect = String(results.dbDialect ?? '').trim();
|
|
638
|
+
const builtinDbImage = String(results.builtinDbImage ?? '').trim();
|
|
604
639
|
const dbHost = String(results.dbHost ?? '').trim();
|
|
605
640
|
const dbPort = String(results.dbPort ?? '').trim();
|
|
606
641
|
const dbDatabase = String(results.dbDatabase ?? '').trim();
|
|
@@ -622,6 +657,7 @@ Prompt modes:
|
|
|
622
657
|
...(results.buildDts !== undefined ? { buildDts: Boolean(results.buildDts) } : {}),
|
|
623
658
|
...(results.builtinDb !== undefined ? { builtinDb: Boolean(results.builtinDb) } : {}),
|
|
624
659
|
...(dbDialect ? { dbDialect } : {}),
|
|
660
|
+
...(builtinDbImage || results.builtinDb === false ? { builtinDbImage: builtinDbImage || undefined } : {}),
|
|
625
661
|
...(dbHost ? { dbHost } : {}),
|
|
626
662
|
...(dbPort ? { dbPort } : {}),
|
|
627
663
|
...(dbDatabase ? { dbDatabase } : {}),
|
|
@@ -652,6 +688,9 @@ Prompt modes:
|
|
|
652
688
|
if (options?.resume) {
|
|
653
689
|
argv.push('--resume');
|
|
654
690
|
}
|
|
691
|
+
if (Boolean(flags.verbose)) {
|
|
692
|
+
argv.push('--verbose');
|
|
693
|
+
}
|
|
655
694
|
const lang = String(results.lang ?? '').trim();
|
|
656
695
|
if (lang) {
|
|
657
696
|
argv.push('--lang', lang);
|
|
@@ -676,7 +715,7 @@ Prompt modes:
|
|
|
676
715
|
if (source) {
|
|
677
716
|
argv.push('--source', source);
|
|
678
717
|
}
|
|
679
|
-
const version =
|
|
718
|
+
const version = resolveInitDownloadVersion(results);
|
|
680
719
|
if (version) {
|
|
681
720
|
argv.push('--version', version);
|
|
682
721
|
}
|
|
@@ -727,6 +766,10 @@ Prompt modes:
|
|
|
727
766
|
if (dbDialect) {
|
|
728
767
|
argv.push('--db-dialect', dbDialect);
|
|
729
768
|
}
|
|
769
|
+
const builtinDbImage = String(results.builtinDbImage ?? '').trim();
|
|
770
|
+
if (builtinDb && builtinDbImage) {
|
|
771
|
+
argv.push('--builtin-db-image', builtinDbImage);
|
|
772
|
+
}
|
|
730
773
|
const dbHost = String(results.dbHost ?? '').trim();
|
|
731
774
|
if (dbHost) {
|
|
732
775
|
argv.push('--db-host', dbHost);
|