@netlify/build 35.11.2 → 35.13.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.
@@ -48,7 +48,7 @@ export interface NetlifyPluginConstants {
48
48
  EDGE_FUNCTIONS_SRC?: string;
49
49
  /**
50
50
  * the directory where database migration source files live.
51
- * `undefined` if no `netlify/db/migrations` directory exists and if not specified by the user.
51
+ * `undefined` if no `netlify/database/migrations` directory exists and if not specified by the user.
52
52
  */
53
53
  DB_MIGRATIONS_SRC?: string;
54
54
  /**
@@ -100,7 +100,7 @@ export declare const getConstants: ({ configPath, buildDir, packagePath, functio
100
100
  token: any;
101
101
  mode: any;
102
102
  }) => Promise<NetlifyPluginConstants>;
103
- export declare const addMutableConstants: ({ constants, buildDir, netlifyConfig: { build: { publish, edge_functions: edgeFunctions }, functionsDirectory, db, }, }: {
103
+ export declare const addMutableConstants: ({ constants, buildDir, netlifyConfig: { build: { publish, edge_functions: edgeFunctions }, functionsDirectory, database, }, }: {
104
104
  constants: any;
105
105
  buildDir: any;
106
106
  netlifyConfig: {
@@ -109,7 +109,7 @@ export declare const addMutableConstants: ({ constants, buildDir, netlifyConfig:
109
109
  edge_functions: any;
110
110
  };
111
111
  functionsDirectory: any;
112
- db: any;
112
+ database: any;
113
113
  };
114
114
  }) => Promise<{
115
115
  [k: string]: string | boolean | undefined;
@@ -52,7 +52,7 @@ const DB_MIGRATIONS_DIST = '.netlify/internal/db/migrations';
52
52
  // Retrieve constants which might change during the build if a plugin modifies
53
53
  // `netlifyConfig` or creates some default directories.
54
54
  // Unlike readonly constants, this is called again before each build step.
55
- export const addMutableConstants = async function ({ constants, buildDir, netlifyConfig: { build: { publish, edge_functions: edgeFunctions }, functionsDirectory, db, }, }) {
55
+ export const addMutableConstants = async function ({ constants, buildDir, netlifyConfig: { build: { publish, edge_functions: edgeFunctions }, functionsDirectory, database, }, }) {
56
56
  const constantsA = {
57
57
  ...constants,
58
58
  // Directory that contains the deploy-ready HTML files and assets generated by the build
@@ -62,7 +62,7 @@ export const addMutableConstants = async function ({ constants, buildDir, netlif
62
62
  // The directory where Edge Functions source code lives
63
63
  EDGE_FUNCTIONS_SRC: edgeFunctions,
64
64
  // The directory where database migration source files live
65
- DB_MIGRATIONS_SRC: db?.migrations?.path,
65
+ DB_MIGRATIONS_SRC: database?.migrations?.path,
66
66
  };
67
67
  const constantsB = await addDefaultConstants(constantsA, buildDir);
68
68
  const constantsC = normalizeConstantsPaths(constantsB, buildDir);
@@ -84,7 +84,7 @@ const DEFAULT_PATHS = [
84
84
  { constantName: 'FUNCTIONS_SRC', defaultPath: 'netlify-automatic-functions' },
85
85
  { constantName: 'FUNCTIONS_SRC', defaultPath: 'netlify/functions' },
86
86
  { constantName: 'EDGE_FUNCTIONS_SRC', defaultPath: 'netlify/edge-functions' },
87
- { constantName: 'DB_MIGRATIONS_SRC', defaultPath: 'netlify/db/migrations' },
87
+ { constantName: 'DB_MIGRATIONS_SRC', defaultPath: 'netlify/database/migrations' },
88
88
  ];
89
89
  const addDefaultConstant = async function ({ constants, constantName, defaultPath, buildDir }) {
90
90
  // Configuration paths are relative to the build directory.
@@ -1,8 +1,10 @@
1
1
  import { join } from 'node:path';
2
2
  import { logDbProvisioning, logDbMigrations } from '../../log/messages/core_steps.js';
3
3
  import { getPackageJson } from '../../utils/package.js';
4
- import { readMigrationEntries, getMigrationNames } from './utils.js';
5
- const NPM_PACKAGE_NAME = '@netlify/db';
4
+ import { readMigrationEntries, getMigrationNames, getMigrationsSrc } from './utils.js';
5
+ const NPM_PACKAGE_NAME = '@netlify/database';
6
+ // TODO: Remove once we stop supporting the legacy `@netlify/db` package name.
7
+ const NPM_PACKAGE_NAME_LEGACY = '@netlify/db';
6
8
  const condition = async ({ buildDir, packagePath, featureFlags }) => {
7
9
  if (!featureFlags?.netlify_build_db_setup) {
8
10
  return false;
@@ -22,10 +24,11 @@ const condition = async ({ buildDir, packagePath, featureFlags }) => {
22
24
  const coreStep = async ({ api, branch, buildDir, constants, context, logs }) => {
23
25
  const siteId = constants.SITE_ID;
24
26
  logDbProvisioning({ logs, branch, context });
25
- const entries = await readMigrationEntries(buildDir, constants.DB_MIGRATIONS_SRC);
27
+ const migrationsSrc = await getMigrationsSrc(buildDir, constants.DB_MIGRATIONS_SRC);
28
+ const entries = await readMigrationEntries(buildDir, migrationsSrc);
26
29
  const migrationNames = getMigrationNames(entries);
27
- if (migrationNames.length > 0) {
28
- logDbMigrations({ logs, migrations: migrationNames, srcDir: constants.DB_MIGRATIONS_SRC });
30
+ if (migrationNames.length > 0 && migrationsSrc) {
31
+ logDbMigrations({ logs, migrations: migrationNames, srcDir: migrationsSrc });
29
32
  }
30
33
  // @ts-expect-error This is an internal method for now so it isn't typed yet.
31
34
  // eslint-disable-next-line @typescript-eslint/no-unsafe-call
@@ -45,13 +48,16 @@ const coreStep = async ({ api, branch, buildDir, constants, context, logs }) =>
45
48
  };
46
49
  const hasDBPackage = (packageJSON) => {
47
50
  const { dependencies = {}, devDependencies = {} } = packageJSON;
48
- return NPM_PACKAGE_NAME in dependencies || NPM_PACKAGE_NAME in devDependencies;
51
+ return (NPM_PACKAGE_NAME in dependencies ||
52
+ NPM_PACKAGE_NAME in devDependencies ||
53
+ NPM_PACKAGE_NAME_LEGACY in dependencies ||
54
+ NPM_PACKAGE_NAME_LEGACY in devDependencies);
49
55
  };
50
56
  export const dbSetup = {
51
57
  event: 'onPreBuild',
52
58
  coreStep,
53
59
  coreStepId: 'db_provision',
54
- coreStepName: 'Netlify DB setup',
55
- coreStepDescription: () => 'Netlify DB setup',
60
+ coreStepName: 'Netlify Database setup',
61
+ coreStepDescription: () => 'Netlify Database setup',
56
62
  condition,
57
63
  };
@@ -1,22 +1,23 @@
1
1
  import { copyFile, mkdir } from 'node:fs/promises';
2
2
  import { join, resolve } from 'node:path';
3
3
  import { pathExists } from 'path-exists';
4
- import { readMigrationEntries } from './utils.js';
4
+ import { readMigrationEntries, getMigrationsSrc } from './utils.js';
5
5
  import { validateMigrations, formatValidationErrors } from './validation.js';
6
6
  const condition = async ({ featureFlags, constants, buildDir }) => {
7
7
  if (!featureFlags?.netlify_build_db_setup) {
8
8
  return false;
9
9
  }
10
- const srcDir = constants.DB_MIGRATIONS_SRC;
10
+ const srcDir = await getMigrationsSrc(buildDir, constants.DB_MIGRATIONS_SRC);
11
11
  if (!srcDir) {
12
12
  return false;
13
13
  }
14
14
  return pathExists(resolve(buildDir, srcDir));
15
15
  };
16
16
  const coreStep = async ({ constants, buildDir, systemLog }) => {
17
- const srcDir = resolve(buildDir, constants.DB_MIGRATIONS_SRC);
17
+ const migrationsSrc = await getMigrationsSrc(buildDir, constants.DB_MIGRATIONS_SRC);
18
+ const srcDir = resolve(buildDir, migrationsSrc);
18
19
  const destDir = resolve(buildDir, constants.DB_MIGRATIONS_DIST);
19
- const { dirNames, fileNames } = await readMigrationEntries(buildDir, constants.DB_MIGRATIONS_SRC);
20
+ const { dirNames, fileNames } = await readMigrationEntries(buildDir, migrationsSrc);
20
21
  if (dirNames.length === 0 && fileNames.length === 0) {
21
22
  systemLog('No migration directories found, skipping copy.');
22
23
  return {};
@@ -52,7 +53,7 @@ export const copyDbMigrations = {
52
53
  event: 'onBuild',
53
54
  coreStep,
54
55
  coreStepId: 'db_migrations_copy',
55
- coreStepName: 'Netlify DB migrations',
56
+ coreStepName: 'Netlify Database migrations',
56
57
  coreStepDescription: () => 'Copy database migrations to internal directory',
57
58
  condition,
58
59
  quiet: true,
@@ -1,3 +1,13 @@
1
+ /**
2
+ * Returns the effective migrations source directory for the current build.
3
+ *
4
+ * If the user has set `database.migrations.path` in their config, or the new
5
+ * default `netlify/database/migrations` directory exists, that value will have
6
+ * been populated into `constants.DB_MIGRATIONS_SRC` upstream. If not, we fall
7
+ * back to the legacy `netlify/db/migrations` directory for backwards
8
+ * compatibility.
9
+ */
10
+ export declare const getMigrationsSrc: (buildDir: string, configuredSrc: string | undefined) => Promise<string | undefined>;
1
11
  export interface MigrationEntries {
2
12
  dirNames: string[];
3
13
  fileNames: string[];
@@ -1,6 +1,27 @@
1
1
  import { readdir, stat } from 'node:fs/promises';
2
2
  import { join, resolve } from 'node:path';
3
3
  import { pathExists } from 'path-exists';
4
+ // TODO: Remove once we drop support for the legacy `netlify/db/migrations` directory.
5
+ const LEGACY_DB_MIGRATIONS_SRC = 'netlify/db/migrations';
6
+ /**
7
+ * Returns the effective migrations source directory for the current build.
8
+ *
9
+ * If the user has set `database.migrations.path` in their config, or the new
10
+ * default `netlify/database/migrations` directory exists, that value will have
11
+ * been populated into `constants.DB_MIGRATIONS_SRC` upstream. If not, we fall
12
+ * back to the legacy `netlify/db/migrations` directory for backwards
13
+ * compatibility.
14
+ */
15
+ // TODO: Remove the legacy fallback once we drop support for `netlify/db/migrations`.
16
+ export const getMigrationsSrc = async (buildDir, configuredSrc) => {
17
+ if (configuredSrc) {
18
+ return configuredSrc;
19
+ }
20
+ if (await pathExists(resolve(buildDir, LEGACY_DB_MIGRATIONS_SRC))) {
21
+ return LEGACY_DB_MIGRATIONS_SRC;
22
+ }
23
+ return undefined;
24
+ };
4
25
  export const readMigrationEntries = async (buildDir, migrationsSrc) => {
5
26
  const empty = { dirNames: [], fileNames: [] };
6
27
  if (!migrationsSrc) {
@@ -1,9 +1,15 @@
1
1
  export declare const MIGRATION_DIR_PATTERN: RegExp;
2
2
  export declare const MIGRATION_FILE_PATTERN: RegExp;
3
- interface ValidationError {
3
+ interface MissingSqlFileError {
4
4
  type: 'missing_sql_file';
5
5
  dirName: string;
6
6
  }
7
+ interface DuplicateMigrationNumberError {
8
+ type: 'duplicate_migration_number';
9
+ migrationNumber: string;
10
+ names: string[];
11
+ }
12
+ type ValidationError = MissingSqlFileError | DuplicateMigrationNumberError;
7
13
  interface ValidationResult {
8
14
  valid: true;
9
15
  dirs: string[];
@@ -13,6 +19,7 @@ interface ValidationFailure {
13
19
  valid: false;
14
20
  errors: ValidationError[];
15
21
  }
22
+ export declare const trackMigrationNumber: (numberToNames: Map<string, string[]>, name: string) => void;
16
23
  export declare const validateMigrations: (dirNames: string[], fileNames: string[], existingSqlFiles: Set<string>) => ValidationResult | ValidationFailure;
17
24
  export declare const formatValidationErrors: (errors: ValidationError[]) => string;
18
25
  export {};
@@ -1,35 +1,56 @@
1
1
  export const MIGRATION_DIR_PATTERN = /^\d+_[a-z0-9_-]+$/;
2
2
  export const MIGRATION_FILE_PATTERN = /^\d+_[a-z0-9_-]+\.sql$/;
3
+ export const trackMigrationNumber = (numberToNames, name) => {
4
+ const key = /^(\d+)_/.exec(name)[1].replace(/^0+/, '') || '0';
5
+ const existing = numberToNames.get(key);
6
+ if (existing) {
7
+ existing.push(name);
8
+ }
9
+ else {
10
+ numberToNames.set(key, [name]);
11
+ }
12
+ };
3
13
  export const validateMigrations = (dirNames, fileNames, existingSqlFiles) => {
4
14
  const errors = [];
5
15
  const matchingDirs = [];
16
+ const numberToNames = new Map();
6
17
  for (const dirName of dirNames) {
7
18
  if (!MIGRATION_DIR_PATTERN.test(dirName)) {
8
19
  continue;
9
20
  }
10
21
  matchingDirs.push(dirName);
22
+ trackMigrationNumber(numberToNames, dirName);
11
23
  if (!existingSqlFiles.has(dirName)) {
12
24
  errors.push({ type: 'missing_sql_file', dirName });
13
25
  }
14
26
  }
15
- if (errors.length > 0) {
16
- return { valid: false, errors };
17
- }
18
27
  const matchingFiles = [];
19
28
  for (const fileName of fileNames) {
20
29
  if (MIGRATION_FILE_PATTERN.test(fileName)) {
21
30
  matchingFiles.push(fileName);
31
+ trackMigrationNumber(numberToNames, fileName);
22
32
  }
23
33
  }
34
+ for (const [migrationNumber, names] of numberToNames) {
35
+ if (names.length > 1) {
36
+ errors.push({ type: 'duplicate_migration_number', migrationNumber, names: names.sort() });
37
+ }
38
+ }
39
+ if (errors.length > 0) {
40
+ return { valid: false, errors };
41
+ }
24
42
  return {
25
43
  valid: true,
26
- dirs: [...matchingDirs].sort(),
27
- files: [...matchingFiles].sort(),
44
+ dirs: matchingDirs.sort(),
45
+ files: matchingFiles.sort(),
28
46
  };
29
47
  };
30
48
  export const formatValidationErrors = (errors) => {
31
49
  const lines = errors.map((error) => {
32
- return ` - "${error.dirName}/migration.sql" is missing.`;
50
+ if (error.type === 'missing_sql_file') {
51
+ return ` - "${error.dirName}/migration.sql" is missing.`;
52
+ }
53
+ return ` - Duplicate migration number ${error.migrationNumber}: ${error.names.map((n) => `"${n}"`).join(', ')}`;
33
54
  });
34
55
  return `Database migration validation failed:\n${lines.join('\n')}`;
35
56
  };
@@ -1,5 +1,5 @@
1
1
  import { describe, expect, test } from 'vitest';
2
- import { MIGRATION_DIR_PATTERN, MIGRATION_FILE_PATTERN, validateMigrations, formatValidationErrors, } from './validation.js';
2
+ import { MIGRATION_DIR_PATTERN, MIGRATION_FILE_PATTERN, trackMigrationNumber, validateMigrations, formatValidationErrors, } from './validation.js';
3
3
  describe('MIGRATION_DIR_PATTERN', () => {
4
4
  const validNames = [
5
5
  '1700000000_create-users',
@@ -55,6 +55,29 @@ describe('MIGRATION_FILE_PATTERN', () => {
55
55
  expect(MIGRATION_FILE_PATTERN.test(name)).toBe(false);
56
56
  });
57
57
  });
58
+ describe('trackMigrationNumber', () => {
59
+ const cases = [
60
+ { name: '001_create-users', expected: '1' },
61
+ { name: '1_init', expected: '1' },
62
+ { name: '0001_add-posts', expected: '1' },
63
+ { name: '42_z', expected: '42' },
64
+ { name: '1700000000_create-users', expected: '1700000000' },
65
+ { name: '1700000000_create-users.sql', expected: '1700000000' },
66
+ { name: '001_create-users.sql', expected: '1' },
67
+ { name: '0000000000_init', expected: '0' },
68
+ ];
69
+ test.each(cases)('tracks $name under key $expected', ({ name, expected }) => {
70
+ const map = new Map();
71
+ trackMigrationNumber(map, name);
72
+ expect(map.get(expected)).toEqual([name]);
73
+ });
74
+ test('groups names with the same migration number', () => {
75
+ const map = new Map();
76
+ trackMigrationNumber(map, '001_create-users');
77
+ trackMigrationNumber(map, '1_init');
78
+ expect(map.get('1')).toEqual(['001_create-users', '1_init']);
79
+ });
80
+ });
58
81
  describe('validateMigrations', () => {
59
82
  test('returns valid result with sorted dirs when all dirs are valid', () => {
60
83
  const dirNames = ['1700000001_add-posts', '1700000000_create-users'];
@@ -132,6 +155,52 @@ describe('validateMigrations', () => {
132
155
  files: [],
133
156
  });
134
157
  });
158
+ test('returns error for duplicate migration numbers in dirs', () => {
159
+ const dirNames = ['001_create-users', '001_add-posts'];
160
+ const existingSqlFiles = new Set(['001_create-users', '001_add-posts']);
161
+ const result = validateMigrations(dirNames, [], existingSqlFiles);
162
+ expect(result).toEqual({
163
+ valid: false,
164
+ errors: [
165
+ { type: 'duplicate_migration_number', migrationNumber: '1', names: ['001_add-posts', '001_create-users'] },
166
+ ],
167
+ });
168
+ });
169
+ test('returns error for duplicate migration numbers in files', () => {
170
+ const fileNames = ['001_create-users.sql', '001_add-posts.sql'];
171
+ const result = validateMigrations([], fileNames, new Set());
172
+ expect(result).toEqual({
173
+ valid: false,
174
+ errors: [
175
+ {
176
+ type: 'duplicate_migration_number',
177
+ migrationNumber: '1',
178
+ names: ['001_add-posts.sql', '001_create-users.sql'],
179
+ },
180
+ ],
181
+ });
182
+ });
183
+ test('returns error for duplicate migration numbers across dirs and files', () => {
184
+ const dirNames = ['001_create-users'];
185
+ const fileNames = ['001_add-posts.sql'];
186
+ const existingSqlFiles = new Set(['001_create-users']);
187
+ const result = validateMigrations(dirNames, fileNames, existingSqlFiles);
188
+ expect(result).toEqual({
189
+ valid: false,
190
+ errors: [
191
+ { type: 'duplicate_migration_number', migrationNumber: '1', names: ['001_add-posts.sql', '001_create-users'] },
192
+ ],
193
+ });
194
+ });
195
+ test('treats different zero-padded prefixes as duplicates', () => {
196
+ const dirNames = ['1_init', '001_setup'];
197
+ const existingSqlFiles = new Set(['1_init', '001_setup']);
198
+ const result = validateMigrations(dirNames, [], existingSqlFiles);
199
+ expect(result).toEqual({
200
+ valid: false,
201
+ errors: [{ type: 'duplicate_migration_number', migrationNumber: '1', names: ['001_setup', '1_init'] }],
202
+ });
203
+ });
135
204
  });
136
205
  describe('formatValidationErrors', () => {
137
206
  test('formats missing_sql_file errors', () => {
@@ -147,4 +216,11 @@ describe('formatValidationErrors', () => {
147
216
  expect(message).toContain('"1700000000_create-users/migration.sql" is missing');
148
217
  expect(message).toContain('"1700000001_add-posts/migration.sql" is missing');
149
218
  });
219
+ test('formats duplicate_migration_number errors', () => {
220
+ const message = formatValidationErrors([
221
+ { type: 'duplicate_migration_number', migrationNumber: '1', names: ['001_add-posts', '001_create-users'] },
222
+ ]);
223
+ expect(message).toContain('Database migration validation failed');
224
+ expect(message).toContain('Duplicate migration number 1: "001_add-posts", "001_create-users"');
225
+ });
150
226
  });
@@ -1,6 +1,7 @@
1
1
  import { createReadStream, promises as fs, existsSync } from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { fdir } from 'fdir';
4
+ import ignore from 'ignore';
4
5
  import { minimatch } from 'minimatch';
5
6
  import { LIKELY_SECRET_PREFIXES, SAFE_LISTED_VALUES } from './secret_prefixes.js';
6
7
  /**
@@ -170,21 +171,16 @@ export function findLikelySecrets({ text, omitValuesFromEnhancedScan = [], }) {
170
171
  */
171
172
  export async function getFilePathsToScan({ env, base }) {
172
173
  const omitPathsAlways = ['.git/', '.cache/'];
173
- // node modules is dense and is only useful to scan if the repo itself commits these
174
- // files. As a simple check to understand if the repo would commit these files, we expect
175
- // that they would not ignore them from their git settings. So if gitignore includes
176
- // node_modules anywhere we will omit looking in those folders - this will allow repos
177
- // that do commit node_modules to still scan them.
178
- let ignoreNodeModules = false;
174
+ // Files/folders ignored by the repo's .gitignore should not be scanned, since they
175
+ // are not committed to the repo and are not part of the build output the user controls.
176
+ // This also naturally excludes things like node_modules when the repo gitignores it.
179
177
  const gitignorePath = path.resolve(base, '.gitignore');
180
178
  const gitignoreContents = existsSync(gitignorePath) ? await fs.readFile(gitignorePath, 'utf-8') : '';
181
- if (gitignoreContents?.includes('node_modules')) {
182
- ignoreNodeModules = true;
183
- }
179
+ const gitIgnoreFilter = gitignoreContents ? ignore().add(gitignoreContents) : null;
184
180
  let files = await new fdir()
185
181
  .withRelativePaths()
186
- .filter((path) => {
187
- if (ignoreNodeModules && path.includes('node_modules')) {
182
+ .filter((filePath) => {
183
+ if (gitIgnoreFilter && filePath && gitIgnoreFilter.ignores(filePath)) {
188
184
  return false;
189
185
  }
190
186
  return true;
@@ -25,11 +25,11 @@ interface NetlifyPlugin {
25
25
  package: string;
26
26
  inputs: PluginInputs;
27
27
  }
28
- interface DbMigrationsConfig {
28
+ interface DatabaseMigrationsConfig {
29
29
  path?: string;
30
30
  }
31
- interface DbConfig {
32
- migrations?: DbMigrationsConfig;
31
+ interface DatabaseConfig {
32
+ migrations?: DatabaseMigrationsConfig;
33
33
  }
34
34
  interface ImagesConfig {
35
35
  remote_images: string[];
@@ -60,6 +60,6 @@ export interface NetlifyConfig {
60
60
  /**
61
61
  * object with options for database configuration
62
62
  */
63
- db?: DbConfig;
63
+ database?: DatabaseConfig;
64
64
  }
65
65
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@netlify/build",
3
- "version": "35.11.2",
3
+ "version": "35.13.0",
4
4
  "description": "Netlify build module",
5
5
  "type": "module",
6
6
  "exports": "./lib/index.js",
@@ -69,7 +69,7 @@
69
69
  "@bugsnag/js": "^8.0.0",
70
70
  "@netlify/blobs": "^10.4.4",
71
71
  "@netlify/cache-utils": "^6.0.5",
72
- "@netlify/config": "^24.4.4",
72
+ "@netlify/config": "^24.5.0",
73
73
  "@netlify/edge-bundler": "14.9.19",
74
74
  "@netlify/functions-utils": "^6.2.29",
75
75
  "@netlify/git-utils": "^6.0.4",
@@ -86,6 +86,7 @@
86
86
  "figures": "^6.0.0",
87
87
  "filter-obj": "^6.0.0",
88
88
  "hot-shots": "11.4.0",
89
+ "ignore": "^7.0.0",
89
90
  "indent-string": "^5.0.0",
90
91
  "is-plain-obj": "^4.0.0",
91
92
  "keep-func-props": "^6.0.0",
@@ -152,5 +153,5 @@
152
153
  "engines": {
153
154
  "node": ">=18.14.0"
154
155
  },
155
- "gitHead": "9f8b16e1b34b63d007f4dc7a91e9a440aba53ebc"
156
+ "gitHead": "c77982ba1d1886e81f1a517318fcf93123f2a260"
156
157
  }