Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: monorepo notifications and channels #19

Merged
merged 4 commits into from
Dec 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 11 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,11 @@ the root `package.json` file, for example:
}
```

You can set the `mainWorkspace` [plugin option](#plugin-options) to use in
notifications of new releases (e.g. in issue and pull request comments made by
the [@semantic-release/github](https://github.com/semantic-release/github)
plugin.

See [our roadmap](#roadmap) for further implementation status.

## Configuration
Expand Down Expand Up @@ -170,11 +175,12 @@ for example:
}
```

| Options | Description | Default |
| ------------ | ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `npmPublish` | Whether to publish the NPM package to the registry. If `false` the `package.json` version will still be updated. | `false` if the `package.json` [private](https://docs.npmjs.com/files/package.json#private) property is `true`, `true` otherwise. |
| `pkgRoot` | Directory path to publish. | `.` |
| `tarballDir` | Directory path in which to write the package tarball. If `false` the tarball is not kept on the file system. | `false` |
| Options | Description | Default |
| --------------- | ---------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `npmPublish` | Whether to publish the NPM package to the registry. If `false` the `package.json` version will still be updated. | `false` if the `package.json` [private](https://docs.npmjs.com/files/package.json#private) property is `true`, `true` otherwise. |
| `pkgRoot` | Directory path to publish. | `.` |
| `tarballDir` | Directory path in which to write the package tarball. If `false` the tarball is not kept on the file system. | `false` |
| `mainWorkspace` | Name of monorepo workspace to be used in release info | |

> **Note**: the `pkgRoot` directory must contain a `package.json`. The version
> will be updated only in the `package.json` within the `pkgRoot` directory.
Expand Down
46 changes: 24 additions & 22 deletions src/add-channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { getRegistry } from "./get-registry.js";
import { getReleaseInfo } from "./get-release-info.js";
import { getYarnConfig } from "./get-yarn-config.js";
import { reasonToNotPublish, shouldPublish } from "./should-publish.js";
import { getWorkspaces } from "./yarn-workspaces.js";

export async function addChannel(
pluginConfig: PluginConfig,
Expand All @@ -32,31 +33,32 @@ export async function addChannel(
const distTag = getChannel(channel!);
const isMonorepo = typeof pkg.workspaces !== "undefined";

if (isMonorepo) {
logger.log(`Adding npm tags to monorepo workspaces is not supported yet`);
return false;
}
const packagesToTag = isMonorepo
? (await getWorkspaces({ cwd })).map(({ name }) => name)
: [pkg.name];

logger.log(
`Adding version ${version} to npm registry ${registry} on dist-tag ${distTag}`
);
const result = execa(
"yarn",
["npm", "tag", "add", `${pkg.name}@${version}`, distTag],
{
cwd: basePath,
env,
}
);
result.stdout!.pipe(stdout, { end: false });
result.stderr!.pipe(stderr, { end: false });
await result;
for (const name of packagesToTag) {
logger.log(
`Adding version ${version} to npm registry ${registry} (tagged as @${distTag})`
);
const result = execa(
"yarn",
["npm", "tag", "add", `${name}@${version}`, distTag],
{
cwd: basePath,
env,
}
);
result.stdout!.pipe(stdout, { end: false });
result.stderr!.pipe(stderr, { end: false });
await result;

logger.log(
`Added ${pkg.name}@${version} to dist-tag @${distTag} on ${registry}`
);
logger.log(
`Added ${name}@${version} on ${registry} (tagged as @${distTag})`
);
}

return getReleaseInfo(pkg, context, distTag, registry);
return getReleaseInfo(pkg, pluginConfig, context, distTag, registry);
}

const reason = reasonToNotPublish(pluginConfig, pkg);
Expand Down
15 changes: 15 additions & 0 deletions src/definitions/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ Your configuration for the "pkgRoot" option is "${pkgRoot}".`,
};
}

export function EINVALIDMAINWORKSPACE({
mainWorkspace,
}: {
mainWorkspace: unknown;
}) {
return {
message: 'Invalid "mainWorkspace" option.',
details: `The [mainWorkspace option](${linkify(
"README.md#plugin-options"
)}) option, if defined, must be a "String".

Your configuration for the "mainWorkspace" option is "${mainWorkspace}".`,
};
}

export function ENONPMTOKEN({ registry }: { registry: string }) {
return {
message: "No NPM access token specified.",
Expand Down
1 change: 1 addition & 0 deletions src/definitions/pluginConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export type PluginConfig = {
npmPublish?: boolean;
tarballDir?: string;
pkgRoot?: string;
mainWorkspace?: string;
};
4 changes: 3 additions & 1 deletion src/get-release-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
import type { PackageJson } from "read-pkg";
import { isDefaultRegistry } from "./definitions/constants.js";
import type { PublishContext } from "./definitions/context.js";
import type { PluginConfig } from "./definitions/pluginConfig.js";

export function getReleaseInfo(
{ name }: PackageJson,
{ mainWorkspace }: PluginConfig,
{
nextRelease: { version },
}: {
Expand All @@ -17,7 +19,7 @@ export function getReleaseInfo(
return {
name: `npm package (@${distTag} dist-tag)`,
url: isDefaultRegistry(registry)
? `https://www.npmjs.com/package/${name}/v/${version}`
? `https://www.npmjs.com/package/${mainWorkspace ?? name}/v/${version}`
: undefined,
channel: distTag,
};
Expand Down
4 changes: 4 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ export async function verifyConditions(
pluginConfig.pkgRoot,
publishPlugin.pkgRoot
);
pluginConfig.mainWorkspace = _.defaultTo(
pluginConfig.mainWorkspace,
publishPlugin.mainWorkspace
);
}

await verify(pluginConfig, context);
Expand Down
10 changes: 6 additions & 4 deletions src/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export async function publish(
nextRelease: { version, channel },
logger,
} = context;
const { pkgRoot } = pluginConfig;
const { pkgRoot, mainWorkspace } = pluginConfig;
const execa = await getImplementation("execa");

if (shouldPublish(pluginConfig, pkg)) {
Expand All @@ -36,7 +36,7 @@ export async function publish(
: [];

logger.log(
`Publishing version ${version} to npm registry ${registry} on dist-tag ${distTag}`
`Publishing version ${version} to npm registry ${registry} (tagged as @${distTag})`
);
const result = execa(
"yarn",
Expand All @@ -51,10 +51,12 @@ export async function publish(
await result;

logger.log(
`Published ${pkg.name}@${version} on ${registry} (with tag @${distTag})`
`Published ${
mainWorkspace ?? pkg.name
}@${version} on ${registry} (tagged as @${distTag})`
);

return getReleaseInfo(pkg, context, distTag, registry);
return getReleaseInfo(pkg, pluginConfig, context, distTag, registry);
}

const reason = reasonToNotPublish(pluginConfig, pkg);
Expand Down
34 changes: 14 additions & 20 deletions src/verify-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,24 @@ const VALIDATORS = {
npmPublish: _.isBoolean,
tarballDir: isNonEmptyString,
pkgRoot: isNonEmptyString,
mainWorkspace: isNonEmptyString,
};

export function verifyConfig({
npmPublish,
tarballDir,
pkgRoot,
}: PluginConfig) {
const errors = Object.entries({ npmPublish, tarballDir, pkgRoot }).reduce(
(errors, [option, value]) => {
if (_.isNil(value)) {
return errors;
}
export function verifyConfig(config: PluginConfig) {
const errors = Object.entries(config).reduce((errors, [option, value]) => {
if (_.isNil(value)) {
return errors;
}

if (VALIDATORS[option as keyof PluginConfig](value)) {
return errors;
}
if (VALIDATORS[option as keyof PluginConfig](value)) {
return errors;
}

return [
...errors,
getError(`EINVALID${option.toUpperCase()}` as any, { [option]: value }),
];
},
[] as ErrorDefinition[]
);
return [
...errors,
getError(`EINVALID${option.toUpperCase()}` as any, { [option]: value }),
];
}, [] as ErrorDefinition[]);

return errors;
}
21 changes: 21 additions & 0 deletions src/yarn-workspaces.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { getImplementation } from "./container.js";
import type { CommonContext } from "./definitions/context.js";

export async function getWorkspaces({ cwd }: { cwd: CommonContext["cwd"] }) {
const execa = await getImplementation("execa");

const { stdout } = await execa(
"yarn",
["workspaces", "list", "--json", "--no-private"],
{
cwd,
}
);

return stdout
.split("\n")
.reduce(
(acc, line) => [...acc, JSON.parse(line)],
[] as { location: string; name: string }[]
);
}
19 changes: 19 additions & 0 deletions test/get-release-info.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ test("Default registry and scoped module", (t) => {
t.deepEqual(
getReleaseInfo(
{ name: "@scope/module" },
{},
{ env: {}, nextRelease: { version: "1.0.0" } },
"latest",
"https://registry.npmjs.org/"
Expand All @@ -21,6 +22,7 @@ test("Custom registry and scoped module", (t) => {
t.deepEqual(
getReleaseInfo(
{ name: "@scope/module" },
{},
{ env: {}, nextRelease: { version: "1.0.0" } },
"latest",
"https://custom.registry.org/"
Expand All @@ -32,3 +34,20 @@ test("Custom registry and scoped module", (t) => {
}
);
});

test("With mainWorkspace set", (t) => {
t.deepEqual(
getReleaseInfo(
{ name: "@scope/module" },
{ mainWorkspace: "custom-workspace" },
{ env: {}, nextRelease: { version: "1.0.0" } },
"latest",
"https://registry.npmjs.org/"
),
{
name: "npm package (@latest dist-tag)",
url: "https://www.npmjs.com/package/custom-workspace/v/1.0.0",
channel: "latest",
}
);
});
Loading