@lhechenberger/check-links 0.2.1 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/package.json +12 -12
- package/src/classify.ts +1 -0
- package/src/cli.ts +4 -3
- package/src/index.ts +3 -3
- package/src/run.ts +10 -10
package/README.md
CHANGED
|
@@ -64,6 +64,11 @@ export default defineConfig({
|
|
|
64
64
|
| `concurrency` | `20` | Parallel requests of the crawl |
|
|
65
65
|
| `timeout` | `30000` | Timeout per request in ms |
|
|
66
66
|
|
|
67
|
+
### Type-checking and unused-file tools
|
|
68
|
+
|
|
69
|
+
- The package ships TypeScript source (no build step). Relative imports inside it have no `.ts` extension, so a project that type-checks its `check-links.config.ts` (e.g. listed in `tsconfig.json` `include`) needs `moduleResolution: "bundler"` (or `"node16"`/`"nodenext"` is **not** supported) and Node or Bun types (`@types/node` or `@types/bun`), since the code uses `process`. Before 0.2.2 the imports ended in `.ts`; if you see `TS5097` from `node_modules/@lhechenberger/check-links`, update the package.
|
|
70
|
+
- [knip](https://knip.dev) reports the config as an unused file. Declare it as an entry in `knip.json`: `{ "workspaces": { "apps/web": { "entry": ["check-links.config.ts"] } } }` (use `"entry"` at the top level in a single-package repo).
|
|
71
|
+
|
|
67
72
|
## GitHub workflow: check every Vercel deployment
|
|
68
73
|
|
|
69
74
|
The package contains a workflow template that runs the check when Vercel reports a ready deployment, shows the result as a **"Broken Links" check run** on the deployed commit (so it appears on the pull request, with the full output) and reports the commit status that Vercel's Deployment Checks can require before a production deployment gets its domain.
|
package/package.json
CHANGED
|
@@ -1,29 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lhechenberger/check-links",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Crawls a deployed site for broken links, separating flaky failures from real ones. Built for Vercel preview deployments.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"broken-links",
|
|
7
|
+
"ci",
|
|
7
8
|
"link-checker",
|
|
8
9
|
"linkinator",
|
|
9
|
-
"vercel"
|
|
10
|
-
"ci"
|
|
10
|
+
"vercel"
|
|
11
11
|
],
|
|
12
12
|
"homepage": "https://github.com/LukasHechenberger/workbench/tree/main/packages/check-links#readme",
|
|
13
13
|
"bugs": {
|
|
14
14
|
"url": "https://github.com/LukasHechenberger/workbench/issues"
|
|
15
15
|
},
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"author": "Lukas Hechenberger",
|
|
16
18
|
"repository": {
|
|
17
19
|
"type": "git",
|
|
18
20
|
"url": "git+https://github.com/LukasHechenberger/workbench.git",
|
|
19
21
|
"directory": "packages/check-links"
|
|
20
22
|
},
|
|
21
|
-
"license": "MIT",
|
|
22
|
-
"author": "Lukas Hechenberger",
|
|
23
|
-
"type": "module",
|
|
24
|
-
"exports": {
|
|
25
|
-
".": "./src/index.ts"
|
|
26
|
-
},
|
|
27
23
|
"bin": {
|
|
28
24
|
"check-links": "src/cli.ts"
|
|
29
25
|
},
|
|
@@ -32,6 +28,13 @@
|
|
|
32
28
|
"!src/**/*.test.ts",
|
|
33
29
|
"templates"
|
|
34
30
|
],
|
|
31
|
+
"type": "module",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": "./src/index.ts"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
},
|
|
35
38
|
"scripts": {
|
|
36
39
|
"test": "bun test"
|
|
37
40
|
},
|
|
@@ -40,8 +43,5 @@
|
|
|
40
43
|
},
|
|
41
44
|
"engines": {
|
|
42
45
|
"bun": ">=1.2"
|
|
43
|
-
},
|
|
44
|
-
"publishConfig": {
|
|
45
|
-
"access": "public"
|
|
46
46
|
}
|
|
47
47
|
}
|
package/src/classify.ts
CHANGED
|
@@ -38,6 +38,7 @@ export async function classify(
|
|
|
38
38
|
// A burst of requests is what failed in the first place, so the second pass is gentle
|
|
39
39
|
for (let i = 0; i < toRecheck.length; i += concurrency) {
|
|
40
40
|
const batch = toRecheck.slice(i, i + concurrency);
|
|
41
|
+
// oxlint-disable-next-line no-await-in-loop -- one batch after the other, on purpose
|
|
41
42
|
const results = await Promise.all(batch.map((link) => recovers(link)));
|
|
42
43
|
for (const [index, link] of batch.entries()) (results[index] ? flaky : errors).push(link);
|
|
43
44
|
}
|
package/src/cli.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { dirname, resolve } from 'node:path';
|
|
4
4
|
import { parseArgs } from 'node:util';
|
|
5
|
-
import type { CheckLinksConfig } from './config
|
|
6
|
-
import { run } from './run
|
|
5
|
+
import type { CheckLinksConfig } from './config';
|
|
6
|
+
import { run } from './run';
|
|
7
7
|
|
|
8
8
|
const usage = `Usage:
|
|
9
9
|
check-links [url] [--config check-links.config.ts]
|
|
@@ -37,7 +37,8 @@ if (positionals[0] === 'init-workflow') {
|
|
|
37
37
|
const configPath = resolve(values.config ?? 'check-links.config.ts');
|
|
38
38
|
let config: CheckLinksConfig = {};
|
|
39
39
|
if (existsSync(configPath)) {
|
|
40
|
-
|
|
40
|
+
const loaded: { default: CheckLinksConfig } = await import(configPath);
|
|
41
|
+
config = loaded.default;
|
|
41
42
|
} else if (values.config) {
|
|
42
43
|
console.error(`Config ${configPath} not found`);
|
|
43
44
|
process.exit(1);
|
package/src/index.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { classify, type Classification, type ClassifyOptions } from './classify
|
|
2
|
-
export { defineConfig, type CheckLinksConfig, type LinkContext } from './config
|
|
3
|
-
export { run } from './run
|
|
1
|
+
export { classify, type Classification, type ClassifyOptions } from './classify';
|
|
2
|
+
export { defineConfig, type CheckLinksConfig, type LinkContext } from './config';
|
|
3
|
+
export { run } from './run';
|
package/src/run.ts
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import { check, type LinkResult, LinkState } from 'linkinator';
|
|
2
|
-
import { classify } from './classify
|
|
3
|
-
import type { CheckLinksConfig } from './config
|
|
2
|
+
import { classify } from './classify';
|
|
3
|
+
import type { CheckLinksConfig } from './config';
|
|
4
4
|
|
|
5
5
|
const BYPASS_ENV = 'VERCEL_AUTOMATION_BYPASS_SECRET';
|
|
6
6
|
|
|
7
|
+
const print = (title: string, results: LinkResult[]) => {
|
|
8
|
+
if (results.length === 0) return;
|
|
9
|
+
console.log(`\n${title}`);
|
|
10
|
+
for (const link of results) {
|
|
11
|
+
console.log(` [${link.status ?? 'error'}] ${link.url}\n on ${link.parent}`);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
7
15
|
/** Crawls `url` and prints the result. Returns the process exit code. */
|
|
8
16
|
export async function run(url: string, config: CheckLinksConfig): Promise<number> {
|
|
9
17
|
const { origin } = new URL(url);
|
|
@@ -64,14 +72,6 @@ export async function run(url: string, config: CheckLinksConfig): Promise<number
|
|
|
64
72
|
concurrency: config.recheckConcurrency ?? 5,
|
|
65
73
|
});
|
|
66
74
|
|
|
67
|
-
const print = (title: string, results: LinkResult[]) => {
|
|
68
|
-
if (results.length === 0) return;
|
|
69
|
-
console.log(`\n${title}`);
|
|
70
|
-
for (const link of results) {
|
|
71
|
-
console.log(` [${link.status ?? 'error'}] ${link.url}\n on ${link.parent}`);
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
|
|
75
75
|
print(`${config.warningTitle ?? 'Broken links that are only warnings'} (warnings):`, warnings);
|
|
76
76
|
print('Links that worked on a second request (warnings):', flaky);
|
|
77
77
|
print('Broken links:', errors);
|