mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-07-09 17:19:02 +00:00
fix(vscode-ide-companion): exclude workspace packages from NOTICES.txt generation (#4455)
* fix(vscode-ide-companion): exclude workspace packages from NOTICES.txt generation Workspace-linked packages (link: true in package-lock.json) have no version field, causing @undefined entries in NOTICES.txt. Skip them during dependency collection since they are first-party code. Regenerated NOTICES.txt on top of the express 5.2.1 lockfile update. Closes #4446 * fix(vscode-ide-companion): resolve workspace and nested deps in NOTICES.txt generation Rewrite collectDependencies to mirror Node.js module resolution: - Walk up node_modules chain from each package's location (not just root) - Follow workspace link resolved pointers to collect their third-party deps - Pass resolved lockfile key to getDependencyLicense for exact path lookup Previously @modelcontextprotocol/sdk and its transitive deps (zod-to-json-schema, ajv@8, etc.) were missing because they are installed under the workspace's local node_modules and were never hoisted to root. Also fixes version mismatch where ajv@6.12.6 license was incorrectly used instead of ajv@8.17.1. Total NOTICES entries: 80 → 102.
This commit is contained in:
parent
5aca042421
commit
92d533265c
2 changed files with 889 additions and 347 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -14,27 +14,26 @@ const projectRoot = path.resolve(
|
|||
const packagePath = path.join(projectRoot, 'packages', 'vscode-ide-companion');
|
||||
const noticeFilePath = path.join(packagePath, 'NOTICES.txt');
|
||||
|
||||
async function getDependencyLicense(depName, depVersion) {
|
||||
let depPackageJsonPath;
|
||||
/**
|
||||
* Read license information for a dependency from its on-disk location.
|
||||
*
|
||||
* @param {string} depName - Package name
|
||||
* @param {string} depVersion - Resolved version string
|
||||
* @param {string} resolvedKey - Lockfile key indicating where the package is installed
|
||||
* @returns {Promise<{name: string, version: string, repository: string, license: string}>}
|
||||
*/
|
||||
async function getDependencyLicense(depName, depVersion, resolvedKey) {
|
||||
let licenseContent = 'License text not found.';
|
||||
let repositoryUrl = 'No repository found';
|
||||
|
||||
try {
|
||||
depPackageJsonPath = path.join(
|
||||
projectRoot,
|
||||
'node_modules',
|
||||
depName,
|
||||
'package.json',
|
||||
);
|
||||
if (!(await fs.stat(depPackageJsonPath).catch(() => false))) {
|
||||
depPackageJsonPath = path.join(
|
||||
packagePath,
|
||||
'node_modules',
|
||||
depName,
|
||||
'package.json',
|
||||
);
|
||||
}
|
||||
// Derive the on-disk path directly from the lockfile key
|
||||
const depPackageJsonPath = path.join(
|
||||
projectRoot,
|
||||
resolvedKey,
|
||||
'package.json',
|
||||
);
|
||||
|
||||
try {
|
||||
const depPackageJsonContent = await fs.readFile(
|
||||
depPackageJsonPath,
|
||||
'utf-8',
|
||||
|
|
@ -76,7 +75,7 @@ async function getDependencyLicense(depName, depVersion) {
|
|||
}
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
`Warning: Could not find package.json for ${depName}: ${e.message}`,
|
||||
`Warning: Could not find package.json for ${depName} at ${depPackageJsonPath}: ${e.message}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -88,24 +87,90 @@ async function getDependencyLicense(depName, depVersion) {
|
|||
};
|
||||
}
|
||||
|
||||
function collectDependencies(packageName, packageLock, dependenciesMap) {
|
||||
/**
|
||||
* Resolve a package in the lockfile by walking up the node_modules chain,
|
||||
* mirroring Node.js module resolution algorithm.
|
||||
*
|
||||
* @param {string} packageName - Package to find
|
||||
* @param {object} packages - packageLock.packages map
|
||||
* @param {string} resolveFrom - Lockfile key to start resolution from
|
||||
* @returns {{info: object, key: string} | null}
|
||||
*/
|
||||
function resolveInLockfile(packageName, packages, resolveFrom) {
|
||||
// Walk up from resolveFrom, trying each node_modules level
|
||||
let current = resolveFrom;
|
||||
while (current) {
|
||||
const candidate = `${current}/node_modules/${packageName}`;
|
||||
if (packages[candidate]) {
|
||||
return { info: packages[candidate], key: candidate };
|
||||
}
|
||||
// Move up: strip the last /node_modules/... segment
|
||||
const lastNm = current.lastIndexOf('/node_modules/');
|
||||
if (lastNm === -1) break;
|
||||
current = current.slice(0, lastNm);
|
||||
}
|
||||
// Finally try root hoisted level
|
||||
const hoistedKey = `node_modules/${packageName}`;
|
||||
if (packages[hoistedKey]) {
|
||||
return { info: packages[hoistedKey], key: hoistedKey };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively collect third-party dependencies by walking the lockfile.
|
||||
* Mirrors Node.js module resolution: walks up the node_modules chain from
|
||||
* the current package's location.
|
||||
*
|
||||
* @param {string} packageName - Package to resolve
|
||||
* @param {object} packageLock - Parsed package-lock.json
|
||||
* @param {Map<string, {version: string, resolvedKey: string}>} dependenciesMap - Accumulated results
|
||||
* @param {string} resolveFrom - Lockfile key prefix to resolve from (e.g. "packages/vscode-ide-companion")
|
||||
*/
|
||||
function collectDependencies(
|
||||
packageName,
|
||||
packageLock,
|
||||
dependenciesMap,
|
||||
resolveFrom,
|
||||
) {
|
||||
if (dependenciesMap.has(packageName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const packageInfo = packageLock.packages[`node_modules/${packageName}`];
|
||||
if (!packageInfo) {
|
||||
const resolved = resolveInLockfile(
|
||||
packageName,
|
||||
packageLock.packages,
|
||||
resolveFrom,
|
||||
);
|
||||
if (!resolved) {
|
||||
console.warn(
|
||||
`Warning: Could not find package info for ${packageName} in package-lock.json.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
dependenciesMap.set(packageName, packageInfo.version);
|
||||
const { info: packageInfo, key: resolvedKey } = resolved;
|
||||
|
||||
// Workspace-linked packages: follow resolved pointer to collect their third-party deps
|
||||
if (packageInfo.link) {
|
||||
const realInfo = packageLock.packages[packageInfo.resolved];
|
||||
if (realInfo?.dependencies) {
|
||||
for (const depName of Object.keys(realInfo.dependencies)) {
|
||||
collectDependencies(depName, packageLock, dependenciesMap, resolveFrom);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
dependenciesMap.set(packageName, {
|
||||
version: packageInfo.version,
|
||||
resolvedKey,
|
||||
});
|
||||
|
||||
if (packageInfo.dependencies) {
|
||||
for (const depName of Object.keys(packageInfo.dependencies)) {
|
||||
collectDependencies(depName, packageLock, dependenciesMap);
|
||||
// Resolve transitive deps from THIS package's location
|
||||
collectDependencies(depName, packageLock, dependenciesMap, resolvedKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -125,15 +190,22 @@ async function main() {
|
|||
|
||||
const allDependencies = new Map();
|
||||
const directDependencies = Object.keys(packageJson.dependencies);
|
||||
const workspacePrefix = path.relative(projectRoot, packagePath);
|
||||
|
||||
for (const depName of directDependencies) {
|
||||
collectDependencies(depName, packageLockJson, allDependencies);
|
||||
collectDependencies(
|
||||
depName,
|
||||
packageLockJson,
|
||||
allDependencies,
|
||||
workspacePrefix,
|
||||
);
|
||||
}
|
||||
|
||||
const dependencyEntries = Array.from(allDependencies.entries());
|
||||
|
||||
const licensePromises = dependencyEntries.map(([depName, depVersion]) =>
|
||||
getDependencyLicense(depName, depVersion),
|
||||
const licensePromises = dependencyEntries.map(
|
||||
([depName, { version, resolvedKey }]) =>
|
||||
getDependencyLicense(depName, version, resolvedKey),
|
||||
);
|
||||
|
||||
const dependencyLicenses = await Promise.all(licensePromises);
|
||||
|
|
@ -151,6 +223,7 @@ async function main() {
|
|||
|
||||
await fs.writeFile(noticeFilePath, noticeText);
|
||||
console.log(`NOTICES.txt generated at ${noticeFilePath}`);
|
||||
console.log(`Total dependencies: ${dependencyEntries.length}`);
|
||||
} catch (error) {
|
||||
console.error('Error generating NOTICES.txt:', error);
|
||||
process.exit(1);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue