Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ jobs:
- name: Run format:check
run: yarn format:check

- name: Run lint and typecheck
run: yarn lint

ci-ok:
name: CI OK
runs-on: ubuntu-latest
Expand Down
31 changes: 31 additions & 0 deletions .oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["typescript", "unicorn", "oxc", "vitest", "import", "promise"],
"categories": {
"correctness": "error",
"suspicious": "error",
"perf": "error",
"pedantic": "error"
},
"options": {
"typeAware": true,
"typeCheck": true
Comment thread
marcalexiei marked this conversation as resolved.
Outdated
},
"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",
"typescript/array-type": ["error", { "default": "generic", "readonly": "generic" }],
"typescript/consistent-type-imports": "error",
"typescript/no-unsafe-type-assertion": "off",
"typescript/return-await": "off",
"typescript/strict-boolean-expressions": "off"
},
"env": {
"builtin": true
}
}
122 changes: 72 additions & 50 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,56 +109,62 @@ 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 = {
globs: pnpmWorkspace.packages,
tool: "pnpm",
globs: safeLoad(await fetchTextFile("pnpm-workspace.yaml")).packages,
};
} else {
let rootPackageJsonContent = await rootPackageJsonContentsPromise;
const rootPackageJsonContent = await rootPackageJsonContentsPromise;

if (rootPackageJsonContent.workspaces) {
if (!Array.isArray(rootPackageJsonContent.workspaces)) {
if (Array.isArray(rootPackageJsonContent.workspaces)) {
tool = {
globs: rootPackageJsonContent.workspaces,
tool: "yarn",
globs: rootPackageJsonContent.workspaces.packages,
};
} else {
tool = {
globs: rootPackageJsonContent.workspaces.packages,
tool: "yarn",
globs: rootPackageJsonContent.workspaces,
};
}
} else if (rootPackageJsonContent.bolt && rootPackageJsonContent.bolt.workspaces) {
tool = {
tool: "bolt",
globs: rootPackageJsonContent.bolt.workspaces,
tool: "bolt",
Comment thread
marcalexiei marked this conversation as resolved.
};
}
}

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 @@ -167,10 +187,12 @@ export let getChangedPackages = async ({
throw new Error("an error occurred when fetching files");
}

const rawConfig = await rawConfigPromise;

const releasePlan = assembleReleasePlan(
await Promise.all(changesetPromises),
packages,
await configPromise.then((rawConfig) => parseConfig(rawConfig, packages)),
parseConfig(rawConfig, packages),
Comment thread
marcalexiei marked this conversation as resolved.
Outdated
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 @@ -120,8 +119,8 @@ const hasChangesetBeenAdded = (
),
);

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

app.on(["pull_request.opened", "pull_request.synchronize"], async (context) => {
Expand Down Expand Up @@ -152,13 +151,13 @@ export default (app: Probot) => {
// 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;
}
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
"scripts": {
"format": "oxfmt",
"format:check": "oxfmt --check",
"lint": "oxlint",
"lint:fix": "oxlint --fix",
"test": "vitest"
},
"dependencies": {
Expand All @@ -32,11 +34,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
2 changes: 1 addition & 1 deletion pages/api/webhook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createNodeMiddleware, createProbot } from "probot";

import app from "../../index";

// requires:
// Requires:
// - APP_ID
// - PRIVATE_KEY
// - WEBHOOK_SECRET
Expand Down
Loading