@nocobase/cli 2.1.0-beta.9 → 2.1.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (250) hide show
  1. package/bin/run.cmd +3 -0
  2. package/bin/run.js +145 -0
  3. package/bin/session-env.js +39 -0
  4. package/dist/commands/api/resource/create.js +15 -0
  5. package/dist/commands/api/resource/destroy.js +15 -0
  6. package/dist/commands/api/resource/get.js +15 -0
  7. package/dist/commands/api/resource/index.js +20 -0
  8. package/dist/commands/api/resource/list.js +16 -0
  9. package/dist/commands/api/resource/query.js +15 -0
  10. package/dist/commands/api/resource/update.js +15 -0
  11. package/dist/commands/app/autostart/disable.js +55 -0
  12. package/dist/commands/app/autostart/enable.js +55 -0
  13. package/dist/commands/app/autostart/list.js +37 -0
  14. package/dist/commands/app/autostart/run.js +84 -0
  15. package/dist/commands/app/autostart/shared.js +49 -0
  16. package/dist/commands/app/destroy.js +234 -0
  17. package/dist/commands/app/down.js +71 -0
  18. package/dist/commands/app/logs.js +115 -0
  19. package/dist/commands/app/restart.js +229 -0
  20. package/dist/commands/app/shared.js +123 -0
  21. package/dist/commands/app/start.js +416 -0
  22. package/dist/commands/app/stop.js +183 -0
  23. package/dist/commands/app/upgrade.js +523 -0
  24. package/dist/commands/backup/create.js +147 -0
  25. package/dist/commands/backup/index.js +20 -0
  26. package/dist/commands/backup/restore.js +105 -0
  27. package/{src/cli.js → dist/commands/build.js} +4 -11
  28. package/dist/commands/config/delete.js +42 -0
  29. package/dist/commands/config/get.js +39 -0
  30. package/dist/commands/config/index.js +20 -0
  31. package/dist/commands/config/list.js +29 -0
  32. package/dist/commands/config/set.js +49 -0
  33. package/dist/commands/db/check.js +240 -0
  34. package/dist/commands/db/logs.js +85 -0
  35. package/dist/commands/db/ps.js +47 -0
  36. package/dist/commands/db/shared.js +96 -0
  37. package/dist/commands/db/start.js +86 -0
  38. package/dist/commands/db/stop.js +71 -0
  39. package/{templates/plugin/src/client/models/index.ts → dist/commands/dev.js} +4 -4
  40. package/{src/commands/locale/react-js-cron/index.js → dist/commands/down.js} +3 -8
  41. package/dist/commands/download.js +13 -0
  42. package/dist/commands/env/add.js +406 -0
  43. package/dist/commands/env/auth.js +189 -0
  44. package/dist/commands/env/current.js +21 -0
  45. package/dist/commands/env/info.js +202 -0
  46. package/dist/commands/env/list.js +43 -0
  47. package/dist/commands/env/remove.js +174 -0
  48. package/dist/commands/env/shared.js +204 -0
  49. package/dist/commands/env/status.js +93 -0
  50. package/dist/commands/env/update.js +448 -0
  51. package/dist/commands/env/use.js +38 -0
  52. package/dist/commands/examples/prompts-stages.js +150 -0
  53. package/dist/commands/examples/prompts-test.js +181 -0
  54. package/dist/commands/init.js +1390 -0
  55. package/dist/commands/install.js +2609 -0
  56. package/dist/commands/license/activate.js +179 -0
  57. package/dist/commands/license/env.js +94 -0
  58. package/dist/commands/license/generate-id.js +108 -0
  59. package/dist/commands/license/id.js +70 -0
  60. package/dist/commands/license/index.js +20 -0
  61. package/dist/commands/license/plugins/clean.js +115 -0
  62. package/dist/commands/license/plugins/index.js +20 -0
  63. package/dist/commands/license/plugins/list.js +64 -0
  64. package/dist/commands/license/plugins/shared.js +382 -0
  65. package/dist/commands/license/plugins/sync.js +314 -0
  66. package/dist/commands/license/shared.js +423 -0
  67. package/dist/commands/license/status.js +64 -0
  68. package/dist/commands/logs.js +12 -0
  69. package/dist/commands/plugin/disable.js +86 -0
  70. package/dist/commands/plugin/enable.js +86 -0
  71. package/dist/commands/plugin/import.js +108 -0
  72. package/dist/commands/plugin/list.js +82 -0
  73. package/dist/commands/pm/disable.js +12 -0
  74. package/dist/commands/pm/enable.js +12 -0
  75. package/dist/commands/pm/list.js +12 -0
  76. package/dist/commands/proxy/caddy/current.js +17 -0
  77. package/dist/commands/proxy/caddy/generate.js +69 -0
  78. package/dist/commands/proxy/caddy/index.js +28 -0
  79. package/dist/commands/proxy/caddy/info.js +31 -0
  80. package/dist/commands/proxy/caddy/reload.js +30 -0
  81. package/dist/commands/proxy/caddy/restart.js +28 -0
  82. package/dist/commands/proxy/caddy/start.js +30 -0
  83. package/dist/commands/proxy/caddy/status.js +19 -0
  84. package/dist/commands/proxy/caddy/stop.js +30 -0
  85. package/dist/commands/proxy/caddy/use.js +26 -0
  86. package/dist/commands/proxy/index.js +28 -0
  87. package/dist/commands/proxy/nginx/current.js +18 -0
  88. package/dist/commands/proxy/nginx/generate.js +68 -0
  89. package/dist/commands/proxy/nginx/index.js +28 -0
  90. package/dist/commands/proxy/nginx/info.js +34 -0
  91. package/dist/commands/proxy/nginx/reload.js +30 -0
  92. package/dist/commands/proxy/nginx/restart.js +28 -0
  93. package/dist/commands/proxy/nginx/start.js +30 -0
  94. package/dist/commands/proxy/nginx/status.js +19 -0
  95. package/dist/commands/proxy/nginx/stop.js +30 -0
  96. package/dist/commands/proxy/nginx/use.js +31 -0
  97. package/dist/commands/restart.js +12 -0
  98. package/dist/commands/revision/create.js +118 -0
  99. package/dist/commands/scaffold/migration.js +38 -0
  100. package/dist/commands/scaffold/plugin.js +37 -0
  101. package/dist/commands/self/check.js +71 -0
  102. package/dist/commands/self/index.js +20 -0
  103. package/dist/commands/self/update.js +152 -0
  104. package/dist/commands/session/id.js +24 -0
  105. package/dist/commands/session/remove.js +57 -0
  106. package/dist/commands/session/setup.js +62 -0
  107. package/dist/commands/skills/check.js +69 -0
  108. package/dist/commands/skills/index.js +20 -0
  109. package/dist/commands/skills/install.js +80 -0
  110. package/dist/commands/skills/remove.js +80 -0
  111. package/dist/commands/skills/update.js +87 -0
  112. package/dist/commands/source/build.js +58 -0
  113. package/dist/commands/source/dev.js +182 -0
  114. package/dist/commands/source/download.js +883 -0
  115. package/dist/commands/source/publish.js +109 -0
  116. package/dist/commands/source/registry/logs.js +70 -0
  117. package/dist/commands/source/registry/start.js +57 -0
  118. package/dist/commands/source/registry/status.js +33 -0
  119. package/dist/commands/source/registry/stop.js +48 -0
  120. package/dist/commands/source/test.js +476 -0
  121. package/dist/commands/start.js +12 -0
  122. package/dist/commands/stop.js +12 -0
  123. package/dist/commands/test.js +12 -0
  124. package/dist/commands/upgrade.js +12 -0
  125. package/dist/commands/v1.js +210 -0
  126. package/dist/generated/command-registry.js +134 -0
  127. package/dist/help/runtime-help.js +23 -0
  128. package/dist/lib/api-client.js +335 -0
  129. package/dist/lib/api-command-compat.js +641 -0
  130. package/dist/lib/app-health.js +139 -0
  131. package/dist/lib/app-managed-resources.js +337 -0
  132. package/dist/lib/app-public-path.js +80 -0
  133. package/dist/lib/app-runtime.js +189 -0
  134. package/dist/lib/auth-store.js +528 -0
  135. package/dist/lib/backup.js +171 -0
  136. package/dist/lib/bootstrap.js +409 -0
  137. package/dist/lib/build-config.js +18 -0
  138. package/dist/lib/builtin-db.js +86 -0
  139. package/dist/lib/cli-config.js +569 -0
  140. package/dist/lib/cli-entry-error.js +52 -0
  141. package/dist/lib/cli-home.js +47 -0
  142. package/dist/lib/cli-locale.js +141 -0
  143. package/dist/lib/command-discovery.js +39 -0
  144. package/dist/lib/command-log.js +284 -0
  145. package/dist/lib/db-connection-check.js +219 -0
  146. package/dist/lib/docker-env-file.js +60 -0
  147. package/dist/lib/docker-image.js +37 -0
  148. package/dist/lib/docker-log-stream.js +45 -0
  149. package/dist/lib/env-auth.js +963 -0
  150. package/dist/lib/env-command-config.js +45 -0
  151. package/dist/lib/env-config.js +108 -0
  152. package/dist/lib/env-guard.js +61 -0
  153. package/dist/lib/env-paths.js +101 -0
  154. package/dist/lib/env-proxy.js +1325 -0
  155. package/dist/lib/generated-command.js +203 -0
  156. package/dist/lib/http-request.js +49 -0
  157. package/dist/lib/inquirer-theme.js +17 -0
  158. package/dist/lib/inquirer.js +243 -0
  159. package/dist/lib/managed-env-file.js +101 -0
  160. package/dist/lib/managed-init-env.js +32 -0
  161. package/dist/lib/naming.js +70 -0
  162. package/dist/lib/object-utils.js +76 -0
  163. package/dist/lib/openapi.js +62 -0
  164. package/dist/lib/plugin-import.js +279 -0
  165. package/dist/lib/plugin-storage.js +64 -0
  166. package/dist/lib/post-processors.js +23 -0
  167. package/dist/lib/prompt-catalog-core.js +186 -0
  168. package/dist/lib/prompt-catalog-terminal.js +374 -0
  169. package/{src/index.js → dist/lib/prompt-catalog.js} +2 -6
  170. package/dist/lib/prompt-validators.js +278 -0
  171. package/dist/lib/prompt-web-ui.js +2234 -0
  172. package/dist/lib/proxy-caddy.js +274 -0
  173. package/dist/lib/proxy-nginx.js +330 -0
  174. package/dist/lib/resource-command.js +357 -0
  175. package/dist/lib/resource-request.js +104 -0
  176. package/dist/lib/run-npm.js +429 -0
  177. package/dist/lib/runtime-env-vars.js +32 -0
  178. package/dist/lib/runtime-generator.js +498 -0
  179. package/dist/lib/runtime-store.js +56 -0
  180. package/dist/lib/self-manager.js +301 -0
  181. package/dist/lib/session-id.js +17 -0
  182. package/dist/lib/session-integration.js +703 -0
  183. package/dist/lib/session-store.js +118 -0
  184. package/dist/lib/skills-manager.js +438 -0
  185. package/dist/lib/source-publish.js +326 -0
  186. package/dist/lib/source-registry.js +188 -0
  187. package/dist/lib/startup-update.js +309 -0
  188. package/dist/lib/ui.js +159 -0
  189. package/dist/locale/en-US.json +526 -0
  190. package/dist/locale/zh-CN.json +526 -0
  191. package/dist/post-processors/data-modeling.js +84 -0
  192. package/dist/post-processors/data-source-manager.js +138 -0
  193. package/dist/post-processors/index.js +19 -0
  194. package/nocobase-ctl.config.json +388 -0
  195. package/package.json +133 -25
  196. package/bin/index.js +0 -39
  197. package/nocobase.conf.tpl +0 -95
  198. package/src/commands/benchmark.js +0 -73
  199. package/src/commands/build.js +0 -49
  200. package/src/commands/clean.js +0 -30
  201. package/src/commands/client.js +0 -166
  202. package/src/commands/create-nginx-conf.js +0 -37
  203. package/src/commands/create-plugin.js +0 -33
  204. package/src/commands/dev.js +0 -200
  205. package/src/commands/doc.js +0 -76
  206. package/src/commands/e2e.js +0 -265
  207. package/src/commands/global.js +0 -43
  208. package/src/commands/index.js +0 -45
  209. package/src/commands/instance-id.js +0 -47
  210. package/src/commands/locale/cronstrue.js +0 -122
  211. package/src/commands/locale/react-js-cron/en-US.json +0 -75
  212. package/src/commands/locale/react-js-cron/zh-CN.json +0 -33
  213. package/src/commands/locale/react-js-cron/zh-TW.json +0 -33
  214. package/src/commands/locale.js +0 -81
  215. package/src/commands/p-test.js +0 -88
  216. package/src/commands/perf.js +0 -63
  217. package/src/commands/pkg.js +0 -321
  218. package/src/commands/pm2.js +0 -37
  219. package/src/commands/postinstall.js +0 -88
  220. package/src/commands/start.js +0 -148
  221. package/src/commands/tar.js +0 -36
  222. package/src/commands/test-coverage.js +0 -55
  223. package/src/commands/test.js +0 -107
  224. package/src/commands/umi.js +0 -33
  225. package/src/commands/update-deps.js +0 -72
  226. package/src/commands/upgrade.js +0 -47
  227. package/src/commands/view-license-key.js +0 -44
  228. package/src/license.js +0 -76
  229. package/src/logger.js +0 -75
  230. package/src/plugin-generator.js +0 -80
  231. package/src/util.js +0 -517
  232. package/templates/bundle-status.html +0 -338
  233. package/templates/create-app-package.json +0 -39
  234. package/templates/plugin/.npmignore.tpl +0 -2
  235. package/templates/plugin/README.md.tpl +0 -1
  236. package/templates/plugin/client.d.ts +0 -2
  237. package/templates/plugin/client.js +0 -1
  238. package/templates/plugin/package.json.tpl +0 -11
  239. package/templates/plugin/server.d.ts +0 -2
  240. package/templates/plugin/server.js +0 -1
  241. package/templates/plugin/src/client/client.d.ts +0 -249
  242. package/templates/plugin/src/client/index.tsx.tpl +0 -1
  243. package/templates/plugin/src/client/locale.ts +0 -21
  244. package/templates/plugin/src/client/plugin.tsx.tpl +0 -10
  245. package/templates/plugin/src/index.ts +0 -2
  246. package/templates/plugin/src/locale/en-US.json +0 -1
  247. package/templates/plugin/src/locale/zh-CN.json +0 -1
  248. package/templates/plugin/src/server/collections/.gitkeep +0 -0
  249. package/templates/plugin/src/server/index.ts.tpl +0 -1
  250. package/templates/plugin/src/server/plugin.ts.tpl +0 -19
