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

feat: Throw an Error if package.json has duplicate "repository" key #1656

Merged
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Expand Up @@ -229,7 +229,7 @@ Before pushing your code changes make sure there are no linting errors with `npm

### Tests

Running the integration test requires you to install [Docker](https://docs.docker.com/engine/installation) on your machine.
Running the integration test requires you to install [Docker](https://docs.docker.com/engine/installation) on your machine. Note: the tests assume that running `git init` will create a `master` branch by default. If your local `git` is configured differently (see [`init.defaultBranch`](https://github.blog/2020-07-27-highlights-from-git-2-28/#introducing-init-defaultbranch)), change it temporarily when running the tests.

All the [semantic-release](https://github.com/semantic-release) repositories use [AVA](https://github.com/avajs/ava) for writing and running tests.

Expand Down
4 changes: 4 additions & 0 deletions lib/definitions/errors.js
Expand Up @@ -29,6 +29,10 @@ Please make sure to add the \`repositoryUrl\` to the [semantic-release configura
'docs/usage/configuration.md'
)}).`,
}),
EDUPLICATEREPOSITORYKEY: ({packageJsonPath}) => ({
message: 'Duplicate `"repository"` key in package.json.',
details: `Your package.json file at ${packageJsonPath} has more than one "repository" keys.`,
}),
EGITNOPERMISSION: ({options: {repositoryUrl}, branch: {name}}) => ({
message: 'Cannot push to the Git repository.',
details: `**semantic-release** cannot push the version tag to the branch \`${name}\` on the remote Git repository with URL \`${repositoryUrl}\`.
Expand Down
23 changes: 19 additions & 4 deletions lib/get-config.js
@@ -1,12 +1,15 @@
const {readFile} = require('fs').promises;
const {castArray, pickBy, isNil, isString, isPlainObject} = require('lodash');
const readPkgUp = require('read-pkg-up');
const findPkgUp = require('pkg-up');
const {cosmiconfig} = require('cosmiconfig');
const resolveFrom = require('resolve-from');
const findDuplicatedPropertyKeys = require('find-duplicated-property-keys');
const debug = require('debug')('semantic-release:config');
const {repoUrl} = require('./git');
const PLUGINS_DEFINITIONS = require('./definitions/plugins');
const plugins = require('./plugins');
const {validatePlugin, parseConfig} = require('./plugins/utils');
const getError = require('./get-error');

const CONFIG_NAME = 'release';
const CONFIG_FILES = [
Expand Down Expand Up @@ -74,7 +77,7 @@ module.exports = async (context, cliOptions) => {
{name: 'beta', prerelease: true},
{name: 'alpha', prerelease: true},
],
repositoryUrl: (await pkgRepoUrl({normalize: false, cwd})) || (await repoUrl({cwd, env})),
repositoryUrl: (await pkgRepoUrl({cwd})) || (await repoUrl({cwd, env})),
tagFormat: `v\${version}`,
plugins: [
'@semantic-release/commit-analyzer',
Expand All @@ -93,6 +96,18 @@ module.exports = async (context, cliOptions) => {
};

async function pkgRepoUrl(options) {
const {packageJson} = (await readPkgUp(options)) || {};
return packageJson && (isPlainObject(packageJson.repository) ? packageJson.repository.url : packageJson.repository);
const packageJsonPath = await findPkgUp(options);
if (!packageJsonPath) return;

const packageJsonString = await readFile(packageJsonPath, 'utf-8');
const result = findDuplicatedPropertyKeys(packageJsonString);

if (result.length > 0) {
throw getError('EDUPLICATEREPOSITORYKEY', {packageJsonPath});
}

const {repository} = require(packageJsonPath);
if (!repository) return;

return isPlainObject(repository) ? repository.url : repository;
}
6 changes: 4 additions & 2 deletions package.json
Expand Up @@ -31,6 +31,7 @@
"env-ci": "^5.0.0",
"execa": "^4.0.0",
"figures": "^3.0.0",
"find-duplicated-property-keys": "^1.2.2",
"find-versions": "^3.0.0",
"get-stream": "^5.0.0",
"git-log-parser": "^1.2.0",
Expand All @@ -42,7 +43,7 @@
"micromatch": "^4.0.2",
"p-each-series": "^2.1.0",
"p-reduce": "^2.0.0",
"read-pkg-up": "^7.0.0",
"pkg-up": "^3.1.0",
"resolve-from": "^5.0.0",
"semver": "^7.3.2",
"semver-diff": "^3.1.1",
Expand Down Expand Up @@ -128,7 +129,8 @@
"prettier": true,
"space": true,
"rules": {
"unicorn/string-content": "off"
"unicorn/string-content": "off",
"node/no-unsupported-features/node-builtins": "off"
}
}
}
18 changes: 18 additions & 0 deletions test/get-config.test.js
Expand Up @@ -516,3 +516,21 @@ test('Throw an Error if one of the shareable config cannot be found', async (t)
code: 'MODULE_NOT_FOUND',
});
});

test('Throw an Error if package.json has duplicate "repository" key', async (t) => {
// Create a git repository, set the current working directory at the root of the repo
const {cwd} = await gitRepo();

// Create package.json with duplicate "repository" key
await writeFile(
path.resolve(cwd, 'package.json'),
`{
"repository": "https://github.com/octocat/repository",
"repository": "https://github.com/octocat/repository"
}`
);

const error = await t.throwsAsync(t.context.getConfig({cwd}));
t.is(error.code, 'EDUPLICATEREPOSITORYKEY');
t.is(error.message, 'Duplicate `"repository"` key in package.json.');
});