@nocobase/cli 2.1.0-beta.32 → 2.1.0-beta.34
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/app/down.js +10 -13
- package/dist/commands/app/logs.js +0 -1
- package/dist/commands/app/restart.js +63 -2
- package/dist/commands/app/start.js +41 -17
- package/dist/commands/app/stop.js +0 -1
- package/dist/commands/app/upgrade.js +9 -4
- package/dist/commands/env/add.js +3 -4
- package/dist/commands/env/auth.js +3 -2
- package/dist/commands/env/remove.js +38 -13
- package/dist/commands/env/update.js +9 -2
- package/dist/commands/examples/prompts-stages.js +4 -4
- package/dist/commands/examples/prompts-test.js +4 -4
- package/dist/commands/init.js +38 -31
- package/dist/commands/install.js +100 -63
- package/dist/commands/license/activate.js +66 -64
- package/dist/commands/license/id.js +0 -1
- package/dist/commands/license/plugins/clean.js +0 -1
- package/dist/commands/license/plugins/list.js +0 -1
- package/dist/commands/license/plugins/sync.js +0 -1
- package/dist/commands/license/shared.js +3 -3
- package/dist/commands/license/status.js +0 -1
- package/dist/commands/plugin/disable.js +0 -1
- package/dist/commands/plugin/enable.js +0 -1
- package/dist/commands/plugin/list.js +0 -1
- package/dist/commands/self/update.js +12 -3
- package/dist/commands/skills/install.js +12 -3
- package/dist/commands/skills/remove.js +12 -3
- package/dist/commands/skills/update.js +12 -3
- package/dist/commands/source/dev.js +0 -1
- package/dist/commands/source/download.js +29 -17
- package/dist/lib/app-managed-resources.js +8 -2
- package/dist/lib/bootstrap.js +11 -2
- package/dist/lib/db-connection-check.js +3 -23
- package/dist/lib/docker-env-file.js +52 -0
- package/dist/lib/env-auth.js +4 -3
- package/dist/lib/env-config.js +1 -0
- package/dist/lib/env-guard.js +8 -7
- package/dist/lib/generated-command.js +0 -1
- package/dist/lib/inquirer-theme.js +17 -0
- package/dist/lib/inquirer.js +244 -0
- package/dist/lib/object-utils.js +76 -0
- package/dist/lib/prompt-catalog-core.js +185 -0
- package/dist/lib/prompt-catalog-terminal.js +375 -0
- package/dist/lib/prompt-catalog.js +2 -573
- package/dist/lib/prompt-validators.js +56 -1
- package/dist/lib/resource-command.js +0 -1
- package/dist/lib/skills-manager.js +75 -11
- package/dist/lib/startup-update.js +12 -8
- package/dist/lib/ui.js +28 -51
- package/dist/locale/en-US.json +8 -3
- package/dist/locale/zh-CN.json +8 -3
- package/dist/post-processors/data-modeling.js +25 -7
- package/dist/post-processors/data-source-manager.js +24 -0
- package/nocobase-ctl.config.json +20 -1
- package/package.json +7 -5
package/dist/commands/init.js
CHANGED
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
import { Command, Flags } from '@oclif/core';
|
|
10
|
-
import * as p from '@clack/prompts';
|
|
11
10
|
import pc from 'picocolors';
|
|
12
11
|
import { existsSync } from 'node:fs';
|
|
13
12
|
import path from 'node:path';
|
|
@@ -19,10 +18,11 @@ import { resolveDefaultConfigScope } from '../lib/cli-home.js';
|
|
|
19
18
|
import { runPromptCatalogWebUI, } from "../lib/prompt-web-ui.js";
|
|
20
19
|
import { validateApiBaseUrl, validateEnvKey } from "../lib/prompt-validators.js";
|
|
21
20
|
import { inspectSkillsStatus, installNocoBaseSkills, updateNocoBaseSkills, } from '../lib/skills-manager.js';
|
|
21
|
+
import { omitKeys, pickKeys } from "../lib/object-utils.js";
|
|
22
|
+
import { printInfo, printStage, printVerbose, printWarning } from '../lib/ui.js';
|
|
22
23
|
import Download from "./download.js";
|
|
23
24
|
import EnvAdd from "./env/add.js";
|
|
24
25
|
import Install, { defaultDbPortForDialect } from "./install.js";
|
|
25
|
-
import _ from 'lodash';
|
|
26
26
|
const DEFAULT_INIT_API_BASE_URL = 'http://localhost:13000/api';
|
|
27
27
|
const DEFAULT_INIT_APP_NAME = 'local';
|
|
28
28
|
const DOWNLOAD_OUTPUT_DIR_PROMPT = Download.prompts.outputDir;
|
|
@@ -129,15 +129,18 @@ function shellQuoteArg(value) {
|
|
|
129
129
|
: `'${value.replace(/'/g, `'\\''`)}'`;
|
|
130
130
|
}
|
|
131
131
|
function initTitle() {
|
|
132
|
-
return
|
|
132
|
+
return 'Set up NocoBase';
|
|
133
|
+
}
|
|
134
|
+
function logInitStage(title) {
|
|
135
|
+
printStage(title);
|
|
133
136
|
}
|
|
134
137
|
function logInitUiReady(command, url) {
|
|
135
|
-
|
|
136
|
-
|
|
138
|
+
command.log(translateCli('commands.init.messages.uiReady'));
|
|
139
|
+
command.log(translateCli('commands.init.messages.uiReadyHelp'));
|
|
137
140
|
command.log(`URL: ${url}`);
|
|
138
141
|
}
|
|
139
142
|
function logInitUiBrowserOpenFallback() {
|
|
140
|
-
|
|
143
|
+
printWarning(translateCli('commands.init.messages.uiOpenBrowserFallback'));
|
|
141
144
|
}
|
|
142
145
|
function formatBrowserOpenError(error) {
|
|
143
146
|
if (error instanceof Error) {
|
|
@@ -313,8 +316,8 @@ Prompt modes:
|
|
|
313
316
|
min: 0,
|
|
314
317
|
max: 65535,
|
|
315
318
|
}),
|
|
316
|
-
...
|
|
317
|
-
...
|
|
319
|
+
...pickKeys(EnvAdd.flags, INIT_ENV_ADD_FLAG_NAMES),
|
|
320
|
+
...omitKeys(Install.flags, ['yes', 'env']),
|
|
318
321
|
};
|
|
319
322
|
async run() {
|
|
320
323
|
const parsedResult = await this.parse(Init);
|
|
@@ -337,10 +340,10 @@ Prompt modes:
|
|
|
337
340
|
if (normalizedFlags.resume) {
|
|
338
341
|
const envName = String(normalizedFlags.env ?? '').trim();
|
|
339
342
|
if (!envName) {
|
|
340
|
-
|
|
343
|
+
this.error(formatResumeEnvRequiredMessage());
|
|
341
344
|
this.exit(1);
|
|
342
345
|
}
|
|
343
|
-
|
|
346
|
+
logInitStage(initTitle());
|
|
344
347
|
await this.syncNocoBaseSkills({
|
|
345
348
|
skip: Boolean(normalizedFlags['skip-skills']),
|
|
346
349
|
});
|
|
@@ -351,7 +354,6 @@ Prompt modes:
|
|
|
351
354
|
const message = error instanceof Error ? error.message : String(error);
|
|
352
355
|
this.error(message);
|
|
353
356
|
}
|
|
354
|
-
p.outro('Workspace init finished.');
|
|
355
357
|
return;
|
|
356
358
|
}
|
|
357
359
|
const interactive = Boolean(stdinStream.isTTY && stdoutStream.isTTY);
|
|
@@ -377,21 +379,21 @@ Prompt modes:
|
|
|
377
379
|
}
|
|
378
380
|
if (normalizedFlags.yes && !String(presetValues.appName ?? '').trim()) {
|
|
379
381
|
const formatted = formatSkippedAppNameRequiredMessage();
|
|
380
|
-
|
|
382
|
+
this.error(highlightInitValidationMessage(formatted));
|
|
381
383
|
this.exit(1);
|
|
382
384
|
}
|
|
383
385
|
const appName = String(presetValues.appName ?? '').trim();
|
|
384
386
|
if (useBrowserUi) {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
+
logInitStage(initTitle());
|
|
388
|
+
this.log(translateCli('commands.init.messages.uiOpening'));
|
|
387
389
|
}
|
|
388
390
|
else {
|
|
389
|
-
|
|
391
|
+
logInitStage(initTitle());
|
|
390
392
|
if (normalizedFlags.yes) {
|
|
391
|
-
|
|
393
|
+
printInfo(`Non-interactive setup for env "${appName}" (--yes).`);
|
|
392
394
|
}
|
|
393
395
|
else if (!interactive) {
|
|
394
|
-
|
|
396
|
+
printWarning('No interactive terminal detected. NocoBase will be installed using the provided flags and safe defaults.');
|
|
395
397
|
}
|
|
396
398
|
}
|
|
397
399
|
const dynamicInitialValues = await Init.buildDynamicInitialValuesForInstall(normalizedFlags, presetValues);
|
|
@@ -412,7 +414,7 @@ Prompt modes:
|
|
|
412
414
|
},
|
|
413
415
|
onOpenBrowserError: (_url, err) => {
|
|
414
416
|
logInitUiBrowserOpenFallback();
|
|
415
|
-
|
|
417
|
+
this.log(`Browser open error: ${formatBrowserOpenError(err)}`);
|
|
416
418
|
},
|
|
417
419
|
});
|
|
418
420
|
}
|
|
@@ -422,12 +424,11 @@ Prompt modes:
|
|
|
422
424
|
yes: normalizedFlags.yes || useBrowserUi || !interactive,
|
|
423
425
|
hooks: {
|
|
424
426
|
onCancel: () => {
|
|
425
|
-
p.cancel('Init cancelled.');
|
|
426
427
|
this.exit(0);
|
|
427
428
|
},
|
|
428
429
|
onMissingNonInteractive: (message) => {
|
|
429
430
|
const formatted = formatInitValidationMessage(message);
|
|
430
|
-
|
|
431
|
+
this.error(highlightInitValidationMessage(formatted));
|
|
431
432
|
this.exit(1);
|
|
432
433
|
},
|
|
433
434
|
},
|
|
@@ -438,7 +439,7 @@ Prompt modes:
|
|
|
438
439
|
? await getEnv(String(results.appName ?? '').trim(), { scope: resolveDefaultConfigScope() })
|
|
439
440
|
: undefined;
|
|
440
441
|
if (existingEnv && Boolean(normalizedFlags.force)) {
|
|
441
|
-
|
|
442
|
+
printWarning(`Reconfiguring existing env ${pc.cyan(pc.bold(`"${existingEnv.name}"`))} from the global config because ${pc.bold('--force')} was set. The env config will be updated before install starts, then refreshed again after install succeeds.`);
|
|
442
443
|
}
|
|
443
444
|
await this.syncNocoBaseSkills({
|
|
444
445
|
skip: Boolean(normalizedFlags['skip-skills']),
|
|
@@ -447,14 +448,16 @@ Prompt modes:
|
|
|
447
448
|
try {
|
|
448
449
|
// oclif explicit registry keys use `:` (e.g. `env:add`); users still type `nb env add`.
|
|
449
450
|
if (hasNocobase) {
|
|
450
|
-
|
|
451
|
+
logInitStage('Connecting to the env');
|
|
452
|
+
printVerbose('Running nb env add');
|
|
451
453
|
await this.config.runCommand('env:add', this.buildEnvAddArgv(results));
|
|
452
454
|
}
|
|
453
455
|
else {
|
|
454
|
-
|
|
456
|
+
logInitStage('Saving env config');
|
|
455
457
|
await this.persistManagedEnvConfig(results, normalizedFlags);
|
|
456
458
|
managedInstallResults = results;
|
|
457
|
-
|
|
459
|
+
printInfo(`Saved env config for "${String(results.appName ?? DEFAULT_INIT_APP_NAME).trim() || DEFAULT_INIT_APP_NAME}".`);
|
|
460
|
+
printVerbose('Running nb init');
|
|
458
461
|
await this.config.runCommand('install', this.buildInstallArgv(results, normalizedFlags));
|
|
459
462
|
}
|
|
460
463
|
}
|
|
@@ -463,10 +466,9 @@ Prompt modes:
|
|
|
463
466
|
const formatted = managedInstallResults
|
|
464
467
|
? this.formatManagedInstallFailureMessage(message, managedInstallResults, normalizedFlags)
|
|
465
468
|
: message;
|
|
466
|
-
|
|
469
|
+
this.error(pc.red(formatted));
|
|
467
470
|
this.exit(1);
|
|
468
471
|
}
|
|
469
|
-
p.outro('Workspace init finished.');
|
|
470
472
|
}
|
|
471
473
|
static async buildDynamicInitialValuesForInstall(flags, presetValues) {
|
|
472
474
|
const out = {};
|
|
@@ -706,22 +708,25 @@ Prompt modes:
|
|
|
706
708
|
}
|
|
707
709
|
async syncNocoBaseSkills(options) {
|
|
708
710
|
if (options?.skip) {
|
|
709
|
-
|
|
711
|
+
printVerbose('Skipped agent skills sync.');
|
|
710
712
|
return;
|
|
711
713
|
}
|
|
712
714
|
try {
|
|
715
|
+
logInitStage('Syncing agent skills');
|
|
713
716
|
const status = await inspectSkillsStatus();
|
|
714
717
|
if (!status.installed) {
|
|
715
|
-
|
|
718
|
+
printVerbose('Installing NocoBase agent skills (nb skills install)');
|
|
716
719
|
await installNocoBaseSkills();
|
|
720
|
+
printInfo('Agent skills ready.');
|
|
717
721
|
return;
|
|
718
722
|
}
|
|
719
|
-
|
|
723
|
+
printVerbose('Updating NocoBase agent skills (nb skills update)');
|
|
720
724
|
await updateNocoBaseSkills();
|
|
725
|
+
printInfo('Agent skills ready.');
|
|
721
726
|
}
|
|
722
727
|
catch (error) {
|
|
723
728
|
const message = error instanceof Error ? error.message : String(error);
|
|
724
|
-
|
|
729
|
+
this.error(pc.red(`Skills sync failed: ${message}`));
|
|
725
730
|
this.exit(1);
|
|
726
731
|
}
|
|
727
732
|
}
|
|
@@ -798,9 +803,11 @@ Prompt modes:
|
|
|
798
803
|
if (options?.nonInteractive ?? true) {
|
|
799
804
|
argv.unshift('-y');
|
|
800
805
|
}
|
|
806
|
+
argv.push('--skip-save-env-log');
|
|
801
807
|
const processArgv = process.argv.slice(2);
|
|
802
808
|
const envName = String(results.appName ?? DEFAULT_INIT_APP_NAME).trim() || DEFAULT_INIT_APP_NAME;
|
|
803
809
|
const source = String(results.source ?? '').trim();
|
|
810
|
+
const hasNocobase = String(results.hasNocobase ?? '').trim() === 'yes';
|
|
804
811
|
const apiBaseUrl = String(results.apiBaseUrl ?? '').trim();
|
|
805
812
|
const authType = String(results.authType ?? '').trim();
|
|
806
813
|
const accessToken = String(results.accessToken ?? '');
|
|
@@ -811,7 +818,7 @@ Prompt modes:
|
|
|
811
818
|
if (Boolean(flags.verbose)) {
|
|
812
819
|
argv.push('--verbose');
|
|
813
820
|
}
|
|
814
|
-
if (apiBaseUrl) {
|
|
821
|
+
if (hasNocobase && apiBaseUrl) {
|
|
815
822
|
argv.push('--api-base-url', apiBaseUrl);
|
|
816
823
|
}
|
|
817
824
|
if (authType) {
|
package/dist/commands/install.js
CHANGED
|
@@ -7,8 +7,6 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
import { Command, Flags } from '@oclif/core';
|
|
10
|
-
import * as p from '@clack/prompts';
|
|
11
|
-
import _ from 'lodash';
|
|
12
10
|
import { spawn } from 'node:child_process';
|
|
13
11
|
import crypto from 'node:crypto';
|
|
14
12
|
import { mkdir } from 'node:fs/promises';
|
|
@@ -24,9 +22,11 @@ import { findAvailableTcpPort, validateAvailableTcpPort, validateTcpPort, valida
|
|
|
24
22
|
import { validateExternalDbConfig } from "../lib/db-connection-check.js";
|
|
25
23
|
import { formatMissingManagedAppEnvMessage } from '../lib/app-runtime.js';
|
|
26
24
|
import { run, runNocoBaseCommand } from '../lib/run-npm.js';
|
|
27
|
-
import {
|
|
25
|
+
import { printInfo, printStage, printVerbose, printWarning, setVerboseMode, } from '../lib/ui.js';
|
|
26
|
+
import { omitKeys, upperFirst } from "../lib/object-utils.js";
|
|
28
27
|
import { getEnv, setCurrentEnv, upsertEnv } from '../lib/auth-store.js';
|
|
29
28
|
import { buildStoredEnvConfig } from '../lib/env-config.js';
|
|
29
|
+
import { resolveDockerEnvFileArg, } from "../lib/docker-env-file.js";
|
|
30
30
|
import Download, { defaultDockerRegistryForLang, } from './download.js';
|
|
31
31
|
import EnvAdd from "./env/add.js";
|
|
32
32
|
const DEFAULT_INSTALL_ENV_NAME = 'local';
|
|
@@ -260,6 +260,13 @@ async function commandOutput(command, args, options) {
|
|
|
260
260
|
});
|
|
261
261
|
}
|
|
262
262
|
export default class Install extends Command {
|
|
263
|
+
ensuredDockerNetworks = new Set();
|
|
264
|
+
logStage(title) {
|
|
265
|
+
printStage(title);
|
|
266
|
+
}
|
|
267
|
+
logDetail(message) {
|
|
268
|
+
printVerbose(message);
|
|
269
|
+
}
|
|
263
270
|
static hidden = true;
|
|
264
271
|
static description = 'Install NocoBase: database, storage, admin user, and `nocobase-v1 install`. Optionally run `nb source download` first; distribution and image details are configured on `nb source download`, not here. Use `--resume` to continue an interrupted setup from the saved workspace env config.';
|
|
265
272
|
static examples = [
|
|
@@ -288,6 +295,10 @@ export default class Install extends Command {
|
|
|
288
295
|
description: 'Show detailed command output',
|
|
289
296
|
default: false,
|
|
290
297
|
}),
|
|
298
|
+
'skip-save-env-log': Flags.boolean({
|
|
299
|
+
hidden: true,
|
|
300
|
+
default: false,
|
|
301
|
+
}),
|
|
291
302
|
env: Flags.string({
|
|
292
303
|
char: 'e',
|
|
293
304
|
description: 'App/env name to create or update. Defaults app paths to ./<envName>/source/ and ./<envName>/storage/.',
|
|
@@ -373,7 +384,7 @@ export default class Install extends Command {
|
|
|
373
384
|
description: 'Download NocoBase app files or pull a Docker image before installing',
|
|
374
385
|
default: false,
|
|
375
386
|
}),
|
|
376
|
-
...
|
|
387
|
+
...omitKeys(Download.flags, ['yes']),
|
|
377
388
|
};
|
|
378
389
|
/** Environment name only: run before {@link Install.prompts} (see `run`). */
|
|
379
390
|
static envPrompts = {
|
|
@@ -797,7 +808,7 @@ export default class Install extends Command {
|
|
|
797
808
|
const database = String(dbResults.dbDatabase ?? '').trim();
|
|
798
809
|
const address = host && port ? `${host}:${port}` : host || port || '(unknown address)';
|
|
799
810
|
const target = database ? `${address}/${database}` : address;
|
|
800
|
-
|
|
811
|
+
printVerbose(`Checking external ${dialect} database: ${target}`);
|
|
801
812
|
const validationError = await validateExternalDbConfig(dbResults);
|
|
802
813
|
if (validationError) {
|
|
803
814
|
throw new Error(validationError);
|
|
@@ -1026,7 +1037,7 @@ export default class Install extends Command {
|
|
|
1026
1037
|
}
|
|
1027
1038
|
const nextPort = await findAvailableTcpPort();
|
|
1028
1039
|
if (options?.warn) {
|
|
1029
|
-
|
|
1040
|
+
printWarning(`${options.label ?? 'Default port'} ${normalized} is already in use. Using available port ${nextPort} for this setup.`);
|
|
1030
1041
|
}
|
|
1031
1042
|
return nextPort;
|
|
1032
1043
|
}
|
|
@@ -1093,7 +1104,6 @@ export default class Install extends Command {
|
|
|
1093
1104
|
yes: false,
|
|
1094
1105
|
hooks: {
|
|
1095
1106
|
onCancel: () => {
|
|
1096
|
-
p.cancel('Download cancelled.');
|
|
1097
1107
|
exit(0);
|
|
1098
1108
|
},
|
|
1099
1109
|
onMissingNonInteractive: (message) => {
|
|
@@ -1443,18 +1453,23 @@ export default class Install extends Command {
|
|
|
1443
1453
|
throw new Error(`Built-in database does not support "${dbDialect}" yet. Please choose PostgreSQL, MySQL, MariaDB, or KingbaseES.`);
|
|
1444
1454
|
}
|
|
1445
1455
|
async ensureDockerNetwork(name) {
|
|
1446
|
-
|
|
1456
|
+
if (this.ensuredDockerNetworks.has(name)) {
|
|
1457
|
+
return;
|
|
1458
|
+
}
|
|
1459
|
+
printVerbose(`Checking Docker network: ${name}`);
|
|
1447
1460
|
const exists = await commandSucceeds('docker', ['network', 'inspect', name]);
|
|
1448
1461
|
if (exists) {
|
|
1449
|
-
|
|
1462
|
+
printVerbose(`Docker network already exists: ${name}`);
|
|
1463
|
+
this.ensuredDockerNetworks.add(name);
|
|
1450
1464
|
return;
|
|
1451
1465
|
}
|
|
1452
|
-
|
|
1466
|
+
printVerbose(`Creating Docker network: ${name}`);
|
|
1453
1467
|
try {
|
|
1454
1468
|
await run('docker', ['network', 'create', name], {
|
|
1455
1469
|
errorName: 'docker network create',
|
|
1456
1470
|
});
|
|
1457
|
-
|
|
1471
|
+
printVerbose(`Docker network is ready: ${name}`);
|
|
1472
|
+
this.ensuredDockerNetworks.add(name);
|
|
1458
1473
|
}
|
|
1459
1474
|
catch (error) {
|
|
1460
1475
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1489,7 +1504,7 @@ export default class Install extends Command {
|
|
|
1489
1504
|
if (!params.force) {
|
|
1490
1505
|
return true;
|
|
1491
1506
|
}
|
|
1492
|
-
|
|
1507
|
+
printVerbose(`Removing existing ${params.displayName}: ${params.containerName}`);
|
|
1493
1508
|
await this.removeDockerContainer(params.containerName);
|
|
1494
1509
|
return false;
|
|
1495
1510
|
}
|
|
@@ -1513,7 +1528,7 @@ export default class Install extends Command {
|
|
|
1513
1528
|
async ensureBuiltinDbContainer(plan, options) {
|
|
1514
1529
|
const exists = await this.dockerContainerExists(plan.containerName);
|
|
1515
1530
|
if (exists) {
|
|
1516
|
-
|
|
1531
|
+
printVerbose(`Built-in ${plan.dbDialect} container already exists: ${plan.containerName}`);
|
|
1517
1532
|
return;
|
|
1518
1533
|
}
|
|
1519
1534
|
await mkdir(plan.dataDir, { recursive: true });
|
|
@@ -1540,7 +1555,8 @@ export default class Install extends Command {
|
|
|
1540
1555
|
dbPassword: params.dbResults.dbPassword,
|
|
1541
1556
|
builtinDbImage: params.dbResults.builtinDbImage,
|
|
1542
1557
|
});
|
|
1543
|
-
|
|
1558
|
+
this.logStage('Preparing database');
|
|
1559
|
+
printInfo(`Using built-in ${plan.dbDialect} database.`);
|
|
1544
1560
|
await this.ensureDockerNetwork(plan.networkName);
|
|
1545
1561
|
const existingContainerKept = await this.removeDockerContainerIfForced({
|
|
1546
1562
|
containerName: plan.containerName,
|
|
@@ -1556,10 +1572,11 @@ export default class Install extends Command {
|
|
|
1556
1572
|
await this.ensureBuiltinDbContainer(plan, {
|
|
1557
1573
|
stdio: params.commandStdio ?? 'ignore',
|
|
1558
1574
|
});
|
|
1559
|
-
|
|
1575
|
+
printInfo(`${upperFirst(plan.dbDialect)} database ready.`);
|
|
1576
|
+
printVerbose(`Built-in ${plan.dbDialect} database ready at ${plan.dbHost}:${plan.dbPort}`);
|
|
1560
1577
|
return plan;
|
|
1561
1578
|
}
|
|
1562
|
-
static buildDockerAppPlan(params) {
|
|
1579
|
+
static async buildDockerAppPlan(params) {
|
|
1563
1580
|
const dockerRegistry = String(downloadResultsValue(params.downloadResults, 'dockerRegistry') ?? '').trim()
|
|
1564
1581
|
|| defaultDockerRegistryForLang(process.env.NB_LOCALE);
|
|
1565
1582
|
const version = String(downloadResultsValue(params.downloadResults, 'version') ?? '').trim() || DEFAULT_DOCKER_VERSION;
|
|
@@ -1582,6 +1599,8 @@ export default class Install extends Command {
|
|
|
1582
1599
|
const appKey = crypto.randomBytes(32).toString('hex');
|
|
1583
1600
|
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
|
|
1584
1601
|
const containerName = Install.buildDockerAppContainerName(params.envName, params.dockerContainerPrefix ?? params.workspaceName);
|
|
1602
|
+
const configuredEnvFile = String(params.appResults.envFile ?? '').trim();
|
|
1603
|
+
const envFile = await resolveDockerEnvFileArg(params.envName, configuredEnvFile ? { envFile: configuredEnvFile } : undefined);
|
|
1585
1604
|
const initEnvVars = Install.buildInitAppEnvVars({
|
|
1586
1605
|
appResults: params.appResults,
|
|
1587
1606
|
rootResults: params.rootResults,
|
|
@@ -1598,6 +1617,9 @@ export default class Install extends Command {
|
|
|
1598
1617
|
'-p',
|
|
1599
1618
|
`${appPort}:80`,
|
|
1600
1619
|
];
|
|
1620
|
+
if (envFile) {
|
|
1621
|
+
args.push('--env-file', envFile);
|
|
1622
|
+
}
|
|
1601
1623
|
for (const [key, value] of Object.entries(initEnvVars)) {
|
|
1602
1624
|
args.push('-e', `${key}=${value}`);
|
|
1603
1625
|
}
|
|
@@ -1609,6 +1631,7 @@ export default class Install extends Command {
|
|
|
1609
1631
|
imageRef,
|
|
1610
1632
|
appPort,
|
|
1611
1633
|
storagePath,
|
|
1634
|
+
envFile,
|
|
1612
1635
|
appKey,
|
|
1613
1636
|
timeZone,
|
|
1614
1637
|
args,
|
|
@@ -1617,7 +1640,7 @@ export default class Install extends Command {
|
|
|
1617
1640
|
async ensureDockerAppContainer(plan, options) {
|
|
1618
1641
|
const exists = await this.dockerContainerExists(plan.containerName);
|
|
1619
1642
|
if (exists) {
|
|
1620
|
-
|
|
1643
|
+
printVerbose(`App container already exists: ${plan.containerName}`);
|
|
1621
1644
|
return 'existing';
|
|
1622
1645
|
}
|
|
1623
1646
|
await mkdir(plan.storagePath, { recursive: true });
|
|
@@ -1631,7 +1654,7 @@ export default class Install extends Command {
|
|
|
1631
1654
|
const networkName = params.builtinDbPlan?.networkName
|
|
1632
1655
|
?? Install.buildBuiltinDbNetworkName(params.envName, params.dockerNetworkName ?? params.workspaceName);
|
|
1633
1656
|
await this.ensureDockerNetwork(networkName);
|
|
1634
|
-
const plan = Install.buildDockerAppPlan({
|
|
1657
|
+
const plan = await Install.buildDockerAppPlan({
|
|
1635
1658
|
envName: params.envName,
|
|
1636
1659
|
workspaceName: params.workspaceName,
|
|
1637
1660
|
dockerContainerPrefix: params.dockerContainerPrefix,
|
|
@@ -1641,7 +1664,7 @@ export default class Install extends Command {
|
|
|
1641
1664
|
rootResults: params.rootResults,
|
|
1642
1665
|
networkName,
|
|
1643
1666
|
});
|
|
1644
|
-
|
|
1667
|
+
printVerbose('Starting NocoBase app (Docker)');
|
|
1645
1668
|
await this.removeDockerContainerIfForced({
|
|
1646
1669
|
containerName: plan.containerName,
|
|
1647
1670
|
displayName: 'app container',
|
|
@@ -1655,7 +1678,7 @@ export default class Install extends Command {
|
|
|
1655
1678
|
plan.appKey = env.APP_KEY || plan.appKey;
|
|
1656
1679
|
plan.timeZone = env.TZ || plan.timeZone;
|
|
1657
1680
|
}
|
|
1658
|
-
|
|
1681
|
+
printVerbose(`NocoBase app is starting at http://127.0.0.1:${plan.appPort}`);
|
|
1659
1682
|
return plan;
|
|
1660
1683
|
}
|
|
1661
1684
|
static pushDownloadArgIfValue(argv, flag, value) {
|
|
@@ -1666,6 +1689,9 @@ export default class Install extends Command {
|
|
|
1666
1689
|
}
|
|
1667
1690
|
static buildDownloadArgvFromResults(results, options) {
|
|
1668
1691
|
const argv = ['-y', '--no-intro'];
|
|
1692
|
+
if (options?.compactLog) {
|
|
1693
|
+
argv.push('--compact-log');
|
|
1694
|
+
}
|
|
1669
1695
|
const source = String(results.source ?? '').trim();
|
|
1670
1696
|
if (options?.verbose) {
|
|
1671
1697
|
argv.push('--verbose');
|
|
@@ -1718,11 +1744,8 @@ export default class Install extends Command {
|
|
|
1718
1744
|
async downloadManagedSource(params) {
|
|
1719
1745
|
const argv = Install.buildDownloadArgvFromResults(params.downloadResults, {
|
|
1720
1746
|
verbose: params.verbose,
|
|
1747
|
+
compactLog: true,
|
|
1721
1748
|
});
|
|
1722
|
-
const source = String(params.downloadResults.source ?? '').trim();
|
|
1723
|
-
p.log.step(source === 'docker'
|
|
1724
|
-
? 'Downloading Docker image'
|
|
1725
|
-
: 'Downloading local NocoBase app files');
|
|
1726
1749
|
return await this.config.runCommand('source:download', argv);
|
|
1727
1750
|
}
|
|
1728
1751
|
async downloadLocalApp(params) {
|
|
@@ -1784,7 +1807,7 @@ export default class Install extends Command {
|
|
|
1784
1807
|
rootResults: params.rootResults,
|
|
1785
1808
|
});
|
|
1786
1809
|
const args = ['start', '--quickstart', '--daemon'];
|
|
1787
|
-
|
|
1810
|
+
this.logDetail(`Stopping any existing local NocoBase process in ${params.projectRoot}`);
|
|
1788
1811
|
try {
|
|
1789
1812
|
await runNocoBaseCommand(['pm2', 'kill'], {
|
|
1790
1813
|
cwd: params.projectRoot,
|
|
@@ -1794,15 +1817,15 @@ export default class Install extends Command {
|
|
|
1794
1817
|
}
|
|
1795
1818
|
catch (error) {
|
|
1796
1819
|
const message = error instanceof Error ? error.message : String(error);
|
|
1797
|
-
|
|
1820
|
+
this.logDetail(`Skipped local process cleanup before start: ${message}`);
|
|
1798
1821
|
}
|
|
1799
|
-
|
|
1822
|
+
this.logDetail(`Starting local NocoBase app from ${params.projectRoot}`);
|
|
1800
1823
|
await runNocoBaseCommand(args, {
|
|
1801
1824
|
cwd: params.projectRoot,
|
|
1802
1825
|
env,
|
|
1803
1826
|
stdio: params.commandStdio ?? 'ignore',
|
|
1804
1827
|
});
|
|
1805
|
-
|
|
1828
|
+
this.logDetail(`Local app is starting at http://127.0.0.1:${env.APP_PORT}`);
|
|
1806
1829
|
return {
|
|
1807
1830
|
source: params.source,
|
|
1808
1831
|
projectRoot: params.projectRoot,
|
|
@@ -1876,35 +1899,29 @@ export default class Install extends Command {
|
|
|
1876
1899
|
const fetchImpl = options?.fetchImpl ?? fetch;
|
|
1877
1900
|
const startedAt = Date.now();
|
|
1878
1901
|
let lastMessage = 'No response yet';
|
|
1879
|
-
let
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
stopTask();
|
|
1890
|
-
taskActive = false;
|
|
1891
|
-
p.log.info(`Application health check passed: ${healthCheckUrl}`);
|
|
1892
|
-
return;
|
|
1893
|
-
}
|
|
1894
|
-
lastMessage = result.message;
|
|
1895
|
-
const elapsedSeconds = Math.max(1, Math.floor((Date.now() - startedAt) / 1000));
|
|
1896
|
-
updateTask(`Waiting for application health check: ${healthCheckUrl}. Still starting... (${elapsedSeconds}s elapsed, last status: ${Install.formatHealthCheckMessage(lastMessage)})`);
|
|
1897
|
-
const remainingMs = timeoutMs - (Date.now() - startedAt);
|
|
1898
|
-
if (remainingMs <= 0) {
|
|
1899
|
-
break;
|
|
1900
|
-
}
|
|
1901
|
-
await Install.sleep(Math.min(intervalMs, remainingMs));
|
|
1902
|
+
let lastLoggedStatus = '';
|
|
1903
|
+
printInfo('Waiting for NocoBase to become ready...');
|
|
1904
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
1905
|
+
const result = await Install.requestAppHealthCheck({
|
|
1906
|
+
healthCheckUrl,
|
|
1907
|
+
fetchImpl,
|
|
1908
|
+
requestTimeoutMs,
|
|
1909
|
+
});
|
|
1910
|
+
if (result.ok) {
|
|
1911
|
+
return;
|
|
1902
1912
|
}
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1913
|
+
lastMessage = result.message;
|
|
1914
|
+
const elapsedSeconds = Math.max(1, Math.floor((Date.now() - startedAt) / 1000));
|
|
1915
|
+
const statusLine = `Waiting for NocoBase to become ready... (${elapsedSeconds}s elapsed, last status: ${Install.formatHealthCheckMessage(lastMessage)})`;
|
|
1916
|
+
if (statusLine !== lastLoggedStatus) {
|
|
1917
|
+
printInfo(statusLine);
|
|
1918
|
+
lastLoggedStatus = statusLine;
|
|
1919
|
+
}
|
|
1920
|
+
const remainingMs = timeoutMs - (Date.now() - startedAt);
|
|
1921
|
+
if (remainingMs <= 0) {
|
|
1922
|
+
break;
|
|
1907
1923
|
}
|
|
1924
|
+
await Install.sleep(Math.min(intervalMs, remainingMs));
|
|
1908
1925
|
}
|
|
1909
1926
|
const logHint = options?.containerName
|
|
1910
1927
|
? ` You can inspect startup logs with: docker logs ${options.containerName}`
|
|
@@ -1931,6 +1948,7 @@ export default class Install extends Command {
|
|
|
1931
1948
|
|| DEFAULT_INSTALL_APP_PORT;
|
|
1932
1949
|
const storagePath = String(params.appResults.storagePath ?? '').trim()
|
|
1933
1950
|
|| defaultInstallStoragePath(params.envName);
|
|
1951
|
+
const envFile = String(params.appResults.envFile ?? '').trim() || undefined;
|
|
1934
1952
|
const apiBaseUrl = Install.resolveApiBaseUrl({
|
|
1935
1953
|
appResults: params.appResults,
|
|
1936
1954
|
envAddResults: params.envAddResults,
|
|
@@ -1953,6 +1971,7 @@ export default class Install extends Command {
|
|
|
1953
1971
|
appRootPath: params.appResults.appRootPath,
|
|
1954
1972
|
appPort,
|
|
1955
1973
|
storagePath,
|
|
1974
|
+
...(envFile ? { envFile } : {}),
|
|
1956
1975
|
appKey: params.appResults.appKey,
|
|
1957
1976
|
timezone: params.appResults.timeZone,
|
|
1958
1977
|
builtinDb: params.dbResults.builtinDb,
|
|
@@ -2049,7 +2068,14 @@ export default class Install extends Command {
|
|
|
2049
2068
|
},
|
|
2050
2069
|
yes,
|
|
2051
2070
|
});
|
|
2052
|
-
const
|
|
2071
|
+
const envAddPromptsForInstall = {
|
|
2072
|
+
...EnvAdd.prompts,
|
|
2073
|
+
apiBaseUrl: {
|
|
2074
|
+
...EnvAdd.prompts.apiBaseUrl,
|
|
2075
|
+
validate: undefined,
|
|
2076
|
+
},
|
|
2077
|
+
};
|
|
2078
|
+
const envAddResults = await runPromptCatalog(envAddPromptsForInstall, {
|
|
2053
2079
|
initialValues: {
|
|
2054
2080
|
apiBaseUrl: `http://127.0.0.1:${appResults.appPort ?? DEFAULT_INSTALL_APP_PORT}/api`,
|
|
2055
2081
|
},
|
|
@@ -2077,13 +2103,14 @@ export default class Install extends Command {
|
|
|
2077
2103
|
const parsed = {
|
|
2078
2104
|
...flags,
|
|
2079
2105
|
};
|
|
2106
|
+
setVerboseMode(Boolean(parsed.verbose));
|
|
2080
2107
|
const commandStdio = this.commandStdio(parsed.verbose);
|
|
2081
2108
|
if (!parsed['no-intro']) {
|
|
2082
|
-
|
|
2109
|
+
this.logStage('Set up NocoBase');
|
|
2083
2110
|
}
|
|
2084
2111
|
if (parsed.resume) {
|
|
2085
2112
|
const envLabel = Install.toOptionalPromptString(parsed.env);
|
|
2086
|
-
|
|
2113
|
+
printInfo(envLabel
|
|
2087
2114
|
? `Resuming setup for env "${envLabel}" from the saved workspace config`
|
|
2088
2115
|
: 'Resuming setup from the saved workspace config');
|
|
2089
2116
|
}
|
|
@@ -2100,6 +2127,9 @@ export default class Install extends Command {
|
|
|
2100
2127
|
: undefined;
|
|
2101
2128
|
await Install.ensureExternalDbReadyForInstall(dbResults);
|
|
2102
2129
|
if (!parsed.resume) {
|
|
2130
|
+
if (!parsed['skip-save-env-log']) {
|
|
2131
|
+
this.logStage('Saving env config');
|
|
2132
|
+
}
|
|
2103
2133
|
await this.saveInstalledEnv({
|
|
2104
2134
|
envName,
|
|
2105
2135
|
appResults,
|
|
@@ -2108,7 +2138,9 @@ export default class Install extends Command {
|
|
|
2108
2138
|
rootResults,
|
|
2109
2139
|
envAddResults,
|
|
2110
2140
|
});
|
|
2111
|
-
|
|
2141
|
+
if (!parsed['skip-save-env-log']) {
|
|
2142
|
+
printInfo(`Saved env config for "${envName}".`);
|
|
2143
|
+
}
|
|
2112
2144
|
}
|
|
2113
2145
|
let builtinDbPlan;
|
|
2114
2146
|
if (Boolean(dbResults.builtinDb)) {
|
|
@@ -2132,11 +2164,13 @@ export default class Install extends Command {
|
|
|
2132
2164
|
let dockerAppPlan;
|
|
2133
2165
|
let localAppPlan;
|
|
2134
2166
|
if (Boolean(appResults.fetchSource)) {
|
|
2167
|
+
this.logStage('Preparing application');
|
|
2135
2168
|
if (source === 'docker') {
|
|
2136
2169
|
await this.downloadManagedSource({
|
|
2137
2170
|
downloadResults,
|
|
2138
2171
|
verbose: parsed.verbose,
|
|
2139
2172
|
});
|
|
2173
|
+
printInfo('Application image ready.');
|
|
2140
2174
|
dockerAppPlan = await this.installDockerApp({
|
|
2141
2175
|
envName,
|
|
2142
2176
|
dockerNetworkName,
|
|
@@ -2160,6 +2194,7 @@ export default class Install extends Command {
|
|
|
2160
2194
|
downloadResults,
|
|
2161
2195
|
verbose: parsed.verbose,
|
|
2162
2196
|
});
|
|
2197
|
+
printInfo('Application files ready.');
|
|
2163
2198
|
localAppPlan = await this.startLocalApp({
|
|
2164
2199
|
envName,
|
|
2165
2200
|
source: localSource,
|
|
@@ -2174,15 +2209,17 @@ export default class Install extends Command {
|
|
|
2174
2209
|
}
|
|
2175
2210
|
}
|
|
2176
2211
|
else {
|
|
2177
|
-
|
|
2212
|
+
this.logDetail('Skipped app download and install.');
|
|
2178
2213
|
}
|
|
2179
2214
|
if (dockerAppPlan || localAppPlan) {
|
|
2215
|
+
this.logStage('Starting NocoBase');
|
|
2180
2216
|
await this.waitForAppHealthCheck(Install.resolveApiBaseUrl({
|
|
2181
2217
|
appResults,
|
|
2182
2218
|
envAddResults,
|
|
2183
2219
|
}), {
|
|
2184
2220
|
containerName: dockerAppPlan?.containerName,
|
|
2185
2221
|
});
|
|
2222
|
+
printInfo(`NocoBase is ready at http://127.0.0.1:${dockerAppPlan?.appPort ?? localAppPlan?.appPort}`);
|
|
2186
2223
|
}
|
|
2187
2224
|
if (dockerAppPlan || localAppPlan || builtinDbPlan) {
|
|
2188
2225
|
await this.saveInstalledEnv({
|
|
@@ -2199,9 +2236,9 @@ export default class Install extends Command {
|
|
|
2199
2236
|
envAddResults,
|
|
2200
2237
|
appReady: Boolean(dockerAppPlan || localAppPlan),
|
|
2201
2238
|
});
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2239
|
+
if (!dockerAppPlan && !localAppPlan) {
|
|
2240
|
+
printInfo(`Install config for "${envName}" has been saved.`);
|
|
2241
|
+
}
|
|
2205
2242
|
}
|
|
2206
2243
|
}
|
|
2207
2244
|
function downloadResultsValue(downloadResults, key) {
|