Skip to content
Open
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This action for [Changesets](https://github.com/atlassian/changesets) creates a
- version - The command to update version, edit CHANGELOG, read and delete changesets. Default to `changeset version` if not provided
- commit - The commit message to use. Default to `Version Packages`
- title - The pull request title. Default to `Version Packages`
- skipNpmrcCheck - Check if there is a user-defined `.npmrc` file with a proper npm registry and an `authToken`. If not, a default `.npmrc` file is created.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Andarist I added this option to opt-out of the .npmrc checks.

I find it useful in case I don't need the use the default npm registry (for example when using github packages).

What do you think?


### Outputs

Expand Down
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ inputs:
title:
description: The pull request title. Default to `Version Packages`
required: false
skipNpmrcCheck:
description: >
Check if there is a user-defined `.npmrc` file with a proper npm registry and an `authToken`. If not, a default `.npmrc` file is created.
required: false
outputs:
published:
description: A boolean value to indicate whether a publishing is happened or not
Expand Down
200 changes: 172 additions & 28 deletions dist/index.js

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@
"@changesets/read": "^0.4.6",
"@manypkg/get-packages": "^1.1.1",
"@types/fs-extra": "^8.0.0",
"@types/ini": "1.3.31",
"@types/jest": "^24.0.18",
"@types/node": "^12.7.1",
"@types/semver": "^6.0.2",
"babel-jest": "^24.9.0",
"fs-extra": "^8.1.0",
"husky": "^3.0.3",
"ini": "^2.0.0",
"jest": "^24.9.0",
"mdast-util-to-string": "^1.0.6",
"remark-parse": "^7.0.1",
Expand Down
32 changes: 5 additions & 27 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as core from "@actions/core";
import fs from "fs-extra";
import * as ini from "ini";
import * as gitUtils from "./gitUtils";
import * as npmUtils from "./npmUtils";
import { runPublish, runVersion } from "./run";
import readChangesetState from "./readChangesetState";

Expand Down Expand Up @@ -42,33 +44,9 @@ const getOptionalInput = (name: string) => core.getInput(name) || undefined;
"No changesets found, attempting to publish any unpublished packages to npm"
);

