crawlforge-extractors 1.1.0 → 1.2.0
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/package.json +1 -1
- package/src/templates.js +85 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "crawlforge-extractors",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Extraction logic shared by the CrawlForge MCP server and REST API — scrape templates, charset-correct capped body reading, and structural fingerprinting. One implementation, so the two surfaces cannot drift apart.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
package/src/templates.js
CHANGED
|
@@ -104,6 +104,33 @@ function tidy(value) {
|
|
|
104
104
|
* "by Jonathan Haidt (Author) Format: Hardcover" on a book. Each states the
|
|
105
105
|
* same fact wrapped in different chrome.
|
|
106
106
|
*/
|
|
107
|
+
function npmLicense(value) {
|
|
108
|
+
if (!value) return null;
|
|
109
|
+
if (typeof value === 'string') return value;
|
|
110
|
+
// Very old packages use { type, url } or an array of them.
|
|
111
|
+
if (Array.isArray(value)) return value.map(npmLicense).filter(Boolean).join(', ') || null;
|
|
112
|
+
return value.type || null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function npmRepositoryUrl(repository) {
|
|
116
|
+
const raw = typeof repository === 'string' ? repository : repository?.url;
|
|
117
|
+
if (!raw) return null;
|
|
118
|
+
// Registry URLs come as git+ssh://git@host/o/r.git, git+https://…, git://…
|
|
119
|
+
// or a bare "owner/repo" shorthand. Normalise to a browsable https URL.
|
|
120
|
+
let url = raw.replace(/^git\+/, '').replace(/\.git$/, '');
|
|
121
|
+
if (/^[\w.-]+\/[\w.-]+$/.test(url)) return `https://github.com/${url}`;
|
|
122
|
+
url = url.replace(/^git@([^:]+):/, 'https://$1/');
|
|
123
|
+
url = url.replace(/^(?:git|ssh):\/\/(?:git@)?/, 'https://');
|
|
124
|
+
url = url.replace(/^https:\/\/git@/, 'https://');
|
|
125
|
+
return url;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function npmBugsUrl(bugs) {
|
|
129
|
+
if (!bugs) return null;
|
|
130
|
+
if (typeof bugs === 'string') return bugs;
|
|
131
|
+
return bugs.url || bugs.email || null;
|
|
132
|
+
}
|
|
133
|
+
|
|
107
134
|
function amazonByline($) {
|
|
108
135
|
const contributor = tidy($('#bylineInfo .contributorNameID').first().text());
|
|
109
136
|
const raw = tidy($('#bylineInfo').first().text());
|
|
@@ -466,25 +493,66 @@ export const TEMPLATES = [
|
|
|
466
493
|
{
|
|
467
494
|
id: 'npm-package',
|
|
468
495
|
name: 'npm Package',
|
|
469
|
-
description:
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
496
|
+
description:
|
|
497
|
+
'Read a package from the npm registry API rather than the rendered npmjs.com page: exact ' +
|
|
498
|
+
'latest version, license, repository, homepage, maintainers, dependencies and any ' +
|
|
499
|
+
'deprecation notice. npmjs.com blocks plain HTTP fetches, and its markup carries no stable ' +
|
|
500
|
+
'hooks, so the page itself yields almost nothing. Weekly download counts are not included — ' +
|
|
501
|
+
'they live on a separate api.npmjs.org endpoint.',
|
|
502
|
+
targetPattern: /npmjs\.com\/package\/|registry\.npmjs\.org\//i,
|
|
503
|
+
|
|
504
|
+
/** Point the fetch at the registry document for the same package. */
|
|
505
|
+
resolveUrl(url) {
|
|
506
|
+
const parsed = new URL(url);
|
|
507
|
+
if (parsed.hostname === 'registry.npmjs.org') return url;
|
|
508
|
+
// /package/<name>, /package/@scope/<name>, either optionally followed by
|
|
509
|
+
// /v/<version> or /access etc.
|
|
510
|
+
const match = parsed.pathname.match(/\/package\/((?:@[^/]+\/)?[^/]+)/i);
|
|
511
|
+
if (!match) return url;
|
|
512
|
+
return `https://registry.npmjs.org/${match[1]}`;
|
|
513
|
+
},
|
|
514
|
+
|
|
515
|
+
extractRaw(body, url) {
|
|
516
|
+
let doc;
|
|
517
|
+
try {
|
|
518
|
+
doc = JSON.parse(body);
|
|
519
|
+
} catch {
|
|
520
|
+
throw new Error(
|
|
521
|
+
`Not an npm registry document: ${url} did not return JSON. ` +
|
|
522
|
+
'This template reads the npm registry API.'
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// The registry answers an unknown package with {"error":"Not found"}.
|
|
527
|
+
if (!doc || typeof doc.name !== 'string') {
|
|
528
|
+
const reason = typeof doc?.error === 'string' ? doc.error : 'no package document';
|
|
529
|
+
throw new Error(`No npm package at ${url}: ${reason}.`);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const latest = doc['dist-tags']?.latest || null;
|
|
533
|
+
const release = (latest && doc.versions?.[latest]) || {};
|
|
534
|
+
const deps = release.dependencies || {};
|
|
477
535
|
|
|
478
536
|
return {
|
|
479
|
-
name:
|
|
480
|
-
version:
|
|
481
|
-
description:
|
|
482
|
-
license:
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
maintainers:
|
|
537
|
+
name: doc.name,
|
|
538
|
+
version: latest,
|
|
539
|
+
description: release.description || doc.description || null,
|
|
540
|
+
license: npmLicense(release.license ?? doc.license),
|
|
541
|
+
homepage: release.homepage || doc.homepage || null,
|
|
542
|
+
repository: npmRepositoryUrl(release.repository || doc.repository),
|
|
543
|
+
bugs: npmBugsUrl(release.bugs || doc.bugs),
|
|
544
|
+
keywords: release.keywords || doc.keywords || [],
|
|
545
|
+
maintainers: (doc.maintainers || [])
|
|
546
|
+
.map(m => (typeof m === 'string' ? m : m?.name))
|
|
547
|
+
.filter(Boolean),
|
|
548
|
+
dependencies: deps,
|
|
549
|
+
dependency_count: Object.keys(deps).length,
|
|
550
|
+
// A string when the publisher deprecated it, false otherwise — npm
|
|
551
|
+
// keeps serving deprecated packages, so this is the only signal.
|
|
552
|
+
deprecated: typeof release.deprecated === 'string' ? release.deprecated : false,
|
|
553
|
+
published: (latest && doc.time?.[latest]) || null,
|
|
554
|
+
last_modified: doc.time?.modified || null,
|
|
555
|
+
install_command: `npm install ${doc.name}`
|
|
488
556
|
};
|
|
489
557
|
}
|
|
490
558
|
}
|