@nocobase/cli 2.1.11 → 2.1.12

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.
@@ -26,7 +26,7 @@ function formatSkillsUpdateMessage(result, verbose) {
26
26
  }
27
27
  export default class SelfUpdate extends Command {
28
28
  static summary = 'Update the globally installed NocoBase CLI';
29
- static description = 'Update the current NocoBase CLI install when it is managed by a standard global npm install.';
29
+ static description = 'Update the current NocoBase CLI install when it is managed by a standard global npm, pnpm, or yarn install.';
30
30
  static examples = [
31
31
  '<%= config.bin %> <%= command.id %>',
32
32
  '<%= config.bin %> <%= command.id %> --yes',
@@ -77,7 +77,7 @@ export default class SelfUpdate extends Command {
77
77
  message: flags.skills
78
78
  ? `Update ${status.packageName} from ${status.currentVersion} to ${status.latestVersion} and refresh the globally installed NocoBase AI coding skills?`
79
79
  : `Update ${status.packageName} from ${status.currentVersion} to ${status.latestVersion}?`,
80
- default: false,
80
+ default: true,
81
81
  });
82
82
  }
83
83
  catch {
@@ -99,6 +99,17 @@ function normalizeAuthConfig(config) {
99
99
  ? settings.log.retentionDays
100
100
  : undefined;
101
101
  const logEnabled = typeof settings.log?.enabled === 'boolean' ? settings.log.enabled : undefined;
102
+ const hasBinSettings = settings.bin?.docker ||
103
+ settings.bin?.caddy ||
104
+ settings.bin?.git ||
105
+ settings.bin?.nginx ||
106
+ settings.bin?.pnpm ||
107
+ settings.bin?.yarn;
108
+ const hasProxySettings = settings.proxy?.nbCliRoot ||
109
+ settings.proxy?.caddyDriver ||
110
+ settings.proxy?.nginxDriver ||
111
+ settings.proxy?.upstreamHost ||
112
+ settings.proxy?.host;
102
113
  return {
103
114
  name: config.name || config.dockerResourcePrefix,
104
115
  settings: {
@@ -123,22 +134,19 @@ function normalizeAuthConfig(config) {
123
134
  },
124
135
  }
125
136
  : {}),
126
- ...(settings.bin?.docker || settings.bin?.caddy || settings.bin?.git || settings.bin?.nginx || settings.bin?.yarn
137
+ ...(hasBinSettings
127
138
  ? {
128
139
  bin: {
129
140
  ...(settings.bin?.docker ? { docker: normalizeOptionalString(settings.bin.docker) } : {}),
130
141
  ...(settings.bin?.caddy ? { caddy: normalizeOptionalString(settings.bin.caddy) } : {}),
131
142
  ...(settings.bin?.git ? { git: normalizeOptionalString(settings.bin.git) } : {}),
132
143
  ...(settings.bin?.nginx ? { nginx: normalizeOptionalString(settings.bin.nginx) } : {}),
144
+ ...(settings.bin?.pnpm ? { pnpm: normalizeOptionalString(settings.bin.pnpm) } : {}),
133
145
  ...(settings.bin?.yarn ? { yarn: normalizeOptionalString(settings.bin.yarn) } : {}),
134
146
  },
135
147
  }
136
148
  : {}),
137
- ...(settings.proxy?.nbCliRoot ||
138
- settings.proxy?.caddyDriver ||
139
- settings.proxy?.nginxDriver ||
140
- settings.proxy?.upstreamHost ||
141
- settings.proxy?.host
149
+ ...(hasProxySettings
142
150
  ? {
143
151
  proxy: {
144
152
  ...(settings.proxy?.nbCliRoot ? { nbCliRoot: normalizeOptionalString(settings.proxy.nbCliRoot) } : {}),
@@ -16,6 +16,7 @@ export const DEFAULT_DOCKER_BIN = 'docker';
16
16
  export const DEFAULT_CADDY_BIN = 'caddy';
17
17
  export const DEFAULT_GIT_BIN = 'git';
18
18
  export const DEFAULT_NGINX_BIN = 'nginx';
19
+ export const DEFAULT_PNPM_BIN = 'pnpm';
19
20
  export const PROXY_PROVIDER_OPTIONS = ['nginx', 'caddy'];
20
21
  export const DEFAULT_PROXY_PROVIDER = 'nginx';
21
22
  export const NGINX_PROXY_DRIVER_OPTIONS = ['local', 'docker'];
@@ -40,6 +41,7 @@ export const SUPPORTED_CLI_CONFIG_KEYS = [
40
41
  'bin.caddy',
41
42
  'bin.git',
42
43
  'bin.nginx',
44
+ 'bin.pnpm',
43
45
  'proxy.nb-cli-root',
44
46
  'proxy.caddy-driver',
45
47
  'proxy.nginx-driver',
@@ -136,6 +138,7 @@ function pruneSettings(config) {
136
138
  !trimValue(bin.caddy) &&
137
139
  !trimValue(bin.git) &&
138
140
  !trimValue(bin.nginx) &&
141
+ !trimValue(bin.pnpm) &&
139
142
  !trimValue(bin.yarn)) {
140
143
  delete config.settings?.bin;
141
144
  }
@@ -187,6 +190,8 @@ export function getExplicitCliConfigValue(config, key) {
187
190
  return trimValue(config.settings?.bin?.git);
188
191
  case 'bin.nginx':
189
192
  return trimValue(config.settings?.bin?.nginx);
193
+ case 'bin.pnpm':
194
+ return trimValue(config.settings?.bin?.pnpm);
190
195
  case 'proxy.nb-cli-root':
191
196
  return trimValue(config.settings?.proxy?.nbCliRoot);
192
197
  case 'proxy.caddy-driver':
@@ -233,6 +238,8 @@ export function getEffectiveCliConfigValue(config, key) {
233
238
  return DEFAULT_GIT_BIN;
234
239
  case 'bin.nginx':
235
240
  return DEFAULT_NGINX_BIN;
241
+ case 'bin.pnpm':
242
+ return DEFAULT_PNPM_BIN;
236
243
  case 'proxy.nb-cli-root':
237
244
  return explicit ?? resolveCliHomeRoot();
238
245
  case 'proxy.caddy-driver':
@@ -387,6 +394,12 @@ export async function setCliConfigValue(key, value, options = {}) {
387
394
  nginx: normalized,
388
395
  };
389
396
  break;
397
+ case 'bin.pnpm':
398
+ config.settings.bin = {
399
+ ...(config.settings.bin ?? {}),
400
+ pnpm: normalized,
401
+ };
402
+ break;
390
403
  case 'proxy.nb-cli-root':
391
404
  config.settings.proxy = {
392
405
  ...(config.settings.proxy ?? {}),
@@ -496,6 +509,11 @@ export async function deleteCliConfigValue(key, options = {}) {
496
509
  delete config.settings.bin.nginx;
497
510
  }
498
511
  break;
512
+ case 'bin.pnpm':
513
+ if (config.settings.bin) {
514
+ delete config.settings.bin.pnpm;
515
+ }
516
+ break;
499
517
  case 'proxy.nb-cli-root':
500
518
  if (config.settings.proxy) {
501
519
  delete config.settings.proxy.nbCliRoot;
@@ -556,6 +574,7 @@ const CONFIGURABLE_COMMAND_KEYS = {
556
574
  caddy: 'bin.caddy',
557
575
  git: 'bin.git',
558
576
  nginx: 'bin.nginx',
577
+ pnpm: 'bin.pnpm',
559
578
  yarn: 'bin.yarn',
560
579
  };
561
580
  export function isConfigurableCommandName(value) {
@@ -10,7 +10,7 @@ import { spawn } from 'node:child_process';
10
10
  import net from 'node:net';
11
11
  import { translateCli } from "./cli-locale.js";
12
12
  const API_BASE_URL_EXAMPLE = 'http://localhost:13000/api';
13
- const ENV_KEY_PATTERN = /^[A-Za-z0-9]+$/;
13
+ const ENV_KEY_PATTERN = /^[A-Za-z0-9_-]+$/;
14
14
  const APP_PUBLIC_PATH_PATTERN = /^\/(?:[A-Za-z0-9_-]+(?:\/[A-Za-z0-9_-]+)*)?\/?$/;
15
15
  const TCP_PORT_EXAMPLE = '13000';
16
16
  const API_BASE_URL_REQUEST_TIMEOUT_MS = 5_000;
@@ -44,6 +44,10 @@ const MISSING_COMMAND_SPECS = {
44
44
  displayName: 'Yarn',
45
45
  configKey: 'bin.yarn',
46
46
  },
47
+ pnpm: {
48
+ displayName: 'pnpm',
49
+ configKey: 'bin.pnpm',
50
+ },
47
51
  };
48
52
  const DOCKER_DAEMON_UNAVAILABLE_PATTERNS = [
49
53
  /cannot connect to the docker daemon/i,
@@ -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 fsp from 'node:fs/promises';
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
  }
@@ -108,56 +106,216 @@ function readCurrentVersion(packageRoot) {
108
106
  const pkg = JSON.parse(content);
109
107
  return String(pkg.version ?? '').trim();
110
108
  }
111
- function detectInstallMethod(packageRoot, globalPrefix) {
109
+ function detectInstallMethod(packageRoot, options = {}) {
112
110
  if (fs.existsSync(path.join(packageRoot, 'src'))
113
111
  && fs.existsSync(path.join(packageRoot, 'tsconfig.json'))) {
114
112
  return 'source';
115
113
  }
116
- if (globalPrefix && isSubPath(globalPrefix, packageRoot)) {
114
+ if (options.globalPrefix && isSubPath(options.globalPrefix, packageRoot)) {
117
115
  return 'npm-global';
118
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
+ }
119
132
  if (packageRoot.includes(`${path.sep}node_modules${path.sep}`)) {
120
133
  return 'package-local';
121
134
  }
122
135
  return 'unknown';
123
136
  }
124
- function getInstallMethodCacheFile() {
125
- return path.join(resolveCliHomeDir('global'), INSTALL_METHOD_CACHE_FILE);
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');
126
145
  }
127
- function getInstallMethodCacheKey(packageRoot) {
128
- return normalizePath(path.join(packageRoot, 'bin', 'run.js'));
146
+ function normalizePnpmGlobalNodeModulesRoot(pnpmGlobalRoot) {
147
+ const normalizedGlobalRoot = normalizePath(pnpmGlobalRoot);
148
+ return path.basename(normalizedGlobalRoot) === 'node_modules'
149
+ ? normalizedGlobalRoot
150
+ : path.join(normalizedGlobalRoot, 'node_modules');
129
151
  }
130
- async function readInstallMethodCache() {
152
+ function readSiblingPnpmGlobalNodeModulesRoots(pnpmGlobalRoot) {
153
+ const globalNodeModulesDir = normalizePnpmGlobalNodeModulesRoot(pnpmGlobalRoot);
154
+ const globalProjectDir = path.dirname(globalNodeModulesDir);
155
+ let entries;
131
156
  try {
132
- const raw = await fsp.readFile(getInstallMethodCacheFile(), 'utf8');
133
- return JSON.parse(raw);
157
+ entries = fs.readdirSync(globalProjectDir);
134
158
  }
135
159
  catch {
136
- return {};
160
+ return [];
137
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'));
138
175
  }
139
- async function writeInstallMethodCache(state) {
140
- const filePath = getInstallMethodCacheFile();
141
- await fsp.mkdir(path.dirname(filePath), { recursive: true });
142
- await fsp.writeFile(filePath, JSON.stringify(state, null, 2));
176
+ function readPnpmGlobalNodeModulesRoots(pnpmGlobalRoot) {
177
+ return [normalizePnpmGlobalNodeModulesRoot(pnpmGlobalRoot), ...readSiblingPnpmGlobalNodeModulesRoots(pnpmGlobalRoot)];
143
178
  }
144
- async function readCachedInstallMethod(packageRoot) {
145
- const state = await readInstallMethodCache();
146
- return state.entries?.[getInstallMethodCacheKey(packageRoot)];
179
+ function packageNameToPath(packageName) {
180
+ return packageName.split('/').filter(Boolean);
147
181
  }
148
- async function writeCachedInstallMethod(packageRoot, entry) {
149
- const state = await readInstallMethodCache();
150
- const entries = {
151
- ...(state.entries ?? {}),
152
- [getInstallMethodCacheKey(packageRoot)]: entry,
153
- };
154
- await writeInstallMethodCache({ entries });
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;
155
268
  }
156
269
  async function readGlobalPrefix(commandOutputFn) {
157
270
  try {
158
- return (await commandOutputFn('npm', ['prefix', '-g'], {
271
+ return cleanCommandPath(await commandOutputFn('npm', ['prefix', '-g'], {
272
+ cwd: resolvePackageManagerProbeCwd(),
159
273
  errorName: 'npm prefix',
160
- })).trim();
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
+ }));
161
319
  }
162
320
  catch {
163
321
  return undefined;
@@ -174,21 +332,21 @@ function getUnsupportedSelfUpdateReason(installMethod) {
174
332
  if (installMethod === 'source') {
175
333
  return [
176
334
  'This CLI is running from source in a repository checkout.',
177
- '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.',
178
336
  'Upgrade this checkout through your repo workflow instead.',
179
337
  ].join(' ');
180
338
  }
181
339
  if (installMethod === 'package-local') {
182
340
  return [
183
341
  'This CLI is installed from a local project dependency tree.',
184
- '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.',
185
343
  'Upgrade the parent project dependency that provides this CLI instead.',
186
344
  ].join(' ');
187
345
  }
188
346
  if (installMethod === 'unknown') {
189
347
  return [
190
- 'This CLI install could not be recognized as a standard global npm install.',
191
- '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.',
192
350
  ].join(' ');
193
351
  }
194
352
  return undefined;
@@ -217,22 +375,45 @@ export function getSelfUpdatePackageSpec(status) {
217
375
  }
218
376
  export async function inspectSelfInstall(options = {}) {
219
377
  const packageRoot = options.packageRoot ? normalizePath(options.packageRoot) : PACKAGE_ROOT;
378
+ const packageName = options.packageName ?? DEFAULT_PACKAGE_NAME;
220
379
  const commandOutputFn = options.commandOutputFn ?? commandOutput;
221
- const cachedInstallMethod = await readCachedInstallMethod(packageRoot);
222
- const globalPrefix = cachedInstallMethod?.globalPrefix ?? await readGlobalPrefix(commandOutputFn);
223
- const installMethod = cachedInstallMethod?.installMethod ?? detectInstallMethod(packageRoot, globalPrefix);
224
- if (!cachedInstallMethod) {
225
- await writeCachedInstallMethod(packageRoot, {
226
- installMethod,
227
- globalPrefix,
228
- });
229
- }
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
+ });
230
396
  return {
231
397
  packageRoot,
232
398
  installMethod,
233
399
  globalPrefix,
234
400
  };
235
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
+ }
236
417
  export async function inspectSelfStatus(options = {}) {
237
418
  const packageRoot = options.packageRoot ? normalizePath(options.packageRoot) : PACKAGE_ROOT;
238
419
  const packageName = options.packageName ?? DEFAULT_PACKAGE_NAME;
@@ -240,6 +421,8 @@ export async function inspectSelfStatus(options = {}) {
240
421
  const channel = options.channel && options.channel !== 'auto' ? options.channel : detectChannel(currentVersion);
241
422
  const commandOutputFn = options.commandOutputFn ?? commandOutput;
242
423
  const { installMethod, globalPrefix } = await inspectSelfInstall({
424
+ currentBinPath: options.currentBinPath,
425
+ packageName,
243
426
  packageRoot,
244
427
  commandOutputFn,
245
428
  });
@@ -261,7 +444,7 @@ export async function inspectSelfStatus(options = {}) {
261
444
  latestVersion,
262
445
  updateAvailable,
263
446
  installMethod,
264
- updatable: installMethod === 'npm-global',
447
+ updatable: installMethod === 'npm-global' || installMethod === 'pnpm-global' || installMethod === 'yarn-global',
265
448
  updateBlockedReason: getUnsupportedSelfUpdateReason(installMethod),
266
449
  globalPrefix,
267
450
  registryError,
@@ -270,9 +453,30 @@ export async function inspectSelfStatus(options = {}) {
270
453
  export function formatUnsupportedSelfUpdateMessage(status) {
271
454
  return status.updateBlockedReason
272
455
  ?? [
273
- '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.',
274
457
  ].join('\n');
275
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
+ }
276
480
  export async function updateSelf(options = {}) {
277
481
  const status = await inspectSelfStatus(options);
278
482
  if (!status.updatable) {
@@ -291,9 +495,10 @@ export async function updateSelf(options = {}) {
291
495
  };
292
496
  }
293
497
  const packageSpec = getSelfUpdatePackageSpec(status);
294
- await (options.runFn ?? run)('npm', ['install', '-g', packageSpec], {
498
+ const installCommand = resolveSelfUpdateInstallCommand(status.installMethod, packageSpec);
499
+ await (options.runFn ?? run)(installCommand.command, installCommand.args, {
295
500
  stdio: options.verbose ? 'inherit' : 'ignore',
296
- errorName: 'npm install',
501
+ errorName: installCommand.errorName,
297
502
  });
298
503
  return {
299
504
  action: 'updated',
@@ -151,7 +151,7 @@ export async function shouldRunStartupUpdateCheck(argv, now = new Date()) {
151
151
  return readCurrentInstallLastCheckedDate(state) !== todayStamp(now);
152
152
  }
153
153
  export function shouldEnableStartupUpdateForInstallMethod(installMethod) {
154
- return installMethod === 'npm-global';
154
+ return installMethod === 'npm-global' || installMethod === 'pnpm-global' || installMethod === 'yarn-global';
155
155
  }
156
156
  function hasPendingUpdates(selfStatus, skillsStatus) {
157
157
  return Boolean(selfStatus.updateAvailable || skillsStatus.updateAvailable === true);
@@ -68,7 +68,7 @@
68
68
  "unreachable": "Unable to connect to the API base URL. Check the address and make sure the server is reachable. Details: {{details}}"
69
69
  },
70
70
  "envKey": {
71
- "invalid": "Use letters and numbers only."
71
+ "invalid": "Use letters, numbers, hyphens, and underscores only."
72
72
  },
73
73
  "appPublicPath": {
74
74
  "invalid": "Use / or a slash-separated path like /nocobase/ or /foo-bar/baz_2/. Each path segment may contain letters, numbers, hyphens, and underscores only."
@@ -68,7 +68,7 @@
68
68
  "unreachable": "无法连接到该 API 地址,请检查地址是否正确,并确认服务可访问。详情:{{details}}"
69
69
  },
70
70
  "envKey": {
71
- "invalid": "仅支持字母和数字。"
71
+ "invalid": "仅支持字母、数字、中划线和下划线。"
72
72
  },
73
73
  "appPublicPath": {
74
74
  "invalid": "请输入 /,或类似 /nocobase/、/foo-bar/baz_2/ 这样的路径。每个路径段仅支持字母、数字、中划线和下划线。"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/cli",
3
- "version": "2.1.11",
3
+ "version": "2.1.12",
4
4
  "description": "NocoBase Command Line Tool",
5
5
  "type": "module",
6
6
  "main": "dist/generated/command-registry.js",
@@ -143,5 +143,5 @@
143
143
  "type": "git",
144
144
  "url": "git+https://github.com/nocobase/nocobase.git"
145
145
  },
146
- "gitHead": "1ccf891d837e21089f65f84b892407b34a0a0cb9"
146
+ "gitHead": "3a69ed089fbb5a3e8700c0134f83d654f0190d25"
147
147
  }