@nocobase/cli 2.3.0-alpha.1 → 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/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 +283 -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
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
add_header Cache-Control "public" always;
|
|
2
|
+
add_header Content-Security-Policy "sandbox" always;
|
|
2
3
|
add_header X-Content-Type-Options "nosniff" always;
|
|
3
4
|
|
|
4
5
|
access_log off;
|
|
5
6
|
autoindex off;
|
|
6
7
|
|
|
7
8
|
# Force potentially renderable uploaded files to download.
|
|
8
|
-
location ~* \.(?:htm|html|svg|svgz|xhtml)$ {
|
|
9
|
+
location ~* \.(?:htm|html|pdf|svg|svgz|xht|xhtml|xml|xsl|xslt)$ {
|
|
9
10
|
add_header Cache-Control "public" always;
|
|
11
|
+
add_header Content-Security-Policy "sandbox" always;
|
|
10
12
|
add_header X-Content-Type-Options "nosniff" always;
|
|
11
13
|
add_header Content-Disposition "attachment" always;
|
|
12
14
|
}
|
|
@@ -16,6 +18,7 @@ location ~* \.md$ {
|
|
|
16
18
|
default_type text/markdown;
|
|
17
19
|
|
|
18
20
|
add_header Cache-Control "public" always;
|
|
21
|
+
add_header Content-Security-Policy "sandbox" always;
|
|
19
22
|
add_header X-Content-Type-Options "nosniff" always;
|
|
20
23
|
add_header Content-Disposition "inline" always;
|
|
21
24
|
}
|
|
@@ -14,6 +14,7 @@ export default class ConfigSet extends Command {
|
|
|
14
14
|
static description = 'Set a CLI configuration value for a supported configuration key.';
|
|
15
15
|
static examples = [
|
|
16
16
|
'<%= config.bin %> <%= command.id %> locale zh-CN',
|
|
17
|
+
'<%= config.bin %> <%= command.id %> default-portal-template @nocobase/portal-template-default',
|
|
17
18
|
'<%= config.bin %> <%= command.id %> update.policy prompt',
|
|
18
19
|
'<%= config.bin %> <%= command.id %> docker.network nocobase',
|
|
19
20
|
'<%= config.bin %> <%= command.id %> docker.container-prefix nb',
|
package/dist/commands/init.js
CHANGED
|
@@ -27,6 +27,7 @@ import { omitKeys, pickKeys } from "../lib/object-utils.js";
|
|
|
27
27
|
import { ENV_CONFIG_SCHEMA_VERSION } from '../lib/env-config.js';
|
|
28
28
|
import { printInfo, printStage, printVerbose, printWarning } from '../lib/ui.js';
|
|
29
29
|
import { persistHookScript } from '../lib/hook-script.js';
|
|
30
|
+
import { ensureManagedEnvFileDefaults } from '../lib/managed-env-file.js';
|
|
30
31
|
import Download from "./download.js";
|
|
31
32
|
import EnvAdd from "./env/add.js";
|
|
32
33
|
import Install, { defaultDbPortForDialect } from "./install.js";
|
|
@@ -607,6 +608,7 @@ Prompt modes:
|
|
|
607
608
|
? { setupMode: normalizeInitSetupMode(presetValues.hasNocobase) }
|
|
608
609
|
: {}),
|
|
609
610
|
},
|
|
611
|
+
yesInitialValues: {},
|
|
610
612
|
values: presetValues,
|
|
611
613
|
yes: normalizedFlags.yes || useBrowserUi || !interactive,
|
|
612
614
|
hooks: {
|
|
@@ -679,7 +681,8 @@ Prompt modes:
|
|
|
679
681
|
}
|
|
680
682
|
static async buildDynamicInitialValuesForInstall(flags, presetValues) {
|
|
681
683
|
const out = {};
|
|
682
|
-
|
|
684
|
+
const shouldResolveAppInitialValues = !Object.prototype.hasOwnProperty.call(presetValues, 'appPort');
|
|
685
|
+
if (shouldResolveAppInitialValues) {
|
|
683
686
|
const appInitialValues = await Install.buildAppPromptInitialValues({
|
|
684
687
|
envName: String(presetValues.appName ?? '').trim(),
|
|
685
688
|
flags: {
|
|
@@ -690,7 +693,7 @@ Prompt modes:
|
|
|
690
693
|
},
|
|
691
694
|
warnOnPortFallback: false,
|
|
692
695
|
});
|
|
693
|
-
if (appInitialValues.appPort !== undefined) {
|
|
696
|
+
if (appInitialValues.appPort !== undefined && !Object.prototype.hasOwnProperty.call(presetValues, 'appPort')) {
|
|
694
697
|
out.appPort = appInitialValues.appPort;
|
|
695
698
|
}
|
|
696
699
|
}
|
|
@@ -1034,7 +1037,8 @@ Prompt modes:
|
|
|
1034
1037
|
const dbSchema = String(results.dbSchema ?? '').trim();
|
|
1035
1038
|
const dbTablePrefix = String(results.dbTablePrefix ?? '').trim();
|
|
1036
1039
|
const apiBaseUrl = String(results.apiBaseUrl ?? '').trim();
|
|
1037
|
-
const
|
|
1040
|
+
const authTypeInput = String(results.authType ?? '').trim();
|
|
1041
|
+
const authType = authTypeInput === 'basic' || authTypeInput === 'token' || authTypeInput === 'oauth' ? authTypeInput : 'oauth';
|
|
1038
1042
|
const authUsername = authType === 'basic' ? String(results.username ?? results.rootUsername ?? '').trim() : '';
|
|
1039
1043
|
const accessToken = String(results.accessToken ?? '');
|
|
1040
1044
|
const skipDownload = results.skipDownload === true;
|
|
@@ -1055,7 +1059,7 @@ Prompt modes:
|
|
|
1055
1059
|
: Boolean(results.builtinDb);
|
|
1056
1060
|
results.appKey = appKey;
|
|
1057
1061
|
results.timeZone = timeZone;
|
|
1058
|
-
|
|
1062
|
+
const savedEnvConfig = {
|
|
1059
1063
|
schemaVersion: ENV_CONFIG_SCHEMA_VERSION,
|
|
1060
1064
|
...(source === 'docker'
|
|
1061
1065
|
? { kind: 'docker' }
|
|
@@ -1100,7 +1104,11 @@ Prompt modes:
|
|
|
1100
1104
|
...(results.dbUnderscored !== undefined ? { dbUnderscored: Boolean(results.dbUnderscored) } : {}),
|
|
1101
1105
|
setupState: 'prepared',
|
|
1102
1106
|
...(String(results.lang ?? '').trim() ? { lang: String(results.lang ?? '').trim() } : {}),
|
|
1103
|
-
}
|
|
1107
|
+
};
|
|
1108
|
+
await upsertEnv(envName, savedEnvConfig, { scope: resolveDefaultConfigScope() });
|
|
1109
|
+
if (source === 'docker' || appPath) {
|
|
1110
|
+
await ensureManagedEnvFileDefaults(envName, savedEnvConfig);
|
|
1111
|
+
}
|
|
1104
1112
|
}
|
|
1105
1113
|
buildEnvAddArgv(results) {
|
|
1106
1114
|
const argv = [String(results.appName ?? DEFAULT_INIT_APP_NAME)];
|
package/dist/commands/install.js
CHANGED
|
@@ -29,6 +29,7 @@ import { buildStoredEnvConfig } from '../lib/env-config.js';
|
|
|
29
29
|
import { resolveDockerEnvFileArg } from "../lib/docker-env-file.js";
|
|
30
30
|
import { startDockerLogFollower } from '../lib/docker-log-stream.js';
|
|
31
31
|
import { buildInitAppEnvVarsFromConfig } from '../lib/managed-init-env.js';
|
|
32
|
+
import { ensureManagedEnvFileDefaults } from '../lib/managed-env-file.js';
|
|
32
33
|
import { buildHookContext, persistHookScript, resolveHookScriptPath, runHookScriptHook, } from '../lib/hook-script.js';
|
|
33
34
|
import { areConfiguredPathsEquivalent, deriveConfiguredSourcePath, deriveConfiguredStoragePath, inferConfiguredAppPathFromLegacyConfig, } from '../lib/env-paths.js';
|
|
34
35
|
import Download from './download.js';
|
|
@@ -53,6 +54,10 @@ const DEFAULT_INSTALL_ROOT_EMAIL = 'admin@nocobase.com';
|
|
|
53
54
|
const DEFAULT_INSTALL_ROOT_PASSWORD = 'admin123';
|
|
54
55
|
const DEFAULT_INSTALL_ROOT_NICKNAME = 'Super Admin';
|
|
55
56
|
const DEFAULT_INSTALL_API_HOST = '127.0.0.1';
|
|
57
|
+
const DEFAULT_INSTALL_PORTAL_TYPE = 'ai';
|
|
58
|
+
const DEFAULT_INSTALL_PORTAL_NAME = 'main';
|
|
59
|
+
const DEFAULT_INSTALL_PORTAL_TEMPLATE = '@nocobase/portal-template-default';
|
|
60
|
+
const INSTALL_PORTAL_TYPES = ['no-code', 'ai'];
|
|
56
61
|
function toOptionalPromptString(value) {
|
|
57
62
|
const text = String(value ?? '').trim();
|
|
58
63
|
return text || undefined;
|
|
@@ -180,6 +185,9 @@ function defaultBuiltinDbImageForDialect(value, options) {
|
|
|
180
185
|
function defaultDbDatabaseForDialect(value) {
|
|
181
186
|
return String(value ?? '').trim() === 'kingbase' ? 'kingbase' : DEFAULT_INSTALL_DB_DATABASE;
|
|
182
187
|
}
|
|
188
|
+
function isAiMode(values) {
|
|
189
|
+
return String(values.portalType ?? DEFAULT_INSTALL_PORTAL_TYPE).trim() === 'ai';
|
|
190
|
+
}
|
|
183
191
|
function supportsDbSchemaPrompt(value) {
|
|
184
192
|
const dialect = String(value ?? '').trim();
|
|
185
193
|
return dialect === 'postgres' || dialect === 'kingbase';
|
|
@@ -404,6 +412,16 @@ export default class Install extends Command {
|
|
|
404
412
|
'app-public-path': Flags.string({
|
|
405
413
|
description: 'Public path for the local app, for example / or /console/',
|
|
406
414
|
}),
|
|
415
|
+
'portal-type': Flags.string({
|
|
416
|
+
description: 'Initial portal type for the installed app',
|
|
417
|
+
options: [...INSTALL_PORTAL_TYPES],
|
|
418
|
+
}),
|
|
419
|
+
'portal-name': Flags.string({
|
|
420
|
+
description: 'Initial portal name',
|
|
421
|
+
}),
|
|
422
|
+
'portal-template': Flags.string({
|
|
423
|
+
description: 'Initial portal template npm package or local path when --portal-type ai is used',
|
|
424
|
+
}),
|
|
407
425
|
'root-username': Flags.string({
|
|
408
426
|
description: 'Initial admin username for the installed app',
|
|
409
427
|
required: false,
|
|
@@ -513,6 +531,41 @@ export default class Install extends Command {
|
|
|
513
531
|
yesInitialValue: '/',
|
|
514
532
|
validate: validateAppPublicPath,
|
|
515
533
|
},
|
|
534
|
+
portalType: {
|
|
535
|
+
type: 'select',
|
|
536
|
+
message: installText('prompts.portalType.message'),
|
|
537
|
+
options: [
|
|
538
|
+
{
|
|
539
|
+
value: 'no-code',
|
|
540
|
+
label: installText('prompts.portalType.noCodeLabel'),
|
|
541
|
+
hint: installText('prompts.portalType.noCodeHint'),
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
value: 'ai',
|
|
545
|
+
label: installText('prompts.portalType.aiLabel'),
|
|
546
|
+
hint: installText('prompts.portalType.aiHint'),
|
|
547
|
+
},
|
|
548
|
+
],
|
|
549
|
+
initialValue: DEFAULT_INSTALL_PORTAL_TYPE,
|
|
550
|
+
yesInitialValue: DEFAULT_INSTALL_PORTAL_TYPE,
|
|
551
|
+
required: true,
|
|
552
|
+
},
|
|
553
|
+
portalName: {
|
|
554
|
+
type: 'text',
|
|
555
|
+
message: installText('prompts.portalName.message'),
|
|
556
|
+
placeholder: DEFAULT_INSTALL_PORTAL_NAME,
|
|
557
|
+
initialValue: DEFAULT_INSTALL_PORTAL_NAME,
|
|
558
|
+
yesInitialValue: DEFAULT_INSTALL_PORTAL_NAME,
|
|
559
|
+
required: true,
|
|
560
|
+
},
|
|
561
|
+
portalTemplate: {
|
|
562
|
+
type: 'text',
|
|
563
|
+
message: installText('prompts.portalTemplate.message'),
|
|
564
|
+
placeholder: DEFAULT_INSTALL_PORTAL_TEMPLATE,
|
|
565
|
+
yesInitialValue: DEFAULT_INSTALL_PORTAL_TEMPLATE,
|
|
566
|
+
hidden: (values) => !isAiMode(values),
|
|
567
|
+
required: true,
|
|
568
|
+
},
|
|
516
569
|
};
|
|
517
570
|
}
|
|
518
571
|
static dbPrompts = {
|
|
@@ -772,6 +825,24 @@ export default class Install extends Command {
|
|
|
772
825
|
preset.appPublicPath = v;
|
|
773
826
|
}
|
|
774
827
|
}
|
|
828
|
+
if (flags['portal-type'] !== undefined) {
|
|
829
|
+
const v = String(flags['portal-type'] ?? '').trim();
|
|
830
|
+
if (v) {
|
|
831
|
+
preset.portalType = v;
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
if (flags['portal-name'] !== undefined) {
|
|
835
|
+
const v = String(flags['portal-name'] ?? '').trim();
|
|
836
|
+
if (v) {
|
|
837
|
+
preset.portalName = v;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
if (flags['portal-template'] !== undefined) {
|
|
841
|
+
const v = String(flags['portal-template'] ?? '').trim();
|
|
842
|
+
if (v) {
|
|
843
|
+
preset.portalTemplate = v;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
775
846
|
if (flags['root-username'] !== undefined) {
|
|
776
847
|
preset.rootUsername = String(flags['root-username'] ?? '').trim();
|
|
777
848
|
}
|
|
@@ -863,6 +934,9 @@ export default class Install extends Command {
|
|
|
863
934
|
'appPort',
|
|
864
935
|
'storagePath',
|
|
865
936
|
'appPublicPath',
|
|
937
|
+
'portalType',
|
|
938
|
+
'portalName',
|
|
939
|
+
'portalTemplate',
|
|
866
940
|
]);
|
|
867
941
|
}
|
|
868
942
|
static buildDbPresetValuesFromFlags(flags, argv = process.argv.slice(2)) {
|
|
@@ -1112,6 +1186,9 @@ export default class Install extends Command {
|
|
|
1112
1186
|
const rootPassword = Install.toOptionalPromptString(config.rootPassword);
|
|
1113
1187
|
const rootNickname = Install.toOptionalPromptString(config.rootNickname);
|
|
1114
1188
|
const lang = Install.toOptionalPromptString(config.lang);
|
|
1189
|
+
const portalType = Install.toOptionalPromptString(config.portalType);
|
|
1190
|
+
const portalName = Install.toOptionalPromptString(config.portalName);
|
|
1191
|
+
const portalTemplate = Install.toOptionalPromptString(config.portalTemplate);
|
|
1115
1192
|
const auth = config.auth;
|
|
1116
1193
|
const savedAuthType = Install.toOptionalPromptString(config.authType) ?? Install.toOptionalPromptString(auth?.type);
|
|
1117
1194
|
const appPreset = {
|
|
@@ -1121,6 +1198,9 @@ export default class Install extends Command {
|
|
|
1121
1198
|
...(appPort ? { appPort } : {}),
|
|
1122
1199
|
...(storagePath ? { storagePath } : {}),
|
|
1123
1200
|
...(appPublicPath ? { appPublicPath } : {}),
|
|
1201
|
+
...(portalType ? { portalType } : {}),
|
|
1202
|
+
...(portalName ? { portalName } : {}),
|
|
1203
|
+
...(portalTemplate ? { portalTemplate } : {}),
|
|
1124
1204
|
...(hookScript ? { hookScript } : {}),
|
|
1125
1205
|
};
|
|
1126
1206
|
const downloadPreset = {
|
|
@@ -1263,6 +1343,12 @@ export default class Install extends Command {
|
|
|
1263
1343
|
warn: params.warnOnPortFallback ?? true,
|
|
1264
1344
|
});
|
|
1265
1345
|
}
|
|
1346
|
+
if (params.flags['portal-template'] === undefined) {
|
|
1347
|
+
const defaultPortalTemplate = Install.toOptionalPromptString(await getCliConfigValue('default-portal-template'));
|
|
1348
|
+
if (defaultPortalTemplate) {
|
|
1349
|
+
initialValues.portalTemplate = defaultPortalTemplate;
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1266
1352
|
return initialValues;
|
|
1267
1353
|
}
|
|
1268
1354
|
static shouldPublishBuiltinDbPortForValues(values) {
|
|
@@ -1430,14 +1516,17 @@ export default class Install extends Command {
|
|
|
1430
1516
|
static buildDockerAppContainerName(envName, containerPrefix) {
|
|
1431
1517
|
return Install.sanitizeDockerResourceName(`${Install.buildBuiltinDbContainerPrefix(containerPrefix)}-${envName}-app`);
|
|
1432
1518
|
}
|
|
1433
|
-
static buildInitAppEnvVars(params) {
|
|
1519
|
+
static buildInitAppEnvVars(params, options = {}) {
|
|
1434
1520
|
return buildInitAppEnvVarsFromConfig({
|
|
1435
1521
|
lang: String(params.appResults.lang ?? ''),
|
|
1436
1522
|
rootUsername: String(params.rootResults.rootUsername ?? ''),
|
|
1437
1523
|
rootEmail: String(params.rootResults.rootEmail ?? ''),
|
|
1438
1524
|
rootPassword: String(params.rootResults.rootPassword ?? ''),
|
|
1439
1525
|
rootNickname: String(params.rootResults.rootNickname ?? ''),
|
|
1440
|
-
|
|
1526
|
+
portalType: String(params.appResults.portalType ?? ''),
|
|
1527
|
+
portalName: String(params.appResults.portalName ?? ''),
|
|
1528
|
+
portalTemplate: String(params.appResults.portalTemplate ?? ''),
|
|
1529
|
+
}, options);
|
|
1441
1530
|
}
|
|
1442
1531
|
static shouldPublishBuiltinDbPort(source) {
|
|
1443
1532
|
return String(source ?? '').trim() !== 'docker';
|
|
@@ -2228,9 +2317,13 @@ export default class Install extends Command {
|
|
|
2228
2317
|
}
|
|
2229
2318
|
async saveInstalledEnv(params) {
|
|
2230
2319
|
const defaultApiHost = await resolveDefaultApiHost();
|
|
2231
|
-
|
|
2320
|
+
const savedEnvConfig = Install.buildSavedEnvConfig(params, { defaultApiHost });
|
|
2321
|
+
await upsertEnv(params.envName, savedEnvConfig, {
|
|
2232
2322
|
scope: resolveDefaultConfigScope(),
|
|
2233
2323
|
});
|
|
2324
|
+
if (params.ensureEnvFileDefaults !== false) {
|
|
2325
|
+
await ensureManagedEnvFileDefaults(params.envName, savedEnvConfig);
|
|
2326
|
+
}
|
|
2234
2327
|
await setCurrentEnv(params.envName, { scope: resolveDefaultConfigScope() });
|
|
2235
2328
|
}
|
|
2236
2329
|
async syncInstalledEnvConnection(params) {
|
|
@@ -2309,6 +2402,9 @@ export default class Install extends Command {
|
|
|
2309
2402
|
const appRootPath = Install.toOptionalPromptString(params.appResults.appRootPath);
|
|
2310
2403
|
const storagePath = Install.toOptionalPromptString(params.appResults.storagePath);
|
|
2311
2404
|
const appPublicPath = Install.toOptionalPromptString(params.appResults.appPublicPath);
|
|
2405
|
+
const portalType = Install.toOptionalPromptString(params.appResults.portalType);
|
|
2406
|
+
const portalName = Install.toOptionalPromptString(params.appResults.portalName);
|
|
2407
|
+
const portalTemplate = Install.toOptionalPromptString(params.appResults.portalTemplate);
|
|
2312
2408
|
const derivedAppRootPath = appPath ? deriveConfiguredSourcePath(appPath) : undefined;
|
|
2313
2409
|
const derivedStoragePath = appPath ? deriveConfiguredStoragePath(appPath) : undefined;
|
|
2314
2410
|
const appPort = String(params.appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim() || DEFAULT_INSTALL_APP_PORT;
|
|
@@ -2343,6 +2439,9 @@ export default class Install extends Command {
|
|
|
2343
2439
|
...(appPublicPath ? { appPublicPath } : {}),
|
|
2344
2440
|
...(envFile ? { envFile } : {}),
|
|
2345
2441
|
lang: params.appResults.lang,
|
|
2442
|
+
portalType,
|
|
2443
|
+
portalName,
|
|
2444
|
+
portalTemplate,
|
|
2346
2445
|
appKey: params.appResults.appKey,
|
|
2347
2446
|
timezone: params.appResults.timeZone,
|
|
2348
2447
|
builtinDb: params.dbResults.builtinDb,
|
|
@@ -2394,6 +2493,7 @@ export default class Install extends Command {
|
|
|
2394
2493
|
'app-root-path': parsed['app-root-path'] ?? Install.toOptionalPromptString(appPreset.appRootPath),
|
|
2395
2494
|
'app-port': parsed['app-port'] ?? Install.toOptionalPromptString(appPreset.appPort),
|
|
2396
2495
|
'storage-path': parsed['storage-path'] ?? Install.toOptionalPromptString(appPreset.storagePath),
|
|
2496
|
+
'portal-template': parsed['portal-template'] ?? Install.toOptionalPromptString(appPreset.portalTemplate),
|
|
2397
2497
|
},
|
|
2398
2498
|
}),
|
|
2399
2499
|
values: appPreset,
|
|
@@ -2565,6 +2665,7 @@ export default class Install extends Command {
|
|
|
2565
2665
|
dbResults,
|
|
2566
2666
|
rootResults,
|
|
2567
2667
|
envAddResults,
|
|
2668
|
+
ensureEnvFileDefaults: false,
|
|
2568
2669
|
});
|
|
2569
2670
|
if (!parsed['skip-save-env-log']) {
|
|
2570
2671
|
printInfo(`Saved env config for "${envName}".`);
|
|
@@ -0,0 +1,88 @@
|
|
|
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 { Args, Command, Flags } from '@oclif/core';
|
|
10
|
+
import { getCurrentEnvName, getEnv } from '../../lib/auth-store.js';
|
|
11
|
+
import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
|
|
12
|
+
import { translateCli } from '../../lib/cli-locale.js';
|
|
13
|
+
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
14
|
+
import { configurePortalWorkspace } from '../../lib/portal-configure.js';
|
|
15
|
+
import { printInfo, printSuccess } from '../../lib/ui.js';
|
|
16
|
+
const portalConfigureText = (key, values, fallback) => translateCli(`commands.portalConfigure.${key}`, values, { fallback });
|
|
17
|
+
export default class PortalConfig extends Command {
|
|
18
|
+
static summary = 'Update portal source configuration';
|
|
19
|
+
static examples = [
|
|
20
|
+
'<%= config.bin %> <%= command.id %> customer --source-storage nocobase',
|
|
21
|
+
'<%= config.bin %> <%= command.id %> customer --source-storage git --git-repo git@github.com:nocobase/customer-portal.git',
|
|
22
|
+
'<%= config.bin %> <%= command.id %> customer --git-branch main --git-path portals/customer',
|
|
23
|
+
];
|
|
24
|
+
static args = {
|
|
25
|
+
portal: Args.string({
|
|
26
|
+
required: true,
|
|
27
|
+
description: 'Portal name',
|
|
28
|
+
}),
|
|
29
|
+
};
|
|
30
|
+
static flags = {
|
|
31
|
+
env: Flags.string({
|
|
32
|
+
char: 'e',
|
|
33
|
+
description: 'CLI env name; omitted uses the current env',
|
|
34
|
+
}),
|
|
35
|
+
yes: Flags.boolean({
|
|
36
|
+
char: 'y',
|
|
37
|
+
description: 'Confirm using --env when it targets a different env than the current env',
|
|
38
|
+
default: false,
|
|
39
|
+
}),
|
|
40
|
+
'source-storage': Flags.string({
|
|
41
|
+
description: 'Where portal source code is managed',
|
|
42
|
+
options: ['nocobase', 'git'],
|
|
43
|
+
}),
|
|
44
|
+
'git-repo': Flags.string({
|
|
45
|
+
description: 'Git repository URL used when --source-storage=git',
|
|
46
|
+
}),
|
|
47
|
+
'git-branch': Flags.string({
|
|
48
|
+
description: 'Git branch used when --source-storage=git',
|
|
49
|
+
}),
|
|
50
|
+
'git-path': Flags.string({
|
|
51
|
+
description: 'Directory inside the Git repository for this portal; defaults to the repository root',
|
|
52
|
+
}),
|
|
53
|
+
};
|
|
54
|
+
async run() {
|
|
55
|
+
const { args, flags } = await this.parse(PortalConfig);
|
|
56
|
+
const requestedEnv = hasExplicitEnvSelection(this.argv) ? flags.env : undefined;
|
|
57
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
58
|
+
command: this,
|
|
59
|
+
requestedEnv,
|
|
60
|
+
yes: flags.yes,
|
|
61
|
+
});
|
|
62
|
+
if (!confirmed) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const scope = resolveDefaultConfigScope();
|
|
66
|
+
const envName = requestedEnv ?? (await getCurrentEnvName({ scope }));
|
|
67
|
+
const env = await getEnv(envName, { scope });
|
|
68
|
+
if (!env) {
|
|
69
|
+
this.error(portalConfigureText(requestedEnv ? 'errors.envNotConfigured' : 'errors.noEnvConfigured', { envName }, requestedEnv
|
|
70
|
+
? `Env "${envName}" is not configured. Run \`nb env add ${envName} --api-base-url <url>\` first.`
|
|
71
|
+
: 'No NocoBase env is configured yet. Run `nb init --ui` to create one first.'));
|
|
72
|
+
}
|
|
73
|
+
const result = await configurePortalWorkspace({
|
|
74
|
+
portal: args.portal,
|
|
75
|
+
env,
|
|
76
|
+
envName,
|
|
77
|
+
cliVersion: String(this.config.pjson.version ?? '').trim(),
|
|
78
|
+
sourceStorage: flags['source-storage'],
|
|
79
|
+
gitRepo: flags['git-repo'],
|
|
80
|
+
gitBranch: flags['git-branch'],
|
|
81
|
+
gitPath: flags['git-path'],
|
|
82
|
+
});
|
|
83
|
+
printSuccess(portalConfigureText('messages.updated', { portal: result.portal, portalDir: result.portalDir }, `Portal "${result.portal}" configuration updated at ${result.portalDir}/portal.config.json.`));
|
|
84
|
+
printInfo(result.remoteSynced
|
|
85
|
+
? portalConfigureText('messages.remoteSynced', undefined, 'Remote portal record: synced')
|
|
86
|
+
: portalConfigureText('messages.remoteSkipped', undefined, 'Remote portal record: not found; local config only'));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
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 { Args, Command, Flags } from '@oclif/core';
|
|
10
|
+
import { getEnv } from '../../lib/auth-store.js';
|
|
11
|
+
import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
|
|
12
|
+
import { translateCli } from '../../lib/cli-locale.js';
|
|
13
|
+
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
14
|
+
import { createPortalWorkspace } from '../../lib/portal-create.js';
|
|
15
|
+
import { printInfo, printSuccess } from '../../lib/ui.js';
|
|
16
|
+
const DEFAULT_PORTAL_TEMPLATE = '@nocobase/portal-template-default';
|
|
17
|
+
const portalCreateText = (key, values, fallback) => translateCli(`commands.portalCreate.${key}`, values, { fallback });
|
|
18
|
+
export default class PortalCreate extends Command {
|
|
19
|
+
static summary = 'Create a local AI portal from a template';
|
|
20
|
+
static examples = [
|
|
21
|
+
'<%= config.bin %> <%= command.id %> customer',
|
|
22
|
+
'<%= config.bin %> <%= command.id %> customer --template @nocobase/portal-template-default',
|
|
23
|
+
'<%= config.bin %> <%= command.id %> customer --env dev --yes',
|
|
24
|
+
'<%= config.bin %> <%= command.id %> customer --source-storage git --git-repo git@github.com:nocobase/customer-portal.git',
|
|
25
|
+
];
|
|
26
|
+
static args = {
|
|
27
|
+
portal: Args.string({
|
|
28
|
+
required: true,
|
|
29
|
+
description: 'AI Portal name',
|
|
30
|
+
}),
|
|
31
|
+
};
|
|
32
|
+
static flags = {
|
|
33
|
+
template: Flags.string({
|
|
34
|
+
description: 'Template package, local path, or file:// URL',
|
|
35
|
+
default: DEFAULT_PORTAL_TEMPLATE,
|
|
36
|
+
}),
|
|
37
|
+
env: Flags.string({
|
|
38
|
+
char: 'e',
|
|
39
|
+
description: 'CLI env name; omitted uses the current env',
|
|
40
|
+
}),
|
|
41
|
+
yes: Flags.boolean({
|
|
42
|
+
char: 'y',
|
|
43
|
+
description: 'Confirm using --env when it targets a different env than the current env',
|
|
44
|
+
default: false,
|
|
45
|
+
}),
|
|
46
|
+
title: Flags.string({
|
|
47
|
+
description: 'Portal display title; defaults to a title generated from the portal slug',
|
|
48
|
+
}),
|
|
49
|
+
force: Flags.boolean({
|
|
50
|
+
description: 'Delete the existing portal and recreate it',
|
|
51
|
+
default: false,
|
|
52
|
+
}),
|
|
53
|
+
'source-storage': Flags.string({
|
|
54
|
+
description: 'Where portal source code is managed',
|
|
55
|
+
options: ['nocobase', 'git'],
|
|
56
|
+
default: 'nocobase',
|
|
57
|
+
}),
|
|
58
|
+
'git-repo': Flags.string({
|
|
59
|
+
description: 'Git repository URL used when --source-storage=git',
|
|
60
|
+
}),
|
|
61
|
+
'git-branch': Flags.string({
|
|
62
|
+
description: 'Git branch used when --source-storage=git',
|
|
63
|
+
}),
|
|
64
|
+
'git-path': Flags.string({
|
|
65
|
+
description: 'Directory inside the Git repository for this portal; defaults to the repository root',
|
|
66
|
+
}),
|
|
67
|
+
};
|
|
68
|
+
async run() {
|
|
69
|
+
const { args, flags } = await this.parse(PortalCreate);
|
|
70
|
+
const requestedEnv = hasExplicitEnvSelection(this.argv) ? flags.env : undefined;
|
|
71
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
72
|
+
command: this,
|
|
73
|
+
requestedEnv,
|
|
74
|
+
yes: flags.yes,
|
|
75
|
+
});
|
|
76
|
+
if (!confirmed) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
const scope = resolveDefaultConfigScope();
|
|
80
|
+
const env = await getEnv(flags.env, { scope });
|
|
81
|
+
if (!env) {
|
|
82
|
+
this.error(flags.env
|
|
83
|
+
? portalCreateText('errors.envNotConfigured', { envName: flags.env }, `Env "${flags.env}" is not configured. Run \`nb env add ${flags.env} --api-base-url <url>\` first.`)
|
|
84
|
+
: portalCreateText('errors.noEnvConfigured', undefined, 'No NocoBase env is configured yet. Run `nb init --ui` to create one first.'));
|
|
85
|
+
}
|
|
86
|
+
const result = await createPortalWorkspace({
|
|
87
|
+
portal: args.portal,
|
|
88
|
+
title: flags.title,
|
|
89
|
+
template: flags.template,
|
|
90
|
+
env,
|
|
91
|
+
envName: flags.env,
|
|
92
|
+
cliVersion: String(this.config.pjson.version ?? '').trim(),
|
|
93
|
+
force: flags.force,
|
|
94
|
+
sourceStorage: flags['source-storage'],
|
|
95
|
+
gitRepo: flags['git-repo'],
|
|
96
|
+
gitBranch: flags['git-branch'],
|
|
97
|
+
gitPath: flags['git-path'],
|
|
98
|
+
onSkipInstall: (message) => printInfo(message),
|
|
99
|
+
});
|
|
100
|
+
printSuccess(portalCreateText('messages.created', { portal: result.portal, portalDir: result.portalDir }, `Portal "${result.portal}" created at ${result.portalDir}.`));
|
|
101
|
+
printInfo(portalCreateText('messages.app', { app: result.app }, `App: ${result.app}`));
|
|
102
|
+
printInfo(portalCreateText('messages.base', { base: result.portalBase }, `Base: ${result.portalBase}`));
|
|
103
|
+
printInfo(portalCreateText('messages.sourceStorage', { sourceStorage: result.sourceStorage }, `Source storage: ${result.sourceStorage}`));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
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 { Args, Command, Flags } from '@oclif/core';
|
|
10
|
+
import { getCurrentEnvName, getEnv } from '../../lib/auth-store.js';
|
|
11
|
+
import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
|
|
12
|
+
import { translateCli } from '../../lib/cli-locale.js';
|
|
13
|
+
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
14
|
+
import { findPortalListItem, formatPortalInfo } from '../../lib/portal-info.js';
|
|
15
|
+
import { deployPortalWorkspace } from '../../lib/portal-deploy.js';
|
|
16
|
+
import { listPortalWorkspaces } from '../../lib/portal-list.js';
|
|
17
|
+
import { printSuccess } from '../../lib/ui.js';
|
|
18
|
+
const portalDeployText = (key, values, fallback) => translateCli(`commands.portalDeploy.${key}`, values, { fallback });
|
|
19
|
+
export default class PortalDeploy extends Command {
|
|
20
|
+
static summary = 'Build and deploy a portal';
|
|
21
|
+
static examples = [
|
|
22
|
+
'<%= config.bin %> <%= command.id %> customer',
|
|
23
|
+
'<%= config.bin %> <%= command.id %> customer --env dev --yes',
|
|
24
|
+
];
|
|
25
|
+
static args = {
|
|
26
|
+
portal: Args.string({
|
|
27
|
+
required: true,
|
|
28
|
+
description: 'Portal name',
|
|
29
|
+
}),
|
|
30
|
+
};
|
|
31
|
+
static flags = {
|
|
32
|
+
env: Flags.string({
|
|
33
|
+
char: 'e',
|
|
34
|
+
description: 'CLI env name; omitted uses the current env',
|
|
35
|
+
}),
|
|
36
|
+
yes: Flags.boolean({
|
|
37
|
+
char: 'y',
|
|
38
|
+
description: 'Confirm using --env when it targets a different env than the current env',
|
|
39
|
+
default: false,
|
|
40
|
+
}),
|
|
41
|
+
};
|
|
42
|
+
async run() {
|
|
43
|
+
const { args, flags } = await this.parse(PortalDeploy);
|
|
44
|
+
const requestedEnv = hasExplicitEnvSelection(this.argv) ? flags.env : undefined;
|
|
45
|
+
const confirmed = await ensureCrossEnvConfirmed({
|
|
46
|
+
command: this,
|
|
47
|
+
requestedEnv,
|
|
48
|
+
yes: flags.yes,
|
|
49
|
+
});
|
|
50
|
+
if (!confirmed) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const scope = resolveDefaultConfigScope();
|
|
54
|
+
const envName = requestedEnv ?? (await getCurrentEnvName({ scope }));
|
|
55
|
+
const env = await getEnv(envName, { scope });
|
|
56
|
+
if (!env) {
|
|
57
|
+
this.error(portalDeployText(requestedEnv ? 'errors.envNotConfigured' : 'errors.noEnvConfigured', { envName }, requestedEnv
|
|
58
|
+
? `Env "${envName}" is not configured. Run \`nb env add ${envName} --api-base-url <url>\` first.`
|
|
59
|
+
: 'No NocoBase env is configured yet. Run `nb init --ui` to create one first.'));
|
|
60
|
+
}
|
|
61
|
+
const result = await deployPortalWorkspace({
|
|
62
|
+
portal: args.portal,
|
|
63
|
+
env,
|
|
64
|
+
envName,
|
|
65
|
+
cliVersion: String(this.config.pjson.version ?? '').trim(),
|
|
66
|
+
});
|
|
67
|
+
printSuccess(portalDeployText('messages.deployed', { portal: result.portal }, `Portal "${result.portal}" deployed.`));
|
|
68
|
+
const info = await listPortalWorkspaces({
|
|
69
|
+
env,
|
|
70
|
+
envName,
|
|
71
|
+
cliVersion: String(this.config.pjson.version ?? '').trim(),
|
|
72
|
+
});
|
|
73
|
+
const portal = findPortalListItem(info.items, result.portal);
|
|
74
|
+
if (!portal) {
|
|
75
|
+
this.error(translateCli(`commands.portalInfo.errors.notFound`, { portal: result.portal }, {
|
|
76
|
+
fallback: `Portal "${result.portal}" was not found.`,
|
|
77
|
+
}));
|
|
78
|
+
}
|
|
79
|
+
this.log(formatPortalInfo(portal));
|
|
80
|
+
}
|
|
81
|
+
}
|