@nocobase/cli 3.0.0-alpha.6 → 3.0.0-alpha.8

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.
@@ -11,6 +11,7 @@ import { setCurrentEnv, upsertEnv } from '../../lib/auth-store.js';
11
11
  import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
12
12
  import { ENV_BOOLEAN_CONFIG_FLAG_MAP, ENV_STRING_CONFIG_FLAG_MAP } from '../../lib/env-command-config.js';
13
13
  import { buildStoredEnvConfig } from '../../lib/env-config.js';
14
+ import { PUBLIC_APP_CLIENT_ENTRY_MODES } from '../../lib/app-client-entry-mode.js';
14
15
  import { runPromptCatalog, } from '../../lib/prompt-catalog.js';
15
16
  import { applyCliLocale, CLI_LOCALE_FLAG_DESCRIPTION, CLI_LOCALE_FLAG_OPTIONS, localeText, } from '../../lib/cli-locale.js';
16
17
  import { validateApiBaseUrl } from '../../lib/prompt-validators.js';
@@ -172,6 +173,11 @@ export default class EnvAdd extends Command {
172
173
  hidden: true,
173
174
  description: 'Docker env file saved with this env',
174
175
  }),
176
+ 'app-client-entry-mode': Flags.string({
177
+ hidden: true,
178
+ description: 'UI entry mode saved with this env',
179
+ options: [...PUBLIC_APP_CLIENT_ENTRY_MODES],
180
+ }),
175
181
  'app-port': Flags.string({
176
182
  hidden: true,
177
183
  description: 'Application HTTP port saved with this env',
@@ -16,6 +16,8 @@ import { appendDiagnosticLogPath } from '../../lib/cli-entry-error.js';
16
16
  import { getActiveCommandLogFile } from '../../lib/command-log.js';
17
17
  import { ENV_BOOLEAN_CONFIG_FLAG_MAP, ENV_STRING_CONFIG_FLAG_MAP } from '../../lib/env-command-config.js';
18
18
  import { buildStoredEnvConfig } from '../../lib/env-config.js';
19
+ import { PUBLIC_APP_CLIENT_ENTRY_MODES } from '../../lib/app-client-entry-mode.js';
20
+ import { upsertManagedEnvFileValues } from '../../lib/managed-env-file.js';
19
21
  import { validateApiBaseUrl } from '../../lib/prompt-validators.js';
20
22
  import { failTask, printInfo, printVerbose, printWarningBlock, setVerboseMode, startTask, stopTask, succeedTask, } from '../../lib/ui.js';
21
23
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -35,6 +37,7 @@ const UPDATE_STRING_FLAGS = [
35
37
  'app-public-path',
36
38
  'cdn-base-url',
37
39
  'env-file',
40
+ 'app-client-entry-mode',
38
41
  'app-port',
39
42
  'app-key',
40
43
  'timezone',
@@ -68,6 +71,7 @@ const APP_RESTART_FIELDS = new Set([
68
71
  'storage-path',
69
72
  'app-public-path',
70
73
  'env-file',
74
+ 'app-client-entry-mode',
71
75
  'app-port',
72
76
  'app-key',
73
77
  'timezone',
@@ -255,6 +259,10 @@ export default class EnvUpdate extends Command {
255
259
  hidden: true,
256
260
  description: 'Saved Docker --env-file path for this env',
257
261
  }),
262
+ 'app-client-entry-mode': Flags.string({
263
+ description: 'Saved UI entry mode for this env',
264
+ options: [...PUBLIC_APP_CLIENT_ENTRY_MODES],
265
+ }),
258
266
  'app-port': Flags.string({
259
267
  description: 'Saved application HTTP port for this env',
260
268
  }),
@@ -423,6 +431,11 @@ export default class EnvUpdate extends Command {
423
431
  startTask(`Saving env config: ${envName}`);
424
432
  try {
425
433
  await replaceEnvConfig(envName, nextConfig, { scope: resolveDefaultConfigScope() });
434
+ if (providedFields.has('app-client-entry-mode') && nextConfig.appClientEntryMode) {
435
+ await upsertManagedEnvFileValues(envName, nextConfig, {
436
+ APP_CLIENT_ENTRY_MODE: nextConfig.appClientEntryMode,
437
+ });
438
+ }
426
439
  succeedTask(`Saved env config for "${envName}".`);
427
440
  }
428
441
  catch (error) {
@@ -13,6 +13,7 @@ import { existsSync } from 'node:fs';
13
13
  import path from 'node:path';
14
14
  import { stdin as stdinStream, stdout as stdoutStream } from 'node:process';
15
15
  import { getEnv, upsertEnv } from "../lib/auth-store.js";
16
+ import { defaultAppClientEntryModeForDownloadVersion, normalizePublicAppClientEntryMode, PUBLIC_APP_CLIENT_ENTRY_MODES, } from '../lib/app-client-entry-mode.js';
16
17
  import { runPromptCatalog, } from "../lib/prompt-catalog.js";
17
18
  import { applyCliLocale, localeText, translateCli } from "../lib/cli-locale.js";
18
19
  import { resolveConfiguredEnvPath, resolveDefaultConfigScope, resolveEnvRelativePath } from '../lib/cli-home.js';
@@ -47,6 +48,23 @@ const INIT_ENV_ADD_FLAG_NAMES = [
47
48
  'skip-auth',
48
49
  ];
49
50
  const initText = (key, values) => localeText(`commands.init.${key}`, values);
51
+ const PUBLIC_APP_CLIENT_ENTRY_MODE_OPTIONS = [
52
+ {
53
+ value: 'modern-only',
54
+ label: initText('prompts.appClientEntryMode.modernOnlyLabel'),
55
+ hint: initText('prompts.appClientEntryMode.modernOnlyHint'),
56
+ },
57
+ {
58
+ value: 'modern-default',
59
+ label: initText('prompts.appClientEntryMode.modernDefaultLabel'),
60
+ hint: initText('prompts.appClientEntryMode.modernDefaultHint'),
61
+ },
62
+ {
63
+ value: 'legacy-default',
64
+ label: initText('prompts.appClientEntryMode.legacyDefaultLabel'),
65
+ hint: initText('prompts.appClientEntryMode.legacyDefaultHint'),
66
+ },
67
+ ];
50
68
  function withExtraHidden(def, extraHidden) {
51
69
  if (def.type === 'run') {
52
70
  return def;
@@ -106,6 +124,11 @@ function resolveInitDownloadVersion(results) {
106
124
  }
107
125
  return preset;
108
126
  }
127
+ function resolveInitAppClientEntryMode(results, explicitValue) {
128
+ return (normalizePublicAppClientEntryMode(explicitValue) ??
129
+ normalizePublicAppClientEntryMode(results.appClientEntryMode) ??
130
+ defaultAppClientEntryModeForDownloadVersion(resolveInitDownloadVersion(results)));
131
+ }
109
132
  function initVersionPromptValue(version) {
110
133
  return version === 'latest' || version === 'beta' || version === 'alpha' ? version : 'other';
111
134
  }
@@ -366,6 +389,15 @@ Prompt modes:
366
389
  source: installLikeOnly(Download.prompts.source),
367
390
  version: installLikeOnly(Download.prompts.version),
368
391
  otherVersion: installLikeOnly(Download.prompts.otherVersion),
392
+ appClientEntryMode: installLikeOnly({
393
+ type: 'select',
394
+ message: initText('prompts.appClientEntryMode.message'),
395
+ options: [...PUBLIC_APP_CLIENT_ENTRY_MODE_OPTIONS],
396
+ variant: 'radio',
397
+ hidden: (values) => resolveInitDownloadVersion(values) === 'latest',
398
+ initialValue: (values) => defaultAppClientEntryModeForDownloadVersion(values.version),
399
+ required: true,
400
+ }),
369
401
  dockerRegistry: installLikeOnly(Download.prompts.dockerRegistry),
370
402
  dockerPlatform: installLikeOnly(Download.prompts.dockerPlatform),
371
403
  dockerSave: installLikeDownloadExecutionOnly(Download.prompts.dockerSave),
@@ -490,6 +522,10 @@ Prompt modes:
490
522
  description: 'Skip installing NocoBase AI coding skills during init',
491
523
  default: false,
492
524
  }),
525
+ 'app-client-entry-mode': Flags.string({
526
+ description: 'UI entry mode for this app env: modern-only, modern-default, or legacy-default',
527
+ options: [...PUBLIC_APP_CLIENT_ENTRY_MODES],
528
+ }),
493
529
  'ui-host': Flags.string({
494
530
  description: 'Browser-accessible host for the --ui setup page URL (default: 127.0.0.1)',
495
531
  }),
@@ -778,6 +814,7 @@ Prompt modes:
778
814
  source: c.source,
779
815
  version: c.version,
780
816
  otherVersion: c.otherVersion,
817
+ appClientEntryMode: c.appClientEntryMode,
781
818
  dockerRegistry: c.dockerRegistry,
782
819
  dockerPlatform: c.dockerPlatform,
783
820
  dockerSave: c.dockerSave,
@@ -890,6 +927,9 @@ Prompt modes:
890
927
  if (flags['app-public-path'] !== undefined && String(flags['app-public-path']).trim() !== '') {
891
928
  preset.appPublicPath = String(flags['app-public-path']).trim();
892
929
  }
930
+ if (flags['app-client-entry-mode'] !== undefined && String(flags['app-client-entry-mode']).trim() !== '') {
931
+ preset.appClientEntryMode = String(flags['app-client-entry-mode']).trim();
932
+ }
893
933
  if (flags['root-username'] !== undefined) {
894
934
  preset.rootUsername = String(flags['root-username'] ?? '').trim();
895
935
  }
@@ -1027,6 +1067,7 @@ Prompt modes:
1027
1067
  const existingEnv = await getEnv(envName, { scope: resolveDefaultConfigScope() });
1028
1068
  const appPort = String(results.appPort ?? '').trim();
1029
1069
  const appPublicPath = String(results.appPublicPath ?? '').trim();
1070
+ const appClientEntryMode = resolveInitAppClientEntryMode(results, flags['app-client-entry-mode']);
1030
1071
  const source = String(results.source ?? '').trim();
1031
1072
  const version = resolveInitDownloadVersion(results);
1032
1073
  const dockerRegistry = String(results.dockerRegistry ?? '').trim();
@@ -1100,6 +1141,7 @@ Prompt modes:
1100
1141
  ...(storagePath && !areConfiguredPathsEquivalent(storagePath, derivedStoragePath) ? { storagePath } : {}),
1101
1142
  ...(appPort ? { appPort } : {}),
1102
1143
  ...(appPublicPath ? { appPublicPath } : {}),
1144
+ ...(appClientEntryMode ? { appClientEntryMode } : {}),
1103
1145
  ...(appKey ? { appKey } : {}),
1104
1146
  ...(timeZone ? { timezone: timeZone } : {}),
1105
1147
  ...(!skipDownload && results.devDependencies !== undefined
@@ -1228,6 +1270,13 @@ Prompt modes:
1228
1270
  if (appPublicPath) {
1229
1271
  argv.push('--app-public-path', appPublicPath);
1230
1272
  }
1273
+ const appClientEntryMode = normalizePublicAppClientEntryMode(flags['app-client-entry-mode']) ||
1274
+ (results.setupMode === 'install-new' && results.hasNocobase === undefined
1275
+ ? resolveInitAppClientEntryMode(results)
1276
+ : undefined);
1277
+ if (appClientEntryMode) {
1278
+ argv.push('--app-client-entry-mode', appClientEntryMode);
1279
+ }
1231
1280
  if (flags.force) {
1232
1281
  argv.push('--force');
1233
1282
  }
@@ -25,6 +25,7 @@ import { commandOutput, commandSucceeds, ensureDockerDaemonRunning, run, runNoco
25
25
  import { printInfo, printStage, printVerbose, printWarning, setVerboseMode } from '../lib/ui.js';
26
26
  import { omitKeys, upperFirst } from "../lib/object-utils.js";
27
27
  import { clearEnvRootSetup, getEnv, setCurrentEnv, upsertEnv } from '../lib/auth-store.js';
28
+ import { defaultAppClientEntryModeForDownloadVersion, normalizePublicAppClientEntryMode, PUBLIC_APP_CLIENT_ENTRY_MODES, } from '../lib/app-client-entry-mode.js';
28
29
  import { buildStoredEnvConfig } from '../lib/env-config.js';
29
30
  import { resolveDockerEnvFileArg } from "../lib/docker-env-file.js";
30
31
  import { startDockerLogFollower } from '../lib/docker-log-stream.js';
@@ -54,10 +55,7 @@ const DEFAULT_INSTALL_ROOT_EMAIL = 'admin@nocobase.com';
54
55
  const DEFAULT_INSTALL_ROOT_PASSWORD = 'admin123';
55
56
  const DEFAULT_INSTALL_ROOT_NICKNAME = 'Super Admin';
56
57
  const DEFAULT_INSTALL_API_HOST = '127.0.0.1';
57
- const DEFAULT_INSTALL_PORTAL_TYPE = 'ai';
58
- const DEFAULT_INSTALL_PORTAL_NAME = 'main';
59
58
  const DEFAULT_INSTALL_PORTAL_TEMPLATE = '@nocobase/portal-template-default';
60
- const INSTALL_PORTAL_TYPES = ['no-code', 'ai'];
61
59
  function toOptionalPromptString(value) {
62
60
  const text = String(value ?? '').trim();
63
61
  return text || undefined;
@@ -185,9 +183,6 @@ function defaultBuiltinDbImageForDialect(value, options) {
185
183
  function defaultDbDatabaseForDialect(value) {
186
184
  return String(value ?? '').trim() === 'kingbase' ? 'kingbase' : DEFAULT_INSTALL_DB_DATABASE;
187
185
  }
188
- function isAiMode(values) {
189
- return String(values.portalType ?? DEFAULT_INSTALL_PORTAL_TYPE).trim() === 'ai';
190
- }
191
186
  function supportsDbSchemaPrompt(value) {
192
187
  const dialect = String(value ?? '').trim();
193
188
  return dialect === 'postgres' || dialect === 'kingbase';
@@ -412,15 +407,12 @@ export default class Install extends Command {
412
407
  'app-public-path': Flags.string({
413
408
  description: 'Public path for the local app, for example / or /console/',
414
409
  }),
415
- 'portal-type': Flags.string({
416
- description: 'Initial portal type for the installed app',
417
- options: [...INSTALL_PORTAL_TYPES],
418
- }),
419
- 'portal-name': Flags.string({
420
- description: 'Initial portal name',
410
+ 'app-client-entry-mode': Flags.string({
411
+ description: 'UI entry mode for this app env: modern-only, modern-default, or legacy-default',
412
+ options: [...PUBLIC_APP_CLIENT_ENTRY_MODES],
421
413
  }),
422
414
  'portal-template': Flags.string({
423
- description: 'Initial portal template npm package or local path when --portal-type ai is used',
415
+ description: 'Template npm package or local path for the default AI Portal "main"',
424
416
  }),
425
417
  'root-username': Flags.string({
426
418
  description: 'Initial admin username for the installed app',
@@ -531,39 +523,12 @@ export default class Install extends Command {
531
523
  yesInitialValue: '/',
532
524
  validate: validateAppPublicPath,
533
525
  },
534
- portalType: {
535
- type: 'select',
536
- message: installText('prompts.portalType.message'),
537
- options: [
538
- {
539
- value: 'no-code',
540
- label: installText('prompts.portalType.noCodeLabel'),
541
- hint: installText('prompts.portalType.noCodeHint'),
542
- },
543
- {
544
- value: 'ai',
545
- label: installText('prompts.portalType.aiLabel'),
546
- hint: installText('prompts.portalType.aiHint'),
547
- },
548
- ],
549
- initialValue: DEFAULT_INSTALL_PORTAL_TYPE,
550
- yesInitialValue: DEFAULT_INSTALL_PORTAL_TYPE,
551
- required: true,
552
- },
553
- portalName: {
554
- type: 'text',
555
- message: installText('prompts.portalName.message'),
556
- placeholder: DEFAULT_INSTALL_PORTAL_NAME,
557
- initialValue: DEFAULT_INSTALL_PORTAL_NAME,
558
- yesInitialValue: DEFAULT_INSTALL_PORTAL_NAME,
559
- required: true,
560
- },
561
526
  portalTemplate: {
562
527
  type: 'text',
563
528
  message: installText('prompts.portalTemplate.message'),
564
529
  placeholder: DEFAULT_INSTALL_PORTAL_TEMPLATE,
530
+ initialValue: DEFAULT_INSTALL_PORTAL_TEMPLATE,
565
531
  yesInitialValue: DEFAULT_INSTALL_PORTAL_TEMPLATE,
566
- hidden: (values) => !isAiMode(values),
567
532
  required: true,
568
533
  },
569
534
  };
@@ -825,16 +790,10 @@ export default class Install extends Command {
825
790
  preset.appPublicPath = v;
826
791
  }
827
792
  }
828
- if (flags['portal-type'] !== undefined) {
829
- const v = String(flags['portal-type'] ?? '').trim();
830
- if (v) {
831
- preset.portalType = v;
832
- }
833
- }
834
- if (flags['portal-name'] !== undefined) {
835
- const v = String(flags['portal-name'] ?? '').trim();
793
+ if (flags['app-client-entry-mode'] !== undefined) {
794
+ const v = normalizePublicAppClientEntryMode(flags['app-client-entry-mode']);
836
795
  if (v) {
837
- preset.portalName = v;
796
+ preset.appClientEntryMode = v;
838
797
  }
839
798
  }
840
799
  if (flags['portal-template'] !== undefined) {
@@ -934,8 +893,7 @@ export default class Install extends Command {
934
893
  'appPort',
935
894
  'storagePath',
936
895
  'appPublicPath',
937
- 'portalType',
938
- 'portalName',
896
+ 'appClientEntryMode',
939
897
  'portalTemplate',
940
898
  ]);
941
899
  }
@@ -1186,8 +1144,6 @@ export default class Install extends Command {
1186
1144
  const rootPassword = Install.toOptionalPromptString(config.rootPassword);
1187
1145
  const rootNickname = Install.toOptionalPromptString(config.rootNickname);
1188
1146
  const lang = Install.toOptionalPromptString(config.lang);
1189
- const portalType = Install.toOptionalPromptString(config.portalType);
1190
- const portalName = Install.toOptionalPromptString(config.portalName);
1191
1147
  const portalTemplate = Install.toOptionalPromptString(config.portalTemplate);
1192
1148
  const auth = config.auth;
1193
1149
  const savedAuthType = Install.toOptionalPromptString(config.authType) ?? Install.toOptionalPromptString(auth?.type);
@@ -1198,8 +1154,6 @@ export default class Install extends Command {
1198
1154
  ...(appPort ? { appPort } : {}),
1199
1155
  ...(storagePath ? { storagePath } : {}),
1200
1156
  ...(appPublicPath ? { appPublicPath } : {}),
1201
- ...(portalType ? { portalType } : {}),
1202
- ...(portalName ? { portalName } : {}),
1203
1157
  ...(portalTemplate ? { portalTemplate } : {}),
1204
1158
  ...(hookScript ? { hookScript } : {}),
1205
1159
  };
@@ -1523,8 +1477,6 @@ export default class Install extends Command {
1523
1477
  rootEmail: String(params.rootResults.rootEmail ?? ''),
1524
1478
  rootPassword: String(params.rootResults.rootPassword ?? ''),
1525
1479
  rootNickname: String(params.rootResults.rootNickname ?? ''),
1526
- portalType: String(params.appResults.portalType ?? ''),
1527
- portalName: String(params.appResults.portalName ?? ''),
1528
1480
  portalTemplate: String(params.appResults.portalTemplate ?? ''),
1529
1481
  }, options);
1530
1482
  }
@@ -1874,6 +1826,7 @@ export default class Install extends Command {
1874
1826
  const extractClientAssets = resolveExtractClientAssetsDefaultEnabled(process.env.NOCOBASE_EXTRACT_CLIENT_ASSETS);
1875
1827
  const appKey = Install.resolveManagedAppKey(params.appResults.appKey);
1876
1828
  const appPublicPath = Install.toOptionalPromptString(params.appResults.appPublicPath);
1829
+ const appClientEntryMode = Install.toOptionalPromptString(params.appResults.appClientEntryMode);
1877
1830
  const timeZone = Install.resolveManagedTimeZone(params.appResults.timeZone);
1878
1831
  const containerName = Install.buildDockerAppContainerName(params.envName, params.dockerContainerPrefix ?? params.workspaceName);
1879
1832
  const configuredEnvFile = String(params.appResults.envFile ?? '').trim();
@@ -1901,6 +1854,7 @@ export default class Install extends Command {
1901
1854
  }
1902
1855
  args.push('-e', `APP_KEY=${appKey}`, '-e', `DB_DIALECT=${dbDialect}`, '-e', `DB_HOST=${dbHost}`, '-e', `DB_PORT=${dbPort}`, '-e', `DB_DATABASE=${dbDatabase}`, '-e', `DB_USER=${dbUser}`, '-e', `DB_PASSWORD=${dbPassword}`, '-e', `TZ=${timeZone}`, '-v', `${storagePath}:/app/nocobase/storage`);
1903
1856
  pushOptionalEnvArg(args, 'APP_PUBLIC_PATH', appPublicPath);
1857
+ pushOptionalEnvArg(args, 'APP_CLIENT_ENTRY_MODE', appClientEntryMode);
1904
1858
  pushOptionalEnvArg(args, 'DB_SCHEMA', dbSchema);
1905
1859
  pushOptionalEnvArg(args, 'DB_TABLE_PREFIX', dbTablePrefix);
1906
1860
  pushOptionalEnvArg(args, 'DB_UNDERSCORED', dbUnderscored);
@@ -2161,6 +2115,7 @@ export default class Install extends Command {
2161
2115
  }),
2162
2116
  };
2163
2117
  setOptionalEnvVar(env, 'APP_PUBLIC_PATH', Install.toOptionalPromptString(params.appResults.appPublicPath));
2118
+ setOptionalEnvVar(env, 'APP_CLIENT_ENTRY_MODE', Install.toOptionalPromptString(params.appResults.appClientEntryMode));
2164
2119
  setOptionalEnvVar(env, 'DB_SCHEMA', optionalEnvString(params.dbResults.dbSchema));
2165
2120
  setOptionalEnvVar(env, 'DB_TABLE_PREFIX', optionalEnvString(params.dbResults.dbTablePrefix));
2166
2121
  setOptionalEnvVar(env, 'DB_UNDERSCORED', optionalEnvBoolean(params.dbResults.dbUnderscored));
@@ -2402,8 +2357,7 @@ export default class Install extends Command {
2402
2357
  const appRootPath = Install.toOptionalPromptString(params.appResults.appRootPath);
2403
2358
  const storagePath = Install.toOptionalPromptString(params.appResults.storagePath);
2404
2359
  const appPublicPath = Install.toOptionalPromptString(params.appResults.appPublicPath);
2405
- const portalType = Install.toOptionalPromptString(params.appResults.portalType);
2406
- const portalName = Install.toOptionalPromptString(params.appResults.portalName);
2360
+ const appClientEntryMode = Install.toOptionalPromptString(params.appResults.appClientEntryMode);
2407
2361
  const portalTemplate = Install.toOptionalPromptString(params.appResults.portalTemplate);
2408
2362
  const derivedAppRootPath = appPath ? deriveConfiguredSourcePath(appPath) : undefined;
2409
2363
  const derivedStoragePath = appPath ? deriveConfiguredStoragePath(appPath) : undefined;
@@ -2437,10 +2391,9 @@ export default class Install extends Command {
2437
2391
  appPort,
2438
2392
  ...(storagePath && !areConfiguredPathsEquivalent(storagePath, derivedStoragePath) ? { storagePath } : {}),
2439
2393
  ...(appPublicPath ? { appPublicPath } : {}),
2394
+ ...(appClientEntryMode ? { appClientEntryMode } : {}),
2440
2395
  ...(envFile ? { envFile } : {}),
2441
2396
  lang: params.appResults.lang,
2442
- portalType,
2443
- portalName,
2444
2397
  portalTemplate,
2445
2398
  appKey: params.appResults.appKey,
2446
2399
  timezone: params.appResults.timeZone,
@@ -2512,6 +2465,9 @@ export default class Install extends Command {
2512
2465
  };
2513
2466
  downloadOpts.yes = yes;
2514
2467
  const downloadResults = await runPromptCatalog(Download.prompts, downloadOpts);
2468
+ appResults.appClientEntryMode =
2469
+ normalizePublicAppClientEntryMode(appResults.appClientEntryMode) ??
2470
+ defaultAppClientEntryModeForDownloadVersion(downloadResultsValue(downloadResults, 'version'));
2515
2471
  if (parsed['skip-download']) {
2516
2472
  delete downloadResults.outputDir;
2517
2473
  delete downloadResults.replace;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+ export const APP_CLIENT_ENTRY_MODES = [
10
+ 'legacy-default',
11
+ 'modern-default',
12
+ 'modern-only',
13
+ 'settings-default',
14
+ ];
15
+ export const PUBLIC_APP_CLIENT_ENTRY_MODES = ['legacy-default', 'modern-default', 'modern-only'];
16
+ export function normalizeAppClientEntryMode(value) {
17
+ const text = String(value ?? '').trim();
18
+ return APP_CLIENT_ENTRY_MODES.includes(text) ? text : undefined;
19
+ }
20
+ export function normalizePublicAppClientEntryMode(value) {
21
+ const text = String(value ?? '').trim();
22
+ return PUBLIC_APP_CLIENT_ENTRY_MODES.includes(text)
23
+ ? text
24
+ : undefined;
25
+ }
26
+ export function defaultAppClientEntryModeForDownloadVersion(version) {
27
+ return String(version ?? '').trim() === 'latest' ? 'legacy-default' : 'modern-only';
28
+ }
@@ -186,6 +186,7 @@ export async function buildSavedDockerRunArgs(runtime, options) {
186
186
  const dbTablePrefix = trimValue(config.dbTablePrefix);
187
187
  const dbUnderscored = typeof config.dbUnderscored === 'boolean' ? config.dbUnderscored : undefined;
188
188
  const extractClientAssets = resolveDockerClientAssetsExtractEnabled(process.env.NOCOBASE_EXTRACT_CLIENT_ASSETS);
189
+ const appClientEntryMode = trimValue(config.appClientEntryMode);
189
190
  const dockerRegistry = trimValue(config.dockerRegistry) || DEFAULT_DOCKER_REGISTRY;
190
191
  const version = trimValue(config.downloadVersion) || DEFAULT_DOCKER_VERSION;
191
192
  const imageRef = resolveDockerImageRef(dockerRegistry, version, {
@@ -245,6 +246,7 @@ export async function buildSavedDockerRunArgs(runtime, options) {
245
246
  const lifecycleEnvVars = managedAppLifecycleEnvVars();
246
247
  args.push('-e', `APP_ENV=${lifecycleEnvVars.APP_ENV}`, '-e', `NODE_ENV=${lifecycleEnvVars.NODE_ENV}`, '-e', `APP_KEY=${appKey}`, '-e', `DB_DIALECT=${dbDialect}`, '-e', `DB_HOST=${dbHost}`, '-e', `DB_PORT=${dbPort}`, '-e', `DB_DATABASE=${dbDatabase}`, '-e', `DB_USER=${dbUser}`, '-e', `DB_PASSWORD=${dbPassword}`, '-e', `TZ=${timeZone}`, '-v', `${storagePath}:${DOCKER_APP_STORAGE_DESTINATION}`);
247
248
  pushOptionalEnvArg(args, 'APP_PUBLIC_PATH', appPublicPath ? resolveAppPublicPath(appPublicPath) : undefined);
249
+ pushOptionalEnvArg(args, 'APP_CLIENT_ENTRY_MODE', appClientEntryMode);
248
250
  pushOptionalEnvArg(args, 'DB_SCHEMA', dbSchema || undefined);
249
251
  pushOptionalEnvArg(args, 'DB_TABLE_PREFIX', dbTablePrefix || undefined);
250
252
  pushOptionalEnvArg(args, 'DB_UNDERSCORED', dbUnderscored);
@@ -335,6 +335,7 @@ export class Env {
335
335
  put('APP_PORT', this.appPort);
336
336
  put('APP_PUBLIC_PATH', this.config.appPublicPath ? resolveAppPublicPath(this.config.appPublicPath) : undefined);
337
337
  put('CDN_BASE_URL', this.config.cdnBaseUrl);
338
+ put('APP_CLIENT_ENTRY_MODE', this.config.appClientEntryMode);
338
339
  put('APP_KEY', this.config.appKey);
339
340
  put('TZ', this.config.timezone);
340
341
  put('DB_DIALECT', this.config.dbDialect);
@@ -27,40 +27,6 @@ const DEFAULT_DEVICE_POLL_INTERVAL_SECONDS = 5;
27
27
  function normalizeBaseUrl(baseUrl) {
28
28
  return baseUrl.replace(/\/+$/, '');
29
29
  }
30
- function buildDeviceVerificationPathFromApiBaseUrl(apiBaseUrl) {
31
- const url = new URL(apiBaseUrl);
32
- const subappMatch = url.pathname.match(/^(.*)\/api\/__app\/([^/]+)\/?$/);
33
- if (subappMatch) {
34
- const publicPath = (subappMatch[1] || '').replace(/\/+$/, '');
35
- return `${publicPath}/settings/apps/${subappMatch[2]}/idpOAuth/device`;
36
- }
37
- const appMatch = url.pathname.match(/^(.*)\/api\/?$/);
38
- if (appMatch) {
39
- const publicPath = (appMatch[1] || '').replace(/\/+$/, '');
40
- return `${publicPath}/settings/idpOAuth/device`;
41
- }
42
- return undefined;
43
- }
44
- export function resolveDeviceVerificationUrlForApiBaseUrl(verificationUrl, apiBaseUrl) {
45
- try {
46
- const devicePath = buildDeviceVerificationPathFromApiBaseUrl(apiBaseUrl);
47
- if (!devicePath) {
48
- return verificationUrl;
49
- }
50
- const originalUrl = new URL(verificationUrl);
51
- if (!originalUrl.pathname.endsWith('/idpOAuth/device')) {
52
- return verificationUrl;
53
- }
54
- const publicUrl = new URL(apiBaseUrl);
55
- publicUrl.pathname = devicePath;
56
- publicUrl.search = originalUrl.search;
57
- publicUrl.hash = originalUrl.hash;
58
- return publicUrl.toString();
59
- }
60
- catch {
61
- return verificationUrl;
62
- }
63
- }
64
30
  export function getOauthMetadataUrl(baseUrl) {
65
31
  return `${normalizeBaseUrl(baseUrl)}/.well-known/oauth-authorization-server`;
66
32
  }
@@ -1051,7 +1017,7 @@ async function authenticateEnvWithOauthDevice(options) {
1051
1017
  baseUrl: options.baseUrl,
1052
1018
  });
1053
1019
  stopTask();
1054
- const verificationUrl = resolveDeviceVerificationUrlForApiBaseUrl(deviceAuthorization.verification_uri_complete || deviceAuthorization.verification_uri, options.baseUrl);
1020
+ const verificationUrl = deviceAuthorization.verification_uri_complete || deviceAuthorization.verification_uri;
1055
1021
  const browser = await browserOpener(verificationUrl);
1056
1022
  cleanupBrowserOpenTarget = browser.cleanup;
1057
1023
  if (!browser.opened) {
@@ -19,6 +19,7 @@ export const ENV_STRING_CONFIG_FLAG_MAP = {
19
19
  'app-public-path': 'appPublicPath',
20
20
  'cdn-base-url': 'cdnBaseUrl',
21
21
  'env-file': 'envFile',
22
+ 'app-client-entry-mode': 'appClientEntryMode',
22
23
  'app-port': 'appPort',
23
24
  'app-key': 'appKey',
24
25
  timezone: 'timezone',
@@ -6,6 +6,7 @@
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 { normalizeAppClientEntryMode } from './app-client-entry-mode.js';
9
10
  import { normalizeEnvProxyConfig } from './env-proxy-config.js';
10
11
  import { resolveAppPublicPath } from './app-public-path.js';
11
12
  import { normalizeEnvPortalsConfig } from './env-portal-config.js';
@@ -37,8 +38,6 @@ const STRING_ENV_CONFIG_KEYS = [
37
38
  'dbSchema',
38
39
  'dbTablePrefix',
39
40
  'lang',
40
- 'portalType',
41
- 'portalName',
42
41
  'portalTemplate',
43
42
  'rootUsername',
44
43
  'rootEmail',
@@ -87,6 +86,10 @@ export function buildStoredEnvConfig(input) {
87
86
  envConfig[key] = key === 'appPublicPath' ? resolveAppPublicPath(value) : value;
88
87
  }
89
88
  }
89
+ const appClientEntryMode = normalizeAppClientEntryMode(input.appClientEntryMode);
90
+ if (appClientEntryMode) {
91
+ envConfig.appClientEntryMode = appClientEntryMode;
92
+ }
90
93
  const setupState = resolveSetupState(input.setupState);
91
94
  if (setupState) {
92
95
  envConfig.setupState = setupState;
@@ -22,7 +22,7 @@ const DEFAULT_PLUGIN_STATICS_PATH = '/static/plugins/';
22
22
  const DEFAULT_MODERN_CLIENT_PREFIX = 'v';
23
23
  const SETTINGS_CLIENT_PREFIX = 'settings';
24
24
  const DEFAULT_APP_CLIENT_ENTRY_MODE = 'legacy-default';
25
- const APP_CLIENT_ENTRY_MODES = new Set(['legacy-default', 'modern-default', 'modern-only']);
25
+ const APP_CLIENT_ENTRY_MODES = new Set(['legacy-default', 'modern-default', 'modern-only', 'settings-default']);
26
26
  const DEFAULT_API_CLIENT_STORAGE_PREFIX = 'NOCOBASE_';
27
27
  const DEFAULT_API_CLIENT_STORAGE_TYPE = 'localStorage';
28
28
  const DEFAULT_ESM_CDN_BASE_URL = 'https://esm.sh';
@@ -8,6 +8,7 @@
8
8
  */
9
9
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
10
10
  import path from 'node:path';
11
+ import { normalizeAppClientEntryMode } from './app-client-entry-mode.js';
11
12
  import { resolveEnvKind } from './auth-store.js';
12
13
  import { resolveConfiguredEnvPath } from './cli-home.js';
13
14
  import { resolveDockerEnvFileArg, resolveDockerEnvFilePath } from "./docker-env-file.js";
@@ -17,6 +18,14 @@ export const DEFAULT_MANAGED_ENV_FILE_VALUES = {
17
18
  APP_PROCESS_ADAPTER: 'local',
18
19
  APP_CLIENT_ENTRY_MODE: 'modern-only',
19
20
  };
21
+ function buildManagedEnvFileDefaults(config, defaults = DEFAULT_MANAGED_ENV_FILE_VALUES) {
22
+ return {
23
+ ...defaults,
24
+ APP_CLIENT_ENTRY_MODE: normalizeAppClientEntryMode(config?.appClientEntryMode) ??
25
+ trimValue(defaults.APP_CLIENT_ENTRY_MODE) ??
26
+ DEFAULT_MANAGED_ENV_FILE_VALUES.APP_CLIENT_ENTRY_MODE,
27
+ };
28
+ }
20
29
  function trimValue(value) {
21
30
  const text = String(value ?? '').trim();
22
31
  return text || undefined;
@@ -36,6 +45,29 @@ function stripWrappingQuotes(value) {
36
45
  }
37
46
  return value;
38
47
  }
48
+ function escapeRegExp(value) {
49
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
50
+ }
51
+ function upsertSimpleEnvContent(content, values) {
52
+ let nextContent = content;
53
+ const missingEntries = new Map(Object.entries(values).filter(([, value]) => trimValue(value)));
54
+ for (const key of Array.from(missingEntries.keys())) {
55
+ const pattern = new RegExp(`^\\s*(?:export\\s+)?${escapeRegExp(key)}\\s*=.*$`, 'gm');
56
+ if (!pattern.test(nextContent)) {
57
+ continue;
58
+ }
59
+ nextContent = nextContent.replace(pattern, `${key}=${values[key]}`);
60
+ missingEntries.delete(key);
61
+ }
62
+ if (missingEntries.size === 0) {
63
+ return nextContent;
64
+ }
65
+ const separator = nextContent && !nextContent.endsWith('\n') ? '\n' : '';
66
+ const appended = Array.from(missingEntries.entries())
67
+ .map(([key, value]) => `${key}=${value}`)
68
+ .join('\n');
69
+ return `${nextContent}${separator}${appended}\n`;
70
+ }
39
71
  export function parseSimpleEnvFile(content) {
40
72
  const values = {};
41
73
  for (const rawLine of content.split(/\r?\n/)) {
@@ -112,7 +144,8 @@ export async function ensureManagedEnvFileDefaults(envName, config, defaults = D
112
144
  }
113
145
  }
114
146
  const existing = parseSimpleEnvFile(content);
115
- const missingEntries = Object.entries(defaults).filter(([key, value]) => trimValue(value) && !existing[key]);
147
+ const resolvedDefaults = buildManagedEnvFileDefaults(config, defaults);
148
+ const missingEntries = Object.entries(resolvedDefaults).filter(([key, value]) => trimValue(value) && !existing[key]);
116
149
  if (missingEntries.length === 0) {
117
150
  return envFilePath;
118
151
  }
@@ -122,6 +155,29 @@ export async function ensureManagedEnvFileDefaults(envName, config, defaults = D
122
155
  await writeFile(envFilePath, nextContent, 'utf8');
123
156
  return envFilePath;
124
157
  }
158
+ export async function upsertManagedEnvFileValues(envName, config, values) {
159
+ const envFilePath = resolveManagedEnvFilePathFromConfig(envName, config);
160
+ if (!envFilePath) {
161
+ return undefined;
162
+ }
163
+ let content = '';
164
+ try {
165
+ content = await readFile(envFilePath, 'utf8');
166
+ }
167
+ catch (error) {
168
+ const code = error && typeof error === 'object' && 'code' in error ? String(error.code) : '';
169
+ if (code !== 'ENOENT') {
170
+ throw error;
171
+ }
172
+ }
173
+ const nextContent = upsertSimpleEnvContent(content, values);
174
+ if (nextContent === content) {
175
+ return envFilePath;
176
+ }
177
+ await mkdir(path.dirname(envFilePath), { recursive: true });
178
+ await writeFile(envFilePath, nextContent, 'utf8');
179
+ return envFilePath;
180
+ }
125
181
  export async function resolveManagedRuntimeEnvFilePath(runtime) {
126
182
  if (runtime.kind === 'local') {
127
183
  return resolveManagedLocalEnvFilePath(runtime);
@@ -29,8 +29,6 @@ export function buildInitAppEnvVarsFromConfig(config, options = {}) {
29
29
  put('INIT_ROOT_PASSWORD', config?.rootPassword);
30
30
  put('INIT_ROOT_NICKNAME', config?.rootNickname);
31
31
  if (options.includePortal !== false) {
32
- put('INIT_PORTAL_TYPE', config?.portalType);
33
- put('INIT_PORTAL_NAME', config?.portalName);
34
32
  put('INIT_PORTAL_TEMPLATE', config?.portalTemplate);
35
33
  }
36
34
  return out;
@@ -24,6 +24,10 @@ import { listPortalWorkspaces } from './portal-list.js';
24
24
  import { findPortalListItem } from './portal-info.js';
25
25
  import { resolvePnpmInstallCommand, run, runPnpmInstallCommand } from './run-npm.js';
26
26
  const execFileAsync = promisify(execFile);
27
+ const NOCOBASE_CLI_GIT_IDENTITY = {
28
+ name: 'NocoBase CLI',
29
+ email: '314549027+nocobase-cli@users.noreply.github.com',
30
+ };
27
31
  const portalSourceText = (key, values, fallback) => translateCli(`commands.portalSource.${key}`, values, { fallback });
28
32
  const PULL_SOURCE_OPERATION = {
29
33
  method: 'POST',
@@ -215,6 +219,33 @@ async function runGit(args, cwd) {
215
219
  maxBuffer: 10 * 1024 * 1024,
216
220
  });
217
221
  }
222
+ function isValidGitIdentity(identity) {
223
+ return (!/[\r\n<>]/.test(identity.name) &&
224
+ !/[\r\n<>\s]/.test(identity.email) &&
225
+ identity.email.includes('@'));
226
+ }
227
+ async function resolveLocalGitIdentity(cwd) {
228
+ let output;
229
+ try {
230
+ output = (await runGit(['var', 'GIT_AUTHOR_IDENT'], cwd)).stdout;
231
+ }
232
+ catch {
233
+ return undefined;
234
+ }
235
+ const match = /^(.*) <([^<>]+)> -?\d+ [+-]\d{4}$/.exec(trimValue(output));
236
+ if (!match) {
237
+ return undefined;
238
+ }
239
+ const identity = { name: match[1], email: match[2] };
240
+ if (!isValidGitIdentity(identity)) {
241
+ return undefined;
242
+ }
243
+ if (identity.name === NOCOBASE_CLI_GIT_IDENTITY.name &&
244
+ identity.email === NOCOBASE_CLI_GIT_IDENTITY.email) {
245
+ return undefined;
246
+ }
247
+ return identity;
248
+ }
218
249
  async function installPortalDependencies(params) {
219
250
  if (params.installDependencies === false) {
220
251
  return {
@@ -488,6 +519,7 @@ async function pullGitPortalSource(params) {
488
519
  }
489
520
  async function pushGitPortalSource(params) {
490
521
  const git = assertGitSourceConfig(params.context);
522
+ const localIdentity = await resolveLocalGitIdentity(params.context.portalDir);
491
523
  const tempDir = await mkdtemp(path.join(os.tmpdir(), 'nocobase-cli-portal-git-push-'));
492
524
  try {
493
525
  const repoDir = await cloneGitSource({
@@ -506,15 +538,20 @@ async function pushGitPortalSource(params) {
506
538
  if (!status.stdout.trim()) {
507
539
  return undefined;
508
540
  }
509
- await runGit([
541
+ const commitIdentity = localIdentity ?? NOCOBASE_CLI_GIT_IDENTITY;
542
+ const commitArgs = [
510
543
  '-c',
511
- 'user.name=NocoBase CLI',
544
+ `user.name=${commitIdentity.name}`,
512
545
  '-c',
513
- 'user.email=nocobase-cli@localhost',
546
+ `user.email=${commitIdentity.email}`,
514
547
  'commit',
515
548
  '-m',
516
549
  trimValue(params.message) || `chore(portal): update ${params.context.portal}`,
517
- ], repoDir);
550
+ ];
551
+ if (localIdentity) {
552
+ commitArgs.push('-m', `Co-authored-by: ${NOCOBASE_CLI_GIT_IDENTITY.name} <${NOCOBASE_CLI_GIT_IDENTITY.email}>`);
553
+ }
554
+ await runGit(commitArgs, repoDir);
518
555
  await runGit(['push', 'origin', git.branch], repoDir);
519
556
  const revision = await runGit(['rev-parse', 'HEAD'], repoDir);
520
557
  return revision.stdout.trim();
@@ -60,7 +60,7 @@ export function mergedBoolean(key, def, iv, useYesInitial) {
60
60
  }
61
61
  return def.initialValue ?? true;
62
62
  }
63
- export function mergedSelect(key, def, iv, useYesInitial) {
63
+ export function mergedSelect(key, def, iv, useYesInitial, valuesSoFar = {}) {
64
64
  const enabledValueList = enabledSelectOptionValues(def.options);
65
65
  if (hasIvKey(iv, key)) {
66
66
  const s = String(iv[key]);
@@ -72,7 +72,7 @@ export function mergedSelect(key, def, iv, useYesInitial) {
72
72
  if (useYesInitial && def.yesInitialValue !== undefined && enabledValueList.includes(def.yesInitialValue)) {
73
73
  return def.yesInitialValue;
74
74
  }
75
- const d = def.initialValue;
75
+ const d = typeof def.initialValue === 'function' ? def.initialValue(valuesSoFar) : def.initialValue;
76
76
  if (d !== undefined && enabledValueList.includes(d)) {
77
77
  return d;
78
78
  }
@@ -221,11 +221,12 @@ export async function runPromptCatalog(catalog, options = {}) {
221
221
  if (def.type === 'select') {
222
222
  const message = resolvePromptText(def.message, locale, key);
223
223
  const valueList = selectOptionValues(def.options);
224
+ const valuesSoFar = { ...computationSeed, ...out };
224
225
  if (def.required && def.options.length === 0) {
225
226
  hooks.onMissingNonInteractive(t('promptCatalog.nonInteractive.selectRequiredNoOptions', { key }));
226
227
  }
227
228
  if (!interactive) {
228
- const merged = mergedSelect(key, def, resolveIv, useYesInitial);
229
+ const merged = mergedSelect(key, def, resolveIv, useYesInitial, valuesSoFar);
229
230
  if (merged === undefined || !valueList.includes(merged)) {
230
231
  const bad = hasIvKey(resolveIv, key) && !valueList.includes(String(resolveIv[key]))
231
232
  ? String(resolveIv[key])
@@ -243,10 +244,8 @@ export async function runPromptCatalog(catalog, options = {}) {
243
244
  }
244
245
  continue;
245
246
  }
246
- const merged = mergedSelect(key, def, promptIv, false);
247
- const uiInitial = merged ??
248
- (def.initialValue && valueList.includes(def.initialValue) ? def.initialValue : undefined) ??
249
- valueList[0];
247
+ const merged = mergedSelect(key, def, promptIv, false, valuesSoFar);
248
+ const uiInitial = merged ?? valueList[0];
250
249
  if (uiInitial === undefined || !valueList.includes(uiInitial)) {
251
250
  const hint = def.required
252
251
  ? t('promptCatalog.nonInteractive.selectRequiredInteractive', { key })
@@ -34,6 +34,13 @@ function resolveTextDefault(def, out) {
34
34
  }
35
35
  return String(iv ?? '');
36
36
  }
37
+ function resolveSelectDefault(def, out) {
38
+ const iv = def.initialValue;
39
+ if (typeof iv === 'function') {
40
+ return iv(out);
41
+ }
42
+ return iv;
43
+ }
37
44
  function resolvePasswordDefault(def, out) {
38
45
  const iv = def.initialValue;
39
46
  if (typeof iv === 'function') {
@@ -98,7 +105,7 @@ function defaultValueForInput(key, def, out) {
98
105
  const first = def.options
99
106
  .find((o) => typeof o === 'string' || o.disabled !== true);
100
107
  const firstValue = typeof first === 'string' ? first : first?.value;
101
- const i = def.initialValue;
108
+ const i = resolveSelectDefault(def, out);
102
109
  const enabledValues = def.options
103
110
  .filter((o) => typeof o === 'string' || o.disabled !== true)
104
111
  .map((o) => (typeof o === 'string' ? o : o.value));
@@ -468,7 +468,7 @@
468
468
  "betaLabel": "beta",
469
469
  "betaHint": "Preview release. Good for trying upcoming features before general release.",
470
470
  "alphaLabel": "alpha",
471
- "alphaHint": "Development release. Includes the newest changes, but may be incomplete or unstable.",
471
+ "alphaHint": "Choose this version to try the new features in 3.0.",
472
472
  "otherLabel": "Other",
473
473
  "otherHint": "Enter another package version, Docker tag, or Git ref manually, such as a branch name."
474
474
  },
@@ -657,6 +657,15 @@
657
657
  "skipDownload": {
658
658
  "message": "Skip downloading NocoBase and reuse existing local app files or Docker images"
659
659
  },
660
+ "appClientEntryMode": {
661
+ "message": "App client entry mode",
662
+ "modernOnlyLabel": "Modern UI only",
663
+ "modernOnlyHint": "Only the modern UI is available; the legacy UI cannot be accessed.",
664
+ "modernDefaultLabel": "Modern UI by default",
665
+ "modernDefaultHint": "Open the modern UI by default while keeping the legacy UI available.",
666
+ "legacyDefaultLabel": "Legacy UI by default",
667
+ "legacyDefaultHint": "Open the legacy UI by default while keeping the modern UI available."
668
+ },
660
669
  "portalType": {
661
670
  "message": "Portal type",
662
671
  "noCodeLabel": "No-code portal",
@@ -468,7 +468,7 @@
468
468
  "betaLabel": "beta",
469
469
  "betaHint": "测试版。包含即将发布的新功能,适合提前体验和反馈。",
470
470
  "alphaLabel": "alpha",
471
- "alphaHint": "开发版。功能更新最快,但可能不完整或不稳定。",
471
+ "alphaHint": "如果要体验 3.0 新功能,选择这个版本。",
472
472
  "otherLabel": "其他",
473
473
  "otherHint": "手动填写其他版本号、Docker tag 或 Git ref,例如分支名。"
474
474
  },
@@ -657,6 +657,15 @@
657
657
  "skipDownload": {
658
658
  "message": "跳过下载 NocoBase,直接复用已有的本地应用文件或 Docker 镜像"
659
659
  },
660
+ "appClientEntryMode": {
661
+ "message": "应用界面入口",
662
+ "modernOnlyLabel": "仅使用新版界面",
663
+ "modernOnlyHint": "只能进入新版界面,不可进入旧版界面。",
664
+ "modernDefaultLabel": "默认进入新版界面",
665
+ "modernDefaultHint": "默认进入新版界面,同时保留旧版界面入口。",
666
+ "legacyDefaultLabel": "默认进入旧版界面",
667
+ "legacyDefaultHint": "默认进入旧版界面,也可以进入新版界面。"
668
+ },
660
669
  "portalType": {
661
670
  "message": "Portal 类型",
662
671
  "noCodeLabel": "无代码 Portal",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/cli",
3
- "version": "3.0.0-alpha.6",
3
+ "version": "3.0.0-alpha.8",
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": "3508eabe683b9a47a28d0f3425098eb58873a9d7"
150
+ "gitHead": "ce017f3deb8b2414c6b13818042c4187270d1d8a"
151
151
  }