Skip to content

Commit 8aad40f

Browse files
committed
fix and simplify
1 parent 7fcec0c commit 8aad40f

File tree

1 file changed

+56
-102
lines changed

1 file changed

+56
-102
lines changed

convert-nuget.js

Lines changed: 56 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
const fs = require('fs').promises;
88
const path = require('path');
9-
const { execSync, execFileSync } = require('child_process');
9+
const { execFileSync } = require('child_process');
1010
const { XMLParser } = require('fast-xml-parser');
1111

1212
const DEFAULT_TFM = 'net472';
@@ -170,32 +170,17 @@ ${packageRefs}
170170
async function findCsprojFile(dir) {
171171
try {
172172
const entries = await fs.readdir(dir, { withFileTypes: true });
173-
const csprojFiles = [];
174-
for (const entry of entries) {
175-
if (entry.isFile() && entry.name.endsWith('.csproj')) {
176-
const fullPath = path.join(dir, entry.name);
177-
// Validate path: ensure it resolves to expected directory (prevent path traversal)
178-
const resolvedPath = path.resolve(fullPath);
179-
const resolvedDir = path.resolve(dir);
180-
if (!resolvedPath.startsWith(resolvedDir)) {
181-
// Path traversal detected, skip this file
182-
continue;
183-
}
184-
csprojFiles.push(fullPath);
185-
}
186-
}
187-
if (csprojFiles.length === 0) {
188-
return null;
189-
}
173+
const csprojFiles = entries
174+
.filter(e => e.isFile() && e.name.endsWith('.csproj'))
175+
.map(e => path.join(dir, e.name));
176+
177+
if (csprojFiles.length === 0) return null;
190178
if (csprojFiles.length > 1) {
191179
throw new Error(`Multiple .csproj files found in ${dir}: ${csprojFiles.map(f => path.basename(f)).join(', ')}`);
192180
}
193181
return csprojFiles[0];
194182
} catch (err) {
195-
if (err.message.includes('Multiple .csproj files')) {
196-
throw err;
197-
}
198-
// Directory might not exist or be unreadable
183+
if (err.message.includes('Multiple .csproj files')) throw err;
199184
return null;
200185
}
201186
}
@@ -229,37 +214,19 @@ async function processPackagesConfig(packagesConfigPath, defaultTfm, failOnSkipp
229214
cwd: dir,
230215
stdio: 'pipe',
231216
});
232-
// Copy lock file if it exists in the project directory
233-
// Handle race condition by catching errors during copy operation
217+
// Copy lock file (check root first, then obj/)
234218
const lockPath = path.join(dir, 'packages.lock.json');
219+
const objLockPath = path.join(dir, 'obj', 'packages.lock.json');
220+
235221
try {
236-
await fs.access(lockPath);
237-
// Lock file already in place
222+
await fs.copyFile(lockPath, lockFilePath);
238223
console.log(` ✓ Lock file already exists: ${lockFilePath}`);
239224
return { success: true, skippedPackages: [] };
240225
} catch {
241-
// Lock file should be in obj/ subdirectory
242-
const objLockPath = path.join(dir, 'obj', 'packages.lock.json');
243226
try {
244-
// Use access to check existence, then copy with error handling for race conditions
245-
await fs.access(objLockPath);
246-
try {
247-
await fs.copyFile(objLockPath, lockFilePath);
248-
console.log(` ✓ Generated: ${lockFilePath}`);
249-
return { success: true, skippedPackages: [] };
250-
} catch (copyErr) {
251-
// Race condition: file might have been deleted between access and copy
252-
// Check if it still exists
253-
try {
254-
await fs.access(objLockPath);
255-
// Still exists, rethrow original error
256-
throw copyErr;
257-
} catch {
258-
// File was deleted, treat as not found
259-
console.log(` Warning: Lock file not found after restore`);
260-
return { success: false, skippedPackages: [] };
261-
}
262-
}
227+
await fs.copyFile(objLockPath, lockFilePath);
228+
console.log(` ✓ Generated: ${lockFilePath}`);
229+
return { success: true, skippedPackages: [] };
263230
} catch {
264231
console.log(` Warning: Lock file not found after restore`);
265232
return { success: false, skippedPackages: [] };
@@ -301,16 +268,8 @@ async function processPackagesConfig(packagesConfigPath, defaultTfm, failOnSkipp
301268
let restoreSuccess = false;
302269
let lastError = null;
303270

304-
// Retry loop: remove incompatible packages on NU1202 errors
305-
// Normalize version for comparison: remove trailing .0 segments (1.0.0.0 -> 1.0.0, but 1.0.0.1 -> 1.0.0.1)
306-
const normalizeVersion = v => {
307-
// Remove trailing .0 segments, but preserve non-zero segments
308-
const parts = v.split('.');
309-
while (parts.length > 1 && parts[parts.length - 1] === '0') {
310-
parts.pop();
311-
}
312-
return parts.join('.');
313-
};
271+
// Normalize version for comparison: remove trailing .0 segments
272+
const normalizeVersion = v => v.split('.').filter((p, i, arr) => i < arr.length - 1 || p !== '0').join('.');
314273

315274
while (retryCount < maxRetries && compatiblePackages.length > 0) {
316275
try {
@@ -362,18 +321,15 @@ async function processPackagesConfig(packagesConfigPath, defaultTfm, failOnSkipp
362321

363322
if (!restoreSuccess) {
364323
console.error(' Error: dotnet restore failed:');
365-
const stdout = lastError.stdout?.toString();
366-
const stderr = lastError.stderr?.toString();
367-
if (stdout) console.error(stdout);
368-
if (stderr) console.error(stderr);
369-
return { success: false, skippedPackages: skippedPackages };
324+
if (lastError.stdout) console.error(lastError.stdout.toString());
325+
if (lastError.stderr) console.error(lastError.stderr.toString());
326+
return { success: false, skippedPackages };
370327
}
371328

372329
// Copy lock file
373-
const lockPath = path.join(workdir, 'packages.lock.json');
374-
await fs.copyFile(lockPath, lockFilePath);
330+
await fs.copyFile(path.join(workdir, 'packages.lock.json'), lockFilePath);
375331
console.log(` ✓ Generated: ${lockFilePath}`);
376-
return { success: true, skippedPackages: skippedPackages };
332+
return { success: true, skippedPackages };
377333
} finally {
378334
// Cleanup
379335
await fs.rm(workdir, { recursive: true, force: true });
@@ -391,33 +347,13 @@ async function processPackagesConfig(packagesConfigPath, defaultTfm, failOnSkipp
391347
async function main() {
392348
const { tfm, rootDir, failOnSkipped } = parseArgs();
393349

394-
// Resolve root directory to absolute path
350+
// Resolve root directory to absolute path and validate
395351
const resolvedRootDir = path.resolve(rootDir);
396352

397-
// Validate path traversal: ensure resolved path is within expected bounds
398-
// Get the actual current working directory to validate against
399-
const cwd = process.cwd();
400-
const resolvedCwd = path.resolve(cwd);
401-
402-
// Check if the resolved path is actually a subdirectory or matches the expected path
403-
// This prevents path traversal attacks like ../../../etc/passwd
404-
if (!resolvedRootDir.startsWith(resolvedCwd) && resolvedRootDir !== resolvedCwd) {
405-
// Allow paths that are absolute and start with / (Unix) or drive letter (Windows)
406-
// but validate they're reasonable
407-
const isAbsolute = path.isAbsolute(rootDir);
408-
if (isAbsolute) {
409-
// For absolute paths, ensure they exist and are directories
410-
// The path.resolve() already normalized, but we need to ensure it's safe
411-
// Check that it doesn't contain suspicious patterns
412-
if (resolvedRootDir.includes('..') || resolvedRootDir.includes('~')) {
413-
console.error(`Error: Invalid root directory path: ${resolvedRootDir}`);
414-
process.exit(1);
415-
}
416-
} else {
417-
// Relative paths should resolve within cwd
418-
console.error(`Error: Root directory resolves outside expected bounds: ${resolvedRootDir}`);
419-
process.exit(1);
420-
}
353+
// Validate path traversal: ensure resolved path doesn't contain suspicious patterns
354+
if (resolvedRootDir.includes('..') || (resolvedRootDir.includes('~') && !resolvedRootDir.startsWith(process.env.HOME || ''))) {
355+
console.error(`Error: Invalid root directory path: ${resolvedRootDir}`);
356+
process.exit(1);
421357
}
422358

423359
// Check if root directory exists
@@ -432,12 +368,33 @@ async function main() {
432368
process.exit(1);
433369
}
434370

435-
// Check for dotnet
436-
try {
437-
execSync('dotnet', ['--version'], { stdio: 'ignore' });
438-
} catch (err) {
439-
console.error('Error: dotnet CLI is required but not installed');
440-
process.exit(1);
371+
// Check for dotnet - try common paths, then fall back to PATH
372+
const commonPaths = [
373+
process.env.DOTNET_ROOT && path.join(process.env.DOTNET_ROOT, 'dotnet'),
374+
'/usr/bin/dotnet',
375+
'/usr/local/bin/dotnet',
376+
'/usr/share/dotnet/dotnet'
377+
].filter(Boolean);
378+
379+
let dotnetFound = false;
380+
for (const testPath of commonPaths) {
381+
try {
382+
await fs.access(testPath);
383+
execFileSync(testPath, ['--version'], { stdio: 'ignore' });
384+
dotnetFound = true;
385+
break;
386+
} catch {
387+
continue;
388+
}
389+
}
390+
391+
if (!dotnetFound) {
392+
try {
393+
execFileSync('dotnet', ['--version'], { stdio: 'ignore' });
394+
} catch {
395+
console.error('Error: dotnet CLI is required but not installed');
396+
process.exit(1);
397+
}
441398
}
442399

443400
console.log('=== NuGet packages.config to packages.lock.json Converter ===');
@@ -457,14 +414,11 @@ async function main() {
457414
const result = await processPackagesConfig(packagesConfig, tfm, failOnSkipped);
458415
if (result.success) {
459416
successCount++;
460-
if (result.skippedPackages && result.skippedPackages.length > 0) {
461-
totalSkippedPackages.push(...result.skippedPackages);
462-
}
463417
} else {
464418
failCount++;
465-
if (result.skippedPackages && result.skippedPackages.length > 0) {
466-
totalSkippedPackages.push(...result.skippedPackages);
467-
}
419+
}
420+
if (result.skippedPackages?.length > 0) {
421+
totalSkippedPackages.push(...result.skippedPackages);
468422
}
469423
}
470424

0 commit comments

Comments
 (0)