@@ -0,0 +1,523 @@
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 { getCurrentEnvName, upsertEnv } from '../../lib/auth-store.js';
11
+ import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, } from '../../lib/app-runtime.js';
12
+ import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
13
+ import { DEFAULT_DOCKER_REGISTRY } from "../../lib/docker-image.js";
14
+ import { confirm } from "../../lib/inquirer.js";
15
+ import { announceTargetEnv, isInteractiveTerminal, printInfo, printWarning, succeedTask } from '../../lib/ui.js';
16
+ import { resolveAppUrlFromApiBaseUrl } from '../env/shared.js';
17
+ function trimValue(value) {
18
+ return String(value ?? '').trim();
19
+ }
20
+ function normalizeEnvName(value) {
21
+ const text = trimValue(value);
22
+ return text || undefined;
23
+ }
24
+ function formatAppUrl(port) {
25
+ const value = trimValue(port);
26
+ return value ? `http://127.0.0.1:${value}` : undefined;
27
+ }
28
+ function formatDisplayUrl(apiBaseUrl, appPort) {
29
+ const resolvedFromApiBaseUrl = resolveAppUrlFromApiBaseUrl(apiBaseUrl);
30
+ if (resolvedFromApiBaseUrl) {
31
+ return resolvedFromApiBaseUrl;
32
+ }
33
+ const appUrl = formatAppUrl(appPort);
34
+ if (appUrl) {
35
+ return appUrl;
36
+ }
37
+ const value = trimValue(apiBaseUrl);
38
+ return value ? value.replace(/\/api\/?$/, '') : undefined;
39
+ }
40
+ function readEnvValue(env, key) {
41
+ return trimValue(env.config[key]);
42
+ }
43
+ function normalizeDockerPlatform(value) {
44
+ const text = trimValue(value);
45
+ if (!text || text === 'auto') {
46
+ return undefined;
47
+ }
48
+ if (text === 'linux/amd64' || text === 'linux/arm64') {
49
+ return text;
50
+ }
51
+ return undefined;
52
+ }
53
+ function isDownloadableLocalRuntime(runtime) {
54
+ return runtime.kind === 'local' && (runtime.source === 'npm' || runtime.source === 'git');
55
+ }
56
+ function formatLocalDownloadFailure(envName, source, message) {
57
+ const sourceLabel = source === 'git' ? 'the saved Git checkout' : 'the saved npm app';
58
+ return [
59
+ `Couldn't refresh NocoBase for "${envName}".`,
60
+ `The CLI was not able to update ${sourceLabel} before restarting it.`,
61
+ 'Check the saved source settings for this env, then try again.',
62
+ `Details: ${message}`,
63
+ ].join('\n');
64
+ }
65
+ function formatDockerDownloadFailure(envName, message) {
66
+ return [
67
+ `Couldn't refresh the Docker image for "${envName}".`,
68
+ 'The CLI was not able to pull the latest image for this env.',
69
+ 'Check the saved Docker source settings and your Docker network access, then try again.',
70
+ `Details: ${message}`,
71
+ ].join('\n');
72
+ }
73
+ function buildManagedActionArgv(envName, flags, options) {
74
+ const argv = ['--env', envName, '--yes'];
75
+ if (flags.verbose) {
76
+ argv.push('--verbose');
77
+ }
78
+ if (options?.quickstart) {
79
+ argv.push('--quickstart');
80
+ }
81
+ return argv;
82
+ }
83
+ function shouldSkipDownload(flags) {
84
+ return Boolean(flags['skip-download'] || flags['skip-code-update']);
85
+ }
86
+ function buildUpgradeCliArgv(envName, flags, options) {
87
+ const argv = ['--env', envName];
88
+ if (shouldSkipDownload(flags)) {
89
+ argv.push('--skip-download');
90
+ }
91
+ const version = normalizeEnvName(flags.version);
92
+ if (version) {
93
+ argv.push('--version', version);
94
+ }
95
+ if (flags.verbose) {
96
+ argv.push('--verbose');
97
+ }
98
+ if (options?.yes ?? flags.yes) {
99
+ argv.push('--yes');
100
+ }
101
+ if (options?.force ?? flags.force) {
102
+ argv.push('--force');
103
+ }
104
+ return argv;
105
+ }
106
+ function buildUpgradeCliCommand(envName, flags, options) {
107
+ return ['nb', 'app', 'upgrade', ...buildUpgradeCliArgv(envName, flags, options)].join(' ');
108
+ }
109
+ function formatUpgradeOperationSummary(runtime, flags) {
110
+ const mayRunUpgradeMigrations = 'It may also run upgrade migrations.';
111
+ if (shouldSkipDownload(flags)) {
112
+ const sourceLabel = runtime.kind === 'docker'
113
+ ? 'saved Docker image'
114
+ : runtime.source === 'local'
115
+ ? 'saved local app path'
116
+ : runtime.source === 'git'
117
+ ? 'saved Git checkout'
118
+ : 'saved npm app';
119
+ return [
120
+ 'This operation will stop the app, skip source download and commercial plugin sync,',
121
+ `and start it again with the ${sourceLabel}.`,
122
+ mayRunUpgradeMigrations,
123
+ ].join(' ');
124
+ }
125
+ if (runtime.kind === 'docker') {
126
+ return [
127
+ 'This operation will stop the app, replace the saved Docker image,',
128
+ 'sync commercial plugins when applicable, and start the app again.',
129
+ mayRunUpgradeMigrations,
130
+ ].join(' ');
131
+ }
132
+ if (runtime.source === 'local') {
133
+ return [
134
+ 'This operation will stop the app, reuse the saved local app path,',
135
+ 'sync commercial plugins when applicable, and start the app again.',
136
+ mayRunUpgradeMigrations,
137
+ ].join(' ');
138
+ }
139
+ return [
140
+ 'This operation will stop the app, replace the saved source,',
141
+ 'sync commercial plugins when applicable, and start the app again.',
142
+ mayRunUpgradeMigrations,
143
+ ].join(' ');
144
+ }
145
+ function formatUpgradePromptSummary(flags) {
146
+ if (shouldSkipDownload(flags)) {
147
+ return 'This will stop and restart the app, and may run upgrade migrations.';
148
+ }
149
+ return 'This will stop and restart the app, update the saved source or image, and may run upgrade migrations.';
150
+ }
151
+ function formatUpgradeForceRequiredMessage(runtime, flags) {
152
+ return [
153
+ `\`nb app upgrade\` needs confirmation in non-interactive mode before upgrading "${runtime.envName}".`,
154
+ '',
155
+ formatUpgradeOperationSummary(runtime, flags),
156
+ '',
157
+ 'Interactive confirmation is unavailable in the current AI agent session, and the agent will not add `--force` on your behalf.',
158
+ '',
159
+ 'To continue:',
160
+ `- re-run \`${buildUpgradeCliCommand(runtime.envName, flags, { force: true })}\``,
161
+ `- or switch to an interactive terminal and re-run \`${buildUpgradeCliCommand(runtime.envName, flags, {
162
+ force: false,
163
+ })}\``,
164
+ ].join('\n');
165
+ }
166
+ function formatMissingUpgradeFlagList(options) {
167
+ const missingFlags = [
168
+ options.missingYes ? '`--yes`' : undefined,
169
+ options.missingForce ? '`--force`' : undefined,
170
+ ].filter(Boolean);
171
+ if (missingFlags.length <= 1) {
172
+ return missingFlags[0] ?? '';
173
+ }
174
+ return `${missingFlags[0]} or ${missingFlags[1]}`;
175
+ }
176
+ function formatUpgradeCrossEnvConfirmationRequiredMessage(currentEnv, runtime, flags, options) {
177
+ return [
178
+ `Refusing to upgrade env "${runtime.envName}" because the current env is "${currentEnv}" and interactive confirmation is unavailable in the current AI agent session.`,
179
+ '',
180
+ formatUpgradeOperationSummary(runtime, flags),
181
+ '',
182
+ `For safety, the agent will not switch envs automatically and will not add ${formatMissingUpgradeFlagList(options)} on your behalf.`,
183
+ '',
184
+ 'To continue:',
185
+ `- run \`nb env use ${runtime.envName}\` yourself, then re-run \`${buildUpgradeCliCommand(runtime.envName, flags, {
186
+ yes: false,
187
+ force: true,
188
+ })}\``,
189
+ `- or re-run \`${buildUpgradeCliCommand(runtime.envName, flags, { yes: true, force: true })}\``,
190
+ ].join('\n');
191
+ }
192
+ function buildLicenseSyncArgv(envName, flags, options) {
193
+ const argv = ['--env', envName, '--yes', '--skip-if-no-license'];
194
+ if (flags.verbose) {
195
+ argv.push('--verbose');
196
+ }
197
+ if (options?.version) {
198
+ argv.push('--version', options.version);
199
+ }
200
+ return argv;
201
+ }
202
+ function buildEnvUpdateArgv(envName, flags) {
203
+ const argv = [envName];
204
+ if (flags.verbose) {
205
+ argv.push('--verbose');
206
+ }
207
+ return argv;
208
+ }
209
+ function formatEnvUpdateWarning(envName, message) {
210
+ return [
211
+ `NocoBase was upgraded for "${envName}", but the CLI could not refresh the saved env runtime.`,
212
+ `Run \`nb env update ${envName}\` to refresh it manually.`,
213
+ `Details: ${message}`,
214
+ ].join(' ');
215
+ }
216
+ async function runWithSuppressedTargetEnvLog(task) {
217
+ const previousTargetEnv = process.env.NB_SKIP_TARGET_ENV_LOG;
218
+ process.env.NB_SKIP_TARGET_ENV_LOG = '1';
219
+ try {
220
+ return await task();
221
+ }
222
+ finally {
223
+ if (previousTargetEnv === undefined) {
224
+ delete process.env.NB_SKIP_TARGET_ENV_LOG;
225
+ }
226
+ else {
227
+ process.env.NB_SKIP_TARGET_ENV_LOG = previousTargetEnv;
228
+ }
229
+ }
230
+ }
231
+ async function runWithSuppressedStartSuccessLog(task) {
232
+ const previousStartSuccess = process.env.NB_SKIP_APP_START_SUCCESS_LOG;
233
+ process.env.NB_SKIP_APP_START_SUCCESS_LOG = '1';
234
+ try {
235
+ return await task();
236
+ }
237
+ finally {
238
+ if (previousStartSuccess === undefined) {
239
+ delete process.env.NB_SKIP_APP_START_SUCCESS_LOG;
240
+ }
241
+ else {
242
+ process.env.NB_SKIP_APP_START_SUCCESS_LOG = previousStartSuccess;
243
+ }
244
+ }
245
+ }
246
+ export default class AppUpgrade extends Command {
247
+ static hidden = false;
248
+ static description = 'Upgrade the selected NocoBase app. The CLI stops the current app, optionally replaces the saved source or image, then starts the app again. Use --version to upgrade to a specific saved source version or image tag.';
249
+ static examples = [
250
+ '<%= config.bin %> <%= command.id %>',
251
+ '<%= config.bin %> <%= command.id %> --force',
252
+ '<%= config.bin %> <%= command.id %> --env local',
253
+ '<%= config.bin %> <%= command.id %> --env local --force',
254
+ '<%= config.bin %> <%= command.id %> --env local -s',
255
+ '<%= config.bin %> <%= command.id %> --env local --version beta',
256
+ '<%= config.bin %> <%= command.id %> --env local --verbose',
257
+ '<%= config.bin %> <%= command.id %> --env local-docker -s',
258
+ ];
259
+ static flags = {
260
+ env: Flags.string({
261
+ char: 'e',
262
+ description: 'CLI env name to upgrade. Defaults to the current env when omitted',
263
+ }),
264
+ yes: Flags.boolean({
265
+ char: 'y',
266
+ description: 'Confirm using --env when it targets a different env than the current env',
267
+ default: false,
268
+ }),
269
+ force: Flags.boolean({
270
+ char: 'f',
271
+ description: 'Skip the upgrade confirmation prompt',
272
+ default: false,
273
+ }),
274
+ 'skip-download': Flags.boolean({
275
+ char: 's',
276
+ description: 'Restart with the saved local source or Docker image without downloading updates first',
277
+ required: false,
278
+ }),
279
+ 'skip-code-update': Flags.boolean({
280
+ hidden: true,
281
+ deprecated: true,
282
+ description: 'Deprecated alias for --skip-download',
283
+ required: false,
284
+ }),
285
+ version: Flags.string({
286
+ description: 'Override the saved downloadVersion for this upgrade. When the upgrade succeeds, the new version is saved back to the env config.',
287
+ required: false,
288
+ }),
289
+ verbose: Flags.boolean({
290
+ description: 'Show raw output from the underlying local or Docker commands',
291
+ default: true,
292
+ }),
293
+ };
294
+ static resolveUpgradeVersion(runtime, flags) {
295
+ const requestedVersion = trimValue(flags.version);
296
+ if (runtime.kind === 'local' && runtime.source === 'local') {
297
+ if (requestedVersion) {
298
+ throw new Error([
299
+ `Env "${runtime.envName}" is managed from an existing local app path.`,
300
+ 'This source does not support `nb app upgrade --version` because the CLI does not manage that code checkout.',
301
+ 'Update the local app path yourself, then run `nb app upgrade` to restart it.',
302
+ ].join('\n'));
303
+ }
304
+ return {};
305
+ }
306
+ if (shouldSkipDownload(flags)) {
307
+ return {
308
+ persistDownloadVersion: requestedVersion || undefined,
309
+ };
310
+ }
311
+ const savedVersion = readEnvValue(runtime.env, 'downloadVersion');
312
+ const downloadVersion = requestedVersion || savedVersion;
313
+ if (!downloadVersion) {
314
+ throw new Error([
315
+ `Env "${runtime.envName}" does not have a saved \`downloadVersion\`.`,
316
+ 'This env cannot be upgraded until a source version is explicit.',
317
+ `Re-run \`nb init --ui --env ${runtime.envName}\` for this env, or pass \`--version\` to \`nb app upgrade\`.`,
318
+ ].join('\n'));
319
+ }
320
+ return {
321
+ downloadVersion,
322
+ persistDownloadVersion: requestedVersion || undefined,
323
+ };
324
+ }
325
+ static buildLocalDownloadArgv(runtime, downloadVersion, options) {
326
+ const argv = ['-y', '--no-intro', '--source', runtime.source, '--replace'];
327
+ if (options?.verbose) {
328
+ argv.push('--verbose');
329
+ }
330
+ argv.push('--version', downloadVersion, '--output-dir', runtime.projectRoot);
331
+ const gitUrl = readEnvValue(runtime.env, 'gitUrl');
332
+ if (gitUrl) {
333
+ argv.push('--git-url', gitUrl);
334
+ }
335
+ const npmRegistry = readEnvValue(runtime.env, 'npmRegistry');
336
+ if (npmRegistry) {
337
+ argv.push('--npm-registry', npmRegistry);
338
+ }
339
+ if (runtime.env.config.devDependencies === true) {
340
+ argv.push('--dev-dependencies');
341
+ }
342
+ if (runtime.env.config.build === false) {
343
+ argv.push('--no-build');
344
+ }
345
+ if (runtime.env.config.buildDts === true) {
346
+ argv.push('--build-dts');
347
+ }
348
+ return argv;
349
+ }
350
+ static buildDockerDownloadArgv(runtime, downloadVersion, options) {
351
+ const argv = ['-y', '--no-intro'];
352
+ if (options?.verbose) {
353
+ argv.push('--verbose');
354
+ }
355
+ argv.push('--source', 'docker', '--replace', '--docker-registry', readEnvValue(runtime.env, 'dockerRegistry') || DEFAULT_DOCKER_REGISTRY, '--version', downloadVersion);
356
+ const dockerPlatform = normalizeDockerPlatform(runtime.env.config.dockerPlatform);
357
+ if (dockerPlatform) {
358
+ argv.push('--docker-platform', dockerPlatform);
359
+ }
360
+ return argv;
361
+ }
362
+ static async persistDownloadVersion(runtime, downloadVersion) {
363
+ const { name: _name, ...envConfig } = runtime.env.config;
364
+ try {
365
+ await upsertEnv(runtime.envName, {
366
+ ...envConfig,
367
+ downloadVersion,
368
+ });
369
+ }
370
+ catch (error) {
371
+ const message = error instanceof Error ? error.message : String(error);
372
+ throw new Error(`NocoBase was upgraded for "${runtime.envName}", but the CLI could not save \`downloadVersion=${downloadVersion}\`. Details: ${message}`);
373
+ }
374
+ }
375
+ async run() {
376
+ const { flags } = await this.parse(AppUpgrade);
377
+ const parsed = flags;
378
+ const requestedEnv = normalizeEnvName(parsed.env);
379
+ const explicitEnvSelection = Boolean(requestedEnv && hasExplicitEnvSelection(this.argv));
380
+ const runtime = await resolveManagedAppRuntime(requestedEnv);
381
+ if (!runtime) {
382
+ this.error(formatMissingManagedAppEnvMessage(requestedEnv));
383
+ }
384
+ if (runtime.kind === 'http') {
385
+ this.error([
386
+ `Can't upgrade "${runtime.envName}" from this machine.`,
387
+ 'This env only has an API connection, so there is no saved local app or Docker runtime to upgrade here.',
388
+ 'If you want a local NocoBase AI environment that the CLI can upgrade, run `nb init --ui` first.',
389
+ ].join('\n'));
390
+ }
391
+ if (runtime.kind === 'ssh') {
392
+ this.error([
393
+ `Can't upgrade "${runtime.envName}" yet.`,
394
+ 'SSH env support is reserved but not implemented yet.',
395
+ 'Use a local or Docker env if you need CLI-managed upgrades right now.',
396
+ ].join('\n'));
397
+ }
398
+ const interactiveTerminal = isInteractiveTerminal();
399
+ if (explicitEnvSelection) {
400
+ if (!interactiveTerminal) {
401
+ const currentEnv = normalizeEnvName(await getCurrentEnvName());
402
+ if (currentEnv && currentEnv !== requestedEnv) {
403
+ const missingYes = !parsed.yes;
404
+ const missingForce = !parsed.force;
405
+ if (missingYes || missingForce) {
406
+ this.error(formatUpgradeCrossEnvConfirmationRequiredMessage(currentEnv, runtime, parsed, {
407
+ missingYes,
408
+ missingForce,
409
+ }));
410
+ }
411
+ }
412
+ }
413
+ else {
414
+ const confirmed = await ensureCrossEnvConfirmed({
415
+ command: this,
416
+ requestedEnv,
417
+ yes: parsed.yes,
418
+ });
419
+ if (!confirmed) {
420
+ return;
421
+ }
422
+ }
423
+ }
424
+ if (!interactiveTerminal) {
425
+ if (!parsed.force) {
426
+ this.error(formatUpgradeForceRequiredMessage(runtime, parsed));
427
+ }
428
+ }
429
+ else if (!parsed.force) {
430
+ let confirmed = false;
431
+ try {
432
+ confirmed = await confirm({
433
+ message: `Upgrade "${runtime.envName}"? ${formatUpgradePromptSummary(parsed)}`,
434
+ default: false,
435
+ });
436
+ }
437
+ catch {
438
+ return;
439
+ }
440
+ if (!confirmed) {
441
+ return;
442
+ }
443
+ }
444
+ announceTargetEnv(runtime.envName);
445
+ try {
446
+ const resolvedVersion = AppUpgrade.resolveUpgradeVersion(runtime, parsed);
447
+ const skipDownload = shouldSkipDownload(parsed);
448
+ const runCommand = this.config.runCommand.bind(this.config);
449
+ await runWithSuppressedTargetEnvLog(async () => {
450
+ await runCommand('app:stop', buildManagedActionArgv(runtime.envName, parsed));
451
+ });
452
+ if (skipDownload) {
453
+ printInfo(`Skipping source download for "${runtime.envName}" (--skip-download).`);
454
+ printInfo(`Skipping commercial plugin sync for "${runtime.envName}" (--skip-download).`);
455
+ }
456
+ else if (runtime.kind === 'local' && runtime.source === 'local') {
457
+ printInfo(`Skipping source download for "${runtime.envName}" because this env is managed from an existing local app path.`);
458
+ }
459
+ else {
460
+ const downloadVersion = resolvedVersion.downloadVersion;
461
+ if (!downloadVersion) {
462
+ throw new Error(`Missing downloadVersion for "${runtime.envName}".`);
463
+ }
464
+ try {
465
+ if (runtime.kind === 'docker') {
466
+ await runCommand('source:download', AppUpgrade.buildDockerDownloadArgv(runtime, downloadVersion, {
467
+ verbose: parsed.verbose,
468
+ }));
469
+ }
470
+ else if (isDownloadableLocalRuntime(runtime)) {
471
+ await runCommand('source:download', AppUpgrade.buildLocalDownloadArgv(runtime, downloadVersion, {
472
+ verbose: parsed.verbose,
473
+ }));
474
+ }
475
+ else {
476
+ throw new Error(`Skipping source download for "${runtime.envName}" because this env is managed from an existing local app path.`);
477
+ }
478
+ }
479
+ catch (error) {
480
+ const message = error instanceof Error ? error.message : String(error);
481
+ if (runtime.kind === 'docker') {
482
+ throw new Error(formatDockerDownloadFailure(runtime.envName, message));
483
+ }
484
+ if (isDownloadableLocalRuntime(runtime)) {
485
+ throw new Error(formatLocalDownloadFailure(runtime.envName, runtime.source, message));
486
+ }
487
+ throw new Error(message);
488
+ }
489
+ }
490
+ if (!skipDownload) {
491
+ await runWithSuppressedTargetEnvLog(async () => {
492
+ await runCommand('license:plugins:sync', buildLicenseSyncArgv(runtime.envName, parsed, {
493
+ version: resolvedVersion.persistDownloadVersion,
494
+ }));
495
+ });
496
+ }
497
+ await runWithSuppressedTargetEnvLog(async () => {
498
+ const startArgv = buildManagedActionArgv(runtime.envName, parsed, { quickstart: true });
499
+ startArgv.push('--no-sync-licensed-plugins');
500
+ await runWithSuppressedStartSuccessLog(async () => {
501
+ await runCommand('app:start', startArgv);
502
+ });
503
+ });
504
+ if (resolvedVersion.persistDownloadVersion) {
505
+ await AppUpgrade.persistDownloadVersion(runtime, resolvedVersion.persistDownloadVersion);
506
+ }
507
+ try {
508
+ await runWithSuppressedTargetEnvLog(async () => {
509
+ await runCommand('env:update', buildEnvUpdateArgv(runtime.envName, parsed));
510
+ });
511
+ }
512
+ catch (error) {
513
+ const message = error instanceof Error ? error.message : String(error);
514
+ printWarning(formatEnvUpdateWarning(runtime.envName, message));
515
+ }
516
+ const displayUrl = formatDisplayUrl(runtime.env.baseUrl, runtime.env.appPort === undefined || runtime.env.appPort === null ? undefined : String(runtime.env.appPort));
517
+ succeedTask(`NocoBase has been upgraded for "${runtime.envName}"${displayUrl ? ` at ${displayUrl}` : ''}.`);
518
+ }
519
+ catch (error) {
520
+ this.error(error instanceof Error ? error.message : String(error));
521
+ }
522
+ }
523
+ }
@@ -0,0 +1,147 @@
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 { BACKUP_CREATE_TIMEOUT_MS, BACKUP_POLL_INTERVAL_MS, BACKUP_RUNTIME_COMMANDS, buildBackupEnvArgv, ensureBackupRuntimeCommands, resolveBackupCreateOutputPath, resolveBackupTargetEnv, runBackupCliJsonCommand, sleep, } from '../../lib/backup.js';
11
+ import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
12
+ import { announceTargetEnv, failTask, startTask, succeedTask, updateTask } from '../../lib/ui.js';
13
+ function formatBackupCreateTimeoutError(envName, name) {
14
+ return [
15
+ `Backup "${name}" did not finish in time for "${envName}".`,
16
+ `Waited ${Math.floor(BACKUP_CREATE_TIMEOUT_MS / 1000)}s but it still reports \`inProgress: true\`.`,
17
+ ].join(' ');
18
+ }
19
+ function readBackupCreateResult(response) {
20
+ const name = String(response.data?.name ?? '').trim();
21
+ if (!name) {
22
+ throw new Error('Backup creation did not return a backup name.');
23
+ }
24
+ return {
25
+ name,
26
+ inProgress: Boolean(response.data?.inProgress),
27
+ };
28
+ }
29
+ function readBackupInProgress(response, name) {
30
+ const status = response.data?.[name];
31
+ if (!status || typeof status !== 'object') {
32
+ throw new Error(`Backup status did not include "${name}".`);
33
+ }
34
+ return Boolean(status.inProgress);
35
+ }
36
+ function readDownloadOutput(response) {
37
+ const output = String(response.data?.output ?? '').trim();
38
+ return output || undefined;
39
+ }
40
+ export default class BackupCreate extends Command {
41
+ static summary = 'Create a backup through the selected env and download it locally';
42
+ static examples = [
43
+ '<%= config.bin %> <%= command.id %>',
44
+ '<%= config.bin %> <%= command.id %> --output ./fixtures/base.nbdump',
45
+ '<%= config.bin %> <%= command.id %> --env e2e --output ./fixtures',
46
+ ];
47
+ static flags = {
48
+ env: Flags.string({
49
+ char: 'e',
50
+ description: 'CLI env name to back up. Defaults to the current env when omitted',
51
+ }),
52
+ yes: Flags.boolean({
53
+ char: 'y',
54
+ description: 'Confirm using --env when it targets a different env than the current env',
55
+ default: false,
56
+ }),
57
+ output: Flags.string({
58
+ char: 'o',
59
+ description: 'Download path. When omitted, save to the current directory using the remote backup filename',
60
+ }),
61
+ 'json-output': Flags.boolean({
62
+ char: 'j',
63
+ description: 'Print the final backup result as JSON',
64
+ default: false,
65
+ }),
66
+ };
67
+ async run() {
68
+ const { flags } = await this.parse(BackupCreate);
69
+ const requestedEnv = flags.env?.trim() || undefined;
70
+ const explicitEnvSelection = Boolean(requestedEnv && hasExplicitEnvSelection(this.argv));
71
+ const jsonOutput = Boolean(flags['json-output']);
72
+ if (explicitEnvSelection) {
73
+ const confirmed = await ensureCrossEnvConfirmed({
74
+ command: this,
75
+ requestedEnv,
76
+ yes: flags.yes,
77
+ });
78
+ if (!confirmed) {
79
+ return;
80
+ }
81
+ }
82
+ const { envName, env } = await resolveBackupTargetEnv(requestedEnv);
83
+ const envArgv = buildBackupEnvArgv({
84
+ requestedEnv,
85
+ explicitEnvSelection,
86
+ yes: flags.yes,
87
+ });
88
+ if (!jsonOutput) {
89
+ announceTargetEnv(envName);
90
+ }
91
+ await ensureBackupRuntimeCommands({
92
+ envName,
93
+ env,
94
+ commandIds: [
95
+ BACKUP_RUNTIME_COMMANDS.create,
96
+ BACKUP_RUNTIME_COMMANDS.status,
97
+ BACKUP_RUNTIME_COMMANDS.download,
98
+ ],
99
+ quiet: jsonOutput,
100
+ });
101
+ try {
102
+ if (!jsonOutput) {
103
+ startTask(`Creating backup for "${envName}"...`);
104
+ }
105
+ const createResponse = await runBackupCliJsonCommand(['api', 'backup', 'create', ...envArgv], { errorName: 'nb api backup create' });
106
+ const { name, inProgress } = readBackupCreateResult(createResponse);
107
+ const outputPath = await resolveBackupCreateOutputPath(flags.output, name);
108
+ const startedAt = Date.now();
109
+ let pending = inProgress;
110
+ while (pending) {
111
+ const now = Date.now();
112
+ const elapsedMs = now - startedAt;
113
+ if (elapsedMs >= BACKUP_CREATE_TIMEOUT_MS) {
114
+ throw new Error(formatBackupCreateTimeoutError(envName, name));
115
+ }
116
+ const elapsedSeconds = Math.max(1, Math.floor(elapsedMs / 1000));
117
+ if (!jsonOutput) {
118
+ updateTask(`Waiting for backup "${name}" to finish for "${envName}"... (${elapsedSeconds}s elapsed)`);
119
+ }
120
+ await sleep(BACKUP_POLL_INTERVAL_MS);
121
+ const statusResponse = await runBackupCliJsonCommand(['api', 'backup', 'status', '--name', name, ...envArgv], { errorName: 'nb api backup status' });
122
+ pending = readBackupInProgress(statusResponse, name);
123
+ }
124
+ if (!jsonOutput) {
125
+ updateTask(`Downloading backup "${name}" for "${envName}"...`);
126
+ }
127
+ const downloadResponse = await runBackupCliJsonCommand(['api', 'backup', 'download', '--name', name, '--output', outputPath, ...envArgv], { errorName: 'nb api backup download' });
128
+ const savedPath = readDownloadOutput(downloadResponse) ?? outputPath;
129
+ const result = {
130
+ env: envName,
131
+ name,
132
+ output: savedPath,
133
+ };
134
+ if (jsonOutput) {
135
+ this.log(JSON.stringify(result, null, 2));
136
+ return;
137
+ }
138
+ succeedTask(`Backup saved to ${savedPath}`);
139
+ }
140
+ catch (error) {
141
+ if (!jsonOutput) {
142
+ failTask(`Failed to create backup for "${envName}".`);
143
+ }
144
+ throw error;
145
+ }
146
+ }
147
+ }
@@ -0,0 +1,20 @@
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, loadHelpClass } from '@oclif/core';
10
+ export default class Backup extends Command {
11
+ static summary = 'Create or restore NocoBase backups';
12
+ async run() {
13
+ await this.parse(Backup);
14
+ const Help = await loadHelpClass(this.config);
15
+ await new Help(this.config, this.config.pjson.oclif.helpOptions ?? this.config.pjson.helpOptions).showHelp([
16
+ this.id ?? 'backup',
17
+ ...this.argv,
18
+ ]);
19
+ }
20
+ }