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) Add Option to Select Programming Language (Kotlin or Java) for Android Plugins #122

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ As of the `0.8.0` release, example apps for testing are included when initializi
--author <author> ......... Author name and email (e.g. "Name <[email protected]>")
--license <id> ............ SPDX License ID (e.g. "MIT")
--description <text> ...... Short description of plugin features
--android-lang <text> ..... Language for Android plugin development (e.g. "kotlin" or "java")
```
13 changes: 13 additions & 0 deletions assets/plugin-template/README.md.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,21 @@

## Install

To use npm

```bash
npm install {{{ PACKAGE_NAME }}}
````

To use yarn

```bash
yarn add {{{ PACKAGE_NAME }}}
```

Sync native files

```bash
npx cap sync
```

Expand Down
15 changes: 15 additions & 0 deletions assets/plugin-template/android/build.gradle.mustache
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,31 @@ ext {
androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.0'
androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1'
androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1'
{{ #KOTLIN }}
androidxCoreKTXVersion = project.hasProperty('androidxCoreKTXVersion') ? rootProject.ext.androidxCoreKTXVersion : '1.15.0'
{{ /KOTLIN }}
}

buildscript {
{{ #KOTLIN }}
ext.kotlin_version = project.hasProperty("kotlin_version") ? rootProject.ext.kotlin_version : '1.9.25'
{{ /KOTLIN }}
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:8.7.2'
{{ #KOTLIN }}
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
{{ /KOTLIN }}
}
}

apply plugin: 'com.android.library'
{{ #KOTLIN }}
apply plugin: 'kotlin-android'
{{ /KOTLIN }}

android {
namespace "{{ PACKAGE_ID }}"
Expand Down Expand Up @@ -52,6 +64,9 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':capacitor-android')
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
{{ #KOTLIN }}
implementation "androidx.core:core-ktx:$androidxCoreKTXVersion"
{{ /KOTLIN }}
testImplementation "junit:junit:$junitVersion"
androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion"
androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package {{ PACKAGE_ID }}

import android.util.Log

class {{ CLASS }} {

fun echo(value: String?): String? {
Log.i("Echo", value ?: "null")

return value
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package {{ PACKAGE_ID }}

import com.getcapacitor.JSObject
import com.getcapacitor.Plugin
import com.getcapacitor.PluginCall
import com.getcapacitor.annotation.CapacitorPlugin
import com.getcapacitor.PluginMethod

@CapacitorPlugin(name = "{{ CLASS }}")
class {{ CLASS }}Plugin : Plugin() {

private val implementation = {{ CLASS }}()

@PluginMethod
fun echo(call: PluginCall) {
val value = call.getString("value")

val ret = JSObject().apply {
put("value", implementation.echo(value))
}
call.resolve(ret)
}
}
1 change: 1 addition & 0 deletions src/help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const help = `
--author <author> ......... Author name and email (e.g. "Name <[email protected]>")
--license <id> ............ SPDX License ID (e.g. "MIT")
--description <text> ...... Short description of plugin features
--android-lang ............ Language for Android plugin development (e.g. "kotlin" or "java")

-h, --help ................ Print help, then quit
--verbose ................. Print verbose output to stderr
Expand Down
3 changes: 3 additions & 0 deletions src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface OptionValues {
author: string;
license: string;
description: string;
'android-lang': string;
}

export type Validators = {
Expand Down Expand Up @@ -57,6 +58,8 @@ export const VALIDATORS: Validators = {
typeof value !== 'string' || value.trim().length === 0 ? `Must provide a valid license, e.g. "MIT"` : true,
description: (value) =>
typeof value !== 'string' || value.trim().length === 0 ? `Must provide a description` : true,
'android-lang': (value) =>
typeof value !== 'string' || value.trim().length === 0 ? `Must provide a language, e.g. "kotlin"` : true,
dir: (value) =>
typeof value !== 'string' || value.trim().length === 0
? `Must provide a directory, e.g. "my-plugin"`
Expand Down
9 changes: 9 additions & 0 deletions src/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ export const gatherDetails = (initialOptions: Options): Promise<OptionValues> =>
message: `Enter a SPDX license identifier for your plugin.\n`,
validate: VALIDATORS.license,
},
{
type: 'select',
name: 'android-lang',
message: `What language would you like to use for your Android plugin?\n`,
choices: [
{ title: 'Kotlin', value: 'kotlin' },
{ title: 'Java', value: 'java' }
]
},
{
type: 'text',
name: 'description',
Expand Down
51 changes: 43 additions & 8 deletions src/template.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { readFile, rmdir, mkdir, writeFile, unlink } from 'fs/promises';
import { readFile, rmdir, mkdir, writeFile, unlink, readdir, rm } from 'fs/promises';
import Mustache from 'mustache';
import { dirname, join, resolve, sep } from 'path';
import tar from 'tar';
Expand All @@ -25,6 +25,7 @@ export const extractTemplate = async (
): Promise<void> => {
const templateFiles: string[] = [];
const templateFolders: string[] = [];
const androidLang = details['android-lang'].toLowerCase();
await mkdir(dir, { recursive: true });
await tar.extract({
file: type === 'PLUGIN_TEMPLATE' ? TEMPLATE_PATH : WWW_TEMPLATE_PATH,
Expand All @@ -41,14 +42,44 @@ export const extractTemplate = async (
});

await Promise.all(templateFiles.map((p) => resolve(dir, p)).map((p) => applyTemplate(p, details)));
await Promise.all(templateFolders.map((p) => resolve(dir, p)).map((p) => rmdir(p)));
await deleteUnnecessaryFolders(dir, androidLang);
await Promise.all(templateFolders.map((p) => resolve(dir, p)).map((p) => rm(p, { recursive: true })));
};

const deleteUnnecessaryFolders = async (dir: string, androidLang: string): Promise<void> => {
const androidFolder = join(dir, 'android', 'src', 'main');
const javaFolder = join(androidFolder, 'java');
const kotlinFolder = join(androidFolder, 'kotlin');

if (androidLang === 'kotlin' && await folderExists(javaFolder)) {
await rm(javaFolder, { recursive: true });
}

if (androidLang === 'java' && await folderExists(kotlinFolder)) {
await rm(kotlinFolder, { recursive: true });
}
};

const folderExists = async (folderPath: string): Promise<boolean> => {
try {
const files = await readdir(folderPath);
return files != null;
} catch (err) {
return false;
}
};

export const applyTemplate = async (
p: string,
{ name, 'package-id': packageId, 'class-name': className, repo, author, license, description }: OptionValues,
{ name, 'package-id': packageId, 'class-name': className, repo, author, license, description, 'android-lang': androidLang }: OptionValues,
): Promise<void> => {
const template = await readFile(p, { encoding: 'utf8' });

const conditionalView = {
KOTLIN: androidLang.toLowerCase() === 'kotlin', // Set KOTLIN flag
// Add more flags...
}

const view = {
CAPACITOR_VERSION: CAPACITOR_VERSION,
PACKAGE_NAME: name,
Expand All @@ -60,17 +91,21 @@ export const applyTemplate = async (
AUTHOR: author,
LICENSE: license,
DESCRIPTION: description,
ANDROID_LANG: androidLang,
};

const contents = Mustache.render(template, view);
const filePath = Object.entries(view).reduce(
(acc, [key, value]) => (value ? acc.replaceAll(`__${key}__`, value) : acc),
p.substring(0, p.length - MUSTACHE_EXTENSION.length),
const combinedView = { ...view, ...conditionalView };
const intermediateContents = Mustache.render(template, combinedView);
const finalContents = Mustache.render(intermediateContents, view);
let filePath = p.substring(0, p.length - MUSTACHE_EXTENSION.length);
filePath = Object.entries(view).reduce(
(acc, [key, value]) => (value ? acc.replaceAll(`__${key}__`, value.toString()) : acc),
filePath,
);

await mkdir(dirname(filePath), { recursive: true });
// take off the .mustache extension and write the file, then remove the template
await writeFile(filePath, contents, { encoding: 'utf8' });
await writeFile(filePath, finalContents, { encoding: 'utf8' });

await unlink(p);
};
Expand Down