@nocobase/cli 2.1.0-rc.1 → 2.1.0-rc.2

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.
Files changed (75) hide show
  1. package/dist/lib/api-client.js +335 -0
  2. package/dist/lib/api-command-compat.js +641 -0
  3. package/dist/lib/app-health.js +139 -0
  4. package/dist/lib/app-managed-resources.js +337 -0
  5. package/dist/lib/app-public-path.js +80 -0
  6. package/dist/lib/app-runtime.js +189 -0
  7. package/dist/lib/auth-store.js +528 -0
  8. package/dist/lib/backup.js +171 -0
  9. package/dist/lib/bootstrap.js +409 -0
  10. package/dist/lib/build-config.js +18 -0
  11. package/dist/lib/builtin-db.js +86 -0
  12. package/dist/lib/cli-config.js +569 -0
  13. package/dist/lib/cli-entry-error.js +52 -0
  14. package/dist/lib/cli-home.js +47 -0
  15. package/dist/lib/cli-locale.js +141 -0
  16. package/dist/lib/command-discovery.js +39 -0
  17. package/dist/lib/command-log.js +284 -0
  18. package/dist/lib/db-connection-check.js +219 -0
  19. package/dist/lib/docker-env-file.js +60 -0
  20. package/dist/lib/docker-image.js +37 -0
  21. package/dist/lib/docker-log-stream.js +45 -0
  22. package/dist/lib/env-auth.js +963 -0
  23. package/dist/lib/env-command-config.js +45 -0
  24. package/dist/lib/env-config.js +108 -0
  25. package/dist/lib/env-guard.js +61 -0
  26. package/dist/lib/env-paths.js +101 -0
  27. package/dist/lib/env-proxy.js +1325 -0
  28. package/dist/lib/generated-command.js +203 -0
  29. package/dist/lib/http-request.js +49 -0
  30. package/dist/lib/inquirer-theme.js +17 -0
  31. package/dist/lib/inquirer.js +243 -0
  32. package/dist/lib/managed-env-file.js +101 -0
  33. package/dist/lib/managed-init-env.js +32 -0
  34. package/dist/lib/naming.js +70 -0
  35. package/dist/lib/object-utils.js +76 -0
  36. package/dist/lib/openapi.js +62 -0
  37. package/dist/lib/plugin-import.js +279 -0
  38. package/dist/lib/plugin-storage.js +64 -0
  39. package/dist/lib/post-processors.js +23 -0
  40. package/dist/lib/prompt-catalog-core.js +186 -0
  41. package/dist/lib/prompt-catalog-terminal.js +374 -0
  42. package/dist/lib/prompt-catalog.js +10 -0
  43. package/dist/lib/prompt-validators.js +278 -0
  44. package/dist/lib/prompt-web-ui.js +2234 -0
  45. package/dist/lib/proxy-caddy.js +274 -0
  46. package/dist/lib/proxy-nginx.js +330 -0
  47. package/dist/lib/resource-command.js +357 -0
  48. package/dist/lib/resource-request.js +104 -0
  49. package/dist/lib/run-npm.js +429 -0
  50. package/dist/lib/runtime-env-vars.js +32 -0
  51. package/dist/lib/runtime-generator.js +498 -0
  52. package/dist/lib/runtime-store.js +56 -0
  53. package/dist/lib/self-manager.js +301 -0
  54. package/dist/lib/session-id.js +17 -0
  55. package/dist/lib/session-integration.js +703 -0
  56. package/dist/lib/session-store.js +118 -0
  57. package/dist/lib/skills-manager.js +438 -0
  58. package/dist/lib/source-publish.js +326 -0
  59. package/dist/lib/source-registry.js +188 -0
  60. package/dist/lib/startup-update.js +309 -0
  61. package/dist/lib/ui.js +159 -0
  62. package/package.json +6 -1
  63. package/assets/env-proxy/nginx/app.conf.tpl +0 -23
  64. package/assets/env-proxy/nginx/nocobase.conf.tpl +0 -5
  65. package/assets/env-proxy/nginx/snippets/dist-location.conf +0 -5
  66. package/assets/env-proxy/nginx/snippets/gzip.conf +0 -17
  67. package/assets/env-proxy/nginx/snippets/log-format-http.conf +0 -13
  68. package/assets/env-proxy/nginx/snippets/maps-http.conf +0 -14
  69. package/assets/env-proxy/nginx/snippets/mime-types.conf +0 -98
  70. package/assets/env-proxy/nginx/snippets/proxy-location.conf +0 -17
  71. package/assets/env-proxy/nginx/snippets/spa-location.conf +0 -6
  72. package/assets/env-proxy/nginx/snippets/uploads-location.conf +0 -21
  73. package/scripts/build.mjs +0 -34
  74. package/scripts/clean.mjs +0 -9
  75. package/tsconfig.json +0 -19
