@nocobase/cli 2.2.0-alpha.11 → 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 +58 -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,7 @@ 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';
|
|
23
24
|
const DEFAULT_APP_CLIENT_ENTRY_MODE = 'legacy-default';
|
|
24
25
|
const APP_CLIENT_ENTRY_MODES = new Set(['legacy-default', 'modern-default', 'modern-only']);
|
|
25
26
|
const DEFAULT_API_CLIENT_STORAGE_PREFIX = 'NOCOBASE_';
|
|
@@ -69,7 +70,11 @@ function normalizeModernClientPrefix(value) {
|
|
|
69
70
|
const segment = String(value || '')
|
|
70
71
|
.trim()
|
|
71
72
|
.replace(/^\/+|\/+$/g, '');
|
|
72
|
-
|
|
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;
|
|
73
78
|
}
|
|
74
79
|
function normalizeAppClientEntryMode(value) {
|
|
75
80
|
const normalized = String(value || '').trim();
|
|
@@ -306,7 +311,7 @@ function createManualProxyEnvSettings(input) {
|
|
|
306
311
|
pluginStaticsPath: prefixRuntimePath(appPublicPath, DEFAULT_PLUGIN_STATICS_PATH, {
|
|
307
312
|
trailingSlash: true,
|
|
308
313
|
}),
|
|
309
|
-
modernClientPrefix:
|
|
314
|
+
modernClientPrefix: normalizeModernClientPrefix(process.env.APP_MODERN_CLIENT_PREFIX),
|
|
310
315
|
appClientEntryMode: normalizeAppClientEntryMode(process.env.APP_CLIENT_ENTRY_MODE),
|
|
311
316
|
cdnBaseUrl: trimValue(input.cdnBaseUrl),
|
|
312
317
|
apiClientStoragePrefix: DEFAULT_API_CLIENT_STORAGE_PREFIX,
|
|
@@ -499,6 +504,9 @@ function buildNginxManagedConfigBlock(context) {
|
|
|
499
504
|
const apiBasePathNoTrailingSlash = trimTrailingSlash(context.apiBasePath);
|
|
500
505
|
const appPublicPathNoTrailingSlash = trimTrailingSlash(context.appPublicPath);
|
|
501
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(?:/|$)`;
|
|
502
510
|
const isRootMounted = context.appPublicPath === '/';
|
|
503
511
|
const appPublicPathRedirectBlock = isRootMounted
|
|
504
512
|
? ''
|
|
@@ -571,6 +579,17 @@ function buildNginxManagedConfigBlock(context) {
|
|
|
571
579
|
` return 302 ${context.v2PublicPath}$is_args$args;`,
|
|
572
580
|
' }',
|
|
573
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
|
+
'',
|
|
574
593
|
` location ^~ ${context.v2PublicPath} {`,
|
|
575
594
|
` alias ${context.publicDir}/;`,
|
|
576
595
|
` try_files $uri /index-v2.html =404;`,
|
|
@@ -648,8 +667,8 @@ function buildNginxPortalLocationBlock(context) {
|
|
|
648
667
|
function buildNginxRuntimeConfig(context, variant) {
|
|
649
668
|
return {
|
|
650
669
|
__webpack_public_path__: context.cdnBaseUrl,
|
|
651
|
-
__nocobase_public_path__: variant === '
|
|
652
|
-
...(variant
|
|
670
|
+
__nocobase_public_path__: variant === 'v2' ? context.v2PublicPath : context.appPublicPath,
|
|
671
|
+
...(variant !== 'v1' ? { __nocobase_modern_client_prefix__: context.modernClientPrefix } : {}),
|
|
653
672
|
__nocobase_app_client_entry_mode__: context.appClientEntryMode,
|
|
654
673
|
__nocobase_api_base_url__: context.apiBasePath,
|
|
655
674
|
__nocobase_api_client_storage_prefix__: context.apiClientStoragePrefix,
|
|
@@ -701,6 +720,7 @@ async function buildEnvProxyNginxRenderContext(source, options) {
|
|
|
701
720
|
esmCdnSuffix: source.settings.esmCdnSuffix,
|
|
702
721
|
indexV1Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'v1', { scope: options?.scope }), options),
|
|
703
722
|
indexV2Path: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'v2', { scope: options?.scope }), options),
|
|
723
|
+
indexSettingsPath: await mapProxyPathFromCliRoot(resolveEnvProxyNginxIndexOutputPath(source.envName, 'settings', { scope: options?.scope }), options),
|
|
704
724
|
modernClientPrefix: source.settings.modernClientPrefix,
|
|
705
725
|
appClientEntryMode: source.settings.appClientEntryMode,
|
|
706
726
|
proxyHost,
|
|
@@ -821,16 +841,20 @@ async function buildNginxBundleFromSource(source, options) {
|
|
|
821
841
|
const mainTemplate = await readEnvProxyNginxAssetText('nocobase.conf.tpl');
|
|
822
842
|
const sourceIndexV1Path = path.join(source.distRootPath, context.activeVersion, 'index.html');
|
|
823
843
|
const sourceIndexV2Path = path.join(source.distRootPath, context.activeVersion, DEFAULT_MODERN_CLIENT_PREFIX, 'index.html');
|
|
824
|
-
const
|
|
844
|
+
const sourceIndexSettingsPath = path.join(source.distRootPath, context.activeVersion, 'settings', 'index.html');
|
|
845
|
+
const [sourceIndexV1Content, sourceIndexV2Content, sourceIndexSettingsContent] = await Promise.all([
|
|
825
846
|
readFile(sourceIndexV1Path, 'utf8'),
|
|
826
847
|
readFile(sourceIndexV2Path, 'utf8'),
|
|
848
|
+
readFile(sourceIndexSettingsPath, 'utf8'),
|
|
827
849
|
]);
|
|
828
850
|
const v1RuntimeScript = buildRuntimeConfigScriptTag(buildNginxRuntimeConfig(context, 'v1'));
|
|
829
851
|
const v2RuntimeScript = buildRuntimeConfigScriptTag(buildNginxRuntimeConfig(context, 'v2'));
|
|
852
|
+
const settingsRuntimeScript = buildRuntimeConfigScriptTag(buildNginxRuntimeConfig(context, 'settings'));
|
|
830
853
|
const sourceV1PublicPath = extractRuntimePublicPath(sourceIndexV1Content);
|
|
831
854
|
const sourceV2PublicPath = extractRuntimePublicPath(sourceIndexV2Content);
|
|
832
855
|
const indexV1AssetPublicPath = context.cdnBaseUrl;
|
|
833
856
|
const indexV2AssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/${DEFAULT_MODERN_CLIENT_PREFIX}/`;
|
|
857
|
+
const indexSettingsAssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/settings/`;
|
|
834
858
|
const appConfigIncludePath = await mapProxyPathFromCliRoot(path.join(resolveEnvProxyProviderRootDir('nginx', { scope: options?.scope }), '*', resolveEnvProxyFileSpec('nginx').appFilename), options);
|
|
835
859
|
const managedConfigBlock = buildNginxManagedConfigBlock(context);
|
|
836
860
|
const templateValues = {
|
|
@@ -854,6 +878,7 @@ async function buildNginxBundleFromSource(source, options) {
|
|
|
854
878
|
appConfigPath: resolveEnvProxyAppOutputPath(source.envName, { scope: options?.scope, provider: 'nginx' }),
|
|
855
879
|
indexV1Path: resolveEnvProxyNginxIndexOutputPath(source.envName, 'v1', { scope: options?.scope }),
|
|
856
880
|
indexV2Path: resolveEnvProxyNginxIndexOutputPath(source.envName, 'v2', { scope: options?.scope }),
|
|
881
|
+
indexSettingsPath: resolveEnvProxyNginxIndexOutputPath(source.envName, 'settings', { scope: options?.scope }),
|
|
857
882
|
mainConfigPath: resolveEnvProxyMainOutputPath({ scope: options?.scope, provider: 'nginx' }),
|
|
858
883
|
snippetsDir: resolveEnvProxyNginxSnippetsOutputDir({ scope: options?.scope }),
|
|
859
884
|
appPublicPath: context.appPublicPath,
|
|
@@ -871,6 +896,7 @@ async function buildNginxBundleFromSource(source, options) {
|
|
|
871
896
|
}),
|
|
872
897
|
indexV1Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV1Content, sourceV1PublicPath, indexV1AssetPublicPath), v1RuntimeScript),
|
|
873
898
|
indexV2Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV2Content, sourceV2PublicPath, indexV2AssetPublicPath), v2RuntimeScript),
|
|
899
|
+
indexSettingsContent: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexSettingsContent, '/settings/', indexSettingsAssetPublicPath), settingsRuntimeScript),
|
|
874
900
|
};
|
|
875
901
|
}
|
|
876
902
|
export async function buildEnvProxyCaddyBundle(runtime, options) {
|
|
@@ -887,21 +913,26 @@ async function buildCaddyBundleFromSource(source, options) {
|
|
|
887
913
|
const context = await buildEnvProxyCaddyRenderContextFromSource(source, options);
|
|
888
914
|
const sourceIndexV1Path = path.join(source.distRootPath, context.activeVersion, 'index.html');
|
|
889
915
|
const sourceIndexV2Path = path.join(source.distRootPath, context.activeVersion, DEFAULT_MODERN_CLIENT_PREFIX, 'index.html');
|
|
890
|
-
const
|
|
916
|
+
const sourceIndexSettingsPath = path.join(source.distRootPath, context.activeVersion, 'settings', 'index.html');
|
|
917
|
+
const [sourceIndexV1Content, sourceIndexV2Content, sourceIndexSettingsContent] = await Promise.all([
|
|
891
918
|
readFile(sourceIndexV1Path, 'utf8'),
|
|
892
919
|
readFile(sourceIndexV2Path, 'utf8'),
|
|
920
|
+
readFile(sourceIndexSettingsPath, 'utf8'),
|
|
893
921
|
]);
|
|
894
922
|
const v1RuntimeScript = buildRuntimeConfigScriptTag(buildCaddyRuntimeConfig(context, 'v1'));
|
|
895
923
|
const v2RuntimeScript = buildRuntimeConfigScriptTag(buildCaddyRuntimeConfig(context, 'v2'));
|
|
924
|
+
const settingsRuntimeScript = buildRuntimeConfigScriptTag(buildCaddyRuntimeConfig(context, 'settings'));
|
|
896
925
|
const sourceV1PublicPath = extractRuntimePublicPath(sourceIndexV1Content);
|
|
897
926
|
const sourceV2PublicPath = extractRuntimePublicPath(sourceIndexV2Content);
|
|
898
927
|
const indexV1AssetPublicPath = context.cdnBaseUrl;
|
|
899
928
|
const indexV2AssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/${DEFAULT_MODERN_CLIENT_PREFIX}/`;
|
|
929
|
+
const indexSettingsAssetPublicPath = `${trimTrailingSlash(context.cdnBaseUrl)}/settings/`;
|
|
900
930
|
const appConfigPath = resolveEnvProxyAppOutputPath(source.envName, { scope: options?.scope, provider: 'caddy' });
|
|
901
931
|
const entryDir = resolveEnvProxyEntryDir(source.envName, { scope: options?.scope, provider: 'caddy' });
|
|
902
932
|
const publicDir = resolveEnvProxyCaddyPublicOutputDir(source.envName, { scope: options?.scope });
|
|
903
933
|
const renderedPublicDir = await mapProxyPathFromCliRoot(publicDir, { ...options, provider: 'caddy' });
|
|
904
934
|
const appConfigContent = renderCaddyAppTemplate(buildCaddySiteAddress(), {
|
|
935
|
+
activeVersion: context.activeVersion,
|
|
905
936
|
appPublicPath: context.appPublicPath,
|
|
906
937
|
apiBasePath: context.apiBasePath,
|
|
907
938
|
apiPort: context.apiPort,
|
|
@@ -922,6 +953,7 @@ async function buildCaddyBundleFromSource(source, options) {
|
|
|
922
953
|
appConfigPath,
|
|
923
954
|
indexV1Path: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'v1', { scope: options?.scope }),
|
|
924
955
|
indexV2Path: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'v2', { scope: options?.scope }),
|
|
956
|
+
indexSettingsPath: resolveEnvProxyCaddyIndexOutputPath(source.envName, 'settings', { scope: options?.scope }),
|
|
925
957
|
mainConfigPath: resolveEnvProxyMainOutputPath({ scope: options?.scope, provider: 'caddy' }),
|
|
926
958
|
appPublicPath: context.appPublicPath,
|
|
927
959
|
apiBasePath: context.apiBasePath,
|
|
@@ -935,6 +967,7 @@ async function buildCaddyBundleFromSource(source, options) {
|
|
|
935
967
|
mainConfigContent: await buildEnvProxyMainConfig({ provider: 'caddy', scope: options?.scope }),
|
|
936
968
|
indexV1Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV1Content, sourceV1PublicPath, indexV1AssetPublicPath), v1RuntimeScript),
|
|
937
969
|
indexV2Content: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexV2Content, sourceV2PublicPath, indexV2AssetPublicPath), v2RuntimeScript),
|
|
970
|
+
indexSettingsContent: injectRuntimeScriptIntoHtml(rewriteHtmlAssetPublicPath(sourceIndexSettingsContent, '/settings/', indexSettingsAssetPublicPath), settingsRuntimeScript),
|
|
938
971
|
};
|
|
939
972
|
}
|
|
940
973
|
async function pathExists(candidate) {
|
|
@@ -1189,6 +1222,9 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
|
1189
1222
|
const uploadsPath = `${context.appPublicPath}storage/uploads/`;
|
|
1190
1223
|
const fileAccessPathMatcher = toCaddyPathMatcher(`${context.appPublicPath}files/`);
|
|
1191
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(?:/.*)?$`;
|
|
1192
1228
|
const uploadsPathMatcher = toCaddyPathMatcher(uploadsPath);
|
|
1193
1229
|
const apiPathMatcher = toCaddyPathMatcher(context.apiBasePath);
|
|
1194
1230
|
const appPublicPathNoTrailingSlash = trimTrailingSlash(context.appPublicPath);
|
|
@@ -1246,6 +1282,12 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
|
1246
1282
|
' file_server',
|
|
1247
1283
|
' }',
|
|
1248
1284
|
'',
|
|
1285
|
+
` handle_path ${settingsAssetsPathMatcher} {`,
|
|
1286
|
+
` root * ${settingsAssetsRoot}`,
|
|
1287
|
+
' header Cache-Control "public, max-age=31536000, immutable"',
|
|
1288
|
+
' file_server',
|
|
1289
|
+
' }',
|
|
1290
|
+
'',
|
|
1249
1291
|
' @oauth path_regexp oauth ^/\\.well-known/oauth-authorization-server/(.+)$',
|
|
1250
1292
|
' handle @oauth {',
|
|
1251
1293
|
' rewrite * /{re.oauth.1}/.well-known/oauth-authorization-server',
|
|
@@ -1279,6 +1321,15 @@ function renderCaddyAppTemplate(siteAddress, context, publicDir) {
|
|
|
1279
1321
|
` reverse_proxy ${context.proxyHost}:${context.apiPort}`,
|
|
1280
1322
|
' }',
|
|
1281
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
|
+
'',
|
|
1282
1333
|
' # Keep the v2 SPA route above the fallback SPA route.',
|
|
1283
1334
|
` handle_path ${toCaddyPathMatcher(context.v2PublicPath)} {`,
|
|
1284
1335
|
` root * ${publicDir}`,
|
|
@@ -1351,6 +1402,7 @@ async function buildEnvProxyRenderState(runtime, options) {
|
|
|
1351
1402
|
: await mapProxyPathFromCliRoot(distClientRoot, options);
|
|
1352
1403
|
const provider = resolveProxyProviderName(options?.provider);
|
|
1353
1404
|
const templateContext = {
|
|
1405
|
+
activeVersion: runtimeVersion,
|
|
1354
1406
|
appPublicPath: settings.appPublicPath,
|
|
1355
1407
|
apiBasePath: settings.apiBasePath,
|
|
1356
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
|
}
|