@nocobase/cli 2.1.0-alpha.24 → 2.1.0-alpha.26

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 (74) hide show
  1. package/README.md +41 -49
  2. package/README.zh-CN.md +38 -45
  3. package/bin/run.js +15 -0
  4. package/dist/commands/app/down.js +260 -0
  5. package/dist/commands/app/logs.js +98 -0
  6. package/dist/commands/app/restart.js +75 -0
  7. package/dist/commands/app/start.js +252 -0
  8. package/dist/commands/app/stop.js +98 -0
  9. package/dist/commands/app/upgrade.js +595 -0
  10. package/dist/commands/build.js +3 -48
  11. package/dist/commands/db/shared.js +19 -5
  12. package/dist/commands/dev.js +3 -140
  13. package/dist/commands/down.js +3 -184
  14. package/dist/commands/download.js +4 -856
  15. package/dist/commands/env/add.js +33 -48
  16. package/dist/commands/env/auth.js +6 -13
  17. package/dist/commands/env/info.js +152 -0
  18. package/dist/commands/env/list.js +27 -18
  19. package/dist/commands/env/remove.js +4 -10
  20. package/dist/commands/env/shared.js +158 -0
  21. package/dist/commands/env/update.js +7 -13
  22. package/dist/commands/env/use.js +5 -13
  23. package/dist/commands/{prompts-stages.js → examples/prompts-stages.js} +3 -3
  24. package/dist/commands/{prompts-test.js → examples/prompts-test.js} +3 -3
  25. package/dist/commands/init.js +270 -64
  26. package/dist/commands/install.js +352 -86
  27. package/dist/commands/logs.js +3 -81
  28. package/dist/commands/plugin/disable.js +64 -0
  29. package/dist/commands/plugin/enable.js +64 -0
  30. package/dist/commands/plugin/list.js +62 -0
  31. package/dist/commands/pm/disable.js +3 -54
  32. package/dist/commands/pm/enable.js +3 -54
  33. package/dist/commands/pm/list.js +3 -45
  34. package/dist/commands/restart.js +12 -0
  35. package/dist/commands/scaffold/migration.js +1 -1
  36. package/dist/commands/scaffold/plugin.js +1 -1
  37. package/dist/commands/self/check.js +1 -1
  38. package/dist/commands/self/update.js +13 -3
  39. package/dist/commands/skills/check.js +11 -5
  40. package/dist/commands/skills/index.js +1 -1
  41. package/dist/commands/skills/install.js +20 -7
  42. package/dist/commands/skills/remove.js +71 -0
  43. package/dist/commands/skills/update.js +27 -7
  44. package/dist/commands/source/build.js +58 -0
  45. package/dist/commands/source/dev.js +157 -0
  46. package/dist/commands/source/download.js +866 -0
  47. package/dist/commands/source/test.js +467 -0
  48. package/dist/commands/start.js +3 -202
  49. package/dist/commands/stop.js +3 -81
  50. package/dist/commands/test.js +3 -457
  51. package/dist/commands/upgrade.js +3 -574
  52. package/dist/help/runtime-help.js +3 -0
  53. package/dist/lib/api-client.js +22 -7
  54. package/dist/lib/app-health.js +126 -0
  55. package/dist/lib/app-managed-resources.js +264 -0
  56. package/dist/lib/app-runtime.js +16 -5
  57. package/dist/lib/auth-store.js +162 -43
  58. package/dist/lib/bootstrap.js +13 -12
  59. package/dist/lib/cli-home.js +38 -6
  60. package/dist/lib/cli-locale.js +15 -1
  61. package/dist/lib/env-auth.js +3 -3
  62. package/dist/lib/env-config.js +80 -0
  63. package/dist/lib/generated-command.js +10 -2
  64. package/dist/lib/http-request.js +49 -0
  65. package/dist/lib/prompt-web-ui.js +13 -6
  66. package/dist/lib/resource-command.js +10 -2
  67. package/dist/lib/runtime-generator.js +1 -1
  68. package/dist/lib/self-manager.js +1 -1
  69. package/dist/lib/skills-manager.js +173 -79
  70. package/dist/lib/startup-update.js +203 -0
  71. package/dist/locale/en-US.json +4 -1
  72. package/dist/locale/zh-CN.json +4 -1
  73. package/package.json +27 -4
  74. package/dist/commands/ps.js +0 -116
package/README.md CHANGED
@@ -58,7 +58,7 @@ nb init --ui
58
58
 
