-
-
Notifications
You must be signed in to change notification settings - Fork 5
ADFA-4857 | Add project-to-template plugin #56
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
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| *.iml | ||
| .gradle/ | ||
| /local.properties | ||
| .idea/ | ||
| .androidide/ | ||
| .DS_Store | ||
| /build | ||
| /captures | ||
| .externalNativeBuild | ||
| .cxx | ||
| local.properties | ||
| release.properties | ||
| *.jks | ||
| *.keystore | ||
| *.p12 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,104 @@ | ||
| # Project to Template | ||
|
|
||
| A [Code On The Go](https://github.com/appdevforall/CodeOnTheGo) plugin (`.cgp`) | ||
| that converts the Android project currently open in the IDE into a Code On The | ||
| Go (`.cgt`) template bundle — driven entirely from a tab inside the IDE, with an | ||
| option to install the result straight into the New Project template picker. The | ||
| original project directory is never modified: the plugin copies it into a new | ||
| bundle directory next to it and templatizes the copy. | ||
|
|
||
| This started as a `templatize_project.py` desktop script, was ported to a | ||
| standalone Kotlin/Compose Android app, and is now a Code On The Go plugin so it | ||
| runs inside the IDE with no separate app to install. | ||
|
|
||
| ## UI | ||
|
|
||
| The plugin adds a **Project to Template** item to the IDE's sidebar (under | ||
| "tools"), which opens a tab with: | ||
|
|
||
| - **Project** — read-only, always the project currently open in the IDE | ||
| (via `IdeProjectService.getCurrentProject()`). If no project is open, | ||
| conversion is disabled until one is. | ||
| - **Template name** — written into the generated `template.json`'s `name` | ||
| field, and used as the template subdirectory / `templates.json` `path` entry. | ||
| - **Dry run** — preview the substitutions against a disposable copy without | ||
| writing or deleting anything. | ||
| - **Skip cleanup** — skip `build/` and keystore removal. | ||
|
|
||
| **Convert to Template** stays disabled until a project is open and a template | ||
| name is entered. Tapping it runs the pipeline on a background thread and streams | ||
| a live log (`[OK]` / `[SKIP]` / `[REMOVED]` / `[REVIEW]`), followed by a summary | ||
| with the output paths. On a real (non-dry-run) conversion, an **Install | ||
| Template** button then appears below the log — so you can review the log first — | ||
| and tapping it registers the `.cgt` directly with the IDE via | ||
| `IdeTemplateService`, so it shows up immediately in the New Project template | ||
| picker. | ||
|
|
||
| ## What the conversion does | ||
|
|
||
| For each run, it: | ||
|
|
||
| 1. Creates a new output directory (`<project-dir>-cgt`, next to the project). | ||
| 2. Copies the project into a subdirectory named after the template name, | ||
| skipping `.git`, `.gradle`, `.cg`, `.idea`, `.claude`, `.androidide`, and | ||
| `release.properties`. | ||
| 3. Templatizes the copy: replaces concrete values (Gradle/AGP/Kotlin versions, | ||
| package name, app name, SDK levels, Java compatibility levels) with Pebble | ||
| tokens (`${{ TOKEN }}`), saving each modified file with a `.peb` suffix; | ||
| removes `build/` directories; deletes keystore files (`*.jks`, `*.keystore`, | ||
| `*.p12`); and flags files that may contain machine-specific or personal | ||
| information (`local.properties`, `google-services.json`, `key.properties`, | ||
| `GoogleService-Info.plist`) for manual review. | ||
| 4. Writes a `templates.json` at the top of the output directory. | ||
| 5. Adds a `template/` directory containing `template.json` (the parameter/ | ||
| metadata schema) and a placeholder `thumb.png` (replace with a real | ||
| thumbnail before shipping). | ||
| 6. Zips the output directory into a `<template-name>.cgt` file next to it. | ||
| 7. If the user opts in, registers that `.cgt` with the IDE via | ||
| `IdeTemplateService.registerTemplate(...)`. | ||
|
|
||
| Both Kotlin DSL (`build.gradle.kts`) and Groovy DSL (`build.gradle`) projects | ||
| are supported. Assumes the app module is named `app` (falls back to the first | ||
| module). | ||
|
|
||
| ## Pebble whitespace quirk | ||
|
|
||
| A line/segment that ends with a Pebble token must be followed by at least one | ||
| character of whitespace, or the parser eats the next character (often a newline, | ||
| quote, semicolon, or dot). The conversion inserts a sacrificial space after | ||
| every inserted token — after the closing quote when the token is immediately | ||
| followed by one, so the quote itself isn't eaten. | ||
|
|
||
| ## Project layout | ||
|
|
||
| - `Templatizer.kt` — the substitution pipeline, pure `java.io.File` logic with | ||
| no dependency on the plugin API (easy to reason about and test in isolation), | ||
| plus the `.cgt` bundle writer/zipper. | ||
| - `ProjectToTemplatePlugin.kt` — the `IPlugin` entry point: registers the | ||
| sidebar item and the main editor tab, and provides in-IDE tooltip help. | ||
| - `fragments/ProjectToTemplateFragment.kt` — the tab's UI: the input form, | ||
| background execution, live log, and the install-template flow via | ||
| `IdeTemplateService`. | ||
| - `src/main/assets/docs/index.html` — the offline Tier 3 help page. | ||
|
|
||
| This plugin uses the shared repo-root `../libs/plugin-api.jar` + | ||
| `../libs/gradle-plugin.jar` and the repo-root `../gradlew` — it does not bundle | ||
| its own copies. See the repo `CLAUDE.md` for the monorepo conventions. | ||
|
|
||
| ## Building | ||
|
|
||
| From this directory, using the repo-root Gradle wrapper: | ||
|
|
||
| ``` | ||
| ../gradlew assemblePlugin # release .cgp -> build/plugin/project-to-template.cgp | ||
| ../gradlew assemblePluginDebug # debug .cgp -> build/plugin/project-to-template-debug.cgp | ||
| ``` | ||
|
|
||
| Install the resulting `.cgp` through Code On The Go's Plugin Manager. A | ||
| `local.properties` with `sdk.dir=<Android SDK path>` is required to build. | ||
|
|
||
| ## Next steps | ||
|
|
||
| After running a conversion, inspect the generated `.peb` files in the output | ||
| bundle to confirm the substitutions and spacing look right, and replace the | ||
| placeholder `thumb.png` before distributing the `.cgt` file. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| import org.jetbrains.kotlin.gradle.dsl.JvmTarget | ||
|
|
||
| plugins { | ||
| id("com.android.application") | ||
| id("org.jetbrains.kotlin.android") | ||
| id("com.itsaky.androidide.plugins.build") | ||
| } | ||
|
|
||
| pluginBuilder { | ||
| pluginName = "project-to-template" | ||
| } | ||
|
|
||
| android { | ||
| namespace = "org.appdevforall.projecttotemplate" | ||
| compileSdk = 34 | ||
|
|
||
| defaultConfig { | ||
| applicationId = "org.appdevforall.projecttotemplate" | ||
| minSdk = 26 | ||
| targetSdk = 34 | ||
| versionCode = 1 | ||
| versionName = "1.0.0" | ||
| } | ||
|
|
||
| buildTypes { | ||
| release { | ||
| isMinifyEnabled = false | ||
| proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") | ||
| } | ||
| } | ||
|
|
||
| compileOptions { | ||
| sourceCompatibility = JavaVersion.VERSION_17 | ||
| targetCompatibility = JavaVersion.VERSION_17 | ||
| } | ||
|
|
||
| packaging { | ||
| resources { | ||
| excludes += setOf( | ||
| "META-INF/versions/9/OSGI-INF/MANIFEST.MF", | ||
| "META-INF/DEPENDENCIES", | ||
| "META-INF/LICENSE", | ||
| "META-INF/LICENSE.txt", | ||
| "META-INF/NOTICE", | ||
| "META-INF/NOTICE.txt" | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| testOptions { | ||
| unitTests.isReturnDefaultValues = true | ||
| } | ||
| } | ||
|
|
||
| kotlin { | ||
| compilerOptions { | ||
| jvmTarget.set(JvmTarget.JVM_17) | ||
| } | ||
| } | ||
|
|
||
| dependencies { | ||
| // The plugin-api jar is the canonical contract for plugins. Available | ||
| // at compile time; the IDE provides it at runtime. | ||
| compileOnly(files("../libs/plugin-api.jar")) | ||
|
|
||
| implementation("androidx.appcompat:appcompat:1.6.1") | ||
| implementation("com.google.android.material:material:1.10.0") | ||
| implementation("androidx.fragment:fragment-ktx:1.8.8") | ||
| implementation("androidx.core:core-ktx:1.13.1") | ||
| implementation("org.jetbrains.kotlin:kotlin-stdlib:2.3.0") | ||
| implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1") | ||
|
|
||
| testImplementation("junit:junit:4.13.2") | ||
| } | ||
|
|
||
| // Disable AAR metadata checks that fail under the plugin-builder pipeline. | ||
| tasks.matching { | ||
| it.name.contains("checkDebugAarMetadata") || | ||
| it.name.contains("checkReleaseAarMetadata") | ||
| }.configureEach { | ||
| enabled = false | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| android.useAndroidX=true | ||
| android.nonTransitiveRClass=true | ||
| kotlin.code.style=official | ||
| org.gradle.jvmargs=-Xmx2560m -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 | ||
| org.gradle.caching=true |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import { Resvg } from '@resvg/resvg-js'; | ||
| import { writeFileSync } from 'node:fs'; | ||
|
|
||
| // "Project to Template": a solid source (project) card, an arrow, and a dashed | ||
| // template card. Two separate cards, left-to-right transformation. | ||
| function svg({ tile, card, line, outline, arrow }) { | ||
| return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 192 192" width="192" height="192"> | ||
| <rect x="8" y="8" width="176" height="176" rx="42" fill="${tile}"/> | ||
| <!-- project (source) card: solid, left --> | ||
| <rect x="22" y="56" width="52" height="80" rx="12" fill="${card}"/> | ||
| <rect x="34" y="80" width="28" height="8" rx="4" fill="${line}"/> | ||
| <rect x="34" y="98" width="28" height="8" rx="4" fill="${line}"/> | ||
| <rect x="34" y="116" width="18" height="8" rx="4" fill="${line}"/> | ||
| <!-- arrow: project -> template --> | ||
| <path d="M78,89 L98,89 L98,79 L114,96 L98,113 L98,103 L78,103 Z" fill="${arrow}"/> | ||
| <!-- template (generated) card: dashed outline, right --> | ||
| <rect x="118" y="56" width="52" height="80" rx="12" fill="none" stroke="${outline}" | ||
| stroke-width="6" stroke-linecap="round" stroke-dasharray="15 12"/> | ||
| </svg>`; | ||
| } | ||
|
|
||
| const day = svg({ tile: '#E7ECFB', card: '#3E5488', line: '#E7ECFB', outline: '#7A8EC4', arrow: '#3E5488' }); | ||
| const night = svg({ tile: '#1E2740', card: '#B1C5FF', line: '#1E2740', outline: '#6E82BC', arrow: '#B1C5FF' }); | ||
|
|
||
| for (const [name, s] of [['icon_day', day], ['icon_night', night]]) { | ||
| const png = new Resvg(s, { fitTo: { mode: 'width', value: 192 } }).render().asPng(); | ||
| writeFileSync(new URL(`./${name}.png`, import.meta.url), png); | ||
| writeFileSync(new URL(`./${name}.svg`, import.meta.url), s); | ||
| console.log('wrote', name); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| # Add project specific ProGuard rules here. |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.