@nocobase/cli 2.1.0-beta.33 → 2.1.0-beta.34

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 (52) hide show
  1. package/dist/commands/app/down.js +10 -13
  2. package/dist/commands/app/logs.js +0 -1
  3. package/dist/commands/app/restart.js +63 -2
  4. package/dist/commands/app/start.js +41 -17
  5. package/dist/commands/app/stop.js +0 -1
  6. package/dist/commands/app/upgrade.js +9 -4
  7. package/dist/commands/env/add.js +3 -4
  8. package/dist/commands/env/auth.js +3 -2
  9. package/dist/commands/env/remove.js +38 -13
  10. package/dist/commands/env/update.js +9 -2
  11. package/dist/commands/examples/prompts-stages.js +4 -4
  12. package/dist/commands/examples/prompts-test.js +4 -4
  13. package/dist/commands/init.js +38 -31
  14. package/dist/commands/install.js +100 -63
  15. package/dist/commands/license/activate.js +66 -64
  16. package/dist/commands/license/id.js +0 -1
  17. package/dist/commands/license/plugins/clean.js +0 -1
  18. package/dist/commands/license/plugins/list.js +0 -1
  19. package/dist/commands/license/plugins/sync.js +0 -1
  20. package/dist/commands/license/shared.js +3 -3
  21. package/dist/commands/license/status.js +0 -1
  22. package/dist/commands/plugin/disable.js +0 -1
  23. package/dist/commands/plugin/enable.js +0 -1
  24. package/dist/commands/plugin/list.js +0 -1
  25. package/dist/commands/self/update.js +12 -3
  26. package/dist/commands/skills/install.js +12 -3
  27. package/dist/commands/skills/remove.js +12 -3
  28. package/dist/commands/skills/update.js +12 -3
  29. package/dist/commands/source/dev.js +0 -1
  30. package/dist/commands/source/download.js +29 -17
  31. package/dist/lib/app-managed-resources.js +8 -2
  32. package/dist/lib/bootstrap.js +12 -3
  33. package/dist/lib/db-connection-check.js +3 -23
  34. package/dist/lib/docker-env-file.js +52 -0
  35. package/dist/lib/env-auth.js +4 -3
  36. package/dist/lib/env-config.js +1 -0
  37. package/dist/lib/env-guard.js +8 -7
  38. package/dist/lib/generated-command.js +0 -1
  39. package/dist/lib/inquirer-theme.js +17 -0
  40. package/dist/lib/inquirer.js +244 -0
  41. package/dist/lib/object-utils.js +76 -0
  42. package/dist/lib/prompt-catalog-core.js +185 -0
  43. package/dist/lib/prompt-catalog-terminal.js +375 -0
  44. package/dist/lib/prompt-catalog.js +2 -573
  45. package/dist/lib/prompt-validators.js +56 -1
  46. package/dist/lib/resource-command.js +0 -1
  47. package/dist/lib/skills-manager.js +75 -11
  48. package/dist/lib/startup-update.js +12 -8
  49. package/dist/lib/ui.js +28 -51
  50. package/dist/locale/en-US.json +8 -3
  51. package/dist/locale/zh-CN.json +8 -3
  52. package/package.json +7 -5
@@ -6,8 +6,11 @@
6
6
  * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
+ import fs from 'node:fs';
9
10
  import fsp from 'node:fs/promises';
10
11
  import path from 'node:path';
12
+ import { createGunzip } from 'node:zlib';
13
+ import * as tar from 'tar';
11
14
  import { resolveCliHomeDir } from './cli-home.js';
12
15
  import { compareVersions } from './self-manager.js';
13
16
  import { commandOutput, commandOutputViaFile, run } from './run-npm.js';
@@ -33,6 +36,15 @@ function resolveSkillsRoot(options = {}) {
33
36
  function getSkillsCacheRoot(globalRoot) {
34
37
  return path.join(globalRoot, 'cache', 'skills');
35
38
  }
39
+ function getCachedSkillsPackageDir(cacheRoot) {
40
+ return path.join(cacheRoot, 'node_modules', '@nocobase', 'skills');
41
+ }
42
+ function getCachedSkillsPackRoot(cacheRoot) {
43
+ return path.join(cacheRoot, 'pack');
44
+ }
45
+ function getCachedSkillsExtractRoot(cacheRoot) {
46
+ return path.join(cacheRoot, 'extract');
47
+ }
36
48
  export function getManagedSkillsStateFile(workspaceRoot) {
37
49
  return path.join(workspaceRoot, 'skills.json');
38
50
  }
@@ -95,7 +107,7 @@ async function readPublishedSkillsVersion(options = {}) {
95
107
  }
96
108
  }
