Skip to content

Commit 5f7bb18

Browse files
authoredJan 5, 2021
fix: map paths in results back to filesystem (#231)
Fixes #166. This updates the returned paths in the results to map to the filesystem if a local path was given.
1 parent 1850490 commit 5f7bb18

File tree

4 files changed

+79
-13
lines changed

4 files changed

+79
-13
lines changed
 

‎src/index.ts

+44-10
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import {EventEmitter} from 'events';
22
import {URL} from 'url';
33
import * as http from 'http';
4+
import * as path from 'path';
45

56
import {request, GaxiosResponse} from 'gaxios';
67

78
import {Queue} from './queue';
89
import {getLinks} from './links';
910
import {startWebServer} from './server';
10-
import {CheckOptions, processOptions} from './options';
11+
import {CheckOptions, InternalCheckOptions, processOptions} from './options';
1112

1213
export {CheckOptions};
1314

@@ -92,6 +93,7 @@ export class LinkChecker extends EventEmitter {
9293
}
9394
options.path[i] = `http://localhost:${port}/${options.path[i]}`;
9495
}
96+
options.staticHttpServerHost = `http://localhost:${port}/`;
9597
}
9698

9799
if (process.env.LINKINATOR_DEBUG) {
@@ -146,10 +148,10 @@ export class LinkChecker extends EventEmitter {
146148
const proto = opts.url.protocol;
147149
if (proto !== 'http:' && proto !== 'https:') {
148150
const r: LinkResult = {
149-
url: opts.url.href,
151+
url: mapUrl(opts.url.href, opts.checkOptions),
150152
status: 0,
151153
state: LinkState.SKIPPED,
152-
parent: opts.parent,
154+
parent: mapUrl(opts.parent, opts.checkOptions),
153155
};
154156
opts.results.push(r);
155157
this.emit('link', r);
@@ -162,7 +164,7 @@ export class LinkChecker extends EventEmitter {
162164
(await opts.checkOptions.linksToSkip(opts.url.href))
163165
) {
164166
const result: LinkResult = {
165-
url: opts.url.href,
167+
url: mapUrl(opts.url.href, opts.checkOptions),
166168
state: LinkState.SKIPPED,
167169
parent: opts.parent,
168170
};
@@ -181,9 +183,9 @@ export class LinkChecker extends EventEmitter {
181183

182184
if (skips.length > 0) {
183185
const result: LinkResult = {
184-
url: opts.url.href,
186+
url: mapUrl(opts.url.href, opts.checkOptions),
185187
state: LinkState.SKIPPED,
186-
parent: opts.parent,
188+
parent: mapUrl(opts.parent, opts.checkOptions),
187189
};
188190
opts.results.push(result);
189191
this.emit('link', result);
@@ -285,10 +287,10 @@ export class LinkChecker extends EventEmitter {
285287
}
286288

287289
const result: LinkResult = {
288-
url: opts.url.href,
290+
url: mapUrl(opts.url.href, opts.checkOptions),
289291
status,
290292
state,
291-
parent: opts.parent,
293+
parent: mapUrl(opts.parent, opts.checkOptions),
292294
failureDetails: failures,
293295
};
294296
opts.results.push(result);
@@ -303,10 +305,10 @@ export class LinkChecker extends EventEmitter {
303305
// creating a new URL obj, treat it as a broken link.
304306
if (!result.url) {
305307
const r = {
306-
url: result.link,
308+
url: mapUrl(result.link, opts.checkOptions),
307309
status: 0,
308310
state: LinkState.BROKEN,
309-
parent: opts.url.href,
311+
parent: mapUrl(opts.url.href, opts.checkOptions),
310312
};
311313
opts.results.push(r);
312314
this.emit('link', r);
@@ -427,3 +429,35 @@ function isHtml(response: GaxiosResponse): boolean {
427429
!!contentType.match(/application\/xhtml\+xml/g)
428430
);
429431
}
432+
433+
/**
434+
* When running a local static web server for the user, translate paths from
435+
* the Url generated back to something closer to a local filesystem path.
436+
* @example
437+
* http://localhost:0000/test/route/README.md => test/route/README.md
438+
* @param url The url that was checked
439+
* @param options Original CheckOptions passed into the client
440+
*/
441+
function mapUrl(url?: string, options?: InternalCheckOptions): string {
442+
if (!url) {
443+
return url!;
444+
}
445+
let newUrl = url;
446+
447+
// trim the starting http://localhost:0000 if we stood up a local static server
448+
if (
449+
options?.staticHttpServerHost?.length &&
450+
url?.startsWith(options.staticHttpServerHost)
451+
) {
452+
newUrl = url.slice(options.staticHttpServerHost.length);
453+
454+
// add the full filesystem path back if we trimmed it
455+
if (options?.syntheticServerRoot?.length) {
456+
newUrl = path.join(options.syntheticServerRoot, newUrl);
457+
}
458+
if (newUrl === '') {
459+
newUrl = `.${path.sep}`;
460+
}
461+
}
462+
return newUrl!;
463+
}

‎src/options.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,19 @@ export interface CheckOptions {
1919
retry?: boolean;
2020
}
2121

22+
export interface InternalCheckOptions extends CheckOptions {
23+
syntheticServerRoot?: string;
24+
staticHttpServerHost?: string;
25+
}
26+
2227
/**
2328
* Validate the provided flags all work with each other.
2429
* @param options CheckOptions passed in from the CLI (or API)
2530
*/
2631
export async function processOptions(
2732
opts: CheckOptions
28-
): Promise<CheckOptions> {
29-
const options = Object.assign({}, opts);
33+
): Promise<InternalCheckOptions> {
34+
const options = Object.assign({}, opts) as InternalCheckOptions;
3035

3136
// ensure at least one path is provided
3237
if (options.path.length === 0) {
@@ -131,6 +136,7 @@ export async function processOptions(
131136
options.serverRoot = options.path[0];
132137
options.path = '/';
133138
}
139+
options.syntheticServerRoot = options.serverRoot;
134140
}
135141
}
136142
return options;

‎test/test.cli.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ describe('cli', function () {
7575
'csv',
7676
'test/fixtures/markdown/README.md',
7777
]);
78-
assert.match(res.stdout, /\/README.md,200,OK,/);
78+
assert.match(res.stdout, /README.md,200,OK,/);
7979
});
8080

8181
it('should provide JSON if asked nicely', async () => {

‎test/test.index.ts

+26
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,9 @@ describe('linkinator', () => {
395395
});
396396
assert.ok(results.passed);
397397
assert.strictEqual(results.links.length, 6);
398+
const licenseLink = results.links.find(x => x.url.endsWith('LICENSE.md'));
399+
assert.ok(licenseLink);
400+
assert.strictEqual(licenseLink.url, 'test/fixtures/markdown/LICENSE.md');
398401
});
399402

400403
it('should autoscan markdown if specifically in path', async () => {
@@ -497,4 +500,27 @@ describe('linkinator', () => {
497500
assert.ok(!results.passed);
498501
assert.strictEqual(results.links.length, 3);
499502
});
503+
504+
it('should provide a relative path in the results', async () => {
505+
const scope = nock('http://fake.local').head('/').reply(200);
506+
const results = await check({path: 'test/fixtures/basic'});
507+
assert.strictEqual(results.links.length, 2);
508+
const [rootLink, fakeLink] = results.links;
509+
assert.strictEqual(rootLink.url, path.join('test', 'fixtures', 'basic'));
510+
assert.strictEqual(fakeLink.url, 'http://fake.local/');
511+
scope.done();
512+
});
513+
514+
it('should provide a server root relative path in the results', async () => {
515+
const scope = nock('http://fake.local').head('/').reply(200);
516+
const results = await check({
517+
path: '.',
518+
serverRoot: 'test/fixtures/basic',
519+
});
520+
assert.strictEqual(results.links.length, 2);
521+
const [rootLink, fakeLink] = results.links;
522+
assert.strictEqual(rootLink.url, `.${path.sep}`);
523+
assert.strictEqual(fakeLink.url, 'http://fake.local/');
524+
scope.done();
525+
});
500526
});

0 commit comments

Comments
 (0)
Please sign in to comment.