@nocobase/cli 2.1.0-beta.9 → 2.2.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/env-proxy/nginx/app.conf.tpl +23 -0
- package/assets/env-proxy/nginx/nocobase.conf.tpl +5 -0
- package/assets/env-proxy/nginx/snippets/dist-location.conf +5 -0
- package/assets/env-proxy/nginx/snippets/gzip.conf +17 -0
- package/assets/env-proxy/nginx/snippets/log-format-http.conf +13 -0
- package/assets/env-proxy/nginx/snippets/maps-http.conf +14 -0
- package/assets/env-proxy/nginx/snippets/mime-types.conf +98 -0
- package/assets/env-proxy/nginx/snippets/proxy-location.conf +17 -0
- package/assets/env-proxy/nginx/snippets/spa-location.conf +6 -0
- package/assets/env-proxy/nginx/snippets/uploads-location.conf +21 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +145 -0
- package/bin/session-env.js +39 -0
- package/dist/commands/api/resource/create.js +15 -0
- package/dist/commands/api/resource/destroy.js +15 -0
- package/dist/commands/api/resource/get.js +15 -0
- package/dist/commands/api/resource/index.js +20 -0
- package/dist/commands/api/resource/list.js +16 -0
- package/dist/commands/api/resource/query.js +15 -0
- package/dist/commands/api/resource/update.js +15 -0
- package/dist/commands/app/autostart/disable.js +55 -0
- package/dist/commands/app/autostart/enable.js +55 -0
- package/dist/commands/app/autostart/list.js +37 -0
- package/dist/commands/app/autostart/run.js +84 -0
- package/dist/commands/app/autostart/shared.js +49 -0
- package/dist/commands/app/destroy.js +234 -0
- package/dist/commands/app/down.js +71 -0
- package/dist/commands/app/logs.js +115 -0
- package/dist/commands/app/restart.js +229 -0
- package/dist/commands/app/shared.js +123 -0
- package/dist/commands/app/start.js +416 -0
- package/dist/commands/app/stop.js +183 -0
- package/dist/commands/app/upgrade.js +523 -0
- package/dist/commands/backup/create.js +147 -0
- package/dist/commands/backup/index.js +20 -0
- package/dist/commands/backup/restore.js +105 -0
- package/{src/cli.js → dist/commands/build.js} +4 -11
- package/dist/commands/config/delete.js +42 -0
- package/dist/commands/config/get.js +39 -0
- package/dist/commands/config/index.js +20 -0
- package/dist/commands/config/list.js +29 -0
- package/dist/commands/config/set.js +49 -0
- package/dist/commands/db/check.js +240 -0
- package/dist/commands/db/logs.js +85 -0
- package/dist/commands/db/ps.js +47 -0
- package/dist/commands/db/shared.js +96 -0
- package/dist/commands/db/start.js +86 -0
- package/dist/commands/db/stop.js +71 -0
- package/{templates/plugin/src/client/models/index.ts → dist/commands/dev.js} +4 -4
- package/{src/commands/locale/react-js-cron/index.js → dist/commands/down.js} +3 -8
- package/dist/commands/download.js +13 -0
- package/dist/commands/env/add.js +406 -0
- package/dist/commands/env/auth.js +189 -0
- package/dist/commands/env/current.js +21 -0
- package/dist/commands/env/info.js +202 -0
- package/dist/commands/env/list.js +43 -0
- package/dist/commands/env/remove.js +174 -0
- package/dist/commands/env/shared.js +204 -0
- package/dist/commands/env/status.js +93 -0
- package/dist/commands/env/update.js +448 -0
- package/dist/commands/env/use.js +38 -0
- package/dist/commands/examples/prompts-stages.js +150 -0
- package/dist/commands/examples/prompts-test.js +181 -0
- package/dist/commands/init.js +1390 -0
- package/dist/commands/install.js +2609 -0
- package/dist/commands/license/activate.js +179 -0
- package/dist/commands/license/env.js +94 -0
- package/dist/commands/license/generate-id.js +108 -0
- package/dist/commands/license/id.js +70 -0
- package/dist/commands/license/index.js +20 -0
- package/dist/commands/license/plugins/clean.js +115 -0
- package/dist/commands/license/plugins/index.js +20 -0
- package/dist/commands/license/plugins/list.js +64 -0
- package/dist/commands/license/plugins/shared.js +382 -0
- package/dist/commands/license/plugins/sync.js +314 -0
- package/dist/commands/license/shared.js +423 -0
- package/dist/commands/license/status.js +64 -0
- package/dist/commands/logs.js +12 -0
- package/dist/commands/plugin/disable.js +86 -0
- package/dist/commands/plugin/enable.js +86 -0
- package/dist/commands/plugin/import.js +108 -0
- package/dist/commands/plugin/list.js +82 -0
- package/dist/commands/pm/disable.js +12 -0
- package/dist/commands/pm/enable.js +12 -0
- package/dist/commands/pm/list.js +12 -0
- package/dist/commands/proxy/caddy/current.js +17 -0
- package/dist/commands/proxy/caddy/generate.js +69 -0
- package/dist/commands/proxy/caddy/index.js +28 -0
- package/dist/commands/proxy/caddy/info.js +31 -0
- package/dist/commands/proxy/caddy/reload.js +30 -0
- package/dist/commands/proxy/caddy/restart.js +28 -0
- package/dist/commands/proxy/caddy/start.js +30 -0
- package/dist/commands/proxy/caddy/status.js +19 -0
- package/dist/commands/proxy/caddy/stop.js +30 -0
- package/dist/commands/proxy/caddy/use.js +26 -0
- package/dist/commands/proxy/index.js +28 -0
- package/dist/commands/proxy/nginx/current.js +18 -0
- package/dist/commands/proxy/nginx/generate.js +68 -0
- package/dist/commands/proxy/nginx/index.js +28 -0
- package/dist/commands/proxy/nginx/info.js +34 -0
- package/dist/commands/proxy/nginx/reload.js +30 -0
- package/dist/commands/proxy/nginx/restart.js +28 -0
- package/dist/commands/proxy/nginx/start.js +30 -0
- package/dist/commands/proxy/nginx/status.js +19 -0
- package/dist/commands/proxy/nginx/stop.js +30 -0
- package/dist/commands/proxy/nginx/use.js +31 -0
- package/dist/commands/restart.js +12 -0
- package/dist/commands/revision/create.js +118 -0
- package/dist/commands/scaffold/migration.js +38 -0
- package/dist/commands/scaffold/plugin.js +37 -0
- package/dist/commands/self/check.js +71 -0
- package/dist/commands/self/index.js +20 -0
- package/dist/commands/self/update.js +152 -0
- package/dist/commands/session/id.js +24 -0
- package/dist/commands/session/remove.js +57 -0
- package/dist/commands/session/setup.js +62 -0
- package/dist/commands/skills/check.js +69 -0
- package/dist/commands/skills/index.js +20 -0
- package/dist/commands/skills/install.js +80 -0
- package/dist/commands/skills/remove.js +80 -0
- package/dist/commands/skills/update.js +87 -0
- package/dist/commands/source/build.js +58 -0
- package/dist/commands/source/dev.js +182 -0
- package/dist/commands/source/download.js +884 -0
- package/dist/commands/source/publish.js +109 -0
- package/dist/commands/source/registry/logs.js +70 -0
- package/dist/commands/source/registry/start.js +57 -0
- package/dist/commands/source/registry/status.js +33 -0
- package/dist/commands/source/registry/stop.js +48 -0
- package/dist/commands/source/test.js +476 -0
- package/dist/commands/start.js +12 -0
- package/dist/commands/stop.js +12 -0
- package/dist/commands/test.js +12 -0
- package/dist/commands/upgrade.js +12 -0
- package/dist/commands/v1.js +210 -0
- package/dist/generated/command-registry.js +134 -0
- package/dist/help/runtime-help.js +23 -0
- package/dist/lib/api-client.js +335 -0
- package/dist/lib/api-command-compat.js +641 -0
- package/dist/lib/app-health.js +139 -0
- package/dist/lib/app-managed-resources.js +337 -0
- package/dist/lib/app-public-path.js +80 -0
- package/dist/lib/app-runtime.js +189 -0
- package/dist/lib/auth-store.js +528 -0
- package/dist/lib/backup.js +171 -0
- package/dist/lib/bootstrap.js +409 -0
- package/dist/lib/build-config.js +18 -0
- package/dist/lib/builtin-db.js +86 -0
- package/dist/lib/cli-config.js +569 -0
- package/dist/lib/cli-entry-error.js +52 -0
- package/dist/lib/cli-home.js +47 -0
- package/dist/lib/cli-locale.js +141 -0
- package/dist/lib/command-discovery.js +39 -0
- package/dist/lib/command-log.js +284 -0
- package/dist/lib/db-connection-check.js +219 -0
- package/dist/lib/docker-env-file.js +60 -0
- package/dist/lib/docker-image.js +37 -0
- package/dist/lib/docker-log-stream.js +45 -0
- package/dist/lib/env-auth.js +963 -0
- package/dist/lib/env-command-config.js +45 -0
- package/dist/lib/env-config.js +108 -0
- package/dist/lib/env-guard.js +61 -0
- package/dist/lib/env-paths.js +101 -0
- package/dist/lib/env-proxy.js +1325 -0
- package/dist/lib/generated-command.js +203 -0
- package/dist/lib/http-request.js +49 -0
- package/dist/lib/inquirer-theme.js +17 -0
- package/dist/lib/inquirer.js +243 -0
- package/dist/lib/managed-env-file.js +101 -0
- package/dist/lib/managed-init-env.js +32 -0
- package/dist/lib/naming.js +70 -0
- package/dist/lib/object-utils.js +76 -0
- package/dist/lib/openapi.js +62 -0
- package/dist/lib/plugin-import.js +279 -0
- package/dist/lib/plugin-storage.js +64 -0
- package/dist/lib/post-processors.js +23 -0
- package/dist/lib/prompt-catalog-core.js +186 -0
- package/dist/lib/prompt-catalog-terminal.js +374 -0
- package/{src/index.js → dist/lib/prompt-catalog.js} +2 -6
- package/dist/lib/prompt-validators.js +278 -0
- package/dist/lib/prompt-web-ui.js +2234 -0
- package/dist/lib/proxy-caddy.js +274 -0
- package/dist/lib/proxy-nginx.js +330 -0
- package/dist/lib/resource-command.js +357 -0
- package/dist/lib/resource-request.js +104 -0
- package/dist/lib/run-npm.js +429 -0
- package/dist/lib/runtime-env-vars.js +32 -0
- package/dist/lib/runtime-generator.js +498 -0
- package/dist/lib/runtime-store.js +56 -0
- package/dist/lib/self-manager.js +301 -0
- package/dist/lib/session-id.js +17 -0
- package/dist/lib/session-integration.js +703 -0
- package/dist/lib/session-store.js +118 -0
- package/dist/lib/skills-manager.js +438 -0
- package/dist/lib/source-publish.js +326 -0
- package/dist/lib/source-registry.js +188 -0
- package/dist/lib/startup-update.js +309 -0
- package/dist/lib/ui.js +159 -0
- package/dist/locale/en-US.json +526 -0
- package/dist/locale/zh-CN.json +526 -0
- package/dist/post-processors/data-modeling.js +84 -0
- package/dist/post-processors/data-source-manager.js +138 -0
- package/dist/post-processors/index.js +19 -0
- package/nocobase-ctl.config.json +388 -0
- package/package.json +128 -24
- package/scripts/build.mjs +34 -0
- package/scripts/clean.mjs +9 -0
- package/tsconfig.json +19 -0
- package/bin/index.js +0 -39
- package/nocobase.conf.tpl +0 -95
- package/src/commands/benchmark.js +0 -73
- package/src/commands/build.js +0 -49
- package/src/commands/clean.js +0 -30
- package/src/commands/client.js +0 -166
- package/src/commands/create-nginx-conf.js +0 -37
- package/src/commands/create-plugin.js +0 -33
- package/src/commands/dev.js +0 -200
- package/src/commands/doc.js +0 -76
- package/src/commands/e2e.js +0 -265
- package/src/commands/global.js +0 -43
- package/src/commands/index.js +0 -45
- package/src/commands/instance-id.js +0 -47
- package/src/commands/locale/cronstrue.js +0 -122
- package/src/commands/locale/react-js-cron/en-US.json +0 -75
- package/src/commands/locale/react-js-cron/zh-CN.json +0 -33
- package/src/commands/locale/react-js-cron/zh-TW.json +0 -33
- package/src/commands/locale.js +0 -81
- package/src/commands/p-test.js +0 -88
- package/src/commands/perf.js +0 -63
- package/src/commands/pkg.js +0 -321
- package/src/commands/pm2.js +0 -37
- package/src/commands/postinstall.js +0 -88
- package/src/commands/start.js +0 -148
- package/src/commands/tar.js +0 -36
- package/src/commands/test-coverage.js +0 -55
- package/src/commands/test.js +0 -107
- package/src/commands/umi.js +0 -33
- package/src/commands/update-deps.js +0 -72
- package/src/commands/upgrade.js +0 -47
- package/src/commands/view-license-key.js +0 -44
- package/src/license.js +0 -76
- package/src/logger.js +0 -75
- package/src/plugin-generator.js +0 -80
- package/src/util.js +0 -517
- package/templates/bundle-status.html +0 -338
- package/templates/create-app-package.json +0 -39
- package/templates/plugin/.npmignore.tpl +0 -2
- package/templates/plugin/README.md.tpl +0 -1
- package/templates/plugin/client.d.ts +0 -2
- package/templates/plugin/client.js +0 -1
- package/templates/plugin/package.json.tpl +0 -11
- package/templates/plugin/server.d.ts +0 -2
- package/templates/plugin/server.js +0 -1
- package/templates/plugin/src/client/client.d.ts +0 -249
- package/templates/plugin/src/client/index.tsx.tpl +0 -1
- package/templates/plugin/src/client/locale.ts +0 -21
- package/templates/plugin/src/client/plugin.tsx.tpl +0 -10
- package/templates/plugin/src/index.ts +0 -2
- package/templates/plugin/src/locale/en-US.json +0 -1
- package/templates/plugin/src/locale/zh-CN.json +0 -1
- package/templates/plugin/src/server/collections/.gitkeep +0 -0
- package/templates/plugin/src/server/index.ts.tpl +0 -1
- package/templates/plugin/src/server/plugin.ts.tpl +0 -19
|
@@ -0,0 +1,141 @@
|
|
|
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 { readFileSync } from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import { resolveCliHomeDir } from './cli-home.js';
|
|
13
|
+
export const SUPPORTED_CLI_LOCALES = ['en-US', 'zh-CN'];
|
|
14
|
+
export const CLI_LOCALE_FLAG_OPTIONS = [...SUPPORTED_CLI_LOCALES];
|
|
15
|
+
export const CLI_LOCALE_FLAG_DESCRIPTION = 'Language for CLI prompts and the local setup UI.';
|
|
16
|
+
const DEFAULT_CLI_LOCALE = 'en-US';
|
|
17
|
+
const localeCache = {};
|
|
18
|
+
export function normalizeCliLocale(value) {
|
|
19
|
+
const raw = String(value ?? '').trim();
|
|
20
|
+
if (!raw) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
const normalized = raw.replace(/\..*$/, '').replace(/_/g, '-').toLowerCase();
|
|
24
|
+
if (normalized === 'zh' || normalized.startsWith('zh-')) {
|
|
25
|
+
return 'zh-CN';
|
|
26
|
+
}
|
|
27
|
+
if (normalized === 'en' || normalized.startsWith('en-')) {
|
|
28
|
+
return 'en-US';
|
|
29
|
+
}
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
function readConfiguredCliLocale() {
|
|
33
|
+
try {
|
|
34
|
+
const configPath = path.join(resolveCliHomeDir(), 'config.json');
|
|
35
|
+
const content = readFileSync(configPath, 'utf8');
|
|
36
|
+
const parsed = JSON.parse(content);
|
|
37
|
+
return normalizeCliLocale(parsed.settings?.locale === undefined ? undefined : String(parsed.settings.locale));
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function loadLocaleMessages(locale) {
|
|
44
|
+
if (localeCache[locale]) {
|
|
45
|
+
return localeCache[locale];
|
|
46
|
+
}
|
|
47
|
+
const moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
|
48
|
+
const fallbackPath = path.resolve(moduleDir, '..', 'locale', `${locale}.json`);
|
|
49
|
+
const fileUrl = new URL(`../locale/${locale}.json`, import.meta.url);
|
|
50
|
+
let parsed;
|
|
51
|
+
try {
|
|
52
|
+
parsed = JSON.parse(readFileSync(fileUrl, 'utf8'));
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
const code = error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
|
|
56
|
+
if (code !== 'ENOENT') {
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
parsed = JSON.parse(readFileSync(fallbackPath, 'utf8'));
|
|
60
|
+
}
|
|
61
|
+
localeCache[locale] = parsed;
|
|
62
|
+
return parsed;
|
|
63
|
+
}
|
|
64
|
+
function getPathValue(input, path) {
|
|
65
|
+
let current = input;
|
|
66
|
+
for (const part of path.split('.')) {
|
|
67
|
+
if (!current || typeof current !== 'object' || !Object.prototype.hasOwnProperty.call(current, part)) {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
current = current[part];
|
|
71
|
+
}
|
|
72
|
+
return current;
|
|
73
|
+
}
|
|
74
|
+
function interpolateTemplate(template, values) {
|
|
75
|
+
return template.replace(/{{\s*([\w.]+)\s*}}/g, (_match, key) => {
|
|
76
|
+
const value = getPathValue(values, key);
|
|
77
|
+
return value === undefined || value === null ? '' : String(value);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
export function detectCliLocale(configuredLocale) {
|
|
81
|
+
const resolvedConfiguredLocale = configuredLocale ?? readConfiguredCliLocale();
|
|
82
|
+
const candidates = [
|
|
83
|
+
process.env.NB_LOCALE,
|
|
84
|
+
resolvedConfiguredLocale,
|
|
85
|
+
process.env.LC_ALL,
|
|
86
|
+
process.env.LC_MESSAGES,
|
|
87
|
+
process.env.LANG,
|
|
88
|
+
Intl.DateTimeFormat().resolvedOptions().locale,
|
|
89
|
+
];
|
|
90
|
+
for (const candidate of candidates) {
|
|
91
|
+
const locale = normalizeCliLocale(candidate);
|
|
92
|
+
if (locale) {
|
|
93
|
+
return locale;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return DEFAULT_CLI_LOCALE;
|
|
97
|
+
}
|
|
98
|
+
export function resolveCliLocale(preferred, options) {
|
|
99
|
+
return normalizeCliLocale(preferred) ?? detectCliLocale(options?.configuredLocale);
|
|
100
|
+
}
|
|
101
|
+
export function applyCliLocale(preferred) {
|
|
102
|
+
const locale = resolveCliLocale(preferred);
|
|
103
|
+
process.env.NB_LOCALE = locale;
|
|
104
|
+
return locale;
|
|
105
|
+
}
|
|
106
|
+
export function createCliTranslate(preferred) {
|
|
107
|
+
const locale = resolveCliLocale(preferred);
|
|
108
|
+
return (key, values, fallback) => {
|
|
109
|
+
const messages = loadLocaleMessages(locale);
|
|
110
|
+
const template = getPathValue(messages, key);
|
|
111
|
+
if (typeof template !== 'string') {
|
|
112
|
+
return interpolateTemplate(fallback ?? key, values);
|
|
113
|
+
}
|
|
114
|
+
return interpolateTemplate(template, values);
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
export function translateCli(key, values, options) {
|
|
118
|
+
return createCliTranslate(options?.locale)(key, values, options?.fallback);
|
|
119
|
+
}
|
|
120
|
+
export function localeText(key, values, fallback) {
|
|
121
|
+
return {
|
|
122
|
+
key,
|
|
123
|
+
...(values ? { values } : {}),
|
|
124
|
+
...(fallback ? { fallback } : {}),
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
export function isLocalizedTextDef(value) {
|
|
128
|
+
return Boolean(value && typeof value === 'object' && typeof value.key === 'string');
|
|
129
|
+
}
|
|
130
|
+
export function resolveLocalizedText(text, options) {
|
|
131
|
+
if (text === undefined) {
|
|
132
|
+
return options?.fallback ?? '';
|
|
133
|
+
}
|
|
134
|
+
if (typeof text === 'string') {
|
|
135
|
+
return text;
|
|
136
|
+
}
|
|
137
|
+
return translateCli(text.key, text.values, {
|
|
138
|
+
locale: options?.locale,
|
|
139
|
+
fallback: text.fallback ?? options?.fallback,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
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 { readdir } from 'node:fs/promises';
|
|
10
|
+
import { join } from 'node:path';
|
|
11
|
+
/**
|
|
12
|
+
* Recursively collect command module paths under `commandsRoot` (e.g. `dist/commands` → `.js`, `src/commands` → `.ts`).
|
|
13
|
+
*/
|
|
14
|
+
export async function collectCommandModulePaths(commandsRoot, extension) {
|
|
15
|
+
const entries = await readdir(commandsRoot, { withFileTypes: true });
|
|
16
|
+
const files = [];
|
|
17
|
+
for (const ent of entries) {
|
|
18
|
+
const full = join(commandsRoot, ent.name);
|
|
19
|
+
if (ent.isDirectory()) {
|
|
20
|
+
files.push(...(await collectCommandModulePaths(full, extension)));
|
|
21
|
+
}
|
|
22
|
+
else if (ent.isFile() && ent.name.endsWith(extension)) {
|
|
23
|
+
files.push(full);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return files.sort();
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Map a path relative to `commands/` with `.js` / `.ts` to an oclif explicit-registry key.
|
|
30
|
+
* `api/resource/foo.js` → `api:resource:foo`; trailing `index` maps to the parent command.
|
|
31
|
+
*/
|
|
32
|
+
export function commandRelativePathToRegistryKey(relativePath) {
|
|
33
|
+
const normalized = relativePath.replace(/\\/g, '/').replace(/\.(js|ts)$/i, '');
|
|
34
|
+
const segments = normalized.split('/').filter(Boolean);
|
|
35
|
+
if (segments.at(-1) === 'index') {
|
|
36
|
+
segments.pop();
|
|
37
|
+
}
|
|
38
|
+
return segments.join(':');
|
|
39
|
+
}
|
|
@@ -0,0 +1,284 @@
|
|
|
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 fs from 'node:fs';
|
|
10
|
+
import fsp from 'node:fs/promises';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import { resolveCliHomeDir } from './cli-home.js';
|
|
13
|
+
import { DEFAULT_LOG_ENABLED, DEFAULT_LOG_RETENTION_DAYS, getCliConfigValue } from './cli-config.js';
|
|
14
|
+
const ACTIVE_LOG_FILE_ENV = 'NB_CLI_ACTIVE_LOG_FILE';
|
|
15
|
+
const ACTIVE_META_FILE_ENV = 'NB_CLI_ACTIVE_META_FILE';
|
|
16
|
+
const LOG_DIR_ENV = 'NB_CLI_LOG_DIR';
|
|
17
|
+
const LOG_DISABLED_ENV = 'NB_CLI_LOG_DISABLED';
|
|
18
|
+
const ANSI_ESCAPE_PATTERN = new RegExp(String.raw `\u001B\[[0-?]*[ -/]*[@-~]`, 'g');
|
|
19
|
+
const CLEANUP_STATE_FILE = '.cleanup-state.json';
|
|
20
|
+
const CLEANUP_INTERVAL_MS = 12 * 60 * 60 * 1000;
|
|
21
|
+
function isTruthyEnvFlag(value) {
|
|
22
|
+
const normalized = String(value ?? '').trim().toLowerCase();
|
|
23
|
+
return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
|
|
24
|
+
}
|
|
25
|
+
function pad(value) {
|
|
26
|
+
return String(value).padStart(2, '0');
|
|
27
|
+
}
|
|
28
|
+
function formatDateParts(date) {
|
|
29
|
+
const year = date.getFullYear();
|
|
30
|
+
const month = pad(date.getMonth() + 1);
|
|
31
|
+
const day = pad(date.getDate());
|
|
32
|
+
const hour = pad(date.getHours());
|
|
33
|
+
const minute = pad(date.getMinutes());
|
|
34
|
+
const second = pad(date.getSeconds());
|
|
35
|
+
return {
|
|
36
|
+
dayKey: `${year}-${month}-${day}`,
|
|
37
|
+
timestampKey: `${year}${month}${day}-${hour}${minute}${second}`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function sanitizeFileNameSegment(value) {
|
|
41
|
+
const normalized = value
|
|
42
|
+
.trim()
|
|
43
|
+
.replace(/[^a-zA-Z0-9._-]+/g, '-')
|
|
44
|
+
.replace(/-+/g, '-')
|
|
45
|
+
.replace(/^-|-$/g, '');
|
|
46
|
+
return normalized || 'command';
|
|
47
|
+
}
|
|
48
|
+
function resolveSessionDirName(sessionId) {
|
|
49
|
+
const trimmed = String(sessionId ?? '').trim();
|
|
50
|
+
if (!trimmed) {
|
|
51
|
+
return 'no-session';
|
|
52
|
+
}
|
|
53
|
+
return sanitizeFileNameSegment(trimmed);
|
|
54
|
+
}
|
|
55
|
+
export function stripAnsi(value) {
|
|
56
|
+
return value.replace(ANSI_ESCAPE_PATTERN, '');
|
|
57
|
+
}
|
|
58
|
+
export function shouldEnableCommandLog(env = process.env) {
|
|
59
|
+
return !isTruthyEnvFlag(env[LOG_DISABLED_ENV]);
|
|
60
|
+
}
|
|
61
|
+
export function resolveCommandLogDir(env = process.env) {
|
|
62
|
+
if (!shouldEnableCommandLog(env)) {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
const configured = String(env[LOG_DIR_ENV] ?? '').trim();
|
|
66
|
+
if (configured) {
|
|
67
|
+
return path.resolve(configured);
|
|
68
|
+
}
|
|
69
|
+
return path.join(resolveCliHomeDir(), 'logs');
|
|
70
|
+
}
|
|
71
|
+
export function getActiveCommandLogFile(env = process.env) {
|
|
72
|
+
const value = String(env[ACTIVE_LOG_FILE_ENV] ?? '').trim();
|
|
73
|
+
return value || undefined;
|
|
74
|
+
}
|
|
75
|
+
function readActiveCommandMetaFile(env = process.env) {
|
|
76
|
+
const value = String(env[ACTIVE_META_FILE_ENV] ?? '').trim();
|
|
77
|
+
return value || undefined;
|
|
78
|
+
}
|
|
79
|
+
export function hasActiveCommandLog(env = process.env) {
|
|
80
|
+
return Boolean(getActiveCommandLogFile(env));
|
|
81
|
+
}
|
|
82
|
+
export function sanitizeArgv(argv) {
|
|
83
|
+
const sensitiveFlags = new Set(['--token', '--access-token', '--password', '--secret']);
|
|
84
|
+
const sanitized = [];
|
|
85
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
86
|
+
const token = argv[index];
|
|
87
|
+
if (sensitiveFlags.has(token)) {
|
|
88
|
+
sanitized.push(token);
|
|
89
|
+
if (index + 1 < argv.length) {
|
|
90
|
+
sanitized.push('***');
|
|
91
|
+
index += 1;
|
|
92
|
+
}
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const matched = token.match(/^(--(?:token|access-token|password|secret))=(.*)$/);
|
|
96
|
+
if (matched) {
|
|
97
|
+
sanitized.push(`${matched[1]}=***`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
sanitized.push(token);
|
|
101
|
+
}
|
|
102
|
+
return sanitized;
|
|
103
|
+
}
|
|
104
|
+
export function getCommandLogCommandId(argv) {
|
|
105
|
+
const positional = argv.filter((token) => token && !token.startsWith('-'));
|
|
106
|
+
if (positional.length === 0) {
|
|
107
|
+
return 'command';
|
|
108
|
+
}
|
|
109
|
+
const commandPath = positional[0] === 'api' ? positional.slice(0, 3) : positional.slice(0, 2);
|
|
110
|
+
return sanitizeFileNameSegment(commandPath.join('-'));
|
|
111
|
+
}
|
|
112
|
+
function parseDateDirName(name) {
|
|
113
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(name)) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
const parsed = new Date(`${name}T00:00:00.000Z`);
|
|
117
|
+
return Number.isNaN(parsed.getTime()) ? undefined : parsed;
|
|
118
|
+
}
|
|
119
|
+
async function readCleanupState(filePath) {
|
|
120
|
+
try {
|
|
121
|
+
return JSON.parse(await fsp.readFile(filePath, 'utf8'));
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return {};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function writeCleanupState(filePath, state) {
|
|
128
|
+
await fsp.writeFile(filePath, `${JSON.stringify(state, null, 2)}\n`, 'utf8');
|
|
129
|
+
}
|
|
130
|
+
async function resolveCommandLogRetentionDays() {
|
|
131
|
+
const configured = await getCliConfigValue('log.retention-days').catch(() => String(DEFAULT_LOG_RETENTION_DAYS));
|
|
132
|
+
const parsed = Number.parseInt(String(configured).trim(), 10);
|
|
133
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
134
|
+
return DEFAULT_LOG_RETENTION_DAYS;
|
|
135
|
+
}
|
|
136
|
+
return parsed;
|
|
137
|
+
}
|
|
138
|
+
async function resolveCommandLogEnabled() {
|
|
139
|
+
const configured = await getCliConfigValue('log.enabled').catch(() => String(DEFAULT_LOG_ENABLED));
|
|
140
|
+
const normalized = String(configured).trim().toLowerCase();
|
|
141
|
+
if (normalized === 'false') {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
if (normalized === 'true') {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
return DEFAULT_LOG_ENABLED;
|
|
148
|
+
}
|
|
149
|
+
async function cleanupCommandLogs(rootDir, retentionDays, now = new Date()) {
|
|
150
|
+
if (retentionDays === 0) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const entries = await fsp.readdir(rootDir, { withFileTypes: true }).catch(() => []);
|
|
154
|
+
const cutoff = new Date(Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate()));
|
|
155
|
+
cutoff.setUTCDate(cutoff.getUTCDate() - retentionDays + 1);
|
|
156
|
+
for (const entry of entries) {
|
|
157
|
+
if (!entry.isDirectory()) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const dirDate = parseDateDirName(entry.name);
|
|
161
|
+
if (!dirDate || dirDate >= cutoff) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
await fsp.rm(path.join(rootDir, entry.name), { recursive: true, force: true });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
async function maybeCleanupCommandLogs(rootDir, env = process.env) {
|
|
168
|
+
await fsp.mkdir(rootDir, { recursive: true });
|
|
169
|
+
const stateFile = path.join(rootDir, CLEANUP_STATE_FILE);
|
|
170
|
+
const state = await readCleanupState(stateFile);
|
|
171
|
+
const lastCleanupAt = Date.parse(String(state.lastCleanupAt ?? ''));
|
|
172
|
+
if (!Number.isNaN(lastCleanupAt) && Date.now() - lastCleanupAt < CLEANUP_INTERVAL_MS) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const retentionDays = await resolveCommandLogRetentionDays();
|
|
176
|
+
await cleanupCommandLogs(rootDir, retentionDays);
|
|
177
|
+
await writeCleanupState(stateFile, { lastCleanupAt: new Date().toISOString() });
|
|
178
|
+
}
|
|
179
|
+
async function writeMetaFile(filePath, meta) {
|
|
180
|
+
await fsp.mkdir(path.dirname(filePath), { recursive: true });
|
|
181
|
+
await fsp.writeFile(filePath, `${JSON.stringify(meta, null, 2)}\n`, 'utf8');
|
|
182
|
+
}
|
|
183
|
+
function writeMetaFileSync(filePath, meta) {
|
|
184
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
185
|
+
fs.writeFileSync(filePath, `${JSON.stringify(meta, null, 2)}\n`, 'utf8');
|
|
186
|
+
}
|
|
187
|
+
export async function initCommandLogSession(options) {
|
|
188
|
+
const env = options.env ?? process.env;
|
|
189
|
+
if (!shouldEnableCommandLog(env)) {
|
|
190
|
+
return undefined;
|
|
191
|
+
}
|
|
192
|
+
const enabled = await resolveCommandLogEnabled().catch(() => DEFAULT_LOG_ENABLED);
|
|
193
|
+
if (!enabled) {
|
|
194
|
+
return undefined;
|
|
195
|
+
}
|
|
196
|
+
const rootDir = resolveCommandLogDir(env);
|
|
197
|
+
if (!rootDir) {
|
|
198
|
+
return undefined;
|
|
199
|
+
}
|
|
200
|
+
await maybeCleanupCommandLogs(rootDir, env).catch(() => undefined);
|
|
201
|
+
const startedAt = options.startedAt ?? new Date();
|
|
202
|
+
const { dayKey, timestampKey } = formatDateParts(startedAt);
|
|
203
|
+
const commandId = getCommandLogCommandId(options.argv);
|
|
204
|
+
const fileStem = `${timestampKey}-${commandId}-${options.pid ?? process.pid}`;
|
|
205
|
+
const sessionDirName = resolveSessionDirName(options.sessionId);
|
|
206
|
+
const logDir = path.join(rootDir, dayKey, sessionDirName);
|
|
207
|
+
const logFile = path.join(logDir, `${fileStem}.log`);
|
|
208
|
+
const metaFile = path.join(logDir, `${fileStem}.meta.json`);
|
|
209
|
+
const meta = {
|
|
210
|
+
command: commandId,
|
|
211
|
+
argv: sanitizeArgv(options.argv),
|
|
212
|
+
cwd: options.cwd,
|
|
213
|
+
pid: options.pid ?? process.pid,
|
|
214
|
+
ppid: options.ppid ?? process.ppid,
|
|
215
|
+
sessionId: options.sessionId,
|
|
216
|
+
startedAt: startedAt.toISOString(),
|
|
217
|
+
logFile,
|
|
218
|
+
cliVersion: options.cliVersion,
|
|
219
|
+
nodeVersion: options.nodeVersion,
|
|
220
|
+
platform: options.platform,
|
|
221
|
+
interactive: options.interactive,
|
|
222
|
+
verbose: options.verbose,
|
|
223
|
+
};
|
|
224
|
+
await fsp.mkdir(logDir, { recursive: true });
|
|
225
|
+
await fsp.writeFile(logFile, '', 'utf8');
|
|
226
|
+
await writeMetaFile(metaFile, meta);
|
|
227
|
+
env[ACTIVE_LOG_FILE_ENV] = logFile;
|
|
228
|
+
env[ACTIVE_META_FILE_ENV] = metaFile;
|
|
229
|
+
return {
|
|
230
|
+
logFile,
|
|
231
|
+
metaFile,
|
|
232
|
+
meta,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
export function finalizeCommandLogSessionSync(session, options = {}) {
|
|
236
|
+
if (!session) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const endedAt = options.endedAt ?? new Date();
|
|
240
|
+
const startedAtMs = Date.parse(session.meta.startedAt);
|
|
241
|
+
const nextMeta = {
|
|
242
|
+
...session.meta,
|
|
243
|
+
endedAt: endedAt.toISOString(),
|
|
244
|
+
durationMs: Number.isNaN(startedAtMs) ? undefined : Math.max(0, endedAt.getTime() - startedAtMs),
|
|
245
|
+
exitCode: options.exitCode ?? session.meta.exitCode,
|
|
246
|
+
signal: options.signal ?? session.meta.signal,
|
|
247
|
+
errorMessage: options.errorMessage ?? session.meta.errorMessage,
|
|
248
|
+
};
|
|
249
|
+
writeMetaFileSync(session.metaFile, nextMeta);
|
|
250
|
+
session.meta = nextMeta;
|
|
251
|
+
}
|
|
252
|
+
export function appendCommandLogChunk(chunk, env = process.env) {
|
|
253
|
+
const logFile = getActiveCommandLogFile(env);
|
|
254
|
+
if (!logFile) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const text = typeof chunk === 'string' ? chunk : Buffer.from(chunk).toString('utf8');
|
|
258
|
+
if (!text) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
fs.appendFileSync(logFile, stripAnsi(text), 'utf8');
|
|
262
|
+
}
|
|
263
|
+
export function installCommandLogWriteHooks(env = process.env) {
|
|
264
|
+
if (!hasActiveCommandLog(env)) {
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
const originalStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
268
|
+
const originalStderrWrite = process.stderr.write.bind(process.stderr);
|
|
269
|
+
process.stdout.write = ((chunk, encoding, callback) => {
|
|
270
|
+
appendCommandLogChunk(chunk, env);
|
|
271
|
+
return originalStdoutWrite(chunk, encoding, callback);
|
|
272
|
+
});
|
|
273
|
+
process.stderr.write = ((chunk, encoding, callback) => {
|
|
274
|
+
appendCommandLogChunk(chunk, env);
|
|
275
|
+
return originalStderrWrite(chunk, encoding, callback);
|
|
276
|
+
});
|
|
277
|
+
return () => {
|
|
278
|
+
process.stdout.write = originalStdoutWrite;
|
|
279
|
+
process.stderr.write = originalStderrWrite;
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
export function getActiveCommandMetaFile(env = process.env) {
|
|
283
|
+
return readActiveCommandMetaFile(env);
|
|
284
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
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
|
+
const mysqlLowerCaseTableNamesCache = new Map();
|
|
14
|
+
function trimPromptValue(value) {
|
|
15
|
+
return String(value ?? '').trim();
|
|
16
|
+
}
|
|
17
|
+
export function readExternalDbConnectionConfig(values) {
|
|
18
|
+
const builtinDb = values.builtinDb === undefined ? true : Boolean(values.builtinDb);
|
|
19
|
+
if (builtinDb) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
const dialect = trimPromptValue(values.dbDialect || 'postgres');
|
|
23
|
+
if (dialect !== 'postgres' && dialect !== 'kingbase' && dialect !== 'mysql' && dialect !== 'mariadb') {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
const host = trimPromptValue(values.dbHost);
|
|
27
|
+
const portText = trimPromptValue(values.dbPort);
|
|
28
|
+
const database = trimPromptValue(values.dbDatabase);
|
|
29
|
+
const user = trimPromptValue(values.dbUser);
|
|
30
|
+
const password = String(values.dbPassword ?? '');
|
|
31
|
+
if (!host || !portText || !database || !user || !password) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
if (validateTcpPort(portText)) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
return {
|
|
38
|
+
dialect,
|
|
39
|
+
host,
|
|
40
|
+
port: Number.parseInt(portText, 10),
|
|
41
|
+
database,
|
|
42
|
+
user,
|
|
43
|
+
password,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function formatDbCheckAddress(config) {
|
|
47
|
+
return `${config.host}:${config.port}/${config.database}`;
|
|
48
|
+
}
|
|
49
|
+
function buildValidationCacheKey(config) {
|
|
50
|
+
return JSON.stringify(config);
|
|
51
|
+
}
|
|
52
|
+
function formatDbConnectionError(config, error) {
|
|
53
|
+
const maybeError = error;
|
|
54
|
+
const code = String(maybeError?.code ?? '').trim().toUpperCase();
|
|
55
|
+
const errno = typeof maybeError?.errno === 'number' ? maybeError.errno : undefined;
|
|
56
|
+
const rawMessage = String(maybeError?.message || maybeError?.sqlMessage || error || '').trim();
|
|
57
|
+
if (code === 'ECONNREFUSED' || code === 'ENOTFOUND' || code === 'EHOSTUNREACH' || code === 'ECONNRESET') {
|
|
58
|
+
return translateCli('validators.dbConnection.unreachable', {
|
|
59
|
+
host: config.host,
|
|
60
|
+
port: config.port,
|
|
61
|
+
details: rawMessage,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (code === 'ETIMEDOUT') {
|
|
65
|
+
return translateCli('validators.dbConnection.timeout', {
|
|
66
|
+
host: config.host,
|
|
67
|
+
port: config.port,
|
|
68
|
+
seconds: Math.ceil(DB_CONNECTION_TIMEOUT_MS / 1000),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
if (code === '28P01' || code === '28000' || code === 'ER_ACCESS_DENIED_ERROR' || errno === 1045) {
|
|
72
|
+
return translateCli('validators.dbConnection.authenticationFailed', {
|
|
73
|
+
user: config.user,
|
|
74
|
+
database: config.database,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (code === '3D000' || code === 'ER_BAD_DB_ERROR' || errno === 1049) {
|
|
78
|
+
return translateCli('validators.dbConnection.databaseNotFound', {
|
|
79
|
+
database: config.database,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
return translateCli('validators.dbConnection.connectionFailed', {
|
|
83
|
+
details: rawMessage || code || String(error),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
async function checkPostgresFamilyConnection(config) {
|
|
87
|
+
const { default: pg } = await import('pg');
|
|
88
|
+
const client = new pg.Client({
|
|
89
|
+
host: config.host,
|
|
90
|
+
port: config.port,
|
|
91
|
+
user: config.user,
|
|
92
|
+
password: config.password,
|
|
93
|
+
database: config.database,
|
|
94
|
+
connectionTimeoutMillis: DB_CONNECTION_TIMEOUT_MS,
|
|
95
|
+
});
|
|
96
|
+
try {
|
|
97
|
+
await client.connect();
|
|
98
|
+
await client.query('SELECT 1');
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
await Promise.resolve(client.end()).catch(() => undefined);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async function checkMysqlFamilyConnection(config) {
|
|
105
|
+
const { default: mysql } = await import('mysql2/promise');
|
|
106
|
+
const connection = await mysql.createConnection({
|
|
107
|
+
host: config.host,
|
|
108
|
+
port: config.port,
|
|
109
|
+
user: config.user,
|
|
110
|
+
password: config.password,
|
|
111
|
+
database: config.database,
|
|
112
|
+
connectTimeout: DB_CONNECTION_TIMEOUT_MS,
|
|
113
|
+
});
|
|
114
|
+
try {
|
|
115
|
+
await connection.query('SELECT 1');
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
await Promise.resolve(connection.end()).catch(() => undefined);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async function readMysqlFamilyLowerCaseTableNames(config) {
|
|
122
|
+
const { default: mysql } = await import('mysql2/promise');
|
|
123
|
+
const connection = await mysql.createConnection({
|
|
124
|
+
host: config.host,
|
|
125
|
+
port: config.port,
|
|
126
|
+
user: config.user,
|
|
127
|
+
password: config.password,
|
|
128
|
+
database: config.database,
|
|
129
|
+
connectTimeout: DB_CONNECTION_TIMEOUT_MS,
|
|
130
|
+
});
|
|
131
|
+
try {
|
|
132
|
+
const [rows] = await connection.query(`SHOW VARIABLES LIKE 'lower_case_table_names'`);
|
|
133
|
+
if (!Array.isArray(rows)) {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
for (const row of rows) {
|
|
137
|
+
if (!row || typeof row !== 'object') {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const value = String(row.Value ?? '').trim();
|
|
141
|
+
if (value === '0' || value === '1' || value === '2') {
|
|
142
|
+
return value;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return undefined;
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
await Promise.resolve(connection.end()).catch(() => undefined);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async function performExternalDbConnectionCheck(config) {
|
|
152
|
+
try {
|
|
153
|
+
switch (config.dialect) {
|
|
154
|
+
case 'postgres':
|
|
155
|
+
case 'kingbase': {
|
|
156
|
+
await checkPostgresFamilyConnection(config);
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
case 'mysql':
|
|
160
|
+
case 'mariadb': {
|
|
161
|
+
await checkMysqlFamilyConnection(config);
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
return formatDbConnectionError(config, error);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
export async function checkExternalDbConnection(config) {
|
|
171
|
+
const cacheKey = buildValidationCacheKey(config);
|
|
172
|
+
const cached = externalDbValidationCache.get(cacheKey);
|
|
173
|
+
if (cached) {
|
|
174
|
+
return await cached;
|
|
175
|
+
}
|
|
176
|
+
const pending = performExternalDbConnectionCheck(config);
|
|
177
|
+
externalDbValidationCache.set(cacheKey, pending);
|
|
178
|
+
return await pending;
|
|
179
|
+
}
|
|
180
|
+
async function readMysqlLowerCaseTableNamesMode(config) {
|
|
181
|
+
if (config.dialect !== 'mysql' && config.dialect !== 'mariadb') {
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
const cacheKey = buildValidationCacheKey(config);
|
|
185
|
+
const cached = mysqlLowerCaseTableNamesCache.get(cacheKey);
|
|
186
|
+
if (cached) {
|
|
187
|
+
return await cached;
|
|
188
|
+
}
|
|
189
|
+
const pending = readMysqlFamilyLowerCaseTableNames(config);
|
|
190
|
+
mysqlLowerCaseTableNamesCache.set(cacheKey, pending);
|
|
191
|
+
return await pending;
|
|
192
|
+
}
|
|
193
|
+
export async function validateExternalDbConfig(values) {
|
|
194
|
+
const config = readExternalDbConnectionConfig(values);
|
|
195
|
+
if (!config) {
|
|
196
|
+
return undefined;
|
|
197
|
+
}
|
|
198
|
+
return await checkExternalDbConnection(config);
|
|
199
|
+
}
|
|
200
|
+
export async function validateMysqlLowerCaseTableNamesCompatibility(values) {
|
|
201
|
+
const config = readExternalDbConnectionConfig(values);
|
|
202
|
+
if (!config || (config.dialect !== 'mysql' && config.dialect !== 'mariadb')) {
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
try {
|
|
206
|
+
const mode = await readMysqlLowerCaseTableNamesMode(config);
|
|
207
|
+
if (mode === '1' && values.dbUnderscored !== true) {
|
|
208
|
+
return translateCli('validators.dbConnection.lowerCaseTableNamesRequiresUnderscored');
|
|
209
|
+
}
|
|
210
|
+
return undefined;
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
return formatDbConnectionError(config, error);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
export function clearExternalDbValidationCache() {
|
|
217
|
+
externalDbValidationCache.clear();
|
|
218
|
+
mysqlLowerCaseTableNamesCache.clear();
|
|
219
|
+
}
|