Skip to content

Commit

Permalink
feat(nx-flutter): add support for Nx 's dependency graph generation
Browse files Browse the repository at this point in the history
Closes #28
  • Loading branch information
tinesoft committed Apr 26, 2021
1 parent 95abe9d commit 6fb58de
Show file tree
Hide file tree
Showing 12 changed files with 182 additions and 20 deletions.
Binary file added images/nx-flutter-dep-graph.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"private": true,
"dependencies": {
"inquirer": "^7.3.3",
"js-yaml": "^4.1.0",
"node-fetch": "^2.6.1",
"unzipper": "^0.10.11",
"xmldoc": "^1.1.2"
Expand Down
30 changes: 16 additions & 14 deletions packages/nx-flutter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,24 @@

## Contents

- [Features](#features)
- [Prerequisite](#prerequisite)
- [Getting Started](#getting-started)
- [Plugin Usage](#plugin-usage)

## Features

Here is a list of some of the coolest features of the plugin:

- ✅ Generation of Flutter applications/packages/modules/plugins based on **Flutter CLI** API
- ✅ Building, packaging, testing, etc your Flutter projects
- ✅ Integration with Nx's **dependency graph** (through `nx dep-graph` or `nx affected:dep-graph`): this allows you to **visualize** the dependencies of any Flutter projects inside your workspace, just like Nx natively does it for JS/TS-based projects!

![Nx Flutter dependency graph](https://raw.githubusercontent.com/tinesoft/nxrocks/develop/images/nx-flutter-dep-graph.png)
*Example of running the `nx dep-graph` command on a workspace with 2 Flutter projects inside*

- ...

## Prerequisite

This plugin relies on [`flutter's command-line`](https://flutter.dev/docs/reference/flutter-cli) to do its job. So, you must have [`flutter`](https://flutter.dev) installed on your system. If not, head to [flutter.dev/docs/get-started/install](https://flutter.dev/docs/get-started/install) and follow installation instructions for your OS.
Expand Down Expand Up @@ -42,10 +56,10 @@ npm install @nxrocks/nx-flutter --save-dev
yarn add @nxrocks/nx-flutter --dev
```

### Generating Application
### Generating Project


Simply run the `application` Schematics with the following command:
Simply run the `project` Schematics with the following command:

```
nx g @nxrocks/nx-flutter:create <app-folder>
Expand All @@ -59,18 +73,6 @@ To skip the interactive prompt, or if you want to customize all non-prompted opt
nx g @nxrocks/nx-flutter:create <app-folder> --optionName1 optionValue1 ... --optionNameN optionValueN
```

<details>
<summary><b><i>⚠️ Special generation instructions in case above fail ⚠️</i></b></summary>

Due to [a current bug with Nx's `nx g` command](https://github.com/nrwl/nx/issues/4499), you might need the following workaround in order to generate the application successfully (otherwise, [this error](https://github.com/tinesoft/nxrocks/issues/22#issuecomment-758021348) might occur):

1. Change the `"version"` field in your `workspace.json` from `2` to `1`
2. Generate the application with **Angular DevKit's schematics-cli**:
* Run `npx @angular-devkit/schematics-cli @nxrocks/nx-flutter:create <app-folder>`
* Answer to the prompted questions
3. Change the `"version"` field in your `workspace.json` back to `2`
</details>

#### Generation Options

Here the list of available generation options :
Expand Down
3 changes: 2 additions & 1 deletion packages/nx-flutter/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"dependencies": {
"@nrwl/workspace": "*",
"@nrwl/devkit": "*",
"inquirer": "^7.3.3"
"inquirer": "^7.3.3",
"js-yaml": "4.1.0"
}
}
11 changes: 10 additions & 1 deletion packages/nx-flutter/src/generators/project/generator.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Tree, logger, readProjectConfiguration } from '@nrwl/devkit';
import { Tree, logger, readProjectConfiguration, readJson } from '@nrwl/devkit';
import { createTreeWithEmptyWorkspace } from '@nrwl/devkit/testing';

import { projectGenerator } from './generator';
Expand Down Expand Up @@ -182,4 +182,13 @@ describe('application generator', () => {
])
);
});


it('should add plugin to nx.json', async () => {
await projectGenerator(tree, options);
const nxJson = readJson(tree, 'nx.json');
expect(nxJson.plugins).toEqual(['@nxrocks/nx-flutter']);

});

});
4 changes: 2 additions & 2 deletions packages/nx-flutter/src/generators/project/generator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Tree, addProjectConfiguration, } from '@nrwl/devkit';

import { isFlutterInstalled } from '../../utils/flutter-utils';
import { normalizeOptions, promptAdditionalOptions, generateFlutterProject } from './lib';
import { normalizeOptions, promptAdditionalOptions, generateFlutterProject, addPluginToNxJson } from './lib';
import { ProjectGeneratorOptions } from './schema';

export async function projectGenerator(tree:Tree, options: ProjectGeneratorOptions) {
Expand Down Expand Up @@ -68,7 +68,7 @@ export async function projectGenerator(tree:Tree, options: ProjectGeneratorOptio
tags: normalizedOptions.parsedTags,
});
await generateFlutterProject(tree,normalizedOptions)

addPluginToNxJson(tree);
}

export default projectGenerator;
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Tree, readJson, writeJson} from '@nrwl/devkit';

export function addPluginToNxJson(tree:Tree){
const nxJson = readJson(tree, 'nx.json');
nxJson.plugins = (nxJson.plugins || []);
nxJson.plugins.push('@nxrocks/nx-flutter');

writeJson(tree,'nx.json', nxJson);
}
3 changes: 2 additions & 1 deletion packages/nx-flutter/src/generators/project/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { generateFlutterProject } from './generate-project';
export { promptAdditionalOptions } from './prompt-options';
export { addPluginToNxJson } from './add-plugin-to-nx-json'
export { normalizeOptions } from './normalize-options';
export { promptAdditionalOptions } from './prompt-options';
3 changes: 2 additions & 1 deletion packages/nx-flutter/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { projectGenerator } from './generators/project/generator';
export { projectGenerator } from './generators/project/generator';
export { processProjectGraph } from './project-graph';
85 changes: 85 additions & 0 deletions packages/nx-flutter/src/project-graph.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {
logger,
ProjectGraph,
ProjectGraphBuilder,
ProjectGraphProcessorContext,
DependencyType,
ProjectConfiguration,
WorkspaceJsonConfiguration
} from '@nrwl/devkit';

import * as path from 'path';

import { fileExists } from '@nrwl/workspace/src/utils/fileutils';
import { appRootPath } from '@nrwl/workspace/src/utils/app-root';
import { getPackageInfo, PackageInfo } from './utils/deps-utils';
interface WorkspacePackageInfoConfiguration {
projects: {
[projectName: string]: PackageInfo;
};

packages: {
[packageId: string]: string;
}
}

function isFlutterProject(project: ProjectConfiguration): boolean {

return fileExists(path.join(appRootPath, project.root, 'pubspec.yaml'));
}

function getPackageInfosForNxFlutterProjects(workspace: WorkspaceJsonConfiguration): WorkspacePackageInfoConfiguration {
const workspacePackageInfo = {
projects: {},
packages: {}
};

Object.entries(workspace.projects).filter(([, project]) => isFlutterProject(project))
.forEach(([projectName, project]) => {
try {
const pkgInfo = getPackageInfo(path.join(appRootPath, project.root));

workspacePackageInfo.projects[projectName] = pkgInfo;
workspacePackageInfo.packages[pkgInfo.packageId] = projectName;
}
catch {
logger.warn(`[nx-flutter]: Failed to get package info for project '${projectName}'`);
}
});

return workspacePackageInfo;
}

function addDependenciesForProject(rootProjectName: string, rootPkgInfo: PackageInfo, builder: ProjectGraphBuilder, workspace: WorkspacePackageInfoConfiguration): void {

logger.debug(`[nx-flutter]: Adding dependencies for project '${rootProjectName}'...`)

rootPkgInfo.dependencies.forEach(depPkgInfo => {
const depProjectName = workspace.packages[depPkgInfo.packageId];

if (depProjectName) {
builder.addDependency(
DependencyType.static,
rootProjectName,
depProjectName
);
}
});
}

export function processProjectGraph(
graph: ProjectGraph,
context: ProjectGraphProcessorContext
): ProjectGraph {
const builder = new ProjectGraphBuilder(graph);

logger.debug('[nx-flutter]: Looking Flutter related projects inside the workspace...');

const workspace = getPackageInfosForNxFlutterProjects(context.workspace);

Object.entries(workspace.projects).forEach(([projectName, pkgInfo]) => {
addDependenciesForProject(projectName, pkgInfo, builder, workspace);
});

return builder.getProjectGraph();
}
41 changes: 41 additions & 0 deletions packages/nx-flutter/src/utils/deps-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { load } from 'js-yaml';
import * as path from 'path';
import * as fs from 'fs';

import { fileExists } from '@nrwl/workspace/src/utils/fileutils';

interface Pubspec {
name: string;
version: string;
description: string;
homepage?: string
documentation?: string
environment?: Record<string, string>
dependencies?: Record<string, string>,
dev_dependencies?: Record<string, string>
}
export interface PackageInfo {
packageId: string;
dependencies?: PackageInfo[];
}

export function getPackageInfo(projectRoot: string): PackageInfo {

if (fileExists(path.join(projectRoot, 'pubspec.yaml'))) { //flutterproject

const pubspec = load(fs.readFileSync(path.join(projectRoot, 'pubspec.yaml'), 'utf8')) as Pubspec;

const dependencies: PackageInfo[] = [];

Object.keys(Object.assign({}, pubspec.dependencies, pubspec.dev_dependencies )).forEach(depId => {
dependencies.push({packageId: depId});
});

return { packageId: pubspec.name, dependencies: dependencies};
}

throw new Error(
`Cannot inspect dependencies of Flutter projet at: '${projectRoot}'.\n` +
`No 'pubspec.yaml' was found.`
);
}
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1828,6 +1828,11 @@ argparse@^1.0.7:
dependencies:
sprintf-js "~1.0.2"

argparse@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==

argv-formatter@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/argv-formatter/-/argv-formatter-1.0.0.tgz#a0ca0cbc29a5b73e836eebe1cbf6c5e0e4eb82f9"
Expand Down Expand Up @@ -6114,6 +6119,13 @@ js-yaml@^3.13.1, js-yaml@^3.9.0:
argparse "^1.0.7"
esprima "^4.0.0"

js-yaml@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
dependencies:
argparse "^2.0.1"

jsbn@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
Expand Down

0 comments on commit 6fb58de

Please sign in to comment.