@nocobase/cli 2.1.0-alpha.4 → 2.1.0-alpha.45

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 (214) hide show
  1. package/LICENSE.txt +107 -0
  2. package/README.md +405 -19
  3. package/README.zh-CN.md +355 -0
  4. package/bin/run.cmd +3 -0
  5. package/bin/run.js +136 -0
  6. package/bin/session-env.js +39 -0
  7. package/dist/commands/api/resource/create.js +15 -0
  8. package/dist/commands/api/resource/destroy.js +15 -0
  9. package/dist/commands/api/resource/get.js +15 -0
  10. package/dist/commands/api/resource/index.js +20 -0
  11. package/dist/commands/api/resource/list.js +16 -0
  12. package/dist/commands/api/resource/query.js +15 -0
  13. package/dist/commands/api/resource/update.js +15 -0
  14. package/dist/commands/app/destroy.js +225 -0
  15. package/dist/commands/app/down.js +71 -0
  16. package/dist/commands/app/logs.js +114 -0
  17. package/dist/commands/app/restart.js +158 -0
  18. package/dist/commands/app/shared.js +122 -0
  19. package/dist/commands/app/start.js +286 -0
  20. package/dist/commands/app/stop.js +161 -0
  21. package/dist/commands/app/upgrade.js +519 -0
  22. package/dist/commands/backup/create.js +147 -0
  23. package/dist/commands/backup/index.js +20 -0
  24. package/dist/commands/backup/restore.js +105 -0
  25. package/{src/cli.js → dist/commands/build.js} +4 -11
  26. package/dist/commands/config/delete.js +34 -0
  27. package/dist/commands/config/get.js +33 -0
  28. package/dist/commands/config/index.js +20 -0
  29. package/dist/commands/config/list.js +29 -0
  30. package/dist/commands/config/set.js +39 -0
  31. package/dist/commands/db/check.js +240 -0
  32. package/dist/commands/db/logs.js +85 -0
  33. package/dist/commands/db/ps.js +60 -0
  34. package/dist/commands/db/shared.js +96 -0
  35. package/dist/commands/db/start.js +86 -0
  36. package/dist/commands/db/stop.js +71 -0
  37. package/{templates/plugin/src/client/models/index.ts → dist/commands/dev.js} +4 -4
  38. package/{src/commands/locale/react-js-cron/index.js → dist/commands/down.js} +3 -8
  39. package/dist/commands/download.js +13 -0
  40. package/dist/commands/env/add.js +426 -0
  41. package/dist/commands/env/auth.js +189 -0
  42. package/dist/commands/env/current.js +21 -0
  43. package/dist/commands/env/info.js +201 -0
  44. package/dist/commands/env/list.js +44 -0
  45. package/dist/commands/env/remove.js +84 -0
  46. package/dist/commands/env/shared.js +196 -0
  47. package/dist/commands/env/status.js +90 -0
  48. package/dist/commands/env/update.js +74 -0
  49. package/dist/commands/env/use.js +38 -0
  50. package/dist/commands/examples/prompts-stages.js +150 -0
  51. package/dist/commands/examples/prompts-test.js +181 -0
  52. package/dist/commands/init.js +1152 -0
  53. package/dist/commands/install.js +2424 -0
  54. package/dist/commands/license/activate.js +360 -0
  55. package/dist/commands/license/env.js +94 -0
  56. package/dist/commands/license/generate-id.js +108 -0
  57. package/dist/commands/license/id.js +70 -0
  58. package/dist/commands/license/index.js +20 -0
  59. package/dist/commands/license/plugins/clean.js +115 -0
  60. package/dist/commands/license/plugins/index.js +20 -0
  61. package/dist/commands/license/plugins/list.js +64 -0
  62. package/dist/commands/license/plugins/shared.js +331 -0
  63. package/dist/commands/license/plugins/sync.js +314 -0
  64. package/dist/commands/license/shared.js +423 -0
  65. package/dist/commands/license/status.js +64 -0
  66. package/dist/commands/logs.js +12 -0
  67. package/dist/commands/plugin/disable.js +86 -0
  68. package/dist/commands/plugin/enable.js +86 -0
  69. package/dist/commands/plugin/list.js +82 -0
  70. package/dist/commands/pm/disable.js +12 -0
  71. package/dist/commands/pm/enable.js +12 -0
  72. package/dist/commands/pm/list.js +12 -0
  73. package/dist/commands/restart.js +12 -0
  74. package/dist/commands/scaffold/migration.js +38 -0
  75. package/dist/commands/scaffold/plugin.js +37 -0
  76. package/dist/commands/self/check.js +71 -0
  77. package/dist/commands/self/index.js +20 -0
  78. package/dist/commands/self/update.js +95 -0
  79. package/dist/commands/session/id.js +24 -0
  80. package/dist/commands/session/remove.js +57 -0
  81. package/dist/commands/session/setup.js +62 -0
  82. package/dist/commands/skills/check.js +69 -0
  83. package/dist/commands/skills/index.js +20 -0
  84. package/dist/commands/skills/install.js +80 -0
  85. package/dist/commands/skills/remove.js +80 -0
  86. package/dist/commands/skills/update.js +87 -0
  87. package/dist/commands/source/build.js +58 -0
  88. package/dist/commands/source/dev.js +182 -0
  89. package/dist/commands/source/download.js +884 -0
  90. package/dist/commands/source/publish.js +109 -0
  91. package/dist/commands/source/registry/logs.js +70 -0
  92. package/dist/commands/source/registry/start.js +57 -0
  93. package/dist/commands/source/registry/status.js +33 -0
  94. package/dist/commands/source/registry/stop.js +48 -0
  95. package/dist/commands/source/test.js +477 -0
  96. package/dist/commands/start.js +12 -0
  97. package/dist/commands/stop.js +12 -0
  98. package/dist/commands/test.js +12 -0
  99. package/dist/commands/upgrade.js +12 -0
  100. package/dist/commands/v1.js +210 -0
  101. package/dist/generated/command-registry.js +134 -0
  102. package/dist/help/runtime-help.js +23 -0
  103. package/dist/lib/api-client.js +335 -0
  104. package/dist/lib/api-command-compat.js +641 -0
  105. package/dist/lib/app-health.js +132 -0
  106. package/dist/lib/app-managed-resources.js +316 -0
  107. package/dist/lib/app-runtime.js +180 -0
  108. package/dist/lib/auth-store.js +396 -0
  109. package/dist/lib/backup.js +171 -0
  110. package/dist/lib/bootstrap.js +403 -0
  111. package/dist/lib/build-config.js +18 -0
  112. package/dist/lib/builtin-db.js +86 -0
  113. package/dist/lib/cli-config.js +271 -0
  114. package/dist/lib/cli-home.js +47 -0
  115. package/dist/lib/cli-locale.js +141 -0
  116. package/dist/lib/command-discovery.js +39 -0
  117. package/dist/lib/db-connection-check.js +219 -0
  118. package/dist/lib/docker-env-file.js +52 -0
  119. package/dist/lib/docker-image.js +37 -0
  120. package/dist/lib/env-auth.js +952 -0
  121. package/dist/lib/env-config.js +95 -0
  122. package/dist/lib/env-guard.js +62 -0
  123. package/dist/lib/generated-command.js +203 -0
  124. package/dist/lib/http-request.js +49 -0
  125. package/dist/lib/inquirer-theme.js +17 -0
  126. package/dist/lib/inquirer.js +244 -0
  127. package/dist/lib/naming.js +70 -0
  128. package/dist/lib/object-utils.js +76 -0
  129. package/dist/lib/openapi.js +62 -0
  130. package/dist/lib/plugin-storage.js +64 -0
  131. package/dist/lib/post-processors.js +23 -0
  132. package/dist/lib/prompt-catalog-core.js +185 -0
  133. package/dist/lib/prompt-catalog-terminal.js +375 -0
  134. package/{src/index.js → dist/lib/prompt-catalog.js} +2 -6
  135. package/dist/lib/prompt-validators.js +258 -0
  136. package/dist/lib/prompt-web-ui.js +2227 -0
  137. package/dist/lib/resource-command.js +357 -0
  138. package/dist/lib/resource-request.js +104 -0
  139. package/dist/lib/run-npm.js +385 -0
  140. package/dist/lib/runtime-env-vars.js +32 -0
  141. package/dist/lib/runtime-generator.js +498 -0
  142. package/dist/lib/runtime-store.js +56 -0
  143. package/dist/lib/self-manager.js +301 -0
  144. package/dist/lib/session-id.js +17 -0
  145. package/dist/lib/session-integration.js +703 -0
  146. package/dist/lib/session-store.js +118 -0
  147. package/dist/lib/skills-manager.js +436 -0
  148. package/dist/lib/source-publish.js +306 -0
  149. package/dist/lib/source-registry.js +188 -0
  150. package/dist/lib/startup-update.js +285 -0
  151. package/dist/lib/ui.js +158 -0
  152. package/dist/locale/en-US.json +425 -0
  153. package/dist/locale/zh-CN.json +425 -0
  154. package/dist/post-processors/data-modeling.js +84 -0
  155. package/dist/post-processors/data-source-manager.js +138 -0
  156. package/dist/post-processors/index.js +19 -0
  157. package/nocobase-ctl.config.json +388 -0
  158. package/package.json +132 -26
  159. package/LICENSE +0 -661
  160. package/bin/index.js +0 -39
  161. package/nocobase.conf.tpl +0 -95
  162. package/src/commands/benchmark.js +0 -73
  163. package/src/commands/build.js +0 -49
  164. package/src/commands/clean.js +0 -30
  165. package/src/commands/client.js +0 -166
  166. package/src/commands/create-nginx-conf.js +0 -37
  167. package/src/commands/create-plugin.js +0 -33
  168. package/src/commands/dev.js +0 -200
  169. package/src/commands/doc.js +0 -76
  170. package/src/commands/e2e.js +0 -265
  171. package/src/commands/global.js +0 -43
  172. package/src/commands/index.js +0 -45
  173. package/src/commands/instance-id.js +0 -47
  174. package/src/commands/locale/cronstrue.js +0 -122
  175. package/src/commands/locale/react-js-cron/en-US.json +0 -75
  176. package/src/commands/locale/react-js-cron/zh-CN.json +0 -33
  177. package/src/commands/locale/react-js-cron/zh-TW.json +0 -33
  178. package/src/commands/locale.js +0 -81
  179. package/src/commands/p-test.js +0 -88
  180. package/src/commands/perf.js +0 -63
  181. package/src/commands/pkg.js +0 -321
  182. package/src/commands/pm2.js +0 -37
  183. package/src/commands/postinstall.js +0 -88
  184. package/src/commands/start.js +0 -148
  185. package/src/commands/tar.js +0 -36
  186. package/src/commands/test-coverage.js +0 -55
  187. package/src/commands/test.js +0 -107
  188. package/src/commands/umi.js +0 -33
  189. package/src/commands/update-deps.js +0 -72
  190. package/src/commands/upgrade.js +0 -47
  191. package/src/commands/view-license-key.js +0 -44
  192. package/src/license.js +0 -76
  193. package/src/logger.js +0 -75
  194. package/src/plugin-generator.js +0 -80
  195. package/src/util.js +0 -517
  196. package/templates/bundle-status.html +0 -338
  197. package/templates/create-app-package.json +0 -39
  198. package/templates/plugin/.npmignore.tpl +0 -2
  199. package/templates/plugin/README.md.tpl +0 -1
  200. package/templates/plugin/client.d.ts +0 -2
  201. package/templates/plugin/client.js +0 -1
  202. package/templates/plugin/package.json.tpl +0 -11
  203. package/templates/plugin/server.d.ts +0 -2
  204. package/templates/plugin/server.js +0 -1
  205. package/templates/plugin/src/client/client.d.ts +0 -249
  206. package/templates/plugin/src/client/index.tsx.tpl +0 -1
  207. package/templates/plugin/src/client/locale.ts +0 -21
  208. package/templates/plugin/src/client/plugin.tsx.tpl +0 -10
  209. package/templates/plugin/src/index.ts +0 -2
  210. package/templates/plugin/src/locale/en-US.json +0 -1
  211. package/templates/plugin/src/locale/zh-CN.json +0 -1
  212. package/templates/plugin/src/server/collections/.gitkeep +0 -0
  213. package/templates/plugin/src/server/index.ts.tpl +0 -1
  214. package/templates/plugin/src/server/plugin.ts.tpl +0 -19
