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

Add licenses command to pnpm #5567

Merged
merged 49 commits into from Nov 17, 2022
Merged

Conversation

weyert
Copy link
Contributor

@weyert weyert commented Oct 30, 2022

Introduces a new command named licenses which generates a license compliance report

refs #2825

@weyert
Copy link
Contributor Author

weyert commented Oct 30, 2022

If you run the command pd licenses --dir ~/temporary/license-proto/pnpm-licenses --json you will get a output similar to:

{
  "MIT": [
    {
      "name": "next",
      "path": "/Users/User/temporary/license-proto/pnpm-licenses/node_modules/next",
      "license": "MIT",
      "vendorUrl": "https://nextjs.org"
    }
  ],
  "Apache-2.0": [
    {
      "name": "typescript",
      "path": "/Users/User/temporary/license-proto/pnpm-licenses/node_modules/typescript",
      "license": "Apache-2.0",
      "vendorName": "Microsoft Corp.",
      "vendorUrl": "https://www.typescriptlang.org/"
    }
  ]
}

If you leave out the --json and the command is pd licenses --dir ~/temporary/license-proto/pnpm-licenses --long you will get:

┌──────────────────┬────────────┬─────────────────────────────────┐
│ Package          │ License    │ Details                         │
├──────────────────┼────────────┼─────────────────────────────────┤
│ typescript (dev) │ Apache-2.0 │ Microsoft Corp.                 │
│                  │            │ https://www.typescriptlang.org/ │
├──────────────────┼────────────┼─────────────────────────────────┤
│ next             │ MIT        │ https://nextjs.org              │
└──────────────────┴────────────┴─────────────────────────────────┘

If you exclude the --long you will get an output similar to:

┌──────────────────┬────────────┐
│ Package          │ License    │
├──────────────────┼────────────┤
│ typescript (dev) │ Apache-2.0 │
├──────────────────┼────────────┤
│ next             │ MIT        │
└──────────────────┴────────────┘

@weyert
Copy link
Contributor Author

weyert commented Oct 30, 2022

It's currently a draft because of two issues:

  • Need to sort the unit tests
  • Unable to get transitive dependencies (e.g. the dependencies of the next and typescript packages)

Would appreciate some helper on the second issue how I could make that work

@zkochan
Copy link
Member

zkochan commented Oct 31, 2022

Unable to get transitive dependencies (e.g. the dependencies of the next and typescript packages)

There is a package for walking the lockfile: https://github.com/pnpm/pnpm/tree/main/packages/lockfile-walker

@weyert
Copy link
Contributor Author

weyert commented Oct 31, 2022

Unable to get transitive dependencies (e.g. the dependencies of the next and typescript packages)

There is a package for walking the lockfile: https://github.com/pnpm/pnpm/tree/main/packages/lockfile-walker

Thank you, I will have a look and see how I can use it :)

@weyert weyert marked this pull request as ready for review November 1, 2022 01:13
@weyert
Copy link
Contributor Author

weyert commented Nov 1, 2022

I have reimplemented to use the lockfile-walker package and added tests to the new packages this PR is introducing

@weyert
Copy link
Contributor Author

weyert commented Nov 1, 2022

Only question I am having whether peerDependencies of a package that user hasn't installed should be included?

@zkochan
Copy link
Member

zkochan commented Nov 1, 2022

The manifests should be updated with pnpm update-manifests

@weyert
Copy link
Contributor Author

weyert commented Nov 1, 2022

The manifests should be updated with pnpm update-manifests

