Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .buildkite/scripts/steps/checks/quick_checks.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@
{
"script": ".buildkite/scripts/steps/checks/native_modules.sh"
},
{
"script": ".buildkite/scripts/steps/checks/yarn_install_scripts.sh"
},
{
"script": ".buildkite/scripts/steps/checks/test_files_missing_owner.sh"
},
Expand All @@ -85,4 +88,4 @@
"script": ".buildkite/scripts/steps/checks/verify_moon_projects.sh",
"mayChangeFiles": true
}
]
]
6 changes: 6 additions & 0 deletions .buildkite/scripts/steps/checks/yarn_install_scripts.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash

set -euo pipefail

echo --- Check Yarn Install Scripts
node scripts/yarn_install_scripts scan --validate
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ packages/kbn-tsconfig/base @elastic/kibana-operations
packages/kbn-validate-next-docs-cli @elastic/kibana-operations
packages/kbn-web-worker-stub @elastic/kibana-operations
packages/kbn-whereis-pkg-cli @elastic/kibana-operations
packages/kbn-yarn-install-scripts @elastic/kibana-operations @elastic/kibana-security
packages/kbn-yarn-lock-validator @elastic/kibana-operations
src/core @elastic/kibana-core
src/core/packages/analytics/browser @elastic/kibana-core
Expand Down
3 changes: 3 additions & 0 deletions .yarnrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ yarn-offline-mirror ".yarn-local-mirror"

# Always look into the cache first before fetching online
--install.prefer-offline true

# Install scripts are managed by `yarn kbn bootstrap` via @kbn/yarn-install-scripts
ignore-scripts true
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -1625,6 +1625,7 @@
"@kbn/validate-next-docs-cli": "link:packages/kbn-validate-next-docs-cli",
"@kbn/web-worker-stub": "link:packages/kbn-web-worker-stub",
"@kbn/whereis-pkg-cli": "link:packages/kbn-whereis-pkg-cli",
"@kbn/yarn-install-scripts": "link:packages/kbn-yarn-install-scripts",
"@kbn/yarn-lock-validator": "link:packages/kbn-yarn-lock-validator",
"@mapbox/vector-tile": "1.3.1",
"@mswjs/http-middleware": "0.10.3",
Expand Down
50 changes: 50 additions & 0 deletions packages/kbn-yarn-install-scripts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# @kbn/yarn-install-scripts

Automatic script execution is disabled in `.yarnrc`. This package manages node_module lifecycle scripts for install and postinstall.

## Configuration

The `config.json` file contains an array of packages with install scripts and their configured status:

```json
{
"packages": [
{
"path": "package-name",
"lifecycle": "postinstall",
"required": true,
"reason": "reason for action"
}
]
}
```

- `path`: The package path in node_modules (e.g., `@elastic/eui`)
- `lifecycle`: Either `install` or `postinstall`
- `required`: `true` to run the script during bootstrap, `false` to skip it
- `reason`: Explanation of why the script is required or not

## CLI Usage

```bash
node scripts/yarn_install_scripts <command> [options]
```

### Commands

#### `run`

Run allowed install scripts defined in `config.json`:

```bash
node scripts/yarn_install_scripts run
node scripts/yarn_install_scripts run --verbose # Show install logs
```

#### `scan`

Discovers packages with install scripts and shows whether they are run or skipped:

```bash
node scripts/yarn_install_scripts scan
```
66 changes: 66 additions & 0 deletions packages/kbn-yarn-install-scripts/cli/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { run } from '@kbn/dev-cli-runner';
import { createFailError } from '@kbn/dev-cli-errors';

import { runCommand } from './run_command';
import { scanCommand } from './scan_command';

const SUPPORTED_COMMANDS = ['run', 'scan'];

