Skip to content
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

feat(config): allow to provide glob expressions for linked and ignore packages #458

Merged
merged 7 commits into from
Oct 6, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/config-file-options.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ There are two caveats to this.

These restrictions exist to ensure your repository or published code do not end up in a broken state. For a more detailed intricacies of publishing, check out our guide on [problems publishing in monorepos](./problems-publishing-in-monorepos).

> NOTE: you can also provide glob expressions to match the packages, according to the [micromatch](https://www.npmjs.com/package/micromatch) format.
emmenko marked this conversation as resolved.
Show resolved Hide resolved

## `linked` (array of package names)

This option can be used to declare that packages should 'share' a version, instead of being versioned completely independently. As an example, if you have a `@changesets/button` component and a `@changesets/theme` component and you want to make sure that when one gets bumped to `2.0.0`, the other is also bumped to `2.0.0`. To achieve this you would have the config:
Expand Down
18 changes: 18 additions & 0 deletions docs/linked-packages.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,21 @@ I now have another changeset with a major for `pkg-a` and I do a release, the re

- `pkg-a` is at `3.0.0`
- `pkg-b` is at `2.0.0`

## Using glob expressions

Sometimes you want to link many or all packages within your project (for example in a monorepository setup), in which case you would need to keep the list of linked packages up-to-date.

To make it simpler to maintain that list, you can provide glob expressions in the linked list that would match and resolve to all the packages that you wish to include.

For example:

```json
{
"linked": [["pkg-*"]]
}
```

It will match all packages starting with `pkg-`.

**The glob expressions must be defined according to the [micromatch](https://www.npmjs.com/package/micromatch) format.**
4 changes: 3 additions & 1 deletion packages/config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
"@changesets/logger": "^0.0.5",
"@changesets/types": "^3.1.0",
"@manypkg/get-packages": "^1.0.1",
"fs-extra": "^7.0.1"
"@types/micromatch": "^4.0.1",
emmenko marked this conversation as resolved.
Show resolved Hide resolved
"fs-extra": "^7.0.1",
"micromatch": "^4.0.2"
},
"devDependencies": {
"fixturez": "^1.1.0",
Expand Down
188 changes: 116 additions & 72 deletions packages/config/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import fixturez from "fixturez";
import { read, parse } from "./";
import jestInCase from "jest-in-case";
import * as logger from "@changesets/logger";
import { Config, WrittenConfig } from "@changesets/types";
import { Packages } from "@manypkg/get-packages";

jest.mock("@changesets/logger");
Expand All @@ -17,6 +18,14 @@ let defaultPackages: Packages = {
tool: "yarn"
};

const withPackages = (pkgNames: string[]) => ({
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Helper function for the tests

...defaultPackages,
packages: pkgNames.map(pkgName => ({
packageJson: { name: pkgName, version: "" },
dir: "dir"
}))
});

test("read reads the config", async () => {
let dir = f.find("new-config");
let config = await read(dir, defaultPackages);
Expand Down Expand Up @@ -49,7 +58,13 @@ let defaults = {
}
} as const;

let correctCases = {
let correctCases: {
emmenko marked this conversation as resolved.
Show resolved Hide resolved
[caseName: string]: {
packages?: string[];
input: WrittenConfig;
output: Config;
};
} = {
defaults: {
input: {},
output: defaults
Expand Down Expand Up @@ -126,6 +141,23 @@ let correctCases = {
linked: [["pkg-a", "pkg-b"]]
}
},
linkedWithGlobs: {
packages: [
"pkg-a",
"pkg-b",
"@pkg/a",
"@pkg/b",
"@pkg-other/a",
"@pkg-other/b"
],
input: {
linked: [["pkg-*", "@pkg/*"], ["@pkg-other/a"]]
},
output: {
...defaults,
linked: [["pkg-a", "pkg-b", "@pkg/a", "@pkg/b"], ["@pkg-other/a"]]
}
},
"update internal dependencies minor": {
input: {
updateInternalDependencies: "minor"
Expand All @@ -152,26 +184,27 @@ let correctCases = {
...defaults,
ignore: ["pkg-a", "pkg-b"]
}
},
ignoreWithGlobs: {
packages: ["pkg-a", "pkg-b", "@pkg/a", "@pkg/b"],
input: {
ignore: ["pkg-*", "@pkg/*"]
},
output: {
...defaults,
ignore: ["pkg-a", "pkg-b", "@pkg/a", "@pkg/b"]
}
}
} as const;
};

jestInCase(
"parse",
testCase => {
expect(
parse(testCase.input, {
...defaultPackages,
packages: [
{
packageJson: { name: "pkg-a", version: "" },
dir: "dir"
},
{
packageJson: { name: "pkg-b", version: "" },
dir: "dir"
}
]
})
parse(
testCase.input,
withPackages(testCase.packages || ["pkg-a", "pkg-b"])
)
).toEqual(testCase.output);
},
correctCases
Expand All @@ -182,15 +215,18 @@ let unsafeParse = parse as any;
describe("parser errors", () => {
test("changelog invalid value", () => {
expect(() => {
unsafeParse({ changelog: {} });
unsafeParse({ changelog: {} }, defaultPackages);
}).toThrowErrorMatchingInlineSnapshot(`
"Some errors occurred when validating the changesets config:
The \`changelog\` option is set as {} when the only valid values are undefined, a module path(e.g. \\"@changesets/cli/changelog\\" or \\"./some-module\\") or a tuple with a module path and config for the changelog generator(e.g. [\\"@changesets/cli/changelog\\", { someOption: true }])"
`);
});
test("changelog array with 3 values", () => {
expect(() => {
unsafeParse({ changelog: ["some-module", "something", "other"] });
unsafeParse(
{ changelog: ["some-module", "something", "other"] },
defaultPackages
);
}).toThrowErrorMatchingInlineSnapshot(`
"Some errors occurred when validating the changesets config:
The \`changelog\` option is set as [
Expand All @@ -202,7 +238,7 @@ The \`changelog\` option is set as [
});
test("changelog array with first value not string", () => {
expect(() => {
unsafeParse({ changelog: [false, "something"] });
unsafeParse({ changelog: [false, "something"] }, defaultPackages);
emmenko marked this conversation as resolved.
Show resolved Hide resolved
}).toThrowErrorMatchingInlineSnapshot(`
"Some errors occurred when validating the changesets config:
The \`changelog\` option is set as [
Expand All @@ -213,31 +249,31 @@ The \`changelog\` option is set as [
});
test("access other string", () => {
expect(() => {
unsafeParse({ access: "something" });
unsafeParse({ access: "something" }, defaultPackages);
}).toThrowErrorMatchingInlineSnapshot(`
"Some errors occurred when validating the changesets config:
The \`access\` option is set as \\"something\\" when the only valid values are undefined, \\"public\\" or \\"restricted\\""
`);
});
test("commit non-boolean", () => {
expect(() => {
unsafeParse({ commit: "something" });
unsafeParse({ commit: "something" }, defaultPackages);
}).toThrowErrorMatchingInlineSnapshot(`
"Some errors occurred when validating the changesets config:
The \`commit\` option is set as \\"something\\" when the only valid values are undefined or a boolean"
`);
});
test("linked non-array", () => {
expect(() => {
unsafeParse({ linked: {} });
unsafeParse({ linked: {} }, defaultPackages);
}).toThrowErrorMatchingInlineSnapshot(`
"Some errors occurred when validating the changesets config:
The \`linked\` option is set as {} when the only valid values are undefined or an array of arrays of package names"
`);
});
test("linked array of non array", () => {
expect(() => {
unsafeParse({ linked: [{}] });
unsafeParse({ linked: [{}] }, defaultPackages);
}).toThrowErrorMatchingInlineSnapshot(`
"Some errors occurred when validating the changesets config:
The \`linked\` option is set as [
Expand All @@ -247,7 +283,7 @@ The \`linked\` option is set as [
});
test("linked array of array of non-string", () => {
expect(() => {
unsafeParse({ linked: [[{}]] });
unsafeParse({ linked: [[{}]] }, defaultPackages);
}).toThrowErrorMatchingInlineSnapshot(`
"Some errors occurred when validating the changesets config:
The \`linked\` option is set as [
Expand All @@ -257,64 +293,67 @@ The \`linked\` option is set as [
] when the only valid values are undefined or an array of arrays of package names"
`);
});
test("linked pacakge that does not exist", () => {
test("linked package that does not exist", () => {
expect(() => {
parse({ linked: [["not-existing"]] }, defaultPackages);
}).toThrowErrorMatchingInlineSnapshot(`
"Some errors occurred when validating the changesets config:
The package or glob expression \\"not-existing\\" specified in the \`linked\` option does not match any package in the project. You may have misspelled the package name or provided an invalid glob expression. Note that glob expressions must be defined according to https://www.npmjs.com/package/micromatch."
`);
});
test("linked package that does not exist (using glob expressions)", () => {
expect(() => {
parse({ linked: [["pkg-a"]] }, defaultPackages);
parse({ linked: [["pkg-a", "foo/*"]] }, withPackages(["pkg-a"]));
}).toThrowErrorMatchingInlineSnapshot(`
"Some errors occurred when validating the changesets config:
The package \\"pkg-a\\" is specified in the \`linked\` option but it is not found in the project. You may have misspelled the package name."
The package or glob expression \\"foo/*\\" specified in the \`linked\` option does not match any package in the project. You may have misspelled the package name or provided an invalid glob expression. Note that glob expressions must be defined according to https://www.npmjs.com/package/micromatch."
`);
});
test("linked package in two linked groups", () => {
expect(() => {
parse({ linked: [["pkg-a"], ["pkg-a"]] }, withPackages(["pkg-a"]));
}).toThrowErrorMatchingInlineSnapshot(`
"Some errors occurred when validating the changesets config:
The package \\"pkg-a\\" is defined in multiple sets of linked packages. Packages can only be defined in a single set of linked packages. If you are using glob expressions, make sure that they are valid according to https://www.npmjs.com/package/micromatch."
`);
});
test("linked package in two linked groups (using glob expressions)", () => {
expect(() => {
parse(
{ linked: [["pkg-a"], ["pkg-a"]] },
{
...defaultPackages,
packages: [
{
packageJson: { name: "pkg-a", version: "" },
dir: "dir"
}
]
}
{ linked: [["pkg-*"], ["pkg-*"]] },
withPackages(["pkg-a", "pkg-b"])
);
}).toThrowErrorMatchingInlineSnapshot(`
"Some errors occurred when validating the changesets config:
The package \\"pkg-a\\" is in multiple sets of linked packages. Packages can only be in a single set of linked packages."
The package \\"pkg-a\\" is defined in multiple sets of linked packages. Packages can only be defined in a single set of linked packages. If you are using glob expressions, make sure that they are valid according to https://www.npmjs.com/package/micromatch.
The package \\"pkg-b\\" is defined in multiple sets of linked packages. Packages can only be defined in a single set of linked packages. If you are using glob expressions, make sure that they are valid according to https://www.npmjs.com/package/micromatch."
`);
});
test("access private warns and sets to restricted", () => {
let config = unsafeParse({ access: "private" }, []);
let config = unsafeParse({ access: "private" }, defaultPackages);
expect(config).toEqual(defaults);
expect(logger.warn).toBeCalledWith(
'The `access` option is set as "private", but this is actually not a valid value - the correct form is "restricted".'
);
});
test("updateInternalDependencies not patch or minor", () => {
expect(() => {
unsafeParse({ updateInternalDependencies: "major" });
unsafeParse({ updateInternalDependencies: "major" }, defaultPackages);
}).toThrowErrorMatchingInlineSnapshot(`
"Some errors occurred when validating the changesets config:
The \`updateInternalDependencies\` option is set as \\"major\\" but can only be 'patch' or 'minor'"
`);
});
test("ignore non-array", () => {
expect(() =>
unsafeParse({
ignore: "string value"
})
).toThrowErrorMatchingInlineSnapshot(`
expect(() => unsafeParse({ ignore: "string value" }, defaultPackages))
.toThrowErrorMatchingInlineSnapshot(`
"Some errors occurred when validating the changesets config:
The \`ignore\` option is set as \\"string value\\" when the only valid values are undefined or an array of package names"
`);
});
test("ignore array of non-string", () => {
expect(() =>
unsafeParse({
ignore: [123, "pkg-a"]
})
).toThrowErrorMatchingInlineSnapshot(`
expect(() => unsafeParse({ ignore: [123, "pkg-a"] }, defaultPackages))
.toThrowErrorMatchingInlineSnapshot(`
"Some errors occurred when validating the changesets config:
The \`ignore\` option is set as [
123,
Expand All @@ -323,24 +362,23 @@ The \`ignore\` option is set as [
`);
});
test("ignore package that does not exist", () => {
expect(() =>
parse(
{
ignore: ["pkg-a"]
},
defaultPackages
)
).toThrowErrorMatchingInlineSnapshot(`
expect(() => unsafeParse({ ignore: ["pkg-a"] }, defaultPackages))
.toThrowErrorMatchingInlineSnapshot(`
"Some errors occurred when validating the changesets config:
The package or glob expression \\"pkg-a\\" is specified in the \`ignore\` option but it is not found in the project. You may have misspelled the package name or provided an invalid glob expression. Note that glob expressions must be defined according to https://www.npmjs.com/package/micromatch."
`);
});
test("ignore package that does not exist (using glob expressions)", () => {
expect(() => unsafeParse({ ignore: ["pkg-*"] }, defaultPackages))
.toThrowErrorMatchingInlineSnapshot(`
"Some errors occurred when validating the changesets config:
The package \\"pkg-a\\" is specified in the \`ignore\` option but it is not found in the project. You may have misspelled the package name."
The package or glob expression \\"pkg-*\\" is specified in the \`ignore\` option but it is not found in the project. You may have misspelled the package name or provided an invalid glob expression. Note that glob expressions must be defined according to https://www.npmjs.com/package/micromatch."
`);
});
test("ingore missing dependent packages", async () => {
expect(() =>
parse(
{
ignore: ["pkg-b"]
},
unsafeParse(
{ ignore: ["pkg-b"] },
{
...defaultPackages,
packages: [
Expand All @@ -367,23 +405,29 @@ The package \\"pkg-a\\" depends on the ignored package \\"pkg-b\\", but \\"pkg-a

test("onlyUpdatePeerDependentsWhenOutOfRange non-boolean", () => {
expect(() => {
unsafeParse({
___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
onlyUpdatePeerDependentsWhenOutOfRange: "not true"
}
});
unsafeParse(
{
___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
onlyUpdatePeerDependentsWhenOutOfRange: "not true"
}
},
defaultPackages
);
}).toThrowErrorMatchingInlineSnapshot(`
"Some errors occurred when validating the changesets config:
The \`onlyUpdatePeerDependentsWhenOutOfRange\` option is set as \\"not true\\" when the only valid values are undefined or a boolean"
`);
});
test("useCalculatedVersionForSnapshots non-boolean", () => {
expect(() => {
unsafeParse({
___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
useCalculatedVersionForSnapshots: "not true"
}
});
unsafeParse(
{
___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
useCalculatedVersionForSnapshots: "not true"
}
},
defaultPackages
);
}).toThrowErrorMatchingInlineSnapshot(`
"Some errors occurred when validating the changesets config:
The \`useCalculatedVersionForSnapshots\` option is set as \\"not true\\" when the only valid values are undefined or a boolean"
Expand Down