@nocobase/cli 2.3.0-beta.6 → 2.4.0-alpha.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 (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,205 @@
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 { appendAppPublicPath } from './app-public-path.js';
10
+ import { executeApiRequest } from './api-client.js';
11
+ import { translateCli } from './cli-locale.js';
12
+ import { buildPortalBasePath, resolvePortalDeployPath, resolvePortalAppContext, resolveSavedPortalSourcePath, resolvePortalStoragePath, } from './portal-create.js';
13
+ const portalListText = (key, values, fallback) => translateCli(`commands.portalList.${key}`, values, { fallback });
14
+ const LIST_PORTALS_OPERATION = {
15
+ method: 'GET',
16
+ pathTemplate: '/multiPortals:list',
17
+ parameters: [
18
+ {
19
+ name: 'pageSize',
20
+ flagName: 'pageSize',
21
+ in: 'query',
22
+ type: 'integer',
23
+ },
24
+ {
25
+ name: 'sort[]',
26
+ flagName: 'sort',
27
+ in: 'query',
28
+ isArray: true,
29
+ },
30
+ ],
31
+ };
32
+ function trimValue(value) {
33
+ return String(value ?? '').trim();
34
+ }
35
+ function readRecordString(record, key) {
36
+ return trimValue(record[key]);
37
+ }
38
+ function readRecordBoolean(record, key) {
39
+ return record[key] === true;
40
+ }
41
+ function readRecordObject(record, key) {
42
+ const value = record[key];
43
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
44
+ return {};
45
+ }
46
+ return value;
47
+ }
48
+ function readListData(data) {
49
+ if (!data || typeof data !== 'object' || Array.isArray(data)) {
50
+ return [];
51
+ }
52
+ const directData = data.data;
53
+ if (Array.isArray(directData)) {
54
+ return directData.filter((item) => (Boolean(item) && typeof item === 'object' && !Array.isArray(item)));
55
+ }
56
+ return readListData(directData);
57
+ }
58
+ function buildPortalAccessUrl(apiBaseUrl, portalBase) {
59
+ try {
60
+ const baseUrl = new URL(apiBaseUrl);
61
+ return new URL(portalBase, baseUrl.origin).toString();
62
+ }
63
+ catch {
64
+ return portalBase;
65
+ }
66
+ }
67
+ function normalizeRootPath(pathname) {
68
+ const trimmed = pathname.trim();
69
+ if (!trimmed || trimmed === '/') {
70
+ return '/';
71
+ }
72
+ return `/${trimmed.replace(/^\/+/, '').replace(/\/+$/, '')}`;
73
+ }
74
+ function isAbsoluteUrl(value) {
75
+ return /^[a-z][a-z\d+\-.]*:\/\//i.test(value) || value.startsWith('//');
76
+ }
77
+ function stripBasePath(pathname, basePath) {
78
+ const pathValue = normalizeRootPath(pathname);
79
+ const baseValue = normalizeRootPath(basePath);
80
+ if (pathValue === baseValue) {
81
+ return '/';
82
+ }
83
+ if (pathValue.startsWith(`${baseValue}/`)) {
84
+ return pathValue.slice(baseValue.length) || '/';
85
+ }
86
+ return pathValue;
87
+ }
88
+ function normalizeNoCodeRoutePath(routePath, appPublicPath, app) {
89
+ let normalizedRoutePath = normalizeRootPath(routePath);
90
+ const basePaths = app === 'main'
91
+ ? []
92
+ : [
93
+ appendAppPublicPath(appPublicPath, `v/apps/${app}`, { trailingSlash: false }),
94
+ appendAppPublicPath(appPublicPath, `x/apps/${app}`, { trailingSlash: false }),
95
+ `/v/apps/${app}`,
96
+ `/x/apps/${app}`,
97
+ ];
98
+ for (const basePath of [
99
+ ...basePaths,
100
+ appendAppPublicPath(appPublicPath, 'v', { trailingSlash: false }),
101
+ appendAppPublicPath(appPublicPath, 'x', { trailingSlash: false }),
102
+ '/v',
103
+ '/x',
104
+ ]) {
105
+ normalizedRoutePath = stripBasePath(normalizedRoutePath, basePath);
106
+ }
107
+ return normalizedRoutePath;
108
+ }
109
+ function buildNoCodePortalBasePath(params) {
110
+ if (isAbsoluteUrl(params.routePath)) {
111
+ return params.routePath;
112
+ }
113
+ const normalizedRoutePath = normalizeNoCodeRoutePath(params.routePath, params.appPublicPath, params.app);
114
+ const routeSegment = normalizedRoutePath.replace(/^\/+/, '');
115
+ let segment = normalizedRoutePath === '/' ? 'v' : `v/${routeSegment}`;
116
+ if (params.app !== 'main') {
117
+ segment = normalizedRoutePath === '/' ? `v/apps/${params.app}` : `v/apps/${params.app}/${routeSegment}`;
118
+ }
119
+ return appendAppPublicPath(params.appPublicPath, segment, { trailingSlash: normalizedRoutePath === '/' });
120
+ }
121
+ export function toPortalOutputItem(item) {
122
+ return {
123
+ name: item.portalName,
124
+ url: item.portalUrl,
125
+ portalType: item.portalType,
126
+ developmentPath: item.portalDir,
127
+ deploymentPath: item.deployDir,
128
+ enabled: item.enabled,
129
+ isDefault: item.isDefault,
130
+ sourceStorage: item.sourceStorage,
131
+ };
132
+ }
133
+ async function listMultiPortalRecords(params) {
134
+ const apiRequest = params.apiRequest ?? executeApiRequest;
135
+ const response = await apiRequest({
136
+ cliVersion: params.cliVersion ?? '',
137
+ envName: params.envName,
138
+ flags: {
139
+ pageSize: 200,
140
+ sort: ['portalName'],
141
+ },
142
+ operation: LIST_PORTALS_OPERATION,
143
+ });
144
+ if (!response.ok) {
145
+ throw new Error(portalListText('errors.listFailed', { status: response.status, details: JSON.stringify(response.data, null, 2) }, `Portal list failed with status ${response.status}\n${JSON.stringify(response.data, null, 2)}`));
146
+ }
147
+ return readListData(response.data);
148
+ }
149
+ export async function listPortalWorkspaces(options) {
150
+ const apiBaseUrl = trimValue(options.env.apiBaseUrl);
151
+ const storagePath = resolvePortalStoragePath(options.env);
152
+ const { app, appPublicPath, portalBaseApp } = options.appContext ?? (await resolvePortalAppContext(options));
153
+ const mode = options.env.kind;
154
+ const baseApp = portalBaseApp ?? app;
155
+ if (mode !== 'local' && mode !== 'docker' && mode !== 'http') {
156
+ throw new Error(portalListText('errors.unsupportedEnvKind', { kind: mode }, `Cannot list portals for ${mode} envs in the first version.`));
157
+ }
158
+ const listMode = mode;
159
+ const records = await listMultiPortalRecords({
160
+ envName: options.envName,
161
+ cliVersion: options.cliVersion,
162
+ apiRequest: options.apiRequest,
163
+ });
164
+ const items = records.map((record) => {
165
+ const uid = readRecordString(record, 'uid');
166
+ const portalName = readRecordString(record, 'portalName') || uid;
167
+ const routePath = readRecordString(record, 'routePath') || `/${portalName}`;
168
+ const portalType = readRecordString(record, 'portalType');
169
+ const enabled = readRecordBoolean(record, 'enabled');
170
+ const isDefault = readRecordBoolean(record, 'isDefault');
171
+ const recordOptions = readRecordObject(record, 'options');
172
+ const git = readRecordObject(recordOptions, 'git');
173
+ const sourceStorage = trimValue(recordOptions.sourceStorage) || readRecordString(record, 'sourceStorage') || 'nocobase';
174
+ const isAi = portalType === 'ai';
175
+ const portalDir = isAi ? resolveSavedPortalSourcePath(options.env, portalName) ?? '' : '';
176
+ const deployDir = isAi ? resolvePortalDeployPath({ storagePath, app, portal: portalName }) : '';
177
+ return {
178
+ uid,
179
+ portalName,
180
+ routePath,
181
+ portalType,
182
+ enabled,
183
+ isDefault,
184
+ sourceStorage,
185
+ gitRepo: trimValue(git.repo) || readRecordString(record, 'gitRepo'),
186
+ gitBranch: trimValue(git.branch) || readRecordString(record, 'gitBranch'),
187
+ gitPath: trimValue(git.path) || readRecordString(record, 'gitPath'),
188
+ sourceRevision: trimValue(recordOptions.sourceRevision) || readRecordString(record, 'sourceRevision'),
189
+ options: recordOptions,
190
+ portalUrl: enabled
191
+ ? buildPortalAccessUrl(apiBaseUrl, isAi
192
+ ? buildPortalBasePath({ app: baseApp, appPublicPath, portal: portalName })
193
+ : buildNoCodePortalBasePath({ app: baseApp, appPublicPath, routePath }))
194
+ : '',
195
+ portalDir,
196
+ deployDir,
197
+ };
198
+ });
199
+ return {
200
+ app,
201
+ mode: listMode,
202
+ storagePath,
203
+ items,
204
+ };
205
+ }
@@ -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
+ import { readdir, readFile, realpath, stat } from 'node:fs/promises';
10
+ import os from 'node:os';
11
+ import path from 'node:path';
12
+ async function resolveExistingPath(target) {
13
+ const resolved = path.resolve(target);
14
+ try {
15
+ return await realpath(resolved);
16
+ }
17
+ catch {
18
+ return resolved;
19
+ }
20
+ }
21
+ function isSameOrAncestor(candidate, child) {
22
+ const relative = path.relative(candidate, child);
23
+ return !relative || (!relative.startsWith('..') && !path.isAbsolute(relative));
24
+ }
25
+ export async function isUnsafePortalDeletePath(target) {
26
+ const resolvedTarget = await resolveExistingPath(target);
27
+ const root = path.parse(resolvedTarget).root;
28
+ if (resolvedTarget === root) {
29
+ return true;
30
+ }
31
+ const homeDir = await resolveExistingPath(os.homedir());
32
+ if (resolvedTarget === homeDir) {
33
+ return true;
34
+ }
35
+ const cwd = await resolveExistingPath(process.cwd());
36
+ return isSameOrAncestor(resolvedTarget, cwd);
37
+ }
38
+ async function isDirectory(target) {
39
+ try {
40
+ return (await stat(target)).isDirectory();
41
+ }
42
+ catch {
43
+ return false;
44
+ }
45
+ }
46
+ async function isEmptyDirectory(target) {
47
+ try {
48
+ return (await readdir(target)).length === 0;
49
+ }
50
+ catch {
51
+ return false;
52
+ }
53
+ }
54
+ async function hasNocoBasePackageField(target) {
55
+ try {
56
+ const data = JSON.parse(await readFile(path.join(target, 'package.json'), 'utf-8'));
57
+ return (!!data &&
58
+ typeof data === 'object' &&
59
+ !Array.isArray(data) &&
60
+ Object.prototype.hasOwnProperty.call(data, 'nocobase'));
61
+ }
62
+ catch {
63
+ return false;
64
+ }
65
+ }
66
+ export async function canReplacePortalDirectory(target) {
67
+ const resolvedTarget = await resolveExistingPath(target);
68
+ const root = path.parse(resolvedTarget).root;
69
+ if (resolvedTarget === root || resolvedTarget === (await resolveExistingPath(os.homedir()))) {
70
+ return false;
71
+ }
72
+ if (!(await isDirectory(resolvedTarget))) {
73
+ return false;
74
+ }
75
+ return (await isEmptyDirectory(resolvedTarget)) || (await hasNocoBasePackageField(resolvedTarget));
76
+ }