export function cli(): void {
run(
async ({ log, flags, flagsReader }) => {
const args = flagsReader.getPositionals();
const command = args[0];

if (!command) {
throw createFailError(
`No command specified. Usage: node scripts/yarn_install_scripts <${SUPPORTED_COMMANDS.join(
'|'
)}>`
);
}

if (!SUPPORTED_COMMANDS.includes(command)) {
throw createFailError(
`Invalid command: ${command}. Must be one of: ${SUPPORTED_COMMANDS.join(', ')}`
);
}

if (command === 'run') runCommand(log, Boolean(flags.verbose), Boolean(flags['dry-run']));
if (command === 'scan') scanCommand(log, Boolean(flags.validate));
},
{
usage: `node scripts/yarn_install_scripts <run|scan> [options]`,
description: 'Manage yarn install lifecycle scripts for dependencies',
flags: {
boolean: ['verbose', 'validate', 'dry-run'],
default: {
verbose: false,
validate: false,
'dry-run': false,
},
help: `
Commands:
run - Run allowed install scripts
scan - List packages with install scripts

Options for 'run':
--verbose Show full output from install scripts
--dry-run Show which install scripts would be run without running them

Options for 'scan':
--validate Exit with error if any scripts are unconfigured
`,
},
}
);
}
87 changes: 87 additions & 0 deletions packages/kbn-yarn-install-scripts/cli/run_command.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* 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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import type { ToolingLog } from '@kbn/tooling-log';

import { runCommand } from './run_command';
import { loadConfig, runInstallScripts } from '../src';
import type { InstallScriptsConfig } from '../src/types';

jest.mock('../src', () => ({
loadConfig: jest.fn(),
runInstallScripts: jest.fn(),
}));

const mockLoadConfig = loadConfig as jest.MockedFunction<typeof loadConfig>;
const mockRunInstallScripts = runInstallScripts as jest.MockedFunction<typeof runInstallScripts>;

const mockLog = {
info: jest.fn(),
warning: jest.fn(),
error: jest.fn(),
success: jest.fn(),
debug: jest.fn(),
verbose: jest.fn(),
write: jest.fn(),
} as unknown as jest.Mocked<ToolingLog>;

describe('runCommand', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('should load config and run install scripts', () => {
const mockConfig: InstallScriptsConfig = {
packages: [
{ path: '@elastic/eui', lifecycle: 'postinstall', required: true, reason: 'Required' },
],
};
mockLoadConfig.mockReturnValue(mockConfig);
runCommand(mockLog, false, false);
expect(mockLoadConfig).toHaveBeenCalledTimes(1);
expect(mockRunInstallScripts).toHaveBeenCalledWith({
config: mockConfig,
log: mockLog,
verbose: false,
dryRun: false,
});
expect(mockLog.success).toHaveBeenCalledWith('Install scripts complete');
});

it('should pass verbose=true to runInstallScripts', () => {
mockLoadConfig.mockReturnValue({ packages: [] });
runCommand(mockLog, true, false);
expect(mockRunInstallScripts).toHaveBeenCalledWith(expect.objectContaining({ verbose: true }));
});

it('should pass dryRun=true to runInstallScripts', () => {
mockLoadConfig.mockReturnValue({ packages: [] });
runCommand(mockLog, false, true);
expect(mockRunInstallScripts).toHaveBeenCalledWith(expect.objectContaining({ dryRun: true }));
});

it('should propagate errors from loadConfig', () => {
mockLoadConfig.mockImplementation(() => {
throw new Error('Config not found');
});

expect(() => runCommand(mockLog, false, false)).toThrow('Config not found');
expect(mockRunInstallScripts).not.toHaveBeenCalled();
});

it('should propagate errors from runInstallScripts', () => {
mockLoadConfig.mockReturnValue({ packages: [] });
mockRunInstallScripts.mockImplementation(() => {
throw new Error('Script failed');
});

expect(() => runCommand(mockLog, false, false)).toThrow('Script failed');
expect(mockLog.success).not.toHaveBeenCalled();
});
});
25 changes: 25 additions & 0 deletions packages/kbn-yarn-install-scripts/cli/run_command.ts
Original file line number Diff line number Diff line change
@@ -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", the "GNU Affero General Public License v3.0 only", 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", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import type { ToolingLog } from '@kbn/tooling-log';

import { loadConfig, runInstallScripts } from '../src';

export function runCommand(log: ToolingLog, verbose: boolean, dryRun: boolean): void {
const config = loadConfig();

runInstallScripts({
config,
log,
verbose,
dryRun,
});

log.success('Install scripts complete');
}
Loading