@nocobase/cli 2.1.0-beta.2 → 2.1.0-beta.21
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/LICENSE.txt +107 -0
- package/README.md +367 -19
- package/README.zh-CN.md +336 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +131 -0
- package/dist/commands/api/resource/create.js +15 -0
- package/dist/commands/api/resource/destroy.js +15 -0
- package/dist/commands/api/resource/get.js +15 -0
- package/dist/commands/api/resource/index.js +20 -0
- package/dist/commands/api/resource/list.js +16 -0
- package/dist/commands/api/resource/query.js +15 -0
- package/dist/commands/api/resource/update.js +15 -0
- package/dist/commands/build.js +57 -0
- package/dist/commands/db/logs.js +85 -0
- package/dist/commands/db/ps.js +60 -0
- package/dist/commands/db/shared.js +95 -0
- package/dist/commands/db/start.js +70 -0
- package/dist/commands/db/stop.js +70 -0
- package/dist/commands/dev.js +156 -0
- package/dist/commands/down.js +197 -0
- package/dist/commands/download.js +865 -0
- package/dist/commands/env/add.js +307 -0
- package/dist/commands/env/auth.js +55 -0
- package/dist/commands/env/list.js +36 -0
- package/dist/commands/env/remove.js +59 -0
- package/dist/commands/env/update.js +67 -0
- package/dist/commands/env/use.js +28 -0
- package/dist/commands/init.js +950 -0
- package/dist/commands/install.js +1927 -0
- package/dist/commands/logs.js +97 -0
- package/dist/commands/pm/disable.js +63 -0
- package/dist/commands/pm/enable.js +63 -0
- package/dist/commands/pm/list.js +61 -0
- package/dist/commands/prompts-stages.js +150 -0
- package/dist/commands/prompts-test.js +181 -0
- package/dist/commands/ps.js +119 -0
- package/dist/commands/restart.js +74 -0
- package/dist/commands/scaffold/migration.js +38 -0
- package/dist/commands/scaffold/plugin.js +37 -0
- package/dist/commands/self/check.js +71 -0
- package/dist/commands/self/index.js +20 -0
- package/dist/commands/self/update.js +86 -0
- package/dist/commands/skills/check.js +69 -0
- package/dist/commands/skills/index.js +20 -0
- package/dist/commands/skills/install.js +71 -0
- package/dist/commands/skills/update.js +71 -0
- package/dist/commands/start.js +218 -0
- package/dist/commands/stop.js +97 -0
- package/dist/commands/test.js +466 -0
- package/dist/commands/upgrade.js +594 -0
- package/dist/generated/command-registry.js +133 -0
- package/dist/help/runtime-help.js +20 -0
- package/dist/lib/api-client.js +244 -0
- package/dist/lib/app-runtime.js +153 -0
- package/dist/lib/auth-store.js +357 -0
- package/dist/lib/bootstrap.js +388 -0
- package/dist/lib/build-config.js +10 -0
- package/dist/lib/cli-home.js +61 -0
- package/dist/lib/cli-locale.js +115 -0
- package/dist/lib/command-discovery.js +39 -0
- package/dist/lib/env-auth.js +872 -0
- package/dist/lib/generated-command.js +150 -0
- package/dist/lib/http-request.js +49 -0
- package/dist/lib/naming.js +70 -0
- package/dist/lib/openapi.js +62 -0
- package/dist/lib/post-processors.js +23 -0
- package/dist/lib/prompt-catalog.js +581 -0
- package/dist/lib/prompt-validators.js +185 -0
- package/dist/lib/prompt-web-ui.js +2096 -0
- package/dist/lib/resource-command.js +343 -0
- package/dist/lib/resource-request.js +104 -0
- package/dist/lib/run-npm.js +197 -0
- package/dist/lib/runtime-generator.js +419 -0
- package/dist/lib/runtime-store.js +56 -0
- package/dist/lib/self-manager.js +246 -0
- package/dist/lib/skills-manager.js +269 -0
- package/dist/lib/startup-update.js +203 -0
- package/dist/lib/ui.js +175 -0
- package/dist/locale/en-US.json +336 -0
- package/dist/locale/zh-CN.json +336 -0
- package/dist/post-processors/data-modeling.js +66 -0
- package/dist/post-processors/data-source-manager.js +114 -0
- package/dist/post-processors/index.js +19 -0
- package/nocobase-ctl.config.json +287 -0
- package/package.json +60 -26
- package/LICENSE +0 -661
- package/bin/index.js +0 -39
- package/nocobase.conf.tpl +0 -95
- package/src/cli.js +0 -19
- package/src/commands/benchmark.js +0 -73
- package/src/commands/build.js +0 -49
- package/src/commands/clean.js +0 -30
- package/src/commands/client.js +0 -166
- package/src/commands/create-nginx-conf.js +0 -37
- package/src/commands/create-plugin.js +0 -33
- package/src/commands/dev.js +0 -200
- package/src/commands/doc.js +0 -76
- package/src/commands/e2e.js +0 -265
- package/src/commands/global.js +0 -43
- package/src/commands/index.js +0 -45
- package/src/commands/instance-id.js +0 -47
- package/src/commands/locale/cronstrue.js +0 -122
- package/src/commands/locale/react-js-cron/en-US.json +0 -75
- package/src/commands/locale/react-js-cron/index.js +0 -17
- package/src/commands/locale/react-js-cron/zh-CN.json +0 -33
- package/src/commands/locale/react-js-cron/zh-TW.json +0 -33
- package/src/commands/locale.js +0 -81
- package/src/commands/p-test.js +0 -88
- package/src/commands/perf.js +0 -63
- package/src/commands/pkg.js +0 -321
- package/src/commands/pm2.js +0 -37
- package/src/commands/postinstall.js +0 -88
- package/src/commands/start.js +0 -148
- package/src/commands/tar.js +0 -36
- package/src/commands/test-coverage.js +0 -55
- package/src/commands/test.js +0 -107
- package/src/commands/umi.js +0 -33
- package/src/commands/update-deps.js +0 -72
- package/src/commands/upgrade.js +0 -47
- package/src/commands/view-license-key.js +0 -44
- package/src/index.js +0 -14
- package/src/license.js +0 -76
- package/src/logger.js +0 -75
- package/src/plugin-generator.js +0 -80
- package/src/util.js +0 -517
- package/templates/bundle-status.html +0 -338
- package/templates/create-app-package.json +0 -39
- package/templates/plugin/.npmignore.tpl +0 -2
- package/templates/plugin/README.md.tpl +0 -1
- package/templates/plugin/client.d.ts +0 -2
- package/templates/plugin/client.js +0 -1
- package/templates/plugin/package.json.tpl +0 -11
- package/templates/plugin/server.d.ts +0 -2
- package/templates/plugin/server.js +0 -1
- package/templates/plugin/src/client/client.d.ts +0 -249
- package/templates/plugin/src/client/index.tsx.tpl +0 -1
- package/templates/plugin/src/client/locale.ts +0 -21
- package/templates/plugin/src/client/models/index.ts +0 -12
- package/templates/plugin/src/client/plugin.tsx.tpl +0 -10
- package/templates/plugin/src/index.ts +0 -2
- package/templates/plugin/src/locale/en-US.json +0 -1
- package/templates/plugin/src/locale/zh-CN.json +0 -1
- package/templates/plugin/src/server/collections/.gitkeep +0 -0
- package/templates/plugin/src/server/index.ts.tpl +0 -1
- package/templates/plugin/src/server/plugin.ts.tpl +0 -19
|
@@ -0,0 +1,246 @@
|
|
|
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';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import { commandOutput, run } from './run-npm.js';
|
|
13
|
+
const DEFAULT_PACKAGE_NAME = '@nocobase/cli';
|
|
14
|
+
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
15
|
+
function normalizePath(value) {
|
|
16
|
+
return path.resolve(value);
|
|
17
|
+
}
|
|
18
|
+
function isSubPath(parent, child) {
|
|
19
|
+
const relative = path.relative(normalizePath(parent), normalizePath(child));
|
|
20
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
21
|
+
}
|
|
22
|
+
function parseVersion(version) {
|
|
23
|
+
const normalized = String(version ?? '').trim();
|
|
24
|
+
const match = normalized.match(/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-.]+))?$/);
|
|
25
|
+
if (!match) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
major: Number(match[1]),
|
|
30
|
+
minor: Number(match[2]),
|
|
31
|
+
patch: Number(match[3]),
|
|
32
|
+
prerelease: match[4] ? match[4].split('.').filter(Boolean) : [],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function compareIdentifier(left, right) {
|
|
36
|
+
const leftNumeric = /^\d+$/.test(left);
|
|
37
|
+
const rightNumeric = /^\d+$/.test(right);
|
|
38
|
+
if (leftNumeric && rightNumeric) {
|
|
39
|
+
return Number(left) - Number(right);
|
|
40
|
+
}
|
|
41
|
+
if (leftNumeric) {
|
|
42
|
+
return -1;
|
|
43
|
+
}
|
|
44
|
+
if (rightNumeric) {
|
|
45
|
+
return 1;
|
|
46
|
+
}
|
|
47
|
+
return left.localeCompare(right);
|
|
48
|
+
}
|
|
49
|
+
export function compareVersions(leftVersion, rightVersion) {
|
|
50
|
+
const left = parseVersion(leftVersion);
|
|
51
|
+
const right = parseVersion(rightVersion);
|
|
52
|
+
if (!left || !right) {
|
|
53
|
+
return String(leftVersion ?? '').localeCompare(String(rightVersion ?? ''));
|
|
54
|
+
}
|
|
55
|
+
if (left.major !== right.major) {
|
|
56
|
+
return left.major - right.major;
|
|
57
|
+
}
|
|
58
|
+
if (left.minor !== right.minor) {
|
|
59
|
+
return left.minor - right.minor;
|
|
60
|
+
}
|
|
61
|
+
if (left.patch !== right.patch) {
|
|
62
|
+
return left.patch - right.patch;
|
|
63
|
+
}
|
|
64
|
+
if (left.prerelease.length === 0 && right.prerelease.length === 0) {
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
if (left.prerelease.length === 0) {
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
if (right.prerelease.length === 0) {
|
|
71
|
+
return -1;
|
|
72
|
+
}
|
|
73
|
+
const maxLength = Math.max(left.prerelease.length, right.prerelease.length);
|
|
74
|
+
for (let index = 0; index < maxLength; index += 1) {
|
|
75
|
+
const leftIdentifier = left.prerelease[index];
|
|
76
|
+
const rightIdentifier = right.prerelease[index];
|
|
77
|
+
if (leftIdentifier === undefined) {
|
|
78
|
+
return -1;
|
|
79
|
+
}
|
|
80
|
+
if (rightIdentifier === undefined) {
|
|
81
|
+
return 1;
|
|
82
|
+
}
|
|
83
|
+
const compared = compareIdentifier(leftIdentifier, rightIdentifier);
|
|
84
|
+
if (compared !== 0) {
|
|
85
|
+
return compared;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
function detectChannel(currentVersion) {
|
|
91
|
+
if (/-alpha(?:[.-]|$)/i.test(currentVersion)) {
|
|
92
|
+
return 'alpha';
|
|
93
|
+
}
|
|
94
|
+
if (/-beta(?:[.-]|$)/i.test(currentVersion)) {
|
|
95
|
+
return 'beta';
|
|
96
|
+
}
|
|
97
|
+
return 'latest';
|
|
98
|
+
}
|
|
99
|
+
function readCurrentVersion(packageRoot) {
|
|
100
|
+
const packageJsonPath = path.join(packageRoot, 'package.json');
|
|
101
|
+
const content = fs.readFileSync(packageJsonPath, 'utf8');
|
|
102
|
+
const pkg = JSON.parse(content);
|
|
103
|
+
return String(pkg.version ?? '').trim();
|
|
104
|
+
}
|
|
105
|
+
function detectInstallMethod(packageRoot, globalPrefix) {
|
|
106
|
+
if (fs.existsSync(path.join(packageRoot, 'src'))
|
|
107
|
+
&& fs.existsSync(path.join(packageRoot, 'tsconfig.json'))) {
|
|
108
|
+
return 'source';
|
|
109
|
+
}
|
|
110
|
+
if (globalPrefix && isSubPath(globalPrefix, packageRoot)) {
|
|
111
|
+
return 'npm-global';
|
|
112
|
+
}
|
|
113
|
+
if (packageRoot.includes(`${path.sep}node_modules${path.sep}`)) {
|
|
114
|
+
return 'package-local';
|
|
115
|
+
}
|
|
116
|
+
return 'unknown';
|
|
117
|
+
}
|
|
118
|
+
async function readGlobalPrefix(commandOutputFn) {
|
|
119
|
+
try {
|
|
120
|
+
return (await commandOutputFn('npm', ['prefix', '-g'], {
|
|
121
|
+
errorName: 'npm prefix',
|
|
122
|
+
})).trim();
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async function readDistTags(packageName, commandOutputFn) {
|
|
129
|
+
const output = await commandOutputFn('npm', ['view', packageName, 'dist-tags', '--json'], {
|
|
130
|
+
errorName: 'npm view',
|
|
131
|
+
});
|
|
132
|
+
const parsed = JSON.parse(output);
|
|
133
|
+
return parsed ?? {};
|
|
134
|
+
}
|
|
135
|
+
function getUnsupportedSelfUpdateReason(installMethod) {
|
|
136
|
+
if (installMethod === 'source') {
|
|
137
|
+
return [
|
|
138
|
+
'This CLI is running from source in a repository checkout.',
|
|
139
|
+
'Automatic self-update is only supported for standard global npm installs.',
|
|
140
|
+
'Upgrade this checkout through your repo workflow instead.',
|
|
141
|
+
].join(' ');
|
|
142
|
+
}
|
|
143
|
+
if (installMethod === 'package-local') {
|
|
144
|
+
return [
|
|
145
|
+
'This CLI is installed from a local project dependency tree.',
|
|
146
|
+
'Automatic self-update is only supported for standard global npm installs.',
|
|
147
|
+
'Upgrade the parent project dependency that provides this CLI instead.',
|
|
148
|
+
].join(' ');
|
|
149
|
+
}
|
|
150
|
+
if (installMethod === 'unknown') {
|
|
151
|
+
return [
|
|
152
|
+
'This CLI install could not be recognized as a standard global npm install.',
|
|
153
|
+
'Automatic self-update is only supported for standard global npm installs.',
|
|
154
|
+
].join(' ');
|
|
155
|
+
}
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
export function getRecommendedSelfUpdateCommand(status) {
|
|
159
|
+
if (!status.updatable || !status.updateAvailable) {
|
|
160
|
+
return undefined;
|
|
161
|
+
}
|
|
162
|
+
return 'nb self update --yes';
|
|
163
|
+
}
|
|
164
|
+
export function formatSelfUpdateUnavailableMessage(status) {
|
|
165
|
+
if (status.registryError) {
|
|
166
|
+
return [
|
|
167
|
+
`Couldn't resolve the latest published version for ${status.packageName}.`,
|
|
168
|
+
'Check your npm registry access and try again.',
|
|
169
|
+
`Details: ${status.registryError}`,
|
|
170
|
+
].join('\n');
|
|
171
|
+
}
|
|
172
|
+
return [
|
|
173
|
+
`Couldn't resolve the latest published version for ${status.packageName}.`,
|
|
174
|
+
'Check your npm registry access and try again.',
|
|
175
|
+
].join('\n');
|
|
176
|
+
}
|
|
177
|
+
export function getSelfUpdatePackageSpec(status) {
|
|
178
|
+
return `${status.packageName}@${status.channel}`;
|
|
179
|
+
}
|
|
180
|
+
export async function inspectSelfStatus(options = {}) {
|
|
181
|
+
const packageRoot = options.packageRoot ? normalizePath(options.packageRoot) : PACKAGE_ROOT;
|
|
182
|
+
const packageName = options.packageName ?? DEFAULT_PACKAGE_NAME;
|
|
183
|
+
const currentVersion = options.currentVersion ?? readCurrentVersion(packageRoot);
|
|
184
|
+
const channel = options.channel && options.channel !== 'auto' ? options.channel : detectChannel(currentVersion);
|
|
185
|
+
const commandOutputFn = options.commandOutputFn ?? commandOutput;
|
|
186
|
+
const globalPrefix = await readGlobalPrefix(commandOutputFn);
|
|
187
|
+
const installMethod = detectInstallMethod(packageRoot, globalPrefix);
|
|
188
|
+
let latestVersion;
|
|
189
|
+
let registryError;
|
|
190
|
+
try {
|
|
191
|
+
const distTags = await readDistTags(packageName, commandOutputFn);
|
|
192
|
+
latestVersion = distTags[channel] || distTags.latest;
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
registryError = error instanceof Error ? error.message : String(error);
|
|
196
|
+
}
|
|
197
|
+
const updateAvailable = latestVersion ? compareVersions(latestVersion, currentVersion) > 0 : false;
|
|
198
|
+
return {
|
|
199
|
+
packageName,
|
|
200
|
+
packageRoot,
|
|
201
|
+
currentVersion,
|
|
202
|
+
channel,
|
|
203
|
+
latestVersion,
|
|
204
|
+
updateAvailable,
|
|
205
|
+
installMethod,
|
|
206
|
+
updatable: installMethod === 'npm-global',
|
|
207
|
+
updateBlockedReason: getUnsupportedSelfUpdateReason(installMethod),
|
|
208
|
+
globalPrefix,
|
|
209
|
+
registryError,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
export function formatUnsupportedSelfUpdateMessage(status) {
|
|
213
|
+
return status.updateBlockedReason
|
|
214
|
+
?? [
|
|
215
|
+
'Automatic self-update is only supported for standard global npm installs.',
|
|
216
|
+
].join('\n');
|
|
217
|
+
}
|
|
218
|
+
export async function updateSelf(options = {}) {
|
|
219
|
+
const status = await inspectSelfStatus(options);
|
|
220
|
+
if (!status.updatable) {
|
|
221
|
+
throw new Error(formatUnsupportedSelfUpdateMessage(status));
|
|
222
|
+
}
|
|
223
|
+
const targetVersion = options.targetVersion ?? status.latestVersion;
|
|
224
|
+
if (!targetVersion) {
|
|
225
|
+
throw new Error(formatSelfUpdateUnavailableMessage(status));
|
|
226
|
+
}
|
|
227
|
+
if (!targetVersion || compareVersions(targetVersion, status.currentVersion) <= 0) {
|
|
228
|
+
return {
|
|
229
|
+
action: 'noop',
|
|
230
|
+
status,
|
|
231
|
+
targetVersion,
|
|
232
|
+
packageSpec: getSelfUpdatePackageSpec(status),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
const packageSpec = getSelfUpdatePackageSpec(status);
|
|
236
|
+
await (options.runFn ?? run)('npm', ['install', '-g', packageSpec], {
|
|
237
|
+
stdio: options.verbose ? 'inherit' : 'ignore',
|
|
238
|
+
errorName: 'npm install',
|
|
239
|
+
});
|
|
240
|
+
return {
|
|
241
|
+
action: 'updated',
|
|
242
|
+
status,
|
|
243
|
+
targetVersion,
|
|
244
|
+
packageSpec,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
@@ -0,0 +1,269 @@
|
|
|
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 fsp from 'node:fs/promises';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { resolveCliHomeDir } from './cli-home.js';
|
|
12
|
+
import { compareVersions } from './self-manager.js';
|
|
13
|
+
import { commandOutput, run } from './run-npm.js';
|
|
14
|
+
export const NOCOBASE_SKILLS_SOURCE = 'nocobase/skills';
|
|
15
|
+
export const NOCOBASE_SKILLS_PACKAGE_NAME = '@nocobase/skills';
|
|
16
|
+
const NOCOBASE_SKILLS_NAME_PREFIX = 'nocobase-';
|
|
17
|
+
function normalizePath(value) {
|
|
18
|
+
return path.resolve(value);
|
|
19
|
+
}
|
|
20
|
+
export function resolveGlobalSkillsRoot(_startCwd = process.cwd()) {
|
|
21
|
+
return normalizePath(resolveCliHomeDir('global'));
|
|
22
|
+
}
|
|
23
|
+
export function resolveSkillsWorkspaceRoot(startCwd = process.cwd()) {
|
|
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');
|
|
35
|
+
}
|
|
36
|
+
export function getManagedSkillsStateFile(workspaceRoot) {
|
|
37
|
+
return path.join(workspaceRoot, 'skills.json');
|
|
38
|
+
}
|
|
39
|
+
async function ensureSkillsWorkspaceRoot(workspaceRoot) {
|
|
40
|
+
await fsp.mkdir(workspaceRoot, { recursive: true });
|
|
41
|
+
}
|
|
42
|
+
async function readManagedSkillsState(workspaceRoot) {
|
|
43
|
+
const filePath = getManagedSkillsStateFile(workspaceRoot);
|
|
44
|
+
try {
|
|
45
|
+
const content = await fsp.readFile(filePath, 'utf8');
|
|
46
|
+
return JSON.parse(content);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
return undefined;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function writeManagedSkillsState(workspaceRoot, state) {
|
|
53
|
+
const filePath = getManagedSkillsStateFile(workspaceRoot);
|
|
54
|
+
await fsp.mkdir(path.dirname(filePath), { recursive: true });
|
|
55
|
+
await fsp.writeFile(filePath, JSON.stringify(state, null, 2));
|
|
56
|
+
}
|
|
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,
|
|
62
|
+
errorName: 'skills list',
|
|
63
|
+
});
|
|
64
|
+
const parsed = JSON.parse(output);
|
|
65
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
66
|
+
}
|
|
67
|
+
export async function listProjectSkills(options = {}) {
|
|
68
|
+
return await listGlobalSkills(options);
|
|
69
|
+
}
|
|
70
|
+
function pickInstalledNocoBaseSkillNames(installedSkills, state) {
|
|
71
|
+
const installedNames = new Set(installedSkills.map((skill) => String(skill.name ?? '').trim()).filter(Boolean));
|
|
72
|
+
if (state?.skillNames?.length) {
|
|
73
|
+
return state.skillNames.filter((name) => installedNames.has(name)).sort();
|
|
74
|
+
}
|
|
75
|
+
return Array.from(installedNames)
|
|
76
|
+
.filter((name) => name.startsWith(NOCOBASE_SKILLS_NAME_PREFIX))
|
|
77
|
+
.sort();
|
|
78
|
+
}
|
|
79
|
+
async function readPublishedSkillsVersion(options = {}) {
|
|
80
|
+
const globalRoot = resolveSkillsRoot(options);
|
|
81
|
+
await ensureSkillsWorkspaceRoot(globalRoot);
|
|
82
|
+
try {
|
|
83
|
+
const output = await (options.commandOutputFn ?? commandOutput)('npm', ['view', NOCOBASE_SKILLS_PACKAGE_NAME, 'version', '--json'], {
|
|
84
|
+
cwd: globalRoot,
|
|
85
|
+
errorName: 'npm view',
|
|
86
|
+
});
|
|
87
|
+
const parsed = JSON.parse(output);
|
|
88
|
+
const version = String(parsed ?? '').trim();
|
|
89
|
+
return { version: version || undefined };
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
return {
|
|
93
|
+
error: error instanceof Error ? error.message : String(error),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
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
|
+
}
|
|
138
|
+
export async function inspectSkillsStatus(options = {}) {
|
|
139
|
+
const globalRoot = resolveSkillsRoot(options);
|
|
140
|
+
const stateFile = getManagedSkillsStateFile(globalRoot);
|
|
141
|
+
const [installedSkills, managedState] = await Promise.all([
|
|
142
|
+
listGlobalSkills({
|
|
143
|
+
globalRoot,
|
|
144
|
+
commandOutputFn: options.commandOutputFn,
|
|
145
|
+
}),
|
|
146
|
+
readManagedSkillsState(globalRoot),
|
|
147
|
+
]);
|
|
148
|
+
const installedSkillNames = pickInstalledNocoBaseSkillNames(installedSkills, managedState);
|
|
149
|
+
const managedByNb = managedState?.packageName === NOCOBASE_SKILLS_PACKAGE_NAME;
|
|
150
|
+
let latestVersion;
|
|
151
|
+
let registryError;
|
|
152
|
+
let updateAvailable = installedSkillNames.length > 0 ? null : false;
|
|
153
|
+
if (installedSkillNames.length > 0 || managedByNb) {
|
|
154
|
+
const published = await readPublishedSkillsVersion({
|
|
155
|
+
globalRoot,
|
|
156
|
+
commandOutputFn: options.commandOutputFn,
|
|
157
|
+
});
|
|
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;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const installedVersion = managedState?.installedVersion ?? managedState?.installedRef;
|
|
166
|
+
return {
|
|
167
|
+
globalRoot,
|
|
168
|
+
workspaceRoot: globalRoot,
|
|
169
|
+
stateFile,
|
|
170
|
+
installed: installedSkillNames.length > 0,
|
|
171
|
+
managedByNb,
|
|
172
|
+
sourcePackage: managedState?.sourcePackage ?? NOCOBASE_SKILLS_SOURCE,
|
|
173
|
+
npmPackageName: managedState?.packageName ?? NOCOBASE_SKILLS_PACKAGE_NAME,
|
|
174
|
+
installedSkillNames,
|
|
175
|
+
latestVersion,
|
|
176
|
+
installedVersion,
|
|
177
|
+
latestRef: latestVersion,
|
|
178
|
+
installedRef: installedVersion,
|
|
179
|
+
updateAvailable,
|
|
180
|
+
registryError,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function formatSkillsNotInstalledMessage() {
|
|
184
|
+
return [
|
|
185
|
+
'NocoBase AI coding skills are not installed globally.',
|
|
186
|
+
'Run `nb skills install` first.',
|
|
187
|
+
].join('\n');
|
|
188
|
+
}
|
|
189
|
+
async function persistManagedSkillsState(globalRoot, options = {}) {
|
|
190
|
+
const installedSkills = await listGlobalSkills({
|
|
191
|
+
globalRoot,
|
|
192
|
+
commandOutputFn: options.commandOutputFn,
|
|
193
|
+
});
|
|
194
|
+
const managedState = await readManagedSkillsState(globalRoot);
|
|
195
|
+
const installedSkillNames = pickInstalledNocoBaseSkillNames(installedSkills, managedState);
|
|
196
|
+
const published = await readPublishedSkillsVersion({
|
|
197
|
+
globalRoot,
|
|
198
|
+
commandOutputFn: options.commandOutputFn,
|
|
199
|
+
});
|
|
200
|
+
const now = new Date().toISOString();
|
|
201
|
+
await writeManagedSkillsState(globalRoot, {
|
|
202
|
+
packageName: NOCOBASE_SKILLS_PACKAGE_NAME,
|
|
203
|
+
sourcePackage: NOCOBASE_SKILLS_SOURCE,
|
|
204
|
+
installedAt: managedState?.installedAt ?? now,
|
|
205
|
+
updatedAt: now,
|
|
206
|
+
installedVersion: published.version,
|
|
207
|
+
skillNames: installedSkillNames,
|
|
208
|
+
});
|
|
209
|
+
return await inspectSkillsStatus({
|
|
210
|
+
globalRoot,
|
|
211
|
+
commandOutputFn: options.commandOutputFn,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
async function reinstallManagedSkills(globalRoot, options = {}, targetVersion) {
|
|
215
|
+
const prepared = await prepareLocalSkillsPackage(globalRoot, options, targetVersion);
|
|
216
|
+
try {
|
|
217
|
+
await (options.runFn ?? run)('npx', ['-y', 'skills', 'add', prepared.packageDir, '-g', '-y'], {
|
|
218
|
+
cwd: globalRoot,
|
|
219
|
+
stdio: options.verbose ? 'inherit' : 'ignore',
|
|
220
|
+
errorName: 'skills add',
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
finally {
|
|
224
|
+
await prepared.cleanup();
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
export async function installNocoBaseSkills(options = {}) {
|
|
228
|
+
const globalRoot = resolveSkillsRoot(options);
|
|
229
|
+
const status = await inspectSkillsStatus({
|
|
230
|
+
globalRoot,
|
|
231
|
+
commandOutputFn: options.commandOutputFn,
|
|
232
|
+
});
|
|
233
|
+
if (status.installed) {
|
|
234
|
+
return {
|
|
235
|
+
action: 'noop',
|
|
236
|
+
status,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
await ensureSkillsWorkspaceRoot(globalRoot);
|
|
240
|
+
await reinstallManagedSkills(globalRoot, options, status.latestVersion);
|
|
241
|
+
return {
|
|
242
|
+
action: 'installed',
|
|
243
|
+
status: await persistManagedSkillsState(globalRoot, options),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
export async function updateNocoBaseSkills(options = {}) {
|
|
247
|
+
const globalRoot = resolveSkillsRoot(options);
|
|
248
|
+
const status = await inspectSkillsStatus({
|
|
249
|
+
globalRoot,
|
|
250
|
+
commandOutputFn: options.commandOutputFn,
|
|
251
|
+
});
|
|
252
|
+
if (!status.installed) {
|
|
253
|
+
throw new Error(formatSkillsNotInstalledMessage());
|
|
254
|
+
}
|
|
255
|
+
if (status.managedByNb
|
|
256
|
+
&& status.latestVersion
|
|
257
|
+
&& status.installedVersion
|
|
258
|
+
&& compareVersions(status.latestVersion, status.installedVersion) <= 0) {
|
|
259
|
+
return {
|
|
260
|
+
action: 'noop',
|
|
261
|
+
status,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
await reinstallManagedSkills(globalRoot, options, status.latestVersion);
|
|
265
|
+
return {
|
|
266
|
+
action: 'updated',
|
|
267
|
+
status: await persistManagedSkillsState(globalRoot, options),
|
|
268
|
+
};
|
|
269
|
+
}
|