Skip to content

Commit

Permalink
fix(nx-flutter): fix non-interactive generation of flutter projects
Browse files Browse the repository at this point in the history
BREAKING CHANGE: `interactive` option has been renamed into `skipAdditionalPrompts`

`interactive` is a reserved option for `nx generate` command, that gets deleted once Nx has interpreted it, so we need our own. Must still be combined with `--no-interactive` (from Nx), for fully non-interactivity
  • Loading branch information
tinesoft committed Aug 4, 2022
1 parent d78a139 commit 6c4a5aa
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 92 deletions.
37 changes: 19 additions & 18 deletions e2e/nx-flutter-e2e/tests/nx-flutter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { ensureNxProjectWithDeps } from '@nxrocks/common/testing';
jest.mock('inquirer'); // we mock 'inquirer' to bypass the interactive prompt
import * as inquirer from 'inquirer';

xdescribe('nx-flutter e2e', () => {
describe('nx-flutter e2e', () => {

beforeAll(async () => {

Expand All @@ -35,10 +35,11 @@ xdescribe('nx-flutter e2e', () => {
jest.resetAllMocks();
});

it.only('should create nx-flutter project with default options', async() => {
it('should create nx-flutter project with default options', async() => {
const appName = uniq('nx-flutter');

await runNxCommandAsync(`generate @nxrocks/nx-flutter:create ${appName} --no-interactive`);
const sep = process.platform === 'win32' ? '\\' : '/';
await runNxCommandAsync(`generate @nxrocks/nx-flutter:create ${appName} --skipAdditionalPrompts=true --no-interactive`);

const executors = [
{ name: 'analyze', output: `Analyzing ${appName}` },
Expand All @@ -47,25 +48,25 @@ xdescribe('nx-flutter e2e', () => {
//{ name: 'attach', output: `Attaching ${appName}` },

//build commands
{ name: 'build-aar', output: `Running Gradle task 'assembleAarDebug'...` },
{ name: 'build-apk', output: `Built build/app/outputs/flutter-apk/app-release.apk` },
{ name: 'build-appbundle', output: `Built build/app/outputs/bundle/release/app-release.aab` },
{ name: 'build-bundle', output: `Done in` },
//{ name: 'build-aar', output: `Running Gradle task 'assembleAarDebug'...` }, // only for module or plugin projects
{ name: 'build-apk', output: `Built build${sep}app${sep}outputs${sep}flutter-apk${sep}app-release.apk` },
//{ name: 'build-appbundle', output: `Built build/app/outputs/bundle/release/app-release.aab` },
//{ name: 'build-bundle', output: `Done in` },

//required an iOS certificate
//{name: 'buildIos', output: `No valid code signing certificates were found`},
//{name: 'buildIosFramework', output: `Building frameworks for iOS is only supported from a module`},
//{name: 'buildIpa', output: `No valid code signing certificates were found`},
//{name: 'build-ios', output: `No valid code signing certificates were found`},
//{name: 'build-ios-framework', output: `Building frameworks for iOS is only supported from a module`},
//{name: 'build-ipa', output: `No valid code signing certificates were found`},

{ name: 'clean', output: `Deleting flutter_export_environment.sh...` },
//{ name: 'clean', output: `Deleting flutter_export_environment.sh...` },

//required a test file under 'test_driver/main_test.dart' (default)
//{ name: 'drive', output: `xxx` },

{ name: 'format', output: `Done in ` },
//{ name: 'format', output: `Formatted no files ` },

//required arb files under 'lib/l10n'
//{ name: 'genL10n', output: `The 'arb-dir' directory, 'LocalDirectory: 'lib/l10n'', does not exist` },
//{ name: 'gen-l10n', output: `The 'arb-dir' directory, 'LocalDirectory: 'lib/l10n'', does not exist` },

//required a running device
//{ name: 'install', output: `No target device found` },
Expand All @@ -88,7 +89,7 @@ xdescribe('nx-flutter e2e', () => {
expect(() =>
checkFilesExist(`apps/${appName}/pubspec.yaml`)
).not.toThrow();
}, 200000);
}, 400000);

it('should create nx-flutter project with given options', async() => {
const appName = uniq('nx-flutter');
Expand All @@ -101,12 +102,12 @@ xdescribe('nx-flutter e2e', () => {
const pub = true;
const offline = true;

await runNxCommandAsync(`generate @nxrocks/nx-flutter:create ${appName} --interactive=false --org=${org} --description="${description}" --androidLanguage=${androidLanguage} --iosLanguage=${iosLanguage} --template=${template} --platforms="${platforms}" --pub=${pub} --offline=${offline} `);
await runNxCommandAsync(`generate @nxrocks/nx-flutter:create ${appName} --skipAdditionalPrompts=true --no-interactive --org=${org} --description="${description}" --androidLanguage=${androidLanguage} --iosLanguage=${iosLanguage} --template=${template} --platforms="${platforms}" --pub=${pub} --offline=${offline} `);

const executors = [

{ name: 'clean', output: `Deleting flutter_export_environment.sh...` },
{ name: 'format', output: `Done in ` },
{ name: 'format', output: `Formatted no files ` },
{ name: 'test', output: `All tests passed!` },
];

Expand All @@ -129,7 +130,7 @@ xdescribe('nx-flutter e2e', () => {
const appName = uniq('nx-flutter');

await runNxCommandAsync(
`generate @nxrocks/nx-flutter:create ${appName} --interactive=false --directory subdir`
`generate @nxrocks/nx-flutter:create ${appName} --skipAdditionalPrompts=true --no-interactive --directory subdir`
);
expect(() =>
checkFilesExist(`apps/subdir/${appName}/pubspec.yaml`)
Expand All @@ -142,7 +143,7 @@ xdescribe('nx-flutter e2e', () => {
const appName = uniq('nx-flutter');

await runNxCommandAsync(
`generate @nxrocks/nx-flutter:create ${appName} --interactive=false --tags e2etag,e2ePackage`
`generate @nxrocks/nx-flutter:create ${appName} --skipAdditionalPrompts=true --no-interactive --tags e2etag,e2ePackage`
);
const project = readJson(`apps/${appName}/project.json`);
expect(project.tags).toEqual(['e2etag', 'e2ePackage']);
Expand Down
16 changes: 8 additions & 8 deletions e2e/smoke/tests/smoke.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,11 @@ describe('nxrocks smoke tests', () => {
);

execSync(
`npx nx g @nxrocks/nx-flutter:new ${flutterapp} --projectType application --interactive=false`,
`npx nx g @nxrocks/nx-flutter:new ${flutterapp} --template app --no-interactive --skipAdditionalPrompts=true`,
execSyncOptions(),
);
execSync(
`npx nx g @nxrocks/nx-flutter:new ${flutterlib} --projectType library --interactive=false`,
`npx nx g @nxrocks/nx-flutter:new ${flutterlib} --template plugin --no-interactive --skipAdditionalPrompts=true`,
execSyncOptions(),
);

Expand All @@ -127,8 +127,8 @@ describe('nxrocks smoke tests', () => {
execSync(`npx nx build ${bootlib}`, execSyncOptions());
execSync(`npx nx build ${quarkusapp}`, execSyncOptions());
execSync(`npx nx build ${quarkuslib}`, execSyncOptions());
execSync(`npx nx build ${flutterapp}`, execSyncOptions());
execSync(`npx nx build ${flutterlib}`, execSyncOptions());
execSync(`npx nx clean ${flutterapp}`, execSyncOptions());
execSync(`npx nx clean ${flutterlib}`, execSyncOptions());
execSync(`npx nx build ${mnApp}`, execSyncOptions());

expect(true).toBeTruthy();
Expand Down Expand Up @@ -173,11 +173,11 @@ describe('nxrocks smoke tests', () => {
);

execSync(
`npx nx g @nxrocks/nx-flutter:new ${flutterapp} --projectType application --interactive=false`,
`npx nx g @nxrocks/nx-flutter:new ${flutterapp} --template app --no-interactive --skipAdditionalPrompts=true`,
execSyncOptions(),
);
execSync(
`npx nx g @nxrocks/nx-flutter:new ${flutterlib} --projectType library --interactive=false`,
`npx nx g @nxrocks/nx-flutter:new ${flutterlib} --template plugin --no-interactive --skipAdditionalPrompts=true`,
execSyncOptions(),
);

Expand All @@ -199,8 +199,8 @@ describe('nxrocks smoke tests', () => {
execSync(`npx nx build ${bootlib}`, execSyncOptions());
execSync(`npx nx build ${quarkusapp}`, execSyncOptions());
execSync(`npx nx build ${quarkuslib}`, execSyncOptions());
execSync(`npx nx build ${flutterapp}`, execSyncOptions());
execSync(`npx nx build ${flutterlib}`, execSyncOptions());
execSync(`npx nx clean ${flutterapp}`, execSyncOptions());
execSync(`npx nx clean ${flutterlib}`, execSyncOptions());
execSync(`npx nx build ${mnApp}`, execSyncOptions());

expect(true).toBeTruthy();
Expand Down
2 changes: 1 addition & 1 deletion packages/nx-flutter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ Option | Value | Description
`platforms` | `android` \| `ios` \| `linux` \| `macos` \| `windows` \| `web` | Platforms supported by the project to generate
`pub` | `boolean` | Whether to run "flutter pub get" after the project has been created
`offline` | `boolean` | Whether or not to run 'flutter pub get' in offline mode
`interactive` | `boolean` | Whether or not to prompt for additional options (like `platforms`, `language`, etc). Useful in a CI environment, to avoid waiting for user input.
`skipAdditionalPrompts` | `boolean` | Whether or not to prompt for additional options (like `platforms`, `language`, etc). Useful in a CI environment, to avoid waiting for user input. Must be combined with `--interactive=false` or `--no-interactive` for full non-interactivity.
`tags` | `string` | Tags to use for linting (comma-separated)
`directory` | `string` | Directory where the project is placed

Expand Down
2 changes: 1 addition & 1 deletion packages/nx-flutter/generators.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "nx-flutter",
"version": "0.0.1",
"generators": {
"application": {
"project": {
"factory": "./src/generators/project/generator",
"schema": "./src/generators/project/schema.json",
"description": "Generator to generate the project",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ describe('application generator', () => {
name: 'testapp',
template: 'app',
platforms: ['android', 'ios', 'web', 'linux', 'windows', 'macos'],
interactive: true
};


Expand Down
115 changes: 56 additions & 59 deletions packages/nx-flutter/src/generators/project/generator.ts
Original file line number Diff line number Diff line change
@@ -1,75 +1,72 @@
import { Tree, addProjectConfiguration, } from '@nrwl/devkit';
import { addPluginToNxJson, NX_FLUTTER_PKG } from '@nxrocks/common';

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

export async function projectGenerator(tree:Tree, options: ProjectGeneratorOptions) {

if (!isFlutterInstalled()) {
throw new Error("'flutter' was not found on your system's PATH.\nPlease make sure you have installed it correctly.\n👉🏾 https://flutter.dev/docs/get-started/install");
}

const normalizedOptions = normalizeOptions(tree,options);

if(options.interactive )
await promptAdditionalOptions(tree,normalizedOptions);
if(!options.skipAdditionalPrompts ) {
await promptAdditionalOptions(tree,normalizedOptions);
}

const targets = {};
const commands = [
{ key: 'analyze', value: 'analyze' },
{ key: 'clean', value: 'clean' },
{ key: 'format', value: `format ${normalizedOptions.projectRoot}/*` },
{ key: 'test', value: 'test' },
];

if(normalizedOptions.template === 'app'){
commands.push(
{ key: 'assemble', value: 'assemble' },
{ key: 'attach', value: 'attach' },
{ key: 'drive', value: 'drive' },
{ key: 'gen-l10n', value: 'gen-l10n' },
{ key: 'install', value: 'install' },
{ key: 'run', value: 'run' },
)
}

const targets = {};
const commands = [
{ key: 'analyze', value: 'analyze' },
{ key: 'clean', value: 'clean' },
{ key: 'format', value: `format ${normalizedOptions.projectRoot}/*` },
{ key: 'test', value: 'test' },
];

if(normalizedOptions.template === 'app'){
commands.push(
{ key: 'assemble', value: 'assemble' },
{ key: 'attach', value: 'attach' },
{ key: 'drive', value: 'drive' },
{ key: 'gen-l10n', value: 'gen-l10n' },
{ key: 'install', value: 'install' },
{ key: 'run', value: 'run' },
)
}
if(normalizedOptions.platforms?.indexOf('android') != -1) {
commands.push(
{key: 'build-aar', value: 'build aar'},
{key: 'build-apk', value: 'build apk'},
{key: 'build-appbundle', value: 'build appbundle'},
{key: 'build-bundle', value: 'build bundle'},
)
}

if(normalizedOptions.platforms?.indexOf('android') != -1) {
commands.push(
{key: 'build-aar', value: 'build aar'},
{key: 'build-apk', value: 'build apk'},
{key: 'build-appbundle', value: 'build appbundle'},
{key: 'build-bundle', value: 'build bundle'},
)
}
if(normalizedOptions.platforms?.indexOf('ios') != -1) {
commands.push(
{key: 'build-ios', value: 'build ios'},
{key: 'build-ios-framework', value: 'build ios-framework'},
{key: 'build-ipa', value: 'build ipa'},
)
}

for (const command of commands) {
targets[command.key] = {
executor: `@nrwl/workspace:run-commands`,
options: {
command: `flutter ${command.value}`,
cwd: normalizedOptions.projectRoot
}
};
}
addProjectConfiguration(tree, normalizedOptions.projectName, {
root: normalizedOptions.projectRoot,
sourceRoot: `${normalizedOptions.projectRoot}/src`,
projectType: normalizedOptions.template === 'app' ? 'application' : 'library',
targets: targets,
tags: normalizedOptions.parsedTags,
});

if(normalizedOptions.platforms?.indexOf('ios') != -1) {
commands.push(
{key: 'build-ios', value: 'build ios'},
{key: 'build-ios-framework', value: 'build ios-framework'},
{key: 'build-ipa', value: 'build ipa'},
)
}

for (const command of commands) {
targets[command.key] = {
executor: `@nrwl/workspace:run-commands`,
options: {
command: `flutter ${command.value}`,
cwd: normalizedOptions.projectRoot
}
};
}
addProjectConfiguration(tree, normalizedOptions.projectName, {
root: normalizedOptions.projectRoot,
sourceRoot: `${normalizedOptions.projectRoot}/src`,
projectType: normalizedOptions.template === 'app' ? 'application' : 'library',
targets: targets,
tags: normalizedOptions.parsedTags,
});
await generateFlutterProject(tree,normalizedOptions)
addPluginToNxJson(NX_FLUTTER_PKG, tree);
await generateFlutterProject(tree,normalizedOptions)
addPluginToNxJson(NX_FLUTTER_PKG, tree);
}

export default projectGenerator;
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Tree, logger } from '@nrwl/devkit';
import { execSync } from 'child_process'
import { buildFlutterCreateOptions } from '../../../utils/flutter-utils';
import { buildFlutterCreateOptions, isFlutterInstalled } from '../../../utils/flutter-utils';

import { NormalizedSchema } from '../schema';

Expand All @@ -10,6 +10,10 @@ export async function generateFlutterProject(tree: Tree, options: NormalizedSche

logger.info(`Generating Flutter project with following options : ${opts}...`);

if (!isFlutterInstalled()) {
throw new Error("'flutter' was not found on your system's PATH.\nPlease make sure you have installed it correctly.\n👉🏾 https://flutter.dev/docs/get-started/install");
}

// Create the command to execute
const execute = `flutter create ${opts} ${options.projectRoot}`;
try {
Expand Down
2 changes: 1 addition & 1 deletion packages/nx-flutter/src/generators/project/schema.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export interface ProjectGeneratorOptions {
tags?: string;
directory?: string;

interactive?: boolean;
skipAdditionalPrompts?: boolean; //`interactive` is a reserved option for `nx generate` command, that gets deleted once Nx has interpreted it, so we need our own
}

export interface NormalizedSchema extends ProjectGeneratorOptions {
Expand Down
4 changes: 2 additions & 2 deletions packages/nx-flutter/src/generators/project/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,10 @@
"type": "boolean",
"description": "Whether or not to overwrite existing files when performing operations"
},
"interactive": {
"skipAdditionalPrompts": {
"type": "boolean",
"description": "Whether or not to prompt for additional options (like platforms, androidLanguage, etc)",
"default": true
"default": false
}
},
"required": [
Expand Down

0 comments on commit 6c4a5aa

Please sign in to comment.