Skip to content

Commit

Permalink
feat: add support for config files (#74)
Browse files Browse the repository at this point in the history
  • Loading branch information
JustinBeckwith committed Jun 22, 2019
1 parent 1784b38 commit 2c118b3
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 12 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ node_modules/
package-lock.json
.nyc_output
build/
coverage
24 changes: 24 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ $ linkinator LOCATION [ --arguments ]
Required. Either the URL or the path on disk to check for broken links.

Flags

--config
Path to the config file to use. Looks for `linkinator.config.json` by default.

--recurse, -r
Recurively follow links on the same root domain.

Expand Down Expand Up @@ -91,6 +95,26 @@ Maybe you're going to pipe the output to another program. Use the `--format` op
$ linkinator ./docs --format CSV
```

### Configuration file
You can pass options directly to the `linkinator` CLI, or you can define a config file. By default, `linkinator` will look for a `linkinator.config.json` file in the current working directory.

All options are optional. It should look like this:

```json
{
"format": "json",
"recurse": true,
"silent": true,
"skip": "www.googleapis.com"
}
```

To load config settings outside the CWD, you can pass the `--config` flag to the `linkinator` CLI:

```sh
$ linkinator --config /some/path/your-config.json
```

## API Usage

#### linkinator.check(options)
Expand Down
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
"pretest": "npm run compile",
"prepare": "npm run compile",
"compile": "tsc -p .",
"test": "nyc mocha build/test",
"test": "c8 mocha build/test",
"fix": "gts fix",
"codecov": "nyc report --reporter=json && codecov -f coverage/*.json",
"codecov": "c8 report --reporter=json && codecov -f coverage/*.json",
"lint": "gts check"
},
"dependencies": {
Expand All @@ -40,11 +40,12 @@
"@types/server-destroy": "^1.0.0",
"@types/sinon": "^7.0.6",
"@types/update-notifier": "^2.5.0",
"assert-rejects": "^1.0.0",
"c8": "^5.0.1",
"codecov": "^3.2.0",
"gts": "^1.0.0",
"mocha": "^6.1.4",
"nock": "^10.0.6",
"nyc": "^14.0.0",
"semantic-release": "^15.13.15",
"sinon": "^7.2.3",
"source-map-support": "^0.5.10",
Expand All @@ -68,7 +69,7 @@
"link",
"checker"
],
"nyc": {
"c8": {
"exclude": [
"build/test"
]
Expand Down
25 changes: 17 additions & 8 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import * as updateNotifier from 'update-notifier';
import chalk from 'chalk';
import { LinkChecker, LinkState, LinkResult, CheckOptions } from './index';
import { promisify } from 'util';
import { Flags, getConfig } from './config';
const toCSV = promisify(require('jsonexport'));

const pkg = require('../../package.json');
Expand All @@ -21,6 +22,9 @@ const cli = meow(
Required. Either the URL or the path on disk to check for broken links.
Flags
--config
Path to the config file to use. Looks for \`linkinator.config.json\` by default.
--recurse, -r
Recurively follow links on the same root domain.
Expand All @@ -45,22 +49,24 @@ const cli = meow(
`,
{
flags: {
recurse: { type: 'boolean', alias: 'r' },
config: { type: 'string' },
recurse: { type: 'boolean', alias: 'r', default: undefined },
skip: { type: 'string', alias: 's' },
format: { type: 'string', alias: 'f' },
silent: { type: 'boolean' },
silent: { type: 'boolean', default: undefined },
},
}
);

let flags: { [index: string]: string };
let flags: Flags;

async function main() {
if (cli.input.length !== 1) {
cli.showHelp();
return;
}
flags = cli.flags;
flags = await getConfig(cli.flags);

const start = Date.now();

if (!flags.silent) {
Expand Down Expand Up @@ -93,10 +99,13 @@ async function main() {
}
log(` ${state} ${chalk.gray(link.url)}`);
});
const opts: CheckOptions = { path: cli.input[0], recurse: cli.flags.recurse };
if (cli.flags.skip) {
const skips = cli.flags.skip as string;
opts.linksToSkip = skips.split(' ').filter(x => !!x);
const opts: CheckOptions = { path: cli.input[0], recurse: flags.recurse };
if (flags.skip) {
if (typeof flags.skip === 'string') {
opts.linksToSkip = flags.skip.split(' ').filter(x => !!x);
} else if (Array.isArray(flags.skip)) {
opts.linksToSkip = flags.skip;
}
}
const result = await checker.check(opts);
log();
Expand Down
45 changes: 45 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import * as fs from 'fs';
import { promisify } from 'util';

const readFile = promisify(fs.readFile);

export interface Flags {
config?: string;
recurse?: boolean;
skip?: string;
format?: string;
silent?: boolean;
}

export async function getConfig(flags: Flags) {
// check to see if a config file path was passed
const configPath = flags.config || 'linkinator.config.json';
let configData: string | undefined;
try {
configData = await readFile(configPath, { encoding: 'utf8' });
} catch (e) {
if (flags.config) {
console.error(`Unable to find config file ${flags.config}`);
throw e;
}
}

let config: Flags = {};
if (configData) {
config = JSON.parse(configData);
}
// `meow` is set up to pass boolean flags as `undefined` if not passed.
// copy the struct, and delete properties that are `undefined` so the merge
// doesn't blast away config level settings.
const strippedFlags = Object.assign({}, flags);
Object.entries(strippedFlags).forEach(([key, value]) => {
if (typeof value === 'undefined') {
delete (strippedFlags as { [index: string]: {} })[key];
}
});

// combine the flags passed on the CLI with the flags in the config file,
// with CLI flags getting precedence
config = Object.assign({}, config, strippedFlags);
return config;
}
6 changes: 6 additions & 0 deletions test/fixtures/config/linkinator.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"format": "json",
"recurse": true,
"silent": true,
"skip": "馃尦"
}
48 changes: 48 additions & 0 deletions test/test.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import * as assert from 'assert';
import * as path from 'path';
import { getConfig, Flags } from '../src/config';
const assertRejects = require('assert-rejects');

describe('config', () => {
it('should allow passing no config', async () => {
const cfg: Flags = {
format: 'json',
recurse: true,
silent: true,
skip: '馃尦',
};
const config = await getConfig(cfg);
assert.deepStrictEqual(config, cfg);
});

it('should throw with a reasonable message if the path doesnt exist', async () => {
const cfg = {
config: '/path/does/not/exist',
};
await assertRejects(getConfig(cfg), /ENOENT: no such file or directory/);
});

it('should allow reading from a config file', async () => {
const configPath = path.resolve(
'test/fixtures/config/linkinator.config.json'
);
const expected = require(configPath);
const config = await getConfig({ config: configPath });
delete config.config;
assert.deepStrictEqual(config, expected);
});

it('should merge config settings from the CLI and object', async () => {
const configPath = path.resolve(
'test/fixtures/config/linkinator.config.json'
);
const expected = require(configPath);
expected.skip = 'loo';
const config = await getConfig({
config: configPath,
skip: 'loo',
});
delete config.config;
assert.deepStrictEqual(config, expected);
});
});

0 comments on commit 2c118b3

Please sign in to comment.