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

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 (211) hide show
  1. package/LICENSE.txt +107 -0
  2. package/README.md +393 -19
  3. package/README.zh-CN.md +343 -0
  4. package/bin/run.cmd +3 -0
  5. package/bin/run.js +135 -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/down.js +301 -0
  15. package/dist/commands/app/logs.js +114 -0
  16. package/dist/commands/app/restart.js +158 -0
  17. package/dist/commands/app/start.js +305 -0
  18. package/dist/commands/app/stop.js +115 -0
  19. package/dist/commands/app/upgrade.js +636 -0
  20. package/dist/commands/backup/create.js +147 -0
  21. package/dist/commands/backup/index.js +20 -0
  22. package/dist/commands/backup/restore.js +105 -0
  23. package/{src/cli.js → dist/commands/build.js} +4 -11
  24. package/dist/commands/config/delete.js +30 -0
  25. package/dist/commands/config/get.js +29 -0
  26. package/dist/commands/config/index.js +20 -0
  27. package/dist/commands/config/list.js +29 -0
  28. package/dist/commands/config/set.js +35 -0
  29. package/dist/commands/db/check.js +240 -0
  30. package/dist/commands/db/logs.js +85 -0
  31. package/dist/commands/db/ps.js +60 -0
  32. package/dist/commands/db/shared.js +96 -0
  33. package/dist/commands/db/start.js +71 -0
  34. package/dist/commands/db/stop.js +71 -0
  35. package/{templates/plugin/src/client/models/index.ts → dist/commands/dev.js} +4 -4
  36. package/{src/commands/locale/react-js-cron/index.js → dist/commands/down.js} +3 -8
  37. package/dist/commands/download.js +13 -0
  38. package/dist/commands/env/add.js +366 -0
  39. package/dist/commands/env/auth.js +130 -0
  40. package/dist/commands/env/current.js +21 -0
  41. package/dist/commands/env/info.js +157 -0
  42. package/dist/commands/env/list.js +44 -0
  43. package/dist/commands/env/remove.js +84 -0
  44. package/dist/commands/env/shared.js +158 -0
  45. package/dist/commands/env/status.js +90 -0
  46. package/dist/commands/env/update.js +74 -0
  47. package/dist/commands/env/use.js +38 -0
  48. package/dist/commands/examples/prompts-stages.js +150 -0
  49. package/dist/commands/examples/prompts-test.js +181 -0
  50. package/dist/commands/init.js +1092 -0
  51. package/dist/commands/install.js +2378 -0
  52. package/dist/commands/license/activate.js +360 -0
  53. package/dist/commands/license/env.js +94 -0
  54. package/dist/commands/license/generate-id.js +108 -0
  55. package/dist/commands/license/id.js +70 -0
  56. package/dist/commands/license/index.js +20 -0
  57. package/dist/commands/license/plugins/clean.js +115 -0
  58. package/dist/commands/license/plugins/index.js +20 -0
  59. package/dist/commands/license/plugins/list.js +64 -0
  60. package/dist/commands/license/plugins/shared.js +325 -0
  61. package/dist/commands/license/plugins/sync.js +285 -0
  62. package/dist/commands/license/shared.js +423 -0
  63. package/dist/commands/license/status.js +64 -0
  64. package/dist/commands/logs.js +12 -0
  65. package/dist/commands/plugin/disable.js +86 -0
  66. package/dist/commands/plugin/enable.js +86 -0
  67. package/dist/commands/plugin/list.js +82 -0
  68. package/dist/commands/pm/disable.js +12 -0
  69. package/dist/commands/pm/enable.js +12 -0
  70. package/dist/commands/pm/list.js +12 -0
  71. package/dist/commands/restart.js +12 -0
  72. package/dist/commands/scaffold/migration.js +38 -0
  73. package/dist/commands/scaffold/plugin.js +37 -0
  74. package/dist/commands/self/check.js +71 -0
  75. package/dist/commands/self/index.js +20 -0
  76. package/dist/commands/self/update.js +95 -0
  77. package/dist/commands/session/id.js +24 -0
  78. package/dist/commands/session/remove.js +57 -0
  79. package/dist/commands/session/setup.js +62 -0
  80. package/dist/commands/skills/check.js +69 -0
  81. package/dist/commands/skills/index.js +20 -0
  82. package/dist/commands/skills/install.js +80 -0
  83. package/dist/commands/skills/remove.js +80 -0
  84. package/dist/commands/skills/update.js +87 -0
  85. package/dist/commands/source/build.js +58 -0
  86. package/dist/commands/source/dev.js +182 -0
  87. package/dist/commands/source/download.js +880 -0
  88. package/dist/commands/source/publish.js +109 -0
  89. package/dist/commands/source/registry/logs.js +70 -0
  90. package/dist/commands/source/registry/start.js +57 -0
  91. package/dist/commands/source/registry/status.js +33 -0
  92. package/dist/commands/source/registry/stop.js +48 -0
  93. package/dist/commands/source/test.js +477 -0
  94. package/dist/commands/start.js +12 -0
  95. package/dist/commands/stop.js +12 -0
  96. package/dist/commands/test.js +12 -0
  97. package/dist/commands/upgrade.js +12 -0
  98. package/dist/commands/v1.js +210 -0
  99. package/dist/generated/command-registry.js +133 -0
  100. package/dist/help/runtime-help.js +23 -0
  101. package/dist/lib/api-client.js +329 -0
  102. package/dist/lib/app-health.js +126 -0
  103. package/dist/lib/app-managed-resources.js +316 -0
  104. package/dist/lib/app-runtime.js +180 -0
  105. package/dist/lib/auth-store.js +368 -0
  106. package/dist/lib/backup.js +171 -0
  107. package/dist/lib/bootstrap.js +403 -0
  108. package/dist/lib/build-config.js +18 -0
  109. package/dist/lib/builtin-db.js +86 -0
  110. package/dist/lib/cli-config.js +176 -0
  111. package/dist/lib/cli-home.js +47 -0
  112. package/dist/lib/cli-locale.js +129 -0
  113. package/dist/lib/command-discovery.js +39 -0
  114. package/dist/lib/db-connection-check.js +158 -0
  115. package/dist/lib/docker-env-file.js +52 -0
  116. package/dist/lib/docker-image.js +37 -0
  117. package/dist/lib/env-auth.js +873 -0
  118. package/dist/lib/env-config.js +94 -0
  119. package/dist/lib/env-guard.js +62 -0
  120. package/dist/lib/generated-command.js +186 -0
  121. package/dist/lib/http-request.js +49 -0
  122. package/dist/lib/inquirer-theme.js +17 -0
  123. package/dist/lib/inquirer.js +244 -0
  124. package/dist/lib/naming.js +70 -0
  125. package/dist/lib/object-utils.js +76 -0
  126. package/dist/lib/openapi.js +62 -0
  127. package/dist/lib/plugin-storage.js +64 -0
  128. package/dist/lib/post-processors.js +23 -0
  129. package/dist/lib/prompt-catalog-core.js +185 -0
  130. package/dist/lib/prompt-catalog-terminal.js +375 -0
  131. package/{src/index.js → dist/lib/prompt-catalog.js} +2 -6
  132. package/dist/lib/prompt-validators.js +240 -0
  133. package/dist/lib/prompt-web-ui.js +2103 -0
  134. package/dist/lib/resource-command.js +357 -0
  135. package/dist/lib/resource-request.js +104 -0
  136. package/dist/lib/run-npm.js +275 -0
  137. package/dist/lib/runtime-env-vars.js +32 -0
  138. package/dist/lib/runtime-generator.js +498 -0
  139. package/dist/lib/runtime-store.js +56 -0
  140. package/dist/lib/self-manager.js +301 -0
  141. package/dist/lib/session-id.js +17 -0
  142. package/dist/lib/session-integration.js +703 -0
  143. package/dist/lib/session-store.js +118 -0
  144. package/dist/lib/skills-manager.js +360 -0
  145. package/dist/lib/source-publish.js +306 -0
  146. package/dist/lib/source-registry.js +188 -0
  147. package/dist/lib/startup-update.js +285 -0
  148. package/dist/lib/ui.js +155 -0
  149. package/dist/locale/en-US.json +344 -0
  150. package/dist/locale/zh-CN.json +344 -0
  151. package/dist/post-processors/data-modeling.js +84 -0
  152. package/dist/post-processors/data-source-manager.js +138 -0
  153. package/dist/post-processors/index.js +19 -0
  154. package/nocobase-ctl.config.json +388 -0
  155. package/package.json +100 -26
  156. package/LICENSE +0 -661
  157. package/bin/index.js +0 -39
  158. package/nocobase.conf.tpl +0 -95
  159. package/src/commands/benchmark.js +0 -73
  160. package/src/commands/build.js +0 -49
  161. package/src/commands/clean.js +0 -30
  162. package/src/commands/client.js +0 -166
  163. package/src/commands/create-nginx-conf.js +0 -37
  164. package/src/commands/create-plugin.js +0 -33
  165. package/src/commands/dev.js +0 -200
  166. package/src/commands/doc.js +0 -76
  167. package/src/commands/e2e.js +0 -265
  168. package/src/commands/global.js +0 -43
  169. package/src/commands/index.js +0 -45
  170. package/src/commands/instance-id.js +0 -47
  171. package/src/commands/locale/cronstrue.js +0 -122
  172. package/src/commands/locale/react-js-cron/en-US.json +0 -75
  173. package/src/commands/locale/react-js-cron/zh-CN.json +0 -33
  174. package/src/commands/locale/react-js-cron/zh-TW.json +0 -33
  175. package/src/commands/locale.js +0 -81
  176. package/src/commands/p-test.js +0 -88
  177. package/src/commands/perf.js +0 -63
  178. package/src/commands/pkg.js +0 -321
  179. package/src/commands/pm2.js +0 -37
  180. package/src/commands/postinstall.js +0 -88
  181. package/src/commands/start.js +0 -148
  182. package/src/commands/tar.js +0 -36
  183. package/src/commands/test-coverage.js +0 -55
  184. package/src/commands/test.js +0 -107
  185. package/src/commands/umi.js +0 -33
  186. package/src/commands/update-deps.js +0 -72
  187. package/src/commands/upgrade.js +0 -47
  188. package/src/commands/view-license-key.js +0 -44
  189. package/src/license.js +0 -76
  190. package/src/logger.js +0 -75
  191. package/src/plugin-generator.js +0 -80
  192. package/src/util.js +0 -517
  193. package/templates/bundle-status.html +0 -338
  194. package/templates/create-app-package.json +0 -39
  195. package/templates/plugin/.npmignore.tpl +0 -2
  196. package/templates/plugin/README.md.tpl +0 -1
  197. package/templates/plugin/client.d.ts +0 -2
  198. package/templates/plugin/client.js +0 -1
  199. package/templates/plugin/package.json.tpl +0 -11
  200. package/templates/plugin/server.d.ts +0 -2
  201. package/templates/plugin/server.js +0 -1
  202. package/templates/plugin/src/client/client.d.ts +0 -249
  203. package/templates/plugin/src/client/index.tsx.tpl +0 -1
  204. package/templates/plugin/src/client/locale.ts +0 -21
  205. package/templates/plugin/src/client/plugin.tsx.tpl +0 -10
  206. package/templates/plugin/src/index.ts +0 -2
  207. package/templates/plugin/src/locale/en-US.json +0 -1
  208. package/templates/plugin/src/locale/zh-CN.json +0 -1
  209. package/templates/plugin/src/server/collections/.gitkeep +0 -0
  210. package/templates/plugin/src/server/index.ts.tpl +0 -1
  211. package/templates/plugin/src/server/plugin.ts.tpl +0 -19
