@nocobase/cli 2.1.0-beta.9 → 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,528 @@
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 { resolveAppPublicPath } from './app-public-path.js';
12
+ import { resolveCliHomeDir, resolveConfiguredEnvPath, resolveEnvRelativePath } from './cli-home.js';
13
+ import { normalizeCliLocale } from './cli-locale.js';
14
+ import { inferConfiguredAppPathFromLegacyConfig, resolveConfiguredAppPath, resolveConfiguredSourcePath, resolveConfiguredStoragePath, } from './env-paths.js';
15
+ import { cleanupCurrentSessionAfterEnvRemoval, resolveEffectiveCurrentEnv, setSessionCurrentEnv, } from './session-store.js';
16
+ function normalizeStoredEnvKind(value) {
17
+ const kind = String(value ?? '').trim();
18
+ if (kind === 'remote') {
19
+ return 'http';
20
+ }
21
+ if (kind === 'local' || kind === 'http' || kind === 'docker' || kind === 'ssh') {
22
+ return kind;
23
+ }
24
+ return undefined;
25
+ }
26
+ function normalizeOptionalString(value) {
27
+ const normalized = String(value ?? '').trim();
28
+ return normalized || undefined;
29
+ }
30
+ function normalizeOptionalCliLocale(value) {
31
+ const normalized = normalizeOptionalString(value);
32
+ if (!normalized) {
33
+ return undefined;
34
+ }
35
+ return normalizeCliLocale(normalized);
36
+ }
37
+ function normalizeOptionalCliUpdatePolicy(value) {
38
+ const normalized = normalizeOptionalString(value);
39
+ if (normalized === 'prompt' || normalized === 'auto' || normalized === 'off') {
40
+ return normalized;
41
+ }
42
+ return undefined;
43
+ }
44
+ export function readEnvApiBaseUrl(config) {
45
+ if (!config) {
46
+ return undefined;
47
+ }
48
+ return (normalizeOptionalString(config.apiBaseUrl) ??
49
+ normalizeOptionalString(config.baseUrl) ??
50
+ normalizeOptionalString(config.apibaseUrl));
51
+ }
52
+ export function resolveEnvKind(config) {
53
+ if (!config) {
54
+ return undefined;
55
+ }
56
+ const explicitKind = normalizeStoredEnvKind(config.kind);
57
+ if (explicitKind) {
58
+ return explicitKind;
59
+ }
60
+ const source = String(config.source ?? '').trim();
61
+ if (source === 'docker') {
62
+ return 'docker';
63
+ }
64
+ if (source === 'npm' || source === 'git' || source === 'local') {
65
+ return 'local';
66
+ }
67
+ if (String(config.appPath ?? '').trim() || String(config.appRootPath ?? '').trim()) {
68
+ return 'local';
69
+ }
70
+ if (readEnvApiBaseUrl(config) || config.auth) {
71
+ return 'http';
72
+ }
73
+ return undefined;
74
+ }
75
+ function normalizeEnvConfigEntry(entry) {
76
+ if (!entry) {
77
+ return entry;
78
+ }
79
+ const { kind: _kind, apiBaseUrl: _apiBaseUrl, baseUrl: _baseUrl, apibaseUrl: _legacyApiBaseUrl, ...rest } = entry;
80
+ const normalizedKind = resolveEnvKind(entry);
81
+ const apiBaseUrl = readEnvApiBaseUrl(entry);
82
+ return {
83
+ ...rest,
84
+ ...(normalizedKind ? { kind: normalizedKind } : {}),
85
+ ...(apiBaseUrl !== undefined ? { apiBaseUrl } : {}),
86
+ ...(normalizeOptionalString(entry.appPublicPath) ? { appPublicPath: resolveAppPublicPath(entry.appPublicPath) } : {}),
87
+ };
88
+ }
89
+ function normalizeAuthConfig(config) {
90
+ const settings = config.settings ?? {};
91
+ const locale = normalizeOptionalCliLocale(settings.locale);
92
+ const defaultUiHost = normalizeOptionalString(settings.init?.defaultUiHost);
93
+ const defaultApiHost = normalizeOptionalString(settings.init?.defaultApiHost);
94
+ const updatePolicy = normalizeOptionalCliUpdatePolicy(settings.update?.policy);
95
+ const logRetentionDays = typeof settings.log?.retentionDays === 'number' && Number.isInteger(settings.log.retentionDays)
96
+ ? settings.log.retentionDays
97
+ : undefined;
98
+ const logEnabled = typeof settings.log?.enabled === 'boolean' ? settings.log.enabled : undefined;
99
+ return {
100
+ name: config.name || config.dockerResourcePrefix,
101
+ settings: {
102
+ ...(locale ? { locale } : {}),
103
+ ...(defaultUiHost || defaultApiHost
104
+ ? {
105
+ init: {
106
+ ...(defaultUiHost ? { defaultUiHost } : {}),
107
+ ...(defaultApiHost ? { defaultApiHost } : {}),
108
+ },
109
+ }
110
+ : {}),
111
+ ...(updatePolicy ? { update: { policy: updatePolicy } } : {}),
112
+ ...(settings.license?.pkgUrl ? { license: { pkgUrl: normalizeOptionalString(settings.license.pkgUrl) } } : {}),
113
+ ...(settings.docker?.network || settings.docker?.containerPrefix
114
+ ? {
115
+ docker: {
116
+ ...(settings.docker?.network ? { network: normalizeOptionalString(settings.docker.network) } : {}),
117
+ ...(settings.docker?.containerPrefix
118
+ ? { containerPrefix: normalizeOptionalString(settings.docker.containerPrefix) }
119
+ : {}),
120
+ },
121
+ }
122
+ : {}),
123
+ ...(settings.bin?.docker || settings.bin?.caddy || settings.bin?.git || settings.bin?.nginx || settings.bin?.yarn
124
+ ? {
125
+ bin: {
126
+ ...(settings.bin?.docker ? { docker: normalizeOptionalString(settings.bin.docker) } : {}),
127
+ ...(settings.bin?.caddy ? { caddy: normalizeOptionalString(settings.bin.caddy) } : {}),
128
+ ...(settings.bin?.git ? { git: normalizeOptionalString(settings.bin.git) } : {}),
129
+ ...(settings.bin?.nginx ? { nginx: normalizeOptionalString(settings.bin.nginx) } : {}),
130
+ ...(settings.bin?.yarn ? { yarn: normalizeOptionalString(settings.bin.yarn) } : {}),
131
+ },
132
+ }
133
+ : {}),
134
+ ...(settings.proxy?.nbCliRoot ||
135
+ settings.proxy?.caddyDriver ||
136
+ settings.proxy?.nginxDriver ||
137
+ settings.proxy?.upstreamHost ||
138
+ settings.proxy?.host
139
+ ? {
140
+ proxy: {
141
+ ...(settings.proxy?.nbCliRoot ? { nbCliRoot: normalizeOptionalString(settings.proxy.nbCliRoot) } : {}),
142
+ ...(settings.proxy?.caddyDriver
143
+ ? { caddyDriver: normalizeOptionalString(settings.proxy.caddyDriver) }
144
+ : {}),
145
+ ...(settings.proxy?.nginxDriver
146
+ ? { nginxDriver: normalizeOptionalString(settings.proxy.nginxDriver) }
147
+ : {}),
148
+ ...(settings.proxy?.upstreamHost || settings.proxy?.host
149
+ ? {
150
+ upstreamHost: normalizeOptionalString(settings.proxy?.upstreamHost ?? settings.proxy.host),
151
+ }
152
+ : {}),
153
+ },
154
+ }
155
+ : {}),
156
+ ...(logEnabled !== undefined || logRetentionDays !== undefined
157
+ ? {
158
+ log: {
159
+ ...(logEnabled !== undefined ? { enabled: logEnabled } : {}),
160
+ ...(logRetentionDays !== undefined ? { retentionDays: logRetentionDays } : {}),
161
+ },
162
+ }
163
+ : {}),
164
+ },
165
+ lastEnv: config.lastEnv ||
166
+ config.currentEnv ||
167
+ 'default',
168
+ envs: Object.fromEntries(Object.entries(config.envs || {}).map(([envName, entry]) => [envName, normalizeEnvConfigEntry(entry) ?? {}])),
169
+ };
170
+ }
171
+ function getConfigFile(options = {}) {
172
+ return path.join(resolveCliHomeDir(options.scope), 'config.json');
173
+ }
174
+ function createDefaultConfig() {
175
+ return {
176
+ lastEnv: 'default',
177
+ envs: {},
178
+ };
179
+ }
180
+ async function readStoredAuthConfig(filePath) {
181
+ try {
182
+ const content = await fs.readFile(filePath, 'utf8');
183
+ const parsed = JSON.parse(content);
184
+ return normalizeAuthConfig(parsed);
185
+ }
186
+ catch (_error) {
187
+ return undefined;
188
+ }
189
+ }
190
+ export async function loadExactAuthConfig(options = {}) {
191
+ return (await readStoredAuthConfig(getConfigFile(options))) ?? createDefaultConfig();
192
+ }
193
+ export async function loadAuthConfig(options = {}) {
194
+ return await loadExactAuthConfig(options);
195
+ }
196
+ export async function saveAuthConfig(config, options = {}) {
197
+ const filePath = getConfigFile(options);
198
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
199
+ await fs.writeFile(filePath, JSON.stringify(normalizeAuthConfig(config), null, 2));
200
+ }
201
+ export async function listEnvs(options = {}) {
202
+ const config = await loadAuthConfig(options);
203
+ return {
204
+ lastEnv: config.lastEnv || 'default',
205
+ envs: config.envs,
206
+ };
207
+ }
208
+ export async function getCurrentEnvName(options = {}) {
209
+ const config = await loadAuthConfig(options);
210
+ return await resolveEffectiveCurrentEnv(Object.keys(config.envs).sort(), {
211
+ scope: options.scope,
212
+ lastEnv: config.lastEnv,
213
+ });
214
+ }
215
+ export async function setCurrentEnv(envName, options = {}) {
216
+ const config = await loadExactAuthConfig(options);
217
+ if (!config.envs[envName]) {
218
+ throw new Error(`Env "${envName}" is not configured`);
219
+ }
220
+ config.lastEnv = envName;
221
+ await setSessionCurrentEnv(envName, options.scope);
222
+ await saveAuthConfig(config, options);
223
+ }
224
+ export class Env {
225
+ config;
226
+ constructor(config = {}) {
227
+ this.config = config;
228
+ }
229
+ get name() {
230
+ return this.config.name;
231
+ }
232
+ get baseUrl() {
233
+ return readEnvApiBaseUrl(this.config);
234
+ }
235
+ get apiBaseUrl() {
236
+ return readEnvApiBaseUrl(this.config);
237
+ }
238
+ get auth() {
239
+ return this.config.auth;
240
+ }
241
+ get authType() {
242
+ return resolveConfiguredAuthType(this.config);
243
+ }
244
+ get runtime() {
245
+ return this.config.runtime;
246
+ }
247
+ get kind() {
248
+ return resolveEnvKind(this.config);
249
+ }
250
+ get appRootPath() {
251
+ if (this.kind === 'ssh') {
252
+ const configuredPath = String(this.config.appRootPath ?? '').trim();
253
+ if (configuredPath) {
254
+ return configuredPath;
255
+ }
256
+ }
257
+ const legacyPath = resolveConfiguredEnvPath(this.config.appRootPath);
258
+ if (legacyPath) {
259
+ return legacyPath;
260
+ }
261
+ return this.kind === 'local' ? this.sourcePath : resolveEnvRelativePath('.');
262
+ }
263
+ get appPath() {
264
+ if (this.kind === 'ssh') {
265
+ const configuredPath = String(this.config.appPath ?? inferConfiguredAppPathFromLegacyConfig(this.config) ?? '').trim();
266
+ if (configuredPath) {
267
+ return configuredPath;
268
+ }
269
+ }
270
+ return resolveConfiguredAppPath(this.config) ?? resolveEnvRelativePath('.');
271
+ }
272
+ get sourcePath() {
273
+ if (this.kind === 'ssh') {
274
+ const configuredPath = String(this.config.appRootPath ?? '').trim();
275
+ if (configuredPath) {
276
+ return configuredPath;
277
+ }
278
+ }
279
+ return resolveConfiguredSourcePath(this.config) ?? path.join(this.appPath, 'source');
280
+ }
281
+ get storagePath() {
282
+ if (this.kind === 'ssh') {
283
+ const configuredPath = String(this.config.storagePath ?? '').trim();
284
+ if (configuredPath) {
285
+ return configuredPath;
286
+ }
287
+ }
288
+ const resolvedStoragePath = resolveConfiguredStoragePath(this.config);
289
+ if (resolvedStoragePath) {
290
+ return resolvedStoragePath;
291
+ }
292
+ return this.kind === 'local' || this.kind === 'docker'
293
+ ? path.join(this.appPath, 'storage')
294
+ : resolveEnvRelativePath('.');
295
+ }
296
+ get appPort() {
297
+ return this.config.appPort;
298
+ }
299
+ get envVars() {
300
+ const out = {
301
+ STORAGE_PATH: this.storagePath,
302
+ };
303
+ const put = (key, value) => {
304
+ if (value === undefined || value === null) {
305
+ return;
306
+ }
307
+ out[key] = String(value);
308
+ };
309
+ put('APP_PORT', this.appPort);
310
+ put('APP_PUBLIC_PATH', this.config.appPublicPath ? resolveAppPublicPath(this.config.appPublicPath) : undefined);
311
+ put('CDN_BASE_URL', this.config.cdnBaseUrl);
312
+ put('APP_KEY', this.config.appKey);
313
+ put('TZ', this.config.timezone);
314
+ put('DB_DIALECT', this.config.dbDialect);
315
+ if (!this.config.builtinDb) {
316
+ put('DB_HOST', this.config.dbHost);
317
+ put('DB_PORT', this.config.dbPort);
318
+ }
319
+ else if (String(this.config.source ?? '').trim() !== 'docker') {
320
+ put('DB_PORT', this.config.dbPort);
321
+ }
322
+ put('DB_DATABASE', this.config.dbDatabase);
323
+ put('DB_USER', this.config.dbUser);
324
+ put('DB_PASSWORD', this.config.dbPassword);
325
+ put('DB_SCHEMA', this.config.dbSchema);
326
+ put('DB_TABLE_PREFIX', this.config.dbTablePrefix);
327
+ put('DB_UNDERSCORED', this.config.dbUnderscored);
328
+ return out;
329
+ }
330
+ }
331
+ export async function getEnv(envName, options = {}) {
332
+ const { config: snapshot, ...loadOptions } = options;
333
+ const config = snapshot ?? (await loadAuthConfig(loadOptions));
334
+ const resolved = envName?.trim() ||
335
+ (await resolveEffectiveCurrentEnv(Object.keys(config.envs).sort(), {
336
+ scope: loadOptions.scope,
337
+ lastEnv: config.lastEnv,
338
+ }));
339
+ const envConfig = config.envs[resolved];
340
+ if (!envConfig) {
341
+ return undefined;
342
+ }
343
+ return new Env({ ...(normalizeEnvConfigEntry(envConfig) ?? {}), name: resolved });
344
+ }
345
+ function areAuthConfigsEquivalent(left, right) {
346
+ if (!left && !right) {
347
+ return true;
348
+ }
349
+ if (!left || !right || left.type !== right.type) {
350
+ return false;
351
+ }
352
+ if (left.type === 'token' && right.type === 'token') {
353
+ return left.accessToken === right.accessToken;
354
+ }
355
+ if (left.type === 'oauth' && right.type === 'oauth') {
356
+ return (left.accessToken === right.accessToken &&
357
+ left.refreshToken === right.refreshToken &&
358
+ left.expiresAt === right.expiresAt &&
359
+ left.scope === right.scope &&
360
+ left.issuer === right.issuer &&
361
+ left.clientId === right.clientId &&
362
+ left.resource === right.resource);
363
+ }
364
+ return false;
365
+ }
366
+ async function writeEnv(envName, updater, options = {}) {
367
+ const config = await loadExactAuthConfig(options);
368
+ const previous = config.envs[envName];
369
+ config.envs[envName] = updater(previous);
370
+ await saveAuthConfig(config, options);
371
+ }
372
+ function normalizeConfiguredAuthType(value) {
373
+ return value === 'basic' || value === 'token' || value === 'oauth' ? value : undefined;
374
+ }
375
+ export function resolveConfiguredAuthType(config) {
376
+ return normalizeConfiguredAuthType(config?.authType) ?? normalizeConfiguredAuthType(config?.auth?.type);
377
+ }
378
+ export async function upsertEnv(envName, config, options = {}) {
379
+ await writeEnv(envName, (previous) => {
380
+ const { apiBaseUrl: _apiBaseUrl, baseUrl: _baseUrl, apibaseUrl: _legacyApiBaseUrl, accessToken, authType, authUsername, ...rest } = config;
381
+ const nextApiBaseUrl = readEnvApiBaseUrl(config);
382
+ const previousApiBaseUrl = readEnvApiBaseUrl(previous);
383
+ const baseUrlChanged = previousApiBaseUrl !== nextApiBaseUrl;
384
+ const previousAuthType = resolveConfiguredAuthType(previous);
385
+ const requestedAuthType = normalizeConfiguredAuthType(authType);
386
+ const nextAuthType = requestedAuthType ?? (accessToken ? 'token' : previousAuthType);
387
+ const nextAuthUsername = nextAuthType === 'basic' ? normalizeOptionalString(authUsername) ?? previous?.authUsername : undefined;
388
+ const nextAuth = accessToken
389
+ ? {
390
+ type: 'token',
391
+ accessToken,
392
+ }
393
+ : nextAuthType === 'oauth' && !baseUrlChanged && previous?.auth?.type === 'oauth'
394
+ ? previous.auth
395
+ : undefined;
396
+ const authChanged = !areAuthConfigsEquivalent(previous?.auth, nextAuth);
397
+ const authTypeChanged = previousAuthType !== nextAuthType;
398
+ const authUsernameChanged = previous?.authUsername !== nextAuthUsername;
399
+ return {
400
+ ...previous,
401
+ apiBaseUrl: nextApiBaseUrl,
402
+ authType: nextAuthType,
403
+ authUsername: nextAuthUsername,
404
+ auth: nextAuth,
405
+ ...rest,
406
+ runtime: baseUrlChanged || authChanged || authTypeChanged || authUsernameChanged ? undefined : previous?.runtime,
407
+ };
408
+ }, options);
409
+ }
410
+ export async function updateEnvConnection(envName, updates, options = {}) {
411
+ await writeEnv(envName, (previous) => {
412
+ const nextApiBaseUrl = readEnvApiBaseUrl(updates) ?? readEnvApiBaseUrl(previous);
413
+ const previousApiBaseUrl = readEnvApiBaseUrl(previous);
414
+ const baseUrlChanged = previousApiBaseUrl !== nextApiBaseUrl;
415
+ const previousAuthType = resolveConfiguredAuthType(previous);
416
+ const requestedAuthType = normalizeConfiguredAuthType(updates.authType);
417
+ const nextAuthType = requestedAuthType ?? (updates.accessToken ? 'token' : previousAuthType);
418
+ const nextAuthUsername = nextAuthType === 'basic' ? normalizeOptionalString(updates.authUsername) ?? previous?.authUsername : undefined;
419
+ const nextAuth = updates.accessToken
420
+ ? {
421
+ type: 'token',
422
+ accessToken: updates.accessToken,
423
+ }
424
+ : nextAuthType === 'oauth' && !baseUrlChanged && previous?.auth?.type === 'oauth'
425
+ ? previous.auth
426
+ : undefined;
427
+ const authChanged = !areAuthConfigsEquivalent(previous?.auth, nextAuth);
428
+ const authTypeChanged = previousAuthType !== nextAuthType;
429
+ const authUsernameChanged = previous?.authUsername !== nextAuthUsername;
430
+ return {
431
+ ...previous,
432
+ ...(nextApiBaseUrl !== undefined ? { apiBaseUrl: nextApiBaseUrl } : {}),
433
+ authType: nextAuthType,
434
+ authUsername: nextAuthUsername,
435
+ auth: nextAuth,
436
+ runtime: baseUrlChanged || authChanged || authTypeChanged || authUsernameChanged ? undefined : previous?.runtime,
437
+ };
438
+ }, options);
439
+ }
440
+ export async function replaceEnvConfig(envName, config, options = {}) {
441
+ await writeEnv(envName, (previous) => {
442
+ if (!previous) {
443
+ throw new Error(`Env "${envName}" is not configured`);
444
+ }
445
+ const { apiBaseUrl: _apiBaseUrl, baseUrl: _baseUrl, apibaseUrl: _legacyApiBaseUrl, accessToken, authType, authUsername, ...rest } = config;
446
+ const nextApiBaseUrl = readEnvApiBaseUrl(config);
447
+ const previousApiBaseUrl = readEnvApiBaseUrl(previous);
448
+ const baseUrlChanged = previousApiBaseUrl !== nextApiBaseUrl;
449
+ const previousAuthType = resolveConfiguredAuthType(previous);
450
+ const nextAuthType = normalizeConfiguredAuthType(authType) ?? (accessToken ? 'token' : undefined);
451
+ const nextAuthUsername = nextAuthType === 'basic' ? normalizeOptionalString(authUsername) : undefined;
452
+ const nextAuth = accessToken
453
+ ? {
454
+ type: 'token',
455
+ accessToken,
456
+ }
457
+ : nextAuthType === 'oauth' && !baseUrlChanged && previous?.auth?.type === 'oauth'
458
+ ? previous.auth
459
+ : undefined;
460
+ const authChanged = !areAuthConfigsEquivalent(previous?.auth, nextAuth);
461
+ const authTypeChanged = previousAuthType !== nextAuthType;
462
+ const authUsernameChanged = previous?.authUsername !== nextAuthUsername;
463
+ return {
464
+ ...rest,
465
+ ...(nextApiBaseUrl !== undefined ? { apiBaseUrl: nextApiBaseUrl } : {}),
466
+ ...(nextAuthType ? { authType: nextAuthType } : {}),
467
+ ...(nextAuthUsername ? { authUsername: nextAuthUsername } : {}),
468
+ ...(nextAuth ? { auth: nextAuth } : {}),
469
+ runtime: baseUrlChanged || authChanged || authTypeChanged || authUsernameChanged ? undefined : previous?.runtime,
470
+ };
471
+ }, options);
472
+ }
473
+ export async function setEnvOauthSession(envName, auth, options = {}) {
474
+ await writeEnv(envName, (previous) => ({
475
+ ...previous,
476
+ authType: 'oauth',
477
+ auth,
478
+ runtime: options.preserveRuntime ? previous?.runtime : undefined,
479
+ }), options);
480
+ }
481
+ export async function setEnvRuntime(envName, runtime, options = {}) {
482
+ const config = await loadExactAuthConfig(options);
483
+ const current = config.envs[envName] ?? {};
484
+ config.envs[envName] = {
485
+ ...current,
486
+ runtime,
487
+ };
488
+ await saveAuthConfig(config, options);
489
+ }
490
+ export async function clearEnvRootSetup(envName, options = {}) {
491
+ const config = await loadExactAuthConfig(options);
492
+ const current = config.envs[envName];
493
+ if (!current) {
494
+ return false;
495
+ }
496
+ const { rootUsername: _rootUsername, rootEmail: _rootEmail, rootPassword: _rootPassword, rootNickname: _rootNickname, ...rest } = current;
497
+ config.envs[envName] = rest;
498
+ await saveAuthConfig(config, options);
499
+ return true;
500
+ }
501
+ export async function removeEnv(envName, options = {}) {
502
+ const config = await loadExactAuthConfig(options);
503
+ if (!config.envs[envName]) {
504
+ throw new Error(`Env "${envName}" is not configured`);
505
+ }
506
+ delete config.envs[envName];
507
+ if (config.lastEnv === envName) {
508
+ const nextEnv = Object.keys(config.envs).sort()[0];
509
+ config.lastEnv = nextEnv ?? 'default';
510
+ }
511
+ await saveAuthConfig(config, options);
512
+ const remainingEnvNames = Object.keys(config.envs).sort();
513
+ const fallbackEnv = remainingEnvNames.length
514
+ ? await resolveEffectiveCurrentEnv(remainingEnvNames, {
515
+ scope: options.scope,
516
+ lastEnv: config.lastEnv,
517
+ })
518
+ : undefined;
519
+ await cleanupCurrentSessionAfterEnvRemoval(envName, {
520
+ scope: options.scope,
521
+ fallbackEnv,
522
+ });
523
+ return {
524
+ removed: envName,
525
+ lastEnv: config.lastEnv || 'default',
526
+ hasEnvs: Object.keys(config.envs).length > 0,
527
+ };
528
+ }
@@ -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 init --ui` first.');
66
+ }
67
+ if (requestedEnv?.trim()) {
68
+ throw new Error(`Env "${envName}" is not configured. Run \`nb init --ui --env ${envName}\` 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 run \`nb init --ui --env ${envName}\` to create or connect it.`,
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
+ }