@nocobase/cli 2.1.0-alpha.24 → 2.1.0-alpha.26

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 (74) hide show
  1. package/README.md +41 -49
  2. package/README.zh-CN.md +38 -45
  3. package/bin/run.js +15 -0
  4. package/dist/commands/app/down.js +260 -0
  5. package/dist/commands/app/logs.js +98 -0
  6. package/dist/commands/app/restart.js +75 -0
  7. package/dist/commands/app/start.js +252 -0
  8. package/dist/commands/app/stop.js +98 -0
  9. package/dist/commands/app/upgrade.js +595 -0
  10. package/dist/commands/build.js +3 -48
  11. package/dist/commands/db/shared.js +19 -5
  12. package/dist/commands/dev.js +3 -140
  13. package/dist/commands/down.js +3 -184
  14. package/dist/commands/download.js +4 -856
  15. package/dist/commands/env/add.js +33 -48
  16. package/dist/commands/env/auth.js +6 -13
  17. package/dist/commands/env/info.js +152 -0
  18. package/dist/commands/env/list.js +27 -18
  19. package/dist/commands/env/remove.js +4 -10
  20. package/dist/commands/env/shared.js +158 -0
  21. package/dist/commands/env/update.js +7 -13
  22. package/dist/commands/env/use.js +5 -13
  23. package/dist/commands/{prompts-stages.js → examples/prompts-stages.js} +3 -3
  24. package/dist/commands/{prompts-test.js → examples/prompts-test.js} +3 -3
  25. package/dist/commands/init.js +270 -64
  26. package/dist/commands/install.js +352 -86
  27. package/dist/commands/logs.js +3 -81
  28. package/dist/commands/plugin/disable.js +64 -0
  29. package/dist/commands/plugin/enable.js +64 -0
  30. package/dist/commands/plugin/list.js +62 -0
  31. package/dist/commands/pm/disable.js +3 -54
  32. package/dist/commands/pm/enable.js +3 -54
  33. package/dist/commands/pm/list.js +3 -45
  34. package/dist/commands/restart.js +12 -0
  35. package/dist/commands/scaffold/migration.js +1 -1
  36. package/dist/commands/scaffold/plugin.js +1 -1
  37. package/dist/commands/self/check.js +1 -1
  38. package/dist/commands/self/update.js +13 -3
  39. package/dist/commands/skills/check.js +11 -5
  40. package/dist/commands/skills/index.js +1 -1
  41. package/dist/commands/skills/install.js +20 -7
  42. package/dist/commands/skills/remove.js +71 -0
  43. package/dist/commands/skills/update.js +27 -7
  44. package/dist/commands/source/build.js +58 -0
  45. package/dist/commands/source/dev.js +157 -0
  46. package/dist/commands/source/download.js +866 -0
  47. package/dist/commands/source/test.js +467 -0
  48. package/dist/commands/start.js +3 -202
  49. package/dist/commands/stop.js +3 -81
  50. package/dist/commands/test.js +3 -457
  51. package/dist/commands/upgrade.js +3 -574
  52. package/dist/help/runtime-help.js +3 -0
  53. package/dist/lib/api-client.js +22 -7
  54. package/dist/lib/app-health.js +126 -0
  55. package/dist/lib/app-managed-resources.js +264 -0
  56. package/dist/lib/app-runtime.js +16 -5
  57. package/dist/lib/auth-store.js +162 -43
  58. package/dist/lib/bootstrap.js +13 -12
  59. package/dist/lib/cli-home.js +38 -6
  60. package/dist/lib/cli-locale.js +15 -1
  61. package/dist/lib/env-auth.js +3 -3
  62. package/dist/lib/env-config.js +80 -0
  63. package/dist/lib/generated-command.js +10 -2
  64. package/dist/lib/http-request.js +49 -0
  65. package/dist/lib/prompt-web-ui.js +13 -6
  66. package/dist/lib/resource-command.js +10 -2
  67. package/dist/lib/runtime-generator.js +1 -1
  68. package/dist/lib/self-manager.js +1 -1
  69. package/dist/lib/skills-manager.js +173 -79
  70. package/dist/lib/startup-update.js +203 -0
  71. package/dist/locale/en-US.json +4 -1
  72. package/dist/locale/zh-CN.json +4 -1
  73. package/package.json +27 -4
  74. package/dist/commands/ps.js +0 -116
