Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
12 changes: 10 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,23 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # 6.0.1
- name: Checkout
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # 6.0.1
with:
persist-credentials: false

- uses: ./.github/actions/ci-setup
- name: Setup dependencies
uses: ./.github/actions/ci-setup

- name: Run format:check
run: yarn format:check

- name: Run lint
run: yarn lint:ci

- name: Run typecheck
run: yarn typecheck

ci-ok:
name: CI OK
runs-on: ubuntu-latest
Expand Down
37 changes: 37 additions & 0 deletions .oxlintrc.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "unicorn", "oxc", "vitest", "import", "promise"],
"categories": {
"correctness": "error",
"suspicious": "error",
"perf": "error",
"pedantic": "error",
},
Comment on lines +4 to +9
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I haven’t enabled the style and restrictions categories yet to keep the review scope lighter.

Let me know if you’d like me to include them.
I can either add them in this PR or follow up with a separate one, whichever you prefer.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't know what style would bring to the table over just relying on the formatter.

As to restrictions, I briefly looked through the list and some of those rules seem to be useful while some doesn't so perhaps we should try to enable that and turn off specific rules we don't like. I'll try to make a pass through this to assess which ones we want.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I don't know what style would bring to the table over just relying on the formatter.

Actually I find some of the style rules, like typescript/consistent-type-imports, pretty useful.
I would consider give some of them a go, but we we can do it later


As to restrictions, I briefly looked through the list and some of those rules seem to be useful while some doesn't so perhaps we should try to enable that and turn off specific rules we don't like. I'll try to make a pass through this to assess which ones we want.

Sure, I'll focus on addressing your other feedbacks for now.

"options": {
"typeAware": true,
"typeCheck": false,
},
"rules": {
"func-style": "off",
"max-lines": "off",
"max-lines-per-function": "off",
"no-implicit-coercion": "off",
"no-magic-numbers": "off",
"no-ternary": "off",
"no-warning-comments": "off",
"jest/no-conditional-in-test": "off",
"typescript/array-type": ["error", { "default": "generic", "readonly": "generic" }],
"typescript/ban-types": "off", // deprecated, replaced by no-unsafe-function-type and no-wrapper-object-types
"typescript/consistent-type-imports": "error",
"typescript/explicit-function-return-type": "off",
"typescript/explicit-module-boundary-types": "off",
"typescript/no-unsafe-type-assertion": "off",
"typescript/no-unsafe-function-type": "error",
"typescript/no-wrapper-object-types": "error",
"typescript/return-await": "off",
"typescript/strict-boolean-expressions": "off",
},
"env": {
"builtin": true,
},
}
118 changes: 69 additions & 49 deletions get-changed-packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@ import nodePath from "path";
import assembleReleasePlan from "@changesets/assemble-release-plan";
import { parse as parseConfig } from "@changesets/config";
import parseChangeset from "@changesets/parse";
import { PreState, NewChangeset } from "@changesets/types";
import { Packages, Tool } from "@manypkg/get-packages";
import type {
NewChangeset,
PreState,
WrittenConfig,
PackageJSON as ChangesetPackageJSON,
} from "@changesets/types";
import type { Packages, Tool } from "@manypkg/get-packages";
import { safeLoad } from "js-yaml";
import micromatch from "micromatch";
import fetch from "node-fetch";
import { ProbotOctokit } from "probot";
import type { ProbotOctokit } from "probot";

