File size: 3,497 Bytes
38be44d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
/**
 * @license
 * Copyright 2025 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import fs from 'node:fs';
import { dirname, join } from 'node:path';
import { fileURLToPath } from 'node:url';

const __dirname = dirname(fileURLToPath(import.meta.url));
const root = join(__dirname, '..');
const lockfilePath = join(root, 'package-lock.json');

function readJsonFile(filePath) {
  try {
    const fileContent = fs.readFileSync(filePath, 'utf-8');
    return JSON.parse(fileContent);
  } catch (error) {
    console.error(`Error reading or parsing ${filePath}:`, error);
    return null;
  }
}

console.log('Checking lockfile...');

const lockfile = readJsonFile(lockfilePath);
if (lockfile === null) {
  process.exit(1);
}
const packages = lockfile.packages || {};
const invalidPackages = [];

for (const [location, details] of Object.entries(packages)) {
  // 1. Skip the root package itself.
  if (location === '') {
    continue;
  }

  // 2. Skip local workspace packages.
  // They are identifiable in two ways:
  // a) As a symlink within node_modules.
  // b) As the source package definition, whose path is not in node_modules.
  if (details.link === true || !location.includes('node_modules')) {
    continue;
  }

  // 3. Any remaining package should be a third-party dependency.
  // 1) Registry package with both "resolved" and "integrity" fields is valid.
  if (details.resolved && details.integrity) {
    continue;
  }
  // 2) Git and file dependencies only need a "resolved" field.
  const isGitOrFileDep =
    details.resolved?.startsWith('git') ||
    details.resolved?.startsWith('file:');
  if (isGitOrFileDep) {
    continue;
  }

  // Mark the left dependency as invalid.
  invalidPackages.push(location);
}

if (invalidPackages.length > 0) {
  console.error(
    '\nError: The following dependencies in package-lock.json are missing the "resolved" or "integrity" field:',
  );
  invalidPackages.forEach((pkg) => console.error(`- ${pkg}`));
  process.exitCode = 1;
} else {
  console.log('Lockfile check passed.');
}

// Check that gaxios v7+ with stream corruption bug is NOT resolved in any workspace node_modules.
// gaxios v7.x (versions < 7.1.6) has a bug where Array.toString() joins stream chunks with
// commas, corrupting error response JSON at TCP chunk boundaries.
// See: https://github.com/google-gemini/gemini-cli/pull/21884
function isCorruptedGaxios(version) {
  if (!version) return false;
  const match = version.match(/^7\.(\d+)\.(\d+)/);
  if (match) {
    const minor = parseInt(match[1], 10);
    const patch = parseInt(match[2], 10);
    if (minor < 1 || (minor === 1 && patch < 6)) {
      return true;
    }
  }
  return false;
}

const gaxiosViolations = [];
for (const [location, details] of Object.entries(packages)) {
  if (
    location.match(/(^|\/)node_modules\/gaxios$/) &&
    !location.includes('@google/genai/node_modules') &&
    isCorruptedGaxios(details.version)
  ) {
    gaxiosViolations.push(`${location} (v${details.version})`);
  }
}

if (gaxiosViolations.length > 0) {
  console.error(
    '\nError: gaxios versions with stream corruption bug (v7.x < 7.1.6) detected in workspace node_modules.',
  );
  console.error('See: https://github.com/google-gemini/gemini-cli/pull/21884');
  gaxiosViolations.forEach((v) => console.error(`- ${v}`));
  console.error(
    '\nPlease ensure gaxios resolves to a version containing the fix (>= 7.1.6).',
  );
  process.exitCode = 1;
}

if (!process.exitCode) {
  process.exitCode = 0;
}