@vercel/python-analysis 0.8.0 → 0.8.1

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 CHANGED
@@ -66,9 +66,10 @@ __export(src_exports, {
66
66
  containsAppOrHandler: () => containsAppOrHandler,
67
67
  createMinimalManifest: () => createMinimalManifest,
68
68
  discoverPythonPackage: () => discoverPythonPackage,
69
+ extendDistRecord: () => extendDistRecord,
69
70
  getStringConstant: () => getStringConstant,
70
71
  isPrivatePackageSource: () => isPrivatePackageSource,
71
- normalizePackageName: () => normalizePackageName,
72
+ normalizePackageName: () => normalizePackageName2,
72
73
  parseDjangoSettingsModule: () => parseDjangoSettingsModule,
73
74
  parseUvLock: () => parseUvLock,
74
75
  scanDistributions: () => scanDistributions,
@@ -139,8 +140,51 @@ async function parseDjangoSettingsModule(content) {
139
140
  }
140
141
 
141
142
  // src/manifest/dist-metadata.ts
143
+ var import_node_crypto = require("crypto");
144
+ var import_node_fs = require("fs");
142
145
  var import_promises2 = require("fs/promises");
143
146
  var import_node_path2 = require("path");
147
+
148
+ // src/manifest/pep508.ts
149
+ var EXTRAS_REGEX = /^(.+)\[([^\]]+)\]$/;
150
+ function splitExtras(spec) {
151
+ const match = EXTRAS_REGEX.exec(spec);
152
+ if (!match) {
153
+ return [spec, void 0];
154
+ }
155
+ const extras = match[2].split(",").map((e) => e.trim());
156
+ return [match[1], extras];
157
+ }
158
+ function normalizePackageName(name) {
159
+ return name.toLowerCase().replace(/[-_.]+/g, "-");
160
+ }
161
+ function formatPep508(req) {
162
+ let result = req.name;
163
+ if (req.extras && req.extras.length > 0) {
164
+ result += `[${req.extras.join(",")}]`;
165
+ }
166
+ if (req.url) {
167
+ result += ` @ ${req.url}`;
168
+ } else if (req.version && req.version !== "*") {
169
+ result += req.version;
170
+ }
171
+ if (req.markers) {
172
+ result += ` ; ${req.markers}`;
173
+ }
174
+ return result;
175
+ }
176
+ function mergeExtras(existing, additional) {
177
+ const result = new Set(existing || []);
178
+ if (additional) {
179
+ const additionalArray = Array.isArray(additional) ? additional : [additional];
180
+ for (const extra of additionalArray) {
181
+ result.add(extra);
182
+ }
183
+ }
184
+ return result.size > 0 ? Array.from(result) : void 0;
185
+ }
186
+
187
+ // src/manifest/dist-metadata.ts
144
188
  async function readDistInfoFile(distInfoDir, filename) {
145
189
  try {
146
190
  return await (0, import_promises2.readFile)((0, import_node_path2.join)(distInfoDir, filename), "utf-8");
@@ -227,6 +271,62 @@ async function scanDistributions(sitePackagesDir) {
227
271
  }
228
272
  return index;
229
273
  }
274
+ function hashFile(filePath) {
275
+ return new Promise((resolve, reject) => {
276
+ const h = (0, import_node_crypto.createHash)("sha256");
277
+ let size = 0;
278
+ const stream = (0, import_node_fs.createReadStream)(filePath);
279
+ stream.on("data", (chunk) => {
280
+ size += chunk.length;
281
+ h.update(chunk);
282
+ });
283
+ stream.on("error", reject);
284
+ stream.on("end", () => {
285
+ resolve({ hash: h.digest("base64url"), size });
286
+ });
287
+ });
288
+ }
289
+ async function extendDistRecord(sitePackagesDir, packageName, paths) {
290
+ const normalizedTarget = normalizePackageName(packageName);
291
+ const entries = await (0, import_promises2.readdir)(sitePackagesDir);
292
+ const distInfoDirName = entries.find((e) => {
293
+ if (!e.endsWith(".dist-info"))
294
+ return false;
295
+ const withoutSuffix = e.slice(0, -".dist-info".length);
296
+ const lastHyphen = withoutSuffix.lastIndexOf("-");
297
+ if (lastHyphen === -1)
298
+ return false;
299
+ const dirName = withoutSuffix.slice(0, lastHyphen);
300
+ return normalizePackageName(dirName) === normalizedTarget;
301
+ });
302
+ if (!distInfoDirName) {
303
+ throw new Error(
304
+ `No .dist-info directory found for package "${packageName}" in ${sitePackagesDir}`
305
+ );
306
+ }
307
+ const recordPath = (0, import_node_path2.join)(sitePackagesDir, distInfoDirName, "RECORD");
308
+ let existingRecord;
309
+ try {
310
+ existingRecord = await (0, import_promises2.readFile)(recordPath, "utf-8");
311
+ } catch {
312
+ throw new Error(`RECORD file not found in ${distInfoDirName}`);
313
+ }
314
+ const existingPaths = new Set(
315
+ existingRecord.split("\n").filter((line) => line.length > 0).map((line) => line.split(",")[0])
316
+ );
317
+ const newEntries = paths.filter((p) => !existingPaths.has(p));
318
+ if (newEntries.length > 0) {
319
+ const prefix = existingRecord.length > 0 && !existingRecord.endsWith("\n") ? "\n" : "";
320
+ const lines = [];
321
+ for (const p of newEntries) {
322
+ const fullPath = (0, import_node_path2.join)(sitePackagesDir, p);
323
+ const { hash, size } = await hashFile(fullPath);
324
+ lines.push(`${p},sha256=${hash},${size}`);
325
+ }
326
+ await (0, import_promises2.appendFile)(recordPath, prefix + lines.join("\n") + "\n");
327
+ }
328
+ return newEntries.length;
329
+ }
230
330
 
231
331
  // src/manifest/package.ts
232
332
  var import_node_path6 = __toESM(require("path"), 1);
@@ -447,42 +547,6 @@ var PipfileLikeSchema = pipfileLikeSchema;
447
547
  var PipfileLockMetaSchema = pipfileLockMetaSchema.passthrough();
448
548
  var PipfileLockLikeSchema = pipfileLockLikeSchema;
449
549
 
450
- // src/manifest/pep508.ts
451
- var EXTRAS_REGEX = /^(.+)\[([^\]]+)\]$/;
452
- function splitExtras(spec) {
453
- const match = EXTRAS_REGEX.exec(spec);
454
- if (!match) {
455
- return [spec, void 0];
456
- }
457
- const extras = match[2].split(",").map((e) => e.trim());
458
- return [match[1], extras];
459
- }
460
- function formatPep508(req) {
461
- let result = req.name;
462
- if (req.extras && req.extras.length > 0) {
463
- result += `[${req.extras.join(",")}]`;
464
- }
465
- if (req.url) {
466
- result += ` @ ${req.url}`;
467
- } else if (req.version && req.version !== "*") {
468
- result += req.version;
469
- }
470
- if (req.markers) {
471
- result += ` ; ${req.markers}`;
472
- }
473
- return result;
474
- }
475
- function mergeExtras(existing, additional) {
476
- const result = new Set(existing || []);
477
- if (additional) {
478
- const additionalArray = Array.isArray(additional) ? additional : [additional];
479
- for (const extra of additionalArray) {
480
- result.add(extra);
481
- }
482
- }
483
- return result.size > 0 ? Array.from(result) : void 0;
484
- }
485
-
486
550
  // src/util/type.ts
487
551
  function isPlainObject(value) {
488
552
  return value != null && typeof value === "object" && !Array.isArray(value);
@@ -2280,7 +2344,7 @@ function isPrivatePackageSource(source) {
2280
2344
  }
2281
2345
  return false;
2282
2346
  }
2283
- function normalizePackageName(name) {
2347
+ function normalizePackageName2(name) {
2284
2348
  return name.toLowerCase().replace(/[-_.]+/g, "-");
2285
2349
  }
2286
2350
  function classifyPackages(options) {
@@ -2288,9 +2352,9 @@ function classifyPackages(options) {
2288
2352
  const privatePackages = [];
2289
2353
  const publicPackages = [];
2290
2354
  const packageVersions = {};
2291
- const excludeSet = new Set(excludePackages.map(normalizePackageName));
2355
+ const excludeSet = new Set(excludePackages.map(normalizePackageName2));
2292
2356
  for (const pkg of lockFile.packages) {
2293
- if (excludeSet.has(normalizePackageName(pkg.name))) {
2357
+ if (excludeSet.has(normalizePackageName2(pkg.name))) {
2294
2358
  continue;
2295
2359
  }
2296
2360
  packageVersions[pkg.name] = pkg.version;
@@ -2490,6 +2554,7 @@ var HashDigestSchema = hashDigestSchema;
2490
2554
  containsAppOrHandler,
2491
2555
  createMinimalManifest,
2492
2556
  discoverPythonPackage,
2557
+ extendDistRecord,
2493
2558
  getStringConstant,
2494
2559
  isPrivatePackageSource,
2495
2560
  normalizePackageName,
package/dist/index.d.ts CHANGED
@@ -8,7 +8,7 @@
8
8
  */
9
9
  export { containsAppOrHandler, getStringConstant, parseDjangoSettingsModule, } from './semantic/entrypoints';
10
10
  export type { Distribution, DistributionIndex, PackagePath, DirectUrlInfo, } from './manifest/dist-metadata';
11
- export { scanDistributions } from './manifest/dist-metadata';
11
+ export { extendDistRecord, scanDistributions } from './manifest/dist-metadata';
12
12
  export type { PythonConfig, PythonConfigs, PythonLockFile, PythonManifest, PythonManifestOrigin, PythonPackage, PythonVersionConfig, } from './manifest/package';
13
13
  export { discoverPythonPackage, PythonConfigKind, PythonLockFileKind, PythonManifestConvertedKind, PythonManifestKind, } from './manifest/package';
14
14
  export { createMinimalManifest, stringifyManifest, type CreateMinimalManifestOptions, } from './manifest/serialize';
package/dist/index.js CHANGED
@@ -60,8 +60,51 @@ async function parseDjangoSettingsModule(content) {
60
60
  }
61
61
 
62
62
  // src/manifest/dist-metadata.ts
63
- import { readdir, readFile as readFile2 } from "fs/promises";
63
+ import { createHash } from "crypto";
64
+ import { createReadStream } from "fs";
65
+ import { appendFile, readdir, readFile as readFile2 } from "fs/promises";
64
66
  import { join as join2 } from "path";
67
+
68
+ // src/manifest/pep508.ts
69
+ var EXTRAS_REGEX = /^(.+)\[([^\]]+)\]$/;
70
+ function splitExtras(spec) {
71
+ const match = EXTRAS_REGEX.exec(spec);
72
+ if (!match) {
73
+ return [spec, void 0];
74
+ }
75
+ const extras = match[2].split(",").map((e) => e.trim());
76
+ return [match[1], extras];
77
+ }
78
+ function normalizePackageName(name) {
79
+ return name.toLowerCase().replace(/[-_.]+/g, "-");
80
+ }
81
+ function formatPep508(req) {
82
+ let result = req.name;
83
+ if (req.extras && req.extras.length > 0) {
84
+ result += `[${req.extras.join(",")}]`;
85
+ }
86
+ if (req.url) {
87
+ result += ` @ ${req.url}`;
88
+ } else if (req.version && req.version !== "*") {
89
+ result += req.version;
90
+ }
91
+ if (req.markers) {
92
+ result += ` ; ${req.markers}`;
93
+ }
94
+ return result;
95
+ }
96
+ function mergeExtras(existing, additional) {
97
+ const result = new Set(existing || []);
98
+ if (additional) {
99
+ const additionalArray = Array.isArray(additional) ? additional : [additional];
100
+ for (const extra of additionalArray) {
101
+ result.add(extra);
102
+ }
103
+ }
104
+ return result.size > 0 ? Array.from(result) : void 0;
105
+ }
106
+
107
+ // src/manifest/dist-metadata.ts
65
108
  async function readDistInfoFile(distInfoDir, filename) {
66
109
  try {
67
110
  return await readFile2(join2(distInfoDir, filename), "utf-8");
@@ -148,6 +191,62 @@ async function scanDistributions(sitePackagesDir) {
148
191
  }
149
192
  return index;
150
193
  }
194
+ function hashFile(filePath) {
195
+ return new Promise((resolve, reject) => {
196
+ const h = createHash("sha256");
197
+ let size = 0;
198
+ const stream = createReadStream(filePath);
199
+ stream.on("data", (chunk) => {
200
+ size += chunk.length;
201
+ h.update(chunk);
202
+ });
203
+ stream.on("error", reject);
204
+ stream.on("end", () => {
205
+ resolve({ hash: h.digest("base64url"), size });
206
+ });
207
+ });
208
+ }
209
+ async function extendDistRecord(sitePackagesDir, packageName, paths) {
210
+ const normalizedTarget = normalizePackageName(packageName);
211
+ const entries = await readdir(sitePackagesDir);
212
+ const distInfoDirName = entries.find((e) => {
213
+ if (!e.endsWith(".dist-info"))
214
+ return false;
215
+ const withoutSuffix = e.slice(0, -".dist-info".length);
216
+ const lastHyphen = withoutSuffix.lastIndexOf("-");
217
+ if (lastHyphen === -1)
218
+ return false;
219
+ const dirName = withoutSuffix.slice(0, lastHyphen);
220
+ return normalizePackageName(dirName) === normalizedTarget;
221
+ });
222
+ if (!distInfoDirName) {
223
+ throw new Error(
224
+ `No .dist-info directory found for package "${packageName}" in ${sitePackagesDir}`
225
+ );
226
+ }
227
+ const recordPath = join2(sitePackagesDir, distInfoDirName, "RECORD");
228
+ let existingRecord;
229
+ try {
230
+ existingRecord = await readFile2(recordPath, "utf-8");
231
+ } catch {
232
+ throw new Error(`RECORD file not found in ${distInfoDirName}`);
233
+ }
234
+ const existingPaths = new Set(
235
+ existingRecord.split("\n").filter((line) => line.length > 0).map((line) => line.split(",")[0])
236
+ );
237
+ const newEntries = paths.filter((p) => !existingPaths.has(p));
238
+ if (newEntries.length > 0) {
239
+ const prefix = existingRecord.length > 0 && !existingRecord.endsWith("\n") ? "\n" : "";
240
+ const lines = [];
241
+ for (const p of newEntries) {
242
+ const fullPath = join2(sitePackagesDir, p);
243
+ const { hash, size } = await hashFile(fullPath);
244
+ lines.push(`${p},sha256=${hash},${size}`);
245
+ }
246
+ await appendFile(recordPath, prefix + lines.join("\n") + "\n");
247
+ }
248
+ return newEntries.length;
249
+ }
151
250
 
152
251
  // src/manifest/package.ts
153
252
  import path3 from "path";
@@ -371,42 +470,6 @@ var PipfileLikeSchema = pipfileLikeSchema;
371
470
  var PipfileLockMetaSchema = pipfileLockMetaSchema.passthrough();
372
471
  var PipfileLockLikeSchema = pipfileLockLikeSchema;
373
472
 
374
- // src/manifest/pep508.ts
375
- var EXTRAS_REGEX = /^(.+)\[([^\]]+)\]$/;
376
- function splitExtras(spec) {
377
- const match = EXTRAS_REGEX.exec(spec);
378
- if (!match) {
379
- return [spec, void 0];
380
- }
381
- const extras = match[2].split(",").map((e) => e.trim());
382
- return [match[1], extras];
383
- }
384
- function formatPep508(req) {
385
- let result = req.name;
386
- if (req.extras && req.extras.length > 0) {
387
- result += `[${req.extras.join(",")}]`;
388
- }
389
- if (req.url) {
390
- result += ` @ ${req.url}`;
391
- } else if (req.version && req.version !== "*") {
392
- result += req.version;
393
- }
394
- if (req.markers) {
395
- result += ` ; ${req.markers}`;
396
- }
397
- return result;
398
- }
399
- function mergeExtras(existing, additional) {
400
- const result = new Set(existing || []);
401
- if (additional) {
402
- const additionalArray = Array.isArray(additional) ? additional : [additional];
403
- for (const extra of additionalArray) {
404
- result.add(extra);
405
- }
406
- }
407
- return result.size > 0 ? Array.from(result) : void 0;
408
- }
409
-
410
473
  // src/util/type.ts
411
474
  function isPlainObject(value) {
412
475
  return value != null && typeof value === "object" && !Array.isArray(value);
@@ -2204,7 +2267,7 @@ function isPrivatePackageSource(source) {
2204
2267
  }
2205
2268
  return false;
2206
2269
  }
2207
- function normalizePackageName(name) {
2270
+ function normalizePackageName2(name) {
2208
2271
  return name.toLowerCase().replace(/[-_.]+/g, "-");
2209
2272
  }
2210
2273
  function classifyPackages(options) {
@@ -2212,9 +2275,9 @@ function classifyPackages(options) {
2212
2275
  const privatePackages = [];
2213
2276
  const publicPackages = [];
2214
2277
  const packageVersions = {};
2215
- const excludeSet = new Set(excludePackages.map(normalizePackageName));
2278
+ const excludeSet = new Set(excludePackages.map(normalizePackageName2));
2216
2279
  for (const pkg of lockFile.packages) {
2217
- if (excludeSet.has(normalizePackageName(pkg.name))) {
2280
+ if (excludeSet.has(normalizePackageName2(pkg.name))) {
2218
2281
  continue;
2219
2282
  }
2220
2283
  packageVersions[pkg.name] = pkg.version;
@@ -2413,9 +2476,10 @@ export {
2413
2476
  containsAppOrHandler,
2414
2477
  createMinimalManifest,
2415
2478
  discoverPythonPackage,
2479
+ extendDistRecord,
2416
2480
  getStringConstant,
2417
2481
  isPrivatePackageSource,
2418
- normalizePackageName,
2482
+ normalizePackageName2 as normalizePackageName,
2419
2483
  parseDjangoSettingsModule,
2420
2484
  parseUvLock,
2421
2485
  scanDistributions,
@@ -101,3 +101,17 @@ export type DistributionIndex = Map<string, Distribution>;
101
101
  * @returns Map of normalized package name to distribution info
102
102
  */
103
103
  export declare function scanDistributions(sitePackagesDir: string): Promise<DistributionIndex>;
104
+ /**
105
+ * Append new file entries to a distribution's RECORD file.
106
+ *
107
+ * Finds the `.dist-info` directory for the given package, reads its RECORD,
108
+ * and appends any paths not already tracked. The caller is responsible for
109
+ * scanning directories and building the path list.
110
+ *
111
+ * @param sitePackagesDir - Absolute path to a site-packages directory
112
+ * @param packageName - Distribution name (matched via PEP 503 normalization)
113
+ * @param paths - File paths relative to site-packages to add
114
+ * @returns Count of new entries appended (skips paths already in RECORD)
115
+ * @throws If the dist-info directory or RECORD file is not found
116
+ */
117
+ export declare function extendDistRecord(sitePackagesDir: string, packageName: string, paths: string[]): Promise<number>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/python-analysis",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
4
4
  "main": "./dist/index.cjs",
5
5
  "module": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",