export let getChangedPackages = async ({
export const getChangedPackages = async ({
owner,
repo,
ref,
Expand All @@ -21,12 +26,12 @@ export let getChangedPackages = async ({
owner: string;
repo: string;
ref: string;
changedFiles: string[] | Promise<string[]>;
changedFiles: Array<string> | Promise<Array<string>>;
octokit: InstanceType<typeof ProbotOctokit>;
installationToken: string;
}) => {
let hasErrored = false;
let encodedCredentials = Buffer.from(`x-access-token:${installationToken}`).toString("base64");
const encodedCredentials = Buffer.from(`x-access-token:${installationToken}`).toString("base64");

function fetchFile(path: string) {
return fetch(`https://raw.githubusercontent.com/${owner}/${repo}/${ref}/${path}`, {
Expand All @@ -36,54 +41,63 @@ export let getChangedPackages = async ({
});
}

function fetchJsonFile(path: string) {
return fetchFile(path)
.then((x) => x.json())
.catch((err) => {
hasErrored = true;
console.error(err);
return {};
});
async function fetchJsonFile<T>(path: string): Promise<T> {
try {
const x = await fetchFile(path);
return x.json() as Promise<T>;
} catch (error) {
hasErrored = true;
console.error(error);
return {} as Promise<T>;
}
}

async function fetchTextFile(path: string): Promise<string> {
try {
const x = await fetchFile(path);
return x.text();
} catch (err) {
hasErrored = true;
console.error(err);
return "";
}
}

function fetchTextFile(path: string) {
return fetchFile(path)
.then((x) => x.text())
.catch((err) => {
hasErrored = true;
console.error(err);
return "";
});
interface PackageJSON extends ChangesetPackageJSON {
workspaces?: Array<string> | { packages: Array<string> };
bolt?: { workspaces: Array<string> };
}

async function getPackage(pkgPath: string) {
let jsonContent = await fetchJsonFile(pkgPath + "/package.json");
async function getPackage(pkgPath: string): Promise<{ dir: string; packageJson: PackageJSON }> {
const jsonContent = await fetchJsonFile(pkgPath + "/package.json");
return {
packageJson: jsonContent,
dir: pkgPath,
packageJson: jsonContent as PackageJSON,
};
}

let rootPackageJsonContentsPromise = fetchJsonFile("package.json");
let configPromise: Promise<any> = fetchJsonFile(".changeset/config.json");
const rootPackageJsonContentsPromise: Promise<PackageJSON> = fetchJsonFile("package.json");
const rawConfigPromise: Promise<WrittenConfig> = fetchJsonFile(".changeset/config.json");

let tree = await octokit.git.getTree({
const tree = await octokit.git.getTree({
owner,
repo,
recursive: "1",
tree_sha: ref,
});

let preStatePromise: Promise<PreState> | undefined;
let changesetPromises: Promise<NewChangeset>[] = [];
let potentialWorkspaceDirectories: string[] = [];
const changesetPromises: Array<Promise<NewChangeset>> = [];
const potentialWorkspaceDirectories: Array<string> = [];
let isPnpm = false;
let changedFiles = await changedFilesPromise;
const changedFiles = await changedFilesPromise;

for (let item of tree.data.tree) {
if (!item.path) continue;
for (const item of tree.data.tree) {
if (!item.path) {
continue;
}
if (item.path.endsWith("/package.json")) {
let dirPath = nodePath.dirname(item.path);
const dirPath = nodePath.dirname(item.path);
potentialWorkspaceDirectories.push(dirPath);
} else if (item.path === "pnpm-workspace.yaml") {
isPnpm = true;
Expand All @@ -95,43 +109,49 @@ export let getChangedPackages = async ({
item.path.endsWith(".md") &&
changedFiles.includes(item.path)
) {
let res = /\.changeset\/([^\.]+)\.md/.exec(item.path);
const res = /\.changeset\/([^.]+)\.md/.exec(item.path);
if (!res) {
throw new Error("could not get name from changeset filename");
}
let id = res[1];
const id = res[1];

changesetPromises.push(
fetchTextFile(item.path).then((text) => {
return { ...parseChangeset(text), id };
}),
fetchTextFile(item.path).then((text) => ({ ...parseChangeset(text), id })),
);
}
}
let tool:
| {
tool: Tool;
globs: string[];
globs: Array<string>;
}
| undefined;

if (isPnpm) {
interface PnpmWorkspace {
packages: Array<string>;
}

const pnpmWorkspaceContent = await fetchTextFile("pnpm-workspace.yaml");
const pnpmWorkspace = safeLoad(pnpmWorkspaceContent) as PnpmWorkspace;

tool = {
tool: "pnpm",
globs: safeLoad(await fetchTextFile("pnpm-workspace.yaml")).packages,
globs: pnpmWorkspace.packages,
};
} else {
let rootPackageJsonContent = await rootPackageJsonContentsPromise;
const rootPackageJsonContent = await rootPackageJsonContentsPromise;

if (rootPackageJsonContent.workspaces) {
if (!Array.isArray(rootPackageJsonContent.workspaces)) {
if (Array.isArray(rootPackageJsonContent.workspaces)) {
tool = {
tool: "yarn",
globs: rootPackageJsonContent.workspaces.packages,
globs: rootPackageJsonContent.workspaces,
};
} else {
tool = {
tool: "yarn",
globs: rootPackageJsonContent.workspaces,
globs: rootPackageJsonContent.workspaces.packages,
};
}
} else if (rootPackageJsonContent.bolt && rootPackageJsonContent.bolt.workspaces) {
Expand All @@ -142,9 +162,9 @@ export let getChangedPackages = async ({
}
}

let rootPackageJsonContent = await rootPackageJsonContentsPromise;
const rootPackageJsonContent = await rootPackageJsonContentsPromise;

let packages: Packages = {
const packages: Packages = {
root: {
dir: "/",
packageJson: rootPackageJsonContent,
Expand All @@ -157,7 +177,7 @@ export let getChangedPackages = async ({
if (!Array.isArray(tool.globs) || !tool.globs.every((x) => typeof x === "string")) {
throw new Error("globs are not valid: " + JSON.stringify(tool.globs));
}
let matches = micromatch(potentialWorkspaceDirectories, tool.globs);
const matches = micromatch(potentialWorkspaceDirectories, tool.globs);

packages.packages = await Promise.all(matches.map((dir) => getPackage(dir)));
} else {
Expand All @@ -170,7 +190,7 @@ export let getChangedPackages = async ({
const releasePlan = assembleReleasePlan(
await Promise.all(changesetPromises),
packages,
await configPromise.then((rawConfig) => parseConfig(rawConfig, packages)),
parseConfig(await rawConfigPromise, packages),
await preStatePromise,
);

Expand Down
23 changes: 11 additions & 12 deletions index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { ValidationError } from "@changesets/errors";
import { ReleasePlan, ComprehensiveRelease, VersionType } from "@changesets/types";
import { EmitterWebhookEvent } from "@octokit/webhooks";
import type { ReleasePlan, ComprehensiveRelease, VersionType } from "@changesets/types";
import type { EmitterWebhookEvent } from "@octokit/webhooks";
import { captureException } from "@sentry/node";
// @ts-ignore
import humanId from "human-id";
import markdownTable from "markdown-table";
import { Probot, Context } from "probot";
import type { Probot, Context } from "probot";

import { getChangedPackages } from "./get-changed-packages";

Expand All @@ -31,15 +30,15 @@ const getReleasePlanMessage = (releasePlan: ReleasePlan | null) => {
]);

return `<details><summary>This PR includes ${
releasePlan.changesets.length
releasePlan.changesets.length > 0
? `changesets to release ${
publishableReleases.length === 1 ? "1 package" : `${publishableReleases.length} packages`
}`
: "no changesets"
}</summary>

${
publishableReleases.length
publishableReleases.length > 0
? table
: "When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types"
}
Expand Down Expand Up @@ -83,7 +82,7 @@ Not sure what this means? [Click here to learn what changesets are](https://git

`;

const getNewChangesetTemplate = (changedPackages: string[], title: string) =>
const getNewChangesetTemplate = (changedPackages: Array<string>, title: string) =>
encodeURIComponent(`---
${changedPackages.map((x) => `"${x}": patch`).join("\n")}
---
Expand Down Expand Up @@ -121,7 +120,7 @@ const hasChangesetBeenAdded = (
);

export default (app: Probot) => {
app.auth();
void app.auth();
app.log("Yay, the app was loaded!");

app.on(["pull_request.opened", "pull_request.synchronize"], async (context) => {
Expand All @@ -146,19 +145,19 @@ export default (app: Probot) => {
});

const [commentId, hasChangeset, { changedPackages, releasePlan }] = await Promise.all([
// we know the comment won't exist on opened events
// We know the comment won't exist on opened events
// ok, well like technically that's wrong
// but reducing time is nice here so that
// deploying this doesn't cost money
context.payload.action === "synchronize"
? getCommentId(context, { ...repo, issue_number: number })
: undefined,
: Promise.resolve(null),
hasChangesetBeenAdded(changedFilesPromise),
getChangedPackages({
repo: context.payload.pull_request.head.repo.name,
owner: context.payload.pull_request.head.repo.owner.login,
ref: context.payload.pull_request.head.ref,
changedFiles: changedFilesPromise.then((x) => x.data.map((x) => x.filename)),
changedFiles: changedFilesPromise.then((x) => x.data.map(({ filename }) => filename)),
octokit: context.octokit,
installationToken: (
await (await app.auth()).apps.createInstallationAccessToken({
Expand Down Expand Up @@ -196,7 +195,7 @@ export default (app: Probot) => {
errFromFetchingChangedFiles,
};

if (commentId != null) {
if (commentId !== null) {
return context.octokit.issues.updateComment({
...prComment,
comment_id: commentId,
Expand Down
3 changes: 3 additions & 0 deletions modules.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
declare module "human-id" {
export default function humanId(options: { separator: string; capitalize: boolean }): string;
}
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
"scripts": {
"format": "oxfmt",
"format:check": "oxfmt --check",
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"lint:ci": "oxlint --format=github",
"typecheck": "tsc --noEmit",
"test": "vitest"
},
"dependencies": {
Expand All @@ -32,11 +36,13 @@
"probot": "^12.2.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"typescript": "^4.7.4"
"typescript": "^6.0.2"
},
"devDependencies": {
"msw": "^2.12.14",
"oxfmt": "^0.42.0",
"oxlint": "^1.58.0",
"oxlint-tsgolint": "^0.19.0",
"vite": "^8.0.3",
"vitest": "^4.1.2"
},
Expand Down
Loading