@nocobase/cli 2.1.0-beta.44.test.4 → 2.1.0-beta.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/commands/source/download.js +1 -1
  2. package/dist/lib/api-client.js +335 -0
  3. package/dist/lib/api-command-compat.js +641 -0
  4. package/dist/lib/app-health.js +139 -0
  5. package/dist/lib/app-managed-resources.js +332 -0
  6. package/dist/lib/app-public-path.js +80 -0
  7. package/dist/lib/app-runtime.js +189 -0
  8. package/dist/lib/auth-store.js +520 -0
  9. package/dist/lib/backup.js +171 -0
  10. package/dist/lib/bootstrap.js +409 -0
  11. package/dist/lib/build-config.js +18 -0
  12. package/dist/lib/builtin-db.js +86 -0
  13. package/dist/lib/cli-config.js +497 -0
  14. package/dist/lib/cli-entry-error.js +52 -0
  15. package/dist/lib/cli-home.js +47 -0
  16. package/dist/lib/cli-locale.js +141 -0
  17. package/dist/lib/command-discovery.js +39 -0
  18. package/dist/lib/command-log.js +284 -0
  19. package/dist/lib/db-connection-check.js +219 -0
  20. package/dist/lib/docker-env-file.js +60 -0
  21. package/dist/lib/docker-image.js +37 -0
  22. package/dist/lib/docker-log-stream.js +45 -0
  23. package/dist/lib/env-auth.js +963 -0
  24. package/dist/lib/env-command-config.js +45 -0
  25. package/dist/lib/env-config.js +100 -0
  26. package/dist/lib/env-guard.js +61 -0
  27. package/dist/lib/env-paths.js +101 -0
  28. package/dist/lib/env-proxy.js +1295 -0
  29. package/dist/lib/generated-command.js +203 -0
  30. package/dist/lib/http-request.js +49 -0
  31. package/dist/lib/inquirer-theme.js +17 -0
  32. package/dist/lib/inquirer.js +243 -0
  33. package/dist/lib/managed-env-file.js +98 -0
  34. package/dist/lib/naming.js +70 -0
  35. package/dist/lib/object-utils.js +76 -0
  36. package/dist/lib/openapi.js +62 -0
  37. package/dist/lib/plugin-import.js +279 -0
  38. package/dist/lib/plugin-storage.js +64 -0
  39. package/dist/lib/post-processors.js +23 -0
  40. package/dist/lib/prompt-catalog-core.js +186 -0
  41. package/dist/lib/prompt-catalog-terminal.js +375 -0
  42. package/dist/lib/prompt-catalog.js +10 -0
  43. package/dist/lib/prompt-validators.js +272 -0
  44. package/dist/lib/prompt-web-ui.js +2234 -0
  45. package/dist/lib/resource-command.js +357 -0
  46. package/dist/lib/resource-request.js +104 -0
  47. package/dist/lib/run-npm.js +429 -0
  48. package/dist/lib/runtime-env-vars.js +32 -0
  49. package/dist/lib/runtime-generator.js +498 -0
  50. package/dist/lib/runtime-store.js +56 -0
  51. package/dist/lib/self-manager.js +301 -0
  52. package/dist/lib/session-id.js +17 -0
  53. package/dist/lib/session-integration.js +703 -0
  54. package/dist/lib/session-store.js +118 -0
  55. package/dist/lib/skills-manager.js +438 -0
  56. package/dist/lib/source-publish.js +326 -0
  57. package/dist/lib/source-registry.js +188 -0
  58. package/dist/lib/startup-update.js +309 -0
  59. package/dist/lib/ui.js +159 -0
  60. package/package.json +3 -2
