@nocobase/cli 2.1.31 → 2.2.0-alpha.10
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/config/set.js +1 -0
- package/dist/commands/init.js +119 -3
- package/dist/commands/install.js +97 -2
- package/dist/commands/portal/config.js +88 -0
- package/dist/commands/portal/create.js +104 -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/auth-store.js +3 -1
- package/dist/lib/cli-config.js +20 -1
- package/dist/lib/env-config.js +3 -0
- package/dist/lib/env-proxy.js +94 -2
- package/dist/lib/managed-init-env.js +6 -1
- 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/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/package.json +5 -2
|
@@ -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
|
@@ -32,8 +32,12 @@ import EnvAdd from "./env/add.js";
|
|
|
32
32
|
import Install, { defaultDbPortForDialect } from "./install.js";
|
|
33
33
|
const DEFAULT_INIT_API_BASE_URL = 'http://localhost:13000/api';
|
|
34
34
|
const DEFAULT_INIT_APP_NAME = 'local';
|
|
35
|
+
const DEFAULT_INIT_PORTAL_TYPE = 'no-code';
|
|
36
|
+
const DEFAULT_INIT_PORTAL_NAME = 'admin';
|
|
37
|
+
const DEFAULT_INIT_PORTAL_TEMPLATE = '@nocobase/portal-template-default';
|
|
35
38
|
const DOWNLOAD_OUTPUT_DIR_PROMPT = Download.prompts.outputDir;
|
|
36
39
|
const INIT_SETUP_MODES = ['install-new', 'manage-local', 'connect-remote'];
|
|
40
|
+
const INIT_PORTAL_TYPES = ['no-code', 'ai'];
|
|
37
41
|
const INIT_ENV_ADD_FLAG_NAMES = [
|
|
38
42
|
'locale',
|
|
39
43
|
'default-api-base-url',
|
|
@@ -80,6 +84,9 @@ function isInstallNewSetupMode(values) {
|
|
|
80
84
|
function isInstallLikeSetupMode(values) {
|
|
81
85
|
return !isRemoteSetupMode(values);
|
|
82
86
|
}
|
|
87
|
+
function isAiMode(values) {
|
|
88
|
+
return String(values.portalType ?? DEFAULT_INIT_PORTAL_TYPE).trim() === 'ai';
|
|
89
|
+
}
|
|
83
90
|
function remoteConnectionOnly(def) {
|
|
84
91
|
return withExtraHidden(def, (values) => !isRemoteSetupMode(values));
|
|
85
92
|
}
|
|
@@ -387,6 +394,42 @@ Prompt modes:
|
|
|
387
394
|
devDependencies: installLikeDownloadExecutionOnly(Download.prompts.devDependencies),
|
|
388
395
|
build: installLikeDownloadExecutionOnly(Download.prompts.build),
|
|
389
396
|
buildDts: installLikeDownloadExecutionOnly(Download.prompts.buildDts),
|
|
397
|
+
portalType: installNewOnly({
|
|
398
|
+
type: 'select',
|
|
399
|
+
variant: 'radio',
|
|
400
|
+
message: initText('prompts.portalType.message'),
|
|
401
|
+
options: [
|
|
402
|
+
{
|
|
403
|
+
value: 'no-code',
|
|
404
|
+
label: initText('prompts.portalType.noCodeLabel'),
|
|
405
|
+
hint: initText('prompts.portalType.noCodeHint'),
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
value: 'ai',
|
|
409
|
+
label: initText('prompts.portalType.aiLabel'),
|
|
410
|
+
hint: initText('prompts.portalType.aiHint'),
|
|
411
|
+
},
|
|
412
|
+
],
|
|
413
|
+
initialValue: DEFAULT_INIT_PORTAL_TYPE,
|
|
414
|
+
yesInitialValue: DEFAULT_INIT_PORTAL_TYPE,
|
|
415
|
+
required: true,
|
|
416
|
+
}),
|
|
417
|
+
portalName: installNewOnly({
|
|
418
|
+
type: 'text',
|
|
419
|
+
message: initText('prompts.portalName.message'),
|
|
420
|
+
placeholder: DEFAULT_INIT_PORTAL_NAME,
|
|
421
|
+
initialValue: DEFAULT_INIT_PORTAL_NAME,
|
|
422
|
+
yesInitialValue: DEFAULT_INIT_PORTAL_NAME,
|
|
423
|
+
required: true,
|
|
424
|
+
}),
|
|
425
|
+
portalTemplate: installNewOnly({
|
|
426
|
+
type: 'text',
|
|
427
|
+
message: initText('prompts.portalTemplate.message'),
|
|
428
|
+
placeholder: DEFAULT_INIT_PORTAL_TEMPLATE,
|
|
429
|
+
yesInitialValue: DEFAULT_INIT_PORTAL_TEMPLATE,
|
|
430
|
+
hidden: (values) => !isAiMode(values),
|
|
431
|
+
required: true,
|
|
432
|
+
}),
|
|
390
433
|
dbDialect: installLikeOnly(Install.dbPrompts.dbDialect),
|
|
391
434
|
builtinDb: installLikeOnly(Install.dbPrompts.builtinDb),
|
|
392
435
|
builtinDbImage: installLikeOnly(Install.dbPrompts.builtinDbImage),
|
|
@@ -412,6 +455,13 @@ Prompt modes:
|
|
|
412
455
|
...Init.prompts,
|
|
413
456
|
installApiBaseUrl: createInstallConnectionApiBaseUrlPrompt(options.defaultApiHost),
|
|
414
457
|
};
|
|
458
|
+
const defaultPortalTemplate = String(options.defaultPortalTemplate ?? '').trim();
|
|
459
|
+
if (defaultPortalTemplate) {
|
|
460
|
+
prompts.portalTemplate = {
|
|
461
|
+
...prompts.portalTemplate,
|
|
462
|
+
yesInitialValue: defaultPortalTemplate,
|
|
463
|
+
};
|
|
464
|
+
}
|
|
415
465
|
if (flags['skip-auth']) {
|
|
416
466
|
const accessTokenPrompt = {
|
|
417
467
|
...EnvAdd.prompts.accessToken,
|
|
@@ -461,6 +511,16 @@ Prompt modes:
|
|
|
461
511
|
description: 'Setup mode: install a new app, manage a local app by reusing its database, or connect a remote app',
|
|
462
512
|
options: [...INIT_SETUP_MODES],
|
|
463
513
|
}),
|
|
514
|
+
'portal-type': Flags.string({
|
|
515
|
+
description: 'Initial portal type: no-code or ai',
|
|
516
|
+
options: [...INIT_PORTAL_TYPES],
|
|
517
|
+
}),
|
|
518
|
+
'portal-name': Flags.string({
|
|
519
|
+
description: 'Initial portal name',
|
|
520
|
+
}),
|
|
521
|
+
'portal-template': Flags.string({
|
|
522
|
+
description: 'Initial portal template npm package or local path when --portal-type ai is used',
|
|
523
|
+
}),
|
|
464
524
|
ui: Flags.boolean({
|
|
465
525
|
description: 'Open the guided setup flow in a local browser form (not valid with --yes)',
|
|
466
526
|
default: false,
|
|
@@ -578,7 +638,8 @@ Prompt modes:
|
|
|
578
638
|
const dynamicInitialValues = await Init.buildDynamicInitialValuesForInstall(normalizedFlags, presetValues);
|
|
579
639
|
const defaultUiHost = await resolveDefaultUiHost();
|
|
580
640
|
const defaultApiHost = await resolveDefaultApiHost();
|
|
581
|
-
const
|
|
641
|
+
const defaultPortalTemplate = String(dynamicInitialValues.portalTemplate ?? '').trim();
|
|
642
|
+
const promptCatalog = this.buildPromptCatalog(normalizedFlags, { defaultApiHost, defaultPortalTemplate });
|
|
582
643
|
if (useBrowserUi) {
|
|
583
644
|
presetValues = await runPromptCatalogWebUI({
|
|
584
645
|
stages: Init.buildWebUiStages(promptCatalog),
|
|
@@ -607,6 +668,7 @@ Prompt modes:
|
|
|
607
668
|
? { setupMode: normalizeInitSetupMode(presetValues.hasNocobase) }
|
|
608
669
|
: {}),
|
|
609
670
|
},
|
|
671
|
+
yesInitialValues: pickKeys(dynamicInitialValues, ['portalTemplate']),
|
|
610
672
|
values: presetValues,
|
|
611
673
|
yes: normalizedFlags.yes || useBrowserUi || !interactive,
|
|
612
674
|
hooks: {
|
|
@@ -679,7 +741,9 @@ Prompt modes:
|
|
|
679
741
|
}
|
|
680
742
|
static async buildDynamicInitialValuesForInstall(flags, presetValues) {
|
|
681
743
|
const out = {};
|
|
682
|
-
|
|
744
|
+
const shouldResolveAppInitialValues = !Object.prototype.hasOwnProperty.call(presetValues, 'appPort') ||
|
|
745
|
+
!Object.prototype.hasOwnProperty.call(presetValues, 'portalTemplate');
|
|
746
|
+
if (shouldResolveAppInitialValues) {
|
|
683
747
|
const appInitialValues = await Install.buildAppPromptInitialValues({
|
|
684
748
|
envName: String(presetValues.appName ?? '').trim(),
|
|
685
749
|
flags: {
|
|
@@ -687,12 +751,20 @@ Prompt modes:
|
|
|
687
751
|
'app-path': flags['app-path'] ?? '',
|
|
688
752
|
'app-root-path': flags['app-root-path'] ?? '',
|
|
689
753
|
'storage-path': flags['storage-path'] ?? '',
|
|
754
|
+
'portal-template': flags['portal-template'] ??
|
|
755
|
+
(Object.prototype.hasOwnProperty.call(presetValues, 'portalTemplate')
|
|
756
|
+
? String(presetValues.portalTemplate ?? '')
|
|
757
|
+
: undefined),
|
|
690
758
|
},
|
|
691
759
|
warnOnPortFallback: false,
|
|
692
760
|
});
|
|
693
|
-
if (appInitialValues.appPort !== undefined) {
|
|
761
|
+
if (appInitialValues.appPort !== undefined && !Object.prototype.hasOwnProperty.call(presetValues, 'appPort')) {
|
|
694
762
|
out.appPort = appInitialValues.appPort;
|
|
695
763
|
}
|
|
764
|
+
if (appInitialValues.portalTemplate !== undefined &&
|
|
765
|
+
!Object.prototype.hasOwnProperty.call(presetValues, 'portalTemplate')) {
|
|
766
|
+
out.portalTemplate = appInitialValues.portalTemplate;
|
|
767
|
+
}
|
|
696
768
|
}
|
|
697
769
|
const downloadSeed = { ...presetValues };
|
|
698
770
|
if (flags.yes && !Object.prototype.hasOwnProperty.call(downloadSeed, 'source')) {
|
|
@@ -771,6 +843,14 @@ Prompt modes:
|
|
|
771
843
|
buildDts: c.buildDts,
|
|
772
844
|
},
|
|
773
845
|
},
|
|
846
|
+
{
|
|
847
|
+
sectionTitle: initText('webUi.portalType.title'),
|
|
848
|
+
sectionDescription: initText('webUi.portalType.description'),
|
|
849
|
+
catalog: {
|
|
850
|
+
portalName: c.portalName,
|
|
851
|
+
portalType: c.portalType,
|
|
852
|
+
},
|
|
853
|
+
},
|
|
774
854
|
{
|
|
775
855
|
sectionTitle: initText('webUi.configureDatabase.title'),
|
|
776
856
|
sectionDescription: initText('webUi.configureDatabase.description'),
|
|
@@ -871,6 +951,15 @@ Prompt modes:
|
|
|
871
951
|
if (flags['app-public-path'] !== undefined && String(flags['app-public-path']).trim() !== '') {
|
|
872
952
|
preset.appPublicPath = String(flags['app-public-path']).trim();
|
|
873
953
|
}
|
|
954
|
+
if (flags['portal-type'] !== undefined && String(flags['portal-type']).trim() !== '') {
|
|
955
|
+
preset.portalType = String(flags['portal-type']).trim();
|
|
956
|
+
}
|
|
957
|
+
if (flags['portal-name'] !== undefined && String(flags['portal-name']).trim() !== '') {
|
|
958
|
+
preset.portalName = String(flags['portal-name']).trim();
|
|
959
|
+
}
|
|
960
|
+
if (flags['portal-template'] !== undefined && String(flags['portal-template']).trim() !== '') {
|
|
961
|
+
preset.portalTemplate = String(flags['portal-template']).trim();
|
|
962
|
+
}
|
|
874
963
|
if (flags['root-username'] !== undefined) {
|
|
875
964
|
preset.rootUsername = String(flags['root-username'] ?? '').trim();
|
|
876
965
|
}
|
|
@@ -1008,6 +1097,9 @@ Prompt modes:
|
|
|
1008
1097
|
const existingEnv = await getEnv(envName, { scope: resolveDefaultConfigScope() });
|
|
1009
1098
|
const appPort = String(results.appPort ?? '').trim();
|
|
1010
1099
|
const appPublicPath = String(results.appPublicPath ?? '').trim();
|
|
1100
|
+
const portalType = String(results.portalType ?? '').trim();
|
|
1101
|
+
const portalName = String(results.portalName ?? '').trim();
|
|
1102
|
+
const portalTemplate = String(results.portalTemplate ?? '').trim();
|
|
1011
1103
|
const source = String(results.source ?? '').trim();
|
|
1012
1104
|
const version = resolveInitDownloadVersion(results);
|
|
1013
1105
|
const dockerRegistry = String(results.dockerRegistry ?? '').trim();
|
|
@@ -1080,6 +1172,9 @@ Prompt modes:
|
|
|
1080
1172
|
...(storagePath && !areConfiguredPathsEquivalent(storagePath, derivedStoragePath) ? { storagePath } : {}),
|
|
1081
1173
|
...(appPort ? { appPort } : {}),
|
|
1082
1174
|
...(appPublicPath ? { appPublicPath } : {}),
|
|
1175
|
+
...(portalType && portalType !== DEFAULT_INIT_PORTAL_TYPE ? { portalType } : {}),
|
|
1176
|
+
...(portalName ? { portalName } : {}),
|
|
1177
|
+
...(portalTemplate ? { portalTemplate } : {}),
|
|
1083
1178
|
...(appKey ? { appKey } : {}),
|
|
1084
1179
|
...(timeZone ? { timezone: timeZone } : {}),
|
|
1085
1180
|
...(!skipDownload && results.devDependencies !== undefined
|
|
@@ -1204,6 +1299,18 @@ Prompt modes:
|
|
|
1204
1299
|
if (appPublicPath) {
|
|
1205
1300
|
argv.push('--app-public-path', appPublicPath);
|
|
1206
1301
|
}
|
|
1302
|
+
const portalType = String(results.portalType ?? '').trim();
|
|
1303
|
+
if (portalType && portalType !== DEFAULT_INIT_PORTAL_TYPE) {
|
|
1304
|
+
argv.push('--portal-type', portalType);
|
|
1305
|
+
}
|
|
1306
|
+
const portalName = String(results.portalName ?? '').trim();
|
|
1307
|
+
if (portalName) {
|
|
1308
|
+
argv.push('--portal-name', portalName);
|
|
1309
|
+
}
|
|
1310
|
+
const portalTemplate = String(results.portalTemplate ?? '').trim();
|
|
1311
|
+
if (portalTemplate) {
|
|
1312
|
+
argv.push('--portal-template', portalTemplate);
|
|
1313
|
+
}
|
|
1207
1314
|
if (flags.force) {
|
|
1208
1315
|
argv.push('--force');
|
|
1209
1316
|
}
|
|
@@ -1401,6 +1508,15 @@ Prompt modes:
|
|
|
1401
1508
|
delete normalized.rootPassword;
|
|
1402
1509
|
delete normalized.rootNickname;
|
|
1403
1510
|
}
|
|
1511
|
+
const portalType = normalizeConnectionString(normalized.portalType) || DEFAULT_INIT_PORTAL_TYPE;
|
|
1512
|
+
normalized.portalType = portalType;
|
|
1513
|
+
normalized.portalName = normalizeConnectionString(normalized.portalName) || DEFAULT_INIT_PORTAL_NAME;
|
|
1514
|
+
if (portalType === 'ai') {
|
|
1515
|
+
normalized.portalTemplate = normalizeConnectionString(normalized.portalTemplate);
|
|
1516
|
+
}
|
|
1517
|
+
else {
|
|
1518
|
+
delete normalized.portalTemplate;
|
|
1519
|
+
}
|
|
1404
1520
|
delete normalized.installApiBaseUrl;
|
|
1405
1521
|
delete normalized.installAuthType;
|
|
1406
1522
|
delete normalized.installUsername;
|
package/dist/commands/install.js
CHANGED
|
@@ -53,6 +53,10 @@ const DEFAULT_INSTALL_ROOT_EMAIL = 'admin@nocobase.com';
|
|
|
53
53
|
const DEFAULT_INSTALL_ROOT_PASSWORD = 'admin123';
|
|
54
54
|
const DEFAULT_INSTALL_ROOT_NICKNAME = 'Super Admin';
|
|
55
55
|
const DEFAULT_INSTALL_API_HOST = '127.0.0.1';
|
|
56
|
+
const DEFAULT_INSTALL_PORTAL_TYPE = 'no-code';
|
|
57
|
+
const DEFAULT_INSTALL_PORTAL_NAME = 'admin';
|
|
58
|
+
const DEFAULT_INSTALL_PORTAL_TEMPLATE = '@nocobase/portal-template-default';
|
|
59
|
+
const INSTALL_PORTAL_TYPES = ['no-code', 'ai'];
|
|
56
60
|
function toOptionalPromptString(value) {
|
|
57
61
|
const text = String(value ?? '').trim();
|
|
58
62
|
return text || undefined;
|
|
@@ -180,6 +184,9 @@ function defaultBuiltinDbImageForDialect(value, options) {
|
|
|
180
184
|
function defaultDbDatabaseForDialect(value) {
|
|
181
185
|
return String(value ?? '').trim() === 'kingbase' ? 'kingbase' : DEFAULT_INSTALL_DB_DATABASE;
|
|
182
186
|
}
|
|
187
|
+
function isAiMode(values) {
|
|
188
|
+
return String(values.portalType ?? DEFAULT_INSTALL_PORTAL_TYPE).trim() === 'ai';
|
|
189
|
+
}
|
|
183
190
|
function supportsDbSchemaPrompt(value) {
|
|
184
191
|
const dialect = String(value ?? '').trim();
|
|
185
192
|
return dialect === 'postgres' || dialect === 'kingbase';
|
|
@@ -404,6 +411,16 @@ export default class Install extends Command {
|
|
|
404
411
|
'app-public-path': Flags.string({
|
|
405
412
|
description: 'Public path for the local app, for example / or /console/',
|
|
406
413
|
}),
|
|
414
|
+
'portal-type': Flags.string({
|
|
415
|
+
description: 'Initial portal type for the installed app',
|
|
416
|
+
options: [...INSTALL_PORTAL_TYPES],
|
|
417
|
+
}),
|
|
418
|
+
'portal-name': Flags.string({
|
|
419
|
+
description: 'Initial portal name',
|
|
420
|
+
}),
|
|
421
|
+
'portal-template': Flags.string({
|
|
422
|
+
description: 'Initial portal template npm package or local path when --portal-type ai is used',
|
|
423
|
+
}),
|
|
407
424
|
'root-username': Flags.string({
|
|
408
425
|
description: 'Initial admin username for the installed app',
|
|
409
426
|
required: false,
|
|
@@ -513,6 +530,41 @@ export default class Install extends Command {
|
|
|
513
530
|
yesInitialValue: '/',
|
|
514
531
|
validate: validateAppPublicPath,
|
|
515
532
|
},
|
|
533
|
+
portalType: {
|
|
534
|
+
type: 'select',
|
|
535
|
+
message: installText('prompts.portalType.message'),
|
|
536
|
+
options: [
|
|
537
|
+
{
|
|
538
|
+
value: 'no-code',
|
|
539
|
+
label: installText('prompts.portalType.noCodeLabel'),
|
|
540
|
+
hint: installText('prompts.portalType.noCodeHint'),
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
value: 'ai',
|
|
544
|
+
label: installText('prompts.portalType.aiLabel'),
|
|
545
|
+
hint: installText('prompts.portalType.aiHint'),
|
|
546
|
+
},
|
|
547
|
+
],
|
|
548
|
+
initialValue: DEFAULT_INSTALL_PORTAL_TYPE,
|
|
549
|
+
yesInitialValue: DEFAULT_INSTALL_PORTAL_TYPE,
|
|
550
|
+
required: true,
|
|
551
|
+
},
|
|
552
|
+
portalName: {
|
|
553
|
+
type: 'text',
|
|
554
|
+
message: installText('prompts.portalName.message'),
|
|
555
|
+
placeholder: DEFAULT_INSTALL_PORTAL_NAME,
|
|
556
|
+
initialValue: DEFAULT_INSTALL_PORTAL_NAME,
|
|
557
|
+
yesInitialValue: DEFAULT_INSTALL_PORTAL_NAME,
|
|
558
|
+
required: true,
|
|
559
|
+
},
|
|
560
|
+
portalTemplate: {
|
|
561
|
+
type: 'text',
|
|
562
|
+
message: installText('prompts.portalTemplate.message'),
|
|
563
|
+
placeholder: DEFAULT_INSTALL_PORTAL_TEMPLATE,
|
|
564
|
+
yesInitialValue: DEFAULT_INSTALL_PORTAL_TEMPLATE,
|
|
565
|
+
hidden: (values) => !isAiMode(values),
|
|
566
|
+
required: true,
|
|
567
|
+
},
|
|
516
568
|
};
|
|
517
569
|
}
|
|
518
570
|
static dbPrompts = {
|
|
@@ -772,6 +824,24 @@ export default class Install extends Command {
|
|
|
772
824
|
preset.appPublicPath = v;
|
|
773
825
|
}
|
|
774
826
|
}
|
|
827
|
+
if (flags['portal-type'] !== undefined) {
|
|
828
|
+
const v = String(flags['portal-type'] ?? '').trim();
|
|
829
|
+
if (v) {
|
|
830
|
+
preset.portalType = v;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
if (flags['portal-name'] !== undefined) {
|
|
834
|
+
const v = String(flags['portal-name'] ?? '').trim();
|
|
835
|
+
if (v) {
|
|
836
|
+
preset.portalName = v;
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
if (flags['portal-template'] !== undefined) {
|
|
840
|
+
const v = String(flags['portal-template'] ?? '').trim();
|
|
841
|
+
if (v) {
|
|
842
|
+
preset.portalTemplate = v;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
775
845
|
if (flags['root-username'] !== undefined) {
|
|
776
846
|
preset.rootUsername = String(flags['root-username'] ?? '').trim();
|
|
777
847
|
}
|
|
@@ -863,6 +933,9 @@ export default class Install extends Command {
|
|
|
863
933
|
'appPort',
|
|
864
934
|
'storagePath',
|
|
865
935
|
'appPublicPath',
|
|
936
|
+
'portalType',
|
|
937
|
+
'portalName',
|
|
938
|
+
'portalTemplate',
|
|
866
939
|
]);
|
|
867
940
|
}
|
|
868
941
|
static buildDbPresetValuesFromFlags(flags, argv = process.argv.slice(2)) {
|
|
@@ -1112,6 +1185,9 @@ export default class Install extends Command {
|
|
|
1112
1185
|
const rootPassword = Install.toOptionalPromptString(config.rootPassword);
|
|
1113
1186
|
const rootNickname = Install.toOptionalPromptString(config.rootNickname);
|
|
1114
1187
|
const lang = Install.toOptionalPromptString(config.lang);
|
|
1188
|
+
const portalType = Install.toOptionalPromptString(config.portalType);
|
|
1189
|
+
const portalName = Install.toOptionalPromptString(config.portalName);
|
|
1190
|
+
const portalTemplate = Install.toOptionalPromptString(config.portalTemplate);
|
|
1115
1191
|
const auth = config.auth;
|
|
1116
1192
|
const savedAuthType = Install.toOptionalPromptString(config.authType) ?? Install.toOptionalPromptString(auth?.type);
|
|
1117
1193
|
const appPreset = {
|
|
@@ -1121,6 +1197,9 @@ export default class Install extends Command {
|
|
|
1121
1197
|
...(appPort ? { appPort } : {}),
|
|
1122
1198
|
...(storagePath ? { storagePath } : {}),
|
|
1123
1199
|
...(appPublicPath ? { appPublicPath } : {}),
|
|
1200
|
+
...(portalType ? { portalType } : {}),
|
|
1201
|
+
...(portalName ? { portalName } : {}),
|
|
1202
|
+
...(portalTemplate ? { portalTemplate } : {}),
|
|
1124
1203
|
...(hookScript ? { hookScript } : {}),
|
|
1125
1204
|
};
|
|
1126
1205
|
const downloadPreset = {
|
|
@@ -1263,6 +1342,12 @@ export default class Install extends Command {
|
|
|
1263
1342
|
warn: params.warnOnPortFallback ?? true,
|
|
1264
1343
|
});
|
|
1265
1344
|
}
|
|
1345
|
+
if (params.flags['portal-template'] === undefined) {
|
|
1346
|
+
const defaultPortalTemplate = Install.toOptionalPromptString(await getCliConfigValue('default-portal-template'));
|
|
1347
|
+
if (defaultPortalTemplate) {
|
|
1348
|
+
initialValues.portalTemplate = defaultPortalTemplate;
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1266
1351
|
return initialValues;
|
|
1267
1352
|
}
|
|
1268
1353
|
static shouldPublishBuiltinDbPortForValues(values) {
|
|
@@ -1430,14 +1515,17 @@ export default class Install extends Command {
|
|
|
1430
1515
|
static buildDockerAppContainerName(envName, containerPrefix) {
|
|
1431
1516
|
return Install.sanitizeDockerResourceName(`${Install.buildBuiltinDbContainerPrefix(containerPrefix)}-${envName}-app`);
|
|
1432
1517
|
}
|
|
1433
|
-
static buildInitAppEnvVars(params) {
|
|
1518
|
+
static buildInitAppEnvVars(params, options = {}) {
|
|
1434
1519
|
return buildInitAppEnvVarsFromConfig({
|
|
1435
1520
|
lang: String(params.appResults.lang ?? ''),
|
|
1436
1521
|
rootUsername: String(params.rootResults.rootUsername ?? ''),
|
|
1437
1522
|
rootEmail: String(params.rootResults.rootEmail ?? ''),
|
|
1438
1523
|
rootPassword: String(params.rootResults.rootPassword ?? ''),
|
|
1439
1524
|
rootNickname: String(params.rootResults.rootNickname ?? ''),
|
|
1440
|
-
|
|
1525
|
+
portalType: String(params.appResults.portalType ?? ''),
|
|
1526
|
+
portalName: String(params.appResults.portalName ?? ''),
|
|
1527
|
+
portalTemplate: String(params.appResults.portalTemplate ?? ''),
|
|
1528
|
+
}, options);
|
|
1441
1529
|
}
|
|
1442
1530
|
static shouldPublishBuiltinDbPort(source) {
|
|
1443
1531
|
return String(source ?? '').trim() !== 'docker';
|
|
@@ -2309,6 +2397,9 @@ export default class Install extends Command {
|
|
|
2309
2397
|
const appRootPath = Install.toOptionalPromptString(params.appResults.appRootPath);
|
|
2310
2398
|
const storagePath = Install.toOptionalPromptString(params.appResults.storagePath);
|
|
2311
2399
|
const appPublicPath = Install.toOptionalPromptString(params.appResults.appPublicPath);
|
|
2400
|
+
const portalType = Install.toOptionalPromptString(params.appResults.portalType);
|
|
2401
|
+
const portalName = Install.toOptionalPromptString(params.appResults.portalName);
|
|
2402
|
+
const portalTemplate = Install.toOptionalPromptString(params.appResults.portalTemplate);
|
|
2312
2403
|
const derivedAppRootPath = appPath ? deriveConfiguredSourcePath(appPath) : undefined;
|
|
2313
2404
|
const derivedStoragePath = appPath ? deriveConfiguredStoragePath(appPath) : undefined;
|
|
2314
2405
|
const appPort = String(params.appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim() || DEFAULT_INSTALL_APP_PORT;
|
|
@@ -2343,6 +2434,9 @@ export default class Install extends Command {
|
|
|
2343
2434
|
...(appPublicPath ? { appPublicPath } : {}),
|
|
2344
2435
|
...(envFile ? { envFile } : {}),
|
|
2345
2436
|
lang: params.appResults.lang,
|
|
2437
|
+
portalType,
|
|
2438
|
+
portalName,
|
|
2439
|
+
portalTemplate,
|
|
2346
2440
|
appKey: params.appResults.appKey,
|
|
2347
2441
|
timezone: params.appResults.timeZone,
|
|
2348
2442
|
builtinDb: params.dbResults.builtinDb,
|
|
@@ -2394,6 +2488,7 @@ export default class Install extends Command {
|
|
|
2394
2488
|
'app-root-path': parsed['app-root-path'] ?? Install.toOptionalPromptString(appPreset.appRootPath),
|
|
2395
2489
|
'app-port': parsed['app-port'] ?? Install.toOptionalPromptString(appPreset.appPort),
|
|
2396
2490
|
'storage-path': parsed['storage-path'] ?? Install.toOptionalPromptString(appPreset.storagePath),
|
|
2491
|
+
'portal-template': parsed['portal-template'] ?? Install.toOptionalPromptString(appPreset.portalTemplate),
|
|
2397
2492
|
},
|
|
2398
2493
|
}),
|
|
2399
2494
|
values: appPreset,
|
|
@@ -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,104 @@
|
|
|
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 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: '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 env = await getEnv(flags.env, { scope: resolveDefaultConfigScope() });
|
|
80
|
+
if (!env) {
|
|
81
|
+
this.error(flags.env
|
|
82
|
+
? portalCreateText('errors.envNotConfigured', { envName: flags.env }, `Env "${flags.env}" is not configured. Run \`nb env add ${flags.env} --api-base-url <url>\` first.`)
|
|
83
|
+
: portalCreateText('errors.noEnvConfigured', undefined, 'No NocoBase env is configured yet. Run `nb init --ui` to create one first.'));
|
|
84
|
+
}
|
|
85
|
+
const result = await createPortalWorkspace({
|
|
86
|
+
portal: args.portal,
|
|
87
|
+
title: flags.title,
|
|
88
|
+
template: flags.template,
|
|
89
|
+
env,
|
|
90
|
+
envName: flags.env,
|
|
91
|
+
cliVersion: String(this.config.pjson.version ?? '').trim(),
|
|
92
|
+
force: flags.force,
|
|
93
|
+
sourceStorage: flags['source-storage'],
|
|
94
|
+
gitRepo: flags['git-repo'],
|
|
95
|
+
gitBranch: flags['git-branch'],
|
|
96
|
+
gitPath: flags['git-path'],
|
|
97
|
+
onSkipInstall: (message) => printInfo(message),
|
|
98
|
+
});
|
|
99
|
+
printSuccess(portalCreateText('messages.created', { portal: result.portal, portalDir: result.portalDir }, `Portal "${result.portal}" created at ${result.portalDir}.`));
|
|
100
|
+
printInfo(portalCreateText('messages.app', { app: result.app }, `App: ${result.app}`));
|
|
101
|
+
printInfo(portalCreateText('messages.base', { base: result.portalBase }, `Base: ${result.portalBase}`));
|
|
102
|
+
printInfo(portalCreateText('messages.sourceStorage', { sourceStorage: result.sourceStorage }, `Source storage: ${result.sourceStorage}`));
|
|
103
|
+
}
|
|
104
|
+
}
|