astro-ghostcms/.pnpm-store/v3/files/f4/980ebc22a70cc5483146f7c92c7...

360 lines
9.9 KiB
Plaintext

import {
ReleasePlan,
Config,
ChangelogFunctions,
NewChangeset,
ModCompWithPackage,
} from "@changesets/types";
import { defaultConfig } from "@changesets/config";
import * as git from "@changesets/git";
import resolveFrom from "resolve-from";
import { Packages } from "@manypkg/get-packages";
import detectIndent from "detect-indent";
import fs from "fs-extra";
import path from "path";
import prettier from "prettier";
import versionPackage from "./version-package";
import getChangelogEntry from "./get-changelog-entry";
function getPrettierInstance(cwd: string): typeof prettier {
try {
return require(require.resolve("prettier", { paths: [cwd] }));
} catch (err) {
if (!err || (err as any).code !== "MODULE_NOT_FOUND") {
throw err;
}
return prettier;
}
}
function stringDefined(s: string | undefined): s is string {
return !!s;
}
async function getCommitsThatAddChangesets(
changesetIds: string[],
cwd: string
) {
const paths = changesetIds.map((id) => `.changeset/${id}.md`);
const commits = await git.getCommitsThatAddFiles(paths, { cwd });
if (commits.every(stringDefined)) {
// We have commits for all files
return commits;
}
// Some files didn't exist. Try legacy filenames instead
const missingIds = changesetIds
.map((id, i) => (commits[i] ? undefined : id))
.filter(stringDefined);
const legacyPaths = missingIds.map((id) => `.changeset/${id}/changes.json`);
const commitsForLegacyPaths = await git.getCommitsThatAddFiles(legacyPaths, {
cwd,
});
// Fill in the blanks in the array of commits
changesetIds.forEach((id, i) => {
if (!commits[i]) {
const missingIndex = missingIds.indexOf(id);
commits[i] = commitsForLegacyPaths[missingIndex];
}
});
return commits;
}
export default async function applyReleasePlan(
releasePlan: ReleasePlan,
packages: Packages,
config: Config = defaultConfig,
snapshot?: string | boolean
) {
let cwd = packages.root.dir;
let touchedFiles = [];
const packagesByName = new Map(
packages.packages.map((x) => [x.packageJson.name, x])
);
let { releases, changesets } = releasePlan;
let releasesWithPackage = releases.map((release) => {
let pkg = packagesByName.get(release.name);
if (!pkg)
throw new Error(
`Could not find matching package for release of: ${release.name}`
);
return {
...release,
...pkg,
};
});
// I think this might be the wrong place to do this, but gotta do it somewhere - add changelog entries to releases
let releaseWithChangelogs = await getNewChangelogEntry(
releasesWithPackage,
changesets,
config,
cwd
);
if (releasePlan.preState !== undefined && snapshot === undefined) {
if (releasePlan.preState.mode === "exit") {
await fs.remove(path.join(cwd, ".changeset", "pre.json"));
} else {
await fs.writeFile(
path.join(cwd, ".changeset", "pre.json"),
JSON.stringify(releasePlan.preState, null, 2) + "\n"
);
}
}
let versionsToUpdate = releases.map(({ name, newVersion, type }) => ({
name,
version: newVersion,
type,
}));
// iterate over releases updating packages
let finalisedRelease = releaseWithChangelogs.map((release) => {
return versionPackage(release, versionsToUpdate, {
updateInternalDependencies: config.updateInternalDependencies,
onlyUpdatePeerDependentsWhenOutOfRange:
config.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH
.onlyUpdatePeerDependentsWhenOutOfRange,
bumpVersionsWithWorkspaceProtocolOnly:
config.bumpVersionsWithWorkspaceProtocolOnly,
snapshot,
});
});
let prettierInstance = getPrettierInstance(cwd);
let prettierConfig = await prettierInstance.resolveConfig(cwd);
for (let release of finalisedRelease) {
let { changelog, packageJson, dir, name } = release;
const pkgJSONPath = path.resolve(dir, "package.json");
await updatePackageJson(pkgJSONPath, packageJson);
touchedFiles.push(pkgJSONPath);
if (changelog && changelog.length > 0) {
const changelogPath = path.resolve(dir, "CHANGELOG.md");
await updateChangelog(
changelogPath,
changelog,
name,
prettierInstance,
prettierConfig
);
touchedFiles.push(changelogPath);
}
}
if (
releasePlan.preState === undefined ||
releasePlan.preState.mode === "exit"
) {
let changesetFolder = path.resolve(cwd, ".changeset");
await Promise.all(
changesets.map(async (changeset) => {
let changesetPath = path.resolve(changesetFolder, `${changeset.id}.md`);
let changesetFolderPath = path.resolve(changesetFolder, changeset.id);
if (await fs.pathExists(changesetPath)) {
// DO NOT remove changeset for ignored packages
// Mixed changeset that contains both ignored packages and not ignored packages are disallowed
// At this point, we know there is no such changeset, because otherwise the program would've already failed,
// so we just check if any ignored package exists in this changeset, and only remove it if none exists
// Ignored list is added in v2, so we don't need to do it for v1 changesets
if (
!changeset.releases.find((release) =>
config.ignore.includes(release.name)
)
) {
touchedFiles.push(changesetPath);
await fs.remove(changesetPath);
}
// TO REMOVE LOGIC - this works to remove v1 changesets. We should be removed in the future
} else if (await fs.pathExists(changesetFolderPath)) {
touchedFiles.push(changesetFolderPath);
await fs.remove(changesetFolderPath);
}
})
);
}
// We return the touched files to be committed in the cli
return touchedFiles;
}
async function getNewChangelogEntry(
releasesWithPackage: ModCompWithPackage[],
changesets: NewChangeset[],
config: Config,
cwd: string
) {
if (!config.changelog) {
return Promise.resolve(
releasesWithPackage.map((release) => ({
...release,
changelog: null,
}))
);
}
let getChangelogFuncs: ChangelogFunctions = {
getReleaseLine: () => Promise.resolve(""),
getDependencyReleaseLine: () => Promise.resolve(""),
};
const changelogOpts = config.changelog[1];
let changesetPath = path.join(cwd, ".changeset");
let changelogPath = resolveFrom(changesetPath, config.changelog[0]);
let possibleChangelogFunc = require(changelogPath);
if (possibleChangelogFunc.default) {
possibleChangelogFunc = possibleChangelogFunc.default;
}
if (
typeof possibleChangelogFunc.getReleaseLine === "function" &&
typeof possibleChangelogFunc.getDependencyReleaseLine === "function"
) {
getChangelogFuncs = possibleChangelogFunc;
} else {
throw new Error("Could not resolve changelog generation functions");
}
let commits = await getCommitsThatAddChangesets(
changesets.map((cs) => cs.id),
cwd
);
let moddedChangesets = changesets.map((cs, i) => ({
...cs,
commit: commits[i],
}));
return Promise.all(
releasesWithPackage.map(async (release) => {
let changelog = await getChangelogEntry(
release,
releasesWithPackage,
moddedChangesets,
getChangelogFuncs,
changelogOpts,
{
updateInternalDependencies: config.updateInternalDependencies,
onlyUpdatePeerDependentsWhenOutOfRange:
config.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH
.onlyUpdatePeerDependentsWhenOutOfRange,
}
);
return {
...release,
changelog,
};
})
).catch((e) => {
console.error(
"The following error was encountered while generating changelog entries"
);
console.error(
"We have escaped applying the changesets, and no files should have been affected"
);
throw e;
});
}
async function updateChangelog(
changelogPath: string,
changelog: string,
name: string,
prettierInstance: typeof prettier,
prettierConfig: prettier.Options | null
) {
let templateString = `\n\n${changelog.trim()}\n`;
try {
if (fs.existsSync(changelogPath)) {
await prependFile(
changelogPath,
templateString,
name,
prettierInstance,
prettierConfig
);
} else {
await writeFormattedMarkdownFile(
changelogPath,
`# ${name}${templateString}`,
prettierInstance,
prettierConfig
);
}
} catch (e) {
console.warn(e);
}
}
async function updatePackageJson(
pkgJsonPath: string,
pkgJson: any
): Promise<void> {
const pkgRaw = await fs.readFile(pkgJsonPath, "utf-8");
const indent = detectIndent(pkgRaw).indent || " ";
const stringified =
JSON.stringify(pkgJson, null, indent) + (pkgRaw.endsWith("\n") ? "\n" : "");
return fs.writeFile(pkgJsonPath, stringified);
}
async function prependFile(
filePath: string,
data: string,
name: string,
prettierInstance: typeof prettier,
prettierConfig: prettier.Options | null
) {
const fileData = fs.readFileSync(filePath).toString();
// if the file exists but doesn't have the header, we'll add it in
if (!fileData) {
const completelyNewChangelog = `# ${name}${data}`;
await writeFormattedMarkdownFile(
filePath,
completelyNewChangelog,
prettierInstance,
prettierConfig
);
return;
}
const newChangelog = fileData.replace("\n", data);
await writeFormattedMarkdownFile(
filePath,
newChangelog,
prettierInstance,
prettierConfig
);
}
async function writeFormattedMarkdownFile(
filePath: string,
content: string,
prettierInstance: typeof prettier,
prettierConfig: prettier.Options | null
) {
await fs.writeFile(
filePath,
// Prettier v3 returns a promise
await prettierInstance.format(content, {
...prettierConfig,
filepath: filePath,
parser: "markdown",
})
);
}