@vercel/python 6.39.0 → 6.40.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.
Files changed (2) hide show
  1. package/dist/index.js +169 -89
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -4341,6 +4341,11 @@ var PythonDependencyExternalizer = class {
4341
4341
  this.allVendorFiles = {};
4342
4342
  this.totalBundleSize = 0;
4343
4343
  this.analyzed = false;
4344
+ // Resolved once at the start of analyze(). The venv is immutable
4345
+ // after construction (quirks run before the bundle span) so these
4346
+ // do not change between analyze() and generateBundle().
4347
+ this.sitePackageDirs = null;
4348
+ this.distributions = null;
4344
4349
  this.venvPath = options.venvPath;
4345
4350
  this.vendorDir = options.vendorDir;
4346
4351
  this.workPath = options.workPath;
@@ -4371,8 +4376,17 @@ var PythonDependencyExternalizer = class {
4371
4376
  * Must be called before generateBundle().
4372
4377
  */
4373
4378
  async analyze(files) {
4374
- this.allVendorFiles = await mirrorPackagesIntoVendor({
4375
- venvPath: this.venvPath,
4379
+ this.sitePackageDirs = await getVenvSitePackagesDirs(this.venvPath);
4380
+ this.distributions = /* @__PURE__ */ new Map();
4381
+ for (const dir of this.sitePackageDirs) {
4382
+ try {
4383
+ await import_fs5.default.promises.access(dir);
4384
+ } catch {
4385
+ continue;
4386
+ }
4387
+ this.distributions.set(dir, await (0, import_python_analysis3.scanDistributions)(dir));
4388
+ }
4389
+ this.allVendorFiles = await this.mirrorPackagesIntoVendor({
4376
4390
  vendorDirName: this.vendorDir
4377
4391
  });
4378
4392
  const tempFilesForSizing = { ...files };
@@ -4542,7 +4556,7 @@ To fix this, either:
4542
4556
  wheels available.`
4543
4557
  });
4544
4558
  }
4545
- const packageSizes = await calculatePerPackageSizes(this.venvPath);
4559
+ const packageSizes = await this.calculatePerPackageSizes();
4546
4560
  const alwaysBundled = [
4547
4561
  ...classification.privatePackages,
4548
4562
  "vercel-runtime",
@@ -4550,8 +4564,7 @@ To fix this, either:
4550
4564
  ...this.alwaysBundlePackages,
4551
4565
  ...forceBundledDueToWheels
4552
4566
  ];
4553
- const alwaysBundledFiles = await mirrorPackagesIntoVendor({
4554
- venvPath: this.venvPath,
4567
+ const alwaysBundledFiles = await this.mirrorPackagesIntoVendor({
4555
4568
  vendorDirName: this.vendorDir,
4556
4569
  includePackages: alwaysBundled
4557
4570
  });
@@ -4597,8 +4610,7 @@ To fix this, either:
4597
4610
  );
4598
4611
  const bundledPublic = lambdaKnapsack(publicPackageSizes, remainingCapacity);
4599
4612
  const allBundledPackages = [...alwaysBundled, ...bundledPublic];
4600
- const selectedVendorFiles = await mirrorPackagesIntoVendor({
4601
- venvPath: this.venvPath,
4613
+ const selectedVendorFiles = await this.mirrorPackagesIntoVendor({
4602
4614
  vendorDirName: this.vendorDir,
4603
4615
  includePackages: allBundledPackages
4604
4616
  });
@@ -4747,6 +4759,130 @@ splitting your application.`,
4747
4759
  }
4748
4760
  return incompatible;
4749
4761
  }
4762
+ /**
4763
+ * Mirror packages from site-packages into the _vendor directory.
4764
+ *
4765
+ * When `includePackages` is provided, only distributions whose normalized
4766
+ * name is in the list are included. When omitted, every distribution is
4767
+ * included.
4768
+ *
4769
+ * Reads `this.sitePackageDirs` and `this.distributions` which are
4770
+ * resolved once at the start of `analyze()`.
4771
+ */
4772
+ async mirrorPackagesIntoVendor({
4773
+ vendorDirName,
4774
+ includePackages
4775
+ }) {
4776
+ const vendorFiles = {};
4777
+ if (includePackages && includePackages.length === 0) {
4778
+ return vendorFiles;
4779
+ }
4780
+ const includeSet = includePackages ? new Set(includePackages.map(import_python_analysis3.normalizePackageName)) : null;
4781
+ const pending = [];
4782
+ for (const dir of this.sitePackageDirs) {
4783
+ const dirDistributions = this.distributions.get(dir);
4784
+ if (!dirDistributions)
4785
+ continue;
4786
+ const resolvedDir = (0, import_path6.resolve)(dir);
4787
+ const dirPrefix = resolvedDir + import_path6.sep;
4788
+ for (const [name, dist] of dirDistributions) {
4789
+ if (includeSet && !includeSet.has(name))
4790
+ continue;
4791
+ for (const { path: rawPath, size: recordSize } of dist.files) {
4792
+ const filePath = rawPath.replaceAll("/", import_path6.sep);
4793
+ if (!(0, import_path6.resolve)(resolvedDir, filePath).startsWith(dirPrefix)) {
4794
+ continue;
4795
+ }
4796
+ if (filePath.endsWith(".pyc") || filePath.split(import_path6.sep).includes("__pycache__")) {
4797
+ continue;
4798
+ }
4799
+ const srcFsPath = (0, import_path6.join)(dir, filePath);
4800
+ const bundlePath = (0, import_path6.join)(vendorDirName, filePath).replace(/\\/g, "/");
4801
+ pending.push({
4802
+ bundlePath,
4803
+ srcFsPath,
4804
+ // RECORD sizes are bigint; convert to number for FileFsRef.
4805
+ recordSize: recordSize !== void 0 && recordSize !== null ? Number(recordSize) : void 0
4806
+ });
4807
+ }
4808
+ }
4809
+ }
4810
+ const results = await Promise.all(
4811
+ pending.map(async ({ bundlePath, srcFsPath, recordSize }) => {
4812
+ if (recordSize !== void 0) {
4813
+ try {
4814
+ await import_fs5.default.promises.access(srcFsPath);
4815
+ return { bundlePath, srcFsPath, size: recordSize };
4816
+ } catch {
4817
+ return null;
4818
+ }
4819
+ } else {
4820
+ try {
4821
+ const stats = await import_fs5.default.promises.stat(srcFsPath);
4822
+ return { bundlePath, srcFsPath, size: stats.size };
4823
+ } catch {
4824
+ return null;
4825
+ }
4826
+ }
4827
+ })
4828
+ );
4829
+ for (const result of results) {
4830
+ if (result) {
4831
+ vendorFiles[result.bundlePath] = new import_build_utils5.FileFsRef({
4832
+ fsPath: result.srcFsPath,
4833
+ size: result.size
4834
+ });
4835
+ }
4836
+ }
4837
+ (0, import_build_utils5.debug)(
4838
+ `Mirrored ${Object.keys(vendorFiles).length} files` + (includePackages ? ` from ${includePackages.length} packages` : "")
4839
+ );
4840
+ return vendorFiles;
4841
+ }
4842
+ /**
4843
+ * Calculate the uncompressed size of each installed distribution.
4844
+ *
4845
+ * Returns a map of normalized package name to total size in bytes.
4846
+ * Uses RECORD sizes when available, falling back to stat for files
4847
+ * without a recorded size. All stat calls run in parallel.
4848
+ */
4849
+ async calculatePerPackageSizes() {
4850
+ const sizes = /* @__PURE__ */ new Map();
4851
+ for (const dir of this.sitePackageDirs) {
4852
+ const dirDistributions = this.distributions.get(dir);
4853
+ if (!dirDistributions)
4854
+ continue;
4855
+ const resolvedDir = (0, import_path6.resolve)(dir);
4856
+ const dirPrefix = resolvedDir + import_path6.sep;
4857
+ for (const [name, dist] of dirDistributions) {
4858
+ let knownSize = 0;
4859
+ const statPromises = [];
4860
+ for (const { path: rawPath, size: recordSize } of dist.files) {
4861
+ const filePath = rawPath.replaceAll("/", import_path6.sep);
4862
+ if (!(0, import_path6.resolve)(resolvedDir, filePath).startsWith(dirPrefix)) {
4863
+ continue;
4864
+ }
4865
+ if (filePath.endsWith(".pyc") || filePath.split(import_path6.sep).includes("__pycache__")) {
4866
+ continue;
4867
+ }
4868
+ if (recordSize !== void 0 && recordSize !== null) {
4869
+ knownSize += Number(recordSize);
4870
+ } else {
4871
+ statPromises.push(
4872
+ import_fs5.default.promises.stat((0, import_path6.join)(dir, filePath)).then((stats) => stats.size).catch(() => 0)
4873
+ );
4874
+ }
4875
+ }
4876
+ const statSizes = await Promise.all(statPromises);
4877
+ let totalSize = knownSize;
4878
+ for (const s of statSizes) {
4879
+ totalSize += s;
4880
+ }
4881
+ sizes.set(name, totalSize);
4882
+ }
4883
+ }
4884
+ return sizes;
4885
+ }
4750
4886
  };
4751
4887
  async function getPackagesReachableOnPlatform(lockFile, projectName, pythonMajor, pythonMinor, sysPlatform, platformMachine) {
4752
4888
  if (!projectName)
@@ -4806,66 +4942,35 @@ async function getPackagesReachableOnPlatform(lockFile, projectName, pythonMajor
4806
4942
  }
4807
4943
  return visited;
4808
4944
  }
4809
- async function mirrorPackagesIntoVendor({
4810
- venvPath,
4811
- vendorDirName,
4812
- includePackages
4813
- }) {
4814
- const vendorFiles = {};
4815
- if (includePackages && includePackages.length === 0) {
4816
- return vendorFiles;
4817
- }
4818
- const includeSet = includePackages ? new Set(includePackages.map(import_python_analysis3.normalizePackageName)) : null;
4819
- const sitePackageDirs = await getVenvSitePackagesDirs(venvPath);
4820
- for (const dir of sitePackageDirs) {
4821
- if (!import_fs5.default.existsSync(dir))
4822
- continue;
4823
- const resolvedDir = (0, import_path6.resolve)(dir);
4824
- const dirPrefix = resolvedDir + import_path6.sep;
4825
- const distributions = await (0, import_python_analysis3.scanDistributions)(dir);
4826
- for (const [name, dist] of distributions) {
4827
- if (includeSet && !includeSet.has(name))
4828
- continue;
4829
- for (const { path: rawPath } of dist.files) {
4830
- const filePath = rawPath.replaceAll("/", import_path6.sep);
4831
- if (!(0, import_path6.resolve)(resolvedDir, filePath).startsWith(dirPrefix)) {
4832
- continue;
4833
- }
4834
- if (filePath.endsWith(".pyc") || filePath.split(import_path6.sep).includes("__pycache__")) {
4835
- continue;
4836
- }
4837
- const srcFsPath = (0, import_path6.join)(dir, filePath);
4838
- if (!import_fs5.default.existsSync(srcFsPath)) {
4839
- continue;
4840
- }
4841
- const bundlePath = (0, import_path6.join)(vendorDirName, filePath).replace(/\\/g, "/");
4842
- vendorFiles[bundlePath] = new import_build_utils5.FileFsRef({ fsPath: srcFsPath });
4843
- }
4844
- }
4845
- }
4846
- (0, import_build_utils5.debug)(
4847
- `Mirrored ${Object.keys(vendorFiles).length} files` + (includePackages ? ` from ${includePackages.length} packages` : "")
4848
- );
4849
- return vendorFiles;
4850
- }
4851
4945
  async function calculateBundleSize(files) {
4852
- let totalSize = 0;
4946
+ let knownSize = 0;
4947
+ const statPromises = [];
4853
4948
  for (const filePath of Object.keys(files)) {
4854
4949
  const file = files[filePath];
4855
4950
  if ("fsPath" in file && file.fsPath) {
4856
- try {
4857
- const stats = await import_fs5.default.promises.stat(file.fsPath);
4858
- totalSize += stats.size;
4859
- } catch (err) {
4860
- console.warn(
4861
- `Warning: Failed to stat file ${file.fsPath}, size will not be included in bundle calculation: ${err}`
4951
+ const fsRef = file;
4952
+ if (typeof fsRef.size === "number") {
4953
+ knownSize += fsRef.size;
4954
+ } else {
4955
+ statPromises.push(
4956
+ import_fs5.default.promises.stat(fsRef.fsPath).then((stats) => stats.size).catch((err) => {
4957
+ console.warn(
4958
+ `Warning: Failed to stat file ${fsRef.fsPath}, size will not be included in bundle calculation: ${err}`
4959
+ );
4960
+ return 0;
4961
+ })
4862
4962
  );
4863
4963
  }
4864
4964
  } else if ("data" in file) {
4865
4965
  const data = file.data;
4866
- totalSize += typeof data === "string" ? Buffer.byteLength(data) : data.length;
4966
+ knownSize += typeof data === "string" ? Buffer.byteLength(data) : data.length;
4867
4967
  }
4868
4968
  }
4969
+ const statSizes = await Promise.all(statPromises);
4970
+ let totalSize = knownSize;
4971
+ for (const s of statSizes) {
4972
+ totalSize += s;
4973
+ }
4869
4974
  return totalSize;
4870
4975
  }
4871
4976
  function lambdaKnapsack(packages, capacity) {
@@ -4883,36 +4988,6 @@ function lambdaKnapsack(packages, capacity) {
4883
4988
  }
4884
4989
  return bundled;
4885
4990
  }
4886
- async function calculatePerPackageSizes(venvPath) {
4887
- const sizes = /* @__PURE__ */ new Map();
4888
- const sitePackageDirs = await getVenvSitePackagesDirs(venvPath);
4889
- for (const dir of sitePackageDirs) {
4890
- if (!import_fs5.default.existsSync(dir))
4891
- continue;
4892
- const resolvedDir = (0, import_path6.resolve)(dir);
4893
- const dirPrefix = resolvedDir + import_path6.sep;
4894
- const distributions = await (0, import_python_analysis3.scanDistributions)(dir);
4895
- for (const [name, dist] of distributions) {
4896
- let totalSize = 0;
4897
- for (const { path: rawPath } of dist.files) {
4898
- const filePath = rawPath.replaceAll("/", import_path6.sep);
4899
- if (!(0, import_path6.resolve)(resolvedDir, filePath).startsWith(dirPrefix)) {
4900
- continue;
4901
- }
4902
- if (filePath.endsWith(".pyc") || filePath.split(import_path6.sep).includes("__pycache__")) {
4903
- continue;
4904
- }
4905
- try {
4906
- const stats = await import_fs5.default.promises.stat((0, import_path6.join)(dir, filePath));
4907
- totalSize += stats.size;
4908
- } catch {
4909
- }
4910
- }
4911
- sizes.set(name, totalSize);
4912
- }
4913
- }
4914
- return sizes;
4915
- }
4916
4991
 
4917
4992
  // src/diagnostics.ts
4918
4993
  var import_fs6 = __toESM(require("fs"));
@@ -5160,7 +5235,7 @@ function buildCronRouteTable(crons) {
5160
5235
  async function getServiceCrons(opts) {
5161
5236
  const { service, entrypoint, rawEntrypoint, handlerFunction } = opts;
5162
5237
  const isScheduledService = !!service && (0, import_build_utils7.isScheduleTriggeredService)(service);
5163
- if (!isScheduledService || !service.name || typeof service.schedule !== "string") {
5238
+ if (!isScheduledService || !service.name || typeof service.schedule !== "string" && !Array.isArray(service.schedule)) {
5164
5239
  return void 0;
5165
5240
  }
5166
5241
  const cronEntrypoint = entrypoint || rawEntrypoint;
@@ -5187,7 +5262,12 @@ async function getServiceCrons(opts) {
5187
5262
  );
5188
5263
  const moduleName = entrypointToModule(cronEntrypoint);
5189
5264
  const resolvedHandler = handlerFunction ? `${moduleName}:${handlerFunction}` : moduleName;
5190
- return [{ path: cronPath, schedule: service.schedule, resolvedHandler }];
5265
+ const schedules = Array.isArray(service.schedule) ? service.schedule : [service.schedule];
5266
+ return schedules.map((schedule) => ({
5267
+ path: cronPath,
5268
+ schedule,
5269
+ resolvedHandler
5270
+ }));
5191
5271
  }
5192
5272
  async function getServiceCronsDynamic(opts) {
5193
5273
  const {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vercel/python",
3
- "version": "6.39.0",
3
+ "version": "6.40.0",
4
4
  "main": "./dist/index.js",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://vercel.com/docs/runtimes#official-runtimes/python",
@@ -34,9 +34,9 @@
34
34
  "smol-toml": "1.5.2",
35
35
  "vitest": "2.1.4",
36
36
  "which": "3.0.0",
37
+ "@vercel/build-utils": "13.23.0",
37
38
  "@vercel/error-utils": "2.1.0",
38
- "@vercel/python-runtime": "0.13.2",
39
- "@vercel/build-utils": "13.22.0"
39
+ "@vercel/python-runtime": "0.13.2"
40
40
  },
41
41
  "scripts": {
42
42
  "build": "node ../../utils/build-builder.mjs",