@nocobase/cli 2.1.0-beta.44.test.4 → 2.1.0-beta.45
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/source/download.js +1 -1
- 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 +332 -0
- package/dist/lib/app-public-path.js +80 -0
- package/dist/lib/app-runtime.js +189 -0
- package/dist/lib/auth-store.js +520 -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 +497 -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 +100 -0
- package/dist/lib/env-guard.js +61 -0
- package/dist/lib/env-paths.js +101 -0
- package/dist/lib/env-proxy.js +1295 -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 +98 -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 +375 -0
- package/dist/lib/prompt-catalog.js +10 -0
- package/dist/lib/prompt-validators.js +272 -0
- package/dist/lib/prompt-web-ui.js +2234 -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/package.json +3 -2
|
@@ -0,0 +1,203 @@
|
|
|
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
|
+
/**
|
|
10
|
+
* This file is part of the NocoBase (R) project.
|
|
11
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
12
|
+
* Authors: NocoBase Team.
|
|
13
|
+
*
|
|
14
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
15
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
16
|
+
*/
|
|
17
|
+
import { Command, Flags } from '@oclif/core';
|
|
18
|
+
import { executeApiRequest } from './api-client.js';
|
|
19
|
+
import { findApiCommandCompatViolation, formatApiCommandCompatViolation } from './api-command-compat.js';
|
|
20
|
+
import { ensureCrossEnvConfirmed } from './env-guard.js';
|
|
21
|
+
import { applyPostProcessor } from './post-processors.js';
|
|
22
|
+
import { readInstalledManagedSkillsVersion } from './skills-manager.js';
|
|
23
|
+
import { registerPostProcessors } from '../post-processors/index.js';
|
|
24
|
+
function buildParameterFlag(parameter, options) {
|
|
25
|
+
const hints = [parameter.in];
|
|
26
|
+
if (parameter.isFile) {
|
|
27
|
+
hints.push('file path');
|
|
28
|
+
}
|
|
29
|
+
else if (parameter.type === 'object' || parameter.type === 'array' || parameter.jsonEncoded) {
|
|
30
|
+
hints.push('JSON');
|
|
31
|
+
}
|
|
32
|
+
else if (parameter.isArray) {
|
|
33
|
+
hints.push('repeatable');
|
|
34
|
+
}
|
|
35
|
+
else if (parameter.type) {
|
|
36
|
+
hints.push(parameter.type);
|
|
37
|
+
}
|
|
38
|
+
const description = [
|
|
39
|
+
`${parameter.description ?? ''}${parameter.description ? ' ' : ''}[${hints.join(', ')}]`.trim(),
|
|
40
|
+
parameter.jsonShape ? `Shape: ${parameter.jsonShape}` : undefined,
|
|
41
|
+
]
|
|
42
|
+
.filter(Boolean)
|
|
43
|
+
.join('\n');
|
|
44
|
+
const required = options?.required ?? parameter.required;
|
|
45
|
+
const helpGroup = parameter.in === 'body'
|
|
46
|
+
? 'Body Field'
|
|
47
|
+
: parameter.in === 'path'
|
|
48
|
+
? 'Path Parameter'
|
|
49
|
+
: parameter.in === 'query'
|
|
50
|
+
? 'Query Parameter'
|
|
51
|
+
: parameter.in === 'header'
|
|
52
|
+
? 'Header Parameter'
|
|
53
|
+
: parameter.in === 'cookie'
|
|
54
|
+
? 'Cookie Parameter'
|
|
55
|
+
: undefined;
|
|
56
|
+
if (parameter.type === 'boolean') {
|
|
57
|
+
return Flags.boolean({
|
|
58
|
+
description,
|
|
59
|
+
allowNo: true,
|
|
60
|
+
...(helpGroup ? { helpGroup } : {}),
|
|
61
|
+
...(required ? { required: true } : {}),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (parameter.isArray && !parameter.jsonEncoded) {
|
|
65
|
+
return Flags.string({
|
|
66
|
+
description,
|
|
67
|
+
multiple: true,
|
|
68
|
+
...(helpGroup ? { helpGroup } : {}),
|
|
69
|
+
...(required ? { required: true } : {}),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
return Flags.string({
|
|
73
|
+
description,
|
|
74
|
+
...(helpGroup ? { helpGroup } : {}),
|
|
75
|
+
...(required ? { required: true } : {}),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
export function createGeneratedFlags(operation) {
|
|
79
|
+
const flags = {};
|
|
80
|
+
for (const parameter of operation.parameters) {
|
|
81
|
+
flags[parameter.flagName] = buildParameterFlag(parameter, {
|
|
82
|
+
// Body flags are an alternative authoring path to --body/--body-file.
|
|
83
|
+
// Enforce required body semantics later in parseBody(), after we know
|
|
84
|
+
// which input mode the user chose.
|
|
85
|
+
required: parameter.in === 'body' && !parameter.isFile ? false : parameter.required,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
if (operation.hasBody && operation.requestContentType !== 'multipart/form-data') {
|
|
89
|
+
flags.body = Flags.string({
|
|
90
|
+
description: 'Full JSON request body string. Do not combine with body field flags.',
|
|
91
|
+
helpGroup: 'Raw JSON Body',
|
|
92
|
+
exclusive: ['body-file'],
|
|
93
|
+
});
|
|
94
|
+
flags['body-file'] = Flags.string({
|
|
95
|
+
description: 'Path to a JSON file containing the full request body. Do not combine with body field flags.',
|
|
96
|
+
helpGroup: 'Raw JSON Body',
|
|
97
|
+
exclusive: ['body'],
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (operation.responseType === 'binary') {
|
|
101
|
+
flags.output = Flags.string({
|
|
102
|
+
description: 'Path where the downloaded response should be written.',
|
|
103
|
+
helpGroup: 'Output',
|
|
104
|
+
required: true,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
flags['api-base-url'] = Flags.string({
|
|
108
|
+
description: 'NocoBase API base URL, for example http://localhost:13000/api',
|
|
109
|
+
helpGroup: 'Global',
|
|
110
|
+
});
|
|
111
|
+
flags.verbose = Flags.boolean({
|
|
112
|
+
description: 'Show detailed progress output',
|
|
113
|
+
default: false,
|
|
114
|
+
helpGroup: 'Global',
|
|
115
|
+
});
|
|
116
|
+
flags.yes = Flags.boolean({
|
|
117
|
+
char: 'y',
|
|
118
|
+
description: 'Confirm using --env when it targets a different env than the current env',
|
|
119
|
+
default: false,
|
|
120
|
+
helpGroup: 'Global',
|
|
121
|
+
});
|
|
122
|
+
flags.env = Flags.string({
|
|
123
|
+
char: 'e',
|
|
124
|
+
description: 'Environment name',
|
|
125
|
+
helpGroup: 'Global',
|
|
126
|
+
});
|
|
127
|
+
flags.role = Flags.string({
|
|
128
|
+
description: 'Role override, sent as X-Role',
|
|
129
|
+
helpGroup: 'Global',
|
|
130
|
+
});
|
|
131
|
+
flags.token = Flags.string({
|
|
132
|
+
char: 't',
|
|
133
|
+
description: 'API key override',
|
|
134
|
+
helpGroup: 'Global',
|
|
135
|
+
});
|
|
136
|
+
flags['json-output'] = Flags.boolean({
|
|
137
|
+
char: 'j',
|
|
138
|
+
description: 'Print raw JSON response',
|
|
139
|
+
default: true,
|
|
140
|
+
allowNo: true,
|
|
141
|
+
helpGroup: 'Global',
|
|
142
|
+
});
|
|
143
|
+
return flags;
|
|
144
|
+
}
|
|
145
|
+
export class GeneratedApiCommand extends Command {
|
|
146
|
+
static operation;
|
|
147
|
+
static runtimeVersion;
|
|
148
|
+
async run() {
|
|
149
|
+
registerPostProcessors();
|
|
150
|
+
const ctor = this.constructor;
|
|
151
|
+
const { flags } = await this.parse(ctor);
|
|
152
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
153
|
+
command: this,
|
|
154
|
+
requestedEnv: flags.env,
|
|
155
|
+
yes: flags.yes,
|
|
156
|
+
});
|
|
157
|
+
if (!confirmed) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const cliVersion = String(this.config.pjson.version ?? '').trim();
|
|
161
|
+
const skillsVersion = await readInstalledManagedSkillsVersion();
|
|
162
|
+
const compatViolation = findApiCommandCompatViolation({
|
|
163
|
+
packageJson: this.config.pjson,
|
|
164
|
+
commandId: ctor.operation.commandId,
|
|
165
|
+
cliVersion,
|
|
166
|
+
appVersion: ctor.runtimeVersion,
|
|
167
|
+
skillsVersion,
|
|
168
|
+
});
|
|
169
|
+
if (compatViolation) {
|
|
170
|
+
this.error(formatApiCommandCompatViolation(compatViolation));
|
|
171
|
+
}
|
|
172
|
+
const response = await executeApiRequest({
|
|
173
|
+
cliVersion,
|
|
174
|
+
skillsVersion,
|
|
175
|
+
envName: flags.env,
|
|
176
|
+
baseUrl: flags['api-base-url'],
|
|
177
|
+
role: flags.role,
|
|
178
|
+
token: flags.token,
|
|
179
|
+
flags,
|
|
180
|
+
operation: {
|
|
181
|
+
method: ctor.operation.method,
|
|
182
|
+
pathTemplate: ctor.operation.pathTemplate,
|
|
183
|
+
parameters: ctor.operation.parameters,
|
|
184
|
+
hasBody: ctor.operation.hasBody,
|
|
185
|
+
bodyRequired: ctor.operation.bodyRequired,
|
|
186
|
+
requestContentType: ctor.operation.requestContentType,
|
|
187
|
+
responseType: ctor.operation.responseType,
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
if (!response.ok) {
|
|
191
|
+
this.error(`Request failed with status ${response.status}\n${JSON.stringify(response.data, null, 2)}`);
|
|
192
|
+
}
|
|
193
|
+
const processedData = await applyPostProcessor(response.data, {
|
|
194
|
+
flags,
|
|
195
|
+
operation: ctor.operation,
|
|
196
|
+
});
|
|
197
|
+
if (flags['json-output']) {
|
|
198
|
+
this.log(JSON.stringify(processedData, null, 2));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
this.log(`HTTP ${response.status}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
function normalizeLocationUrl(location, currentUrl) {
|
|
10
|
+
try {
|
|
11
|
+
return new URL(location, currentUrl).toString();
|
|
12
|
+
}
|
|
13
|
+
catch (_error) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function shouldPreserveAuthorizationRedirect(fromUrl, toUrl) {
|
|
18
|
+
try {
|
|
19
|
+
const from = new URL(fromUrl);
|
|
20
|
+
const to = new URL(toUrl);
|
|
21
|
+
return (from.hostname === to.hostname &&
|
|
22
|
+
from.port === to.port &&
|
|
23
|
+
from.pathname === to.pathname &&
|
|
24
|
+
from.search === to.search &&
|
|
25
|
+
from.protocol === 'http:' &&
|
|
26
|
+
to.protocol === 'https:');
|
|
27
|
+
}
|
|
28
|
+
catch (_error) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export async function fetchWithPreservedAuthRedirect(url, init = {}) {
|
|
33
|
+
const response = await fetch(url, {
|
|
34
|
+
...init,
|
|
35
|
+
redirect: 'manual',
|
|
36
|
+
});
|
|
37
|
+
const location = response.headers.get('location');
|
|
38
|
+
if (!location || ![301, 302, 307, 308].includes(response.status)) {
|
|
39
|
+
return response;
|
|
40
|
+
}
|
|
41
|
+
const nextUrl = normalizeLocationUrl(location, url);
|
|
42
|
+
if (!nextUrl || !shouldPreserveAuthorizationRedirect(url, nextUrl)) {
|
|
43
|
+
return response;
|
|
44
|
+
}
|
|
45
|
+
return fetch(nextUrl, {
|
|
46
|
+
...init,
|
|
47
|
+
redirect: 'manual',
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -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,243 @@
|
|
|
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
|
+
}, [config.default]);
|
|
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 transformedValue = value ? config.transformer?.(value) : undefined;
|
|
236
|
+
const displayValue = status === 'done'
|
|
237
|
+
? theme.style.answer(transformedValue ?? maskChar.repeat(value.length))
|
|
238
|
+
: transformedValue ?? (maskChar ? maskChar.repeat(value.length) : '');
|
|
239
|
+
const headerLine = [prefix, message].filter(Boolean).join(' ');
|
|
240
|
+
const inputLine = buildInputLine(displayValue);
|
|
241
|
+
const prompt = headerLine.endsWith('\n') ? `${headerLine}${inputLine}` : `${headerLine}\n${inputLine}`;
|
|
242
|
+
return [prompt, error ? buildErrorLine(error) : ''];
|
|
243
|
+
});
|
|
@@ -0,0 +1,98 @@
|
|
|
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 { readFile } from 'node:fs/promises';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { resolveConfiguredEnvPath } from './cli-home.js';
|
|
12
|
+
import { resolveDockerEnvFileArg } from "./docker-env-file.js";
|
|
13
|
+
import { resolveConfiguredAppPath } from './env-paths.js';
|
|
14
|
+
function trimValue(value) {
|
|
15
|
+
const text = String(value ?? '').trim();
|
|
16
|
+
return text || undefined;
|
|
17
|
+
}
|
|
18
|
+
function stripWrappingQuotes(value) {
|
|
19
|
+
if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
|
|
20
|
+
return value
|
|
21
|
+
.slice(1, -1)
|
|
22
|
+
.replace(/\\n/g, '\n')
|
|
23
|
+
.replace(/\\r/g, '\r')
|
|
24
|
+
.replace(/\\t/g, '\t')
|
|
25
|
+
.replace(/\\"/g, '"')
|
|
26
|
+
.replace(/\\\\/g, '\\');
|
|
27
|
+
}
|
|
28
|
+
if (value.length >= 2 && value.startsWith("'") && value.endsWith("'")) {
|
|
29
|
+
return value.slice(1, -1);
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
export function parseSimpleEnvFile(content) {
|
|
34
|
+
const values = {};
|
|
35
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
36
|
+
const trimmed = rawLine.trim();
|
|
37
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
const line = trimmed.startsWith('export ') ? trimmed.slice('export '.length).trim() : trimmed;
|
|
41
|
+
const separatorIndex = line.indexOf('=');
|
|
42
|
+
if (separatorIndex <= 0) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const key = line.slice(0, separatorIndex).trim();
|
|
46
|
+
if (!key) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
values[key] = stripWrappingQuotes(line.slice(separatorIndex + 1).trim());
|
|
50
|
+
}
|
|
51
|
+
return values;
|
|
52
|
+
}
|
|
53
|
+
export function resolveManagedLocalEnvFilePath(runtime) {
|
|
54
|
+
const config = runtime.env.config ?? {};
|
|
55
|
+
const explicitEnvFile = trimValue(config.envFile);
|
|
56
|
+
if (explicitEnvFile) {
|
|
57
|
+
return resolveConfiguredEnvPath(explicitEnvFile) ?? explicitEnvFile;
|
|
58
|
+
}
|
|
59
|
+
const configuredAppPath = resolveConfiguredAppPath(config);
|
|
60
|
+
if (configuredAppPath) {
|
|
61
|
+
return path.join(configuredAppPath, '.env');
|
|
62
|
+
}
|
|
63
|
+
if (path.basename(runtime.projectRoot) === 'source') {
|
|
64
|
+
return path.resolve(runtime.projectRoot, '..', '.env');
|
|
65
|
+
}
|
|
66
|
+
return path.join(runtime.projectRoot, '.env');
|
|
67
|
+
}
|
|
68
|
+
export async function resolveManagedRuntimeEnvFilePath(runtime) {
|
|
69
|
+
if (runtime.kind === 'local') {
|
|
70
|
+
return resolveManagedLocalEnvFilePath(runtime);
|
|
71
|
+
}
|
|
72
|
+
return await resolveDockerEnvFileArg(runtime.envName, runtime.env.config ?? {});
|
|
73
|
+
}
|
|
74
|
+
export async function readManagedRuntimeEnvValues(runtime) {
|
|
75
|
+
const envFilePath = await resolveManagedRuntimeEnvFilePath(runtime);
|
|
76
|
+
if (!envFilePath) {
|
|
77
|
+
return {
|
|
78
|
+
envFilePath: undefined,
|
|
79
|
+
envValues: {},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
return {
|
|
84
|
+
envFilePath,
|
|
85
|
+
envValues: parseSimpleEnvFile(await readFile(envFilePath, 'utf8')),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
const code = error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
|
|
90
|
+
if (code === 'ENOENT') {
|
|
91
|
+
return {
|
|
92
|
+
envFilePath,
|
|
93
|
+
envValues: {},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
throw error;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
export function toKebabCase(value) {
|
|
3
|
+
return value
|
|
4
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
5
|
+
.replace(/[^a-zA-Z0-9]+/g, '-')
|
|
6
|
+
.replace(/-+/g, '-')
|
|
7
|
+
.replace(/^-|-$/g, '')
|
|
8
|
+
.toLowerCase();
|
|
9
|
+
}
|
|
10
|
+
export function splitPathAction(pathTemplate) {
|
|
11
|
+
const normalizedPath = pathTemplate.replace(/^\/+/, '');
|
|
12
|
+
const separatorIndex = normalizedPath.lastIndexOf(':');
|
|
13
|
+
if (separatorIndex === -1) {
|
|
14
|
+
return {
|
|
15
|
+
resourcePath: normalizedPath,
|
|
16
|
+
action: 'call',
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
resourcePath: normalizedPath.slice(0, separatorIndex),
|
|
21
|
+
action: normalizedPath.slice(separatorIndex + 1),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function toLogicalResourceName(pathTemplate) {
|
|
25
|
+
const { resourcePath } = splitPathAction(pathTemplate);
|
|
26
|
+
return resourcePath
|
|
27
|
+
.split('/')
|
|
28
|
+
.filter(Boolean)
|
|
29
|
+
.filter((segment) => !segment.startsWith('{'))
|
|
30
|
+
.map((segment) => toKebabCase(segment))
|
|
31
|
+
.join('.');
|
|
32
|
+
}
|
|
33
|
+
export function toLogicalActionName(pathTemplate) {
|
|
34
|
+
return toKebabCase(splitPathAction(pathTemplate).action);
|
|
35
|
+
}
|
|
36
|
+
export function toResourceSegments(pathTemplate, options) {
|
|
37
|
+
const { resourcePath, action } = splitPathAction(pathTemplate);
|
|
38
|
+
const pathSegments = resourcePath
|
|
39
|
+
.split('/')
|
|
40
|
+
.filter(Boolean)
|
|
41
|
+
.flatMap((segment) => {
|
|
42
|
+
if (!segment.startsWith('{')) {
|
|
43
|
+
return [toKebabCase(segment)];
|
|
44
|
+
}
|
|
45
|
+
if (!options?.includeParams) {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
return [`by-${toKebabCase(segment.slice(1, -1))}`];
|
|
49
|
+
});
|
|
50
|
+
return [...pathSegments, toKebabCase(action)].filter(Boolean);
|
|
51
|
+
}
|
|
52
|
+
export function toCommandSegments(moduleName, pathTemplate, options) {
|
|
53
|
+
const resourceSegments = toResourceSegments(pathTemplate, options);
|
|
54
|
+
const segments = [options?.omitModule ? '' : toKebabCase(moduleName), ...resourceSegments].filter(Boolean);
|
|
55
|
+
return segments.length ? segments : [toKebabCase(moduleName), 'call'];
|
|
56
|
+
}
|
|
57
|
+
export function toClassName(segments) {
|
|
58
|
+
return segments
|
|
59
|
+
.map((segment) => segment.replace(/(^\w|-\w)/g, (token) => token.replace('-', '').toUpperCase()))
|
|
60
|
+
.join('');
|
|
61
|
+
}
|
|
62
|
+
export function toOutputFile(outputRoot, segments) {
|
|
63
|
+
const folder = path.join(outputRoot, ...segments.slice(0, -1));
|
|
64
|
+
const filePath = path.join(folder, `${segments.at(-1)}.ts`);
|
|
65
|
+
return filePath;
|
|
66
|
+
}
|
|
67
|
+
export function toImportPath(fromFile, targetFile) {
|
|
68
|
+
const relative = path.relative(path.dirname(fromFile), targetFile).replace(/\\/g, '/');
|
|
69
|
+
return relative.startsWith('.') ? relative : `./${relative}`;
|
|
70
|
+
}
|