@@ -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,101 @@
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 normalizeEnvFilePath(value) {
19
+ return value.replace(/\\/g, '/');
20
+ }
21
+ function stripWrappingQuotes(value) {
22
+ if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
23
+ return value
24
+ .slice(1, -1)
25
+ .replace(/\\n/g, '\n')
26
+ .replace(/\\r/g, '\r')
27
+ .replace(/\\t/g, '\t')
28
+ .replace(/\\"/g, '"')
29
+ .replace(/\\\\/g, '\\');
30
+ }
31
+ if (value.length >= 2 && value.startsWith("'") && value.endsWith("'")) {
32
+ return value.slice(1, -1);
33
+ }
34
+ return value;
35
+ }
36
+ export function parseSimpleEnvFile(content) {
37
+ const values = {};
38
+ for (const rawLine of content.split(/\r?\n/)) {
39
+ const trimmed = rawLine.trim();
40
+ if (!trimmed || trimmed.startsWith('#')) {
41
+ continue;
42
+ }
43
+ const line = trimmed.startsWith('export ') ? trimmed.slice('export '.length).trim() : trimmed;
44
+ const separatorIndex = line.indexOf('=');
45
+ if (separatorIndex <= 0) {
46
+ continue;
47
+ }
48
+ const key = line.slice(0, separatorIndex).trim();
49
+ if (!key) {
50
+ continue;
51
+ }
52
+ values[key] = stripWrappingQuotes(line.slice(separatorIndex + 1).trim());
53
+ }
54
+ return values;
55
+ }
56
+ export function resolveManagedLocalEnvFilePath(runtime) {
57
+ const config = runtime.env.config ?? {};
58
+ const explicitEnvFile = trimValue(config.envFile);
59
+ if (explicitEnvFile) {
60
+ return normalizeEnvFilePath(resolveConfiguredEnvPath(explicitEnvFile) ?? explicitEnvFile);
61
+ }
62
+ const configuredAppPath = resolveConfiguredAppPath(config);
63
+ if (configuredAppPath) {
64
+ return normalizeEnvFilePath(path.join(configuredAppPath, '.env'));
65
+ }
66
+ if (path.basename(runtime.projectRoot) === 'source') {
67
+ return normalizeEnvFilePath(path.resolve(runtime.projectRoot, '..', '.env'));
68
+ }
69
+ return normalizeEnvFilePath(path.join(runtime.projectRoot, '.env'));
70
+ }
71
+ export async function resolveManagedRuntimeEnvFilePath(runtime) {
72
+ if (runtime.kind === 'local') {
73
+ return resolveManagedLocalEnvFilePath(runtime);
74
+ }
75
+ return await resolveDockerEnvFileArg(runtime.envName, runtime.env.config ?? {});
76
+ }
77
+ export async function readManagedRuntimeEnvValues(runtime) {
78
+ const envFilePath = await resolveManagedRuntimeEnvFilePath(runtime);
79
+ if (!envFilePath) {
80
+ return {
81
+ envFilePath: undefined,
82
+ envValues: {},
83
+ };
84
+ }
85
+ try {
86
+ return {
87
+ envFilePath,
88
+ envValues: parseSimpleEnvFile(await readFile(envFilePath, 'utf8')),
89
+ };
90
+ }
91
+ catch (error) {
92
+ const code = error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
93
+ if (code === 'ENOENT') {
94
+ return {
95
+ envFilePath,
96
+ envValues: {},
97
+ };
98
+ }
99
+ throw error;
100
+ }
101
+ }
@@ -0,0 +1,32 @@
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 trimValue(value) {
10
+ return String(value ?? '').trim();
11
+ }
12
+ export function resolveManagedSetupState(value) {
13
+ return value === 'prepared' || value === 'installed' ? value : undefined;
14
+ }
15
+ export function isPreparedSetupState(value) {
16
+ return resolveManagedSetupState(value) === 'prepared';
17
+ }
18
+ export function buildInitAppEnvVarsFromConfig(config) {
19
+ const out = {};
20
+ const put = (key, value) => {
21
+ const text = trimValue(value);
22
+ if (text) {
23
+ out[key] = text;
24
+ }
25
+ };
26
+ put('INIT_APP_LANG', config?.lang);
27
+ put('INIT_ROOT_USERNAME', config?.rootUsername);
28
+ put('INIT_ROOT_EMAIL', config?.rootEmail);
29
+ put('INIT_ROOT_PASSWORD', config?.rootPassword);
30
+ put('INIT_ROOT_NICKNAME', config?.rootNickname);
31
+ return out;
32
+ }