@nocobase/cli 2.1.0-beta.34 → 2.1.0-beta.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.
@@ -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,109 @@
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
+ if (message.includes('The specified --cwd does not exist:')
14
+ || message.includes('The specified --cwd is not a directory:')
15
+ || message.includes('Couldn\'t find a NocoBase source project from --cwd:')) {
16
+ return message;
17
+ }
18
+ return [
19
+ 'Couldn\'t publish a source snapshot.',
20
+ 'Check that Docker is running, the target npm registry is reachable, and the current directory is a NocoBase source repo.',
21
+ `Details: ${message}`,
22
+ ].join('\n');
23
+ }
24
+ export default class SourcePublish extends Command {
25
+ static description = 'Publish the current NocoBase source repo as a snapshot version to an npm registry for install testing.';
26
+ static examples = [
27
+ '<%= config.bin %> <%= command.id %> --snapshot',
28
+ '<%= config.bin %> <%= command.id %> --snapshot --no-build',
29
+ '<%= config.bin %> <%= command.id %> --snapshot --build-dts',
30
+ '<%= config.bin %> <%= command.id %> --snapshot --cwd /path/to/nocobase/source',
31
+ '<%= config.bin %> <%= command.id %> --snapshot --npm-registry=http://127.0.0.1:4873',
32
+ '<%= config.bin %> <%= command.id %> --snapshot --json',
33
+ ];
34
+ static flags = {
35
+ snapshot: Flags.boolean({
36
+ description: 'Publish the current source repo as a unique snapshot version',
37
+ required: true,
38
+ default: false,
39
+ }),
40
+ 'npm-registry': Flags.string({
41
+ description: 'npm registry URL to publish to. Defaults to the running local source registry when available',
42
+ required: false,
43
+ }),
44
+ cwd: Flags.string({
45
+ description: 'Source repository path. Defaults to the nearest detected NocoBase source root from the current working directory',
46
+ required: false,
47
+ }),
48
+ 'no-build': Flags.boolean({
49
+ description: 'Skip building the source repo before snapshot versioning and publish',
50
+ default: false,
51
+ }),
52
+ 'build-dts': Flags.boolean({
53
+ description: 'Generate TypeScript declaration files during the source build',
54
+ default: false,
55
+ }),
56
+ json: Flags.boolean({
57
+ description: 'Print the publish result as JSON',
58
+ default: false,
59
+ }),
60
+ verbose: Flags.boolean({
61
+ description: 'Show detailed command output while versioning and publishing the snapshot',
62
+ default: false,
63
+ }),
64
+ };
65
+ async run() {
66
+ const { flags } = await this.parse(SourcePublish);
67
+ if (!flags.snapshot) {
68
+ this.error('`nb source publish` currently requires `--snapshot`.');
69
+ }
70
+ if (!flags.json) {
71
+ startTask('Publishing a source snapshot...');
72
+ }
73
+ try {
74
+ const result = await publishSourceSnapshot({
75
+ cwd: flags.cwd,
76
+ npmRegistry: flags['npm-registry'],
77
+ build: !flags['no-build'],
78
+ buildDts: flags['build-dts'],
79
+ verbose: flags.verbose,
80
+ });
81
+ if (flags.json) {
82
+ this.log(JSON.stringify({
83
+ version: result.version,
84
+ npmRegistry: result.npmRegistry,
85
+ gitSha: result.gitSha,
86
+ projectRoot: result.projectRoot,
87
+ suggestedInitCommand: buildSuggestedInitCommand(result),
88
+ }, null, 2));
89
+ return;
90
+ }
91
+ succeedTask(`Published source snapshot ${result.version} to ${result.npmRegistry}.`);
92
+ printInfo(`Source root: ${result.projectRoot}`);
93
+ printInfo(`Snapshot version: ${result.version}`);
94
+ printInfo(`npm registry: ${result.npmRegistry}`);
95
+ printInfo(`Next: ${buildSuggestedInitCommand(result)}`);
96
+ }
97
+ catch (error) {
98
+ const message = error instanceof Error ? error.message : String(error);
99
+ if (flags.json) {
100
+ this.logToStderr(JSON.stringify({
101
+ error: formatPublishFailure(message),
102
+ }, null, 2));
103
+ this.exit(1);
104
+ }
105
+ failTask('Failed to publish the source snapshot.');
106
+ this.error(formatPublishFailure(message));
107
+ }
108
+ }
109
+ }
@@ -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
+ }
@@ -0,0 +1,48 @@
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 { stopSourceRegistry } from '../../../lib/source-registry.js';
12
+ function formatStopFailure(message) {
13
+ return [
14
+ 'Couldn\'t stop the source registry.',
15
+ 'Check that Docker is installed and the saved registry container still exists, then try again.',
16
+ `Details: ${message}`,
17
+ ].join('\n');
18
+ }
19
+ export default class SourceRegistryStop extends Command {
20
+ static description = 'Stop the local Docker-based npm registry used for source snapshot tests.';
21
+ static examples = [
22
+ '<%= config.bin %> <%= command.id %>',
23
+ '<%= config.bin %> <%= command.id %> --verbose',
24
+ ];
25
+ static flags = {
26
+ verbose: Flags.boolean({
27
+ description: 'Show raw Docker output while stopping the registry container',
28
+ default: false,
29
+ }),
30
+ };
31
+ async run() {
32
+ const { flags } = await this.parse(SourceRegistryStop);
33
+ startTask('Stopping the source registry...');
34
+ try {
35
+ const state = await stopSourceRegistry({
36
+ stdio: flags.verbose ? 'inherit' : 'ignore',
37
+ });
38
+ succeedTask(state === 'already-stopped'
39
+ ? 'The source registry is already stopped.'
40
+ : 'The source registry has stopped.');
41
+ }
42
+ catch (error) {
43
+ const message = error instanceof Error ? error.message : String(error);
44
+ failTask('Failed to stop the source registry.');
45
+ this.error(formatStopFailure(message));
46
+ }
47
+ }
48
+ }
@@ -0,0 +1,210 @@
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 } from '@oclif/core';
10
+ import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runDockerNocoBaseCommand, runLocalNocoBaseCommand, } from '../lib/app-runtime.js';
11
+ import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../lib/env-guard.js';
12
+ import { announceTargetEnv } from '../lib/ui.js';
13
+ const SILENT_LIKE_PASSTHROUGH_FLAGS = new Set(['--help', '-h', '--silent']);
14
+ const SILENT_RUNTIME_ENV_VARS = {
15
+ LOGGER_SILENT: 'true',
16
+ NODE_NO_WARNINGS: '1',
17
+ };
18
+ const SILENT_STDERR_FILTERS = [
19
+ /^\(node:\d+\) \[DEP0040\] DeprecationWarning: The `punycode` module is deprecated\..*$/,
20
+ /^\(Use `node --trace-deprecation .*$/,
21
+ /^About to overwrite ArrayBuffer\.prototype properties /,
22
+ ];
23
+ function parseBridgeArgv(argv) {
24
+ let requestedEnv;
25
+ let yes = false;
26
+ const passthroughArgs = [];
27
+ for (let index = 0; index < argv.length; index += 1) {
28
+ const token = argv[index];
29
+ if (token === '--') {
30
+ passthroughArgs.push(...argv.slice(index + 1));
31
+ break;
32
+ }
33
+ if (token === '--env') {
34
+ const value = argv[index + 1];
35
+ if (!value || value === '--') {
36
+ throw new Error('Missing value for `--env`.');
37
+ }
38
+ requestedEnv = value.trim() || undefined;
39
+ index += 1;
40
+ continue;
41
+ }
42
+ if (token.startsWith('--env=')) {
43
+ requestedEnv = token.slice('--env='.length).trim() || undefined;
44
+ continue;
45
+ }
46
+ if (token === '-e') {
47
+ const value = argv[index + 1];
48
+ if (!value || value === '--') {
49
+ throw new Error('Missing value for `-e`.');
50
+ }
51
+ requestedEnv = value.trim() || undefined;
52
+ index += 1;
53
+ continue;
54
+ }
55
+ if (token.startsWith('-e') && token.length > 2) {
56
+ requestedEnv = token.slice(2).trim() || undefined;
57
+ continue;
58
+ }
59
+ if (token === '--yes') {
60
+ yes = true;
61
+ continue;
62
+ }
63
+ passthroughArgs.push(...argv.slice(index));
64
+ break;
65
+ }
66
+ return {
67
+ requestedEnv,
68
+ yes,
69
+ passthroughArgs,
70
+ };
71
+ }
72
+ function formatHttpEnvError(envName) {
73
+ return [
74
+ `Can't run \`nb v1\` for "${envName}" yet.`,
75
+ 'This env only has an API connection, so the v1 bridge is not available here.',
76
+ 'Use a local or Docker env instead.',
77
+ ].join('\n');
78
+ }
79
+ function formatSshEnvError(envName) {
80
+ return [
81
+ `Can't run \`nb v1\` for "${envName}" yet.`,
82
+ 'SSH env support is reserved but not implemented yet.',
83
+ 'Use a local or Docker env right now.',
84
+ ].join('\n');
85
+ }
86
+ function hasSilentLikePassthrough(args) {
87
+ return args.some((arg) => SILENT_LIKE_PASSTHROUGH_FLAGS.has(arg));
88
+ }
89
+ function shouldFilterSilentStderrLine(line) {
90
+ const normalized = line.replace(/\r$/, '');
91
+ return SILENT_STDERR_FILTERS.some((pattern) => pattern.test(normalized));
92
+ }
93
+ function createSilentBridgeOptions() {
94
+ let pendingStderr = '';
95
+ const flushBufferedStderr = (force) => {
96
+ while (true) {
97
+ const newlineIndex = pendingStderr.indexOf('\n');
98
+ if (newlineIndex === -1) {
99
+ break;
100
+ }
101
+ const line = pendingStderr.slice(0, newlineIndex);
102
+ pendingStderr = pendingStderr.slice(newlineIndex + 1);
103
+ if (!shouldFilterSilentStderrLine(line)) {
104
+ process.stderr.write(`${line}\n`);
105
+ }
106
+ }
107
+ if (force && pendingStderr) {
108
+ if (!shouldFilterSilentStderrLine(pendingStderr)) {
109
+ process.stderr.write(pendingStderr);
110
+ }
111
+ pendingStderr = '';
112
+ }
113
+ };
114
+ return {
115
+ commandOptions: {
116
+ stdio: 'pipe',
117
+ env: {
118
+ ...SILENT_RUNTIME_ENV_VARS,
119
+ },
120
+ onStdout: (chunk) => {
121
+ process.stdout.write(chunk);
122
+ },
123
+ onStderr: (chunk) => {
124
+ pendingStderr += chunk;
125
+ flushBufferedStderr(false);
126
+ },
127
+ },
128
+ flush: () => {
129
+ flushBufferedStderr(true);
130
+ },
131
+ };
132
+ }
133
+ export default class V1 extends Command {
134
+ static hidden = true;
135
+ static strict = false;
136
+ static summary = 'Forward commands to the selected env through the v1 bridge';
137
+ static description = 'Forward v1-compatible commands to the selected env. Defaults to the current env when `--env` is omitted. Local envs run `nocobase-v1`, and Docker envs run inside the saved app container. Bridge flags (`--env`, `--yes`) must appear before the forwarded command. Use `--` when the forwarded command needs the same flag names.';
138
+ static examples = [
139
+ '<%= config.bin %> <%= command.id %> build',
140
+ '<%= config.bin %> <%= command.id %> --env local pm list',
141
+ '<%= config.bin %> <%= command.id %> --env docker-local -- pm enable @nocobase/plugin-sample --yes',
142
+ ];
143
+ async run() {
144
+ const originalArgv = [...this.argv];
145
+ await this.parse({ strict: false, flags: {}, args: {} }, []);
146
+ this.argv = originalArgv;
147
+ let parsed;
148
+ try {
149
+ parsed = parseBridgeArgv(this.argv);
150
+ }
151
+ catch (error) {
152
+ const message = error instanceof Error ? error.message : String(error);
153
+ this.error(message);
154
+ }
155
+ const { requestedEnv, yes, passthroughArgs } = parsed;
156
+ if (passthroughArgs.length === 0) {
157
+ this.error('Pass at least one v1 command to forward.');
158
+ }
159
+ const explicitEnvSelection = Boolean(requestedEnv && hasExplicitEnvSelection(this.argv));
160
+ if (explicitEnvSelection) {
161
+ const confirmed = await ensureCrossEnvConfirmed({
162
+ command: this,
163
+ requestedEnv,
164
+ yes,
165
+ });
166
+ if (!confirmed) {
167
+ return;
168
+ }
169
+ }
170
+ const runtime = await resolveManagedAppRuntime(requestedEnv);
171
+ if (!runtime) {
172
+ this.error(formatMissingManagedAppEnvMessage(requestedEnv));
173
+ }
174
+ const silentLike = hasSilentLikePassthrough(passthroughArgs);
175
+ const silentBridge = silentLike ? createSilentBridgeOptions() : undefined;
176
+ if (!silentLike) {
177
+ announceTargetEnv(runtime.envName);
178
+ }
179
+ if (runtime.kind === 'local') {
180
+ try {
181
+ await runLocalNocoBaseCommand(runtime, passthroughArgs, silentBridge?.commandOptions);
182
+ }
183
+ catch (error) {
184
+ const message = error instanceof Error ? error.message : String(error);
185
+ this.error(message);
186
+ }
187
+ finally {
188
+ silentBridge?.flush();
189
+ }
190
+ return;
191
+ }
192
+ if (runtime.kind === 'docker') {
193
+ try {
194
+ await runDockerNocoBaseCommand(runtime.containerName, passthroughArgs, silentBridge?.commandOptions);
195
+ }
196
+ catch (error) {
197
+ const message = error instanceof Error ? error.message : String(error);
198
+ this.error(message);
199
+ }
200
+ finally {
201
+ silentBridge?.flush();
202
+ }
203
+ return;
204
+ }
205
+ if (runtime.kind === 'http') {
206
+ this.error(formatHttpEnvError(runtime.envName));
207
+ }
208
+ this.error(formatSshEnvError(runtime.envName));
209
+ }
210
+ }
@@ -7,7 +7,7 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
  import { mkdir, readdir } from 'node:fs/promises';