When I try to run this I am getting this error:

 WARN  Failed to create bin at /Users/weyert/Development/Projects/Opensource/pnpm/packages/exe/node_modules/.bin/pkg. The source file at /Users/weyert/Development/Projects/Opensource/pnpm/node_modules/.pnpm/github.com+vercel+pkg@7e4fef9f0828b11c7a9655b95425f32094f2daa2_pp6fkuhwkrqq7cjcj7uqpf37e4/node_modules/pkg/lib-es5/bin.js does not exist.
 ```

Not sure what's wrong, re-running `pnpm install` doesn't fix it

packages/licenses/src/getPkgInfo.ts Outdated Show resolved Hide resolved
packages/licenses/src/getPkgInfo.ts Outdated Show resolved Hide resolved
packages/licenses/src/licenses.ts Outdated Show resolved Hide resolved
packages/licenses/src/lockfileToLicenseNodeTree.ts Outdated Show resolved Hide resolved
version,
prefix: modules,
});
} catch (err: unknown) {}
Copy link
Member

Choose a reason for hiding this comment

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

why should this error be ignored?

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 have the problem that sometimes an optional dependency is listed in the lock file but not stored in modules directory and then the fetching fails. In that case I am skipping/ignoring this package so it won't get listed.

Copy link
Member

Choose a reason for hiding this comment

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

No, it is not OK to skip such issues. It would mean that your license checker doesn't check all the dependencies, so a package with a bad license might pass under your radar. Besides, now that you read from the CAFS, the optional deps will always be found. So this try/catch is not needed.

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 think this problem is resolved now. I have removed it, while making this change I did struggle to make it work with PNPM repo and the pkg-package as it's a tarball and the workspace projects but I think I cracked it. Only definitely not 100% sure it's the right approach

packages/licenses/src/lockfileToLicenseNodeTree.ts Outdated Show resolved Hide resolved
packages/plugin-commands-licenses/src/licenses.ts Outdated Show resolved Hide resolved
@weyert
Copy link
Contributor Author

weyert commented Nov 2, 2022

@zkochan Thank you for taking the time to review my PR. I have tried to apply your suggestions to my branch. I can totally imagine that are better ways to do some things :)

@matzeeable
Copy link

Hey @weyert !

Nice to see such a command finding a place in PNPM and thank you for your work! 😀

Does this PR also include disclaimer-file generation, like yarn generate-disclaimer?

@weyert
Copy link
Contributor Author

weyert commented Nov 2, 2022

Hey @weyert !

Nice to see such a command finding a place in PNPM and thank you for your work! 😀

Does this PR also include disclaimer-file generation, like yarn generate-disclaimer?

Not yet as I didn't have a need for it yet . I am happy to work on it in a follow-up pull request

@zkochan
Copy link
Member

zkochan commented Nov 3, 2022

The approach that is used here has some disadvantages.

  1. It won't work with a hoisted node_modules (when node-linked is set to hoisted) as you are reading the package.json files from node_modules/.pnpm, which will not exist in a hoisted node_modules
  2. It won't print the licenses for skipped optional packages
  3. It won't work on a project that has no node_modules. So, you'll have to run pnpm install first. This one might not be critical.

A better solution would be to read the package.json file and the license files from the content-addressable store. It would solve issues the 1st and 2nd issues. The 3rd issue will still exist unless we will dynamically fetch missing packages to the store (which may be done in a separate PR).

You can see examples how to read info from the store here: https://github.com/pnpm/pnpm/blob/main/packages/package-requester/src/packageRequester.ts

For instance, see how the readManifestFromStore function is used to read the manifest.

To find the license file in the content-addressable store, you should read the index file of the package. It contains the list of files. The path to the index file is calculated from the package's integrity checksum:

const filesIndexFile = opts.pkg.resolution['integrity']
? ctx.getFilePathInCafs(opts.pkg.resolution['integrity'], 'index')
: path.join(target, 'integrity.json')

@weyert
Copy link
Contributor Author

weyert commented Nov 3, 2022

The approach that is used here has some disadvantages.

  1. It won't work with a hoisted node_modules (when node-linked is set to hoisted) as you are reading the package.json files from node_modules/.pnpm, which will not exist in a hoisted node_modules
  2. It won't print the licenses for skipped optional packages
  3. It won't work on a project that has no node_modules. So, you'll have to run pnpm install first. This one might not be critical.

I have tried to accommodate your change requests. I hope what I changed is what you intended. Hopefully I didn't misunderstood you

@zkochan zkochan changed the title Add licenses command to PNPM Add licenses command to pnpm Nov 4, 2022
packages/licenses/src/getPkgInfo.ts Outdated Show resolved Hide resolved
packages/licenses/src/getPkgInfo.ts Outdated Show resolved Hide resolved
packages/licenses/src/getPkgInfo.ts Outdated Show resolved Hide resolved
packages/licenses/src/getPkgInfo.ts Outdated Show resolved Hide resolved
packages/licenses/src/getPkgInfo.ts Outdated Show resolved Hide resolved
packages/licenses/src/getPkgInfo.ts Outdated Show resolved Hide resolved
packages/licenses/src/getPkgInfo.ts Outdated Show resolved Hide resolved
version,
prefix: modules,
});
} catch (err: unknown) {}
Copy link
Member

Choose a reason for hiding this comment

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

No, it is not OK to skip such issues. It would mean that your license checker doesn't check all the dependencies, so a package with a bad license might pass under your radar. Besides, now that you read from the CAFS, the optional deps will always be found. So this try/catch is not needed.

packages/plugin-commands-licenses/src/licenses.ts Outdated Show resolved Hide resolved
packages/plugin-commands-licenses/src/licenses.ts Outdated Show resolved Hide resolved
@weyert
Copy link
Contributor Author

weyert commented Nov 5, 2022

I have updated the PR to resolve issues and accommodated the request changes

I have attached an example of the command when running against this repo:
pnpm-licenses.txt

packages/licenses/src/getPkgInfo.ts Outdated Show resolved Hide resolved
packages/licenses/src/getPkgInfo.ts Outdated Show resolved Hide resolved
}
}

const licenseNodeTree: LicenseNodeTree = {
Copy link
Member

Choose a reason for hiding this comment

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

Why do you create a tree of license nodes if in the output you just list all the packages in a flat list?

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 will have a look at changing this

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would this be a blocker for the PR to be merged?

Copy link
Member

Choose a reason for hiding this comment

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

Isn't that simpler code?

packages/licenses/package.json Outdated Show resolved Hide resolved
packages/licenses/package.json Outdated Show resolved Hide resolved
packages/licenses/package.json Outdated Show resolved Hide resolved
packages/licenses/src/getPkgInfo.ts Outdated Show resolved Hide resolved
packages/licenses/src/getPkgInfo.ts Outdated Show resolved Hide resolved
Comment on lines 221 to 230
const isLocalPkg = packageResolution.type === 'directory'
if (isLocalPkg) {
const localInfo = await fetchFromDir(
path.join(opts.lockfileDir, packageResolution.directory),
{}
)
return {
local: true,
files: localInfo.filesIndex,
}
Copy link
Member

Choose a reason for hiding this comment

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

This will scan the whole package with all subdirectories even though the license files are only searched for in the root directory of the package.

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 couldn't find a way to get the package file index of a pnpm workspace project, so generating one. Is there a way to get it? It's not available in the CAFS

packages/licenses/src/getPkgInfo.ts Outdated Show resolved Hide resolved
packages/licenses/src/getPkgInfo.ts Outdated Show resolved Hide resolved
packages/licenses/src/getPkgInfo.ts Outdated Show resolved Hide resolved
packages/licenses/src/index.ts Outdated Show resolved Hide resolved
packages/license-scanner/src/getPkgInfo.ts Outdated Show resolved Hide resolved
packages/license-scanner/src/getPkgInfo.ts Outdated Show resolved Hide resolved
packages/license-scanner/src/getPkgInfo.ts Outdated Show resolved Hide resolved
packages/license-scanner/src/getPkgInfo.ts Outdated Show resolved Hide resolved
Comment on lines +136 to +140

return {
name: 'Unknown',
licenseFile: licenseContents?.toString('utf-8'),
}
Copy link
Member

Choose a reason for hiding this comment

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

What if the package has multiple license files?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hah good question, I haven't discovered any of such package yet. Are you aware of one?

I don't think other licenses commands of other packages handle this use case, though

Copy link
Member

Choose a reason for hiding this comment

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

I have seen such packages in the past.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Packages that don't use license-property but have multiple license files? Happy to improve the code after this PR has been merged sounds like a pretty edge case to me

Copy link
Contributor

Choose a reason for hiding this comment

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

I work in a company that archives npm data and I can tell you without question that there are more packages that do that than you'd think. Roughly 10% of packages in the registry. People do nutty stuff in package.json and npm has no validation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If you happen to archive npm data via work, could younbe so kind and suggest some npm packages that does this so I can make it part of a test case?

Copy link
Contributor

Choose a reason for hiding this comment

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

give me a bit of time to get to that, but I'll take a look for you. if I don't get to this in a week, please ping me

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, of course, I am already happy you want to take the time help me :)

packages/license-scanner/src/licenses.ts Outdated Show resolved Hide resolved
@zkochan
Copy link
Member

zkochan commented Nov 11, 2022

I see that in yarn v1 there is a yarn licenses list command that does something similar.

In yarn v2 they don't have this command but there is a plugin that may be used. With this plugin, the command is: yarn licenses audit.

I wonder if we need to make this command pnpm licenses list. In the future we might add a pnpm licenses audit command as well.

cc @pnpm/collaborators

Weyert de Boer and others added 22 commits November 17, 2022 03:09
Updated the code so that the `getPkgInfo` function attempts to fetch
the `package.json` and potential license files from the cafs directory
by integrity id
Improved handling of reading package manifest for packages resolving to
workspace projects,and tarballs

Improved the handlign of parsing the license from the `package.json`-file
Renamed the `@pnpm/licenses`-package to `@pnpm/license-scanner`

Updated the keywords for the packages

Renamed the main function of `@pnpm/license-scanner` to be `findDependencyLicenses`

Renamed to the fields `vendorName`, `vendorUrl` to the original fields
used in the packages: `author` and `homepage`
@zkochan zkochan merged commit d84a30a into pnpm:main Nov 17, 2022
@welcome
Copy link

welcome bot commented Nov 17, 2022

Congrats on merging your first pull request! 🎉🎉🎉

@zkochan zkochan added this to the v7.17 milestone Nov 20, 2022
@weyert weyert deleted the add-licenses-command-v2 branch November 22, 2022 00:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants