@epic-web/workshop-utils 6.74.3 → 6.75.0
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.
|
@@ -165,6 +165,32 @@ function getArgumentsForLineNumber(editor, fileName, lineNumber, colNumber, work
|
|
|
165
165
|
// can result in errors or confusing behavior.
|
|
166
166
|
return [fileName];
|
|
167
167
|
}
|
|
168
|
+
function getWindowsProcessPaths() {
|
|
169
|
+
const systemRoot = process.env.SystemRoot ?? process.env.WINDIR;
|
|
170
|
+
const wmicPath = systemRoot
|
|
171
|
+
? path.join(systemRoot, 'System32', 'wbem', 'wmic.exe')
|
|
172
|
+
: null;
|
|
173
|
+
if (wmicPath && fs.existsSync(wmicPath)) {
|
|
174
|
+
try {
|
|
175
|
+
const output = child_process
|
|
176
|
+
.execSync(`"${wmicPath}" process where "executablepath is not null" get executablepath`, { stdio: ['ignore', 'pipe', 'ignore'] })
|
|
177
|
+
.toString();
|
|
178
|
+
return output.split(/\r?\n/);
|
|
179
|
+
}
|
|
180
|
+
catch {
|
|
181
|
+
// Fall through to PowerShell
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
try {
|
|
185
|
+
const output = child_process
|
|
186
|
+
.execSync('powershell.exe -NoProfile -Command "Get-Process | Where-Object { $_.Path } | ForEach-Object { $_.Path }"', { stdio: ['ignore', 'pipe', 'ignore'] })
|
|
187
|
+
.toString();
|
|
188
|
+
return output.split(/\r?\n/);
|
|
189
|
+
}
|
|
190
|
+
catch {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
}
|
|
168
194
|
function guessEditor() {
|
|
169
195
|
// Explicit config always wins
|
|
170
196
|
if (process.env.EPICSHOP_EDITOR) {
|
|
@@ -188,10 +214,7 @@ function guessEditor() {
|
|
|
188
214
|
else if (process.platform === 'win32') {
|
|
189
215
|
// Some processes need elevated rights to get its executable path.
|
|
190
216
|
// Just filter them out upfront. This also saves 10-20ms on the command.
|
|
191
|
-
const
|
|
192
|
-
.execSync('wmic process where "executablepath is not null" get executablepath')
|
|
193
|
-
.toString();
|
|
194
|
-
const runningProcesses = output.split('\r\n');
|
|
217
|
+
const runningProcesses = getWindowsProcessPaths();
|
|
195
218
|
for (let i = 0; i < runningProcesses.length; i++) {
|
|
196
219
|
const processPath = runningProcesses[i]?.trim();
|
|
197
220
|
if (!processPath)
|
|
@@ -7,7 +7,7 @@ export type RootPackageInstallStatus = {
|
|
|
7
7
|
missingDependencies: Array<string>;
|
|
8
8
|
missingDevDependencies: Array<string>;
|
|
9
9
|
missingOptionalDependencies: Array<string>;
|
|
10
|
-
reason: 'missing-
|
|
10
|
+
reason: 'missing-dependencies' | 'package-json-unreadable' | 'up-to-date';
|
|
11
11
|
};
|
|
12
12
|
export type WorkspaceInstallStatus = {
|
|
13
13
|
roots: Array<RootPackageInstallStatus>;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { execa } from 'execa';
|
|
4
5
|
import { globby } from 'globby';
|
|
5
6
|
import { getErrorMessage } from "../utils.js";
|
|
6
7
|
const workspaceIgnorePatterns = [
|
|
@@ -76,39 +77,53 @@ async function listPackageJsonPaths(cwd) {
|
|
|
76
77
|
ignore: workspaceIgnorePatterns,
|
|
77
78
|
});
|
|
78
79
|
}
|
|
79
|
-
|
|
80
|
+
function parseNpmLsOutput(raw) {
|
|
81
|
+
const trimmed = raw.trim();
|
|
82
|
+
if (!trimmed)
|
|
83
|
+
return null;
|
|
80
84
|
try {
|
|
81
|
-
|
|
82
|
-
const packages = new Set();
|
|
83
|
-
for (const entry of entries) {
|
|
84
|
-
if (entry.name.startsWith('.'))
|
|
85
|
-
continue;
|
|
86
|
-
if (entry.name.startsWith('@')) {
|
|
87
|
-
if (!entry.isDirectory())
|
|
88
|
-
continue;
|
|
89
|
-
const scopePath = path.join(nodeModulesPath, entry.name);
|
|
90
|
-
const scopeEntries = await fs.readdir(scopePath, {
|
|
91
|
-
withFileTypes: true,
|
|
92
|
-
});
|
|
93
|
-
for (const scopedEntry of scopeEntries) {
|
|
94
|
-
if (scopedEntry.name.startsWith('.'))
|
|
95
|
-
continue;
|
|
96
|
-
if (scopedEntry.isDirectory() || scopedEntry.isSymbolicLink()) {
|
|
97
|
-
packages.add(`${entry.name}/${scopedEntry.name}`);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
continue;
|
|
101
|
-
}
|
|
102
|
-
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
103
|
-
packages.add(entry.name);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
return packages;
|
|
85
|
+
return JSON.parse(trimmed);
|
|
107
86
|
}
|
|
108
87
|
catch {
|
|
109
88
|
return null;
|
|
110
89
|
}
|
|
111
90
|
}
|
|
91
|
+
function getFailingDependencies(expectedDependencies, output) {
|
|
92
|
+
if (!output?.dependencies)
|
|
93
|
+
return expectedDependencies;
|
|
94
|
+
return expectedDependencies.filter((dependency) => {
|
|
95
|
+
const entry = output.dependencies?.[dependency];
|
|
96
|
+
if (!entry)
|
|
97
|
+
return true;
|
|
98
|
+
return Boolean(entry.missing || entry.invalid);
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
async function checkDependenciesWithNpmLs(rootDir, expectedDependencies, packageManager) {
|
|
102
|
+
if (expectedDependencies.length === 0) {
|
|
103
|
+
return { ok: true, failingDependencies: [] };
|
|
104
|
+
}
|
|
105
|
+
// Use the detected package manager, defaulting to npm
|
|
106
|
+
// pnpm has compatible ls output format
|
|
107
|
+
const command = packageManager === 'pnpm' ? 'pnpm' : 'npm';
|
|
108
|
+
try {
|
|
109
|
+
const result = await execa(command, ['ls', '--depth=0', '--json', ...expectedDependencies], { cwd: rootDir, reject: false });
|
|
110
|
+
const output = parseNpmLsOutput(result.stdout);
|
|
111
|
+
const ok = result.exitCode === 0;
|
|
112
|
+
const failingDependencies = ok
|
|
113
|
+
? []
|
|
114
|
+
: getFailingDependencies(expectedDependencies, output);
|
|
115
|
+
return {
|
|
116
|
+
ok,
|
|
117
|
+
failingDependencies: ok || failingDependencies.length > 0
|
|
118
|
+
? failingDependencies
|
|
119
|
+
: expectedDependencies,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
console.warn(`⚠️ Failed to run npm ls in ${rootDir}:`, getErrorMessage(error));
|
|
124
|
+
return { ok: false, failingDependencies: expectedDependencies };
|
|
125
|
+
}
|
|
126
|
+
}
|
|
112
127
|
function getExpectedDependencies(dependencies) {
|
|
113
128
|
return Object.keys(dependencies ?? {}).sort();
|
|
114
129
|
}
|
|
@@ -148,11 +163,7 @@ export async function getRootPackageInstallStatus(packageJsonPath) {
|
|
|
148
163
|
const dependencies = getExpectedDependencies(packageJson.dependencies);
|
|
149
164
|
const devDependencies = getExpectedDependencies(packageJson.devDependencies);
|
|
150
165
|
const optionalDependencies = getExpectedDependencies(packageJson.optionalDependencies);
|
|
151
|
-
const expectedDependencies = [
|
|
152
|
-
...dependencies,
|
|
153
|
-
...devDependencies,
|
|
154
|
-
...optionalDependencies,
|
|
155
|
-
];
|
|
166
|
+
const expectedDependencies = Array.from(new Set([...dependencies, ...devDependencies, ...optionalDependencies]));
|
|
156
167
|
if (expectedDependencies.length === 0) {
|
|
157
168
|
return {
|
|
158
169
|
rootDir,
|
|
@@ -166,23 +177,12 @@ export async function getRootPackageInstallStatus(packageJsonPath) {
|
|
|
166
177
|
reason: 'up-to-date',
|
|
167
178
|
};
|
|
168
179
|
}
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
dependencyHash,
|
|
176
|
-
dependenciesNeedInstall: true,
|
|
177
|
-
missingDependencies: dependencies,
|
|
178
|
-
missingDevDependencies: devDependencies,
|
|
179
|
-
missingOptionalDependencies: optionalDependencies,
|
|
180
|
-
reason: 'missing-node-modules',
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
const missingDependencies = dependencies.filter((dep) => !installedPackages.has(dep));
|
|
184
|
-
const missingDevDependencies = devDependencies.filter((dep) => !installedPackages.has(dep));
|
|
185
|
-
const missingOptionalDependencies = optionalDependencies.filter((dep) => !installedPackages.has(dep));
|
|
180
|
+
const npmLsResult = await checkDependenciesWithNpmLs(rootDir, expectedDependencies, packageManager);
|
|
181
|
+
const failingDependencies = new Set(npmLsResult.failingDependencies);
|
|
182
|
+
const missingDependencies = dependencies.filter((dep) => failingDependencies.has(dep));
|
|
183
|
+
const missingDevDependencies = devDependencies.filter((dep) => failingDependencies.has(dep));
|
|
184
|
+
const missingOptionalDependencies = optionalDependencies.filter((dep) => failingDependencies.has(dep));
|
|
185
|
+
// Optional dependencies should not trigger install requirement
|
|
186
186
|
const dependenciesNeedInstall = missingDependencies.length > 0 || missingDevDependencies.length > 0;
|
|
187
187
|
return {
|
|
188
188
|
rootDir,
|