10
- import { dockerContainerExists, startDockerContainer } from './app-runtime.js';
10
+ import { dockerContainerExists, runLocalNocoBaseCommand, startDockerContainer } from './app-runtime.js';
11
11
  import { deriveBuiltinDbConnection, resolveBuiltinDbConnection } from './builtin-db.js';
12
12
  import { resolveConfiguredEnvPath } from './cli-home.js';
13
13
  import { resolveDockerEnvFileArg } from "./docker-env-file.js";
@@ -32,6 +32,18 @@ function localSourceLabel(source) {
32
32
  function trimValue(value) {
33
33
  return String(value ?? '').trim();
34
34
  }
35
+ function pushOptionalEnvArg(args, key, value) {
36
+ if (typeof value === 'string') {
37
+ if (!value) {
38
+ return;
39
+ }
40
+ args.push('-e', `${key}=${value}`);
41
+ return;
42
+ }
43
+ if (typeof value === 'boolean') {
44
+ args.push('-e', `${key}=${String(value)}`);
45
+ }
46
+ }
35
47
  function normalizeDockerPlatform(value) {
36
48
  const text = trimValue(value);
37
49
  if (!text || text === 'auto') {
@@ -58,6 +70,14 @@ function formatLocalSourceRestoreFailure(envName, source, message) {
58
70
  `Details: ${message}`,
59
71
  ].join('\n');
60
72
  }
73
+ function formatLocalPostinstallFailure(envName, message) {
74
+ return [
75
+ `Couldn't prepare NocoBase for "${envName}".`,
76
+ 'The CLI was not able to run `nocobase-v1 postinstall` before starting the local app.',
77
+ 'Check the local dependencies, storage path, and saved env settings, then try again.',
78
+ `Details: ${message}`,
79
+ ].join('\n');
80
+ }
61
81
  function formatSavedDockerSettingsIncomplete(envName, missing) {
62
82
  return [
63
83
  `Can't start NocoBase for "${envName}" yet.`,
@@ -101,6 +121,9 @@ export async function buildSavedDockerRunArgs(runtime) {
101
121
  const dbDatabase = trimValue(config.dbDatabase);
102
122
  const dbUser = trimValue(config.dbUser);
103
123
  const dbPassword = trimValue(config.dbPassword);
124
+ const dbSchema = trimValue(config.dbSchema);
125
+ const dbTablePrefix = trimValue(config.dbTablePrefix);
126
+ const dbUnderscored = typeof config.dbUnderscored === 'boolean' ? config.dbUnderscored : undefined;
104
127
  const dockerRegistry = trimValue(config.dockerRegistry) || DEFAULT_DOCKER_REGISTRY;
105
128
  const version = trimValue(config.downloadVersion) || DEFAULT_DOCKER_VERSION;
106
129
  const imageRef = resolveDockerImageRef(dockerRegistry, version, {
@@ -155,7 +178,11 @@ export async function buildSavedDockerRunArgs(runtime) {
155
178
  if (envFile) {
156
179
  args.push('--env-file', envFile);
157
180
  }
158
- 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);
181
+ 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}`);
182
+ pushOptionalEnvArg(args, 'DB_SCHEMA', dbSchema || undefined);
183
+ pushOptionalEnvArg(args, 'DB_TABLE_PREFIX', dbTablePrefix || undefined);
184
+ pushOptionalEnvArg(args, 'DB_UNDERSCORED', dbUnderscored);
185
+ args.push(imageRef);
159
186
  return {
160
187
  appPort: appPort || undefined,
161
188
  storagePath,
@@ -274,3 +301,16 @@ export async function ensureSavedLocalSource(runtime, runCommand, options) {
274
301
  throw new Error(formatLocalSourceRestoreFailure(runtime.envName, runtime.source, error instanceof Error ? error.message : String(error)));
275
302
  }
276
303
  }
304
+ export async function ensureLocalPostinstall(runtime, options) {
305
+ options?.onStartTask?.(`Running local postinstall for "${runtime.envName}"...`);
306
+ try {
307
+ await runLocalNocoBaseCommand(runtime, ['postinstall'], {
308
+ stdio: commandStdio(options?.verbose),
309
+ });
310
+ options?.onSucceedTask?.(`Local postinstall finished for "${runtime.envName}".`);
311
+ }
312
+ catch (error) {
313
+ options?.onFailTask?.(`Failed to run local postinstall for "${runtime.envName}".`);
314
+ throw new Error(formatLocalPostinstallFailure(runtime.envName, error instanceof Error ? error.message : String(error)));
315
+ }
316
+ }
@@ -119,8 +119,13 @@ export async function runLocalNocoBaseCommand(runtime, args, options) {
119
119
  const envVars = await buildRuntimeEnvVars(runtime);
120
120
  await runNocoBaseCommand(args, {
121
121
  cwd: runtime.projectRoot,
122
- env: envVars,
122
+ env: {
123
+ ...envVars,
124
+ ...options?.env,
125
+ },
123
126
  stdio: options?.stdio,
127
+ onStdout: options?.onStdout,
128
+ onStderr: options?.onStderr,
124
129
  });
125
130
  }
126
131
  export async function dockerContainerExists(containerName) {
@@ -163,9 +168,13 @@ export async function stopDockerContainer(containerName, options) {
163
168
  });
164
169
  return 'stopped';
165
170
  }
166
- export async function runDockerNocoBaseCommand(containerName, args) {
167
- await startDockerContainer(containerName);
168
- await run('docker', ['exec', '-w', DOCKER_APP_WORKDIR, containerName, 'yarn', 'nocobase', ...args], {
171
+ export async function runDockerNocoBaseCommand(containerName, args, options) {
172
+ await startDockerContainer(containerName, { stdio: options?.stdio });
173
+ const dockerEnvArgs = Object.entries(options?.env ?? {}).flatMap(([key, value]) => ['-e', `${key}=${value}`]);
174
+ await run('docker', ['exec', ...dockerEnvArgs, '-w', DOCKER_APP_WORKDIR, containerName, 'yarn', 'nocobase', ...args], {
169
175
  errorName: 'docker exec',
176
+ stdio: options?.stdio,
177
+ onStdout: options?.onStdout,
178
+ onStderr: options?.onStderr,
170
179
  });
171
180
  }