Skip to content

Commit

Permalink
fix(bazel): integration test rule not able to setup mappings for reso…
Browse files Browse the repository at this point in the history
…lutions

Fixes that the Bazel integration test rule is not able to setup mappings
for Yarn resolutions because the record keys do not exactly match to a
package name, but rather follow certain allowed patterns, like
`**/<pkg-name>`.
  • Loading branch information
devversion committed Nov 8, 2021
1 parent 4f0f6a0 commit e05e4b0
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 8 deletions.
59 changes: 51 additions & 8 deletions bazel/integration/test_runner/package_json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,18 @@ export function updateMappingsForPackageJson(
): PackageJson {
const newPackageJson = {...packageJson};

updateMappingForRecord(newPackageJson.dependencies ?? {}, mappings);
updateMappingForRecord(newPackageJson.devDependencies ?? {}, mappings);
updateMappingForRecord(newPackageJson.optionalDependencies ?? {}, mappings);
updateMappingForRecord(newPackageJson.resolutions ?? {}, mappings);
updateMappingForRecord(newPackageJson, 'dependencies', mappings);
updateMappingForRecord(newPackageJson, 'devDependencies', mappings);
updateMappingForRecord(newPackageJson, 'optionalDependencies', mappings);
// The object for Yarn resolutions will not directly match with the mapping keys
// specified here, as resolutions usually follow a format as followed:
// 1. `**/<pkg-name>`
// 2. `<other-pkg>/**/<pkg-name>`
// 3. `<pkg-name>`
// More details here: https://classic.yarnpkg.com/lang/en/docs/selective-version-resolutions/.
// We pass a regular expression which matches the `<pkg-name>` so that the mappings can
// be applied for resolutions as well.
updateMappingForRecord(newPackageJson, 'resolutions', mappings, /([^/]+)$/);

return newPackageJson;
}
Expand All @@ -64,15 +72,31 @@ export function updateMappingsForPackageJson(
* @throws An error if there is a dependency entry referring to a local file. Such
* entries should not use `file:` but instead configure a mapping through Bazel.
*/
function updateMappingForRecord(record: DependencyRecord, mappings: PackageMappings) {
for (const [pkgName, value] of Object.entries(record)) {
function updateMappingForRecord(
pkgJson: PackageJson,
recordName: keyof PackageJson,
mappings: PackageMappings,
nameMatchRegex?: RegExp,
) {
const record = pkgJson[recordName] ?? {};

for (const [entryKey, value] of Object.entries(record)) {
const pkgName = getPackageNameFromDependencyEntry(entryKey, nameMatchRegex);

if (pkgName === null) {
throw Error(`Could not determine package name for "${recordName}" entry: ${entryKey}.`);
}

// Print the resolved package name to ease debugging when packages are not mapped properly.
debug(`updateMappingForRecord: Resolved "${recordName}@${entryKey}" to package: ${pkgName}`);

const mappedAbsolutePath = mappings[pkgName];

// If the value of the dependency entry is referring to a local file, then we report
// an error as this is likely a missing mapping that should be set up through Bazel.
if (mappedAbsolutePath === undefined && value.startsWith(`file:`)) {
throw Error(
`Unexpected dependency entry for: ${pkgName}, pointing to: ${value}.` +
`Unexpected dependency entry for: ${entryKey}, pointing to: ${value}. ` +
`Instead, configure the mapping through the integration test Bazel target.`,
);
}
Expand All @@ -82,6 +106,25 @@ function updateMappingForRecord(record: DependencyRecord, mappings: PackageMappi
continue;
}

record[pkgName] = mappedAbsolutePath;
record[entryKey] = mappedAbsolutePath;
}
}

/**
* Gets the package name from a dependency record entry.
*
* @param entryKey Key of the dependency record entry.
* @param nameMatchRegex Optional regular expression that can be specified to match the package
* name in a dependency entry. This is useful for e.g. Yarn resolutions using patterns.
* The first capturing group is expected to return the matched package name.
*/
function getPackageNameFromDependencyEntry(
entryKey: string,
nameMatchRegex?: RegExp,
): string | null {
if (nameMatchRegex) {
const matches = entryKey.match(nameMatchRegex);
return matches === null ? null : matches[1];
}
return entryKey;
}
11 changes: 11 additions & 0 deletions bazel/integration/tests/package_mappings/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,16 @@
"license": "MIT",
"dependencies": {
"fake_pkg": "0.0.0"
},
"devDependencies": {
"fake_pkg": "0.0.0"
},
"optionalDependencies": {
"fake_pkg": "0.0.0"
},
"resolutions": {
"**/fake_pkg": "0.0.0",
"some_pkg/**/fake_pkg": "0.0.0",
"fake_pkg": "0.0.0"
}
}
11 changes: 11 additions & 0 deletions bazel/integration/tests/package_mappings/test.mjs
Original file line number Diff line number Diff line change
@@ -1,8 +1,19 @@
import fakePkg from 'fake_pkg';
import fs from 'fs';

// Sanity check that the installed package matches the one we have
// built from source using `pkg_npm`.
if (fakePkg !== 'This is a fake package!') {
console.error('Fake package is not matching with locally-built one.');
process.exitCode = 1;
}

const pkgJson = JSON.parse(fs.readFileSync('./package.json', 'utf8'));
const recordsToCheck = ['dependencies', 'devDependencies', 'optionalDependencies', 'resolutions'];

for (const recordName of recordsToCheck) {
if (Object.values(pkgJson[recordName]).includes('0.0.0')) {
console.error(`The "${recordName}" field has not been replaced with mapped archives.`);
process.exitCode = 1;
}
}

0 comments on commit e05e4b0

Please sign in to comment.