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

feat: introduce CompositeProject #289

Merged
merged 15 commits into from
Nov 11, 2020
Merged
Show file tree
Hide file tree
Changes from 12 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
68 changes: 68 additions & 0 deletions API.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ Name|Description
[AwsCdkConstructLibrary](#projen-awscdkconstructlibrary)|AWS CDK construct library project.
[AwsCdkTypeScriptApp](#projen-awscdktypescriptapp)|AWS CDK app in TypeScript.
[Component](#projen-component)|Represents a project component.
[CompositeProject](#projen-compositeproject)|Creates a composite project.
[ConstructLibrary](#projen-constructlibrary)|A multi-language library for CDK constructs.
[ConstructLibraryAws](#projen-constructlibraryaws)|*No description*
[ConstructLibraryCdk8s](#projen-constructlibrarycdk8s)|CDK8s construct library project.
Expand Down Expand Up @@ -55,6 +56,8 @@ Name|Description
[AwsCdkConstructLibraryOptions](#projen-awscdkconstructlibraryoptions)|Options for the construct-lib-aws project.
[AwsCdkTypeScriptAppOptions](#projen-awscdktypescriptappoptions)|*No description*
[Catalog](#projen-catalog)|*No description*
[CompositeProjectChild](#projen-compositeprojectchild)|Declares a sub-project of the composite project.
[CompositeProjectOptions](#projen-compositeprojectoptions)|Options for `CompositeProject`.
[ConstructLibraryAwsOptions](#projen-constructlibraryawsoptions)|*No description*
[ConstructLibraryCdk8sOptions](#projen-constructlibrarycdk8soptions)|*No description*
[ConstructLibraryOptions](#projen-constructlibraryoptions)|*No description*
Expand Down Expand Up @@ -527,6 +530,44 @@ synthesize(_outdir: string): void



## class CompositeProject 🔹 <a id="projen-compositeproject"></a>

Creates a composite project.

__Extends__: [Project](#projen-project)

### Initializer




```ts
new CompositeProject(options?: CompositeProjectOptions)
```

* **options** (<code>[CompositeProjectOptions](#projen-compositeprojectoptions)</code>) *No description*
* **projects** (<code>Array<[CompositeProjectChild](#projen-compositeprojectchild)></code>) Declaratively define sub-projects by their sub paths. __*Optional*__


### Methods


#### addProject(subPath, project)🔹 <a id="projen-compositeproject-addproject"></a>

Adds a project as a sub-project at a sub path.

```ts
addProject(subPath: string, project: Project): void
```

* **subPath** (<code>string</code>) *No description*
* **project** (<code>[Project](#projen-project)</code>) *No description*






## class ConstructLibrary 🔹 <a id="projen-constructlibrary"></a>

A multi-language library for CDK constructs.
Expand Down Expand Up @@ -3711,6 +3752,33 @@ Name | Type | Description



## struct CompositeProjectChild 🔹 <a id="projen-compositeprojectchild"></a>


Declares a sub-project of the composite project.



Name | Type | Description
-----|------|-------------
**path**🔹 | <code>string</code> | Project subpath.
**project**🔹 | <code>[Project](#projen-project)</code> | Projen project to synthesize in `path`.



## struct CompositeProjectOptions 🔹 <a id="projen-compositeprojectoptions"></a>


Options for `CompositeProject`.



Name | Type | Description
-----|------|-------------
**projects**?🔹 | <code>Array<[CompositeProjectChild](#projen-compositeprojectchild)></code> | Declaratively define sub-projects by their sub paths.<br/>__*Optional*__



## struct ConstructLibraryAwsOptions ⚠️ <a id="projen-constructlibraryawsoptions"></a>


Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ list):
* [awscdk-app-ts](https://github.com/projen/projen/blob/master/API.md#projen-awscdktypescriptapp) - AWS CDK app in TypeScript.
* [awscdk-construct](https://github.com/projen/projen/blob/master/API.md#projen-awscdkconstructlibrary) - AWS CDK construct library project.
* [cdk8s-construct](https://github.com/projen/projen/blob/master/API.md#projen-constructlibrarycdk8s) - CDK8s construct library project.
* [composite](https://github.com/projen/projen/blob/master/API.md#projen-compositeproject) - Creates a composite project.
* [jsii](https://github.com/projen/projen/blob/master/API.md#projen-jsiiproject) - Multi-language jsii library project.
* [nextjs](https://github.com/projen/projen/blob/master/API.md#projen-nextjsproject) - Next.js project without TypeScript.
* [nextjs-ts](https://github.com/projen/projen/blob/master/API.md#projen-nextjstypescriptproject) - Next.js project with TypeScript.
Expand Down
86 changes: 86 additions & 0 deletions src/composite-project.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import * as path from 'path';
import { Component } from './component';
import { Project } from './project';

/**
* Options for `CompositeProject`.
*/
export interface CompositeProjectOptions {
/**
* Declaratively define sub-projects by their sub paths.
* @example
* [
* {
* path: path.join('packages', 'foo'),
* project: new NodeProject({ name: 'foo' }),
* },
* ...
* ]
*/
readonly projects?: CompositeProjectChild[];
misterjoshua marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Declares a sub-project of the composite project.
*/
export interface CompositeProjectChild {
eladb marked this conversation as resolved.
Show resolved Hide resolved
/**
* Project subpath.
*/
readonly path: string;

/**
* Projen project to synthesize in `path`.
*/
readonly project: Project;
}

/**
* Creates a composite project.
*/
export class CompositeProject extends Project {
// Tracks which sub paths have already been added.
private readonly projects: Record<string, ProjectComponent>;

constructor(options?: CompositeProjectOptions) {
super();

this.projects = {};

// Add declaratively defined subprojects.
for (const subProject of options?.projects ?? []) {
this.addProject(subProject.path, subProject.project);
}
}

/**
* Adds a project as a sub-project at a sub path.
* @param subPath
* @param project
*/
addProject(subPath: string, project: Project) {
misterjoshua marked this conversation as resolved.
Show resolved Hide resolved
if (this.projects[subPath]) {
throw new Error(`Cannot add project as the sub path ${subPath} is already in use`);
}

this.projects[subPath] = new ProjectComponent(this, {
path: subPath,
project: project,
});
}
}

/**
* Represent a project as a component in another project.
* @experimental
*/
class ProjectComponent extends Component {
constructor(project: Project, private readonly options: CompositeProjectChild) {
super(project);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Maybe we could also offer access to the parent root then for .github stuff etc

@hoegertn I was thinking that there's a design decision to be made about how a project can know the parent root so that it knows the right place to put the .github stuff. Given that a sub-project is instantiated before it knows that it's a sub-project, it seems to me that there's a need to inform the sub-project of that. I could call back to the project to inform it here, for instance.


synthesize(outdir: string) {
// Synths the project into a subdir given by `path`
this.options.project.synth(path.join(outdir, this.options.path));
}
}
misterjoshua marked this conversation as resolved.
Show resolved Hide resolved
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from './awscdk-app-ts';
export * from './awscdk-construct';
export * from './cdk8s-construct';
export * from './component';
export * from './composite-project';
export * from './construct-lib';
export * from './dependabot';
export * from './docker-compose';
Expand Down
22 changes: 22 additions & 0 deletions test/__snapshots__/inventory.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2994,6 +2994,28 @@ jest typescript tests and only if all tests pass, run the compiler.",
"pjid": "cdk8s-construct",
"typename": "ConstructLibraryCdk8s",
},
Object {
"docs": "Creates a composite project.",
"docsurl": "https://github.com/projen/projen/blob/master/API.md#projen-compositeproject",
"fqn": "projen.CompositeProject",
"moduleName": "projen",
"options": Array [
Object {
"deprecated": false,
"docs": "Declaratively define sub-projects by their sub paths.",
"name": "projects",
"optional": true,
"parent": "CompositeProjectOptions",
"path": Array [
"projects",
],
"switch": "projects",
"type": "unknown",
},
],
"pjid": "composite",
"typename": "CompositeProject",
},
Object {
"docs": "Multi-language jsii library project.",
"docsurl": "https://github.com/projen/projen/blob/master/API.md#projen-jsiiproject",
Expand Down
12 changes: 12 additions & 0 deletions test/__snapshots__/new.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,18 @@ project.synth();
"
`;

exports[`projen new composite 1`] = `
"const { CompositeProject } = require('projen');

const project = new CompositeProject({
/* CompositeProjectOptions */
// projects: undefined, /* Declaratively define sub-projects by their sub paths. */
});

project.synth();
"
`;

exports[`projen new jsii 1`] = `
"const { JsiiProject } = require('projen');

Expand Down
66 changes: 66 additions & 0 deletions test/composite-project.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import * as path from 'path';
import * as fs from 'fs-extra';
import { NodeProject, Project, CompositeProject } from '../src';
import * as logging from '../src/logging';

logging.disable();

let tempDir: string;
beforeEach(() => {
tempDir = fs.mkdtempSync(path.join(__dirname, 'tmp.subdir'));
});

afterEach(() => {
if (tempDir) {
fs.removeSync(tempDir);
}
});

test('composing projects declaratively', () => {
const comp = new CompositeProject({
projects: [
{
path: path.join('packages', 'foo'),
project: new Project(),
},
],
});

comp.synth(tempDir);

// THEN
expect(fs.existsSync(path.join(tempDir, 'packages', 'foo', '.gitignore'))).toBeTruthy();
});

test('composing projects synthesizes to subdirs', () => {
// GIVEN
const comp = new CompositeProject();

// WHEN
comp.addProject(path.join('packages', 'foo'), new NodeProject({ name: 'foo' }));
comp.addProject(path.join('packages', 'bar'), new NodeProject({ name: 'bar' }));

comp.synth(tempDir);

// THEN
expect(fs.pathExistsSync(path.join(tempDir, 'README.md')));
expect(fs.readJSONSync(path.join(tempDir, 'packages', 'foo', 'package.json')))
.toEqual(expect.objectContaining({
name: 'foo',
}));
expect(fs.readJSONSync(path.join(tempDir, 'packages', 'bar', 'package.json')))
.toEqual(expect.objectContaining({
name: 'bar',
}));
});

test('errors when paths overlap', () => {
// GIVEN
const comp = new CompositeProject();
comp.addProject(path.join('packages', 'foo'), new NodeProject({ name: 'foo' }));

// WHEN/THEN
expect(() => {
comp.addProject(path.join('packages', 'foo'), new NodeProject({ name: 'bar' }));
}).toThrowError(/foo.*already in use/i);
});