@@ -0,0 +1,201 @@
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 { appRootPath, appUrl, dbStatus, runtimeStatus, 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
+ appRootPath: appRootPath(runtime),
124
+ storagePath: storagePath(runtime),
125
+ appPort: runtime.env.config.appPort,
126
+ appStatus: await runtimeStatus(runtime),
127
+ source: runtime.source,
128
+ downloadVersion: runtime.env.config.downloadVersion,
129
+ dockerRegistry: runtime.env.config.dockerRegistry,
130
+ dockerPlatform: runtime.env.config.dockerPlatform,
131
+ timezone: runtime.env.config.timezone,
132
+ };
133
+ const dbGroup = {
134
+ databaseStatus: await dbStatus(runtime),
135
+ builtinDb: runtime.env.config.builtinDb,
136
+ dbDialect,
137
+ builtinDbImage: runtime.env.config.builtinDbImage,
138
+ dbHost: builtinDbConnection?.dbHost ?? runtime.env.config.dbHost,
139
+ dbPort: builtinDbConnection?.dbPort ?? runtime.env.config.dbPort,
140
+ dbDatabase: runtime.env.config.dbDatabase,
141
+ dbUser: runtime.env.config.dbUser,
142
+ dbPassword: maskSecret(runtime.env.config.dbPassword, showSecrets),
143
+ dbTablePrefix: runtime.env.config.dbTablePrefix,
144
+ dbUnderscored: runtime.env.config.dbUnderscored,
145
+ ...(dbDialect === 'postgres' ? { dbSchema: runtime.env.config.dbSchema } : {}),
146
+ };
147
+ const authGroup = {
148
+ type: runtime.env.authType ?? auth?.type,
149
+ sessionType: auth?.type,
150
+ username: runtime.env.config.authUsername,
151
+ expiresAt: auth?.type === 'oauth' ? auth.expiresAt : undefined,
152
+ scope: auth?.type === 'oauth' ? auth.scope : undefined,
153
+ issuer: auth?.type === 'oauth' ? auth.issuer : undefined,
154
+ clientId: auth?.type === 'oauth' ? auth.clientId : undefined,
155
+ resource: auth?.type === 'oauth' ? auth.resource : undefined,
156
+ accessToken: maskSecret(auth?.accessToken, showSecrets),
157
+ refreshToken: maskSecret(auth?.type === 'oauth' ? auth.refreshToken : undefined, showSecrets),
158
+ };
159
+ const apiGroup = {
160
+ apiBaseUrl: runtime.env.apiBaseUrl,
161
+ 'auth.type': authGroup.type,
162
+ 'auth.sessionType': authGroup.sessionType,
163
+ 'auth.username': authGroup.username,
164
+ 'auth.expiresAt': authGroup.expiresAt,
165
+ 'auth.scope': authGroup.scope,
166
+ 'auth.issuer': authGroup.issuer,
167
+ 'auth.clientId': authGroup.clientId,
168
+ 'auth.resource': authGroup.resource,
169
+ 'auth.accessToken': authGroup.accessToken,
170
+ 'auth.refreshToken': authGroup.refreshToken,
171
+ };
172
+ const output = {
173
+ ok: true,
174
+ env: runtime.envName,
175
+ kind: runtime.kind,
176
+ app: serializeGroup(appGroup),
177
+ db: serializeGroup(dbGroup),
178
+ api: {
179
+ apiBaseUrl: normalizeJsonValue(runtime.env.apiBaseUrl),
180
+ auth: serializeGroup(authGroup),
181
+ },
182
+ };
183
+ if (fieldPath) {
184
+ const selected = resolveFieldPath(output, fieldPath);
185
+ if (selected === MISSING_FIELD) {
186
+ this.error(`Unknown field "${fieldPath}". Use dot notation like app.url, db.databaseStatus, or api.auth.type.`);
187
+ }
188
+ if (flags.json) {
189
+ this.log(JSON.stringify(selected, null, 2));
190
+ return;
191
+ }
192
+ this.log(typeof selected === 'object' ? JSON.stringify(selected, null, 2) : String(selected));
193
+ return;
194
+ }
195
+ if (flags.json) {
196
+ this.log(JSON.stringify(output, null, 2));
197
+ return;
198
+ }
199
+ this.log([createGroupTable('App', appGroup), createGroupTable('DB', dbGroup), createGroupTable('API', apiGroup)].join('\n\n'));
200
+ }
201
+ }
@@ -0,0 +1,44 @@
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 { renderTable } from '../../lib/ui.js';
13
+ import { resolveApiBaseUrl } from './shared.js';
14
+ export default class EnvList extends Command {
15
+ static summary = 'List configured environments';
16
+ static examples = [
17
+ '<%= config.bin %> <%= command.id %>',
18
+ ];
19
+ async run() {
20
+ await this.parse(EnvList);
21
+ const scope = resolveDefaultConfigScope();
22
+ const { envs } = await listEnvs({ scope });
23
+ const currentEnv = await getCurrentEnvName({ scope });
24
+ const names = Object.keys(envs).sort();
25
+ if (!names.length) {
26
+ this.log('No envs configured.');
27
+ this.log('Run `nb env add <name> --api-base-url <url>` to add one.');
28
+ return;
29
+ }
30
+ const rows = [];
31
+ for (const name of names) {
32
+ const env = envs[name];
33
+ rows.push([
34
+ name === currentEnv ? '*' : '',
35
+ name,
36
+ env.kind ?? '-',
37
+ resolveApiBaseUrl(env),
38
+ resolveConfiguredAuthType(env) ?? env.auth?.type ?? '',
39
+ env.runtime?.version ?? '',
40
+ ]);
41
+ }
42
+ this.log(renderTable(['Current', 'Name', 'Kind', 'API Base URL', 'Auth', 'Runtime'], rows));
43
+ }
44
+ }
@@ -0,0 +1,84 @@
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 { getCurrentEnvName, loadAuthConfig, removeEnv } from '../../lib/auth-store.js';
11
+ import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
12
+ import { confirm } from "../../lib/inquirer.js";
13
+ import { isInteractiveTerminal, printVerbose, setVerboseMode } from '../../lib/ui.js';
14
+ export default class EnvRemove extends Command {
15
+ static summary = 'Remove a configured environment';
16
+ static description = 'Remove the saved CLI env config for an environment. This command does not clean local app files, containers, or storage data.';
17
+ static examples = [
18
+ '<%= config.bin %> <%= command.id %> staging',
19
+ '<%= config.bin %> <%= command.id %> staging --yes',
20
+ ];
21
+ static flags = {
22
+ yes: Flags.boolean({
23
+ char: 'y',
24
+ description: 'Skip confirmation and remove the saved CLI env config',
25
+ default: false,
26
+ }),
27
+ force: Flags.boolean({
28
+ char: 'f',
29
+ hidden: true,
30
+ default: false,
31
+ }),
32
+ verbose: Flags.boolean({
33
+ description: 'Show detailed progress output',
34
+ default: false,
35
+ }),
36
+ };
37
+ static args = {
38
+ name: Args.string({
39
+ description: 'Configured environment name to remove',
40
+ required: true,
41
+ }),
42
+ };
43
+ async run() {
44
+ const { args, flags } = await this.parse(EnvRemove);
45
+ setVerboseMode(flags.verbose);
46
+ const scope = resolveDefaultConfigScope();
47
+ const config = await loadAuthConfig({ scope });
48
+ if (!config.envs[args.name]) {
49
+ this.error(`Env "${args.name}" is not configured`);
50
+ }
51
+ const currentEnv = await getCurrentEnvName({ scope });
52
+ const skipConfirmation = flags.yes || flags.force;
53
+ if (!skipConfirmation) {
54
+ if (!isInteractiveTerminal()) {
55
+ this.error(`Refusing to remove env "${args.name}" without confirmation in non-interactive mode. Re-run with \`--yes\` to remove only the saved CLI env config.`);
56
+ }
57
+ const subject = args.name === currentEnv ? `current env "${args.name}"` : `env "${args.name}"`;
58
+ let confirmed = false;
59
+ try {
60
+ confirmed = await confirm({
61
+ message: `Remove ${subject}? Only the saved CLI env config will be removed.`,
62
+ default: false,
63
+ });
64
+ }
65
+ catch {
66
+ return;
67
+ }
68
+ if (!confirmed) {
69
+ return;
70
+ }
71
+ }
72
+ printVerbose(`Removing env "${args.name}"`);
73
+ const result = await removeEnv(args.name, { scope });
74
+ if (result.hasEnvs) {
75
+ if (args.name === currentEnv) {
76
+ this.log(`Removed env "${result.removed}". Switched current env to "${await getCurrentEnvName({ scope })}".`);
77
+ return;
78
+ }
79
+ this.log(`Removed env "${result.removed}".`);
80
+ return;
81
+ }
82
+ this.log(`Removed env "${result.removed}". No envs configured.`);
83
+ }
84
+ }
@@ -0,0 +1,196 @@
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
+ export function resolveApiBaseUrl(config) {
12
+ return String(config.apiBaseUrl ?? config.baseUrl ?? config.apibaseUrl ?? '').trim();
13
+ }
14
+ function buildAppPath(publicPath, subapp) {
15
+ const normalizedPublicPath = publicPath.replace(/\/+$/, '');
16
+ if (!subapp) {
17
+ return normalizedPublicPath ? `${normalizedPublicPath}/` : '/';
18
+ }
19
+ const normalizedSubapp = subapp.replace(/^\/+|\/+$/g, '');
20
+ return `${normalizedPublicPath ? normalizedPublicPath : ''}/apps/${normalizedSubapp}/`;
21
+ }
22
+ export function resolveAppUrlFromApiBaseUrl(apiBaseUrl) {
23
+ const value = String(apiBaseUrl ?? '').trim();
24
+ if (!value) {
25
+ return '';
26
+ }
27
+ try {
28
+ const url = new URL(value);
29
+ const subappMatch = url.pathname.match(/^(.*)\/api\/__app\/([^/]+)\/?$/);
30
+ if (subappMatch) {
31
+ url.pathname = buildAppPath(subappMatch[1] ?? '', subappMatch[2]);
32
+ url.search = '';
33
+ url.hash = '';
34
+ return url.toString();
35
+ }
36
+ const appMatch = url.pathname.match(/^(.*)\/api\/?$/);
37
+ if (appMatch) {
38
+ url.pathname = buildAppPath(appMatch[1] ?? '');
39
+ url.search = '';
40
+ url.hash = '';
41
+ return url.toString();
42
+ }
43
+ }
44
+ catch {
45
+ return value;
46
+ }
47
+ return value;
48
+ }
49
+ export function appUrl(runtime) {
50
+ const resolvedFromApiBaseUrl = resolveAppUrlFromApiBaseUrl(runtime.env.apiBaseUrl ?? resolveApiBaseUrl(runtime.env.config));
51
+ if (resolvedFromApiBaseUrl) {
52
+ return resolvedFromApiBaseUrl;
53
+ }
54
+ const port = String(runtime.env.config.appPort ?? '').trim();
55
+ if (port) {
56
+ return `http://127.0.0.1:${port}/`;
57
+ }
58
+ return '';
59
+ }
60
+ export function appRootPath(runtime) {
61
+ if (runtime.kind === 'http' || runtime.kind === 'docker') {
62
+ return '-';
63
+ }
64
+ if (runtime.kind === 'local') {
65
+ return String(runtime.projectRoot ?? runtime.env.appRootPath ?? '').trim() || '-';
66
+ }
67
+ return String(runtime.env.config.appRootPath ?? '').trim() || '-';
68
+ }
69
+ export function storagePath(runtime) {
70
+ if (runtime.kind === 'http') {
71
+ return '-';
72
+ }
73
+ const value = String(runtime.env.storagePath ?? runtime.env.config.storagePath ?? '').trim();
74
+ return value || '-';
75
+ }
76
+ function collectErrorCodes(value) {
77
+ if (!value || typeof value !== 'object') {
78
+ return [];
79
+ }
80
+ if (Array.isArray(value)) {
81
+ return value.flatMap((item) => collectErrorCodes(item));
82
+ }
83
+ const out = [];
84
+ const record = value;
85
+ if (typeof record.code === 'string') {
86
+ out.push(record.code);
87
+ }
88
+ for (const key of ['data', 'error', 'errors']) {
89
+ out.push(...collectErrorCodes(record[key]));
90
+ }
91
+ return out;
92
+ }
93
+ function isAuthFailureData(value) {
94
+ const codes = collectErrorCodes(value);
95
+ return codes.some((code) => ['EMPTY_TOKEN', 'INVALID_TOKEN', 'EXPIRED_TOKEN', 'BLOCKED_TOKEN', 'EXPIRED_SESSION', 'NOT_EXIST_USER'].includes(code));
96
+ }
97
+ function isNetworkFailure(error) {
98
+ if (!(error instanceof Error)) {
99
+ return false;
100
+ }
101
+ return (error.name === 'AbortError'
102
+ || /fetch failed|network|timeout|timed out|abort|ECONNREFUSED|ECONNRESET|ENOTFOUND|ETIMEDOUT|EAI_AGAIN|ENETUNREACH/i.test(error.message));
103
+ }
104
+ function isUnconfiguredFailure(error) {
105
+ return error instanceof Error && /missing (a )?base url|missing base URL/i.test(error.message);
106
+ }
107
+ function isAuthFailure(error) {
108
+ return (error instanceof Error
109
+ && /EMPTY_TOKEN|INVALID_TOKEN|EXPIRED_TOKEN|BLOCKED_TOKEN|EXPIRED_SESSION|NOT_EXIST_USER|invalid_grant|sign in|signin|authentication failed/i.test(error.message));
110
+ }
111
+ export async function apiStatus(envName, config, options = {}) {
112
+ if (!resolveApiBaseUrl(config)) {
113
+ return 'unconfigured';
114
+ }
115
+ try {
116
+ const response = await executeRawApiRequest({
117
+ envName,
118
+ scope: options.scope,
119
+ method: 'GET',
120
+ path: '/auth:check',
121
+ timeoutMs: options.timeoutMs ?? 2000,
122
+ });
123
+ if (response.ok) {
124
+ return 'ok';
125
+ }
126
+ if (response.status === 401 || response.status === 403 || isAuthFailureData(response.data)) {
127
+ return 'auth failed';
128
+ }
129
+ return 'error';
130
+ }
131
+ catch (error) {
132
+ if (isUnconfiguredFailure(error)) {
133
+ return 'unconfigured';
134
+ }
135
+ if (isAuthFailure(error)) {
136
+ return 'auth failed';
137
+ }
138
+ if (isNetworkFailure(error)) {
139
+ return 'unreachable';
140
+ }
141
+ return 'error';
142
+ }
143
+ }
144
+ async function isLocalAppHealthy(runtime) {
145
+ const port = String(runtime.env.config.appPort ?? '').trim();
146
+ if (!port) {
147
+ return false;
148
+ }
149
+ const controller = new AbortController();
150
+ const timeout = setTimeout(() => controller.abort(), 1500);
151
+ try {
152
+ const response = await fetch(`http://127.0.0.1:${port}/api/__health_check`, {
153
+ signal: controller.signal,
154
+ });
155
+ const text = await response.text();
156
+ return response.ok && text.trim().toLowerCase() === 'ok';
157
+ }
158
+ catch (_error) {
159
+ return false;
160
+ }
161
+ finally {
162
+ clearTimeout(timeout);
163
+ }
164
+ }
165
+ async function dockerStatus(containerName) {
166
+ if (!(await dockerContainerExists(containerName))) {
167
+ return 'missing';
168
+ }
169
+ return await dockerContainerIsRunning(containerName) ? 'running' : 'stopped';
170
+ }
171
+ export async function dbStatus(runtime) {
172
+ if (!runtime.env.config.builtinDb) {
173
+ return runtime.kind === 'http' ? 'external' : '-';
174
+ }
175
+ if (runtime.kind === 'http') {
176
+ return 'external';
177
+ }
178
+ if (runtime.kind === 'ssh') {
179
+ return '-';
180
+ }
181
+ const dbDialect = String(runtime.env.config.dbDialect ?? 'postgres').trim() || 'postgres';
182
+ const containerName = buildDockerDbContainerName(runtime.envName, dbDialect, runtime.dockerContainerPrefix || runtime.workspaceName);
183
+ return await dockerStatus(containerName);
184
+ }
185
+ export async function runtimeStatus(runtime) {
186
+ if (runtime.kind === 'http') {
187
+ return 'http';
188
+ }
189
+ if (runtime.kind === 'ssh') {
190
+ return 'ssh';
191
+ }
192
+ if (runtime.kind === 'docker') {
193
+ return await dockerStatus(runtime.containerName);
194
+ }
195
+ return await isLocalAppHealthy(runtime) ? 'running' : 'stopped';
196
+ }
@@ -0,0 +1,90 @@
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 { getCurrentEnvName, listEnvs } from '../../lib/auth-store.js';
12
+ import { resolveDefaultConfigScope } from '../../lib/cli-home.js';
13
+ import { renderTable } from '../../lib/ui.js';
14
+ import { apiStatus, runtimeStatus } from './shared.js';
15
+ export default class EnvStatus extends Command {
16
+ static summary = 'Show application runtime status for the current env, one env, or all envs';
17
+ static examples = [
18
+ '<%= config.bin %> <%= command.id %>',
19
+ '<%= config.bin %> <%= command.id %> app1',
20
+ '<%= config.bin %> <%= command.id %> --all',
21
+ '<%= config.bin %> <%= command.id %> --all --json-output',
22
+ ];
23
+ static args = {
24
+ name: Args.string({
25
+ description: 'Configured environment name to inspect. Defaults to the current env when omitted; cannot be used with --all',
26
+ required: false,
27
+ }),
28
+ };
29
+ static flags = {
30
+ all: Flags.boolean({
31
+ description: 'Show status for all configured envs',
32
+ default: false,
33
+ }),
34
+ 'json-output': Flags.boolean({
35
+ description: 'Output the result as JSON',
36
+ default: false,
37
+ }),
38
+ };
39
+ async run() {
40
+ const { args, flags } = await this.parse(EnvStatus);
41
+ const scope = resolveDefaultConfigScope();
42
+ const requestedEnv = args.name?.trim() || undefined;
43
+ if (requestedEnv && flags.all) {
44
+ this.error('`nb env status <name>` and `nb env status --all` cannot be used together.');
45
+ }
46
+ const { envs } = await listEnvs({ scope });
47
+ const configuredEnvNames = Object.keys(envs).sort();
48
+ if (!configuredEnvNames.length) {
49
+ this.log('No envs configured.');
50
+ this.log('Run `nb env add <name> --api-base-url <url>` to add one.');
51
+ return;
52
+ }
53
+ const envNames = flags.all
54
+ ? configuredEnvNames
55
+ : [requestedEnv || (await getCurrentEnvName({ scope }))];
56
+ const rows = [];
57
+ for (const envName of envNames) {
58
+ const runtime = await resolveManagedAppRuntime(envName);
59
+ if (!runtime) {
60
+ if (!flags.all) {
61
+ this.error(formatMissingManagedAppEnvMessage(envName));
62
+ }
63
+ rows.push({
64
+ env: envName,
65
+ status: 'missing',
66
+ apiBaseUrl: '',
67
+ });
68
+ continue;
69
+ }
70
+ const status = runtime.kind === 'http' || runtime.kind === 'ssh'
71
+ ? await apiStatus(envName, {
72
+ ...envs[envName],
73
+ ...(runtime.env.config ?? {}),
74
+ }, { scope })
75
+ : await runtimeStatus(runtime);
76
+ rows.push({
77
+ env: runtime.envName,
78
+ status,
79
+ apiBaseUrl: runtime.env.apiBaseUrl
80
+ || String(runtime.env.config.apiBaseUrl ?? runtime.env.config.baseUrl ?? envs[envName]?.apiBaseUrl ?? envs[envName]?.baseUrl ?? '').trim(),
81
+ });
82
+ }
83
+ if (flags['json-output']) {
84
+ this.log(JSON.stringify(flags.all ? rows : rows[0], null, 2));
85
+ return;
86
+ }
87
+ const tableRows = rows.map((row) => [row.env, row.status, row.apiBaseUrl]);
88
+ this.log(renderTable(['Env', 'Status', 'API Base URL'], tableRows));
89
+ }
90
+ }