59
59
  `nb init` can either connect to an existing NocoBase app or install a new one.
60
60
  When creating a new app, it can also install NocoBase AI coding skills
61
- (`nocobase/skills`) for the current workspace.
61
+ (`nocobase/skills`) globally.
62
62
 
63
63
  ### Non-Interactive Setup
64
64
 
@@ -113,15 +113,9 @@ If `nb init` was interrupted after the env config had already been saved, you ca
113
113
  nb init --env app1 --resume
114
114
  ```
115
115
 
116
- The advanced low-level equivalent is:
117
-
118
- ```bash
119
- nb install --env app1 --resume
120
- ```
121
-
122
116
  `--resume` reuses the saved workspace env config for app, source, database, and env connection settings. In interactive mode, it only asks for any missing setup-only values.
123
117
 
124
- In non-interactive mode, pass these setup-only flags again because they are not saved in env config:
118
+ In non-interactive resume mode, `nb init --resume --yes` uses default initialization values unless these flags are passed explicitly:
125
119
 
126
120
  - `--lang`
127
121
  - `--root-username`
@@ -134,37 +128,32 @@ In non-interactive mode, pass these setup-only flags again because they are not
134
128
  | Command | Description |
135
129
  | --- | --- |
136
130
  | `nb init` | Set up NocoBase and connect it as a CLI env for coding agents. |
137
- | `nb install` | Advanced command used by `nb init` to install a local NocoBase app and save env config. In most cases, use `nb init` instead. |
138
- | `nb download` | Advanced command used by `nb init` or `nb upgrade` to fetch NocoBase from Docker, npm, or Git. It is rarely used directly. |
139
- | `nb start` | Start the selected local app or Docker container. |
140
- | `nb stop` | Stop the selected local app or Docker container. |
141
- | `nb dev` | Run development mode for npm/Git source envs. |
142
- | `nb logs` | Show app logs for npm/Git or Docker envs. |
143
- | `nb ps` | Show runtime status for configured envs. |
131
+ | `nb app` | Manage app runtimes: start, stop, restart, logs, cleanup, and upgrades. |
132
+ | `nb source` | Manage the local source project: download, develop, build, and test. |
144
133
  | `nb db` | Inspect or manage built-in database runtime status for local envs. |
145
- | `nb upgrade` | Refresh code/image and restart the selected app. |
146
- | `nb down` | Stop and remove local runtime containers for an env. |
147
134
  | `nb env` | Manage saved CLI env connections. |
148
135
  | `nb api` | Call NocoBase API resources from the CLI. |
149
- | `nb pm` | Manage plugins for the selected NocoBase env. |
136
+ | `nb plugin` | Manage plugins for the selected NocoBase env. |
150
137
  | `nb self` | Check or update the installed NocoBase CLI. |
151
- | `nb skills` | Check, install, or update NocoBase AI coding skills for the current workspace. |
138
+ | `nb skills` | Check, install, or update global NocoBase AI coding skills. |
152
139
 
153
- Recommended style: use `--env` explicitly for app/runtime commands. `-e` is the short form:
140
+ Recommended style: pass the env name explicitly when operating on a specific env. Runtime commands accept `--env`, and `nb env info` also accepts a positional env name:
154
141
 
155
142
  ```bash
156
- nb start --env app1
157
- nb logs --env app1
158
- nb ps --env app1
143
+ nb app start --env app1
144
+ nb app restart --env app1
145
+ nb app logs --env app1
146
+ nb env info app1
159
147
  nb db ps --env app1
160
148
  ```
161
149
 
162
150
  Equivalent shorthand examples:
163
151
 
164
152
  ```bash
165
- nb start -e app1
166
- nb logs -e app1
167
- nb upgrade -e app1
153
+ nb app start -e app1
154
+ nb app restart -e app1
155
+ nb app logs -e app1
156
+ nb app upgrade -e app1
168
157
  nb db start -e app1
169
158
  ```
170
159
 
@@ -183,7 +172,7 @@ Update the CLI when it is installed globally with npm:
183
172
  nb self update
184
173
  ```
185
174
 
186
- Check whether the current workspace already has the NocoBase AI coding skills:
175
+ Check whether the global NocoBase AI coding skills are installed:
187
176
 
188
177
  ```bash
189
178
  nb skills check
@@ -205,17 +194,18 @@ Docker envs are managed through saved Docker containers and images:
205
194
 
206
195
  ```bash
207
196
  nb init --env app1 --yes --source docker --version alpha
