diff --git a/.changeset/fast-jars-thank.md b/.changeset/fast-jars-thank.md new file mode 100644 index 000000000..d2700b194 --- /dev/null +++ b/.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`. diff --git a/.changeset/khaki-kangaroos-tie.md b/.changeset/khaki-kangaroos-tie.md new file mode 100644 index 000000000..eb5dbebff --- /dev/null +++ b/.changeset/khaki-kangaroos-tie.md @@ -0,0 +1,5 @@ +--- +"@changesets/git": minor +--- + +Add `tagExists` & `remoteTagExists` git helpers diff --git a/.changeset/rich-horses-push.md b/.changeset/rich-horses-push.md new file mode 100644 index 000000000..09f611a7e --- /dev/null +++ b/.changeset/rich-horses-push.md @@ -0,0 +1,6 @@ +--- +"@changesets/config": minor +"@changesets/types": minor +--- + +Added support for the `privatePackages` property in the config. diff --git a/README.md b/README.md index c82537068..4ba10bfd0 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/versioning-apps.md b/docs/versioning-apps.md new file mode 100644 index 000000000..6a3e08950 --- /dev/null +++ b/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" +} +``` diff --git a/packages/apply-release-plan/src/index.test.ts b/packages/apply-release-plan/src/index.test.ts index a0facff7c..c73cc3f2d 100644 --- a/packages/apply-release-plan/src/index.test.ts +++ b/packages/apply-release-plan/src/index.test.ts @@ -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", @@ -90,6 +91,7 @@ async function testSetup( baseBranch: "main", updateInternalDependencies: "patch", ignore: [], + privatePackages: { version: true, tag: false }, snapshot: { useCalculatedVersion: false, prereleaseTemplate: null, @@ -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, @@ -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, @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", @@ -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", diff --git a/packages/cli/src/commands/add/__tests__/add.ts b/packages/cli/src/commands/add/__tests__/add.ts index 78f8d8138..833e2ce30 100644 --- a/packages/cli/src/commands/add/__tests__/add.ts +++ b/packages/cli/src/commands/add/__tests__/add.ts @@ -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" }, @@ -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"; @@ -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( @@ -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); @@ -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( @@ -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); diff --git a/packages/cli/src/commands/add/createChangeset.ts b/packages/cli/src/commands/add/createChangeset.ts index f5898b8f2..c6bb0bfd9 100644 --- a/packages/cli/src/commands/add/createChangeset.ts +++ b/packages/cli/src/commands/add/createChangeset.ts @@ -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, allPackages: Package[] @@ -116,7 +116,7 @@ export default async function createChangeset( allPackages ); - let pkgJsonsByName = getPkgJsonByName(allPackages); + let pkgJsonsByName = getPkgJsonsByName(allPackages); let pkgsLeftToGetBumpTypeFor = new Set(packagesToRelease); diff --git a/packages/cli/src/commands/add/index.ts b/packages/cli/src/commands/add/index.ts index c15b90f2e..9352aa483 100644 --- a/packages/cli/src/commands/add/index.ts +++ b/packages/cli/src/commands/add/index.ts @@ -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, diff --git a/packages/cli/src/commands/add/isListablePackage.ts b/packages/cli/src/commands/add/isListablePackage.ts new file mode 100644 index 000000000..16d17c177 --- /dev/null +++ b/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) { + return false; + } + + const hasVersionField = !!packageJson.version; + return hasVersionField; +} diff --git a/packages/cli/src/commands/publish/__tests__/releaseCommand.test.ts b/packages/cli/src/commands/publish/__tests__/releaseCommand.test.ts index 4e4aecad0..5c5427046 100644 --- a/packages/cli/src/commands/publish/__tests__/releaseCommand.test.ts +++ b/packages/cli/src/commands/publish/__tests__/releaseCommand.test.ts @@ -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", () => { diff --git a/packages/cli/src/commands/publish/getUntaggedPrivatePackages.ts b/packages/cli/src/commands/publish/getUntaggedPrivatePackages.ts new file mode 100644 index 000000000..ed60c7b7c --- /dev/null +++ b/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; +} diff --git a/packages/cli/src/commands/publish/index.ts b/packages/cli/src/commands/publish/index.ts index 377d5e47c..d57f6edf9 100644 --- a/packages/cli/src/commands/publish/index.ts +++ b/packages/cli/src/commands/publish/index.ts @@ -1,4 +1,4 @@ -import publishPackages from "./publishPackages"; +import publishPackages, { PublishedResult } from "./publishPackages"; import { ExitError } from "@changesets/errors"; import { error, log, success, warn } from "@changesets/logger"; import * as git from "@changesets/git"; @@ -6,6 +6,7 @@ import { readPreState } from "@changesets/pre"; import { Config, PreState } from "@changesets/types"; import { getPackages } from "@manypkg/get-packages"; import chalk from "chalk"; +import { getUntaggedPrivatePackages } from "./getUntaggedPrivatePackages"; function logReleases(pkgs: Array<{ name: string; newVersion: string }>) { const mappedPkgs = pkgs.map((p) => `${p.name}@${p.newVersion}`).join("\n"); @@ -57,7 +58,9 @@ export default async function run( const { packages, tool } = await getPackages(cwd); - const response = await publishPackages({ + const tagPrivatePackages = + config.privatePackages && config.privatePackages.tag; + const publishedPackages = await publishPackages({ packages, // if not public, we won't pass the access, and it works as normal access: config.access, @@ -65,37 +68,68 @@ export default async function run( preState, tag: releaseTag, }); + const privatePackages = packages.filter( + (pkg) => pkg.packageJson.private && pkg.packageJson.version + ); + const untaggedPrivatePackageReleases = tagPrivatePackages + ? await getUntaggedPrivatePackages(privatePackages, cwd, tool) + : []; - const successful = response.filter((p) => p.published); - const unsuccessful = response.filter((p) => !p.published); + if ( + publishedPackages.length === 0 && + untaggedPrivatePackageReleases.length === 0 + ) { + warn("No unpublished projects to publish"); + } + + const successfulNpmPublishes = publishedPackages.filter((p) => p.published); + const unsuccessfulNpmPublishes = publishedPackages.filter( + (p) => !p.published + ); - if (successful.length > 0) { + if (successfulNpmPublishes.length > 0) { success("packages published successfully:"); - logReleases(successful); + logReleases(successfulNpmPublishes); if (gitTag) { // We create the tags after the push above so that we know that HEAD won't change and that pushing // won't suffer from a race condition if another merge happens in the mean time (pushing tags won't // fail if we are behind the base branch). - log(`Creating git tag${successful.length > 1 ? "s" : ""}...`); - if (tool !== "root") { - for (const pkg of successful) { - const tag = `${pkg.name}@${pkg.newVersion}`; - log("New tag: ", tag); - await git.tag(tag, cwd); - } - } else { - const tag = `v${successful[0].newVersion}`; - log("New tag: ", tag); - await git.tag(tag, cwd); - } + log(`Creating git tag${successfulNpmPublishes.length > 1 ? "s" : ""}...`); + + await tagPublish(tool, successfulNpmPublishes, cwd); } } - if (unsuccessful.length > 0) { + if (untaggedPrivatePackageReleases.length > 0) { + success("found untagged projects:"); + logReleases(untaggedPrivatePackageReleases); + + await tagPublish(tool, untaggedPrivatePackageReleases, cwd); + } + + if (unsuccessfulNpmPublishes.length > 0) { error("packages failed to publish:"); - logReleases(unsuccessful); + logReleases(unsuccessfulNpmPublishes); throw new ExitError(1); } } + +async function tagPublish( + tool: string, + packageReleases: PublishedResult[], + cwd: string +) { + if (tool !== "root") { + for (const pkg of packageReleases) { + const tag = `${pkg.name}@${pkg.newVersion}`; + log("New tag: ", tag); + await git.tag(tag, cwd); + } + } else { + const tag = `v${packageReleases[0].newVersion}`; + log("New tag: ", tag); + await git.tag(tag, cwd); + } +} diff --git a/packages/cli/src/commands/publish/publishPackages.ts b/packages/cli/src/commands/publish/publishPackages.ts index 02fce7625..c48ab2f4a 100644 --- a/packages/cli/src/commands/publish/publishPackages.ts +++ b/packages/cli/src/commands/publish/publishPackages.ts @@ -18,7 +18,7 @@ type PkgInfo = { publishedVersions: string[]; }; -type PublishedResult = { +export type PublishedResult = { name: string; newVersion: string; published: boolean; @@ -88,19 +88,20 @@ export default async function publishPackages({ }) { const packagesByName = new Map(packages.map((x) => [x.packageJson.name, x])); const publicPackages = packages.filter((pkg) => !pkg.packageJson.private); - const twoFactorState: TwoFactorState = getTwoFactorState({ - otp, - publicPackages, - }); const unpublishedPackagesInfo = await getUnpublishedPackages( publicPackages, preState ); if (unpublishedPackagesInfo.length === 0) { - warn("No unpublished packages to publish"); + return []; } + const twoFactorState: TwoFactorState = getTwoFactorState({ + otp, + publicPackages, + }); + return Promise.all( unpublishedPackagesInfo.map((pkgInfo) => { let pkg = packagesByName.get(pkgInfo.name)!; diff --git a/packages/config/schema.json b/packages/config/schema.json index f973d07a7..e7eb04e8b 100644 --- a/packages/config/schema.json +++ b/packages/config/schema.json @@ -75,6 +75,20 @@ "description": "Determines whether Changesets should commit the results of the add and version command.", "default": false }, + "privatePackages": { + "anyOf": [ + { + "type": "object", + "properties": { + "tag": { "type": "boolean" }, + "version": { "type": "boolean" } + } + }, + { + "const": false + } + ] + }, "access": { "enum": ["restricted", "public"], "type": "string", diff --git a/packages/config/src/index.test.ts b/packages/config/src/index.test.ts index 85b1f8e79..6b12f530c 100644 --- a/packages/config/src/index.test.ts +++ b/packages/config/src/index.test.ts @@ -45,6 +45,10 @@ test("read reads the config", async () => { updateInternalDependencies: "patch", ignore: [], bumpVersionsWithWorkspaceProtocolOnly: false, + privatePackages: { + tag: false, + version: true, + }, ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, updateInternalDependents: "out-of-range", @@ -56,7 +60,7 @@ test("read reads the config", async () => { }); }); -let defaults = { +let defaults: Config = { fixed: [], linked: [], changelog: ["@changesets/cli/changelog", null], @@ -65,6 +69,7 @@ let defaults = { baseBranch: "master", updateInternalDependencies: "patch", ignore: [], + privatePackages: { version: true, tag: false }, ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, updateInternalDependents: "out-of-range", @@ -74,7 +79,7 @@ let defaults = { prereleaseTemplate: null, }, bumpVersionsWithWorkspaceProtocolOnly: false, -} as const; +}; let correctCases: Record = { defaults: { @@ -640,4 +645,11 @@ The \`snapshot.useCalculatedVersion\` option is set as \\"not true\\" when the o The \`useCalculatedVersionForSnapshots\` option is set as \\"not true\\" when the only valid values are undefined or a boolean" `); }); + + test("privatePackages false disables versioning and tagging", () => { + expect(unsafeParse({ privatePackages: false }, defaultPackages)).toEqual({ + ...defaults, + privatePackages: { version: false, tag: false }, + }); + }); }); diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index 08cca2cb7..0f2468394 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -462,7 +462,28 @@ export let parse = (json: WrittenConfig, packages: Packages): Config => { json.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH ?.updateInternalDependents ?? "out-of-range", }, + + // TODO consider enabling this by default in the next major version + privatePackages: + json.privatePackages === false + ? { tag: false, version: false } + : json.privatePackages + ? { + version: json.privatePackages.version ?? true, + tag: json.privatePackages.tag ?? false, + } + : { version: true, tag: false }, }; + + if ( + config.privatePackages.version === false && + config.privatePackages.tag === true + ) { + throw new ValidationError( + `The \`privatePackages.tag\` option is set to \`true\` but \`privatePackages.version\` is set to \`false\`. This is not allowed.` + ); + } + return config; }; diff --git a/packages/git/src/index.test.ts b/packages/git/src/index.test.ts index 09ac3ea39..30049ae89 100644 --- a/packages/git/src/index.test.ts +++ b/packages/git/src/index.test.ts @@ -13,6 +13,7 @@ import { getChangedPackagesSinceRef, getChangedChangesetFilesSinceRef, getAllTags, + tagExists, } from "./"; const f = fixtures(__dirname); @@ -226,6 +227,23 @@ describe("git", () => { }); }); + describe("tagExists", () => { + it("returns false when no tag exists", async () => { + await add("packages/pkg-a/package.json", cwd); + await commit("added packageA package.json", cwd); + + expect(await tagExists("tag_which_doesn't_exist", cwd)).toBe(false); + }); + + it("returns true when tag exists", async () => { + await add("packages/pkg-a/package.json", cwd); + await commit("added packageA package.json", cwd); + await tag("tag_message", cwd); + + expect(await tagExists("tag_message", cwd)).toBe(true); + }); + }); + describe("getCommitsThatAddFiles", () => { it("should commit a file and get the hash of that commit", async () => { await add("packages/pkg-b/package.json", cwd); diff --git a/packages/git/src/index.ts b/packages/git/src/index.ts index 875f61b04..6f9eb0e7a 100644 --- a/packages/git/src/index.ts +++ b/packages/git/src/index.ts @@ -281,6 +281,13 @@ export async function getChangedPackagesSinceRef({ ); } +export async function tagExists(tagStr: string, cwd: string) { + const gitCmd = await spawn("git", ["tag", "-l", tagStr], { cwd }); + const output = gitCmd.stdout.toString().trim(); + const tagExists = !!output; + return tagExists; +} + export async function getCurrentCommitId({ cwd, }: { @@ -290,3 +297,16 @@ export async function getCurrentCommitId({ .toString() .trim(); } + +export async function remoteTagExists(tagStr: string) { + const gitCmd = await spawn("git", [ + "ls-remote", + "--tags", + "origin", + "-l", + tagStr, + ]); + const output = gitCmd.stdout.toString().trim(); + const tagExists = !!output; + return tagExists; +} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 3a48b8224..13fdad1a0 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -61,6 +61,11 @@ export type PackageGroup = ReadonlyArray; export type Fixed = ReadonlyArray; export type Linked = ReadonlyArray; +export interface PrivatePackages { + version: boolean; + tag: boolean; +} + export type Config = { changelog: false | readonly [string, any]; commit: false | readonly [string, any]; @@ -68,6 +73,8 @@ export type Config = { linked: Linked; access: AccessType; baseBranch: string; + /** Features enabled for Private packages */ + privatePackages: PrivatePackages; /** The minimum bump type to trigger automatic update of internal dependencies that are part of the same release */ updateInternalDependencies: "patch" | "minor"; ignore: ReadonlyArray; @@ -90,6 +97,13 @@ export type WrittenConfig = { linked?: Linked; access?: AccessType; baseBranch?: string; + /** Opt in to tracking non-npm / private packages */ + privatePackages?: + | false + | { + version?: boolean; + tag?: boolean; + }; /** The minimum bump type to trigger automatic update of internal dependencies that are part of the same release */ updateInternalDependencies?: "patch" | "minor"; ignore?: ReadonlyArray;