diff --git a/.changeset/olive-ducks-camp.md b/.changeset/olive-ducks-camp.md new file mode 100644 index 000000000..8ce622e61 --- /dev/null +++ b/.changeset/olive-ducks-camp.md @@ -0,0 +1,28 @@ +--- +"@changesets/cli": patch +"@changesets/config": patch +--- + +A possibility to use the calculated version for snapshot releases is now stable 🥳 All snapshot-related config parameters are now grouped under a single config property called `snapshot`. + +To migrate, make sure to update your `config.json`. + +Old usage (still works, but comes with a deprecated warning): + +```json +{ + "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { + "useCalculatedVersionForSnapshots": true + } +} +``` + +New usage: + +```json +{ + "snapshot": { + "useCalculatedVersion": true + } +} +``` diff --git a/.changeset/quick-squids-dance.md b/.changeset/quick-squids-dance.md new file mode 100644 index 000000000..467f4fc03 --- /dev/null +++ b/.changeset/quick-squids-dance.md @@ -0,0 +1,5 @@ +--- +"@changesets/cli": minor +--- + +Added a new config flag for `changesets version --snapshot` mode: `--snapshot-prerelease-template` diff --git a/.changeset/short-cats-invent.md b/.changeset/short-cats-invent.md new file mode 100644 index 000000000..2f10ba463 --- /dev/null +++ b/.changeset/short-cats-invent.md @@ -0,0 +1,5 @@ +--- +"@changesets/git": minor +--- + +Added a new helper function: `getCurrentCommitId` diff --git a/.changeset/strong-geckos-divide.md b/.changeset/strong-geckos-divide.md new file mode 100644 index 000000000..f3235810e --- /dev/null +++ b/.changeset/strong-geckos-divide.md @@ -0,0 +1,8 @@ +--- +"@changesets/assemble-release-plan": minor +"@changesets/cli": minor +"@changesets/config": minor +"@changesets/types": minor +--- + +Added a new config option: `snapshot.prereleaseTemplate` for customizing the way snapshot release numbers are being composed. diff --git a/.vscode/settings.json b/.vscode/settings.json index 55712c19f..22012885b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,9 @@ { - "typescript.tsdk": "node_modules/typescript/lib" -} \ No newline at end of file + "typescript.tsdk": "node_modules/typescript/lib", + "grammarly.selectors": [ + { + "language": "markdown", + "scheme": "file" + } + ] +} diff --git a/README.md b/README.md index ad8139c75..648d46b4a 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) +- [Experimental Options](./docs/experimental-options.md) ## Cool Projects already using Changesets for versioning and changelogs diff --git a/docs/config-file-options.md b/docs/config-file-options.md index f31f87698..75c893563 100644 --- a/docs/config-file-options.md +++ b/docs/config-file-options.md @@ -14,7 +14,7 @@ Changesets has a minimal amount of configuration options. Mostly these are for w } ``` -> NOTE: the `linked`, `updateInternalDependencies`, and `ignore` options are only for behaviour in monorepos. +> NOTE: the `linked`, `fixed`, `updateInternalDependencies`, `bumpVersionsWithWorkspaceProtocolOnly`, and `ignore` options are only for behaviour in monorepos. ## `commit` (`boolean`, or module path as a `string`, or a tuple like `[modulePath: string, options: any]`) @@ -156,3 +156,40 @@ You would specify our github changelog generator with: ``` For more details on these functions and information on how to write your own see [changelog-functions](./modifying-changelog-format.md) + +## `bumpVersionsWithWorkspaceProtocolOnly` (boolean) + +Determines whether Changesets should only bump dependency ranges that use workspace protocol of packages that are part of the workspace. + +## `snapshot` (object or undefined) + +Default value: `undefined` + +### `useCalculatedVersion` (optional boolean) + +Default value: `false` + +When `changesets version --snapshot` is used, the default behavior is to use `0.0.0` as the base version for the snapshot release. + +Setting `useCalculatedVersion: true` will change the default behavior and will use the calculated version, based on the changeset files. + +### `prereleaseTemplate` (optional string) + +Default value: `undefined` (see note below) + +Configures the suffix for the snapshot releases, using a template with placeholders. + +**Available placeholders:** + +You can use the following placeholders for customizing the snapshot release version: + +- `{tag}` - the name of the snapshot tag, as specified in `--snapshot something` +- `{commit}` - the Git commit ID +- `{timestamp}` - Unix timestamp of the time of the release +- `{datetime}` - date and time of the release (14 characters, for example, `20211213000730`) + +> Note: if you are using `--snapshot` with empty tag name, you cannot use `{tag}` as placeholder - this will result in error. + +**Default behavior** + +If you are not specifying `prereleaseTemplate`, the default behavior will fall back to using the following template: `{tag}-{datetime}`, and in cases where the tag is empty (`--snapshot` with no tag name), it will use `{datetime}` only. diff --git a/docs/experimental-options.md b/docs/experimental-options.md new file mode 100644 index 000000000..9eb163949 --- /dev/null +++ b/docs/experimental-options.md @@ -0,0 +1,17 @@ +# Experimental Options + +All experimental options are configured in `config.json` under `___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH` flag. + +> Please use these experimental flags with caution, and please pay attention to release notes - these config flags might change in patch versions. + +## `updateInternalDependents` (type: `'out-of-range' | 'always'`) + +Default value: `out-of-range`. + +The config flag can be used to add dependent packages to the release (if they are not already a part of it) with patch bumps. + +## `onlyUpdatePeerDependentsWhenOutOfRange` (type: `boolean`) + +Default value: `false` + +When set to `true`, Changesets will only bump peer dependents when `peerDependencies` are leaving the range. diff --git a/packages/apply-release-plan/src/index.test.ts b/packages/apply-release-plan/src/index.test.ts index af36aa2d1..0fe9b3256 100644 --- a/packages/apply-release-plan/src/index.test.ts +++ b/packages/apply-release-plan/src/index.test.ts @@ -51,8 +51,11 @@ class FakeReleasePlan { ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, - updateInternalDependents: "out-of-range", - useCalculatedVersionForSnapshots: false + updateInternalDependents: "out-of-range" + }, + snapshot: { + useCalculatedVersion: false, + prereleaseTemplate: null }, ...config }; @@ -87,10 +90,13 @@ async function testSetup( baseBranch: "main", updateInternalDependencies: "patch", ignore: [], + snapshot: { + useCalculatedVersion: false, + prereleaseTemplate: null + }, ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, - updateInternalDependents: "out-of-range", - useCalculatedVersionForSnapshots: false + updateInternalDependents: "out-of-range" } }; } @@ -490,8 +496,11 @@ describe("apply release plan", () => { ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, - updateInternalDependents: "out-of-range", - useCalculatedVersionForSnapshots: false + updateInternalDependents: "out-of-range" + }, + snapshot: { + useCalculatedVersion: false, + prereleaseTemplate: null } } ); @@ -553,8 +562,11 @@ describe("apply release plan", () => { ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, - updateInternalDependents: "out-of-range", - useCalculatedVersionForSnapshots: false + updateInternalDependents: "out-of-range" + }, + snapshot: { + useCalculatedVersion: false, + prereleaseTemplate: null } } ); @@ -743,8 +755,11 @@ describe("apply release plan", () => { ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, - updateInternalDependents: "out-of-range", - useCalculatedVersionForSnapshots: false + updateInternalDependents: "out-of-range" + }, + snapshot: { + useCalculatedVersion: false, + prereleaseTemplate: null } } ); @@ -828,8 +843,11 @@ describe("apply release plan", () => { ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, - updateInternalDependents: "out-of-range", - useCalculatedVersionForSnapshots: false + updateInternalDependents: "out-of-range" + }, + snapshot: { + useCalculatedVersion: false, + prereleaseTemplate: null } } ); @@ -905,8 +923,11 @@ describe("apply release plan", () => { ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, - updateInternalDependents: "out-of-range", - useCalculatedVersionForSnapshots: false + updateInternalDependents: "out-of-range" + }, + snapshot: { + useCalculatedVersion: false, + prereleaseTemplate: null } } ); @@ -982,8 +1003,11 @@ describe("apply release plan", () => { ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, - updateInternalDependents: "out-of-range", - useCalculatedVersionForSnapshots: false + updateInternalDependents: "out-of-range" + }, + snapshot: { + useCalculatedVersion: false, + prereleaseTemplate: null } } ); @@ -1062,8 +1086,11 @@ describe("apply release plan", () => { ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, - updateInternalDependents: "out-of-range", - useCalculatedVersionForSnapshots: false + updateInternalDependents: "out-of-range" + }, + snapshot: { + useCalculatedVersion: false, + prereleaseTemplate: null } } ); @@ -1147,8 +1174,11 @@ describe("apply release plan", () => { ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, - updateInternalDependents: "out-of-range", - useCalculatedVersionForSnapshots: false + updateInternalDependents: "out-of-range" + }, + snapshot: { + useCalculatedVersion: false, + prereleaseTemplate: null } } ); @@ -1224,8 +1254,11 @@ describe("apply release plan", () => { ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, - updateInternalDependents: "out-of-range", - useCalculatedVersionForSnapshots: false + updateInternalDependents: "out-of-range" + }, + snapshot: { + useCalculatedVersion: false, + prereleaseTemplate: null } } ); @@ -1301,8 +1334,11 @@ describe("apply release plan", () => { ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, - updateInternalDependents: "out-of-range", - useCalculatedVersionForSnapshots: false + updateInternalDependents: "out-of-range" + }, + snapshot: { + useCalculatedVersion: false, + prereleaseTemplate: null } } ); @@ -1382,8 +1418,11 @@ describe("apply release plan", () => { ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: true, - updateInternalDependents: "out-of-range", - useCalculatedVersionForSnapshots: false + updateInternalDependents: "out-of-range" + }, + snapshot: { + useCalculatedVersion: false, + prereleaseTemplate: null } } ); @@ -1544,8 +1583,11 @@ describe("apply release plan", () => { ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, - updateInternalDependents: "out-of-range", - useCalculatedVersionForSnapshots: false + updateInternalDependents: "out-of-range" + }, + snapshot: { + useCalculatedVersion: false, + prereleaseTemplate: null } } ); @@ -1649,8 +1691,11 @@ describe("apply release plan", () => { ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, - updateInternalDependents: "out-of-range", - useCalculatedVersionForSnapshots: false + updateInternalDependents: "out-of-range" + }, + snapshot: { + useCalculatedVersion: false, + prereleaseTemplate: null } } ); @@ -1734,8 +1779,11 @@ describe("apply release plan", () => { ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, - updateInternalDependents: "out-of-range", - useCalculatedVersionForSnapshots: false + updateInternalDependents: "out-of-range" + }, + snapshot: { + useCalculatedVersion: false, + prereleaseTemplate: null } } ); @@ -1823,8 +1871,11 @@ describe("apply release plan", () => { ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, - updateInternalDependents: "out-of-range", - useCalculatedVersionForSnapshots: false + updateInternalDependents: "out-of-range" + }, + snapshot: { + useCalculatedVersion: false, + prereleaseTemplate: null } } ); @@ -1926,8 +1977,11 @@ describe("apply release plan", () => { ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, - updateInternalDependents: "out-of-range", - useCalculatedVersionForSnapshots: false + updateInternalDependents: "out-of-range" + }, + snapshot: { + useCalculatedVersion: false, + prereleaseTemplate: null } } ); diff --git a/packages/assemble-release-plan/src/index.test.ts b/packages/assemble-release-plan/src/index.test.ts index 6b6cbc5f1..d7c958601 100644 --- a/packages/assemble-release-plan/src/index.test.ts +++ b/packages/assemble-release-plan/src/index.test.ts @@ -37,7 +37,9 @@ describe("assemble-release-plan", () => { setup.packages, defaultConfig, undefined, - true + { + tag: undefined + } ); expect(releases.length).toBe(1); @@ -50,7 +52,9 @@ describe("assemble-release-plan", () => { setup.packages, defaultConfig, undefined, - "foo" + { + tag: "foo" + } ); expect(releases.length).toBe(1); diff --git a/packages/assemble-release-plan/src/index.ts b/packages/assemble-release-plan/src/index.ts index f302369c2..3c9ef782a 100644 --- a/packages/assemble-release-plan/src/index.ts +++ b/packages/assemble-release-plan/src/index.ts @@ -16,6 +16,11 @@ import { Packages, Package } from "@manypkg/get-packages"; import { getDependentsGraph } from "@changesets/get-dependents-graph"; import { PreInfo, InternalRelease } from "./types"; +type SnapshotReleaseParameters = { + tag?: string | undefined; + commit?: string | undefined; +}; + function getPreVersion(version: string) { let parsed = semver.parse(version)!; let preVersion = @@ -27,31 +32,64 @@ function getPreVersion(version: string) { return preVersion; } -function getSnapshotSuffix(snapshot?: string | boolean): string | undefined { - if (snapshot === undefined) { - return; +function getSnapshotSuffix( + template: Config["snapshot"]["prereleaseTemplate"], + snapshotParameters: SnapshotReleaseParameters +): string { + let snapshotRefDate = new Date(); + + const placeholderValues = { + commit: snapshotParameters.commit, + tag: snapshotParameters.tag, + timestamp: snapshotRefDate.getTime().toString(), + datetime: snapshotRefDate + .toISOString() + .replace(/\.\d{3}Z$/, "") + .replace(/[^\d]/g, "") + }; + + // We need a special handling because we need to handle a case where `--snapshot` is used without any template, + // and the resulting version needs to be composed without a tag. + if (!template) { + return [placeholderValues.tag, placeholderValues.datetime] + .filter(Boolean) + .join("-"); } - let dateAndTime = new Date() - .toISOString() - .replace(/\.\d{3}Z$/, "") - .replace(/[^\d]/g, ""); - let tag = ""; + const placeholders = Object.keys(placeholderValues) as Array< + keyof typeof placeholderValues + >; - if (typeof snapshot === "string") tag = `-${snapshot}`; + if (!template.includes(`{tag}`) && placeholderValues.tag !== undefined) { + throw new Error( + `Failed to compose snapshot version: "{tag}" placeholder is missing, but the snapshot parameter is defined (value: '${placeholderValues.tag}')` + ); + } + + return placeholders.reduce((prev, key) => { + return prev.replace(new RegExp(`\\{${key}\\}`, "g"), () => { + const value = placeholderValues[key]; + if (value === undefined) { + throw new Error( + `Failed to compose snapshot version: "{${key}}" placeholder is used without having a value defined!` + ); + } - return `${tag}-${dateAndTime}`; + return value; + }); + }, template); } -function getNewVersion( +function getSnapshotVersion( release: InternalRelease, preInfo: PreInfo | undefined, - snapshotSuffix: string | undefined, - useCalculatedVersionForSnapshots: boolean + useCalculatedVersion: boolean, + snapshotSuffix: string ): string { if (release.type === "none") { return release.oldVersion; } + /** * Using version as 0.0.0 so that it does not hinder with other version release * For example; @@ -59,19 +97,24 @@ function getNewVersion( * and a consumer is using the range ^1.0.0-beta, most people would expect that range to resolve to 1.0.0-beta.0 * but it'll actually resolve to 1.0.0-canary-hash. Using 0.0.0 solves this problem because it won't conflict with other versions. * - * You can set `useCalculatedVersionForSnapshots` flag to true to use calculated versions if you don't care about the above problem. + * You can set `snapshot.useCalculatedVersion` flag to true to use calculated versions if you don't care about the above problem. */ - if (snapshotSuffix && !useCalculatedVersionForSnapshots) { - return `0.0.0${snapshotSuffix}`; - } + const baseVersion = useCalculatedVersion + ? incrementVersion(release, preInfo) + : `0.0.0`; - const calculatedVersion = incrementVersion(release, preInfo); + return `${baseVersion}-${snapshotSuffix}`; +} - if (snapshotSuffix && useCalculatedVersionForSnapshots) { - return `${calculatedVersion}${snapshotSuffix}`; +function getNewVersion( + release: InternalRelease, + preInfo: PreInfo | undefined +): string { + if (release.type === "none") { + return release.oldVersion; } - return calculatedVersion; + return incrementVersion(release, preInfo); } function assembleReleasePlan( @@ -80,7 +123,10 @@ function assembleReleasePlan( config: Config, // intentionally not using an optional parameter here so the result of `readPreState` has to be passed in here preState: PreState | undefined, - snapshot?: string | boolean + // snapshot: undefined -> not using snaphot + // snapshot: { tag: undefined } -> --snapshot (empty tag) + // snapshot: { tag: "canary" } -> --snapshot canary + snapshot?: SnapshotReleaseParameters ): ReleasePlan { let packagesByName = new Map( packages.packages.map(x => [x.packageJson.name, x]) @@ -94,9 +140,6 @@ function assembleReleasePlan( const preInfo = getPreInfo(changesets, packagesByName, config, preState); - // Caching the snapshot version here and use this if it is snapshot release - const snapshotSuffix = getSnapshotSuffix(snapshot); - // releases is, at this point a list of all packages we are going to releases, // flattened down to one release per package, having a reference back to their // changesets, and with a calculated new versions @@ -158,18 +201,23 @@ function assembleReleasePlan( } } + // Caching the snapshot version here and use this if it is snapshot release + const snapshotSuffix = + snapshot && getSnapshotSuffix(config.snapshot.prereleaseTemplate, snapshot); + return { changesets: relevantChangesets, releases: [...releases.values()].map(incompleteRelease => { return { ...incompleteRelease, - newVersion: getNewVersion( - incompleteRelease, - preInfo, - snapshotSuffix, - config.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH - .useCalculatedVersionForSnapshots - ) + newVersion: snapshotSuffix + ? getSnapshotVersion( + incompleteRelease, + preInfo, + config.snapshot.useCalculatedVersion, + snapshotSuffix + ) + : getNewVersion(incompleteRelease, preInfo) }; }), preState: preInfo?.state diff --git a/packages/cli/src/commands/version/index.ts b/packages/cli/src/commands/version/index.ts index 308435a3b..3dff717d4 100644 --- a/packages/cli/src/commands/version/index.ts +++ b/packages/cli/src/commands/version/index.ts @@ -12,6 +12,7 @@ import { removeEmptyFolders } from "../../utils/v1-legacy/removeFolders"; import { readPreState } from "@changesets/pre"; import { ExitError } from "@changesets/errors"; import { getCommitFunctions } from "../../commit/getCommitFunctions"; +import { getCurrentCommitId } from "@changesets/git"; let importantSeparator = chalk.red( "===============================IMPORTANT!===============================" @@ -71,6 +72,13 @@ export default async function version( releaseConfig, preState, options.snapshot + ? { + tag: options.snapshot === true ? undefined : options.snapshot, + commit: config.snapshot.prereleaseTemplate?.includes("{commit}") + ? await getCurrentCommitId({ cwd }) + : undefined + } + : undefined ); let [...touchedFiles] = await applyReleasePlan( diff --git a/packages/cli/src/commands/version/version.test.ts b/packages/cli/src/commands/version/version.test.ts index cf58af328..e4fe9e0ec 100644 --- a/packages/cli/src/commands/version/version.test.ts +++ b/packages/cli/src/commands/version/version.test.ts @@ -13,6 +13,37 @@ import pre from "../pre"; import version from "./index"; import humanId from "human-id"; +function mockGlobalDate< + Args extends any[], + Return extends Promise | void +>( + testFn: (...args: Args) => Return, + fixedDate: string = "2021-12-13T00:07:30.879Z" +) { + return async (...args: Args) => { + const originalDate = Date; + const MockedDate = class MockedDate extends Date { + constructor() { + super(fixedDate); + } + + static now() { + return new MockedDate().getTime(); + } + } as typeof Date; + + // eslint-disable-next-line no-global-assign + Date = MockedDate; + + try { + await testFn(...args); + } finally { + // eslint-disable-next-line no-global-assign + Date = originalDate; + } + }; +} + const f = fixtures(__dirname); let changelogPath = path.resolve(__dirname, "../../changelog"); @@ -43,6 +74,9 @@ git.commit.mockImplementation(() => Promise.resolve(true)); git.getCommitsThatAddFiles.mockImplementation(changesetIds => Promise.resolve(changesetIds.map(() => "g1th4sh")) ); +// @ts-ignore +git.getCurrentCommitId.mockImplementation(() => Promise.resolve("abcdef")); + // @ts-ignore git.tag.mockImplementation(() => Promise.resolve(true)); @@ -713,15 +747,9 @@ describe("snapshot release", () => { `); }); - it("should not bump version of an ignored package when its dependency gets updated", async () => { - const originalDate = Date; - // eslint-disable-next-line no-global-assign - Date = class Date { - toISOString() { - return "2021-12-13T00:07:30.879Z"; - } - } as any; - try { + it( + "should not bump version of an ignored package when its dependency gets updated", + mockGlobalDate(async () => { const cwd = await f.copy("simple-project"); await writeChangeset( { @@ -758,13 +786,113 @@ describe("snapshot release", () => { }, ] `); - } finally { - // eslint-disable-next-line no-global-assign - Date = originalDate; - } + }) + ); + + describe("snapshotPrereleaseTemplate", () => { + it('should throw an error when "{tag}" and empty snapshot is used', async () => { + let cwd = f.copy("simple-project"); + await writeChangesets([simpleChangeset2], cwd); + jest.spyOn(fs, "writeFile"); + + expect( + version( + cwd, + { snapshot: true }, + { + ...modifiedDefaultConfig, + commit: false, + snapshot: { + ...modifiedDefaultConfig.snapshot, + prereleaseTemplate: `{tag}.{commit}` + } + } + ) + ).rejects.toThrow( + 'Failed to compose snapshot version: "{tag}" placeholder is used without having a value defined!' + ); + }); + + it('should throw an error when "{tag}" is set and named snapshot is used', async () => { + let cwd = f.copy("simple-project"); + await writeChangesets([simpleChangeset2], cwd); + jest.spyOn(fs, "writeFile"); + + expect( + version( + cwd, + { snapshot: "test" }, + { + ...modifiedDefaultConfig, + commit: false, + snapshot: { + ...modifiedDefaultConfig.snapshot, + prereleaseTemplate: `{commit}` + } + } + ) + ).rejects.toThrow( + "Failed to compose snapshot version: \"{tag}\" placeholder is missing, but the snapshot parameter is defined (value: 'test')" + ); + }); + + it.each<[string | null | undefined, string | true, string]>([ + // Template-based + ["{tag}", "test", "0.0.0-test"], + ["{tag}-{tag}", "test", "0.0.0-test-test"], + ["{commit}", true, "0.0.0-abcdef"], + ["{timestamp}", true, "0.0.0-1639354050879"], + ["{datetime}", true, "0.0.0-20211213000730"], + // Mixing template and static string + [ + "{tag}.{timestamp}.{commit}", + "alpha", + "0.0.0-alpha.1639354050879.abcdef" + ], + ["{datetime}-{tag}", "alpha", "0.0.0-20211213000730-alpha"], + // Legacy support + ["", "test", "0.0.0-test-20211213000730"], + [undefined, "canary", "0.0.0-canary-20211213000730"], + [null, "alpha", "0.0.0-alpha-20211213000730"] + ])( + "should customize release correctly based on snapshotPrereleaseTemplate template: %p (tag: '%p')", + mockGlobalDate( + async (snapshotTemplate, snapshotValue, expectedResult) => { + let cwd = f.copy("simple-project"); + await writeChangesets([simpleChangeset2], cwd); + const spy = jest.spyOn(fs, "writeFile"); + await version( + cwd, + { snapshot: snapshotValue }, + { + ...modifiedDefaultConfig, + commit: false, + snapshot: { + ...modifiedDefaultConfig.snapshot, + prereleaseTemplate: snapshotTemplate as string + } + } + ); + + expect(getPkgJSON("pkg-a", spy.mock.calls)).toEqual( + expect.objectContaining({ + name: "pkg-a", + version: expectedResult + }) + ); + + expect(getPkgJSON("pkg-b", spy.mock.calls)).toEqual( + expect.objectContaining({ + name: "pkg-b", + version: expectedResult + }) + ); + } + ) + ); }); - describe("useCalculatedVersionForSnapshots: true", () => { + describe("snapshot.useCalculatedVersion: true", () => { it("should update packages using calculated version", async () => { let cwd = f.copy("simple-project"); await writeChangesets([simpleChangeset2], cwd); @@ -777,9 +905,9 @@ describe("snapshot release", () => { { ...modifiedDefaultConfig, commit: false, - ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { - ...modifiedDefaultConfig.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH, - useCalculatedVersionForSnapshots: true + snapshot: { + useCalculatedVersion: true, + prereleaseTemplate: null } } ); @@ -815,9 +943,9 @@ describe("snapshot release", () => { }, { ...modifiedDefaultConfig, - ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { - ...modifiedDefaultConfig.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH, - useCalculatedVersionForSnapshots: true + snapshot: { + useCalculatedVersion: true, + prereleaseTemplate: null } } ); @@ -840,15 +968,9 @@ describe("snapshot release", () => { `); }); - it("should not bump version of an ignored package when its dependency gets updated", async () => { - const originalDate = Date; - // eslint-disable-next-line no-global-assign - Date = class Date { - toISOString() { - return "2021-12-13T00:07:30.879Z"; - } - } as any; - try { + it( + "should not bump version of an ignored package when its dependency gets updated", + mockGlobalDate(async () => { const cwd = await f.copy("simple-project"); await writeChangeset( { @@ -866,9 +988,9 @@ describe("snapshot release", () => { { ...modifiedDefaultConfig, ignore: ["pkg-a"], - ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { - ...modifiedDefaultConfig.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH, - useCalculatedVersionForSnapshots: true + snapshot: { + useCalculatedVersion: true, + prereleaseTemplate: null } } ); @@ -889,11 +1011,8 @@ describe("snapshot release", () => { }, ] `); - } finally { - // eslint-disable-next-line no-global-assign - Date = originalDate; - } - }); + }) + ); }); }); diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index f11778b3c..3aebf966b 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -11,7 +11,7 @@ const { input, flags } = meow( Commands init add [--empty] [--open] - version [--ignore] [--snapshot ] + version [--ignore] [--snapshot ] [--snapshot-prerelease-template