build: generate_template script + workflow (#11)

This commit is contained in:
Felix Angelov 2022-04-08 17:05:06 -05:00 committed by GitHub
parent 6791e7e613
commit b6e0a6e7c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 435 additions and 9 deletions

View File

@ -0,0 +1,44 @@
name: generate_template
on:
push:
paths:
- tool/generator/**
- app/**
branches:
- main
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: dart-lang/setup-dart@v1
- name: Install Dependencies
working-directory: tool/generator
run: dart pub get
- name: Generate Template
run: dart ./tool/generator/main.dart
- name: Config Git User
run: |
git config user.name VGV Bot
git config user.email vgvbot@users.noreply.github.com
- name: Create Pull Request
uses: peter-evans/create-pull-request@v3.6.0
with:
base: main
branch: chore/generate-template
commit-message: "chore: generate template"
title: "chore: generate template"
body: Please squash and merge me!
labels: bot
author: VGV Bot <vgvbot@users.noreply.github.com>
assignees: vgvbot
reviewers: felangel
committer: VGV Bot <vgvbot@users.noreply.github.com>

3
analysis_options.yaml Normal file
View File

@ -0,0 +1,3 @@
analyzer:
exclude:
- brick/**

53
brick/brick.yaml Normal file
View File

@ -0,0 +1,53 @@
name: very_good_flutter_plugin
description: A very good federated Flutter plugin.
version: 0.1.0+1
environment:
mason: ">=0.1.0-dev <0.1.0"
vars:
project_name:
type: string
description: The name of the flutter plugin
default: my_plugin
prompt: What is the name of the plugin?
description:
type: string
description: A short description of the plugin
default: A very good plugin
prompt: Please enter the plugin description.
org_name:
type: string
description: The organization name
default: com.example.verygood.plugin
prompt: What is the organization name?
android:
type: boolean
description: Whether the plugin will support Android
default: true
prompt: Do you want to include Android support?
ios:
type: boolean
description: Whether the plugin will support iOS
default: true
prompt: Do you want to include iOS support?
web:
type: boolean
description: Whether the plugin will support Web
default: true
prompt: Do you want to include Web support?
linux:
type: boolean
description: Whether the plugin will support Linux
default: true
prompt: Do you want to include Linux support?
macos:
type: boolean
description: Whether the plugin will support MacOS
default: true
prompt: Do you want to include MacOS support?
windows:
type: boolean
description: Whether the plugin will support Windows
default: true
prompt: Do you want to include Windows support?

48
src/.gitignore vendored Normal file
View File

@ -0,0 +1,48 @@
.DS_Store
.atom/
.idea/
.vscode/
.packages
.pub/
.dart_tool/
pubspec.lock
flutter_export_environment.sh
coverage/
Podfile.lock
Pods/
.symlinks/
**/Flutter/App.framework/
**/Flutter/ephemeral/
**/Flutter/Flutter.podspec
**/Flutter/Flutter.framework/
**/Flutter/Generated.xcconfig
**/Flutter/flutter_assets/
ServiceDefinitions.json
xcuserdata/
**/DerivedData/
local.properties
keystore.properties
.gradle/
gradlew
gradlew.bat
gradle-wrapper.jar
.flutter-plugins-dependencies
*.iml
generated_plugin_registrant.cc
generated_plugin_registrant.h
generated_plugin_registrant.dart
GeneratedPluginRegistrant.java
GeneratedPluginRegistrant.h
GeneratedPluginRegistrant.m
GeneratedPluginRegistrant.swift
build/
.flutter-plugins
.project
.classpath
.settings

21
src/LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Very Good Ventures
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

26
src/README.md Normal file
View File

@ -0,0 +1,26 @@
# my_plugin
[![Very Good Ventures][logo_white]][very_good_ventures_link_dark]
[![Very Good Ventures][logo_black]][very_good_ventures_link_light]
Developed with 💙 by [Very Good Ventures][very_good_ventures_link] 🦄
![coverage][coverage_badge]
[![style: very good analysis][very_good_analysis_badge]][very_good_analysis_link]
[![License: MIT][license_badge]][license_link]
A Very Good Flutter Federated Plugin created by the [Very Good Ventures Team][very_good_ventures_link].
Generated by the [Very Good CLI][very_good_cli_link] 🤖
[coverage_badge]: app/coverage_badge.svg
[license_badge]: https://img.shields.io/badge/license-MIT-blue.svg
[license_link]: https://opensource.org/licenses/MIT
[logo_black]: https://raw.githubusercontent.com/VGVentures/very_good_brand/main/styles/README/vgv_logo_black.png#gh-light-mode-only
[logo_white]: https://raw.githubusercontent.com/VGVentures/very_good_brand/main/styles/README/vgv_logo_white.png#gh-dark-mode-only
[very_good_analysis_badge]: https://img.shields.io/badge/style-very_good_analysis-B22C89.svg
[very_good_analysis_link]: https://pub.dev/packages/very_good_analysis
[very_good_cli_link]: https://github.com/VeryGoodOpenSource/very_good_cli
[very_good_ventures_link]: https://verygood.ventures/?utm_source=github&utm_medium=banner&utm_campaign=core
[very_good_ventures_link_dark]: https://verygood.ventures/?utm_source=github&utm_medium=banner&utm_campaign=core#gh-dark-mode-only
[very_good_ventures_link_light]: https://verygood.ventures/?utm_source=github&utm_medium=banner&utm_campaign=core#gh-light-mode-only

View File

@ -43,7 +43,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "dev.flutter.plugins.example"
applicationId "com.example.my_plugin.example"
minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()

View File

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="dev.flutter.plugins.example">
package="com.example.my_plugin.example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->

View File

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="dev.flutter.plugins.example">
package="com.example.my_plugin.example">
<application
android:label="example"
android:name="${applicationName}"

View File

@ -1,4 +1,4 @@
package dev.flutter.plugins.example
package com.example.my_plugin.example
import io.flutter.embedding.android.FlutterActivity

View File

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="dev.flutter.plugins.example">
package="com.example.my_plugin">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->

View File

@ -363,7 +363,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.example;
PRODUCT_BUNDLE_IDENTIFIER = com.example.my_plugin;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@ -492,7 +492,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.example;
PRODUCT_BUNDLE_IDENTIFIER = com.example.my_plugin;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -515,7 +515,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.example;
PRODUCT_BUNDLE_IDENTIFIER = com.example.my_plugin;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;

View File

@ -8,7 +8,7 @@
PRODUCT_NAME = example
// The application's bundle identifier
PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.example
PRODUCT_BUNDLE_IDENTIFIER = com.example.my_plugin
// The copyright displayed in application information
PRODUCT_COPYRIGHT = Copyright © 2022 dev.flutter.plugins. All rights reserved.

222
tool/generator/main.dart Normal file
View File

@ -0,0 +1,222 @@
import 'dart:io';
import 'package:path/path.dart' as path;
final _githubPath = path.join('.github');
final _sourcePath = path.join('src');
final _targetPath = path.join('brick', '__brick__');
final _androidPath = path.join(_targetPath, 'my_plugin_android', 'android');
final _androidKotlinPath = path.join(_androidPath, 'src', 'main', 'kotlin');
final _sourceMyPluginKtPath = path.join(
_androidKotlinPath,
'com',
'example',
'my_plugin',
'MyPluginPlugin.kt',
);
final _targetMyPluginKtPath = path.join(
_androidKotlinPath,
'{{#pathCase}}{{org_name}}{{/pathCase}}',
'{{#pascalCase}}{{project_name}}{{/pascalCase}}Plugin.kt',
);
final year = DateTime.now().year;
final copyrightHeader = '''
// Copyright (c) $year, Very Good Ventures
// https://verygood.ventures
//
// Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT.
''';
final excludedFiles = [
path.join(
_targetPath,
'.github',
'workflows',
'generate_template.yaml',
),
path.join(_targetPath, '.github', 'CODEOWNERS'),
];
void main() async {
// Remove Previously Generated Files
final targetDir = Directory(_targetPath);
if (targetDir.existsSync()) {
await targetDir.delete(recursive: true);
}
// Copy Project Files
await Future.wait([
Shell.cp(_sourcePath, _targetPath),
Shell.cp(_githubPath, path.join(_targetPath)),
() async {
await Shell.mkdir(File(_targetMyPluginKtPath).parent.path);
await Shell.cp(_sourceMyPluginKtPath, _targetMyPluginKtPath);
await Shell.rm(File(_sourceMyPluginKtPath).parent.parent.path);
}()
]);
// Remove excluded files
await Future.wait(
excludedFiles.map((file) => File(file).delete(recursive: true)),
);
await Future.wait(
Directory(_targetPath)
.listSync(recursive: true)
.whereType<File>()
.map((_) async {
var file = _;
if (!file.existsSync()) return;
// Add copyright header to all .dart files
if (path.extension(file.path) == '.dart') {
final contents = await file.readAsString();
file = await file.writeAsString('$copyrightHeader\n$contents');
}
// Template File Contents
final contents =
file.isAsset() ? await file.readAsBytes() : await file.readAsString();
final templatedContents = (contents is String)
? contents
.replaceAll(
'com.example.my_plugin',
'{{#dotCase}}{{org_name}}{{/dotCase}}',
)
.replaceAll(
'my_plugin',
'{{#snakeCase}}{{project_name}}{{/snakeCase}}',
)
.replaceAll(
'my-plugin',
'{{#paramCase}}{{project_name}}{{/paramCase}}',
)
.replaceAll(
'MyPlugin',
'{{#pascalCase}}{{project_name}}{{/pascalCase}}',
)
.replaceAll(
'A very good Flutter federated plugin',
'{{{description}}}',
)
: contents;
file = templatedContents is String
? await file.writeAsString(templatedContents)
: await file.writeAsBytes(templatedContents as List<int>);
/// Template file paths
final fileSegments = file.path.split('/').sublist(2);
if (fileSegments
.any((e) => e.contains('my_plugin') || e.contains('MyPlugin'))) {
final newSegments = fileSegments.map((e) {
return e
.replaceAll(
'MyPlugin',
'{{#pascalCase}}{{project_name}}{{/pascalCase}}',
)
.replaceAll(
'my_plugin',
'{{#snakeCase}}{{project_name}}{{/snakeCase}}',
);
});
final newPathSegment = newSegments.join('/');
final newPath = path.join(_targetPath, newPathSegment);
final newFile = File(newPath)..createSync(recursive: true);
templatedContents is String
? newFile.writeAsStringSync(templatedContents)
: newFile.writeAsBytesSync(templatedContents as List<int>);
file = newFile;
}
}),
);
// Clean up top-level directories
const topLevelDirs = [
'my_plugin',
'my_plugin_android',
'my_plugin_ios',
'my_plugin_linux',
'my_plugin_macos',
'my_plugin_platform_interface',
'my_plugin_web',
'my_plugin_windows',
];
for (final dir in topLevelDirs) {
Directory(path.join(_targetPath, dir)).deleteSync(recursive: true);
}
}
class Shell {
static Future<void> cp(String source, String destination) {
return _Cmd.run('cp', ['-rf', source, destination]);
}
static Future<void> rm(String source) {
return _Cmd.run('rm', ['-rf', source]);
}
static Future<void> mkdir(String destination) {
return _Cmd.run('mkdir', ['-p', destination]);
}
}
class _Cmd {
static Future<ProcessResult> run(
String cmd,
List<String> args, {
bool throwOnError = true,
String? processWorkingDir,
}) async {
final result = await Process.run(cmd, args,
workingDirectory: processWorkingDir, runInShell: true);
if (throwOnError) {
_throwIfProcessFailed(result, cmd, args);
}
return result;
}
static void _throwIfProcessFailed(
ProcessResult pr,
String process,
List<String> args,
) {
if (pr.exitCode != 0) {
final values = {
'Standard out': pr.stdout.toString().trim(),
'Standard error': pr.stderr.toString().trim()
}..removeWhere((k, v) => v.isEmpty);
String message;
if (values.isEmpty) {
message = 'Unknown error';
} else if (values.length == 1) {
message = values.values.single;
} else {
message = values.entries.map((e) => '${e.key}\n${e.value}').join('\n');
}
throw ProcessException(process, args, message, pr.exitCode);
}
}
}
extension on File {
bool isAsset() {
const extensions = {
'.png',
'.ico',
'.svg',
'.jpg',
'.jpeg',
'.mov',
'.mp4',
'mp3',
'.wav',
'.ttf'
};
final ext = path.extension(this.path);
return extensions.contains(ext);
}
}

View File

@ -0,0 +1,9 @@
name: generator
description: A template generator for Very Good Flutter Plugin.
publish_to: none
environment:
sdk: ">=2.12.0 <3.0.0"
dependencies:
path: ^1.8.0