mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
chore(NA): avoid more native modules on production dependencies (#190183)
This PR is the final step needed for us to re-enable pointer compression on serverless. It introduces a cli later used in a CI check that will prevent the addition of new native modules on production as discussed and agreed with Luke and Brandon previously. It also removes the usages of `cbor-x` and replaces those with a wrapping package `@kbn/cbor` which relies on the implementation coming from `borc` with no native modules involved. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
259518214a
commit
433cc1e3d8
57 changed files with 1029 additions and 67 deletions
8
.buildkite/scripts/steps/checks/native_modules.sh
Executable file
8
.buildkite/scripts/steps/checks/native_modules.sh
Executable file
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
source .buildkite/scripts/common/util.sh
|
||||
|
||||
echo --- Check Production Native Node Modules
|
||||
node scripts/check_prod_native_modules
|
|
@ -17,3 +17,4 @@
|
|||
.buildkite/scripts/steps/checks/yarn_deduplicate.sh
|
||||
.buildkite/scripts/steps/checks/prettier_topology.sh
|
||||
.buildkite/scripts/steps/checks/renovate.sh
|
||||
.buildkite/scripts/steps/checks/native_modules.sh
|
||||
|
|
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
|
@ -68,11 +68,13 @@ packages/kbn-capture-oas-snapshot-cli @elastic/kibana-core
|
|||
x-pack/test/cases_api_integration/common/plugins/cases @elastic/response-ops
|
||||
packages/kbn-cases-components @elastic/response-ops
|
||||
x-pack/plugins/cases @elastic/response-ops
|
||||
packages/kbn-cbor @elastic/kibana-operations
|
||||
packages/kbn-cell-actions @elastic/security-threat-hunting-explore
|
||||
src/plugins/chart_expressions/common @elastic/kibana-visualizations
|
||||
packages/kbn-chart-icons @elastic/kibana-visualizations
|
||||
src/plugins/charts @elastic/kibana-visualizations
|
||||
packages/kbn-check-mappings-update-cli @elastic/kibana-core
|
||||
packages/kbn-check-prod-native-modules-cli @elastic/kibana-operations
|
||||
packages/kbn-ci-stats-core @elastic/kibana-operations
|
||||
packages/kbn-ci-stats-performance-metrics @elastic/kibana-operations
|
||||
packages/kbn-ci-stats-reporter @elastic/kibana-operations
|
||||
|
|
|
@ -199,6 +199,7 @@
|
|||
"@kbn/cases-api-integration-test-plugin": "link:x-pack/test/cases_api_integration/common/plugins/cases",
|
||||
"@kbn/cases-components": "link:packages/kbn-cases-components",
|
||||
"@kbn/cases-plugin": "link:x-pack/plugins/cases",
|
||||
"@kbn/cbor": "link:packages/kbn-cbor",
|
||||
"@kbn/cell-actions": "link:packages/kbn-cell-actions",
|
||||
"@kbn/chart-expressions-common": "link:src/plugins/chart_expressions/common",
|
||||
"@kbn/chart-icons": "link:packages/kbn-chart-icons",
|
||||
|
@ -1028,13 +1029,13 @@
|
|||
"base64-js": "^1.3.1",
|
||||
"bitmap-sdf": "^1.0.3",
|
||||
"blurhash": "^2.0.1",
|
||||
"borc": "3.0.0",
|
||||
"brace": "0.11.1",
|
||||
"brok": "^5.0.2",
|
||||
"byte-size": "^8.1.0",
|
||||
"cacheable-lookup": "6",
|
||||
"camelcase-keys": "7.0.2",
|
||||
"canvg": "^3.0.9",
|
||||
"cbor-x": "^1.3.3",
|
||||
"chalk": "^4.1.0",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"chroma-js": "^2.1.0",
|
||||
|
@ -1313,6 +1314,7 @@
|
|||
"@kbn/bazel-runner": "link:packages/kbn-bazel-runner",
|
||||
"@kbn/capture-oas-snapshot-cli": "link:packages/kbn-capture-oas-snapshot-cli",
|
||||
"@kbn/check-mappings-update-cli": "link:packages/kbn-check-mappings-update-cli",
|
||||
"@kbn/check-prod-native-modules-cli": "link:packages/kbn-check-prod-native-modules-cli",
|
||||
"@kbn/ci-stats-core": "link:packages/kbn-ci-stats-core",
|
||||
"@kbn/ci-stats-performance-metrics": "link:packages/kbn-ci-stats-performance-metrics",
|
||||
"@kbn/ci-stats-reporter": "link:packages/kbn-ci-stats-reporter",
|
||||
|
|
3
packages/kbn-cbor/README.md
Normal file
3
packages/kbn-cbor/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# @kbn/cbor
|
||||
|
||||
Simple wrapper around borc to expose CBOR encode and decode methods with reasonable performance and no native modules
|
40
packages/kbn-cbor/index.test.ts
Normal file
40
packages/kbn-cbor/index.test.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { encode, decode } from '.';
|
||||
|
||||
describe('KbnCbor', () => {
|
||||
it('should correctly encode and decode data', () => {
|
||||
const data = { hello: 'world', count: 123, isValid: true };
|
||||
|
||||
// encoding
|
||||
const encoded = encode(data);
|
||||
expect(encoded).toBeInstanceOf(Buffer);
|
||||
expect(encoded.length).toBeGreaterThan(0);
|
||||
|
||||
// decoding
|
||||
const decoded = decode(encoded);
|
||||
expect(decoded).toEqual(data);
|
||||
});
|
||||
|
||||
it('should encode data to Buffer', () => {
|
||||
const data = { foo: 'bar' };
|
||||
|
||||
const encoded = encode(data);
|
||||
expect(Buffer.isBuffer(encoded)).toBe(true);
|
||||
});
|
||||
|
||||
it('should decode Buffer to original data', () => {
|
||||
const data = { foo: 'bar', num: 42, arr: [1, 2, 3] };
|
||||
|
||||
const encoded = encode(data);
|
||||
const decoded = decode(encoded);
|
||||
|
||||
expect(decoded).toEqual(data);
|
||||
});
|
||||
});
|
25
packages/kbn-cbor/index.ts
Normal file
25
packages/kbn-cbor/index.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
// NOTE: This can possibly be replaced with node-cbor using encode, and decodeFirstSync if we do need
|
||||
// to change into something better maintained but for now we are going to stick with borc as it is
|
||||
// a little faster
|
||||
import { encode as encodeJS, decode as decodeJS } from 'borc';
|
||||
|
||||
export class KbnCbor {
|
||||
static encode(data: unknown) {
|
||||
return encodeJS(data);
|
||||
}
|
||||
|
||||
static decode(uint8: any) {
|
||||
return decodeJS(uint8);
|
||||
}
|
||||
}
|
||||
|
||||
export const encode = KbnCbor.encode;
|
||||
export const decode = KbnCbor.decode;
|
13
packages/kbn-cbor/jest.config.js
Normal file
13
packages/kbn-cbor/jest.config.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test',
|
||||
rootDir: '../..',
|
||||
roots: ['<rootDir>/packages/kbn-cbor'],
|
||||
};
|
5
packages/kbn-cbor/kibana.jsonc
Normal file
5
packages/kbn-cbor/kibana.jsonc
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/cbor",
|
||||
"owner": "@elastic/kibana-operations"
|
||||
}
|
7
packages/kbn-cbor/package.json
Normal file
7
packages/kbn-cbor/package.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "@kbn/cbor",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0",
|
||||
"main": "./index.ts"
|
||||
}
|
18
packages/kbn-cbor/tsconfig.json
Normal file
18
packages/kbn-cbor/tsconfig.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"../../typings/borc.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": []
|
||||
}
|
3
packages/kbn-check-prod-native-modules-cli/README.md
Normal file
3
packages/kbn-check-prod-native-modules-cli/README.md
Normal file
|
@ -0,0 +1,3 @@
|
|||
# @kbn/check-prod-native-modules-cli
|
||||
|
||||
Simple and straightforward CLI for searching for native modules installed as prod dependencies or as a result of any prod dependency.
|
|
@ -0,0 +1,243 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { promises as fs, existsSync } from 'fs';
|
||||
import { ToolingLog } from '@kbn/tooling-log';
|
||||
import { findProductionDependencies, readYarnLock } from '@kbn/yarn-lock-validator';
|
||||
import {
|
||||
checkProdNativeModules,
|
||||
checkDependencies,
|
||||
isNativeModule,
|
||||
} from './check_prod_native_modules';
|
||||
|
||||
jest.mock('fs', () => ({
|
||||
promises: {
|
||||
readdir: jest.fn(),
|
||||
},
|
||||
existsSync: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@kbn/repo-info', () => ({
|
||||
REPO_ROOT: '/mocked/repo/root',
|
||||
}));
|
||||
|
||||
jest.mock('@kbn/tooling-log', () => ({
|
||||
ToolingLog: jest.fn().mockImplementation(() => ({
|
||||
info: jest.fn(),
|
||||
error: jest.fn(),
|
||||
success: jest.fn(),
|
||||
})),
|
||||
}));
|
||||
|
||||
jest.mock('@kbn/yarn-lock-validator', () => ({
|
||||
findProductionDependencies: jest.fn(),
|
||||
readYarnLock: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock(
|
||||
// eslint-disable-next-line @kbn/imports/no_unresolvable_imports
|
||||
'/test/node_modules/test-package/package.json',
|
||||
() => ({
|
||||
name: 'test-package',
|
||||
version: '1.0.0',
|
||||
}),
|
||||
{ virtual: true }
|
||||
);
|
||||
|
||||
jest.mock(
|
||||
// eslint-disable-next-line @kbn/imports/no_unresolvable_imports
|
||||
'/test/node_modules/@scope/package/package.json',
|
||||
() => ({
|
||||
name: '@scope/package',
|
||||
version: '1.0.0',
|
||||
}),
|
||||
{ virtual: true }
|
||||
);
|
||||
|
||||
describe('Check Prod Native Modules', () => {
|
||||
let mockLog: jest.Mocked<ToolingLog>;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
mockLog = new ToolingLog() as jest.Mocked<ToolingLog>;
|
||||
});
|
||||
|
||||
describe('isNativeModule', () => {
|
||||
it('should return true if binding.gyp is found', async () => {
|
||||
(fs.readdir as jest.Mock).mockResolvedValueOnce([
|
||||
{ name: 'binding.gyp', isDirectory: () => false },
|
||||
]);
|
||||
|
||||
const result = await isNativeModule('/test/path', mockLog);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return true if .node file is found', async () => {
|
||||
(fs.readdir as jest.Mock).mockResolvedValueOnce([
|
||||
{ name: 'test.node', isDirectory: () => false },
|
||||
]);
|
||||
|
||||
const result = await isNativeModule('/test/path', mockLog);
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if no native module indicators are found', async () => {
|
||||
(fs.readdir as jest.Mock).mockResolvedValueOnce([
|
||||
{ name: 'regular.js', isDirectory: () => false },
|
||||
]);
|
||||
|
||||
const result = await isNativeModule('/test/path', mockLog);
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should log an error if there is an issue reading the directory', async () => {
|
||||
(fs.readdir as jest.Mock).mockRejectedValueOnce(new Error('Read error'));
|
||||
|
||||
await isNativeModule('/test/path', mockLog);
|
||||
expect(mockLog.error).toHaveBeenCalledWith('Error when reading /test/path: Read error');
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkDependencies', () => {
|
||||
it('should identify native modules in production dependencies', async () => {
|
||||
const mockProductionDependencies = new Map([['test-package@1.0.0', true]]);
|
||||
const mockProdNativeModulesFound: Array<{ name: string; version: string; path: string }> = [];
|
||||
|
||||
(fs.readdir as jest.Mock).mockResolvedValueOnce([
|
||||
{ name: 'test-package', isDirectory: () => true },
|
||||
]);
|
||||
(fs.readdir as jest.Mock)
|
||||
.mockResolvedValueOnce([{ name: 'binding.gyp', isDirectory: () => false }])
|
||||
.mockResolvedValueOnce([]);
|
||||
(existsSync as jest.Mock).mockReturnValue(true);
|
||||
jest
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
.spyOn(require('./check_prod_native_modules'), 'isNativeModule')
|
||||
.mockResolvedValueOnce(true);
|
||||
|
||||
await checkDependencies(
|
||||
'/test/node_modules',
|
||||
mockProductionDependencies,
|
||||
mockProdNativeModulesFound,
|
||||
mockLog
|
||||
);
|
||||
|
||||
expect(mockProdNativeModulesFound).toEqual([
|
||||
{ name: 'test-package', version: '1.0.0', path: '/test/node_modules/test-package' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should handle scoped packages', async () => {
|
||||
const mockProductionDependencies = new Map([['@scope/package@1.0.0', true]]);
|
||||
const mockProdNativeModulesFound: Array<{ name: string; version: string; path: string }> = [];
|
||||
|
||||
(fs.readdir as jest.Mock)
|
||||
.mockResolvedValueOnce([{ name: '@scope', isDirectory: () => true }])
|
||||
.mockResolvedValueOnce([{ name: 'package', isDirectory: () => true }]);
|
||||
(fs.readdir as jest.Mock)
|
||||
.mockResolvedValueOnce([{ name: 'binding.gyp', isDirectory: () => false }])
|
||||
.mockResolvedValueOnce([]);
|
||||
(existsSync as jest.Mock).mockReturnValue(true);
|
||||
(existsSync as jest.Mock).mockReturnValue(true);
|
||||
jest
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
.spyOn(require('./check_prod_native_modules'), 'isNativeModule')
|
||||
.mockResolvedValueOnce(true);
|
||||
|
||||
await checkDependencies(
|
||||
'/test/node_modules',
|
||||
mockProductionDependencies,
|
||||
mockProdNativeModulesFound,
|
||||
mockLog
|
||||
);
|
||||
|
||||
expect(mockProdNativeModulesFound).toEqual([
|
||||
{ name: '@scope/package', version: '1.0.0', path: '/test/node_modules/@scope/package' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkProdNativeModules', () => {
|
||||
it('should return false when no native modules are found', async () => {
|
||||
(existsSync as jest.Mock).mockReturnValue(true);
|
||||
(findProductionDependencies as jest.Mock).mockReturnValue(new Map());
|
||||
(readYarnLock as jest.Mock).mockResolvedValueOnce({});
|
||||
(fs.readdir as jest.Mock).mockResolvedValue([]);
|
||||
jest
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
.spyOn(require('./check_prod_native_modules'), 'checkDependencies')
|
||||
.mockResolvedValue(undefined);
|
||||
|
||||
const result = await checkProdNativeModules(mockLog);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(mockLog.success).toHaveBeenCalledWith(
|
||||
'No production native modules installed were found'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return true and log errors when native modules are found', async () => {
|
||||
(existsSync as jest.Mock).mockReturnValueOnce(true).mockReturnValueOnce(true);
|
||||
(findProductionDependencies as jest.Mock).mockReturnValue(
|
||||
new Map([['native-module@1.0.0', { name: 'native-module', version: '1.0.0' }]])
|
||||
);
|
||||
(readYarnLock as jest.Mock).mockResolvedValueOnce({});
|
||||
|
||||
// Mock loadPackageJson to return a mock package JSON object
|
||||
jest
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
.spyOn(require('./helpers'), 'loadPackageJson')
|
||||
.mockImplementation((packageJsonPath: any) => {
|
||||
return {
|
||||
name: 'native-module',
|
||||
version: '1.0.0',
|
||||
};
|
||||
});
|
||||
|
||||
(fs.readdir as jest.Mock)
|
||||
.mockResolvedValueOnce([{ name: 'native-module', isDirectory: () => true }])
|
||||
// .mockResolvedValueOnce([{ name: 'package.json', isDirectory: () => false }])
|
||||
.mockResolvedValueOnce([{ name: 'binding.gyp', isDirectory: () => false }]);
|
||||
jest
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
.spyOn(require('./check_prod_native_modules'), 'checkDependencies')
|
||||
.mockImplementationOnce((_, __, prodNativeModulesFound: any) => {
|
||||
prodNativeModulesFound.push({
|
||||
name: 'native-module',
|
||||
version: '1.0.0',
|
||||
path: '/path/to/native-module',
|
||||
});
|
||||
});
|
||||
|
||||
const result = await checkProdNativeModules(mockLog);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(mockLog.error).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'Production native module detected: node_modules/native-module'
|
||||
);
|
||||
expect(mockLog.error).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'Production native modules were detected and logged above'
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error if root node_modules folder is not found', async () => {
|
||||
(existsSync as jest.Mock).mockReturnValue(false);
|
||||
(findProductionDependencies as jest.Mock).mockReturnValue(new Map());
|
||||
(readYarnLock as jest.Mock).mockResolvedValueOnce({});
|
||||
|
||||
const result = await checkProdNativeModules(mockLog);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(mockLog.error).toHaveBeenCalledWith(
|
||||
'No root node_modules folder was found in the project. Impossible to continue'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import * as path from 'path';
|
||||
import { promises as fs, existsSync } from 'fs';
|
||||
import { REPO_ROOT } from '@kbn/repo-info';
|
||||
import type { ToolingLog } from '@kbn/tooling-log';
|
||||
import { findProductionDependencies, readYarnLock } from '@kbn/yarn-lock-validator';
|
||||
import { loadPackageJson } from './helpers';
|
||||
|
||||
// Checks if a given path contains a native module or not recursively
|
||||
async function isNativeModule(modulePath: string, log: ToolingLog): Promise<boolean> {
|
||||
const stack: string[] = [modulePath];
|
||||
|
||||
while (stack.length > 0) {
|
||||
const currentPath = stack.pop() as string;
|
||||
|
||||
// Skip processing if the current directory is a node_modules folder
|
||||
if (path.basename(currentPath) === 'node_modules') {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const entries = await fs.readdir(currentPath, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const entryPath = path.join(currentPath, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
stack.push(entryPath);
|
||||
} else if (entry.name === 'binding.gyp' || entry.name.endsWith('.node')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
log.error(`Error when reading ${currentPath}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Searches through node_modules and for each module which is a prod dep (or a direct result of one) checks recursively for native modules
|
||||
async function checkDependencies(
|
||||
rootNodeModulesDir: string,
|
||||
productionDependencies: Map<string, boolean>,
|
||||
prodNativeModulesFound: Array<{ name: string; version: string; path: string }>,
|
||||
log: ToolingLog
|
||||
) {
|
||||
const stack: string[] = [rootNodeModulesDir];
|
||||
|
||||
while (stack.length > 0) {
|
||||
const currentDir = stack.pop() as string;
|
||||
|
||||
try {
|
||||
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
|
||||
const entryPath = path.join(currentDir, entry.name);
|
||||
if (entry.name.startsWith('@')) {
|
||||
// Handle scoped packages (e.g., @scope/package)
|
||||
stack.push(entryPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
const packageJsonPath = path.join(entryPath, 'package.json');
|
||||
if (existsSync(packageJsonPath)) {
|
||||
const packageJson = loadPackageJson(packageJsonPath);
|
||||
const dependencyKey = `${packageJson.name}@${packageJson.version}`;
|
||||
|
||||
if (productionDependencies.has(dependencyKey)) {
|
||||
const isNative = await isNativeModule(entryPath, log);
|
||||
if (isNative) {
|
||||
prodNativeModulesFound.push({
|
||||
name: packageJson.name,
|
||||
version: packageJson.version,
|
||||
path: entryPath,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adds nested node_modules to the stack to check for further dependencies
|
||||
const nestedNodeModulesPath = path.join(entryPath, 'node_modules');
|
||||
if (existsSync(nestedNodeModulesPath)) {
|
||||
stack.push(nestedNodeModulesPath);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error(`Error processing directory ${currentDir}: ${err.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if there are native modules in the production dependencies
|
||||
async function checkProdNativeModules(log: ToolingLog) {
|
||||
log.info('Checking for native modules on production dependencies...');
|
||||
const rootNodeModulesDir = path.join(REPO_ROOT, 'node_modules');
|
||||
const prodNativeModulesFound: Array<{ name: string; version: string; path: string }> = [];
|
||||
|
||||
try {
|
||||
// Gets all production dependencies based on package.json and then searches across transient dependencies using lock file
|
||||
const rawProductionDependencies = findProductionDependencies(log, await readYarnLock());
|
||||
|
||||
// Converts rawProductionDependencies into a simple Map of production dependencies
|
||||
const productionDependencies: Map<string, boolean> = new Map();
|
||||
rawProductionDependencies.forEach((depInfo, depKey) => {
|
||||
productionDependencies.set(`${depInfo.name}@${depInfo.version}`, true);
|
||||
});
|
||||
|
||||
// Fail if no root node_modules folder
|
||||
if (!existsSync(rootNodeModulesDir)) {
|
||||
throw new Error(
|
||||
'No root node_modules folder was found in the project. Impossible to continue'
|
||||
);
|
||||
}
|
||||
|
||||
// Goes into the node_modules folder and for each node_module which is a production dependency (or a result of one) checks recursively if there are native modules
|
||||
await checkDependencies(
|
||||
rootNodeModulesDir,
|
||||
productionDependencies,
|
||||
prodNativeModulesFound,
|
||||
log
|
||||
);
|
||||
|
||||
// In that case no prod native modules were found
|
||||
if (!prodNativeModulesFound.length) {
|
||||
log.success('No production native modules installed were found');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Logs every detected native module at once
|
||||
prodNativeModulesFound.forEach((dep) => {
|
||||
log.error(`Production native module detected: ${path.relative(REPO_ROOT, dep.path)}`);
|
||||
});
|
||||
|
||||
throw new Error('Production native modules were detected and logged above');
|
||||
} catch (err) {
|
||||
log.error(err.message);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export { checkProdNativeModules, checkDependencies, isNativeModule };
|
14
packages/kbn-check-prod-native-modules-cli/helpers.ts
Normal file
14
packages/kbn-check-prod-native-modules-cli/helpers.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
// helper function to load package.json
|
||||
function loadPackageJson(packageJsonPath: string) {
|
||||
return require(packageJsonPath);
|
||||
}
|
||||
|
||||
export { loadPackageJson };
|
4
packages/kbn-check-prod-native-modules-cli/integration_tests/__fixtures__/no_native_modules/node_modules/package-a/package.json
generated
vendored
Normal file
4
packages/kbn-check-prod-native-modules-cli/integration_tests/__fixtures__/no_native_modules/node_modules/package-a/package.json
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "package-a",
|
||||
"version": "1.0.0"
|
||||
}
|
4
packages/kbn-check-prod-native-modules-cli/integration_tests/__fixtures__/no_native_modules/node_modules/package-b/package.json
generated
vendored
Normal file
4
packages/kbn-check-prod-native-modules-cli/integration_tests/__fixtures__/no_native_modules/node_modules/package-b/package.json
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "package-b",
|
||||
"version": "2.0.0"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "no-native-modules-project",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"package-a": "^1.0.0",
|
||||
"package-b": "^2.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
|
||||
package-a@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/package-a/-/package-a-1.0.0.tgz"
|
||||
integrity sha1-example123
|
||||
|
||||
package-b@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/package-b/-/package-b-2.0.0.tgz"
|
||||
integrity sha1-example456
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "no-node-modules-project",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"package-a": "^1.0.0",
|
||||
"package-b": "^2.0.0"
|
||||
}
|
||||
}
|
11
packages/kbn-check-prod-native-modules-cli/integration_tests/__fixtures__/no_node_modules/yarn.lock
generated
Normal file
11
packages/kbn-check-prod-native-modules-cli/integration_tests/__fixtures__/no_node_modules/yarn.lock
generated
Normal file
|
@ -0,0 +1,11 @@
|
|||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
|
||||
package-a@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/package-a/-/package-a-1.0.0.tgz"
|
||||
integrity sha1-example123
|
||||
|
||||
package-b@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/package-b/-/package-b-2.0.0.tgz"
|
||||
integrity sha1-example456
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "native_module",
|
||||
"sources": [ "" ]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "native-module",
|
||||
"version": "1.0.0",
|
||||
"gypfile": true
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "package-b",
|
||||
"version": "2.0.0"
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "with-native-modules-project",
|
||||
"version": "1.0.0",
|
||||
"devDependencies": {
|
||||
"native-module": "^1.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"package-b": "^2.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
|
||||
native-module@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/native-module/-/native-module-1.0.0.tgz"
|
||||
integrity sha1-example789
|
||||
|
||||
package-b@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/package-b/-/package-b-2.0.0.tgz"
|
||||
integrity sha1-example456
|
8
packages/kbn-check-prod-native-modules-cli/integration_tests/__fixtures__/with_native_modules/node_modules/native-module/binding.gyp
generated
vendored
Normal file
8
packages/kbn-check-prod-native-modules-cli/integration_tests/__fixtures__/with_native_modules/node_modules/native-module/binding.gyp
generated
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "native_module",
|
||||
"sources": [ "" ]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "native-module",
|
||||
"version": "1.0.0",
|
||||
"gypfile": true
|
||||
}
|
1
packages/kbn-check-prod-native-modules-cli/integration_tests/__fixtures__/with_native_modules/node_modules/native-module2/a.node
generated
vendored
Normal file
1
packages/kbn-check-prod-native-modules-cli/integration_tests/__fixtures__/with_native_modules/node_modules/native-module2/a.node
generated
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
0
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "native-module2",
|
||||
"version": "1.0.0"
|
||||
}
|
4
packages/kbn-check-prod-native-modules-cli/integration_tests/__fixtures__/with_native_modules/node_modules/package-b/package.json
generated
vendored
Normal file
4
packages/kbn-check-prod-native-modules-cli/integration_tests/__fixtures__/with_native_modules/node_modules/package-b/package.json
generated
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "package-b",
|
||||
"version": "2.0.0"
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"name": "with-native-modules-project",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"native-module": "^1.0.0",
|
||||
"native-module2": "^1.0.0",
|
||||
"package-b": "^2.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
|
||||
native-module@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/native-module/-/native-module-1.0.0.tgz"
|
||||
integrity sha1-example789
|
||||
|
||||
native-module2@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/native-module/-/native-module2-1.0.0.tgz"
|
||||
integrity sha1-example789
|
||||
|
||||
package-b@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/package-b/-/package-b-2.0.0.tgz"
|
||||
integrity sha1-example456
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "native_module",
|
||||
"sources": [ "" ]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "native-module",
|
||||
"version": "1.0.0",
|
||||
"gypfile": true
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "packaga-a",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"native-module": "^1.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"name": "package-b",
|
||||
"version": "2.0.0"
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "with-native-modules-project",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"package-a": "^1.0.0",
|
||||
"package-b": "^2.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
|
||||
native-module@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/native-module/-/native-module-1.0.0.tgz"
|
||||
integrity sha1-example789
|
||||
|
||||
package-a@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/package-a/-/package-a-1.0.0.tgz"
|
||||
integrity sha1-example789
|
||||
dependencies:
|
||||
native-module "^1.0.0"
|
||||
|
||||
package-b@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/package-b/-/package-b-2.0.0.tgz"
|
||||
integrity sha1-example456
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { ToolingLog } from '@kbn/tooling-log';
|
||||
import { checkProdNativeModules } from '../check_prod_native_modules';
|
||||
|
||||
describe('checkProdNativeModules', () => {
|
||||
let mockLog: jest.Mocked<ToolingLog>;
|
||||
const fixturesDir = path.join(__dirname, '__fixtures__');
|
||||
|
||||
beforeEach(() => {
|
||||
mockLog = {
|
||||
info: jest.fn(),
|
||||
success: jest.fn(),
|
||||
error: jest.fn(),
|
||||
} as unknown as jest.Mocked<ToolingLog>;
|
||||
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should return false when no native modules are found', async () => {
|
||||
// Use a fixture without native modules
|
||||
const noNativeModulesDir = path.join(fixturesDir, 'no_native_modules');
|
||||
const noNativeModulesPkgJsonPath = path.join(noNativeModulesDir, 'package.json');
|
||||
jest.spyOn(process, 'cwd').mockReturnValue(noNativeModulesDir);
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
jest.replaceProperty(require('@kbn/repo-info'), 'REPO_ROOT', noNativeModulesDir);
|
||||
|
||||
const noNativeModulesPkgJson = JSON.parse(fs.readFileSync(noNativeModulesPkgJsonPath, 'utf8'));
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
jest.replaceProperty(require('@kbn/repo-info'), 'kibanaPackageJson', noNativeModulesPkgJson);
|
||||
|
||||
const result = await checkProdNativeModules(mockLog);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(mockLog.success).toHaveBeenCalledWith(
|
||||
'No production native modules installed were found'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return true and log errors when native modules are found', async () => {
|
||||
// Use a fixture with native modules
|
||||
const withNativeModulesDir = path.join(fixturesDir, 'with_native_modules');
|
||||
const withNativeModulesPkgJsonPath = path.join(withNativeModulesDir, 'package.json');
|
||||
jest.spyOn(process, 'cwd').mockReturnValue(withNativeModulesDir);
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
jest.replaceProperty(require('@kbn/repo-info'), 'REPO_ROOT', withNativeModulesDir);
|
||||
|
||||
const withNativeModulesPkgJson = JSON.parse(
|
||||
fs.readFileSync(withNativeModulesPkgJsonPath, 'utf8')
|
||||
);
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
jest.replaceProperty(require('@kbn/repo-info'), 'kibanaPackageJson', withNativeModulesPkgJson);
|
||||
|
||||
const result = await checkProdNativeModules(mockLog);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(mockLog.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Production native module detected:')
|
||||
);
|
||||
expect(mockLog.error).toHaveBeenCalledWith(
|
||||
'Production native modules were detected and logged above'
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw an error when root node_modules folder is not found', async () => {
|
||||
// Use a fixture without node_modules
|
||||
const noNodeModulesDir = path.join(fixturesDir, 'no_node_modules');
|
||||
const noNodeModulesPkgJsonPath = path.join(noNodeModulesDir, 'package.json');
|
||||
jest.spyOn(process, 'cwd').mockReturnValue(noNodeModulesDir);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
jest.replaceProperty(require('@kbn/repo-info'), 'REPO_ROOT', noNodeModulesDir);
|
||||
|
||||
const noNodeModulesPkgJson = JSON.parse(fs.readFileSync(noNodeModulesPkgJsonPath, 'utf8'));
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
jest.replaceProperty(require('@kbn/repo-info'), 'kibanaPackageJson', noNodeModulesPkgJson);
|
||||
|
||||
expect(await checkProdNativeModules(mockLog)).toBe(true);
|
||||
expect(mockLog.error).toHaveBeenCalledWith(
|
||||
'No root node_modules folder was found in the project. Impossible to continue'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return false when no prod native modules are found', async () => {
|
||||
// Use a fixture without native modules
|
||||
const withDevNativeModulesDir = path.join(fixturesDir, 'with_dev_native_modules');
|
||||
const withDevNativeModulesPkgJsonPath = path.join(withDevNativeModulesDir, 'package.json');
|
||||
jest.spyOn(process, 'cwd').mockReturnValue(withDevNativeModulesDir);
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
jest.replaceProperty(require('@kbn/repo-info'), 'REPO_ROOT', withDevNativeModulesDir);
|
||||
|
||||
const withDevNativeModulesPkgJson = JSON.parse(
|
||||
fs.readFileSync(withDevNativeModulesPkgJsonPath, 'utf8')
|
||||
);
|
||||
|
||||
jest.replaceProperty(
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
require('@kbn/repo-info'),
|
||||
'kibanaPackageJson',
|
||||
withDevNativeModulesPkgJson
|
||||
);
|
||||
|
||||
const result = await checkProdNativeModules(mockLog);
|
||||
|
||||
expect(result).toBe(false);
|
||||
expect(mockLog.success).toHaveBeenCalledWith(
|
||||
'No production native modules installed were found'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return true and log errors when prod transient native modules are found', async () => {
|
||||
// Use a fixture with native modules
|
||||
const withTransientNativeModulesDir = path.join(fixturesDir, 'with_transient_native_modules');
|
||||
const withTransientNativeModulesPkgJsonPath = path.join(
|
||||
withTransientNativeModulesDir,
|
||||
'package.json'
|
||||
);
|
||||
jest.spyOn(process, 'cwd').mockReturnValue(withTransientNativeModulesDir);
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
jest.replaceProperty(require('@kbn/repo-info'), 'REPO_ROOT', withTransientNativeModulesDir);
|
||||
|
||||
const withTransientNativeModulesPkgJson = JSON.parse(
|
||||
fs.readFileSync(withTransientNativeModulesPkgJsonPath, 'utf8')
|
||||
);
|
||||
|
||||
jest.replaceProperty(
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
require('@kbn/repo-info'),
|
||||
'kibanaPackageJson',
|
||||
withTransientNativeModulesPkgJson
|
||||
);
|
||||
|
||||
const result = await checkProdNativeModules(mockLog);
|
||||
|
||||
expect(result).toBe(true);
|
||||
expect(mockLog.error).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Production native module detected:')
|
||||
);
|
||||
expect(mockLog.error).toHaveBeenCalledWith(
|
||||
'Production native modules were detected and logged above'
|
||||
);
|
||||
});
|
||||
});
|
13
packages/kbn-check-prod-native-modules-cli/jest.config.js
Normal file
13
packages/kbn-check-prod-native-modules-cli/jest.config.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test/jest_node',
|
||||
rootDir: '../..',
|
||||
roots: ['<rootDir>/packages/kbn-check-prod-native-modules-cli'],
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
preset: '@kbn/test/jest_integration_node',
|
||||
rootDir: '../..',
|
||||
roots: ['<rootDir>/packages/kbn-check-prod-native-modules-cli'],
|
||||
};
|
6
packages/kbn-check-prod-native-modules-cli/kibana.jsonc
Normal file
6
packages/kbn-check-prod-native-modules-cli/kibana.jsonc
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"type": "shared-server",
|
||||
"id": "@kbn/check-prod-native-modules-cli",
|
||||
"owner": "@elastic/kibana-operations",
|
||||
"devOnly": true
|
||||
}
|
7
packages/kbn-check-prod-native-modules-cli/package.json
Normal file
7
packages/kbn-check-prod-native-modules-cli/package.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"name": "@kbn/check-prod-native-modules-cli",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0",
|
||||
"main": "./run_check_prod_native_modules_cli"
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { run } from '@kbn/dev-cli-runner';
|
||||
import { createFailError } from '@kbn/dev-cli-errors';
|
||||
import { checkProdNativeModules } from './check_prod_native_modules';
|
||||
|
||||
run(
|
||||
async ({ log }) => {
|
||||
const foundProdNativeModules = await checkProdNativeModules(log);
|
||||
if (foundProdNativeModules) {
|
||||
throw createFailError(
|
||||
'Failed: check all previous errors before continuing. Chat with the Kibana Operations Team if you do need help.'
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
usage: `node scripts/check_prod_native_modules`,
|
||||
description:
|
||||
'Check if there are production dependencies that contains or installs dependencies that contain native modules and errors out on those cases',
|
||||
}
|
||||
);
|
23
packages/kbn-check-prod-native-modules-cli/tsconfig.json
Normal file
23
packages/kbn-check-prod-native-modules-cli/tsconfig.json
Normal file
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"types": [
|
||||
"jest",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/repo-info",
|
||||
"@kbn/tooling-log",
|
||||
"@kbn/dev-cli-runner",
|
||||
"@kbn/dev-cli-errors",
|
||||
"@kbn/yarn-lock-validator",
|
||||
]
|
||||
}
|
|
@ -9,3 +9,4 @@
|
|||
export { readYarnLock } from './src/yarn_lock';
|
||||
export type { YarnLock } from './src/yarn_lock';
|
||||
export { validateDependencies } from './src/validate_yarn_lock';
|
||||
export { findProductionDependencies } from './src/find_production_dependencies';
|
||||
|
|
10
scripts/check_prod_native_modules.js
Normal file
10
scripts/check_prod_native_modules.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
require('../src/setup_node_env');
|
||||
require('@kbn/check-prod-native-modules-cli');
|
|
@ -9,13 +9,12 @@
|
|||
import type { Logger } from '@kbn/core/server';
|
||||
import { set } from '@kbn/safer-lodash-set';
|
||||
import { Readable } from 'stream';
|
||||
import { encode } from 'cbor-x';
|
||||
import { encode, decode } from '@kbn/cbor';
|
||||
import { elasticsearchServiceMock, loggingSystemMock } from '@kbn/core/server/mocks';
|
||||
import { ContentStream, ContentStreamEncoding, ContentStreamParameters } from './content_stream';
|
||||
import type { GetResponse } from '@elastic/elasticsearch/lib/api/types';
|
||||
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { FileDocument } from '../../../../file_client/file_metadata_client/adapters/es_index';
|
||||
import * as cborx from 'cbor-x';
|
||||
import { IndexRequest } from '@elastic/elasticsearch/lib/api/types';
|
||||
|
||||
describe('ContentStream', () => {
|
||||
|
@ -415,7 +414,7 @@ describe('ContentStream', () => {
|
|||
stream.end('some data');
|
||||
await new Promise((resolve) => stream.once('finish', resolve));
|
||||
const docBuffer = (client.index.mock.calls[0][0] as IndexRequest).document as Buffer;
|
||||
const docData = cborx.decode(docBuffer);
|
||||
const docData = decode(docBuffer);
|
||||
|
||||
expect(docData).toHaveProperty('@timestamp');
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import { createId } from '@paralleldrive/cuid2';
|
||||
import * as cborx from 'cbor-x';
|
||||
import { encode, decode } from '@kbn/cbor';
|
||||
import { errors as esErrors } from '@elastic/elasticsearch';
|
||||
import type { ElasticsearchClient, Logger } from '@kbn/core/server';
|
||||
import { ByteSizeValue } from '@kbn/config-schema';
|
||||
|
@ -144,7 +144,7 @@ export class ContentStream extends Duplex {
|
|||
}
|
||||
const buffer = Buffer.concat(chunks);
|
||||
const decodedChunkDoc: GetResponse<FileChunkDocument> | undefined = buffer.byteLength
|
||||
? (cborx.decode(buffer) as GetResponse<FileChunkDocument>)
|
||||
? (decode(buffer) as GetResponse<FileChunkDocument>)
|
||||
: undefined;
|
||||
|
||||
// Because `asStream` was used in retrieving the document, errors are also not be processed
|
||||
|
@ -245,7 +245,7 @@ export class ContentStream extends Duplex {
|
|||
id,
|
||||
index,
|
||||
op_type: 'create',
|
||||
document: cborx.encode({
|
||||
document: encode({
|
||||
data,
|
||||
bid,
|
||||
// Mark it as last?
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import { Readable } from 'stream';
|
||||
import { encode } from 'cbor-x';
|
||||
import { encode } from '@kbn/cbor';
|
||||
import { promisify } from 'util';
|
||||
import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
|
||||
import { elasticsearchServiceMock } from '@kbn/core/server/mocks';
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"@kbn/logging",
|
||||
"@kbn/core-http-common",
|
||||
"@kbn/core-lifecycle-server-internal",
|
||||
"@kbn/cbor",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -130,6 +130,8 @@
|
|||
"@kbn/cases-components/*": ["packages/kbn-cases-components/*"],
|
||||
"@kbn/cases-plugin": ["x-pack/plugins/cases"],
|
||||
"@kbn/cases-plugin/*": ["x-pack/plugins/cases/*"],
|
||||
"@kbn/cbor": ["packages/kbn-cbor"],
|
||||
"@kbn/cbor/*": ["packages/kbn-cbor/*"],
|
||||
"@kbn/cell-actions": ["packages/kbn-cell-actions"],
|
||||
"@kbn/cell-actions/*": ["packages/kbn-cell-actions/*"],
|
||||
"@kbn/chart-expressions-common": ["src/plugins/chart_expressions/common"],
|
||||
|
@ -140,6 +142,8 @@
|
|||
"@kbn/charts-plugin/*": ["src/plugins/charts/*"],
|
||||
"@kbn/check-mappings-update-cli": ["packages/kbn-check-mappings-update-cli"],
|
||||
"@kbn/check-mappings-update-cli/*": ["packages/kbn-check-mappings-update-cli/*"],
|
||||
"@kbn/check-prod-native-modules-cli": ["packages/kbn-check-prod-native-modules-cli"],
|
||||
"@kbn/check-prod-native-modules-cli/*": ["packages/kbn-check-prod-native-modules-cli/*"],
|
||||
"@kbn/ci-stats-core": ["packages/kbn-ci-stats-core"],
|
||||
"@kbn/ci-stats-core/*": ["packages/kbn-ci-stats-core/*"],
|
||||
"@kbn/ci-stats-performance-metrics": ["packages/kbn-ci-stats-performance-metrics"],
|
||||
|
|
9
typings/borc.d.ts
vendored
Normal file
9
typings/borc.d.ts
vendored
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
declare module 'borc';
|
|
@ -10,7 +10,7 @@
|
|||
import type { Client } from '@elastic/elasticsearch';
|
||||
import type { SearchHit } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { basename } from 'path';
|
||||
import * as cborx from 'cbor-x';
|
||||
import { encode } from '@kbn/cbor';
|
||||
import { AGENT_ACTIONS_INDEX, AGENT_ACTIONS_RESULTS_INDEX } from '@kbn/fleet-plugin/common';
|
||||
import { FleetActionGenerator } from '../../../common/endpoint/data_generators/fleet_action_generator';
|
||||
import { EndpointActionGenerator } from '../../../common/endpoint/data_generators/endpoint_action_generator';
|
||||
|
@ -236,7 +236,7 @@ export const sendEndpointActionResponse = async (
|
|||
{
|
||||
index: FILE_STORAGE_DATA_INDEX,
|
||||
id: `${fileMeta._id}.0`,
|
||||
document: cborx.encode({
|
||||
document: encode({
|
||||
bid: fileMeta._id,
|
||||
last: true,
|
||||
'@timestamp': new Date().toISOString(),
|
||||
|
|
|
@ -211,6 +211,7 @@
|
|||
"@kbn/esql-ast",
|
||||
"@kbn/esql-validation-autocomplete",
|
||||
"@kbn/config",
|
||||
"@kbn/cbor",
|
||||
"@kbn/zod",
|
||||
]
|
||||
}
|
||||
|
|
96
yarn.lock
96
yarn.lock
|
@ -1449,36 +1449,6 @@
|
|||
"@types/tough-cookie" "^4.0.5"
|
||||
tough-cookie "^4.1.4"
|
||||
|
||||
"@cbor-extract/cbor-extract-darwin-arm64@2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.0.0.tgz#cf0667e4c22111c9d45e16c29964892b12460a76"
|
||||
integrity sha512-jebtLrruvsBbGMsUn0QxZW/8Z7caS9OkszVKZ64WTWajUkyohmolUdKL2nbfaTyyi3ABJrxVNM4YO1pvMsNI1g==
|
||||
|
||||
"@cbor-extract/cbor-extract-darwin-x64@2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-darwin-x64/-/cbor-extract-darwin-x64-2.0.0.tgz#7bc01e7911b97eee4c78ae074bd3108f2ff208c3"
|
||||
integrity sha512-LGYjdlyqANBqCDzBujCqXpPcK70rvaQgw98/aquzBuEmK0KXS7i579CoVG1yS/eb3bMqiVPevBri45jbR6Tlsg==
|
||||
|
||||
"@cbor-extract/cbor-extract-linux-arm64@2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-linux-arm64/-/cbor-extract-linux-arm64-2.0.0.tgz#e40608afed5f373091560fa9dcd19c7f52f510b0"
|
||||
integrity sha512-c1rbQcSF01yVgbG60zEfHNsUkXiEEQRNdYqm5qpqEAkLx4gA6DDU91IQbalkqXfwDuQzcMovOc1TC3uJJIi2OQ==
|
||||
|
||||
"@cbor-extract/cbor-extract-linux-arm@2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-linux-arm/-/cbor-extract-linux-arm-2.0.0.tgz#f52a7580fb23e305370e66ae9ff136de3729c4b8"
|
||||
integrity sha512-cOGHEIif5rPbpix6qhpuatrZzm6HeC5rT0nXt8ynLTc7PzfXmovswD9x6d9h5NcHswkV5y3PbkNbpel/tLADYg==
|
||||
|
||||
"@cbor-extract/cbor-extract-linux-x64@2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-linux-x64/-/cbor-extract-linux-x64-2.0.0.tgz#8c936b8a93f915bf3c2459d5b4b78d244bda0f26"
|
||||
integrity sha512-WYeE1b5WGf9pbbQH3qeNBXq710gGsuVFUiP148RY8In+2pCp/fxjBpe701ngam9/fF5D+gJs8B1i5wv/PN7JZA==
|
||||
|
||||
"@cbor-extract/cbor-extract-win32-x64@2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@cbor-extract/cbor-extract-win32-x64/-/cbor-extract-win32-x64-2.0.0.tgz#4d4ad91527a8313c3db1e2167a8821dfae9d6211"
|
||||
integrity sha512-XqVuJEnE0jpl/RkuSp04FF2UE73gY52Y4nZaIE6j9GAeSH2cHYU5CCd4TaVMDi2M18ZpZv7XhL/k+nneQzyJpQ==
|
||||
|
||||
"@cfaester/enzyme-adapter-react-18@^0.8.0":
|
||||
version "0.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@cfaester/enzyme-adapter-react-18/-/enzyme-adapter-react-18-0.8.0.tgz#313814eb79658a6e74209f9f1743bcefff14a46f"
|
||||
|
@ -3545,6 +3515,10 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/cbor@link:packages/kbn-cbor":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/cell-actions@link:packages/kbn-cell-actions":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
@ -3565,6 +3539,10 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/check-prod-native-modules-cli@link:packages/kbn-check-prod-native-modules-cli":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/ci-stats-core@link:packages/kbn-ci-stats-core":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
@ -8604,6 +8582,11 @@
|
|||
"@smithy/util-buffer-from" "^3.0.0"
|
||||
tslib "^2.6.2"
|
||||
|
||||
"@sovpro/delimited-stream@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@sovpro/delimited-stream/-/delimited-stream-1.1.0.tgz#4334bba7ee241036e580fdd99c019377630d26b4"
|
||||
integrity sha512-kQpk267uxB19X3X2T1mvNMjyvIEonpNSHrMlK5ZaBU6aZxw7wPbpgKJOjHN3+/GPVpXgAV9soVT2oyHpLkLtyw==
|
||||
|
||||
"@statoscope/extensions@5.28.1":
|
||||
version "5.28.1"
|
||||
resolved "https://registry.yarnpkg.com/@statoscope/extensions/-/extensions-5.28.1.tgz#bc270f9366c4b2c13342f1a0d138520cf607a5bb"
|
||||
|
@ -13369,6 +13352,19 @@ boolbase@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
|
||||
integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
|
||||
|
||||
borc@3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/borc/-/borc-3.0.0.tgz#49ada1be84de86f57bb1bb89789f34c186dfa4fe"
|
||||
integrity sha512-ec4JmVC46kE0+layfnwM3l15O70MlFiEbmQHY/vpqIKiUtPVntv4BY4NVnz3N4vb21edV3mY97XVckFvYHWF9g==
|
||||
dependencies:
|
||||
bignumber.js "^9.0.0"
|
||||
buffer "^6.0.3"
|
||||
commander "^2.15.0"
|
||||
ieee754 "^1.1.13"
|
||||
iso-url "^1.1.5"
|
||||
json-text-sequence "~0.3.0"
|
||||
readable-stream "^3.6.0"
|
||||
|
||||
bowser@^1.7.3:
|
||||
version "1.9.4"
|
||||
resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.9.4.tgz#890c58a2813a9d3243704334fa81b96a5c150c9a"
|
||||
|
@ -13914,27 +13910,6 @@ caseless@~0.12.0:
|
|||
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
||||
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
|
||||
|
||||
cbor-extract@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/cbor-extract/-/cbor-extract-2.0.2.tgz#8e45339627fb8b47071e8e71138c630019125939"
|
||||
integrity sha512-QoLGEgPff03ad/L66P91ci5Zmf7Woq8bh4H5XT3+D5annlrPH5ObHf2Yvo53eDQaDkQtF9tJwMKSWANGXDmwUA==
|
||||
dependencies:
|
||||
node-gyp-build-optional-packages "5.0.3"
|
||||
optionalDependencies:
|
||||
"@cbor-extract/cbor-extract-darwin-arm64" "2.0.0"
|
||||
"@cbor-extract/cbor-extract-darwin-x64" "2.0.0"
|
||||
"@cbor-extract/cbor-extract-linux-arm" "2.0.0"
|
||||
"@cbor-extract/cbor-extract-linux-arm64" "2.0.0"
|
||||
"@cbor-extract/cbor-extract-linux-x64" "2.0.0"
|
||||
"@cbor-extract/cbor-extract-win32-x64" "2.0.0"
|
||||
|
||||
cbor-x@^1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/cbor-x/-/cbor-x-1.3.3.tgz#5ba0f6d3f6720ea5ba38804e583c020bccf2f762"
|
||||
integrity sha512-y3V8GlypWM01t3NtYvXmDehuU3bt4q3tewCrvj5EMfUYT6v9HjRu4NHYH3EgbzJCOaZFroAhzci9PHvIIDuOEQ==
|
||||
optionalDependencies:
|
||||
cbor-extract "^2.0.2"
|
||||
|
||||
ccount@^1.0.0:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.5.tgz#ac82a944905a65ce204eb03023157edf29425c17"
|
||||
|
@ -14500,7 +14475,7 @@ comma-separated-tokens@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea"
|
||||
integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==
|
||||
|
||||
commander@2, commander@^2.19.0, commander@^2.20.0, commander@^2.7.1:
|
||||
commander@2, commander@^2.15.0, commander@^2.19.0, commander@^2.20.0, commander@^2.7.1:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
@ -20718,6 +20693,11 @@ isnumber@~1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/isnumber/-/isnumber-1.0.0.tgz#0e3f9759b581d99dd85086f0ec2a74909cfadd01"
|
||||
integrity sha1-Dj+XWbWB2Z3YUIbw7Cp0kJz63QE=
|
||||
|
||||
iso-url@^1.1.5:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/iso-url/-/iso-url-1.2.1.tgz#db96a49d8d9a64a1c889fc07cc525d093afb1811"
|
||||
integrity sha512-9JPDgCN4B7QPkLtYAAOrEuAWvP9rWvR5offAr0/SeF046wIkglqH3VXgYYP6NcsKslH80UIVgmPqNe3j7tG2ng==
|
||||
|
||||
isobject@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
|
||||
|
@ -21606,6 +21586,13 @@ json-stringify-safe@5.0.1, json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.
|
|||
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
|
||||
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
|
||||
|
||||
json-text-sequence@~0.3.0:
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/json-text-sequence/-/json-text-sequence-0.3.0.tgz#6603e0ee45da41f949669fd18744b97fb209e6ce"
|
||||
integrity sha512-7khKIYPKwXQem4lWXfpIN/FEnhztCeRPSxH4qm3fVlqulwujrRDD54xAwDDn/qVKpFtV550+QAkcWJcufzqQuA==
|
||||
dependencies:
|
||||
"@sovpro/delimited-stream" "^1.1.0"
|
||||
|
||||
json5@*, json5@^2.1.2, json5@^2.2.2, json5@^2.2.3:
|
||||
version "2.2.3"
|
||||
resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
|
||||
|
@ -23894,11 +23881,6 @@ node-forge@^1, node-forge@^1.2.1, node-forge@^1.3.1:
|
|||
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3"
|
||||
integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==
|
||||
|
||||
node-gyp-build-optional-packages@5.0.3:
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.3.tgz#92a89d400352c44ad3975010368072b41ad66c17"
|
||||
integrity sha512-k75jcVzk5wnnc/FMxsf4udAoTEUv2jY3ycfdSd3yWu6Cnd1oee6/CfZJApyscA4FJOmdoixWwiwOyf16RzD5JA==
|
||||
|
||||
node-gyp-build-optional-packages@5.0.7:
|
||||
version "5.0.7"
|
||||
resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.7.tgz#5d2632bbde0ab2f6e22f1bbac2199b07244ae0b3"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue