@nocobase/cli 2.2.0-alpha.10 → 2.2.0-alpha.12
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/init.js +12 -120
- package/dist/commands/install.js +9 -3
- package/dist/commands/portal/create.js +4 -3
- package/dist/lib/api-client.js +7 -0
- package/dist/lib/env-auth.js +2 -2
- package/dist/lib/env-proxy.js +68 -6
- package/dist/lib/managed-env-file.js +58 -2
- package/dist/lib/naming.js +9 -0
- package/dist/lib/proxy-caddy.js +2 -0
- package/dist/lib/proxy-nginx.js +1 -0
- package/nocobase-ctl.config.json +111 -0
- package/package.json +2 -2
package/dist/commands/init.js
CHANGED
|
@@ -27,17 +27,14 @@ 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";
|
|
33
34
|
const DEFAULT_INIT_API_BASE_URL = 'http://localhost:13000/api';
|
|
34
35
|
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';
|
|
38
36
|
const DOWNLOAD_OUTPUT_DIR_PROMPT = Download.prompts.outputDir;
|
|
39
37
|
const INIT_SETUP_MODES = ['install-new', 'manage-local', 'connect-remote'];
|
|
40
|
-
const INIT_PORTAL_TYPES = ['no-code', 'ai'];
|
|
41
38
|
const INIT_ENV_ADD_FLAG_NAMES = [
|
|
42
39
|
'locale',
|
|
43
40
|
'default-api-base-url',
|
|
@@ -84,9 +81,6 @@ function isInstallNewSetupMode(values) {
|
|
|
84
81
|
function isInstallLikeSetupMode(values) {
|
|
85
82
|
return !isRemoteSetupMode(values);
|
|
86
83
|
}
|
|
87
|
-
function isAiMode(values) {
|
|
88
|
-
return String(values.portalType ?? DEFAULT_INIT_PORTAL_TYPE).trim() === 'ai';
|
|
89
|
-
}
|
|
90
84
|
function remoteConnectionOnly(def) {
|
|
91
85
|
return withExtraHidden(def, (values) => !isRemoteSetupMode(values));
|
|
92
86
|
}
|
|
@@ -394,42 +388,6 @@ Prompt modes:
|
|
|
394
388
|
devDependencies: installLikeDownloadExecutionOnly(Download.prompts.devDependencies),
|
|
395
389
|
build: installLikeDownloadExecutionOnly(Download.prompts.build),
|
|
396
390
|
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
|
-
}),
|
|
433
391
|
dbDialect: installLikeOnly(Install.dbPrompts.dbDialect),
|
|
434
392
|
builtinDb: installLikeOnly(Install.dbPrompts.builtinDb),
|
|
435
393
|
builtinDbImage: installLikeOnly(Install.dbPrompts.builtinDbImage),
|
|
@@ -455,13 +413,6 @@ Prompt modes:
|
|
|
455
413
|
...Init.prompts,
|
|
456
414
|
installApiBaseUrl: createInstallConnectionApiBaseUrlPrompt(options.defaultApiHost),
|
|
457
415
|
};
|
|
458
|
-
const defaultPortalTemplate = String(options.defaultPortalTemplate ?? '').trim();
|
|
459
|
-
if (defaultPortalTemplate) {
|
|
460
|
-
prompts.portalTemplate = {
|
|
461
|
-
...prompts.portalTemplate,
|
|
462
|
-
yesInitialValue: defaultPortalTemplate,
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
416
|
if (flags['skip-auth']) {
|
|
466
417
|
const accessTokenPrompt = {
|
|
467
418
|
...EnvAdd.prompts.accessToken,
|
|
@@ -511,16 +462,6 @@ Prompt modes:
|
|
|
511
462
|
description: 'Setup mode: install a new app, manage a local app by reusing its database, or connect a remote app',
|
|
512
463
|
options: [...INIT_SETUP_MODES],
|
|
513
464
|
}),
|
|
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
|
-
}),
|
|
524
465
|
ui: Flags.boolean({
|
|
525
466
|
description: 'Open the guided setup flow in a local browser form (not valid with --yes)',
|
|
526
467
|
default: false,
|
|
@@ -638,8 +579,7 @@ Prompt modes:
|
|
|
638
579
|
const dynamicInitialValues = await Init.buildDynamicInitialValuesForInstall(normalizedFlags, presetValues);
|
|
639
580
|
const defaultUiHost = await resolveDefaultUiHost();
|
|
640
581
|
const defaultApiHost = await resolveDefaultApiHost();
|
|
641
|
-
const
|
|
642
|
-
const promptCatalog = this.buildPromptCatalog(normalizedFlags, { defaultApiHost, defaultPortalTemplate });
|
|
582
|
+
const promptCatalog = this.buildPromptCatalog(normalizedFlags, { defaultApiHost });
|
|
643
583
|
if (useBrowserUi) {
|
|
644
584
|
presetValues = await runPromptCatalogWebUI({
|
|
645
585
|
stages: Init.buildWebUiStages(promptCatalog),
|
|
@@ -668,7 +608,7 @@ Prompt modes:
|
|
|
668
608
|
? { setupMode: normalizeInitSetupMode(presetValues.hasNocobase) }
|
|
669
609
|
: {}),
|
|
670
610
|
},
|
|
671
|
-
yesInitialValues:
|
|
611
|
+
yesInitialValues: {},
|
|
672
612
|
values: presetValues,
|
|
673
613
|
yes: normalizedFlags.yes || useBrowserUi || !interactive,
|
|
674
614
|
hooks: {
|
|
@@ -741,8 +681,7 @@ Prompt modes:
|
|
|
741
681
|
}
|
|
742
682
|
static async buildDynamicInitialValuesForInstall(flags, presetValues) {
|
|
743
683
|
const out = {};
|
|
744
|
-
const shouldResolveAppInitialValues = !Object.prototype.hasOwnProperty.call(presetValues, 'appPort')
|
|
745
|
-
!Object.prototype.hasOwnProperty.call(presetValues, 'portalTemplate');
|
|
684
|
+
const shouldResolveAppInitialValues = !Object.prototype.hasOwnProperty.call(presetValues, 'appPort');
|
|
746
685
|
if (shouldResolveAppInitialValues) {
|
|
747
686
|
const appInitialValues = await Install.buildAppPromptInitialValues({
|
|
748
687
|
envName: String(presetValues.appName ?? '').trim(),
|
|
@@ -751,20 +690,12 @@ Prompt modes:
|
|
|
751
690
|
'app-path': flags['app-path'] ?? '',
|
|
752
691
|
'app-root-path': flags['app-root-path'] ?? '',
|
|
753
692
|
'storage-path': flags['storage-path'] ?? '',
|
|
754
|
-
'portal-template': flags['portal-template'] ??
|
|
755
|
-
(Object.prototype.hasOwnProperty.call(presetValues, 'portalTemplate')
|
|
756
|
-
? String(presetValues.portalTemplate ?? '')
|
|
757
|
-
: undefined),
|
|
758
693
|
},
|
|
759
694
|
warnOnPortFallback: false,
|
|
760
695
|
});
|
|
761
696
|
if (appInitialValues.appPort !== undefined && !Object.prototype.hasOwnProperty.call(presetValues, 'appPort')) {
|
|
762
697
|
out.appPort = appInitialValues.appPort;
|
|
763
698
|
}
|
|
764
|
-
if (appInitialValues.portalTemplate !== undefined &&
|
|
765
|
-
!Object.prototype.hasOwnProperty.call(presetValues, 'portalTemplate')) {
|
|
766
|
-
out.portalTemplate = appInitialValues.portalTemplate;
|
|
767
|
-
}
|
|
768
699
|
}
|
|
769
700
|
const downloadSeed = { ...presetValues };
|
|
770
701
|
if (flags.yes && !Object.prototype.hasOwnProperty.call(downloadSeed, 'source')) {
|
|
@@ -843,14 +774,6 @@ Prompt modes:
|
|
|
843
774
|
buildDts: c.buildDts,
|
|
844
775
|
},
|
|
845
776
|
},
|
|
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
|
-
},
|
|
854
777
|
{
|
|
855
778
|
sectionTitle: initText('webUi.configureDatabase.title'),
|
|
856
779
|
sectionDescription: initText('webUi.configureDatabase.description'),
|
|
@@ -951,15 +874,6 @@ Prompt modes:
|
|
|
951
874
|
if (flags['app-public-path'] !== undefined && String(flags['app-public-path']).trim() !== '') {
|
|
952
875
|
preset.appPublicPath = String(flags['app-public-path']).trim();
|
|
953
876
|
}
|
|
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
|
-
}
|
|
963
877
|
if (flags['root-username'] !== undefined) {
|
|
964
878
|
preset.rootUsername = String(flags['root-username'] ?? '').trim();
|
|
965
879
|
}
|
|
@@ -1097,9 +1011,6 @@ Prompt modes:
|
|
|
1097
1011
|
const existingEnv = await getEnv(envName, { scope: resolveDefaultConfigScope() });
|
|
1098
1012
|
const appPort = String(results.appPort ?? '').trim();
|
|
1099
1013
|
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();
|
|
1103
1014
|
const source = String(results.source ?? '').trim();
|
|
1104
1015
|
const version = resolveInitDownloadVersion(results);
|
|
1105
1016
|
const dockerRegistry = String(results.dockerRegistry ?? '').trim();
|
|
@@ -1126,7 +1037,8 @@ Prompt modes:
|
|
|
1126
1037
|
const dbSchema = String(results.dbSchema ?? '').trim();
|
|
1127
1038
|
const dbTablePrefix = String(results.dbTablePrefix ?? '').trim();
|
|
1128
1039
|
const apiBaseUrl = String(results.apiBaseUrl ?? '').trim();
|
|
1129
|
-
const
|
|
1040
|
+
const authTypeInput = String(results.authType ?? '').trim();
|
|
1041
|
+
const authType = authTypeInput === 'basic' || authTypeInput === 'token' || authTypeInput === 'oauth' ? authTypeInput : 'oauth';
|
|
1130
1042
|
const authUsername = authType === 'basic' ? String(results.username ?? results.rootUsername ?? '').trim() : '';
|
|
1131
1043
|
const accessToken = String(results.accessToken ?? '');
|
|
1132
1044
|
const skipDownload = results.skipDownload === true;
|
|
@@ -1147,7 +1059,7 @@ Prompt modes:
|
|
|
1147
1059
|
: Boolean(results.builtinDb);
|
|
1148
1060
|
results.appKey = appKey;
|
|
1149
1061
|
results.timeZone = timeZone;
|
|
1150
|
-
|
|
1062
|
+
const savedEnvConfig = {
|
|
1151
1063
|
schemaVersion: ENV_CONFIG_SCHEMA_VERSION,
|
|
1152
1064
|
...(source === 'docker'
|
|
1153
1065
|
? { kind: 'docker' }
|
|
@@ -1172,9 +1084,6 @@ Prompt modes:
|
|
|
1172
1084
|
...(storagePath && !areConfiguredPathsEquivalent(storagePath, derivedStoragePath) ? { storagePath } : {}),
|
|
1173
1085
|
...(appPort ? { appPort } : {}),
|
|
1174
1086
|
...(appPublicPath ? { appPublicPath } : {}),
|
|
1175
|
-
...(portalType && portalType !== DEFAULT_INIT_PORTAL_TYPE ? { portalType } : {}),
|
|
1176
|
-
...(portalName ? { portalName } : {}),
|
|
1177
|
-
...(portalTemplate ? { portalTemplate } : {}),
|
|
1178
1087
|
...(appKey ? { appKey } : {}),
|
|
1179
1088
|
...(timeZone ? { timezone: timeZone } : {}),
|
|
1180
1089
|
...(!skipDownload && results.devDependencies !== undefined
|
|
@@ -1195,7 +1104,11 @@ Prompt modes:
|
|
|
1195
1104
|
...(results.dbUnderscored !== undefined ? { dbUnderscored: Boolean(results.dbUnderscored) } : {}),
|
|
1196
1105
|
setupState: 'prepared',
|
|
1197
1106
|
...(String(results.lang ?? '').trim() ? { lang: String(results.lang ?? '').trim() } : {}),
|
|
1198
|
-
}
|
|
1107
|
+
};
|
|
1108
|
+
await upsertEnv(envName, savedEnvConfig, { scope: resolveDefaultConfigScope() });
|
|
1109
|
+
if (source === 'docker' || appPath) {
|
|
1110
|
+
await ensureManagedEnvFileDefaults(envName, savedEnvConfig);
|
|
1111
|
+
}
|
|
1199
1112
|
}
|
|
1200
1113
|
buildEnvAddArgv(results) {
|
|
1201
1114
|
const argv = [String(results.appName ?? DEFAULT_INIT_APP_NAME)];
|
|
@@ -1299,18 +1212,6 @@ Prompt modes:
|
|
|
1299
1212
|
if (appPublicPath) {
|
|
1300
1213
|
argv.push('--app-public-path', appPublicPath);
|
|
1301
1214
|
}
|
|
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
|
-
}
|
|
1314
1215
|
if (flags.force) {
|
|
1315
1216
|
argv.push('--force');
|
|
1316
1217
|
}
|
|
@@ -1508,15 +1409,6 @@ Prompt modes:
|
|
|
1508
1409
|
delete normalized.rootPassword;
|
|
1509
1410
|
delete normalized.rootNickname;
|
|
1510
1411
|
}
|
|
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
|
-
}
|
|
1520
1412
|
delete normalized.installApiBaseUrl;
|
|
1521
1413
|
delete normalized.installAuthType;
|
|
1522
1414
|
delete normalized.installUsername;
|
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,8 +54,8 @@ 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';
|
|
56
|
-
const DEFAULT_INSTALL_PORTAL_TYPE = '
|
|
57
|
-
const DEFAULT_INSTALL_PORTAL_NAME = '
|
|
57
|
+
const DEFAULT_INSTALL_PORTAL_TYPE = 'ai';
|
|
58
|
+
const DEFAULT_INSTALL_PORTAL_NAME = 'main';
|
|
58
59
|
const DEFAULT_INSTALL_PORTAL_TEMPLATE = '@nocobase/portal-template-default';
|
|
59
60
|
const INSTALL_PORTAL_TYPES = ['no-code', 'ai'];
|
|
60
61
|
function toOptionalPromptString(value) {
|
|
@@ -2316,9 +2317,13 @@ export default class Install extends Command {
|
|
|
2316
2317
|
}
|
|
2317
2318
|
async saveInstalledEnv(params) {
|
|
2318
2319
|
const defaultApiHost = await resolveDefaultApiHost();
|
|
2319
|
-
|
|
2320
|
+
const savedEnvConfig = Install.buildSavedEnvConfig(params, { defaultApiHost });
|
|
2321
|
+
await upsertEnv(params.envName, savedEnvConfig, {
|
|
2320
2322
|
scope: resolveDefaultConfigScope(),
|
|
2321
2323
|
});
|
|
2324
|
+
if (params.ensureEnvFileDefaults !== false) {
|
|
2325
|
+
await ensureManagedEnvFileDefaults(params.envName, savedEnvConfig);
|
|
2326
|
+
}
|
|
2322
2327
|
await setCurrentEnv(params.envName, { scope: resolveDefaultConfigScope() });
|
|
2323
2328
|
}
|
|
2324
2329
|
async syncInstalledEnvConnection(params) {
|
|
@@ -2660,6 +2665,7 @@ export default class Install extends Command {
|
|
|
2660
2665
|
dbResults,
|
|
2661
2666
|
rootResults,
|
|
2662
2667
|
envAddResults,
|
|
2668
|
+
ensureEnvFileDefaults: false,
|
|
2663
2669
|
});
|
|
2664
2670
|
if (!parsed['skip-save-env-log']) {
|
|
2665
2671
|
printInfo(`Saved env config for "${envName}".`);
|
|
@@ -16,7 +16,7 @@ import { printInfo, printSuccess } from '../../lib/ui.js';
|
|
|
16
16
|
const DEFAULT_PORTAL_TEMPLATE = '@nocobase/portal-template-default';
|
|
17
17
|
const portalCreateText = (key, values, fallback) => translateCli(`commands.portalCreate.${key}`, values, { fallback });
|
|
18
18
|
export default class PortalCreate extends Command {
|
|
19
|
-
static summary = 'Create a local portal from a template';
|
|
19
|
+
static summary = 'Create a local AI portal from a template';
|
|
20
20
|
static examples = [
|
|
21
21
|
'<%= config.bin %> <%= command.id %> customer',
|
|
22
22
|
'<%= config.bin %> <%= command.id %> customer --template @nocobase/portal-template-default',
|
|
@@ -26,7 +26,7 @@ export default class PortalCreate extends Command {
|
|
|
26
26
|
static args = {
|
|
27
27
|
portal: Args.string({
|
|
28
28
|
required: true,
|
|
29
|
-
description: 'Portal name',
|
|
29
|
+
description: 'AI Portal name',
|
|
30
30
|
}),
|
|
31
31
|
};
|
|
32
32
|
static flags = {
|
|
@@ -76,7 +76,8 @@ export default class PortalCreate extends Command {
|
|
|
76
76
|
if (!confirmed) {
|
|
77
77
|
return;
|
|
78
78
|
}
|
|
79
|
-
const
|
|
79
|
+
const scope = resolveDefaultConfigScope();
|
|
80
|
+
const env = await getEnv(flags.env, { scope });
|
|
80
81
|
if (!env) {
|
|
81
82
|
this.error(flags.env
|
|
82
83
|
? portalCreateText('errors.envNotConfigured', { envName: flags.env }, `Env "${flags.env}" is not configured. Run \`nb env add ${flags.env} --api-base-url <url>\` first.`)
|
package/dist/lib/api-client.js
CHANGED
|
@@ -199,6 +199,13 @@ async function createMultipartBody(flags, operation) {
|
|
|
199
199
|
if (value === undefined) {
|
|
200
200
|
continue;
|
|
201
201
|
}
|
|
202
|
+
if (Array.isArray(value)) {
|
|
203
|
+
for (const item of value) {
|
|
204
|
+
formData.append(parameter.name, typeof item === 'object' ? JSON.stringify(item) : String(item));
|
|
205
|
+
}
|
|
206
|
+
hasValues = hasValues || value.length > 0;
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
202
209
|
formData.append(parameter.name, typeof value === 'object' ? JSON.stringify(value) : String(value));
|
|
203
210
|
hasValues = true;
|
|
204
211
|
}
|
package/dist/lib/env-auth.js
CHANGED
|
@@ -32,12 +32,12 @@ function buildDeviceVerificationPathFromApiBaseUrl(apiBaseUrl) {
|
|
|
32
32
|
const subappMatch = url.pathname.match(/^(.*)\/api\/__app\/([^/]+)\/?$/);
|
|
33
33
|
if (subappMatch) {
|
|
34
34
|
const publicPath = (subappMatch[1] || '').replace(/\/+$/, '');
|
|
35
|
-
return `${publicPath}/apps/${subappMatch[2]}/idpOAuth/device`;
|
|
35
|
+
return `${publicPath}/settings/apps/${subappMatch[2]}/idpOAuth/device`;
|
|
36
36
|
}
|
|
37
37
|
const appMatch = url.pathname.match(/^(.*)\/api\/?$/);
|
|
38
38
|
if (appMatch) {
|
|
39
39
|
const publicPath = (appMatch[1] || '').replace(/\/+$/, '');
|
|
40
|
-
return `${publicPath}/idpOAuth/device`;
|
|
40
|
+
return `${publicPath}/settings/idpOAuth/device`;
|
|
41
41
|
}
|
|
42
42
|
return undefined;
|
|
43
43
|
}
|
package/dist/lib/env-proxy.js
CHANGED
|
@@ -20,6 +20,9 @@ const DEFAULT_API_BASE_PATH = '/api/';
|
|
|
20
20
|
const DEFAULT_WS_PATH = '/ws';
|
|
21
21
|
const DEFAULT_PLUGIN_STATICS_PATH = '/static/plugins/';
|
|
22
22
|
const DEFAULT_MODERN_CLIENT_PREFIX = 'v';
|
|
23
|
+
const SETTINGS_CLIENT_PREFIX = 'settings';
|
|
24
|
+
const DEFAULT_APP_CLIENT_ENTRY_MODE = 'legacy-default';
|
|
25
|
+
const APP_CLIENT_ENTRY_MODES = new Set(['legacy-default', 'modern-default', 'modern-only']);
|
|
23
26
|
const DEFAULT_API_CLIENT_STORAGE_PREFIX = 'NOCOBASE_';
|
|
24
27
|
const DEFAULT_API_CLIENT_STORAGE_TYPE = 'localStorage';
|
|
25
28
|
const DEFAULT_ESM_CDN_BASE_URL = 'https://esm.sh';
|
|
@@ -67,7 +70,15 @@ function normalizeModernClientPrefix(value) {
|
|
|
67
70
|
const segment = String(value || '')
|
|
68
71
|
.trim()
|
|
69
72
|
.replace(/^\/+|\/+$/g, '');
|
|
70
|
-
|
|
73
|
+
const normalized = segment || DEFAULT_MODERN_CLIENT_PREFIX;
|
|
74
|
+
if (normalized === SETTINGS_CLIENT_PREFIX) {
|
|
75
|
+
throw new Error('APP_MODERN_CLIENT_PREFIX "settings" is reserved for the standalone Settings application.');
|
|
76
|
+
}
|
|
77
|
+
return normalized;
|
|
78
|
+
}
|
|
79
|
+
function normalizeAppClientEntryMode(value) {
|
|
80
|
+
const normalized = String(value || '').trim();
|
|
81
|
+
return APP_CLIENT_ENTRY_MODES.has(normalized) ? normalized : DEFAULT_APP_CLIENT_ENTRY_MODE;
|
|
71
82
|
}
|
|
72
83
|
function normalizeApiBasePath(value = DEFAULT_API_BASE_PATH) {
|
|
73
84
|
return resolveAppPublicPath(value);
|
|
@@ -275,6 +286,7 @@ export async function loadEnvProxySettings(runtime, options) {
|
|
|
275
286
|
wsPath: prefixRuntimePath(appPublicPath, envValues.WS_PATH || DEFAULT_WS_PATH),
|
|
276
287
|
pluginStaticsPath: prefixRuntimePath(appPublicPath, envValues.PLUGIN_STATICS_PATH || DEFAULT_PLUGIN_STATICS_PATH, { trailingSlash: true }),
|
|
277
288
|
modernClientPrefix: normalizeModernClientPrefix(envValues.APP_MODERN_CLIENT_PREFIX),
|
|
289
|
+
appClientEntryMode: normalizeAppClientEntryMode(envValues.APP_CLIENT_ENTRY_MODE),
|
|
278
290
|
cdnBaseUrl: trimValue(options?.cdnBaseUrl) ??
|
|
279
291
|
trimValue(runtime.env.envVars?.CDN_BASE_URL) ??
|
|
280
292
|
trimValue(envValues.CDN_BASE_URL),
|
|
@@ -299,7 +311,8 @@ function createManualProxyEnvSettings(input) {
|
|
|
299
311
|
pluginStaticsPath: prefixRuntimePath(appPublicPath, DEFAULT_PLUGIN_STATICS_PATH, {
|
|
300
312
|
trailingSlash: true,
|
|
301
313
|
}),
|
|
302
|
-
modernClientPrefix:
|
|
314
|
+
modernClientPrefix: normalizeModernClientPrefix(process.env.APP_MODERN_CLIENT_PREFIX),
|
|
315
|
+
appClientEntryMode: normalizeAppClientEntryMode(process.env.APP_CLIENT_ENTRY_MODE),
|
|
303
316
|
cdnBaseUrl: trimValue(input.cdnBaseUrl),
|
|
304
317
|
apiClientStoragePrefix: DEFAULT_API_CLIENT_STORAGE_PREFIX,
|
|
305
318
|
apiClientStorageType: DEFAULT_API_CLIENT_STORAGE_TYPE,
|
|
@@ -491,6 +504,9 @@ function buildNginxManagedConfigBlock(context) {
|
|
|
491
504
|
const apiBasePathNoTrailingSlash = trimTrailingSlash(context.apiBasePath);
|
|
492
505
|
const appPublicPathNoTrailingSlash = trimTrailingSlash(context.appPublicPath);
|
|
493
506
|
const fileAccessPath = `${context.appPublicPath}files/`;
|
|
507
|
+
const settingsAssetsPath = `${context.appPublicPath}settings/assets/`;
|
|
508
|
+
const settingsAssetsRoot = joinRuntimePath(context.distRootDir, `${context.activeVersion}/settings/assets`);
|
|
509
|
+
const settingsRoutePattern = `^${escapeRegExp(context.appPublicPath)}settings(?:/|$)`;
|
|
494
510
|
const isRootMounted = context.appPublicPath === '/';
|
|
495
511
|
const appPublicPathRedirectBlock = isRootMounted
|
|
496
512
|
? ''
|
|
@@ -563,6 +579,17 @@ function buildNginxManagedConfigBlock(context) {
|
|
|
563
579
|
` return 302 ${context.v2PublicPath}$is_args$args;`,
|
|
564
580
|
' }',
|
|
565
581
|
'',
|
|
582
|
+
` location ^~ ${settingsAssetsPath} {`,
|
|
583
|
+
` alias ${settingsAssetsRoot}/;`,
|
|
584
|
+
` include ${context.snippetsDir}/dist-location.conf;`,
|
|
585
|
+
' }',
|
|
586
|
+
'',
|
|
587
|
+
` location ~ ${settingsRoutePattern} {`,
|
|
588
|
+
` root ${context.publicDir};`,
|
|
589
|
+
` try_files $uri /index-settings.html =404;`,
|
|
590
|
+
` include ${context.snippetsDir}/spa-location.conf;`,
|
|
591
|
+
' }',
|
|
592
|
+
'',
|
|
566
593
|
` location ^~ ${context.v2PublicPath} {`,
|
|
567
594
|
` alias ${context.publicDir}/;`,
|
|
568
595
|
` try_files $uri /index-v2.html =404;`,
|
|
@@ -640,8 +667,9 @@ function buildNginxPortalLocationBlock(context) {
|
|
|
640
667
|
function buildNginxRuntimeConfig(context, variant) {
|
|
641
668
|
return {
|
|
642
669
|
__webpack_public_path__: context.cdnBaseUrl,
|
|
643
|
-
__nocobase_public_path__: variant === '
|
|
644
|
-
...(variant
|
|
670
|
+
__nocobase_public_path__: variant === 'v2' ? context.v2PublicPath : context.appPublicPath,
|
|
671
|
+
...(variant !== 'v1' ? { __nocobase_modern_client_prefix__: context.modernClientPrefix } : {}),
|
|
672
|
+
__nocobase_app_client_entry_mode__: context.appClientEntryMode,
|
|
645
673
|
__nocobase_api_base_url__: context.apiBasePath,
|
|
646
674
|
__nocobase_api_client_storage_prefix__: context.apiClientStoragePrefix,
|
|
647
675
|
__nocobase_api_client_storage_type__: context.apiClientStorageType,
|
|
@@ -692,7 +720,9 @@ async function buildEnvProxyNginxRenderContext(source, options) {
|
|
|
692
720
|
esmCdnSuffix: source.settings.esmCdnSuffix,
|
|
693
721
|
indexV1Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'v1', { scope: options?.scope }), options),
|
|
694
722
|
indexV2Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'v2', { scope: options?.scope }), options),
|
|
723
|
+
indexSettingsPath: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'settings', { scope: options?.scope }), options),
|
|
695
724
|
modernClientPrefix: source.settings.modernClientPrefix,
|
|
725
|
+
appClientEntryMode: source.settings.appClientEntryMode,
|
|
696
726
|
proxyHost,
|
|
697
727
|
snippetsDir: mappedSnippetsDir,
|
|
698
728
|
storageDir: mappedStorageDir,
|
|
@@ -811,16 +841,20 @@ async function buildNginxBundleFromSource(source, options) {
|
|
|
811
841
|
const mainTemplate = await readEnvProxyNginxAssetText('nocobase.conf.tpl');
|
|
812
842
|
const sourceIndexV1Path = path.join(source.distRootPath, context.activeVersion, 'index.html');
|
|
813
843
|
const sourceIndexV2Path = path.join(source.distRootPath, context.activeVersion, DEFAULT_MODERN_CLIENT_PREFIX, 'index.html');
|
|
814
|
-
const
|
|
844
|
+
const sourceIndexSettingsPath = path.join(source.distRootPath, context.activeVersion, 'settings', 'index.html');
|
|
845
|
+
const [sourceIndexV1Content, sourceIndexV2Content, sourceIndexSettingsContent] = await Promise.all([
|
|
815
846
|
readFile(sourceIndexV1Path, 'utf8'),
|
|
816
847
|
readFile(sourceIndexV2Path, 'utf8'),
|
|
848
|
+
readFile(sourceIndexSettingsPath, 'utf8'),
|
|
817
849
|
]);
|
|
818
850
|
const v1RuntimeScript = buildRuntimeConfigScriptTag(buildNginxRuntimeConfig(context, 'v1'));
|
|
819
851
|
const v2RuntimeScript = buildRuntimeConfigScriptTag(buildNginxRuntimeConfig(context, 'v2'));
|
|
852
|
+
const settingsRuntimeScript = buildRuntimeConfigScriptTag(buildNginxRuntimeConfig(context, 'settings'));
|
|
820
853
|
const sourceV1PublicPath = extractRuntimePublicPath(sourceIndexV1Content);
|
|
821
854
|
const sourceV2PublicPath = extractRuntimePublicPath(sourceIndexV2Content);
|
|
822
855
|
const indexV1AssetPublicPath = context.cdnBaseUrl;
|
|
823
856
|
const indexV2AssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/${DEFAULT_MODERN_CLIENT_PREFIX}/`;
|
|
857
|
+
const indexSettingsAssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/settings/`;
|
|
824
858
|
const appConfigIncludePath = await mapProxyPathFromCliRoot(path.join(resolveEnvProxyProviderRootDir('nginx', { scope: options?.scope }), '*', resolveEnvProxyFileSpec('nginx').appFilename), options);
|
|
825
859
|
const managedConfigBlock = buildNginxManagedConfigBlock(context);
|
|
826
860
|
const templateValues = {
|
|
@@ -844,6 +878,7 @@ async function buildNginxBundleFromSource(source, options) {
|
|
|
844
878
|
appConfigPath: resolveEnvProxyAppOutputPath(source.envName, { scope: options?.scope, provider: 'nginx' }),
|
|
845
879
|
indexV1Path: resolveEnvProxyNginxIndexOutputPath(source.envName, 'v1', { scope: options?.scope }),
|
|
846
880
|
indexV2Path: resolveEnvProxyNginxIndexOutputPath(source.envName, 'v2', { scope: options?.scope }),
|
|
881
|
+
indexSettingsPath: resolveEnvProxyNginxIndexOutputPath(source.envName, 'settings', { scope: options?.scope }),
|
|
847
882
|
mainConfigPath: resolveEnvProxyMainOutputPath({ scope: options?.scope, provider: 'nginx' }),
|
|
848
883
|
snippetsDir: resolveEnvProxyNginxSnippetsOutputDir({ scope: options?.scope }),
|
|
849
884
|
appPublicPath: context.appPublicPath,
|
|
@@ -861,6 +896,7 @@ async function buildNginxBundleFromSource(source, options) {
|
|
|
861
896
|
}),
|
|
862
897
|
indexV1Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV1Content, sourceV1PublicPath, indexV1AssetPublicPath), v1RuntimeScript),
|
|
863
898
|
indexV2Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV2Content, sourceV2PublicPath, indexV2AssetPublicPath), v2RuntimeScript),
|
|
899
|
+
indexSettingsContent: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexSettingsContent, '/settings/', indexSettingsAssetPublicPath), settingsRuntimeScript),
|
|
864
900
|
};
|
|
865
901
|
}
|
|
866
902
|
export async function buildEnvProxyCaddyBundle(runtime, options) {
|
|
@@ -877,21 +913,26 @@ async function buildCaddyBundleFromSource(source, options) {
|
|
|
877
913
|
const context = await buildEnvProxyCaddyRenderContextFromSource(source, options);
|
|
878
914
|
const sourceIndexV1Path = path.join(source.distRootPath, context.activeVersion, 'index.html');
|
|
879
915
|
const sourceIndexV2Path = path.join(source.distRootPath, context.activeVersion, DEFAULT_MODERN_CLIENT_PREFIX, 'index.html');
|
|
880
|
-
const
|
|
916
|
+
const sourceIndexSettingsPath = path.join(source.distRootPath, context.activeVersion, 'settings', 'index.html');
|
|
917
|
+
const [sourceIndexV1Content, sourceIndexV2Content, sourceIndexSettingsContent] = await Promise.all([
|
|
881
918
|
readFile(sourceIndexV1Path, 'utf8'),
|
|
882
919
|
readFile(sourceIndexV2Path, 'utf8'),
|
|
920
|
+
readFile(sourceIndexSettingsPath, 'utf8'),
|
|
883
921
|
]);
|
|
884
922
|
const v1RuntimeScript = buildRuntimeConfigScriptTag(buildCaddyRuntimeConfig(context, 'v1'));
|
|
885
923
|
const v2RuntimeScript = buildRuntimeConfigScriptTag(buildCaddyRuntimeConfig(context, 'v2'));
|
|
924
|
+
const settingsRuntimeScript = buildRuntimeConfigScriptTag(buildCaddyRuntimeConfig(context, 'settings'));
|
|
886
925
|
const sourceV1PublicPath = extractRuntimePublicPath(sourceIndexV1Content);
|
|
887
926
|
const sourceV2PublicPath = extractRuntimePublicPath(sourceIndexV2Content);
|
|
888
927
|
const indexV1AssetPublicPath = context.cdnBaseUrl;
|
|
889
928
|
const indexV2AssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/${DEFAULT_MODERN_CLIENT_PREFIX}/`;
|
|
929
|
+
const indexSettingsAssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/settings/`;
|
|
890
930
|
const appConfigPath = resolveEnvProxyAppOutputPath(source.envName, { scope: options?.scope, provider: 'caddy' });
|
|
891
931
|
const entryDir = resolveEnvProxyEntryDir(source.envName, { scope: options?.scope, provider: 'caddy' });
|
|
892
932
|
const publicDir = resolveEnvProxyCaddyPublicOutputDir(source.envName, { scope: options?.scope });
|
|
893
933
|
const renderedPublicDir = await mapProxyPathFromCliRoot(publicDir, { ...options, provider: 'caddy' });
|
|
894
934
|
const appConfigContent = renderCaddyAppTemplate(buildCaddySiteAddress(), {
|
|
935
|
+
activeVersion: context.activeVersion,
|
|
895
936
|
appPublicPath: context.appPublicPath,
|
|
896
937
|
apiBasePath: context.apiBasePath,
|
|
897
938
|
apiPort: context.apiPort,
|
|
@@ -912,6 +953,7 @@ async function buildCaddyBundleFromSource(source, options) {
|
|
|
912
953
|
appConfigPath,
|
|
913
954
|
indexV1Path: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'v1', { scope: options?.scope }),
|
|
914
955
|
indexV2Path: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'v2', { scope: options?.scope }),
|
|
956
|
+
indexSettingsPath: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'settings', { scope: options?.scope }),
|
|
915
957
|
mainConfigPath: resolveEnvProxyMainOutputPath({ scope: options?.scope, provider: 'caddy' }),
|
|
916
958
|
appPublicPath: context.appPublicPath,
|
|
917
959
|
apiBasePath: context.apiBasePath,
|
|
@@ -925,6 +967,7 @@ async function buildCaddyBundleFromSource(source, options) {
|
|
|
925
967
|
mainConfigContent: await buildEnvProxyMainConfig({ provider: 'caddy', scope: options?.scope }),
|
|
926
968
|
indexV1Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV1Content, sourceV1PublicPath, indexV1AssetPublicPath), v1RuntimeScript),
|
|
927
969
|
indexV2Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV2Content, sourceV2PublicPath, indexV2AssetPublicPath), v2RuntimeScript),
|
|
970
|
+
indexSettingsContent: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexSettingsContent, '/settings/', indexSettingsAssetPublicPath), settingsRuntimeScript),
|
|
928
971
|
};
|
|
929
972
|
}
|
|
930
973
|
async function pathExists(candidate) {
|
|
@@ -1179,6 +1222,9 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
|
1179
1222
|
const uploadsPath = `${context.appPublicPath}storage/uploads/`;
|
|
1180
1223
|
const fileAccessPathMatcher = toCaddyPathMatcher(`${context.appPublicPath}files/`);
|
|
1181
1224
|
const distPathMatcher = toCaddyPathMatcher(context.distPath);
|
|
1225
|
+
const settingsAssetsPathMatcher = toCaddyPathMatcher(`${context.appPublicPath}settings/assets/`);
|
|
1226
|
+
const settingsAssetsRoot = joinRuntimePath(context.distClientRoot, `${context.activeVersion}/settings/assets`);
|
|
1227
|
+
const settingsRoutePattern = `^${escapeRegExp(context.appPublicPath)}settings(?:/.*)?$`;
|
|
1182
1228
|
const uploadsPathMatcher = toCaddyPathMatcher(uploadsPath);
|
|
1183
1229
|
const apiPathMatcher = toCaddyPathMatcher(context.apiBasePath);
|
|
1184
1230
|
const appPublicPathNoTrailingSlash = trimTrailingSlash(context.appPublicPath);
|
|
@@ -1236,6 +1282,12 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
|
1236
1282
|
' file_server',
|
|
1237
1283
|
' }',
|
|
1238
1284
|
'',
|
|
1285
|
+
` handle_path ${settingsAssetsPathMatcher} {`,
|
|
1286
|
+
` root * ${settingsAssetsRoot}`,
|
|
1287
|
+
' header Cache-Control "public, max-age=31536000, immutable"',
|
|
1288
|
+
' file_server',
|
|
1289
|
+
' }',
|
|
1290
|
+
'',
|
|
1239
1291
|
' @oauth path_regexp oauth ^/\\.well-known/oauth-authorization-server/(.+)$',
|
|
1240
1292
|
' handle @oauth {',
|
|
1241
1293
|
' rewrite * /{re.oauth.1}/.well-known/oauth-authorization-server',
|
|
@@ -1269,6 +1321,15 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
|
1269
1321
|
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1270
1322
|
' }',
|
|
1271
1323
|
'',
|
|
1324
|
+
` @settingsRoute path_regexp settingsRoute ${settingsRoutePattern}`,
|
|
1325
|
+
' handle @settingsRoute {',
|
|
1326
|
+
` root * ${publicDir}`,
|
|
1327
|
+
' header Cache-Control "no-store, no-cache, must-revalidate"',
|
|
1328
|
+
' header X-Robots-Tag "noindex, nofollow"',
|
|
1329
|
+
' try_files {path} /index-settings.html',
|
|
1330
|
+
' file_server',
|
|
1331
|
+
' }',
|
|
1332
|
+
'',
|
|
1272
1333
|
' # Keep the v2 SPA route above the fallback SPA route.',
|
|
1273
1334
|
` handle_path ${toCaddyPathMatcher(context.v2PublicPath)} {`,
|
|
1274
1335
|
` root * ${publicDir}`,
|
|
@@ -1341,6 +1402,7 @@ async function buildEnvProxyRenderState(runtime, options) {
|
|
|
1341
1402
|
: await mapProxyPathFromCliRoot(distClientRoot, options);
|
|
1342
1403
|
const provider = resolveProxyProviderName(options?.provider);
|
|
1343
1404
|
const templateContext = {
|
|
1405
|
+
activeVersion: runtimeVersion,
|
|
1344
1406
|
appPublicPath: settings.appPublicPath,
|
|
1345
1407
|
apiBasePath: settings.apiBasePath,
|
|
1346
1408
|
apiPort,
|
|
@@ -6,11 +6,17 @@
|
|
|
6
6
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
|
-
import { readFile } from 'node:fs/promises';
|
|
9
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
10
10
|
import path from 'node:path';
|
|
11
|
+
import { resolveEnvKind } from './auth-store.js';
|
|
11
12
|
import { resolveConfiguredEnvPath } from './cli-home.js';
|
|
12
|
-
import { resolveDockerEnvFileArg } from "./docker-env-file.js";
|
|
13
|
+
import { resolveDockerEnvFileArg, resolveDockerEnvFilePath } from "./docker-env-file.js";
|
|
13
14
|
import { resolveConfiguredAppPath } from './env-paths.js';
|
|
15
|
+
export const DEFAULT_MANAGED_ENV_FILE_VALUES = {
|
|
16
|
+
APP_DISCOVERY_ADAPTER: 'local',
|
|
17
|
+
APP_PROCESS_ADAPTER: 'local',
|
|
18
|
+
APP_CLIENT_ENTRY_MODE: 'modern-only',
|
|
19
|
+
};
|
|
14
20
|
function trimValue(value) {
|
|
15
21
|
const text = String(value ?? '').trim();
|
|
16
22
|
return text || undefined;
|
|
@@ -68,6 +74,56 @@ export function resolveManagedLocalEnvFilePath(runtime) {
|
|
|
68
74
|
}
|
|
69
75
|
return normalizeEnvFilePath(path.join(runtime.projectRoot, '.env'));
|
|
70
76
|
}
|
|
77
|
+
export function resolveManagedEnvFilePathFromConfig(envName, config) {
|
|
78
|
+
const kind = config?.kind ?? resolveEnvKind(config);
|
|
79
|
+
if (kind === 'docker') {
|
|
80
|
+
const filePath = resolveDockerEnvFilePath(envName, config);
|
|
81
|
+
return filePath ? normalizeEnvFilePath(filePath) : undefined;
|
|
82
|
+
}
|
|
83
|
+
if (kind !== 'local') {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
const explicitEnvFile = trimValue(config?.envFile);
|
|
87
|
+
if (explicitEnvFile) {
|
|
88
|
+
return normalizeEnvFilePath(resolveConfiguredEnvPath(explicitEnvFile) ?? explicitEnvFile);
|
|
89
|
+
}
|
|
90
|
+
const configuredAppPath = resolveConfiguredAppPath(config);
|
|
91
|
+
if (configuredAppPath) {
|
|
92
|
+
return normalizeEnvFilePath(path.join(configuredAppPath, '.env'));
|
|
93
|
+
}
|
|
94
|
+
const configuredAppRootPath = trimValue(config?.appRootPath);
|
|
95
|
+
if (configuredAppRootPath) {
|
|
96
|
+
const appRootPath = resolveConfiguredEnvPath(configuredAppRootPath) ?? configuredAppRootPath;
|
|
97
|
+
return normalizeEnvFilePath(path.basename(appRootPath) === 'source' ? path.resolve(appRootPath, '..', '.env') : path.join(appRootPath, '.env'));
|
|
98
|
+
}
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
export async function ensureManagedEnvFileDefaults(envName, config, defaults = DEFAULT_MANAGED_ENV_FILE_VALUES) {
|
|
102
|
+
const envFilePath = resolveManagedEnvFilePathFromConfig(envName, config);
|
|
103
|
+
if (!envFilePath) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
let content = '';
|
|
107
|
+
try {
|
|
108
|
+
content = await readFile(envFilePath, 'utf8');
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
const code = error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
|
|
112
|
+
if (code !== 'ENOENT') {
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const existing = parseSimpleEnvFile(content);
|
|
117
|
+
const missingEntries = Object.entries(defaults).filter(([key, value]) => trimValue(value) && !existing[key]);
|
|
118
|
+
if (missingEntries.length === 0) {
|
|
119
|
+
return envFilePath;
|
|
120
|
+
}
|
|
121
|
+
const separator = content && !content.endsWith('\n') ? '\n' : '';
|
|
122
|
+
const nextContent = `${content}${separator}${missingEntries.map(([key, value]) => `${key}=${value}`).join('\n')}\n`;
|
|
123
|
+
await mkdir(path.dirname(envFilePath), { recursive: true });
|
|
124
|
+
await writeFile(envFilePath, nextContent, 'utf8');
|
|
125
|
+
return envFilePath;
|
|
126
|
+
}
|
|
71
127
|
export async function resolveManagedRuntimeEnvFilePath(runtime) {
|
|
72
128
|
if (runtime.kind === 'local') {
|
|
73
129
|
return resolveManagedLocalEnvFilePath(runtime);
|
package/dist/lib/naming.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
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
|
+
*/
|
|
1
9
|
import path from 'node:path';
|
|
2
10
|
export function toKebabCase(value) {
|
|
3
11
|
return value
|
|
12
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
|
|
4
13
|
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
5
14
|
.replace(/[^a-zA-Z0-9]+/g, '-')
|
|
6
15
|
.replace(/-+/g, '-')
|
package/dist/lib/proxy-caddy.js
CHANGED
|
@@ -79,6 +79,7 @@ export async function writeCaddyProxyBundle(runtime, appEntryOptions, runtimeCon
|
|
|
79
79
|
writeFile(bundle.appConfigPath, nextAppConfigContent, 'utf8'),
|
|
80
80
|
writeFile(bundle.indexV1Path, bundle.indexV1Content, 'utf8'),
|
|
81
81
|
writeFile(bundle.indexV2Path, bundle.indexV2Content, 'utf8'),
|
|
82
|
+
writeFile(bundle.indexSettingsPath, bundle.indexSettingsContent, 'utf8'),
|
|
82
83
|
writeFile(bundle.mainConfigPath, bundle.mainConfigContent, 'utf8'),
|
|
83
84
|
]);
|
|
84
85
|
return {
|
|
@@ -100,6 +101,7 @@ export async function writeManualCaddyProxyBundle(input, appEntryOptions, runtim
|
|
|
100
101
|
writeFile(bundle.appConfigPath, nextAppConfigContent, 'utf8'),
|
|
101
102
|
writeFile(bundle.indexV1Path, bundle.indexV1Content, 'utf8'),
|
|
102
103
|
writeFile(bundle.indexV2Path, bundle.indexV2Content, 'utf8'),
|
|
104
|
+
writeFile(bundle.indexSettingsPath, bundle.indexSettingsContent, 'utf8'),
|
|
103
105
|
writeFile(bundle.mainConfigPath, bundle.mainConfigContent, 'utf8'),
|
|
104
106
|
]);
|
|
105
107
|
return {
|
package/dist/lib/proxy-nginx.js
CHANGED
|
@@ -107,6 +107,7 @@ async function writeResolvedNginxProxyBundle(bundle, appEntryOptions, options) {
|
|
|
107
107
|
writeFile(bundle.appConfigPath, nextAppConfigContent, 'utf8'),
|
|
108
108
|
writeFile(bundle.indexV1Path, bundle.indexV1Content, 'utf8'),
|
|
109
109
|
writeFile(bundle.indexV2Path, bundle.indexV2Content, 'utf8'),
|
|
110
|
+
writeFile(bundle.indexSettingsPath, bundle.indexSettingsContent, 'utf8'),
|
|
110
111
|
writeFile(bundle.mainConfigPath, bundle.mainConfigContent, 'utf8'),
|
|
111
112
|
syncEnvProxyNginxSnippets(),
|
|
112
113
|
]);
|
package/nocobase-ctl.config.json
CHANGED
|
@@ -202,6 +202,117 @@
|
|
|
202
202
|
}
|
|
203
203
|
}
|
|
204
204
|
},
|
|
205
|
+
"ai": {
|
|
206
|
+
"name": "ai",
|
|
207
|
+
"description": "Discover LLM providers and manage LLM services and AI employees.",
|
|
208
|
+
"include": true,
|
|
209
|
+
"resources": {
|
|
210
|
+
"includes": ["ai", "llmServices", "aiEmployees"],
|
|
211
|
+
"excludes": [],
|
|
212
|
+
"overrides": {
|
|
213
|
+
"ai": {
|
|
214
|
+
"name": "llm-providers",
|
|
215
|
+
"description": "Discover providers and models and test unsaved LLM settings.",
|
|
216
|
+
"topLevel": false,
|
|
217
|
+
"operations": {
|
|
218
|
+
"includes": [
|
|
219
|
+
"ai:listLLMProviders",
|
|
220
|
+
"ai:listProviderModels",
|
|
221
|
+
"ai:testFlight",
|
|
222
|
+
"ai:listModels",
|
|
223
|
+
"ai:listLLMServices"
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
"llmServices": {
|
|
228
|
+
"name": "llm-services",
|
|
229
|
+
"description": "Manage saved LLM service configurations.",
|
|
230
|
+
"topLevel": false,
|
|
231
|
+
"operations": {
|
|
232
|
+
"includes": [
|
|
233
|
+
"llmServices:list",
|
|
234
|
+
"llmServices:get",
|
|
235
|
+
"llmServices:create",
|
|
236
|
+
"llmServices:update",
|
|
237
|
+
"llmServices:destroy"
|
|
238
|
+
]
|
|
239
|
+
}
|
|
240
|
+
},
|
|
241
|
+
"aiEmployees": {
|
|
242
|
+
"name": "employees",
|
|
243
|
+
"description": "Manage AI employees.",
|
|
244
|
+
"topLevel": false,
|
|
245
|
+
"operations": {
|
|
246
|
+
"includes": [
|
|
247
|
+
"aiEmployees:list",
|
|
248
|
+
"aiEmployees:get",
|
|
249
|
+
"aiEmployees:create",
|
|
250
|
+
"aiEmployees:update",
|
|
251
|
+
"aiEmployees:destroy"
|
|
252
|
+
]
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
"kb": {
|
|
259
|
+
"name": "kb",
|
|
260
|
+
"description": "Manage vector databases, knowledge bases, documents, vectorization, and retrieval tests.",
|
|
261
|
+
"include": true,
|
|
262
|
+
"resources": {
|
|
263
|
+
"includes": ["aiVectorDatabases", "aiKnowledgeBase", "aiKnowledgeBaseDocs"],
|
|
264
|
+
"excludes": [],
|
|
265
|
+
"overrides": {
|
|
266
|
+
"aiVectorDatabases": {
|
|
267
|
+
"name": "vector-databases",
|
|
268
|
+
"description": "Manage vector database connections.",
|
|
269
|
+
"topLevel": false,
|
|
270
|
+
"operations": {
|
|
271
|
+
"includes": [
|
|
272
|
+
"aiVectorDatabases:listProviders",
|
|
273
|
+
"aiVectorDatabases:testConnection",
|
|
274
|
+
"aiVectorDatabases:list",
|
|
275
|
+
"aiVectorDatabases:get",
|
|
276
|
+
"aiVectorDatabases:create",
|
|
277
|
+
"aiVectorDatabases:update",
|
|
278
|
+
"aiVectorDatabases:destroy"
|
|
279
|
+
]
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
"aiKnowledgeBase": {
|
|
283
|
+
"name": "kb",
|
|
284
|
+
"segments": ["kb"],
|
|
285
|
+
"description": "Manage knowledge bases and retrieval tests.",
|
|
286
|
+
"topLevel": true,
|
|
287
|
+
"operations": {
|
|
288
|
+
"includes": [
|
|
289
|
+
"aiKnowledgeBase:list",
|
|
290
|
+
"aiKnowledgeBase:get",
|
|
291
|
+
"aiKnowledgeBase:create",
|
|
292
|
+
"aiKnowledgeBase:update",
|
|
293
|
+
"aiKnowledgeBase:destroy",
|
|
294
|
+
"aiKnowledgeBase:runHitTest",
|
|
295
|
+
"aiKnowledgeBase:listExternalVectorStoreProviders"
|
|
296
|
+
]
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
"aiKnowledgeBaseDocs": {
|
|
300
|
+
"name": "documents",
|
|
301
|
+
"description": "Manage knowledge base documents and vectorization.",
|
|
302
|
+
"topLevel": false,
|
|
303
|
+
"operations": {
|
|
304
|
+
"includes": [
|
|
305
|
+
"aiKnowledgeBaseDocs:list",
|
|
306
|
+
"aiKnowledgeBaseDocs:get",
|
|
307
|
+
"aiKnowledgeBaseDocs:upload",
|
|
308
|
+
"aiKnowledgeBaseDocs:vectorization",
|
|
309
|
+
"aiKnowledgeBaseDocs:destroy"
|
|
310
|
+
]
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
},
|
|
205
316
|
"flow-engine": {
|
|
206
317
|
"name": "flow-engine",
|
|
207
318
|
"description": "Manage flow surface composition, configuration, layout, and mutation APIs.",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/cli",
|
|
3
|
-
"version": "2.2.0-alpha.
|
|
3
|
+
"version": "2.2.0-alpha.12",
|
|
4
4
|
"description": "NocoBase Command Line Tool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/generated/command-registry.js",
|
|
@@ -147,5 +147,5 @@
|
|
|
147
147
|
"type": "git",
|
|
148
148
|
"url": "git+https://github.com/nocobase/nocobase.git"
|
|
149
149
|
},
|
|
150
|
-
"gitHead": "
|
|
150
|
+
"gitHead": "96ae69754a6398de63037ead3a4aad5e5d0ab918"
|
|
151
151
|
}
|