Skip to content

Commit

Permalink
Support for snapshot versioning and publish under custom tag (#359)
Browse files Browse the repository at this point in the history
* initial implementation of snapshot release

* add logic to release changelog file too

* use unique hash for versioning

* lint fix - remove unused vars

* add error message when user runs snapshot in pre state

* revert unused code from pre command

* use hash cache

* revert adding logger to git

* update changelog for snapshot release support

* 1: [Version command] Move caching of snapshot version to the assemble-release-plan's main function
2: [Version command] Add condition to throw error if snapshot version is made while changeset is in pre-state
3: [Publish command] Update the condision to capture preState.mode and updated the error message when tag is passed in pre state

* split changessets and updated the description

* Update .changeset/violet-gorillas-march.md

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

* Update .changeset/dry-rivers-train.md

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

Co-authored-by: Mitchell Hamilton <mitchell@hamil.town>
Co-authored-by: Mateusz Burzy艅ski <mateuszburzynski@gmail.com>
  • Loading branch information
3 people committed May 21, 2020
1 parent 9f9c61f commit 6d0790a
Show file tree
Hide file tree
Showing 14 changed files with 257 additions and 66 deletions.
14 changes: 14 additions & 0 deletions .changeset/dry-rivers-train.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"@changesets/apply-release-plan": minor
"@changesets/assemble-release-plan": minor
"@changesets/cli": minor
---

Add support for snapshot flag to version command. Usage: `changeset version --snapshot [tag]`. The updated version of the packages looks like `0.0.0[-tag]-YYYYMMDDHHMMSS` where YYYY, MM, DD, HH, MM, and SS is the date and time of when the snapshot version is created. You can use this feature with the tag option in the publish command to publish packages under experimental tags from feature branches. To publish a snapshot version of a package under an experimental tag you can do:

```
$ # Version packages to snapshot version
$ changeset version --snapshot
$ # Publish packages under exprimental tag, keeping next and latest tag clean
$ changeset publish --tag exprimental
```
5 changes: 5 additions & 0 deletions .changeset/violet-gorillas-march.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@changesets/cli": minor
---

Add support for tag flag to publish command. Usage: `changeset publish --tag <tag>`. This will publish the packages under passed npm tag.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ node_modules/
*error.log
scratchings.js
dist/
.env
.env
coverage/
5 changes: 3 additions & 2 deletions packages/apply-release-plan/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ async function getCommitThatAddsChangeset(changesetId: string, cwd: string) {
export default async function applyReleasePlan(
releasePlan: ReleasePlan,
packages: Packages,
config: Config = defaultConfig
config: Config = defaultConfig,
snapshot?: string | boolean
) {
let cwd = packages.root.dir;

Expand Down Expand Up @@ -73,7 +74,7 @@ export default async function applyReleasePlan(
cwd
);

if (releasePlan.preState !== undefined) {
if (releasePlan.preState !== undefined && snapshot === undefined) {
if (releasePlan.preState.mode === "exit") {
await fs.remove(path.join(cwd, ".changeset", "pre.json"));
} else {
Expand Down
39 changes: 37 additions & 2 deletions packages/assemble-release-plan/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,37 @@ function getPreVersion(version: string) {
return preVersion;
}

/**
* Using version as 0.0.0 so that it does not hinder with other version release
* For example;
* if user has a regular pre-release at 1.0.0-beta.0 and then you had a snapshot pre-release at 1.0.0-canary-git-hash
* 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.
*/
function getSnapshotReleaseVersion(snapshot?: string | boolean) {
const now = new Date();

let dateAndTime = [
now.getUTCFullYear(),
now.getUTCMonth(),
now.getUTCDate(),
now.getUTCHours(),
now.getUTCMinutes(),
now.getUTCSeconds()
].join("");

let tag = "";
if (typeof snapshot === "string") tag = `-${snapshot}`;

return `0.0.0${tag}-${dateAndTime}`;
}

function assembleReleasePlan(
changesets: NewChangeset[],
packages: Packages,
config: Config,
preState: PreState | undefined
preState: PreState | undefined,
snapshot?: string | boolean
): ReleasePlan {
let updatedPreState: PreState | undefined =
preState === undefined
Expand All @@ -36,6 +62,12 @@ function assembleReleasePlan(
}
};

// Caching the snapshot version here and use this if it is snapshot release
let snapshotVersion: string;
if (snapshot !== undefined) {
snapshotVersion = getSnapshotReleaseVersion(snapshot);
}

let packagesByName = new Map(
packages.packages.map(x => [x.packageJson.name, x])
);
Expand Down Expand Up @@ -171,7 +203,10 @@ function assembleReleasePlan(
releases: [...releases.values()].map(incompleteRelease => {
return {
...incompleteRelease,
newVersion: incrementVersion(incompleteRelease, preInfo)!
newVersion:
snapshot === undefined
? incrementVersion(incompleteRelease, preInfo)!
: snapshotVersion
};
}),
preState: updatedPreState
Expand Down
7 changes: 4 additions & 3 deletions packages/cli/src/commands/pre/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import fixturez from "fixturez";
import pre from "./index";
import * as fs from "fs-extra";
import path from "path";
import chalk from "chalk";
import * as fs from "fs-extra";
import { PreState } from "@changesets/types";
import * as logger from "@changesets/logger";
import { ExitError } from "@changesets/errors";
import chalk from "chalk";

import pre from "./index";

let f = fixturez(__dirname);

Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/pre/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {

export default async function pre(
cwd: string,
options: { tag: string; command: "enter" } | { command: "exit"; tag?: string }
options: { command: "enter"; tag: string } | { command: "exit"; tag?: string }
) {
if (options.command === "enter") {
try {
Expand Down
37 changes: 37 additions & 0 deletions packages/cli/src/commands/publish/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import fixtures from "fixturez";
import publishCommand from "../index";
import { defaultConfig } from "@changesets/config";
import * as path from "path";
import * as pre from "@changesets/pre";
import { Config } from "@changesets/types";

let changelogPath = path.resolve(__dirname, "../../changelog");
let modifiedDefaultConfig: Config = {
...defaultConfig,
changelog: [changelogPath, null]
};

const f = fixtures(__dirname);

jest.mock("../npm-utils.ts");
jest.mock("../publishPackages.ts");
jest.mock("@changesets/pre");

describe("Publish command", () => {
let cwd: string;

beforeEach(async () => {
cwd = await f.copy("simple-project");
});
describe("in pre state", () => {
beforeEach(() => {
// @ts-ignore
pre.readPreState.mockImplementation(() => ({ mode: "pre" }));
});
it("should report error if the tag option is used in pre release", async () => {
await expect(
publishCommand(cwd, { tag: "exprimental" }, modifiedDefaultConfig)
).rejects.toThrowError();
});
});
});
44 changes: 31 additions & 13 deletions packages/cli/src/commands/publish/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ExitError } from "@changesets/errors";
import { error, log, success, warn } from "@changesets/logger";
import * as git from "@changesets/git";
import { readPreState } from "@changesets/pre";
import { Config } from "@changesets/types";
import { Config, PreState } from "@changesets/types";
import chalk from "chalk";

function logReleases(pkgs: Array<{ name: string; newVersion: string }>) {
Expand All @@ -19,30 +19,48 @@ let importantEnd = chalk.red(
"----------------------------------------------------------------------"
);

export default async function run(
cwd: string,
{ otp }: { otp?: string },
config: Config
) {
let preState = await readPreState(cwd);
function showNonLatestTagWarning(tag?: string, preState?: PreState) {
warn(importantSeparator);
if (preState) {
warn(importantSeparator);
warn(
`You are in prerelease mode so packages will be published to the ${chalk.cyan(
preState.tag
)} dist tag except for packages that have not had normal releases which will be published to ${chalk.cyan(
"latest"
)}`
)}
dist tag except for packages that have not had normal releases which will be published to ${chalk.cyan(
"latest"
)}`
);
warn(importantEnd);
} else if (tag !== "latest") {
warn(`Packages will be released under the ${tag} tag`);
}
warn(importantEnd);
}

export default async function run(
cwd: string,
{ otp, tag }: { otp?: string; tag?: string },
config: Config
) {
const releaseTag = tag && tag.length > 0 ? tag : undefined;
let preState = await readPreState(cwd);

if (releaseTag && preState && preState.mode === "pre") {
error("Releasing under custom tag is not allowed in pre mode");
log("To resolve this exit the pre mode by running `changeset pre exit`");
throw new ExitError(1);
}

if (releaseTag || preState) {
showNonLatestTagWarning(tag, preState);
}

const response = await publishPackages({
cwd,
// if not public, we wont pass the access, and it works as normal
access: config.access,
otp,
preState
preState,
tag: releaseTag
});

const successful = response.filter(p => p.published);
Expand Down
20 changes: 14 additions & 6 deletions packages/cli/src/commands/publish/publishPackages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,28 @@ import { TwoFactorState } from "../../utils/types";
import { PreState } from "@changesets/types";
import isCI from "../../utils/isCI";

function getReleaseTag(pkgInfo: PkgInfo, preState?: PreState, tag?: string) {
if (tag) return tag;

if (preState !== undefined && pkgInfo.publishedState !== "only-pre") {
return preState.tag;
}

return "latest";
}

export default async function publishPackages({
cwd,
access,
otp,
preState
preState,
tag
}: {
cwd: string;
access: AccessType;
otp?: string;
preState: PreState | undefined;
tag?: string;
}) {
const packages = await getPackages(cwd);
const packagesByName = new Map(
Expand Down Expand Up @@ -76,11 +88,7 @@ export default async function publishPackages({
pkg,
access,
twoFactorState,
preState === undefined
? "latest"
: pkgInfo.publishedState === "only-pre"
? "latest"
: preState.tag
getReleaseTag(pkgInfo, preState, tag)
)
);
}
Expand Down
47 changes: 37 additions & 10 deletions packages/cli/src/commands/version/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import chalk from "chalk";
import path from "path";
import { log, warn } from "@changesets/logger";
import { log, warn, error } from "@changesets/logger";
import { Config } from "@changesets/types";
import applyReleasePlan from "@changesets/apply-release-plan";
import readChangesets from "@changesets/read";
Expand All @@ -9,6 +9,7 @@ import { getPackages } from "@manypkg/get-packages";

import { removeEmptyFolders } from "../../utils/v1-legacy/removeFolders";
import { readPreState } from "@changesets/pre";
import { ExitError } from "@changesets/errors";

let importantSeparator = chalk.red(
"===============================IMPORTANT!==============================="
Expand All @@ -18,7 +19,13 @@ let importantEnd = chalk.red(
"----------------------------------------------------------------------"
);

export default async function version(cwd: string, config: Config) {
export default async function version(
cwd: string,
options: {
snapshot?: string | boolean;
},
config: Config
) {
let [_changesets, _preState] = await Promise.all([
readChangesets(cwd),
readPreState(cwd),
Expand All @@ -31,11 +38,17 @@ export default async function version(cwd: string, config: Config) {

if (preState !== undefined && preState.mode === "pre") {
warn(importantSeparator);
warn("You are in prerelease mode");
warn(
"If you meant to do a normal release you should revert these changes and run `changeset pre exit`"
);
warn("You can then run `changeset version` again to do a normal release");
if (options.snapshot !== undefined) {
error("Snapshot release is not allowed in pre mode");
log("To resolve this exit the pre mode by running `changeset pre exit`");
throw new ExitError(1);
} else {
warn("You are in prerelease mode");
warn(
"If you meant to do a normal release you should revert these changes and run `changeset pre exit`"
);
warn("You can then run `changeset version` again to do a normal release");
}
warn(importantEnd);
}

Expand All @@ -49,11 +62,25 @@ export default async function version(cwd: string, config: Config) {

let packages = await getPackages(cwd);

let releasePlan = assembleReleasePlan(changesets, packages, config, preState);
let releasePlan = assembleReleasePlan(
changesets,
packages,
config,
preState,
options.snapshot
);

await applyReleasePlan(releasePlan, packages, config);
await applyReleasePlan(
releasePlan,
packages,
{
...config,
commit: false
},
options.snapshot
);

if (config.commit) {
if (options.snapshot !== undefined && config.commit) {
log("All files have been updated and committed. You're ready to publish!");
} else {
log("All files have been updated. Review them and commit at your leisure");
Expand Down
Loading

0 comments on commit 6d0790a

Please sign in to comment.