208
- nb start --env app1
209
- nb logs --env app1
210
- nb stop --env app1
197
+ nb app start --env app1
198
+ nb app restart --env app1
199
+ nb app logs --env app1
200
+ nb app stop --env app1
211
201
  ```
212
202
 
213
203
  Docker downloads support platform selection:
214
204
 
215
205
  ```bash
216
- nb download --source docker --version alpha --docker-platform auto
217
- nb download --source docker --version alpha --docker-platform linux/amd64
218
- nb download --source docker --version alpha --docker-platform linux/arm64
206
+ nb source download --source docker --version alpha --docker-platform auto
207
+ nb source download --source docker --version alpha --docker-platform linux/amd64
208
+ nb source download --source docker --version alpha --docker-platform linux/arm64
219
209
  ```
220
210
 
221
211
  ### npm and Git
@@ -224,11 +214,11 @@ npm and Git envs use a local source directory and can run development mode:
224
214
 
225
215
  ```bash
226
216
  nb init --env app1 --yes --source git --version alpha
227
- nb dev --env app1
217
+ nb source dev --env app1
228
218
  ```
229
219
 
230
- `nb dev` only supports npm/Git source envs. Docker envs can be inspected with
231
- `nb logs`, and remote envs only support API/env operations.
220
+ `nb source dev` only supports npm/Git source envs. Docker envs can be inspected with
221
+ `nb app logs`, and remote envs only support API/env operations.
232
222
 
233
223
  ### Existing NocoBase App
234
224
 
@@ -236,7 +226,7 @@ To connect an existing app, use `nb init` and choose the existing-app setup
236
226
  path, or add the env directly:
237
227
 
238
228
  ```bash
239
- nb env add app1 --base-url http://localhost:13000/api
229
+ nb env add app1 --api-base-url http://localhost:13000/api
240
230
  ```
241
231
 
242
232
  `nb env add` will start the authentication flow automatically when needed.
@@ -246,14 +236,14 @@ nb env add app1 --base-url http://localhost:13000/api
246
236
  Upgrade refreshes the saved source or image, then restarts the app:
247
237
 
248
238
  ```bash
249
- nb upgrade --env app1
239
+ nb app upgrade --env app1
250
240
  ```
251
241
 
252
242
  Use `--skip-code-update` or `-s` to restart with the saved local code or Docker
253
243
  image without downloading updates first:
254
244
 
255
245
  ```bash
256
- nb upgrade --env app1 -s
246
+ nb app upgrade --env app1 -s
257
247
  ```
258
248
 
259
249
  ## Database Commands
@@ -279,23 +269,19 @@ Notes:
279
269
  Bring down a local env:
280
270
 
281
271
  ```bash
282
- nb down --env app1
272
+ nb app down --env app1
283
273
  ```
284
274
 
285
- By default, `nb down` stops the app and removes app/database containers if they
286
- exist. It keeps user data, source files, and CLI env config.
275
+ By default, `nb app down` stops the app and removes app/database containers if they
276
+ exist. For local envs, it also deletes the saved local app files. It keeps storage data and CLI env config.
287
277
 
288
278
  Use explicit flags for destructive cleanup:
289
279
 
290
280
  ```bash
291
- nb down --env app1 --remove-data
292
- nb down --env app1 --remove-source
293
- nb down --env app1 --remove-env
281
+ nb app down --env app1 --all --yes
294
282
  ```
295
283
 
296
- - `--remove-data`: delete storage and managed database data. This requires confirmation unless `--yes` is used.
297
- - `--remove-source`: delete the npm/Git source directory.
298
- - `--remove-env`: remove the saved CLI env config.
284
+ - `--all`: delete everything for the env, including storage data and the saved env config. This requires `--yes`.
299
285
 
300
286
  ## Environment Management
301
287
 
@@ -305,12 +291,18 @@ Show the current env:
305
291
  nb env
306
292
  ```
307
293
 
308
- List configured envs:
294
+ List configured envs with token-verified API status:
309
295
 
310
296
  ```bash
311
297
  nb env list
312
298
  ```
313
299
 
300
+ Show details for one env:
301
+
302
+ ```bash
303
+ nb env info app1
304
+ ```
305
+
314
306
  Switch the current env:
315
307
 
316
308
  ```bash
package/README.zh-CN.md CHANGED
@@ -53,7 +53,7 @@ nb init
53
53
  nb init --ui
54
54
  ```
55
55
 
