diff --git a/.changeset/pink-eagles-agree.md b/.changeset/pink-eagles-agree.md new file mode 100644 index 000000000..0a655de3b --- /dev/null +++ b/.changeset/pink-eagles-agree.md @@ -0,0 +1,7 @@ +--- +"@changesets/assemble-release-plan": minor +"@changesets/config": minor +"@changesets/types": minor +--- + +Adds two options to configure snapshot preid format: which character separates timestamp part from the rest of the preid, and whether the timestamp part comes first in the preid. diff --git a/docs/config-file-options.md b/docs/config-file-options.md index f31f87698..f92f8c34c 100644 --- a/docs/config-file-options.md +++ b/docs/config-file-options.md @@ -156,3 +156,11 @@ 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) + +## `snapshotTimestampSeparator` (`'-'` or `'.'`) + +This option sets which character is used for separating timestamp part from the rest of the preid when doing [snapshot releases](./snapshot-releases.md), i.e. `0.0.0-bulbasaur-THE_TIME_YOU_DID_THIS` `vs 0.0.0-bulbasaur.THE_TIME_YOU_DID_THIS`. The default is `-`. + +## `snapshotTimestampPosition` (`start` or `end`) + +This option sets whether timestamp part comes at the start or at the end of the preid when doing [snapshot releases](./snapshot-releases.md), i.e. `0.0.0-bulbasaur-THE_TIME_YOU_DID_THIS` `vs 0.0.0-THE_TIME_YOU_DID_THIS-bulbasaur`. The default is `end`. diff --git a/packages/apply-release-plan/src/index.test.ts b/packages/apply-release-plan/src/index.test.ts index 27572fd82..76435fb53 100644 --- a/packages/apply-release-plan/src/index.test.ts +++ b/packages/apply-release-plan/src/index.test.ts @@ -48,6 +48,8 @@ class FakeReleasePlan { access: "restricted", baseBranch: "main", updateInternalDependencies: "patch", + snapshotTimestampSeparator: "-", + snapshotTimestampPosition: "end", ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, @@ -85,6 +87,8 @@ async function testSetup( access: "restricted", baseBranch: "main", updateInternalDependencies: "patch", + snapshotTimestampSeparator: "-", + snapshotTimestampPosition: "end", ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, @@ -485,6 +489,8 @@ describe("apply release plan", () => { access: "restricted", baseBranch: "main", updateInternalDependencies: "patch", + snapshotTimestampSeparator: "-", + snapshotTimestampPosition: "end", ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, @@ -548,6 +554,8 @@ describe("apply release plan", () => { access: "restricted", baseBranch: "main", updateInternalDependencies: "patch", + snapshotTimestampSeparator: "-", + snapshotTimestampPosition: "end", ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, @@ -697,6 +705,8 @@ describe("apply release plan", () => { access: "restricted", baseBranch: "main", updateInternalDependencies, + snapshotTimestampSeparator: "-", + snapshotTimestampPosition: "end", ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, @@ -782,6 +792,8 @@ describe("apply release plan", () => { access: "restricted", baseBranch: "main", updateInternalDependencies, + snapshotTimestampSeparator: "-", + snapshotTimestampPosition: "end", ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, @@ -859,6 +871,8 @@ describe("apply release plan", () => { access: "restricted", baseBranch: "main", updateInternalDependencies, + snapshotTimestampSeparator: "-", + snapshotTimestampPosition: "end", ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, @@ -936,6 +950,8 @@ describe("apply release plan", () => { access: "restricted", baseBranch: "main", updateInternalDependencies, + snapshotTimestampSeparator: "-", + snapshotTimestampPosition: "end", ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, @@ -1016,6 +1032,8 @@ describe("apply release plan", () => { access: "restricted", baseBranch: "main", updateInternalDependencies, + snapshotTimestampSeparator: "-", + snapshotTimestampPosition: "end", ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, @@ -1101,6 +1119,8 @@ describe("apply release plan", () => { access: "restricted", baseBranch: "main", updateInternalDependencies, + snapshotTimestampSeparator: "-", + snapshotTimestampPosition: "end", ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, @@ -1178,6 +1198,8 @@ describe("apply release plan", () => { access: "restricted", baseBranch: "main", updateInternalDependencies, + snapshotTimestampSeparator: "-", + snapshotTimestampPosition: "end", ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, @@ -1255,6 +1277,8 @@ describe("apply release plan", () => { access: "restricted", baseBranch: "main", updateInternalDependencies, + snapshotTimestampSeparator: "-", + snapshotTimestampPosition: "end", ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, @@ -1336,6 +1360,8 @@ describe("apply release plan", () => { access: "restricted", baseBranch: "main", updateInternalDependencies: "patch", + snapshotTimestampSeparator: "-", + snapshotTimestampPosition: "end", ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: true, @@ -1498,6 +1524,8 @@ describe("apply release plan", () => { null ], updateInternalDependencies: "patch", + snapshotTimestampSeparator: "-", + snapshotTimestampPosition: "end", ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, @@ -1603,6 +1631,8 @@ describe("apply release plan", () => { access: "restricted", baseBranch: "main", updateInternalDependencies: "patch", + snapshotTimestampSeparator: "-", + snapshotTimestampPosition: "end", ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, @@ -1688,6 +1718,8 @@ describe("apply release plan", () => { access: "restricted", baseBranch: "main", updateInternalDependencies: "minor", + snapshotTimestampSeparator: "-", + snapshotTimestampPosition: "end", ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, @@ -1777,6 +1809,8 @@ describe("apply release plan", () => { access: "restricted", baseBranch: "main", updateInternalDependencies: "minor", + snapshotTimestampSeparator: "-", + snapshotTimestampPosition: "end", ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, @@ -1880,6 +1914,8 @@ describe("apply release plan", () => { access: "restricted", baseBranch: "main", updateInternalDependencies: "minor", + snapshotTimestampSeparator: "-", + snapshotTimestampPosition: "end", ignore: [], ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, diff --git a/packages/assemble-release-plan/src/index.test.ts b/packages/assemble-release-plan/src/index.test.ts index 6b6cbc5f1..1a7fb52d7 100644 --- a/packages/assemble-release-plan/src/index.test.ts +++ b/packages/assemble-release-plan/src/index.test.ts @@ -31,31 +31,109 @@ describe("assemble-release-plan", () => { }); }); - it("should assemble release plan for basic setup with snapshot", () => { - let { releases } = assembleReleasePlan( - setup.changesets, - setup.packages, - defaultConfig, - undefined, - true - ); + test.each([ + { + snapshot: true, + snapshotTimestampSeparator: undefined, + snapshotTimestampPosition: undefined, + resultRegexp: /0\.0\.0-\d{14}/ + }, + { + snapshot: "foo", + snapshotTimestampSeparator: undefined, + snapshotTimestampPosition: undefined, + resultRegexp: /0\.0\.0-foo-\d{14}/ + }, + { + snapshot: "foo", + snapshotTimestampSeparator: "." as const, + snapshotTimestampPosition: undefined, + resultRegexp: /0\.0\.0-foo\.\d{14}/ + }, + { + snapshot: "foo", + snapshotTimestampSeparator: "-" as const, + snapshotTimestampPosition: undefined, + resultRegexp: /0\.0\.0-foo-\d{14}/ + }, + { + snapshot: "foo", + snapshotTimestampSeparator: undefined, + snapshotTimestampPosition: "start" as const, + resultRegexp: /0\.0\.0-\d{14}-foo/ + }, + { + snapshot: "foo", + snapshotTimestampSeparator: undefined, + snapshotTimestampPosition: "end" as const, + resultRegexp: /0\.0\.0-foo-\d{14}/ + }, + { + snapshot: "foo", + snapshotTimestampSeparator: "-" as const, + snapshotTimestampPosition: "start" as const, + resultRegexp: /0\.0\.0-\d{14}-foo/ + }, + { + snapshot: "foo", + snapshotTimestampSeparator: "-" as const, + snapshotTimestampPosition: "end" as const, + resultRegexp: /0\.0\.0-foo-\d{14}/ + }, + { + snapshot: "foo", + snapshotTimestampSeparator: "." as const, + snapshotTimestampPosition: "start" as const, + resultRegexp: /0\.0\.0-\d{14}\.foo/ + }, + { + snapshot: "foo", + snapshotTimestampSeparator: "." as const, + snapshotTimestampPosition: "end" as const, + resultRegexp: /0\.0\.0-foo\.\d{14}/ + }, + { + snapshot: true, + snapshotTimestampSeparator: undefined, + snapshotTimestampPosition: "start" as const, + resultRegexp: /0\.0\.0-\d{14}/ + }, + { + snapshot: true, + snapshotTimestampSeparator: undefined, + snapshotTimestampPosition: "end" as const, + resultRegexp: /0\.0\.0-\d{14}/ + } + ])( + "should assemble release plan for basic setup with %j", + ({ + snapshot, + snapshotTimestampSeparator, + snapshotTimestampPosition, + resultRegexp + }) => { + let config = { ...defaultConfig }; + + if (snapshotTimestampSeparator) { + config.snapshotTimestampSeparator = snapshotTimestampSeparator; + } + + if (snapshotTimestampPosition) { + config.snapshotTimestampPosition = snapshotTimestampPosition; + } - expect(releases.length).toBe(1); - expect(/0\.0\.0-\d{14}/.test(releases[0].newVersion)).toBeTruthy(); - }); - - it("should assemble release plan for basic setup with snapshot and tag", () => { - let { releases } = assembleReleasePlan( - setup.changesets, - setup.packages, - defaultConfig, - undefined, - "foo" - ); + let { releases } = assembleReleasePlan( + setup.changesets, + setup.packages, + config, + undefined, + snapshot + ); - expect(releases.length).toBe(1); - expect(/0\.0\.0-foo-\d{14}/.test(releases[0].newVersion)).toBeTruthy(); - }); + expect(releases.length).toBe(1); + expect(resultRegexp.test(releases[0].newVersion)).toBeTruthy(); + } + ); it("should assemble release plan with multiple packages", () => { setup.addChangeset({ diff --git a/packages/assemble-release-plan/src/index.ts b/packages/assemble-release-plan/src/index.ts index f302369c2..7f9917b87 100644 --- a/packages/assemble-release-plan/src/index.ts +++ b/packages/assemble-release-plan/src/index.ts @@ -27,20 +27,36 @@ function getPreVersion(version: string) { return preVersion; } -function getSnapshotSuffix(snapshot?: string | boolean): string | undefined { +function getSnapshotSuffix( + snapshot: string | boolean | undefined, + timestampSeparator: "-" | ".", + timestampPosition: "start" | "end" +): string | undefined { if (snapshot === undefined) { return; } - let dateAndTime = new Date() + const parts: string[] = []; + const timestamp = new Date() .toISOString() .replace(/\.\d{3}Z$/, "") .replace(/[^\d]/g, ""); - let tag = ""; - if (typeof snapshot === "string") tag = `-${snapshot}`; + if (timestampPosition === "start") { + parts.push(timestamp); - return `${tag}-${dateAndTime}`; + if (typeof snapshot === "string") { + parts.push(snapshot); + } + } else { + if (typeof snapshot === "string") { + parts.push(snapshot); + } + + parts.push(timestamp); + } + + return `-${parts.join(timestampSeparator)}`; } function getNewVersion( @@ -95,7 +111,11 @@ 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); + const snapshotSuffix = getSnapshotSuffix( + snapshot, + config.snapshotTimestampSeparator, + config.snapshotTimestampPosition + ); // 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 diff --git a/packages/config/src/index.test.ts b/packages/config/src/index.test.ts index dfcc64838..b129ef6c9 100644 --- a/packages/config/src/index.test.ts +++ b/packages/config/src/index.test.ts @@ -45,6 +45,8 @@ test("read reads the config", async () => { updateInternalDependencies: "patch", ignore: [], bumpVersionsWithWorkspaceProtocolOnly: false, + snapshotTimestampSeparator: "-", + snapshotTimestampPosition: "end", ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, updateInternalDependents: "out-of-range", @@ -62,6 +64,8 @@ let defaults = { baseBranch: "master", updateInternalDependencies: "patch", ignore: [], + snapshotTimestampSeparator: "-", + snapshotTimestampPosition: "end", ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: false, updateInternalDependents: "out-of-range", diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts index fc8fed2d5..069b574e3 100644 --- a/packages/config/src/index.ts +++ b/packages/config/src/index.ts @@ -23,7 +23,9 @@ export let defaultWrittenConfig = { access: "restricted", baseBranch: "master", updateInternalDependencies: "patch", - ignore: [] as ReadonlyArray + ignore: [] as ReadonlyArray, + snapshotTimestampSeparator: "-", + snapshotTimestampPosition: "end" } as const; function flatten(arr: Array): T[] { @@ -315,6 +317,32 @@ export let parse = (json: WrittenConfig, packages: Packages): Config => { } } + if ( + json.snapshotTimestampSeparator !== undefined && + !["-", "."].includes(json.snapshotTimestampSeparator) + ) { + messages.push( + `The \`snapshotTimestampSeparator\` option is set as ${JSON.stringify( + json.snapshotTimestampSeparator, + null, + 2 + )} but can only be '-' or '.'` + ); + } + + if ( + json.snapshotTimestampPosition !== undefined && + !["start", "end"].includes(json.snapshotTimestampPosition) + ) { + messages.push( + `The \`snapshotTimestampPosition\` option is set as ${JSON.stringify( + json.snapshotTimestampPosition, + null, + 2 + )} but can only be 'start' or 'end'` + ); + } + if (json.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH !== undefined) { const { onlyUpdatePeerDependentsWhenOutOfRange, @@ -398,6 +426,15 @@ export let parse = (json: WrittenConfig, packages: Packages): Config => { bumpVersionsWithWorkspaceProtocolOnly: json.bumpVersionsWithWorkspaceProtocolOnly === true, + snapshotTimestampSeparator: + json.snapshotTimestampSeparator === undefined + ? defaultWrittenConfig.snapshotTimestampSeparator + : json.snapshotTimestampSeparator, + snapshotTimestampPosition: + json.snapshotTimestampPosition === undefined + ? defaultWrittenConfig.snapshotTimestampPosition + : json.snapshotTimestampPosition, + ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: { onlyUpdatePeerDependentsWhenOutOfRange: json.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH === undefined || diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 9492550a6..d78e51b42 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -73,6 +73,8 @@ export type Config = { ignore: ReadonlyArray; /** This is supposed to be used with pnpm's `link-workspace-packages: false` and Berry's `enableTransparentWorkspaces: false` */ bumpVersionsWithWorkspaceProtocolOnly?: boolean; + snapshotTimestampSeparator: "-" | "."; + snapshotTimestampPosition: "start" | "end"; ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: Required< ExperimentalOptions >; @@ -89,6 +91,8 @@ export type WrittenConfig = { updateInternalDependencies?: "patch" | "minor"; ignore?: ReadonlyArray; bumpVersionsWithWorkspaceProtocolOnly?: boolean; + snapshotTimestampSeparator?: "-" | "."; + snapshotTimestampPosition?: "start" | "end"; ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH?: ExperimentalOptions; };