@@ -0,0 +1,203 @@
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 fs from 'node:fs/promises';
10
+ import path from 'node:path';
11
+ import * as p from '@clack/prompts';
12
+ import { inspectSelfStatus, } from './self-manager.js';
13
+ import { inspectSkillsStatus } from './skills-manager.js';
14
+ import { resolveCliHomeDir } from './cli-home.js';
15
+ import { isInteractiveTerminal, printWarning } from './ui.js';
16
+ import { run } from './run-npm.js';
17
+ const STARTUP_UPDATE_STATE_FILE = 'startup-update.json';
18
+ const NB_SKIP_STARTUP_UPDATE_ENV = 'NB_SKIP_STARTUP_UPDATE';
19
+ function getStateFile() {
20
+ return path.join(resolveCliHomeDir('global'), STARTUP_UPDATE_STATE_FILE);
21
+ }
22
+ function todayStamp(now = new Date()) {
23
+ return now.toISOString().slice(0, 10);
24
+ }
25
+ function shouldSkipByArgv(argv) {
26
+ const tokens = argv.filter((token) => token && !token.startsWith('-'));
27
+ if (tokens.length === 0) {
28
+ return false;
29
+ }
30
+ if (tokens[0] === 'self' || tokens[0] === 'skills') {
31
+ return true;
32
+ }
33
+ return false;
34
+ }
35
+ async function readState() {
36
+ try {
37
+ const raw = await fs.readFile(getStateFile(), 'utf8');
38
+ return JSON.parse(raw);
39
+ }
40
+ catch {
41
+ return {};
42
+ }
43
+ }
44
+ async function writeState(state) {
45
+ const filePath = getStateFile();
46
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
47
+ await fs.writeFile(filePath, JSON.stringify(state, null, 2));
48
+ }
49
+ async function markChecked(now = new Date()) {
50
+ await writeState({ lastCheckedDate: todayStamp(now) });
51
+ }
52
+ export async function shouldRunStartupUpdateCheck(argv, now = new Date()) {
53
+ if (process.env[NB_SKIP_STARTUP_UPDATE_ENV] === '1') {
54
+ return false;
55
+ }
56
+ if (shouldSkipByArgv(argv)) {
57
+ return false;
58
+ }
59
+ const state = await readState();
60
+ return state.lastCheckedDate !== todayStamp(now);
61
+ }
62
+ export function shouldEnableStartupUpdateForInstallMethod(installMethod) {
63
+ return installMethod === 'npm-global';
64
+ }
65
+ function hasPendingUpdates(selfStatus, skillsStatus) {
66
+ return Boolean(selfStatus.updateAvailable || skillsStatus.updateAvailable === true);
67
+ }
68
+ function describeCliUpdate(selfStatus) {
69
+ return selfStatus.latestVersion
70
+ ? `NocoBase CLI: ${selfStatus.currentVersion} -> ${selfStatus.latestVersion}`
71
+ : `NocoBase CLI: update available from ${selfStatus.currentVersion}`;
72
+ }
73
+ function describeSkillsUpdate() {
74
+ return 'NocoBase AI skills: update available';
75
+ }
76
+ function describeSkillsUpdateWithVersion(skillsStatus) {
77
+ if (skillsStatus.installedVersion && skillsStatus.latestVersion) {
78
+ return `NocoBase AI skills: ${skillsStatus.installedVersion} -> ${skillsStatus.latestVersion}`;
79
+ }
80
+ if (skillsStatus.latestVersion) {
81
+ return `NocoBase AI skills: latest ${skillsStatus.latestVersion} available`;
82
+ }
83
+ return describeSkillsUpdate();
84
+ }
85
+ function buildPromptMessage(selfStatus, skillsStatus) {
86
+ const lines = [];
87
+ const hasCliUpdate = selfStatus.updateAvailable;
88
+ const hasSkillsUpdate = skillsStatus.updateAvailable === true;
89
+ if (hasCliUpdate && hasSkillsUpdate) {
90
+ lines.push('Updates are available for your NocoBase CLI and AI skills.');
91
+ }
92
+ else if (hasCliUpdate) {
93
+ lines.push('An update is available for your NocoBase CLI.');
94
+ }
95
+ else if (hasSkillsUpdate) {
96
+ lines.push('An update is available for your NocoBase AI skills.');
97
+ }
98
+ else {
99
+ lines.push('A NocoBase CLI or skills update is available.');
100
+ }
101
+ if (hasCliUpdate) {
102
+ lines.push(`- ${describeCliUpdate(selfStatus)}`);
103
+ }
104
+ if (hasSkillsUpdate) {
105
+ lines.push(`- ${describeSkillsUpdateWithVersion(skillsStatus)}`);
106
+ }
107
+ lines.push('Update now?');
108
+ return lines.join('\n');
109
+ }
110
+ function buildUpdateCommands(selfStatus, skillsStatus) {
111
+ const commands = [];
112
+ if (selfStatus.updateAvailable && selfStatus.updatable) {
113
+ commands.push('nb self update --yes');
114
+ }
115
+ if (skillsStatus.updateAvailable === true) {
116
+ commands.push('nb skills update --yes');
117
+ }
118
+ return commands;
119
+ }
120
+ function buildNonInteractiveWarning(selfStatus, skillsStatus) {
121
+ const commands = buildUpdateCommands(selfStatus, skillsStatus);
122
+ const details = [];
123
+ if (selfStatus.updateAvailable) {
124
+ details.push(describeCliUpdate(selfStatus));
125
+ }
126
+ if (skillsStatus.updateAvailable === true) {
127
+ details.push(describeSkillsUpdateWithVersion(skillsStatus));
128
+ }
129
+ return [
130
+ `Updates available${details.length ? `: ${details.join(', ')}` : '.'}`,
131
+ 'Non-interactive session, skipped auto-update.',
132
+ commands.length
133
+ ? `Run: ${commands.join(' && ')}`
134
+ : 'Check with: `nb self check` and `nb skills check`.',
135
+ 'You may run into compatibility issues until you update.',
136
+ ].join(' ');
137
+ }
138
+ function buildDeclinedWarning(selfStatus, skillsStatus) {
139
+ const commands = buildUpdateCommands(selfStatus, skillsStatus);
140
+ const details = [];
141
+ if (selfStatus.updateAvailable) {
142
+ details.push(describeCliUpdate(selfStatus));
143
+ }
144
+ if (skillsStatus.updateAvailable === true) {
145
+ details.push(describeSkillsUpdateWithVersion(skillsStatus));
146
+ }
147
+ return [
148
+ `Skipped updates${details.length ? `: ${details.join(', ')}` : '.'}`,
149
+ commands.length
150
+ ? `Run: ${commands.join(' && ')}`
151
+ : 'Check with: `nb self check` and `nb skills check`.',
152
+ 'You may run into compatibility issues until you update.',
153
+ ].join(' ');
154
+ }
155
+ async function runStartupUpdates() {
156
+ await run('nb', ['self', 'update', '--yes'], {
157
+ stdio: 'inherit',
158
+ env: {
159
+ [NB_SKIP_STARTUP_UPDATE_ENV]: '1',
160
+ },
161
+ errorName: 'nb self update',
162
+ });
163
+ await run('nb', ['skills', 'update', '--yes'], {
164
+ stdio: 'inherit',
165
+ env: {
166
+ [NB_SKIP_STARTUP_UPDATE_ENV]: '1',
167
+ },
168
+ errorName: 'nb skills update',
169
+ });
170
+ }
171
+ export async function maybeRunStartupUpdatePrompt(argv) {
172
+ if (!(await shouldRunStartupUpdateCheck(argv))) {
173
+ return { kind: 'skipped' };
174
+ }
175
+ const selfStatus = await inspectSelfStatus();
176
+ if (!shouldEnableStartupUpdateForInstallMethod(selfStatus.installMethod)) {
177
+ return { kind: 'skipped' };
178
+ }
179
+ const skillsStatus = await inspectSkillsStatus();
180
+ if (!hasPendingUpdates(selfStatus, skillsStatus)) {
181
+ await markChecked();
182
+ return { kind: 'no-update' };
183
+ }
184
+ if (!isInteractiveTerminal()) {
185
+ printWarning(buildNonInteractiveWarning(selfStatus, skillsStatus));
186
+ await markChecked();
187
+ return { kind: 'warned' };
188
+ }
189
+ const answer = await p.confirm({
190
+ message: buildPromptMessage(selfStatus, skillsStatus),
191
+ active: 'Yes',
192
+ inactive: 'No',
193
+ initialValue: true,
194
+ });
195
+ if (p.isCancel(answer) || !answer) {
196
+ printWarning(buildDeclinedWarning(selfStatus, skillsStatus));
197
+ await markChecked();
198
+ return { kind: 'declined' };
199
+ }
200
+ await runStartupUpdates();
201
+ await markChecked();
202
+ return { kind: 'updated' };
203
+ }
@@ -73,6 +73,8 @@
73
73
  },
