@westbayberry/dg 1.0.53 → 1.0.56
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 -1
- package/dist/index.mjs +249 -114
- package/dist/packages/cli/src/alt-screen.js +36 -0
- package/dist/packages/cli/src/api.js +322 -0
- package/dist/packages/cli/src/auth.js +218 -0
- package/dist/packages/cli/src/bin.js +386 -0
- package/dist/packages/cli/src/config.js +228 -0
- package/dist/packages/cli/src/discover.js +126 -0
- package/dist/packages/cli/src/first-run.js +135 -0
- package/dist/packages/cli/src/hook.js +360 -0
- package/dist/packages/cli/src/lockfile.js +303 -0
- package/dist/packages/cli/src/npm-wrapper.js +218 -0
- package/dist/packages/cli/src/pip-wrapper.js +273 -0
- package/dist/packages/cli/src/sanitize.js +38 -0
- package/dist/packages/cli/src/scan-core.js +144 -0
- package/dist/packages/cli/src/setup-status.js +46 -0
- package/dist/packages/cli/src/static-output.js +625 -0
- package/dist/packages/cli/src/telemetry.js +141 -0
- package/dist/packages/cli/src/ui/App.js +137 -0
- package/dist/packages/cli/src/ui/InitApp.js +391 -0
- package/dist/packages/cli/src/ui/LoginApp.js +51 -0
- package/dist/packages/cli/src/ui/NpmWrapperApp.js +73 -0
- package/dist/packages/cli/src/ui/PipWrapperApp.js +72 -0
- package/dist/packages/cli/src/ui/components/ConfirmPrompt.js +24 -0
- package/dist/packages/cli/src/ui/components/DemoScanAnimation.js +26 -0
- package/dist/packages/cli/src/ui/components/DurationLine.js +7 -0
- package/dist/packages/cli/src/ui/components/ErrorView.js +30 -0
- package/dist/packages/cli/src/ui/components/FileSavePrompt.js +210 -0
- package/dist/packages/cli/src/ui/components/InteractiveResultsView.js +557 -0
- package/dist/packages/cli/src/ui/components/Mascot.js +33 -0
- package/dist/packages/cli/src/ui/components/ProgressBar.js +51 -0
- package/dist/packages/cli/src/ui/components/ProgressDots.js +35 -0
- package/dist/packages/cli/src/ui/components/ProjectSelector.js +60 -0
- package/dist/packages/cli/src/ui/components/ResultsView.js +105 -0
- package/dist/packages/cli/src/ui/components/ScanResultCard.js +54 -0
- package/dist/packages/cli/src/ui/components/ScoreHeader.js +142 -0
- package/dist/packages/cli/src/ui/components/SetupBanner.js +17 -0
- package/dist/packages/cli/src/ui/components/Spinner.js +11 -0
- package/dist/packages/cli/src/ui/hooks/useExpandAnimation.js +44 -0
- package/dist/packages/cli/src/ui/hooks/useInit.js +341 -0
- package/dist/packages/cli/src/ui/hooks/useLogin.js +121 -0
- package/dist/packages/cli/src/ui/hooks/useNpmWrapper.js +192 -0
- package/dist/packages/cli/src/ui/hooks/usePipWrapper.js +195 -0
- package/dist/packages/cli/src/ui/hooks/useScan.js +202 -0
- package/dist/packages/cli/src/ui/hooks/useTerminalSize.js +29 -0
- package/dist/packages/cli/src/update-check.js +152 -0
- package/dist/packages/cli/src/wizard-demo-data.js +63 -0
- package/dist/src/ecosystem.js +2 -0
- package/dist/src/lockfile/diff.js +38 -0
- package/dist/src/lockfile/parse_package_json.js +41 -0
- package/dist/src/lockfile/parse_package_lock.js +55 -0
- package/dist/src/lockfile/parse_pipfile_lock.js +69 -0
- package/dist/src/lockfile/parse_pnpm_lock.js +62 -0
- package/dist/src/lockfile/parse_poetry_lock.js +71 -0
- package/dist/src/lockfile/parse_requirements.js +83 -0
- package/dist/src/lockfile/parse_yarn_lock.js +66 -0
- package/dist/src/logger.js +21 -0
- package/dist/src/npm/h2pool.js +161 -0
- package/dist/src/npm/registry.js +299 -0
- package/dist/src/npm/tarball.js +274 -0
- package/dist/src/pypi/registry.js +299 -0
- package/dist/src/pypi/tarball.js +361 -0
- package/dist/src/types.js +2 -0
- package/package.json +6 -3
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.diffLockfiles = diffLockfiles;
|
|
4
|
+
function diffLockfiles(base, head, maxPackages, directDeps) {
|
|
5
|
+
const allChanges = [];
|
|
6
|
+
for (const [name, headEntry] of head.packages) {
|
|
7
|
+
const baseEntry = base?.packages.get(name);
|
|
8
|
+
if (!baseEntry) {
|
|
9
|
+
allChanges.push({
|
|
10
|
+
name,
|
|
11
|
+
oldVersion: null,
|
|
12
|
+
newVersion: headEntry.version,
|
|
13
|
+
isDirect: directDeps?.has(name) ?? false,
|
|
14
|
+
isDev: headEntry.dev ?? false,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
else if (baseEntry.version !== headEntry.version) {
|
|
18
|
+
allChanges.push({
|
|
19
|
+
name,
|
|
20
|
+
oldVersion: baseEntry.version,
|
|
21
|
+
newVersion: headEntry.version,
|
|
22
|
+
isDirect: directDeps?.has(name) ?? false,
|
|
23
|
+
isDev: headEntry.dev ?? false,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
allChanges.sort((a, b) => {
|
|
28
|
+
if (a.isDirect !== b.isDirect)
|
|
29
|
+
return a.isDirect ? -1 : 1;
|
|
30
|
+
return a.name.localeCompare(b.name);
|
|
31
|
+
});
|
|
32
|
+
if (allChanges.length <= maxPackages) {
|
|
33
|
+
return { changes: allChanges, skipped: [] };
|
|
34
|
+
}
|
|
35
|
+
const changes = allChanges.slice(0, maxPackages);
|
|
36
|
+
const skipped = allChanges.slice(maxPackages).map((c) => c.name);
|
|
37
|
+
return { changes, skipped };
|
|
38
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.diffPackageJsons = diffPackageJsons;
|
|
4
|
+
function diffPackageJsons(baseContent, headContent, maxPackages) {
|
|
5
|
+
const head = JSON.parse(headContent);
|
|
6
|
+
const headDeps = {
|
|
7
|
+
...head.dependencies,
|
|
8
|
+
...head.devDependencies,
|
|
9
|
+
};
|
|
10
|
+
let baseDeps = {};
|
|
11
|
+
if (baseContent) {
|
|
12
|
+
const base = JSON.parse(baseContent);
|
|
13
|
+
baseDeps = { ...base.dependencies, ...base.devDependencies };
|
|
14
|
+
}
|
|
15
|
+
const changes = [];
|
|
16
|
+
for (const [name, version] of Object.entries(headDeps)) {
|
|
17
|
+
if (changes.length >= maxPackages)
|
|
18
|
+
break;
|
|
19
|
+
const baseVersion = baseDeps[name];
|
|
20
|
+
const isDev = !!(head.devDependencies && head.devDependencies[name]);
|
|
21
|
+
if (!baseVersion) {
|
|
22
|
+
changes.push({
|
|
23
|
+
name,
|
|
24
|
+
oldVersion: null,
|
|
25
|
+
newVersion: version,
|
|
26
|
+
isDirect: true,
|
|
27
|
+
isDev,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
else if (baseVersion !== version) {
|
|
31
|
+
changes.push({
|
|
32
|
+
name,
|
|
33
|
+
oldVersion: baseVersion,
|
|
34
|
+
newVersion: version,
|
|
35
|
+
isDirect: true,
|
|
36
|
+
isDev,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return { changes };
|
|
41
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseLockfile = parseLockfile;
|
|
4
|
+
function parseLockfile(content) {
|
|
5
|
+
const json = JSON.parse(content);
|
|
6
|
+
const lockfileVersion = json.lockfileVersion ?? 1;
|
|
7
|
+
const packages = new Map();
|
|
8
|
+
if (lockfileVersion >= 2 && json.packages) {
|
|
9
|
+
for (const [path, entry] of Object.entries(json.packages)) {
|
|
10
|
+
if (path === "")
|
|
11
|
+
continue;
|
|
12
|
+
const name = extractPackageName(path);
|
|
13
|
+
if (name && !packages.has(name)) {
|
|
14
|
+
const e = entry;
|
|
15
|
+
const deps = e.dependencies;
|
|
16
|
+
packages.set(name, {
|
|
17
|
+
version: e.version ?? "",
|
|
18
|
+
resolved: e.resolved,
|
|
19
|
+
integrity: e.integrity,
|
|
20
|
+
dev: e.dev,
|
|
21
|
+
optional: e.optional,
|
|
22
|
+
hasPlatformRestriction: !!(e.os || e.cpu),
|
|
23
|
+
dependencies: deps && typeof deps === "object" ? deps : undefined,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
else if (json.dependencies) {
|
|
29
|
+
parseLegacyDeps(json.dependencies, packages);
|
|
30
|
+
}
|
|
31
|
+
return { lockfileVersion, packages };
|
|
32
|
+
}
|
|
33
|
+
function extractPackageName(nodePath) {
|
|
34
|
+
const prefix = "node_modules/";
|
|
35
|
+
const lastIdx = nodePath.lastIndexOf(prefix);
|
|
36
|
+
if (lastIdx === -1)
|
|
37
|
+
return null;
|
|
38
|
+
const name = nodePath.slice(lastIdx + prefix.length);
|
|
39
|
+
return name || null;
|
|
40
|
+
}
|
|
41
|
+
function parseLegacyDeps(deps, packages, parentPrefix = "") {
|
|
42
|
+
for (const [name, value] of Object.entries(deps)) {
|
|
43
|
+
const fullName = parentPrefix ? `${parentPrefix}/${name}` : name;
|
|
44
|
+
const entry = value;
|
|
45
|
+
packages.set(fullName, {
|
|
46
|
+
version: entry.version ?? "",
|
|
47
|
+
resolved: entry.resolved,
|
|
48
|
+
integrity: entry.integrity,
|
|
49
|
+
dev: entry.dev,
|
|
50
|
+
});
|
|
51
|
+
if (entry.dependencies) {
|
|
52
|
+
parseLegacyDeps(entry.dependencies, packages);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parsePipfileLock = parsePipfileLock;
|
|
4
|
+
exports.diffPipfileLocks = diffPipfileLocks;
|
|
5
|
+
/**
|
|
6
|
+
* Parse a Pipfile.lock (JSON format) into package entries.
|
|
7
|
+
* Pipfile.lock has two sections: "default" (production) and "develop" (dev).
|
|
8
|
+
* Each entry is keyed by package name with a "version" field like "==1.2.3".
|
|
9
|
+
*/
|
|
10
|
+
function parsePipfileLock(content) {
|
|
11
|
+
const packages = new Map();
|
|
12
|
+
const json = JSON.parse(content);
|
|
13
|
+
parsePipfileSection(json.default, false, packages);
|
|
14
|
+
parsePipfileSection(json.develop, true, packages);
|
|
15
|
+
return { packages };
|
|
16
|
+
}
|
|
17
|
+
function parsePipfileSection(section, isDev, packages) {
|
|
18
|
+
if (!section || typeof section !== "object")
|
|
19
|
+
return;
|
|
20
|
+
for (const [rawName, entry] of Object.entries(section)) {
|
|
21
|
+
if (!entry || typeof entry !== "object")
|
|
22
|
+
continue;
|
|
23
|
+
const entryObj = entry;
|
|
24
|
+
const rawVersion = entryObj.version;
|
|
25
|
+
if (typeof rawVersion !== "string")
|
|
26
|
+
continue;
|
|
27
|
+
const name = normalizePyName(rawName);
|
|
28
|
+
// Strip leading "==" from version
|
|
29
|
+
const version = rawVersion.replace(/^==/, "");
|
|
30
|
+
if (!packages.has(name)) {
|
|
31
|
+
packages.set(name, { name, version, isDev });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function diffPipfileLocks(base, head, maxPackages) {
|
|
36
|
+
const changes = [];
|
|
37
|
+
for (const [name, headEntry] of head.packages) {
|
|
38
|
+
const baseEntry = base?.packages.get(name);
|
|
39
|
+
if (!baseEntry) {
|
|
40
|
+
changes.push({
|
|
41
|
+
name,
|
|
42
|
+
oldVersion: null,
|
|
43
|
+
newVersion: headEntry.version,
|
|
44
|
+
isDirect: true,
|
|
45
|
+
isDev: headEntry.isDev,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
else if (baseEntry.version !== headEntry.version) {
|
|
49
|
+
changes.push({
|
|
50
|
+
name,
|
|
51
|
+
oldVersion: baseEntry.version,
|
|
52
|
+
newVersion: headEntry.version,
|
|
53
|
+
isDirect: true,
|
|
54
|
+
isDev: headEntry.isDev,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
changes.sort((a, b) => a.name.localeCompare(b.name));
|
|
59
|
+
if (changes.length <= maxPackages) {
|
|
60
|
+
return { changes, skipped: [] };
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
changes: changes.slice(0, maxPackages),
|
|
64
|
+
skipped: changes.slice(maxPackages).map((c) => c.name),
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
function normalizePyName(name) {
|
|
68
|
+
return name.toLowerCase().replace(/[-_.]+/g, "-");
|
|
69
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parsePnpmLock = parsePnpmLock;
|
|
4
|
+
/**
|
|
5
|
+
* Parse a pnpm-lock.yaml file (v5, v6, v9 formats).
|
|
6
|
+
*
|
|
7
|
+
* Formats:
|
|
8
|
+
* v5: /package/version: (under `packages:`)
|
|
9
|
+
* v6+: /package@version: (under `packages:`)
|
|
10
|
+
* v9: package@version: (under `packages:`, no leading /)
|
|
11
|
+
*
|
|
12
|
+
* We only need package name + version for diffing — no YAML library needed.
|
|
13
|
+
*/
|
|
14
|
+
function parsePnpmLock(content) {
|
|
15
|
+
const packages = new Map();
|
|
16
|
+
const lines = content.split("\n");
|
|
17
|
+
let inPackages = false;
|
|
18
|
+
// v5 format: /pkg/1.0.0
|
|
19
|
+
const v5Re = /^\s+'?\/?(@?[^/]+\/[^/]+)\/([^:'(]+)/;
|
|
20
|
+
// v6+/v9 format: /pkg@1.0.0 or pkg@1.0.0
|
|
21
|
+
const v6Re = /^\s+'?\/?(@?[^@\s']+)@([^:'(\s]+)/;
|
|
22
|
+
let lockfileVersion = 0;
|
|
23
|
+
const versionMatch = content.match(/^lockfileVersion:\s*'?(\d+)/m);
|
|
24
|
+
if (versionMatch)
|
|
25
|
+
lockfileVersion = parseInt(versionMatch[1], 10);
|
|
26
|
+
for (const line of lines) {
|
|
27
|
+
// Detect the packages: section
|
|
28
|
+
if (/^packages:/.test(line)) {
|
|
29
|
+
inPackages = true;
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
// A new top-level key ends the packages section
|
|
33
|
+
if (inPackages && /^\S/.test(line) && !line.startsWith(" ") && !line.startsWith("'")) {
|
|
34
|
+
if (!line.startsWith("packages:"))
|
|
35
|
+
inPackages = false;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (!inPackages)
|
|
39
|
+
continue;
|
|
40
|
+
// Try v6+/v9 format first (more common in modern pnpm)
|
|
41
|
+
let match = line.match(v6Re);
|
|
42
|
+
if (match) {
|
|
43
|
+
const [, name, version] = match;
|
|
44
|
+
if (!packages.has(name)) {
|
|
45
|
+
packages.set(name, { version });
|
|
46
|
+
}
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
// Try v5 format: /package/version
|
|
50
|
+
match = line.match(v5Re);
|
|
51
|
+
if (match) {
|
|
52
|
+
const rawName = match[1];
|
|
53
|
+
const version = match[2];
|
|
54
|
+
// v5 scoped packages: /@scope/name/version → name is @scope/name
|
|
55
|
+
// v5 unscoped: /name/version
|
|
56
|
+
if (!packages.has(rawName)) {
|
|
57
|
+
packages.set(rawName, { version });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return { lockfileVersion, packages };
|
|
62
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parsePoetryLock = parsePoetryLock;
|
|
4
|
+
exports.diffPoetryLocks = diffPoetryLocks;
|
|
5
|
+
/**
|
|
6
|
+
* Parse a poetry.lock file (TOML format) into package entries.
|
|
7
|
+
* Uses a simple regex-based parser since poetry.lock has a predictable structure:
|
|
8
|
+
*
|
|
9
|
+
* [[package]]
|
|
10
|
+
* name = "package-name"
|
|
11
|
+
* version = "1.2.3"
|
|
12
|
+
* category = "main" | "dev"
|
|
13
|
+
*
|
|
14
|
+
* We don't need a full TOML parser — just extract package blocks.
|
|
15
|
+
*/
|
|
16
|
+
function parsePoetryLock(content) {
|
|
17
|
+
const packages = new Map();
|
|
18
|
+
// Split into [[package]] blocks
|
|
19
|
+
const blocks = content.split(/^\[\[package\]\]\s*$/m);
|
|
20
|
+
for (const block of blocks) {
|
|
21
|
+
const nameMatch = block.match(/^name\s*=\s*"([^"]+)"/m);
|
|
22
|
+
const versionMatch = block.match(/^version\s*=\s*"([^"]+)"/m);
|
|
23
|
+
if (!nameMatch || !versionMatch)
|
|
24
|
+
continue;
|
|
25
|
+
const name = normalizePyName(nameMatch[1]);
|
|
26
|
+
const version = versionMatch[1];
|
|
27
|
+
// Check category (poetry 1.x uses "category", poetry 2.x uses groups)
|
|
28
|
+
const categoryMatch = block.match(/^category\s*=\s*"([^"]+)"/m);
|
|
29
|
+
const category = categoryMatch?.[1] ?? "main";
|
|
30
|
+
const isDev = category === "dev";
|
|
31
|
+
if (!packages.has(name)) {
|
|
32
|
+
packages.set(name, { name, version, isDev, category });
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return { packages };
|
|
36
|
+
}
|
|
37
|
+
function diffPoetryLocks(base, head, maxPackages) {
|
|
38
|
+
const changes = [];
|
|
39
|
+
for (const [name, headEntry] of head.packages) {
|
|
40
|
+
const baseEntry = base?.packages.get(name);
|
|
41
|
+
if (!baseEntry) {
|
|
42
|
+
changes.push({
|
|
43
|
+
name,
|
|
44
|
+
oldVersion: null,
|
|
45
|
+
newVersion: headEntry.version,
|
|
46
|
+
isDirect: true,
|
|
47
|
+
isDev: headEntry.isDev,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
else if (baseEntry.version !== headEntry.version) {
|
|
51
|
+
changes.push({
|
|
52
|
+
name,
|
|
53
|
+
oldVersion: baseEntry.version,
|
|
54
|
+
newVersion: headEntry.version,
|
|
55
|
+
isDirect: true,
|
|
56
|
+
isDev: headEntry.isDev,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
changes.sort((a, b) => a.name.localeCompare(b.name));
|
|
61
|
+
if (changes.length <= maxPackages) {
|
|
62
|
+
return { changes, skipped: [] };
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
changes: changes.slice(0, maxPackages),
|
|
66
|
+
skipped: changes.slice(maxPackages).map((c) => c.name),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function normalizePyName(name) {
|
|
70
|
+
return name.toLowerCase().replace(/[-_.]+/g, "-");
|
|
71
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseRequirements = parseRequirements;
|
|
4
|
+
exports.diffRequirements = diffRequirements;
|
|
5
|
+
/**
|
|
6
|
+
* Parse a requirements.txt file into package entries.
|
|
7
|
+
* Handles: name==version, name>=version, name~=version, name!=version,
|
|
8
|
+
* extras like name[extra1,extra2]==version, comments (#), -r includes (ignored),
|
|
9
|
+
* environment markers (;), and line continuations (\).
|
|
10
|
+
*/
|
|
11
|
+
function parseRequirements(content) {
|
|
12
|
+
const packages = new Map();
|
|
13
|
+
// Join line continuations
|
|
14
|
+
const joined = content.replace(/\\\n/g, "");
|
|
15
|
+
const lines = joined.split("\n");
|
|
16
|
+
for (const rawLine of lines) {
|
|
17
|
+
const line = rawLine.trim();
|
|
18
|
+
// Skip empty lines, comments, options, and -r/-c references
|
|
19
|
+
if (!line || line.startsWith("#") || line.startsWith("-") || line.startsWith("--")) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
// Strip environment markers (everything after ;)
|
|
23
|
+
const withoutMarker = line.split(";")[0].trim();
|
|
24
|
+
if (!withoutMarker)
|
|
25
|
+
continue;
|
|
26
|
+
// Parse package name, extras, and version specifier
|
|
27
|
+
const match = withoutMarker.match(/^([a-zA-Z0-9][-a-zA-Z0-9_.]*(?:\[[^\]]*\])?)(?:\s*(==|>=|<=|~=|!=|>|<|===)\s*([^\s,;]+))?/);
|
|
28
|
+
if (!match)
|
|
29
|
+
continue;
|
|
30
|
+
const nameWithExtras = match[1];
|
|
31
|
+
const version = match[3] || "";
|
|
32
|
+
// Extract extras: name[extra1,extra2] -> name, [extra1, extra2]
|
|
33
|
+
const extrasMatch = nameWithExtras.match(/^([a-zA-Z0-9][-a-zA-Z0-9_.]*)(?:\[([^\]]*)\])?$/);
|
|
34
|
+
if (!extrasMatch)
|
|
35
|
+
continue;
|
|
36
|
+
const name = normalizePyName(extrasMatch[1]);
|
|
37
|
+
const extras = extrasMatch[2]
|
|
38
|
+
? extrasMatch[2].split(",").map((e) => e.trim()).filter(Boolean)
|
|
39
|
+
: undefined;
|
|
40
|
+
if (!packages.has(name)) {
|
|
41
|
+
packages.set(name, { name, version, extras });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { packages };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Diff two requirements.txt files to find added/changed packages.
|
|
48
|
+
*/
|
|
49
|
+
function diffRequirements(base, head, maxPackages) {
|
|
50
|
+
const changes = [];
|
|
51
|
+
for (const [name, headEntry] of head.packages) {
|
|
52
|
+
const baseEntry = base?.packages.get(name);
|
|
53
|
+
if (!baseEntry) {
|
|
54
|
+
changes.push({
|
|
55
|
+
name,
|
|
56
|
+
oldVersion: null,
|
|
57
|
+
newVersion: headEntry.version,
|
|
58
|
+
isDirect: true,
|
|
59
|
+
isDev: false,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
else if (baseEntry.version !== headEntry.version) {
|
|
63
|
+
changes.push({
|
|
64
|
+
name,
|
|
65
|
+
oldVersion: baseEntry.version,
|
|
66
|
+
newVersion: headEntry.version,
|
|
67
|
+
isDirect: true,
|
|
68
|
+
isDev: false,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
changes.sort((a, b) => a.name.localeCompare(b.name));
|
|
73
|
+
if (changes.length <= maxPackages) {
|
|
74
|
+
return { changes, skipped: [] };
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
changes: changes.slice(0, maxPackages),
|
|
78
|
+
skipped: changes.slice(maxPackages).map((c) => c.name),
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function normalizePyName(name) {
|
|
82
|
+
return name.toLowerCase().replace(/[-_.]+/g, "-");
|
|
83
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseYarnLock = parseYarnLock;
|
|
4
|
+
/**
|
|
5
|
+
* Parse a yarn.lock v1 (classic) file.
|
|
6
|
+
*
|
|
7
|
+
* Format:
|
|
8
|
+
* "package@^1.0.0", "package@~1.2.0":
|
|
9
|
+
* version "1.2.3"
|
|
10
|
+
* resolved "https://registry.yarnpkg.com/..."
|
|
11
|
+
* integrity sha512-...
|
|
12
|
+
*/
|
|
13
|
+
function parseYarnLock(content) {
|
|
14
|
+
const packages = new Map();
|
|
15
|
+
const lines = content.split("\n");
|
|
16
|
+
let currentName = null;
|
|
17
|
+
let currentEntry = {};
|
|
18
|
+
for (const line of lines) {
|
|
19
|
+
// Skip comments and empty lines
|
|
20
|
+
if (line.startsWith("#") || line.trim() === "") {
|
|
21
|
+
if (currentName && currentEntry.version) {
|
|
22
|
+
packages.set(currentName, { version: currentEntry.version, resolved: currentEntry.resolved, integrity: currentEntry.integrity });
|
|
23
|
+
currentName = null;
|
|
24
|
+
currentEntry = {};
|
|
25
|
+
}
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
// Package header line — not indented, ends with ":"
|
|
29
|
+
if (!line.startsWith(" ") && !line.startsWith("\t") && line.endsWith(":")) {
|
|
30
|
+
// Flush previous entry
|
|
31
|
+
if (currentName && currentEntry.version) {
|
|
32
|
+
packages.set(currentName, { version: currentEntry.version, resolved: currentEntry.resolved, integrity: currentEntry.integrity });
|
|
33
|
+
}
|
|
34
|
+
// Extract package name from first specifier: "name@version" or name@version
|
|
35
|
+
const raw = line.slice(0, -1); // remove trailing ":"
|
|
36
|
+
const firstSpec = raw.split(",")[0].trim().replace(/^"/, "").replace(/"$/, "");
|
|
37
|
+
const atIdx = firstSpec.startsWith("@")
|
|
38
|
+
? firstSpec.indexOf("@", 1)
|
|
39
|
+
: firstSpec.indexOf("@");
|
|
40
|
+
currentName = atIdx > 0 ? firstSpec.slice(0, atIdx) : null;
|
|
41
|
+
currentEntry = {};
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
// Indented property lines
|
|
45
|
+
const trimmed = line.trim();
|
|
46
|
+
const versionMatch = trimmed.match(/^version\s+"([^"]+)"/);
|
|
47
|
+
if (versionMatch) {
|
|
48
|
+
currentEntry.version = versionMatch[1];
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
const resolvedMatch = trimmed.match(/^resolved\s+"([^"]+)"/);
|
|
52
|
+
if (resolvedMatch) {
|
|
53
|
+
currentEntry.resolved = resolvedMatch[1];
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const integrityMatch = trimmed.match(/^integrity\s+(\S+)/);
|
|
57
|
+
if (integrityMatch) {
|
|
58
|
+
currentEntry.integrity = integrityMatch[1];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
// Flush last entry
|
|
62
|
+
if (currentName && currentEntry.version) {
|
|
63
|
+
packages.set(currentName, { version: currentEntry.version, resolved: currentEntry.resolved, integrity: currentEntry.integrity });
|
|
64
|
+
}
|
|
65
|
+
return { lockfileVersion: 1, packages };
|
|
66
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.logger = void 0;
|
|
4
|
+
exports.setLogger = setLogger;
|
|
5
|
+
const consoleLogger = {
|
|
6
|
+
info: (msg) => console.log(`[INFO] ${msg}`),
|
|
7
|
+
warning: (msg) => console.warn(`[WARN] ${msg}`),
|
|
8
|
+
debug: (msg) => {
|
|
9
|
+
if (process.env.DEBUG)
|
|
10
|
+
console.log(`[DEBUG] ${msg}`);
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
let _logger = consoleLogger;
|
|
14
|
+
function setLogger(logger) {
|
|
15
|
+
_logger = logger;
|
|
16
|
+
}
|
|
17
|
+
exports.logger = new Proxy({}, {
|
|
18
|
+
get(_, prop) {
|
|
19
|
+
return _logger[prop] ?? (() => { });
|
|
20
|
+
},
|
|
21
|
+
});
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.h2pool = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* HTTP/2 connection pool for npm registry calls.
|
|
9
|
+
*
|
|
10
|
+
* Multiplexes all requests to the same origin over a single TCP+TLS connection.
|
|
11
|
+
* registry.npmjs.org supports 100 concurrent streams per connection (Cloudflare).
|
|
12
|
+
*
|
|
13
|
+
* Benchmarked: 152 requests via HTTP/2 = 179ms vs fetch() HTTP/1.1 = 895ms (5x faster).
|
|
14
|
+
*/
|
|
15
|
+
const http2_1 = __importDefault(require("http2"));
|
|
16
|
+
const zlib_1 = require("zlib");
|
|
17
|
+
const MAX_CONCURRENT_STREAMS = 100;
|
|
18
|
+
class H2Pool {
|
|
19
|
+
constructor() {
|
|
20
|
+
this.sessions = new Map();
|
|
21
|
+
}
|
|
22
|
+
getSession(origin) {
|
|
23
|
+
let session = this.sessions.get(origin);
|
|
24
|
+
if (session && !session.closed && !session.destroyed)
|
|
25
|
+
return session;
|
|
26
|
+
session = http2_1.default.connect(origin);
|
|
27
|
+
session.on("error", () => {
|
|
28
|
+
this.sessions.delete(origin);
|
|
29
|
+
});
|
|
30
|
+
session.on("close", () => {
|
|
31
|
+
this.sessions.delete(origin);
|
|
32
|
+
});
|
|
33
|
+
// Note: NOT calling session.unref() — sessions must stay alive for the server process
|
|
34
|
+
this.sessions.set(origin, session);
|
|
35
|
+
return session;
|
|
36
|
+
}
|
|
37
|
+
request(url, options = {}) {
|
|
38
|
+
const parsed = new URL(url);
|
|
39
|
+
const origin = parsed.origin;
|
|
40
|
+
const session = this.getSession(origin);
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
const reqHeaders = {
|
|
43
|
+
":path": parsed.pathname + parsed.search,
|
|
44
|
+
":method": options.method || "GET",
|
|
45
|
+
"accept-encoding": "gzip",
|
|
46
|
+
};
|
|
47
|
+
if (options.headers) {
|
|
48
|
+
for (const [k, v] of Object.entries(options.headers)) {
|
|
49
|
+
reqHeaders[k.toLowerCase()] = v;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
let req;
|
|
53
|
+
try {
|
|
54
|
+
req = session.request(reqHeaders);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
// Session may have died — remove and reject
|
|
58
|
+
this.sessions.delete(origin);
|
|
59
|
+
reject(err);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (options.timeout) {
|
|
63
|
+
req.setTimeout(options.timeout, () => {
|
|
64
|
+
req.close(http2_1.default.constants.NGHTTP2_CANCEL);
|
|
65
|
+
reject(new Error(`H2 request timed out after ${options.timeout}ms`));
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
let status = 0;
|
|
69
|
+
let responseHeaders = {};
|
|
70
|
+
const chunks = [];
|
|
71
|
+
req.on("response", (h) => {
|
|
72
|
+
status = h[":status"];
|
|
73
|
+
responseHeaders = h;
|
|
74
|
+
});
|
|
75
|
+
req.on("data", (chunk) => chunks.push(chunk));
|
|
76
|
+
req.on("end", () => {
|
|
77
|
+
const raw = Buffer.concat(chunks);
|
|
78
|
+
const encoding = responseHeaders["content-encoding"];
|
|
79
|
+
let body;
|
|
80
|
+
try {
|
|
81
|
+
body = encoding === "gzip" ? (0, zlib_1.gunzipSync)(raw).toString() : raw.toString();
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// Truncated gzip response from server — treat as empty/failed
|
|
85
|
+
resolve({ status: 0, headers: responseHeaders, body: "" });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
resolve({ status, headers: responseHeaders, body });
|
|
89
|
+
});
|
|
90
|
+
req.on("error", (err) => {
|
|
91
|
+
reject(err);
|
|
92
|
+
});
|
|
93
|
+
if (options.body) {
|
|
94
|
+
req.write(options.body);
|
|
95
|
+
}
|
|
96
|
+
req.end();
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Send many requests in parallel over a single H2 connection.
|
|
101
|
+
* Automatically respects the server's max concurrent streams limit.
|
|
102
|
+
*/
|
|
103
|
+
async requestAll(urls, options) {
|
|
104
|
+
if (urls.length === 0)
|
|
105
|
+
return [];
|
|
106
|
+
const results = new Array(urls.length);
|
|
107
|
+
let nextIdx = 0;
|
|
108
|
+
let completed = 0;
|
|
109
|
+
let inFlight = 0;
|
|
110
|
+
return new Promise((resolve, reject) => {
|
|
111
|
+
let failed = false;
|
|
112
|
+
const launch = () => {
|
|
113
|
+
while (nextIdx < urls.length && inFlight < MAX_CONCURRENT_STREAMS) {
|
|
114
|
+
const idx = nextIdx++;
|
|
115
|
+
inFlight++;
|
|
116
|
+
this.request(urls[idx], options || {})
|
|
117
|
+
.then((res) => {
|
|
118
|
+
results[idx] = res;
|
|
119
|
+
})
|
|
120
|
+
.catch((err) => {
|
|
121
|
+
// Store error as a failed response instead of rejecting everything
|
|
122
|
+
results[idx] = { status: 0, headers: {}, body: "" };
|
|
123
|
+
})
|
|
124
|
+
.finally(() => {
|
|
125
|
+
inFlight--;
|
|
126
|
+
completed++;
|
|
127
|
+
if (completed === urls.length) {
|
|
128
|
+
resolve(results);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
launch();
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
launch();
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
/** Close and remove the session for a specific origin, forcing a fresh connection next time. */
|
|
140
|
+
resetOrigin(origin) {
|
|
141
|
+
const session = this.sessions.get(origin);
|
|
142
|
+
if (session) {
|
|
143
|
+
try {
|
|
144
|
+
session.close();
|
|
145
|
+
}
|
|
146
|
+
catch { /* already closed */ }
|
|
147
|
+
this.sessions.delete(origin);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
close() {
|
|
151
|
+
for (const session of this.sessions.values()) {
|
|
152
|
+
try {
|
|
153
|
+
session.close();
|
|
154
|
+
}
|
|
155
|
+
catch { /* already closed */ }
|
|
156
|
+
}
|
|
157
|
+
this.sessions.clear();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
/** Shared global pool instance — reused across the process lifetime. */
|
|
161
|
+
exports.h2pool = new H2Pool();
|