97
109
  async function readCachedSkillsVersion(cacheRoot) {
98
- const packageJsonPath = path.join(cacheRoot, 'node_modules', '@nocobase', 'skills', 'package.json');
110
+ const packageJsonPath = path.join(getCachedSkillsPackageDir(cacheRoot), 'package.json');
99
111
  try {
100
112
  const content = await fsp.readFile(packageJsonPath, 'utf8');
101
113
  const parsed = JSON.parse(content);
@@ -106,9 +118,59 @@ async function readCachedSkillsVersion(cacheRoot) {
106
118
  return undefined;
107
119
  }
108
120
  }
121
+ async function resolvePackedSkillsTarball(packRoot) {
122
+ const entries = await fsp.readdir(packRoot, { withFileTypes: true });
123
+ const tarballs = entries
124
+ .filter((entry) => entry.isFile() && entry.name.endsWith('.tgz'))
125
+ .map((entry) => path.join(packRoot, entry.name))
126
+ .sort();
127
+ if (tarballs.length === 1) {
128
+ return tarballs[0];
129
+ }
130
+ if (tarballs.length === 0) {
131
+ throw new Error(`npm pack did not produce a local tarball for ${NOCOBASE_SKILLS_PACKAGE_NAME}.`);
132
+ }
133
+ throw new Error(`npm pack produced multiple tarballs for ${NOCOBASE_SKILLS_PACKAGE_NAME}.`);
134
+ }
135
+ async function extractPackedSkillsTarball(tarballPath, cacheRoot, targetVersion) {
136
+ const packageDir = getCachedSkillsPackageDir(cacheRoot);
137
+ const extractRoot = getCachedSkillsExtractRoot(cacheRoot);
138
+ await fsp.rm(extractRoot, { recursive: true, force: true });
139
+ await fsp.mkdir(extractRoot, { recursive: true });
140
+ try {
141
+ await new Promise((resolve, reject) => {
142
+ fs.createReadStream(tarballPath)
143
+ .pipe(createGunzip())
144
+ .pipe(tar.extract({ cwd: extractRoot, strip: 1 }))
145
+ .on('finish', () => resolve())
146
+ .on('error', reject);
147
+ });
148
+ const packageJsonPath = path.join(extractRoot, 'package.json');
149
+ const packageJsonRaw = await fsp.readFile(packageJsonPath, 'utf8');
150
+ const manifest = JSON.parse(packageJsonRaw);
151
+ const packageName = String(manifest.name ?? '').trim();
152
+ const packageVersion = String(manifest.version ?? '').trim();
153
+ if (packageName !== NOCOBASE_SKILLS_PACKAGE_NAME) {
154
+ throw new Error(`packed tarball resolved to ${packageName || '(missing package name)'} instead of ${NOCOBASE_SKILLS_PACKAGE_NAME}.`);
155
+ }
156
+ if (targetVersion && packageVersion !== targetVersion) {
157
+ throw new Error(`packed tarball resolved to version ${packageVersion || '(missing version)'} instead of ${targetVersion}.`);
158
+ }
159
+ await fsp.rm(packageDir, { recursive: true, force: true });
160
+ await fsp.mkdir(path.dirname(packageDir), { recursive: true });
161
+ await fsp.rename(extractRoot, packageDir);
162
+ return packageDir;
163
+ }
164
+ catch (error) {
165
+ await fsp.rm(extractRoot, { recursive: true, force: true });
166
+ const message = error instanceof Error ? error.message : String(error);
167
+ throw new Error(`failed to extract ${NOCOBASE_SKILLS_PACKAGE_NAME} tarball: ${message}`);
168
+ }
169
+ }
109
170
  async function prepareLocalSkillsPackage(globalRoot, options = {}, targetVersion) {
110
171
  const cacheRoot = getSkillsCacheRoot(globalRoot);
111
- const packageDir = path.join(cacheRoot, 'node_modules', '@nocobase', 'skills');
172
+ const packageDir = getCachedSkillsPackageDir(cacheRoot);
173
+ const packRoot = getCachedSkillsPackRoot(cacheRoot);
112
174
  const packageSpec = targetVersion ? `${NOCOBASE_SKILLS_PACKAGE_NAME}@${targetVersion}` : NOCOBASE_SKILLS_PACKAGE_NAME;
113
175
  const cachedVersion = await readCachedSkillsVersion(cacheRoot);
114
176
  await fsp.mkdir(cacheRoot, { recursive: true });
@@ -118,17 +180,19 @@ async function prepareLocalSkillsPackage(globalRoot, options = {}, targetVersion
118
180
  cleanup: async () => undefined,
119
181
  };
120
182
  }
121
- await fsp.rm(path.join(cacheRoot, 'node_modules'), { recursive: true, force: true });
122
- await (options.runFn ?? run)('npm', ['install', '--no-save', '--ignore-scripts', '--no-package-lock', packageSpec], {
123
- cwd: cacheRoot,
124
- stdio: options.verbose ? 'inherit' : 'ignore',
125
- errorName: 'npm install',
126
- });
183
+ await fsp.rm(packRoot, { recursive: true, force: true });
184
+ await fsp.mkdir(packRoot, { recursive: true });
127
185
  try {
128
- await fsp.access(packageDir);
186
+ await (options.runFn ?? run)('npm', ['pack', '--silent', packageSpec], {
187
+ cwd: packRoot,
188
+ stdio: options.verbose ? 'inherit' : 'ignore',
189
+ errorName: 'npm pack',
190
+ });
191
+ const tarballPath = await resolvePackedSkillsTarball(packRoot);
192
+ await extractPackedSkillsTarball(tarballPath, cacheRoot, targetVersion);
129
193
  }
130
- catch {
131
- throw new Error(`npm install did not produce a local ${NOCOBASE_SKILLS_PACKAGE_NAME} package.`);
194
+ finally {
195
+ await fsp.rm(packRoot, { recursive: true, force: true });
132
196
  }
133
197
  return {
134
198
  packageDir,
@@ -9,7 +9,7 @@
9
9
  import fs from 'node:fs/promises';
10
10
  import path from 'node:path';
11
11
  import { fileURLToPath } from 'node:url';
12
- import * as p from '@clack/prompts';
12
+ import { confirm } from "./inquirer.js";
13
13
  import { inspectSelfInstall, inspectSelfStatus, } from './self-manager.js';
14
14
  import { inspectSkillsStatus } from './skills-manager.js';
15
15
  import { resolveCliHomeDir } from './cli-home.js';
@@ -264,13 +264,17 @@ export async function maybeRunStartupUpdatePrompt(argv) {
264
264
  await markChecked();
265
265
  return { kind: 'warned' };
266
266
  }
267
- const answer = await p.confirm({
268
- message: buildPromptMessage(selfStatus, skillsStatus),
269
- active: 'Yes',
270
- inactive: 'No',
271
- initialValue: true,
272
- });
273
- if (p.isCancel(answer) || !answer) {
267
+ let answer = false;
268
+ try {
269
+ answer = await confirm({
270
+ message: buildPromptMessage(selfStatus, skillsStatus),
271
+ default: true,
272
+ });
273
+ }
274
+ catch {
275
+ answer = false;
276
+ }
277
+ if (!answer) {
274
278
  printWarning(buildDeclinedWarning(selfStatus, skillsStatus));
275
279
  await markChecked();
276
280
  return { kind: 'declined' };
package/dist/lib/ui.js CHANGED
@@ -6,12 +6,14 @@
6
6
  * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
- import readline from 'node:readline/promises';
10
9
  import { stdin as input, stdout as output } from 'node:process';
11
10
  import ora from 'ora';
12
11
  import pc from 'picocolors';
13
12
  let activeSpinner;
14
13
  let verboseMode = false;
14
+ let lastStaticTaskMessage;
15
+ let lastStaticTaskAt = 0;
16
+ const STATIC_TASK_UPDATE_THROTTLE_MS = 3_000;
15
17
  function stringWidth(value) {
16
18
  return Array.from(value).length;
17
19
  }
@@ -22,58 +24,21 @@ function pad(value, width) {
22
24
  export function isInteractiveTerminal() {
23
25
  return Boolean(input.isTTY && output.isTTY);
24
26
  }
27
+ function supportsDynamicTaskUpdates() {
28
+ return isInteractiveTerminal() && process.env.TERM !== 'dumb';
29
+ }
25
30
  export function setVerboseMode(value) {
26
31
  verboseMode = value;
27
32
  }
28
33
  export function isVerboseMode() {
29
34
  return verboseMode;
30
35
  }
31
- export async function promptText(message, options) {
32
- if (!isInteractiveTerminal()) {
33
- return options?.defaultValue ?? '';
34
- }
35
- const rl = readline.createInterface({
36
- input,
37
- output,
38
- terminal: true,
39
- });
40
- try {
41
- const suffix = options?.defaultValue ? ` (${options.defaultValue})` : '';
42
- const hint = options?.secret ? ' [input visible]' : '';
43
- const prompt = `${message}${suffix}${hint}: `;
44
- const answer = await rl.question(prompt);
45
- return answer.trim() || options?.defaultValue || '';
46
- }
47
- finally {
48
- rl.close();
49
- }
50
- }
51
- export async function confirmAction(message, options) {
52
- if (!isInteractiveTerminal()) {
53
- return Boolean(options?.defaultValue);
54
- }
55
- stopTask();
56
- const rl = readline.createInterface({
57
- input,
58
- output,
59
- terminal: true,
60
- });
61
- try {
62
- const suffix = options?.defaultValue ? pc.dim('[Y/n]') : pc.dim('[y/N]');
63
- const prompt = `${pc.yellow('?')} ${pc.bold(message)} ${suffix} `;
64
- const answer = await rl.question(prompt);
65
- const normalized = answer.trim().toLowerCase();
66
- if (!normalized) {
67
- return Boolean(options?.defaultValue);
68
- }
69
- return normalized === 'y' || normalized === 'yes';
70
- }
71
- finally {
72
- rl.close();
73
- }
74
- }
75
36
  export function printSection(title) {
76
- console.log(pc.bold(title));
37
+ console.log(title);
38
+ }
39
+ export function printStage(title) {
40
+ clearActiveSpinner();
41
+ console.log(title);
77
42
  }
78
43
  export function printInfo(message) {
79
44
  if (activeSpinner) {
@@ -134,17 +99,27 @@ export function startTask(message) {
134
99
  if (activeSpinner) {
135
100
  activeSpinner.stop();
136
101
  }
102
+ lastStaticTaskMessage = message;
103
+ lastStaticTaskAt = Date.now();
104
+ if (!supportsDynamicTaskUpdates()) {
105
+ activeSpinner = undefined;
106
+ console.log(pc.cyan(message));
107
+ return;
108
+ }
137
109
  activeSpinner = ora({
138
110
  text: pc.cyan(message),
139
- isSilent: !isInteractiveTerminal(),
111
+ isSilent: false,
140
112
  }).start();
141
- if (!isInteractiveTerminal()) {
142
- console.log(pc.cyan(message));
143
- }
144
113
  }
145
114
  export function updateTask(message) {
146
115
  if (!activeSpinner) {
147
- startTask(message);
116
+ const now = Date.now();
117
+ if (message !== lastStaticTaskMessage
118
+ && now - lastStaticTaskAt >= STATIC_TASK_UPDATE_THROTTLE_MS) {
119
+ console.log(pc.cyan(message));
120
+ lastStaticTaskMessage = message;
121
+ lastStaticTaskAt = now;
122
+ }
148
123
  return;
149
124
  }
150
125
  activeSpinner.text = pc.cyan(message);
@@ -166,6 +141,8 @@ export function failTask(message) {
166
141
  console.error(pc.red(message));
167
142
  }
168
143
  export function stopTask() {
144
+ lastStaticTaskMessage = undefined;
145
+ lastStaticTaskAt = 0;
169
146
  clearActiveSpinner();
170
147
  }
171
148
  export function renderTable(headers, rows) {
@@ -51,7 +51,12 @@
51
51
  "validators": {
52
52
  "apiBaseUrl": {
53
53
  "invalid": "Enter a valid URL, for example {{example}}.",
54
- "invalidProtocol": "URL must start with http:// or https://, for example {{example}}."
54
+ "invalidProtocol": "URL must start with http:// or https://, for example {{example}}.",
55
+ "healthCheckPathNotAllowed": "Do not include /__health_check in the API base URL. Enter the application API base URL, for example http://localhost:13000/api.",
56
+ "maintaining": "The API base URL is reachable, but the app is still starting or in maintenance mode. Please try again later.",
57
+ "healthCheckFailed": "The API base URL did not pass the health check (HTTP {{status}}). Make sure it points to a running NocoBase API endpoint.",
58
+ "timeout": "Timed out waiting for the API base URL health check after about {{seconds}} seconds.",
59
+ "unreachable": "Unable to connect to the API base URL. Check the address and make sure the server is reachable. Details: {{details}}"
55
60
  },
56
61
  "envKey": {
57
62
  "invalid": "Use letters and numbers only."
@@ -85,7 +90,7 @@
85
90
  },
86
91
  "apiBaseUrl": {
87
92
  "message": "What is the API base URL?",
88
- "placeholder": "http://localhost:13000/api"
93
+ "placeholder": "https://demo.example.com/api or https://demo.example.com/api/__app/<subapp>"
89
94
  },
90
95
  "authType": {
91
96
  "message": "How would you like to sign in?",
@@ -302,7 +307,7 @@
302
307
  },
303
308
  "apiBaseUrl": {
304
309
  "message": "API base URL",
305
- "placeholder": "http://localhost:13000/api"
310
+ "placeholder": "https://demo.example.com/api or https://demo.example.com/api/__app/<subapp>"
306
311
  }
307
312
  },
308
313
  "webUi": {
@@ -51,7 +51,12 @@
51
51
  "validators": {
52
52
  "apiBaseUrl": {
53
53
  "invalid": "请输入有效的 URL,例如 {{example}}。",
54
- "invalidProtocol": "URL 必须以 http:// 或 https:// 开头,例如 {{example}}。"
54
+ "invalidProtocol": "URL 必须以 http:// 或 https:// 开头,例如 {{example}}。",
55
+ "healthCheckPathNotAllowed": "API 基础地址中不要包含 /__health_check。请输入应用的 API 基础地址,例如 http://localhost:13000/api。",
56
+ "maintaining": "该 API 地址可以访问,但应用仍在启动或维护过程中,请稍后再试。",
57
+ "healthCheckFailed": "该 API 地址未通过健康检查(HTTP {{status}}),请确认它指向的是一个正在运行的 NocoBase API 地址。",
58
+ "timeout": "等待 API 地址健康检查超时,约 {{seconds}} 秒内未收到响应。",
59
+ "unreachable": "无法连接到该 API 地址,请检查地址是否正确,并确认服务可访问。详情:{{details}}"
55
60
  },
56
61
  "envKey": {
57
62
  "invalid": "仅支持字母和数字。"
@@ -85,7 +90,7 @@
85
90
  },
86
91
  "apiBaseUrl": {
87
92
  "message": "API 基础地址是什么?",
88
- "placeholder": "http://localhost:13000/api"
93
+ "placeholder": "https://demo.example.com/api 或 https://demo.example.com/api/__app/<subapp>"
89
94
  },
90
95
  "authType": {
91
96
  "message": "你想使用哪种登录方式?",
@@ -302,7 +307,7 @@
302
307
  },
303
308
  "apiBaseUrl": {
304
309
  "message": "API 基础地址",
305
- "placeholder": "http://localhost:13000/api"
310
+ "placeholder": "https://demo.example.com/api 或 https://demo.example.com/api/__app/<subapp>"
306
311
  }
307
312
  },
308
313
  "webUi": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/cli",
3
- "version": "2.1.0-beta.33",
3
+ "version": "2.1.0-beta.34",
4
4
  "description": "NocoBase Command Line Tool",
5
5
  "type": "module",
6
6
  "main": "dist/generated/command-registry.js",
@@ -80,12 +80,14 @@
80
80
  }
81
81
  },
82
82
  "dependencies": {
83
- "@clack/prompts": "^0.9.1",
83
+ "@inquirer/ansi": "^2.0.5",
84
+ "@inquirer/confirm": "^6.0.13",
85
+ "@inquirer/core": "^11.1.10",
86
+ "@inquirer/select": "^5.1.5",
87
+ "@inquirer/type": "^4.0.5",
84
88
  "@nocobase/license-kit": "^0.3.8",
85
89
  "@oclif/core": "^4.10.4",
86
90
  "cross-spawn": "^7.0.6",
87
- "lodash": "^4.17.21",
88
- "mariadb": "^2.5.6",
89
91
  "mysql2": "^3.14.0",
90
92
  "openapi-types": "^12.1.3",
91
93
  "ora": "^8.2.0",
@@ -103,5 +105,5 @@
103
105
  "type": "git",
104
106
  "url": "git+https://github.com/nocobase/nocobase.git"
105
107
  },
106
- "gitHead": "4815c394e80a264fa8ed619246280923c47aeb72"
108
+ "gitHead": "ca804833299c547f8d49f8d58f73273a4bfcd03c"
107
109
  }