56
- `nb init` 可以连接已有的 NocoBase 应用,也可以安装一个新的 NocoBase 应用。创建新应用时,还可以为当前工作区安装 NocoBase AI coding skills (`nocobase/skills`)。
56
+ `nb init` 可以连接已有的 NocoBase 应用,也可以安装一个新的 NocoBase 应用。创建新应用时,还可以全局安装 NocoBase AI coding skills (`nocobase/skills`)。
57
57
 
58
58
  ### 非交互式初始化
59
59
 
@@ -108,15 +108,9 @@ nb init --env app1 --yes --source git --version fix/cli-v2
108
108
  nb init --env app1 --resume
109
109
  ```
110
110
 
111
- 对应的底层高级命令是:
112
-
113
- ```bash
114
- nb install --env app1 --resume
115
- ```
116
-
117
111
  `--resume` 会复用工作区里已保存的 env config,包括应用、source、数据库和 env 连接相关配置。在交互模式下,只会继续补齐缺失的初始化参数。
118
112
 
119
- 在非交互模式下,需要重新传这些只用于初始化、不会保存到 env config 的参数:
113
+ 在非交互恢复模式下,如果没有显式传入这些参数,`nb init --resume --yes` 会使用默认初始化值:
120
114
 
121
115
  - `--lang`
122
116
  - `--root-username`
@@ -129,35 +123,30 @@ nb install --env app1 --resume
129
123
  | 命令 | 说明 |
130
124
  | --- | --- |
131
125
  | `nb init` | 初始化 NocoBase,并连接为 coding agent 可使用的 CLI env。 |
132
- | `nb install` | `nb init` 内部会使用的高级命令,用于安装本地 NocoBase 应用并保存 env 配置。通常建议直接使用 `nb init`。 |
133
- | `nb download` | `nb init` 或 `nb upgrade` 会使用的高级命令,用于从 Docker、npm 或 Git 获取 NocoBase。通常很少直接使用。 |
134
- | `nb start` | 启动选中的本地应用或 Docker 容器。 |
135
- | `nb stop` | 停止选中的本地应用或 Docker 容器。 |
136
- | `nb dev` | 为 npm/Git 源码 env 启动开发模式。 |
137
- | `nb logs` | 查看 npm/Git 或 Docker env 的应用日志。 |
138
- | `nb ps` | 查看已配置 env 的运行状态。 |
126
+ | `nb app` | 管理应用运行态:启动、停止、重启、日志、状态、清理和升级。 |
127
+ | `nb source` | 管理本地源码工程:下载、开发、构建和测试。 |
139
128
  | `nb db` | 查看或管理本地 env 的内置数据库运行状态。 |
140
- | `nb upgrade` | 更新源码/镜像并重启选中的应用。 |
141
- | `nb down` | 停止并移除某个 env 的本地运行容器。 |
142
129
  | `nb env` | 管理已保存的 CLI env 连接。 |
143
130
  | `nb api` | 通过 CLI 调用 NocoBase API 资源。 |
144
- | `nb pm` | 管理选中 NocoBase env 的插件。 |
131
+ | `nb plugin` | 管理选中 NocoBase env 的插件。 |
145
132
 
146
- 推荐在应用和运行时相关命令里显式使用 `--env`;`-e` 是它的简写:
133
+ 推荐在操作指定 env 时显式传入 env 名称。运行时命令支持 `--env`,`nb env info` 也支持位置参数:
147
134
 
148
135
  ```bash
149
- nb start --env app1
150
- nb logs --env app1
151
- nb ps --env app1
136
+ nb app start --env app1
137
+ nb app restart --env app1
138
+ nb app logs --env app1
139
+ nb env info app1
152
140
  nb db ps --env app1
153
141
  ```
154
142
 
155
143
  等价的简写示例:
156
144
 
157
145
  ```bash
158
- nb start -e app1
159
- nb logs -e app1
160
- nb upgrade -e app1
146
+ nb app start -e app1
147
+ nb app restart -e app1
148
+ nb app logs -e app1
149
+ nb app upgrade -e app1
161
150
  nb db start -e app1
162
151
  ```
163
152
 
@@ -169,17 +158,18 @@ Docker env 会通过已保存的 Docker 容器和镜像进行管理:
169
158
 
170
159
  ```bash
171
160
  nb init --env app1 --yes --source docker --version alpha
172
- nb start --env app1
173
- nb logs --env app1
174
- nb stop --env app1
161
+ nb app start --env app1
162
+ nb app restart --env app1
163
+ nb app logs --env app1
164
+ nb app stop --env app1
175
165
  ```
176
166
 
177
167
  Docker 下载支持指定平台:
178
168
 
179
169
  ```bash
