@nocobase/cli 2.1.0-beta.20 → 2.1.0-beta.22
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.
- package/README.md +32 -50
- package/README.zh-CN.md +29 -46
- package/bin/run.js +15 -0
- package/dist/commands/app/down.js +260 -0
- package/dist/commands/app/info.js +140 -0
- package/dist/commands/app/logs.js +98 -0
- package/dist/commands/app/ps.js +60 -0
- package/dist/commands/app/restart.js +75 -0
- package/dist/commands/app/shared.js +95 -0
- package/dist/commands/app/start.js +252 -0
- package/dist/commands/app/stop.js +98 -0
- package/dist/commands/app/upgrade.js +595 -0
- package/dist/commands/build.js +3 -48
- package/dist/commands/db/shared.js +19 -5
- package/dist/commands/dev.js +3 -140
- package/dist/commands/down.js +3 -184
- package/dist/commands/download.js +4 -856
- package/dist/commands/env/add.js +33 -48
- package/dist/commands/env/auth.js +6 -13
- package/dist/commands/env/list.js +10 -15
- package/dist/commands/env/remove.js +4 -10
- package/dist/commands/env/update.js +7 -13
- package/dist/commands/env/use.js +5 -13
- package/dist/commands/{prompts-stages.js → examples/prompts-stages.js} +3 -3
- package/dist/commands/{prompts-test.js → examples/prompts-test.js} +3 -3
- package/dist/commands/init.js +262 -63
- package/dist/commands/install.js +352 -86
- package/dist/commands/logs.js +3 -81
- package/dist/commands/plugin/disable.js +64 -0
- package/dist/commands/plugin/enable.js +64 -0
- package/dist/commands/plugin/list.js +62 -0
- package/dist/commands/pm/disable.js +3 -54
- package/dist/commands/pm/enable.js +3 -54
- package/dist/commands/pm/list.js +3 -45
- package/dist/commands/ps.js +3 -107
- package/dist/commands/restart.js +3 -65
- package/dist/commands/scaffold/migration.js +1 -1
- package/dist/commands/scaffold/plugin.js +1 -1
- package/dist/commands/self/check.js +1 -1
- package/dist/commands/self/update.js +13 -3
- package/dist/commands/skills/check.js +11 -5
- package/dist/commands/skills/index.js +1 -1
- package/dist/commands/skills/install.js +20 -7
- package/dist/commands/skills/remove.js +71 -0
- package/dist/commands/skills/update.js +27 -7
- package/dist/commands/source/build.js +58 -0
- package/dist/commands/source/dev.js +157 -0
- package/dist/commands/source/download.js +866 -0
- package/dist/commands/source/test.js +467 -0
- package/dist/commands/start.js +3 -202
- package/dist/commands/stop.js +3 -81
- package/dist/commands/test.js +3 -457
- package/dist/commands/upgrade.js +3 -574
- package/dist/help/runtime-help.js +3 -0
- package/dist/lib/api-client.js +3 -2
- package/dist/lib/app-health.js +126 -0
- package/dist/lib/app-managed-resources.js +264 -0
- package/dist/lib/app-runtime.js +16 -5
- package/dist/lib/auth-store.js +162 -43
- package/dist/lib/bootstrap.js +13 -12
- package/dist/lib/cli-home.js +38 -6
- package/dist/lib/cli-locale.js +15 -1
- package/dist/lib/env-auth.js +3 -3
- package/dist/lib/env-config.js +80 -0
- package/dist/lib/generated-command.js +10 -2
- package/dist/lib/http-request.js +49 -0
- package/dist/lib/resource-command.js +10 -2
- package/dist/lib/runtime-generator.js +1 -1
- package/dist/lib/self-manager.js +1 -1
- package/dist/lib/skills-manager.js +173 -79
- package/dist/lib/startup-update.js +203 -0
- package/dist/locale/en-US.json +4 -1
- package/dist/locale/zh-CN.json +4 -1
- package/package.json +26 -3
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
function normalizeLocationUrl(location, currentUrl) {
|
|
10
|
+
try {
|
|
11
|
+
return new URL(location, currentUrl).toString();
|
|
12
|
+
}
|
|
13
|
+
catch (_error) {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function shouldPreserveAuthorizationRedirect(fromUrl, toUrl) {
|
|
18
|
+
try {
|
|
19
|
+
const from = new URL(fromUrl);
|
|
20
|
+
const to = new URL(toUrl);
|
|
21
|
+
return (from.hostname === to.hostname &&
|
|
22
|
+
from.port === to.port &&
|
|
23
|
+
from.pathname === to.pathname &&
|
|
24
|
+
from.search === to.search &&
|
|
25
|
+
from.protocol === 'http:' &&
|
|
26
|
+
to.protocol === 'https:');
|
|
27
|
+
}
|
|
28
|
+
catch (_error) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export async function fetchWithPreservedAuthRedirect(url, init = {}) {
|
|
33
|
+
const response = await fetch(url, {
|
|
34
|
+
...init,
|
|
35
|
+
redirect: 'manual',
|
|
36
|
+
});
|
|
37
|
+
const location = response.headers.get('location');
|
|
38
|
+
if (!location || ![301, 302, 307, 308].includes(response.status)) {
|
|
39
|
+
return response;
|
|
40
|
+
}
|
|
41
|
+
const nextUrl = normalizeLocationUrl(location, url);
|
|
42
|
+
if (!nextUrl || !shouldPreserveAuthorizationRedirect(url, nextUrl)) {
|
|
43
|
+
return response;
|
|
44
|
+
}
|
|
45
|
+
return fetch(nextUrl, {
|
|
46
|
+
...init,
|
|
47
|
+
redirect: 'manual',
|
|
48
|
+
});
|
|
49
|
+
}
|
|
@@ -1,3 +1,11 @@
|
|
|
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
|
+
*/
|
|
1
9
|
import { Flags } from '@oclif/core';
|
|
2
10
|
import { executeResourceRequest } from './resource-request.js';
|
|
3
11
|
import { setVerboseMode } from './ui.js';
|
|
@@ -80,7 +88,7 @@ function printResponse(command, response, jsonOutput) {
|
|
|
80
88
|
command.log(`HTTP ${response.status}`);
|
|
81
89
|
}
|
|
82
90
|
export const resourceBaseFlags = {
|
|
83
|
-
'base-url': Flags.string({
|
|
91
|
+
'api-base-url': Flags.string({
|
|
84
92
|
description: 'NocoBase API base URL, for example http://localhost:13000/api',
|
|
85
93
|
}),
|
|
86
94
|
verbose: Flags.boolean({
|
|
@@ -325,7 +333,7 @@ export async function runResourceCommand(command, action, flags, args) {
|
|
|
325
333
|
setVerboseMode(Boolean(flags.verbose));
|
|
326
334
|
const response = await executeResourceRequest({
|
|
327
335
|
envName: flags.env,
|
|
328
|
-
baseUrl: flags['base-url'],
|
|
336
|
+
baseUrl: flags['api-base-url'],
|
|
329
337
|
role: flags.role,
|
|
330
338
|
token: flags.token,
|
|
331
339
|
action,
|
|
@@ -10,7 +10,7 @@ import { createHash } from 'node:crypto';
|
|
|
10
10
|
import { loadBuildConfig } from './build-config.js';
|
|
11
11
|
import { toKebabCase, toLogicalActionName, toLogicalResourceName, toResourceSegments } from './naming.js';
|
|
12
12
|
import { collectOperations } from './openapi.js';
|
|
13
|
-
const RESERVED_FLAG_NAMES = new Set(['base-url', 'env', 'token', 'json-output', 'body', 'body-file']);
|
|
13
|
+
const RESERVED_FLAG_NAMES = new Set(['api-base-url', 'base-url', 'env', 'token', 'json-output', 'body', 'body-file']);
|
|
14
14
|
function matchesPattern(value, pattern) {
|
|
15
15
|
if (!value) {
|
|
16
16
|
return false;
|
package/dist/lib/self-manager.js
CHANGED
|
@@ -234,7 +234,7 @@ export async function updateSelf(options = {}) {
|
|
|
234
234
|
}
|
|
235
235
|
const packageSpec = getSelfUpdatePackageSpec(status);
|
|
236
236
|
await (options.runFn ?? run)('npm', ['install', '-g', packageSpec], {
|
|
237
|
-
stdio: 'inherit',
|
|
237
|
+
stdio: options.verbose ? 'inherit' : 'ignore',
|
|
238
238
|
errorName: 'npm install',
|
|
239
239
|
});
|
|
240
240
|
return {
|
|
@@ -6,31 +6,38 @@
|
|
|
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';
|
|
10
9
|
import fsp from 'node:fs/promises';
|
|
11
10
|
import path from 'node:path';
|
|
11
|
+
import { resolveCliHomeDir } from './cli-home.js';
|
|
12
|
+
import { compareVersions } from './self-manager.js';
|
|
12
13
|
import { commandOutput, run } from './run-npm.js';
|
|
13
|
-
export const
|
|
14
|
-
export const
|
|
14
|
+
export const NOCOBASE_SKILLS_SOURCE = 'nocobase/skills';
|
|
15
|
+
export const NOCOBASE_SKILLS_PACKAGE_NAME = '@nocobase/skills';
|
|
15
16
|
const NOCOBASE_SKILLS_NAME_PREFIX = 'nocobase-';
|
|
16
17
|
function normalizePath(value) {
|
|
17
18
|
return path.resolve(value);
|
|
18
19
|
}
|
|
20
|
+
export function resolveGlobalSkillsRoot(_startCwd = process.cwd()) {
|
|
21
|
+
return normalizePath(resolveCliHomeDir('global'));
|
|
22
|
+
}
|
|
19
23
|
export function resolveSkillsWorkspaceRoot(startCwd = process.cwd()) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
return resolveGlobalSkillsRoot(startCwd);
|
|
25
|
+
}
|
|
26
|
+
function resolveSkillsRoot(options = {}) {
|
|
27
|
+
return options.globalRoot
|
|
28
|
+
? normalizePath(options.globalRoot)
|
|
29
|
+
: options.workspaceRoot
|
|
30
|
+
? normalizePath(options.workspaceRoot)
|
|
31
|
+
: resolveGlobalSkillsRoot();
|
|
32
|
+
}
|
|
33
|
+
function getSkillsCacheRoot(globalRoot) {
|
|
34
|
+
return path.join(globalRoot, 'cache', 'skills');
|
|
31
35
|
}
|
|
32
36
|
export function getManagedSkillsStateFile(workspaceRoot) {
|
|
33
|
-
return path.join(workspaceRoot, '
|
|
37
|
+
return path.join(workspaceRoot, 'skills.json');
|
|
38
|
+
}
|
|
39
|
+
async function ensureSkillsWorkspaceRoot(workspaceRoot) {
|
|
40
|
+
await fsp.mkdir(workspaceRoot, { recursive: true });
|
|
34
41
|
}
|
|
35
42
|
async function readManagedSkillsState(workspaceRoot) {
|
|
36
43
|
const filePath = getManagedSkillsStateFile(workspaceRoot);
|
|
@@ -47,15 +54,19 @@ async function writeManagedSkillsState(workspaceRoot, state) {
|
|
|
47
54
|
await fsp.mkdir(path.dirname(filePath), { recursive: true });
|
|
48
55
|
await fsp.writeFile(filePath, JSON.stringify(state, null, 2));
|
|
49
56
|
}
|
|
50
|
-
export async function
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
57
|
+
export async function listGlobalSkills(options = {}) {
|
|
58
|
+
const globalRoot = resolveSkillsRoot(options);
|
|
59
|
+
await ensureSkillsWorkspaceRoot(globalRoot);
|
|
60
|
+
const output = await (options.commandOutputFn ?? commandOutput)('npx', ['-y', 'skills', 'list', '-g', '--json'], {
|
|
61
|
+
cwd: globalRoot,
|
|
54
62
|
errorName: 'skills list',
|
|
55
63
|
});
|
|
56
64
|
const parsed = JSON.parse(output);
|
|
57
65
|
return Array.isArray(parsed) ? parsed : [];
|
|
58
66
|
}
|
|
67
|
+
export async function listProjectSkills(options = {}) {
|
|
68
|
+
return await listGlobalSkills(options);
|
|
69
|
+
}
|
|
59
70
|
function pickInstalledNocoBaseSkillNames(installedSkills, state) {
|
|
60
71
|
const installedNames = new Set(installedSkills.map((skill) => String(skill.name ?? '').trim()).filter(Boolean));
|
|
61
72
|
if (state?.skillNames?.length) {
|
|
@@ -65,14 +76,17 @@ function pickInstalledNocoBaseSkillNames(installedSkills, state) {
|
|
|
65
76
|
.filter((name) => name.startsWith(NOCOBASE_SKILLS_NAME_PREFIX))
|
|
66
77
|
.sort();
|
|
67
78
|
}
|
|
68
|
-
|
|
79
|
+
async function readPublishedSkillsVersion(options = {}) {
|
|
80
|
+
const globalRoot = resolveSkillsRoot(options);
|
|
81
|
+
await ensureSkillsWorkspaceRoot(globalRoot);
|
|
69
82
|
try {
|
|
70
|
-
const output = await (options.commandOutputFn ?? commandOutput)('
|
|
71
|
-
cwd:
|
|
72
|
-
errorName: '
|
|
83
|
+
const output = await (options.commandOutputFn ?? commandOutput)('npm', ['view', NOCOBASE_SKILLS_PACKAGE_NAME, 'version', '--json'], {
|
|
84
|
+
cwd: globalRoot,
|
|
85
|
+
errorName: 'npm view',
|
|
73
86
|
});
|
|
74
|
-
const
|
|
75
|
-
|
|
87
|
+
const parsed = JSON.parse(output);
|
|
88
|
+
const version = String(parsed ?? '').trim();
|
|
89
|
+
return { version: version || undefined };
|
|
76
90
|
}
|
|
77
91
|
catch (error) {
|
|
78
92
|
return {
|
|
@@ -80,80 +94,134 @@ export async function readNocoBaseSkillsHeadRef(options = {}) {
|
|
|
80
94
|
};
|
|
81
95
|
}
|
|
82
96
|
}
|
|
97
|
+
async function readCachedSkillsVersion(cacheRoot) {
|
|
98
|
+
const packageJsonPath = path.join(cacheRoot, 'node_modules', '@nocobase', 'skills', 'package.json');
|
|
99
|
+
try {
|
|
100
|
+
const content = await fsp.readFile(packageJsonPath, 'utf8');
|
|
101
|
+
const parsed = JSON.parse(content);
|
|
102
|
+
const version = String(parsed.version ?? '').trim();
|
|
103
|
+
return version || undefined;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
async function prepareLocalSkillsPackage(globalRoot, options = {}, targetVersion) {
|
|
110
|
+
const cacheRoot = getSkillsCacheRoot(globalRoot);
|
|
111
|
+
const packageDir = path.join(cacheRoot, 'node_modules', '@nocobase', 'skills');
|
|
112
|
+
const packageSpec = targetVersion ? `${NOCOBASE_SKILLS_PACKAGE_NAME}@${targetVersion}` : NOCOBASE_SKILLS_PACKAGE_NAME;
|
|
113
|
+
const cachedVersion = await readCachedSkillsVersion(cacheRoot);
|
|
114
|
+
await fsp.mkdir(cacheRoot, { recursive: true });
|
|
115
|
+
if (targetVersion && cachedVersion && compareVersions(cachedVersion, targetVersion) === 0) {
|
|
116
|
+
return {
|
|
117
|
+
packageDir,
|
|
118
|
+
cleanup: async () => undefined,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
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
|
+
});
|
|
127
|
+
try {
|
|
128
|
+
await fsp.access(packageDir);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
throw new Error(`npm install did not produce a local ${NOCOBASE_SKILLS_PACKAGE_NAME} package.`);
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
packageDir,
|
|
135
|
+
cleanup: async () => undefined,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
83
138
|
export async function inspectSkillsStatus(options = {}) {
|
|
84
|
-
const
|
|
85
|
-
const stateFile = getManagedSkillsStateFile(
|
|
139
|
+
const globalRoot = resolveSkillsRoot(options);
|
|
140
|
+
const stateFile = getManagedSkillsStateFile(globalRoot);
|
|
86
141
|
const [installedSkills, managedState] = await Promise.all([
|
|
87
|
-
|
|
88
|
-
|
|
142
|
+
listGlobalSkills({
|
|
143
|
+
globalRoot,
|
|
89
144
|
commandOutputFn: options.commandOutputFn,
|
|
90
145
|
}),
|
|
91
|
-
readManagedSkillsState(
|
|
146
|
+
readManagedSkillsState(globalRoot),
|
|
92
147
|
]);
|
|
93
148
|
const installedSkillNames = pickInstalledNocoBaseSkillNames(installedSkills, managedState);
|
|
94
|
-
const managedByNb = managedState?.packageName ===
|
|
95
|
-
let
|
|
149
|
+
const managedByNb = managedState?.packageName === NOCOBASE_SKILLS_PACKAGE_NAME;
|
|
150
|
+
let latestVersion;
|
|
96
151
|
let registryError;
|
|
97
152
|
let updateAvailable = installedSkillNames.length > 0 ? null : false;
|
|
98
153
|
if (installedSkillNames.length > 0 || managedByNb) {
|
|
99
|
-
const
|
|
100
|
-
|
|
154
|
+
const published = await readPublishedSkillsVersion({
|
|
155
|
+
globalRoot,
|
|
101
156
|
commandOutputFn: options.commandOutputFn,
|
|
102
157
|
});
|
|
103
|
-
|
|
104
|
-
registryError =
|
|
105
|
-
|
|
106
|
-
|
|
158
|
+
latestVersion = published.version;
|
|
159
|
+
registryError = published.error;
|
|
160
|
+
const installedVersion = managedState?.installedVersion ?? managedState?.installedRef;
|
|
161
|
+
if (installedVersion && latestVersion) {
|
|
162
|
+
updateAvailable = compareVersions(latestVersion, installedVersion) > 0;
|
|
107
163
|
}
|
|
108
164
|
}
|
|
165
|
+
const installedVersion = managedState?.installedVersion ?? managedState?.installedRef;
|
|
109
166
|
return {
|
|
110
|
-
|
|
167
|
+
globalRoot,
|
|
168
|
+
workspaceRoot: globalRoot,
|
|
111
169
|
stateFile,
|
|
112
170
|
installed: installedSkillNames.length > 0,
|
|
113
171
|
managedByNb,
|
|
114
|
-
sourcePackage:
|
|
172
|
+
sourcePackage: managedState?.sourcePackage ?? NOCOBASE_SKILLS_SOURCE,
|
|
173
|
+
npmPackageName: managedState?.packageName ?? NOCOBASE_SKILLS_PACKAGE_NAME,
|
|
115
174
|
installedSkillNames,
|
|
116
|
-
|
|
117
|
-
|
|
175
|
+
latestVersion,
|
|
176
|
+
installedVersion,
|
|
177
|
+
latestRef: latestVersion,
|
|
178
|
+
installedRef: installedVersion,
|
|
118
179
|
updateAvailable,
|
|
119
180
|
registryError,
|
|
120
181
|
};
|
|
121
182
|
}
|
|
122
|
-
function
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
'Run `nb skills install` first.',
|
|
126
|
-
].join('\n');
|
|
127
|
-
}
|
|
128
|
-
async function persistManagedSkillsState(workspaceRoot, options = {}) {
|
|
129
|
-
const installedSkills = await listProjectSkills({
|
|
130
|
-
workspaceRoot,
|
|
183
|
+
async function persistManagedSkillsState(globalRoot, options = {}) {
|
|
184
|
+
const installedSkills = await listGlobalSkills({
|
|
185
|
+
globalRoot,
|
|
131
186
|
commandOutputFn: options.commandOutputFn,
|
|
132
187
|
});
|
|
133
|
-
const managedState = await readManagedSkillsState(
|
|
188
|
+
const managedState = await readManagedSkillsState(globalRoot);
|
|
134
189
|
const installedSkillNames = pickInstalledNocoBaseSkillNames(installedSkills, managedState);
|
|
135
|
-
const
|
|
136
|
-
|
|
190
|
+
const published = await readPublishedSkillsVersion({
|
|
191
|
+
globalRoot,
|
|
137
192
|
commandOutputFn: options.commandOutputFn,
|
|
138
193
|
});
|
|
139
194
|
const now = new Date().toISOString();
|
|
140
|
-
await writeManagedSkillsState(
|
|
141
|
-
packageName:
|
|
142
|
-
|
|
195
|
+
await writeManagedSkillsState(globalRoot, {
|
|
196
|
+
packageName: NOCOBASE_SKILLS_PACKAGE_NAME,
|
|
197
|
+
sourcePackage: NOCOBASE_SKILLS_SOURCE,
|
|
143
198
|
installedAt: managedState?.installedAt ?? now,
|
|
144
199
|
updatedAt: now,
|
|
145
|
-
|
|
200
|
+
installedVersion: published.version,
|
|
146
201
|
skillNames: installedSkillNames,
|
|
147
202
|
});
|
|
148
203
|
return await inspectSkillsStatus({
|
|
149
|
-
|
|
204
|
+
globalRoot,
|
|
150
205
|
commandOutputFn: options.commandOutputFn,
|
|
151
206
|
});
|
|
152
207
|
}
|
|
208
|
+
async function reinstallManagedSkills(globalRoot, options = {}, targetVersion) {
|
|
209
|
+
const prepared = await prepareLocalSkillsPackage(globalRoot, options, targetVersion);
|
|
210
|
+
try {
|
|
211
|
+
await (options.runFn ?? run)('npx', ['-y', 'skills', 'add', prepared.packageDir, '-g', '-y'], {
|
|
212
|
+
cwd: globalRoot,
|
|
213
|
+
stdio: options.verbose ? 'inherit' : 'ignore',
|
|
214
|
+
errorName: 'skills add',
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
finally {
|
|
218
|
+
await prepared.cleanup();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
153
221
|
export async function installNocoBaseSkills(options = {}) {
|
|
154
|
-
const
|
|
222
|
+
const globalRoot = resolveSkillsRoot(options);
|
|
155
223
|
const status = await inspectSkillsStatus({
|
|
156
|
-
|
|
224
|
+
globalRoot,
|
|
157
225
|
commandOutputFn: options.commandOutputFn,
|
|
158
226
|
});
|
|
159
227
|
if (status.installed) {
|
|
@@ -162,41 +230,67 @@ export async function installNocoBaseSkills(options = {}) {
|
|
|
162
230
|
status,
|
|
163
231
|
};
|
|
164
232
|
}
|
|
165
|
-
await (
|
|
166
|
-
|
|
167
|
-
stdio: 'inherit',
|
|
168
|
-
errorName: 'skills add',
|
|
169
|
-
});
|
|
233
|
+
await ensureSkillsWorkspaceRoot(globalRoot);
|
|
234
|
+
await reinstallManagedSkills(globalRoot, options, status.latestVersion);
|
|
170
235
|
return {
|
|
171
236
|
action: 'installed',
|
|
172
|
-
status: await persistManagedSkillsState(
|
|
237
|
+
status: await persistManagedSkillsState(globalRoot, options),
|
|
173
238
|
};
|
|
174
239
|
}
|
|
175
240
|
export async function updateNocoBaseSkills(options = {}) {
|
|
176
|
-
const
|
|
241
|
+
const globalRoot = resolveSkillsRoot(options);
|
|
177
242
|
const status = await inspectSkillsStatus({
|
|
178
|
-
|
|
243
|
+
globalRoot,
|
|
179
244
|
commandOutputFn: options.commandOutputFn,
|
|
180
245
|
});
|
|
181
246
|
if (!status.installed) {
|
|
182
|
-
|
|
247
|
+
return {
|
|
248
|
+
action: 'noop',
|
|
249
|
+
reason: 'not-installed',
|
|
250
|
+
status,
|
|
251
|
+
};
|
|
183
252
|
}
|
|
184
253
|
if (status.managedByNb
|
|
185
|
-
&& status.
|
|
186
|
-
&& status.
|
|
187
|
-
&& status.
|
|
254
|
+
&& status.latestVersion
|
|
255
|
+
&& status.installedVersion
|
|
256
|
+
&& compareVersions(status.latestVersion, status.installedVersion) <= 0) {
|
|
188
257
|
return {
|
|
189
258
|
action: 'noop',
|
|
259
|
+
reason: 'up-to-date',
|
|
190
260
|
status,
|
|
191
261
|
};
|
|
192
262
|
}
|
|
193
|
-
await (
|
|
194
|
-
cwd: workspaceRoot,
|
|
195
|
-
stdio: 'inherit',
|
|
196
|
-
errorName: 'skills update',
|
|
197
|
-
});
|
|
263
|
+
await reinstallManagedSkills(globalRoot, options, status.latestVersion);
|
|
198
264
|
return {
|
|
199
265
|
action: 'updated',
|
|
200
|
-
status: await persistManagedSkillsState(
|
|
266
|
+
status: await persistManagedSkillsState(globalRoot, options),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
export async function removeNocoBaseSkills(options = {}) {
|
|
270
|
+
const globalRoot = resolveSkillsRoot(options);
|
|
271
|
+
const status = await inspectSkillsStatus({
|
|
272
|
+
globalRoot,
|
|
273
|
+
commandOutputFn: options.commandOutputFn,
|
|
274
|
+
});
|
|
275
|
+
if (!status.installed || status.installedSkillNames.length === 0) {
|
|
276
|
+
return {
|
|
277
|
+
action: 'noop',
|
|
278
|
+
status,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
for (const skillName of status.installedSkillNames) {
|
|
282
|
+
await (options.runFn ?? run)('npx', ['-y', 'skills', 'remove', skillName, '-g', '-y'], {
|
|
283
|
+
cwd: globalRoot,
|
|
284
|
+
stdio: options.verbose ? 'inherit' : 'ignore',
|
|
285
|
+
errorName: 'skills remove',
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
await fsp.rm(getManagedSkillsStateFile(globalRoot), { force: true });
|
|
289
|
+
return {
|
|
290
|
+
action: 'removed',
|
|
291
|
+
status: await inspectSkillsStatus({
|
|
292
|
+
globalRoot,
|
|
293
|
+
commandOutputFn: options.commandOutputFn,
|
|
294
|
+
}),
|
|
201
295
|
};
|
|
202
296
|
}
|
|
@@ -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
|
+
}
|