@nocobase/cli 2.1.0-beta.8 → 2.1.0

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 (263) hide show
  1. package/assets/env-proxy/nginx/app.conf.tpl +23 -0
  2. package/assets/env-proxy/nginx/nocobase.conf.tpl +5 -0
  3. package/assets/env-proxy/nginx/snippets/dist-location.conf +5 -0
  4. package/assets/env-proxy/nginx/snippets/gzip.conf +17 -0
  5. package/assets/env-proxy/nginx/snippets/log-format-http.conf +13 -0
  6. package/assets/env-proxy/nginx/snippets/maps-http.conf +14 -0
  7. package/assets/env-proxy/nginx/snippets/mime-types.conf +98 -0
  8. package/assets/env-proxy/nginx/snippets/proxy-location.conf +17 -0
  9. package/assets/env-proxy/nginx/snippets/spa-location.conf +6 -0
  10. package/assets/env-proxy/nginx/snippets/uploads-location.conf +21 -0
  11. package/bin/run.cmd +3 -0
  12. package/bin/run.js +145 -0
  13. package/bin/session-env.js +39 -0
  14. package/dist/commands/api/resource/create.js +15 -0
  15. package/dist/commands/api/resource/destroy.js +15 -0
  16. package/dist/commands/api/resource/get.js +15 -0
  17. package/dist/commands/api/resource/index.js +20 -0
  18. package/dist/commands/api/resource/list.js +16 -0
  19. package/dist/commands/api/resource/query.js +15 -0
  20. package/dist/commands/api/resource/update.js +15 -0
  21. package/dist/commands/app/autostart/disable.js +55 -0
  22. package/dist/commands/app/autostart/enable.js +55 -0
  23. package/dist/commands/app/autostart/list.js +37 -0
  24. package/dist/commands/app/autostart/run.js +84 -0
  25. package/dist/commands/app/autostart/shared.js +49 -0
  26. package/dist/commands/app/destroy.js +234 -0
  27. package/dist/commands/app/down.js +71 -0
  28. package/dist/commands/app/logs.js +115 -0
  29. package/dist/commands/app/restart.js +229 -0
  30. package/dist/commands/app/shared.js +123 -0
  31. package/dist/commands/app/start.js +416 -0
  32. package/dist/commands/app/stop.js +183 -0
  33. package/dist/commands/app/upgrade.js +523 -0
  34. package/dist/commands/backup/create.js +147 -0
  35. package/dist/commands/backup/index.js +20 -0
  36. package/dist/commands/backup/restore.js +105 -0
  37. package/{src/cli.js → dist/commands/build.js} +4 -11
  38. package/dist/commands/config/delete.js +42 -0
  39. package/dist/commands/config/get.js +39 -0
  40. package/dist/commands/config/index.js +20 -0
  41. package/dist/commands/config/list.js +29 -0
  42. package/dist/commands/config/set.js +49 -0
  43. package/dist/commands/db/check.js +240 -0
  44. package/dist/commands/db/logs.js +85 -0
  45. package/dist/commands/db/ps.js +47 -0
  46. package/dist/commands/db/shared.js +96 -0
  47. package/dist/commands/db/start.js +86 -0
  48. package/dist/commands/db/stop.js +71 -0
  49. package/{templates/plugin/src/client/models/index.ts → dist/commands/dev.js} +4 -4
  50. package/{src/commands/locale/react-js-cron/index.js → dist/commands/down.js} +3 -8
  51. package/dist/commands/download.js +13 -0
  52. package/dist/commands/env/add.js +406 -0
  53. package/dist/commands/env/auth.js +189 -0
  54. package/dist/commands/env/current.js +21 -0
  55. package/dist/commands/env/info.js +202 -0
  56. package/dist/commands/env/list.js +43 -0
  57. package/dist/commands/env/remove.js +174 -0
  58. package/dist/commands/env/shared.js +204 -0
  59. package/dist/commands/env/status.js +93 -0
  60. package/dist/commands/env/update.js +448 -0
  61. package/dist/commands/env/use.js +38 -0
  62. package/dist/commands/examples/prompts-stages.js +150 -0
  63. package/dist/commands/examples/prompts-test.js +181 -0
  64. package/dist/commands/init.js +1390 -0
  65. package/dist/commands/install.js +2609 -0
  66. package/dist/commands/license/activate.js +179 -0
  67. package/dist/commands/license/env.js +94 -0
  68. package/dist/commands/license/generate-id.js +108 -0
  69. package/dist/commands/license/id.js +70 -0
  70. package/dist/commands/license/index.js +20 -0
  71. package/dist/commands/license/plugins/clean.js +115 -0
  72. package/dist/commands/license/plugins/index.js +20 -0
  73. package/dist/commands/license/plugins/list.js +64 -0
  74. package/dist/commands/license/plugins/shared.js +382 -0
  75. package/dist/commands/license/plugins/sync.js +314 -0
  76. package/dist/commands/license/shared.js +423 -0
  77. package/dist/commands/license/status.js +64 -0
  78. package/dist/commands/logs.js +12 -0
  79. package/dist/commands/plugin/disable.js +86 -0
  80. package/dist/commands/plugin/enable.js +86 -0
  81. package/dist/commands/plugin/import.js +108 -0
  82. package/dist/commands/plugin/list.js +82 -0
  83. package/dist/commands/pm/disable.js +12 -0
  84. package/dist/commands/pm/enable.js +12 -0
  85. package/dist/commands/pm/list.js +12 -0
  86. package/dist/commands/proxy/caddy/current.js +17 -0
  87. package/dist/commands/proxy/caddy/generate.js +69 -0
  88. package/dist/commands/proxy/caddy/index.js +28 -0
  89. package/dist/commands/proxy/caddy/info.js +31 -0
  90. package/dist/commands/proxy/caddy/reload.js +30 -0
  91. package/dist/commands/proxy/caddy/restart.js +28 -0
  92. package/dist/commands/proxy/caddy/start.js +30 -0
  93. package/dist/commands/proxy/caddy/status.js +19 -0
  94. package/dist/commands/proxy/caddy/stop.js +30 -0
  95. package/dist/commands/proxy/caddy/use.js +26 -0
  96. package/dist/commands/proxy/index.js +28 -0
  97. package/dist/commands/proxy/nginx/current.js +18 -0
  98. package/dist/commands/proxy/nginx/generate.js +68 -0
  99. package/dist/commands/proxy/nginx/index.js +28 -0
  100. package/dist/commands/proxy/nginx/info.js +34 -0
  101. package/dist/commands/proxy/nginx/reload.js +30 -0
  102. package/dist/commands/proxy/nginx/restart.js +28 -0
  103. package/dist/commands/proxy/nginx/start.js +30 -0
  104. package/dist/commands/proxy/nginx/status.js +19 -0
  105. package/dist/commands/proxy/nginx/stop.js +30 -0
  106. package/dist/commands/proxy/nginx/use.js +31 -0
  107. package/dist/commands/restart.js +12 -0
  108. package/dist/commands/revision/create.js +118 -0
  109. package/dist/commands/scaffold/migration.js +38 -0
  110. package/dist/commands/scaffold/plugin.js +37 -0
  111. package/dist/commands/self/check.js +71 -0
  112. package/dist/commands/self/index.js +20 -0
  113. package/dist/commands/self/update.js +152 -0
  114. package/dist/commands/session/id.js +24 -0
  115. package/dist/commands/session/remove.js +57 -0
  116. package/dist/commands/session/setup.js +62 -0
  117. package/dist/commands/skills/check.js +69 -0
  118. package/dist/commands/skills/index.js +20 -0
  119. package/dist/commands/skills/install.js +80 -0
  120. package/dist/commands/skills/remove.js +80 -0
  121. package/dist/commands/skills/update.js +87 -0
  122. package/dist/commands/source/build.js +58 -0
  123. package/dist/commands/source/dev.js +182 -0
  124. package/dist/commands/source/download.js +884 -0
  125. package/dist/commands/source/publish.js +109 -0
  126. package/dist/commands/source/registry/logs.js +70 -0
  127. package/dist/commands/source/registry/start.js +57 -0
  128. package/dist/commands/source/registry/status.js +33 -0
  129. package/dist/commands/source/registry/stop.js +48 -0
  130. package/dist/commands/source/test.js +476 -0
  131. package/dist/commands/start.js +12 -0
  132. package/dist/commands/stop.js +12 -0
  133. package/dist/commands/test.js +12 -0
  134. package/dist/commands/upgrade.js +12 -0
  135. package/dist/commands/v1.js +210 -0
  136. package/dist/generated/command-registry.js +134 -0
  137. package/dist/help/runtime-help.js +23 -0
  138. package/dist/lib/api-client.js +335 -0
  139. package/dist/lib/api-command-compat.js +641 -0
  140. package/dist/lib/app-health.js +139 -0
  141. package/dist/lib/app-managed-resources.js +337 -0
  142. package/dist/lib/app-public-path.js +80 -0
  143. package/dist/lib/app-runtime.js +189 -0
  144. package/dist/lib/auth-store.js +528 -0
  145. package/dist/lib/backup.js +171 -0
  146. package/dist/lib/bootstrap.js +409 -0
  147. package/dist/lib/build-config.js +18 -0
  148. package/dist/lib/builtin-db.js +86 -0
  149. package/dist/lib/cli-config.js +569 -0
  150. package/dist/lib/cli-entry-error.js +52 -0
  151. package/dist/lib/cli-home.js +47 -0
  152. package/dist/lib/cli-locale.js +141 -0
  153. package/dist/lib/command-discovery.js +39 -0
  154. package/dist/lib/command-log.js +284 -0
  155. package/dist/lib/db-connection-check.js +219 -0
  156. package/dist/lib/docker-env-file.js +60 -0
  157. package/dist/lib/docker-image.js +37 -0
  158. package/dist/lib/docker-log-stream.js +45 -0
  159. package/dist/lib/env-auth.js +963 -0
  160. package/dist/lib/env-command-config.js +45 -0
  161. package/dist/lib/env-config.js +108 -0
  162. package/dist/lib/env-guard.js +61 -0
  163. package/dist/lib/env-paths.js +101 -0
  164. package/dist/lib/env-proxy.js +1325 -0
  165. package/dist/lib/generated-command.js +203 -0
  166. package/dist/lib/http-request.js +49 -0
  167. package/dist/lib/inquirer-theme.js +17 -0
  168. package/dist/lib/inquirer.js +243 -0
  169. package/dist/lib/managed-env-file.js +101 -0
  170. package/dist/lib/managed-init-env.js +32 -0
  171. package/dist/lib/naming.js +70 -0
  172. package/dist/lib/object-utils.js +76 -0
  173. package/dist/lib/openapi.js +62 -0
  174. package/dist/lib/plugin-import.js +279 -0
  175. package/dist/lib/plugin-storage.js +64 -0
  176. package/dist/lib/post-processors.js +23 -0
  177. package/dist/lib/prompt-catalog-core.js +186 -0
  178. package/dist/lib/prompt-catalog-terminal.js +374 -0
  179. package/{src/index.js → dist/lib/prompt-catalog.js} +2 -6
  180. package/dist/lib/prompt-validators.js +278 -0
  181. package/dist/lib/prompt-web-ui.js +2234 -0
  182. package/dist/lib/proxy-caddy.js +274 -0
  183. package/dist/lib/proxy-nginx.js +330 -0
  184. package/dist/lib/resource-command.js +357 -0
  185. package/dist/lib/resource-request.js +104 -0
  186. package/dist/lib/run-npm.js +429 -0
  187. package/dist/lib/runtime-env-vars.js +32 -0
  188. package/dist/lib/runtime-generator.js +498 -0
  189. package/dist/lib/runtime-store.js +56 -0
  190. package/dist/lib/self-manager.js +301 -0
  191. package/dist/lib/session-id.js +17 -0
  192. package/dist/lib/session-integration.js +703 -0
  193. package/dist/lib/session-store.js +118 -0
  194. package/dist/lib/skills-manager.js +438 -0
  195. package/dist/lib/source-publish.js +326 -0
  196. package/dist/lib/source-registry.js +188 -0
  197. package/dist/lib/startup-update.js +309 -0
  198. package/dist/lib/ui.js +159 -0
  199. package/dist/locale/en-US.json +526 -0
  200. package/dist/locale/zh-CN.json +526 -0
  201. package/dist/post-processors/data-modeling.js +84 -0
  202. package/dist/post-processors/data-source-manager.js +138 -0
  203. package/dist/post-processors/index.js +19 -0
  204. package/nocobase-ctl.config.json +388 -0
  205. package/package.json +128 -24
  206. package/scripts/build.mjs +34 -0
  207. package/scripts/clean.mjs +9 -0
  208. package/tsconfig.json +19 -0
  209. package/bin/index.js +0 -39
  210. package/nocobase.conf.tpl +0 -95
  211. package/src/commands/benchmark.js +0 -73
  212. package/src/commands/build.js +0 -49
  213. package/src/commands/clean.js +0 -30
  214. package/src/commands/client.js +0 -166
  215. package/src/commands/create-nginx-conf.js +0 -37
  216. package/src/commands/create-plugin.js +0 -33
  217. package/src/commands/dev.js +0 -200
  218. package/src/commands/doc.js +0 -76
  219. package/src/commands/e2e.js +0 -265
  220. package/src/commands/global.js +0 -43
  221. package/src/commands/index.js +0 -45
  222. package/src/commands/instance-id.js +0 -47
  223. package/src/commands/locale/cronstrue.js +0 -122
  224. package/src/commands/locale/react-js-cron/en-US.json +0 -75
  225. package/src/commands/locale/react-js-cron/zh-CN.json +0 -33
  226. package/src/commands/locale/react-js-cron/zh-TW.json +0 -33
  227. package/src/commands/locale.js +0 -81
  228. package/src/commands/p-test.js +0 -88
  229. package/src/commands/perf.js +0 -63
  230. package/src/commands/pkg.js +0 -321
  231. package/src/commands/pm2.js +0 -37
  232. package/src/commands/postinstall.js +0 -88
  233. package/src/commands/start.js +0 -148
  234. package/src/commands/tar.js +0 -36
  235. package/src/commands/test-coverage.js +0 -55
  236. package/src/commands/test.js +0 -107
  237. package/src/commands/umi.js +0 -33
  238. package/src/commands/update-deps.js +0 -72
  239. package/src/commands/upgrade.js +0 -47
  240. package/src/commands/view-license-key.js +0 -44
  241. package/src/license.js +0 -76
  242. package/src/logger.js +0 -75
  243. package/src/plugin-generator.js +0 -80
  244. package/src/util.js +0 -517
  245. package/templates/bundle-status.html +0 -338
  246. package/templates/create-app-package.json +0 -39
  247. package/templates/plugin/.npmignore.tpl +0 -2
  248. package/templates/plugin/README.md.tpl +0 -1
  249. package/templates/plugin/client.d.ts +0 -2
  250. package/templates/plugin/client.js +0 -1
  251. package/templates/plugin/package.json.tpl +0 -11
  252. package/templates/plugin/server.d.ts +0 -2
  253. package/templates/plugin/server.js +0 -1
  254. package/templates/plugin/src/client/client.d.ts +0 -249
  255. package/templates/plugin/src/client/index.tsx.tpl +0 -1
  256. package/templates/plugin/src/client/locale.ts +0 -21
  257. package/templates/plugin/src/client/plugin.tsx.tpl +0 -10
  258. package/templates/plugin/src/index.ts +0 -2
  259. package/templates/plugin/src/locale/en-US.json +0 -1
  260. package/templates/plugin/src/locale/zh-CN.json +0 -1
  261. package/templates/plugin/src/server/collections/.gitkeep +0 -0
  262. package/templates/plugin/src/server/index.ts.tpl +0 -1
  263. package/templates/plugin/src/server/plugin.ts.tpl +0 -19
