Skip to content

Commit

Permalink
feat: monorepo notifications and channels (#19)
Browse files Browse the repository at this point in the history
- [x] Add plugin option to specify main workspace used in release
notifications (e.g. GitHub issue/pr comments)
- [x] Implement new plugin option
- [x] Iterate workspaces to add channels for each workspace
  • Loading branch information
hongaar authored Dec 8, 2022
1 parent 4012c03 commit 5f5ac71
Show file tree
Hide file tree
Showing 13 changed files with 239 additions and 135 deletions.
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

0 comments on commit 5f5ac71

Please sign in to comment.