180
- nb download --source docker --version alpha --docker-platform auto
181
- nb download --source docker --version alpha --docker-platform linux/amd64
182
- nb download --source docker --version alpha --docker-platform linux/arm64
170
+ nb source download --source docker --version alpha --docker-platform auto
171
+ nb source download --source docker --version alpha --docker-platform linux/amd64
172
+ nb source download --source docker --version alpha --docker-platform linux/arm64
183
173
  ```
184
174
 
185
175
  ### npm 和 Git
@@ -188,17 +178,17 @@ npm 和 Git env 会使用本地源码目录,并支持开发模式:
188
178
 
189
179
  ```bash
190
180
  nb init --env app1 --yes --source git --version alpha
191
- nb dev --env app1
181
+ nb source dev --env app1
192
182
  ```
193
183
 
194
- `nb dev` 只支持 npm/Git 源码 env。Docker env 可以通过 `nb logs` 查看日志,remote env 只支持 API 和 env 相关操作。
184
+ `nb source dev` 只支持 npm/Git 源码 env。Docker env 可以通过 `nb app logs` 查看日志,remote env 只支持 API 和 env 相关操作。
195
185
 
196
186
  ### 已有 NocoBase 应用
197
187
 
198
188
  如果要连接已有应用,可以运行 `nb init` 并选择已有应用流程,也可以直接添加 env:
199
189
 
200
190
  ```bash
201
- nb env add app1 --base-url http://localhost:13000/api
191
+ nb env add app1 --api-base-url http://localhost:13000/api
202
192
  ```
203
193
 
204
194
  `nb env add` 会在需要时自动进入认证流程。
@@ -208,13 +198,13 @@ nb env add app1 --base-url http://localhost:13000/api
208
198
  升级会更新已保存的源码或镜像,然后重启应用:
209
199
 
210
200
  ```bash
211
- nb upgrade --env app1
201
+ nb app upgrade --env app1
212
202
  ```
213
203
 
214
204
  如果只想使用当前已保存的本地源码或 Docker 镜像重启应用,可以使用 `--skip-code-update` 或 `-s`:
215
205
 
216
206
  ```bash
217
- nb upgrade --env app1 -s
207
+ nb app upgrade --env app1 -s
218
208
  ```
219
209
 
220
210
  ## 数据库命令
@@ -240,22 +230,19 @@ nb db logs --env app1
240
230
  关闭并清理某个本地 env:
241
231
 
242
232
  ```bash
243
- nb down --env app1
233
+ nb app down --env app1
244
234
  ```
245
235
 
246
- 默认情况下,`nb down` 会停止应用,并在存在时移除应用容器和数据库容器。它不会删除用户数据、源码文件和 CLI env 配置。
236
+ 默认情况下,`nb app down` 会停止应用,并在存在时移除应用容器和数据库容器。它不会删除用户数据、源码文件和 CLI env 配置。
237
+ 对于本地 env,它还会删除已保存的本地 app 目录。默认仍会保留 storage 数据和 CLI env 配置。
247
238
 
248
239
  如需执行破坏性清理,需要显式指定参数:
249
240
 
250
241
  ```bash
251
- nb down --env app1 --remove-data
252
- nb down --env app1 --remove-source
253
- nb down --env app1 --remove-env
242
+ nb app down --env app1 --all --yes
254
243
  ```
255
244
 
256
- - `--remove-data`:删除 storage 和托管数据库数据。除非使用 `--yes`,否则需要二次确认。
257
- - `--remove-source`:删除 npm/Git 源码目录。
258
- - `--remove-env`:删除已保存的 CLI env 配置。
245
+ - `--all`:删除该 env 的所有内容,包括 storage 数据和已保存的 CLI env 配置。必须同时传 `--yes`。
259
246
 
260
247
  ## Env 管理
261
248
 
@@ -265,12 +252,18 @@ nb down --env app1 --remove-env
265
252
  nb env
266
253
  ```
267
254
 
268
- 查看已配置的 env
255
+ 查看已配置的 env 及 Token 验证后的 API 状态:
269
256
 
270
257
  ```bash
271
258
  nb env list
272
259
  ```
273
260
 
261
+ 查看某个 env 的详情:
262
+
263
+ ```bash
264
+ nb env info app1
265
+ ```
266
+
274
267
  切换当前 env:
275
268
 