@@ -0,0 +1,309 @@
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 { fileURLToPath } from 'node:url';
12
+ import { confirm } from "./inquirer.js";
13
+ import { DEFAULT_UPDATE_POLICY, getExplicitCliConfigValue, loadCliConfig, } from './cli-config.js';
14
+ import { inspectSelfInstall, inspectSelfStatus, } from './self-manager.js';
15
+ import { inspectSkillsStatus } from './skills-manager.js';
16
+ import { resolveCliHomeDir } from './cli-home.js';
17
+ import { isInteractiveTerminal, printWarning } from './ui.js';
18
+ import { run } from './run-npm.js';
19
+ const STARTUP_UPDATE_STATE_FILE = 'startup-update.json';
20
+ const NB_SKIP_STARTUP_UPDATE_ENV = 'NB_SKIP_STARTUP_UPDATE';
21
+ function getStateFile() {
22
+ return path.join(resolveCliHomeDir('global'), STARTUP_UPDATE_STATE_FILE);
23
+ }
24
+ function getCurrentInstallBinPath() {
25
+ return path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..', 'bin', 'run.js');
26
+ }
27
+ function getCurrentInstallEntry(state) {
28
+ return state.entries?.[getCurrentInstallBinPath()];
29
+ }
30
+ function todayStamp(now = new Date()) {
31
+ const timeZone = String(process.env.TZ ?? '').trim();
32
+ if (timeZone) {
33
+ try {
34
+ const parts = new Intl.DateTimeFormat('en-CA', {
35
+ timeZone,
36
+ year: 'numeric',
37
+ month: '2-digit',
38
+ day: '2-digit',
39
+ }).formatToParts(now);
40
+ const year = parts.find((part) => part.type === 'year')?.value;
41
+ const month = parts.find((part) => part.type === 'month')?.value;
42
+ const day = parts.find((part) => part.type === 'day')?.value;
43
+ if (year && month && day) {
44
+ return `${year}-${month}-${day}`;
45
+ }
46
+ }
47
+ catch {
48
+ // Fall back to the host local timezone.
49
+ }
50
+ }
51
+ const year = now.getFullYear();
52
+ const month = String(now.getMonth() + 1).padStart(2, '0');
53
+ const day = String(now.getDate()).padStart(2, '0');
54
+ return `${year}-${month}-${day}`;
55
+ }
56
+ function shouldSkipByArgv(argv) {
57
+ const tokens = argv.filter((token) => token && !token.startsWith('-'));
58
+ if (tokens.length === 0) {
59
+ return false;
60
+ }
61
+ if (tokens[0] === 'self' || tokens[0] === 'skills') {
62
+ return true;
63
+ }
64
+ return false;
65
+ }
66
+ async function readState() {
67
+ try {
68
+ const raw = await fs.readFile(getStateFile(), 'utf8');
69
+ return JSON.parse(raw);
70
+ }
71
+ catch {
72
+ return {};
73
+ }
74
+ }
75
+ async function writeState(state) {
76
+ const filePath = getStateFile();
77
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
78
+ await fs.writeFile(filePath, JSON.stringify(state, null, 2));
79
+ }
80
+ function readCurrentInstallLastCheckedDate(state) {
81
+ return getCurrentInstallEntry(state)?.lastCheckedDate ?? state.lastCheckedDate;
82
+ }
83
+ async function writeCurrentInstallEntry(updater) {
84
+ const state = await readState();
85
+ const installBinPath = getCurrentInstallBinPath();
86
+ const nextEntry = updater(getCurrentInstallEntry(state), state);
87
+ const entries = {
88
+ ...(state.entries ?? {}),
89
+ };
90
+ if (nextEntry?.policy || nextEntry?.lastCheckedDate) {
91
+ entries[installBinPath] = nextEntry;
92
+ }
93
+ else {
94
+ delete entries[installBinPath];
95
+ }
96
+ await writeState({
97
+ ...state,
98
+ entries: Object.keys(entries).length ? entries : undefined,
99
+ });
100
+ }
101
+ async function markChecked(now = new Date()) {
102
+ await writeCurrentInstallEntry((current) => {
103
+ return {
104
+ ...current,
105
+ lastCheckedDate: todayStamp(now),
106
+ };
107
+ });
108
+ }
109
+ function resolveLegacyStartupUpdatePolicy(state) {
110
+ const policy = getCurrentInstallEntry(state)?.policy;
111
+ if (policy === 'disabled') {
112
+ return 'off';
113
+ }
114
+ if (policy === 'daily') {
115
+ return 'prompt';
116
+ }
117
+ return undefined;
118
+ }
119
+ async function resolveStartupUpdatePolicy(state) {
120
+ const config = await loadCliConfig({ scope: 'global' });
121
+ const explicit = getExplicitCliConfigValue(config, 'update.policy');
122
+ return explicit ?? resolveLegacyStartupUpdatePolicy(state) ?? DEFAULT_UPDATE_POLICY;
123
+ }
124
+ export async function clearLegacyStartupUpdatePolicyForCurrentInstall() {
125
+ const state = await readState();
126
+ const current = getCurrentInstallEntry(state);
127
+ if (!current?.policy) {
128
+ return false;
129
+ }
130
+ await writeCurrentInstallEntry(() => ({
131
+ lastCheckedDate: current.lastCheckedDate,
132
+ }));
133
+ return true;
134
+ }
135
+ export async function shouldRunStartupUpdateCheck(argv, now = new Date()) {
136
+ if (process.env[NB_SKIP_STARTUP_UPDATE_ENV] === '1') {
137
+ return false;
138
+ }
139
+ if (shouldSkipByArgv(argv)) {
140
+ return false;
141
+ }
142
+ const state = await readState();
143
+ const policy = await resolveStartupUpdatePolicy(state);
144
+ if (policy === 'off') {
145
+ return false;
146
+ }
147
+ const selfInstall = await inspectSelfInstall();
148
+ if (!shouldEnableStartupUpdateForInstallMethod(selfInstall.installMethod)) {
149
+ return false;
150
+ }
151
+ return readCurrentInstallLastCheckedDate(state) !== todayStamp(now);
152
+ }
153
+ export function shouldEnableStartupUpdateForInstallMethod(installMethod) {
154
+ return installMethod === 'npm-global';
155
+ }
156
+ function hasPendingUpdates(selfStatus, skillsStatus) {
157
+ return Boolean(selfStatus.updateAvailable || skillsStatus.updateAvailable === true);
158
+ }
159
+ function describeCliUpdate(selfStatus) {
160
+ return selfStatus.latestVersion
161
+ ? `NocoBase CLI: ${selfStatus.currentVersion} -> ${selfStatus.latestVersion}`
162
+ : `NocoBase CLI: update available from ${selfStatus.currentVersion}`;
163
+ }
164
+ function describeSkillsUpdate() {
165
+ return 'NocoBase AI skills: update available';
166
+ }
167
+ function describeSkillsUpdateWithVersion(skillsStatus) {
168
+ if (skillsStatus.installedVersion && skillsStatus.latestVersion) {
169
+ return `NocoBase AI skills: ${skillsStatus.installedVersion} -> ${skillsStatus.latestVersion}`;
170
+ }
171
+ if (skillsStatus.latestVersion) {
172
+ return `NocoBase AI skills: latest ${skillsStatus.latestVersion} available`;
173
+ }
174
+ return describeSkillsUpdate();
175
+ }
176
+ function buildPromptMessage(selfStatus, skillsStatus) {
177
+ const lines = [];
178
+ const hasCliUpdate = selfStatus.updateAvailable;
179
+ const hasSkillsUpdate = skillsStatus.updateAvailable === true;
180
+ if (hasCliUpdate && hasSkillsUpdate) {
181
+ lines.push('Updates are available for your NocoBase CLI and AI skills.');
182
+ }
183
+ else if (hasCliUpdate) {
184
+ lines.push('An update is available for your NocoBase CLI.');
185
+ }
186
+ else if (hasSkillsUpdate) {
187
+ lines.push('An update is available for your NocoBase AI skills.');
188
+ }
189
+ else {
190
+ lines.push('A NocoBase CLI or skills update is available.');
191
+ }
192
+ if (hasCliUpdate) {
193
+ lines.push(`- ${describeCliUpdate(selfStatus)}`);
194
+ }
195
+ if (hasSkillsUpdate) {
196
+ lines.push(`- ${describeSkillsUpdateWithVersion(skillsStatus)}`);
197
+ }
198
+ lines.push('Update now?');
199
+ return lines.join('\n');
200
+ }
201
+ function buildUpdateCommands(selfStatus, skillsStatus) {
202
+ if (selfStatus.updateAvailable && selfStatus.updatable) {
203
+ return [skillsStatus.updateAvailable === true ? 'nb self update --yes --skills' : 'nb self update --yes'];
204
+ }
205
+ if (skillsStatus.updateAvailable === true) {
206
+ return ['nb skills update --yes'];
207
+ }
208
+ return [];
209
+ }
210
+ function buildNonInteractiveWarning(selfStatus, skillsStatus) {
211
+ const commands = buildUpdateCommands(selfStatus, skillsStatus);
212
+ const details = [];
213
+ if (selfStatus.updateAvailable) {
214
+ details.push(describeCliUpdate(selfStatus));
215
+ }
216
+ if (skillsStatus.updateAvailable === true) {
217
+ details.push(describeSkillsUpdateWithVersion(skillsStatus));
218
+ }
219
+ return [
220
+ `Updates available${details.length ? `: ${details.join(', ')}` : '.'}`,
221
+ 'Non-interactive session, skipped auto-update.',
222
+ commands.length
223
+ ? `Run: ${commands.join(' && ')}`
224
+ : 'Check with: `nb self check` and `nb skills check`.',
225
+ 'You may run into compatibility issues until you update.',
226
+ ].join(' ');
227
+ }
228
+ function buildDeclinedWarning(selfStatus, skillsStatus) {
229
+ const commands = buildUpdateCommands(selfStatus, skillsStatus);
230
+ const details = [];
231
+ if (selfStatus.updateAvailable) {
232
+ details.push(describeCliUpdate(selfStatus));
233
+ }
234
+ if (skillsStatus.updateAvailable === true) {
235
+ details.push(describeSkillsUpdateWithVersion(skillsStatus));
236
+ }
237
+ return [
238
+ `Skipped updates${details.length ? `: ${details.join(', ')}` : '.'}`,
239
+ commands.length
240
+ ? `Run: ${commands.join(' && ')}`
241
+ : 'Check with: `nb self check` and `nb skills check`.',
242
+ 'You may run into compatibility issues until you update.',
243
+ ].join(' ');
244
+ }
245
+ async function runStartupUpdates(selfStatus, skillsStatus) {
246
+ if (selfStatus.updateAvailable && selfStatus.updatable) {
247
+ const args = ['self', 'update', '--yes'];
248
+ if (skillsStatus.updateAvailable === true) {
249
+ args.push('--skills');
250
+ }
251
+ await run('nb', args, {
252
+ stdio: 'inherit',
253
+ env: {
254
+ [NB_SKIP_STARTUP_UPDATE_ENV]: '1',
255
+ },
256
+ errorName: 'nb self update',
257
+ });
258
+ return;
259
+ }
260
+ if (skillsStatus.updateAvailable === true) {
261
+ await run('nb', ['skills', 'update', '--yes'], {
262
+ stdio: 'inherit',
263
+ env: {
264
+ [NB_SKIP_STARTUP_UPDATE_ENV]: '1',
265
+ },
266
+ errorName: 'nb skills update',
267
+ });
268
+ }
269
+ }
270
+ export async function maybeRunStartupUpdate(argv) {
271
+ if (!(await shouldRunStartupUpdateCheck(argv))) {
272
+ return { kind: 'skipped' };
273
+ }
274
+ const policy = await resolveStartupUpdatePolicy(await readState());
275
+ const selfStatus = await inspectSelfStatus();
276
+ const skillsStatus = await inspectSkillsStatus();
277
+ if (!hasPendingUpdates(selfStatus, skillsStatus)) {
278
+ await markChecked();
279
+ return { kind: 'no-update' };
280
+ }
281
+ if (!isInteractiveTerminal()) {
282
+ printWarning(buildNonInteractiveWarning(selfStatus, skillsStatus));
283
+ await markChecked();
284
+ return { kind: 'warned' };
285
+ }
286
+ if (policy === 'auto') {
287
+ await runStartupUpdates(selfStatus, skillsStatus);
288
+ await markChecked();
289
+ return { kind: 'updated' };
290
+ }
291
+ let answer = false;
292
+ try {
293
+ answer = await confirm({
294
+ message: buildPromptMessage(selfStatus, skillsStatus),
295
+ default: true,
296
+ });
297
+ }
298
+ catch {
299
+ answer = false;
300
+ }
301
+ if (!answer) {
302
+ printWarning(buildDeclinedWarning(selfStatus, skillsStatus));
303
+ await markChecked();
304
+ return { kind: 'declined' };
305
+ }
306
+ await runStartupUpdates(selfStatus, skillsStatus);
307
+ await markChecked();
308
+ return { kind: 'updated' };
309
+ }
package/dist/lib/ui.js ADDED
@@ -0,0 +1,159 @@
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 { stdin as input, stdout as output } from 'node:process';
10
+ import ora from 'ora';
11
+ import pc from 'picocolors';
12
+ import { translateCli } from './cli-locale.js';
13
+ let activeSpinner;
14
+ let verboseMode = false;
15
+ let lastStaticTaskMessage;
16
+ let lastStaticTaskAt = 0;
17
+ const STATIC_TASK_UPDATE_THROTTLE_MS = 3_000;
18
+ function stringWidth(value) {
19
+ return Array.from(value).length;
20
+ }
21
+ function pad(value, width) {
22
+ const padding = Math.max(0, width - stringWidth(value));
23
+ return `${value}${' '.repeat(padding)}`;
24
+ }
25
+ export function isInteractiveTerminal() {
26
+ return Boolean(input.isTTY && output.isTTY);
27
+ }
28
+ function supportsDynamicTaskUpdates() {
29
+ return isInteractiveTerminal() && process.env.TERM !== 'dumb';
30
+ }
31
+ export function setVerboseMode(value) {
32
+ verboseMode = value;
33
+ }
34
+ export function isVerboseMode() {
35
+ return verboseMode;
36
+ }
37
+ export function printSection(title) {
38
+ console.log(title);
39
+ }
40
+ export function printStage(title) {
41
+ clearActiveSpinner();
42
+ console.log(title);
43
+ }
44
+ export function printInfo(message) {
45
+ if (activeSpinner) {
46
+ if (!isInteractiveTerminal()) {
47
+ activeSpinner = undefined;
48
+ console.log(pc.cyan(message));
49
+ return;
50
+ }
51
+ activeSpinner.info(pc.cyan(message));
52
+ activeSpinner = undefined;
53
+ return;
54
+ }
55
+ console.log(pc.cyan(message));
56
+ }
57
+ export function announceTargetEnv(envName) {
58
+ if (process.env.NB_SKIP_TARGET_ENV_LOG === '1') {
59
+ return;
60
+ }
61
+ printInfo(translateCli('commands.shared.targetEnv', { envName }));
62
+ }
63
+ export function printVerbose(message) {
64
+ if (!verboseMode) {
65
+ return;
66
+ }
67
+ printInfo(message);
68
+ }
69
+ export function printSuccess(message) {
70
+ if (activeSpinner) {
71
+ if (!isInteractiveTerminal()) {
72
+ activeSpinner = undefined;
73
+ console.log(pc.green(message));
74
+ return;
75
+ }
76
+ activeSpinner.succeed(pc.green(message));
77
+ activeSpinner = undefined;
78
+ return;
79
+ }
80
+ console.log(pc.green(message));
81
+ }
82
+ function clearActiveSpinner() {
83
+ if (activeSpinner) {
84
+ activeSpinner.stop();
85
+ activeSpinner = undefined;
86
+ }
87
+ }
88
+ export function printWarning(message) {
89
+ clearActiveSpinner();
90
+ console.log(pc.yellow(`⚠ Warning: ${message}`));
91
+ }
92
+ export function printWarningBlock(message) {
93
+ clearActiveSpinner();
94
+ console.log(pc.yellow(`⚠ Warning\n${message}\n`));
95
+ }
96
+ export function printVerboseWarning(message) {
97
+ if (!verboseMode) {
98
+ return;
99
+ }
100
+ printWarning(message);
101
+ }
102
+ export function startTask(message) {
103
+ if (activeSpinner) {
104
+ activeSpinner.stop();
105
+ }
106
+ lastStaticTaskMessage = message;
107
+ lastStaticTaskAt = Date.now();
108
+ if (!supportsDynamicTaskUpdates()) {
109
+ activeSpinner = undefined;
110
+ console.log(pc.cyan(message));
111
+ return;
112
+ }
113
+ activeSpinner = ora({
114
+ text: pc.cyan(message),
115
+ isSilent: false,
116
+ }).start();
117
+ }
118
+ export function updateTask(message) {
119
+ if (!activeSpinner) {
120
+ const now = Date.now();
121
+ if (message !== lastStaticTaskMessage
122
+ && now - lastStaticTaskAt >= STATIC_TASK_UPDATE_THROTTLE_MS) {
123
+ console.log(pc.cyan(message));
124
+ lastStaticTaskMessage = message;
125
+ lastStaticTaskAt = now;
126
+ }
127
+ return;
128
+ }
129
+ activeSpinner.text = pc.cyan(message);
130
+ }
131
+ export function succeedTask(message) {
132
+ if (activeSpinner) {
133
+ activeSpinner.succeed(pc.green(message));
134
+ activeSpinner = undefined;
135
+ return;
136
+ }
137
+ console.log(pc.green(message));
138
+ }
139
+ export function failTask(message) {
140
+ if (activeSpinner) {
141
+ activeSpinner.fail(pc.red(message));
142
+ activeSpinner = undefined;
143
+ return;
144
+ }
145
+ console.error(pc.red(message));
146
+ }
147
+ export function stopTask() {
148
+ lastStaticTaskMessage = undefined;
149
+ lastStaticTaskAt = 0;
150
+ clearActiveSpinner();
151
+ }
152
+ export function renderTable(headers, rows) {
153
+ const widths = headers.map((header, index) => {
154
+ return rows.reduce((max, row) => Math.max(max, stringWidth(row[index] ?? '')), stringWidth(header));
155
+ });
156
+ const renderRow = (row) => row.map((cell, index) => pad(cell ?? '', widths[index])).join(' ').trimEnd();
157
+ const divider = widths.map((width) => '-'.repeat(width)).join(' ');
158
+ return [renderRow(headers), divider, ...rows.map(renderRow)].join('\n');
159
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/cli",
3
- "version": "2.1.0-beta.44.test.4",
3
+ "version": "2.1.0-beta.45",
4
4
  "description": "NocoBase Command Line Tool",
5
5
  "type": "module",
6
6
  "main": "dist/generated/command-registry.js",
@@ -137,5 +137,6 @@
137
137
  "repository": {
138
138
  "type": "git",
139
139
  "url": "git+https://github.com/nocobase/nocobase.git"
140
- }
140
+ },
141
+ "gitHead": "42587115fc34c3eb01ef2b2549f1c998e5708318"
141
142
  }