@nocobase/cli 2.1.0-beta.33 → 2.1.0-beta.34
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/app/down.js +10 -13
- package/dist/commands/app/logs.js +0 -1
- package/dist/commands/app/restart.js +63 -2
- package/dist/commands/app/start.js +41 -17
- package/dist/commands/app/stop.js +0 -1
- package/dist/commands/app/upgrade.js +9 -4
- package/dist/commands/env/add.js +3 -4
- package/dist/commands/env/auth.js +3 -2
- package/dist/commands/env/remove.js +38 -13
- package/dist/commands/env/update.js +9 -2
- package/dist/commands/examples/prompts-stages.js +4 -4
- package/dist/commands/examples/prompts-test.js +4 -4
- package/dist/commands/init.js +38 -31
- package/dist/commands/install.js +100 -63
- package/dist/commands/license/activate.js +66 -64
- package/dist/commands/license/id.js +0 -1
- package/dist/commands/license/plugins/clean.js +0 -1
- package/dist/commands/license/plugins/list.js +0 -1
- package/dist/commands/license/plugins/sync.js +0 -1
- package/dist/commands/license/shared.js +3 -3
- package/dist/commands/license/status.js +0 -1
- package/dist/commands/plugin/disable.js +0 -1
- package/dist/commands/plugin/enable.js +0 -1
- package/dist/commands/plugin/list.js +0 -1
- package/dist/commands/self/update.js +12 -3
- package/dist/commands/skills/install.js +12 -3
- package/dist/commands/skills/remove.js +12 -3
- package/dist/commands/skills/update.js +12 -3
- package/dist/commands/source/dev.js +0 -1
- package/dist/commands/source/download.js +29 -17
- package/dist/lib/app-managed-resources.js +8 -2
- package/dist/lib/bootstrap.js +12 -3
- package/dist/lib/db-connection-check.js +3 -23
- package/dist/lib/docker-env-file.js +52 -0
- package/dist/lib/env-auth.js +4 -3
- package/dist/lib/env-config.js +1 -0
- package/dist/lib/env-guard.js +8 -7
- package/dist/lib/generated-command.js +0 -1
- package/dist/lib/inquirer-theme.js +17 -0
- package/dist/lib/inquirer.js +244 -0
- package/dist/lib/object-utils.js +76 -0
- package/dist/lib/prompt-catalog-core.js +185 -0
- package/dist/lib/prompt-catalog-terminal.js +375 -0
- package/dist/lib/prompt-catalog.js +2 -573
- package/dist/lib/prompt-validators.js +56 -1
- package/dist/lib/resource-command.js +0 -1
- package/dist/lib/skills-manager.js +75 -11
- package/dist/lib/startup-update.js +12 -8
- package/dist/lib/ui.js +28 -51
- package/dist/locale/en-US.json +8 -3
- package/dist/locale/zh-CN.json +8 -3
- package/package.json +7 -5
package/dist/lib/bootstrap.js
CHANGED
|
@@ -6,12 +6,13 @@
|
|
|
6
6
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
|
+
import { confirm } from "./inquirer.js";
|
|
9
10
|
import { getCurrentEnvName, getEnv, setEnvRuntime, updateEnvConnection } from './auth-store.js';
|
|
10
11
|
import { resolveAccessToken } from './env-auth.js';
|
|
11
12
|
import { fetchWithPreservedAuthRedirect } from './http-request.js';
|
|
12
13
|
import { generateRuntime } from './runtime-generator.js';
|
|
13
14
|
import { hasRuntimeSync, saveRuntime } from './runtime-store.js';
|
|
14
|
-
import {
|
|
15
|
+
import { printInfo, printVerbose, printWarningBlock, setVerboseMode, stopTask, updateTask } from './ui.js';
|
|
15
16
|
const APP_RETRY_INTERVAL = 2000;
|
|
16
17
|
const APP_RETRY_TIMEOUT = 120000;
|
|
17
18
|
function readFlag(argv, name) {
|
|
@@ -186,7 +187,15 @@ async function waitForSwaggerSchema(baseUrl, token, role) {
|
|
|
186
187
|
return await requestJson(swaggerUrl, { token, role });
|
|
187
188
|
}
|
|
188
189
|
async function confirmEnableApiDoc() {
|
|
189
|
-
|
|
190
|
+
try {
|
|
191
|
+
return await confirm({
|
|
192
|
+
message: 'Enable the API documentation plugin now?',
|
|
193
|
+
default: false,
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
190
199
|
}
|
|
191
200
|
async function fetchSwaggerSchema(baseUrl, token, role, context = {}, options = {}) {
|
|
192
201
|
let response = options.retryAppAvailability === false
|
|
@@ -335,7 +344,7 @@ export async function ensureRuntimeFromArgv(argv, options) {
|
|
|
335
344
|
}
|
|
336
345
|
stopTask();
|
|
337
346
|
const message = error instanceof Error ? error.message : String(error);
|
|
338
|
-
printWarningBlock(`Unable to load runtime commands. Showing built-in help instead.\
|
|
347
|
+
printWarningBlock(`Unable to load runtime commands. Showing built-in help instead.\n\n${message}`);
|
|
339
348
|
}
|
|
340
349
|
}
|
|
341
350
|
export async function updateEnvRuntime(options) {
|
|
@@ -100,7 +100,7 @@ async function checkPostgresFamilyConnection(config) {
|
|
|
100
100
|
await Promise.resolve(client.end()).catch(() => undefined);
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
|
-
async function
|
|
103
|
+
async function checkMysqlFamilyConnection(config) {
|
|
104
104
|
const { default: mysql } = await import('mysql2/promise');
|
|
105
105
|
const connection = await mysql.createConnection({
|
|
106
106
|
host: config.host,
|
|
@@ -117,23 +117,6 @@ async function checkMysqlConnection(config) {
|
|
|
117
117
|
await Promise.resolve(connection.end()).catch(() => undefined);
|
|
118
118
|
}
|
|
119
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
120
|
async function performExternalDbConnectionCheck(config) {
|
|
138
121
|
try {
|
|
139
122
|
switch (config.dialect) {
|
|
@@ -142,12 +125,9 @@ async function performExternalDbConnectionCheck(config) {
|
|
|
142
125
|
await checkPostgresFamilyConnection(config);
|
|
143
126
|
return undefined;
|
|
144
127
|
}
|
|
145
|
-
case 'mysql':
|
|
146
|
-
await checkMysqlConnection(config);
|
|
147
|
-
return undefined;
|
|
148
|
-
}
|
|
128
|
+
case 'mysql':
|
|
149
129
|
case 'mariadb': {
|
|
150
|
-
await
|
|
130
|
+
await checkMysqlFamilyConnection(config);
|
|
151
131
|
return undefined;
|
|
152
132
|
}
|
|
153
133
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
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 { access } from 'node:fs/promises';
|
|
10
|
+
import { resolveConfiguredEnvPath } from './cli-home.js';
|
|
11
|
+
function trimValue(value) {
|
|
12
|
+
const text = String(value ?? '').trim();
|
|
13
|
+
return text || undefined;
|
|
14
|
+
}
|
|
15
|
+
export function defaultDockerEnvFilePath(envName) {
|
|
16
|
+
return `${envName}/.env`;
|
|
17
|
+
}
|
|
18
|
+
export function resolveConfiguredDockerEnvFilePath(envName, config) {
|
|
19
|
+
return trimValue(config?.envFile) || defaultDockerEnvFilePath(envName);
|
|
20
|
+
}
|
|
21
|
+
export function hasExplicitDockerEnvFile(config) {
|
|
22
|
+
return Boolean(trimValue(config?.envFile));
|
|
23
|
+
}
|
|
24
|
+
export function resolveDockerEnvFilePath(envName, config) {
|
|
25
|
+
return resolveConfiguredEnvPath(resolveConfiguredDockerEnvFilePath(envName, config));
|
|
26
|
+
}
|
|
27
|
+
export async function dockerEnvFileExists(envName, config) {
|
|
28
|
+
const filePath = resolveDockerEnvFilePath(envName, config);
|
|
29
|
+
if (!filePath) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
try {
|
|
33
|
+
await access(filePath);
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
catch (_error) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export async function resolveDockerEnvFileArg(envName, config) {
|
|
41
|
+
const filePath = resolveDockerEnvFilePath(envName, config);
|
|
42
|
+
if (!filePath) {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
if (await dockerEnvFileExists(envName, config)) {
|
|
46
|
+
return filePath;
|
|
47
|
+
}
|
|
48
|
+
if (hasExplicitDockerEnvFile(config)) {
|
|
49
|
+
throw new Error(`The configured envFile for "${envName}" does not exist: ${resolveConfiguredDockerEnvFilePath(envName, config)}`);
|
|
50
|
+
}
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
package/dist/lib/env-auth.js
CHANGED
|
@@ -14,7 +14,7 @@ import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
|
14
14
|
import os from 'node:os';
|
|
15
15
|
import path from 'node:path';
|
|
16
16
|
import { getCurrentEnvName, getEnv, setEnvOauthSession, } from './auth-store.js';
|
|
17
|
-
import { printInfo, printVerbose, printWarning, printWarningBlock, updateTask } from './ui.js';
|
|
17
|
+
import { printInfo, printVerbose, printWarning, printWarningBlock, stopTask, updateTask } from './ui.js';
|
|
18
18
|
const ACCESS_TOKEN_REFRESH_WINDOW_MS = 60_000;
|
|
19
19
|
const LOOPBACK_HOST = '127.0.0.1';
|
|
20
20
|
const OAUTH_LOGIN_TIMEOUT_MS = 5 * 60 * 1000;
|
|
@@ -823,10 +823,11 @@ export async function authenticateEnvWithOauth(options) {
|
|
|
823
823
|
const browser = await maybeOpenBrowser(authorizationUrl.toString());
|
|
824
824
|
cleanupBrowserOpenTarget = browser.cleanup;
|
|
825
825
|
if (!browser.opened) {
|
|
826
|
-
printWarningBlock('We could not open your browser automatically. Open
|
|
826
|
+
printWarningBlock('We could not open your browser automatically. Open the URL below to continue signing in:');
|
|
827
827
|
}
|
|
828
828
|
else {
|
|
829
|
-
|
|
829
|
+
stopTask();
|
|
830
|
+
printInfo('Open this URL to sign in.');
|
|
830
831
|
}
|
|
831
832
|
printInfo(authorizationUrl.toString());
|
|
832
833
|
const code = await new Promise((resolve, reject) => {
|
package/dist/lib/env-config.js
CHANGED
package/dist/lib/env-guard.js
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
|
-
import * as p from '@clack/prompts';
|
|
10
9
|
import { stdin as input, stdout as output } from 'node:process';
|
|
11
10
|
import { getCurrentEnvName } from './auth-store.js';
|
|
11
|
+
import { confirm } from "./inquirer.js";
|
|
12
12
|
function normalizeEnvName(value) {
|
|
13
13
|
const text = String(value ?? '').trim();
|
|
14
14
|
return text || undefined;
|
|
@@ -50,12 +50,13 @@ export async function ensureCrossEnvConfirmed(options) {
|
|
|
50
50
|
if (!interactiveTerminal) {
|
|
51
51
|
options.command.error(formatCrossEnvRefusalMessage(currentEnv, requestedEnv));
|
|
52
52
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
try {
|
|
54
|
+
return Boolean(await confirm({
|
|
55
|
+
message: formatCrossEnvPromptMessage(currentEnv, requestedEnv),
|
|
56
|
+
default: false,
|
|
57
|
+
}));
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
58
60
|
return false;
|
|
59
61
|
}
|
|
60
|
-
return Boolean(answer);
|
|
61
62
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
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
|
+
export function buildPlainMessageTheme(extra) {
|
|
10
|
+
return {
|
|
11
|
+
...extra,
|
|
12
|
+
style: {
|
|
13
|
+
...extra?.style,
|
|
14
|
+
message: (text) => text,
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
}
|
|
@@ -0,0 +1,244 @@
|
|
|
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 { cursorTo } from '@inquirer/ansi';
|
|
10
|
+
import pc from 'picocolors';
|
|
11
|
+
import { createPrompt, isBackspaceKey, isEnterKey, makeTheme, useEffect, useKeypress, usePrefix, useState, } from '@inquirer/core';
|
|
12
|
+
let confirmModulePromise;
|
|
13
|
+
let selectModulePromise;
|
|
14
|
+
function loadConfirmModule() {
|
|
15
|
+
confirmModulePromise ??= import('@inquirer/confirm');
|
|
16
|
+
return confirmModulePromise;
|
|
17
|
+
}
|
|
18
|
+
function loadSelectModule() {
|
|
19
|
+
selectModulePromise ??= import('@inquirer/select');
|
|
20
|
+
return selectModulePromise;
|
|
21
|
+
}
|
|
22
|
+
export function buildPlainMessageTheme(extra) {
|
|
23
|
+
return {
|
|
24
|
+
...extra,
|
|
25
|
+
style: {
|
|
26
|
+
...extra?.style,
|
|
27
|
+
message: (text) => text,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function buildSelectAnswer(text) {
|
|
32
|
+
return `\n${pc.cyan('❯')} ${pc.cyan(text)}`;
|
|
33
|
+
}
|
|
34
|
+
function buildConfirmAnswer(text) {
|
|
35
|
+
return `\n${pc.cyan('❯')} ${pc.cyan(text)}`;
|
|
36
|
+
}
|
|
37
|
+
function isFullWidthCodePoint(codePoint) {
|
|
38
|
+
return (codePoint >= 0x1100 &&
|
|
39
|
+
(codePoint <= 0x115f ||
|
|
40
|
+
codePoint === 0x2329 ||
|
|
41
|
+
codePoint === 0x232a ||
|
|
42
|
+
(codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f) ||
|
|
43
|
+
(codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
|
|
44
|
+
(codePoint >= 0xf900 && codePoint <= 0xfaff) ||
|
|
45
|
+
(codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
|
|
46
|
+
(codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
|
|
47
|
+
(codePoint >= 0xff00 && codePoint <= 0xff60) ||
|
|
48
|
+
(codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
|
|
49
|
+
(codePoint >= 0x1f300 && codePoint <= 0x1f64f) ||
|
|
50
|
+
(codePoint >= 0x1f900 && codePoint <= 0x1f9ff) ||
|
|
51
|
+
(codePoint >= 0x20000 && codePoint <= 0x3fffd)));
|
|
52
|
+
}
|
|
53
|
+
function stringWidth(value) {
|
|
54
|
+
let width = 0;
|
|
55
|
+
for (const char of value) {
|
|
56
|
+
const codePoint = char.codePointAt(0);
|
|
57
|
+
if (codePoint == null)
|
|
58
|
+
continue;
|
|
59
|
+
if (codePoint <= 0x1f || (codePoint >= 0x7f && codePoint <= 0x9f))
|
|
60
|
+
continue;
|
|
61
|
+
width += isFullWidthCodePoint(codePoint) ? 2 : 1;
|
|
62
|
+
}
|
|
63
|
+
return width;
|
|
64
|
+
}
|
|
65
|
+
function stripAnsi(value) {
|
|
66
|
+
return value.replace(new RegExp(String.raw `\\u001B\\[[0-9;]*m`, 'g'), '');
|
|
67
|
+
}
|
|
68
|
+
function lastLineWidth(value) {
|
|
69
|
+
const lines = stripAnsi(value).split('\n');
|
|
70
|
+
return stringWidth(lines[lines.length - 1] ?? '');
|
|
71
|
+
}
|
|
72
|
+
function resolveRequiredError(required) {
|
|
73
|
+
if (typeof required === 'string') {
|
|
74
|
+
return required;
|
|
75
|
+
}
|
|
76
|
+
return '此项为必填';
|
|
77
|
+
}
|
|
78
|
+
function hasUsableDefault(value) {
|
|
79
|
+
return value != null && value !== '';
|
|
80
|
+
}
|
|
81
|
+
function buildIdleHint(config) {
|
|
82
|
+
const placeholder = config.placeholder?.trim();
|
|
83
|
+
if (placeholder) {
|
|
84
|
+
return placeholder;
|
|
85
|
+
}
|
|
86
|
+
return '';
|
|
87
|
+
}
|
|
88
|
+
function buildInputLine(value) {
|
|
89
|
+
return `${pc.cyan('❯')} ${value}`;
|
|
90
|
+
}
|
|
91
|
+
function buildErrorLine(error) {
|
|
92
|
+
return pc.red(`✖ ${error}`);
|
|
93
|
+
}
|
|
94
|
+
export const input = createPrompt((config, done) => {
|
|
95
|
+
const theme = makeTheme(config.theme);
|
|
96
|
+
const [status, setStatus] = useState('idle');
|
|
97
|
+
const [defaultValue, setDefaultValue] = useState(config.default ?? '');
|
|
98
|
+
const [value, setValue] = useState('');
|
|
99
|
+
const [error, setError] = useState();
|
|
100
|
+
const prefix = usePrefix({ status, theme });
|
|
101
|
+
useEffect((rl) => {
|
|
102
|
+
if (config.default) {
|
|
103
|
+
rl.write(config.default);
|
|
104
|
+
setValue(config.default);
|
|
105
|
+
}
|
|
106
|
+
}, []);
|
|
107
|
+
useKeypress(async (key, rl) => {
|
|
108
|
+
if (status !== 'idle')
|
|
109
|
+
return;
|
|
110
|
+
if (isBackspaceKey(key) && !value) {
|
|
111
|
+
setDefaultValue('');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
if (isBackspaceKey(key)) {
|
|
115
|
+
setValue(rl.line.slice(0, -1));
|
|
116
|
+
setError(undefined);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (!isEnterKey(key)) {
|
|
120
|
+
setValue(rl.line);
|
|
121
|
+
setError(undefined);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const answer = value || defaultValue;
|
|
125
|
+
setStatus('loading');
|
|
126
|
+
if (!answer && config.required) {
|
|
127
|
+
rl.write(value);
|
|
128
|
+
setError(resolveRequiredError(config.required));
|
|
129
|
+
setStatus('idle');
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (config.validate) {
|
|
133
|
+
const result = await config.validate(answer);
|
|
134
|
+
if (result !== true) {
|
|
135
|
+
rl.write(value);
|
|
136
|
+
setError(typeof result === 'string' ? result : 'Invalid input');
|
|
137
|
+
setStatus('idle');
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
setValue(answer);
|
|
142
|
+
setStatus('done');
|
|
143
|
+
done(answer);
|
|
144
|
+
});
|
|
145
|
+
const message = theme.style.message(config.message, status);
|
|
146
|
+
const isTyping = value.length > 0;
|
|
147
|
+
const hasDefault = hasUsableDefault(defaultValue);
|
|
148
|
+
const idleHint = buildIdleHint(config);
|
|
149
|
+
const showIdleHint = !isTyping && status === 'idle' && idleHint !== '';
|
|
150
|
+
const showDefaultValue = !isTyping && status === 'idle' && hasDefault;
|
|
151
|
+
const displayValue = status === 'done'
|
|
152
|
+
? theme.style.answer(value)
|
|
153
|
+
: isTyping
|
|
154
|
+
? value
|
|
155
|
+
: showDefaultValue
|
|
156
|
+
? defaultValue
|
|
157
|
+
: showIdleHint
|
|
158
|
+
? pc.dim(idleHint)
|
|
159
|
+
: '';
|
|
160
|
+
const headerLine = [prefix, message].filter(Boolean).join(' ');
|
|
161
|
+
const inputLine = buildInputLine(displayValue);
|
|
162
|
+
const prompt = headerLine.endsWith('\n') ? `${headerLine}${inputLine}` : `${headerLine}\n${inputLine}`;
|
|
163
|
+
const promptWithCursorFix = showIdleHint
|
|
164
|
+
? prompt + cursorTo(lastLineWidth(buildInputLine('')))
|
|
165
|
+
: showDefaultValue
|
|
166
|
+
? prompt + cursorTo(lastLineWidth(buildInputLine(defaultValue)))
|
|
167
|
+
: prompt;
|
|
168
|
+
return [promptWithCursorFix, error ? buildErrorLine(error) : ''];
|
|
169
|
+
});
|
|
170
|
+
export async function confirm(options) {
|
|
171
|
+
const module = await loadConfirmModule();
|
|
172
|
+
return module.default({
|
|
173
|
+
...options,
|
|
174
|
+
theme: buildPlainMessageTheme({
|
|
175
|
+
style: {
|
|
176
|
+
answer: buildConfirmAnswer,
|
|
177
|
+
},
|
|
178
|
+
...options.theme,
|
|
179
|
+
}),
|
|
180
|
+
transformer: options.transformer ?? ((value) => (value ? 'Yes' : 'No')),
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
export async function select(options) {
|
|
184
|
+
const module = await loadSelectModule();
|
|
185
|
+
return module.default({
|
|
186
|
+
...options,
|
|
187
|
+
theme: buildPlainMessageTheme({
|
|
188
|
+
style: {
|
|
189
|
+
answer: buildSelectAnswer,
|
|
190
|
+
},
|
|
191
|
+
...options.theme,
|
|
192
|
+
}),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
function resolveMaskChar(mask) {
|
|
196
|
+
if (typeof mask === 'string') {
|
|
197
|
+
return mask;
|
|
198
|
+
}
|
|
199
|
+
if (mask === false) {
|
|
200
|
+
return '';
|
|
201
|
+
}
|
|
202
|
+
return '•';
|
|
203
|
+
}
|
|
204
|
+
export const password = createPrompt((config, done) => {
|
|
205
|
+
const theme = makeTheme(config.theme);
|
|
206
|
+
const [status, setStatus] = useState('idle');
|
|
207
|
+
const [value, setValue] = useState('');
|
|
208
|
+
const [error, setError] = useState();
|
|
209
|
+
const prefix = usePrefix({ status, theme });
|
|
210
|
+
useKeypress(async (key, rl) => {
|
|
211
|
+
if (status !== 'idle')
|
|
212
|
+
return;
|
|
213
|
+
if (!isEnterKey(key)) {
|
|
214
|
+
setValue(rl.line);
|
|
215
|
+
setError(undefined);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const answer = value;
|
|
219
|
+
setStatus('loading');
|
|
220
|
+
if (config.validate) {
|
|
221
|
+
const result = await config.validate(answer);
|
|
222
|
+
if (result !== true) {
|
|
223
|
+
rl.write(value);
|
|
224
|
+
setError(typeof result === 'string' ? result : 'Invalid input');
|
|
225
|
+
setStatus('idle');
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
setValue(answer);
|
|
230
|
+
setStatus('done');
|
|
231
|
+
done(answer);
|
|
232
|
+
});
|
|
233
|
+
const message = theme.style.message(config.message, status);
|
|
234
|
+
const maskChar = resolveMaskChar(config.mask);
|
|
235
|
+
const displayValue = status === 'done'
|
|
236
|
+
? theme.style.answer(maskChar.repeat(value.length))
|
|
237
|
+
: maskChar
|
|
238
|
+
? maskChar.repeat(value.length)
|
|
239
|
+
: '';
|
|
240
|
+
const headerLine = [prefix, message].filter(Boolean).join(' ');
|
|
241
|
+
const inputLine = buildInputLine(displayValue);
|
|
242
|
+
const prompt = headerLine.endsWith('\n') ? `${headerLine}${inputLine}` : `${headerLine}\n${inputLine}`;
|
|
243
|
+
return [prompt, error ? buildErrorLine(error) : ''];
|
|
244
|
+
});
|
|
@@ -0,0 +1,76 @@
|
|
|
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
|
+
export function pickKeys(object, keys) {
|
|
10
|
+
const picked = {};
|
|
11
|
+
for (const key of keys) {
|
|
12
|
+
if (Object.prototype.hasOwnProperty.call(object, key)) {
|
|
13
|
+
const typedKey = key;
|
|
14
|
+
picked[typedKey] = object[typedKey];
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return picked;
|
|
18
|
+
}
|
|
19
|
+
export function omitKeys(object, keys) {
|
|
20
|
+
const omittedKeys = new Set(keys);
|
|
21
|
+
const result = {};
|
|
22
|
+
for (const key of Object.keys(object)) {
|
|
23
|
+
if (!omittedKeys.has(key)) {
|
|
24
|
+
const typedKey = key;
|
|
25
|
+
result[typedKey] = object[typedKey];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
export function upperFirst(value) {
|
|
31
|
+
if (!value) {
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
34
|
+
return value[0].toUpperCase() + value.slice(1);
|
|
35
|
+
}
|
|
36
|
+
export function deepEqual(left, right) {
|
|
37
|
+
if (Object.is(left, right)) {
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
if (left == null || right == null) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
if (typeof left !== typeof right) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
if (typeof left !== 'object') {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
if (Array.isArray(left) || Array.isArray(right)) {
|
|
50
|
+
if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
54
|
+
if (!deepEqual(left[index], right[index])) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
const leftObject = left;
|
|
61
|
+
const rightObject = right;
|
|
62
|
+
const leftKeys = Object.keys(leftObject);
|
|
63
|
+
const rightKeys = Object.keys(rightObject);
|
|
64
|
+
if (leftKeys.length !== rightKeys.length) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
for (const key of leftKeys) {
|
|
68
|
+
if (!Object.prototype.hasOwnProperty.call(rightObject, key)) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
if (!deepEqual(leftObject[key], rightObject[key])) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return true;
|
|
76
|
+
}
|