Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[kbn-pm] Enable intermediate build directory for projects #16839

Merged
merged 5 commits into from
Feb 21, 2018
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
54 changes: 38 additions & 16 deletions packages/kbn-pm/dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6787,14 +6787,10 @@ var _writePkg = __webpack_require__(290);

var _writePkg2 = _interopRequireDefault(_writePkg);

var _path = __webpack_require__(1);

var _path2 = _interopRequireDefault(_path);

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function readPackageJson(dir) {
return (0, _readPkg2.default)(_path2.default.join(dir, 'package.json'), { normalize: false });
return (0, _readPkg2.default)(dir, { normalize: false });
}
function writePackageJson(path, json) {
return (0, _writePkg2.default)(path, json);
Expand Down Expand Up @@ -9642,9 +9638,22 @@ class Project {
}
throw new _errors.CliError(`[${this.name}] depends on [${project.name}], but it's not using the local package. ${updateMsg}`, meta);
}
getBuildConfig() {
return this.json.kibana && this.json.kibana.build || {};
}
/**
* Whether a package should not be included in the Kibana build artifact.
*/
skipFromBuild() {
const json = this.json;
return json.kibana && json.kibana.build && json.kibana.build.skip === true;
return this.getBuildConfig().skip === true;
}
/**
* Returns the directory that should be copied into the Kibana build artifact.
* This config can be specified to only include the project's build artifacts
* instead of everything located in the project directory.
*/
getIntermediateBuildDirectory() {
return _path2.default.resolve(this.path, this.getBuildConfig().intermediateBuildDirectory || '.');
}
hasScript(name) {
return name in this.scripts;
Expand Down Expand Up @@ -40994,25 +41003,38 @@ let buildProject = (() => {
return _ref4.apply(this, arguments);
};
})();
/**
* Copy all the project's files from its "intermediate build directory" and
* into the build. The intermediate directory can either be the root of the
* project or some other location defined in the project's `package.json`.
*
* When copying all the files into the build, we exclude `node_modules` because
* we want the Kibana build to be responsible for actually installing all
* dependencies. The primary reason for allowing the Kibana build process to
* manage dependencies is that it will "dedupe" them, so we don't include
* unnecessary copies of dependencies.
*/


