@nocobase/cli 2.2.0-test.16 → 3.0.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.
- package/dist/commands/app/start.js +1 -21
- package/dist/commands/config/set.js +1 -1
- package/dist/commands/init.js +12 -121
- package/dist/commands/install.js +39 -55
- package/dist/commands/portal/config.js +88 -0
- package/dist/commands/portal/create.js +7 -6
- package/dist/commands/portal/deploy.js +2 -2
- package/dist/commands/portal/destroy.js +6 -6
- package/dist/commands/portal/dev.js +3 -3
- package/dist/commands/portal/index.js +1 -1
- package/dist/commands/portal/info.js +3 -3
- package/dist/commands/portal/list.js +5 -5
- package/dist/commands/portal/pull.js +11 -4
- package/dist/commands/portal/push.js +4 -4
- package/dist/lib/api-client.js +7 -0
- package/dist/lib/env-auth.js +2 -2
- package/dist/lib/env-config.js +1 -1
- package/dist/lib/env-proxy.js +68 -6
- package/dist/lib/managed-env-file.js +58 -2
- package/dist/lib/managed-init-env.js +1 -1
- package/dist/lib/naming.js +9 -0
- package/dist/lib/portal-config.js +133 -0
- package/dist/lib/portal-configure.js +117 -0
- package/dist/lib/portal-create.js +24 -79
- package/dist/lib/portal-deploy.js +23 -15
- package/dist/lib/portal-destroy.js +3 -3
- package/dist/lib/portal-dev.js +3 -3
- package/dist/lib/portal-info.js +2 -2
- package/dist/lib/portal-list.js +32 -18
- package/dist/lib/portal-source.js +150 -43
- package/dist/lib/proxy-caddy.js +2 -0
- package/dist/lib/proxy-nginx.js +1 -0
- package/dist/locale/en-US.json +67 -43
- package/dist/locale/zh-CN.json +59 -35
- package/nocobase-ctl.config.json +111 -0
- package/package.json +4 -3
- package/dist/lib/portal-template.js +0 -190
|
@@ -20,10 +20,11 @@ import { executeApiRequest } from './api-client.js';
|
|
|
20
20
|
import { resolveEnvRelativePath } from './cli-home.js';
|
|
21
21
|
import { translateCli } from './cli-locale.js';
|
|
22
22
|
import { buildPortalCommandEnv } from './portal-command-env.js';
|
|
23
|
+
import { buildPortalConfig, mergePortalConfigIntoOptions, writePortalConfig, } from './portal-config.js';
|
|
23
24
|
import { run } from './run-npm.js';
|
|
24
25
|
const DEFAULT_PORTAL_TEMPLATE = '@nocobase/portal-template-default';
|
|
25
26
|
const DEFAULT_PORTAL_APP_NAME = 'main';
|
|
26
|
-
const TEMPLATE_COPY_EXCLUDED_NAMES = new Set(['.git', 'node_modules', '.DS_Store'
|
|
27
|
+
const TEMPLATE_COPY_EXCLUDED_NAMES = new Set(['.git', 'node_modules', '.DS_Store']);
|
|
27
28
|
const NPM_PACK_TIMEOUT_MS = 30_000;
|
|
28
29
|
const portalCreateText = (key, values, fallback) => translateCli(`commands.portalCreate.${key}`, values, { fallback });
|
|
29
30
|
const FIRST_OR_CREATE_PORTAL_OPERATION = {
|
|
@@ -96,7 +97,7 @@ function safeTempPrefix(parentDir, portal) {
|
|
|
96
97
|
function assertPortalDirIsInsideParent(parentDir, portalDir) {
|
|
97
98
|
const relative = path.relative(parentDir, portalDir);
|
|
98
99
|
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
99
|
-
throw new Error(portalCreateText('errors.outsideParent', { parentDir, portalDir }, `Refusing to modify a
|
|
100
|
+
throw new Error(portalCreateText('errors.outsideParent', { parentDir, portalDir }, `Refusing to modify a portal outside ${parentDir}: ${portalDir}`));
|
|
100
101
|
}
|
|
101
102
|
}
|
|
102
103
|
function shouldCopyTemplateEntry(templateDir, source) {
|
|
@@ -165,7 +166,7 @@ async function downloadNpmTemplatePackage(params) {
|
|
|
165
166
|
}
|
|
166
167
|
catch (error) {
|
|
167
168
|
const message = error instanceof Error ? error.message : String(error);
|
|
168
|
-
throw new Error(portalCreateText('errors.templateExtractFailed', { source: params.source, details: message }, `Failed to extract
|
|
169
|
+
throw new Error(portalCreateText('errors.templateExtractFailed', { source: params.source, details: message }, `Failed to extract portal template "${params.source}": ${message}`));
|
|
169
170
|
}
|
|
170
171
|
if (!(await pathExists(path.join(extractRoot, 'package.json')))) {
|
|
171
172
|
throw new Error(portalCreateText('errors.templateMissingPackageJson', { source: params.source }, `Portal template "${params.source}" is invalid: package.json is missing.`));
|
|
@@ -184,7 +185,7 @@ async function downloadNpmTemplatePackage(params) {
|
|
|
184
185
|
}
|
|
185
186
|
catch (error) {
|
|
186
187
|
const details = trimValue(stderr) || trimValue(stdout) || (error instanceof Error ? error.message : String(error));
|
|
187
|
-
throw new Error(portalCreateText('errors.templateDownloadFailed', { source: params.source, details }, `Failed to download
|
|
188
|
+
throw new Error(portalCreateText('errors.templateDownloadFailed', { source: params.source, details }, `Failed to download portal template "${params.source}" with npm pack. ${details}`));
|
|
188
189
|
}
|
|
189
190
|
finally {
|
|
190
191
|
if (shouldCleanupPackRoot) {
|
|
@@ -247,74 +248,29 @@ export async function resolvePortalTemplate(source = DEFAULT_PORTAL_TEMPLATE, op
|
|
|
247
248
|
export function validatePortalSlug(value) {
|
|
248
249
|
const portal = trimValue(value);
|
|
249
250
|
if (!/^[a-z0-9][a-z0-9_-]*$/.test(portal)) {
|
|
250
|
-
throw new Error(portalCreateText('errors.invalidPortalName', { value }, `Invalid
|
|
251
|
+
throw new Error(portalCreateText('errors.invalidPortalName', { value }, `Invalid portal name "${value}". Use lowercase letters, numbers, underscores, or hyphens, ` +
|
|
251
252
|
'and start with a lowercase letter or number.'));
|
|
252
253
|
}
|
|
253
254
|
return portal;
|
|
254
255
|
}
|
|
255
|
-
function validatePortalSourceStorage(value) {
|
|
256
|
-
const sourceStorage = trimValue(value) || 'nocobase';
|
|
257
|
-
if (sourceStorage === 'nocobase' || sourceStorage === 'git') {
|
|
258
|
-
return sourceStorage;
|
|
259
|
-
}
|
|
260
|
-
throw new Error(portalCreateText('errors.invalidSourceStorage', { value: sourceStorage }, `Invalid source storage "${sourceStorage}". Use "nocobase" or "git".`));
|
|
261
|
-
}
|
|
262
|
-
function isFullGitRemoteUrl(value) {
|
|
263
|
-
return /^(?:https?:\/\/|ssh:\/\/|git@[^:]+:).+/.test(value);
|
|
264
|
-
}
|
|
265
|
-
function validateGitPath(value) {
|
|
266
|
-
const gitPath = trimValue(value);
|
|
267
|
-
if (!gitPath || path.isAbsolute(gitPath) || gitPath.split(/[\\/]+/).includes('..')) {
|
|
268
|
-
throw new Error(portalCreateText('errors.invalidGitPath', { value }, '--git-path must be a relative path inside the Git repository.'));
|
|
269
|
-
}
|
|
270
|
-
return gitPath.replace(/\\/g, '/').replace(/^\/+|\/+$/g, '');
|
|
271
|
-
}
|
|
272
|
-
function validatePortalSourceOptions(options, portal) {
|
|
273
|
-
const sourceStorage = validatePortalSourceStorage(options.sourceStorage);
|
|
274
|
-
const hasGitOption = Boolean(trimValue(options.gitRepo) || trimValue(options.gitBranch) || trimValue(options.gitPath));
|
|
275
|
-
if (sourceStorage === 'nocobase') {
|
|
276
|
-
if (hasGitOption) {
|
|
277
|
-
throw new Error(portalCreateText('errors.gitOptionsForNocobaseStorage', undefined, '--git-repo, --git-branch, and --git-path can only be used with --source-storage git.'));
|
|
278
|
-
}
|
|
279
|
-
return { sourceStorage };
|
|
280
|
-
}
|
|
281
|
-
const repo = trimValue(options.gitRepo);
|
|
282
|
-
if (!repo) {
|
|
283
|
-
throw new Error(portalCreateText('errors.gitRepoRequired', undefined, '--git-repo is required when --source-storage is git.'));
|
|
284
|
-
}
|
|
285
|
-
if (!isFullGitRemoteUrl(repo)) {
|
|
286
|
-
throw new Error(portalCreateText('errors.gitRepoInvalid', undefined, '--git-repo must be a full Git remote URL.'));
|
|
287
|
-
}
|
|
288
|
-
return {
|
|
289
|
-
sourceStorage,
|
|
290
|
-
git: {
|
|
291
|
-
repo,
|
|
292
|
-
branch: trimValue(options.gitBranch) || 'main',
|
|
293
|
-
path: validateGitPath(options.gitPath ?? portal),
|
|
294
|
-
},
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
256
|
async function syncMultiPortalRecord(params) {
|
|
298
257
|
const apiRequest = params.apiRequest ?? executeApiRequest;
|
|
299
258
|
const response = await apiRequest({
|
|
300
259
|
cliVersion: params.cliVersion ?? '',
|
|
301
260
|
envName: params.envName,
|
|
302
261
|
flags: {
|
|
303
|
-
filterKeys: ['
|
|
262
|
+
filterKeys: ['portalName'],
|
|
304
263
|
body: JSON.stringify({
|
|
305
264
|
uid: params.portal,
|
|
306
265
|
title: params.title,
|
|
307
|
-
|
|
308
|
-
|
|
266
|
+
portalType: 'ai',
|
|
267
|
+
portalName: params.portal,
|
|
309
268
|
routePath: `/${params.portal}`,
|
|
310
269
|
authCheck: true,
|
|
311
270
|
enabled: true,
|
|
312
271
|
uiLayoutUid: 'admin-layout-model',
|
|
313
272
|
skipCreatePortalDirectory: true,
|
|
314
|
-
options:
|
|
315
|
-
sourceStorage: params.sourceStorage,
|
|
316
|
-
git: params.git,
|
|
317
|
-
},
|
|
273
|
+
options: mergePortalConfigIntoOptions(params.config),
|
|
318
274
|
}),
|
|
319
275
|
},
|
|
320
276
|
operation: FIRST_OR_CREATE_PORTAL_OPERATION,
|
|
@@ -326,7 +282,7 @@ async function syncMultiPortalRecord(params) {
|
|
|
326
282
|
function validatePortalAppName(value) {
|
|
327
283
|
const app = trimValue(value);
|
|
328
284
|
if (!/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(app)) {
|
|
329
|
-
throw new Error(portalCreateText('errors.invalidPortalAppName', { value }, `Invalid
|
|
285
|
+
throw new Error(portalCreateText('errors.invalidPortalAppName', { value }, `Invalid portal app name "${value}" from apiBaseUrl. Use letters, numbers, underscores, or hyphens, ` +
|
|
330
286
|
'and start with a letter or number.'));
|
|
331
287
|
}
|
|
332
288
|
return app;
|
|
@@ -341,7 +297,7 @@ export function titleFromPortalSlug(portal) {
|
|
|
341
297
|
export function resolvePortalAppFromApiBaseUrl(apiBaseUrl, appPublicPath) {
|
|
342
298
|
const normalizedApiBaseUrl = trimValue(apiBaseUrl);
|
|
343
299
|
if (!normalizedApiBaseUrl) {
|
|
344
|
-
throw new Error(portalCreateText('errors.missingApiBaseUrl', undefined, 'Cannot create a
|
|
300
|
+
throw new Error(portalCreateText('errors.missingApiBaseUrl', undefined, 'Cannot create a portal because the selected env has no apiBaseUrl.'));
|
|
345
301
|
}
|
|
346
302
|
const configuredPublicPath = trimValue(appPublicPath);
|
|
347
303
|
let inferredPublicPath = '/';
|
|
@@ -371,7 +327,7 @@ export function buildPortalBasePath(params) {
|
|
|
371
327
|
}
|
|
372
328
|
export function resolvePortalStoragePath(env) {
|
|
373
329
|
if (env.kind === 'ssh') {
|
|
374
|
-
throw new Error(portalCreateText('errors.sshUnsupported', undefined, 'Cannot create a
|
|
330
|
+
throw new Error(portalCreateText('errors.sshUnsupported', undefined, 'Cannot create a portal for ssh envs in the first version.'));
|
|
375
331
|
}
|
|
376
332
|
if (env.kind === 'http' && !trimValue(env.config.storagePath)) {
|
|
377
333
|
const envName = trimValue(env.name);
|
|
@@ -397,7 +353,13 @@ export function resolvePortalStoragePath(env) {
|
|
|
397
353
|
export async function createPortalWorkspace(options) {
|
|
398
354
|
const portal = validatePortalSlug(options.portal);
|
|
399
355
|
const title = trimValue(options.title) || titleFromPortalSlug(portal);
|
|
400
|
-
const
|
|
356
|
+
const portalConfig = buildPortalConfig({
|
|
357
|
+
portal,
|
|
358
|
+
sourceStorage: options.sourceStorage,
|
|
359
|
+
gitRepo: options.gitRepo,
|
|
360
|
+
gitBranch: options.gitBranch,
|
|
361
|
+
gitPath: options.gitPath,
|
|
362
|
+
});
|
|
401
363
|
const apiBaseUrl = trimValue(options.env.apiBaseUrl);
|
|
402
364
|
const envApiUrl = resolvePortalEnvApiUrl(apiBaseUrl);
|
|
403
365
|
const storagePath = resolvePortalStoragePath(options.env);
|
|
@@ -408,7 +370,7 @@ export async function createPortalWorkspace(options) {
|
|
|
408
370
|
assertPortalDirIsInsideParent(portalParentDir, portalDir);
|
|
409
371
|
const targetExists = await pathExists(portalDir);
|
|
410
372
|
if (targetExists && !options.force) {
|
|
411
|
-
throw new Error(portalCreateText('errors.workspaceExists', { portalDir }, `Portal
|
|
373
|
+
throw new Error(portalCreateText('errors.workspaceExists', { portalDir }, `Portal already exists: ${portalDir}\nPass --force to delete it and create a new portal.`));
|
|
412
374
|
}
|
|
413
375
|
const template = await resolvePortalTemplate(options.template, {
|
|
414
376
|
npmRegistry: trimValue(options.env.config.npmRegistry),
|
|
@@ -421,23 +383,7 @@ export async function createPortalWorkspace(options) {
|
|
|
421
383
|
await copyTemplate(template.dir, tempDir);
|
|
422
384
|
await writeFile(path.join(tempDir, '.env'), [`NOCOBASE_API_URL=${envApiUrl}`, `NOCOBASE_PORTAL_BASE=${portalBase}`].join('\n') + '\n', 'utf-8');
|
|
423
385
|
await writeFile(path.join(tempDir, '.env.local'), [`NOCOBASE_API_URL=${apiBaseUrl}`, `NOCOBASE_PORTAL_BASE=${portalBase}`].join('\n') + '\n', 'utf-8');
|
|
424
|
-
|
|
425
|
-
schemaVersion: 1,
|
|
426
|
-
app,
|
|
427
|
-
name: portal,
|
|
428
|
-
title,
|
|
429
|
-
basePath: portalBase,
|
|
430
|
-
apiBaseUrl,
|
|
431
|
-
sourceStorage: sourceOptions.sourceStorage,
|
|
432
|
-
git: sourceOptions.git,
|
|
433
|
-
template: {
|
|
434
|
-
type: template.type,
|
|
435
|
-
source: template.source,
|
|
436
|
-
},
|
|
437
|
-
createdBy: 'nb portal create',
|
|
438
|
-
createdAt: new Date().toISOString(),
|
|
439
|
-
};
|
|
440
|
-
await writeFile(path.join(tempDir, 'portal.config.json'), `${JSON.stringify(portalConfig, null, 2)}\n`, 'utf-8');
|
|
386
|
+
await writePortalConfig(tempDir, portalConfig);
|
|
441
387
|
if (targetExists) {
|
|
442
388
|
await rm(portalDir, { recursive: true, force: true });
|
|
443
389
|
}
|
|
@@ -460,8 +406,7 @@ export async function createPortalWorkspace(options) {
|
|
|
460
406
|
await syncMultiPortalRecord({
|
|
461
407
|
portal,
|
|
462
408
|
title,
|
|
463
|
-
|
|
464
|
-
git: sourceOptions.git,
|
|
409
|
+
config: portalConfig,
|
|
465
410
|
envName: options.envName,
|
|
466
411
|
cliVersion: options.cliVersion,
|
|
467
412
|
apiRequest: options.apiRequest,
|
|
@@ -476,7 +421,7 @@ export async function createPortalWorkspace(options) {
|
|
|
476
421
|
portalBase,
|
|
477
422
|
template,
|
|
478
423
|
installSkipped: !hasPackageJson,
|
|
479
|
-
sourceStorage:
|
|
424
|
+
sourceStorage: portalConfig.sourceStorage,
|
|
480
425
|
};
|
|
481
426
|
}
|
|
482
427
|
finally {
|
|
@@ -15,6 +15,7 @@ import { translateCli } from './cli-locale.js';
|
|
|
15
15
|
import { buildPortalBasePath, resolvePortalAppFromApiBaseUrl, resolvePortalStoragePath, titleFromPortalSlug, validatePortalSlug, } from './portal-create.js';
|
|
16
16
|
import { buildPortalCommandEnv } from './portal-command-env.js';
|
|
17
17
|
import { updatePortalEnvFiles } from './portal-env-files.js';
|
|
18
|
+
import { mergePortalConfigIntoOptions, readPortalConfig } from './portal-config.js';
|
|
18
19
|
import { run } from './run-npm.js';
|
|
19
20
|
const portalDeployText = (key, values, fallback) => translateCli(`commands.portalDeploy.${key}`, values, { fallback });
|
|
20
21
|
const DEPLOY_OPERATION = {
|
|
@@ -161,22 +162,26 @@ async function uploadPortalDist(params) {
|
|
|
161
162
|
}
|
|
162
163
|
async function syncMultiPortalRecord(params) {
|
|
163
164
|
const apiRequest = params.apiRequest ?? executeApiRequest;
|
|
165
|
+
const body = {
|
|
166
|
+
uid: params.portal,
|
|
167
|
+
title: titleFromPortalSlug(params.portal),
|
|
168
|
+
portalType: 'ai',
|
|
169
|
+
portalName: params.portal,
|
|
170
|
+
routePath: `/${params.portal}`,
|
|
171
|
+
authCheck: true,
|
|
172
|
+
enabled: true,
|
|
173
|
+
uiLayoutUid: DEFAULT_PORTAL_UI_LAYOUT_UID,
|
|
174
|
+
skipCreatePortalDirectory: true,
|
|
175
|
+
};
|
|
176
|
+
if (params.config) {
|
|
177
|
+
body.options = mergePortalConfigIntoOptions(params.config);
|
|
178
|
+
}
|
|
164
179
|
const response = await apiRequest({
|
|
165
180
|
cliVersion: params.cliVersion ?? '',
|
|
166
181
|
envName: params.envName,
|
|
167
182
|
flags: {
|
|
168
|
-
filterKeys: ['
|
|
169
|
-
body: JSON.stringify(
|
|
170
|
-
uid: params.portal,
|
|
171
|
-
title: titleFromPortalSlug(params.portal),
|
|
172
|
-
developmentMode: 'vibe-coding',
|
|
173
|
-
routeName: params.portal,
|
|
174
|
-
routePath: `/${params.portal}`,
|
|
175
|
-
authCheck: true,
|
|
176
|
-
enabled: true,
|
|
177
|
-
uiLayoutUid: DEFAULT_PORTAL_UI_LAYOUT_UID,
|
|
178
|
-
skipCreatePortalDirectory: true,
|
|
179
|
-
}),
|
|
183
|
+
filterKeys: ['portalName'],
|
|
184
|
+
body: JSON.stringify(body),
|
|
180
185
|
},
|
|
181
186
|
operation: FIRST_OR_CREATE_PORTAL_OPERATION,
|
|
182
187
|
});
|
|
@@ -193,9 +198,10 @@ export async function deployPortalWorkspace(options) {
|
|
|
193
198
|
const portalDir = path.join(storagePath, 'portals', app, portal);
|
|
194
199
|
const distDir = path.join(portalDir, 'dist');
|
|
195
200
|
if (!(await pathExists(portalDir))) {
|
|
196
|
-
throw new Error(portalDeployText('errors.workspaceMissing', { portalDir, portal }, `Portal
|
|
201
|
+
throw new Error(portalDeployText('errors.workspaceMissing', { portalDir, portal }, `Portal does not exist: ${portalDir}\nRun \`nb portal create ${portal}\` first.`));
|
|
197
202
|
}
|
|
198
|
-
await assertFileExists(path.join(portalDir, 'package.json'), portalDeployText('errors.packageJsonMissing', { portalDir }, `Portal
|
|
203
|
+
await assertFileExists(path.join(portalDir, 'package.json'), portalDeployText('errors.packageJsonMissing', { portalDir }, `Portal is invalid: package.json is missing in ${portalDir}.`));
|
|
204
|
+
const portalConfig = await readPortalConfig(portalDir);
|
|
199
205
|
await updatePortalEnvFiles({
|
|
200
206
|
portalDir,
|
|
201
207
|
apiBaseUrl,
|
|
@@ -221,6 +227,7 @@ export async function deployPortalWorkspace(options) {
|
|
|
221
227
|
if (options.env.kind === 'local' || options.env.kind === 'docker') {
|
|
222
228
|
await syncMultiPortalRecord({
|
|
223
229
|
portal,
|
|
230
|
+
config: portalConfig,
|
|
224
231
|
envName: options.envName,
|
|
225
232
|
cliVersion: options.cliVersion,
|
|
226
233
|
apiRequest: options.apiRequest,
|
|
@@ -237,7 +244,7 @@ export async function deployPortalWorkspace(options) {
|
|
|
237
244
|
};
|
|
238
245
|
}
|
|
239
246
|
if (options.env.kind !== 'http') {
|
|
240
|
-
throw new Error(portalDeployText('errors.unsupportedEnvKind', { kind: options.env.kind }, `Cannot deploy a
|
|
247
|
+
throw new Error(portalDeployText('errors.unsupportedEnvKind', { kind: options.env.kind }, `Cannot deploy a portal for ${options.env.kind} envs in the first version.`));
|
|
241
248
|
}
|
|
242
249
|
const archive = await packPortalDist(distDir);
|
|
243
250
|
let uploadResult;
|
|
@@ -257,6 +264,7 @@ export async function deployPortalWorkspace(options) {
|
|
|
257
264
|
}
|
|
258
265
|
await syncMultiPortalRecord({
|
|
259
266
|
portal,
|
|
267
|
+
config: portalConfig,
|
|
260
268
|
envName: options.envName,
|
|
261
269
|
cliVersion: options.cliVersion,
|
|
262
270
|
apiRequest: options.apiRequest,
|
|
@@ -39,7 +39,7 @@ async function pathExists(target) {
|
|
|
39
39
|
function assertPortalDirIsInsideParent(parentDir, portalDir) {
|
|
40
40
|
const relative = path.relative(parentDir, portalDir);
|
|
41
41
|
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) {
|
|
42
|
-
throw new Error(portalDestroyText('errors.outsideParent', { parentDir, portalDir }, `Refusing to delete a
|
|
42
|
+
throw new Error(portalDestroyText('errors.outsideParent', { parentDir, portalDir }, `Refusing to delete a portal outside ${parentDir}: ${portalDir}`));
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
async function destroyMultiPortalRecord(params) {
|
|
@@ -70,13 +70,13 @@ export async function destroyPortalWorkspace(options) {
|
|
|
70
70
|
const portalDir = path.join(portalParentDir, portal);
|
|
71
71
|
const mode = options.env.kind;
|
|
72
72
|
if (mode !== 'local' && mode !== 'docker' && mode !== 'http') {
|
|
73
|
-
throw new Error(portalDestroyText('errors.unsupportedEnvKind', { kind: mode }, `Cannot destroy a
|
|
73
|
+
throw new Error(portalDestroyText('errors.unsupportedEnvKind', { kind: mode }, `Cannot destroy a portal for ${mode} envs in the first version.`));
|
|
74
74
|
}
|
|
75
75
|
const destroyMode = mode;
|
|
76
76
|
assertPortalDirIsInsideParent(portalParentDir, portalDir);
|
|
77
77
|
const workspaceExists = await pathExists(portalDir);
|
|
78
78
|
if (!workspaceExists && !options.force) {
|
|
79
|
-
throw new Error(portalDestroyText('errors.workspaceMissing', { portalDir, portal }, `Portal
|
|
79
|
+
throw new Error(portalDestroyText('errors.workspaceMissing', { portalDir, portal }, `Portal does not exist: ${portalDir}\nPass --force to ignore missing local files.`));
|
|
80
80
|
}
|
|
81
81
|
const recordDeleted = await destroyMultiPortalRecord({
|
|
82
82
|
portal,
|
package/dist/lib/portal-dev.js
CHANGED
|
@@ -42,16 +42,16 @@ export async function devPortalWorkspace(options) {
|
|
|
42
42
|
const portal = validatePortalSlug(options.portal);
|
|
43
43
|
const apiBaseUrl = trimValue(options.env.apiBaseUrl);
|
|
44
44
|
if (options.env.kind === 'ssh') {
|
|
45
|
-
throw new Error(portalDevText('errors.sshUnsupported', undefined, 'Cannot start a
|
|
45
|
+
throw new Error(portalDevText('errors.sshUnsupported', undefined, 'Cannot start a portal in dev mode for ssh envs in the first version.'));
|
|
46
46
|
}
|
|
47
47
|
const storagePath = resolvePortalStoragePath(options.env);
|
|
48
48
|
const { app, appPublicPath } = resolvePortalAppFromApiBaseUrl(apiBaseUrl, options.env.config.appPublicPath);
|
|
49
49
|
const portalBase = buildPortalBasePath({ app, appPublicPath, portal });
|
|
50
50
|
const portalDir = path.join(storagePath, 'portals', app, portal);
|
|
51
51
|
if (!(await pathExists(portalDir))) {
|
|
52
|
-
throw new Error(portalDevText('errors.workspaceMissing', { portalDir, portal }, `Portal
|
|
52
|
+
throw new Error(portalDevText('errors.workspaceMissing', { portalDir, portal }, `Portal does not exist: ${portalDir}\nRun \`nb portal create ${portal}\` first.`));
|
|
53
53
|
}
|
|
54
|
-
await assertFileExists(path.join(portalDir, 'package.json'), portalDevText('errors.packageJsonMissing', { portalDir }, `Portal
|
|
54
|
+
await assertFileExists(path.join(portalDir, 'package.json'), portalDevText('errors.packageJsonMissing', { portalDir }, `Portal is invalid: package.json is missing in ${portalDir}.`));
|
|
55
55
|
await updatePortalEnvFiles({
|
|
56
56
|
portalDir,
|
|
57
57
|
apiBaseUrl,
|
package/dist/lib/portal-info.js
CHANGED
|
@@ -16,14 +16,14 @@ function formatBoolean(value) {
|
|
|
16
16
|
return value ? 'yes' : 'no';
|
|
17
17
|
}
|
|
18
18
|
export function findPortalListItem(items, portal) {
|
|
19
|
-
return items.find((item) => item.
|
|
19
|
+
return items.find((item) => item.portalName === portal || item.uid === portal);
|
|
20
20
|
}
|
|
21
21
|
export function formatPortalInfo(item) {
|
|
22
22
|
const outputItem = toPortalOutputItem(item);
|
|
23
23
|
return [
|
|
24
24
|
`${portalInfoText('fields.name', undefined, 'Name')}: ${outputItem.name}`,
|
|
25
25
|
`${portalInfoText('fields.url', undefined, 'URL')}: ${outputItem.url}`,
|
|
26
|
-
`${portalInfoText('fields.
|
|
26
|
+
`${portalInfoText('fields.portalType', undefined, 'Portal type')}: ${outputItem.portalType}`,
|
|
27
27
|
`${portalInfoText('fields.path', undefined, 'Local path')}: ${outputItem.localPath}`,
|
|
28
28
|
`${portalInfoText('fields.enabled', undefined, 'Enabled')}: ${formatBoolean(outputItem.enabled)}`,
|
|
29
29
|
`${portalInfoText('fields.localSynced', undefined, 'Local synced')}: ${formatBoolean(outputItem.localSynced)}`,
|
package/dist/lib/portal-list.js
CHANGED
|
@@ -96,9 +96,18 @@ function stripBasePath(pathname, basePath) {
|
|
|
96
96
|
}
|
|
97
97
|
return pathValue;
|
|
98
98
|
}
|
|
99
|
-
function normalizeNoCodeRoutePath(routePath, appPublicPath) {
|
|
99
|
+
function normalizeNoCodeRoutePath(routePath, appPublicPath, app) {
|
|
100
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
|
+
];
|
|
101
109
|
for (const basePath of [
|
|
110
|
+
...basePaths,
|
|
102
111
|
appendAppPublicPath(appPublicPath, 'v', { trailingSlash: false }),
|
|
103
112
|
appendAppPublicPath(appPublicPath, 'x', { trailingSlash: false }),
|
|
104
113
|
'/v',
|
|
@@ -112,15 +121,19 @@ function buildNoCodePortalBasePath(params) {
|
|
|
112
121
|
if (isAbsoluteUrl(params.routePath)) {
|
|
113
122
|
return params.routePath;
|
|
114
123
|
}
|
|
115
|
-
const normalizedRoutePath = normalizeNoCodeRoutePath(params.routePath, params.appPublicPath);
|
|
116
|
-
const
|
|
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
|
+
}
|
|
117
130
|
return appendAppPublicPath(params.appPublicPath, segment, { trailingSlash: normalizedRoutePath === '/' });
|
|
118
131
|
}
|
|
119
132
|
export function toPortalOutputItem(item) {
|
|
120
133
|
return {
|
|
121
|
-
name: item.
|
|
134
|
+
name: item.portalName,
|
|
122
135
|
url: item.portalUrl,
|
|
123
|
-
|
|
136
|
+
portalType: item.portalType,
|
|
124
137
|
localPath: item.localSynced === true ? item.portalDir : '',
|
|
125
138
|
enabled: item.enabled,
|
|
126
139
|
sourceStorage: item.sourceStorage,
|
|
@@ -134,7 +147,7 @@ async function listMultiPortalRecords(params) {
|
|
|
134
147
|
envName: params.envName,
|
|
135
148
|
flags: {
|
|
136
149
|
pageSize: 200,
|
|
137
|
-
sort: ['
|
|
150
|
+
sort: ['portalName'],
|
|
138
151
|
},
|
|
139
152
|
operation: LIST_PORTALS_OPERATION,
|
|
140
153
|
});
|
|
@@ -149,7 +162,7 @@ export async function listPortalWorkspaces(options) {
|
|
|
149
162
|
const { app, appPublicPath } = resolvePortalAppFromApiBaseUrl(apiBaseUrl, options.env.config.appPublicPath);
|
|
150
163
|
const mode = options.env.kind;
|
|
151
164
|
if (mode !== 'local' && mode !== 'docker' && mode !== 'http') {
|
|
152
|
-
throw new Error(portalListText('errors.unsupportedEnvKind', { kind: mode }, `Cannot list
|
|
165
|
+
throw new Error(portalListText('errors.unsupportedEnvKind', { kind: mode }, `Cannot list portals for ${mode} envs in the first version.`));
|
|
153
166
|
}
|
|
154
167
|
const listMode = mode;
|
|
155
168
|
const records = await listMultiPortalRecords({
|
|
@@ -159,33 +172,34 @@ export async function listPortalWorkspaces(options) {
|
|
|
159
172
|
});
|
|
160
173
|
const items = await Promise.all(records.map(async (record) => {
|
|
161
174
|
const uid = readRecordString(record, 'uid');
|
|
162
|
-
const
|
|
163
|
-
const routePath = readRecordString(record, 'routePath') || `/${
|
|
164
|
-
const
|
|
175
|
+
const portalName = readRecordString(record, 'portalName') || uid;
|
|
176
|
+
const routePath = readRecordString(record, 'routePath') || `/${portalName}`;
|
|
177
|
+
const portalType = readRecordString(record, 'portalType');
|
|
165
178
|
const enabled = readRecordBoolean(record, 'enabled');
|
|
166
179
|
const options = readRecordObject(record, 'options');
|
|
167
180
|
const git = readRecordObject(options, 'git');
|
|
168
181
|
const sourceStorage = trimValue(options.sourceStorage) || readRecordString(record, 'sourceStorage') || 'nocobase';
|
|
169
|
-
const
|
|
170
|
-
const portalDir =
|
|
182
|
+
const isAi = portalType === 'ai';
|
|
183
|
+
const portalDir = isAi ? path.join(storagePath, 'portals', app, portalName) : '';
|
|
171
184
|
return {
|
|
172
185
|
uid,
|
|
173
|
-
|
|
186
|
+
portalName,
|
|
174
187
|
routePath,
|
|
175
|
-
|
|
188
|
+
portalType,
|
|
176
189
|
enabled,
|
|
177
190
|
sourceStorage,
|
|
178
191
|
gitRepo: trimValue(git.repo) || readRecordString(record, 'gitRepo'),
|
|
179
192
|
gitBranch: trimValue(git.branch) || readRecordString(record, 'gitBranch'),
|
|
180
193
|
gitPath: trimValue(git.path) || readRecordString(record, 'gitPath'),
|
|
181
194
|
sourceRevision: trimValue(options.sourceRevision) || readRecordString(record, 'sourceRevision'),
|
|
195
|
+
options,
|
|
182
196
|
portalUrl: enabled
|
|
183
|
-
? buildPortalAccessUrl(apiBaseUrl,
|
|
184
|
-
? buildPortalBasePath({ app, appPublicPath, portal:
|
|
185
|
-
: buildNoCodePortalBasePath({ appPublicPath, routePath }))
|
|
197
|
+
? buildPortalAccessUrl(apiBaseUrl, isAi
|
|
198
|
+
? buildPortalBasePath({ app, appPublicPath, portal: portalName })
|
|
199
|
+
: buildNoCodePortalBasePath({ app, appPublicPath, routePath }))
|
|
186
200
|
: '',
|
|
187
201
|
portalDir,
|
|
188
|
-
localSynced:
|
|
202
|
+
localSynced: isAi ? await pathExists(portalDir) : null,
|
|
189
203
|
};
|
|
190
204
|
}));
|
|
191
205
|
return {
|