Skip to content

Commit

Permalink
Merge pull request #9 from JoshuaKGoldberg/ownership
Browse files Browse the repository at this point in the history
feat: add --ownership option
  • Loading branch information
JoshuaKGoldberg authored Jun 15, 2023
2 parents 165c578 + 7c48369 commit b82e7dd
Show file tree
Hide file tree
Showing 11 changed files with 263 additions and 63 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,14 @@ npx tidelift-me-up
### Options
- `--ownership` _(default: `["author", "publisher"]`)_: If provided, any filters user packages must match one of based on username: `"author"`, `"maintainer"`, and/or `"publisher"`
- `--since` _(default: 2 years ago)_: A date that packages need to have been updated since to be considered
- This will be provided as a string to the `Date` constructor
- `--username` _(default: result of `npm whoami`)_: The npm username to search for packages maintained by
- The search is done by [`npm-user-packages`](https://github.com/kevva/npm-user-packages), which fetches from [npm.io](https://npm.io)
```shell
npx tidelift-me-up --since 2020 --username your-username
npx tidelift-me-up --ownership author --ownership publisher --since 2020 --username your-username
```
## Development
Expand Down
19 changes: 19 additions & 0 deletions src/assertValidOwnership.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";

import { assertValidOwnership } from "./assertValidOwnership.js";

describe("assertValidOwnership", () => {
it("does not throw an error when ownership is undefined", () => {
expect(() => assertValidOwnership(undefined)).not.toThrow();
});

it("does not throw an error when ownership contains valid ownership", () => {
expect(() => assertValidOwnership(["author"])).not.toThrow();
});

it("throws an error when ownership contains invalid ownership", () => {
expect(() => assertValidOwnership(["abc"])).toThrowError(
`Unknown --ownership: abc (must be one of: author, maintainer, publisher)`
);
});
});
20 changes: 20 additions & 0 deletions src/assertValidOwnership.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {
PackageOwnershipForm,
packageOwnershipForms,
} from "./packageOwnershipForms.js";

export function assertValidOwnership(
ownership: string[] | undefined
): asserts ownership is PackageOwnershipForm[] | undefined {
if (ownership) {
for (const ownershipForm of ownership) {
if (!["author", "maintainer", "publisher"].includes(ownershipForm)) {
throw new Error(
`Unknown --ownership: ${ownershipForm} (must be one of: ${packageOwnershipForms.join(
", "
)})`
);
}
}
}
}
149 changes: 149 additions & 0 deletions src/createUserPackagesFilter.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { describe, expect, it } from "vitest";

import {
UserPackageData,
createUserPackagesFilter,
} from "./createUserPackagesFilter.js";

const username = "abc123";

const createPackageData = (overrides?: Partial<UserPackageData>) =>
({
author: { name: "" },
date: new Date().toString(),
maintainers: [],
publisher: { email: "", username: "" },
...overrides,
} satisfies UserPackageData);

describe("createUserPackagesFilter", () => {
it("filters a package when its date predates since", () => {
const filter = createUserPackagesFilter({
ownership: ["author"],
since: new Date(Date.now() - 10_000),
username,
});

const actual = filter(
createPackageData({
author: { name: "", username },
date: new Date(Date.now() - 20_000).toString(),
})
);

expect(actual).toBe(false);
});

it("allows a package when its date is after since", () => {
const filter = createUserPackagesFilter({
ownership: ["author"],
since: new Date(Date.now() - 20_000),
username,
});

const actual = filter(
createPackageData({
author: { name: "", username },
date: new Date(Date.now() - 10_000).toString(),
})
);

expect(actual).toBe(true);
});

it("filters a package when author ownership doesn't match", () => {
const filter = createUserPackagesFilter({
ownership: ["author"],
since: new Date(0),
username,
});

const actual = filter(
createPackageData({
author: { name: "", username: "other" },
})
);

expect(actual).toBe(false);
});

it("allows a package when author ownership matches", () => {
const filter = createUserPackagesFilter({
ownership: ["author"],
since: new Date(0),
username,
});

const actual = filter(
createPackageData({
author: { name: "", username },
})
);

expect(actual).toBe(true);
});

it("filters a package when maintainer ownership doesn't match", () => {
const filter = createUserPackagesFilter({
ownership: ["maintainer"],
since: new Date(0),
username,
});

const actual = filter(
createPackageData({
maintainers: [{ email: "", username: "other" }],
})
);

expect(actual).toBe(false);
});

it("allows a package when maintainer ownership matches", () => {
const filter = createUserPackagesFilter({
ownership: ["maintainer"],
since: new Date(0),
username,
});

const actual = filter(
createPackageData({
maintainers: [{ email: "", username }],
})
);

expect(actual).toBe(true);
});

it("filters a package when publisher ownership doesn't match", () => {
const filter = createUserPackagesFilter({
ownership: ["publisher"],
since: new Date(0),
username,
});

const actual = filter(
createPackageData({
publisher: { email: "", username: "other" },
})
);

expect(actual).toBe(false);
});

it("allows a package when publisher ownership matches", () => {
const filter = createUserPackagesFilter({
ownership: ["publisher"],
since: new Date(0),
username,
});

const actual = filter(
createPackageData({
publisher: { email: "", username },
})
);

expect(actual).toBe(true);
});
});
48 changes: 48 additions & 0 deletions src/createUserPackagesFilter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { PackageData } from "npm-user-packages";

import { PackageOwnershipForm } from "./packageOwnershipForms.js";

export interface FilterSettings {
ownership: PackageOwnershipForm[];
since: Date;
username: string;
}

export type UserPackageData = Pick<
PackageData,
"author" | "date" | "maintainers" | "publisher"
>;

export function createUserPackagesFilter({
ownership,
since,
username,
}: FilterSettings) {
return (userPackage: UserPackageData) => {
if (new Date(userPackage.date) < since) {
return false;
}

if (
!ownership.some((ownershipForm) => {
switch (ownershipForm) {
case "author":
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
return userPackage.author?.username === username;

case "maintainer":
return userPackage.maintainers.some(
(maintainer) => maintainer.username === username
);

case "publisher":
return userPackage.publisher.username === username;
}
})
) {
return false;
}

return true;
};
}
3 changes: 3 additions & 0 deletions src/packageOwnershipForms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export type PackageOwnershipForm = "author" | "maintainer" | "publisher";

export const packageOwnershipForms = ["author", "maintainer", "publisher"];
55 changes: 0 additions & 55 deletions src/tideliftMeUp.test.ts

This file was deleted.

7 changes: 5 additions & 2 deletions src/tideliftMeUp.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import npmUserPackages from "npm-user-packages";

import { createUserPackagesFilter } from "./createUserPackagesFilter.js";
import { getPackageEstimates } from "./getPackageEstimates.js";
import { PackageOwnershipForm } from "./packageOwnershipForms.js";

export interface TideliftMeUpSettings {
ownership?: PackageOwnershipForm[];
since?: Date | number | string;
username: string;
}

export async function tideliftMeUp({
ownership = ["author", "publisher"],
since = getTwoYearsAgo(),
username,
}: TideliftMeUpSettings) {
const sinceDate = new Date(since);
const userPackages = (await npmUserPackages(username)).filter(
(userPackage) => new Date(userPackage.date) >= sinceDate
createUserPackagesFilter({ ownership, since: new Date(since), username })
);

return await getPackageEstimates(
Expand Down
8 changes: 6 additions & 2 deletions src/tideliftMeUpCli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ describe("tideliftMeUpCli", () => {
await tideliftMeUpCli(["--username", username]);

expect(mockGetNpmWhoami).not.toHaveBeenCalled();
expect(mockTideliftMeUp).toHaveBeenCalledWith({ username });
expect(mockTideliftMeUp).toHaveBeenCalledWith({
username,
});
});

it("logs packages for a username when --username is not provided and the user is logged in", async () => {
Expand All @@ -47,7 +49,9 @@ describe("tideliftMeUpCli", () => {

await tideliftMeUpCli([]);

expect(mockTideliftMeUp).toHaveBeenCalledWith({ username });
expect(mockTideliftMeUp).toHaveBeenCalledWith({
username,
});
});

it("logs a package as already lifted when it is", async () => {
Expand Down
Loading

0 comments on commit b82e7dd

Please sign in to comment.