@nocobase/cli 2.3.0-beta.6 → 2.4.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/commands/api/swagger/get.js +96 -0
  2. package/dist/commands/api/swagger/index.js +20 -0
  3. package/dist/commands/api/swagger/list.js +92 -0
  4. package/dist/commands/config/set.js +1 -0
  5. package/dist/commands/env/add.js +6 -0
  6. package/dist/commands/env/update.js +13 -0
  7. package/dist/commands/init.js +51 -5
  8. package/dist/commands/install.js +60 -3
  9. package/dist/commands/portal/config.js +99 -0
  10. package/dist/commands/portal/create.js +96 -0
  11. package/dist/commands/portal/deploy.js +81 -0
  12. package/dist/commands/portal/destroy.js +126 -0
  13. package/dist/commands/portal/dev.js +73 -0
  14. package/dist/commands/portal/index.js +20 -0
  15. package/dist/commands/portal/info.js +82 -0
  16. package/dist/commands/portal/list.js +98 -0
  17. package/dist/commands/portal/pull.js +113 -0
  18. package/dist/commands/portal/push.js +79 -0
  19. package/dist/commands/source/dev.js +1 -1
  20. package/dist/lib/api-client.js +35 -9
  21. package/dist/lib/app-client-entry-mode.js +28 -0
  22. package/dist/lib/app-managed-resources.js +2 -0
  23. package/dist/lib/auth-store.js +55 -2
  24. package/dist/lib/bootstrap.js +3 -1
  25. package/dist/lib/cli-config.js +20 -1
  26. package/dist/lib/env-auth.js +2 -36
  27. package/dist/lib/env-command-config.js +1 -0
  28. package/dist/lib/env-config.js +11 -0
  29. package/dist/lib/env-portal-config.js +30 -0
  30. package/dist/lib/env-proxy.js +154 -7
  31. package/dist/lib/managed-env-file.js +119 -9
  32. package/dist/lib/managed-init-env.js +4 -1
  33. package/dist/lib/portal-build-html.js +27 -0
  34. package/dist/lib/portal-command-env.js +31 -0
  35. package/dist/lib/portal-config.js +119 -0
  36. package/dist/lib/portal-configure.js +110 -0
  37. package/dist/lib/portal-create.js +515 -0
  38. package/dist/lib/portal-deploy.js +266 -0
  39. package/dist/lib/portal-destroy.js +114 -0
  40. package/dist/lib/portal-dev.js +78 -0
  41. package/dist/lib/portal-env-files.js +54 -0
  42. package/dist/lib/portal-info.js +28 -0
  43. package/dist/lib/portal-list.js +205 -0
  44. package/dist/lib/portal-path-safety.js +76 -0
  45. package/dist/lib/portal-source.js +692 -0
  46. package/dist/lib/prompt-catalog-core.js +2 -2
  47. package/dist/lib/prompt-catalog-terminal.js +4 -5
  48. package/dist/lib/prompt-web-ui.js +12 -6
  49. package/dist/lib/proxy-caddy.js +2 -0
  50. package/dist/lib/proxy-nginx.js +1 -0
  51. package/dist/lib/run-npm.js +85 -20
  52. package/dist/lib/swagger-command.js +52 -0
  53. package/dist/lib/ui.js +28 -1
  54. package/dist/locale/en-US.json +245 -1
  55. package/dist/locale/zh-CN.json +245 -1
  56. package/package.json +5 -2