let userNpmrcPath = `${process.env.HOME}/.npmrc`;
if (fs.existsSync(userNpmrcPath)) {
console.log("Found existing user .npmrc file");
const userNpmrcContent = await fs.readFile(userNpmrcPath, "utf8");
const authLine = userNpmrcContent.split("\n").find((line) => {
// check based on https://github.com/npm/cli/blob/8f8f71e4dd5ee66b3b17888faad5a7bf6c657eed/test/lib/adduser.js#L103-L105
return /^\s*\/\/registry\.npmjs\.org\/:[_-]authToken=/i.test(line);
});
if (authLine) {
console.log(
"Found existing auth token for the npm registry in the user .npmrc file"
);
} else {
console.log(
"Didn't find existing auth token for the npm registry in the user .npmrc file, creating one"
);
fs.appendFileSync(
userNpmrcPath,
`\n//registry.npmjs.org/:_authToken=${process.env.NPM_TOKEN}\n`
);
}
} else {
console.log("No user .npmrc file found, creating one");
fs.writeFileSync(
userNpmrcPath,
`//registry.npmjs.org/:_authToken=${process.env.NPM_TOKEN}\n`
);
const skipNpmrcCheck = getOptionalInput("skipNpmrcCheck");
if (skipNpmrcCheck !== "true") {
npmUtils.checkNpmConfig();
}

const result = await runPublish({
Expand Down
107 changes: 107 additions & 0 deletions src/npmUtils.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import fs from "fs";
import path from "path";
import * as ini from "ini";
import fixtures from "fixturez";
import { checkNpmConfig } from "./npmUtils";

const f = fixtures(__dirname);
const authToken = "npm_abc";

describe("checkNpmConfig", () => {
let tmpDir: string;
let npmrcPath: string;

beforeEach(() => {
tmpDir = f.temp();
npmrcPath = path.join(tmpDir, ".npmrc");
});

describe("when .npmrc exists", () => {
describe("when authToken is defined", () => {
beforeEach(() => {
fs.writeFileSync(
npmrcPath,
ini.stringify({
email: "npm@company.com",
"//registry.npmjs.org/:_authToken": authToken,
})
);
});
test("should not change the .npmrc file", () => {
checkNpmConfig({ HOME: tmpDir });

const npmConfig = ini.parse(fs.readFileSync(npmrcPath, "utf-8"));
expect(npmConfig).toMatchInlineSnapshot(`
Object {
"//registry.npmjs.org/:_authToken": "npm_abc",
"email": "npm@company.com",
}
`);
});
});
describe("when authToken is not defined", () => {
beforeEach(() => {
fs.writeFileSync(
npmrcPath,
ini.stringify({
email: "npm@company.com",
})
);
});
describe("when NPM_TOKEN environment variable is not defined", () => {
test("it should log a warning", () => {
console.warn = jest.fn();
checkNpmConfig({
HOME: tmpDir,
NPM_TOKEN: undefined,
});
expect(console.warn).toHaveBeenCalledWith(
"Missing `NPM_TOKEN` environment variable, skipping update of .npmrc file."
);
});
});
test("should inject NPM_TOKEN value in .npmrc file", () => {
checkNpmConfig({
HOME: tmpDir,
NPM_TOKEN: authToken,
});

const npmConfig = ini.parse(fs.readFileSync(npmrcPath, "utf-8"));
expect(npmConfig).toMatchInlineSnapshot(`
Object {
"//registry.npmjs.org/:_authToken": "npm_abc",
"email": "npm@company.com",
}
`);
});
});
});

describe("when .npmrc does not exist", () => {
describe("when NPM_TOKEN environment variable is not defined", () => {
test("it should log a warning", () => {
console.warn = jest.fn();
checkNpmConfig({
HOME: tmpDir,
NPM_TOKEN: undefined,
});
expect(console.warn).toHaveBeenCalledWith(
"Missing `NPM_TOKEN` environment variable, skipping creation of .npmrc file."
);
});
});
test("should create a new .npmrc config", () => {
checkNpmConfig({
HOME: tmpDir,
NPM_TOKEN: authToken,
});

const npmConfig = ini.parse(fs.readFileSync(npmrcPath, "utf-8"));
expect(npmConfig).toMatchInlineSnapshot(`
Object {
"//registry.npmjs.org/:_authToken": "npm_abc",
}
`);
});
});
});
59 changes: 59 additions & 0 deletions src/npmUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import fs from "fs";
import * as ini from "ini";

// https://docs.npmjs.com/using-private-packages-in-a-ci-cd-workflow#create-and-check-in-a-project-specific-npmrc-file
const npmRegistryTokenKey = "//registry.npmjs.org/:_authToken";
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did some research and couldn't find a case where the value isn't exactly like this (for the npm registry). Therefore I think we can omit the regex and simply do a 1:1 check.


export const checkNpmConfig = (
// Allow to inject a custom object, useful in tests.
processEnv = process.env
) => {
const userNpmrcPath = `${processEnv.HOME}/.npmrc`;

if (fs.existsSync(userNpmrcPath)) {
console.log(`Found existing user .npmrc file at ${userNpmrcPath}.`);

// Parse the `.npmrc` content using the `npm/ini` package.
const npmConfig = ini.parse(fs.readFileSync(userNpmrcPath, "utf-8"));
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use ini.parse to take care of parsing the config (all whitespaces are stripped out)


let hasAuthTokenForDefaultNpmRegistry = false;
// Check if there is at least a registry defined with an `_authToken`.
for (const [key, value] of Object.entries(npmConfig)) {
if (npmRegistryTokenKey === key && Boolean(value)) {
hasAuthTokenForDefaultNpmRegistry = true;
}
}

if (hasAuthTokenForDefaultNpmRegistry) {
console.log(
"The .npmrc file has an entry for the npm registry with an authToken defined."
);
} else {
console.log(
"The .npmrc file does not have an authToken defined, appending one using the `NPM_TOKEN` environment variable..."
);
if (processEnv.NPM_TOKEN) {
npmConfig["//registry.npmjs.org/:_authToken"] = processEnv.NPM_TOKEN;
fs.writeFileSync(userNpmrcPath, ini.stringify(npmConfig));
} else {
console.warn(
"Missing `NPM_TOKEN` environment variable, skipping update of .npmrc file."
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So now in case the NPM_TOKEN is missing, we simply log a warning.

);
}
}
} else {
console.log("No user .npmrc file found, creating one...");
if (processEnv.NPM_TOKEN) {
fs.writeFileSync(
userNpmrcPath,
ini.stringify({
"//registry.npmjs.org/:_authToken": processEnv.NPM_TOKEN,
})
);
} else {
console.warn(
"Missing `NPM_TOKEN` environment variable, skipping creation of .npmrc file."
);
}
}
};
10 changes: 10 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2265,6 +2265,11 @@
dependencies:
"@types/node" "*"

"@types/ini@1.3.31":
version "1.3.31"
resolved "https://registry.yarnpkg.com/@types/ini/-/ini-1.3.31.tgz#c78541a187bd88d5c73e990711c9d85214800d1b"
integrity sha512-8ecxxaG4AlVEM1k9+BsziMw8UsX0qy3jYI1ad/71RrDZ+rdL6aZB0wLfAuflQiDhkD5o4yJ0uPK3OSUic3fG0w==

"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff"
Expand Down Expand Up @@ -4940,6 +4945,11 @@ ini@^1.3.4, ini@~1.3.0:
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==

ini@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5"
integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==

invariant@^2.2.2, invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
Expand Down