Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[lockfile-explorer] Fix some issues when parsing certain lockfile syntaxes #4697

Merged
merged 14 commits into from May 15, 2024

Conversation

L-Qun
Copy link
Contributor

@L-Qun L-Qun commented May 11, 2024

Summary

Fix some bugs for the lockfile-explorer tool

screenshot-20240510-192105

screenshot-20240510-185009

How it was tested

Manually tested with Rushstack repo locally.

apps/lockfile-explorer/src/start.ts Outdated Show resolved Hide resolved

/**
* This operation exactly mirrors the behavior of PNPM's own implementation:
* https://github.com/pnpm/pnpm/blob/73ebfc94e06d783449579cda0c30a40694d210e4/lockfile/lockfile-file/src/experiments/inlineSpecifiersLockfileConverters.ts#L162
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you not just grab this function from a published package?

common/config/rush/browser-approved-packages.json Outdated Show resolved Hide resolved
Comment on lines +170 to +174
if (dependency.entryId.startsWith('/')) {
// Local package
// eslint-disable-next-line no-console
console.error('Could not resolve dependency entryId: ', dependency.entryId, dependency);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the else case not an error?

Copy link
Contributor Author

@L-Qun L-Qun May 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the code, use dependencyKey and entryId to perform matching:

image

Thus, the issue "Could not resolve dependency entryId" primarily from two aspects:

  1. The previous simple replace would cause the "@" at incorrect positions to be replaced:
    image
    For example, "/@byted/xxx@1.0.0(@xxx/application-http@1.1.0)" will be converted to "//byted/xxx@1.0.0(@xxx/application-http@1.1.0)'"

  2. After being parsed by parseDependencies in LockfileDependency, the entryId is further processed based on its original content. I suppose the processed results will not appear in the pnpm-lock.yaml file. For example, content like link:../../liveart-core/packages/actions will be transformed by LockfileDependency to start with 'project', which will not appear in the pnpm-lock.yaml file. Therefore, I have imposed further restrictions here.
    image

const updatedPackages: Lockfile['packages'] = {};
for (const dependencyPath in packages) {
if (Object.prototype.hasOwnProperty.call(packages, dependencyPath)) {
updatedPackages[convertLockfileV6DepPathToV5DepPath(dependencyPath)] = packages[dependencyPath];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to convert V5->V6 instead of vice-versa?

Copy link
Contributor Author

@L-Qun L-Qun May 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that the existing logic is based on handling the v5 version of path.
image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally, I think resolving dependencies in the browser is not very appropriate, as it limits our use of pnpm-related open-source libraries.

const doc = yaml.load(pnpmLockfileText);
const doc = yaml.load(pnpmLockfileText) as Lockfile;
const { packages, lockfileVersion } = doc;
if (packages && lockfileVersion.toString().startsWith('6.')) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this fix really about supporting multiple versions of the PNPM lockfile format? That might be a better description for the change log.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image
I think the previous code already supports multiple versions of the PNPM lockfile format, although there are some issues.

@@ -103,7 +105,17 @@ function startApp(debugMode: boolean): void {

app.get('/api/lockfile', async (req: express.Request, res: express.Response) => {
const pnpmLockfileText: string = await FileSystem.readFileAsync(appState.pnpmLockfileLocation);
const doc = yaml.load(pnpmLockfileText);
const doc = yaml.load(pnpmLockfileText) as Lockfile;
const { packages, lockfileVersion } = doc;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lockfileVersion is a pretty important field here. We should probably parse it into a number variable (or major/minor variables?) that can more easily be compared. And ideally we should detect format versions that are unsupported (because they are too old or too new) and in that case, display a friendly-looking error message to the end user.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe lockfile-explorer tool only support v5/v6 for now. For other versions, I have implemented checks and throw an error.

@octogonz octogonz changed the title fix some bugs for lockfile-explorer [lockfile-explorer]: Fix some issues when parsing certain lockfile syntaxes May 13, 2024
@octogonz octogonz changed the title [lockfile-explorer]: Fix some issues when parsing certain lockfile syntaxes [lockfile-explorer] Fix some issues when parsing certain lockfile syntaxes May 13, 2024
if (typeof lockfileVersion === 'string') {
shrinkwrapFileMajorVersion = parseInt(lockfileVersion.substring(0, lockfileVersion.indexOf('.')), 10);
} else if (typeof lockfileVersion === 'number') {
shrinkwrapFileMajorVersion = lockfileVersion;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If it is encoded as a number, then shrinkwrapFileMajorVersion should probably be Math.floor(lockfileVersion) right?


let shrinkwrapFileMajorVersion: number;
if (typeof lockfileVersion === 'string') {
shrinkwrapFileMajorVersion = parseInt(lockfileVersion.substring(0, lockfileVersion.indexOf('.')), 10);
Copy link
Collaborator

@octogonz octogonz May 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code assumes the version contains . and that it starts with digits. Otherwise parseInt() can return NaN which will fail the test if (shrinkwrapFileMajorVersion < 5 || shrinkwrapFileMajorVersion > 6).

Generally speaking, we don't need to worry about variables not conforming to their declared TypeScript type or expected value. However in this case, the input file has not been validated with a JSON schema, and the version of its file format is also unknown. Thus some extra validation here seems worthwhile.

@octogonz
Copy link
Collaborator

@L-Qun Thanks for making these fixes! 🎉

@octogonz octogonz merged commit 624dc0c into microsoft:main May 15, 2024
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Closed
Development

Successfully merging this pull request may close these issues.

None yet

3 participants