@nocobase/cli 2.1.0-alpha.34 → 2.1.0-alpha.36
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 +0 -1
- package/dist/commands/app/start.js +0 -1
- package/dist/commands/app/stop.js +0 -1
- package/dist/commands/app/upgrade.js +0 -1
- 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 +89 -61
- 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/bootstrap.js +12 -3
- package/dist/lib/db-connection-check.js +3 -23
- package/dist/lib/env-auth.js +4 -3
- 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/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,7 +22,8 @@ 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';
|
|
30
29
|
import Download, { defaultDockerRegistryForLang, } from './download.js';
|
|
@@ -260,6 +259,13 @@ async function commandOutput(command, args, options) {
|
|
|
260
259
|
});
|
|
261
260
|
}
|
|
262
261
|
export default class Install extends Command {
|
|
262
|
+
ensuredDockerNetworks = new Set();
|
|
263
|
+
logStage(title) {
|
|
264
|
+
printStage(title);
|
|
265
|
+
}
|
|
266
|
+
logDetail(message) {
|
|
267
|
+
printVerbose(message);
|
|
268
|
+
}
|
|
263
269
|
static hidden = true;
|
|
264
270
|
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
271
|
static examples = [
|
|
@@ -288,6 +294,10 @@ export default class Install extends Command {
|
|
|
288
294
|
description: 'Show detailed command output',
|
|
289
295
|
default: false,
|
|
290
296
|
}),
|
|
297
|
+
'skip-save-env-log': Flags.boolean({
|
|
298
|
+
hidden: true,
|
|
299
|
+
default: false,
|
|
300
|
+
}),
|
|
291
301
|
env: Flags.string({
|
|
292
302
|
char: 'e',
|
|
293
303
|
description: 'App/env name to create or update. Defaults app paths to ./<envName>/source/ and ./<envName>/storage/.',
|
|
@@ -373,7 +383,7 @@ export default class Install extends Command {
|
|
|
373
383
|
description: 'Download NocoBase app files or pull a Docker image before installing',
|
|
374
384
|
default: false,
|
|
375
385
|
}),
|
|
376
|
-
...
|
|
386
|
+
...omitKeys(Download.flags, ['yes']),
|
|
377
387
|
};
|
|
378
388
|
/** Environment name only: run before {@link Install.prompts} (see `run`). */
|
|
379
389
|
static envPrompts = {
|
|
@@ -797,7 +807,7 @@ export default class Install extends Command {
|
|
|
797
807
|
const database = String(dbResults.dbDatabase ?? '').trim();
|
|
798
808
|
const address = host && port ? `${host}:${port}` : host || port || '(unknown address)';
|
|
799
809
|
const target = database ? `${address}/${database}` : address;
|
|
800
|
-
|
|
810
|
+
printVerbose(`Checking external ${dialect} database: ${target}`);
|
|
801
811
|
const validationError = await validateExternalDbConfig(dbResults);
|
|
802
812
|
if (validationError) {
|
|
803
813
|
throw new Error(validationError);
|
|
@@ -1026,7 +1036,7 @@ export default class Install extends Command {
|
|
|
1026
1036
|
}
|
|
1027
1037
|
const nextPort = await findAvailableTcpPort();
|
|
1028
1038
|
if (options?.warn) {
|
|
1029
|
-
|
|
1039
|
+
printWarning(`${options.label ?? 'Default port'} ${normalized} is already in use. Using available port ${nextPort} for this setup.`);
|
|
1030
1040
|
}
|
|
1031
1041
|
return nextPort;
|
|
1032
1042
|
}
|
|
@@ -1093,7 +1103,6 @@ export default class Install extends Command {
|
|
|
1093
1103
|
yes: false,
|
|
1094
1104
|
hooks: {
|
|
1095
1105
|
onCancel: () => {
|
|
1096
|
-
p.cancel('Download cancelled.');
|
|
1097
1106
|
exit(0);
|
|
1098
1107
|
},
|
|
1099
1108
|
onMissingNonInteractive: (message) => {
|
|
@@ -1443,18 +1452,23 @@ export default class Install extends Command {
|
|
|
1443
1452
|
throw new Error(`Built-in database does not support "${dbDialect}" yet. Please choose PostgreSQL, MySQL, MariaDB, or KingbaseES.`);
|
|
1444
1453
|
}
|
|
1445
1454
|
async ensureDockerNetwork(name) {
|
|
1446
|
-
|
|
1455
|
+
if (this.ensuredDockerNetworks.has(name)) {
|
|
1456
|
+
return;
|
|
1457
|
+
}
|
|
1458
|
+
printVerbose(`Checking Docker network: ${name}`);
|
|
1447
1459
|
const exists = await commandSucceeds('docker', ['network', 'inspect', name]);
|
|
1448
1460
|
if (exists) {
|
|
1449
|
-
|
|
1461
|
+
printVerbose(`Docker network already exists: ${name}`);
|
|
1462
|
+
this.ensuredDockerNetworks.add(name);
|
|
1450
1463
|
return;
|
|
1451
1464
|
}
|
|
1452
|
-
|
|
1465
|
+
printVerbose(`Creating Docker network: ${name}`);
|
|
1453
1466
|
try {
|
|
1454
1467
|
await run('docker', ['network', 'create', name], {
|
|
1455
1468
|
errorName: 'docker network create',
|
|
1456
1469
|
});
|
|
1457
|
-
|
|
1470
|
+
printVerbose(`Docker network is ready: ${name}`);
|
|
1471
|
+
this.ensuredDockerNetworks.add(name);
|
|
1458
1472
|
}
|
|
1459
1473
|
catch (error) {
|
|
1460
1474
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -1489,7 +1503,7 @@ export default class Install extends Command {
|
|
|
1489
1503
|
if (!params.force) {
|
|
1490
1504
|
return true;
|
|
1491
1505
|
}
|
|
1492
|
-
|
|
1506
|
+
printVerbose(`Removing existing ${params.displayName}: ${params.containerName}`);
|
|
1493
1507
|
await this.removeDockerContainer(params.containerName);
|
|
1494
1508
|
return false;
|
|
1495
1509
|
}
|
|
@@ -1513,7 +1527,7 @@ export default class Install extends Command {
|
|
|
1513
1527
|
async ensureBuiltinDbContainer(plan, options) {
|
|
1514
1528
|
const exists = await this.dockerContainerExists(plan.containerName);
|
|
1515
1529
|
if (exists) {
|
|
1516
|
-
|
|
1530
|
+
printVerbose(`Built-in ${plan.dbDialect} container already exists: ${plan.containerName}`);
|
|
1517
1531
|
return;
|
|
1518
1532
|
}
|
|
1519
1533
|
await mkdir(plan.dataDir, { recursive: true });
|
|
@@ -1540,7 +1554,8 @@ export default class Install extends Command {
|
|
|
1540
1554
|
dbPassword: params.dbResults.dbPassword,
|
|
1541
1555
|
builtinDbImage: params.dbResults.builtinDbImage,
|
|
1542
1556
|
});
|
|
1543
|
-
|
|
1557
|
+
this.logStage('Preparing database');
|
|
1558
|
+
printInfo(`Using built-in ${plan.dbDialect} database.`);
|
|
1544
1559
|
await this.ensureDockerNetwork(plan.networkName);
|
|
1545
1560
|
const existingContainerKept = await this.removeDockerContainerIfForced({
|
|
1546
1561
|
containerName: plan.containerName,
|
|
@@ -1556,7 +1571,8 @@ export default class Install extends Command {
|
|
|
1556
1571
|
await this.ensureBuiltinDbContainer(plan, {
|
|
1557
1572
|
stdio: params.commandStdio ?? 'ignore',
|
|
1558
1573
|
});
|
|
1559
|
-
|
|
1574
|
+
printInfo(`${upperFirst(plan.dbDialect)} database ready.`);
|
|
1575
|
+
printVerbose(`Built-in ${plan.dbDialect} database ready at ${plan.dbHost}:${plan.dbPort}`);
|
|
1560
1576
|
return plan;
|
|
1561
1577
|
}
|
|
1562
1578
|
static buildDockerAppPlan(params) {
|
|
@@ -1617,7 +1633,7 @@ export default class Install extends Command {
|
|
|
1617
1633
|
async ensureDockerAppContainer(plan, options) {
|
|
1618
1634
|
const exists = await this.dockerContainerExists(plan.containerName);
|
|
1619
1635
|
if (exists) {
|
|
1620
|
-
|
|
1636
|
+
printVerbose(`App container already exists: ${plan.containerName}`);
|
|
1621
1637
|
return 'existing';
|
|
1622
1638
|
}
|
|
1623
1639
|
await mkdir(plan.storagePath, { recursive: true });
|
|
@@ -1641,7 +1657,7 @@ export default class Install extends Command {
|
|
|
1641
1657
|
rootResults: params.rootResults,
|
|
1642
1658
|
networkName,
|
|
1643
1659
|
});
|
|
1644
|
-
|
|
1660
|
+
printVerbose('Starting NocoBase app (Docker)');
|
|
1645
1661
|
await this.removeDockerContainerIfForced({
|
|
1646
1662
|
containerName: plan.containerName,
|
|
1647
1663
|
displayName: 'app container',
|
|
@@ -1655,7 +1671,7 @@ export default class Install extends Command {
|
|
|
1655
1671
|
plan.appKey = env.APP_KEY || plan.appKey;
|
|
1656
1672
|
plan.timeZone = env.TZ || plan.timeZone;
|
|
1657
1673
|
}
|
|
1658
|
-
|
|
1674
|
+
printVerbose(`NocoBase app is starting at http://127.0.0.1:${plan.appPort}`);
|
|
1659
1675
|
return plan;
|
|
1660
1676
|
}
|
|
1661
1677
|
static pushDownloadArgIfValue(argv, flag, value) {
|
|
@@ -1666,6 +1682,9 @@ export default class Install extends Command {
|
|
|
1666
1682
|
}
|
|
1667
1683
|
static buildDownloadArgvFromResults(results, options) {
|
|
1668
1684
|
const argv = ['-y', '--no-intro'];
|
|
1685
|
+
if (options?.compactLog) {
|
|
1686
|
+
argv.push('--compact-log');
|
|
1687
|
+
}
|
|
1669
1688
|
const source = String(results.source ?? '').trim();
|
|
1670
1689
|
if (options?.verbose) {
|
|
1671
1690
|
argv.push('--verbose');
|
|
@@ -1718,11 +1737,8 @@ export default class Install extends Command {
|
|
|
1718
1737
|
async downloadManagedSource(params) {
|
|
1719
1738
|
const argv = Install.buildDownloadArgvFromResults(params.downloadResults, {
|
|
1720
1739
|
verbose: params.verbose,
|
|
1740
|
+
compactLog: true,
|
|
1721
1741
|
});
|
|
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
1742
|
return await this.config.runCommand('source:download', argv);
|
|
1727
1743
|
}
|
|
1728
1744
|
async downloadLocalApp(params) {
|
|
@@ -1784,7 +1800,7 @@ export default class Install extends Command {
|
|
|
1784
1800
|
rootResults: params.rootResults,
|
|
1785
1801
|
});
|
|
1786
1802
|
const args = ['start', '--quickstart', '--daemon'];
|
|
1787
|
-
|
|
1803
|
+
this.logDetail(`Stopping any existing local NocoBase process in ${params.projectRoot}`);
|
|
1788
1804
|
try {
|
|
1789
1805
|
await runNocoBaseCommand(['pm2', 'kill'], {
|
|
1790
1806
|
cwd: params.projectRoot,
|
|
@@ -1794,15 +1810,15 @@ export default class Install extends Command {
|
|
|
1794
1810
|
}
|
|
1795
1811
|
catch (error) {
|
|
1796
1812
|
const message = error instanceof Error ? error.message : String(error);
|
|
1797
|
-
|
|
1813
|
+
this.logDetail(`Skipped local process cleanup before start: ${message}`);
|
|
1798
1814
|
}
|
|
1799
|
-
|
|
1815
|
+
this.logDetail(`Starting local NocoBase app from ${params.projectRoot}`);
|
|
1800
1816
|
await runNocoBaseCommand(args, {
|
|
1801
1817
|
cwd: params.projectRoot,
|
|
1802
1818
|
env,
|
|
1803
1819
|
stdio: params.commandStdio ?? 'ignore',
|
|
1804
1820
|
});
|
|
1805
|
-
|
|
1821
|
+
this.logDetail(`Local app is starting at http://127.0.0.1:${env.APP_PORT}`);
|
|
1806
1822
|
return {
|
|
1807
1823
|
source: params.source,
|
|
1808
1824
|
projectRoot: params.projectRoot,
|
|
@@ -1876,35 +1892,29 @@ export default class Install extends Command {
|
|
|
1876
1892
|
const fetchImpl = options?.fetchImpl ?? fetch;
|
|
1877
1893
|
const startedAt = Date.now();
|
|
1878
1894
|
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));
|
|
1895
|
+
let lastLoggedStatus = '';
|
|
1896
|
+
printInfo('Waiting for NocoBase to become ready...');
|
|
1897
|
+
while (Date.now() - startedAt < timeoutMs) {
|
|
1898
|
+
const result = await Install.requestAppHealthCheck({
|
|
1899
|
+
healthCheckUrl,
|
|
1900
|
+
fetchImpl,
|
|
1901
|
+
requestTimeoutMs,
|
|
1902
|
+
});
|
|
1903
|
+
if (result.ok) {
|
|
1904
|
+
return;
|
|
1902
1905
|
}
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1906
|
+
lastMessage = result.message;
|
|
1907
|
+
const elapsedSeconds = Math.max(1, Math.floor((Date.now() - startedAt) / 1000));
|
|
1908
|
+
const statusLine = `Waiting for NocoBase to become ready... (${elapsedSeconds}s elapsed, last status: ${Install.formatHealthCheckMessage(lastMessage)})`;
|
|
1909
|
+
if (statusLine !== lastLoggedStatus) {
|
|
1910
|
+
printInfo(statusLine);
|
|
1911
|
+
lastLoggedStatus = statusLine;
|
|
1912
|
+
}
|
|
1913
|
+
const remainingMs = timeoutMs - (Date.now() - startedAt);
|
|
1914
|
+
if (remainingMs <= 0) {
|
|
1915
|
+
break;
|
|
1907
1916
|
}
|
|
1917
|
+
await Install.sleep(Math.min(intervalMs, remainingMs));
|
|
1908
1918
|
}
|
|
1909
1919
|
const logHint = options?.containerName
|
|
1910
1920
|
? ` You can inspect startup logs with: docker logs ${options.containerName}`
|
|
@@ -2049,7 +2059,14 @@ export default class Install extends Command {
|
|
|
2049
2059
|
},
|
|
2050
2060
|
yes,
|
|
2051
2061
|
});
|
|
2052
|
-
const
|
|
2062
|
+
const envAddPromptsForInstall = {
|
|
2063
|
+
...EnvAdd.prompts,
|
|
2064
|
+
apiBaseUrl: {
|
|
2065
|
+
...EnvAdd.prompts.apiBaseUrl,
|
|
2066
|
+
validate: undefined,
|
|
2067
|
+
},
|
|
2068
|
+
};
|
|
2069
|
+
const envAddResults = await runPromptCatalog(envAddPromptsForInstall, {
|
|
2053
2070
|
initialValues: {
|
|
2054
2071
|
apiBaseUrl: `http://127.0.0.1:${appResults.appPort ?? DEFAULT_INSTALL_APP_PORT}/api`,
|
|
2055
2072
|
},
|
|
@@ -2077,13 +2094,14 @@ export default class Install extends Command {
|
|
|
2077
2094
|
const parsed = {
|
|
2078
2095
|
...flags,
|
|
2079
2096
|
};
|
|
2097
|
+
setVerboseMode(Boolean(parsed.verbose));
|
|
2080
2098
|
const commandStdio = this.commandStdio(parsed.verbose);
|
|
2081
2099
|
if (!parsed['no-intro']) {
|
|
2082
|
-
|
|
2100
|
+
this.logStage('Set up NocoBase');
|
|
2083
2101
|
}
|
|
2084
2102
|
if (parsed.resume) {
|
|
2085
2103
|
const envLabel = Install.toOptionalPromptString(parsed.env);
|
|
2086
|
-
|
|
2104
|
+
printInfo(envLabel
|
|
2087
2105
|
? `Resuming setup for env "${envLabel}" from the saved workspace config`
|
|
2088
2106
|
: 'Resuming setup from the saved workspace config');
|
|
2089
2107
|
}
|
|
@@ -2100,6 +2118,9 @@ export default class Install extends Command {
|
|
|
2100
2118
|
: undefined;
|
|
2101
2119
|
await Install.ensureExternalDbReadyForInstall(dbResults);
|
|
2102
2120
|
if (!parsed.resume) {
|
|
2121
|
+
if (!parsed['skip-save-env-log']) {
|
|
2122
|
+
this.logStage('Saving env config');
|
|
2123
|
+
}
|
|
2103
2124
|
await this.saveInstalledEnv({
|
|
2104
2125
|
envName,
|
|
2105
2126
|
appResults,
|
|
@@ -2108,7 +2129,9 @@ export default class Install extends Command {
|
|
|
2108
2129
|
rootResults,
|
|
2109
2130
|
envAddResults,
|
|
2110
2131
|
});
|
|
2111
|
-
|
|
2132
|
+
if (!parsed['skip-save-env-log']) {
|
|
2133
|
+
printInfo(`Saved env config for "${envName}".`);
|
|
2134
|
+
}
|
|
2112
2135
|
}
|
|
2113
2136
|
let builtinDbPlan;
|
|
2114
2137
|
if (Boolean(dbResults.builtinDb)) {
|
|
@@ -2132,11 +2155,13 @@ export default class Install extends Command {
|
|
|
2132
2155
|
let dockerAppPlan;
|
|
2133
2156
|
let localAppPlan;
|
|
2134
2157
|
if (Boolean(appResults.fetchSource)) {
|
|
2158
|
+
this.logStage('Preparing application');
|
|
2135
2159
|
if (source === 'docker') {
|
|
2136
2160
|
await this.downloadManagedSource({
|
|
2137
2161
|
downloadResults,
|
|
2138
2162
|
verbose: parsed.verbose,
|
|
2139
2163
|
});
|
|
2164
|
+
printInfo('Application image ready.');
|
|
2140
2165
|
dockerAppPlan = await this.installDockerApp({
|
|
2141
2166
|
envName,
|
|
2142
2167
|
dockerNetworkName,
|
|
@@ -2160,6 +2185,7 @@ export default class Install extends Command {
|
|
|
2160
2185
|
downloadResults,
|
|
2161
2186
|
verbose: parsed.verbose,
|
|
2162
2187
|
});
|
|
2188
|
+
printInfo('Application files ready.');
|
|
2163
2189
|
localAppPlan = await this.startLocalApp({
|
|
2164
2190
|
envName,
|
|
2165
2191
|
source: localSource,
|
|
@@ -2174,15 +2200,17 @@ export default class Install extends Command {
|
|
|
2174
2200
|
}
|
|
2175
2201
|
}
|
|
2176
2202
|
else {
|
|
2177
|
-
|
|
2203
|
+
this.logDetail('Skipped app download and install.');
|
|
2178
2204
|
}
|
|
2179
2205
|
if (dockerAppPlan || localAppPlan) {
|
|
2206
|
+
this.logStage('Starting NocoBase');
|
|
2180
2207
|
await this.waitForAppHealthCheck(Install.resolveApiBaseUrl({
|
|
2181
2208
|
appResults,
|
|
2182
2209
|
envAddResults,
|
|
2183
2210
|
}), {
|
|
2184
2211
|
containerName: dockerAppPlan?.containerName,
|
|
2185
2212
|
});
|
|
2213
|
+
printInfo(`NocoBase is ready at http://127.0.0.1:${dockerAppPlan?.appPort ?? localAppPlan?.appPort}`);
|
|
2186
2214
|
}
|
|
2187
2215
|
if (dockerAppPlan || localAppPlan || builtinDbPlan) {
|
|
2188
2216
|
await this.saveInstalledEnv({
|
|
@@ -2199,9 +2227,9 @@ export default class Install extends Command {
|
|
|
2199
2227
|
envAddResults,
|
|
2200
2228
|
appReady: Boolean(dockerAppPlan || localAppPlan),
|
|
2201
2229
|
});
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2230
|
+
if (!dockerAppPlan && !localAppPlan) {
|
|
2231
|
+
printInfo(`Install config for "${envName}" has been saved.`);
|
|
2232
|
+
}
|
|
2205
2233
|
}
|
|
2206
2234
|
}
|
|
2207
2235
|
function downloadResultsValue(downloadResults, key) {
|
|
@@ -6,10 +6,10 @@
|
|
|
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 * as p from '@clack/prompts';
|
|
10
9
|
import { Command, Flags } from '@oclif/core';
|
|
11
10
|
import { readFile } from 'node:fs/promises';
|
|
12
11
|
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
12
|
+
import { input, password as promptPassword, select } from "../../lib/inquirer.js";
|
|
13
13
|
import { createLicenseEnvFlag, ensureInstanceId, licenseJsonFlag, licensePkgUrlFlag, licenseYesFlag, redactLicenseKey, requireLicenseRuntime, resolveLicenseKeyFile, resolveLicenseServiceUrl, saveLicenseKey, sanitizeLicenseOutput, validateLicenseKey, } from './shared.js';
|
|
14
14
|
import { announceTargetEnv, isInteractiveTerminal } from '../../lib/ui.js';
|
|
15
15
|
import { appUrl } from '../env/shared.js';
|
|
@@ -17,102 +17,107 @@ function resolveOnlineInputValue(value) {
|
|
|
17
17
|
return String(value ?? '').trim();
|
|
18
18
|
}
|
|
19
19
|
async function promptActivationMode() {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
20
|
+
try {
|
|
21
|
+
return await select({
|
|
22
|
+
message: 'How do you want to activate the license?',
|
|
23
|
+
choices: [
|
|
24
|
+
{ value: 'key', name: 'Use an existing license key' },
|
|
25
|
+
{ value: 'online', name: 'Request and activate a license online' },
|
|
26
|
+
{ value: 'cancel', name: 'Cancel' },
|
|
27
|
+
],
|
|
28
|
+
default: 'key',
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
31
32
|
return 'cancel';
|
|
32
33
|
}
|
|
33
|
-
return answer;
|
|
34
34
|
}
|
|
35
35
|
async function promptLicenseKeyInput() {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
36
|
+
let answer;
|
|
37
|
+
try {
|
|
38
|
+
answer = await select({
|
|
39
|
+
message: 'How do you want to provide the license key?',
|
|
40
|
+
choices: [
|
|
41
|
+
{ value: 'key', name: 'Paste the license key' },
|
|
42
|
+
{ value: 'file', name: 'Read the key from a file' },
|
|
43
|
+
],
|
|
44
|
+
default: 'key',
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
46
48
|
return {};
|
|
47
49
|
}
|
|
48
50
|
if (answer === 'key') {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
try {
|
|
52
|
+
const key = await input({
|
|
53
|
+
message: 'License key',
|
|
54
|
+
validate: (value) => String(value ?? '').trim() ? true : 'License key is required.',
|
|
55
|
+
});
|
|
56
|
+
return { key: String(key ?? '').trim() || undefined };
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
55
59
|
return {};
|
|
56
60
|
}
|
|
57
|
-
return { key: String(key ?? '').trim() || undefined };
|
|
58
61
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
62
|
+
try {
|
|
63
|
+
const keyFile = await input({
|
|
64
|
+
message: 'Path to the license key file',
|
|
65
|
+
validate: (value) => String(value ?? '').trim() ? true : 'License key file path is required.',
|
|
66
|
+
});
|
|
67
|
+
return { keyFile: String(keyFile ?? '').trim() || undefined };
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
65
70
|
return {};
|
|
66
71
|
}
|
|
67
|
-
return { keyFile: String(keyFile ?? '').trim() || undefined };
|
|
68
72
|
}
|
|
69
73
|
async function promptOnlineActivationInput(initial) {
|
|
70
74
|
let account = String(initial.account ?? '').trim();
|
|
71
75
|
if (!account) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
try {
|
|
77
|
+
const answer = await input({
|
|
78
|
+
message: 'Service account',
|
|
79
|
+
validate: (value) => String(value ?? '').trim() ? true : 'Service account is required.',
|
|
80
|
+
});
|
|
81
|
+
account = String(answer ?? '').trim();
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
78
84
|
return;
|
|
79
85
|
}
|
|
80
|
-
account = String(answer ?? '').trim();
|
|
81
86
|
}
|
|
82
87
|
if (!account) {
|
|
83
|
-
p.cancel('License activation cancelled.');
|
|
84
88
|
return;
|
|
85
89
|
}
|
|
86
90
|
let password = String(initial.password ?? '').trim();
|
|
87
91
|
if (!password) {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
try {
|
|
93
|
+
const answer = await promptPassword({
|
|
94
|
+
message: 'Service password',
|
|
95
|
+
mask: '•',
|
|
96
|
+
validate: (value) => String(value ?? '').trim() ? true : 'Service password is required.',
|
|
97
|
+
});
|
|
98
|
+
password = String(answer ?? '').trim();
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
94
101
|
return;
|
|
95
102
|
}
|
|
96
|
-
password = String(answer ?? '').trim();
|
|
97
103
|
}
|
|
98
104
|
if (!password) {
|
|
99
|
-
p.cancel('License activation cancelled.');
|
|
100
105
|
return;
|
|
101
106
|
}
|
|
102
107
|
let appName = String(initial.appName ?? '').trim();
|
|
103
108
|
if (!appName) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
try {
|
|
110
|
+
const answer = await input({
|
|
111
|
+
message: 'Application name',
|
|
112
|
+
validate: (value) => String(value ?? '').trim() ? true : 'Application name is required.',
|
|
113
|
+
});
|
|
114
|
+
appName = String(answer ?? '').trim();
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
110
117
|
return;
|
|
111
118
|
}
|
|
112
|
-
appName = String(answer ?? '').trim();
|
|
113
119
|
}
|
|
114
120
|
if (!appName) {
|
|
115
|
-
p.cancel('License activation cancelled.');
|
|
116
121
|
return;
|
|
117
122
|
}
|
|
118
123
|
return {
|
|
@@ -206,7 +211,6 @@ export default class LicenseActivate extends Command {
|
|
|
206
211
|
yes: flags.yes,
|
|
207
212
|
});
|
|
208
213
|
if (!confirmed) {
|
|
209
|
-
this.log('Canceled.');
|
|
210
214
|
return;
|
|
211
215
|
}
|
|
212
216
|
}
|
|
@@ -223,7 +227,6 @@ export default class LicenseActivate extends Command {
|
|
|
223
227
|
}
|
|
224
228
|
const mode = await promptActivationMode();
|
|
225
229
|
if (mode === 'cancel') {
|
|
226
|
-
this.log('Cancelled license activation.');
|
|
227
230
|
return;
|
|
228
231
|
}
|
|
229
232
|
if (mode === 'online') {
|
|
@@ -258,7 +261,6 @@ export default class LicenseActivate extends Command {
|
|
|
258
261
|
}
|
|
259
262
|
const prompted = await promptOnlineActivationInput(initialOnline);
|
|
260
263
|
if (!prompted) {
|
|
261
|
-
this.log('Cancelled license activation.');
|
|
262
264
|
return;
|
|
263
265
|
}
|
|
264
266
|
onlineInput = prompted;
|
|
@@ -10,12 +10,12 @@ import { Flags } from '@oclif/core';
|
|
|
10
10
|
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
import { getEnvAsync, getInstanceIdAsync, keyDecrypt } from '@nocobase/license-kit';
|
|
13
|
-
import _ from 'lodash';
|
|
14
13
|
import { checkExternalDbConnection, readExternalDbConnectionConfig, } from "../../lib/db-connection-check.js";
|
|
15
14
|
import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageRef, } from "../../lib/docker-image.js";
|
|
16
15
|
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime } from '../../lib/app-runtime.js';
|
|
17
16
|
import { buildRuntimeEnvVars } from '../../lib/runtime-env-vars.js';
|
|
18
17
|
import { resolveLicensePkgUrlFromConfig } from '../../lib/cli-config.js';
|
|
18
|
+
import { deepEqual, omitKeys } from "../../lib/object-utils.js";
|
|
19
19
|
import { commandOutput } from '../../lib/run-npm.js';
|
|
20
20
|
import { appUrl } from '../env/shared.js';
|
|
21
21
|
export function createLicenseEnvFlag(description) {
|
|
@@ -296,7 +296,7 @@ export function isDbMatch(env, keyData) {
|
|
|
296
296
|
if (currentDb?.id && licenseDb?.id) {
|
|
297
297
|
return currentDb.id === licenseDb.id;
|
|
298
298
|
}
|
|
299
|
-
return
|
|
299
|
+
return deepEqual(omitKeys(currentDb, ['id']), omitKeys(licenseDb, ['id']));
|
|
300
300
|
}
|
|
301
301
|
export function isSysMatch(env, keyData) {
|
|
302
302
|
const instance = keyData?.instanceData;
|
|
@@ -307,7 +307,7 @@ export function isSysMatch(env, keyData) {
|
|
|
307
307
|
sys: item?.sys ?? null,
|
|
308
308
|
osVer: item?.osVer ?? null,
|
|
309
309
|
});
|
|
310
|
-
return
|
|
310
|
+
return deepEqual(normalize(env), normalize(instance));
|
|
311
311
|
}
|
|
312
312
|
export async function getLicenseStatus(keyData) {
|
|
313
313
|
if (!keyData) {
|