-
-
Notifications
You must be signed in to change notification settings - Fork 787
fix(parse): improve error messages for malformed changesets #1831
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
464283d
5e2c8eb
e5166df
a315dfd
5485d2d
2d4920f
e6e5d8e
4f4e8d5
3be3624
b165615
5f1b473
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@changesets/parse": patch | ||
| --- | ||
|
|
||
| Improve error messages for malformed changeset files. The new error messages explain what went wrong, show what was received, and provide examples of the correct format. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,42 +3,108 @@ 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)}` | ||
| ); | ||
| } | ||
|
Comment on lines
+39
to
+47
|
||
| } | ||
| } | ||
|
|
||
| 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 - invalid frontmatter: ${contents}` | ||
| `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 { | ||
| const yamlStuff = yaml.load(roughReleases) as | ||
| | Record<string, VersionType> | ||
| | undefined; | ||
|
|
||
| if (yamlStuff) { | ||
| releases = Object.entries(yamlStuff).map(([name, type]) => ({ | ||
| name, | ||
| type, | ||
| })); | ||
| } else { | ||
| releases = []; | ||
| } | ||
| yamlStuff = yaml.load(roughReleases) as typeof yamlStuff; | ||
| } catch (e) { | ||
| throw new Error( | ||
| `could not parse changeset - invalid frontmatter: ${contents}` | ||
| `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 (!releases) { | ||
| throw new Error(`could not parse changeset - unknown error: ${contents}`); | ||
| 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 }; | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.