@nocobase/cli 2.3.0-alpha.1 → 3.0.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.
- package/assets/env-proxy/nginx/snippets/uploads-location.conf +4 -1
- package/dist/commands/config/set.js +1 -0
- package/dist/commands/init.js +13 -5
- package/dist/commands/install.js +104 -3
- package/dist/commands/portal/config.js +88 -0
- package/dist/commands/portal/create.js +105 -0
- package/dist/commands/portal/deploy.js +81 -0
- package/dist/commands/portal/destroy.js +104 -0
- package/dist/commands/portal/dev.js +71 -0
- package/dist/commands/portal/index.js +20 -0
- package/dist/commands/portal/info.js +82 -0
- package/dist/commands/portal/list.js +98 -0
- package/dist/commands/portal/pull.js +84 -0
- package/dist/commands/portal/push.js +79 -0
- package/dist/commands/source/dev.js +1 -1
- package/dist/lib/api-client.js +7 -0
- package/dist/lib/auth-store.js +3 -1
- package/dist/lib/cli-config.js +20 -1
- package/dist/lib/env-auth.js +2 -2
- package/dist/lib/env-config.js +3 -0
- package/dist/lib/env-proxy.js +141 -8
- package/dist/lib/managed-env-file.js +58 -2
- package/dist/lib/managed-init-env.js +6 -1
- package/dist/lib/naming.js +9 -0
- package/dist/lib/portal-command-env.js +31 -0
- package/dist/lib/portal-config.js +133 -0
- package/dist/lib/portal-configure.js +117 -0
- package/dist/lib/portal-create.js +433 -0
- package/dist/lib/portal-deploy.js +298 -0
- package/dist/lib/portal-destroy.js +100 -0
- package/dist/lib/portal-dev.js +79 -0
- package/dist/lib/portal-env-files.js +53 -0
- package/dist/lib/portal-info.js +31 -0
- package/dist/lib/portal-list.js +211 -0
- package/dist/lib/portal-source.js +523 -0
- package/dist/lib/proxy-caddy.js +2 -0
- package/dist/lib/proxy-nginx.js +1 -0
- package/dist/lib/run-npm.js +17 -16
- package/dist/lib/ui.js +28 -1
- package/dist/locale/en-US.json +178 -0
- package/dist/locale/zh-CN.json +178 -0
- package/nocobase-ctl.config.json +111 -0
- package/package.json +5 -2
|
@@ -0,0 +1,211 @@
|
|
|
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 { appendAppPublicPath } from './app-public-path.js';
|
|
12
|
+
import { executeApiRequest } from './api-client.js';
|
|
13
|
+
import { translateCli } from './cli-locale.js';
|
|
14
|
+
import { buildPortalBasePath, resolvePortalAppFromApiBaseUrl, resolvePortalStoragePath, } from './portal-create.js';
|
|
15
|
+
const portalListText = (key, values, fallback) => translateCli(`commands.portalList.${key}`, values, { fallback });
|
|
16
|
+
const LIST_PORTALS_OPERATION = {
|
|
17
|
+
method: 'GET',
|
|
18
|
+
pathTemplate: '/multiPortals:list',
|
|
19
|
+
parameters: [
|
|
20
|
+
{
|
|
21
|
+
name: 'pageSize',
|
|
22
|
+
flagName: 'pageSize',
|
|
23
|
+
in: 'query',
|
|
24
|
+
type: 'integer',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'sort[]',
|
|
28
|
+
flagName: 'sort',
|
|
29
|
+
in: 'query',
|
|
30
|
+
isArray: true,
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
function trimValue(value) {
|
|
35
|
+
return String(value ?? '').trim();
|
|
36
|
+
}
|
|
37
|
+
function readRecordString(record, key) {
|
|
38
|
+
return trimValue(record[key]);
|
|
39
|
+
}
|
|
40
|
+
function readRecordBoolean(record, key) {
|
|
41
|
+
return record[key] === true;
|
|
42
|
+
}
|
|
43
|
+
function readRecordObject(record, key) {
|
|
44
|
+
const value = record[key];
|
|
45
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
function readListData(data) {
|
|
51
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
const directData = data.data;
|
|
55
|
+
if (Array.isArray(directData)) {
|
|
56
|
+
return directData.filter((item) => (Boolean(item) && typeof item === 'object' && !Array.isArray(item)));
|
|
57
|
+
}
|
|
58
|
+
return readListData(directData);
|
|
59
|
+
}
|
|
60
|
+
async function pathExists(target) {
|
|
61
|
+
try {
|
|
62
|
+
await stat(target);
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function buildPortalAccessUrl(apiBaseUrl, portalBase) {
|
|
70
|
+
try {
|
|
71
|
+
const baseUrl = new URL(apiBaseUrl);
|
|
72
|
+
return new URL(portalBase, baseUrl.origin).toString();
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return portalBase;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function normalizeRootPath(pathname) {
|
|
79
|
+
const trimmed = pathname.trim();
|
|
80
|
+
if (!trimmed || trimmed === '/') {
|
|
81
|
+
return '/';
|
|
82
|
+
}
|
|
83
|
+
return `/${trimmed.replace(/^\/+/, '').replace(/\/+$/, '')}`;
|
|
84
|
+
}
|
|
85
|
+
function isAbsoluteUrl(value) {
|
|
86
|
+
return /^[a-z][a-z\d+\-.]*:\/\//i.test(value) || value.startsWith('//');
|
|
87
|
+
}
|
|
88
|
+
function stripBasePath(pathname, basePath) {
|
|
89
|
+
const pathValue = normalizeRootPath(pathname);
|
|
90
|
+
const baseValue = normalizeRootPath(basePath);
|
|
91
|
+
if (pathValue === baseValue) {
|
|
92
|
+
return '/';
|
|
93
|
+
}
|
|
94
|
+
if (pathValue.startsWith(`${baseValue}/`)) {
|
|
95
|
+
return pathValue.slice(baseValue.length) || '/';
|
|
96
|
+
}
|
|
97
|
+
return pathValue;
|
|
98
|
+
}
|
|
99
|
+
function normalizeNoCodeRoutePath(routePath, appPublicPath, app) {
|
|
100
|
+
let normalizedRoutePath = normalizeRootPath(routePath);
|
|
101
|
+
const basePaths = app === 'main'
|
|
102
|
+
? []
|
|
103
|
+
: [
|
|
104
|
+
appendAppPublicPath(appPublicPath, `v/apps/${app}`, { trailingSlash: false }),
|
|
105
|
+
appendAppPublicPath(appPublicPath, `x/apps/${app}`, { trailingSlash: false }),
|
|
106
|
+
`/v/apps/${app}`,
|
|
107
|
+
`/x/apps/${app}`,
|
|
108
|
+
];
|
|
109
|
+
for (const basePath of [
|
|
110
|
+
...basePaths,
|
|
111
|
+
appendAppPublicPath(appPublicPath, 'v', { trailingSlash: false }),
|
|
112
|
+
appendAppPublicPath(appPublicPath, 'x', { trailingSlash: false }),
|
|
113
|
+
'/v',
|
|
114
|
+
'/x',
|
|
115
|
+
]) {
|
|
116
|
+
normalizedRoutePath = stripBasePath(normalizedRoutePath, basePath);
|
|
117
|
+
}
|
|
118
|
+
return normalizedRoutePath;
|
|
119
|
+
}
|
|
120
|
+
function buildNoCodePortalBasePath(params) {
|
|
121
|
+
if (isAbsoluteUrl(params.routePath)) {
|
|
122
|
+
return params.routePath;
|
|
123
|
+
}
|
|
124
|
+
const normalizedRoutePath = normalizeNoCodeRoutePath(params.routePath, params.appPublicPath, params.app);
|
|
125
|
+
const routeSegment = normalizedRoutePath.replace(/^\/+/, '');
|
|
126
|
+
let segment = normalizedRoutePath === '/' ? 'v' : `v/${routeSegment}`;
|
|
127
|
+
if (params.app !== 'main') {
|
|
128
|
+
segment = normalizedRoutePath === '/' ? `v/apps/${params.app}` : `v/apps/${params.app}/${routeSegment}`;
|
|
129
|
+
}
|
|
130
|
+
return appendAppPublicPath(params.appPublicPath, segment, { trailingSlash: normalizedRoutePath === '/' });
|
|
131
|
+
}
|
|
132
|
+
export function toPortalOutputItem(item) {
|
|
133
|
+
return {
|
|
134
|
+
name: item.portalName,
|
|
135
|
+
url: item.portalUrl,
|
|
136
|
+
portalType: item.portalType,
|
|
137
|
+
localPath: item.localSynced === true ? item.portalDir : '',
|
|
138
|
+
enabled: item.enabled,
|
|
139
|
+
sourceStorage: item.sourceStorage,
|
|
140
|
+
localSynced: item.localSynced,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
async function listMultiPortalRecords(params) {
|
|
144
|
+
const apiRequest = params.apiRequest ?? executeApiRequest;
|
|
145
|
+
const response = await apiRequest({
|
|
146
|
+
cliVersion: params.cliVersion ?? '',
|
|
147
|
+
envName: params.envName,
|
|
148
|
+
flags: {
|
|
149
|
+
pageSize: 200,
|
|
150
|
+
sort: ['portalName'],
|
|
151
|
+
},
|
|
152
|
+
operation: LIST_PORTALS_OPERATION,
|
|
153
|
+
});
|
|
154
|
+
if (!response.ok) {
|
|
155
|
+
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)}`));
|
|
156
|
+
}
|
|
157
|
+
return readListData(response.data);
|
|
158
|
+
}
|
|
159
|
+
export async function listPortalWorkspaces(options) {
|
|
160
|
+
const apiBaseUrl = trimValue(options.env.apiBaseUrl);
|
|
161
|
+
const storagePath = resolvePortalStoragePath(options.env);
|
|
162
|
+
const { app, appPublicPath } = resolvePortalAppFromApiBaseUrl(apiBaseUrl, options.env.config.appPublicPath);
|
|
163
|
+
const mode = options.env.kind;
|
|
164
|
+
if (mode !== 'local' && mode !== 'docker' && mode !== 'http') {
|
|
165
|
+
throw new Error(portalListText('errors.unsupportedEnvKind', { kind: mode }, `Cannot list portals for ${mode} envs in the first version.`));
|
|
166
|
+
}
|
|
167
|
+
const listMode = mode;
|
|
168
|
+
const records = await listMultiPortalRecords({
|
|
169
|
+
envName: options.envName,
|
|
170
|
+
cliVersion: options.cliVersion,
|
|
171
|
+
apiRequest: options.apiRequest,
|
|
172
|
+
});
|
|
173
|
+
const items = await Promise.all(records.map(async (record) => {
|
|
174
|
+
const uid = readRecordString(record, 'uid');
|
|
175
|
+
const portalName = readRecordString(record, 'portalName') || uid;
|
|
176
|
+
const routePath = readRecordString(record, 'routePath') || `/${portalName}`;
|
|
177
|
+
const portalType = readRecordString(record, 'portalType');
|
|
178
|
+
const enabled = readRecordBoolean(record, 'enabled');
|
|
179
|
+
const options = readRecordObject(record, 'options');
|
|
180
|
+
const git = readRecordObject(options, 'git');
|
|
181
|
+
const sourceStorage = trimValue(options.sourceStorage) || readRecordString(record, 'sourceStorage') || 'nocobase';
|
|
182
|
+
const isAi = portalType === 'ai';
|
|
183
|
+
const portalDir = isAi ? path.join(storagePath, 'portals', app, portalName) : '';
|
|
184
|
+
return {
|
|
185
|
+
uid,
|
|
186
|
+
portalName,
|
|
187
|
+
routePath,
|
|
188
|
+
portalType,
|
|
189
|
+
enabled,
|
|
190
|
+
sourceStorage,
|
|
191
|
+
gitRepo: trimValue(git.repo) || readRecordString(record, 'gitRepo'),
|
|
192
|
+
gitBranch: trimValue(git.branch) || readRecordString(record, 'gitBranch'),
|
|
193
|
+
gitPath: trimValue(git.path) || readRecordString(record, 'gitPath'),
|
|
194
|
+
sourceRevision: trimValue(options.sourceRevision) || readRecordString(record, 'sourceRevision'),
|
|
195
|
+
options,
|
|
196
|
+
portalUrl: enabled
|
|
197
|
+
? buildPortalAccessUrl(apiBaseUrl, isAi
|
|
198
|
+
? buildPortalBasePath({ app, appPublicPath, portal: portalName })
|
|
199
|
+
: buildNoCodePortalBasePath({ app, appPublicPath, routePath }))
|
|
200
|
+
: '',
|
|
201
|
+
portalDir,
|
|
202
|
+
localSynced: isAi ? await pathExists(portalDir) : null,
|
|
203
|
+
};
|
|
204
|
+
}));
|
|
205
|
+
return {
|
|
206
|
+
app,
|
|
207
|
+
mode: listMode,
|
|
208
|
+
storagePath,
|
|
209
|
+
items,
|
|
210
|
+
};
|
|
211
|
+
}
|