276
269
  ```bash
package/bin/run.js CHANGED
@@ -61,6 +61,10 @@ if (isDev && !process.env._NOCO_CLI_TSX_CHILD) {
61
61
 
62
62
  const bootstrapPath = isDev ? path.join(root, 'src/lib/bootstrap.ts') : path.join(root, 'dist/lib/bootstrap.js');
63
63
  const { ensureRuntimeFromArgv } = await import(pathToFileURL(bootstrapPath).href);
64
+ const startupUpdatePath = isDev
65
+ ? path.join(root, 'src/lib/startup-update.ts')
66
+ : path.join(root, 'dist/lib/startup-update.js');
67
+ const { maybeRunStartupUpdatePrompt } = await import(pathToFileURL(startupUpdatePath).href);
64
68
  const { flush, run, settings } = await import('@oclif/core');
65
69
 
66
70
  if (isDev) {
@@ -102,6 +106,17 @@ function formatCliEntryError(error, argv) {
102
106
 
103
107
  try {
104
108
  const argv = process.argv.slice(2);
109
+ const startupUpdate = await maybeRunStartupUpdatePrompt(argv);
110
+ if (startupUpdate.kind === 'updated') {
111
+ const result = spawnSync(process.execPath, process.argv.slice(1), {
112
+ stdio: 'inherit',
113
+ env: {
114
+ ...process.env,
115
+ NB_SKIP_STARTUP_UPDATE: '1',
116
+ },
117
+ });
118
+ process.exit(result.status === null ? 1 : result.status);
119
+ }
105
120
  if (argv[0] === 'api') {
106
121
  await ensureRuntimeFromArgv(argv, {
107
122
  configFile: path.join(root, 'nocobase-ctl.config.json'),
@@ -0,0 +1,260 @@
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 * as p from '@clack/prompts';
10
+ import { Command, Flags } from '@oclif/core';
11
+ import fsp from 'node:fs/promises';
12
+ import os from 'node:os';
13
+ import path from 'node:path';
14
+ import { buildDockerDbContainerName, formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runLocalNocoBaseCommand, } from '../../lib/app-runtime.js';
15
+ import { removeEnv } from '../../lib/auth-store.js';
16
+ import { resolveConfiguredEnvPath } from '../../lib/cli-home.js';
17
+ import { commandOutput, commandSucceeds, run } from '../../lib/run-npm.js';
18
+ import { failTask, isInteractiveTerminal, printInfo, startTask, succeedTask, } from '../../lib/ui.js';
19
+ function resolveConfiguredPath(value) {
20
+ return resolveConfiguredEnvPath(value);
21
+ }
22
+ function assertSafeRemovalPath(target, label) {
23
+ const resolved = path.resolve(target);
24
+ const cwd = path.resolve(process.cwd());
25
+ const home = path.resolve(os.homedir());
26
+ const root = path.parse(resolved).root;
27
+ if (resolved === root || resolved === cwd || resolved === home) {
28
+ throw new Error(`Refusing to remove ${label} at "${resolved}" because it is too broad.`);
29
+ }
30
+ }
31
+ async function removePathIfExists(target, label) {
32
+ const resolved = path.resolve(target);
33
+ assertSafeRemovalPath(resolved, label);
34
+ await fsp.rm(resolved, { recursive: true, force: true });
35
+ }
36
+ async function dockerContainerExists(containerName) {
37
+ return await commandSucceeds('docker', ['container', 'inspect', containerName]);
38
+ }
39
+ async function removeDockerContainerIfExists(containerName) {
40
+ if (!(await dockerContainerExists(containerName))) {
41
+ return 'missing';
42
+ }
43
+ await run('docker', ['rm', '-f', containerName], {
44
+ errorName: 'docker rm',
45
+ stdio: 'ignore',
46
+ });
47
+ return 'removed';
48
+ }
49
+ async function dockerNetworkExists(networkName) {
50
+ return await commandSucceeds('docker', ['network', 'inspect', networkName]);
51
+ }
52
+ async function dockerNetworkHasActiveEndpoints(networkName) {
53
+ try {
54
+ const output = await commandOutput('docker', [
55
+ 'network',
56
+ 'inspect',
57
+ networkName,
58
+ '--format',
59
+ '{{json .Containers}}',
60
+ ], {
61
+ errorName: 'docker network inspect',
62
+ });
63
+ const containers = JSON.parse(output || '{}');
64
+ return Boolean(containers && typeof containers === 'object' && Object.keys(containers).length > 0);
65
+ }
66
+ catch {
67
+ return false;
68
+ }
69
+ }
70
+ async function removeDockerNetworkIfUnused(networkName) {
71
+ if (!(await dockerNetworkExists(networkName))) {
72
+ return 'missing';
73
+ }
74
+ try {
75
+ await run('docker', ['network', 'rm', networkName], {
76
+ errorName: 'docker network rm',
77
+ stdio: 'ignore',
78
+ });
79
+ return 'removed';
80
+ }
81
+ catch (error) {
82
+ const message = error instanceof Error ? error.message : String(error);
83
+ if (/has active endpoints|is in use|active endpoints/i.test(message)
84
+ || (await dockerNetworkExists(networkName) && await dockerNetworkHasActiveEndpoints(networkName))) {
85
+ return 'in-use';
86
+ }
87
+ throw error;
88
+ }
89
+ }
90
+ function builtinDbContainerName(runtime) {
91
+ if (!runtime.env.config.builtinDb) {
92
+ return undefined;
93
+ }
94
+ const dbDialect = String(runtime.env.config.dbDialect ?? 'postgres').trim() || 'postgres';
95
+ const workspaceName = runtime.workspaceName;
96
+ return buildDockerDbContainerName(runtime.envName, dbDialect, workspaceName);
97
+ }
98
+ function managedDockerNetworkName(runtime) {
99
+ return runtime.workspaceName?.trim() || undefined;
100
+ }
101
+ async function confirmDownAll(envName, yes) {
102
+ if (yes) {
103
+ return true;
104
+ }
105
+ if (!isInteractiveTerminal()) {
106
+ throw new Error(`\`nb app down --all\` needs confirmation. Re-run with --yes to delete everything for "${envName}" in non-interactive mode.`);
107
+ }
108
+ const answer = await p.confirm({
109
+ message: `Delete everything for "${envName}"? This removes the app, managed containers, storage data, and the saved CLI env config.`,
110
+ active: 'yes',
111
+ inactive: 'no',
112
+ initialValue: false,
113
+ });
114
+ if (p.isCancel(answer)) {
115
+ p.cancel('Down cancelled.');
116
+ return false;
117
+ }
118
+ return answer;
119
+ }
120
+ function formatDownFailure(envName, message) {
121
+ return [
122
+ `Couldn't bring down NocoBase for "${envName}".`,
123
+ 'Some local runtime resources may still exist. Check Docker or the local app process, then try again.',
124
+ `Details: ${message}`,
125
+ ].join('\n');
126
+ }
127
+ export default class AppDown extends Command {
128
+ static hidden = false;
129
+ static description = 'Bring down the selected env by removing runtime containers and the saved local app files. Storage data and env config are kept unless explicitly requested.';
130
+ static examples = [
131
+ '<%= config.bin %> <%= command.id %> --env app1',
132
+ '<%= config.bin %> <%= command.id %> --env app1 --all --yes',
133
+ ];
134
+ static flags = {
135
+ env: Flags.string({
136
+ char: 'e',
137
+ description: 'CLI env name to bring down. Defaults to the current env when omitted',
138
+ }),
139
+ all: Flags.boolean({
140
+ description: 'Delete everything for this env, including storage data and the saved env config',
141
+ default: false,
142
+ }),
143
+ yes: Flags.boolean({
144
+ char: 'y',
145
+ description: 'Confirm destructive actions without prompting',
146
+ default: false,
147
+ }),
148
+ verbose: Flags.boolean({
149
+ description: 'Show raw output from shutdown commands',
150
+ default: false,
151
+ }),
152
+ };
153
+ async run() {
154
+ const { flags } = await this.parse(AppDown);
155
+ const requestedEnv = flags.env?.trim() || undefined;
156
+ const removeData = Boolean(flags.all);
157
+ const removeEnvConfig = Boolean(flags.all);
158
+ const runtime = await resolveManagedAppRuntime(requestedEnv);
159
+ if (!runtime) {
160
+ this.error(formatMissingManagedAppEnvMessage(requestedEnv));
161
+ }
162
+ if (runtime.kind === 'http') {
163
+ this.error([
164
+ `Can't bring down "${runtime.envName}" from this machine.`,
165
+ 'This env only has an API connection, so there is no saved local app, Docker app, or managed database to remove here.',
166
+ 'Use `nb env remove` if you only want to remove the CLI connection config.',
167
+ ].join('\n'));
168
+ }
169
+ if (runtime.kind === 'ssh') {
170
+ this.error([
171
+ `Can't bring down "${runtime.envName}" yet.`,
172
+ 'SSH env support is reserved but not implemented yet.',
173
+ 'Use `nb env remove` if you only want to remove the saved CLI config for now.',
174
+ ].join('\n'));
175
+ }
176
+ if (flags.all) {
177
+ let confirmed = false;
178
+ try {
179
+ confirmed = await confirmDownAll(runtime.envName, flags.yes);
180
+ }
181
+ catch (error) {
182
+ this.error(error instanceof Error ? error.message : String(error));
183
+ }
184
+ if (!confirmed) {
185
+ return;
186
+ }
187
+ }
188
+ try {
189
+ if (runtime.kind === 'docker') {
190
+ startTask(`Removing Docker app container for "${runtime.envName}"...`);
191
+ const state = await removeDockerContainerIfExists(runtime.containerName);
192
+ succeedTask(state === 'removed'
193
+ ? `Docker app container removed for "${runtime.envName}".`
194
+ : `No Docker app container found for "${runtime.envName}".`);
195
+ }
196
+ else {
197
+ startTask(`Stopping local NocoBase app for "${runtime.envName}"...`);
198
+ await runLocalNocoBaseCommand(runtime, ['pm2', 'kill'], {
199
+ stdio: flags.verbose ? 'inherit' : 'ignore',
200
+ });
201
+ succeedTask(`Local NocoBase app stopped for "${runtime.envName}".`);
202
+ }
203
+ if (runtime.kind === 'local' || runtime.kind === 'docker') {
204
+ const dbContainerName = builtinDbContainerName(runtime);
205
+ if (dbContainerName) {
206
+ startTask(`Removing built-in database container for "${runtime.envName}"...`);
207
+ const state = await removeDockerContainerIfExists(dbContainerName);
208
+ succeedTask(state === 'removed'
209
+ ? `Built-in database container removed for "${runtime.envName}".`
210
+ : `No built-in database container found for "${runtime.envName}".`);
211
+ }
212
+ const networkName = managedDockerNetworkName(runtime);
213
+ if (networkName) {
214
+ startTask(`Removing Docker network for "${runtime.envName}" if unused...`);
215
+ const state = await removeDockerNetworkIfUnused(networkName);
216
+ if (state === 'removed') {
217
+ succeedTask(`Docker network removed for "${runtime.envName}".`);
218
+ }
219
+ else if (state === 'missing') {
220
+ succeedTask(`No Docker network found for "${runtime.envName}".`);
221
+ }
222
+ else {
223
+ succeedTask(`Docker network is still in use for "${runtime.envName}". Keeping it.`);
224
+ }
225
+ }
226
+ }
227
+ if (runtime.kind === 'local') {
228
+ const localAppPath = resolveConfiguredPath(runtime.env.config.appRootPath) || runtime.projectRoot;
229
+ if (localAppPath) {
230
+ startTask(`Removing local app files for "${runtime.envName}"...`);
231
+ await removePathIfExists(localAppPath, `app files for "${runtime.envName}"`);
232
+ succeedTask(`Local app files removed for "${runtime.envName}".`);
233
+ }
234
+ else {
235
+ printInfo(`No saved local app path found for "${runtime.envName}".`);
236
+ }
237
+ }
238
+ if (removeData) {
239
+ const configuredStoragePath = resolveConfiguredPath(runtime.env.config.storagePath);
240
+ if (configuredStoragePath) {
241
+ startTask(`Removing storage data for "${runtime.envName}"...`);
242
+ await removePathIfExists(configuredStoragePath, `storage data for "${runtime.envName}"`);
243
+ succeedTask(`Storage data removed for "${runtime.envName}".`);
244
+ }
245
+ else {
246
+ printInfo(`No saved storage path found for "${runtime.envName}".`);
247
+ }
248
+ }
249
+ if (removeEnvConfig) {
250
+ startTask(`Removing saved CLI env config for "${runtime.envName}"...`);
251
+ const result = await removeEnv(runtime.envName);
252
+ succeedTask(`Saved CLI env config removed for "${runtime.envName}"${result.currentEnv ? ` (current env: ${result.currentEnv})` : ''}.`);
253
+ }
254
+ }
255
+ catch (error) {
256
+ failTask(`Failed to bring down NocoBase for "${runtime.envName}".`);
257
+ this.error(formatDownFailure(runtime.envName, error instanceof Error ? error.message : String(error)));
258
+ }
259
+ }
260
+ }