Skip to content

Commit

Permalink
feat: daysToExpiry option (#15)
Browse files Browse the repository at this point in the history
<!-- 👋 Hi, thanks for sending a PR to azdo-npm-auth! 💖.
Please fill out all fields below and make sure each item is true and [x]
checked.
Otherwise we may not be able to review your PR. -->

## PR Checklist

- [x] Addresses an existing open issue: fixes #16
- [ ] That issue was marked as [`status: accepting
prs`](https://github.com/johnnyreilly/azdo-npm-auth/issues?q=is%3Aopen+is%3Aissue+label%3A%22status%3A+accepting+prs%22)
- [x] Steps in
[CONTRIBUTING.md](https://github.com/johnnyreilly/azdo-npm-auth/blob/main/.github/CONTRIBUTING.md)
were taken

## Overview

<!-- Description of what is changed and how the code change does that.
-->
Output now looks like this:

```bash
┌  📦🔑 Welcome to azdo-npm-auth 0.9.0! 📦🔑
│
●  options:
│  - pat: [NONE SUPPLIED - WILL ACQUIRE FROM AZURE]
│  - config: ./ignore/.npmrc
│  - email: [NONE SUPPLIED - WILL USE DEFAULT VALUE]
│  - daysToExpiry: 1
│
●  🎬 Starting parsing project .npmrc.
│
◇  ✅ Passed parsing project .npmrc.
│     
│     Loading .npmrc at: /Users/john.reilly/code/github.com/azdo-npm-auth/ignore/.npmrc
│     Parsed: 
│     - organisation: johnnyreilly
│     - urlWithoutRegistryAtStart: //pkgs.dev.azure.com/johnnyreilly/_packaging/johnnyreilly-packages/npm/registry/
│     - urlWithoutRegistryAtEnd: //pkgs.dev.azure.com/johnnyreilly/_packaging/johnnyreilly-packages/npm/
│
●  🎬 Starting creating Personal Access Token.
│
◇  ✅ Passed creating Personal Access Token.
│     
│     Creating Azure CLI Token
│     Created Azure CLI Token
│     
│     Creating Personal Access Token with API:
│     {
│       "displayName": "made by azdo-npm-auth at: 2024-11-15T08:49:11.487Z",
│       "scope": "vso.packaging",
│       "allOrgs": false,
│       "validTo": "2024-11-16T08:49:11.486Z"
│     }
│     Created Personal Access Token with API
│     
│     Personal Access Token created:
│     - name: made by azdo-npm-auth at: 2024-11-15T08:49:11.487Z
│     - scope: vso.packaging
│     - validTo: 2024-11-16T08:49:11.4866667Z
│
●  🎬 Starting constructing user .npmrc.
│
◇  ✅ Passed constructing user .npmrc.
│     
│
●  🎬 Starting writing user .npmrc.
│
◇  ✅ Passed writing user .npmrc.
│     
│     Writing users .npmrc to: /Users/john.reilly/.npmrc
│
└  📦🔑 Thanks for using azdo-npm-auth 0.9.0! 📦🔑
```
  • Loading branch information
johnnyreilly authored Nov 15, 2024
1 parent ae4cca7 commit 8c23d20
Show file tree
Hide file tree
Showing 13 changed files with 241 additions and 178 deletions.
114 changes: 59 additions & 55 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,38 +16,11 @@ Simply set up user authentication to Azure DevOps npm feeds, optionally using th

To get `azdo-npm-auth` to create the necessary user `.npmrc` file, run the following command:

```shell
npx cross-env npm_config_registry=https://registry.npmjs.org npx azdo-npm-auth
```

You might be wondering what the `cross-env npm_config_registry=https://registry.npmjs.org` part is for. It is a way to ensure that the `npx` command uses the **public** npm registry to install `azdo-npm-auth`. Without this, you might encounter an error like below:

```shell
npm error code E401
npm error Unable to authenticate, your authentication token seems to be invalid.
npm error To correct this please try logging in again with:
npm error npm login
```

The `npx cross-env` at the start is only necessary if you're catering for Windows users that do not use Bash. Otherwise you could simplify to just:

```shell
npm_config_registry=https://registry.npmjs.org npx azdo-npm-auth
```

## Help with `npm error code E401`

When you are attempting to install from private feeds, npm will commonly error out with some form of `npm error code E401`.

This section exists to list some classic errors you might encounter when you try to `npm i`. Regardless of the error, the remedy is generally:

```shell
npm_config_registry=https://registry.npmjs.org npx azdo-npm-auth
```

### User `.npmrc` not found

When you have no user `.npmrc` file you'll encounter an error like this:
You might be wondering what the `npm_config_registry=https://registry.npmjs.org` part is for. It is a way to ensure that the `npx` command uses the **public** npm registry to install `azdo-npm-auth`. Without this, you might encounter an error like below:

```shell
npm error code E401
Expand All @@ -56,67 +29,57 @@ npm error To correct this please try logging in again with:
npm error npm login
```

### Token used in user `.npmrc` file is expired

When your token has expired in your user `.npmrc` file you'll encounter an error like this:
If you're catering for Windows users that do not use Bash then you might need to introduce a `npx cross-env` prefix:

```shell
npm error code E401
npm error Incorrect or missing password.
npm error If you were trying to login, change your password, create an
npm error authentication token or enable two-factor authentication then
npm error that means you likely typed your password in incorrectly.
npm error Please try again, or recover your password at:
npm error https://www.npmjs.com/forgot
npm error
npm error If you were doing some other operation then your saved credentials are
npm error probably out of date. To correct this please try logging in again with:
npm error npm login
npx cross-env npm_config_registry=https://registry.npmjs.org npx azdo-npm-auth
```

## Integration with `package.json`

### `preinstall` script
### Custom npm script

A great way to integrate `azdo-npm-auth` is by using it in a `preinstall` script in your `package.json`:
We generally advise setting up a custom npm script like the one below:

```json
"scripts": {
"preinstall": "npx --yes azdo-npm-auth --config ./subdirectory-with-another-package-json/.npmrc"
"auth": "npm_config_registry=https://registry.npmjs.org npx --yes azdo-npm-auth"
},
```

The `--yes` flag above skips having npm challenge the user as to whether to download the package; useful in a CI environment.
Users should `npm run auth` when a `npm error code E401` is encountered. We've called this script `auth` - you can choose any name you like.

You might be wondering why we do not recommend using a `preinstall` script. It's possible but there are gotchas. Read on.

However, as you're probably noticing, this requires having multiple `package.json`s and only having the `.npmrc` file in the nested one. Assuming that works for you, brilliant. It may not - don't worry. We'll talk about that in a second.
### `preinstall` script

In case you're wondering, the below **won't** work:
First the bad news. The below **won't** work:

```json
"scripts": {
"preinstall": "npm_config_registry=https://registry.npmjs.org npx --yes azdo-npm-auth"
},
```

Alas, it is not possible to get the `preinstall` script to ignore the project `.npmrc` file when it runs. As a consequence the `preinstall` script results in a `npm error code E401` and much sadness.

### `auth` script
Alas, it is not possible to get the `preinstall` script to ignore the project `.npmrc` file when it runs. As a consequence the `preinstall` script results in a `npm error code E401` and much sadness. Read more about E401s [here](#help-with-npm-error-code-e401).

Instead, we generally advise setting up a script like the one below:
It is still possible to integrate `azdo-npm-auth` in a `preinstall` script in your `package.json`:

```json
"scripts": {
"auth": "npm_config_registry=https://registry.npmjs.org npx --yes azdo-npm-auth"
"preinstall": "npx --yes azdo-npm-auth --config ./subdir-with-package-json/.npmrc"
},
```

And using `npm run auth` when a `npm error code E401` is encountered. We've called this script `auth` for the example - you can choose any name you like.
However, as you're probably noticing, this approach requires having multiple `package.json`s and only having the `.npmrc` file in the nested one. Assuming that works for you, brilliant. It may not - don't worry. We'll talk about that in a second.

The `--yes` flag above skips having npm challenge the user as to whether to download the package; useful in a CI environment.

## Prerequisites

If you would like `azdo-npm-auth` to acquire a token on your behalf, then it requires that your [Azure DevOps organisation is connected with your Azure account / Microsoft Entra ID](https://learn.microsoft.com/en-us/azure/devops/organizations/accounts/connect-organization-to-azure-ad?view=azure-devops). Then, assuming you are authenticated with Azure, it can acquire an Azure DevOps Personal Access Token on your behalf. To authenticate, run `az login`. [If you need to install the Azure CLI, follow these instructions](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli). It is not necessary to run `az login` if you are already authenticated with Azure.

If you would like to acquire a PAT token manually, there is a `--pat` option for that very circumstance.
If you would like to acquire a PAT token manually and supply it, there is a `--pat` option for that very need.

`azdo-npm-auth` requires the project `.npmrc` file exists in order that it can acquire the information to create the content of a user `.npmrc` file. There is an optional `config` parameter; if it is not supplied `azdo-npm-auth` will default to use the `.npmrc` in the current project directory. There will be instructions for creating a project `.npmrc` file in Azure DevOps, for connecting to the Azure Artifacts npm feed. A project `.npmrc` file will look something like this:

Expand Down Expand Up @@ -154,12 +117,53 @@ There is an official package named [`ado-npm-auth`](https://github.com/microsoft

`-e` | `--email` (`string`): Allows users to supply an explicit email - if not supplied, the example ADO value will be used

`-d` | `--daysToExpiry` (`number`): Allows users to supply an explicit number of days to expiry - if not supplied, then ADO will determine the expiry date

`-p` | `--pat` (`string`): Allows users to supply an explicit Personal Access Token (which must include the Packaging read and write scopes) - if not supplied, will be acquired from the Azure CLI

`-h` | `--help`: Show help

`-v` | `--version`: Show version

## Help with `npm error code E401`

When you are attempting to install from private feeds, npm will commonly error out with some form of `npm error code E401`.

This section exists to list some classic errors you might encounter when you try to `npm i`. Regardless of the error, the remedy is generally:

```shell
npm_config_registry=https://registry.npmjs.org npx azdo-npm-auth
```

### User `.npmrc` not found

When you have no user `.npmrc` file you'll encounter an error like this:

```shell
npm error code E401
npm error Unable to authenticate, your authentication token seems to be invalid.
npm error To correct this please try logging in again with:
npm error npm login
```

### Token used in user `.npmrc` file is expired

When your token has expired in your user `.npmrc` file you'll encounter an error like this:

```shell
npm error code E401
npm error Incorrect or missing password.
npm error If you were trying to login, change your password, create an
npm error authentication token or enable two-factor authentication then
npm error that means you likely typed your password in incorrectly.
npm error Please try again, or recover your password at:
npm error https://www.npmjs.com/forgot
npm error
npm error If you were doing some other operation then your saved credentials are
npm error probably out of date. To correct this please try logging in again with:
npm error npm login
```

## Credits

> 💙 This package was templated with [`create-typescript-app`](https://github.com/JoshuaKGoldberg/create-typescript-app).
4 changes: 4 additions & 0 deletions src/bin/help.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ describe("logHelpText", () => {
"
-e | --email (string): Allows users to supply an explicit email - if not supplied, the example ADO value will be used",
],
[
"
-d | --daysToExpiry (string): Allows users to supply an explicit number of days to expiry - if not supplied, then ADO will determine the expiry date",
],
[
"
-p | --pat (string): Allows users to supply an explicit Personal Access Token (which must include the Packaging read and write scopes) - if not supplied, will be acquired from the Azure CLI",
Expand Down
77 changes: 45 additions & 32 deletions src/bin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
parseProjectNpmrc,
writeNpmrc,
} from "../index.js";
import { logLine } from "../shared/cli/lines.js";
import { withSpinner } from "../shared/cli/spinners.js";
import { StatusCodes } from "../shared/codes.js";
import { options } from "../shared/options/args.js";
Expand All @@ -25,6 +24,15 @@ const operationMessage = (verb: string) =>
export async function bin(args: string[]) {
console.clear();

const logger = {
info: (message = "") => {
prompts.log.info(message);
},
error: (message = "") => {
prompts.log.error(message);
},
};

const version = await getVersionFromPackageJson();

const introPrompts = `${chalk.blueBright(`📦🔑 Welcome to`)} ${chalk.bgBlueBright.black(`azdo-npm-auth`)} ${chalk.blueBright(`${version}! 📦🔑`)}`;
Expand All @@ -47,37 +55,36 @@ export async function bin(args: string[]) {
}

prompts.intro(introPrompts);
logLine();

const mappedOptions = {
pat: values.pat,
config: values.config,
email: values.email,
daysToExpiry: values.daysToExpiry ? Number(values.daysToExpiry) : undefined,
};

const optionsParseResult = optionsSchema.safeParse(mappedOptions);

if (!optionsParseResult.success) {
logLine(
logger.error(
chalk.red(
fromZodError(optionsParseResult.error, {
issueSeparator: "\n - ",
}),
),
);
logLine();

prompts.cancel(operationMessage("failed"));
prompts.outro(outroPrompts);

return StatusCodes.Failure;
}

const { config, email, pat } = optionsParseResult.data;
const { config, email, pat, daysToExpiry } = optionsParseResult.data;

// TODO: this will prevent this file from running tests on the server after this - create an override parameter
if (ci.isCI) {
logLine(
logger.error(
`Detected that you are running on a CI server (${ci.name ?? ""}) and so will not generate a user .npmrc file`,
);
prompts.outro(outroPrompts);
Expand All @@ -87,22 +94,21 @@ export async function bin(args: string[]) {

prompts.log.info(`options:
- pat: ${pat ? "supplied" : "[NONE SUPPLIED - WILL ACQUIRE FROM AZURE]"}
- config: ${config ?? "[NONE SUPPLIED - WILL USE DEFAULT]"}
- email: ${email ?? "[NONE SUPPLIED - WILL USE DEFAULT]"}`);

const logger = {
info: prompts.log.info,
error: prompts.log.error,
};
- config: ${config ?? "[NONE SUPPLIED - WILL USE DEFAULT LOCATION]"}
- email: ${email ?? "[NONE SUPPLIED - WILL USE DEFAULT VALUE]"}
- daysToExpiry: ${daysToExpiry ? daysToExpiry.toLocaleString() : "[NONE SUPPLIED - API WILL DETERMINE EXPIRY]"}`);

try {
const parsedProjectNpmrc = await withSpinner(`Parsing project .npmrc`, () =>
parseProjectNpmrc({
npmrcPath: config
? path.resolve(config)
: path.resolve(process.cwd(), ".npmrc"),
logger,
}),
const parsedProjectNpmrc = await withSpinner(
`Parsing project .npmrc`,
logger,
(logger) =>
parseProjectNpmrc({
npmrcPath: config
? path.resolve(config)
: path.resolve(process.cwd(), ".npmrc"),
logger,
}),
);

const personalAccessToken = pat
Expand All @@ -111,22 +117,29 @@ export async function bin(args: string[]) {
token: pat,
},
}
: await withSpinner(`Creating Personal Access Token`, () =>
createPat({ logger, organisation: parsedProjectNpmrc.organisation }),
: await withSpinner(`Creating Personal Access Token`, logger, (logger) =>
createPat({
logger,
organisation: parsedProjectNpmrc.organisation,
daysToExpiry,
}),
);

const npmrc = await withSpinner(`Constructing user .npmrc`, () =>
Promise.resolve(
createUserNpmrc({
parsedProjectNpmrc,
email,
logger,
pat: personalAccessToken.patToken.token,
}),
),
const npmrc = await withSpinner(
`Constructing user .npmrc`,
logger,
(logger) =>
Promise.resolve(
createUserNpmrc({
parsedProjectNpmrc,
email,
logger,
pat: personalAccessToken.patToken.token,
}),
),
);

await withSpinner(`Writing user .npmrc`, () =>
await withSpinner(`Writing user .npmrc`, logger, (logger) =>
writeNpmrc({
npmrc,
logger,
Expand Down
Loading

0 comments on commit 8c23d20

Please sign in to comment.