Skip to content

Commit

Permalink
Add support for tagging private packages when their version changes (#…
Browse files Browse the repository at this point in the history
…662)

* Tag private packages when their version changes

* Fixed formatting

* Export tagExists function

* Implement config option to opt into the private packages versioning feature

* Added doco on tracking private packages

* Fixed tests compilation

* Filter package list based on tracking private packages config

Fixes #620

* Fixed lint issue

* Fixed tests

* Update docs again

* Remove requirement of being logged into NPM if there are no unpublished packages

When only publishing private packages (apps?) we shouldn't need users to be logged into NPM

* Update .changeset/khaki-kangaroos-tie.md

Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>

* Update packages/config/src/index.ts

Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>

* Update packages/git/src/index.test.ts

Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>

* Use remote tag check instead of local

* Updated config to work against a privatePackages flag

Allows opting into tagging private package versions & also opting out of versioning private packages entirely

* Fixed lint error

* Fixed tests

* Fixed linting errors

* Update packages/cli/src/commands/publish/publishPackages.ts

Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>

* Update packages/cli/src/commands/add/createChangeset.ts

Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>

* Fixed duplication of logic when filtering listable packages

* Removed flags enum and simplified config

* Added cwd to tagExists and fixed tests

* Hoist tagging private packages out of publish package

* Addressed feedback

* Fix linting issues after rebase

* Fixed up issues around config

* Fixed some minor config issues

* Expanded scope of privatePackages changesets to include config and types packages

* tweak changesets

Co-authored-by: Mateusz Burzyński <mateuszburzynski@gmail.com>
  • Loading branch information
JakeGinnivan and Andarist committed Oct 1, 2022
1 parent 98d63e0 commit 8c08469
Show file tree
Hide file tree
Showing 20 changed files with 294 additions and 49 deletions.
9 changes: 9 additions & 0 deletions .changeset/fast-jars-thank.md
@@ -0,0 +1,9 @@
---
"@changesets/cli": minor
---

Private packages can now be tagged in the same way public packages do when they are published to npm.

To enable set `privatePackages: { version: true, tag: true }` in your config.json.

You can also now opt private packages out of versioning entirely by setting `privatePackages: false`.
5 changes: 5 additions & 0 deletions .changeset/khaki-kangaroos-tie.md
@@ -0,0 +1,5 @@
---
"@changesets/git": minor
---

Add `tagExists` & `remoteTagExists` git helpers
6 changes: 6 additions & 0 deletions .changeset/rich-horses-push.md
@@ -0,0 +1,6 @@
---
"@changesets/config": minor
"@changesets/types": minor
---

