@nocobase/cli 2.2.0-alpha.1 → 2.2.0-alpha.2
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/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 +92 -0
- package/dist/commands/app/upgrade.js +11 -0
- package/dist/commands/examples/prompts-stages.js +2 -2
- package/dist/commands/examples/prompts-test.js +2 -2
- package/dist/commands/init.js +22 -10
- package/dist/commands/install.js +135 -4
- package/dist/commands/license/activate.js +4 -1
- package/dist/commands/license/shared.js +24 -15
- 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 +67 -2
- package/dist/lib/api-command-compat.js +51 -8
- package/dist/lib/app-managed-resources.js +101 -1
- package/dist/lib/auth-store.js +34 -12
- package/dist/lib/cli-config.js +19 -0
- package/dist/lib/env-config.js +6 -0
- package/dist/lib/hook-script.js +160 -0
- package/dist/lib/prompt-web-ui.js +7 -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 +10 -4
- package/dist/locale/zh-CN.json +10 -4
- package/package.json +7 -2
- package/assets/env-proxy/nginx/app.conf.tpl +0 -23
- package/assets/env-proxy/nginx/nocobase.conf.tpl +0 -5
- package/assets/env-proxy/nginx/snippets/dist-location.conf +0 -5
- package/assets/env-proxy/nginx/snippets/gzip.conf +0 -17
- package/assets/env-proxy/nginx/snippets/log-format-http.conf +0 -13
- package/assets/env-proxy/nginx/snippets/maps-http.conf +0 -14
- package/assets/env-proxy/nginx/snippets/mime-types.conf +0 -98
- package/assets/env-proxy/nginx/snippets/proxy-location.conf +0 -17
- package/assets/env-proxy/nginx/snippets/spa-location.conf +0 -6
- package/assets/env-proxy/nginx/snippets/uploads-location.conf +0 -21
- package/scripts/build.mjs +0 -34
- package/scripts/clean.mjs +0 -9
- package/tsconfig.json +0 -19
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
import { copyFile, mkdir } from 'node:fs/promises';
|
|
10
|
+
import { createRequire } from 'node:module';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
export const ENV_HOOK_SCRIPT_CONFIG_PATH = '.nb/hooks.mjs';
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
const { spawn } = require('node:child_process');
|
|
15
|
+
function trimValue(value) {
|
|
16
|
+
return String(value ?? '').trim();
|
|
17
|
+
}
|
|
18
|
+
function normalizeHookPhase(value) {
|
|
19
|
+
const text = trimValue(value);
|
|
20
|
+
if (text === 'init' || text === 'upgrade' || text === 'restore' || text === 'source-download' || text === 'app-start') {
|
|
21
|
+
return text;
|
|
22
|
+
}
|
|
23
|
+
return 'init';
|
|
24
|
+
}
|
|
25
|
+
function normalizeHookCommand(value) {
|
|
26
|
+
const text = trimValue(value);
|
|
27
|
+
if (text === 'source:download' || text === 'app:start' || text === 'app:restart' || text === 'app:upgrade') {
|
|
28
|
+
return text;
|
|
29
|
+
}
|
|
30
|
+
return 'init';
|
|
31
|
+
}
|
|
32
|
+
function normalizeHookSource(value) {
|
|
33
|
+
const text = trimValue(value);
|
|
34
|
+
if (text === 'npm' || text === 'git' || text === 'docker') {
|
|
35
|
+
return text;
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
function isDependencyHookSource(source) {
|
|
40
|
+
return source === 'npm' || source === 'git';
|
|
41
|
+
}
|
|
42
|
+
function isRecord(value) {
|
|
43
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
44
|
+
}
|
|
45
|
+
export function resolveHookScriptPath(params) {
|
|
46
|
+
const hookScript = trimValue(params.hookScript);
|
|
47
|
+
if (!hookScript) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
if (path.isAbsolute(hookScript)) {
|
|
51
|
+
return hookScript;
|
|
52
|
+
}
|
|
53
|
+
const appPath = trimValue(params.appPath);
|
|
54
|
+
if (!appPath) {
|
|
55
|
+
return hookScript;
|
|
56
|
+
}
|
|
57
|
+
const usesWindowsSeparators = appPath.includes('\\') || /^[a-zA-Z]:([\\/]|$)/.test(appPath) || appPath.startsWith('\\\\');
|
|
58
|
+
return usesWindowsSeparators ? path.win32.join(appPath, hookScript) : path.posix.join(appPath, hookScript);
|
|
59
|
+
}
|
|
60
|
+
export async function persistHookScript(params) {
|
|
61
|
+
const sourcePath = path.resolve(params.sourcePath);
|
|
62
|
+
const targetPath = path.join(params.appPath, ENV_HOOK_SCRIPT_CONFIG_PATH);
|
|
63
|
+
await mkdir(path.dirname(targetPath), { recursive: true });
|
|
64
|
+
if (path.resolve(sourcePath) !== path.resolve(targetPath)) {
|
|
65
|
+
await copyFile(sourcePath, targetPath);
|
|
66
|
+
}
|
|
67
|
+
return ENV_HOOK_SCRIPT_CONFIG_PATH;
|
|
68
|
+
}
|
|
69
|
+
const hookRunnerScript = `
|
|
70
|
+
import { pathToFileURL } from 'node:url';
|
|
71
|
+
|
|
72
|
+
const knownHookNames = ['beforeDependencyInstall', 'beforeAppInstall', 'afterAppStart'];
|
|
73
|
+
const [, hookScriptPath, hookName, contextJson] = process.argv;
|
|
74
|
+
const url = pathToFileURL(hookScriptPath);
|
|
75
|
+
url.searchParams.set('t', String(Date.now()));
|
|
76
|
+
|
|
77
|
+
const imported = await import(url.href);
|
|
78
|
+
const hooks = imported.default ?? imported;
|
|
79
|
+
|
|
80
|
+
if (!hooks || typeof hooks !== 'object' || Array.isArray(hooks)) {
|
|
81
|
+
throw new Error('Hook script must export an object.');
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (const knownHookName of knownHookNames) {
|
|
85
|
+
if (Object.prototype.hasOwnProperty.call(hooks, knownHookName) && typeof hooks[knownHookName] !== 'function') {
|
|
86
|
+
throw new Error(\`Hook "\${knownHookName}" must be a function.\`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const hook = hooks[hookName];
|
|
91
|
+
if (typeof hook === 'function') {
|
|
92
|
+
await hook(JSON.parse(contextJson));
|
|
93
|
+
}
|
|
94
|
+
`;
|
|
95
|
+
async function runHookInSubprocess(params) {
|
|
96
|
+
await new Promise((resolve, reject) => {
|
|
97
|
+
const child = spawn(process.execPath, ['--input-type=module', '--eval', hookRunnerScript, params.hookScriptPath, params.hookName, JSON.stringify(params.context)], {
|
|
98
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
99
|
+
});
|
|
100
|
+
let stdout = '';
|
|
101
|
+
let stderr = '';
|
|
102
|
+
child.stdout?.on?.('data', (chunk) => {
|
|
103
|
+
stdout += String(chunk);
|
|
104
|
+
});
|
|
105
|
+
child.stderr?.on?.('data', (chunk) => {
|
|
106
|
+
stderr += String(chunk);
|
|
107
|
+
});
|
|
108
|
+
child.once('error', reject);
|
|
109
|
+
child.once('close', (code) => {
|
|
110
|
+
if (code === 0) {
|
|
111
|
+
resolve();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const output = stderr.trim() || stdout.trim();
|
|
115
|
+
reject(new Error(output || `Hook process exited with code ${code ?? 'unknown'}.`));
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
export function buildHookContext(params) {
|
|
120
|
+
const source = normalizeHookSource(params.source);
|
|
121
|
+
if (!source) {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
const version = trimValue(params.version);
|
|
125
|
+
return {
|
|
126
|
+
phase: normalizeHookPhase(params.phase),
|
|
127
|
+
command: normalizeHookCommand(params.command),
|
|
128
|
+
envName: trimValue(params.envName),
|
|
129
|
+
source,
|
|
130
|
+
...(version ? { version } : {}),
|
|
131
|
+
appPath: params.appPath,
|
|
132
|
+
sourcePath: params.sourcePath,
|
|
133
|
+
storagePath: params.storagePath,
|
|
134
|
+
hookScript: params.hookScript,
|
|
135
|
+
envConfig: { ...(params.envConfig ?? {}) },
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
export function buildBeforeDependencyInstallHookContext(params) {
|
|
139
|
+
const context = buildHookContext(params);
|
|
140
|
+
if (!context || !isDependencyHookSource(context.source)) {
|
|
141
|
+
return undefined;
|
|
142
|
+
}
|
|
143
|
+
return context;
|
|
144
|
+
}
|
|
145
|
+
export async function runHookScriptHook(params) {
|
|
146
|
+
try {
|
|
147
|
+
await runHookInSubprocess(params);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
151
|
+
throw new Error([`Hook script failed: ${params.hookScriptPath}`, `Hook stage: ${params.hookName}`, `Details: ${message}`].join('\n'));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
export async function runBeforeDependencyInstallHook(params) {
|
|
155
|
+
await runHookScriptHook({
|
|
156
|
+
hookScriptPath: params.hookScriptPath,
|
|
157
|
+
hookName: 'beforeDependencyInstall',
|
|
158
|
+
context: params.context,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
@@ -19,7 +19,8 @@ export const PWC_FORM_META_STEP = '_pwcStep';
|
|
|
19
19
|
/** Form POST JSON meta field: current field key when validating a single field. */
|
|
20
20
|
export const PWC_FORM_META_FIELD = '_pwcField';
|
|
21
21
|
const DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;
|
|
22
|
-
const
|
|
22
|
+
const DEFAULT_PUBLIC_HOST = '127.0.0.1';
|
|
23
|
+
const LISTEN_HOST = '0.0.0.0';
|
|
23
24
|
function resolveUiText(text, locale, fallback = '') {
|
|
24
25
|
return resolveLocalizedText(text, { locale, fallback });
|
|
25
26
|
}
|
|
@@ -705,7 +706,7 @@ function runPromptCatalogWebUIImpl(options) {
|
|
|
705
706
|
const initialShow = reflowWebFormState(merged, Object.fromEntries(Object.entries(formDefaults).map(([k, v]) => [k, v])), userSeed).show;
|
|
706
707
|
const submitPath = options.submitPath ?? DEFAULT_SUBMIT;
|
|
707
708
|
const reflowPath = options.reflowPath ?? DEFAULT_REFLOW;
|
|
708
|
-
const
|
|
709
|
+
const publicHost = options.host ?? DEFAULT_PUBLIC_HOST;
|
|
709
710
|
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
710
711
|
const pageTitle = resolveUiText(options.pageTitle, locale, t('promptCatalog.web.pageTitle'));
|
|
711
712
|
const h1 = resolveUiText(options.documentHeading, locale, t('promptCatalog.web.documentHeading'));
|
|
@@ -753,7 +754,7 @@ function runPromptCatalogWebUIImpl(options) {
|
|
|
753
754
|
}
|
|
754
755
|
};
|
|
755
756
|
const servePage = (port) => {
|
|
756
|
-
const base = `http://${
|
|
757
|
+
const base = `http://${publicHost}:${port}`;
|
|
757
758
|
const formInner = buildPwcFormHtml(catalog, formDefaults, initialShow, pwcStepDefs, 0, pwcNSteps, locale, uiText);
|
|
758
759
|
const wizardClientJson = JSON.stringify({ n: pwcNSteps, stepDefs: pwcStepDefs });
|
|
759
760
|
const pwcValStepUrl = pwcNSteps > 1 ? JSON.stringify(base + resolveValidateStepPath) : 'null';
|
|
@@ -2071,11 +2072,6 @@ function runPromptCatalogWebUIImpl(options) {
|
|
|
2071
2072
|
return page;
|
|
2072
2073
|
};
|
|
2073
2074
|
server = createServer((req, res) => {
|
|
2074
|
-
if (!req.socket.remoteAddress ||
|
|
2075
|
-
!['127.0.0.1', '::1', '::ffff:127.0.0.1'].includes(req.socket.remoteAddress)) {
|
|
2076
|
-
res.writeHead(403).end();
|
|
2077
|
-
return;
|
|
2078
|
-
}
|
|
2079
2075
|
if (req.method === 'GET' && (req.url === '/' || req.url === '')) {
|
|
2080
2076
|
const addr = server?.address();
|
|
2081
2077
|
const port = typeof addr === 'object' && addr ? Number(addr.port) : 0;
|
|
@@ -2212,15 +2208,15 @@ function runPromptCatalogWebUIImpl(options) {
|
|
|
2212
2208
|
}
|
|
2213
2209
|
res.writeHead(404).end();
|
|
2214
2210
|
});
|
|
2215
|
-
server.listen(options.port ?? 0,
|
|
2211
|
+
server.listen(options.port ?? 0, LISTEN_HOST, () => {
|
|
2216
2212
|
const addr = server?.address();
|
|
2217
2213
|
if (typeof addr !== 'object' || !addr) {
|
|
2218
2214
|
rejectAndClose(new Error('Failed to bind HTTP server'));
|
|
2219
2215
|
return;
|
|
2220
2216
|
}
|
|
2221
2217
|
const port = addr.port;
|
|
2222
|
-
const startUrl = `http://${
|
|
2223
|
-
options.onServerStart?.({ host, port, url: startUrl });
|
|
2218
|
+
const startUrl = `http://${publicHost}:${port}/`;
|
|
2219
|
+
options.onServerStart?.({ host: publicHost, listenHost: LISTEN_HOST, port, url: startUrl });
|
|
2224
2220
|
const onOpenBrowserError = options.onOpenBrowserError ?? ((u, err) => console.warn(String(err), u));
|
|
2225
2221
|
try {
|
|
2226
2222
|
openUrlInDefaultBrowser(startUrl, onOpenBrowserError);
|
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
|
}
|
|
@@ -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',
|