🚨 Active Supply Chain Attack:node-ipc Package Compromised.Learn More
Socket
Book a DemoSign in
Socket

@changesets/parse

Package Overview
Dependencies
Maintainers
4
Versions
31
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@changesets/parse - npm Package Compare versions

Comparing version
1.0.0-next.1
to
1.0.0-next.2
+19
-0
CHANGELOG.md
# @changesets/parse
## 1.0.0-next.2
### Patch Changes
- Updated dependencies [[`c19b112`](https://github.com/changesets/changesets/commit/c19b1123d27986da0e14e99d65b0f9a408def35c)]:
- @changesets/types@7.0.0-next.2
## 0.4.3
### Patch Changes
- [#1831](https://github.com/changesets/changesets/pull/1831) [`1f91879`](https://github.com/changesets/changesets/commit/1f91879d4977c49593619b07e1374cd804f16757) Thanks [@murataslan1](https://github.com/murataslan1)! - 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.
## 0.4.2
### Patch Changes
- [#1772](https://github.com/changesets/changesets/pull/1772) [`4c5a207`](https://github.com/changesets/changesets/commit/4c5a2078e45d1d269ecc65c3c24b898ed6451245) Thanks [@marcalexiei](https://github.com/marcalexiei)! - Update `js-yaml` to `v4`
## 1.0.0-next.1

@@ -4,0 +23,0 @@

+37
-13
import yaml from 'js-yaml';
const mdRegex = /\s*---([^]*?)\n\s*---(\s*(?:\n|$)[^]*)/;
const EXAMPLE_FORMAT = `---\n"package-name": patch\n---`;
const validVersionTypes = ["major", "minor", "patch", "none"];
function truncate(s, max = 200) {
return s.length > max ? s.slice(0, max) + "..." : s;
}
function validateReleases(releases, contents) {
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)}`);
}
}
}
function parseChangesetFile(contents) {
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}`);
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)}`);
}

@@ -12,18 +34,20 @@ let [, roughReleases, roughSummary] = execResult;

let releases;
let yamlStuff;
try {
const yamlStuff = yaml.safeLoad(roughReleases);
if (yamlStuff) {
releases = Object.entries(yamlStuff).map(([name, type]) => ({
name,
type
}));
} else {
releases = [];
}
yamlStuff = yaml.load(roughReleases);
} catch (e) {
throw new Error(`could not parse changeset - invalid frontmatter: ${contents}`);
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 (!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 {

@@ -30,0 +54,0 @@ releases,

{
"name": "@changesets/parse",
"version": "1.0.0-next.1",
"version": "1.0.0-next.2",
"description": "Parse a changeset file's contents into a usable json object",

@@ -13,11 +13,12 @@ "type": "module",

"dependencies": {
"@changesets/types": "^7.0.0-next.1",
"js-yaml": "^3.13.1"
"@changesets/types": "^7.0.0-next.2",
"js-yaml": "^4.1.1"
},
"devDependencies": {
"@types/js-yaml": "^4.0.9",
"outdent": "^0.8.0"
},
"engines": {
"node": ">=20.0.0"
"node": ">=20.19.0"
}
}

@@ -218,2 +218,16 @@ import { outdent } from "outdent";

});
it("should handle package name unquoted and version quoted", () => {
const changesetMd = `---
pkg: "minor"
---
something`;
const changeset = parse(changesetMd);
expect(changeset).toEqual({
releases: [{ name: "pkg", type: "minor" }],
summary: "something",
});
});
it("should throw if the frontmatter is followed by non-whitespace characters on the same line", () => {

@@ -228,3 +242,12 @@ const changesetMd = outdent`---

expect(() => parse(changesetMd)).toThrowErrorMatchingInlineSnapshot(`
[Error: could not parse changeset - invalid frontmatter: ---
[Error: could not parse changeset - missing or invalid frontmatter.
Changesets must start with frontmatter delimited by "---".
Example:
---
"package-name": patch
---
Your changeset summary here.
Received content:
---
"cool-package": minor

@@ -236,2 +259,113 @@ --- fail

});
it("should throw when frontmatter hasn't a valid yml structure", () => {
const changesetMd = outdent`---
: minor
---
Nice simple summary
`;
expect(() => parse(changesetMd)).toThrowErrorMatchingInlineSnapshot(`
[Error: could not parse changeset - invalid YAML in frontmatter.
The frontmatter between the "---" delimiters must be valid YAML.
YAML error: incomplete explicit mapping pair; a key node is missed; or followed by a non-tabulated empty line (2:1)
1 |
2 | : minor
-----^
Frontmatter content:
: minor]
`);
});
it("should throw when file is completely empty", () => {
expect(() => parse("")).toThrowErrorMatchingInlineSnapshot(`
[Error: could not parse changeset - file is empty.
Changesets must have frontmatter with package names and version types.
Example:
---
"package-name": patch
---
Your changeset summary here.]
`);
expect(() => parse(" ")).toThrowErrorMatchingInlineSnapshot(`
[Error: could not parse changeset - file is empty.
Changesets must have frontmatter with package names and version types.
Example:
---
"package-name": patch
---
Your changeset summary here.]
`);
expect(() => parse("\n\n")).toThrowErrorMatchingInlineSnapshot(`
[Error: could not parse changeset - file is empty.
Changesets must have frontmatter with package names and version types.
Example:
---
"package-name": patch
---
Your changeset summary here.]
`);
});
it("should throw when frontmatter is missing", () => {
const changesetMd = "Just some content without frontmatter";
expect(() => parse(changesetMd)).toThrowErrorMatchingInlineSnapshot(`
[Error: could not parse changeset - missing or invalid frontmatter.
Changesets must start with frontmatter delimited by "---".
Example:
---
"package-name": patch
---
Your changeset summary here.
Received content:
Just some content without frontmatter]
`);
});
it("should throw when version type is invalid", () => {
const changesetMd = outdent`---
"cool-package": invalid-type
---
Nice simple summary
`;
expect(() => parse(changesetMd)).toThrowErrorMatchingInlineSnapshot(`
[Error: could not parse changeset - invalid version type "invalid-type" for package "cool-package".
Valid version types are: major, minor, patch, none
Changeset contents:
---
"cool-package": invalid-type
---
Nice simple summary]
`);
});
it("should throw with helpful message when package name is empty", () => {
const changesetMd = outdent`---
"": minor
---
Nice simple summary
`;
expect(() => parse(changesetMd)).toThrowErrorMatchingInlineSnapshot(`
[Error: could not parse changeset - invalid package name in frontmatter.
Expected a non-empty string for package name, but got: ""
Changeset contents:
---
"": minor
---
Nice simple summary]
`);
});
});

@@ -6,2 +6,47 @@ import yaml from "js-yaml";

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): {

@@ -11,6 +56,19 @@ summary: string;

} {
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)}`,
);

@@ -22,24 +80,34 @@ }

let releases: Release[];
let yamlStuff: Record<string, VersionType> | undefined;
try {
const yamlStuff: { [key: string]: VersionType } =
yaml.safeLoad(roughReleases);
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 };
}