@@ -0,0 +1,202 @@
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 { Args, Command, Flags } from '@oclif/core';
10
+ import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime } from '../../lib/app-runtime.js';
11
+ import { resolveBuiltinDbConnection } from '../../lib/builtin-db.js';
12
+ import { renderTable } from '../../lib/ui.js';
13
+ import { appPath, appUrl, dbStatus, runtimeStatus, sourcePath, storagePath } from './shared.js';
14
+ const MISSING_FIELD = Symbol('missingField');
15
+ const FORBIDDEN_FIELD_PATH_SEGMENTS = new Set(['__proto__', 'constructor', 'prototype']);
16
+ function normalizeJsonValue(value) {
17
+ if (value === undefined || value === null || value === '') {
18
+ return '-';
19
+ }
20
+ if (typeof value === 'boolean' || typeof value === 'number') {
21
+ return value;
22
+ }
23
+ return String(value);
24
+ }
25
+ function normalizeValue(value) {
26
+ if (value === undefined || value === null || value === '') {
27
+ return '-';
28
+ }
29
+ if (typeof value === 'boolean') {
30
+ return value ? 'true' : 'false';
31
+ }
32
+ return String(value);
33
+ }
34
+ function maskSecret(value, showSecrets) {
35
+ const normalized = normalizeValue(value);
36
+ if (normalized === '-') {
37
+ return normalized;
38
+ }
39
+ return showSecrets ? normalized : '********';
40
+ }
41
+ function createGroupTable(title, values) {
42
+ const rows = Object.entries(values).map(([field, value]) => [field, normalizeValue(value)]);
43
+ return `${title}\n${renderTable(['Field', 'Value'], rows)}`;
44
+ }
45
+ function serializeGroup(values) {
46
+ return Object.fromEntries(Object.entries(values).map(([field, value]) => [field, normalizeJsonValue(value)]));
47
+ }
48
+ function resolveFieldPath(value, path) {
49
+ const segments = path
50
+ .split('.')
51
+ .map((segment) => segment.trim())
52
+ .filter(Boolean);
53
+ if (segments.length === 0) {
54
+ return MISSING_FIELD;
55
+ }
56
+ let current = value;
57
+ for (const segment of segments) {
58
+ if (!current ||
59
+ typeof current !== 'object' ||
60
+ FORBIDDEN_FIELD_PATH_SEGMENTS.has(segment) ||
61
+ !Object.prototype.hasOwnProperty.call(current, segment)) {
62
+ return MISSING_FIELD;
63
+ }
64
+ current = current[segment];
65
+ }
66
+ return current;
67
+ }
68
+ export default class EnvInfo extends Command {
69
+ static hidden = false;
70
+ static description = 'Show grouped details for the selected NocoBase env, including app, database, API, and auth settings.';
71
+ static examples = [
72
+ '<%= config.bin %> <%= command.id %> app1',
73
+ '<%= config.bin %> <%= command.id %> app1 --json',
74
+ '<%= config.bin %> <%= command.id %> app1 --show-secrets',
75
+ '<%= config.bin %> <%= command.id %> app1 --field app.url',
76
+ ];
77
+ static args = {
78
+ name: Args.string({
79
+ description: 'Configured environment name to inspect. Defaults to the current env when omitted',
80
+ required: false,
81
+ }),
82
+ };
83
+ static flags = {
84
+ env: Flags.string({
85
+ char: 'e',
86
+ hidden: true,
87
+ deprecated: true,
88
+ description: 'Environment name (same as the optional positional argument; for compatibility with -e/--env on other commands)',
89
+ }),
90
+ json: Flags.boolean({
91
+ description: 'Output the result as JSON',
92
+ default: false,
93
+ }),
94
+ field: Flags.string({
95
+ description: 'Return only a single field using dot notation, for example app.url or api.auth.type',
96
+ }),
97
+ 'show-secrets': Flags.boolean({
98
+ description: 'Show secret values in plain text',
99
+ default: false,
100
+ }),
101
+ };
102
+ async run() {
103
+ const { args, flags } = await this.parse(EnvInfo);
104
+ const envNameArg = args.name?.trim() || undefined;
105
+ const envNameFlag = flags.env?.trim() || undefined;
106
+ if (envNameArg && envNameFlag && envNameArg !== envNameFlag) {
107
+ this.error(`Environment name was provided both as the argument ("${envNameArg}") and as --env ("${envNameFlag}"). Please use only one.`);
108
+ }
109
+ const requestedEnv = envNameArg || envNameFlag;
110
+ const showSecrets = flags['show-secrets'];
111
+ const fieldPath = flags.field?.trim() || undefined;
112
+ const runtime = await resolveManagedAppRuntime(requestedEnv);
113
+ if (!runtime) {
114
+ this.error(formatMissingManagedAppEnvMessage(requestedEnv));
115
+ }
116
+ const auth = runtime.env.auth;
117
+ const builtinDbConnection = (runtime.kind === 'local' || runtime.kind === 'docker') && runtime.env.config.builtinDb
118
+ ? await resolveBuiltinDbConnection(runtime)
119
+ : undefined;
120
+ const dbDialect = builtinDbConnection?.dbDialect ?? runtime.env.config.dbDialect;
121
+ const appGroup = {
122
+ url: appUrl(runtime),
123
+ appPath: appPath(runtime),
124
+ sourcePath: sourcePath(runtime),
125
+ storagePath: storagePath(runtime),
126
+ appPort: runtime.env.config.appPort,
127
+ appStatus: await runtimeStatus(runtime),
128
+ source: runtime.source,
129
+ downloadVersion: runtime.env.config.downloadVersion,
130
+ dockerRegistry: runtime.env.config.dockerRegistry,
131
+ dockerPlatform: runtime.env.config.dockerPlatform,
132
+ timezone: runtime.env.config.timezone,
133
+ };
134
+ const dbGroup = {
135
+ databaseStatus: await dbStatus(runtime),
136
+ builtinDb: runtime.env.config.builtinDb,
137
+ dbDialect,
138
+ builtinDbImage: runtime.env.config.builtinDbImage,
139
+ dbHost: builtinDbConnection?.dbHost ?? runtime.env.config.dbHost,
140
+ dbPort: builtinDbConnection?.dbPort ?? runtime.env.config.dbPort,
141
+ dbDatabase: runtime.env.config.dbDatabase,
142
+ dbUser: runtime.env.config.dbUser,
143
+ dbPassword: maskSecret(runtime.env.config.dbPassword, showSecrets),
144
+ dbTablePrefix: runtime.env.config.dbTablePrefix,
145
+ dbUnderscored: runtime.env.config.dbUnderscored,
146
+ ...(dbDialect === 'postgres' ? { dbSchema: runtime.env.config.dbSchema } : {}),
147
+ };
148
+ const authGroup = {
149
+ type: runtime.env.authType ?? auth?.type,
150
+ sessionType: auth?.type,
151
+ username: runtime.env.config.authUsername,
152
+ expiresAt: auth?.type === 'oauth' ? auth.expiresAt : undefined,
153
+ scope: auth?.type === 'oauth' ? auth.scope : undefined,
154
+ issuer: auth?.type === 'oauth' ? auth.issuer : undefined,
155
+ clientId: auth?.type === 'oauth' ? auth.clientId : undefined,
156
+ resource: auth?.type === 'oauth' ? auth.resource : undefined,
157
+ accessToken: maskSecret(auth?.accessToken, showSecrets),
158
+ refreshToken: maskSecret(auth?.type === 'oauth' ? auth.refreshToken : undefined, showSecrets),
159
+ };
160
+ const apiGroup = {
161
+ apiBaseUrl: runtime.env.apiBaseUrl,
162
+ 'auth.type': authGroup.type,
163
+ 'auth.sessionType': authGroup.sessionType,
164
+ 'auth.username': authGroup.username,
165
+ 'auth.expiresAt': authGroup.expiresAt,
166
+ 'auth.scope': authGroup.scope,
167
+ 'auth.issuer': authGroup.issuer,
168
+ 'auth.clientId': authGroup.clientId,
169
+ 'auth.resource': authGroup.resource,
170
+ 'auth.accessToken': authGroup.accessToken,
171
+ 'auth.refreshToken': authGroup.refreshToken,
172
+ };
173
+ const output = {
174
+ ok: true,
175
+ env: runtime.envName,
176
+ kind: runtime.kind,
177
+ app: serializeGroup(appGroup),
178
+ db: serializeGroup(dbGroup),
179
+ api: {
180
+ apiBaseUrl: normalizeJsonValue(runtime.env.apiBaseUrl),
181
+ auth: serializeGroup(authGroup),
182
+ },
183
+ };
184
+ if (fieldPath) {
185
+ const selected = resolveFieldPath(output, fieldPath);
186
+ if (selected === MISSING_FIELD) {
187
+ this.error(`Unknown field "${fieldPath}". Use dot notation like app.url, db.databaseStatus, or api.auth.type.`);
188
+ }
189
+ if (flags.json) {
190
+ this.log(JSON.stringify(selected, null, 2));
191
+ return;
192
+ }
193
+ this.log(typeof selected === 'object' ? JSON.stringify(selected, null, 2) : String(selected));
194
+ return;
195
+ }
196
+ if (flags.json) {
197
+ this.log(JSON.stringify(output, null, 2));
198
+ return;
199
+ }
200
+ this.log([createGroupTable('App', appGroup), createGroupTable('DB', dbGroup), createGroupTable('API', apiGroup)].join('\n\n'));
201
+ }
202
+ }
@@ -0,0 +1,43 @@
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 { getCurrentEnvName, listEnvs, resolveConfiguredAuthType } from '../../lib/auth-store.js';
11
+ import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
12
+ import { translateCli } from '../../lib/cli-locale.js';
13
+ import { renderTable } from '../../lib/ui.js';
14
+ import { resolveApiBaseUrl } from './shared.js';
15
+ export default class EnvList extends Command {
16
+ static summary = 'List configured environments';
17
+ static examples = ['<%= config.bin %> <%= command.id %>'];
18
+ async run() {
19
+ await this.parse(EnvList);
20
+ const scope = resolveDefaultConfigScope();
21
+ const { envs } = await listEnvs({ scope });
22
+ const currentEnv = await getCurrentEnvName({ scope });
23
+ const names = Object.keys(envs).sort();
24
+ if (!names.length) {
25
+ this.log(translateCli('commands.env.messages.noEnvsConfigured'));
26
+ this.log(translateCli('commands.env.messages.noEnvsConfiguredHelp'));
27
+ return;
28
+ }
29
+ const rows = [];
30
+ for (const name of names) {
31
+ const env = envs[name];
32
+ rows.push([
33
+ name === currentEnv ? '*' : '',
34
+ name,
35
+ env.kind ?? '-',
36
+ resolveApiBaseUrl(env),
37
+ resolveConfiguredAuthType(env) ?? env.auth?.type ?? '',
38
+ env.runtime?.version ?? '',
39
+ ]);
40
+ }
41
+ this.log(renderTable(['Current', 'Name', 'Kind', 'API Base URL', 'Auth', 'Runtime'], rows));
42
+ }
43
+ }
@@ -0,0 +1,174 @@
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 { Args, Command, Flags } from '@oclif/core';
10
+ import { resolveManagedAppRuntime } from '../../lib/app-runtime.js';
11
+ import { getCurrentEnvName, loadAuthConfig, removeEnv } from '../../lib/auth-store.js';
12
+ import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
13
+ import { confirm, input } from "../../lib/inquirer.js";
14
+ import { isInteractiveTerminal, printInfo, printVerbose, setVerboseMode } from '../../lib/ui.js';
15
+ function formatRemoveForceRequiredMessage(envName, purge) {
16
+ if (purge) {
17
+ return [
18
+ `Refusing to purge env "${envName}" without confirmation in non-interactive mode.`,
19
+ 'Re-run with `--purge --force` to continue.',
20
+ ].join('\n');
21
+ }
22
+ return [
23
+ `Refusing to remove env "${envName}" without confirmation in non-interactive mode.`,
24
+ 'Re-run with `--force` to continue.',
25
+ ].join('\n');
26
+ }
27
+ function buildRemovePrompt(runtime, options) {
28
+ const subject = options.isCurrent ? `current env "${runtime.envName}"` : `env "${runtime.envName}"`;
29
+ if (options.purge) {
30
+ const lines = [`Purge ${subject}?`];
31
+ if (runtime.kind === 'local' || runtime.kind === 'docker') {
32
+ lines.push('This removes CLI-managed local runtime resources for this env on this machine.');
33
+ lines.push('Storage data will be removed, and downloaded local app files will be removed when applicable.');
34
+ lines.push('External database services are not managed by the CLI and will be left untouched.');
35
+ }
36
+ else {
37
+ lines.push('This env has no CLI-managed local runtime resources on this machine.');
38
+ lines.push('Only the saved CLI env config will be removed. External services are not touched.');
39
+ }
40
+ lines.push(`Type "${runtime.envName}" to confirm:`);
41
+ return lines.join('\n');
42
+ }
43
+ const lines = [`Remove ${subject}?`];
44
+ if (runtime.kind === 'local' || runtime.kind === 'docker') {
45
+ lines.push('NocoBase and any CLI-managed built-in database for this env will be stopped on this machine.');
46
+ lines.push('The saved CLI env config will then be removed. Storage data and local app files will be kept.');
47
+ }
48
+ else {
49
+ lines.push('Only the saved CLI env config will be removed.');
50
+ }
51
+ return lines.join('\n');
52
+ }
53
+ async function confirmEnvRemoval(runtime, options) {
54
+ if (!isInteractiveTerminal()) {
55
+ if (options.force) {
56
+ return true;
57
+ }
58
+ throw new Error(formatRemoveForceRequiredMessage(runtime.envName, options.purge));
59
+ }
60
+ if (options.force) {
61
+ return true;
62
+ }
63
+ if (options.purge) {
64
+ try {
65
+ await input({
66
+ message: buildRemovePrompt(runtime, options),
67
+ required: true,
68
+ validate: (value) => (value.trim() === runtime.envName ? true : `Type "${runtime.envName}" to confirm.`),
69
+ placeholder: runtime.envName,
70
+ });
71
+ return true;
72
+ }
73
+ catch {
74
+ return false;
75
+ }
76
+ }
77
+ try {
78
+ return await confirm({
79
+ message: buildRemovePrompt(runtime, options),
80
+ default: false,
81
+ });
82
+ }
83
+ catch {
84
+ return false;
85
+ }
86
+ }
87
+ export default class EnvRemove extends Command {
88
+ static summary = 'Remove a configured environment';
89
+ static description = 'Remove a configured env. Local and Docker envs stop CLI-managed runtime resources on this machine first; pass `--purge` to also delete managed local resources, storage data, and downloaded app files when applicable.';
90
+ static examples = [
91
+ '<%= config.bin %> <%= command.id %> staging',
92
+ '<%= config.bin %> <%= command.id %> staging --force',
93
+ '<%= config.bin %> <%= command.id %> staging --purge --force',
94
+ ];
95
+ static flags = {
96
+ yes: Flags.boolean({
97
+ char: 'y',
98
+ hidden: true,
99
+ default: false,
100
+ }),
101
+ force: Flags.boolean({
102
+ char: 'f',
103
+ description: 'Skip confirmation for the selected remove mode',
104
+ default: false,
105
+ }),
106
+ purge: Flags.boolean({
107
+ description: 'Also remove CLI-managed local runtime resources, storage data, and downloaded app files when applicable. For remote API envs, only the saved CLI env config will be removed.',
108
+ default: false,
109
+ }),
110
+ verbose: Flags.boolean({
111
+ description: 'Show detailed progress output',
112
+ default: false,
113
+ }),
114
+ };
115
+ static args = {
116
+ name: Args.string({
117
+ description: 'Configured environment name to remove',
118
+ required: true,
119
+ }),
120
+ };
121
+ async run() {
122
+ const { args, flags } = await this.parse(EnvRemove);
123
+ setVerboseMode(flags.verbose);
124
+ const scope = resolveDefaultConfigScope();
125
+ const config = await loadAuthConfig({ scope });
126
+ if (!config.envs[args.name]) {
127
+ this.error(`Env "${args.name}" is not configured`);
128
+ }
129
+ const currentEnv = await getCurrentEnvName({ scope });
130
+ const runtime = await resolveManagedAppRuntime(args.name);
131
+ if (!runtime) {
132
+ this.error(`Env "${args.name}" is not configured`);
133
+ }
134
+ const skipConfirmation = flags.yes || flags.force;
135
+ let confirmed = false;
136
+ try {
137
+ confirmed = await confirmEnvRemoval(runtime, {
138
+ force: skipConfirmation,
139
+ isCurrent: args.name === currentEnv,
140
+ purge: flags.purge,
141
+ });
142
+ }
143
+ catch (error) {
144
+ this.error(error instanceof Error ? error.message : String(error));
145
+ }
146
+ if (!confirmed) {
147
+ return;
148
+ }
149
+ const runCommand = this.config.runCommand.bind(this.config);
150
+ const verboseArgv = flags.verbose ? ['--verbose'] : [];
151
+ if (flags.purge) {
152
+ if (runtime.kind === 'local' || runtime.kind === 'docker') {
153
+ await runCommand('app:destroy', ['--env', runtime.envName, '--force', ...verboseArgv]);
154
+ return;
155
+ }
156
+ printInfo(`No local CLI-managed resources were found for "${runtime.envName}". Removing the saved CLI env config only.`);
157
+ }
158
+ else if (runtime.kind === 'local' || runtime.kind === 'docker') {
159
+ printVerbose(`Stopping CLI-managed runtime resources for "${runtime.envName}" before removing the env config.`);
160
+ await runCommand('app:stop', ['--env', runtime.envName, '--with-db', '--yes', ...verboseArgv]);
161
+ }
162
+ printVerbose(`Removing env "${args.name}"`);
163
+ const result = await removeEnv(args.name, { scope });
164
+ if (result.hasEnvs) {
165
+ if (args.name === currentEnv) {
166
+ this.log(`Removed env "${result.removed}". Switched current env to "${await getCurrentEnvName({ scope })}".`);
167
+ return;
168
+ }
169
+ this.log(`Removed env "${result.removed}".`);
170
+ return;
171
+ }
172
+ this.log(`Removed env "${result.removed}". No envs configured.`);
173
+ }
174
+ }
@@ -0,0 +1,204 @@
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 { buildDockerDbContainerName, dockerContainerExists, dockerContainerIsRunning, } from '../../lib/app-runtime.js';
10
+ import { executeRawApiRequest } from '../../lib/api-client.js';
11
+ import { buildLocalAppUrl } from '../../lib/app-public-path.js';
12
+ export function resolveApiBaseUrl(config) {
13
+ return String(config.apiBaseUrl ?? config.baseUrl ?? config.apibaseUrl ?? '').trim();
14
+ }
15
+ function buildAppPath(publicPath, subapp) {
16
+ const normalizedPublicPath = publicPath.replace(/\/+$/, '');
17
+ if (!subapp) {
18
+ return normalizedPublicPath ? `${normalizedPublicPath}/` : '/';
19
+ }
20
+ const normalizedSubapp = subapp.replace(/^\/+|\/+$/g, '');
21
+ return `${normalizedPublicPath ? normalizedPublicPath : ''}/apps/${normalizedSubapp}/`;
22
+ }
23
+ export function resolveAppUrlFromApiBaseUrl(apiBaseUrl) {
24
+ const value = String(apiBaseUrl ?? '').trim();
25
+ if (!value) {
26
+ return '';
27
+ }
28
+ try {
29
+ const url = new URL(value);
30
+ const subappMatch = url.pathname.match(/^(.*)\/api\/__app\/([^/]+)\/?$/);
31
+ if (subappMatch) {
32
+ url.pathname = buildAppPath(subappMatch[1] ?? '', subappMatch[2]);
33
+ url.search = '';
34
+ url.hash = '';
35
+ return url.toString();
36
+ }
37
+ const appMatch = url.pathname.match(/^(.*)\/api\/?$/);
38
+ if (appMatch) {
39
+ url.pathname = buildAppPath(appMatch[1] ?? '');
40
+ url.search = '';
41
+ url.hash = '';
42
+ return url.toString();
43
+ }
44
+ }
45
+ catch {
46
+ return value;
47
+ }
48
+ return value;
49
+ }
50
+ export function appUrl(runtime) {
51
+ const resolvedFromApiBaseUrl = resolveAppUrlFromApiBaseUrl(runtime.env.apiBaseUrl ?? resolveApiBaseUrl(runtime.env.config));
52
+ if (resolvedFromApiBaseUrl) {
53
+ return resolvedFromApiBaseUrl;
54
+ }
55
+ const port = String(runtime.env.config.appPort ?? '').trim();
56
+ if (port) {
57
+ return buildLocalAppUrl(port, runtime.env.config?.appPublicPath) ?? '';
58
+ }
59
+ return '';
60
+ }
61
+ export function appPath(runtime) {
62
+ if (runtime.kind === 'http') {
63
+ return '-';
64
+ }
65
+ const value = String(runtime.env.appPath ?? runtime.env.config.appPath ?? '').trim();
66
+ return value || '-';
67
+ }
68
+ export function sourcePath(runtime) {
69
+ if (runtime.kind === 'http' || runtime.kind === 'docker') {
70
+ return '-';
71
+ }
72
+ if (runtime.kind === 'local') {
73
+ return String(runtime.projectRoot ?? runtime.env.sourcePath ?? runtime.env.appRootPath ?? '').trim() || '-';
74
+ }
75
+ return String(runtime.env.config.appRootPath ?? '').trim() || '-';
76
+ }
77
+ export function storagePath(runtime) {
78
+ if (runtime.kind === 'http') {
79
+ return '-';
80
+ }
81
+ const value = String(runtime.env.storagePath ?? runtime.env.config.storagePath ?? '').trim();
82
+ return value || '-';
83
+ }
84
+ function collectErrorCodes(value) {
85
+ if (!value || typeof value !== 'object') {
86
+ return [];
87
+ }
88
+ if (Array.isArray(value)) {
89
+ return value.flatMap((item) => collectErrorCodes(item));
90
+ }
91
+ const out = [];
92
+ const record = value;
93
+ if (typeof record.code === 'string') {
94
+ out.push(record.code);
95
+ }
96
+ for (const key of ['data', 'error', 'errors']) {
97
+ out.push(...collectErrorCodes(record[key]));
98
+ }
99
+ return out;
100
+ }
101
+ function isAuthFailureData(value) {
102
+ const codes = collectErrorCodes(value);
103
+ return codes.some((code) => ['EMPTY_TOKEN', 'INVALID_TOKEN', 'EXPIRED_TOKEN', 'BLOCKED_TOKEN', 'EXPIRED_SESSION', 'NOT_EXIST_USER'].includes(code));
104
+ }
105
+ function isNetworkFailure(error) {
106
+ if (!(error instanceof Error)) {
107
+ return false;
108
+ }
109
+ return (error.name === 'AbortError' ||
110
+ /fetch failed|network|timeout|timed out|abort|ECONNREFUSED|ECONNRESET|ENOTFOUND|ETIMEDOUT|EAI_AGAIN|ENETUNREACH/i.test(error.message));
111
+ }
112
+ function isUnconfiguredFailure(error) {
113
+ return error instanceof Error && /missing (a )?base url|missing base URL/i.test(error.message);
114
+ }
115
+ function isAuthFailure(error) {
116
+ return (error instanceof Error &&
117
+ /EMPTY_TOKEN|INVALID_TOKEN|EXPIRED_TOKEN|BLOCKED_TOKEN|EXPIRED_SESSION|NOT_EXIST_USER|invalid_grant|sign in|signin|authentication failed/i.test(error.message));
118
+ }
119
+ export async function apiStatus(envName, config, options = {}) {
120
+ if (!resolveApiBaseUrl(config)) {
121
+ return 'unconfigured';
122
+ }
123
+ try {
124
+ const response = await executeRawApiRequest({
125
+ envName,
126
+ scope: options.scope,
127
+ method: 'GET',
128
+ path: '/auth:check',
129
+ timeoutMs: options.timeoutMs ?? 2000,
130
+ });
131
+ if (response.ok) {
132
+ return 'ok';
133
+ }
134
+ if (response.status === 401 || response.status === 403 || isAuthFailureData(response.data)) {
135
+ return 'auth failed';
136
+ }
137
+ return 'error';
138
+ }
139
+ catch (error) {
140
+ if (isUnconfiguredFailure(error)) {
141
+ return 'unconfigured';
142
+ }
143
+ if (isAuthFailure(error)) {
144
+ return 'auth failed';
145
+ }
146
+ if (isNetworkFailure(error)) {
147
+ return 'unreachable';
148
+ }
149
+ return 'error';
150
+ }
151
+ }
152
+ async function isLocalAppHealthy(runtime) {
153
+ const port = String(runtime.env.config.appPort ?? '').trim();
154
+ if (!port) {
155
+ return false;
156
+ }
157
+ const controller = new AbortController();
158
+ const timeout = setTimeout(() => controller.abort(), 1500);
159
+ try {
160
+ const response = await fetch(`http://127.0.0.1:${port}/api/__health_check`, {
161
+ signal: controller.signal,
162
+ });
163
+ const text = await response.text();
164
+ return response.ok && text.trim().toLowerCase() === 'ok';
165
+ }
166
+ catch (_error) {
167
+ return false;
168
+ }
169
+ finally {
170
+ clearTimeout(timeout);
171
+ }
172
+ }
173
+ async function dockerStatus(containerName) {
174
+ if (!(await dockerContainerExists(containerName))) {
175
+ return 'missing';
176
+ }
177
+ return (await dockerContainerIsRunning(containerName)) ? 'running' : 'stopped';
178
+ }
179
+ export async function dbStatus(runtime) {
180
+ if (!runtime.env.config.builtinDb) {
181
+ return runtime.kind === 'http' ? 'external' : '-';
182
+ }
183
+ if (runtime.kind === 'http') {
184
+ return 'external';
185
+ }
186
+ if (runtime.kind === 'ssh') {
187
+ return '-';
188
+ }
189
+ const dbDialect = String(runtime.env.config.dbDialect ?? 'postgres').trim() || 'postgres';
190
+ const containerName = buildDockerDbContainerName(runtime.envName, dbDialect, runtime.dockerContainerPrefix || runtime.workspaceName);
191
+ return await dockerStatus(containerName);
192
+ }
193
+ export async function runtimeStatus(runtime) {
194
+ if (runtime.kind === 'http') {
195
+ return 'http';
196
+ }
197
+ if (runtime.kind === 'ssh') {
198
+ return 'ssh';
199
+ }
200
+ if (runtime.kind === 'docker') {
201
+ return await dockerStatus(runtime.containerName);
202
+ }
203
+ return (await isLocalAppHealthy(runtime)) ? 'running' : 'stopped';
204
+ }