@nocobase/cli 2.1.0-beta.23 → 2.1.0-beta.24

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 CHANGED
@@ -60,12 +60,16 @@ nb init --ui
60
60
  When creating a new app, it can also install NocoBase AI coding skills
61
61
  (`nocobase/skills`) globally.
62
62
 
63
+ Use `--skip-skills` if the skills are managed separately, or when running in CI
64
+ or offline environments where `nb init` should not install or update them.
65
+
63
66
  ### Non-Interactive Setup
64
67
 
65
68
  When prompts are skipped, an app/env name is required:
66
69
 
67
70
  ```bash
68
71
  nb init --env app1 --yes
72
+ nb init --env app1 --yes --skip-skills
69
73
  ```
70
74
 
71
75
  Install with Docker:
@@ -111,6 +115,7 @@ If `nb init` was interrupted after the env config had already been saved, you ca
111
115
 
112
116
  ```bash
113
117
  nb init --env app1 --resume
118
+ nb init --env app1 --resume --skip-skills
114
119
  ```
115
120
 
116
121
  `--resume` reuses the saved workspace env config for app, source, database, and env connection settings. In interactive mode, it only asks for any missing setup-only values.
package/README.zh-CN.md CHANGED
@@ -55,12 +55,15 @@ nb init --ui
55
55
 
56
56
  `nb init` 可以连接已有的 NocoBase 应用,也可以安装一个新的 NocoBase 应用。创建新应用时,还可以全局安装 NocoBase AI coding skills (`nocobase/skills`)。
57
57
 
58
+ 如果已经自行管理 skills,或在 CI、离线环境中运行,不希望 `nb init` 安装或更新 skills,可以传入 `--skip-skills`。
59
+
58
60
  ### 非交互式初始化
59
61
 
60
62
  跳过交互提示时,必须提供 app/env name:
61
63
 
62
64
  ```bash
63
65
  nb init --env app1 --yes
66
+ nb init --env app1 --yes --skip-skills
64
67
  ```
65
68
 
66
69
  使用 Docker 安装:
@@ -106,6 +109,7 @@ nb init --env app1 --yes --source git --version fix/cli-v2
106
109
 
107
110
  ```bash
108
111
  nb init --env app1 --resume
112
+ nb init --env app1 --resume --skip-skills
109
113
  ```
110
114
 
111
115
  `--resume` 会复用工作区里已保存的 env config,包括应用、source、数据库和 env 连接相关配置。在交互模式下,只会继续补齐缺失的初始化参数。
