@nocobase/cli 2.1.0-alpha.37 → 2.1.0-alpha.39
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/bin/run.js +2 -1
- package/bin/session-env.js +12 -0
- package/dist/commands/app/restart.js +63 -1
- package/dist/commands/app/start.js +53 -17
- package/dist/commands/app/upgrade.js +16 -3
- package/dist/commands/install.js +11 -2
- package/dist/commands/source/dev.js +8 -1
- package/dist/commands/source/publish.js +92 -0
- package/dist/commands/source/registry/logs.js +70 -0
- package/dist/commands/source/registry/start.js +57 -0
- package/dist/commands/source/registry/status.js +33 -0
- package/dist/commands/source/registry/stop.js +48 -0
- package/dist/lib/app-managed-resources.js +30 -3
- package/dist/lib/bootstrap.js +1 -1
- package/dist/lib/docker-env-file.js +52 -0
- package/dist/lib/env-config.js +1 -0
- package/dist/lib/run-npm.js +8 -9
- package/dist/lib/source-publish.js +287 -0
- package/dist/lib/source-registry.js +188 -0
- package/package.json +2 -2
package/bin/run.js
CHANGED
|
@@ -5,7 +5,7 @@ import fs from 'node:fs';
|
|
|
5
5
|
import { createRequire } from 'node:module';
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
8
|
-
import { normalizeSessionEnv } from './session-env.js';
|
|
8
|
+
import { normalizeNodeOptions, normalizeSessionEnv } from './session-env.js';
|
|
9
9
|
|
|
10
10
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
11
11
|
const requireFromCli = createRequire(import.meta.url);
|
|
@@ -18,6 +18,7 @@ if (process.env.NB_CLI_USE_DIST === '1') {
|
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
normalizeSessionEnv();
|
|
21
|
+
normalizeNodeOptions();
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* In the monorepo, plain `node` cannot load `.ts`. Re-exec once with `--import <tsx>`
|
package/bin/session-env.js
CHANGED
|
@@ -4,6 +4,7 @@ const SESSION_ENV_SOURCES = [
|
|
|
4
4
|
'COPILOT_AGENT_SESSION_ID',
|
|
5
5
|
'CLAUDE_CODE_SESSION_ID',
|
|
6
6
|
];
|
|
7
|
+
const PRESERVE_SYMLINKS_FLAG = '--preserve-symlinks';
|
|
7
8
|
|
|
8
9
|
export function resolveNormalizedSessionId(env = process.env) {
|
|
9
10
|
for (const key of SESSION_ENV_SOURCES) {
|
|
@@ -25,3 +26,14 @@ export function normalizeSessionEnv(env = process.env) {
|
|
|
25
26
|
env.NB_SESSION_ID = sessionId;
|
|
26
27
|
return sessionId;
|
|
27
28
|
}
|
|
29
|
+
|
|
30
|
+
export function normalizeNodeOptions(env = process.env) {
|
|
31
|
+
const currentNodeOptions = String(env.NODE_OPTIONS ?? '').trim();
|
|
32
|
+
const flags = currentNodeOptions ? currentNodeOptions.split(/\s+/) : [];
|
|
33
|
+
|
|
34
|
+
if (!flags.includes(PRESERVE_SYMLINKS_FLAG)) {
|
|
35
|
+
env.NODE_OPTIONS = [...flags, PRESERVE_SYMLINKS_FLAG].join(' ');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return env.NODE_OPTIONS;
|
|
39
|
+
}
|
|
@@ -8,6 +8,11 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Command, Flags } from '@oclif/core';
|
|
10
10
|
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
11
|
+
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, stopDockerContainer, } from '../../lib/app-runtime.js';
|
|
12
|
+
import { formatAppUrl, resolveManagedAppApiBaseUrl, waitForAppReady } from '../../lib/app-health.js';
|
|
13
|
+
import { recreateSavedDockerApp } from '../../lib/app-managed-resources.js';
|
|
14
|
+
import { run } from '../../lib/run-npm.js';
|
|
15
|
+
import { announceTargetEnv, failTask, startTask, succeedTask } from '../../lib/ui.js';
|
|
11
16
|
function argvHasToken(argv, tokens) {
|
|
12
17
|
return tokens.some((token) => argv.includes(token));
|
|
13
18
|
}
|
|
@@ -16,9 +21,17 @@ function pushFlag(argv, flag, value) {
|
|
|
16
21
|
argv.push(flag, String(value));
|
|
17
22
|
}
|
|
18
23
|
}
|
|
24
|
+
function formatDockerRestartFailure(envName, message) {
|
|
25
|
+
return [
|
|
26
|
+
`Couldn't restart NocoBase for "${envName}".`,
|
|
27
|
+
'The CLI was not able to recreate the saved Docker app container successfully.',
|
|
28
|
+
'Check the saved Docker image, envFile, container settings, and database connection, then try again.',
|
|
29
|
+
`Details: ${message}`,
|
|
30
|
+
].join('\n');
|
|
31
|
+
}
|
|
19
32
|
export default class AppRestart extends Command {
|
|
20
33
|
static hidden = false;
|
|
21
|
-
static description = 'Restart NocoBase for the selected env
|
|
34
|
+
static description = 'Restart NocoBase for the selected env. Local npm/git installs stop and start the app again, and Docker installs recreate the saved app container so saved env changes can take effect.';
|
|
22
35
|
static examples = [
|
|
23
36
|
'<%= config.bin %> <%= command.id %>',
|
|
24
37
|
'<%= config.bin %> <%= command.id %> --env local',
|
|
@@ -71,6 +84,55 @@ export default class AppRestart extends Command {
|
|
|
71
84
|
return;
|
|
72
85
|
}
|
|
73
86
|
}
|
|
87
|
+
const runtime = await resolveManagedAppRuntime(requestedEnv);
|
|
88
|
+
const commandStdio = flags.verbose ? 'inherit' : 'ignore';
|
|
89
|
+
if (!runtime) {
|
|
90
|
+
this.error(formatMissingManagedAppEnvMessage(requestedEnv));
|
|
91
|
+
}
|
|
92
|
+
if (runtime.kind === 'docker') {
|
|
93
|
+
announceTargetEnv(runtime.envName);
|
|
94
|
+
startTask(`Stopping NocoBase for "${runtime.envName}" before restart...`);
|
|
95
|
+
try {
|
|
96
|
+
const state = await stopDockerContainer(runtime.containerName, {
|
|
97
|
+
stdio: commandStdio,
|
|
98
|
+
});
|
|
99
|
+
succeedTask(state === 'already-stopped'
|
|
100
|
+
? `NocoBase was already stopped for "${runtime.envName}".`
|
|
101
|
+
: `Stopped NocoBase for "${runtime.envName}".`);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
105
|
+
failTask(`Failed to stop NocoBase for "${runtime.envName}".`);
|
|
106
|
+
this.error(formatDockerRestartFailure(runtime.envName, message));
|
|
107
|
+
}
|
|
108
|
+
startTask(`Recreating the Docker app container for "${runtime.envName}"...`);
|
|
109
|
+
try {
|
|
110
|
+
await run('docker', ['rm', '-f', runtime.containerName], {
|
|
111
|
+
errorName: 'docker rm',
|
|
112
|
+
stdio: commandStdio,
|
|
113
|
+
}).catch(() => undefined);
|
|
114
|
+
await recreateSavedDockerApp(runtime, {
|
|
115
|
+
verbose: flags.verbose,
|
|
116
|
+
});
|
|
117
|
+
succeedTask(`Docker app container is ready for "${runtime.envName}".`);
|
|
118
|
+
}
|
|
119
|
+
catch (error) {
|
|
120
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
121
|
+
failTask(`Failed to recreate NocoBase for "${runtime.envName}".`);
|
|
122
|
+
this.error(formatDockerRestartFailure(runtime.envName, message));
|
|
123
|
+
}
|
|
124
|
+
const appUrl = formatAppUrl(runtime.env.appPort === undefined || runtime.env.appPort === null
|
|
125
|
+
? undefined
|
|
126
|
+
: String(runtime.env.appPort));
|
|
127
|
+
await waitForAppReady({
|
|
128
|
+
envName: runtime.envName,
|
|
129
|
+
apiBaseUrl: resolveManagedAppApiBaseUrl(runtime),
|
|
130
|
+
containerName: runtime.containerName,
|
|
131
|
+
logHint: `You can inspect startup logs with \`nb app logs --env ${runtime.envName}\`.`,
|
|
132
|
+
});
|
|
133
|
+
succeedTask(`NocoBase is running for "${runtime.envName}"${appUrl ? ` at ${appUrl}` : ''}.`);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
74
136
|
const stopArgv = [];
|
|
75
137
|
const daemonFlagWasProvided = argvHasToken(this.argv, ['--daemon', '--no-daemon']);
|
|
76
138
|
pushFlag(stopArgv, '--env', requestedEnv);
|
|
@@ -10,7 +10,8 @@ import { Command, Flags } from '@oclif/core';
|
|
|
10
10
|
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
11
11
|
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runLocalNocoBaseCommand, startDockerContainer, } from '../../lib/app-runtime.js';
|
|
12
12
|
import { AppHealthCheckError, formatAppUrl, isAppReady, resolveManagedAppApiBaseUrl, waitForAppReady, } from '../../lib/app-health.js';
|
|
13
|
-
import { ensureBuiltinDbReady, ensureSavedLocalSource, recreateSavedDockerApp, } from '../../lib/app-managed-resources.js';
|
|
13
|
+
import { ensureBuiltinDbReady, ensureLocalPostinstall, ensureSavedLocalSource, recreateSavedDockerApp, } from '../../lib/app-managed-resources.js';
|
|
14
|
+
import { run } from '../../lib/run-npm.js';
|
|
14
15
|
import { announceTargetEnv, failTask, printInfo, startTask, succeedTask } from '../../lib/ui.js';
|
|
15
16
|
function argvHasToken(argv, tokens) {
|
|
16
17
|
return tokens.some((token) => argv.includes(token));
|
|
@@ -89,6 +90,10 @@ export default class AppStart extends Command {
|
|
|
89
90
|
description: 'Show raw startup output from the underlying local or Docker command',
|
|
90
91
|
default: false,
|
|
91
92
|
}),
|
|
93
|
+
recreate: Flags.boolean({
|
|
94
|
+
description: 'Recreate the saved Docker app container before starting it',
|
|
95
|
+
default: false,
|
|
96
|
+
}),
|
|
92
97
|
};
|
|
93
98
|
async run() {
|
|
94
99
|
const { flags } = await this.parse(AppStart);
|
|
@@ -149,29 +154,49 @@ export default class AppStart extends Command {
|
|
|
149
154
|
? undefined
|
|
150
155
|
: String(runtime.env.appPort));
|
|
151
156
|
const apiBaseUrl = resolveManagedAppApiBaseUrl(runtime);
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
catch (error) {
|
|
163
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
164
|
-
if (/does not exist/i.test(message)) {
|
|
165
|
-
printInfo(`The saved Docker app container for "${runtime.envName}" is missing. Recreating it from the saved Docker env settings...`);
|
|
157
|
+
if (flags.recreate) {
|
|
158
|
+
startTask(`Recreating the Docker app container for "${runtime.envName}"...`);
|
|
159
|
+
try {
|
|
160
|
+
await run('docker', ['rm', '-f', runtime.containerName], {
|
|
161
|
+
errorName: 'docker rm',
|
|
162
|
+
stdio: commandStdio,
|
|
163
|
+
}).catch(() => undefined);
|
|
166
164
|
await recreateSavedDockerApp(runtime, {
|
|
167
165
|
verbose: flags.verbose,
|
|
168
166
|
});
|
|
167
|
+
succeedTask(`Docker app container is ready for "${runtime.envName}".`);
|
|
169
168
|
}
|
|
170
|
-
|
|
171
|
-
|
|
169
|
+
catch (error) {
|
|
170
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
171
|
+
failTask(`Failed to recreate NocoBase for "${runtime.envName}".`);
|
|
172
172
|
this.error(formatDockerStartFailure(runtime.envName, message));
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
|
+
else {
|
|
176
|
+
startTask(`Starting NocoBase for "${runtime.envName}"...`);
|
|
177
|
+
try {
|
|
178
|
+
const state = await startDockerContainer(runtime.containerName, {
|
|
179
|
+
stdio: commandStdio,
|
|
180
|
+
});
|
|
181
|
+
if (state === 'already-running' && await isAppReady(apiBaseUrl)) {
|
|
182
|
+
succeedTask(`NocoBase is already running for "${runtime.envName}"${appUrl ? ` at ${appUrl}` : ''}.`);
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
188
|
+
if (/does not exist/i.test(message)) {
|
|
189
|
+
printInfo(`The saved Docker app container for "${runtime.envName}" is missing. Recreating it from the saved Docker env settings...`);
|
|
190
|
+
await recreateSavedDockerApp(runtime, {
|
|
191
|
+
verbose: flags.verbose,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
failTask(`Failed to start NocoBase for "${runtime.envName}".`);
|
|
196
|
+
this.error(formatDockerStartFailure(runtime.envName, message));
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
175
200
|
await waitForAppReady({
|
|
176
201
|
envName: runtime.envName,
|
|
177
202
|
apiBaseUrl,
|
|
@@ -233,6 +258,17 @@ export default class AppStart extends Command {
|
|
|
233
258
|
}
|
|
234
259
|
return;
|
|
235
260
|
}
|
|
261
|
+
try {
|
|
262
|
+
await ensureLocalPostinstall(runtime, {
|
|
263
|
+
verbose: flags.verbose,
|
|
264
|
+
onStartTask: startTask,
|
|
265
|
+
onSucceedTask: succeedTask,
|
|
266
|
+
onFailTask: failTask,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
catch (error) {
|
|
270
|
+
this.error(error instanceof Error ? error.message : String(error));
|
|
271
|
+
}
|
|
236
272
|
if (flags.daemon === false) {
|
|
237
273
|
printInfo(`Starting NocoBase for "${runtime.envName}" in the foreground${appUrl ? ` at ${appUrl}` : ''}. Press Ctrl+C to stop.`);
|
|
238
274
|
}
|
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Command, Flags } from '@oclif/core';
|
|
10
10
|
import { upsertEnv } from '../../lib/auth-store.js';
|
|
11
|
+
import { ensureLocalPostinstall } from '../../lib/app-managed-resources.js';
|
|
11
12
|
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runLocalNocoBaseCommand, startDockerContainer, stopDockerContainer, } from '../../lib/app-runtime.js';
|
|
12
13
|
import { resolveConfiguredEnvPath } from '../../lib/cli-home.js';
|
|
13
14
|
import { deriveBuiltinDbConnection } from '../../lib/builtin-db.js';
|
|
15
|
+
import { resolveDockerEnvFileArg } from "../../lib/docker-env-file.js";
|
|
14
16
|
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
15
17
|
import { DEFAULT_DOCKER_REGISTRY, DEFAULT_DOCKER_VERSION, resolveDockerImageRef, } from "../../lib/docker-image.js";
|
|
16
18
|
import { commandSucceeds, run } from '../../lib/run-npm.js';
|
|
@@ -311,12 +313,13 @@ export default class AppUpgrade extends Command {
|
|
|
311
313
|
argv.push('--daemon');
|
|
312
314
|
return argv;
|
|
313
315
|
}
|
|
314
|
-
static buildDockerUpgradePlan(runtime, downloadVersion) {
|
|
316
|
+
static async buildDockerUpgradePlan(runtime, downloadVersion) {
|
|
315
317
|
const dockerRegistry = readEnvValue(runtime.env, 'dockerRegistry') || DEFAULT_DOCKER_REGISTRY;
|
|
316
318
|
const appPort = runtime.env.appPort === undefined || runtime.env.appPort === null
|
|
317
319
|
? ''
|
|
318
320
|
: trimValue(runtime.env.appPort);
|
|
319
321
|
const storagePath = readEnvValue(runtime.env, 'storagePath');
|
|
322
|
+
const envFile = await resolveDockerEnvFileArg(runtime.envName, runtime.env.config);
|
|
320
323
|
const appKey = readEnvValue(runtime.env, 'appKey');
|
|
321
324
|
const timeZone = readEnvValue(runtime.env, 'timezone');
|
|
322
325
|
const builtinDbConnection = runtime.env.config.builtinDb ? deriveBuiltinDbConnection(runtime) : undefined;
|
|
@@ -378,6 +381,9 @@ export default class AppUpgrade extends Command {
|
|
|
378
381
|
if (appPort) {
|
|
379
382
|
args.push('-p', `${appPort}:80`);
|
|
380
383
|
}
|
|
384
|
+
if (envFile) {
|
|
385
|
+
args.push('--env-file', envFile);
|
|
386
|
+
}
|
|
381
387
|
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}:${DOCKER_APP_STORAGE_DESTINATION}`, imageRef);
|
|
382
388
|
return {
|
|
383
389
|
containerName: runtime.containerName,
|
|
@@ -387,6 +393,7 @@ export default class AppUpgrade extends Command {
|
|
|
387
393
|
imageRef,
|
|
388
394
|
appPort: appPort || undefined,
|
|
389
395
|
storagePath,
|
|
396
|
+
envFile,
|
|
390
397
|
appKey,
|
|
391
398
|
timeZone,
|
|
392
399
|
dbDialect,
|
|
@@ -430,6 +437,12 @@ export default class AppUpgrade extends Command {
|
|
|
430
437
|
else {
|
|
431
438
|
printInfo(`Skipping code download for "${runtime.envName}" because this env is managed from an existing local app path.`);
|
|
432
439
|
}
|
|
440
|
+
await ensureLocalPostinstall(runtime, {
|
|
441
|
+
verbose: flags.verbose,
|
|
442
|
+
onStartTask: startTask,
|
|
443
|
+
onSucceedTask: succeedTask,
|
|
444
|
+
onFailTask: failTask,
|
|
445
|
+
});
|
|
433
446
|
startTask(`Starting upgraded NocoBase for "${runtime.envName}"...`);
|
|
434
447
|
try {
|
|
435
448
|
await runLocalNocoBaseCommand(runtime, AppUpgrade.buildLocalStartArgv(runtime), {
|
|
@@ -452,7 +465,7 @@ export default class AppUpgrade extends Command {
|
|
|
452
465
|
const apiBaseUrl = resolveApiBaseUrl(runtime);
|
|
453
466
|
const containerExists = await dockerContainerExists(runtime.containerName);
|
|
454
467
|
if (!flags['skip-code-update']) {
|
|
455
|
-
const plan = AppUpgrade.buildDockerUpgradePlan(runtime, downloadVersion);
|
|
468
|
+
const plan = await AppUpgrade.buildDockerUpgradePlan(runtime, downloadVersion);
|
|
456
469
|
startTask(`Refreshing the Docker image for "${runtime.envName}"...`);
|
|
457
470
|
try {
|
|
458
471
|
await runCommand('source:download', AppUpgrade.buildDockerDownloadArgv(runtime, plan));
|
|
@@ -500,7 +513,7 @@ export default class AppUpgrade extends Command {
|
|
|
500
513
|
}
|
|
501
514
|
}
|
|
502
515
|
else {
|
|
503
|
-
const plan = AppUpgrade.buildDockerUpgradePlan(runtime, downloadVersion);
|
|
516
|
+
const plan = await AppUpgrade.buildDockerUpgradePlan(runtime, downloadVersion);
|
|
504
517
|
const displayUrl = formatDisplayUrl(apiBaseUrl, plan.appPort);
|
|
505
518
|
startTask(`Recreating the Docker app container for "${runtime.envName}"...`);
|
|
506
519
|
try {
|
package/dist/commands/install.js
CHANGED
|
@@ -26,6 +26,7 @@ import { printInfo, printStage, printVerbose, printWarning, setVerboseMode, } fr
|
|
|
26
26
|
import { omitKeys, upperFirst } from "../lib/object-utils.js";
|
|
27
27
|
import { getEnv, setCurrentEnv, upsertEnv } from '../lib/auth-store.js';
|
|
28
28
|
import { buildStoredEnvConfig } from '../lib/env-config.js';
|
|
29
|
+
import { resolveDockerEnvFileArg, } from "../lib/docker-env-file.js";
|
|
29
30
|
import Download, { defaultDockerRegistryForLang, } from './download.js';
|
|
30
31
|
import EnvAdd from "./env/add.js";
|
|
31
32
|
const DEFAULT_INSTALL_ENV_NAME = 'local';
|
|
@@ -1575,7 +1576,7 @@ export default class Install extends Command {
|
|
|
1575
1576
|
printVerbose(`Built-in ${plan.dbDialect} database ready at ${plan.dbHost}:${plan.dbPort}`);
|
|
1576
1577
|
return plan;
|
|
1577
1578
|
}
|
|
1578
|
-
static buildDockerAppPlan(params) {
|
|
1579
|
+
static async buildDockerAppPlan(params) {
|
|
1579
1580
|
const dockerRegistry = String(downloadResultsValue(params.downloadResults, 'dockerRegistry') ?? '').trim()
|
|
1580
1581
|
|| defaultDockerRegistryForLang(process.env.NB_LOCALE);
|
|
1581
1582
|
const version = String(downloadResultsValue(params.downloadResults, 'version') ?? '').trim() || DEFAULT_DOCKER_VERSION;
|
|
@@ -1598,6 +1599,8 @@ export default class Install extends Command {
|
|
|
1598
1599
|
const appKey = crypto.randomBytes(32).toString('hex');
|
|
1599
1600
|
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
|
|
1600
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);
|
|
1601
1604
|
const initEnvVars = Install.buildInitAppEnvVars({
|
|
1602
1605
|
appResults: params.appResults,
|
|
1603
1606
|
rootResults: params.rootResults,
|
|
@@ -1614,6 +1617,9 @@ export default class Install extends Command {
|
|
|
1614
1617
|
'-p',
|
|
1615
1618
|
`${appPort}:80`,
|
|
1616
1619
|
];
|
|
1620
|
+
if (envFile) {
|
|
1621
|
+
args.push('--env-file', envFile);
|
|
1622
|
+
}
|
|
1617
1623
|
for (const [key, value] of Object.entries(initEnvVars)) {
|
|
1618
1624
|
args.push('-e', `${key}=${value}`);
|
|
1619
1625
|
}
|
|
@@ -1625,6 +1631,7 @@ export default class Install extends Command {
|
|
|
1625
1631
|
imageRef,
|
|
1626
1632
|
appPort,
|
|
1627
1633
|
storagePath,
|
|
1634
|
+
envFile,
|
|
1628
1635
|
appKey,
|
|
1629
1636
|
timeZone,
|
|
1630
1637
|
args,
|
|
@@ -1647,7 +1654,7 @@ export default class Install extends Command {
|
|
|
1647
1654
|
const networkName = params.builtinDbPlan?.networkName
|
|
1648
1655
|
?? Install.buildBuiltinDbNetworkName(params.envName, params.dockerNetworkName ?? params.workspaceName);
|
|
1649
1656
|
await this.ensureDockerNetwork(networkName);
|
|
1650
|
-
const plan = Install.buildDockerAppPlan({
|
|
1657
|
+
const plan = await Install.buildDockerAppPlan({
|
|
1651
1658
|
envName: params.envName,
|
|
1652
1659
|
workspaceName: params.workspaceName,
|
|
1653
1660
|
dockerContainerPrefix: params.dockerContainerPrefix,
|
|
@@ -1941,6 +1948,7 @@ export default class Install extends Command {
|
|
|
1941
1948
|
|| DEFAULT_INSTALL_APP_PORT;
|
|
1942
1949
|
const storagePath = String(params.appResults.storagePath ?? '').trim()
|
|
1943
1950
|
|| defaultInstallStoragePath(params.envName);
|
|
1951
|
+
const envFile = String(params.appResults.envFile ?? '').trim() || undefined;
|
|
1944
1952
|
const apiBaseUrl = Install.resolveApiBaseUrl({
|
|
1945
1953
|
appResults: params.appResults,
|
|
1946
1954
|
envAddResults: params.envAddResults,
|
|
@@ -1963,6 +1971,7 @@ export default class Install extends Command {
|
|
|
1963
1971
|
appRootPath: params.appResults.appRootPath,
|
|
1964
1972
|
appPort,
|
|
1965
1973
|
storagePath,
|
|
1974
|
+
...(envFile ? { envFile } : {}),
|
|
1966
1975
|
appKey: params.appResults.appKey,
|
|
1967
1976
|
timezone: params.appResults.timeZone,
|
|
1968
1977
|
builtinDb: params.dbResults.builtinDb,
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Command, Flags } from '@oclif/core';
|
|
10
10
|
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
11
|
+
import { ensureLocalPostinstall } from '../../lib/app-managed-resources.js';
|
|
11
12
|
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runLocalNocoBaseCommand, } from '../../lib/app-runtime.js';
|
|
12
|
-
import { announceTargetEnv, printInfo } from '../../lib/ui.js';
|
|
13
|
+
import { announceTargetEnv, failTask, printInfo, startTask, succeedTask } from '../../lib/ui.js';
|
|
13
14
|
function formatUnsupportedRuntimeMessage(kind, envName) {
|
|
14
15
|
if (kind === 'docker') {
|
|
15
16
|
return [
|
|
@@ -159,6 +160,12 @@ export default class SourceDev extends Command {
|
|
|
159
160
|
}
|
|
160
161
|
printInfo(`Starting NocoBase dev mode for "${runtime.envName}" from ${runtime.projectRoot}. Press Ctrl+C to stop.`);
|
|
161
162
|
try {
|
|
163
|
+
await ensureLocalPostinstall(runtime, {
|
|
164
|
+
onStartTask: startTask,
|
|
165
|
+
onSucceedTask: succeedTask,
|
|
166
|
+
onFailTask: failTask,
|
|
167
|
+
verbose: true,
|
|
168
|
+
});
|
|
162
169
|
await runLocalNocoBaseCommand(runtime, npmArgs, {
|
|
163
170
|
stdio: 'inherit',
|
|
164
171
|
});
|
|
@@ -0,0 +1,92 @@
|
|
|
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
|
+
import { Command, Flags } from '@oclif/core';
|
|
10
|
+
import { buildSuggestedInitCommand, publishSourceSnapshot } from '../../lib/source-publish.js';
|
|
11
|
+
import { failTask, printInfo, startTask, succeedTask } from '../../lib/ui.js';
|
|
12
|
+
function formatPublishFailure(message) {
|
|
13
|
+
return [
|
|
14
|
+
'Couldn\'t publish a source snapshot.',
|
|
15
|
+
'Check that Docker is running, the target npm registry is reachable, and the current directory is a NocoBase source repo.',
|
|
16
|
+
`Details: ${message}`,
|
|
17
|
+
].join('\n');
|
|
18
|
+
}
|
|
19
|
+
export default class SourcePublish extends Command {
|
|
20
|
+
static description = 'Publish the current NocoBase source repo as a snapshot version to an npm registry for install testing.';
|
|
21
|
+
static examples = [
|
|
22
|
+
'<%= config.bin %> <%= command.id %> --snapshot',
|
|
23
|
+
'<%= config.bin %> <%= command.id %> --snapshot --cwd /path/to/nocobase/source',
|
|
24
|
+
'<%= config.bin %> <%= command.id %> --snapshot --npm-registry=http://127.0.0.1:4873',
|
|
25
|
+
'<%= config.bin %> <%= command.id %> --snapshot --json',
|
|
26
|
+
];
|
|
27
|
+
static flags = {
|
|
28
|
+
snapshot: Flags.boolean({
|
|
29
|
+
description: 'Publish the current source repo as a unique snapshot version',
|
|
30
|
+
required: true,
|
|
31
|
+
default: false,
|
|
32
|
+
}),
|
|
33
|
+
'npm-registry': Flags.string({
|
|
34
|
+
description: 'npm registry URL to publish to. Defaults to the running local source registry when available',
|
|
35
|
+
required: false,
|
|
36
|
+
}),
|
|
37
|
+
cwd: Flags.string({
|
|
38
|
+
description: 'Source repository path. Defaults to the nearest detected NocoBase source root from the current working directory',
|
|
39
|
+
required: false,
|
|
40
|
+
}),
|
|
41
|
+
json: Flags.boolean({
|
|
42
|
+
description: 'Print the publish result as JSON',
|
|
43
|
+
default: false,
|
|
44
|
+
}),
|
|
45
|
+
verbose: Flags.boolean({
|
|
46
|
+
description: 'Show detailed command output while versioning and publishing the snapshot',
|
|
47
|
+
default: false,
|
|
48
|
+
}),
|
|
49
|
+
};
|
|
50
|
+
async run() {
|
|
51
|
+
const { flags } = await this.parse(SourcePublish);
|
|
52
|
+
if (!flags.snapshot) {
|
|
53
|
+
this.error('`nb source publish` currently requires `--snapshot`.');
|
|
54
|
+
}
|
|
55
|
+
if (!flags.json) {
|
|
56
|
+
startTask('Publishing a source snapshot...');
|
|
57
|
+
}
|
|
58
|
+
try {
|
|
59
|
+
const result = await publishSourceSnapshot({
|
|
60
|
+
cwd: flags.cwd,
|
|
61
|
+
npmRegistry: flags['npm-registry'],
|
|
62
|
+
verbose: flags.verbose,
|
|
63
|
+
});
|
|
64
|
+
if (flags.json) {
|
|
65
|
+
this.log(JSON.stringify({
|
|
66
|
+
version: result.version,
|
|
67
|
+
npmRegistry: result.npmRegistry,
|
|
68
|
+
gitSha: result.gitSha,
|
|
69
|
+
projectRoot: result.projectRoot,
|
|
70
|
+
suggestedInitCommand: buildSuggestedInitCommand(result),
|
|
71
|
+
}, null, 2));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
succeedTask(`Published source snapshot ${result.version} to ${result.npmRegistry}.`);
|
|
75
|
+
printInfo(`Source root: ${result.projectRoot}`);
|
|
76
|
+
printInfo(`Snapshot version: ${result.version}`);
|
|
77
|
+
printInfo(`npm registry: ${result.npmRegistry}`);
|
|
78
|
+
printInfo(`Next: ${buildSuggestedInitCommand(result)}`);
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
82
|
+
if (flags.json) {
|
|
83
|
+
this.logToStderr(JSON.stringify({
|
|
84
|
+
error: formatPublishFailure(message),
|
|
85
|
+
}, null, 2));
|
|
86
|
+
this.exit(1);
|
|
87
|
+
}
|
|
88
|
+
failTask('Failed to publish the source snapshot.');
|
|
89
|
+
this.error(formatPublishFailure(message));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
import { Command, Flags } from '@oclif/core';
|
|
10
|
+
import { run } from '../../../lib/run-npm.js';
|
|
11
|
+
import { printInfo } from '../../../lib/ui.js';
|
|
12
|
+
import { getSourceRegistryInfo } from '../../../lib/source-registry.js';
|
|
13
|
+
function formatLogsFailure(message) {
|
|
14
|
+
if (/does not exist/i.test(message)) {
|
|
15
|
+
return [
|
|
16
|
+
'Can\'t show source registry logs yet.',
|
|
17
|
+
'The saved source registry container could not be found on this machine.',
|
|
18
|
+
'Start the registry first with `nb source registry start`.',
|
|
19
|
+
`Details: ${message}`,
|
|
20
|
+
].join('\n');
|
|
21
|
+
}
|
|
22
|
+
return [
|
|
23
|
+
'Couldn\'t show source registry logs.',
|
|
24
|
+
'Check that Docker is installed and the source registry container still exists, then try again.',
|
|
25
|
+
`Details: ${message}`,
|
|
26
|
+
].join('\n');
|
|
27
|
+
}
|
|
28
|
+
export default class SourceRegistryLogs extends Command {
|
|
29
|
+
static description = 'Show logs for the local Docker-based npm registry used for source tests.';
|
|
30
|
+
static examples = [
|
|
31
|
+
'<%= config.bin %> <%= command.id %>',
|
|
32
|
+
'<%= config.bin %> <%= command.id %> --tail 200',
|
|
33
|
+
'<%= config.bin %> <%= command.id %> --follow',
|
|
34
|
+
];
|
|
35
|
+
static flags = {
|
|
36
|
+
tail: Flags.integer({
|
|
37
|
+
description: 'Number of recent log lines to show before following',
|
|
38
|
+
default: 100,
|
|
39
|
+
min: 0,
|
|
40
|
+
}),
|
|
41
|
+
follow: Flags.boolean({
|
|
42
|
+
char: 'f',
|
|
43
|
+
description: 'Keep streaming new log lines',
|
|
44
|
+
default: false,
|
|
45
|
+
allowNo: true,
|
|
46
|
+
}),
|
|
47
|
+
};
|
|
48
|
+
async run() {
|
|
49
|
+
const { flags } = await this.parse(SourceRegistryLogs);
|
|
50
|
+
const info = getSourceRegistryInfo();
|
|
51
|
+
printInfo(flags.follow
|
|
52
|
+
? `Showing source registry logs from "${info.containerName}" (press Ctrl+C to stop).`
|
|
53
|
+
: `Showing recent source registry logs from "${info.containerName}".`);
|
|
54
|
+
const dockerArgs = ['logs', '--tail', String(flags.tail ?? 100)];
|
|
55
|
+
if (flags.follow) {
|
|
56
|
+
dockerArgs.push('--follow');
|
|
57
|
+
}
|
|
58
|
+
dockerArgs.push(info.containerName);
|
|
59
|
+
try {
|
|
60
|
+
await run('docker', dockerArgs, {
|
|
61
|
+
errorName: 'docker logs',
|
|
62
|
+
stdio: 'inherit',
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
67
|
+
this.error(formatLogsFailure(message));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
import { Command, Flags } from '@oclif/core';
|
|
10
|
+
import { failTask, startTask, succeedTask } from '../../../lib/ui.js';
|
|
11
|
+
import { getSourceRegistryInfo, startSourceRegistry } from '../../../lib/source-registry.js';
|
|
12
|
+
function formatStartFailure(message) {
|
|
13
|
+
if (/port is already allocated|address already in use/i.test(message)) {
|
|
14
|
+
return [
|
|
15
|
+
'Can\'t start the source registry.',
|
|
16
|
+
'Port 4873 is already in use on this machine.',
|
|
17
|
+
'Stop the conflicting process, or free the port before trying again.',
|
|
18
|
+
`Details: ${message}`,
|
|
19
|
+
].join('\n');
|
|
20
|
+
}
|
|
21
|
+
return [
|
|
22
|
+
'Couldn\'t start the source registry.',
|
|
23
|
+
'Check that Docker is installed and running, then try again.',
|
|
24
|
+
`Details: ${message}`,
|
|
25
|
+
].join('\n');
|
|
26
|
+
}
|
|
27
|
+
export default class SourceRegistryStart extends Command {
|
|
28
|
+
static description = 'Start the local Docker-based npm registry used for source snapshot publish and install tests.';
|
|
29
|
+
static examples = [
|
|
30
|
+
'<%= config.bin %> <%= command.id %>',
|
|
31
|
+
'<%= config.bin %> <%= command.id %> --verbose',
|
|
32
|
+
];
|
|
33
|
+
static flags = {
|
|
34
|
+
verbose: Flags.boolean({
|
|
35
|
+
description: 'Show raw Docker output while starting the registry container',
|
|
36
|
+
default: false,
|
|
37
|
+
}),
|
|
38
|
+
};
|
|
39
|
+
async run() {
|
|
40
|
+
const { flags } = await this.parse(SourceRegistryStart);
|
|
41
|
+
const info = getSourceRegistryInfo();
|
|
42
|
+
startTask(`Starting the source registry at ${info.url}...`);
|
|
43
|
+
try {
|
|
44
|
+
const state = await startSourceRegistry({
|
|
45
|
+
stdio: flags.verbose ? 'inherit' : 'ignore',
|
|
46
|
+
});
|
|
47
|
+
succeedTask(state === 'already-running'
|
|
48
|
+
? `The source registry is already running at ${info.url}.`
|
|
49
|
+
: `The source registry is running at ${info.url}.`);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
53
|
+
failTask('Failed to start the source registry.');
|
|
54
|
+
this.error(formatStartFailure(message));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
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
|
+
import { Command, Flags } from '@oclif/core';
|
|
10
|
+
import { renderTable } from '../../../lib/ui.js';
|
|
11
|
+
import { resolveSourceRegistryInfo } from '../../../lib/source-registry.js';
|
|
12
|
+
export default class SourceRegistryStatus extends Command {
|
|
13
|
+
static description = 'Show the status of the local Docker-based npm registry used for source tests.';
|
|
14
|
+
static examples = [
|
|
15
|
+
'<%= config.bin %> <%= command.id %>',
|
|
16
|
+
'<%= config.bin %> <%= command.id %> --json',
|
|
17
|
+
];
|
|
18
|
+
static flags = {
|
|
19
|
+
json: Flags.boolean({
|
|
20
|
+
description: 'Print the source registry status as JSON',
|
|
21
|
+
default: false,
|
|
22
|
+
}),
|
|
23
|
+
};
|
|
24
|
+
async run() {
|
|
25
|
+
const { flags } = await this.parse(SourceRegistryStatus);
|
|
26
|
+
const info = await resolveSourceRegistryInfo();
|
|
27
|
+
if (flags.json) {
|
|
28
|
+
this.log(JSON.stringify(info, null, 2));
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
this.log(renderTable(['Container', 'Status', 'URL', 'Storage'], [[info.containerName, info.status, info.url, info.storageDir]]));
|
|
32
|
+
}
|
|
33
|
+
}
|