@nocobase/cli 2.2.0-beta.8 → 2.3.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/assets/env-proxy/nginx/app.conf.tpl +23 -0
  2. package/assets/env-proxy/nginx/nocobase.conf.tpl +5 -0
  3. package/assets/env-proxy/nginx/snippets/dist-location.conf +5 -0
  4. package/assets/env-proxy/nginx/snippets/gzip.conf +17 -0
  5. package/assets/env-proxy/nginx/snippets/log-format-http.conf +13 -0
  6. package/assets/env-proxy/nginx/snippets/maps-http.conf +14 -0
  7. package/assets/env-proxy/nginx/snippets/mime-types.conf +98 -0
  8. package/assets/env-proxy/nginx/snippets/proxy-location.conf +18 -0
  9. package/assets/env-proxy/nginx/snippets/spa-location.conf +6 -0
  10. package/assets/env-proxy/nginx/snippets/uploads-location.conf +21 -0
  11. package/dist/commands/app/start.js +4 -1
  12. package/dist/commands/env/info.js +11 -1
  13. package/dist/commands/init.js +11 -1
  14. package/dist/commands/install.js +61 -130
  15. package/dist/commands/proxy/caddy/generate.js +93 -7
  16. package/dist/commands/proxy/nginx/generate.js +98 -7
  17. package/dist/commands/revision/create.js +1 -1
  18. package/dist/commands/source/download.js +18 -14
  19. package/dist/lib/app-managed-resources.js +3 -2
  20. package/dist/lib/auth-store.js +68 -0
  21. package/dist/lib/cli-config.js +54 -1
  22. package/dist/lib/docker-image.js +94 -6
  23. package/dist/lib/env-config.js +5 -0
  24. package/dist/lib/env-proxy-config.js +48 -0
  25. package/dist/lib/env-proxy.js +193 -59
  26. package/dist/lib/prompt-catalog-terminal.js +32 -19
  27. package/dist/lib/prompt-web-ui.js +13 -2
  28. package/dist/lib/proxy-caddy.js +77 -9
  29. package/dist/lib/proxy-nginx.js +71 -11
  30. package/dist/locale/en-US.json +38 -38
  31. package/dist/locale/zh-CN.json +38 -38
  32. package/package.json +3 -2
@@ -9,14 +9,17 @@
9
9
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
10
10
  import path from 'node:path';
11
11
  import { dockerContainerExists, dockerContainerIsRunning, startDockerContainer, stopDockerContainer, } from './app-runtime.js';
12
+ import { loadAuthConfig } from './auth-store.js';
12
13
  import { CADDY_PROXY_DRIVER_OPTIONS, DEFAULT_CADDY_PROXY_DRIVER, getCliConfigValue, normalizeCaddyProxyDriver, resolveDockerContainerPrefix, setCliConfigValue, } from './cli-config.js';
13
14
  import { resolveCliHomeRoot } from './cli-home.js';
14
- import { applyEnvProxyAppEntryOptions, buildEnvProxyCaddyBundle, buildEnvProxyMainConfig, mapProxyPathFromCliRoot, resolveEnvProxyMainOutputPath, } from './env-proxy.js';
15
- import { run } from './run-npm.js';
15
+ import { applyEnvProxyAppEntryOptions, buildManualEnvProxyCaddyBundle, buildEnvProxyCaddyBundle, buildEnvProxyMainConfig, mapProxyPathFromCliRoot, resolveEnvProxyMainOutputPath, } from './env-proxy.js';
16
+ import { normalizeEnvProxyConfig } from './env-proxy-config.js';
17
+ import { commandOutput, run } from './run-npm.js';
16
18
  const DOCKER_CADDY_PROXY_CONTAINER_SUFFIX = 'caddy-proxy';
17
19
  const DOCKER_CADDY_PROXY_IMAGE = 'caddy:latest';
18
20
  const DOCKER_CADDY_PROXY_RUNTIME_ROOT = '/apps';
