Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -1896,6 +1896,7 @@ packages/tools/buildServerDocker.js
packages/tools/checkIgnoredFiles.js
packages/tools/checkLibPaths.test.js
packages/tools/checkLibPaths.js
packages/tools/checkYarnPatches.js
packages/tools/convertThemesToCss.js
packages/tools/fuzzer/ActionRunner.js
packages/tools/fuzzer/Fuzzer.js
Expand Down
6 changes: 6 additions & 0 deletions .github/scripts/run_ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,12 @@ if [ "$RUN_TESTS" == "1" ]; then
if [ $testResult -ne 0 ]; then
exit $testResult
fi

yarn checkYarnPatches
testResult=$?
if [ $testResult -ne 0 ]; then
exit $testResult
fi
fi

# =============================================================================
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -1869,6 +1869,7 @@ packages/tools/buildServerDocker.js
packages/tools/checkIgnoredFiles.js
packages/tools/checkLibPaths.test.js
packages/tools/checkLibPaths.js
packages/tools/checkYarnPatches.js
packages/tools/convertThemesToCss.js
packages/tools/fuzzer/ActionRunner.js
packages/tools/fuzzer/Fuzzer.js
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"buildWebsiteTranslations": "node packages/tools/website/buildTranslations.js",
"checkIgnoredFiles": "node ./packages/tools/checkIgnoredFiles.js",
"checkLibPaths": "node ./packages/tools/checkLibPaths.js",
"checkYarnPatches": "node ./packages/tools/checkYarnPatches.js",
Comment thread
laurent22 marked this conversation as resolved.
"circularDependencyCheck": "madge --warning --circular --extensions js ./",
"clean": "npm run clean --workspaces --if-present && node packages/tools/clean && yarn cache clean",
"crowdin": "crowdin",
Expand Down
66 changes: 66 additions & 0 deletions packages/tools/checkYarnPatches.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { getRootDir } from '@joplin/utils';
import { readFile } from 'fs-extra';
import { join } from 'path';

// Checks that all patch resolutions in package.json are actually applied in
// yarn.lock. Catches the case where a dependency version is upgraded but the
// resolution still targets the old version, causing the patch to silently not
// apply.

const main = async () => {
const rootDir = await getRootDir();
const packageJson = JSON.parse(await readFile(join(rootDir, 'package.json'), 'utf8'));
const yarnLock = await readFile(join(rootDir, 'yarn.lock'), 'utf8');

const resolutions: Record<string, string> = packageJson.resolutions ?? {};
const errors: string[] = [];
Comment thread
laurent22 marked this conversation as resolved.

for (const [key, value] of Object.entries(resolutions)) {
if (!value.startsWith('patch:')) continue;

// Extract the patch target, e.g. "patch:nanoid@npm%3A3.3.11#..." -> "nanoid@npm:3.3.11"
const patchTarget = value
.replace(/^patch:/, '')
.replace(/#.*$/, '')
.replace(/%3A/g, ':');

// Extract package name and version from the patch target.
// Supports both "pkg@npm:version" and "pkg@version" formats.
const match = patchTarget.match(/^(.+)@(?:npm:)?(.+)$/);
if (!match) {
errors.push(
`Invalid patch format for "${key}": "${patchTarget}" does not match ` +
'expected pattern "packageName@npm:version" or "packageName@version".',
);
continue;
}

const [, packageName, patchVersion] = match;
const hasNpmPrefix = patchTarget.includes('@npm:');

// Check that yarn.lock contains a resolved entry for this exact
// patch. The lockfile entry looks like:
// "pkg@patch:pkg@npm%3Aversion#path::..." (with npm prefix)
// "pkg@patch:pkg@version#path::..." (without npm prefix)
const versionPart = hasNpmPrefix ? `@npm%3A${patchVersion}` : `@${patchVersion}`;
const patchPattern = `"${packageName}@patch:${packageName}${versionPart}#`;
if (!yarnLock.includes(patchPattern)) {
errors.push(
`Resolution "${key}" patches ${packageName}@${patchVersion}, ` +
'but yarn.lock has no matching entry. The dependency was likely ' +
'upgraded — update the patch to target the current version.',
);
}
}

if (errors.length > 0) {
throw new Error(`Yarn patch validation failed:\n\n${errors.join('\n\n')}`);
}

console.log(`All ${Object.values(resolutions).filter(v => v.startsWith('patch:')).length} patch resolutions are applied.`);
};

main().catch((error) => {
console.error(error);
process.exit(1);
});
Loading