@@ -0,0 +1,368 @@
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 { promises as fs } from 'node:fs';
10
+ import path from 'node:path';
11
+ import { resolveCliHomeDir, resolveConfiguredEnvPath, resolveEnvRelativePath, } from './cli-home.js';
12
+ import { cleanupCurrentSessionAfterEnvRemoval, resolveEffectiveCurrentEnv, setSessionCurrentEnv, } from './session-store.js';
13
+ function normalizeStoredEnvKind(value) {
14
+ const kind = String(value ?? '').trim();
15
+ if (kind === 'remote') {
16
+ return 'http';
17
+ }
18
+ if (kind === 'local' || kind === 'http' || kind === 'docker' || kind === 'ssh') {
19
+ return kind;
20
+ }
21
+ return undefined;
22
+ }
23
+ function normalizeOptionalString(value) {
24
+ const normalized = String(value ?? '').trim();
25
+ return normalized || undefined;
26
+ }
27
+ export function readEnvApiBaseUrl(config) {
28
+ if (!config) {
29
+ return undefined;
30
+ }
31
+ return (normalizeOptionalString(config.apiBaseUrl)
32
+ ?? normalizeOptionalString(config.baseUrl)
33
+ ?? normalizeOptionalString(config.apibaseUrl));
34
+ }
35
+ export function resolveEnvKind(config) {
36
+ if (!config) {
37
+ return undefined;
38
+ }
39
+ const explicitKind = normalizeStoredEnvKind(config.kind);
40
+ if (explicitKind) {
41
+ return explicitKind;
42
+ }
43
+ const source = String(config.source ?? '').trim();
44
+ if (source === 'docker') {
45
+ return 'docker';
46
+ }
47
+ if (source === 'npm' || source === 'git' || source === 'local') {
48
+ return 'local';
49
+ }
50
+ if (String(config.appRootPath ?? '').trim()) {
51
+ return 'local';
52
+ }
53
+ if (readEnvApiBaseUrl(config) || config.auth) {
54
+ return 'http';
55
+ }
56
+ return undefined;
57
+ }
58
+ function normalizeEnvConfigEntry(entry) {
59
+ if (!entry) {
60
+ return entry;
61
+ }
62
+ const { kind: _kind, apiBaseUrl: _apiBaseUrl, baseUrl: _baseUrl, apibaseUrl: _legacyApiBaseUrl, ...rest } = entry;
63
+ const normalizedKind = resolveEnvKind(entry);
64
+ const apiBaseUrl = readEnvApiBaseUrl(entry);
65
+ return {
66
+ ...rest,
67
+ ...(normalizedKind ? { kind: normalizedKind } : {}),
68
+ ...(apiBaseUrl !== undefined ? { apiBaseUrl } : {}),
69
+ };
70
+ }
71
+ function normalizeAuthConfig(config) {
72
+ const settings = config.settings ?? {};
73
+ return {
74
+ name: config.name || config.dockerResourcePrefix,
75
+ settings: {
76
+ ...(settings.license?.pkgUrl ? { license: { pkgUrl: normalizeOptionalString(settings.license.pkgUrl) } } : {}),
77
+ ...(settings.docker?.network || settings.docker?.containerPrefix
78
+ ? {
79
+ docker: {
80
+ ...(settings.docker?.network ? { network: normalizeOptionalString(settings.docker.network) } : {}),
81
+ ...(settings.docker?.containerPrefix
82
+ ? { containerPrefix: normalizeOptionalString(settings.docker.containerPrefix) }
83
+ : {}),
84
+ },
85
+ }
86
+ : {}),
87
+ },
88
+ lastEnv: config.lastEnv || config.currentEnv || 'default',
89
+ envs: Object.fromEntries(Object.entries(config.envs || {}).map(([envName, entry]) => [envName, normalizeEnvConfigEntry(entry) ?? {}])),
90
+ };
91
+ }
92
+ function getConfigFile(options = {}) {
93
+ return path.join(resolveCliHomeDir(options.scope), 'config.json');
94
+ }
95
+ function createDefaultConfig() {
96
+ return {
97
+ lastEnv: 'default',
98
+ envs: {},
99
+ };
100
+ }
101
+ async function readStoredAuthConfig(filePath) {
102
+ try {
103
+ const content = await fs.readFile(filePath, 'utf8');
104
+ const parsed = JSON.parse(content);
105
+ return normalizeAuthConfig(parsed);
106
+ }
107
+ catch (_error) {
108
+ return undefined;
109
+ }
110
+ }
111
+ export async function loadExactAuthConfig(options = {}) {
112
+ return (await readStoredAuthConfig(getConfigFile(options))) ?? createDefaultConfig();
113
+ }
114
+ export async function loadAuthConfig(options = {}) {
115
+ return await loadExactAuthConfig(options);
116
+ }
117
+ export async function saveAuthConfig(config, options = {}) {
118
+ const filePath = getConfigFile(options);
119
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
120
+ await fs.writeFile(filePath, JSON.stringify(normalizeAuthConfig(config), null, 2));
121
+ }
122
+ export async function listEnvs(options = {}) {
123
+ const config = await loadAuthConfig(options);
124
+ return {
125
+ lastEnv: config.lastEnv || 'default',
126
+ envs: config.envs,
127
+ };
128
+ }
129
+ export async function getCurrentEnvName(options = {}) {
130
+ const config = await loadAuthConfig(options);
131
+ return await resolveEffectiveCurrentEnv(Object.keys(config.envs).sort(), {
132
+ scope: options.scope,
133
+ lastEnv: config.lastEnv,
134
+ });
135
+ }
136
+ export async function setCurrentEnv(envName, options = {}) {
137
+ const config = await loadExactAuthConfig(options);
138
+ if (!config.envs[envName]) {
139
+ throw new Error(`Env "${envName}" is not configured`);
140
+ }
141
+ config.lastEnv = envName;
142
+ await setSessionCurrentEnv(envName, options.scope);
143
+ await saveAuthConfig(config, options);
144
+ }
145
+ export class Env {
146
+ config;
147
+ constructor(config = {}) {
148
+ this.config = config;
149
+ }
150
+ get name() {
151
+ return this.config.name;
152
+ }
153
+ get baseUrl() {
154
+ return readEnvApiBaseUrl(this.config);
155
+ }
156
+ get apiBaseUrl() {
157
+ return readEnvApiBaseUrl(this.config);
158
+ }
159
+ get auth() {
160
+ return this.config.auth;
161
+ }
162
+ get authType() {
163
+ return resolveConfiguredAuthType(this.config);
164
+ }
165
+ get runtime() {
166
+ return this.config.runtime;
167
+ }
168
+ get kind() {
169
+ return resolveEnvKind(this.config);
170
+ }
171
+ get appRootPath() {
172
+ if (this.kind === 'ssh') {
173
+ const configuredPath = String(this.config.appRootPath ?? '').trim();
174
+ if (configuredPath) {
175
+ return configuredPath;
176
+ }
177
+ }
178
+ return resolveConfiguredEnvPath(this.config.appRootPath) ?? resolveEnvRelativePath('.');
179
+ }
180
+ get storagePath() {
181
+ if (this.kind === 'ssh') {
182
+ const configuredPath = String(this.config.storagePath ?? '').trim();
183
+ if (configuredPath) {
184
+ return configuredPath;
185
+ }
186
+ }
187
+ return resolveConfiguredEnvPath(this.config.storagePath) ?? resolveEnvRelativePath('.');
188
+ }
189
+ get appPort() {
190
+ return this.config.appPort;
191
+ }
192
+ get envVars() {
193
+ const out = {
194
+ STORAGE_PATH: this.storagePath,
195
+ };
196
+ const put = (key, value) => {
197
+ if (value === undefined || value === null) {
198
+ return;
199
+ }
200
+ out[key] = String(value);
201
+ };
202
+ put('APP_PORT', this.appPort);
203
+ put('APP_KEY', this.config.appKey);
204
+ put('TZ', this.config.timezone);
205
+ put('DB_DIALECT', this.config.dbDialect);
206
+ if (!this.config.builtinDb) {
207
+ put('DB_HOST', this.config.dbHost);
208
+ put('DB_PORT', this.config.dbPort);
209
+ }
210
+ else if (String(this.config.source ?? '').trim() !== 'docker') {
211
+ put('DB_PORT', this.config.dbPort);
212
+ }
213
+ put('DB_DATABASE', this.config.dbDatabase);
214
+ put('DB_USER', this.config.dbUser);
215
+ put('DB_PASSWORD', this.config.dbPassword);
216
+ put('DB_SCHEMA', this.config.dbSchema);
217
+ put('DB_TABLE_PREFIX', this.config.dbTablePrefix);
218
+ put('DB_UNDERSCORED', this.config.dbUnderscored);
219
+ return out;
220
+ }
221
+ }
222
+ export async function getEnv(envName, options = {}) {
223
+ const { config: snapshot, ...loadOptions } = options;
224
+ const config = snapshot ?? (await loadAuthConfig(loadOptions));
225
+ const resolved = envName?.trim() || (await resolveEffectiveCurrentEnv(Object.keys(config.envs).sort(), {
226
+ scope: loadOptions.scope,
227
+ lastEnv: config.lastEnv,
228
+ }));
229
+ const envConfig = config.envs[resolved];
230
+ if (!envConfig) {
231
+ return undefined;
232
+ }
233
+ return new Env({ ...(normalizeEnvConfigEntry(envConfig) ?? {}), name: resolved });
234
+ }
235
+ function areAuthConfigsEquivalent(left, right) {
236
+ if (!left && !right) {
237
+ return true;
238
+ }
239
+ if (!left || !right || left.type !== right.type) {
240
+ return false;
241
+ }
242
+ if (left.type === 'token' && right.type === 'token') {
243
+ return left.accessToken === right.accessToken;
244
+ }
245
+ if (left.type === 'oauth' && right.type === 'oauth') {
246
+ return (left.accessToken === right.accessToken &&
247
+ left.refreshToken === right.refreshToken &&
248
+ left.expiresAt === right.expiresAt &&
249
+ left.scope === right.scope &&
250
+ left.issuer === right.issuer &&
251
+ left.clientId === right.clientId &&
252
+ left.resource === right.resource);
253
+ }
254
+ return false;
255
+ }
256
+ async function writeEnv(envName, updater, options = {}) {
257
+ const config = await loadExactAuthConfig(options);
258
+ const previous = config.envs[envName];
259
+ config.envs[envName] = updater(previous);
260
+ await saveAuthConfig(config, options);
261
+ }
262
+ function normalizeConfiguredAuthType(value) {
263
+ return value === 'token' || value === 'oauth' ? value : undefined;
264
+ }
265
+ export function resolveConfiguredAuthType(config) {
266
+ return normalizeConfiguredAuthType(config?.authType) ?? normalizeConfiguredAuthType(config?.auth?.type);
267
+ }
268
+ export async function upsertEnv(envName, config, options = {}) {
269
+ await writeEnv(envName, (previous) => {
270
+ const { apiBaseUrl: _apiBaseUrl, baseUrl: _baseUrl, apibaseUrl: _legacyApiBaseUrl, accessToken, authType, ...rest } = config;
271
+ const nextApiBaseUrl = readEnvApiBaseUrl(config);
272
+ const previousApiBaseUrl = readEnvApiBaseUrl(previous);
273
+ const baseUrlChanged = previousApiBaseUrl !== nextApiBaseUrl;
274
+ const previousAuthType = resolveConfiguredAuthType(previous);
275
+ const requestedAuthType = normalizeConfiguredAuthType(authType);
276
+ const nextAuthType = requestedAuthType ?? (accessToken ? 'token' : previousAuthType);
277
+ const nextAuth = accessToken
278
+ ? {
279
+ type: 'token',
280
+ accessToken,
281
+ }
282
+ : nextAuthType === 'token' || baseUrlChanged || previous?.auth?.type === 'token'
283
+ ? undefined
284
+ : previous?.auth;
285
+ const authChanged = !areAuthConfigsEquivalent(previous?.auth, nextAuth);
286
+ const authTypeChanged = previousAuthType !== nextAuthType;
287
+ return {
288
+ ...previous,
289
+ apiBaseUrl: nextApiBaseUrl,
290
+ authType: nextAuthType,
291
+ auth: nextAuth,
292
+ ...rest,
293
+ runtime: baseUrlChanged || authChanged || authTypeChanged ? undefined : previous?.runtime,
294
+ };
295
+ }, options);
296
+ }
297
+ export async function updateEnvConnection(envName, updates, options = {}) {
298
+ await writeEnv(envName, (previous) => {
299
+ const nextApiBaseUrl = readEnvApiBaseUrl(updates) ?? readEnvApiBaseUrl(previous);
300
+ const previousApiBaseUrl = readEnvApiBaseUrl(previous);
301
+ const baseUrlChanged = previousApiBaseUrl !== nextApiBaseUrl;
302
+ const previousAuthType = resolveConfiguredAuthType(previous);
303
+ const requestedAuthType = normalizeConfiguredAuthType(updates.authType);
304
+ const nextAuthType = requestedAuthType ?? (updates.accessToken ? 'token' : previousAuthType);
305
+ const nextAuth = updates.accessToken
306
+ ? {
307
+ type: 'token',
308
+ accessToken: updates.accessToken,
309
+ }
310
+ : nextAuthType === 'token' || baseUrlChanged || previous?.auth?.type === 'token'
311
+ ? undefined
312
+ : previous?.auth;
313
+ const authChanged = !areAuthConfigsEquivalent(previous?.auth, nextAuth);
314
+ const authTypeChanged = previousAuthType !== nextAuthType;
315
+ return {
316
+ ...previous,
317
+ ...(nextApiBaseUrl !== undefined ? { apiBaseUrl: nextApiBaseUrl } : {}),
318
+ authType: nextAuthType,
319
+ auth: nextAuth,
320
+ runtime: baseUrlChanged || authChanged || authTypeChanged ? undefined : previous?.runtime,
321
+ };
322
+ }, options);
323
+ }
324
+ export async function setEnvOauthSession(envName, auth, options = {}) {
325
+ await writeEnv(envName, (previous) => ({
326
+ ...previous,
327
+ authType: 'oauth',
328
+ auth,
329
+ runtime: options.preserveRuntime ? previous?.runtime : undefined,
330
+ }), options);
331
+ }
332
+ export async function setEnvRuntime(envName, runtime, options = {}) {
333
+ const config = await loadExactAuthConfig(options);
334
+ const current = config.envs[envName] ?? {};
335
+ config.envs[envName] = {
336
+ ...current,
337
+ runtime,
338
+ };
339
+ await saveAuthConfig(config, options);
340
+ }
341
+ export async function removeEnv(envName, options = {}) {
342
+ const config = await loadExactAuthConfig(options);
343
+ if (!config.envs[envName]) {
344
+ throw new Error(`Env "${envName}" is not configured`);
345
+ }
346
+ delete config.envs[envName];
347
+ if (config.lastEnv === envName) {
348
+ const nextEnv = Object.keys(config.envs).sort()[0];
349
+ config.lastEnv = nextEnv ?? 'default';
350
+ }
351
+ await saveAuthConfig(config, options);
352
+ const remainingEnvNames = Object.keys(config.envs).sort();
353
+ const fallbackEnv = remainingEnvNames.length
354
+ ? await resolveEffectiveCurrentEnv(remainingEnvNames, {
355
+ scope: options.scope,
356
+ lastEnv: config.lastEnv,
357
+ })
358
+ : undefined;
359
+ await cleanupCurrentSessionAfterEnvRemoval(envName, {
360
+ scope: options.scope,
361
+ fallbackEnv,
362
+ });
363
+ return {
364
+ removed: envName,
365
+ lastEnv: config.lastEnv || 'default',
366
+ hasEnvs: Object.keys(config.envs).length > 0,
367
+ };
368
+ }
@@ -0,0 +1,171 @@
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 { promises as fs } from 'node:fs';
10
+ import path from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+ import { getCurrentEnvName, getEnv, listEnvs } from './auth-store.js';
13
+ import { updateEnvRuntime } from './bootstrap.js';
14
+ import { resolveDefaultConfigScope } from './cli-home.js';
15
+ import { commandOutput } from './run-npm.js';
16
+ import { loadRuntimeSync } from './runtime-store.js';
17
+ import { failTask, startTask, succeedTask } from './ui.js';
18
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
+ const CLI_PACKAGE_ROOT = path.resolve(__dirname, '..', '..');
20
+ const CLI_CONFIG_FILE = path.join(CLI_PACKAGE_ROOT, 'nocobase-ctl.config.json');
21
+ const CLI_ENTRY_FILE = path.join(CLI_PACKAGE_ROOT, 'bin', 'run.js');
22
+ export const BACKUP_POLL_INTERVAL_MS = 2_000;
23
+ export const BACKUP_CREATE_TIMEOUT_MS = 600_000;
24
+ export const BACKUP_RUNTIME_COMMANDS = {
25
+ create: 'backup create',
26
+ status: 'backup status',
27
+ download: 'backup download',
28
+ restoreUpload: 'backup restore-upload',
29
+ };
30
+ function hasRequiredBackupCommands(runtime, commandIds) {
31
+ if (!runtime) {
32
+ return false;
33
+ }
34
+ const available = new Set(runtime.commands.map((command) => command.commandId));
35
+ return commandIds.every((commandId) => available.has(commandId));
36
+ }
37
+ function formatMissingBackupRuntimeCommands(envName, commandIds) {
38
+ const missing = commandIds.map((commandId) => `nb api ${commandId}`).join(', ');
39
+ return [
40
+ `The selected env "${envName}" does not expose the backup API commands required by \`nb backup\`.`,
41
+ `Missing commands: ${missing}`,
42
+ 'Enable or upgrade the backup/restore capability for that env, then try again.',
43
+ ].join('\n');
44
+ }
45
+ export function buildBackupEnvArgv(options) {
46
+ const argv = [];
47
+ if (options.explicitEnvSelection && options.requestedEnv) {
48
+ argv.push('--env', options.requestedEnv);
49
+ }
50
+ if (options.yes || options.explicitEnvSelection) {
51
+ argv.push('--yes');
52
+ }
53
+ return argv;
54
+ }
55
+ export async function resolveBackupTargetEnv(requestedEnv) {
56
+ const scope = resolveDefaultConfigScope();
57
+ const envName = requestedEnv?.trim() || (await getCurrentEnvName({ scope }));
58
+ const env = await getEnv(envName, { scope });
59
+ if (env) {
60
+ return { scope, envName, env };
61
+ }
62
+ const { envs } = await listEnvs({ scope });
63
+ const configuredEnvNames = Object.keys(envs);
64
+ if (!configuredEnvNames.length) {
65
+ throw new Error('No env is configured. Run `nb env add <name> --api-base-url <url>` first.');
66
+ }
67
+ if (requestedEnv?.trim()) {
68
+ throw new Error(`Env "${envName}" is not configured. Run \`nb env add ${envName} --api-base-url <url>\` first.`);
69
+ }
70
+ throw new Error([
71
+ `Current env "${envName}" is not configured.`,
72
+ 'Switch to an existing env with `nb env use <name>`, or add one with `nb env add <name> --api-base-url <url>`.',
73
+ ].join('\n'));
74
+ }
75
+ export async function ensureBackupRuntimeCommands(params) {
76
+ const scope = resolveDefaultConfigScope();
77
+ const env = params.env ?? (await getEnv(params.envName, { scope }));
78
+ const currentRuntime = loadRuntimeSync(env?.runtime?.version, { scope });
79
+ if (hasRequiredBackupCommands(currentRuntime, params.commandIds)) {
80
+ return;
81
+ }
82
+ if (!params.quiet) {
83
+ startTask(`Refreshing env runtime for "${params.envName}" to load backup commands...`);
84
+ }
85
+ try {
86
+ const runtime = await updateEnvRuntime({
87
+ envName: params.envName,
88
+ scope,
89
+ configFile: CLI_CONFIG_FILE,
90
+ quiet: params.quiet,
91
+ });
92
+ if (!hasRequiredBackupCommands(runtime, params.commandIds)) {
93
+ throw new Error(formatMissingBackupRuntimeCommands(params.envName, params.commandIds));
94
+ }
95
+ if (!params.quiet) {
96
+ succeedTask(`Env runtime is ready for backup commands in "${params.envName}".`);
97
+ }
98
+ }
99
+ catch (error) {
100
+ if (!params.quiet) {
101
+ failTask(`Failed to refresh backup commands for "${params.envName}".`);
102
+ }
103
+ throw error;
104
+ }
105
+ }
106
+ export async function runBackupCliCommand(argv, options) {
107
+ return await commandOutput(process.execPath, [CLI_ENTRY_FILE, ...argv], {
108
+ errorName: options?.errorName ?? `nb ${argv.join(' ')}`,
109
+ env: {
110
+ NB_SKIP_STARTUP_UPDATE: '1',
111
+ // When the parent CLI already runs in tsx source mode, it sets
112
+ // `_NOCO_CLI_TSX_CHILD=1`. Clear it here so `bin/run.js` can re-exec
113
+ // itself with `--import tsx` again instead of trying to import `.ts`
114
+ // sources without the loader.
115
+ _NOCO_CLI_TSX_CHILD: '',
116
+ },
117
+ });
118
+ }
119
+ export async function runBackupCliJsonCommand(argv, options) {
120
+ const output = await runBackupCliCommand([...argv, '--json-output'], options);
121
+ try {
122
+ return JSON.parse(output);
123
+ }
124
+ catch {
125
+ throw new Error(`Unexpected JSON output from ${options?.errorName ?? `nb ${argv.join(' ')}`}: ${output || '(empty output)'}`);
126
+ }
127
+ }
128
+ export async function resolveBackupCreateOutputPath(output, remoteName) {
129
+ const requestedOutput = String(output ?? '').trim();
130
+ if (!requestedOutput) {
131
+ return path.resolve(process.cwd(), remoteName);
132
+ }
133
+ const resolvedOutput = path.resolve(process.cwd(), requestedOutput);
134
+ try {
135
+ const stats = await fs.stat(resolvedOutput);
136
+ if (stats.isDirectory()) {
137
+ return path.join(resolvedOutput, remoteName);
138
+ }
139
+ }
140
+ catch {
141
+ // Treat non-existing paths as an explicit target file path.
142
+ }
143
+ return resolvedOutput;
144
+ }
145
+ export async function resolveBackupRestoreFilePath(file) {
146
+ const resolvedFile = path.resolve(process.cwd(), file);
147
+ let stats;
148
+ try {
149
+ stats = await fs.stat(resolvedFile);
150
+ }
151
+ catch {
152
+ throw new Error(`Backup file not found: ${resolvedFile}`);
153
+ }
154
+ if (!stats.isFile()) {
155
+ throw new Error(`Backup restore input must be a file: ${resolvedFile}`);
156
+ }
157
+ return resolvedFile;
158
+ }
159
+ export function resolveBackupWaitApiBaseUrl(env) {
160
+ const baseUrl = String(env.baseUrl ?? '').trim();
161
+ if (baseUrl) {
162
+ return baseUrl.replace(/\/+$/, '');
163
+ }
164
+ const appPort = env.appPort === undefined || env.appPort === null
165
+ ? ''
166
+ : String(env.appPort).trim();
167
+ return appPort ? `http://127.0.0.1:${appPort}/api` : undefined;
168
+ }
169
+ export async function sleep(ms) {
170
+ await new Promise((resolve) => setTimeout(resolve, ms));
171
+ }