@vercel/python-analysis 0.5.0-canary.20260211174907.cdd2da6 → 0.5.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/dist/index.cjs +253 -48
- package/dist/index.d.ts +4 -0
- package/dist/index.js +215 -16
- package/dist/manifest/dist-metadata.d.ts +103 -0
- package/dist/manifest/uv-lock-parser.d.ts +77 -0
- package/dist/util/error.d.ts +7 -1
- package/dist/wasm/interfaces/wasi-random-insecure-seed.d.ts +2 -0
- package/dist/wasm/vercel_python_analysis.core.wasm +0 -0
- package/dist/wasm/vercel_python_analysis.core2.wasm +0 -0
- package/dist/wasm/vercel_python_analysis.core3.wasm +0 -0
- package/dist/wasm/vercel_python_analysis.core4.wasm +0 -0
- package/dist/wasm/vercel_python_analysis.d.ts +60 -0
- package/dist/wasm/vercel_python_analysis.js +734 -20
- package/package.json +1 -1
- /package/dist/{semantic → wasm}/load.d.ts +0 -0
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// src/
|
|
1
|
+
// src/wasm/load.ts
|
|
2
2
|
import { readFile } from "fs/promises";
|
|
3
3
|
import { createRequire } from "module";
|
|
4
4
|
import { dirname, join } from "path";
|
|
@@ -17,7 +17,7 @@ function getWasmDir() {
|
|
|
17
17
|
}
|
|
18
18
|
async function getCoreModule(path4) {
|
|
19
19
|
const wasmPath = join(getWasmDir(), path4);
|
|
20
|
-
const wasmBytes = await readFile(wasmPath);
|
|
20
|
+
const wasmBytes = new Uint8Array(await readFile(wasmPath));
|
|
21
21
|
return WebAssembly.compile(wasmBytes);
|
|
22
22
|
}
|
|
23
23
|
async function importWasmModule() {
|
|
@@ -47,6 +47,96 @@ async function containsAppOrHandler(source) {
|
|
|
47
47
|
return mod.containsAppOrHandler(source);
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
// src/manifest/dist-metadata.ts
|
|
51
|
+
import { readdir, readFile as readFile2 } from "fs/promises";
|
|
52
|
+
import { join as join2 } from "path";
|
|
53
|
+
async function readDistInfoFile(distInfoDir, filename) {
|
|
54
|
+
try {
|
|
55
|
+
return await readFile2(join2(distInfoDir, filename), "utf-8");
|
|
56
|
+
} catch {
|
|
57
|
+
return void 0;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
async function scanDistributions(sitePackagesDir) {
|
|
61
|
+
const mod = await importWasmModule();
|
|
62
|
+
const index = /* @__PURE__ */ new Map();
|
|
63
|
+
let entries;
|
|
64
|
+
try {
|
|
65
|
+
entries = await readdir(sitePackagesDir);
|
|
66
|
+
} catch {
|
|
67
|
+
return index;
|
|
68
|
+
}
|
|
69
|
+
const distInfoDirs = entries.filter((e) => e.endsWith(".dist-info"));
|
|
70
|
+
for (const dirName of distInfoDirs) {
|
|
71
|
+
const distInfoPath = join2(sitePackagesDir, dirName);
|
|
72
|
+
const metadataContent = await readDistInfoFile(distInfoPath, "METADATA");
|
|
73
|
+
if (!metadataContent) {
|
|
74
|
+
console.debug(`Missing METADATA in ${dirName}`);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
let metadata;
|
|
78
|
+
try {
|
|
79
|
+
metadata = mod.parseDistMetadata(
|
|
80
|
+
new TextEncoder().encode(metadataContent)
|
|
81
|
+
);
|
|
82
|
+
} catch (e) {
|
|
83
|
+
console.debug(`Failed to parse METADATA for ${dirName}: ${e}`);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const normalizedName = mod.normalizePackageName(metadata.name);
|
|
87
|
+
let files = [];
|
|
88
|
+
const recordContent = await readDistInfoFile(distInfoPath, "RECORD");
|
|
89
|
+
if (recordContent) {
|
|
90
|
+
try {
|
|
91
|
+
files = mod.parseRecord(recordContent);
|
|
92
|
+
} catch (e) {
|
|
93
|
+
console.warn(`Failed to parse RECORD for ${dirName}: ${e}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
let origin;
|
|
97
|
+
const directUrlContent = await readDistInfoFile(
|
|
98
|
+
distInfoPath,
|
|
99
|
+
"direct_url.json"
|
|
100
|
+
);
|
|
101
|
+
if (directUrlContent) {
|
|
102
|
+
try {
|
|
103
|
+
origin = mod.parseDirectUrl(directUrlContent);
|
|
104
|
+
} catch (e) {
|
|
105
|
+
console.debug(`Failed to parse direct_url.json for ${dirName}: ${e}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const installerContent = await readDistInfoFile(distInfoPath, "INSTALLER");
|
|
109
|
+
const installer = installerContent?.trim() || void 0;
|
|
110
|
+
const dist = {
|
|
111
|
+
name: normalizedName,
|
|
112
|
+
version: metadata.version,
|
|
113
|
+
metadataVersion: metadata.metadataVersion,
|
|
114
|
+
summary: metadata.summary,
|
|
115
|
+
description: metadata.description,
|
|
116
|
+
descriptionContentType: metadata.descriptionContentType,
|
|
117
|
+
requiresDist: metadata.requiresDist,
|
|
118
|
+
requiresPython: metadata.requiresPython,
|
|
119
|
+
providesExtra: metadata.providesExtra,
|
|
120
|
+
author: metadata.author,
|
|
121
|
+
authorEmail: metadata.authorEmail,
|
|
122
|
+
maintainer: metadata.maintainer,
|
|
123
|
+
maintainerEmail: metadata.maintainerEmail,
|
|
124
|
+
license: metadata.license,
|
|
125
|
+
licenseExpression: metadata.licenseExpression,
|
|
126
|
+
classifiers: metadata.classifiers,
|
|
127
|
+
homePage: metadata.homePage,
|
|
128
|
+
projectUrls: metadata.projectUrls,
|
|
129
|
+
platforms: metadata.platforms,
|
|
130
|
+
dynamic: metadata.dynamic,
|
|
131
|
+
files,
|
|
132
|
+
origin,
|
|
133
|
+
installer
|
|
134
|
+
};
|
|
135
|
+
index.set(normalizedName, dist);
|
|
136
|
+
}
|
|
137
|
+
return index;
|
|
138
|
+
}
|
|
139
|
+
|
|
50
140
|
// src/manifest/package.ts
|
|
51
141
|
import path3 from "path";
|
|
52
142
|
import { match as minimatchMatch } from "minimatch";
|
|
@@ -58,7 +148,7 @@ import toml from "smol-toml";
|
|
|
58
148
|
|
|
59
149
|
// src/util/fs.ts
|
|
60
150
|
import path from "path";
|
|
61
|
-
import { readFile as
|
|
151
|
+
import { readFile as readFile3 } from "fs-extra";
|
|
62
152
|
|
|
63
153
|
// src/util/error.ts
|
|
64
154
|
import util from "util";
|
|
@@ -66,7 +156,14 @@ var isErrnoException = (error, code = void 0) => {
|
|
|
66
156
|
return util.types.isNativeError(error) && "code" in error && (code === void 0 || error.code === code);
|
|
67
157
|
};
|
|
68
158
|
var PythonAnalysisError = class extends Error {
|
|
69
|
-
constructor({
|
|
159
|
+
constructor({
|
|
160
|
+
message,
|
|
161
|
+
code,
|
|
162
|
+
path: path4,
|
|
163
|
+
link,
|
|
164
|
+
action,
|
|
165
|
+
fileContent
|
|
166
|
+
}) {
|
|
70
167
|
super(message);
|
|
71
168
|
this.hideStackTrace = true;
|
|
72
169
|
this.name = "PythonAnalysisError";
|
|
@@ -74,13 +171,14 @@ var PythonAnalysisError = class extends Error {
|
|
|
74
171
|
this.path = path4;
|
|
75
172
|
this.link = link;
|
|
76
173
|
this.action = action;
|
|
174
|
+
this.fileContent = fileContent;
|
|
77
175
|
}
|
|
78
176
|
};
|
|
79
177
|
|
|
80
178
|
// src/util/fs.ts
|
|
81
179
|
async function readFileIfExists(file) {
|
|
82
180
|
try {
|
|
83
|
-
return await
|
|
181
|
+
return await readFile3(file);
|
|
84
182
|
} catch (error) {
|
|
85
183
|
if (!isErrnoException(error, "ENOENT")) {
|
|
86
184
|
throw error;
|
|
@@ -135,7 +233,8 @@ function parseRawConfig(content, filename, filetype = void 0) {
|
|
|
135
233
|
throw new PythonAnalysisError({
|
|
136
234
|
message: `Could not parse config file "${filename}": ${error.message}`,
|
|
137
235
|
code: "PYTHON_CONFIG_PARSE_ERROR",
|
|
138
|
-
path: filename
|
|
236
|
+
path: filename,
|
|
237
|
+
fileContent: content
|
|
139
238
|
});
|
|
140
239
|
}
|
|
141
240
|
throw error;
|
|
@@ -153,7 +252,8 @@ function parseConfig(content, filename, schema, filetype = void 0) {
|
|
|
153
252
|
message: `Invalid config in "${filename}":
|
|
154
253
|
${issues}`,
|
|
155
254
|
code: "PYTHON_CONFIG_VALIDATION_ERROR",
|
|
156
|
-
path: filename
|
|
255
|
+
path: filename,
|
|
256
|
+
fileContent: content
|
|
157
257
|
});
|
|
158
258
|
}
|
|
159
259
|
return result.data;
|
|
@@ -981,9 +1081,9 @@ function normalizePathRequirement(rawLine) {
|
|
|
981
1081
|
}
|
|
982
1082
|
return req;
|
|
983
1083
|
}
|
|
984
|
-
function convertRequirementsToPyprojectToml(fileContent,
|
|
1084
|
+
function convertRequirementsToPyprojectToml(fileContent, readFile4) {
|
|
985
1085
|
const pyproject = {};
|
|
986
|
-
const parsed = parseRequirementsFile(fileContent,
|
|
1086
|
+
const parsed = parseRequirementsFile(fileContent, readFile4);
|
|
987
1087
|
const deps = [];
|
|
988
1088
|
const sources = {};
|
|
989
1089
|
for (const req of parsed.requirements) {
|
|
@@ -1042,11 +1142,11 @@ function buildIndexEntries(pipOptions) {
|
|
|
1042
1142
|
}
|
|
1043
1143
|
return indexes;
|
|
1044
1144
|
}
|
|
1045
|
-
function parseRequirementsFile(fileContent,
|
|
1145
|
+
function parseRequirementsFile(fileContent, readFile4) {
|
|
1046
1146
|
const visited = /* @__PURE__ */ new Set();
|
|
1047
|
-
return parseRequirementsFileInternal(fileContent,
|
|
1147
|
+
return parseRequirementsFileInternal(fileContent, readFile4, visited);
|
|
1048
1148
|
}
|
|
1049
|
-
function parseRequirementsFileInternal(fileContent,
|
|
1149
|
+
function parseRequirementsFileInternal(fileContent, readFile4, visited) {
|
|
1050
1150
|
const { cleanedContent, options, pathRequirements, editableRequirements } = extractPipArguments(fileContent);
|
|
1051
1151
|
const hashMap = buildHashMap(fileContent);
|
|
1052
1152
|
const requirements = parsePipRequirementsFile(cleanedContent);
|
|
@@ -1094,18 +1194,18 @@ function parseRequirementsFileInternal(fileContent, readFile3, visited) {
|
|
|
1094
1194
|
normalized.push(norm);
|
|
1095
1195
|
}
|
|
1096
1196
|
}
|
|
1097
|
-
if (
|
|
1197
|
+
if (readFile4) {
|
|
1098
1198
|
for (const refPath of mergedOptions.requirementFiles) {
|
|
1099
1199
|
const refPathKey = normalize(refPath);
|
|
1100
1200
|
if (visited.has(refPathKey)) {
|
|
1101
1201
|
continue;
|
|
1102
1202
|
}
|
|
1103
1203
|
visited.add(refPathKey);
|
|
1104
|
-
const refContent =
|
|
1204
|
+
const refContent = readFile4(refPath);
|
|
1105
1205
|
if (refContent != null) {
|
|
1106
1206
|
const refParsed = parseRequirementsFileInternal(
|
|
1107
1207
|
refContent,
|
|
1108
|
-
|
|
1208
|
+
readFile4,
|
|
1109
1209
|
visited
|
|
1110
1210
|
);
|
|
1111
1211
|
const existingNames = new Set(
|
|
@@ -1973,12 +2073,16 @@ async function maybeLoadRequirementsTxt(root, subdir, fileName) {
|
|
|
1973
2073
|
} catch (error) {
|
|
1974
2074
|
if (error instanceof PythonAnalysisError) {
|
|
1975
2075
|
error.path = requirementsTxtRelPath;
|
|
2076
|
+
if (!error.fileContent) {
|
|
2077
|
+
error.fileContent = requirementsContent;
|
|
2078
|
+
}
|
|
1976
2079
|
throw error;
|
|
1977
2080
|
}
|
|
1978
2081
|
throw new PythonAnalysisError({
|
|
1979
2082
|
message: `could not parse ${fileName}: ${error instanceof Error ? error.message : String(error)}`,
|
|
1980
2083
|
code: "PYTHON_REQUIREMENTS_PARSE_ERROR",
|
|
1981
|
-
path: requirementsTxtRelPath
|
|
2084
|
+
path: requirementsTxtRelPath,
|
|
2085
|
+
fileContent: requirementsContent
|
|
1982
2086
|
});
|
|
1983
2087
|
}
|
|
1984
2088
|
}
|
|
@@ -2038,6 +2142,95 @@ function createMinimalManifest(options = {}) {
|
|
|
2038
2142
|
};
|
|
2039
2143
|
}
|
|
2040
2144
|
|
|
2145
|
+
// src/manifest/uv-lock-parser.ts
|
|
2146
|
+
import toml3 from "smol-toml";
|
|
2147
|
+
function parseUvLock(content, path4) {
|
|
2148
|
+
let parsed;
|
|
2149
|
+
try {
|
|
2150
|
+
parsed = toml3.parse(content);
|
|
2151
|
+
} catch (error) {
|
|
2152
|
+
throw new PythonAnalysisError({
|
|
2153
|
+
message: `Could not parse uv.lock: ${error instanceof Error ? error.message : String(error)}`,
|
|
2154
|
+
code: "PYTHON_UV_LOCK_PARSE_ERROR",
|
|
2155
|
+
path: path4,
|
|
2156
|
+
fileContent: content
|
|
2157
|
+
});
|
|
2158
|
+
}
|
|
2159
|
+
const packages = (parsed.package ?? []).filter((pkg) => pkg.name && pkg.version).map((pkg) => ({
|
|
2160
|
+
name: pkg.name,
|
|
2161
|
+
version: pkg.version,
|
|
2162
|
+
source: pkg.source
|
|
2163
|
+
}));
|
|
2164
|
+
return { version: parsed.version, packages };
|
|
2165
|
+
}
|
|
2166
|
+
var PUBLIC_PYPI_PATTERNS = [
|
|
2167
|
+
"https://pypi.org",
|
|
2168
|
+
"https://files.pythonhosted.org",
|
|
2169
|
+
"pypi.org"
|
|
2170
|
+
];
|
|
2171
|
+
function isPublicPyPIRegistry(registryUrl) {
|
|
2172
|
+
if (!registryUrl)
|
|
2173
|
+
return true;
|
|
2174
|
+
const normalized = registryUrl.toLowerCase();
|
|
2175
|
+
return PUBLIC_PYPI_PATTERNS.some((pattern) => normalized.includes(pattern));
|
|
2176
|
+
}
|
|
2177
|
+
function isPrivatePackageSource(source) {
|
|
2178
|
+
if (!source)
|
|
2179
|
+
return false;
|
|
2180
|
+
if (source.git)
|
|
2181
|
+
return true;
|
|
2182
|
+
if (source.path)
|
|
2183
|
+
return true;
|
|
2184
|
+
if (source.editable)
|
|
2185
|
+
return true;
|
|
2186
|
+
if (source.url)
|
|
2187
|
+
return true;
|
|
2188
|
+
if (source.virtual)
|
|
2189
|
+
return true;
|
|
2190
|
+
if (source.registry && !isPublicPyPIRegistry(source.registry)) {
|
|
2191
|
+
return true;
|
|
2192
|
+
}
|
|
2193
|
+
return false;
|
|
2194
|
+
}
|
|
2195
|
+
function normalizePackageName(name) {
|
|
2196
|
+
return name.toLowerCase().replace(/[-_.]+/g, "-");
|
|
2197
|
+
}
|
|
2198
|
+
function classifyPackages(options) {
|
|
2199
|
+
const { lockFile, excludePackages = [] } = options;
|
|
2200
|
+
const privatePackages = [];
|
|
2201
|
+
const publicPackages = [];
|
|
2202
|
+
const packageVersions = {};
|
|
2203
|
+
const excludeSet = new Set(excludePackages.map(normalizePackageName));
|
|
2204
|
+
for (const pkg of lockFile.packages) {
|
|
2205
|
+
if (excludeSet.has(normalizePackageName(pkg.name))) {
|
|
2206
|
+
continue;
|
|
2207
|
+
}
|
|
2208
|
+
packageVersions[pkg.name] = pkg.version;
|
|
2209
|
+
if (isPrivatePackageSource(pkg.source)) {
|
|
2210
|
+
privatePackages.push(pkg.name);
|
|
2211
|
+
} else {
|
|
2212
|
+
publicPackages.push(pkg.name);
|
|
2213
|
+
}
|
|
2214
|
+
}
|
|
2215
|
+
return { privatePackages, publicPackages, packageVersions };
|
|
2216
|
+
}
|
|
2217
|
+
function generateRuntimeRequirements(classification) {
|
|
2218
|
+
const lines = [
|
|
2219
|
+
"# Auto-generated requirements for runtime installation",
|
|
2220
|
+
"# Private packages are bundled separately and not listed here.",
|
|
2221
|
+
""
|
|
2222
|
+
];
|
|
2223
|
+
for (const pkgName of classification.publicPackages) {
|
|
2224
|
+
const version = classification.packageVersions[pkgName];
|
|
2225
|
+
if (version) {
|
|
2226
|
+
lines.push(`${pkgName}==${version}`);
|
|
2227
|
+
} else {
|
|
2228
|
+
lines.push(pkgName);
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
return lines.join("\n");
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2041
2234
|
// src/manifest/python-selector.ts
|
|
2042
2235
|
function selectPython(constraints, available) {
|
|
2043
2236
|
const warnings = [];
|
|
@@ -2220,9 +2413,15 @@ export {
|
|
|
2220
2413
|
UvConfigSchema,
|
|
2221
2414
|
UvConfigWorkspaceSchema,
|
|
2222
2415
|
UvIndexEntrySchema,
|
|
2416
|
+
classifyPackages,
|
|
2223
2417
|
containsAppOrHandler,
|
|
2224
2418
|
createMinimalManifest,
|
|
2225
2419
|
discoverPythonPackage,
|
|
2420
|
+
generateRuntimeRequirements,
|
|
2421
|
+
isPrivatePackageSource,
|
|
2422
|
+
normalizePackageName,
|
|
2423
|
+
parseUvLock,
|
|
2424
|
+
scanDistributions,
|
|
2226
2425
|
selectPython,
|
|
2227
2426
|
stringifyManifest
|
|
2228
2427
|
};
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Installed distribution metadata scanning.
|
|
3
|
+
*
|
|
4
|
+
* Scans a site-packages directory for .dist-info subdirectories and parses
|
|
5
|
+
* their metadata using WASM-based parsers.
|
|
6
|
+
*
|
|
7
|
+
* Nomenclature strives to follow that of Python's importlib.metadata module:
|
|
8
|
+
* https://docs.python.org/3/library/importlib.metadata.html
|
|
9
|
+
*/
|
|
10
|
+
/** A file record from a RECORD file (analogous to importlib.metadata.PackagePath). */
|
|
11
|
+
export interface PackagePath {
|
|
12
|
+
path: string;
|
|
13
|
+
hash?: string;
|
|
14
|
+
size?: bigint;
|
|
15
|
+
}
|
|
16
|
+
/** PEP 610 direct URL origin info (analogous to importlib.metadata.Distribution.origin). */
|
|
17
|
+
export type DirectUrlInfo = {
|
|
18
|
+
tag: 'local-directory';
|
|
19
|
+
val: {
|
|
20
|
+
url: string;
|
|
21
|
+
editable: boolean;
|
|
22
|
+
};
|
|
23
|
+
} | {
|
|
24
|
+
tag: 'archive';
|
|
25
|
+
val: {
|
|
26
|
+
url: string;
|
|
27
|
+
hash?: string;
|
|
28
|
+
};
|
|
29
|
+
} | {
|
|
30
|
+
tag: 'vcs';
|
|
31
|
+
val: {
|
|
32
|
+
url: string;
|
|
33
|
+
vcs: string;
|
|
34
|
+
commitId?: string;
|
|
35
|
+
requestedRevision?: string;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* An installed distribution parsed from a .dist-info directory.
|
|
40
|
+
* Analogous to importlib.metadata.Distribution.
|
|
41
|
+
*/
|
|
42
|
+
export interface Distribution {
|
|
43
|
+
/** Normalized package name (PEP 503). */
|
|
44
|
+
name: string;
|
|
45
|
+
/** Package version string. */
|
|
46
|
+
version: string;
|
|
47
|
+
/** Metadata version (e.g. "2.1", "2.3"). */
|
|
48
|
+
metadataVersion: string;
|
|
49
|
+
/** One-line summary. */
|
|
50
|
+
summary?: string;
|
|
51
|
+
/** Full description. */
|
|
52
|
+
description?: string;
|
|
53
|
+
/** Description content type (e.g. "text/markdown"). */
|
|
54
|
+
descriptionContentType?: string;
|
|
55
|
+
/** PEP 508 dependency specifiers (analogous to importlib.metadata.requires()). */
|
|
56
|
+
requiresDist: string[];
|
|
57
|
+
/** Python version requirement (e.g. ">=3.8"). */
|
|
58
|
+
requiresPython?: string;
|
|
59
|
+
/** Extra names provided by this distribution. */
|
|
60
|
+
providesExtra: string[];
|
|
61
|
+
/** Author name. */
|
|
62
|
+
author?: string;
|
|
63
|
+
/** Author email. */
|
|
64
|
+
authorEmail?: string;
|
|
65
|
+
/** Maintainer name. */
|
|
66
|
+
maintainer?: string;
|
|
67
|
+
/** Maintainer email. */
|
|
68
|
+
maintainerEmail?: string;
|
|
69
|
+
/** License text. */
|
|
70
|
+
license?: string;
|
|
71
|
+
/** SPDX license expression. */
|
|
72
|
+
licenseExpression?: string;
|
|
73
|
+
/** Trove classifiers. */
|
|
74
|
+
classifiers: string[];
|
|
75
|
+
/** Home page URL. */
|
|
76
|
+
homePage?: string;
|
|
77
|
+
/** Project URLs as [label, url] pairs. */
|
|
78
|
+
projectUrls: [string, string][];
|
|
79
|
+
/** Supported platforms. */
|
|
80
|
+
platforms: string[];
|
|
81
|
+
/** Dynamic metadata fields. */
|
|
82
|
+
dynamic: string[];
|
|
83
|
+
/** Installed files from RECORD (analogous to importlib.metadata.files()). */
|
|
84
|
+
files: PackagePath[];
|
|
85
|
+
/** PEP 610 origin info (analogous to importlib.metadata.Distribution.origin). */
|
|
86
|
+
origin?: DirectUrlInfo;
|
|
87
|
+
/** Installer tool name (from INSTALLER file). */
|
|
88
|
+
installer?: string;
|
|
89
|
+
}
|
|
90
|
+
/** Map of normalized package name to distribution info. */
|
|
91
|
+
export type DistributionIndex = Map<string, Distribution>;
|
|
92
|
+
/**
|
|
93
|
+
* Scan a site-packages directory for installed distributions.
|
|
94
|
+
*
|
|
95
|
+
* Reads .dist-info directories and parses their METADATA, RECORD,
|
|
96
|
+
* direct_url.json, and INSTALLER files.
|
|
97
|
+
*
|
|
98
|
+
* Analogous to importlib.metadata.distributions() but returns an indexed map.
|
|
99
|
+
*
|
|
100
|
+
* @param sitePackagesDir - Absolute path to a site-packages directory
|
|
101
|
+
* @returns Map of normalized package name to distribution info
|
|
102
|
+
*/
|
|
103
|
+
export declare function scanDistributions(sitePackagesDir: string): Promise<DistributionIndex>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A source of a package in a uv.lock file
|
|
3
|
+
*/
|
|
4
|
+
export interface UvLockPackageSource {
|
|
5
|
+
registry?: string;
|
|
6
|
+
url?: string;
|
|
7
|
+
git?: string;
|
|
8
|
+
path?: string;
|
|
9
|
+
editable?: string;
|
|
10
|
+
virtual?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A package entry from a parsed uv.lock file.
|
|
14
|
+
*/
|
|
15
|
+
export interface UvLockPackage {
|
|
16
|
+
name: string;
|
|
17
|
+
version: string;
|
|
18
|
+
source?: UvLockPackageSource;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Parsed uv.lock file structure.
|
|
22
|
+
*/
|
|
23
|
+
export interface UvLockFile {
|
|
24
|
+
version?: number;
|
|
25
|
+
packages: UvLockPackage[];
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Parse the contents of a uv.lock file.
|
|
29
|
+
*
|
|
30
|
+
* @param content - The raw content of the uv.lock file
|
|
31
|
+
* @param path - Optional path to the file for error reporting
|
|
32
|
+
*/
|
|
33
|
+
export declare function parseUvLock(content: string, path?: string): UvLockFile;
|
|
34
|
+
/**
|
|
35
|
+
* Check if a package source indicates it's a private package.
|
|
36
|
+
*
|
|
37
|
+
* Private packages are those from:
|
|
38
|
+
* - Git repositories
|
|
39
|
+
* - Local file paths
|
|
40
|
+
* - Editable installs
|
|
41
|
+
* - Direct URLs
|
|
42
|
+
* - Non-PyPI registry URLs (private PyPI mirrors, custom indexes)
|
|
43
|
+
*/
|
|
44
|
+
export declare function isPrivatePackageSource(source: UvLockPackageSource | undefined): boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Result of classifying packages from a uv.lock file.
|
|
47
|
+
*/
|
|
48
|
+
export interface PackageClassification {
|
|
49
|
+
privatePackages: string[];
|
|
50
|
+
publicPackages: string[];
|
|
51
|
+
packageVersions: Record<string, string>;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Normalize a Python package name according to PEP 503.
|
|
55
|
+
*/
|
|
56
|
+
export declare function normalizePackageName(name: string): string;
|
|
57
|
+
/**
|
|
58
|
+
* Options for classifying packages.
|
|
59
|
+
*/
|
|
60
|
+
export interface ClassifyPackagesOptions {
|
|
61
|
+
lockFile: UvLockFile;
|
|
62
|
+
excludePackages?: string[];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Classify packages from a uv.lock file into private and public categories.
|
|
66
|
+
*
|
|
67
|
+
* This is used for determining which packages can be installed from PyPI
|
|
68
|
+
* at runtime vs. which must be bundled with the deployment.
|
|
69
|
+
*/
|
|
70
|
+
export declare function classifyPackages(options: ClassifyPackagesOptions): PackageClassification;
|
|
71
|
+
/**
|
|
72
|
+
* Generate requirements.txt content for runtime installation.
|
|
73
|
+
*
|
|
74
|
+
* Only includes public packages that will be installed at runtime from PyPI.
|
|
75
|
+
* Private packages should be bundled separately.
|
|
76
|
+
*/
|
|
77
|
+
export declare function generateRuntimeRequirements(classification: PackageClassification): string;
|
package/dist/util/error.d.ts
CHANGED
|
@@ -22,6 +22,11 @@ interface PythonAnalysisErrorProps {
|
|
|
22
22
|
* Optional "action" to display before the `link`, such as "Learn More".
|
|
23
23
|
*/
|
|
24
24
|
action?: string;
|
|
25
|
+
/**
|
|
26
|
+
* The raw content of the file that failed to parse, if available.
|
|
27
|
+
* Useful for diagnostic logging by callers.
|
|
28
|
+
*/
|
|
29
|
+
fileContent?: string;
|
|
25
30
|
}
|
|
26
31
|
/**
|
|
27
32
|
* This error should be thrown from Python analysis functions
|
|
@@ -34,6 +39,7 @@ export declare class PythonAnalysisError extends Error {
|
|
|
34
39
|
path?: string;
|
|
35
40
|
link?: string;
|
|
36
41
|
action?: string;
|
|
37
|
-
|
|
42
|
+
fileContent?: string;
|
|
43
|
+
constructor({ message, code, path, link, action, fileContent, }: PythonAnalysisErrorProps);
|
|
38
44
|
}
|
|
39
45
|
export {};
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -1,18 +1,78 @@
|
|
|
1
1
|
// world root:component/root
|
|
2
|
+
export interface RecordEntry {
|
|
3
|
+
path: string,
|
|
4
|
+
hash?: string,
|
|
5
|
+
size?: bigint,
|
|
6
|
+
}
|
|
7
|
+
export interface DistMetadata {
|
|
8
|
+
metadataVersion: string,
|
|
9
|
+
name: string,
|
|
10
|
+
version: string,
|
|
11
|
+
summary?: string,
|
|
12
|
+
description?: string,
|
|
13
|
+
descriptionContentType?: string,
|
|
14
|
+
requiresDist: Array<string>,
|
|
15
|
+
requiresPython?: string,
|
|
16
|
+
providesExtra: Array<string>,
|
|
17
|
+
author?: string,
|
|
18
|
+
authorEmail?: string,
|
|
19
|
+
maintainer?: string,
|
|
20
|
+
maintainerEmail?: string,
|
|
21
|
+
license?: string,
|
|
22
|
+
licenseExpression?: string,
|
|
23
|
+
classifiers: Array<string>,
|
|
24
|
+
homePage?: string,
|
|
25
|
+
projectUrls: Array<[string, string]>,
|
|
26
|
+
platforms: Array<string>,
|
|
27
|
+
dynamic: Array<string>,
|
|
28
|
+
}
|
|
29
|
+
export interface DirUrlInfo {
|
|
30
|
+
url: string,
|
|
31
|
+
editable: boolean,
|
|
32
|
+
}
|
|
33
|
+
export interface ArchiveUrlInfo {
|
|
34
|
+
url: string,
|
|
35
|
+
hash?: string,
|
|
36
|
+
}
|
|
37
|
+
export interface VcsUrlInfo {
|
|
38
|
+
url: string,
|
|
39
|
+
vcs: string,
|
|
40
|
+
commitId?: string,
|
|
41
|
+
requestedRevision?: string,
|
|
42
|
+
}
|
|
43
|
+
export type DirectUrlInfo = DirectUrlInfoLocalDirectory | DirectUrlInfoArchive | DirectUrlInfoVcs;
|
|
44
|
+
export interface DirectUrlInfoLocalDirectory {
|
|
45
|
+
tag: 'local-directory',
|
|
46
|
+
val: DirUrlInfo,
|
|
47
|
+
}
|
|
48
|
+
export interface DirectUrlInfoArchive {
|
|
49
|
+
tag: 'archive',
|
|
50
|
+
val: ArchiveUrlInfo,
|
|
51
|
+
}
|
|
52
|
+
export interface DirectUrlInfoVcs {
|
|
53
|
+
tag: 'vcs',
|
|
54
|
+
val: VcsUrlInfo,
|
|
55
|
+
}
|
|
2
56
|
import type * as WasiCliEnvironment from './interfaces/wasi-cli-environment.js'; // wasi:cli/environment@0.2.6
|
|
3
57
|
import type * as WasiCliExit from './interfaces/wasi-cli-exit.js'; // wasi:cli/exit@0.2.6
|
|
4
58
|
import type * as WasiCliStderr from './interfaces/wasi-cli-stderr.js'; // wasi:cli/stderr@0.2.6
|
|
5
59
|
import type * as WasiIoError from './interfaces/wasi-io-error.js'; // wasi:io/error@0.2.6
|
|
6
60
|
import type * as WasiIoStreams from './interfaces/wasi-io-streams.js'; // wasi:io/streams@0.2.6
|
|
61
|
+
import type * as WasiRandomInsecureSeed from './interfaces/wasi-random-insecure-seed.js'; // wasi:random/insecure-seed@0.2.6
|
|
7
62
|
export interface ImportObject {
|
|
8
63
|
'wasi:cli/environment@0.2.6': typeof WasiCliEnvironment,
|
|
9
64
|
'wasi:cli/exit@0.2.6': typeof WasiCliExit,
|
|
10
65
|
'wasi:cli/stderr@0.2.6': typeof WasiCliStderr,
|
|
11
66
|
'wasi:io/error@0.2.6': typeof WasiIoError,
|
|
12
67
|
'wasi:io/streams@0.2.6': typeof WasiIoStreams,
|
|
68
|
+
'wasi:random/insecure-seed@0.2.6': typeof WasiRandomInsecureSeed,
|
|
13
69
|
}
|
|
14
70
|
export interface Root {
|
|
15
71
|
containsAppOrHandler(source: string): boolean,
|
|
72
|
+
parseDistMetadata(content: Uint8Array): DistMetadata,
|
|
73
|
+
parseRecord(content: string): Array<RecordEntry>,
|
|
74
|
+
parseDirectUrl(content: string): DirectUrlInfo,
|
|
75
|
+
normalizePackageName(name: string): string,
|
|
16
76
|
}
|
|
17
77
|
|
|
18
78
|
/**
|