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

fix: better logging #16

Merged
merged 1 commit 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
38 changes: 26 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ As an added bonus, this plugin will also publish some simple monorepo patterns.
- [`package.json` file](#packagejson-file)
- [Plugin options](#plugin-options)
- [Examples](#examples)
- [Only create package tarball](#only-create-package-tarball)
- [Plugin steps](#plugin-steps)
- [Development](#development)
- [Roadmap](#roadmap)
Expand Down Expand Up @@ -59,14 +60,25 @@ for example:

## NPM registry authentication

The NPM authentication configuration is **required** and can be set either via
[environment variables](#environment-variables) or the
Providing a NPM access token in your configuration is **required** and can be
set either via [environment variables](#environment-variables) or the
[`.yarnrc.yml`](#yarnrcyml-file) file.

> **Note**: when
> [two-factor authentication](https://docs.npmjs.com/configuring-two-factor-authentication)
> is enabled on your NPM account and enabled for writes (default setting), the
> token needs to be of type **Automation**.
Make sure your access token has write access to the package you want to publish:

- **When using a
[classic/legacy token](https://docs.npmjs.com/creating-and-viewing-access-tokens#creating-legacy-tokens-on-the-website)**,
it must be either:
- A "**Publish**" token if you're not using 2FA or if 2FA is disabled for
write operations (The "Require two-factor authentication for write actions"
is unchecked in your 2FA settings)
- An "**Automation**" token if 2FA is enabled for write operations (The
"Require two-factor authentication for write actions" is checked in your 2FA
settings)
- **When using a
[granular access token](https://docs.npmjs.com/creating-and-viewing-access-tokens#creating-granular-access-tokens-on-the-website)**
make sure it has "**Read and write**" permissions on the package you want to
publish.

> **Note**: only the
> [`npmAuthToken`](https://yarnpkg.com/configuration/yarnrc/#npmAuthToken) is
Expand All @@ -83,7 +95,7 @@ release is due, all workspaces will be published to the NPM registry.

Monorepos are detected by the presence of a
[`workspaces`](https://yarnpkg.com/configuration/manifest#workspaces) option in
the root `package.json` file:
the root `package.json` file, for example:

```json
{
Expand All @@ -97,10 +109,10 @@ See [our roadmap](#roadmap) for further implementation status.

### Environment variables

| Variable | Description |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `YARN_NPM_AUTH_TOKEN` | [NPM token](https://docs.npmjs.com/creating-and-viewing-access-tokens). Translates to the [npmAuthToken](https://yarnpkg.com/configuration/yarnrc#npmAuthToken) `.yarnrc.yml` option. |
| `YARN_NPM_PUBLISH_REGISTRY` | NPM registry to use. Translates to the [npmPublishRegistry](https://yarnpkg.com/configuration/yarnrc#npmPublishRegistry) `.yarnrc.yml` option. |
| Variable | Description |
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `YARN_NPM_AUTH_TOKEN` | [NPM access token](https://docs.npmjs.com/creating-and-viewing-access-tokens). Translates to the [npmAuthToken](https://yarnpkg.com/configuration/yarnrc#npmAuthToken) `.yarnrc.yml` option. |
| `YARN_NPM_PUBLISH_REGISTRY` | NPM registry to use. Translates to the [npmPublishRegistry](https://yarnpkg.com/configuration/yarnrc#npmPublishRegistry) `.yarnrc.yml` option. |

Most other Yarn options could be specified as environment variables as well.
Just prefix the names and write them in snake case. Refer to the
Expand All @@ -121,7 +133,7 @@ of option.
The
[`registry`](https://yarnpkg.com/configuration/manifest#publishConfig.registry)
can be configured in the `package.json` and will take precedence over the
configuration in environment variables and the `.yarnrc.yml` file.
configuration in environment variables and the `.yarnrc.yml` file:

```json
{
Expand Down Expand Up @@ -169,6 +181,8 @@ for example:

## Examples

### Only create package tarball

The `npmPublish` and `tarballDir` option can be used to skip the publishing to
the NPM registry and instead release the package tarball with another plugin.
For example with the
Expand Down
2 changes: 2 additions & 0 deletions src/definitions/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ export const PLUGIN_HOMEPAGE =
"https://github.com/hongaar/semantic-release-yarn";
export const PLUGIN_GIT_BRANCH = "main";

export const YARNRC_FILENAME = ".yarnrc.yml";

export const DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org";
export const DEFAULT_YARN_REGISTRY = "https://registry.yarnpkg.com";

Expand Down
72 changes: 40 additions & 32 deletions src/definitions/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,77 +6,85 @@ function linkify(file: string) {

export function EINVALIDNPMPUBLISH({ npmPublish }: { npmPublish: unknown }) {
return {
message: "Invalid `npmPublish` option.",
message: 'Invalid "npmPublish" option.',
details: `The [npmPublish option](${linkify(
"README.md#npmpublish"
)}) option, if defined, must be a \`Boolean\`.
"README.md#plugin-options"
)}) option, if defined, must be a "Boolean".

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

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

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

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

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

export function ENONPMTOKEN({ registry }: { registry: string }) {
return {
message: "No npm token specified.",
details: `An [npm token](${linkify(
message: "No NPM access token specified.",
details: `An [NPM access token](${linkify(
"README.md#npm-registry-authentication"
)}) must be created and set in the \`YARN_NPM_AUTH_TOKEN\` environment variable on your CI environment.

Please make sure to create an [npm token](https://docs.npmjs.com/getting-started/working_with_tokens#how-to-create-new-tokens) and to set it in the \`YARN_NPM_AUTH_TOKEN\` environment variable on your CI environment. The token must allow to publish to the registry \`${registry}\`.`,
)}) must be provided in your configuration. The token must allow to publish to the registry "${registry}".

Please refer to the [npm registry authentication](${linkify(
"README.md#npm-registry-authentication"
)}) section of the README to learn how to configure the NPM registry access token.`,
};
}

export function EINVALIDNPMTOKEN({ registry }: { registry: string }) {
return {
message: "Invalid npm token.",
details: `The [npm token](${linkify(
message: "Invalid NPM access token.",
details: `The [NPM access token](${linkify(
"README.md#npm-registry-authentication"
)}) configured in the \`YARN_NPM_AUTH_TOKEN\` environment variable must be a valid [token](https://docs.npmjs.com/getting-started/working_with_tokens) allowing to publish to the registry \`${registry}\`.

If you are using Two Factor Authentication for your account, set its level to ["Authorization only"](https://docs.npmjs.com/getting-started/using-two-factor-authentication#levels-of-authentication) in your account settings. **semantic-release** cannot publish with the default "
Authorization and writes" level.

Please make sure to set the \`YARN_NPM_AUTH_TOKEN\` environment variable in your CI with the exact value of the npm token.`,
)}) configured must be a valid [access token](https://docs.npmjs.com/getting-started/working_with_tokens) allowing to publish to the registry "${registry}".

Please refer to the [npm registry authentication](${linkify(
"README.md#npm-registry-authentication"
)}) section of the README to learn how to configure the NPM registry access token.`,
};
}

export function ENOPKGNAME() {
return {
message: "Missing `name` property in `package.json`.",
details: `The \`package.json\`'s [name](https://docs.npmjs.com/files/package.json#name) property is required in order to publish a package to the npm registry.
message: 'Missing "name" property in "package.json".',
details: `The "package.json"'s [name](https://docs.npmjs.com/files/package.json#name) property is required in order to publish a package to the registry.

Please make sure to add a valid \`name\` for your package in your \`package.json\`.`,
Please make sure to add a valid "name" for your package in your "package.json".`,
};
}

export function ENOPKG() {
return {
message: "Missing `package.json` file.",
details: `A [package.json file](https://docs.npmjs.com/files/package.json) at the root of your project is required to release on npm.
message: 'Missing "package.json" file.',
details: `A [package.json file](https://docs.npmjs.com/files/package.json) at the root of your project is required to release on NPM.

Please follow the [npm guideline](https://docs.npmjs.com/getting-started/creating-node-modules) to create a valid \`package.json\` file.`,
Please follow the [npm guideline](https://docs.npmjs.com/getting-started/creating-node-modules) to create a valid "package.json" file.`,
};
}

export function ENOYARN() {
return {
message: "Yarn not found.",
details: `The Yarn CLI could not be found in your PATH. Make sure Yarn is installed and try again.`,
};
}

Expand All @@ -87,6 +95,6 @@ export function EINVALIDYARN({ version }: { version: string }) {
"README.md#install"
)}) to review which versions of Yarn are currently supported

Your version of Yarn is \`${version}\`.`,
Your version of Yarn is "${version}".`,
};
}
9 changes: 5 additions & 4 deletions src/get-error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ export type ErrorDefinition = Error & {
semanticRelease: boolean;
};

export function getError(
code: keyof typeof ERROR_DEFINITIONS,
ctx: any = {}
export function getError<T extends keyof typeof ERROR_DEFINITIONS>(
code: T,
ctx: Parameters<typeof ERROR_DEFINITIONS[T]>[0]
): ErrorDefinition {
const { message, details } = ERROR_DEFINITIONS[code](ctx);
const { message, details } = ERROR_DEFINITIONS[code](ctx as any);

return new SemanticReleaseError(message, code, details);
}
4 changes: 2 additions & 2 deletions src/get-pkg.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ export async function getPkg(
});

if (!pkg.name) {
throw getError("ENOPKGNAME");
throw getError("ENOPKGNAME", undefined);
}

return pkg;
} catch (error: any) {
if (error.code === "ENOENT") {
throw getError("ENOPKG");
throw getError("ENOPKG", undefined);
}

throw error;
Expand Down
32 changes: 27 additions & 5 deletions src/get-token.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,49 @@
// @ts-ignore
import toNerfDart from "nerf-dart";
import { YARNRC_FILENAME } from "./definitions/constants.js";
import type { CommonContext } from "./definitions/context.js";
import type { Yarnrc } from "./definitions/yarnrc.js";

export function getToken(
registry: string,
{ npmRegistries, npmAuthToken }: Yarnrc,
{ env }: { env?: CommonContext["env"] }
{
env,
logger,
}: { env: CommonContext["env"]; logger: CommonContext["logger"] }
) {
// @todo implement yarnrc.npmScopes

const registryId = toNerfDart(registry);

// @todo implement yarnrc.npmScopes

// Lookup in yarnrc.npmRegistries
// @todo - Verify this does in fact override an auth token set in env var
const entry =
npmRegistries &&
Object.entries(npmRegistries).find(([id, { npmAuthToken }]) => {
return toNerfDart(id) === registryId && npmAuthToken;
});

if (entry) {
logger.log(
`Using token from "${YARNRC_FILENAME}: npmRegistries["${entry[0]}"].npmAuthToken"`
);

return npmRegistries[entry[0]]!.npmAuthToken!;
}

return env?.["YARN_NPM_AUTH_TOKEN"] || npmAuthToken;
// Return env var if set
if (env["YARN_NPM_AUTH_TOKEN"]) {
logger.log(`Using token from environment variable YARN_NPM_AUTH_TOKEN`);

return env["YARN_NPM_AUTH_TOKEN"];
}

// Return yarnrc.npmAuthToken if set
if (npmAuthToken) {
logger.log(`Using token from "${YARNRC_FILENAME}: npmAuthToken"`);

return npmAuthToken;
}

return;
}
3 changes: 2 additions & 1 deletion src/get-yarn-config.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { cosmiconfig } from "cosmiconfig";
import _ from "lodash";
import { dirname, resolve } from "node:path";
import { YARNRC_FILENAME } from "./definitions/constants.js";
import type { CommonContext } from "./definitions/context.js";
import type { Yarnrc } from "./definitions/yarnrc.js";

Expand All @@ -13,7 +14,7 @@ export async function getYarnConfig({
}): Promise<Yarnrc> {
const result = await cosmiconfigSearchRecursive<Yarnrc>(
cosmiconfig("yarn", {
searchPlaces: [".yarnrc.yml"],
searchPlaces: [YARNRC_FILENAME],
}),
cwd
);
Expand Down
3 changes: 2 additions & 1 deletion src/get-yarn-version.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { getImplementation } from "./container.js";
import type { CommonContext } from "./definitions/context.js";
import { getError } from "./get-error.js";

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

try {
return (await execa("yarn", ["--version"], { cwd })).stdout;
} catch {
throw new Error("Could not determine Yarn version. Is Yarn installed?");
throw getError("ENOYARN", undefined);
}
}

Expand Down
Loading