@@ -0,0 +1,132 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import { Command, Flags } from '@oclif/core';
10
+ import { formatMissingManagedAppEnvMessage } from '../../lib/app-runtime.js';
11
+ import { getEnv } from '../../lib/auth-store.js';
12
+ import { checkExternalDbConnection, formatDbCheckAddress, readExternalDbConnectionConfig, } from "../../lib/db-connection-check.js";
13
+ import { validateTcpPort } from "../../lib/prompt-validators.js";
14
+ function trimValue(value) {
15
+ const text = String(value ?? '').trim();
16
+ return text || undefined;
17
+ }
18
+ function resolveRequiredDbField(flagValue, envValue) {
19
+ return trimValue(flagValue) ?? trimValue(envValue);
20
+ }
21
+ function formatMissingFieldsMessage(missing) {
22
+ return [
23
+ 'Missing database settings for connectivity check.',
24
+ `Required: ${missing.join(', ')}.`,
25
+ 'Pass `--env <name>` to reuse a saved env, or provide all `--db-*` flags explicitly.',
26
+ ].join('\n');
27
+ }
28
+ export default class DbCheck extends Command {
29
+ static description = 'Check whether the current machine can connect to a database using saved env config or explicit --db-* flags.';
30
+ static examples = [
31
+ '<%= config.bin %> <%= command.id %> --env app1',
32
+ '<%= config.bin %> <%= command.id %> --db-dialect postgres --db-host 127.0.0.1 --db-port 5432 --db-database nocobase --db-user nocobase --db-password secret',
33
+ '<%= config.bin %> <%= command.id %> --env app1 --db-password new-secret --json',
34
+ ];
35
+ static flags = {
36
+ env: Flags.string({
37
+ char: 'e',
38
+ description: 'CLI env name to read saved database settings from. Defaults to the current env when omitted.',
39
+ }),
40
+ 'db-dialect': Flags.string({
41
+ description: 'Database dialect: postgres, kingbase, mysql, or mariadb.',
42
+ options: ['postgres', 'kingbase', 'mysql', 'mariadb'],
43
+ }),
44
+ 'db-host': Flags.string({
45
+ description: 'Database host name or IP address.',
46
+ }),
47
+ 'db-port': Flags.string({
48
+ description: 'Database TCP port.',
49
+ }),
50
+ 'db-database': Flags.string({
51
+ description: 'Database name.',
52
+ }),
53
+ 'db-user': Flags.string({
54
+ description: 'Database username.',
55
+ }),
56
+ 'db-password': Flags.string({
57
+ description: 'Database password.',
58
+ }),
59
+ json: Flags.boolean({
60
+ description: 'Output the check result as JSON.',
61
+ default: false,
62
+ }),
63
+ };
64
+ async run() {
65
+ const { flags } = await this.parse(DbCheck);
66
+ const envName = flags.env?.trim() || undefined;
67
+ const env = envName || !flags['db-host'] ? await getEnv(envName) : undefined;
68
+ if (envName && !env) {
69
+ this.error(formatMissingManagedAppEnvMessage(envName));
70
+ }
71
+ const config = env?.config ?? {};
72
+ const dbConfig = {
73
+ builtinDb: false,
74
+ dbDialect: resolveRequiredDbField(flags['db-dialect'], config.dbDialect),
75
+ dbHost: resolveRequiredDbField(flags['db-host'], config.dbHost),
76
+ dbPort: resolveRequiredDbField(flags['db-port'], config.dbPort),
77
+ dbDatabase: resolveRequiredDbField(flags['db-database'], config.dbDatabase),
78
+ dbUser: resolveRequiredDbField(flags['db-user'], config.dbUser),
79
+ dbPassword: flags['db-password'] !== undefined
80
+ ? String(flags['db-password'] ?? '')
81
+ : String(config.dbPassword ?? ''),
82
+ };
83
+ const missing = [];
84
+ if (!dbConfig.dbDialect) {
85
+ missing.push('--db-dialect');
86
+ }
87
+ if (!dbConfig.dbHost) {
88
+ missing.push('--db-host');
89
+ }
90
+ if (!dbConfig.dbPort) {
91
+ missing.push('--db-port');
92
+ }
93
+ if (!dbConfig.dbDatabase) {
94
+ missing.push('--db-database');
95
+ }
96
+ if (!dbConfig.dbUser) {
97
+ missing.push('--db-user');
98
+ }
99
+ if (!dbConfig.dbPassword) {
100
+ missing.push('--db-password');
101
+ }
102
+ if (missing.length > 0) {
103
+ this.error(formatMissingFieldsMessage(missing));
104
+ }
105
+ const portError = validateTcpPort(dbConfig.dbPort);
106
+ if (portError) {
107
+ this.error(portError);
108
+ }
109
+ const connectionConfig = readExternalDbConnectionConfig(dbConfig);
110
+ if (!connectionConfig) {
111
+ this.error('Unsupported or incomplete database settings for connectivity check.');
112
+ }
113
+ const address = formatDbCheckAddress(connectionConfig);
114
+ const validationError = await checkExternalDbConnection(connectionConfig);
115
+ if (flags.json) {
116
+ this.log(JSON.stringify({
117
+ ok: !validationError,
118
+ env: env?.name,
119
+ dialect: connectionConfig.dialect,
120
+ address,
121
+ error: validationError ?? null,
122
+ }, null, 2));
123
+ return;
124
+ }
125
+ if (validationError) {
126
+ this.error(validationError);
127
+ }
128
+ this.log(env?.name
129
+ ? `Database check passed for env "${env.name}" (${connectionConfig.dialect} ${address}).`
130
+ : `Database check passed (${connectionConfig.dialect} ${address}).`);
131
+ }
132
+ }
@@ -303,7 +303,6 @@ Prompt modes:
303
303
  }),
304
304
  'skip-skills': Flags.boolean({
305
305
  description: 'Skip installing or updating NocoBase AI coding skills during init',
306
- hidden: true,
307
306
  default: false,
308
307
  }),
