UNPKG

@changesets/parse

Version:

Parse a changeset file's contents into a usable json object

111 lines (95 loc) 3.48 kB
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 }; }