@nocobase/cli 2.2.0-beta.1 → 2.2.0-beta.10
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/assets/env-proxy/nginx/snippets/proxy-location.conf +1 -0
- package/bin/early-locale.js +89 -0
- package/bin/node-version.js +35 -0
- package/bin/run.js +9 -0
- package/bin/windows-admin.js +60 -0
- package/dist/commands/app/destroy.js +4 -3
- package/dist/commands/app/restart.js +38 -0
- package/dist/commands/app/shared.js +49 -3
- package/dist/commands/app/start.js +95 -0
- package/dist/commands/app/upgrade.js +11 -0
- package/dist/commands/env/info.js +11 -1
- package/dist/commands/examples/prompts-stages.js +2 -2
- package/dist/commands/examples/prompts-test.js +2 -2
- package/dist/commands/init.js +33 -11
- package/dist/commands/install.js +159 -107
- package/dist/commands/license/activate.js +4 -1
- package/dist/commands/license/shared.js +24 -15
- package/dist/commands/proxy/caddy/generate.js +93 -7
- package/dist/commands/proxy/nginx/generate.js +98 -7
- package/dist/commands/revision/create.js +1 -1
- package/dist/commands/self/check.js +1 -1
- package/dist/commands/self/update.js +4 -4
- package/dist/commands/skills/check.js +4 -5
- package/dist/commands/skills/install.js +18 -1
- package/dist/commands/skills/update.js +19 -4
- package/dist/commands/source/dev.js +9 -5
- package/dist/commands/source/download.js +85 -16
- package/dist/lib/api-command-compat.js +51 -8
- package/dist/lib/app-managed-resources.js +104 -5
- package/dist/lib/auth-store.js +102 -12
- package/dist/lib/cli-config.js +73 -1
- package/dist/lib/docker-image.js +94 -6
- package/dist/lib/env-auth.js +291 -45
- package/dist/lib/env-config.js +11 -0
- package/dist/lib/env-proxy-config.js +48 -0
- package/dist/lib/env-proxy.js +164 -58
- package/dist/lib/hook-script.js +160 -0
- package/dist/lib/prompt-catalog-terminal.js +32 -19
- package/dist/lib/prompt-validators.js +1 -1
- package/dist/lib/prompt-web-ui.js +20 -13
- package/dist/lib/proxy-caddy.js +77 -9
- package/dist/lib/proxy-nginx.js +71 -11
- package/dist/lib/run-npm.js +4 -0
- package/dist/lib/self-manager.js +254 -46
- package/dist/lib/skills-manager.js +116 -23
- package/dist/lib/source-publish.js +2 -2
- package/dist/lib/startup-update.js +1 -1
- package/dist/locale/en-US.json +49 -43
- package/dist/locale/zh-CN.json +49 -43
- package/package.json +8 -2
- package/scripts/build.mjs +0 -34
- package/scripts/clean.mjs +0 -9
- package/tsconfig.json +0 -19
package/dist/lib/self-manager.js
CHANGED
|
@@ -7,14 +7,12 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
import fs from 'node:fs';
|
|
10
|
-
import
|
|
10
|
+
import os from 'node:os';
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
import { fileURLToPath } from 'node:url';
|
|
13
|
-
import { resolveCliHomeDir } from './cli-home.js';
|
|
14
13
|
import { commandOutput, run } from './run-npm.js';
|
|
15
14
|
const DEFAULT_PACKAGE_NAME = '@nocobase/cli';
|
|
16
15
|
const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
17
|
-
const INSTALL_METHOD_CACHE_FILE = 'self-install-methods.json';
|
|
18
16
|
function normalizePath(value) {
|
|
19
17
|
return path.resolve(value);
|
|
20
18
|
}
|
|
@@ -97,6 +95,9 @@ function detectChannel(currentVersion) {
|
|
|
97
95
|
if (/-beta(?:[.-]|$)/i.test(currentVersion)) {
|
|
98
96
|
return 'beta';
|
|
99
97
|
}
|
|
98
|
+
if (/-test(?:[.-]|$)/i.test(currentVersion)) {
|
|
99
|
+
return 'test';
|
|
100
|
+
}
|
|
100
101
|
return 'latest';
|
|
101
102
|
}
|
|
102
103
|
function readCurrentVersion(packageRoot) {
|
|
@@ -105,56 +106,216 @@ function readCurrentVersion(packageRoot) {
|
|
|
105
106
|
const pkg = JSON.parse(content);
|
|
106
107
|
return String(pkg.version ?? '').trim();
|
|
107
108
|
}
|
|
108
|
-
function detectInstallMethod(packageRoot,
|
|
109
|
+
function detectInstallMethod(packageRoot, options = {}) {
|
|
109
110
|
if (fs.existsSync(path.join(packageRoot, 'src'))
|
|
110
111
|
&& fs.existsSync(path.join(packageRoot, 'tsconfig.json'))) {
|
|
111
112
|
return 'source';
|
|
112
113
|
}
|
|
113
|
-
if (globalPrefix && isSubPath(globalPrefix, packageRoot)) {
|
|
114
|
+
if (options.globalPrefix && isSubPath(options.globalPrefix, packageRoot)) {
|
|
114
115
|
return 'npm-global';
|
|
115
116
|
}
|
|
117
|
+
if (options.yarnGlobalDir && isSubPath(options.yarnGlobalDir, packageRoot)) {
|
|
118
|
+
return 'yarn-global';
|
|
119
|
+
}
|
|
120
|
+
if (isPnpmGlobalInstallPath({
|
|
121
|
+
packageName: options.packageName ?? DEFAULT_PACKAGE_NAME,
|
|
122
|
+
packageRoot,
|
|
123
|
+
pnpmGlobalBin: options.pnpmGlobalBin,
|
|
124
|
+
pnpmGlobalRoot: options.pnpmGlobalRoot,
|
|
125
|
+
})) {
|
|
126
|
+
return 'pnpm-global';
|
|
127
|
+
}
|
|
128
|
+
// Best-effort fallback for environments where pnpm probes are unavailable.
|
|
129
|
+
if (isPnpmGlobalPath(packageRoot)) {
|
|
130
|
+
return 'pnpm-global';
|
|
131
|
+
}
|
|
116
132
|
if (packageRoot.includes(`${path.sep}node_modules${path.sep}`)) {
|
|
117
133
|
return 'package-local';
|
|
118
134
|
}
|
|
119
135
|
return 'unknown';
|
|
120
136
|
}
|
|
121
|
-
function
|
|
122
|
-
|
|
137
|
+
function isPnpmGlobalPath(packageRoot) {
|
|
138
|
+
const normalized = normalizePath(packageRoot);
|
|
139
|
+
const segments = normalized.split(path.sep).filter(Boolean);
|
|
140
|
+
const globalIndex = segments.findIndex((segment, index) => segment === 'global' && segments[index - 1] === 'pnpm');
|
|
141
|
+
if (globalIndex === -1) {
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
return segments.slice(globalIndex + 2).includes('node_modules');
|
|
123
145
|
}
|
|
124
|
-
function
|
|
125
|
-
|
|
146
|
+
function normalizePnpmGlobalNodeModulesRoot(pnpmGlobalRoot) {
|
|
147
|
+
const normalizedGlobalRoot = normalizePath(pnpmGlobalRoot);
|
|
148
|
+
return path.basename(normalizedGlobalRoot) === 'node_modules'
|
|
149
|
+
? normalizedGlobalRoot
|
|
150
|
+
: path.join(normalizedGlobalRoot, 'node_modules');
|
|
126
151
|
}
|
|
127
|
-
|
|
152
|
+
function readSiblingPnpmGlobalNodeModulesRoots(pnpmGlobalRoot) {
|
|
153
|
+
const globalNodeModulesDir = normalizePnpmGlobalNodeModulesRoot(pnpmGlobalRoot);
|
|
154
|
+
const globalProjectDir = path.dirname(globalNodeModulesDir);
|
|
155
|
+
let entries;
|
|
128
156
|
try {
|
|
129
|
-
|
|
130
|
-
return JSON.parse(raw);
|
|
157
|
+
entries = fs.readdirSync(globalProjectDir);
|
|
131
158
|
}
|
|
132
159
|
catch {
|
|
133
|
-
return
|
|
160
|
+
return [];
|
|
134
161
|
}
|
|
162
|
+
return entries
|
|
163
|
+
.filter((entry) => {
|
|
164
|
+
if (entry === path.basename(globalNodeModulesDir)) {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
return fs.statSync(path.join(globalProjectDir, entry)).isDirectory();
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
.map((entry) => path.join(globalProjectDir, entry, 'node_modules'));
|
|
135
175
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
await fsp.mkdir(path.dirname(filePath), { recursive: true });
|
|
139
|
-
await fsp.writeFile(filePath, JSON.stringify(state, null, 2));
|
|
176
|
+
function readPnpmGlobalNodeModulesRoots(pnpmGlobalRoot) {
|
|
177
|
+
return [normalizePnpmGlobalNodeModulesRoot(pnpmGlobalRoot), ...readSiblingPnpmGlobalNodeModulesRoots(pnpmGlobalRoot)];
|
|
140
178
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
return state.entries?.[getInstallMethodCacheKey(packageRoot)];
|
|
179
|
+
function packageNameToPath(packageName) {
|
|
180
|
+
return packageName.split('/').filter(Boolean);
|
|
144
181
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
182
|
+
function isSameRealPath(left, right) {
|
|
183
|
+
try {
|
|
184
|
+
return normalizePath(fs.realpathSync(left)) === normalizePath(fs.realpathSync(right));
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
return false;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
function isPnpmGlobalRootPath(pnpmGlobalRoot, packageRoot) {
|
|
191
|
+
if (isSubPath(pnpmGlobalRoot, packageRoot)) {
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
const globalNodeModulesDir = normalizePnpmGlobalNodeModulesRoot(pnpmGlobalRoot);
|
|
195
|
+
const globalProjectDir = path.dirname(globalNodeModulesDir);
|
|
196
|
+
if (!isSubPath(globalProjectDir, packageRoot)) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
const segments = normalizePath(packageRoot).split(path.sep).filter(Boolean);
|
|
200
|
+
const pnpmStoreIndex = segments.indexOf('.pnpm');
|
|
201
|
+
if (pnpmStoreIndex === -1) {
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
return segments.slice(pnpmStoreIndex + 1).includes('node_modules');
|
|
205
|
+
}
|
|
206
|
+
function isPnpmGlobalPackagePath(pnpmGlobalRoot, packageRoot, packageName) {
|
|
207
|
+
return readPnpmGlobalNodeModulesRoots(pnpmGlobalRoot).some((pnpmGlobalNodeModulesRoot) => isSameRealPath(path.join(pnpmGlobalNodeModulesRoot, ...packageNameToPath(packageName)), packageRoot));
|
|
208
|
+
}
|
|
209
|
+
function isPnpmGlobalBinProjectPath(pnpmGlobalBin, packageRoot, packageName) {
|
|
210
|
+
const pnpmHome = path.dirname(normalizePath(pnpmGlobalBin));
|
|
211
|
+
const globalDir = path.join(pnpmHome, 'global');
|
|
212
|
+
if (isSubPath(globalDir, packageRoot)) {
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
let entries;
|
|
216
|
+
try {
|
|
217
|
+
entries = fs.readdirSync(globalDir);
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
return entries.some((entry) => {
|
|
223
|
+
const pnpmGlobalRoot = path.join(globalDir, entry);
|
|
224
|
+
return (isPnpmGlobalRootPath(pnpmGlobalRoot, packageRoot)
|
|
225
|
+
|| isPnpmGlobalPackagePath(pnpmGlobalRoot, packageRoot, packageName));
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
function isPnpmGlobalInstallPath(options) {
|
|
229
|
+
if (options.pnpmGlobalRoot) {
|
|
230
|
+
const matchedRoot = isPnpmGlobalRootPath(options.pnpmGlobalRoot, options.packageRoot)
|
|
231
|
+
|| isPnpmGlobalPackagePath(options.pnpmGlobalRoot, options.packageRoot, options.packageName);
|
|
232
|
+
if (matchedRoot) {
|
|
233
|
+
return true;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (options.pnpmGlobalBin) {
|
|
237
|
+
return isPnpmGlobalBinProjectPath(options.pnpmGlobalBin, options.packageRoot, options.packageName);
|
|
238
|
+
}
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
function readCurrentBinPath(currentBinPath) {
|
|
242
|
+
const candidate = String(currentBinPath ?? process.argv[1] ?? '').trim();
|
|
243
|
+
if (!candidate) {
|
|
244
|
+
return undefined;
|
|
245
|
+
}
|
|
246
|
+
return normalizePath(candidate);
|
|
247
|
+
}
|
|
248
|
+
function cleanCommandPath(value) {
|
|
249
|
+
const trimmed = value.trim();
|
|
250
|
+
return trimmed ? trimmed : undefined;
|
|
251
|
+
}
|
|
252
|
+
function resolvePackageManagerProbeCwd() {
|
|
253
|
+
const candidates = [os.homedir(), os.tmpdir(), path.parse(process.cwd()).root];
|
|
254
|
+
for (const candidate of candidates) {
|
|
255
|
+
if (!candidate) {
|
|
256
|
+
continue;
|
|
257
|
+
}
|
|
258
|
+
try {
|
|
259
|
+
if (fs.statSync(candidate).isDirectory()) {
|
|
260
|
+
return candidate;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
catch {
|
|
264
|
+
// Ignore invalid probe cwd candidates and continue to the next one.
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return undefined;
|
|
152
268
|
}
|
|
153
269
|
async function readGlobalPrefix(commandOutputFn) {
|
|
154
270
|
try {
|
|
155
|
-
return (await commandOutputFn('npm', ['prefix', '-g'], {
|
|
271
|
+
return cleanCommandPath(await commandOutputFn('npm', ['prefix', '-g'], {
|
|
272
|
+
cwd: resolvePackageManagerProbeCwd(),
|
|
156
273
|
errorName: 'npm prefix',
|
|
157
|
-
}))
|
|
274
|
+
}));
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
return undefined;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
async function readPnpmGlobalBin(commandOutputFn) {
|
|
281
|
+
try {
|
|
282
|
+
return cleanCommandPath(await commandOutputFn('pnpm', ['bin', '-g'], {
|
|
283
|
+
cwd: resolvePackageManagerProbeCwd(),
|
|
284
|
+
errorName: 'pnpm bin',
|
|
285
|
+
}));
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
return undefined;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
async function readPnpmGlobalRoot(commandOutputFn) {
|
|
292
|
+
try {
|
|
293
|
+
return cleanCommandPath(await commandOutputFn('pnpm', ['root', '-g'], {
|
|
294
|
+
cwd: resolvePackageManagerProbeCwd(),
|
|
295
|
+
errorName: 'pnpm root',
|
|
296
|
+
}));
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
async function readYarnGlobalDir(commandOutputFn) {
|
|
303
|
+
try {
|
|
304
|
+
return cleanCommandPath(await commandOutputFn('yarn', ['global', 'dir'], {
|
|
305
|
+
cwd: resolvePackageManagerProbeCwd(),
|
|
306
|
+
errorName: 'yarn global dir',
|
|
307
|
+
}));
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
return undefined;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
async function readYarnGlobalBin(commandOutputFn) {
|
|
314
|
+
try {
|
|
315
|
+
return cleanCommandPath(await commandOutputFn('yarn', ['global', 'bin'], {
|
|
316
|
+
cwd: resolvePackageManagerProbeCwd(),
|
|
317
|
+
errorName: 'yarn global bin',
|
|
318
|
+
}));
|
|
158
319
|
}
|
|
159
320
|
catch {
|
|
160
321
|
return undefined;
|
|
@@ -171,21 +332,21 @@ function getUnsupportedSelfUpdateReason(installMethod) {
|
|
|
171
332
|
if (installMethod === 'source') {
|
|
172
333
|
return [
|
|
173
334
|
'This CLI is running from source in a repository checkout.',
|
|
174
|
-
'Automatic self-update is only supported for standard global npm installs.',
|
|
335
|
+
'Automatic self-update is only supported for standard global npm, pnpm, or yarn installs.',
|
|
175
336
|
'Upgrade this checkout through your repo workflow instead.',
|
|
176
337
|
].join(' ');
|
|
177
338
|
}
|
|
178
339
|
if (installMethod === 'package-local') {
|
|
179
340
|
return [
|
|
180
341
|
'This CLI is installed from a local project dependency tree.',
|
|
181
|
-
'Automatic self-update is only supported for standard global npm installs.',
|
|
342
|
+
'Automatic self-update is only supported for standard global npm, pnpm, or yarn installs.',
|
|
182
343
|
'Upgrade the parent project dependency that provides this CLI instead.',
|
|
183
344
|
].join(' ');
|
|
184
345
|
}
|
|
185
346
|
if (installMethod === 'unknown') {
|
|
186
347
|
return [
|
|
187
|
-
'This CLI install could not be recognized as a standard global npm install.',
|
|
188
|
-
'Automatic self-update is only supported for standard global npm installs.',
|
|
348
|
+
'This CLI install could not be recognized as a standard global npm, pnpm, or yarn install.',
|
|
349
|
+
'Automatic self-update is only supported for standard global npm, pnpm, or yarn installs.',
|
|
189
350
|
].join(' ');
|
|
190
351
|
}
|
|
191
352
|
return undefined;
|
|
@@ -214,22 +375,45 @@ export function getSelfUpdatePackageSpec(status) {
|
|
|
214
375
|
}
|
|
215
376
|
export async function inspectSelfInstall(options = {}) {
|
|
216
377
|
const packageRoot = options.packageRoot ? normalizePath(options.packageRoot) : PACKAGE_ROOT;
|
|
378
|
+
const packageName = options.packageName ?? DEFAULT_PACKAGE_NAME;
|
|
217
379
|
const commandOutputFn = options.commandOutputFn ?? commandOutput;
|
|
218
|
-
const
|
|
219
|
-
const globalPrefix =
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
380
|
+
const currentBinPath = readCurrentBinPath(options.currentBinPath);
|
|
381
|
+
const globalPrefix = await readGlobalPrefix(commandOutputFn);
|
|
382
|
+
const pnpmGlobalBin = await readPnpmGlobalBin(commandOutputFn);
|
|
383
|
+
const pnpmGlobalRoot = await readPnpmGlobalRoot(commandOutputFn);
|
|
384
|
+
const yarnGlobalDir = await readYarnGlobalDir(commandOutputFn);
|
|
385
|
+
const yarnGlobalBin = await readYarnGlobalBin(commandOutputFn);
|
|
386
|
+
const installMethod = detectInstallMethodFromSignals({
|
|
387
|
+
packageRoot,
|
|
388
|
+
currentBinPath,
|
|
389
|
+
globalPrefix,
|
|
390
|
+
packageName,
|
|
391
|
+
pnpmGlobalBin,
|
|
392
|
+
pnpmGlobalRoot,
|
|
393
|
+
yarnGlobalBin,
|
|
394
|
+
yarnGlobalDir,
|
|
395
|
+
});
|
|
227
396
|
return {
|
|
228
397
|
packageRoot,
|
|
229
398
|
installMethod,
|
|
230
399
|
globalPrefix,
|
|
231
400
|
};
|
|
232
401
|
}
|
|
402
|
+
function detectInstallMethodFromSignals(options) {
|
|
403
|
+
if (options.currentBinPath && options.yarnGlobalBin && isSubPath(options.yarnGlobalBin, options.currentBinPath)) {
|
|
404
|
+
return 'yarn-global';
|
|
405
|
+
}
|
|
406
|
+
if (options.currentBinPath && options.pnpmGlobalBin && isSubPath(options.pnpmGlobalBin, options.currentBinPath)) {
|
|
407
|
+
return 'pnpm-global';
|
|
408
|
+
}
|
|
409
|
+
return detectInstallMethod(options.packageRoot, {
|
|
410
|
+
globalPrefix: options.globalPrefix,
|
|
411
|
+
packageName: options.packageName,
|
|
412
|
+
pnpmGlobalBin: options.pnpmGlobalBin,
|
|
413
|
+
pnpmGlobalRoot: options.pnpmGlobalRoot,
|
|
414
|
+
yarnGlobalDir: options.yarnGlobalDir,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
233
417
|
export async function inspectSelfStatus(options = {}) {
|
|
234
418
|
const packageRoot = options.packageRoot ? normalizePath(options.packageRoot) : PACKAGE_ROOT;
|
|
235
419
|
const packageName = options.packageName ?? DEFAULT_PACKAGE_NAME;
|
|
@@ -237,6 +421,8 @@ export async function inspectSelfStatus(options = {}) {
|
|
|
237
421
|
const channel = options.channel && options.channel !== 'auto' ? options.channel : detectChannel(currentVersion);
|
|
238
422
|
const commandOutputFn = options.commandOutputFn ?? commandOutput;
|
|
239
423
|
const { installMethod, globalPrefix } = await inspectSelfInstall({
|
|
424
|
+
currentBinPath: options.currentBinPath,
|
|
425
|
+
packageName,
|
|
240
426
|
packageRoot,
|
|
241
427
|
commandOutputFn,
|
|
242
428
|
});
|
|
@@ -258,7 +444,7 @@ export async function inspectSelfStatus(options = {}) {
|
|
|
258
444
|
latestVersion,
|
|
259
445
|
updateAvailable,
|
|
260
446
|
installMethod,
|
|
261
|
-
updatable: installMethod === 'npm-global',
|
|
447
|
+
updatable: installMethod === 'npm-global' || installMethod === 'pnpm-global' || installMethod === 'yarn-global',
|
|
262
448
|
updateBlockedReason: getUnsupportedSelfUpdateReason(installMethod),
|
|
263
449
|
globalPrefix,
|
|
264
450
|
registryError,
|
|
@@ -267,9 +453,30 @@ export async function inspectSelfStatus(options = {}) {
|
|
|
267
453
|
export function formatUnsupportedSelfUpdateMessage(status) {
|
|
268
454
|
return status.updateBlockedReason
|
|
269
455
|
?? [
|
|
270
|
-
'Automatic self-update is only supported for standard global npm installs.',
|
|
456
|
+
'Automatic self-update is only supported for standard global npm, pnpm, or yarn installs.',
|
|
271
457
|
].join('\n');
|
|
272
458
|
}
|
|
459
|
+
function resolveSelfUpdateInstallCommand(installMethod, packageSpec) {
|
|
460
|
+
if (installMethod === 'pnpm-global') {
|
|
461
|
+
return {
|
|
462
|
+
command: 'pnpm',
|
|
463
|
+
args: ['add', '-g', packageSpec],
|
|
464
|
+
errorName: 'pnpm add',
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
if (installMethod === 'yarn-global') {
|
|
468
|
+
return {
|
|
469
|
+
command: 'yarn',
|
|
470
|
+
args: ['global', 'add', packageSpec],
|
|
471
|
+
errorName: 'yarn global add',
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
return {
|
|
475
|
+
command: 'npm',
|
|
476
|
+
args: ['install', '-g', packageSpec],
|
|
477
|
+
errorName: 'npm install',
|
|
478
|
+
};
|
|
479
|
+
}
|
|
273
480
|
export async function updateSelf(options = {}) {
|
|
274
481
|
const status = await inspectSelfStatus(options);
|
|
275
482
|
if (!status.updatable) {
|
|
@@ -288,9 +495,10 @@ export async function updateSelf(options = {}) {
|
|
|
288
495
|
};
|
|
289
496
|
}
|
|
290
497
|
const packageSpec = getSelfUpdatePackageSpec(status);
|
|
291
|
-
|
|
498
|
+
const installCommand = resolveSelfUpdateInstallCommand(status.installMethod, packageSpec);
|
|
499
|
+
await (options.runFn ?? run)(installCommand.command, installCommand.args, {
|
|
292
500
|
stdio: options.verbose ? 'inherit' : 'ignore',
|
|
293
|
-
errorName:
|
|
501
|
+
errorName: installCommand.errorName,
|
|
294
502
|
});
|
|
295
503
|
return {
|
|
296
504
|
action: 'updated',
|
|
@@ -21,8 +21,8 @@ const NOCOBASE_SKILLS_NAME_PREFIX = 'nocobase-';
|
|
|
21
21
|
// resolves and boots the package, even when the local skills installation is healthy.
|
|
22
22
|
const SKILLS_LIST_TIMEOUT_MS = 15000;
|
|
23
23
|
const SKILLS_NPM_VIEW_TIMEOUT_MS = 3000;
|
|
24
|
-
const SKILLS_PACK_TIMEOUT_MS =
|
|
25
|
-
const SKILLS_ADD_TIMEOUT_MS =
|
|
24
|
+
const SKILLS_PACK_TIMEOUT_MS = 120000;
|
|
25
|
+
const SKILLS_ADD_TIMEOUT_MS = 120000;
|
|
26
26
|
const NPM_REGISTRY_UNAVAILABLE_PATTERNS = [
|
|
27
27
|
'enotfound',
|
|
28
28
|
'eai_again',
|
|
@@ -154,10 +154,13 @@ export async function listGlobalSkills(options = {}) {
|
|
|
154
154
|
export async function listProjectSkills(options = {}) {
|
|
155
155
|
return await listGlobalSkills(options);
|
|
156
156
|
}
|
|
157
|
-
function pickInstalledNocoBaseSkillNames(installedSkills, state) {
|
|
157
|
+
function pickInstalledNocoBaseSkillNames(installedSkills, state, sourceSkillNames = []) {
|
|
158
158
|
const installedNames = new Set(installedSkills.map((skill) => String(skill.name ?? '').trim()).filter(Boolean));
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
const managedNames = new Set([...sourceSkillNames, ...(state?.skillNames ?? [])]);
|
|
160
|
+
if (managedNames.size > 0) {
|
|
161
|
+
return Array.from(managedNames)
|
|
162
|
+
.filter((name) => installedNames.has(name))
|
|
163
|
+
.sort();
|
|
161
164
|
}
|
|
162
165
|
return Array.from(installedNames)
|
|
163
166
|
.filter((name) => name.startsWith(NOCOBASE_SKILLS_NAME_PREFIX))
|
|
@@ -194,6 +197,31 @@ async function readCachedSkillsVersion(cacheRoot) {
|
|
|
194
197
|
return undefined;
|
|
195
198
|
}
|
|
196
199
|
}
|
|
200
|
+
async function readCachedPackageSkillNames(globalRoot) {
|
|
201
|
+
const skillsDir = path.join(getCachedSkillsPackageDir(getSkillsCacheRoot(globalRoot)), 'skills');
|
|
202
|
+
try {
|
|
203
|
+
const entries = await fsp.readdir(skillsDir, { withFileTypes: true });
|
|
204
|
+
const skillNames = await Promise.all(entries
|
|
205
|
+
.filter((entry) => entry.isDirectory())
|
|
206
|
+
.map(async (entry) => {
|
|
207
|
+
const skillName = entry.name.trim();
|
|
208
|
+
if (!skillName) {
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
await fsp.access(path.join(skillsDir, skillName, 'SKILL.md'));
|
|
213
|
+
return skillName;
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
}));
|
|
219
|
+
return skillNames.filter((name) => Boolean(name)).sort();
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
return [];
|
|
223
|
+
}
|
|
224
|
+
}
|
|
197
225
|
async function resolvePackedSkillsTarball(packRoot) {
|
|
198
226
|
const entries = await fsp.readdir(packRoot, { withFileTypes: true });
|
|
199
227
|
const tarballs = entries
|
|
@@ -251,6 +279,7 @@ async function prepareLocalSkillsPackage(globalRoot, options = {}, targetVersion
|
|
|
251
279
|
const cachedVersion = await readCachedSkillsVersion(cacheRoot);
|
|
252
280
|
await fsp.mkdir(cacheRoot, { recursive: true });
|
|
253
281
|
if (targetVersion && cachedVersion && compareVersions(cachedVersion, targetVersion) === 0) {
|
|
282
|
+
options.onProgress?.(`Using cached ${NOCOBASE_SKILLS_PACKAGE_NAME}@${targetVersion}...`);
|
|
254
283
|
return {
|
|
255
284
|
packageDir,
|
|
256
285
|
cleanup: async () => undefined,
|
|
@@ -259,12 +288,14 @@ async function prepareLocalSkillsPackage(globalRoot, options = {}, targetVersion
|
|
|
259
288
|
await fsp.rm(packRoot, { recursive: true, force: true });
|
|
260
289
|
await fsp.mkdir(packRoot, { recursive: true });
|
|
261
290
|
try {
|
|
262
|
-
|
|
291
|
+
options.onProgress?.(`Downloading ${packageSpec}...`);
|
|
292
|
+
await (options.runFn ?? run)('npm', ['pack', ...(options.verbose ? [] : ['--silent']), packageSpec], {
|
|
263
293
|
cwd: packRoot,
|
|
264
294
|
stdio: options.verbose ? 'inherit' : 'ignore',
|
|
265
295
|
errorName: 'npm pack',
|
|
266
296
|
timeoutMs: SKILLS_PACK_TIMEOUT_MS,
|
|
267
297
|
});
|
|
298
|
+
options.onProgress?.(`Extracting ${NOCOBASE_SKILLS_PACKAGE_NAME}...`);
|
|
268
299
|
const tarballPath = await resolvePackedSkillsTarball(packRoot);
|
|
269
300
|
await extractPackedSkillsTarball(tarballPath, cacheRoot, targetVersion);
|
|
270
301
|
}
|
|
@@ -279,14 +310,16 @@ async function prepareLocalSkillsPackage(globalRoot, options = {}, targetVersion
|
|
|
279
310
|
export async function inspectSkillsStatus(options = {}) {
|
|
280
311
|
const globalRoot = resolveSkillsRoot(options);
|
|
281
312
|
const stateFile = getManagedSkillsStateFile(globalRoot);
|
|
282
|
-
const [installedSkills, managedState] = await Promise.all([
|
|
313
|
+
const [installedSkills, managedState, cachedSkillNames] = await Promise.all([
|
|
283
314
|
listGlobalSkills({
|
|
284
315
|
globalRoot,
|
|
285
316
|
commandOutputFn: options.commandOutputFn,
|
|
286
317
|
}),
|
|
287
318
|
readManagedSkillsState(globalRoot),
|
|
319
|
+
readCachedPackageSkillNames(globalRoot),
|
|
288
320
|
]);
|
|
289
|
-
const installedSkillNames = pickInstalledNocoBaseSkillNames(installedSkills, managedState);
|
|
321
|
+
const installedSkillNames = pickInstalledNocoBaseSkillNames(installedSkills, managedState, cachedSkillNames);
|
|
322
|
+
const packageSkillNames = cachedSkillNames;
|
|
290
323
|
const managedByNb = managedState?.packageName === NOCOBASE_SKILLS_PACKAGE_NAME;
|
|
291
324
|
let latestVersion;
|
|
292
325
|
let registryError;
|
|
@@ -312,6 +345,7 @@ export async function inspectSkillsStatus(options = {}) {
|
|
|
312
345
|
managedByNb,
|
|
313
346
|
sourcePackage: managedState?.sourcePackage ?? NOCOBASE_SKILLS_SOURCE,
|
|
314
347
|
npmPackageName: managedState?.packageName ?? NOCOBASE_SKILLS_PACKAGE_NAME,
|
|
348
|
+
packageSkillNames,
|
|
315
349
|
installedSkillNames,
|
|
316
350
|
latestVersion,
|
|
317
351
|
installedVersion,
|
|
@@ -321,13 +355,18 @@ export async function inspectSkillsStatus(options = {}) {
|
|
|
321
355
|
registryError,
|
|
322
356
|
};
|
|
323
357
|
}
|
|
324
|
-
async function persistManagedSkillsState(globalRoot, options = {}) {
|
|
325
|
-
const installedSkills = await
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
358
|
+
async function persistManagedSkillsState(globalRoot, options = {}, installedVersion) {
|
|
359
|
+
const [installedSkills, managedState, cachedSkillNames] = await Promise.all([
|
|
360
|
+
listGlobalSkills({
|
|
361
|
+
globalRoot,
|
|
362
|
+
commandOutputFn: options.commandOutputFn,
|
|
363
|
+
}),
|
|
364
|
+
readManagedSkillsState(globalRoot),
|
|
365
|
+
readCachedPackageSkillNames(globalRoot),
|
|
366
|
+
]);
|
|
367
|
+
const installedSkillNames = pickInstalledNocoBaseSkillNames(installedSkills, managedState, cachedSkillNames);
|
|
368
|
+
const packageSkillNames = cachedSkillNames.length ? cachedSkillNames : installedSkillNames;
|
|
369
|
+
const cachedVersion = await readCachedSkillsVersion(getSkillsCacheRoot(globalRoot));
|
|
331
370
|
const published = await readPublishedSkillsVersion({
|
|
332
371
|
globalRoot,
|
|
333
372
|
commandOutputFn: options.commandOutputFn,
|
|
@@ -338,8 +377,8 @@ async function persistManagedSkillsState(globalRoot, options = {}) {
|
|
|
338
377
|
sourcePackage: NOCOBASE_SKILLS_SOURCE,
|
|
339
378
|
installedAt: managedState?.installedAt ?? now,
|
|
340
379
|
updatedAt: now,
|
|
341
|
-
installedVersion: published.version,
|
|
342
|
-
skillNames:
|
|
380
|
+
installedVersion: installedVersion ?? cachedVersion ?? published.version,
|
|
381
|
+
skillNames: packageSkillNames,
|
|
343
382
|
});
|
|
344
383
|
return await inspectSkillsStatus({
|
|
345
384
|
globalRoot,
|
|
@@ -349,7 +388,8 @@ async function persistManagedSkillsState(globalRoot, options = {}) {
|
|
|
349
388
|
async function reinstallManagedSkills(globalRoot, options = {}, targetVersion) {
|
|
350
389
|
const prepared = await prepareLocalSkillsPackage(globalRoot, options, targetVersion);
|
|
351
390
|
try {
|
|
352
|
-
|
|
391
|
+
options.onProgress?.('Installing NocoBase AI coding skills globally...');
|
|
392
|
+
await (options.runFn ?? run)('npx', ['-y', 'skills', 'add', prepared.packageDir, '-g', '-y', '--skill', '*'], {
|
|
353
393
|
cwd: globalRoot,
|
|
354
394
|
stdio: options.verbose ? 'inherit' : 'ignore',
|
|
355
395
|
errorName: 'skills add',
|
|
@@ -360,31 +400,66 @@ async function reinstallManagedSkills(globalRoot, options = {}, targetVersion) {
|
|
|
360
400
|
await prepared.cleanup();
|
|
361
401
|
}
|
|
362
402
|
}
|
|
403
|
+
function pickObsoleteManagedSkillNames(installedSkillNames, packageSkillNames) {
|
|
404
|
+
if (!packageSkillNames.length) {
|
|
405
|
+
return [];
|
|
406
|
+
}
|
|
407
|
+
const packageSkillNameSet = new Set(packageSkillNames);
|
|
408
|
+
return installedSkillNames.filter((skillName) => !packageSkillNameSet.has(skillName)).sort();
|
|
409
|
+
}
|
|
410
|
+
async function removeObsoleteManagedSkills(globalRoot, installedSkillNames, options = {}) {
|
|
411
|
+
const packageSkillNames = await readCachedPackageSkillNames(globalRoot);
|
|
412
|
+
const obsoleteSkillNames = pickObsoleteManagedSkillNames(installedSkillNames, packageSkillNames);
|
|
413
|
+
for (const skillName of obsoleteSkillNames) {
|
|
414
|
+
options.onProgress?.(`Removing obsolete skill ${skillName}...`);
|
|
415
|
+
await (options.runFn ?? run)('npx', ['-y', 'skills', 'remove', skillName, '-g', '-y'], {
|
|
416
|
+
cwd: globalRoot,
|
|
417
|
+
stdio: options.verbose ? 'inherit' : 'ignore',
|
|
418
|
+
errorName: 'skills remove',
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
363
422
|
export async function installNocoBaseSkills(options = {}) {
|
|
364
423
|
const globalRoot = resolveSkillsRoot(options);
|
|
424
|
+
options.onProgress?.('Checking installed NocoBase AI coding skills...');
|
|
365
425
|
const status = await inspectSkillsStatus({
|
|
366
426
|
globalRoot,
|
|
367
427
|
commandOutputFn: options.commandOutputFn,
|
|
368
428
|
});
|
|
369
|
-
|
|
429
|
+
const cachedSkillNames = await readCachedPackageSkillNames(globalRoot);
|
|
430
|
+
const missingCachedSkillNames = cachedSkillNames.filter((name) => !status.installedSkillNames.includes(name));
|
|
431
|
+
const obsoleteSkillNames = pickObsoleteManagedSkillNames(status.installedSkillNames, cachedSkillNames);
|
|
432
|
+
const targetVersion = String(options.targetVersion ?? '').trim() || undefined;
|
|
433
|
+
const targetVersionMatches = !targetVersion || status.installedVersion === targetVersion;
|
|
434
|
+
if (status.installed && targetVersionMatches && missingCachedSkillNames.length === 0 && obsoleteSkillNames.length === 0) {
|
|
370
435
|
return {
|
|
371
436
|
action: 'noop',
|
|
372
437
|
status,
|
|
373
438
|
};
|
|
374
439
|
}
|
|
375
440
|
await ensureSkillsWorkspaceRoot(globalRoot);
|
|
376
|
-
|
|
441
|
+
if (!status.installed || !targetVersionMatches || missingCachedSkillNames.length > 0) {
|
|
442
|
+
const installVersion = targetVersion ?? status.latestVersion;
|
|
443
|
+
await reinstallManagedSkills(globalRoot, options, installVersion);
|
|
444
|
+
}
|
|
445
|
+
await removeObsoleteManagedSkills(globalRoot, status.installedSkillNames, options);
|
|
446
|
+
options.onProgress?.('Verifying installed NocoBase AI coding skills...');
|
|
377
447
|
return {
|
|
378
448
|
action: 'installed',
|
|
379
|
-
status: await persistManagedSkillsState(globalRoot, options),
|
|
449
|
+
status: await persistManagedSkillsState(globalRoot, options, targetVersion),
|
|
380
450
|
};
|
|
381
451
|
}
|
|
382
452
|
export async function updateNocoBaseSkills(options = {}) {
|
|
383
453
|
const globalRoot = resolveSkillsRoot(options);
|
|
454
|
+
options.onProgress?.('Checking installed NocoBase AI coding skills...');
|
|
384
455
|
const status = await inspectSkillsStatus({
|
|
385
456
|
globalRoot,
|
|
386
457
|
commandOutputFn: options.commandOutputFn,
|
|
387
458
|
});
|
|
459
|
+
const cachedSkillNames = await readCachedPackageSkillNames(globalRoot);
|
|
460
|
+
const missingCachedSkillNames = cachedSkillNames.filter((name) => !status.installedSkillNames.includes(name));
|
|
461
|
+
const obsoleteSkillNames = pickObsoleteManagedSkillNames(status.installedSkillNames, cachedSkillNames);
|
|
462
|
+
const targetVersion = String(options.targetVersion ?? '').trim() || undefined;
|
|
388
463
|
if (!status.installed) {
|
|
389
464
|
return {
|
|
390
465
|
action: 'noop',
|
|
@@ -393,8 +468,11 @@ export async function updateNocoBaseSkills(options = {}) {
|
|
|
393
468
|
};
|
|
394
469
|
}
|
|
395
470
|
if (status.managedByNb &&
|
|
471
|
+
!targetVersion &&
|
|
396
472
|
status.latestVersion &&
|
|
397
473
|
status.installedVersion &&
|
|
474
|
+
missingCachedSkillNames.length === 0 &&
|
|
475
|
+
obsoleteSkillNames.length === 0 &&
|
|
398
476
|
compareVersions(status.latestVersion, status.installedVersion) <= 0) {
|
|
399
477
|
return {
|
|
400
478
|
action: 'noop',
|
|
@@ -402,10 +480,25 @@ export async function updateNocoBaseSkills(options = {}) {
|
|
|
402
480
|
status,
|
|
403
481
|
};
|
|
404
482
|
}
|
|
405
|
-
|
|
483
|
+
if (targetVersion &&
|
|
484
|
+
status.installedVersion === targetVersion &&
|
|
485
|
+
missingCachedSkillNames.length === 0 &&
|
|
486
|
+
obsoleteSkillNames.length === 0) {
|
|
487
|
+
return {
|
|
488
|
+
action: 'noop',
|
|
489
|
+
reason: 'up-to-date',
|
|
490
|
+
status,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
if (!targetVersion || status.installedVersion !== targetVersion || missingCachedSkillNames.length > 0) {
|
|
494
|
+
const installVersion = targetVersion ?? status.latestVersion;
|
|
495
|
+
await reinstallManagedSkills(globalRoot, options, installVersion);
|
|
496
|
+
}
|
|
497
|
+
await removeObsoleteManagedSkills(globalRoot, status.installedSkillNames, options);
|
|
498
|
+
options.onProgress?.('Verifying installed NocoBase AI coding skills...');
|
|
406
499
|
return {
|
|
407
500
|
action: 'updated',
|
|
408
|
-
status: await persistManagedSkillsState(globalRoot, options),
|
|
501
|
+
status: await persistManagedSkillsState(globalRoot, options, targetVersion),
|
|
409
502
|
};
|
|
410
503
|
}
|
|
411
504
|
export async function removeNocoBaseSkills(options = {}) {
|