Skip to content

Commit

Permalink
feat: Changing to a config based approach for obtaining the official …
Browse files Browse the repository at this point in the history
…openapi generator jar.
  • Loading branch information
gibahjoe committed Nov 2, 2024
1 parent 2ff3b99 commit a0f3ca2
Show file tree
Hide file tree
Showing 27 changed files with 596 additions and 142 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/code_quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ jobs:
uses: dart-lang/[email protected]
with:
sdk: stable

- name: Install Melos
run: dart pub global activate melos

- name: Bootstrap packages with Melos
run: melos bootstrap

- name: Install Dependencies
run: dart pub get
- name: Build runner
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/release_please.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ permissions:

jobs:
release-please:
if: github.event.pull_request.merged == true && !startsWith(github.event.pull_request.title, 'chore(release):')
runs-on: ubuntu-latest
steps:
- uses: google-github-actions/release-please-action@v3
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ doc/api/
.DS_Store

**/coverage

pubspec_overrides.yaml
!pubspec.lock
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ Check out the known issues article here [Known Issues](openapi-generator-annotat

## Contributing

All contributions are welcome. Please ensure to read through our [contributing guidelines](Contributing.md) before
All contributions are welcome. Please ensure to read through our [contributing guidelines](CONTRIBUTING.md) before
sending your PRs.

## Features and bugs
Expand Down
9 changes: 5 additions & 4 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Openapi Generator last run: : 2024-01-16T03:54:39.346434
// Openapi Generator last run: : 2024-10-31T23:11:13.130123
import 'package:flutter/material.dart';
import 'package:openapi_generator_annotations/openapi_generator_annotations.dart';

Expand All @@ -8,11 +8,12 @@ void main() {

@Openapi(
additionalProperties:
DioProperties(pubName: 'petstore_api', pubAuthor: 'Johnny dep..'),
DioProperties(pubName: 'petstore_api', pubAuthor: 'Johnny dep.'),
inputSpec:
RemoteSpec(path: 'https://petstore3.swagger.io/api/v3/openapi.json'),
typeMappings: {'Pet': 'ExamplePet'},
generatorName: Generator.dio,
generatorName: Generator.dioAlt,
updateAnnotatedFile: true,
runSourceGenOnOutput: true,
outputDirectory: 'api/petstore_api',
)
Expand Down Expand Up @@ -112,7 +113,7 @@ class _MyHomePageState extends State<MyHomePage> {
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
Expand Down
2 changes: 1 addition & 1 deletion example/openapi-spec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
openapi: 3.0.1
info:
title: OpenAPI Petstore
title: OpenAPI Petstore.
description: This is a sample server Petstore server. For this sample, you can use
the api key `special-key` to test the authorization filters.
license:
Expand Down
2 changes: 1 addition & 1 deletion example/openapi.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
openapi: "3.0.3"
info:
title: "OpenAPI definition"
title: "OpenAPI definition."
version: "v0.1"
paths:
/v0.1/transactions:
Expand Down
7 changes: 7 additions & 0 deletions melos.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
name: openapi_generator_dart

packages:
- example
- openapi-generator
- openapi-generator-annotations
- openapi-generator-cli
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ class Openapi {
final String? outputDirectory;

/// Defines whether the output directory should be cleaned up before generating the output.
///
///
/// e.g [''], ['lib/src']
final List<dynamic>? cleanSubOutputDirectory;
final List<String>? cleanSubOutputDirectory;

/// Skips the default behavior of validating an input specification.
///
Expand Down
4 changes: 1 addition & 3 deletions openapi-generator-annotations/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,9 @@ homepage: https://github.com/gibahjoe/openapi-generator-dart


environment:
sdk: '>=2.12.0 <3.0.0'
sdk: '>=2.12.0 <4.0.0'

dependencies:
crypto: '>=3.0.0 <4.0.0'
meta: '>=1.3.0 <2.0.0'

dev_dependencies:
test:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,6 @@ void main() {
props.pubAuthor,
props.pubAuthorEmail,
props.sourceFolder,
props.nullSafe,
props.nullSafeArrayDefault,
props.listAnyOf,
props.pubspecDevDependencies,
props.pubspecDependencies
Expand Down Expand Up @@ -266,8 +264,6 @@ void main() {
pubDescription: 'test',
pubAuthorEmail: '[email protected]',
pubAuthor: 'test',
nullSafe: true,
nullSafeArrayDefault: true,
listAnyOf: false,
pubspecDevDependencies: 'something',
pubspecDependencies: 'test',
Expand All @@ -289,8 +285,6 @@ void main() {
'sortParamsByRequiredFlag': props.sortParamsByRequiredFlag,
'sourceFolder': props.sourceFolder,
'wrapper': 'none',
'nullSafe': props.nullSafe,
'nullSafeArrayDefault': props.nullSafeArrayDefault,
'listAnyOf': props.listAnyOf,
'pubspecDevDependencies': props.pubspecDevDependencies,
'pubspecDependencies': props.pubspecDependencies,
Expand All @@ -314,8 +308,6 @@ void main() {
expect(actual.pubAuthor, props.pubAuthor);
expect(actual.pubAuthorEmail, props.pubAuthorEmail);
expect(actual.sourceFolder, props.sourceFolder);
expect(actual.nullSafe, props.nullSafe);
expect(actual.nullSafeArrayDefault, props.nullSafeArrayDefault);
expect(actual.listAnyOf, props.listAnyOf);
expect(actual.pubspecDevDependencies, props.pubspecDevDependencies);
expect(actual.pubspecDependencies, props.pubspecDependencies);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ void main() {
expect(props.cachePath, isNull);
expect(props.projectPubspecPath, isNull);
expect(props.debugLogging, isFalse);
expect(props.nameMappings, isNull);
});
group('NextGen', () {
test('Sets cachePath', () {
Expand Down
214 changes: 198 additions & 16 deletions openapi-generator-cli/bin/main.dart
Original file line number Diff line number Diff line change
@@ -1,26 +1,208 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';

/// Proxies commandline ars from openapi-generator to the build [arguments] the commandline arguments to proxy
void main(List<String> arguments) async {
exitCode = 0; // presume success
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as p;

const baseDownloadUrl =
'https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli';

/// Default configuration values
class ConfigDefaults {
static const openapiGeneratorVersion = '7.9.0';
static const additionalCommands = '';
static const downloadUrlOverride = null;
static const jarCacheDir = '.dart_tool/openapi_generator_cache';
static const customGeneratorUrls = <String>[
'https://repo1.maven.org/maven2/com/bluetrainsoftware/maven/openapi-dart-generator/7.2/openapi-dart-generator-7.2.jar'
];
}

/// Configuration keys as static constants
class ConfigKeys {
static const openapiGeneratorVersion = 'openapiGeneratorVersion';
static const additionalCommands = 'additionalCommands';
static const downloadUrlOverride = 'downloadUrlOverride';
static const jarCachePath = 'jarCacheDir';
static const customGeneratorUrls = 'customGeneratorUrls';
}

/// Resolves a given path to an absolute path, handling both relative and absolute inputs
String resolvePath(String path) {
return p.isAbsolute(path) ? path : p.absolute(Directory.current.path, path);
}

/// Loads configuration from JSON file or creates it with default values if not found
Future<Map<String, dynamic>> loadOrCreateConfig(String configPath) async {
_logOutput('[info] Loading config $configPath');
configPath = resolvePath(configPath);
final configFile = File(configPath);
if (await configFile.exists()) {
final contents = await configFile.readAsString();
return jsonDecode(contents);
} else {
_logOutput('[info] Config $configPath not found. Creating...');
final defaultConfig = {
ConfigKeys.openapiGeneratorVersion:
ConfigDefaults.openapiGeneratorVersion,
ConfigKeys.additionalCommands: ConfigDefaults.additionalCommands,
ConfigKeys.downloadUrlOverride: ConfigDefaults.downloadUrlOverride,
ConfigKeys.jarCachePath: ConfigDefaults.jarCacheDir,
ConfigKeys.customGeneratorUrls: ConfigDefaults.customGeneratorUrls,
};
final encoder = JsonEncoder.withIndent(' ');
final beautifiedJson = encoder.convert(defaultConfig);
await configFile.writeAsString(beautifiedJson);
return defaultConfig;
}
}

void _logOutput(String message) {
stdout.writeln(message);
print(message);
}

/// Constructs the default OpenAPI Generator JAR file download URL based on the version
String constructJarUrl(String version) {
return '$baseDownloadUrl/$version/openapi-generator-cli-$version.jar';
}

/// Downloads a JAR file to the specified output path if it doesn't already exist
Future<void> downloadJar(String url, String outputPath) async {
outputPath = resolvePath(outputPath);
final file = File(outputPath);
if (!await file.exists()) {
_logOutput('Downloading $url...');

final request = http.Request('GET', Uri.parse(url));
final response = await request.send();

var binPath = (await Isolate.resolvePackageUri(
Uri.parse('package:openapi_generator_cli/openapi-generator.jar')))!
.toFilePath(windows: Platform.isWindows);
var javaOpts = Platform.environment['JAVA_OPTS'] ?? '';
if (response.statusCode == 200) {
final contentLength = response.contentLength ?? 0;
final output = file.openWrite();
var downloadedBytes = 0;

var commands = [
'-jar',
"${"$binPath"}",
// Listen to the stream and write to the file in smaller chunks
await response.stream.listen(
(chunk) {
downloadedBytes += chunk.length;
output.add(chunk);

// Display progress if content length is known
if (contentLength != 0) {
final progress = (downloadedBytes / contentLength) * 100;
stdout.write('\rProgress: ${progress.toStringAsFixed(2)}%');
}
},
onDone: () async {
await output.close();
print('\nDownloaded to $outputPath\n');
},
onError: (e) {
print('\nDownload failed: $e\n');
},
cancelOnError: true,
).asFuture();
} else {
throw Exception(
'Failed to download $url. Status code: ${response.statusCode}');
}
} else {
print('[info] $outputPath found. No need to download');
}
}

/// Executes the OpenAPI Generator using all JARs in the classpath
Future<void> executeWithClasspath(
List<String> jarPaths, List<String> arguments) async {
final javaOpts = Platform.environment['JAVA_OPTS'] ?? '';
final classpath = jarPaths.join(Platform.isWindows ? ';' : ':');
final commands = [
'-cp',
classpath,
'org.openapitools.codegen.OpenAPIGenerator',
...arguments,
];

if (javaOpts.isNotEmpty) {
commands.insert(0, javaOpts);
}
await Process.run('java', commands).then((ProcessResult pr) {
print(pr.exitCode);
print(pr.stdout);
print(pr.stderr);
});

final result = await Process.run('java', commands);
print(result.stdout);
print(result.stderr);
}

/// Main function handling config loading, JAR downloading, and command execution
Future<void> main(List<String> arguments) async {
exitCode = 0; // presume success

// Determine config path from arguments or default to 'openapi_generator_config.json'
final configArgIndex = arguments.indexOf('--config');
final configFilePath =
(configArgIndex != -1 && configArgIndex + 1 < arguments.length)
? arguments[configArgIndex + 1]
: 'openapi_generator_config.json';

print('Using config file: $configFilePath');

final config = await loadOrCreateConfig(configFilePath);
final String version = (config[ConfigKeys.openapiGeneratorVersion] ??
ConfigDefaults.openapiGeneratorVersion);
final String additionalCommands = config[ConfigKeys.additionalCommands] ??
ConfigDefaults.additionalCommands;
final String? overrideUrl = config[ConfigKeys.downloadUrlOverride];
final cachePath = resolvePath(
config[ConfigKeys.jarCachePath] ?? ConfigDefaults.jarCacheDir);

final customGeneratorUrls = List<String>.from(
config[ConfigKeys.customGeneratorUrls] ??
ConfigDefaults.customGeneratorUrls);
try {
// Load or create configuration

// Ensure the cache directory exists
await Directory(cachePath).create(recursive: true);

// Define paths for the OpenAPI Generator JAR and custom generator JARs
final openapiJarPath = '$cachePath/openapi-generator-cli-$version.jar';
final customJarPaths = <String>[];

// Download the OpenAPI Generator JAR if it doesn't exist
await downloadJar(overrideUrl ?? constructJarUrl(version), openapiJarPath);

// Download each custom generator JAR if it doesn't exist and store in `customJarPaths`
for (var i = 0; i < customGeneratorUrls.length; i++) {
final customJarUrl = customGeneratorUrls[i];
final originalFileName = customJarUrl.split('/').last;
final customJarPath = '$cachePath/custom-$originalFileName';
await downloadJar(customJarUrl, customJarPath);
customJarPaths.add(customJarPath);
}

// Combine all JAR paths (OpenAPI Generator + custom generators) for the classpath
final jarPaths = [openapiJarPath, ...customJarPaths];

// Prepare additional arguments, excluding the --config flag and its value
final filteredArguments = <String>[
...additionalCommands.split(' '),
...arguments.where((arg) => arg != '--config' && arg != configFilePath),
];

// Execute using all JARs in the classpath
await executeWithClasspath(
jarPaths,
filteredArguments
.map(
(e) => e.trim(),
)
.where(
(element) => element.isNotEmpty,
)
.toList());
} catch (e) {
_logOutput('Error: $e');
exitCode = 1;
}
}
Binary file removed openapi-generator-cli/lib/openapi-generator.jar
Binary file not shown.
Loading

0 comments on commit a0f3ca2

Please sign in to comment.