Skip to content

Commit

Permalink
add support for new schema used by pnpm 9
Browse files Browse the repository at this point in the history
  • Loading branch information
JaninaWibkerQC committed Feb 28, 2024
1 parent b87ad44 commit 1beae68
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 21 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@quantco/pnpm-licenses",
"version": "1.0.3",
"version": "2.0.0",
"description": "Generate third party license disclaimers in pnpm-based projects",
"homepage": "https://github.com/Quantco/pnpm-licenses",
"repository": {
Expand Down
2 changes: 2 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ type Dependency = {
}
```
> Note that if multiple versions of a package are installed the output will contain the same package multiple times with differing versions (and paths)
### Options
```
Expand Down
46 changes: 43 additions & 3 deletions src/get-dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,17 @@ import fs from 'fs/promises'
import { exec } from 'child_process'
import z from 'zod'

const pnpmDependencySchema = z.object({
const pnpmDependencyGroupedSchema = z.object({
name: z.string(),
versions: z.array(z.string()),
paths: z.array(z.string()),
license: z.string(),
author: z.string().optional(),
homepage: z.string().optional(),
description: z.string().optional()
})

const pnpmDependencyFlattenedSchema = z.object({
name: z.string(),
version: z.string(),
path: z.string(),
Expand All @@ -12,11 +22,33 @@ const pnpmDependencySchema = z.object({
description: z.string().optional()
})

const pnpmDependencySchema = z.union([pnpmDependencyGroupedSchema, pnpmDependencyFlattenedSchema])

const pnpmInputSchema = z.record(z.string(), z.array(pnpmDependencySchema))

export type PnpmDependency = z.infer<typeof pnpmDependencySchema>
export type PnpmJson = z.infer<typeof pnpmInputSchema>

export type PnpmDependencyFlattened = z.infer<typeof pnpmDependencyFlattenedSchema>

export const flattenDependencies = (deps: PnpmDependency[]): PnpmDependencyFlattened[] =>
deps.flatMap(({ name, license, author, homepage, description, ...rest }) => {
if ('version' in rest) {
return [{ name, license, author, homepage, description, ...rest }]
} else {
const { versions, paths } = rest
return versions.map((version, i) => ({
name,
version,
path: paths[i],
license,
author,
homepage,
description
}))
}
})

async function read(stream: NodeJS.ReadableStream) {
const chunks: Buffer[] = []
for await (const chunk of stream) {
Expand All @@ -32,7 +64,10 @@ export type IOOptions = (
) &
({ stdout: true; outputFile: undefined } | { stdout: false; outputFile: string })

export const getDependencies = (options: { prod: boolean }, ioOptions: IOOptions): Promise<PnpmJson> => {
export const getDependencies = (
options: { prod: boolean },
ioOptions: IOOptions
): Promise<PnpmDependencyFlattened[]> => {
let inputPromise: Promise<string> | undefined

if (ioOptions.stdin) {
Expand All @@ -49,5 +84,10 @@ export const getDependencies = (options: { prod: boolean }, ioOptions: IOOptions
}

// TODO: proper error handling pls
return inputPromise.then(JSON.parse).then(pnpmInputSchema.parse)
return inputPromise
.then(JSON.parse)
.then(pnpmInputSchema.parse)
.then(Object.values)
.then((deps: PnpmDependency[][]) => deps.flat())
.then(flattenDependencies)
}
8 changes: 4 additions & 4 deletions src/get-license-text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import path from 'path'
import licenseTexts from 'spdx-license-list/full.js'
import stripIndent from 'strip-indent'
import removeMarkdown from 'remove-markdown'
import type { PnpmDependency } from './get-dependencies'
import type { PnpmDependencyFlattened } from './get-dependencies'

const LICENSE_BASENAMES = [/* eslint-disable prettier/prettier */
/^LICENSE$/i, // e.g. LICENSE
Expand Down Expand Up @@ -36,7 +36,7 @@ const LICENSE_TEXT_SUBSTRINGS = {
}

export class MissingLicenseError extends Error {
constructor(public dependency: PnpmDependency) {
constructor(public dependency: PnpmDependencyFlattened) {
super('No license text found for dependency ' + dependency.name)
}
}
Expand All @@ -50,14 +50,14 @@ const prettifyLicenseText = (licenseText: string) => {
return stripIndent(removeMarkdown(licenseText)).trim()
}

export type PnpmDependencyResolvedLicenseText = PnpmDependency & {
export type PnpmDependencyResolvedLicenseText = PnpmDependencyFlattened & {
licenseText: string
additionalText?: string
resolvedBy: (typeof resolvedByTypes)[number]
}

export const getLicenseText = async (
dependency: PnpmDependency
dependency: PnpmDependencyFlattened
): Promise<{ licenseText: string; additionalText?: string; resolvedBy: (typeof resolvedByTypes)[number] }> => {
const files = await fs.readdir(dependency.path)

Expand Down
16 changes: 7 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import fs from 'fs/promises'
import multimatch from 'multimatch'
import { getDependencies } from './get-dependencies'
import type { PnpmDependency } from './get-dependencies'
import type { PnpmDependencyFlattened } from './get-dependencies'
import { generateDisclaimer } from './generate-disclaimer'
import { resolveLicensesBestEffort } from './resolve-licenses-best-effort'

Expand Down Expand Up @@ -31,10 +31,9 @@ const output = (value: string, options: IOOptions) => {
}

export const listCommand = async (options: ListOptions, ioOptions: IOOptions) => {
const deps = getDependencies(options, ioOptions)
.then(Object.values)
.then((deps: PnpmDependency[][]) => deps.flat())
.then((deps: PnpmDependency[]) => deps.filter((dep) => multimatch(dep.name, options.filters).length === 0))
const deps = getDependencies(options, ioOptions).then((deps: PnpmDependencyFlattened[]) =>
deps.filter((dep) => multimatch(dep.name, options.filters).length === 0)
)

const { successful, failed } = await resolveLicensesBestEffort(await deps)

Check warning on line 38 in src/index.ts

View workflow job for this annotation

GitHub Actions / build

'failed' is assigned a value but never used

Expand All @@ -43,10 +42,9 @@ export const listCommand = async (options: ListOptions, ioOptions: IOOptions) =>
}

export const generateDisclaimerCommand = async (options: GenerateDisclaimerOptions, ioOptions: IOOptions) => {
const deps = getDependencies(options, ioOptions)
.then(Object.values)
.then((deps: PnpmDependency[][]) => deps.flat())
.then((deps: PnpmDependency[]) => deps.filter((dep) => multimatch(dep.name, options.filters).length === 0))
const deps = getDependencies(options, ioOptions).then((deps: PnpmDependencyFlattened[]) =>
deps.filter((dep) => multimatch(dep.name, options.filters).length === 0)
)

const { successful, failed } = await resolveLicensesBestEffort(await deps)

Check warning on line 49 in src/index.ts

View workflow job for this annotation

GitHub Actions / build

'failed' is assigned a value but never used

Expand Down
8 changes: 4 additions & 4 deletions src/resolve-licenses-best-effort.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import type { PnpmDependency } from './get-dependencies'
import type { PnpmDependencyFlattened } from './get-dependencies'
import { getLicenseText } from './get-license-text'
import type { PnpmDependencyResolvedLicenseText } from './get-license-text'

export const resolveLicensesBestEffort = async (
deps: PnpmDependency[]
): Promise<{ successful: PnpmDependencyResolvedLicenseText[]; failed: PnpmDependency[] }> => {
deps: PnpmDependencyFlattened[]
): Promise<{ successful: PnpmDependencyResolvedLicenseText[]; failed: PnpmDependencyFlattened[] }> => {
const depsWithLicensesPromise = deps.map(async (dep) => ({ ...dep, ...(await getLicenseText(dep)) }))

// keep track of which licenses could be resolved and which couldn't
// include the index to restore the original order afterwards
const successful: [number, PnpmDependencyResolvedLicenseText][] = []
const failed: [number, PnpmDependency][] = []
const failed: [number, PnpmDependencyFlattened][] = []

await Promise.all(
depsWithLicensesPromise.map((depPromise, index) =>
Expand Down

0 comments on commit 1beae68

Please sign in to comment.