plusui-native 0.2.100 → 0.2.103
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "plusui-native",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.103",
|
|
4
4
|
"description": "PlusUI CLI - Build C++ desktop apps modern UI ",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
"semver": "^7.6.0",
|
|
28
28
|
"which": "^4.0.0",
|
|
29
29
|
"execa": "^8.0.1",
|
|
30
|
-
"plusui-native-builder": "^0.1.
|
|
31
|
-
"plusui-native-connect": "^0.1.
|
|
30
|
+
"plusui-native-builder": "^0.1.101",
|
|
31
|
+
"plusui-native-connect": "^0.1.101"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
-
"plusui-native-connect": "^0.1.
|
|
34
|
+
"plusui-native-connect": "^0.1.101"
|
|
35
35
|
},
|
|
36
36
|
"publishConfig": {
|
|
37
37
|
"access": "public"
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { execSync } from 'child_process';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { join, dirname } from 'path';
|
|
2
4
|
import semver from 'semver';
|
|
3
5
|
|
|
4
6
|
const REQUIRED_JUST_VERSION = '1.0.0';
|
|
@@ -16,30 +18,71 @@ async function tryCommand(command) {
|
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
20
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
// Known install locations for just.exe on Windows.
|
|
22
|
+
function getJustCandidatePaths() {
|
|
23
|
+
if (process.platform !== 'win32') return [];
|
|
24
|
+
const home = process.env.USERPROFILE || process.env.HOME || '';
|
|
25
|
+
const localApp = process.env.LOCALAPPDATA || join(home, 'AppData', 'Local');
|
|
26
|
+
return [
|
|
27
|
+
join(localApp, 'Microsoft', 'WinGet', 'Links', 'just.exe'),
|
|
28
|
+
join(localApp, 'Microsoft', 'WinGet', 'Packages', 'Casey.Just_Microsoft.Winget.Source_8wekyb3d8bbwe', 'just.exe'),
|
|
29
|
+
'C:\\Program Files\\just\\just.exe',
|
|
30
|
+
join(home, '.plusui', 'bin', 'just.exe'),
|
|
31
|
+
join(home, '.cargo', 'bin', 'just.exe'),
|
|
32
|
+
join(home, 'scoop', 'shims', 'just.exe'),
|
|
33
|
+
join(home, 'scoop', 'apps', 'just', 'current', 'just.exe'),
|
|
34
|
+
];
|
|
35
|
+
}
|
|
21
36
|
|
|
22
|
-
|
|
37
|
+
export async function detectJust() {
|
|
38
|
+
// 1. Try PATH first
|
|
39
|
+
const pathResult = await tryCommand('just --version');
|
|
40
|
+
if (pathResult.success) {
|
|
41
|
+
const versionMatch = pathResult.output.match(/just (\d+\.\d+\.\d+)/);
|
|
42
|
+
const version = versionMatch ? versionMatch[1] : null;
|
|
23
43
|
return {
|
|
24
|
-
found:
|
|
25
|
-
|
|
26
|
-
|
|
44
|
+
found: true,
|
|
45
|
+
inPath: true,
|
|
46
|
+
version: version || 'unknown',
|
|
47
|
+
valid: version ? semver.gte(version, REQUIRED_JUST_VERSION) : true,
|
|
27
48
|
requiredVersion: REQUIRED_JUST_VERSION
|
|
28
49
|
};
|
|
29
50
|
}
|
|
30
51
|
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
52
|
+
// 2. On Windows, probe known install locations
|
|
53
|
+
if (process.platform === 'win32') {
|
|
54
|
+
for (const candidate of getJustCandidatePaths()) {
|
|
55
|
+
if (existsSync(candidate)) {
|
|
56
|
+
const result = await tryCommand(`"${candidate}" --version`);
|
|
57
|
+
if (result.success) {
|
|
58
|
+
const versionMatch = result.output.match(/just (\d+\.\d+\.\d+)/);
|
|
59
|
+
const version = versionMatch ? versionMatch[1] : null;
|
|
34
60
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
61
|
+
// Inject the directory into the current process PATH so subsequent
|
|
62
|
+
// calls (e.g. the Justfile runner) can find just without a new terminal.
|
|
63
|
+
const dir = dirname(candidate);
|
|
64
|
+
if (!process.env.PATH.includes(dir)) {
|
|
65
|
+
process.env.PATH = dir + ';' + process.env.PATH;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
found: true,
|
|
70
|
+
inPath: false, // found on disk but not originally in PATH
|
|
71
|
+
foundPath: candidate,
|
|
72
|
+
version: version || 'unknown',
|
|
73
|
+
valid: version ? semver.gte(version, REQUIRED_JUST_VERSION) : true,
|
|
74
|
+
requiredVersion: REQUIRED_JUST_VERSION
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
38
80
|
|
|
39
81
|
return {
|
|
40
|
-
found:
|
|
41
|
-
|
|
42
|
-
|
|
82
|
+
found: false,
|
|
83
|
+
inPath: false,
|
|
84
|
+
version: null,
|
|
85
|
+
valid: false,
|
|
43
86
|
requiredVersion: REQUIRED_JUST_VERSION
|
|
44
87
|
};
|
|
45
88
|
}
|
package/src/doctor/index.js
CHANGED
|
@@ -102,11 +102,16 @@ export class EnvironmentDoctor {
|
|
|
102
102
|
console.log(chalk.bold('\nInstallation Results\n'));
|
|
103
103
|
console.log('====================\n');
|
|
104
104
|
|
|
105
|
+
let anyPathRefreshNeeded = false;
|
|
106
|
+
|
|
105
107
|
for (const result of fixResults) {
|
|
106
108
|
if (result.success) {
|
|
107
|
-
|
|
108
|
-
if (
|
|
109
|
-
console.log(chalk.
|
|
109
|
+
const alreadyHad = result.message && /already installed/i.test(result.message);
|
|
110
|
+
if (alreadyHad) {
|
|
111
|
+
console.log(chalk.green(`✓ ${result.name} already installed and up to date`));
|
|
112
|
+
} else {
|
|
113
|
+
console.log(chalk.green(`✓ ${result.name} installed successfully`));
|
|
114
|
+
if (result.pathRefreshNeeded) anyPathRefreshNeeded = true;
|
|
110
115
|
}
|
|
111
116
|
} else {
|
|
112
117
|
console.log(chalk.red(`✗ ${result.name} installation failed`));
|
|
@@ -128,8 +133,18 @@ export class EnvironmentDoctor {
|
|
|
128
133
|
}
|
|
129
134
|
}
|
|
130
135
|
|
|
136
|
+
if (anyPathRefreshNeeded) {
|
|
137
|
+
console.log(chalk.yellow('\n ACTION REQUIRED: Refresh your PATH to use the installed tools.'));
|
|
138
|
+
if (this.platform === 'win32') {
|
|
139
|
+
console.log(chalk.white(' Run this in your current terminal to reload PATH without reopening it:'));
|
|
140
|
+
console.log(chalk.cyan('\n $env:PATH = [System.Environment]::GetEnvironmentVariable("PATH","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("PATH","User")\n'));
|
|
141
|
+
} else {
|
|
142
|
+
console.log(chalk.white(' Run: source ~/.bashrc (or open a new terminal)\n'));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
131
146
|
// Re-run diagnosis to verify
|
|
132
|
-
console.log(chalk.bold('\
|
|
147
|
+
console.log(chalk.bold('\nVerifying installation...\n'));
|
|
133
148
|
const newResults = await this.diagnose();
|
|
134
149
|
|
|
135
150
|
return newResults;
|
|
@@ -67,6 +67,35 @@ const TOOL_NAMES = {
|
|
|
67
67
|
|
|
68
68
|
export async function installTool(toolName) {
|
|
69
69
|
const name = TOOL_NAMES[toolName] || toolName;
|
|
70
|
+
|
|
71
|
+
// just: use the official install script with prebuilt binaries
|
|
72
|
+
if (toolName === 'just') {
|
|
73
|
+
console.log(`Installing ${name} from prebuilt binaries...`);
|
|
74
|
+
const destDir = `${process.env.HOME || '~'}/.plusui/bin`;
|
|
75
|
+
await tryCommand(`mkdir -p "${destDir}"`);
|
|
76
|
+
const result = await tryCommand(
|
|
77
|
+
`curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to "${destDir}"`,
|
|
78
|
+
120000
|
|
79
|
+
);
|
|
80
|
+
if (result.success) {
|
|
81
|
+
// Add to PATH in shell profiles
|
|
82
|
+
const addToPath = `grep -qF '.plusui/bin' "$HOME/.bashrc" 2>/dev/null || echo 'export PATH="$HOME/.plusui/bin:$PATH"' >> "$HOME/.bashrc"`;
|
|
83
|
+
await tryCommand(addToPath, 5000);
|
|
84
|
+
// Inject into current process PATH immediately
|
|
85
|
+
if (!process.env.PATH.includes(destDir)) {
|
|
86
|
+
process.env.PATH = destDir + ':' + process.env.PATH;
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
success: true,
|
|
90
|
+
message: `${name} installed successfully`,
|
|
91
|
+
output: result.output,
|
|
92
|
+
pathRefreshNeeded: true
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// Fall through to package manager if script failed
|
|
96
|
+
console.log(` Direct install failed, trying package manager...`);
|
|
97
|
+
}
|
|
98
|
+
|
|
70
99
|
const pm = await detectPackageManager();
|
|
71
100
|
|
|
72
101
|
if (!pm) {
|
|
@@ -90,8 +119,6 @@ export async function installTool(toolName) {
|
|
|
90
119
|
}
|
|
91
120
|
|
|
92
121
|
console.log(`Installing ${name} via ${pm}...`);
|
|
93
|
-
console.log(`Running: ${command}`);
|
|
94
|
-
|
|
95
122
|
const result = await tryCommand(command);
|
|
96
123
|
|
|
97
124
|
if (result.success) {
|
|
@@ -30,6 +30,9 @@ const INSTALL_COMMANDS = {
|
|
|
30
30
|
name: 'Node.js'
|
|
31
31
|
},
|
|
32
32
|
just: {
|
|
33
|
+
// Use the official install script with prebuilt binaries as primary method
|
|
34
|
+
installScript: `curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to "$HOME/.plusui/bin"`,
|
|
35
|
+
addToPath: `grep -qF '$HOME/.plusui/bin' "$HOME/.zshrc" 2>/dev/null || echo 'export PATH="$HOME/.plusui/bin:$PATH"' >> "$HOME/.zshrc"; grep -qF '$HOME/.plusui/bin' "$HOME/.bash_profile" 2>/dev/null || echo 'export PATH="$HOME/.plusui/bin:$PATH"' >> "$HOME/.bash_profile"`,
|
|
33
36
|
command: 'brew install just',
|
|
34
37
|
manual: 'https://github.com/casey/just/releases',
|
|
35
38
|
name: 'Just Command Runner'
|
|
@@ -72,6 +75,33 @@ export async function installTool(toolName) {
|
|
|
72
75
|
};
|
|
73
76
|
}
|
|
74
77
|
|
|
78
|
+
// just: use the official install script with prebuilt binaries
|
|
79
|
+
if (toolName === 'just' && tool.installScript) {
|
|
80
|
+
console.log(`Installing ${tool.name} from prebuilt binaries...`);
|
|
81
|
+
const destDir = `${process.env.HOME || '~'}/.plusui/bin`;
|
|
82
|
+
const mkdirResult = await tryCommand(`mkdir -p "${destDir}"`);
|
|
83
|
+
const result = await tryCommand(
|
|
84
|
+
`curl --proto '=https' --tlsv1.2 -sSf https://just.systems/install.sh | bash -s -- --to "${destDir}"`,
|
|
85
|
+
120000
|
|
86
|
+
);
|
|
87
|
+
if (result.success) {
|
|
88
|
+
// Add to PATH in shell profiles
|
|
89
|
+
await tryCommand(tool.addToPath, 5000);
|
|
90
|
+
// Inject into current process PATH immediately
|
|
91
|
+
if (!process.env.PATH.includes(destDir)) {
|
|
92
|
+
process.env.PATH = destDir + ':' + process.env.PATH;
|
|
93
|
+
}
|
|
94
|
+
return {
|
|
95
|
+
success: true,
|
|
96
|
+
message: `${tool.name} installed successfully`,
|
|
97
|
+
output: result.output,
|
|
98
|
+
pathRefreshNeeded: true
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// Fall through to Homebrew if script failed
|
|
102
|
+
console.log(` Direct install failed, trying Homebrew...`);
|
|
103
|
+
}
|
|
104
|
+
|
|
75
105
|
// Check if Homebrew is available
|
|
76
106
|
const hasHomebrew = await checkHomebrew();
|
|
77
107
|
|
|
@@ -91,7 +121,7 @@ export async function installTool(toolName) {
|
|
|
91
121
|
};
|
|
92
122
|
}
|
|
93
123
|
|
|
94
|
-
// Attempt auto-installation
|
|
124
|
+
// Attempt auto-installation via Homebrew
|
|
95
125
|
console.log(`Installing ${tool.name} via Homebrew...`);
|
|
96
126
|
const result = await tryCommand(tool.command);
|
|
97
127
|
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { execSync } from 'child_process';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { homedir } from 'os';
|
|
2
5
|
|
|
3
6
|
async function tryCommand(command, timeout = 30000) {
|
|
4
7
|
try {
|
|
@@ -20,40 +23,110 @@ async function checkWinget() {
|
|
|
20
23
|
return result.success;
|
|
21
24
|
}
|
|
22
25
|
|
|
26
|
+
// Run winget via cmd shell with output redirected to a temp file so it works
|
|
27
|
+
// in non-interactive (piped) Node child processes where winget goes silent.
|
|
28
|
+
async function tryWinget(args, timeout = 300000) {
|
|
29
|
+
const tmp = join(process.env.TEMP || 'C:\\Temp', `plusui_winget_${Date.now()}.txt`);
|
|
30
|
+
let exitOk = false;
|
|
31
|
+
try {
|
|
32
|
+
execSync(`cmd /c "winget ${args} > "${tmp}" 2>&1"`, { stdio: 'ignore', timeout, shell: false });
|
|
33
|
+
exitOk = true;
|
|
34
|
+
} catch (_) {}
|
|
35
|
+
let output = '';
|
|
36
|
+
try { output = execSync(`type "${tmp}"`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim(); } catch (_) {}
|
|
37
|
+
try { execSync(`del /f /q "${tmp}"`, { stdio: 'ignore' }); } catch (_) {}
|
|
38
|
+
return { success: exitOk, output };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Install just using the official prebuilt binaries from GitHub releases.
|
|
42
|
+
// Uses PowerShell to download and extract — no bash required on Windows.
|
|
43
|
+
async function installJustFromGitHub(destDir) {
|
|
44
|
+
const justExe = join(destDir, 'just.exe');
|
|
45
|
+
|
|
46
|
+
// Ensure destination directory exists
|
|
47
|
+
try { execSync(`cmd /c "mkdir "${destDir}" 2>nul"`, { stdio: 'ignore' }); } catch (_) {}
|
|
48
|
+
|
|
49
|
+
const psLines = [
|
|
50
|
+
`$ErrorActionPreference = 'Stop'`,
|
|
51
|
+
`$dest = '${destDir.replace(/\\/g, '\\\\')}'`,
|
|
52
|
+
`$exe = '${justExe.replace(/\\/g, '\\\\')}'`,
|
|
53
|
+
// Fetch latest release metadata
|
|
54
|
+
`$rel = Invoke-RestMethod -Uri 'https://api.github.com/repos/casey/just/releases/latest' -UseBasicParsing`,
|
|
55
|
+
// Pick the Windows x86_64 MSVC zip
|
|
56
|
+
`$asset = $rel.assets | Where-Object { $_.name -match 'x86_64-pc-windows-msvc\\.zip$' } | Select-Object -First 1`,
|
|
57
|
+
`if (-not $asset) { throw 'No Windows asset found in latest just release' }`,
|
|
58
|
+
`$zip = Join-Path $env:TEMP 'just_install.zip'`,
|
|
59
|
+
`Invoke-WebRequest -Uri $asset.browser_download_url -OutFile $zip -UseBasicParsing`,
|
|
60
|
+
`Expand-Archive -Path $zip -DestinationPath $dest -Force`,
|
|
61
|
+
`Remove-Item $zip -Force`,
|
|
62
|
+
`Write-Host "just installed to $exe"`,
|
|
63
|
+
];
|
|
64
|
+
|
|
65
|
+
const result = await tryCommand(
|
|
66
|
+
`powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "${psLines.join('; ')}"`,
|
|
67
|
+
120000
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
if (!result.success || !existsSync(justExe)) {
|
|
71
|
+
return { success: false, output: result.stdout + result.stderr };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Add destDir to user PATH permanently (registry)
|
|
75
|
+
const addToPath = [
|
|
76
|
+
`$p = [Environment]::GetEnvironmentVariable('PATH','User')`,
|
|
77
|
+
`$d = '${destDir.replace(/\\/g, '\\\\')}' `,
|
|
78
|
+
`if ($p -notlike "*$d*") { [Environment]::SetEnvironmentVariable('PATH', ($p.TrimEnd(';') + ';' + $d), 'User') }`,
|
|
79
|
+
];
|
|
80
|
+
await tryCommand(
|
|
81
|
+
`powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command "${addToPath.join('; ')}"`,
|
|
82
|
+
10000
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Also inject into the current process PATH so tools work immediately
|
|
86
|
+
if (!process.env.PATH.includes(destDir)) {
|
|
87
|
+
process.env.PATH = destDir + ';' + process.env.PATH;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return { success: true, justExe };
|
|
91
|
+
}
|
|
92
|
+
|
|
23
93
|
const INSTALL_COMMANDS = {
|
|
24
94
|
cmake: {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
// elevation automatically when needed.
|
|
28
|
-
command: 'winget install -e --id Kitware.CMake --accept-package-agreements --accept-source-agreements --disable-interactivity',
|
|
95
|
+
wingetId: 'Kitware.CMake',
|
|
96
|
+
wingetArgs: 'install -e --id Kitware.CMake --accept-package-agreements --accept-source-agreements --disable-interactivity',
|
|
29
97
|
manual: 'https://cmake.org/download/',
|
|
30
98
|
name: 'CMake',
|
|
31
99
|
timeout: 300000,
|
|
32
100
|
requiresElevation: true
|
|
33
101
|
},
|
|
34
102
|
nodejs: {
|
|
35
|
-
|
|
103
|
+
wingetId: 'OpenJS.NodeJS',
|
|
104
|
+
wingetArgs: 'install -e --id OpenJS.NodeJS --scope user --accept-package-agreements --accept-source-agreements --disable-interactivity',
|
|
36
105
|
manual: 'https://nodejs.org/',
|
|
37
106
|
name: 'Node.js',
|
|
38
107
|
timeout: 300000
|
|
39
108
|
},
|
|
40
109
|
just: {
|
|
41
|
-
|
|
110
|
+
wingetId: 'Casey.Just',
|
|
111
|
+
wingetArgs: 'install -e --id Casey.Just --scope user --accept-package-agreements --accept-source-agreements --disable-interactivity',
|
|
42
112
|
manual: 'https://github.com/casey/just/releases',
|
|
43
113
|
name: 'Just Command Runner',
|
|
44
114
|
timeout: 300000
|
|
45
115
|
},
|
|
46
116
|
webview2: {
|
|
47
|
-
|
|
117
|
+
wingetId: 'Microsoft.EdgeWebView2Runtime',
|
|
118
|
+
wingetArgs: 'install -e --id Microsoft.EdgeWebView2Runtime --scope user --accept-package-agreements --accept-source-agreements --disable-interactivity',
|
|
48
119
|
manual: 'https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section',
|
|
49
120
|
name: 'WebView2 Runtime',
|
|
50
121
|
timeout: 300000
|
|
51
122
|
},
|
|
52
123
|
visualstudio: {
|
|
53
|
-
|
|
124
|
+
wingetId: 'Microsoft.VisualStudio.2022.BuildTools',
|
|
125
|
+
wingetArgs: 'install -e --id Microsoft.VisualStudio.2022.BuildTools --accept-package-agreements --accept-source-agreements --disable-interactivity --override "--wait --quiet --norestart --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.VC.CMake.Project --add Microsoft.VisualStudio.Component.Windows11SDK.22621"',
|
|
54
126
|
manual: 'https://visualstudio.microsoft.com/downloads/',
|
|
55
127
|
name: 'Visual Studio 2022',
|
|
56
128
|
timeout: 3600000,
|
|
129
|
+
requiresElevation: true,
|
|
57
130
|
instructions: [
|
|
58
131
|
'1. Download Visual Studio 2022 Build Tools or Community (free)',
|
|
59
132
|
'2. Run the installer',
|
|
@@ -70,61 +143,59 @@ export async function installTool(toolName) {
|
|
|
70
143
|
const tool = INSTALL_COMMANDS[toolName];
|
|
71
144
|
|
|
72
145
|
if (!tool) {
|
|
73
|
-
return {
|
|
74
|
-
success: false,
|
|
75
|
-
error: `Unknown tool: ${toolName}`
|
|
76
|
-
};
|
|
146
|
+
return { success: false, error: `Unknown tool: ${toolName}` };
|
|
77
147
|
}
|
|
78
148
|
|
|
79
|
-
//
|
|
80
|
-
|
|
149
|
+
// just gets its own install path using the official prebuilt binaries
|
|
150
|
+
if (toolName === 'just') {
|
|
151
|
+
return installJust(tool);
|
|
152
|
+
}
|
|
81
153
|
|
|
82
|
-
|
|
154
|
+
// All other tools go through winget
|
|
155
|
+
const hasWinget = await checkWinget();
|
|
156
|
+
if (!hasWinget) {
|
|
83
157
|
return {
|
|
84
158
|
success: false,
|
|
85
|
-
autoInstallAvailable: false,
|
|
86
|
-
manual: tool.manual,
|
|
87
159
|
message: 'winget is not available. Please install manually.',
|
|
88
160
|
downloadUrl: tool.manual
|
|
89
161
|
};
|
|
90
162
|
}
|
|
91
163
|
|
|
92
|
-
// Attempt auto-installation
|
|
93
164
|
console.log(`Installing ${tool.name} via winget...`);
|
|
94
|
-
const result = await
|
|
165
|
+
const result = await tryWinget(tool.wingetArgs, tool.timeout || 300000);
|
|
95
166
|
|
|
96
167
|
if (result.success) {
|
|
97
|
-
return {
|
|
98
|
-
success: true,
|
|
99
|
-
message: `${tool.name} installed successfully`,
|
|
100
|
-
output: result.output,
|
|
101
|
-
// Remind callers that the current process PATH won't include the new
|
|
102
|
-
// install – they should re-spawn or check known filesystem paths.
|
|
103
|
-
pathRefreshNeeded: true
|
|
104
|
-
};
|
|
168
|
+
return { success: true, message: `${tool.name} installed successfully`, output: result.output, pathRefreshNeeded: true };
|
|
105
169
|
}
|
|
106
170
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
171
|
+
const alreadyInstalled =
|
|
172
|
+
/No newer package versions are available/i.test(result.output) ||
|
|
173
|
+
/No available upgrade found/i.test(result.output) ||
|
|
174
|
+
/already installed/i.test(result.output);
|
|
175
|
+
|
|
176
|
+
if (alreadyInstalled) {
|
|
177
|
+
const upgradeResult = await tryWinget(
|
|
178
|
+
`upgrade -e --id ${tool.wingetId} --accept-package-agreements --accept-source-agreements --disable-interactivity`,
|
|
179
|
+
tool.timeout || 300000
|
|
180
|
+
);
|
|
181
|
+
if (upgradeResult.success) {
|
|
182
|
+
return { success: true, message: `${tool.name} installed successfully`, output: upgradeResult.output, pathRefreshNeeded: true };
|
|
183
|
+
}
|
|
184
|
+
return { success: true, message: `${tool.name} is already installed and up to date`, output: result.output, pathRefreshNeeded: true };
|
|
185
|
+
}
|
|
111
186
|
|
|
112
187
|
const failResult = {
|
|
113
188
|
success: false,
|
|
114
|
-
autoInstallAvailable: true,
|
|
115
|
-
error: result.error,
|
|
116
189
|
message: `Failed to install ${tool.name} automatically`,
|
|
117
|
-
reason,
|
|
118
|
-
manual: tool.manual,
|
|
190
|
+
reason: result.output || 'Unknown error',
|
|
119
191
|
downloadUrl: tool.manual
|
|
120
192
|
};
|
|
121
193
|
|
|
122
194
|
if (tool.requiresElevation) {
|
|
123
195
|
failResult.instructions = [
|
|
124
196
|
'This tool requires administrator rights to install system-wide.',
|
|
125
|
-
'Option 1: Re-run from an elevated (Run as Administrator) terminal:',
|
|
126
|
-
|
|
127
|
-
`Option 2: Install manually via winget (elevated): ${tool.command}`,
|
|
197
|
+
'Option 1: Re-run from an elevated (Run as Administrator) terminal: plusui doctor --fix',
|
|
198
|
+
`Option 2: winget ${tool.wingetArgs}`,
|
|
128
199
|
`Option 3: Download from: ${tool.manual}`
|
|
129
200
|
];
|
|
130
201
|
}
|
|
@@ -132,15 +203,65 @@ export async function installTool(toolName) {
|
|
|
132
203
|
return failResult;
|
|
133
204
|
}
|
|
134
205
|
|
|
206
|
+
async function installJust(tool) {
|
|
207
|
+
const destDir = join(homedir(), '.plusui', 'bin');
|
|
208
|
+
console.log(`Installing ${tool.name} from prebuilt binaries...`);
|
|
209
|
+
|
|
210
|
+
// Use the official prebuilt binary from GitHub releases
|
|
211
|
+
const ghResult = await installJustFromGitHub(destDir);
|
|
212
|
+
if (ghResult.success) {
|
|
213
|
+
return {
|
|
214
|
+
success: true,
|
|
215
|
+
message: `${tool.name} installed successfully`,
|
|
216
|
+
output: `Installed to ${ghResult.justExe}`,
|
|
217
|
+
pathRefreshNeeded: true
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Fallback: try winget
|
|
222
|
+
console.log(` Direct download failed, trying winget...`);
|
|
223
|
+
const wingetResult = await tryWinget(tool.wingetArgs, tool.timeout || 300000);
|
|
224
|
+
if (wingetResult.success) {
|
|
225
|
+
return { success: true, message: `${tool.name} installed successfully`, output: wingetResult.output, pathRefreshNeeded: true };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const wingetSaysInstalled =
|
|
229
|
+
/No newer package versions are available/i.test(wingetResult.output) ||
|
|
230
|
+
/No available upgrade found/i.test(wingetResult.output) ||
|
|
231
|
+
/already installed/i.test(wingetResult.output);
|
|
232
|
+
|
|
233
|
+
if (wingetSaysInstalled) {
|
|
234
|
+
const upgradeResult = await tryWinget(
|
|
235
|
+
`upgrade -e --id ${tool.wingetId} --scope user --accept-package-agreements --accept-source-agreements --disable-interactivity`,
|
|
236
|
+
tool.timeout || 300000
|
|
237
|
+
);
|
|
238
|
+
if (upgradeResult.success) {
|
|
239
|
+
return { success: true, message: `${tool.name} installed successfully`, output: upgradeResult.output, pathRefreshNeeded: true };
|
|
240
|
+
}
|
|
241
|
+
return { success: true, message: `${tool.name} is already installed and up to date`, output: wingetResult.output, pathRefreshNeeded: true };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
success: false,
|
|
246
|
+
message: `Failed to install ${tool.name} automatically`,
|
|
247
|
+
reason: ghResult.output || wingetResult.output || 'All install methods failed',
|
|
248
|
+
downloadUrl: tool.manual,
|
|
249
|
+
instructions: [
|
|
250
|
+
`winget install -e --id Casey.Just`,
|
|
251
|
+
`Or download from: ${tool.manual}`,
|
|
252
|
+
`Or via cargo: cargo install just`,
|
|
253
|
+
]
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
|
|
135
257
|
export function getInstallInstructions(toolName) {
|
|
136
258
|
const tool = INSTALL_COMMANDS[toolName];
|
|
137
259
|
if (!tool) return null;
|
|
138
|
-
|
|
139
260
|
return {
|
|
140
261
|
name: tool.name,
|
|
141
262
|
manual: tool.manual,
|
|
142
263
|
instructions: tool.instructions || [
|
|
143
|
-
`Install via winget: ${tool.
|
|
264
|
+
`Install via winget: winget ${tool.wingetArgs}`,
|
|
144
265
|
`Or download from: ${tool.manual}`
|
|
145
266
|
]
|
|
146
267
|
};
|
package/src/doctor/reporter.js
CHANGED
|
@@ -72,7 +72,12 @@ export class DoctorReporter {
|
|
|
72
72
|
// Just
|
|
73
73
|
if (results.just.found) {
|
|
74
74
|
if (results.just.valid) {
|
|
75
|
-
|
|
75
|
+
if (results.just.inPath === false && results.just.foundPath) {
|
|
76
|
+
output += chalk.yellow(`⚠ Just v${results.just.version} (installed but not in PATH yet — open a new terminal)\n`);
|
|
77
|
+
output += chalk.gray(` Found at: ${results.just.foundPath}\n`);
|
|
78
|
+
} else {
|
|
79
|
+
output += chalk.green(`✓ Just v${results.just.version}\n`);
|
|
80
|
+
}
|
|
76
81
|
} else {
|
|
77
82
|
output += chalk.yellow(`⚠ Just v${results.just.version} (requires >= ${results.just.requiredVersion})\n`);
|
|
78
83
|
}
|
|
@@ -126,8 +131,19 @@ export class DoctorReporter {
|
|
|
126
131
|
if (missingTools.length === 0) {
|
|
127
132
|
output += chalk.green('Status: ✓ Ready to build PlusUI apps!\n');
|
|
128
133
|
} else {
|
|
134
|
+
const missingNames = missingTools.map(t => {
|
|
135
|
+
switch (t.name) {
|
|
136
|
+
case 'nodejs': return 'Node.js';
|
|
137
|
+
case 'cmake': return 'CMake';
|
|
138
|
+
case 'compiler': return t.info.name || 'C++ Compiler';
|
|
139
|
+
case 'just': return 'Just';
|
|
140
|
+
case 'webview2': return 'WebView2';
|
|
141
|
+
default: return t.name;
|
|
142
|
+
}
|
|
143
|
+
});
|
|
129
144
|
output += chalk.red(`Status: ✗ Needs Setup\n`);
|
|
130
|
-
output += `Missing: ${
|
|
145
|
+
output += `Missing: ${missingNames.join(', ')}\n`;
|
|
146
|
+
output += chalk.yellow(`Run: plusui doctor --fix to install automatically\n`);
|
|
131
147
|
}
|
|
132
148
|
|
|
133
149
|
console.log(output);
|