@nocobase/cli 2.1.10 → 2.1.11-test.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/dist/commands/install.js +4 -2
- package/dist/commands/self/update.js +2 -2
- package/dist/commands/source/dev.js +9 -5
- package/dist/lib/app-managed-resources.js +91 -1
- package/dist/lib/auth-store.js +7 -1
- package/dist/lib/cli-config.js +19 -0
- package/dist/lib/run-npm.js +4 -0
- package/dist/lib/self-manager.js +223 -46
- package/dist/lib/startup-update.js +1 -1
- package/package.json +2 -3
package/dist/commands/install.js
CHANGED
|
@@ -15,7 +15,7 @@ import { appendAppPublicPath } from '../lib/app-public-path.js';
|
|
|
15
15
|
import { runPromptCatalog, } from "../lib/prompt-catalog.js";
|
|
16
16
|
import { applyCliLocale, localeText, resolveCliLocale, translateCli } from "../lib/cli-locale.js";
|
|
17
17
|
import { resolveConfiguredEnvPath, resolveDefaultConfigScope, resolveEnvRoot, resolveEnvRelativePath, } from '../lib/cli-home.js';
|
|
18
|
-
import { defaultDockerContainerPrefix, defaultDockerNetworkName } from '../lib/app-runtime.js';
|
|
18
|
+
import { defaultDockerContainerPrefix, defaultDockerNetworkName, managedAppLifecycleEnvVars, } from '../lib/app-runtime.js';
|
|
19
19
|
import { resolveDefaultApiHost, resolveDockerContainerPrefix, resolveDockerNetworkName } from '../lib/cli-config.js';
|
|
20
20
|
import { DEFAULT_DOCKER_VERSION, resolveDockerImageRef } from "../lib/docker-image.js";
|
|
21
21
|
import { findAvailableTcpPort, validateAppPublicPath, validateAvailableTcpPort, validateTcpPort, validateEnvKey, } from "../lib/prompt-validators.js";
|
|
@@ -1960,7 +1960,7 @@ export default class Install extends Command {
|
|
|
1960
1960
|
}
|
|
1961
1961
|
static resolveAbsoluteStoragePath(envName, appResults) {
|
|
1962
1962
|
const configuredStoragePath = resolveConfiguredStoragePathValue(appResults, envName);
|
|
1963
|
-
return resolveConfiguredEnvPath(configuredStoragePath) ?? resolveEnvRelativePath(defaultInstallStoragePath(envName));
|
|
1963
|
+
return (resolveConfiguredEnvPath(configuredStoragePath) ?? resolveEnvRelativePath(defaultInstallStoragePath(envName)));
|
|
1964
1964
|
}
|
|
1965
1965
|
async prepareHookScriptForInstall(params) {
|
|
1966
1966
|
const appPath = Install.resolveAbsoluteAppPath(params.envName, params.appResults);
|
|
@@ -2057,7 +2057,9 @@ export default class Install extends Command {
|
|
|
2057
2057
|
const dbDialect = String(params.dbResults.dbDialect ?? 'postgres').trim() || 'postgres';
|
|
2058
2058
|
const appKey = Install.resolveManagedAppKey(params.appResults.appKey);
|
|
2059
2059
|
const timeZone = Install.resolveManagedTimeZone(params.appResults.timeZone);
|
|
2060
|
+
const lifecycleEnvVars = managedAppLifecycleEnvVars();
|
|
2060
2061
|
const env = {
|
|
2062
|
+
...lifecycleEnvVars,
|
|
2061
2063
|
STORAGE_PATH: storagePath,
|
|
2062
2064
|
APP_PORT: String(params.appResults.appPort ?? DEFAULT_INSTALL_APP_PORT).trim() || DEFAULT_INSTALL_APP_PORT,
|
|
2063
2065
|
APP_KEY: appKey,
|
|
@@ -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:
|
|
80
|
+
default: true,
|
|
81
81
|
});
|
|
82
82
|
}
|
|
83
83
|
catch {
|
|
@@ -8,8 +8,9 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { Command, Flags } from '@oclif/core';
|
|
10
10
|
import { ensureCrossEnvConfirmed, hasExplicitEnvSelection } from '../../lib/env-guard.js';
|
|
11
|
-
import { ensureLocalPostinstall } from '../../lib/app-managed-resources.js';
|
|
11
|
+
import { ensureLocalPostinstall, ensureNpmSourceDevDependencies } from '../../lib/app-managed-resources.js';
|
|
12
12
|
import { formatMissingManagedAppEnvMessage, resolveManagedAppRuntime, runLocalNocoBaseCommand, } from '../../lib/app-runtime.js';
|
|
13
|
+
import { findAvailableTcpPort } from '../../lib/prompt-validators.js';
|
|
13
14
|
import { announceTargetEnv, failTask, printInfo, startTask, succeedTask } from '../../lib/ui.js';
|
|
14
15
|
function formatUnsupportedRuntimeMessage(kind, envName) {
|
|
15
16
|
if (kind === 'docker') {
|
|
@@ -129,10 +130,7 @@ export default class SourceDev extends Command {
|
|
|
129
130
|
this.error(formatUnsupportedRuntimeMessage(runtime.kind, runtime.envName));
|
|
130
131
|
}
|
|
131
132
|
announceTargetEnv(runtime.envName);
|
|
132
|
-
const devPort = flags.port ||
|
|
133
|
-
(runtime.env.appPort !== undefined && runtime.env.appPort !== null
|
|
134
|
-
? String(runtime.env.appPort).trim()
|
|
135
|
-
: undefined);
|
|
133
|
+
const devPort = flags.port?.trim() || (await findAvailableTcpPort());
|
|
136
134
|
const appUrl = appUrlForPort(devPort);
|
|
137
135
|
if (await isAppAlreadyRunning(appUrl)) {
|
|
138
136
|
this.error([
|
|
@@ -160,6 +158,12 @@ export default class SourceDev extends Command {
|
|
|
160
158
|
}
|
|
161
159
|
printInfo(`Starting NocoBase dev mode for "${runtime.envName}" from ${runtime.projectRoot}. Press Ctrl+C to stop.`);
|
|
162
160
|
try {
|
|
161
|
+
await ensureNpmSourceDevDependencies(runtime, {
|
|
162
|
+
onStartTask: startTask,
|
|
163
|
+
onSucceedTask: succeedTask,
|
|
164
|
+
onFailTask: failTask,
|
|
165
|
+
verbose: true,
|
|
166
|
+
});
|
|
163
167
|
await ensureLocalPostinstall(runtime, {
|
|
164
168
|
onStartTask: startTask,
|
|
165
169
|
onSucceedTask: succeedTask,
|
|
@@ -6,7 +6,9 @@
|
|
|
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 {
|
|
9
|
+
import { existsSync } from 'node:fs';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
|
|
10
12
|
import { resolveAppPublicPath } from './app-public-path.js';
|
|
11
13
|
import { dockerContainerExists, managedAppLifecycleEnvVars, runLocalNocoBaseCommand, startDockerContainer, } from './app-runtime.js';
|
|
12
14
|
import { deriveBuiltinDbConnection, resolveBuiltinDbConnection } from './builtin-db.js';
|
|
@@ -91,6 +93,15 @@ function formatLocalPostinstallFailure(envName, message) {
|
|
|
91
93
|
`Details: ${message}`,
|
|
92
94
|
].join('\n');
|
|
93
95
|
}
|
|
96
|
+
function formatNpmSourceDevDependenciesFailure(envName, projectRoot, message) {
|
|
97
|
+
return [
|
|
98
|
+
`Couldn't prepare source dev dependencies for "${envName}".`,
|
|
99
|
+
'`nb source dev` requires @nocobase/devtools in npm source envs.',
|
|
100
|
+
`Source directory: ${projectRoot}`,
|
|
101
|
+
`Run \`cd ${projectRoot} && yarn install\` after fixing package.json, then try again.`,
|
|
102
|
+
`Details: ${message}`,
|
|
103
|
+
].join('\n');
|
|
104
|
+
}
|
|
94
105
|
function formatSavedDockerSettingsIncomplete(envName, missing) {
|
|
95
106
|
return [
|
|
96
107
|
`Can't start NocoBase for "${envName}" yet.`,
|
|
@@ -115,6 +126,45 @@ async function localProjectHasFiles(projectRoot) {
|
|
|
115
126
|
return false;
|
|
116
127
|
}
|
|
117
128
|
}
|
|
129
|
+
function isRecord(value) {
|
|
130
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
131
|
+
}
|
|
132
|
+
async function readPackageJson(projectRoot) {
|
|
133
|
+
const packageJsonPath = path.join(projectRoot, 'package.json');
|
|
134
|
+
const content = await readFile(packageJsonPath, 'utf-8');
|
|
135
|
+
const parsed = JSON.parse(content);
|
|
136
|
+
if (!isRecord(parsed)) {
|
|
137
|
+
throw new Error(`${packageJsonPath} must contain a JSON object.`);
|
|
138
|
+
}
|
|
139
|
+
return parsed;
|
|
140
|
+
}
|
|
141
|
+
function getStringDependency(packageJson, section, packageName) {
|
|
142
|
+
const dependencies = packageJson[section];
|
|
143
|
+
if (!isRecord(dependencies)) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
const version = dependencies[packageName];
|
|
147
|
+
return typeof version === 'string' && version.trim() ? version.trim() : undefined;
|
|
148
|
+
}
|
|
149
|
+
function ensureDevDependencies(packageJson) {
|
|
150
|
+
const devDependencies = packageJson.devDependencies;
|
|
151
|
+
if (devDependencies === undefined) {
|
|
152
|
+
const next = {};
|
|
153
|
+
packageJson.devDependencies = next;
|
|
154
|
+
return next;
|
|
155
|
+
}
|
|
156
|
+
if (!isRecord(devDependencies)) {
|
|
157
|
+
throw new Error('package.json devDependencies must be an object.');
|
|
158
|
+
}
|
|
159
|
+
return devDependencies;
|
|
160
|
+
}
|
|
161
|
+
function hasNpmSourceDevtools(projectRoot) {
|
|
162
|
+
return existsSync(path.join(projectRoot, 'node_modules', '@nocobase', 'devtools', 'package.json'));
|
|
163
|
+
}
|
|
164
|
+
function npmRegistryEnv(runtime) {
|
|
165
|
+
const npmRegistry = String(runtime.env.config?.npmRegistry ?? '').trim();
|
|
166
|
+
return npmRegistry ? { npm_config_registry: npmRegistry } : undefined;
|
|
167
|
+
}
|
|
118
168
|
export async function buildSavedDockerRunArgs(runtime, options) {
|
|
119
169
|
const config = runtime.env.config ?? {};
|
|
120
170
|
const storagePath = trimValue(resolveConfiguredStoragePath(config));
|
|
@@ -345,3 +395,43 @@ export async function ensureLocalPostinstall(runtime, options) {
|
|
|
345
395
|
throw new Error(formatLocalPostinstallFailure(runtime.envName, error instanceof Error ? error.message : String(error)));
|
|
346
396
|
}
|
|
347
397
|
}
|
|
398
|
+
export async function ensureNpmSourceDevDependencies(runtime, options) {
|
|
399
|
+
if (runtime.source !== 'npm') {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
let taskStarted = false;
|
|
403
|
+
try {
|
|
404
|
+
const packageJson = await readPackageJson(runtime.projectRoot);
|
|
405
|
+
const appVersion = getStringDependency(packageJson, 'dependencies', '@nocobase/app');
|
|
406
|
+
const devtoolsVersion = getStringDependency(packageJson, 'devDependencies', '@nocobase/devtools');
|
|
407
|
+
let updatedPackageJson = false;
|
|
408
|
+
if (!devtoolsVersion) {
|
|
409
|
+
if (!appVersion) {
|
|
410
|
+
throw new Error('Cannot determine @nocobase/devtools version because dependencies["@nocobase/app"] is missing.');
|
|
411
|
+
}
|
|
412
|
+
const devDependencies = ensureDevDependencies(packageJson);
|
|
413
|
+
devDependencies['@nocobase/devtools'] = appVersion;
|
|
414
|
+
updatedPackageJson = true;
|
|
415
|
+
await writeFile(path.join(runtime.projectRoot, 'package.json'), `${JSON.stringify(packageJson, null, 2)}\n`, 'utf-8');
|
|
416
|
+
}
|
|
417
|
+
const needsInstall = updatedPackageJson || !hasNpmSourceDevtools(runtime.projectRoot);
|
|
418
|
+
if (!needsInstall) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
options?.onStartTask?.(`Preparing source dev dependencies for "${runtime.envName}"...`);
|
|
422
|
+
taskStarted = true;
|
|
423
|
+
await run('yarn', ['install'], {
|
|
424
|
+
cwd: runtime.projectRoot,
|
|
425
|
+
env: npmRegistryEnv(runtime),
|
|
426
|
+
errorName: 'yarn install',
|
|
427
|
+
stdio: commandStdio(options?.verbose),
|
|
428
|
+
});
|
|
429
|
+
options?.onSucceedTask?.(`Source dev dependencies are ready for "${runtime.envName}".`);
|
|
430
|
+
}
|
|
431
|
+
catch (error) {
|
|
432
|
+
if (taskStarted) {
|
|
433
|
+
options?.onFailTask?.(`Failed to prepare source dev dependencies for "${runtime.envName}".`);
|
|
434
|
+
}
|
|
435
|
+
throw new Error(formatNpmSourceDevDependenciesFailure(runtime.envName, runtime.projectRoot, error instanceof Error ? error.message : String(error)));
|
|
436
|
+
}
|
|
437
|
+
}
|
package/dist/lib/auth-store.js
CHANGED
|
@@ -123,13 +123,19 @@ function normalizeAuthConfig(config) {
|
|
|
123
123
|
},
|
|
124
124
|
}
|
|
125
125
|
: {}),
|
|
126
|
-
...(settings.bin?.docker ||
|
|
126
|
+
...(settings.bin?.docker ||
|
|
127
|
+
settings.bin?.caddy ||
|
|
128
|
+
settings.bin?.git ||
|
|
129
|
+
settings.bin?.nginx ||
|
|
130
|
+
settings.bin?.pnpm ||
|
|
131
|
+
settings.bin?.yarn
|
|
127
132
|
? {
|
|
128
133
|
bin: {
|
|
129
134
|
...(settings.bin?.docker ? { docker: normalizeOptionalString(settings.bin.docker) } : {}),
|
|
130
135
|
...(settings.bin?.caddy ? { caddy: normalizeOptionalString(settings.bin.caddy) } : {}),
|
|
131
136
|
...(settings.bin?.git ? { git: normalizeOptionalString(settings.bin.git) } : {}),
|
|
132
137
|
...(settings.bin?.nginx ? { nginx: normalizeOptionalString(settings.bin.nginx) } : {}),
|
|
138
|
+
...(settings.bin?.pnpm ? { pnpm: normalizeOptionalString(settings.bin.pnpm) } : {}),
|
|
133
139
|
...(settings.bin?.yarn ? { yarn: normalizeOptionalString(settings.bin.yarn) } : {}),
|
|
134
140
|
},
|
|
135
141
|
}
|
package/dist/lib/cli-config.js
CHANGED
|
@@ -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) {
|
package/dist/lib/run-npm.js
CHANGED
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
|
}
|
|
@@ -108,56 +106,188 @@ function readCurrentVersion(packageRoot) {
|
|
|
108
106
|
const pkg = JSON.parse(content);
|
|
109
107
|
return String(pkg.version ?? '').trim();
|
|
110
108
|
}
|
|
111
|
-
function detectInstallMethod(packageRoot,
|
|
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
|
+
if (isPnpmGlobalPath(packageRoot)) {
|
|
129
|
+
return 'pnpm-global';
|
|
130
|
+
}
|
|
119
131
|
if (packageRoot.includes(`${path.sep}node_modules${path.sep}`)) {
|
|
120
132
|
return 'package-local';
|
|
121
133
|
}
|
|
122
134
|
return 'unknown';
|
|
123
135
|
}
|
|
124
|
-
function
|
|
125
|
-
|
|
136
|
+
function isPnpmGlobalPath(packageRoot) {
|
|
137
|
+
const normalized = normalizePath(packageRoot);
|
|
138
|
+
const segments = normalized.split(path.sep).filter(Boolean);
|
|
139
|
+
const globalIndex = segments.findIndex((segment, index) => segment === 'global' && segments[index - 1] === 'pnpm');
|
|
140
|
+
if (globalIndex === -1) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
return segments.slice(globalIndex + 2).includes('node_modules');
|
|
144
|
+
}
|
|
145
|
+
function normalizePnpmGlobalNodeModulesRoot(pnpmGlobalRoot) {
|
|
146
|
+
const normalizedGlobalRoot = normalizePath(pnpmGlobalRoot);
|
|
147
|
+
return path.basename(normalizedGlobalRoot) === 'node_modules'
|
|
148
|
+
? normalizedGlobalRoot
|
|
149
|
+
: path.join(normalizedGlobalRoot, 'node_modules');
|
|
126
150
|
}
|
|
127
|
-
function
|
|
128
|
-
return
|
|
151
|
+
function packageNameToPath(packageName) {
|
|
152
|
+
return packageName.split('/').filter(Boolean);
|
|
129
153
|
}
|
|
130
|
-
|
|
154
|
+
function isSameRealPath(left, right) {
|
|
131
155
|
try {
|
|
132
|
-
|
|
133
|
-
return JSON.parse(raw);
|
|
156
|
+
return normalizePath(fs.realpathSync(left)) === normalizePath(fs.realpathSync(right));
|
|
134
157
|
}
|
|
135
158
|
catch {
|
|
136
|
-
return
|
|
159
|
+
return false;
|
|
137
160
|
}
|
|
138
161
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
162
|
+
function isPnpmGlobalRootPath(pnpmGlobalRoot, packageRoot) {
|
|
163
|
+
if (isSubPath(pnpmGlobalRoot, packageRoot)) {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
const globalNodeModulesDir = normalizePnpmGlobalNodeModulesRoot(pnpmGlobalRoot);
|
|
167
|
+
const globalProjectDir = path.dirname(globalNodeModulesDir);
|
|
168
|
+
if (!isSubPath(globalProjectDir, packageRoot)) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
const segments = normalizePath(packageRoot).split(path.sep).filter(Boolean);
|
|
172
|
+
const pnpmStoreIndex = segments.indexOf('.pnpm');
|
|
173
|
+
if (pnpmStoreIndex === -1) {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
return segments.slice(pnpmStoreIndex + 1).includes('node_modules');
|
|
143
177
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return state.entries?.[getInstallMethodCacheKey(packageRoot)];
|
|
178
|
+
function isPnpmGlobalPackagePath(pnpmGlobalRoot, packageRoot, packageName) {
|
|
179
|
+
return isSameRealPath(path.join(normalizePnpmGlobalNodeModulesRoot(pnpmGlobalRoot), ...packageNameToPath(packageName)), packageRoot);
|
|
147
180
|
}
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
|
|
181
|
+
function isPnpmGlobalBinProjectPath(pnpmGlobalBin, packageRoot, packageName) {
|
|
182
|
+
const pnpmHome = path.dirname(normalizePath(pnpmGlobalBin));
|
|
183
|
+
const globalDir = path.join(pnpmHome, 'global');
|
|
184
|
+
if (isSubPath(globalDir, packageRoot)) {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
let entries;
|
|
188
|
+
try {
|
|
189
|
+
entries = fs.readdirSync(globalDir);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
return entries.some((entry) => {
|
|
195
|
+
const pnpmGlobalRoot = path.join(globalDir, entry, 'node_modules');
|
|
196
|
+
return (isPnpmGlobalRootPath(pnpmGlobalRoot, packageRoot)
|
|
197
|
+
|| isPnpmGlobalPackagePath(pnpmGlobalRoot, packageRoot, packageName));
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
function isPnpmGlobalInstallPath(options) {
|
|
201
|
+
if (options.pnpmGlobalRoot) {
|
|
202
|
+
const matchedRoot = isPnpmGlobalRootPath(options.pnpmGlobalRoot, options.packageRoot)
|
|
203
|
+
|| isPnpmGlobalPackagePath(options.pnpmGlobalRoot, options.packageRoot, options.packageName);
|
|
204
|
+
if (matchedRoot) {
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (options.pnpmGlobalBin) {
|
|
209
|
+
return isPnpmGlobalBinProjectPath(options.pnpmGlobalBin, options.packageRoot, options.packageName);
|
|
210
|
+
}
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
function readCurrentBinPath(currentBinPath) {
|
|
214
|
+
const candidate = String(currentBinPath ?? process.argv[1] ?? '').trim();
|
|
215
|
+
if (!candidate) {
|
|
216
|
+
return undefined;
|
|
217
|
+
}
|
|
218
|
+
return normalizePath(candidate);
|
|
219
|
+
}
|
|
220
|
+
function cleanCommandPath(value) {
|
|
221
|
+
const trimmed = value.trim();
|
|
222
|
+
return trimmed ? trimmed : undefined;
|
|
223
|
+
}
|
|
224
|
+
function resolvePackageManagerProbeCwd() {
|
|
225
|
+
const candidates = [os.homedir(), os.tmpdir(), path.parse(process.cwd()).root];
|
|
226
|
+
for (const candidate of candidates) {
|
|
227
|
+
if (!candidate) {
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
try {
|
|
231
|
+
if (fs.statSync(candidate).isDirectory()) {
|
|
232
|
+
return candidate;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
catch {
|
|
236
|
+
// Ignore invalid probe cwd candidates and continue to the next one.
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return undefined;
|
|
155
240
|
}
|
|
156
241
|
async function readGlobalPrefix(commandOutputFn) {
|
|
157
242
|
try {
|
|
158
|
-
return (await commandOutputFn('npm', ['prefix', '-g'], {
|
|
243
|
+
return cleanCommandPath(await commandOutputFn('npm', ['prefix', '-g'], {
|
|
244
|
+
cwd: resolvePackageManagerProbeCwd(),
|
|
159
245
|
errorName: 'npm prefix',
|
|
160
|
-
}))
|
|
246
|
+
}));
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
async function readPnpmGlobalBin(commandOutputFn) {
|
|
253
|
+
try {
|
|
254
|
+
return cleanCommandPath(await commandOutputFn('pnpm', ['bin', '-g'], {
|
|
255
|
+
cwd: resolvePackageManagerProbeCwd(),
|
|
256
|
+
errorName: 'pnpm bin',
|
|
257
|
+
}));
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
return undefined;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
async function readPnpmGlobalRoot(commandOutputFn) {
|
|
264
|
+
try {
|
|
265
|
+
return cleanCommandPath(await commandOutputFn('pnpm', ['root', '-g'], {
|
|
266
|
+
cwd: resolvePackageManagerProbeCwd(),
|
|
267
|
+
errorName: 'pnpm root',
|
|
268
|
+
}));
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
async function readYarnGlobalDir(commandOutputFn) {
|
|
275
|
+
try {
|
|
276
|
+
return cleanCommandPath(await commandOutputFn('yarn', ['global', 'dir'], {
|
|
277
|
+
cwd: resolvePackageManagerProbeCwd(),
|
|
278
|
+
errorName: 'yarn global dir',
|
|
279
|
+
}));
|
|
280
|
+
}
|
|
281
|
+
catch {
|
|
282
|
+
return undefined;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
async function readYarnGlobalBin(commandOutputFn) {
|
|
286
|
+
try {
|
|
287
|
+
return cleanCommandPath(await commandOutputFn('yarn', ['global', 'bin'], {
|
|
288
|
+
cwd: resolvePackageManagerProbeCwd(),
|
|
289
|
+
errorName: 'yarn global bin',
|
|
290
|
+
}));
|
|
161
291
|
}
|
|
162
292
|
catch {
|
|
163
293
|
return undefined;
|
|
@@ -174,21 +304,21 @@ function getUnsupportedSelfUpdateReason(installMethod) {
|
|
|
174
304
|
if (installMethod === 'source') {
|
|
175
305
|
return [
|
|
176
306
|
'This CLI is running from source in a repository checkout.',
|
|
177
|
-
'Automatic self-update is only supported for standard global npm installs.',
|
|
307
|
+
'Automatic self-update is only supported for standard global npm, pnpm, or yarn installs.',
|
|
178
308
|
'Upgrade this checkout through your repo workflow instead.',
|
|
179
309
|
].join(' ');
|
|
180
310
|
}
|
|
181
311
|
if (installMethod === 'package-local') {
|
|
182
312
|
return [
|
|
183
313
|
'This CLI is installed from a local project dependency tree.',
|
|
184
|
-
'Automatic self-update is only supported for standard global npm installs.',
|
|
314
|
+
'Automatic self-update is only supported for standard global npm, pnpm, or yarn installs.',
|
|
185
315
|
'Upgrade the parent project dependency that provides this CLI instead.',
|
|
186
316
|
].join(' ');
|
|
187
317
|
}
|
|
188
318
|
if (installMethod === 'unknown') {
|
|
189
319
|
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.',
|
|
320
|
+
'This CLI install could not be recognized as a standard global npm, pnpm, or yarn install.',
|
|
321
|
+
'Automatic self-update is only supported for standard global npm, pnpm, or yarn installs.',
|
|
192
322
|
].join(' ');
|
|
193
323
|
}
|
|
194
324
|
return undefined;
|
|
@@ -217,22 +347,45 @@ export function getSelfUpdatePackageSpec(status) {
|
|
|
217
347
|
}
|
|
218
348
|
export async function inspectSelfInstall(options = {}) {
|
|
219
349
|
const packageRoot = options.packageRoot ? normalizePath(options.packageRoot) : PACKAGE_ROOT;
|
|
350
|
+
const packageName = options.packageName ?? DEFAULT_PACKAGE_NAME;
|
|
220
351
|
const commandOutputFn = options.commandOutputFn ?? commandOutput;
|
|
221
|
-
const
|
|
222
|
-
const globalPrefix =
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
352
|
+
const currentBinPath = readCurrentBinPath(options.currentBinPath);
|
|
353
|
+
const globalPrefix = await readGlobalPrefix(commandOutputFn);
|
|
354
|
+
const pnpmGlobalBin = await readPnpmGlobalBin(commandOutputFn);
|
|
355
|
+
const pnpmGlobalRoot = await readPnpmGlobalRoot(commandOutputFn);
|
|
356
|
+
const yarnGlobalDir = await readYarnGlobalDir(commandOutputFn);
|
|
357
|
+
const yarnGlobalBin = await readYarnGlobalBin(commandOutputFn);
|
|
358
|
+
const installMethod = detectInstallMethodFromSignals({
|
|
359
|
+
packageRoot,
|
|
360
|
+
currentBinPath,
|
|
361
|
+
globalPrefix,
|
|
362
|
+
packageName,
|
|
363
|
+
pnpmGlobalBin,
|
|
364
|
+
pnpmGlobalRoot,
|
|
365
|
+
yarnGlobalBin,
|
|
366
|
+
yarnGlobalDir,
|
|
367
|
+
});
|
|
230
368
|
return {
|
|
231
369
|
packageRoot,
|
|
232
370
|
installMethod,
|
|
233
371
|
globalPrefix,
|
|
234
372
|
};
|
|
235
373
|
}
|
|
374
|
+
function detectInstallMethodFromSignals(options) {
|
|
375
|
+
if (options.currentBinPath && options.yarnGlobalBin && isSubPath(options.yarnGlobalBin, options.currentBinPath)) {
|
|
376
|
+
return 'yarn-global';
|
|
377
|
+
}
|
|
378
|
+
if (options.currentBinPath && options.pnpmGlobalBin && isSubPath(options.pnpmGlobalBin, options.currentBinPath)) {
|
|
379
|
+
return 'pnpm-global';
|
|
380
|
+
}
|
|
381
|
+
return detectInstallMethod(options.packageRoot, {
|
|
382
|
+
globalPrefix: options.globalPrefix,
|
|
383
|
+
packageName: options.packageName,
|
|
384
|
+
pnpmGlobalBin: options.pnpmGlobalBin,
|
|
385
|
+
pnpmGlobalRoot: options.pnpmGlobalRoot,
|
|
386
|
+
yarnGlobalDir: options.yarnGlobalDir,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
236
389
|
export async function inspectSelfStatus(options = {}) {
|
|
237
390
|
const packageRoot = options.packageRoot ? normalizePath(options.packageRoot) : PACKAGE_ROOT;
|
|
238
391
|
const packageName = options.packageName ?? DEFAULT_PACKAGE_NAME;
|
|
@@ -240,6 +393,8 @@ export async function inspectSelfStatus(options = {}) {
|
|
|
240
393
|
const channel = options.channel && options.channel !== 'auto' ? options.channel : detectChannel(currentVersion);
|
|
241
394
|
const commandOutputFn = options.commandOutputFn ?? commandOutput;
|
|
242
395
|
const { installMethod, globalPrefix } = await inspectSelfInstall({
|
|
396
|
+
currentBinPath: options.currentBinPath,
|
|
397
|
+
packageName,
|
|
243
398
|
packageRoot,
|
|
244
399
|
commandOutputFn,
|
|
245
400
|
});
|
|
@@ -261,7 +416,7 @@ export async function inspectSelfStatus(options = {}) {
|
|
|
261
416
|
latestVersion,
|
|
262
417
|
updateAvailable,
|
|
263
418
|
installMethod,
|
|
264
|
-
updatable: installMethod === 'npm-global',
|
|
419
|
+
updatable: installMethod === 'npm-global' || installMethod === 'pnpm-global' || installMethod === 'yarn-global',
|
|
265
420
|
updateBlockedReason: getUnsupportedSelfUpdateReason(installMethod),
|
|
266
421
|
globalPrefix,
|
|
267
422
|
registryError,
|
|
@@ -270,9 +425,30 @@ export async function inspectSelfStatus(options = {}) {
|
|
|
270
425
|
export function formatUnsupportedSelfUpdateMessage(status) {
|
|
271
426
|
return status.updateBlockedReason
|
|
272
427
|
?? [
|
|
273
|
-
'Automatic self-update is only supported for standard global npm installs.',
|
|
428
|
+
'Automatic self-update is only supported for standard global npm, pnpm, or yarn installs.',
|
|
274
429
|
].join('\n');
|
|
275
430
|
}
|
|
431
|
+
function resolveSelfUpdateInstallCommand(installMethod, packageSpec) {
|
|
432
|
+
if (installMethod === 'pnpm-global') {
|
|
433
|
+
return {
|
|
434
|
+
command: 'pnpm',
|
|
435
|
+
args: ['add', '-g', packageSpec],
|
|
436
|
+
errorName: 'pnpm add',
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
if (installMethod === 'yarn-global') {
|
|
440
|
+
return {
|
|
441
|
+
command: 'yarn',
|
|
442
|
+
args: ['global', 'add', packageSpec],
|
|
443
|
+
errorName: 'yarn global add',
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
return {
|
|
447
|
+
command: 'npm',
|
|
448
|
+
args: ['install', '-g', packageSpec],
|
|
449
|
+
errorName: 'npm install',
|
|
450
|
+
};
|
|
451
|
+
}
|
|
276
452
|
export async function updateSelf(options = {}) {
|
|
277
453
|
const status = await inspectSelfStatus(options);
|
|
278
454
|
if (!status.updatable) {
|
|
@@ -291,9 +467,10 @@ export async function updateSelf(options = {}) {
|
|
|
291
467
|
};
|
|
292
468
|
}
|
|
293
469
|
const packageSpec = getSelfUpdatePackageSpec(status);
|
|
294
|
-
|
|
470
|
+
const installCommand = resolveSelfUpdateInstallCommand(status.installMethod, packageSpec);
|
|
471
|
+
await (options.runFn ?? run)(installCommand.command, installCommand.args, {
|
|
295
472
|
stdio: options.verbose ? 'inherit' : 'ignore',
|
|
296
|
-
errorName:
|
|
473
|
+
errorName: installCommand.errorName,
|
|
297
474
|
});
|
|
298
475
|
return {
|
|
299
476
|
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);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/cli",
|
|
3
|
-
"version": "2.1.10",
|
|
3
|
+
"version": "2.1.11-test.10",
|
|
4
4
|
"description": "NocoBase Command Line Tool",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/generated/command-registry.js",
|
|
@@ -142,6 +142,5 @@
|
|
|
142
142
|
"repository": {
|
|
143
143
|
"type": "git",
|
|
144
144
|
"url": "git+https://github.com/nocobase/nocobase.git"
|
|
145
|
-
}
|
|
146
|
-
"gitHead": "65d5bc38d72d5a6fca8bcf9798a0b976e382cac9"
|
|
145
|
+
}
|
|
147
146
|
}
|