74
74
  "scope": {
75
75
  "message": "Where should this connection be saved?",
76
+ "autoLabel": "Auto",
77
+ "autoHint": "project if this repo already has .nocobase, otherwise global",
76
78
  "projectLabel": "Project",
77
79
  "projectHint": ".nocobase in this repo",
78
80
  "globalLabel": "Global",
@@ -268,7 +270,7 @@
268
270
  },
269
271
  "init": {
270
272
  "validation": {
271
- "envExists": "Env \"{{envName}}\" already exists in this workspace. Choose another env name."
273
+ "envExists": "Env \"{{envName}}\" already exists. Choose another env name."
272
274
  },
273
275
  "messages": {
274
276
  "title": "Set Up Your NocoBase AI Workspace",
@@ -276,6 +278,7 @@
276
278
  "appNameEnvHelp": "Use `nb init --yes --env <envName>` to continue.",
277
279
  "resumeEnvRequired": "Env name is required when resuming setup.",
278
280
  "resumeEnvHelp": "Use `nb init --resume --env <envName>` to continue.",
281
+ "resumeAfterInstallFailure": "Resume this setup with:\n {{command}}",
279
282
  "uiOpening": "A local setup form will open in your browser. That form needs a person to fill it in. If you are using an AI agent, do not stop this process while the CLI waits for the submission.",
280
283
  "uiReady": "Local setup form is ready.",
281
284
  "uiReadyHelp": "If your browser does not open automatically, copy the URL below into your browser to continue. Keep this terminal session running while the CLI waits for the submission.",
@@ -73,6 +73,8 @@
73
73
  },
74
74
  "scope": {
75
75
  "message": "这个连接要保存到哪里?",
76
+ "autoLabel": "自动",
77
+ "autoHint": "当前仓库已有 .nocobase 时保存到项目内,否则保存到全局",
76
78
  "projectLabel": "项目内",
77
79
  "projectHint": "保存在当前仓库的 .nocobase 中",
78
80
  "globalLabel": "全局",
@@ -268,7 +270,7 @@
268
270
  },
269
271
  "init": {
270
272
  "validation": {
271
- "envExists": "Env \"{{envName}}\" 已存在于当前 workspace 中,请换一个 env name。"
273
+ "envExists": "Env \"{{envName}}\" 已存在,请换一个 env name。"
272
274
  },
273
275
  "messages": {
274
276
  "title": "初始化你的 NocoBase AI 工作区",
@@ -276,6 +278,7 @@
276
278
  "appNameEnvHelp": "请使用 `nb init --yes --env <envName>` 继续。",
277
279
  "resumeEnvRequired": "恢复安装时必须提供 Env name。",
278
280
  "resumeEnvHelp": "请使用 `nb init --resume --env <envName>` 继续。",
281
+ "resumeAfterInstallFailure": "继续这次初始化请执行:\n {{command}}",
279
282
  "uiOpening": "本地 setup 表单即将在浏览器中打开。这个表单需要等待人工填写。如果你正在使用 AI agent,请不要停止当前进程,CLI 会继续等待表单提交结果。",
280
283
  "uiReady": "本地 setup 表单已就绪。",
281
284
  "uiReadyHelp": "如果没有自动打开浏览器,请复制下面的地址到浏览器中继续。CLI 会继续等待表单提交,请保持当前终端会话继续运行。",
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@nocobase/cli",
3
- "version": "2.1.0-alpha.24",
3
+ "version": "2.1.0-alpha.26",
4
4
  "description": "NocoBase Command Line Tool",
5
5
  "type": "module",
6
6
  "main": "dist/generated/command-registry.js",
7
7
  "scripts": {
8
8
  "clean": "node ./scripts/clean.mjs",
9
9
  "build": "node ./scripts/build.mjs",
10
- "test": "TEST_ENV=server-side yarn --cwd ../../.. vitest run packages/core/cli"
10
+ "test": "NB_CLI_ROOT=. TEST_ENV=server-side yarn --cwd ../../.. vitest run packages/core/cli"
11
11
  },
12
12
  "keywords": [],
13
13
  "author": "",
@@ -36,8 +36,27 @@
36
36
  "dirname": "nb",
37
37
  "topicSeparator": " ",
38
38
  "topics": {
39
+ "app": {
40
+ "description": "Manage NocoBase app runtimes: start, stop, restart, logs, and upgrades."
41
+ },
42
+ "source": {
43
+ "description": "Work with the local NocoBase source project: download, develop, build, and test."
44
+ },
45
+ "scaffold": {
46
+ "description": "Generate NocoBase plugin development scaffolds."
47
+ },
48
+ "plugin": {
49
+ "description": "Manage plugins in the selected NocoBase env."
50
+ },
51
+ "pm": {
52
+ "description": "Manage plugins in the selected NocoBase env.",
53
+ "hidden": true
54
+ },
55
+ "db": {
56
+ "description": "Manage the built-in database for the selected env."
57
+ },
39
58
  "env": {
40
- "description": "Manage NocoBase project environments and update command runtimes."
59
+ "description": "Manage NocoBase project environments, status, details, and command runtimes."
41
60
  },
42
61
  "self": {
43
62
  "description": "Inspect or update the NocoBase CLI itself."
@@ -45,6 +64,10 @@
45
64
  "skills": {
46
65
  "description": "Inspect or synchronize NocoBase AI coding skills for the current workspace."
47
66
  },
67
+ "examples": {
68
+ "description": "Internal example commands for prompt and UI experiments.",
69
+ "hidden": true
70
+ },
48
71
  "api": {
49
72
  "description": "Work with NocoBase API."
50
73
  }
@@ -68,5 +91,5 @@
68
91
  "type": "git",
69
92
  "url": "git+https://github.com/nocobase/nocobase.git"
70
93
  },
71
- "gitHead": "effaf56a9b9ea2d40200c4c10854a84d9622f071"
94
+ "gitHead": "e6e6518030175b58080218bb4357642398bcb54a"
72
95
  }
@@ -1,116 +0,0 @@
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, Flags } from '@oclif/core';
10
- import { buildDockerDbContainerName, dockerContainerExists, dockerContainerIsRunning, formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, } from '../lib/app-runtime.js';
11
- import { listEnvs } from '../lib/auth-store.js';
12
- import { renderTable } from '../lib/ui.js';
13
- function appUrl(runtime) {
14
- const port = String(runtime.env.config.appPort ?? '').trim();
15
- if (port) {
16
- return `http://127.0.0.1:${port}`;
17
- }
18
- const baseUrl = String(runtime.env.config.baseUrl ?? '').trim();
19
- return baseUrl.replace(/\/api\/?$/, '');
20
- }
21
- async function isLocalAppHealthy(runtime) {
22
- const port = String(runtime.env.config.appPort ?? '').trim();
23
- if (!port) {
24
- return false;
25
- }
26
- const controller = new AbortController();
27
- const timeout = setTimeout(() => controller.abort(), 1500);
28
- try {
29
- const response = await fetch(`http://127.0.0.1:${port}/api/__health_check`, {
30
- signal: controller.signal,
31
- });
32
- const text = await response.text();
33
- return response.ok && text.trim().toLowerCase() === 'ok';
34
- }
35
- catch (_error) {
36
- return false;
37
- }
38
- finally {
39
- clearTimeout(timeout);
40
- }
41
- }
42
- async function dockerStatus(containerName) {
43
- if (!(await dockerContainerExists(containerName))) {
44
- return 'missing';
45
- }
46
- return await dockerContainerIsRunning(containerName) ? 'running' : 'stopped';
47
- }
48
- async function dbStatus(runtime) {
49
- if (!runtime.env.config.builtinDb) {
50
- return runtime.kind === 'remote' ? 'external' : '-';
51
- }
52
- if (runtime.kind === 'remote') {
53
- return 'external';
54
- }
55
- const dbDialect = String(runtime.env.config.dbDialect ?? 'postgres').trim() || 'postgres';
56
- const containerName = buildDockerDbContainerName(runtime.envName, dbDialect, runtime.workspaceName);
57
- return await dockerStatus(containerName);
58
- }
59
- async function appStatus(runtime) {
60
- if (runtime.kind === 'remote') {
61
- return 'remote';
62
- }
63
- if (runtime.kind === 'docker') {
64
- return await dockerStatus(runtime.containerName);
65
- }
66
- return await isLocalAppHealthy(runtime) ? 'running' : 'stopped';
67
- }
68
- function sourceLabel(runtime) {
69
- if (runtime.kind === 'remote') {
70
- return 'remote';
71
- }
72
- return runtime.source;
73
- }
74
- export default class Ps extends Command {
75
- static description = 'Show NocoBase runtime status for configured envs without starting or stopping anything.';
76
- static examples = [
77
- '<%= config.bin %> <%= command.id %>',
78
- '<%= config.bin %> <%= command.id %> --env app1',
79
- ];
80
- static flags = {
81
- env: Flags.string({
82
- char: 'e',
83
- description: 'CLI env name to inspect. Omit to show all configured envs',
84
- }),
85
- };
86
- async run() {
87
- const { flags } = await this.parse(Ps);
88
- const requestedEnv = flags.env?.trim() || undefined;
89
- const envNames = requestedEnv
90
- ? [requestedEnv]
91
- : Object.keys((await listEnvs()).envs).sort();
92
- if (!envNames.length) {
93
- this.log('No NocoBase env is configured yet. Run `nb init` to create one first.');
94
- return;
95
- }
96
- const rows = [];
97
- for (const envName of envNames) {
98
- const runtime = await resolveManagedAppRuntime(envName);
99
- if (!runtime) {
100
- if (requestedEnv) {
101
- this.error(formatMissingManagedAppEnvMessage(envName));
102
- }
103
- rows.push([envName, '-', 'missing', '-', '']);
104
- continue;
105
- }
106
- rows.push([
107
- runtime.envName,
108
- sourceLabel(runtime),
109
- await appStatus(runtime),
110
- await dbStatus(runtime),
111
- appUrl(runtime),
112
- ]);
113
- }
114
- this.log(renderTable(['Env', 'Source', 'App', 'Database', 'URL'], rows));
115
- }
116
- }