19
21
  const DOCKER_CADDY_PROXY_CONF_DESTINATION = '/etc/caddy/Caddyfile';
22
+ const DEFAULT_DOCKER_CADDY_PROXY_PUBLISHED_PORTS = [80, 443];
20
23
  async function readOptionalTextFile(filePath) {
21
24
  try {
22
25
  return await readFile(filePath, 'utf8');
@@ -62,8 +65,9 @@ export async function resolveCaddyProxyRuntimeContext(options) {
62
65
  upstreamHost: resolveCaddyProxyUpstreamHost(driver),
63
66
  };
64
67
  }
65
- export async function writeCaddyProxyBundle(runtime, appEntryOptions, runtimeContext) {
68
+ export async function writeCaddyProxyBundle(runtime, appEntryOptions, runtimeContext, options) {
66
69
  const bundle = await buildEnvProxyCaddyBundle(runtime, {
70
+ cdnBaseUrl: options?.cdnBaseUrl,
67
71
  runtimeCliRoot: runtimeContext.runtimeCliRoot,
68
72
  upstreamHost: runtimeContext.upstreamHost,
69
73
  });
@@ -82,6 +86,27 @@ export async function writeCaddyProxyBundle(runtime, appEntryOptions, runtimeCon
82
86
  status,
83
87
  };
84
88
  }
89
+ export async function writeManualCaddyProxyBundle(input, appEntryOptions, runtimeContext, options) {
90
+ const bundle = await buildManualEnvProxyCaddyBundle(input, {
91
+ cdnBaseUrl: options?.cdnBaseUrl,
92
+ runtimeCliRoot: runtimeContext.runtimeCliRoot,
93
+ upstreamHost: input.upstreamHost || runtimeContext.upstreamHost,
94
+ });
95
+ const currentAppConfigContent = await readOptionalTextFile(bundle.appConfigPath);
96
+ const nextAppConfigContent = applyEnvProxyAppEntryOptions(bundle.appConfigContent, 'caddy', appEntryOptions);
97
+ const status = currentAppConfigContent ? 'updated' : 'created';
98
+ await Promise.all([mkdir(bundle.entryDir, { recursive: true }), mkdir(bundle.publicDir, { recursive: true })]);
99
+ await Promise.all([
100
+ writeFile(bundle.appConfigPath, nextAppConfigContent, 'utf8'),
101
+ writeFile(bundle.indexV1Path, bundle.indexV1Content, 'utf8'),
102
+ writeFile(bundle.indexV2Path, bundle.indexV2Content, 'utf8'),
103
+ writeFile(bundle.mainConfigPath, bundle.mainConfigContent, 'utf8'),
104
+ ]);
105
+ return {
106
+ bundle,
107
+ status,
108
+ };
109
+ }
85
110
  function resolveLocalCaddyPidFilePath() {
86
111
  return path.join(path.dirname(resolveEnvProxyMainOutputPath({ provider: 'caddy' })), 'caddy.pid');
87
112
  }
@@ -149,11 +174,16 @@ async function reloadLocalCaddyProxy(runtimeContext) {
149
174
  }
150
175
  async function ensureDockerCaddyProxyContainer(runtimeContext) {
151
176
  const containerName = await resolveCaddyProxyContainerName();
177
+ const mainConfigPath = await ensureCaddyProxyMainConfig(runtimeContext);
178
+ const publishedPorts = await resolveDockerCaddyPublishedPorts();
152
179
  if (await dockerContainerExists(containerName)) {
153
- return;
180
+ if (await dockerCaddyProxyContainerMatchesPublishedPorts(containerName, publishedPorts)) {
181
+ return;
182
+ }
183
+ await removeDockerCaddyProxyContainer(containerName);
154
184
  }
155
185
  const hostCliRoot = String(process.env.NB_CLI_ROOT ?? resolveCliHomeRoot()).trim() || resolveCliHomeRoot();
156
- const mainConfigPath = await ensureCaddyProxyMainConfig(runtimeContext);
186
+ const dockerPortArgs = publishedPorts.flatMap((port) => ['-p', `${port}:${port}`]);
157
187
  await run('docker', [
158
188
  'run',
159
189
  '-d',
@@ -161,8 +191,7 @@ async function ensureDockerCaddyProxyContainer(runtimeContext) {
161
191
  containerName,
162
192
  '--add-host',
163
193
  'host.docker.internal:host-gateway',
164
- '-p',
165
- '80:80',
194
+ ...dockerPortArgs,
166
195
  '-v',
167
196
  `${hostCliRoot}:${DOCKER_CADDY_PROXY_RUNTIME_ROOT}`,
168
197
  '-v',
@@ -173,14 +202,53 @@ async function ensureDockerCaddyProxyContainer(runtimeContext) {
173
202
  stdio: 'ignore',
174
203
  });
175
204
  }
205
+ async function resolveDockerCaddyPublishedPorts() {
206
+ const config = await loadAuthConfig();
207
+ const ports = new Set(DEFAULT_DOCKER_CADDY_PROXY_PUBLISHED_PORTS);
208
+ for (const envConfig of Object.values(config.envs)) {
209
+ const port = normalizeEnvProxyConfig(envConfig.proxy)?.port;
210
+ if (port !== undefined) {
211
+ ports.add(port);
212
+ }
213
+ }
214
+ return Array.from(ports).sort((left, right) => left - right);
215
+ }
216
+ async function readDockerCaddyPublishedPorts(containerName) {
217
+ const output = await commandOutput('docker', ['inspect', '--format', '{{json .HostConfig.PortBindings}}', containerName], { errorName: 'docker inspect' });
218
+ const parsed = JSON.parse(output.trim() || '{}');
219
+ const ports = new Set();
220
+ for (const bindings of Object.values(parsed)) {
221
+ if (!Array.isArray(bindings)) {
222
+ continue;
223
+ }
224
+ for (const binding of bindings) {
225
+ const port = Number.parseInt(String(binding?.HostPort ?? '').trim(), 10);
226
+ if (Number.isInteger(port) && port > 0) {
227
+ ports.add(port);
228
+ }
229
+ }
230
+ }
231
+ return Array.from(ports).sort((left, right) => left - right);
232
+ }
233
+ async function dockerCaddyProxyContainerMatchesPublishedPorts(containerName, expectedPorts) {
234
+ const currentPorts = await readDockerCaddyPublishedPorts(containerName);
235
+ return currentPorts.length === expectedPorts.length && currentPorts.every((port, index) => port === expectedPorts[index]);
236
+ }
237
+ async function removeDockerCaddyProxyContainer(containerName) {
238
+ await run('docker', ['rm', '-f', containerName], {
239
+ errorName: 'docker rm',
240
+ stdio: 'ignore',
241
+ });
242
+ }
176
243
  async function startDockerCaddyProxy(runtimeContext) {
177
244
  const containerName = await resolveCaddyProxyContainerName();
178
245
  await ensureCaddyProxyMainConfig(runtimeContext);
179
- if (await dockerContainerExists(containerName)) {
246
+ const existedBeforeEnsure = await dockerContainerExists(containerName);
247
+ await ensureDockerCaddyProxyContainer(runtimeContext);
248
+ if (existedBeforeEnsure && (await dockerContainerExists(containerName))) {
180
249
  const state = await startDockerContainer(containerName, { stdio: 'ignore' });
181
250
  return state === 'already-running' ? 'already-running' : 'started';
182
251
  }
183
- await ensureDockerCaddyProxyContainer(runtimeContext);
184
252
  return 'started';
185
253
  }
186
254
  async function stopDockerCaddyProxy() {
@@ -9,14 +9,17 @@
9
9
  import { mkdir, readFile, writeFile } from 'node:fs/promises';
10
10
  import path from 'node:path';
11
11
  import { dockerContainerExists, dockerContainerIsRunning, startDockerContainer, stopDockerContainer, } from './app-runtime.js';
12
+ import { loadAuthConfig } from './auth-store.js';
12
13
  import { DEFAULT_NGINX_PROXY_DRIVER, getCliConfigValue, NGINX_PROXY_DRIVER_OPTIONS, normalizeNginxProxyDriver, resolveDockerContainerPrefix, setCliConfigValue, } from './cli-config.js';
13
14
  import { resolveCliHomeRoot } from './cli-home.js';
14
- import { applyEnvProxyAppEntryOptions, appConfigHasManagedNginxBlock, buildEnvProxyMainConfig, buildEnvProxyNginxBundle, extractManagedNginxConfigBlock, installEnvProxyProvider, mapProxyPathFromCliRoot, reloadEnvProxyProvider, resolveEnvProxyMainOutputPath, replaceManagedNginxConfigBlock, syncEnvProxyNginxSnippets, } from './env-proxy.js';
15
- import { run } from './run-npm.js';
15
+ import { applyEnvProxyAppEntryOptions, appConfigHasManagedNginxBlock, buildManualEnvProxyNginxBundle, buildEnvProxyMainConfig, buildEnvProxyNginxBundle, extractManagedNginxConfigBlock, installEnvProxyProvider, mapProxyPathFromCliRoot, reloadEnvProxyProvider, resolveEnvProxyMainOutputPath, replaceManagedNginxConfigBlock, syncEnvProxyNginxSnippets, } from './env-proxy.js';
16
+ import { normalizeEnvProxyConfig } from './env-proxy-config.js';
17
+ import { commandOutput, run } from './run-npm.js';
16
18
  const DOCKER_NGINX_PROXY_CONTAINER_SUFFIX = 'nginx-proxy';
17
19
  const DOCKER_NGINX_PROXY_IMAGE = 'nginx:latest';
18
20
  const DOCKER_NGINX_PROXY_RUNTIME_ROOT = '/apps';
19
21
  const DOCKER_NGINX_PROXY_CONF_DESTINATION = '/etc/nginx/conf.d/default.conf';
22
+ const DEFAULT_DOCKER_NGINX_PROXY_PUBLISHED_PORTS = [80, 443];
20
23
  async function readOptionalTextFile(filePath) {
21
24
  try {
22
25
  return await readFile(filePath, 'utf8');
@@ -66,11 +69,23 @@ function buildNginxManagedBlockMissingMessage(appConfigPath) {
66
69
  return (`The editable nginx app entry config at ${appConfigPath} does not contain the NocoBase managed block. ` +
67
70
  'Restore the managed block or delete the file and regenerate the proxy config.');
68
71
  }
69
- export async function writeNginxProxyBundle(runtime, appEntryOptions, runtimeContext) {
72
+ export async function writeNginxProxyBundle(runtime, appEntryOptions, runtimeContext, options) {
70
73
  const bundle = await buildEnvProxyNginxBundle(runtime, {
74
+ cdnBaseUrl: options?.cdnBaseUrl,
71
75
  runtimeCliRoot: runtimeContext.runtimeCliRoot,
72
76
  upstreamHost: runtimeContext.upstreamHost,
73
77
  });
78
+ return await writeResolvedNginxProxyBundle(bundle, appEntryOptions, options);
79
+ }
80
+ export async function writeManualNginxProxyBundle(input, appEntryOptions, runtimeContext, options) {
81
+ const bundle = await buildManualEnvProxyNginxBundle(input, {
82
+ cdnBaseUrl: options?.cdnBaseUrl,
83
+ runtimeCliRoot: runtimeContext.runtimeCliRoot,
84
+ upstreamHost: runtimeContext.upstreamHost,
85
+ });
86
+ return await writeResolvedNginxProxyBundle(bundle, appEntryOptions, options);
87
+ }
88
+ async function writeResolvedNginxProxyBundle(bundle, appEntryOptions, options) {
74
89
  const managedConfigBlock = extractManagedNginxConfigBlock(bundle.appConfigContent);
75
90
  if (!managedConfigBlock) {
76
91
  throw new Error('Failed to render the managed nginx config block.');
@@ -79,10 +94,12 @@ export async function writeNginxProxyBundle(runtime, appEntryOptions, runtimeCon
79
94
  let nextAppConfigContent = applyEnvProxyAppEntryOptions(bundle.appConfigContent, 'nginx', appEntryOptions);
80
95
  let status = 'created';
81
96
  if (currentAppConfigContent) {
82
- if (!appConfigHasManagedNginxBlock(currentAppConfigContent)) {
97
+ if (!appConfigHasManagedNginxBlock(currentAppConfigContent) && !options?.force) {
83
98
  throw new Error(buildNginxManagedBlockMissingMessage(bundle.appConfigPath));
84
99
  }
85
- nextAppConfigContent = applyEnvProxyAppEntryOptions(replaceManagedNginxConfigBlock(currentAppConfigContent, managedConfigBlock), 'nginx', appEntryOptions);
100
+ nextAppConfigContent = appConfigHasManagedNginxBlock(currentAppConfigContent)
101
+ ? applyEnvProxyAppEntryOptions(replaceManagedNginxConfigBlock(currentAppConfigContent, managedConfigBlock), 'nginx', appEntryOptions)
102
+ : applyEnvProxyAppEntryOptions(bundle.appConfigContent, 'nginx', appEntryOptions);
86
103
  status = 'updated';
87
104
  }
88
105
  await Promise.all([mkdir(bundle.entryDir, { recursive: true }), mkdir(bundle.publicDir, { recursive: true })]);
@@ -217,11 +234,16 @@ async function reloadLocalNginxProxy(runtimeContext) {
217
234
  }
218
235
  async function ensureDockerNginxProxyContainer(runtimeContext) {
219
236
  const containerName = await resolveNginxProxyContainerName();
237
+ const mainConfigPath = await ensureNginxProxyMainConfig(runtimeContext);
238
+ const publishedPorts = await resolveDockerNginxPublishedPorts();
220
239
  if (await dockerContainerExists(containerName)) {
221
- return;
240
+ if (await dockerNginxProxyContainerMatchesPublishedPorts(containerName, publishedPorts)) {
241
+ return;
242
+ }
243
+ await removeDockerNginxProxyContainer(containerName);
222
244
  }
223
245
  const hostCliRoot = String(process.env.NB_CLI_ROOT ?? resolveCliHomeRoot()).trim() || resolveCliHomeRoot();
224
- const mainConfigPath = await ensureNginxProxyMainConfig(runtimeContext);
246
+ const dockerPortArgs = publishedPorts.flatMap((port) => ['-p', `${port}:${port}`]);
225
247
  await run('docker', [
226
248
  'run',
227
249
  '-d',
@@ -229,8 +251,7 @@ async function ensureDockerNginxProxyContainer(runtimeContext) {
229
251
  containerName,
230
252
  '--add-host',
231
253
  'host.docker.internal:host-gateway',
232
- '-p',
233
- '80:80',
254
+ ...dockerPortArgs,
234
255
  '-v',
235
256
  `${hostCliRoot}:${DOCKER_NGINX_PROXY_RUNTIME_ROOT}`,
236
257
  '-v',
@@ -241,14 +262,53 @@ async function ensureDockerNginxProxyContainer(runtimeContext) {
241
262
  stdio: 'ignore',
242
263
  });
243
264
  }
265
+ async function resolveDockerNginxPublishedPorts() {
266
+ const config = await loadAuthConfig();
267
+ const ports = new Set(DEFAULT_DOCKER_NGINX_PROXY_PUBLISHED_PORTS);
268
+ for (const envConfig of Object.values(config.envs)) {
269
+ const port = normalizeEnvProxyConfig(envConfig.proxy)?.port;
270
+ if (port !== undefined) {
271
+ ports.add(port);
272
+ }
273
+ }
274
+ return Array.from(ports).sort((left, right) => left - right);
275
+ }
276
+ async function readDockerNginxPublishedPorts(containerName) {
277
+ const output = await commandOutput('docker', ['inspect', '--format', '{{json .HostConfig.PortBindings}}', containerName], { errorName: 'docker inspect' });
278
+ const parsed = JSON.parse(output.trim() || '{}');
279
+ const ports = new Set();
280
+ for (const bindings of Object.values(parsed)) {
281
+ if (!Array.isArray(bindings)) {
282
+ continue;
283
+ }
284
+ for (const binding of bindings) {
285
+ const port = Number.parseInt(String(binding?.HostPort ?? '').trim(), 10);
286
+ if (Number.isInteger(port) && port > 0) {
287
+ ports.add(port);
288
+ }
289
+ }
290
+ }
291
+ return Array.from(ports).sort((left, right) => left - right);
292
+ }
293
+ async function dockerNginxProxyContainerMatchesPublishedPorts(containerName, expectedPorts) {
294
+ const currentPorts = await readDockerNginxPublishedPorts(containerName);
295
+ return currentPorts.length === expectedPorts.length && currentPorts.every((port, index) => port === expectedPorts[index]);
296
+ }
297
+ async function removeDockerNginxProxyContainer(containerName) {
298
+ await run('docker', ['rm', '-f', containerName], {
299
+ errorName: 'docker rm',
300
+ stdio: 'ignore',
301
+ });
302
+ }
244
303
  async function startDockerNginxProxy(runtimeContext) {
245
304
  const containerName = await resolveNginxProxyContainerName();
246
305
  await ensureNginxProxyMainConfig(runtimeContext);
247
- if (await dockerContainerExists(containerName)) {
306
+ const existedBeforeEnsure = await dockerContainerExists(containerName);
307
+ await ensureDockerNginxProxyContainer(runtimeContext);
308
+ if (existedBeforeEnsure && (await dockerContainerExists(containerName))) {
248
309
  const state = await startDockerContainer(containerName, { stdio: 'ignore' });
249
310
  return state === 'already-running' ? 'already-running' : 'started';
250
311
  }
251
- await ensureDockerNginxProxyContainer(runtimeContext);
252
312
  return 'started';
253
313
  }
254
314
  async function stopDockerNginxProxy() {
@@ -93,20 +93,20 @@
93
93
  "envAdd": {
94
94
  "prompts": {
95
95
  "name": {
96
- "message": "What would you like to call this environment?",
96
+ "message": "App environment identifier",
97
97
  "placeholder": "default"
98
98
  },
99
99
  "scope": {
100
- "message": "Where should this connection be saved?",
100
+ "message": "Connection save location",
101
101
  "globalLabel": "Global",
102
102
  "globalHint": "user-level config"
103
103
  },
104
104
  "apiBaseUrl": {
105
- "message": "What is the API base URL?",
105
+ "message": "API base URL",
106
106
  "placeholder": "https://demo.example.com/api or https://demo.example.com/api/__app/<subapp>"
107
107
  },
108
108
  "authType": {
109
- "message": "Which authentication method would you like to use?",
109
+ "message": "Authentication method",
110
110
  "basicLabel": "Basic authentication (username + password)",
111
111
  "basicHint": "uses your credentials to fetch a token after save",
112
112
  "oauthLabel": "OAuth (browser authentication)",
@@ -114,14 +114,14 @@
114
114
  "tokenLabel": "API token / API key"
115
115
  },
116
116
  "username": {
117
- "message": "Enter the username for basic login",
117
+ "message": "Basic login username",
118
118
  "placeholder": "admin"
119
119
  },
120
120
  "password": {
121
- "message": "Enter the password for basic login"
121
+ "message": "Basic login password"
122
122
  },
123
123
  "accessToken": {
124
- "message": "Enter an API token or API key",
124
+ "message": "API token or API key",
125
125
  "placeholder": "Enter your API token / API key"
126
126
  }
127
127
  }
@@ -179,7 +179,7 @@
179
179
  },
180
180
  "prompts": {
181
181
  "provideMethod": {
182
- "message": "How do you want to provide the license key?",
182
+ "message": "License key input method",
183
183
  "keyOption": "Paste the license key",
184
184
  "fileOption": "Read the key from a file"
185
185
  },
@@ -252,7 +252,7 @@
252
252
  },
253
253
  "prompts": {
254
254
  "source": {
255
- "message": "How would you like to get NocoBase?",
255
+ "message": "NocoBase install source",
256
256
  "dockerLabel": "Docker install (Recommended)",
257
257
  "dockerHint": "Best for no-code and low-maintenance setups. You can upgrade later by pulling a newer image and restarting the app.",
258
258
  "npmLabel": "create-nocobase-app install",
@@ -261,9 +261,9 @@
261
261
  "gitHint": "Best when you want to try the latest unreleased changes, contribute code, or debug and modify NocoBase source directly. This option is more developer-oriented."
262
262
  },
263
263
  "version": {
264
- "message": "Which version would you like to use?",
264
+ "message": "NocoBase version",
265
265
  "latestLabel": "latest",
266
- "latestHint": "Stable release. Best for production use and the most predictable experience. Currently unavailable.",
266
+ "latestHint": "Stable release. Best for production use and the most predictable experience.",
267
267
  "betaLabel": "beta",
268
268
  "betaHint": "Preview release. Good for trying upcoming features before general release.",
269
269
  "alphaLabel": "alpha",
@@ -272,15 +272,15 @@
272
272
  "otherHint": "Enter another package version, Docker tag, or Git ref manually, such as a branch name."
273
273
  },
274
274
  "otherVersion": {
275
- "message": "Enter the version, Docker tag, or Git ref you want to use.",
275
+ "message": "Custom version, Docker tag, or Git ref",
276
276
  "placeholder": "For example: fix/cli-v2"
277
277
  },
278
278
  "dockerRegistry": {
279
- "message": "Which Docker registry would you like to use? The image tag is set separately in Version.",
279
+ "message": "Docker registry (image tag is set in Version)",
280
280
  "placeholder": "nocobase/nocobase"
281
281
  },
282
282
  "dockerPlatform": {
283
- "message": "Which Docker image platform should be used?",
283
+ "message": "Architecture",
284
284
  "autoLabel": "Auto",
285
285
  "autoHint": "Use Docker default for this machine"
286
286
  },
@@ -323,79 +323,79 @@
323
323
  },
324
324
  "prompts": {
325
325
  "env": {
326
- "message": "What would you like to call this app?",
326
+ "message": "App environment identifier",
327
327
  "placeholder": "local"
328
328
  },
329
329
  "lang": {
330
- "message": "Which language would you like to use for the app?"
330
+ "message": "App language"
331
331
  },
332
332
  "appPath": {
333
- "message": "Where should this app be stored? (relative to {{root}})",
333
+ "message": "App directory (relative to {{root}})",
334
334
  "placeholder": "./<env>/"
335
335
  },
336
336
  "appPort": {
337
- "message": "Which port should this app use?",
337
+ "message": "App port",
338
338
  "placeholder": "13000"
339
339
  },
340
340
  "appPublicPath": {
341
- "message": "What is the app subpath? (for example, /nocobase/)",
341
+ "message": "App subpath (for example, /nocobase/)",
342
342
  "placeholder": "/ or /nocobase/"
343
343
  },
344
344
  "storagePath": {
345
- "message": "Where should uploads and local files be stored?",
345
+ "message": "Uploads and local files directory",
346
346
  "placeholder": "./<env>/storage/"
347
347
  },
348
348
  "dbDialect": {
349
- "message": "Which database would you like to use?"
349
+ "message": "Database type"
350
350
  },
351
351
  "builtinDb": {
352
- "message": "Would you like to use the built-in database?"
352
+ "message": "Use built-in database"
353
353
  },
354
354
  "builtinDbImage": {
355
- "message": "Which Docker image should be used for the built-in database?",
355
+ "message": "Built-in database Docker image",
356
356
  "placeholder": "postgres:16"
357
357
  },
358
358
  "dbHost": {
359
- "message": "What is the database host?",
359
+ "message": "Database host",
360
360
  "placeholder": "127.0.0.1"
361
361
  },
362
362
  "dbPort": {
363
- "message": "What is the database port?",
363
+ "message": "Database port",
364
364
  "placeholder": "5432"
365
365
  },
366
366
  "dbDatabase": {
367
- "message": "What is the database name?"
367
+ "message": "Database name"
368
368
  },
369
369
  "dbUser": {
370
- "message": "What is the database username?"
370
+ "message": "Database username"
371
371
  },
372
372
  "dbPassword": {
373
- "message": "What is the database password?"
373
+ "message": "Database password"
374
374
  },
375
375
  "dbSchema": {
376
- "message": "What is the database schema? (PostgreSQL only, optional)",
376
+ "message": "Database schema (PostgreSQL/KingbaseES only, optional)",
377
377
  "placeholder": "Leave empty to use the default schema"
378
378
  },
379
379
  "dbTablePrefix": {
380
- "message": "What table prefix should be used? (optional)",
380
+ "message": "Database table prefix (optional)",
381
381
  "placeholder": "For example: nb_"
382
382
  },
383
383
  "dbUnderscored": {
384
- "message": "Use underscored names for database tables and columns?"
384
+ "message": "Use underscored names for database tables and columns"
385
385
  },
386
386
  "rootUsername": {
387
- "message": "Choose the initial admin username",
387
+ "message": "Initial admin username",
388
388
  "placeholder": "nocobase"
389
389
  },
390
390
  "rootEmail": {
391
- "message": "What is the initial admin email?",
391
+ "message": "Initial admin email",
392
392
  "placeholder": "admin@nocobase.com"
393
393
  },
394
394
  "rootPassword": {
395
- "message": "Choose the initial admin password"
395
+ "message": "Initial admin password"
396
396
  },
397
397
  "rootNickname": {
398
- "message": "What display name should the initial admin use?",
398
+ "message": "Initial admin display name",
399
399
  "placeholder": "Super Admin"
400
400
  }
401
401
  }
@@ -419,11 +419,11 @@
419
419
  },
420
420
  "prompts": {
421
421
  "appName": {
422
- "message": "What should this environment use for `--env`?",
422
+ "message": "Unique app environment identifier (used by the CLI to locate and manage this environment)",
423
423
  "placeholder": "local"
424
424
  },
425
425
  "setupMode": {
426
- "message": "How would you like to set up this environment?",
426
+ "message": "App environment setup method",
427
427
  "installNewLabel": "Install a new app",
428
428
  "installNewHint": "Install a brand-new app from scratch. Best for first-time setup, evaluation, or a fresh local environment.",
429
429
  "manageLocalLabel": "Manage an app already on this machine",
@@ -432,7 +432,7 @@
432
432
  "connectRemoteHint": "Save only the remote app connection. Nothing will be installed or taken over on this machine."
433
433
  },
434
434
  "installSkills": {
435
- "message": "Install NocoBase AI coding skills (nocobase/skills)?"
435
+ "message": "Install NocoBase AI coding skills (nocobase/skills)"
436
436
  },
437
437
  "apiBaseUrl": {
438
438
  "message": "API base URL",