@nocobase/cli 2.1.0-alpha.20 → 2.1.0-alpha.22
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 +256 -89
- package/README.zh-CN.md +332 -0
- package/bin/run.js +21 -2
- package/dist/commands/build.js +7 -1
- package/dist/commands/db/logs.js +85 -0
- package/dist/commands/db/ps.js +60 -0
- package/dist/commands/db/shared.js +81 -0
- package/dist/commands/db/start.js +55 -7
- package/dist/commands/db/stop.js +70 -0
- package/dist/commands/dev.js +112 -21
- package/dist/commands/down.js +193 -0
- package/dist/commands/download.js +633 -183
- package/dist/commands/env/add.js +260 -131
- package/dist/commands/env/auth.js +9 -8
- package/dist/commands/init.js +723 -103
- package/dist/commands/install.js +1702 -565
- package/dist/commands/logs.js +90 -0
- package/dist/commands/pm/disable.js +35 -3
- package/dist/commands/pm/enable.js +35 -3
- package/dist/commands/pm/list.js +37 -4
- package/dist/commands/prompts-stages.js +150 -0
- package/dist/commands/prompts-test.js +181 -0
- package/dist/commands/ps.js +116 -0
- package/dist/commands/start.js +171 -15
- package/dist/commands/stop.js +90 -0
- package/dist/commands/upgrade.js +559 -11
- package/dist/lib/api-client.js +49 -5
- package/dist/lib/app-runtime.js +142 -0
- package/dist/lib/auth-store.js +44 -3
- package/dist/lib/bootstrap.js +7 -3
- package/dist/lib/cli-locale.js +115 -0
- package/dist/lib/env-auth.js +427 -82
- package/dist/lib/prompt-catalog.js +574 -0
- package/dist/lib/prompt-validators.js +185 -0
- package/dist/lib/prompt-web-ui.js +2061 -0
- package/dist/lib/run-npm.js +71 -7
- package/dist/lib/runtime-generator.js +12 -1
- package/dist/locale/en-US.json +282 -0
- package/dist/locale/zh-CN.json +282 -0
- package/package.json +5 -4
- package/dist/commands/restart.js +0 -32
- package/dist/lib/init-browser-wizard.js +0 -431
|
@@ -0,0 +1,142 @@
|
|
|
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 path from 'node:path';
|
|
10
|
+
import { getEnv, loadAuthConfig } from './auth-store.js';
|
|
11
|
+
import { commandOutput, commandSucceeds, run, runNocoBaseCommand } from './run-npm.js';
|
|
12
|
+
const DOCKER_APP_WORKDIR = '/app/nocobase';
|
|
13
|
+
function sanitizeDockerResourceName(value) {
|
|
14
|
+
const normalized = value
|
|
15
|
+
.trim()
|
|
16
|
+
.toLowerCase()
|
|
17
|
+
.replace(/[^a-z0-9_.-]+/g, '-')
|
|
18
|
+
.replace(/-+/g, '-')
|
|
19
|
+
.replace(/^-+|-+$/g, '');
|
|
20
|
+
return normalized || 'nocobase';
|
|
21
|
+
}
|
|
22
|
+
export function defaultWorkspaceName(cwd = process.cwd()) {
|
|
23
|
+
return sanitizeDockerResourceName(`nb-${path.basename(cwd)}`);
|
|
24
|
+
}
|
|
25
|
+
export function buildDockerAppContainerName(envName, workspaceName) {
|
|
26
|
+
const workspace = workspaceName?.trim() || defaultWorkspaceName();
|
|
27
|
+
return sanitizeDockerResourceName(`${workspace}-${envName}-app`);
|
|
28
|
+
}
|
|
29
|
+
export function buildDockerDbContainerName(envName, dbDialect, workspaceName) {
|
|
30
|
+
const workspace = workspaceName?.trim() || defaultWorkspaceName();
|
|
31
|
+
const dialect = dbDialect.trim() || 'postgres';
|
|
32
|
+
return sanitizeDockerResourceName(`${workspace}-${envName}-${dialect}`);
|
|
33
|
+
}
|
|
34
|
+
function normalizeEnvSource(env) {
|
|
35
|
+
const source = String(env.config.source ?? '').trim();
|
|
36
|
+
if (source === 'docker' || source === 'npm' || source === 'git') {
|
|
37
|
+
return source;
|
|
38
|
+
}
|
|
39
|
+
if (env.config.appRootPath) {
|
|
40
|
+
return 'local';
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
export async function resolveManagedAppRuntime(envName) {
|
|
45
|
+
const config = await loadAuthConfig();
|
|
46
|
+
const env = await getEnv(envName, { config });
|
|
47
|
+
if (!env) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
const resolvedName = env.name || envName?.trim() || config.currentEnv || 'default';
|
|
51
|
+
const source = normalizeEnvSource(env);
|
|
52
|
+
const workspaceName = config.name?.trim() || defaultWorkspaceName();
|
|
53
|
+
if (source === 'docker') {
|
|
54
|
+
return {
|
|
55
|
+
kind: 'docker',
|
|
56
|
+
env,
|
|
57
|
+
envName: resolvedName,
|
|
58
|
+
source,
|
|
59
|
+
workspaceName,
|
|
60
|
+
containerName: buildDockerAppContainerName(resolvedName, workspaceName),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (env.config.appRootPath) {
|
|
64
|
+
return {
|
|
65
|
+
kind: 'local',
|
|
66
|
+
env,
|
|
67
|
+
envName: resolvedName,
|
|
68
|
+
source: source === 'git' ? 'git' : source === 'npm' ? 'npm' : 'local',
|
|
69
|
+
projectRoot: env.appRootPath,
|
|
70
|
+
workspaceName,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
kind: 'remote',
|
|
75
|
+
env,
|
|
76
|
+
envName: resolvedName,
|
|
77
|
+
source,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export function formatMissingManagedAppEnvMessage(envName) {
|
|
81
|
+
const requested = String(envName ?? '').trim();
|
|
82
|
+
if (requested) {
|
|
83
|
+
return [
|
|
84
|
+
`Env "${requested}" is not configured in this workspace.`,
|
|
85
|
+
`If you want to create a new NocoBase AI environment, run \`nb init --env ${requested}\` first.`,
|
|
86
|
+
].join('\n');
|
|
87
|
+
}
|
|
88
|
+
return 'No NocoBase env is configured yet. Run `nb init` to create one first.';
|
|
89
|
+
}
|
|
90
|
+
export async function runLocalNocoBaseCommand(runtime, args, options) {
|
|
91
|
+
await runNocoBaseCommand(args, {
|
|
92
|
+
cwd: runtime.projectRoot,
|
|
93
|
+
env: runtime.env.envVars,
|
|
94
|
+
stdio: options?.stdio,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
export async function dockerContainerExists(containerName) {
|
|
98
|
+
return await commandSucceeds('docker', ['container', 'inspect', containerName]);
|
|
99
|
+
}
|
|
100
|
+
export async function dockerContainerIsRunning(containerName) {
|
|
101
|
+
try {
|
|
102
|
+
const output = await commandOutput('docker', ['inspect', '--format', '{{.State.Running}}', containerName], { errorName: 'docker inspect' });
|
|
103
|
+
return output.trim() === 'true';
|
|
104
|
+
}
|
|
105
|
+
catch (_error) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
export async function startDockerContainer(containerName, options) {
|
|
110
|
+
const exists = await dockerContainerExists(containerName);
|
|
111
|
+
if (!exists) {
|
|
112
|
+
throw new Error(`Docker app container "${containerName}" does not exist.`);
|
|
113
|
+
}
|
|
114
|
+
if (await dockerContainerIsRunning(containerName)) {
|
|
115
|
+
return 'already-running';
|
|
116
|
+
}
|
|
117
|
+
await run('docker', ['start', containerName], {
|
|
118
|
+
errorName: 'docker start',
|
|
119
|
+
stdio: options?.stdio,
|
|
120
|
+
});
|
|
121
|
+
return 'started';
|
|
122
|
+
}
|
|
123
|
+
export async function stopDockerContainer(containerName, options) {
|
|
124
|
+
const exists = await dockerContainerExists(containerName);
|
|
125
|
+
if (!exists) {
|
|
126
|
+
throw new Error(`Docker app container "${containerName}" does not exist.`);
|
|
127
|
+
}
|
|
128
|
+
if (!(await dockerContainerIsRunning(containerName))) {
|
|
129
|
+
return 'already-stopped';
|
|
130
|
+
}
|
|
131
|
+
await run('docker', ['stop', containerName], {
|
|
132
|
+
errorName: 'docker stop',
|
|
133
|
+
stdio: options?.stdio,
|
|
134
|
+
});
|
|
135
|
+
return 'stopped';
|
|
136
|
+
}
|
|
137
|
+
export async function runDockerNocoBaseCommand(containerName, args) {
|
|
138
|
+
await startDockerContainer(containerName);
|
|
139
|
+
await run('docker', ['exec', '-w', DOCKER_APP_WORKDIR, containerName, 'yarn', 'nocobase', ...args], {
|
|
140
|
+
errorName: 'docker exec',
|
|
141
|
+
});
|
|
142
|
+
}
|
package/dist/lib/auth-store.js
CHANGED
|
@@ -21,6 +21,7 @@ export async function loadAuthConfig(options = {}) {
|
|
|
21
21
|
const content = await fs.readFile(getConfigFile(options), 'utf8');
|
|
22
22
|
const parsed = JSON.parse(content);
|
|
23
23
|
return {
|
|
24
|
+
name: parsed.name || parsed.dockerResourcePrefix,
|
|
24
25
|
currentEnv: parsed.currentEnv || 'default',
|
|
25
26
|
envs: parsed.envs || {},
|
|
26
27
|
};
|
|
@@ -53,6 +54,17 @@ export async function setCurrentEnv(envName, options = {}) {
|
|
|
53
54
|
config.currentEnv = envName;
|
|
54
55
|
await saveAuthConfig(config, options);
|
|
55
56
|
}
|
|
57
|
+
export async function ensureWorkspaceName(defaultName, options = {}) {
|
|
58
|
+
const config = await loadAuthConfig(options);
|
|
59
|
+
const existing = config.name?.trim();
|
|
60
|
+
if (existing) {
|
|
61
|
+
return existing;
|
|
62
|
+
}
|
|
63
|
+
const next = defaultName.trim();
|
|
64
|
+
config.name = next;
|
|
65
|
+
await saveAuthConfig(config, options);
|
|
66
|
+
return next;
|
|
67
|
+
}
|
|
56
68
|
export class Env {
|
|
57
69
|
config;
|
|
58
70
|
constructor(config = {}) {
|
|
@@ -87,11 +99,40 @@ export class Env {
|
|
|
87
99
|
}
|
|
88
100
|
return path.resolve(process.cwd(), storagePath);
|
|
89
101
|
}
|
|
102
|
+
get appPort() {
|
|
103
|
+
return this.config.appPort;
|
|
104
|
+
}
|
|
105
|
+
get envVars() {
|
|
106
|
+
const out = {
|
|
107
|
+
STORAGE_PATH: this.storagePath,
|
|
108
|
+
};
|
|
109
|
+
const put = (key, value) => {
|
|
110
|
+
if (value === undefined || value === null) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
out[key] = String(value);
|
|
114
|
+
};
|
|
115
|
+
put('APP_PORT', this.appPort);
|
|
116
|
+
put('APP_KEY', this.config.appKey);
|
|
117
|
+
put('TZ', this.config.timezone);
|
|
118
|
+
put('DB_DIALECT', this.config.dbDialect);
|
|
119
|
+
put('DB_HOST', this.config.dbHost);
|
|
120
|
+
put('DB_PORT', this.config.dbPort);
|
|
121
|
+
put('DB_DATABASE', this.config.dbDatabase);
|
|
122
|
+
put('DB_USER', this.config.dbUser);
|
|
123
|
+
put('DB_PASSWORD', this.config.dbPassword);
|
|
124
|
+
return out;
|
|
125
|
+
}
|
|
90
126
|
}
|
|
91
127
|
export async function getEnv(envName, options = {}) {
|
|
92
|
-
const config =
|
|
93
|
-
const
|
|
94
|
-
|
|
128
|
+
const { config: snapshot, ...loadOptions } = options;
|
|
129
|
+
const config = snapshot ?? (await loadAuthConfig(loadOptions));
|
|
130
|
+
const resolved = envName?.trim() || config.currentEnv || 'default';
|
|
131
|
+
const envConfig = config.envs[resolved];
|
|
132
|
+
if (!envConfig) {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
return new Env({ ...envConfig, name: resolved });
|
|
95
136
|
}
|
|
96
137
|
function areAuthConfigsEquivalent(left, right) {
|
|
97
138
|
if (!left && !right) {
|
package/dist/lib/bootstrap.js
CHANGED
|
@@ -354,9 +354,13 @@ export async function updateEnvRuntime(options) {
|
|
|
354
354
|
});
|
|
355
355
|
if (!baseUrl) {
|
|
356
356
|
throw new Error([
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
357
|
+
env
|
|
358
|
+
? `Env "${envName}" is missing a base URL.`
|
|
359
|
+
: `Env "${envName}" is not configured. Run \`nb env add ${envName}\` first.`,
|
|
360
|
+
env ? 'Update it with `nb env add <name> --base-url <url>` first.' : '',
|
|
361
|
+
]
|
|
362
|
+
.filter(Boolean)
|
|
363
|
+
.join('\n'));
|
|
360
364
|
}
|
|
361
365
|
updateTask('Loading command runtime...');
|
|
362
366
|
try {
|
|
@@ -0,0 +1,115 @@
|
|
|
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
|
+
export const SUPPORTED_CLI_LOCALES = ['en-US', 'zh-CN'];
|
|
11
|
+
export const CLI_LOCALE_FLAG_OPTIONS = [...SUPPORTED_CLI_LOCALES];
|
|
12
|
+
export const CLI_LOCALE_FLAG_DESCRIPTION = 'Language for CLI prompts and the local setup UI.';
|
|
13
|
+
const DEFAULT_CLI_LOCALE = 'en-US';
|
|
14
|
+
const localeCache = {};
|
|
15
|
+
function normalizeCliLocale(value) {
|
|
16
|
+
const raw = String(value ?? '').trim();
|
|
17
|
+
if (!raw) {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
const normalized = raw.replace(/\..*$/, '').replace(/_/g, '-').toLowerCase();
|
|
21
|
+
if (normalized === 'zh' || normalized.startsWith('zh-')) {
|
|
22
|
+
return 'zh-CN';
|
|
23
|
+
}
|
|
24
|
+
if (normalized === 'en' || normalized.startsWith('en-')) {
|
|
25
|
+
return 'en-US';
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
function loadLocaleMessages(locale) {
|
|
30
|
+
if (localeCache[locale]) {
|
|
31
|
+
return localeCache[locale];
|
|
32
|
+
}
|
|
33
|
+
const fileUrl = new URL(`../locale/${locale}.json`, import.meta.url);
|
|
34
|
+
const parsed = JSON.parse(readFileSync(fileUrl, 'utf8'));
|
|
35
|
+
localeCache[locale] = parsed;
|
|
36
|
+
return parsed;
|
|
37
|
+
}
|
|
38
|
+
function getPathValue(input, path) {
|
|
39
|
+
let current = input;
|
|
40
|
+
for (const part of path.split('.')) {
|
|
41
|
+
if (!current || typeof current !== 'object' || !Object.prototype.hasOwnProperty.call(current, part)) {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
current = current[part];
|
|
45
|
+
}
|
|
46
|
+
return current;
|
|
47
|
+
}
|
|
48
|
+
function interpolateTemplate(template, values) {
|
|
49
|
+
return template.replace(/{{\s*([\w.]+)\s*}}/g, (_match, key) => {
|
|
50
|
+
const value = getPathValue(values, key);
|
|
51
|
+
return value === undefined || value === null ? '' : String(value);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
export function detectCliLocale() {
|
|
55
|
+
const candidates = [
|
|
56
|
+
process.env.NB_LOCALE,
|
|
57
|
+
process.env.LC_ALL,
|
|
58
|
+
process.env.LC_MESSAGES,
|
|
59
|
+
process.env.LANG,
|
|
60
|
+
Intl.DateTimeFormat().resolvedOptions().locale,
|
|
61
|
+
];
|
|
62
|
+
for (const candidate of candidates) {
|
|
63
|
+
const locale = normalizeCliLocale(candidate);
|
|
64
|
+
if (locale) {
|
|
65
|
+
return locale;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return DEFAULT_CLI_LOCALE;
|
|
69
|
+
}
|
|
70
|
+
export function resolveCliLocale(preferred) {
|
|
71
|
+
return normalizeCliLocale(preferred) ?? detectCliLocale();
|
|
72
|
+
}
|
|
73
|
+
export function applyCliLocale(preferred) {
|
|
74
|
+
const locale = resolveCliLocale(preferred);
|
|
75
|
+
process.env.NB_LOCALE = locale;
|
|
76
|
+
return locale;
|
|
77
|
+
}
|
|
78
|
+
export function createCliTranslate(preferred) {
|
|
79
|
+
const locale = resolveCliLocale(preferred);
|
|
80
|
+
return (key, values, fallback) => {
|
|
81
|
+
const messages = loadLocaleMessages(locale);
|
|
82
|
+
const template = getPathValue(messages, key);
|
|
83
|
+
if (typeof template !== 'string') {
|
|
84
|
+
return interpolateTemplate(fallback ?? key, values);
|
|
85
|
+
}
|
|
86
|
+
return interpolateTemplate(template, values);
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export function translateCli(key, values, options) {
|
|
90
|
+
return createCliTranslate(options?.locale)(key, values, options?.fallback);
|
|
91
|
+
}
|
|
92
|
+
export function localeText(key, values, fallback) {
|
|
93
|
+
return {
|
|
94
|
+
key,
|
|
95
|
+
...(values ? { values } : {}),
|
|
96
|
+
...(fallback ? { fallback } : {}),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
export function isLocalizedTextDef(value) {
|
|
100
|
+
return Boolean(value
|
|
101
|
+
&& typeof value === 'object'
|
|
102
|
+
&& typeof value.key === 'string');
|
|
103
|
+
}
|
|
104
|
+
export function resolveLocalizedText(text, options) {
|
|
105
|
+
if (text === undefined) {
|
|
106
|
+
return options?.fallback ?? '';
|
|
107
|
+
}
|
|
108
|
+
if (typeof text === 'string') {
|
|
109
|
+
return text;
|
|
110
|
+
}
|
|
111
|
+
return translateCli(text.key, text.values, {
|
|
112
|
+
locale: options?.locale,
|
|
113
|
+
fallback: text.fallback ?? options?.fallback,
|
|
114
|
+
});
|
|
115
|
+
}
|