Skip to content

Commit

Permalink
feat: noparse mode
Browse files Browse the repository at this point in the history
  • Loading branch information
johnnyreilly committed Dec 1, 2024
1 parent 97390b8 commit 11f2bab
Show file tree
Hide file tree
Showing 12 changed files with 223 additions and 24 deletions.
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,22 @@ If you're catering for Windows users that do not use Bash then you might need to
npx cross-env npm_config_registry=https://registry.npmjs.org npx azdo-npm-auth
```

### "No parse"-mode / manually supplying `organization`, `project`, and `feed`

If you would like to manually supply the `organization`, `project`, and `feed` values, you can do so. In this mode of operation `azdo-npm-auth` will not attempt to parse the `.npmrc` file, and will use the supplied values to build a user `.npmrc` file.

If your feed is project-scoped, you will need to supply the `project` value:

```shell
npm_config_registry=https://registry.npmjs.org npx azdo-npm-auth --organization johnnyreilly --project my-project --feed project-feed-name
```

If your feed is organization-scoped, you will **not** need to supply the `project` value:

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

## Integration with `package.json`

### Custom npm script
Expand Down Expand Up @@ -115,6 +131,12 @@ There is an official package named [`ado-npm-auth`](https://github.com/microsoft

`-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
Expand Down
12 changes: 12 additions & 0 deletions src/bin/help.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,18 @@ describe("logHelpText", () => {
"
-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",
Expand Down
51 changes: 38 additions & 13 deletions src/bin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { fromZodError } from "zod-validation-error";
import {
createPat,
createUserNpmrc,
makeParsedProjectNpmrc,
parseProjectNpmrc,
writeNpmrc,
} from "../index.js";
Expand Down Expand Up @@ -59,6 +60,9 @@ export async function bin(args: string[]) {
const mappedOptions = {
pat: values.pat,
config: values.config,
organization: values.organization,
project: values.project,
feed: values.feed,
email: values.email,
daysToExpiry: values.daysToExpiry ? Number(values.daysToExpiry) : undefined,
};
Expand All @@ -80,7 +84,8 @@ export async function bin(args: string[]) {
return StatusCodes.Failure;
}

const { config, email, pat, daysToExpiry } = optionsParseResult.data;
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
if (ci.isCI) {
Expand All @@ -92,23 +97,43 @@ export async function bin(args: string[]) {
return StatusCodes.Success;
}

prompts.log.info(`options:
const shouldParseProjectNpmrc = !organization && !feed;

const optionsSuffix = shouldParseProjectNpmrc
? `- config: ${config ?? "[NONE SUPPLIED - WILL USE DEFAULT LOCATION]"}`
: `- organization: ${organization ?? ""}
- project: ${project ?? ""}
- feed: ${feed ?? ""}
`;

prompts.log.info(
`options:
- pat: ${pat ? "supplied" : "[NONE SUPPLIED - WILL ACQUIRE FROM AZURE]"}
- 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]"}`);
- daysToExpiry: ${daysToExpiry ? daysToExpiry.toLocaleString() : "[NONE SUPPLIED - API WILL DETERMINE EXPIRY]"}
${optionsSuffix}`,
);

try {
const parsedProjectNpmrc = await withSpinner(
`Parsing project .npmrc`,
shouldParseProjectNpmrc
? `Parsing project .npmrc`
: "Making parsed project .npmrc",
logger,
(logger) =>
parseProjectNpmrc({
npmrcPath: config
? path.resolve(config)
: path.resolve(process.cwd(), ".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 ?? "",
});
},
);

const personalAccessToken = pat
Expand All @@ -120,7 +145,7 @@ export async function bin(args: string[]) {
: await withSpinner(`Creating Personal Access Token`, logger, (logger) =>
createPat({
logger,
organisation: parsedProjectNpmrc.organisation,
organisation: parsedProjectNpmrc.organization,
daysToExpiry,
}),
);
Expand Down
9 changes: 6 additions & 3 deletions src/createUserNpmrc.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { ParsedProjectNpmrc } from "./parseProjectNpmrc.js";
import type { Logger } from "./shared/cli/logger.js";
import type { ParsedProjectNpmrc } from "./types.js";

/**
* Make a user .npmrc file that looks a little like this:
Expand All @@ -26,8 +26,11 @@ export function createUserNpmrc({
}): string {
const base64EncodedPAT = Buffer.from(pat).toString("base64");

const { urlWithoutRegistryAtEnd, urlWithoutRegistryAtStart, organisation } =
parsedProjectNpmrc;
const {
urlWithoutRegistryAtEnd,
urlWithoutRegistryAtStart,
organization: organisation,
} = parsedProjectNpmrc;

const npmrc = `; begin auth token
${urlWithoutRegistryAtStart}:username=${organisation}
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from "./createPat.js";
export * from "./createUserNpmrc.js";
export * from "./makeParsedProjectNpmrc.js";
export * from "./parseProjectNpmrc.js";
export * from "./types.js";
export * from "./writeNpmrc.js";
50 changes: 50 additions & 0 deletions src/makeParsedProjectNpmrc.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { describe, expect, it } from "vitest";

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

describe("makeParsedProjectNpmrc", () => {
it("given no project it constructs an organisation feed ParsedProjectNpmrc", () => {
const result = makeParsedProjectNpmrc({
organization: "johnnyreilly",
feed: "npmrc-script-organization",
});
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("given a project it constructs a project feed ParsedProjectNpmrc", () => {
const result = makeParsedProjectNpmrc({
organization: "johnnyreilly",
project: "azure-static-web-apps",
feed: "npmrc-script-demo",
});
expect(result).toEqual({
organization: "johnnyreilly",
urlWithoutRegistryAtEnd:
"//pkgs.dev.azure.com/johnnyreilly/azure-static-web-apps/_packaging/npmrc-script-demo/npm/",
urlWithoutRegistryAtStart:
"//pkgs.dev.azure.com/johnnyreilly/azure-static-web-apps/_packaging/npmrc-script-demo/npm/registry/",
});
});
});
/*
registry=https://pkgs.dev.azure.com/investec/_packaging/investec-npm-packages/npm/registry
always-auth=true
registry=https://pkgs.dev.azure.com/johnnyreilly/ _packaging/npmrc-script-organization/npm/registry/
always-auth=true
registry=https://pkgs.dev.azure.com/johnnyreilly/azure-static-web-apps/_packaging/npmrc-script-demo/npm/registry/
always-auth=true
registry=https://pkgs.dev.azure.com/[ORGANIZATION]/[PROJECT]/_packaging/[FEED_NAME]/npm/registry/
*/
38 changes: 38 additions & 0 deletions src/makeParsedProjectNpmrc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import type { ParsedProjectNpmrc } from "./types.js";

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

/**
* Construct a ParsedProjectNpmrc object using the provided parameters
*/
export function makeParsedProjectNpmrc({
organization,
project,
feed,
logger = fallbackLogger,
}: {
organization: string;
project?: string | undefined;
feed: string;
logger?: Logger;
}): ParsedProjectNpmrc {
const urlWithoutRegistryAtEnd = project
? `//pkgs.dev.azure.com/${organization}/${project}/_packaging/${feed}/npm/`
: `//pkgs.dev.azure.com/${organization}/_packaging/${feed}/npm/`;

const urlWithoutRegistryAtStart = project
? `//pkgs.dev.azure.com/${organization}/${project}/_packaging/${feed}/npm/registry/`
: `//pkgs.dev.azure.com/${organization}/_packaging/${feed}/npm/registry/`;

// eg
// 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/",

logger.info(`Made:
- organization: ${organization}
- urlWithoutRegistryAtStart: ${urlWithoutRegistryAtStart}
- urlWithoutRegistryAtEnd: ${urlWithoutRegistryAtEnd}`);

return { urlWithoutRegistryAtStart, urlWithoutRegistryAtEnd, organization };
}
2 changes: 1 addition & 1 deletion src/parseProjectNpmrc.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ always-auth=true`);
npmrcPath: "/home/john/code/github/azdo-npm-auth/.npmrc",
});
expect(result).toEqual({
organisation: "johnnyreilly",
organization: "johnnyreilly",
urlWithoutRegistryAtEnd:
"//pkgs.dev.azure.com/johnnyreilly/_packaging/npmrc-script-organization/npm/",
urlWithoutRegistryAtStart:
Expand Down
14 changes: 7 additions & 7 deletions src/parseProjectNpmrc.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
import type { ParsedProjectNpmrc } from "./types.js";

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

export interface ParsedProjectNpmrc {
organisation: string;
urlWithoutRegistryAtEnd: string;
urlWithoutRegistryAtStart: string;
}

/**
* Read the project .npmrc file to acquire necessary info
*/
Expand Down Expand Up @@ -49,5 +45,9 @@ export async function parseProjectNpmrc({
- urlWithoutRegistryAtStart: ${urlWithoutRegistryAtStart}
- urlWithoutRegistryAtEnd: ${urlWithoutRegistryAtEnd}`);

return { urlWithoutRegistryAtStart, urlWithoutRegistryAtEnd, organisation };
return {
urlWithoutRegistryAtStart,
urlWithoutRegistryAtEnd,
organization: organisation,
};
}
36 changes: 36 additions & 0 deletions src/shared/options/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ export const options = {
type: "string",
},

organization: {
short: "o",
type: "string",
},

project: {
short: "r",
type: "string",
},

feed: {
short: "f",
type: "string",
},

email: {
short: "e",
type: "string",
Expand Down Expand Up @@ -50,6 +65,27 @@ export const allArgOptions: Record<ValidOption, DocOption> = {
docsSection: "optional",
},

organization: {
...options.organization,
description:
"The Azure DevOps organization - only required if not parsing from the .npmrc file",
docsSection: "optional",
},

project: {
...options.project,
description:
"The Azure DevOps project - only required if not parsing from the .npmrc file and the feed is project-scoped",
docsSection: "optional",
},

feed: {
...options.feed,
description:
"The Azure Artifacts feed - only required if not parsing from the .npmrc file",
docsSection: "optional",
},

email: {
...options.email,
description:
Expand Down
6 changes: 6 additions & 0 deletions src/shared/options/optionsSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ import { z } from "zod";
export const optionsSchema = z.object({
pat: z.string().optional(),
config: z.string().optional(),

// registry=https://pkgs.dev.azure.com/[ORGANIZATION]/[PROJECT]/_packaging/[FEED_NAME]/npm/registry/
organization: z.string().optional(),
project: z.string().optional(),
feed: z.string().optional(),

email: z.string().optional(),
daysToExpiry: z.number().optional(),
});
6 changes: 6 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,9 @@ export interface TokenResult {
};
patTokenError: string;
}

export interface ParsedProjectNpmrc {
organization: string;
urlWithoutRegistryAtEnd: string;
urlWithoutRegistryAtStart: string;
}

0 comments on commit 11f2bab

Please sign in to comment.