diff --git a/packages/elastic-datemath/yarn.lock b/packages/elastic-datemath/yarn.lock new file mode 120000 index 000000000000..3f82ebc9cdba --- /dev/null +++ b/packages/elastic-datemath/yarn.lock @@ -0,0 +1 @@ +../../yarn.lock \ No newline at end of file diff --git a/packages/kbn-babel-code-parser/yarn.lock b/packages/kbn-babel-code-parser/yarn.lock new file mode 120000 index 000000000000..3f82ebc9cdba --- /dev/null +++ b/packages/kbn-babel-code-parser/yarn.lock @@ -0,0 +1 @@ +../../yarn.lock \ No newline at end of file diff --git a/packages/kbn-babel-preset/yarn.lock b/packages/kbn-babel-preset/yarn.lock new file mode 120000 index 000000000000..3f82ebc9cdba --- /dev/null +++ b/packages/kbn-babel-preset/yarn.lock @@ -0,0 +1 @@ +../../yarn.lock \ No newline at end of file diff --git a/packages/kbn-dev-utils/yarn.lock b/packages/kbn-dev-utils/yarn.lock new file mode 120000 index 000000000000..3f82ebc9cdba --- /dev/null +++ b/packages/kbn-dev-utils/yarn.lock @@ -0,0 +1 @@ +../../yarn.lock \ No newline at end of file diff --git a/packages/kbn-es-query/yarn.lock b/packages/kbn-es-query/yarn.lock new file mode 120000 index 000000000000..3f82ebc9cdba --- /dev/null +++ b/packages/kbn-es-query/yarn.lock @@ -0,0 +1 @@ +../../yarn.lock \ No newline at end of file diff --git a/packages/kbn-es/yarn.lock b/packages/kbn-es/yarn.lock new file mode 120000 index 000000000000..3f82ebc9cdba --- /dev/null +++ b/packages/kbn-es/yarn.lock @@ -0,0 +1 @@ +../../yarn.lock \ No newline at end of file diff --git a/packages/kbn-eslint-import-resolver-kibana/yarn.lock b/packages/kbn-eslint-import-resolver-kibana/yarn.lock new file mode 120000 index 000000000000..3f82ebc9cdba --- /dev/null +++ b/packages/kbn-eslint-import-resolver-kibana/yarn.lock @@ -0,0 +1 @@ +../../yarn.lock \ No newline at end of file diff --git a/packages/kbn-eslint-plugin-license-header/yarn.lock b/packages/kbn-eslint-plugin-license-header/yarn.lock new file mode 120000 index 000000000000..3f82ebc9cdba --- /dev/null +++ b/packages/kbn-eslint-plugin-license-header/yarn.lock @@ -0,0 +1 @@ +../../yarn.lock \ No newline at end of file diff --git a/packages/kbn-i18n/yarn.lock b/packages/kbn-i18n/yarn.lock new file mode 120000 index 000000000000..3f82ebc9cdba --- /dev/null +++ b/packages/kbn-i18n/yarn.lock @@ -0,0 +1 @@ +../../yarn.lock \ No newline at end of file diff --git a/packages/kbn-interpreter/yarn.lock b/packages/kbn-interpreter/yarn.lock new file mode 120000 index 000000000000..3f82ebc9cdba --- /dev/null +++ b/packages/kbn-interpreter/yarn.lock @@ -0,0 +1 @@ +../../yarn.lock \ No newline at end of file diff --git a/packages/kbn-plugin-generator/yarn.lock b/packages/kbn-plugin-generator/yarn.lock new file mode 120000 index 000000000000..3f82ebc9cdba --- /dev/null +++ b/packages/kbn-plugin-generator/yarn.lock @@ -0,0 +1 @@ +../../yarn.lock \ No newline at end of file diff --git a/packages/kbn-plugin-helpers/yarn.lock b/packages/kbn-plugin-helpers/yarn.lock new file mode 120000 index 000000000000..3f82ebc9cdba --- /dev/null +++ b/packages/kbn-plugin-helpers/yarn.lock @@ -0,0 +1 @@ +../../yarn.lock \ No newline at end of file diff --git a/packages/kbn-pm/yarn.lock b/packages/kbn-pm/yarn.lock new file mode 120000 index 000000000000..3f82ebc9cdba --- /dev/null +++ b/packages/kbn-pm/yarn.lock @@ -0,0 +1 @@ +../../yarn.lock \ No newline at end of file diff --git a/packages/kbn-system-loader/yarn.lock b/packages/kbn-system-loader/yarn.lock new file mode 120000 index 000000000000..3f82ebc9cdba --- /dev/null +++ b/packages/kbn-system-loader/yarn.lock @@ -0,0 +1 @@ +../../yarn.lock \ No newline at end of file diff --git a/packages/kbn-test/yarn.lock b/packages/kbn-test/yarn.lock new file mode 120000 index 000000000000..3f82ebc9cdba --- /dev/null +++ b/packages/kbn-test/yarn.lock @@ -0,0 +1 @@ +../../yarn.lock \ No newline at end of file diff --git a/packages/kbn-ui-framework/yarn.lock b/packages/kbn-ui-framework/yarn.lock new file mode 120000 index 000000000000..3f82ebc9cdba --- /dev/null +++ b/packages/kbn-ui-framework/yarn.lock @@ -0,0 +1 @@ +../../yarn.lock \ No newline at end of file diff --git a/scripts/check_lockfile_symlinks.js b/scripts/check_lockfile_symlinks.js new file mode 100644 index 000000000000..b41a354da83f --- /dev/null +++ b/scripts/check_lockfile_symlinks.js @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +require('../src/setup_node_env'); +require('../src/dev/run_check_lockfile_symlinks'); diff --git a/src/dev/run_check_lockfile_symlinks.js b/src/dev/run_check_lockfile_symlinks.js new file mode 100644 index 000000000000..52b4a59fbd11 --- /dev/null +++ b/src/dev/run_check_lockfile_symlinks.js @@ -0,0 +1,218 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { existsSync, lstatSync, readFileSync, readlinkSync } from 'fs'; +import globby from 'globby'; +import { dirname } from 'path'; + +import { run, createFailError } from './run'; + +import { REPO_ROOT } from './constants'; +import { File } from './file'; +import { matchesAnyGlob } from './globs'; + +const LOCKFILE_GLOBS = ['**/yarn.lock']; +const MANIFEST_GLOBS = ['**/package.json']; +const IGNORE_FILE_GLOBS = [ + // tests aren't used in production, ignore them + '**/test/**/*', + // fixtures aren't used in production, ignore them + '**/*fixtures*/**/*', +]; + +run(async ({ log }) => { + const paths = await globby(LOCKFILE_GLOBS.concat(MANIFEST_GLOBS), { + cwd: REPO_ROOT, + nodir: true, + gitignore: true, + ignore: [ + // the gitignore: true option makes sure that we don't + // include files from node_modules in the result, but it still + // loads all of the files from node_modules before filtering + // so it's still super slow. This prevents loading the files + // and still relies on gitignore to to final ignores + '**/node_modules', + ], + }); + + const files = paths.map(path => new File(path)); + + await checkLockfileSymlinks(log, files); +}); + +async function checkLockfileSymlinks(log, files) { + const filtered = files.filter(file => !matchesAnyGlob(file.getRelativePath(), IGNORE_FILE_GLOBS)); + await checkOnlyLockfileAtProjectRoot(filtered); + await checkSuperfluousSymlinks(log, filtered); + await checkMissingSymlinks(log, filtered); + await checkIncorrectSymlinks(log, filtered); +} + +async function checkOnlyLockfileAtProjectRoot(files) { + const errorPaths = []; + + files + .filter(file => matchesAnyGlob(file.getRelativePath(), LOCKFILE_GLOBS)) + .forEach(file => { + const path = file.getRelativePath(); + const parent = dirname(path); + const stats = lstatSync(path); + if (!stats.isSymbolicLink() && parent !== '.') { + errorPaths.push(path); + } + }); + + if (errorPaths.length) { + throw createFailError( + `These directories MUST NOT have a 'yarn.lock' file:\n${listPaths(errorPaths)}` + ); + } +} + +async function checkSuperfluousSymlinks(log, files) { + const errorPaths = []; + + files + .filter(file => matchesAnyGlob(file.getRelativePath(), LOCKFILE_GLOBS)) + .forEach(file => { + const path = file.getRelativePath(); + const parent = dirname(path); + const stats = lstatSync(path); + if (!stats.isSymbolicLink()) { + return; + } + + const manifestPath = `${parent}/package.json`; + if (!existsSync(manifestPath)) { + log.warning( + `No manifest found at '${manifestPath}', but found an adjacent 'yarn.lock' symlink.` + ); + errorPaths.push(path); + return; + } + + try { + const manifest = readFileSync(manifestPath); + try { + const json = JSON.parse(manifest); + if (!json.dependencies || !Object.keys(json.dependencies).length) { + log.warning( + `Manifest at '${manifestPath}' has an adjacent 'yarn.lock' symlink, but manifest has no dependencies.` + ); + errorPaths.push(path); + } + } catch (err) { + log.warning( + `Manifest at '${manifestPath}' has an adjacent 'yarn.lock' symlink, but could not parse manifest JSON (${err.message}).` + ); + errorPaths.push(path); + } + } catch (err) { + log.warning( + `Manifest at '${manifestPath}', has an adjacent 'yarn.lock' symlink, but could not read manifest (${err.message}).` + ); + errorPaths.push(path); + } + }); + + if (errorPaths.length) { + throw createFailError( + `These directories MUST NOT have a 'yarn.lock' symlink:\n${listPaths(errorPaths)}` + ); + } +} + +async function checkMissingSymlinks(log, files) { + const errorPaths = []; + + files + .filter(file => matchesAnyGlob(file.getRelativePath(), MANIFEST_GLOBS)) + .forEach(file => { + const path = file.getRelativePath(); + const parent = dirname(path); + const lockfilePath = `${parent}/yarn.lock`; + if (existsSync(lockfilePath)) { + return; + } + + try { + const manifest = readFileSync(path); + try { + const json = JSON.parse(manifest); + if (json.dependencies && Object.keys(json.dependencies).length) { + const correctSymlink = getCorrectSymlink(lockfilePath); + log.warning( + `Manifest at '${path}' has dependencies, but did not find an adjacent 'yarn.lock' symlink to '${correctSymlink}'.` + ); + errorPaths.push(`${parent}/yarn.lock`); + } + } catch (err) { + log.warning(`Could not parse manifest JSON at '${path}' (${err.message}).`); + } + } catch (err) { + log.warning(`Could not read manifest at '${path}' (${err.message}).`); + } + }); + + if (errorPaths.length) { + throw createFailError( + `These directories MUST have a 'yarn.lock' symlink:\n${listPaths(errorPaths)}` + ); + } +} + +async function checkIncorrectSymlinks(log, files) { + const errorPaths = []; + + files + .filter(file => matchesAnyGlob(file.getRelativePath(), LOCKFILE_GLOBS)) + .forEach(file => { + const path = file.getRelativePath(); + const stats = lstatSync(path); + if (!stats.isSymbolicLink()) { + return; + } + + const symlink = readlinkSync(path); + const correctSymlink = getCorrectSymlink(path); + if (symlink !== correctSymlink) { + log.warning( + `Symlink at '${path}' points to '${symlink}', but it should point to '${correctSymlink}'.` + ); + errorPaths.push(path); + } + }); + + if (errorPaths.length) { + throw createFailError( + `These symlinks do NOT point to the 'yarn.lock' file in the project root:\n${listPaths( + errorPaths + )}` + ); + } +} + +function getCorrectSymlink(path) { + const count = path.split('/').length - 1; + return `${'../'.repeat(count)}yarn.lock`; +} + +function listPaths(paths) { + return paths.map(path => ` - ${path}`).join('\n'); +} diff --git a/tasks/config/run.js b/tasks/config/run.js index 5fd4b49cac29..3f13d6b48d85 100644 --- a/tasks/config/run.js +++ b/tasks/config/run.js @@ -102,6 +102,16 @@ module.exports = function (grunt) { ] }, + // used by the test and jenkins:unit tasks + // runs the check_lockfile_symlinks script to ensure manifests with non-dev dependencies have adjacent lockfile symlinks + checkLockfileSymlinks: { + cmd: process.execPath, + args: [ + require.resolve('../../scripts/check_lockfile_symlinks'), + '--quiet', // only log errors, not warnings + ], + }, + // used by the test and jenkins:unit tasks // runs the tslint script to check for Typescript linting errors typeCheck: { diff --git a/tasks/jenkins.js b/tasks/jenkins.js index 47b9429fed00..8c56a7fef7c9 100644 --- a/tasks/jenkins.js +++ b/tasks/jenkins.js @@ -32,6 +32,7 @@ module.exports = function (grunt) { 'run:checkFileCasing', 'licenses', 'verifyDependencyVersions', + 'run:checkLockfileSymlinks', 'run:verifyNotice', 'test:server', 'test:jest', diff --git a/x-pack/.gitignore b/x-pack/.gitignore index 32bd16a0f022..eb1aec466bbf 100644 --- a/x-pack/.gitignore +++ b/x-pack/.gitignore @@ -10,6 +10,3 @@ /.env /.kibana-plugin-helpers.dev.* !/plugins/infra/**/target - -# We don't want any yarn.lock files in here -/yarn.lock diff --git a/x-pack/plugins/infra/yarn.lock b/x-pack/plugins/infra/yarn.lock new file mode 120000 index 000000000000..6e09764ec763 --- /dev/null +++ b/x-pack/plugins/infra/yarn.lock @@ -0,0 +1 @@ +../../../yarn.lock \ No newline at end of file diff --git a/x-pack/yarn.lock b/x-pack/yarn.lock new file mode 120000 index 000000000000..1fe23b6e377d --- /dev/null +++ b/x-pack/yarn.lock @@ -0,0 +1 @@ +../yarn.lock \ No newline at end of file