Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/warm-numbers-doubt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@changesets/cli": minor
---

Changeset CLI can now be run from the nested directories in the project, where the `.changeset` directory has to be found in one of the parent directories
6 changes: 3 additions & 3 deletions packages/cli/src/commands/pre/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ import {
} from "@changesets/errors";

export default async function pre(
cwd: string,
rootDir: string,
options: { command: "enter"; tag: string } | { command: "exit"; tag?: string }
) {
if (options.command === "enter") {
try {
await enterPre(cwd, options.tag);
await enterPre(rootDir, options.tag);
logger.success(`Entered pre mode with tag ${pc.cyan(options.tag)}`);
logger.info(
"Run `changeset version` to version packages with prerelease versions"
Expand All @@ -30,7 +30,7 @@ export default async function pre(
}
} else {
try {
await exitPre(cwd);
await exitPre(rootDir);
logger.success(`Exited pre mode`);
logger.info(
"Run `changeset version` to version packages with normal versions"
Expand Down
56 changes: 55 additions & 1 deletion packages/cli/src/run.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import path from "path";
import { error } from "@changesets/logger";
import { testdir } from "@changesets/test-utils";

import add from "./commands/add";
import { run } from "./run";
import writeChangeset from "@changesets/write";
Expand Down Expand Up @@ -299,4 +299,58 @@ describe("cli", () => {
);
});
});

it("should be able to add a changesed when called from subdirectory", async () => {
const rootDir = await testdir({
"package.json": JSON.stringify({
private: true,
workspaces: ["packages/*"],
}),
"packages/pkg-a/package.json": JSON.stringify({
name: "pkg-a",
version: "1.0.0",
}),
".changeset/config.json": JSON.stringify({}),
});

const cwd = path.resolve(rootDir, "packages", "pkg-a");

await run([], { message: "test" }, cwd);

expect(add).toHaveBeenCalledWith(
rootDir,
{
empty: undefined,
open: undefined,
message: "test",
},
expect.any(Object)
);
});

it("should throw when .changeset folder is missing when called from subdirectory", async () => {
const rootDir = await testdir({
"package.json": JSON.stringify({
private: true,
workspaces: ["packages/*"],
}),
"packages/pkg-a/package.json": JSON.stringify({
name: "pkg-a",
version: "1.0.0",
}),
});

const cwd = path.resolve(rootDir, "packages", "pkg-a");

try {
await run(["version"], {}, cwd);
} catch (e) {
// ignore the error. We just want to validate the error message
}

const loggerErrorCalls = (error as any).mock.calls;
expect(loggerErrorCalls[0][0].trim()).toEqual(
"There is no .changeset folder."
);
});
});
28 changes: 14 additions & 14 deletions packages/cli/src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,15 @@ export async function run(
flags: { [name: string]: any },
cwd: string
) {
const packages = await getPackages(cwd);
const rootDir = packages.root.dir;

if (input[0] === "init") {
await init(cwd);
await init(rootDir);
return;
}

if (!fs.existsSync(path.resolve(cwd, ".changeset"))) {
if (!fs.existsSync(path.resolve(rootDir, ".changeset"))) {
error("There is no .changeset folder. ");
error(
"If this is the first time `changesets` have been used in this project, run `yarn changeset init` to get set up."
Expand All @@ -37,14 +40,12 @@ export async function run(
throw new ExitError(1);
}

const packages = await getPackages(cwd);

let config: Config;
try {
config = await read(cwd, packages);
config = await read(rootDir, packages);
} catch (e) {
let oldConfigExists = await fs.pathExists(
path.resolve(cwd, ".changeset/config.js")
path.resolve(rootDir, ".changeset/config.js")
);
if (oldConfigExists) {
error(
Expand All @@ -66,7 +67,7 @@ export async function run(
if (input.length < 1) {
const { empty, open, since, message }: CliOptions = flags;
// @ts-ignore if this is undefined, we have already exited
await add(cwd, { empty, open, since, message }, config);
await add(rootDir, { empty, open, since, message }, config);
} else if (input[0] !== "pre" && input.length > 1) {
error(
"Too many arguments passed to changesets - we only accept the command name as an argument"
Expand Down Expand Up @@ -107,7 +108,7 @@ export async function run(

switch (input[0]) {
case "add": {
await add(cwd, { empty, open, since, message }, config);
await add(rootDir, { empty, open, since, message }, config);
return;
}
case "version": {
Expand Down Expand Up @@ -188,19 +189,19 @@ export async function run(
config.snapshot.prereleaseTemplate = snapshotPrereleaseTemplate;
}

await version(cwd, { snapshot }, config);
await version(rootDir, { snapshot }, config);
return;
}
case "publish": {
await publish(cwd, { otp, tag, gitTag }, config);
await publish(rootDir, { otp, tag, gitTag }, config);
return;
}
case "status": {
await status(cwd, { sinceMaster, since, verbose, output }, config);
await status(rootDir, { sinceMaster, since, verbose, output }, config);
return;
}
case "tag": {
await tagCommand(cwd, config);
await tagCommand(rootDir, config);
return;
}
case "pre": {
Expand All @@ -216,8 +217,7 @@ export async function run(
error(`A tag must be passed when using prerelease enter`);
throw new ExitError(1);
}
// @ts-ignore
await pre(cwd, { command, tag });
await pre(rootDir, { command, tag });
return;
}
case "bump": {
Expand Down
5 changes: 4 additions & 1 deletion packages/config/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ test("read reads the config", async () => {
commit: true,
}),
});
let config = await read(cwd, defaultPackages);
let config = await read(cwd, {
...defaultPackages,
root: { ...defaultPackages.root, dir: cwd },
});
expect(config).toEqual({
fixed: [],
linked: [],
Expand Down
4 changes: 3 additions & 1 deletion packages/config/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,9 @@ function isArray<T>(

export let read = async (cwd: string, packages?: Packages) => {
packages ??= await getPackages(cwd);
let json = await fs.readJSON(path.join(cwd, ".changeset", "config.json"));
let json = await fs.readJSON(
path.join(packages.root.dir, ".changeset", "config.json")
);
return parse(json, packages);
};

Expand Down
6 changes: 3 additions & 3 deletions packages/get-release-plan/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ export default async function getReleasePlan(
passedConfig?: Config
): Promise<ReleasePlan> {
const packages = await getPackages(cwd);
const preState = await readPreState(cwd);
const readConfig = await read(cwd, packages);
const preState = await readPreState(packages.root.dir);
const readConfig = await read(packages.root.dir, packages);
const config = passedConfig ? { ...readConfig, ...passedConfig } : readConfig;
const changesets = await readChangesets(cwd, sinceRef);
const changesets = await readChangesets(packages.root.dir, sinceRef);

return assembleReleasePlan(changesets, packages, config, preState);
}
16 changes: 9 additions & 7 deletions packages/pre/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import {
PreEnterButInPreModeError,
} from "@changesets/errors";

export async function readPreState(cwd: string): Promise<PreState | undefined> {
let preStatePath = path.resolve(cwd, ".changeset", "pre.json");
export async function readPreState(
rootDir: string
): Promise<PreState | undefined> {
let preStatePath = path.resolve(rootDir, ".changeset", "pre.json");
// TODO: verify that the pre state isn't broken
let preState: PreState | undefined;
try {
Expand All @@ -29,10 +31,10 @@ export async function readPreState(cwd: string): Promise<PreState | undefined> {
return preState;
}

export async function exitPre(cwd: string) {
let preStatePath = path.resolve(cwd, ".changeset", "pre.json");
export async function exitPre(rootDir: string) {
let preStatePath = path.resolve(rootDir, ".changeset", "pre.json");
// TODO: verify that the pre state isn't broken
let preState = await readPreState(cwd);
let preState = await readPreState(rootDir);

if (preState === undefined) {
throw new PreExitButNotInPreModeError();
Expand All @@ -44,8 +46,8 @@ export async function exitPre(cwd: string) {
);
}

export async function enterPre(cwd: string, tag: string) {
let packages = await getPackages(cwd);
export async function enterPre(rootDir: string, tag: string) {
let packages = await getPackages(rootDir);
let preStatePath = path.resolve(packages.root.dir, ".changeset", "pre.json");
let preState: PreState | undefined = await readPreState(packages.root.dir);
// can't reenter if pre mode still exists, but we should allow exited pre mode to be reentered
Expand Down
4 changes: 2 additions & 2 deletions packages/read/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,10 @@ async function filterChangesetsSinceRef(
}

export default async function getChangesets(
cwd: string,
rootDir: string,
sinceRef?: string
): Promise<Array<NewChangeset>> {
let changesetBase = path.join(cwd, ".changeset");
let changesetBase = path.join(rootDir, ".changeset");
let contents: string[];
try {
contents = await fs.readdir(changesetBase);
Expand Down
6 changes: 3 additions & 3 deletions packages/write/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ function getPrettierInstance(cwd: string): typeof prettier {

async function writeChangeset(
changeset: Changeset,
cwd: string,
rootDir: string,
options?: { prettier?: boolean }
): Promise<string> {
const { summary, releases } = changeset;

const changesetBase = path.resolve(cwd, ".changeset");
const changesetBase = path.resolve(rootDir, ".changeset");

// Worth understanding that the ID merely needs to be a unique hash to avoid git conflicts
// experimenting with human readable ids to make finding changesets easier
Expand All @@ -32,7 +32,7 @@ async function writeChangeset(
});

const prettierInstance =
options?.prettier !== false ? getPrettierInstance(cwd) : undefined;
options?.prettier !== false ? getPrettierInstance(rootDir) : undefined;
const newChangesetPath = path.resolve(changesetBase, `${changesetID}.md`);

// NOTE: The quotation marks in here are really important even though they are
Expand Down