Skip to content

Commit

Permalink
BREAKING CHANGE: registry feature and change shortcuts
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

- [ ] Addresses an existing open issue: fixes #000
- [ ] 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.
-->
This PR adds the `--registry` parameter / feature and changes the short
alias for some other parameters.
  • Loading branch information
johnnyreilly authored Dec 7, 2024
1 parent 8042232 commit b348db5
Show file tree
Hide file tree
Showing 13 changed files with 177 additions and 82 deletions.
29 changes: 12 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,23 +131,18 @@ There is an official package named [`ado-npm-auth`](https://github.com/microsoft

## Options

`-c` | `--config` (`string`): The location of the .npmrc file. Defaults to current directory

`-o` | `--organization` (`string`): The Azure DevOps organization - only required if not parsing from the .npmrc file

`-r` | `--project` (`string`): The Azure DevOps project - only required if not parsing from the .npmrc file and the feed is project-scoped

`-f` | `--feed` (`string`): The Azure Artifacts feed - only required if not parsing from the .npmrc file

`-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
| Short | Long | Type | Description |
| ----- | ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `-c` | `--config` | `string` | The location of the .npmrc file. Defaults to current directory |
| `-o` | `--organization` | `string` | The Azure DevOps organization - only required if not parsing from the .npmrc file |
| `-p` | `--project` | `string` | The Azure DevOps project - only required if not parsing from the .npmrc file and the feed is project-scoped |
| `-f` | `--feed` | `string` | The Azure Artifacts feed - only required if not parsing from the .npmrc file |
| `-r` | `--registry` | `string` | The registry to use, eg 'https://pkgs.dev.azure.com/johnnyreilly/_packaging/npmrc-script-organization/npm/registry/' - only required if not parsing from the .npmrc file |
| `-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 |
| `-t` | `--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`

Expand Down
8 changes: 6 additions & 2 deletions src/bin/help.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,16 @@ describe("logHelpText", () => {
],
[
"
-r | --project (string): The Azure DevOps project - only required if not parsing from the .npmrc file and the feed is project-scoped",
-p | --project (string): The Azure DevOps project - only required if not parsing from the .npmrc file and the feed is project-scoped",
],
[
"
-f | --feed (string): The Azure Artifacts feed - only required if not parsing from the .npmrc file",
],
[
"
-r | --registry (string): The registry to use, eg 'https://pkgs.dev.azure.com/johnnyreilly/_packaging/npmrc-script-organization/npm/registry/' - only required if not parsing from the .npmrc file",
],
[
"
-e | --email (string): Allows users to supply an explicit email - if not supplied, the example ADO value will be used",
Expand All @@ -76,7 +80,7 @@ describe("logHelpText", () => {
],
[
"
-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",
-t | --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
73 changes: 47 additions & 26 deletions src/bin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import { fromZodError } from "zod-validation-error";
import {
createPat,
createUserNpmrc,
makeParsedProjectNpmrc,
parseProjectNpmrc,
projectNpmrcMake,
projectNpmrcParse,
writeNpmrc,
} from "../index.js";
import { projectNpmrcRegistry } from "../projectNpmrcRegistry.js";
import { withSpinner } from "../shared/cli/spinners.js";
import { StatusCodes } from "../shared/codes.js";
import { options } from "../shared/options/args.js";
Expand Down Expand Up @@ -63,6 +64,7 @@ export async function bin(args: string[]) {
organization: values.organization,
project: values.project,
feed: values.feed,
registry: values.registry,
email: values.email,
daysToExpiry: values.daysToExpiry ? Number(values.daysToExpiry) : undefined,
};
Expand All @@ -84,10 +86,18 @@ export async function bin(args: string[]) {
return StatusCodes.Failure;
}

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

// TODO: this will prevent this file from running tests on the server after this - create an override parameter
const {
config,
organization,
project,
feed,
registry,
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) {
logger.error(
`Detected that you are running on a CI server (${ci.name ?? ""}) and so will not generate a user .npmrc file`,
Expand All @@ -97,11 +107,18 @@ export async function bin(args: string[]) {
return StatusCodes.Success;
}

const shouldParseProjectNpmrc = !organization && !feed;

const optionsSuffix = shouldParseProjectNpmrc
? `- config: ${config ?? "[NONE SUPPLIED - WILL USE DEFAULT LOCATION]"}`
: `- organization: ${organization ?? ""}
const projectNpmrcMode = registry
? "registry"
: !organization && !feed
? "parse"
: "make";

const optionsSuffix =
projectNpmrcMode === "registry"
? `- registry: ${registry ?? ""}`
: projectNpmrcMode === "parse"
? `- config: ${config ?? "[NONE SUPPLIED - WILL USE DEFAULT LOCATION]"}`
: `- organization: ${organization ?? ""}
- project: ${project ?? ""}
- feed: ${feed ?? ""}
`;
Expand All @@ -116,23 +133,27 @@ ${optionsSuffix}`,

try {
const parsedProjectNpmrc = await withSpinner(
shouldParseProjectNpmrc
? `Parsing project .npmrc`
: "Making parsed project .npmrc",
projectNpmrcMode === "registry"
? `Using supplied registry`
: projectNpmrcMode === "parse"
? `Parsing project .npmrc`
: "Making parsed project .npmrc",
logger,
async (logger) => {
return shouldParseProjectNpmrc
? await parseProjectNpmrc({
npmrcPath: config
? path.resolve(config)
: path.resolve(process.cwd(), ".npmrc"),
logger,
})
: makeParsedProjectNpmrc({
organization: organization ?? "",
project,
feed: feed ?? "",
});
return projectNpmrcMode === "registry"
? projectNpmrcRegistry({ registry: registry ?? "", logger })
: projectNpmrcMode === "parse"
? await projectNpmrcParse({
npmrcPath: config
? path.resolve(config)
: path.resolve(process.cwd(), ".npmrc"),
logger,
})
: projectNpmrcMake({
organization: organization ?? "",
project,
feed: feed ?? "",
});
},
);

Expand Down
5 changes: 3 additions & 2 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from "./createPat.js";
export * from "./createUserNpmrc.js";
export * from "./makeParsedProjectNpmrc.js";
export * from "./parseProjectNpmrc.js";
export * from "./projectNpmrcMake.js";
export * from "./projectNpmrcParse.js";
export * from "./projectNpmrcRegistry.js";
export * from "./types.js";
export * from "./writeNpmrc.js";
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { describe, expect, it } from "vitest";

import { makeParsedProjectNpmrc } from "./makeParsedProjectNpmrc.js";
import { projectNpmrcMake } from "./projectNpmrcMake.js";

describe("makeParsedProjectNpmrc", () => {
describe("projectNpmrcMake", () => {
it("given no project it constructs an organisation feed ParsedProjectNpmrc", () => {
const result = makeParsedProjectNpmrc({
const result = projectNpmrcMake({
organization: "johnnyreilly",
feed: "npmrc-script-organization",
});
Expand All @@ -18,7 +18,7 @@ describe("makeParsedProjectNpmrc", () => {
});

it("given a project it constructs a project feed ParsedProjectNpmrc", () => {
const result = makeParsedProjectNpmrc({
const result = projectNpmrcMake({
organization: "johnnyreilly",
project: "azure-static-web-apps",
feed: "npmrc-script-demo",
Expand Down
2 changes: 1 addition & 1 deletion src/makeParsedProjectNpmrc.ts → src/projectNpmrcMake.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { fallbackLogger, type Logger } from "./shared/cli/logger.js";
/**
* Construct a ParsedProjectNpmrc object using the provided parameters
*/
export function makeParsedProjectNpmrc({
export function projectNpmrcMake({
organization,
project,
feed,
Expand Down
10 changes: 5 additions & 5 deletions src/parseProjectNpmrc.test.ts → src/projectNpmrcParse.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { describe, expect, it, vi } from "vitest";

import { parseProjectNpmrc } from "./parseProjectNpmrc.js";
import { projectNpmrcParse } from "./projectNpmrcParse.js";

const mockReadFile = vi.fn();

Expand All @@ -10,12 +10,12 @@ vi.mock("./shared/readFileSafe.js", () => ({
},
}));

describe("parseProjectNpmrc", () => {
describe("projectNpmrcParse", () => {
it("outputs the expected structure on successful parse", async () => {
mockReadFile.mockResolvedValue(`registry=https://pkgs.dev.azure.com/johnnyreilly/_packaging/npmrc-script-organization/npm/registry/
always-auth=true`);
const result = await parseProjectNpmrc({
const result = await projectNpmrcParse({
npmrcPath: "/home/john/code/github/azdo-npm-auth/.npmrc",
});
expect(result).toEqual({
Expand All @@ -32,7 +32,7 @@ always-auth=true`);
mockReadFile.mockResolvedValue(`registry=https://pkgs.dev.azure.com/johnnyreilly/_packaging/npmrc-script-organization/npm/registry
always-auth=true`);
const result = await parseProjectNpmrc({
const result = await projectNpmrcParse({
npmrcPath: "/home/john/code/github/azdo-npm-auth/.npmrc",
});
expect(result).toEqual({
Expand All @@ -47,7 +47,7 @@ always-auth=true`);
it("errors on invalid content", async () => {
mockReadFile.mockResolvedValue(`stuff`);
await expect(() =>
parseProjectNpmrc({
projectNpmrcParse({
npmrcPath: "/home/john/code/github/azdo-npm-auth/.npmrc",
}),
).rejects.toThrowError("Unable to extract information from project .npmrc");
Expand Down
28 changes: 5 additions & 23 deletions src/parseProjectNpmrc.ts → src/projectNpmrcParse.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import type { ParsedProjectNpmrc } from "./types.js";

import { makeFromRegistry } from "./projectNpmrcShared.js";
import { fallbackLogger, type Logger } from "./shared/cli/logger.js";
import { readFileSafe } from "./shared/readFileSafe.js";

/**
* Read the project .npmrc file to acquire necessary info
*/
export async function parseProjectNpmrc({
export async function projectNpmrcParse({
npmrcPath,
logger = fallbackLogger,
}: {
Expand All @@ -28,26 +29,7 @@ export async function parseProjectNpmrc({
throw new Error(`Unable to extract information from project .npmrc`);
}

const urlWithoutRegistryAtStart = match[0]
.replace("registry=https:", "")
.trim();
const urlWithoutRegistryAtEnd = urlWithoutRegistryAtStart.replace(
/registry\/$/,
"",
);
// extract the organisation which we will use as the username
// not sure why this is the case, but this is the behaviour
// defined in ADO
const organisation = urlWithoutRegistryAtEnd.split("/")[3];

logger.info(`Parsed:
- organisation: ${organisation}
- urlWithoutRegistryAtStart: ${urlWithoutRegistryAtStart}
- urlWithoutRegistryAtEnd: ${urlWithoutRegistryAtEnd}`);

return {
urlWithoutRegistryAtStart,
urlWithoutRegistryAtEnd,
organization: organisation,
};
const registry = match[0].replace("registry=", "").trim();

return makeFromRegistry({ registry, logger });
}
27 changes: 27 additions & 0 deletions src/projectNpmrcRegistry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { describe, expect, it } from "vitest";

import { projectNpmrcRegistry } from "./projectNpmrcRegistry.js";

describe("projectNpmrcRegistry", () => {
it("outputs the expected structure on successful parse", () => {
const result = projectNpmrcRegistry({
registry:
"https://pkgs.dev.azure.com/johnnyreilly/_packaging/npmrc-script-organization/npm/registry/",
});
expect(result).toEqual({
organization: "johnnyreilly",
urlWithoutRegistryAtEnd:
"//pkgs.dev.azure.com/johnnyreilly/_packaging/npmrc-script-organization/npm/",
urlWithoutRegistryAtStart:
"//pkgs.dev.azure.com/johnnyreilly/_packaging/npmrc-script-organization/npm/registry/",
});
});

it("errors on invalid content", () => {
expect(() =>
projectNpmrcRegistry({
registry: "stuff",
}),
).toThrowError("Unable to extract information");
});
});
19 changes: 19 additions & 0 deletions src/projectNpmrcRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type { ParsedProjectNpmrc } from "./types.js";

import { makeFromRegistry } from "./projectNpmrcShared.js";
import { fallbackLogger, type Logger } from "./shared/cli/logger.js";

/**
* Parse the registry parameter to acquire necessary info
*/
export function projectNpmrcRegistry({
registry,
logger = fallbackLogger,
}: {
registry: string;
logger?: Logger;
}): ParsedProjectNpmrc {
logger.info(`Parsing from registry: ${registry}`);

return makeFromRegistry({ registry, logger });
}
33 changes: 33 additions & 0 deletions src/projectNpmrcShared.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import type { Logger } from "./shared/cli/logger.js";

export function makeFromRegistry({
registry,
logger,
}: {
registry: string;
logger: Logger;
}) {
if (!registry.startsWith("https:")) {
throw new Error("Unable to extract information");
}
const urlWithoutRegistryAtStart = registry.replace("https:", "");
const urlWithoutRegistryAtEnd = urlWithoutRegistryAtStart.replace(
/registry\/$/,
"",
);
// extract the organisation which we will use as the username
// not sure why this is the case, but this is the behaviour
// defined in ADO
const organisation = urlWithoutRegistryAtEnd.split("/")[3];

logger.info(`Parsed:
- organisation: ${organisation}
- urlWithoutRegistryAtStart: ${urlWithoutRegistryAtStart}
- urlWithoutRegistryAtEnd: ${urlWithoutRegistryAtEnd}`);

return {
urlWithoutRegistryAtStart,
urlWithoutRegistryAtEnd,
organization: organisation,
};
}
Loading

0 comments on commit b348db5

Please sign in to comment.