Added support for the `privatePackages` property in the config.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -55,6 +55,7 @@ To make releasing easier, you can use [this changesets github action](https://gi
- [Prereleases](./docs/prereleases.md)
- [Problems publishing in monorepos](./docs/problems-publishing-in-monorepos.md)
- [Snapshot releases](./docs/snapshot-releases.md)
- [Versioning applications and other non-npm packages](./docs/versioning-apps.md)
- [Experimental Options](./docs/experimental-options.md)

## Cool Projects already using Changesets for versioning and changelogs
Expand Down
22 changes: 22 additions & 0 deletions docs/versioning-apps.md
@@ -0,0 +1,22 @@
# Managing applications or non-npm packages

Changesets can also be used to manage application versions or non-npm packages (ie dotnet NuGet packages, ruby gems, docker images etc).

The only requirement is that the project has a package.json file to manage the versions and dependencies within the repo.

To enable this feature set `privatePackages` to `{ version: true, tag: true }` in your `.changesets/config.json` file. By default changesets will only update the changelog and version (ie `{ version: true, tag: false }`).

> **Note**
> Changesets only versions NPM package.json files, you can trigger releases for other package formats by creating workflows which trigger on tags/releases being created by changesets.
## Setting up a package

To enable a project to be tracked by changesets, it needs a minimal package.json with at least `name`, `private` and `version`.

```json
{
"name": "my-project",
"private": true,
"version": "0.0.1"
}
```
18 changes: 18 additions & 0 deletions packages/apply-release-plan/src/index.test.ts
Expand Up @@ -49,6 +49,7 @@ class FakeReleasePlan {
baseBranch: "main",
updateInternalDependencies: "patch",
ignore: [],
privatePackages: { version: true, tag: false },
___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
onlyUpdatePeerDependentsWhenOutOfRange: false,
updateInternalDependents: "out-of-range",
Expand Down Expand Up @@ -90,6 +91,7 @@ async function testSetup(
baseBranch: "main",
updateInternalDependencies: "patch",
ignore: [],
privatePackages: { version: true, tag: false },
snapshot: {
useCalculatedVersion: false,
prereleaseTemplate: null,
Expand Down Expand Up @@ -493,6 +495,7 @@ describe("apply release plan", () => {
access: "restricted",
baseBranch: "main",
updateInternalDependencies: "patch",
privatePackages: { version: true, tag: false },
ignore: [],
___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
onlyUpdatePeerDependentsWhenOutOfRange: false,
Expand Down Expand Up @@ -559,6 +562,7 @@ describe("apply release plan", () => {
access: "restricted",
baseBranch: "main",
updateInternalDependencies: "patch",
privatePackages: { version: true, tag: false },
ignore: [],
___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
onlyUpdatePeerDependentsWhenOutOfRange: false,
Expand Down Expand Up @@ -753,6 +757,7 @@ describe("apply release plan", () => {
baseBranch: "main",
updateInternalDependencies,
ignore: [],
privatePackages: { version: true, tag: false },
___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
onlyUpdatePeerDependentsWhenOutOfRange: false,
updateInternalDependents: "out-of-range",
Expand Down Expand Up @@ -841,6 +846,7 @@ describe("apply release plan", () => {
baseBranch: "main",
updateInternalDependencies,
ignore: [],
privatePackages: { version: true, tag: false },
___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
onlyUpdatePeerDependentsWhenOutOfRange: false,
updateInternalDependents: "out-of-range",
Expand Down Expand Up @@ -921,6 +927,7 @@ describe("apply release plan", () => {
baseBranch: "main",
updateInternalDependencies,
ignore: [],
privatePackages: { version: true, tag: false },
___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
onlyUpdatePeerDependentsWhenOutOfRange: false,
updateInternalDependents: "out-of-range",
Expand Down Expand Up @@ -1001,6 +1008,7 @@ describe("apply release plan", () => {
baseBranch: "main",
updateInternalDependencies,
ignore: [],
privatePackages: { version: true, tag: false },
___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
onlyUpdatePeerDependentsWhenOutOfRange: false,
updateInternalDependents: "out-of-range",
Expand Down Expand Up @@ -1084,6 +1092,7 @@ describe("apply release plan", () => {
baseBranch: "main",
updateInternalDependencies,
ignore: [],
privatePackages: { version: true, tag: false },
___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
onlyUpdatePeerDependentsWhenOutOfRange: false,
updateInternalDependents: "out-of-range",
Expand Down Expand Up @@ -1172,6 +1181,7 @@ describe("apply release plan", () => {
baseBranch: "main",
updateInternalDependencies,
ignore: [],
privatePackages: { version: true, tag: false },
___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
onlyUpdatePeerDependentsWhenOutOfRange: false,
updateInternalDependents: "out-of-range",
Expand Down Expand Up @@ -1252,6 +1262,7 @@ describe("apply release plan", () => {
baseBranch: "main",
updateInternalDependencies,
ignore: [],
privatePackages: { version: true, tag: false },
___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
onlyUpdatePeerDependentsWhenOutOfRange: false,
updateInternalDependents: "out-of-range",
Expand Down Expand Up @@ -1332,6 +1343,7 @@ describe("apply release plan", () => {
baseBranch: "main",
updateInternalDependencies,
ignore: [],
privatePackages: { version: true, tag: false },
___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
onlyUpdatePeerDependentsWhenOutOfRange: false,
updateInternalDependents: "out-of-range",
Expand Down Expand Up @@ -1416,6 +1428,7 @@ describe("apply release plan", () => {
baseBranch: "main",
updateInternalDependencies: "patch",
ignore: [],
privatePackages: { version: true, tag: false },
___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
onlyUpdatePeerDependentsWhenOutOfRange: true,
updateInternalDependents: "out-of-range",
Expand Down Expand Up @@ -1596,6 +1609,7 @@ describe("apply release plan", () => {
],
updateInternalDependencies: "patch",
ignore: [],
privatePackages: { version: true, tag: false },
___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
onlyUpdatePeerDependentsWhenOutOfRange: false,
updateInternalDependents: "out-of-range",
Expand Down Expand Up @@ -1704,6 +1718,7 @@ describe("apply release plan", () => {
baseBranch: "main",
updateInternalDependencies: "patch",
ignore: [],
privatePackages: { version: true, tag: false },
___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
onlyUpdatePeerDependentsWhenOutOfRange: false,
updateInternalDependents: "out-of-range",
Expand Down Expand Up @@ -1792,6 +1807,7 @@ describe("apply release plan", () => {
baseBranch: "main",
updateInternalDependencies: "minor",
ignore: [],
privatePackages: { version: true, tag: false },
___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
onlyUpdatePeerDependentsWhenOutOfRange: false,
updateInternalDependents: "out-of-range",
Expand Down Expand Up @@ -1884,6 +1900,7 @@ describe("apply release plan", () => {
baseBranch: "main",
updateInternalDependencies: "minor",
ignore: [],
privatePackages: { version: true, tag: false },
___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
onlyUpdatePeerDependentsWhenOutOfRange: false,
updateInternalDependents: "out-of-range",
Expand Down Expand Up @@ -1990,6 +2007,7 @@ describe("apply release plan", () => {
baseBranch: "main",
updateInternalDependencies: "minor",
ignore: [],
privatePackages: { version: true, tag: false },
___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
onlyUpdatePeerDependentsWhenOutOfRange: false,
updateInternalDependents: "out-of-range",
Expand Down
14 changes: 8 additions & 6 deletions packages/cli/src/commands/add/__tests__/add.ts
Expand Up @@ -116,7 +116,7 @@ describe("Changesets", () => {
"should read summary",
// @ts-ignore
async ({ consoleSummaries, editorSummaries, expectedSummary }) => {
const cwd = await f.copy("simple-project");
const cwd = f.copy("simple-project");

mockUserResponses({
releases: { "pkg-a": "patch" },
Expand All @@ -137,7 +137,7 @@ describe("Changesets", () => {
);

it("should generate a changeset in a single package repo", async () => {
const cwd = await f.copy("single-package");
const cwd = f.copy("single-package");

const summary = "summary message mock";

Expand Down Expand Up @@ -175,7 +175,7 @@ describe("Changesets", () => {
});

it("should commit when the commit flag is passed in", async () => {
const cwd = await f.copy("simple-project-custom-config");
const cwd = f.copy("simple-project-custom-config");

mockUserResponses({ releases: { "pkg-a": "patch" } });
await addChangeset(
Expand All @@ -191,7 +191,7 @@ describe("Changesets", () => {
});

it("should create empty changeset when empty flag is passed in", async () => {
const cwd = await f.copy("simple-project");
const cwd = f.copy("simple-project");

await addChangeset(cwd, { empty: true }, defaultConfig);

Expand All @@ -204,8 +204,9 @@ describe("Changesets", () => {
})
);
});

it("should not include ignored packages in the prompt", async () => {
const cwd = await f.copy("internal-dependencies");
const cwd = f.copy("internal-dependencies");

mockUserResponses({ releases: { "pkg-a": "patch" } });
await addChangeset(
Expand All @@ -218,8 +219,9 @@ describe("Changesets", () => {
const { choices } = askCheckboxPlus.mock.calls[0][1][0];
expect(choices).toEqual(["pkg-a", "pkg-c"]);
});

it("should not include private packages without a version in the prompt", async () => {
const cwd = await f.copy("private-package-without-version-field");
const cwd = f.copy("private-package-without-version-field");

mockUserResponses({ releases: { "pkg-a": "patch" } });
await addChangeset(cwd, { empty: false }, defaultConfig);
Expand Down
12 changes: 6 additions & 6 deletions packages/cli/src/commands/add/createChangeset.ts
Expand Up @@ -94,16 +94,16 @@ async function getPackagesToRelease(
return [allPackages[0].packageJson.name];
}

function formatPkgNameAndVersion(pkgName: string, version: string) {
return `${bold(pkgName)}@${bold(version)}`;
}

function getPkgJsonByName(packages: Package[]) {
function getPkgJsonsByName(packages: Package[]) {
return new Map(
packages.map(({ packageJson }) => [packageJson.name, packageJson])
);
}

function formatPkgNameAndVersion(pkgName: string, version: string) {
return `${bold(pkgName)}@${bold(version)}`;
}

export default async function createChangeset(
changedPackages: Array<string>,
allPackages: Package[]
Expand All @@ -116,7 +116,7 @@ export default async function createChangeset(
allPackages
);

let pkgJsonsByName = getPkgJsonByName(allPackages);
let pkgJsonsByName = getPkgJsonsByName(allPackages);

let pkgsLeftToGetBumpTypeFor = new Set(packagesToRelease);

Expand Down
9 changes: 1 addition & 8 deletions packages/cli/src/commands/add/index.ts
Expand Up @@ -13,14 +13,7 @@ import { getCommitFunctions } from "../../commit/getCommitFunctions";
import createChangeset from "./createChangeset";
import printConfirmationMessage from "./messages";
import { ExternalEditor } from "external-editor";
import { PackageJSON } from "@changesets/types";

function isListablePackage(config: Config, packageJson: PackageJSON) {
return (
!config.ignore.includes(packageJson.name) &&
(packageJson.version || !packageJson.private)
);
}
import { isListablePackage } from "./isListablePackage";

export default async function add(
cwd: string,
Expand Down
17 changes: 17 additions & 0 deletions packages/cli/src/commands/add/isListablePackage.ts
@@ -0,0 +1,17 @@
import { Config } from "@changesets/types";
import { PackageJSON } from "@changesets/types";

export function isListablePackage(config: Config, packageJson: PackageJSON) {
const packageIgnoredInConfig = config.ignore.includes(packageJson.name);

if (packageIgnoredInConfig) {
return false;
}

if (!config.privatePackages && packageJson.private) {

This comment has been minimized.

Copy link
@mino01x

mino01x Oct 19, 2022

Contributor

is something wrong? or i misunderstand

!config.privatePackages?.version && packageJson.private

This comment has been minimized.

Copy link
@Andarist

Andarist Oct 19, 2022

Author Member

Yes, there is a chance that there is a problem with the current condition. It would be lovely to get a PR with a fix and a test case for this.

return false;
}

const hasVersionField = !!packageJson.version;
return hasVersionField;
}
Expand Up @@ -33,7 +33,7 @@ describe("running release", () => {
let cwd: string;

beforeEach(async () => {
cwd = await f.copy("simple-project");
cwd = f.copy("simple-project");
});

describe("When there is no changeset commits", () => {
Expand Down
38 changes: 38 additions & 0 deletions packages/cli/src/commands/publish/getUntaggedPrivatePackages.ts
@@ -0,0 +1,38 @@
import * as git from "@changesets/git";
import { Package, Tool } from "@manypkg/get-packages";
import { PublishedResult } from "./publishPackages";

export async function getUntaggedPrivatePackages(
privatePackages: Package[],
cwd: string,
tool: Tool
) {
const packageWithTags = await Promise.all(
privatePackages.map(async (privatePkg) => {
const tagName =
tool === "root"
? `v${privatePkg.packageJson.version}`
: `${privatePkg.packageJson.name}@${privatePkg.packageJson.version}`;
const isMissingTag = !(
(await git.tagExists(tagName, cwd)) ||
(await git.remoteTagExists(tagName))
);

return { pkg: privatePkg, isMissingTag };
})
);

const untagged: PublishedResult[] = [];

for (const packageWithTag of packageWithTags) {
if (packageWithTag.isMissingTag) {
untagged.push({
name: packageWithTag.pkg.packageJson.name,
newVersion: packageWithTag.pkg.packageJson.version,
published: false,
});
}
}

return untagged;
}

0 comments on commit 8c08469

Please sign in to comment.