309
308
  'ui-host': Flags.string({
@@ -18,6 +18,7 @@ import { runPromptCatalog, } from "../lib/prompt-catalog.js";
18
18
  import { applyCliLocale, localeText, resolveCliLocale, translateCli, } from "../lib/cli-locale.js";
19
19
  import { resolveConfiguredEnvPath, resolveDefaultConfigScope, resolveEnvRoot, resolveEnvRelativePath, } from '../lib/cli-home.js';
20
20
  import { findAvailableTcpPort, validateAvailableTcpPort, validateTcpPort, validateEnvKey, } from "../lib/prompt-validators.js";
21
+ import { validateExternalDbConfig } from "../lib/db-connection-check.js";
21
22
  import { formatMissingManagedAppEnvMessage } from '../lib/app-runtime.js';
22
23
  import { run, runNocoBaseCommand } from '../lib/run-npm.js';
23
24
  import { startTask, stopTask, updateTask } from '../lib/ui.js';
@@ -180,6 +181,16 @@ function validateBuiltinDbEnabled(value, values) {
180
181
  }
181
182
  return translateCli('commands.install.validation.builtinDbUnsupported', { dialect });
182
183
  }
184
+ async function validateExternalDbPromptField(value, values) {
185
+ const builtinDb = values.builtinDb === undefined ? true : Boolean(values.builtinDb);
186
+ if (builtinDb) {
187
+ return undefined;
188
+ }
189
+ if (typeof value === 'string' && value.trim() === '') {
190
+ return undefined;
191
+ }
192
+ return await validateExternalDbConfig(values);
193
+ }
183
194
  function defaultInstallAppRootPath(envName) {
184
195
  const name = String(envName ?? DEFAULT_INSTALL_ENV_NAME).trim() || DEFAULT_INSTALL_ENV_NAME;
185
196
  return `./${name}/source/`;
@@ -447,6 +458,7 @@ export default class Install extends Command {
447
458
  initialValue: (values) => defaultDbHostForBuiltinDb(values),
448
459
  yesInitialValue: DEFAULT_INSTALL_BUILTIN_DB_HOST,
449
460
  required: true,
461
+ validate: validateExternalDbPromptField,
450
462
  hidden: (values) => Boolean(values.builtinDb),
451
463
  },
452
464
  dbPort: {
@@ -464,6 +476,7 @@ export default class Install extends Command {
464
476
  message: installText('prompts.dbDatabase.message'),
465
477
  initialValue: (values) => defaultDbDatabaseForDialect(values.dbDialect),
466
478
  required: true,
479
+ validate: validateExternalDbPromptField,
467
480
  },
468
481
  dbUser: {
469
482
  type: 'text',
@@ -471,6 +484,7 @@ export default class Install extends Command {
471
484
  initialValue: DEFAULT_INSTALL_DB_USER,
472
485
  yesInitialValue: DEFAULT_INSTALL_DB_USER,
473
486
  required: true,
487
+ validate: validateExternalDbPromptField,
474
488
  },
475
489
  dbPassword: {
476
490
  type: 'password',
@@ -478,6 +492,7 @@ export default class Install extends Command {
478
492
  initialValue: DEFAULT_INSTALL_DB_PASSWORD,
479
493
  yesInitialValue: DEFAULT_INSTALL_DB_PASSWORD,
480
494
  required: true,
495
+ validate: validateExternalDbPromptField,
481
496
  },
482
497
  };
483
498
  static rootUserPrompts = {
@@ -741,6 +756,9 @@ export default class Install extends Command {
741
756
  const builtinDb = values.builtinDb === undefined ? true : Boolean(values.builtinDb);
742
757
  const source = String(values.source ?? '').trim();
743
758
  if (!builtinDb || source === 'docker') {
759
+ if (!builtinDb) {
760
+ return await validateExternalDbConfig({ ...values, dbPort: value });
761
+ }
744
762
  return undefined;
745
763
  }
746
764
  return await Install.validateResumeAwareTcpPort(value, values, 'db');
@@ -765,6 +783,23 @@ export default class Install extends Command {
765
783
  });
766
784
  return reusesManagedPort ? undefined : portError;
767
785
  }
786
+ static async ensureExternalDbReadyForInstall(dbResults) {
787
+ const builtinDb = dbResults.builtinDb === undefined ? true : Boolean(dbResults.builtinDb);
788
+ if (builtinDb) {
789
+ return;
790
+ }
791
+ const dialect = String(dbResults.dbDialect ?? 'postgres').trim() || 'postgres';
792
+ const host = String(dbResults.dbHost ?? '').trim();
793
+ const port = String(dbResults.dbPort ?? '').trim();
794
+ const database = String(dbResults.dbDatabase ?? '').trim();
795
+ const address = host && port ? `${host}:${port}` : host || port || '(unknown address)';
796
+ const target = database ? `${address}/${database}` : address;
797
+ p.log.step(`Checking external ${dialect} database: ${target}`);
798
+ const validationError = await validateExternalDbConfig(dbResults);
799
+ if (validationError) {
800
+ throw new Error(validationError);
801
+ }
802
+ }
768
803
  static async readResumePortValidationContext(values) {
769
804
  if (!Boolean(values.resume)) {
770
805
  return undefined;
@@ -2044,6 +2079,7 @@ export default class Install extends Command {
2044
2079
  const workspaceName = usesDockerResources
2045
2080
  ? await Install.ensureWorkspaceName()
2046
2081
  : undefined;
2082
+ await Install.ensureExternalDbReadyForInstall(dbResults);
2047
2083
  if (!parsed.resume) {
2048
2084
  await this.saveInstalledEnv({
2049
2085
  envName,
@@ -2053,6 +2089,7 @@ export default class Install extends Command {
2053
2089
  rootResults,
2054
2090
  envAddResults,
2055
2091
  });
2092
+ p.log.info(`Saved install config for env "${envName}"`);
2056
2093
  }
2057
2094
  let builtinDbPlan;
2058
2095
  if (Boolean(dbResults.builtinDb)) {
@@ -0,0 +1,178 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import { translateCli } from "./cli-locale.js";
10
+ import { validateTcpPort } from "./prompt-validators.js";
11
+ const DB_CONNECTION_TIMEOUT_MS = 5_000;
12
+ const externalDbValidationCache = new Map();
13
+ function trimPromptValue(value) {
14
+ return String(value ?? '').trim();
15
+ }
16
+ export function readExternalDbConnectionConfig(values) {
17
+ const builtinDb = values.builtinDb === undefined ? true : Boolean(values.builtinDb);
18
+ if (builtinDb) {
19
+ return undefined;
20
+ }
21
+ const dialect = trimPromptValue(values.dbDialect || 'postgres');
22
+ if (dialect !== 'postgres' && dialect !== 'kingbase' && dialect !== 'mysql' && dialect !== 'mariadb') {
23
+ return undefined;
24
+ }
25
+ const host = trimPromptValue(values.dbHost);
26
+ const portText = trimPromptValue(values.dbPort);
27
+ const database = trimPromptValue(values.dbDatabase);
28
+ const user = trimPromptValue(values.dbUser);
29
+ const password = String(values.dbPassword ?? '');
30
+ if (!host || !portText || !database || !user || !password) {
31
+ return undefined;
32
+ }
33
+ if (validateTcpPort(portText)) {
34
+ return undefined;
35
+ }
36
+ return {
37
+ dialect,
38
+ host,
39
+ port: Number.parseInt(portText, 10),
40
+ database,
41
+ user,
42
+ password,
43
+ };
44
+ }
45
+ export function formatDbCheckAddress(config) {
46
+ return `${config.host}:${config.port}/${config.database}`;
47
+ }
48
+ function buildValidationCacheKey(config) {
49
+ return JSON.stringify(config);
50
+ }
51
+ function formatDbConnectionError(config, error) {
52
+ const maybeError = error;
53
+ const code = String(maybeError?.code ?? '').trim().toUpperCase();
54
+ const errno = typeof maybeError?.errno === 'number' ? maybeError.errno : undefined;
55
+ const rawMessage = String(maybeError?.message || maybeError?.sqlMessage || error || '').trim();
56
+ if (code === 'ECONNREFUSED' || code === 'ENOTFOUND' || code === 'EHOSTUNREACH' || code === 'ECONNRESET') {
57
+ return translateCli('validators.dbConnection.unreachable', {
58
+ host: config.host,
59
+ port: config.port,
60
+ details: rawMessage,
61
+ });
62
+ }
63
+ if (code === 'ETIMEDOUT') {
64
+ return translateCli('validators.dbConnection.timeout', {
65
+ host: config.host,
66
+ port: config.port,
67
+ seconds: Math.ceil(DB_CONNECTION_TIMEOUT_MS / 1000),
68
+ });
69
+ }
70
+ if (code === '28P01' || code === '28000' || code === 'ER_ACCESS_DENIED_ERROR' || errno === 1045) {
71
+ return translateCli('validators.dbConnection.authenticationFailed', {
72
+ user: config.user,
73
+ database: config.database,
74
+ });
75
+ }
76
+ if (code === '3D000' || code === 'ER_BAD_DB_ERROR' || errno === 1049) {
77
+ return translateCli('validators.dbConnection.databaseNotFound', {
78
+ database: config.database,
79
+ });
80
+ }
81
+ return translateCli('validators.dbConnection.connectionFailed', {
82
+ details: rawMessage || code || String(error),
83
+ });
84
+ }
85
+ async function checkPostgresFamilyConnection(config) {
86
+ const { default: pg } = await import('pg');
87
+ const client = new pg.Client({
88
+ host: config.host,
89
+ port: config.port,
90
+ user: config.user,
91
+ password: config.password,
92
+ database: config.database,
93
+ connectionTimeoutMillis: DB_CONNECTION_TIMEOUT_MS,
94
+ });
95
+ try {
96
+ await client.connect();
97
+ await client.query('SELECT 1');
98
+ }
99
+ finally {
100
+ await Promise.resolve(client.end()).catch(() => undefined);
101
+ }
102
+ }
103
+ async function checkMysqlConnection(config) {
104
+ const { default: mysql } = await import('mysql2/promise');
105
+ const connection = await mysql.createConnection({
106
+ host: config.host,
107
+ port: config.port,
108
+ user: config.user,
109
+ password: config.password,
110
+ database: config.database,
111
+ connectTimeout: DB_CONNECTION_TIMEOUT_MS,
112
+ });
113
+ try {
114
+ await connection.query('SELECT 1');
115
+ }
116
+ finally {
117
+ await Promise.resolve(connection.end()).catch(() => undefined);
118
+ }
119
+ }
120
+ async function checkMariaDbConnection(config) {
121
+ const { default: mariadb } = await import('mariadb');
122
+ const connection = await mariadb.createConnection({
123
+ host: config.host,
124
+ port: config.port,
125
+ user: config.user,
126
+ password: config.password,
127
+ database: config.database,
128
+ connectTimeout: DB_CONNECTION_TIMEOUT_MS,
129
+ });
130
+ try {
131
+ await connection.query('SELECT 1');
132
+ }
133
+ finally {
134
+ await Promise.resolve(connection.end()).catch(() => undefined);
135
+ }
136
+ }
137
+ async function performExternalDbConnectionCheck(config) {
138
+ try {
139
+ switch (config.dialect) {
140
+ case 'postgres':
141
+ case 'kingbase': {
142
+ await checkPostgresFamilyConnection(config);
143
+ return undefined;
144
+ }
145
+ case 'mysql': {
146
+ await checkMysqlConnection(config);
147
+ return undefined;
148
+ }
149
+ case 'mariadb': {
150
+ await checkMariaDbConnection(config);
151
+ return undefined;
152
+ }
153
+ }
154
+ }
155
+ catch (error) {
156
+ return formatDbConnectionError(config, error);
157
+ }
158
+ }
159
+ export async function checkExternalDbConnection(config) {
160
+ const cacheKey = buildValidationCacheKey(config);
161
+ const cached = externalDbValidationCache.get(cacheKey);
162
+ if (cached) {
163
+ return await cached;
164
+ }
165
+ const pending = performExternalDbConnectionCheck(config);
166
+ externalDbValidationCache.set(cacheKey, pending);
167
+ return await pending;
168
+ }
169
+ export async function validateExternalDbConfig(values) {
170
+ const config = readExternalDbConnectionConfig(values);
171
+ if (!config) {
172
+ return undefined;
173
+ }
174
+ return await checkExternalDbConnection(config);
175
+ }
176
+ export function clearExternalDbValidationCache() {
177
+ externalDbValidationCache.clear();
178
+ }
@@ -20,7 +20,10 @@ function getStateFile() {
20
20
  return path.join(resolveCliHomeDir('global'), STARTUP_UPDATE_STATE_FILE);
21
21
  }
22
22
  function todayStamp(now = new Date()) {
23
- return now.toISOString().slice(0, 10);
23
+ const year = now.getFullYear();
24
+ const month = String(now.getMonth() + 1).padStart(2, '0');
25
+ const day = String(now.getDate()).padStart(2, '0');
26
+ return `${year}-${month}-${day}`;
24
27
  }
25
28
  function shouldSkipByArgv(argv) {
26
29
  const tokens = argv.filter((token) => token && !token.startsWith('-'));
@@ -62,6 +62,13 @@
62
62
  "allocateNotDockerPublished": "Failed to allocate an available TCP port that is not already published by Docker.",
63
63
  "alreadyInUse": "Port {{port}} is already in use. Choose another port.",
64
64
  "alreadyInUseByDocker": "Port {{port}} is already in use by a Docker container. Choose another port."
65
+ },
66
+ "dbConnection": {
67
+ "unreachable": "Can't reach the database at {{host}}:{{port}}. Check the host, port, and network connectivity. Details: {{details}}",
68
+ "timeout": "Timed out connecting to the database at {{host}}:{{port}} after about {{seconds}} seconds.",
69
+ "authenticationFailed": "Failed to sign in to database \"{{database}}\" with user \"{{user}}\". Check the username and password.",
70
+ "databaseNotFound": "Database \"{{database}}\" does not exist or is not accessible with the current connection settings.",
71
+ "connectionFailed": "Database connection check failed. Details: {{details}}"
65
72
  }
66
73
  },
67
74
  "commands": {
@@ -273,7 +280,7 @@
273
280
  "envExists": "Env \"{{envName}}\" already exists. Choose another env name."
274
281
  },
275
282
  "messages": {
276
- "title": "Set Up Your NocoBase AI Workspace",
283
+ "title": "Set Up NocoBase for Coding Agents",
277
284
  "appNameRequiredWhenSkipped": "Env name is required when prompts are skipped.",
278
285
  "appNameEnvHelp": "Use `nb init --yes --env <envName>` to continue.",
279
286
  "resumeEnvRequired": "Env name is required when resuming setup.",
@@ -303,24 +310,24 @@
303
310
  }
304
311
  },
305
312
  "webUi": {
306
- "pageTitle": "Set Up Your NocoBase AI Workspace",
307
- "documentHeading": "Set Up Your NocoBase AI Workspace",
308
- "documentHint": "Connect an existing NocoBase app, or install a new one and connect it, so coding agents can work with NocoBase in this workspace.",
313
+ "pageTitle": "Set Up NocoBase for Coding Agents",
314
+ "documentHeading": "Set Up NocoBase for Coding Agents",
315
+ "documentHint": "Connect an existing NocoBase app, or install a new one, so coding agents can access and work with NocoBase.",
309
316
  "gettingStarted": {
310
317
  "title": "Getting started",
311
- "description": "Pick your setup path."
318
+ "description": "Choose whether to connect an existing app or install a new one."
312
319
  },
313
320
  "connectExistingApp": {
314
321
  "title": "Connect an existing app",
315
- "description": "Add your app connection."
322
+ "description": "Save your app connection."
316
323
  },
317
324
  "createNewApp": {
318
- "title": "Create a new app",
319
- "description": "Set project basics."
325
+ "title": "Install a new app",
326
+ "description": "Set app basics and install options."
320
327
  },
321
328
  "downloadAppFiles": {
322
329
  "title": "Download app files",
323
- "description": "Choose source and options."
330
+ "description": "Choose how to get the app files."
324
331
  },
325
332
  "configureDatabase": {
326
333
  "title": "Configure the database",
@@ -62,6 +62,13 @@
62
62
  "allocateNotDockerPublished": "分配未被 Docker 占用的可用 TCP 端口失败。",
63
63
  "alreadyInUse": "端口 {{port}} 已被占用,请更换其他端口。",
64
64
  "alreadyInUseByDocker": "端口 {{port}} 已被 Docker 容器占用,请更换其他端口。"
65
+ },
66
+ "dbConnection": {
67
+ "unreachable": "无法连接到数据库 {{host}}:{{port}}。请检查主机、端口和网络连通性。详情:{{details}}",
68
+ "timeout": "连接数据库 {{host}}:{{port}} 超时,约 {{seconds}} 秒内未成功建立连接。",
69
+ "authenticationFailed": "无法使用用户 \"{{user}}\" 登录数据库 \"{{database}}\"。请检查用户名和密码。",
70
+ "databaseNotFound": "数据库 \"{{database}}\" 不存在,或当前连接配置无权访问该数据库。",
71
+ "connectionFailed": "数据库连接检查失败。详情:{{details}}"
65
72
  }
66
73
  },
67
74
  "commands": {
@@ -273,7 +280,7 @@
273
280
  "envExists": "Env \"{{envName}}\" 已存在,请换一个 env name。"
274
281
  },
275
282
  "messages": {
276
- "title": "初始化你的 NocoBase AI 工作区",
283
+ "title": "配置供 Coding Agents 使用的 NocoBase",
277
284
  "appNameRequiredWhenSkipped": "跳过 prompts 时必须提供 Env name。",
278
285
  "appNameEnvHelp": "请使用 `nb init --yes --env <envName>` 继续。",
279
286
  "resumeEnvRequired": "恢复安装时必须提供 Env name。",
@@ -303,24 +310,24 @@
303
310
  }
304
311
  },
305
312
  "webUi": {
306
- "pageTitle": "初始化你的 NocoBase AI 工作区",
307
- "documentHeading": "初始化你的 NocoBase AI 工作区",
308
- "documentHint": "连接已有的 NocoBase 应用,或安装一个新的应用后再完成连接,让 Coding Agents 可以在当前工作区中结合 NocoBase 协同工作。",
313
+ "pageTitle": "配置供 Coding Agents 使用的 NocoBase",
314
+ "documentHeading": "配置供 Coding Agents 使用的 NocoBase",
315
+ "documentHint": "连接已有的 NocoBase 应用,或安装一个新的应用,让 Coding Agents 可以在当前工作区中访问和操作 NocoBase",
309
316
  "gettingStarted": {
310
317
  "title": "开始设置",
311
- "description": "选择你的配置路径。"
318
+ "description": "选择连接已有应用,或安装一个新应用。"
312
319
  },
313
320
  "connectExistingApp": {
314
321
  "title": "连接已有应用",
315
- "description": "添加现有应用连接。"
322
+ "description": "保存现有应用连接。"
316
323
  },
317
324
  "createNewApp": {
318
- "title": "创建新应用",
319
- "description": "设置项目基础信息。"
325
+ "title": "安装新应用",
326
+ "description": "设置应用基础信息和安装选项。"
320
327
  },
321
328
  "downloadAppFiles": {
322
329
  "title": "下载应用文件",
323
- "description": "选择来源和下载选项。"
330
+ "description": "选择获取应用文件的方式。"
324
331
  },
325
332
  "configureDatabase": {
326
333
  "title": "配置数据库",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/cli",
3
- "version": "2.1.0-beta.23",
3
+ "version": "2.1.0-beta.24",
4
4
  "description": "NocoBase Command Line Tool",
5
5
  "type": "module",
6
6
  "main": "dist/generated/command-registry.js",
@@ -78,8 +78,11 @@
78
78
  "@oclif/core": "^4.10.4",
79
79
  "cross-spawn": "^7.0.6",
80
80
  "lodash": "^4.17.21",
81
+ "mariadb": "^2.5.6",
82
+ "mysql2": "^3.14.0",
81
83
  "openapi-types": "^12.1.3",
82
84
  "ora": "^8.2.0",
85
+ "pg": "^8.14.1",
83
86
  "picocolors": "^1.1.1"
84
87
  },
85
88
  "devDependencies": {
@@ -91,5 +94,5 @@
91
94
  "type": "git",
92
95
  "url": "git+https://github.com/nocobase/nocobase.git"
93
96
  },
94
- "gitHead": "bb4c0d3551bf9eff505b63756dd24a0813231f16"
97
+ "gitHead": "f77b85530a2d127d9bfe4dca3a26fbb02c1139ba"
95
98
  }