Skip to content

Commit

Permalink
[7.x] Add lockfile symlinks (#66211)
Browse files Browse the repository at this point in the history
  • Loading branch information
jportner committed May 12, 2020
1 parent daea374 commit ded2238
Show file tree
Hide file tree
Showing 24 changed files with 274 additions and 3 deletions.
1 change: 1 addition & 0 deletions packages/elastic-datemath/yarn.lock
1 change: 1 addition & 0 deletions packages/kbn-babel-code-parser/yarn.lock
1 change: 1 addition & 0 deletions packages/kbn-babel-preset/yarn.lock
1 change: 1 addition & 0 deletions packages/kbn-dev-utils/yarn.lock
1 change: 1 addition & 0 deletions packages/kbn-es/yarn.lock
1 change: 1 addition & 0 deletions packages/kbn-eslint-import-resolver-kibana/yarn.lock
1 change: 1 addition & 0 deletions packages/kbn-eslint-plugin-eslint/yarn.lock
1 change: 1 addition & 0 deletions packages/kbn-i18n/yarn.lock
1 change: 1 addition & 0 deletions packages/kbn-interpreter/yarn.lock
1 change: 1 addition & 0 deletions packages/kbn-plugin-generator/yarn.lock
1 change: 1 addition & 0 deletions packages/kbn-plugin-helpers/yarn.lock
1 change: 1 addition & 0 deletions packages/kbn-pm/yarn.lock
1 change: 1 addition & 0 deletions packages/kbn-spec-to-console/yarn.lock
1 change: 1 addition & 0 deletions packages/kbn-storybook/yarn.lock
1 change: 1 addition & 0 deletions packages/kbn-test/yarn.lock
1 change: 1 addition & 0 deletions packages/kbn-ui-framework/yarn.lock
1 change: 1 addition & 0 deletions packages/kbn-utility-types/yarn.lock
21 changes: 21 additions & 0 deletions scripts/check_lockfile_symlinks.js
Original file line number Diff line number Diff line change
@@ -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');
222 changes: 222 additions & 0 deletions src/dev/run_check_lockfile_symlinks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/*
* 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 '@kbn/dev-utils';

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*/**/*',
// cypress isn't used in production, ignore it
'x-pack/plugins/apm/e2e/*',
// apm scripts aren't used in production, ignore them
'x-pack/plugins/apm/scripts/*',
];

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');
}
11 changes: 11 additions & 0 deletions tasks/config/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,17 @@ module.exports = function(grunt) {
],
}),

// used by the test tasks
// runs the check_lockfile_symlinks script to ensure manifests with non-dev dependencies have adjacent lockfile symlinks
checkLockfileSymlinks: scriptWithGithubChecks({
title: 'Check lockfile symlinks',
cmd: NODE,
args: [
'scripts/check_lockfile_symlinks',
'--quiet', // only log errors, not warnings
],
}),

// used by the test tasks
// runs the check_published_api_changes script to ensure API changes are explictily accepted
checkDocApiChanges: scriptWithGithubChecks({
Expand Down
1 change: 1 addition & 0 deletions tasks/jenkins.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ module.exports = function(grunt) {
'run:typeCheck',
'run:i18nCheck',
'run:checkFileCasing',
'run:checkLockfileSymlinks',
'run:licenses',
'run:verifyDependencyVersions',
'run:verifyNotice',
Expand Down
3 changes: 0 additions & 3 deletions x-pack/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,3 @@
!/legacy/plugins/infra/**/target
.cache
!/legacy/plugins/siem/**/target

# We don't want any yarn.lock files in here
/yarn.lock
1 change: 1 addition & 0 deletions x-pack/plugins/endpoint/yarn.lock
1 change: 1 addition & 0 deletions x-pack/yarn.lock

0 comments on commit ded2238

Please sign in to comment.