@@ -0,0 +1,266 @@
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 { chmod, mkdtemp, readdir, rm, stat } from 'node:fs/promises';
10
+ import os from 'node:os';
11
+ import path from 'node:path';
12
+ import * as tar from 'tar';
13
+ import { executeApiRequest } from './api-client.js';
14
+ import { translateCli } from './cli-locale.js';
15
+ import { ensurePortalBuildHtmlReadsEnvOnly } from './portal-build-html.js';
16
+ import { buildPortalBasePath, resolvePortalAppContext, resolvePortalEnvApiUrl, resolveSavedPortalSourcePath, resolvePortalSourcePath, titleFromPortalSlug, validatePortalSlug, } from './portal-create.js';
17
+ import { buildPortalCommandEnv } from './portal-command-env.js';
18
+ import { updatePortalEnvFiles } from './portal-env-files.js';
19
+ import { resolvePnpmInstallCommand, run, runPnpmCommand, runPnpmInstallCommand } from './run-npm.js';
20
+ const portalDeployText = (key, values, fallback) => translateCli(`commands.portalDeploy.${key}`, values, { fallback });
21
+ const DEPLOY_OPERATION = {
22
+ method: 'POST',
23
+ pathTemplate: '/multiPortals:deploy',
24
+ requestContentType: 'multipart/form-data',
25
+ hasBody: true,
26
+ bodyRequired: true,
27
+ parameters: [
28
+ {
29
+ name: 'file',
30
+ flagName: 'file',
31
+ in: 'body',
32
+ required: true,
33
+ isFile: true,
34
+ },
35
+ {
36
+ name: 'app',
37
+ flagName: 'app',
38
+ in: 'body',
39
+ required: true,
40
+ },
41
+ {
42
+ name: 'portal',
43
+ flagName: 'portal',
44
+ in: 'body',
45
+ required: true,
46
+ },
47
+ {
48
+ name: 'basePath',
49
+ flagName: 'basePath',
50
+ in: 'body',
51
+ required: true,
52
+ },
53
+ ],
54
+ };
55
+ const FIRST_OR_CREATE_PORTAL_OPERATION = {
56
+ method: 'POST',
57
+ pathTemplate: '/multiPortals:firstOrCreate',
58
+ hasBody: true,
59
+ bodyRequired: true,
60
+ parameters: [
61
+ {
62
+ name: 'filterKeys[]',
63
+ flagName: 'filterKeys',
64
+ in: 'query',
65
+ required: true,
66
+ isArray: true,
67
+ },
68
+ ],
69
+ };
70
+ const DEFAULT_PORTAL_UI_LAYOUT_UID = 'admin-layout-model';
71
+ const PORTAL_PUBLIC_DIR_MODE = 0o755;
72
+ const PORTAL_PUBLIC_FILE_MODE = 0o644;
73
+ function trimValue(value) {
74
+ return String(value ?? '').trim();
75
+ }
76
+ function readDistPathFromUploadResponse(data) {
77
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
78
+ return undefined;
79
+ }
80
+ const directDistPath = data.distPath;
81
+ if (typeof directDistPath === 'string' && directDistPath.trim()) {
82
+ return directDistPath;
83
+ }
84
+ return readDistPathFromUploadResponse(data.data);
85
+ }
86
+ async function pathExists(target) {
87
+ try {
88
+ await stat(target);
89
+ return true;
90
+ }
91
+ catch {
92
+ return false;
93
+ }
94
+ }
95
+ async function chmodPortalDistTree(targetDir) {
96
+ await chmod(targetDir, PORTAL_PUBLIC_DIR_MODE);
97
+ const entries = await readdir(targetDir, { withFileTypes: true });
98
+ await Promise.all(entries.map(async (entry) => {
99
+ const entryPath = path.join(targetDir, entry.name);
100
+ if (entry.isDirectory()) {
101
+ await chmodPortalDistTree(entryPath);
102
+ return;
103
+ }
104
+ if (entry.isFile()) {
105
+ await chmod(entryPath, PORTAL_PUBLIC_FILE_MODE);
106
+ }
107
+ }));
108
+ }
109
+ async function assertFileExists(filePath, message) {
110
+ try {
111
+ const fileStat = await stat(filePath);
112
+ if (fileStat.isFile()) {
113
+ return;
114
+ }
115
+ }
116
+ catch {
117
+ // Throw the normalized message below.
118
+ }
119
+ throw new Error(message);
120
+ }
121
+ async function packPortalDist(distDir) {
122
+ const tempDir = await mkdtemp(path.join(os.tmpdir(), 'nocobase-cli-portal-dist-'));
123
+ const archivePath = path.join(tempDir, 'dist.tar.gz');
124
+ const entries = await readdir(distDir);
125
+ await tar.create({
126
+ cwd: distDir,
127
+ file: archivePath,
128
+ gzip: true,
129
+ }, entries);
130
+ return {
131
+ archivePath,
132
+ cleanup: async () => {
133
+ await rm(tempDir, { recursive: true, force: true });
134
+ },
135
+ };
136
+ }
137
+ async function uploadPortalDist(params) {
138
+ const apiRequest = params.apiRequest ?? executeApiRequest;
139
+ const response = await apiRequest({
140
+ cliVersion: params.cliVersion ?? '',
141
+ envName: params.envName,
142
+ flags: {
143
+ file: params.archivePath,
144
+ app: params.app,
145
+ portal: params.portal,
146
+ basePath: params.portalBase,
147
+ },
148
+ operation: DEPLOY_OPERATION,
149
+ });
150
+ if (!response.ok) {
151
+ throw new Error(portalDeployText('errors.uploadFailed', { status: response.status, details: JSON.stringify(response.data, null, 2) }, `Portal dist upload failed with status ${response.status}\n${JSON.stringify(response.data, null, 2)}`));
152
+ }
153
+ return {
154
+ distPath: readDistPathFromUploadResponse(response.data),
155
+ };
156
+ }
157
+ async function syncMultiPortalRecord(params) {
158
+ const apiRequest = params.apiRequest ?? executeApiRequest;
159
+ const body = {
160
+ uid: params.portal,
161
+ title: titleFromPortalSlug(params.portal),
162
+ portalType: 'ai',
163
+ portalName: params.portal,
164
+ routePath: `/${params.portal}`,
165
+ authCheck: true,
166
+ enabled: true,
167
+ uiLayoutUid: DEFAULT_PORTAL_UI_LAYOUT_UID,
168
+ skipCreatePortalDirectory: true,
169
+ };
170
+ const response = await apiRequest({
171
+ cliVersion: params.cliVersion ?? '',
172
+ envName: params.envName,
173
+ flags: {
174
+ filterKeys: ['portalName'],
175
+ body: JSON.stringify(body),
176
+ },
177
+ operation: FIRST_OR_CREATE_PORTAL_OPERATION,
178
+ });
179
+ if (!response.ok) {
180
+ throw new Error(portalDeployText('errors.recordSyncFailed', { status: response.status, details: JSON.stringify(response.data, null, 2) }, `Portal record sync failed with status ${response.status}\n${JSON.stringify(response.data, null, 2)}`));
181
+ }
182
+ }
183
+ export async function deployPortalWorkspace(options) {
184
+ const portal = validatePortalSlug(options.portal);
185
+ const apiBaseUrl = trimValue(options.env.apiBaseUrl);
186
+ const envApiUrl = resolvePortalEnvApiUrl(apiBaseUrl);
187
+ const { app, appPublicPath, portalBaseApp } = await resolvePortalAppContext(options);
188
+ const portalBase = buildPortalBasePath({ app: portalBaseApp ?? app, appPublicPath, portal });
189
+ const deployBase = buildPortalBasePath({ app, appPublicPath, portal });
190
+ const portalDir = resolveSavedPortalSourcePath(options.env, portal) ?? resolvePortalSourcePath(portal);
191
+ const distDir = path.join(portalDir, 'dist');
192
+ if (!(await pathExists(portalDir))) {
193
+ throw new Error(portalDeployText('errors.workspaceMissing', { portalDir, portal }, `Portal does not exist: ${portalDir}\nRun \`nb portal create ${portal}\` first.`));
194
+ }
195
+ await assertFileExists(path.join(portalDir, 'package.json'), portalDeployText('errors.packageJsonMissing', { portalDir }, `Portal is invalid: package.json is missing in ${portalDir}.`));
196
+ await updatePortalEnvFiles({
197
+ portalDir,
198
+ apiBaseUrl,
199
+ portalBase,
200
+ });
201
+ await ensurePortalBuildHtmlReadsEnvOnly(portalDir);
202
+ const runCommand = options.runCommand ?? run;
203
+ const installCommand = await resolvePnpmInstallCommand(portalDir);
204
+ await runPnpmInstallCommand(runCommand, installCommand.args, {
205
+ cwd: portalDir,
206
+ env: buildPortalCommandEnv(),
207
+ envMode: 'replace',
208
+ errorName: installCommand.errorName,
209
+ });
210
+ await runPnpmCommand(runCommand, ['build'], {
211
+ cwd: portalDir,
212
+ env: buildPortalCommandEnv({
213
+ NOCOBASE_API_URL: apiBaseUrl,
214
+ NOCOBASE_PORTAL_BASE: portalBase,
215
+ }),
216
+ envMode: 'replace',
217
+ errorName: 'pnpm build',
218
+ });
219
+ await runPnpmCommand(runCommand, ['build:html'], {
220
+ cwd: portalDir,
221
+ env: buildPortalCommandEnv({
222
+ NOCOBASE_API_URL: envApiUrl,
223
+ NOCOBASE_PORTAL_BASE: portalBase,
224
+ }),
225
+ envMode: 'replace',
226
+ errorName: 'pnpm build:html',
227
+ });
228
+ await assertFileExists(path.join(distDir, 'index.html'), portalDeployText('errors.distMissing', { distDir }, `Portal build did not produce ${path.join(distDir, 'index.html')}.`));
229
+ await chmodPortalDistTree(distDir);
230
+ if (options.env.kind !== 'local' && options.env.kind !== 'docker' && options.env.kind !== 'http') {
231
+ throw new Error(portalDeployText('errors.unsupportedEnvKind', { kind: options.env.kind }, `Cannot deploy a portal for ${options.env.kind} envs in the first version.`));
232
+ }
233
+ const archive = await packPortalDist(distDir);
234
+ let uploadResult;
235
+ try {
236
+ uploadResult = await uploadPortalDist({
237
+ archivePath: archive.archivePath,
238
+ app,
239
+ portal,
240
+ portalBase: deployBase,
241
+ envName: options.envName,
242
+ cliVersion: options.cliVersion,
243
+ apiRequest: options.apiRequest,
244
+ });
245
+ }
246
+ finally {
247
+ await archive.cleanup();
248
+ }
249
+ await syncMultiPortalRecord({
250
+ portal,
251
+ envName: options.envName,
252
+ cliVersion: options.cliVersion,
253
+ apiRequest: options.apiRequest,
254
+ });
255
+ return {
256
+ app,
257
+ portal,
258
+ portalDir,
259
+ portalBase,
260
+ distDir,
261
+ serverDistPath: uploadResult.distPath,
262
+ mode: options.env.kind,
263
+ uploaded: true,
264
+ recordSynced: true,
265
+ };
266
+ }
@@ -0,0 +1,114 @@
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 { rm, stat } from 'node:fs/promises';
10
+ import path from 'node:path';
11
+ import { executeApiRequest } from './api-client.js';
12
+ import { translateCli } from './cli-locale.js';
13
+ import { isUnsafePortalDeletePath } from './portal-path-safety.js';
14
+ import { buildPortalBasePath, resolvePortalAppContext, resolvePortalDeployPath, resolveSavedPortalSourcePath, resolvePortalStoragePath, validatePortalSlug, } from './portal-create.js';
15
+ const portalDestroyText = (key, values, fallback) => translateCli(`commands.portalDestroy.${key}`, values, { fallback });
16
+ const DESTROY_PORTAL_OPERATION = {
17
+ method: 'POST',
18
+ pathTemplate: '/multiPortals:destroy',
19
+ parameters: [
20
+ {
21
+ name: 'filter',
22
+ flagName: 'filter',
23
+ in: 'query',
24
+ type: 'object',
25
+ required: true,
26
+ },
27
+ ],
28
+ };
29
+ async function pathExists(target) {
30
+ try {
31
+ await stat(target);
32
+ return true;
33
+ }
34
+ catch {
35
+ return false;
36
+ }
37
+ }
38
+ function assertPortalDirIsInsideParent(parentDir, portalDir) {
39
+ const relative = path.relative(parentDir, portalDir);
40
+ if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
41
+ throw new Error(portalDestroyText('errors.outsideParent', { parentDir, portalDir }, `Refusing to delete a portal outside ${parentDir}: ${portalDir}`));
42
+ }
43
+ }
44
+ async function destroyMultiPortalRecord(params) {
45
+ const apiRequest = params.apiRequest ?? executeApiRequest;
46
+ const response = await apiRequest({
47
+ cliVersion: params.cliVersion ?? '',
48
+ envName: params.envName,
49
+ flags: {
50
+ filter: {
51
+ portalName: params.portal,
52
+ },
53
+ },
54
+ operation: DESTROY_PORTAL_OPERATION,
55
+ });
56
+ if (response.ok) {
57
+ return true;
58
+ }
59
+ if (params.force && response.status === 404) {
60
+ return false;
61
+ }
62
+ throw new Error(portalDestroyText('errors.recordDestroyFailed', { status: response.status, details: JSON.stringify(response.data, null, 2) }, `Portal record destroy failed with status ${response.status}\n${JSON.stringify(response.data, null, 2)}`));
63
+ }
64
+ export async function destroyPortalWorkspace(options) {
65
+ const portal = validatePortalSlug(options.portal);
66
+ const storagePath = resolvePortalStoragePath(options.env);
67
+ const { app, appPublicPath, portalBaseApp } = await resolvePortalAppContext(options);
68
+ const portalBase = buildPortalBasePath({ app: portalBaseApp ?? app, appPublicPath, portal });
69
+ const portalDeployDir = resolvePortalDeployPath({ storagePath, app, portal });
70
+ const portalDevDir = resolveSavedPortalSourcePath(options.env, portal) ?? '';
71
+ const mode = options.env.kind;
72
+ if (mode !== 'local' && mode !== 'docker' && mode !== 'http') {
73
+ throw new Error(portalDestroyText('errors.unsupportedEnvKind', { kind: mode }, `Cannot destroy a portal for ${mode} envs in the first version.`));
74
+ }
75
+ const destroyMode = mode;
76
+ if (portalDevDir) {
77
+ assertPortalDirIsInsideParent(path.dirname(portalDevDir), portalDevDir);
78
+ }
79
+ assertPortalDirIsInsideParent(path.dirname(portalDeployDir), portalDeployDir);
80
+ const developmentPathIsDeploymentPath = portalDevDir
81
+ ? path.resolve(portalDevDir) === path.resolve(portalDeployDir)
82
+ : false;
83
+ const deploymentPathExists = await pathExists(portalDeployDir);
84
+ const developmentPathExists = options.deleteDevPath && portalDevDir && !developmentPathIsDeploymentPath
85
+ ? await pathExists(portalDevDir)
86
+ : false;
87
+ if (developmentPathExists && (await isUnsafePortalDeletePath(portalDevDir))) {
88
+ throw new Error(portalDestroyText('errors.unsafeDevelopmentPath', { portalDir: portalDevDir }, `Refusing to delete an unsafe portal development path: ${portalDevDir}`));
89
+ }
90
+ const recordDeleted = await destroyMultiPortalRecord({
91
+ portal,
92
+ envName: options.envName,
93
+ cliVersion: options.cliVersion,
94
+ force: options.force,
95
+ apiRequest: options.apiRequest,
96
+ });
97
+ if (deploymentPathExists) {
98
+ await rm(portalDeployDir, { recursive: true, force: true });
99
+ }
100
+ if (developmentPathExists) {
101
+ await rm(portalDevDir, { recursive: true, force: true });
102
+ }
103
+ return {
104
+ app,
105
+ portal,
106
+ developmentPath: portalDevDir,
107
+ deploymentPath: portalDeployDir,
108
+ portalBase,
109
+ mode: destroyMode,
110
+ recordDeleted,
111
+ developmentPathDeleted: developmentPathExists || (developmentPathIsDeploymentPath && deploymentPathExists),
112
+ deploymentPathDeleted: deploymentPathExists,
113
+ };
114
+ }
@@ -0,0 +1,78 @@
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 { stat } from 'node:fs/promises';
10
+ import path from 'node:path';
11
+ import { translateCli } from './cli-locale.js';
12
+ import { buildPortalBasePath, resolvePortalAppContext, resolveSavedPortalSourcePath, resolvePortalSourcePath, validatePortalSlug, } from './portal-create.js';
13
+ import { buildPortalCommandEnv } from './portal-command-env.js';
14
+ import { updatePortalEnvFiles } from './portal-env-files.js';
15
+ import { run, runPnpmCommand } from './run-npm.js';
16
+ const portalDevText = (key, values, fallback) => translateCli(`commands.portalDev.${key}`, values, { fallback });
17
+ function trimValue(value) {
18
+ return String(value ?? '').trim();
19
+ }
20
+ async function pathExists(target) {
21
+ try {
22
+ await stat(target);
23
+ return true;
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ }
29
+ async function assertFileExists(filePath, message) {
30
+ try {
31
+ const fileStat = await stat(filePath);
32
+ if (fileStat.isFile()) {
33
+ return;
34
+ }
35
+ }
36
+ catch {
37
+ // Throw the normalized message below.
38
+ }
39
+ throw new Error(message);
40
+ }
41
+ export async function devPortalWorkspace(options) {
42
+ const portal = validatePortalSlug(options.portal);
43
+ const apiBaseUrl = trimValue(options.env.apiBaseUrl);
44
+ if (options.env.kind === 'ssh') {
45
+ throw new Error(portalDevText('errors.sshUnsupported', undefined, 'Cannot start a portal in dev mode for ssh envs in the first version.'));
46
+ }
47
+ const { app, appPublicPath, portalBaseApp } = await resolvePortalAppContext(options);
48
+ const portalBase = buildPortalBasePath({ app: portalBaseApp ?? app, appPublicPath, portal });
49
+ const portalDir = resolveSavedPortalSourcePath(options.env, portal) ?? resolvePortalSourcePath(portal);
50
+ if (!(await pathExists(portalDir))) {
51
+ throw new Error(portalDevText('errors.workspaceMissing', { portalDir, portal }, `Portal does not exist: ${portalDir}\nRun \`nb portal create ${portal}\` first.`));
52
+ }
53
+ await assertFileExists(path.join(portalDir, 'package.json'), portalDevText('errors.packageJsonMissing', { portalDir }, `Portal is invalid: package.json is missing in ${portalDir}.`));
54
+ await updatePortalEnvFiles({
55
+ portalDir,
56
+ apiBaseUrl,
57
+ portalBase,
58
+ });
59
+ const result = {
60
+ app,
61
+ portal,
62
+ portalDir,
63
+ portalBase,
64
+ mode: options.env.kind,
65
+ };
66
+ options.onStart?.(result);
67
+ const runCommand = options.runCommand ?? run;
68
+ await runPnpmCommand(runCommand, ['dev'], {
69
+ cwd: portalDir,
70
+ env: buildPortalCommandEnv({
71
+ NOCOBASE_API_URL: apiBaseUrl,
72
+ NOCOBASE_PORTAL_BASE: portalBase,
73
+ }),
74
+ envMode: 'replace',
75
+ errorName: 'pnpm dev',
76
+ });
77
+ return result;
78
+ }
@@ -0,0 +1,54 @@
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, writeFile } from 'node:fs/promises';
10
+ import path from 'node:path';
11
+ import { resolvePortalEnvApiUrl } from './portal-create.js';
12
+ function upsertEnvContent(content, values) {
13
+ const nextValues = { ...values };
14
+ const lines = content ? content.replace(/\r\n/g, '\n').split('\n') : [];
15
+ const result = [];
16
+ for (const line of lines) {
17
+ if (!line && result.length === lines.length - 1) {
18
+ continue;
19
+ }
20
+ const match = line.match(/^(\s*)([A-Za-z_][A-Za-z0-9_]*)\s*=/);
21
+ const key = match?.[2];
22
+ if (key && Object.prototype.hasOwnProperty.call(nextValues, key)) {
23
+ result.push(`${key}=${nextValues[key]}`);
24
+ delete nextValues[key];
25
+ continue;
26
+ }
27
+ result.push(line);
28
+ }
29
+ for (const [key, value] of Object.entries(nextValues)) {
30
+ result.push(`${key}=${value}`);
31
+ }
32
+ return `${result.join('\n').replace(/\n*$/, '')}\n`;
33
+ }
34
+ export async function upsertPortalEnvFile(filePath, values) {
35
+ let content = '';
36
+ try {
37
+ content = await readFile(filePath, 'utf-8');
38
+ }
39
+ catch {
40
+ content = '';
41
+ }
42
+ await writeFile(filePath, upsertEnvContent(content, values), 'utf-8');
43
+ }
44
+ export async function updatePortalEnvFiles(params) {
45
+ const envApiUrl = resolvePortalEnvApiUrl(params.apiBaseUrl);
46
+ await upsertPortalEnvFile(path.join(params.portalDir, '.env'), {
47
+ NOCOBASE_API_URL: envApiUrl,
48
+ NOCOBASE_PORTAL_BASE: params.portalBase,
49
+ });
50
+ await upsertPortalEnvFile(path.join(params.portalDir, '.env.local'), {
51
+ NOCOBASE_API_URL: params.apiBaseUrl,
52
+ NOCOBASE_PORTAL_BASE: params.portalBase,
53
+ });
54
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ import { translateCli } from './cli-locale.js';
10
+ import { toPortalOutputItem } from './portal-list.js';
11
+ const portalInfoText = (key, values, fallback) => translateCli(`commands.portalInfo.${key}`, values, { fallback });
12
+ function formatBoolean(value) {
13
+ return value ? 'yes' : 'no';
14
+ }
15
+ export function findPortalListItem(items, portal) {
16
+ return items.find((item) => item.portalName === portal || item.uid === portal);
17
+ }
18
+ export function formatPortalInfo(item) {
19
+ const outputItem = toPortalOutputItem(item);
20
+ return [
21
+ `${portalInfoText('fields.name', undefined, 'Name')}: ${outputItem.name}`,
22
+ `${portalInfoText('fields.url', undefined, 'URL')}: ${outputItem.url}`,
23
+ `${portalInfoText('fields.portalType', undefined, 'Portal type')}: ${outputItem.portalType}`,
24
+ `${portalInfoText('fields.developmentPath', undefined, 'Development path')}: ${outputItem.developmentPath}`,
25
+ `${portalInfoText('fields.deploymentPath', undefined, 'Deployment path')}: ${outputItem.deploymentPath}`,
26
+ `${portalInfoText('fields.enabled', undefined, 'Enabled')}: ${formatBoolean(outputItem.enabled)}`,
27
+ ].join('\n');
28
+ }