let copyToBuild = (() => {
var _ref5 = _asyncToGenerator(function* (project, kibanaRoot, buildRoot) {
// We want the package to have the same relative location within the build
const relativeProjectPath = (0, _path.relative)(kibanaRoot, project.path);
const buildProjectPath = (0, _path.resolve)(buildRoot, relativeProjectPath);
// When copying all the files into the build, we exclude `package.json` as we
// write a separate "production-ready" `package.json` below, and we exclude
// `node_modules` because we want the Kibana build to actually install all
// dependencies. The primary reason for allowing the Kibana build process to
// install the dependencies is that it will "dedupe" them, so we don't include
// unnecessary copies of dependencies.
yield (0, _cpy2.default)(['**/*', '!package.json', '!node_modules/**'], buildProjectPath, {
cwd: project.path,
yield (0, _cpy2.default)(['**/*', '!node_modules/**'], buildProjectPath, {
cwd: project.getIntermediateBuildDirectory(),
parents: true,
nodir: true,
dot: true
});
const packageJson = project.json;
// If a project is using an intermediate build directory, we special-case our
// handling of `package.json`, as the project build process might have copied
// (a potentially modified) `package.json` into the intermediate build
// directory already. If so, we want to use that `package.json` as the basis
// for creating the production-ready `package.json`. If it's not present in
// the intermediate build, we fall back to using the project's already defined
// `package.json`.
const packageJson = (yield (0, _fs.isFile)((0, _path.join)(buildProjectPath, 'package.json'))) ? yield (0, _package_json.readPackageJson)(buildProjectPath) : project.json;
const preparedPackageJson = (0, _package_json.createProductionPackageJson)(packageJson);
yield (0, _package_json.writePackageJson)(buildProjectPath, preparedPackageJson);
});
Expand Down
38 changes: 27 additions & 11 deletions packages/kbn-pm/src/production/build_production_projects.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import del from 'del';
import { relative, resolve } from 'path';
import { relative, resolve, join } from 'path';
import copy from 'cpy';

import { getProjectPaths } from '../config';
Expand All @@ -11,8 +11,9 @@ import {
import {
createProductionPackageJson,
writePackageJson,
readPackageJson,
} from '../utils/package_json';
import { isDirectory } from '../utils/fs';
import { isDirectory, isFile } from '../utils/fs';
import { Project } from '../utils/project';

export async function buildProductionProjects({
Expand Down Expand Up @@ -76,6 +77,17 @@ async function buildProject(project: Project) {
}
}

/**
* Copy all the project's files from its "intermediate build directory" and
* into the build. The intermediate directory can either be the root of the
* project or some other location defined in the project's `package.json`.
*
* When copying all the files into the build, we exclude `node_modules` because
* we want the Kibana build to be responsible for actually installing all
* dependencies. The primary reason for allowing the Kibana build process to
* manage dependencies is that it will "dedupe" them, so we don't include
* unnecessary copies of dependencies.
*/
async function copyToBuild(
project: Project,
kibanaRoot: string,
Expand All @@ -85,20 +97,24 @@ async function copyToBuild(
const relativeProjectPath = relative(kibanaRoot, project.path);
const buildProjectPath = resolve(buildRoot, relativeProjectPath);

// When copying all the files into the build, we exclude `package.json` as we
// write a separate "production-ready" `package.json` below, and we exclude
// `node_modules` because we want the Kibana build to actually install all
// dependencies. The primary reason for allowing the Kibana build process to
// install the dependencies is that it will "dedupe" them, so we don't include
// unnecessary copies of dependencies.
await copy(['**/*', '!package.json', '!node_modules/**'], buildProjectPath, {
cwd: project.path,
await copy(['**/*', '!node_modules/**'], buildProjectPath, {
cwd: project.getIntermediateBuildDirectory(),
parents: true,
nodir: true,
dot: true,
});

const packageJson = project.json;
// If a project is using an intermediate build directory, we special-case our
// handling of `package.json`, as the project build process might have copied
// (a potentially modified) `package.json` into the intermediate build
// directory already. If so, we want to use that `package.json` as the basis
// for creating the production-ready `package.json`. If it's not present in
// the intermediate build, we fall back to using the project's already defined
// `package.json`.
const packageJson = (await isFile(join(buildProjectPath, 'package.json')))
? await readPackageJson(buildProjectPath)
: project.json;

const preparedPackageJson = createProductionPackageJson(packageJson);
await writePackageJson(buildProjectPath, preparedPackageJson);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "@elastic/baz",
"version": "1.0.0",
"main": "./index.js"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log('@elastic/baz');
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "@elastic/baz",
"version": "1.0.0",
"private": true,
"main": "./code.js",
"dependencies": {
"noop3": "999.999.999"
},
"devDependencies": {
"shx": "^0.2.2"
},
"scripts": {
"build": "shx cp code.js build/index.js"
},
"kibana": {
"build": {
"intermediateBuildDirectory": "build"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"name": "@elastic/quux",
"version": "1.0.0",
"private": true,
"main": "./quux.js",
"devDependencies": {
"shx": "^0.2.2"
},
"scripts": {
"build": "shx mkdir build; shx cp quux.js build/index.js"
},
"kibana": {
"build": {
"intermediateBuildDirectory": "build"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log('@elastic/quux');
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,81 @@ Array [
"packages/bar/src/index.js",
"packages/bar/target/index.js",
"packages/bar/yarn.lock",
"packages/baz/index.js",
"packages/baz/package.json",
"packages/foo/package.json",
"packages/foo/src/index.js",
"packages/foo/target/index.js",
"packages/foo/yarn.lock",
"packages/quux/index.js",
"packages/quux/package.json",
]
`;

exports[`kbn-pm production builds and copies projects for production: packages/bar/package.json 1`] = `
Object {
"dependencies": Object {
"lodash": "4.17.4",
},
"devDependencies": Object {
"babel-cli": "^6.26.0",
"babel-preset-env": "^1.6.1",
},
"main": "./target/index.js",
"name": "@elastic/bar",
"private": true,
"scripts": Object {
"build": "babel --presets env --out-dir target src",
},
"version": "1.0.0",
}
`;

exports[`kbn-pm production builds and copies projects for production: packages/baz/package.json 1`] = `
Object {
"main": "./index.js",
"name": "@elastic/baz",
"version": "1.0.0",
}
`;

exports[`kbn-pm production builds and copies projects for production: packages/foo/package.json 1`] = `
Object {
"dependencies": Object {
"@elastic/bar": "file:../bar",
"lodash": "4.17.4",
},
"devDependencies": Object {
"babel-cli": "^6.26.0",
"babel-preset-env": "^1.6.1",
"moment": "2.20.1",
},
"main": "./target/index.js",
"name": "@elastic/foo",
"private": true,
"scripts": Object {
"build": "babel --presets env --out-dir target src",
},
"version": "1.0.0",
}
`;

exports[`kbn-pm production builds and copies projects for production: packages/quux/package.json 1`] = `
Object {
"devDependencies": Object {
"shx": "^0.2.2",
},
"kibana": Object {
"build": Object {
"intermediateBuildDirectory": "build",
},
},
"main": "./quux.js",
"name": "@elastic/quux",
"private": true,
"scripts": Object {
"build": "shx mkdir build; shx cp quux.js build/index.js",
},
"version": "1.0.0",
}
`;
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import tempy from 'tempy';
import copy from 'cpy';
import { resolve } from 'path';
import { resolve, relative, join } from 'path';
import globby from 'globby';

import { buildProductionProjects } from '../build_production_projects';
import { getProjects } from '../../utils/projects';
import { readPackageJson } from '../../utils/package_json';

describe('kbn-pm production', function() {
test(
Expand Down Expand Up @@ -38,7 +39,15 @@ describe('kbn-pm production', function() {
});

expect(files.sort()).toMatchSnapshot();

for (const file of files) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Maybe we can get rid of "conditional" expect and do something like this instead?

const packageFilePromises = files
  .filter(file => file.endsWith('package.json'))
  .map(packageFile => readPackageJson(join(buildRoot, packageFile)));
expect(await Promise.all(packageFilePromises)).toMatchSnapshot();

Copy link
Contributor Author

@kimjoar kimjoar Feb 21, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As the snapshots already account for the expect not running (aka it handles the snapshot count), I think I'll keep this as-is for now.

if (file.endsWith('package.json')) {
expect(await readPackageJson(join(buildRoot, file))).toMatchSnapshot(
file
);
}
}
},
60 * 1000
2 * 60 * 1000
);
});
2 changes: 1 addition & 1 deletion packages/kbn-pm/src/utils/package_json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export type PackageDependencies = { [key: string]: string };
export type PackageScripts = { [key: string]: string };

export function readPackageJson(dir: string) {
return readPkg(path.join(dir, 'package.json'), { normalize: false });
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed this, as it's already handled by readPkg

return readPkg(dir, { normalize: false });
}

export function writePackageJson(path: string, json: PackageJson) {
Expand Down
25 changes: 25 additions & 0 deletions packages/kbn-pm/src/utils/project.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,28 @@ describe('#getExecutables()', () => {
).toThrowErrorMatchingSnapshot();
});
});

describe('#getIntermediateBuildDirectory', () => {
test('is the same as the project path when not specified', () => {
const project = createProjectWith({}, 'packages/my-project');
const path = project.getIntermediateBuildDirectory();

expect(path).toBe(project.path);
});

test('appends the `intermediateBuildDirectory` to project path when specified', () => {
const project = createProjectWith(
{
kibana: {
build: {
intermediateBuildDirectory: 'quux',
},
},
},
'packages/my-project'
);
const path = project.getIntermediateBuildDirectory();

expect(path).toBe(join(project.path, 'quux'));
});
});
27 changes: 25 additions & 2 deletions packages/kbn-pm/src/utils/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ import {
} from './package_json';
import { CliError } from './errors';

interface BuildConfig {
skip?: boolean;
intermediateBuildDirectory?: string;
}

export class Project {
static async fromPath(path: string) {
const pkgJson = await readPackageJson(path);
Expand Down Expand Up @@ -86,9 +91,27 @@ export class Project {
);
}

getBuildConfig(): BuildConfig {
return (this.json.kibana && this.json.kibana.build) || {};
}

/**
* Whether a package should not be included in the Kibana build artifact.
*/
skipFromBuild() {
const json = this.json;
return json.kibana && json.kibana.build && json.kibana.build.skip === true;
return this.getBuildConfig().skip === true;
}

/**
* Returns the directory that should be copied into the Kibana build artifact.
* This config can be specified to only include the project's build artifacts
* instead of everything located in the project directory.
*/
getIntermediateBuildDirectory() {
return path.resolve(
this.path,
this.getBuildConfig().intermediateBuildDirectory || '.'
);
}

hasScript(name: string) {
Expand Down