@changesets/parse
Version:
Parse a changeset file's contents into a usable json object
111 lines (95 loc) • 3.48 kB
text/typescript
import yaml from "js-yaml";
import { Release, VersionType } from "@changesets/types";
const mdRegex = /\s*---([^]*?)\n\s*---(\s*(?:\n|$)[^]*)/;
const EXAMPLE_FORMAT = `---\n"package-name": patch\n---`;
const validVersionTypes: readonly VersionType[] = [
"major",
"minor",
"patch",
"none",
];
function truncate(s: string, max = 200): string {
return s.length > max ? s.slice(0, max) + "..." : s;
}
function validateReleases(releases: Release[], contents: string): void {
for (const release of releases) {
if (typeof release.name !== "string" || release.name.trim() === "") {
throw new Error(
`could not parse changeset - invalid package name in frontmatter.\n` +
`Expected a non-empty string for package name, but got: ${JSON.stringify(
release.name
)}\n` +
`Changeset contents:\n${truncate(contents)}`
);
}
if (typeof release.type !== "string") {
throw new Error(
`could not parse changeset - invalid release type for package "${release.name}".\n` +
`Expected a string for release type, but got: ${typeof release.type}\n` +
`Changeset contents:\n${truncate(contents)}`
);
}
if (!validVersionTypes.includes(release.type)) {
throw new Error(
`could not parse changeset - invalid version type ${JSON.stringify(
release.type
)} for package "${release.name}".\n` +
`Valid version types are: ${validVersionTypes.join(", ")}\n` +
`Changeset contents:\n${truncate(contents)}`
);
}
}
}
export default function parseChangesetFile(contents: string): {
summary: string;
releases: Release[];
} {
const trimmedContents = contents.trim();
if (!trimmedContents) {
throw new Error(
`could not parse changeset - file is empty.\n` +
`Changesets must have frontmatter with package names and version types.\n` +
`Example:\n${EXAMPLE_FORMAT}\n\nYour changeset summary here.`
);
}
const execResult = mdRegex.exec(contents);
if (!execResult) {
throw new Error(
`could not parse changeset - missing or invalid frontmatter.\n` +
`Changesets must start with frontmatter delimited by "---".\n` +
`Example:\n${EXAMPLE_FORMAT}\n\nYour changeset summary here.\n` +
`Received content:\n${truncate(trimmedContents)}`
);
}
let [, roughReleases, roughSummary] = execResult;
let summary = roughSummary.trim();
let releases: Release[];
let yamlStuff: Record<string, VersionType> | undefined;
try {
yamlStuff = yaml.load(roughReleases) as typeof yamlStuff;
} catch (e) {
throw new Error(
`could not parse changeset - invalid YAML in frontmatter.\n` +
`The frontmatter between the "---" delimiters must be valid YAML.\n` +
`YAML error: ${e instanceof Error ? e.message : String(e)}\n` +
`Frontmatter content:\n${roughReleases}`
);
}
if (yamlStuff) {
if (typeof yamlStuff !== "object" || Array.isArray(yamlStuff)) {
throw new Error(
`could not parse changeset - frontmatter must be an object mapping package names to version types.\n` +
`Expected format:\n${EXAMPLE_FORMAT}\n` +
`Received:\n${roughReleases}`
);
}
releases = Object.entries(yamlStuff).map(([name, type]) => ({
name,
type,
}));
} else {
releases = [];
}
validateReleases(releases, contents);
return { releases, summary };
}